前一节中的tfe程序不太好,因为很多东西都塞到了tfeapplication .c中。以及tfeapplication .c中的许多静态变量。文件tfeapplication.c应该被分成几个文件。
- tfeapplication.c只有与应用程序相关的代码。
- 一个文件用于主窗口
- 一个文件用于首选项对话框的文件
- 一个用于警告对话框的文件
首选项对话框由ui文件定义。它有GtkBox, GtkLabel和GtkFontButton。这样的小部件可以定义为复合小部件。复合组件是:
- 构件的
子对象
(不是子构件)。例如,首选项组合构件是GtkDialog的子对象。 - 可以从模板XML构建复合小构件。构件是用模板标签定义的,而不是对象标签。
下一小节将展示如何构建首选项对话框。
Preference dialog
首先,编写一个模板XML文件。
1 <?xml version="1.0" encoding="UTF-8"?>
2 <interface>
3 <template class="TfePref" parent="GtkDialog">
4 <property name="title">Preferences</property>
5 <property name="resizable">FALSE</property>
6 <property name="modal">TRUE</property>
7 <child internal-child="content_area">
8 <object class="GtkBox" id="content_area">
9 <child>
10 <object class="GtkBox" id="pref_boxh">
11 <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
12 <property name="spacing">12</property>
13 <property name="margin-start">12</property>
14 <property name="margin-end">12</property>
15 <property name="margin-top">12</property>
16 <property name="margin-bottom">12</property>
17 <child>
18 <object class="GtkLabel" id="fontlabel">
19 <property name="label">Font:</property>
20 <property name="xalign">1</property>
21 </object>
22 </child>
23 <child>
24 <object class="GtkFontButton" id="fontbtn">
25 </object>
26 </child>
27 </object>
28 </child>
29 </object>
30 </child>
31 </template>
32 </interface>
33
模板标记指定一个复合小构件。class值是对象的名称。它是TfePref。parent值指定复合构件的父类。因此。TfePref是GtkDialog的一个子类。parent属性是可选的。但建议指定它。其他行和以前一样。
TfePref类的定义类似于TfeTextView。有两个文件tfeprefer.h和tfeprefer.c。
文件tfepre.h定义了类型并声明了公共函数。这些定义是公开的,对任何C文件都是开放的。
1 #ifndef __TFE_PREF_H__
2 #define __TFE_PREF_H__
3
4 #include <gtk/gtk.h>
5
6 #define TFE_TYPE_PREF tfe_pref_get_type ()
7 G_DECLARE_FINAL_TYPE (TfePref, tfe_pref, TFE, PREF, GtkDialog)
8
9 GtkWidget *
10 tfe_pref_new (void);
11
12 #endif /* __TFE_PREF_H__ */
13
- 6:定义一个类型TFE_TYPE_PREF,它是一个宏,被tfe_pref_get_type()取代。
- 7:宏G_DECLAER_FINAL_TYPE扩展为:
- 函数tfe_pref_get_type()被声明。
- TfePrep类型定义为typedef struct _TfePrep TfePrep。
- TfePrepClass类型定义为struct {GtkDialogClass *parent;}的typedef类型。
- 定义了两个函数TFE_PREF()和TFE_IS_PREF()。
- 9-10: tfe_pref_new创建一个新的TfePref对象。
文件tfepre .c包含:
- struct _TfePrep结构
- G_DEFINE_TYPE宏
- Initialize初始化和dispose释放函数
- 公共函数
1 #include <gtk/gtk.h>
2 #include "tfepref.h"
3
4 struct _TfePref
5 {
6 GtkDialog parent;
7 GSettings *settings;
8 GtkFontButton *fontbtn;
9 };
10
11 G_DEFINE_TYPE (TfePref, tfe_pref, GTK_TYPE_DIALOG);
12
13 static void
14 tfe_pref_dispose (GObject *gobject) {
15 TfePref *pref = TFE_PREF (gobject);
16
17 g_clear_object (&pref->settings);
18 G_OBJECT_CLASS (tfe_pref_parent_class)->dispose (gobject);
19 }
20
21 static void
22 tfe_pref_init (TfePref *pref) {
23 gtk_widget_init_template (GTK_WIDGET (pref));
24 pref->settings = g_settings_new ("com.github.ToshioCP.tfe");
25 g_settings_bind (pref->settings, "font", pref->fontbtn, "font", G_SETTINGS_BIND_DEFAULT);
26 }
27
28 static void
29 tfe_pref_class_init (TfePrefClass *class) {
30 GObjectClass *object_class = G_OBJECT_CLASS (class);
31
32 object_class->dispose = tfe_pref_dispose;
33 gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class), "/com/github/ToshioCP/tfe/tfepref.ui");
34 gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfePref, fontbtn);
35 }
36
37 GtkWidget *
38 tfe_pref_new (void) {
39 return GTK_WIDGET (g_object_new (TFE_TYPE_PREF, NULL));
40 }
41
- 4-9:定义结构体struct _TfePref。每个TfePref实例都有自己的结构数据。该结构使用到:
- 一个GSettings实例
- 一个FontButton实例
- 11: G_DEFINE_TYPE宏。宏扩展为:
- 类初始化函数tfe_pref_class_init的声明
- 实例初始化函数tfe_pref_init的声明
- 静态变量tfe_pref_parent_class指向父类(GtkDialogClass)结构。
- tfe_pref_get_type()函数的定义
- 13-19: tfe_pref_dispose函数。它在销毁过程中被调用,并释放对其他对象的所有引用。有关销毁过程的进一步信息,请参阅第11节。
- 17: g_clear_object通常用于dispose处理程序。g_clear_object (&pref->gsettings)做:
- g_object_unref(pref->gsettings)
- pref->settings = NULL
- 21-26:实例初始化函数。
- 23:函数gtk_widget_init_template创建并初始化子构件。这些构件是基于在gtk_widget_class_set_template_from_resource函数中创建的模板创建的。
- 24:创建GSettings实例并将其指针分配到pref->settings中。实例引用一个id为com.github.ToshioCP.tfe的GSetting。
- 25:绑定GSettings数据字体和pref->fontbtn (GtkFontButton)的字体属性。元素pref->fontbtn指向GtkFontButton,它是ui文件中fontbtn的实例。该关系是由gtk_widget_class_bind_template_child函数建立的。
- 28-35:类初始化函数。
- 32:设置dispose处理程序。
- 33: gtk_widget_class_set_template_from_resource函数将XML文件(tfepref.ui)中的描述与小部件关联起来。目前还没有创建实例。它只是使类识别对象的结构。这就是为什么XML文件中的顶级标签不是
<object>
,而是<template>
。实例稍后将在gtk_widget_init_template函数中创建。 - 34: gtk_widget_class_bind_template_child宏绑定结构成员(struct _TfePref中的fontbtn)和XML文件中的id fontbtn。两个名称必须相同。这个绑定是在成员和模板(不是实例)之间。
- 37-40:函数tfe_pref_new创建一个TfePref实例。
现在,使用这个对话框非常简单。调用者只是创建这个对象并显示它。
TfePref *pref;
pref = tfe_pref_new ();
gtk_window_set_transient_for (GTK_WINDOW (pref), win); /* win is the main window */
gtk_window_present (GTK_WINDOW (pref));
当用户单击关闭按钮时,该实例将自动销毁。这是所有。如果您想再次显示对话框,只需创建并显示它。
Alert dialog
它几乎与偏好对话框相同。
它的ui文件是:
1 <?xml version="1.0" encoding="UTF-8"?>
2 <interface>
3 <template class="TfeAlert" parent="GtkDialog">
4 <property name="title">Are you sure?</property>
5 <property name="resizable">FALSE</property>
6 <property name="modal">TRUE</property>
7 <child internal-child="content_area">
8 <object class="GtkBox">
9 <child>
10 <object class="GtkBox">
11 <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
12 <property name="spacing">12</property>
13 <property name="margin-start">12</property>
14 <property name="margin-end">12</property>
15 <property name="margin-top">12</property>
16 <property name="margin-bottom">12</property>
17 <child>
18 <object class="GtkImage">
19 <property name="icon-name">dialog-warning</property>
20 <property name="icon-size">GTK_ICON_SIZE_LARGE</property>
21 </object>
22 </child>
23 <child>
24 <object class="GtkLabel" id="lb_alert">
25 </object>
26 </child>
27 </object>
28 </child>
29 </object>
30 </child>
31 <child type="action">
32 <object class="GtkButton" id="btn_cancel">
33 <property name="label">Cancel</property>
34 </object>
35 </child>
36 <child type="action">
37 <object class="GtkButton" id="btn_accept">
38 <property name="label">Close</property>
39 </object>
40 </child>
41 <action-widgets>
42 <action-widget response="cancel" default="true">btn_cancel</action-widget>
43 <action-widget response="accept">btn_accept</action-widget>
44 </action-widgets>
45 </template>
46 </interface>
47
头文件是:
1 #ifndef __TFE_ALERT_H__
2 #定义__TFE_ALERT_H__
3.
4 #包含<gtk/gtk.h>
5
6 #define TFE_TYPE_ALERT ()
7 G_DECLARE_FINAL_TYPE (tfe_alert, tfe_alert, TFE, ALERT, GtkDialog)
8
9无效
10 tfe_alert_set_message (TfeAlert *alert, const char *message);
11
12个空
tfe_alert_set_button_label (TfeAlert *alert, const char *label);
14
15 GtkWidget *
16 tfe_alert_new (void);
17
18 #endif /* __TFE_ALERT_H__ */
19
有三个公共功能。函数tfe_alert_set_message和tfe_alert_set_button_label设置警告对话框的标签和按钮名称。例如,如果你想在用户试图关闭页面时显示一个警告框,而不保存内容,可以这样设置:
tfe_alert_set_message (alert, "Contents aren't saved yet.\nAre you sure to close?");
tfe_alert_set_button_label (alert, "Close");
函数tfe_alert_new创建了一个TfeAlert对话框。
C源文件如下:
1 #include <gtk/gtk.h>
2 #include "tfealert.h"
3
4 struct _TfeAlert
5 {
6 GtkDialog parent;
7 GtkLabel *lb_alert;
8 GtkButton *btn_accept;
9 };
10
11 G_DEFINE_TYPE (TfeAlert, tfe_alert, GTK_TYPE_DIALOG);
12
13 void
14 tfe_alert_set_message (TfeAlert *alert, const char *message) {
15 gtk_label_set_text (alert->lb_alert, message);
16 }
17
18 void
19 tfe_alert_set_button_label (TfeAlert *alert, const char *label) {
20 gtk_button_set_label (alert->btn_accept, label);
21 }
22
23 static void
24 tfe_alert_init (TfeAlert *alert) {
25 gtk_widget_init_template (GTK_WIDGET (alert));
26 }
27
28 static void
29 tfe_alert_class_init (TfeAlertClass *class) {
30 gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class), "/com/github/ToshioCP/tfe/tfealert.ui");
31 gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfeAlert, lb_alert);
32 gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfeAlert, btn_accept);
33 }
34
35 GtkWidget *
36 tfe_alert_new (void) {
37 return GTK_WIDGET (g_object_new (TFE_TYPE_ALERT, NULL));
38 }
39
该程序几乎与tfeprefer .c相同。
alert对象的用法如下。
- 1.编写“response”信号处理程序。
- 2.创建一个TfeAlert对象。
- 3.将“response”信号连接到处理程序
- 4.显示对话框
- 5.在信号处理程序中,对response-id做一些处理并销毁对话框。
Top-level window
TfeWindow是GtkApplicationWindow的一个子类。
1 <?xml version="1.0" encoding="UTF-8"?>
2 <interface>
3 <template class="TfeWindow" parent="GtkApplicationWindow">
4 <property name="title">file editor</property>
5 <property name="default-width">600</property>
6 <property name="default-height">400</property>
7 <child>
8 <object class="GtkBox" id="boxv">
9 <property name="orientation">GTK_ORIENTATION_VERTICAL</property>
10 <child>
11 <object class="GtkBox" id="boxh">
12 <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
13 <child>
14 <object class="GtkLabel" id="dmy1">
15 <property name="width-chars">10</property>
16 </object>
17 </child>
18 <child>
19 <object class="GtkButton" id="btno">
20 <property name="label">Open</property>
21 <property name="action-name">win.open</property>
22 </object>
23 </child>
24 <child>
25 <object class="GtkButton" id="btns">
26 <property name="label">Save</property>
27 <property name="action-name">win.save</property>
28 </object>
29 </child>
30 <child>
31 <object class="GtkLabel" id="dmy2">
32 <property name="hexpand">TRUE</property>
33 </object>
34 </child>
35 <child>
36 <object class="GtkButton" id="btnc">
37 <property name="label">Close</property>
38 <property name="action-name">win.close</property>
39 </object>
40 </child>
41 <child>
42 <object class="GtkMenuButton" id="btnm">
43 <property name="direction">down</property>
44 <property name="halign">start</property>
45 <property name="icon-name">open-menu-symbolic</property>
46 </object>
47 </child>
48 <child>
49 <object class="GtkLabel" id="dmy3">
50 <property name="width-chars">10</property>
51 </object>
52 </child>
53 </object>
54 </child>
55 <child>
56 <object class="GtkNotebook" id="nb">
57 <property name="scrollable">TRUE</property>
58 <property name="hexpand">TRUE</property>
59 <property name="vexpand">TRUE</property>
60 </object>
61 </child>
62 </object>
63 </child>
64 </template>
65 </interface>
66
除了模板tag和按钮中的“action-name”属性之外,这个XML文件几乎与之前相同。
GtkButton实现了GtkActionable接口,该接口具有action-name属性。如果设置了这个属性,则GtkButton在单击时激活操作。例如,如果点击打开按钮,“win.open”操作将被激活,open_activated处理程序将被调用。
“<Control>o”
加速器也使用此操作(参见tfeapplication.c)。如果对按钮使用"clicked"信号,则需要它的信号处理程序。这样,就会有两个处理程序:
- 按钮上“clicked”信号的处理程序
"win.open" action
上的“activate”信号处理程序。“<Control>o”
加速器连接到该动作
这两个处理程序几乎相同。这是低效的。将按钮连接到动作是减少不必要代码的好方法。
1 #ifndef __TFE_WINDOW_H__
2 #define __TFE_WINDOW_H__
3
4 #include <gtk/gtk.h>
5
6 #define TFE_TYPE_WINDOW tfe_window_get_type ()
7 G_DECLARE_FINAL_TYPE (TfeWindow, tfe_window, TFE, WINDOW, GtkApplicationWindow)
8
9 void
10 tfe_window_notebook_page_new (TfeWindow *win);
11
12 void
13 tfe_window_notebook_page_new_with_files (TfeWindow *win, GFile **files, int n_files);
14
15 GtkWidget *
16 tfe_window_new (GtkApplication *app);
17
18 #endif /* __TFE_WINDOW_H__ */
19
有三个公共功能。函数tfe_window_notebook_page_new创建一个新的notebook页。这是notebook_page_new的一个包装器函数。它由TfeApplication对象调用。函数tfe_window_notebook_page_new_with_files从给定文件中读取内容,创建notebook页。函数tfe_window_new创建了一个TfeWindow实例。
1 #include <gtk/gtk.h>
2 #include "tfewindow.h"
3 #include "tfenotebook.h"
4 #include "tfepref.h"
5 #include "tfealert.h"
6
7 struct _TfeWindow {
8 GtkApplicationWindow parent;
9 GtkMenuButton *btnm;
10 GtkNotebook *nb;
11 gboolean is_quit;
12 };
13
14 G_DEFINE_TYPE (TfeWindow, tfe_window, GTK_TYPE_APPLICATION_WINDOW);
15
16 /* alert response signal handler */
17 static void
18 alert_response_cb (GtkDialog *alert, int response_id, gpointer user_data) {
19 TfeWindow *win = TFE_WINDOW (user_data);
20
21 gtk_window_destroy (GTK_WINDOW (alert));
22 if (response_id == GTK_RESPONSE_ACCEPT) {
23 if (win->is_quit)
24 gtk_window_destroy(GTK_WINDOW (win));
25 else
26 notebook_page_close (win->nb);
27 }
28 }
29
30 static gboolean
31 close_request_cb (TfeWindow *win) {
32 TfeAlert *alert;
33
34 if (has_saved_all (GTK_NOTEBOOK (win->nb)))
35 return false;
36 else {
37 win->is_quit = true;
38 alert = TFE_ALERT (tfe_alert_new ());
39 gtk_window_set_transient_for (GTK_WINDOW (alert), GTK_WINDOW (win));
40 tfe_alert_set_message (alert, "Contents aren't saved yet.\nAre you sure to quit?");
41 tfe_alert_set_button_label (alert, "Quit");
42 g_signal_connect (GTK_DIALOG (alert), "response", G_CALLBACK (alert_response_cb), win);
43 gtk_window_present (GTK_WINDOW (alert));
44 return true;
45 }
46 }
47
48 /* ----- action activated handlers ----- */
49 static void
50 open_activated (GSimpleAction *action, GVariant *parameter, gpointer user_data) {
51 TfeWindow *win = TFE_WINDOW (user_data);
52
53 notebook_page_open (GTK_NOTEBOOK (win->nb));
54 }
55
56 static void
57 save_activated (GSimpleAction *action, GVariant *parameter, gpointer user_data) {
58 TfeWindow *win = TFE_WINDOW (user_data);
59
60 notebook_page_save (GTK_NOTEBOOK (win->nb));
61 }
62
63 static void
64 close_activated (GSimpleAction *action, GVariant *parameter, gpointer user_data) {
65 TfeWindow *win = TFE_WINDOW (user_data);
66 TfeAlert *alert;
67
68 if (has_saved (win->nb))
69 notebook_page_close (win->nb);
70 else {
71 win->is_quit = false;
72 alert = TFE_ALERT (tfe_alert_new ());
73 gtk_window_set_transient_for (GTK_WINDOW (alert), GTK_WINDOW (win));
74 tfe_alert_set_message (alert, "Contents aren't saved yet.\nAre you sure to close?");
75 tfe_alert_set_button_label (alert, "Close");
76 g_signal_connect (GTK_DIALOG (alert), "response", G_CALLBACK (alert_response_cb), win);
77 gtk_widget_show (GTK_WIDGET (alert));
78 }
79 }
80
81 static void
82 new_activated (GSimpleAction *action, GVariant *parameter, gpointer user_data) {
83 TfeWindow *win = TFE_WINDOW (user_data);
84
85 notebook_page_new (GTK_NOTEBOOK (win->nb));
86 }
87
88 static void
89 saveas_activated (GSimpleAction *action, GVariant *parameter, gpointer user_data) {
90 TfeWindow *win = TFE_WINDOW (user_data);
91
92 notebook_page_saveas (GTK_NOTEBOOK (win->nb));
93 }
94
95 static void
96 pref_activated (GSimpleAction *action, GVariant *parameter, gpointer user_data) {
97 TfeWindow *win = TFE_WINDOW (user_data);
98 GtkWidget *pref;
99
100 pref = tfe_pref_new ();
101 gtk_window_set_transient_for (GTK_WINDOW (pref), GTK_WINDOW (win));
102 gtk_window_present (GTK_WINDOW (pref));
103 }
104
105 static void
106 close_all_activated (GSimpleAction *action, GVariant *parameter, gpointer user_data) {
107 TfeWindow *win = TFE_WINDOW (user_data);
108
109 if (close_request_cb (win) == false)
110 gtk_window_destroy (GTK_WINDOW (win));
111 }
112
113 /* --- public functions --- */
114
115 void
116 tfe_window_notebook_page_new (TfeWindow *win) {
117 notebook_page_new (win->nb);
118 }
119
120 void
121 tfe_window_notebook_page_new_with_files (TfeWindow *win, GFile **files, int n_files) {
122 int i;
123
124 for (i = 0; i < n_files; i++)
125 notebook_page_new_with_file (win->nb, files[i]);
126 if (gtk_notebook_get_n_pages (win->nb) == 0)
127 notebook_page_new (win->nb);
128 }
129
130 static void
131 tfe_window_init (TfeWindow *win) {
132 GtkBuilder *build;
133 GMenuModel *menu;
134
135 gtk_widget_init_template (GTK_WIDGET (win));
136
137 build = gtk_builder_new_from_resource ("/com/github/ToshioCP/tfe/menu.ui");
138 menu = G_MENU_MODEL (gtk_builder_get_object (build, "menu"));
139 gtk_menu_button_set_menu_model (win->btnm, menu);
140 g_object_unref(build);
141
142 /* ----- action ----- */
143 const GActionEntry win_entries[] = {
144 { "open", open_activated, NULL, NULL, NULL },
145 { "save", save_activated, NULL, NULL, NULL },
146 { "close", close_activated, NULL, NULL, NULL },
147 { "new", new_activated, NULL, NULL, NULL },
148 { "saveas", saveas_activated, NULL, NULL, NULL },
149 { "pref", pref_activated, NULL, NULL, NULL },
150 { "close-all", close_all_activated, NULL, NULL, NULL }
151 };
152 g_action_map_add_action_entries (G_ACTION_MAP (win), win_entries, G_N_ELEMENTS (win_entries), win);
153
154 g_signal_connect (GTK_WINDOW (win), "close-request", G_CALLBACK (close_request_cb), NULL);
155 }
156
157 static void
158 tfe_window_class_init (TfeWindowClass *class) {
159 gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class), "/com/github/ToshioCP/tfe/tfewindow.ui");
160 gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfeWindow, btnm);
161 gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfeWindow, nb);
162 }
163
164 GtkWidget *
165 tfe_window_new (GtkApplication *app) {
166 return GTK_WIDGET (g_object_new (TFE_TYPE_WINDOW, "application", app, NULL));
167 }
- 7-12: _TfeWindow结构体。TfeWindow实例指向该结构。
14: G_DEFINE_TYPE宏。
17-28: alert_response_cb是TfeAlert对话框的“响应”信号的回调函数。
21:销毁警告对话框。
22-27:如果用户点击了accept按钮,它会销毁主窗口或关闭当前的notebook页面。
30-46: TfeWindow上的一个“关闭请求”信号处理程序。当用户单击关闭按钮(右上角的x形按钮)时,会在窗口关闭前调用处理程序。如果处理程序返回true,则不会调用默认的处理程序,窗口也不会关闭。如果处理程序返回false,则调用默认的处理程序并关闭窗口。
34:如果has_saved_all返回true,处理程序返回false,窗口将关闭。否则,它会显示一个警告对话框。
48-111:动作激活信号处理程序。user_data是一个指向TfeWindow实例的指针。
115-128:公共函数。
130-155:实例初始化功能。
135:函数gtk_widget_init_template创建一个子部件并初始化它们。
137-140:构建和插入菜单。它被插入到菜单按钮中。
143-152:创建操作并将它们插入窗口。行动的范围是“赢”。
154:连接“关闭请求”信号和处理程序。
157-162:类初始化函数。
159:设置复合小部件模板
160-161:在子类模板中绑定私有变量。
164 - 167: tfe_window_new。这个函数创建了一个TfeWindow实例。