Gtk4-tutorial/sec10.md

347 lines
15 KiB
Markdown
Raw Normal View History

Up: [Readme.md](src/Readme.md), Prev: [Section 9](src/sec9.src.md), Next: [Section 11](src/sec11.src.md)
# Functions in TfeTextView
2020-12-21 13:12:05 +01:00
In this section I will explain each function in TfeTextView object.
### tfe.h and tfetextview.h
`tfe.h` is a top header file and it includes `gtk.h` and all the header files.
Every C source files, which are `tfeapplication.c`, `tfenotebook.c` and `tfetextview.c`, include `tfe.h` at the beginning of each file.
1 #include <gtk/gtk.h>
2
3 #include "tfetextview.h"
4 #include "tfenotebook.h"
`tfetextview.h` is a header file which describes the public functions in `tfetextview.c`.
1 #define TFE_TYPE_TEXT_VIEW tfe_text_view_get_type ()
2 G_DECLARE_FINAL_TYPE (TfeTextView, tfe_text_view, TFE, TEXT_VIEW, GtkTextView)
3
4 /* "open-response" signal response */
5 enum
6 {
7 TFE_OPEN_RESPONSE_SUCCESS,
8 TFE_OPEN_RESPONSE_CANCEL,
9 TFE_OPEN_RESPONSE_ERROR
10 };
11
12 GFile *
13 tfe_text_view_get_file (TfeTextView *tv);
14
15 void
16 tfe_text_view_open (TfeTextView *tv);
17
18 void
19 tfe_text_view_save (TfeTextView *tv);
20
21 void
22 tfe_text_view_saveas (TfeTextView *tv);
23
24 GtkWidget *
25 tfe_text_view_new_with_file (GFile *file);
26
27 GtkWidget *
28 tfe_text_view_new (void);
29
- 1-2: These two lines are used to define TfeTextView.
- 4-10: Definitions of parameter used in the handler of "open-response" signal.
- 12-28: Public functions on GtkTextView.
Each function will be explained later in this section.
## Functions to generate TfeTextView object
TfeTextView Object is generated by `tfe_text_view_new` or `tfe_text_view_new_with_file`.
GtkWidget *tfe_text_view_new (void);
`tfe_text_view_new` just generates a new TfeTextView object and returns the pointer to the new object.
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.
Parameter:
- `file`: a pointer to the GFile object.
Return value:
- A pointer to the generated TfeTextView object but it is casted to a pointer to GtkWidget.
If an error occures during the genration process, NULL is returned.
Each function is defined as follows.
1 GtkWidget *
2 tfe_text_view_new_with_file (GFile *file) {
3 g_return_val_if_fail (G_IS_FILE (file), NULL);
4
5 GtkWidget *tv;
6 char *contents;
7 gsize length;
8
9 if (! g_file_load_contents (file, NULL, &contents, &length, NULL, NULL)) /* read error */
10 return NULL;
11
12 tv = tfe_text_view_new();
13 gtk_text_buffer_set_text (TFE_TEXT_VIEW (tv)->tb, contents, length);
14 g_free (contents);
15 TFE_TEXT_VIEW (tv)->file = g_file_dup (file);
16 return tv;
17 }
18
19 GtkWidget *
20 tfe_text_view_new (void) {
21 return gtk_widget_new (TFE_TYPE_TEXT_VIEW, NULL);
22 }
- 18-21: `tfe_text_view_new`.
Just returns the value from the function `gtk_widget_new`.
Initialization is done in `tfe_text_view_init` which is called in the process of `gtk_widget_new` function.
- 1-16: `tfe_text_view_new_with_file`
- 3: `g_return_val_if_fail` is described in [Glib API reference](https://developer.gnome.org/glib/stable/glib-Warnings-and-Assertions.html#g-return-val-if-fail).
It tests whether the argument `file` is a pointer to GFile.
If it's true, then the program goes on to the next line.
If it's false, then it returns NULL (the second argument) immediately.
And at the same time it logs out the error message (usually the log is outputted to stderr or stdout).
This function is used to check the programmer's error.
If an error occurs, the solution is usually to change the (caller) program and fix the bug.
You need to distinguish programmer's errors and runtime errors.
You shouldn't use this function to find runtime errors.
- 9-10: If an error occurs when reading the file, then return NULL.
- 11-15: Generate TfeTextView and set the pointer to it to `tv`.
Set the contents read from the file to GtkTextBuffer `tv->tb`.
Free the memories pointed by `contents`.
Duplicate `file` and set it to `tv->file`.
Return `tv`.
## Save and saveas functions
Save and saveas functions write the contents in GtkTextBuffer to a file.
void tfe_text_view_save (TfeTextView *tv)
`save` function writes the contents in GtkTextBuffer to a file specified by `tv->file`.
If `tv->file` is NULL, then it shows GtkFileChooserDialog and lets the user to give a file to the program. After that, it saves the contents to the specified file and set the file into `tv->file`.
void tfe_text_view_saveas (TfeTextView *tv)
`saveas` function uses GtkFileChooserDialog and lets the user to give a new file to the program. Then, the function changes `tv->file` and save the contents to the specified new file.
If an error occures, it is shown to the user through the message dialog.
The error is managed only in the object and no information is notified to the caller.
1 static void
2 saveas_dialog_response (GtkWidget *dialog, gint response, TfeTextView *tv) {
3 GFile *file;
4
5 if (response == GTK_RESPONSE_ACCEPT) {
6 file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
7 if (G_IS_FILE(file)) {
8 tv->file = file;
9 tv->changed = TRUE;
10 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
11 tfe_text_view_save (TFE_TEXT_VIEW (tv));
12 }
13 }
14 gtk_window_destroy (GTK_WINDOW (dialog));
15 }
16
17 void
18 tfe_text_view_save (TfeTextView *tv) {
19 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
20
21 GtkTextIter start_iter;
22 GtkTextIter end_iter;
23 gchar *contents;
24 GtkWidget *message_dialog;
25 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
26 GError *err = NULL;
27
28 if (! tv->changed)
29 return; /* no necessary to save it */
30 else if (tv->file == NULL)
31 tfe_text_view_saveas (tv);
32 else {
33 gtk_text_buffer_get_bounds (tv->tb, &start_iter, &end_iter);
34 contents = gtk_text_buffer_get_text (tv->tb, &start_iter, &end_iter, FALSE);
35 if (g_file_replace_contents (tv->file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, &err))
36 tv->changed = FALSE;
37 else {
38 /* It is possible that tv->file is broken. */
39 /* It is a good idea to set tv->file to NULL. */
40 if (G_IS_FILE (tv->file))
41 g_object_unref (tv->file);
42 tv->file =NULL;
43 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
44 tv->changed = TRUE;
45 message_dialog = gtk_message_dialog_new (GTK_WINDOW (win), GTK_DIALOG_MODAL,
46 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
47 "%s.\n", err->message);
48 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
49 gtk_widget_show (message_dialog);
50 g_error_free (err);
51 }
52 }
53 }
54
55 void
56 tfe_text_view_saveas (TfeTextView *tv) {
57 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
58
59 GtkWidget *dialog;
60 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
61
62 dialog = gtk_file_chooser_dialog_new ("Save file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_SAVE,
63 "_Cancel", GTK_RESPONSE_CANCEL,
64 "_Save", GTK_RESPONSE_ACCEPT,
65 NULL);
66 g_signal_connect (dialog, "response", G_CALLBACK (saveas_dialog_response), tv);
67 gtk_widget_show (dialog);
68 }
- 17-53: `Tfe_text_view_save` function.
- 19: 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.
- 28-29: If the buffer hasn't modified, then it doesn't need to save it.
So the function returns.
- 30-31: If `tv->file` is NULL, no file has given yet.
It calls `tfe_text_view_save`, which lets the user to choose a file to save.
- 33-35: Save the buffer to the file.
If it succeeds, assigns FALSE to `tv->changed`.
- 38-50: If file writing fails, it assigns NULL to `tv->file`.
Emits "change-file" signal.
Shows the error message dialog (45-49).
Because the handler is `gtk_window_destroy`, the dialog disappears when user clicks on the button in the dialog.
- 55-68: `tfe_text_view_saveas` function.
It shows GtkFileChooserDialog and lets the user choose a file and give it to the signal handler.
- 62: Generate 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.
- 63: connect the "response" signal of the dialog and `saveas_dialog_response` handler.
- 1-15: `saveas_dialog_response` signal handler.
- 5-13: If the response is `GTK_RESPONSE_ACCEPT`, which is set to the argument when the user has clicked on Save button, then gets a pointer to the GFile object, set it to `tv->file`, assign TRUE to `tv->changed`, emits "change-file" signal then call `tfe_text_view_save` to save the buffer to the file.
![Saveas process](saveas.png)
When you use GtkFileChooserDialog, you need to divide the program into two parts.
They are a function which generates GtkFileChooserDialog and the signal handler.
The function just generates and shows the dialog.
The rest is done by the handler.
It gets Gfile from GtkFileChooserDialog, save the buffer to the file and do some things necessary.
## Open function
Open function shows GtkFileChooserDialog to the user and let him/her choose a file.
Then read the file and set it to GtkTextBuffer.
void tfe_text_view_open (TfeTextView *tv)
TfeTextView object `tv` has to be generated in advance.
And it should be empty and `tv->file` is NULL.
If it is not empty, `tfe_text_view_open` doesn't treat it as an error.
If you want to revert the buffer, calling this function is apropreate.
Otherwise probably bad things will happen.
1 static void
2 open_dialog_response(GtkWidget *dialog, gint response, TfeTextView *tv) {
3 GFile *file;
4 char *contents;
5 gsize length;
6 GtkWidget *message_dialog;
7 GError *err = NULL;
8
9 if (response != GTK_RESPONSE_ACCEPT)
10 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_CANCEL);
11 else if (! G_IS_FILE (file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog))))
12 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
13 else if (! g_file_load_contents (file, NULL, &contents, &length, NULL, &err)) { /* read error */
14 if (G_IS_FILE (file))
15 g_object_unref (file);
16 message_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog), 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 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
23 } else {
24 gtk_text_buffer_set_text (tv->tb, contents, length);
25 g_free (contents);
26 tv->file = file;
27 /* tv->changed = FALSE;*/
28 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS);
29 }
30 gtk_window_destroy (GTK_WINDOW (dialog));
31 }
32
33 void
34 tfe_text_view_open (TfeTextView *tv) {
35 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
36
37 GtkWidget *dialog;
38
39 dialog = gtk_file_chooser_dialog_new ("Open file", NULL, GTK_FILE_CHOOSER_ACTION_OPEN,
40 "Cancel", GTK_RESPONSE_CANCEL,
41 "Open", GTK_RESPONSE_ACCEPT,
42 NULL);
43 g_signal_connect (dialog, "response", G_CALLBACK (open_dialog_response), tv);
44 gtk_widget_show (dialog);
45 }
- 33-45: `tfe_text_view_open` function.
- 39: Generate GtkFileChooserDialog.
The title is "Open file".
No transient parent window.
The action is open mode.
The buttons are Cancel and Open.
- 43: connect the "reponse" signal of the dialog and `open_dialog_response` signal handler.
- 44: Show the dialog.
- 1-31: `open_dialog_response` signal handler.
- 9-10: 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`.
- 11-12: Get a pointer to Gfile by `gtk_file_chooser_get_file`.
If it is not GFile, maybe an error occured.
Then it emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_ERROR`.
- 13-22: If an error occurs when it has read the file, then it decreases the reference count of 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-28: If the file has successfully read, then the text is set to GtkTextBuffer, free the temporary buffer pointed by `contents`, set file to `tv->file` (no duplication or unref is not necessary) and emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_SUCCESS`.
- 30: close 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.
![Caller and TfeTextView](open.png)
1. A caller get a pointer `tv` to TfeTextView by calling `tfe_text_view_new`.
2. The caller connects the handler (left bottom in the diagram) and the signal "open-response".
3. It calls `tfe_text_view_open` to let the user select a file from GtkFileChooserDialog.
4. The dialog emits a signal and it invokes the handler `open_dialog_response`.
5. The handler read the file and set it into GtkTextBuffer and emits a signal to inform the response status.
6. The handler outside TfeTextView recieves the signal.
## Get file function
`gtk_text_view_get_file` is a simple function show as follows.
1 GFile *
2 tfe_text_view_get_file (TfeTextView *tv) {
3 g_return_val_if_fail (TFE_IS_TEXT_VIEW (tv), NULL);
4
5 return g_file_dup (tv->file);
6 }
The important thing is duplicate `tv->file`.
Otherwise, if the caller free the GFile object, `tv->file` is no more guaranteed to point the GFile.
## Source file of tfetextview.c
All the source files are listed in [Section 13](ch13.html).
Up: [Readme.md](src/Readme.md), Prev: [Section 9](src/sec9.src.md), Next: [Section 11](src/sec11.src.md)