Use integer targets for GtkBuilder menu files

Hi there,

I’m using GtkBuilder with xml files to build menus and connect handlers with an array of GActionEntrys.
This works for simple stateless actions and boolean and string targets.
I cannot however get menu items (a section to create radio buttons) to work using integer (or double) targets, they’re greyed out. When I change parameter_type to string (“s”), the handler is called, with a string in the GVariant.

Here’s a snippet of the code with the handlers:

static void on_set_magnification(GSimpleAction *action,
                                 GVariant      *parameter,
                                 gpointer       data)
{
    gint32 i = g_variant_get_int32(parameter);

    g_print("%s(): got magnification of %d%%\n", __func__, i);
    g_simple_action_set_state(action, parameter);
}

static void on_foo(GSimpleAction *action,
                   GVariant      *parameter,
                   gpointer       data)
{
    g_print("FOO!\n");
}

static const GActionEntry preview_actions[] = {
    {
        .name = "close",
        .activate = on_close,
    },
    {
        .name = "set-magnification",
        .parameter_type = "i",
        .state = "100",
        .change_state = on_set_magnification,
    },
    {
        .name = "foo",
        .activate = on_foo
    }
};


GtkWidget *ui_preview_window_new(GtkApplication *app)
{
    GtkBuilder *builder;
    GMenuModel *menu;
    GtkWidget  *header;
    GtkWidget  *grid;
    GtkWidget  *primary;
    GtkWidget  *secondary;

    if (preview_window != NULL) {
        gtk_window_present(GTK_WINDOW(preview_window));
        return preview_window;
    }
    preview_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    header = gtk_header_bar_new();
    gtk_header_bar_set_show_close_button(GTK_HEADER_BAR(header), TRUE);

    primary = gtk_menu_button_new();
    gtk_header_bar_pack_start(GTK_HEADER_BAR(header), primary);
    builder = gtk_builder_new_from_resource(RESOURCE_PREFIX
                                            "/ui/preview-window-main.ui");
    menu = G_MENU_MODEL(gtk_builder_get_object(builder, "main-menu"));
    gtk_menu_button_set_menu_model(GTK_MENU_BUTTON(primary), menu);
    g_object_unref(builder);

    secondary = gtk_menu_button_new();
    gtk_header_bar_pack_end(GTK_HEADER_BAR(header), secondary);
    builder = gtk_builder_new_from_resource(RESOURCE_PREFIX
                                            "/ui/preview-window-gears.ui");
    menu = G_MENU_MODEL(gtk_builder_get_object(builder, "gears-menu"));
    gtk_menu_button_set_menu_model(GTK_MENU_BUTTON(secondary), menu);
    g_object_unref(builder);

    gtk_header_bar_set_title(GTK_HEADER_BAR(header), "Preview");
    gtk_window_set_titlebar(GTK_WINDOW(preview_window), header);

    grid = gtk_grid_new();
    preview_area = preview_create();
    gtk_grid_attach(GTK_GRID(grid), preview_area, 0, 0, 1, 1);
    gtk_container_add(GTK_CONTAINER(preview_window), grid);

    action_group = g_simple_action_group_new();
    g_action_map_add_action_entries(G_ACTION_MAP(action_group),
                                    preview_actions,
                                    G_N_ELEMENTS(preview_actions),
                                    preview_window);
    gtk_widget_insert_action_group(preview_window, "preview", G_ACTION_GROUP(action_group));

    g_signal_connect(preview_window,
                     "destroy",
                     G_CALLBACK(on_destroy),
                     NULL);
    g_signal_connect(preview_window,
                     "delete-event",
                     G_CALLBACK(on_delete_event),
                     NULL);

    return preview_window;
}

And here’s the UI file:

<?xml version="1.0" encoding="UTF-8"?>
<interface>
    <menu id="gears-menu">
        <section>
            <item>
                <attribute name="label">100%</attribute>
                <attribute name="action">preview.set-magnification</attribute>
                <attribute name="target">100</attribute>
            </item>
            <item>
                <attribute name="label">150%</attribute>
                <attribute name="action">preview.set-magnification</attribute>
                <attribute name="target">150</attribute>
            </item>
            <item>
                <attribute name="label">200%</attribute>
                <attribute name="action">preview.set-magnification</attribute>
                <attribute name="target">200</attribute>
            </item>
            <item>
                <attribute name="label">300%</attribute>
                <attribute name="action">preview.set-magnification</attribute>
                <attribute name="target">300</attribute>
            </item>
            <item>
                <attribute name="label">400%</attribute>
                <attribute name="action">preview.set-magnification</attribute>
                <attribute name="target">400</attribute>
            </item>
        </section>
        <section>
            <item>
                <attribute name="label">Foo</attribute>
                <attribute name="action">preview.foo</attribute>
            </item>
        </section>
    </menu>
</interface>

The ‘foo’ action works, it prints on stdout. The integer targets for set-magnification however do not. When I change the parameter type to string then the actions get triggered.
I’ve tried prefixing the .state with “i” and “int32”, etc, I tried prefixing the targets in the XML with “i” “int32”, etc, I can’t get it to work.

I could, as a workaround, use strings and then convert the string to int in the action handler, but that seems silly when GVariant can hold a whole bunch of different types.

So can anyone spot where I’m messing up, or perhaps point me to examples of GActionEntry’s with non-bool/non-string values?

Thanks.

EDIT: I forgot to mention I’m using Gtk+ 3.24.26 and GLib 2.66.8

The GActionEntry bits are fine.

However GtkBuilder parses all menu attributes as type “s” if not otherwise specified. That is, the following will work:

            <item>
                <attribute name="label">100%</attribute>
                <attribute name="action">preview.set-magnification</attribute>
                <attribute name="target" type="i">100</attribute>
            </item>

Thank you so much, that was the bit I was missing!

Out of curiosity, where did you find this information? I suppose I could look through the source code of GtkBuilder – where I found that the target value is parsed as a GVariant – but perhaps there’s a better place to find documentation on GtkBuilder, especially its XML schema.

Anyway, problem solved, thanks again.

I’m afraid that if this is documented somewhere, I’m not aware of it. That is, I had to peek at the source (gtkbuilder-menus.c).

Right. Well, looking at the source will work for me, no problem.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.