--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/motif/Grid.c Thu Dec 12 20:01:43 2024 +0100 @@ -0,0 +1,582 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * + */ + +#define _GNU_SOURCE +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "Grid.h" + +#include <X11/Xlib.h> + + + +static XtActionsRec actionslist[] = { + {"getfocus",grid_getfocus}, + {"loosefocus",grid_loosefocus}, + {"NULL",NULL} +}; + +//static char defaultTranslations[] = "<BtnDown>: mousedown()\n"; +static char defaultTranslations[] = "\ +<EnterWindow>: getfocus()\n\ +<LeaveWindow>: loosefocus()\n"; + + +///* +static XtResource constraints[] = +{ + { + gridColumn, + gridColumn, + XmRDimension, + sizeof (Dimension), + XtOffsetOf( GridConstraintRec, + grid.x), + XmRImmediate, + (XtPointer) 0 + }, + { + gridRow, + gridRow, + XmRDimension, + sizeof (Dimension), + XtOffsetOf( GridConstraintRec, + grid.y), + XmRImmediate, + (XtPointer) 0 + }, + { + gridColspan, + gridColspan, + XmRDimension, + sizeof (Dimension), + XtOffsetOf( GridConstraintRec, + grid.colspan), + XmRImmediate, + (XtPointer) 0 + }, + { + gridRowspan, + gridRowspan, + XmRDimension, + sizeof (Dimension), + XtOffsetOf( GridConstraintRec, + grid.rowspan), + XmRImmediate, + (XtPointer) 0 + }, + { + gridMarginLeft, + gridMarginLeft, + XmRDimension, + sizeof (Dimension), + XtOffsetOf( GridConstraintRec, + grid.margin_left), + XmRImmediate, + (XtPointer) 0 + }, + { + gridMarginRight, + gridMarginRight, + XmRDimension, + sizeof (Dimension), + XtOffsetOf( GridConstraintRec, + grid.margin_right), + XmRImmediate, + (XtPointer) 0 + }, + { + gridMarginTop, + gridMarginTop, + XmRDimension, + sizeof (Dimension), + XtOffsetOf( GridConstraintRec, + grid.margin_top), + XmRImmediate, + (XtPointer) 0 + }, + { + gridMarginBottom, + gridMarginBottom, + XmRDimension, + sizeof (Dimension), + XtOffsetOf( GridConstraintRec, + grid.margin_bottom), + XmRImmediate, + (XtPointer) 0 + }, + { + gridHExpand, + gridHExpand, + XmRBoolean, + sizeof (Boolean), + XtOffsetOf( GridConstraintRec, + grid.hexpand), + XmRImmediate, + (XtPointer) 0 + }, + { + gridVExpand, + gridVExpand, + XmRBoolean, + sizeof (Boolean), + XtOffsetOf( GridConstraintRec, + grid.vexpand), + XmRImmediate, + (XtPointer) 0 + }, + { + gridHFill, + gridHFill, + XmRBoolean, + sizeof (Boolean), + XtOffsetOf( GridConstraintRec, + grid.hfill), + XmRImmediate, + (XtPointer) 0 + }, + { + gridVFill, + gridVFill, + XmRBoolean, + sizeof (Boolean), + XtOffsetOf( GridConstraintRec, + grid.vfill), + XmRImmediate, + (XtPointer) 0 + } + +}; +//*/ +//static XtResource constraints[] = {}; + +GridClassRec gridClassRec = { + // Core Class + { + //(WidgetClass)&constraintClassRec, // superclass + (WidgetClass)&xmManagerClassRec, + "Grid", // class_name + sizeof(GridRec), // widget_size + grid_class_initialize, // class_initialize + NULL, // class_part_initialize + FALSE, // class_inited + (XtInitProc)grid_initialize, // initialize + NULL, // initialize_hook + grid_realize, // realize + actionslist, // actions + XtNumber(actionslist), // num_actions + NULL, // resources + 0, // num_resources + NULLQUARK, // xrm_class + True, // compress_motion + True, // compress_exposure + True, // compress_enterleave + False, // visible_interest + (XtWidgetProc)grid_destroy, // destroy + (XtWidgetProc)grid_resize, // resize + (XtExposeProc)grid_expose, // expose + grid_set_values, // set_values + NULL, // set_values_hook + XtInheritSetValuesAlmost, // set_values_almost + NULL, // get_values_hook + (XtAcceptFocusProc)grid_acceptfocus, // accept_focus + XtVersion, // version + NULL, // callback_offsets + //NULL, // tm_table + defaultTranslations, + XtInheritQueryGeometry, // query_geometry + NULL, // display_accelerator + NULL, // extension + }, + // Composite Class + { + GridGeometryManager, /* geometry_manager */ + GridChangeManaged, /* change_managed */ + XtInheritInsertChild, /* insert_child */ + XtInheritDeleteChild, /* delete_child */ + NULL, /* extension */ + }, + // Constraint Class + { + constraints, /* resources */ + XtNumber(constraints), /* num_resources */ + sizeof(GridConstraintRec), /* constraint_size */ + grid_constraint_init, /* initialize */ + NULL, /* destroy */ + ConstraintSetValues, /* set_values */ + NULL, /* extension */ + }, + // XmManager Class + ///* + { + NULL, + NULL, + 0, + NULL, + 0, + NULL, + NULL + }, + //*/ + // MyWidget Class + { + 0 + } +}; + +WidgetClass gridClass = (WidgetClass)&gridClassRec; + + +void grid_class_initialize(Widget request, Widget new, ArgList args, Cardinal *num_args) { + +} +void grid_initialize(Widget request, Widget new, ArgList args, Cardinal num_args) { + MyWidget mn = (MyWidget)new; + + mn->mywidget.max_col = 0; + mn->mywidget.max_row = 0; + +} +void grid_realize(MyWidget w,XtValueMask *valueMask,XSetWindowAttributes *attributes) { + XtMakeResizeRequest((Widget)w, 400, 400, NULL, NULL); + (coreClassRec.core_class.realize)((Widget)w, valueMask, attributes); + grid_place_children(w); +} + + +void grid_destroy(MyWidget widget) { + +} +void grid_resize(MyWidget widget) { + grid_place_children(widget); +} + +void grid_expose(MyWidget widget, XEvent *event, Region region) { + +} + + +Boolean grid_set_values(Widget old, Widget request, Widget neww, ArgList args, Cardinal *num_args) { + return False; +} + +Boolean grid_acceptfocus(Widget w, Time *t) { + +} + +void grid_getfocus(MyWidget myw, XEvent *event, String *params, Cardinal *nparam) { + +} + +void grid_loosefocus(MyWidget myw, XEvent *event, String *params, Cardinal *nparam) { + +} + + + +XtGeometryResult GridGeometryManager(Widget widget, XtWidgetGeometry *request, XtWidgetGeometry *reply) { + GridRec *grid = (GridRec*)XtParent(widget); + GridConstraintRec *constraints = widget->core.constraints; + //XtVaSetValues(widget, XmNwidth, request->width, XmNheight, request->height, NULL); + if((request->request_mode & CWWidth) == CWWidth) { + widget->core.width = request->width; + constraints->grid.pref_width = request->width; + } + if((request->request_mode & CWHeight) == CWHeight) { + widget->core.height = request->height; + constraints->grid.pref_height = request->height; + } + grid_place_children((MyWidget)XtParent(widget)); + return XtGeometryYes; +} + +void GridChangeManaged(Widget widget) { + +} + +Boolean ConstraintSetValues(Widget old, Widget request, Widget neww, ArgList args, Cardinal *num_args) { + GridConstraintRec *constraints = neww->core.constraints; + MyWidget grid = (MyWidget)XtParent(neww); + if(constraints->grid.x > grid->mywidget.max_col) { + grid->mywidget.max_col = constraints->grid.x; + } + if(constraints->grid.y > grid->mywidget.max_row) { + grid->mywidget.max_row = constraints->grid.y; + } +} + + +void grid_constraint_init( + Widget request, + Widget neww, + ArgList args, + Cardinal* num_args +) +{ + GridConstraintRec *constraints = neww->core.constraints; + + MyWidget grid = (MyWidget)XtParent(neww); + if(constraints->grid.x > grid->mywidget.max_col) { + grid->mywidget.max_col = constraints->grid.x; + } + if(constraints->grid.y > grid->mywidget.max_row) { + grid->mywidget.max_row = constraints->grid.y; + } + constraints->grid.pref_width = neww->core.width; + constraints->grid.pref_height = neww->core.height; +} + +void grid_place_children(MyWidget w) { + int ncols = w->mywidget.max_col+1; + int nrows = w->mywidget.max_row+1; + GridDef *cols = calloc(ncols, sizeof(GridDef)); + GridDef *rows = calloc(nrows, sizeof(GridDef)); + int num_cols_expanding = 0; + int num_rows_expanding = 0; + int req_width = 0; + int req_height = 0; + + // calculate the minimum size requirements for all columns and rows + // we need to run this 2 times: for widgets without colspan/rowspan first + // and then again for colspan/rowspan > 1 + int span_max = 1; + for(int r=0;r<2;r++) { + for(int i=0;i<w->composite.num_children;i++) { + Widget child = w->composite.children[i]; + GridConstraintRec *constraints = child->core.constraints; + + if(constraints->grid.colspan > span_max || constraints->grid.rowspan > span_max) { + continue; + } + + int x = constraints->grid.x; + int y = constraints->grid.y; + // make sure ncols/nrows is correct + // errors shouldn't happen, unless someone messes up the grid internals + if(x >= ncols) { + fprintf(stderr, "Error: widget x out of bounds\n"); + continue; + } + if(y >= nrows) { + fprintf(stderr, "Error: widget y out of bounds\n"); + continue; + } + GridDef *col = &cols[x]; + GridDef *row = &rows[y]; + + if(constraints->grid.hexpand) { + if(constraints->grid.colspan > 1) { + // check if any column in the span is expanding + // if not, make the last column expanding + GridDef *last_col = col; + for(int c=x;c<ncols;c++) { + last_col = &cols[c]; + if(last_col->expand) { + break; + } + } + last_col->expand = TRUE; + } else { + col->expand = TRUE; + } + } + if(constraints->grid.vexpand) { + if(constraints->grid.rowspan > 1) { + GridDef *last_row = row; + for(int c=x;c<nrows;c++) { + last_row = &rows[c]; + if(last_row->expand) { + break; + } + } + last_row->expand = TRUE; + } else { + row->expand = TRUE; + } + } + + // column size + if(constraints->grid.colspan > 1) { + // check size of all columns in span + Dimension span_width = col->size; + GridDef *last_col = col; + for(int s=x+1;s<ncols;s++) { + last_col = &cols[s]; + span_width = last_col->size; + + } + int diff = constraints->grid.pref_width - span_width; + if(diff > 0) { + last_col->size += diff; + } + } else if(constraints->grid.pref_width > col->size) { + col->size = constraints->grid.pref_width; + } + // row size + if(constraints->grid.rowspan > 1) { + Dimension span_height = row->size; + GridDef *last_row = row; + for(int s=x+1;s<nrows;s++) { + last_row = &rows[s]; + span_height = last_row->size; + + } + int diff = constraints->grid.pref_height - span_height; + if(diff > 0) { + last_row->size += diff; + } + } else if(constraints->grid.pref_height > row->size) { + row->size = constraints->grid.pref_height; + } + } + span_max = 50000; // not sure if this is unreasonable low or high + } + + + for(int i=0;i<ncols;i++) { + if(cols[i].expand) { + num_cols_expanding++; + } + req_width += cols[i].size; + } + for(int i=0;i<nrows;i++) { + if(rows[i].expand) { + num_rows_expanding++; + } + req_height += rows[i].size; + } + + if(req_width > 0 && req_height > 0) { + Widget parent = w->core.parent; + Dimension rwidth = req_width; + Dimension rheight = req_height; + if(rwidth < w->core.width) { + //rwidth = w->core.width; + } + if(rheight < w->core.height) { + //rheight = w->core.height; + } + + if(!w->mywidget.sizerequest) { + Dimension actual_width, actual_height; + w->mywidget.sizerequest = TRUE; + XtMakeResizeRequest((Widget)w, req_width, req_height, &actual_width, &actual_height); + w->mywidget.sizerequest = FALSE; + //printf("size request: %d %d\n", (int)actual_width, (int)actual_height); + } + + + + } + + int hexpand = 0; + int width_diff = (int)w->core.width - req_width; + int hexpand2 = 0; + if(width_diff > 0 && num_cols_expanding > 0) { + hexpand = width_diff / num_cols_expanding; + hexpand2 = width_diff-hexpand*num_cols_expanding; + } + int x = 0; + for(int i=0;i<ncols;i++) { + cols[i].pos = x; + if(cols[i].expand) { + cols[i].size += hexpand + hexpand2; + } + x += cols[i].size; + + hexpand2 = 0; + } + + int vexpand = 0; + int height_diff = (int)w->core.height - req_height; + int vexpand2 = 0; + if(height_diff > 0 && num_rows_expanding > 0) { + vexpand = height_diff / num_rows_expanding; + vexpand2 = height_diff-vexpand*num_rows_expanding; + } + int y = 0; + for(int i=0;i<nrows;i++) { + rows[i].pos = y; + if(rows[i].expand) { + rows[i].size += vexpand + vexpand2; + } + y += rows[i].size; + + vexpand2 = 0; + } + + for(int i=0;i<w->composite.num_children;i++) { + Widget child = w->composite.children[i]; + GridConstraintRec *constraints = child->core.constraints; + GridDef c = cols[constraints->grid.x]; + GridDef r = rows[constraints->grid.y]; + int x = c.pos; + int y = r.pos; + int width = constraints->grid.pref_width; + int height = constraints->grid.pref_height; + if(constraints->grid.hfill) { + if(constraints->grid.colspan > 1) { + Dimension cwidth = 0; + for(int j=0;j<constraints->grid.colspan;j++) { + if(constraints->grid.x+j < ncols) { + cwidth += cols[constraints->grid.x+j].size; + } + } + width = cwidth; + } else { + width = c.size; + } + } + if(constraints->grid.vfill) { + if(constraints->grid.rowspan > 1) { + Dimension cheight = 0; + for(int j=0;j<constraints->grid.rowspan;j++) { + if(constraints->grid.y+j < nrows) { + cheight += rows[constraints->grid.y+j].size; + } + } + height = cheight; + } else { + height = r.size; + } + } + + XtConfigureWidget(child, x, y, width, height, child->core.border_width); + //printf("child %d %d - %d %d\n", (int)child->core.x, (int)child->core.y, (int)child->core.width, (int)child->core.height); + } + + free(cols); + free(rows); +} +