implement gtk4 menu radio items

Wed, 15 Oct 2025 18:08:43 +0200

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Wed, 15 Oct 2025 18:08:43 +0200
changeset 849
63623ef950e5
parent 848
5a4b72489111
child 850
3e1c3f4e2ad4

implement gtk4 menu radio items

application/main.c file | annotate | diff | comparison | revisions
ui/gtk/menu.c file | annotate | diff | comparison | revisions
ui/gtk/menu.h file | annotate | diff | comparison | revisions
--- a/application/main.c	Tue Oct 14 12:48:55 2025 +0200
+++ b/application/main.c	Wed Oct 15 18:08:43 2025 +0200
@@ -863,6 +863,10 @@
         ui_menuitem("Save");
         
         ui_menuseparator();
+        ui_menu_radioitem(.label = "Option 1", .varname = "menu_radio");
+        ui_menu_radioitem(.label = "Option 2", .varname = "menu_radio");
+        ui_menu_radioitem(.label = "Option 3", .varname = "menu_radio");
+        ui_menuseparator();
         
         ui_menuitem("Close");
     }
--- a/ui/gtk/menu.c	Tue Oct 14 12:48:55 2025 +0200
+++ b/ui/gtk/menu.c	Wed Oct 15 18:08:43 2025 +0200
@@ -43,6 +43,7 @@
 
 #include <cx/linked_list.h>
 #include <cx/array_list.h>
+#include <cx/printf.h>
 
 #if GTK_MAJOR_VERSION <= 3
 
@@ -487,8 +488,144 @@
     // 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 ui_action_set_state(UiInteger *i, int64_t value) {
+    
+}
+
+static int64_t ui_action_get_state(UiInteger *i) {
+    return 0;
+}
+
+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;
+    }
     
+    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 = v;
+    event.set = 0;
+    
+    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;
+    }
+}
+
+
+
+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));
+
+        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) {
--- a/ui/gtk/menu.h	Tue Oct 14 12:48:55 2025 +0200
+++ b/ui/gtk/menu.h	Wed Oct 15 18:08:43 2025 +0200
@@ -98,6 +98,14 @@
     void             *userdata;
 };
 
+typedef struct UiMenuRadioGroup {
+    UiObject      *obj;
+    CxList        *callbacks;
+    UiVar         *var;
+    GSimpleAction *action;
+} UiMenuRadioGroup;
+
+
 void ui_gmenu_add_menu_items(GMenu *parent, int i, UiMenu *menu, UiObject *obj);
 
 void ui_gmenu_add_menu(GMenu *parent, int index, UiMenuItemI *item, UiObject *obj);

mercurial