mirror of
https://github.com/ToshioCP/Gtk4-tutorial.git
synced 2025-01-12 20:03:28 +01:00
Bug in tfetextview.c fixed. Section 22 and some other sections are updated.
This commit is contained in:
parent
e8b34260c5
commit
a896bbd52f
13 changed files with 352 additions and 313 deletions
12
gfm/sec10.md
12
gfm/sec10.md
|
@ -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.
|
||||
|
||||
|
|
246
gfm/sec12.md
246
gfm/sec12.md
|
@ -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,23 +360,23 @@ 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.
|
||||
|
@ -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 *
|
||||
|
|
|
@ -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 }
|
||||
~~~
|
||||
|
||||
|
|
246
gfm/sec15.md
246
gfm/sec15.md
|
@ -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 }
|
||||
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 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 }
|
||||
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
|
||||
~~~
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
113
src/sec12.src.md
113
src/sec12.src.md
|
@ -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,23 +173,23 @@ 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.
|
||||
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue