15 KiB
Up: README.md, Prev: Section 25, Next: Section 27
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.
The following colors are available. (without new line charactor)
- 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. Title bar, four buttons in the tool bar, textview and drawing area. The ui file is as follows.
1 <?xml version="1.0" encoding="UTF-8"?>
2 <interface>
3 <object class="GtkApplicationWindow" id="win">
4 <property name="title">color changer</property>
5 <property name="default-width">600</property>
6 <property name="default-height">400</property>
7 <child>
8 <object class="GtkBox" id="boxv">
9 <property name="orientation">GTK_ORIENTATION_VERTICAL</property>
10 <child>
11 <object class="GtkBox" id="boxh1">
12 <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
13 <child>
14 <object class="GtkLabel" id="dmy1">
15 <property name="width-chars">10</property>
16 </object>
17 </child>
18 <child>
19 <object class="GtkButton" id="btnr">
20 <property name="label">Run</property>
21 <signal name="clicked" handler="run_cb"></signal>
22 </object>
23 </child>
24 <child>
25 <object class="GtkButton" id="btno">
26 <property name="label">Open</property>
27 <signal name="clicked" handler="open_cb"></signal>
28 </object>
29 </child>
30 <child>
31 <object class="GtkLabel" id="dmy2">
32 <property name="hexpand">TRUE</property>
33 </object>
34 </child>
35 <child>
36 <object class="GtkButton" id="btns">
37 <property name="label">Save</property>
38 <signal name="clicked" handler="save_cb"></signal>
39 </object>
40 </child>
41 <child>
42 <object class="GtkButton" id="btnc">
43 <property name="label">Close</property>
44 <signal name="clicked" handler="close_cb"></signal>
45 </object>
46 </child>
47 <child>
48 <object class="GtkLabel" id="dmy3">
49 <property name="width-chars">10</property>
50 </object>
51 </child>
52 </object>
53 </child>
54 <child>
55 <object class="GtkBox" id="boxh2">
56 <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
57 <property name="homogeneous">TRUE</property>
58 <child>
59 <object class="GtkScrolledWindow" id="scr">
60 <property name="hexpand">TRUE</property>
61 <property name="vexpand">TRUE</property>
62 <child>
63 <object class="TfeTextView" id="tv">
64 <property name="wrap-mode">GTK_WRAP_WORD_CHAR</property>
65 </object>
66 </child>
67 </object>
68 </child>
69 <child>
70 <object class="GtkDrawingArea" id="da">
71 <property name="hexpand">TRUE</property>
72 <property name="vexpand">TRUE</property>
73 </object>
74 </child>
75 </object>
76 </child>
77 </object>
78 </child>
79 </object>
80 </interface>
- 10-53: The horizontal box
boxh1
makes a tool bar which has four buttons,Run
,Open
,Save
andClose
. This is similar to thetfe
text editor in Section 9. There are two differences.Run
button replacesNew
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. Options "-WI, --export-dynamic" CFLAG is necessary when you compile the application. You can achieve this by adding "export_dynamic: true" argument to the executable function inmeson.build
. And be careful that the handler must be defined without 'static' class. - 54-76: The horizontal box
boxh2
includes GtkScrolledWindow and GtkDrawingArea. 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 <?xml version="1.0" encoding="UTF-8"?>
2 <gresources>
3 <gresource prefix="/com/github/ToshioCP/color">
4 <file>color.ui</file>
5 </gresource>
6 </gresources>
Drawing function and surface
The main point of this program is a drawing function.
1 static void
2 draw_func (GtkDrawingArea *drawing_area, cairo_t *cr, int width, int height, gpointer user_data) {
3 if (surface) {
4 cairo_set_source_surface (cr, surface, 0, 0);
5 cairo_paint (cr);
6 }
7 }
The surface
variable in line 3 is a static variable.
static cairo_surface_t *surface = NULL;
The drawing function just copies the surface
to its own surface with the cairo_paint
function.
The surface (pointed by the static variable surface
) is built by the run
function.
1 static void
2 run (void) {
3 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
4 GtkTextIter start_iter;
5 GtkTextIter end_iter;
6 char *contents;
7 cairo_t *cr;
8
9 gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
10 contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
11 if (surface) {
12 cr = cairo_create (surface);
13 if (g_strcmp0 ("red", contents) == 0)
14 cairo_set_source_rgb (cr, 1, 0, 0);
15 else if (g_strcmp0 ("green", contents) == 0)
16 cairo_set_source_rgb (cr, 0, 1, 0);
17 else if (g_strcmp0 ("blue", contents) == 0)
18 cairo_set_source_rgb (cr, 0, 0, 1);
19 else if (g_strcmp0 ("white", contents) == 0)
20 cairo_set_source_rgb (cr, 1, 1, 1);
21 else if (g_strcmp0 ("black", contents) == 0)
22 cairo_set_source_rgb (cr, 0, 0, 0);
23 else if (g_strcmp0 ("light", contents) == 0)
24 cairo_set_source_rgba (cr, 1, 1, 1, 0.5);
25 else if (g_strcmp0 ("dark", contents) == 0)
26 cairo_set_source_rgba (cr, 0, 0, 0, 0.5);
27 else
28 cairo_set_source_surface (cr, surface, 0, 0);
29 cairo_paint (cr);
30 cairo_destroy (cr);
31 }
32 g_free (contents);
33 }
- 9-10: Gets the string in the GtkTextBuffer and inserts it to
contents
. - 11: If the variable
surface
points a surface instance, it is painted as follows. - 12- 30: The source is set based on the string
contents
and copied to the surface withcairo_paint
. - 24,26: Alpha channel is used in "light" and "dark" procedure.
The drawing area just reflects the surface
.
But one problem is resizing.
If a user resizes the main window, the drawing area is also resized.
It makes size difference between the surface and the drawing area.
So, the surface needs to be resized to fit the drawing area.
It is accomplished by connecting the "resize" signal on the drawing area to a handler.
g_signal_connect (GTK_DRAWING_AREA (da), "resize", G_CALLBACK (resize_cb), NULL);
The handler is as follows.
1 static void
2 resize_cb (GtkDrawingArea *drawing_area, int width, int height, gpointer user_data) {
3 if (surface)
4 cairo_surface_destroy (surface);
5 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
6 run ();
7 }
If the variable surface
sets a surface instance, it is destroyed.
A new surface is created and its size fits the drawing area.
The surface is assigned to the variable surface
.
The function run
is called and the surface is colored.
The signal is emitted when:
- The drawing area is realized (it appears on the display).
- It is changed (resized) while realized
So, the first surface is created when it is realized.
Colorapplication.c
This is the main file.
- Builds widgets by GtkBuilder.
- Sets a drawing function for GtkDrawingArea. And connects a handler to the "resize" signal on the GtkDrawingArea instance.
- Implements each call back function.
Particularly,
Run
signal handler is the point in this program.
The following is colorapplication.c
.
1 #include <gtk/gtk.h>
2 #include "../tfetextview/tfetextview.h"
3
4 static GtkWidget *win;
5 static GtkWidget *tv;
6 static GtkWidget *da;
7
8 static cairo_surface_t *surface = NULL;
9
10 static void
11 run (void) {
12 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
13 GtkTextIter start_iter;
14 GtkTextIter end_iter;
15 char *contents;
16 cairo_t *cr;
17
18 gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
19 contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
20 if (surface) {
21 cr = cairo_create (surface);
22 if (g_strcmp0 ("red", contents) == 0)
23 cairo_set_source_rgb (cr, 1, 0, 0);
24 else if (g_strcmp0 ("green", contents) == 0)
25 cairo_set_source_rgb (cr, 0, 1, 0);
26 else if (g_strcmp0 ("blue", contents) == 0)
27 cairo_set_source_rgb (cr, 0, 0, 1);
28 else if (g_strcmp0 ("white", contents) == 0)
29 cairo_set_source_rgb (cr, 1, 1, 1);
30 else if (g_strcmp0 ("black", contents) == 0)
31 cairo_set_source_rgb (cr, 0, 0, 0);
32 else if (g_strcmp0 ("light", contents) == 0)
33 cairo_set_source_rgba (cr, 1, 1, 1, 0.5);
34 else if (g_strcmp0 ("dark", contents) == 0)
35 cairo_set_source_rgba (cr, 0, 0, 0, 0.5);
36 else
37 cairo_set_source_surface (cr, surface, 0, 0);
38 cairo_paint (cr);
39 cairo_destroy (cr);
40 }
41 g_free (contents);
42 }
43
44 void
45 run_cb (GtkWidget *btnr) {
46 run ();
47 gtk_widget_queue_draw (GTK_WIDGET (da));
48 }
49
50 void
51 open_cb (GtkWidget *btno) {
52 tfe_text_view_open (TFE_TEXT_VIEW (tv), GTK_WINDOW (win));
53 }
54
55 void
56 save_cb (GtkWidget *btns) {
57 tfe_text_view_save (TFE_TEXT_VIEW (tv));
58 }
59
60 void
61 close_cb (GtkWidget *btnc) {
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 app_activate (GApplication *application) {
83 gtk_window_present (GTK_WINDOW (win));
84 }
85
86 static void
87 app_startup (GApplication *application) {
88 GtkApplication *app = GTK_APPLICATION (application);
89 GtkBuilder *build;
90 GdkDisplay *display;
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 display = gdk_display_get_default ();
102 GtkCssProvider *provider = gtk_css_provider_new ();
103 gtk_css_provider_load_from_data (provider, "textview {padding: 10px; font-family: monospace; font-size: 12pt;}", -1);
104 gtk_style_context_add_provider_for_display (display, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_USER);
105 }
106
107 static void
108 app_shutdown (GApplication *application) {
109 if (surface)
110 cairo_surface_destroy (surface);
111 }
112
113 #define APPLICATION_ID "com.github.ToshioCP.color"
114
115 int
116 main (int argc, char **argv) {
117 GtkApplication *app;
118 int stat;
119
120 app = gtk_application_new (APPLICATION_ID, G_APPLICATION_DEFAULT_FLAGS);
121
122 g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL);
123 g_signal_connect (app, "shutdown", G_CALLBACK (app_shutdown), NULL);
124 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
125
126 stat =g_application_run (G_APPLICATION (app), argc, argv);
127 g_object_unref (app);
128 return stat;
129 }
130
- 4-8: Win, tv, da and surface are defined as static variables.
- 10-42: Run function.
- 44-63: Handlers for button signals.
- 65-71: Resize handler.
- 73-79: Drawing function.
- 81-84: Application activate handler. It just shows the main window.
- 86-105: Application startup handler.
- 92- 97: It builds widgets according to the ui resource. The static variables win, tv and da are assigned instances.
- 98: Connects "resize" signal and a handler.
- 99: Drawing function is set.
- 101-104: CSS for textview padding is set.
- 107-111: Application shutdown handler. If there exists a surface instance, it will be destroyed.
- 116-129: A function
main
. It creates a new application instance. And connects three signals startup, shutdown and activate to their handlers. It runs the application. It releases the reference to the application and returns withstat
value.
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/tfetextview.c')
9
10 executable('color', sourcefiles, resources, dependencies: gtkdep, export_dynamic: true)
Build and try
Type the following to compile the program.
$ 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.
No new line charactor is needed.
Then, click on the 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, Prev: Section 25, Next: Section 27