diff --git a/.gitignore b/.gitignore
index 44cf7dd..fed3b74 100755
--- a/.gitignore
+++ b/.gitignore
@@ -15,6 +15,7 @@ src/tfe/resources.c
src/tfe5/_build
src/tfe5/hello.txt
src/menu/a.out
+src/color/_build
html/*
latex/*
diff --git a/Readme.md b/Readme.md
index a8f019f..0c83b2b 100644
--- a/Readme.md
+++ b/Readme.md
@@ -32,3 +32,4 @@ You can read it without download.
1. [Stateful action](gfm/sec17.md)
1. [Ui file for menu and action entries](gfm/sec18.md)
1. [GtkDrawingArea and Cairo](gfm/sec19.md)
+1. [Combine GtkDrawingArea and TfeTextView](gfm/sec20.md)
diff --git a/gfm/sec10.md b/gfm/sec10.md
index 2f579b7..447fb85 100644
--- a/gfm/sec10.md
+++ b/gfm/sec10.md
@@ -5,7 +5,7 @@ Up: [Readme.md](../Readme.md), Prev: [Section 9](sec9.md), Next: [Section 11](s
This section and the following four sections are explanations about the next version of the text file editor (tfe).
It is tfe5.
It has many changes from the prior version.
-All the sources are listed after the five sections.
+All the sources are listed in [Section 15](sec15.md).
## Encapsulation
diff --git a/gfm/sec19.md b/gfm/sec19.md
index 12b3b81..160d640 100644
--- a/gfm/sec19.md
+++ b/gfm/sec19.md
@@ -26,7 +26,7 @@ We can draw shapes and images with different colors on surfaces.
- cairo context manages the transference from source to destination through mask with its functions.
For example, `cairo_stroke` is a function to draw a path to the destination by the transference.
- transformation is applied before the transfer completes.
-The transformation is called affine, which is a mathematicsterminology, and represented by matrix multiplication and vector addition.
+The transformation is called affine, which is a mathematics terminology, and represented by matrix multiplication and vector addition.
Scaling, rotation, reflection, shearing and translation are examples of affine transformation.
In this section, we don't use it.
That means we only use identity transformation.
diff --git a/gfm/sec20.md b/gfm/sec20.md
new file mode 100644
index 0000000..7a555cc
--- /dev/null
+++ b/gfm/sec20.md
@@ -0,0 +1,372 @@
+Up: [Readme.md](../Readme.md), Prev: [Section 19](sec19.md)
+
+# Combine GtkDrawingArea and TfeTextView
+
+Now, we will make a new application which has GtkDrawingArea and TfeTextView in it.
+Its name is "color".
+If you write a color in TfeTextView and click on the `run` button, then the color of GtkDrawingArea changes to the color given by you.
+
+![color](../image/color.png)
+
+The following colors are available.
+
+- white
+- black
+- red
+- green
+- blue
+
+In addition the following two options are also available.
+
+- light: Make the color of the drawing area lighter.
+- dark: Make the color of the drawing area darker.
+
+This application can only do very simple things.
+However, it tells us that if we add powerful parser to it, we will be able to make it more efficient.
+I want to show it to you in the later section by making a turtle graphics language like Logo program language.
+
+In this section, we focus on how to bind the two objects.
+
+# Color.ui and color.gresource.xml
+
+First, We need to make the ui file of the widgets.
+The image in the previous subsection gives us the structure of the widgets.
+Title bar, four buttons in the tool bar and two widgets textview and drawing area.
+The ui file is as follows.
+
+ 1
+ 2
+ 80
+ 81
+
+- 9-53: This part describes the tool bar which has four buttons, `Run`, `Open`, `Save` and `Close`.
+This is similar to the toolbar of tfe text editor in [Section 8](sec8.md).
+There are two differences.
+`Run` button replaces `New` button.
+Signal element are added to each button object.
+It has "name" attribute which is a signal name and "handler" attribute which is the name of its signal handler function.
+Options "-WI, --export-dynamic" CFLAG is necessary when you compile the application.
+You can achieve this by adding "export_dynamic: true" argument to executable function in `meson.build`.
+And be careful that the handler must be defined without 'static' class.
+- 54-76: Put GtkScrolledWindow and GtkDrawingArea into GtkBox.
+GtkBox has "homogeneous property with TRUE value, so the two children have the same width in the box.
+TfeTextView is a child of GtkScrolledWindow.
+
+The xml file for the resource compiler is almost same as before.
+Just substitute "color" for "tfe".
+
+ 1
+ 2
+ 3
+ 4 color.ui
+ 5
+ 6
+
+# Tfetextview.h, tfetextview.c and color.h
+
+First two files are almost same as before.
+The only difference is the header file in tfettextview.c.
+
+ $ diff tfe5/tfetextview.c color/tfetextview.c
+ 1c1
+ < #include "tfe.h"
+ ---
+ > #include "color.h"
+
+Color.h just includes tfetextview.h.
+
+ 1 #include
+ 2
+ 3 #include "tfetextview.h"
+
+# Colorapplication.c
+
+This is the main file.
+It deals with:
+
+- Build widgets by GtkBuilder.
+- Set drawing function to GtkDrawingArea.
+And connect a handler to "resize" signal on GtkDrawingArea.
+- Implement each call back functions.
+Particularly, `Run` signal handler is the point in this program.
+
+The following is `colorapplication.c`.
+
+ 1 #include "color.h"
+ 2
+ 3 static GtkWidget *win;
+ 4 static GtkWidget *tv;
+ 5 static GtkWidget *da;
+ 6
+ 7 static cairo_surface_t *surface = NULL;
+ 8
+ 9 static void
+ 10 run (void) {
+ 11 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
+ 12 GtkTextIter start_iter;
+ 13 GtkTextIter end_iter;
+ 14 char *contents;
+ 15 cairo_t *cr;
+ 16
+ 17 gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
+ 18 contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
+ 19 if (surface) {
+ 20 cr = cairo_create (surface);
+ 21 if (g_strcmp0 ("red", contents) == 0)
+ 22 cairo_set_source_rgb (cr, 1, 0, 0);
+ 23 else if (g_strcmp0 ("green", contents) == 0)
+ 24 cairo_set_source_rgb (cr, 0, 1, 0);
+ 25 else if (g_strcmp0 ("blue", contents) == 0)
+ 26 cairo_set_source_rgb (cr, 0, 0, 1);
+ 27 else if (g_strcmp0 ("white", contents) == 0)
+ 28 cairo_set_source_rgb (cr, 1, 1, 1);
+ 29 else if (g_strcmp0 ("black", contents) == 0)
+ 30 cairo_set_source_rgb (cr, 0, 0, 0);
+ 31 else if (g_strcmp0 ("light", contents) == 0)
+ 32 cairo_set_source_rgba (cr, 1, 1, 1, 0.5);
+ 33 else if (g_strcmp0 ("dark", contents) == 0)
+ 34 cairo_set_source_rgba (cr, 0, 0, 0, 0.5);
+ 35 else
+ 36 cairo_set_source_surface (cr, surface, 0, 0);
+ 37 cairo_paint (cr);
+ 38 cairo_destroy (cr);
+ 39 }
+ 40 }
+ 41
+ 42 void
+ 43 run_cb (GtkWidget *btnr) {
+ 44 run ();
+ 45 gtk_widget_queue_draw (GTK_WIDGET (da));
+ 46 }
+ 47
+ 48 void
+ 49 open_cb (GtkWidget *btno) {
+ 50 tfe_text_view_open (TFE_TEXT_VIEW (tv), win);
+ 51 }
+ 52
+ 53 void
+ 54 save_cb (GtkWidget *btns) {
+ 55 tfe_text_view_save (TFE_TEXT_VIEW (tv));
+ 56 }
+ 57
+ 58 void
+ 59 close_cb (GtkWidget *btnc) {
+ 60 if (surface)
+ 61 cairo_surface_destroy (surface);
+ 62 gtk_window_destroy (GTK_WINDOW (win));
+ 63 }
+ 64
+ 65 static void
+ 66 resize_cb (GtkDrawingArea *drawing_area, int width, int height, gpointer user_data) {
+ 67 if (surface)
+ 68 cairo_surface_destroy (surface);
+ 69 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
+ 70 run ();
+ 71 }
+ 72
+ 73 static void
+ 74 draw_func (GtkDrawingArea *drawing_area, cairo_t *cr, int width, int height, gpointer user_data) {
+ 75 if (surface) {
+ 76 cairo_set_source_surface (cr, surface, 0, 0);
+ 77 cairo_paint (cr);
+ 78 }
+ 79 }
+ 80
+ 81 static void
+ 82 activate (GApplication *application) {
+ 83 gtk_widget_show (win);
+ 84 }
+ 85
+ 86 static void
+ 87 startup (GApplication *application) {
+ 88 GtkApplication *app = GTK_APPLICATION (application);
+ 89 GtkBuilder *build;
+ 90
+ 91 build = gtk_builder_new_from_resource ("/com/github/ToshioCP/color/color.ui");
+ 92 win = GTK_WIDGET (gtk_builder_get_object (build, "win"));
+ 93 gtk_window_set_application (GTK_WINDOW (win), app);
+ 94 tv = GTK_WIDGET (gtk_builder_get_object (build, "tv"));
+ 95 da = GTK_WIDGET (gtk_builder_get_object (build, "da"));
+ 96 g_object_unref(build);
+ 97 g_signal_connect (GTK_DRAWING_AREA (da), "resize", G_CALLBACK (resize_cb), NULL);
+ 98 gtk_drawing_area_set_draw_func (GTK_DRAWING_AREA (da), draw_func, NULL, NULL);
+ 99
+ 100 GdkDisplay *display;
+ 101
+ 102 display = gtk_widget_get_display (GTK_WIDGET (win));
+ 103 GtkCssProvider *provider = gtk_css_provider_new ();
+ 104 gtk_css_provider_load_from_data (provider, "textview {padding: 10px; font-family: monospace; font-size: 12pt;}", -1);
+ 105 gtk_style_context_add_provider_for_display (display, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_USER);
+ 106 }
+ 107
+ 108 int
+ 109 main (int argc, char **argv) {
+ 110 GtkApplication *app;
+ 111 int stat;
+ 112
+ 113 app = gtk_application_new ("com.github.ToshioCP.color", G_APPLICATION_FLAGS_NONE);
+ 114
+ 115 g_signal_connect (app, "startup", G_CALLBACK (startup), NULL);
+ 116 g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);
+ 117
+ 118 stat =g_application_run (G_APPLICATION (app), argc, argv);
+ 119 g_object_unref (app);
+ 120 return stat;
+ 121 }
+ 122
+
+- 108-121: The function `main` is almost same as before but there are some differences.
+The application ID is "com.github.ToshioCP.color".
+`G_APPLICATION_FLAGS_NONE` is specified so no open signal handler is necessary.
+- 86-106: Startup handler.
+- 91-96: Build widgets.
+The pointers of the top window, TfeTextView and GtkDrawingArea objects are stored to static variables `win`, `tv` and `da` respectively.
+This is because these objects are often used in handlers.
+They never be rewritten so they're thread safe.
+- 97: connect "resize" signal and the handler.
+- 98: set the drawing function.
+- 81-84: Activate handler, which just show the widgets.
+- 73-79: The drawing function.
+It just copy `surface` to destination.
+- 65-71: Resize handler.
+Re-create the surface to fit the width and height of the drawing area and paint by calling the function `run`.
+- 58-63: Close handler.
+It destroys `surface` if it exists.
+Then it destroys the top window and quits the application.
+- 48-56: Open and save handler.
+They just call the corresponding functions of TfeTextView.
+- 42-46: Run handler.
+It calls run function to paint the surface.
+After that `gtk_widget_queue_draw` is called.
+This fhunction adds the widget (GtkDrawingArea) to the queue to be redrawn.
+It is important to know that the drawing function is called when it is necessary.
+For example, when another window is moved and uncovers part of the widget, or when the window containing it is resized.
+But repaint of `surface` is not automatically notified to gtk.
+Therefore, you need to call `gtk_widget_queue_draw` to redraw the widget.
+- 9-40: Run function paint the surface.
+First, it gets the contents of GtkTextBuffer.
+Then compare it to "red", "green" and so on.
+If it matches the color, then the surface is painted the color.
+If it matches "light" or "dark", then the color of the surface is lightened or darkened respectively.
+Alpha channel is used.
+
+# Meson.build
+
+This file is almost same as before.
+An argument "export_dynamic: true" is added to executable function.
+
+ 1 project('color', 'c')
+ 2
+ 3 gtkdep = dependency('gtk4')
+ 4
+ 5 gnome=import('gnome')
+ 6 resources = gnome.compile_resources('resources','color.gresource.xml')
+ 7
+ 8 sourcefiles=files('colorapplication.c', 'tfetextview.c')
+ 9
+ 10 executable('color', sourcefiles, resources, dependencies: gtkdep, export_dynamic: true)
+
+# Compile and execute it
+
+First you need to export some variables (refer to [Section 2](sec2.md)).
+
+ $ . env.sh
+
+Then type the following to compile it.
+
+ $ meson _build
+ $ ninja -C _build
+
+The application is made in `_build` directory.
+Type the following to execute it.
+
+ $ _build/color
+
+Type "red", "green", "blue", "white", black", "light" or "dark" in the TfeTextView.
+Then, click on `Run` button.
+Make sure the color of GtkDrawingArea changes.
+
+In this program TfeTextView is used to change the color.
+You can use buttons or menus instead of textview.
+Probably it is more appropriate.
+Using textview is unnatural.
+It is a good practice to make such application by yourself.
+
+Up: [Readme.md](../Readme.md), Prev: [Section 19](sec19.md)
diff --git a/gfm/sec3.md b/gfm/sec3.md
index e98f44e..455aa39 100644
--- a/gfm/sec3.md
+++ b/gfm/sec3.md
@@ -127,8 +127,8 @@ The function `g_signal_connect` has four arguments.
4. Data to pass to the handler. If no data is necessary, NULL should be given.
You can find the description of each signal in API reference.
-For example, "activate" signal is in GApplication subsection in GIO API reference.
-The handler function is described in that subsection.
+For example, "activate" signal is in GApplication section in GIO API reference.
+The handler function is described in that section.
In addition, `g_signal_connect` is described in GObject API reference.
API reference is very important.
diff --git a/gfm/sec7.md b/gfm/sec7.md
index 7334e6c..22359aa 100644
--- a/gfm/sec7.md
+++ b/gfm/sec7.md
@@ -49,7 +49,7 @@ For example, TfeTextView has GtkTextbuffer correspods to GtkTextView inside TfeT
And important thing is that TfeTextView can have a memory to keep a pointer to GFile.
However, to understand the general theory about gobjects 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 section.
+So, I will just show you the way how to write the code and avoid the theoretical side in the next subsection.
## How to define a child widget of GtkTextView
diff --git a/image/TfeTextView.png b/image/TfeTextView.png
index d8ba32b..0861906 100644
Binary files a/image/TfeTextView.png and b/image/TfeTextView.png differ
diff --git a/image/color.png b/image/color.png
new file mode 100644
index 0000000..f0c4294
Binary files /dev/null and b/image/color.png differ
diff --git a/lib/lib_src2md.rb b/lib/lib_src2md.rb
index 6617756..63579a0 100644
--- a/lib/lib_src2md.rb
+++ b/lib/lib_src2md.rb
@@ -142,7 +142,7 @@ def change_rel_link line, src_dir, basedir
p_basedir = Pathname.new basedir
left = ""
right = line
- while right =~ /(!?\[[^\]]*\])\((.*)\)/
+ while right =~ /(!?\[[^\]]*\])\(([^\)]*)\)/
left = $`
right = $'
name = $1
diff --git a/src/color/color.gresource.xml b/src/color/color.gresource.xml
new file mode 100644
index 0000000..52a631d
--- /dev/null
+++ b/src/color/color.gresource.xml
@@ -0,0 +1,6 @@
+
+
+
+ color.ui
+
+
diff --git a/src/color/color.h b/src/color/color.h
new file mode 100644
index 0000000..82505f7
--- /dev/null
+++ b/src/color/color.h
@@ -0,0 +1,3 @@
+#include
+
+#include "tfetextview.h"
diff --git a/src/color/color.ui b/src/color/color.ui
new file mode 100644
index 0000000..9417464
--- /dev/null
+++ b/src/color/color.ui
@@ -0,0 +1,81 @@
+
+
+ color changer
+ 600
+ 400
+
+
+ GTK_ORIENTATION_VERTICAL
+
+
+ GTK_ORIENTATION_HORIZONTAL
+
+
+ 10
+
+
+
+
+ Run
+
+
+
+
+
+
+ Open
+
+
+
+
+
+ TRUE
+
+
+
+
+ Save
+
+
+
+
+
+ Close
+
+
+
+
+
+ 10
+
+
+
+
+
+
+ GTK_ORIENTATION_HORIZONTAL
+ TRUE
+
+
+ TRUE
+ TRUE
+
+
+ GTK_WRAP_WORD_CHAR
+
+
+
+
+
+
+ TRUE
+ TRUE
+
+
+
+
+
+
+
+
+
diff --git a/src/color/colorapplication.c b/src/color/colorapplication.c
new file mode 100644
index 0000000..98eff38
--- /dev/null
+++ b/src/color/colorapplication.c
@@ -0,0 +1,122 @@
+#include "color.h"
+
+static GtkWidget *win;
+static GtkWidget *tv;
+static GtkWidget *da;
+
+static cairo_surface_t *surface = NULL;
+
+static void
+run (void) {
+ GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
+ GtkTextIter start_iter;
+ GtkTextIter end_iter;
+ char *contents;
+ cairo_t *cr;
+
+ gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
+ contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
+ if (surface) {
+ cr = cairo_create (surface);
+ if (g_strcmp0 ("red", contents) == 0)
+ cairo_set_source_rgb (cr, 1, 0, 0);
+ else if (g_strcmp0 ("green", contents) == 0)
+ cairo_set_source_rgb (cr, 0, 1, 0);
+ else if (g_strcmp0 ("blue", contents) == 0)
+ cairo_set_source_rgb (cr, 0, 0, 1);
+ else if (g_strcmp0 ("white", contents) == 0)
+ cairo_set_source_rgb (cr, 1, 1, 1);
+ else if (g_strcmp0 ("black", contents) == 0)
+ cairo_set_source_rgb (cr, 0, 0, 0);
+ else if (g_strcmp0 ("light", contents) == 0)
+ cairo_set_source_rgba (cr, 1, 1, 1, 0.5);
+ else if (g_strcmp0 ("dark", contents) == 0)
+ cairo_set_source_rgba (cr, 0, 0, 0, 0.5);
+ else
+ cairo_set_source_surface (cr, surface, 0, 0);
+ cairo_paint (cr);
+ cairo_destroy (cr);
+ }
+}
+
+void
+run_cb (GtkWidget *btnr) {
+ run ();
+ gtk_widget_queue_draw (GTK_WIDGET (da));
+}
+
+void
+open_cb (GtkWidget *btno) {
+ tfe_text_view_open (TFE_TEXT_VIEW (tv), win);
+}
+
+void
+save_cb (GtkWidget *btns) {
+ tfe_text_view_save (TFE_TEXT_VIEW (tv));
+}
+
+void
+close_cb (GtkWidget *btnc) {
+ if (surface)
+ cairo_surface_destroy (surface);
+ gtk_window_destroy (GTK_WINDOW (win));
+}
+
+static void
+resize_cb (GtkDrawingArea *drawing_area, int width, int height, gpointer user_data) {
+ if (surface)
+ cairo_surface_destroy (surface);
+ surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
+ run ();
+}
+
+static void
+draw_func (GtkDrawingArea *drawing_area, cairo_t *cr, int width, int height, gpointer user_data) {
+ if (surface) {
+ cairo_set_source_surface (cr, surface, 0, 0);
+ cairo_paint (cr);
+ }
+}
+
+static void
+activate (GApplication *application) {
+ gtk_widget_show (win);
+}
+
+static void
+startup (GApplication *application) {
+ GtkApplication *app = GTK_APPLICATION (application);
+ GtkBuilder *build;
+
+ build = gtk_builder_new_from_resource ("/com/github/ToshioCP/color/color.ui");
+ win = GTK_WIDGET (gtk_builder_get_object (build, "win"));
+ gtk_window_set_application (GTK_WINDOW (win), app);
+ tv = GTK_WIDGET (gtk_builder_get_object (build, "tv"));
+ da = GTK_WIDGET (gtk_builder_get_object (build, "da"));
+ g_object_unref(build);
+ g_signal_connect (GTK_DRAWING_AREA (da), "resize", G_CALLBACK (resize_cb), NULL);
+ gtk_drawing_area_set_draw_func (GTK_DRAWING_AREA (da), draw_func, NULL, NULL);
+
+GdkDisplay *display;
+
+ display = gtk_widget_get_display (GTK_WIDGET (win));
+ GtkCssProvider *provider = gtk_css_provider_new ();
+ gtk_css_provider_load_from_data (provider, "textview {padding: 10px; font-family: monospace; font-size: 12pt;}", -1);
+ gtk_style_context_add_provider_for_display (display, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_USER);
+}
+
+int
+main (int argc, char **argv) {
+ GtkApplication *app;
+ int stat;
+
+ app = gtk_application_new ("com.github.ToshioCP.color", G_APPLICATION_FLAGS_NONE);
+
+ g_signal_connect (app, "startup", G_CALLBACK (startup), NULL);
+ g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);
+
+ stat =g_application_run (G_APPLICATION (app), argc, argv);
+ g_object_unref (app);
+ return stat;
+}
+
diff --git a/src/color/meson.build b/src/color/meson.build
new file mode 100644
index 0000000..a37abe4
--- /dev/null
+++ b/src/color/meson.build
@@ -0,0 +1,10 @@
+project('color', 'c')
+
+gtkdep = dependency('gtk4')
+
+gnome=import('gnome')
+resources = gnome.compile_resources('resources','color.gresource.xml')
+
+sourcefiles=files('colorapplication.c', 'tfetextview.c')
+
+executable('color', sourcefiles, resources, dependencies: gtkdep, export_dynamic: true)
diff --git a/src/color/tfetextview.c b/src/color/tfetextview.c
new file mode 100644
index 0000000..9342a26
--- /dev/null
+++ b/src/color/tfetextview.c
@@ -0,0 +1,218 @@
+#include "color.h"
+
+struct _TfeTextView
+{
+ GtkTextView parent;
+ GFile *file;
+};
+
+G_DEFINE_TYPE (TfeTextView, tfe_text_view, GTK_TYPE_TEXT_VIEW);
+
+enum {
+ CHANGE_FILE,
+ OPEN_RESPONSE,
+ NUMBER_OF_SIGNALS
+};
+
+static guint tfe_text_view_signals[NUMBER_OF_SIGNALS];
+
+static void
+tfe_text_view_dispose (GObject *gobject) {
+ TfeTextView *tv = TFE_TEXT_VIEW (gobject);
+
+ if (G_IS_FILE (tv->file))
+ g_clear_object (&tv->file);
+
+ G_OBJECT_CLASS (tfe_text_view_parent_class)->dispose (gobject);
+}
+
+static void
+tfe_text_view_init (TfeTextView *tv) {
+ GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
+
+ tv->file = NULL;
+ gtk_text_buffer_set_modified (tb, FALSE);
+ gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
+}
+
+static void
+tfe_text_view_class_init (TfeTextViewClass *class) {
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+ object_class->dispose = tfe_text_view_dispose;
+ tfe_text_view_signals[CHANGE_FILE] = g_signal_newv ("change-file",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
+ NULL /* closure */,
+ NULL /* accumulator */,
+ NULL /* accumulator data */,
+ NULL /* C marshaller */,
+ G_TYPE_NONE /* return_type */,
+ 0 /* n_params */,
+ NULL /* param_types */);
+ GType param_types[] = {G_TYPE_INT};
+ tfe_text_view_signals[OPEN_RESPONSE] = g_signal_newv ("open-response",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
+ NULL /* closure */,
+ NULL /* accumulator */,
+ NULL /* accumulator data */,
+ NULL /* C marshaller */,
+ G_TYPE_NONE /* return_type */,
+ 1 /* n_params */,
+ param_types);
+}
+
+GFile *
+tfe_text_view_get_file (TfeTextView *tv) {
+ g_return_val_if_fail (TFE_IS_TEXT_VIEW (tv), NULL);
+
+ return g_file_dup (tv->file);
+}
+
+static void
+open_dialog_response(GtkWidget *dialog, gint response, TfeTextView *tv) {
+ GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
+ GFile *file;
+ char *contents;
+ gsize length;
+ GtkWidget *message_dialog;
+ GError *err = NULL;
+
+ if (response != GTK_RESPONSE_ACCEPT)
+ g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_CANCEL);
+ else if (! G_IS_FILE (file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog))))
+ g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
+ else if (! g_file_load_contents (file, NULL, &contents, &length, NULL, &err)) { /* read error */
+ if (G_IS_FILE (file))
+ g_object_unref (file);
+ message_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL,
+ GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
+ "%s.\n", err->message);
+ g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
+ gtk_widget_show (message_dialog);
+ g_error_free (err);
+ g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
+ } else {
+ gtk_text_buffer_set_text (tb, contents, length);
+ g_free (contents);
+ 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[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS);
+ }
+ gtk_window_destroy (GTK_WINDOW (dialog));
+}
+
+void
+tfe_text_view_open (TfeTextView *tv, GtkWidget *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,
+ "Cancel", GTK_RESPONSE_CANCEL,
+ "Open", GTK_RESPONSE_ACCEPT,
+ NULL);
+ g_signal_connect (dialog, "response", G_CALLBACK (open_dialog_response), tv);
+ gtk_widget_show (dialog);
+}
+
+static void
+saveas_dialog_response (GtkWidget *dialog, gint response, TfeTextView *tv) {
+ GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
+ GFile *file;
+
+ if (response == GTK_RESPONSE_ACCEPT) {
+ file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
+ if (G_IS_FILE(file)) {
+ tv->file = file;
+ gtk_text_buffer_set_modified (tb, TRUE);
+ g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
+ tfe_text_view_save (TFE_TEXT_VIEW (tv));
+ }
+ }
+ gtk_window_destroy (GTK_WINDOW (dialog));
+}
+
+void
+tfe_text_view_save (TfeTextView *tv) {
+ g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
+
+ GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
+ GtkTextIter start_iter;
+ GtkTextIter end_iter;
+ gchar *contents;
+ GtkWidget *message_dialog;
+ GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
+ GError *err = NULL;
+
+ if (! gtk_text_buffer_get_modified (tb))
+ return; /* no necessary to save it */
+ else if (tv->file == NULL)
+ tfe_text_view_saveas (tv);
+ else {
+ 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 (tv->file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, &err))
+ gtk_text_buffer_set_modified (tb, FALSE);
+ else {
+/* It is possible that tv->file is broken. */
+/* It is a good idea to set tv->file to NULL. */
+ if (G_IS_FILE (tv->file))
+ g_object_unref (tv->file);
+ tv->file =NULL;
+ g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
+ gtk_text_buffer_set_modified (tb, TRUE);
+ message_dialog = gtk_message_dialog_new (GTK_WINDOW (win), GTK_DIALOG_MODAL,
+ GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
+ "%s.\n", err->message);
+ g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
+ gtk_widget_show (message_dialog);
+ g_error_free (err);
+ }
+ }
+}
+
+void
+tfe_text_view_saveas (TfeTextView *tv) {
+ g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
+
+ GtkWidget *dialog;
+ GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
+
+ dialog = gtk_file_chooser_dialog_new ("Save file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_SAVE,
+ "_Cancel", GTK_RESPONSE_CANCEL,
+ "_Save", GTK_RESPONSE_ACCEPT,
+ NULL);
+ g_signal_connect (dialog, "response", G_CALLBACK (saveas_dialog_response), tv);
+ gtk_widget_show (dialog);
+}
+
+GtkWidget *
+tfe_text_view_new_with_file (GFile *file) {
+ g_return_val_if_fail (G_IS_FILE (file), NULL);
+
+ GtkWidget *tv;
+ GtkTextBuffer *tb;
+ char *contents;
+ gsize length;
+
+ if (! g_file_load_contents (file, NULL, &contents, &length, NULL, NULL)) /* read error */
+ return NULL;
+
+ tv = tfe_text_view_new();
+ tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
+ gtk_text_buffer_set_text (tb, contents, length);
+ g_free (contents);
+ TFE_TEXT_VIEW (tv)->file = g_file_dup (file);
+ return tv;
+}
+
+GtkWidget *
+tfe_text_view_new (void) {
+ return GTK_WIDGET (g_object_new (TFE_TYPE_TEXT_VIEW, NULL));
+}
+
diff --git a/src/color/tfetextview.h b/src/color/tfetextview.h
new file mode 100644
index 0000000..7849da5
--- /dev/null
+++ b/src/color/tfetextview.h
@@ -0,0 +1,29 @@
+#define TFE_TYPE_TEXT_VIEW tfe_text_view_get_type ()
+G_DECLARE_FINAL_TYPE (TfeTextView, tfe_text_view, TFE, TEXT_VIEW, GtkTextView)
+
+/* "open-response" signal response */
+enum
+{
+ TFE_OPEN_RESPONSE_SUCCESS,
+ TFE_OPEN_RESPONSE_CANCEL,
+ TFE_OPEN_RESPONSE_ERROR
+};
+
+GFile *
+tfe_text_view_get_file (TfeTextView *tv);
+
+void
+tfe_text_view_open (TfeTextView *tv, GtkWidget *win);
+
+void
+tfe_text_view_save (TfeTextView *tv);
+
+void
+tfe_text_view_saveas (TfeTextView *tv);
+
+GtkWidget *
+tfe_text_view_new_with_file (GFile *file);
+
+GtkWidget *
+tfe_text_view_new (void);
+
diff --git a/src/sec10.src.md b/src/sec10.src.md
index 279f9da..5fed4ef 100644
--- a/src/sec10.src.md
+++ b/src/sec10.src.md
@@ -3,7 +3,7 @@
This section and the following four sections are explanations about the next version of the text file editor (tfe).
It is tfe5.
It has many changes from the prior version.
-All the sources are listed after the five sections.
+All the sources are listed in [Section 15](sec15.src.md).
## Encapsulation
diff --git a/src/sec19.src.md b/src/sec19.src.md
index f8eb8c3..8e5e012 100644
--- a/src/sec19.src.md
+++ b/src/sec19.src.md
@@ -24,7 +24,7 @@ We can draw shapes and images with different colors on surfaces.
- cairo context manages the transference from source to destination through mask with its functions.
For example, `cairo_stroke` is a function to draw a path to the destination by the transference.
- transformation is applied before the transfer completes.
-The transformation is called affine, which is a mathematicsterminology, and represented by matrix multiplication and vector addition.
+The transformation is called affine, which is a mathematics terminology, and represented by matrix multiplication and vector addition.
Scaling, rotation, reflection, shearing and translation are examples of affine transformation.
In this section, we don't use it.
That means we only use identity transformation.
diff --git a/src/sec20.src.md b/src/sec20.src.md
new file mode 100644
index 0000000..738917b
--- /dev/null
+++ b/src/sec20.src.md
@@ -0,0 +1,149 @@
+# Combine GtkDrawingArea and TfeTextView
+
+Now, we will make a new application which has GtkDrawingArea and TfeTextView in it.
+Its name is "color".
+If you write a color in TfeTextView and click on the `run` button, then the color of GtkDrawingArea changes to the color given by you.
+
+![color](../image/color.png)
+
+The following colors are available.
+
+- white
+- black
+- red
+- green
+- blue
+
+In addition the following two options are also available.
+
+- light: Make the color of the drawing area lighter.
+- dark: Make the color of the drawing area darker.
+
+This application can only do very simple things.
+However, it tells us that if we add powerful parser to it, we will be able to make it more efficient.
+I want to show it to you in the later section by making a turtle graphics language like Logo program language.
+
+In this section, we focus on how to bind the two objects.
+
+# Color.ui and color.gresource.xml
+
+First, We need to make the ui file of the widgets.
+The image in the previous subsection gives us the structure of the widgets.
+Title bar, four buttons in the tool bar and two widgets textview and drawing area.
+The ui file is as follows.
+
+@@@ color/color.ui
+
+- 9-53: This part describes the tool bar which has four buttons, `Run`, `Open`, `Save` and `Close`.
+This is similar to the toolbar of tfe text editor in [Section 8](sec8.src.md).
+There are two differences.
+`Run` button replaces `New` button.
+Signal element are added to each button object.
+It has "name" attribute which is a signal name and "handler" attribute which is the name of its signal handler function.
+Options "-WI, --export-dynamic" CFLAG is necessary when you compile the application.
+You can achieve this by adding "export_dynamic: true" argument to executable function in `meson.build`.
+And be careful that the handler must be defined without 'static' class.
+- 54-76: Put GtkScrolledWindow and GtkDrawingArea into GtkBox.
+GtkBox has "homogeneous property with TRUE value, so the two children have the same width in the box.
+TfeTextView is a child of GtkScrolledWindow.
+
+The xml file for the resource compiler is almost same as before.
+Just substitute "color" for "tfe".
+
+@@@ color/color.gresource.xml
+
+# Tfetextview.h, tfetextview.c and color.h
+
+First two files are almost same as before.
+The only difference is the header file in tfettextview.c.
+
+$$$
+diff tfe5/tfetextview.c color/tfetextview.c
+$$$
+
+Color.h just includes tfetextview.h.
+
+@@@ color/color.h
+
+# Colorapplication.c
+
+This is the main file.
+It deals with:
+
+- Build widgets by GtkBuilder.
+- Set drawing function to GtkDrawingArea.
+And connect a handler to "resize" signal on GtkDrawingArea.
+- Implement each call back functions.
+Particularly, `Run` signal handler is the point in this program.
+
+The following is `colorapplication.c`.
+
+@@@ color/colorapplication.c
+
+- 108-121: The function `main` is almost same as before but there are some differences.
+The application ID is "com.github.ToshioCP.color".
+`G_APPLICATION_FLAGS_NONE` is specified so no open signal handler is necessary.
+- 86-106: Startup handler.
+- 91-96: Build widgets.
+The pointers of the top window, TfeTextView and GtkDrawingArea objects are stored to static variables `win`, `tv` and `da` respectively.
+This is because these objects are often used in handlers.
+They never be rewritten so they're thread safe.
+- 97: connect "resize" signal and the handler.
+- 98: set the drawing function.
+- 81-84: Activate handler, which just show the widgets.
+- 73-79: The drawing function.
+It just copy `surface` to destination.
+- 65-71: Resize handler.
+Re-create the surface to fit the width and height of the drawing area and paint by calling the function `run`.
+- 58-63: Close handler.
+It destroys `surface` if it exists.
+Then it destroys the top window and quits the application.
+- 48-56: Open and save handler.
+They just call the corresponding functions of TfeTextView.
+- 42-46: Run handler.
+It calls run function to paint the surface.
+After that `gtk_widget_queue_draw` is called.
+This fhunction adds the widget (GtkDrawingArea) to the queue to be redrawn.
+It is important to know that the drawing function is called when it is necessary.
+For example, when another window is moved and uncovers part of the widget, or when the window containing it is resized.
+But repaint of `surface` is not automatically notified to gtk.
+Therefore, you need to call `gtk_widget_queue_draw` to redraw the widget.
+- 9-40: Run function paint the surface.
+First, it gets the contents of GtkTextBuffer.
+Then compare it to "red", "green" and so on.
+If it matches the color, then the surface is painted the color.
+If it matches "light" or "dark", then the color of the surface is lightened or darkened respectively.
+Alpha channel is used.
+
+# Meson.build
+
+This file is almost same as before.
+An argument "export_dynamic: true" is added to executable function.
+
+@@@ color/meson.build
+
+# Compile and execute it
+
+First you need to export some variables (refer to [Section 2](sec2.src.md)).
+
+ $ . env.sh
+
+Then type the following to compile it.
+
+ $ meson _build
+ $ ninja -C _build
+
+The application is made in `_build` directory.
+Type the following to execute it.
+
+ $ _build/color
+
+Type "red", "green", "blue", "white", black", "light" or "dark" in the TfeTextView.
+Then, click on `Run` button.
+Make sure the color of GtkDrawingArea changes.
+
+In this program TfeTextView is used to change the color.
+You can use buttons or menus instead of textview.
+Probably it is more appropriate.
+Using textview is unnatural.
+It is a good practice to make such application by yourself.
diff --git a/src/sec3.src.md b/src/sec3.src.md
index bdef097..acdd9cf 100644
--- a/src/sec3.src.md
+++ b/src/sec3.src.md
@@ -95,8 +95,8 @@ The function `g_signal_connect` has four arguments.
4. Data to pass to the handler. If no data is necessary, NULL should be given.
You can find the description of each signal in API reference.
-For example, "activate" signal is in GApplication subsection in GIO API reference.
-The handler function is described in that subsection.
+For example, "activate" signal is in GApplication section in GIO API reference.
+The handler function is described in that section.
In addition, `g_signal_connect` is described in GObject API reference.
API reference is very important.
diff --git a/src/sec7.src.md b/src/sec7.src.md
index 4e47fde..7e72b43 100644
--- a/src/sec7.src.md
+++ b/src/sec7.src.md
@@ -47,7 +47,7 @@ For example, TfeTextView has GtkTextbuffer correspods to GtkTextView inside TfeT
And important thing is that TfeTextView can have a memory to keep a pointer to GFile.
However, to understand the general theory about gobjects 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 section.
+So, I will just show you the way how to write the code and avoid the theoretical side in the next subsection.
## How to define a child widget of GtkTextView