ui/gtk/menu.c

changeset 112
c3f2f16fa4b8
parent 110
c00e968d018b
child 113
dde28a806552
--- a/ui/gtk/menu.c	Sat Oct 04 14:54:25 2025 +0200
+++ b/ui/gtk/menu.c	Sun Oct 19 21:20:08 2025 +0200
@@ -33,6 +33,7 @@
 
 #include "menu.h"
 #include "toolkit.h"
+#include "widget.h"
 #include "../common/context.h"
 #include "../common/menu.h"
 #include "../common/types.h"
@@ -42,6 +43,7 @@
 
 #include <cx/linked_list.h>
 #include <cx/array_list.h>
+#include <cx/printf.h>
 
 #if GTK_MAJOR_VERSION <= 3
 
@@ -135,44 +137,6 @@
     }
 }
 
-/*
-void add_menuitem_st_widget(
-        GtkWidget *parent,
-        int index,
-        UiMenuItemI *item,
-        UiObject *obj)
-{
-    UiStMenuItem *i = (UiStMenuItem*)item;
-    
-    GtkWidget *widget = gtk_image_menu_item_new_from_stock(i->stockid, obj->ctx->accel_group);
-    
-    if(i->callback != NULL) {
-        UiEventData *event = malloc(sizeof(UiEventData));
-        event->obj = obj;
-        event->userdata = i->userdata;
-        event->callback = i->callback;
-        event->value = 0;
-
-        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(parent), widget);
-    
-    if(i->groups) {
-        uic_add_group_widget(obj->ctx, widget, (ui_enablefunc)ui_set_enabled, i->groups);
-    }
-}
-*/
-
 void add_menuseparator_widget(
         GtkWidget *parent,
         int index,
@@ -214,25 +178,6 @@
     // TODO
 }
 
-/*
-void add_checkitemnv_widget(GtkWidget *p, int index, UiMenuItemI *item, UiObject *obj) {
-    UiCheckItemNV *ci = (UiCheckItemNV*)item;
-    GtkWidget *widget = gtk_check_menu_item_new_with_mnemonic(ci->label);
-    gtk_menu_shell_append(GTK_MENU_SHELL(p), widget);
-    
-    UiVar *var = uic_create_var(obj->ctx, ci->varname, UI_VAR_INTEGER);
-    if(var) {
-        UiInteger *value = var->value;
-        value->obj = widget;
-        value->get = ui_checkitem_get;
-        value->set = ui_checkitem_set;
-        value = 0;
-    } else {
-        // TODO: error
-    }
-}
-*/
-
 static void menuitem_list_remove_binding(void *obj) {
     UiActiveMenuItemList *ls = obj;
     UiList *list = ls->var->value;
@@ -261,8 +206,8 @@
     ls->oldcount = 0;
     ls->getvalue = il->getvalue;
     
-    //UiVar* var = uic_create_var(ui_global_context(), il->varname, UI_VAR_LIST);
-    UiVar* var = uic_create_var(obj->ctx, il->varname, UI_VAR_LIST);
+    UiVar* var = uic_create_var(ui_global_context(), il->varname, UI_VAR_LIST);
+    //UiVar* var = uic_create_var(obj->ctx, il->varname, UI_VAR_LIST);
     ls->var = var;
     if(var) {
         UiList *list = var->value;
@@ -468,6 +413,9 @@
     GMenu *section = NULL;
     while(it) {
         if(it->type == UI_MENU_SEPARATOR) {
+            if(section) {
+                g_object_unref(section);
+            }
             section = g_menu_new();
             g_menu_append_section(parent, NULL, G_MENU_MODEL(section));
             index++;
@@ -481,6 +429,9 @@
         }
         it = it->next;
     }
+    if(section) {
+        g_object_unref(section);
+    }
 }
 
 void ui_gmenu_add_menu(GMenu *parent, int index, UiMenuItemI *item, UiObject *obj) {
@@ -488,6 +439,7 @@
     GMenu *menu = g_menu_new();
     ui_gmenu_add_menu_items(menu, 0, mi, obj);
     g_menu_append_submenu(parent, mi->label, G_MENU_MODEL(menu));
+    g_object_unref(menu);
 }
 
 static void action_enable(GSimpleAction *action, int enabled) {
@@ -499,6 +451,7 @@
      
     GSimpleAction *action = g_simple_action_new(item->id, NULL);
     g_action_map_add_action(obj->ctx->action_map, G_ACTION(action));
+    g_object_unref(action);
     
     if(i->groups) {
         CxList *groups = cxArrayListCreateSimple(sizeof(int), i->ngroups);
@@ -529,7 +482,8 @@
     }
     
     char action_name[32];
-    snprintf(action_name, 32, "win.%s", item->id); 
+    snprintf(action_name, 32, "win.%s", item->id);
+    
     g_menu_append(parent, i->label, action_name);
 }
 
@@ -543,8 +497,161 @@
     // TODO
 }
 
-void ui_gmenu_add_radioitem(GMenu *p, int index, UiMenuItemI *item, UiObject *obj) {
+
+
+typedef struct UiCallbackData {
+    ui_callback callback;
+    void *userdata;
+} UiCallbackData;
+
+static void radiogroup_remove_binding(void *obj) {
+    UiMenuRadioGroup *group = obj;
+    if(group->var) {
+        UiInteger *i = group->var->value;
+        CxList *bindings = i->obj;
+        if(bindings) {
+            (void)cxListFindRemove(bindings, obj);
+        }
+    }
+}
+
+static void stateful_action_notify_group(UiMenuRadioGroup *group, UiInteger *i) {
+    UiEvent event;
+    event.obj = group->obj;
+    event.window = event.obj->window;
+    event.document = event.obj->ctx->document;
+    event.eventdata = NULL;
+    event.eventdatatype = 0;
+    event.intval = (int)i->value;
+    event.set = ui_get_setop();
+    
+    CxIterator iter = cxListIterator(group->callbacks);
+    cx_foreach(UiCallbackData *, cb, iter) {
+        if(cb->callback) {
+            cb->callback(&event, cb->userdata);
+        }
+    }
     
+    UiObserver *obs = i->observers;
+    while(obs) {
+        if(obs->callback) {
+            obs->callback(&event, obs->data);
+        }
+        obs = obs->next;
+    }
+}
+
+static void ui_action_set_state(UiInteger *i, int64_t value) {
+    i->value = value;
+    CxList *bindings = i->obj;
+    CxIterator iter = cxListIterator(bindings);
+    cx_foreach(UiMenuRadioGroup *, group, iter) {
+        char buf[32];
+        snprintf(buf, 32, "%d", (int)value);
+        GVariant *state = g_variant_new_string(buf);
+        g_action_change_state(G_ACTION(group->action), state);
+        stateful_action_notify_group(group, i);
+    }
+}
+
+static int64_t ui_action_get_state(UiInteger *i) {
+    return i->value;
+}
+
+static UiMenuRadioGroup* create_radio_group(UiObject *obj, UiMenuRadioItem *item, GSimpleAction *action) {
+    UiMenuRadioGroup *group = cxZalloc(obj->ctx->allocator, sizeof(UiMenuRadioGroup));
+    group->callbacks = cxArrayListCreate(obj->ctx->allocator, NULL, sizeof(UiCallbackData), 8);
+    group->var = uic_create_var(ui_global_context(), item->varname, UI_VAR_INTEGER);
+    group->obj = obj;
+    group->action = action;
+    if(group->var) {
+        UiInteger *i = group->var->value;
+        CxList *bindings = i->obj;
+        if(!bindings) {
+            bindings = cxLinkedListCreate(group->var->from_ctx->mp->allocator, NULL, CX_STORE_POINTERS);
+            i->obj = bindings;
+            i->set = ui_action_set_state;
+            i->get = ui_action_get_state;
+        }
+        cxListAdd(bindings, group);
+        // the destruction of the toplevel obj must remove the binding
+        uic_context_add_destructor(obj->ctx, radiogroup_remove_binding, group);
+    }
+    return group;
+}
+
+static void stateful_action_activate(
+        GSimpleAction *action,
+        GVariant      *state,
+        UiMenuRadioGroup *group)
+{
+    g_simple_action_set_state(action, state);
+    
+    UiVar *var = group->var;
+    if(!var) {
+        return;
+    }
+    UiInteger *i = var->value;
+    
+    gsize len;
+    const char *s = g_variant_get_string(state, &len);
+    cxstring value = cx_strn(s, len);
+    int v;
+    if(!cx_strtoi(value, &v, 10)) {
+        i->value = v;
+    }
+    
+    CxList *bindings = i->obj;
+    CxIterator iter = cxListIterator(bindings);
+    cx_foreach(UiMenuRadioGroup *, gr, iter) {
+        stateful_action_notify_group(gr, i);
+    }
+}
+
+
+void ui_gmenu_add_radioitem(GMenu *parent, int index, UiMenuItemI *item, UiObject *obj) {
+    UiMenuRadioItem *i = (UiMenuRadioItem*)item;
+    
+    if(!i->varname) {
+        return;
+    }
+
+    // All radio buttons with the name varname use the same GAction
+    UiMenuRadioGroup *group = NULL;
+    GAction *action = g_action_map_lookup_action(obj->ctx->action_map, i->varname);
+    if(!action) {
+        GVariant *state = g_variant_new_string("0");
+        GSimpleAction *newAction = g_simple_action_new_stateful(i->varname, G_VARIANT_TYPE_STRING, state);
+        g_action_map_add_action(obj->ctx->action_map, G_ACTION(newAction));
+        g_object_unref(newAction);
+
+        group = create_radio_group(obj, i, newAction);
+        g_object_set_data(G_OBJECT(newAction), "ui_radiogroup", group);
+
+        g_signal_connect(
+                newAction,
+                "change-state",
+                G_CALLBACK(stateful_action_activate),
+                group);
+    } else {
+        group = g_object_get_data(G_OBJECT(action), "ui_radiogroup");
+        if(!group) {
+            fprintf(stderr, "Error: missing ui_radiogroup property for action %s\n", i->varname);
+            return; // error, should not happen
+        }
+    }
+    
+    size_t item_index = cxListSize(group->callbacks);
+    
+    UiCallbackData cb;
+    cb.callback = i->callback;
+    cb.userdata = i->userdata;
+    cxListAdd(group->callbacks, &cb);
+    
+    
+    cxmutstr action_name = cx_asprintf("win.%s::%d", i->varname, (int)item_index);
+    g_menu_append(parent, i->label, action_name.ptr);
+    free(action_name.ptr);
 }
 
 static void menuitem_list_remove_binding(void *obj) {
@@ -578,6 +685,7 @@
     
     GSimpleAction *action = g_simple_action_new(item->id, g_variant_type_new("i"));
     g_action_map_add_action(obj->ctx->action_map, G_ACTION(action));
+    g_object_unref(action);
     snprintf(ls->action, 32, "win.%s", item->id);
     
     UiVar* var = uic_create_var(ui_global_context(), il->varname, UI_VAR_LIST);
@@ -691,6 +799,7 @@
         GVariant *v = g_variant_new("i", i);
         g_menu_item_set_action_and_target_value(item, list->action, v);
         g_menu_insert_item(list->menu, list->index+i, item);
+        g_object_unref(item);
         
         elm = ui_list_next(ls);
         i++;
@@ -710,6 +819,7 @@
     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));
+    g_object_unref(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);

mercurial