ui/gtk/list.c

changeset 109
c3dfcb8f0be7
parent 108
77254bd6dccb
child 110
c00e968d018b
--- a/ui/gtk/list.c	Sun Jul 20 22:04:39 2025 +0200
+++ b/ui/gtk/list.c	Sun Aug 24 15:24:16 2025 +0200
@@ -39,21 +39,22 @@
 #include <cx/linked_list.h>
 
 #include "list.h"
+#include "button.h"
 #include "icon.h"
 #include "menu.h"
 #include "dnd.h"
 
 
-void* ui_strmodel_getvalue(void *elm, int column) {
-    return column == 0 ? elm : NULL;
+static void* getvalue_wrapper(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult) {
+    ui_getvaluefunc getvalue = (ui_getvaluefunc)userdata;
+    return getvalue(elm, col);
 }
 
-static void* model_getvalue(UiModel *model, UiList *list, void *elm, int row, int col, UiBool *freeResult) {
-    if(model->getvalue2) {
-        return model->getvalue2(list, elm, row, col, model->getvalue2data, freeResult);
-    } else if(model->getvalue) {
-        return model->getvalue(elm, col);
-    }
+static void* str_getvalue(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult) {
+    return elm;
+}
+
+static void* null_getvalue(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult) {
     return NULL;
 }
 
@@ -74,6 +75,37 @@
     }
 }
 
+static UiListView* create_listview(UiObject *obj, UiListArgs *args) {
+    UiListView *tableview = malloc(sizeof(UiListView));
+    memset(tableview, 0, sizeof(UiListView));
+    tableview->obj = obj;
+    tableview->model = args->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->ondropdata;
+    tableview->selection.count = 0;
+    tableview->selection.rows = NULL;
+    
+    if(args->getvalue2) {
+        tableview->getvalue = args->getvalue2;
+        tableview->getvaluedata = args->getvalue2data;
+    } else if(args->getvalue) {
+        tableview->getvalue = getvalue_wrapper;
+        tableview->getvaluedata = (void*)args->getvalue;
+    } else {
+        tableview->getvalue = null_getvalue;
+    }
+    
+    return tableview;
+}
+
 #if GTK_CHECK_VERSION(4, 10, 0)
 
 
@@ -131,16 +163,17 @@
     }
 }
 
-static void column_factory_bind( GtkListItemFactory *factory, GtkListItem *item, gpointer userdata) {
+static void column_factory_bind(GtkListItemFactory *factory, GtkListItem *item, gpointer userdata) {
     UiColData *col = userdata;
     UiList *list = col->listview->var ? col->listview->var->value : NULL;
+    UiListView *listview = col->listview;
     
     ObjWrapper *obj = gtk_list_item_get_item(item);
     UiModel *model = col->listview->model;
     UiModelType type = model->types[col->model_column];
     
     UiBool freevalue = FALSE;
-    void *data = model_getvalue(model, list, obj->data, obj->i, col->data_column, &freevalue);
+    void *data = listview->getvalue(list, obj->data, obj->i, col->data_column, listview->getvaluedata, &freevalue);
     GtkWidget *child = gtk_list_item_get_child(item);
     
     switch(type) {
@@ -172,7 +205,7 @@
             
         }
         case UI_ICON_TEXT_FREE: {
-            void *data2 = model_getvalue(model, list, obj->data, obj->i, col->data_column+1, &freevalue);
+            void *data2 = listview->getvalue(list, obj->data, obj->i, col->data_column+1, listview->getvaluedata, &freevalue);
             if(type == UI_ICON_TEXT_FREE) {
                 freevalue = TRUE;
             }
@@ -199,55 +232,32 @@
         selection_model = GTK_SELECTION_MODEL(gtk_multi_selection_new(G_LIST_MODEL(liststore)));
     } else {
         selection_model = GTK_SELECTION_MODEL(gtk_single_selection_new(G_LIST_MODEL(liststore)));
+        gtk_single_selection_set_can_unselect(GTK_SINGLE_SELECTION(selection_model), TRUE);
+        gtk_single_selection_set_autoselect(GTK_SINGLE_SELECTION(selection_model), FALSE);
     }
     g_signal_connect(selection_model, "selection-changed", G_CALLBACK(ui_listview_selection_changed), listview);
     return selection_model;
 }
 
-static UiListView* create_listview(UiObject *obj, UiListArgs *args) {
-    UiListView *tableview = malloc(sizeof(UiListView));
-    memset(tableview, 0, sizeof(UiListView));
-    tableview->obj = obj;
-    tableview->model = args->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->ondropdata;
-    tableview->selection.count = 0;
-    tableview->selection.rows = NULL;
-    return tableview;
-}
-
 UIWIDGET ui_listview_create(UiObject *obj, UiListArgs *args) {
     UiObject* current = uic_current_obj(obj);
     
     // to simplify things and share code with ui_table_create, we also
     // use a UiModel for the listview
     UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1);
-    if(args->getvalue2) {
-        model->getvalue2 = args->getvalue2;
-        model->getvalue2data = args->getvalue2data;
-    } else if(args->getvalue) {
-        model->getvalue = args->getvalue;
-    } else {
-        model->getvalue = ui_strmodel_getvalue;
-    }
     args->model = model;
     
     GListStore *ls = g_list_store_new(G_TYPE_OBJECT);
     UiListView *listview = create_listview(obj, args);
+    if(!args->getvalue && !args->getvalue2) {
+        listview->getvalue = str_getvalue;
+    }
     
     listview->columns = malloc(sizeof(UiColData));
     listview->columns->listview = listview;
     listview->columns->data_column = 0;
     listview->columns->model_column = 0;
-    
+     
     GtkListItemFactory *factory = gtk_signal_list_item_factory_new();
     g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), listview->columns);
     g_signal_connect(factory, "bind", G_CALLBACK(column_factory_bind), listview->columns);
@@ -280,7 +290,7 @@
         ui_update_liststore(ls, list);
     } else if (args->static_elements && args->static_nelm > 0) {
         listview_copy_static_elements(listview, args->static_elements, args->static_nelm);
-        listview->model->getvalue = ui_strmodel_getvalue; // force strmodel
+        listview->getvalue = str_getvalue; // force string values
         ui_update_liststore_static(ls, listview->elements, listview->nelm);
     }
     
@@ -320,19 +330,15 @@
     // to simplify things and share code with ui_tableview_create, we also
     // use a UiModel for the listview
     UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1);
-    if(args->getvalue2) {
-        model->getvalue2 = args->getvalue2;
-        model->getvalue2data = args->getvalue2data;
-    } else if(args->getvalue) {
-        model->getvalue = args->getvalue;
-    } else {
-        model->getvalue = ui_strmodel_getvalue;
-    }
     args->model = model;
     
     GListStore *ls = g_list_store_new(G_TYPE_OBJECT);
     UiListView *listview = create_listview(obj, args);
     
+    if(!args->getvalue && !args->getvalue2) {
+        listview->getvalue = str_getvalue;
+    }
+    
     listview->columns = malloc(sizeof(UiColData));
     listview->columns->listview = listview;
     listview->columns->data_column = 0;
@@ -370,7 +376,7 @@
         ui_update_liststore(ls, list);
     } else if (args->static_elements && args->static_nelm > 0) {
         listview_copy_static_elements(listview, args->static_elements, args->static_nelm);
-        listview->model->getvalue = ui_strmodel_getvalue; // force strmodel
+        listview->getvalue = str_getvalue; // force string values
         ui_update_liststore_static(ls, listview->elements, listview->nelm);
     }
     
@@ -626,14 +632,20 @@
         void *value = list->get(list, i);
         if(value) {
             ObjWrapper *obj = obj_wrapper_new(value, i);
+            UiListSelection sel = list->getselection(list);
             // TODO: if index i is selected, the selection is lost
             // is it possible to update the item without removing it?
+            // workaround: save selection and reapply it
             int count = g_list_model_get_n_items(G_LIST_MODEL(view->liststore));
             if(count <= i) {
                 g_list_store_splice(view->liststore, i, 0, (void **)&obj, 1);
             } else {
                 g_list_store_splice(view->liststore, i, 1, (void **)&obj, 1);
             }
+            if(sel.count > 0) {
+                list->setselection(list, sel);
+            }
+            ui_listselection_free(sel);
         }
     }
 }
@@ -695,12 +707,13 @@
 
 #else
 
-static void update_list_row(GtkListStore *store, GtkTreeIter *iter, UiModel *model, UiList *list, void *elm, int row) {
+static void update_list_row(UiListView *listview, GtkListStore *store, GtkTreeIter *iter, UiList *list, void *elm, int row) {
+    UiModel *model = listview->model;
     // set column values
     int c = 0;
     for(int i=0;i<model->columns;i++,c++) {
         UiBool freevalue = FALSE;
-        void *data = model_getvalue(model, list, elm, row, c, &freevalue);
+        void *data = listview->getvalue(list, elm, row, c, listview->getvaluedata, &freevalue);
 
         GValue value = G_VALUE_INIT;
         switch(model->types[i]) {
@@ -765,7 +778,7 @@
                 c++;
                 
                 freevalue = FALSE;
-                char *str = model_getvalue(model, list, elm, row, c, &freevalue);
+                char *str = listview->getvalue(list, elm, row, c, listview->getvaluedata, &freevalue);
                 g_value_init(&value, G_TYPE_STRING);
                 g_value_set_string(&value, str);
                 if(model->types[i] == UI_ICON_TEXT_FREE || freevalue) {
@@ -779,7 +792,8 @@
     }
 }
 
-static GtkListStore* create_list_store(UiList *list, UiModel *model) {
+static GtkListStore* create_list_store(UiListView *listview, UiList *list) {
+    UiModel *model = listview->model;
     int columns = model->columns;
     GType types[2*columns];
     int c = 0;
@@ -807,7 +821,7 @@
             GtkTreeIter iter;
             gtk_list_store_insert (store, &iter, -1);
             
-            update_list_row(store, &iter, model, list, elm, i++);
+            update_list_row(listview, store, &iter, list, elm, i++);
             
             // next row
             elm = list->next(list);
@@ -841,36 +855,29 @@
 #endif
     
     UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1);
-    if(args->getvalue2) {
-        model->getvalue2 = args->getvalue2;
-        model->getvalue2data = args->getvalue2data;
-    } else if(args->getvalue) {
-        model->getvalue = args->getvalue;
-    } else {
-        model->getvalue = ui_strmodel_getvalue;
+    
+    UiListView *listview = create_listview(obj, args);
+    if(!args->getvalue && !args->getvalue2) {
+        listview->getvalue = str_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));
-    g_object_unref(listmodel);
-    
-    UiListView *listview = malloc(sizeof(UiListView));
-    memset(listview, 0, sizeof(UiListView));
-    listview->obj = obj;
-    listview->widget = view;
-    listview->var = var;
     listview->model = model;
-    listview->selection.count = 0;
-    listview->selection.rows = NULL;
     g_signal_connect(
                 view,
                 "destroy",
                 G_CALLBACK(ui_listview_destroy),
                 listview);
     
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args->list, args->varname, UI_VAR_LIST);
+    
+    // init listview
+    listview->widget = view;
+    listview->var = var;
+    
+    UiList *list = var ? var->value : NULL;
+    GtkListStore *listmodel = create_list_store(listview, list);
+    gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel));
+    g_object_unref(listmodel);
+    
     // bind var
     list->update = ui_listview_update;
     list->getselection = ui_listview_getselection;
@@ -921,7 +928,7 @@
     SCROLLEDWINDOW_SET_CHILD(scroll_area, view);
     
     UI_APPLY_LAYOUT2(current, args);
-    current->container->add(current->container, scroll_area, FALSE);
+    current->container->add(current->container, scroll_area);
     
     // ct->current should point to view, not scroll_area, to make it possible
     // to add a context menu
@@ -1006,36 +1013,23 @@
     
     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_object_unref(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));
-    memset(tableview, 0, sizeof(UiListView));
-    tableview->obj = obj;
-    tableview->widget = view;
-    tableview->var = var;
-    tableview->model = model;
-    tableview->ondragstart = args->ondragstart;
-    tableview->ondragstartdata = args->ondragstartdata;
-    tableview->ondragcomplete = args->ondragcomplete;
-    tableview->ondragcompletedata = args->ondragcompletedata;
-    tableview->ondrop = args->ondrop;
-    tableview->ondropdata = args->ondropdata;
-    tableview->selection.count = 0;
-    tableview->selection.rows = NULL;
+    UiListView *tableview = create_listview(obj, args);
     g_signal_connect(
                 view,
                 "destroy",
                 G_CALLBACK(ui_listview_destroy),
                 tableview);
     
+    UiList *list = var ? var->value : NULL;
+    GtkListStore *listmodel = create_list_store(tableview, list);
+    gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel));
+    g_object_unref(listmodel);
+    
     // bind var
     list->update = ui_listview_update;
     list->getselection = ui_listview_getselection;
@@ -1098,7 +1092,7 @@
     }
     
     UI_APPLY_LAYOUT2(current, args);
-    current->container->add(current->container, scroll_area, FALSE);
+    current->container->add(current->container, scroll_area);
     
     // ct->current should point to view, not scroll_area, to make it possible
     // to add a context menu
@@ -1112,7 +1106,7 @@
 void ui_listview_update(UiList *list, int i) {
     UiListView *view = list->obj;
     if(i < 0) {
-        GtkListStore *store = create_list_store(list, view->model);
+        GtkListStore *store = create_list_store(view, list);
         gtk_tree_view_set_model(GTK_TREE_VIEW(view->widget), GTK_TREE_MODEL(store));
         g_object_unref(G_OBJECT(store));
     } else {
@@ -1120,7 +1114,7 @@
         GtkTreeModel *store = gtk_tree_view_get_model(GTK_TREE_VIEW(view->widget));
         GtkTreeIter iter;
         if(gtk_tree_model_iter_nth_child(store, &iter, NULL, i)) {
-            update_list_row(GTK_LIST_STORE(store), &iter, view->model, list, elm, i);
+            update_list_row(view, GTK_LIST_STORE(store), &iter, list, elm, i);
         }
     }
 }
@@ -1150,46 +1144,41 @@
 UIWIDGET ui_combobox_create(UiObject *obj, UiListArgs *args) {
     UiObject* current = uic_current_obj(obj);
     
-    UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1);
-    if(args->getvalue2) {
-        model->getvalue2 = args->getvalue2;
-        model->getvalue2data = args->getvalue2data;
-    } else if(args->getvalue) {
-        model->getvalue = args->getvalue;
-    } else {
-        model->getvalue = ui_strmodel_getvalue;
-    }
+    GtkWidget *combobox = gtk_combo_box_new();
     
-    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->static_elements, args->static_nelm, args->onactivate, args->onactivatedata);
     ui_set_name_and_style(combobox, args->name, args->style_class);
     ui_set_widget_groups(obj->ctx, combobox, args->groups);
     UI_APPLY_LAYOUT2(current, args);
-    current->container->add(current->container, combobox, FALSE);
+    current->container->add(current->container, combobox);
     current->container->current = combobox;
-    return combobox;
-}
-
-GtkWidget* ui_create_combobox(UiObject *obj, UiModel *model, UiVar *var, char **elm, size_t nelm, ui_callback f, void *udata) {
-    GtkWidget *combobox = gtk_combo_box_new();
-       
-    UiListView *uicbox = malloc(sizeof(UiListView));
-    memset(uicbox, 0, sizeof(UiListView));
-    uicbox->obj = obj;
-    uicbox->widget = combobox;
+    
+    UiListView *listview = create_listview(obj, args);
+    listview->widget = combobox;
+    listview->model = ui_model(obj->ctx, UI_STRING, "", -1);
+    g_signal_connect(
+                combobox,
+                "destroy",
+                G_CALLBACK(ui_listview_destroy),
+                listview);
     
+    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);
-    
-    if(!list && elm && nelm > 0) {
-        listview_copy_static_elements(uicbox, elm, nelm);
-        for(int i=0;i<nelm;i++) {
+    GtkListStore *listmodel = create_list_store(listview, list);
+    if(var) {
+        listview->var = var;
+        list->update = ui_combobox_modelupdate;
+        list->getselection = ui_combobox_getselection;
+        list->setselection = ui_combobox_setselection;
+        list->obj = listview;
+        list->update(list, -1);
+    } else if(args->static_nelm > 0) {
+        listview_copy_static_elements(listview, args->static_elements, args->static_nelm);
+        for(int i=0;i<args->static_nelm;i++) {
             GtkTreeIter iter;
             GValue value = G_VALUE_INIT;
             gtk_list_store_insert(listmodel, &iter, -1);
             g_value_init(&value, G_TYPE_STRING);
-            g_value_set_string(&value, uicbox->elements[i]);
+            g_value_set_string(&value, listview->elements[i]);
             gtk_list_store_set_value(listmodel, &iter, 0, &value);
         }
     }
@@ -1199,23 +1188,6 @@
         g_object_unref(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;
-        list->getselection = ui_combobox_getselection;
-        list->setselection = ui_combobox_setselection;
-        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(
@@ -1227,13 +1199,13 @@
     gtk_combo_box_set_active(GTK_COMBO_BOX(combobox), 0);
     
     // add callback
-    if(f) {
+    if(args->onactivate) {
         UiEventData *event = ui_malloc(obj->ctx, sizeof(UiEventData));
         event->obj = obj;
-        event->userdata = udata;
-        event->callback = f;
+        event->userdata = args->onactivatedata;
+        event->callback = args->onactivate;
         event->value = 0;
-        event->customdata = uicbox;
+        event->customdata = listview;
 
         g_signal_connect(
                 combobox,
@@ -1268,7 +1240,7 @@
 
 void ui_combobox_modelupdate(UiList *list, int i) {
     UiListView *view = list->obj;
-    GtkListStore *store = create_list_store(view->var->value, view->model);
+    GtkListStore *store = create_list_store(view, list);
     gtk_combo_box_set_model(GTK_COMBO_BOX(view->widget), GTK_TREE_MODEL(store));
     g_object_unref(store);
 }
@@ -1709,19 +1681,6 @@
     free(v);
 }
 
-void ui_combobox_destroy(GtkWidget *w, UiListView *v) {
-    if(v->var) {
-        ui_destroy_boundvar(v->obj->ctx, v->var);
-    }
-    if(v->elements) {
-        for(int i=0;i<v->nelm;i++) {
-            free(v->elements[i]);
-        }
-        free(v->elements);
-    }
-    free(v);
-}
-
 
 /* ------------------------------ Source List ------------------------------ */
 
@@ -1801,16 +1760,16 @@
     uisublist.listbox = uilistbox;
     uisublist.userdata = sublist->userdata;
     uisublist.index = cxListSize(sublists);
-
+    uisublist.startpos = 0;
+    cxListAdd(sublists, &uisublist);
+    
     // bind UiList
     UiListBoxSubList *sublist_ptr = cxListAt(uilistbox->sublists, cxListSize(sublists)-1);
-    UiList *list = uisublist.var->value;
-    if(list) {
+    if(uisublist.var && uisublist.var->value) {
+        UiList *list = uisublist.var->value;
         list->obj = sublist_ptr;
         list->update = ui_listbox_list_update;
     }
-
-    cxListAdd(sublists, &uisublist);
 }
 
 UIEXPORT UIWIDGET ui_sourcelist_create(UiObject *obj, UiSourceListArgs *args) {
@@ -1883,6 +1842,11 @@
     g_object_set_data(G_OBJECT(scroll_area), "ui_listbox", uilistbox);
     g_object_set_data(G_OBJECT(listbox), "ui_listbox", uilistbox);
     
+    if(args->contextmenu) {
+        UIMENU menu = ui_contextmenu_create(args->contextmenu, obj, listbox);
+        ui_widget_set_contextmenu(listbox, menu);
+    }
+    
     // signals
     g_signal_connect(
                 listbox,
@@ -1952,11 +1916,37 @@
         }
         
         // reload sublist
+        sublist->startpos = pos;
         ui_listbox_update_sublist(listbox, sublist, pos);
         pos += sublist->numitems;
     }
 }
 
+static void listbox_button_clicked(GtkWidget *widget, UiEventDataExt *data) {
+    UiListBoxSubList *sublist = data->customdata0;
+    
+    UiSubListEventData eventdata;
+    eventdata.list = sublist->var->value;
+    eventdata.sublist_index = sublist->index;
+    eventdata.row_index = data->value0;
+    eventdata.sublist_userdata = sublist->userdata;
+    eventdata.row_data = eventdata.list->get(eventdata.list, eventdata.row_index);
+    eventdata.event_data = data->customdata2;
+    
+    UiEvent event;
+    event.obj = data->obj;
+    event.window = event.obj->window;
+    event.document = event.obj->ctx->document;
+    event.eventdata = &eventdata;
+    event.eventdatatype = UI_EVENT_DATA_SUBLIST;
+    event.intval = data->value0;
+    event.set = ui_get_setop();
+    
+    if(data->callback2) {
+        data->callback2(&event, data->userdata2);
+    }
+}
+
 static GtkWidget* create_listbox_row(UiListBox *listbox, UiListBoxSubList *sublist, UiSubListItem *item, int index) {
     GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10);
     if(item->icon) {
@@ -1966,7 +1956,9 @@
     GtkWidget *label = gtk_label_new(item->label);
     gtk_widget_set_halign(label, GTK_ALIGN_START);
     BOX_ADD_EXPAND(hbox, label);
-    // TODO: badge, button
+    if(item->badge) {
+        
+    }
     GtkWidget *row = gtk_list_box_row_new();
     LISTBOX_ROW_SET_CHILD(row, hbox);
     
@@ -1991,6 +1983,34 @@
     
     g_object_set_data(G_OBJECT(row), "ui-listbox-row-eventdata", event);
     
+    // badge
+    if(item->badge) {
+        GtkWidget *badge = gtk_label_new(item->badge);
+        WIDGET_ADD_CSS_CLASS(badge, "ui-badge");
+#if GTK_CHECK_VERSION(4, 0, 0)
+        gtk_widget_set_valign(badge, GTK_ALIGN_CENTER);
+        BOX_ADD(hbox, badge);
+#else
+        GtkWidget *align = gtk_alignment_new(0.5, 0.5, 0, 0);
+        gtk_container_add(GTK_CONTAINER(align), badge);
+        BOX_ADD(hbox, align);
+#endif
+    }
+    // button
+    if(item->button_icon || item->button_label) {
+        GtkWidget *button = gtk_button_new();
+        gtk_button_set_label(GTK_BUTTON(button), item->button_label);
+        ui_button_set_icon_name(button, item->button_icon);
+        WIDGET_ADD_CSS_CLASS(button, "flat");
+        BOX_ADD(hbox, button);
+        g_signal_connect(
+                button,
+                "clicked",
+                G_CALLBACK(listbox_button_clicked),
+                event
+                );
+    }
+    
     return row;
 }
 
@@ -2005,6 +2025,9 @@
     sublist->numitems = 0;
     
     // create items for each UiList element
+    if(!sublist->var) {
+        return;
+    }
     UiList *list = sublist->var->value;
     if(!list) {
         return;
@@ -2012,6 +2035,20 @@
     
     size_t index = 0;
     void *elm = list->first(list);
+    
+    if(!elm && sublist->header) {
+        // empty row for header
+        GtkWidget *row = gtk_list_box_row_new();
+        cxListAdd(sublist->widgets, row);
+        g_object_set_data(G_OBJECT(row), "ui_listbox", listbox);
+        g_object_set_data(G_OBJECT(row), "ui_listbox_sublist", sublist);
+        intptr_t rowindex = listbox_insert_index + index;
+        g_object_set_data(G_OBJECT(row), "ui_listbox_row_index", (gpointer)rowindex);
+        gtk_list_box_insert(listbox->listbox, row, listbox_insert_index + index);
+        sublist->numitems = 1;
+        return;
+    }
+    
     while(elm) {
         UiSubListItem item = { NULL, NULL, NULL, NULL, NULL, NULL };
         if(listbox->getvalue) {
@@ -2055,6 +2092,14 @@
 
 void ui_listbox_list_update(UiList *list, int i) {
     UiListBoxSubList *sublist = list->obj;
+    ui_listbox_update_sublist(sublist->listbox, sublist, sublist->startpos);
+    size_t pos = 0;
+    CxIterator it = cxListIterator(sublist->listbox->sublists);
+    cx_foreach(UiListBoxSubList *, ls, it) {
+        ls->startpos = pos;
+        pos += sublist->numitems;
+    }
+    
 }
 
 void ui_listbox_row_activate(GtkListBox *self, GtkListBoxRow *row, gpointer user_data) {

mercurial