Menus and Actions
Menus
Users often use menus to give a command to the application. It is like this:
There are two types of objects.
- “File”, “Edit”, “View”, “Cut”, “Copy”, “Paste” and “Select All”. They are called “menu item” or simply “item”. When the user clicks one of these items, then something will happen.
- Menubar, submenu referenced by “Edit” item and two sections. They are called “menu”. Menu is an ordered list of items. They are similar to arrays.
- Menubar has three items, which are “File”, “Edit” and “View”.
- The menu item labeled “Edit” has a link to the submenu which has two items. These two items don’t have labels. Each item refers to a section.
- The first section has three items – “Cut”, “Copy” and “Paste”.
- The second section has one item – “Select All”.
Menus can build a complicated structure thanks to the links of menu items.
GMenuModel, GMenu and GMenuItem
GMenuModel is an abstract object which represents a menu. GMenu is a simple implementation of GMenuModel and a child object of GMenuModel.
GObject -- GMenuModel -- GMenu
Because GMenuModel is an abstract object, it isn’t
instantiatable. Therefore, it doesn’t have any functions to
create its instance. If you want to create a menu, use
g_menu_new to create a GMenu instance. GMenu
inherits all the GMenuModel functions.
GMenuItem is an object directly derived from GObject. GMenuItem and Gmenu (or GMenuModel) don’t have a parent-child relationship.
GObject -- GMenuModel -- GMenu
GObject -- GMenuItem
GMenuItem has attributes. One of the attributes is label. For example, One of the menu item in the diagram above has “Edit” label. “Cut”, “Copy”, “Paste” and “Select All” are also the labels of the menu items. Other attributes will be explained later.
Some menu items have a link to another GMenu. There are two types of links, submenu and section.
GMenuItem can be inserted, appended or prepended to GMenu. When it is inserted, all of the attributes and link values are copied and stored in the menu. The GMenuItem itself is not really inserted. Therefore, after the insertion, GMenuItem is useless and it should be freed. The same goes for appending or prepending.
The following code shows how to append GMenuItem to GMenu.
GMenu *menu = g_menu_new ();
GMenuItem *menu_item_quit = g_menu_item_new ("Quit", "app.quit");
g_menu_append_item (menu, menu_item_quit);
g_object_unref (menu_item_quit);
Menu and Action
One of the menu item attributes is an action and this attribute points to an action object.
There are two action objects, GSimpleAction and GPropertyAction. GSimpleAction is often used. And it is used with a menu item. This section only covers GSimpleAction.
An action that corresponds to a menu item will be activated when the menu item is clicked. Then the action emits an activate signal.
- menu item is clicked.
- The corresponding action is activated.
- The action emits a signal.
- The connected handler is invoked.
The following code is an example.
static void
quit_activated(GSimpleAction *action, GVariant *parameter, gpointer app) { ... ... ...}
GSimpleAction *act_quit = g_simple_action_new ("quit", NULL);
g_action_map_add_action (G_ACTION_MAP (app), G_ACTION (act_quit));
g_signal_connect (act_quit, "activate", G_CALLBACK (quit_activated), app);
GMenuItem *menu_item_quit = g_menu_item_new ("Quit", "app.quit");act_quitrepresents an action named “quit”. The functiong_simple_action_newcreates a stateless action, meaningact_quitdoes not maintain any internal state. Stateless actions are like simple push buttons: when clicked, they perform an operation (like “Quit”, “Copy”, or “Paste”) and nothing more. Most menu items are connected to stateless actions that take no parameters, which is exactly what the NULL argument specifies here. (Stateful actions, on the other hand, will be explained in the next section.)- The action
act_quitis added to the GtkApplication instance usingg_action_map_add_action. This means the action’s scope is application-wide, which is precisely what the “app” prefix in “app.quit” signifies. - Finally, the “activate” signal of the action is connected to
the callback handler
quit_activated. - The variable
menu_item_quitpoints to a menu item (though we often simply say it is the menu item). It has the label “Quit” and is connected to the action “app.quit”. Here, “app” is a prefix indicating that the action belongs to the GtkApplication instance, and “quit” is the name of the action. Therefore, “app.quit” refers to theact_quitaction.
If the menu is clicked, the corresponding action “quit” will
be activated and emits an “activate” signal. Then, the handler
quit_activated is called.
Menu Bar
While modern applications often use menu buttons instead of traditional menu bars, the classic style is still widely used.
An application typically defines only one menu model. If an application has multiple windows with menu bars, they display exactly the same menus because every window creates its own menu bar instance based on the same shared menu model.
An application’s menu bar typically remains unchanged once set. Therefore, the ideal place to configure it is in the startup handler, as this handler is called only once in the primary application instance.
It is helpful for readers to understand how application instances behave.
- When an application runs for the first time, this instance is called the primary instance.
- It registers itself with the system and, upon success, emits the “startup” signal.
- After that, an “activate” or “open” signal is emitted.
- If the application is launched again while the primary instance is running, this new instance is called a remote instance.
- A remote instance does not emit the “startup” signal.
- Instead of emitting “activate” or “open” signals locally, it forwards these events to the primary instance and then quits.
Therefore, the “activate” or “open” handlers can be called multiple times, whereas the “startup” handler is called exactly once. This is why the menu bar should be set in the startup handler.
static void
app_startup (GApplication *app) {
... ... ...
gtk_application_set_menubar (GTK_APPLICATION (app), G_MENU_MODEL (menubar));
... ... ...
}Simple Example
The following is a simple example of menus and actions. The
source file menu1.c is located in the /src/menu
directory.
#include <gtk/gtk.h>
static void
quit_activated(GSimpleAction *action, GVariant *parameter, GApplication *application) {
g_application_quit (application);
}
static void
app_activate (GApplication *application) {
GtkApplication *app = GTK_APPLICATION (application);
GtkWidget *win = gtk_application_window_new (app);
gtk_window_set_title (GTK_WINDOW (win), "menu1");
gtk_window_set_default_size (GTK_WINDOW (win), 800, 600);
gtk_application_window_set_show_menubar (GTK_APPLICATION_WINDOW (win), TRUE);
gtk_window_present (GTK_WINDOW (win));
}
static void
app_startup (GApplication *application) {
GtkApplication *app = GTK_APPLICATION (application);
GSimpleAction *act_quit = g_simple_action_new ("quit", NULL);
g_signal_connect (act_quit, "activate", G_CALLBACK (quit_activated), application);
g_action_map_add_action (G_ACTION_MAP (app), G_ACTION (act_quit));
g_object_unref (act_quit);
GMenu *menubar = g_menu_new ();
GMenuItem *menu_item_menu = g_menu_item_new ("Menu", NULL);
GMenu *menu = g_menu_new ();
GMenuItem *menu_item_quit = g_menu_item_new ("Quit", "app.quit");
g_menu_append_item (menu, menu_item_quit);
g_object_unref (menu_item_quit);
g_menu_item_set_submenu (menu_item_menu, G_MENU_MODEL (menu));
g_object_unref (menu);
g_menu_append_item (menubar, menu_item_menu);
g_object_unref (menu_item_menu);
gtk_application_set_menubar (GTK_APPLICATION (app), G_MENU_MODEL (menubar));
g_object_unref (menubar);
}
#define APPLICATION_ID "com.github.ToshioCP.menu1"
int
main (int argc, char **argv) {
GtkApplication *app;
int stat;
app = gtk_application_new (APPLICATION_ID, G_APPLICATION_DEFAULT_FLAGS);
g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL);
g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
stat = g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);
return stat;
}- 3–6:
quit_activatedis the handler for the “activate” signal on theact_quitaction. The “activate” signal handler takes three parameters:- Action: The action instance that emitted the signal.
- Parameter: The parameter passed to the action. In this
example, it is NULL because the second argument of
g_simple_action_new(line 22) is NULL, so we can safely ignore it. - User Data: The user data, which is provided as the fourth
argument to
g_signal_connect(line 23) when connecting the signal to the handler.
- 5: The
g_application_quitfunction immediately quits the application. - 8–16:
app_activateis the handler for the application’s “activate” signal. - 11–13: Creates a GtkApplicationWindow named
win, and sets its title and default size. - 14: Instructs the GtkApplicationWindow to display the menu bar.
- 15: Shows the window on the screen.
- 18–40:
app_startupis the handler for the application’s “startup” signal. - 22: Creates a stateless GSimpleAction named
act_quit. The first argument ofg_simple_action_newis the action’s name, and the second is its parameter type. By passing NULL, we specify that the “quit” action takes no parameters. - 23: Connects the “activate” signal of the action to the
quit_activatedhandler. - 24: Adds the action to the GtkApplication instance
app. Because GtkApplication implements the GActionMap and GActionGroup interfaces, it can hold a group of actions usingg_action_map_add_action(see the Gio API Reference). Since this action belongs to the application, its scope is “app”. Therefore, it is referred to as “app.quit” when a prefix is required. - 25: Frees the initial reference to
act_quit. Becauseg_action_map_add_actionacquires its own reference to the action when it is added to the application, our local reference is no longer needed. - 27–30: Creates the GMenu and GMenuItem instances.
menubarandmenuare GMenu objects, whilemenu_item_menuandmenu_item_quitare GMenuItem objects.menu_item_menuhas the label “Menu” but no action, whereasmenu_item_quithas the label “Quit” and is linked to the “app.quit” action. - 31–32: Appends
menu_item_quittomenu. As discussed previously, the menu model copies all attributes and links to form a new item internally. Thus, the originalmenu_item_quit instanceis no longer needed and is safely freed usingg_object_unref. - 33–34: Sets
menuas a submenu ofmenu_item_menu. Once linked, the originalmenuis no longer needed and is freed. - 35–36: Appends
menu_item_menuto themenubarand then freesmenu_item_menu. Through this process, the entire menu structure is built and anchored to themenubarvariable. (The resulting menu structure is shown in the diagram below.) - 38–39: The completed menubar model is set into the
GtkApplication. Finally, the initial reference to
menubaris freed, completing the startup initialization safely without memory leaks.
Compiling and Running
Change your current directory to src/menu. Use
comp to compile menu1.c.
$ bash comp menu1
$ ./a.out
A window will appear. Click “Menu” on the menu bar to open the drop-down menu. Click “Quit” to exit the application.
Testing Primary and Remote Application Instances
Let’s try running the application twice simultaneously to see
how multiple instances behave. You can do this elegantly by
executing both commands on a single line using
&.
$ ./a.out & ./a.out
Two identical windows will appear at once. Here is what happens behind the scenes:
- The First Command (./a.out &): This runs the application in the background and creates the primary instance. It executes the startup and activate handlers, displaying the first window.
- The Second Command (./a.out): This runs immediately after in the foreground, creating a remote instance. Because the primary instance is already running, the remote instance does not emit the startup signal. Instead, it forwards its activate signal to the primary instance and immediately quits.
- The Result: The primary instance receives the forwarded signal and executes its activate handler a second time, creating the second window.
Because both windows belong to the single primary instance and are generated from the same shared menu model, they look exactly the same. Finally, if you click the “Quit” item on either window’s menu, the primary instance quits, and both windows will close simultaneously.
The reason a second window appeared in our test is
specifically because of how our activate handler is
written. Currently, it calls
gtk_application_window_new every time it is
invoked.
This behavior is entirely up to you as a developer. Depending on your application’s design, you can change what happens during subsequent executions:
- Single Window Design: If you want your
application to only ever have one window, you should create the
window in the
startuphandler. Youractivatehandler would then only be responsible for presenting it (e.g., usinggtk_window_present). With this design, a second execution will simply bring the existing window to the front instead of creating a new one. - Tabbed Interface Design: Alternatively, you
can design the
activatehandler to add a new notebook page (tab). For example, a text file editor (liketfe) might open a new blank tab in the existing window rather than spawning a second window. In practice, this is often a much better user experience.
In real-world desktop environments, users frequently launch
applications multiple times, such as by double-clicking an icon
repeatedly. Therefore, it is crucial to carefully consider how
you divide your logic between the startup and
activate (or open) handlers to ensure
your application behaves exactly as intended.