# HG changeset patch # User Olaf Wintermann # Date 1760708670 -7200 # Node ID 380ec881faa2caa112394ef34ca45ea543c432f6 # Parent a04cb4398034b18f383e8eb34e53d48b21ba9d5a add support for selectable source list header rows (GTK) diff -r a04cb4398034 -r 380ec881faa2 application/main.c --- a/application/main.c Thu Oct 16 15:57:05 2025 +0200 +++ b/application/main.c Fri Oct 17 15:44:30 2025 +0200 @@ -444,7 +444,9 @@ void action_sourcelist_activate(UiEvent *event, void *userdata) { UiSubListEventData *eventdata = event->eventdata; printf("sourcelist %s index %d\n", eventdata->row_data, eventdata->row_index); - ui_list_update_row(eventdata->list, eventdata->row_index); + if(eventdata->row_index >= 0) { + ui_list_update_row(eventdata->list, eventdata->row_index); + } } void action_table_activate(UiEvent *event, void *userdata) { @@ -609,6 +611,7 @@ ui_sidebar(obj, .margin = 0, .spacing = 0) { ui_sourcelist(obj, .fill = TRUE, .getvalue = sourcelist_getvalue, + .header_is_item = TRUE, .sublists = UI_SUBLISTS(UI_SUBLIST(.varname = "srclist1", .header = "Header 1", .userdata = "Sublist1"), UI_SUBLIST(.varname = "srclist2", .header = "Header 2", .userdata = "Sublist2")), .onactivate = action_sourcelist_activate); } diff -r a04cb4398034 -r 380ec881faa2 ui/gtk/list.c --- a/ui/gtk/list.c Thu Oct 16 15:57:05 2025 +0200 +++ b/ui/gtk/list.c Fri Oct 17 15:44:30 2025 +0200 @@ -2089,7 +2089,7 @@ if(sublist->separator) { GtkWidget *separator = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL); gtk_list_box_row_set_header(row, separator); - } else if(sublist->header) { + } else if(sublist->header && !listbox->header_is_item) { GtkWidget *header = gtk_label_new(sublist->header); gtk_widget_set_halign(header, GTK_ALIGN_START); if(row == listbox->first_row) { @@ -2177,6 +2177,7 @@ UiListBox *uilistbox = malloc(sizeof(UiListBox)); uilistbox->obj = obj; uilistbox->listbox = GTK_LIST_BOX(listbox); + uilistbox->header_is_item = args->header_is_item; uilistbox->getvalue = args->getvalue; uilistbox->getvaluedata = args->getvaluedata; uilistbox->onactivate = args->onactivate; @@ -2326,12 +2327,17 @@ } static void listbox_fill_row(UiListBox *listbox, GtkWidget *row, UiListBoxSubList *sublist, UiSubListItem *item, int index) { + UiBool is_header = index < 0; + GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10); if(item->icon) { GtkWidget *icon = ICON_IMAGE(item->icon); BOX_ADD(hbox, icon); } GtkWidget *label = gtk_label_new(item->label); + if(is_header) { + WIDGET_ADD_CSS_CLASS(label, "ui-listbox-header-row"); + } gtk_widget_set_halign(label, GTK_ALIGN_START); BOX_ADD_EXPAND(hbox, label); if(item->badge) { @@ -2352,6 +2358,9 @@ event->userdata2 = listbox->onbuttonclickdata; event->value0 = index; + // TODO: semi-memory leak when listbox_fill_row is called again for the same row + // each row update will create a new UiEventDataExt object and a separate destroy handler + g_signal_connect( row, "destroy", @@ -2390,7 +2399,8 @@ } static void update_sublist_item(UiListBox *listbox, UiListBoxSubList *sublist, int index) { - GtkListBoxRow *row = gtk_list_box_get_row_at_index(listbox->listbox, sublist->startpos + index); + int header_row = listbox->header_is_item && sublist->header ? 1 : 0; + GtkListBoxRow *row = gtk_list_box_get_row_at_index(listbox->listbox, sublist->startpos + index + header_row); if(!row) { return; } @@ -2438,22 +2448,32 @@ return; } - size_t index = 0; + int index = 0; void *elm = list->first(list); + void *first = elm; - if(!elm && sublist->header) { + if(sublist->header && !listbox->header_is_item && !elm) { // empty row for header GtkWidget *row = gtk_list_box_row_new(); cxListAdd(sublist->widgets, row); g_object_set_data(G_OBJECT(row), "ui_listbox", listbox); g_object_set_data(G_OBJECT(row), "ui_listbox_sublist", sublist); intptr_t rowindex = listbox_insert_index + index; - g_object_set_data(G_OBJECT(row), "ui_listbox_row_index", (gpointer)rowindex); + //g_object_set_data(G_OBJECT(row), "ui_listbox_row_index", (gpointer)rowindex); gtk_list_box_insert(listbox->listbox, row, listbox_insert_index + index); sublist->numitems = 1; return; } + int first_index = 0; + int header_row = 0; + if(listbox->header_is_item && sublist->header) { + index = -1; + first_index = -1; + header_row = 1; + elm = sublist->header; + } + while(elm) { UiSubListItem item = { NULL, NULL, NULL, NULL, NULL, NULL }; if(listbox->getvalue) { @@ -2464,8 +2484,8 @@ // create listbox item GtkWidget *row = gtk_list_box_row_new(); - listbox_fill_row(listbox, row, sublist, &item, (int)index); - if(index == 0) { + listbox_fill_row(listbox, row, sublist, &item, index); + if(index == first_index) { // first row in the sublist, set ui_listbox data to the row // which is then used by the headerfunc g_object_set_data(G_OBJECT(row), "ui_listbox", listbox); @@ -2477,8 +2497,8 @@ } } intptr_t rowindex = listbox_insert_index + index; - g_object_set_data(G_OBJECT(row), "ui_listbox_row_index", (gpointer)rowindex); - gtk_list_box_insert(listbox->listbox, row, listbox_insert_index + index); + //g_object_set_data(G_OBJECT(row), "ui_listbox_row_index", (gpointer)rowindex); + gtk_list_box_insert(listbox->listbox, row, listbox_insert_index + index + header_row); cxListAdd(sublist->widgets, row); // cleanup @@ -2489,7 +2509,7 @@ free(item.badge); // next row - elm = list->next(list); + elm = index >= 0 ? list->next(list) : first; index++; } @@ -2523,7 +2543,7 @@ eventdata.sublist_index = sublist->index; eventdata.row_index = data->value0; eventdata.sublist_userdata = sublist->userdata; - eventdata.row_data = eventdata.list->get(eventdata.list, eventdata.row_index); + eventdata.row_data = eventdata.row_index >= 0 ? eventdata.list->get(eventdata.list, eventdata.row_index) : NULL; eventdata.event_data = data->customdata2; UiEvent event; diff -r a04cb4398034 -r 380ec881faa2 ui/gtk/list.h --- a/ui/gtk/list.h Thu Oct 16 15:57:05 2025 +0200 +++ b/ui/gtk/list.h Fri Oct 17 15:44:30 2025 +0200 @@ -126,6 +126,7 @@ ui_callback onbuttonclick; void *onbuttonclickdata; GtkListBoxRow *first_row; + UiBool header_is_item; }; diff -r a04cb4398034 -r 380ec881faa2 ui/gtk/toolkit.c --- a/ui/gtk/toolkit.c Thu Oct 16 15:57:05 2025 +0200 +++ b/ui/gtk/toolkit.c Fri Oct 17 15:44:30 2025 +0200 @@ -394,6 +394,9 @@ " margin-top: 4px;\n" " margin-bottom: 10px;\n" "}\n" +".ui-listbox-header-row {\n" +" font-weight: bold;\n" +"}\n" ".ui-badge {\n" " background-color: #e53935;\n" " color: white;\n" @@ -444,6 +447,9 @@ " margin-top: 4px;\n" " margin-bottom: 10px;\n" "}\n" +".ui-listbox-header-row {\n" +" font-weight: bold;\n" +"}\n" ".ui-badge {\n" " background-color: #e53935;\n" " color: white;\n" diff -r a04cb4398034 -r 380ec881faa2 ui/ui/tree.h --- a/ui/ui/tree.h Thu Oct 16 15:57:05 2025 +0200 +++ b/ui/ui/tree.h Fri Oct 17 15:44:30 2025 +0200 @@ -185,12 +185,13 @@ * will be passed to free */ struct UiSubListItem { - char *icon; - char *label; - char *button_icon; - char *button_label; - char *badge; - void *eventdata; + char *icon; + char *label; + char *button_icon; + char *button_label; + UiMenuBuilder *button_menu; + char *badge; + void *eventdata; }; struct UiSourceListArgs { @@ -255,6 +256,11 @@ void *getvaluedata; /* + * is a sublist header a selectable item + */ + UiBool header_is_item; + + /* * activated when a list item is selected */ ui_callback onactivate;