add new gtk4 tableview implementation

Sun, 05 Jan 2025 16:44:37 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sun, 05 Jan 2025 16:44:37 +0100
changeset 436
222205801430
parent 435
883a569cc9a3
child 437
f02a62de0328

add new gtk4 tableview implementation

application/main.c file | annotate | diff | comparison | revisions
ui/gtk/list.c file | annotate | diff | comparison | revisions
ui/gtk/list.h file | annotate | diff | comparison | revisions
--- a/application/main.c	Sun Jan 05 10:30:39 2025 +0100
+++ b/application/main.c	Sun Jan 05 16:44:37 2025 +0100
@@ -273,10 +273,27 @@
     printf("sourcelist %s index %d\n", event->eventdata, event->intval);
 }
 
+void action_table_activate(UiEvent *event, void *userdata) {
+    char *s = userdata;
+    printf("table event: %s\n", s);
+    UiListSelection *sel = event->eventdata;
+    for(int i=0;i<sel->count;i++) {
+        printf("%d\n", sel->rows[i]);
+    }
+    printf("\n");
+}
+
 UiMenuBuilder *menubuilder;
 
 void* table_getvalue(void *row, int col) {
-    return row;
+    switch(col) {
+        case 0: return row;
+        case 1: return (void*)(intptr_t)1234;
+        case 2: return ui_foldericon(16);
+        case 3: return ui_fileicon(16);
+        case 4: return "file";
+    }
+    return NULL;
 }
 
 void sourcelist_getvalue(void *sublistdata, void *rowdata, int index, UiSubListItem *item) {
@@ -345,7 +362,7 @@
     }
     
     ui_tabview(obj, .spacing=10, .margin=10, .tabview = UI_TABVIEW_NAVIGATION_SIDE, .varname="tabview") {
-        ui_tab(obj, "Tab 1") {
+        ui_tab(obj, "Tab 0") {
             ui_vbox(obj, .fill = UI_OFF, .margin = 15, .spacing = 15) {
                 ui_button(obj, .label = "Test Button", .icon = "application-x-generic", .onclick = action_button);
                 ui_togglebutton(obj, .label = "Toggle");
@@ -392,12 +409,15 @@
                     ui_radiobutton(obj, .label = "Radio 3", .varname = "radio");
                 }
                 ui_newline(obj);
-                
-                UiModel *model = ui_model(obj->ctx, UI_STRING, "col1", -1);
-                model->getvalue = table_getvalue;
-                ui_table(obj, .model = model, .list = doc->list2, .colspan = 2, .hexpand = TRUE, .contextmenu = menubuilder);
             }
         }
+        ui_tab(obj, "Tab 1") {
+            UiModel *model = ui_model(obj->ctx, UI_STRING, "col1", UI_INTEGER, "col2", UI_ICON, "col3", UI_ICON_TEXT, "col4", UI_INTEGER, "col5", -1);
+            model->getvalue = table_getvalue;
+            ui_table(obj, .model = model, .list = doc->list2, .colspan = 2, .fill = UI_ON, .contextmenu = menubuilder, .multiselection = TRUE,
+                    .onactivate = action_table_activate, .onactivatedata = "activate",
+                    .onselection = action_table_activate, .onselectiondata = "selection");
+        }
         ui_tab(obj, "Tab 2") {
             ui_button(obj, .label = "Button 1 Start Thread", .onclick=action_start_thread);
             ui_button(obj, .label = "Button 2 Notify Thread", .onclick=action_notify_thread);
--- a/ui/gtk/list.c	Sun Jan 05 10:30:39 2025 +0100
+++ b/ui/gtk/list.c	Sun Jan 05 16:44:37 2025 +0100
@@ -290,6 +290,329 @@
     };
 */
 
+#if GTK_CHECK_VERSION(4, 10, 0)
+
+
+/* BEGIN GObject wrapper for generic pointers */
+
+typedef struct _ObjWrapper {
+    GObject parent_instance;
+    void *data;
+} ObjWrapper;
+
+typedef struct _ObjWrapperClass {
+    GObjectClass parent_class;
+} ObjWrapperClass;
+
+G_DEFINE_TYPE(ObjWrapper, obj_wrapper, G_TYPE_OBJECT)
+
+static void obj_wrapper_class_init(ObjWrapperClass *klass) {
+    
+}
+
+static void obj_wrapper_init(ObjWrapper *self) {
+    self->data = NULL;
+}
+
+ObjWrapper* obj_wrapper_new(void* data) {
+    ObjWrapper *obj = g_object_new(obj_wrapper_get_type(), NULL);
+    obj->data = data;
+    return obj;
+}
+
+/* END GObject wrapper for generic pointers */
+
+static void column_factory_setup(GtkListItemFactory *factory, GtkListItem *item, gpointer userdata) {
+    UiColData *col = userdata;
+    UiModel *model = col->listview->model;
+    UiModelType type = model->types[col->model_column];
+    if(type == UI_ICON_TEXT || type == UI_ICON_TEXT_FREE) {
+        GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
+        GtkWidget *image = gtk_image_new();
+        GtkWidget *label = gtk_label_new(NULL);
+        BOX_ADD(hbox, image);
+        BOX_ADD(hbox, label);
+        gtk_list_item_set_child(item, hbox);
+        g_object_set_data(G_OBJECT(hbox), "image", image);
+        g_object_set_data(G_OBJECT(hbox), "label", label);
+    } else if(type == UI_ICON) {
+        GtkWidget *image = gtk_image_new();
+        gtk_list_item_set_child(item, image);
+    } else {
+        GtkWidget *label = gtk_label_new(NULL);
+        gtk_list_item_set_child(item, label);
+    }
+}
+
+static void column_factory_bind( GtkListItemFactory *factory, GtkListItem *item, gpointer userdata) {
+    UiColData *col = userdata;
+    
+    ObjWrapper *obj = gtk_list_item_get_item(item);
+    UiModel *model = col->listview->model;
+    UiModelType type = model->types[col->model_column];
+    
+    void *data = model->getvalue(obj->data, col->data_column);
+    GtkWidget *child = gtk_list_item_get_child(item);
+    
+    bool freevalue = TRUE;
+    switch(type) {
+        case UI_STRING: {
+            freevalue = FALSE;
+        }
+        case UI_STRING_FREE: {
+            gtk_label_set_label(GTK_LABEL(child), data);
+            if(freevalue) {
+                free(data);
+            }
+            break;
+        }
+        case UI_INTEGER: {
+            intptr_t intvalue = (intptr_t)data;
+            char buf[32];
+            snprintf(buf, 32, "%d", (int)intvalue);
+            gtk_label_set_label(GTK_LABEL(child), buf);
+            break;
+        }
+        case UI_ICON: {
+            UiIcon *icon = data;
+            if(icon) {
+                gtk_image_set_from_paintable(GTK_IMAGE(child), GDK_PAINTABLE(icon->info));
+            }
+            break;
+        }
+        case UI_ICON_TEXT: {
+            freevalue = FALSE;
+        }
+        case UI_ICON_TEXT_FREE: {
+            void *data2 = model->getvalue(obj->data, col->data_column+1);
+            GtkWidget *image = g_object_get_data(G_OBJECT(child), "image");
+            GtkWidget *label = g_object_get_data(G_OBJECT(child), "label");
+            if(data && image) {
+                UiIcon *icon = data;
+                gtk_image_set_from_paintable(GTK_IMAGE(image), GDK_PAINTABLE(icon->info));
+            }
+            if(data2 && label) {
+                gtk_label_set_label(GTK_LABEL(label), data2);
+            }
+            if(freevalue) {
+                free(data2);
+            }
+            break;
+        }
+    }
+}
+
+UIWIDGET ui_table_create(UiObject *obj, UiListArgs args) {
+    UiObject* current = uic_current_obj(obj);
+    
+    GListStore *ls = g_list_store_new(G_TYPE_OBJECT);
+    //g_list_store_append(ls, v1);
+    
+    GtkSelectionModel *selection_model;
+    if(args.multiselection) {
+        selection_model = GTK_SELECTION_MODEL(gtk_multi_selection_new(G_LIST_MODEL(ls)));
+    } else {
+        selection_model = GTK_SELECTION_MODEL(gtk_single_selection_new(G_LIST_MODEL(ls)));
+    }
+    
+    GtkWidget *view = gtk_column_view_new(GTK_SELECTION_MODEL(selection_model));
+    
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST);
+    
+    // create obj to store all relevant data we need for handling events
+    // and list updates
+    UiListView *tableview = malloc(sizeof(UiListView));
+    tableview->obj = obj;
+    tableview->widget = view;
+    tableview->var = var;
+    tableview->model = args.model;
+    tableview->liststore = ls;
+    tableview->selectionmodel = selection_model;
+    tableview->onactivate = args.onactivate;
+    tableview->onactivatedata = args.onactivatedata;
+    tableview->onselection = args.onselection;
+    tableview->onselectiondata = args.onselectiondata;
+    tableview->ondragstart = args.ondragstart;
+    tableview->ondragstartdata = args.ondragstartdata;
+    tableview->ondragcomplete = args.ondragcomplete;
+    tableview->ondragcompletedata = args.ondragcompletedata;
+    tableview->ondrop = args.ondrop;
+    tableview->ondropdata = args.ondropsdata;
+    tableview->selection.count = 0;
+    tableview->selection.rows = NULL;
+    g_signal_connect(
+                view,
+                "destroy",
+                G_CALLBACK(ui_listview_destroy),
+                tableview);
+    
+    
+    // create columns from UiModel
+    UiModel *model = args.model;
+    int columns = model ? model->columns : 0;
+    
+    tableview->columns = calloc(columns, sizeof(UiColData));
+    
+    int addi = 0;
+    for(int i=0;i<columns;i++) {
+        tableview->columns[i].listview = tableview;
+        tableview->columns[i].model_column = i;
+        tableview->columns[i].data_column = i+addi;
+        
+        if(model->types[i] == UI_ICON_TEXT || model->types[i] == UI_ICON_TEXT_FREE) {
+            // icon+text has 2 data columns
+            addi++;
+        }
+        
+        GtkListItemFactory *factory = gtk_signal_list_item_factory_new();
+        UiColData *col = &tableview->columns[i]; 
+        g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), col);
+	g_signal_connect(factory, "bind", G_CALLBACK(column_factory_bind), col);
+        
+        GtkColumnViewColumn *column = gtk_column_view_column_new(model->titles[i], factory);
+        gtk_column_view_column_set_resizable(column, true);
+        gtk_column_view_append_column(GTK_COLUMN_VIEW(view), column);
+    }
+    
+    // bind listview to list
+    if(var && var->value) {
+        UiList *list = var->value;
+        
+        list->obj = tableview;
+        list->update = ui_listview_update2;
+        list->getselection = ui_listview_getselection2;
+        list->setselection = ui_listview_setselection2;
+        
+        ui_update_liststore(ls, list);
+    }
+    
+    // event handling
+    if(args.onactivate) {
+        g_signal_connect(view, "activate", G_CALLBACK(ui_columnview_activate), tableview);
+    }
+    // always handle selection-changed, to keep track of the current selection
+    g_signal_connect(selection_model, "selection-changed", G_CALLBACK(ui_listview_selection_changed), tableview);
+    
+    // add widget to parent
+    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 UiListSelection selectionmodel_get_selection(GtkSelectionModel *model) {
+    UiListSelection sel = { 0, NULL };
+    GtkBitset *bitset = gtk_selection_model_get_selection(model);
+    int n = gtk_bitset_get_size(bitset);
+    printf("bitset %d\n", n);
+    
+    gtk_bitset_unref(bitset);
+    return sel;
+}
+
+static void listview_event(ui_callback cb, void *cbdata, UiListView *view) {
+    UiEvent event;
+    event.obj = view->obj;
+    event.document = event.obj->ctx->document;
+    event.window = event.obj->window;
+    event.intval = view->selection.count;
+    event.eventdata = &view->selection;
+    if(cb) {
+        cb(&event, cbdata);
+    }
+}
+
+void ui_columnview_activate(GtkColumnView* self, guint position, gpointer userdata) {
+    UiListView *view = userdata;
+    listview_event(view->onactivate, view->onactivatedata, view);
+}
+
+void ui_listview_selection_changed(GtkSelectionModel* self, guint position, guint n_items, gpointer userdata) {
+    UiListView *view = userdata;
+    free(view->selection.rows);
+    view->selection.count = 0;
+    view->selection.rows = NULL;
+    
+    CX_ARRAY_DECLARE(int, newselection);
+    cx_array_initialize(newselection, 8);
+     
+    size_t nitems = g_list_model_get_n_items(G_LIST_MODEL(view->liststore));
+    
+    for(size_t i=0;i<nitems;i++) {
+        if(gtk_selection_model_is_selected(view->selectionmodel, i)) {
+            int s = (int)i;
+            cx_array_simple_add(newselection, s);
+        }
+    }
+    
+    if(newselection_size > 0) {
+        view->selection.count = newselection_size;
+        view->selection.rows = newselection;
+    } else {
+        free(newselection);
+    }
+    
+    listview_event(view->onselection, view->onselectiondata, view);
+}
+
+void ui_update_liststore(GListStore *liststore, UiList *list) {
+    g_list_store_remove_all(liststore);
+    void *elm = list->first(list);
+    while(elm) {
+        ObjWrapper *obj = obj_wrapper_new(elm);
+        g_list_store_append(liststore, obj);
+        elm = list->next(list);
+    }
+}
+
+void ui_listview_update2(UiList *list, int i) {
+    UiListView *view = list->obj;
+    ui_update_liststore(view->liststore, view->var->value);
+}
+
+UiListSelection ui_listview_getselection2(UiList *list) {
+    UiListView *view = list->obj;
+    UiListSelection selection;
+    selection.count = view->selection.count;
+    selection.rows = calloc(selection.count, sizeof(int));
+    memcpy(selection.rows, view->selection.rows, selection.count*sizeof(int));
+    return selection;
+}
+
+void ui_listview_setselection2(UiList *list, UiListSelection selection) {
+    UiListView *view = list->obj;
+    UiListSelection newselection;
+    newselection.count = view->selection.count;
+    if(selection.count > 0) {
+        newselection.rows = calloc(newselection.count, sizeof(int));
+        memcpy(newselection.rows, selection.rows, selection.count*sizeof(int));
+    } else {
+        newselection.rows = NULL;
+    }
+    free(view->selection.rows);
+    view->selection = newselection;
+    
+    gtk_selection_model_unselect_all(view->selectionmodel);
+    if(selection.count > 0) {
+        for(int i=0;i<selection.count;i++) {
+            gtk_selection_model_select_item(view->selectionmodel, selection.rows[i], FALSE);
+        }
+    }
+}
+
+#else
+
 UIWIDGET ui_table_create(UiObject *obj, UiListArgs args) {
     UiObject* current = uic_current_obj(obj);
     
@@ -376,6 +699,8 @@
     tableview->ondragcompletedata = args.ondragcompletedata;
     tableview->ondrop = args.ondrop;
     tableview->ondropdata = args.ondropsdata;
+    tableview->selection.count = 0;
+    tableview->selection.rows = NULL;
     g_signal_connect(
                 view,
                 "destroy",
@@ -453,6 +778,10 @@
     return scroll_area;
 }
 
+
+#endif
+
+
 #if GTK_MAJOR_VERSION >= 4
 
 static GdkContentProvider *ui_listview_dnd_prepare(GtkDragSource *source, double x, double y, void *data) {
@@ -789,6 +1118,10 @@
 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);
+#if GTK_CHECK_VERSION(4, 10, 0)
+    free(v->columns);
+#endif
+    free(v->selection.rows);
     free(v);
 }
 
--- a/ui/gtk/list.h	Sun Jan 05 10:30:39 2025 +0100
+++ b/ui/gtk/list.h	Sun Jan 05 16:44:37 2025 +0100
@@ -37,21 +37,39 @@
 #ifdef	__cplusplus
 extern "C" {
 #endif
+    
+typedef struct UiColData UiColData;
 
 typedef struct UiListView {
-    UiObject    *obj;
-    GtkWidget   *widget;
-    UiVar       *var;
-    UiModel     *model;
-    ui_callback ondragstart;
-    void        *ondragstartdata;
-    ui_callback ondragcomplete;
-    void        *ondragcompletedata;
-    ui_callback ondrop;
-    void        *ondropdata;
+    UiObject          *obj;
+    GtkWidget         *widget;
+    UiVar             *var;
+    UiModel           *model;
+#if GTK_CHECK_VERSION(4, 10, 0)
+    GListStore        *liststore;
+    GtkSelectionModel *selectionmodel;
+    UiColData         *columns;
+#endif
+    ui_callback       onactivate;
+    void              *onactivatedata;
+    ui_callback       onselection;
+    void              *onselectiondata;
+    ui_callback       ondragstart;
+    void              *ondragstartdata;
+    ui_callback       ondragcomplete;
+    void              *ondragcompletedata;
+    ui_callback       ondrop;
+    void              *ondropdata;
+    UiListSelection   selection;
     
 } UiListView;
 
+struct UiColData {
+    UiListView *listview;
+    int model_column;
+    int data_column;
+};
+
 typedef struct UiTreeEventData {
     UiObject    *obj;
     ui_callback activate;
@@ -86,6 +104,20 @@
     GtkListBoxRow            *first_row;
 };
 
+
+#if GTK_CHECK_VERSION(4, 10, 0)
+
+void ui_update_liststore(GListStore *liststore, UiList *list);
+
+void ui_listview_update2(UiList *list, int i);
+UiListSelection ui_listview_getselection2(UiList *list);
+void ui_listview_setselection2(UiList *list, UiListSelection selection);
+
+void ui_columnview_activate(GtkColumnView* self, guint position, gpointer userdata);
+void ui_listview_selection_changed(GtkSelectionModel* self, guint position, guint n_items, gpointer user_data);
+
+#endif
+
 void* ui_strmodel_getvalue(void *elm, int column);
 
 UIWIDGET ui_listview_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata);

mercurial