add context menu, implement 'Select All'

Tue, 26 Nov 2024 10:40:45 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Tue, 26 Nov 2024 10:40:45 +0100
changeset 88
e27526429d85
parent 87
5360027fb282
child 89
2fbb3cac05a5

add context menu, implement 'Select All'

application/application.c file | annotate | diff | comparison | revisions
application/application.h file | annotate | diff | comparison | revisions
application/window.c file | annotate | diff | comparison | revisions
ui/common/context.c file | annotate | diff | comparison | revisions
ui/common/context.h file | annotate | diff | comparison | revisions
ui/common/menu.c file | annotate | diff | comparison | revisions
ui/common/menu.h file | annotate | diff | comparison | revisions
ui/gtk/image.c file | annotate | diff | comparison | revisions
ui/gtk/list.c file | annotate | diff | comparison | revisions
ui/gtk/menu.c file | annotate | diff | comparison | revisions
ui/gtk/menu.h file | annotate | diff | comparison | revisions
ui/gtk/toolkit.c file | annotate | diff | comparison | revisions
ui/ui/image.h file | annotate | diff | comparison | revisions
ui/ui/menu.h file | annotate | diff | comparison | revisions
ui/ui/toolkit.h file | annotate | diff | comparison | revisions
ui/ui/tree.h file | annotate | diff | comparison | revisions
ui/winui/appmenu.cpp file | annotate | diff | comparison | revisions
ui/winui/toolkit.cpp file | annotate | diff | comparison | revisions
--- a/application/application.c	Sun Nov 17 15:19:32 2024 +0100
+++ b/application/application.c	Tue Nov 26 10:40:45 2024 +0100
@@ -307,6 +307,14 @@
     ui_listselection_free(sel);
 }
 
+void action_selectall(UiEvent *event, void *data) {
+    DavBrowser *browser = event->document;
+    int nres = ui_list_count(browser->resources);
+    for(int i=0;i<nres;i++) {
+        ui_list_setselection(browser->resources, i);
+    }
+}
+
 static void newfiledialog_result(UiEvent *event, void *data) {
     DavBrowser *browser = event->document;
     char *path = event->eventdata;
--- a/application/application.h	Sun Nov 17 15:19:32 2024 +0100
+++ b/application/application.h	Tue Nov 26 10:40:45 2024 +0100
@@ -184,6 +184,8 @@
 
 void action_delete(UiEvent *event, void *data);
 
+void action_selectall(UiEvent *event, void *data);
+
 void action_newfile(UiEvent *event, void *data);
 
 void action_mkcol(UiEvent *event, void *data);
--- a/application/window.c	Sun Nov 17 15:19:32 2024 +0100
+++ b/application/window.c	Tue Nov 26 10:40:45 2024 +0100
@@ -42,9 +42,29 @@
 
 static UiPathElm* dav_get_pathelm(const char *full_path, size_t len, size_t *ret_nelm, void* data);
 
+static UiMenuBuilder *contextmenu;
+
 void window_init(void) {
     folder_icon = ui_foldericon(16);
     file_icon = ui_fileicon(16);
+    
+    // initialize the browser context menu
+    ui_contextmenu(&contextmenu) {
+        ui_menuitem(.label = "New Folder", .onclick = action_mkcol, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION));
+        ui_menuitem(.label = "New File", .onclick = action_newfile, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION));
+        ui_menuseparator();
+        //ui_menuitem(.label = "Cut", .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION));
+        //ui_menuitem(.label = "Copy", .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION));
+        //ui_menuitem(.label = "Paste", .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION));
+        ui_menuitem(.label = "Download", .onclick = action_download, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION));
+        ui_menuitem(.label = "Delete", .onclick = action_delete, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION));
+        ui_menuitem(.label = "Select All", .onclick = action_selectall, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION));
+        ui_menuseparator();
+        ui_menuitem(.label = "Rename", .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION));
+        ui_menuseparator();
+        ui_menuitem("Open Properties", .onclick = action_open_properties, .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION));
+        ui_menuitem("Open as Text File", .onclick = action_open_properties, .onclickdata = "text/plain", .groups = UI_GROUPS(APP_STATE_BROWSER_SESSION, APP_STATE_BROWSER_SELECTION));
+    }
 }
 
 UiObject* window_create(void) {
@@ -75,7 +95,15 @@
     model->columnsize[0] = -1;
     model->columnsize[1] = 150;
     model->getvalue = (ui_getvaluefunc) window_resource_table_getvalue;
-    ui_table(obj, .fill = UI_ON, .model = model, .onselection = action_list_selection, .onactivate = action_list_activate, .ondrop = action_dnd_drop, .varname = "reslist", .multiselection = TRUE);
+    ui_table(obj,
+            .fill = UI_ON,
+            .model = model,
+            .onselection = action_list_selection,
+            .onactivate = action_list_activate,
+            .ondrop = action_dnd_drop,
+            .varname = "reslist",
+            .multiselection = TRUE,
+            .contextmenu = contextmenu);
 
     // status bar
 
--- a/ui/common/context.c	Sun Nov 17 15:19:32 2024 +0100
+++ b/ui/common/context.c	Tue Nov 26 10:40:45 2024 +0100
@@ -62,7 +62,7 @@
     ctx->vars = cxHashMapCreate(mp->allocator, CX_STORE_POINTERS, 16);
     
     ctx->documents = cxLinkedListCreate(mp->allocator, cx_cmp_intptr, CX_STORE_POINTERS);
-    ctx->group_widgets = cxLinkedListCreate(mp->allocator, NULL, sizeof(UiGroupWidget));
+    ctx->group_widgets = cxLinkedListCreate(mp->allocator, cx_cmp_ptr, sizeof(UiGroupWidget));
     ctx->groups = cxArrayListCreate(mp->allocator, cx_cmp_int, sizeof(int), 32);
     
     ctx->attach_document = uic_context_attach_document;
@@ -331,6 +331,7 @@
                 t->obj = f->obj;
                 t->update = f->update;
                 t->getselection = f->getselection;
+                t->setselection = f->setselection;
             }
 
             UiVar tmp = *from;
@@ -556,6 +557,10 @@
     cxListAdd(ctx->group_widgets, &gw);
 }
 
+void uic_remove_group_widget(UiContext *ctx, void *widget) {
+    (void)cxListFindRemove(ctx->group_widgets, widget);
+}
+
 UIEXPORT void *ui_allocator(UiContext *ctx) {
     return (void*)ctx->allocator;
 }
--- a/ui/common/context.h	Sun Nov 17 15:19:32 2024 +0100
+++ b/ui/common/context.h	Tue Nov 26 10:40:45 2024 +0100
@@ -140,7 +140,7 @@
 void uic_check_group_widgets(UiContext *ctx);
 void uic_add_group_widget(UiContext *ctx, void *widget, ui_enablefunc enable, CxList *groups);
 void uic_add_group_widget_i(UiContext *ctx, void *widget, ui_enablefunc enable, const int *groups, size_t numgroups);
-
+void uic_remove_group_widget(UiContext *ctx, void *widget);
 
 #ifdef	__cplusplus
 }
--- a/ui/common/menu.c	Sun Nov 17 15:19:32 2024 +0100
+++ b/ui/common/menu.c	Tue Nov 26 10:40:45 2024 +0100
@@ -35,23 +35,27 @@
 #include <cx/array_list.h>
 
 
-static UiMenu *menus_begin;
-static UiMenu *menus_end;
-static CxList *current;
+static UiMenuBuilder *current_builder;
+static UiMenuBuilder global_builder;
 
 static int menu_item_counter = 0;
 
+void uic_menu_init(void) {
+    global_builder.current = cxLinkedListCreate(cxDefaultAllocator, NULL, CX_STORE_POINTERS);
+    current_builder = &global_builder;
+}
+
 static void add_menu(UiMenu *menu) {
     cx_linked_list_add(
-            (void**)&menus_begin,
-            (void**)&menus_end,
+            (void**)&current_builder->menus_begin,
+            (void**)&current_builder->menus_end,
             offsetof(UiMenu, item.prev),
             offsetof(UiMenu, item.next),
             menu);
 }
 
 static void add_item(UiMenuItemI *item) {
-    UiMenu *menu = cxListAt(current, 0);
+    UiMenu *menu = cxListAt(current_builder->current, 0);
     cx_linked_list_add(
             (void**)&menu->items_begin,
             (void**)&menu->items_end,
@@ -86,11 +90,7 @@
     return NULL;
 }
 
-void ui_menu_create(const char *label) {
-    if(!current) {
-        current = cxLinkedListCreate(cxDefaultAllocator, NULL, CX_STORE_POINTERS);
-    }
-    
+void ui_menu_create(const char *label) {   
     // create menu
     UiMenu *menu = malloc(sizeof(UiMenu));
     mitem_set_id(&menu->item);
@@ -105,7 +105,7 @@
 
     menu->end = 0;
 
-    if (cxListSize(current) == 0) {
+    if (cxListSize(current_builder->current) == 0) {
         add_menu(menu);
     }
     else {
@@ -115,16 +115,15 @@
 }
 
 UIEXPORT void ui_menu_end(void) {
-    cxListRemove(current, 0);
+    cxListRemove(current_builder->current, 0);
+    if(cxListSize(current_builder->current) == 0) {
+        current_builder = &global_builder;
+    }
 }
 
 
 
 void ui_menuitem_create(UiMenuItemArgs args) {
-    if (!current) {
-        return; // error?
-    }
-
     UiMenuItem* item = malloc(sizeof(UiMenuItem));
     mitem_set_id(&item->item);
     item->item.prev = NULL;
@@ -142,10 +141,6 @@
 }
 
 void ui_menuseparator() {
-    if(!current) {
-        return;
-    }
-    
     UiMenuItemI  *item = malloc(sizeof(UiMenuItemI));
     item->id[0] = 0;
     item->prev = NULL;
@@ -156,10 +151,6 @@
 }
 
 void ui_menu_toggleitem_create(UiMenuToggleItemArgs args) {
-    if(!current) {
-        return;
-    }
-    
     UiMenuCheckItem *item = malloc(sizeof(UiMenuCheckItem));
     mitem_set_id(&item->item);
     item->item.prev = NULL;
@@ -178,10 +169,6 @@
 }
 
 void ui_menu_radioitem_create(UiMenuToggleItemArgs args) {
-    if (!current) {
-        return;
-    }
-
     UiMenuCheckItem* item = malloc(sizeof(UiMenuCheckItem));
     mitem_set_id(&item->item);
     item->item.prev = NULL;
@@ -200,10 +187,6 @@
 }
 
 void ui_menu_itemlist_create(UiMenuItemListArgs args) {
-    if(!current) {
-        return;
-    }
-    
     UiMenuItemList*item = malloc(sizeof(UiMenuItemList));
     mitem_set_id(&item->item);
     item->item.prev = NULL;
@@ -218,10 +201,6 @@
 }
 
 void ui_menu_checkitemlist_create(UiMenuItemListArgs args) {
-    if (!current) {
-        return;
-    }
-
     UiMenuItemList* item = malloc(sizeof(UiMenuItemList));
     mitem_set_id(&item->item);
     item->item.prev = NULL;
@@ -235,10 +214,6 @@
 }
 
 void ui_menu_radioitemlist_create(UiMenuItemListArgs args) {
-    if (!current) {
-        return;
-    }
-
     UiMenuItemList* item = malloc(sizeof(UiMenuItemList));
     mitem_set_id(&item->item);
     item->item.prev = NULL;
@@ -253,27 +228,101 @@
 
 
 void uic_add_menu_to_stack(UiMenu* menu) {
-    if (!current) {
-        current = cxLinkedListCreate(cxDefaultAllocator, NULL, CX_STORE_POINTERS);
-    }
-
-    cxListInsert(current, 0, menu);
+    cxListInsert(current_builder->current, 0, menu);
 }
 
 UiMenu* uic_get_menu_list(void) {
-    return menus_begin;
+    return current_builder->menus_begin;
 }
 
 UIEXPORT void ui_menu_close(void) {
-    UiMenu* menu = cxListAt(current, 0);
+    UiMenu* menu = cxListAt(current_builder->current, 0);
     menu->end = 1;
 }
 
 UIEXPORT int ui_menu_is_open(void) {
-    UiMenu* menu = cxListAt(current, 0);
+    UiMenu* menu = cxListAt(current_builder->current, 0);
     if (menu->end) {
         ui_menu_end();
         return 0;
     }
     return 1;
 }
+
+
+void ui_contextmenu_builder(UiMenuBuilder **out_builder) {
+    UiMenuBuilder *builder = malloc(sizeof(UiMenuBuilder));
+    builder->menus_begin = NULL;
+    builder->menus_end = NULL;
+    builder->current = cxLinkedListCreate(cxDefaultAllocator, NULL, CX_STORE_POINTERS);
+    current_builder = builder;
+    *out_builder = builder;
+    
+    ui_menu_create(NULL);
+}
+
+
+
+static void free_menuitem(UiMenuItemI *item) {
+    switch(item->type) {
+        default: break;
+        case UI_MENU: {
+            UiMenu *menu = (UiMenu*)item;
+            UiMenuItemI *m = menu->items_begin;
+            while(m) {
+                UiMenuItemI *next = m->next;
+                free_menuitem(m);
+                m = next;
+            }
+            break;
+        }
+        case UI_MENU_ITEM: {
+            UiMenuItem *i = (UiMenuItem*)item;
+            free(i->groups);
+            free(i->label);
+            free(i->stockid);
+            free(i->icon);
+            break;
+        }
+        case UI_MENU_CHECK_ITEM: {
+            UiMenuCheckItem *i = (UiMenuCheckItem*)item;
+            free(i->groups);
+            free(i->label);
+            free(i->stockid);
+            free(i->icon);
+            free(i->varname);
+            break;
+        }
+        case UI_MENU_RADIO_ITEM: {
+            UiMenuRadioItem *i = (UiMenuRadioItem*)item;
+            free(i->groups);
+            free(i->label);
+            free(i->stockid);
+            free(i->icon);
+            //free(i->varname);
+            break;
+        }
+        case UI_MENU_ITEM_LIST: {
+            break;
+        }
+        case UI_MENU_CHECKITEM_LIST: {
+            break;
+        }
+        case UI_MENU_RADIOITEM_LIST: {
+            break;
+        }
+    }
+    
+    free(item);
+}
+
+void ui_menubuilder_free(UiMenuBuilder *builder) {
+    UiMenuItemI *m = &builder->menus_begin->item;
+    while(m) {
+        UiMenuItemI *next = m->next;
+        free_menuitem(m);
+        m = next;
+    }
+    cxListDestroy(builder->current);
+    free(builder);
+}
--- a/ui/common/menu.h	Sun Nov 17 15:19:32 2024 +0100
+++ b/ui/common/menu.h	Tue Nov 26 10:40:45 2024 +0100
@@ -76,9 +76,9 @@
 struct UiMenuItem {
     UiMenuItemI    item;
     ui_callback    callback;
-    const char     *label;
-    const char     *stockid;
-    const char     *icon;
+    char           *label;
+    char           *stockid;
+    char           *icon;
     void           *userdata;
     int            *groups;
     size_t         ngroups;
@@ -86,10 +86,10 @@
 
 struct UiMenuCheckItem {
     UiMenuItemI    item;
-    const char* label;
-    const char* stockid;
-    const char* icon;
-    const char* varname;
+    char           *label;
+    char           *stockid;
+    char           *icon;
+    char           *varname;
     ui_callback    callback;
     void           *userdata;
     int            *groups;
@@ -98,11 +98,11 @@
 
 struct UiMenuRadioItem {
     UiMenuItemI    item;
-    const char* label;
-    const char* stockid;
-    const char* icon;
+    char           *label;
+    char           *stockid;
+    char           *icon;
     ui_callback    callback;
-    void* userdata;
+    void           *userdata;
     int            *groups;
     size_t         ngroups;
 };
@@ -112,10 +112,19 @@
     ui_getvaluefunc getvalue;
     ui_callback     callback;
     void            *userdata;
-    const char      *varname;
+    char            *varname;
 };
 
 
+
+struct UiMenuBuilder {
+    UiMenu *menus_begin;
+    UiMenu *menus_end;
+    CxList *current;
+};
+
+void uic_menu_init(void);
+
 UiMenu* uic_get_menu_list(void);
 
 void uic_add_menu_to_stack(UiMenu* menu);
--- a/ui/gtk/image.c	Sun Nov 17 15:19:32 2024 +0100
+++ b/ui/gtk/image.c	Tue Nov 26 10:40:45 2024 +0100
@@ -29,6 +29,7 @@
 #include "image.h"
 
 #include "container.h"
+#include "menu.h"
 #include "../common/context.h"
 #include "../common/object.h"
 
@@ -51,6 +52,7 @@
     gtk_container_add(GTK_CONTAINER(eventbox), image);
 #else
     SCROLLEDWINDOW_SET_CHILD(scrolledwindow, image);
+    GtkWidget *eventbox = image;
 #endif
     
     UI_APPLY_LAYOUT1(current, args);
@@ -70,6 +72,11 @@
         }
     }
     
+    if(args.contextmenu) {
+        UIMENU menu = ui_contextmenu_create(args.contextmenu, obj, eventbox);
+        ui_widget_set_contextmenu(eventbox, menu);
+    }
+    
     return scrolledwindow;
 }
 
--- a/ui/gtk/list.c	Sun Nov 17 15:19:32 2024 +0100
+++ b/ui/gtk/list.c	Tue Nov 26 10:40:45 2024 +0100
@@ -37,6 +37,7 @@
 
 #include "list.h"
 #include "icon.h"
+#include "menu.h"
 
 
 void* ui_strmodel_getvalue(void *elm, int column) {
@@ -235,6 +236,10 @@
                 G_CALLBACK(ui_listview_selection_event),
                 event);
     }
+    if(args.contextmenu) {
+        UIMENU menu = ui_contextmenu_create(args.contextmenu, obj, view);
+        ui_widget_set_contextmenu(view, menu);
+    }
     
     
     // add widget to the current container
@@ -370,6 +375,7 @@
     // bind var
     list->update = ui_listview_update;
     list->getselection = ui_listview_getselection;
+    list->setselection = ui_listview_setselection;
     list->obj = tableview;
     
     // add callback
@@ -396,8 +402,8 @@
                 event);
     }
     // TODO: destroy callback
-
     
+      
     GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(view));
     if(args.multiselection) {
         gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
@@ -411,6 +417,11 @@
             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);
+        ui_widget_set_contextmenu(scroll_area, menu);
+    }
+    
     UI_APPLY_LAYOUT1(current, args);
     current->container->add(current->container, scroll_area, FALSE);
     
--- a/ui/gtk/menu.c	Sun Nov 17 15:19:32 2024 +0100
+++ b/ui/gtk/menu.c	Tue Nov 26 10:40:45 2024 +0100
@@ -355,34 +355,28 @@
  * widget menu functions
  */
 
+UIMENU ui_contextmenu_create(UiMenuBuilder *builder, UiObject *obj, UIWIDGET widget) {
+    GtkWidget *menu_widget = gtk_menu_new();
+    ui_add_menu_items(menu_widget, 0, builder->menus_begin, obj);
+    return GTK_MENU(menu_widget);
+}
+
 static gboolean ui_button_press_event(GtkWidget *widget, GdkEvent *event, GtkMenu *menu) {
     if(event->type == GDK_BUTTON_PRESS) {
         GdkEventButton *e = (GdkEventButton*)event;
         if(e->button == 3) {
             gtk_widget_show_all(GTK_WIDGET(menu));
-            ui_contextmenu_popup(menu);
-            return TRUE;
+            ui_contextmenu_popup(menu, widget, 0, 0);
         }
     }
     return FALSE;
 }
 
-UIMENU ui_contextmenu(UiObject *obj) {
-    UiContainer *ct = uic_get_current_container(obj);
-    return ui_contextmenu_w(obj, ct->current);
+void ui_widget_set_contextmenu(GtkWidget *widget, GtkMenu *menu) {
+    g_signal_connect(widget, "button-press-event", (GCallback) ui_button_press_event, menu);
 }
 
-UIMENU ui_contextmenu_w(UiObject *obj, UIWIDGET widget) {
-    UiContainer *ct = uic_get_current_container(obj);
-    
-    GtkMenu *menu = GTK_MENU(gtk_menu_new());
-    g_signal_connect(widget, "button-press-event", (GCallback) ui_button_press_event, menu);
-    
-    ct->menu = menu;
-    return menu;
-}
-
-void ui_contextmenu_popup(UIMENU menu) {
+void ui_contextmenu_popup(UIMENU menu, GtkWidget *widget, int x, int y) {
 #if GTK_MAJOR_VERSION >= 3 && GTK_MINOR_VERSION >= 16
     gtk_menu_popup_at_pointer(menu, NULL);
 #else
@@ -390,116 +384,6 @@
 #endif
 }
 
-void ui_widget_menuitem(UiObject *obj, char *label, ui_callback f, void *userdata) {
-    ui_widget_menuitem_gr(obj, label, f, userdata, -1);
-}
-
-void ui_widget_menuitem_gr(UiObject *obj, char *label, ui_callback f, void *userdata, ...) {
-    UiContainer *ct = uic_get_current_container(obj);
-    if(!ct->menu) {
-        return;
-    }
-    
-    // add groups
-    CxList *groups = NULL;
-    va_list ap;
-    va_start(ap, userdata);
-    int group;
-    while((group = va_arg(ap, int)) != -1) {
-        if(!groups) {
-            groups = cxArrayListCreate(cxDefaultAllocator, NULL, sizeof(int), 16);
-        }
-        cxListAdd(groups, &group);
-    }
-    va_end(ap);
-    
-    // create menuitem
-    GtkWidget *widget = gtk_menu_item_new_with_mnemonic(label);
-    gtk_widget_show(widget);
-    
-    if(f) {
-        UiEventData *event = malloc(sizeof(UiEventData));
-        event->obj = obj;
-        event->userdata = userdata;
-        event->callback = f;
-        event->value = 0;
-        event->customdata = NULL;
-
-        g_signal_connect(
-                widget,
-                "activate",
-                G_CALLBACK(ui_menu_event_wrapper),
-                event);
-        g_signal_connect(
-                widget,
-                "destroy",
-                G_CALLBACK(ui_destroy_userdata),
-                event);
-    }
-    
-    gtk_menu_shell_append(GTK_MENU_SHELL(ct->menu), widget);
-    
-    if(groups) {
-        uic_add_group_widget(obj->ctx, widget, (ui_enablefunc)ui_set_enabled, groups);
-        cxListDestroy(groups);
-    }
-}
-
-void ui_widget_menuitem_st(UiObject *obj, char *stockid, ui_callback f, void *userdata) {
-    ui_widget_menuitem_stgr(obj, stockid, f, userdata, -1);
-}
-
-void ui_widget_menuitem_stgr(UiObject *obj, char *stockid, ui_callback f, void *userdata, ...) {
-    UiContainer *ct = uic_get_current_container(obj);
-    if(!ct->menu) {
-        return;
-    }
-    
-    // add groups
-    CxList *groups = NULL;
-    va_list ap;
-    va_start(ap, userdata);
-    int group;
-    while((group = va_arg(ap, int)) != -1) {
-        if(!groups) {
-            groups = cxArrayListCreate(cxDefaultAllocator, NULL, sizeof(int), 16);
-        }
-        cxListAdd(groups, &group);
-    }
-    va_end(ap);
-    
-    // create menuitem
-    GtkWidget *widget = gtk_image_menu_item_new_from_stock(stockid, obj->ctx->accel_group);
-    gtk_widget_show(widget);
-    
-    if(f) {
-        UiEventData *event = malloc(sizeof(UiEventData));
-        event->obj = obj;
-        event->userdata = userdata;
-        event->callback = f;
-        event->value = 0;
-        event->customdata = NULL;
-
-        g_signal_connect(
-                widget,
-                "activate",
-                G_CALLBACK(ui_menu_event_wrapper),
-                event);
-        g_signal_connect(
-                widget,
-                "destroy",
-                G_CALLBACK(ui_destroy_userdata),
-                event);
-    }
-    
-    gtk_menu_shell_append(GTK_MENU_SHELL(ct->menu), widget);
-    
-    if(groups) {
-        uic_add_group_widget(obj->ctx, widget, (ui_enablefunc)ui_set_enabled, groups);
-        cxListDestroy(groups);
-    }
-}
-
 #endif /* GTK_MAJOR_VERSION <= 3 */
 
 
@@ -548,12 +432,23 @@
     g_menu_append_submenu(parent, mi->label, G_MENU_MODEL(menu));
 }
 
+static void action_enable(GSimpleAction *action, int enabled) {
+    g_simple_action_set_enabled(action, enabled);
+}
+
 void ui_gmenu_add_menuitem(GMenu *parent, int index, UiMenuItemI *item, UiObject *obj) {
     UiMenuItem *i = (UiMenuItem*)item;
-
+     
     GSimpleAction *action = g_simple_action_new(item->id, NULL);
     g_action_map_add_action(obj->ctx->action_map, G_ACTION(action));
     
+    if(i->groups) {
+        CxList *groups = cxArrayListCreateSimple(sizeof(int), i->ngroups);
+        cxListAddArray(groups, i->groups, i->ngroups);
+        uic_add_group_widget(obj->ctx, action, (ui_enablefunc)action_enable, groups);
+        cxListDestroy(groups);
+    }
+    
     if(i->callback != NULL) {
         UiEventData *event = malloc(sizeof(UiEventData));
         event->obj = obj;
@@ -702,4 +597,43 @@
     list->oldcount = i;
 }
 
+
+/* --------------------- context menu / menubuilder --------------------- */
+
+static void remove_popover(GtkWidget *object, GtkPopoverMenu *menu) {
+    gtk_widget_unparent(GTK_WIDGET(menu));
+}
+
+UIMENU ui_contextmenu_create(UiMenuBuilder *builder, UiObject *obj, GtkWidget *widget) {
+    GMenu *menu = g_menu_new();
+    ui_gmenu_add_menu_items(menu, 0, builder->menus_begin, obj);
+    GtkWidget *contextmenu = gtk_popover_menu_new_from_model(G_MENU_MODEL(menu));
+    gtk_popover_set_has_arrow(GTK_POPOVER(contextmenu), FALSE);
+    gtk_widget_set_halign(contextmenu, GTK_ALIGN_START);
+    gtk_widget_set_parent(GTK_WIDGET(contextmenu), widget);
+    g_signal_connect(
+                widget,
+                "destroy",
+                G_CALLBACK(remove_popover),
+                contextmenu);
+    return GTK_POPOVER_MENU(contextmenu);
+}
+
+static void gesture_button_press(GtkGestureClick *gesture, gint n_press, gdouble x, gdouble y, gpointer user_data) {
+    gtk_popover_set_pointing_to(GTK_POPOVER(user_data), &(GdkRectangle){ x, y, 0, 0 });
+    gtk_popover_popup(GTK_POPOVER(user_data));
+}
+
+void ui_widget_set_contextmenu(GtkWidget *widget, GtkPopoverMenu *menu) {
+    GtkGesture *gesture = gtk_gesture_click_new();
+    gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(gesture), 3);
+    gtk_widget_add_controller(widget, GTK_EVENT_CONTROLLER(gesture));
+    g_signal_connect(gesture, "pressed", G_CALLBACK(gesture_button_press), menu);
+}
+
+void ui_contextmenu_popup(UIMENU menu, UIWIDGET widget, int x, int y) {
+    gtk_popover_set_pointing_to(GTK_POPOVER(menu), &(GdkRectangle){ x, y, 0, 0 });
+    gtk_popover_popup(GTK_POPOVER(menu));
+}
+
 #endif
--- a/ui/gtk/menu.h	Sun Nov 17 15:19:32 2024 +0100
+++ b/ui/gtk/menu.h	Tue Nov 26 10:40:45 2024 +0100
@@ -39,6 +39,9 @@
 extern "C" {
 #endif
     
+UIMENU ui_create_menu(UiMenuBuilder *builder, UiObject *obj);
+void ui_widget_set_contextmenu(GtkWidget *widget, UIMENU menu);
+    
 #if GTK_MAJOR_VERSION <= 3
 
 typedef struct UiActiveMenuItemList UiActiveMenuItemList;
--- a/ui/gtk/toolkit.c	Sun Nov 17 15:19:32 2024 +0100
+++ b/ui/gtk/toolkit.c	Tue Nov 26 10:40:45 2024 +0100
@@ -78,6 +78,7 @@
     
     ui_css_init();
     uic_docmgr_init();
+    uic_menu_init();
     uic_toolbar_init();
     ui_image_init();
     uic_load_app_properties();
--- a/ui/ui/image.h	Sun Nov 17 15:19:32 2024 +0100
+++ b/ui/ui/image.h	Tue Nov 26 10:40:45 2024 +0100
@@ -52,6 +52,7 @@
     UiBool autoscale;
     UiGeneric *value;
     const char *varname;
+    UiMenuBuilder *contextmenu;
 } UiImageViewerArgs;
     
 #define ui_imageviewer(obj, ...) ui_imageviewer_create(obj, (UiImageViewerArgs){ __VA_ARGS__ } )
--- a/ui/ui/menu.h	Sun Nov 17 15:19:32 2024 +0100
+++ b/ui/ui/menu.h	Tue Nov 26 10:40:45 2024 +0100
@@ -86,35 +86,18 @@
 UIEXPORT void ui_menu_toggleitemlist_create(UiMenuItemListArgs args);
 UIEXPORT void ui_menu_radioitemlist_create(UiMenuItemListArgs args);
 
-UIEXPORT void ui_menu_deprecated(char *label);
-UIEXPORT void ui_submenu_deprecated(char *label); // deprecated
-UIEXPORT void ui_submenu_end_deprecated(); // deprecated
-
-UIEXPORT void ui_menuitem_deprecated(char *label, ui_callback f, void *userdata);
-UIEXPORT void ui_menuitem_st(char *stockid, ui_callback f, void *userdata);
-UIEXPORT void ui_menuitem_gr(char *label, ui_callback f, void *userdata, ...);
-UIEXPORT void ui_menuitem_stgr(char *stockid, ui_callback f, void *userdata, ...);
-
-
-UIEXPORT void ui_checkitem_deprecated(char *label, ui_callback f, void *userdata);
-UIEXPORT void ui_checkitem_nv_deprecated(char *label, char *vname);
-
-UIEXPORT void ui_menuitem_list_deprecated(UiList *items, ui_callback f, void *userdata);
-
 UIEXPORT void ui_menu_end(void); // TODO: private
 
 /*
  * widget menu functions
  */
-UIEXPORT UIMENU ui_contextmenu(UiObject *obj);
-UIEXPORT UIMENU ui_contextmenu_w(UiObject *obj, UIWIDGET widget);
-UIEXPORT void ui_contextmenu_popup(UIMENU menu);
+
+#define ui_contextmenu(builder) for(ui_contextmenu_builder(builder);ui_menu_is_open();ui_menu_close())
 
-UIEXPORT void ui_widget_menuitem(UiObject *obj, char *label, ui_callback f, void *userdata);
-UIEXPORT void ui_widget_menuitem_st(UiObject *obj, char *stockid, ui_callback f, void *userdata);
-UIEXPORT void ui_widget_menuitem_gr(UiObject *obj, char *label, ui_callback f, void *userdata, ...);
-UIEXPORT void ui_widget_menuitem_stgr(UiObject *obj, char *stockid, ui_callback f, void *userdata, ...);
-
+UIEXPORT void ui_contextmenu_builder(UiMenuBuilder **out_builder);
+UIEXPORT void ui_menubuilder_free(UiMenuBuilder *builder);
+UIEXPORT UIMENU ui_contextmenu_create(UiMenuBuilder *builder, UiObject *obj, UIWIDGET widget);
+UIEXPORT void ui_contextmenu_popup(UIMENU menu, UIWIDGET widget, int x, int y);
 
 // used for macro
 UIEXPORT void ui_menu_close(void);
--- a/ui/ui/toolkit.h	Sun Nov 17 15:19:32 2024 +0100
+++ b/ui/ui/toolkit.h	Tue Nov 26 10:40:45 2024 +0100
@@ -174,15 +174,16 @@
 typedef struct UiListSelection UiListSelection;
 
 /* begin opaque types */
-typedef struct UiContext    UiContext;
-typedef struct UiContainer  UiContainer;
+typedef struct UiContext     UiContext;
+typedef struct UiContainer   UiContainer;
+typedef struct UiMenuBuilder UiMenuBuilder;
 
-typedef struct UiIcon       UiIcon;
-typedef struct UiImage      UiImage;
+typedef struct UiIcon        UiIcon;
+typedef struct UiImage       UiImage;
 
-typedef struct UiDnD        UiDnD;
+typedef struct UiDnD         UiDnD;
 
-typedef struct UiThreadpool UiThreadpool;
+typedef struct UiThreadpool  UiThreadpool;
 /* end opaque types */
 
 typedef struct UiTabbedPane UiTabbedPane;
--- a/ui/ui/tree.h	Sun Nov 17 15:19:32 2024 +0100
+++ b/ui/ui/tree.h	Tue Nov 26 10:40:45 2024 +0100
@@ -130,6 +130,7 @@
     ui_callback ondrop;
     void* ondropsdata;
     UiBool multiselection;
+    UiMenuBuilder *contextmenu;
     
     const int *groups;
 };
--- a/ui/winui/appmenu.cpp	Sun Nov 17 15:19:32 2024 +0100
+++ b/ui/winui/appmenu.cpp	Tue Nov 26 10:40:45 2024 +0100
@@ -285,3 +285,11 @@
 
     return flyout;
 }
+
+UIMENU ui_contextmenu_create(UiMenuBuilder *builder, UiObject *obj, UIWIDGET widget) {
+    return NULL;
+}
+
+void ui_contextmenu_popup(UIMENU menu, UIWIDGET widget, int x, int y) {
+    
+}
--- a/ui/winui/toolkit.cpp	Sun Nov 17 15:19:32 2024 +0100
+++ b/ui/winui/toolkit.cpp	Tue Nov 26 10:40:45 2024 +0100
@@ -166,6 +166,7 @@
 
 	uic_init_global_context();
 	uic_docmgr_init();
+        uic_menu_init();
 	uic_toolbar_init();
 	
 	uic_load_app_properties();

mercurial