ffeapplication.c包含了除tfetxtview.c和tfenotebook.c之外的所有代码。它:
- 应用程序支持,主要处理命令行参数。
- 使用ui文件构建构件。
- 连接按钮信号及其处理程序。
- 管理CSS。
main
函数main是C语言中第一个被调用的函数。它将用户给出的命令行与Gtk应用程序连接起来。
1 #define APPLICATION_ID "com.github.ToshioCP.tfe"
2
3 int
4 main (int argc, char **argv) {
5 GtkApplication *app;
6 int stat;
7
8 app = gtk_application_new (APPLICATION_ID, G_APPLICATION_HANDLES_OPEN);
9
10 g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL);
11 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
12 g_signal_connect (app, "open", G_CALLBACK (app_open), NULL);
13
14 stat =g_application_run (G_APPLICATION (app), argc, argv);
15 g_object_unref (app);
16 return stat;
17 }
- 1:定义应用id。很容易找到应用程序id,而且比id嵌入gtk_application_new要好。
- 8:创建GtkApplication对象。
- 10-12:将"startup"、"activate"和"open"信号连接到它们的处理程序。
- 14:运行应用。
- 15-16:释放对应用程序的引用并返回状态。
startup signal handler
启动信号是在GtkApplication实例初始化之后发出的。信号处理程序需要做的是初始化应用程序。
- 使用ui文件构建小构件。
- 连接按钮信号及其处理程序。
- 设置CSS。
处理程序如下所示。
1 static void
2 app_startup (GApplication *application) {
3 GtkApplication *app = GTK_APPLICATION (application);
4 GtkBuilder *build;
5 GtkApplicationWindow *win;
6 GtkNotebook *nb;
7 GtkButton *btno;
8 GtkButton *btnn;
9 GtkButton *btns;
10 GtkButton *btnc;
11
12 build = gtk_builder_new_from_resource ("/com/github/ToshioCP/tfe/tfe.ui");
13 win = GTK_APPLICATION_WINDOW (gtk_builder_get_object (build, "win"));
14 nb = GTK_NOTEBOOK (gtk_builder_get_object (build, "nb"));
15 gtk_window_set_application (GTK_WINDOW (win), app);
16 btno = GTK_BUTTON (gtk_builder_get_object (build, "btno"));
17 btnn = GTK_BUTTON (gtk_builder_get_object (build, "btnn"));
18 btns = GTK_BUTTON (gtk_builder_get_object (build, "btns"));
19 btnc = GTK_BUTTON (gtk_builder_get_object (build, "btnc"));
20 g_signal_connect_swapped (btno, "clicked", G_CALLBACK (open_cb), nb);
21 g_signal_connect_swapped (btnn, "clicked", G_CALLBACK (new_cb), nb);
22 g_signal_connect_swapped (btns, "clicked", G_CALLBACK (save_cb), nb);
23 g_signal_connect_swapped (btnc, "clicked", G_CALLBACK (close_cb), nb);
24 g_object_unref(build);
25
26 GdkDisplay *display;
27
28 display = gtk_widget_get_display (GTK_WIDGET (win));
29 GtkCssProvider *provider = gtk_css_provider_new ();
30 gtk_css_provider_load_from_data (provider, "textview {padding: 10px; font-family: monospace; font-size: 12pt;}", -1);
31 gtk_style_context_add_provider_for_display (display, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
32
33 g_signal_connect (win, "destroy", G_CALLBACK (before_destroy), provider);
34 g_object_unref (provider);
35 }
- 12-15:使用ui文件(资源)构建小构件。用gtk_window_set_application连接顶级窗口和应用程序。
- 16-23:获取按钮并连接它们的信号和处理程序。
- 24:释放对GtkBuilder的引用。
- 26 ~ 31:设置CSS。Gtk中的CSS类似于HTML中的CSS。你可以用CSS设置外边距、边框、内边距、颜色、字体等。在这个程序中,CSS在第30行。它设置GtkTextView的padding, font-family和font- size。
- 26-28: GdkDisplay用于设置CSS。CSS将在下一小节中解释。
- 33:连接主窗口上的destroy信号和before_destroy处理程序。下一小节将解释该处理程序。
- 34:对于启动处理程序来说,provider是无用的,因此g_object_unref(provider)被调用。注意:这并不意味着provider的销毁。它由display引用,因此引用计数不为零。
CSS in Gtk
CSS是Cascading Style Sheet(层叠样式表)的缩写。它最初与HTML一起用于描述文档的表示语义。您可能已经发现Gtk中的小部件类似于浏览器中的窗口。这意味着CSS也可以应用于Gtk窗口系统。
CSS nodes, selectors
CSS的语法如下。
selector { color: yellow; padding-top: 10px; ...}
每个widget都有CSS node。例如,GtkTextView有textview节点。如果你想设置样式为GtkTextView,用“textview”代替选择器。
textview {color: yellow; ...}
类、ID和其他一些东西可以应用到选择器上,比如Web CSS。有关更多信息,请参阅GTK 4 API参考——GTK中的CSS。
在第30行,CSS是一个字符串。
textview {padding: 10px; font-family: monospace; font-size: 12pt;}
- 内边距是边界和内容之间的空白。这个空格让textview更容易阅读。
- font-family是字体的名称。“monospace”是一个通用的字体家族关键词。
- font-size设置为12pt。
GtkStyleContext, GtkCSSProvider and GdkDisplay
GtkStyleContext是一个存储影响窗口组件的样式信息的对象。每个小构件都连接到相应的GtkStyleContext。可以通过gtk_widget_get_style_context获取context。
GtkCssProvider是一个解析CSS以设置窗口组件样式的对象。
要将CSS应用于窗口组件,您需要将GtkStyleProvider (GtkCSSProvider的接口)添加到GtkStyleContext。但是,相反,您可以将它添加到窗口的GdkDisplay(通常是顶层窗口)。
再次查看启动处理程序的源文件。
- 28:通过gtk_widget_get_display获取display。
- 29:创建一个GtkCssProvider实例。
- 30:将CSS放入provider中。
- 31:将provider添加到display中。gtk_style_context_add_provider_for_display的最后一个参数是样式提供程序的优先级。GTK_STYLE_PROVIDER_PRIORITY_APPLICATION是特定于应用程序的样式信息的优先级。也经常使用GTK_STYLE_PROVIDER_PRIORITY_USER,它是最高的优先级。因此,GTK_STYLE_PROVIDER_PRIORITY_USER通常用于特定的小构件。
可以将provider添加到GtkTextView的context而不是gdkdisplay中。为此,重写tfe_text_view_new。首先,获取TfeTextView对象的GtkStyleContext对象。然后将CSS提供程序添加到上下文。
GtkWidget *
tfe_text_view_new (void) {
GtkWidget *tv;
tv = gtk_widget_new (TFE_TYPE_TEXT_VIEW, NULL);
GtkStyleContext *context;
context = gtk_widget_get_style_context (GTK_WIDGET (tv));
GtkCssProvider *provider = gtk_css_provider_new ();
gtk_css_provider_load_from_data (provider, "textview {padding: 10px; font-family: monospace; font-size: 12pt;}", -1);
gtk_style_context_add_provider (context, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_USER);
return tv;
}
context中的CSS优先于display中的CSS。
1 static void
2 before_destroy (GtkWidget *win, GtkCssProvider *provider) {
3 GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (win));
4 gtk_style_context_remove_provider_for_display (display, GTK_STYLE_PROVIDER (provider));
5 }
当部件被销毁时,或者更准确地说,在其释放过程中,会发出一个“destroy”信号。before_destroy处理程序连接到主窗口中的信号。(参见app_startup的程序列表。)因此,它会在窗口被销毁时被调用。
处理程序从GdkDisplay中删除CSS提供程序。
activate and open handler
activate和open信号的处理程序分别是app_activate和app_open。他们只是创建了一个新的GtkNotebookPage。
1 static void
2 app_activate (GApplication *application) {
3 GtkApplication *app = GTK_APPLICATION (application);
4 GtkWidget *win = GTK_WIDGET (gtk_application_get_active_window (app));
5 GtkWidget *boxv = gtk_window_get_child (GTK_WINDOW (win));
6 GtkNotebook *nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv));
7
8 notebook_page_new (nb);
9 gtk_window_present (GTK_WINDOW (win));
10 }
11
12 static void
13 app_open (GApplication *application, GFile ** files, gint n_files, const gchar *hint) {
14 GtkApplication *app = GTK_APPLICATION (application);
15 GtkWidget *win = GTK_WIDGET (gtk_application_get_active_window (app));
16 GtkWidget *boxv = gtk_window_get_child (GTK_WINDOW (win));
17 GtkNotebook *nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv));
18 int i;
19
20 for (i = 0; i < n_files; i++)
21 notebook_page_new_with_file (nb, files[i]);
22 if (gtk_notebook_get_n_pages (nb) == 0)
23 notebook_page_new (nb);
24 gtk_window_present (GTK_WINDOW (win));
25 }
- 1: app_activate。
- 8-10:创建一个新页面并显示窗口。
- 12-25:app_open。
- 20-21:创建带有文件的notebook页面。
- 22-23:如果没有创建页面,可能是因为读取错误,那么它将创建一个空页面。
- 24:显示窗口。
多亏了tfenotebook.c和tfetextview.c,这些代码变得非常简单。
Primary instance
每个会话一次只能运行一个GApplication实例。会话是一个有点困难的概念,而且与平台有关,但粗略地说,它对应于图形桌面登录。当你使用你的电脑时,你可能先登录,然后你的桌面出现,直到你注销。这就是会议。
但是,Linux是多进程操作系统,可以运行同一个应用程序的两个或多个实例。这不是矛盾吗?
当第一个实例启动时,它用它的应用程序ID(例如com.github.ToshioCP.tfe)注册自己。在注册之后,启动信号被发出,然后激活或打开信号被发出,并且实例的主循环运行。我在前一小节中写了“启动信号是在应用程序实例初始化之后发出的”。更准确地说,它是在注册之后发出的。
如果调用具有相同应用程序ID的另一个实例,它也会尝试注册自己。因为这是第二个实例,ID的注册已经完成,所以它失败了。由于失败,启动信号没有发出。之后,activate或open信号在主实例中发出,而不是在第二个实例中。primary实例接收信号,并调用其处理程序。另一方面,第二个实例没有收到信号,它立即退出。
试着连续运行两个实例。
$ ./_build/tfe &
[1] 84453
$ ./build/tfe tfeapplication.c
$
首先,主实例打开一个窗口。然后,在第二个实例运行之后,一个包含tfeapplication.c内容的新的notebook页面将出现在主实例的窗口中。这是因为open信号是在主实例中发出的。第二个实例立即退出,因此shell提示符很快出现。
a series of handlers correspond to the button signals
1 static void
2 open_cb (GtkNotebook *nb) {
3 notebook_page_open (nb);
4 }
5
6 static void
7 new_cb (GtkNotebook *nb) {
8 notebook_page_new (nb);
9 }
10
11 static void
12 save_cb (GtkNotebook *nb) {
13 notebook_page_save (nb);
14 }
15
16 static void
17 close_cb (GtkNotebook *nb) {
18 notebook_page_close (GTK_NOTEBOOK (nb));
19 }
open_cb、new_cb、save_cb和close_cb只调用相应的notebook页面函数。
meson.build
1 project('tfe', 'c')
2
3 gtkdep = dependency('gtk4')
4
5 gnome=import('gnome')
6 resources = gnome.compile_resources('resources','tfe.gresource.xml')
7
8 sourcefiles=files('tfeapplication.c', 'tfenotebook.c', '../tfetextview/tfetextview.c')
9
10 executable('tfe', sourcefiles, resources, dependencies: gtkdep)
在这个文件中,只修改了源文件名,而不是以前的版本。
source files
您可以从存储库下载这些文件。有两种选择。
- 使用git并克隆。
- 运行浏览器并打开顶部页面。然后点击“代码”按钮,在弹出菜单中点击“下载ZIP”。之后,解压缩归档文件。
如果使用git,运行终端并输入以下命令。
$ git clone https://github.com/ToshioCP/Gtk4-tutorial.git
源文件在/src/tfe5目录下。