Making an application menu

appmenu.png

This page describes how to get a menu for your application into the top bar of gnome-shell.

Requirements

In order to have an application menu, you must be using GtkApplication. See HowDoI/GtkApplication.

Each item you want exposed in your application menu must already be exposed as a GAction. See HowDoI/GAction.

Overview

Application menus are defined using GMenu and published by setting the GMenu on your GtkApplication using gtk_application_set_app_menu() from your application's startup function.

The easiest way to define a GMenu is using XML.

Actions vs. presentation

As mentioned in HowDoI/GAction, actions contain no presentation information.

As a dual to this, GMenu is almost entirely presentational information.

  • layout of the items into a hierarchy of submenus and sections
  • labels for each item
  • (optional) icons for items

Each clickable item in a GMenu also contains a reference (by name) to the action it is associated with.

Action presentation information should not ever be changed. For this reason, items in a GMenu are immutable. The idea here is that all of this information should be contained within a GtkBuilder file and never modified. GMenus themselves are mutable, however, to allow for the possibility of inserting and removing programmatically-created items such as you would want to do with a bookmark menu. The items themselves are still immutable, however.

All dynamic changes to a menu item are made by changing the state of the action that they associate with. The easiest example is that of showing a check mark or radio indicator on a menu item: you do this by setting the correct state on the associated action -- not by modifying the menu item.

This reflects the fact that the application menu (which is only defined once) may refer to actions on multiple windows, where those actions have different state, per window.

Action scopes

When referred to from a GMenu or GtkActionable widget, action names are always given in a form like "app.quit" or "win.fullscreen". The part before the dot is the scope of the action and, presently, will always be "app" or "win".

"app" actions affect the application as a whole and are installed directly on the GtkApplication. "win" actions affect one window specifically and are installed onto each GtkApplicationWindow.

When the application menu is shown by the shell, the "win." actions are referring to the copy of this action on the currently focused window. If such an action does not exist, the menu item will be greyed out.

The "app.quit" form is used only from the presentation layer (ie: from GMenu or widgets). When defining the actions themselves, only the name is used ("quit"). You create an action named "quit" and install it onto the application, and then refer to that from the GMenu as "app.quit". Similarly, you create an action named "fullscreen" and install it onto a window, and then refer to that from the menu as "win.fullscreen".

static void
quit_cb (GSimpleAction *action,
         GVariant      *parameter,
         gpointer       user_data)
{
  /* ...quit... */
}

const GActionEntry app_actions[] = {
  { "quit", quit_cb }
};

static void
my_app_startup (GApplication *app)
{
  /* ... */

  GMenu *menu;

  // for the actions, we use the name "quit"
  g_action_map_add_action_entries (G_ACTION_MAP (app), app_actions, G_N_ELEMENTS (app_actions), app);

  // from the menu, we refer to the action as "app.quit" since we installed it on the application
  menu = g_menu_new ();
  g_menu_append (menu, "Quit", "app.quit");

  /* ... */
  gtk_application_set_app_menu (GTK_APPLICATION (app),
                                G_MENU_MODEL (menu));
  g_object_unref (menu);
}

Sections, separators, headings

GMenu does not use explicitly inserted separators. Instead, it has a concept of sections. Like a submenu, each subsection of a GMenu is, itself, a GMenu.

In C:

   1 {
   2   GMenu *edit_menu;
   3   GMenu *s1, *s2;
   4 
   5   edit_menu = g_menu_new ();
   6 
   7   s1 = g_menu_new ();
   8   g_menu_append (s1, _("Copy"), "win.copy");
   9   g_menu_append (s1, _("Paste"), "win.paste");
  10   g_menu_append_section (edit_menu, NULL, s1);
  11 
  12   s2 = g_menu_new ();
  13   g_menu_append (s2, _("Preferences"), "app.preferences");
  14   g_menu_append_section (edit_menu, NULL, s2);
  15 }

or the same, in XML:

  <menu>
    <section>
      <item>
        <attribute name="label" translatable="yes">Copy</attribute>
        <attribute name="action">win.copy</attribute>
      </item>
      <item>
        <attribute name="label" translatable="yes">Paste</attribute>
        <attribute name="action">win.paste</attribute>
      </item>
    </section>
    <section>
      <item>
        <attribute name="label" translatable="yes">Preferences</attribute>
        <attribute name="action">app.preferences</attribute>
      </item>
    </section>
  </menu>

Note that double-quotes (") are used in the XML as intltool does not recognise single-quotes (').

Separators are automatically inserted between sections.

Strictly speaking, a separator is inserted at the top of any non-empty section that is not the first section.

Sections can also be named (see "View as" in the image above). This is the second parameter to g_menu_append_section() which is NULL in the example above. If a section is named then a section heading will appear at the top of the section, in place of the separator. In this case, the section heading is shown if the section is non-empty, even if the section is the first section.

Sections can be nested, but separators are only inserted between the toplevel sections within a menu.

Attributes on menu items

Toggles, radios

Toggle menu items and radio items are made in exactly the same way as a normal menu item. The only different is the type of the action that they are associated with.

See HowDoI/GAction for information on how to create an action that will be rendered as a toggle or radio item.

Note that, in contrast to other systems, in GMenuModel, a radio group of menu items all share the same action, but use a different target value on that action.

See the following XML sniplet.

<menu id="appmenu">
  <section>
    <item>
      <attribute name="label" translatable="yes">Fullscreen</attribute>
      <attribute name="action">win.fullscreen</attribute>
    </item>
  </section>
  <section>
    <item>
      <attribute name="label" translatable="yes">Left Align</attribute>
      <attribute name="action">win.justify</attribute>
      <attribute name="target">left</attribute>
    </item>
    <item>
      <attribute name="label" translatable="yes">Center</attribute>
      <attribute name="action">win.justify</attribute>
      <attribute name="target">center</attribute>
    </item>
    <item>
      <attribute name="label" translatable="yes">Right Align</attribute>
      <attribute name="action">win.justify</attribute>
      <attribute name="target">right</attribute>
    </item>
  </section>
</menu>

...and the equivalent in C:

{
  GMenu *section, *appmenu;

  appmenu = g_menu_new ();

  section = g_menu_new ();
  g_menu_append (section, "Fullscreen", "win.fullscreen);
  g_menu_append_section (appmenu, NULL, section);
  g_object_unref (section);

  section = g_menu_new ();
  g_menu_append (section, "Left Align", "justify::left");
  g_menu_append (section, "Center", "justify::center");
  g_menu_append (section, "Right Align", "justify::right");
  g_menu_append_section (appmenu, NULL, section);
  g_object_unref (section);

  ...
}

Icons

GMenu has support for icons, but this support works differently than it did in GtkMenu.

The most important thing to understand is the semantic difference between icons in GtkMenu and those in GMenu. In GtkMenu, icons were used for verbs -- things like "save", "print", etc.

In GMenu, icons are never used for verbs. They are only used for nouns: things like a webpage, an application, a device. In these cases, the icon would be the favicon for the page, the icon for the application or a representative icon for the device.

The way the icon is positioned is also different: icons from GtkImageMenuItem were positioned at the same place as the checkmark in GtkCheckMenuItem (and it was not possible to have a menu with both an icon and a checkmark). In menus rendered by GMenu, the icon is positioned to the left of the label, and causes the label to be pushed to the right. Because the icon is drawn in the space that normally belongs to the label, it is still possible to have a check or radio indicator in the usual place.

Any GIcon can be used as the icon for a menu item, using g_menu_item_set_icon().

{
  GdkPixbuf *pixbuf;
  const gchar *url;
  GMenuItem *item;

  pixbuf = bookmark_get_favicon (bookmark);
  url = bookmark_get_url (bookmark);

  item = g_menu_item_new (bookmark_get_name (bookmark), NULL);
  g_menu_item_set_action_and_target (item, "app.open-url", "s", url);

  if (pixbuf != NULL)
    {
      g_menu_item_set_icon (item, G_ICON (pixbuf));
      g_object_unref (pixbuf);
    }

  g_menu_append_item (bookmarks_menu, item);
}

Fallback

What if your application gets run in a desktop environment that doesn't support application menus ? GTK+ will take care of making your menu available, with a fallback. If the ::show-menubar property on your application window is set to TRUE (the default), it will insert a menubar to present your menu:

fallback1.png

If you want to provide your own fallback, disable the built-in fallback by calling

  gtk_application_window_set_show_menubar (win, FALSE);

With client-side window decorations, GTK+ provides fallback in the form of a menu button:

fallack2.png

The GtkHeaderBar widget will automatically determine whether to show the fallback menu button if the environment does not support application menus, and if the GtkSettings:gtk-decoration-layout property contains the menu string.

fallback3.png

If you want to force fallback application menu button, you should change the value of those settings using something like:

  g_object_set (gtk_widget_get_settings (headerbar),
                "gtk-shell-shows-app-menu", FALSE,
                "gtk-decoration-layout", "menu:close",
                NULL);

HowDoI/ApplicationMenu (last edited 2017-02-15 16:25:53 by EmmanueleBassi)