ui/gtk/container.c

Sun, 15 Dec 2024 22:13:05 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sun, 15 Dec 2024 22:13:05 +0100
branch
newapi
changeset 417
f0fee61a70be
parent 410
a080c69125d1
permissions
-rw-r--r--

implement menu item callbacks (Motif)

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2017 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 <limits.h>

#include "container.h"
#include "toolkit.h"
#include "headerbar.h"

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


void ui_container_begin_close(UiObject *obj) {
    UiContainer *ct = uic_get_current_container(obj);
    ct->close = 1;
}

int ui_container_finish(UiObject *obj) {
    UiContainer *ct = uic_get_current_container(obj);
    if(ct->close) {
        ui_end(obj);
        return 0;
    }
    return 1;
}

GtkWidget* ui_gtk_vbox_new(int spacing) {
#if GTK_MAJOR_VERSION >= 3
    return gtk_box_new(GTK_ORIENTATION_VERTICAL, spacing);
#else
    return gtk_vbox_new(FALSE, spacing);
#endif
}

GtkWidget* ui_gtk_hbox_new(int spacing) {
#if GTK_MAJOR_VERSION >= 3
    return gtk_box_new(GTK_ORIENTATION_HORIZONTAL, spacing);
#else
    return gtk_hbox_new(FALSE, spacing);
#endif
}

GtkWidget* ui_subcontainer_create(
        UiSubContainerType type,
        UiObject *newobj,
        int spacing,
        int columnspacing,
        int rowspacing,
        int margin)
{
    GtkWidget *sub = NULL;
    GtkWidget *add = NULL;
    switch(type) {
        default: {
            sub = ui_gtk_vbox_new(spacing);
            add = ui_box_set_margin(sub, margin);
            newobj->container = ui_box_container(newobj, sub, type);
            newobj->widget = sub;
            break;
        }
        case UI_CONTAINER_HBOX: {
            sub = ui_gtk_hbox_new(spacing);
            add = ui_box_set_margin(sub, margin);
            newobj->container = ui_box_container(newobj, sub, type);
            newobj->widget = sub;
            break;
        }
        case UI_CONTAINER_GRID: {
            sub = ui_create_grid_widget(columnspacing, rowspacing);
            add = ui_box_set_margin(sub, margin);
            newobj->container = ui_grid_container(newobj, sub);
            newobj->widget = sub;
            break;
        }
        case UI_CONTAINER_NO_SUB: {
            break;
        }
    }
    return add;
}


/* -------------------- Box Container -------------------- */
UiContainer* ui_box_container(UiObject *obj, GtkWidget *box, UiSubContainerType type) {
    UiBoxContainer *ct = cxCalloc(
            obj->ctx->allocator,
            1,
            sizeof(UiBoxContainer));
    ct->container.widget = box;
    ct->container.add = ui_box_container_add;
    ct->type = type;
    return (UiContainer*)ct;
}

void ui_box_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
    UiBoxContainer *bc = (UiBoxContainer*)ct;
    if(ct->layout.fill != UI_LAYOUT_UNDEFINED) {
        fill = ui_lb2bool(ct->layout.fill);
    }
    
    if(bc->has_fill && fill) {
        fprintf(stderr, "UiError: container has 2 filled widgets");
        fill = FALSE;
    }
    if(fill) {
        bc->has_fill = TRUE;
    }
    
    UiBool expand = fill;
#if GTK_MAJOR_VERSION >= 4
    gtk_box_append(GTK_BOX(ct->widget), widget);
    GtkAlign align = expand ? GTK_ALIGN_FILL : GTK_ALIGN_START; 
    if(bc->type == UI_CONTAINER_VBOX) {
        gtk_widget_set_valign(widget, align);
        gtk_widget_set_vexpand(widget, expand);
        gtk_widget_set_hexpand(widget, TRUE);
    } else if(bc->type == UI_CONTAINER_HBOX) {
        gtk_widget_set_halign(widget, align);
        gtk_widget_set_hexpand(widget, expand);
        gtk_widget_set_vexpand(widget, TRUE);
    }
    
#else
    gtk_box_pack_start(GTK_BOX(ct->widget), widget, expand, fill, 0);
#endif
    
    ui_reset_layout(ct->layout);
    ct->current = widget;
}

UiContainer* ui_grid_container(UiObject *obj, GtkWidget *grid) {
    UiGridContainer *ct = cxCalloc(
            obj->ctx->allocator,
            1,
            sizeof(UiGridContainer));
    ct->container.widget = grid;
    ct->container.add = ui_grid_container_add;
    UI_GTK_V2(ct->width = 0);
    UI_GTK_V2(ct->height = 1);
    return (UiContainer*)ct;
}

#if GTK_MAJOR_VERSION >= 3
void ui_grid_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
    UiGridContainer *grid = (UiGridContainer*)ct;
    
    if(ct->layout.newline) {
        grid->x = 0;
        grid->y++;
        ct->layout.newline = FALSE;
    }
    
    int hexpand = FALSE;
    int vexpand = FALSE;
    int hfill = FALSE;
    int vfill = FALSE;
    if(ct->layout.fill != UI_LAYOUT_UNDEFINED) {
        fill = ui_lb2bool(ct->layout.fill);
    }
    if(ct->layout.hexpand != UI_LAYOUT_UNDEFINED) {
        hexpand = ct->layout.hexpand;
        hfill = TRUE;
    }
    if(ct->layout.vexpand != UI_LAYOUT_UNDEFINED) {
        vexpand = ct->layout.vexpand;
        vfill = TRUE;
    }
    if(fill) {
        hfill = TRUE;
        vfill = TRUE;
    }
    
    if(!hfill) {
        gtk_widget_set_halign(widget, GTK_ALIGN_START);
    }
    if(!vfill) {
        gtk_widget_set_valign(widget, GTK_ALIGN_START);
    }
    
    gtk_widget_set_hexpand(widget, hexpand);
    gtk_widget_set_vexpand(widget, vexpand);
    
    int colspan = ct->layout.colspan > 0 ? ct->layout.colspan : 1;
    int rowspan = ct->layout.rowspan > 0 ? ct->layout.rowspan : 1;
    
    gtk_grid_attach(GTK_GRID(ct->widget), widget, grid->x, grid->y, colspan, rowspan);
    grid->x += colspan;
    
    ui_reset_layout(ct->layout);
    ct->current = widget;
}
#endif
#ifdef UI_GTK2
void ui_grid_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
    UiGridContainer *grid = (UiGridContainer*)ct;
    
    if(ct->layout.newline) {
        grid->x = 0;
        grid->y++;
        ct->layout.newline = FALSE;
    }
    
    int hexpand = FALSE;
    int vexpand = FALSE;
    if(ct->layout.hexpand != UI_LAYOUT_UNDEFINED) {
        hexpand = ct->layout.hexpand;
    }
    if(ct->layout.vexpand != UI_LAYOUT_UNDEFINED) {
        vexpand = ct->layout.vexpand;
    }
    GtkAttachOptions xoptions = hexpand ? GTK_FILL | GTK_EXPAND : GTK_FILL;
    GtkAttachOptions yoptions = vexpand ? GTK_FILL | GTK_EXPAND : GTK_FILL;
    
    int colspan = ct->layout.colspan > 0 ? ct->layout.colspan : 1;
    int rowspan = ct->layout.rowspan > 0 ? ct->layout.rowspan : 1;
    // TODO: use colspan/rowspan
    
    gtk_table_attach(GTK_TABLE(ct->widget), widget, grid->x, grid->x+1, grid->y, grid->y+1, xoptions, yoptions, 0, 0);
    grid->x++;
    int nw = grid->x > grid->width ? grid->x : grid->width;
    if(grid->x > grid->width || grid->y > grid->height) {
        grid->width = nw;
        grid->height = grid->y + 1;
        gtk_table_resize(GTK_TABLE(ct->widget), grid->width, grid->height);
    }
    
    ui_reset_layout(ct->layout);
    ct->current = widget;
}
#endif

UiContainer* ui_frame_container(UiObject *obj, GtkWidget *frame) {
    UiContainer *ct = cxCalloc(
            obj->ctx->allocator,
            1,
            sizeof(UiContainer));
    ct->widget = frame;
    ct->add = ui_frame_container_add;
    return ct;
}

void ui_frame_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
    FRAME_SET_CHILD(ct->widget, widget);
}

UiContainer* ui_expander_container(UiObject *obj, GtkWidget *expander) {
    UiContainer *ct = cxCalloc(
            obj->ctx->allocator,
            1,
            sizeof(UiContainer));
    ct->widget = expander;
    ct->add = ui_expander_container_add;
    return ct;
}

void ui_expander_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
    EXPANDER_SET_CHILD(ct->widget, widget);
}

void ui_scrolledwindow_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
    // TODO: check if the widget implements GtkScrollable
    SCROLLEDWINDOW_SET_CHILD(ct->widget, widget);
    ui_reset_layout(ct->layout);
    ct->current = widget;
}

UiContainer* ui_scrolledwindow_container(UiObject *obj, GtkWidget *scrolledwindow) {
    UiContainer *ct = cxCalloc(
            obj->ctx->allocator,
            1,
            sizeof(UiContainer));
    ct->widget = scrolledwindow;
    ct->add = ui_scrolledwindow_container_add;
    return ct;
}

UiContainer* ui_tabview_container(UiObject *obj, GtkWidget *tabview) {
    UiTabViewContainer *ct = cxCalloc(
            obj->ctx->allocator,
            1,
            sizeof(UiTabViewContainer));
    ct->container.widget = tabview;
    ct->container.add = ui_tabview_container_add;
    return (UiContainer*)ct;
}

void ui_tabview_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
    UiGtkTabView *data = ui_widget_get_tabview_data(ct->widget);
    if(!data) {
        fprintf(stderr, "UI Error: widget is not a tabview");
        return;
    }
    data->add_tab(ct->widget, -1, ct->layout.label, widget);
    
    ui_reset_layout(ct->layout);
    ct->current = widget;
}



GtkWidget* ui_box_set_margin(GtkWidget *box, int margin) {
    GtkWidget *ret = box;
#if GTK_MAJOR_VERSION >= 3
#if GTK_MAJOR_VERSION * 1000 + GTK_MINOR_VERSION >= 3012
    gtk_widget_set_margin_start(box, margin);
    gtk_widget_set_margin_end(box, margin);
#else
    gtk_widget_set_margin_left(box, margin);
    gtk_widget_set_margin_right(box, margin);
#endif
    gtk_widget_set_margin_top(box, margin);
    gtk_widget_set_margin_bottom(box, margin);
#elif defined(UI_GTK2)
    GtkWidget *a = gtk_alignment_new(0.5, 0.5, 1, 1);
    gtk_alignment_set_padding(GTK_ALIGNMENT(a), margin, margin, margin, margin);
    gtk_container_add(GTK_CONTAINER(a), box);
    ret = a;
#endif
    return ret;
}

UIWIDGET ui_box_create(UiObject *obj, UiContainerArgs args, UiSubContainerType type) {
    UiObject *current = uic_current_obj(obj);
    UiContainer *ct = current->container;
    UI_APPLY_LAYOUT1(current, args);
    
    GtkWidget *box = type == UI_CONTAINER_VBOX ? ui_gtk_vbox_new(args.spacing) : ui_gtk_hbox_new(args.spacing);
    ui_set_name_and_style(box, args.name, args.style_class);
    GtkWidget *widget = args.margin > 0 ? ui_box_set_margin(box, args.margin) : box;
    ct->add(ct, widget, TRUE);
    
    UiObject *newobj = uic_object_new(obj, box);
    newobj->container = ui_box_container(obj, box, type);
    uic_obj_add(obj, newobj);
    
    return widget;
}

UIEXPORT UIWIDGET ui_vbox_create(UiObject *obj, UiContainerArgs args) {
    return ui_box_create(obj, args, UI_CONTAINER_VBOX);
}

UIEXPORT UIWIDGET ui_hbox_create(UiObject *obj, UiContainerArgs args) {
    return ui_box_create(obj, args, UI_CONTAINER_HBOX);
}

GtkWidget* ui_create_grid_widget(int colspacing, int rowspacing) {
#if GTK_MAJOR_VERSION >= 3
    GtkWidget *grid = gtk_grid_new();
    gtk_grid_set_column_spacing(GTK_GRID(grid), colspacing);
    gtk_grid_set_row_spacing(GTK_GRID(grid), rowspacing);
#else
    GtkWidget *grid = gtk_table_new(1, 1, FALSE);
    gtk_table_set_col_spacings(GTK_TABLE(grid), colspacing);
    gtk_table_set_row_spacings(GTK_TABLE(grid), rowspacing);
#endif
    return grid;
}

UIWIDGET ui_grid_create(UiObject *obj, UiContainerArgs args) {
    UiObject* current = uic_current_obj(obj);
    UI_APPLY_LAYOUT1(current, args);
    GtkWidget *widget;
    
    GtkWidget *grid = ui_create_grid_widget(args.columnspacing, args.rowspacing);
    ui_set_name_and_style(grid, args.name, args.style_class);
    widget = ui_box_set_margin(grid, args.margin);
    current->container->add(current->container, widget, TRUE);
    
    UiObject *newobj = uic_object_new(obj, grid);
    newobj->container = ui_grid_container(obj, grid);
    uic_obj_add(obj, newobj);
    
    return widget;
}

UIWIDGET ui_frame_create(UiObject *obj, UiFrameArgs args) {
    UiObject* current = uic_current_obj(obj);
    UI_APPLY_LAYOUT1(current, args);
    
    GtkWidget *frame = gtk_frame_new(args.label);
    UiObject *newobj = uic_object_new(obj, frame);
    GtkWidget *sub = ui_subcontainer_create(args.subcontainer, newobj, args.spacing, args.columnspacing, args.rowspacing, args.margin);
    if(sub) {
        FRAME_SET_CHILD(frame, sub);
    } else {
        newobj->widget = frame;
        newobj->container = ui_frame_container(obj, frame);
    }
    current->container->add(current->container, frame, FALSE);
    uic_obj_add(obj, newobj);
    
    return frame;
}

UIEXPORT UIWIDGET ui_expander_create(UiObject *obj, UiFrameArgs args) {
    UiObject* current = uic_current_obj(obj);
    UI_APPLY_LAYOUT1(current, args);
    
    GtkWidget *expander = gtk_expander_new(args.label);
    gtk_expander_set_expanded(GTK_EXPANDER(expander), args.isexpanded);
    UiObject *newobj = uic_object_new(obj, expander);
    GtkWidget *sub = ui_subcontainer_create(args.subcontainer, newobj, args.spacing, args.columnspacing, args.rowspacing, args.margin);
    if(sub) {
        EXPANDER_SET_CHILD(expander, sub);
    } else {
        newobj->widget = expander;
        newobj->container = ui_expander_container(obj, expander);
    }
    current->container->add(current->container, expander, FALSE);
    uic_obj_add(obj, newobj);
    
    return expander;
}


UIWIDGET ui_scrolledwindow_create(UiObject* obj, UiFrameArgs args) {
    UiObject* current = uic_current_obj(obj);
    UI_APPLY_LAYOUT1(current, args);
    
    GtkWidget *sw = SCROLLEDWINDOW_NEW();
    ui_set_name_and_style(sw, args.name, args.style_class);
    GtkWidget *widget = ui_box_set_margin(sw, args.margin);
    current->container->add(current->container, widget, TRUE);
    
    UiObject *newobj = uic_object_new(obj, sw);
    GtkWidget *sub = ui_subcontainer_create(args.subcontainer, newobj, args.spacing, args.columnspacing, args.rowspacing, args.margin);
    if(sub) {
        SCROLLEDWINDOW_SET_CHILD(sw, sub);
    } else {
        newobj->widget = sw;
        newobj->container = ui_scrolledwindow_container(obj, sw);
    }
    
    uic_obj_add(obj, newobj);
    
    return sw;
}


void ui_notebook_tab_select(UIWIDGET tabview, int tab) {
    gtk_notebook_set_current_page(GTK_NOTEBOOK(tabview), tab);
}

void ui_notebook_tab_remove(UIWIDGET tabview, int tab) {
    gtk_notebook_remove_page(GTK_NOTEBOOK(tabview), tab);
}

void ui_notebook_tab_add(UIWIDGET widget, int index, const char *name, UIWIDGET child) {
    gtk_notebook_insert_page(
            GTK_NOTEBOOK(widget),
            child,
            gtk_label_new(name),
            index);
}

int64_t ui_notebook_get(UiInteger *i) {
    GtkNotebook *nb = i->obj;
    i->value = gtk_notebook_get_current_page(nb);
    return i->value;
}

void ui_notebook_set(UiInteger *i, int64_t value) {
    GtkNotebook *nb = i->obj;
    gtk_notebook_set_current_page(nb, value);
    i->value = gtk_notebook_get_current_page(nb);
}


#if GTK_MAJOR_VERSION >= 4
static int stack_set_page(GtkWidget *stack, int index) {
    GtkSelectionModel *pages = gtk_stack_get_pages(GTK_STACK(stack));
    GListModel *list = G_LIST_MODEL(pages);
    GtkStackPage *page = g_list_model_get_item(list, index);
    if(page) {
        gtk_stack_set_visible_child(GTK_STACK(stack), gtk_stack_page_get_child(page));
    } else {
        fprintf(stderr, "UI Error: ui_stack_set value out of bounds\n");
        return -1;
    }
    return index;
}

void ui_stack_tab_select(UIWIDGET tabview, int tab) {
    stack_set_page(tabview, tab);
}

void ui_stack_tab_remove(UIWIDGET tabview, int tab) {
    GtkStack *stack = GTK_STACK(tabview);
    GtkWidget *current = gtk_stack_get_visible_child(stack);
    GtkSelectionModel *pages = gtk_stack_get_pages(stack);
    GListModel *list = G_LIST_MODEL(pages);
    GtkStackPage *page = g_list_model_get_item(list, tab);
    if(page) {
        gtk_stack_remove(stack, gtk_stack_page_get_child(page));
    }
}

void ui_stack_tab_add(UIWIDGET widget, int index, const char *name, UIWIDGET child) {
    (void)gtk_stack_add_titled(GTK_STACK(widget), child, name, name);
}

int64_t ui_stack_get(UiInteger *i) {
    GtkStack *stack = GTK_STACK(i->obj);
    GtkWidget *current = gtk_stack_get_visible_child(stack);
    GtkSelectionModel *pages = gtk_stack_get_pages(stack);
    GListModel *list = G_LIST_MODEL(pages);
    int nitems = g_list_model_get_n_items(list);
    for(int p=0;p<nitems;p++) {
        GtkStackPage *page = g_list_model_get_item(list, p);
        GtkWidget *child = gtk_stack_page_get_child(page);
        if(child == current) {
            i->value = p;
            break;
        }
    }
    return i->value;
}

void ui_stack_set(UiInteger *i, int64_t value) {
    GtkWidget *widget = i->obj;
    if(stack_set_page(widget, value) >= 0) {
        i->value = value;
    }
}
#elif GTK_MAJOR_VERSION >= 3
static GtkWidget* stack_get_child(GtkWidget *stack, int index) {
    GList *children = gtk_container_get_children(GTK_CONTAINER(stack));
    if(children) {
        return g_list_nth_data(children, index);
    }
    return NULL;
}

void ui_stack_tab_select(UIWIDGET tabview, int tab) {
    GtkWidget *child = stack_get_child(tabview, tab);
    if(child) {
        gtk_stack_set_visible_child(GTK_STACK(tabview), child);
    }
}

void ui_stack_tab_remove(UIWIDGET tabview, int tab) {
    GtkWidget *child = stack_get_child(tabview, tab);
    if(child) {
        gtk_container_remove(GTK_CONTAINER(tabview), child);
    }
}

void ui_stack_tab_add(UIWIDGET widget, int index, const char *name, UIWIDGET child) {
    gtk_stack_add_titled(GTK_STACK(widget), child, name, name);
}

int64_t ui_stack_get(UiInteger *i) {
    GtkWidget *visible = gtk_stack_get_visible_child(GTK_STACK(i->obj));
    GList *children = gtk_container_get_children(GTK_CONTAINER(i->obj));
    GList *elm = children;
    int n = 0;
    int64_t v = -1;
    while(elm) {
        GtkWidget *child = elm->data;
        if(child == visible) {
            v = n;
            break;
        }
        
        elm = elm->next;
        n++;
    }
    g_list_free(children);
    i->value = v;
    return v;
}

void ui_stack_set(UiInteger *i, int64_t value) {
    GtkWidget *child = stack_get_child(i->obj, value);
    if(child) {
        gtk_stack_set_visible_child(GTK_STACK(i->obj), child);
        i->value = value;
    }
}

#endif




UiGtkTabView* ui_widget_get_tabview_data(UIWIDGET tabview) {
    return g_object_get_data(G_OBJECT(tabview), "ui_tabview");
}

typedef int64_t(*ui_tabview_get_func)(UiInteger*);
typedef void (*ui_tabview_set_func)(UiInteger*, int64_t);

UIWIDGET ui_tabview_create(UiObject* obj, UiTabViewArgs args) {
    UiGtkTabView *data = malloc(sizeof(UiGtkTabView));
    data->margin = args.margin;
    data->spacing = args.spacing;
    data->columnspacing = args.columnspacing;
    data->rowspacing = args.rowspacing;
    
    ui_tabview_get_func getfunc = NULL;
    ui_tabview_set_func setfunc = NULL;
    
    GtkWidget *widget = NULL;
    GtkWidget *data_widget = NULL;
    switch(args.tabview) {
        case UI_TABVIEW_DOC: {
            // TODO
            break;
        }
        case UI_TABVIEW_NAVIGATION_SIDE: {
#if GTK_CHECK_VERSION(3, 10, 0)
            widget = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
            GtkWidget *sidebar = gtk_stack_sidebar_new();
            BOX_ADD(widget, sidebar);
            GtkWidget *stack = gtk_stack_new();
            gtk_stack_set_transition_type (GTK_STACK(stack), GTK_STACK_TRANSITION_TYPE_SLIDE_UP_DOWN);
            gtk_stack_sidebar_set_stack(GTK_STACK_SIDEBAR(sidebar), GTK_STACK(stack));
            BOX_ADD_EXPAND(widget, stack);
            data->select_tab = ui_stack_tab_select;
            data->remove_tab = ui_stack_tab_remove;
            data->add_tab = ui_stack_tab_add;
            getfunc = ui_stack_get;
            setfunc = ui_stack_set;
            data_widget = stack;
#else
            // TODO
#endif
            break;
        }
        case UI_TABVIEW_DEFAULT: /* fall through */
        case UI_TABVIEW_NAVIGATION_TOP: /* fall through */
        case UI_TABVIEW_INVISIBLE: /* fall through */
        case UI_TABVIEW_NAVIGATION_TOP2: {
            widget = gtk_notebook_new();
            data_widget = widget;
            data->select_tab = ui_notebook_tab_select;
            data->remove_tab = ui_notebook_tab_remove;
            data->add_tab = ui_notebook_tab_add;
            getfunc = ui_notebook_get;
            setfunc = ui_notebook_set;
            if(args.tabview == UI_TABVIEW_INVISIBLE) {
                gtk_notebook_set_show_tabs(GTK_NOTEBOOK(widget), FALSE);
                gtk_notebook_set_show_border(GTK_NOTEBOOK(widget), FALSE);
            }
            break;
        }
    }
    
    UiObject* current = uic_current_obj(obj);
    if(args.value || args.varname) {
        UiVar *var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_INTEGER);
        UiInteger *i = var->value;
        i->get = getfunc;
        i->set = setfunc;
        i->obj = data_widget;
    }
    
    g_object_set_data(G_OBJECT(widget), "ui_tabview", data);
    data->widget = data_widget;
    data->subcontainer = args.subcontainer;
    
    UI_APPLY_LAYOUT1(current, args);
    current->container->add(current->container, widget, TRUE);
    
    UiObject *newobj = uic_object_new(obj, widget);
    newobj->container = ui_tabview_container(obj, widget);
    uic_obj_add(obj, newobj);
    data->obj = newobj;
    
    return widget;
}

void ui_tab_create(UiObject* obj, const char* title) {
    UiObject* current = uic_current_obj(obj);
    UiGtkTabView *data = ui_widget_get_tabview_data(current->widget);
    if(!data) {
        fprintf(stderr, "UI Error: widget is not a tabview\n");
        return;
    }
    
    UiObject *newobj = ui_tabview_add(current->widget, title, -1);
    current->next = newobj;
}



void ui_tabview_select(UIWIDGET tabview, int tab) {
    UiGtkTabView *data = ui_widget_get_tabview_data(tabview);
    if(!data) {
        fprintf(stderr, "UI Error: widget is not a tabview\n");
        return;
    }
    data->select_tab(tabview, tab);
}

void ui_tabview_remove(UIWIDGET tabview, int tab) {
    UiGtkTabView *data = ui_widget_get_tabview_data(tabview);
    if(!data) {
        fprintf(stderr, "UI Error: widget is not a tabview\n");
        return;
    }
    data->remove_tab(tabview, tab);
}

UiObject* ui_tabview_add(UIWIDGET tabview, const char *name, int tab_index) {
    UiGtkTabView *data = ui_widget_get_tabview_data(tabview);
    if(!data) {
        fprintf(stderr, "UI Error: widget is not a tabview\n");
        return NULL;
    }
    
    UiObject *newobj = cxCalloc(data->obj->ctx->allocator, 1, sizeof(UiObject));
    newobj->ctx = data->obj->ctx;
    
    GtkWidget *sub;
    switch(data->subcontainer) {
        default: {
            sub = ui_gtk_vbox_new(data->spacing);
            newobj->container = ui_box_container(newobj, sub, data->subcontainer);
            break;
        }
        case UI_CONTAINER_HBOX: {
            sub = ui_gtk_hbox_new(data->spacing);
            newobj->container = ui_box_container(newobj, sub, data->subcontainer);
            break;
        }
        case UI_CONTAINER_GRID: {
            sub = ui_create_grid_widget(data->columnspacing, data->rowspacing);
            newobj->container = ui_grid_container(newobj, sub);
            break;
        }
    }
    newobj->widget = sub;
    GtkWidget *widget = ui_box_set_margin(sub, data->margin);
    
    data->add_tab(data->widget, tab_index, name, widget);
    
    return newobj;
}


/* -------------------- Headerbar -------------------- */

static void hb_set_part(UiObject *obj, int part) {
    UiObject* current = uic_current_obj(obj);
    GtkWidget *headerbar = current->widget;
    
    UiHeaderbarContainer *hb = cxCalloc(
            obj->ctx->allocator,
            1,
            sizeof(UiHeaderbarContainer));
    memcpy(hb, current->container, sizeof(UiHeaderbarContainer));
    
    UiObject *newobj = uic_object_new(obj, headerbar);
    newobj->container = (UiContainer*)hb;
    uic_obj_add(obj, newobj);
    
    hb->part = part;
}

void ui_headerbar_start_create(UiObject *obj) {
    hb_set_part(obj, 0);
}

void ui_headerbar_center_create(UiObject *obj) {
    hb_set_part(obj, 2);
}

void ui_headerbar_end_create(UiObject *obj) {
    hb_set_part(obj, 1);
}

UIWIDGET ui_headerbar_fallback_create(UiObject *obj, UiHeaderbarArgs args) {
    UiObject *current = uic_current_obj(obj);
    UiContainer *ct = current->container;
    UI_APPLY_LAYOUT1(current, args);
    
    GtkWidget *box = ui_gtk_hbox_new(args.alt_spacing);
    ui_set_name_and_style(box, args.name, args.style_class);
    ct->add(ct, box, FALSE);
    
    UiObject *newobj = uic_object_new(obj, box);
    newobj->container = ui_headerbar_fallback_container(obj, box);
    uic_obj_add(obj, newobj);
    
    return box;
}

static void hb_fallback_set_part(UiObject *obj, int part) {
    UiObject* current = uic_current_obj(obj);
    GtkWidget *headerbar = current->widget;
    
    UiObject *newobj = uic_object_new(obj, headerbar);
    newobj->container = ui_headerbar_container(obj, headerbar);
    uic_obj_add(obj, newobj);
    
    UiHeaderbarContainer *hb = (UiHeaderbarContainer*)newobj->container;
    hb->part = part;
}

UiContainer* ui_headerbar_fallback_container(UiObject *obj, GtkWidget *headerbar) {
    UiHeaderbarContainer *ct = cxCalloc(
            obj->ctx->allocator,
            1,
            sizeof(UiHeaderbarContainer));
    ct->container.widget = headerbar;
    ct->container.add = ui_headerbar_fallback_container_add;
    return (UiContainer*)ct;
}

void ui_headerbar_fallback_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
    UiHeaderbarContainer *hb = (UiHeaderbarContainer*)ct;
    BOX_ADD(ct->widget, widget);
}

#if GTK_CHECK_VERSION(3, 10, 0)

UIWIDGET ui_headerbar_create(UiObject *obj, UiHeaderbarArgs args) {
    GtkWidget *headerbar = g_object_get_data(G_OBJECT(obj->widget), "ui_headerbar");
    if(!headerbar) {
        return ui_headerbar_fallback_create(obj, args);
    }
    
    UiObject *newobj = uic_object_new(obj, headerbar);
    newobj->container = ui_headerbar_container(obj, headerbar);
    uic_obj_add(obj, newobj);
    
    return headerbar;    
}

UiContainer* ui_headerbar_container(UiObject *obj, GtkWidget *headerbar) {
    UiHeaderbarContainer *ct = cxCalloc(
            obj->ctx->allocator,
            1,
            sizeof(UiHeaderbarContainer));
    ct->container.widget = headerbar;
    ct->container.add = ui_headerbar_container_add;
    return (UiContainer*)ct;
}

void ui_headerbar_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) {
    UiHeaderbarContainer *hb = (UiHeaderbarContainer*)ct;
    if(hb->part == 0) {
        UI_HEADERBAR_PACK_START(ct->widget, widget);
    } else if(hb->part == 1) {
        UI_HEADERBAR_PACK_END(ct->widget, widget);
    } else if(hb->part == 2) {
        if(!hb->centerbox) {
            GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
            hb->centerbox = box;
            UI_HEADERBAR_SET_TITLE_WIDGET(ct->widget, box);
        }
        BOX_ADD(hb->centerbox, widget);
    }
}

#else

UIWIDGET ui_headerbar_create(UiObject *obj, UiHeaderbarArgs args) {
    return ui_headerbar_fallback_create(obj, args);  
}

#endif

/* -------------------- Sidebar -------------------- */

#ifdef UI_LIBADWAITA
UIWIDGET ui_sidebar_create(UiObject *obj, UiSidebarArgs args) {
    GtkWidget *sidebar_toolbar_view = g_object_get_data(G_OBJECT(obj->widget), "ui_sidebar");
    if(!sidebar_toolbar_view) {
        fprintf(stderr, "Error: window is not configured for sidebar\n");
        return NULL;
    }
    
    GtkWidget *box = ui_gtk_vbox_new(args.spacing);
    ui_box_set_margin(box, args.margin);
    adw_toolbar_view_set_content(ADW_TOOLBAR_VIEW(sidebar_toolbar_view), box);
    
    UiObject *newobj = uic_object_new(obj, box);
    newobj->container = ui_box_container(obj, box, UI_CONTAINER_VBOX);
    uic_obj_add(obj, newobj);
    
    return box;
}
#else
UIWIDGET ui_sidebar_create(UiObject *obj, UiSidebarArgs args) {
    GtkWidget *sidebar_vbox = g_object_get_data(G_OBJECT(obj->widget), "ui_sidebar");
    
    GtkWidget *box = ui_gtk_vbox_new(args.spacing);
    ui_box_set_margin(box, args.margin);
    BOX_ADD_EXPAND(sidebar_vbox, box);
    
    UiObject *newobj = uic_object_new(obj, box);
    newobj->container = ui_box_container(obj, box, UI_CONTAINER_VBOX);
    uic_obj_add(obj, newobj);
    
    return box;
}
#endif

/* -------------------- Splitpane -------------------- */

static GtkWidget* create_paned(UiOrientation orientation) {
#if GTK_MAJOR_VERSION >= 3
    switch(orientation) {
        case UI_HORIZONTAL: return gtk_paned_new(GTK_ORIENTATION_HORIZONTAL);
        case UI_VERTICAL: return gtk_paned_new(GTK_ORIENTATION_VERTICAL);
    }
#else
    switch(orientation) {
        case UI_HORIZONTAL: return gtk_hpaned_new();
        case UI_VERTICAL: return gtk_vpaned_new();
    }
#endif
    return NULL;
}






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

void ui_layout_fill(UiObject *obj, UiBool fill) {
    UiContainer *ct = uic_get_current_container(obj);
    ct->layout.fill = ui_bool2lb(fill);
}

void ui_layout_hexpand(UiObject *obj, UiBool expand) {
    UiContainer *ct = uic_get_current_container(obj);
    ct->layout.hexpand = expand;
}

void ui_layout_vexpand(UiObject *obj, UiBool expand) {
    UiContainer *ct = uic_get_current_container(obj);
    ct->layout.vexpand = expand;
}

void ui_layout_hfill(UiObject *obj, UiBool fill) {
    UiContainer *ct = uic_get_current_container(obj);
    ct->layout.hfill = fill;
}

void ui_layout_vfill(UiObject *obj, UiBool fill) {
    UiContainer *ct = uic_get_current_container(obj);
    ct->layout.vfill = fill;
}

void ui_layout_colspan(UiObject* obj, int cols) {
    UiContainer* ct = uic_get_current_container(obj);
    ct->layout.colspan = cols;
}

void ui_layout_rowspan(UiObject* obj, int rows) {
    UiContainer* ct = uic_get_current_container(obj);
    ct->layout.rowspan = rows;
}

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

mercurial