ui/gtk/container.c

Mon, 07 Oct 2024 23:26:52 +0200

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Mon, 07 Oct 2024 23:26:52 +0200
branch
newapi
changeset 329
08ce6680d1cf
parent 328
059cba080ab4
permissions
-rw-r--r--

merge

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



/* -------------------- 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;
    if(ct->layout.hexpand != UI_LAYOUT_UNDEFINED) {
        hexpand = ct->layout.hexpand;
    }
    if(ct->layout.vexpand != UI_LAYOUT_UNDEFINED) {
        vexpand = ct->layout.vexpand;
    }
    
    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_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;
}

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_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;
}



static GtkWidget* 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 ? 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);
}

static GtkWidget* create_grid(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 = create_grid(args.columnspacing, args.rowspacing);
    ui_set_name_and_style(grid, args.name, args.style_class);
    widget = 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_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);
    UiObject *newobj = uic_object_new(obj, sw);
    newobj->container = ui_scrolledwindow_container(obj, sw);
    uic_obj_add(obj, newobj);
    
    return sw;
}


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

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

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

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;
    
    GtkWidget *widget = NULL;
    switch(args.tabview) {
        case UI_TABVIEW_DOC: {
            // TODO
            break;
        }
        case UI_TABVIEW_NAVIGATION_SIDE: {
            // TODO
            break;
        }
        case UI_TABVIEW_DEFAULT: 
        case UI_TABVIEW_NAVIGATION_TOP:
        case UI_TABVIEW_NAVIGATION_TOP2: {
            widget = gtk_notebook_new();
            data->select_tab = ui_notebook_tab_select;
            data->remove_tab = ui_notebook_tab_remove;
            data->add_tab = ui_notebook_tab_add;
            break;
        }
        case UI_TABVIEW_INVISIBLE: {
            widget = gtk_notebook_new();
            gtk_notebook_set_show_tabs(GTK_NOTEBOOK(widget), FALSE);
            gtk_notebook_set_show_border(GTK_NOTEBOOK(widget), FALSE);
            data->select_tab = ui_notebook_tab_select;
            data->remove_tab = ui_notebook_tab_remove;
            data->add_tab = ui_notebook_tab_add;
            break;
        }
    }
    
    g_object_set_data(G_OBJECT(widget), "ui_tabview", data);
    data->widget = widget;
    data->subcontainer = args.subcontainer;
    
    UiObject* current = uic_current_obj(obj);
    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(data->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 = create_grid(data->columnspacing, data->rowspacing);
            newobj->container = ui_grid_container(newobj, sub);
            break;
        }
    }
    newobj->widget = sub;
    GtkWidget *widget = box_set_margin(sub, data->margin);
    
    data->add_tab(data->widget, tab_index, name, widget);
    
    return newobj;
}

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