From cbd454c85bda66e1b1aa34ff1e8ee698c68fa1e5 Mon Sep 17 00:00:00 2001 From: Toshio Sekiya Date: Wed, 16 Jun 2021 21:32:44 +0900 Subject: [PATCH] Modify section 8 and 9. --- gfm/sec8.md | 314 +++++++++++++++++++++++-------------------- gfm/sec9.md | 73 +++++----- image/child.png | Bin 10879 -> 21388 bytes image/child.xcf | Bin 0 -> 28041 bytes src/sec8.src.md | 68 +++++----- src/sec9.src.md | 53 ++++---- src/tfe/taketori.txt | 4 + src/tfe/tfe1.c | 32 +++-- src/tfe/tfe2.c | 18 +-- src/tfe/tfe3.c | 18 +-- src/tfe/tfe3_r.c | 18 +-- 11 files changed, 313 insertions(+), 285 deletions(-) create mode 100644 image/child.xcf create mode 100644 src/tfe/taketori.txt diff --git a/gfm/sec8.md b/gfm/sec8.md index bcb5e9b..4a34229 100644 --- a/gfm/sec8.md +++ b/gfm/sec8.md @@ -9,16 +9,16 @@ Now we go on to rewrite it and make a very simple editor. Its source file name is tfe1.c (text file editor 1). GtkTextView originally has a feature of multi line editing. -Therefore, we don't need to rewrite the program from scratch. +Therefore, we don't need to write the program from scratch. We just add two things to the file viewer. -- Static memory is needed to store a pointer to GFile. -- We need to implement file write function. +- Memory to store a pointer to the GFile instance. +- A function to write the file. A couple of ways are possible to get memories to keep GFile. - Use global variables. -- make a child object so that it can extend the memories for the GFile object. +- make a child object so that it can extend the instance memory for the GFile object. Using global variables is easy to implement. Define a sufficient size array of pointers to GFile. @@ -28,13 +28,13 @@ For example, GFile *f[20]; ~~~ -And `f[i]` corresponds to i-th GtkNotebookPage. +And `f[i]` corresponds to the i-th GtkNotebookPage. However, there are two problems. One is the size of the array. -If a user gives arguments more than that, bad thing may happen. +If a user gives arguments more than that (20 in the example above), it is impossible to store all the pointers to the GFile instances. The other is the difficulty of maintenance of the program. It is a small program so far. -However, if you continue developing it, then its size grows bigger and bigger. +However, if you continue developing it, then the size of the program grows bigger and bigger. Generally speaking, the bigger the program size, the more difficult to maintain global variables. Making child object is a good idea in terms of maintenance. @@ -43,7 +43,7 @@ What we are thinking about now is "child object". A child object includes its parent object. And a child object derives everything from the parent object. -![Child widget of GtkTextView](../image/child.png) +![Child object of GtkTextView](../image/child.png) We will define TfeTextView as a child object of GtkTextView. It has everything that GtkTextView has. @@ -52,8 +52,9 @@ And important thing is that TfeTextView can have a memory to keep a pointer to G However, to understand the general theory about Gobject is very hard especially for beginners. So, I will just show you the way how to write the code and avoid the theoretical side in the next subsection. +If you want to know about GObject system, refer to [GObject tutorial](https://github.com/ToshioCP/Gobject-tutorial). -## How to define a child widget of GtkTextView +## How to define a child object of GtkTextView Let's define TfeTextView object which is a child object of GtkTextView. First, look at the program below. @@ -98,6 +99,7 @@ If you are curious about the background theory of this program, It's very good f Because knowing the theory is very important for you to program GTK applications. Look at [GObject API reference](https://developer.gnome.org/gobject/stable/). All you need is described in it. +Or, refer to [GObject tutorial](https://github.com/ToshioCP/Gobject-tutorial). However, it's a tough journey especially for beginners. For now, you don't need to know such difficult theory. Just remember the instructions below. @@ -107,8 +109,8 @@ Tfe and TextView. Tfe is called prefix, namespace or module. TextView is called object. - There are three patterns. -TfeTextView (camel case), tfe\_text\_view (this is used to write functions) and TFE\_TEXT\_VIEW (This is used to write casts). -- First, define TFE\_TYPE\_TEXT\_VIEW as tfe\_text\_view\_get\_type (). +TfeTextView (camel case), tfe\_text\_view (this is used to write functions) and TFE\_TEXT\_VIEW (This is used to cast a pointer to point TfeTextView type). +- First, define TFE\_TYPE\_TEXT\_VIEW macro as tfe\_text\_view\_get\_type (). The name is always (prefix)\_TYPE\_(object) and the letters are upper case. And the replacement text is always (prefix)\_(object)\_get\_type () and the letters are lower case. - Next, use G\_DECLARE\_FINAL\_TYPE macro. @@ -118,31 +120,30 @@ The underscore is necessary. The first member is the parent object. Notice this is not a pointer but the object itself. The second member and after are members of the child object. -TfeTextView structure has a pointer to GFile as a member. +TfeTextView structure has a pointer to a GFile instance as a member. - Use G\_DEFINE\_TYPE macro. The arguments are the child object name in camel case, lower case with underscore and parent object type (prefix)\_TYPE\_(module). - Define instance init function (tfe\_text\_view\_init). Usually you don't need to do anything. - Define class init function (tfe\_text\_view\_class\_init). -You don't need to do anything in this widget. +You don't need to do anything in this object. - Write function codes you want to add (tfe\_text\_view\_set\_file and tfe\_text\_view\_get\_file). -`tv` is a pointer to TfeTextView object instance which is a C-structure declared with the tag \_TfeTextView. -So, the structure has a member `file` as a pointer to GFile. +`tv` is a pointer to the TfeTextView object instance which is a C-structure declared with the tag \_TfeTextView. +So, the structure has a member `file` as a pointer to a GFile instance. `tv->file = f` is an assignment of `f` to a member `file` of the structure pointed by `tv`. This is an example how to use the extended memory in a child widget. -- Write object generation function. +- Write a function to create an instance. Its name is (prefix)\_(object)\_new. If the parent object function needs parameters, this function also need them. You sometimes might want to add some parameters. It's your choice. -Use g\_object\_new function to generate the child widget. +Use g\_object\_new function to create the instance. The arguments are (prefix)\_TYPE\_(object), a list to initialize properties and NULL. In this code no property needs to be initialized. -And the return value must be casted to GtkWidget. +And the return value is casted to GtkWidget. This program is not perfect. It has some problems. -But I don't discuss them now. It will be modified later. ## Close-request signal @@ -150,7 +151,7 @@ It will be modified later. Imagine that you use this editor. First, you run the editor with arguments. The arguments are filenames. -The editor reads the files and shows its window with the text of files in it. +The editor reads the files and shows the window with the text of files in it. Then you edit the text. After you finish editing, you exit the editor. The editor updates files just before the window closes. @@ -158,7 +159,7 @@ The editor updates files just before the window closes. GtkWindow emits "close-request" signal before it closes. We connect the signal and the handler `before_close`. A handler is a C function. -When a function is connected to a certain signal, we call it handler. +When a function is connected to a certain signal, we call it a handler. The function `before_close` is invoked when the signal "close-request" is emitted. ~~~C @@ -171,46 +172,54 @@ The program of `before_close` is as follows. ~~~C 1 static gboolean - 2 before_close (GtkWindow *win, GtkWidget *nb) { - 3 GtkWidget *scr; - 4 GtkWidget *tv; - 5 GFile *file; - 6 GtkTextBuffer *tb; - 7 GtkTextIter start_iter; - 8 GtkTextIter end_iter; - 9 char *contents; -10 unsigned int n; -11 unsigned int i; -12 -13 n = gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)); -14 for (i = 0; i < n; ++i) { -15 scr = gtk_notebook_get_nth_page (GTK_NOTEBOOK (nb), i); -16 tv = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (scr)); -17 file = tfe_text_view_get_file (TFE_TEXT_VIEW (tv)); -18 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv)); -19 gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter); -20 contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE); -21 if (! g_file_replace_contents (file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, NULL)) -22 g_print ("ERROR : Can't save %s.", g_file_get_path (file)); -23 } -24 return FALSE; -25 } + 2 before_close (GtkWindow *win, gpointer user_data) { + 3 GtkWidget *nb = GTK_WIDGET (user_data); + 4 GtkWidget *scr; + 5 GtkWidget *tv; + 6 GFile *file; + 7 char *pathname; + 8 GtkTextBuffer *tb; + 9 GtkTextIter start_iter; +10 GtkTextIter end_iter; +11 char *contents; +12 unsigned int n; +13 unsigned int i; +14 +15 n = gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)); +16 for (i = 0; i < n; ++i) { +17 scr = gtk_notebook_get_nth_page (GTK_NOTEBOOK (nb), i); +18 tv = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (scr)); +19 file = tfe_text_view_get_file (TFE_TEXT_VIEW (tv)); +20 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv)); +21 gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter); +22 contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE); +23 if (! g_file_replace_contents (file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, NULL)) { +24 pathname = g_file_get_path (file); +25 g_print ("ERROR : Can't save %s.", pathname); +26 g_free (pathname); +27 } +28 g_free (contents); +29 } +30 return FALSE; +31 } ~~~ The numbers on the left of items are line numbers in the source code. -- 13: Gets the number of pages `nb` has. -- 14-23: For loop with regard to the index to each pages. -- 15-17: Gets GtkScrolledWindow, TfeTextView and a pointer to GFile. -The pointer was stored when `on_open` handler had run. It will be shown later. -- 18-20: Gets GtkTextBuffer and contents. `start_iter` and `end_iter` are iterators of the buffer. +- 15: Gets the number of pages `nb` has. +- 16-29: For loop with regard to the index to each pages. +- 17-19: Gets GtkScrolledWindow, TfeTextView and a pointer to GFile. +The pointer was stored when `app_open` handler had run. It will be shown later. +- 20-22: Gets GtkTextBuffer and contents. `start_iter` and `end_iter` are iterators of the buffer. I don't want to explain them now because it would take a lot of time. Just remember these lines for the present. -- 21: Writes the file. +- 23-27: Writes the contents to the file. +If it fails, it outputs an error message. +- 28: Frees `contents`. ## Source code of tfe1.c -Now I will show you all the source code of `tfe1`.c. +Now I will show you all the source code of `tfe1.c`. ~~~C 1 #include @@ -254,113 +263,120 @@ Now I will show you all the source code of `tfe1`.c. 39 /* ---------- end of the definition of TfeTextView ---------- */ 40 41 static gboolean - 42 before_close (GtkWindow *win, GtkWidget *nb) { - 43 GtkWidget *scr; - 44 GtkWidget *tv; - 45 GFile *file; - 46 GtkTextBuffer *tb; - 47 GtkTextIter start_iter; - 48 GtkTextIter end_iter; - 49 char *contents; - 50 unsigned int n; - 51 unsigned int i; - 52 - 53 n = gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)); - 54 for (i = 0; i < n; ++i) { - 55 scr = gtk_notebook_get_nth_page (GTK_NOTEBOOK (nb), i); - 56 tv = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (scr)); - 57 file = tfe_text_view_get_file (TFE_TEXT_VIEW (tv)); - 58 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv)); - 59 gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter); - 60 contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE); - 61 if (! g_file_replace_contents (file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, NULL)) - 62 g_print ("ERROR : Can't save %s.", g_file_get_path (file)); - 63 } - 64 return FALSE; - 65 } - 66 - 67 static void - 68 on_activate (GApplication *app, gpointer user_data) { - 69 g_print ("You need to give filenames as arguments.\n"); - 70 } - 71 - 72 static void - 73 on_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) { - 74 GtkWidget *win; - 75 GtkWidget *nb; - 76 GtkWidget *lab; - 77 GtkNotebookPage *nbp; - 78 GtkWidget *scr; - 79 GtkWidget *tv; - 80 GtkTextBuffer *tb; - 81 char *contents; - 82 gsize length; - 83 char *filename; - 84 int i; - 85 - 86 win = gtk_application_window_new (GTK_APPLICATION (app)); - 87 gtk_window_set_title (GTK_WINDOW (win), "file editor"); - 88 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300); - 89 gtk_window_maximize (GTK_WINDOW (win)); - 90 - 91 nb = gtk_notebook_new (); - 92 gtk_window_set_child (GTK_WINDOW (win), nb); - 93 - 94 for (i = 0; i < n_files; i++) { - 95 if (g_file_load_contents (files[i], NULL, &contents, &length, NULL, NULL)) { - 96 scr = gtk_scrolled_window_new (); - 97 tv = tfe_text_view_new (); - 98 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv)); - 99 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR); -100 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv); -101 -102 tfe_text_view_set_file (TFE_TEXT_VIEW (tv), g_file_dup (files[i])); -103 gtk_text_buffer_set_text (tb, contents, length); -104 g_free (contents); -105 filename = g_file_get_basename (files[i]); -106 lab = gtk_label_new (filename); -107 gtk_notebook_append_page (GTK_NOTEBOOK (nb), scr, lab); -108 nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr); -109 g_object_set (nbp, "tab-expand", TRUE, NULL); -110 g_free (filename); -111 } else { -112 filename = g_file_get_path (files[i]); -113 g_print ("No such file: %s.\n", filename); -114 g_free (filename); -115 } -116 } -117 if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) { -118 g_signal_connect (win, "close-request", G_CALLBACK (before_close), nb); -119 gtk_widget_show (win); -120 } else -121 gtk_window_destroy (GTK_WINDOW (win)); -122 } -123 -124 int -125 main (int argc, char **argv) { -126 GtkApplication *app; -127 int stat; + 42 before_close (GtkWindow *win, gpointer user_data) { + 43 GtkWidget *nb = GTK_WIDGET (user_data); + 44 GtkWidget *scr; + 45 GtkWidget *tv; + 46 GFile *file; + 47 char *pathname; + 48 GtkTextBuffer *tb; + 49 GtkTextIter start_iter; + 50 GtkTextIter end_iter; + 51 char *contents; + 52 unsigned int n; + 53 unsigned int i; + 54 + 55 n = gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)); + 56 for (i = 0; i < n; ++i) { + 57 scr = gtk_notebook_get_nth_page (GTK_NOTEBOOK (nb), i); + 58 tv = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (scr)); + 59 file = tfe_text_view_get_file (TFE_TEXT_VIEW (tv)); + 60 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv)); + 61 gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter); + 62 contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE); + 63 if (! g_file_replace_contents (file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, NULL)) { + 64 pathname = g_file_get_path (file); + 65 g_print ("ERROR : Can't save %s.", pathname); + 66 g_free (pathname); + 67 } + 68 g_free (contents); + 69 } + 70 return FALSE; + 71 } + 72 + 73 static void + 74 app_activate (GApplication *app, gpointer user_data) { + 75 g_print ("You need to give filenames as arguments.\n"); + 76 } + 77 + 78 static void + 79 app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) { + 80 GtkWidget *win; + 81 GtkWidget *nb; + 82 GtkWidget *lab; + 83 GtkNotebookPage *nbp; + 84 GtkWidget *scr; + 85 GtkWidget *tv; + 86 GtkTextBuffer *tb; + 87 char *contents; + 88 gsize length; + 89 char *filename; + 90 int i; + 91 + 92 win = gtk_application_window_new (GTK_APPLICATION (app)); + 93 gtk_window_set_title (GTK_WINDOW (win), "file editor"); + 94 gtk_window_maximize (GTK_WINDOW (win)); + 95 + 96 nb = gtk_notebook_new (); + 97 gtk_window_set_child (GTK_WINDOW (win), nb); + 98 + 99 for (i = 0; i < n_files; i++) { +100 if (g_file_load_contents (files[i], NULL, &contents, &length, NULL, NULL)) { +101 scr = gtk_scrolled_window_new (); +102 tv = tfe_text_view_new (); +103 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv)); +104 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR); +105 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv); +106 +107 tfe_text_view_set_file (TFE_TEXT_VIEW (tv), g_file_dup (files[i])); +108 gtk_text_buffer_set_text (tb, contents, length); +109 g_free (contents); +110 filename = g_file_get_basename (files[i]); +111 lab = gtk_label_new (filename); +112 gtk_notebook_append_page (GTK_NOTEBOOK (nb), scr, lab); +113 nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr); +114 g_object_set (nbp, "tab-expand", TRUE, NULL); +115 g_free (filename); +116 } else if ((filename = g_file_get_path (files[i])) != NULL) { +117 g_print ("No such file: %s.\n", filename); +118 g_free (filename); +119 } else +120 g_print ("No valid file is given\n"); +121 } +122 if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) { +123 g_signal_connect (win, "close-request", G_CALLBACK (before_close), nb); +124 gtk_widget_show (win); +125 } else +126 gtk_window_destroy (GTK_WINDOW (win)); +127 } 128 -129 app = gtk_application_new ("com.github.ToshioCP.tfe1", G_APPLICATION_HANDLES_OPEN); -130 g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL); -131 g_signal_connect (app, "open", G_CALLBACK (on_open), NULL); -132 stat =g_application_run (G_APPLICATION (app), argc, argv); -133 g_object_unref (app); -134 return stat; -135 } -136 +129 int +130 main (int argc, char **argv) { +131 GtkApplication *app; +132 int stat; +133 +134 app = gtk_application_new ("com.github.ToshioCP.tfe1", G_APPLICATION_HANDLES_OPEN); +135 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL); +136 g_signal_connect (app, "open", G_CALLBACK (app_open), NULL); +137 stat =g_application_run (G_APPLICATION (app), argc, argv); +138 g_object_unref (app); +139 return stat; +140 } ~~~ -- 102: Sets the pointer to GFile into TfeTextView. +- 107: Sets the pointer to GFile into TfeTextView. `files[i]` is a pointer to GFile structure. It will be freed by the system. So you need to copy it. `g_file_dup` duplicates the given GFile structure. -- 118: Connects "close-request" signal and `before_close` handler. +- 123: Connects "close-request" signal and `before_close` handler. The fourth argument is called user data and it is given to the signal handler. So, `nb` is given to `before_close` as the second argument. Now compile and run it. -Type `./a.out somefile` and make sure that the file is modified. +There's a sample file in the directory `tfe`. +Type `./a.out taketori.txt`. +Modify the contents and close the window. +Make sure that the file is modified. Now we got a very simple editor. It's not smart. diff --git a/gfm/sec9.md b/gfm/sec9.md index 7360c80..cf530a4 100644 --- a/gfm/sec9.md +++ b/gfm/sec9.md @@ -5,7 +5,7 @@ Up: [Readme.md](../Readme.md), Prev: [Section 8](sec8.md), Next: [Section 10](s ## New, open and save button We made the simplest editor in the previous section. -It reads the files in `on_open` function at start-up and writes them when closing the window. +It reads the files in `app_open` function at start-up and writes them when closing the window. It works but is not good. It is better to make "New", "Open", "Save" and "Close" buttons. This section describes how to put those buttons into the window. @@ -14,11 +14,11 @@ Signals and handlers will be explained later. ![Screenshot of the file editor](../image/screenshot_tfe2.png) The screenshot above shows the layout. -The function `on_open` in the source code `tfe2.c` is as follows. +The function `app_open` in the source code `tfe2.c` is as follows. ~~~C 1 static void - 2 on_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) { + 2 app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) { 3 GtkWidget *win; 4 GtkWidget *nb; 5 GtkWidget *lab; @@ -92,11 +92,11 @@ The function `on_open` in the source code `tfe2.c` is as follows. 73 nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr); 74 g_object_set (nbp, "tab-expand", TRUE, NULL); 75 g_free (filename); -76 } else { -77 filename = g_file_get_path (files[i]); -78 g_print ("No such file: %s.\n", filename); -79 g_free (filename); -80 } +76 } else if ((filename = g_file_get_path (files[i])) != NULL) { +77 g_print ("No such file: %s.\n", filename); +78 g_free (filename); +79 } else +80 g_print ("No valid file is given\n"); 81 } 82 if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) { 83 gtk_widget_show (win); @@ -107,25 +107,25 @@ The function `on_open` in the source code `tfe2.c` is as follows. The point is how to build the window. -- 25-27: Generates GtkApplicationWindow and sets the title and default size. -- 29-30: Generates GtkBox `boxv`. +- 25-27: Creates a GtkApplicationWindow instance and sets the title and default size. +- 29-30: Creates a GtkBox instance `boxv`. It is a vertical box and a child of GtkApplicationWindow. It has two children. -The first child is a horizontal box includes buttons. -The second child is GtkNotebook. -- 32-33: Generates GtkBox `boxh` and appends it to 'boxv' as a first child. -- 35-40: Generates three dummy labels. +The first child is a horizontal box. +The second child is a GtkNotebook. +- 32-33: Creates a GtkBox instance `boxh` and appends it to `boxv` as a first child. +- 35-40: Creates three dummy labels. The labels `dmy1` and `dmy3` has a character width of ten. The other label `dmy2` has hexpand property which is set to be TRUE. This makes the label expands horizontally as long as possible. -- 41-44: Generates four buttons. +- 41-44: Creates four buttons. - 46-52: Appends these GtkLabel and GtkButton to `boxh`. -- 54-57: Generates GtkNotebook and sets hexpand and vexpand properties TRUE. +- 54-57: Creates a GtkNotebook instance and sets hexpand and vexpand properties TRUE. This makes it expand horizontally and vertically as big as possible. It is appended to `boxv` as the second child. The number of lines is 33(=57-25+1) to build the widgets. -And we needed many variables (boxv, boxh, dmy1 ...). +And we needed many variables (`boxv`, `boxh`, `dmy1`, ...). Most of them aren't necessary except building the widgets. Are there any good solution to reduce these work? @@ -199,7 +199,7 @@ First, let's look at the ui file `tfe3.ui` that defines a structure of the widge 59 ~~~ -This is coded with XML structure. +The structure of this file is XML. Constructs beginning with `<` and ending with `>` are called tags. And there are two types of tags, start tag and end tag. For example, `` is a start tag and `` is an end tag. @@ -209,15 +209,15 @@ Some tags, for example, object tags can have a class and id attributes in the st - 1: The first line is XML declaration. It specifies that the version of XML is 1.0 and the encoding is UTF-8. Even if the line is left out, GtkBuilder builds objects from the ui file. -But ui files must use UTF-8 encoding, or GtkBuilder can't recognize it and fatal error occurs. +But ui files must use UTF-8 encoding, or GtkBuilder can't recognize it and a fatal error occurs. - 3-6: An object with `GtkApplicationWindow` class and `win` id is defined. This is the top level window. And the three properties of the window are defined. -`title` property is "file editor", `default-width` property is 400 and `default-height` property is 300. -- 7: child tag means a child of the object above. -For example, line 7 tells us that GtkBox object which id is "boxv" is a child of `win`. +`title` property is "file editor", `default-width` property is 600 and `default-height` property is 400. +- 7: child tag means a child of the widget above. +For example, line 7 tells us that GtkBox object which id is "boxv" is a child widget of `win`. -Compare this ui file and the lines 25-57 in the source code of `on_open` function. +Compare this ui file and the lines 25-57 in the source code of `app_open` function. Those two describe the same structure of widgets. You can check the ui file with `gtk4-builder-tool`. @@ -227,7 +227,7 @@ If the ui file includes some syntactical error, `gtk4-builder-tool` prints the e - `gtk4-builder-tool simplify ` simplifies the ui file and prints the result. If `--replace` option is given, it replaces the ui file with the simplified one. If the ui file specifies a value of property but it is default, then it will be removed. -Anf some values are simplified. +And some values are simplified. For example, "TRUE"and "FALSE" becomes "1" and "0" respectively. However, "TRUE" or "FALSE" is better for maintenance. @@ -247,7 +247,7 @@ nb = GTK_WIDGET (gtk_builder_get_object (build, "nb")); ~~~ The function `gtk_builder_new_from_file` reads the file given as an argument. -Then, it builds the widgets and pointers to them, creates GtkBuilder object and put the pointers in it. +Then, it builds the widgets and creates GtkBuilder object. The function `gtk_builder_get_object (build, "win")` returns the pointer to the widget `win`, which is the id in the ui file. All the widgets are connected based on the parent-children relationship described in the ui file. We only need `win` and `nb` for the program after this, so we don't need to take out any other widgets. @@ -314,15 +314,15 @@ $ cd tfe; diff tfe2.c tfe3.c > app = gtk_application_new ("com.github.ToshioCP.tfe3", G_APPLICATION_HANDLES_OPEN); ~~~ -`60,103c61,65` means 42 (=103-60+1) lines change to 5 (=65-61+1) lines. -Therefore 37 lines are reduced. +`60,103c61,65` means 44 (=103-60+1) lines are changed to 5 (=65-61+1) lines. +Therefore, 39 lines are reduced. Using ui file not only shortens C source files, but also makes the widgets' structure clear. -Now I'll show you `on_open` function in the C source code `tfe3.c`. +Now I'll show you `app_open` function in the C file `tfe3.c`. ~~~C 1 static void - 2 on_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) { + 2 app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) { 3 GtkWidget *win; 4 GtkWidget *nb; 5 GtkWidget *lab; @@ -358,11 +358,11 @@ Now I'll show you `on_open` function in the C source code `tfe3.c`. 35 nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr); 36 g_object_set (nbp, "tab-expand", TRUE, NULL); 37 g_free (filename); -38 } else { -39 filename = g_file_get_path (files[i]); -40 g_print ("No such file: %s.\n", filename); -41 g_free (filename); -42 } +38 } else if ((filename = g_file_get_path (files[i])) != NULL) { +39 g_print ("No such file: %s.\n", filename); +40 g_free (filename); +41 } else +42 g_print ("No valid file is given\n"); 43 } 44 if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) { 45 gtk_widget_show (win); @@ -373,12 +373,11 @@ Now I'll show you `on_open` function in the C source code `tfe3.c`. The whole source code of `tfe3.c` is stored in [src/tfe](https://github.com/ToshioCP/Gtk4-tutorial/tree/main/src/tfe) directory. If you want to see it, click the link above. -You can also get the source files below. ### Using ui string GtkBuilder can build widgets using string. -Use the function gtk\_builder\_new\_from\_string instead of gtk\_builder\_new\_from\_file. +Use the function `gtk_builder_new_from_string` instead of `gtk_builder_new_from_file`. ~~~C char *uistring; @@ -406,7 +405,7 @@ The disadvantage is that writing C string is a bit bothersome because of the dou If you want to use this method, you should write a script that transforms ui file into C-string. - Add backslash before each double quote. -- Add double quote at the left and right. +- Add double quotes at the left and right of the string in each line. ### Using Gresource diff --git a/image/child.png b/image/child.png index 8a733ed5e06ef6c9a0f8518cd1d4883212f9428e..8e0d713043bb216f128ad23d4a7dd455428a55a8 100644 GIT binary patch literal 21388 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 X7;34}JKFwWAOtEdFIMp4<=g)Opbdfr literal 10879 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 diff --git a/image/child.xcf b/image/child.xcf new file mode 100644 index 0000000000000000000000000000000000000000..a8647de38f88daf91dee07e154b1d3ffcbe6279f GIT binary patch literal 28041 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 = f` is an assignment of `f` to a member `file` of the structure pointed by `tv`. This is an example how to use the extended memory in a child widget. -- Write object generation function. +- Write a function to create an instance. Its name is (prefix)\_(object)\_new. If the parent object function needs parameters, this function also need them. You sometimes might want to add some parameters. It's your choice. -Use g\_object\_new function to generate the child widget. +Use g\_object\_new function to create the instance. The arguments are (prefix)\_TYPE\_(object), a list to initialize properties and NULL. In this code no property needs to be initialized. -And the return value must be casted to GtkWidget. +And the return value is casted to GtkWidget. This program is not perfect. It has some problems. -But I don't discuss them now. It will be modified later. ## Close-request signal @@ -148,7 +149,7 @@ It will be modified later. Imagine that you use this editor. First, you run the editor with arguments. The arguments are filenames. -The editor reads the files and shows its window with the text of files in it. +The editor reads the files and shows the window with the text of files in it. Then you edit the text. After you finish editing, you exit the editor. The editor updates files just before the window closes. @@ -156,7 +157,7 @@ The editor updates files just before the window closes. GtkWindow emits "close-request" signal before it closes. We connect the signal and the handler `before_close`. A handler is a C function. -When a function is connected to a certain signal, we call it handler. +When a function is connected to a certain signal, we call it a handler. The function `before_close` is invoked when the signal "close-request" is emitted. ~~~C @@ -173,33 +174,38 @@ tfe/tfe1.c before_close The numbers on the left of items are line numbers in the source code. -- 13: Gets the number of pages `nb` has. -- 14-23: For loop with regard to the index to each pages. -- 15-17: Gets GtkScrolledWindow, TfeTextView and a pointer to GFile. -The pointer was stored when `on_open` handler had run. It will be shown later. -- 18-20: Gets GtkTextBuffer and contents. `start_iter` and `end_iter` are iterators of the buffer. +- 15: Gets the number of pages `nb` has. +- 16-29: For loop with regard to the index to each pages. +- 17-19: Gets GtkScrolledWindow, TfeTextView and a pointer to GFile. +The pointer was stored when `app_open` handler had run. It will be shown later. +- 20-22: Gets GtkTextBuffer and contents. `start_iter` and `end_iter` are iterators of the buffer. I don't want to explain them now because it would take a lot of time. Just remember these lines for the present. -- 21: Writes the file. +- 23-27: Writes the contents to the file. +If it fails, it outputs an error message. +- 28: Frees `contents`. ## Source code of tfe1.c -Now I will show you all the source code of `tfe1`.c. +Now I will show you all the source code of `tfe1.c`. @@@include tfe/tfe1.c @@@ -- 102: Sets the pointer to GFile into TfeTextView. +- 107: Sets the pointer to GFile into TfeTextView. `files[i]` is a pointer to GFile structure. It will be freed by the system. So you need to copy it. `g_file_dup` duplicates the given GFile structure. -- 118: Connects "close-request" signal and `before_close` handler. +- 123: Connects "close-request" signal and `before_close` handler. The fourth argument is called user data and it is given to the signal handler. So, `nb` is given to `before_close` as the second argument. Now compile and run it. -Type `./a.out somefile` and make sure that the file is modified. +There's a sample file in the directory `tfe`. +Type `./a.out taketori.txt`. +Modify the contents and close the window. +Make sure that the file is modified. Now we got a very simple editor. It's not smart. diff --git a/src/sec9.src.md b/src/sec9.src.md index a7df680..84d0e5f 100644 --- a/src/sec9.src.md +++ b/src/sec9.src.md @@ -3,7 +3,7 @@ ## New, open and save button We made the simplest editor in the previous section. -It reads the files in `on_open` function at start-up and writes them when closing the window. +It reads the files in `app_open` function at start-up and writes them when closing the window. It works but is not good. It is better to make "New", "Open", "Save" and "Close" buttons. This section describes how to put those buttons into the window. @@ -12,33 +12,33 @@ Signals and handlers will be explained later. ![Screenshot of the file editor](../image/screenshot_tfe2.png){width=9.3cm height=6.825cm} The screenshot above shows the layout. -The function `on_open` in the source code `tfe2.c` is as follows. +The function `app_open` in the source code `tfe2.c` is as follows. @@@include -tfe/tfe2.c on_open +tfe/tfe2.c app_open @@@ The point is how to build the window. -- 25-27: Generates GtkApplicationWindow and sets the title and default size. -- 29-30: Generates GtkBox `boxv`. +- 25-27: Creates a GtkApplicationWindow instance and sets the title and default size. +- 29-30: Creates a GtkBox instance `boxv`. It is a vertical box and a child of GtkApplicationWindow. It has two children. -The first child is a horizontal box includes buttons. -The second child is GtkNotebook. -- 32-33: Generates GtkBox `boxh` and appends it to 'boxv' as a first child. -- 35-40: Generates three dummy labels. +The first child is a horizontal box. +The second child is a GtkNotebook. +- 32-33: Creates a GtkBox instance `boxh` and appends it to `boxv` as a first child. +- 35-40: Creates three dummy labels. The labels `dmy1` and `dmy3` has a character width of ten. The other label `dmy2` has hexpand property which is set to be TRUE. This makes the label expands horizontally as long as possible. -- 41-44: Generates four buttons. +- 41-44: Creates four buttons. - 46-52: Appends these GtkLabel and GtkButton to `boxh`. -- 54-57: Generates GtkNotebook and sets hexpand and vexpand properties TRUE. +- 54-57: Creates a GtkNotebook instance and sets hexpand and vexpand properties TRUE. This makes it expand horizontally and vertically as big as possible. It is appended to `boxv` as the second child. The number of lines is 33(=57-25+1) to build the widgets. -And we needed many variables (boxv, boxh, dmy1 ...). +And we needed many variables (`boxv`, `boxh`, `dmy1`, ...). Most of them aren't necessary except building the widgets. Are there any good solution to reduce these work? @@ -54,7 +54,7 @@ First, let's look at the ui file `tfe3.ui` that defines a structure of the widge tfe/tfe3.ui @@@ -This is coded with XML structure. +The structure of this file is XML. Constructs beginning with `<` and ending with `>` are called tags. And there are two types of tags, start tag and end tag. For example, `` is a start tag and `` is an end tag. @@ -64,15 +64,15 @@ Some tags, for example, object tags can have a class and id attributes in the st - 1: The first line is XML declaration. It specifies that the version of XML is 1.0 and the encoding is UTF-8. Even if the line is left out, GtkBuilder builds objects from the ui file. -But ui files must use UTF-8 encoding, or GtkBuilder can't recognize it and fatal error occurs. +But ui files must use UTF-8 encoding, or GtkBuilder can't recognize it and a fatal error occurs. - 3-6: An object with `GtkApplicationWindow` class and `win` id is defined. This is the top level window. And the three properties of the window are defined. -`title` property is "file editor", `default-width` property is 400 and `default-height` property is 300. -- 7: child tag means a child of the object above. -For example, line 7 tells us that GtkBox object which id is "boxv" is a child of `win`. +`title` property is "file editor", `default-width` property is 600 and `default-height` property is 400. +- 7: child tag means a child of the widget above. +For example, line 7 tells us that GtkBox object which id is "boxv" is a child widget of `win`. -Compare this ui file and the lines 25-57 in the source code of `on_open` function. +Compare this ui file and the lines 25-57 in the source code of `app_open` function. Those two describe the same structure of widgets. You can check the ui file with `gtk4-builder-tool`. @@ -82,7 +82,7 @@ If the ui file includes some syntactical error, `gtk4-builder-tool` prints the e - `gtk4-builder-tool simplify ` simplifies the ui file and prints the result. If `--replace` option is given, it replaces the ui file with the simplified one. If the ui file specifies a value of property but it is default, then it will be removed. -Anf some values are simplified. +And some values are simplified. For example, "TRUE"and "FALSE" becomes "1" and "0" respectively. However, "TRUE" or "FALSE" is better for maintenance. @@ -102,7 +102,7 @@ nb = GTK_WIDGET (gtk_builder_get_object (build, "nb")); ~~~ The function `gtk_builder_new_from_file` reads the file given as an argument. -Then, it builds the widgets and pointers to them, creates GtkBuilder object and put the pointers in it. +Then, it builds the widgets and creates GtkBuilder object. The function `gtk_builder_get_object (build, "win")` returns the pointer to the widget `win`, which is the id in the ui file. All the widgets are connected based on the parent-children relationship described in the ui file. We only need `win` and `nb` for the program after this, so we don't need to take out any other widgets. @@ -112,24 +112,23 @@ This reduces lines in the C source file. cd tfe; diff tfe2.c tfe3.c @@@ -`60,103c61,65` means 42 (=103-60+1) lines change to 5 (=65-61+1) lines. -Therefore 37 lines are reduced. +`60,103c61,65` means 44 (=103-60+1) lines are changed to 5 (=65-61+1) lines. +Therefore, 39 lines are reduced. Using ui file not only shortens C source files, but also makes the widgets' structure clear. -Now I'll show you `on_open` function in the C source code `tfe3.c`. +Now I'll show you `app_open` function in the C file `tfe3.c`. @@@include -tfe/tfe3.c on_open +tfe/tfe3.c app_open @@@ The whole source code of `tfe3.c` is stored in [src/tfe](https://github.com/ToshioCP/Gtk4-tutorial/tree/main/src/tfe) directory. If you want to see it, click the link above. -You can also get the source files below. ### Using ui string GtkBuilder can build widgets using string. -Use the function gtk\_builder\_new\_from\_string instead of gtk\_builder\_new\_from\_file. +Use the function `gtk_builder_new_from_string` instead of `gtk_builder_new_from_file`. ~~~C char *uistring; @@ -157,7 +156,7 @@ The disadvantage is that writing C string is a bit bothersome because of the dou If you want to use this method, you should write a script that transforms ui file into C-string. - Add backslash before each double quote. -- Add double quote at the left and right. +- Add double quotes at the left and right of the string in each line. ### Using Gresource diff --git a/src/tfe/taketori.txt b/src/tfe/taketori.txt new file mode 100644 index 0000000..1db3b29 --- /dev/null +++ b/src/tfe/taketori.txt @@ -0,0 +1,4 @@ +Once upon a time, there was an old man who was called Taketori-no-Okina. It is a japanese word that means a man whose work is making bamboo baskets. +One day, he went into a mountain and found a shining bamboo. "What a mysterious bamboo it is!," he said. He cut it, then there was a small cute baby girl in it. The girl was shining faintly. He thought this baby girl is a gift from Heaven and took her home. +His wife was surprized at his tale. They were very happy because they had no children. + diff --git a/src/tfe/tfe1.c b/src/tfe/tfe1.c index 65f88da..8e384d2 100644 --- a/src/tfe/tfe1.c +++ b/src/tfe/tfe1.c @@ -39,10 +39,12 @@ tfe_text_view_new (void) { /* ---------- end of the definition of TfeTextView ---------- */ static gboolean -before_close (GtkWindow *win, GtkWidget *nb) { +before_close (GtkWindow *win, gpointer user_data) { + GtkWidget *nb = GTK_WIDGET (user_data); GtkWidget *scr; GtkWidget *tv; GFile *file; + char *pathname; GtkTextBuffer *tb; GtkTextIter start_iter; GtkTextIter end_iter; @@ -58,19 +60,23 @@ before_close (GtkWindow *win, GtkWidget *nb) { tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv)); gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter); contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE); - if (! g_file_replace_contents (file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, NULL)) - g_print ("ERROR : Can't save %s.", g_file_get_path (file)); + if (! g_file_replace_contents (file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, NULL)) { + pathname = g_file_get_path (file); + g_print ("ERROR : Can't save %s.", pathname); + g_free (pathname); + } + g_free (contents); } return FALSE; } static void -on_activate (GApplication *app, gpointer user_data) { +app_activate (GApplication *app, gpointer user_data) { g_print ("You need to give filenames as arguments.\n"); } static void -on_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) { +app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) { GtkWidget *win; GtkWidget *nb; GtkWidget *lab; @@ -85,7 +91,6 @@ on_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer win = gtk_application_window_new (GTK_APPLICATION (app)); gtk_window_set_title (GTK_WINDOW (win), "file editor"); - gtk_window_set_default_size (GTK_WINDOW (win), 400, 300); gtk_window_maximize (GTK_WINDOW (win)); nb = gtk_notebook_new (); @@ -108,11 +113,11 @@ on_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr); g_object_set (nbp, "tab-expand", TRUE, NULL); g_free (filename); - } else { - filename = g_file_get_path (files[i]); - g_print ("No such file: %s.\n", filename); - g_free (filename); - } + } else if ((filename = g_file_get_path (files[i])) != NULL) { + g_print ("No such file: %s.\n", filename); + g_free (filename); + } else + g_print ("No valid file is given\n"); } if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) { g_signal_connect (win, "close-request", G_CALLBACK (before_close), nb); @@ -127,10 +132,9 @@ main (int argc, char **argv) { int stat; app = gtk_application_new ("com.github.ToshioCP.tfe1", G_APPLICATION_HANDLES_OPEN); - g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL); - g_signal_connect (app, "open", G_CALLBACK (on_open), NULL); + g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL); + g_signal_connect (app, "open", G_CALLBACK (app_open), NULL); stat =g_application_run (G_APPLICATION (app), argc, argv); g_object_unref (app); return stat; } - diff --git a/src/tfe/tfe2.c b/src/tfe/tfe2.c index 1a17ae2..52eb44c 100644 --- a/src/tfe/tfe2.c +++ b/src/tfe/tfe2.c @@ -39,12 +39,12 @@ tfe_text_view_new (void) { /* ---------- end of the definition of TfeTextView ---------- */ static void -on_activate (GApplication *app, gpointer user_data) { +app_activate (GApplication *app, gpointer user_data) { g_print ("You need a filename argument.\n"); } static void -on_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) { +app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) { GtkWidget *win; GtkWidget *nb; GtkWidget *lab; @@ -118,11 +118,11 @@ on_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr); g_object_set (nbp, "tab-expand", TRUE, NULL); g_free (filename); - } else { - filename = g_file_get_path (files[i]); - g_print ("No such file: %s.\n", filename); - g_free (filename); - } + } else if ((filename = g_file_get_path (files[i])) != NULL) { + g_print ("No such file: %s.\n", filename); + g_free (filename); + } else + g_print ("No valid file is given\n"); } if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) { gtk_widget_show (win); @@ -136,8 +136,8 @@ main (int argc, char **argv) { int stat; app = gtk_application_new ("com.github.ToshioCP.tfe2", G_APPLICATION_HANDLES_OPEN); - g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL); - g_signal_connect (app, "open", G_CALLBACK (on_open), NULL); + g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL); + g_signal_connect (app, "open", G_CALLBACK (app_open), NULL); stat =g_application_run (G_APPLICATION (app), argc, argv); g_object_unref (app); return stat; diff --git a/src/tfe/tfe3.c b/src/tfe/tfe3.c index 9bcaa1a..7566fb0 100644 --- a/src/tfe/tfe3.c +++ b/src/tfe/tfe3.c @@ -39,12 +39,12 @@ tfe_text_view_new (void) { /* ---------- end of the definition of TfeTextView ---------- */ static void -on_activate (GApplication *app, gpointer user_data) { +app_activate (GApplication *app, gpointer user_data) { g_print ("You need a filename argument.\n"); } static void -on_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) { +app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) { GtkWidget *win; GtkWidget *nb; GtkWidget *lab; @@ -80,11 +80,11 @@ on_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr); g_object_set (nbp, "tab-expand", TRUE, NULL); g_free (filename); - } else { - filename = g_file_get_path (files[i]); - g_print ("No such file: %s.\n", filename); - g_free (filename); - } + } else if ((filename = g_file_get_path (files[i])) != NULL) { + g_print ("No such file: %s.\n", filename); + g_free (filename); + } else + g_print ("No valid file is given\n"); } if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) { gtk_widget_show (win); @@ -98,8 +98,8 @@ main (int argc, char **argv) { int stat; app = gtk_application_new ("com.github.ToshioCP.tfe3", G_APPLICATION_HANDLES_OPEN); - g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL); - g_signal_connect (app, "open", G_CALLBACK (on_open), NULL); + g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL); + g_signal_connect (app, "open", G_CALLBACK (app_open), NULL); stat =g_application_run (G_APPLICATION (app), argc, argv); g_object_unref (app); return stat; diff --git a/src/tfe/tfe3_r.c b/src/tfe/tfe3_r.c index 589a99a..c0274da 100644 --- a/src/tfe/tfe3_r.c +++ b/src/tfe/tfe3_r.c @@ -39,12 +39,12 @@ tfe_text_view_new (void) { /* ---------- end of the definition of TfeTextView ---------- */ static void -on_activate (GApplication *app, gpointer user_data) { +app_activate (GApplication *app, gpointer user_data) { g_print ("You need a filename argument.\n"); } static void -on_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) { +app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) { GtkWidget *win; GtkWidget *nb; GtkWidget *lab; @@ -80,11 +80,11 @@ on_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr); g_object_set (nbp, "tab-expand", TRUE, NULL); g_free (filename); - } else { - filename = g_file_get_path (files[i]); - g_print ("No such file: %s.\n", filename); - g_free (filename); - } + } else if ((filename = g_file_get_path (files[i])) != NULL) { + g_print ("No such file: %s.\n", filename); + g_free (filename); + } else + g_print ("No valid file is given\n"); } if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) { gtk_widget_show (win); @@ -98,8 +98,8 @@ main (int argc, char **argv) { int stat; app = gtk_application_new ("com.github.ToshioCP.tfe3", G_APPLICATION_HANDLES_OPEN); - g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL); - g_signal_connect (app, "open", G_CALLBACK (on_open), NULL); + g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL); + g_signal_connect (app, "open", G_CALLBACK (app_open), NULL); stat =g_application_run (G_APPLICATION (app), argc, argv); g_object_unref (app); return stat;