ui/gtk/list.c

changeset 108
77254bd6dccb
parent 103
6606616eca9f
child 109
c3dfcb8f0be7
--- a/ui/gtk/list.c	Sat Apr 05 17:57:04 2025 +0200
+++ b/ui/gtk/list.c	Sun Jul 20 22:04:39 2025 +0200
@@ -48,6 +48,15 @@
     return column == 0 ? elm : NULL;
 }
 
+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);
+    }
+    return NULL;
+}
+
 /*
 static GtkTargetEntry targetentries[] =
     {
@@ -73,6 +82,7 @@
 typedef struct _ObjWrapper {
     GObject parent_instance;
     void *data;
+    int i;
 } ObjWrapper;
 
 typedef struct _ObjWrapperClass {
@@ -89,9 +99,10 @@
     self->data = NULL;
 }
 
-ObjWrapper* obj_wrapper_new(void* data) {
+ObjWrapper* obj_wrapper_new(void* data, int i) {
     ObjWrapper *obj = g_object_new(obj_wrapper_get_type(), NULL);
     obj->data = data;
+    obj->i = i;
     return obj;
 }
 
@@ -122,20 +133,21 @@
 
 static void column_factory_bind( GtkListItemFactory *factory, GtkListItem *item, gpointer userdata) {
     UiColData *col = userdata;
+    UiList *list = col->listview->var ? col->listview->var->value : NULL;
     
     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);
+    UiBool freevalue = FALSE;
+    void *data = model_getvalue(model, list, obj->data, obj->i, col->data_column, &freevalue);
     GtkWidget *child = gtk_list_item_get_child(item);
     
-    bool freevalue = TRUE;
     switch(type) {
+        case UI_STRING_FREE: {
+            freevalue = TRUE;
+        }
         case UI_STRING: {
-            freevalue = FALSE;
-        }
-        case UI_STRING_FREE: {
             gtk_label_set_label(GTK_LABEL(child), data);
             if(freevalue) {
                 free(data);
@@ -157,10 +169,13 @@
             break;
         }
         case UI_ICON_TEXT: {
-            freevalue = FALSE;
+            
         }
         case UI_ICON_TEXT_FREE: {
-            void *data2 = model->getvalue(obj->data, col->data_column+1);
+            void *data2 = model_getvalue(model, list, obj->data, obj->i, col->data_column+1, &freevalue);
+            if(type == UI_ICON_TEXT_FREE) {
+                freevalue = TRUE;
+            }
             GtkWidget *image = g_object_get_data(G_OBJECT(child), "image");
             GtkWidget *label = g_object_get_data(G_OBJECT(child), "label");
             if(data && image) {
@@ -189,34 +204,41 @@
     return selection_model;
 }
 
-static UiListView* create_listview(UiObject *obj, UiListArgs args) {
+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->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) {
+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;
+    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);
@@ -230,10 +252,10 @@
     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);
+    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);
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args->list, args->varname, UI_VAR_LIST);
     
     // init listview
     listview->widget = view;
@@ -256,19 +278,23 @@
         list->setselection = ui_listview_setselection2;
         
         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);
+    } 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
         ui_update_liststore_static(ls, listview->elements, listview->nelm);
     }
     
     // event handling
-    if(args.onactivate) {
+    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);
     }
+    if(args->contextmenu) {
+        UIMENU menu = ui_contextmenu_create(args->contextmenu, obj, view);
+        ui_widget_set_contextmenu(view, menu);
+    }
     
     // add widget to parent
     GtkWidget *scroll_area = SCROLLEDWINDOW_NEW();
@@ -278,8 +304,8 @@
             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);
+    UI_APPLY_LAYOUT2(current, args);
+    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
@@ -288,14 +314,21 @@
     return scroll_area;
 }
 
-UIWIDGET ui_combobox_create(UiObject *obj, UiListArgs args) {
+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;
+    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);
@@ -312,7 +345,7 @@
     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);
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args->list, args->varname, UI_VAR_LIST);
     
     // init listview
     listview->widget = view;
@@ -335,20 +368,20 @@
         list->setselection = ui_combobox_setselection;
         
         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);
+    } 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
         ui_update_liststore_static(ls, listview->elements, listview->nelm);
     }
     
     // event handling
-    if(args.onactivate) {
+    if(args->onactivate) {
         g_signal_connect(view, "notify::selected", G_CALLBACK(ui_dropdown_notify), listview);
     }
     
     // add widget to parent 
-    UI_APPLY_LAYOUT1(current, args);
-    current->container->add(current->container, view, FALSE);
+    UI_APPLY_LAYOUT2(current, args);
+    current->container->add(current->container, view);
     return view;
 }
 
@@ -361,7 +394,7 @@
     gtk_drop_down_set_selected(GTK_DROP_DOWN(dropdown), index);
 }
 
-UIWIDGET ui_table_create(UiObject *obj, UiListArgs args) {
+UIWIDGET ui_table_create(UiObject *obj, UiListArgs *args) {
     UiObject* current = uic_current_obj(obj);
     
     GListStore *ls = g_list_store_new(G_TYPE_OBJECT);
@@ -371,10 +404,10 @@
     // and list updates
     UiListView *tableview = create_listview(obj, args);
     
-    GtkSelectionModel *selection_model = create_selection_model(tableview, ls, args.multiselection);
+    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);
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args->list, args->varname, UI_VAR_LIST);
     
     // init tableview
     tableview->widget = view;
@@ -389,7 +422,7 @@
     
     
     // create columns from UiModel
-    UiModel *model = args.model;
+    UiModel *model = args->model;
     int columns = model ? model->columns : 0;
     
     tableview->columns = calloc(columns, sizeof(UiColData));
@@ -435,9 +468,13 @@
     }
     
     // event handling
-    if(args.onactivate) {
+    if(args->onactivate) {
         g_signal_connect(view, "activate", G_CALLBACK(ui_columnview_activate), tableview);
     }
+    if(args->contextmenu) {
+        UIMENU menu = ui_contextmenu_create(args->contextmenu, obj, view);
+        ui_widget_set_contextmenu(view, menu);
+    }
     
     // add widget to parent
     GtkWidget *scroll_area = SCROLLEDWINDOW_NEW();
@@ -447,8 +484,8 @@
             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);
+    UI_APPLY_LAYOUT2(current, args);
+    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
@@ -474,6 +511,7 @@
     event.window = event.obj->window;
     event.intval = view->selection.count;
     event.eventdata = &view->selection;
+    event.eventdatatype = UI_EVENT_DATA_LIST_SELECTION;
     event.set = ui_get_setop();
     if(cb) {
         cb(&event, cbdata);
@@ -517,6 +555,7 @@
         event.window = event.obj->window;
         event.intval = index;
         event.eventdata = eventdata->data;
+        event.eventdatatype = UI_EVENT_DATA_LIST_ELM;
         event.set = ui_get_setop();
         view->onactivate(&event, view->onactivatedata);
     }
@@ -554,6 +593,7 @@
         event.window = event.obj->window;
         event.intval = view->selection.count;
         event.eventdata = &view->selection;
+        event.eventdatatype = UI_EVENT_DATA_LIST_SELECTION;
         event.set = ui_get_setop();
         view->onactivate(&event, view->onactivatedata);
     }
@@ -561,9 +601,10 @@
 
 void ui_update_liststore(GListStore *liststore, UiList *list) {
     g_list_store_remove_all(liststore);
+    int i = 0;
     void *elm = list->first(list);
     while(elm) {
-        ObjWrapper *obj = obj_wrapper_new(elm);
+        ObjWrapper *obj = obj_wrapper_new(elm, i++);
         g_list_store_append(liststore, obj);
         elm = list->next(list);
     }
@@ -572,7 +613,7 @@
 void ui_update_liststore_static(GListStore *liststore, char **elm, size_t nelm) {
     g_list_store_remove_all(liststore);
     for(int i=0;i<nelm;i++) {
-        ObjWrapper *obj = obj_wrapper_new(elm[i]);
+        ObjWrapper *obj = obj_wrapper_new(elm[i], i);
         g_list_store_append(liststore, obj);
     }
 }
@@ -584,10 +625,15 @@
     } else {
         void *value = list->get(list, i);
         if(value) {
-            ObjWrapper *obj = obj_wrapper_new(value);
+            ObjWrapper *obj = obj_wrapper_new(value, i);
             // TODO: if index i is selected, the selection is lost
             // is it possible to update the item without removing it?
-            g_list_store_splice(view->liststore, i, 1, (void **)&obj, 1);
+            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);
+            }
         }
     }
 }
@@ -649,19 +695,22 @@
 
 #else
 
-static void update_list_row(GtkListStore *store, GtkTreeIter *iter, UiModel *model, void *elm) {
+static void update_list_row(GtkListStore *store, GtkTreeIter *iter, UiModel *model, UiList *list, void *elm, int row) {
     // set column values
     int c = 0;
     for(int i=0;i<model->columns;i++,c++) {
-        void *data = model->getvalue(elm, c);
+        UiBool freevalue = FALSE;
+        void *data = model_getvalue(model, list, elm, row, c, &freevalue);
 
         GValue value = G_VALUE_INIT;
         switch(model->types[i]) {
-            case UI_STRING: 
             case UI_STRING_FREE: {
+                freevalue = TRUE;
+            } 
+            case UI_STRING: {
                 g_value_init(&value, G_TYPE_STRING);
                 g_value_set_string(&value, data);
-                if(model->types[i] == UI_STRING_FREE) {
+                if(freevalue) {
                     free(data);
                 }
                 break;
@@ -714,11 +763,12 @@
                 }
 #endif
                 c++;
-
-                char *str = model->getvalue(elm, c);
+                
+                freevalue = FALSE;
+                char *str = model_getvalue(model, list, elm, row, c, &freevalue);
                 g_value_init(&value, G_TYPE_STRING);
                 g_value_set_string(&value, str);
-                if(model->types[i] == UI_ICON_TEXT_FREE) {
+                if(model->types[i] == UI_ICON_TEXT_FREE || freevalue) {
                     free(str);
                 }
                 break;
@@ -751,12 +801,13 @@
     
     if(list) {
         void *elm = list->first(list);
+        int i = 0;
 	while(elm) {
             // insert new row
             GtkTreeIter iter;
             gtk_list_store_insert (store, &iter, -1);
             
-            update_list_row(store, &iter, model, elm);
+            update_list_row(store, &iter, model, list, elm, i++);
             
             // next row
             elm = list->next(list);
@@ -767,13 +818,13 @@
 }
 
 
-UIWIDGET ui_listview_create(UiObject *obj, UiListArgs args) {
+UIWIDGET ui_listview_create(UiObject *obj, UiListArgs *args) {
     UiObject* current = uic_current_obj(obj);
     
     // create treeview
     GtkWidget *view = gtk_tree_view_new();
-    ui_set_name_and_style(view, args.name, args.style_class);
-    ui_set_widget_groups(obj->ctx, view, args.groups);
+    ui_set_name_and_style(view, args->name, args->style_class);
+    ui_set_widget_groups(obj->ctx, view, args->groups);
     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);
@@ -790,9 +841,16 @@
 #endif
     
     UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1);
-    model->getvalue = args.getvalue ? args.getvalue : ui_strmodel_getvalue;
+    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;
+    }
     
-    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST);
+    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);
@@ -822,24 +880,24 @@
     // add callback
     UiTreeEventData *event = malloc(sizeof(UiTreeEventData));
     event->obj = obj;
-    event->activate = args.onactivate;
-    event->activatedata = args.onactivatedata;
-    event->selection = args.onselection;
-    event->selectiondata = args.onselectiondata;
+    event->activate = args->onactivate;
+    event->activatedata = args->onactivatedata;
+    event->selection = args->onselection;
+    event->selectiondata = args->onselectiondata;
     g_signal_connect(
             view,
             "destroy",
             G_CALLBACK(ui_destroy_userdata),
             event);
     
-    if(args.onactivate) {
+    if(args->onactivate) {
         g_signal_connect(
                 view,
                 "row-activated",
                 G_CALLBACK(ui_listview_activate_event),
                 event);
     }
-    if(args.onselection) {
+    if(args->onselection) {
         GtkTreeSelection *selection = gtk_tree_view_get_selection(
                 GTK_TREE_VIEW(view));
         g_signal_connect(
@@ -848,8 +906,8 @@
                 G_CALLBACK(ui_listview_selection_event),
                 event);
     }
-    if(args.contextmenu) {
-        UIMENU menu = ui_contextmenu_create(args.contextmenu, obj, view);
+    if(args->contextmenu) {
+        UIMENU menu = ui_contextmenu_create(args->contextmenu, obj, view);
         ui_widget_set_contextmenu(view, menu);
     }
     
@@ -862,7 +920,7 @@
             GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS  
     SCROLLEDWINDOW_SET_CHILD(scroll_area, view);
     
-    UI_APPLY_LAYOUT1(current, args);
+    UI_APPLY_LAYOUT2(current, args);
     current->container->add(current->container, scroll_area, FALSE);
     
     // ct->current should point to view, not scroll_area, to make it possible
@@ -883,13 +941,13 @@
     gtk_combo_box_set_active(GTK_COMBO_BOX(dropdown), index);
 }
 
-UIWIDGET ui_table_create(UiObject *obj, UiListArgs args) {
+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;
+    UiModel *model = args->model;
     int columns = model ? model->columns : 0;
     
     int addi = 0;
@@ -946,7 +1004,7 @@
     
 #endif
     
-    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST);
+    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);
@@ -964,12 +1022,12 @@
     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.ondropsdata;
+    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;
     g_signal_connect(
@@ -987,18 +1045,18 @@
     // 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) {
+    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) {
+    if(args->onselection) {
         GtkTreeSelection *selection = gtk_tree_view_get_selection(
                 GTK_TREE_VIEW(view));
         g_signal_connect(
@@ -1010,15 +1068,15 @@
     // TODO: destroy callback
     
     
-    if(args.ondragstart) {
-        ui_listview_add_dnd(tableview, &args);
+    if(args->ondragstart) {
+        ui_listview_add_dnd(tableview, args);
     }
-    if(args.ondrop) {
-        ui_listview_enable_drop(tableview, &args);
+    if(args->ondrop) {
+        ui_listview_enable_drop(tableview, args);
     }
       
     GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(view));
-    if(args.multiselection) {
+    if(args->multiselection) {
         gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
     }
     
@@ -1030,8 +1088,8 @@
             GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS  
     SCROLLEDWINDOW_SET_CHILD(scroll_area, view);
     
-    if(args.contextmenu) {
-        UIMENU menu = ui_contextmenu_create(args.contextmenu, obj, scroll_area);
+    if(args->contextmenu) {
+        UIMENU menu = ui_contextmenu_create(args->contextmenu, obj, scroll_area);
 #if GTK_MAJOR_VERSION >= 4
         ui_widget_set_contextmenu(scroll_area, menu);
 #else
@@ -1039,7 +1097,7 @@
 #endif
     }
     
-    UI_APPLY_LAYOUT1(current, args);
+    UI_APPLY_LAYOUT2(current, args);
     current->container->add(current->container, scroll_area, FALSE);
     
     // ct->current should point to view, not scroll_area, to make it possible
@@ -1062,7 +1120,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, elm);
+            update_list_row(GTK_LIST_STORE(store), &iter, view->model, list, elm, i);
         }
     }
 }
@@ -1089,18 +1147,25 @@
 
 /* --------------------------- ComboBox ---------------------------  */
 
-UIWIDGET ui_combobox_create(UiObject *obj, UiListArgs args) {
+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);
+    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 = 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_LAYOUT1(current, args);
+    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->current = combobox;
     return combobox;
@@ -1323,6 +1388,7 @@
         event.window = event.obj->window;
         event.document = event.obj->ctx->document;
         event.eventdata = dnd;
+        event.eventdatatype = UI_EVENT_DATA_DND;
         event.intval = 0;
         event.set = ui_get_setop();
         listview->ondragstart(&event, listview->ondragstartdata);
@@ -1359,6 +1425,7 @@
         event.window = event.obj->window;
         event.document = event.obj->ctx->document;
         event.eventdata = &dnd;
+        event.eventdatatype = UI_EVENT_DATA_DND;
         event.intval = 0;
         event.set = ui_get_setop();
         listview->ondragcomplete(&event, listview->ondragcompletedata);
@@ -1388,6 +1455,7 @@
         event.window = event.obj->window;
         event.document = event.obj->ctx->document;
         event.eventdata = &dnd;
+        event.eventdatatype = UI_EVENT_DATA_DND;
         event.intval = 0;
         event.set = ui_get_setop();
         listview->ondrop(&event, listview->ondropdata);
@@ -1745,7 +1813,7 @@
     cxListAdd(sublists, &uisublist);
 }
 
-UIEXPORT UIWIDGET ui_sourcelist_create(UiObject *obj, UiSourceListArgs args) {
+UIEXPORT UIWIDGET ui_sourcelist_create(UiObject *obj, UiSourceListArgs *args) {
     UiObject* current = uic_current_obj(obj);
     
 #ifdef UI_GTK3
@@ -1753,7 +1821,7 @@
 #else
     GtkWidget *listbox = gtk_list_box_new();
 #endif
-    if(!args.style_class) {
+    if(!args->style_class) {
 #if GTK_MAJOR_VERSION >= 4
         WIDGET_ADD_CSS_CLASS(listbox, "navigation-sidebar");
 #else
@@ -1764,31 +1832,32 @@
     GtkWidget *scroll_area = SCROLLEDWINDOW_NEW();
     SCROLLEDWINDOW_SET_CHILD(scroll_area, listbox);
     
-    ui_set_name_and_style(listbox, args.name, args.style_class);
-    ui_set_widget_groups(obj->ctx, listbox, args.groups);
-    UI_APPLY_LAYOUT1(current, args);
-    current->container->add(current->container, scroll_area, TRUE);
+    ui_set_name_and_style(listbox, args->name, args->style_class);
+    ui_set_widget_groups(obj->ctx, listbox, args->groups);
+    UI_APPLY_LAYOUT2(current, args);
+    current->container->add(current->container, scroll_area);
     
     UiListBox *uilistbox = malloc(sizeof(UiListBox));
     uilistbox->obj = obj;
     uilistbox->listbox = GTK_LIST_BOX(listbox);
-    uilistbox->getvalue = args.getvalue;
-    uilistbox->onactivate = args.onactivate;
-    uilistbox->onactivatedata = args.onactivatedata;
-    uilistbox->onbuttonclick = args.onbuttonclick;
-    uilistbox->onbuttonclickdata = args.onbuttonclickdata;
+    uilistbox->getvalue = args->getvalue;
+    uilistbox->getvaluedata = args->getvaluedata;
+    uilistbox->onactivate = args->onactivate;
+    uilistbox->onactivatedata = args->onactivatedata;
+    uilistbox->onbuttonclick = args->onbuttonclick;
+    uilistbox->onbuttonclickdata = args->onbuttonclickdata;
     uilistbox->sublists = cxArrayListCreateSimple(sizeof(UiListBoxSubList), 4);
     uilistbox->sublists->collection.advanced_destructor = (cx_destructor_func2)sublist_destroy;
     uilistbox->sublists->collection.destructor_data = obj;
     uilistbox->first_row = NULL;
     
-    if(args.sublists) {
+    if(args->sublists) {
         // static sublist initalization
-        if(args.numsublists == 0 && args.sublists) {
-            args.numsublists = INT_MAX;
+        if(args->numsublists == 0 && args->sublists) {
+            args->numsublists = INT_MAX;
         }
-        for(int i=0;i<args.numsublists;i++) {
-            UiSubList sublist = args.sublists[i];
+        for(int i=0;i<args->numsublists;i++) {
+            UiSubList sublist = args->sublists[i];
             if(!sublist.varname && !sublist.value) {
                 break;
             }
@@ -1799,13 +1868,13 @@
         // fill items
         ui_listbox_update(uilistbox, 0, cxListSize(uilistbox->sublists));
     } else {
-        UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.dynamic_sublist, args.varname, UI_VAR_LIST);
+        UiVar* var = uic_widget_var(obj->ctx, current->ctx, args->dynamic_sublist, args->varname, UI_VAR_LIST);
         if(var) {
             UiList *list = var->value;
             list->obj = uilistbox;
             list->update = ui_listbox_dynamic_update;
             
-            ui_listbox_dynamic_update(list, 0);
+            ui_listbox_dynamic_update(list, -1);
         }
     }
     
@@ -1821,7 +1890,7 @@
                 G_CALLBACK(ui_destroy_sourcelist),
                 uilistbox);
     
-    if(args.onactivate) {
+    if(args->onactivate) {
         g_signal_connect(
                 listbox,
                 "row-activated",
@@ -1838,6 +1907,15 @@
     // unbind/free previous list vars
     CxIterator i = cxListIterator(uilistbox->sublists);
     cx_foreach(UiListBoxSubList *, s, i) {
+        // TODO: "unbind/free previous list vars" will also remove
+        //       the widget list. This makes the widget optimization 
+        //       in ui_listbox_update_sublist pointless
+        //       Is it actually possible to not recreate the whole list?
+        CxIterator r = cxListIterator(s->widgets);
+        cx_foreach(GtkWidget*, widget, r) {
+            LISTBOX_REMOVE(uilistbox->listbox, widget);
+        }
+        
         if(s->var) {
             UiList *sl = s->var->value;
             sl->obj = NULL;
@@ -1936,7 +2014,11 @@
     void *elm = list->first(list);
     while(elm) {
         UiSubListItem item = { NULL, NULL, NULL, NULL, NULL, NULL };
-        listbox->getvalue(sublist->userdata, elm, index, &item);
+        if(listbox->getvalue) {
+            listbox->getvalue(list, sublist->userdata, elm, index, &item, listbox->getvaluedata);
+        } else {
+            item.label = strdup(elm);
+        }
         
         // create listbox item
         GtkWidget *row = create_listbox_row(listbox, sublist, &item, (int)index);
@@ -1987,7 +2069,7 @@
     eventdata.sublist_index = sublist->index;
     eventdata.row_index = data->value0;
     eventdata.sublist_userdata = sublist->userdata;
-    eventdata.row_data = ui_list_get(eventdata.list, eventdata.row_index);
+    eventdata.row_data = eventdata.list->get(eventdata.list, eventdata.row_index);
     eventdata.event_data = data->customdata2;
     
     UiEvent event;
@@ -1995,6 +2077,7 @@
     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();
     

mercurial