ui/motif/container.c

2 weeks ago

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Wed, 26 Feb 2025 21:14:24 +0100 (2 weeks ago)
changeset 480
7dfd5e546b99
parent 475
8e06a446d552
child 482
5bc95a6228b0
permissions
-rw-r--r--

move ui_customwidget to separate file

/*
 * 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.
 */

#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>

#include "container.h"
#include "../common/context.h"
#include "../common/object.h"

#include <cx/array_list.h>

#include "Grid.h"




/* ---------------------------- Box Container ---------------------------- */

static UIWIDGET box_create(UiObject *obj, UiContainerArgs args, UiBoxOrientation orientation) { 
    UiContainerPrivate *ctn = ui_obj_container(obj);
    UI_APPLY_LAYOUT(ctn->layout, args);
    
    Arg xargs[16];
    int n = 0;
    
    if(orientation == UI_BOX_VERTICAL) {
        //XtSetArg(xargs[n], gridRowSpacing, args.spacing); n++;
    } else {
        //XtSetArg(xargs[n], gridColumnSpacing, args.spacing); n++;
    }
    
    Widget parent = ctn->prepare(ctn, xargs, &n);
    Widget grid = XtCreateManagedWidget(args.name ? args.name : "boxcontainer", gridClass, parent, xargs, n);
    ctn->add(ctn, grid);
    
    UiContainerX *container = ui_box_container(obj, grid, orientation);
    uic_object_push_container(obj, container);
    
    return grid;
}

// public
UIWIDGET ui_vbox_create(UiObject *obj, UiContainerArgs args) {
    return box_create(obj, args, UI_BOX_VERTICAL);
}

// public
UIWIDGET ui_hbox_create(UiObject *obj, UiContainerArgs args) {
    return box_create(obj, args, UI_BOX_HORIZONTAL);
}

UiContainerX* ui_box_container(UiObject *obj, Widget grid, UiBoxOrientation orientation) {
    UiBoxContainer *ctn = ui_malloc(obj->ctx, sizeof(UiBoxContainer));
    memset(ctn, 0, sizeof(UiBoxContainer));
    ctn->container.prepare = orientation == UI_BOX_VERTICAL ? ui_vbox_prepare : ui_hbox_prepare;
    ctn->container.add = ui_box_container_add;
    ctn->container.widget = grid;
    ctn->n = 0;
    return (UiContainerX*)ctn;
}

static Widget ui_box_container_prepare(UiBoxContainer *box, Arg *args, int *n) {
    int a = *n;
    box->n++;
    return box->container.widget;
}

Widget ui_vbox_prepare(UiContainerPrivate *ctn, Arg *args, int *n) {
    UiBoxContainer *box = (UiBoxContainer*)ctn;
    int a = *n;
    XtSetArg(args[a], gridRow, box->n); a++;
    if(box->container.layout.fill == UI_ON) {
        XtSetArg(args[a], gridVExpand, TRUE); a++;
        XtSetArg(args[a], gridVFill, TRUE); a++;
    }
    XtSetArg(args[a], gridHExpand, TRUE); a++;
    XtSetArg(args[a], gridHFill, TRUE); a++;
    *n = a;
    return ui_box_container_prepare(box, args, n);
}

Widget ui_hbox_prepare(UiContainerPrivate *ctn, Arg *args, int *n) {
    UiBoxContainer *box = (UiBoxContainer*)ctn;
    int a = *n;
    XtSetArg(args[a], gridColumn, box->n); a++;
    if(box->container.layout.fill == UI_ON) {
        XtSetArg(args[a], gridHExpand, TRUE); a++;
        XtSetArg(args[a], gridHFill, TRUE); a++;
    }
    XtSetArg(args[a], gridVExpand, TRUE); a++;
    XtSetArg(args[a], gridVFill, TRUE); a++;
    *n = a;
    return ui_box_container_prepare(box, args, n);
}

void ui_box_container_add(UiContainerPrivate *ctn, Widget widget) {
    ui_reset_layout(ctn->layout);
    
}


/* ---------------------------- Grid Container ---------------------------- */

// public
UIWIDGET ui_grid_create(UiObject *obj, UiContainerArgs args) {
    Arg xargs[16];
    int n = 0;
    
    UiContainerPrivate *ctn = ui_obj_container(obj);
    UI_APPLY_LAYOUT(ctn->layout, args);
    
    Widget parent = ctn->prepare(ctn, xargs, &n);
    XtSetArg(xargs[n], gridMargin, args.margin); n++;
    XtSetArg(xargs[n], gridColumnSpacing, args.columnspacing); n++;
    XtSetArg(xargs[n], gridRowSpacing, args.rowspacing); n++;
    Widget grid = XtCreateManagedWidget(args.name ? args.name : "gridcontainer", gridClass, parent, xargs, n);
    ctn->add(ctn, grid);
    
    UiContainerX *container = ui_grid_container(obj, grid);
    uic_object_push_container(obj, container);
    
    return grid;
}

UiContainerX* ui_grid_container(UiObject *obj, Widget grid) {
    UiGridContainer *ctn = ui_malloc(obj->ctx, sizeof(UiGridContainer));
    memset(ctn, 0, sizeof(UiBoxContainer));
    ctn->container.prepare = ui_grid_container_prepare;
    ctn->container.add = ui_grid_container_add;
    ctn->container.widget = grid;
    ctn->x = 0;
    ctn->y = 0;
    return (UiContainerX*)ctn;
}

Widget ui_grid_container_prepare(UiContainerPrivate *ctn, Arg *args, int *n) {
    UiGridContainer *grid = (UiGridContainer*)ctn;
    if(ctn->layout.newline) {
        grid->y++;
        grid->x = 0;
    }
    
    int a = *n;
    XtSetArg(args[a], gridColumn, grid->x); a++;
    XtSetArg(args[a], gridRow, grid->y); a++;
    if(ctn->layout.colspan > 0) {
        XtSetArg(args[a], gridColspan, ctn->layout.colspan); a++;
    }
    if(ctn->layout.rowspan > 0) {
        XtSetArg(args[a], gridRowspan, ctn->layout.rowspan); a++;
    }
    
    if(grid->container.layout.fill == UI_ON) {
        grid->container.layout.hfill = TRUE;
        grid->container.layout.vfill = TRUE;
        grid->container.layout.hexpand = TRUE;
        grid->container.layout.vexpand = TRUE;
    }
    
    if(grid->container.layout.hfill) {
        XtSetArg(args[a], gridHFill, TRUE); a++;
    }
    if(grid->container.layout.vfill) {
        XtSetArg(args[a], gridVFill, TRUE); a++;
    }
    if(grid->container.layout.hexpand) {
        XtSetArg(args[a], gridHExpand, TRUE); a++;
    }
    if(grid->container.layout.vexpand) {
        XtSetArg(args[a], gridVExpand, TRUE); a++;
    }
    
    *n = a;
    return ctn->widget;
}

void ui_grid_container_add(UiContainerPrivate *ctn, Widget widget) {
    UiGridContainer *grid = (UiGridContainer*)ctn;
    grid->x++;
    ui_reset_layout(ctn->layout);
}


/* -------------------------- TabView Container -------------------------- */

static void ui_tabbar_resize(Widget widget, XtPointer udata, XtPointer cdata) {
    UiMotifTabView *tabview = udata;
    
    if(tabview->tabview == UI_TABVIEW_INVISIBLE) {
        return;
    }
    
    int width = 0;
    int height = 0;
    XtVaGetValues(widget, XmNwidth, &width, XmNheight, &height, NULL);
    int numbuttons = cxListSize(tabview->tabs);
    if(numbuttons == 0) {
        return;
    }
    int button_width = width / numbuttons;
    int x = 0;
    
    CxIterator i = cxListIterator(tabview->tabs);
    cx_foreach(UiTab *, tab, i) {
        if(i.index + 1 == numbuttons) {
            button_width = width - x;
        }
        XtVaSetValues(
                tab->tab_button,
                XmNx, x,
                XmNy, 0,
                XmNwidth,
                button_width,
                
                NULL);
        x += button_width;
    }
    
    if(height <= tabview->height) {
        XtVaSetValues(widget, XmNheight, tabview->height + 4, NULL);
    }
}

static void ui_tabbar_expose(Widget widget, XtPointer udata, XtPointer cdata) {
    UiMotifTabView *tabview = udata;
    XmDrawingAreaCallbackStruct *cbs = (XmDrawingAreaCallbackStruct *)cdata;
    XEvent *event = cbs->event;
    Display *dpy = XtDisplay(widget); 
    
    if(!tabview->gc_initialized) {
        XGCValues gcvals;
        gcvals.foreground = tabview->fg1;
        tabview->gc = XCreateGC(XtDisplay(tabview->tabbar), XtWindow(tabview->tabbar), (GCForeground), &gcvals);
    }
    
    if(tabview->current_tab) {
        Widget tab = tabview->current_tab->tab_button;
        XFillRectangle(dpy, XtWindow(widget), tabview->gc, tab->core.x, tab->core.height, tab->core.width, 4);
    }
}

UIWIDGET ui_tabview_create(UiObject *obj, UiTabViewArgs args) {
    Arg xargs[16];
    int n = 0;
    
    UiContainerPrivate *ctn = ui_obj_container(obj);
    UI_APPLY_LAYOUT(ctn->layout, args);
    
    // create widgets
    // form
    // - tabbar  (Drawing Area)
    // - content (Frame)
    UiMotifTabView *tabview = malloc(sizeof(UiMotifTabView));
    memset(tabview, 0, sizeof(UiMotifTabView));
    char *name = args.name ? (char*)args.name : "tabview";
    XtSetArg(xargs[n], XmNuserData, tabview); n++;
    Widget parent = ctn->prepare(ctn, xargs, &n);
    Widget form = XmCreateForm(parent, name, xargs, n);
    XtManageChild(form);
    
    n = 0;
    XtSetArg(xargs[n], XmNleftAttachment, XmATTACH_FORM); n++;
    XtSetArg(xargs[n], XmNrightAttachment, XmATTACH_FORM); n++;
    XtSetArg(xargs[n], XmNtopAttachment, XmATTACH_FORM); n++;
    XtSetArg(xargs[n], XmNorientation, XmHORIZONTAL); n++;
    XtSetArg(xargs[n], XmNpacking, XmPACK_TIGHT); n++;
    XtSetArg(xargs[n], XmNspacing, 1); n++;
    XtSetArg(xargs[n], XmNmarginWidth, 0); n++;
    XtSetArg(xargs[n], XmNmarginHeight, 0); n++;
    Widget tabbar = XmCreateDrawingArea(form, "ui_test", xargs, n);
    XtManageChild(tabbar);
    XtAddCallback(tabbar, XmNresizeCallback , ui_tabbar_resize, tabview);
    XtAddCallback(tabbar, XmNexposeCallback, ui_tabbar_expose, tabview);
    
    n = 0;
    XtSetArg(xargs[n], XmNleftAttachment, XmATTACH_FORM); n++;
    XtSetArg(xargs[n], XmNrightAttachment, XmATTACH_FORM); n++;
    XtSetArg(xargs[n], XmNbottomAttachment, XmATTACH_FORM); n++;
    XtSetArg(xargs[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
    XtSetArg(xargs[n], XmNtopWidget, tabbar); n++;
    Widget content = XmCreateFrame(form, "tabviewcontent", xargs, n);
    
    // setup tabview object, that holds all relevant objects
    tabview->obj = obj;
    tabview->form = form;
    tabview->tabbar = tabbar;
    tabview->content = content;
    tabview->tabview = args.tabview;
    tabview->subcontainer = args.subcontainer;
    tabview->select = ui_motif_tabview_select;
    tabview->add = ui_motif_tabview_add_tab;
    tabview->remove = ui_motif_tabview_remove;
    tabview->tabs = cxArrayListCreate(obj->ctx->allocator, cx_cmp_ptr, sizeof(UiTab), 8);
    tabview->current_index = -1;
    
    UiTabViewContainer *ct = ui_malloc(obj->ctx, sizeof(UiTabViewContainer));
    ct->container.widget = form;
    ct->container.type = UI_CONTAINER_TABVIEW;
    ct->container.prepare = ui_tabview_container_prepare;
    ct->container.add = ui_tabview_container_add;
    ct->tabview = tabview;
    
    UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args.value, args.varname, UI_VAR_INTEGER);
    if(var) {
        UiInteger *i = var->value;
        i->obj = tabview;
        i->get = ui_tabview_get;
        i->set = ui_tabview_set;
    }
    
    uic_object_push_container(obj, (UiContainerX*)ct);
    
    return form;
}

int64_t ui_tabview_get(UiInteger *i) {
    UiMotifTabView *tabview = i->obj;
    i->value = tabview->current_index;
    return i->value;
}

void ui_tabview_set(UiInteger *i, int64_t value) {
    UiMotifTabView *tabview = i->obj;
    if(value < cxListSize(tabview->tabs)) {
        ui_motif_tabview_select(tabview, value);
        i->value = value;
    }
}

void ui_tab_create(UiObject *obj, const char* title) {
    UiContainerPrivate *ctn = ui_obj_container(obj);
    if(ctn->type != UI_CONTAINER_TABVIEW) {
        fprintf(stderr, "UI Error: container is not a tabview\n");
        return;
    }
    
    UiMotifTabView *tabview = NULL;
    XtVaGetValues(ctn->widget, XmNuserData, &tabview, NULL);
    if(!tabview) {
        fprintf(stderr, "UI Error: no tabview\n");
        return;
    }
    
    
    Widget child = ui_vbox_create(obj, (UiContainerArgs) { 0 });
    if(tabview->current) {
        XtUnmanageChild(child);
    } else {
        tabview->current = child;
    }
    
    tabview->add(tabview, -1, title, child);
}

void ui_tabview_select(UIWIDGET tabview, int tab) {
    UiMotifTabView *tabviewdata = NULL;
    XtVaGetValues(tabview, XmNuserData, &tabviewdata, NULL);
    if(tabviewdata) {
        ui_motif_tabview_select(tabviewdata, tab);
    } else {
        fprintf(stderr, "ui_tabview_select: widget is not a tabview\n");
    }
}

void ui_tabview_remove(UIWIDGET tabview, int tab) {
    UiMotifTabView *tabviewdata = NULL;
    XtVaGetValues(tabview, XmNuserData, &tabviewdata, NULL);
    if(tabviewdata) {
        ui_motif_tabview_remove(tabviewdata, tab);
    } else {
        fprintf(stderr, "ui_tabview_select: widget is not a tabview\n");
    }
}

UiObject* ui_tabview_add(UIWIDGET tabview, const char *name, int tab_index) {
    UiMotifTabView *tabviewdata = NULL;
    XtVaGetValues(tabview, XmNuserData, &tabviewdata, NULL);
    if(tabviewdata) {
        Arg args[16];
        int n = 0;
        
        XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
        XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
        XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
        XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
        XtSetArg(args[n], XmNtopWidget, tabviewdata->tabbar); n++;
        
        Widget grid = XtCreateManagedWidget("vbox", gridClass, tabviewdata->content, args, n);
        
        UiObject *newobj = ui_calloc(tabviewdata->obj->ctx, 1, sizeof(UiObject));
        newobj->ctx = tabviewdata->obj->ctx;
        newobj->widget = grid;
        UiContainerX *container = ui_box_container(newobj, grid, UI_BOX_VERTICAL);
        newobj->container_begin = container;
        newobj->container_end = container;
        return newobj;
    } else {
        fprintf(stderr, "ui_tabview_select: widget is not a tabview\n");
        return NULL;
    }
}

void ui_motif_tabview_select(UiMotifTabView *tabview, int tab) {
    UiTab *t = cxListAt(tabview->tabs, tab);
    if(t) {
        tabview->current_index = tab;
        ui_motif_tabview_change_tab(tabview, t);
    }
}

static void ui_tab_button_callback(Widget widget, UiTab *tab, XtPointer d) {
    UiMotifTabView *tabview = NULL;
    XtVaGetValues(widget, XmNuserData, &tabview, NULL);
    ui_motif_tabview_change_tab(tabview, tab);
}

void ui_motif_tabview_add_tab(UiMotifTabView *tabview, int index, const char *name, Widget child) {
    UiTab tab;
    
    Arg args[16];
    int n = 0;
    
    XmString label = XmStringCreateLocalized((char*)name);
    XtSetArg(args[n], XmNlabelString, label); n++;
    XtSetArg(args[n], XmNshadowThickness, 0); n++;
    XtSetArg(args[n], XmNhighlightThickness, 0); n++;
    XtSetArg(args[n], XmNuserData, tabview); n++;
    
    Widget button = XmCreatePushButton(tabview->tabbar, "tab_button", args, n);
    if(tabview->tabview != UI_TABVIEW_INVISIBLE) {
        XtManageChild(button);
    }
    
    if(tabview->height == 0) {
        Dimension h;
        XtVaGetValues(
                button,
                XmNarmColor,
                &tabview->bg1,
                XmNbackground,
                &tabview->bg2,
                XmNhighlightColor,
                &tabview->fg1,
                XmNheight,
                &h,
                NULL);
        tabview->height = h + 2; // border
        
        XtVaSetValues(tabview->tabbar, XmNbackground, tabview->bg1, NULL);
    }
    
    tab.tab_button = button;
    tab.child = child;
    size_t newtab_index = cxListSize(tabview->tabs);
    cxListAdd(tabview->tabs, &tab);
    UiTab *newtab = cxListAt(tabview->tabs, newtab_index);
    
    XtAddCallback(
            button,
            XmNactivateCallback,
            (XtCallbackProc)ui_tab_button_callback,
            newtab);
    
    if(newtab_index == 0) {
        ui_motif_tabview_change_tab(tabview, newtab);
    } else {
        XtVaSetValues(button, XmNbackground, tabview->bg1, NULL);
    }
}

void ui_motif_tabview_remove(UiMotifTabView *tabview, int index) {
    UiTab *tab = cxListAt(tabview->tabs, index);
    if(tab) {
        if(tab == tabview->current_tab) {
            if(index > 0) {
                ui_motif_tabview_select(tabview, index-1);
            } else {
                if(index < cxListSize(tabview->tabs)) {
                    ui_motif_tabview_select(tabview, index+1);
                } else {
                    tabview->current_tab = NULL;
                    tabview->current_index = -1;
                }
            }
        }
        XtDestroyWidget(tab->tab_button);
        XtDestroyWidget(tab->child);
        cxListRemove(tabview->tabs, index);
    }
}

void ui_motif_tabview_change_tab(UiMotifTabView *tabview, UiTab *tab) {
    if(tabview->current_tab) {
        XtVaSetValues(tabview->current_tab->tab_button, XmNshadowThickness, 0, XmNbackground, tabview->bg1, NULL);
        XtUnmanageChild(tabview->current_tab->child);
    }
    XtVaSetValues(tab->tab_button, XmNshadowThickness, 1, XmNbackground, tabview->bg2, NULL);
    tabview->current_tab = tab;
    tabview->current_index = (int)cxListFind(tabview->tabs, tab);;
    XtManageChild(tab->child);
}

Widget ui_tabview_container_prepare(UiContainerPrivate *ctn, Arg *args, int *n) {
    UiTabViewContainer *ct = (UiTabViewContainer*)ctn;
    UiMotifTabView *tabview = ct->tabview;
    int a = *n;
    XtSetArg(args[a], XmNleftAttachment, XmATTACH_FORM); a++;
    XtSetArg(args[a], XmNrightAttachment, XmATTACH_FORM); a++;
    XtSetArg(args[a], XmNbottomAttachment, XmATTACH_FORM); a++;
    XtSetArg(args[a], XmNtopAttachment, XmATTACH_WIDGET); a++;
    XtSetArg(args[a], XmNtopWidget, tabview->tabbar); a++;
    *n = a;
    return tabview->form;
}

void ui_tabview_container_add(UiContainerPrivate *ctn, Widget widget) {
    ui_reset_layout(ctn->layout);
}



/* -------------------- Container Helper Functions -------------------- */

void ui_container_begin_close(UiObject *obj) {
    UiContainerPrivate *ct = ui_obj_container(obj);
    ct->container.close = 1;
}

int ui_container_finish(UiObject *obj) {
    UiContainerPrivate *ct = ui_obj_container(obj);
    if(ct->container.close) {
        ui_end_new(obj);
        return 0;
    }
    return 1;
}


/*
 * -------------------- Layout Functions --------------------
 * 
 * functions for setting layout attributes for the current container
 *
 */

void ui_newline(UiObject *obj) {
    UiContainerPrivate *ct = ui_obj_container(obj);
    ct->layout.newline = TRUE;
}

mercurial