2021-01-25 10:35:49 +01:00
|
|
|
Up: [Readme.md](../Readme.md), Prev: [Section 18](sec18.md), Next: [Section 20](sec20.md)
|
2021-01-22 14:14:05 +01:00
|
|
|
|
2021-06-15 04:24:42 +02:00
|
|
|
# Ui file for menu and action entries
|
2021-01-22 14:14:05 +01:00
|
|
|
|
2021-06-15 04:24:42 +02:00
|
|
|
## Ui file for menu
|
2021-01-22 14:14:05 +01:00
|
|
|
|
2021-06-15 04:24:42 +02:00
|
|
|
You might have thought that building menus is really bothersome.
|
|
|
|
Yes, the program was complicated and it needs lots of time to code it.
|
|
|
|
The situation is similar to building widgets.
|
|
|
|
When we built widgets, using ui file was a good way to avoid such complicated coding.
|
|
|
|
The same goes for menus.
|
2021-01-22 14:14:05 +01:00
|
|
|
|
2021-06-15 04:24:42 +02:00
|
|
|
The ui file for menus has interface, menu tags.
|
|
|
|
The file starts and ends with interface tag.
|
2021-01-22 14:14:05 +01:00
|
|
|
|
2021-02-17 15:59:50 +01:00
|
|
|
~~~xml
|
2021-06-15 04:24:42 +02:00
|
|
|
<interface>
|
|
|
|
<menu id="menubar">
|
|
|
|
</menu>
|
|
|
|
</interface>
|
2021-02-17 15:59:50 +01:00
|
|
|
~~~
|
2021-01-22 14:14:05 +01:00
|
|
|
|
2021-06-15 04:24:42 +02:00
|
|
|
`menu` tag corresponds to GMenu object.
|
|
|
|
`id` attribute defines the name of the object.
|
|
|
|
It will be referred by GtkBuilder.
|
2021-01-22 14:14:05 +01:00
|
|
|
|
2021-02-17 15:59:50 +01:00
|
|
|
~~~xml
|
2021-06-15 04:24:42 +02:00
|
|
|
<submenu>
|
|
|
|
<attribute name="label">File</attribute>
|
|
|
|
<item>
|
|
|
|
<attribute name="label">New</attribute>
|
|
|
|
<attribute name="action">win.new</attribute>
|
|
|
|
</item>
|
|
|
|
</submenu>
|
2021-02-17 15:59:50 +01:00
|
|
|
~~~
|
|
|
|
|
2021-06-15 04:24:42 +02:00
|
|
|
`item` tag corresponds to item in 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.
|
2021-02-17 15:59:50 +01:00
|
|
|
|
2021-06-15 04:24:42 +02:00
|
|
|
The ui file above can be described as follows.
|
2021-02-17 15:59:50 +01:00
|
|
|
|
|
|
|
~~~xml
|
2021-06-15 04:24:42 +02:00
|
|
|
<item>
|
|
|
|
<attribute name="label">File</attribute>
|
|
|
|
<link name="submenu">
|
|
|
|
<item>
|
|
|
|
<attribute name="label">New</attribute>
|
|
|
|
<attribute name="action">win.new</attribute>
|
|
|
|
</item>
|
|
|
|
</link>
|
|
|
|
</item>
|
2021-02-17 15:59:50 +01:00
|
|
|
~~~
|
|
|
|
|
2021-06-15 04:24:42 +02:00
|
|
|
`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 file style.
|
2021-02-17 15:59:50 +01:00
|
|
|
|
2021-06-15 04:24:42 +02:00
|
|
|
The following is a screenshot of the sample program in this section.
|
|
|
|
Its name is `menu3`.
|
2021-02-17 15:59:50 +01:00
|
|
|
|
2021-06-15 04:24:42 +02:00
|
|
|
![menu3](../image/menu3.png)
|
2021-02-17 15:59:50 +01:00
|
|
|
|
2021-06-15 04:24:42 +02:00
|
|
|
The following is the ui file of the menu in `menu3`.
|
2021-02-17 15:59:50 +01:00
|
|
|
|
|
|
|
~~~xml
|
2021-06-15 04:24:42 +02:00
|
|
|
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">win.new</attribute>
|
|
|
|
10 </item>
|
|
|
|
11 <item>
|
|
|
|
12 <attribute name="label">Open</attribute>
|
|
|
|
13 <attribute name="action">win.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">win.cut</attribute>
|
|
|
|
45 </item>
|
|
|
|
46 <item>
|
|
|
|
47 <attribute name="label">Copy</attribute>
|
|
|
|
48 <attribute name="action">win.copy</attribute>
|
|
|
|
49 </item>
|
|
|
|
50 <item>
|
|
|
|
51 <attribute name="label">Paste</attribute>
|
|
|
|
52 <attribute name="action">win.paste</attribute>
|
|
|
|
53 </item>
|
|
|
|
54 </section>
|
|
|
|
55 <section>
|
|
|
|
56 <item>
|
|
|
|
57 <attribute name="label">Select All</attribute>
|
|
|
|
58 <attribute name="action">win.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 below.
|
2021-02-17 15:59:50 +01:00
|
|
|
|
2021-06-15 04:24:42 +02:00
|
|
|
~~~xml
|
|
|
|
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>
|
2021-02-17 15:59:50 +01:00
|
|
|
~~~
|
|
|
|
|
2021-06-15 04:24:42 +02:00
|
|
|
GtkBuilder builds menus from the resource.
|
2021-02-17 15:59:50 +01:00
|
|
|
|
|
|
|
~~~C
|
2021-06-15 04:24:42 +02:00
|
|
|
GtkBuilder *builder = gtk_builder_new_from_resource ("/com/github/ToshioCP/menu3/menu3.ui");
|
|
|
|
GMenuModel *menubar = G_MENU_MODEL (gtk_builder_get_object (builder, "menubar"));
|
2021-02-17 15:59:50 +01:00
|
|
|
|
2021-06-15 04:24:42 +02:00
|
|
|
gtk_application_set_menubar (GTK_APPLICATION (app), menubar);
|
|
|
|
g_object_unref (builder);
|
2021-02-17 15:59:50 +01:00
|
|
|
~~~
|
|
|
|
|
2021-06-15 04:24:42 +02:00
|
|
|
It is important that `builder` is unreferred after the GMenuModel `menubar` is inserted to the application.
|
|
|
|
If you do it before setting, bad thing will happen -- your computer might freeze.
|
2021-02-17 15:59:50 +01:00
|
|
|
|
2021-06-15 04:24:42 +02:00
|
|
|
## Action entry
|
2021-02-17 15:59:50 +01:00
|
|
|
|
2021-06-15 04:24:42 +02:00
|
|
|
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.
|
2021-02-17 15:59:50 +01:00
|
|
|
|
2021-06-15 04:24:42 +02:00
|
|
|
GActionEntry contains action name, signal handlers, parameter and state.
|
2021-02-17 15:59:50 +01:00
|
|
|
|
|
|
|
~~~C
|
2021-06-15 04:24:42 +02:00
|
|
|
typedef struct _GActionEntry GActionEntry;
|
|
|
|
|
|
|
|
struct _GActionEntry
|
|
|
|
{
|
|
|
|
/* action name */
|
|
|
|
const gchar *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 gchar *parameter_type;
|
|
|
|
/* initial state given in GVariant text format */
|
|
|
|
const gchar *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:
|
2021-02-17 15:59:50 +01:00
|
|
|
|
|
|
|
~~~C
|
2021-06-15 04:24:42 +02:00
|
|
|
{ "fullscreen", NULL, NULL, "false", fullscreen_changed }
|
|
|
|
{ "color", color_activated, "s", "red", NULL }
|
|
|
|
{ "quit", quit_activated, NULL, NULL, NULL },
|
2021-02-17 15:59:50 +01:00
|
|
|
~~~
|
|
|
|
|
2021-06-15 04:24:42 +02:00
|
|
|
And `g_action_map_add_action_entries` does all the process instead of the functions you have needed.
|
2021-02-17 15:59:50 +01:00
|
|
|
|
|
|
|
~~~C
|
2021-06-15 04:24:42 +02:00
|
|
|
const GActionEntry app_entries[] = {
|
|
|
|
{ "quit", quit_activated, NULL, NULL, NULL }
|
|
|
|
};
|
|
|
|
g_action_map_add_action_entries (G_ACTION_MAP (app), app_entries,
|
|
|
|
G_N_ELEMENTS (app_entries), app);
|
2021-02-17 15:59:50 +01:00
|
|
|
~~~
|
|
|
|
|
2021-06-15 04:24:42 +02:00
|
|
|
The code above does:
|
2021-02-17 15:59:50 +01:00
|
|
|
|
2021-06-15 04:24:42 +02:00
|
|
|
- Builds the "quit" action
|
|
|
|
- Connects the action and the "activate" signal handler `quit_activate`
|
|
|
|
- Adds the action to the action map `app`.
|
2021-02-17 15:59:50 +01:00
|
|
|
|
2021-06-15 04:24:42 +02:00
|
|
|
The same goes for the other actions.
|
2021-02-17 15:59:50 +01:00
|
|
|
|
|
|
|
~~~C
|
2021-06-15 04:24:42 +02:00
|
|
|
const GActionEntry win_entries[] = {
|
|
|
|
{ "fullscreen", NULL, NULL, "false", fullscreen_changed },
|
|
|
|
{ "color", color_activated, "s", "red", NULL }
|
|
|
|
};
|
|
|
|
g_action_map_add_action_entries (G_ACTION_MAP (win), win_entries,
|
|
|
|
G_N_ELEMENTS (win_entries), win);
|
2021-02-17 15:59:50 +01:00
|
|
|
~~~
|
2021-06-15 04:24:42 +02:00
|
|
|
The code above does:
|
2021-02-17 15:59:50 +01:00
|
|
|
|
2021-06-15 04:24:42 +02:00
|
|
|
- Builds a "fullscreen" action and "color" action.
|
|
|
|
- Connects the "fullscreen" action and the "change-state" signal handler `fullscreen_changed`
|
|
|
|
- Its initial state is set to FALSE.
|
|
|
|
- Connects the "color" action and the "activate" signal handler `color_activate`
|
|
|
|
- Its parameter type is string and the initial value is "red".
|
|
|
|
- Adds the actions to the action map `win`.
|
2021-02-17 15:59:50 +01:00
|
|
|
|
2021-06-15 04:24:42 +02:00
|
|
|
## Example code
|
2021-02-17 15:59:50 +01:00
|
|
|
|
2021-06-15 04:24:42 +02:00
|
|
|
The C source code of `menu3` and `meson.build` is as follows.
|
2021-02-17 15:59:50 +01:00
|
|
|
|
|
|
|
~~~C
|
2021-06-15 04:24:42 +02:00
|
|
|
1 #include <gtk/gtk.h>
|
|
|
|
2
|
|
|
|
3 static void
|
|
|
|
4 new_activated (GSimpleAction *action, GVariant *parameter, gpointer win) {
|
|
|
|
5 }
|
|
|
|
6
|
|
|
|
7 static void
|
|
|
|
8 open_activated (GSimpleAction *action, GVariant *parameter, gpointer win) {
|
|
|
|
9 }
|
|
|
|
10
|
|
|
|
11 static void
|
|
|
|
12 save_activated (GSimpleAction *action, GVariant *parameter, gpointer win) {
|
|
|
|
13 }
|
|
|
|
14
|
|
|
|
15 static void
|
|
|
|
16 saveas_activated (GSimpleAction *action, GVariant *parameter, gpointer win) {
|
|
|
|
17 }
|
|
|
|
18
|
|
|
|
19 static void
|
|
|
|
20 close_activated (GSimpleAction *action, GVariant *parameter, gpointer win) {
|
|
|
|
21 }
|
|
|
|
22
|
|
|
|
23 static void
|
|
|
|
24 cut_activated (GSimpleAction *action, GVariant *parameter, gpointer win) {
|
|
|
|
25 }
|
|
|
|
26
|
|
|
|
27 static void
|
|
|
|
28 copy_activated (GSimpleAction *action, GVariant *parameter, gpointer win) {
|
|
|
|
29 }
|
|
|
|
30
|
|
|
|
31 static void
|
|
|
|
32 paste_activated (GSimpleAction *action, GVariant *parameter, gpointer win) {
|
|
|
|
33 }
|
|
|
|
34
|
|
|
|
35 static void
|
|
|
|
36 selectall_activated (GSimpleAction *action, GVariant *parameter, gpointer win) {
|
|
|
|
37 }
|
|
|
|
38
|
|
|
|
39 static void
|
|
|
|
40 fullscreen_changed (GSimpleAction *action, GVariant *state, gpointer win) {
|
|
|
|
41 if (g_variant_get_boolean (state))
|
|
|
|
42 gtk_window_maximize (GTK_WINDOW (win));
|
|
|
|
43 else
|
|
|
|
44 gtk_window_unmaximize (GTK_WINDOW (win));
|
|
|
|
45 g_simple_action_set_state (action, state);
|
|
|
|
46 }
|
|
|
|
47
|
|
|
|
48 static void
|
|
|
|
49 quit_activated (GSimpleAction *action, GVariant *parameter, gpointer app)
|
|
|
|
50 {
|
|
|
|
51 g_application_quit (G_APPLICATION(app));
|
|
|
|
52 }
|
|
|
|
53
|
|
|
|
54 static void
|
|
|
|
55 on_activate (GApplication *app, gpointer user_data) {
|
|
|
|
56 GtkWidget *win = gtk_application_window_new (GTK_APPLICATION (app));
|
|
|
|
57
|
|
|
|
58 const GActionEntry win_entries[] = {
|
|
|
|
59 { "new", new_activated, NULL, NULL, NULL },
|
|
|
|
60 { "open", open_activated, NULL, NULL, NULL },
|
|
|
|
61 { "save", save_activated, NULL, NULL, NULL },
|
|
|
|
62 { "saveas", saveas_activated, NULL, NULL, NULL },
|
|
|
|
63 { "close", close_activated, NULL, NULL, NULL },
|
|
|
|
64 { "cut", cut_activated, NULL, NULL, NULL },
|
|
|
|
65 { "copy", copy_activated, NULL, NULL, NULL },
|
|
|
|
66 { "paste", paste_activated, NULL, NULL, NULL },
|
|
|
|
67 { "selectall", selectall_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_widget_show (win);
|
|
|
|
77 }
|
|
|
|
78
|
|
|
|
79 static void
|
|
|
|
80 on_startup (GApplication *app, gpointer user_data) {
|
|
|
|
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 { "quit", quit_activated, NULL, NULL, NULL }
|
|
|
|
89 };
|
|
|
|
90 g_action_map_add_action_entries (G_ACTION_MAP (app), app_entries, G_N_ELEMENTS (app_entries), app);
|
|
|
|
91 }
|
|
|
|
92
|
|
|
|
93 int
|
|
|
|
94 main (int argc, char **argv) {
|
|
|
|
95 GtkApplication *app;
|
|
|
|
96 int stat;
|
|
|
|
97
|
|
|
|
98 app = gtk_application_new ("com.github.ToshioCP.menu3", G_APPLICATION_FLAGS_NONE);
|
|
|
|
99 g_signal_connect (app, "startup", G_CALLBACK (on_startup), NULL);
|
|
|
|
100 g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
|
|
|
|
101
|
|
|
|
102 stat =g_application_run (G_APPLICATION (app), argc, argv);
|
|
|
|
103 g_object_unref (app);
|
|
|
|
104 return stat;
|
|
|
|
105 }
|
|
|
|
106
|
|
|
|
~~~
|
|
|
|
|
|
|
|
meson.build
|
2021-02-17 15:59:50 +01:00
|
|
|
|
|
|
|
~~~meson
|
2021-06-15 04:24:42 +02:00
|
|
|
1 project('menu3', 'c')
|
2021-02-17 15:59:50 +01:00
|
|
|
2
|
|
|
|
3 gtkdep = dependency('gtk4')
|
|
|
|
4
|
|
|
|
5 gnome=import('gnome')
|
2021-06-15 04:24:42 +02:00
|
|
|
6 resources = gnome.compile_resources('resources','menu3.gresource.xml')
|
|
|
|
7
|
|
|
|
8 sourcefiles=files('menu3.c')
|
|
|
|
9
|
|
|
|
10 executable('menu3', sourcefiles, resources, dependencies: gtkdep)
|
2021-02-17 15:59:50 +01:00
|
|
|
~~~
|
|
|
|
|
2021-01-22 14:14:05 +01:00
|
|
|
|
2021-01-25 10:35:49 +01:00
|
|
|
Up: [Readme.md](../Readme.md), Prev: [Section 18](sec18.md), Next: [Section 20](sec20.md)
|