Up: [Readme.md](../Readme.md), Prev: [Section 22](sec22.md), Next: [Section 24](sec24.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 name of 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. ~~~xml 1 2 3 4 color changer 5 600 6 400 7 8 9 GTK_ORIENTATION_VERTICAL 10 11 12 GTK_ORIENTATION_HORIZONTAL 13 14 15 10 16 17 18 19 20 Run 21 22 23 24 25 26 Open 27 28 29 30 31 32 TRUE 33 34 35 36 37 Save 38 39 40 41 42 43 Close 44 45 46 47 48 49 10 50 51 52 53 54 55 56 GTK_ORIENTATION_HORIZONTAL 57 TRUE 58 59 60 TRUE 61 TRUE 62 63 64 GTK_WRAP_WORD_CHAR 65 66 67 68 69 70 71 TRUE 72 TRUE 73 74 75 76 77 78 79 80 ~~~ - 10-53: This part is the tool bar which has four buttons, `Run`, `Open`, `Save` and `Close`. This is similar to the toolbar of tfe text editor in [Section 9](sec9.md). There are two differences. `Run` button replaces `New` button. A signal element is 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: Puts 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". ~~~xml 1 2 3 4 color.ui 5 6 ~~~ ## Tfetextview.h, tfetextview.c and color.h First two files are the same as before. Color.h just includes tfetextview.h. ~~~C 1 #include 2 3 #include "../tfetextview/tfetextview.h" ~~~ ## Colorapplication.c This is the main file. It deals with: - Building widgets by GtkBuilder. - Setting a drawing function of GtkDrawingArea. And connecting a handler to "resize" signal on GtkDrawingArea. - Implementing each call back functions. Particularly, `Run` signal handler is the point in this program. The following is `colorapplication.c`. ~~~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 g_free (contents); 41 } 42 43 void 44 run_cb (GtkWidget *btnr) { 45 run (); 46 gtk_widget_queue_draw (GTK_WIDGET (da)); 47 } 48 49 void 50 open_cb (GtkWidget *btno) { 51 tfe_text_view_open (TFE_TEXT_VIEW (tv), GTK_WINDOW (win)); 52 } 53 54 void 55 save_cb (GtkWidget *btns) { 56 tfe_text_view_save (TFE_TEXT_VIEW (tv)); 57 } 58 59 void 60 close_cb (GtkWidget *btnc) { 61 if (surface) 62 cairo_surface_destroy (surface); 63 gtk_window_destroy (GTK_WINDOW (win)); 64 } 65 66 static void 67 resize_cb (GtkDrawingArea *drawing_area, int width, int height, gpointer user_data) { 68 if (surface) 69 cairo_surface_destroy (surface); 70 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height); 71 run (); 72 } 73 74 static void 75 draw_func (GtkDrawingArea *drawing_area, cairo_t *cr, int width, int height, gpointer user_data) { 76 if (surface) { 77 cairo_set_source_surface (cr, surface, 0, 0); 78 cairo_paint (cr); 79 } 80 } 81 82 static void 83 app_activate (GApplication *application) { 84 gtk_widget_show (win); 85 } 86 87 static void 88 app_startup (GApplication *application) { 89 GtkApplication *app = GTK_APPLICATION (application); 90 GtkBuilder *build; 91 92 build = gtk_builder_new_from_resource ("/com/github/ToshioCP/color/color.ui"); 93 win = GTK_WIDGET (gtk_builder_get_object (build, "win")); 94 gtk_window_set_application (GTK_WINDOW (win), app); 95 tv = GTK_WIDGET (gtk_builder_get_object (build, "tv")); 96 da = GTK_WIDGET (gtk_builder_get_object (build, "da")); 97 g_object_unref(build); 98 g_signal_connect (GTK_DRAWING_AREA (da), "resize", G_CALLBACK (resize_cb), NULL); 99 gtk_drawing_area_set_draw_func (GTK_DRAWING_AREA (da), draw_func, NULL, NULL); 100 101 GdkDisplay *display; 102 103 display = gtk_widget_get_display (GTK_WIDGET (win)); 104 GtkCssProvider *provider = gtk_css_provider_new (); 105 gtk_css_provider_load_from_data (provider, "textview {padding: 10px; font-family: monospace; font-size: 12pt;}", -1); 106 gtk_style_context_add_provider_for_display (display, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_USER); 107 } 108 109 #define APPLICATION_ID "com.github.ToshioCP.color" 110 111 int 112 main (int argc, char **argv) { 113 GtkApplication *app; 114 int stat; 115 116 app = gtk_application_new (APPLICATION_ID, G_APPLICATION_FLAGS_NONE); 117 118 g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL); 119 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL); 120 121 stat =g_application_run (G_APPLICATION (app), argc, argv); 122 g_object_unref (app); 123 return stat; 124 } 125 ~~~ - 109-124: 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. - 87-107: Startup handler. - 92-97: Builds 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. - 98: connects "resize" signal and the handler. - 99: sets the drawing function. - 82-85: Activate handler, which just shows the widgets. - 74-80: The drawing function. It just copies `surface` to destination. - 66-72: Resize handler. Re-creates the surface to fit its width and height for the drawing area and paints by calling the function `run`. - 59-64: Close handler. It destroys `surface` if it exists. Then it destroys the top-level window and quits the application. - 49-57: Open and save handler. They just call the corresponding functions of TfeTextView. - 43-47: Run handler. It calls run function to paint the surface. After that `gtk_widget_queue_draw` is called. This function adds the widget (GtkDrawingArea) to the queue to be redrawn. It is important to know that the window is redrawn whenever 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 repainting `surface` is not automatically notified to gtk. Therefore, you need to call `gtk_widget_queue_draw` to redraw the widget. - 9-41: Run function paints the surface. First, it gets the contents of GtkTextBuffer. Then it compares 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. ~~~meson 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/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)) if you've installed Gtk4 from the source. If you've installed Gtk4 from the distribution packages, you don't need to do this. $ . 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 22](sec22.md), Next: [Section 24](sec24.md)