Up: README.md, Prev: Section 12, Next: Section 14
The TfeTextView class will be finally completed in this section. The remaining topic is functions. TfeTextView functions, which are constructors and instance methods, are described in this section.
The source files are in the directory src/tfetextview.
You can get them by downloading the repository.
The header file tfetextview.h provides:
- The type of TfeTextView, which is
TFE_TYPE_TEXT_VIEW. - The macro
G_DECLARE_FINAL_TYPE, the expansion of which includes some useful functions and definitions. - Constants for the
open-responsesignal. - Public functions of
tfetextview.c. They are constructors and instance methods.
Therefore, Any programs use TfeTextView needs to include tfetextview.h.
1 #pragma once
2
3 #include <gtk/gtk.h>
4
5 #define TFE_TYPE_TEXT_VIEW tfe_text_view_get_type ()
6 G_DECLARE_FINAL_TYPE (TfeTextView, tfe_text_view, TFE, TEXT_VIEW, GtkTextView)
7
8 /* "open-response" signal response */
9 enum TfeTextViewOpenResponseType
10 {
11 TFE_OPEN_RESPONSE_SUCCESS,
12 TFE_OPEN_RESPONSE_CANCEL,
13 TFE_OPEN_RESPONSE_ERROR
14 };
15
16 GFile *
17 tfe_text_view_get_file (TfeTextView *tv);
18
19 void
20 tfe_text_view_open (TfeTextView *tv, GtkWindow *win);
21
22 void
23 tfe_text_view_save (TfeTextView *tv);
24
25 void
26 tfe_text_view_saveas (TfeTextView *tv);
27
28 GtkWidget *
29 tfe_text_view_new_with_file (GFile *file);
30
31 GtkWidget *
32 tfe_text_view_new (void);- 1: The preprocessor directive
#pragma oncemakes the header file be included only once. It is non-standard but widely used. - 3: Includes gtk4 header files.
The header file
gtk4also has the same mechanism to avoid being included multiple times. - 5-6: These two lines define TfeTextView type, its class structure and some useful definitions.
TfeTextViewandTfeTextViewClassare declared as typedef of C structures.- You need to define a structure
_TfeTextViewlater. - The class structure
_TfeTextViewClassis defined here. You don't need to define it by yourself. - Convenience functions
TFE_TEXT_VIEW ()for casting andTFE_IS_TEXT_VIEWfor type check are defined.
- 8-14: A definition of the values of the "open-response" signal parameters.
- 16-32: Declarations of public functions on TfeTextView.
A TfeTextView instance is created with tfe_text_view_new or tfe_text_view_new_with_file.
These functions are called constructors.
GtkWidget *tfe_text_view_new (void);It just creates a new TfeTextView instance and returns the pointer to the new instance.
GtkWidget *tfe_text_view_new_with_file (GFile *file);It is given a Gfile object as an argument and it loads the file into the GtkTextBuffer instance, then returns the pointer to the new instance.
The argument file is owned by the caller and the function doesn't change it.
If an error occurs during the creation process, NULL will be 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 GtkTextBuffer *tb;
7 char *contents;
8 gsize length;
9
10 if (! g_file_load_contents (file, NULL, &contents, &length, NULL, NULL)) /* read error */
11 return NULL;
12
13 tv = tfe_text_view_new();
14 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
15 gtk_text_buffer_set_text (tb, contents, length);
16 TFE_TEXT_VIEW (tv)->file = g_file_dup (file);
17 gtk_text_buffer_set_modified (tb, FALSE);
18 g_free (contents);
19 return tv;
20 }
21
22 GtkWidget *
23 tfe_text_view_new (void) {
24 return GTK_WIDGET (g_object_new (TFE_TYPE_TEXT_VIEW, "wrap-mode", GTK_WRAP_WORD_CHAR, NULL));
25 }- 22-25:
tfe_text_view_newfunction. Just returns the value from the functiong_object_newbut casts it to the pointer to GtkWidget. The functiong_object_newcreates any instances of its descendant class. The arguments are the type of the class, property list and NULL, which is the end mark of the property list. TfeTextView "wrap-mode" property has GTK_WRAP_WORD_CHAR as the default value. - 1-20:
tfe_text_view_new_with_filefunction. - 3:
g_return_val_if_failis described in GLib API Reference -- g_return_val_if_fail. And also GLib API Reference -- Message Logging. It tests whether the argumentfileis a pointer to GFile. If it's true, the program goes on to the next line. If it's false, 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. - 10-11: Reads the file. If an error occurs, NULL is returned.
- 13: Calls the function
tfe_text_view_new. The function creates TfeTextView instance and returns the pointer to the instance. - 14: Gets the pointer to the GtkTextBuffer instance corresponds to
tv. The pointer is assigned totb - 15: Assigns the contents read from the file to
tb. - 16: Duplicates
fileand setstv->fileto point it. GFile is not thread safe. The duplication makes sure that the GFile instance oftvkeeps the file information even if the original one is changed by other thread. - 17: The function
gtk_text_buffer_set_modified (tb, FALSE)sets the modification flag oftbto FALSE. The modification flag indicates that the contents has been modified. It is used when the contents are saved. If the modification flag is FALSE, it doesn't need to save the contents. - 18: Frees the memories pointed by
contents. - 19: Returns
tv, which is a pointer to the newly created TfeTextView instance. If an error happens, NULL is returned.
Save and saveas functions write the contents in the GtkTextBuffer to a file.
void tfe_text_view_save (TfeTextView *tv)The function tfe_text_view_save writes the contents in the GtkTextBuffer to a file specified by tv->file.
If tv->file is NULL, then it shows file chooser dialog 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 for the file.
void tfe_text_view_saveas (TfeTextView *tv)The function saveas shows a file chooser dialog and prompts the user to select a existed file or specify a new file to save.
Then, the function changes tv->file and save the contents to the specified file.
If an error occurs, it is shown to the user through the alert dialog.
The error is managed only in the TfeTextView and no information is notified to the caller.
1 static gboolean
2 save_file (GFile *file, GtkTextBuffer *tb, GtkWindow *win) {
3 GtkTextIter start_iter;
4 GtkTextIter end_iter;
5 char *contents;
6 gboolean stat;
7 GtkAlertDialog *alert_dialog;
8 GError *err = NULL;
9
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 stat = g_file_replace_contents (file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, &err);
13 if (stat)
14 gtk_text_buffer_set_modified (tb, FALSE);
15 else {
16 alert_dialog = gtk_alert_dialog_new ("%s", err->message);
17 gtk_alert_dialog_show (alert_dialog, win);
18 g_object_unref (alert_dialog);
19 g_error_free (err);
20 }
21 g_free (contents);
22 return stat;
23 }- The function
save_fileis called fromsaveas_dialog_responseandtfe_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. So, a caller of this function don't need to take care of errors. The class of this function isstatic. Therefore, only functions in this file (tfetextview.c) call this function. Such static functions usually don't haveg_return_val_if_failfunctions. - 10-11: Gets the text contents from the buffer.
- 12: The function
g_file_replace_contentswrites the contents to the file and returns the status (true = success/ false = fail). It has many parameters, but some of them are almost always given the same values.- GFile* file: GFile to which the contents are saved.
- const char* contents: contents to be saved. The string is owned by the caller.
- gsize length: the length of the contents
- const char* etag: entity tag. It is usually NULL.
- gboolean make_backup: true to make a backup if the file exists. false not to make it. the file will be overwritten.
- GFileCreateFlags flags: usually
G_FILE_CREATE_NONEis fine. - char** new_etag: new entity tag. It is usually NULL.
- GCancellable* cancellable: If a cancellable instance is set, the other thread can cancel this operation. it is usually NULL.
- GError** error: If error happens, GError will be set.
- 13,14: If no error happens, set the modified flag to be FALSE. This means that the buffer is not modified since it has been saved.
- 16-19: If it fails to save the contents, an error message will be displayed.
- 16: Creates an alert dialog. The parameters are printf-like format string followed by values to insert into the string. GtkAlertDialog is available since version 4.10. If your version is older than 4.10, use GtkMessageDialog instead. GtkMessageDialog is deprecated since version 4.10.
- 17: Show the alert dialog. The parameters are the dialog and the transient parent window. This allows window managers to keep the dialog on top of the parent window, or center the dialog over the parent window. It is possible to give no parent window to the dialog by giving NULL as the argument. However, it is encouraged to give parents to dialogs.
- 18: Releases the dialog.
- 19: Frees the GError struct pointed by
errwithg_error_freefunction. - 21: Frees
contents. - 22: Returns the status to the caller.
1 static void
2 save_dialog_cb(GObject *source_object, GAsyncResult *res, gpointer data) {
3 GtkFileDialog *dialog = GTK_FILE_DIALOG (source_object);
4 TfeTextView *tv = TFE_TEXT_VIEW (data);
5 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
6 GFile *file;
7 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
8 GError *err = NULL;
9 GtkAlertDialog *alert_dialog;
10
11 if (((file = gtk_file_dialog_save_finish (dialog, res, &err)) != NULL) && save_file(file, tb, GTK_WINDOW (win))) {
12 // The following is complicated. The comments here will help your understanding
13 // G_IS_FILE(tv->file) && tv->file == file => nothing to do
14 // G_IS_FILE(tv->file) && tv->file != file => unref(tv->file), tv->file=file, emit change_file signal
15 // tv->file==NULL => tv->file=file, emit change_file signal
16 if (! (G_IS_FILE (tv->file) && g_file_equal (tv->file, file))) {
17 if (G_IS_FILE (tv->file))
18 g_object_unref (tv->file);
19 tv->file = file; // The ownership of 'file' moves to TfeTextView.
20 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
21 }
22 }
23 if (err) {
24 alert_dialog = gtk_alert_dialog_new ("%s", err->message);
25 gtk_alert_dialog_show (alert_dialog, GTK_WINDOW (win));
26 g_object_unref (alert_dialog);
27 g_clear_error (&err);
28 }
29 }- The function
save_dialog_cbis a call back function that is given to thegtk_file_dialog_savefunction as an argument. Thegtk_file_dialog_saveshows a file chooser dialog to the user. The user chooses or types a filename and clicks on theSavebutton or just clicks on theCancelbutton. Then the call back function is called with the result. This is the general way in GIO to manage asynchronous operations. A pair of functionsg_data_input_stream_read_line_asyncandg_data_input_stream_read_line_finishare one example. These functions are thread-safe. The arguments ofsave_dialog_cbare:- GObject *source_object: The GObject instance that the operation was started with.
It is actually the GtkFileDialog instance that is shown to the user.
However, the call back function is defined as
AsyncReadyCallback, which is a general call back function for an asynchronous operation. So the type is GObject and you need to cast it to GtkFileDialog later. - GAsyncResult *res: The result of the asynchronous operation.
It will be given to the
gtk_dialog_save_finishfunction. - gpointer data: A user data set in the
gtk_dialog_savefunction.
- GObject *source_object: The GObject instance that the operation was started with.
It is actually the GtkFileDialog instance that is shown to the user.
However, the call back function is defined as
- 11: Calls
gtk_dialog_save_finish. It is given the resultresas an argument and returns a pointer to a GFile object the user has chosen. If the user has canceled or an error happens, it returns NULL, creates a GError object and setserrto point it. Ifgtk_dialog_save_finishreturns a GFile, the functionsave_fileis called. - 12-21: If the file is successfully saved, these lines are executed. See the comments, line 12-15, for the details.
- 23-28: If an error happens, show the error message through the alert dialog.
1 void
2 tfe_text_view_save (TfeTextView *tv) {
3 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
4
5 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
6 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
7
8 if (! gtk_text_buffer_get_modified (tb))
9 return; /* no need to save it */
10 else if (tv->file == NULL)
11 tfe_text_view_saveas (tv);
12 else
13 save_file (tv->file, tb, GTK_WINDOW (win));
14 }- The function
tfe_text_view_savewrites the contents to thetv->filefile. It callstfe_text_view_saveasorsave_file. - 1-3: The function is public, i.e. it is open to the other objects.
So, it doesn't have
staticclass. Public functions should check the parameter type withg_return_if_failfunction. Iftvis not a pointer to a TfeTextView instance, then it logs an error message and immediately returns. This function is similar tog_return_val_if_fail, but no value is returned becausetfe_text_view_savedoesn't return a value (void). - 5-6: GtkTextBuffer
tband GtkWidget (GtkWindow)winare set. The functiongtk_widget_get_ancestor (widget, type)returns the first ancestor of the widget with the type, which is a GType. The parent-child relationship here is the one for widgets, not classes. More precisely, the returned widget's type is thetypeor a descendant object type of thetype. Be careful, the "descendant object" in the previous sentence is not "descendant widget". For example, the type of GtkWindow isGTK_TYPE_WINDOWand the one of TfeTextView isTFE_TYPE_TEXT_VIEW. The top level window may be a GtkApplicationWindow, but it is a descendant of GtkWindow. Therefore,gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW)possibly returns GtkWindow or GtkApplicationWindow. - 8-9: If the buffer hasn't modified, it doesn't need to be saved.
- 10-11: If
tv->fileis NULL, which means no file has given yet, it callstfe_text_view_saveasto prompt a user to select a file and save the contents. - 12-13: Otherwise, it calls
save_fileto save the contents to the filetv->file.
1 void
2 tfe_text_view_saveas (TfeTextView *tv) {
3 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
4
5 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
6 GtkFileDialog *dialog;
7
8 dialog = gtk_file_dialog_new ();
9 gtk_file_dialog_save (dialog, GTK_WINDOW (win), NULL, save_dialog_cb, tv);
10 g_object_unref (dialog);
11 }The function tfe_text_view_saveas shows a file chooser dialog and prompts the user to choose a file and save the contents.
- 1-3: Check the type of
tvbecause the function is public. - 6: GtkWidget
winis set to the window which is an ancestor ottv. - 8: Creates a GtkFileDialog instance. GtkFileDialog is available since version 4.10. If your Gtk version is older than 4.10, use GtkFileChooserDialog instead. GtkFileChooserDialog is deprecated since version 4.10.
- 9: Calls
gtk_file_dialog_savefunction. The arguments are:- dialog: GtkFileDialog.
- GTK_WINDOW (win): transient parent window.
- NULL: NULL means no cancellable object. If you put a cancellable object here, you can cancel the operation by other thread. In many cases, it is NULL. See GCancellable for further information.
save_dialog_cb: A callback to call when the operation is complete. The type of the pointer to the callback function is GAsyncReadyCallback. If a cancellable object is given and the operation is cancelled, the callback won't be called.tv: This is an optional user data which is gpointer type. It is used in the callback function.
- 10: Releases the GtkFileDialog instance because it is useless anymore.
This function just shows the file chooser dialog. The rest of the operation is done by the callback function.
Open function shows a file chooser dialog to a user and prompts them to choose a file. Then it reads the file and puts the text into GtkTextBuffer.
void tfe_text_view_open (TfeTextView *tv, GtkWindow *win);The parameter win is the transient window.
A file chooser dialog will be shown at the center of the window.
This function may 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-level window from tv.
That's why the function needs win parameter.
This function is usually called when the buffer of tv is empty.
However, even if the buffer 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 appropriate.
Open and read process is divided into two phases.
One is creating and showing a file chooser dialog and the other is the callback function.
The former is tfe_text_view_open and the latter is open_dialog_cb.
1 static void
2 open_dialog_cb (GObject *source_object, GAsyncResult *res, gpointer data) {
3 GtkFileDialog *dialog = GTK_FILE_DIALOG (source_object);
4 TfeTextView *tv = TFE_TEXT_VIEW (data);
5 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
6 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
7 GFile *file;
8 char *contents;
9 gsize length;
10 gboolean file_changed;
11 GtkAlertDialog *alert_dialog;
12 GError *err = NULL;
13
14 if ((file = gtk_file_dialog_open_finish (dialog, res, &err)) != NULL
15 && g_file_load_contents (file, NULL, &contents, &length, NULL, &err)) {
16 gtk_text_buffer_set_text (tb, contents, length);
17 g_free (contents);
18 gtk_text_buffer_set_modified (tb, FALSE);
19 // G_IS_FILE(tv->file) && tv->file == file => unref(tv->file), tv->file=file, emit response with SUCCESS
20 // G_IS_FILE(tv->file) && tv->file != file => unref(tv->file), tv->file=file, emit response with SUCCESS, emit change-file
21 // tv->file==NULL => tv->file=file, emit response with SUCCESS, emit change-file
22 // The order is important. If you unref tv->file first, you can't compare tv->file and file anymore.
23 // And the signals are emitted after new tv->file is set. Or the handler can't catch the new file.
24 file_changed = (G_IS_FILE (tv->file) && g_file_equal (tv->file, file)) ? FALSE : TRUE;
25 if (G_IS_FILE (tv->file))
26 g_object_unref (tv->file);
27 tv->file = file; // The ownership of 'file' moves to TfeTextView
28 if (file_changed)
29 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
30 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS);
31 } else {
32 if (err->code == GTK_DIALOG_ERROR_DISMISSED) // The user canceled the file chooser dialog
33 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_CANCEL);
34 else {
35 alert_dialog = gtk_alert_dialog_new ("%s", err->message);
36 gtk_alert_dialog_show (alert_dialog, GTK_WINDOW (win));
37 g_object_unref (alert_dialog);
38 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
39 }
40 g_clear_error (&err);
41 }
42 }This function is similar to save_dialog_cb.
Both are callback functions on a GtkFileDialog object.
- 2: It has three parameters like
save_dialog_cb. They are:- GObject *source_object: The GObject instance that the operation was started with. It is actually the GtkFileDialog instance that is shown to the user. It will be casted to GtkFileDialog later.
- GAsyncResult *res: The result of the asynchronous operation.
It will be given to the
gtk_dialog_open_finishfunction. - gpointer data: A user data set in the
gtk_dialog_openfunction. It is actually a TfeTextView instance and it will be casted to TfeTextView later.
- 14: The function
gtk_file_dialog_open_finishreturns a GFile object if the operation has succeeded. Otherwise it returns NULL. - 16-30: If the user selects a file and the file has successfully been read, the codes from 16 to 30 will be executed.
- 16-18: Sets the buffer of
tvwith the text read from the file. And freescontents. Then sets the modified status to false. - 19-30: The codes are a bit complicated.
See the comments.
If the file (
tv->file) is changed, "change-file" signal is emitted. The signal "open-response" is emitted with the parameterTFE_OPEN_RESPONSE_SUCCESS. - 31-41: If the operation failed, the codes from 31 to 41 will be executed.
- 32-33: If the error code is
GTK_DIALOG_ERROR_DISMISSED, it means that the user has clicked on the "Cancel" button or close button on the header bar. Then, "open-response" signal is emitted with the parameterTFE_OPEN_RESPONSE_CANCEL. The Dialog error is described here in the GTK API reference. - 35-38: If another error occurs, it shows an alert dialog to report the error and emits "open-response" signal with the parameter
TFE_OPEN_RESPONSE_ERROR. - 40: Clears the error structure.
1 void
2 tfe_text_view_open (TfeTextView *tv, GtkWindow *win) {
3 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
4 // 'win' is used for a transient window of the GtkFileDialog.
5 // It can be NULL.
6 g_return_if_fail (GTK_IS_WINDOW (win) || win == NULL);
7
8 GtkFileDialog *dialog;
9
10 dialog = gtk_file_dialog_new ();
11 gtk_file_dialog_open (dialog, win, NULL, open_dialog_cb, tv);
12 g_object_unref (dialog);
13 }- 3-6: Check the type of the arguments
tvandwin. Public functions always need to check the arguments. - 10: Creates a GtkFileDialog instance.
- 11: Calls
gtk_file_dialog_open. The arguments are:dialog: the GtkFileDialog instancewin: the transient window for the file chooser dialogNULL: NULL means no cancellable objectopen_dialog_cb: callback functiontv: user data which is used in the callback function
- 12: Releases the dialog instance because it is useless anymore.
The whole process between the caller and TfeTextView is shown in the following diagram.
It is really complicated.
Because gtk_file_dialog_open can't return the status of the operation.
- A caller gets a pointer
tvto a TfeTextView instance by callingtfe_text_view_new. - The caller connects the handler (left bottom in the diagram) and the signal "open-response".
- It calls
tfe_text_view_opento prompt the user to select a file from the file chooser dialog. - When the dialog is closed, the callback
open_dialog_cbis called. - The callback function reads the file and inserts the text into GtkTextBuffer and emits a signal to inform the status as a response code.
- The handler of the "open-response" signal is invoked and the operation status is given to it as an argument (signal parameter).
You can get the GFile in a TfeTextView instance with tfe_text_view_get_file.
It is very simple.
1 GFile *
2 tfe_text_view_get_file (TfeTextView *tv) {
3 g_return_val_if_fail (TFE_IS_TEXT_VIEW (tv), NULL);
4
5 if (G_IS_FILE (tv->file))
6 return g_file_dup (tv->file);
7 else
8 return NULL;
9 }The important thing is to duplicate tv->file.
Otherwise, if the caller frees the GFile object, tv->file is no more guaranteed to point the GFile.
Another reason to use g_file_dup is that GFile isn't thread-safe.
If you use GFile in the different thread, the duplication is necessary.
See Gio API Reference -- g_file_dup.
Refer API document of TfeTextView.
It is under the directory src/tfetextview.
You can find all the TfeTextView source codes under src/tfetextview directories.
Up: README.md, Prev: Section 12, Next: Section 14

