# HG changeset patch # User Olaf Wintermann # Date 1733487069 -3600 # Node ID 3e91b7aff6a13ffde5773f8e5770481c53d7fb0f # Parent a080c69125d10d89242c0d8a506d1e119f5cc9a1 add sourcelist widget (GTK) diff -r a080c69125d1 -r 3e91b7aff6a1 application/main.c --- a/application/main.c Thu Dec 05 20:02:30 2024 +0100 +++ b/application/main.c Fri Dec 06 13:11:09 2024 +0100 @@ -48,6 +48,8 @@ UiInteger *radio; UiInteger *tabview; UiGeneric *image; + UiList *srclist1; + UiList *srclist2; } MyDocument; MyDocument *doc1; @@ -168,6 +170,17 @@ doc->radio = ui_int_new(docctx, "radio"); doc->tabview = ui_int_new(docctx, "tabview"); doc->image = ui_generic_new(docctx, "image"); + + doc->srclist1 = ui_list_new(docctx, "srclist1"); + doc->srclist2 = ui_list_new(docctx, "srclist2"); + ui_list_append(doc->srclist1, "test1"); + ui_list_append(doc->srclist1, "test2"); + ui_list_append(doc->srclist1, "test3"); + ui_list_append(doc->srclist2, "x1"); + ui_list_append(doc->srclist2, "x2"); + + + //doc->text = ui_text_new(docctx, "text"); return doc; } @@ -252,12 +265,21 @@ ui_show(obj); } +void action_sourcelist_activate(UiEvent *event, void *userdata) { + printf("sourcelist %s index %d\n", event->eventdata, event->intval); +} + UiMenuBuilder *menubuilder; void* table_getvalue(void *row, int col) { return row; } +void sourcelist_getvalue(void *sublistdata, void *rowdata, int index, UiSubListItem *item) { + item->label = strdup(rowdata); + item->eventdata = sublistdata; +} + void application_startup(UiEvent *event, void *data) { // global list UiContext *global = ui_global_context(); @@ -270,17 +292,16 @@ UiObject *obj = ui_sidebar_window("Test", NULL); - ui_sidebar(obj, .margin = 8, .spacing = 8) { - ui_button(obj, .label = "Button 1", .style_class = "flat"); - ui_button(obj, .label = "Button 2", .style_class = "flat"); - ui_button(obj, .label = "Button 3", .style_class = "flat"); - ui_button(obj, .label = "Button 4", .style_class = "flat"); - ui_button(obj, .label = "Button 5", .style_class = "flat"); - } - MyDocument *doc = create_doc(); ui_attach_document(obj->ctx, doc); + ui_sidebar(obj, .margin = 0, .spacing = 0) { + ui_sourcelist(obj, .fill = UI_ON, + .getvalue = sourcelist_getvalue, + .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); + } + ui_tabview(obj, .spacing=10, .margin=10, .tabview = UI_TABVIEW_NAVIGATION_SIDE, .varname="tabview") { ui_tab(obj, "Tab 1") { ui_vbox(obj, .fill = UI_OFF, .margin = 15, .spacing = 15) { @@ -314,7 +335,7 @@ ui_newline(obj); //ui_button(obj, .label="Test"); - ui_path_textfield(obj, .varname = "path"); + ui_path_textfield(obj, .varname = "path", .hfill = TRUE, .hexpand = TRUE); ui_set(doc->path, "/test/path/longdirectoryname/123"); ui_newline(obj); diff -r a080c69125d1 -r 3e91b7aff6a1 ui/gtk/list.c --- a/ui/gtk/list.c Thu Dec 05 20:02:30 2024 +0100 +++ b/ui/gtk/list.c Fri Dec 06 13:11:09 2024 +0100 @@ -36,6 +36,7 @@ #include "container.h" #include +#include #include "list.h" #include "icon.h" @@ -982,3 +983,266 @@ gtk_combo_box_set_active(GTK_COMBO_BOX(combobox->widget), selection.rows[0]); } } + + +/* ------------------------------ Source List ------------------------------ */ + +static void ui_destroy_sourcelist(GtkWidget *w, UiListBox *v) { + cxListDestroy(v->sublists); + free(v); +} + +static void sublist_destroy(UiObject *obj, UiListBoxSubList *sublist) { + free(sublist->header); + ui_destroy_boundvar(obj->ctx, sublist->var); + cxListDestroy(sublist->widgets); +} + +static void listbox_create_header(GtkListBoxRow* row, GtkListBoxRow* before, gpointer user_data) { + // first rows in sublists have the ui_listbox property + UiListBox *listbox = g_object_get_data(G_OBJECT(row), "ui_listbox"); + if(!listbox) { + return; + } + + UiListBoxSubList *sublist = g_object_get_data(G_OBJECT(row), "ui_listbox_sublist"); + if(!sublist) { + return; + } + + if(sublist->separator) { + GtkWidget *separator = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL); + gtk_list_box_row_set_header(row, separator); + } else if(sublist->header) { + GtkWidget *header = gtk_label_new(sublist->header); + gtk_widget_set_halign(header, GTK_ALIGN_START); + if(row == listbox->first_row) { + WIDGET_ADD_CSS_CLASS(header, "ui-listbox-header-first"); + } else { + WIDGET_ADD_CSS_CLASS(header, "ui-listbox-header"); + } + gtk_list_box_row_set_header(row, header); + } +} + +UIEXPORT UIWIDGET ui_sourcelist_create(UiObject *obj, UiSourceListArgs args) { + UiObject* current = uic_current_obj(obj); + + GtkWidget *listbox = gtk_list_box_new(); + if(!args.style_class) { + WIDGET_ADD_CSS_CLASS(listbox, "navigation-sidebar"); + } + gtk_list_box_set_header_func(GTK_LIST_BOX(listbox), listbox_create_header, NULL, NULL); + 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); + + 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->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.numsublists == 0 && args.sublists) { + args.numsublists = INT_MAX; + } + for(int i=0;ictx, + current->ctx, + sublist.value, + sublist.varname, + UI_VAR_LIST); + uisublist.numitems = 0; + uisublist.header = sublist.header ? strdup(sublist.header) : NULL; + uisublist.separator = sublist.separator; + uisublist.widgets = cxLinkedListCreateSimple(CX_STORE_POINTERS); + uisublist.listbox = uilistbox; + uisublist.userdata = sublist.userdata; + uisublist.index = i; + + cxListAdd(uilistbox->sublists, &uisublist); + + // bind UiList + UiListBoxSubList *sublist_ptr = cxListAt(uilistbox->sublists, cxListSize(uilistbox->sublists)-1); + UiList *list = uisublist.var->value; + if(list) { + list->obj = sublist_ptr; + list->update = ui_listbox_list_update; + } + } + // fill items + ui_listbox_update(uilistbox, 0, cxListSize(uilistbox->sublists)); + + // register uilistbox for both widgets, so it doesn't matter which + // widget is used later + g_object_set_data(G_OBJECT(scroll_area), "ui_listbox", uilistbox); + g_object_set_data(G_OBJECT(listbox), "ui_listbox", uilistbox); + + // signals + g_signal_connect( + listbox, + "destroy", + G_CALLBACK(ui_destroy_sourcelist), + uilistbox); + + if(args.onactivate) { + g_signal_connect( + listbox, + "row-activated", + G_CALLBACK(ui_listbox_row_activate), + NULL); + } + + return scroll_area; +} + +void ui_listbox_update(UiListBox *listbox, int from, int to) { + CxIterator i = cxListIterator(listbox->sublists); + size_t pos = 0; + cx_foreach(UiListBoxSubList *, sublist, i) { + if(i.index < from) { + pos += sublist->numitems; + continue; + } + if(i.index > to) { + break; + } + + // reload sublist + ui_listbox_update_sublist(listbox, sublist, pos); + pos += sublist->numitems; + } +} + +static GtkWidget* create_listbox_row(UiListBox *listbox, UiListBoxSubList *sublist, UiSubListItem *item, int index) { + 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); + gtk_widget_set_halign(label, GTK_ALIGN_START); + BOX_ADD_EXPAND(hbox, label); + // TODO: badge, button + GtkWidget *row = gtk_list_box_row_new(); + LISTBOX_ROW_SET_CHILD(row, hbox); + + // signals + UiEventDataExt *event = malloc(sizeof(UiEventDataExt)); + memset(event, 0, sizeof(UiEventDataExt)); + event->obj = listbox->obj; + event->customdata0 = sublist; + event->customdata1 = sublist->var; + event->customdata2 = item->eventdata; + event->callback = listbox->onactivate; + event->userdata = listbox->onactivatedata; + event->callback2 = listbox->onbuttonclick; + event->userdata2 = listbox->onbuttonclickdata; + event->value0 = index; + + g_signal_connect( + row, + "destroy", + G_CALLBACK(ui_destroy_userdata), + event); + + g_object_set_data(G_OBJECT(row), "ui-listbox-row-eventdata", event); + + return row; +} + +void ui_listbox_update_sublist(UiListBox *listbox, UiListBoxSubList *sublist, size_t listbox_insert_index) { + // clear sublist + CxIterator r = cxListIterator(sublist->widgets); + cx_foreach(GtkWidget*, widget, r) { + LISTBOX_REMOVE(listbox->listbox, widget); + } + cxListClear(sublist->widgets); + + sublist->numitems = 0; + + // create items for each UiList element + UiList *list = sublist->var->value; + if(!list) { + return; + } + + size_t index = 0; + void *elm = list->first(list); + while(elm) { + UiSubListItem item = { NULL, NULL, NULL, NULL, NULL, NULL }; + listbox->getvalue(sublist->userdata, elm, index, &item); + + // create listbox item + GtkWidget *row = create_listbox_row(listbox, sublist, &item, (int)index); + if(index == 0) { + // 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); + g_object_set_data(G_OBJECT(row), "ui_listbox_sublist", sublist); + + if(listbox_insert_index == 0) { + // first row in the GtkListBox + listbox->first_row = GTK_LIST_BOX_ROW(row); + } + } + 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); + cxListAdd(sublist->widgets, row); + + // cleanup + free(item.label); + free(item.icon); + free(item.button_label); + free(item.button_icon); + free(item.badge); + + // next row + elm = list->next(list); + index++; + } + + sublist->numitems = cxListSize(sublist->widgets); +} + +void ui_listbox_list_update(UiList *list, int i) { + UiListBoxSubList *sublist = list->obj; +} + +void ui_listbox_row_activate(GtkListBox *self, GtkListBoxRow *row, gpointer user_data) { + UiEventDataExt *data = g_object_get_data(G_OBJECT(row), "ui-listbox-row-eventdata"); + if(!data) { + return; + } + UiListBoxSubList *sublist = data->customdata0; + + UiEvent event; + event.obj = data->obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = data->customdata2; + event.intval = data->value0; + + if(data->callback) { + data->callback(&event, data->userdata); + } +} diff -r a080c69125d1 -r 3e91b7aff6a1 ui/gtk/list.h --- a/ui/gtk/list.h Thu Dec 05 20:02:30 2024 +0100 +++ b/ui/gtk/list.h Fri Dec 06 13:11:09 2024 +0100 @@ -32,6 +32,8 @@ #include "../ui/tree.h" #include "toolkit.h" +#include + #ifdef __cplusplus extern "C" { #endif @@ -57,7 +59,33 @@ void *activatedata; void *selectiondata; } UiTreeEventData; + +typedef struct UiListBox UiListBox; + +typedef struct UiListBoxSubList { + UiVar *var; + size_t numitems; + char *header; + UiBool separator; + CxList *widgets; + UiListBox *listbox; + void *userdata; + size_t index; +} UiListBoxSubList; + +struct UiListBox { + UiObject *obj; + GtkListBox *listbox; + CxList *sublists; // contains UiListBoxSubList elements + ui_sublist_getvalue_func getvalue; + ui_callback onactivate; + void *onactivatedata; + ui_callback onbuttonclick; + void *onbuttonclickdata; + GtkListBoxRow *first_row; +}; + void* ui_strmodel_getvalue(void *elm, int column); UIWIDGET ui_listview_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata); @@ -94,6 +122,12 @@ void ui_combobox_modelupdate(UiList *list, int i); UiListSelection ui_combobox_getselection(UiList *list); void ui_combobox_setselection(UiList *list, UiListSelection selection); + +void ui_listbox_update(UiListBox *listbox, int from, int to); +void ui_listbox_update_sublist(UiListBox *listbox, UiListBoxSubList *sublist, size_t listbox_insert_index); +void ui_listbox_list_update(UiList *list, int i); + +void ui_listbox_row_activate(GtkListBox *self, GtkListBoxRow *row, gpointer user_data); #ifdef __cplusplus } diff -r a080c69125d1 -r 3e91b7aff6a1 ui/gtk/toolkit.c --- a/ui/gtk/toolkit.c Thu Dec 05 20:02:30 2024 +0100 +++ b/ui/gtk/toolkit.c Fri Dec 06 13:11:09 2024 +0100 @@ -300,6 +300,10 @@ free(data); } +void ui_destroy_widget_var(GtkWidget *object, UiVar *var) { + ui_destroy_boundvar(NULL, var); +} + void ui_destroy_boundvar(UiContext *ctx, UiVar *var) { uic_unbind_var(var); @@ -362,6 +366,18 @@ ".ui_label_title {\n" " font-weight: bold;\n" "}\n" +".ui-listbox-header {\n" +" font-weight: bold;\n" +" margin-left: 10px;\n" +" margin-top: 12px;\n" +" margin-bottom: 10px;\n" +"}\n" +".ui-listbox-header-first {\n" +" font-weight: bold;\n" +" margin-left: 10px;\n" +" margin-top: 4px;\n" +" margin-bottom: 10px;\n" +"}\n" ; #elif GTK_MAJOR_VERSION == 3 diff -r a080c69125d1 -r 3e91b7aff6a1 ui/gtk/toolkit.h --- a/ui/gtk/toolkit.h Thu Dec 05 20:02:30 2024 +0100 +++ b/ui/gtk/toolkit.h Fri Dec 06 13:11:09 2024 +0100 @@ -70,6 +70,9 @@ #define EXPANDER_SET_CHILD(expander, child) gtk_expander_set_child(GTK_EXPANDER(expander), child) #define WIDGET_ADD_CSS_CLASS(w, cssclass) gtk_widget_add_css_class(w, cssclass) #define WIDGET_REMOVE_CSS_CLASS(w, cssclass) gtk_widget_remove_css_class(w, cssclass) +#define ICON_IMAGE(icon) gtk_image_new_from_icon_name(icon) +#define LISTBOX_REMOVE(listbox, row) gtk_list_box_remove(GTK_LIST_BOX(listbox), row) +#define LISTBOX_ROW_SET_CHILD(row, child) gtk_list_box_row_set_child(GTK_LIST_BOX_ROW(row), child) #else #define WINDOW_SHOW(window) gtk_widget_show_all(window) #define WINDOW_DESTROY(window) gtk_widget_destroy(window) @@ -86,6 +89,9 @@ #define EXPANDER_SET_CHILD(expander, child) gtk_container_add(GTK_CONTAINER(expander), child) #define WIDGET_ADD_CSS_CLASS(w, cssclass) gtk_style_context_add_class(gtk_widget_get_style_context(w), cssclass) #define WIDGET_REMOVE_CSS_CLASS(w, cssclass) gtk_style_context_remove_class(gtk_widget_get_style_context(w), cssclass) +#define ICON_IMAGE(icon) gtk_image_new_from_icon_name(icon, GTK_ICON_SIZE_BUTTON) +#define LISTBOX_REMOVE(listbox, row) gtk_container_remove(GTK_CONTAINER(listbox), row) +#define LISTBOX_ROW_SET_CHILD(row, child) gtk_container_add(GTK_CONTAINER(row), child) #endif #ifdef UI_GTK2 @@ -168,6 +174,7 @@ void ui_destroy_userdata(GtkWidget *object, void *userdata); void ui_destroy_vardata(GtkWidget *object, UiVarEventData *data); +void ui_destroy_widget_var(GtkWidget *object, UiVar *var); void ui_destroy_boundvar(UiContext *ctx, UiVar *var); void ui_set_active_window(UiObject *obj); diff -r a080c69125d1 -r 3e91b7aff6a1 ui/gtk/window.c --- a/ui/gtk/window.c Thu Dec 05 20:02:30 2024 +0100 +++ b/ui/gtk/window.c Fri Dec 06 13:11:09 2024 +0100 @@ -165,6 +165,9 @@ GtkWidget *toolbar_view = adw_toolbar_view_new(); adw_toolbar_view_set_content(ADW_TOOLBAR_VIEW(toolbar_view), vbox); + GtkWidget *content_box = ui_gtk_vbox_new(0); + BOX_ADD_EXPAND(GTK_BOX(vbox), content_box); + if(sidebar) { GtkWidget *splitview = adw_overlay_split_view_new(); adw_application_window_set_content(ADW_APPLICATION_WINDOW(obj->widget), splitview); diff -r a080c69125d1 -r 3e91b7aff6a1 ui/ui/toolkit.h --- a/ui/ui/toolkit.h Thu Dec 05 20:02:30 2024 +0100 +++ b/ui/ui/toolkit.h Fri Dec 06 13:11:09 2024 +0100 @@ -151,7 +151,7 @@ #define UI_GROUPS(...) (const int[]){ __VA_ARGS__, -1 } /* public types */ -typedef int UiBool; +typedef _Bool UiBool; typedef struct UiObject UiObject; typedef struct UiContainerX UiContainerX; diff -r a080c69125d1 -r 3e91b7aff6a1 ui/ui/tree.h --- a/ui/ui/tree.h Thu Dec 05 20:02:30 2024 +0100 +++ b/ui/ui/tree.h Fri Dec 06 13:11:09 2024 +0100 @@ -35,11 +35,15 @@ extern "C" { #endif -typedef struct UiModel UiModel; -typedef struct UiListCallbacks UiListCallbacks; -typedef struct UiListDnd UiListDnd; +typedef struct UiModel UiModel; +typedef struct UiListCallbacks UiListCallbacks; +typedef struct UiListDnd UiListDnd; -typedef struct UiListArgs UiListArgs; +typedef struct UiListArgs UiListArgs; +typedef struct UiSourceListArgs UiSourceListArgs; + +typedef struct UiSubList UiSubList; +typedef struct UiSubListItem UiSubListItem; typedef enum UiModelType { UI_STRING = 0, @@ -130,6 +134,81 @@ const int *groups; }; +typedef void (*ui_sublist_getvalue_func)(void *sublist_userdata, void *rowdata, int index, UiSubListItem *item); + +struct UiSubList { + UiList *value; + const char *varname; + const char *header; + UiBool separator; + void *userdata; +}; + +/* + * list item members must be filled by the sublist getvalue func + * all members must be allocated (by malloc, strdup, ...) the pointer + * will be passed to free + */ +struct UiSubListItem { + char *icon; + char *label; + char *button_icon; + char *button_label; + char *badge; + void *eventdata; +}; + +struct UiSourceListArgs { + UiTri fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + int colspan; + int rowspan; + const char *name; + const char *style_class; + + const int *groups; + + /* + * list of sublists + * a sublist must have a varname or a value + * + * the last entry in the list must contain all NULL values or numsublists + * must contain the number of sublists + */ + UiSubList *sublists; + /* + * optional number of sublists + * if the value is 0, it is assumed, that sublists is null-terminated + * (last item contains only NULL values) + */ + size_t numsublists; + + /* + * callback for each list item, that should fill all necessary + * UiSubListItem fields + */ + ui_sublist_getvalue_func getvalue; + + /* + * activated when a list item is selected + */ + ui_callback onactivate; + void *onactivatedata; + + /* + * activated, when the additional list item button is clicked + */ + ui_callback onbuttonclick; + void *onbuttonclickdata; +}; + +#define UI_SUBLIST(...) (UiSubList){ __VA_ARGS__ } +#define UI_SUBLISTS(...) (UiSubList[]){ __VA_ARGS__, (UiSubList){NULL,NULL,NULL,0} } + + UIEXPORT UiModel* ui_model(UiContext *ctx, ...); UIEXPORT UiModel* ui_model_copy(UiContext *ctx, UiModel* model); UIEXPORT void ui_model_free(UiContext *ctx, UiModel *mi); @@ -138,16 +217,14 @@ #define ui_table(obj, ...) ui_table_create(obj, (UiListArgs) { __VA_ARGS__ } ) #define ui_combobox(obj, ...) ui_combobox_create(obj, (UiListArgs) { __VA_ARGS__ } ) #define ui_breadcrumbbar(obj, ...) ui_breadcrumbbar_create(obj, (UiListArgs) { __VA_ARGS__ } ) +#define ui_sourcelist(obj, ...) ui_sourcelist_create(obj, (UiSourceListArgs) { __VA_ARGS__ } ) UIEXPORT UIWIDGET ui_listview_create(UiObject* obj, UiListArgs args); UIEXPORT UIWIDGET ui_table_create(UiObject* obj, UiListArgs args); UIEXPORT UIWIDGET ui_combobox_create(UiObject* obj, UiListArgs args); UIEXPORT UIWIDGET ui_breadcrumbbar_create(UiObject* obj, UiListArgs args); -void ui_table_dragsource_deprecated(UIWIDGET tablewidget, int actions, char *target0, ...); -void ui_table_dragsource_a_deprecated(UIWIDGET tablewidget, int actions, char **targets, int nelm); -void ui_table_dragdest_deprecated(UIWIDGET tablewidget, int actions, char *target0, ...); -void ui_table_dragdest_a_deprecated(UIWIDGET tablewidget, int actions, char **targets, int nelm); +UIEXPORT UIWIDGET ui_sourcelist_create(UiObject *obj, UiSourceListArgs args); #ifdef __cplusplus