diff -r 000000000000 -r 2483f517c562 ui/gtk/container.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/gtk/container.c Sun Jan 21 16:30:18 2024 +0100 @@ -0,0 +1,628 @@ +/* + * 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 +#include +#include + +#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) { +#ifdef UI_GTK3 + return gtk_box_new(GTK_ORIENTATION_VERTICAL, spacing); +#else + return gtk_vbox_new(FALSE, spacing); +#endif +} + +GtkWidget* ui_gtk_hbox_new(int spacing) { +#ifdef UI_GTK3 + return gtk_box_new(GTK_ORIENTATION_HORIZONTAL, spacing); +#else + return gtk_hbox_new(FALSE, spacing); +#endif +} + +/* -------------------- Frame Container (deprecated) -------------------- */ +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) { + gtk_container_add(GTK_CONTAINER(ct->widget), widget); + ui_reset_layout(ct->layout); + ct->current = widget; +} + + +/* -------------------- Box Container -------------------- */ +UiContainer* ui_box_container(UiObject *obj, GtkWidget *box) { + UiBoxContainer *ct = cxCalloc( + obj->ctx->allocator, + 1, + sizeof(UiBoxContainer)); + ct->container.widget = box; + ct->container.add = ui_box_container_add; + 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; + gtk_box_pack_start(GTK_BOX(ct->widget), widget, expand, fill, 0); + + 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; +#ifdef UI_GTK2 + ct->width = 0; + ct->height = 1; +#endif + return (UiContainer*)ct; +} + +#ifdef UI_GTK3 +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; + } + + if(hexpand) { + gtk_widget_set_hexpand(widget, TRUE); + } + if(vexpand) { + gtk_widget_set_vexpand(widget, TRUE); + } + + int gwidth = ct->layout.gridwidth > 0 ? ct->layout.gridwidth : 1; + + gtk_grid_attach(GTK_GRID(ct->widget), widget, grid->x, grid->y, gwidth, 1); + grid->x += gwidth; + + 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; + + 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 +#ifdef UI_GTK3 + gtk_container_add(GTK_CONTAINER(ct->widget), widget); +#else + gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(ct->widget), widget); +#endif + 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) { + gtk_notebook_append_page( + GTK_NOTEBOOK(ct->widget), + widget, + gtk_label_new(ct->layout.label)); + + ui_reset_layout(ct->layout); + ct->current = widget; +} + + +UIWIDGET ui_vbox(UiObject *obj) { + return ui_vbox_sp(obj, 0, 0); +} + +UIWIDGET ui_hbox(UiObject *obj) { + return ui_hbox_sp(obj, 0, 0); +} + +static GtkWidget* box_set_margin(GtkWidget *box, int margin) { + GtkWidget *ret = box; +#ifdef UI_GTK3 +#if GTK_MAJOR_VERSION == 3 && GTK_MINOR_VERSION >= 12 + 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_vbox_sp(UiObject *obj, int margin, int spacing) { + UiContainer *ct = uic_get_current_container(obj); + + GtkWidget *vbox = ui_gtk_vbox_new(spacing); + GtkWidget *widget = margin > 0 ? box_set_margin(vbox, margin) : vbox; + ct->add(ct, widget, TRUE); + + UiObject *newobj = uic_object_new(obj, vbox); + newobj->container = ui_box_container(obj, vbox); + uic_obj_add(obj, newobj); + + return widget; +} + +UIWIDGET ui_hbox_sp(UiObject *obj, int margin, int spacing) { + UiContainer *ct = uic_get_current_container(obj); + + GtkWidget *hbox = ui_gtk_hbox_new(spacing); + GtkWidget *widget = margin > 0 ? box_set_margin(hbox, margin) : hbox; + ct->add(ct, widget, TRUE); + + UiObject *newobj = uic_object_new(obj, hbox); + newobj->container = ui_box_container(obj, hbox); + uic_obj_add(obj, newobj); + + return widget; +} + +UIWIDGET ui_grid(UiObject *obj) { + return ui_grid_sp(obj, 0, 0, 0); +} + +UIWIDGET ui_grid_sp(UiObject *obj, int margin, int columnspacing, int rowspacing) { + UiContainer *ct = uic_get_current_container(obj); + GtkWidget *widget; + +#ifdef UI_GTK3 + GtkWidget *grid = gtk_grid_new(); + gtk_grid_set_column_spacing(GTK_GRID(grid), columnspacing); + gtk_grid_set_row_spacing(GTK_GRID(grid), rowspacing); +#if GTK_MAJOR_VERSION == 3 && GTK_MINOR_VERSION >= 12 + gtk_widget_set_margin_start(grid, margin); + gtk_widget_set_margin_end(grid, margin); +#else + gtk_widget_set_margin_left(grid, margin); + gtk_widget_set_margin_right(grid, margin); +#endif + gtk_widget_set_margin_top(grid, margin); + gtk_widget_set_margin_bottom(grid, margin); + + widget = grid; +#elif defined(UI_GTK2) + GtkWidget *grid = gtk_table_new(1, 1, FALSE); + + gtk_table_set_col_spacings(GTK_TABLE(grid), columnspacing); + gtk_table_set_row_spacings(GTK_TABLE(grid), rowspacing); + + if(margin > 0) { + 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), grid); + widget = a; + } else { + widget = grid; + } +#endif + ct->add(ct, 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(UiObject *obj) { + UiContainer *ct = uic_get_current_container(obj); + GtkWidget *sw = gtk_scrolled_window_new(NULL, NULL); + ct->add(ct, sw, TRUE); + + UiObject *newobj = uic_object_new(obj, sw); + newobj->container = ui_scrolledwindow_container(obj, sw); + uic_obj_add(obj, newobj); + + return sw; +} + +UIWIDGET ui_tabview(UiObject *obj) { + GtkWidget *tabview = gtk_notebook_new(); + gtk_notebook_set_show_border(GTK_NOTEBOOK(tabview), FALSE); + gtk_notebook_set_show_tabs(GTK_NOTEBOOK(tabview), FALSE); + + UiContainer *ct = uic_get_current_container(obj); + ct->add(ct, tabview, TRUE); + + UiObject *tabviewobj = uic_object_new(obj, tabview); + tabviewobj->container = ui_tabview_container(obj, tabview); + uic_obj_add(obj, tabviewobj); + + return tabview; +} + +void ui_tab(UiObject *obj, char *title) { + UiContainer *ct = uic_get_current_container(obj); + ct->layout.label = title; + ui_vbox(obj); +} + +void ui_select_tab(UIWIDGET tabview, int tab) { + gtk_notebook_set_current_page(GTK_NOTEBOOK(tabview), tab); +} + +/* -------------------- Splitpane -------------------- */ + +static GtkWidget* create_paned(UiOrientation orientation) { +#ifdef UI_GTK3 + 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; +} + +UIWIDGET ui_splitpane(UiObject *obj, int max, UiOrientation orientation) { + GtkWidget *paned = create_paned(orientation); + UiContainer *ct = uic_get_current_container(obj); + ct->add(ct, paned, TRUE); + + if(max <= 0) max = INT_MAX; + + UiPanedContainer *pctn = cxCalloc( + obj->ctx->allocator, + 1, + sizeof(UiPanedContainer)); + pctn->container.widget = paned; + pctn->container.add = ui_paned_container_add; + pctn->current_pane = paned; + pctn->orientation = orientation; + pctn->max = max; + pctn->cur = 0; + + UiObject *pobj = uic_object_new(obj, paned); + pobj->container = (UiContainer*)pctn; + + uic_obj_add(obj, pobj); + + return paned; +} + +UIWIDGET ui_hsplitpane(UiObject *obj, int max) { + return ui_splitpane(obj, max, UI_HORIZONTAL); +} + +UIWIDGET ui_vsplitpane(UiObject *obj, int max) { + return ui_splitpane(obj, max, UI_VERTICAL); +} + +void ui_paned_container_add(UiContainer *ct, GtkWidget *widget, UiBool fill) { + UiPanedContainer *pctn = (UiPanedContainer*)ct; + + gboolean resize = (ct->layout.hexpand || ct->layout.vexpand) ? TRUE : FALSE; + int width = ct->layout.width; + ui_reset_layout(ct->layout); + + if(pctn->cur == 0) { + gtk_paned_pack1(GTK_PANED(pctn->current_pane), widget, resize, resize); + } else if(pctn->cur < pctn->max-1) { + GtkWidget *nextPane = create_paned(pctn->orientation); + gtk_paned_pack2(GTK_PANED(pctn->current_pane), nextPane, TRUE, TRUE); + gtk_paned_pack1(GTK_PANED(nextPane), widget, resize, resize); + pctn->current_pane = nextPane; + } else if(pctn->cur == pctn->max-1) { + gtk_paned_pack2(GTK_PANED(pctn->current_pane), widget, resize, resize); + width = 0; // disable potential call of gtk_paned_set_position + } else { + fprintf(stderr, "Splitpane max reached: %d\n", pctn->max); + return; + } + + if(width > 0) { + gtk_paned_set_position(GTK_PANED(pctn->current_pane), width); + } + + pctn->cur++; +} + + +/* -------------------- Sidebar (deprecated) -------------------- */ +UIWIDGET ui_sidebar(UiObject *obj) { +#ifdef UI_GTK3 + GtkWidget *paned = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL); +#else + GtkWidget *paned = gtk_hpaned_new(); +#endif + gtk_paned_set_position(GTK_PANED(paned), 200); + + GtkWidget *sidebar = ui_gtk_vbox_new(0); + gtk_paned_pack1(GTK_PANED(paned), sidebar, TRUE, FALSE); + + UiObject *left = uic_object_new(obj, sidebar); + UiContainer *ct1 = ui_box_container(obj, sidebar); + left->container = ct1; + + UiObject *right = uic_object_new(obj, sidebar); + UiContainer *ct2 = cxCalloc( + obj->ctx->allocator, + 1, + sizeof(UiContainer)); + ct2->widget = paned; + ct2->add = ui_split_container_add2; + right->container = ct2; + + UiContainer *ct = uic_get_current_container(obj); + ct->add(ct, paned, TRUE); + + uic_obj_add(obj, right); + uic_obj_add(obj, left); + + return sidebar; +} + +void ui_split_container_add1(UiContainer *ct, GtkWidget *widget, UiBool fill) { + // TODO: remove + gtk_paned_pack1(GTK_PANED(ct->widget), widget, TRUE, FALSE); + + ui_reset_layout(ct->layout); + ct->current = widget; +} + +void ui_split_container_add2(UiContainer *ct, GtkWidget *widget, UiBool fill) { + gtk_paned_pack2(GTK_PANED(ct->widget), widget, TRUE, FALSE); + + ui_reset_layout(ct->layout); + ct->current = widget; +} + + +/* -------------------- Document Tabview -------------------- */ +static void page_change(GtkNotebook *notebook, GtkWidget *page, guint page_num, gpointer data) { + GQuark q = g_quark_from_static_string("ui.tab.object"); + UiObject *tab = g_object_get_qdata(G_OBJECT(page), q); + if(!tab) { + return; + } + + //printf("page_change: %d\n", page_num); + UiContext *ctx = tab->ctx; + uic_context_detach_all(ctx->parent); // TODO: fix? + ctx->parent->attach_document(ctx->parent, ctx->document); +} + +UiTabbedPane* ui_tabbed_document_view(UiObject *obj) { + GtkWidget *tabview = gtk_notebook_new(); + gtk_notebook_set_show_border(GTK_NOTEBOOK(tabview), FALSE); + + g_signal_connect( + tabview, + "switch-page", + G_CALLBACK(page_change), + NULL); + + UiContainer *ct = uic_get_current_container(obj); + ct->add(ct, tabview, TRUE); + + UiTabbedPane *tabbedpane = ui_malloc(obj->ctx, sizeof(UiTabbedPane)); + tabbedpane->ctx = uic_current_obj(obj)->ctx; + tabbedpane->widget = tabview; + tabbedpane->document = NULL; + + return tabbedpane; +} + +UiObject* ui_document_tab(UiTabbedPane *view) { + GtkWidget *frame = gtk_frame_new(NULL); + gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_NONE); + // TODO: label + gtk_notebook_append_page(GTK_NOTEBOOK(view->widget), frame, NULL); + + UiObject *tab = ui_malloc(view->ctx, sizeof(UiObject)); + tab->widget = NULL; // initialization for uic_context() + tab->ctx = uic_context(tab, view->ctx->allocator); + tab->ctx->parent = view->ctx; + tab->ctx->attach_document = uic_context_attach_document; + tab->ctx->detach_document2 = uic_context_detach_document2; + tab->widget = frame; + tab->window = view->ctx->obj->window; + tab->container = ui_frame_container(tab, frame); + tab->next = NULL; + + GQuark q = g_quark_from_static_string("ui.tab.object"); + g_object_set_qdata(G_OBJECT(frame), q, tab); + + return tab; +} + +void ui_tab_set_document(UiContext *ctx, void *document) { + // TODO: remove? + if(ctx->parent->document) { + //ctx->parent->detach_document(ctx->parent, ctx->parent->document); + } + //uic_context_set_document(ctx, document); + //uic_context_set_document(ctx->parent, document); + //ctx->parent->document = document; +} + +void ui_tab_detach_document(UiContext *ctx) { + // TODO: remove? + //uic_context_detach_document(ctx->parent); +} + + +/* + * -------------------- 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_width(UiObject *obj, int width) { + UiContainer *ct = uic_get_current_container(obj); + ct->layout.width = width; +} + +void ui_layout_gridwidth(UiObject *obj, int width) { + UiContainer *ct = uic_get_current_container(obj); + ct->layout.gridwidth = width; +} + +void ui_newline(UiObject *obj) { + UiContainer *ct = uic_get_current_container(obj); + ct->layout.newline = TRUE; +} +