Sun, 05 Jan 2025 17:31:53 +0100
add new gtk4 listview/combobox implementation
application/main.c | file | annotate | diff | comparison | revisions | |
ui/gtk/list.c | file | annotate | diff | comparison | revisions | |
ui/gtk/list.h | file | annotate | diff | comparison | revisions |
--- a/application/main.c Sun Jan 05 16:56:05 2025 +0100 +++ b/application/main.c Sun Jan 05 17:31:53 2025 +0100 @@ -414,7 +414,7 @@ ui_tab(obj, "Tab 1") { UiModel *model = ui_model(obj->ctx, UI_STRING, "col1", UI_INTEGER, "col2", UI_ICON, "col3", UI_ICON_TEXT, "col4", UI_INTEGER, "col5", -1); model->getvalue = table_getvalue; - ui_table(obj, .model = model, .list = doc->list2, .colspan = 2, .fill = UI_ON, .contextmenu = menubuilder, .multiselection = TRUE, + ui_listview(obj, .model = model, .list = doc->list2, .colspan = 2, .fill = UI_ON, .contextmenu = menubuilder, .multiselection = TRUE, .onactivate = action_table_activate, .onactivatedata = "activate", .onselection = action_table_activate, .onselectiondata = "selection"); }
--- a/ui/gtk/list.c Sun Jan 05 16:56:05 2025 +0100 +++ b/ui/gtk/list.c Sun Jan 05 17:31:53 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_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); + + 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]; @@ -264,356 +785,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 }, - }; -*/ - -#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; - } - } -} - -UIWIDGET ui_table_create(UiObject *obj, UiListArgs args) { - UiObject* current = uic_current_obj(obj); - - GListStore *ls = g_list_store_new(G_TYPE_OBJECT); - //g_list_store_append(ls, v1); - - GtkSelectionModel *selection_model; - if(args.multiselection) { - selection_model = GTK_SELECTION_MODEL(gtk_multi_selection_new(G_LIST_MODEL(ls))); - } else { - selection_model = GTK_SELECTION_MODEL(gtk_single_selection_new(G_LIST_MODEL(ls))); - } - - GtkWidget *view = gtk_column_view_new(GTK_SELECTION_MODEL(selection_model)); - - UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST); - - // create obj to store all relevant data we need for handling events - // and list updates - UiListView *tableview = malloc(sizeof(UiListView)); - tableview->obj = obj; - tableview->widget = view; - tableview->var = var; - tableview->model = args.model; - tableview->liststore = ls; - tableview->selectionmodel = selection_model; - tableview->onactivate = args.onactivate; - tableview->onactivatedata = args.onactivatedata; - tableview->onselection = args.onselection; - tableview->onselectiondata = args.onselectiondata; - tableview->ondragstart = args.ondragstart; - tableview->ondragstartdata = args.ondragstartdata; - tableview->ondragcomplete = args.ondragcomplete; - tableview->ondragcompletedata = args.ondragcompletedata; - tableview->ondrop = args.ondrop; - tableview->ondropdata = args.ondropsdata; - tableview->selection.count = 0; - tableview->selection.rows = NULL; - g_signal_connect( - view, - "destroy", - G_CALLBACK(ui_listview_destroy), - tableview); - - - // create columns from UiModel - UiModel *model = args.model; - int columns = model ? model->columns : 0; - - tableview->columns = calloc(columns, sizeof(UiColData)); - - int addi = 0; - for(int i=0;i<columns;i++) { - tableview->columns[i].listview = tableview; - tableview->columns[i].model_column = i; - tableview->columns[i].data_column = i+addi; - - if(model->types[i] == UI_ICON_TEXT || model->types[i] == UI_ICON_TEXT_FREE) { - // icon+text has 2 data columns - addi++; - } - - GtkListItemFactory *factory = gtk_signal_list_item_factory_new(); - UiColData *col = &tableview->columns[i]; - g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), col); - g_signal_connect(factory, "bind", G_CALLBACK(column_factory_bind), col); - - GtkColumnViewColumn *column = gtk_column_view_column_new(model->titles[i], factory); - gtk_column_view_column_set_resizable(column, true); - gtk_column_view_append_column(GTK_COLUMN_VIEW(view), column); - } - - // bind listview to list - if(var && var->value) { - UiList *list = var->value; - - list->obj = tableview; - list->update = ui_listview_update2; - list->getselection = ui_listview_getselection2; - list->setselection = ui_listview_setselection2; - - ui_update_liststore(ls, list); - } - - // event handling - if(args.onactivate) { - g_signal_connect(view, "activate", G_CALLBACK(ui_columnview_activate), tableview); - } - // always handle selection-changed, to keep track of the current selection - g_signal_connect(selection_model, "selection-changed", G_CALLBACK(ui_listview_selection_changed), tableview); - - // add widget to parent - GtkWidget *scroll_area = SCROLLEDWINDOW_NEW(); - gtk_scrolled_window_set_policy( - GTK_SCROLLED_WINDOW(scroll_area), - GTK_POLICY_AUTOMATIC, - GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS - SCROLLEDWINDOW_SET_CHILD(scroll_area, view); - - UI_APPLY_LAYOUT1(current, args); - current->container->add(current->container, scroll_area, FALSE); - - // ct->current should point to view, not scroll_area, to make it possible - // to add a context menu - current->container->current = view; - - return scroll_area; -} - -static UiListSelection selectionmodel_get_selection(GtkSelectionModel *model) { - UiListSelection sel = { 0, NULL }; - GtkBitset *bitset = gtk_selection_model_get_selection(model); - int n = gtk_bitset_get_size(bitset); - printf("bitset %d\n", n); - - gtk_bitset_unref(bitset); - return sel; -} - -static void listview_event(ui_callback cb, void *cbdata, UiListView *view) { - UiEvent event; - event.obj = view->obj; - event.document = event.obj->ctx->document; - event.window = event.obj->window; - event.intval = view->selection.count; - event.eventdata = &view->selection; - if(cb) { - cb(&event, cbdata); - } -} - -void ui_columnview_activate(GtkColumnView* self, guint position, gpointer userdata) { - UiListView *view = userdata; - listview_event(view->onactivate, view->onactivatedata, view); -} - -void ui_listview_selection_changed(GtkSelectionModel* self, guint position, guint n_items, gpointer userdata) { - UiListView *view = userdata; - free(view->selection.rows); - view->selection.count = 0; - view->selection.rows = NULL; - - CX_ARRAY_DECLARE(int, newselection); - cx_array_initialize(newselection, 8); - - size_t nitems = g_list_model_get_n_items(G_LIST_MODEL(view->liststore)); - - for(size_t i=0;i<nitems;i++) { - if(gtk_selection_model_is_selected(view->selectionmodel, i)) { - int s = (int)i; - cx_array_simple_add(newselection, s); - } - } - - if(newselection_size > 0) { - view->selection.count = newselection_size; - view->selection.rows = newselection; - } else { - free(newselection); - } - - listview_event(view->onselection, view->onselectiondata, view); -} - -void ui_update_liststore(GListStore *liststore, UiList *list) { - g_list_store_remove_all(liststore); - void *elm = list->first(list); - while(elm) { - ObjWrapper *obj = obj_wrapper_new(elm); - g_list_store_append(liststore, obj); - elm = list->next(list); - } -} - -void ui_listview_update2(UiList *list, int i) { - UiListView *view = list->obj; - ui_update_liststore(view->liststore, view->var->value); -} - -UiListSelection ui_listview_getselection2(UiList *list) { - UiListView *view = list->obj; - UiListSelection selection; - selection.count = view->selection.count; - selection.rows = calloc(selection.count, sizeof(int)); - memcpy(selection.rows, view->selection.rows, selection.count*sizeof(int)); - return selection; -} - -void ui_listview_setselection2(UiList *list, UiListSelection selection) { - UiListView *view = list->obj; - UiListSelection newselection; - newselection.count = view->selection.count; - if(selection.count > 0) { - newselection.rows = calloc(newselection.count, sizeof(int)); - memcpy(newselection.rows, selection.rows, selection.count*sizeof(int)); - } else { - newselection.rows = NULL; - } - free(view->selection.rows); - view->selection = newselection; - - gtk_selection_model_unselect_all(view->selectionmodel); - if(selection.count > 0) { - for(int i=0;i<selection.count;i++) { - gtk_selection_model_select_item(view->selectionmodel, selection.rows[i], FALSE); - } - } -} - -#else - UIWIDGET ui_table_create(UiObject *obj, UiListArgs args) { UiObject* current = uic_current_obj(obj); @@ -780,6 +951,221 @@ } + +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 @@ -1092,29 +1478,6 @@ 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); @@ -1132,193 +1495,6 @@ } -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) {
--- a/ui/gtk/list.h Sun Jan 05 16:56:05 2025 +0100 +++ b/ui/gtk/list.h Sun Jan 05 17:31:53 2025 +0100 @@ -113,9 +113,11 @@ UiListSelection ui_listview_getselection2(UiList *list); void ui_listview_setselection2(UiList *list, UiListSelection selection); -void ui_columnview_activate(GtkColumnView* self, guint position, gpointer userdata); +void ui_columnview_activate(void *ignore, guint position, gpointer userdata); void ui_listview_selection_changed(GtkSelectionModel* self, guint position, guint n_items, gpointer user_data); +void ui_dropdown_activate(GtkDropDown* self, gpointer userdata); + #endif void* ui_strmodel_getvalue(void *elm, int column); @@ -132,6 +134,9 @@ void ui_combobox_destroy(GtkWidget *w, UiListView *v); void ui_listview_destroy(GtkWidget *w, UiListView *v); +#if GTK_CHECK_VERSION(4, 10, 0) + +#else void ui_listview_activate_event( GtkTreeView *tree_view, GtkTreePath *path, @@ -144,6 +149,7 @@ GtkTreeSelection *selection, UiTreeEventData *event); int ui_tree_path_list_index(GtkTreePath *path); +#endif void ui_listview_add_dnd(UiListView *listview, UiListArgs *args); void ui_listview_enable_drop(UiListView *listview, UiListArgs *args);