Up: README.md, Prev: Section 18, Next: Section 20
You may have thought that building menus was really bothersome. Yes, the program was complicated and it needs lots of time to code them. The situation is similar to building widgets. When we built widgets, using ui file was a good way to avoid such complication. The same goes for menus.
The ui file for menus has interface and menu tags. The file starts and ends with interface tags.
<interface>
<menu id="menubar">
</menu>
</interface>
menu
tag corresponds to GMenu object.
id
attribute defines the name of the object.
It will be referred by GtkBuilder.
<submenu>
<attribute name="label">File</attribute>
<item>
<attribute name="label">New</attribute>
<attribute name="action">win.new</attribute>
</item>
</submenu>
item
tag corresponds to an item in the GMenu which has the same structure as GMenuItem.
The item above has a label attribute.
Its value is "New".
The item also has an action attribute and its value is "win.new".
"win" is a prefix and "new" is an action name.
submenu
tag corresponds to both GMenuItem and GMenu.
The GMenuItem has a link to GMenu.
The ui file above can be described as follows.
<item>
<attribute name="label">File</attribute>
<link name="submenu">
<item>
<attribute name="label">New</attribute>
<attribute name="action">win.new</attribute>
</item>
</link>
</item>
link
tag expresses the link to submenu.
And at the same time it also expresses the submenu itself.
This file illustrates the relationship between the menus and items better than the prior ui file.
But submenu
tag is simple and easy to understand.
So, we usually prefer the former ui style.
For further information, see GTK 4 API reference -- PopoverMenu.
The following is a screenshot of the sample program menu3
.
It is located in the directory src/menu3.
The following is the ui file for menu3
.
1 <?xml version="1.0" encoding="UTF-8"?>
2 <interface>
3 <menu id="menubar">
4 <submenu>
5 <attribute name="label">File</attribute>
6 <section>
7 <item>
8 <attribute name="label">New</attribute>
9 <attribute name="action">app.new</attribute>
10 </item>
11 <item>
12 <attribute name="label">Open</attribute>
13 <attribute name="action">app.open</attribute>
14 </item>
15 </section>
16 <section>
17 <item>
18 <attribute name="label">Save</attribute>
19 <attribute name="action">win.save</attribute>
20 </item>
21 <item>
22 <attribute name="label">Save As…</attribute>
23 <attribute name="action">win.saveas</attribute>
24 </item>
25 </section>
26 <section>
27 <item>
28 <attribute name="label">Close</attribute>
29 <attribute name="action">win.close</attribute>
30 </item>
31 </section>
32 <section>
33 <item>
34 <attribute name="label">Quit</attribute>
35 <attribute name="action">app.quit</attribute>
36 </item>
37 </section>
38 </submenu>
39 <submenu>
40 <attribute name="label">Edit</attribute>
41 <section>
42 <item>
43 <attribute name="label">Cut</attribute>
44 <attribute name="action">app.cut</attribute>
45 </item>
46 <item>
47 <attribute name="label">Copy</attribute>
48 <attribute name="action">app.copy</attribute>
49 </item>
50 <item>
51 <attribute name="label">Paste</attribute>
52 <attribute name="action">app.paste</attribute>
53 </item>
54 </section>
55 <section>
56 <item>
57 <attribute name="label">Select All</attribute>
58 <attribute name="action">app.selectall</attribute>
59 </item>
60 </section>
61 </submenu>
62 <submenu>
63 <attribute name="label">View</attribute>
64 <section>
65 <item>
66 <attribute name="label">Full Screen</attribute>
67 <attribute name="action">win.fullscreen</attribute>
68 </item>
69 </section>
70 </submenu>
71 </menu>
72 </interface>
The ui file is converted to the resource by the resource compiler glib-compile-resouces
with xml file.
1 <?xml version="1.0" encoding="UTF-8"?>
2 <gresources>
3 <gresource prefix="/com/github/ToshioCP/menu3">
4 <file>menu3.ui</file>
5 </gresource>
6 </gresources>
GtkBuilder builds menus from the resource.
GtkBuilder *builder = gtk_builder_new_from_resource ("/com/github/ToshioCP/menu3/menu3.ui");
GMenuModel *menubar = G_MENU_MODEL (gtk_builder_get_object (builder, "menubar"));
gtk_application_set_menubar (GTK_APPLICATION (app), menubar);
g_object_unref (builder);
The builder instance is freed after the GMenuModel menubar
is inserted to the application.
If you do it before the insertion, bad thing will happen -- your computer might freeze.
It is because you don't own the menubar
instance.
The function gtk_builder_get_object
just returns the pointer to menubar
and doesn't increase the reference count of menubar
.
So, if you released bulder
before gtk_application_set_menubar
, builder
would be destroyed and menubar
as well.
The coding for building actions and signal handlers is bothersome work as well.
Therefore, it should be automated.
You can implement them easily with GActionEntry structure and g_action_map_add_action_entries
function.
GActionEntry contains action name, signal handlers, parameter and state.
typedef struct _GActionEntry GActionEntry;
struct _GActionEntry
{
/* action name */
const char *name;
/* activate handler */
void (* activate) (GSimpleAction *action, GVariant *parameter, gpointer user_data);
/* the type of the parameter given as a single GVariant type string */
const char *parameter_type;
/* initial state given in GVariant text format */
const char *state;
/* change-state handler */
void (* change_state) (GSimpleAction *action, GVariant *value, gpointer user_data);
/*< private >*/
gsize padding[3];
};
For example, the actions in the previous section are:
{ "fullscreen", NULL, NULL, "false", fullscreen_changed }
{ "color", color_activated, "s", "'red'", NULL }
{ "quit", quit_activated, NULL, NULL, NULL },
- Fullscreen action is stateful, but doesn't have parameters.
So, the third element (parameter type) is NULL.
GVariant text format provides "true" and "false" as boolean GVariant values.
The initial state of the action is false (the fourth element).
It doesn't have activate handler, so the second element is NULL.
Instead, it has change-state handler.
The fifth element
fullscreen_changed
is the handler. - Color action is stateful and has a parameter.
The parameter type is string.
GVariant format strings provides string formats to represent GVariant types.
The third element "s" means GVariant string type.
GVariant text format defines that strings are surrounded by single or double quotes.
So, the string red is 'red' or "red".
The fourth element is
"'red'"
, which is a C string format and the string is 'red'. You can write"\"red\""
instead. The second elementcolor_activated
is the activate handler. The action doesn't have change-state handler, so the fifth element is NULL. - Quit action is non-stateful and has no parameter.
So, the third and fourth elements are NULL.
The second element
quit_activated
is the activate handler. The action doesn't have change-state handler, so the fifth element is NULL.
The function g_action_map_add_action_entries
does everything
to create GSimpleAction instances and add them to a GActionMap (an application or window).
const GActionEntry app_entries[] = {
{ "color", color_activated, "s", "'red'", NULL },
{ "quit", quit_activated, NULL, NULL, NULL }
};
g_action_map_add_action_entries (G_ACTION_MAP (app), app_entries,
G_N_ELEMENTS (app_entries), app);
The code above does:
- Builds the "color" and "quit" actions
- Connects the action and the "activate" signal handlers (
color_activated
andquit_activated
). - Adds the actions to the action map
app
.
The same goes for the other action.
const GActionEntry win_entries[] = {
{ "fullscreen", NULL, NULL, "false", fullscreen_changed }
};
g_action_map_add_action_entries (G_ACTION_MAP (win), win_entries,
G_N_ELEMENTS (win_entries), win);
The code above does:
- Builds the "fullscreen" action.
- Connects the action and the signal handler
fullscreen_changed
- Its initial state is set to false.
- Adds the action to the action map
win
.
Source files are menu3.c
, menu3.ui
, menu3.gresource.xml
and meson.build
.
They are in the directory src/menu3.
The following are menu3.c
and meson.build
.
1 #include <gtk/gtk.h>
2
3 static void
4 new_activated (GSimpleAction *action, GVariant *parameter, gpointer user_data) {
5 }
6
7 static void
8 open_activated (GSimpleAction *action, GVariant *parameter, gpointer user_data) {
9 }
10
11 static void
12 save_activated (GSimpleAction *action, GVariant *parameter, gpointer user_data) {
13 }
14
15 static void
16 saveas_activated (GSimpleAction *action, GVariant *parameter, gpointer user_data) {
17 }
18
19 static void
20 close_activated (GSimpleAction *action, GVariant *parameter, gpointer user_data) {
21 GtkWindow *win = GTK_WINDOW (user_data);
22
23 gtk_window_destroy (win);
24 }
25
26 static void
27 cut_activated (GSimpleAction *action, GVariant *parameter, gpointer user_data) {
28 }
29
30 static void
31 copy_activated (GSimpleAction *action, GVariant *parameter, gpointer user_data) {
32 }
33
34 static void
35 paste_activated (GSimpleAction *action, GVariant *parameter, gpointer user_data) {
36 }
37
38 static void
39 selectall_activated (GSimpleAction *action, GVariant *parameter, gpointer user_data) {
40 }
41
42 static void
43 fullscreen_changed (GSimpleAction *action, GVariant *state, gpointer user_data) {
44 GtkWindow *win = GTK_WINDOW (user_data);
45
46 if (g_variant_get_boolean (state))
47 gtk_window_maximize (win);
48 else
49 gtk_window_unmaximize (win);
50 g_simple_action_set_state (action, state);
51 }
52
53 static void
54 quit_activated (GSimpleAction *action, GVariant *parameter, gpointer user_data) {
55 GApplication *app = G_APPLICATION (user_data);
56
57 g_application_quit (app);
58 }
59
60 static void
61 app_activate (GApplication *app) {
62 GtkWidget *win = gtk_application_window_new (GTK_APPLICATION (app));
63
64 const GActionEntry win_entries[] = {
65 { "save", save_activated, NULL, NULL, NULL },
66 { "saveas", saveas_activated, NULL, NULL, NULL },
67 { "close", close_activated, NULL, NULL, NULL },
68 { "fullscreen", NULL, NULL, "false", fullscreen_changed }
69 };
70 g_action_map_add_action_entries (G_ACTION_MAP (win), win_entries, G_N_ELEMENTS (win_entries), win);
71
72 gtk_application_window_set_show_menubar (GTK_APPLICATION_WINDOW (win), TRUE);
73
74 gtk_window_set_title (GTK_WINDOW (win), "menu3");
75 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
76 gtk_window_present (GTK_WINDOW (win));
77 }
78
79 static void
80 app_startup (GApplication *app) {
81 GtkBuilder *builder = gtk_builder_new_from_resource ("/com/github/ToshioCP/menu3/menu3.ui");
82 GMenuModel *menubar = G_MENU_MODEL (gtk_builder_get_object (builder, "menubar"));
83
84 gtk_application_set_menubar (GTK_APPLICATION (app), menubar);
85 g_object_unref (builder);
86
87 const GActionEntry app_entries[] = {
88 { "new", new_activated, NULL, NULL, NULL },
89 { "open", open_activated, NULL, NULL, NULL },
90 { "cut", cut_activated, NULL, NULL, NULL },
91 { "copy", copy_activated, NULL, NULL, NULL },
92 { "paste", paste_activated, NULL, NULL, NULL },
93 { "selectall", selectall_activated, NULL, NULL, NULL },
94 { "quit", quit_activated, NULL, NULL, NULL }
95 };
96 g_action_map_add_action_entries (G_ACTION_MAP (app), app_entries, G_N_ELEMENTS (app_entries), app);
97 }
98
99 #define APPLICATION_ID "com.github.ToshioCP.menu3"
100
101 int
102 main (int argc, char **argv) {
103 GtkApplication *app;
104 int stat;
105
106 app = gtk_application_new (APPLICATION_ID, G_APPLICATION_DEFAULT_FLAGS);
107 g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL);
108 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
109
110 stat =g_application_run (G_APPLICATION (app), argc, argv);
111 g_object_unref (app);
112 return stat;
113 }
114
meson.build
1 project('menu3', 'c')
2
3 gtkdep = dependency('gtk4')
4
5 gnome=import('gnome')
6 resources = gnome.compile_resources('resources','menu3.gresource.xml')
7
8 sourcefiles=files('menu3.c')
9
10 executable('menu3', sourcefiles, resources, dependencies: gtkdep)
Action handlers need to follow the following format.
static void
handler (GSimpleAction *action_name, GVariant *parameter, gpointer user_data) { ... ... ... }
You can't write, for example, "GApplication *app" instead of "gpointer user_data".
Because g_action_map_add_action_entries
expects that handlers follow the format above.
There are menu2_ui.c
and menu2.ui
under the menu
directory.
They are other examples to show menu ui file and g_action_map_add_action_entries
.
It includes a stateful action with parameters.
<item>
<attribute name="label">Red</attribute>
<attribute name="action">app.color</attribute>
<attribute name="target">red</attribute>
</item>
Action name and target are separated like this.
Action attribute includes prefix and name only.
You can't write like <attribute name="action">app.color::red</attribute>
.
Up: README.md, Prev: Section 18, Next: Section 20