ui/gtk/list.c

changeset 101
7b3a3130be44
parent 100
d2bd73d28ff1
--- a/ui/gtk/list.c	Thu Dec 12 20:01:43 2024 +0100
+++ b/ui/gtk/list.c	Mon Jan 06 22:22:55 2025 +0100
@@ -48,6 +48,527 @@
     return column == 0 ? elm : NULL;
 }
 
+/*
+static GtkTargetEntry targetentries[] =
+    {
+      { "STRING",        0, 0 },
+      { "text/plain",    0, 1 },
+      { "text/uri-list", 0, 2 },
+    };
+*/
+
+#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_label_set_xalign(GTK_LABEL(label), 0);
+        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;
+        }
+    }
+}
+
+static GtkSelectionModel* create_selection_model(UiListView *listview, GListStore *liststore, bool multiselection) {
+    GtkSelectionModel *selection_model;
+    if(multiselection) {
+        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)));
+    }
+    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.ondropsdata;
+    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);
+    model->getvalue = args.getvalue ? args.getvalue : ui_strmodel_getvalue;
+    args.model = model;
+    
+    GListStore *ls = g_list_store_new(G_TYPE_OBJECT);
+    UiListView *listview = create_listview(obj, args);
+    
+    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);
+    
+    GtkSelectionModel *selection_model = create_selection_model(listview, ls, args.multiselection);
+    GtkWidget *view = gtk_list_view_new(GTK_SELECTION_MODEL(selection_model), factory);
+    
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST);
+    
+    // init listview
+    listview->widget = view;
+    listview->var = var;
+    listview->liststore = ls;
+    listview->selectionmodel = selection_model;
+    g_signal_connect(
+                view,
+                "destroy",
+                G_CALLBACK(ui_listview_destroy),
+                listview);
+    
+    // bind listview to list
+    if(var && var->value) {
+        UiList *list = var->value;
+        
+        list->obj = listview;
+        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) {
+        // columnview and listview can use the same callback function, because
+        // the first parameter (which is technically a different pointer type)
+        // is ignored
+        g_signal_connect(view, "activate", G_CALLBACK(ui_columnview_activate), listview);
+    }
+    
+    // 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;
+}
+
+UIWIDGET ui_combobox_create(UiObject *obj, UiListArgs args) {
+    UiObject* current = uic_current_obj(obj);
+    
+    // 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);
+    model->getvalue = args.getvalue ? args.getvalue : ui_strmodel_getvalue;
+    args.model = model;
+    
+    GListStore *ls = g_list_store_new(G_TYPE_OBJECT);
+    UiListView *listview = create_listview(obj, args);
+    
+    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);
+    
+    GtkWidget *view = gtk_drop_down_new(G_LIST_MODEL(ls), NULL);
+    gtk_drop_down_set_factory(GTK_DROP_DOWN(view), factory);
+    
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST);
+    
+    // init listview
+    listview->widget = view;
+    listview->var = var;
+    listview->liststore = ls;
+    listview->selectionmodel = NULL;
+    g_signal_connect(
+                view,
+                "destroy",
+                G_CALLBACK(ui_listview_destroy),
+                listview);
+    
+    // bind listview to list
+    if(var && var->value) {
+        UiList *list = var->value;
+        
+        list->obj = listview;
+        list->update = ui_listview_update2;
+        list->getselection = ui_combobox_getselection;
+        list->setselection = ui_combobox_setselection;
+        
+        ui_update_liststore(ls, list);
+    }
+    
+    // event handling
+    if(args.onactivate) {
+        g_signal_connect(view, "activate", G_CALLBACK(ui_columnview_activate), listview);
+    }
+    
+    // add widget to parent 
+    UI_APPLY_LAYOUT1(current, args);
+    current->container->add(current->container, view, FALSE);
+    return view;
+}
+
+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);
+    
+    // create obj to store all relevant data we need for handling events
+    // and list updates
+    UiListView *tableview = create_listview(obj, args);
+    
+    GtkSelectionModel *selection_model = create_selection_model(tableview, ls, args.multiselection);
+    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);
+    
+    // init tableview
+    tableview->widget = view;
+    tableview->var = var;
+    tableview->liststore = ls;
+    tableview->selectionmodel = selection_model;
+    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);
+    }
+    
+    // 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(void *ignore, 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_dropdown_activate(GtkDropDown* self, gpointer userdata) {
+    UiListView *view = userdata;
+    guint selection = gtk_drop_down_get_selected(GTK_DROP_DOWN(view->widget));
+    UiListSelection sel = { 0, NULL };
+    int sel2 = (int)selection;
+    if(selection != GTK_INVALID_LIST_POSITION) {
+        sel.count = 1;
+        sel.rows = &sel2;
+    }
+    
+    if(view->onactivate) {
+        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;
+        view->onactivate(&event, view->onactivatedata);
+    }
+}
+
+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);
+        }
+    }
+}
+
+UiListSelection ui_combobox_getselection(UiList *list) {
+    UiListView *view = list->obj;
+    guint selection = gtk_drop_down_get_selected(GTK_DROP_DOWN(view->widget));
+    UiListSelection sel = { 0, NULL };
+    if(selection != GTK_INVALID_LIST_POSITION) {
+        sel.count = 1;
+        sel.rows = malloc(sizeof(int));
+        sel.rows[0] = (int)selection;
+    }
+    return sel;
+}
+
+void ui_combobox_setselection(UiList *list, UiListSelection selection) {
+    UiListView *view = list->obj;
+    if(selection.count > 0) {
+        gtk_drop_down_set_selected(GTK_DROP_DOWN(view->widget), selection.rows[0]);
+    } else {
+        gtk_drop_down_set_selected(GTK_DROP_DOWN(view->widget), GTK_INVALID_LIST_POSITION);
+    }
+}
+
+#else
+
 static GtkListStore* create_list_store(UiList *list, UiModel *model) {
     int columns = model->columns;
     GType types[2*columns];
@@ -199,6 +720,8 @@
     listview->widget = view;
     listview->var = var;
     listview->model = model;
+    listview->selection.count = 0;
+    listview->selection.rows = NULL;
     g_signal_connect(
                 view,
                 "destroy",
@@ -264,32 +787,6 @@
     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);
     
@@ -376,6 +873,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 +952,225 @@
     return scroll_area;
 }
 
+
+
+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));
+}
+
+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_setselection(UiList *list, UiListSelection selection) {
+    UiListView *view = list->obj;
+    GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view->widget));
+    GtkTreePath *path = gtk_tree_path_new_from_indicesv(selection.rows, selection.count);
+    gtk_tree_selection_select_path(sel, path);
+    //g_object_unref(path);
+}
+
+
+
+/* --------------------------- 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 ? args.getvalue : ui_strmodel_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_set_name_and_style(combobox, args.name, args.style_class);
+    ui_set_widget_groups(obj->ctx, combobox, args.groups);
+    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));
+        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(
+            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));
+    g_object_unref(store);
+}
+
+UiListSelection ui_combobox_getselection(UiList *list) {
+    UiListView *combobox = list->obj;
+    UiListSelection ret;
+    ret.rows = malloc(sizeof(int*));
+    ret.count = 1;
+    ret.rows[0] = gtk_combo_box_get_active(GTK_COMBO_BOX(combobox->widget));
+    return ret;
+}
+
+void ui_combobox_setselection(UiList *list, UiListSelection selection) {
+    UiListView *combobox = list->obj;
+    if(selection.count > 0) {
+        gtk_combo_box_set_active(GTK_COMBO_BOX(combobox->widget), selection.rows[0]);
+    }
+}
+
+
+
+
+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];
+}
+
+
+#endif
+
+
 #if GTK_MAJOR_VERSION >= 4
 
 static GdkContentProvider *ui_listview_dnd_prepare(GtkDragSource *source, double x, double y, void *data) {
@@ -762,33 +1480,14 @@
     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));
-}
-
-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_setselection(UiList *list, UiListSelection selection) {
-    UiListView *view = list->obj;
-    GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view->widget));
-    GtkTreePath *path = gtk_tree_path_new_from_indicesv(selection.rows, selection.count);
-    gtk_tree_selection_select_path(sel, path);
-    //g_object_unref(path);
-}
 
 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);
 }
 
@@ -798,204 +1497,17 @@
 }
 
 
-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 ? args.getvalue : ui_strmodel_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_set_name_and_style(combobox, args.name, args.style_class);
-    ui_set_widget_groups(obj->ctx, combobox, args.groups);
-    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));
-        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(
-            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));
-    g_object_unref(store);
-}
-
-UiListSelection ui_combobox_getselection(UiList *list) {
-    UiListView *combobox = list->obj;
-    UiListSelection ret;
-    ret.rows = malloc(sizeof(int*));
-    ret.count = 1;
-    ret.rows[0] = gtk_combo_box_get_active(GTK_COMBO_BOX(combobox->widget));
-    return ret;
-}
-
-void ui_combobox_setselection(UiList *list, UiListSelection selection) {
-    UiListView *combobox = list->obj;
-    if(selection.count > 0) {
-        gtk_combo_box_set_active(GTK_COMBO_BOX(combobox->widget), selection.rows[0]);
-    }
-}
-
-
 /* ------------------------------ Source List ------------------------------ */
 
 static void ui_destroy_sourcelist(GtkWidget *w, UiListBox *v) {
-    cxListDestroy(v->sublists);
+    cxListFree(v->sublists);
     free(v);
 }
 
 static void sublist_destroy(UiObject *obj, UiListBoxSubList *sublist) {
     free(sublist->header);
     ui_destroy_boundvar(obj->ctx, sublist->var);
-    cxListDestroy(sublist->widgets);
+    cxListFree(sublist->widgets);
 }
 
 static void listbox_create_header(GtkListBoxRow* row, GtkListBoxRow* before, gpointer user_data) {

mercurial