Ui file for menu
你可能认为构建菜单真的很麻烦。是的,程序很复杂,需要很多时间来编码。这种情况类似于构建小构建。当我们构建部件时,使用ui文件是避免这种复杂性的好方法。菜单也是如此。
菜单的ui文件有界面和菜单标签。文件以interface标签开始和结束。
<interface>
<menu id="menubar">
</menu>
</interface>
menu标签对应于GMenu对象。Id属性定义对象的名称。它将由GtkBuilder引用。
<submenu>
<attribute name="label">File</attribute>
<item>
<attribute name="label">New</attribute>
<attribute name="action">win.new</attribute>
</item>
</submenu>
item标签对应GMenu中的一个item,该项目与GMenuItem具有相同的结构。上面的项有一个label属性。它的值是"New"。这个item还有一个action属性,它的值是"win.new"。"win"是前缀,"new"是动作名称。submenu标签同时对应GMenuItem和GMenu。GMenuItem有一个指向GMenu的链接。
上面的ui文件可以描述如下。
<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标签表示与submenu的链接。同时也表达了子菜单submenu本身。这个文件比之前的ui文件更好地说明了菜单和菜单项之间的关系。但是子菜单标签简单易懂。所以,我们通常更喜欢前一种ui风格。
有关更多信息,请参阅GTK 4 API参考——PopoverMenu。
下面是示例程序menu3的屏幕截图。它位于目录src/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>
资源编译器glib-compile- resources把xml格式ui文件转换为资源resource。
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从资源中构建菜单。
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);
在将GMenuModel菜单栏插入应用程序之后,构建器实例就会被释放。如果你在插入之前这样做,不好的事情会发生——你的电脑可能会死机。
Action entry
构建动作和信号处理程序的编写也是很麻烦的工作。因此,它应该是自动化的。你可以使用GActionEntry结构和g_action_map_add_action_entries函数轻松实现它们。
GActionEntry包含动作名称、信号处理程序、参数和状态。
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];
};
例如,上一节中的操作如下:
{ "fullscreen", NULL, NULL, "false", fullscreen_changed }
{ "color", color_activated, "s", "'red'", NULL }
{ "quit", quit_activated, NULL, NULL, NULL },
- 全屏操作是有状态的,但没有参数。因此,第三个元素(参数类型)是NULL。GVariant文本格式提供“true”和“false”作为布尔值。操作的初始状态为false(第四个元素)。它没有activate处理程序,所以第二个元素是NULL。相反,它有change-state处理程序。fullscreen_changed的第五个元素是处理程序。
- 颜色动作是有状态的,并且有一个参数。参数类型为string。GVariant格式字符串提供了表示GVariant类型的字符串格式。第三个元素“s”表示GVariant字符串类型。GVariant文本格式定义了字符串用单引号或双引号括起来。字符串red是’red’或"red"。第四个元素是"‘red’“,这是一个C字符串格式,字符串是’red’。你可以写成”“red”"。第二个元素color_activated是activate处理程序。这个动作没有change-state处理程序,因此第5个元素是NULL。
- 退出操作是无状态的,没有参数。因此,第三和第四个元素为NULL。第二个成员quit_activated是activate处理程序。这个动作没有change-state处理程序,因此第5个元素是NULL。
函数g_action_map_add_action_entries负责创建GSimpleAction实例并将它们添加到GActionMap(一个应用程序或窗口)。
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);
上面的代码做了:
- 创建"color"和"quit"操作
- 连接动作和“activate”信号处理程序(color_activated和quit_activated)。
- 将操作添加到操作映射应用程序。
另一个动作也是如此。
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);
上面的代码做了:
- 构建“全屏”操作。
- 连接动作和信号处理程序fullscreen_changed
- 它的初始状态设置为false。
- 将action添加到action map win。
Example
源文件是menu3.c、menu3.ui、menu3.gresource.xml和meson.build。它们位于目录src/menu3中。下面是menu3.c和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 {
56 GApplication *app = G_APPLICATION (user_data);
57
58 g_application_quit (app);
59 }
60
61 static void
62 app_activate (GApplication *app) {
63 GtkWidget *win = gtk_application_window_new (GTK_APPLICATION (app));
64
65 const GActionEntry win_entries[] = {
66 { "save", save_activated, NULL, NULL, NULL },
67 { "saveas", saveas_activated, NULL, NULL, NULL },
68 { "close", close_activated, NULL, NULL, NULL },
69 { "fullscreen", NULL, NULL, "false", fullscreen_changed }
70 };
71 g_action_map_add_action_entries (G_ACTION_MAP (win), win_entries, G_N_ELEMENTS (win_entries), win);
72
73 gtk_application_window_set_show_menubar (GTK_APPLICATION_WINDOW (win), TRUE);
74
75 gtk_window_set_title (GTK_WINDOW (win), "menu3");
76 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
77 gtk_widget_show (win);
78 }
79
80 static void
81 app_startup (GApplication *app) {
82 GtkBuilder *builder = gtk_builder_new_from_resource ("/com/github/ToshioCP/menu3/menu3.ui");
83 GMenuModel *menubar = G_MENU_MODEL (gtk_builder_get_object (builder, "menubar"));
84
85 gtk_application_set_menubar (GTK_APPLICATION (app), menubar);
86 g_object_unref (builder);
87
88 const GActionEntry app_entries[] = {
89 { "new", new_activated, NULL, NULL, NULL },
90 { "open", open_activated, NULL, NULL, NULL },
91 { "cut", cut_activated, NULL, NULL, NULL },
92 { "copy", copy_activated, NULL, NULL, NULL },
93 { "paste", paste_activated, NULL, NULL, NULL },
94 { "selectall", selectall_activated, NULL, NULL, NULL },
95 { "quit", quit_activated, NULL, NULL, NULL }
96 };
97 g_action_map_add_action_entries (G_ACTION_MAP (app), app_entries, G_N_ELEMENTS (app_entries), app);
98 }
99
100 #define APPLICATION_ID "com.github.ToshioCP.menu3"
101
102 int
103 main (int argc, char **argv) {
104 GtkApplication *app;
105 int stat;
106
107 app = gtk_application_new (APPLICATION_ID, G_APPLICATION_DEFAULT_FLAGS);
108 g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL);
109 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
110
111 stat =g_application_run (G_APPLICATION (app), argc, argv);
112 g_object_unref (app);
113 return stat;
114 }
115
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)
动作处理程序需要遵循以下格式。
static void
handler (GSimpleAction *action_name, GVariant *parameter, gpointer user_data) { ... ... ... }
例如,你不能写“GApplication *app”而不是“gpointer user_data”。因为g_action_map_add_action_entries期望处理程序遵循上述格式。
有menu2_ui.c和menu2.ui在菜单目录下。它们是显示菜单ui文件和g_action_map_add_action_entries的其他例子。它包括一个带参数的有状态动作。
<item>
<attribute name="label">Red</attribute>
<attribute name="action">app.color</attribute>
<attribute name="target">red</attribute>
</item>
动作名称和目标像这样分开。动作属性只包括前缀和名称。你不能这样写<attribute name="action">app.color::red</attribute>
。