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.
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?
- TfeTextView should read/write a file by itself or not?
- 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.
So, every ancestors is included in the child instance.
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 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.
1. Initialize GObject part in TfeTextView instance.
1. Initialize GObject (GInitiallyUnowned) part in TfeTextView instance.
2. Initialize GtkWidget part in TfeTextView instance.
3. Initialize GtkTextView 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
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.
Each object can have more than one instance.
Those instances have the same structure.
@ -357,14 +357,14 @@ It is illustrated in the following diagram.
## Destruction of TfeTextView
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 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.
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.
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.
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);
19
20 void
21 tfe_text_view_open (TfeTextView *tv, GtkWidget *win);
21 tfe_text_view_open (TfeTextView *tv, GtkWindow *win);
22
23 void
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.
- 4: Includes gtk4 header files.
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.
- 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
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
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.
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.
@ -124,7 +124,7 @@ You need to distinguish programmer's errors and 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.
- 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`.
The pointer is assigned to `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`.
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
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.
~~~C
1 static void
2 saveas_dialog_response (GtkWidget *dialog, gint response, TfeTextView *tv) {
3 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
4 GFile *file;
5 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
6
7 if (response == GTK_RESPONSE_ACCEPT) {
8 file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
9 if (! G_IS_FILE (file))
10 g_warning ("TfeTextView: gtk_file_chooser_get_file returns non GFile object.\n");
11 else {
12 save_file(file, tb, GTK_WINDOW (win));
13 if (G_IS_FILE (tv->file))
14 g_object_unref (tv->file);
15 tv->file = file;
16 gtk_text_buffer_set_modified (tb, FALSE);
17 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
18 }
19 }
20 gtk_window_destroy (GTK_WINDOW (dialog));
21 }
22
23 void
24 tfe_text_view_save (TfeTextView *tv) {
25 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
26
27 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
28 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
29
30 if (! gtk_text_buffer_get_modified (tb))
31 return; /* no need to save it */
32 else if (tv->file == NULL)
33 tfe_text_view_saveas (tv);
34 else if (! G_IS_FILE (tv->file))
35 g_error ("TfeTextView: The pointer in this object isn't NULL nor GFile object.\n");
36 else {
37 if (save_file (tv->file, tb, GTK_WINDOW (win)))
38 gtk_text_buffer_set_modified (tb, FALSE);
39 }
40 }
41
42 void
43 tfe_text_view_saveas (TfeTextView *tv) {
44 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
1 static gboolean
2 save_file (GFile *file, GtkTextBuffer *tb, GtkWindow *win) {
3 GtkTextIter start_iter;
4 GtkTextIter end_iter;
5 gchar *contents;
6 GtkWidget *message_dialog;
7 GError *err = NULL;
8
9 /* This function doesn't check G_IS_FILE (file). The caller should check it. */
10 gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
11 contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
12 if (g_file_replace_contents (file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, &err)) {
13 gtk_text_buffer_set_modified (tb, FALSE);
14 return TRUE;
15 } else {
16 message_dialog = gtk_message_dialog_new (win, GTK_DIALOG_MODAL,
17 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
18 "%s.\n", err->message);
19 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
20 gtk_widget_show (message_dialog);
21 g_error_free (err);
22 return FALSE;
23 }
24 }
25
26 static void
27 saveas_dialog_response (GtkWidget *dialog, gint response, TfeTextView *tv) {
28 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
29 GFile *file;
30 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
31
32 if (response == GTK_RESPONSE_ACCEPT) {
33 file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
34 if (! G_IS_FILE (file))
35 g_warning ("TfeTextView: gtk_file_chooser_get_file returns non GFile object.\n");
36 else if (save_file(file, tb, GTK_WINDOW (win))) {
37 if (G_IS_FILE (tv->file))
38 g_object_unref (tv->file);
39 tv->file = file;
40 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
41 }
42 }
43 gtk_window_destroy (GTK_WINDOW (dialog));
44 }
45
46 GtkWidget *dialog;
47 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
48
49 dialog = gtk_file_chooser_dialog_new ("Save file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_SAVE,
50 "Cancel", GTK_RESPONSE_CANCEL,
51 "Save", GTK_RESPONSE_ACCEPT,
52 NULL);
53 g_signal_connect (dialog, "response", G_CALLBACK (saveas_dialog_response), tv);
54 gtk_widget_show (dialog);
55 }
46 void
47 tfe_text_view_save (TfeTextView *tv) {
48 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
49
50 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
51 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
52
53 if (! gtk_text_buffer_get_modified (tb))
54 return; /* no need to save it */
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.
- 22: If `tv` is not a pointer to TfeTextView, then it logs an error message and immediately returns.
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.
- 32-33: If the buffer hasn't modified, then it doesn't need to save it.
So the function returns.
- 34-35: 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.
- 37-38: Gets the contents of the GtkTextBuffer and sets `contents` to point it.
- 39-40: Saves the content to the file.
If it succeeds, it resets the modified bit in the GtkTextBuffer.
- 42-53: If file writing fails, it assigns NULL to `tv->file`.
- 1-24: `save_file` function.
This function is called from `saveas_dialog_response` and `tfe_text_view_save`.
This function saves the contents of the buffer to the file given as an argument.
If error happens, it displays an error message.
- 10-11: Gets the text contents from the buffer.
- 12-14: Saves the contents to the file.
If it's been done without error, set the modified flag to be FALSE.
This means that the buffer is not modified since it has been saved.
And return TRUE to the caller.
- 15-22: If it's failed to save the contents, displays an error message.
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.
Shows the error message dialog (48-52).
Because the handler is `gtk_window_destroy`, the dialog disappears when user clicks on the button in the dialog.
Frees the GError object.
- 58-71: `tfe_text_view_saveas` function.
- 46-51: `Tfe_text_view_save` function.
- 48: If `tv` is not a pointer to TfeTextView, then it logs an error message and immediately returns.
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.
- 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.
- 65-68: Generates GtkFileChooserDialog.
- 70-73: Creates GtkFileChooserDialog.
The title is "Save file".
Transient parent of the dialog is `win`, which is the top level window.
The action is save mode.
The buttons are Cancel and Save.
- 69: 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.
- 74: connects the "response" signal of the dialog and `saveas_dialog_response` handler.
![Saveas process](../image/saveas.png)
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.
The function just generates and shows GtkFileChooserDialog.
One is a function which creates GtkFileChooserDialog and the other is a signal handler.
The function just creates and shows GtkFileChooserDialog.
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`.
@ -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.
~~~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.
It will be a transient parent window of GtkFileChooserDialog when the dialog is generated..
The parameter `win` is the top level window.
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.
It is possible to give no parent window to the 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.
Therefore it is impossible to get the top window from `tv`.
That's why the function needs `win` parameter.
@ -311,13 +345,13 @@ Otherwise probably bad things will happen.
36 }
37
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));
41 g_return_if_fail (GTK_IS_WINDOW (win));
42
43 GtkWidget *dialog;
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,
47 "Open", GTK_RESPONSE_ACCEPT,
48 NULL);
@ -326,30 +360,30 @@ Otherwise probably bad things will happen.
51 }
~~~
- 37-50: `tfe_text_view_open` function.
- 44-47: Generates GtkFileChooserDialog.
- 38-51: `tfe_text_view_open` function.
- 45-48: Creates GtkFileChooserDialog.
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 buttons are Cancel and Open.
- 48: connects the "response" signal of the dialog and `open_dialog_response` signal handler.
- 49: Shows the dialog.
- 1-35: `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`.
- 12-13: Gets a pointer to Gfile by `gtk_file_chooser_get_file`.
If it is not GFile, maybe an error occurred.
- 49: connects the "response" signal of the dialog and `open_dialog_response` signal handler.
- 50: Shows the dialog.
- 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 on the header bar, then it emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_CANCEL`.
- 12-14: Gets a pointer to Gfile by `gtk_file_chooser_get_file`.
If it doesn't point GFile, maybe an error occurred.
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`.
- 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).
- 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`.
- 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.
- 34: closes GtkFileCooserDialog.
- 35: closes GtkFileCooserDialog.
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.
Because signal is the only way for GtkFileChooserDialog to communicate with others.
In Gtk3, `gtk_dialog_run` function is available.
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)
@ -362,7 +396,7 @@ However, in Gtk4, `gtk_dialog_run`is unavailable any more.
## 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
1 GFile *

View file

@ -167,7 +167,7 @@ The return value NULL means that an error has happened.
24
25 tv = tfe_text_view_new ();
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 }
~~~

View file

@ -368,7 +368,7 @@ It is a good practice for you to add more features.
109
110 tv = tfe_text_view_new ();
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 }
114
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);
19
20 void
21 tfe_text_view_open (TfeTextView *tv, GtkWidget *win);
21 tfe_text_view_open (TfeTextView *tv, GtkWindow *win);
22
23 void
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));
106 if (! G_IS_FILE (file))
107 g_warning ("TfeTextView: gtk_file_chooser_get_file returns non GFile object.\n");
108 else {
109 save_file(file, tb, GTK_WINDOW (win));
110 if (G_IS_FILE (tv->file))
111 g_object_unref (tv->file);
112 tv->file = file;
113 gtk_text_buffer_set_modified (tb, FALSE);
114 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
115 }
116 }
117 gtk_window_destroy (GTK_WINDOW (dialog));
118 }
119
120 void
121 tfe_text_view_save (TfeTextView *tv) {
122 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
123
124 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
125 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
126
127 if (! gtk_text_buffer_get_modified (tb))
128 return; /* no need to save it */
129 else if (tv->file == NULL)
130 tfe_text_view_saveas (tv);
131 else if (! G_IS_FILE (tv->file))
132 g_error ("TfeTextView: The pointer in this object isn't NULL nor GFile object.\n");
133 else {
134 if (save_file (tv->file, tb, GTK_WINDOW (win)))
135 gtk_text_buffer_set_modified (tb, FALSE);
136 }
137 }
108 else if (save_file(file, tb, GTK_WINDOW (win))) {
109 if (G_IS_FILE (tv->file))
110 g_object_unref (tv->file);
111 tv->file = file;
112 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
113 }
114 }
115 gtk_window_destroy (GTK_WINDOW (dialog));
116 }
117
118 void
119 tfe_text_view_save (TfeTextView *tv) {
120 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
121
122 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
123 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
124
125 if (! gtk_text_buffer_get_modified (tb))
126 return; /* no need to save it */
127 else if (tv->file == NULL)
128 tfe_text_view_saveas (tv);
129 else if (! G_IS_FILE (tv->file))
130 g_error ("TfeTextView: The pointer in this object isn't NULL nor GFile object.\n");
131 else
132 save_file (tv->file, tb, GTK_WINDOW (win));
133 }
134
135 void
136 tfe_text_view_saveas (TfeTextView *tv) {
137 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
138
139 void
140 tfe_text_view_saveas (TfeTextView *tv) {
141 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
142
143 GtkWidget *dialog;
144 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
145
146 dialog = gtk_file_chooser_dialog_new ("Save file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_SAVE,
147 "Cancel", GTK_RESPONSE_CANCEL,
148 "Save", GTK_RESPONSE_ACCEPT,
149 NULL);
150 g_signal_connect (dialog, "response", G_CALLBACK (saveas_dialog_response), tv);
151 gtk_widget_show (dialog);
152 }
139 GtkWidget *dialog;
140 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
141
142 dialog = gtk_file_chooser_dialog_new ("Save file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_SAVE,
143 "Cancel", GTK_RESPONSE_CANCEL,
144 "Save", GTK_RESPONSE_ACCEPT,
145 NULL);
146 g_signal_connect (dialog, "response", G_CALLBACK (saveas_dialog_response), tv);
147 gtk_widget_show (dialog);
148 }
149
150 GtkWidget *
151 tfe_text_view_new_with_file (GFile *file) {
152 g_return_val_if_fail (G_IS_FILE (file), NULL);
153
154 GtkWidget *
155 tfe_text_view_new_with_file (GFile *file) {
156 g_return_val_if_fail (G_IS_FILE (file), NULL);
157
158 GtkWidget *tv;
159 GtkTextBuffer *tb;
160 char *contents;
161 gsize length;
162
163 if (! g_file_load_contents (file, NULL, &contents, &length, NULL, NULL)) /* read error */
164 return NULL;
165
166 tv = tfe_text_view_new();
167 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
168 gtk_text_buffer_set_text (tb, contents, length);
169 g_free (contents);
170 TFE_TEXT_VIEW (tv)->file = g_file_dup (file);
171 return tv;
172 }
173
174 static void
175 open_dialog_response(GtkWidget *dialog, gint response, TfeTextView *tv) {
176 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
177 GFile *file;
178 char *contents;
179 gsize length;
180 GtkWidget *message_dialog;
181 GError *err = NULL;
182
183 if (response != GTK_RESPONSE_ACCEPT)
184 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_CANCEL);
185 else if (! G_IS_FILE (file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)))) {
186 g_warning ("TfeTextView: gtk_file_chooser_get_file returns non GFile object.\n");
187 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
188 } else if (! g_file_load_contents (file, NULL, &contents, &length, NULL, &err)) { /* read error */
189 if (G_IS_FILE (file))
190 g_object_unref (file);
191 message_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL,
192 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
193 "%s.\n", err->message);
194 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
195 gtk_widget_show (message_dialog);
196 g_error_free (err);
197 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
198 } else {
199 gtk_text_buffer_set_text (tb, contents, length);
200 g_free (contents);
201 if (G_IS_FILE (tv->file))
202 g_object_unref (tv->file);
203 tv->file = file;
204 gtk_text_buffer_set_modified (tb, FALSE);
205 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS);
206 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
207 }
208 gtk_window_destroy (GTK_WINDOW (dialog));
209 }
210
211 void
212 tfe_text_view_open (TfeTextView *tv, GtkWidget *win) {
213 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
214 g_return_if_fail (GTK_IS_WINDOW (win));
215
216 GtkWidget *dialog;
217
218 dialog = gtk_file_chooser_dialog_new ("Open file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_OPEN,
219 "Cancel", GTK_RESPONSE_CANCEL,
220 "Open", GTK_RESPONSE_ACCEPT,
221 NULL);
222 g_signal_connect (dialog, "response", G_CALLBACK (open_dialog_response), tv);
223 gtk_widget_show (dialog);
224 }
225
226 GtkWidget *
227 tfe_text_view_new (void) {
228 return GTK_WIDGET (g_object_new (TFE_TYPE_TEXT_VIEW, NULL));
229 }
230
154 GtkWidget *tv;
155 GtkTextBuffer *tb;
156 char *contents;
157 gsize length;
158
159 if (! g_file_load_contents (file, NULL, &contents, &length, NULL, NULL)) /* read error */
160 return NULL;
161
162 tv = tfe_text_view_new();
163 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
164 gtk_text_buffer_set_text (tb, contents, length);
165 g_free (contents);
166 TFE_TEXT_VIEW (tv)->file = g_file_dup (file);
167 return tv;
168 }
169
170 static void
171 open_dialog_response(GtkWidget *dialog, gint response, TfeTextView *tv) {
172 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
173 GFile *file;
174 char *contents;
175 gsize length;
176 GtkWidget *message_dialog;
177 GError *err = NULL;
178
179 if (response != GTK_RESPONSE_ACCEPT)
180 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_CANCEL);
181 else if (! G_IS_FILE (file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)))) {
182 g_warning ("TfeTextView: gtk_file_chooser_get_file returns non GFile object.\n");
183 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
184 } else if (! g_file_load_contents (file, NULL, &contents, &length, NULL, &err)) { /* read error */
185 if (G_IS_FILE (file))
186 g_object_unref (file);
187 message_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL,
188 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
189 "%s.\n", err->message);
190 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
191 gtk_widget_show (message_dialog);
192 g_error_free (err);
193 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
194 } else {
195 gtk_text_buffer_set_text (tb, contents, length);
196 g_free (contents);
197 if (G_IS_FILE (tv->file))
198 g_object_unref (tv->file);
199 tv->file = file;
200 gtk_text_buffer_set_modified (tb, FALSE);
201 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS);
202 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
203 }
204 gtk_window_destroy (GTK_WINDOW (dialog));
205 }
206
207 void
208 tfe_text_view_open (TfeTextView *tv, GtkWindow *win) {
209 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
210 g_return_if_fail (GTK_IS_WINDOW (win));
211
212 GtkWidget *dialog;
213
214 dialog = gtk_file_chooser_dialog_new ("Open file", win, GTK_FILE_CHOOSER_ACTION_OPEN,
215 "Cancel", GTK_RESPONSE_CANCEL,
216 "Open", GTK_RESPONSE_ACCEPT,
217 NULL);
218 g_signal_connect (dialog, "response", G_CALLBACK (open_dialog_response), tv);
219 gtk_widget_show (dialog);
220 }
221
222 GtkWidget *
223 tfe_text_view_new (void) {
224 return GTK_WIDGET (g_object_new (TFE_TYPE_TEXT_VIEW, NULL));
225 }
226
~~~
## 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
6 9 153 tfe5/tfe.gresource.xml
4 6 87 tfe5/tfe.h
140 373 3580 tfe5/tfenotebook.c
140 374 3593 tfe5/tfenotebook.c
15 21 241 tfe5/tfenotebook.h
230 686 8144 tfetextview/tfetextview.c
226 677 8023 tfetextview/tfetextview.c
35 60 701 tfetextview/tfetextview.h
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
@ -1926,4 +1926,4 @@ Lately, lots of source codes are in the internet.
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.
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?
- TfeTextView should read/write a file by itself or not?
- 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.
So, every ancestors is included in the child instance.
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 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.
1. Initialize GObject part in TfeTextView instance.
1. Initialize GObject (GInitiallyUnowned) part in TfeTextView instance.
2. Initialize GtkWidget part in TfeTextView instance.
3. Initialize GtkTextView 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
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.
Each object can have more than one instance.
Those instances have the same structure.
@ -205,14 +205,14 @@ It is illustrated in the following diagram.
## Destruction of TfeTextView
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 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.
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.
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.
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.
- 4: Includes gtk4 header files.
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.
- 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
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
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.
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.
@ -62,7 +62,7 @@ You need to distinguish programmer's errors and 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.
- 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`.
The pointer is assigned to `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`.
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
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.
@@@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.
- 22: If `tv` is not a pointer to TfeTextView, then it logs an error message and immediately returns.
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.
- 32-33: If the buffer hasn't modified, then it doesn't need to save it.
So the function returns.
- 34-35: 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.
- 37-38: Gets the contents of the GtkTextBuffer and sets `contents` to point it.
- 39-40: Saves the content to the file.
If it succeeds, it resets the modified bit in the GtkTextBuffer.
- 42-53: If file writing fails, it assigns NULL to `tv->file`.
- 1-24: `save_file` function.
This function is called from `saveas_dialog_response` and `tfe_text_view_save`.
This function saves the contents of the buffer to the file given as an argument.
If error happens, it displays an error message.
- 10-11: Gets the text contents from the buffer.
- 12-14: Saves the contents to the file.
If it's been done without error, set the modified flag to be FALSE.
This means that the buffer is not modified since it has been saved.
And return TRUE to the caller.
- 15-22: If it's failed to save the contents, displays an error message.
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.
Shows the error message dialog (48-52).
Because the handler is `gtk_window_destroy`, the dialog disappears when user clicks on the button in the dialog.
Frees the GError object.
- 58-71: `tfe_text_view_saveas` function.
- 46-51: `Tfe_text_view_save` function.
- 48: If `tv` is not a pointer to TfeTextView, then it logs an error message and immediately returns.
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.
- 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.
- 65-68: Generates GtkFileChooserDialog.
- 70-73: Creates GtkFileChooserDialog.
The title is "Save file".
Transient parent of the dialog is `win`, which is the top level window.
The action is save mode.
The buttons are Cancel and Save.
- 69: 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.
- 74: connects the "response" signal of the dialog and `saveas_dialog_response` handler.
![Saveas process](../image/saveas.png){width=10.7cm height=5.16cm}
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.
The function just generates and shows GtkFileChooserDialog.
One is a function which creates GtkFileChooserDialog and the other is a signal handler.
The function just creates and shows GtkFileChooserDialog.
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`.
@ -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.
~~~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.
It will be a transient parent window of GtkFileChooserDialog when the dialog is generated..
The parameter `win` is the top level window.
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.
It is possible to give no parent window to the 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.
Therefore it is impossible to get the top window from `tv`.
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
@@@
- 37-50: `tfe_text_view_open` function.
- 44-47: Generates GtkFileChooserDialog.
- 38-51: `tfe_text_view_open` function.
- 45-48: Creates GtkFileChooserDialog.
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 buttons are Cancel and Open.
- 48: connects the "response" signal of the dialog and `open_dialog_response` signal handler.
- 49: Shows the dialog.
- 1-35: `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`.
- 12-13: Gets a pointer to Gfile by `gtk_file_chooser_get_file`.
If it is not GFile, maybe an error occurred.
- 49: connects the "response" signal of the dialog and `open_dialog_response` signal handler.
- 50: Shows the dialog.
- 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 on the header bar, then it emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_CANCEL`.
- 12-14: Gets a pointer to Gfile by `gtk_file_chooser_get_file`.
If it doesn't point GFile, maybe an error occurred.
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`.
- 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).
- 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`.
- 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.
- 34: closes GtkFileCooserDialog.
- 35: closes GtkFileCooserDialog.
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.
Because signal is the only way for GtkFileChooserDialog to communicate with others.
In Gtk3, `gtk_dialog_run` function is available.
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}
@ -196,7 +209,7 @@ However, in Gtk4, `gtk_dialog_run`is unavailable any more.
## 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
tfetextview/tfetextview.c tfe_text_view_get_file

View file

@ -109,7 +109,7 @@ notebook_page_open (GtkNotebook *nb) {
tv = tfe_text_view_new ();
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

View file

@ -179,7 +179,7 @@ notebook_page_open (GtkNotebook *nb) {
tv = tfe_text_view_new ();
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

View file

@ -180,7 +180,7 @@ notebook_page_open (GtkNotebook *nb) {
tv = tfe_text_view_new ();
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

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));
if (! G_IS_FILE (file))
g_warning ("TfeTextView: gtk_file_chooser_get_file returns non GFile object.\n");
else {
save_file(file, tb, GTK_WINDOW (win));
else if (save_file(file, tb, GTK_WINDOW (win))) {
if (G_IS_FILE (tv->file))
g_object_unref (tv->file);
tv->file = file;
gtk_text_buffer_set_modified (tb, FALSE);
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);
else if (! G_IS_FILE (tv->file))
g_error ("TfeTextView: The pointer in this object isn't NULL nor GFile object.\n");
else {
if (save_file (tv->file, tb, GTK_WINDOW (win)))
gtk_text_buffer_set_modified (tb, FALSE);
}
else
save_file (tv->file, tb, GTK_WINDOW (win));
}
void
@ -209,13 +205,13 @@ open_dialog_response(GtkWidget *dialog, gint response, TfeTextView *tv) {
}
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 (GTK_IS_WINDOW (win));
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,
"Open", GTK_RESPONSE_ACCEPT,
NULL);

View file

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

View file

@ -36,7 +36,7 @@ run_cb (GtkWidget *btnr) {
void
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