# HG changeset patch # User Olaf Wintermann # Date 1727949489 -7200 # Node ID 8d63b7cd3d98932266a2eff7d8963931515f7ff2 # Parent be224cf1f50ecadc949b1584e212c205d7eae8b5 rename tree.c to list.c (GTK) diff -r be224cf1f50e -r 8d63b7cd3d98 ui/gtk/list.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/gtk/list.c Thu Oct 03 11:58:09 2024 +0200 @@ -0,0 +1,666 @@ +/* + * 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 + +#include "../common/context.h" +#include "../common/object.h" +#include "container.h" + +#include "list.h" +#include "image.h" + + +void* ui_strmodel_getvalue(void *elm, int column) { + return column == 0 ? elm : NULL; +} + +static GtkListStore* create_list_store(UiList *list, UiModel *model) { + int columns = model->columns; + GType types[2*columns]; + int c = 0; + for(int i=0;itypes[i]) { + case UI_STRING: + case UI_STRING_FREE: types[c] = G_TYPE_STRING; break; + case UI_INTEGER: types[c] = G_TYPE_INT; break; + case UI_ICON: types[c] = G_TYPE_OBJECT; break; + case UI_ICON_TEXT: + case UI_ICON_TEXT_FREE: { + types[c] = G_TYPE_OBJECT; + types[++c] = G_TYPE_STRING; + } + } + } + + GtkListStore *store = gtk_list_store_newv(c, types); + + if(list) { + void *elm = list->first(list); + while(elm) { + // insert new row + GtkTreeIter iter; + gtk_list_store_insert (store, &iter, -1); + + // set column values + int c = 0; + for(int i=0;igetvalue(elm, c); + + GValue value = G_VALUE_INIT; + switch(model->types[i]) { + case UI_STRING: + case UI_STRING_FREE: { + g_value_init(&value, G_TYPE_STRING); + g_value_set_string(&value, data); + if(model->types[i] == UI_STRING_FREE) { + free(data); + } + break; + } + case UI_INTEGER: { + g_value_init(&value, G_TYPE_INT); + int *intptr = data; + g_value_set_int(&value, *intptr); + break; + } + case UI_ICON: { + g_value_init(&value, G_TYPE_OBJECT); + UiIcon *icon = data; +#if GTK_MAJOR_VERSION >= 4 + g_value_set_object(&value, icon->info); // TODO: does this work? +#else + if(!icon->pixbuf && icon->info) { + GError *error = NULL; + GdkPixbuf *pixbuf = gtk_icon_info_load_icon(icon->info, &error); + icon->pixbuf = pixbuf; + } + + if(icon->pixbuf) { + g_value_set_object(&value, icon->pixbuf); + } +#endif + break; + } + case UI_ICON_TEXT: + case UI_ICON_TEXT_FREE: { + UiIcon *icon = data; +#if GTK_MAJOR_VERSION >= 4 + GValue iconvalue = G_VALUE_INIT; + g_value_init(&iconvalue, G_TYPE_OBJECT); + g_value_set_object(&iconvalue, ui_icon_pixbuf(icon)); + gtk_list_store_set_value(store, &iter, c, &iconvalue); +#else + GValue pixbufvalue = G_VALUE_INIT; + if(!icon->pixbuf && icon->info) { + GError *error = NULL; + GdkPixbuf *pixbuf = gtk_icon_info_load_icon(icon->info, &error); + icon->pixbuf = pixbuf; + } + g_value_init(&pixbufvalue, G_TYPE_OBJECT); + g_value_set_object(&pixbufvalue, icon->pixbuf); + gtk_list_store_set_value(store, &iter, c, &pixbufvalue); +#endif + c++; + + char *str = model->getvalue(elm, c); + g_value_init(&value, G_TYPE_STRING); + g_value_set_string(&value, str); + if(model->types[i] == UI_ICON_TEXT_FREE) { + free(str); + } + break; + } + } + + gtk_list_store_set_value(store, &iter, c, &value); + } + + // next row + elm = list->next(list); + } + } + + return store; +} + + +UIWIDGET ui_listview_create(UiObject *obj, UiListArgs args) { + UiObject* current = uic_current_obj(obj); + + // create treeview + GtkWidget *view = gtk_tree_view_new(); + GtkCellRenderer *renderer = gtk_cell_renderer_text_new(); + GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes(NULL, renderer, "text", 0, NULL); + gtk_tree_view_append_column(GTK_TREE_VIEW(view), column); + + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE); +#ifdef UI_GTK3 +#if GTK_MINOR_VERSION >= 8 + gtk_tree_view_set_activate_on_single_click(GTK_TREE_VIEW(view), TRUE); +#else + // TODO: implement for older gtk3 +#endif +#else + // TODO: implement for gtk2 +#endif + + UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1); + model->getvalue = args.getvalue; + + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST); + + UiList *list = var ? var->value : NULL; + GtkListStore *listmodel = create_list_store(list, model); + gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel)); + + UiListView *listview = malloc(sizeof(UiListView)); + listview->obj = obj; + listview->widget = view; + listview->var = var; + listview->model = model; + g_signal_connect( + view, + "destroy", + G_CALLBACK(ui_listview_destroy), + listview); + + // bind var + list->update = ui_listview_update; + list->getselection = ui_listview_getselection; + list->obj = listview; + + // add callback + if(args.onactivate) { + UiTreeEventData *event = ui_malloc(obj->ctx, sizeof(UiTreeEventData)); + event->obj = obj; + event->activatedata = args.onactivatedata; + event->activate = args.onactivate; + event->selection = NULL; + + g_signal_connect( + view, + "row-activated", + G_CALLBACK(ui_listview_activate_event), + event); + } + + // add widget to the current container + GtkWidget *scroll_area = SCROLLEDWINDOW_NEW(); + gtk_scrolled_window_set_policy( + GTK_SCROLLED_WINDOW(scroll_area), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS + SCROLLEDWINDOW_SET_CHILD(scroll_area, view); + + UI_APPLY_LAYOUT1(current, args); + current->container->add(current->container, scroll_area, FALSE); + + // ct->current should point to view, not scroll_area, to make it possible + // to add a context menu + current->container->current = view; + + return scroll_area; +} + +/* +static void drag_begin(GtkWidget *widget, GdkDragContext *context, gpointer udata) { + printf("drag begin\n"); + +} + +static void drag_end( + GtkWidget *widget, + GdkDragContext *context, + guint time, + gpointer udata) +{ + printf("drag end\n"); + +} +*/ + +/* +static GtkTargetEntry targetentries[] = + { + { "STRING", 0, 0 }, + { "text/plain", 0, 1 }, + { "text/uri-list", 0, 2 }, + }; +*/ + +UIWIDGET ui_table_create(UiObject *obj, UiListArgs args) { + UiObject* current = uic_current_obj(obj); + + // create treeview + GtkWidget *view = gtk_tree_view_new(); + + UiModel *model = args.model; + int columns = model ? model->columns : 0; + + int addi = 0; + for(int i=0;itypes[i] == UI_ICON_TEXT) { + column = gtk_tree_view_column_new(); + gtk_tree_view_column_set_title(column, model->titles[i]); + + GtkCellRenderer *iconrenderer = gtk_cell_renderer_pixbuf_new(); + GtkCellRenderer *textrenderer = gtk_cell_renderer_text_new(); + + gtk_tree_view_column_pack_end(column, textrenderer, TRUE); + gtk_tree_view_column_pack_start(column, iconrenderer, FALSE); + + + gtk_tree_view_column_add_attribute(column, iconrenderer, "pixbuf", i); + gtk_tree_view_column_add_attribute(column, textrenderer, "text", i+1); + + addi++; + } else if (model->types[i] == UI_ICON) { + GtkCellRenderer *iconrenderer = gtk_cell_renderer_pixbuf_new(); + column = gtk_tree_view_column_new_with_attributes( + model->titles[i], + iconrenderer, + "pixbuf", + i + addi, + NULL); + } else { + GtkCellRenderer *renderer = gtk_cell_renderer_text_new(); + column = gtk_tree_view_column_new_with_attributes( + model->titles[i], + renderer, + "text", + i + addi, + NULL); + } + gtk_tree_view_column_set_resizable(column, TRUE); + gtk_tree_view_append_column(GTK_TREE_VIEW(view), column); + } + + //gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE); +#ifdef UI_GTK3 + //gtk_tree_view_set_activate_on_single_click(GTK_TREE_VIEW(view), TRUE); +#else + +#endif + + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST); + + UiList *list = var ? var->value : NULL; + GtkListStore *listmodel = create_list_store(list, model); + gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel)); + + //g_signal_connect(view, "drag-begin", G_CALLBACK(drag_begin), NULL); + //g_signal_connect(view, "drag-end", G_CALLBACK(drag_end), NULL); + + // add TreeView as observer to the UiList to update the TreeView if the + // data changes + UiListView *tableview = malloc(sizeof(UiListView)); + tableview->obj = obj; + tableview->widget = view; + tableview->var = var; + tableview->model = model; + g_signal_connect( + view, + "destroy", + G_CALLBACK(ui_listview_destroy), + tableview); + + // bind var + list->update = ui_listview_update; + list->getselection = ui_listview_getselection; + list->obj = tableview; + + // add callback + UiTreeEventData *event = ui_malloc(obj->ctx, sizeof(UiTreeEventData)); + event->obj = obj; + event->activate = args.onactivate; + event->selection = args.onselection; + event->activatedata = args.onactivatedata; + event->selectiondata = args.onselectiondata; + if(args.onactivate) { + g_signal_connect( + view, + "row-activated", + G_CALLBACK(ui_listview_activate_event), + event); + } + if(args.onselection) { + GtkTreeSelection *selection = gtk_tree_view_get_selection( + GTK_TREE_VIEW(view)); + g_signal_connect( + selection, + "changed", + G_CALLBACK(ui_listview_selection_event), + event); + } + // TODO: destroy callback + + + GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(view)); + gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE); + + // add widget to the current container + GtkWidget *scroll_area = SCROLLEDWINDOW_NEW(); + gtk_scrolled_window_set_policy( + GTK_SCROLLED_WINDOW(scroll_area), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS + SCROLLEDWINDOW_SET_CHILD(scroll_area, view); + + UI_APPLY_LAYOUT1(current, args); + current->container->add(current->container, scroll_area, FALSE); + + // ct->current should point to view, not scroll_area, to make it possible + // to add a context menu + current->container->current = view; + + return scroll_area; +} + + +GtkWidget* ui_get_tree_widget(UIWIDGET widget) { + return SCROLLEDWINDOW_GET_CHILD(widget); +} + +static char** targets2array(char *target0, va_list ap, int *nelm) { + int al = 16; + char **targets = calloc(16, sizeof(char*)); + targets[0] = target0; + + int i = 1; + char *target; + while((target = va_arg(ap, char*)) != NULL) { + if(i >= al) { + al *= 2; + targets = realloc(targets, al*sizeof(char*)); + } + targets[i] = target; + i++; + } + + *nelm = i; + return targets; +} + +/* +static GtkTargetEntry* targetstr2gtktargets(char **str, int nelm) { + GtkTargetEntry *targets = calloc(nelm, sizeof(GtkTargetEntry)); + for(int i=0;iobj; + GtkListStore *store = create_list_store(list, view->model); + gtk_tree_view_set_model(GTK_TREE_VIEW(view->widget), GTK_TREE_MODEL(store)); + g_object_unref(G_OBJECT(store)); + // TODO: free old model +} + +UiListSelection ui_listview_getselection(UiList *list) { + UiListView *view = list->obj; + UiListSelection selection = ui_listview_selection( + gtk_tree_view_get_selection(GTK_TREE_VIEW(view->widget)), + NULL); + return selection; +} + +void ui_listview_destroy(GtkWidget *w, UiListView *v) { + gtk_tree_view_set_model(GTK_TREE_VIEW(w), NULL); + ui_destroy_boundvar(v->obj->ctx, v->var); + // TODO: destroy model? + free(v); +} + +void ui_combobox_destroy(GtkWidget *w, UiListView *v) { + gtk_combo_box_set_model(GTK_COMBO_BOX(w), NULL); + ui_destroy_boundvar(v->obj->ctx, v->var); + // TODO: destroy model? + free(v); +} + + +void ui_listview_activate_event( + GtkTreeView *treeview, + GtkTreePath *path, + GtkTreeViewColumn *column, + UiTreeEventData *event) +{ + UiListSelection selection = ui_listview_selection( + gtk_tree_view_get_selection(treeview), + event); + + UiEvent e; + e.obj = event->obj; + e.window = event->obj->window; + e.document = event->obj->ctx->document; + e.eventdata = &selection; + e.intval = selection.count > 0 ? selection.rows[0] : -1; + event->activate(&e, event->activatedata); + + if(selection.count > 0) { + free(selection.rows); + } +} + +void ui_listview_selection_event( + GtkTreeSelection *treeselection, + UiTreeEventData *event) +{ + UiListSelection selection = ui_listview_selection(treeselection, event); + + UiEvent e; + e.obj = event->obj; + e.window = event->obj->window; + e.document = event->obj->ctx->document; + e.eventdata = &selection; + e.intval = selection.count > 0 ? selection.rows[0] : -1; + event->selection(&e, event->selectiondata); + + if(selection.count > 0) { + free(selection.rows); + } +} + +UiListSelection ui_listview_selection( + GtkTreeSelection *selection, + UiTreeEventData *event) +{ + GList *rows = gtk_tree_selection_get_selected_rows(selection, NULL); + + UiListSelection ls; + ls.count = g_list_length(rows); + ls.rows = calloc(ls.count, sizeof(int)); + GList *r = rows; + int i = 0; + while(r) { + GtkTreePath *path = r->data; + ls.rows[i] = ui_tree_path_list_index(path); + r = r->next; + i++; + } + return ls; +} + +int ui_tree_path_list_index(GtkTreePath *path) { + int depth = gtk_tree_path_get_depth(path); + if(depth == 0) { + fprintf(stderr, "UiError: treeview selection: depth == 0\n"); + return -1; + } + int *indices = gtk_tree_path_get_indices(path); + return indices[depth - 1]; +} + + +/* --------------------------- ComboBox --------------------------- */ + +UIWIDGET ui_combobox_create(UiObject *obj, UiListArgs args) { + UiObject* current = uic_current_obj(obj); + + UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1); + model->getvalue = args.getvalue; + + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST); + + GtkWidget *combobox = ui_create_combobox(obj, model, var, args.onactivate, args.onactivatedata); + UI_APPLY_LAYOUT1(current, args); + current->container->add(current->container, combobox, FALSE); + current->container->current = combobox; + return combobox; +} + +GtkWidget* ui_create_combobox(UiObject *obj, UiModel *model, UiVar *var, ui_callback f, void *udata) { + GtkWidget *combobox = gtk_combo_box_new(); + + UiListView *uicbox = malloc(sizeof(UiListView)); + uicbox->obj = obj; + uicbox->widget = combobox; + + UiList *list = var ? var->value : NULL; + GtkListStore *listmodel = create_list_store(list, model); + + if(listmodel) { + gtk_combo_box_set_model(GTK_COMBO_BOX(combobox), GTK_TREE_MODEL(listmodel)); + } + + uicbox->var = var; + uicbox->model = model; + + g_signal_connect( + combobox, + "destroy", + G_CALLBACK(ui_combobox_destroy), + uicbox); + + // bind var + if(list) { + list->update = ui_combobox_modelupdate; + // TODO: combobox getselection + list->obj = uicbox; + } + + GtkCellRenderer *renderer = gtk_cell_renderer_text_new(); + gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combobox), renderer, TRUE); + gtk_cell_layout_set_attributes( + GTK_CELL_LAYOUT(combobox), + renderer, + "text", + 0, + NULL); + gtk_combo_box_set_active(GTK_COMBO_BOX(combobox), 0); + + // add callback + if(f) { + UiEventData *event = ui_malloc(obj->ctx, sizeof(UiEventData)); + event->obj = obj; + event->userdata = udata; + event->callback = f; + event->value = 0; + event->customdata = NULL; + + g_signal_connect( + combobox, + "changed", + G_CALLBACK(ui_combobox_change_event), + event); + } + + return combobox; +} + +void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e) { + UiEvent event; + event.obj = e->obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = NULL; + event.intval = gtk_combo_box_get_active(widget); + e->callback(&event, e->userdata); +} + +void ui_combobox_modelupdate(UiList *list, int i) { + UiListView *view = list->obj; + GtkListStore *store = create_list_store(view->var->value, view->model); + gtk_combo_box_set_model(GTK_COMBO_BOX(view->widget), GTK_TREE_MODEL(store)); +} + diff -r be224cf1f50e -r 8d63b7cd3d98 ui/gtk/list.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/gtk/list.h Thu Oct 03 11:58:09 2024 +0200 @@ -0,0 +1,90 @@ +/* + * 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. + */ + +#ifndef TREE_H +#define TREE_H + +#include "../ui/tree.h" +#include "toolkit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UiListView { + UiObject *obj; + GtkWidget *widget; + UiVar *var; + UiModel *model; +} UiListView; + +typedef struct UiTreeEventData { + UiObject *obj; + ui_callback activate; + ui_callback selection; + void *activatedata; + void *selectiondata; +} UiTreeEventData; + +void* ui_strmodel_getvalue(void *elm, int column); + +UIWIDGET ui_listview_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata); +UIWIDGET ui_table_var(UiObject *obj, UiVar *var, UiModel *model, UiListCallbacks cb); + +GtkWidget* ui_get_tree_widget(UIWIDGET widget); + +void ui_listview_update(UiList *list, int i); +UiListSelection ui_listview_getselection(UiList *list); + +void ui_combobox_destroy(GtkWidget *w, UiListView *v); +void ui_listview_destroy(GtkWidget *w, UiListView *v); + +void ui_listview_activate_event( + GtkTreeView *tree_view, + GtkTreePath *path, + GtkTreeViewColumn *column, + UiTreeEventData *event); +void ui_listview_selection_event( + GtkTreeSelection *treeselection, + UiTreeEventData *event); +UiListSelection ui_listview_selection( + GtkTreeSelection *selection, + UiTreeEventData *event); +int ui_tree_path_list_index(GtkTreePath *path); + +UIWIDGET ui_combobox_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata); +GtkWidget* ui_create_combobox(UiObject *obj, UiModel *model, UiVar *var, ui_callback f, void *udata); +void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e); +void ui_combobox_modelupdate(UiList *list, int i); + +#ifdef __cplusplus +} +#endif + +#endif /* TREE_H */ + diff -r be224cf1f50e -r 8d63b7cd3d98 ui/gtk/objs.mk --- a/ui/gtk/objs.mk Thu Oct 03 11:42:14 2024 +0200 +++ b/ui/gtk/objs.mk Thu Oct 03 11:58:09 2024 +0200 @@ -38,7 +38,7 @@ GTKOBJ += button.o GTKOBJ += display.o GTKOBJ += text.o -GTKOBJ += tree.o +GTKOBJ += list.o GTKOBJ += image.o GTKOBJ += graphics.o GTKOBJ += range.o diff -r be224cf1f50e -r 8d63b7cd3d98 ui/gtk/toolbar.c --- a/ui/gtk/toolbar.c Thu Oct 03 11:42:14 2024 +0200 +++ b/ui/gtk/toolbar.c Thu Oct 03 11:58:09 2024 +0200 @@ -34,7 +34,7 @@ #include "menu.h" #include "button.h" #include "image.h" -#include "tree.h" +#include "list.h" #include #include #include diff -r be224cf1f50e -r 8d63b7cd3d98 ui/gtk/toolbar.h --- a/ui/gtk/toolbar.h Thu Oct 03 11:42:14 2024 +0200 +++ b/ui/gtk/toolbar.h Thu Oct 03 11:58:09 2024 +0200 @@ -34,7 +34,7 @@ #include #include -#include "tree.h" +#include "list.h" #ifdef __cplusplus extern "C" { diff -r be224cf1f50e -r 8d63b7cd3d98 ui/gtk/tree.c --- a/ui/gtk/tree.c Thu Oct 03 11:42:14 2024 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,666 +0,0 @@ -/* - * 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 - -#include "../common/context.h" -#include "../common/object.h" -#include "container.h" - -#include "tree.h" -#include "image.h" - - -void* ui_strmodel_getvalue(void *elm, int column) { - return column == 0 ? elm : NULL; -} - -static GtkListStore* create_list_store(UiList *list, UiModel *model) { - int columns = model->columns; - GType types[2*columns]; - int c = 0; - for(int i=0;itypes[i]) { - case UI_STRING: - case UI_STRING_FREE: types[c] = G_TYPE_STRING; break; - case UI_INTEGER: types[c] = G_TYPE_INT; break; - case UI_ICON: types[c] = G_TYPE_OBJECT; break; - case UI_ICON_TEXT: - case UI_ICON_TEXT_FREE: { - types[c] = G_TYPE_OBJECT; - types[++c] = G_TYPE_STRING; - } - } - } - - GtkListStore *store = gtk_list_store_newv(c, types); - - if(list) { - void *elm = list->first(list); - while(elm) { - // insert new row - GtkTreeIter iter; - gtk_list_store_insert (store, &iter, -1); - - // set column values - int c = 0; - for(int i=0;igetvalue(elm, c); - - GValue value = G_VALUE_INIT; - switch(model->types[i]) { - case UI_STRING: - case UI_STRING_FREE: { - g_value_init(&value, G_TYPE_STRING); - g_value_set_string(&value, data); - if(model->types[i] == UI_STRING_FREE) { - free(data); - } - break; - } - case UI_INTEGER: { - g_value_init(&value, G_TYPE_INT); - int *intptr = data; - g_value_set_int(&value, *intptr); - break; - } - case UI_ICON: { - g_value_init(&value, G_TYPE_OBJECT); - UiIcon *icon = data; -#if GTK_MAJOR_VERSION >= 4 - g_value_set_object(&value, icon->info); // TODO: does this work? -#else - if(!icon->pixbuf && icon->info) { - GError *error = NULL; - GdkPixbuf *pixbuf = gtk_icon_info_load_icon(icon->info, &error); - icon->pixbuf = pixbuf; - } - - if(icon->pixbuf) { - g_value_set_object(&value, icon->pixbuf); - } -#endif - break; - } - case UI_ICON_TEXT: - case UI_ICON_TEXT_FREE: { - UiIcon *icon = data; -#if GTK_MAJOR_VERSION >= 4 - GValue iconvalue = G_VALUE_INIT; - g_value_init(&iconvalue, G_TYPE_OBJECT); - g_value_set_object(&iconvalue, ui_icon_pixbuf(icon)); - gtk_list_store_set_value(store, &iter, c, &iconvalue); -#else - GValue pixbufvalue = G_VALUE_INIT; - if(!icon->pixbuf && icon->info) { - GError *error = NULL; - GdkPixbuf *pixbuf = gtk_icon_info_load_icon(icon->info, &error); - icon->pixbuf = pixbuf; - } - g_value_init(&pixbufvalue, G_TYPE_OBJECT); - g_value_set_object(&pixbufvalue, icon->pixbuf); - gtk_list_store_set_value(store, &iter, c, &pixbufvalue); -#endif - c++; - - char *str = model->getvalue(elm, c); - g_value_init(&value, G_TYPE_STRING); - g_value_set_string(&value, str); - if(model->types[i] == UI_ICON_TEXT_FREE) { - free(str); - } - break; - } - } - - gtk_list_store_set_value(store, &iter, c, &value); - } - - // next row - elm = list->next(list); - } - } - - return store; -} - - -UIWIDGET ui_listview_create(UiObject *obj, UiListArgs args) { - UiObject* current = uic_current_obj(obj); - - // create treeview - GtkWidget *view = gtk_tree_view_new(); - GtkCellRenderer *renderer = gtk_cell_renderer_text_new(); - GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes(NULL, renderer, "text", 0, NULL); - gtk_tree_view_append_column(GTK_TREE_VIEW(view), column); - - gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE); -#ifdef UI_GTK3 -#if GTK_MINOR_VERSION >= 8 - gtk_tree_view_set_activate_on_single_click(GTK_TREE_VIEW(view), TRUE); -#else - // TODO: implement for older gtk3 -#endif -#else - // TODO: implement for gtk2 -#endif - - UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1); - model->getvalue = args.getvalue; - - UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST); - - UiList *list = var ? var->value : NULL; - GtkListStore *listmodel = create_list_store(list, model); - gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel)); - - UiListView *listview = malloc(sizeof(UiListView)); - listview->obj = obj; - listview->widget = view; - listview->var = var; - listview->model = model; - g_signal_connect( - view, - "destroy", - G_CALLBACK(ui_listview_destroy), - listview); - - // bind var - list->update = ui_listview_update; - list->getselection = ui_listview_getselection; - list->obj = listview; - - // add callback - if(args.onactivate) { - UiTreeEventData *event = ui_malloc(obj->ctx, sizeof(UiTreeEventData)); - event->obj = obj; - event->activatedata = args.onactivatedata; - event->activate = args.onactivate; - event->selection = NULL; - - g_signal_connect( - view, - "row-activated", - G_CALLBACK(ui_listview_activate_event), - event); - } - - // add widget to the current container - GtkWidget *scroll_area = SCROLLEDWINDOW_NEW(); - gtk_scrolled_window_set_policy( - GTK_SCROLLED_WINDOW(scroll_area), - GTK_POLICY_AUTOMATIC, - GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS - SCROLLEDWINDOW_SET_CHILD(scroll_area, view); - - UI_APPLY_LAYOUT1(current, args); - current->container->add(current->container, scroll_area, FALSE); - - // ct->current should point to view, not scroll_area, to make it possible - // to add a context menu - current->container->current = view; - - return scroll_area; -} - -/* -static void drag_begin(GtkWidget *widget, GdkDragContext *context, gpointer udata) { - printf("drag begin\n"); - -} - -static void drag_end( - GtkWidget *widget, - GdkDragContext *context, - guint time, - gpointer udata) -{ - printf("drag end\n"); - -} -*/ - -/* -static GtkTargetEntry targetentries[] = - { - { "STRING", 0, 0 }, - { "text/plain", 0, 1 }, - { "text/uri-list", 0, 2 }, - }; -*/ - -UIWIDGET ui_table_create(UiObject *obj, UiListArgs args) { - UiObject* current = uic_current_obj(obj); - - // create treeview - GtkWidget *view = gtk_tree_view_new(); - - UiModel *model = args.model; - int columns = model ? model->columns : 0; - - int addi = 0; - for(int i=0;itypes[i] == UI_ICON_TEXT) { - column = gtk_tree_view_column_new(); - gtk_tree_view_column_set_title(column, model->titles[i]); - - GtkCellRenderer *iconrenderer = gtk_cell_renderer_pixbuf_new(); - GtkCellRenderer *textrenderer = gtk_cell_renderer_text_new(); - - gtk_tree_view_column_pack_end(column, textrenderer, TRUE); - gtk_tree_view_column_pack_start(column, iconrenderer, FALSE); - - - gtk_tree_view_column_add_attribute(column, iconrenderer, "pixbuf", i); - gtk_tree_view_column_add_attribute(column, textrenderer, "text", i+1); - - addi++; - } else if (model->types[i] == UI_ICON) { - GtkCellRenderer *iconrenderer = gtk_cell_renderer_pixbuf_new(); - column = gtk_tree_view_column_new_with_attributes( - model->titles[i], - iconrenderer, - "pixbuf", - i + addi, - NULL); - } else { - GtkCellRenderer *renderer = gtk_cell_renderer_text_new(); - column = gtk_tree_view_column_new_with_attributes( - model->titles[i], - renderer, - "text", - i + addi, - NULL); - } - gtk_tree_view_column_set_resizable(column, TRUE); - gtk_tree_view_append_column(GTK_TREE_VIEW(view), column); - } - - //gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE); -#ifdef UI_GTK3 - //gtk_tree_view_set_activate_on_single_click(GTK_TREE_VIEW(view), TRUE); -#else - -#endif - - UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST); - - UiList *list = var ? var->value : NULL; - GtkListStore *listmodel = create_list_store(list, model); - gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel)); - - //g_signal_connect(view, "drag-begin", G_CALLBACK(drag_begin), NULL); - //g_signal_connect(view, "drag-end", G_CALLBACK(drag_end), NULL); - - // add TreeView as observer to the UiList to update the TreeView if the - // data changes - UiListView *tableview = malloc(sizeof(UiListView)); - tableview->obj = obj; - tableview->widget = view; - tableview->var = var; - tableview->model = model; - g_signal_connect( - view, - "destroy", - G_CALLBACK(ui_listview_destroy), - tableview); - - // bind var - list->update = ui_listview_update; - list->getselection = ui_listview_getselection; - list->obj = tableview; - - // add callback - UiTreeEventData *event = ui_malloc(obj->ctx, sizeof(UiTreeEventData)); - event->obj = obj; - event->activate = args.onactivate; - event->selection = args.onselection; - event->activatedata = args.onactivatedata; - event->selectiondata = args.onselectiondata; - if(args.onactivate) { - g_signal_connect( - view, - "row-activated", - G_CALLBACK(ui_listview_activate_event), - event); - } - if(args.onselection) { - GtkTreeSelection *selection = gtk_tree_view_get_selection( - GTK_TREE_VIEW(view)); - g_signal_connect( - selection, - "changed", - G_CALLBACK(ui_listview_selection_event), - event); - } - // TODO: destroy callback - - - GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(view)); - gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE); - - // add widget to the current container - GtkWidget *scroll_area = SCROLLEDWINDOW_NEW(); - gtk_scrolled_window_set_policy( - GTK_SCROLLED_WINDOW(scroll_area), - GTK_POLICY_AUTOMATIC, - GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS - SCROLLEDWINDOW_SET_CHILD(scroll_area, view); - - UI_APPLY_LAYOUT1(current, args); - current->container->add(current->container, scroll_area, FALSE); - - // ct->current should point to view, not scroll_area, to make it possible - // to add a context menu - current->container->current = view; - - return scroll_area; -} - - -GtkWidget* ui_get_tree_widget(UIWIDGET widget) { - return SCROLLEDWINDOW_GET_CHILD(widget); -} - -static char** targets2array(char *target0, va_list ap, int *nelm) { - int al = 16; - char **targets = calloc(16, sizeof(char*)); - targets[0] = target0; - - int i = 1; - char *target; - while((target = va_arg(ap, char*)) != NULL) { - if(i >= al) { - al *= 2; - targets = realloc(targets, al*sizeof(char*)); - } - targets[i] = target; - i++; - } - - *nelm = i; - return targets; -} - -/* -static GtkTargetEntry* targetstr2gtktargets(char **str, int nelm) { - GtkTargetEntry *targets = calloc(nelm, sizeof(GtkTargetEntry)); - for(int i=0;iobj; - GtkListStore *store = create_list_store(list, view->model); - gtk_tree_view_set_model(GTK_TREE_VIEW(view->widget), GTK_TREE_MODEL(store)); - g_object_unref(G_OBJECT(store)); - // TODO: free old model -} - -UiListSelection ui_listview_getselection(UiList *list) { - UiListView *view = list->obj; - UiListSelection selection = ui_listview_selection( - gtk_tree_view_get_selection(GTK_TREE_VIEW(view->widget)), - NULL); - return selection; -} - -void ui_listview_destroy(GtkWidget *w, UiListView *v) { - gtk_tree_view_set_model(GTK_TREE_VIEW(w), NULL); - ui_destroy_boundvar(v->obj->ctx, v->var); - // TODO: destroy model? - free(v); -} - -void ui_combobox_destroy(GtkWidget *w, UiListView *v) { - gtk_combo_box_set_model(GTK_COMBO_BOX(w), NULL); - ui_destroy_boundvar(v->obj->ctx, v->var); - // TODO: destroy model? - free(v); -} - - -void ui_listview_activate_event( - GtkTreeView *treeview, - GtkTreePath *path, - GtkTreeViewColumn *column, - UiTreeEventData *event) -{ - UiListSelection selection = ui_listview_selection( - gtk_tree_view_get_selection(treeview), - event); - - UiEvent e; - e.obj = event->obj; - e.window = event->obj->window; - e.document = event->obj->ctx->document; - e.eventdata = &selection; - e.intval = selection.count > 0 ? selection.rows[0] : -1; - event->activate(&e, event->activatedata); - - if(selection.count > 0) { - free(selection.rows); - } -} - -void ui_listview_selection_event( - GtkTreeSelection *treeselection, - UiTreeEventData *event) -{ - UiListSelection selection = ui_listview_selection(treeselection, event); - - UiEvent e; - e.obj = event->obj; - e.window = event->obj->window; - e.document = event->obj->ctx->document; - e.eventdata = &selection; - e.intval = selection.count > 0 ? selection.rows[0] : -1; - event->selection(&e, event->selectiondata); - - if(selection.count > 0) { - free(selection.rows); - } -} - -UiListSelection ui_listview_selection( - GtkTreeSelection *selection, - UiTreeEventData *event) -{ - GList *rows = gtk_tree_selection_get_selected_rows(selection, NULL); - - UiListSelection ls; - ls.count = g_list_length(rows); - ls.rows = calloc(ls.count, sizeof(int)); - GList *r = rows; - int i = 0; - while(r) { - GtkTreePath *path = r->data; - ls.rows[i] = ui_tree_path_list_index(path); - r = r->next; - i++; - } - return ls; -} - -int ui_tree_path_list_index(GtkTreePath *path) { - int depth = gtk_tree_path_get_depth(path); - if(depth == 0) { - fprintf(stderr, "UiError: treeview selection: depth == 0\n"); - return -1; - } - int *indices = gtk_tree_path_get_indices(path); - return indices[depth - 1]; -} - - -/* --------------------------- ComboBox --------------------------- */ - -UIWIDGET ui_combobox_create(UiObject *obj, UiListArgs args) { - UiObject* current = uic_current_obj(obj); - - UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1); - model->getvalue = args.getvalue; - - UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST); - - GtkWidget *combobox = ui_create_combobox(obj, model, var, args.onactivate, args.onactivatedata); - UI_APPLY_LAYOUT1(current, args); - current->container->add(current->container, combobox, FALSE); - current->container->current = combobox; - return combobox; -} - -GtkWidget* ui_create_combobox(UiObject *obj, UiModel *model, UiVar *var, ui_callback f, void *udata) { - GtkWidget *combobox = gtk_combo_box_new(); - - UiListView *uicbox = malloc(sizeof(UiListView)); - uicbox->obj = obj; - uicbox->widget = combobox; - - UiList *list = var ? var->value : NULL; - GtkListStore *listmodel = create_list_store(list, model); - - if(listmodel) { - gtk_combo_box_set_model(GTK_COMBO_BOX(combobox), GTK_TREE_MODEL(listmodel)); - } - - uicbox->var = var; - uicbox->model = model; - - g_signal_connect( - combobox, - "destroy", - G_CALLBACK(ui_combobox_destroy), - uicbox); - - // bind var - if(list) { - list->update = ui_combobox_modelupdate; - // TODO: combobox getselection - list->obj = uicbox; - } - - GtkCellRenderer *renderer = gtk_cell_renderer_text_new(); - gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combobox), renderer, TRUE); - gtk_cell_layout_set_attributes( - GTK_CELL_LAYOUT(combobox), - renderer, - "text", - 0, - NULL); - gtk_combo_box_set_active(GTK_COMBO_BOX(combobox), 0); - - // add callback - if(f) { - UiEventData *event = ui_malloc(obj->ctx, sizeof(UiEventData)); - event->obj = obj; - event->userdata = udata; - event->callback = f; - event->value = 0; - event->customdata = NULL; - - g_signal_connect( - combobox, - "changed", - G_CALLBACK(ui_combobox_change_event), - event); - } - - return combobox; -} - -void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e) { - UiEvent event; - event.obj = e->obj; - event.window = event.obj->window; - event.document = event.obj->ctx->document; - event.eventdata = NULL; - event.intval = gtk_combo_box_get_active(widget); - e->callback(&event, e->userdata); -} - -void ui_combobox_modelupdate(UiList *list, int i) { - UiListView *view = list->obj; - GtkListStore *store = create_list_store(view->var->value, view->model); - gtk_combo_box_set_model(GTK_COMBO_BOX(view->widget), GTK_TREE_MODEL(store)); -} - diff -r be224cf1f50e -r 8d63b7cd3d98 ui/gtk/tree.h --- a/ui/gtk/tree.h Thu Oct 03 11:42:14 2024 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,90 +0,0 @@ -/* - * 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. - */ - -#ifndef TREE_H -#define TREE_H - -#include "../ui/tree.h" -#include "toolkit.h" - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct UiListView { - UiObject *obj; - GtkWidget *widget; - UiVar *var; - UiModel *model; -} UiListView; - -typedef struct UiTreeEventData { - UiObject *obj; - ui_callback activate; - ui_callback selection; - void *activatedata; - void *selectiondata; -} UiTreeEventData; - -void* ui_strmodel_getvalue(void *elm, int column); - -UIWIDGET ui_listview_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata); -UIWIDGET ui_table_var(UiObject *obj, UiVar *var, UiModel *model, UiListCallbacks cb); - -GtkWidget* ui_get_tree_widget(UIWIDGET widget); - -void ui_listview_update(UiList *list, int i); -UiListSelection ui_listview_getselection(UiList *list); - -void ui_combobox_destroy(GtkWidget *w, UiListView *v); -void ui_listview_destroy(GtkWidget *w, UiListView *v); - -void ui_listview_activate_event( - GtkTreeView *tree_view, - GtkTreePath *path, - GtkTreeViewColumn *column, - UiTreeEventData *event); -void ui_listview_selection_event( - GtkTreeSelection *treeselection, - UiTreeEventData *event); -UiListSelection ui_listview_selection( - GtkTreeSelection *selection, - UiTreeEventData *event); -int ui_tree_path_list_index(GtkTreePath *path); - -UIWIDGET ui_combobox_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata); -GtkWidget* ui_create_combobox(UiObject *obj, UiModel *model, UiVar *var, ui_callback f, void *udata); -void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e); -void ui_combobox_modelupdate(UiList *list, int i); - -#ifdef __cplusplus -} -#endif - -#endif /* TREE_H */ -