Gtk4-migrating-from-gtk3: Menus, Actions and multiple windows generated using the same XML ui file

Dear All,
thanks in advance for reading me, I am migrating a Gtk3 app to Gtk4 and I am facing some issues,
first I had troubles to switch from the GtkMenu to the new menu format, but I got lot of help here , thanks !
So I managed to re-create my the menus of my application, and attach actions, so far like this:

The main.ui XML file:

<interface>
  <menu id="menu_bar">
    <item>
          <attribute name="label" translatable="yes">Open</attribute>
          <attribute name="action">app.open</attribute>
          <attribute name="accel">&lt;Control&gt;O</attribute>
        </item>
  </menu>
</interface>

And the corresponding code:

void do_something_with_my_struct (gpointer data)
{
  my_struct * user_data = (my_struct *)data; 
  // Then do what ever I want to do
}

static void atomes_menu_bar_action (GSimpleAction * action, GVariant * parameter, gpointer data)
{
  gchar * name = g_strdup_printf ("%s", g_action_get_name(G_ACTION(action)));
  if (g_strcmp0 (name, "connect") == 0)
  {
    do_something_with_my_struct (data);
  }
}

GtkWidget * create_main_window (GApplication * my_app)
{
  GSimpleAction * act_connect    = g_simple_action_new ("open",  NULL);
  g_action_map_add_action (G_ACTION_MAP(my_app), G_ACTION(act_connect));
  my_struct * user_data = create_struct (); // to prepare my data structure
  g_signal_connect (act_connect, "activate", G_CALLBACK(do_simple_action),  (gpointer)user_data);
 
  GtkWidget * window = gtk_application_window_new (GTK_APPLICATION(my_app));
  GtkBuilder * builder = gtk_builder_new_from_file ("menus/main.ui");
  GMenuModel * model = G_MENU_MODEL (gtk_builder_get_object (builder, "menu_bar")); 
  gtk_application_set_menubar (GTK_APPLICATION(my_app), model);
  gtk_application_window_set_show_menubar (GTK_APPLICATION_WINDOW(window), TRUE);
  return window;
}

This works for the main application window, what about other windows generated within my application.
In particular if each of these windows do have menu bar / menu, generated using a similar method, and most of all generated using the same XML file … the action name in this XML file being the same then no matter what I do in the code, like changing the data in my_struct, then I am guessing that each new command that I add to the G_ACTION_MAP will already exist with the same name, and if it works, will only replace the previous one … am I correct ?
If yes what can I do to ensure that for each window the activation of the menu item responds with the proper action ?

Thanks in advance for your lights !

Best regards

SĂ©bastien

I am guessing that each new command that I add to the G_ACTION_MAP will already exist with the same name, and if it works, will only replace the previous one … am I correct ?

Yes. Unless you want to unnecessarily complicate your life, you should only add application-wide actions for actions that apply to the whole application (like “help”, “open-prefs”, “new-window”). The canonical place for creating those actions is the startup vfunc or signal, which runs exactly once for the primary instance. Other global setup (like calling gtk_application_set_menubar()) should go there as well.

For actions in the context of a single window, you should use the “win” namespace and add them to the window (like GApplication, GtkApplicationWindow implements the GActionGroup and GActionMap interfaces).

Dear @fmuellner, first of all thank you for your answer,
I manage to insert actions, for each windows, even if menus are generated using the same method,
using the following lines of code:

GMenu * curve_menu_bar (GSimpleActionGroup * action_group, gchar * str)
{
  GMenu * menu = g_menu_new ();
  gchar * str_edit = g_strdup_printf ("%.edit.curve", str);
  GMenuItem * item = g_menu_item_new ("Edit Curve", (const gchar *)str_edit);
  g_menu_item_set_attribute (item, "accel", "s","<CTRL>E", NULL);
  g_menu_append_item (menu, item);
  g_object_unref (item);
  return menu;
}

... 

GSimpleActionGroup * action_group = g_simple_action_group_new ();
GSimpleAction * curve_action;
curve_action = g_simple_action_new ("edit.curve", NULL);
g_action_map_add_action (G_ACTION_MAP(action_group), G_ACTION(curve_action));

// data is a pointer that contains appropriate information to identify each window in the callback.
g_signal_connect (curve_action, "activate", G_CALLBACK(curve_menu_action), data);

// action_id is an integer, that I use as identifier, and is different for each window.
gchar * str = g_strdup_printf ("c-%d", action_id);
GtkWidget * menu = gtk_menu_bar_new_from_model ((GMenuModel *)curve_menu_bar(str));
gtk_widget_insert_action_group (menu, str, G_ACTION_GROUP(action_group));
g_free (str);

In curve_menu_bar(str) I create the GMenu(s) and GMenuItem(s) and set up the actions accordingly, doint so everything is working perfectly.
I go only one small issue remaining, and that concerns the accelerators, that for some reason do not work, after creating the menu items they properly appear in the menu, but I need to I handle the “key-press-event” signal on the window associated to the menu to get them working.
For the main application window it was working without it, so am I doing something wrong while setting up my actions, or is it standard behavior ?

Thanks in advance for your help.
Best regards.

SĂ©bastien

Ok, find out the solutiom my-self,
the action group must be attached to the window it-self and not the menu.

1 Like

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