2023-01-03 07:30:06 +01:00
|
|
|
Up: [README.md](../README.md), Prev: [Section 16](sec16.md), Next: [Section 18](sec18.md)
|
2021-01-13 05:29:35 +01:00
|
|
|
|
2023-07-15 10:27:53 +02:00
|
|
|
# Menus and actions
|
2021-01-13 05:29:35 +01:00
|
|
|
|
2023-07-15 10:27:53 +02:00
|
|
|
## Menus
|
2021-01-13 05:29:35 +01:00
|
|
|
|
2023-07-15 10:27:53 +02:00
|
|
|
Users often use menus to tell a command to the application.
|
2021-06-15 04:24:42 +02:00
|
|
|
It is like this:
|
2021-01-13 05:29:35 +01:00
|
|
|
|
2021-06-15 04:24:42 +02:00
|
|
|
![Menu](../image/menu.png)
|
2021-01-13 05:29:35 +01:00
|
|
|
|
2023-07-15 10:27:53 +02:00
|
|
|
There are two types of objects.
|
2021-01-13 05:29:35 +01:00
|
|
|
|
2021-06-15 04:24:42 +02:00
|
|
|
- "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.
|
2021-01-13 05:29:35 +01:00
|
|
|
|
2021-06-15 04:24:42 +02:00
|
|
|
![Menu structure](../image/menu_structure.png)
|
2021-01-13 05:29:35 +01:00
|
|
|
|
2021-06-15 04:24:42 +02:00
|
|
|
- Menubar is a menu which 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 is a menu which has three items -- "Cut", "Copy" and "Paste".
|
|
|
|
- The second section is a menu which has one item -- "Select All".
|
2021-01-13 05:29:35 +01:00
|
|
|
|
2021-06-15 04:24:42 +02:00
|
|
|
Menus can build a complicated structure thanks to the links of menu items.
|
2021-01-13 05:29:35 +01:00
|
|
|
|
2021-06-15 04:24:42 +02:00
|
|
|
## GMenuModel, GMenu and GMenuItem
|
2021-01-13 05:29:35 +01:00
|
|
|
|
2021-06-15 04:24:42 +02:00
|
|
|
GMenuModel is an abstract object which represents a menu.
|
|
|
|
GMenu is a simple implementation of GMenuModel and a child object of GMenuModel.
|
2021-01-13 05:29:35 +01:00
|
|
|
|
2021-06-20 15:16:41 +02:00
|
|
|
GObject -- GMenuModel -- GMenu
|
2021-01-13 05:29:35 +01:00
|
|
|
|
2021-06-20 15:16:41 +02:00
|
|
|
Because GMenuModel is an abstract object, it isn't instantiatable.
|
2021-06-21 04:22:04 +02:00
|
|
|
Therefore, it doesn't have any functions to create its instance.
|
2021-06-20 15:16:41 +02:00
|
|
|
If you want to create a menu, use `g_menu_new` to create a GMenu instance.
|
2023-01-03 07:30:06 +01:00
|
|
|
GMenu inherits all the functions of GMenuModel.
|
2021-01-13 05:29:35 +01:00
|
|
|
|
2021-06-15 04:24:42 +02:00
|
|
|
GMenuItem is an object directly derived from GObject.
|
|
|
|
GMenuItem and Gmenu (or GMenuModel) don't have a parent-child relationship.
|
2021-01-13 05:29:35 +01:00
|
|
|
|
2021-06-15 04:24:42 +02:00
|
|
|
GObject -- GMenuModel -- GMenu
|
|
|
|
GObject -- GMenuItem
|
2021-01-13 05:29:35 +01:00
|
|
|
|
2021-06-20 15:16:41 +02:00
|
|
|
GMenuItem has attributes.
|
2021-06-15 04:24:42 +02:00
|
|
|
One of the attributes is label.
|
2023-01-03 07:30:06 +01:00
|
|
|
For example, there is a menu item which has "Edit" label in the first diagram.
|
2021-06-20 15:16:41 +02:00
|
|
|
"Cut", "Copy", "Paste" and "Select All" are also the labels of the menu items.
|
2021-06-15 04:24:42 +02:00
|
|
|
Other attributes will be explained later.
|
2021-01-13 05:29:35 +01:00
|
|
|
|
2021-06-15 04:24:42 +02:00
|
|
|
Some menu items have a link to another GMenu.
|
|
|
|
There are two types of links, submenu and section.
|
2021-01-13 05:29:35 +01:00
|
|
|
|
2021-06-15 04:24:42 +02:00
|
|
|
GMenuItem can be inserted, appended or prepended to GMenu.
|
2023-01-03 07:30:06 +01:00
|
|
|
When it is inserted, all of the attributes and link values are copied and stored in the menu.
|
2021-06-15 04:24:42 +02:00
|
|
|
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.
|
2021-01-13 05:29:35 +01:00
|
|
|
|
2021-06-15 04:24:42 +02:00
|
|
|
The following code shows how to append GMenuItem to GMenu.
|
2021-01-13 05:29:35 +01:00
|
|
|
|
2021-06-15 04:24:42 +02:00
|
|
|
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);
|
2021-01-13 05:29:35 +01:00
|
|
|
|
2021-06-15 04:24:42 +02:00
|
|
|
## Menu and action
|
2021-01-13 05:29:35 +01:00
|
|
|
|
2021-06-15 04:24:42 +02:00
|
|
|
One of the attributes of menu items is an action.
|
|
|
|
This attribute points an action object.
|
2021-01-13 05:29:35 +01:00
|
|
|
|
2021-06-15 04:24:42 +02:00
|
|
|
There are two action objects, GSimpleAction and GPropertyAction.
|
|
|
|
GSimpleAction is often used.
|
|
|
|
And it is used with a menu item.
|
|
|
|
Only GSimpleAction is described in this section.
|
2021-01-13 05:29:35 +01:00
|
|
|
|
2021-06-15 04:24:42 +02:00
|
|
|
An action corresponds to a menu item will be activated when the menu item is clicked.
|
|
|
|
Then the action emits an activate signal.
|
2021-01-13 05:29:35 +01:00
|
|
|
|
2021-06-15 04:24:42 +02:00
|
|
|
1. menu item is clicked.
|
|
|
|
2. The corresponding action is activated.
|
|
|
|
3. The action emits a signal.
|
|
|
|
4. The connected handler is invoked.
|
2021-01-13 05:29:35 +01:00
|
|
|
|
|
|
|
|
2021-06-15 04:24:42 +02:00
|
|
|
The following code is an example.
|
2021-01-13 05:29:35 +01:00
|
|
|
|
2021-06-21 04:22:04 +02:00
|
|
|
~~~C
|
|
|
|
static void
|
|
|
|
quit_activated(GSimpleAction *action, GVariant *parameter, gpointer app) { ... ... ...}
|
2020-12-22 03:30:06 +01:00
|
|
|
|
2021-06-21 04:22:04 +02:00
|
|
|
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");
|
|
|
|
~~~
|
2020-12-22 03:30:06 +01:00
|
|
|
|
2023-01-03 07:30:06 +01:00
|
|
|
- The variable `menu_item_quit` points a menu item.
|
|
|
|
It is actually a pointer, but we often say that `menu_item_quit` *is* a menu item.
|
2021-06-15 04:24:42 +02:00
|
|
|
It has a label "Quit" and is connected to an action "app.quit".
|
2023-01-03 07:30:06 +01:00
|
|
|
"app" is a prefix and "quit" is the name of the action.
|
|
|
|
The prefix "app" means that the action belongs to the GtkApplication instance.
|
|
|
|
- `act_quit` is an action.
|
2021-06-15 04:24:42 +02:00
|
|
|
It has a name "quit".
|
2021-06-20 15:16:41 +02:00
|
|
|
The function `g_simple_action_new` creates a stateless action.
|
2021-06-15 04:24:42 +02:00
|
|
|
So, `act_quit` is stateless.
|
|
|
|
The meaning of stateless will be explained later.
|
|
|
|
The argument `NULL` means that the action doesn't have an parameter.
|
2021-06-20 15:16:41 +02:00
|
|
|
Most of the actions are stateless and have no parameter.
|
2023-01-03 07:30:06 +01:00
|
|
|
- The action `act_quit` is added to the GtkApplication instance with `g_action_map_add_action`.
|
|
|
|
So, the action's scope is application.
|
|
|
|
The prefix of `app.quit` indicates the scope.
|
|
|
|
- "activate" signal of the action is connected to the handler `quit_activated`.
|
|
|
|
|
|
|
|
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
|
|
|
|
|
2023-07-15 10:27:53 +02:00
|
|
|
A menu bar and menus are traditional.
|
2023-01-03 07:30:06 +01:00
|
|
|
Menu buttons are often used instead of a menu bar lately, but the old style is still used widely.
|
|
|
|
|
|
|
|
Applications have only one menu bar.
|
|
|
|
If an application has two or more windows which have menu bars, the menu bars are exactly the same.
|
|
|
|
Because every window refers to the same menubar instance in the application.
|
|
|
|
|
|
|
|
An application's menu bar is usually unchanged once it is set.
|
|
|
|
So, it is appropriate to set it in the "startup" handler.
|
2023-07-15 10:27:53 +02:00
|
|
|
Because the handler is called only once in the primary application instance.
|
2023-01-03 07:30:06 +01:00
|
|
|
|
|
|
|
I think it is good for readers to clarify how applications behave.
|
|
|
|
|
2023-07-15 10:27:53 +02:00
|
|
|
- When an application runs for the first time, the instance is called primary.
|
2023-01-03 07:30:06 +01:00
|
|
|
- The primary instance registers itself to the system. If it succeeds, it emits "startup" signal.
|
|
|
|
- When the instance is activated, an "activate" or "open" signal is emitted.
|
|
|
|
- If the application is run for the second time or later and there exists a primary instance, the instance is called a remote instance.
|
2023-07-15 10:27:53 +02:00
|
|
|
- A remote instance doesn't emit "startup" signal.
|
2023-01-03 07:30:06 +01:00
|
|
|
- If it tries to emit an "activate" or "open" signal, the signals are not emitted on the remote instance but primary instance.
|
|
|
|
- The remote instance quits.
|
|
|
|
|
|
|
|
Therefore, an "activate" or "open" handler can be called twice or more.
|
|
|
|
On the other hand, a "startup" handler is called once.
|
2023-07-15 10:27:53 +02:00
|
|
|
So, the menubar should be set in the "startup" handler.
|
2023-01-03 07:30:06 +01:00
|
|
|
|
|
|
|
~~~C
|
|
|
|
static void
|
|
|
|
app_startup (GApplication *app) {
|
|
|
|
... ... ...
|
|
|
|
gtk_application_set_menubar (GTK_APPLICATION (app), G_MENU_MODEL (menubar));
|
|
|
|
... ... ...
|
|
|
|
}
|
|
|
|
~~~
|
2021-01-13 05:29:35 +01:00
|
|
|
|
2021-06-15 04:24:42 +02:00
|
|
|
## Simple example
|
2021-01-13 05:29:35 +01:00
|
|
|
|
2021-06-15 04:24:42 +02:00
|
|
|
The following is a simple example of menus and actions.
|
2023-01-03 07:30:06 +01:00
|
|
|
The source file `menu1.c` is located at [src/menu](../src/menu) directory.
|
2020-12-22 03:30:06 +01:00
|
|
|
|
2021-01-25 10:35:49 +01:00
|
|
|
~~~C
|
2021-06-15 04:24:42 +02:00
|
|
|
1 #include <gtk/gtk.h>
|
|
|
|
2
|
|
|
|
3 static void
|
2023-01-03 07:30:06 +01:00
|
|
|
4 quit_activated(GSimpleAction *action, GVariant *parameter, GApplication *application) {
|
|
|
|
5 g_application_quit (application);
|
|
|
|
6 }
|
|
|
|
7
|
|
|
|
8 static void
|
|
|
|
9 app_activate (GApplication *application) {
|
|
|
|
10 GtkApplication *app = GTK_APPLICATION (application);
|
2023-07-15 10:27:53 +02:00
|
|
|
11 GtkWidget *win = gtk_application_window_new (app);
|
2023-01-03 07:30:06 +01:00
|
|
|
12 gtk_window_set_title (GTK_WINDOW (win), "menu1");
|
|
|
|
13 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
|
|
|
|
14
|
|
|
|
15 gtk_application_window_set_show_menubar (GTK_APPLICATION_WINDOW (win), TRUE);
|
|
|
|
16 gtk_window_present (GTK_WINDOW (win));
|
|
|
|
17 }
|
|
|
|
18
|
|
|
|
19 static void
|
|
|
|
20 app_startup (GApplication *application) {
|
|
|
|
21 GtkApplication *app = GTK_APPLICATION (application);
|
|
|
|
22
|
|
|
|
23 GSimpleAction *act_quit = g_simple_action_new ("quit", NULL);
|
|
|
|
24 g_action_map_add_action (G_ACTION_MAP (app), G_ACTION (act_quit));
|
|
|
|
25 g_signal_connect (act_quit, "activate", G_CALLBACK (quit_activated), application);
|
|
|
|
26
|
|
|
|
27 GMenu *menubar = g_menu_new ();
|
|
|
|
28 GMenuItem *menu_item_menu = g_menu_item_new ("Menu", NULL);
|
|
|
|
29 GMenu *menu = g_menu_new ();
|
|
|
|
30 GMenuItem *menu_item_quit = g_menu_item_new ("Quit", "app.quit");
|
|
|
|
31 g_menu_append_item (menu, menu_item_quit);
|
|
|
|
32 g_object_unref (menu_item_quit);
|
|
|
|
33 g_menu_item_set_submenu (menu_item_menu, G_MENU_MODEL (menu));
|
2023-07-15 10:27:53 +02:00
|
|
|
34 g_object_unref (menu);
|
|
|
|
35 g_menu_append_item (menubar, menu_item_menu);
|
|
|
|
36 g_object_unref (menu_item_menu);
|
|
|
|
37
|
|
|
|
38 gtk_application_set_menubar (GTK_APPLICATION (app), G_MENU_MODEL (menubar));
|
|
|
|
39 }
|
|
|
|
40
|
|
|
|
41 #define APPLICATION_ID "com.github.ToshioCP.menu1"
|
|
|
|
42
|
|
|
|
43 int
|
|
|
|
44 main (int argc, char **argv) {
|
|
|
|
45 GtkApplication *app;
|
|
|
|
46 int stat;
|
|
|
|
47
|
|
|
|
48 app = gtk_application_new (APPLICATION_ID, G_APPLICATION_DEFAULT_FLAGS);
|
|
|
|
49 g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL);
|
|
|
|
50 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
|
|
|
|
51
|
|
|
|
52 stat =g_application_run (G_APPLICATION (app), argc, argv);
|
|
|
|
53 g_object_unref (app);
|
|
|
|
54 return stat;
|
|
|
|
55 }
|
|
|
|
56
|
2021-01-25 10:35:49 +01:00
|
|
|
~~~
|
2021-01-13 05:29:35 +01:00
|
|
|
|
2023-01-03 07:30:06 +01:00
|
|
|
- 3-6: `quit_activated` is a handler of the "activate" signal on the action `act_quit`.
|
2021-06-20 15:16:41 +02:00
|
|
|
Handlers of the "activate" signal have three parameters.
|
|
|
|
1. The action instance on which the signal is emitted.
|
2021-06-15 04:24:42 +02:00
|
|
|
2. Parameter.
|
2023-01-03 07:30:06 +01:00
|
|
|
In this example it is `NULL` because the second argument of `g_simple_action_new` (line 23) is `NULL`.
|
2021-06-15 04:24:42 +02:00
|
|
|
You don' t need to care about it.
|
|
|
|
3. User data.
|
2023-01-03 07:30:06 +01:00
|
|
|
It is the fourth parameter in the `g_signal_connect` (line 25) that connects the action and the handler.
|
|
|
|
- 5: The function `g_application_quit` immediately quits the application.
|
2023-07-15 10:27:53 +02:00
|
|
|
- 8-17: `app_activate` is an "activate" signal handler on the application.
|
2023-01-03 07:30:06 +01:00
|
|
|
- 11-13: Creates a GtkApplicationWindow `win`. And sets the title and the default size.
|
|
|
|
- 15: Sets GtkApplicationWindow to show the menubar.
|
|
|
|
- 16: Shows the window.
|
2023-07-15 10:27:53 +02:00
|
|
|
- 19-38: `app_startup` is a "startup" signal handler on the application.
|
2023-01-03 07:30:06 +01:00
|
|
|
- 23: Creates GSimpleAction `act_quit`.
|
2021-06-15 04:24:42 +02:00
|
|
|
It is stateless.
|
|
|
|
The first argument of `g_simple_action_new` is a name of the action and the second argument is a parameter.
|
|
|
|
If you don't need the parameter, pass `NULL`.
|
|
|
|
Therefore, `act_quit` has a name "quit" and no parameter.
|
2023-01-03 07:30:06 +01:00
|
|
|
- 24: Adds the action to GtkApplication `app`.
|
2021-06-15 04:24:42 +02:00
|
|
|
GtkApplication implements an interface GActionMap and GActionGroup.
|
2021-06-20 15:16:41 +02:00
|
|
|
GtkApplication (GActionMap) can have a group of actions and the actions are added with the function `g_action_map_add_action`.
|
2023-01-03 07:30:06 +01:00
|
|
|
This function is described in [Gio API Reference -- g\_action\_map\_add\_action](https://docs.gtk.org/gio/method.ActionMap.add_action.html).
|
|
|
|
Because this action belongs to GtkApplication, its scope is "app" and it is referred with "app.quit" if the prefix (scope) is necessary.
|
|
|
|
- 25: Connects "activate" signal of the action and the handler `quit_activated`.
|
|
|
|
- 27-30: Creates GMenu and GMenuItem instances.
|
2021-06-15 04:24:42 +02:00
|
|
|
`menubar` and `menu` are GMenu.
|
|
|
|
`menu_item_menu` and `menu_item_quit` are GMenuItem.
|
|
|
|
`menu_item_menu` has a label "Menu" and no action.
|
2021-06-20 15:16:41 +02:00
|
|
|
`menu_item_quit` has a label "Quit" and an action "app.quit".
|
2023-01-03 07:30:06 +01:00
|
|
|
- 31-32: Appends `menu_item_quit` to `menu`.
|
2021-06-20 15:16:41 +02:00
|
|
|
As I mentioned before, all the attributes and links are copied and used to form a new item in `menu`.
|
2023-01-03 07:30:06 +01:00
|
|
|
Therefore after the addition, `menu_item_quit` is no longer needed.
|
2021-06-15 04:24:42 +02:00
|
|
|
It is freed by `g_object_unref`.
|
2023-07-15 10:27:53 +02:00
|
|
|
- 33-34: Sets the submenu link in `menu_item_menu` to point `menu`.
|
|
|
|
Then, `menu` is no more useful and it is freed.
|
|
|
|
- 35-36: Appends `menu_item_menu` to `menubar`.
|
2021-06-15 04:24:42 +02:00
|
|
|
Then frees `menu_item_menu`.
|
2023-07-15 10:27:53 +02:00
|
|
|
GMenu and GMenuItem are built and finally connected to the variable `menubar`.
|
2021-06-15 04:24:42 +02:00
|
|
|
The structure of the menu is shown in the diagram below.
|
2023-07-15 10:27:53 +02:00
|
|
|
- 38: The menubar is inserted to the application.
|
2021-06-15 04:24:42 +02:00
|
|
|
|
|
|
|
![menu and action](../image/menu1.png)
|
|
|
|
|
2023-01-03 07:30:06 +01:00
|
|
|
## Compiling
|
|
|
|
|
|
|
|
Change your current directory to `src/menu`.
|
|
|
|
Use comp to compile `menu1.c`.
|
|
|
|
|
|
|
|
~~~
|
|
|
|
$ comp menu1
|
|
|
|
$ ./a.out
|
|
|
|
~~~
|
|
|
|
|
|
|
|
Then, a window appears.
|
|
|
|
Click on "Menu" on the menubar, then a menu appears.
|
|
|
|
Click on "Quit" menu, then the application quits.
|
|
|
|
|
2021-06-15 04:24:42 +02:00
|
|
|
![Screenshot of menu1](../image/menu1_screenshot.png)
|
2021-01-13 05:29:35 +01:00
|
|
|
|
2023-01-03 07:30:06 +01:00
|
|
|
## Primary and remote application instances
|
|
|
|
|
|
|
|
Let's try running the application twice.
|
|
|
|
Use `&` in your shell command line, then the application runs concurrently.
|
|
|
|
|
|
|
|
~~~
|
|
|
|
$ ./a.out &
|
|
|
|
[1] 70969
|
|
|
|
$ ./a.out
|
|
|
|
$
|
|
|
|
~~~
|
|
|
|
|
|
|
|
Then, two windows appear.
|
|
|
|
|
|
|
|
- The first `./a.out` calls the application and a primary instance is created.
|
|
|
|
It calls "startup" and "activate" handlers and shows a window.
|
|
|
|
- The second`./a.out` calls the the application again and the created instance is a remote one.
|
|
|
|
It doesn't emit "startup" signal.
|
|
|
|
And it activates the application but the "activate" signal is emitted on the primary instance.
|
|
|
|
The remote instance quits.
|
|
|
|
- The primary instance called "activate" handler.
|
|
|
|
The handler creates a new window.
|
|
|
|
It adds a menu bar to the window with `gtk_application_window_set_show_menubar` function.
|
|
|
|
|
|
|
|
Both the windows have menu bars.
|
|
|
|
And they are exactly the same.
|
|
|
|
The two windows belong to the primary instance.
|
|
|
|
|
|
|
|
If you click on the "Quit" menu, the application (the primary instance) quits.
|
|
|
|
|
|
|
|
![menu1 -- two windows](../image/menu1_two_windows.png)
|
|
|
|
|
2023-07-15 10:27:53 +02:00
|
|
|
The second execution makes a new window.
|
2023-01-03 07:30:06 +01:00
|
|
|
However, it depends on the "activate" handler.
|
2023-07-15 10:27:53 +02:00
|
|
|
If you create your window in the startup handler and the activate handler just presents the window, no new window is created at the second execution.
|
2023-01-03 07:30:06 +01:00
|
|
|
For example, tfe (text file editor) doesn't create a second window.
|
|
|
|
It just creates a new notebook page.
|
|
|
|
Because its activate handler doesn't create any window but just creates a new notebook page.
|
|
|
|
|
|
|
|
Second or more executions often happen on the desktop applications.
|
|
|
|
If you double-click the icon twice or more, the application is run multiple times.
|
|
|
|
Therefore, you need to think about your startup and activate (open) handler carefully.
|
2021-01-13 05:29:35 +01:00
|
|
|
|
2023-01-03 07:30:06 +01:00
|
|
|
Up: [README.md](../README.md), Prev: [Section 16](sec16.md), Next: [Section 18](sec18.md)
|