GtkNotebook是文本文件编辑器tfe中一个非常重要的对象。它连接application和TfeTextView对象。tfenotebook.h中声明了一组公开函数。“tfenotebook”这个词只用于文件名。没有“TfeNotebook”对象。
1 void
2 notebook_page_save(GtkNotebook *nb);
3
4 void
5 notebook_page_close (GtkNotebook *nb);
6
7 void
8 notebook_page_open (GtkNotebook *nb);
9
10 void
11 notebook_page_new_with_file (GtkNotebook *nb, GFile *file);
12
13 void
14 notebook_page_new (GtkNotebook *nb);
15
这个头文件描述了tfenotebook.c中的公共函数。
- 1-2: notebook_page_save 将当前页面保存到选项卡中指定的文件中。如果名称是untitled或untitled后跟数字,则会出现FileChooserDialog,用户可以选择或指定文件名。
- 4-5: notebook_page_close关闭当前页面。
- 7-8: notebook_page_open显示一个文件选择器对话框,用户可以选择一个文件。该文件被插入到一个新页。
- 10-11: notebook_page_new_with_file创建一个新页,读取一个文件并将其作为参数插入到该页中。
- 13-14: notebook_page_new创建一个新的空页。
你可能会发现,除了notebook_page_close之外,其他函数都是的高级函数
- tfe_text_view_save
- tef_text_view_open
- tfe_text_view_new_with_file
- tfe_text_view_new
分别是。
有两层。其中之一是tfe_text_view…,它是较低层。另一个是note_book…,这是更高的层次。
现在让我们看看每个函数的程序。
1 notebook_page_new
1 static char*
2 get_untitled () {
3 static int c = -1;
4 if (++c == 0)
5 return g_strdup_printf("Untitled");
6 else
7 return g_strdup_printf ("Untitled%u", c);
8 }
9
10 static void
11 notebook_page_build (GtkNotebook *nb, GtkWidget *tv, const char *filename) {
12 GtkWidget *scr = gtk_scrolled_window_new ();
13 GtkNotebookPage *nbp;
14 GtkWidget *lab;
15 int i;
16
17 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
18 lab = gtk_label_new (filename);
19 i = gtk_notebook_append_page (nb, scr, lab);
20 nbp = gtk_notebook_get_page (nb, scr);
21 g_object_set (nbp, "tab-expand", TRUE, NULL);
22 gtk_notebook_set_current_page (nb, i);
23 g_signal_connect (GTK_TEXT_VIEW (tv), "change-file", G_CALLBACK (file_changed_cb), nb);
24 }
25
26 void
27 notebook_page_new (GtkNotebook *nb) {
28 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
29
30 GtkWidget *tv;
31 char *filename;
32
33 if ((tv = tfe_text_view_new ()) == NULL)
34 return;
35 filename = get_untitled ();
36 notebook_page_build (nb, tv, filename);
37 g_free (filename);
38 }
- 26-38: notebook_page_new函数。
- 28: g_return_if_fail用于检查参数。
- 33-34:创建TfeTextView对象如果失败,则不会创建notebook页,函数返回给调用者。
- 35:创建文件名,文件名为"Untitled", “Untitled1”, … .
- 1-8: get_untitled函数。
- 3:静态变量c在第一次调用这个函数时初始化。此后,除非显式地修改,否则c的值将保持不变。
- 4-7:将c加1,如果它为0,则返回"Untitled"。如果它是一个正整数,则返回
"Untitled<the integer>"
,例如"Untitled1", “Untitled2”,以此类推。函数g_strdup_printf创建一个字符串,当它变得无用时,g_free应该释放它。get_untitled的调用者负责释放字符串。 - 36:调用notebook_page_build构建页面的内容。
- 37:释放文件名。
- 10- 24: notebook_page_build函数。带有const限定符的形参在函数中不会改变。这意味着参数filename归调用者所有。调用者需要在它变得无用时释放它。
- 12:创建GtkScrolledWindow。
- 17:将tv插入GtkscrolledWindow作为子窗口。
- 18-19:创建GtkLabel,然后将scr和lab附加到GtkNotebook实例nb。
- 20-21:设置“tab-expand”属性为TRUE。函数g_object_set用于设置对象的属性。对象是派生自GObject的任何对象。在很多情况下,对象都有自己设置属性的函数,但有时也没有。在这种情况下,使用g_object_set来设置属性。
- 22:将当前页面设置为新创建的页面。
- 23:连接change-file信号和file_changed_cb处理程序。
2 notebook_page_new_with_file
1 void
2 notebook_page_new_with_file (GtkNotebook *nb, GFile *file) {
3 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
4 g_return_if_fail(G_IS_FILE (file));
5
6 GtkWidget *tv;
7 char *filename;
8
9 if ((tv = tfe_text_view_new_with_file (file)) == NULL)
10 return; /* read error */
11 filename = g_file_get_basename (file);
12 notebook_page_build (nb, tv, filename);
13 g_free (filename);
14 }
- 9-10:调用tfe_text_view_new_with_file。如果函数返回NULL,说明发生了错误。然后,它什么都不做并返回。
- 11-13:获取文件名,构建页面内容并释放文件名。
3 notebook_page_open
1 static void
2 open_response (TfeTextView *tv, int response, GtkNotebook *nb) {
3 GFile *file;
4 char *filename;
5
6 if (response != TFE_OPEN_RESPONSE_SUCCESS) {
7 g_object_ref_sink (tv);
8 g_object_unref (tv);
9 }else {
10 file = tfe_text_view_get_file (tv);
11 filename = g_file_get_basename (file);
12 g_object_unref (file);
13 notebook_page_build (nb, GTK_WIDGET (tv), filename);
14 g_free (filename);
15 }
16 }
17
18 void
19 notebook_page_open (GtkNotebook *nb) {
20 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
21
22 GtkWidget *tv;
23
24 if ((tv = tfe_text_view_new ()) == NULL)
25 return;
26 g_signal_connect (TFE_TEXT_VIEW (tv), "open-response", G_CALLBACK (open_response), nb);
27 tfe_text_view_open (TFE_TEXT_VIEW (tv), GTK_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (nb), GTK_TYPE_WINDOW)));
28 }
- 18-28: notebook_page_open函数。
- 24-25:创建TfeTextView对象。如果返回NULL,说明发生了错误。然后,它返回给调用者。
- 26:连接信号“open-response”和处理程序open_response。
- 27:调用tfe_text_view_open。稍后会发出“open-response”信号,通知打开和读取文件的结果。
- 1-16: open_response处理程序。
- 6-8:如果响应代码不是TFE_OPEN_RESPONSE_SUCCESS,则打开和读取新文件失败。然后,需要取消notebook_page_open事先做的事情。实例tv还不是GtkScrolledWindow的子部件。这样的实例具有浮动引用。稍后会解释浮动引用。首先需要调用g_object_ref_sink。然后将浮点引用转换为普通引用。现在调用g_object_unref将引用计数减少1。
- 9-15:其他一切正常。获取文件名,构建页面的内容并释放文件名。
4 Floating reference
所有构件都派生自GInitiallyUnowned。GObject和GInitiallyUnowned几乎是一样的。区别是这样的。当一个GInitiallyUnowned的实例被创建时,该实例有一个浮动引用并且它的引用计数为零。另一方面,当GObject(不是GInitiallyUnowned)的实例被创建时,没有给出浮动引用。并且该实例具有普通的引用计数而不是浮动引用。它们的后代会继承它们,所以每个widget一开始都有一个浮动引用。例如,非Widget组件类GtkTextBuffer是GObject的直接子类,它没有浮动引用。它被创建时的引用计数是1。
函数g_object_ref_sink将浮动引用转换为普通引用。如果实例没有浮动引用,g_object_ref_sink将引用计数加1。当一个构件作为子构件被添加到另一个构件时,使用它。
GtkTextView *tv = gtk_text_view_new (); // floating reference
GtkScrolledWindow *scr = gtk_scrolled_window_new ();
gtk_scrolled_window_set_child (scr, tv); // tv's reference count is one
在tv作为子构件添加到scr时,需要使用g_object_ref_sink。
g_object_ref_sink (tv);
因此,浮点引用被转换为普通引用。也就是说,浮点引用被删除,引用计数变为1。正因为如此,调用者不需要减少tv的引用计数。如果Object_A不是GInitiallyUnowned的后代,程序如下所示:
Object_A *obj_a = object_a_new (); // reference count is one
GtkScrolledWindow *scr = gtk_scrolled_window_new ();
gtk_scrolled_window_set_child (scr, obj_a); // obj_a's reference count is two
// obj_a is referred by the caller (this program) and scrolled window
g_object_unref (obj_a); // obj_a's reference count is one because the caller no longer refers obj_a.
这个例子告诉我们调用者需要unref obj_a。
如果将g_object_unref用于具有浮动引用的实例,则需要提前将浮动引用转换为普通引用。更多信息请参见GObject API参考。
5 notebook_page_close
1 void
2 notebook_page_close (GtkNotebook *nb) {
3 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
4
5 GtkWidget *win;
6 int i;
7
8 if (gtk_notebook_get_n_pages (nb) == 1) {
9 win = gtk_widget_get_ancestor (GTK_WIDGET (nb), GTK_TYPE_WINDOW);
10 gtk_window_destroy(GTK_WINDOW (win));
11 } else {
12 i = gtk_notebook_get_current_page (nb);
13 gtk_notebook_remove_page (GTK_NOTEBOOK (nb), i);
14 }
15 }
该函数关闭当前页面。如果该页面是notebook中唯一的页面,那么该函数会销毁顶层窗口并退出应用程序。
8-10:如果当前页是notebook拥有的唯一一页,则调用gtk_window_destroy销毁顶层窗口。
11-13:否则,删除当前页面。子部件(TfeTextView)也会被销毁。
6 notebook_page_save
1 static TfeTextView *
2 get_current_textview (GtkNotebook *nb) {
3 int i;
4 GtkWidget *scr;
5 GtkWidget *tv;
6
7 i = gtk_notebook_get_current_page (nb);
8 scr = gtk_notebook_get_nth_page (nb, i);
9 tv = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (scr));
10 return TFE_TEXT_VIEW (tv);
11 }
12
13 void
14 notebook_page_save (GtkNotebook *nb) {
15 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
16
17 TfeTextView *tv;
18
19 tv = get_current_textview (nb);
20 tfe_text_view_save (TFE_TEXT_VIEW (tv));
21 }
- 13-21: notebook_page_save。
- 19:获取属于当前页面的TfeTextView
- 20:调用tfe_text_view_save。
- 1 - 11: get_current_textview。这个函数获取当前页面的TfeTextView对象。
- 7:获取当前页面的页码。
- 8:获取当前页面的子构件scr,它是一个GtkScrolledWindow实例。
- 9-10:取得scr的子构件件,也就是一个TfeTextView实例,并返回它。
7 file_changed_cb handler
file_changed_cb函数是一个连接到“change-file”信号的处理程序。如果TfeTextView实例中的文件被更改,它会发出这个信号。这个处理程序更改GtkNotebookPage的标签。
1 static void
2 file_changed_cb (TfeTextView *tv, GtkNotebook *nb) {
3 GtkWidget *scr;
4 GtkWidget *label;
5 GFile *file;
6 char *filename;
7
8 file = tfe_text_view_get_file (tv);
9 scr = gtk_widget_get_parent (GTK_WIDGET (tv));
10 if (G_IS_FILE (file)) {
11 filename = g_file_get_basename (file);
12 g_object_unref (file);
13 } else
14 filename = get_untitled ();
15 label = gtk_label_new (filename);
16 g_free (filename);
17 gtk_notebook_set_tab_label (nb, scr, label);
18 }
- 8:从tv获取GFile实例。
- 9:获取tv的父组件GkScrolledWindow实例。
- 10-12:如果file指向GFile,则将GFile的文件名赋值给filename。然后,取消引用GFile对象文件。
- 13-14:否则(file为NULL),将untitled字符串赋值给filename。
- 15-16:用文件名创建一个GtkLabel实例标签,用label设置GtkNotebookPage的标签。