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