--- a/ui/gtk/tree.c Sun Sep 29 20:25:41 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 <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <stdarg.h> - -#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;i<columns;i++,c++) { - switch(model->types[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;i<columns;i++,c++) { - void *data = model->getvalue(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;i<columns;i++) { - GtkTreeViewColumn *column = NULL; - if(model->types[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;i<nelm;i++) { - targets[i].target = str[i]; - } - return targets; -} -*/ - -void ui_table_dragsource(UIWIDGET tablewidget, int actions, char *target0, ...) { - va_list ap; - va_start(ap, target0); - int nelm; - char **targets = targets2array(target0, ap, &nelm); - va_end(ap); - - // disabled - //ui_table_dragsource_a(tablewidget, actions, targets, nelm); - - free(targets); -} - -/* -void ui_table_dragsource_a(UIWIDGET tablewidget, int actions, char **targets, int nelm) { - GtkTargetEntry* t = targetstr2gtktargets(targets, nelm); - gtk_tree_view_enable_model_drag_source( - GTK_TREE_VIEW(ui_get_tree_widget(tablewidget)), - GDK_BUTTON1_MASK, - t, - nelm, - GDK_ACTION_COPY|GDK_ACTION_MOVE|GDK_ACTION_LINK); - free(t); -} - - -void ui_table_dragdest(UIWIDGET tablewidget, int actions, char *target0, ...) { - va_list ap; - va_start(ap, target0); - int nelm; - char **targets = targets2array(target0, ap, &nelm); - va_end(ap); - ui_table_dragdest_a(tablewidget, actions, targets, nelm); - free(targets); -} - -void ui_table_dragdest_a(UIWIDGET tablewidget, int actions, char **targets, int nelm) { - GtkTargetEntry* t = targetstr2gtktargets(targets, nelm); - gtk_tree_view_enable_model_drag_dest( - GTK_TREE_VIEW(ui_get_tree_widget(tablewidget)), - t, - nelm, - GDK_ACTION_COPY|GDK_ACTION_MOVE|GDK_ACTION_LINK); - free(t); -} -*/ - -void ui_listview_update(UiList *list, int i) { - UiListView *view = list->obj; - 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)); -} -