Modify section 8 and 9.

This commit is contained in:
Toshio Sekiya 2021-06-16 21:32:44 +09:00
parent 8eaf6064a8
commit cbd454c85b
11 changed files with 313 additions and 285 deletions

View file

@ -9,16 +9,16 @@ Now we go on to rewrite it and make a very simple editor.
Its source file name is tfe1.c (text file editor 1). Its source file name is tfe1.c (text file editor 1).
GtkTextView originally has a feature of multi line editing. GtkTextView originally has a feature of multi line editing.
Therefore, we don't need to rewrite the program from scratch. Therefore, we don't need to write the program from scratch.
We just add two things to the file viewer. We just add two things to the file viewer.
- Static memory is needed to store a pointer to GFile. - Memory to store a pointer to the GFile instance.
- We need to implement file write function. - A function to write the file.
A couple of ways are possible to get memories to keep GFile. A couple of ways are possible to get memories to keep GFile.
- Use global variables. - Use global variables.
- make a child object so that it can extend the memories for the GFile object. - make a child object so that it can extend the instance memory for the GFile object.
Using global variables is easy to implement. Using global variables is easy to implement.
Define a sufficient size array of pointers to GFile. Define a sufficient size array of pointers to GFile.
@ -28,13 +28,13 @@ For example,
GFile *f[20]; GFile *f[20];
~~~ ~~~
And `f[i]` corresponds to i-th GtkNotebookPage. And `f[i]` corresponds to the i-th GtkNotebookPage.
However, there are two problems. However, there are two problems.
One is the size of the array. One is the size of the array.
If a user gives arguments more than that, bad thing may happen. If a user gives arguments more than that (20 in the example above), it is impossible to store all the pointers to the GFile instances.
The other is the difficulty of maintenance of the program. The other is the difficulty of maintenance of the program.
It is a small program so far. It is a small program so far.
However, if you continue developing it, then its size grows bigger and bigger. However, if you continue developing it, then the size of the program grows bigger and bigger.
Generally speaking, the bigger the program size, the more difficult to maintain global variables. Generally speaking, the bigger the program size, the more difficult to maintain global variables.
Making child object is a good idea in terms of maintenance. Making child object is a good idea in terms of maintenance.
@ -43,7 +43,7 @@ What we are thinking about now is "child object".
A child object includes its parent object. A child object includes its parent object.
And a child object derives everything from the parent object. And a child object derives everything from the parent object.
![Child widget of GtkTextView](../image/child.png) ![Child object of GtkTextView](../image/child.png)
We will define TfeTextView as a child object of GtkTextView. We will define TfeTextView as a child object of GtkTextView.
It has everything that GtkTextView has. It has everything that GtkTextView has.
@ -52,8 +52,9 @@ And important thing is that TfeTextView can have a memory to keep a pointer to G
However, to understand the general theory about Gobject is very hard especially for beginners. However, to understand the general theory about Gobject is very hard especially for beginners.
So, I will just show you the way how to write the code and avoid the theoretical side in the next subsection. So, I will just show you the way how to write the code and avoid the theoretical side in the next subsection.
If you want to know about GObject system, refer to [GObject tutorial](https://github.com/ToshioCP/Gobject-tutorial).
## How to define a child widget of GtkTextView ## How to define a child object of GtkTextView
Let's define TfeTextView object which is a child object of GtkTextView. Let's define TfeTextView object which is a child object of GtkTextView.
First, look at the program below. First, look at the program below.
@ -98,6 +99,7 @@ If you are curious about the background theory of this program, It's very good f
Because knowing the theory is very important for you to program GTK applications. Because knowing the theory is very important for you to program GTK applications.
Look at [GObject API reference](https://developer.gnome.org/gobject/stable/). Look at [GObject API reference](https://developer.gnome.org/gobject/stable/).
All you need is described in it. All you need is described in it.
Or, refer to [GObject tutorial](https://github.com/ToshioCP/Gobject-tutorial).
However, it's a tough journey especially for beginners. However, it's a tough journey especially for beginners.
For now, you don't need to know such difficult theory. For now, you don't need to know such difficult theory.
Just remember the instructions below. Just remember the instructions below.
@ -107,8 +109,8 @@ Tfe and TextView.
Tfe is called prefix, namespace or module. Tfe is called prefix, namespace or module.
TextView is called object. TextView is called object.
- There are three patterns. - There are three patterns.
TfeTextView (camel case), tfe\_text\_view (this is used to write functions) and TFE\_TEXT\_VIEW (This is used to write casts). TfeTextView (camel case), tfe\_text\_view (this is used to write functions) and TFE\_TEXT\_VIEW (This is used to cast a pointer to point TfeTextView type).
- First, define TFE\_TYPE\_TEXT\_VIEW as tfe\_text\_view\_get\_type (). - First, define TFE\_TYPE\_TEXT\_VIEW macro as tfe\_text\_view\_get\_type ().
The name is always (prefix)\_TYPE\_(object) and the letters are upper case. The name is always (prefix)\_TYPE\_(object) and the letters are upper case.
And the replacement text is always (prefix)\_(object)\_get\_type () and the letters are lower case. And the replacement text is always (prefix)\_(object)\_get\_type () and the letters are lower case.
- Next, use G\_DECLARE\_FINAL\_TYPE macro. - Next, use G\_DECLARE\_FINAL\_TYPE macro.
@ -118,31 +120,30 @@ The underscore is necessary.
The first member is the parent object. The first member is the parent object.
Notice this is not a pointer but the object itself. Notice this is not a pointer but the object itself.
The second member and after are members of the child object. The second member and after are members of the child object.
TfeTextView structure has a pointer to GFile as a member. TfeTextView structure has a pointer to a GFile instance as a member.
- Use G\_DEFINE\_TYPE macro. - Use G\_DEFINE\_TYPE macro.
The arguments are the child object name in camel case, lower case with underscore and parent object type (prefix)\_TYPE\_(module). The arguments are the child object name in camel case, lower case with underscore and parent object type (prefix)\_TYPE\_(module).
- Define instance init function (tfe\_text\_view\_init). - Define instance init function (tfe\_text\_view\_init).
Usually you don't need to do anything. Usually you don't need to do anything.
- Define class init function (tfe\_text\_view\_class\_init). - Define class init function (tfe\_text\_view\_class\_init).
You don't need to do anything in this widget. You don't need to do anything in this object.
- Write function codes you want to add (tfe\_text\_view\_set\_file and tfe\_text\_view\_get\_file). - Write function codes you want to add (tfe\_text\_view\_set\_file and tfe\_text\_view\_get\_file).
`tv` is a pointer to TfeTextView object instance which is a C-structure declared with the tag \_TfeTextView. `tv` is a pointer to the TfeTextView object instance which is a C-structure declared with the tag \_TfeTextView.
So, the structure has a member `file` as a pointer to GFile. So, the structure has a member `file` as a pointer to a GFile instance.
`tv->file = f` is an assignment of `f` to a member `file` of the structure pointed by `tv`. `tv->file = f` is an assignment of `f` to a member `file` of the structure pointed by `tv`.
This is an example how to use the extended memory in a child widget. This is an example how to use the extended memory in a child widget.
- Write object generation function. - Write a function to create an instance.
Its name is (prefix)\_(object)\_new. Its name is (prefix)\_(object)\_new.
If the parent object function needs parameters, this function also need them. If the parent object function needs parameters, this function also need them.
You sometimes might want to add some parameters. You sometimes might want to add some parameters.
It's your choice. It's your choice.
Use g\_object\_new function to generate the child widget. Use g\_object\_new function to create the instance.
The arguments are (prefix)\_TYPE\_(object), a list to initialize properties and NULL. The arguments are (prefix)\_TYPE\_(object), a list to initialize properties and NULL.
In this code no property needs to be initialized. In this code no property needs to be initialized.
And the return value must be casted to GtkWidget. And the return value is casted to GtkWidget.
This program is not perfect. This program is not perfect.
It has some problems. It has some problems.
But I don't discuss them now.
It will be modified later. It will be modified later.
## Close-request signal ## Close-request signal
@ -150,7 +151,7 @@ It will be modified later.
Imagine that you use this editor. Imagine that you use this editor.
First, you run the editor with arguments. First, you run the editor with arguments.
The arguments are filenames. The arguments are filenames.
The editor reads the files and shows its window with the text of files in it. The editor reads the files and shows the window with the text of files in it.
Then you edit the text. Then you edit the text.
After you finish editing, you exit the editor. After you finish editing, you exit the editor.
The editor updates files just before the window closes. The editor updates files just before the window closes.
@ -158,7 +159,7 @@ The editor updates files just before the window closes.
GtkWindow emits "close-request" signal before it closes. GtkWindow emits "close-request" signal before it closes.
We connect the signal and the handler `before_close`. We connect the signal and the handler `before_close`.
A handler is a C function. A handler is a C function.
When a function is connected to a certain signal, we call it handler. When a function is connected to a certain signal, we call it a handler.
The function `before_close` is invoked when the signal "close-request" is emitted. The function `before_close` is invoked when the signal "close-request" is emitted.
~~~C ~~~C
@ -171,46 +172,54 @@ The program of `before_close` is as follows.
~~~C ~~~C
1 static gboolean 1 static gboolean
2 before_close (GtkWindow *win, GtkWidget *nb) { 2 before_close (GtkWindow *win, gpointer user_data) {
3 GtkWidget *scr; 3 GtkWidget *nb = GTK_WIDGET (user_data);
4 GtkWidget *tv; 4 GtkWidget *scr;
5 GFile *file; 5 GtkWidget *tv;
6 GtkTextBuffer *tb; 6 GFile *file;
7 GtkTextIter start_iter; 7 char *pathname;
8 GtkTextIter end_iter; 8 GtkTextBuffer *tb;
9 char *contents; 9 GtkTextIter start_iter;
10 unsigned int n; 10 GtkTextIter end_iter;
11 unsigned int i; 11 char *contents;
12 12 unsigned int n;
13 n = gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)); 13 unsigned int i;
14 for (i = 0; i < n; ++i) { 14
15 scr = gtk_notebook_get_nth_page (GTK_NOTEBOOK (nb), i); 15 n = gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb));
16 tv = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (scr)); 16 for (i = 0; i < n; ++i) {
17 file = tfe_text_view_get_file (TFE_TEXT_VIEW (tv)); 17 scr = gtk_notebook_get_nth_page (GTK_NOTEBOOK (nb), i);
18 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv)); 18 tv = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (scr));
19 gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter); 19 file = tfe_text_view_get_file (TFE_TEXT_VIEW (tv));
20 contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE); 20 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
21 if (! g_file_replace_contents (file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, NULL)) 21 gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
22 g_print ("ERROR : Can't save %s.", g_file_get_path (file)); 22 contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
23 } 23 if (! g_file_replace_contents (file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, NULL)) {
24 return FALSE; 24 pathname = g_file_get_path (file);
25 } 25 g_print ("ERROR : Can't save %s.", pathname);
26 g_free (pathname);
27 }
28 g_free (contents);
29 }
30 return FALSE;
31 }
~~~ ~~~
The numbers on the left of items are line numbers in the source code. The numbers on the left of items are line numbers in the source code.
- 13: Gets the number of pages `nb` has. - 15: Gets the number of pages `nb` has.
- 14-23: For loop with regard to the index to each pages. - 16-29: For loop with regard to the index to each pages.
- 15-17: Gets GtkScrolledWindow, TfeTextView and a pointer to GFile. - 17-19: Gets GtkScrolledWindow, TfeTextView and a pointer to GFile.
The pointer was stored when `on_open` handler had run. It will be shown later. The pointer was stored when `app_open` handler had run. It will be shown later.
- 18-20: Gets GtkTextBuffer and contents. `start_iter` and `end_iter` are iterators of the buffer. - 20-22: Gets GtkTextBuffer and contents. `start_iter` and `end_iter` are iterators of the buffer.
I don't want to explain them now because it would take a lot of time. I don't want to explain them now because it would take a lot of time.
Just remember these lines for the present. Just remember these lines for the present.
- 21: Writes the file. - 23-27: Writes the contents to the file.
If it fails, it outputs an error message.
- 28: Frees `contents`.
## Source code of tfe1.c ## Source code of tfe1.c
Now I will show you all the source code of `tfe1`.c. Now I will show you all the source code of `tfe1.c`.
~~~C ~~~C
1 #include <gtk/gtk.h> 1 #include <gtk/gtk.h>
@ -254,113 +263,120 @@ Now I will show you all the source code of `tfe1`.c.
39 /* ---------- end of the definition of TfeTextView ---------- */ 39 /* ---------- end of the definition of TfeTextView ---------- */
40 40
41 static gboolean 41 static gboolean
42 before_close (GtkWindow *win, GtkWidget *nb) { 42 before_close (GtkWindow *win, gpointer user_data) {
43 GtkWidget *scr; 43 GtkWidget *nb = GTK_WIDGET (user_data);
44 GtkWidget *tv; 44 GtkWidget *scr;
45 GFile *file; 45 GtkWidget *tv;
46 GtkTextBuffer *tb; 46 GFile *file;
47 GtkTextIter start_iter; 47 char *pathname;
48 GtkTextIter end_iter; 48 GtkTextBuffer *tb;
49 char *contents; 49 GtkTextIter start_iter;
50 unsigned int n; 50 GtkTextIter end_iter;
51 unsigned int i; 51 char *contents;
52 52 unsigned int n;
53 n = gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)); 53 unsigned int i;
54 for (i = 0; i < n; ++i) { 54
55 scr = gtk_notebook_get_nth_page (GTK_NOTEBOOK (nb), i); 55 n = gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb));
56 tv = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (scr)); 56 for (i = 0; i < n; ++i) {
57 file = tfe_text_view_get_file (TFE_TEXT_VIEW (tv)); 57 scr = gtk_notebook_get_nth_page (GTK_NOTEBOOK (nb), i);
58 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv)); 58 tv = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (scr));
59 gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter); 59 file = tfe_text_view_get_file (TFE_TEXT_VIEW (tv));
60 contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE); 60 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
61 if (! g_file_replace_contents (file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, NULL)) 61 gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
62 g_print ("ERROR : Can't save %s.", g_file_get_path (file)); 62 contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
63 } 63 if (! g_file_replace_contents (file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, NULL)) {
64 return FALSE; 64 pathname = g_file_get_path (file);
65 } 65 g_print ("ERROR : Can't save %s.", pathname);
66 66 g_free (pathname);
67 static void 67 }
68 on_activate (GApplication *app, gpointer user_data) { 68 g_free (contents);
69 g_print ("You need to give filenames as arguments.\n"); 69 }
70 } 70 return FALSE;
71 71 }
72 static void 72
73 on_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) { 73 static void
74 GtkWidget *win; 74 app_activate (GApplication *app, gpointer user_data) {
75 GtkWidget *nb; 75 g_print ("You need to give filenames as arguments.\n");
76 GtkWidget *lab; 76 }
77 GtkNotebookPage *nbp; 77
78 GtkWidget *scr; 78 static void
79 GtkWidget *tv; 79 app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) {
80 GtkTextBuffer *tb; 80 GtkWidget *win;
81 char *contents; 81 GtkWidget *nb;
82 gsize length; 82 GtkWidget *lab;
83 char *filename; 83 GtkNotebookPage *nbp;
84 int i; 84 GtkWidget *scr;
85 85 GtkWidget *tv;
86 win = gtk_application_window_new (GTK_APPLICATION (app)); 86 GtkTextBuffer *tb;
87 gtk_window_set_title (GTK_WINDOW (win), "file editor"); 87 char *contents;
88 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300); 88 gsize length;
89 gtk_window_maximize (GTK_WINDOW (win)); 89 char *filename;
90 90 int i;
91 nb = gtk_notebook_new (); 91
92 gtk_window_set_child (GTK_WINDOW (win), nb); 92 win = gtk_application_window_new (GTK_APPLICATION (app));
93 93 gtk_window_set_title (GTK_WINDOW (win), "file editor");
94 for (i = 0; i < n_files; i++) { 94 gtk_window_maximize (GTK_WINDOW (win));
95 if (g_file_load_contents (files[i], NULL, &contents, &length, NULL, NULL)) { 95
96 scr = gtk_scrolled_window_new (); 96 nb = gtk_notebook_new ();
97 tv = tfe_text_view_new (); 97 gtk_window_set_child (GTK_WINDOW (win), nb);
98 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv)); 98
99 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR); 99 for (i = 0; i < n_files; i++) {
100 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv); 100 if (g_file_load_contents (files[i], NULL, &contents, &length, NULL, NULL)) {
101 101 scr = gtk_scrolled_window_new ();
102 tfe_text_view_set_file (TFE_TEXT_VIEW (tv), g_file_dup (files[i])); 102 tv = tfe_text_view_new ();
103 gtk_text_buffer_set_text (tb, contents, length); 103 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
104 g_free (contents); 104 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
105 filename = g_file_get_basename (files[i]); 105 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
106 lab = gtk_label_new (filename); 106
107 gtk_notebook_append_page (GTK_NOTEBOOK (nb), scr, lab); 107 tfe_text_view_set_file (TFE_TEXT_VIEW (tv), g_file_dup (files[i]));
108 nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr); 108 gtk_text_buffer_set_text (tb, contents, length);
109 g_object_set (nbp, "tab-expand", TRUE, NULL); 109 g_free (contents);
110 g_free (filename); 110 filename = g_file_get_basename (files[i]);
111 } else { 111 lab = gtk_label_new (filename);
112 filename = g_file_get_path (files[i]); 112 gtk_notebook_append_page (GTK_NOTEBOOK (nb), scr, lab);
113 g_print ("No such file: %s.\n", filename); 113 nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr);
114 g_free (filename); 114 g_object_set (nbp, "tab-expand", TRUE, NULL);
115 } 115 g_free (filename);
116 } 116 } else if ((filename = g_file_get_path (files[i])) != NULL) {
117 if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) { 117 g_print ("No such file: %s.\n", filename);
118 g_signal_connect (win, "close-request", G_CALLBACK (before_close), nb); 118 g_free (filename);
119 gtk_widget_show (win); 119 } else
120 } else 120 g_print ("No valid file is given\n");
121 gtk_window_destroy (GTK_WINDOW (win)); 121 }
122 } 122 if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) {
123 123 g_signal_connect (win, "close-request", G_CALLBACK (before_close), nb);
124 int 124 gtk_widget_show (win);
125 main (int argc, char **argv) { 125 } else
126 GtkApplication *app; 126 gtk_window_destroy (GTK_WINDOW (win));
127 int stat; 127 }
128 128
129 app = gtk_application_new ("com.github.ToshioCP.tfe1", G_APPLICATION_HANDLES_OPEN); 129 int
130 g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL); 130 main (int argc, char **argv) {
131 g_signal_connect (app, "open", G_CALLBACK (on_open), NULL); 131 GtkApplication *app;
132 stat =g_application_run (G_APPLICATION (app), argc, argv); 132 int stat;
133 g_object_unref (app); 133
134 return stat; 134 app = gtk_application_new ("com.github.ToshioCP.tfe1", G_APPLICATION_HANDLES_OPEN);
135 } 135 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
136 136 g_signal_connect (app, "open", G_CALLBACK (app_open), NULL);
137 stat =g_application_run (G_APPLICATION (app), argc, argv);
138 g_object_unref (app);
139 return stat;
140 }
~~~ ~~~
- 102: Sets the pointer to GFile into TfeTextView. - 107: Sets the pointer to GFile into TfeTextView.
`files[i]` is a pointer to GFile structure. `files[i]` is a pointer to GFile structure.
It will be freed by the system. So you need to copy it. It will be freed by the system. So you need to copy it.
`g_file_dup` duplicates the given GFile structure. `g_file_dup` duplicates the given GFile structure.
- 118: Connects "close-request" signal and `before_close` handler. - 123: Connects "close-request" signal and `before_close` handler.
The fourth argument is called user data and it is given to the signal handler. The fourth argument is called user data and it is given to the signal handler.
So, `nb` is given to `before_close` as the second argument. So, `nb` is given to `before_close` as the second argument.
Now compile and run it. Now compile and run it.
Type `./a.out somefile` and make sure that the file is modified. There's a sample file in the directory `tfe`.
Type `./a.out taketori.txt`.
Modify the contents and close the window.
Make sure that the file is modified.
Now we got a very simple editor. Now we got a very simple editor.
It's not smart. It's not smart.

View file

@ -5,7 +5,7 @@ Up: [Readme.md](../Readme.md), Prev: [Section 8](sec8.md), Next: [Section 10](s
## New, open and save button ## New, open and save button
We made the simplest editor in the previous section. We made the simplest editor in the previous section.
It reads the files in `on_open` function at start-up and writes them when closing the window. It reads the files in `app_open` function at start-up and writes them when closing the window.
It works but is not good. It works but is not good.
It is better to make "New", "Open", "Save" and "Close" buttons. It is better to make "New", "Open", "Save" and "Close" buttons.
This section describes how to put those buttons into the window. This section describes how to put those buttons into the window.
@ -14,11 +14,11 @@ Signals and handlers will be explained later.
![Screenshot of the file editor](../image/screenshot_tfe2.png) ![Screenshot of the file editor](../image/screenshot_tfe2.png)
The screenshot above shows the layout. The screenshot above shows the layout.
The function `on_open` in the source code `tfe2.c` is as follows. The function `app_open` in the source code `tfe2.c` is as follows.
~~~C ~~~C
1 static void 1 static void
2 on_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) { 2 app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) {
3 GtkWidget *win; 3 GtkWidget *win;
4 GtkWidget *nb; 4 GtkWidget *nb;
5 GtkWidget *lab; 5 GtkWidget *lab;
@ -92,11 +92,11 @@ The function `on_open` in the source code `tfe2.c` is as follows.
73 nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr); 73 nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr);
74 g_object_set (nbp, "tab-expand", TRUE, NULL); 74 g_object_set (nbp, "tab-expand", TRUE, NULL);
75 g_free (filename); 75 g_free (filename);
76 } else { 76 } else if ((filename = g_file_get_path (files[i])) != NULL) {
77 filename = g_file_get_path (files[i]); 77 g_print ("No such file: %s.\n", filename);
78 g_print ("No such file: %s.\n", filename); 78 g_free (filename);
79 g_free (filename); 79 } else
80 } 80 g_print ("No valid file is given\n");
81 } 81 }
82 if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) { 82 if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) {
83 gtk_widget_show (win); 83 gtk_widget_show (win);
@ -107,25 +107,25 @@ The function `on_open` in the source code `tfe2.c` is as follows.
The point is how to build the window. The point is how to build the window.
- 25-27: Generates GtkApplicationWindow and sets the title and default size. - 25-27: Creates a GtkApplicationWindow instance and sets the title and default size.
- 29-30: Generates GtkBox `boxv`. - 29-30: Creates a GtkBox instance `boxv`.
It is a vertical box and a child of GtkApplicationWindow. It is a vertical box and a child of GtkApplicationWindow.
It has two children. It has two children.
The first child is a horizontal box includes buttons. The first child is a horizontal box.
The second child is GtkNotebook. The second child is a GtkNotebook.
- 32-33: Generates GtkBox `boxh` and appends it to 'boxv' as a first child. - 32-33: Creates a GtkBox instance `boxh` and appends it to `boxv` as a first child.
- 35-40: Generates three dummy labels. - 35-40: Creates three dummy labels.
The labels `dmy1` and `dmy3` has a character width of ten. The labels `dmy1` and `dmy3` has a character width of ten.
The other label `dmy2` has hexpand property which is set to be TRUE. The other label `dmy2` has hexpand property which is set to be TRUE.
This makes the label expands horizontally as long as possible. This makes the label expands horizontally as long as possible.
- 41-44: Generates four buttons. - 41-44: Creates four buttons.
- 46-52: Appends these GtkLabel and GtkButton to `boxh`. - 46-52: Appends these GtkLabel and GtkButton to `boxh`.
- 54-57: Generates GtkNotebook and sets hexpand and vexpand properties TRUE. - 54-57: Creates a GtkNotebook instance and sets hexpand and vexpand properties TRUE.
This makes it expand horizontally and vertically as big as possible. This makes it expand horizontally and vertically as big as possible.
It is appended to `boxv` as the second child. It is appended to `boxv` as the second child.
The number of lines is 33(=57-25+1) to build the widgets. The number of lines is 33(=57-25+1) to build the widgets.
And we needed many variables (boxv, boxh, dmy1 ...). And we needed many variables (`boxv`, `boxh`, `dmy1`, ...).
Most of them aren't necessary except building the widgets. Most of them aren't necessary except building the widgets.
Are there any good solution to reduce these work? Are there any good solution to reduce these work?
@ -199,7 +199,7 @@ First, let's look at the ui file `tfe3.ui` that defines a structure of the widge
59 </interface> 59 </interface>
~~~ ~~~
This is coded with XML structure. The structure of this file is XML.
Constructs beginning with `<` and ending with `>` are called tags. Constructs beginning with `<` and ending with `>` are called tags.
And there are two types of tags, start tag and end tag. And there are two types of tags, start tag and end tag.
For example, `<interface>` is a start tag and `</interface>` is an end tag. For example, `<interface>` is a start tag and `</interface>` is an end tag.
@ -209,15 +209,15 @@ Some tags, for example, object tags can have a class and id attributes in the st
- 1: The first line is XML declaration. - 1: The first line is XML declaration.
It specifies that the version of XML is 1.0 and the encoding is UTF-8. It specifies that the version of XML is 1.0 and the encoding is UTF-8.
Even if the line is left out, GtkBuilder builds objects from the ui file. Even if the line is left out, GtkBuilder builds objects from the ui file.
But ui files must use UTF-8 encoding, or GtkBuilder can't recognize it and fatal error occurs. But ui files must use UTF-8 encoding, or GtkBuilder can't recognize it and a fatal error occurs.
- 3-6: An object with `GtkApplicationWindow` class and `win` id is defined. - 3-6: An object with `GtkApplicationWindow` class and `win` id is defined.
This is the top level window. This is the top level window.
And the three properties of the window are defined. And the three properties of the window are defined.
`title` property is "file editor", `default-width` property is 400 and `default-height` property is 300. `title` property is "file editor", `default-width` property is 600 and `default-height` property is 400.
- 7: child tag means a child of the object above. - 7: child tag means a child of the widget above.
For example, line 7 tells us that GtkBox object which id is "boxv" is a child of `win`. For example, line 7 tells us that GtkBox object which id is "boxv" is a child widget of `win`.
Compare this ui file and the lines 25-57 in the source code of `on_open` function. Compare this ui file and the lines 25-57 in the source code of `app_open` function.
Those two describe the same structure of widgets. Those two describe the same structure of widgets.
You can check the ui file with `gtk4-builder-tool`. You can check the ui file with `gtk4-builder-tool`.
@ -227,7 +227,7 @@ If the ui file includes some syntactical error, `gtk4-builder-tool` prints the e
- `gtk4-builder-tool simplify <ui file name>` simplifies the ui file and prints the result. - `gtk4-builder-tool simplify <ui file name>` simplifies the ui file and prints the result.
If `--replace` option is given, it replaces the ui file with the simplified one. If `--replace` option is given, it replaces the ui file with the simplified one.
If the ui file specifies a value of property but it is default, then it will be removed. If the ui file specifies a value of property but it is default, then it will be removed.
Anf some values are simplified. And some values are simplified.
For example, "TRUE"and "FALSE" becomes "1" and "0" respectively. For example, "TRUE"and "FALSE" becomes "1" and "0" respectively.
However, "TRUE" or "FALSE" is better for maintenance. However, "TRUE" or "FALSE" is better for maintenance.
@ -247,7 +247,7 @@ nb = GTK_WIDGET (gtk_builder_get_object (build, "nb"));
~~~ ~~~
The function `gtk_builder_new_from_file` reads the file given as an argument. The function `gtk_builder_new_from_file` reads the file given as an argument.
Then, it builds the widgets and pointers to them, creates GtkBuilder object and put the pointers in it. Then, it builds the widgets and creates GtkBuilder object.
The function `gtk_builder_get_object (build, "win")` returns the pointer to the widget `win`, which is the id in the ui file. The function `gtk_builder_get_object (build, "win")` returns the pointer to the widget `win`, which is the id in the ui file.
All the widgets are connected based on the parent-children relationship described in the ui file. All the widgets are connected based on the parent-children relationship described in the ui file.
We only need `win` and `nb` for the program after this, so we don't need to take out any other widgets. We only need `win` and `nb` for the program after this, so we don't need to take out any other widgets.
@ -314,15 +314,15 @@ $ cd tfe; diff tfe2.c tfe3.c
> app = gtk_application_new ("com.github.ToshioCP.tfe3", G_APPLICATION_HANDLES_OPEN); > app = gtk_application_new ("com.github.ToshioCP.tfe3", G_APPLICATION_HANDLES_OPEN);
~~~ ~~~
`60,103c61,65` means 42 (=103-60+1) lines change to 5 (=65-61+1) lines. `60,103c61,65` means 44 (=103-60+1) lines are changed to 5 (=65-61+1) lines.
Therefore 37 lines are reduced. Therefore, 39 lines are reduced.
Using ui file not only shortens C source files, but also makes the widgets' structure clear. Using ui file not only shortens C source files, but also makes the widgets' structure clear.
Now I'll show you `on_open` function in the C source code `tfe3.c`. Now I'll show you `app_open` function in the C file `tfe3.c`.
~~~C ~~~C
1 static void 1 static void
2 on_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) { 2 app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) {
3 GtkWidget *win; 3 GtkWidget *win;
4 GtkWidget *nb; 4 GtkWidget *nb;
5 GtkWidget *lab; 5 GtkWidget *lab;
@ -358,11 +358,11 @@ Now I'll show you `on_open` function in the C source code `tfe3.c`.
35 nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr); 35 nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr);
36 g_object_set (nbp, "tab-expand", TRUE, NULL); 36 g_object_set (nbp, "tab-expand", TRUE, NULL);
37 g_free (filename); 37 g_free (filename);
38 } else { 38 } else if ((filename = g_file_get_path (files[i])) != NULL) {
39 filename = g_file_get_path (files[i]); 39 g_print ("No such file: %s.\n", filename);
40 g_print ("No such file: %s.\n", filename); 40 g_free (filename);
41 g_free (filename); 41 } else
42 } 42 g_print ("No valid file is given\n");
43 } 43 }
44 if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) { 44 if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) {
45 gtk_widget_show (win); 45 gtk_widget_show (win);
@ -373,12 +373,11 @@ Now I'll show you `on_open` function in the C source code `tfe3.c`.
The whole source code of `tfe3.c` is stored in [src/tfe](https://github.com/ToshioCP/Gtk4-tutorial/tree/main/src/tfe) directory. The whole source code of `tfe3.c` is stored in [src/tfe](https://github.com/ToshioCP/Gtk4-tutorial/tree/main/src/tfe) directory.
If you want to see it, click the link above. If you want to see it, click the link above.
You can also get the source files below.
### Using ui string ### Using ui string
GtkBuilder can build widgets using string. GtkBuilder can build widgets using string.
Use the function gtk\_builder\_new\_from\_string instead of gtk\_builder\_new\_from\_file. Use the function `gtk_builder_new_from_string` instead of `gtk_builder_new_from_file`.
~~~C ~~~C
char *uistring; char *uistring;
@ -406,7 +405,7 @@ The disadvantage is that writing C string is a bit bothersome because of the dou
If you want to use this method, you should write a script that transforms ui file into C-string. If you want to use this method, you should write a script that transforms ui file into C-string.
- Add backslash before each double quote. - Add backslash before each double quote.
- Add double quote at the left and right. - Add double quotes at the left and right of the string in each line.
### Using Gresource ### Using Gresource

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 21 KiB

BIN
image/child.xcf Normal file

Binary file not shown.

View file

@ -7,16 +7,16 @@ Now we go on to rewrite it and make a very simple editor.
Its source file name is tfe1.c (text file editor 1). Its source file name is tfe1.c (text file editor 1).
GtkTextView originally has a feature of multi line editing. GtkTextView originally has a feature of multi line editing.
Therefore, we don't need to rewrite the program from scratch. Therefore, we don't need to write the program from scratch.
We just add two things to the file viewer. We just add two things to the file viewer.
- Static memory is needed to store a pointer to GFile. - Memory to store a pointer to the GFile instance.
- We need to implement file write function. - A function to write the file.
A couple of ways are possible to get memories to keep GFile. A couple of ways are possible to get memories to keep GFile.
- Use global variables. - Use global variables.
- make a child object so that it can extend the memories for the GFile object. - make a child object so that it can extend the instance memory for the GFile object.
Using global variables is easy to implement. Using global variables is easy to implement.
Define a sufficient size array of pointers to GFile. Define a sufficient size array of pointers to GFile.
@ -26,13 +26,13 @@ For example,
GFile *f[20]; GFile *f[20];
~~~ ~~~
And `f[i]` corresponds to i-th GtkNotebookPage. And `f[i]` corresponds to the i-th GtkNotebookPage.
However, there are two problems. However, there are two problems.
One is the size of the array. One is the size of the array.
If a user gives arguments more than that, bad thing may happen. If a user gives arguments more than that (20 in the example above), it is impossible to store all the pointers to the GFile instances.
The other is the difficulty of maintenance of the program. The other is the difficulty of maintenance of the program.
It is a small program so far. It is a small program so far.
However, if you continue developing it, then its size grows bigger and bigger. However, if you continue developing it, then the size of the program grows bigger and bigger.
Generally speaking, the bigger the program size, the more difficult to maintain global variables. Generally speaking, the bigger the program size, the more difficult to maintain global variables.
Making child object is a good idea in terms of maintenance. Making child object is a good idea in terms of maintenance.
@ -41,7 +41,7 @@ What we are thinking about now is "child object".
A child object includes its parent object. A child object includes its parent object.
And a child object derives everything from the parent object. And a child object derives everything from the parent object.
![Child widget of GtkTextView](../image/child.png){width=9.675cm height=4.89cm} ![Child object of GtkTextView](../image/child.png){width=9.675cm height=4.89cm}
We will define TfeTextView as a child object of GtkTextView. We will define TfeTextView as a child object of GtkTextView.
It has everything that GtkTextView has. It has everything that GtkTextView has.
@ -50,8 +50,9 @@ And important thing is that TfeTextView can have a memory to keep a pointer to G
However, to understand the general theory about Gobject is very hard especially for beginners. However, to understand the general theory about Gobject is very hard especially for beginners.
So, I will just show you the way how to write the code and avoid the theoretical side in the next subsection. So, I will just show you the way how to write the code and avoid the theoretical side in the next subsection.
If you want to know about GObject system, refer to [GObject tutorial](https://github.com/ToshioCP/Gobject-tutorial).
## How to define a child widget of GtkTextView ## How to define a child object of GtkTextView
Let's define TfeTextView object which is a child object of GtkTextView. Let's define TfeTextView object which is a child object of GtkTextView.
First, look at the program below. First, look at the program below.
@ -96,6 +97,7 @@ If you are curious about the background theory of this program, It's very good f
Because knowing the theory is very important for you to program GTK applications. Because knowing the theory is very important for you to program GTK applications.
Look at [GObject API reference](https://developer.gnome.org/gobject/stable/). Look at [GObject API reference](https://developer.gnome.org/gobject/stable/).
All you need is described in it. All you need is described in it.
Or, refer to [GObject tutorial](https://github.com/ToshioCP/Gobject-tutorial).
However, it's a tough journey especially for beginners. However, it's a tough journey especially for beginners.
For now, you don't need to know such difficult theory. For now, you don't need to know such difficult theory.
Just remember the instructions below. Just remember the instructions below.
@ -105,8 +107,8 @@ Tfe and TextView.
Tfe is called prefix, namespace or module. Tfe is called prefix, namespace or module.
TextView is called object. TextView is called object.
- There are three patterns. - There are three patterns.
TfeTextView (camel case), tfe\_text\_view (this is used to write functions) and TFE\_TEXT\_VIEW (This is used to write casts). TfeTextView (camel case), tfe\_text\_view (this is used to write functions) and TFE\_TEXT\_VIEW (This is used to cast a pointer to point TfeTextView type).
- First, define TFE\_TYPE\_TEXT\_VIEW as tfe\_text\_view\_get\_type (). - First, define TFE\_TYPE\_TEXT\_VIEW macro as tfe\_text\_view\_get\_type ().
The name is always (prefix)\_TYPE\_(object) and the letters are upper case. The name is always (prefix)\_TYPE\_(object) and the letters are upper case.
And the replacement text is always (prefix)\_(object)\_get\_type () and the letters are lower case. And the replacement text is always (prefix)\_(object)\_get\_type () and the letters are lower case.
- Next, use G\_DECLARE\_FINAL\_TYPE macro. - Next, use G\_DECLARE\_FINAL\_TYPE macro.
@ -116,31 +118,30 @@ The underscore is necessary.
The first member is the parent object. The first member is the parent object.
Notice this is not a pointer but the object itself. Notice this is not a pointer but the object itself.
The second member and after are members of the child object. The second member and after are members of the child object.
TfeTextView structure has a pointer to GFile as a member. TfeTextView structure has a pointer to a GFile instance as a member.
- Use G\_DEFINE\_TYPE macro. - Use G\_DEFINE\_TYPE macro.
The arguments are the child object name in camel case, lower case with underscore and parent object type (prefix)\_TYPE\_(module). The arguments are the child object name in camel case, lower case with underscore and parent object type (prefix)\_TYPE\_(module).
- Define instance init function (tfe\_text\_view\_init). - Define instance init function (tfe\_text\_view\_init).
Usually you don't need to do anything. Usually you don't need to do anything.
- Define class init function (tfe\_text\_view\_class\_init). - Define class init function (tfe\_text\_view\_class\_init).
You don't need to do anything in this widget. You don't need to do anything in this object.
- Write function codes you want to add (tfe\_text\_view\_set\_file and tfe\_text\_view\_get\_file). - Write function codes you want to add (tfe\_text\_view\_set\_file and tfe\_text\_view\_get\_file).
`tv` is a pointer to TfeTextView object instance which is a C-structure declared with the tag \_TfeTextView. `tv` is a pointer to the TfeTextView object instance which is a C-structure declared with the tag \_TfeTextView.
So, the structure has a member `file` as a pointer to GFile. So, the structure has a member `file` as a pointer to a GFile instance.
`tv->file = f` is an assignment of `f` to a member `file` of the structure pointed by `tv`. `tv->file = f` is an assignment of `f` to a member `file` of the structure pointed by `tv`.
This is an example how to use the extended memory in a child widget. This is an example how to use the extended memory in a child widget.
- Write object generation function. - Write a function to create an instance.
Its name is (prefix)\_(object)\_new. Its name is (prefix)\_(object)\_new.
If the parent object function needs parameters, this function also need them. If the parent object function needs parameters, this function also need them.
You sometimes might want to add some parameters. You sometimes might want to add some parameters.
It's your choice. It's your choice.
Use g\_object\_new function to generate the child widget. Use g\_object\_new function to create the instance.
The arguments are (prefix)\_TYPE\_(object), a list to initialize properties and NULL. The arguments are (prefix)\_TYPE\_(object), a list to initialize properties and NULL.
In this code no property needs to be initialized. In this code no property needs to be initialized.
And the return value must be casted to GtkWidget. And the return value is casted to GtkWidget.
This program is not perfect. This program is not perfect.
It has some problems. It has some problems.
But I don't discuss them now.
It will be modified later. It will be modified later.
## Close-request signal ## Close-request signal
@ -148,7 +149,7 @@ It will be modified later.
Imagine that you use this editor. Imagine that you use this editor.
First, you run the editor with arguments. First, you run the editor with arguments.
The arguments are filenames. The arguments are filenames.
The editor reads the files and shows its window with the text of files in it. The editor reads the files and shows the window with the text of files in it.
Then you edit the text. Then you edit the text.
After you finish editing, you exit the editor. After you finish editing, you exit the editor.
The editor updates files just before the window closes. The editor updates files just before the window closes.
@ -156,7 +157,7 @@ The editor updates files just before the window closes.
GtkWindow emits "close-request" signal before it closes. GtkWindow emits "close-request" signal before it closes.
We connect the signal and the handler `before_close`. We connect the signal and the handler `before_close`.
A handler is a C function. A handler is a C function.
When a function is connected to a certain signal, we call it handler. When a function is connected to a certain signal, we call it a handler.
The function `before_close` is invoked when the signal "close-request" is emitted. The function `before_close` is invoked when the signal "close-request" is emitted.
~~~C ~~~C
@ -173,33 +174,38 @@ tfe/tfe1.c before_close
The numbers on the left of items are line numbers in the source code. The numbers on the left of items are line numbers in the source code.
- 13: Gets the number of pages `nb` has. - 15: Gets the number of pages `nb` has.
- 14-23: For loop with regard to the index to each pages. - 16-29: For loop with regard to the index to each pages.
- 15-17: Gets GtkScrolledWindow, TfeTextView and a pointer to GFile. - 17-19: Gets GtkScrolledWindow, TfeTextView and a pointer to GFile.
The pointer was stored when `on_open` handler had run. It will be shown later. The pointer was stored when `app_open` handler had run. It will be shown later.
- 18-20: Gets GtkTextBuffer and contents. `start_iter` and `end_iter` are iterators of the buffer. - 20-22: Gets GtkTextBuffer and contents. `start_iter` and `end_iter` are iterators of the buffer.
I don't want to explain them now because it would take a lot of time. I don't want to explain them now because it would take a lot of time.
Just remember these lines for the present. Just remember these lines for the present.
- 21: Writes the file. - 23-27: Writes the contents to the file.
If it fails, it outputs an error message.
- 28: Frees `contents`.
## Source code of tfe1.c ## Source code of tfe1.c
Now I will show you all the source code of `tfe1`.c. Now I will show you all the source code of `tfe1.c`.
@@@include @@@include
tfe/tfe1.c tfe/tfe1.c
@@@ @@@
- 102: Sets the pointer to GFile into TfeTextView. - 107: Sets the pointer to GFile into TfeTextView.
`files[i]` is a pointer to GFile structure. `files[i]` is a pointer to GFile structure.
It will be freed by the system. So you need to copy it. It will be freed by the system. So you need to copy it.
`g_file_dup` duplicates the given GFile structure. `g_file_dup` duplicates the given GFile structure.
- 118: Connects "close-request" signal and `before_close` handler. - 123: Connects "close-request" signal and `before_close` handler.
The fourth argument is called user data and it is given to the signal handler. The fourth argument is called user data and it is given to the signal handler.
So, `nb` is given to `before_close` as the second argument. So, `nb` is given to `before_close` as the second argument.
Now compile and run it. Now compile and run it.
Type `./a.out somefile` and make sure that the file is modified. There's a sample file in the directory `tfe`.
Type `./a.out taketori.txt`.
Modify the contents and close the window.
Make sure that the file is modified.
Now we got a very simple editor. Now we got a very simple editor.
It's not smart. It's not smart.

View file

@ -3,7 +3,7 @@
## New, open and save button ## New, open and save button
We made the simplest editor in the previous section. We made the simplest editor in the previous section.
It reads the files in `on_open` function at start-up and writes them when closing the window. It reads the files in `app_open` function at start-up and writes them when closing the window.
It works but is not good. It works but is not good.
It is better to make "New", "Open", "Save" and "Close" buttons. It is better to make "New", "Open", "Save" and "Close" buttons.
This section describes how to put those buttons into the window. This section describes how to put those buttons into the window.
@ -12,33 +12,33 @@ Signals and handlers will be explained later.
![Screenshot of the file editor](../image/screenshot_tfe2.png){width=9.3cm height=6.825cm} ![Screenshot of the file editor](../image/screenshot_tfe2.png){width=9.3cm height=6.825cm}
The screenshot above shows the layout. The screenshot above shows the layout.
The function `on_open` in the source code `tfe2.c` is as follows. The function `app_open` in the source code `tfe2.c` is as follows.
@@@include @@@include
tfe/tfe2.c on_open tfe/tfe2.c app_open
@@@ @@@
The point is how to build the window. The point is how to build the window.
- 25-27: Generates GtkApplicationWindow and sets the title and default size. - 25-27: Creates a GtkApplicationWindow instance and sets the title and default size.
- 29-30: Generates GtkBox `boxv`. - 29-30: Creates a GtkBox instance `boxv`.
It is a vertical box and a child of GtkApplicationWindow. It is a vertical box and a child of GtkApplicationWindow.
It has two children. It has two children.
The first child is a horizontal box includes buttons. The first child is a horizontal box.
The second child is GtkNotebook. The second child is a GtkNotebook.
- 32-33: Generates GtkBox `boxh` and appends it to 'boxv' as a first child. - 32-33: Creates a GtkBox instance `boxh` and appends it to `boxv` as a first child.
- 35-40: Generates three dummy labels. - 35-40: Creates three dummy labels.
The labels `dmy1` and `dmy3` has a character width of ten. The labels `dmy1` and `dmy3` has a character width of ten.
The other label `dmy2` has hexpand property which is set to be TRUE. The other label `dmy2` has hexpand property which is set to be TRUE.
This makes the label expands horizontally as long as possible. This makes the label expands horizontally as long as possible.
- 41-44: Generates four buttons. - 41-44: Creates four buttons.
- 46-52: Appends these GtkLabel and GtkButton to `boxh`. - 46-52: Appends these GtkLabel and GtkButton to `boxh`.
- 54-57: Generates GtkNotebook and sets hexpand and vexpand properties TRUE. - 54-57: Creates a GtkNotebook instance and sets hexpand and vexpand properties TRUE.
This makes it expand horizontally and vertically as big as possible. This makes it expand horizontally and vertically as big as possible.
It is appended to `boxv` as the second child. It is appended to `boxv` as the second child.
The number of lines is 33(=57-25+1) to build the widgets. The number of lines is 33(=57-25+1) to build the widgets.
And we needed many variables (boxv, boxh, dmy1 ...). And we needed many variables (`boxv`, `boxh`, `dmy1`, ...).
Most of them aren't necessary except building the widgets. Most of them aren't necessary except building the widgets.
Are there any good solution to reduce these work? Are there any good solution to reduce these work?
@ -54,7 +54,7 @@ First, let's look at the ui file `tfe3.ui` that defines a structure of the widge
tfe/tfe3.ui tfe/tfe3.ui
@@@ @@@
This is coded with XML structure. The structure of this file is XML.
Constructs beginning with `<` and ending with `>` are called tags. Constructs beginning with `<` and ending with `>` are called tags.
And there are two types of tags, start tag and end tag. And there are two types of tags, start tag and end tag.
For example, `<interface>` is a start tag and `</interface>` is an end tag. For example, `<interface>` is a start tag and `</interface>` is an end tag.
@ -64,15 +64,15 @@ Some tags, for example, object tags can have a class and id attributes in the st
- 1: The first line is XML declaration. - 1: The first line is XML declaration.
It specifies that the version of XML is 1.0 and the encoding is UTF-8. It specifies that the version of XML is 1.0 and the encoding is UTF-8.
Even if the line is left out, GtkBuilder builds objects from the ui file. Even if the line is left out, GtkBuilder builds objects from the ui file.
But ui files must use UTF-8 encoding, or GtkBuilder can't recognize it and fatal error occurs. But ui files must use UTF-8 encoding, or GtkBuilder can't recognize it and a fatal error occurs.
- 3-6: An object with `GtkApplicationWindow` class and `win` id is defined. - 3-6: An object with `GtkApplicationWindow` class and `win` id is defined.
This is the top level window. This is the top level window.
And the three properties of the window are defined. And the three properties of the window are defined.
`title` property is "file editor", `default-width` property is 400 and `default-height` property is 300. `title` property is "file editor", `default-width` property is 600 and `default-height` property is 400.
- 7: child tag means a child of the object above. - 7: child tag means a child of the widget above.
For example, line 7 tells us that GtkBox object which id is "boxv" is a child of `win`. For example, line 7 tells us that GtkBox object which id is "boxv" is a child widget of `win`.
Compare this ui file and the lines 25-57 in the source code of `on_open` function. Compare this ui file and the lines 25-57 in the source code of `app_open` function.
Those two describe the same structure of widgets. Those two describe the same structure of widgets.
You can check the ui file with `gtk4-builder-tool`. You can check the ui file with `gtk4-builder-tool`.
@ -82,7 +82,7 @@ If the ui file includes some syntactical error, `gtk4-builder-tool` prints the e
- `gtk4-builder-tool simplify <ui file name>` simplifies the ui file and prints the result. - `gtk4-builder-tool simplify <ui file name>` simplifies the ui file and prints the result.
If `--replace` option is given, it replaces the ui file with the simplified one. If `--replace` option is given, it replaces the ui file with the simplified one.
If the ui file specifies a value of property but it is default, then it will be removed. If the ui file specifies a value of property but it is default, then it will be removed.
Anf some values are simplified. And some values are simplified.
For example, "TRUE"and "FALSE" becomes "1" and "0" respectively. For example, "TRUE"and "FALSE" becomes "1" and "0" respectively.
However, "TRUE" or "FALSE" is better for maintenance. However, "TRUE" or "FALSE" is better for maintenance.
@ -102,7 +102,7 @@ nb = GTK_WIDGET (gtk_builder_get_object (build, "nb"));
~~~ ~~~
The function `gtk_builder_new_from_file` reads the file given as an argument. The function `gtk_builder_new_from_file` reads the file given as an argument.
Then, it builds the widgets and pointers to them, creates GtkBuilder object and put the pointers in it. Then, it builds the widgets and creates GtkBuilder object.
The function `gtk_builder_get_object (build, "win")` returns the pointer to the widget `win`, which is the id in the ui file. The function `gtk_builder_get_object (build, "win")` returns the pointer to the widget `win`, which is the id in the ui file.
All the widgets are connected based on the parent-children relationship described in the ui file. All the widgets are connected based on the parent-children relationship described in the ui file.
We only need `win` and `nb` for the program after this, so we don't need to take out any other widgets. We only need `win` and `nb` for the program after this, so we don't need to take out any other widgets.
@ -112,24 +112,23 @@ This reduces lines in the C source file.
cd tfe; diff tfe2.c tfe3.c cd tfe; diff tfe2.c tfe3.c
@@@ @@@
`60,103c61,65` means 42 (=103-60+1) lines change to 5 (=65-61+1) lines. `60,103c61,65` means 44 (=103-60+1) lines are changed to 5 (=65-61+1) lines.
Therefore 37 lines are reduced. Therefore, 39 lines are reduced.
Using ui file not only shortens C source files, but also makes the widgets' structure clear. Using ui file not only shortens C source files, but also makes the widgets' structure clear.
Now I'll show you `on_open` function in the C source code `tfe3.c`. Now I'll show you `app_open` function in the C file `tfe3.c`.
@@@include @@@include
tfe/tfe3.c on_open tfe/tfe3.c app_open
@@@ @@@
The whole source code of `tfe3.c` is stored in [src/tfe](https://github.com/ToshioCP/Gtk4-tutorial/tree/main/src/tfe) directory. The whole source code of `tfe3.c` is stored in [src/tfe](https://github.com/ToshioCP/Gtk4-tutorial/tree/main/src/tfe) directory.
If you want to see it, click the link above. If you want to see it, click the link above.
You can also get the source files below.
### Using ui string ### Using ui string
GtkBuilder can build widgets using string. GtkBuilder can build widgets using string.
Use the function gtk\_builder\_new\_from\_string instead of gtk\_builder\_new\_from\_file. Use the function `gtk_builder_new_from_string` instead of `gtk_builder_new_from_file`.
~~~C ~~~C
char *uistring; char *uistring;
@ -157,7 +156,7 @@ The disadvantage is that writing C string is a bit bothersome because of the dou
If you want to use this method, you should write a script that transforms ui file into C-string. If you want to use this method, you should write a script that transforms ui file into C-string.
- Add backslash before each double quote. - Add backslash before each double quote.
- Add double quote at the left and right. - Add double quotes at the left and right of the string in each line.
### Using Gresource ### Using Gresource

4
src/tfe/taketori.txt Normal file
View file

@ -0,0 +1,4 @@
Once upon a time, there was an old man who was called Taketori-no-Okina. It is a japanese word that means a man whose work is making bamboo baskets.
One day, he went into a mountain and found a shining bamboo. "What a mysterious bamboo it is!," he said. He cut it, then there was a small cute baby girl in it. The girl was shining faintly. He thought this baby girl is a gift from Heaven and took her home.
His wife was surprized at his tale. They were very happy because they had no children.

View file

@ -39,10 +39,12 @@ tfe_text_view_new (void) {
/* ---------- end of the definition of TfeTextView ---------- */ /* ---------- end of the definition of TfeTextView ---------- */
static gboolean static gboolean
before_close (GtkWindow *win, GtkWidget *nb) { before_close (GtkWindow *win, gpointer user_data) {
GtkWidget *nb = GTK_WIDGET (user_data);
GtkWidget *scr; GtkWidget *scr;
GtkWidget *tv; GtkWidget *tv;
GFile *file; GFile *file;
char *pathname;
GtkTextBuffer *tb; GtkTextBuffer *tb;
GtkTextIter start_iter; GtkTextIter start_iter;
GtkTextIter end_iter; GtkTextIter end_iter;
@ -58,19 +60,23 @@ before_close (GtkWindow *win, GtkWidget *nb) {
tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv)); tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter); gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE); contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
if (! g_file_replace_contents (file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, NULL)) if (! g_file_replace_contents (file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, NULL)) {
g_print ("ERROR : Can't save %s.", g_file_get_path (file)); pathname = g_file_get_path (file);
g_print ("ERROR : Can't save %s.", pathname);
g_free (pathname);
}
g_free (contents);
} }
return FALSE; return FALSE;
} }
static void static void
on_activate (GApplication *app, gpointer user_data) { app_activate (GApplication *app, gpointer user_data) {
g_print ("You need to give filenames as arguments.\n"); g_print ("You need to give filenames as arguments.\n");
} }
static void static void
on_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) { app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) {
GtkWidget *win; GtkWidget *win;
GtkWidget *nb; GtkWidget *nb;
GtkWidget *lab; GtkWidget *lab;
@ -85,7 +91,6 @@ on_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer
win = gtk_application_window_new (GTK_APPLICATION (app)); win = gtk_application_window_new (GTK_APPLICATION (app));
gtk_window_set_title (GTK_WINDOW (win), "file editor"); gtk_window_set_title (GTK_WINDOW (win), "file editor");
gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
gtk_window_maximize (GTK_WINDOW (win)); gtk_window_maximize (GTK_WINDOW (win));
nb = gtk_notebook_new (); nb = gtk_notebook_new ();
@ -108,11 +113,11 @@ on_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer
nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr); nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr);
g_object_set (nbp, "tab-expand", TRUE, NULL); g_object_set (nbp, "tab-expand", TRUE, NULL);
g_free (filename); g_free (filename);
} else { } else if ((filename = g_file_get_path (files[i])) != NULL) {
filename = g_file_get_path (files[i]); g_print ("No such file: %s.\n", filename);
g_print ("No such file: %s.\n", filename); g_free (filename);
g_free (filename); } else
} g_print ("No valid file is given\n");
} }
if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) { if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) {
g_signal_connect (win, "close-request", G_CALLBACK (before_close), nb); g_signal_connect (win, "close-request", G_CALLBACK (before_close), nb);
@ -127,10 +132,9 @@ main (int argc, char **argv) {
int stat; int stat;
app = gtk_application_new ("com.github.ToshioCP.tfe1", G_APPLICATION_HANDLES_OPEN); app = gtk_application_new ("com.github.ToshioCP.tfe1", G_APPLICATION_HANDLES_OPEN);
g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL); g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
g_signal_connect (app, "open", G_CALLBACK (on_open), NULL); g_signal_connect (app, "open", G_CALLBACK (app_open), NULL);
stat =g_application_run (G_APPLICATION (app), argc, argv); stat =g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app); g_object_unref (app);
return stat; return stat;
} }

View file

@ -39,12 +39,12 @@ tfe_text_view_new (void) {
/* ---------- end of the definition of TfeTextView ---------- */ /* ---------- end of the definition of TfeTextView ---------- */
static void static void
on_activate (GApplication *app, gpointer user_data) { app_activate (GApplication *app, gpointer user_data) {
g_print ("You need a filename argument.\n"); g_print ("You need a filename argument.\n");
} }
static void static void
on_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) { app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) {
GtkWidget *win; GtkWidget *win;
GtkWidget *nb; GtkWidget *nb;
GtkWidget *lab; GtkWidget *lab;
@ -118,11 +118,11 @@ on_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer
nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr); nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr);
g_object_set (nbp, "tab-expand", TRUE, NULL); g_object_set (nbp, "tab-expand", TRUE, NULL);
g_free (filename); g_free (filename);
} else { } else if ((filename = g_file_get_path (files[i])) != NULL) {
filename = g_file_get_path (files[i]); g_print ("No such file: %s.\n", filename);
g_print ("No such file: %s.\n", filename); g_free (filename);
g_free (filename); } else
} g_print ("No valid file is given\n");
} }
if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) { if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) {
gtk_widget_show (win); gtk_widget_show (win);
@ -136,8 +136,8 @@ main (int argc, char **argv) {
int stat; int stat;
app = gtk_application_new ("com.github.ToshioCP.tfe2", G_APPLICATION_HANDLES_OPEN); app = gtk_application_new ("com.github.ToshioCP.tfe2", G_APPLICATION_HANDLES_OPEN);
g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL); g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
g_signal_connect (app, "open", G_CALLBACK (on_open), NULL); g_signal_connect (app, "open", G_CALLBACK (app_open), NULL);
stat =g_application_run (G_APPLICATION (app), argc, argv); stat =g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app); g_object_unref (app);
return stat; return stat;

View file

@ -39,12 +39,12 @@ tfe_text_view_new (void) {
/* ---------- end of the definition of TfeTextView ---------- */ /* ---------- end of the definition of TfeTextView ---------- */
static void static void
on_activate (GApplication *app, gpointer user_data) { app_activate (GApplication *app, gpointer user_data) {
g_print ("You need a filename argument.\n"); g_print ("You need a filename argument.\n");
} }
static void static void
on_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) { app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) {
GtkWidget *win; GtkWidget *win;
GtkWidget *nb; GtkWidget *nb;
GtkWidget *lab; GtkWidget *lab;
@ -80,11 +80,11 @@ on_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer
nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr); nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr);
g_object_set (nbp, "tab-expand", TRUE, NULL); g_object_set (nbp, "tab-expand", TRUE, NULL);
g_free (filename); g_free (filename);
} else { } else if ((filename = g_file_get_path (files[i])) != NULL) {
filename = g_file_get_path (files[i]); g_print ("No such file: %s.\n", filename);
g_print ("No such file: %s.\n", filename); g_free (filename);
g_free (filename); } else
} g_print ("No valid file is given\n");
} }
if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) { if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) {
gtk_widget_show (win); gtk_widget_show (win);
@ -98,8 +98,8 @@ main (int argc, char **argv) {
int stat; int stat;
app = gtk_application_new ("com.github.ToshioCP.tfe3", G_APPLICATION_HANDLES_OPEN); app = gtk_application_new ("com.github.ToshioCP.tfe3", G_APPLICATION_HANDLES_OPEN);
g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL); g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
g_signal_connect (app, "open", G_CALLBACK (on_open), NULL); g_signal_connect (app, "open", G_CALLBACK (app_open), NULL);
stat =g_application_run (G_APPLICATION (app), argc, argv); stat =g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app); g_object_unref (app);
return stat; return stat;

View file

@ -39,12 +39,12 @@ tfe_text_view_new (void) {
/* ---------- end of the definition of TfeTextView ---------- */ /* ---------- end of the definition of TfeTextView ---------- */
static void static void
on_activate (GApplication *app, gpointer user_data) { app_activate (GApplication *app, gpointer user_data) {
g_print ("You need a filename argument.\n"); g_print ("You need a filename argument.\n");
} }
static void static void
on_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) { app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) {
GtkWidget *win; GtkWidget *win;
GtkWidget *nb; GtkWidget *nb;
GtkWidget *lab; GtkWidget *lab;
@ -80,11 +80,11 @@ on_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer
nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr); nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr);
g_object_set (nbp, "tab-expand", TRUE, NULL); g_object_set (nbp, "tab-expand", TRUE, NULL);
g_free (filename); g_free (filename);
} else { } else if ((filename = g_file_get_path (files[i])) != NULL) {
filename = g_file_get_path (files[i]); g_print ("No such file: %s.\n", filename);
g_print ("No such file: %s.\n", filename); g_free (filename);
g_free (filename); } else
} g_print ("No valid file is given\n");
} }
if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) { if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) {
gtk_widget_show (win); gtk_widget_show (win);
@ -98,8 +98,8 @@ main (int argc, char **argv) {
int stat; int stat;
app = gtk_application_new ("com.github.ToshioCP.tfe3", G_APPLICATION_HANDLES_OPEN); app = gtk_application_new ("com.github.ToshioCP.tfe3", G_APPLICATION_HANDLES_OPEN);
g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL); g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
g_signal_connect (app, "open", G_CALLBACK (on_open), NULL); g_signal_connect (app, "open", G_CALLBACK (app_open), NULL);
stat =g_application_run (G_APPLICATION (app), argc, argv); stat =g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app); g_object_unref (app);
return stat; return stat;