18 KiB
Up: Readme.md, Prev: Section 5}, Next: [Section 7[(src/sec7.src.md)# Ui file and GtkBuiler
New, open and save button
We made the simplest editor in the previous section.
It reads the files in on_open
funciton at start-up and writes it at closing 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.
Signals and handlers will be explained later.
The screenshot above shows the layout.
The function on_open
in the source code tfe2.c
is as follows.
1 static void
2 on_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) {
3 GtkWidget *win;
4 GtkWidget *nb;
5 GtkWidget *lab;
6 GtkNotebookPage *nbp;
7 GtkWidget *scr;
8 GtkWidget *tv;
9 GtkTextBuffer *tb;
10 char *contents;
11 gsize length;
12 char *filename;
13 int i;
14
15 GtkWidget *boxv;
16 GtkWidget *boxh;
17 GtkWidget *dmy1;
18 GtkWidget *dmy2;
19 GtkWidget *dmy3;
20 GtkWidget *btnn; /* button for new */
21 GtkWidget *btno; /* button for open */
22 GtkWidget *btns; /* button for save */
23 GtkWidget *btnc; /* button for close */
24
25
26 win = gtk_application_window_new (GTK_APPLICATION (app));
27 gtk_window_set_title (GTK_WINDOW (win), "file editor");
28 gtk_window_set_default_size (GTK_WINDOW (win), 600, 400);
29
30 boxv = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
31 gtk_window_set_child (GTK_WINDOW (win), boxv);
32
33 boxh = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
34 gtk_box_append (GTK_BOX (boxv), boxh);
35
36 dmy1 = gtk_label_new(NULL); /* dummy label for left space */
37 gtk_label_set_width_chars (GTK_LABEL (dmy1), 10);
38 dmy2 = gtk_label_new(NULL); /* dummy label for center space */
39 gtk_widget_set_hexpand (dmy2, TRUE);
40 dmy3 = gtk_label_new(NULL); /* dummy label for right space */
41 gtk_label_set_width_chars (GTK_LABEL (dmy3), 10);
42 btnn = gtk_button_new_with_label ("New");
43 btno = gtk_button_new_with_label ("Open");
44 btns = gtk_button_new_with_label ("Save");
45 btnc = gtk_button_new_with_label ("Close");
46
47 gtk_box_append (GTK_BOX (boxh), dmy1);
48 gtk_box_append (GTK_BOX (boxh), btnn);
49 gtk_box_append (GTK_BOX (boxh), btno);
50 gtk_box_append (GTK_BOX (boxh), dmy2);
51 gtk_box_append (GTK_BOX (boxh), btns);
52 gtk_box_append (GTK_BOX (boxh), btnc);
53 gtk_box_append (GTK_BOX (boxh), dmy3);
54
55 nb = gtk_notebook_new ();
56 gtk_widget_set_hexpand (nb, TRUE);
57 gtk_widget_set_vexpand (nb, TRUE);
58 gtk_box_append (GTK_BOX (boxv), nb);
59
60 for (i = 0; i < n_files; i++) {
61 if (g_file_load_contents (files[i], NULL, &contents, &length, NULL, NULL)) {
62 scr = gtk_scrolled_window_new ();
63 tv = tfe_text_view_new ();
64 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
65 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
66 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
67
68 tfe_text_view_set_file (TFE_TEXT_VIEW (tv), g_file_dup (files[i]));
69 gtk_text_buffer_set_text (tb, contents, length);
70 g_free (contents);
71 filename = g_file_get_basename (files[i]);
72 lab = gtk_label_new (filename);
73 gtk_notebook_append_page (GTK_NOTEBOOK (nb), scr, lab);
74 nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr);
75 g_object_set (nbp, "tab-expand", TRUE, NULL);
76 g_free (filename);
77 } else {
78 filename = g_file_get_path (files[i]);
79 g_print ("No such file: %s.\n", filename);
80 g_free (filename);
81 }
82 }
83 if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) {
84 gtk_widget_show (win);
85 } else
86 gtk_window_destroy (GTK_WINDOW (win));
87 }
The point is how to build the window.
- 26-28: Generate GtkApplicationWindow and set its title and default size.
- 30-31: Generate GtkBox
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. - 33-34: Generate GtkBox
boxh
and append it to 'boxv' as a first child. - 36-41: Generate three dummy labels.
The labels
dmy1
anddmy3
has a character width of ten. The other labeldmy2
is set hexpand property TRUE. This makes the label expands horizontally as long as possible. - 42-45: Generate four buttons.
- 47-53: Append these GtkLabel and GtkButton to
boxh
. - 55-58: Generate GtkNotebook and set hexpand and vexpand properties TRUE.
This makes it expands horizontally and vertically as big as possible.
It is appended to
boxv
as the second child.
The number of lines is 33(=58-26+1) to build the widgets. 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?
Gtk provides GtkBuilder. It reads ui data and builds a window. It reduces the cumbersom work.
Ui file
First, let's look at the ui file tfe3.ui
that defines a structure of the widgets.
1 <interface>
2 <object class="GtkApplicationWindow" id="win">
3 <property name="title">file editor</property>
4 <property name="default-width">600</property>
5 <property name="default-height">400</property>
6 <child>
7 <object class="GtkBox" id="boxv">
8 <property name="orientation">GTK_ORIENTATION_VERTICAL</property>
9 <child>
10 <object class="GtkBox" id="boxh">
11 <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
12 <child>
13 <object class="GtkLabel" id="dmy1">
14 <property name="width-chars">10</property>
15 </object>
16 </child>
17 <child>
18 <object class="GtkButton" id="btnn">
19 <property name="label">New</property>
20 </object>
21 </child>
22 <child>
23 <object class="GtkButton" id="btno">
24 <property name="label">Open</property>
25 </object>
26 </child>
27 <child>
28 <object class="GtkLabel" id="dmy2">
29 <property name="hexpand">TRUE</property>
30 </object>
31 </child>
32 <child>
33 <object class="GtkButton" id="btns">
34 <property name="label">Save</property>
35 </object>
36 </child>
37 <child>
38 <object class="GtkButton" id="btnc">
39 <property name="label">Close</property>
40 </object>
41 </child>
42 <child>
43 <object class="GtkLabel" id="dmy3">
44 <property name="width-chars">10</property>
45 </object>
46 </child>
47 </object>
48 </child>
49 <child>
50 <object class="GtkNotebook" id="nb">
51 <property name="hexpand">TRUE</property>
52 <property name="vexpand">TRUE</property>
53 </object>
54 </child>
55 </object>
56 </child>
57 </object>
58 </interface>
59
This is coded with XML structure.
Constructs begin with <
and end with >
is called tags.
And it is divided into two parts, start tag and end tag.
For example, <interface>
is a start tag and </interface>
is an end tag.
Ui file begins and ends with interface tags.
Some tags, for example, object tags can have a class and id attributes inside the start tag.
- 2-5: An object with
GtkApplicationWindow
class andwin
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 anddefault-height
property is 300. - 6: 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
.
Compare this ui file and the lines 26-58 in the source code of on_open
.
Those two decribe the same structure of widgets.
GtkBuilder
GtkBuilder builds widgets based on the ui file.
GtkBuilder *build;
build = gtk_builder_new_from_file ("tfe3.ui");
win = GTK_WIDGET (gtk_builder_get_object (build, "win"));
gtk_window_set_application (GTK_WINDOW (win), GTK_APPLICATION (app));
nb = GTK_WIDGET (gtk_builder_get_object (build, "nb"));
The function gtk_builder_new_from_file
reads the file given as an argument, build the widgets, generate GtkBuilder object and set pointers to the widgets in it.
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.
This reduces lines in the C source file.
$ diff tfe2.c tfe3.c
58a59
> GtkBuilder *build;
60,104c61,65
< GtkWidget *boxv;
< GtkWidget *boxh;
< GtkWidget *dmy1;
< GtkWidget *dmy2;
< GtkWidget *dmy3;
< GtkWidget *btnn; /* button for new */
< GtkWidget *btno; /* button for open */
< GtkWidget *btns; /* button for save */
< GtkWidget *btnc; /* button for close */
<
<
< 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), 600, 400);
<
< boxv = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
< gtk_window_set_child (GTK_WINDOW (win), boxv);
<
< boxh = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
< gtk_box_append (GTK_BOX (boxv), boxh);
<
< dmy1 = gtk_label_new(NULL); /* dummy label for left space */
< gtk_label_set_width_chars (GTK_LABEL (dmy1), 10);
< dmy2 = gtk_label_new(NULL); /* dummy label for center space */
< gtk_widget_set_hexpand (dmy2, TRUE);
< dmy3 = gtk_label_new(NULL); /* dummy label for right space */
< gtk_label_set_width_chars (GTK_LABEL (dmy3), 10);
< btnn = gtk_button_new_with_label ("New");
< btno = gtk_button_new_with_label ("Open");
< btns = gtk_button_new_with_label ("Save");
< btnc = gtk_button_new_with_label ("Close");
<
< gtk_box_append (GTK_BOX (boxh), dmy1);
< gtk_box_append (GTK_BOX (boxh), btnn);
< gtk_box_append (GTK_BOX (boxh), btno);
< gtk_box_append (GTK_BOX (boxh), dmy2);
< gtk_box_append (GTK_BOX (boxh), btns);
< gtk_box_append (GTK_BOX (boxh), btnc);
< gtk_box_append (GTK_BOX (boxh), dmy3);
<
< nb = gtk_notebook_new ();
< gtk_widget_set_hexpand (nb, TRUE);
< gtk_widget_set_vexpand (nb, TRUE);
< gtk_box_append (GTK_BOX (boxv), nb);
<
---
> build = gtk_builder_new_from_file ("tfe3.ui");
> win = GTK_WIDGET (gtk_builder_get_object (build, "win"));
> gtk_window_set_application (GTK_WINDOW (win), GTK_APPLICATION (app));
> nb = GTK_WIDGET (gtk_builder_get_object (build, "nb"));
> g_object_unref(build);
139c100
< app = gtk_application_new ("com.github.ToshioCP.tfe2", G_APPLICATION_HANDLES_OPEN);
---
> app = gtk_application_new ("com.github.ToshioCP.tfe3", G_APPLICATION_HANDLES_OPEN);
65,104c61,65
means 40 (=104-65+1) lines change to 5 (=65-61+1) lines.
Therefore 35 lines are reduced.
Using ui file not only shortens C source files, but also makes the widgets' structure clear.
Now I'll show you the C source code tfe3.c
.
Only functions on_open
are shown as follows.
1 static void
2 on_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) {
3 GtkWidget *win;
4 GtkWidget *nb;
5 GtkWidget *lab;
6 GtkNotebookPage *nbp;
7 GtkWidget *scr;
8 GtkWidget *tv;
9 GtkTextBuffer *tb;
10 char *contents;
11 gsize length;
12 char *filename;
13 int i;
14 GtkBuilder *build;
15
16 build = gtk_builder_new_from_file ("tfe3.ui");
17 win = GTK_WIDGET (gtk_builder_get_object (build, "win"));
18 gtk_window_set_application (GTK_WINDOW (win), GTK_APPLICATION (app));
19 nb = GTK_WIDGET (gtk_builder_get_object (build, "nb"));
20 g_object_unref(build);
21 for (i = 0; i < n_files; i++) {
22 if (g_file_load_contents (files[i], NULL, &contents, &length, NULL, NULL)) {
23 scr = gtk_scrolled_window_new ();
24 tv = tfe_text_view_new ();
25 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
26 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
27 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
28
29 tfe_text_view_set_file (TFE_TEXT_VIEW (tv), g_file_dup (files[i]));
30 gtk_text_buffer_set_text (tb, contents, length);
31 g_free (contents);
32 filename = g_file_get_basename (files[i]);
33 lab = gtk_label_new (filename);
34 gtk_notebook_append_page (GTK_NOTEBOOK (nb), scr, lab);
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 }
43 }
44 if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) {
45 gtk_widget_show (win);
46 } else
47 gtk_window_destroy (GTK_WINDOW (win));
48 }
Using ui string
GtkBuilder can build widgets using string. Use the function gtk_builder_new_from_string instead of gtk_builder_new_from_file.
char *uistring;
uistring =
"<interface>"
"<object class="GtkApplicationWindow" id="win">"
"<property name=\"title\">file editor</property>"
"<property name=\"default-width\">600</property>"
"<property name=\"default-height\">400</property>"
"<child>"
"<object class=\"GtkBox\" id=\"boxv\">"
"<property name="orientation">GTK_ORIENTATION_VERTICAL</property>"
... ... ...
... ... ...
"</interface>";
build = gtk_builder_new_from_stringfile (uistring);
This method has an advantage and disadvantage. The advantage is that the ui string is written in the source code. So ui file is not necessary on runtime. The disadvantage is that writing C string is a bit bothersome because of the double quotes. 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.
Using Gresource
Using Gresource is similar to using string. But Gresource is compressed binary data, not text data. And there's a compiler that compiles ui file into Gresource. It can compile not only text files but also binary files such as images, sounds and so on. And after compilation, it bundles them up into one Gresource object.
An xml file is necessary for the resource compiler glib-compile-resources
.
It describes resource files.
1 <?xml version="1.0" encoding="UTF-8"?>
2 <gresources>
3 <gresource prefix="/com/github/ToshioCP/tfe3">
4 <file>tfe3.ui</file>
5 </gresource>
6 </gresources>
- 2: gresources tag can include mulitple gresources (gresource tags). However, this xml has only one gresource.
- 3: The gresource has a prefix
/com/github/ToshioCP/tfe3
. - 4: The gresource has tfe3.ui.
And it is pointed by
/com/github/ToshioCP/tfe3/tfe3.ui
because it needs prefix. If you want to add more files, then insert them between line 4 and 5.
Save this xml text to tfe3.gresource.xml
.
The gresource compiler glib-compile-resources
shows its ussage with the argument --help
.
$ LANG=C glib-compile-resources --help
Usage:
glib-compile-resources [OPTION?] FILE
Compile a resource specification into a resource file.
Resource specification files have the extension .gresource.xml,
and the resource file have the extension called .gresource.
Help Options:
-h, --help Show help options
Application Options:
--version Show program version and exit
--target=FILE Name of the output file
--sourcedir=DIRECTORY The directories to load files referenced in FILE from (default: current directory)
--generate Generate output in the format selected for by the target filename extension
--generate-header Generate source header
--generate-source Generate source code used to link in the resource file into your code
--generate-dependencies Generate dependency list
--dependency-file=FILE Name of the dependency file to generate
--generate-phony-targets Include phony targets in the generated dependency file
--manual-register Don?t automatically create and register resource
--internal Don?t export functions; declare them G_GNUC_INTERNAL
--external-data Don?t embed resource data in the C file; assume it's linked externally instead
--c-name C identifier name used for the generated source code
Now run the compiler.
$ glib-compile-resources tfe3.gresource.xml --target=resources.c --generate-source
Then a C source file resources.c
is generated.
Modify tfe3.c and save it as tfe3_r.c
# include "resources.c"
... ... ...
... ... ...
build = gtk_builder_new_from_resource ("/com/github/ToshioCP/tfe3/tfe3.ui");
... ... ...
... ... ...
Then, compile and run it. The window appears and it is the same as the screenshot at the beginning of this page.
Up: Readme.md, Prev: Section 5}, Next: [Section 7[(src/sec7.src.md)