1 Open 信号
1.1 G_APPLICATION_HANDLES_OPEN flag
在上一节中,我们使用GtkTextView、GtkTextBuffer和GtkScrolledWindow创建了一个非常简单的编辑器。我们将为程序添加文件读取功能,并将其改进为文件查看器。
要给出文件名,最简单的方法是使用命令行参数。
$ ./a.out filename
该程序将打开文件并将其内容插入GtkTextBuffer。
为此,我们需要知道GtkApplication(或GApplication)如何识别参数。这在GIO API参考-应用程序中描述。
当创建GtkApplication时,一个标志(GApplicationFlags)作为参数给出。
GtkApplication *
gtk_application_new (const gchar *application_id, GApplicationFlags flags);
本教程只解释两个标志,G_APPLICATION_DEFAULT_FLAGS
和G_APPLICATION_HANDLES_OPEN
。(在GIO version2.73.3之前使用了G_APPLICATION_FLAGS_NONE
而不是G_APPLICATION_DEFAULT_FLAGS
(GLib 2.73.3 5/Aug/2022)。有些GTK 4应用程序仍然使用G_APPLICATION_FLAGS_NONE
。但现在已弃用,建议使用G_APPLICATION_DEFAULT_FLAGS
。)如果你想处理命令行参数,G_APPLICATION_HANDLES_COMMAND_LINE
标志就是你所需要的。
有关更多信息,请参阅GIO API Reference——ApplicationFlags和GIO API Reference——g_application_run。
我们已经使用了G_APPLICATION_DEFAULT_FLAGS,因为它是最简单的选项,并且不允许任何命令行参数。如果你提供了参数,就会发生错误。
第二简单的选项是G_APPLICATION_HANDLES_OPEN标志。它允许参数,但只允许文件。应用程序假定所有参数都是文件名。
app = gtk_application_new ("com.github.ToshioCP.tfv3", G_APPLICATION_HANDLES_OPEN);
1.2 open 信号
在G_APPLICATION_HANDLES_OPEN标志提供给应用程序时,有两个信号可用。
- activate signal——这个信号在没有参数时发出。
- open signal——当至少有一个参数时,该信号被触发。
“open”信号的处理程序定义如下。
void
open (
GApplication* self,
gpointer files,
gint n_files,
gchar* hint,
gpointer user_data
)
参数有:
- self——应用程序实例(通常是GtkApplication)
- files——一个gfile数组。[array length=n_files][element-type GFile]
- n_files——文件元素的数目
- hint——由调用实例提供的提示(通常可以忽略)
- user_data——连接信号处理程序时的用户数据集。
2 开发一个文件查看器
2.1 什么是一个文件查看器
文件查看器是一个显示文本文件的程序。我们的文件查看器将按如下方式工作。
- 当给出参数时,它将第一个参数识别为文件名并打开它。
- 第二个参数和后面的参数会被忽略。
- 如果没有参数,它会显示一条错误消息并退出。
- 如果它成功打开文件,它读取文件的内容,将它们插入 GtkTextBuffer并显示窗口。
- 如果打开文件失败,它会显示一个错误消息并退出。
程序如下所示。
1 #include <gtk/gtk.h>
2
3 static void
4 app_activate (GApplication *app) {
5 g_printerr ("You need a filename argument.\n");
6 }
7
8 static void
9 app_open (GApplication *app, GFile ** files, int n_files, char *hint) {
10 GtkWidget *win;
11 GtkWidget *scr;
12 GtkWidget *tv;
13 GtkTextBuffer *tb;
14 char *contents;
15 gsize length;
16 char *filename;
17
18 win = gtk_application_window_new (GTK_APPLICATION (app));
19 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
20
21 scr = gtk_scrolled_window_new ();
22 gtk_window_set_child (GTK_WINDOW (win), scr);
23
24 tv = gtk_text_view_new ();
25 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
26 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
27 gtk_text_view_set_editable (GTK_TEXT_VIEW (tv), FALSE);
28 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
29
30 if (g_file_load_contents (files[0], NULL, &contents, &length, NULL, NULL)) {
31 gtk_text_buffer_set_text (tb, contents, length);
32 g_free (contents);
33 if ((filename = g_file_get_basename (files[0])) != NULL) {
34 gtk_window_set_title (GTK_WINDOW (win), filename);
35 g_free (filename);
36 }
37 gtk_window_present (GTK_WINDOW (win));
38 } else {
39 if ((filename = g_file_get_path (files[0])) != NULL) {
40 g_printerr ("No such file: %s.\n", filename);
41 g_free (filename);
42 } else
43 g_printerr ("File can't be opened.\n");
44 gtk_window_destroy (GTK_WINDOW (win));
45 }
46 }
47
48 int
49 main (int argc, char **argv) {
50 GtkApplication *app;
51 int stat;
52
53 app = gtk_application_new ("com.github.ToshioCP.tfv3", G_APPLICATION_HANDLES_OPEN);
54 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
55 g_signal_connect (app, "open", G_CALLBACK (app_open), NULL);
56 stat = g_application_run (G_APPLICATION (app), argc, argv);
57 g_object_unref (app);
58 return stat;
59 }
保存以上代码到tfv3.c,并且编译和运行它。
$ comp tfv3
$ ./a.out tfv3.c
与前一个版本相比,函数main只有两个变化。
- G_APPLICATION_DEFAULT_FLAGS替换为 -G_APPLICATION_HANDLES_OPEN
- 添加g_signal_connect (app, “open”, G_CALLBACK (app_open), NULL)。
在将标志G_APPLICATION_HANDLES_OPEN传递给gtk_application_new函数时,应用程序的行为如下:
- 如果应用程序在没有命令行参数的情况下运行,它被激活时会发出“activate”信号。
- 如果应用程序以命令行参数运行,则在激活时发出“open”信号。
处理程序app_activate变得非常简单。它只是输出错误消息并返回给调用者。然后应用程序立即退出,因为没有创建窗口。
主要工作在处理程序app_open中完成。
创建GtkApplicationWindow, GtkScrolledWindow, GtkTextView和GtkTextBuffer,并将它们连接在一起
在GtktextView中设置wrap模式为GTK_WRAP_WORD_CHAR
将GtkTextView设置为不可编辑,因为程序不是编辑器,而是查看器
读取文件并将文本插入GtkTextBuffer(稍后会解释)。
如果文件没有打开,输出一个错误消息并销毁窗口。这会让应用退出。
下面是程序读取文件的部分。
if (g_file_load_contents (files[0], NULL, &contents, &length, NULL, NULL)) {
gtk_text_buffer_set_text (tb, contents, length);
g_free (contents);
if ((filename = g_file_get_basename (files[0])) != NULL) {
gtk_window_set_title (GTK_WINDOW (win), filename);
g_free (filename);
}
gtk_widget_show (win);
} else {
if ((filename = g_file_get_path (files[0])) != NULL) {
g_printerr ("No such file: %s.\n", filename);
g_free (filename);
} else
g_printerr ("File can't be opened.\n");
gtk_window_destroy (GTK_WINDOW (win));
}
函数g_file_load_contents将文件内容加载到一个临时缓冲区中,该缓冲区是自动分配的,并将内容设置为指向该缓冲区。将缓冲区的长度赋值给length。如果文件内容加载成功,它返回TRUE;如果发生错误,则返回FALSE。如果想了解有关g_file_load_contents的详细信息,请参见g file load contents。
如果成功读取文件,则将内容插入到GtkTextBuffer中,释放由内容指向的临时缓冲区,设置窗口标题,释放由文件名指向的内存,然后显示窗口。如果失败,则输出错误消息并销毁窗口,最后让程序退出。
3 GtkNotebook
GtkNotebook是一个容器部件,可以包含多个带有标签的构件。它一次只显示一个子构建。当单击其标签时,将显示另一个子构件。
大多数更改都发生在app_open函数中。以下项目左侧的数字是源代码中的行号。
- 11-13:定义了变量nb、lab和nbp。它们分别指向GtkNotebook、GtkLabel和GtkNotebookPage。
- 23:窗口的标题设置为"file viewer"。
- 24:窗口默认大小为600x400。
- 26-27创建了gtnotebook,并将其作为子构件插入到GtkApplicationWindow中。
- 29-58 for循环。变量files[i]指向第i个GFile,它是由GtkApplication根据第i个命令行参数创建的。
- 31-36 创建GtkScrollledWindow、GtkTextView。GtkTextBuffer是从GtkTextView获得的。GtkTextView作为子构件连接到GtkScrolledWindow。
- 38-39将文件的内容插入到GtkTextBuffer中,并释放内容指向的内存。
- 40-42:如果文件名来自GFile,则使用该文件名创建GtkLabel。字符串filename被释放。
- 43-44:如果获取文件名失败,则创建空字符串GtkLabel。
- 45-46:将GtkScrolledWindow作为子构件附加到GtkNotebook。并将GtkLabel设置为子构件的选项卡。同时,GtkNoteBookPage会被自动创建。函数gtk_notebook_get_page返回子构件(GtkScrolledWindow)页面的GtkNotebookPage。
47: GtkNotebookPage具有“tab-expand”属性。如果设置为TRUE,选项卡就会尽可能地水平展开。如果为FALSE,则选项卡的宽度由标签的大小决定。g_object_set是一个设置对象属性的通用函数。参见GObject API参考——g_object_set。
48-50:如果读取文件失败,从GFile中取了文件名,则显示“No such file”。文件名被释放。
51-52:如果filename为NULL,则显示“No valid file is given”。
54—57:如果至少存在一个页面,则显示该窗口。否则,窗口将被销毁,应用程序将退出。
参考:Gtk4 tutorial Widgets (3)