Bug in tfetextview.c fixed. Section 22 and some other sections are updated.

This commit is contained in:
Toshio Sekiya 2021-04-02 22:40:56 +09:00
parent e8b34260c5
commit a896bbd52f
13 changed files with 352 additions and 313 deletions

View file

@ -21,7 +21,7 @@ However, first of all, I'd like to focus on the object TfeTextView.
It is a child object of GtkTextView and has a new member `file` in it. It is a child object of GtkTextView and has a new member `file` in it.
The important thing is to manage the Gfile object pointed by `file`. The important thing is to manage the Gfile object pointed by `file`.
- What is necessary to GFile when generating (or initializing) TfeTextView? - What is necessary to GFile when creating (or initializing) TfeTextView?
- What is necessary to GFile when destructing TfeTextView? - What is necessary to GFile when destructing TfeTextView?
- TfeTextView should read/write a file by itself or not? - TfeTextView should read/write a file by itself or not?
- How it communicates with objects outside? - How it communicates with objects outside?
@ -90,7 +90,7 @@ struct _GtkTextView
In each structure, its parent instance is declared at the top of members. In each structure, its parent instance is declared at the top of members.
So, every ancestors is included in the child instance. So, every ancestors is included in the child instance.
This is very important. This is very important.
It guarantees a child widget to derive all the features from ancestors. It guarantees a child widget to inherit all the features from ancestors.
The structure of `TfeTextView` is like the following diagram. The structure of `TfeTextView` is like the following diagram.
![The structure of the instance TfeTextView](../image/TfeTextView.png) ![The structure of the instance TfeTextView](../image/TfeTextView.png)
@ -109,7 +109,7 @@ The function `tfe_text_view_new` generates a new TfeTextView instance.
When this function is run, the following procedure is gone through. When this function is run, the following procedure is gone through.
1. Initialize GObject part in TfeTextView instance. 1. Initialize GObject (GInitiallyUnowned) part in TfeTextView instance.
2. Initialize GtkWidget part in TfeTextView instance. 2. Initialize GtkWidget part in TfeTextView instance.
3. Initialize GtkTextView part in TfeTextView instance. 3. Initialize GtkTextView part in TfeTextView instance.
4. Initialize TfeTextView part in TfeTextView instance. 4. Initialize TfeTextView part in TfeTextView instance.
@ -131,7 +131,7 @@ This function just initializes `tv->file` to be `NULL`.
## Functions and Classes ## Functions and Classes
In Gtk, all objects derived from GObject have class and instance. In Gtk, all objects derived from GObject have class and instance (except abstract object).
Instance is memories which has a structure defined by C structure declaration as I mentioned in the previous two subsections. Instance is memories which has a structure defined by C structure declaration as I mentioned in the previous two subsections.
Each object can have more than one instance. Each object can have more than one instance.
Those instances have the same structure. Those instances have the same structure.
@ -357,14 +357,14 @@ It is illustrated in the following diagram.
## Destruction of TfeTextView ## Destruction of TfeTextView
Every Object derived from GObject has a reference count. Every Object derived from GObject has a reference count.
If an object A refers an object B, then A keeps a pointer to B in A and at the same time increases the reference count of B by one with the function `g_object_ref (B)`. If an object A refers to an object B, then A keeps a pointer to B in A and at the same time increases the reference count of B by one with the function `g_object_ref (B)`.
If A doesn't need B any longer, then A discards the pointer to B (usually it is done by assigning NULL to the pointer) and decreases the reference count of B by one with the function `g_object_unref (B)`. If A doesn't need B any longer, then A discards the pointer to B (usually it is done by assigning NULL to the pointer) and decreases the reference count of B by one with the function `g_object_unref (B)`.
If two objects A and B refer to C, then the reference count of C is two. If two objects A and B refer to C, then the reference count of C is two.
If A no longer needs C, A discards the pointer to C and decreases the reference count in C by one. If A no longer needs C, A discards the pointer to C and decreases the reference count in C by one.
Now the reference count of C is one. Now the reference count of C is one.
In the same way, if B no longer needs C, B discards the pointer to C and decreases the reference count in C by one. In the same way, if B no longer needs C, B discards the pointer to C and decreases the reference count in C by one.
At this moment, no object refers C and the reference count of C is zero. At this moment, no object refers to C and the reference count of C is zero.
This means C is no longer useful. This means C is no longer useful.
Then C destructs itself and finally the memories allocated to C is freed. Then C destructs itself and finally the memories allocated to C is freed.

View file

@ -39,7 +39,7 @@ C source files `tfeapplication.c` and `tfenotebook.c` include `tfe.h` at the beg
18 tfe_text_view_get_file (TfeTextView *tv); 18 tfe_text_view_get_file (TfeTextView *tv);
19 19
20 void 20 void
21 tfe_text_view_open (TfeTextView *tv, GtkWidget *win); 21 tfe_text_view_open (TfeTextView *tv, GtkWindow *win);
22 22
23 void 23 void
24 tfe_text_view_save (TfeTextView *tv); 24 tfe_text_view_save (TfeTextView *tv);
@ -59,26 +59,26 @@ C source files `tfeapplication.c` and `tfenotebook.c` include `tfe.h` at the beg
- 1,2,35: Thanks to these three lines, the following lines are included only once. - 1,2,35: Thanks to these three lines, the following lines are included only once.
- 4: Includes gtk4 header files. - 4: Includes gtk4 header files.
The header file `gtk4` also has the same mechanism to avoid including it multiple times. The header file `gtk4` also has the same mechanism to avoid including it multiple times.
- 6-7: These two lines define TfeTextView. - 6-7: These two lines define TfeTextView type, its class structure and some useful macros.
- 9-15: A definition of the value of the parameter of "open-response" signal. - 9-15: A definition of the value of the parameter of "open-response" signal.
- 17-33: Declaration of public functions on GtkTextView. - 17-33: Declarations of public functions on TfeTextView.
## Functions to generate TfeTextView object ## Functions to create TfeTextView object
TfeTextView Object is generated by `tfe_text_view_new` or `tfe_text_view_new_with_file`. TfeTextView Object is created with `tfe_text_view_new` or `tfe_text_view_new_with_file`.
~~~C ~~~C
GtkWidget *tfe_text_view_new (void); GtkWidget *tfe_text_view_new (void);
~~~ ~~~
`tfe_text_view_new` just generates a new TfeTextView object and returns the pointer to the new object. `tfe_text_view_new` just creates a new TfeTextView object and returns the pointer to the new object.
~~~C ~~~C
GtkWidget *tfe_text_view_new_with_file (GFile *file); GtkWidget *tfe_text_view_new_with_file (GFile *file);
~~~ ~~~
`tfe_text_view_new_with_file` is given a Gfile object as the argument and it loads the file into the GtkTextBuffer object, then returns the pointer to the new object. `tfe_text_view_new_with_file` is given a Gfile object as the argument and it loads the file into the GtkTextBuffer object, then returns the pointer to the new object.
If an error occurs during the generation process, NULL is returned. If an error occurs during the creation process, NULL is returned.
Each function is defined as follows. Each function is defined as follows.
@ -124,7 +124,7 @@ You need to distinguish programmer's errors and runtime errors.
You shouldn't use this function to find runtime errors. You shouldn't use this function to find runtime errors.
- 10-11: If an error occurs when reading the file, then return NULL. - 10-11: If an error occurs when reading the file, then return NULL.
- 13: Calls the function `tfe_text_view_new`. - 13: Calls the function `tfe_text_view_new`.
The function generates TfeTextView instance and returns the pointer to the instance. The function creates TfeTextView instance and returns the pointer to the instance.
- 14: Gets the pointer to GtkTextBuffer corresponds to `tv`. - 14: Gets the pointer to GtkTextBuffer corresponds to `tv`.
The pointer is assigned to `tb` The pointer is assigned to `tb`
- 15: Assigns the contents read from the file to GtkTextBuffer pointed by `tb`. - 15: Assigns the contents read from the file to GtkTextBuffer pointed by `tb`.
@ -142,7 +142,7 @@ void tfe_text_view_save (TfeTextView *tv)
The function `save` writes the contents in GtkTextBuffer to a file specified by `tv->file`. The function `save` writes the contents in GtkTextBuffer to a file specified by `tv->file`.
If `tv->file` is NULL, then it shows GtkFileChooserDialog and prompts the user to choose a file to save. If `tv->file` is NULL, then it shows GtkFileChooserDialog and prompts the user to choose a file to save.
Then it saves the contents to the file and sets `tv->file` to point the GFile instance of the file. Then it saves the contents to the file and sets `tv->file` to point the GFile of the file.
~~~C ~~~C
void tfe_text_view_saveas (TfeTextView *tv) void tfe_text_view_saveas (TfeTextView *tv)
@ -154,97 +154,131 @@ If an error occurs, it is shown to the user through the message dialog.
The error is managed only in the TfeTextView instance and no information is notified to the caller. The error is managed only in the TfeTextView instance and no information is notified to the caller.
~~~C ~~~C
1 static void 1 static gboolean
2 saveas_dialog_response (GtkWidget *dialog, gint response, TfeTextView *tv) { 2 save_file (GFile *file, GtkTextBuffer *tb, GtkWindow *win) {
3 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv)); 3 GtkTextIter start_iter;
4 GFile *file; 4 GtkTextIter end_iter;
5 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW); 5 gchar *contents;
6 6 GtkWidget *message_dialog;
7 if (response == GTK_RESPONSE_ACCEPT) { 7 GError *err = NULL;
8 file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)); 8
9 if (! G_IS_FILE (file)) 9 /* This function doesn't check G_IS_FILE (file). The caller should check it. */
10 g_warning ("TfeTextView: gtk_file_chooser_get_file returns non GFile object.\n"); 10 gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
11 else { 11 contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
12 save_file(file, tb, GTK_WINDOW (win)); 12 if (g_file_replace_contents (file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, &err)) {
13 if (G_IS_FILE (tv->file)) 13 gtk_text_buffer_set_modified (tb, FALSE);
14 g_object_unref (tv->file); 14 return TRUE;
15 tv->file = file; 15 } else {
16 gtk_text_buffer_set_modified (tb, FALSE); 16 message_dialog = gtk_message_dialog_new (win, GTK_DIALOG_MODAL,
17 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0); 17 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
18 } 18 "%s.\n", err->message);
19 } 19 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
20 gtk_window_destroy (GTK_WINDOW (dialog)); 20 gtk_widget_show (message_dialog);
21 } 21 g_error_free (err);
22 22 return FALSE;
23 void 23 }
24 tfe_text_view_save (TfeTextView *tv) { 24 }
25 g_return_if_fail (TFE_IS_TEXT_VIEW (tv)); 25
26 26 static void
27 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv)); 27 saveas_dialog_response (GtkWidget *dialog, gint response, TfeTextView *tv) {
28 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW); 28 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
29 29 GFile *file;
30 if (! gtk_text_buffer_get_modified (tb)) 30 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
31 return; /* no need to save it */ 31
32 else if (tv->file == NULL) 32 if (response == GTK_RESPONSE_ACCEPT) {
33 tfe_text_view_saveas (tv); 33 file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
34 else if (! G_IS_FILE (tv->file)) 34 if (! G_IS_FILE (file))
35 g_error ("TfeTextView: The pointer in this object isn't NULL nor GFile object.\n"); 35 g_warning ("TfeTextView: gtk_file_chooser_get_file returns non GFile object.\n");
36 else { 36 else if (save_file(file, tb, GTK_WINDOW (win))) {
37 if (save_file (tv->file, tb, GTK_WINDOW (win))) 37 if (G_IS_FILE (tv->file))
38 gtk_text_buffer_set_modified (tb, FALSE); 38 g_object_unref (tv->file);
39 } 39 tv->file = file;
40 } 40 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
41 41 }
42 void 42 }
43 tfe_text_view_saveas (TfeTextView *tv) { 43 gtk_window_destroy (GTK_WINDOW (dialog));
44 g_return_if_fail (TFE_IS_TEXT_VIEW (tv)); 44 }
45 45
46 GtkWidget *dialog; 46 void
47 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW); 47 tfe_text_view_save (TfeTextView *tv) {
48 48 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
49 dialog = gtk_file_chooser_dialog_new ("Save file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_SAVE, 49
50 "Cancel", GTK_RESPONSE_CANCEL, 50 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
51 "Save", GTK_RESPONSE_ACCEPT, 51 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
52 NULL); 52
53 g_signal_connect (dialog, "response", G_CALLBACK (saveas_dialog_response), tv); 53 if (! gtk_text_buffer_get_modified (tb))
54 gtk_widget_show (dialog); 54 return; /* no need to save it */
55 } 55 else if (tv->file == NULL)
56 tfe_text_view_saveas (tv);
57 else if (! G_IS_FILE (tv->file))
58 g_error ("TfeTextView: The pointer in this object isn't NULL nor GFile object.\n");
59 else
60 save_file (tv->file, tb, GTK_WINDOW (win));
61 }
62
63 void
64 tfe_text_view_saveas (TfeTextView *tv) {
65 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
66
67 GtkWidget *dialog;
68 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
69
70 dialog = gtk_file_chooser_dialog_new ("Save file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_SAVE,
71 "Cancel", GTK_RESPONSE_CANCEL,
72 "Save", GTK_RESPONSE_ACCEPT,
73 NULL);
74 g_signal_connect (dialog, "response", G_CALLBACK (saveas_dialog_response), tv);
75 gtk_widget_show (dialog);
76 }
~~~ ~~~
- 20-56: `Tfe_text_view_save` function. - 1-24: `save_file` function.
- 22: If `tv` is not a pointer to TfeTextView, then it logs an error message and immediately returns. This function is called from `saveas_dialog_response` and `tfe_text_view_save`.
This function is similar to `g_return_val_if_fail` function, but no value is returned because `tfe_text_view_save` doesn't return a value. This function saves the contents of the buffer to the file given as an argument.
- 32-33: If the buffer hasn't modified, then it doesn't need to save it. If error happens, it displays an error message.
So the function returns. - 10-11: Gets the text contents from the buffer.
- 34-35: If `tv->file` is NULL, no file has given yet. - 12-14: Saves the contents to the file.
It calls `tfe_text_view_saveas` which prompts a user to select a file or specify a new file to save. If it's been done without error, set the modified flag to be FALSE.
- 37-38: Gets the contents of the GtkTextBuffer and sets `contents` to point it. This means that the buffer is not modified since it has been saved.
- 39-40: Saves the content to the file. And return TRUE to the caller.
If it succeeds, it resets the modified bit in the GtkTextBuffer. - 15-22: If it's failed to save the contents, displays an error message.
- 42-53: If file writing fails, it assigns NULL to `tv->file`. Creates a message dialog with the error message.
Connects the "response" signal to `gtk_window_destroy`, so that the dialog disappears when a user clicked on the button.
Shows the window, frees `err` and returns FLASE to the caller.
- 26-44: `saveas_dialog_response` function.
This is a signal handler for a "response" signal on GtkFileChooserDialog created by `tfe_text_view_saveas` function.
This handler analyzes the response and determines whether to save the contents.
- 32-44: If the response is `GTK_RESPONSE_ACCEPT`, the user has clicked on the `Save` button. So, it tries to save.
- 33-35: Gets the GFile `file` from GtkFileChooserDialog.
If it doesn't point GFile, it outputs an error message to the log.
- 36-42: Otherwise, it calls `save_file` to save the contents to the file.
`tv->file` is changed, but if the old GFile pointed by `tv->file` exists, it is freed in advance.
Emits "change-file" signal. Emits "change-file" signal.
Shows the error message dialog (48-52). - 46-51: `Tfe_text_view_save` function.
Because the handler is `gtk_window_destroy`, the dialog disappears when user clicks on the button in the dialog. - 48: If `tv` is not a pointer to TfeTextView, then it logs an error message and immediately returns.
Frees the GError object. This function is similar to `g_return_val_if_fail` function, but no value is returned because `tfe_text_view_save` doesn't return a value.
- 58-71: `tfe_text_view_saveas` function. - 53-54: If the buffer hasn't modified, then it doesn't need to save it.
So the function returns.
- 55-56: If `tv->file` is NULL, no file has given yet.
It calls `tfe_text_view_saveas` which prompts a user to select a file or specify a new file to save.
- 57-58: If `tv->file` doesn't point GFile, somethig bad has happened.
Logs an error message.
- 59-60: Calls `save_file` and saves the contents to the file.
It is possible that `save_file`fails, but no notification will be given to the caller.
- 63-76: `tfe_text_view_saveas` function.
It shows GtkFileChooserDialog and prompts the user to choose a file. It shows GtkFileChooserDialog and prompts the user to choose a file.
- 65-68: Generates GtkFileChooserDialog. - 70-73: Creates GtkFileChooserDialog.
The title is "Save file". The title is "Save file".
Transient parent of the dialog is `win`, which is the top level window. Transient parent of the dialog is `win`, which is the top level window.
The action is save mode. The action is save mode.
The buttons are Cancel and Save. The buttons are Cancel and Save.
- 69: connects the "response" signal of the dialog and `saveas_dialog_response` handler. - 74: connects the "response" signal of the dialog and `saveas_dialog_response` handler.
- 1-18: `saveas_dialog_response` signal handler.
- 6-16: If the response is `GTK_RESPONSE_ACCEPT`, then it gets a pointer to the GFile object.
Then, it sets `tv->file` to point the GFile.
And turns on the modified bit of the GtkTextBuffer, emits "change-file" signal.
Finally, it calls `tfe_text_view_save` to save the buffer to the file.
![Saveas process](../image/saveas.png) ![Saveas process](../image/saveas.png)
When you use GtkFileChooserDialog, you need to divide the program into two parts. When you use GtkFileChooserDialog, you need to divide the program into two parts.
One is are a function which generates GtkFileChooserDialog and the other is a signal handler. One is a function which creates GtkFileChooserDialog and the other is a signal handler.
The function just generates and shows GtkFileChooserDialog. The function just creates and shows GtkFileChooserDialog.
The rest is done by the handler. The rest is done by the handler.
It gets Gfile from GtkFileChooserDialog and saves the buffer to the file by calling `tfe_text_view_save`. It gets Gfile from GtkFileChooserDialog and saves the buffer to the file by calling `tfe_text_view_save`.
@ -254,15 +288,15 @@ Open function shows GtkFileChooserDialog to users and prompts them to choose a f
Then it reads the file and puts the text to GtkTextBuffer. Then it reads the file and puts the text to GtkTextBuffer.
~~~C ~~~C
void tfe_text_view_open (TfeTextView *tv, GtkWidget *win); void tfe_text_view_open (TfeTextView *tv, GtkWindow *win);
~~~ ~~~
The parameter `win` is the top window. The parameter `win` is the top level window.
It will be a transient parent window of GtkFileChooserDialog when the dialog is generated.. It will be a transient parent window of GtkFileChooserDialog when the dialog is created.
This allows window managers to keep the dialog on top of the parent window, or center the dialog over the parent window. This allows window managers to keep the dialog on top of the parent window, or center the dialog over the parent window.
It is possible to give no parent window to the dialog. It is possible to give no parent window to the dialog.
However, it is encouraged to give a parent window to dialog. However, it is encouraged to give a parent window to dialog.
This function might be called just after `tv` has been generated. This function might be called just after `tv` has been created.
In that case, `tv` has not been incorporated into the widget hierarchy. In that case, `tv` has not been incorporated into the widget hierarchy.
Therefore it is impossible to get the top window from `tv`. Therefore it is impossible to get the top window from `tv`.
That's why the function needs `win` parameter. That's why the function needs `win` parameter.
@ -311,13 +345,13 @@ Otherwise probably bad things will happen.
36 } 36 }
37 37
38 void 38 void
39 tfe_text_view_open (TfeTextView *tv, GtkWidget *win) { 39 tfe_text_view_open (TfeTextView *tv, GtkWindow *win) {
40 g_return_if_fail (TFE_IS_TEXT_VIEW (tv)); 40 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
41 g_return_if_fail (GTK_IS_WINDOW (win)); 41 g_return_if_fail (GTK_IS_WINDOW (win));
42 42
43 GtkWidget *dialog; 43 GtkWidget *dialog;
44 44
45 dialog = gtk_file_chooser_dialog_new ("Open file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_OPEN, 45 dialog = gtk_file_chooser_dialog_new ("Open file", win, GTK_FILE_CHOOSER_ACTION_OPEN,
46 "Cancel", GTK_RESPONSE_CANCEL, 46 "Cancel", GTK_RESPONSE_CANCEL,
47 "Open", GTK_RESPONSE_ACCEPT, 47 "Open", GTK_RESPONSE_ACCEPT,
48 NULL); 48 NULL);
@ -326,30 +360,30 @@ Otherwise probably bad things will happen.
51 } 51 }
~~~ ~~~
- 37-50: `tfe_text_view_open` function. - 38-51: `tfe_text_view_open` function.
- 44-47: Generates GtkFileChooserDialog. - 45-48: Creates GtkFileChooserDialog.
The title is "Open file". The title is "Open file".
Transient parent window is the top window of the application, which is given by the caller. Transient parent window is the top level window of the application, which is given by the caller.
The action is open mode. The action is open mode.
The buttons are Cancel and Open. The buttons are Cancel and Open.
- 48: connects the "response" signal of the dialog and `open_dialog_response` signal handler. - 49: connects the "response" signal of the dialog and `open_dialog_response` signal handler.
- 49: Shows the dialog. - 50: Shows the dialog.
- 1-35: `open_dialog_response` signal handler. - 1-36: `open_dialog_response` signal handler.
- 10-11: If the response from GtkFileChooserDialog is not `GTK_RESPONSE_ACCEPT`, which means the user has clicked on the "Cancel" button or close button, then it emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_CANCEL`. - 10-11: If the response from GtkFileChooserDialog is not `GTK_RESPONSE_ACCEPT`, which means the user has clicked on the "Cancel" button or close button on the header bar, then it emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_CANCEL`.
- 12-13: Gets a pointer to Gfile by `gtk_file_chooser_get_file`. - 12-14: Gets a pointer to Gfile by `gtk_file_chooser_get_file`.
If it is not GFile, maybe an error occurred. If it doesn't point GFile, maybe an error occurred.
Then it emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_ERROR`. Then it emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_ERROR`.
- 14-23: If an error occurs at file reading, then it decreases the reference count of the Gfile, shows a message dialog to report the error to the user and emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_ERROR`. - 15-24: If an error occurs at file reading, then it decreases the reference count of the Gfile, shows a message dialog to report the error to the user and emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_ERROR`.
- 24-33: If the file has successfully read, then the text is inserted to GtkTextBuffer, frees the temporary buffer pointed by `contents` and sets `tv->file` to point the file (no duplication or unref is not necessary). - 25-33: If the file has successfully been read, then the text is inserted to GtkTextBuffer, frees the temporary buffer pointed by `contents` and sets `tv->file` to point the file (no duplication or unref is not necessary).
Then, it emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_SUCCESS` and emits "change-file" signal. Then, it emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_SUCCESS` and emits "change-file" signal.
- 34: closes GtkFileCooserDialog. - 35: closes GtkFileCooserDialog.
Now let's think about the whole process between the other object (caller) and TfeTextView. Now let's think about the whole process between the other object (caller) and TfeTextView.
It is shown in the following diagram and you would think that it is really complicated. It is shown in the following diagram and you would think that it is really complicated.
Because signal is the only way for GtkFileChooserDialog to communicate with others. Because signal is the only way for GtkFileChooserDialog to communicate with others.
In Gtk3, `gtk_dialog_run` function is available. In Gtk3, `gtk_dialog_run` function is available.
It simplifies the process. It simplifies the process.
However, in Gtk4, `gtk_dialog_run`is unavailable any more. However, in Gtk4, `gtk_dialog_run` is unavailable any more.
![Caller and TfeTextView](../image/open.png) ![Caller and TfeTextView](../image/open.png)
@ -362,7 +396,7 @@ However, in Gtk4, `gtk_dialog_run`is unavailable any more.
## Get file function ## Get file function
`gtk_text_view_get_file` is a simple function show as follows. `gtk_text_view_get_file` is a simple function shown as follows.
~~~C ~~~C
1 GFile * 1 GFile *

View file

@ -167,7 +167,7 @@ The return value NULL means that an error has happened.
24 24
25 tv = tfe_text_view_new (); 25 tv = tfe_text_view_new ();
26 g_signal_connect (TFE_TEXT_VIEW (tv), "open-response", G_CALLBACK (open_response), nb); 26 g_signal_connect (TFE_TEXT_VIEW (tv), "open-response", G_CALLBACK (open_response), nb);
27 tfe_text_view_open (TFE_TEXT_VIEW (tv), gtk_widget_get_ancestor (GTK_WIDGET (nb), GTK_TYPE_WINDOW)); 27 tfe_text_view_open (TFE_TEXT_VIEW (tv), GTK_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (nb), GTK_TYPE_WINDOW)));
28 } 28 }
~~~ ~~~

View file

@ -368,7 +368,7 @@ It is a good practice for you to add more features.
109 109
110 tv = tfe_text_view_new (); 110 tv = tfe_text_view_new ();
111 g_signal_connect (TFE_TEXT_VIEW (tv), "open-response", G_CALLBACK (open_response), nb); 111 g_signal_connect (TFE_TEXT_VIEW (tv), "open-response", G_CALLBACK (open_response), nb);
112 tfe_text_view_open (TFE_TEXT_VIEW (tv), gtk_widget_get_ancestor (GTK_WIDGET (nb), GTK_TYPE_WINDOW)); 112 tfe_text_view_open (TFE_TEXT_VIEW (tv), GTK_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (nb), GTK_TYPE_WINDOW)));
113 } 113 }
114 114
115 void 115 void
@ -422,7 +422,7 @@ It is a good practice for you to add more features.
18 tfe_text_view_get_file (TfeTextView *tv); 18 tfe_text_view_get_file (TfeTextView *tv);
19 19
20 void 20 void
21 tfe_text_view_open (TfeTextView *tv, GtkWidget *win); 21 tfe_text_view_open (TfeTextView *tv, GtkWindow *win);
22 22
23 void 23 void
24 tfe_text_view_save (TfeTextView *tv); 24 tfe_text_view_save (TfeTextView *tv);
@ -549,129 +549,125 @@ It is a good practice for you to add more features.
105 file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)); 105 file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
106 if (! G_IS_FILE (file)) 106 if (! G_IS_FILE (file))
107 g_warning ("TfeTextView: gtk_file_chooser_get_file returns non GFile object.\n"); 107 g_warning ("TfeTextView: gtk_file_chooser_get_file returns non GFile object.\n");
108 else { 108 else if (save_file(file, tb, GTK_WINDOW (win))) {
109 save_file(file, tb, GTK_WINDOW (win)); 109 if (G_IS_FILE (tv->file))
110 if (G_IS_FILE (tv->file)) 110 g_object_unref (tv->file);
111 g_object_unref (tv->file); 111 tv->file = file;
112 tv->file = file; 112 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
113 gtk_text_buffer_set_modified (tb, FALSE); 113 }
114 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0); 114 }
115 } 115 gtk_window_destroy (GTK_WINDOW (dialog));
116 } 116 }
117 gtk_window_destroy (GTK_WINDOW (dialog)); 117
118 } 118 void
119 119 tfe_text_view_save (TfeTextView *tv) {
120 void 120 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
121 tfe_text_view_save (TfeTextView *tv) { 121
122 g_return_if_fail (TFE_IS_TEXT_VIEW (tv)); 122 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
123 123 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
124 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv)); 124
125 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW); 125 if (! gtk_text_buffer_get_modified (tb))
126 126 return; /* no need to save it */
127 if (! gtk_text_buffer_get_modified (tb)) 127 else if (tv->file == NULL)
128 return; /* no need to save it */ 128 tfe_text_view_saveas (tv);
129 else if (tv->file == NULL) 129 else if (! G_IS_FILE (tv->file))
130 tfe_text_view_saveas (tv); 130 g_error ("TfeTextView: The pointer in this object isn't NULL nor GFile object.\n");
131 else if (! G_IS_FILE (tv->file)) 131 else
132 g_error ("TfeTextView: The pointer in this object isn't NULL nor GFile object.\n"); 132 save_file (tv->file, tb, GTK_WINDOW (win));
133 else { 133 }
134 if (save_file (tv->file, tb, GTK_WINDOW (win))) 134
135 gtk_text_buffer_set_modified (tb, FALSE); 135 void
136 } 136 tfe_text_view_saveas (TfeTextView *tv) {
137 } 137 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
138 138
139 void 139 GtkWidget *dialog;
140 tfe_text_view_saveas (TfeTextView *tv) { 140 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
141 g_return_if_fail (TFE_IS_TEXT_VIEW (tv)); 141
142 142 dialog = gtk_file_chooser_dialog_new ("Save file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_SAVE,
143 GtkWidget *dialog; 143 "Cancel", GTK_RESPONSE_CANCEL,
144 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW); 144 "Save", GTK_RESPONSE_ACCEPT,
145 145 NULL);
146 dialog = gtk_file_chooser_dialog_new ("Save file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_SAVE, 146 g_signal_connect (dialog, "response", G_CALLBACK (saveas_dialog_response), tv);
147 "Cancel", GTK_RESPONSE_CANCEL, 147 gtk_widget_show (dialog);
148 "Save", GTK_RESPONSE_ACCEPT, 148 }
149 NULL); 149
150 g_signal_connect (dialog, "response", G_CALLBACK (saveas_dialog_response), tv); 150 GtkWidget *
151 gtk_widget_show (dialog); 151 tfe_text_view_new_with_file (GFile *file) {
152 } 152 g_return_val_if_fail (G_IS_FILE (file), NULL);
153 153
154 GtkWidget * 154 GtkWidget *tv;
155 tfe_text_view_new_with_file (GFile *file) { 155 GtkTextBuffer *tb;
156 g_return_val_if_fail (G_IS_FILE (file), NULL); 156 char *contents;
157 157 gsize length;
158 GtkWidget *tv; 158
159 GtkTextBuffer *tb; 159 if (! g_file_load_contents (file, NULL, &contents, &length, NULL, NULL)) /* read error */
160 char *contents; 160 return NULL;
161 gsize length; 161
162 162 tv = tfe_text_view_new();
163 if (! g_file_load_contents (file, NULL, &contents, &length, NULL, NULL)) /* read error */ 163 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
164 return NULL; 164 gtk_text_buffer_set_text (tb, contents, length);
165 165 g_free (contents);
166 tv = tfe_text_view_new(); 166 TFE_TEXT_VIEW (tv)->file = g_file_dup (file);
167 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv)); 167 return tv;
168 gtk_text_buffer_set_text (tb, contents, length); 168 }
169 g_free (contents); 169
170 TFE_TEXT_VIEW (tv)->file = g_file_dup (file); 170 static void
171 return tv; 171 open_dialog_response(GtkWidget *dialog, gint response, TfeTextView *tv) {
172 } 172 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
173 173 GFile *file;
174 static void 174 char *contents;
175 open_dialog_response(GtkWidget *dialog, gint response, TfeTextView *tv) { 175 gsize length;
176 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv)); 176 GtkWidget *message_dialog;
177 GFile *file; 177 GError *err = NULL;
178 char *contents; 178
179 gsize length; 179 if (response != GTK_RESPONSE_ACCEPT)
180 GtkWidget *message_dialog; 180 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_CANCEL);
181 GError *err = NULL; 181 else if (! G_IS_FILE (file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)))) {
182 182 g_warning ("TfeTextView: gtk_file_chooser_get_file returns non GFile object.\n");
183 if (response != GTK_RESPONSE_ACCEPT) 183 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
184 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_CANCEL); 184 } else if (! g_file_load_contents (file, NULL, &contents, &length, NULL, &err)) { /* read error */
185 else if (! G_IS_FILE (file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)))) { 185 if (G_IS_FILE (file))
186 g_warning ("TfeTextView: gtk_file_chooser_get_file returns non GFile object.\n"); 186 g_object_unref (file);
187 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR); 187 message_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL,
188 } else if (! g_file_load_contents (file, NULL, &contents, &length, NULL, &err)) { /* read error */ 188 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
189 if (G_IS_FILE (file)) 189 "%s.\n", err->message);
190 g_object_unref (file); 190 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
191 message_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL, 191 gtk_widget_show (message_dialog);
192 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, 192 g_error_free (err);
193 "%s.\n", err->message); 193 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
194 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL); 194 } else {
195 gtk_widget_show (message_dialog); 195 gtk_text_buffer_set_text (tb, contents, length);
196 g_error_free (err); 196 g_free (contents);
197 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR); 197 if (G_IS_FILE (tv->file))
198 } else { 198 g_object_unref (tv->file);
199 gtk_text_buffer_set_text (tb, contents, length); 199 tv->file = file;
200 g_free (contents); 200 gtk_text_buffer_set_modified (tb, FALSE);
201 if (G_IS_FILE (tv->file)) 201 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS);
202 g_object_unref (tv->file); 202 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
203 tv->file = file; 203 }
204 gtk_text_buffer_set_modified (tb, FALSE); 204 gtk_window_destroy (GTK_WINDOW (dialog));
205 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS); 205 }
206 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0); 206
207 } 207 void
208 gtk_window_destroy (GTK_WINDOW (dialog)); 208 tfe_text_view_open (TfeTextView *tv, GtkWindow *win) {
209 } 209 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
210 210 g_return_if_fail (GTK_IS_WINDOW (win));
211 void 211
212 tfe_text_view_open (TfeTextView *tv, GtkWidget *win) { 212 GtkWidget *dialog;
213 g_return_if_fail (TFE_IS_TEXT_VIEW (tv)); 213
214 g_return_if_fail (GTK_IS_WINDOW (win)); 214 dialog = gtk_file_chooser_dialog_new ("Open file", win, GTK_FILE_CHOOSER_ACTION_OPEN,
215 215 "Cancel", GTK_RESPONSE_CANCEL,
216 GtkWidget *dialog; 216 "Open", GTK_RESPONSE_ACCEPT,
217 217 NULL);
218 dialog = gtk_file_chooser_dialog_new ("Open file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_OPEN, 218 g_signal_connect (dialog, "response", G_CALLBACK (open_dialog_response), tv);
219 "Cancel", GTK_RESPONSE_CANCEL, 219 gtk_widget_show (dialog);
220 "Open", GTK_RESPONSE_ACCEPT, 220 }
221 NULL); 221
222 g_signal_connect (dialog, "response", G_CALLBACK (open_dialog_response), tv); 222 GtkWidget *
223 gtk_widget_show (dialog); 223 tfe_text_view_new (void) {
224 } 224 return GTK_WIDGET (g_object_new (TFE_TYPE_TEXT_VIEW, NULL));
225 225 }
226 GtkWidget * 226
227 tfe_text_view_new (void) {
228 return GTK_WIDGET (g_object_new (TFE_TYPE_TEXT_VIEW, NULL));
229 }
230
~~~ ~~~
## Total number of lines, words and characters ## Total number of lines, words and characters
@ -682,12 +678,12 @@ $ LANG=C wc tfe5/meson.build tfe5/tfeapplication.c tfe5/tfe.gresource.xml tfe5/t
97 301 3159 tfe5/tfeapplication.c 97 301 3159 tfe5/tfeapplication.c
6 9 153 tfe5/tfe.gresource.xml 6 9 153 tfe5/tfe.gresource.xml
4 6 87 tfe5/tfe.h 4 6 87 tfe5/tfe.h
140 373 3580 tfe5/tfenotebook.c 140 374 3593 tfe5/tfenotebook.c
15 21 241 tfe5/tfenotebook.h 15 21 241 tfe5/tfenotebook.h
230 686 8144 tfetextview/tfetextview.c 226 677 8023 tfetextview/tfetextview.c
35 60 701 tfetextview/tfetextview.h 35 60 701 tfetextview/tfetextview.h
61 100 2073 tfe5/tfe.ui 61 100 2073 tfe5/tfe.ui
598 1573 18432 total 594 1565 18324 total
~~~ ~~~

View file

@ -1,4 +1,4 @@
Up: [Readme.md](../Readme.md), Prev: [Section 22](sec22.md) Up: [Readme.md](../Readme.md), Prev: [Section 22](sec22.md), Next: [Section 24](sec24.md)
# Tiny turtle graphics interpreter # Tiny turtle graphics interpreter
@ -1926,4 +1926,4 @@ Lately, lots of source codes are in the internet.
Maybe reading source codes are the most useful for programmers. Maybe reading source codes are the most useful for programmers.
Up: [Readme.md](../Readme.md), Prev: [Section 22](sec22.md) Up: [Readme.md](../Readme.md), Prev: [Section 22](sec22.md), Next: [Section 24](sec24.md)

View file

@ -19,7 +19,7 @@ However, first of all, I'd like to focus on the object TfeTextView.
It is a child object of GtkTextView and has a new member `file` in it. It is a child object of GtkTextView and has a new member `file` in it.
The important thing is to manage the Gfile object pointed by `file`. The important thing is to manage the Gfile object pointed by `file`.
- What is necessary to GFile when generating (or initializing) TfeTextView? - What is necessary to GFile when creating (or initializing) TfeTextView?
- What is necessary to GFile when destructing TfeTextView? - What is necessary to GFile when destructing TfeTextView?
- TfeTextView should read/write a file by itself or not? - TfeTextView should read/write a file by itself or not?
- How it communicates with objects outside? - How it communicates with objects outside?
@ -88,7 +88,7 @@ struct _GtkTextView
In each structure, its parent instance is declared at the top of members. In each structure, its parent instance is declared at the top of members.
So, every ancestors is included in the child instance. So, every ancestors is included in the child instance.
This is very important. This is very important.
It guarantees a child widget to derive all the features from ancestors. It guarantees a child widget to inherit all the features from ancestors.
The structure of `TfeTextView` is like the following diagram. The structure of `TfeTextView` is like the following diagram.
![The structure of the instance TfeTextView](../image/TfeTextView.png){width=14.39cm height=2.16cm} ![The structure of the instance TfeTextView](../image/TfeTextView.png){width=14.39cm height=2.16cm}
@ -104,7 +104,7 @@ tfetextview/tfetextview.c tfe_text_view_new
When this function is run, the following procedure is gone through. When this function is run, the following procedure is gone through.
1. Initialize GObject part in TfeTextView instance. 1. Initialize GObject (GInitiallyUnowned) part in TfeTextView instance.
2. Initialize GtkWidget part in TfeTextView instance. 2. Initialize GtkWidget part in TfeTextView instance.
3. Initialize GtkTextView part in TfeTextView instance. 3. Initialize GtkTextView part in TfeTextView instance.
4. Initialize TfeTextView part in TfeTextView instance. 4. Initialize TfeTextView part in TfeTextView instance.
@ -123,7 +123,7 @@ This function just initializes `tv->file` to be `NULL`.
## Functions and Classes ## Functions and Classes
In Gtk, all objects derived from GObject have class and instance. In Gtk, all objects derived from GObject have class and instance (except abstract object).
Instance is memories which has a structure defined by C structure declaration as I mentioned in the previous two subsections. Instance is memories which has a structure defined by C structure declaration as I mentioned in the previous two subsections.
Each object can have more than one instance. Each object can have more than one instance.
Those instances have the same structure. Those instances have the same structure.
@ -205,14 +205,14 @@ It is illustrated in the following diagram.
## Destruction of TfeTextView ## Destruction of TfeTextView
Every Object derived from GObject has a reference count. Every Object derived from GObject has a reference count.
If an object A refers an object B, then A keeps a pointer to B in A and at the same time increases the reference count of B by one with the function `g_object_ref (B)`. If an object A refers to an object B, then A keeps a pointer to B in A and at the same time increases the reference count of B by one with the function `g_object_ref (B)`.
If A doesn't need B any longer, then A discards the pointer to B (usually it is done by assigning NULL to the pointer) and decreases the reference count of B by one with the function `g_object_unref (B)`. If A doesn't need B any longer, then A discards the pointer to B (usually it is done by assigning NULL to the pointer) and decreases the reference count of B by one with the function `g_object_unref (B)`.
If two objects A and B refer to C, then the reference count of C is two. If two objects A and B refer to C, then the reference count of C is two.
If A no longer needs C, A discards the pointer to C and decreases the reference count in C by one. If A no longer needs C, A discards the pointer to C and decreases the reference count in C by one.
Now the reference count of C is one. Now the reference count of C is one.
In the same way, if B no longer needs C, B discards the pointer to C and decreases the reference count in C by one. In the same way, if B no longer needs C, B discards the pointer to C and decreases the reference count in C by one.
At this moment, no object refers C and the reference count of C is zero. At this moment, no object refers to C and the reference count of C is zero.
This means C is no longer useful. This means C is no longer useful.
Then C destructs itself and finally the memories allocated to C is freed. Then C destructs itself and finally the memories allocated to C is freed.

View file

@ -20,26 +20,26 @@ tfetextview//tfetextview.h
- 1,2,35: Thanks to these three lines, the following lines are included only once. - 1,2,35: Thanks to these three lines, the following lines are included only once.
- 4: Includes gtk4 header files. - 4: Includes gtk4 header files.
The header file `gtk4` also has the same mechanism to avoid including it multiple times. The header file `gtk4` also has the same mechanism to avoid including it multiple times.
- 6-7: These two lines define TfeTextView. - 6-7: These two lines define TfeTextView type, its class structure and some useful macros.
- 9-15: A definition of the value of the parameter of "open-response" signal. - 9-15: A definition of the value of the parameter of "open-response" signal.
- 17-33: Declaration of public functions on GtkTextView. - 17-33: Declarations of public functions on TfeTextView.
## Functions to generate TfeTextView object ## Functions to create TfeTextView object
TfeTextView Object is generated by `tfe_text_view_new` or `tfe_text_view_new_with_file`. TfeTextView Object is created with `tfe_text_view_new` or `tfe_text_view_new_with_file`.
~~~C ~~~C
GtkWidget *tfe_text_view_new (void); GtkWidget *tfe_text_view_new (void);
~~~ ~~~
`tfe_text_view_new` just generates a new TfeTextView object and returns the pointer to the new object. `tfe_text_view_new` just creates a new TfeTextView object and returns the pointer to the new object.
~~~C ~~~C
GtkWidget *tfe_text_view_new_with_file (GFile *file); GtkWidget *tfe_text_view_new_with_file (GFile *file);
~~~ ~~~
`tfe_text_view_new_with_file` is given a Gfile object as the argument and it loads the file into the GtkTextBuffer object, then returns the pointer to the new object. `tfe_text_view_new_with_file` is given a Gfile object as the argument and it loads the file into the GtkTextBuffer object, then returns the pointer to the new object.
If an error occurs during the generation process, NULL is returned. If an error occurs during the creation process, NULL is returned.
Each function is defined as follows. Each function is defined as follows.
@ -62,7 +62,7 @@ You need to distinguish programmer's errors and runtime errors.
You shouldn't use this function to find runtime errors. You shouldn't use this function to find runtime errors.
- 10-11: If an error occurs when reading the file, then return NULL. - 10-11: If an error occurs when reading the file, then return NULL.
- 13: Calls the function `tfe_text_view_new`. - 13: Calls the function `tfe_text_view_new`.
The function generates TfeTextView instance and returns the pointer to the instance. The function creates TfeTextView instance and returns the pointer to the instance.
- 14: Gets the pointer to GtkTextBuffer corresponds to `tv`. - 14: Gets the pointer to GtkTextBuffer corresponds to `tv`.
The pointer is assigned to `tb` The pointer is assigned to `tb`
- 15: Assigns the contents read from the file to GtkTextBuffer pointed by `tb`. - 15: Assigns the contents read from the file to GtkTextBuffer pointed by `tb`.
@ -80,7 +80,7 @@ void tfe_text_view_save (TfeTextView *tv)
The function `save` writes the contents in GtkTextBuffer to a file specified by `tv->file`. The function `save` writes the contents in GtkTextBuffer to a file specified by `tv->file`.
If `tv->file` is NULL, then it shows GtkFileChooserDialog and prompts the user to choose a file to save. If `tv->file` is NULL, then it shows GtkFileChooserDialog and prompts the user to choose a file to save.
Then it saves the contents to the file and sets `tv->file` to point the GFile instance of the file. Then it saves the contents to the file and sets `tv->file` to point the GFile of the file.
~~~C ~~~C
void tfe_text_view_saveas (TfeTextView *tv) void tfe_text_view_saveas (TfeTextView *tv)
@ -92,43 +92,56 @@ If an error occurs, it is shown to the user through the message dialog.
The error is managed only in the TfeTextView instance and no information is notified to the caller. The error is managed only in the TfeTextView instance and no information is notified to the caller.
@@@include @@@include
tfetextview/tfetextview.c saveas_dialog_response tfe_text_view_save tfe_text_view_saveas tfetextview/tfetextview.c save_file saveas_dialog_response tfe_text_view_save tfe_text_view_saveas
@@@ @@@
- 20-56: `Tfe_text_view_save` function. - 1-24: `save_file` function.
- 22: If `tv` is not a pointer to TfeTextView, then it logs an error message and immediately returns. This function is called from `saveas_dialog_response` and `tfe_text_view_save`.
This function is similar to `g_return_val_if_fail` function, but no value is returned because `tfe_text_view_save` doesn't return a value. This function saves the contents of the buffer to the file given as an argument.
- 32-33: If the buffer hasn't modified, then it doesn't need to save it. If error happens, it displays an error message.
So the function returns. - 10-11: Gets the text contents from the buffer.
- 34-35: If `tv->file` is NULL, no file has given yet. - 12-14: Saves the contents to the file.
It calls `tfe_text_view_saveas` which prompts a user to select a file or specify a new file to save. If it's been done without error, set the modified flag to be FALSE.
- 37-38: Gets the contents of the GtkTextBuffer and sets `contents` to point it. This means that the buffer is not modified since it has been saved.
- 39-40: Saves the content to the file. And return TRUE to the caller.
If it succeeds, it resets the modified bit in the GtkTextBuffer. - 15-22: If it's failed to save the contents, displays an error message.
- 42-53: If file writing fails, it assigns NULL to `tv->file`. Creates a message dialog with the error message.
Connects the "response" signal to `gtk_window_destroy`, so that the dialog disappears when a user clicked on the button.
Shows the window, frees `err` and returns FLASE to the caller.
- 26-44: `saveas_dialog_response` function.
This is a signal handler for a "response" signal on GtkFileChooserDialog created by `tfe_text_view_saveas` function.
This handler analyzes the response and determines whether to save the contents.
- 32-44: If the response is `GTK_RESPONSE_ACCEPT`, the user has clicked on the `Save` button. So, it tries to save.
- 33-35: Gets the GFile `file` from GtkFileChooserDialog.
If it doesn't point GFile, it outputs an error message to the log.
- 36-42: Otherwise, it calls `save_file` to save the contents to the file.
`tv->file` is changed, but if the old GFile pointed by `tv->file` exists, it is freed in advance.
Emits "change-file" signal. Emits "change-file" signal.
Shows the error message dialog (48-52). - 46-51: `Tfe_text_view_save` function.
Because the handler is `gtk_window_destroy`, the dialog disappears when user clicks on the button in the dialog. - 48: If `tv` is not a pointer to TfeTextView, then it logs an error message and immediately returns.
Frees the GError object. This function is similar to `g_return_val_if_fail` function, but no value is returned because `tfe_text_view_save` doesn't return a value.
- 58-71: `tfe_text_view_saveas` function. - 53-54: If the buffer hasn't modified, then it doesn't need to save it.
So the function returns.
- 55-56: If `tv->file` is NULL, no file has given yet.
It calls `tfe_text_view_saveas` which prompts a user to select a file or specify a new file to save.
- 57-58: If `tv->file` doesn't point GFile, somethig bad has happened.
Logs an error message.
- 59-60: Calls `save_file` and saves the contents to the file.
It is possible that `save_file`fails, but no notification will be given to the caller.
- 63-76: `tfe_text_view_saveas` function.
It shows GtkFileChooserDialog and prompts the user to choose a file. It shows GtkFileChooserDialog and prompts the user to choose a file.
- 65-68: Generates GtkFileChooserDialog. - 70-73: Creates GtkFileChooserDialog.
The title is "Save file". The title is "Save file".
Transient parent of the dialog is `win`, which is the top level window. Transient parent of the dialog is `win`, which is the top level window.
The action is save mode. The action is save mode.
The buttons are Cancel and Save. The buttons are Cancel and Save.
- 69: connects the "response" signal of the dialog and `saveas_dialog_response` handler. - 74: connects the "response" signal of the dialog and `saveas_dialog_response` handler.
- 1-18: `saveas_dialog_response` signal handler.
- 6-16: If the response is `GTK_RESPONSE_ACCEPT`, then it gets a pointer to the GFile object.
Then, it sets `tv->file` to point the GFile.
And turns on the modified bit of the GtkTextBuffer, emits "change-file" signal.
Finally, it calls `tfe_text_view_save` to save the buffer to the file.
![Saveas process](../image/saveas.png){width=10.7cm height=5.16cm} ![Saveas process](../image/saveas.png){width=10.7cm height=5.16cm}
When you use GtkFileChooserDialog, you need to divide the program into two parts. When you use GtkFileChooserDialog, you need to divide the program into two parts.
One is are a function which generates GtkFileChooserDialog and the other is a signal handler. One is a function which creates GtkFileChooserDialog and the other is a signal handler.
The function just generates and shows GtkFileChooserDialog. The function just creates and shows GtkFileChooserDialog.
The rest is done by the handler. The rest is done by the handler.
It gets Gfile from GtkFileChooserDialog and saves the buffer to the file by calling `tfe_text_view_save`. It gets Gfile from GtkFileChooserDialog and saves the buffer to the file by calling `tfe_text_view_save`.
@ -138,15 +151,15 @@ Open function shows GtkFileChooserDialog to users and prompts them to choose a f
Then it reads the file and puts the text to GtkTextBuffer. Then it reads the file and puts the text to GtkTextBuffer.
~~~C ~~~C
void tfe_text_view_open (TfeTextView *tv, GtkWidget *win); void tfe_text_view_open (TfeTextView *tv, GtkWindow *win);
~~~ ~~~
The parameter `win` is the top window. The parameter `win` is the top level window.
It will be a transient parent window of GtkFileChooserDialog when the dialog is generated.. It will be a transient parent window of GtkFileChooserDialog when the dialog is created.
This allows window managers to keep the dialog on top of the parent window, or center the dialog over the parent window. This allows window managers to keep the dialog on top of the parent window, or center the dialog over the parent window.
It is possible to give no parent window to the dialog. It is possible to give no parent window to the dialog.
However, it is encouraged to give a parent window to dialog. However, it is encouraged to give a parent window to dialog.
This function might be called just after `tv` has been generated. This function might be called just after `tv` has been created.
In that case, `tv` has not been incorporated into the widget hierarchy. In that case, `tv` has not been incorporated into the widget hierarchy.
Therefore it is impossible to get the top window from `tv`. Therefore it is impossible to get the top window from `tv`.
That's why the function needs `win` parameter. That's why the function needs `win` parameter.
@ -160,30 +173,30 @@ Otherwise probably bad things will happen.
tfetextview/tfetextview.c open_dialog_response tfe_text_view_open tfetextview/tfetextview.c open_dialog_response tfe_text_view_open
@@@ @@@
- 37-50: `tfe_text_view_open` function. - 38-51: `tfe_text_view_open` function.
- 44-47: Generates GtkFileChooserDialog. - 45-48: Creates GtkFileChooserDialog.
The title is "Open file". The title is "Open file".
Transient parent window is the top window of the application, which is given by the caller. Transient parent window is the top level window of the application, which is given by the caller.
The action is open mode. The action is open mode.
The buttons are Cancel and Open. The buttons are Cancel and Open.
- 48: connects the "response" signal of the dialog and `open_dialog_response` signal handler. - 49: connects the "response" signal of the dialog and `open_dialog_response` signal handler.
- 49: Shows the dialog. - 50: Shows the dialog.
- 1-35: `open_dialog_response` signal handler. - 1-36: `open_dialog_response` signal handler.
- 10-11: If the response from GtkFileChooserDialog is not `GTK_RESPONSE_ACCEPT`, which means the user has clicked on the "Cancel" button or close button, then it emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_CANCEL`. - 10-11: If the response from GtkFileChooserDialog is not `GTK_RESPONSE_ACCEPT`, which means the user has clicked on the "Cancel" button or close button on the header bar, then it emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_CANCEL`.
- 12-13: Gets a pointer to Gfile by `gtk_file_chooser_get_file`. - 12-14: Gets a pointer to Gfile by `gtk_file_chooser_get_file`.
If it is not GFile, maybe an error occurred. If it doesn't point GFile, maybe an error occurred.
Then it emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_ERROR`. Then it emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_ERROR`.
- 14-23: If an error occurs at file reading, then it decreases the reference count of the Gfile, shows a message dialog to report the error to the user and emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_ERROR`. - 15-24: If an error occurs at file reading, then it decreases the reference count of the Gfile, shows a message dialog to report the error to the user and emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_ERROR`.
- 24-33: If the file has successfully read, then the text is inserted to GtkTextBuffer, frees the temporary buffer pointed by `contents` and sets `tv->file` to point the file (no duplication or unref is not necessary). - 25-33: If the file has successfully been read, then the text is inserted to GtkTextBuffer, frees the temporary buffer pointed by `contents` and sets `tv->file` to point the file (no duplication or unref is not necessary).
Then, it emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_SUCCESS` and emits "change-file" signal. Then, it emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_SUCCESS` and emits "change-file" signal.
- 34: closes GtkFileCooserDialog. - 35: closes GtkFileCooserDialog.
Now let's think about the whole process between the other object (caller) and TfeTextView. Now let's think about the whole process between the other object (caller) and TfeTextView.
It is shown in the following diagram and you would think that it is really complicated. It is shown in the following diagram and you would think that it is really complicated.
Because signal is the only way for GtkFileChooserDialog to communicate with others. Because signal is the only way for GtkFileChooserDialog to communicate with others.
In Gtk3, `gtk_dialog_run` function is available. In Gtk3, `gtk_dialog_run` function is available.
It simplifies the process. It simplifies the process.
However, in Gtk4, `gtk_dialog_run`is unavailable any more. However, in Gtk4, `gtk_dialog_run` is unavailable any more.
![Caller and TfeTextView](../image/open.png){width=12.405cm height=9.225cm} ![Caller and TfeTextView](../image/open.png){width=12.405cm height=9.225cm}
@ -196,7 +209,7 @@ However, in Gtk4, `gtk_dialog_run`is unavailable any more.
## Get file function ## Get file function
`gtk_text_view_get_file` is a simple function show as follows. `gtk_text_view_get_file` is a simple function shown as follows.
@@@include @@@include
tfetextview/tfetextview.c tfe_text_view_get_file tfetextview/tfetextview.c tfe_text_view_get_file

View file

@ -109,7 +109,7 @@ notebook_page_open (GtkNotebook *nb) {
tv = tfe_text_view_new (); tv = tfe_text_view_new ();
g_signal_connect (TFE_TEXT_VIEW (tv), "open-response", G_CALLBACK (open_response), nb); g_signal_connect (TFE_TEXT_VIEW (tv), "open-response", G_CALLBACK (open_response), nb);
tfe_text_view_open (TFE_TEXT_VIEW (tv), gtk_widget_get_ancestor (GTK_WIDGET (nb), GTK_TYPE_WINDOW)); tfe_text_view_open (TFE_TEXT_VIEW (tv), GTK_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (nb), GTK_TYPE_WINDOW)));
} }
void void

View file

@ -179,7 +179,7 @@ notebook_page_open (GtkNotebook *nb) {
tv = tfe_text_view_new (); tv = tfe_text_view_new ();
g_signal_connect (TFE_TEXT_VIEW (tv), "open-response", G_CALLBACK (open_response), nb); g_signal_connect (TFE_TEXT_VIEW (tv), "open-response", G_CALLBACK (open_response), nb);
tfe_text_view_open (TFE_TEXT_VIEW (tv), gtk_widget_get_ancestor (GTK_WIDGET (nb), GTK_TYPE_WINDOW)); tfe_text_view_open (TFE_TEXT_VIEW (tv), GTK_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (nb), GTK_TYPE_WINDOW)));
} }
void void

View file

@ -180,7 +180,7 @@ notebook_page_open (GtkNotebook *nb) {
tv = tfe_text_view_new (); tv = tfe_text_view_new ();
g_signal_connect (TFE_TEXT_VIEW (tv), "open-response", G_CALLBACK (open_response), nb); g_signal_connect (TFE_TEXT_VIEW (tv), "open-response", G_CALLBACK (open_response), nb);
tfe_text_view_open (TFE_TEXT_VIEW (tv), gtk_widget_get_ancestor (GTK_WIDGET (nb), GTK_TYPE_WINDOW)); tfe_text_view_open (TFE_TEXT_VIEW (tv), GTK_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (nb), GTK_TYPE_WINDOW)));
} }
void void

View file

@ -105,12 +105,10 @@ saveas_dialog_response (GtkWidget *dialog, gint response, TfeTextView *tv) {
file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)); file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
if (! G_IS_FILE (file)) if (! G_IS_FILE (file))
g_warning ("TfeTextView: gtk_file_chooser_get_file returns non GFile object.\n"); g_warning ("TfeTextView: gtk_file_chooser_get_file returns non GFile object.\n");
else { else if (save_file(file, tb, GTK_WINDOW (win))) {
save_file(file, tb, GTK_WINDOW (win));
if (G_IS_FILE (tv->file)) if (G_IS_FILE (tv->file))
g_object_unref (tv->file); g_object_unref (tv->file);
tv->file = file; tv->file = file;
gtk_text_buffer_set_modified (tb, FALSE);
g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0); g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
} }
} }
@ -130,10 +128,8 @@ tfe_text_view_save (TfeTextView *tv) {
tfe_text_view_saveas (tv); tfe_text_view_saveas (tv);
else if (! G_IS_FILE (tv->file)) else if (! G_IS_FILE (tv->file))
g_error ("TfeTextView: The pointer in this object isn't NULL nor GFile object.\n"); g_error ("TfeTextView: The pointer in this object isn't NULL nor GFile object.\n");
else { else
if (save_file (tv->file, tb, GTK_WINDOW (win))) save_file (tv->file, tb, GTK_WINDOW (win));
gtk_text_buffer_set_modified (tb, FALSE);
}
} }
void void
@ -209,13 +205,13 @@ open_dialog_response(GtkWidget *dialog, gint response, TfeTextView *tv) {
} }
void void
tfe_text_view_open (TfeTextView *tv, GtkWidget *win) { tfe_text_view_open (TfeTextView *tv, GtkWindow *win) {
g_return_if_fail (TFE_IS_TEXT_VIEW (tv)); g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
g_return_if_fail (GTK_IS_WINDOW (win)); g_return_if_fail (GTK_IS_WINDOW (win));
GtkWidget *dialog; GtkWidget *dialog;
dialog = gtk_file_chooser_dialog_new ("Open file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_OPEN, dialog = gtk_file_chooser_dialog_new ("Open file", win, GTK_FILE_CHOOSER_ACTION_OPEN,
"Cancel", GTK_RESPONSE_CANCEL, "Cancel", GTK_RESPONSE_CANCEL,
"Open", GTK_RESPONSE_ACCEPT, "Open", GTK_RESPONSE_ACCEPT,
NULL); NULL);

View file

@ -18,7 +18,7 @@ GFile *
tfe_text_view_get_file (TfeTextView *tv); tfe_text_view_get_file (TfeTextView *tv);
void void
tfe_text_view_open (TfeTextView *tv, GtkWidget *win); tfe_text_view_open (TfeTextView *tv, GtkWindow *win);
void void
tfe_text_view_save (TfeTextView *tv); tfe_text_view_save (TfeTextView *tv);

View file

@ -36,7 +36,7 @@ run_cb (GtkWidget *btnr) {
void void
open_cb (GtkWidget *btno) { open_cb (GtkWidget *btno) {
tfe_text_view_open (TFE_TEXT_VIEW (tv), win); tfe_text_view_open (TFE_TEXT_VIEW (tv), GTK_WINDOW (win));
} }
void void