mirror of
https://github.com/ToshioCP/Gtk4-tutorial.git
synced 2025-01-11 20:03:35 +01:00
Modify section 8 and 9.
This commit is contained in:
parent
8eaf6064a8
commit
cbd454c85b
11 changed files with 313 additions and 285 deletions
314
gfm/sec8.md
314
gfm/sec8.md
|
@ -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).
|
||||
|
||||
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.
|
||||
|
||||
- Static memory is needed to store a pointer to GFile.
|
||||
- We need to implement file write function.
|
||||
- Memory to store a pointer to the GFile instance.
|
||||
- A function to write the file.
|
||||
|
||||
A couple of ways are possible to get memories to keep GFile.
|
||||
|
||||
- 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.
|
||||
Define a sufficient size array of pointers to GFile.
|
||||
|
@ -28,13 +28,13 @@ For example,
|
|||
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.
|
||||
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.
|
||||
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.
|
||||
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
Look at [GObject API reference](https://developer.gnome.org/gobject/stable/).
|
||||
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.
|
||||
For now, you don't need to know such difficult theory.
|
||||
Just remember the instructions below.
|
||||
|
@ -107,8 +109,8 @@ Tfe and TextView.
|
|||
Tfe is called prefix, namespace or module.
|
||||
TextView is called object.
|
||||
- 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).
|
||||
- First, define TFE\_TYPE\_TEXT\_VIEW as tfe\_text\_view\_get\_type ().
|
||||
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 macro as tfe\_text\_view\_get\_type ().
|
||||
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.
|
||||
- Next, use G\_DECLARE\_FINAL\_TYPE macro.
|
||||
|
@ -118,31 +120,30 @@ The underscore is necessary.
|
|||
The first member is the parent object.
|
||||
Notice this is not a pointer but the object itself.
|
||||
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.
|
||||
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).
|
||||
Usually you don't need to do anything.
|
||||
- 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).
|
||||
`tv` is a pointer to 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.
|
||||
`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 a GFile instance.
|
||||
`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.
|
||||
- Write object generation function.
|
||||
- Write a function to create an instance.
|
||||
Its name is (prefix)\_(object)\_new.
|
||||
If the parent object function needs parameters, this function also need them.
|
||||
You sometimes might want to add some parameters.
|
||||
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.
|
||||
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.
|
||||
It has some problems.
|
||||
But I don't discuss them now.
|
||||
It will be modified later.
|
||||
|
||||
## Close-request signal
|
||||
|
@ -150,7 +151,7 @@ It will be modified later.
|
|||
Imagine that you use this editor.
|
||||
First, you run the editor with arguments.
|
||||
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.
|
||||
After you finish editing, you exit the editor.
|
||||
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.
|
||||
We connect the signal and the handler `before_close`.
|
||||
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.
|
||||
|
||||
~~~C
|
||||
|
@ -171,46 +172,54 @@ The program of `before_close` is as follows.
|
|||
|
||||
~~~C
|
||||
1 static gboolean
|
||||
2 before_close (GtkWindow *win, GtkWidget *nb) {
|
||||
3 GtkWidget *scr;
|
||||
4 GtkWidget *tv;
|
||||
5 GFile *file;
|
||||
6 GtkTextBuffer *tb;
|
||||
7 GtkTextIter start_iter;
|
||||
8 GtkTextIter end_iter;
|
||||
9 char *contents;
|
||||
10 unsigned int n;
|
||||
11 unsigned int i;
|
||||
12
|
||||
13 n = gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb));
|
||||
14 for (i = 0; i < n; ++i) {
|
||||
15 scr = gtk_notebook_get_nth_page (GTK_NOTEBOOK (nb), i);
|
||||
16 tv = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (scr));
|
||||
17 file = tfe_text_view_get_file (TFE_TEXT_VIEW (tv));
|
||||
18 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
|
||||
19 gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
|
||||
20 contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
|
||||
21 if (! g_file_replace_contents (file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, NULL))
|
||||
22 g_print ("ERROR : Can't save %s.", g_file_get_path (file));
|
||||
23 }
|
||||
24 return FALSE;
|
||||
25 }
|
||||
2 before_close (GtkWindow *win, gpointer user_data) {
|
||||
3 GtkWidget *nb = GTK_WIDGET (user_data);
|
||||
4 GtkWidget *scr;
|
||||
5 GtkWidget *tv;
|
||||
6 GFile *file;
|
||||
7 char *pathname;
|
||||
8 GtkTextBuffer *tb;
|
||||
9 GtkTextIter start_iter;
|
||||
10 GtkTextIter end_iter;
|
||||
11 char *contents;
|
||||
12 unsigned int n;
|
||||
13 unsigned int i;
|
||||
14
|
||||
15 n = gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb));
|
||||
16 for (i = 0; i < n; ++i) {
|
||||
17 scr = gtk_notebook_get_nth_page (GTK_NOTEBOOK (nb), i);
|
||||
18 tv = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (scr));
|
||||
19 file = tfe_text_view_get_file (TFE_TEXT_VIEW (tv));
|
||||
20 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
|
||||
21 gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
|
||||
22 contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
|
||||
23 if (! g_file_replace_contents (file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, NULL)) {
|
||||
24 pathname = g_file_get_path (file);
|
||||
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.
|
||||
|
||||
- 13: Gets the number of pages `nb` has.
|
||||
- 14-23: For loop with regard to the index to each pages.
|
||||
- 15-17: Gets GtkScrolledWindow, TfeTextView and a pointer to GFile.
|
||||
The pointer was stored when `on_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.
|
||||
- 15: Gets the number of pages `nb` has.
|
||||
- 16-29: For loop with regard to the index to each pages.
|
||||
- 17-19: Gets GtkScrolledWindow, TfeTextView and a pointer to GFile.
|
||||
The pointer was stored when `app_open` handler had run. It will be shown later.
|
||||
- 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.
|
||||
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
|
||||
|
||||
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
|
||||
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 ---------- */
|
||||
40
|
||||
41 static gboolean
|
||||
42 before_close (GtkWindow *win, GtkWidget *nb) {
|
||||
43 GtkWidget *scr;
|
||||
44 GtkWidget *tv;
|
||||
45 GFile *file;
|
||||
46 GtkTextBuffer *tb;
|
||||
47 GtkTextIter start_iter;
|
||||
48 GtkTextIter end_iter;
|
||||
49 char *contents;
|
||||
50 unsigned int n;
|
||||
51 unsigned int i;
|
||||
52
|
||||
53 n = gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb));
|
||||
54 for (i = 0; i < n; ++i) {
|
||||
55 scr = gtk_notebook_get_nth_page (GTK_NOTEBOOK (nb), i);
|
||||
56 tv = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (scr));
|
||||
57 file = tfe_text_view_get_file (TFE_TEXT_VIEW (tv));
|
||||
58 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
|
||||
59 gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
|
||||
60 contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
|
||||
61 if (! g_file_replace_contents (file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, NULL))
|
||||
62 g_print ("ERROR : Can't save %s.", g_file_get_path (file));
|
||||
63 }
|
||||
64 return FALSE;
|
||||
65 }
|
||||
66
|
||||
67 static void
|
||||
68 on_activate (GApplication *app, gpointer user_data) {
|
||||
69 g_print ("You need to give filenames as arguments.\n");
|
||||
70 }
|
||||
71
|
||||
72 static void
|
||||
73 on_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) {
|
||||
74 GtkWidget *win;
|
||||
75 GtkWidget *nb;
|
||||
76 GtkWidget *lab;
|
||||
77 GtkNotebookPage *nbp;
|
||||
78 GtkWidget *scr;
|
||||
79 GtkWidget *tv;
|
||||
80 GtkTextBuffer *tb;
|
||||
81 char *contents;
|
||||
82 gsize length;
|
||||
83 char *filename;
|
||||
84 int i;
|
||||
85
|
||||
86 win = gtk_application_window_new (GTK_APPLICATION (app));
|
||||
87 gtk_window_set_title (GTK_WINDOW (win), "file editor");
|
||||
88 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
|
||||
89 gtk_window_maximize (GTK_WINDOW (win));
|
||||
90
|
||||
91 nb = gtk_notebook_new ();
|
||||
92 gtk_window_set_child (GTK_WINDOW (win), nb);
|
||||
93
|
||||
94 for (i = 0; i < n_files; i++) {
|
||||
95 if (g_file_load_contents (files[i], NULL, &contents, &length, NULL, NULL)) {
|
||||
96 scr = gtk_scrolled_window_new ();
|
||||
97 tv = tfe_text_view_new ();
|
||||
98 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
|
||||
99 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
|
||||
100 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
|
||||
101
|
||||
102 tfe_text_view_set_file (TFE_TEXT_VIEW (tv), g_file_dup (files[i]));
|
||||
103 gtk_text_buffer_set_text (tb, contents, length);
|
||||
104 g_free (contents);
|
||||
105 filename = g_file_get_basename (files[i]);
|
||||
106 lab = gtk_label_new (filename);
|
||||
107 gtk_notebook_append_page (GTK_NOTEBOOK (nb), scr, lab);
|
||||
108 nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr);
|
||||
109 g_object_set (nbp, "tab-expand", TRUE, NULL);
|
||||
110 g_free (filename);
|
||||
111 } else {
|
||||
112 filename = g_file_get_path (files[i]);
|
||||
113 g_print ("No such file: %s.\n", filename);
|
||||
114 g_free (filename);
|
||||
115 }
|
||||
116 }
|
||||
117 if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) {
|
||||
118 g_signal_connect (win, "close-request", G_CALLBACK (before_close), nb);
|
||||
119 gtk_widget_show (win);
|
||||
120 } else
|
||||
121 gtk_window_destroy (GTK_WINDOW (win));
|
||||
122 }
|
||||
123
|
||||
124 int
|
||||
125 main (int argc, char **argv) {
|
||||
126 GtkApplication *app;
|
||||
127 int stat;
|
||||
42 before_close (GtkWindow *win, gpointer user_data) {
|
||||
43 GtkWidget *nb = GTK_WIDGET (user_data);
|
||||
44 GtkWidget *scr;
|
||||
45 GtkWidget *tv;
|
||||
46 GFile *file;
|
||||
47 char *pathname;
|
||||
48 GtkTextBuffer *tb;
|
||||
49 GtkTextIter start_iter;
|
||||
50 GtkTextIter end_iter;
|
||||
51 char *contents;
|
||||
52 unsigned int n;
|
||||
53 unsigned int i;
|
||||
54
|
||||
55 n = gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb));
|
||||
56 for (i = 0; i < n; ++i) {
|
||||
57 scr = gtk_notebook_get_nth_page (GTK_NOTEBOOK (nb), i);
|
||||
58 tv = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (scr));
|
||||
59 file = tfe_text_view_get_file (TFE_TEXT_VIEW (tv));
|
||||
60 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
|
||||
61 gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
|
||||
62 contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
|
||||
63 if (! g_file_replace_contents (file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, NULL)) {
|
||||
64 pathname = g_file_get_path (file);
|
||||
65 g_print ("ERROR : Can't save %s.", pathname);
|
||||
66 g_free (pathname);
|
||||
67 }
|
||||
68 g_free (contents);
|
||||
69 }
|
||||
70 return FALSE;
|
||||
71 }
|
||||
72
|
||||
73 static void
|
||||
74 app_activate (GApplication *app, gpointer user_data) {
|
||||
75 g_print ("You need to give filenames as arguments.\n");
|
||||
76 }
|
||||
77
|
||||
78 static void
|
||||
79 app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) {
|
||||
80 GtkWidget *win;
|
||||
81 GtkWidget *nb;
|
||||
82 GtkWidget *lab;
|
||||
83 GtkNotebookPage *nbp;
|
||||
84 GtkWidget *scr;
|
||||
85 GtkWidget *tv;
|
||||
86 GtkTextBuffer *tb;
|
||||
87 char *contents;
|
||||
88 gsize length;
|
||||
89 char *filename;
|
||||
90 int i;
|
||||
91
|
||||
92 win = gtk_application_window_new (GTK_APPLICATION (app));
|
||||
93 gtk_window_set_title (GTK_WINDOW (win), "file editor");
|
||||
94 gtk_window_maximize (GTK_WINDOW (win));
|
||||
95
|
||||
96 nb = gtk_notebook_new ();
|
||||
97 gtk_window_set_child (GTK_WINDOW (win), nb);
|
||||
98
|
||||
99 for (i = 0; i < n_files; i++) {
|
||||
100 if (g_file_load_contents (files[i], NULL, &contents, &length, NULL, NULL)) {
|
||||
101 scr = gtk_scrolled_window_new ();
|
||||
102 tv = tfe_text_view_new ();
|
||||
103 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
|
||||
104 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
|
||||
105 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
|
||||
106
|
||||
107 tfe_text_view_set_file (TFE_TEXT_VIEW (tv), g_file_dup (files[i]));
|
||||
108 gtk_text_buffer_set_text (tb, contents, length);
|
||||
109 g_free (contents);
|
||||
110 filename = g_file_get_basename (files[i]);
|
||||
111 lab = gtk_label_new (filename);
|
||||
112 gtk_notebook_append_page (GTK_NOTEBOOK (nb), scr, lab);
|
||||
113 nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr);
|
||||
114 g_object_set (nbp, "tab-expand", TRUE, NULL);
|
||||
115 g_free (filename);
|
||||
116 } else if ((filename = g_file_get_path (files[i])) != NULL) {
|
||||
117 g_print ("No such file: %s.\n", filename);
|
||||
118 g_free (filename);
|
||||
119 } else
|
||||
120 g_print ("No valid file is given\n");
|
||||
121 }
|
||||
122 if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) {
|
||||
123 g_signal_connect (win, "close-request", G_CALLBACK (before_close), nb);
|
||||
124 gtk_widget_show (win);
|
||||
125 } else
|
||||
126 gtk_window_destroy (GTK_WINDOW (win));
|
||||
127 }
|
||||
128
|
||||
129 app = gtk_application_new ("com.github.ToshioCP.tfe1", G_APPLICATION_HANDLES_OPEN);
|
||||
130 g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
|
||||
131 g_signal_connect (app, "open", G_CALLBACK (on_open), NULL);
|
||||
132 stat =g_application_run (G_APPLICATION (app), argc, argv);
|
||||
133 g_object_unref (app);
|
||||
134 return stat;
|
||||
135 }
|
||||
136
|
||||
129 int
|
||||
130 main (int argc, char **argv) {
|
||||
131 GtkApplication *app;
|
||||
132 int stat;
|
||||
133
|
||||
134 app = gtk_application_new ("com.github.ToshioCP.tfe1", G_APPLICATION_HANDLES_OPEN);
|
||||
135 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
|
||||
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.
|
||||
It will be freed by the system. So you need to copy it.
|
||||
`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.
|
||||
So, `nb` is given to `before_close` as the second argument.
|
||||
|
||||
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.
|
||||
It's not smart.
|
||||
|
|
73
gfm/sec9.md
73
gfm/sec9.md
|
@ -5,7 +5,7 @@ Up: [Readme.md](../Readme.md), Prev: [Section 8](sec8.md), Next: [Section 10](s
|
|||
## New, open and save button
|
||||
|
||||
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 is better to make "New", "Open", "Save" and "Close" buttons.
|
||||
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)
|
||||
|
||||
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
|
||||
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;
|
||||
4 GtkWidget *nb;
|
||||
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);
|
||||
74 g_object_set (nbp, "tab-expand", TRUE, NULL);
|
||||
75 g_free (filename);
|
||||
76 } else {
|
||||
77 filename = g_file_get_path (files[i]);
|
||||
78 g_print ("No such file: %s.\n", filename);
|
||||
79 g_free (filename);
|
||||
80 }
|
||||
76 } else if ((filename = g_file_get_path (files[i])) != NULL) {
|
||||
77 g_print ("No such file: %s.\n", filename);
|
||||
78 g_free (filename);
|
||||
79 } else
|
||||
80 g_print ("No valid file is given\n");
|
||||
81 }
|
||||
82 if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) {
|
||||
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.
|
||||
|
||||
- 25-27: Generates GtkApplicationWindow and sets the title and default size.
|
||||
- 29-30: Generates GtkBox `boxv`.
|
||||
- 25-27: Creates a GtkApplicationWindow instance and sets the title and default size.
|
||||
- 29-30: Creates a GtkBox instance `boxv`.
|
||||
It is a vertical box and a child of GtkApplicationWindow.
|
||||
It has two children.
|
||||
The first child is a horizontal box includes buttons.
|
||||
The second child is GtkNotebook.
|
||||
- 32-33: Generates GtkBox `boxh` and appends it to 'boxv' as a first child.
|
||||
- 35-40: Generates three dummy labels.
|
||||
The first child is a horizontal box.
|
||||
The second child is a GtkNotebook.
|
||||
- 32-33: Creates a GtkBox instance `boxh` and appends it to `boxv` as a first child.
|
||||
- 35-40: Creates three dummy labels.
|
||||
The labels `dmy1` and `dmy3` has a character width of ten.
|
||||
The other label `dmy2` has hexpand property which is set to be TRUE.
|
||||
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`.
|
||||
- 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.
|
||||
It is appended to `boxv` as the second child.
|
||||
|
||||
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.
|
||||
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>
|
||||
~~~
|
||||
|
||||
This is coded with XML structure.
|
||||
The structure of this file is XML.
|
||||
Constructs beginning with `<` and ending with `>` are called tags.
|
||||
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.
|
||||
|
@ -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.
|
||||
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.
|
||||
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.
|
||||
This is the top level window.
|
||||
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.
|
||||
- 7: child tag means a child of the object above.
|
||||
For example, line 7 tells us that GtkBox object which id is "boxv" is a child of `win`.
|
||||
`title` property is "file editor", `default-width` property is 600 and `default-height` property is 400.
|
||||
- 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 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.
|
||||
|
||||
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.
|
||||
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.
|
||||
Anf some values are simplified.
|
||||
And some values are simplified.
|
||||
For example, "TRUE"and "FALSE" becomes "1" and "0" respectively.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
|
@ -314,15 +314,15 @@ $ cd tfe; diff tfe2.c tfe3.c
|
|||
> 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.
|
||||
Therefore 37 lines are reduced.
|
||||
`60,103c61,65` means 44 (=103-60+1) lines are changed to 5 (=65-61+1) lines.
|
||||
Therefore, 39 lines are reduced.
|
||||
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
|
||||
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;
|
||||
4 GtkWidget *nb;
|
||||
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);
|
||||
36 g_object_set (nbp, "tab-expand", TRUE, NULL);
|
||||
37 g_free (filename);
|
||||
38 } else {
|
||||
39 filename = g_file_get_path (files[i]);
|
||||
40 g_print ("No such file: %s.\n", filename);
|
||||
41 g_free (filename);
|
||||
42 }
|
||||
38 } else if ((filename = g_file_get_path (files[i])) != NULL) {
|
||||
39 g_print ("No such file: %s.\n", filename);
|
||||
40 g_free (filename);
|
||||
41 } else
|
||||
42 g_print ("No valid file is given\n");
|
||||
43 }
|
||||
44 if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) {
|
||||
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.
|
||||
If you want to see it, click the link above.
|
||||
You can also get the source files below.
|
||||
|
||||
### Using ui 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
|
||||
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.
|
||||
|
||||
- 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
|
||||
|
||||
|
|
BIN
image/child.png
BIN
image/child.png
Binary file not shown.
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 21 KiB |
BIN
image/child.xcf
Normal file
BIN
image/child.xcf
Normal file
Binary file not shown.
|
@ -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).
|
||||
|
||||
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.
|
||||
|
||||
- Static memory is needed to store a pointer to GFile.
|
||||
- We need to implement file write function.
|
||||
- Memory to store a pointer to the GFile instance.
|
||||
- A function to write the file.
|
||||
|
||||
A couple of ways are possible to get memories to keep GFile.
|
||||
|
||||
- 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.
|
||||
Define a sufficient size array of pointers to GFile.
|
||||
|
@ -26,13 +26,13 @@ For example,
|
|||
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.
|
||||
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.
|
||||
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.
|
||||
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
Look at [GObject API reference](https://developer.gnome.org/gobject/stable/).
|
||||
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.
|
||||
For now, you don't need to know such difficult theory.
|
||||
Just remember the instructions below.
|
||||
|
@ -105,8 +107,8 @@ Tfe and TextView.
|
|||
Tfe is called prefix, namespace or module.
|
||||
TextView is called object.
|
||||
- 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).
|
||||
- First, define TFE\_TYPE\_TEXT\_VIEW as tfe\_text\_view\_get\_type ().
|
||||
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 macro as tfe\_text\_view\_get\_type ().
|
||||
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.
|
||||
- Next, use G\_DECLARE\_FINAL\_TYPE macro.
|
||||
|
@ -116,31 +118,30 @@ The underscore is necessary.
|
|||
The first member is the parent object.
|
||||
Notice this is not a pointer but the object itself.
|
||||
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.
|
||||
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).
|
||||
Usually you don't need to do anything.
|
||||
- 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).
|
||||
`tv` is a pointer to 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.
|
||||
`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 a GFile instance.
|
||||
`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.
|
||||
- Write object generation function.
|
||||
- Write a function to create an instance.
|
||||
Its name is (prefix)\_(object)\_new.
|
||||
If the parent object function needs parameters, this function also need them.
|
||||
You sometimes might want to add some parameters.
|
||||
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.
|
||||
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.
|
||||
It has some problems.
|
||||
But I don't discuss them now.
|
||||
It will be modified later.
|
||||
|
||||
## Close-request signal
|
||||
|
@ -148,7 +149,7 @@ It will be modified later.
|
|||
Imagine that you use this editor.
|
||||
First, you run the editor with arguments.
|
||||
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.
|
||||
After you finish editing, you exit the editor.
|
||||
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.
|
||||
We connect the signal and the handler `before_close`.
|
||||
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.
|
||||
|
||||
~~~C
|
||||
|
@ -173,33 +174,38 @@ tfe/tfe1.c before_close
|
|||
|
||||
The numbers on the left of items are line numbers in the source code.
|
||||
|
||||
- 13: Gets the number of pages `nb` has.
|
||||
- 14-23: For loop with regard to the index to each pages.
|
||||
- 15-17: Gets GtkScrolledWindow, TfeTextView and a pointer to GFile.
|
||||
The pointer was stored when `on_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.
|
||||
- 15: Gets the number of pages `nb` has.
|
||||
- 16-29: For loop with regard to the index to each pages.
|
||||
- 17-19: Gets GtkScrolledWindow, TfeTextView and a pointer to GFile.
|
||||
The pointer was stored when `app_open` handler had run. It will be shown later.
|
||||
- 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.
|
||||
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
|
||||
|
||||
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
|
||||
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.
|
||||
It will be freed by the system. So you need to copy it.
|
||||
`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.
|
||||
So, `nb` is given to `before_close` as the second argument.
|
||||
|
||||
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.
|
||||
It's not smart.
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
## New, open and save button
|
||||
|
||||
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 is better to make "New", "Open", "Save" and "Close" buttons.
|
||||
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}
|
||||
|
||||
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
|
||||
tfe/tfe2.c on_open
|
||||
tfe/tfe2.c app_open
|
||||
@@@
|
||||
|
||||
The point is how to build the window.
|
||||
|
||||
- 25-27: Generates GtkApplicationWindow and sets the title and default size.
|
||||
- 29-30: Generates GtkBox `boxv`.
|
||||
- 25-27: Creates a GtkApplicationWindow instance and sets the title and default size.
|
||||
- 29-30: Creates a GtkBox instance `boxv`.
|
||||
It is a vertical box and a child of GtkApplicationWindow.
|
||||
It has two children.
|
||||
The first child is a horizontal box includes buttons.
|
||||
The second child is GtkNotebook.
|
||||
- 32-33: Generates GtkBox `boxh` and appends it to 'boxv' as a first child.
|
||||
- 35-40: Generates three dummy labels.
|
||||
The first child is a horizontal box.
|
||||
The second child is a GtkNotebook.
|
||||
- 32-33: Creates a GtkBox instance `boxh` and appends it to `boxv` as a first child.
|
||||
- 35-40: Creates three dummy labels.
|
||||
The labels `dmy1` and `dmy3` has a character width of ten.
|
||||
The other label `dmy2` has hexpand property which is set to be TRUE.
|
||||
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`.
|
||||
- 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.
|
||||
It is appended to `boxv` as the second child.
|
||||
|
||||
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.
|
||||
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
|
||||
@@@
|
||||
|
||||
This is coded with XML structure.
|
||||
The structure of this file is XML.
|
||||
Constructs beginning with `<` and ending with `>` are called tags.
|
||||
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.
|
||||
|
@ -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.
|
||||
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.
|
||||
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.
|
||||
This is the top level window.
|
||||
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.
|
||||
- 7: child tag means a child of the object above.
|
||||
For example, line 7 tells us that GtkBox object which id is "boxv" is a child of `win`.
|
||||
`title` property is "file editor", `default-width` property is 600 and `default-height` property is 400.
|
||||
- 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 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.
|
||||
|
||||
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.
|
||||
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.
|
||||
Anf some values are simplified.
|
||||
And some values are simplified.
|
||||
For example, "TRUE"and "FALSE" becomes "1" and "0" respectively.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
|
@ -112,24 +112,23 @@ This reduces lines in the C source file.
|
|||
cd tfe; diff tfe2.c tfe3.c
|
||||
@@@
|
||||
|
||||
`60,103c61,65` means 42 (=103-60+1) lines change to 5 (=65-61+1) lines.
|
||||
Therefore 37 lines are reduced.
|
||||
`60,103c61,65` means 44 (=103-60+1) lines are changed to 5 (=65-61+1) lines.
|
||||
Therefore, 39 lines are reduced.
|
||||
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
|
||||
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.
|
||||
If you want to see it, click the link above.
|
||||
You can also get the source files below.
|
||||
|
||||
### Using ui 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
|
||||
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.
|
||||
|
||||
- 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
|
||||
|
||||
|
|
4
src/tfe/taketori.txt
Normal file
4
src/tfe/taketori.txt
Normal 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.
|
||||
|
|
@ -39,10 +39,12 @@ tfe_text_view_new (void) {
|
|||
/* ---------- end of the definition of TfeTextView ---------- */
|
||||
|
||||
static gboolean
|
||||
before_close (GtkWindow *win, GtkWidget *nb) {
|
||||
before_close (GtkWindow *win, gpointer user_data) {
|
||||
GtkWidget *nb = GTK_WIDGET (user_data);
|
||||
GtkWidget *scr;
|
||||
GtkWidget *tv;
|
||||
GFile *file;
|
||||
char *pathname;
|
||||
GtkTextBuffer *tb;
|
||||
GtkTextIter start_iter;
|
||||
GtkTextIter end_iter;
|
||||
|
@ -58,19 +60,23 @@ before_close (GtkWindow *win, GtkWidget *nb) {
|
|||
tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
|
||||
gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
|
||||
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))
|
||||
g_print ("ERROR : Can't save %s.", g_file_get_path (file));
|
||||
if (! g_file_replace_contents (file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, NULL)) {
|
||||
pathname = g_file_get_path (file);
|
||||
g_print ("ERROR : Can't save %s.", pathname);
|
||||
g_free (pathname);
|
||||
}
|
||||
g_free (contents);
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
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 *nb;
|
||||
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));
|
||||
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));
|
||||
|
||||
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);
|
||||
g_object_set (nbp, "tab-expand", TRUE, NULL);
|
||||
g_free (filename);
|
||||
} else {
|
||||
filename = g_file_get_path (files[i]);
|
||||
g_print ("No such file: %s.\n", filename);
|
||||
g_free (filename);
|
||||
}
|
||||
} else if ((filename = g_file_get_path (files[i])) != NULL) {
|
||||
g_print ("No such file: %s.\n", filename);
|
||||
g_free (filename);
|
||||
} else
|
||||
g_print ("No valid file is given\n");
|
||||
}
|
||||
if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) {
|
||||
g_signal_connect (win, "close-request", G_CALLBACK (before_close), nb);
|
||||
|
@ -127,10 +132,9 @@ main (int argc, char **argv) {
|
|||
int stat;
|
||||
|
||||
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, "open", G_CALLBACK (on_open), NULL);
|
||||
g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
|
||||
g_signal_connect (app, "open", G_CALLBACK (app_open), NULL);
|
||||
stat =g_application_run (G_APPLICATION (app), argc, argv);
|
||||
g_object_unref (app);
|
||||
return stat;
|
||||
}
|
||||
|
||||
|
|
|
@ -39,12 +39,12 @@ tfe_text_view_new (void) {
|
|||
/* ---------- end of the definition of TfeTextView ---------- */
|
||||
|
||||
static void
|
||||
on_activate (GApplication *app, gpointer user_data) {
|
||||
app_activate (GApplication *app, gpointer user_data) {
|
||||
g_print ("You need a filename argument.\n");
|
||||
}
|
||||
|
||||
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 *nb;
|
||||
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);
|
||||
g_object_set (nbp, "tab-expand", TRUE, NULL);
|
||||
g_free (filename);
|
||||
} else {
|
||||
filename = g_file_get_path (files[i]);
|
||||
g_print ("No such file: %s.\n", filename);
|
||||
g_free (filename);
|
||||
}
|
||||
} else if ((filename = g_file_get_path (files[i])) != NULL) {
|
||||
g_print ("No such file: %s.\n", filename);
|
||||
g_free (filename);
|
||||
} else
|
||||
g_print ("No valid file is given\n");
|
||||
}
|
||||
if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) {
|
||||
gtk_widget_show (win);
|
||||
|
@ -136,8 +136,8 @@ main (int argc, char **argv) {
|
|||
int stat;
|
||||
|
||||
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, "open", G_CALLBACK (on_open), NULL);
|
||||
g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
|
||||
g_signal_connect (app, "open", G_CALLBACK (app_open), NULL);
|
||||
stat =g_application_run (G_APPLICATION (app), argc, argv);
|
||||
g_object_unref (app);
|
||||
return stat;
|
||||
|
|
|
@ -39,12 +39,12 @@ tfe_text_view_new (void) {
|
|||
/* ---------- end of the definition of TfeTextView ---------- */
|
||||
|
||||
static void
|
||||
on_activate (GApplication *app, gpointer user_data) {
|
||||
app_activate (GApplication *app, gpointer user_data) {
|
||||
g_print ("You need a filename argument.\n");
|
||||
}
|
||||
|
||||
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 *nb;
|
||||
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);
|
||||
g_object_set (nbp, "tab-expand", TRUE, NULL);
|
||||
g_free (filename);
|
||||
} else {
|
||||
filename = g_file_get_path (files[i]);
|
||||
g_print ("No such file: %s.\n", filename);
|
||||
g_free (filename);
|
||||
}
|
||||
} else if ((filename = g_file_get_path (files[i])) != NULL) {
|
||||
g_print ("No such file: %s.\n", filename);
|
||||
g_free (filename);
|
||||
} else
|
||||
g_print ("No valid file is given\n");
|
||||
}
|
||||
if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) {
|
||||
gtk_widget_show (win);
|
||||
|
@ -98,8 +98,8 @@ main (int argc, char **argv) {
|
|||
int stat;
|
||||
|
||||
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, "open", G_CALLBACK (on_open), NULL);
|
||||
g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
|
||||
g_signal_connect (app, "open", G_CALLBACK (app_open), NULL);
|
||||
stat =g_application_run (G_APPLICATION (app), argc, argv);
|
||||
g_object_unref (app);
|
||||
return stat;
|
||||
|
|
|
@ -39,12 +39,12 @@ tfe_text_view_new (void) {
|
|||
/* ---------- end of the definition of TfeTextView ---------- */
|
||||
|
||||
static void
|
||||
on_activate (GApplication *app, gpointer user_data) {
|
||||
app_activate (GApplication *app, gpointer user_data) {
|
||||
g_print ("You need a filename argument.\n");
|
||||
}
|
||||
|
||||
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 *nb;
|
||||
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);
|
||||
g_object_set (nbp, "tab-expand", TRUE, NULL);
|
||||
g_free (filename);
|
||||
} else {
|
||||
filename = g_file_get_path (files[i]);
|
||||
g_print ("No such file: %s.\n", filename);
|
||||
g_free (filename);
|
||||
}
|
||||
} else if ((filename = g_file_get_path (files[i])) != NULL) {
|
||||
g_print ("No such file: %s.\n", filename);
|
||||
g_free (filename);
|
||||
} else
|
||||
g_print ("No valid file is given\n");
|
||||
}
|
||||
if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) {
|
||||
gtk_widget_show (win);
|
||||
|
@ -98,8 +98,8 @@ main (int argc, char **argv) {
|
|||
int stat;
|
||||
|
||||
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, "open", G_CALLBACK (on_open), NULL);
|
||||
g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
|
||||
g_signal_connect (app, "open", G_CALLBACK (app_open), NULL);
|
||||
stat =g_application_run (G_APPLICATION (app), argc, argv);
|
||||
g_object_unref (app);
|
||||
return stat;
|
||||
|
|
Loading…
Reference in a new issue