From a896bbd52f2093a1c1e8bc38a95bff53b16670ee Mon Sep 17 00:00:00 2001 From: Toshio Sekiya Date: Fri, 2 Apr 2021 22:40:56 +0900 Subject: [PATCH] Bug in tfetextview.c fixed. Section 22 and some other sections are updated. --- gfm/sec10.md | 12 +- gfm/sec12.md | 248 +++++++++++++++++++-------------- gfm/sec13.md | 2 +- gfm/sec15.md | 248 ++++++++++++++++----------------- gfm/sec23.md | 4 +- src/sec10.src.md | 12 +- src/sec12.src.md | 115 ++++++++------- src/tfe5/tfenotebook.c | 2 +- src/tfe6/tfenotebook.c | 2 +- src/tfe7/tfenotebook.c | 2 +- src/tfetextview/tfetextview.c | 14 +- src/tfetextview/tfetextview.h | 2 +- src/turtle/turtleapplication.c | 2 +- 13 files changed, 352 insertions(+), 313 deletions(-) diff --git a/gfm/sec10.md b/gfm/sec10.md index cabca7c..a811914 100644 --- a/gfm/sec10.md +++ b/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. diff --git a/gfm/sec12.md b/gfm/sec12.md index 1a486e9..2150b9e 100644 --- a/gfm/sec12.md +++ b/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,30 +360,30 @@ Otherwise probably bad things will happen. 51 } ~~~ -- 37-50: `tfe_text_view_open` function. -- 44-47: Generates GtkFileChooserDialog. +- 38-51: `tfe_text_view_open` function. +- 45-48: Creates GtkFileChooserDialog. The title is "Open file". -Transient parent window is the top window of the application, which is given by the caller. +Transient parent window is the top level window of the application, which is given by the caller. The action is open mode. The buttons are Cancel and Open. -- 48: connects the "response" signal of the dialog and `open_dialog_response` signal handler. -- 49: Shows the dialog. -- 1-35: `open_dialog_response` signal handler. -- 10-11: If the response from GtkFileChooserDialog is not `GTK_RESPONSE_ACCEPT`, which means the user has clicked on the "Cancel" button or close button, then it emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_CANCEL`. -- 12-13: Gets a pointer to Gfile by `gtk_file_chooser_get_file`. -If it is not GFile, maybe an error occurred. +- 49: connects the "response" signal of the dialog and `open_dialog_response` signal handler. +- 50: Shows the dialog. +- 1-36: `open_dialog_response` signal handler. +- 10-11: If the response from GtkFileChooserDialog is not `GTK_RESPONSE_ACCEPT`, which means the user has clicked on the "Cancel" button or close button on the header bar, then it emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_CANCEL`. +- 12-14: Gets a pointer to Gfile by `gtk_file_chooser_get_file`. +If it doesn't point GFile, maybe an error occurred. Then it emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_ERROR`. -- 14-23: If an error occurs at file reading, then it decreases the reference count of the Gfile, shows a message dialog to report the error to the user and emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_ERROR`. -- 24-33: If the file has successfully read, then the text is inserted to GtkTextBuffer, frees the temporary buffer pointed by `contents` and sets `tv->file` to point the file (no duplication or unref is not necessary). +- 15-24: If an error occurs at file reading, then it decreases the reference count of the Gfile, shows a message dialog to report the error to the user and emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_ERROR`. +- 25-33: If the file has successfully been read, then the text is inserted to GtkTextBuffer, frees the temporary buffer pointed by `contents` and sets `tv->file` to point the file (no duplication or unref is not necessary). Then, it emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_SUCCESS` and emits "change-file" signal. -- 34: closes GtkFileCooserDialog. +- 35: closes GtkFileCooserDialog. Now let's think about the whole process between the other object (caller) and TfeTextView. It is shown in the following diagram and you would think that it is really complicated. Because signal is the only way for GtkFileChooserDialog to communicate with others. In Gtk3, `gtk_dialog_run` function is available. It simplifies the process. -However, in Gtk4, `gtk_dialog_run`is unavailable any more. +However, in Gtk4, `gtk_dialog_run` is unavailable any more. ![Caller and TfeTextView](../image/open.png) @@ -362,7 +396,7 @@ However, in Gtk4, `gtk_dialog_run`is unavailable any more. ## Get file function -`gtk_text_view_get_file` is a simple function show as follows. +`gtk_text_view_get_file` is a simple function shown as follows. ~~~C 1 GFile * diff --git a/gfm/sec13.md b/gfm/sec13.md index 2c822c8..ccc2511 100644 --- a/gfm/sec13.md +++ b/gfm/sec13.md @@ -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 } ~~~ diff --git a/gfm/sec15.md b/gfm/sec15.md index f60004f..f31c3e8 100644 --- a/gfm/sec15.md +++ b/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 } -116 } -117 gtk_window_destroy (GTK_WINDOW (dialog)); -118 } -119 -120 void -121 tfe_text_view_save (TfeTextView *tv) { -122 g_return_if_fail (TFE_IS_TEXT_VIEW (tv)); -123 -124 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv)); -125 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW); -126 -127 if (! gtk_text_buffer_get_modified (tb)) -128 return; /* no need to save it */ -129 else if (tv->file == NULL) -130 tfe_text_view_saveas (tv); -131 else if (! G_IS_FILE (tv->file)) -132 g_error ("TfeTextView: The pointer in this object isn't NULL nor GFile object.\n"); -133 else { -134 if (save_file (tv->file, tb, GTK_WINDOW (win))) -135 gtk_text_buffer_set_modified (tb, FALSE); -136 } -137 } +108 else if (save_file(file, tb, GTK_WINDOW (win))) { +109 if (G_IS_FILE (tv->file)) +110 g_object_unref (tv->file); +111 tv->file = file; +112 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0); +113 } +114 } +115 gtk_window_destroy (GTK_WINDOW (dialog)); +116 } +117 +118 void +119 tfe_text_view_save (TfeTextView *tv) { +120 g_return_if_fail (TFE_IS_TEXT_VIEW (tv)); +121 +122 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv)); +123 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW); +124 +125 if (! gtk_text_buffer_get_modified (tb)) +126 return; /* no need to save it */ +127 else if (tv->file == NULL) +128 tfe_text_view_saveas (tv); +129 else if (! G_IS_FILE (tv->file)) +130 g_error ("TfeTextView: The pointer in this object isn't NULL nor GFile object.\n"); +131 else +132 save_file (tv->file, tb, GTK_WINDOW (win)); +133 } +134 +135 void +136 tfe_text_view_saveas (TfeTextView *tv) { +137 g_return_if_fail (TFE_IS_TEXT_VIEW (tv)); 138 -139 void -140 tfe_text_view_saveas (TfeTextView *tv) { -141 g_return_if_fail (TFE_IS_TEXT_VIEW (tv)); -142 -143 GtkWidget *dialog; -144 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW); -145 -146 dialog = gtk_file_chooser_dialog_new ("Save file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_SAVE, -147 "Cancel", GTK_RESPONSE_CANCEL, -148 "Save", GTK_RESPONSE_ACCEPT, -149 NULL); -150 g_signal_connect (dialog, "response", G_CALLBACK (saveas_dialog_response), tv); -151 gtk_widget_show (dialog); -152 } +139 GtkWidget *dialog; +140 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW); +141 +142 dialog = gtk_file_chooser_dialog_new ("Save file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_SAVE, +143 "Cancel", GTK_RESPONSE_CANCEL, +144 "Save", GTK_RESPONSE_ACCEPT, +145 NULL); +146 g_signal_connect (dialog, "response", G_CALLBACK (saveas_dialog_response), tv); +147 gtk_widget_show (dialog); +148 } +149 +150 GtkWidget * +151 tfe_text_view_new_with_file (GFile *file) { +152 g_return_val_if_fail (G_IS_FILE (file), NULL); 153 -154 GtkWidget * -155 tfe_text_view_new_with_file (GFile *file) { -156 g_return_val_if_fail (G_IS_FILE (file), NULL); -157 -158 GtkWidget *tv; -159 GtkTextBuffer *tb; -160 char *contents; -161 gsize length; -162 -163 if (! g_file_load_contents (file, NULL, &contents, &length, NULL, NULL)) /* read error */ -164 return NULL; -165 -166 tv = tfe_text_view_new(); -167 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv)); -168 gtk_text_buffer_set_text (tb, contents, length); -169 g_free (contents); -170 TFE_TEXT_VIEW (tv)->file = g_file_dup (file); -171 return tv; -172 } -173 -174 static void -175 open_dialog_response(GtkWidget *dialog, gint response, TfeTextView *tv) { -176 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv)); -177 GFile *file; -178 char *contents; -179 gsize length; -180 GtkWidget *message_dialog; -181 GError *err = NULL; -182 -183 if (response != GTK_RESPONSE_ACCEPT) -184 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_CANCEL); -185 else if (! G_IS_FILE (file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)))) { -186 g_warning ("TfeTextView: gtk_file_chooser_get_file returns non GFile object.\n"); -187 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR); -188 } else if (! g_file_load_contents (file, NULL, &contents, &length, NULL, &err)) { /* read error */ -189 if (G_IS_FILE (file)) -190 g_object_unref (file); -191 message_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL, -192 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, -193 "%s.\n", err->message); -194 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL); -195 gtk_widget_show (message_dialog); -196 g_error_free (err); -197 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR); -198 } else { -199 gtk_text_buffer_set_text (tb, contents, length); -200 g_free (contents); -201 if (G_IS_FILE (tv->file)) -202 g_object_unref (tv->file); -203 tv->file = file; -204 gtk_text_buffer_set_modified (tb, FALSE); -205 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS); -206 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0); -207 } -208 gtk_window_destroy (GTK_WINDOW (dialog)); -209 } -210 -211 void -212 tfe_text_view_open (TfeTextView *tv, GtkWidget *win) { -213 g_return_if_fail (TFE_IS_TEXT_VIEW (tv)); -214 g_return_if_fail (GTK_IS_WINDOW (win)); -215 -216 GtkWidget *dialog; -217 -218 dialog = gtk_file_chooser_dialog_new ("Open file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_OPEN, -219 "Cancel", GTK_RESPONSE_CANCEL, -220 "Open", GTK_RESPONSE_ACCEPT, -221 NULL); -222 g_signal_connect (dialog, "response", G_CALLBACK (open_dialog_response), tv); -223 gtk_widget_show (dialog); -224 } -225 -226 GtkWidget * -227 tfe_text_view_new (void) { -228 return GTK_WIDGET (g_object_new (TFE_TYPE_TEXT_VIEW, NULL)); -229 } -230 +154 GtkWidget *tv; +155 GtkTextBuffer *tb; +156 char *contents; +157 gsize length; +158 +159 if (! g_file_load_contents (file, NULL, &contents, &length, NULL, NULL)) /* read error */ +160 return NULL; +161 +162 tv = tfe_text_view_new(); +163 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv)); +164 gtk_text_buffer_set_text (tb, contents, length); +165 g_free (contents); +166 TFE_TEXT_VIEW (tv)->file = g_file_dup (file); +167 return tv; +168 } +169 +170 static void +171 open_dialog_response(GtkWidget *dialog, gint response, TfeTextView *tv) { +172 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv)); +173 GFile *file; +174 char *contents; +175 gsize length; +176 GtkWidget *message_dialog; +177 GError *err = NULL; +178 +179 if (response != GTK_RESPONSE_ACCEPT) +180 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_CANCEL); +181 else if (! G_IS_FILE (file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)))) { +182 g_warning ("TfeTextView: gtk_file_chooser_get_file returns non GFile object.\n"); +183 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR); +184 } else if (! g_file_load_contents (file, NULL, &contents, &length, NULL, &err)) { /* read error */ +185 if (G_IS_FILE (file)) +186 g_object_unref (file); +187 message_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL, +188 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, +189 "%s.\n", err->message); +190 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL); +191 gtk_widget_show (message_dialog); +192 g_error_free (err); +193 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR); +194 } else { +195 gtk_text_buffer_set_text (tb, contents, length); +196 g_free (contents); +197 if (G_IS_FILE (tv->file)) +198 g_object_unref (tv->file); +199 tv->file = file; +200 gtk_text_buffer_set_modified (tb, FALSE); +201 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS); +202 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0); +203 } +204 gtk_window_destroy (GTK_WINDOW (dialog)); +205 } +206 +207 void +208 tfe_text_view_open (TfeTextView *tv, GtkWindow *win) { +209 g_return_if_fail (TFE_IS_TEXT_VIEW (tv)); +210 g_return_if_fail (GTK_IS_WINDOW (win)); +211 +212 GtkWidget *dialog; +213 +214 dialog = gtk_file_chooser_dialog_new ("Open file", win, GTK_FILE_CHOOSER_ACTION_OPEN, +215 "Cancel", GTK_RESPONSE_CANCEL, +216 "Open", GTK_RESPONSE_ACCEPT, +217 NULL); +218 g_signal_connect (dialog, "response", G_CALLBACK (open_dialog_response), tv); +219 gtk_widget_show (dialog); +220 } +221 +222 GtkWidget * +223 tfe_text_view_new (void) { +224 return GTK_WIDGET (g_object_new (TFE_TYPE_TEXT_VIEW, NULL)); +225 } +226 ~~~ ## Total number of lines, words and characters @@ -682,12 +678,12 @@ $ LANG=C wc tfe5/meson.build tfe5/tfeapplication.c tfe5/tfe.gresource.xml tfe5/t 97 301 3159 tfe5/tfeapplication.c 6 9 153 tfe5/tfe.gresource.xml 4 6 87 tfe5/tfe.h - 140 373 3580 tfe5/tfenotebook.c + 140 374 3593 tfe5/tfenotebook.c 15 21 241 tfe5/tfenotebook.h - 230 686 8144 tfetextview/tfetextview.c + 226 677 8023 tfetextview/tfetextview.c 35 60 701 tfetextview/tfetextview.h 61 100 2073 tfe5/tfe.ui - 598 1573 18432 total + 594 1565 18324 total ~~~ diff --git a/gfm/sec23.md b/gfm/sec23.md index 700b4ee..5bab48f 100644 --- a/gfm/sec23.md +++ b/gfm/sec23.md @@ -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) diff --git a/src/sec10.src.md b/src/sec10.src.md index 9a0d335..19e255c 100644 --- a/src/sec10.src.md +++ b/src/sec10.src.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. diff --git a/src/sec12.src.md b/src/sec12.src.md index d2faa98..e7c36a9 100644 --- a/src/sec12.src.md +++ b/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,30 +173,30 @@ Otherwise probably bad things will happen. tfetextview/tfetextview.c open_dialog_response tfe_text_view_open @@@ -- 37-50: `tfe_text_view_open` function. -- 44-47: Generates GtkFileChooserDialog. +- 38-51: `tfe_text_view_open` function. +- 45-48: Creates GtkFileChooserDialog. The title is "Open file". -Transient parent window is the top window of the application, which is given by the caller. +Transient parent window is the top level window of the application, which is given by the caller. The action is open mode. The buttons are Cancel and Open. -- 48: connects the "response" signal of the dialog and `open_dialog_response` signal handler. -- 49: Shows the dialog. -- 1-35: `open_dialog_response` signal handler. -- 10-11: If the response from GtkFileChooserDialog is not `GTK_RESPONSE_ACCEPT`, which means the user has clicked on the "Cancel" button or close button, then it emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_CANCEL`. -- 12-13: Gets a pointer to Gfile by `gtk_file_chooser_get_file`. -If it is not GFile, maybe an error occurred. +- 49: connects the "response" signal of the dialog and `open_dialog_response` signal handler. +- 50: Shows the dialog. +- 1-36: `open_dialog_response` signal handler. +- 10-11: If the response from GtkFileChooserDialog is not `GTK_RESPONSE_ACCEPT`, which means the user has clicked on the "Cancel" button or close button on the header bar, then it emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_CANCEL`. +- 12-14: Gets a pointer to Gfile by `gtk_file_chooser_get_file`. +If it doesn't point GFile, maybe an error occurred. Then it emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_ERROR`. -- 14-23: If an error occurs at file reading, then it decreases the reference count of the Gfile, shows a message dialog to report the error to the user and emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_ERROR`. -- 24-33: If the file has successfully read, then the text is inserted to GtkTextBuffer, frees the temporary buffer pointed by `contents` and sets `tv->file` to point the file (no duplication or unref is not necessary). +- 15-24: If an error occurs at file reading, then it decreases the reference count of the Gfile, shows a message dialog to report the error to the user and emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_ERROR`. +- 25-33: If the file has successfully been read, then the text is inserted to GtkTextBuffer, frees the temporary buffer pointed by `contents` and sets `tv->file` to point the file (no duplication or unref is not necessary). Then, it emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_SUCCESS` and emits "change-file" signal. -- 34: closes GtkFileCooserDialog. +- 35: closes GtkFileCooserDialog. Now let's think about the whole process between the other object (caller) and TfeTextView. It is shown in the following diagram and you would think that it is really complicated. Because signal is the only way for GtkFileChooserDialog to communicate with others. In Gtk3, `gtk_dialog_run` function is available. It simplifies the process. -However, in Gtk4, `gtk_dialog_run`is unavailable any more. +However, in Gtk4, `gtk_dialog_run` is unavailable any more. ![Caller and TfeTextView](../image/open.png){width=12.405cm height=9.225cm} @@ -196,7 +209,7 @@ However, in Gtk4, `gtk_dialog_run`is unavailable any more. ## Get file function -`gtk_text_view_get_file` is a simple function show as follows. +`gtk_text_view_get_file` is a simple function shown as follows. @@@include tfetextview/tfetextview.c tfe_text_view_get_file diff --git a/src/tfe5/tfenotebook.c b/src/tfe5/tfenotebook.c index 96fe5ba..0c45557 100644 --- a/src/tfe5/tfenotebook.c +++ b/src/tfe5/tfenotebook.c @@ -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 diff --git a/src/tfe6/tfenotebook.c b/src/tfe6/tfenotebook.c index b9838c6..ff40960 100644 --- a/src/tfe6/tfenotebook.c +++ b/src/tfe6/tfenotebook.c @@ -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 diff --git a/src/tfe7/tfenotebook.c b/src/tfe7/tfenotebook.c index 1f56d5c..f67dea4 100644 --- a/src/tfe7/tfenotebook.c +++ b/src/tfe7/tfenotebook.c @@ -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 diff --git a/src/tfetextview/tfetextview.c b/src/tfetextview/tfetextview.c index dba2542..5750441 100644 --- a/src/tfetextview/tfetextview.c +++ b/src/tfetextview/tfetextview.c @@ -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); diff --git a/src/tfetextview/tfetextview.h b/src/tfetextview/tfetextview.h index 521a25c..3797a7a 100644 --- a/src/tfetextview/tfetextview.h +++ b/src/tfetextview/tfetextview.h @@ -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); diff --git a/src/turtle/turtleapplication.c b/src/turtle/turtleapplication.c index ed5a80b..f13d51f 100644 --- a/src/turtle/turtleapplication.c +++ b/src/turtle/turtleapplication.c @@ -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