ui/motif/Grid.c

Thu, 05 Dec 2024 08:40:37 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Thu, 05 Dec 2024 08:40:37 +0100
branch
newapi
changeset 408
b06e43f1edd4
parent 407
8ea123dd89db
permissions
-rw-r--r--

fix grid colspan/rowspan

/*
 * 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);
}

mercurial