Add prerequisite and licence.

This commit is contained in:
Toshio Sekiya 2020-12-22 11:30:06 +09:00
parent 3abea822a9
commit 45cb071dc6
38 changed files with 6996 additions and 6896 deletions

1
.gitignore vendored
View file

@ -4,3 +4,4 @@ insertfunc.rb
makehtml.rb
pickoutfunc.rb
src/img.rb
src/toi.rb

View file

@ -40,9 +40,9 @@ task md: mdfilenames
end
task :md do
buf = [ "# Gtk4 TUtorial for beginners\n", "\n" ]
buf = [ "# Gtk4 Tutorial for beginners\n", "\n" ]
buf << "This tutorial is under development and unstable.\n"
buf << "You should be careful because there might exists bugs, errors or mistakes.\n"
buf << "You should be careful because there exists bugs, errors or mistakes.\n"
buf << "\n"
0.upto(srcfiles.size-1) do |i|
h = File.open(srcfiles[i].path) { |file| file.readline }

View file

@ -1,22 +1,22 @@
# Gtk4 TUtorial for beginners
# Gtk4 Tutorial for beginners
This tutorial is under development and unstable.
You should be careful because there might exists bugs, errors or mistakes.
You should be careful because there exists bugs, errors or mistakes.
- [GtkApplication and GtkApplicationWindow](sec1.md)
- [Widgets (1)](sec2.md)
- [Widgets (2)](sec3.md)
- [Widgets (3)](sec4.md)
- [Define Child object](sec5.md)
- [Ui file and GtkBuiler](sec6.md)
- [Build system](sec7.md)
- [Instance and class](sec8.md)
- [Signals](sec9.md)
- [Functions in TfeTextView](sec10.md)
- [Functions with GtkNotebook](sec11.md)
- [tfeapplication.c](sec12.md)
- [tfe5 source files](sec13.md)
- [Menu and action](sec14.md)
- [Stateful action](sec15.md)
- [Ui file for menu and action entries](sec16.md)
- [GtkMenuButton](sec17.md)
- [Prerequisite and Licence](sec1.md)
- [GtkApplication and GtkApplicationWindow](sec2.md)
- [Widgets (1)](sec3.md)
- [Widgets (2)](sec4.md)
- [Widgets (3)](sec5.md)
- [Define Child object](sec6.md)
- [Ui file and GtkBuiler](sec7.md)
- [Build system](sec8.md)
- [Instance and class](sec9.md)
- [Signals](sec10.md)
- [Functions in TfeTextView](sec11.md)
- [Functions with GtkNotebook](sec12.md)
- [tfeapplication.c](sec13.md)
- [tfe5 source files](sec14.md)
- [Menu and action](sec15.md)
- [Stateful action](sec16.md)
- [Ui file for menu and action entries](sec17.md)

View file

@ -59,6 +59,8 @@ class Sec_file < String
nil
end
end
# Note: is_i? indicates the number is integer mathematically. For example, 2.0 is an integer.
# It doesn't mean the class of the number is Integer.
def is_i?
self.num == self.num.floor
end
@ -68,12 +70,15 @@ class Sec_file < String
n = n.to_f if n =~ /^\d+\.\d+/
end
if n.instance_of?(Integer) || n.instance_of?(Float)
n = n.to_i if n == n.floor
old = self
new = self.gsub(/\d+(\.\d+)?(\.(src\.md|md|html|tex)$)/, "#{n}\\2")
File.rename old, new
self.replace new
@name = File.basename self
@dirname = File.dirname self
if old != new
File.rename old, new
self.replace new
@name = File.basename self
@dirname = File.dirname self
end
end
end
end

306
sec1.md
View file

@ -1,285 +1,53 @@
Up: [Readme.md](Readme.md), Next: [Section 2](sec2.md)
# GtkApplication and GtkApplicationWindow
# Prerequisite and Licence
## GtkApplication
## Prerequisite
### GtkApplication and g\_application\_run
### Tutorial document
Usually people write a programming code to make an application.
What are appications?
Applications are software that runs using libraries, which includes OS, frameworks and so on.
In Gtk4 programming, GtkApplication is an object that runs on GTK libraries.
The tutorial is about gtk4 libraries.
It is originally used on linux with C compiler, but now it is used more widely, on windows and macOS, with Vala, python and so on.
However, this tutorial describes only C programs on linux.
The basic way how to write GtkApplication is as follows.
You need:
- Generate a GtkApplication object
- Run it
- PC with linux distribution like ubuntu, debian and so on.
- Gcc
- Gtk4. Gtk included linux distributions is version three at present.
You need to install gtk4 to your computer.
Refer to [gtk4 gitlab repository](https://gitlab.gnome.org/GNOME/gtk).
However, it might make some trouble like, for example, your pc doesn't recognize usb port.
You should uninstall it after learning gtk4 programing.
That's all.
Very simple.
The following is the C code representing the scenario above.
### Software
1 #include <gtk/gtk.h>
2
3 int
4 main (int argc, char **argv) {
5 GtkApplication *app;
6 int stat;
7
8 app = gtk_application_new ("com.github.ToshioCP.pr1", G_APPLICATION_FLAGS_NONE);
9 stat =g_application_run (G_APPLICATION (app), argc, argv);
10 g_object_unref (app);
11 return stat;
12 }
13
This repository inclludes ruby programs.
They are used to generate markdown files, html files, latex files and a pdf file.
The first line says that this program includes the GTK header libraries.
The function `main` above is a startup function in C language.
The variable `app` is defined as a pointer to GtkApplication, which is actually a structure in which information about the application is stored.
The function `gtk_application_new` generates a GtkApplication and sets its pointer to `app`.
The meaning of the arguments will be explained later.
The function `g_application_run` invokes the GtkApplication pointed by `app`.
(We often say that the function invokes `app`.
Actually, `app` is not an object but an pointer to the object.
However, it is simple and short, and probably no confusion occurs.)
You need:
To compile this, the following command needs to be run.
The string pr1.c is the filename of the C source code.
- Linux distribution like ubuntu.
- Ruby programing language.
There are two ways to install it.
One is install the distribution's package.
The other is using rbenv and ruby-build.
If you want to use the latest version of ruby, use rbenv.
- Rake.
It is a gem, which is a library written in ruby.
You can install it as a package of your ditribution or use gem command.
$ gcc `pkg-config --cflags gtk4` pr1.c `pkg-config --libs gtk4`
## Licence
The C compiler gcc generates an executable file `a.out`.
Let's run it.
Copyright (C) 2020 ToshioCP (Toshio Sekiya)
$ ./a.out
Gtk tutorial repository containes the tutorial document and softwares such as converters, generators and controlers.
All of them make up the 'Gtk tutorial' package.
This package is simply called 'Gtk tutorial' in the following description.
'Gtk4 tutorial' is free; you can redistribute it and/or modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
(a.out:13533): GLib-GIO-WARNING **: 15:30:17.449: Your application does not implement g_application_activate() and has no handlers connected to the "activate" signal. It should do one of these.
$
Oh, just an error message.
But this error message means that the GtkApplication object ran without a doubt.
Now, think about the message in the next section.
### signal
The message tells us that:
1. The application GtkApplication doesn't implement `g_application_activate()`.
2. And it has no handlers connected to the activate signal.
3. You need to solve at least one of this.
These two cause of the error are related to signals.
So, I will explain it to you first.
Signal is emitted when something happens.
For example, a window is generated, a window is destroyed and so on.
The signal "activate" is emitted when the application is activated.
If the signal is connected to a function, which is called signal handler or simply handler, then the function invokes when the signal emits.
The flow is like this:
1. Something happens.
2. If it's related to a certain signal, then the signal is emitted.
3. If the signal is connected to a handler in advance, then the handler is invoked.
Signals are defined in objects.
For example, "activate" signal belongs to GApplication object, which is a parent object of GtkApplication object.
GApplication object is a child object of GObject object.
GObject is the top object in the hierarchy of all the objects.
GObject -- GApplication -- GtkApplication
<---parent --->child
A child object derives signals, functions, properties and so on from its parent object.
So, Gtkapplication also has the "activate" signal.
Now we can solve the problem in `pr1.c`.
We need to connect the activate signal to a handler.
We use a function `g_signal_connect` which connects a signal to a handler.
1 #include <gtk/gtk.h>
2
3 static void
4 on_activate (GApplication *app, gpointer *user_data) {
5 g_print ("GtkApplication is activated.\n");
6 }
7
8 int
9 main (int argc, char **argv) {
10 GtkApplication *app;
11 int stat;
12
13 app = gtk_application_new ("com.github.ToshioCP.pr2", G_APPLICATION_FLAGS_NONE);
14 g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
15 stat =g_application_run (G_APPLICATION (app), argc, argv);
16 g_object_unref (app);
17 return stat;
18 }
19
First, we define the handler `on_activate` which simply displays a message.
In the function `main`, we add `g_signal_connect` before `g_application_run`.
The function `g_signal_connect` has four arguments.
1. An object to which the signal belongs.
2. The name of the signal.
3. A handler function (also called callback), which needs to be casted by `G_CALLBACK`.
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.
In addition, `g_signal_connect` is described in GObject API reference.
API reference is very important.
You should see and understand it to write GTK applications.
Let's compile the source file `pr2.c` above and run it.
$ gcc `pkg-config --cflags gtk4` pr2.c `pkg-config --libs gtk4`
$ ./a.out
GtkApplication is activated.
$
OK, well done.
However, you may have noticed that it's painful to type such a long line to compile.
It is a good idea to use shell script to solve this problem.
Make a text file which contains the following text.
gcc `pkg-config --cflags gtk4` $1.c `pkg-config --libs gtk4`
Then, save it in $HOME/bin, which is usually /home/(username)/bin.
(If your user name is James, then the directory is /home/james/bin).
And turn on the execute bit of the file.
Suppose the filename is comp, then the procedure is as follows.
$ chmod 755 $HOME/bin/comp
$ ls -log $HOME/bin
... ... ...
-rwxr-xr-x 1 62 May 23 08:21 comp
... ... ...
If this is the first time that you make a $HOME/bin directory and save a file in it, then you need to logout and login again.
$ comp pr2
$ ./a.out
GtkApplication is activated.
$
## GtkWindow and GtkApplicationWindow
### GtkWindow
A message "GtkApplication is activated." was printed out in the previous subsection.
It was good in terms of a test of GtkApplication.
However, it is insufficient because GTK is a framework for graphical user interface (GUI).
Now we go ahead with adding a window into this program.
What we need to do is:
1. Generate a GtkWindow.
2. Connect it to GtkApplication.
3. Show the window.
Now rewrite the function `on_activate`.
#### Generate a GtkWindow
1 static void
2 on_activate (GApplication *app, gpointer user_data) {
3 GtkWidget *win;
4
5 win = gtk_window_new ();
6 gtk_window_set_application (GTK_WINDOW (win), GTK_APPLICATION (app));
7 gtk_widget_show (win);
8 }
Widget is an abstract concept that includes all the GUI interfaces such as windows, dialogs, buttons, multiline text, containers and so on.
And GtkWidget is a base object from which all the GUI objects derive.
parent <-----> child
GtkWidget -- GtkWindow
GtkWindow includes GtkWidget at the top of its object.
![GtkWindow and GtkWidget](image/window_widget.png)
The function `gtk_window_new` is defined as follows.
GtkWidget *
gtk_window_new (void);
By this definition, it returns a pointer to GtkWidget, not GtkWindow.
It actually generates a new GtkWindow object (not GtkWidget) but returns a pointer to GtkWidget.
However,the pointer points the GtkWidget and at the same time it also points GtkWindow that contains GtkWidget in it.
If you want to use `win` as a pointer to the GtkWindow, you need to cast it.
(GtkWindow *) win
Or you can use `GTK_WINDOW` macro that performs a similar function.
GTK_WINDOW (win)
This is a recommended way.
#### Connect it to GtkApplication.
The function `gtk_window_set_application` is used to connect GtkWidow to GtkApplication.
gtk_window_set_application (GTK_WINDOW (win), GTK_APPLICATION (app));
You need to cast `win` to GtkWindow and `app` to GtkApplication.
`GTK_WINDOW` and `GTK_APPLICATION` macro is appropriate for that.
GtkApplication continues to run until the related window is destroyed.
If you didn't connect GtkWindow and GtkApplication, GtkApplication shutdowns soon.
Because no window is connected to GtkApplication, it doesn't need to wait anything.
As it shutdowns the generated window is also destroyed.
#### Show the window.
The function `gtk_widget_show` is used to show the window.
Gtk4 changed the default widget visibility to on, so every widget doesn't need this function to show itself.
But, there's an exception.
Top window (this term will be explained later) isn't visible when it is generated.
So you need to use the function above and show the window.
Save the program as `pr3.c` and compile and run it.
$ comp pr3
$ ./a.out
A small window appears.
![Screenshot of the window](image/screenshot_pr3.png)
Click on the close button then the window disappears and the program finishes.
### GtkApplicationWindow
GtkApplicationWindow is a child object of GtkWindow.
It has some extra functionality for better integration with GtkApplication.
It is recommended to use it instead of GtkWindow when you use GtkApplication.
Now rewrite the program and use GtkAppliction Window.
1 static void
2 on_activate (GApplication *app, gpointer user_data) {
3 GtkWidget *win;
4
5 win = gtk_application_window_new (GTK_APPLICATION (app));
6 gtk_window_set_title (GTK_WINDOW (win), "pr4");
7 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
8 gtk_widget_show (win);
9 }
When you generate GtkApplicationWindow, you need to give GtkApplication object as an argument.
Then it automatically connect these two objects.
So you don't need to call `gtk_window_set_application` any more.
The program sets the title and the default size of the window.
Compile it and run `a.out`, then you will see a bigger window with its title "pr4".
![Screenshot of the window](image/screenshot_pr4.png)
'Gtk tutorial' is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the [GNU General Public License](https://www.gnu.org/licenses/gpl-3.0.html) for more details.
Up: [Readme.md](Readme.md), Next: [Section 2](sec2.md)

439
sec10.md
View file

@ -1,346 +1,163 @@
Up: [Readme.md](Readme.md), Prev: [Section 9](sec9.md), Next: [Section 11](sec11.md)
# Functions in TfeTextView
# Signals
In this section I will explain each function in TfeTextView object.
## Signals
### tfe.h and tfetextview.h
In GTK programming, each object is capsulated.
And it is not recommended to use global variables because they tend to make the program complicated.
So, we need something to communicate between objects.
There are two ways to do so.
`tfe.h` is a top header file and it includes `gtk.h` and all the header files.
Every C source files, which are `tfeapplication.c`, `tfenotebook.c` and `tfetextview.c`, include `tfe.h` at the beginning of each file.
- Functions.
For example, `tb = gtk_text_view_get_buffer (tv)`.
The function caller requests `tv`, which is a GtkTextView object, to give back `tb`, which is a GtkTextBuffer object connected to `tv`.
- Signals.
For example, `activate` signal on GApplication object.
When the application is activated, the signal is emitted.
Then the handler, which has been connected to the signal, is invoked.
1 #include <gtk/gtk.h>
2
3 #include "tfetextview.h"
4 #include "tfenotebook.h"
The caller of the function or the handler connected to the signal is usually outside of the object.
One of the difference between these two is that the object is active or passive.
In functions the object responds to the caller.
In signals the object actively sends a signal to the handler.
`tfetextview.h` is a header file which describes the public functions in `tfetextview.c`.
GObject signal can be registered, connected and emitted.
1 #define TFE_TYPE_TEXT_VIEW tfe_text_view_get_type ()
2 G_DECLARE_FINAL_TYPE (TfeTextView, tfe_text_view, TFE, TEXT_VIEW, GtkTextView)
3
4 /* "open-response" signal response */
5 enum
6 {
7 TFE_OPEN_RESPONSE_SUCCESS,
8 TFE_OPEN_RESPONSE_CANCEL,
9 TFE_OPEN_RESPONSE_ERROR
10 };
11
12 GFile *
13 tfe_text_view_get_file (TfeTextView *tv);
14
15 void
16 tfe_text_view_open (TfeTextView *tv);
17
18 void
19 tfe_text_view_save (TfeTextView *tv);
20
21 void
22 tfe_text_view_saveas (TfeTextView *tv);
23
24 GtkWidget *
25 tfe_text_view_new_with_file (GFile *file);
26
27 GtkWidget *
28 tfe_text_view_new (void);
29
1. A signal is registered with the object type on which it can be emitted.
This is done usually when the class is initialized.
2. It is connected to a handler by `g_connect_signal` or its family functions.
3. When it is emmitted, the connected handler is invoked.
- 1-2: These two lines are used to define TfeTextView.
- 4-10: Definitions of parameter used in the handler of "open-response" signal.
- 12-28: Public functions on GtkTextView.
Step one and three are done in the object on which the signal is emitted.
Step two is done outside the objects.
Each function will be explained later in this section.
## Signal registration
## Functions to generate TfeTextView object
In TfeTextView, two signals are registered.
TfeTextView Object is generated by `tfe_text_view_new` or `tfe_text_view_new_with_file`.
- "change-file" signal.
This signal is emitted when `tv->file` is changed.
- "open-response" signal.
`tfe_text_view_open` function is not able to return the status because of using GtkFileChooserDialog.
This signal is emitted instead of the return value of the function.
GtkWidget *tfe_text_view_new (void);
Static variable is used to store the signal ID.
If you need to register two or more signals, static array is usually used.
`tfe_text_view_new` just generates a new TfeTextView object and returns the pointer to the new object.
enum {
CHANGE_FILE,
OPEN_RESPONSE,
NUMBER_OF_SIGNALS
};
GtkWidget *tfe_text_view_new_with_file (GFile *file);
static guint tfe_text_view_signals[NUMBER_OF_SIGNALS];
`tfe_text_view_new_with_file` is given a Gfile object as the argument and it loads the file into the GtkTextBuffer object, then returns the pointer to the new object.
Parameter:
- `file`: a pointer to the GFile object.
Return value:
- A pointer to the generated TfeTextView object but it is casted to a pointer to GtkWidget.
If an error occures during the genration process, NULL is returned.
Each function is defined as follows.
1 GtkWidget *
2 tfe_text_view_new_with_file (GFile *file) {
3 g_return_val_if_fail (G_IS_FILE (file), NULL);
4
5 GtkWidget *tv;
6 char *contents;
7 gsize length;
8
9 if (! g_file_load_contents (file, NULL, &contents, &length, NULL, NULL)) /* read error */
10 return NULL;
11
12 tv = tfe_text_view_new();
13 gtk_text_buffer_set_text (TFE_TEXT_VIEW (tv)->tb, contents, length);
14 g_free (contents);
15 TFE_TEXT_VIEW (tv)->file = g_file_dup (file);
16 return tv;
17 }
18
19 GtkWidget *
20 tfe_text_view_new (void) {
21 return gtk_widget_new (TFE_TYPE_TEXT_VIEW, NULL);
22 }
- 18-21: `tfe_text_view_new`.
Just returns the value from the function `gtk_widget_new`.
Initialization is done in `tfe_text_view_init` which is called in the process of `gtk_widget_new` function.
- 1-16: `tfe_text_view_new_with_file`
- 3: `g_return_val_if_fail` is described in [Glib API reference](https://developer.gnome.org/glib/stable/glib-Warnings-and-Assertions.html#g-return-val-if-fail).
It tests whether the argument `file` is a pointer to GFile.
If it's true, then the program goes on to the next line.
If it's false, then it returns NULL (the second argument) immediately.
And at the same time it logs out the error message (usually the log is outputted to stderr or stdout).
This function is used to check the programmer's error.
If an error occurs, the solution is usually to change the (caller) program and fix the bug.
You need to distinguish programmer's errors and runtime errors.
You shouldn't use this function to find runtime errors.
- 9-10: If an error occurs when reading the file, then return NULL.
- 11-15: Generate TfeTextView and set the pointer to it to `tv`.
Set the contents read from the file to GtkTextBuffer `tv->tb`.
Free the memories pointed by `contents`.
Duplicate `file` and set it to `tv->file`.
Return `tv`.
## Save and saveas functions
Save and saveas functions write the contents in GtkTextBuffer to a file.
void tfe_text_view_save (TfeTextView *tv)
`save` function writes the contents in GtkTextBuffer to a file specified by `tv->file`.
If `tv->file` is NULL, then it shows GtkFileChooserDialog and lets the user to give a file to the program. After that, it saves the contents to the specified file and set the file into `tv->file`.
void tfe_text_view_saveas (TfeTextView *tv)
`saveas` function uses GtkFileChooserDialog and lets the user to give a new file to the program. Then, the function changes `tv->file` and save the contents to the specified new file.
If an error occures, it is shown to the user through the message dialog.
The error is managed only in the object and no information is notified to the caller.
Signal registration codes are written in the class initialization function.
1 static void
2 saveas_dialog_response (GtkWidget *dialog, gint response, TfeTextView *tv) {
3 GFile *file;
2 tfe_text_view_class_init (TfeTextViewClass *class) {
3 GObjectClass *object_class = G_OBJECT_CLASS (class);
4
5 if (response == GTK_RESPONSE_ACCEPT) {
6 file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
7 if (G_IS_FILE(file)) {
8 tv->file = file;
9 tv->changed = TRUE;
10 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
11 tfe_text_view_save (TFE_TEXT_VIEW (tv));
12 }
13 }
14 gtk_window_destroy (GTK_WINDOW (dialog));
15 }
16
17 void
18 tfe_text_view_save (TfeTextView *tv) {
19 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
20
21 GtkTextIter start_iter;
22 GtkTextIter end_iter;
23 gchar *contents;
24 GtkWidget *message_dialog;
25 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
26 GError *err = NULL;
27
28 if (! tv->changed)
29 return; /* no necessary to save it */
30 else if (tv->file == NULL)
31 tfe_text_view_saveas (tv);
32 else {
33 gtk_text_buffer_get_bounds (tv->tb, &start_iter, &end_iter);
34 contents = gtk_text_buffer_get_text (tv->tb, &start_iter, &end_iter, FALSE);
35 if (g_file_replace_contents (tv->file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, &err))
36 tv->changed = FALSE;
37 else {
38 /* It is possible that tv->file is broken. */
39 /* It is a good idea to set tv->file to NULL. */
40 if (G_IS_FILE (tv->file))
41 g_object_unref (tv->file);
42 tv->file =NULL;
43 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
44 tv->changed = TRUE;
45 message_dialog = gtk_message_dialog_new (GTK_WINDOW (win), GTK_DIALOG_MODAL,
46 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
47 "%s.\n", err->message);
48 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
49 gtk_widget_show (message_dialog);
50 g_error_free (err);
51 }
52 }
53 }
54
55 void
56 tfe_text_view_saveas (TfeTextView *tv) {
57 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
58
59 GtkWidget *dialog;
60 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
61
62 dialog = gtk_file_chooser_dialog_new ("Save file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_SAVE,
63 "_Cancel", GTK_RESPONSE_CANCEL,
64 "_Save", GTK_RESPONSE_ACCEPT,
65 NULL);
66 g_signal_connect (dialog, "response", G_CALLBACK (saveas_dialog_response), tv);
67 gtk_widget_show (dialog);
68 }
5 object_class->dispose = tfe_text_view_dispose;
6 tfe_text_view_signals[CHANGE_FILE] = g_signal_newv ("change-file",
7 G_TYPE_FROM_CLASS (class),
8 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
9 NULL /* closure */,
10 NULL /* accumulator */,
11 NULL /* accumulator data */,
12 NULL /* C marshaller */,
13 G_TYPE_NONE /* return_type */,
14 0 /* n_params */,
15 NULL /* param_types */);
16 GType param_types[] = {G_TYPE_INT};
17 tfe_text_view_signals[OPEN_RESPONSE] = g_signal_newv ("open-response",
18 G_TYPE_FROM_CLASS (class),
19 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
20 NULL /* closure */,
21 NULL /* accumulator */,
22 NULL /* accumulator data */,
23 NULL /* C marshaller */,
24 G_TYPE_NONE /* return_type */,
25 1 /* n_params */,
26 param_types);
27 }
- 17-53: `Tfe_text_view_save` function.
- 19: If `tv` is not a pointer to TfeTextView, then it logs an error message and immediately returns.
This function is similar to `g_return_val_if_fail` function, but no value is returned because `tfe_text_view_save` doesn't return a value.
- 28-29: If the buffer hasn't modified, then it doesn't need to save it.
So the function returns.
- 30-31: If `tv->file` is NULL, no file has given yet.
It calls `tfe_text_view_save`, which lets the user to choose a file to save.
- 33-35: Save the buffer to the file.
If it succeeds, assigns FALSE to `tv->changed`.
- 38-50: If file writing fails, it assigns NULL to `tv->file`.
Emits "change-file" signal.
Shows the error message dialog (45-49).
Because the handler is `gtk_window_destroy`, the dialog disappears when user clicks on the button in the dialog.
- 55-68: `tfe_text_view_saveas` function.
It shows GtkFileChooserDialog and lets the user choose a file and give it to the signal handler.
- 62: Generate GtkFileChooserDialog.
The title is "Save file".
Transient parent of the dialog is `win`, which is the top level window.
The action is save mode.
The buttons are Cancel and Save.
- 63: connect the "response" signal of the dialog and `saveas_dialog_response` handler.
- 1-15: `saveas_dialog_response` signal handler.
- 5-13: If the response is `GTK_RESPONSE_ACCEPT`, which is set to the argument when the user has clicked on Save button, then gets a pointer to the GFile object, set it to `tv->file`, assign TRUE to `tv->changed`, emits "change-file" signal then call `tfe_text_view_save` to save the buffer to the file.
- 6-15: Register "change-file"signal.
`g_signal_newv` function is used.
This signal has no default handler (object method handler).
I think you usually don't need to set a default handler in final type object.
If you need it, put the closure of the handler in line 9.
- The return value of `g_signal_newv` is the signal id.
The type of signal id is guint, which is the same as unsigned int.
It is used when the signal is emitted.
- 16-26: Register "open-response" signal.
This signal has a parameter.
- 25: Number of the parameter.
"open-response" signal has one parameter.
- 26: An array of types of parameters.
The array `param_types` is defined in line 16.
It has one element, which is `G_TYPE_INT`.
`G_TYPE_INT` is a type of integer.
Such fundamental types are described in [GObject API reference](https://developer.gnome.org/gobject/stable/gobject-Type-Information.html).
![Saveas process](image/saveas.png)
The handlers are as follows.
When you use GtkFileChooserDialog, you need to divide the program into two parts.
They are a function which generates GtkFileChooserDialog and the signal handler.
The function just generates and shows the dialog.
The rest is done by the handler.
It gets Gfile from GtkFileChooserDialog, save the buffer to the file and do some things necessary.
void change_file_handler (TfeTextView *tv, gpointer user_data);
void open_response_handler (TfeTextView *tv, guint parameter, gpointer user_data);
## Open function
- Because "change-file" signal doesn't have parameter, the handler's parameter is TfeTextView object and user data.
- Because "open-response" signal has one parameter, the handler's parameter is TfeTextView object, the parameter and user data.
- `tv` is the object instance on which the signal is emitted.
- `user_data` comes from the fourth argument of `g_signal_connect`.
- `parameter` comes from the fourth argument of `g_signal_emit`.
Open function shows GtkFileChooserDialog to the user and let him/her choose a file.
Then read the file and set it to GtkTextBuffer.
The parameter is defined in `tfetextview.h` because it is public.
void tfe_text_view_open (TfeTextView *tv)
/* "open-response" signal response */
enum
{
TFE_OPEN_RESPONSE_SUCCESS,
TFE_OPEN_RESPONSE_CANCEL,
TFE_OPEN_RESPONSE_ERROR
};
TfeTextView object `tv` has to be generated in advance.
And it should be empty and `tv->file` is NULL.
If it is not empty, `tfe_text_view_open` doesn't treat it as an error.
If you want to revert the buffer, calling this function is apropreate.
Otherwise probably bad things will happen.
- `TFE_OPEN_RESPONSE_SUCCESS` is set when `tfe_text_view_open` successfully has opend a file and loaded it.
- `TFE_OPEN_RESPONSE_CANCEL` is set when the user canceled to open a file.
- `TFE_OPEN_RESPONSE_ERROR` is set when error occured.
## Signal connection
1 static void
2 open_dialog_response(GtkWidget *dialog, gint response, TfeTextView *tv) {
3 GFile *file;
4 char *contents;
5 gsize length;
6 GtkWidget *message_dialog;
7 GError *err = NULL;
8
9 if (response != GTK_RESPONSE_ACCEPT)
10 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_CANCEL);
11 else if (! G_IS_FILE (file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog))))
12 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
13 else if (! g_file_load_contents (file, NULL, &contents, &length, NULL, &err)) { /* read error */
14 if (G_IS_FILE (file))
15 g_object_unref (file);
16 message_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL,
17 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
18 "%s.\n", err->message);
19 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
20 gtk_widget_show (message_dialog);
21 g_error_free (err);
22 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
23 } else {
24 gtk_text_buffer_set_text (tv->tb, contents, length);
25 g_free (contents);
26 tv->file = file;
27 /* tv->changed = FALSE;*/
28 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS);
29 }
30 gtk_window_destroy (GTK_WINDOW (dialog));
31 }
32
33 void
34 tfe_text_view_open (TfeTextView *tv) {
35 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
36
37 GtkWidget *dialog;
38
39 dialog = gtk_file_chooser_dialog_new ("Open file", NULL, GTK_FILE_CHOOSER_ACTION_OPEN,
40 "Cancel", GTK_RESPONSE_CANCEL,
41 "Open", GTK_RESPONSE_ACCEPT,
42 NULL);
43 g_signal_connect (dialog, "response", G_CALLBACK (open_dialog_response), tv);
44 gtk_widget_show (dialog);
45 }
A signal and a handler are connected by the function `g_signal_connect`.
There some similar functions like `g_signal_connect_after`, `g_signal_connect_swapped` and so on.
But I think `g_signal_connect` is the most common function.
The signals "change-file" is connected to a callback function `file_changed` outside of TfeTextView object.
In the same way, the signals "open-response" is connected to a callback function `open_response` outside of TfeTextView object.
The functions `file_changed` and `open_response` will be explained later.
- 33-45: `tfe_text_view_open` function.
- 39: Generate GtkFileChooserDialog.
The title is "Open file".
No transient parent window.
The action is open mode.
The buttons are Cancel and Open.
- 43: connect the "reponse" signal of the dialog and `open_dialog_response` signal handler.
- 44: Show the dialog.
- 1-31: `open_dialog_response` signal handler.
- 9-10: If the response from GtkFileChooserDialog is not `GTK_RESPONSE_ACCEPT`, which means the user has clicked on the "Cancel" button or close button, then it emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_CANCEL`.
- 11-12: Get a pointer to Gfile by `gtk_file_chooser_get_file`.
If it is not GFile, maybe an error occured.
Then it emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_ERROR`.
- 13-22: If an error occurs when it has read the file, then it decreases the reference count of Gfile, shows a message dialog to report the error to the user and emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_ERROR`.
- 24-28: If the file has successfully read, then the text is set to GtkTextBuffer, free the temporary buffer pointed by `contents`, set file to `tv->file` (no duplication or unref is not necessary) and emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_SUCCESS`.
- 30: close GtkFileCooserDialog.
g_signal_connect (GTK_TEXT_VIEW (tv), "change-file", G_CALLBACK (file_changed), nb);
Now let's think about the whole process between the other object (caller) and TfeTextView.
It is shown in the following diagram and you would think that it is really complicated.
Because signal is the only way for GtkFileChooserDialog to communicate with others.
In Gtk3, `gtk_dialog_run` function is available.
It simplifies the process.
However, in Gtk4, `gtk_dialog_run`is unavailable any more.
g_signal_connect (TFE_TEXT_VIEW (tv), "open-response", G_CALLBACK (open_response), nb);
![Caller and TfeTextView](image/open.png)
## Signal emission
1. A caller get a pointer `tv` to TfeTextView by calling `tfe_text_view_new`.
2. The caller connects the handler (left bottom in the diagram) and the signal "open-response".
3. It calls `tfe_text_view_open` to let the user select a file from GtkFileChooserDialog.
4. The dialog emits a signal and it invokes the handler `open_dialog_response`.
5. The handler read the file and set it into GtkTextBuffer and emits a signal to inform the response status.
6. The handler outside TfeTextView recieves the signal.
Signals are emitted on the object.
The type of the object is the second argument of `g_signal_newv`.
The relationship between the signal and object (type) is made up when the signal is generated.
## Get file function
`g_signal_emit` is used to emit the signal.
The following is extract from `tfetexties.c`.
`gtk_text_view_get_file` is a simple function show as follows.
g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS);
g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_CANCEL);
g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
1 GFile *
2 tfe_text_view_get_file (TfeTextView *tv) {
3 g_return_val_if_fail (TFE_IS_TEXT_VIEW (tv), NULL);
4
5 return g_file_dup (tv->file);
6 }
- The first argument is the object on which the signal is emitted.
- The second argument is the signal id.
- The third argument is the detail of the signal.
"change-file" signal and "open-response" signal doesn't have details and the argument is zero when no details.
- "change-file" signal doesn't have parameter, so no fourth parameter.
- "open-response" signal has one parameter.
The fourth parameter is the parameter.
The important thing is duplicate `tv->file`.
Otherwise, if the caller free the GFile object, `tv->file` is no more guaranteed to point the GFile.
## Source file of tfetextview.c
All the source files are listed in [Section 13](ch13.html).
Up: [Readme.md](Readme.md), Prev: [Section 9](sec9.md), Next: [Section 11](sec11.md)

487
sec11.md
View file

@ -1,205 +1,346 @@
Up: [Readme.md](Readme.md), Prev: [Section 10](sec10.md), Next: [Section 12](sec12.md)
# Functions with GtkNotebook
# Functions in TfeTextView
GtkNotebook is a very important object in the text file editor `tfe`.
It connects the application and TfeTextView objects.
`tfenotebook.h` and `tfenotebook.c` describe a set of functions related to GtkTextbook.
In this section I will explain each function in TfeTextView object.
1 void
2 notebook_page_save(GtkNotebook *nb);
### tfe.h and tfetextview.h
`tfe.h` is a top header file and it includes `gtk.h` and all the header files.
Every C source files, which are `tfeapplication.c`, `tfenotebook.c` and `tfetextview.c`, include `tfe.h` at the beginning of each file.
1 #include <gtk/gtk.h>
2
3 #include "tfetextview.h"
4 #include "tfenotebook.h"
`tfetextview.h` is a header file which describes the public functions in `tfetextview.c`.
1 #define TFE_TYPE_TEXT_VIEW tfe_text_view_get_type ()
2 G_DECLARE_FINAL_TYPE (TfeTextView, tfe_text_view, TFE, TEXT_VIEW, GtkTextView)
3
4 void
5 notebook_page_open (GtkNotebook *nb);
6
7 void
8 notebook_page_new_with_file (GtkNotebook *nb, GFile *file);
9
10 void
11 notebook_page_new (GtkNotebook *nb);
12
This header file shows the public functions in `tfenotebook.c`.
- `notebook_page_new` generates a new GtkNotebookPage and adds GtkScrolledWindow and TfeTextView under the page.
- `notebook_page_new_with_file` generates a new GtkNotebookPage and adds GtkScrolledWindow and TfeTextView under the page. `file` is set to the pointer to GFile in the TfeTextView object and the file is read and set into GtkTextBuffer.
- `notebook_page_open` lets the user select a file and sets it into GtkTextBuffer.
- `notebook_page_save` save the contents in GtkTextBuffer to a file, using the pointer `tv->file`.
You probably find that the functions above are higher level functions of `tfe_text_view_new`, `tfe_text_view_new_with_file`, `tef_text_view_open` and `tfe_text_view_save` respectively.
There are two layers.
One of them is `tfe_text_view ...`, which is the lower level layer.
The other is `note_book ...`, which is the higher level layer.
Now let's look at each program of the functions.
## notebook\_page\_new
1 static gchar*
2 get_untitled () {
3 static int c = -1;
4 if (++c == 0)
5 return g_strdup_printf("Untitled");
6 else
7 return g_strdup_printf ("Untitled%u", c);
8 }
9
10 static void
11 notebook_page_build (GtkNotebook *nb, GtkWidget *tv, char *filename) {
12 GtkWidget *scr;
13 GtkNotebookPage *nbp;
14 GtkWidget *lab;
15 gint i;
16 scr = gtk_scrolled_window_new ();
4 /* "open-response" signal response */
5 enum
6 {
7 TFE_OPEN_RESPONSE_SUCCESS,
8 TFE_OPEN_RESPONSE_CANCEL,
9 TFE_OPEN_RESPONSE_ERROR
10 };
11
12 GFile *
13 tfe_text_view_get_file (TfeTextView *tv);
14
15 void
16 tfe_text_view_open (TfeTextView *tv);
17
18 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
19 lab = gtk_label_new (filename);
20 i = gtk_notebook_append_page (nb, scr, lab);
21 nbp = gtk_notebook_get_page (nb, scr);
22 g_object_set (nbp, "tab-expand", TRUE, NULL);
23 gtk_notebook_set_current_page (nb, i);
24 g_signal_connect (GTK_TEXT_VIEW (tv), "change-file", G_CALLBACK (file_changed), nb);
25 }
18 void
19 tfe_text_view_save (TfeTextView *tv);
20
21 void
22 tfe_text_view_saveas (TfeTextView *tv);
23
24 GtkWidget *
25 tfe_text_view_new_with_file (GFile *file);
26
27 void
28 notebook_page_new (GtkNotebook *nb) {
29 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
30
31 GtkWidget *tv;
32 char *filename;
33
34 tv = tfe_text_view_new ();
35 filename = get_untitled ();
36 notebook_page_build (nb, tv, filename);
37 }
27 GtkWidget *
28 tfe_text_view_new (void);
29
- 27-37: `notebook_page_new` function.
- 29: `g_return_if_fail` is used because `notebook_page_new` is a public function.
- 34: Generate TfeTextView object.
- 35: Generate filename, which is "Untitled", "Untitled2", ... .
- 1-8: `get_untitled` function.
- 3: Static variable `c` is initialized at the first call of this function. After that `c` keeps its value except it is changed explicitly.
- 4-7: Increase `c` by one and if it is zero then the name is "Untitled". If it is a positive integer then the name is "Untitled<the integer>", for example, "Untitled1", "Untitled2", and so on.
It returns the name.
`g_strdup_printf` generates a string and it should be freed by `g_free` function.
The caller of `get_untitled` is in charge of freeing the memories of the string.
- 36: call `notebook_page_build` to build the contents of the page.
- 10- 25: `notebook_page_build` function.
- 17-18: Generate GtkScrolledWindow and set `tv` to its child.
- 19-20: Generate GtkLabel, then GtkNotebookPage.
- 21-22: Set "tab-expand" property to TRUE.
- 23: Set the page to the current page.
- 24: Connect "change-file" signal and `file_changed` handler.
- 1-2: These two lines are used to define TfeTextView.
- 4-10: Definitions of parameter used in the handler of "open-response" signal.
- 12-28: Public functions on GtkTextView.
## notebook\_page\_new\_with\_file
Each function will be explained later in this section.
1 void
2 notebook_page_new_with_file (GtkNotebook *nb, GFile *file) {
3 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
4 g_return_if_fail(G_IS_FILE (file));
5
6 GtkWidget *tv;
7 char *filename;
## Functions to generate TfeTextView object
TfeTextView Object is generated by `tfe_text_view_new` or `tfe_text_view_new_with_file`.
GtkWidget *tfe_text_view_new (void);
`tfe_text_view_new` just generates a new TfeTextView object and returns the pointer to the new object.
GtkWidget *tfe_text_view_new_with_file (GFile *file);
`tfe_text_view_new_with_file` is given a Gfile object as the argument and it loads the file into the GtkTextBuffer object, then returns the pointer to the new object.
Parameter:
- `file`: a pointer to the GFile object.
Return value:
- A pointer to the generated TfeTextView object but it is casted to a pointer to GtkWidget.
If an error occures during the genration process, NULL is returned.
Each function is defined as follows.
1 GtkWidget *
2 tfe_text_view_new_with_file (GFile *file) {
3 g_return_val_if_fail (G_IS_FILE (file), NULL);
4
5 GtkWidget *tv;
6 char *contents;
7 gsize length;
8
9 if ((tv = tfe_text_view_new_with_file (file)) == NULL)
10 return; /* read error */
11 filename = g_file_get_basename (file);
12 notebook_page_build (nb, tv, filename);
13 }
9 if (! g_file_load_contents (file, NULL, &contents, &length, NULL, NULL)) /* read error */
10 return NULL;
11
12 tv = tfe_text_view_new();
13 gtk_text_buffer_set_text (TFE_TEXT_VIEW (tv)->tb, contents, length);
14 g_free (contents);
15 TFE_TEXT_VIEW (tv)->file = g_file_dup (file);
16 return tv;
17 }
18
19 GtkWidget *
20 tfe_text_view_new (void) {
21 return gtk_widget_new (TFE_TYPE_TEXT_VIEW, NULL);
22 }
- 9-10: Call `tfe_text_view_new_with_file`.
If it returns NULL, then do nothing and return because of an error.
-11-13: Get the filename , build the contents of the page, then free `filename`.
- 18-21: `tfe_text_view_new`.
Just returns the value from the function `gtk_widget_new`.
Initialization is done in `tfe_text_view_init` which is called in the process of `gtk_widget_new` function.
- 1-16: `tfe_text_view_new_with_file`
- 3: `g_return_val_if_fail` is described in [Glib API reference](https://developer.gnome.org/glib/stable/glib-Warnings-and-Assertions.html#g-return-val-if-fail).
It tests whether the argument `file` is a pointer to GFile.
If it's true, then the program goes on to the next line.
If it's false, then it returns NULL (the second argument) immediately.
And at the same time it logs out the error message (usually the log is outputted to stderr or stdout).
This function is used to check the programmer's error.
If an error occurs, the solution is usually to change the (caller) program and fix the bug.
You need to distinguish programmer's errors and runtime errors.
You shouldn't use this function to find runtime errors.
- 9-10: If an error occurs when reading the file, then return NULL.
- 11-15: Generate TfeTextView and set the pointer to it to `tv`.
Set the contents read from the file to GtkTextBuffer `tv->tb`.
Free the memories pointed by `contents`.
Duplicate `file` and set it to `tv->file`.
Return `tv`.
## notebook\_page\_open
## Save and saveas functions
Save and saveas functions write the contents in GtkTextBuffer to a file.
void tfe_text_view_save (TfeTextView *tv)
`save` function writes the contents in GtkTextBuffer to a file specified by `tv->file`.
If `tv->file` is NULL, then it shows GtkFileChooserDialog and lets the user to give a file to the program. After that, it saves the contents to the specified file and set the file into `tv->file`.
void tfe_text_view_saveas (TfeTextView *tv)
`saveas` function uses GtkFileChooserDialog and lets the user to give a new file to the program. Then, the function changes `tv->file` and save the contents to the specified new file.
If an error occures, it is shown to the user through the message dialog.
The error is managed only in the object and no information is notified to the caller.
1 static void
2 open_response (TfeTextView *tv, gint response, GtkNotebook *nb) {
2 saveas_dialog_response (GtkWidget *dialog, gint response, TfeTextView *tv) {
3 GFile *file;
4 char *filename;
5
6 if (response != TFE_OPEN_RESPONSE_SUCCESS)
7 g_object_unref (tv);
8 else if (! G_IS_FILE (file = tfe_text_view_get_file (tv)))
9 g_object_unref (tv);
10 else {
11 filename = g_file_get_basename (file);
12 g_object_unref (file);
13 notebook_page_build (nb, GTK_WIDGET (tv), filename);
14 }
4
5 if (response == GTK_RESPONSE_ACCEPT) {
6 file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
7 if (G_IS_FILE(file)) {
8 tv->file = file;
9 tv->changed = TRUE;
10 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
11 tfe_text_view_save (TFE_TEXT_VIEW (tv));
12 }
13 }
14 gtk_window_destroy (GTK_WINDOW (dialog));
15 }
16
17 void
18 notebook_page_open (GtkNotebook *nb) {
19 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
18 tfe_text_view_save (TfeTextView *tv) {
19 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
20
21 GtkWidget *tv;
22
23 tv = tfe_text_view_new ();
24 g_signal_connect (TFE_TEXT_VIEW (tv), "open-response", G_CALLBACK (open_response), nb);
25 tfe_text_view_open (TFE_TEXT_VIEW (tv));
26 }
21 GtkTextIter start_iter;
22 GtkTextIter end_iter;
23 gchar *contents;
24 GtkWidget *message_dialog;
25 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
26 GError *err = NULL;
27
28 if (! tv->changed)
29 return; /* no necessary to save it */
30 else if (tv->file == NULL)
31 tfe_text_view_saveas (tv);
32 else {
33 gtk_text_buffer_get_bounds (tv->tb, &start_iter, &end_iter);
34 contents = gtk_text_buffer_get_text (tv->tb, &start_iter, &end_iter, FALSE);
35 if (g_file_replace_contents (tv->file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, &err))
36 tv->changed = FALSE;
37 else {
38 /* It is possible that tv->file is broken. */
39 /* It is a good idea to set tv->file to NULL. */
40 if (G_IS_FILE (tv->file))
41 g_object_unref (tv->file);
42 tv->file =NULL;
43 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
44 tv->changed = TRUE;
45 message_dialog = gtk_message_dialog_new (GTK_WINDOW (win), GTK_DIALOG_MODAL,
46 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
47 "%s.\n", err->message);
48 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
49 gtk_widget_show (message_dialog);
50 g_error_free (err);
51 }
52 }
53 }
54
55 void
56 tfe_text_view_saveas (TfeTextView *tv) {
57 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
58
59 GtkWidget *dialog;
60 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
61
62 dialog = gtk_file_chooser_dialog_new ("Save file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_SAVE,
63 "_Cancel", GTK_RESPONSE_CANCEL,
64 "_Save", GTK_RESPONSE_ACCEPT,
65 NULL);
66 g_signal_connect (dialog, "response", G_CALLBACK (saveas_dialog_response), tv);
67 gtk_widget_show (dialog);
68 }
- 18-27: `notebook_page_open` function.
- 24: Generate TfeTextView object.
- 25: Connect the signal "open-response" and the handler `open_response`.
- 26: Call `tfe_text_view_open`.
It emits "open-response" signal to inform the status after the series of functions run.
- 1-16: `open_response` handler.
This is the postfunction of `notebook_page_open`.
- 6-7: It the status is NOT `TFE_OPEN_RESPONSE_SUCCESS`, cancel what we did in `notebook_page_open`.
Unref `tv`.
- 8-9: If `tfe_text_view_get_file` returns a pointer not to point GFile, then something bad happens. Cancel what we did. Unref `tv`.
- 10-14: Otherwise, everything was okay.
Get the filename, build the contents of the page, free `filename` and unref `tv`
- 17-53: `Tfe_text_view_save` function.
- 19: If `tv` is not a pointer to TfeTextView, then it logs an error message and immediately returns.
This function is similar to `g_return_val_if_fail` function, but no value is returned because `tfe_text_view_save` doesn't return a value.
- 28-29: If the buffer hasn't modified, then it doesn't need to save it.
So the function returns.
- 30-31: If `tv->file` is NULL, no file has given yet.
It calls `tfe_text_view_save`, which lets the user to choose a file to save.
- 33-35: Save the buffer to the file.
If it succeeds, assigns FALSE to `tv->changed`.
- 38-50: If file writing fails, it assigns NULL to `tv->file`.
Emits "change-file" signal.
Shows the error message dialog (45-49).
Because the handler is `gtk_window_destroy`, the dialog disappears when user clicks on the button in the dialog.
- 55-68: `tfe_text_view_saveas` function.
It shows GtkFileChooserDialog and lets the user choose a file and give it to the signal handler.
- 62: Generate GtkFileChooserDialog.
The title is "Save file".
Transient parent of the dialog is `win`, which is the top level window.
The action is save mode.
The buttons are Cancel and Save.
- 63: connect the "response" signal of the dialog and `saveas_dialog_response` handler.
- 1-15: `saveas_dialog_response` signal handler.
- 5-13: If the response is `GTK_RESPONSE_ACCEPT`, which is set to the argument when the user has clicked on Save button, then gets a pointer to the GFile object, set it to `tv->file`, assign TRUE to `tv->changed`, emits "change-file" signal then call `tfe_text_view_save` to save the buffer to the file.
## notebook\_page\_save
![Saveas process](image/saveas.png)
1 void
2 notebook_page_save(GtkNotebook *nb) {
3 gint i;
4 GtkWidget *scr;
5 GtkWidget *tv;
6
7 i = gtk_notebook_get_current_page (nb);
8 scr = gtk_notebook_get_nth_page (nb, i);
9 tv = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (scr));
10 tfe_text_view_save (TFE_TEXT_VIEW (tv));
11 }
When you use GtkFileChooserDialog, you need to divide the program into two parts.
They are a function which generates GtkFileChooserDialog and the signal handler.
The function just generates and shows the dialog.
The rest is done by the handler.
It gets Gfile from GtkFileChooserDialog, save the buffer to the file and do some things necessary.
- 7-9: Get TfeTextView belongs to the current notebook page.
- 10: Call `tfe_text_view_save`.
## Open function
## file\_changed handler
Open function shows GtkFileChooserDialog to the user and let him/her choose a file.
Then read the file and set it to GtkTextBuffer.
`file_changed` is a handler connected to "change-file" signal.
If `tv->file` is changed, TfeTextView emits this signal.
This handler changes the label of GtkNotebookPage.
void tfe_text_view_open (TfeTextView *tv)
TfeTextView object `tv` has to be generated in advance.
And it should be empty and `tv->file` is NULL.
If it is not empty, `tfe_text_view_open` doesn't treat it as an error.
If you want to revert the buffer, calling this function is apropreate.
Otherwise probably bad things will happen.
1 static void
2 file_changed (TfeTextView *tv, GtkNotebook *nb) {
2 open_dialog_response(GtkWidget *dialog, gint response, TfeTextView *tv) {
3 GFile *file;
4 char *filename;
5 GtkWidget *scr;
6 GtkWidget *label;
7
8 file = tfe_text_view_get_file (tv);
9 scr = gtk_widget_get_parent (GTK_WIDGET (tv));
10 if (G_IS_FILE (file))
11 filename = g_file_get_basename (file);
12 else
13 filename = get_untitled ();
14 label = gtk_label_new (filename);
15 gtk_notebook_set_tab_label (nb, scr, label);
16 g_object_unref (file);
17 g_free (filename);
18 }
4 char *contents;
5 gsize length;
6 GtkWidget *message_dialog;
7 GError *err = NULL;
8
9 if (response != GTK_RESPONSE_ACCEPT)
10 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_CANCEL);
11 else if (! G_IS_FILE (file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog))))
12 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
13 else if (! g_file_load_contents (file, NULL, &contents, &length, NULL, &err)) { /* read error */
14 if (G_IS_FILE (file))
15 g_object_unref (file);
16 message_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL,
17 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
18 "%s.\n", err->message);
19 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
20 gtk_widget_show (message_dialog);
21 g_error_free (err);
22 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
23 } else {
24 gtk_text_buffer_set_text (tv->tb, contents, length);
25 g_free (contents);
26 tv->file = file;
27 /* tv->changed = FALSE;*/
28 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS);
29 }
30 gtk_window_destroy (GTK_WINDOW (dialog));
31 }
32
33 void
34 tfe_text_view_open (TfeTextView *tv) {
35 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
36
37 GtkWidget *dialog;
38
39 dialog = gtk_file_chooser_dialog_new ("Open file", NULL, GTK_FILE_CHOOSER_ACTION_OPEN,
40 "Cancel", GTK_RESPONSE_CANCEL,
41 "Open", GTK_RESPONSE_ACCEPT,
42 NULL);
43 g_signal_connect (dialog, "response", G_CALLBACK (open_dialog_response), tv);
44 gtk_widget_show (dialog);
45 }
- 8: Get GFile from TfeTextView.
- 9: Get the parent (GkScrolledWindow) of `tv`.
- 10-13: If `file` points GFile, then assign the filename of the GFile into `filename`.
Otherwise (this is the case file is NULL), assign untitled string to `filename`.
- 14-15: Generate a label with the filename and set it into GtkNotebookPage.
- 16-17: Free `filename and unref `file`.
- 33-45: `tfe_text_view_open` function.
- 39: Generate GtkFileChooserDialog.
The title is "Open file".
No transient parent window.
The action is open mode.
The buttons are Cancel and Open.
- 43: connect the "reponse" signal of the dialog and `open_dialog_response` signal handler.
- 44: Show the dialog.
- 1-31: `open_dialog_response` signal handler.
- 9-10: If the response from GtkFileChooserDialog is not `GTK_RESPONSE_ACCEPT`, which means the user has clicked on the "Cancel" button or close button, then it emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_CANCEL`.
- 11-12: Get a pointer to Gfile by `gtk_file_chooser_get_file`.
If it is not GFile, maybe an error occured.
Then it emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_ERROR`.
- 13-22: If an error occurs when it has read the file, then it decreases the reference count of Gfile, shows a message dialog to report the error to the user and emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_ERROR`.
- 24-28: If the file has successfully read, then the text is set to GtkTextBuffer, free the temporary buffer pointed by `contents`, set file to `tv->file` (no duplication or unref is not necessary) and emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_SUCCESS`.
- 30: close GtkFileCooserDialog.
Now let's think about the whole process between the other object (caller) and TfeTextView.
It is shown in the following diagram and you would think that it is really complicated.
Because signal is the only way for GtkFileChooserDialog to communicate with others.
In Gtk3, `gtk_dialog_run` function is available.
It simplifies the process.
However, in Gtk4, `gtk_dialog_run`is unavailable any more.
![Caller and TfeTextView](image/open.png)
1. A caller get a pointer `tv` to TfeTextView by calling `tfe_text_view_new`.
2. The caller connects the handler (left bottom in the diagram) and the signal "open-response".
3. It calls `tfe_text_view_open` to let the user select a file from GtkFileChooserDialog.
4. The dialog emits a signal and it invokes the handler `open_dialog_response`.
5. The handler read the file and set it into GtkTextBuffer and emits a signal to inform the response status.
6. The handler outside TfeTextView recieves the signal.
## Get file function
`gtk_text_view_get_file` is a simple function show as follows.
1 GFile *
2 tfe_text_view_get_file (TfeTextView *tv) {
3 g_return_val_if_fail (TFE_IS_TEXT_VIEW (tv), NULL);
4
5 return g_file_dup (tv->file);
6 }
The important thing is duplicate `tv->file`.
Otherwise, if the caller free the GFile object, `tv->file` is no more guaranteed to point the GFile.
## Source file of tfetextview.c
All the source files are listed in [Section 13](ch13.html).
Up: [Readme.md](Readme.md), Prev: [Section 10](sec10.md), Next: [Section 12](sec12.md)

461
sec12.md
View file

@ -1,270 +1,205 @@
Up: [Readme.md](Readme.md), Prev: [Section 11](sec11.md), Next: [Section 13](sec13.md)
# tfeapplication.c
# Functions with GtkNotebook
`tfeapplication.c` includes all the code other than `tfetxtview.c` and `tfenotebook.c`.
It does following things.
GtkNotebook is a very important object in the text file editor `tfe`.
It connects the application and TfeTextView objects.
`tfenotebook.h` and `tfenotebook.c` describe a set of functions related to GtkTextbook.
- Application support, mainly handling command line arguments.
- Build widgets using ui file.
- Connect button signals and their handlers.
- Manage CSS.
## main
Th function `main` is the first invoked function in C language.
It connects the command line given by the user and GTK application.
1 int
2 main (int argc, char **argv) {
3 GtkApplication *app;
4 int stat;
5
6 app = gtk_application_new ("com.github.ToshioCP.tfe", G_APPLICATION_HANDLES_OPEN);
7
8 g_signal_connect (app, "startup", G_CALLBACK (tfe_startup), NULL);
9 g_signal_connect (app, "activate", G_CALLBACK (tfe_activate), NULL);
10 g_signal_connect (app, "open", G_CALLBACK (tfe_open), NULL);
11
12 stat =g_application_run (G_APPLICATION (app), argc, argv);
13 g_object_unref (app);
14 return stat;
15 }
- 6: Generate GtkApplication object.
- 8-10: Connect "startup", "activate" and "open signals to their handlers.
- 12: Run the application.
- 13-14: release the reference to the application and return the status.
## statup signal handler
"startup" signal is emitted just after the application is generated.
What the signal handler needs to do is initialization of the application.
- Build the widgets using ui file.
- Connect button signals and their handlers.
- Set CSS.
The handler is as follows.
1 static void
2 tfe_startup (GApplication *application) {
3 GtkApplication *app = GTK_APPLICATION (application);
4 GtkApplicationWindow *win;
5 GtkNotebook *nb;
6 GtkBuilder *build;
7 GtkButton *btno;
8 GtkButton *btnn;
9 GtkButton *btns;
10 GtkButton *btnc;
11
12 build = gtk_builder_new_from_resource ("/com/github/ToshioCP/tfe/tfe.ui");
13 win = GTK_APPLICATION_WINDOW (gtk_builder_get_object (build, "win"));
14 nb = GTK_NOTEBOOK (gtk_builder_get_object (build, "nb"));
15 gtk_window_set_application (GTK_WINDOW (win), app);
16 btno = GTK_BUTTON (gtk_builder_get_object (build, "btno"));
17 btnn = GTK_BUTTON (gtk_builder_get_object (build, "btnn"));
18 btns = GTK_BUTTON (gtk_builder_get_object (build, "btns"));
19 btnc = GTK_BUTTON (gtk_builder_get_object (build, "btnc"));
20 g_signal_connect (btno, "clicked", G_CALLBACK (open_clicked), nb);
21 g_signal_connect (btnn, "clicked", G_CALLBACK (new_clicked), nb);
22 g_signal_connect (btns, "clicked", G_CALLBACK (save_clicked), nb);
23 g_signal_connect (btnc, "clicked", G_CALLBACK (close_clicked), nb);
24 g_object_unref(build);
25
26 GdkDisplay *display;
27
28 display = gtk_widget_get_display (GTK_WIDGET (win));
29 GtkCssProvider *provider = gtk_css_provider_new ();
30 gtk_css_provider_load_from_data (provider, "textview {padding: 10px; font-family: monospace; font-size: 12pt;}", -1);
31 gtk_style_context_add_provider_for_display (display, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_USER);
32 }
- 12-15: Build widgets using ui file (resource).
Connect the top window and the application using `gtk_window_set_application`.
- 16-23: Get buttons and connect their signals and handlers.
- 24: Release the reference to GtkBuilder.
- 26-31: Set CSS.
CSS in GTK is similar to CSS in HTML.
You can set margin, border, padding, color, font and so on with CSS.
In this program CSS is in line 30.
It sets padding, font-family and font size of GtkTextView.
- 26-28: GdkDisplay is used to set CSS.
CSS will be explained in the next subsection.
## CSS in GTK
CSS is an abbretiation of Cascading Style Sheet.
It is originally used with HTML to describe the presentation semantics of a document.
You might have found that the widgets in GTK is simialr to the window in a browser.
It implies that CSS can also be apllied to GTK windowing system.
### CSS nodes, selectors
The syntax of CSS is as follws.
selector { color: yellow; padding-top: 10px; ...}
Every widget has CSS node.
For example GtkTextView has `textview` node.
If you want to set style to GtkTextView, set "textview" to the selector.
textview {color: yeallow; ...}
Class, ID and some other things can be applied to the selector like Web CSS. Refer GTK4 API reference for further information.
In line 30, the CSS is a string.
textview {padding: 10px; font-family: monospace; font-size: 12pt;}
- padding is a space between the border and contents.
This space makes the text easier to read.
- font-family is a name of font.
"monospace" is one of the generic family font keywords.
- font-size is set to 12pt.
It is a bit large, but easy on the eyes especially for elderly people.
### GtkStyleContext, GtkCSSProvider and GdkDisplay
GtkStyleContext is an object that stores styling information affecting a widget.
Each widget is connected to the corresponding GtkStyleContext.
You can get the context by `gtk_widget_get_style_context`.
GtkCssProvider is an object which parses CSS in order to style widgets.
To apply your CSS to wodgets, you need to add GtkStyleProvider (the interface of GtkCSSProvider) to GtkStyleContext.
However, instead, you can add it to GdkDisplay of the window (usually top level window).
Look at the source file of `startup` handler again.
- 28: The display is obtained by `gtk_widget_get_display`.
- 29: Generate GtkCssProvider.
- 30: Set the CSS into the provider.
- 31: Add the provider to the display.
It is possible to add the provider to the context of GtkTextView instead of GdkDiplay.
To do so, rewrite `tfe_text_view_new`.
GtkWidget *
tfe_text_view_new (void) {
GtkWidget *tv;
tv = gtk_widget_new (TFE_TYPE_TEXT_VIEW, NULL);
GtkStyleContext *context;
context = gtk_widget_get_style_context (GTK_WIDGET (tv));
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 (context, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_USER);
return tv;
}
CSS set to the context takes precedence over the one set to the display.
## activate and open handler
The handler of "activate" and "open" signal are `tfe_activate` and `tfe_open` respectively.
They just generate a new GtkNotebookPage.
1 static void
2 tfe_activate (GApplication *application) {
3 GtkApplication *app = GTK_APPLICATION (application);
4 GtkWidget *win;
5 GtkWidget *boxv;
6 GtkNotebook *nb;
7
8 win = GTK_WIDGET (gtk_application_get_active_window (app));
9 boxv = gtk_window_get_child (GTK_WINDOW (win));
10 nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv));
11
12 notebook_page_new (nb);
13 gtk_widget_show (GTK_WIDGET (win));
14 }
15
16 static void
17 tfe_open (GApplication *application, GFile ** files, gint n_files, const gchar *hint) {
18 GtkApplication *app = GTK_APPLICATION (application);
19 GtkWidget *win;
20 GtkWidget *boxv;
21 GtkNotebook *nb;
22 int i;
23
24 win = GTK_WIDGET (gtk_application_get_active_window (app));
25 boxv = gtk_window_get_child (GTK_WINDOW (win));
26 nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv));
27
28 for (i = 0; i < n_files; i++)
29 notebook_page_new_with_file (nb, files[i]);
30 if (gtk_notebook_get_n_pages (nb) == 0)
31 notebook_page_new (nb);
32 gtk_widget_show (win);
33 }
- 1-14: `tfe_activate`.
- 8-10: Get GtkNotebook object.
- 12-13: Generate a new GtkNotebookPage and show the window.
- 16-33: `tfe_open`.
- 24-26: Get GtkNotebook object.
- 28-29: Generate GtkNotebookPage with files.
- 30-31: If opening and reading file failed and no GtkNotebookPage has generated, then generate a empty page.
- 32: Show the window.
These codes have become really simple thanks to tfenotebook.c and tfetextview.c.
## a series of handlers correspond to the button signals
1 static void
2 open_clicked (GtkWidget *btno, GtkNotebook *nb) {
3 notebook_page_open (nb);
4 }
5
6 static void
7 new_clicked (GtkWidget *btnn, GtkNotebook *nb) {
8 notebook_page_new (nb);
9 }
10
11 static void
12 save_clicked (GtkWidget *btns, GtkNotebook *nb) {
13 notebook_page_save (nb);
14 }
15
16 static void
17 close_clicked (GtkWidget *btnc, GtkNotebook *nb) {
18 GtkWidget *win;
19 GtkWidget *boxv;
20 gint i;
21
22 if (gtk_notebook_get_n_pages (nb) == 1) {
23 boxv = gtk_widget_get_parent (GTK_WIDGET (nb));
24 win = gtk_widget_get_parent (boxv);
25 gtk_window_destroy (GTK_WINDOW (win));
26 } else {
27 i = gtk_notebook_get_current_page (nb);
28 gtk_notebook_remove_page (GTK_NOTEBOOK (nb), i);
29 }
30 }
`open_clicked`, `new_clicked` and `save_clicked` just call corresponding notebook page functions.
`close_clicked` is a bit complicated.
- 22-25: If there's only one page, closing the last page is considered that it also close the top level window and quit the application.
Therefore, it gets the top level window and call `gtk_window_destroy`.
- 26-28: Otherwise, it removes the current page.
## meson.build
1 project('tfe', 'c')
2
3 gtkdep = dependency('gtk4')
4
5 gnome=import('gnome')
6 resources = gnome.compile_resources('resources','tfe.gresource.xml')
7
8 sourcefiles=files('tfeapplication.c', 'tfenotebook.c', 'tfetextview.c')
1 void
2 notebook_page_save(GtkNotebook *nb);
3
4 void
5 notebook_page_open (GtkNotebook *nb);
6
7 void
8 notebook_page_new_with_file (GtkNotebook *nb, GFile *file);
9
10 executable('tfe', sourcefiles, resources, dependencies: gtkdep)
10 void
11 notebook_page_new (GtkNotebook *nb);
12
This header file shows the public functions in `tfenotebook.c`.
- `notebook_page_new` generates a new GtkNotebookPage and adds GtkScrolledWindow and TfeTextView under the page.
- `notebook_page_new_with_file` generates a new GtkNotebookPage and adds GtkScrolledWindow and TfeTextView under the page. `file` is set to the pointer to GFile in the TfeTextView object and the file is read and set into GtkTextBuffer.
- `notebook_page_open` lets the user select a file and sets it into GtkTextBuffer.
- `notebook_page_save` save the contents in GtkTextBuffer to a file, using the pointer `tv->file`.
You probably find that the functions above are higher level functions of `tfe_text_view_new`, `tfe_text_view_new_with_file`, `tef_text_view_open` and `tfe_text_view_save` respectively.
There are two layers.
One of them is `tfe_text_view ...`, which is the lower level layer.
The other is `note_book ...`, which is the higher level layer.
Now let's look at each program of the functions.
## notebook\_page\_new
1 static gchar*
2 get_untitled () {
3 static int c = -1;
4 if (++c == 0)
5 return g_strdup_printf("Untitled");
6 else
7 return g_strdup_printf ("Untitled%u", c);
8 }
9
10 static void
11 notebook_page_build (GtkNotebook *nb, GtkWidget *tv, char *filename) {
12 GtkWidget *scr;
13 GtkNotebookPage *nbp;
14 GtkWidget *lab;
15 gint i;
16 scr = gtk_scrolled_window_new ();
17
18 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
19 lab = gtk_label_new (filename);
20 i = gtk_notebook_append_page (nb, scr, lab);
21 nbp = gtk_notebook_get_page (nb, scr);
22 g_object_set (nbp, "tab-expand", TRUE, NULL);
23 gtk_notebook_set_current_page (nb, i);
24 g_signal_connect (GTK_TEXT_VIEW (tv), "change-file", G_CALLBACK (file_changed), nb);
25 }
26
27 void
28 notebook_page_new (GtkNotebook *nb) {
29 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
30
31 GtkWidget *tv;
32 char *filename;
33
34 tv = tfe_text_view_new ();
35 filename = get_untitled ();
36 notebook_page_build (nb, tv, filename);
37 }
- 27-37: `notebook_page_new` function.
- 29: `g_return_if_fail` is used because `notebook_page_new` is a public function.
- 34: Generate TfeTextView object.
- 35: Generate filename, which is "Untitled", "Untitled2", ... .
- 1-8: `get_untitled` function.
- 3: Static variable `c` is initialized at the first call of this function. After that `c` keeps its value except it is changed explicitly.
- 4-7: Increase `c` by one and if it is zero then the name is "Untitled". If it is a positive integer then the name is "Untitled<the integer>", for example, "Untitled1", "Untitled2", and so on.
It returns the name.
`g_strdup_printf` generates a string and it should be freed by `g_free` function.
The caller of `get_untitled` is in charge of freeing the memories of the string.
- 36: call `notebook_page_build` to build the contents of the page.
- 10- 25: `notebook_page_build` function.
- 17-18: Generate GtkScrolledWindow and set `tv` to its child.
- 19-20: Generate GtkLabel, then GtkNotebookPage.
- 21-22: Set "tab-expand" property to TRUE.
- 23: Set the page to the current page.
- 24: Connect "change-file" signal and `file_changed` handler.
## notebook\_page\_new\_with\_file
1 void
2 notebook_page_new_with_file (GtkNotebook *nb, GFile *file) {
3 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
4 g_return_if_fail(G_IS_FILE (file));
5
6 GtkWidget *tv;
7 char *filename;
8
9 if ((tv = tfe_text_view_new_with_file (file)) == NULL)
10 return; /* read error */
11 filename = g_file_get_basename (file);
12 notebook_page_build (nb, tv, filename);
13 }
- 9-10: Call `tfe_text_view_new_with_file`.
If it returns NULL, then do nothing and return because of an error.
-11-13: Get the filename , build the contents of the page, then free `filename`.
## notebook\_page\_open
1 static void
2 open_response (TfeTextView *tv, gint response, GtkNotebook *nb) {
3 GFile *file;
4 char *filename;
5
6 if (response != TFE_OPEN_RESPONSE_SUCCESS)
7 g_object_unref (tv);
8 else if (! G_IS_FILE (file = tfe_text_view_get_file (tv)))
9 g_object_unref (tv);
10 else {
11 filename = g_file_get_basename (file);
12 g_object_unref (file);
13 notebook_page_build (nb, GTK_WIDGET (tv), filename);
14 }
15 }
16
17 void
18 notebook_page_open (GtkNotebook *nb) {
19 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
20
21 GtkWidget *tv;
22
23 tv = tfe_text_view_new ();
24 g_signal_connect (TFE_TEXT_VIEW (tv), "open-response", G_CALLBACK (open_response), nb);
25 tfe_text_view_open (TFE_TEXT_VIEW (tv));
26 }
- 18-27: `notebook_page_open` function.
- 24: Generate TfeTextView object.
- 25: Connect the signal "open-response" and the handler `open_response`.
- 26: Call `tfe_text_view_open`.
It emits "open-response" signal to inform the status after the series of functions run.
- 1-16: `open_response` handler.
This is the postfunction of `notebook_page_open`.
- 6-7: It the status is NOT `TFE_OPEN_RESPONSE_SUCCESS`, cancel what we did in `notebook_page_open`.
Unref `tv`.
- 8-9: If `tfe_text_view_get_file` returns a pointer not to point GFile, then something bad happens. Cancel what we did. Unref `tv`.
- 10-14: Otherwise, everything was okay.
Get the filename, build the contents of the page, free `filename` and unref `tv`
## notebook\_page\_save
1 void
2 notebook_page_save(GtkNotebook *nb) {
3 gint i;
4 GtkWidget *scr;
5 GtkWidget *tv;
6
7 i = gtk_notebook_get_current_page (nb);
8 scr = gtk_notebook_get_nth_page (nb, i);
9 tv = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (scr));
10 tfe_text_view_save (TFE_TEXT_VIEW (tv));
11 }
- 7-9: Get TfeTextView belongs to the current notebook page.
- 10: Call `tfe_text_view_save`.
## file\_changed handler
`file_changed` is a handler connected to "change-file" signal.
If `tv->file` is changed, TfeTextView emits this signal.
This handler changes the label of GtkNotebookPage.
1 static void
2 file_changed (TfeTextView *tv, GtkNotebook *nb) {
3 GFile *file;
4 char *filename;
5 GtkWidget *scr;
6 GtkWidget *label;
7
8 file = tfe_text_view_get_file (tv);
9 scr = gtk_widget_get_parent (GTK_WIDGET (tv));
10 if (G_IS_FILE (file))
11 filename = g_file_get_basename (file);
12 else
13 filename = get_untitled ();
14 label = gtk_label_new (filename);
15 gtk_notebook_set_tab_label (nb, scr, label);
16 g_object_unref (file);
17 g_free (filename);
18 }
- 8: Get GFile from TfeTextView.
- 9: Get the parent (GkScrolledWindow) of `tv`.
- 10-13: If `file` points GFile, then assign the filename of the GFile into `filename`.
Otherwise (this is the case file is NULL), assign untitled string to `filename`.
- 14-15: Generate a label with the filename and set it into GtkNotebookPage.
- 16-17: Free `filename and unref `file`.
This file is just modified the source file names.
Up: [Readme.md](Readme.md), Prev: [Section 11](sec11.md), Next: [Section 13](sec13.md)

856
sec13.md
View file

@ -1,9 +1,258 @@
Up: [Readme.md](Readme.md), Prev: [Section 12](sec12.md), Next: [Section 14](sec14.md)
# tfe5 source files
# tfeapplication.c
The followings are the source files of tfe5.
`tfeapplication.c` includes all the code other than `tfetxtview.c` and `tfenotebook.c`.
It does following things.
## meson.buld
- Application support, mainly handling command line arguments.
- Build widgets using ui file.
- Connect button signals and their handlers.
- Manage CSS.
## main
Th function `main` is the first invoked function in C language.
It connects the command line given by the user and GTK application.
1 int
2 main (int argc, char **argv) {
3 GtkApplication *app;
4 int stat;
5
6 app = gtk_application_new ("com.github.ToshioCP.tfe", G_APPLICATION_HANDLES_OPEN);
7
8 g_signal_connect (app, "startup", G_CALLBACK (tfe_startup), NULL);
9 g_signal_connect (app, "activate", G_CALLBACK (tfe_activate), NULL);
10 g_signal_connect (app, "open", G_CALLBACK (tfe_open), NULL);
11
12 stat =g_application_run (G_APPLICATION (app), argc, argv);
13 g_object_unref (app);
14 return stat;
15 }
- 6: Generate GtkApplication object.
- 8-10: Connect "startup", "activate" and "open signals to their handlers.
- 12: Run the application.
- 13-14: release the reference to the application and return the status.
## statup signal handler
"startup" signal is emitted just after the application is generated.
What the signal handler needs to do is initialization of the application.
- Build the widgets using ui file.
- Connect button signals and their handlers.
- Set CSS.
The handler is as follows.
1 static void
2 tfe_startup (GApplication *application) {
3 GtkApplication *app = GTK_APPLICATION (application);
4 GtkApplicationWindow *win;
5 GtkNotebook *nb;
6 GtkBuilder *build;
7 GtkButton *btno;
8 GtkButton *btnn;
9 GtkButton *btns;
10 GtkButton *btnc;
11
12 build = gtk_builder_new_from_resource ("/com/github/ToshioCP/tfe/tfe.ui");
13 win = GTK_APPLICATION_WINDOW (gtk_builder_get_object (build, "win"));
14 nb = GTK_NOTEBOOK (gtk_builder_get_object (build, "nb"));
15 gtk_window_set_application (GTK_WINDOW (win), app);
16 btno = GTK_BUTTON (gtk_builder_get_object (build, "btno"));
17 btnn = GTK_BUTTON (gtk_builder_get_object (build, "btnn"));
18 btns = GTK_BUTTON (gtk_builder_get_object (build, "btns"));
19 btnc = GTK_BUTTON (gtk_builder_get_object (build, "btnc"));
20 g_signal_connect (btno, "clicked", G_CALLBACK (open_clicked), nb);
21 g_signal_connect (btnn, "clicked", G_CALLBACK (new_clicked), nb);
22 g_signal_connect (btns, "clicked", G_CALLBACK (save_clicked), nb);
23 g_signal_connect (btnc, "clicked", G_CALLBACK (close_clicked), nb);
24 g_object_unref(build);
25
26 GdkDisplay *display;
27
28 display = gtk_widget_get_display (GTK_WIDGET (win));
29 GtkCssProvider *provider = gtk_css_provider_new ();
30 gtk_css_provider_load_from_data (provider, "textview {padding: 10px; font-family: monospace; font-size: 12pt;}", -1);
31 gtk_style_context_add_provider_for_display (display, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_USER);
32 }
- 12-15: Build widgets using ui file (resource).
Connect the top window and the application using `gtk_window_set_application`.
- 16-23: Get buttons and connect their signals and handlers.
- 24: Release the reference to GtkBuilder.
- 26-31: Set CSS.
CSS in GTK is similar to CSS in HTML.
You can set margin, border, padding, color, font and so on with CSS.
In this program CSS is in line 30.
It sets padding, font-family and font size of GtkTextView.
- 26-28: GdkDisplay is used to set CSS.
CSS will be explained in the next subsection.
## CSS in GTK
CSS is an abbretiation of Cascading Style Sheet.
It is originally used with HTML to describe the presentation semantics of a document.
You might have found that the widgets in GTK is simialr to the window in a browser.
It implies that CSS can also be apllied to GTK windowing system.
### CSS nodes, selectors
The syntax of CSS is as follws.
selector { color: yellow; padding-top: 10px; ...}
Every widget has CSS node.
For example GtkTextView has `textview` node.
If you want to set style to GtkTextView, set "textview" to the selector.
textview {color: yeallow; ...}
Class, ID and some other things can be applied to the selector like Web CSS. Refer GTK4 API reference for further information.
In line 30, the CSS is a string.
textview {padding: 10px; font-family: monospace; font-size: 12pt;}
- padding is a space between the border and contents.
This space makes the text easier to read.
- font-family is a name of font.
"monospace" is one of the generic family font keywords.
- font-size is set to 12pt.
It is a bit large, but easy on the eyes especially for elderly people.
### GtkStyleContext, GtkCSSProvider and GdkDisplay
GtkStyleContext is an object that stores styling information affecting a widget.
Each widget is connected to the corresponding GtkStyleContext.
You can get the context by `gtk_widget_get_style_context`.
GtkCssProvider is an object which parses CSS in order to style widgets.
To apply your CSS to wodgets, you need to add GtkStyleProvider (the interface of GtkCSSProvider) to GtkStyleContext.
However, instead, you can add it to GdkDisplay of the window (usually top level window).
Look at the source file of `startup` handler again.
- 28: The display is obtained by `gtk_widget_get_display`.
- 29: Generate GtkCssProvider.
- 30: Set the CSS into the provider.
- 31: Add the provider to the display.
It is possible to add the provider to the context of GtkTextView instead of GdkDiplay.
To do so, rewrite `tfe_text_view_new`.
GtkWidget *
tfe_text_view_new (void) {
GtkWidget *tv;
tv = gtk_widget_new (TFE_TYPE_TEXT_VIEW, NULL);
GtkStyleContext *context;
context = gtk_widget_get_style_context (GTK_WIDGET (tv));
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 (context, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_USER);
return tv;
}
CSS set to the context takes precedence over the one set to the display.
## activate and open handler
The handler of "activate" and "open" signal are `tfe_activate` and `tfe_open` respectively.
They just generate a new GtkNotebookPage.
1 static void
2 tfe_activate (GApplication *application) {
3 GtkApplication *app = GTK_APPLICATION (application);
4 GtkWidget *win;
5 GtkWidget *boxv;
6 GtkNotebook *nb;
7
8 win = GTK_WIDGET (gtk_application_get_active_window (app));
9 boxv = gtk_window_get_child (GTK_WINDOW (win));
10 nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv));
11
12 notebook_page_new (nb);
13 gtk_widget_show (GTK_WIDGET (win));
14 }
15
16 static void
17 tfe_open (GApplication *application, GFile ** files, gint n_files, const gchar *hint) {
18 GtkApplication *app = GTK_APPLICATION (application);
19 GtkWidget *win;
20 GtkWidget *boxv;
21 GtkNotebook *nb;
22 int i;
23
24 win = GTK_WIDGET (gtk_application_get_active_window (app));
25 boxv = gtk_window_get_child (GTK_WINDOW (win));
26 nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv));
27
28 for (i = 0; i < n_files; i++)
29 notebook_page_new_with_file (nb, files[i]);
30 if (gtk_notebook_get_n_pages (nb) == 0)
31 notebook_page_new (nb);
32 gtk_widget_show (win);
33 }
- 1-14: `tfe_activate`.
- 8-10: Get GtkNotebook object.
- 12-13: Generate a new GtkNotebookPage and show the window.
- 16-33: `tfe_open`.
- 24-26: Get GtkNotebook object.
- 28-29: Generate GtkNotebookPage with files.
- 30-31: If opening and reading file failed and no GtkNotebookPage has generated, then generate a empty page.
- 32: Show the window.
These codes have become really simple thanks to tfenotebook.c and tfetextview.c.
## a series of handlers correspond to the button signals
1 static void
2 open_clicked (GtkWidget *btno, GtkNotebook *nb) {
3 notebook_page_open (nb);
4 }
5
6 static void
7 new_clicked (GtkWidget *btnn, GtkNotebook *nb) {
8 notebook_page_new (nb);
9 }
10
11 static void
12 save_clicked (GtkWidget *btns, GtkNotebook *nb) {
13 notebook_page_save (nb);
14 }
15
16 static void
17 close_clicked (GtkWidget *btnc, GtkNotebook *nb) {
18 GtkWidget *win;
19 GtkWidget *boxv;
20 gint i;
21
22 if (gtk_notebook_get_n_pages (nb) == 1) {
23 boxv = gtk_widget_get_parent (GTK_WIDGET (nb));
24 win = gtk_widget_get_parent (boxv);
25 gtk_window_destroy (GTK_WINDOW (win));
26 } else {
27 i = gtk_notebook_get_current_page (nb);
28 gtk_notebook_remove_page (GTK_NOTEBOOK (nb), i);
29 }
30 }
`open_clicked`, `new_clicked` and `save_clicked` just call corresponding notebook page functions.
`close_clicked` is a bit complicated.
- 22-25: If there's only one page, closing the last page is considered that it also close the top level window and quit the application.
Therefore, it gets the top level window and call `gtk_window_destroy`.
- 26-28: Otherwise, it removes the current page.
## meson.build
1 project('tfe', 'c')
2
@ -16,605 +265,6 @@ The followings are the source files of tfe5.
9
10 executable('tfe', sourcefiles, resources, dependencies: gtkdep)
## tfe.gresource.xml
This file is just modified the source file names.
1 <?xml version="1.0" encoding="UTF-8"?>
2 <gresources>
3 <gresource prefix="/com/github/ToshioCP/tfe">
4 <file>tfe.ui</file>
5 </gresource>
6 </gresources>
## tfe.ui
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 <property name="use-underline">TRUE</property>
21 </object>
22 </child>
23 <child>
24 <object class="GtkButton" id="btno">
25 <property name="label">_Open</property>
26 <property name="use-underline">TRUE</property>
27 </object>
28 </child>
29 <child>
30 <object class="GtkLabel" id="dmy2">
31 <property name="hexpand">TRUE</property>
32 </object>
33 </child>
34 <child>
35 <object class="GtkButton" id="btns">
36 <property name="label">_Save</property>
37 <property name="use-underline">TRUE</property>
38 </object>
39 </child>
40 <child>
41 <object class="GtkButton" id="btnc">
42 <property name="label">_Close</property>
43 <property name="use-underline">TRUE</property>
44 </object>
45 </child>
46 <child>
47 <object class="GtkLabel" id="dmy3">
48 <property name="width-chars">10</property>
49 </object>
50 </child>
51 </object>
52 </child>
53 <child>
54 <object class="GtkNotebook" id="nb">
55 <property name="scrollable">TRUE</property>
56 <property name="hexpand">TRUE</property>
57 <property name="vexpand">TRUE</property>
58 </object>
59 </child>
60 </object>
61 </child>
62 </object>
63 </interface>
64
## tfe.h
1 #include <gtk/gtk.h>
2
3 #include "tfetextview.h"
4 #include "tfenotebook.h"
## tfeapplication.c
1 #include "tfe.h"
2
3 static void
4 open_clicked (GtkWidget *btno, GtkNotebook *nb) {
5 notebook_page_open (nb);
6 }
7
8 static void
9 new_clicked (GtkWidget *btnn, GtkNotebook *nb) {
10 notebook_page_new (nb);
11 }
12
13 static void
14 save_clicked (GtkWidget *btns, GtkNotebook *nb) {
15 notebook_page_save (nb);
16 }
17
18 static void
19 close_clicked (GtkWidget *btnc, GtkNotebook *nb) {
20 GtkWidget *win;
21 GtkWidget *boxv;
22 gint i;
23
24 if (gtk_notebook_get_n_pages (nb) == 1) {
25 boxv = gtk_widget_get_parent (GTK_WIDGET (nb));
26 win = gtk_widget_get_parent (boxv);
27 gtk_window_destroy (GTK_WINDOW (win));
28 } else {
29 i = gtk_notebook_get_current_page (nb);
30 gtk_notebook_remove_page (GTK_NOTEBOOK (nb), i);
31 }
32 }
33
34 static void
35 tfe_activate (GApplication *application) {
36 GtkApplication *app = GTK_APPLICATION (application);
37 GtkWidget *win;
38 GtkWidget *boxv;
39 GtkNotebook *nb;
40
41 win = GTK_WIDGET (gtk_application_get_active_window (app));
42 boxv = gtk_window_get_child (GTK_WINDOW (win));
43 nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv));
44
45 notebook_page_new (nb);
46 gtk_widget_show (GTK_WIDGET (win));
47 }
48
49 static void
50 tfe_open (GApplication *application, GFile ** files, gint n_files, const gchar *hint) {
51 GtkApplication *app = GTK_APPLICATION (application);
52 GtkWidget *win;
53 GtkWidget *boxv;
54 GtkNotebook *nb;
55 int i;
56
57 win = GTK_WIDGET (gtk_application_get_active_window (app));
58 boxv = gtk_window_get_child (GTK_WINDOW (win));
59 nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv));
60
61 for (i = 0; i < n_files; i++)
62 notebook_page_new_with_file (nb, files[i]);
63 if (gtk_notebook_get_n_pages (nb) == 0)
64 notebook_page_new (nb);
65 gtk_widget_show (win);
66 }
67
68
69 static void
70 tfe_startup (GApplication *application) {
71 GtkApplication *app = GTK_APPLICATION (application);
72 GtkApplicationWindow *win;
73 GtkNotebook *nb;
74 GtkBuilder *build;
75 GtkButton *btno;
76 GtkButton *btnn;
77 GtkButton *btns;
78 GtkButton *btnc;
79
80 build = gtk_builder_new_from_resource ("/com/github/ToshioCP/tfe/tfe.ui");
81 win = GTK_APPLICATION_WINDOW (gtk_builder_get_object (build, "win"));
82 nb = GTK_NOTEBOOK (gtk_builder_get_object (build, "nb"));
83 gtk_window_set_application (GTK_WINDOW (win), app);
84 btno = GTK_BUTTON (gtk_builder_get_object (build, "btno"));
85 btnn = GTK_BUTTON (gtk_builder_get_object (build, "btnn"));
86 btns = GTK_BUTTON (gtk_builder_get_object (build, "btns"));
87 btnc = GTK_BUTTON (gtk_builder_get_object (build, "btnc"));
88 g_signal_connect (btno, "clicked", G_CALLBACK (open_clicked), nb);
89 g_signal_connect (btnn, "clicked", G_CALLBACK (new_clicked), nb);
90 g_signal_connect (btns, "clicked", G_CALLBACK (save_clicked), nb);
91 g_signal_connect (btnc, "clicked", G_CALLBACK (close_clicked), nb);
92 g_object_unref(build);
93
94 GdkDisplay *display;
95
96 display = gtk_widget_get_display (GTK_WIDGET (win));
97 GtkCssProvider *provider = gtk_css_provider_new ();
98 gtk_css_provider_load_from_data (provider, "textview {padding: 10px; font-family: monospace; font-size: 12pt;}", -1);
99 gtk_style_context_add_provider_for_display (display, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_USER);
100 }
101
102 int
103 main (int argc, char **argv) {
104 GtkApplication *app;
105 int stat;
106
107 app = gtk_application_new ("com.github.ToshioCP.tfe", G_APPLICATION_HANDLES_OPEN);
108
109 g_signal_connect (app, "startup", G_CALLBACK (tfe_startup), NULL);
110 g_signal_connect (app, "activate", G_CALLBACK (tfe_activate), NULL);
111 g_signal_connect (app, "open", G_CALLBACK (tfe_open), NULL);
112
113 stat =g_application_run (G_APPLICATION (app), argc, argv);
114 g_object_unref (app);
115 return stat;
116 }
117
### tfenotebook.h
1 void
2 notebook_page_save(GtkNotebook *nb);
3
4 void
5 notebook_page_open (GtkNotebook *nb);
6
7 void
8 notebook_page_new_with_file (GtkNotebook *nb, GFile *file);
9
10 void
11 notebook_page_new (GtkNotebook *nb);
12
## tfenotebook.c
1 #include "tfe.h"
2
3 /* The returned string should be freed with g_free() when no longer needed. */
4 static gchar*
5 get_untitled () {
6 static int c = -1;
7 if (++c == 0)
8 return g_strdup_printf("Untitled");
9 else
10 return g_strdup_printf ("Untitled%u", c);
11 }
12
13 static void
14 file_changed (TfeTextView *tv, GtkNotebook *nb) {
15 GFile *file;
16 char *filename;
17 GtkWidget *scr;
18 GtkWidget *label;
19
20 file = tfe_text_view_get_file (tv);
21 scr = gtk_widget_get_parent (GTK_WIDGET (tv));
22 if (G_IS_FILE (file))
23 filename = g_file_get_basename (file);
24 else
25 filename = get_untitled ();
26 label = gtk_label_new (filename);
27 gtk_notebook_set_tab_label (nb, scr, label);
28 g_object_unref (file);
29 g_free (filename);
30 }
31
32 /* Save the contents in the current page */
33 void
34 notebook_page_save(GtkNotebook *nb) {
35 gint i;
36 GtkWidget *scr;
37 GtkWidget *tv;
38
39 i = gtk_notebook_get_current_page (nb);
40 scr = gtk_notebook_get_nth_page (nb, i);
41 tv = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (scr));
42 tfe_text_view_save (TFE_TEXT_VIEW (tv));
43 }
44
45 static void
46 notebook_page_build (GtkNotebook *nb, GtkWidget *tv, char *filename) {
47 GtkWidget *scr;
48 GtkNotebookPage *nbp;
49 GtkWidget *lab;
50 gint i;
51 scr = gtk_scrolled_window_new ();
52
53 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
54 lab = gtk_label_new (filename);
55 i = gtk_notebook_append_page (nb, scr, lab);
56 nbp = gtk_notebook_get_page (nb, scr);
57 g_object_set (nbp, "tab-expand", TRUE, NULL);
58 gtk_notebook_set_current_page (nb, i);
59 g_signal_connect (GTK_TEXT_VIEW (tv), "change-file", G_CALLBACK (file_changed), nb);
60 }
61
62 static void
63 open_response (TfeTextView *tv, gint response, GtkNotebook *nb) {
64 GFile *file;
65 char *filename;
66
67 if (response != TFE_OPEN_RESPONSE_SUCCESS)
68 g_object_unref (tv);
69 else if (! G_IS_FILE (file = tfe_text_view_get_file (tv)))
70 g_object_unref (tv);
71 else {
72 filename = g_file_get_basename (file);
73 g_object_unref (file);
74 notebook_page_build (nb, GTK_WIDGET (tv), filename);
75 }
76 }
77
78 void
79 notebook_page_open (GtkNotebook *nb) {
80 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
81
82 GtkWidget *tv;
83
84 tv = tfe_text_view_new ();
85 g_signal_connect (TFE_TEXT_VIEW (tv), "open-response", G_CALLBACK (open_response), nb);
86 tfe_text_view_open (TFE_TEXT_VIEW (tv));
87 }
88
89 void
90 notebook_page_new_with_file (GtkNotebook *nb, GFile *file) {
91 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
92 g_return_if_fail(G_IS_FILE (file));
93
94 GtkWidget *tv;
95 char *filename;
96
97 if ((tv = tfe_text_view_new_with_file (file)) == NULL)
98 return; /* read error */
99 filename = g_file_get_basename (file);
100 notebook_page_build (nb, tv, filename);
101 }
102
103 void
104 notebook_page_new (GtkNotebook *nb) {
105 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
106
107 GtkWidget *tv;
108 char *filename;
109
110 tv = tfe_text_view_new ();
111 filename = get_untitled ();
112 notebook_page_build (nb, tv, filename);
113 }
114
## tfetextview.h
1 #define TFE_TYPE_TEXT_VIEW tfe_text_view_get_type ()
2 G_DECLARE_FINAL_TYPE (TfeTextView, tfe_text_view, TFE, TEXT_VIEW, GtkTextView)
3
4 /* "open-response" signal response */
5 enum
6 {
7 TFE_OPEN_RESPONSE_SUCCESS,
8 TFE_OPEN_RESPONSE_CANCEL,
9 TFE_OPEN_RESPONSE_ERROR
10 };
11
12 GFile *
13 tfe_text_view_get_file (TfeTextView *tv);
14
15 void
16 tfe_text_view_open (TfeTextView *tv);
17
18 void
19 tfe_text_view_save (TfeTextView *tv);
20
21 void
22 tfe_text_view_saveas (TfeTextView *tv);
23
24 GtkWidget *
25 tfe_text_view_new_with_file (GFile *file);
26
27 GtkWidget *
28 tfe_text_view_new (void);
29
## tfetextview.c
1 #include "tfe.h"
2
3 struct _TfeTextView
4 {
5 GtkTextView parent;
6 GtkTextBuffer *tb;
7 GFile *file;
8 gboolean changed;
9 };
10
11 G_DEFINE_TYPE (TfeTextView, tfe_text_view, GTK_TYPE_TEXT_VIEW);
12
13 enum {
14 CHANGE_FILE,
15 OPEN_RESPONSE,
16 NUMBER_OF_SIGNALS
17 };
18
19 static guint tfe_text_view_signals[NUMBER_OF_SIGNALS];
20
21 /* Signal handler */
22 static void
23 on_changed (GtkTextBuffer *tb, TfeTextView *tv) {
24 tv->changed=TRUE;
25 }
26
27 static void
28 tfe_text_view_dispose (GObject *gobject) {
29 TfeTextView *tv = TFE_TEXT_VIEW (gobject);
30
31 if (G_IS_FILE (tv->file))
32 g_clear_object (&tv->file);
33
34 G_OBJECT_CLASS (tfe_text_view_parent_class)->dispose (gobject);
35 }
36
37 static void
38 tfe_text_view_init (TfeTextView *tv) {
39 tv->tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
40 tv->file = NULL;
41 tv->changed = FALSE;
42 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
43 g_signal_connect (tv->tb, "changed", G_CALLBACK (on_changed), tv);
44 }
45
46 static void
47 tfe_text_view_class_init (TfeTextViewClass *class) {
48 GObjectClass *object_class = G_OBJECT_CLASS (class);
49
50 object_class->dispose = tfe_text_view_dispose;
51 tfe_text_view_signals[CHANGE_FILE] = g_signal_newv ("change-file",
52 G_TYPE_FROM_CLASS (class),
53 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
54 NULL /* closure */,
55 NULL /* accumulator */,
56 NULL /* accumulator data */,
57 NULL /* C marshaller */,
58 G_TYPE_NONE /* return_type */,
59 0 /* n_params */,
60 NULL /* param_types */);
61 GType param_types[] = {G_TYPE_INT};
62 tfe_text_view_signals[OPEN_RESPONSE] = g_signal_newv ("open-response",
63 G_TYPE_FROM_CLASS (class),
64 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
65 NULL /* closure */,
66 NULL /* accumulator */,
67 NULL /* accumulator data */,
68 NULL /* C marshaller */,
69 G_TYPE_NONE /* return_type */,
70 1 /* n_params */,
71 param_types);
72 }
73
74 GFile *
75 tfe_text_view_get_file (TfeTextView *tv) {
76 g_return_val_if_fail (TFE_IS_TEXT_VIEW (tv), NULL);
77
78 return g_file_dup (tv->file);
79 }
80
81 static void
82 open_dialog_response(GtkWidget *dialog, gint response, TfeTextView *tv) {
83 GFile *file;
84 char *contents;
85 gsize length;
86 GtkWidget *message_dialog;
87 GError *err = NULL;
88
89 if (response != GTK_RESPONSE_ACCEPT)
90 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_CANCEL);
91 else if (! G_IS_FILE (file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog))))
92 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
93 else if (! g_file_load_contents (file, NULL, &contents, &length, NULL, &err)) { /* read error */
94 if (G_IS_FILE (file))
95 g_object_unref (file);
96 message_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL,
97 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
98 "%s.\n", err->message);
99 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
100 gtk_widget_show (message_dialog);
101 g_error_free (err);
102 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
103 } else {
104 gtk_text_buffer_set_text (tv->tb, contents, length);
105 g_free (contents);
106 tv->file = file;
107 /* tv->changed = FALSE;*/
108 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS);
109 }
110 gtk_window_destroy (GTK_WINDOW (dialog));
111 }
112
113 void
114 tfe_text_view_open (TfeTextView *tv) {
115 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
116
117 GtkWidget *dialog;
118
119 dialog = gtk_file_chooser_dialog_new ("Open file", NULL, GTK_FILE_CHOOSER_ACTION_OPEN,
120 "Cancel", GTK_RESPONSE_CANCEL,
121 "Open", GTK_RESPONSE_ACCEPT,
122 NULL);
123 g_signal_connect (dialog, "response", G_CALLBACK (open_dialog_response), tv);
124 gtk_widget_show (dialog);
125 }
126
127 static void
128 saveas_dialog_response (GtkWidget *dialog, gint response, TfeTextView *tv) {
129 GFile *file;
130
131 if (response == GTK_RESPONSE_ACCEPT) {
132 file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
133 if (G_IS_FILE(file)) {
134 tv->file = file;
135 tv->changed = TRUE;
136 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
137 tfe_text_view_save (TFE_TEXT_VIEW (tv));
138 }
139 }
140 gtk_window_destroy (GTK_WINDOW (dialog));
141 }
142
143 void
144 tfe_text_view_save (TfeTextView *tv) {
145 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
146
147 GtkTextIter start_iter;
148 GtkTextIter end_iter;
149 gchar *contents;
150 GtkWidget *message_dialog;
151 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
152 GError *err = NULL;
153
154 if (! tv->changed)
155 return; /* no necessary to save it */
156 else if (tv->file == NULL)
157 tfe_text_view_saveas (tv);
158 else {
159 gtk_text_buffer_get_bounds (tv->tb, &start_iter, &end_iter);
160 contents = gtk_text_buffer_get_text (tv->tb, &start_iter, &end_iter, FALSE);
161 if (g_file_replace_contents (tv->file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, &err))
162 tv->changed = FALSE;
163 else {
164 /* It is possible that tv->file is broken. */
165 /* It is a good idea to set tv->file to NULL. */
166 if (G_IS_FILE (tv->file))
167 g_object_unref (tv->file);
168 tv->file =NULL;
169 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
170 tv->changed = TRUE;
171 message_dialog = gtk_message_dialog_new (GTK_WINDOW (win), GTK_DIALOG_MODAL,
172 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
173 "%s.\n", err->message);
174 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
175 gtk_widget_show (message_dialog);
176 g_error_free (err);
177 }
178 }
179 }
180
181 void
182 tfe_text_view_saveas (TfeTextView *tv) {
183 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
184
185 GtkWidget *dialog;
186 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
187
188 dialog = gtk_file_chooser_dialog_new ("Save file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_SAVE,
189 "_Cancel", GTK_RESPONSE_CANCEL,
190 "_Save", GTK_RESPONSE_ACCEPT,
191 NULL);
192 g_signal_connect (dialog, "response", G_CALLBACK (saveas_dialog_response), tv);
193 gtk_widget_show (dialog);
194 }
195
196 GtkWidget *
197 tfe_text_view_new_with_file (GFile *file) {
198 g_return_val_if_fail (G_IS_FILE (file), NULL);
199
200 GtkWidget *tv;
201 char *contents;
202 gsize length;
203
204 if (! g_file_load_contents (file, NULL, &contents, &length, NULL, NULL)) /* read error */
205 return NULL;
206
207 tv = tfe_text_view_new();
208 gtk_text_buffer_set_text (TFE_TEXT_VIEW (tv)->tb, contents, length);
209 g_free (contents);
210 TFE_TEXT_VIEW (tv)->file = g_file_dup (file);
211 return tv;
212 }
213
214 GtkWidget *
215 tfe_text_view_new (void) {
216 return gtk_widget_new (TFE_TYPE_TEXT_VIEW, NULL);
217 }
218
## Total number of lines, words and charcters
$ wc tfe5/meson.build tfe5/tfeapplication.c tfe5/tfe.gresource.xml tfe5/tfe.h tfe5/tfenotebook.c tfe5/tfenotebook.h tfe5/tfetextview.c tfe5/tfetextview.h tfe5/tfe.ui
10 17 279 tfe5/meson.build
117 348 3576 tfe5/tfeapplication.c
6 9 153 tfe5/tfe.gresource.xml
4 6 72 tfe5/tfe.h
114 311 2870 tfe5/tfenotebook.c
12 17 196 tfe5/tfenotebook.h
218 622 7454 tfe5/tfetextview.c
29 47 545 tfe5/tfetextview.h
64 105 2266 tfe5/tfe.ui
574 1482 17411 合計
Up: [Readme.md](Readme.md), Prev: [Section 12](sec12.md), Next: [Section 14](sec14.md)

814
sec14.md
View file

@ -1,216 +1,620 @@
Up: [Readme.md](Readme.md), Prev: [Section 13](sec13.md), Next: [Section 15](sec15.md)
# Menu and action
# tfe5 source files
## Menu
The followings are the source files of tfe5.
Users often use menus to tell the command to the computer.
It is like this:
## meson.buld
![Menu](image/menu.png)
Now let's analyze the menu above.
There are two types of object.
- "File", "Edit", "View", "Cut", "Copy", "Paste" and "Select All".
They are called "menu item" or simply "item".
When the user clicks one of these items, then something will happen.
- Menubar, submenu referenced by "Edit" item and two sections.
They are called "menu".
Menu is an ordered list of items.
They are similar to arrays.
![Menu structure](image/menu_structure.png)
- Menubar is a menu which has three items, which are "File", "Edit" and "View".
- The menu item labeled "Edit" has a link to the submenu which has two items.
These two items don't have labels.
Each item refers to a section.
- The first section is a menu which has three items -- "Cut", "Copy" and "Paste".
- The second section is a menu which has one item -- "Select All".
Menus can build a complicated structure thanks to the links of menu items.
## GMenuModel, GMenu and GMenuItem
GMenuModel is an abstact object which represents a menu.
GMenu is a simple implementation of GMenuModel and a child object of GMenuModel.
GObjct -- GMenuModel -- GMenu
Because GMenuModel is an abstract object, it doesn't have any functions to generate it.
Therefore, if you want to generate a menu, use `g_menu_new` function to generate GMenu object.
GMenu inherits all the functions of GMenuModel because of the child object.
GMenuItem is an object directly derived from GObject.
GMenuItem and Gmenu (or GMenuModel) don't have a parent-child relationship.
GObject -- GMenuModel -- GMenu
GObject -- GMenuItem
Usually, GMenuItem has attributes.
One of the attributes is label.
For example, there is a menu item which has "Edit" label in the first diagram in this section.
"Cut", "Copy", "Paste" and "Select All" are also the lables of menu items.
Other attributes will be explained later.
Some menu items have a link to another GMenu.
There are two types of links, submenu and section.
GMenuItem can be inserted, appended or prepended to GMenu.
When it is inserted, all of the attribute and link values of the item are copied and used to form a new item within the menu.
The GMenuItem itself is not really inserted.
Therefore, after the insertion, GMenuItem is useless and it should be freed.
The same goes for appending or prepending.
The following code shows how to append GMenuItem to GMenu.
GMenu *menu = g_menu_new ();
GMenuItem *menu_item_quit = g_menu_item_new ("Quit", "app.quit");
g_menu_append_item (menu, menu_item_quit);
g_object_unref (menu_item_quit);
## Menu and action
One of the attributes of menu items is an action.
This attribute points an action object.
There are two action objects, GSimpleAction and GPropertyAction.
GSimpleAction is often used.
And it is used with a menu item.
Only GSimpleAction is described in this section.
An action corresponds to a menu item will be activated when the menu item is clicked.
Then the action emits an activate signal.
1. menu item is clicked.
2. The corresponding action is activated.
3. The action emits a signal.
4. The connected handler is invoked.
The following code is an example.
static void
quit_activated(GSimpleAction *action, GVariant *parameter, gpointer app) { ... ... ...}
GSimpleAction *act_quit = g_simple_action_new ("quit", NULL);
g_signal_connect (act_quit, "activate", G_CALLBACK (quit_activated), app);
GMenuItem *menu_item_quit = g_menu_item_new ("Quit", "app.quit");
1. `menu_item_quit` is a menu item.
It has a label "Quit" and is connected to an action "app.quit".
"app" is a prefix and "quit" is the name of an action.
The prefix means that the action belongs to GtkApplication.
If the menu is clicked, then the corresponding action "quit" which belongs to GtkApplication will be activated.
2. `act_quit` is an action.
It has a name "quit".
It belongs to GtkApplication, but it is not obvious in the code above.
The function `g_simple_action_new` generates a stateless action.
So, `act_quit` is stateless.
The meaning of stateless will be explained later.
The argument `NULL` means that the action doesn't have an parameter.
Generally, most of the actions are stateless and have no parameter.
When `act_quit` is activated, it will emit "activate" signal.
3. "activate" signal of the action is connected to the handler `quit_activated`.
So, if the action is activated, the handler will be invoked.
## Simple example
The following is a simple example of menus and actions.
1 #include <gtk/gtk.h>
1 project('tfe', 'c')
2
3 static void
4 quit_activated(GSimpleAction *action, GVariant *parameter, gpointer app)
5 {
6 g_application_quit (G_APPLICATION(app));
7 }
8
9 static void
10 on_activate (GApplication *app, gpointer user_data) {
11 GtkWidget *win = gtk_application_window_new (GTK_APPLICATION (app));
12 gtk_window_set_title (GTK_WINDOW (win), "menu1");
13 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
3 gtkdep = dependency('gtk4')
4
5 gnome=import('gnome')
6 resources = gnome.compile_resources('resources','tfe.gresource.xml')
7
8 sourcefiles=files('tfeapplication.c', 'tfenotebook.c', 'tfetextview.c')
9
10 executable('tfe', sourcefiles, resources, dependencies: gtkdep)
## tfe.gresource.xml
1 <?xml version="1.0" encoding="UTF-8"?>
2 <gresources>
3 <gresource prefix="/com/github/ToshioCP/tfe">
4 <file>tfe.ui</file>
5 </gresource>
6 </gresources>
## tfe.ui
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 <property name="use-underline">TRUE</property>
21 </object>
22 </child>
23 <child>
24 <object class="GtkButton" id="btno">
25 <property name="label">_Open</property>
26 <property name="use-underline">TRUE</property>
27 </object>
28 </child>
29 <child>
30 <object class="GtkLabel" id="dmy2">
31 <property name="hexpand">TRUE</property>
32 </object>
33 </child>
34 <child>
35 <object class="GtkButton" id="btns">
36 <property name="label">_Save</property>
37 <property name="use-underline">TRUE</property>
38 </object>
39 </child>
40 <child>
41 <object class="GtkButton" id="btnc">
42 <property name="label">_Close</property>
43 <property name="use-underline">TRUE</property>
44 </object>
45 </child>
46 <child>
47 <object class="GtkLabel" id="dmy3">
48 <property name="width-chars">10</property>
49 </object>
50 </child>
51 </object>
52 </child>
53 <child>
54 <object class="GtkNotebook" id="nb">
55 <property name="scrollable">TRUE</property>
56 <property name="hexpand">TRUE</property>
57 <property name="vexpand">TRUE</property>
58 </object>
59 </child>
60 </object>
61 </child>
62 </object>
63 </interface>
64
## tfe.h
1 #include <gtk/gtk.h>
2
3 #include "tfetextview.h"
4 #include "tfenotebook.h"
## tfeapplication.c
1 #include "tfe.h"
2
3 static void
4 open_clicked (GtkWidget *btno, GtkNotebook *nb) {
5 notebook_page_open (nb);
6 }
7
8 static void
9 new_clicked (GtkWidget *btnn, GtkNotebook *nb) {
10 notebook_page_new (nb);
11 }
12
13 static void
14 save_clicked (GtkWidget *btns, GtkNotebook *nb) {
15 notebook_page_save (nb);
16 }
17
18 static void
19 close_clicked (GtkWidget *btnc, GtkNotebook *nb) {
20 GtkWidget *win;
21 GtkWidget *boxv;
22 gint i;
23
24 if (gtk_notebook_get_n_pages (nb) == 1) {
25 boxv = gtk_widget_get_parent (GTK_WIDGET (nb));
26 win = gtk_widget_get_parent (boxv);
27 gtk_window_destroy (GTK_WINDOW (win));
28 } else {
29 i = gtk_notebook_get_current_page (nb);
30 gtk_notebook_remove_page (GTK_NOTEBOOK (nb), i);
31 }
32 }
33
34 static void
35 tfe_activate (GApplication *application) {
36 GtkApplication *app = GTK_APPLICATION (application);
37 GtkWidget *win;
38 GtkWidget *boxv;
39 GtkNotebook *nb;
40
41 win = GTK_WIDGET (gtk_application_get_active_window (app));
42 boxv = gtk_window_get_child (GTK_WINDOW (win));
43 nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv));
44
45 notebook_page_new (nb);
46 gtk_widget_show (GTK_WIDGET (win));
47 }
48
49 static void
50 tfe_open (GApplication *application, GFile ** files, gint n_files, const gchar *hint) {
51 GtkApplication *app = GTK_APPLICATION (application);
52 GtkWidget *win;
53 GtkWidget *boxv;
54 GtkNotebook *nb;
55 int i;
56
57 win = GTK_WIDGET (gtk_application_get_active_window (app));
58 boxv = gtk_window_get_child (GTK_WINDOW (win));
59 nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv));
60
61 for (i = 0; i < n_files; i++)
62 notebook_page_new_with_file (nb, files[i]);
63 if (gtk_notebook_get_n_pages (nb) == 0)
64 notebook_page_new (nb);
65 gtk_widget_show (win);
66 }
67
68
69 static void
70 tfe_startup (GApplication *application) {
71 GtkApplication *app = GTK_APPLICATION (application);
72 GtkApplicationWindow *win;
73 GtkNotebook *nb;
74 GtkBuilder *build;
75 GtkButton *btno;
76 GtkButton *btnn;
77 GtkButton *btns;
78 GtkButton *btnc;
79
80 build = gtk_builder_new_from_resource ("/com/github/ToshioCP/tfe/tfe.ui");
81 win = GTK_APPLICATION_WINDOW (gtk_builder_get_object (build, "win"));
82 nb = GTK_NOTEBOOK (gtk_builder_get_object (build, "nb"));
83 gtk_window_set_application (GTK_WINDOW (win), app);
84 btno = GTK_BUTTON (gtk_builder_get_object (build, "btno"));
85 btnn = GTK_BUTTON (gtk_builder_get_object (build, "btnn"));
86 btns = GTK_BUTTON (gtk_builder_get_object (build, "btns"));
87 btnc = GTK_BUTTON (gtk_builder_get_object (build, "btnc"));
88 g_signal_connect (btno, "clicked", G_CALLBACK (open_clicked), nb);
89 g_signal_connect (btnn, "clicked", G_CALLBACK (new_clicked), nb);
90 g_signal_connect (btns, "clicked", G_CALLBACK (save_clicked), nb);
91 g_signal_connect (btnc, "clicked", G_CALLBACK (close_clicked), nb);
92 g_object_unref(build);
93
94 GdkDisplay *display;
95
96 display = gtk_widget_get_display (GTK_WIDGET (win));
97 GtkCssProvider *provider = gtk_css_provider_new ();
98 gtk_css_provider_load_from_data (provider, "textview {padding: 10px; font-family: monospace; font-size: 12pt;}", -1);
99 gtk_style_context_add_provider_for_display (display, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_USER);
100 }
101
102 int
103 main (int argc, char **argv) {
104 GtkApplication *app;
105 int stat;
106
107 app = gtk_application_new ("com.github.ToshioCP.tfe", G_APPLICATION_HANDLES_OPEN);
108
109 g_signal_connect (app, "startup", G_CALLBACK (tfe_startup), NULL);
110 g_signal_connect (app, "activate", G_CALLBACK (tfe_activate), NULL);
111 g_signal_connect (app, "open", G_CALLBACK (tfe_open), NULL);
112
113 stat =g_application_run (G_APPLICATION (app), argc, argv);
114 g_object_unref (app);
115 return stat;
116 }
117
### tfenotebook.h
1 void
2 notebook_page_save(GtkNotebook *nb);
3
4 void
5 notebook_page_open (GtkNotebook *nb);
6
7 void
8 notebook_page_new_with_file (GtkNotebook *nb, GFile *file);
9
10 void
11 notebook_page_new (GtkNotebook *nb);
12
## tfenotebook.c
1 #include "tfe.h"
2
3 /* The returned string should be freed with g_free() when no longer needed. */
4 static gchar*
5 get_untitled () {
6 static int c = -1;
7 if (++c == 0)
8 return g_strdup_printf("Untitled");
9 else
10 return g_strdup_printf ("Untitled%u", c);
11 }
12
13 static void
14 file_changed (TfeTextView *tv, GtkNotebook *nb) {
15 GFile *file;
16 char *filename;
17 GtkWidget *scr;
18 GtkWidget *label;
19
20 file = tfe_text_view_get_file (tv);
21 scr = gtk_widget_get_parent (GTK_WIDGET (tv));
22 if (G_IS_FILE (file))
23 filename = g_file_get_basename (file);
24 else
25 filename = get_untitled ();
26 label = gtk_label_new (filename);
27 gtk_notebook_set_tab_label (nb, scr, label);
28 g_object_unref (file);
29 g_free (filename);
30 }
31
32 /* Save the contents in the current page */
33 void
34 notebook_page_save(GtkNotebook *nb) {
35 gint i;
36 GtkWidget *scr;
37 GtkWidget *tv;
38
39 i = gtk_notebook_get_current_page (nb);
40 scr = gtk_notebook_get_nth_page (nb, i);
41 tv = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (scr));
42 tfe_text_view_save (TFE_TEXT_VIEW (tv));
43 }
44
45 static void
46 notebook_page_build (GtkNotebook *nb, GtkWidget *tv, char *filename) {
47 GtkWidget *scr;
48 GtkNotebookPage *nbp;
49 GtkWidget *lab;
50 gint i;
51 scr = gtk_scrolled_window_new ();
52
53 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
54 lab = gtk_label_new (filename);
55 i = gtk_notebook_append_page (nb, scr, lab);
56 nbp = gtk_notebook_get_page (nb, scr);
57 g_object_set (nbp, "tab-expand", TRUE, NULL);
58 gtk_notebook_set_current_page (nb, i);
59 g_signal_connect (GTK_TEXT_VIEW (tv), "change-file", G_CALLBACK (file_changed), nb);
60 }
61
62 static void
63 open_response (TfeTextView *tv, gint response, GtkNotebook *nb) {
64 GFile *file;
65 char *filename;
66
67 if (response != TFE_OPEN_RESPONSE_SUCCESS)
68 g_object_unref (tv);
69 else if (! G_IS_FILE (file = tfe_text_view_get_file (tv)))
70 g_object_unref (tv);
71 else {
72 filename = g_file_get_basename (file);
73 g_object_unref (file);
74 notebook_page_build (nb, GTK_WIDGET (tv), filename);
75 }
76 }
77
78 void
79 notebook_page_open (GtkNotebook *nb) {
80 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
81
82 GtkWidget *tv;
83
84 tv = tfe_text_view_new ();
85 g_signal_connect (TFE_TEXT_VIEW (tv), "open-response", G_CALLBACK (open_response), nb);
86 tfe_text_view_open (TFE_TEXT_VIEW (tv));
87 }
88
89 void
90 notebook_page_new_with_file (GtkNotebook *nb, GFile *file) {
91 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
92 g_return_if_fail(G_IS_FILE (file));
93
94 GtkWidget *tv;
95 char *filename;
96
97 if ((tv = tfe_text_view_new_with_file (file)) == NULL)
98 return; /* read error */
99 filename = g_file_get_basename (file);
100 notebook_page_build (nb, tv, filename);
101 }
102
103 void
104 notebook_page_new (GtkNotebook *nb) {
105 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
106
107 GtkWidget *tv;
108 char *filename;
109
110 tv = tfe_text_view_new ();
111 filename = get_untitled ();
112 notebook_page_build (nb, tv, filename);
113 }
114
## tfetextview.h
1 #define TFE_TYPE_TEXT_VIEW tfe_text_view_get_type ()
2 G_DECLARE_FINAL_TYPE (TfeTextView, tfe_text_view, TFE, TEXT_VIEW, GtkTextView)
3
4 /* "open-response" signal response */
5 enum
6 {
7 TFE_OPEN_RESPONSE_SUCCESS,
8 TFE_OPEN_RESPONSE_CANCEL,
9 TFE_OPEN_RESPONSE_ERROR
10 };
11
12 GFile *
13 tfe_text_view_get_file (TfeTextView *tv);
14
15 GSimpleAction *act_quit = g_simple_action_new ("quit", NULL);
16 g_action_map_add_action (G_ACTION_MAP (app), G_ACTION (act_quit));
17 g_signal_connect (act_quit, "activate", G_CALLBACK (quit_activated), app);
18
19 GMenu *menubar = g_menu_new ();
20 GMenuItem *menu_item_menu = g_menu_item_new ("Menu", NULL);
21 GMenu *menu = g_menu_new ();
22 GMenuItem *menu_item_quit = g_menu_item_new ("Quit", "app.quit");
23 g_menu_append_item (menu, menu_item_quit);
24 g_object_unref (menu_item_quit);
25 g_menu_item_set_submenu (menu_item_menu, G_MENU_MODEL (menu));
26 g_menu_append_item (menubar, menu_item_menu);
27 g_object_unref (menu_item_menu);
28
29 gtk_application_set_menubar (GTK_APPLICATION (app), G_MENU_MODEL (menubar));
30 gtk_application_window_set_show_menubar (GTK_APPLICATION_WINDOW (win), TRUE);
31 gtk_window_present (GTK_WINDOW (win));
32 /* gtk_widget_show (win); is also OKay instead of gtk_window_present. */
33 }
34
35 int
36 main (int argc, char **argv) {
37 GtkApplication *app;
38 int stat;
39
40 app = gtk_application_new ("com.github.ToshioCP.menu1", G_APPLICATION_FLAGS_NONE);
41 g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
42
43 stat =g_application_run (G_APPLICATION (app), argc, argv);
44 g_object_unref (app);
45 return stat;
46 }
47
15 void
16 tfe_text_view_open (TfeTextView *tv);
17
18 void
19 tfe_text_view_save (TfeTextView *tv);
20
21 void
22 tfe_text_view_saveas (TfeTextView *tv);
23
24 GtkWidget *
25 tfe_text_view_new_with_file (GFile *file);
26
27 GtkWidget *
28 tfe_text_view_new (void);
29
- 3-7: `quit_activated` is a handler of an action `act_quit`.
Handlers of actions have three parameters.
1. The action object which has emitted the signal.
2. Parameter.
In this example it is `NULL` because the second argument of `g_simple_action_new` (line 15) is `NULL`.
You don' t need to care about it.
3. User data.
It is the fourth parameter in the `g_signal_connect` (line 17) that has connected the action and the handler.
- 6: A function `g_application_quit` immediately quits the application.
- 9-33: `on_activate` is a handler of "activate" signal on GtkApplication.
- 11-13: Generate a GtkApplicationWindow and set a pointer to it to `win`. And set the title and default size.
- 15: Generate GSimpleAction `act_quit`.
It is stateless.
The first argument of `g_simple_action_new` is a name of the action and the second argument is a parameter.
If you don't need the parameter, set it `NULL`.
THerefore, `act_quit` has a name "quit" and no parameter.
- 16: Add the action to GtkApplication `app`.
GtkApplication implements an interface GActionMap and GActionGroup.
And GtkApplication can have a group of actions and actions are added by the function `g_action_map_add_action`.
This function is described in GMenuModel section in GIO API reference.
- 17: Connect "activate" signal of the action and the handler `quit_activated`.
- 19-22: Generate GMenu and GMenuItem.
`menubar` and `menu` are GMenu.
`menu_item_menu` and `menu_item_quit` are GMenuItem.
`menu_item_menu` has a label "Menu" and no action.
`menu_item_quit` has a label "Quit".
The second argument "app.quit" is a combination of "app" and "quit".
"app" is a prefix and it means that the action belongs to GtkApplication. "quit" is the name of the action.
Therefore, it points the action which belongs to GtkApplication and has the name "quit" -- it is `act_quit`.
- 23-24: Append `act_quit` to `menu`.
As I mentioned before, all the attribute and link values are copied and used to form a new item within `menu`.
Therefore after the appending, `menu` has a copy of `act_quit` in itself and `act_quit` is no longer needed.
It is freed by `g_object_unref`.
- 25: Set a submenu link to `menu_item_menu`.
And the link points the GMenu `menu`.
- 26-27: Append `menu_item_menu` to `menubar`.
Then free `menu_item_menu`.
GMenu and GMenuItem are connected and finally a menu is made up.
The structure of the menu is shown in the diagram below.
- 29: The menu is set to GtkApplication.
- 30: Set GtkApplicationWindow to show the menubar.
- 31: Show the window.
## tfetextview.c
![menu and action](image/menu1.png)
1 #include "tfe.h"
2
3 struct _TfeTextView
4 {
5 GtkTextView parent;
6 GtkTextBuffer *tb;
7 GFile *file;
8 gboolean changed;
9 };
10
11 G_DEFINE_TYPE (TfeTextView, tfe_text_view, GTK_TYPE_TEXT_VIEW);
12
13 enum {
14 CHANGE_FILE,
15 OPEN_RESPONSE,
16 NUMBER_OF_SIGNALS
17 };
18
19 static guint tfe_text_view_signals[NUMBER_OF_SIGNALS];
20
21 /* Signal handler */
22 static void
23 on_changed (GtkTextBuffer *tb, TfeTextView *tv) {
24 tv->changed=TRUE;
25 }
26
27 static void
28 tfe_text_view_dispose (GObject *gobject) {
29 TfeTextView *tv = TFE_TEXT_VIEW (gobject);
30
31 if (G_IS_FILE (tv->file))
32 g_clear_object (&tv->file);
33
34 G_OBJECT_CLASS (tfe_text_view_parent_class)->dispose (gobject);
35 }
36
37 static void
38 tfe_text_view_init (TfeTextView *tv) {
39 tv->tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
40 tv->file = NULL;
41 tv->changed = FALSE;
42 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
43 g_signal_connect (tv->tb, "changed", G_CALLBACK (on_changed), tv);
44 }
45
46 static void
47 tfe_text_view_class_init (TfeTextViewClass *class) {
48 GObjectClass *object_class = G_OBJECT_CLASS (class);
49
50 object_class->dispose = tfe_text_view_dispose;
51 tfe_text_view_signals[CHANGE_FILE] = g_signal_newv ("change-file",
52 G_TYPE_FROM_CLASS (class),
53 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
54 NULL /* closure */,
55 NULL /* accumulator */,
56 NULL /* accumulator data */,
57 NULL /* C marshaller */,
58 G_TYPE_NONE /* return_type */,
59 0 /* n_params */,
60 NULL /* param_types */);
61 GType param_types[] = {G_TYPE_INT};
62 tfe_text_view_signals[OPEN_RESPONSE] = g_signal_newv ("open-response",
63 G_TYPE_FROM_CLASS (class),
64 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
65 NULL /* closure */,
66 NULL /* accumulator */,
67 NULL /* accumulator data */,
68 NULL /* C marshaller */,
69 G_TYPE_NONE /* return_type */,
70 1 /* n_params */,
71 param_types);
72 }
73
74 GFile *
75 tfe_text_view_get_file (TfeTextView *tv) {
76 g_return_val_if_fail (TFE_IS_TEXT_VIEW (tv), NULL);
77
78 return g_file_dup (tv->file);
79 }
80
81 static void
82 open_dialog_response(GtkWidget *dialog, gint response, TfeTextView *tv) {
83 GFile *file;
84 char *contents;
85 gsize length;
86 GtkWidget *message_dialog;
87 GError *err = NULL;
88
89 if (response != GTK_RESPONSE_ACCEPT)
90 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_CANCEL);
91 else if (! G_IS_FILE (file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog))))
92 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
93 else if (! g_file_load_contents (file, NULL, &contents, &length, NULL, &err)) { /* read error */
94 if (G_IS_FILE (file))
95 g_object_unref (file);
96 message_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL,
97 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
98 "%s.\n", err->message);
99 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
100 gtk_widget_show (message_dialog);
101 g_error_free (err);
102 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
103 } else {
104 gtk_text_buffer_set_text (tv->tb, contents, length);
105 g_free (contents);
106 tv->file = file;
107 /* tv->changed = FALSE;*/
108 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS);
109 }
110 gtk_window_destroy (GTK_WINDOW (dialog));
111 }
112
113 void
114 tfe_text_view_open (TfeTextView *tv) {
115 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
116
117 GtkWidget *dialog;
118
119 dialog = gtk_file_chooser_dialog_new ("Open file", NULL, GTK_FILE_CHOOSER_ACTION_OPEN,
120 "Cancel", GTK_RESPONSE_CANCEL,
121 "Open", GTK_RESPONSE_ACCEPT,
122 NULL);
123 g_signal_connect (dialog, "response", G_CALLBACK (open_dialog_response), tv);
124 gtk_widget_show (dialog);
125 }
126
127 static void
128 saveas_dialog_response (GtkWidget *dialog, gint response, TfeTextView *tv) {
129 GFile *file;
130
131 if (response == GTK_RESPONSE_ACCEPT) {
132 file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
133 if (G_IS_FILE(file)) {
134 tv->file = file;
135 tv->changed = TRUE;
136 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
137 tfe_text_view_save (TFE_TEXT_VIEW (tv));
138 }
139 }
140 gtk_window_destroy (GTK_WINDOW (dialog));
141 }
142
143 void
144 tfe_text_view_save (TfeTextView *tv) {
145 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
146
147 GtkTextIter start_iter;
148 GtkTextIter end_iter;
149 gchar *contents;
150 GtkWidget *message_dialog;
151 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
152 GError *err = NULL;
153
154 if (! tv->changed)
155 return; /* no necessary to save it */
156 else if (tv->file == NULL)
157 tfe_text_view_saveas (tv);
158 else {
159 gtk_text_buffer_get_bounds (tv->tb, &start_iter, &end_iter);
160 contents = gtk_text_buffer_get_text (tv->tb, &start_iter, &end_iter, FALSE);
161 if (g_file_replace_contents (tv->file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, &err))
162 tv->changed = FALSE;
163 else {
164 /* It is possible that tv->file is broken. */
165 /* It is a good idea to set tv->file to NULL. */
166 if (G_IS_FILE (tv->file))
167 g_object_unref (tv->file);
168 tv->file =NULL;
169 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
170 tv->changed = TRUE;
171 message_dialog = gtk_message_dialog_new (GTK_WINDOW (win), GTK_DIALOG_MODAL,
172 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
173 "%s.\n", err->message);
174 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
175 gtk_widget_show (message_dialog);
176 g_error_free (err);
177 }
178 }
179 }
180
181 void
182 tfe_text_view_saveas (TfeTextView *tv) {
183 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
184
185 GtkWidget *dialog;
186 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
187
188 dialog = gtk_file_chooser_dialog_new ("Save file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_SAVE,
189 "_Cancel", GTK_RESPONSE_CANCEL,
190 "_Save", GTK_RESPONSE_ACCEPT,
191 NULL);
192 g_signal_connect (dialog, "response", G_CALLBACK (saveas_dialog_response), tv);
193 gtk_widget_show (dialog);
194 }
195
196 GtkWidget *
197 tfe_text_view_new_with_file (GFile *file) {
198 g_return_val_if_fail (G_IS_FILE (file), NULL);
199
200 GtkWidget *tv;
201 char *contents;
202 gsize length;
203
204 if (! g_file_load_contents (file, NULL, &contents, &length, NULL, NULL)) /* read error */
205 return NULL;
206
207 tv = tfe_text_view_new();
208 gtk_text_buffer_set_text (TFE_TEXT_VIEW (tv)->tb, contents, length);
209 g_free (contents);
210 TFE_TEXT_VIEW (tv)->file = g_file_dup (file);
211 return tv;
212 }
213
214 GtkWidget *
215 tfe_text_view_new (void) {
216 return gtk_widget_new (TFE_TYPE_TEXT_VIEW, NULL);
217 }
218
![Screenshot of menu1](image/menu1_screenshot.png)
## Total number of lines, words and charcters
$ wc tfe5/meson.build tfe5/tfeapplication.c tfe5/tfe.gresource.xml tfe5/tfe.h tfe5/tfenotebook.c tfe5/tfenotebook.h tfe5/tfetextview.c tfe5/tfetextview.h tfe5/tfe.ui
10 17 279 tfe5/meson.build
117 348 3576 tfe5/tfeapplication.c
6 9 153 tfe5/tfe.gresource.xml
4 6 72 tfe5/tfe.h
114 311 2870 tfe5/tfenotebook.c
12 17 196 tfe5/tfenotebook.h
218 622 7454 tfe5/tfetextview.c
29 47 545 tfe5/tfetextview.h
64 105 2266 tfe5/tfe.ui
574 1482 17411 合計
Up: [Readme.md](Readme.md), Prev: [Section 13](sec13.md), Next: [Section 15](sec15.md)

552
sec15.md
View file

@ -1,370 +1,216 @@
Up: [Readme.md](Readme.md), Prev: [Section 14](sec14.md), Next: [Section 16](sec16.md)
# Stateful action
# Menu and action
Some actions have states.
The values of states can be boolean or string.
Actions which have states are called stateful.
## Menu
## Stateful action without a parameter
Users often use menus to tell the command to the computer.
It is like this:
Some menus are called toggle menu.
For example, fullscreen menu has a state which has two values -- fullscreen and non-fullscreen.
The value of the state is changed every time the menu is clicked.
An action corresponds to the fullscreen menu also have a state.
Its value is TRUE or FALSE and it is called boolean value.
TRUE corresponds to fullscreen and FALSE to non-fullscreen.
![Menu](image/menu.png)
The following is an example code to implement a fullscreen menu except the signal handler.
The signal handler will be described after the explanation of this code.
Now let's analyze the menu above.
There are two types of object.
- "File", "Edit", "View", "Cut", "Copy", "Paste" and "Select All".
They are called "menu item" or simply "item".
When the user clicks one of these items, then something will happen.
- Menubar, submenu referenced by "Edit" item and two sections.
They are called "menu".
Menu is an ordered list of items.
They are similar to arrays.
![Menu structure](image/menu_structure.png)
- Menubar is a menu which has three items, which are "File", "Edit" and "View".
- The menu item labeled "Edit" has a link to the submenu which has two items.
These two items don't have labels.
Each item refers to a section.
- The first section is a menu which has three items -- "Cut", "Copy" and "Paste".
- The second section is a menu which has one item -- "Select All".
Menus can build a complicated structure thanks to the links of menu items.
## GMenuModel, GMenu and GMenuItem
GMenuModel is an abstact object which represents a menu.
GMenu is a simple implementation of GMenuModel and a child object of GMenuModel.
GObjct -- GMenuModel -- GMenu
Because GMenuModel is an abstract object, it doesn't have any functions to generate it.
Therefore, if you want to generate a menu, use `g_menu_new` function to generate GMenu object.
GMenu inherits all the functions of GMenuModel because of the child object.
GMenuItem is an object directly derived from GObject.
GMenuItem and Gmenu (or GMenuModel) don't have a parent-child relationship.
GObject -- GMenuModel -- GMenu
GObject -- GMenuItem
Usually, GMenuItem has attributes.
One of the attributes is label.
For example, there is a menu item which has "Edit" label in the first diagram in this section.
"Cut", "Copy", "Paste" and "Select All" are also the lables of menu items.
Other attributes will be explained later.
Some menu items have a link to another GMenu.
There are two types of links, submenu and section.
GMenuItem can be inserted, appended or prepended to GMenu.
When it is inserted, all of the attribute and link values of the item are copied and used to form a new item within the menu.
The GMenuItem itself is not really inserted.
Therefore, after the insertion, GMenuItem is useless and it should be freed.
The same goes for appending or prepending.
The following code shows how to append GMenuItem to GMenu.
GMenu *menu = g_menu_new ();
GMenuItem *menu_item_quit = g_menu_item_new ("Quit", "app.quit");
g_menu_append_item (menu, menu_item_quit);
g_object_unref (menu_item_quit);
## Menu and action
One of the attributes of menu items is an action.
This attribute points an action object.
There are two action objects, GSimpleAction and GPropertyAction.
GSimpleAction is often used.
And it is used with a menu item.
Only GSimpleAction is described in this section.
An action corresponds to a menu item will be activated when the menu item is clicked.
Then the action emits an activate signal.
1. menu item is clicked.
2. The corresponding action is activated.
3. The action emits a signal.
4. The connected handler is invoked.
The following code is an example.
static void
on_activate (GApplication *app, gpointer user_data) {
... ... ...
GSimpleAction *act_fullscreen = g_simple_action_new_stateful ("fullscreen", NULL, g_variant_new_boolean (FALSE));
GMenuItem *menu_item_fullscreen = g_menu_item_new ("Full Screen", "win.fullscreen");
g_signal_connect (act_fullscreen, "change-state", G_CALLBACK (fullscreen_changed), win);
... ... ...
}
quit_activated(GSimpleAction *action, GVariant *parameter, gpointer app) { ... ... ...}
- `act_fullscreen` is GSimpleAction.
It is generated by `g_simple_action_new_stateful`.
The function has three arguments.
The first argument "fullscreen" is the name of the action.
The second argument is a parameter type.
`NULL` means the action doesn't have a parameter.
The third argument is the initial state of the action.
It is a GVariant value.
GVariant will be explained in the next subsection.
The function `g_variant_new_boolean (FALSE)` returns a GVariant value which is the boolean value `FALSE`.
- `menu_item_fullscreen` is GMenuItem.
There are two arguments.
The first argument "Full Screen" is a label which is one of the attributes of GMenuItem.
The second argument is called detailed action.
Detailed action has three parts, prefix, action name and target.
"win.fullscreen" means that the prefix is "win", the action name is "fullscreen" and there's no target.
The prefix says that the action belongs to the window.
- connect the action `act_fullscreen` and the "change-state" signal handler `fullscreen_`value2`changed`.
If the fullscreen menu is clicked, then the corresponding action `act_fullscreen` is activated.
But no handler is connected to "activate" signal.
Then, the default behaviour for boolean-stated actions with a NULL parameter type like `act_fullscreen` is to toggle them via the “change-state” signal.
GSimpleAction *act_quit = g_simple_action_new ("quit", NULL);
g_signal_connect (act_quit, "activate", G_CALLBACK (quit_activated), app);
GMenuItem *menu_item_quit = g_menu_item_new ("Quit", "app.quit");
The following is the "change-state" signal handler.
1. `menu_item_quit` is a menu item.
It has a label "Quit" and is connected to an action "app.quit".
"app" is a prefix and "quit" is the name of an action.
The prefix means that the action belongs to GtkApplication.
If the menu is clicked, then the corresponding action "quit" which belongs to GtkApplication will be activated.
2. `act_quit` is an action.
It has a name "quit".
It belongs to GtkApplication, but it is not obvious in the code above.
The function `g_simple_action_new` generates a stateless action.
So, `act_quit` is stateless.
The meaning of stateless will be explained later.
The argument `NULL` means that the action doesn't have an parameter.
Generally, most of the actions are stateless and have no parameter.
When `act_quit` is activated, it will emit "activate" signal.
3. "activate" signal of the action is connected to the handler `quit_activated`.
So, if the action is activated, the handler will be invoked.
static void
fullscreen_changed(GSimpleAction *action, GVariant *value, gpointer win) {
if (g_variant_get_boolean (value))
gtk_window_maximize (GTK_WINDOW (win));
else
gtk_window_unmaximize (GTK_WINDOW (win));
g_simple_action_set_state (action, value);
}
## Simple example
- There are three parameters.
The first parameter is the action which emits the "change-state" signal.
The second parameter is the value of the state of the action.
But it is toggled because of no "activate" signal handler.
Ther third parameter is a user data which is set in `g_signal_connect`.
- If the value is boolean type and `TRUE`, then maximize the window.
Otherwise unmaximize.
- Set `value` to the state of the action.
Note: the second argument was the toggled state value, but at this stage the state of the action has the original value.
So, you need to set the new value by `g_simple_action_set_state`.
The following is a simple example of menus and actions.
You can use "activate" signal instead ot "change-state" signal, or both signals.
But the way above is the simplest and best.
1 #include <gtk/gtk.h>
2
3 static void
4 quit_activated(GSimpleAction *action, GVariant *parameter, gpointer app)
5 {
6 g_application_quit (G_APPLICATION(app));
7 }
8
9 static void
10 on_activate (GApplication *app, gpointer user_data) {
11 GtkWidget *win = gtk_application_window_new (GTK_APPLICATION (app));
12 gtk_window_set_title (GTK_WINDOW (win), "menu1");
13 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
14
15 GSimpleAction *act_quit = g_simple_action_new ("quit", NULL);
16 g_action_map_add_action (G_ACTION_MAP (app), G_ACTION (act_quit));
17 g_signal_connect (act_quit, "activate", G_CALLBACK (quit_activated), app);
18
19 GMenu *menubar = g_menu_new ();
20 GMenuItem *menu_item_menu = g_menu_item_new ("Menu", NULL);
21 GMenu *menu = g_menu_new ();
22 GMenuItem *menu_item_quit = g_menu_item_new ("Quit", "app.quit");
23 g_menu_append_item (menu, menu_item_quit);
24 g_object_unref (menu_item_quit);
25 g_menu_item_set_submenu (menu_item_menu, G_MENU_MODEL (menu));
26 g_menu_append_item (menubar, menu_item_menu);
27 g_object_unref (menu_item_menu);
28
29 gtk_application_set_menubar (GTK_APPLICATION (app), G_MENU_MODEL (menubar));
30 gtk_application_window_set_show_menubar (GTK_APPLICATION_WINDOW (win), TRUE);
31 gtk_window_present (GTK_WINDOW (win));
32 /* gtk_widget_show (win); is also OKay instead of gtk_window_present. */
33 }
34
35 int
36 main (int argc, char **argv) {
37 GtkApplication *app;
38 int stat;
39
40 app = gtk_application_new ("com.github.ToshioCP.menu1", G_APPLICATION_FLAGS_NONE);
41 g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
42
43 stat =g_application_run (G_APPLICATION (app), argc, argv);
44 g_object_unref (app);
45 return stat;
46 }
47
### GVariant
- 3-7: `quit_activated` is a handler of an action `act_quit`.
Handlers of actions have three parameters.
1. The action object which has emitted the signal.
2. Parameter.
In this example it is `NULL` because the second argument of `g_simple_action_new` (line 15) is `NULL`.
You don' t need to care about it.
3. User data.
It is the fourth parameter in the `g_signal_connect` (line 17) that has connected the action and the handler.
- 6: A function `g_application_quit` immediately quits the application.
- 9-33: `on_activate` is a handler of "activate" signal on GtkApplication.
- 11-13: Generate a GtkApplicationWindow and set a pointer to it to `win`. And set the title and default size.
- 15: Generate GSimpleAction `act_quit`.
It is stateless.
The first argument of `g_simple_action_new` is a name of the action and the second argument is a parameter.
If you don't need the parameter, set it `NULL`.
THerefore, `act_quit` has a name "quit" and no parameter.
- 16: Add the action to GtkApplication `app`.
GtkApplication implements an interface GActionMap and GActionGroup.
And GtkApplication can have a group of actions and actions are added by the function `g_action_map_add_action`.
This function is described in GMenuModel section in GIO API reference.
- 17: Connect "activate" signal of the action and the handler `quit_activated`.
- 19-22: Generate GMenu and GMenuItem.
`menubar` and `menu` are GMenu.
`menu_item_menu` and `menu_item_quit` are GMenuItem.
`menu_item_menu` has a label "Menu" and no action.
`menu_item_quit` has a label "Quit".
The second argument "app.quit" is a combination of "app" and "quit".
"app" is a prefix and it means that the action belongs to GtkApplication. "quit" is the name of the action.
Therefore, it points the action which belongs to GtkApplication and has the name "quit" -- it is `act_quit`.
- 23-24: Append `act_quit` to `menu`.
As I mentioned before, all the attribute and link values are copied and used to form a new item within `menu`.
Therefore after the appending, `menu` has a copy of `act_quit` in itself and `act_quit` is no longer needed.
It is freed by `g_object_unref`.
- 25: Set a submenu link to `menu_item_menu`.
And the link points the GMenu `menu`.
- 26-27: Append `menu_item_menu` to `menubar`.
Then free `menu_item_menu`.
GMenu and GMenuItem are connected and finally a menu is made up.
The structure of the menu is shown in the diagram below.
- 29: The menu is set to GtkApplication.
- 30: Set GtkApplicationWindow to show the menubar.
- 31: Show the window.
GVarient can contain boolean, string or other simple type values.
For example, the following program set TRUE to `value` whose type is GVariant.
![menu and action](image/menu1.png)
GVariant *value = g_variant_new_boolean (TRUE);
Another example is:
GVariant *value2 = g_variant_new_string ("Hello");
`value2` is a GVariant and it has a string type value "Hello".
GVariant can contain other types like int16, int32, int64, double and so on.
If you want to get the boolean value, use g\_variant\_get series functions.
gboolean bool = g_variant_get_boolean (value);
Because `value` has been generated as a boolean type GVariant and `TRUE` value, `bool` equals `TRUE`.
In the same way, you can get a string from `value2`
const gchar *str = g_variant_get_string (value2, NULL);
The second parameter is a pointer to gsize type variable (gsize is defined as unsigned long).
If it isn't NULL, then the length of the string will be set by the function.
If it is NULL, nothing happens.
The returned string `str` can't be changed.
## Stateful action with a parameter
Another example of stateful actions is an action corresponds to color select menus.
For example, there are three menus and each menu has red, green or blue color respectively.
They determine the background color of a certain widget.
One action is connected to the three menus.
The action has a state which values are "red", "green" and "blue".
The values are string.
Those colors are given to the signal handler as a parameter.
static void
on_activate (GApplication *app, gpointer user_data) {
... ... ...
GSimpleAction *act_color = g_simple_action_new_stateful ("color", g_variant_type_new("s"), g_variant_new_string ("red"));
GMenuItem *menu_item_red = g_menu_item_new ("Red", "win.color::red");
GMenuItem *menu_item_green = g_menu_item_new ("Green", "win.color::green");
GMenuItem *menu_item_blue = g_menu_item_new ("Blue", "win.color::blue");
g_signal_connect (act_color, "activate", G_CALLBACK (color_activated), win);
... ... ...
}
- `act_color` is GSimpleAction.
It is generated by `g_simple_action_new_stateful`.
The function has three arguments.
The first argument "color" is the name of the action.
The second argument is a parameter type which is GVariantType.
`g_variant_type_new("s")` generates GVariantType which is a string type (G\_VARIANT\_TYPE\_STRING).
The third argument is the initial state of the action.
It is a GVariant.
GVariantType will be explained in the next subsection.
The function `g_variant_new_string ("red")` returns a GVariant value which has the string value "red".
- `menu_item_red` is GMenuItem.
There are two arguments.
The first argument "Red" is a label which is one of the attributes of GMenuItem.
The second argument is a detailed action.
Its prefix is "win", action name is "color" and target is "red".
Target is sent to the action as a parameter.
The same goes for `menu_item_green` and `menu_item_blue`.
- connect the action `act_color` and the "activate" signal handler `color_activate`.
If one of the three menus is clicked, then the action `act_color` is activated with a parameter to which the menu item gives its target.
No handler is connected to "change-state" signal.
Then the default behaviour is to call `g_simple_action_set_state()` to set the state to the requested value.
The following is the "activate" signal handler.
static void
color_activated(GSimpleAction *action, GVariant *parameter, gpointer win) {
gchar *color = g_strdup_printf ("label#lb {background-color: %s;}", g_variant_get_string (parameter, NULL));
gtk_css_provider_load_from_data (provider, color, -1);
g_free (color);
g_action_change_state (G_ACTION (action), parameter);
}
- There are three parameters.
The first parameter is the action which emits the "activate" signal.
The second parameter is the parameter given to the action.
It is a color specified by the menu.
The third parameter is a user data which is set in `g_signal_connect`.
- `color` is a CSS string generated by `g_strdup_printf`.
The parameter of `g_str_dup` is the same as printf C standard function.
`g_variant_get_string` get the string contained in `parameter`.
- Set the color to the css provider.
- Free the string `color`.
- Change the state by `g_action_change_state`.
The function just set the parameter to the state of the action by `g_simple_action_set_state`.
Therefore, you can use `g_simple_action_set_state` instead of `g_action_change_state`.
Note: If you have set a "change-state" signal handler, `g_action_change_state` will emit "change-state" signal instead of calling `g_simple_action_set_state`.
### GVariantType
GVariantType gives a type of GVariant.
GVariant can contain many kinds of types.
And the type often needs to be recognized at runtime.
GVariantType provides such functionality.
When GVariantType is generated, the type is expressed by the string.
- "b" means boolean type.
- "s" means string type.
The following program is a simple example.
It finally output the string "s".
1 #include <glib.h>
2
3 int
4 main (int argc, char **argv) {
5 GVariantType *vtype = g_variant_type_new ("s");
6 const gchar *type_string = g_variant_type_peek_string (vtype);
7 g_print ("%s\n",type_string);
8 }
- `g_variant_tpe_new` generates GVariantType.
It uses a type string "s" which means string.
- `g_variant_type_peek_string` takes a peek at `vtype`.
It is the string "s" given at the generation time.
- print the string to the terminal.
## Example code
The following code includes stateful actions above.
This program has menus like this:
![menu2](image/menu2.png)
- Fullscreen menu toggles the size of the window between maximum and non-maximum.
If the window is maximum size, which is called full screen, then a check mark is put before "fullscreen" label.
- Red, green and blue menu determines the back ground color of the label, which is the child widget of the window.
The menus have radio buttons on the left of each of the menus.
And the radio button of the selected menu turns on.
- Quit menu quits the application.
The code is as follows.
1 #include <gtk/gtk.h>
2
3 static GtkCssProvider *provider;
4
5 static void
6 fullscreen_changed(GSimpleAction *action, GVariant *value, gpointer win) {
7 if (g_variant_get_boolean (value))
8 gtk_window_maximize (GTK_WINDOW (win));
9 else
10 gtk_window_unmaximize (GTK_WINDOW (win));
11 g_simple_action_set_state (action, value);
12 }
13
14 static void
15 color_activated(GSimpleAction *action, GVariant *parameter, gpointer win) {
16 gchar *color = g_strdup_printf ("label#lb {background-color: %s;}", g_variant_get_string (parameter, NULL));
17 gtk_css_provider_load_from_data (provider, color, -1);
18 g_free (color);
19 g_action_change_state (G_ACTION (action), parameter);
20 }
21
22 static void
23 quit_activated(GSimpleAction *action, GVariant *parameter, gpointer app)
24 {
25 g_application_quit (G_APPLICATION(app));
26 }
27
28 static void
29 on_activate (GApplication *app, gpointer user_data) {
30 GtkWidget *win = gtk_application_window_new (GTK_APPLICATION (app));
31 gtk_window_set_title (GTK_WINDOW (win), "menu2");
32 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
33
34 GtkWidget *lb = gtk_label_new (NULL);
35 gtk_widget_set_name (lb, "lb"); /* the name is used by CSS Selector */
36 gtk_window_set_child (GTK_WINDOW (win), lb);
37
38 GSimpleAction *act_fullscreen
39 = g_simple_action_new_stateful ("fullscreen", NULL, g_variant_new_boolean (FALSE));
40 GSimpleAction *act_color
41 = g_simple_action_new_stateful ("color", g_variant_type_new("s"), g_variant_new_string ("red"));
42 GSimpleAction *act_quit
43 = g_simple_action_new ("quit", NULL);
44
45 GMenu *menubar = g_menu_new ();
46 GMenu *menu = g_menu_new ();
47 GMenu *section1 = g_menu_new ();
48 GMenu *section2 = g_menu_new ();
49 GMenu *section3 = g_menu_new ();
50 GMenuItem *menu_item_fullscreen = g_menu_item_new ("Full Screen", "win.fullscreen");
51 GMenuItem *menu_item_red = g_menu_item_new ("Red", "win.color::red");
52 GMenuItem *menu_item_green = g_menu_item_new ("Green", "win.color::green");
53 GMenuItem *menu_item_blue = g_menu_item_new ("Blue", "win.color::blue");
54 GMenuItem *menu_item_quit = g_menu_item_new ("Quit", "app.quit");
55
56 g_signal_connect (act_fullscreen, "change-state", G_CALLBACK (fullscreen_changed), win);
57 g_signal_connect (act_color, "activate", G_CALLBACK (color_activated), win);
58 g_signal_connect (act_quit, "activate", G_CALLBACK (quit_activated), app);
59 g_action_map_add_action (G_ACTION_MAP (win), G_ACTION (act_fullscreen));
60 g_action_map_add_action (G_ACTION_MAP (win), G_ACTION (act_color));
61 g_action_map_add_action (G_ACTION_MAP (app), G_ACTION (act_quit));
62
63 g_menu_append_item (section1, menu_item_fullscreen);
64 g_menu_append_item (section2, menu_item_red);
65 g_menu_append_item (section2, menu_item_green);
66 g_menu_append_item (section2, menu_item_blue);
67 g_menu_append_item (section3, menu_item_quit);
68 g_object_unref (menu_item_red);
69 g_object_unref (menu_item_green);
70 g_object_unref (menu_item_blue);
71 g_object_unref (menu_item_fullscreen);
72 g_object_unref (menu_item_quit);
73
74 g_menu_append_section (menu, NULL, G_MENU_MODEL (section1));
75 g_menu_append_section (menu, "Color", G_MENU_MODEL (section2));
76 g_menu_append_section (menu, NULL, G_MENU_MODEL (section3));
77 g_menu_append_submenu (menubar, "Menu", G_MENU_MODEL (menu));
78
79 gtk_application_set_menubar (GTK_APPLICATION (app), G_MENU_MODEL (menubar));
80 gtk_application_window_set_show_menubar (GTK_APPLICATION_WINDOW (win), TRUE);
81
82 /* GtkCssProvider *provider = gtk_css_provider_new ();*/
83 provider = gtk_css_provider_new ();
84 GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (win));
85 gtk_css_provider_load_from_data (provider, "label#lb {background-color: red;}", -1);
86 gtk_style_context_add_provider_for_display (display, GTK_STYLE_PROVIDER (provider),
87 GTK_STYLE_PROVIDER_PRIORITY_USER);
88
89 /* gtk_widget_show (win);*/
90 gtk_window_present (GTK_WINDOW (win));
91 }
92
93 int
94 main (int argc, char **argv) {
95 GtkApplication *app;
96 int stat;
97
98 app = gtk_application_new ("com.github.ToshioCP.menu2", G_APPLICATION_FLAGS_NONE);
99 g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
100
101 stat =g_application_run (G_APPLICATION (app), argc, argv);
102 g_object_unref (app);
103 return stat;
104 }
105
- 5-26: Signal handlers.
They have been explained in this section.
- 30-36: `win` and `lb` are GtkApplicationWindow and GtkLabel respectively.
`win` has a title "menu2" and its defaust size is 400x300.
`lb` is named as "lb".
The name is used in CSS.
`lb` is set to `win` as a child.
- 38-43: Three actions are defined.
They are:
- stateful and has no parameter.
It has a toggle state.
- stateful and has a parameter.
Parameter is a string type.
- stateless and has no parameter.
- 45-54: Generate GMenu and GMenuItem.
There are three sections.
- 56-61: Signals are connected to handlers.
And actions are added to GActionMap.
Because `act_fullscreen` and `act_color` have "win" prefix and belong to GtkApplicationWindow,
they are added to `win`.
GtkApplicationWindow implements GActionModel interface like GtkApplication.
`act_quit` has "app" prefix and belongs to GtkApplication,
it is added to `app`.
- 63-77: Connect and build the menus.
Useless GMenuItem are freed.
- 79-80: GMenuModel `menubar` is set to `app`.
Set show menubar property to `TRUE` in `win`.
Note: `gtk_application_window_set_show_menubar` generates GtkPopoverMenubar from GMenuModel.
This is a different point between Gtk3 and Gtk4.
And you can use GtkPopoverMenubar directly and set it as a descendant widget of the window.
You may use GtkBox as a child widget of the window and set GtkPopoverMenubar as the first child of the box.
- 82-87: Set CSS.
`provider` is GtkCssProvider which is defined in line three as a static variable.
Its CSS data is:
`label#lb {background-color: red;}`.
"label#lb" is called selector.
"label" is the node of GtkLabel.
"#" precedes an ID which is an identiable name of the widget.
"lb" is the name of GtkLabel `lb`.
(See line 35).
The style is surrounded by open and close braces.
The style is applied to GtkLabel which has a name "lb".
Other GtkLabel have no effect from this.
The provider is added to GdkDisplay.
- 90: Show the window.
![Screenshot of menu1](image/menu1_screenshot.png)
Up: [Readme.md](Readme.md), Prev: [Section 14](sec14.md), Next: [Section 16](sec16.md)

601
sec16.md
View file

@ -1,310 +1,310 @@
Up: [Readme.md](Readme.md), Prev: [Section 15](sec15.md), Next: [Section 17](sec17.md)
# Ui file for menu and action entries
# Stateful action
## Ui file for menu
Some actions have states.
The values of states can be boolean or string.
Actions which have states are called stateful.
You might have thought that building menus is really bothersome.
Yes, the program was complicated and it needs lots of time to code it.
The situation is similar to building widgets.
When we built widgets, using ui file was a good way to avoid such complicated coding.
The same goes for menus.
## Stateful action without a parameter
The ui file for menus has interface, menu tags.
The file starts and ends with interface tag.
Some menus are called toggle menu.
For example, fullscreen menu has a state which has two values -- fullscreen and non-fullscreen.
The value of the state is changed every time the menu is clicked.
An action corresponds to the fullscreen menu also have a state.
Its value is TRUE or FALSE and it is called boolean value.
TRUE corresponds to fullscreen and FALSE to non-fullscreen.
<interface>
<menu id="menubar">
</menu>
</interface>
The following is an example code to implement a fullscreen menu except the signal handler.
The signal handler will be described after the explanation of this code.
`menu` tag corresponds to GMenu object.
`id` attribute defines the name of the object.
It will be refered by GtkBuilder.
static void
on_activate (GApplication *app, gpointer user_data) {
... ... ...
GSimpleAction *act_fullscreen = g_simple_action_new_stateful ("fullscreen", NULL, g_variant_new_boolean (FALSE));
GMenuItem *menu_item_fullscreen = g_menu_item_new ("Full Screen", "win.fullscreen");
g_signal_connect (act_fullscreen, "change-state", G_CALLBACK (fullscreen_changed), win);
... ... ...
}
<submenu>
<attribute name="label">File</attribute>
<item>
<attribute name="label">New</attribute>
<attribute name="action">win.new</attribute>
</item>
</submenu>
- `act_fullscreen` is GSimpleAction.
It is generated by `g_simple_action_new_stateful`.
The function has three arguments.
The first argument "fullscreen" is the name of the action.
The second argument is a parameter type.
`NULL` means the action doesn't have a parameter.
The third argument is the initial state of the action.
It is a GVariant value.
GVariant will be explained in the next subsection.
The function `g_variant_new_boolean (FALSE)` returns a GVariant value which is the boolean value `FALSE`.
- `menu_item_fullscreen` is GMenuItem.
There are two arguments.
The first argument "Full Screen" is a label which is one of the attributes of GMenuItem.
The second argument is called detailed action.
Detailed action has three parts, prefix, action name and target.
"win.fullscreen" means that the prefix is "win", the action name is "fullscreen" and there's no target.
The prefix says that the action belongs to the window.
- connect the action `act_fullscreen` and the "change-state" signal handler `fullscreen_`value2`changed`.
If the fullscreen menu is clicked, then the corresponding action `act_fullscreen` is activated.
But no handler is connected to "activate" signal.
Then, the default behaviour for boolean-stated actions with a NULL parameter type like `act_fullscreen` is to toggle them via the “change-state” signal.
`item` tag corresponds to item in GMenu which has the same structure as GMenuItem.
The item above has a label attribute.
Its value is "New".
The item also has an action attribute and its value is "win.new".
"win" is a prefix and "new" is an action name.
`submenu` tag corresponds to both GMenuItem and GMenu.
The GMenuItem has a link to GMenu.
The following is the "change-state" signal handler.
The ui file above can be described as follows.
static void
fullscreen_changed(GSimpleAction *action, GVariant *value, gpointer win) {
if (g_variant_get_boolean (value))
gtk_window_maximize (GTK_WINDOW (win));
else
gtk_window_unmaximize (GTK_WINDOW (win));
g_simple_action_set_state (action, value);
}
<item>
<attribute name="label">File</attribute>
<link name="submenu">
<item>
<attribute name="label">New</attribute>
<attribute name="action">win.new</attribute>
</item>
</link>
</item>
- There are three parameters.
The first parameter is the action which emits the "change-state" signal.
The second parameter is the value of the state of the action.
But it is toggled because of no "activate" signal handler.
Ther third parameter is a user data which is set in `g_signal_connect`.
- If the value is boolean type and `TRUE`, then maximize the window.
Otherwise unmaximize.
- Set `value` to the state of the action.
Note: the second argument was the toggled state value, but at this stage the state of the action has the original value.
So, you need to set the new value by `g_simple_action_set_state`.
`link` tag expresses the link to submenu.
And at the same time it also expresses the submenu itself.
This file illustrates the relationship between the menus and items better than the prior ui file.
But `submenu` tag is simple and easy to understand.
So, we usually prefer the former ui file style.
You can use "activate" signal instead ot "change-state" signal, or both signals.
But the way above is the simplest and best.
The following is a screenshot of the sample program in this section.
Its name is `menu3`.
### GVariant
![menu3](image/menu3.png)
GVarient can contain boolean, string or other simple type values.
For example, the following program set TRUE to `value` whose type is GVariant.
The following is the ui file of the menu in `menu3`.
GVariant *value = g_variant_new_boolean (TRUE);
1 <?xml version="1.0" encoding="UTF-8"?>
2 <interface>
3 <menu id="menubar">
4 <submenu>
5 <attribute name="label">File</attribute>
6 <section>
7 <item>
8 <attribute name="label">New</attribute>
9 <attribute name="action">win.new</attribute>
10 </item>
11 <item>
12 <attribute name="label">Open</attribute>
13 <attribute name="action">win.open</attribute>
14 </item>
15 </section>
16 <section>
17 <item>
18 <attribute name="label">Save</attribute>
19 <attribute name="action">win.save</attribute>
20 </item>
21 <item>
22 <attribute name="label">Save As…</attribute>
23 <attribute name="action">win.saveas</attribute>
24 </item>
25 </section>
26 <section>
27 <item>
28 <attribute name="label">Close</attribute>
29 <attribute name="action">win.close</attribute>
30 </item>
31 </section>
32 <section>
33 <item>
34 <attribute name="label">Quit</attribute>
35 <attribute name="action">app.quit</attribute>
36 </item>
37 </section>
38 </submenu>
39 <submenu>
40 <attribute name="label">Edit</attribute>
41 <section>
42 <item>
43 <attribute name="label">Cut</attribute>
44 <attribute name="action">win.cut</attribute>
45 </item>
46 <item>
47 <attribute name="label">Copy</attribute>
48 <attribute name="action">win.copy</attribute>
49 </item>
50 <item>
51 <attribute name="label">Paste</attribute>
52 <attribute name="action">win.paste</attribute>
53 </item>
54 </section>
55 <section>
56 <item>
57 <attribute name="label">Select All</attribute>
58 <attribute name="action">win.selectall</attribute>
59 </item>
60 </section>
61 </submenu>
62 <submenu>
63 <attribute name="label">View</attribute>
64 <section>
65 <item>
66 <attribute name="label">Full Screen</attribute>
67 <attribute name="action">win.fullscreen</attribute>
68 </item>
69 </section>
70 </submenu>
71 </menu>
72 </interface>
Another example is:
The ui file is converted to the resource by the resouce compiler `glib-compile-resouces` with xml file below.
GVariant *value2 = g_variant_new_string ("Hello");
1 <?xml version="1.0" encoding="UTF-8"?>
2 <gresources>
3 <gresource prefix="/com/github/ToshioCP/menu3">
4 <file>menu3.ui</file>
5 </gresource>
6 </gresources>
`value2` is a GVariant and it has a string type value "Hello".
GVariant can contain other types like int16, int32, int64, double and so on.
GtkBuilder builds menus from the resource.
If you want to get the boolean value, use g\_variant\_get series functions.
GtkBuilder *builder = gtk_builder_new_from_resource ("/com/github/ToshioCP/menu3/menu3.ui");
GMenuModel *menubar = G_MENU_MODEL (gtk_builder_get_object (builder, "menubar"));
gboolean bool = g_variant_get_boolean (value);
gtk_application_set_menubar (GTK_APPLICATION (app), menubar);
g_object_unref (builder);
Because `value` has been generated as a boolean type GVariant and `TRUE` value, `bool` equals `TRUE`.
In the same way, you can get a string from `value2`
It is important that `builder` is unreferred after the GMenuModel `menubar` is set to the application.
If you do it before setting, bad thing will happen -- your computer might freeze.
const gchar *str = g_variant_get_string (value2, NULL);
## Action entry
The second parameter is a pointer to gsize type variable (gsize is defined as unsigned long).
If it isn't NULL, then the length of the string will be set by the function.
If it is NULL, nothing happens.
The returned string `str` can't be changed.
The coding for building actions and signal handlers is always the same.
Therefore, it can be automated.
You can implement them easily with GActionEntry `g_action_map_add_action_entries`.
## Stateful action with a parameter
GActionEntry is a strutcure.
It contains action name, signal handlers, parameter and state.
Another example of stateful actions is an action corresponds to color select menus.
For example, there are three menus and each menu has red, green or blue color respectively.
They determine the background color of a certain widget.
One action is connected to the three menus.
The action has a state which values are "red", "green" and "blue".
The values are string.
Those colors are given to the signal handler as a parameter.
typedef struct _GActionEntry GActionEntry;
static void
on_activate (GApplication *app, gpointer user_data) {
... ... ...
GSimpleAction *act_color = g_simple_action_new_stateful ("color", g_variant_type_new("s"), g_variant_new_string ("red"));
GMenuItem *menu_item_red = g_menu_item_new ("Red", "win.color::red");
GMenuItem *menu_item_green = g_menu_item_new ("Green", "win.color::green");
GMenuItem *menu_item_blue = g_menu_item_new ("Blue", "win.color::blue");
g_signal_connect (act_color, "activate", G_CALLBACK (color_activated), win);
... ... ...
}
struct _GActionEntry
{
const gchar *name; /* action name */
void (* activate) (GSimpleAction *action, GVariant *parameter, gpointer user_data); /* activate handler */
const gchar *parameter_type; /* the type of the parameter given as a single GVariant type string */
const gchar *state; /* initial state given in GVariant text format */
void (* change_state) (GSimpleAction *action, GVariant *value, gpointer user_data); /* change-state handler */
/*< private >*/
gsize padding[3];
};
- `act_color` is GSimpleAction.
It is generated by `g_simple_action_new_stateful`.
The function has three arguments.
The first argument "color" is the name of the action.
The second argument is a parameter type which is GVariantType.
`g_variant_type_new("s")` generates GVariantType which is a string type (G\_VARIANT\_TYPE\_STRING).
The third argument is the initial state of the action.
It is a GVariant.
GVariantType will be explained in the next subsection.
The function `g_variant_new_string ("red")` returns a GVariant value which has the string value "red".
- `menu_item_red` is GMenuItem.
There are two arguments.
The first argument "Red" is a label which is one of the attributes of GMenuItem.
The second argument is a detailed action.
Its prefix is "win", action name is "color" and target is "red".
Target is sent to the action as a parameter.
The same goes for `menu_item_green` and `menu_item_blue`.
- connect the action `act_color` and the "activate" signal handler `color_activate`.
If one of the three menus is clicked, then the action `act_color` is activated with a parameter to which the menu item gives its target.
No handler is connected to "change-state" signal.
Then the default behaviour is to call `g_simple_action_set_state()` to set the state to the requested value.
For example, the actions in the previous section are:
The following is the "activate" signal handler.
{ "fullscreen", NULL, NULL, "false", fullscreen_changed }
{ "color", color_activated, "s", "red", NULL }
{ "quit", quit_activated, NULL, NULL, NULL },
static void
color_activated(GSimpleAction *action, GVariant *parameter, gpointer win) {
gchar *color = g_strdup_printf ("label#lb {background-color: %s;}", g_variant_get_string (parameter, NULL));
gtk_css_provider_load_from_data (provider, color, -1);
g_free (color);
g_action_change_state (G_ACTION (action), parameter);
}
And `g_action_map_add_action_entries` does all the process instead of the functions you have needed.
- There are three parameters.
The first parameter is the action which emits the "activate" signal.
The second parameter is the parameter given to the action.
It is a color specified by the menu.
The third parameter is a user data which is set in `g_signal_connect`.
- `color` is a CSS string generated by `g_strdup_printf`.
The parameter of `g_str_dup` is the same as printf C standard function.
`g_variant_get_string` get the string contained in `parameter`.
- Set the color to the css provider.
- Free the string `color`.
- Change the state by `g_action_change_state`.
The function just set the parameter to the state of the action by `g_simple_action_set_state`.
Therefore, you can use `g_simple_action_set_state` instead of `g_action_change_state`.
const GActionEntry app_entries[] = {
{ "quit", quit_activated, NULL, NULL, NULL }
};
g_action_map_add_action_entries (G_ACTION_MAP (app), app_entries, G_N_ELEMENTS (app_entries), app);
Note: If you have set a "change-state" signal handler, `g_action_change_state` will emit "change-state" signal instead of calling `g_simple_action_set_state`.
The code above does:
### GVariantType
- Build the "quit" action
- Connect the action and the "activate" signal handler `quit_activate`
- Add the action to the action map `app`.
GVariantType gives a type of GVariant.
GVariant can contain many kinds of types.
And the type often needs to be recognized at runtime.
GVariantType provides such functionality.
const GActionEntry win_entries[] = {
{ "fullscreen", NULL, NULL, "false", fullscreen_changed },
{ "color", color_activated, "s", "red", NULL }
};
g_action_map_add_action_entries (G_ACTION_MAP (win), win_entries, G_N_ELEMENTS (win_entries), win);
When GVariantType is generated, the type is expressed by the string.
The code above does:
- "b" means boolean type.
- "s" means string type.
- Build the "fullscreen" action and "color" action.
- Connect the "fullscreen" action and the "change-state" signal handler `fullscreen_changed`
- Its initial state is set to FALSE.
- Connect the "color" action and the "activate" signal handler `color_activate`
- Its parameter type is string and the initial value is "red".
- Add the action to the action map `win`.
The following program is a simple example.
It finally output the string "s".
1 #include <glib.h>
2
3 int
4 main (int argc, char **argv) {
5 GVariantType *vtype = g_variant_type_new ("s");
6 const gchar *type_string = g_variant_type_peek_string (vtype);
7 g_print ("%s\n",type_string);
8 }
- `g_variant_tpe_new` generates GVariantType.
It uses a type string "s" which means string.
- `g_variant_type_peek_string` takes a peek at `vtype`.
It is the string "s" given at the generation time.
- print the string to the terminal.
## Example code
The following code includes stateful actions above.
This program has menus like this:
The C source code of `menu3` and `meson.build` is as follows.
![menu2](image/menu2.png)
- Fullscreen menu toggles the size of the window between maximum and non-maximum.
If the window is maximum size, which is called full screen, then a check mark is put before "fullscreen" label.
- Red, green and blue menu determines the back ground color of the label, which is the child widget of the window.
The menus have radio buttons on the left of each of the menus.
And the radio button of the selected menu turns on.
- Quit menu quits the application.
The code is as follows.
1 #include <gtk/gtk.h>
2
3 static void
4 new_activated (GSimpleAction *action, GVariant *parameter, gpointer win) {
5 }
6
7 static void
8 open_activated (GSimpleAction *action, GVariant *parameter, gpointer win) {
9 }
10
11 static void
12 save_activated (GSimpleAction *action, GVariant *parameter, gpointer win) {
13 }
14
15 static void
16 saveas_activated (GSimpleAction *action, GVariant *parameter, gpointer win) {
17 }
18
19 static void
20 close_activated (GSimpleAction *action, GVariant *parameter, gpointer win) {
21 }
22
23 static void
24 cut_activated (GSimpleAction *action, GVariant *parameter, gpointer win) {
25 }
26
27 static void
28 copy_activated (GSimpleAction *action, GVariant *parameter, gpointer win) {
29 }
30
31 static void
32 paste_activated (GSimpleAction *action, GVariant *parameter, gpointer win) {
33 }
34
35 static void
36 selectall_activated (GSimpleAction *action, GVariant *parameter, gpointer win) {
37 }
38
39 static void
40 fullscreen_changed (GSimpleAction *action, GVariant *state, gpointer win) {
41 if (g_variant_get_boolean (state))
42 gtk_window_maximize (GTK_WINDOW (win));
43 else
44 gtk_window_unmaximize (GTK_WINDOW (win));
45 g_simple_action_set_state (action, state);
46 }
47
48 static void
49 quit_activated (GSimpleAction *action, GVariant *parameter, gpointer app)
50 {
51 g_application_quit (G_APPLICATION(app));
52 }
53
54 static void
55 on_activate (GApplication *app, gpointer user_data) {
56 GtkWidget *win = gtk_application_window_new (GTK_APPLICATION (app));
57
58 const GActionEntry win_entries[] = {
59 { "new", new_activated, NULL, NULL, NULL },
60 { "open", open_activated, NULL, NULL, NULL },
61 { "save", save_activated, NULL, NULL, NULL },
62 { "saveas", saveas_activated, NULL, NULL, NULL },
63 { "close", close_activated, NULL, NULL, NULL },
64 { "cut", cut_activated, NULL, NULL, NULL },
65 { "copy", copy_activated, NULL, NULL, NULL },
66 { "paste", paste_activated, NULL, NULL, NULL },
67 { "selectall", selectall_activated, NULL, NULL, NULL },
68 { "fullscreen", NULL, NULL, "false", fullscreen_changed }
69 };
70 g_action_map_add_action_entries (G_ACTION_MAP (win), win_entries, G_N_ELEMENTS (win_entries), win);
71
72 gtk_application_window_set_show_menubar (GTK_APPLICATION_WINDOW (win), TRUE);
3 static GtkCssProvider *provider;
4
5 static void
6 fullscreen_changed(GSimpleAction *action, GVariant *value, gpointer win) {
7 if (g_variant_get_boolean (value))
8 gtk_window_maximize (GTK_WINDOW (win));
9 else
10 gtk_window_unmaximize (GTK_WINDOW (win));
11 g_simple_action_set_state (action, value);
12 }
13
14 static void
15 color_activated(GSimpleAction *action, GVariant *parameter, gpointer win) {
16 gchar *color = g_strdup_printf ("label#lb {background-color: %s;}", g_variant_get_string (parameter, NULL));
17 gtk_css_provider_load_from_data (provider, color, -1);
18 g_free (color);
19 g_action_change_state (G_ACTION (action), parameter);
20 }
21
22 static void
23 quit_activated(GSimpleAction *action, GVariant *parameter, gpointer app)
24 {
25 g_application_quit (G_APPLICATION(app));
26 }
27
28 static void
29 on_activate (GApplication *app, gpointer user_data) {
30 GtkWidget *win = gtk_application_window_new (GTK_APPLICATION (app));
31 gtk_window_set_title (GTK_WINDOW (win), "menu2");
32 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
33
34 GtkWidget *lb = gtk_label_new (NULL);
35 gtk_widget_set_name (lb, "lb"); /* the name is used by CSS Selector */
36 gtk_window_set_child (GTK_WINDOW (win), lb);
37
38 GSimpleAction *act_fullscreen
39 = g_simple_action_new_stateful ("fullscreen", NULL, g_variant_new_boolean (FALSE));
40 GSimpleAction *act_color
41 = g_simple_action_new_stateful ("color", g_variant_type_new("s"), g_variant_new_string ("red"));
42 GSimpleAction *act_quit
43 = g_simple_action_new ("quit", NULL);
44
45 GMenu *menubar = g_menu_new ();
46 GMenu *menu = g_menu_new ();
47 GMenu *section1 = g_menu_new ();
48 GMenu *section2 = g_menu_new ();
49 GMenu *section3 = g_menu_new ();
50 GMenuItem *menu_item_fullscreen = g_menu_item_new ("Full Screen", "win.fullscreen");
51 GMenuItem *menu_item_red = g_menu_item_new ("Red", "win.color::red");
52 GMenuItem *menu_item_green = g_menu_item_new ("Green", "win.color::green");
53 GMenuItem *menu_item_blue = g_menu_item_new ("Blue", "win.color::blue");
54 GMenuItem *menu_item_quit = g_menu_item_new ("Quit", "app.quit");
55
56 g_signal_connect (act_fullscreen, "change-state", G_CALLBACK (fullscreen_changed), win);
57 g_signal_connect (act_color, "activate", G_CALLBACK (color_activated), win);
58 g_signal_connect (act_quit, "activate", G_CALLBACK (quit_activated), app);
59 g_action_map_add_action (G_ACTION_MAP (win), G_ACTION (act_fullscreen));
60 g_action_map_add_action (G_ACTION_MAP (win), G_ACTION (act_color));
61 g_action_map_add_action (G_ACTION_MAP (app), G_ACTION (act_quit));
62
63 g_menu_append_item (section1, menu_item_fullscreen);
64 g_menu_append_item (section2, menu_item_red);
65 g_menu_append_item (section2, menu_item_green);
66 g_menu_append_item (section2, menu_item_blue);
67 g_menu_append_item (section3, menu_item_quit);
68 g_object_unref (menu_item_red);
69 g_object_unref (menu_item_green);
70 g_object_unref (menu_item_blue);
71 g_object_unref (menu_item_fullscreen);
72 g_object_unref (menu_item_quit);
73
74 gtk_window_set_title (GTK_WINDOW (win), "menu3");
75 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
76 gtk_widget_show (win);
77 }
74 g_menu_append_section (menu, NULL, G_MENU_MODEL (section1));
75 g_menu_append_section (menu, "Color", G_MENU_MODEL (section2));
76 g_menu_append_section (menu, NULL, G_MENU_MODEL (section3));
77 g_menu_append_submenu (menubar, "Menu", G_MENU_MODEL (menu));
78
79 static void
80 on_startup (GApplication *app, gpointer user_data) {
81 GtkBuilder *builder = gtk_builder_new_from_resource ("/com/github/ToshioCP/menu3/menu3.ui");
82 GMenuModel *menubar = G_MENU_MODEL (gtk_builder_get_object (builder, "menubar"));
83
84 gtk_application_set_menubar (GTK_APPLICATION (app), menubar);
85 g_object_unref (builder);
86
87 const GActionEntry app_entries[] = {
88 { "quit", quit_activated, NULL, NULL, NULL }
89 };
90 g_action_map_add_action_entries (G_ACTION_MAP (app), app_entries, G_N_ELEMENTS (app_entries), app);
79 gtk_application_set_menubar (GTK_APPLICATION (app), G_MENU_MODEL (menubar));
80 gtk_application_window_set_show_menubar (GTK_APPLICATION_WINDOW (win), TRUE);
81
82 /* GtkCssProvider *provider = gtk_css_provider_new ();*/
83 provider = gtk_css_provider_new ();
84 GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (win));
85 gtk_css_provider_load_from_data (provider, "label#lb {background-color: red;}", -1);
86 gtk_style_context_add_provider_for_display (display, GTK_STYLE_PROVIDER (provider),
87 GTK_STYLE_PROVIDER_PRIORITY_USER);
88
89 /* gtk_widget_show (win);*/
90 gtk_window_present (GTK_WINDOW (win));
91 }
92
93 int
@ -312,26 +312,59 @@ The C source code of `menu3` and `meson.build` is as follows.
95 GtkApplication *app;
96 int stat;
97
98 app = gtk_application_new ("com.github.ToshioCP.menu3", G_APPLICATION_FLAGS_NONE);
99 g_signal_connect (app, "startup", G_CALLBACK (on_startup), NULL);
100 g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
101
102 stat =g_application_run (G_APPLICATION (app), argc, argv);
103 g_object_unref (app);
104 return stat;
105 }
106
98 app = gtk_application_new ("com.github.ToshioCP.menu2", G_APPLICATION_FLAGS_NONE);
99 g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
100
101 stat =g_application_run (G_APPLICATION (app), argc, argv);
102 g_object_unref (app);
103 return stat;
104 }
105
meson.build
- 5-26: Signal handlers.
They have been explained in this section.
- 30-36: `win` and `lb` are GtkApplicationWindow and GtkLabel respectively.
`win` has a title "menu2" and its defaust size is 400x300.
`lb` is named as "lb".
The name is used in CSS.
`lb` is set to `win` as a child.
- 38-43: Three actions are defined.
They are:
- stateful and has no parameter.
It has a toggle state.
- stateful and has a parameter.
Parameter is a string type.
- stateless and has no parameter.
- 45-54: Generate GMenu and GMenuItem.
There are three sections.
- 56-61: Signals are connected to handlers.
And actions are added to GActionMap.
Because `act_fullscreen` and `act_color` have "win" prefix and belong to GtkApplicationWindow,
they are added to `win`.
GtkApplicationWindow implements GActionModel interface like GtkApplication.
`act_quit` has "app" prefix and belongs to GtkApplication,
it is added to `app`.
- 63-77: Connect and build the menus.
Useless GMenuItem are freed.
- 79-80: GMenuModel `menubar` is set to `app`.
Set show menubar property to `TRUE` in `win`.
Note: `gtk_application_window_set_show_menubar` generates GtkPopoverMenubar from GMenuModel.
This is a different point between Gtk3 and Gtk4.
And you can use GtkPopoverMenubar directly and set it as a descendant widget of the window.
You may use GtkBox as a child widget of the window and set GtkPopoverMenubar as the first child of the box.
- 82-87: Set CSS.
`provider` is GtkCssProvider which is defined in line three as a static variable.
Its CSS data is:
`label#lb {background-color: red;}`.
"label#lb" is called selector.
"label" is the node of GtkLabel.
"#" precedes an ID which is an identiable name of the widget.
"lb" is the name of GtkLabel `lb`.
(See line 35).
The style is surrounded by open and close braces.
The style is applied to GtkLabel which has a name "lb".
Other GtkLabel have no effect from this.
The provider is added to GdkDisplay.
- 90: Show the window.
1 project('menu3', 'c')
2
3 gtkdep = dependency('gtk4')
4
5 gnome=import('gnome')
6 resources = gnome.compile_resources('resources','menu3.gresource.xml')
7
8 sourcefiles=files('menu3.c')
9
10 executable('menu3', sourcefiles, resources, dependencies: gtkdep)
Up: [Readme.md](Readme.md), Prev: [Section 15](sec15.md), Next: [Section 17](sec17.md)

335
sec17.md
View file

@ -1,6 +1,337 @@
Up: [Readme.md](Readme.md), Prev: [Section 16](sec16.md)
# GtkMenuButton
# Ui file for menu and action entries
before close
## Ui file for menu
You might have thought that building menus is really bothersome.
Yes, the program was complicated and it needs lots of time to code it.
The situation is similar to building widgets.
When we built widgets, using ui file was a good way to avoid such complicated coding.
The same goes for menus.
The ui file for menus has interface, menu tags.
The file starts and ends with interface tag.
<interface>
<menu id="menubar">
</menu>
</interface>
`menu` tag corresponds to GMenu object.
`id` attribute defines the name of the object.
It will be refered by GtkBuilder.
<submenu>
<attribute name="label">File</attribute>
<item>
<attribute name="label">New</attribute>
<attribute name="action">win.new</attribute>
</item>
</submenu>
`item` tag corresponds to item in GMenu which has the same structure as GMenuItem.
The item above has a label attribute.
Its value is "New".
The item also has an action attribute and its value is "win.new".
"win" is a prefix and "new" is an action name.
`submenu` tag corresponds to both GMenuItem and GMenu.
The GMenuItem has a link to GMenu.
The ui file above can be described as follows.
<item>
<attribute name="label">File</attribute>
<link name="submenu">
<item>
<attribute name="label">New</attribute>
<attribute name="action">win.new</attribute>
</item>
</link>
</item>
`link` tag expresses the link to submenu.
And at the same time it also expresses the submenu itself.
This file illustrates the relationship between the menus and items better than the prior ui file.
But `submenu` tag is simple and easy to understand.
So, we usually prefer the former ui file style.
The following is a screenshot of the sample program in this section.
Its name is `menu3`.
![menu3](image/menu3.png)
The following is the ui file of the menu in `menu3`.
1 <?xml version="1.0" encoding="UTF-8"?>
2 <interface>
3 <menu id="menubar">
4 <submenu>
5 <attribute name="label">File</attribute>
6 <section>
7 <item>
8 <attribute name="label">New</attribute>
9 <attribute name="action">win.new</attribute>
10 </item>
11 <item>
12 <attribute name="label">Open</attribute>
13 <attribute name="action">win.open</attribute>
14 </item>
15 </section>
16 <section>
17 <item>
18 <attribute name="label">Save</attribute>
19 <attribute name="action">win.save</attribute>
20 </item>
21 <item>
22 <attribute name="label">Save As…</attribute>
23 <attribute name="action">win.saveas</attribute>
24 </item>
25 </section>
26 <section>
27 <item>
28 <attribute name="label">Close</attribute>
29 <attribute name="action">win.close</attribute>
30 </item>
31 </section>
32 <section>
33 <item>
34 <attribute name="label">Quit</attribute>
35 <attribute name="action">app.quit</attribute>
36 </item>
37 </section>
38 </submenu>
39 <submenu>
40 <attribute name="label">Edit</attribute>
41 <section>
42 <item>
43 <attribute name="label">Cut</attribute>
44 <attribute name="action">win.cut</attribute>
45 </item>
46 <item>
47 <attribute name="label">Copy</attribute>
48 <attribute name="action">win.copy</attribute>
49 </item>
50 <item>
51 <attribute name="label">Paste</attribute>
52 <attribute name="action">win.paste</attribute>
53 </item>
54 </section>
55 <section>
56 <item>
57 <attribute name="label">Select All</attribute>
58 <attribute name="action">win.selectall</attribute>
59 </item>
60 </section>
61 </submenu>
62 <submenu>
63 <attribute name="label">View</attribute>
64 <section>
65 <item>
66 <attribute name="label">Full Screen</attribute>
67 <attribute name="action">win.fullscreen</attribute>
68 </item>
69 </section>
70 </submenu>
71 </menu>
72 </interface>
The ui file is converted to the resource by the resouce compiler `glib-compile-resouces` with xml file below.
1 <?xml version="1.0" encoding="UTF-8"?>
2 <gresources>
3 <gresource prefix="/com/github/ToshioCP/menu3">
4 <file>menu3.ui</file>
5 </gresource>
6 </gresources>
GtkBuilder builds menus from the resource.
GtkBuilder *builder = gtk_builder_new_from_resource ("/com/github/ToshioCP/menu3/menu3.ui");
GMenuModel *menubar = G_MENU_MODEL (gtk_builder_get_object (builder, "menubar"));
gtk_application_set_menubar (GTK_APPLICATION (app), menubar);
g_object_unref (builder);
It is important that `builder` is unreferred after the GMenuModel `menubar` is set to the application.
If you do it before setting, bad thing will happen -- your computer might freeze.
## Action entry
The coding for building actions and signal handlers is always the same.
Therefore, it can be automated.
You can implement them easily with GActionEntry `g_action_map_add_action_entries`.
GActionEntry is a strutcure.
It contains action name, signal handlers, parameter and state.
typedef struct _GActionEntry GActionEntry;
struct _GActionEntry
{
const gchar *name; /* action name */
void (* activate) (GSimpleAction *action, GVariant *parameter, gpointer user_data); /* activate handler */
const gchar *parameter_type; /* the type of the parameter given as a single GVariant type string */
const gchar *state; /* initial state given in GVariant text format */
void (* change_state) (GSimpleAction *action, GVariant *value, gpointer user_data); /* change-state handler */
/*< private >*/
gsize padding[3];
};
For example, the actions in the previous section are:
{ "fullscreen", NULL, NULL, "false", fullscreen_changed }
{ "color", color_activated, "s", "red", NULL }
{ "quit", quit_activated, NULL, NULL, NULL },
And `g_action_map_add_action_entries` does all the process instead of the functions you have needed.
const GActionEntry app_entries[] = {
{ "quit", quit_activated, NULL, NULL, NULL }
};
g_action_map_add_action_entries (G_ACTION_MAP (app), app_entries, G_N_ELEMENTS (app_entries), app);
The code above does:
- Build the "quit" action
- Connect the action and the "activate" signal handler `quit_activate`
- Add the action to the action map `app`.
const GActionEntry win_entries[] = {
{ "fullscreen", NULL, NULL, "false", fullscreen_changed },
{ "color", color_activated, "s", "red", NULL }
};
g_action_map_add_action_entries (G_ACTION_MAP (win), win_entries, G_N_ELEMENTS (win_entries), win);
The code above does:
- Build the "fullscreen" action and "color" action.
- Connect the "fullscreen" action and the "change-state" signal handler `fullscreen_changed`
- Its initial state is set to FALSE.
- Connect the "color" action and the "activate" signal handler `color_activate`
- Its parameter type is string and the initial value is "red".
- Add the action to the action map `win`.
## Example code
The C source code of `menu3` and `meson.build` is as follows.
1 #include <gtk/gtk.h>
2
3 static void
4 new_activated (GSimpleAction *action, GVariant *parameter, gpointer win) {
5 }
6
7 static void
8 open_activated (GSimpleAction *action, GVariant *parameter, gpointer win) {
9 }
10
11 static void
12 save_activated (GSimpleAction *action, GVariant *parameter, gpointer win) {
13 }
14
15 static void
16 saveas_activated (GSimpleAction *action, GVariant *parameter, gpointer win) {
17 }
18
19 static void
20 close_activated (GSimpleAction *action, GVariant *parameter, gpointer win) {
21 }
22
23 static void
24 cut_activated (GSimpleAction *action, GVariant *parameter, gpointer win) {
25 }
26
27 static void
28 copy_activated (GSimpleAction *action, GVariant *parameter, gpointer win) {
29 }
30
31 static void
32 paste_activated (GSimpleAction *action, GVariant *parameter, gpointer win) {
33 }
34
35 static void
36 selectall_activated (GSimpleAction *action, GVariant *parameter, gpointer win) {
37 }
38
39 static void
40 fullscreen_changed (GSimpleAction *action, GVariant *state, gpointer win) {
41 if (g_variant_get_boolean (state))
42 gtk_window_maximize (GTK_WINDOW (win));
43 else
44 gtk_window_unmaximize (GTK_WINDOW (win));
45 g_simple_action_set_state (action, state);
46 }
47
48 static void
49 quit_activated (GSimpleAction *action, GVariant *parameter, gpointer app)
50 {
51 g_application_quit (G_APPLICATION(app));
52 }
53
54 static void
55 on_activate (GApplication *app, gpointer user_data) {
56 GtkWidget *win = gtk_application_window_new (GTK_APPLICATION (app));
57
58 const GActionEntry win_entries[] = {
59 { "new", new_activated, NULL, NULL, NULL },
60 { "open", open_activated, NULL, NULL, NULL },
61 { "save", save_activated, NULL, NULL, NULL },
62 { "saveas", saveas_activated, NULL, NULL, NULL },
63 { "close", close_activated, NULL, NULL, NULL },
64 { "cut", cut_activated, NULL, NULL, NULL },
65 { "copy", copy_activated, NULL, NULL, NULL },
66 { "paste", paste_activated, NULL, NULL, NULL },
67 { "selectall", selectall_activated, NULL, NULL, NULL },
68 { "fullscreen", NULL, NULL, "false", fullscreen_changed }
69 };
70 g_action_map_add_action_entries (G_ACTION_MAP (win), win_entries, G_N_ELEMENTS (win_entries), win);
71
72 gtk_application_window_set_show_menubar (GTK_APPLICATION_WINDOW (win), TRUE);
73
74 gtk_window_set_title (GTK_WINDOW (win), "menu3");
75 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
76 gtk_widget_show (win);
77 }
78
79 static void
80 on_startup (GApplication *app, gpointer user_data) {
81 GtkBuilder *builder = gtk_builder_new_from_resource ("/com/github/ToshioCP/menu3/menu3.ui");
82 GMenuModel *menubar = G_MENU_MODEL (gtk_builder_get_object (builder, "menubar"));
83
84 gtk_application_set_menubar (GTK_APPLICATION (app), menubar);
85 g_object_unref (builder);
86
87 const GActionEntry app_entries[] = {
88 { "quit", quit_activated, NULL, NULL, NULL }
89 };
90 g_action_map_add_action_entries (G_ACTION_MAP (app), app_entries, G_N_ELEMENTS (app_entries), app);
91 }
92
93 int
94 main (int argc, char **argv) {
95 GtkApplication *app;
96 int stat;
97
98 app = gtk_application_new ("com.github.ToshioCP.menu3", G_APPLICATION_FLAGS_NONE);
99 g_signal_connect (app, "startup", G_CALLBACK (on_startup), NULL);
100 g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
101
102 stat =g_application_run (G_APPLICATION (app), argc, argv);
103 g_object_unref (app);
104 return stat;
105 }
106
meson.build
1 project('menu3', 'c')
2
3 gtkdep = dependency('gtk4')
4
5 gnome=import('gnome')
6 resources = gnome.compile_resources('resources','menu3.gresource.xml')
7
8 sourcefiles=files('menu3.c')
9
10 executable('menu3', sourcefiles, resources, dependencies: gtkdep)
Up: [Readme.md](Readme.md), Prev: [Section 16](sec16.md)

553
sec2.md
View file

@ -1,310 +1,285 @@
Up: [Readme.md](Readme.md), Prev: [Section 1](sec1.md), Next: [Section 3](sec3.md)
# Widgets (1)
# GtkApplication and GtkApplicationWindow
## GtkLabel, GtkButton and Gtkbox
## GtkApplication
### GtkLabel
### GtkApplication and g\_application\_run
We made an window and show it on the screen in the previous chapter.
Now we go on to the next topic, widgets in the window.
The simplest widget is GtkLabel.
It is a widget with a string in it.
Usually people write a programming code to make an application.
What are appications?
Applications are software that runs using libraries, which includes OS, frameworks and so on.
In Gtk4 programming, GtkApplication is an object that runs on GTK libraries.
The basic way how to write GtkApplication is as follows.
- Generate a GtkApplication object
- Run it
That's all.
Very simple.
The following is the C code representing the scenario above.
1 #include <gtk/gtk.h>
2
3 static void
4 on_activate (GApplication *app, gpointer user_data) {
5 GtkWidget *win;
6 GtkWidget *lab;
3 int
4 main (int argc, char **argv) {
5 GtkApplication *app;
6 int stat;
7
8 win = gtk_application_window_new (GTK_APPLICATION (app));
9 gtk_window_set_title (GTK_WINDOW (win), "lb4");
10 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
11
12 lab = gtk_label_new ("Hello.");
13 gtk_window_set_child (GTK_WINDOW (win), lab);
14
15 gtk_widget_show (win);
16 }
17
18 int
19 main (int argc, char **argv) {
20 GtkApplication *app;
21 int stat;
22
23 app = gtk_application_new ("com.github.ToshioCP.lb1", G_APPLICATION_FLAGS_NONE);
24 g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
25 stat =g_application_run (G_APPLICATION (app), argc, argv);
26 g_object_unref (app);
27 return stat;
28 }
29
Save this program to a file `lb1.c`.
Then compile and run it.
$ comp lb1
$ ./a.out
A window with a message "Hello." appears.
![Screenshot of the label](image/screenshot_lb1.png)
There's only a little change between `pr4.c` and `lb1.c`.
Diff is a good program to know the difference between two files.
$ diff misc/pr4.c lb1.c
5a6
> GtkWidget *lab;
8c9
< gtk_window_set_title (GTK_WINDOW (win), "pr4");
---
> gtk_window_set_title (GTK_WINDOW (win), "lb4");
9a11,14
>
> lab = gtk_label_new ("Hello.");
> gtk_window_set_child (GTK_WINDOW (win), lab);
>
18c23
< app = gtk_application_new ("com.github.ToshioCP.pr4", G_APPLICATION_FLAGS_NONE);
---
> app = gtk_application_new ("com.github.ToshioCP.lb1", G_APPLICATION_FLAGS_NONE);
This tells us:
- The definition of a variable lab is added.
- The title of the window is changed.
- A label is generated and connected to the window.
The function `gtk_window_set_child (GTK_WINDOW (win), lab)` makes the label `lab` a child widget of the window `win`.
Be careful.
A child widget is different from a child object.
Objects have parent-child relationship and Widgets also have parent-child relationship.
But these two relationships are totally different.
Don't be confused.
In the program `lb1.c`, `lab` is a child widget of `win`.
Child widgets are always located inside its parent widget in the screen.
See the window appeared on the screen.
The window includes the label.
The window `win` dosen't have any parents.
We call such a window top-level window.
One application can have two or more top-level windows.
### GtkButton
Next widget is GtkButton.
It has a label or icon on it.
In this subsection, we will make a button with a label.
When a button is clicked on, it emits a "clicked" signal.
The following program shows how to catch the signal and do something.
1 #include <gtk/gtk.h>
2
3 static void
4 on_clicked (GtkButton *btn, gpointer user_data) {
5 g_print ("Clicked.\n");
6 }
7
8 static void
9 on_activate (GApplication *app, gpointer user_data) {
10 GtkWidget *win;
11 GtkWidget *btn;
12
13 win = gtk_application_window_new (GTK_APPLICATION (app));
14 gtk_window_set_title (GTK_WINDOW (win), "lb4");
15 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
16
17 btn = gtk_button_new_with_label ("Click me");
18 gtk_window_set_child (GTK_WINDOW (win), btn);
19 g_signal_connect (btn, "clicked", G_CALLBACK (on_clicked), NULL);
20
21 gtk_widget_show (win);
22 }
23
24 int
25 main (int argc, char **argv) {
26 GtkApplication *app;
27 int stat;
28
29 app = gtk_application_new ("com.github.ToshioCP.lb2", G_APPLICATION_FLAGS_NONE);
30 g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
31 stat =g_application_run (G_APPLICATION (app), argc, argv);
32 g_object_unref (app);
33 return stat;
34 }
35
Look at the line 17 to 19.
First, generate a GtkButton widget `btn` with a label "Click me".
Then, set it to the window `win` as a child.
Finally, connect a "clicked" signal of the button to a handler (function) `on_click`.
So, if `btn` is clicked, the function `on_click` is invoked.
Name the program `lb2.c` and save it.
Now compile and run it.
![Screenshot of the label](image/screenshot_lb2.png)
A window with the button appears.
Click the button (it is a large button, you can click everywhere inside the window), then a string "Clicked." appears on the shell terminal.
It shows the handler was invoked by clicking the button.
It's fairly good for us to make sure that the clicked signal was caught and the handler was invoked.
However, using g_print is out of harmony with GTK which is a GUI library.
So, we will change the handler.
The following code is `lb3.c`.
1 static void
2 on_clicked (GtkButton *btn, gpointer user_data) {
3 GtkWindow *win = GTK_WINDOW (user_data);
4 gtk_window_destroy (win);
5 }
6
7 static void
8 on_activate (GApplication *app, gpointer user_data) {
9 GtkWidget *win;
10 GtkWidget *btn;
11
12 win = gtk_application_window_new (GTK_APPLICATION (app));
13 gtk_window_set_title (GTK_WINDOW (win), "lb4");
14 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
15
16 btn = gtk_button_new_with_label ("Quit");
17 gtk_window_set_child (GTK_WINDOW (win), btn);
18 g_signal_connect (btn, "clicked", G_CALLBACK (on_clicked), win);
19
20 gtk_widget_show (win);
21 }
And the difference between `lb2.c` and `lb3.c` is as follows.
$ diff lb2.c lb3.c
5c5,6
< g_print ("Clicked.\n");
---
> GtkWindow *win = GTK_WINDOW (user_data);
> gtk_window_destroy (win);
17c18
< btn = gtk_button_new_with_label ("Click me");
---
> btn = gtk_button_new_with_label ("Quit");
19c20
< g_signal_connect (btn, "clicked", G_CALLBACK (on_clicked), NULL);
---
> g_signal_connect (btn, "clicked", G_CALLBACK (on_clicked), win);
29c30
< app = gtk_application_new ("com.github.ToshioCP.lb2", G_APPLICATION_FLAGS_NONE);
---
> app = gtk_application_new ("com.github.ToshioCP.lb3", G_APPLICATION_FLAGS_NONE);
The change is:
- The function `g_print` in `lb2.c` was deleted and two lines above are inserted instead.
- The label of `btn` is changed from "Click me" to "Quit".
- The fourth argument of `g_signal_connect` is changed from `NULL` to `win`.
Most important is the fourth argument of `g_signal_connect`.
It is described as "data to pass to handler" in the definition of g\_signal\_connect in GObject API reference.
Therefore, `win` which is a pointer to GtkApplicationWindow is passed to the handler as a second parameter user_data.
Then, the handler cast it to a pointer to GtkWindow and call `gtk_window_destroy` and destroy the top window.
Then, the application quits.
### GtkBox
GtkWindow and GtkApplicationWindow can have only one child.
If you want to add two or more widgets inside a window, you need a container widget.
GtkBox is one of the containers.
It arranges two or more child widgets into a single row or column.
The following procedure shows the way to add two buttons in a window.
- Generate GtkApplicationWindow.
- Generate GtkBox and set it a child of GtkApplicationWindow.
- Generate GtkButton and append it to GtkBox.
- Generate another GtkButton and append it to GtkBox.
After this, the Widgets are connected as following diagram.
![Parent-child relationship](image/box.png)
Now, code it.
1 #include <gtk/gtk.h>
2
3 static void
4 on_clicked1 (GtkButton *btn, gpointer user_data) {
5 const gchar *s;
6
7 s = gtk_button_get_label (btn);
8 if (g_strcmp0 (s, "Hello.") == 0)
9 gtk_button_set_label (btn, "Good-bye.");
10 else
11 gtk_button_set_label (btn, "Hello.");
8 app = gtk_application_new ("com.github.ToshioCP.pr1", G_APPLICATION_FLAGS_NONE);
9 stat =g_application_run (G_APPLICATION (app), argc, argv);
10 g_object_unref (app);
11 return stat;
12 }
13
14 static void
15 on_clicked2 (GtkButton *btn, gpointer user_data) {
16 GtkWindow *win = GTK_WINDOW (user_data);
17 gtk_window_destroy (win);
The first line says that this program includes the GTK header libraries.
The function `main` above is a startup function in C language.
The variable `app` is defined as a pointer to GtkApplication, which is actually a structure in which information about the application is stored.
The function `gtk_application_new` generates a GtkApplication and sets its pointer to `app`.
The meaning of the arguments will be explained later.
The function `g_application_run` invokes the GtkApplication pointed by `app`.
(We often say that the function invokes `app`.
Actually, `app` is not an object but an pointer to the object.
However, it is simple and short, and probably no confusion occurs.)
To compile this, the following command needs to be run.
The string pr1.c is the filename of the C source code.
$ gcc `pkg-config --cflags gtk4` pr1.c `pkg-config --libs gtk4`
The C compiler gcc generates an executable file `a.out`.
Let's run it.
$ ./a.out
(a.out:13533): GLib-GIO-WARNING **: 15:30:17.449: Your application does not implement g_application_activate() and has no handlers connected to the "activate" signal. It should do one of these.
$
Oh, just an error message.
But this error message means that the GtkApplication object ran without a doubt.
Now, think about the message in the next section.
### signal
The message tells us that:
1. The application GtkApplication doesn't implement `g_application_activate()`.
2. And it has no handlers connected to the activate signal.
3. You need to solve at least one of this.
These two cause of the error are related to signals.
So, I will explain it to you first.
Signal is emitted when something happens.
For example, a window is generated, a window is destroyed and so on.
The signal "activate" is emitted when the application is activated.
If the signal is connected to a function, which is called signal handler or simply handler, then the function invokes when the signal emits.
The flow is like this:
1. Something happens.
2. If it's related to a certain signal, then the signal is emitted.
3. If the signal is connected to a handler in advance, then the handler is invoked.
Signals are defined in objects.
For example, "activate" signal belongs to GApplication object, which is a parent object of GtkApplication object.
GApplication object is a child object of GObject object.
GObject is the top object in the hierarchy of all the objects.
GObject -- GApplication -- GtkApplication
<---parent --->child
A child object derives signals, functions, properties and so on from its parent object.
So, Gtkapplication also has the "activate" signal.
Now we can solve the problem in `pr1.c`.
We need to connect the activate signal to a handler.
We use a function `g_signal_connect` which connects a signal to a handler.
1 #include <gtk/gtk.h>
2
3 static void
4 on_activate (GApplication *app, gpointer *user_data) {
5 g_print ("GtkApplication is activated.\n");
6 }
7
8 int
9 main (int argc, char **argv) {
10 GtkApplication *app;
11 int stat;
12
13 app = gtk_application_new ("com.github.ToshioCP.pr2", G_APPLICATION_FLAGS_NONE);
14 g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
15 stat =g_application_run (G_APPLICATION (app), argc, argv);
16 g_object_unref (app);
17 return stat;
18 }
19
20 static void
21 on_activate (GApplication *app, gpointer user_data) {
22 GtkWidget *win;
23 GtkWidget *box;
24 GtkWidget *btn1;
25 GtkWidget *btn2;
26
27 win = gtk_application_window_new (GTK_APPLICATION (app));
28 gtk_window_set_title (GTK_WINDOW (win), "lb4");
29 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
30
31 box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 5);
32 gtk_box_set_homogeneous (GTK_BOX (box), TRUE);
33 gtk_window_set_child (GTK_WINDOW (win), box);
34
35 btn1 = gtk_button_new_with_label ("Hello.");
36 g_signal_connect (btn1, "clicked", G_CALLBACK (on_clicked1), NULL);
37
38 btn2 = gtk_button_new_with_label ("Quit");
39 g_signal_connect (btn2, "clicked", G_CALLBACK (on_clicked2), win);
40
41 gtk_box_append (GTK_BOX (box), btn1);
42 gtk_box_append (GTK_BOX (box), btn2);
43
44 gtk_widget_show (win);
45 }
46
47 int
48 main (int argc, char **argv) {
49 GtkApplication *app;
50 int stat;
51
52 app = gtk_application_new ("com.github.ToshioCP.lb4", G_APPLICATION_FLAGS_NONE);
53 g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
54 stat =g_application_run (G_APPLICATION (app), argc, argv);
55 g_object_unref (app);
56 return stat;
57 }
58
Look at the function `on_activate`.
First, we define the handler `on_activate` which simply displays a message.
In the function `main`, we add `g_signal_connect` before `g_application_run`.
The function `g_signal_connect` has four arguments.
After the generation of GtkApplicationWindow, GtkBox is generated.
1. An object to which the signal belongs.
2. The name of the signal.
3. A handler function (also called callback), which needs to be casted by `G_CALLBACK`.
4. Data to pass to the handler. If no data is necessary, NULL should be given.
box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
gtk_box_set_homogeneous (GTK_BOX (box), TRUE);
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.
The first argument arranges children vertically.
The second argument is sizes between children.
The next function fills a box with children, giving them equal space.
In addition, `g_signal_connect` is described in GObject API reference.
API reference is very important.
You should see and understand it to write GTK applications.
After that, two buttons `btn1` and `btn2` are generated and the signal handlers are set.
Then, these two buttons are appended to the box.
Let's compile the source file `pr2.c` above and run it.
![Screenshot of the box](image/screenshot_lb4.png)
$ gcc `pkg-config --cflags gtk4` pr2.c `pkg-config --libs gtk4`
$ ./a.out
GtkApplication is activated.
$
The handler corresponds to `btn1` changes its label.
The handler corresponds to `btn2` destroys the top-level window and the application quits.
OK, well done.
However, you may have noticed that it's painful to type such a long line to compile.
It is a good idea to use shell script to solve this problem.
Make a text file which contains the following text.
gcc `pkg-config --cflags gtk4` $1.c `pkg-config --libs gtk4`
Then, save it in $HOME/bin, which is usually /home/(username)/bin.
(If your user name is James, then the directory is /home/james/bin).
And turn on the execute bit of the file.
Suppose the filename is comp, then the procedure is as follows.
$ chmod 755 $HOME/bin/comp
$ ls -log $HOME/bin
... ... ...
-rwxr-xr-x 1 62 May 23 08:21 comp
... ... ...
If this is the first time that you make a $HOME/bin directory and save a file in it, then you need to logout and login again.
$ comp pr2
$ ./a.out
GtkApplication is activated.
$
## GtkWindow and GtkApplicationWindow
### GtkWindow
A message "GtkApplication is activated." was printed out in the previous subsection.
It was good in terms of a test of GtkApplication.
However, it is insufficient because GTK is a framework for graphical user interface (GUI).
Now we go ahead with adding a window into this program.
What we need to do is:
1. Generate a GtkWindow.
2. Connect it to GtkApplication.
3. Show the window.
Now rewrite the function `on_activate`.
#### Generate a GtkWindow
1 static void
2 on_activate (GApplication *app, gpointer user_data) {
3 GtkWidget *win;
4
5 win = gtk_window_new ();
6 gtk_window_set_application (GTK_WINDOW (win), GTK_APPLICATION (app));
7 gtk_widget_show (win);
8 }
Widget is an abstract concept that includes all the GUI interfaces such as windows, dialogs, buttons, multiline text, containers and so on.
And GtkWidget is a base object from which all the GUI objects derive.
parent <-----> child
GtkWidget -- GtkWindow
GtkWindow includes GtkWidget at the top of its object.
![GtkWindow and GtkWidget](image/window_widget.png)
The function `gtk_window_new` is defined as follows.
GtkWidget *
gtk_window_new (void);
By this definition, it returns a pointer to GtkWidget, not GtkWindow.
It actually generates a new GtkWindow object (not GtkWidget) but returns a pointer to GtkWidget.
However,the pointer points the GtkWidget and at the same time it also points GtkWindow that contains GtkWidget in it.
If you want to use `win` as a pointer to the GtkWindow, you need to cast it.
(GtkWindow *) win
Or you can use `GTK_WINDOW` macro that performs a similar function.
GTK_WINDOW (win)
This is a recommended way.
#### Connect it to GtkApplication.
The function `gtk_window_set_application` is used to connect GtkWidow to GtkApplication.
gtk_window_set_application (GTK_WINDOW (win), GTK_APPLICATION (app));
You need to cast `win` to GtkWindow and `app` to GtkApplication.
`GTK_WINDOW` and `GTK_APPLICATION` macro is appropriate for that.
GtkApplication continues to run until the related window is destroyed.
If you didn't connect GtkWindow and GtkApplication, GtkApplication shutdowns soon.
Because no window is connected to GtkApplication, it doesn't need to wait anything.
As it shutdowns the generated window is also destroyed.
#### Show the window.
The function `gtk_widget_show` is used to show the window.
Gtk4 changed the default widget visibility to on, so every widget doesn't need this function to show itself.
But, there's an exception.
Top window (this term will be explained later) isn't visible when it is generated.
So you need to use the function above and show the window.
Save the program as `pr3.c` and compile and run it.
$ comp pr3
$ ./a.out
A small window appears.
![Screenshot of the window](image/screenshot_pr3.png)
Click on the close button then the window disappears and the program finishes.
### GtkApplicationWindow
GtkApplicationWindow is a child object of GtkWindow.
It has some extra functionality for better integration with GtkApplication.
It is recommended to use it instead of GtkWindow when you use GtkApplication.
Now rewrite the program and use GtkAppliction Window.
1 static void
2 on_activate (GApplication *app, gpointer user_data) {
3 GtkWidget *win;
4
5 win = gtk_application_window_new (GTK_APPLICATION (app));
6 gtk_window_set_title (GTK_WINDOW (win), "pr4");
7 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
8 gtk_widget_show (win);
9 }
When you generate GtkApplicationWindow, you need to give GtkApplication object as an argument.
Then it automatically connect these two objects.
So you don't need to call `gtk_window_set_application` any more.
The program sets the title and the default size of the window.
Compile it and run `a.out`, then you will see a bigger window with its title "pr4".
![Screenshot of the window](image/screenshot_pr4.png)
Up: [Readme.md](Readme.md), Prev: [Section 1](sec1.md), Next: [Section 3](sec3.md)

429
sec3.md
View file

@ -1,165 +1,310 @@
Up: [Readme.md](Readme.md), Prev: [Section 2](sec2.md), Next: [Section 4](sec4.md)
# Widgets (2)
# Widgets (1)
## GtkTextView, GtkTextbuffer and GtkScrolledWindow
## GtkLabel, GtkButton and Gtkbox
### GtkTextView and GtkTextBuffer
### GtkLabel
GtkTextview is a widget for multiline text editing.
GtkTextBuffer is a text buffer which is connected to GtkTextView.
See a sample program `tfv1.c` below.
We made an window and show it on the screen in the previous chapter.
Now we go on to the next topic, widgets in the window.
The simplest widget is GtkLabel.
It is a widget with a string in it.
1 #include <gtk/gtk.h>
2
3 static void
4 on_activate (GApplication *app, gpointer user_data) {
5 GtkWidget *win;
6 GtkWidget *tv;
7 GtkTextBuffer *tb;
8 gchar *text;
9
10 text =
11 "Once upon a time, there was an old man who was called Taketori-no-Okina."
12 "It is a japanese word that means a man whose work is making bamboo baskets.\n"
13 "One day, he went into a mountain and found a shining bamboo."
14 "\"What a mysterious bamboo it is!,\" he said."
15 "He cuts it, then there was a small cute baby girl in it."
16 "The girl was shining faintly."
17 "He thought this baby girl is a gift from Heaven and took her home.\n"
18 "His wife was surprized at his tale."
19 "They were very happy because they had no children."
20 ;
21 win = gtk_application_window_new (GTK_APPLICATION (app));
22 gtk_window_set_title (GTK_WINDOW (win), "Taketori");
23 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
24
25 tv = gtk_text_view_new ();
26 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
27 gtk_text_buffer_set_text (tb, text, -1);
28 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
6 GtkWidget *lab;
7
8 win = gtk_application_window_new (GTK_APPLICATION (app));
9 gtk_window_set_title (GTK_WINDOW (win), "lb4");
10 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
11
12 lab = gtk_label_new ("Hello.");
13 gtk_window_set_child (GTK_WINDOW (win), lab);
14
15 gtk_widget_show (win);
16 }
17
18 int
19 main (int argc, char **argv) {
20 GtkApplication *app;
21 int stat;
22
23 app = gtk_application_new ("com.github.ToshioCP.lb1", G_APPLICATION_FLAGS_NONE);
24 g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
25 stat =g_application_run (G_APPLICATION (app), argc, argv);
26 g_object_unref (app);
27 return stat;
28 }
29
30 gtk_window_set_child (GTK_WINDOW (win), tv);
31
32 gtk_widget_show (win);
33 }
Save this program to a file `lb1.c`.
Then compile and run it.
$ comp lb1
$ ./a.out
A window with a message "Hello." appears.
![Screenshot of the label](image/screenshot_lb1.png)
There's only a little change between `pr4.c` and `lb1.c`.
Diff is a good program to know the difference between two files.
$ diff misc/pr4.c lb1.c
5a6
> GtkWidget *lab;
8c9
< gtk_window_set_title (GTK_WINDOW (win), "pr4");
---
> gtk_window_set_title (GTK_WINDOW (win), "lb4");
9a11,14
>
> lab = gtk_label_new ("Hello.");
> gtk_window_set_child (GTK_WINDOW (win), lab);
>
18c23
< app = gtk_application_new ("com.github.ToshioCP.pr4", G_APPLICATION_FLAGS_NONE);
---
> app = gtk_application_new ("com.github.ToshioCP.lb1", G_APPLICATION_FLAGS_NONE);
This tells us:
- The definition of a variable lab is added.
- The title of the window is changed.
- A label is generated and connected to the window.
The function `gtk_window_set_child (GTK_WINDOW (win), lab)` makes the label `lab` a child widget of the window `win`.
Be careful.
A child widget is different from a child object.
Objects have parent-child relationship and Widgets also have parent-child relationship.
But these two relationships are totally different.
Don't be confused.
In the program `lb1.c`, `lab` is a child widget of `win`.
Child widgets are always located inside its parent widget in the screen.
See the window appeared on the screen.
The window includes the label.
The window `win` dosen't have any parents.
We call such a window top-level window.
One application can have two or more top-level windows.
### GtkButton
Next widget is GtkButton.
It has a label or icon on it.
In this subsection, we will make a button with a label.
When a button is clicked on, it emits a "clicked" signal.
The following program shows how to catch the signal and do something.
1 #include <gtk/gtk.h>
2
3 static void
4 on_clicked (GtkButton *btn, gpointer user_data) {
5 g_print ("Clicked.\n");
6 }
7
8 static void
9 on_activate (GApplication *app, gpointer user_data) {
10 GtkWidget *win;
11 GtkWidget *btn;
12
13 win = gtk_application_window_new (GTK_APPLICATION (app));
14 gtk_window_set_title (GTK_WINDOW (win), "lb4");
15 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
16
17 btn = gtk_button_new_with_label ("Click me");
18 gtk_window_set_child (GTK_WINDOW (win), btn);
19 g_signal_connect (btn, "clicked", G_CALLBACK (on_clicked), NULL);
20
21 gtk_widget_show (win);
22 }
23
24 int
25 main (int argc, char **argv) {
26 GtkApplication *app;
27 int stat;
28
29 app = gtk_application_new ("com.github.ToshioCP.lb2", G_APPLICATION_FLAGS_NONE);
30 g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
31 stat =g_application_run (G_APPLICATION (app), argc, argv);
32 g_object_unref (app);
33 return stat;
34 }
35
Look at the line 17 to 19.
First, generate a GtkButton widget `btn` with a label "Click me".
Then, set it to the window `win` as a child.
Finally, connect a "clicked" signal of the button to a handler (function) `on_click`.
So, if `btn` is clicked, the function `on_click` is invoked.
Name the program `lb2.c` and save it.
Now compile and run it.
![Screenshot of the label](image/screenshot_lb2.png)
A window with the button appears.
Click the button (it is a large button, you can click everywhere inside the window), then a string "Clicked." appears on the shell terminal.
It shows the handler was invoked by clicking the button.
It's fairly good for us to make sure that the clicked signal was caught and the handler was invoked.
However, using g_print is out of harmony with GTK which is a GUI library.
So, we will change the handler.
The following code is `lb3.c`.
1 static void
2 on_clicked (GtkButton *btn, gpointer user_data) {
3 GtkWindow *win = GTK_WINDOW (user_data);
4 gtk_window_destroy (win);
5 }
6
7 static void
8 on_activate (GApplication *app, gpointer user_data) {
9 GtkWidget *win;
10 GtkWidget *btn;
11
12 win = gtk_application_window_new (GTK_APPLICATION (app));
13 gtk_window_set_title (GTK_WINDOW (win), "lb4");
14 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
15
16 btn = gtk_button_new_with_label ("Quit");
17 gtk_window_set_child (GTK_WINDOW (win), btn);
18 g_signal_connect (btn, "clicked", G_CALLBACK (on_clicked), win);
19
20 gtk_widget_show (win);
21 }
And the difference between `lb2.c` and `lb3.c` is as follows.
$ diff lb2.c lb3.c
5c5,6
< g_print ("Clicked.\n");
---
> GtkWindow *win = GTK_WINDOW (user_data);
> gtk_window_destroy (win);
17c18
< btn = gtk_button_new_with_label ("Click me");
---
> btn = gtk_button_new_with_label ("Quit");
19c20
< g_signal_connect (btn, "clicked", G_CALLBACK (on_clicked), NULL);
---
> g_signal_connect (btn, "clicked", G_CALLBACK (on_clicked), win);
29c30
< app = gtk_application_new ("com.github.ToshioCP.lb2", G_APPLICATION_FLAGS_NONE);
---
> app = gtk_application_new ("com.github.ToshioCP.lb3", G_APPLICATION_FLAGS_NONE);
The change is:
- The function `g_print` in `lb2.c` was deleted and two lines above are inserted instead.
- The label of `btn` is changed from "Click me" to "Quit".
- The fourth argument of `g_signal_connect` is changed from `NULL` to `win`.
Most important is the fourth argument of `g_signal_connect`.
It is described as "data to pass to handler" in the definition of g\_signal\_connect in GObject API reference.
Therefore, `win` which is a pointer to GtkApplicationWindow is passed to the handler as a second parameter user_data.
Then, the handler cast it to a pointer to GtkWindow and call `gtk_window_destroy` and destroy the top window.
Then, the application quits.
### GtkBox
GtkWindow and GtkApplicationWindow can have only one child.
If you want to add two or more widgets inside a window, you need a container widget.
GtkBox is one of the containers.
It arranges two or more child widgets into a single row or column.
The following procedure shows the way to add two buttons in a window.
- Generate GtkApplicationWindow.
- Generate GtkBox and set it a child of GtkApplicationWindow.
- Generate GtkButton and append it to GtkBox.
- Generate another GtkButton and append it to GtkBox.
After this, the Widgets are connected as following diagram.
![Parent-child relationship](image/box.png)
Now, code it.
1 #include <gtk/gtk.h>
2
3 static void
4 on_clicked1 (GtkButton *btn, gpointer user_data) {
5 const gchar *s;
6
7 s = gtk_button_get_label (btn);
8 if (g_strcmp0 (s, "Hello.") == 0)
9 gtk_button_set_label (btn, "Good-bye.");
10 else
11 gtk_button_set_label (btn, "Hello.");
12 }
13
14 static void
15 on_clicked2 (GtkButton *btn, gpointer user_data) {
16 GtkWindow *win = GTK_WINDOW (user_data);
17 gtk_window_destroy (win);
18 }
19
20 static void
21 on_activate (GApplication *app, gpointer user_data) {
22 GtkWidget *win;
23 GtkWidget *box;
24 GtkWidget *btn1;
25 GtkWidget *btn2;
26
27 win = gtk_application_window_new (GTK_APPLICATION (app));
28 gtk_window_set_title (GTK_WINDOW (win), "lb4");
29 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
30
31 box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 5);
32 gtk_box_set_homogeneous (GTK_BOX (box), TRUE);
33 gtk_window_set_child (GTK_WINDOW (win), box);
34
35 int
36 main (int argc, char **argv) {
37 GtkApplication *app;
38 int stat;
39
40 app = gtk_application_new ("com.github.ToshioCP.tfv1", G_APPLICATION_FLAGS_NONE);
41 g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
42 stat =g_application_run (G_APPLICATION (app), argc, argv);
43 g_object_unref (app);
44 return stat;
35 btn1 = gtk_button_new_with_label ("Hello.");
36 g_signal_connect (btn1, "clicked", G_CALLBACK (on_clicked1), NULL);
37
38 btn2 = gtk_button_new_with_label ("Quit");
39 g_signal_connect (btn2, "clicked", G_CALLBACK (on_clicked2), win);
40
41 gtk_box_append (GTK_BOX (box), btn1);
42 gtk_box_append (GTK_BOX (box), btn2);
43
44 gtk_widget_show (win);
45 }
46
47 int
48 main (int argc, char **argv) {
49 GtkApplication *app;
50 int stat;
51
52 app = gtk_application_new ("com.github.ToshioCP.lb4", G_APPLICATION_FLAGS_NONE);
53 g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
54 stat =g_application_run (G_APPLICATION (app), argc, argv);
55 g_object_unref (app);
56 return stat;
57 }
58
Look at line 25.
GtkTextView is generated and its pointer is assigned to `tv`.
When GtkTextView is generated, the connected GtkTextBuffer is also generated automatically.
In the next line, the pointer to the buffer is got and assigned to `tb`.
Then, the text from line 10 to 20 is assigned to the buffer.
Look at the function `on_activate`.
GtkTextView has a wrap mode.
When `GTK_WRAP_WORD_CHAR` is set, text wraps in between words, or if that is not enough, also between graphemes.
After the generation of GtkApplicationWindow, GtkBox is generated.
In line 30, `tv` is set to `win` as a child.
box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
gtk_box_set_homogeneous (GTK_BOX (box), TRUE);
Now compile and run it.
The first argument arranges children vertically.
The second argument is sizes between children.
The next function fills a box with children, giving them equal space.
![GtkTextView](image/screenshot_tfv1.png)
After that, two buttons `btn1` and `btn2` are generated and the signal handlers are set.
Then, these two buttons are appended to the box.
There's an I-beam pointer in the window.
You can add or delete any character on GtkTextview.
And your change is kept in GtkTextBuffer.
If you add more characters than the limit of the window, the height of the window extends.
If the height gets bigger than the height of the display screen, you won't be able to control the size of the window back to the original size.
It's a problem.
You can solve it by putting GtkScrolledWindow between GtkApplicationWindow and GtkTextView.
![Screenshot of the box](image/screenshot_lb4.png)
### GtkScrolledWindow
What we need to do is:
- Generate GtkScrolledWindow and set it as a child of GtkApplicationWindow.
- Set GtkTextVies as a child of GtkScrolledWindow.
Modify `tfv1.c` and save it as `tfv2.c`.
The difference between these two files is very little.
$ diff tfv1.c tfv2.c
5a6
> GtkWidget *scr;
24a26,28
> scr = gtk_scrolled_window_new ();
> gtk_window_set_child (GTK_WINDOW (win), scr);
>
30c34
< gtk_window_set_child (GTK_WINDOW (win), tv);
---
> gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
40c44
< app = gtk_application_new ("com.github.ToshioCP.tfv1", G_APPLICATION_FLAGS_NONE);
---
> app = gtk_application_new ("com.github.ToshioCP.tfv2", G_APPLICATION_FLAGS_NONE);
Though you can modify the source file by this diff output, It's good for you to show `tfv2.c`.
1 #include <gtk/gtk.h>
2
3 static void
4 on_activate (GApplication *app, gpointer user_data) {
5 GtkWidget *win;
6 GtkWidget *scr;
7 GtkWidget *tv;
8 GtkTextBuffer *tb;
9 gchar *text;
10
11 text =
12 "Once upon a time, there was an old man who was called Taketori-no-Okina."
13 "It is a japanese word that means a man whose work is making bamboo baskets.\n"
14 "One day, he went into a mountain and found a shining bamboo."
15 "\"What a mysterious bamboo it is!,\" he said."
16 "He cuts it, then there was a small cute baby girl in it."
17 "The girl was shining faintly."
18 "He thought this baby girl is a gift from Heaven and took her home.\n"
19 "His wife was surprized at his tale."
20 "They were very happy because they had no children."
21 ;
22 win = gtk_application_window_new (GTK_APPLICATION (app));
23 gtk_window_set_title (GTK_WINDOW (win), "Taketori");
24 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
25
26 scr = gtk_scrolled_window_new ();
27 gtk_window_set_child (GTK_WINDOW (win), scr);
28
29 tv = gtk_text_view_new ();
30 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
31 gtk_text_buffer_set_text (tb, text, -1);
32 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
33
34 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
35
36 gtk_widget_show (win);
37 }
38
39 int
40 main (int argc, char **argv) {
41 GtkApplication *app;
42 int stat;
43
44 app = gtk_application_new ("com.github.ToshioCP.tfv2", G_APPLICATION_FLAGS_NONE);
45 g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
46 stat =g_application_run (G_APPLICATION (app), argc, argv);
47 g_object_unref (app);
48 return stat;
49 }
50
Now compile and run it.
This time the window doesn't extend even if you type a lot of characters.
It just scrolls.
The handler corresponds to `btn1` changes its label.
The handler corresponds to `btn2` destroys the top-level window and the application quits.
Up: [Readme.md](Readme.md), Prev: [Section 2](sec2.md), Next: [Section 4](sec4.md)

404
sec4.md
View file

@ -1,305 +1,165 @@
Up: [Readme.md](Readme.md), Prev: [Section 3](sec3.md), Next: [Section 5](sec5.md)
# Widgets (3)
# Widgets (2)
## Open signal
## GtkTextView, GtkTextbuffer and GtkScrolledWindow
### G\_APPLICATION\_HANDLES\_OPEN flag
### GtkTextView and GtkTextBuffer
GtkTextView, GtkTextBuffer and GtkScrolledWindow have given us a minimum editor in the previous section.
Next, we will add a read function to this program and remake it into a file viewer.
There are many way to implement the function.
However, because this is a tutorial for beginners, we take the simplest way.
When the program starts, we give a filename as an argument.
$ ./a.out filename
Then it opens the file and set it into GtkTextBuffer.
At the beginning of the implementation, we need to know how GtkApplication (or GApplication) recognizes arguments.
It is described in the GIO API reference.
When GtkApplication is generated, a flag (its type is GApplicationFlags) is given as an argument.
GtkApplication *
gtk_application_new (const gchar *application_id, GApplicationFlags flags);
This flag is described in the GApplication section in GIO API reference.
GApplicationFlags' Members
G_APPLICATION_FLAGS_NONE Default. (No argument allowed)
... ... ...
G_APPLICATION_HANDLES_OPEN This application handles opening files (in the primary instance).
... ... ...
There are ten flags.
But we only need two of them so far.
We've already used `G_APPLICATION_FLAGS_NONE`.
It is the simplest option.
No argument is allowed.
If you give arguments and run the application, then error occurs.
`G_APPLICATION_HANDLES_OPEN` is the second simplest option.
It allows arguments but only files.
The application assumes all the arguments are filenames.
Now we use this flag when generating GtkApplication.
app = gtk_application_new ("com.github.ToshioCP.tfv3", G_APPLICATION_HANDLES_OPEN);
### open signal
When the application starts, two signals are possible.
- activate signal --- This signal is emitted when there's no argument.
- open signal --- This signal is emitted when there is at least one argument.
The handler of open signal is called as follows.
void user_function (GApplication *application,
gpointer files,
gint n_files,
gchar *hint,
gpointer user_data)
The parameters are as follows:
- application --- the application (usually GtkApplication)
- files --- an array of GFiles. [array length=n_files] [element-type GFile]
- n_files --- the length of files
- hint --- a hint provided by the calling instance (usually it can be ignored)
- user_data --- user data set when the signal handler was connected.
The way how to read a file using GFiles will be described in the next section.
## Coding a file viewer
### What is a file viewer?
A file viewer is a program that shows a text file given as an argument.
It works as follows.
- If it is given arguments, it recognizes the first argument as a filename and open it.
- If opening the file succeeds, read and set it to GtkTextBuffer and show the window.
- If it fails to open the file, show an error message and quit.
- If there's no argument, show an error message and quit.
- If there are two or more arguments, the second one and after are ignored.
The program is as follows.
GtkTextview is a widget for multiline text editing.
GtkTextBuffer is a text buffer which is connected to GtkTextView.
See a sample program `tfv1.c` below.
1 #include <gtk/gtk.h>
2
3 static void
4 on_activate (GApplication *app, gpointer user_data) {
5 g_print ("You need a filename argument.\n");
6 }
7
8 static void
9 on_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) {
10 GtkWidget *win;
11 GtkWidget *scr;
12 GtkWidget *tv;
13 GtkTextBuffer *tb;
14 char *contents;
15 gsize length;
16 char *filename;
17
18 win = gtk_application_window_new (GTK_APPLICATION (app));
19 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
20
21 scr = gtk_scrolled_window_new ();
22 gtk_window_set_child (GTK_WINDOW (win), scr);
23
24 tv = gtk_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_text_view_set_editable (GTK_TEXT_VIEW (tv), FALSE);
28 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
5 GtkWidget *win;
6 GtkWidget *tv;
7 GtkTextBuffer *tb;
8 gchar *text;
9
10 text =
11 "Once upon a time, there was an old man who was called Taketori-no-Okina."
12 "It is a japanese word that means a man whose work is making bamboo baskets.\n"
13 "One day, he went into a mountain and found a shining bamboo."
14 "\"What a mysterious bamboo it is!,\" he said."
15 "He cuts it, then there was a small cute baby girl in it."
16 "The girl was shining faintly."
17 "He thought this baby girl is a gift from Heaven and took her home.\n"
18 "His wife was surprized at his tale."
19 "They were very happy because they had no children."
20 ;
21 win = gtk_application_window_new (GTK_APPLICATION (app));
22 gtk_window_set_title (GTK_WINDOW (win), "Taketori");
23 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
24
25 tv = gtk_text_view_new ();
26 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
27 gtk_text_buffer_set_text (tb, text, -1);
28 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
29
30 if (g_file_load_contents (files[0], NULL, &contents, &length, NULL, NULL)) {
31 gtk_text_buffer_set_text (tb, contents, length);
32 g_free (contents);
33 filename = g_file_get_basename (files[0]);
34 gtk_window_set_title (GTK_WINDOW (win), filename);
35 g_free (filename);
36 gtk_widget_show (win);
37 } else {
38 filename = g_file_get_path (files[0]);
39 g_print ("No such file: %s.\n", filename);
40 gtk_window_destroy (GTK_WINDOW (win));
41 }
42 }
43
44 int
45 main (int argc, char **argv) {
46 GtkApplication *app;
47 int stat;
48
49 app = gtk_application_new ("com.github.ToshioCP.tfv3", G_APPLICATION_HANDLES_OPEN);
50 g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
51 g_signal_connect (app, "open", G_CALLBACK (on_open), NULL);
52 stat =g_application_run (G_APPLICATION (app), argc, argv);
53 g_object_unref (app);
54 return stat;
55 }
56
30 gtk_window_set_child (GTK_WINDOW (win), tv);
31
32 gtk_widget_show (win);
33 }
34
35 int
36 main (int argc, char **argv) {
37 GtkApplication *app;
38 int stat;
39
40 app = gtk_application_new ("com.github.ToshioCP.tfv1", G_APPLICATION_FLAGS_NONE);
41 g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
42 stat =g_application_run (G_APPLICATION (app), argc, argv);
43 g_object_unref (app);
44 return stat;
45 }
46
Save it as `tfv3.c`.
Then compile and run it.
Look at line 25.
GtkTextView is generated and its pointer is assigned to `tv`.
When GtkTextView is generated, the connected GtkTextBuffer is also generated automatically.
In the next line, the pointer to the buffer is got and assigned to `tb`.
Then, the text from line 10 to 20 is assigned to the buffer.
$ comp tfv3
$ ./a.out tfv3.c
GtkTextView has a wrap mode.
When `GTK_WRAP_WORD_CHAR` is set, text wraps in between words, or if that is not enough, also between graphemes.
![File viewer](image/screenshot_tfv3.png)
In line 30, `tv` is set to `win` as a child.
Now I want to explain the program `tfv3.c`.
First, the function `main` changes in only two lines.
Now compile and run it.
- `G_APPLICATION_FLAGS_NONE` is replaced with `G_APPLICATION_HANDLES_OPEN`.
- `g_signal_connect (app, "open", G_CALLBACK (on_open), NULL)` is added.
![GtkTextView](image/screenshot_tfv1.png)
Next, the handler `on_activate` is now very simple.
Just output the error message.
The application quits immediately because no window is generated.
There's an I-beam pointer in the window.
You can add or delete any character on GtkTextview.
And your change is kept in GtkTextBuffer.
If you add more characters than the limit of the window, the height of the window extends.
If the height gets bigger than the height of the display screen, you won't be able to control the size of the window back to the original size.
It's a problem.
You can solve it by putting GtkScrolledWindow between GtkApplicationWindow and GtkTextView.
The point is the handler `on_open`.
### GtkScrolledWindow
- It generates GtkApplicationWindow, GtkScrolledWindow, GtkTextView and GtkTextBuffer and connect them.
- Set wrap mode to `GTK_WRAP_WORD_CHAR` in GtktextView.
- Set non-editable to GtkTextView because the program isn't an editor but only a viewer.
- Read the file and set it to GtkTextBuffer (this will be explained in detail later).
- If the file is not opened then output an error message and destroy the window. It makes the application quit.
What we need to do is:
The file reading part of the program is shown again below.
- Generate GtkScrolledWindow and set it as a child of GtkApplicationWindow.
- Set GtkTextVies as a child of GtkScrolledWindow.
if (g_file_load_contents(files[0], NULL, &contents, &length, NULL, NULL)) {
gtk_text_buffer_set_text(tb, contents, length);
g_free(contents);
filename = g_file_get_basename(files[0]);
gtk_window_set_title (GTK_WINDOW (win), filename);
g_free(filename);
gtk_widget_show (win);
} else {
filename = g_file_get_path(files[0]);
g_print ("No such file: %s.\n", filename);
gtk_window_destroy (GTK_WINDOW (win));
}
Modify `tfv1.c` and save it as `tfv2.c`.
The difference between these two files is very little.
The function `g_file_load_contents` loads the file contents into a buffer, which is automatically allocated, and set the pointer to the buffer into `contents`.
And the length of the buffer is set to `length`.
It returns `TRUE` if the file's contents were successfully loaded. `FALSE` if there were errors.
$ diff tfv1.c tfv2.c
5a6
> GtkWidget *scr;
24a26,28
> scr = gtk_scrolled_window_new ();
> gtk_window_set_child (GTK_WINDOW (win), scr);
>
30c34
< gtk_window_set_child (GTK_WINDOW (win), tv);
---
> gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
40c44
< app = gtk_application_new ("com.github.ToshioCP.tfv1", G_APPLICATION_FLAGS_NONE);
---
> app = gtk_application_new ("com.github.ToshioCP.tfv2", G_APPLICATION_FLAGS_NONE);
If the function succeeds, set the contents into GtkTextBuffer, free the buffer memories pointed by `contents`, set the filename to the title of the window,
free the memories pointed by `filename` and show the window.
If it fails, it outputs an error message and destroy the window.
## GtkNotebook
GtkNotebook is a container widget that contains multiple children with tabs in it.
![GtkNotebook](image/screenshot_gtk_notebook.png)
Look at the screenshots above.
The left one is a window at the startup.
It shows the file `pr1.c`.
The filename is in the left tab.
After clicking on the right tab, then the contents of `tfv1.c` appears.
It is shown in the right screenshot.
GtkNotebook widget is between GtkApplicationWindow and GtkScrolledWindow.
Now I want to show you the program `tfv4.c`.
Though you can modify the source file by this diff output, It's good for you to show `tfv2.c`.
1 #include <gtk/gtk.h>
2
3 static void
4 on_activate (GApplication *app, gpointer user_data) {
5 g_print ("You need a filename argument.\n");
6 }
7
8 static void
9 on_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) {
10 GtkWidget *win;
11 GtkWidget *nb;
12 GtkWidget *lab;
13 GtkNotebookPage *nbp;
14 GtkWidget *scr;
15 GtkWidget *tv;
16 GtkTextBuffer *tb;
17 char *contents;
18 gsize length;
19 char *filename;
20 int i;
21
5 GtkWidget *win;
6 GtkWidget *scr;
7 GtkWidget *tv;
8 GtkTextBuffer *tb;
9 gchar *text;
10
11 text =
12 "Once upon a time, there was an old man who was called Taketori-no-Okina."
13 "It is a japanese word that means a man whose work is making bamboo baskets.\n"
14 "One day, he went into a mountain and found a shining bamboo."
15 "\"What a mysterious bamboo it is!,\" he said."
16 "He cuts it, then there was a small cute baby girl in it."
17 "The girl was shining faintly."
18 "He thought this baby girl is a gift from Heaven and took her home.\n"
19 "His wife was surprized at his tale."
20 "They were very happy because they had no children."
21 ;
22 win = gtk_application_window_new (GTK_APPLICATION (app));
23 gtk_window_set_title (GTK_WINDOW (win), "file viewer");
23 gtk_window_set_title (GTK_WINDOW (win), "Taketori");
24 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
25 gtk_window_maximize (GTK_WINDOW (win));
26
27 nb = gtk_notebook_new ();
28 gtk_window_set_child (GTK_WINDOW (win), nb);
29
30 for (i = 0; i < n_files; i++) {
31 if (g_file_load_contents (files[i], NULL, &contents, &length, NULL, NULL)) {
32 scr = gtk_scrolled_window_new ();
33 tv = gtk_text_view_new ();
34 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
35 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
36 gtk_text_view_set_editable (GTK_TEXT_VIEW (tv), FALSE);
37 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
25
26 scr = gtk_scrolled_window_new ();
27 gtk_window_set_child (GTK_WINDOW (win), scr);
28
29 tv = gtk_text_view_new ();
30 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
31 gtk_text_buffer_set_text (tb, text, -1);
32 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
33
34 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
35
36 gtk_widget_show (win);
37 }
38
39 gtk_text_buffer_set_text (tb, contents, length);
40 g_free (contents);
41 filename = g_file_get_basename (files[i]);
42 lab = gtk_label_new (filename);
43 gtk_notebook_append_page (GTK_NOTEBOOK (nb), scr, lab);
44 nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr);
45 g_object_set (nbp, "tab-expand", TRUE, NULL);
46 g_free (filename);
47 } else {
48 filename = g_file_get_path (files[i]);
49 g_print ("No such file: %s.\n", filename);
50 g_free (filename);
51 }
52 }
53 if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0)
54 gtk_widget_show (win);
55 else
56 gtk_window_destroy (GTK_WINDOW (win));
57 }
58
59 int
60 main (int argc, char **argv) {
61 GtkApplication *app;
62 int stat;
63
64 app = gtk_application_new ("com.github.ToshioCP.tfv4", G_APPLICATION_HANDLES_OPEN);
65 g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
66 g_signal_connect (app, "open", G_CALLBACK (on_open), NULL);
67 stat =g_application_run (G_APPLICATION (app), argc, argv);
68 g_object_unref (app);
69 return stat;
70 }
71
39 int
40 main (int argc, char **argv) {
41 GtkApplication *app;
42 int stat;
43
44 app = gtk_application_new ("com.github.ToshioCP.tfv2", G_APPLICATION_FLAGS_NONE);
45 g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
46 stat =g_application_run (G_APPLICATION (app), argc, argv);
47 g_object_unref (app);
48 return stat;
49 }
50
Most of the change is in the function `on_open`.
The numbers at the left of the following items are line numbers in the source code.
- 11-13: Variables `nb`, `lab` and `nbp` are defined and point GtkNotebook, GtkLabel and GtkNotebookPage respectively.
- 23: The window's title is set to "file viewer".
- 25: The size of the window is set to maximum because a big window is appropriate for file viewers.
- 27-28 GtkNotebook is generated and set it as a child of GtkApplicationWindow.
- 30-52 For-loop. Each loop corresponds to an argument. And files[i] is GFile object with respect to the i-th argument.
- 32-37 GtkScrollledWindow, GtkTextView and GtkTextBuffer are generated and GtkTextView is connected to GtkScrolledWindow as a child.
They corresponds to each file, so they are generated inside the for-loop.
- 39-42 Set the contents of the file into GtkTextBuffer and free the memory pointed by `contents`. Get the filename and generate GtkLabel with the filename.
- 43: Append GtkScrolledWindow and GtkLabel to GtkNotebook. The appended objects are children of automatically generated GtkNotebookPage object. Therefore, the structure is like this:
GtkNotebook -- GtkNotebookPage -- (GtkScrolledWindow and GtkLabel)
- 44: Get GtkNotebookPage object and set its pointer to `nbp`.
- 45: GtkNotebookPage has a property "tab-expand". If it is set to TRUE then the tab expand horizontally as long as possible. If FALSE, then the width of the tab is determined by the size of the label. `g_object_set` is a general function to set properties in any objects.
- 46: free the memory pointed by `filename`
- 53-56: If at least one file was read, then the number of GtkNotebookPage is greater than zero. If it's true, then show the window. If it's false, then destroy the window.
Now compile and run it.
This time the window doesn't extend even if you type a lot of characters.
It just scrolls.
Up: [Readme.md](Readme.md), Prev: [Section 3](sec3.md), Next: [Section 5](sec5.md)

583
sec5.md
View file

@ -1,348 +1,305 @@
Up: [Readme.md](Readme.md), Prev: [Section 4](sec4.md), Next: [Section 6](sec6.md)
# Define Child object
# Widgets (3)
## Very simple editor
## Open signal
We made a very simple file viewer in the previous section.
Now we go on to rewrite it and make a very simple editor.
Its source file name is tfe1.c (text file editor 1).
### G\_APPLICATION\_HANDLES\_OPEN flag
GtkTextView originally has a feature of multi line editing.
Therefore, we don't need to rewrite the program from scratch.
We just add two things to the file viewer.
GtkTextView, GtkTextBuffer and GtkScrolledWindow have given us a minimum editor in the previous section.
Next, we will add a read function to this program and remake it into a file viewer.
There are many way to implement the function.
However, because this is a tutorial for beginners, we take the simplest way.
- Static memory is needed to store a pointer to GFile.
- We need to implement file write function.
When the program starts, we give a filename as an argument.
A couple of ways are possible to get memories to keep GFile.
$ ./a.out filename
- Use global variables.
- make a child widget object and extend the memories allocated to the widget.
Then it opens the file and set it into GtkTextBuffer.
Using global variables is easy to implement.
Define a sufficient size array of pointers to GFile.
For example,
At the beginning of the implementation, we need to know how GtkApplication (or GApplication) recognizes arguments.
It is described in the GIO API reference.
GFile *f[20];
When GtkApplication is generated, a flag (its type is GApplicationFlags) is given as an argument.
And `f[i]` corresponds to 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.
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.
Generally speaking, the bigger the program size, the more difficult to maintain global variables.
GtkApplication *
gtk_application_new (const gchar *application_id, GApplicationFlags flags);
Making child widget object is a good idea in terms of maintenance.
However, one thing you need to be careful is the difference between "child object" and "child widget".
What we are thinking about now is "child object".
A child object includes its parent object.
And a child widget object derives everything from the parent widget object.
![Child widget of GtkTwxtView](image/child.png)
This flag is described in the GApplication section in GIO API reference.
We will define TfeTextView as a child widget object of GtkTextView.
It has everything that GtkTextView has.
For example, TfeTextView has GtkTextbuffer correspods to GtkTextView inside TfeTextView.
And important thing is that TfeTextView can have a memory to keep a pointer to GFile.
GApplicationFlags' Members
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.
G_APPLICATION_FLAGS_NONE Default. (No argument allowed)
... ... ...
G_APPLICATION_HANDLES_OPEN This application handles opening files (in the primary instance).
... ... ...
## How to define a child widget of GtkTextView
There are ten flags.
But we only need two of them so far.
We've already used `G_APPLICATION_FLAGS_NONE`.
It is the simplest option.
No argument is allowed.
If you give arguments and run the application, then error occurs.
`G_APPLICATION_HANDLES_OPEN` is the second simplest option.
It allows arguments but only files.
The application assumes all the arguments are filenames.
Let's define TfeTextView widget object which is a child object of GtkTextView.
First, look at the program below.
Now we use this flag when generating GtkApplication.
#define TFE_TYPE_TEXT_VIEW tfe_text_view_get_type ()
G_DECLARE_FINAL_TYPE (TfeTextView, tfe_text_view, TFE, TEXT_VIEW, GtkTextView)
app = gtk_application_new ("com.github.ToshioCP.tfv3", G_APPLICATION_HANDLES_OPEN);
struct _TfeTextView
{
GtkTextView parent;
GFile *file;
};
### open signal
G_DEFINE_TYPE (TfeTextView, tfe_text_view, GTK_TYPE_TEXT_VIEW);
When the application starts, two signals are possible.
static void
tfe_text_view_init (TfeTextView *tv) {
- activate signal --- This signal is emitted when there's no argument.
- open signal --- This signal is emitted when there is at least one argument.
The handler of open signal is called as follows.
void user_function (GApplication *application,
gpointer files,
gint n_files,
gchar *hint,
gpointer user_data)
The parameters are as follows:
- application --- the application (usually GtkApplication)
- files --- an array of GFiles. [array length=n_files] [element-type GFile]
- n_files --- the length of files
- hint --- a hint provided by the calling instance (usually it can be ignored)
- user_data --- user data set when the signal handler was connected.
The way how to read a file using GFiles will be described in the next section.
## Coding a file viewer
### What is a file viewer?
A file viewer is a program that shows a text file given as an argument.
It works as follows.
- If it is given arguments, it recognizes the first argument as a filename and open it.
- If opening the file succeeds, read and set it to GtkTextBuffer and show the window.
- If it fails to open the file, show an error message and quit.
- If there's no argument, show an error message and quit.
- If there are two or more arguments, the second one and after are ignored.
The program is as follows.
1 #include <gtk/gtk.h>
2
3 static void
4 on_activate (GApplication *app, gpointer user_data) {
5 g_print ("You need a filename argument.\n");
6 }
7
8 static void
9 on_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) {
10 GtkWidget *win;
11 GtkWidget *scr;
12 GtkWidget *tv;
13 GtkTextBuffer *tb;
14 char *contents;
15 gsize length;
16 char *filename;
17
18 win = gtk_application_window_new (GTK_APPLICATION (app));
19 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
20
21 scr = gtk_scrolled_window_new ();
22 gtk_window_set_child (GTK_WINDOW (win), scr);
23
24 tv = gtk_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_text_view_set_editable (GTK_TEXT_VIEW (tv), FALSE);
28 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
29
30 if (g_file_load_contents (files[0], NULL, &contents, &length, NULL, NULL)) {
31 gtk_text_buffer_set_text (tb, contents, length);
32 g_free (contents);
33 filename = g_file_get_basename (files[0]);
34 gtk_window_set_title (GTK_WINDOW (win), filename);
35 g_free (filename);
36 gtk_widget_show (win);
37 } else {
38 filename = g_file_get_path (files[0]);
39 g_print ("No such file: %s.\n", filename);
40 gtk_window_destroy (GTK_WINDOW (win));
41 }
42 }
43
44 int
45 main (int argc, char **argv) {
46 GtkApplication *app;
47 int stat;
48
49 app = gtk_application_new ("com.github.ToshioCP.tfv3", G_APPLICATION_HANDLES_OPEN);
50 g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
51 g_signal_connect (app, "open", G_CALLBACK (on_open), NULL);
52 stat =g_application_run (G_APPLICATION (app), argc, argv);
53 g_object_unref (app);
54 return stat;
55 }
56
Save it as `tfv3.c`.
Then compile and run it.
$ comp tfv3
$ ./a.out tfv3.c
![File viewer](image/screenshot_tfv3.png)
Now I want to explain the program `tfv3.c`.
First, the function `main` changes in only two lines.
- `G_APPLICATION_FLAGS_NONE` is replaced with `G_APPLICATION_HANDLES_OPEN`.
- `g_signal_connect (app, "open", G_CALLBACK (on_open), NULL)` is added.
Next, the handler `on_activate` is now very simple.
Just output the error message.
The application quits immediately because no window is generated.
The point is the handler `on_open`.
- It generates GtkApplicationWindow, GtkScrolledWindow, GtkTextView and GtkTextBuffer and connect them.
- Set wrap mode to `GTK_WRAP_WORD_CHAR` in GtktextView.
- Set non-editable to GtkTextView because the program isn't an editor but only a viewer.
- Read the file and set it to GtkTextBuffer (this will be explained in detail later).
- If the file is not opened then output an error message and destroy the window. It makes the application quit.
The file reading part of the program is shown again below.
if (g_file_load_contents(files[0], NULL, &contents, &length, NULL, NULL)) {
gtk_text_buffer_set_text(tb, contents, length);
g_free(contents);
filename = g_file_get_basename(files[0]);
gtk_window_set_title (GTK_WINDOW (win), filename);
g_free(filename);
gtk_widget_show (win);
} else {
filename = g_file_get_path(files[0]);
g_print ("No such file: %s.\n", filename);
gtk_window_destroy (GTK_WINDOW (win));
}
static void
tfe_text_view_class_init (TfeTextViewClass *class) {
}
The function `g_file_load_contents` loads the file contents into a buffer, which is automatically allocated, and set the pointer to the buffer into `contents`.
And the length of the buffer is set to `length`.
It returns `TRUE` if the file's contents were successfully loaded. `FALSE` if there were errors.
void
tfe_text_view_set_file (TfeTextView *tv, GFile *f) {
tv -> file = f;
}
If the function succeeds, set the contents into GtkTextBuffer, free the buffer memories pointed by `contents`, set the filename to the title of the window,
free the memories pointed by `filename` and show the window.
If it fails, it outputs an error message and destroy the window.
GFile *
tfe_text_view_get_file (TfeTextView *tv) {
return tv -> file;
}
## GtkNotebook
GtkWidget *
tfe_text_view_new (void) {
return gtk_widget_new (TFE_TYPE_TEXT_VIEW, NULL);
}
GtkNotebook is a container widget that contains multiple children with tabs in it.
If you are curious about the background theory of this program, It's very good for you.
Because to know the theory is very important for you to program GTK applications.
Look at GObject API reference.
All you need is described in it.
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.
![GtkNotebook](image/screenshot_gtk_notebook.png)
- TfeTextView is divided into two parts.
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 ().
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.
The arguments are the child object name in camel case, lower case with underscore, prefix, object and parent object name.
- Declare the structure \_TfeTextView.
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.
- 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.
- 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-struture declared with the tag \_TfeTextView.
So, the structure has a member `file` as a pointer to GFile.
`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.
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 gtk\_widget\_new function to generate the child widget.
The arguments are (prefix)\_TYPE\_(object), a list to initialize properties and NULL.
In this code no property needs to be initialized.
Look at the screenshots above.
The left one is a window at the startup.
It shows the file `pr1.c`.
The filename is in the left tab.
After clicking on the right tab, then the contents of `tfv1.c` appears.
It is shown in the right screenshot.
This program is not perfect.
It has some problem.
But I don't discuss it now.
It will be modified later.
GtkNotebook widget is between GtkApplicationWindow and GtkScrolledWindow.
Now I want to show you the program `tfv4.c`.
## Close-request signal
1 #include <gtk/gtk.h>
2
3 static void
4 on_activate (GApplication *app, gpointer user_data) {
5 g_print ("You need a filename argument.\n");
6 }
7
8 static void
9 on_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) {
10 GtkWidget *win;
11 GtkWidget *nb;
12 GtkWidget *lab;
13 GtkNotebookPage *nbp;
14 GtkWidget *scr;
15 GtkWidget *tv;
16 GtkTextBuffer *tb;
17 char *contents;
18 gsize length;
19 char *filename;
20 int i;
21
22 win = gtk_application_window_new (GTK_APPLICATION (app));
23 gtk_window_set_title (GTK_WINDOW (win), "file viewer");
24 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
25 gtk_window_maximize (GTK_WINDOW (win));
26
27 nb = gtk_notebook_new ();
28 gtk_window_set_child (GTK_WINDOW (win), nb);
29
30 for (i = 0; i < n_files; i++) {
31 if (g_file_load_contents (files[i], NULL, &contents, &length, NULL, NULL)) {
32 scr = gtk_scrolled_window_new ();
33 tv = gtk_text_view_new ();
34 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
35 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
36 gtk_text_view_set_editable (GTK_TEXT_VIEW (tv), FALSE);
37 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
38
39 gtk_text_buffer_set_text (tb, contents, length);
40 g_free (contents);
41 filename = g_file_get_basename (files[i]);
42 lab = gtk_label_new (filename);
43 gtk_notebook_append_page (GTK_NOTEBOOK (nb), scr, lab);
44 nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr);
45 g_object_set (nbp, "tab-expand", TRUE, NULL);
46 g_free (filename);
47 } else {
48 filename = g_file_get_path (files[i]);
49 g_print ("No such file: %s.\n", filename);
50 g_free (filename);
51 }
52 }
53 if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0)
54 gtk_widget_show (win);
55 else
56 gtk_window_destroy (GTK_WINDOW (win));
57 }
58
59 int
60 main (int argc, char **argv) {
61 GtkApplication *app;
62 int stat;
63
64 app = gtk_application_new ("com.github.ToshioCP.tfv4", G_APPLICATION_HANDLES_OPEN);
65 g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
66 g_signal_connect (app, "open", G_CALLBACK (on_open), NULL);
67 stat =g_application_run (G_APPLICATION (app), argc, argv);
68 g_object_unref (app);
69 return stat;
70 }
71
As a first step, `tfe1.c` writes 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 the function handler.
Then, the function `before_close` is invoked when the signal "close-request" is emittd.
Most of the change is in the function `on_open`.
The numbers at the left of the following items are line numbers in the source code.
g_signal_connect (win, "close-request", G_CALLBACK (before_close), NULL);
- 11-13: Variables `nb`, `lab` and `nbp` are defined and point GtkNotebook, GtkLabel and GtkNotebookPage respectively.
- 23: The window's title is set to "file viewer".
- 25: The size of the window is set to maximum because a big window is appropriate for file viewers.
- 27-28 GtkNotebook is generated and set it as a child of GtkApplicationWindow.
- 30-52 For-loop. Each loop corresponds to an argument. And files[i] is GFile object with respect to the i-th argument.
- 32-37 GtkScrollledWindow, GtkTextView and GtkTextBuffer are generated and GtkTextView is connected to GtkScrolledWindow as a child.
They corresponds to each file, so they are generated inside the for-loop.
- 39-42 Set the contents of the file into GtkTextBuffer and free the memory pointed by `contents`. Get the filename and generate GtkLabel with the filename.
- 43: Append GtkScrolledWindow and GtkLabel to GtkNotebook. The appended objects are children of automatically generated GtkNotebookPage object. Therefore, the structure is like this:
The argument win is GtkApplicationWindow, in which the signal "close-request" is defined, and before\_close is the handler.
`G_CALLBACK` cast is necessary before the handler.
The program of before\_close is as follows.
GtkNotebook -- GtkNotebookPage -- (GtkScrolledWindow and GtkLabel)
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 gchar *contents; /* gchar is the same as char ... typedef char gchar;*/
10 guint n;
11 guint 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 }
- 44: Get GtkNotebookPage object and set its pointer to `nbp`.
- 45: GtkNotebookPage has a property "tab-expand". If it is set to TRUE then the tab expand horizontally as long as possible. If FALSE, then the width of the tab is determined by the size of the label. `g_object_set` is a general function to set properties in any objects.
- 46: free the memory pointed by `filename`
- 53-56: If at least one file was read, then the number of GtkNotebookPage is greater than zero. If it's true, then show the window. If it's false, then destroy the window.
The numbers on the left of items are line numbers in the source code.
- 13: Get the number of pages `nb` has.
- 14-23: For loop with regard to the index to each pages.
- 15-17: Get GtkScrolledWindow, TfeTextView and a pointer to GFile. The pointer was stored when `on_open` handler ran. It will be shown later.
- 18-20: Get GtkTextBuffer and contents. start\_iter and end\_iter is 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: Write the file.
## Source code of tfe1.c
Now I will show you all the source code of `tfe1`.c.
1 #include <gtk/gtk.h>
2
3 /* Define TfeTextView Widget which is the child object of GtkTextView */
4
5 #define TFE_TYPE_TEXT_VIEW tfe_text_view_get_type ()
6 G_DECLARE_FINAL_TYPE (TfeTextView, tfe_text_view, TFE, TEXT_VIEW, GtkTextView)
7
8 struct _TfeTextView
9 {
10 GtkTextView parent;
11 GFile *file;
12 };
13
14 G_DEFINE_TYPE (TfeTextView, tfe_text_view, GTK_TYPE_TEXT_VIEW);
15
16 static void
17 tfe_text_view_init (TfeTextView *tv) {
18 }
19
20 static void
21 tfe_text_view_class_init (TfeTextViewClass *class) {
22 }
23
24 void
25 tfe_text_view_set_file (TfeTextView *tv, GFile *f) {
26 tv -> file = f;
27 }
28
29 GFile *
30 tfe_text_view_get_file (TfeTextView *tv) {
31 return tv -> file;
32 }
33
34 GtkWidget *
35 tfe_text_view_new (void) {
36 return gtk_widget_new (TFE_TYPE_TEXT_VIEW, NULL);
37 }
38
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 gchar *contents; /* gchar is the same as char ... typedef char gchar;*/
50 guint n;
51 guint 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 a filename argument.\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;
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
- 102: set 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` duplicate the given GFile structure.
- 118: connect "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.
Now we got a very simple editor.
It's not smart.
We need more features like open, save, saveas, change font and so on.
We will add them in the next section and after.
Up: [Readme.md](Readme.md), Prev: [Section 4](sec4.md), Next: [Section 6](sec6.md)

717
sec6.md
View file

@ -1,451 +1,348 @@
Up: [Readme.md](Readme.md), Prev: [Section 5](sec5.md), Next: [Section 7](sec7.md)
# Ui file and GtkBuiler
# Define Child object
## New, open and save button
## Very simple editor
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.
We made a very simple file viewer in the previous section.
Now we go on to rewrite it and make a very simple editor.
Its source file name is tfe1.c (text file editor 1).
![Screenshot of the file editor](image/screenshot_tfe2.png)
GtkTextView originally has a feature of multi line editing.
Therefore, we don't need to rewrite the program from scratch.
We just add two things to the file viewer.
The screenshot above shows the layout.
The function `on_open` in the source code `tfe2.c` is as follows.
- Static memory is needed to store a pointer to GFile.
- We need to implement file write function.
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 }
A couple of ways are possible to get memories to keep GFile.
The point is how to build the window.
- Use global variables.
- make a child widget object and extend the memories allocated to the widget.
- 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` and `dmy3` has a character width of ten.
The other label `dmy2` 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.
Using global variables is easy to implement.
Define a sufficient size array of pointers to GFile.
For example,
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?
GFile *f[20];
Gtk provides GtkBuilder.
It reads ui data and builds a window.
It reduces the cumbersom work.
And `f[i]` corresponds to 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.
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.
Generally speaking, the bigger the program size, the more difficult to maintain global variables.
## Ui file
Making child widget object is a good idea in terms of maintenance.
However, one thing you need to be careful is the difference between "child object" and "child widget".
What we are thinking about now is "child object".
A child object includes its parent object.
And a child widget object derives everything from the parent widget object.
![Child widget of GtkTwxtView](image/child.png)
First, let's look at the ui file `tfe3.ui` that defines a structure of the widgets.
We will define TfeTextView as a child widget object of GtkTextView.
It has everything that GtkTextView has.
For example, TfeTextView has GtkTextbuffer correspods to GtkTextView inside TfeTextView.
And important thing is that TfeTextView can have a memory to keep a pointer to GFile.
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
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.
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.
## How to define a child widget of GtkTextView
- 2-5: 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.
- 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.
Let's define TfeTextView widget object which is a child object of GtkTextView.
First, look at the program below.
## GtkBuilder
#define TFE_TYPE_TEXT_VIEW tfe_text_view_get_type ()
G_DECLARE_FINAL_TYPE (TfeTextView, tfe_text_view, TFE, TEXT_VIEW, GtkTextView)
GtkBuilder builds widgets based on the ui file.
struct _TfeTextView
{
GtkTextView parent;
GFile *file;
};
GtkBuilder *build;
G_DEFINE_TYPE (TfeTextView, tfe_text_view, GTK_TYPE_TEXT_VIEW);
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"));
static void
tfe_text_view_init (TfeTextView *tv) {
}
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.
static void
tfe_text_view_class_init (TfeTextViewClass *class) {
}
$ 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);
void
tfe_text_view_set_file (TfeTextView *tv, GFile *f) {
tv -> file = f;
}
`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.
GFile *
tfe_text_view_get_file (TfeTextView *tv) {
return tv -> file;
}
Now I'll show you the C source code `tfe3.c`.
Only functions `on_open` are shown as follows.
GtkWidget *
tfe_text_view_new (void) {
return gtk_widget_new (TFE_TYPE_TEXT_VIEW, NULL);
}
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 }
If you are curious about the background theory of this program, It's very good for you.
Because to know the theory is very important for you to program GTK applications.
Look at GObject API reference.
All you need is described in it.
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.
### Using ui string
- TfeTextView is divided into two parts.
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 ().
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.
The arguments are the child object name in camel case, lower case with underscore, prefix, object and parent object name.
- Declare the structure \_TfeTextView.
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.
- 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.
- 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-struture declared with the tag \_TfeTextView.
So, the structure has a member `file` as a pointer to GFile.
`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.
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 gtk\_widget\_new function to generate the child widget.
The arguments are (prefix)\_TYPE\_(object), a list to initialize properties and NULL.
In this code no property needs to be initialized.
GtkBuilder can build widgets using string.
Use the function gtk\_builder\_new\_from\_string instead of gtk\_builder\_new\_from\_file.
This program is not perfect.
It has some problem.
But I don't discuss it now.
It will be modified later.
char *uistring;
## Close-request signal
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>";
As a first step, `tfe1.c` writes 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 the function handler.
Then, the function `before_close` is invoked when the signal "close-request" is emittd.
build = gtk_builder_new_from_stringfile (uistring);
g_signal_connect (win, "close-request", G_CALLBACK (before_close), NULL);
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.
The argument win is GtkApplicationWindow, in which the signal "close-request" is defined, and before\_close is the handler.
`G_CALLBACK` cast is necessary before the handler.
The program of before\_close is as follows.
- add backslash before each double quote.
- add double quote at the left and right.
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 gchar *contents; /* gchar is the same as char ... typedef char gchar;*/
10 guint n;
11 guint 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 }
### Using Gresource
The numbers on the left of items are line numbers in the source code.
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.
- 13: Get the number of pages `nb` has.
- 14-23: For loop with regard to the index to each pages.
- 15-17: Get GtkScrolledWindow, TfeTextView and a pointer to GFile. The pointer was stored when `on_open` handler ran. It will be shown later.
- 18-20: Get GtkTextBuffer and contents. start\_iter and end\_iter is 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: Write the file.
An xml file is necessary for the resource compiler `glib-compile-resources`.
It describes resource files.
## Source code of tfe1.c
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>
Now I will show you all the source code of `tfe1`.c.
- 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.
1 #include <gtk/gtk.h>
2
3 /* Define TfeTextView Widget which is the child object of GtkTextView */
4
5 #define TFE_TYPE_TEXT_VIEW tfe_text_view_get_type ()
6 G_DECLARE_FINAL_TYPE (TfeTextView, tfe_text_view, TFE, TEXT_VIEW, GtkTextView)
7
8 struct _TfeTextView
9 {
10 GtkTextView parent;
11 GFile *file;
12 };
13
14 G_DEFINE_TYPE (TfeTextView, tfe_text_view, GTK_TYPE_TEXT_VIEW);
15
16 static void
17 tfe_text_view_init (TfeTextView *tv) {
18 }
19
20 static void
21 tfe_text_view_class_init (TfeTextViewClass *class) {
22 }
23
24 void
25 tfe_text_view_set_file (TfeTextView *tv, GFile *f) {
26 tv -> file = f;
27 }
28
29 GFile *
30 tfe_text_view_get_file (TfeTextView *tv) {
31 return tv -> file;
32 }
33
34 GtkWidget *
35 tfe_text_view_new (void) {
36 return gtk_widget_new (TFE_TYPE_TEXT_VIEW, NULL);
37 }
38
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 gchar *contents; /* gchar is the same as char ... typedef char gchar;*/
50 guint n;
51 guint 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 a filename argument.\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;
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
Save this xml text to `tfe3.gresource.xml`.
The gresource compiler `glib-compile-resources` shows its ussage with the argument `--help`.
- 102: set 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` duplicate the given GFile structure.
- 118: connect "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.
$ 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.
Now compile and run it.
Type `./a.out somefile` and make sure that the file is modified.
Now we got a very simple editor.
It's not smart.
We need more features like open, save, saveas, change font and so on.
We will add them in the next section and after.
Up: [Readme.md](Readme.md), Prev: [Section 5](sec5.md), Next: [Section 7](sec7.md)

695
sec7.md
View file

@ -1,168 +1,139 @@
Up: [Readme.md](Readme.md), Prev: [Section 6](sec6.md), Next: [Section 8](sec8.md)
# Build system
# Ui file and GtkBuiler
## What do we need to think about building?
## New, open and save button
We've managed to compile a small editor so far.
But Some bad signs are beginning to appear.
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.
- We have only one C source file and put everything into it.
We need to sort it out.
- There are two compilers, `gcc` and `glib-compile-resources`.
We want to control them by one building tool.
![Screenshot of the file editor](image/screenshot_tfe2.png)
## Divide a C source file into two parts.
The screenshot above shows the layout.
The function `on_open` in the source code `tfe2.c` is as follows.
When you divide C source file into several parts, each file should contain only one thing.
For example, our source has two things, the definition of TfeTextView and functions related to GtkApplication and GtkApplicationWindow.
It is a good idea to separate them into two files, `tfetextview.c` and `tfe.c`.
- `tfetextview.c` includes the definition and functions of TfeTextView.
- `tfe.c` includes functions like `main`, `on_activate`, `on_open` and so on, which relate to GtkApplication and GtkApplicationWindow
Now we have three source files, `tfetextview.c`, `tfe.c` and `tfe3.ui`.
The `3` of `tfe3.ui` is like a version number.
Managing version with filenames is one possible idea but it may make bothersome complicated problem.
You need to rewrite filename in each version and it affects to contents of sourcefiles that refer to filenames.
So, we should take `3` away from the filename.
In `tfe.c` the function `tfe_text_view_new` is invoked to generate TfeTextView.
But it is defined in `tfetextview.c`, not `tfe.c`.
The lack of the declaration (not definition) of `tfe_text_view_new` makes error when `tfe.c` is compiled.
The declaration is necessary in `tfe.c`.
Those public information is usually written in header files.
It has `.h` suffix like `tfetextview.h`
And header files are included by C source files.
For example, `tfetextview.h` is included by `tfe.c`.
`tfetextview.h`
1 #include <gtk/gtk.h>
2
3 #define TFE_TYPE_TEXT_VIEW tfe_text_view_get_type ()
4 G_DECLARE_FINAL_TYPE (TfeTextView, tfe_text_view, TFE, TEXT_VIEW, GtkTextView)
5
6 void
7 tfe_text_view_set_file (TfeTextView *tv, GFile *f);
8
9 GFile *
10 tfe_text_view_get_file (TfeTextView *tv);
11
12 GtkWidget *
13 tfe_text_view_new (void);
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
`tfetextview.c`
1 #include <gtk/gtk.h>
2 #include "tfetextview.h"
3
4 struct _TfeTextView
5 {
6 GtkTextView parent;
7 GFile *file;
8 };
9
10 G_DEFINE_TYPE (TfeTextView, tfe_text_view, GTK_TYPE_TEXT_VIEW);
11
12 static void
13 tfe_text_view_init (TfeTextView *tv) {
14 }
15
16 static void
17 tfe_text_view_class_init (TfeTextViewClass *class) {
18 }
19
20 void
21 tfe_text_view_set_file (TfeTextView *tv, GFile *f) {
22 tv -> file = f;
23 }
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 GFile *
26 tfe_text_view_get_file (TfeTextView *tv) {
27 return tv -> file;
28 }
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 GtkWidget *
31 tfe_text_view_new (void) {
32 return gtk_widget_new (TFE_TYPE_TEXT_VIEW, NULL);
33 }
34
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 }
`tfe.c`
The point is how to build the window.
1 #include <gtk/gtk.h>
2 #include "tfetextview.h"
3
4 static void
5 on_activate (GApplication *app, gpointer user_data) {
6 g_print ("You need a filename argument.\n");
7 }
8
9 static void
10 on_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) {
11 GtkWidget *win;
12 GtkWidget *nb;
13 GtkWidget *lab;
14 GtkNotebookPage *nbp;
15 GtkWidget *scr;
16 GtkWidget *tv;
17 GtkTextBuffer *tb;
18 char *contents;
19 gsize length;
20 char *filename;
21 int i;
22 GtkBuilder *build;
23
24 build = gtk_builder_new_from_resource ("/com/github/ToshioCP/tfe3/tfe.ui");
25 win = GTK_WIDGET (gtk_builder_get_object (build, "win"));
26 gtk_window_set_application (GTK_WINDOW (win), GTK_APPLICATION (app));
27 nb = GTK_WIDGET (gtk_builder_get_object (build, "nb"));
28 g_object_unref (build);
29 for (i = 0; i < n_files; i++) {
30 if (g_file_load_contents (files[i], NULL, &contents, &length, NULL, NULL)) {
31 scr = gtk_scrolled_window_new (NULL, NULL);
32 tv = tfe_text_view_new ();
33 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
34 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
35 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
36
37 tfe_text_view_set_file (TFE_TEXT_VIEW (tv), g_file_dup (files[i]));
38 gtk_text_buffer_set_text (tb, contents, length);
39 g_free (contents);
40 filename = g_file_get_basename (files[i]);
41 lab = gtk_label_new (filename);
42 gtk_notebook_append_page (GTK_NOTEBOOK (nb), scr, lab);
43 nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr);
44 g_object_set (nbp, "tab-expand", TRUE, NULL);
45 g_free (filename);
46 } else {
47 filename = g_file_get_path (files[i]);
48 g_print ("No such file: %s.\n", filename);
49 g_free (filename);
50 }
51 }
52 if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) {
53 gtk_widget_show (win);
54 } else
55 gtk_window_destroy (GTK_WINDOW (win));
56 }
57
58 int
59 main (int argc, char **argv) {
60 GtkApplication *app;
61 int stat;
62
63 app = gtk_application_new ("com.github.ToshioCP.tfe3", G_APPLICATION_HANDLES_OPEN);
64 g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
65 g_signal_connect (app, "open", G_CALLBACK (on_open), NULL);
66 stat =g_application_run (G_APPLICATION (app), argc, argv);
67 g_object_unref (app);
68 return stat;
69 }
70
- 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` and `dmy3` has a character width of ten.
The other label `dmy2` 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.
`tfe.ui`
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">
@ -224,199 +195,257 @@ For example, `tfetextview.h` is included by `tfe.c`.
58 </interface>
59
`tfe.gresource.xml`
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 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.
- 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>tfe.ui</file>
4 <file>tfe3.ui</file>
5 </gresource>
6 </gresources>
## Make
- 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.
Dividing a file makes it easy to maintain source files.
But now we are faced with a new problem.
The building step increases.
Save this xml text to `tfe3.gresource.xml`.
The gresource compiler `glib-compile-resources` shows its ussage with the argument `--help`.
- Compile the ui file `tfe.ui` into `resources.c`.
- Compile `tfe.c` into `tfe.o` (object file).
- Compile `tfetextview.c` into `tfetextview.o`.
- Compile `resources.c` into `resources.o`.
- Link all the object files into application `tfe`.
$ 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 build tool is necessary to manage it.
Make is one of the build tools.
It was originally created in 1976.
So it is an old and widely used program.
Now run the compiler.
Make analyzes Makefile and executes compilers.
All instructions are written in Makefile.
$ glib-compile-resources tfe3.gresource.xml --target=resources.c --generate-source
sample.o: sample.c
gcc -o sample.o sample.c
Then a C source file `resources.c` is generated.
Modify tfe3.c and save it as tfe3_r.c
The sample of Malefile above consists of three elements, `sample.o`, `sample.c` and `gcc -0 sample.o sample.c`.
# include "resources.c"
... ... ...
... ... ...
build = gtk_builder_new_from_resource ("/com/github/ToshioCP/tfe3/tfe3.ui");
... ... ...
... ... ...
- `sample.o` is called target.
- `sample.c` is prerequisite.
- `gcc -0 sample.o sample.c` is recipe.
Recipes follow tab characters, not spaces.
(It is very important. Use tab not space, or make won't work as you expected).
The rule is:
If a prerequisite modified later than a target, then make executes the recipe.
In the example above, if `sample.c` is modified after the generation of `sample.o`, then make executes gcc and compile `sample.c` into `sample.o`.
If the modification time of `sample.c` is older then the generation of `sample.o`, then no compiling is necesarry, so make does nothing.
The Makefile for `tfe` is as follows.
1 all: tfe
2
3 tfe: tfe.o tfetextview.o resources.o
4 gcc -o tfe tfe.o tfetextview.o resources.o `pkg-config --libs gtk4`
5
6 tfe.o: tfe.c tfetextview.h
7 gcc -c -o tfe.o `pkg-config --cflags gtk4` tfe.c
8 tfetextview.o: tfetextview.c tfetextview.h
9 gcc -c -o tfetextview.o `pkg-config --cflags gtk4` tfetextview.c
10 resources.o: resources.c
11 gcc -c -o resources.o `pkg-config --cflags gtk4` resources.c
12
13 resources.c: tfe.gresource.xml tfe.ui
14 glib-compile-resources tfe.gresource.xml --target=resources.c --generate-source
15
16 .Phony: clean
17
18 clean:
19 rm -f tfe tfe.o tfetextview.o resources.o resources.c
Only you need is to type `make`.
$ make
gcc -c -o tfe.o `pkg-config --cflags gtk4` tfe.c
gcc -c -o tfetextview.o `pkg-config --cflags gtk4` tfetextview.c
glib-compile-resources tfe.gresource.xml --target=resources.c --generate-source
gcc -c -o resources.o `pkg-config --cflags gtk4` resources.c
gcc -o tfe tfe.o tfetextview.o resources.o `pkg-config --libs gtk4`
I used only very basic rules to write this Makefile.
There are many more convenient methods to make it more compact.
But it needs long story to explain.
So I want to finish the explanation about make.
## Rake
Rake is a similar program to make.
It is written in Ruby code.
If you don't use Ruby, you don't need to read this subsection.
However, Ruby is really sophisticated and recommendable script language.
- Rakefile controls the behavior of `rake`.
- You can write any ruby code in Rakefile.
Rake has task and file task, which is similar to target, prerequisite and recipe in make.
1 require 'rake/clean'
2
3 targetfile = "tfe"
4 srcfiles = FileList["tfe.c", "tfetextview.c", "resources.c"]
5 rscfile = srcfiles[2]
6 objfiles = srcfiles.gsub(/.c$/, '.o')
7
8 CLEAN.include(targetfile, objfiles, rscfile)
9
10 task default: targetfile
11
12 file targetfile => objfiles do |t|
13 sh "gcc -o #{t.name} #{t.prerequisites.join(' ')} `pkg-config --libs gtk4`"
14 end
15
16 objfiles.each do |obj|
17 src = obj.gsub(/.o$/,'.c')
18 file obj => src do |t|
19 sh "gcc -c -o #{t.name} `pkg-config --cflags gtk4` #{t.source} "
20 end
21 end
22
23 file rscfile => ["tfe.gresource.xml", "tfe.ui"] do |t|
24 sh "glib-compile-resources #{t.prerequisites[0]} --target=#{t.name} --generate-source"
25 end
What `Rakefile` describes is almost same as `Makefile` in the previous subsection.
- 3-6: define target file, source file and so on.
- 1, 8: Load clean library. And define CLEAN file list.
The files included by CLEAN will be removed when `rake clean` is typed on the command line.
- 10: default target depends on targetfile.
default is the final goal of tasks.
- 12-14: targetfile depends on objfiles.
The variable `t` is a task object.
- t.name is a target name
- t.prerequisites is an array of prerequisits.
- t.source is the first element of prerequisites.
- sh is a method to give the following string to shell as an argument and execute.
- 16-21: Loop by each element of the array of objfiles. Each object depends on corresponding source file.
- 23-25: resouce file depends on xml file and ui file.
Rakefile might seem to be difficult for beginners.
But, you can use any ruby syntax in Rakefile, so it is really flexible.
If you practice Ruby and Rakefile, it will be highly productive tools.
## Meson and ninja
Meson is one of the most popular building tool despite the developing version.
And ninja is similar to make but much faster than make.
Several years ago, most of the C developers used autotools and make.
But now the situation has changed.
Many developers are using meson and ninja now.
To use meson, you first need to write `meson.build` file.
1 project('tfe', 'c')
2
3 gtkdep = dependency('gtk4')
4
5 gnome=import('gnome')
6 resources = gnome.compile_resources('resources','tfe.gresource.xml')
7
8 sourcefiles=files('tfe.c', 'tfetextview.c')
9
10 executable('tfe', sourcefiles, resources, dependencies: gtkdep)
- 1: The function `project` defines things about the project.
The first parameter is the name of the project and the second is the programing language.
- 2: `dependency` function defines a dependency that is taken by `pkg-config`.
We put `gtk4` as an argument.
- 5: `import` function inports a module.
In line 5, gnome module is imported and assignd to the variable `gnome`.
gnome module provides helper tools to build GTK programs.
- 6: `.compile_resources` is a method of gnome module and compile files to resources under the instruction of xml file.
In line 6, the resource filename is `resources`, which means `resources.c` and `resources.h`, and xml file is `tfe.gresource.xml`.
This method generates C source file by default.
- 8: define source files.
- 10: executable function generates a target file by building source files.
The first parameter is the filename of the target. The following parameters are source files.
The last parameter has a option `dependencies`.
In line 10 it is `gtkdep` which is defined in line 3.
Now run meson and ninja.
$ meson _build
$ ninja -C _build
Then, the executable file `tfe` has been generated under the directory `_build`.
$ _build/tfe tfe.c tfetextview.c
Then the window appears.
I show you three build tools.
I think meson and ninja is the best choice for the present.
We divided a file into some categorized files and used a build tool.
This method is used by many developers.
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](Readme.md), Prev: [Section 6](sec6.md), Next: [Section 8](sec8.md)

753
sec8.md
View file

@ -1,427 +1,422 @@
Up: [Readme.md](Readme.md), Prev: [Section 7](sec7.md), Next: [Section 9](sec9.md)
# Instance and class
# Build system
This section and the following four sections are descriptions about 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.
## What do we need to think about building?
## Encapsulation
We've managed to compile a small editor so far.
But Some bad signs are beginning to appear.
We've divided C source file into two parts.
But it is not enough in terms of encapsulation.
- We have only one C source file and put everything into it.
We need to sort it out.
- There are two compilers, `gcc` and `glib-compile-resources`.
We want to control them by one building tool.
- `tfe.c` includes everything other than TfeTextView.
It should be divided at least into two parts, `tfeapplication.c` and `tfenotebook.c`.
- Header files also need to be organized.
## Divide a C source file into two parts.
However, first of all, I'd like to focus on the object TfeTextView.
It is a child object of GtkTextView.
And important thing is it has newly added Gfile in it.
When you divide C source file into several parts, each file should contain only one thing.
For example, our source has two things, the definition of TfeTextView and functions related to GtkApplication and GtkApplicationWindow.
It is a good idea to separate them into two files, `tfetextview.c` and `tfe.c`.
- What is necessary to GFile when generating (or initializing) TfeTextView?
- What is necessary to GFile when destructing TfeTextView?
- TfeTextView should read/write a file by itself or not?
- How it communicate with objects outside?
- `tfetextview.c` includes the definition and functions of TfeTextView.
- `tfe.c` includes functions like `main`, `on_activate`, `on_open` and so on, which relate to GtkApplication and GtkApplicationWindow
You need to know at least class/instance and signals before thinking about them.
I will explain them in this section and the next section.
After that I will explain:
Now we have three source files, `tfetextview.c`, `tfe.c` and `tfe3.ui`.
The `3` of `tfe3.ui` is like a version number.
Managing version with filenames is one possible idea but it may make bothersome complicated problem.
You need to rewrite filename in each version and it affects to contents of sourcefiles that refer to filenames.
So, we should take `3` away from the filename.
- Organizing functions.
- How to use FileChooserDialog
In `tfe.c` the function `tfe_text_view_new` is invoked to generate TfeTextView.
But it is defined in `tfetextview.c`, not `tfe.c`.
The lack of the declaration (not definition) of `tfe_text_view_new` makes error when `tfe.c` is compiled.
The declaration is necessary in `tfe.c`.
Those public information is usually written in header files.
It has `.h` suffix like `tfetextview.h`
And header files are included by C source files.
For example, `tfetextview.h` is included by `tfe.c`.
## GObject and its children
`tfetextview.h`
GObject and its children are objects, which have both class and instance.
First, think about instance of objects.
Instance is structured memories and the structure is described using C language structure.
The following is a structure of TfeTextView.
/* This typedef statement is automaticaly generated by the macro G_DECLARE_FINAL_TYPE */
typedef struct _TfeTextView TfeTextView;
struct _TfeTextView {
GtkTextView parent;
GtkTextBuffer *tb;
GFile *file;
gboolean changed;
};
Each instance has similar structure as above.
- `parent` is the structure of GtkTextView which is the parent object of TfeTextView.
- `tb` is a pointer to GtkTextBuffer connected to GtkTextView.
- `file` is a pointer to GFile which is a file corresponds to `tb` (or NULL is available).
- `changed` is TRUE if the buffer has been modified, FALSE if not.
Comparing to the source file in the previous section, `tb` and `changed` are added.
Notice the program above is the declaration of the structure, not the definition.
So, no memories are allocated at this moment.
They are to be allocated when `tfe_text_view_new` function is invoked.
You can find the declaration of the ancestors of TfeTextView in the sourcefiles of GTK and GLib.
The following is extracts from the source files (not exactly the same).
typedef struct _GObject GObject;
typedef struct _GObject GInitiallyUnowned;
struct _GObject
{
GTypeInstance g_type_instance;
volatile guint ref_count;
GData *qdata;
};
typedef struct _GtkWidget GtkWidget;
struct _GtkWidget
{
GInitiallyUnowned parent_instance;
GtkWidgetPrivate *priv;
};
typedef struct _GtkTextView GtkTextView;
struct _GtkTextView
{
GtkWidget parent_instance;
GtkTextViewPrivate *priv;
};
In each structure, its parent instance is declared at the top of the members.
So, every ancestors is included in the child instance.
This is very important.
It guarantees a child widget to derive all the features from ancestors.
The structure of `TfeTextView` is like the following diagram.
![The structure of the instance TfeTextView](image/TfeTextView.png)
## Generate TfeTextView instance
The function `tfe_text_view_new` generates a new TfeTextView instance.
1 GtkWidget *
2 tfe_text_view_new (void) {
3 return gtk_widget_new (TFE_TYPE_TEXT_VIEW, NULL);
4 }
When this function is run, the following procedure is gone through.
1. Initialize GObject instance in TfeTextView instance.
2. Initialize GtkWidget instance in TfeTextView instance.
3. Initialize GtkTextView instance in TfeTextView instance.
4. Initialize TfeTextView instance.
Step one through three is done automatically.
Step four is done by the function `tfe_text_view_init`.
> (In the same way, `gtk_text_view_init`, `gtk_widget_init` and `g_object_init` is the initialization functions of GtkTextView, GtkWidget and GObject respectively.
> You can find them in the GTK or GLib source file.)
1 static void
2 on_changed (GtkTextBuffer *tb, TfeTextView *tv) {
3 tv->changed=TRUE;
4 }
1 #include <gtk/gtk.h>
2
3 #define TFE_TYPE_TEXT_VIEW tfe_text_view_get_type ()
4 G_DECLARE_FINAL_TYPE (TfeTextView, tfe_text_view, TFE, TEXT_VIEW, GtkTextView)
5
6 static void
7 tfe_text_view_init (TfeTextView *tv) {
8 tv->tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
9 tv->file = NULL;
10 tv->changed = FALSE;
11 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
12 g_signal_connect (tv->tb, "changed", G_CALLBACK (on_changed), tv);
13 }
6 void
7 tfe_text_view_set_file (TfeTextView *tv, GFile *f);
8
9 GFile *
10 tfe_text_view_get_file (TfeTextView *tv);
11
12 GtkWidget *
13 tfe_text_view_new (void);
14
`tfe_text_view_init` initializes the instance.
`tfetextview.c`
- 8-10: Initialize `tb`, `file` and `changed`.
- 11: Set the wrap mode of GtkTextView as GTK\_WRAP\_WORD\_CHAR.
- 12: Connect "changed" signal to a handler `on_changed`.
"changed" signal is defined in GtkTextBuffer.
It is emitted when the contents in the buffer is changed.
- 2-4: `on_changed` handler records TRUE to `tv->changed` when "changed" signal is emitted.
## Functions and Classes
In Gtk, all objects derived from GObject have class and instance.
Instance is memories which has a structure defined by C structure declaration as I mentioned in the previous two subsections.
And instance can be generated two or more.
Those instances have the same structure.
However, structured memories are insufficient to define its behavior.
We need at least two things.
One is functions and the other is class.
You've already seen many functions, for example, `tfe_text_view_new` is a function to generate TfeTextView instance.
These functions are similar to object methods in object oriented languages such as Java and Ruby.
Functions are public, which means that they are expected to be used by other objects.
Class comprises mainly pointers to functions.
And the functions are used by the object itself or its children objects.
For example, GObject class is declared in `gobject.h` in GLib source files.
1 typedef struct _GObjectClass GObjectClass;
2 typedef struct _GObjectClass GInitiallyUnownedClass;
1 #include <gtk/gtk.h>
2 #include "tfetextview.h"
3
4 struct _GObjectClass {
5 GTypeClass g_type_class;
6 /*< private >*/
7 GSList *construct_properties;
8 /*< public >*/
9 /* seldom overidden */
10 GObject* (*constructor) (GType type,
11 guint n_construct_properties,
12 GObjectConstructParam *construct_properties);
13 /* overridable methods */
14 void (*set_property) (GObject *object,
15 guint property_id,
16 const GValue *value,
17 GParamSpec *pspec);
18 void (*get_property) (GObject *object,
19 guint property_id,
20 GValue *value,
21 GParamSpec *pspec);
22 void (*dispose) (GObject *object);
23 void (*finalize) (GObject *object);
24 /* seldom overidden */
25 void (*dispatch_properties_changed) (GObject *object,
26 guint n_pspecs,
27 GParamSpec **pspecs);
28 /* signals */
29 void (*notify) (GObject *object,
30 GParamSpec *pspec);
31
32 /* called when done constructing */
33 void (*constructed) (GObject *object);
34 /*< private >*/
35 gsize flags;
36 /* padding */
37 gpointer pdummy[6];
38 };
4 struct _TfeTextView
5 {
6 GtkTextView parent;
7 GFile *file;
8 };
9
10 G_DEFINE_TYPE (TfeTextView, tfe_text_view, GTK_TYPE_TEXT_VIEW);
11
12 static void
13 tfe_text_view_init (TfeTextView *tv) {
14 }
15
16 static void
17 tfe_text_view_class_init (TfeTextViewClass *class) {
18 }
19
20 void
21 tfe_text_view_set_file (TfeTextView *tv, GFile *f) {
22 tv -> file = f;
23 }
24
25 GFile *
26 tfe_text_view_get_file (TfeTextView *tv) {
27 return tv -> file;
28 }
29
30 GtkWidget *
31 tfe_text_view_new (void) {
32 return gtk_widget_new (TFE_TYPE_TEXT_VIEW, NULL);
33 }
34
I'd like to explain some of the members.
There's a pointer to the function `dispose` in line 22.
`tfe.c`
void (*dispose) (GObject *object);
1 #include <gtk/gtk.h>
2 #include "tfetextview.h"
3
4 static void
5 on_activate (GApplication *app, gpointer user_data) {
6 g_print ("You need a filename argument.\n");
7 }
8
9 static void
10 on_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) {
11 GtkWidget *win;
12 GtkWidget *nb;
13 GtkWidget *lab;
14 GtkNotebookPage *nbp;
15 GtkWidget *scr;
16 GtkWidget *tv;
17 GtkTextBuffer *tb;
18 char *contents;
19 gsize length;
20 char *filename;
21 int i;
22 GtkBuilder *build;
23
24 build = gtk_builder_new_from_resource ("/com/github/ToshioCP/tfe3/tfe.ui");
25 win = GTK_WIDGET (gtk_builder_get_object (build, "win"));
26 gtk_window_set_application (GTK_WINDOW (win), GTK_APPLICATION (app));
27 nb = GTK_WIDGET (gtk_builder_get_object (build, "nb"));
28 g_object_unref (build);
29 for (i = 0; i < n_files; i++) {
30 if (g_file_load_contents (files[i], NULL, &contents, &length, NULL, NULL)) {
31 scr = gtk_scrolled_window_new (NULL, NULL);
32 tv = tfe_text_view_new ();
33 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
34 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
35 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
36
37 tfe_text_view_set_file (TFE_TEXT_VIEW (tv), g_file_dup (files[i]));
38 gtk_text_buffer_set_text (tb, contents, length);
39 g_free (contents);
40 filename = g_file_get_basename (files[i]);
41 lab = gtk_label_new (filename);
42 gtk_notebook_append_page (GTK_NOTEBOOK (nb), scr, lab);
43 nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr);
44 g_object_set (nbp, "tab-expand", TRUE, NULL);
45 g_free (filename);
46 } else {
47 filename = g_file_get_path (files[i]);
48 g_print ("No such file: %s.\n", filename);
49 g_free (filename);
50 }
51 }
52 if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) {
53 gtk_widget_show (win);
54 } else
55 gtk_window_destroy (GTK_WINDOW (win));
56 }
57
58 int
59 main (int argc, char **argv) {
60 GtkApplication *app;
61 int stat;
62
63 app = gtk_application_new ("com.github.ToshioCP.tfe3", G_APPLICATION_HANDLES_OPEN);
64 g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
65 g_signal_connect (app, "open", G_CALLBACK (on_open), NULL);
66 stat =g_application_run (G_APPLICATION (app), argc, argv);
67 g_object_unref (app);
68 return stat;
69 }
70
The declaration is a bit complicated.
The asterisk before the identifier `dispose` means pointer.
So, the pointer `disopse` points a function which has one parameter , which points a GObject structure, and returns no value because of void type.
In the same way, line 23 says `finalize` is a pointer to the function which has one paremeter, which points a GObject structure, and returns no value.
`tfe.ui`
void (*finalize) (GObject *object);
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
Look at the declaration of `_GObjectClass` so that you would find that most of the members are pointers to functions.
`tfe.gresource.xml`
- 10: A function pointed by `constructor` is called when the instance is generated. It completes the initialization of the instance.
- 22: A function pointed by `dispose` is called when the instance destructs itself. Destruction process is divided into two phases. First is called disposing and the instance releases all the references to other instances. The second is finalizing.
- 23: A funtion pointed by `finalize` finishes the destruction process.
- The other pointers point functions which are called during the instance lives.
1 <?xml version="1.0" encoding="UTF-8"?>
2 <gresources>
3 <gresource prefix="/com/github/ToshioCP/tfe3">
4 <file>tfe.ui</file>
5 </gresource>
6 </gresources>
## TfeTextView class
## Make
TfeTextView class is a structure and it includes all its ancestors' class in it.
Let's look at all the classes from GObject, which is the top level object, to TfeTextView object, which is the lowest.
Dividing a file makes it easy to maintain source files.
But now we are faced with a new problem.
The building step increases.
GObject -- GInitiallyUnowned -- GtkWidget -- GtkTextView -- TfeTextView
- Compile the ui file `tfe.ui` into `resources.c`.
- Compile `tfe.c` into `tfe.o` (object file).
- Compile `tfetextview.c` into `tfetextview.o`.
- Compile `resources.c` into `resources.o`.
- Link all the object files into application `tfe`.
The following is extracts from the source files (not exactly the same).
Now build tool is necessary to manage it.
Make is one of the build tools.
It was originally created in 1976.
So it is an old and widely used program.
1 struct _GtkWidgetClass {
2 GInitiallyUnownedClass parent_class;
3 /*< public >*/
4 guint activate_signal;
5 /* basics */
6 void (* show) (GtkWidget *widget);
7 void (* hide) (GtkWidget *widget);
8 void (* map) (GtkWidget *widget);
9 void (* unmap) (GtkWidget *widget);
10 void (* realize) (GtkWidget *widget);
11 void (* unrealize) (GtkWidget *widget);
12 void (* root) (GtkWidget *widget);
13 void (* unroot) (GtkWidget *widget);
14 void (* size_allocate) (GtkWidget *widget,
15 int width,
16 int height,
17 int baseline);
18 void (* state_flags_changed) (GtkWidget *widget,
19 GtkStateFlags previous_state_flags);
20 void (* direction_changed) (GtkWidget *widget,
21 GtkTextDirection previous_direction);
22 void (* grab_notify) (GtkWidget *widget,
23 gboolean was_grabbed);
24 /* size requests */
25 GtkSizeRequestMode (* get_request_mode) (GtkWidget *widget);
26 void (* measure) (GtkWidget *widget,
27 GtkOrientation orientation,
28 int for_size,
29 int *minimum,
30 int *natural,
31 int *minimum_baseline,
32 int *natural_baseline);
33 /* Mnemonics */
34 gboolean (* mnemonic_activate) (GtkWidget *widget,
35 gboolean group_cycling);
36 /* explicit focus */
37 gboolean (* grab_focus) (GtkWidget *widget);
38 gboolean (* focus) (GtkWidget *widget,
39 GtkDirectionType direction);
40 void (* set_focus_child) (GtkWidget *widget,
41 GtkWidget *child);
42 /* keyboard navigation */
43 void (* move_focus) (GtkWidget *widget,
44 GtkDirectionType direction);
45 gboolean (* keynav_failed) (GtkWidget *widget,
46 GtkDirectionType direction);
47 /* accessibility support
48 */
49 AtkObject * (* get_accessible) (GtkWidget *widget);
50 gboolean (* query_tooltip) (GtkWidget *widget,
51 gint x,
52 gint y,
53 gboolean keyboard_tooltip,
54 GtkTooltip *tooltip);
55 void (* compute_expand) (GtkWidget *widget,
56 gboolean *hexpand_p,
57 gboolean *vexpand_p);
58 void (* css_changed) (GtkWidget *widget,
59 GtkCssStyleChange *change);
60 void (* system_setting_changed) (GtkWidget *widget,
61 GtkSystemSetting settings);
62 void (* snapshot) (GtkWidget *widget,
63 GtkSnapshot *snapshot);
64 gboolean (* contains) (GtkWidget *widget,
65 gdouble x,
66 gdouble y);
67 /*< private >*/
68 GtkWidgetClassPrivate *priv;
69 gpointer padding[8];
70 };
71
72 struct _GtkTextViewClass {
73 GtkWidgetClass parent_class;
74 /*< public >*/
75 void (* move_cursor) (GtkTextView *text_view,
76 GtkMovementStep step,
77 gint count,
78 gboolean extend_selection);
79 void (* set_anchor) (GtkTextView *text_view);
80 void (* insert_at_cursor) (GtkTextView *text_view,
81 const gchar *str);
82 void (* delete_from_cursor) (GtkTextView *text_view,
83 GtkDeleteType type,
84 gint count);
85 void (* backspace) (GtkTextView *text_view);
86 void (* cut_clipboard) (GtkTextView *text_view);
87 void (* copy_clipboard) (GtkTextView *text_view);
88 void (* paste_clipboard) (GtkTextView *text_view);
89 void (* toggle_overwrite) (GtkTextView *text_view);
90 GtkTextBuffer * (* create_buffer) (GtkTextView *text_view);
91 void (* snapshot_layer) (GtkTextView *text_view,
92 GtkTextViewLayer layer,
93 GtkSnapshot *snapshot);
94 gboolean (* extend_selection) (GtkTextView *text_view,
95 GtkTextExtendSelection granularity,
96 const GtkTextIter *location,
97 GtkTextIter *start,
98 GtkTextIter *end);
99 void (* insert_emoji) (GtkTextView *text_view);
100 /*< private >*/
101 gpointer padding[8];
102 };
103
104 /* The following definition is generated by the macro G_DECLARE_FINAL_TYPE
105 typedef struct {
106 GtkTextView parent_class;
107 } TfeTextViewClass;
108
Make analyzes Makefile and executes compilers.
All instructions are written in Makefile.
- 105-107: This three lines are generated by the macro G\_DECLARE\_FINAL\_TYPE.
So, they are not written in either `tfe_text_view.h` or `tfe_text_view.c`.
- 2, 73, 106: Each derived class puts its parent class at the first member of its structure.
It is the same as instance structures.
- Class members in ancesters are open to their child class.
So, they can be changed in `tfe_text_view_class_init` function.
For example, the `dispose` pointer in GObjectClass will be overridden later in `tfe_text_view_class_init`.
(Override is an object oriented programing terminology.
Override is rewriting ancestors' class methods in the child class.)
- Some class methods are often overridden.
`set_property`, `get_property`, `dispose`, `finalize` and `constructed` are such methods.
sample.o: sample.c
gcc -o sample.o sample.c
TfeTextViewClass includes its ancsestors' class in it.
It is illustrated in the following diagram.
The sample of Malefile above consists of three elements, `sample.o`, `sample.c` and `gcc -0 sample.o sample.c`.
![The structure of TfeTextView Class](image/TfeTextViewClass.png)
- `sample.o` is called target.
- `sample.c` is prerequisite.
- `gcc -0 sample.o sample.c` is recipe.
Recipes follow tab characters, not spaces.
(It is very important. Use tab not space, or make won't work as you expected).
## Destruction of TfeTextView
The rule is:
Every Object derived from GObject has a reference count.
If an object A uses an object B, then A keeps a pointr to B in A and at the same time increaces the reference count of B by one with the function `g_object_ref (B)`.
If A doesn't need B any longer, then A discards the pointer to B (usually it is done by assigning NULL to the pointer) and decreaces the reference count of B by one with the function `g_object_unref (B)`.
If a prerequisite modified later than a target, then make executes the recipe.
If two objects A and B refer to C, then the reference count of C is two.
After A used C and if A no longer needs C, A discards the pointer to C and decreases the reference count in C by one.
Now the reference count of C is one.
In the same way, when B no longer needs C, B discards the pointer to C and decreases the reference count in C by one.
At this moment, no object refers C and the reference count of C is zero.
This means C is no longer useful.
Then C destructs itself and finally the memories allocated to C is freed.
In the example above, if `sample.c` is modified after the generation of `sample.o`, then make executes gcc and compile `sample.c` into `sample.o`.
If the modification time of `sample.c` is older then the generation of `sample.o`, then no compiling is necesarry, so make does nothing.
![Reference count of B](image/refcount.png)
The Makefile for `tfe` is as follows.
The idea above is based on an assumption that an object refered by nothing has reference count of zero.
When the reference count drops to zero, the object starts its destruction process.
The destruction process is split in two phases: disposing and finalizing.
In the disposing process, the object invokes the handler pointed by `dispose` in its class to release all references to other objects.
In the finalizing process, it invokes the handler pointed by `finalize` in its class to complete the destruction process.
1 all: tfe
2
3 tfe: tfe.o tfetextview.o resources.o
4 gcc -o tfe tfe.o tfetextview.o resources.o `pkg-config --libs gtk4`
5
6 tfe.o: tfe.c tfetextview.h
7 gcc -c -o tfe.o `pkg-config --cflags gtk4` tfe.c
8 tfetextview.o: tfetextview.c tfetextview.h
9 gcc -c -o tfetextview.o `pkg-config --cflags gtk4` tfetextview.c
10 resources.o: resources.c
11 gcc -c -o resources.o `pkg-config --cflags gtk4` resources.c
12
13 resources.c: tfe.gresource.xml tfe.ui
14 glib-compile-resources tfe.gresource.xml --target=resources.c --generate-source
15
16 .Phony: clean
17
18 clean:
19 rm -f tfe tfe.o tfetextview.o resources.o resources.c
In the destruction process of TfeTextView, the reference count of widgets related to TfeTextView is automatically decreased.
But GFile pointed by `tv->file` needs to decrease its reference count by one.
You must write the code in the dispose handler `tfe_text_view_dispose`.
Only you need is to type `make`.
1 static void
2 tfe_text_view_dispose (GObject *gobject) {
3 TfeTextView *tv = TFE_TEXT_VIEW (gobject);
4
5 if (G_IS_FILE (tv->file))
6 g_clear_object (&tv->file);
7
8 G_OBJECT_CLASS (tfe_text_view_parent_class)->dispose (gobject);
9 }
$ make
gcc -c -o tfe.o `pkg-config --cflags gtk4` tfe.c
gcc -c -o tfetextview.o `pkg-config --cflags gtk4` tfetextview.c
glib-compile-resources tfe.gresource.xml --target=resources.c --generate-source
gcc -c -o resources.o `pkg-config --cflags gtk4` resources.c
gcc -o tfe tfe.o tfetextview.o resources.o `pkg-config --libs gtk4`
- 5,6: If `tv->file` points a GFile, decrease its reference count.
`g_clear_object` decreases the reference count and assigns NULL to `tv->file`. In dispose handlers, we usually use `g_clear_object` rather than `g_object_unref`.
- 8: invoke parent's despose handler. (This will be explained later.)
I used only very basic rules to write this Makefile.
There are many more convenient methods to make it more compact.
But it needs long story to explain.
So I want to finish the explanation about make.
In the desposing process, the object uses the pointer in its class to call the handler.
Therefore, `tfe_text_view_dispose` needs to be registerd in the class when the TfeTextView class is initialized.
The function `tfe_text_view_class_init` is the class initialization function and it is declared in the replacement produced by `G_DEFINE_TYPE` macro.
## Rake
static void
tfe_text_view_class_init (TfeTextViewClass *class) {
GObjectClass *object_class = G_OBJECT_CLASS (class);
Rake is a similar program to make.
It is written in Ruby code.
If you don't use Ruby, you don't need to read this subsection.
However, Ruby is really sophisticated and recommendable script language.
object_class->dispose = tfe_text_view_dispose;
- Rakefile controls the behavior of `rake`.
- You can write any ruby code in Rakefile.
}
Rake has task and file task, which is similar to target, prerequisite and recipe in make.
Each ancestors' class is generated before TfeTextViewClass.
Therefore, there are four classes and each class has a pointer to each dispose handler.
Look at the following diagram.
There are four classes -- GObjectClass (GInitiallyUnownedClass), GtkWidgetClass, GtkTextViewClass and TfeTextViewClass.
Each class has its own dispose handler -- `dh1`, `dh2`, `dh3` and `tfe_text_view_dispose`.
1 require 'rake/clean'
2
3 targetfile = "tfe"
4 srcfiles = FileList["tfe.c", "tfetextview.c", "resources.c"]
5 rscfile = srcfiles[2]
6 objfiles = srcfiles.gsub(/.c$/, '.o')
7
8 CLEAN.include(targetfile, objfiles, rscfile)
9
10 task default: targetfile
11
12 file targetfile => objfiles do |t|
13 sh "gcc -o #{t.name} #{t.prerequisites.join(' ')} `pkg-config --libs gtk4`"
14 end
15
16 objfiles.each do |obj|
17 src = obj.gsub(/.o$/,'.c')
18 file obj => src do |t|
19 sh "gcc -c -o #{t.name} `pkg-config --cflags gtk4` #{t.source} "
20 end
21 end
22
23 file rscfile => ["tfe.gresource.xml", "tfe.ui"] do |t|
24 sh "glib-compile-resources #{t.prerequisites[0]} --target=#{t.name} --generate-source"
25 end
![dispose handers](image/dispose_handler.png)
What `Rakefile` describes is almost same as `Makefile` in the previous subsection.
Now, look at the `tfe_text_view_dispose` program above.
It first releases the reference to GFile object pointed by `tv->file`.
Then it invokes its parent's dispose handler in line 8.
- 3-6: define target file, source file and so on.
- 1, 8: Load clean library. And define CLEAN file list.
The files included by CLEAN will be removed when `rake clean` is typed on the command line.
- 10: default target depends on targetfile.
default is the final goal of tasks.
- 12-14: targetfile depends on objfiles.
The variable `t` is a task object.
- t.name is a target name
- t.prerequisites is an array of prerequisits.
- t.source is the first element of prerequisites.
- sh is a method to give the following string to shell as an argument and execute.
- 16-21: Loop by each element of the array of objfiles. Each object depends on corresponding source file.
- 23-25: resouce file depends on xml file and ui file.
G_OBJECT_CLASS (tfe_text_view_parent_class)->dispose (gobject);
Rakefile might seem to be difficult for beginners.
But, you can use any ruby syntax in Rakefile, so it is really flexible.
If you practice Ruby and Rakefile, it will be highly productive tools.
`tfe_text_view_parent_class`,which is made by `G_DEFINE_TYPE` macro, is a pointer that points the parent object class.
Therefore, `G_OBJECT_CLASS (tfe_text_view_parent_class)->dispose` points the handler `dh3` in the diagram above.
And `gobject` is a pointer to TfeTextView object which is casted as a GObject instanse.
`dh3` releases all the references to objects in the GtkTextView part (it is actually the private area pointed by `prev`) in TfeTextView instance.
After that, `dh3` calls `dh2`, and `dh2` calls `dh1`.
Finally all the references are released.
## Meson and ninja
Meson is one of the most popular building tool despite the developing version.
And ninja is similar to make but much faster than make.
Several years ago, most of the C developers used autotools and make.
But now the situation has changed.
Many developers are using meson and ninja now.
To use meson, you first need to write `meson.build` file.
1 project('tfe', 'c')
2
3 gtkdep = dependency('gtk4')
4
5 gnome=import('gnome')
6 resources = gnome.compile_resources('resources','tfe.gresource.xml')
7
8 sourcefiles=files('tfe.c', 'tfetextview.c')
9
10 executable('tfe', sourcefiles, resources, dependencies: gtkdep)
- 1: The function `project` defines things about the project.
The first parameter is the name of the project and the second is the programing language.
- 2: `dependency` function defines a dependency that is taken by `pkg-config`.
We put `gtk4` as an argument.
- 5: `import` function inports a module.
In line 5, gnome module is imported and assignd to the variable `gnome`.
gnome module provides helper tools to build GTK programs.
- 6: `.compile_resources` is a method of gnome module and compile files to resources under the instruction of xml file.
In line 6, the resource filename is `resources`, which means `resources.c` and `resources.h`, and xml file is `tfe.gresource.xml`.
This method generates C source file by default.
- 8: define source files.
- 10: executable function generates a target file by building source files.
The first parameter is the filename of the target. The following parameters are source files.
The last parameter has a option `dependencies`.
In line 10 it is `gtkdep` which is defined in line 3.
Now run meson and ninja.
$ meson _build
$ ninja -C _build
Then, the executable file `tfe` has been generated under the directory `_build`.
$ _build/tfe tfe.c tfetextview.c
Then the window appears.
I show you three build tools.
I think meson and ninja is the best choice for the present.
We divided a file into some categorized files and used a build tool.
This method is used by many developers.
Up: [Readme.md](Readme.md), Prev: [Section 7](sec7.md), Next: [Section 9](sec9.md)

520
sec9.md
View file

@ -1,163 +1,427 @@
Up: [Readme.md](Readme.md), Prev: [Section 8](sec8.md), Next: [Section 10](sec10.md)
# Signals
# Instance and class
## Signals
This section and the following four sections are descriptions about 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.
In GTK programming, each object is capsulated.
And it is not recommended to use global variables because they tend to make the program complicated.
So, we need something to communicate between objects.
There are two ways to do so.
## Encapsulation
- Functions.
For example, `tb = gtk_text_view_get_buffer (tv)`.
The function caller requests `tv`, which is a GtkTextView object, to give back `tb`, which is a GtkTextBuffer object connected to `tv`.
- Signals.
For example, `activate` signal on GApplication object.
When the application is activated, the signal is emitted.
Then the handler, which has been connected to the signal, is invoked.
We've divided C source file into two parts.
But it is not enough in terms of encapsulation.
The caller of the function or the handler connected to the signal is usually outside of the object.
One of the difference between these two is that the object is active or passive.
In functions the object responds to the caller.
In signals the object actively sends a signal to the handler.
- `tfe.c` includes everything other than TfeTextView.
It should be divided at least into two parts, `tfeapplication.c` and `tfenotebook.c`.
- Header files also need to be organized.
GObject signal can be registered, connected and emitted.
However, first of all, I'd like to focus on the object TfeTextView.
It is a child object of GtkTextView.
And important thing is it has newly added Gfile in it.
1. A signal is registered with the object type on which it can be emitted.
This is done usually when the class is initialized.
2. It is connected to a handler by `g_connect_signal` or its family functions.
3. When it is emmitted, the connected handler is invoked.
- What is necessary to GFile when generating (or initializing) TfeTextView?
- What is necessary to GFile when destructing TfeTextView?
- TfeTextView should read/write a file by itself or not?
- How it communicate with objects outside?
Step one and three are done in the object on which the signal is emitted.
Step two is done outside the objects.
You need to know at least class/instance and signals before thinking about them.
I will explain them in this section and the next section.
After that I will explain:
## Signal registration
- Organizing functions.
- How to use FileChooserDialog
In TfeTextView, two signals are registered.
## GObject and its children
- "change-file" signal.
This signal is emitted when `tv->file` is changed.
- "open-response" signal.
`tfe_text_view_open` function is not able to return the status because of using GtkFileChooserDialog.
This signal is emitted instead of the return value of the function.
GObject and its children are objects, which have both class and instance.
First, think about instance of objects.
Instance is structured memories and the structure is described using C language structure.
The following is a structure of TfeTextView.
Static variable is used to store the signal ID.
If you need to register two or more signals, static array is usually used.
/* This typedef statement is automaticaly generated by the macro G_DECLARE_FINAL_TYPE */
typedef struct _TfeTextView TfeTextView;
enum {
CHANGE_FILE,
OPEN_RESPONSE,
NUMBER_OF_SIGNALS
struct _TfeTextView {
GtkTextView parent;
GtkTextBuffer *tb;
GFile *file;
gboolean changed;
};
static guint tfe_text_view_signals[NUMBER_OF_SIGNALS];
Each instance has similar structure as above.
Signal registration codes are written in the class initialization function.
- `parent` is the structure of GtkTextView which is the parent object of TfeTextView.
- `tb` is a pointer to GtkTextBuffer connected to GtkTextView.
- `file` is a pointer to GFile which is a file corresponds to `tb` (or NULL is available).
- `changed` is TRUE if the buffer has been modified, FALSE if not.
Comparing to the source file in the previous section, `tb` and `changed` are added.
Notice the program above is the declaration of the structure, not the definition.
So, no memories are allocated at this moment.
They are to be allocated when `tfe_text_view_new` function is invoked.
You can find the declaration of the ancestors of TfeTextView in the sourcefiles of GTK and GLib.
The following is extracts from the source files (not exactly the same).
typedef struct _GObject GObject;
typedef struct _GObject GInitiallyUnowned;
struct _GObject
{
GTypeInstance g_type_instance;
volatile guint ref_count;
GData *qdata;
};
typedef struct _GtkWidget GtkWidget;
struct _GtkWidget
{
GInitiallyUnowned parent_instance;
GtkWidgetPrivate *priv;
};
typedef struct _GtkTextView GtkTextView;
struct _GtkTextView
{
GtkWidget parent_instance;
GtkTextViewPrivate *priv;
};
In each structure, its parent instance is declared at the top of the members.
So, every ancestors is included in the child instance.
This is very important.
It guarantees a child widget to derive all the features from ancestors.
The structure of `TfeTextView` is like the following diagram.
![The structure of the instance TfeTextView](image/TfeTextView.png)
## Generate TfeTextView instance
The function `tfe_text_view_new` generates a new TfeTextView instance.
1 GtkWidget *
2 tfe_text_view_new (void) {
3 return gtk_widget_new (TFE_TYPE_TEXT_VIEW, NULL);
4 }
When this function is run, the following procedure is gone through.
1. Initialize GObject instance in TfeTextView instance.
2. Initialize GtkWidget instance in TfeTextView instance.
3. Initialize GtkTextView instance in TfeTextView instance.
4. Initialize TfeTextView instance.
Step one through three is done automatically.
Step four is done by the function `tfe_text_view_init`.
> (In the same way, `gtk_text_view_init`, `gtk_widget_init` and `g_object_init` is the initialization functions of GtkTextView, GtkWidget and GObject respectively.
> You can find them in the GTK or GLib source file.)
1 static void
2 tfe_text_view_class_init (TfeTextViewClass *class) {
3 GObjectClass *object_class = G_OBJECT_CLASS (class);
4
5 object_class->dispose = tfe_text_view_dispose;
6 tfe_text_view_signals[CHANGE_FILE] = g_signal_newv ("change-file",
7 G_TYPE_FROM_CLASS (class),
8 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
9 NULL /* closure */,
10 NULL /* accumulator */,
11 NULL /* accumulator data */,
12 NULL /* C marshaller */,
13 G_TYPE_NONE /* return_type */,
14 0 /* n_params */,
15 NULL /* param_types */);
16 GType param_types[] = {G_TYPE_INT};
17 tfe_text_view_signals[OPEN_RESPONSE] = g_signal_newv ("open-response",
18 G_TYPE_FROM_CLASS (class),
19 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
20 NULL /* closure */,
21 NULL /* accumulator */,
22 NULL /* accumulator data */,
23 NULL /* C marshaller */,
24 G_TYPE_NONE /* return_type */,
25 1 /* n_params */,
26 param_types);
27 }
2 on_changed (GtkTextBuffer *tb, TfeTextView *tv) {
3 tv->changed=TRUE;
4 }
5
6 static void
7 tfe_text_view_init (TfeTextView *tv) {
8 tv->tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
9 tv->file = NULL;
10 tv->changed = FALSE;
11 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
12 g_signal_connect (tv->tb, "changed", G_CALLBACK (on_changed), tv);
13 }
- 6-15: Register "change-file"signal.
`g_signal_newv` function is used.
This signal has no default handler (object method handler).
I think you usually don't need to set a default handler in final type object.
If you need it, put the closure of the handler in line 9.
- The return value of `g_signal_newv` is the signal id.
The type of signal id is guint, which is the same as unsigned int.
It is used when the signal is emitted.
- 16-26: Register "open-response" signal.
This signal has a parameter.
- 25: Number of the parameter.
"open-response" signal has one parameter.
- 26: An array of types of parameters.
The array `param_types` is defined in line 16.
It has one element, which is `G_TYPE_INT`.
`G_TYPE_INT` is a type of integer.
Such fundamental types are described in [GObject API reference](https://developer.gnome.org/gobject/stable/gobject-Type-Information.html).
`tfe_text_view_init` initializes the instance.
The handlers are as follows.
- 8-10: Initialize `tb`, `file` and `changed`.
- 11: Set the wrap mode of GtkTextView as GTK\_WRAP\_WORD\_CHAR.
- 12: Connect "changed" signal to a handler `on_changed`.
"changed" signal is defined in GtkTextBuffer.
It is emitted when the contents in the buffer is changed.
- 2-4: `on_changed` handler records TRUE to `tv->changed` when "changed" signal is emitted.
void change_file_handler (TfeTextView *tv, gpointer user_data);
void open_response_handler (TfeTextView *tv, guint parameter, gpointer user_data);
## Functions and Classes
- Because "change-file" signal doesn't have parameter, the handler's parameter is TfeTextView object and user data.
- Because "open-response" signal has one parameter, the handler's parameter is TfeTextView object, the parameter and user data.
- `tv` is the object instance on which the signal is emitted.
- `user_data` comes from the fourth argument of `g_signal_connect`.
- `parameter` comes from the fourth argument of `g_signal_emit`.
In Gtk, all objects derived from GObject have class and instance.
Instance is memories which has a structure defined by C structure declaration as I mentioned in the previous two subsections.
And instance can be generated two or more.
Those instances have the same structure.
However, structured memories are insufficient to define its behavior.
We need at least two things.
One is functions and the other is class.
The parameter is defined in `tfetextview.h` because it is public.
You've already seen many functions, for example, `tfe_text_view_new` is a function to generate TfeTextView instance.
These functions are similar to object methods in object oriented languages such as Java and Ruby.
Functions are public, which means that they are expected to be used by other objects.
/* "open-response" signal response */
enum
{
TFE_OPEN_RESPONSE_SUCCESS,
TFE_OPEN_RESPONSE_CANCEL,
TFE_OPEN_RESPONSE_ERROR
};
Class comprises mainly pointers to functions.
And the functions are used by the object itself or its children objects.
For example, GObject class is declared in `gobject.h` in GLib source files.
- `TFE_OPEN_RESPONSE_SUCCESS` is set when `tfe_text_view_open` successfully has opend a file and loaded it.
- `TFE_OPEN_RESPONSE_CANCEL` is set when the user canceled to open a file.
- `TFE_OPEN_RESPONSE_ERROR` is set when error occured.
## Signal connection
1 typedef struct _GObjectClass GObjectClass;
2 typedef struct _GObjectClass GInitiallyUnownedClass;
3
4 struct _GObjectClass {
5 GTypeClass g_type_class;
6 /*< private >*/
7 GSList *construct_properties;
8 /*< public >*/
9 /* seldom overidden */
10 GObject* (*constructor) (GType type,
11 guint n_construct_properties,
12 GObjectConstructParam *construct_properties);
13 /* overridable methods */
14 void (*set_property) (GObject *object,
15 guint property_id,
16 const GValue *value,
17 GParamSpec *pspec);
18 void (*get_property) (GObject *object,
19 guint property_id,
20 GValue *value,
21 GParamSpec *pspec);
22 void (*dispose) (GObject *object);
23 void (*finalize) (GObject *object);
24 /* seldom overidden */
25 void (*dispatch_properties_changed) (GObject *object,
26 guint n_pspecs,
27 GParamSpec **pspecs);
28 /* signals */
29 void (*notify) (GObject *object,
30 GParamSpec *pspec);
31
32 /* called when done constructing */
33 void (*constructed) (GObject *object);
34 /*< private >*/
35 gsize flags;
36 /* padding */
37 gpointer pdummy[6];
38 };
A signal and a handler are connected by the function `g_signal_connect`.
There some similar functions like `g_signal_connect_after`, `g_signal_connect_swapped` and so on.
But I think `g_signal_connect` is the most common function.
The signals "change-file" is connected to a callback function `file_changed` outside of TfeTextView object.
In the same way, the signals "open-response" is connected to a callback function `open_response` outside of TfeTextView object.
The functions `file_changed` and `open_response` will be explained later.
I'd like to explain some of the members.
There's a pointer to the function `dispose` in line 22.
g_signal_connect (GTK_TEXT_VIEW (tv), "change-file", G_CALLBACK (file_changed), nb);
void (*dispose) (GObject *object);
g_signal_connect (TFE_TEXT_VIEW (tv), "open-response", G_CALLBACK (open_response), nb);
The declaration is a bit complicated.
The asterisk before the identifier `dispose` means pointer.
So, the pointer `disopse` points a function which has one parameter , which points a GObject structure, and returns no value because of void type.
In the same way, line 23 says `finalize` is a pointer to the function which has one paremeter, which points a GObject structure, and returns no value.
## Signal emission
void (*finalize) (GObject *object);
Signals are emitted on the object.
The type of the object is the second argument of `g_signal_newv`.
The relationship between the signal and object (type) is made up when the signal is generated.
Look at the declaration of `_GObjectClass` so that you would find that most of the members are pointers to functions.
`g_signal_emit` is used to emit the signal.
The following is extract from `tfetexties.c`.
- 10: A function pointed by `constructor` is called when the instance is generated. It completes the initialization of the instance.
- 22: A function pointed by `dispose` is called when the instance destructs itself. Destruction process is divided into two phases. First is called disposing and the instance releases all the references to other instances. The second is finalizing.
- 23: A funtion pointed by `finalize` finishes the destruction process.
- The other pointers point functions which are called during the instance lives.
g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS);
g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_CANCEL);
g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
## TfeTextView class
- The first argument is the object on which the signal is emitted.
- The second argument is the signal id.
- The third argument is the detail of the signal.
"change-file" signal and "open-response" signal doesn't have details and the argument is zero when no details.
- "change-file" signal doesn't have parameter, so no fourth parameter.
- "open-response" signal has one parameter.
The fourth parameter is the parameter.
TfeTextView class is a structure and it includes all its ancestors' class in it.
Let's look at all the classes from GObject, which is the top level object, to TfeTextView object, which is the lowest.
GObject -- GInitiallyUnowned -- GtkWidget -- GtkTextView -- TfeTextView
The following is extracts from the source files (not exactly the same).
1 struct _GtkWidgetClass {
2 GInitiallyUnownedClass parent_class;
3 /*< public >*/
4 guint activate_signal;
5 /* basics */
6 void (* show) (GtkWidget *widget);
7 void (* hide) (GtkWidget *widget);
8 void (* map) (GtkWidget *widget);
9 void (* unmap) (GtkWidget *widget);
10 void (* realize) (GtkWidget *widget);
11 void (* unrealize) (GtkWidget *widget);
12 void (* root) (GtkWidget *widget);
13 void (* unroot) (GtkWidget *widget);
14 void (* size_allocate) (GtkWidget *widget,
15 int width,
16 int height,
17 int baseline);
18 void (* state_flags_changed) (GtkWidget *widget,
19 GtkStateFlags previous_state_flags);
20 void (* direction_changed) (GtkWidget *widget,
21 GtkTextDirection previous_direction);
22 void (* grab_notify) (GtkWidget *widget,
23 gboolean was_grabbed);
24 /* size requests */
25 GtkSizeRequestMode (* get_request_mode) (GtkWidget *widget);
26 void (* measure) (GtkWidget *widget,
27 GtkOrientation orientation,
28 int for_size,
29 int *minimum,
30 int *natural,
31 int *minimum_baseline,
32 int *natural_baseline);
33 /* Mnemonics */
34 gboolean (* mnemonic_activate) (GtkWidget *widget,
35 gboolean group_cycling);
36 /* explicit focus */
37 gboolean (* grab_focus) (GtkWidget *widget);
38 gboolean (* focus) (GtkWidget *widget,
39 GtkDirectionType direction);
40 void (* set_focus_child) (GtkWidget *widget,
41 GtkWidget *child);
42 /* keyboard navigation */
43 void (* move_focus) (GtkWidget *widget,
44 GtkDirectionType direction);
45 gboolean (* keynav_failed) (GtkWidget *widget,
46 GtkDirectionType direction);
47 /* accessibility support
48 */
49 AtkObject * (* get_accessible) (GtkWidget *widget);
50 gboolean (* query_tooltip) (GtkWidget *widget,
51 gint x,
52 gint y,
53 gboolean keyboard_tooltip,
54 GtkTooltip *tooltip);
55 void (* compute_expand) (GtkWidget *widget,
56 gboolean *hexpand_p,
57 gboolean *vexpand_p);
58 void (* css_changed) (GtkWidget *widget,
59 GtkCssStyleChange *change);
60 void (* system_setting_changed) (GtkWidget *widget,
61 GtkSystemSetting settings);
62 void (* snapshot) (GtkWidget *widget,
63 GtkSnapshot *snapshot);
64 gboolean (* contains) (GtkWidget *widget,
65 gdouble x,
66 gdouble y);
67 /*< private >*/
68 GtkWidgetClassPrivate *priv;
69 gpointer padding[8];
70 };
71
72 struct _GtkTextViewClass {
73 GtkWidgetClass parent_class;
74 /*< public >*/
75 void (* move_cursor) (GtkTextView *text_view,
76 GtkMovementStep step,
77 gint count,
78 gboolean extend_selection);
79 void (* set_anchor) (GtkTextView *text_view);
80 void (* insert_at_cursor) (GtkTextView *text_view,
81 const gchar *str);
82 void (* delete_from_cursor) (GtkTextView *text_view,
83 GtkDeleteType type,
84 gint count);
85 void (* backspace) (GtkTextView *text_view);
86 void (* cut_clipboard) (GtkTextView *text_view);
87 void (* copy_clipboard) (GtkTextView *text_view);
88 void (* paste_clipboard) (GtkTextView *text_view);
89 void (* toggle_overwrite) (GtkTextView *text_view);
90 GtkTextBuffer * (* create_buffer) (GtkTextView *text_view);
91 void (* snapshot_layer) (GtkTextView *text_view,
92 GtkTextViewLayer layer,
93 GtkSnapshot *snapshot);
94 gboolean (* extend_selection) (GtkTextView *text_view,
95 GtkTextExtendSelection granularity,
96 const GtkTextIter *location,
97 GtkTextIter *start,
98 GtkTextIter *end);
99 void (* insert_emoji) (GtkTextView *text_view);
100 /*< private >*/
101 gpointer padding[8];
102 };
103
104 /* The following definition is generated by the macro G_DECLARE_FINAL_TYPE
105 typedef struct {
106 GtkTextView parent_class;
107 } TfeTextViewClass;
108
- 105-107: This three lines are generated by the macro G\_DECLARE\_FINAL\_TYPE.
So, they are not written in either `tfe_text_view.h` or `tfe_text_view.c`.
- 2, 73, 106: Each derived class puts its parent class at the first member of its structure.
It is the same as instance structures.
- Class members in ancesters are open to their child class.
So, they can be changed in `tfe_text_view_class_init` function.
For example, the `dispose` pointer in GObjectClass will be overridden later in `tfe_text_view_class_init`.
(Override is an object oriented programing terminology.
Override is rewriting ancestors' class methods in the child class.)
- Some class methods are often overridden.
`set_property`, `get_property`, `dispose`, `finalize` and `constructed` are such methods.
TfeTextViewClass includes its ancsestors' class in it.
It is illustrated in the following diagram.
![The structure of TfeTextView Class](image/TfeTextViewClass.png)
## Destruction of TfeTextView
Every Object derived from GObject has a reference count.
If an object A uses an object B, then A keeps a pointr to B in A and at the same time increaces the reference count of B by one with the function `g_object_ref (B)`.
If A doesn't need B any longer, then A discards the pointer to B (usually it is done by assigning NULL to the pointer) and decreaces the reference count of B by one with the function `g_object_unref (B)`.
If two objects A and B refer to C, then the reference count of C is two.
After A used C and if A no longer needs C, A discards the pointer to C and decreases the reference count in C by one.
Now the reference count of C is one.
In the same way, when B no longer needs C, B discards the pointer to C and decreases the reference count in C by one.
At this moment, no object refers C and the reference count of C is zero.
This means C is no longer useful.
Then C destructs itself and finally the memories allocated to C is freed.
![Reference count of B](image/refcount.png)
The idea above is based on an assumption that an object refered by nothing has reference count of zero.
When the reference count drops to zero, the object starts its destruction process.
The destruction process is split in two phases: disposing and finalizing.
In the disposing process, the object invokes the handler pointed by `dispose` in its class to release all references to other objects.
In the finalizing process, it invokes the handler pointed by `finalize` in its class to complete the destruction process.
In the destruction process of TfeTextView, the reference count of widgets related to TfeTextView is automatically decreased.
But GFile pointed by `tv->file` needs to decrease its reference count by one.
You must write the code in the dispose handler `tfe_text_view_dispose`.
1 static void
2 tfe_text_view_dispose (GObject *gobject) {
3 TfeTextView *tv = TFE_TEXT_VIEW (gobject);
4
5 if (G_IS_FILE (tv->file))
6 g_clear_object (&tv->file);
7
8 G_OBJECT_CLASS (tfe_text_view_parent_class)->dispose (gobject);
9 }
- 5,6: If `tv->file` points a GFile, decrease its reference count.
`g_clear_object` decreases the reference count and assigns NULL to `tv->file`. In dispose handlers, we usually use `g_clear_object` rather than `g_object_unref`.
- 8: invoke parent's despose handler. (This will be explained later.)
In the desposing process, the object uses the pointer in its class to call the handler.
Therefore, `tfe_text_view_dispose` needs to be registerd in the class when the TfeTextView class is initialized.
The function `tfe_text_view_class_init` is the class initialization function and it is declared in the replacement produced by `G_DEFINE_TYPE` macro.
static void
tfe_text_view_class_init (TfeTextViewClass *class) {
GObjectClass *object_class = G_OBJECT_CLASS (class);
object_class->dispose = tfe_text_view_dispose;
}
Each ancestors' class is generated before TfeTextViewClass.
Therefore, there are four classes and each class has a pointer to each dispose handler.
Look at the following diagram.
There are four classes -- GObjectClass (GInitiallyUnownedClass), GtkWidgetClass, GtkTextViewClass and TfeTextViewClass.
Each class has its own dispose handler -- `dh1`, `dh2`, `dh3` and `tfe_text_view_dispose`.
![dispose handers](image/dispose_handler.png)
Now, look at the `tfe_text_view_dispose` program above.
It first releases the reference to GFile object pointed by `tv->file`.
Then it invokes its parent's dispose handler in line 8.
G_OBJECT_CLASS (tfe_text_view_parent_class)->dispose (gobject);
`tfe_text_view_parent_class`,which is made by `G_DEFINE_TYPE` macro, is a pointer that points the parent object class.
Therefore, `G_OBJECT_CLASS (tfe_text_view_parent_class)->dispose` points the handler `dh3` in the diagram above.
And `gobject` is a pointer to TfeTextView object which is casted as a GObject instanse.
`dh3` releases all the references to objects in the GtkTextView part (it is actually the private area pointed by `prev`) in TfeTextView instance.
After that, `dh3` calls `dh2`, and `dh2` calls `dh1`.
Finally all the references are released.
Up: [Readme.md](Readme.md), Prev: [Section 8](sec8.md), Next: [Section 10](sec10.md)

View file

@ -1,238 +1,51 @@
# GtkApplication and GtkApplicationWindow
# Prerequisite and Licence
## GtkApplication
## Prerequisite
### GtkApplication and g\_application\_run
### Tutorial document
Usually people write a programming code to make an application.
What are appications?
Applications are software that runs using libraries, which includes OS, frameworks and so on.
In Gtk4 programming, GtkApplication is an object that runs on GTK libraries.
The tutorial is about gtk4 libraries.
It is originally used on linux with C compiler, but now it is used more widely, on windows and macOS, with Vala, python and so on.
However, this tutorial describes only C programs on linux.
The basic way how to write GtkApplication is as follows.
You need:
- Generate a GtkApplication object
- Run it
- PC with linux distribution like ubuntu, debian and so on.
- Gcc
- Gtk4. Gtk included linux distributions is version three at present.
You need to install gtk4 to your computer.
Refer to [gtk4 gitlab repository](https://gitlab.gnome.org/GNOME/gtk).
However, it might make some trouble like, for example, your pc doesn't recognize usb port.
You should uninstall it after learning gtk4 programing.
That's all.
Very simple.
The following is the C code representing the scenario above.
### Software
@@@ misc/pr1.c
This repository inclludes ruby programs.
They are used to generate markdown files, html files, latex files and a pdf file.
The first line says that this program includes the GTK header libraries.
The function `main` above is a startup function in C language.
The variable `app` is defined as a pointer to GtkApplication, which is actually a structure in which information about the application is stored.
The function `gtk_application_new` generates a GtkApplication and sets its pointer to `app`.
The meaning of the arguments will be explained later.
The function `g_application_run` invokes the GtkApplication pointed by `app`.
(We often say that the function invokes `app`.
Actually, `app` is not an object but an pointer to the object.
However, it is simple and short, and probably no confusion occurs.)
You need:
To compile this, the following command needs to be run.
The string pr1.c is the filename of the C source code.
- Linux distribution like ubuntu.
- Ruby programing language.
There are two ways to install it.
One is install the distribution's package.
The other is using rbenv and ruby-build.
If you want to use the latest version of ruby, use rbenv.
- Rake.
It is a gem, which is a library written in ruby.
You can install it as a package of your ditribution or use gem command.
$ gcc `pkg-config --cflags gtk4` pr1.c `pkg-config --libs gtk4`
## Licence
The C compiler gcc generates an executable file `a.out`.
Let's run it.
Copyright (C) 2020 ToshioCP (Toshio Sekiya)
$ ./a.out
Gtk tutorial repository containes the tutorial document and softwares such as converters, generators and controlers.
All of them make up the 'Gtk tutorial' package.
This package is simply called 'Gtk tutorial' in the following description.
'Gtk4 tutorial' is free; you can redistribute it and/or modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
(a.out:13533): GLib-GIO-WARNING **: 15:30:17.449: Your application does not implement g_application_activate() and has no handlers connected to the "activate" signal. It should do one of these.
$
Oh, just an error message.
But this error message means that the GtkApplication object ran without a doubt.
Now, think about the message in the next section.
### signal
The message tells us that:
1. The application GtkApplication doesn't implement `g_application_activate()`.
2. And it has no handlers connected to the activate signal.
3. You need to solve at least one of this.
These two cause of the error are related to signals.
So, I will explain it to you first.
Signal is emitted when something happens.
For example, a window is generated, a window is destroyed and so on.
The signal "activate" is emitted when the application is activated.
If the signal is connected to a function, which is called signal handler or simply handler, then the function invokes when the signal emits.
The flow is like this:
1. Something happens.
2. If it's related to a certain signal, then the signal is emitted.
3. If the signal is connected to a handler in advance, then the handler is invoked.
Signals are defined in objects.
For example, "activate" signal belongs to GApplication object, which is a parent object of GtkApplication object.
GApplication object is a child object of GObject object.
GObject is the top object in the hierarchy of all the objects.
GObject -- GApplication -- GtkApplication
<---parent --->child
A child object derives signals, functions, properties and so on from its parent object.
So, Gtkapplication also has the "activate" signal.
Now we can solve the problem in `pr1.c`.
We need to connect the activate signal to a handler.
We use a function `g_signal_connect` which connects a signal to a handler.
@@@ misc/pr2.c
First, we define the handler `on_activate` which simply displays a message.
In the function `main`, we add `g_signal_connect` before `g_application_run`.
The function `g_signal_connect` has four arguments.
1. An object to which the signal belongs.
2. The name of the signal.
3. A handler function (also called callback), which needs to be casted by `G_CALLBACK`.
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.
In addition, `g_signal_connect` is described in GObject API reference.
API reference is very important.
You should see and understand it to write GTK applications.
Let's compile the source file `pr2.c` above and run it.
$ gcc `pkg-config --cflags gtk4` pr2.c `pkg-config --libs gtk4`
$ ./a.out
GtkApplication is activated.
$
OK, well done.
However, you may have noticed that it's painful to type such a long line to compile.
It is a good idea to use shell script to solve this problem.
Make a text file which contains the following text.
gcc `pkg-config --cflags gtk4` $1.c `pkg-config --libs gtk4`
Then, save it in $HOME/bin, which is usually /home/(username)/bin.
(If your user name is James, then the directory is /home/james/bin).
And turn on the execute bit of the file.
Suppose the filename is comp, then the procedure is as follows.
$ chmod 755 $HOME/bin/comp
$ ls -log $HOME/bin
... ... ...
-rwxr-xr-x 1 62 May 23 08:21 comp
... ... ...
If this is the first time that you make a $HOME/bin directory and save a file in it, then you need to logout and login again.
$ comp pr2
$ ./a.out
GtkApplication is activated.
$
## GtkWindow and GtkApplicationWindow
### GtkWindow
A message "GtkApplication is activated." was printed out in the previous subsection.
It was good in terms of a test of GtkApplication.
However, it is insufficient because GTK is a framework for graphical user interface (GUI).
Now we go ahead with adding a window into this program.
What we need to do is:
1. Generate a GtkWindow.
2. Connect it to GtkApplication.
3. Show the window.
Now rewrite the function `on_activate`.
#### Generate a GtkWindow
@@@ misc/pr3.c on_activate
Widget is an abstract concept that includes all the GUI interfaces such as windows, dialogs, buttons, multiline text, containers and so on.
And GtkWidget is a base object from which all the GUI objects derive.
parent <-----> child
GtkWidget -- GtkWindow
GtkWindow includes GtkWidget at the top of its object.
![GtkWindow and GtkWidget](../image/window_widget.png)
The function `gtk_window_new` is defined as follows.
GtkWidget *
gtk_window_new (void);
By this definition, it returns a pointer to GtkWidget, not GtkWindow.
It actually generates a new GtkWindow object (not GtkWidget) but returns a pointer to GtkWidget.
However,the pointer points the GtkWidget and at the same time it also points GtkWindow that contains GtkWidget in it.
If you want to use `win` as a pointer to the GtkWindow, you need to cast it.
(GtkWindow *) win
Or you can use `GTK_WINDOW` macro that performs a similar function.
GTK_WINDOW (win)
This is a recommended way.
#### Connect it to GtkApplication.
The function `gtk_window_set_application` is used to connect GtkWidow to GtkApplication.
gtk_window_set_application (GTK_WINDOW (win), GTK_APPLICATION (app));
You need to cast `win` to GtkWindow and `app` to GtkApplication.
`GTK_WINDOW` and `GTK_APPLICATION` macro is appropriate for that.
GtkApplication continues to run until the related window is destroyed.
If you didn't connect GtkWindow and GtkApplication, GtkApplication shutdowns soon.
Because no window is connected to GtkApplication, it doesn't need to wait anything.
As it shutdowns the generated window is also destroyed.
#### Show the window.
The function `gtk_widget_show` is used to show the window.
Gtk4 changed the default widget visibility to on, so every widget doesn't need this function to show itself.
But, there's an exception.
Top window (this term will be explained later) isn't visible when it is generated.
So you need to use the function above and show the window.
Save the program as `pr3.c` and compile and run it.
$ comp pr3
$ ./a.out
A small window appears.
![Screenshot of the window](../image/screenshot_pr3.png)
Click on the close button then the window disappears and the program finishes.
### GtkApplicationWindow
GtkApplicationWindow is a child object of GtkWindow.
It has some extra functionality for better integration with GtkApplication.
It is recommended to use it instead of GtkWindow when you use GtkApplication.
Now rewrite the program and use GtkAppliction Window.
@@@ misc/pr4.c on_activate
When you generate GtkApplicationWindow, you need to give GtkApplication object as an argument.
Then it automatically connect these two objects.
So you don't need to call `gtk_window_set_application` any more.
The program sets the title and the default size of the window.
Compile it and run `a.out`, then you will see a bigger window with its title "pr4".
![Screenshot of the window](../image/screenshot_pr4.png)
'Gtk tutorial' is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the [GNU General Public License](https://www.gnu.org/licenses/gpl-3.0.html) for more details.

View file

@ -1,176 +1,135 @@
# Functions in TfeTextView
# Signals
In this section I will explain each function in TfeTextView object.
## Signals
### tfe.h and tfetextview.h
In GTK programming, each object is capsulated.
And it is not recommended to use global variables because they tend to make the program complicated.
So, we need something to communicate between objects.
There are two ways to do so.
`tfe.h` is a top header file and it includes `gtk.h` and all the header files.
Every C source files, which are `tfeapplication.c`, `tfenotebook.c` and `tfetextview.c`, include `tfe.h` at the beginning of each file.
- Functions.
For example, `tb = gtk_text_view_get_buffer (tv)`.
The function caller requests `tv`, which is a GtkTextView object, to give back `tb`, which is a GtkTextBuffer object connected to `tv`.
- Signals.
For example, `activate` signal on GApplication object.
When the application is activated, the signal is emitted.
Then the handler, which has been connected to the signal, is invoked.
@@@ tfe5/tfe.h
The caller of the function or the handler connected to the signal is usually outside of the object.
One of the difference between these two is that the object is active or passive.
In functions the object responds to the caller.
In signals the object actively sends a signal to the handler.
`tfetextview.h` is a header file which describes the public functions in `tfetextview.c`.
GObject signal can be registered, connected and emitted.
@@@ tfe5/tfetextview.h
1. A signal is registered with the object type on which it can be emitted.
This is done usually when the class is initialized.
2. It is connected to a handler by `g_connect_signal` or its family functions.
3. When it is emmitted, the connected handler is invoked.
- 1-2: These two lines are used to define TfeTextView.
- 4-10: Definitions of parameter used in the handler of "open-response" signal.
- 12-28: Public functions on GtkTextView.
Step one and three are done in the object on which the signal is emitted.
Step two is done outside the objects.
Each function will be explained later in this section.
## Signal registration
## Functions to generate TfeTextView object
In TfeTextView, two signals are registered.
TfeTextView Object is generated by `tfe_text_view_new` or `tfe_text_view_new_with_file`.
- "change-file" signal.
This signal is emitted when `tv->file` is changed.
- "open-response" signal.
`tfe_text_view_open` function is not able to return the status because of using GtkFileChooserDialog.
This signal is emitted instead of the return value of the function.
GtkWidget *tfe_text_view_new (void);
Static variable is used to store the signal ID.
If you need to register two or more signals, static array is usually used.
`tfe_text_view_new` just generates a new TfeTextView object and returns the pointer to the new object.
enum {
CHANGE_FILE,
OPEN_RESPONSE,
NUMBER_OF_SIGNALS
};
GtkWidget *tfe_text_view_new_with_file (GFile *file);
static guint tfe_text_view_signals[NUMBER_OF_SIGNALS];
`tfe_text_view_new_with_file` is given a Gfile object as the argument and it loads the file into the GtkTextBuffer object, then returns the pointer to the new object.
Signal registration codes are written in the class initialization function.
Parameter:
@@@ tfe5/tfetextview.c tfe_text_view_class_init
- `file`: a pointer to the GFile object.
- 6-15: Register "change-file"signal.
`g_signal_newv` function is used.
This signal has no default handler (object method handler).
I think you usually don't need to set a default handler in final type object.
If you need it, put the closure of the handler in line 9.
- The return value of `g_signal_newv` is the signal id.
The type of signal id is guint, which is the same as unsigned int.
It is used when the signal is emitted.
- 16-26: Register "open-response" signal.
This signal has a parameter.
- 25: Number of the parameter.
"open-response" signal has one parameter.
- 26: An array of types of parameters.
The array `param_types` is defined in line 16.
It has one element, which is `G_TYPE_INT`.
`G_TYPE_INT` is a type of integer.
Such fundamental types are described in [GObject API reference](https://developer.gnome.org/gobject/stable/gobject-Type-Information.html).
Return value:
The handlers are as follows.
- A pointer to the generated TfeTextView object but it is casted to a pointer to GtkWidget.
If an error occures during the genration process, NULL is returned.
void change_file_handler (TfeTextView *tv, gpointer user_data);
void open_response_handler (TfeTextView *tv, guint parameter, gpointer user_data);
Each function is defined as follows.
- Because "change-file" signal doesn't have parameter, the handler's parameter is TfeTextView object and user data.
- Because "open-response" signal has one parameter, the handler's parameter is TfeTextView object, the parameter and user data.
- `tv` is the object instance on which the signal is emitted.
- `user_data` comes from the fourth argument of `g_signal_connect`.
- `parameter` comes from the fourth argument of `g_signal_emit`.
@@@ tfe5/tfetextview.c tfe_text_view_new_with_file tfe_text_view_new
The parameter is defined in `tfetextview.h` because it is public.
- 18-21: `tfe_text_view_new`.
Just returns the value from the function `gtk_widget_new`.
Initialization is done in `tfe_text_view_init` which is called in the process of `gtk_widget_new` function.
- 1-16: `tfe_text_view_new_with_file`
- 3: `g_return_val_if_fail` is described in [Glib API reference](https://developer.gnome.org/glib/stable/glib-Warnings-and-Assertions.html#g-return-val-if-fail).
It tests whether the argument `file` is a pointer to GFile.
If it's true, then the program goes on to the next line.
If it's false, then it returns NULL (the second argument) immediately.
And at the same time it logs out the error message (usually the log is outputted to stderr or stdout).
This function is used to check the programmer's error.
If an error occurs, the solution is usually to change the (caller) program and fix the bug.
You need to distinguish programmer's errors and runtime errors.
You shouldn't use this function to find runtime errors.
- 9-10: If an error occurs when reading the file, then return NULL.
- 11-15: Generate TfeTextView and set the pointer to it to `tv`.
Set the contents read from the file to GtkTextBuffer `tv->tb`.
Free the memories pointed by `contents`.
Duplicate `file` and set it to `tv->file`.
Return `tv`.
/* "open-response" signal response */
enum
{
TFE_OPEN_RESPONSE_SUCCESS,
TFE_OPEN_RESPONSE_CANCEL,
TFE_OPEN_RESPONSE_ERROR
};
## Save and saveas functions
- `TFE_OPEN_RESPONSE_SUCCESS` is set when `tfe_text_view_open` successfully has opend a file and loaded it.
- `TFE_OPEN_RESPONSE_CANCEL` is set when the user canceled to open a file.
- `TFE_OPEN_RESPONSE_ERROR` is set when error occured.
## Signal connection
Save and saveas functions write the contents in GtkTextBuffer to a file.
A signal and a handler are connected by the function `g_signal_connect`.
There some similar functions like `g_signal_connect_after`, `g_signal_connect_swapped` and so on.
But I think `g_signal_connect` is the most common function.
The signals "change-file" is connected to a callback function `file_changed` outside of TfeTextView object.
In the same way, the signals "open-response" is connected to a callback function `open_response` outside of TfeTextView object.
The functions `file_changed` and `open_response` will be explained later.
void tfe_text_view_save (TfeTextView *tv)
g_signal_connect (GTK_TEXT_VIEW (tv), "change-file", G_CALLBACK (file_changed), nb);
`save` function writes the contents in GtkTextBuffer to a file specified by `tv->file`.
If `tv->file` is NULL, then it shows GtkFileChooserDialog and lets the user to give a file to the program. After that, it saves the contents to the specified file and set the file into `tv->file`.
g_signal_connect (TFE_TEXT_VIEW (tv), "open-response", G_CALLBACK (open_response), nb);
void tfe_text_view_saveas (TfeTextView *tv)
## Signal emission
`saveas` function uses GtkFileChooserDialog and lets the user to give a new file to the program. Then, the function changes `tv->file` and save the contents to the specified new file.
Signals are emitted on the object.
The type of the object is the second argument of `g_signal_newv`.
The relationship between the signal and object (type) is made up when the signal is generated.
If an error occures, it is shown to the user through the message dialog.
The error is managed only in the object and no information is notified to the caller.
`g_signal_emit` is used to emit the signal.
The following is extract from `tfetexties.c`.
@@@ tfe5/tfetextview.c saveas_dialog_response tfe_text_view_save tfe_text_view_saveas
g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS);
g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_CANCEL);
g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
- 17-53: `Tfe_text_view_save` function.
- 19: If `tv` is not a pointer to TfeTextView, then it logs an error message and immediately returns.
This function is similar to `g_return_val_if_fail` function, but no value is returned because `tfe_text_view_save` doesn't return a value.
- 28-29: If the buffer hasn't modified, then it doesn't need to save it.
So the function returns.
- 30-31: If `tv->file` is NULL, no file has given yet.
It calls `tfe_text_view_save`, which lets the user to choose a file to save.
- 33-35: Save the buffer to the file.
If it succeeds, assigns FALSE to `tv->changed`.
- 38-50: If file writing fails, it assigns NULL to `tv->file`.
Emits "change-file" signal.
Shows the error message dialog (45-49).
Because the handler is `gtk_window_destroy`, the dialog disappears when user clicks on the button in the dialog.
- 55-68: `tfe_text_view_saveas` function.
It shows GtkFileChooserDialog and lets the user choose a file and give it to the signal handler.
- 62: Generate GtkFileChooserDialog.
The title is "Save file".
Transient parent of the dialog is `win`, which is the top level window.
The action is save mode.
The buttons are Cancel and Save.
- 63: connect the "response" signal of the dialog and `saveas_dialog_response` handler.
- 1-15: `saveas_dialog_response` signal handler.
- 5-13: If the response is `GTK_RESPONSE_ACCEPT`, which is set to the argument when the user has clicked on Save button, then gets a pointer to the GFile object, set it to `tv->file`, assign TRUE to `tv->changed`, emits "change-file" signal then call `tfe_text_view_save` to save the buffer to the file.
- The first argument is the object on which the signal is emitted.
- The second argument is the signal id.
- The third argument is the detail of the signal.
"change-file" signal and "open-response" signal doesn't have details and the argument is zero when no details.
- "change-file" signal doesn't have parameter, so no fourth parameter.
- "open-response" signal has one parameter.
The fourth parameter is the parameter.
![Saveas process](../image/saveas.png)
When you use GtkFileChooserDialog, you need to divide the program into two parts.
They are a function which generates GtkFileChooserDialog and the signal handler.
The function just generates and shows the dialog.
The rest is done by the handler.
It gets Gfile from GtkFileChooserDialog, save the buffer to the file and do some things necessary.
## Open function
Open function shows GtkFileChooserDialog to the user and let him/her choose a file.
Then read the file and set it to GtkTextBuffer.
void tfe_text_view_open (TfeTextView *tv)
TfeTextView object `tv` has to be generated in advance.
And it should be empty and `tv->file` is NULL.
If it is not empty, `tfe_text_view_open` doesn't treat it as an error.
If you want to revert the buffer, calling this function is apropreate.
Otherwise probably bad things will happen.
@@@ tfe5/tfetextview.c open_dialog_response tfe_text_view_open
- 33-45: `tfe_text_view_open` function.
- 39: Generate GtkFileChooserDialog.
The title is "Open file".
No transient parent window.
The action is open mode.
The buttons are Cancel and Open.
- 43: connect the "reponse" signal of the dialog and `open_dialog_response` signal handler.
- 44: Show the dialog.
- 1-31: `open_dialog_response` signal handler.
- 9-10: If the response from GtkFileChooserDialog is not `GTK_RESPONSE_ACCEPT`, which means the user has clicked on the "Cancel" button or close button, then it emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_CANCEL`.
- 11-12: Get a pointer to Gfile by `gtk_file_chooser_get_file`.
If it is not GFile, maybe an error occured.
Then it emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_ERROR`.
- 13-22: If an error occurs when it has read the file, then it decreases the reference count of Gfile, shows a message dialog to report the error to the user and emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_ERROR`.
- 24-28: If the file has successfully read, then the text is set to GtkTextBuffer, free the temporary buffer pointed by `contents`, set file to `tv->file` (no duplication or unref is not necessary) and emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_SUCCESS`.
- 30: close GtkFileCooserDialog.
Now let's think about the whole process between the other object (caller) and TfeTextView.
It is shown in the following diagram and you would think that it is really complicated.
Because signal is the only way for GtkFileChooserDialog to communicate with others.
In Gtk3, `gtk_dialog_run` function is available.
It simplifies the process.
However, in Gtk4, `gtk_dialog_run`is unavailable any more.
![Caller and TfeTextView](../image/open.png)
1. A caller get a pointer `tv` to TfeTextView by calling `tfe_text_view_new`.
2. The caller connects the handler (left bottom in the diagram) and the signal "open-response".
3. It calls `tfe_text_view_open` to let the user select a file from GtkFileChooserDialog.
4. The dialog emits a signal and it invokes the handler `open_dialog_response`.
5. The handler read the file and set it into GtkTextBuffer and emits a signal to inform the response status.
6. The handler outside TfeTextView recieves the signal.
## Get file function
`gtk_text_view_get_file` is a simple function show as follows.
@@@ tfe5/tfetextview.c tfe_text_view_get_file
The important thing is duplicate `tv->file`.
Otherwise, if the caller free the GFile object, `tv->file` is no more guaranteed to point the GFile.
## Source file of tfetextview.c
All the source files are listed in [Section 13](ch13.html).

View file

@ -1,92 +1,176 @@
# Functions with GtkNotebook
# Functions in TfeTextView
GtkNotebook is a very important object in the text file editor `tfe`.
It connects the application and TfeTextView objects.
`tfenotebook.h` and `tfenotebook.c` describe a set of functions related to GtkTextbook.
In this section I will explain each function in TfeTextView object.
@@@ tfe5/tfenotebook.h
### tfe.h and tfetextview.h
This header file shows the public functions in `tfenotebook.c`.
`tfe.h` is a top header file and it includes `gtk.h` and all the header files.
Every C source files, which are `tfeapplication.c`, `tfenotebook.c` and `tfetextview.c`, include `tfe.h` at the beginning of each file.
- `notebook_page_new` generates a new GtkNotebookPage and adds GtkScrolledWindow and TfeTextView under the page.
- `notebook_page_new_with_file` generates a new GtkNotebookPage and adds GtkScrolledWindow and TfeTextView under the page. `file` is set to the pointer to GFile in the TfeTextView object and the file is read and set into GtkTextBuffer.
- `notebook_page_open` lets the user select a file and sets it into GtkTextBuffer.
- `notebook_page_save` save the contents in GtkTextBuffer to a file, using the pointer `tv->file`.
@@@ tfe5/tfe.h
You probably find that the functions above are higher level functions of `tfe_text_view_new`, `tfe_text_view_new_with_file`, `tef_text_view_open` and `tfe_text_view_save` respectively.
There are two layers.
One of them is `tfe_text_view ...`, which is the lower level layer.
The other is `note_book ...`, which is the higher level layer.
`tfetextview.h` is a header file which describes the public functions in `tfetextview.c`.
Now let's look at each program of the functions.
@@@ tfe5/tfetextview.h
## notebook\_page\_new
- 1-2: These two lines are used to define TfeTextView.
- 4-10: Definitions of parameter used in the handler of "open-response" signal.
- 12-28: Public functions on GtkTextView.
@@@ tfe5/tfenotebook.c get_untitled notebook_page_build notebook_page_new
Each function will be explained later in this section.
- 27-37: `notebook_page_new` function.
- 29: `g_return_if_fail` is used because `notebook_page_new` is a public function.
- 34: Generate TfeTextView object.
- 35: Generate filename, which is "Untitled", "Untitled2", ... .
- 1-8: `get_untitled` function.
- 3: Static variable `c` is initialized at the first call of this function. After that `c` keeps its value except it is changed explicitly.
- 4-7: Increase `c` by one and if it is zero then the name is "Untitled". If it is a positive integer then the name is "Untitled<the integer>", for example, "Untitled1", "Untitled2", and so on.
It returns the name.
`g_strdup_printf` generates a string and it should be freed by `g_free` function.
The caller of `get_untitled` is in charge of freeing the memories of the string.
- 36: call `notebook_page_build` to build the contents of the page.
- 10- 25: `notebook_page_build` function.
- 17-18: Generate GtkScrolledWindow and set `tv` to its child.
- 19-20: Generate GtkLabel, then GtkNotebookPage.
- 21-22: Set "tab-expand" property to TRUE.
- 23: Set the page to the current page.
- 24: Connect "change-file" signal and `file_changed` handler.
## Functions to generate TfeTextView object
## notebook\_page\_new\_with\_file
TfeTextView Object is generated by `tfe_text_view_new` or `tfe_text_view_new_with_file`.
@@@ tfe5/tfenotebook.c notebook_page_new_with_file
GtkWidget *tfe_text_view_new (void);
- 9-10: Call `tfe_text_view_new_with_file`.
If it returns NULL, then do nothing and return because of an error.
-11-13: Get the filename , build the contents of the page, then free `filename`.
`tfe_text_view_new` just generates a new TfeTextView object and returns the pointer to the new object.
## notebook\_page\_open
GtkWidget *tfe_text_view_new_with_file (GFile *file);
@@@ tfe5/tfenotebook.c open_response notebook_page_open
`tfe_text_view_new_with_file` is given a Gfile object as the argument and it loads the file into the GtkTextBuffer object, then returns the pointer to the new object.
- 18-27: `notebook_page_open` function.
- 24: Generate TfeTextView object.
- 25: Connect the signal "open-response" and the handler `open_response`.
- 26: Call `tfe_text_view_open`.
It emits "open-response" signal to inform the status after the series of functions run.
- 1-16: `open_response` handler.
This is the postfunction of `notebook_page_open`.
- 6-7: It the status is NOT `TFE_OPEN_RESPONSE_SUCCESS`, cancel what we did in `notebook_page_open`.
Unref `tv`.
- 8-9: If `tfe_text_view_get_file` returns a pointer not to point GFile, then something bad happens. Cancel what we did. Unref `tv`.
- 10-14: Otherwise, everything was okay.
Get the filename, build the contents of the page, free `filename` and unref `tv`
Parameter:
## notebook\_page\_save
- `file`: a pointer to the GFile object.
@@@ tfe5/tfenotebook.c notebook_page_save
Return value:
- 7-9: Get TfeTextView belongs to the current notebook page.
- 10: Call `tfe_text_view_save`.
- A pointer to the generated TfeTextView object but it is casted to a pointer to GtkWidget.
If an error occures during the genration process, NULL is returned.
## file\_changed handler
Each function is defined as follows.
`file_changed` is a handler connected to "change-file" signal.
If `tv->file` is changed, TfeTextView emits this signal.
This handler changes the label of GtkNotebookPage.
@@@ tfe5/tfetextview.c tfe_text_view_new_with_file tfe_text_view_new
@@@ tfe5/tfenotebook.c file_changed
- 18-21: `tfe_text_view_new`.
Just returns the value from the function `gtk_widget_new`.
Initialization is done in `tfe_text_view_init` which is called in the process of `gtk_widget_new` function.
- 1-16: `tfe_text_view_new_with_file`
- 3: `g_return_val_if_fail` is described in [Glib API reference](https://developer.gnome.org/glib/stable/glib-Warnings-and-Assertions.html#g-return-val-if-fail).
It tests whether the argument `file` is a pointer to GFile.
If it's true, then the program goes on to the next line.
If it's false, then it returns NULL (the second argument) immediately.
And at the same time it logs out the error message (usually the log is outputted to stderr or stdout).
This function is used to check the programmer's error.
If an error occurs, the solution is usually to change the (caller) program and fix the bug.
You need to distinguish programmer's errors and runtime errors.
You shouldn't use this function to find runtime errors.
- 9-10: If an error occurs when reading the file, then return NULL.
- 11-15: Generate TfeTextView and set the pointer to it to `tv`.
Set the contents read from the file to GtkTextBuffer `tv->tb`.
Free the memories pointed by `contents`.
Duplicate `file` and set it to `tv->file`.
Return `tv`.
- 8: Get GFile from TfeTextView.
- 9: Get the parent (GkScrolledWindow) of `tv`.
- 10-13: If `file` points GFile, then assign the filename of the GFile into `filename`.
Otherwise (this is the case file is NULL), assign untitled string to `filename`.
- 14-15: Generate a label with the filename and set it into GtkNotebookPage.
- 16-17: Free `filename and unref `file`.
## Save and saveas functions
Save and saveas functions write the contents in GtkTextBuffer to a file.
void tfe_text_view_save (TfeTextView *tv)
`save` function writes the contents in GtkTextBuffer to a file specified by `tv->file`.
If `tv->file` is NULL, then it shows GtkFileChooserDialog and lets the user to give a file to the program. After that, it saves the contents to the specified file and set the file into `tv->file`.
void tfe_text_view_saveas (TfeTextView *tv)
`saveas` function uses GtkFileChooserDialog and lets the user to give a new file to the program. Then, the function changes `tv->file` and save the contents to the specified new file.
If an error occures, it is shown to the user through the message dialog.
The error is managed only in the object and no information is notified to the caller.
@@@ tfe5/tfetextview.c saveas_dialog_response tfe_text_view_save tfe_text_view_saveas
- 17-53: `Tfe_text_view_save` function.
- 19: If `tv` is not a pointer to TfeTextView, then it logs an error message and immediately returns.
This function is similar to `g_return_val_if_fail` function, but no value is returned because `tfe_text_view_save` doesn't return a value.
- 28-29: If the buffer hasn't modified, then it doesn't need to save it.
So the function returns.
- 30-31: If `tv->file` is NULL, no file has given yet.
It calls `tfe_text_view_save`, which lets the user to choose a file to save.
- 33-35: Save the buffer to the file.
If it succeeds, assigns FALSE to `tv->changed`.
- 38-50: If file writing fails, it assigns NULL to `tv->file`.
Emits "change-file" signal.
Shows the error message dialog (45-49).
Because the handler is `gtk_window_destroy`, the dialog disappears when user clicks on the button in the dialog.
- 55-68: `tfe_text_view_saveas` function.
It shows GtkFileChooserDialog and lets the user choose a file and give it to the signal handler.
- 62: Generate GtkFileChooserDialog.
The title is "Save file".
Transient parent of the dialog is `win`, which is the top level window.
The action is save mode.
The buttons are Cancel and Save.
- 63: connect the "response" signal of the dialog and `saveas_dialog_response` handler.
- 1-15: `saveas_dialog_response` signal handler.
- 5-13: If the response is `GTK_RESPONSE_ACCEPT`, which is set to the argument when the user has clicked on Save button, then gets a pointer to the GFile object, set it to `tv->file`, assign TRUE to `tv->changed`, emits "change-file" signal then call `tfe_text_view_save` to save the buffer to the file.
![Saveas process](../image/saveas.png)
When you use GtkFileChooserDialog, you need to divide the program into two parts.
They are a function which generates GtkFileChooserDialog and the signal handler.
The function just generates and shows the dialog.
The rest is done by the handler.
It gets Gfile from GtkFileChooserDialog, save the buffer to the file and do some things necessary.
## Open function
Open function shows GtkFileChooserDialog to the user and let him/her choose a file.
Then read the file and set it to GtkTextBuffer.
void tfe_text_view_open (TfeTextView *tv)
TfeTextView object `tv` has to be generated in advance.
And it should be empty and `tv->file` is NULL.
If it is not empty, `tfe_text_view_open` doesn't treat it as an error.
If you want to revert the buffer, calling this function is apropreate.
Otherwise probably bad things will happen.
@@@ tfe5/tfetextview.c open_dialog_response tfe_text_view_open
- 33-45: `tfe_text_view_open` function.
- 39: Generate GtkFileChooserDialog.
The title is "Open file".
No transient parent window.
The action is open mode.
The buttons are Cancel and Open.
- 43: connect the "reponse" signal of the dialog and `open_dialog_response` signal handler.
- 44: Show the dialog.
- 1-31: `open_dialog_response` signal handler.
- 9-10: If the response from GtkFileChooserDialog is not `GTK_RESPONSE_ACCEPT`, which means the user has clicked on the "Cancel" button or close button, then it emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_CANCEL`.
- 11-12: Get a pointer to Gfile by `gtk_file_chooser_get_file`.
If it is not GFile, maybe an error occured.
Then it emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_ERROR`.
- 13-22: If an error occurs when it has read the file, then it decreases the reference count of Gfile, shows a message dialog to report the error to the user and emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_ERROR`.
- 24-28: If the file has successfully read, then the text is set to GtkTextBuffer, free the temporary buffer pointed by `contents`, set file to `tv->file` (no duplication or unref is not necessary) and emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_SUCCESS`.
- 30: close GtkFileCooserDialog.
Now let's think about the whole process between the other object (caller) and TfeTextView.
It is shown in the following diagram and you would think that it is really complicated.
Because signal is the only way for GtkFileChooserDialog to communicate with others.
In Gtk3, `gtk_dialog_run` function is available.
It simplifies the process.
However, in Gtk4, `gtk_dialog_run`is unavailable any more.
![Caller and TfeTextView](../image/open.png)
1. A caller get a pointer `tv` to TfeTextView by calling `tfe_text_view_new`.
2. The caller connects the handler (left bottom in the diagram) and the signal "open-response".
3. It calls `tfe_text_view_open` to let the user select a file from GtkFileChooserDialog.
4. The dialog emits a signal and it invokes the handler `open_dialog_response`.
5. The handler read the file and set it into GtkTextBuffer and emits a signal to inform the response status.
6. The handler outside TfeTextView recieves the signal.
## Get file function
`gtk_text_view_get_file` is a simple function show as follows.
@@@ tfe5/tfetextview.c tfe_text_view_get_file
The important thing is duplicate `tv->file`.
Otherwise, if the caller free the GFile object, `tv->file` is no more guaranteed to point the GFile.
## Source file of tfetextview.c
All the source files are listed in [Section 13](ch13.html).

View file

@ -1,153 +1,92 @@
# tfeapplication.c
# Functions with GtkNotebook
`tfeapplication.c` includes all the code other than `tfetxtview.c` and `tfenotebook.c`.
It does following things.
GtkNotebook is a very important object in the text file editor `tfe`.
It connects the application and TfeTextView objects.
`tfenotebook.h` and `tfenotebook.c` describe a set of functions related to GtkTextbook.
- Application support, mainly handling command line arguments.
- Build widgets using ui file.
- Connect button signals and their handlers.
- Manage CSS.
@@@ tfe5/tfenotebook.h
## main
This header file shows the public functions in `tfenotebook.c`.
Th function `main` is the first invoked function in C language.
It connects the command line given by the user and GTK application.
- `notebook_page_new` generates a new GtkNotebookPage and adds GtkScrolledWindow and TfeTextView under the page.
- `notebook_page_new_with_file` generates a new GtkNotebookPage and adds GtkScrolledWindow and TfeTextView under the page. `file` is set to the pointer to GFile in the TfeTextView object and the file is read and set into GtkTextBuffer.
- `notebook_page_open` lets the user select a file and sets it into GtkTextBuffer.
- `notebook_page_save` save the contents in GtkTextBuffer to a file, using the pointer `tv->file`.
@@@ tfe5/tfeapplication.c main
You probably find that the functions above are higher level functions of `tfe_text_view_new`, `tfe_text_view_new_with_file`, `tef_text_view_open` and `tfe_text_view_save` respectively.
There are two layers.
One of them is `tfe_text_view ...`, which is the lower level layer.
The other is `note_book ...`, which is the higher level layer.
- 6: Generate GtkApplication object.
- 8-10: Connect "startup", "activate" and "open signals to their handlers.
- 12: Run the application.
- 13-14: release the reference to the application and return the status.
Now let's look at each program of the functions.
## statup signal handler
## notebook\_page\_new
"startup" signal is emitted just after the application is generated.
What the signal handler needs to do is initialization of the application.
@@@ tfe5/tfenotebook.c get_untitled notebook_page_build notebook_page_new
- Build the widgets using ui file.
- Connect button signals and their handlers.
- Set CSS.
- 27-37: `notebook_page_new` function.
- 29: `g_return_if_fail` is used because `notebook_page_new` is a public function.
- 34: Generate TfeTextView object.
- 35: Generate filename, which is "Untitled", "Untitled2", ... .
- 1-8: `get_untitled` function.
- 3: Static variable `c` is initialized at the first call of this function. After that `c` keeps its value except it is changed explicitly.
- 4-7: Increase `c` by one and if it is zero then the name is "Untitled". If it is a positive integer then the name is "Untitled<the integer>", for example, "Untitled1", "Untitled2", and so on.
It returns the name.
`g_strdup_printf` generates a string and it should be freed by `g_free` function.
The caller of `get_untitled` is in charge of freeing the memories of the string.
- 36: call `notebook_page_build` to build the contents of the page.
- 10- 25: `notebook_page_build` function.
- 17-18: Generate GtkScrolledWindow and set `tv` to its child.
- 19-20: Generate GtkLabel, then GtkNotebookPage.
- 21-22: Set "tab-expand" property to TRUE.
- 23: Set the page to the current page.
- 24: Connect "change-file" signal and `file_changed` handler.
The handler is as follows.
## notebook\_page\_new\_with\_file
@@@ tfe5/tfeapplication.c tfe_startup
@@@ tfe5/tfenotebook.c notebook_page_new_with_file
- 12-15: Build widgets using ui file (resource).
Connect the top window and the application using `gtk_window_set_application`.
- 16-23: Get buttons and connect their signals and handlers.
- 24: Release the reference to GtkBuilder.
- 26-31: Set CSS.
CSS in GTK is similar to CSS in HTML.
You can set margin, border, padding, color, font and so on with CSS.
In this program CSS is in line 30.
It sets padding, font-family and font size of GtkTextView.
- 26-28: GdkDisplay is used to set CSS.
CSS will be explained in the next subsection.
- 9-10: Call `tfe_text_view_new_with_file`.
If it returns NULL, then do nothing and return because of an error.
-11-13: Get the filename , build the contents of the page, then free `filename`.
## CSS in GTK
## notebook\_page\_open
CSS is an abbretiation of Cascading Style Sheet.
It is originally used with HTML to describe the presentation semantics of a document.
You might have found that the widgets in GTK is simialr to the window in a browser.
It implies that CSS can also be apllied to GTK windowing system.
@@@ tfe5/tfenotebook.c open_response notebook_page_open
### CSS nodes, selectors
- 18-27: `notebook_page_open` function.
- 24: Generate TfeTextView object.
- 25: Connect the signal "open-response" and the handler `open_response`.
- 26: Call `tfe_text_view_open`.
It emits "open-response" signal to inform the status after the series of functions run.
- 1-16: `open_response` handler.
This is the postfunction of `notebook_page_open`.
- 6-7: It the status is NOT `TFE_OPEN_RESPONSE_SUCCESS`, cancel what we did in `notebook_page_open`.
Unref `tv`.
- 8-9: If `tfe_text_view_get_file` returns a pointer not to point GFile, then something bad happens. Cancel what we did. Unref `tv`.
- 10-14: Otherwise, everything was okay.
Get the filename, build the contents of the page, free `filename` and unref `tv`
The syntax of CSS is as follws.
## notebook\_page\_save
selector { color: yellow; padding-top: 10px; ...}
@@@ tfe5/tfenotebook.c notebook_page_save
Every widget has CSS node.
For example GtkTextView has `textview` node.
If you want to set style to GtkTextView, set "textview" to the selector.
- 7-9: Get TfeTextView belongs to the current notebook page.
- 10: Call `tfe_text_view_save`.
textview {color: yeallow; ...}
## file\_changed handler
Class, ID and some other things can be applied to the selector like Web CSS. Refer GTK4 API reference for further information.
`file_changed` is a handler connected to "change-file" signal.
If `tv->file` is changed, TfeTextView emits this signal.
This handler changes the label of GtkNotebookPage.
In line 30, the CSS is a string.
@@@ tfe5/tfenotebook.c file_changed
textview {padding: 10px; font-family: monospace; font-size: 12pt;}
- 8: Get GFile from TfeTextView.
- 9: Get the parent (GkScrolledWindow) of `tv`.
- 10-13: If `file` points GFile, then assign the filename of the GFile into `filename`.
Otherwise (this is the case file is NULL), assign untitled string to `filename`.
- 14-15: Generate a label with the filename and set it into GtkNotebookPage.
- 16-17: Free `filename and unref `file`.
- padding is a space between the border and contents.
This space makes the text easier to read.
- font-family is a name of font.
"monospace" is one of the generic family font keywords.
- font-size is set to 12pt.
It is a bit large, but easy on the eyes especially for elderly people.
### GtkStyleContext, GtkCSSProvider and GdkDisplay
GtkStyleContext is an object that stores styling information affecting a widget.
Each widget is connected to the corresponding GtkStyleContext.
You can get the context by `gtk_widget_get_style_context`.
GtkCssProvider is an object which parses CSS in order to style widgets.
To apply your CSS to wodgets, you need to add GtkStyleProvider (the interface of GtkCSSProvider) to GtkStyleContext.
However, instead, you can add it to GdkDisplay of the window (usually top level window).
Look at the source file of `startup` handler again.
- 28: The display is obtained by `gtk_widget_get_display`.
- 29: Generate GtkCssProvider.
- 30: Set the CSS into the provider.
- 31: Add the provider to the display.
It is possible to add the provider to the context of GtkTextView instead of GdkDiplay.
To do so, rewrite `tfe_text_view_new`.
GtkWidget *
tfe_text_view_new (void) {
GtkWidget *tv;
tv = gtk_widget_new (TFE_TYPE_TEXT_VIEW, NULL);
GtkStyleContext *context;
context = gtk_widget_get_style_context (GTK_WIDGET (tv));
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 (context, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_USER);
return tv;
}
CSS set to the context takes precedence over the one set to the display.
## activate and open handler
The handler of "activate" and "open" signal are `tfe_activate` and `tfe_open` respectively.
They just generate a new GtkNotebookPage.
@@@ tfe5/tfeapplication.c tfe_activate tfe_open
- 1-14: `tfe_activate`.
- 8-10: Get GtkNotebook object.
- 12-13: Generate a new GtkNotebookPage and show the window.
- 16-33: `tfe_open`.
- 24-26: Get GtkNotebook object.
- 28-29: Generate GtkNotebookPage with files.
- 30-31: If opening and reading file failed and no GtkNotebookPage has generated, then generate a empty page.
- 32: Show the window.
These codes have become really simple thanks to tfenotebook.c and tfetextview.c.
## a series of handlers correspond to the button signals
@@@ tfe5/tfeapplication.c open_clicked new_clicked save_clicked close_clicked
`open_clicked`, `new_clicked` and `save_clicked` just call corresponding notebook page functions.
`close_clicked` is a bit complicated.
- 22-25: If there's only one page, closing the last page is considered that it also close the top level window and quit the application.
Therefore, it gets the top level window and call `gtk_window_destroy`.
- 26-28: Otherwise, it removes the current page.
## meson.build
@@@ tfe5/meson.build
This file is just modified the source file names.

View file

@ -1,45 +1,153 @@
# tfe5 source files
# tfeapplication.c
The followings are the source files of tfe5.
`tfeapplication.c` includes all the code other than `tfetxtview.c` and `tfenotebook.c`.
It does following things.
## meson.buld
- Application support, mainly handling command line arguments.
- Build widgets using ui file.
- Connect button signals and their handlers.
- Manage CSS.
## main
Th function `main` is the first invoked function in C language.
It connects the command line given by the user and GTK application.
@@@ tfe5/tfeapplication.c main
- 6: Generate GtkApplication object.
- 8-10: Connect "startup", "activate" and "open signals to their handlers.
- 12: Run the application.
- 13-14: release the reference to the application and return the status.
## statup signal handler
"startup" signal is emitted just after the application is generated.
What the signal handler needs to do is initialization of the application.
- Build the widgets using ui file.
- Connect button signals and their handlers.
- Set CSS.
The handler is as follows.
@@@ tfe5/tfeapplication.c tfe_startup
- 12-15: Build widgets using ui file (resource).
Connect the top window and the application using `gtk_window_set_application`.
- 16-23: Get buttons and connect their signals and handlers.
- 24: Release the reference to GtkBuilder.
- 26-31: Set CSS.
CSS in GTK is similar to CSS in HTML.
You can set margin, border, padding, color, font and so on with CSS.
In this program CSS is in line 30.
It sets padding, font-family and font size of GtkTextView.
- 26-28: GdkDisplay is used to set CSS.
CSS will be explained in the next subsection.
## CSS in GTK
CSS is an abbretiation of Cascading Style Sheet.
It is originally used with HTML to describe the presentation semantics of a document.
You might have found that the widgets in GTK is simialr to the window in a browser.
It implies that CSS can also be apllied to GTK windowing system.
### CSS nodes, selectors
The syntax of CSS is as follws.
selector { color: yellow; padding-top: 10px; ...}
Every widget has CSS node.
For example GtkTextView has `textview` node.
If you want to set style to GtkTextView, set "textview" to the selector.
textview {color: yeallow; ...}
Class, ID and some other things can be applied to the selector like Web CSS. Refer GTK4 API reference for further information.
In line 30, the CSS is a string.
textview {padding: 10px; font-family: monospace; font-size: 12pt;}
- padding is a space between the border and contents.
This space makes the text easier to read.
- font-family is a name of font.
"monospace" is one of the generic family font keywords.
- font-size is set to 12pt.
It is a bit large, but easy on the eyes especially for elderly people.
### GtkStyleContext, GtkCSSProvider and GdkDisplay
GtkStyleContext is an object that stores styling information affecting a widget.
Each widget is connected to the corresponding GtkStyleContext.
You can get the context by `gtk_widget_get_style_context`.
GtkCssProvider is an object which parses CSS in order to style widgets.
To apply your CSS to wodgets, you need to add GtkStyleProvider (the interface of GtkCSSProvider) to GtkStyleContext.
However, instead, you can add it to GdkDisplay of the window (usually top level window).
Look at the source file of `startup` handler again.
- 28: The display is obtained by `gtk_widget_get_display`.
- 29: Generate GtkCssProvider.
- 30: Set the CSS into the provider.
- 31: Add the provider to the display.
It is possible to add the provider to the context of GtkTextView instead of GdkDiplay.
To do so, rewrite `tfe_text_view_new`.
GtkWidget *
tfe_text_view_new (void) {
GtkWidget *tv;
tv = gtk_widget_new (TFE_TYPE_TEXT_VIEW, NULL);
GtkStyleContext *context;
context = gtk_widget_get_style_context (GTK_WIDGET (tv));
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 (context, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_USER);
return tv;
}
CSS set to the context takes precedence over the one set to the display.
## activate and open handler
The handler of "activate" and "open" signal are `tfe_activate` and `tfe_open` respectively.
They just generate a new GtkNotebookPage.
@@@ tfe5/tfeapplication.c tfe_activate tfe_open
- 1-14: `tfe_activate`.
- 8-10: Get GtkNotebook object.
- 12-13: Generate a new GtkNotebookPage and show the window.
- 16-33: `tfe_open`.
- 24-26: Get GtkNotebook object.
- 28-29: Generate GtkNotebookPage with files.
- 30-31: If opening and reading file failed and no GtkNotebookPage has generated, then generate a empty page.
- 32: Show the window.
These codes have become really simple thanks to tfenotebook.c and tfetextview.c.
## a series of handlers correspond to the button signals
@@@ tfe5/tfeapplication.c open_clicked new_clicked save_clicked close_clicked
`open_clicked`, `new_clicked` and `save_clicked` just call corresponding notebook page functions.
`close_clicked` is a bit complicated.
- 22-25: If there's only one page, closing the last page is considered that it also close the top level window and quit the application.
Therefore, it gets the top level window and call `gtk_window_destroy`.
- 26-28: Otherwise, it removes the current page.
## meson.build
@@@ tfe5/meson.build
## tfe.gresource.xml
This file is just modified the source file names.
@@@ tfe5/tfe.gresource.xml
## tfe.ui
@@@ tfe5/tfe.ui
## tfe.h
@@@ tfe5/tfe.h
## tfeapplication.c
@@@ tfe5/tfeapplication.c
### tfenotebook.h
@@@ tfe5/tfenotebook.h
## tfenotebook.c
@@@ tfe5/tfenotebook.c
## tfetextview.h
@@@ tfe5/tfetextview.h
## tfetextview.c
@@@ tfe5/tfetextview.c
## Total number of lines, words and charcters
$$$
wc tfe5/meson.build tfe5/tfeapplication.c tfe5/tfe.gresource.xml tfe5/tfe.h tfe5/tfenotebook.c tfe5/tfenotebook.h tfe5/tfetextview.c tfe5/tfetextview.h tfe5/tfe.ui
$$$

View file

@ -1,168 +1,45 @@
# Menu and action
# tfe5 source files
## Menu
The followings are the source files of tfe5.
Users often use menus to tell the command to the computer.
It is like this:
## meson.buld
![Menu](../image/menu.png)
@@@ tfe5/meson.build
Now let's analyze the menu above.
There are two types of object.
## tfe.gresource.xml
- "File", "Edit", "View", "Cut", "Copy", "Paste" and "Select All".
They are called "menu item" or simply "item".
When the user clicks one of these items, then something will happen.
- Menubar, submenu referenced by "Edit" item and two sections.
They are called "menu".
Menu is an ordered list of items.
They are similar to arrays.
@@@ tfe5/tfe.gresource.xml
![Menu structure](../image/menu_structure.png)
## tfe.ui
- Menubar is a menu which has three items, which are "File", "Edit" and "View".
- The menu item labeled "Edit" has a link to the submenu which has two items.
These two items don't have labels.
Each item refers to a section.
- The first section is a menu which has three items -- "Cut", "Copy" and "Paste".
- The second section is a menu which has one item -- "Select All".
@@@ tfe5/tfe.ui
Menus can build a complicated structure thanks to the links of menu items.
## tfe.h
## GMenuModel, GMenu and GMenuItem
@@@ tfe5/tfe.h
GMenuModel is an abstact object which represents a menu.
GMenu is a simple implementation of GMenuModel and a child object of GMenuModel.
## tfeapplication.c
GObjct -- GMenuModel -- GMenu
@@@ tfe5/tfeapplication.c
Because GMenuModel is an abstract object, it doesn't have any functions to generate it.
Therefore, if you want to generate a menu, use `g_menu_new` function to generate GMenu object.
GMenu inherits all the functions of GMenuModel because of the child object.
### tfenotebook.h
GMenuItem is an object directly derived from GObject.
GMenuItem and Gmenu (or GMenuModel) don't have a parent-child relationship.
@@@ tfe5/tfenotebook.h
GObject -- GMenuModel -- GMenu
GObject -- GMenuItem
## tfenotebook.c
Usually, GMenuItem has attributes.
One of the attributes is label.
For example, there is a menu item which has "Edit" label in the first diagram in this section.
"Cut", "Copy", "Paste" and "Select All" are also the lables of menu items.
Other attributes will be explained later.
@@@ tfe5/tfenotebook.c
Some menu items have a link to another GMenu.
There are two types of links, submenu and section.
## tfetextview.h
GMenuItem can be inserted, appended or prepended to GMenu.
When it is inserted, all of the attribute and link values of the item are copied and used to form a new item within the menu.
The GMenuItem itself is not really inserted.
Therefore, after the insertion, GMenuItem is useless and it should be freed.
The same goes for appending or prepending.
@@@ tfe5/tfetextview.h
The following code shows how to append GMenuItem to GMenu.
## tfetextview.c
GMenu *menu = g_menu_new ();
GMenuItem *menu_item_quit = g_menu_item_new ("Quit", "app.quit");
g_menu_append_item (menu, menu_item_quit);
g_object_unref (menu_item_quit);
@@@ tfe5/tfetextview.c
## Menu and action
One of the attributes of menu items is an action.
This attribute points an action object.
There are two action objects, GSimpleAction and GPropertyAction.
GSimpleAction is often used.
And it is used with a menu item.
Only GSimpleAction is described in this section.
An action corresponds to a menu item will be activated when the menu item is clicked.
Then the action emits an activate signal.
1. menu item is clicked.
2. The corresponding action is activated.
3. The action emits a signal.
4. The connected handler is invoked.
The following code is an example.
static void
quit_activated(GSimpleAction *action, GVariant *parameter, gpointer app) { ... ... ...}
GSimpleAction *act_quit = g_simple_action_new ("quit", NULL);
g_signal_connect (act_quit, "activate", G_CALLBACK (quit_activated), app);
GMenuItem *menu_item_quit = g_menu_item_new ("Quit", "app.quit");
1. `menu_item_quit` is a menu item.
It has a label "Quit" and is connected to an action "app.quit".
"app" is a prefix and "quit" is the name of an action.
The prefix means that the action belongs to GtkApplication.
If the menu is clicked, then the corresponding action "quit" which belongs to GtkApplication will be activated.
2. `act_quit` is an action.
It has a name "quit".
It belongs to GtkApplication, but it is not obvious in the code above.
The function `g_simple_action_new` generates a stateless action.
So, `act_quit` is stateless.
The meaning of stateless will be explained later.
The argument `NULL` means that the action doesn't have an parameter.
Generally, most of the actions are stateless and have no parameter.
When `act_quit` is activated, it will emit "activate" signal.
3. "activate" signal of the action is connected to the handler `quit_activated`.
So, if the action is activated, the handler will be invoked.
## Simple example
The following is a simple example of menus and actions.
@@@ menu1.c
- 3-7: `quit_activated` is a handler of an action `act_quit`.
Handlers of actions have three parameters.
1. The action object which has emitted the signal.
2. Parameter.
In this example it is `NULL` because the second argument of `g_simple_action_new` (line 15) is `NULL`.
You don' t need to care about it.
3. User data.
It is the fourth parameter in the `g_signal_connect` (line 17) that has connected the action and the handler.
- 6: A function `g_application_quit` immediately quits the application.
- 9-33: `on_activate` is a handler of "activate" signal on GtkApplication.
- 11-13: Generate a GtkApplicationWindow and set a pointer to it to `win`. And set the title and default size.
- 15: Generate GSimpleAction `act_quit`.
It is stateless.
The first argument of `g_simple_action_new` is a name of the action and the second argument is a parameter.
If you don't need the parameter, set it `NULL`.
THerefore, `act_quit` has a name "quit" and no parameter.
- 16: Add the action to GtkApplication `app`.
GtkApplication implements an interface GActionMap and GActionGroup.
And GtkApplication can have a group of actions and actions are added by the function `g_action_map_add_action`.
This function is described in GMenuModel section in GIO API reference.
- 17: Connect "activate" signal of the action and the handler `quit_activated`.
- 19-22: Generate GMenu and GMenuItem.
`menubar` and `menu` are GMenu.
`menu_item_menu` and `menu_item_quit` are GMenuItem.
`menu_item_menu` has a label "Menu" and no action.
`menu_item_quit` has a label "Quit".
The second argument "app.quit" is a combination of "app" and "quit".
"app" is a prefix and it means that the action belongs to GtkApplication. "quit" is the name of the action.
Therefore, it points the action which belongs to GtkApplication and has the name "quit" -- it is `act_quit`.
- 23-24: Append `act_quit` to `menu`.
As I mentioned before, all the attribute and link values are copied and used to form a new item within `menu`.
Therefore after the appending, `menu` has a copy of `act_quit` in itself and `act_quit` is no longer needed.
It is freed by `g_object_unref`.
- 25: Set a submenu link to `menu_item_menu`.
And the link points the GMenu `menu`.
- 26-27: Append `menu_item_menu` to `menubar`.
Then free `menu_item_menu`.
GMenu and GMenuItem are connected and finally a menu is made up.
The structure of the menu is shown in the diagram below.
- 29: The menu is set to GtkApplication.
- 30: Set GtkApplicationWindow to show the menubar.
- 31: Show the window.
![menu and action](../image/menu1.png)
![Screenshot of menu1](../image/menu1_screenshot.png)
## Total number of lines, words and charcters
$$$
wc tfe5/meson.build tfe5/tfeapplication.c tfe5/tfe.gresource.xml tfe5/tfe.h tfe5/tfenotebook.c tfe5/tfenotebook.h tfe5/tfetextview.c tfe5/tfetextview.h tfe5/tfe.ui
$$$

View file

@ -1,257 +1,168 @@
# Stateful action
# Menu and action
Some actions have states.
The values of states can be boolean or string.
Actions which have states are called stateful.
## Menu
## Stateful action without a parameter
Users often use menus to tell the command to the computer.
It is like this:
Some menus are called toggle menu.
For example, fullscreen menu has a state which has two values -- fullscreen and non-fullscreen.
The value of the state is changed every time the menu is clicked.
An action corresponds to the fullscreen menu also have a state.
Its value is TRUE or FALSE and it is called boolean value.
TRUE corresponds to fullscreen and FALSE to non-fullscreen.
![Menu](../image/menu.png)
The following is an example code to implement a fullscreen menu except the signal handler.
The signal handler will be described after the explanation of this code.
Now let's analyze the menu above.
There are two types of object.
- "File", "Edit", "View", "Cut", "Copy", "Paste" and "Select All".
They are called "menu item" or simply "item".
When the user clicks one of these items, then something will happen.
- Menubar, submenu referenced by "Edit" item and two sections.
They are called "menu".
Menu is an ordered list of items.
They are similar to arrays.
![Menu structure](../image/menu_structure.png)
- Menubar is a menu which has three items, which are "File", "Edit" and "View".
- The menu item labeled "Edit" has a link to the submenu which has two items.
These two items don't have labels.
Each item refers to a section.
- The first section is a menu which has three items -- "Cut", "Copy" and "Paste".
- The second section is a menu which has one item -- "Select All".
Menus can build a complicated structure thanks to the links of menu items.
## GMenuModel, GMenu and GMenuItem
GMenuModel is an abstact object which represents a menu.
GMenu is a simple implementation of GMenuModel and a child object of GMenuModel.
GObjct -- GMenuModel -- GMenu
Because GMenuModel is an abstract object, it doesn't have any functions to generate it.
Therefore, if you want to generate a menu, use `g_menu_new` function to generate GMenu object.
GMenu inherits all the functions of GMenuModel because of the child object.
GMenuItem is an object directly derived from GObject.
GMenuItem and Gmenu (or GMenuModel) don't have a parent-child relationship.
GObject -- GMenuModel -- GMenu
GObject -- GMenuItem
Usually, GMenuItem has attributes.
One of the attributes is label.
For example, there is a menu item which has "Edit" label in the first diagram in this section.
"Cut", "Copy", "Paste" and "Select All" are also the lables of menu items.
Other attributes will be explained later.
Some menu items have a link to another GMenu.
There are two types of links, submenu and section.
GMenuItem can be inserted, appended or prepended to GMenu.
When it is inserted, all of the attribute and link values of the item are copied and used to form a new item within the menu.
The GMenuItem itself is not really inserted.
Therefore, after the insertion, GMenuItem is useless and it should be freed.
The same goes for appending or prepending.
The following code shows how to append GMenuItem to GMenu.
GMenu *menu = g_menu_new ();
GMenuItem *menu_item_quit = g_menu_item_new ("Quit", "app.quit");
g_menu_append_item (menu, menu_item_quit);
g_object_unref (menu_item_quit);
## Menu and action
One of the attributes of menu items is an action.
This attribute points an action object.
There are two action objects, GSimpleAction and GPropertyAction.
GSimpleAction is often used.
And it is used with a menu item.
Only GSimpleAction is described in this section.
An action corresponds to a menu item will be activated when the menu item is clicked.
Then the action emits an activate signal.
1. menu item is clicked.
2. The corresponding action is activated.
3. The action emits a signal.
4. The connected handler is invoked.
The following code is an example.
static void
on_activate (GApplication *app, gpointer user_data) {
... ... ...
GSimpleAction *act_fullscreen = g_simple_action_new_stateful ("fullscreen", NULL, g_variant_new_boolean (FALSE));
GMenuItem *menu_item_fullscreen = g_menu_item_new ("Full Screen", "win.fullscreen");
g_signal_connect (act_fullscreen, "change-state", G_CALLBACK (fullscreen_changed), win);
... ... ...
}
quit_activated(GSimpleAction *action, GVariant *parameter, gpointer app) { ... ... ...}
- `act_fullscreen` is GSimpleAction.
It is generated by `g_simple_action_new_stateful`.
The function has three arguments.
The first argument "fullscreen" is the name of the action.
The second argument is a parameter type.
`NULL` means the action doesn't have a parameter.
The third argument is the initial state of the action.
It is a GVariant value.
GVariant will be explained in the next subsection.
The function `g_variant_new_boolean (FALSE)` returns a GVariant value which is the boolean value `FALSE`.
- `menu_item_fullscreen` is GMenuItem.
There are two arguments.
The first argument "Full Screen" is a label which is one of the attributes of GMenuItem.
The second argument is called detailed action.
Detailed action has three parts, prefix, action name and target.
"win.fullscreen" means that the prefix is "win", the action name is "fullscreen" and there's no target.
The prefix says that the action belongs to the window.
- connect the action `act_fullscreen` and the "change-state" signal handler `fullscreen_`value2`changed`.
If the fullscreen menu is clicked, then the corresponding action `act_fullscreen` is activated.
But no handler is connected to "activate" signal.
Then, the default behaviour for boolean-stated actions with a NULL parameter type like `act_fullscreen` is to toggle them via the “change-state” signal.
GSimpleAction *act_quit = g_simple_action_new ("quit", NULL);
g_signal_connect (act_quit, "activate", G_CALLBACK (quit_activated), app);
GMenuItem *menu_item_quit = g_menu_item_new ("Quit", "app.quit");
The following is the "change-state" signal handler.
1. `menu_item_quit` is a menu item.
It has a label "Quit" and is connected to an action "app.quit".
"app" is a prefix and "quit" is the name of an action.
The prefix means that the action belongs to GtkApplication.
If the menu is clicked, then the corresponding action "quit" which belongs to GtkApplication will be activated.
2. `act_quit` is an action.
It has a name "quit".
It belongs to GtkApplication, but it is not obvious in the code above.
The function `g_simple_action_new` generates a stateless action.
So, `act_quit` is stateless.
The meaning of stateless will be explained later.
The argument `NULL` means that the action doesn't have an parameter.
Generally, most of the actions are stateless and have no parameter.
When `act_quit` is activated, it will emit "activate" signal.
3. "activate" signal of the action is connected to the handler `quit_activated`.
So, if the action is activated, the handler will be invoked.
static void
fullscreen_changed(GSimpleAction *action, GVariant *value, gpointer win) {
if (g_variant_get_boolean (value))
gtk_window_maximize (GTK_WINDOW (win));
else
gtk_window_unmaximize (GTK_WINDOW (win));
g_simple_action_set_state (action, value);
}
## Simple example
- There are three parameters.
The first parameter is the action which emits the "change-state" signal.
The second parameter is the value of the state of the action.
But it is toggled because of no "activate" signal handler.
Ther third parameter is a user data which is set in `g_signal_connect`.
- If the value is boolean type and `TRUE`, then maximize the window.
Otherwise unmaximize.
- Set `value` to the state of the action.
Note: the second argument was the toggled state value, but at this stage the state of the action has the original value.
So, you need to set the new value by `g_simple_action_set_state`.
The following is a simple example of menus and actions.
You can use "activate" signal instead ot "change-state" signal, or both signals.
But the way above is the simplest and best.
@@@ menu1.c
### GVariant
- 3-7: `quit_activated` is a handler of an action `act_quit`.
Handlers of actions have three parameters.
1. The action object which has emitted the signal.
2. Parameter.
In this example it is `NULL` because the second argument of `g_simple_action_new` (line 15) is `NULL`.
You don' t need to care about it.
3. User data.
It is the fourth parameter in the `g_signal_connect` (line 17) that has connected the action and the handler.
- 6: A function `g_application_quit` immediately quits the application.
- 9-33: `on_activate` is a handler of "activate" signal on GtkApplication.
- 11-13: Generate a GtkApplicationWindow and set a pointer to it to `win`. And set the title and default size.
- 15: Generate GSimpleAction `act_quit`.
It is stateless.
The first argument of `g_simple_action_new` is a name of the action and the second argument is a parameter.
If you don't need the parameter, set it `NULL`.
THerefore, `act_quit` has a name "quit" and no parameter.
- 16: Add the action to GtkApplication `app`.
GtkApplication implements an interface GActionMap and GActionGroup.
And GtkApplication can have a group of actions and actions are added by the function `g_action_map_add_action`.
This function is described in GMenuModel section in GIO API reference.
- 17: Connect "activate" signal of the action and the handler `quit_activated`.
- 19-22: Generate GMenu and GMenuItem.
`menubar` and `menu` are GMenu.
`menu_item_menu` and `menu_item_quit` are GMenuItem.
`menu_item_menu` has a label "Menu" and no action.
`menu_item_quit` has a label "Quit".
The second argument "app.quit" is a combination of "app" and "quit".
"app" is a prefix and it means that the action belongs to GtkApplication. "quit" is the name of the action.
Therefore, it points the action which belongs to GtkApplication and has the name "quit" -- it is `act_quit`.
- 23-24: Append `act_quit` to `menu`.
As I mentioned before, all the attribute and link values are copied and used to form a new item within `menu`.
Therefore after the appending, `menu` has a copy of `act_quit` in itself and `act_quit` is no longer needed.
It is freed by `g_object_unref`.
- 25: Set a submenu link to `menu_item_menu`.
And the link points the GMenu `menu`.
- 26-27: Append `menu_item_menu` to `menubar`.
Then free `menu_item_menu`.
GMenu and GMenuItem are connected and finally a menu is made up.
The structure of the menu is shown in the diagram below.
- 29: The menu is set to GtkApplication.
- 30: Set GtkApplicationWindow to show the menubar.
- 31: Show the window.
GVarient can contain boolean, string or other simple type values.
For example, the following program set TRUE to `value` whose type is GVariant.
![menu and action](../image/menu1.png)
GVariant *value = g_variant_new_boolean (TRUE);
Another example is:
GVariant *value2 = g_variant_new_string ("Hello");
`value2` is a GVariant and it has a string type value "Hello".
GVariant can contain other types like int16, int32, int64, double and so on.
If you want to get the boolean value, use g\_variant\_get series functions.
gboolean bool = g_variant_get_boolean (value);
Because `value` has been generated as a boolean type GVariant and `TRUE` value, `bool` equals `TRUE`.
In the same way, you can get a string from `value2`
const gchar *str = g_variant_get_string (value2, NULL);
The second parameter is a pointer to gsize type variable (gsize is defined as unsigned long).
If it isn't NULL, then the length of the string will be set by the function.
If it is NULL, nothing happens.
The returned string `str` can't be changed.
## Stateful action with a parameter
Another example of stateful actions is an action corresponds to color select menus.
For example, there are three menus and each menu has red, green or blue color respectively.
They determine the background color of a certain widget.
One action is connected to the three menus.
The action has a state which values are "red", "green" and "blue".
The values are string.
Those colors are given to the signal handler as a parameter.
static void
on_activate (GApplication *app, gpointer user_data) {
... ... ...
GSimpleAction *act_color = g_simple_action_new_stateful ("color", g_variant_type_new("s"), g_variant_new_string ("red"));
GMenuItem *menu_item_red = g_menu_item_new ("Red", "win.color::red");
GMenuItem *menu_item_green = g_menu_item_new ("Green", "win.color::green");
GMenuItem *menu_item_blue = g_menu_item_new ("Blue", "win.color::blue");
g_signal_connect (act_color, "activate", G_CALLBACK (color_activated), win);
... ... ...
}
- `act_color` is GSimpleAction.
It is generated by `g_simple_action_new_stateful`.
The function has three arguments.
The first argument "color" is the name of the action.
The second argument is a parameter type which is GVariantType.
`g_variant_type_new("s")` generates GVariantType which is a string type (G\_VARIANT\_TYPE\_STRING).
The third argument is the initial state of the action.
It is a GVariant.
GVariantType will be explained in the next subsection.
The function `g_variant_new_string ("red")` returns a GVariant value which has the string value "red".
- `menu_item_red` is GMenuItem.
There are two arguments.
The first argument "Red" is a label which is one of the attributes of GMenuItem.
The second argument is a detailed action.
Its prefix is "win", action name is "color" and target is "red".
Target is sent to the action as a parameter.
The same goes for `menu_item_green` and `menu_item_blue`.
- connect the action `act_color` and the "activate" signal handler `color_activate`.
If one of the three menus is clicked, then the action `act_color` is activated with a parameter to which the menu item gives its target.
No handler is connected to "change-state" signal.
Then the default behaviour is to call `g_simple_action_set_state()` to set the state to the requested value.
The following is the "activate" signal handler.
static void
color_activated(GSimpleAction *action, GVariant *parameter, gpointer win) {
gchar *color = g_strdup_printf ("label#lb {background-color: %s;}", g_variant_get_string (parameter, NULL));
gtk_css_provider_load_from_data (provider, color, -1);
g_free (color);
g_action_change_state (G_ACTION (action), parameter);
}
- There are three parameters.
The first parameter is the action which emits the "activate" signal.
The second parameter is the parameter given to the action.
It is a color specified by the menu.
The third parameter is a user data which is set in `g_signal_connect`.
- `color` is a CSS string generated by `g_strdup_printf`.
The parameter of `g_str_dup` is the same as printf C standard function.
`g_variant_get_string` get the string contained in `parameter`.
- Set the color to the css provider.
- Free the string `color`.
- Change the state by `g_action_change_state`.
The function just set the parameter to the state of the action by `g_simple_action_set_state`.
Therefore, you can use `g_simple_action_set_state` instead of `g_action_change_state`.
Note: If you have set a "change-state" signal handler, `g_action_change_state` will emit "change-state" signal instead of calling `g_simple_action_set_state`.
### GVariantType
GVariantType gives a type of GVariant.
GVariant can contain many kinds of types.
And the type often needs to be recognized at runtime.
GVariantType provides such functionality.
When GVariantType is generated, the type is expressed by the string.
- "b" means boolean type.
- "s" means string type.
The following program is a simple example.
It finally output the string "s".
@@@ gvarianttype_test.c
- `g_variant_tpe_new` generates GVariantType.
It uses a type string "s" which means string.
- `g_variant_type_peek_string` takes a peek at `vtype`.
It is the string "s" given at the generation time.
- print the string to the terminal.
## Example code
The following code includes stateful actions above.
This program has menus like this:
![menu2](../image/menu2.png)
- Fullscreen menu toggles the size of the window between maximum and non-maximum.
If the window is maximum size, which is called full screen, then a check mark is put before "fullscreen" label.
- Red, green and blue menu determines the back ground color of the label, which is the child widget of the window.
The menus have radio buttons on the left of each of the menus.
And the radio button of the selected menu turns on.
- Quit menu quits the application.
The code is as follows.
@@@ menu2.c
- 5-26: Signal handlers.
They have been explained in this section.
- 30-36: `win` and `lb` are GtkApplicationWindow and GtkLabel respectively.
`win` has a title "menu2" and its defaust size is 400x300.
`lb` is named as "lb".
The name is used in CSS.
`lb` is set to `win` as a child.
- 38-43: Three actions are defined.
They are:
- stateful and has no parameter.
It has a toggle state.
- stateful and has a parameter.
Parameter is a string type.
- stateless and has no parameter.
- 45-54: Generate GMenu and GMenuItem.
There are three sections.
- 56-61: Signals are connected to handlers.
And actions are added to GActionMap.
Because `act_fullscreen` and `act_color` have "win" prefix and belong to GtkApplicationWindow,
they are added to `win`.
GtkApplicationWindow implements GActionModel interface like GtkApplication.
`act_quit` has "app" prefix and belongs to GtkApplication,
it is added to `app`.
- 63-77: Connect and build the menus.
Useless GMenuItem are freed.
- 79-80: GMenuModel `menubar` is set to `app`.
Set show menubar property to `TRUE` in `win`.
Note: `gtk_application_window_set_show_menubar` generates GtkPopoverMenubar from GMenuModel.
This is a different point between Gtk3 and Gtk4.
And you can use GtkPopoverMenubar directly and set it as a descendant widget of the window.
You may use GtkBox as a child widget of the window and set GtkPopoverMenubar as the first child of the box.
- 82-87: Set CSS.
`provider` is GtkCssProvider which is defined in line three as a static variable.
Its CSS data is:
`label#lb {background-color: red;}`.
"label#lb" is called selector.
"label" is the node of GtkLabel.
"#" precedes an ID which is an identiable name of the widget.
"lb" is the name of GtkLabel `lb`.
(See line 35).
The style is surrounded by open and close braces.
The style is applied to GtkLabel which has a name "lb".
Other GtkLabel have no effect from this.
The provider is added to GdkDisplay.
- 90: Show the window.
![Screenshot of menu1](../image/menu1_screenshot.png)

View file

@ -1,145 +1,257 @@
# Ui file for menu and action entries
# Stateful action
## Ui file for menu
Some actions have states.
The values of states can be boolean or string.
Actions which have states are called stateful.
You might have thought that building menus is really bothersome.
Yes, the program was complicated and it needs lots of time to code it.
The situation is similar to building widgets.
When we built widgets, using ui file was a good way to avoid such complicated coding.
The same goes for menus.
## Stateful action without a parameter
The ui file for menus has interface, menu tags.
The file starts and ends with interface tag.
Some menus are called toggle menu.
For example, fullscreen menu has a state which has two values -- fullscreen and non-fullscreen.
The value of the state is changed every time the menu is clicked.
An action corresponds to the fullscreen menu also have a state.
Its value is TRUE or FALSE and it is called boolean value.
TRUE corresponds to fullscreen and FALSE to non-fullscreen.
<interface>
<menu id="menubar">
</menu>
</interface>
The following is an example code to implement a fullscreen menu except the signal handler.
The signal handler will be described after the explanation of this code.
`menu` tag corresponds to GMenu object.
`id` attribute defines the name of the object.
It will be refered by GtkBuilder.
static void
on_activate (GApplication *app, gpointer user_data) {
... ... ...
GSimpleAction *act_fullscreen = g_simple_action_new_stateful ("fullscreen", NULL, g_variant_new_boolean (FALSE));
GMenuItem *menu_item_fullscreen = g_menu_item_new ("Full Screen", "win.fullscreen");
g_signal_connect (act_fullscreen, "change-state", G_CALLBACK (fullscreen_changed), win);
... ... ...
}
<submenu>
<attribute name="label">File</attribute>
<item>
<attribute name="label">New</attribute>
<attribute name="action">win.new</attribute>
</item>
</submenu>
- `act_fullscreen` is GSimpleAction.
It is generated by `g_simple_action_new_stateful`.
The function has three arguments.
The first argument "fullscreen" is the name of the action.
The second argument is a parameter type.
`NULL` means the action doesn't have a parameter.
The third argument is the initial state of the action.
It is a GVariant value.
GVariant will be explained in the next subsection.
The function `g_variant_new_boolean (FALSE)` returns a GVariant value which is the boolean value `FALSE`.
- `menu_item_fullscreen` is GMenuItem.
There are two arguments.
The first argument "Full Screen" is a label which is one of the attributes of GMenuItem.
The second argument is called detailed action.
Detailed action has three parts, prefix, action name and target.
"win.fullscreen" means that the prefix is "win", the action name is "fullscreen" and there's no target.
The prefix says that the action belongs to the window.
- connect the action `act_fullscreen` and the "change-state" signal handler `fullscreen_`value2`changed`.
If the fullscreen menu is clicked, then the corresponding action `act_fullscreen` is activated.
But no handler is connected to "activate" signal.
Then, the default behaviour for boolean-stated actions with a NULL parameter type like `act_fullscreen` is to toggle them via the “change-state” signal.
`item` tag corresponds to item in GMenu which has the same structure as GMenuItem.
The item above has a label attribute.
Its value is "New".
The item also has an action attribute and its value is "win.new".
"win" is a prefix and "new" is an action name.
`submenu` tag corresponds to both GMenuItem and GMenu.
The GMenuItem has a link to GMenu.
The following is the "change-state" signal handler.
The ui file above can be described as follows.
static void
fullscreen_changed(GSimpleAction *action, GVariant *value, gpointer win) {
if (g_variant_get_boolean (value))
gtk_window_maximize (GTK_WINDOW (win));
else
gtk_window_unmaximize (GTK_WINDOW (win));
g_simple_action_set_state (action, value);
}
<item>
<attribute name="label">File</attribute>
<link name="submenu">
<item>
<attribute name="label">New</attribute>
<attribute name="action">win.new</attribute>
</item>
</link>
</item>
- There are three parameters.
The first parameter is the action which emits the "change-state" signal.
The second parameter is the value of the state of the action.
But it is toggled because of no "activate" signal handler.
Ther third parameter is a user data which is set in `g_signal_connect`.
- If the value is boolean type and `TRUE`, then maximize the window.
Otherwise unmaximize.
- Set `value` to the state of the action.
Note: the second argument was the toggled state value, but at this stage the state of the action has the original value.
So, you need to set the new value by `g_simple_action_set_state`.
`link` tag expresses the link to submenu.
And at the same time it also expresses the submenu itself.
This file illustrates the relationship between the menus and items better than the prior ui file.
But `submenu` tag is simple and easy to understand.
So, we usually prefer the former ui file style.
You can use "activate" signal instead ot "change-state" signal, or both signals.
But the way above is the simplest and best.
The following is a screenshot of the sample program in this section.
Its name is `menu3`.
### GVariant
![menu3](../image/menu3.png)
GVarient can contain boolean, string or other simple type values.
For example, the following program set TRUE to `value` whose type is GVariant.
The following is the ui file of the menu in `menu3`.
GVariant *value = g_variant_new_boolean (TRUE);
@@@ menu3/menu3.ui
Another example is:
The ui file is converted to the resource by the resouce compiler `glib-compile-resouces` with xml file below.
GVariant *value2 = g_variant_new_string ("Hello");
@@@ menu3/menu3.gresource.xml
`value2` is a GVariant and it has a string type value "Hello".
GVariant can contain other types like int16, int32, int64, double and so on.
GtkBuilder builds menus from the resource.
If you want to get the boolean value, use g\_variant\_get series functions.
GtkBuilder *builder = gtk_builder_new_from_resource ("/com/github/ToshioCP/menu3/menu3.ui");
GMenuModel *menubar = G_MENU_MODEL (gtk_builder_get_object (builder, "menubar"));
gboolean bool = g_variant_get_boolean (value);
gtk_application_set_menubar (GTK_APPLICATION (app), menubar);
g_object_unref (builder);
Because `value` has been generated as a boolean type GVariant and `TRUE` value, `bool` equals `TRUE`.
In the same way, you can get a string from `value2`
It is important that `builder` is unreferred after the GMenuModel `menubar` is set to the application.
If you do it before setting, bad thing will happen -- your computer might freeze.
const gchar *str = g_variant_get_string (value2, NULL);
## Action entry
The second parameter is a pointer to gsize type variable (gsize is defined as unsigned long).
If it isn't NULL, then the length of the string will be set by the function.
If it is NULL, nothing happens.
The returned string `str` can't be changed.
The coding for building actions and signal handlers is always the same.
Therefore, it can be automated.
You can implement them easily with GActionEntry `g_action_map_add_action_entries`.
## Stateful action with a parameter
GActionEntry is a strutcure.
It contains action name, signal handlers, parameter and state.
Another example of stateful actions is an action corresponds to color select menus.
For example, there are three menus and each menu has red, green or blue color respectively.
They determine the background color of a certain widget.
One action is connected to the three menus.
The action has a state which values are "red", "green" and "blue".
The values are string.
Those colors are given to the signal handler as a parameter.
typedef struct _GActionEntry GActionEntry;
static void
on_activate (GApplication *app, gpointer user_data) {
... ... ...
GSimpleAction *act_color = g_simple_action_new_stateful ("color", g_variant_type_new("s"), g_variant_new_string ("red"));
GMenuItem *menu_item_red = g_menu_item_new ("Red", "win.color::red");
GMenuItem *menu_item_green = g_menu_item_new ("Green", "win.color::green");
GMenuItem *menu_item_blue = g_menu_item_new ("Blue", "win.color::blue");
g_signal_connect (act_color, "activate", G_CALLBACK (color_activated), win);
... ... ...
}
struct _GActionEntry
{
const gchar *name; /* action name */
void (* activate) (GSimpleAction *action, GVariant *parameter, gpointer user_data); /* activate handler */
const gchar *parameter_type; /* the type of the parameter given as a single GVariant type string */
const gchar *state; /* initial state given in GVariant text format */
void (* change_state) (GSimpleAction *action, GVariant *value, gpointer user_data); /* change-state handler */
/*< private >*/
gsize padding[3];
};
- `act_color` is GSimpleAction.
It is generated by `g_simple_action_new_stateful`.
The function has three arguments.
The first argument "color" is the name of the action.
The second argument is a parameter type which is GVariantType.
`g_variant_type_new("s")` generates GVariantType which is a string type (G\_VARIANT\_TYPE\_STRING).
The third argument is the initial state of the action.
It is a GVariant.
GVariantType will be explained in the next subsection.
The function `g_variant_new_string ("red")` returns a GVariant value which has the string value "red".
- `menu_item_red` is GMenuItem.
There are two arguments.
The first argument "Red" is a label which is one of the attributes of GMenuItem.
The second argument is a detailed action.
Its prefix is "win", action name is "color" and target is "red".
Target is sent to the action as a parameter.
The same goes for `menu_item_green` and `menu_item_blue`.
- connect the action `act_color` and the "activate" signal handler `color_activate`.
If one of the three menus is clicked, then the action `act_color` is activated with a parameter to which the menu item gives its target.
No handler is connected to "change-state" signal.
Then the default behaviour is to call `g_simple_action_set_state()` to set the state to the requested value.
For example, the actions in the previous section are:
The following is the "activate" signal handler.
{ "fullscreen", NULL, NULL, "false", fullscreen_changed }
{ "color", color_activated, "s", "red", NULL }
{ "quit", quit_activated, NULL, NULL, NULL },
static void
color_activated(GSimpleAction *action, GVariant *parameter, gpointer win) {
gchar *color = g_strdup_printf ("label#lb {background-color: %s;}", g_variant_get_string (parameter, NULL));
gtk_css_provider_load_from_data (provider, color, -1);
g_free (color);
g_action_change_state (G_ACTION (action), parameter);
}
And `g_action_map_add_action_entries` does all the process instead of the functions you have needed.
- There are three parameters.
The first parameter is the action which emits the "activate" signal.
The second parameter is the parameter given to the action.
It is a color specified by the menu.
The third parameter is a user data which is set in `g_signal_connect`.
- `color` is a CSS string generated by `g_strdup_printf`.
The parameter of `g_str_dup` is the same as printf C standard function.
`g_variant_get_string` get the string contained in `parameter`.
- Set the color to the css provider.
- Free the string `color`.
- Change the state by `g_action_change_state`.
The function just set the parameter to the state of the action by `g_simple_action_set_state`.
Therefore, you can use `g_simple_action_set_state` instead of `g_action_change_state`.
const GActionEntry app_entries[] = {
{ "quit", quit_activated, NULL, NULL, NULL }
};
g_action_map_add_action_entries (G_ACTION_MAP (app), app_entries, G_N_ELEMENTS (app_entries), app);
Note: If you have set a "change-state" signal handler, `g_action_change_state` will emit "change-state" signal instead of calling `g_simple_action_set_state`.
The code above does:
### GVariantType
- Build the "quit" action
- Connect the action and the "activate" signal handler `quit_activate`
- Add the action to the action map `app`.
GVariantType gives a type of GVariant.
GVariant can contain many kinds of types.
And the type often needs to be recognized at runtime.
GVariantType provides such functionality.
const GActionEntry win_entries[] = {
{ "fullscreen", NULL, NULL, "false", fullscreen_changed },
{ "color", color_activated, "s", "red", NULL }
};
g_action_map_add_action_entries (G_ACTION_MAP (win), win_entries, G_N_ELEMENTS (win_entries), win);
When GVariantType is generated, the type is expressed by the string.
The code above does:
- "b" means boolean type.
- "s" means string type.
- Build the "fullscreen" action and "color" action.
- Connect the "fullscreen" action and the "change-state" signal handler `fullscreen_changed`
- Its initial state is set to FALSE.
- Connect the "color" action and the "activate" signal handler `color_activate`
- Its parameter type is string and the initial value is "red".
- Add the action to the action map `win`.
The following program is a simple example.
It finally output the string "s".
@@@ gvarianttype_test.c
- `g_variant_tpe_new` generates GVariantType.
It uses a type string "s" which means string.
- `g_variant_type_peek_string` takes a peek at `vtype`.
It is the string "s" given at the generation time.
- print the string to the terminal.
## Example code
The following code includes stateful actions above.
This program has menus like this:
The C source code of `menu3` and `meson.build` is as follows.
![menu2](../image/menu2.png)
@@@ menu3/menu3.c
- Fullscreen menu toggles the size of the window between maximum and non-maximum.
If the window is maximum size, which is called full screen, then a check mark is put before "fullscreen" label.
- Red, green and blue menu determines the back ground color of the label, which is the child widget of the window.
The menus have radio buttons on the left of each of the menus.
And the radio button of the selected menu turns on.
- Quit menu quits the application.
meson.build
The code is as follows.
@@@ menu2.c
- 5-26: Signal handlers.
They have been explained in this section.
- 30-36: `win` and `lb` are GtkApplicationWindow and GtkLabel respectively.
`win` has a title "menu2" and its defaust size is 400x300.
`lb` is named as "lb".
The name is used in CSS.
`lb` is set to `win` as a child.
- 38-43: Three actions are defined.
They are:
- stateful and has no parameter.
It has a toggle state.
- stateful and has a parameter.
Parameter is a string type.
- stateless and has no parameter.
- 45-54: Generate GMenu and GMenuItem.
There are three sections.
- 56-61: Signals are connected to handlers.
And actions are added to GActionMap.
Because `act_fullscreen` and `act_color` have "win" prefix and belong to GtkApplicationWindow,
they are added to `win`.
GtkApplicationWindow implements GActionModel interface like GtkApplication.
`act_quit` has "app" prefix and belongs to GtkApplication,
it is added to `app`.
- 63-77: Connect and build the menus.
Useless GMenuItem are freed.
- 79-80: GMenuModel `menubar` is set to `app`.
Set show menubar property to `TRUE` in `win`.
Note: `gtk_application_window_set_show_menubar` generates GtkPopoverMenubar from GMenuModel.
This is a different point between Gtk3 and Gtk4.
And you can use GtkPopoverMenubar directly and set it as a descendant widget of the window.
You may use GtkBox as a child widget of the window and set GtkPopoverMenubar as the first child of the box.
- 82-87: Set CSS.
`provider` is GtkCssProvider which is defined in line three as a static variable.
Its CSS data is:
`label#lb {background-color: red;}`.
"label#lb" is called selector.
"label" is the node of GtkLabel.
"#" precedes an ID which is an identiable name of the widget.
"lb" is the name of GtkLabel `lb`.
(See line 35).
The style is surrounded by open and close braces.
The style is applied to GtkLabel which has a name "lb".
Other GtkLabel have no effect from this.
The provider is added to GdkDisplay.
- 90: Show the window.
@@@ menu3/meson.build

View file

@ -1,4 +1,145 @@
# GtkMenuButton
# Ui file for menu and action entries
before close
## Ui file for menu
You might have thought that building menus is really bothersome.
Yes, the program was complicated and it needs lots of time to code it.
The situation is similar to building widgets.
When we built widgets, using ui file was a good way to avoid such complicated coding.
The same goes for menus.
The ui file for menus has interface, menu tags.
The file starts and ends with interface tag.
<interface>
<menu id="menubar">
</menu>
</interface>
`menu` tag corresponds to GMenu object.
`id` attribute defines the name of the object.
It will be refered by GtkBuilder.
<submenu>
<attribute name="label">File</attribute>
<item>
<attribute name="label">New</attribute>
<attribute name="action">win.new</attribute>
</item>
</submenu>
`item` tag corresponds to item in GMenu which has the same structure as GMenuItem.
The item above has a label attribute.
Its value is "New".
The item also has an action attribute and its value is "win.new".
"win" is a prefix and "new" is an action name.
`submenu` tag corresponds to both GMenuItem and GMenu.
The GMenuItem has a link to GMenu.
The ui file above can be described as follows.
<item>
<attribute name="label">File</attribute>
<link name="submenu">
<item>
<attribute name="label">New</attribute>
<attribute name="action">win.new</attribute>
</item>
</link>
</item>
`link` tag expresses the link to submenu.
And at the same time it also expresses the submenu itself.
This file illustrates the relationship between the menus and items better than the prior ui file.
But `submenu` tag is simple and easy to understand.
So, we usually prefer the former ui file style.
The following is a screenshot of the sample program in this section.
Its name is `menu3`.
![menu3](../image/menu3.png)
The following is the ui file of the menu in `menu3`.
@@@ menu3/menu3.ui
The ui file is converted to the resource by the resouce compiler `glib-compile-resouces` with xml file below.
@@@ menu3/menu3.gresource.xml
GtkBuilder builds menus from the resource.
GtkBuilder *builder = gtk_builder_new_from_resource ("/com/github/ToshioCP/menu3/menu3.ui");
GMenuModel *menubar = G_MENU_MODEL (gtk_builder_get_object (builder, "menubar"));
gtk_application_set_menubar (GTK_APPLICATION (app), menubar);
g_object_unref (builder);
It is important that `builder` is unreferred after the GMenuModel `menubar` is set to the application.
If you do it before setting, bad thing will happen -- your computer might freeze.
## Action entry
The coding for building actions and signal handlers is always the same.
Therefore, it can be automated.
You can implement them easily with GActionEntry `g_action_map_add_action_entries`.
GActionEntry is a strutcure.
It contains action name, signal handlers, parameter and state.
typedef struct _GActionEntry GActionEntry;
struct _GActionEntry
{
const gchar *name; /* action name */
void (* activate) (GSimpleAction *action, GVariant *parameter, gpointer user_data); /* activate handler */
const gchar *parameter_type; /* the type of the parameter given as a single GVariant type string */
const gchar *state; /* initial state given in GVariant text format */
void (* change_state) (GSimpleAction *action, GVariant *value, gpointer user_data); /* change-state handler */
/*< private >*/
gsize padding[3];
};
For example, the actions in the previous section are:
{ "fullscreen", NULL, NULL, "false", fullscreen_changed }
{ "color", color_activated, "s", "red", NULL }
{ "quit", quit_activated, NULL, NULL, NULL },
And `g_action_map_add_action_entries` does all the process instead of the functions you have needed.
const GActionEntry app_entries[] = {
{ "quit", quit_activated, NULL, NULL, NULL }
};
g_action_map_add_action_entries (G_ACTION_MAP (app), app_entries, G_N_ELEMENTS (app_entries), app);
The code above does:
- Build the "quit" action
- Connect the action and the "activate" signal handler `quit_activate`
- Add the action to the action map `app`.
const GActionEntry win_entries[] = {
{ "fullscreen", NULL, NULL, "false", fullscreen_changed },
{ "color", color_activated, "s", "red", NULL }
};
g_action_map_add_action_entries (G_ACTION_MAP (win), win_entries, G_N_ELEMENTS (win_entries), win);
The code above does:
- Build the "fullscreen" action and "color" action.
- Connect the "fullscreen" action and the "change-state" signal handler `fullscreen_changed`
- Its initial state is set to FALSE.
- Connect the "color" action and the "activate" signal handler `color_activate`
- Its parameter type is string and the initial value is "red".
- Add the action to the action map `win`.
## Example code
The C source code of `menu3` and `meson.build` is as follows.
@@@ menu3/menu3.c
meson.build
@@@ menu3/meson.build

View file

@ -1,141 +1,238 @@
# Widgets (1)
# GtkApplication and GtkApplicationWindow
## GtkLabel, GtkButton and Gtkbox
## GtkApplication
### GtkLabel
### GtkApplication and g\_application\_run
We made an window and show it on the screen in the previous chapter.
Now we go on to the next topic, widgets in the window.
The simplest widget is GtkLabel.
It is a widget with a string in it.
Usually people write a programming code to make an application.
What are appications?
Applications are software that runs using libraries, which includes OS, frameworks and so on.
In Gtk4 programming, GtkApplication is an object that runs on GTK libraries.
@@@ lb1.c
The basic way how to write GtkApplication is as follows.
Save this program to a file `lb1.c`.
Then compile and run it.
- Generate a GtkApplication object
- Run it
That's all.
Very simple.
The following is the C code representing the scenario above.
@@@ misc/pr1.c
The first line says that this program includes the GTK header libraries.
The function `main` above is a startup function in C language.
The variable `app` is defined as a pointer to GtkApplication, which is actually a structure in which information about the application is stored.
The function `gtk_application_new` generates a GtkApplication and sets its pointer to `app`.
The meaning of the arguments will be explained later.
The function `g_application_run` invokes the GtkApplication pointed by `app`.
(We often say that the function invokes `app`.
Actually, `app` is not an object but an pointer to the object.
However, it is simple and short, and probably no confusion occurs.)
To compile this, the following command needs to be run.
The string pr1.c is the filename of the C source code.
$ gcc `pkg-config --cflags gtk4` pr1.c `pkg-config --libs gtk4`
The C compiler gcc generates an executable file `a.out`.
Let's run it.
$ comp lb1
$ ./a.out
A window with a message "Hello." appears.
(a.out:13533): GLib-GIO-WARNING **: 15:30:17.449: Your application does not implement g_application_activate() and has no handlers connected to the "activate" signal. It should do one of these.
$
![Screenshot of the label](../image/screenshot_lb1.png)
Oh, just an error message.
But this error message means that the GtkApplication object ran without a doubt.
Now, think about the message in the next section.
There's only a little change between `pr4.c` and `lb1.c`.
Diff is a good program to know the difference between two files.
### signal
$$$
diff misc/pr4.c lb1.c
$$$
The message tells us that:
This tells us:
1. The application GtkApplication doesn't implement `g_application_activate()`.
2. And it has no handlers connected to the activate signal.
3. You need to solve at least one of this.
- The definition of a variable lab is added.
- The title of the window is changed.
- A label is generated and connected to the window.
These two cause of the error are related to signals.
So, I will explain it to you first.
The function `gtk_window_set_child (GTK_WINDOW (win), lab)` makes the label `lab` a child widget of the window `win`.
Be careful.
A child widget is different from a child object.
Objects have parent-child relationship and Widgets also have parent-child relationship.
But these two relationships are totally different.
Don't be confused.
In the program `lb1.c`, `lab` is a child widget of `win`.
Child widgets are always located inside its parent widget in the screen.
See the window appeared on the screen.
The window includes the label.
Signal is emitted when something happens.
For example, a window is generated, a window is destroyed and so on.
The signal "activate" is emitted when the application is activated.
If the signal is connected to a function, which is called signal handler or simply handler, then the function invokes when the signal emits.
The flow is like this:
The window `win` dosen't have any parents.
We call such a window top-level window.
One application can have two or more top-level windows.
1. Something happens.
2. If it's related to a certain signal, then the signal is emitted.
3. If the signal is connected to a handler in advance, then the handler is invoked.
### GtkButton
Signals are defined in objects.
For example, "activate" signal belongs to GApplication object, which is a parent object of GtkApplication object.
GApplication object is a child object of GObject object.
GObject is the top object in the hierarchy of all the objects.
Next widget is GtkButton.
It has a label or icon on it.
In this subsection, we will make a button with a label.
When a button is clicked on, it emits a "clicked" signal.
The following program shows how to catch the signal and do something.
GObject -- GApplication -- GtkApplication
<---parent --->child
@@@ lb2.c
A child object derives signals, functions, properties and so on from its parent object.
So, Gtkapplication also has the "activate" signal.
Look at the line 17 to 19.
First, generate a GtkButton widget `btn` with a label "Click me".
Then, set it to the window `win` as a child.
Finally, connect a "clicked" signal of the button to a handler (function) `on_click`.
So, if `btn` is clicked, the function `on_click` is invoked.
Now we can solve the problem in `pr1.c`.
We need to connect the activate signal to a handler.
We use a function `g_signal_connect` which connects a signal to a handler.
Name the program `lb2.c` and save it.
Now compile and run it.
@@@ misc/pr2.c
![Screenshot of the label](../image/screenshot_lb2.png)
A window with the button appears.
Click the button (it is a large button, you can click everywhere inside the window), then a string "Clicked." appears on the shell terminal.
It shows the handler was invoked by clicking the button.
First, we define the handler `on_activate` which simply displays a message.
In the function `main`, we add `g_signal_connect` before `g_application_run`.
The function `g_signal_connect` has four arguments.
It's fairly good for us to make sure that the clicked signal was caught and the handler was invoked.
However, using g_print is out of harmony with GTK which is a GUI library.
So, we will change the handler.
The following code is `lb3.c`.
1. An object to which the signal belongs.
2. The name of the signal.
3. A handler function (also called callback), which needs to be casted by `G_CALLBACK`.
4. Data to pass to the handler. If no data is necessary, NULL should be given.
@@@ lb3.c on_clicked on_activate
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.
And the difference between `lb2.c` and `lb3.c` is as follows.
In addition, `g_signal_connect` is described in GObject API reference.
API reference is very important.
You should see and understand it to write GTK applications.
$$$
diff lb2.c lb3.c
$$$
Let's compile the source file `pr2.c` above and run it.
The change is:
$ gcc `pkg-config --cflags gtk4` pr2.c `pkg-config --libs gtk4`
$ ./a.out
GtkApplication is activated.
$
- The function `g_print` in `lb2.c` was deleted and two lines above are inserted instead.
- The label of `btn` is changed from "Click me" to "Quit".
- The fourth argument of `g_signal_connect` is changed from `NULL` to `win`.
OK, well done.
However, you may have noticed that it's painful to type such a long line to compile.
It is a good idea to use shell script to solve this problem.
Make a text file which contains the following text.
Most important is the fourth argument of `g_signal_connect`.
It is described as "data to pass to handler" in the definition of g\_signal\_connect in GObject API reference.
Therefore, `win` which is a pointer to GtkApplicationWindow is passed to the handler as a second parameter user_data.
Then, the handler cast it to a pointer to GtkWindow and call `gtk_window_destroy` and destroy the top window.
Then, the application quits.
gcc `pkg-config --cflags gtk4` $1.c `pkg-config --libs gtk4`
### GtkBox
Then, save it in $HOME/bin, which is usually /home/(username)/bin.
(If your user name is James, then the directory is /home/james/bin).
And turn on the execute bit of the file.
Suppose the filename is comp, then the procedure is as follows.
GtkWindow and GtkApplicationWindow can have only one child.
If you want to add two or more widgets inside a window, you need a container widget.
GtkBox is one of the containers.
It arranges two or more child widgets into a single row or column.
The following procedure shows the way to add two buttons in a window.
$ chmod 755 $HOME/bin/comp
$ ls -log $HOME/bin
... ... ...
-rwxr-xr-x 1 62 May 23 08:21 comp
... ... ...
- Generate GtkApplicationWindow.
- Generate GtkBox and set it a child of GtkApplicationWindow.
- Generate GtkButton and append it to GtkBox.
- Generate another GtkButton and append it to GtkBox.
If this is the first time that you make a $HOME/bin directory and save a file in it, then you need to logout and login again.
After this, the Widgets are connected as following diagram.
$ comp pr2
$ ./a.out
GtkApplication is activated.
$
![Parent-child relationship](../image/box.png)
## GtkWindow and GtkApplicationWindow
Now, code it.
### GtkWindow
@@@ lb4.c
A message "GtkApplication is activated." was printed out in the previous subsection.
It was good in terms of a test of GtkApplication.
However, it is insufficient because GTK is a framework for graphical user interface (GUI).
Now we go ahead with adding a window into this program.
What we need to do is:
Look at the function `on_activate`.
1. Generate a GtkWindow.
2. Connect it to GtkApplication.
3. Show the window.
After the generation of GtkApplicationWindow, GtkBox is generated.
Now rewrite the function `on_activate`.
box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
gtk_box_set_homogeneous (GTK_BOX (box), TRUE);
#### Generate a GtkWindow
The first argument arranges children vertically.
The second argument is sizes between children.
The next function fills a box with children, giving them equal space.
@@@ misc/pr3.c on_activate
After that, two buttons `btn1` and `btn2` are generated and the signal handlers are set.
Then, these two buttons are appended to the box.
Widget is an abstract concept that includes all the GUI interfaces such as windows, dialogs, buttons, multiline text, containers and so on.
And GtkWidget is a base object from which all the GUI objects derive.
![Screenshot of the box](../image/screenshot_lb4.png)
parent <-----> child
GtkWidget -- GtkWindow
The handler corresponds to `btn1` changes its label.
The handler corresponds to `btn2` destroys the top-level window and the application quits.
GtkWindow includes GtkWidget at the top of its object.
![GtkWindow and GtkWidget](../image/window_widget.png)
The function `gtk_window_new` is defined as follows.
GtkWidget *
gtk_window_new (void);
By this definition, it returns a pointer to GtkWidget, not GtkWindow.
It actually generates a new GtkWindow object (not GtkWidget) but returns a pointer to GtkWidget.
However,the pointer points the GtkWidget and at the same time it also points GtkWindow that contains GtkWidget in it.
If you want to use `win` as a pointer to the GtkWindow, you need to cast it.
(GtkWindow *) win
Or you can use `GTK_WINDOW` macro that performs a similar function.
GTK_WINDOW (win)
This is a recommended way.
#### Connect it to GtkApplication.
The function `gtk_window_set_application` is used to connect GtkWidow to GtkApplication.
gtk_window_set_application (GTK_WINDOW (win), GTK_APPLICATION (app));
You need to cast `win` to GtkWindow and `app` to GtkApplication.
`GTK_WINDOW` and `GTK_APPLICATION` macro is appropriate for that.
GtkApplication continues to run until the related window is destroyed.
If you didn't connect GtkWindow and GtkApplication, GtkApplication shutdowns soon.
Because no window is connected to GtkApplication, it doesn't need to wait anything.
As it shutdowns the generated window is also destroyed.
#### Show the window.
The function `gtk_widget_show` is used to show the window.
Gtk4 changed the default widget visibility to on, so every widget doesn't need this function to show itself.
But, there's an exception.
Top window (this term will be explained later) isn't visible when it is generated.
So you need to use the function above and show the window.
Save the program as `pr3.c` and compile and run it.
$ comp pr3
$ ./a.out
A small window appears.
![Screenshot of the window](../image/screenshot_pr3.png)
Click on the close button then the window disappears and the program finishes.
### GtkApplicationWindow
GtkApplicationWindow is a child object of GtkWindow.
It has some extra functionality for better integration with GtkApplication.
It is recommended to use it instead of GtkWindow when you use GtkApplication.
Now rewrite the program and use GtkAppliction Window.
@@@ misc/pr4.c on_activate
When you generate GtkApplicationWindow, you need to give GtkApplication object as an argument.
Then it automatically connect these two objects.
So you don't need to call `gtk_window_set_application` any more.
The program sets the title and the default size of the window.
Compile it and run `a.out`, then you will see a bigger window with its title "pr4".
![Screenshot of the window](../image/screenshot_pr4.png)

View file

@ -1,57 +1,141 @@
# Widgets (2)
# Widgets (1)
## GtkTextView, GtkTextbuffer and GtkScrolledWindow
## GtkLabel, GtkButton and Gtkbox
### GtkTextView and GtkTextBuffer
### GtkLabel
GtkTextview is a widget for multiline text editing.
GtkTextBuffer is a text buffer which is connected to GtkTextView.
See a sample program `tfv1.c` below.
We made an window and show it on the screen in the previous chapter.
Now we go on to the next topic, widgets in the window.
The simplest widget is GtkLabel.
It is a widget with a string in it.
@@@ tfv1.c
@@@ lb1.c
Look at line 25.
GtkTextView is generated and its pointer is assigned to `tv`.
When GtkTextView is generated, the connected GtkTextBuffer is also generated automatically.
In the next line, the pointer to the buffer is got and assigned to `tb`.
Then, the text from line 10 to 20 is assigned to the buffer.
Save this program to a file `lb1.c`.
Then compile and run it.
GtkTextView has a wrap mode.
When `GTK_WRAP_WORD_CHAR` is set, text wraps in between words, or if that is not enough, also between graphemes.
$ comp lb1
$ ./a.out
In line 30, `tv` is set to `win` as a child.
A window with a message "Hello." appears.
Now compile and run it.
![Screenshot of the label](../image/screenshot_lb1.png)
![GtkTextView](../image/screenshot_tfv1.png)
There's an I-beam pointer in the window.
You can add or delete any character on GtkTextview.
And your change is kept in GtkTextBuffer.
If you add more characters than the limit of the window, the height of the window extends.
If the height gets bigger than the height of the display screen, you won't be able to control the size of the window back to the original size.
It's a problem.
You can solve it by putting GtkScrolledWindow between GtkApplicationWindow and GtkTextView.
### GtkScrolledWindow
What we need to do is:
- Generate GtkScrolledWindow and set it as a child of GtkApplicationWindow.
- Set GtkTextVies as a child of GtkScrolledWindow.
Modify `tfv1.c` and save it as `tfv2.c`.
The difference between these two files is very little.
There's only a little change between `pr4.c` and `lb1.c`.
Diff is a good program to know the difference between two files.
$$$
diff tfv1.c tfv2.c
diff misc/pr4.c lb1.c
$$$
Though you can modify the source file by this diff output, It's good for you to show `tfv2.c`.
This tells us:
@@@ tfv2.c
- The definition of a variable lab is added.
- The title of the window is changed.
- A label is generated and connected to the window.
The function `gtk_window_set_child (GTK_WINDOW (win), lab)` makes the label `lab` a child widget of the window `win`.
Be careful.
A child widget is different from a child object.
Objects have parent-child relationship and Widgets also have parent-child relationship.
But these two relationships are totally different.
Don't be confused.
In the program `lb1.c`, `lab` is a child widget of `win`.
Child widgets are always located inside its parent widget in the screen.
See the window appeared on the screen.
The window includes the label.
The window `win` dosen't have any parents.
We call such a window top-level window.
One application can have two or more top-level windows.
### GtkButton
Next widget is GtkButton.
It has a label or icon on it.
In this subsection, we will make a button with a label.
When a button is clicked on, it emits a "clicked" signal.
The following program shows how to catch the signal and do something.
@@@ lb2.c
Look at the line 17 to 19.
First, generate a GtkButton widget `btn` with a label "Click me".
Then, set it to the window `win` as a child.
Finally, connect a "clicked" signal of the button to a handler (function) `on_click`.
So, if `btn` is clicked, the function `on_click` is invoked.
Name the program `lb2.c` and save it.
Now compile and run it.
This time the window doesn't extend even if you type a lot of characters.
It just scrolls.
![Screenshot of the label](../image/screenshot_lb2.png)
A window with the button appears.
Click the button (it is a large button, you can click everywhere inside the window), then a string "Clicked." appears on the shell terminal.
It shows the handler was invoked by clicking the button.
It's fairly good for us to make sure that the clicked signal was caught and the handler was invoked.
However, using g_print is out of harmony with GTK which is a GUI library.
So, we will change the handler.
The following code is `lb3.c`.
@@@ lb3.c on_clicked on_activate
And the difference between `lb2.c` and `lb3.c` is as follows.
$$$
diff lb2.c lb3.c
$$$
The change is:
- The function `g_print` in `lb2.c` was deleted and two lines above are inserted instead.
- The label of `btn` is changed from "Click me" to "Quit".
- The fourth argument of `g_signal_connect` is changed from `NULL` to `win`.
Most important is the fourth argument of `g_signal_connect`.
It is described as "data to pass to handler" in the definition of g\_signal\_connect in GObject API reference.
Therefore, `win` which is a pointer to GtkApplicationWindow is passed to the handler as a second parameter user_data.
Then, the handler cast it to a pointer to GtkWindow and call `gtk_window_destroy` and destroy the top window.
Then, the application quits.
### GtkBox
GtkWindow and GtkApplicationWindow can have only one child.
If you want to add two or more widgets inside a window, you need a container widget.
GtkBox is one of the containers.
It arranges two or more child widgets into a single row or column.
The following procedure shows the way to add two buttons in a window.
- Generate GtkApplicationWindow.
- Generate GtkBox and set it a child of GtkApplicationWindow.
- Generate GtkButton and append it to GtkBox.
- Generate another GtkButton and append it to GtkBox.
After this, the Widgets are connected as following diagram.
![Parent-child relationship](../image/box.png)
Now, code it.
@@@ lb4.c
Look at the function `on_activate`.
After the generation of GtkApplicationWindow, GtkBox is generated.
box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
gtk_box_set_homogeneous (GTK_BOX (box), TRUE);
The first argument arranges children vertically.
The second argument is sizes between children.
The next function fills a box with children, giving them equal space.
After that, two buttons `btn1` and `btn2` are generated and the signal handlers are set.
Then, these two buttons are appended to the box.
![Screenshot of the box](../image/screenshot_lb4.png)
The handler corresponds to `btn1` changes its label.
The handler corresponds to `btn2` destroys the top-level window and the application quits.

View file

@ -1,178 +1,57 @@
# Widgets (3)
# Widgets (2)
## Open signal
## GtkTextView, GtkTextbuffer and GtkScrolledWindow
### G\_APPLICATION\_HANDLES\_OPEN flag
### GtkTextView and GtkTextBuffer
GtkTextView, GtkTextBuffer and GtkScrolledWindow have given us a minimum editor in the previous section.
Next, we will add a read function to this program and remake it into a file viewer.
There are many way to implement the function.
However, because this is a tutorial for beginners, we take the simplest way.
GtkTextview is a widget for multiline text editing.
GtkTextBuffer is a text buffer which is connected to GtkTextView.
See a sample program `tfv1.c` below.
When the program starts, we give a filename as an argument.
@@@ tfv1.c
$ ./a.out filename
Look at line 25.
GtkTextView is generated and its pointer is assigned to `tv`.
When GtkTextView is generated, the connected GtkTextBuffer is also generated automatically.
In the next line, the pointer to the buffer is got and assigned to `tb`.
Then, the text from line 10 to 20 is assigned to the buffer.
Then it opens the file and set it into GtkTextBuffer.
GtkTextView has a wrap mode.
When `GTK_WRAP_WORD_CHAR` is set, text wraps in between words, or if that is not enough, also between graphemes.
At the beginning of the implementation, we need to know how GtkApplication (or GApplication) recognizes arguments.
It is described in the GIO API reference.
In line 30, `tv` is set to `win` as a child.
When GtkApplication is generated, a flag (its type is GApplicationFlags) is given as an argument.
Now compile and run it.
GtkApplication *
gtk_application_new (const gchar *application_id, GApplicationFlags flags);
![GtkTextView](../image/screenshot_tfv1.png)
This flag is described in the GApplication section in GIO API reference.
There's an I-beam pointer in the window.
You can add or delete any character on GtkTextview.
And your change is kept in GtkTextBuffer.
If you add more characters than the limit of the window, the height of the window extends.
If the height gets bigger than the height of the display screen, you won't be able to control the size of the window back to the original size.
It's a problem.
You can solve it by putting GtkScrolledWindow between GtkApplicationWindow and GtkTextView.
GApplicationFlags' Members
### GtkScrolledWindow
G_APPLICATION_FLAGS_NONE Default. (No argument allowed)
... ... ...
G_APPLICATION_HANDLES_OPEN This application handles opening files (in the primary instance).
... ... ...
What we need to do is:
There are ten flags.
But we only need two of them so far.
We've already used `G_APPLICATION_FLAGS_NONE`.
It is the simplest option.
No argument is allowed.
If you give arguments and run the application, then error occurs.
- Generate GtkScrolledWindow and set it as a child of GtkApplicationWindow.
- Set GtkTextVies as a child of GtkScrolledWindow.
`G_APPLICATION_HANDLES_OPEN` is the second simplest option.
It allows arguments but only files.
The application assumes all the arguments are filenames.
Modify `tfv1.c` and save it as `tfv2.c`.
The difference between these two files is very little.
Now we use this flag when generating GtkApplication.
$$$
diff tfv1.c tfv2.c
$$$
app = gtk_application_new ("com.github.ToshioCP.tfv3", G_APPLICATION_HANDLES_OPEN);
Though you can modify the source file by this diff output, It's good for you to show `tfv2.c`.
### open signal
@@@ tfv2.c
When the application starts, two signals are possible.
- activate signal --- This signal is emitted when there's no argument.
- open signal --- This signal is emitted when there is at least one argument.
The handler of open signal is called as follows.
void user_function (GApplication *application,
gpointer files,
gint n_files,
gchar *hint,
gpointer user_data)
The parameters are as follows:
- application --- the application (usually GtkApplication)
- files --- an array of GFiles. [array length=n_files] [element-type GFile]
- n_files --- the length of files
- hint --- a hint provided by the calling instance (usually it can be ignored)
- user_data --- user data set when the signal handler was connected.
The way how to read a file using GFiles will be described in the next section.
## Coding a file viewer
### What is a file viewer?
A file viewer is a program that shows a text file given as an argument.
It works as follows.
- If it is given arguments, it recognizes the first argument as a filename and open it.
- If opening the file succeeds, read and set it to GtkTextBuffer and show the window.
- If it fails to open the file, show an error message and quit.
- If there's no argument, show an error message and quit.
- If there are two or more arguments, the second one and after are ignored.
The program is as follows.
@@@ tfv3.c
Save it as `tfv3.c`.
Then compile and run it.
$ comp tfv3
$ ./a.out tfv3.c
![File viewer](../image/screenshot_tfv3.png)
Now I want to explain the program `tfv3.c`.
First, the function `main` changes in only two lines.
- `G_APPLICATION_FLAGS_NONE` is replaced with `G_APPLICATION_HANDLES_OPEN`.
- `g_signal_connect (app, "open", G_CALLBACK (on_open), NULL)` is added.
Next, the handler `on_activate` is now very simple.
Just output the error message.
The application quits immediately because no window is generated.
The point is the handler `on_open`.
- It generates GtkApplicationWindow, GtkScrolledWindow, GtkTextView and GtkTextBuffer and connect them.
- Set wrap mode to `GTK_WRAP_WORD_CHAR` in GtktextView.
- Set non-editable to GtkTextView because the program isn't an editor but only a viewer.
- Read the file and set it to GtkTextBuffer (this will be explained in detail later).
- If the file is not opened then output an error message and destroy the window. It makes the application quit.
The file reading part of the program is shown again below.
if (g_file_load_contents(files[0], NULL, &contents, &length, NULL, NULL)) {
gtk_text_buffer_set_text(tb, contents, length);
g_free(contents);
filename = g_file_get_basename(files[0]);
gtk_window_set_title (GTK_WINDOW (win), filename);
g_free(filename);
gtk_widget_show (win);
} else {
filename = g_file_get_path(files[0]);
g_print ("No such file: %s.\n", filename);
gtk_window_destroy (GTK_WINDOW (win));
}
The function `g_file_load_contents` loads the file contents into a buffer, which is automatically allocated, and set the pointer to the buffer into `contents`.
And the length of the buffer is set to `length`.
It returns `TRUE` if the file's contents were successfully loaded. `FALSE` if there were errors.
If the function succeeds, set the contents into GtkTextBuffer, free the buffer memories pointed by `contents`, set the filename to the title of the window,
free the memories pointed by `filename` and show the window.
If it fails, it outputs an error message and destroy the window.
## GtkNotebook
GtkNotebook is a container widget that contains multiple children with tabs in it.
![GtkNotebook](../image/screenshot_gtk_notebook.png)
Look at the screenshots above.
The left one is a window at the startup.
It shows the file `pr1.c`.
The filename is in the left tab.
After clicking on the right tab, then the contents of `tfv1.c` appears.
It is shown in the right screenshot.
GtkNotebook widget is between GtkApplicationWindow and GtkScrolledWindow.
Now I want to show you the program `tfv4.c`.
@@@ tfv4.c
Most of the change is in the function `on_open`.
The numbers at the left of the following items are line numbers in the source code.
- 11-13: Variables `nb`, `lab` and `nbp` are defined and point GtkNotebook, GtkLabel and GtkNotebookPage respectively.
- 23: The window's title is set to "file viewer".
- 25: The size of the window is set to maximum because a big window is appropriate for file viewers.
- 27-28 GtkNotebook is generated and set it as a child of GtkApplicationWindow.
- 30-52 For-loop. Each loop corresponds to an argument. And files[i] is GFile object with respect to the i-th argument.
- 32-37 GtkScrollledWindow, GtkTextView and GtkTextBuffer are generated and GtkTextView is connected to GtkScrolledWindow as a child.
They corresponds to each file, so they are generated inside the for-loop.
- 39-42 Set the contents of the file into GtkTextBuffer and free the memory pointed by `contents`. Get the filename and generate GtkLabel with the filename.
- 43: Append GtkScrolledWindow and GtkLabel to GtkNotebook. The appended objects are children of automatically generated GtkNotebookPage object. Therefore, the structure is like this:
GtkNotebook -- GtkNotebookPage -- (GtkScrolledWindow and GtkLabel)
- 44: Get GtkNotebookPage object and set its pointer to `nbp`.
- 45: GtkNotebookPage has a property "tab-expand". If it is set to TRUE then the tab expand horizontally as long as possible. If FALSE, then the width of the tab is determined by the size of the label. `g_object_set` is a general function to set properties in any objects.
- 46: free the memory pointed by `filename`
- 53-56: If at least one file was read, then the number of GtkNotebookPage is greater than zero. If it's true, then show the window. If it's false, then destroy the window.
Now compile and run it.
This time the window doesn't extend even if you type a lot of characters.
It just scrolls.

View file

@ -1,187 +1,178 @@
# Define Child object
# Widgets (3)
## Very simple editor
## Open signal
We made a very simple file viewer in the previous section.
Now we go on to rewrite it and make a very simple editor.
Its source file name is tfe1.c (text file editor 1).
### G\_APPLICATION\_HANDLES\_OPEN flag
GtkTextView originally has a feature of multi line editing.
Therefore, we don't need to rewrite the program from scratch.
We just add two things to the file viewer.
GtkTextView, GtkTextBuffer and GtkScrolledWindow have given us a minimum editor in the previous section.
Next, we will add a read function to this program and remake it into a file viewer.
There are many way to implement the function.
However, because this is a tutorial for beginners, we take the simplest way.
- Static memory is needed to store a pointer to GFile.
- We need to implement file write function.
When the program starts, we give a filename as an argument.
A couple of ways are possible to get memories to keep GFile.
$ ./a.out filename
- Use global variables.
- make a child widget object and extend the memories allocated to the widget.
Then it opens the file and set it into GtkTextBuffer.
Using global variables is easy to implement.
Define a sufficient size array of pointers to GFile.
For example,
At the beginning of the implementation, we need to know how GtkApplication (or GApplication) recognizes arguments.
It is described in the GIO API reference.
GFile *f[20];
When GtkApplication is generated, a flag (its type is GApplicationFlags) is given as an argument.
And `f[i]` corresponds to 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.
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.
Generally speaking, the bigger the program size, the more difficult to maintain global variables.
GtkApplication *
gtk_application_new (const gchar *application_id, GApplicationFlags flags);
Making child widget object is a good idea in terms of maintenance.
However, one thing you need to be careful is the difference between "child object" and "child widget".
What we are thinking about now is "child object".
A child object includes its parent object.
And a child widget object derives everything from the parent widget object.
![Child widget of GtkTwxtView](../image/child.png)
This flag is described in the GApplication section in GIO API reference.
We will define TfeTextView as a child widget object of GtkTextView.
It has everything that GtkTextView has.
For example, TfeTextView has GtkTextbuffer correspods to GtkTextView inside TfeTextView.
And important thing is that TfeTextView can have a memory to keep a pointer to GFile.
GApplicationFlags' Members
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.
G_APPLICATION_FLAGS_NONE Default. (No argument allowed)
... ... ...
G_APPLICATION_HANDLES_OPEN This application handles opening files (in the primary instance).
... ... ...
## How to define a child widget of GtkTextView
There are ten flags.
But we only need two of them so far.
We've already used `G_APPLICATION_FLAGS_NONE`.
It is the simplest option.
No argument is allowed.
If you give arguments and run the application, then error occurs.
`G_APPLICATION_HANDLES_OPEN` is the second simplest option.
It allows arguments but only files.
The application assumes all the arguments are filenames.
Let's define TfeTextView widget object which is a child object of GtkTextView.
First, look at the program below.
Now we use this flag when generating GtkApplication.
#define TFE_TYPE_TEXT_VIEW tfe_text_view_get_type ()
G_DECLARE_FINAL_TYPE (TfeTextView, tfe_text_view, TFE, TEXT_VIEW, GtkTextView)
app = gtk_application_new ("com.github.ToshioCP.tfv3", G_APPLICATION_HANDLES_OPEN);
struct _TfeTextView
{
GtkTextView parent;
GFile *file;
};
### open signal
G_DEFINE_TYPE (TfeTextView, tfe_text_view, GTK_TYPE_TEXT_VIEW);
When the application starts, two signals are possible.
static void
tfe_text_view_init (TfeTextView *tv) {
- activate signal --- This signal is emitted when there's no argument.
- open signal --- This signal is emitted when there is at least one argument.
The handler of open signal is called as follows.
void user_function (GApplication *application,
gpointer files,
gint n_files,
gchar *hint,
gpointer user_data)
The parameters are as follows:
- application --- the application (usually GtkApplication)
- files --- an array of GFiles. [array length=n_files] [element-type GFile]
- n_files --- the length of files
- hint --- a hint provided by the calling instance (usually it can be ignored)
- user_data --- user data set when the signal handler was connected.
The way how to read a file using GFiles will be described in the next section.
## Coding a file viewer
### What is a file viewer?
A file viewer is a program that shows a text file given as an argument.
It works as follows.
- If it is given arguments, it recognizes the first argument as a filename and open it.
- If opening the file succeeds, read and set it to GtkTextBuffer and show the window.
- If it fails to open the file, show an error message and quit.
- If there's no argument, show an error message and quit.
- If there are two or more arguments, the second one and after are ignored.
The program is as follows.
@@@ tfv3.c
Save it as `tfv3.c`.
Then compile and run it.
$ comp tfv3
$ ./a.out tfv3.c
![File viewer](../image/screenshot_tfv3.png)
Now I want to explain the program `tfv3.c`.
First, the function `main` changes in only two lines.
- `G_APPLICATION_FLAGS_NONE` is replaced with `G_APPLICATION_HANDLES_OPEN`.
- `g_signal_connect (app, "open", G_CALLBACK (on_open), NULL)` is added.
Next, the handler `on_activate` is now very simple.
Just output the error message.
The application quits immediately because no window is generated.
The point is the handler `on_open`.
- It generates GtkApplicationWindow, GtkScrolledWindow, GtkTextView and GtkTextBuffer and connect them.
- Set wrap mode to `GTK_WRAP_WORD_CHAR` in GtktextView.
- Set non-editable to GtkTextView because the program isn't an editor but only a viewer.
- Read the file and set it to GtkTextBuffer (this will be explained in detail later).
- If the file is not opened then output an error message and destroy the window. It makes the application quit.
The file reading part of the program is shown again below.
if (g_file_load_contents(files[0], NULL, &contents, &length, NULL, NULL)) {
gtk_text_buffer_set_text(tb, contents, length);
g_free(contents);
filename = g_file_get_basename(files[0]);
gtk_window_set_title (GTK_WINDOW (win), filename);
g_free(filename);
gtk_widget_show (win);
} else {
filename = g_file_get_path(files[0]);
g_print ("No such file: %s.\n", filename);
gtk_window_destroy (GTK_WINDOW (win));
}
static void
tfe_text_view_class_init (TfeTextViewClass *class) {
}
The function `g_file_load_contents` loads the file contents into a buffer, which is automatically allocated, and set the pointer to the buffer into `contents`.
And the length of the buffer is set to `length`.
It returns `TRUE` if the file's contents were successfully loaded. `FALSE` if there were errors.
void
tfe_text_view_set_file (TfeTextView *tv, GFile *f) {
tv -> file = f;
}
If the function succeeds, set the contents into GtkTextBuffer, free the buffer memories pointed by `contents`, set the filename to the title of the window,
free the memories pointed by `filename` and show the window.
If it fails, it outputs an error message and destroy the window.
GFile *
tfe_text_view_get_file (TfeTextView *tv) {
return tv -> file;
}
## GtkNotebook
GtkWidget *
tfe_text_view_new (void) {
return gtk_widget_new (TFE_TYPE_TEXT_VIEW, NULL);
}
GtkNotebook is a container widget that contains multiple children with tabs in it.
If you are curious about the background theory of this program, It's very good for you.
Because to know the theory is very important for you to program GTK applications.
Look at GObject API reference.
All you need is described in it.
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.
![GtkNotebook](../image/screenshot_gtk_notebook.png)
- TfeTextView is divided into two parts.
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 ().
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.
The arguments are the child object name in camel case, lower case with underscore, prefix, object and parent object name.
- Declare the structure \_TfeTextView.
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.
- 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.
- 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-struture declared with the tag \_TfeTextView.
So, the structure has a member `file` as a pointer to GFile.
`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.
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 gtk\_widget\_new function to generate the child widget.
The arguments are (prefix)\_TYPE\_(object), a list to initialize properties and NULL.
In this code no property needs to be initialized.
Look at the screenshots above.
The left one is a window at the startup.
It shows the file `pr1.c`.
The filename is in the left tab.
After clicking on the right tab, then the contents of `tfv1.c` appears.
It is shown in the right screenshot.
This program is not perfect.
It has some problem.
But I don't discuss it now.
It will be modified later.
GtkNotebook widget is between GtkApplicationWindow and GtkScrolledWindow.
Now I want to show you the program `tfv4.c`.
## Close-request signal
@@@ tfv4.c
As a first step, `tfe1.c` writes 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 the function handler.
Then, the function `before_close` is invoked when the signal "close-request" is emittd.
Most of the change is in the function `on_open`.
The numbers at the left of the following items are line numbers in the source code.
g_signal_connect (win, "close-request", G_CALLBACK (before_close), NULL);
- 11-13: Variables `nb`, `lab` and `nbp` are defined and point GtkNotebook, GtkLabel and GtkNotebookPage respectively.
- 23: The window's title is set to "file viewer".
- 25: The size of the window is set to maximum because a big window is appropriate for file viewers.
- 27-28 GtkNotebook is generated and set it as a child of GtkApplicationWindow.
- 30-52 For-loop. Each loop corresponds to an argument. And files[i] is GFile object with respect to the i-th argument.
- 32-37 GtkScrollledWindow, GtkTextView and GtkTextBuffer are generated and GtkTextView is connected to GtkScrolledWindow as a child.
They corresponds to each file, so they are generated inside the for-loop.
- 39-42 Set the contents of the file into GtkTextBuffer and free the memory pointed by `contents`. Get the filename and generate GtkLabel with the filename.
- 43: Append GtkScrolledWindow and GtkLabel to GtkNotebook. The appended objects are children of automatically generated GtkNotebookPage object. Therefore, the structure is like this:
The argument win is GtkApplicationWindow, in which the signal "close-request" is defined, and before\_close is the handler.
`G_CALLBACK` cast is necessary before the handler.
The program of before\_close is as follows.
GtkNotebook -- GtkNotebookPage -- (GtkScrolledWindow and GtkLabel)
@@@ tfe1.c before_close
- 44: Get GtkNotebookPage object and set its pointer to `nbp`.
- 45: GtkNotebookPage has a property "tab-expand". If it is set to TRUE then the tab expand horizontally as long as possible. If FALSE, then the width of the tab is determined by the size of the label. `g_object_set` is a general function to set properties in any objects.
- 46: free the memory pointed by `filename`
- 53-56: If at least one file was read, then the number of GtkNotebookPage is greater than zero. If it's true, then show the window. If it's false, then destroy the window.
The numbers on the left of items are line numbers in the source code.
- 13: Get the number of pages `nb` has.
- 14-23: For loop with regard to the index to each pages.
- 15-17: Get GtkScrolledWindow, TfeTextView and a pointer to GFile. The pointer was stored when `on_open` handler ran. It will be shown later.
- 18-20: Get GtkTextBuffer and contents. start\_iter and end\_iter is 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: Write the file.
## Source code of tfe1.c
Now I will show you all the source code of `tfe1`.c.
@@@ tfe1.c
- 102: set 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` duplicate the given GFile structure.
- 118: connect "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.
Now we got a very simple editor.
It's not smart.
We need more features like open, save, saveas, change font and so on.
We will add them in the next section and after.

View file

@ -1,174 +1,187 @@
# Ui file and GtkBuiler
# Define Child object
## New, open and save button
## Very simple editor
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.
We made a very simple file viewer in the previous section.
Now we go on to rewrite it and make a very simple editor.
Its source file name is tfe1.c (text file editor 1).
![Screenshot of the file editor](../image/screenshot_tfe2.png)
GtkTextView originally has a feature of multi line editing.
Therefore, we don't need to rewrite the program from scratch.
We just add two things to the file viewer.
The screenshot above shows the layout.
The function `on_open` in the source code `tfe2.c` is as follows.
- Static memory is needed to store a pointer to GFile.
- We need to implement file write function.
@@@ tfe2.c on_open
A couple of ways are possible to get memories to keep GFile.
The point is how to build the window.
- Use global variables.
- make a child widget object and extend the memories allocated to the widget.
- 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` and `dmy3` has a character width of ten.
The other label `dmy2` 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.
Using global variables is easy to implement.
Define a sufficient size array of pointers to GFile.
For example,
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?
GFile *f[20];
Gtk provides GtkBuilder.
It reads ui data and builds a window.
It reduces the cumbersom work.
And `f[i]` corresponds to 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.
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.
Generally speaking, the bigger the program size, the more difficult to maintain global variables.
## Ui file
Making child widget object is a good idea in terms of maintenance.
However, one thing you need to be careful is the difference between "child object" and "child widget".
What we are thinking about now is "child object".
A child object includes its parent object.
And a child widget object derives everything from the parent widget object.
![Child widget of GtkTwxtView](../image/child.png)
First, let's look at the ui file `tfe3.ui` that defines a structure of the widgets.
We will define TfeTextView as a child widget object of GtkTextView.
It has everything that GtkTextView has.
For example, TfeTextView has GtkTextbuffer correspods to GtkTextView inside TfeTextView.
And important thing is that TfeTextView can have a memory to keep a pointer to GFile.
@@@ tfe3.ui
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.
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.
## How to define a child widget of GtkTextView
- 2-5: 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.
- 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.
Let's define TfeTextView widget object which is a child object of GtkTextView.
First, look at the program below.
## GtkBuilder
#define TFE_TYPE_TEXT_VIEW tfe_text_view_get_type ()
G_DECLARE_FINAL_TYPE (TfeTextView, tfe_text_view, TFE, TEXT_VIEW, GtkTextView)
GtkBuilder builds widgets based on the ui file.
struct _TfeTextView
{
GtkTextView parent;
GFile *file;
};
GtkBuilder *build;
G_DEFINE_TYPE (TfeTextView, tfe_text_view, GTK_TYPE_TEXT_VIEW);
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"));
static void
tfe_text_view_init (TfeTextView *tv) {
}
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.
static void
tfe_text_view_class_init (TfeTextViewClass *class) {
}
$$$
diff tfe2.c tfe3.c
$$$
void
tfe_text_view_set_file (TfeTextView *tv, GFile *f) {
tv -> file = f;
}
`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.
GFile *
tfe_text_view_get_file (TfeTextView *tv) {
return tv -> file;
}
Now I'll show you the C source code `tfe3.c`.
Only functions `on_open` are shown as follows.
GtkWidget *
tfe_text_view_new (void) {
return gtk_widget_new (TFE_TYPE_TEXT_VIEW, NULL);
}
@@@ tfe3.c on_open
If you are curious about the background theory of this program, It's very good for you.
Because to know the theory is very important for you to program GTK applications.
Look at GObject API reference.
All you need is described in it.
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.
### Using ui string
- TfeTextView is divided into two parts.
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 ().
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.
The arguments are the child object name in camel case, lower case with underscore, prefix, object and parent object name.
- Declare the structure \_TfeTextView.
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.
- 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.
- 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-struture declared with the tag \_TfeTextView.
So, the structure has a member `file` as a pointer to GFile.
`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.
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 gtk\_widget\_new function to generate the child widget.
The arguments are (prefix)\_TYPE\_(object), a list to initialize properties and NULL.
In this code no property needs to be initialized.
GtkBuilder can build widgets using string.
Use the function gtk\_builder\_new\_from\_string instead of gtk\_builder\_new\_from\_file.
This program is not perfect.
It has some problem.
But I don't discuss it now.
It will be modified later.
char *uistring;
## Close-request signal
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>";
As a first step, `tfe1.c` writes 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 the function handler.
Then, the function `before_close` is invoked when the signal "close-request" is emittd.
build = gtk_builder_new_from_stringfile (uistring);
g_signal_connect (win, "close-request", G_CALLBACK (before_close), NULL);
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.
The argument win is GtkApplicationWindow, in which the signal "close-request" is defined, and before\_close is the handler.
`G_CALLBACK` cast is necessary before the handler.
The program of before\_close is as follows.
- add backslash before each double quote.
- add double quote at the left and right.
@@@ tfe1.c before_close
### Using Gresource
The numbers on the left of items are line numbers in the source code.
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.
- 13: Get the number of pages `nb` has.
- 14-23: For loop with regard to the index to each pages.
- 15-17: Get GtkScrolledWindow, TfeTextView and a pointer to GFile. The pointer was stored when `on_open` handler ran. It will be shown later.
- 18-20: Get GtkTextBuffer and contents. start\_iter and end\_iter is 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: Write the file.
An xml file is necessary for the resource compiler `glib-compile-resources`.
It describes resource files.
## Source code of tfe1.c
@@@ tfe3.gresource.xml
Now I will show you all the source code of `tfe1`.c.
- 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.
@@@ tfe1.c
Save this xml text to `tfe3.gresource.xml`.
The gresource compiler `glib-compile-resources` shows its ussage with the argument `--help`.
- 102: set 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` duplicate the given GFile structure.
- 118: connect "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.
$$$
LANG=C glib-compile-resources --help
$$$
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.
Now compile and run it.
Type `./a.out somefile` and make sure that the file is modified.
Now we got a very simple editor.
It's not smart.
We need more features like open, save, saveas, change font and so on.
We will add them in the next section and after.

View file

@ -1,191 +1,174 @@
# Build system
# Ui file and GtkBuiler
## What do we need to think about building?
## New, open and save button
We've managed to compile a small editor so far.
But Some bad signs are beginning to appear.
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.
- We have only one C source file and put everything into it.
We need to sort it out.
- There are two compilers, `gcc` and `glib-compile-resources`.
We want to control them by one building tool.
![Screenshot of the file editor](../image/screenshot_tfe2.png)
## Divide a C source file into two parts.
The screenshot above shows the layout.
The function `on_open` in the source code `tfe2.c` is as follows.
When you divide C source file into several parts, each file should contain only one thing.
For example, our source has two things, the definition of TfeTextView and functions related to GtkApplication and GtkApplicationWindow.
It is a good idea to separate them into two files, `tfetextview.c` and `tfe.c`.
@@@ tfe2.c on_open
- `tfetextview.c` includes the definition and functions of TfeTextView.
- `tfe.c` includes functions like `main`, `on_activate`, `on_open` and so on, which relate to GtkApplication and GtkApplicationWindow
The point is how to build the window.
Now we have three source files, `tfetextview.c`, `tfe.c` and `tfe3.ui`.
The `3` of `tfe3.ui` is like a version number.
Managing version with filenames is one possible idea but it may make bothersome complicated problem.
You need to rewrite filename in each version and it affects to contents of sourcefiles that refer to filenames.
So, we should take `3` away from the filename.
- 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` and `dmy3` has a character width of ten.
The other label `dmy2` 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.
In `tfe.c` the function `tfe_text_view_new` is invoked to generate TfeTextView.
But it is defined in `tfetextview.c`, not `tfe.c`.
The lack of the declaration (not definition) of `tfe_text_view_new` makes error when `tfe.c` is compiled.
The declaration is necessary in `tfe.c`.
Those public information is usually written in header files.
It has `.h` suffix like `tfetextview.h`
And header files are included by C source files.
For example, `tfetextview.h` is included by `tfe.c`.
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?
`tfetextview.h`
Gtk provides GtkBuilder.
It reads ui data and builds a window.
It reduces the cumbersom work.
@@@ tfe4/tfetextview.h
## Ui file
`tfetextview.c`
First, let's look at the ui file `tfe3.ui` that defines a structure of the widgets.
@@@ tfe4/tfetextview.c
@@@ tfe3.ui
`tfe.c`
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.
@@@ tfe4/tfe.c
- 2-5: 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.
- 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`.
`tfe.ui`
Compare this ui file and the lines 26-58 in the source code of `on_open`.
Those two decribe the same structure of widgets.
@@@ tfe4/tfe.ui
## GtkBuilder
`tfe.gresource.xml`
GtkBuilder builds widgets based on the ui file.
@@@ tfe4/tfe.gresource.xml
GtkBuilder *build;
## Make
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"));
Dividing a file makes it easy to maintain source files.
But now we are faced with a new problem.
The building step increases.
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.
- Compile the ui file `tfe.ui` into `resources.c`.
- Compile `tfe.c` into `tfe.o` (object file).
- Compile `tfetextview.c` into `tfetextview.o`.
- Compile `resources.c` into `resources.o`.
- Link all the object files into application `tfe`.
$$$
diff tfe2.c tfe3.c
$$$
Now build tool is necessary to manage it.
Make is one of the build tools.
It was originally created in 1976.
So it is an old and widely used program.
`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.
Make analyzes Makefile and executes compilers.
All instructions are written in Makefile.
Now I'll show you the C source code `tfe3.c`.
Only functions `on_open` are shown as follows.
sample.o: sample.c
gcc -o sample.o sample.c
@@@ tfe3.c on_open
The sample of Malefile above consists of three elements, `sample.o`, `sample.c` and `gcc -0 sample.o sample.c`.
### Using ui string
- `sample.o` is called target.
- `sample.c` is prerequisite.
- `gcc -0 sample.o sample.c` is recipe.
Recipes follow tab characters, not spaces.
(It is very important. Use tab not space, or make won't work as you expected).
GtkBuilder can build widgets using string.
Use the function gtk\_builder\_new\_from\_string instead of gtk\_builder\_new\_from\_file.
The rule is:
char *uistring;
If a prerequisite modified later than a target, then make executes the recipe.
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>";
In the example above, if `sample.c` is modified after the generation of `sample.o`, then make executes gcc and compile `sample.c` into `sample.o`.
If the modification time of `sample.c` is older then the generation of `sample.o`, then no compiling is necesarry, so make does nothing.
build = gtk_builder_new_from_stringfile (uistring);
The Makefile for `tfe` is as follows.
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.
@@@ tfe4/Makefile
- add backslash before each double quote.
- add double quote at the left and right.
Only you need is to type `make`.
### Using Gresource
$ make
gcc -c -o tfe.o `pkg-config --cflags gtk4` tfe.c
gcc -c -o tfetextview.o `pkg-config --cflags gtk4` tfetextview.c
glib-compile-resources tfe.gresource.xml --target=resources.c --generate-source
gcc -c -o resources.o `pkg-config --cflags gtk4` resources.c
gcc -o tfe tfe.o tfetextview.o resources.o `pkg-config --libs gtk4`
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.
I used only very basic rules to write this Makefile.
There are many more convenient methods to make it more compact.
But it needs long story to explain.
So I want to finish the explanation about make.
An xml file is necessary for the resource compiler `glib-compile-resources`.
It describes resource files.
## Rake
@@@ tfe3.gresource.xml
Rake is a similar program to make.
It is written in Ruby code.
If you don't use Ruby, you don't need to read this subsection.
However, Ruby is really sophisticated and recommendable script language.
- 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.
- Rakefile controls the behavior of `rake`.
- You can write any ruby code in Rakefile.
Save this xml text to `tfe3.gresource.xml`.
The gresource compiler `glib-compile-resources` shows its ussage with the argument `--help`.
Rake has task and file task, which is similar to target, prerequisite and recipe in make.
$$$
LANG=C glib-compile-resources --help
$$$
@@@ tfe4/Rakefile
Now run the compiler.
What `Rakefile` describes is almost same as `Makefile` in the previous subsection.
$ glib-compile-resources tfe3.gresource.xml --target=resources.c --generate-source
- 3-6: define target file, source file and so on.
- 1, 8: Load clean library. And define CLEAN file list.
The files included by CLEAN will be removed when `rake clean` is typed on the command line.
- 10: default target depends on targetfile.
default is the final goal of tasks.
- 12-14: targetfile depends on objfiles.
The variable `t` is a task object.
- t.name is a target name
- t.prerequisites is an array of prerequisits.
- t.source is the first element of prerequisites.
- sh is a method to give the following string to shell as an argument and execute.
- 16-21: Loop by each element of the array of objfiles. Each object depends on corresponding source file.
- 23-25: resouce file depends on xml file and ui file.
Then a C source file `resources.c` is generated.
Modify tfe3.c and save it as tfe3_r.c
Rakefile might seem to be difficult for beginners.
But, you can use any ruby syntax in Rakefile, so it is really flexible.
If you practice Ruby and Rakefile, it will be highly productive tools.
# include "resources.c"
... ... ...
... ... ...
build = gtk_builder_new_from_resource ("/com/github/ToshioCP/tfe3/tfe3.ui");
... ... ...
... ... ...
## Meson and ninja
Meson is one of the most popular building tool despite the developing version.
And ninja is similar to make but much faster than make.
Several years ago, most of the C developers used autotools and make.
But now the situation has changed.
Many developers are using meson and ninja now.
To use meson, you first need to write `meson.build` file.
@@@ tfe4/meson.build
- 1: The function `project` defines things about the project.
The first parameter is the name of the project and the second is the programing language.
- 2: `dependency` function defines a dependency that is taken by `pkg-config`.
We put `gtk4` as an argument.
- 5: `import` function inports a module.
In line 5, gnome module is imported and assignd to the variable `gnome`.
gnome module provides helper tools to build GTK programs.
- 6: `.compile_resources` is a method of gnome module and compile files to resources under the instruction of xml file.
In line 6, the resource filename is `resources`, which means `resources.c` and `resources.h`, and xml file is `tfe.gresource.xml`.
This method generates C source file by default.
- 8: define source files.
- 10: executable function generates a target file by building source files.
The first parameter is the filename of the target. The following parameters are source files.
The last parameter has a option `dependencies`.
In line 10 it is `gtkdep` which is defined in line 3.
Now run meson and ninja.
$ meson _build
$ ninja -C _build
Then, the executable file `tfe` has been generated under the directory `_build`.
$ _build/tfe tfe.c tfetextview.c
Then the window appears.
I show you three build tools.
I think meson and ninja is the best choice for the present.
We divided a file into some categorized files and used a build tool.
This method is used by many developers.
Then, compile and run it.
The window appears and it is the same as the screenshot at the beginning of this page.

View file

@ -1,258 +1,191 @@
# Instance and class
# Build system
This section and the following four sections are descriptions about 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.
## What do we need to think about building?
## Encapsulation
We've managed to compile a small editor so far.
But Some bad signs are beginning to appear.
We've divided C source file into two parts.
But it is not enough in terms of encapsulation.
- We have only one C source file and put everything into it.
We need to sort it out.
- There are two compilers, `gcc` and `glib-compile-resources`.
We want to control them by one building tool.
- `tfe.c` includes everything other than TfeTextView.
It should be divided at least into two parts, `tfeapplication.c` and `tfenotebook.c`.
- Header files also need to be organized.
## Divide a C source file into two parts.
However, first of all, I'd like to focus on the object TfeTextView.
It is a child object of GtkTextView.
And important thing is it has newly added Gfile in it.
When you divide C source file into several parts, each file should contain only one thing.
For example, our source has two things, the definition of TfeTextView and functions related to GtkApplication and GtkApplicationWindow.
It is a good idea to separate them into two files, `tfetextview.c` and `tfe.c`.
- What is necessary to GFile when generating (or initializing) TfeTextView?
- What is necessary to GFile when destructing TfeTextView?
- TfeTextView should read/write a file by itself or not?
- How it communicate with objects outside?
- `tfetextview.c` includes the definition and functions of TfeTextView.
- `tfe.c` includes functions like `main`, `on_activate`, `on_open` and so on, which relate to GtkApplication and GtkApplicationWindow
You need to know at least class/instance and signals before thinking about them.
I will explain them in this section and the next section.
After that I will explain:
Now we have three source files, `tfetextview.c`, `tfe.c` and `tfe3.ui`.
The `3` of `tfe3.ui` is like a version number.
Managing version with filenames is one possible idea but it may make bothersome complicated problem.
You need to rewrite filename in each version and it affects to contents of sourcefiles that refer to filenames.
So, we should take `3` away from the filename.
- Organizing functions.
- How to use FileChooserDialog
In `tfe.c` the function `tfe_text_view_new` is invoked to generate TfeTextView.
But it is defined in `tfetextview.c`, not `tfe.c`.
The lack of the declaration (not definition) of `tfe_text_view_new` makes error when `tfe.c` is compiled.
The declaration is necessary in `tfe.c`.
Those public information is usually written in header files.
It has `.h` suffix like `tfetextview.h`
And header files are included by C source files.
For example, `tfetextview.h` is included by `tfe.c`.
## GObject and its children
`tfetextview.h`
GObject and its children are objects, which have both class and instance.
First, think about instance of objects.
Instance is structured memories and the structure is described using C language structure.
The following is a structure of TfeTextView.
@@@ tfe4/tfetextview.h
/* This typedef statement is automaticaly generated by the macro G_DECLARE_FINAL_TYPE */
typedef struct _TfeTextView TfeTextView;
`tfetextview.c`
struct _TfeTextView {
GtkTextView parent;
GtkTextBuffer *tb;
GFile *file;
gboolean changed;
};
@@@ tfe4/tfetextview.c
Each instance has similar structure as above.
`tfe.c`
- `parent` is the structure of GtkTextView which is the parent object of TfeTextView.
- `tb` is a pointer to GtkTextBuffer connected to GtkTextView.
- `file` is a pointer to GFile which is a file corresponds to `tb` (or NULL is available).
- `changed` is TRUE if the buffer has been modified, FALSE if not.
@@@ tfe4/tfe.c
Comparing to the source file in the previous section, `tb` and `changed` are added.
Notice the program above is the declaration of the structure, not the definition.
So, no memories are allocated at this moment.
They are to be allocated when `tfe_text_view_new` function is invoked.
`tfe.ui`
You can find the declaration of the ancestors of TfeTextView in the sourcefiles of GTK and GLib.
The following is extracts from the source files (not exactly the same).
@@@ tfe4/tfe.ui
typedef struct _GObject GObject;
typedef struct _GObject GInitiallyUnowned;
struct _GObject
{
GTypeInstance g_type_instance;
volatile guint ref_count;
GData *qdata;
};
`tfe.gresource.xml`
typedef struct _GtkWidget GtkWidget;
struct _GtkWidget
{
GInitiallyUnowned parent_instance;
GtkWidgetPrivate *priv;
};
@@@ tfe4/tfe.gresource.xml
typedef struct _GtkTextView GtkTextView;
struct _GtkTextView
{
GtkWidget parent_instance;
GtkTextViewPrivate *priv;
};
## Make
In each structure, its parent instance is declared at the top of the members.
So, every ancestors is included in the child instance.
This is very important.
It guarantees a child widget to derive all the features from ancestors.
The structure of `TfeTextView` is like the following diagram.
Dividing a file makes it easy to maintain source files.
But now we are faced with a new problem.
The building step increases.
![The structure of the instance TfeTextView](../image/TfeTextView.png)
- Compile the ui file `tfe.ui` into `resources.c`.
- Compile `tfe.c` into `tfe.o` (object file).
- Compile `tfetextview.c` into `tfetextview.o`.
- Compile `resources.c` into `resources.o`.
- Link all the object files into application `tfe`.
Now build tool is necessary to manage it.
Make is one of the build tools.
It was originally created in 1976.
So it is an old and widely used program.
## Generate TfeTextView instance
Make analyzes Makefile and executes compilers.
All instructions are written in Makefile.
The function `tfe_text_view_new` generates a new TfeTextView instance.
sample.o: sample.c
gcc -o sample.o sample.c
@@@ tfe5/tfetextview.c tfe_text_view_new
The sample of Malefile above consists of three elements, `sample.o`, `sample.c` and `gcc -0 sample.o sample.c`.
When this function is run, the following procedure is gone through.
- `sample.o` is called target.
- `sample.c` is prerequisite.
- `gcc -0 sample.o sample.c` is recipe.
Recipes follow tab characters, not spaces.
(It is very important. Use tab not space, or make won't work as you expected).
1. Initialize GObject instance in TfeTextView instance.
2. Initialize GtkWidget instance in TfeTextView instance.
3. Initialize GtkTextView instance in TfeTextView instance.
4. Initialize TfeTextView instance.
The rule is:
Step one through three is done automatically.
Step four is done by the function `tfe_text_view_init`.
If a prerequisite modified later than a target, then make executes the recipe.
> (In the same way, `gtk_text_view_init`, `gtk_widget_init` and `g_object_init` is the initialization functions of GtkTextView, GtkWidget and GObject respectively.
> You can find them in the GTK or GLib source file.)
In the example above, if `sample.c` is modified after the generation of `sample.o`, then make executes gcc and compile `sample.c` into `sample.o`.
If the modification time of `sample.c` is older then the generation of `sample.o`, then no compiling is necesarry, so make does nothing.
@@@ tfe5/tfetextview.c on_changed tfe_text_view_init
The Makefile for `tfe` is as follows.
`tfe_text_view_init` initializes the instance.
@@@ tfe4/Makefile
- 8-10: Initialize `tb`, `file` and `changed`.
- 11: Set the wrap mode of GtkTextView as GTK\_WRAP\_WORD\_CHAR.
- 12: Connect "changed" signal to a handler `on_changed`.
"changed" signal is defined in GtkTextBuffer.
It is emitted when the contents in the buffer is changed.
- 2-4: `on_changed` handler records TRUE to `tv->changed` when "changed" signal is emitted.
Only you need is to type `make`.
## Functions and Classes
$ make
gcc -c -o tfe.o `pkg-config --cflags gtk4` tfe.c
gcc -c -o tfetextview.o `pkg-config --cflags gtk4` tfetextview.c
glib-compile-resources tfe.gresource.xml --target=resources.c --generate-source
gcc -c -o resources.o `pkg-config --cflags gtk4` resources.c
gcc -o tfe tfe.o tfetextview.o resources.o `pkg-config --libs gtk4`
In Gtk, all objects derived from GObject have class and instance.
Instance is memories which has a structure defined by C structure declaration as I mentioned in the previous two subsections.
And instance can be generated two or more.
Those instances have the same structure.
However, structured memories are insufficient to define its behavior.
We need at least two things.
One is functions and the other is class.
I used only very basic rules to write this Makefile.
There are many more convenient methods to make it more compact.
But it needs long story to explain.
So I want to finish the explanation about make.
You've already seen many functions, for example, `tfe_text_view_new` is a function to generate TfeTextView instance.
These functions are similar to object methods in object oriented languages such as Java and Ruby.
Functions are public, which means that they are expected to be used by other objects.
## Rake
Class comprises mainly pointers to functions.
And the functions are used by the object itself or its children objects.
For example, GObject class is declared in `gobject.h` in GLib source files.
Rake is a similar program to make.
It is written in Ruby code.
If you don't use Ruby, you don't need to read this subsection.
However, Ruby is really sophisticated and recommendable script language.
@@@ class_gobject.c
- Rakefile controls the behavior of `rake`.
- You can write any ruby code in Rakefile.
I'd like to explain some of the members.
There's a pointer to the function `dispose` in line 22.
Rake has task and file task, which is similar to target, prerequisite and recipe in make.
void (*dispose) (GObject *object);
@@@ tfe4/Rakefile
The declaration is a bit complicated.
The asterisk before the identifier `dispose` means pointer.
So, the pointer `disopse` points a function which has one parameter , which points a GObject structure, and returns no value because of void type.
In the same way, line 23 says `finalize` is a pointer to the function which has one paremeter, which points a GObject structure, and returns no value.
What `Rakefile` describes is almost same as `Makefile` in the previous subsection.
void (*finalize) (GObject *object);
- 3-6: define target file, source file and so on.
- 1, 8: Load clean library. And define CLEAN file list.
The files included by CLEAN will be removed when `rake clean` is typed on the command line.
- 10: default target depends on targetfile.
default is the final goal of tasks.
- 12-14: targetfile depends on objfiles.
The variable `t` is a task object.
- t.name is a target name
- t.prerequisites is an array of prerequisits.
- t.source is the first element of prerequisites.
- sh is a method to give the following string to shell as an argument and execute.
- 16-21: Loop by each element of the array of objfiles. Each object depends on corresponding source file.
- 23-25: resouce file depends on xml file and ui file.
Look at the declaration of `_GObjectClass` so that you would find that most of the members are pointers to functions.
Rakefile might seem to be difficult for beginners.
But, you can use any ruby syntax in Rakefile, so it is really flexible.
If you practice Ruby and Rakefile, it will be highly productive tools.
- 10: A function pointed by `constructor` is called when the instance is generated. It completes the initialization of the instance.
- 22: A function pointed by `dispose` is called when the instance destructs itself. Destruction process is divided into two phases. First is called disposing and the instance releases all the references to other instances. The second is finalizing.
- 23: A funtion pointed by `finalize` finishes the destruction process.
- The other pointers point functions which are called during the instance lives.
## Meson and ninja
## TfeTextView class
Meson is one of the most popular building tool despite the developing version.
And ninja is similar to make but much faster than make.
Several years ago, most of the C developers used autotools and make.
But now the situation has changed.
Many developers are using meson and ninja now.
TfeTextView class is a structure and it includes all its ancestors' class in it.
Let's look at all the classes from GObject, which is the top level object, to TfeTextView object, which is the lowest.
To use meson, you first need to write `meson.build` file.
GObject -- GInitiallyUnowned -- GtkWidget -- GtkTextView -- TfeTextView
@@@ tfe4/meson.build
The following is extracts from the source files (not exactly the same).
- 1: The function `project` defines things about the project.
The first parameter is the name of the project and the second is the programing language.
- 2: `dependency` function defines a dependency that is taken by `pkg-config`.
We put `gtk4` as an argument.
- 5: `import` function inports a module.
In line 5, gnome module is imported and assignd to the variable `gnome`.
gnome module provides helper tools to build GTK programs.
- 6: `.compile_resources` is a method of gnome module and compile files to resources under the instruction of xml file.
In line 6, the resource filename is `resources`, which means `resources.c` and `resources.h`, and xml file is `tfe.gresource.xml`.
This method generates C source file by default.
- 8: define source files.
- 10: executable function generates a target file by building source files.
The first parameter is the filename of the target. The following parameters are source files.
The last parameter has a option `dependencies`.
In line 10 it is `gtkdep` which is defined in line 3.
@@@ classes.c
Now run meson and ninja.
- 105-107: This three lines are generated by the macro G\_DECLARE\_FINAL\_TYPE.
So, they are not written in either `tfe_text_view.h` or `tfe_text_view.c`.
- 2, 73, 106: Each derived class puts its parent class at the first member of its structure.
It is the same as instance structures.
- Class members in ancesters are open to their child class.
So, they can be changed in `tfe_text_view_class_init` function.
For example, the `dispose` pointer in GObjectClass will be overridden later in `tfe_text_view_class_init`.
(Override is an object oriented programing terminology.
Override is rewriting ancestors' class methods in the child class.)
- Some class methods are often overridden.
`set_property`, `get_property`, `dispose`, `finalize` and `constructed` are such methods.
$ meson _build
$ ninja -C _build
TfeTextViewClass includes its ancsestors' class in it.
It is illustrated in the following diagram.
Then, the executable file `tfe` has been generated under the directory `_build`.
![The structure of TfeTextView Class](../image/TfeTextViewClass.png)
$ _build/tfe tfe.c tfetextview.c
## Destruction of TfeTextView
Then the window appears.
Every Object derived from GObject has a reference count.
If an object A uses an object B, then A keeps a pointr to B in A and at the same time increaces the reference count of B by one with the function `g_object_ref (B)`.
If A doesn't need B any longer, then A discards the pointer to B (usually it is done by assigning NULL to the pointer) and decreaces the reference count of B by one with the function `g_object_unref (B)`.
I show you three build tools.
I think meson and ninja is the best choice for the present.
If two objects A and B refer to C, then the reference count of C is two.
After A used C and if A no longer needs C, A discards the pointer to C and decreases the reference count in C by one.
Now the reference count of C is one.
In the same way, when B no longer needs C, B discards the pointer to C and decreases the reference count in C by one.
At this moment, no object refers C and the reference count of C is zero.
This means C is no longer useful.
Then C destructs itself and finally the memories allocated to C is freed.
![Reference count of B](../image/refcount.png)
The idea above is based on an assumption that an object refered by nothing has reference count of zero.
When the reference count drops to zero, the object starts its destruction process.
The destruction process is split in two phases: disposing and finalizing.
In the disposing process, the object invokes the handler pointed by `dispose` in its class to release all references to other objects.
In the finalizing process, it invokes the handler pointed by `finalize` in its class to complete the destruction process.
In the destruction process of TfeTextView, the reference count of widgets related to TfeTextView is automatically decreased.
But GFile pointed by `tv->file` needs to decrease its reference count by one.
You must write the code in the dispose handler `tfe_text_view_dispose`.
@@@ tfe5/tfetextview.c tfe_text_view_dispose
- 5,6: If `tv->file` points a GFile, decrease its reference count.
`g_clear_object` decreases the reference count and assigns NULL to `tv->file`. In dispose handlers, we usually use `g_clear_object` rather than `g_object_unref`.
- 8: invoke parent's despose handler. (This will be explained later.)
In the desposing process, the object uses the pointer in its class to call the handler.
Therefore, `tfe_text_view_dispose` needs to be registerd in the class when the TfeTextView class is initialized.
The function `tfe_text_view_class_init` is the class initialization function and it is declared in the replacement produced by `G_DEFINE_TYPE` macro.
static void
tfe_text_view_class_init (TfeTextViewClass *class) {
GObjectClass *object_class = G_OBJECT_CLASS (class);
object_class->dispose = tfe_text_view_dispose;
}
Each ancestors' class is generated before TfeTextViewClass.
Therefore, there are four classes and each class has a pointer to each dispose handler.
Look at the following diagram.
There are four classes -- GObjectClass (GInitiallyUnownedClass), GtkWidgetClass, GtkTextViewClass and TfeTextViewClass.
Each class has its own dispose handler -- `dh1`, `dh2`, `dh3` and `tfe_text_view_dispose`.
![dispose handers](../image/dispose_handler.png)
Now, look at the `tfe_text_view_dispose` program above.
It first releases the reference to GFile object pointed by `tv->file`.
Then it invokes its parent's dispose handler in line 8.
G_OBJECT_CLASS (tfe_text_view_parent_class)->dispose (gobject);
`tfe_text_view_parent_class`,which is made by `G_DEFINE_TYPE` macro, is a pointer that points the parent object class.
Therefore, `G_OBJECT_CLASS (tfe_text_view_parent_class)->dispose` points the handler `dh3` in the diagram above.
And `gobject` is a pointer to TfeTextView object which is casted as a GObject instanse.
`dh3` releases all the references to objects in the GtkTextView part (it is actually the private area pointed by `prev`) in TfeTextView instance.
After that, `dh3` calls `dh2`, and `dh2` calls `dh1`.
Finally all the references are released.
We divided a file into some categorized files and used a build tool.
This method is used by many developers.

View file

@ -1,135 +1,258 @@
# Signals
# Instance and class
## Signals
This section and the following four sections are descriptions about 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.
In GTK programming, each object is capsulated.
And it is not recommended to use global variables because they tend to make the program complicated.
So, we need something to communicate between objects.
There are two ways to do so.
## Encapsulation
- Functions.
For example, `tb = gtk_text_view_get_buffer (tv)`.
The function caller requests `tv`, which is a GtkTextView object, to give back `tb`, which is a GtkTextBuffer object connected to `tv`.
- Signals.
For example, `activate` signal on GApplication object.
When the application is activated, the signal is emitted.
Then the handler, which has been connected to the signal, is invoked.
We've divided C source file into two parts.
But it is not enough in terms of encapsulation.
The caller of the function or the handler connected to the signal is usually outside of the object.
One of the difference between these two is that the object is active or passive.
In functions the object responds to the caller.
In signals the object actively sends a signal to the handler.
- `tfe.c` includes everything other than TfeTextView.
It should be divided at least into two parts, `tfeapplication.c` and `tfenotebook.c`.
- Header files also need to be organized.
GObject signal can be registered, connected and emitted.
However, first of all, I'd like to focus on the object TfeTextView.
It is a child object of GtkTextView.
And important thing is it has newly added Gfile in it.
1. A signal is registered with the object type on which it can be emitted.
This is done usually when the class is initialized.
2. It is connected to a handler by `g_connect_signal` or its family functions.
3. When it is emmitted, the connected handler is invoked.
- What is necessary to GFile when generating (or initializing) TfeTextView?
- What is necessary to GFile when destructing TfeTextView?
- TfeTextView should read/write a file by itself or not?
- How it communicate with objects outside?
Step one and three are done in the object on which the signal is emitted.
Step two is done outside the objects.
You need to know at least class/instance and signals before thinking about them.
I will explain them in this section and the next section.
After that I will explain:
## Signal registration
- Organizing functions.
- How to use FileChooserDialog
In TfeTextView, two signals are registered.
## GObject and its children
- "change-file" signal.
This signal is emitted when `tv->file` is changed.
- "open-response" signal.
`tfe_text_view_open` function is not able to return the status because of using GtkFileChooserDialog.
This signal is emitted instead of the return value of the function.
GObject and its children are objects, which have both class and instance.
First, think about instance of objects.
Instance is structured memories and the structure is described using C language structure.
The following is a structure of TfeTextView.
Static variable is used to store the signal ID.
If you need to register two or more signals, static array is usually used.
/* This typedef statement is automaticaly generated by the macro G_DECLARE_FINAL_TYPE */
typedef struct _TfeTextView TfeTextView;
enum {
CHANGE_FILE,
OPEN_RESPONSE,
NUMBER_OF_SIGNALS
struct _TfeTextView {
GtkTextView parent;
GtkTextBuffer *tb;
GFile *file;
gboolean changed;
};
static guint tfe_text_view_signals[NUMBER_OF_SIGNALS];
Each instance has similar structure as above.
Signal registration codes are written in the class initialization function.
- `parent` is the structure of GtkTextView which is the parent object of TfeTextView.
- `tb` is a pointer to GtkTextBuffer connected to GtkTextView.
- `file` is a pointer to GFile which is a file corresponds to `tb` (or NULL is available).
- `changed` is TRUE if the buffer has been modified, FALSE if not.
@@@ tfe5/tfetextview.c tfe_text_view_class_init
Comparing to the source file in the previous section, `tb` and `changed` are added.
Notice the program above is the declaration of the structure, not the definition.
So, no memories are allocated at this moment.
They are to be allocated when `tfe_text_view_new` function is invoked.
- 6-15: Register "change-file"signal.
`g_signal_newv` function is used.
This signal has no default handler (object method handler).
I think you usually don't need to set a default handler in final type object.
If you need it, put the closure of the handler in line 9.
- The return value of `g_signal_newv` is the signal id.
The type of signal id is guint, which is the same as unsigned int.
It is used when the signal is emitted.
- 16-26: Register "open-response" signal.
This signal has a parameter.
- 25: Number of the parameter.
"open-response" signal has one parameter.
- 26: An array of types of parameters.
The array `param_types` is defined in line 16.
It has one element, which is `G_TYPE_INT`.
`G_TYPE_INT` is a type of integer.
Such fundamental types are described in [GObject API reference](https://developer.gnome.org/gobject/stable/gobject-Type-Information.html).
You can find the declaration of the ancestors of TfeTextView in the sourcefiles of GTK and GLib.
The following is extracts from the source files (not exactly the same).
The handlers are as follows.
void change_file_handler (TfeTextView *tv, gpointer user_data);
void open_response_handler (TfeTextView *tv, guint parameter, gpointer user_data);
- Because "change-file" signal doesn't have parameter, the handler's parameter is TfeTextView object and user data.
- Because "open-response" signal has one parameter, the handler's parameter is TfeTextView object, the parameter and user data.
- `tv` is the object instance on which the signal is emitted.
- `user_data` comes from the fourth argument of `g_signal_connect`.
- `parameter` comes from the fourth argument of `g_signal_emit`.
The parameter is defined in `tfetextview.h` because it is public.
/* "open-response" signal response */
enum
typedef struct _GObject GObject;
typedef struct _GObject GInitiallyUnowned;
struct _GObject
{
TFE_OPEN_RESPONSE_SUCCESS,
TFE_OPEN_RESPONSE_CANCEL,
TFE_OPEN_RESPONSE_ERROR
GTypeInstance g_type_instance;
volatile guint ref_count;
GData *qdata;
};
- `TFE_OPEN_RESPONSE_SUCCESS` is set when `tfe_text_view_open` successfully has opend a file and loaded it.
- `TFE_OPEN_RESPONSE_CANCEL` is set when the user canceled to open a file.
- `TFE_OPEN_RESPONSE_ERROR` is set when error occured.
## Signal connection
typedef struct _GtkWidget GtkWidget;
struct _GtkWidget
{
GInitiallyUnowned parent_instance;
GtkWidgetPrivate *priv;
};
A signal and a handler are connected by the function `g_signal_connect`.
There some similar functions like `g_signal_connect_after`, `g_signal_connect_swapped` and so on.
But I think `g_signal_connect` is the most common function.
The signals "change-file" is connected to a callback function `file_changed` outside of TfeTextView object.
In the same way, the signals "open-response" is connected to a callback function `open_response` outside of TfeTextView object.
The functions `file_changed` and `open_response` will be explained later.
typedef struct _GtkTextView GtkTextView;
struct _GtkTextView
{
GtkWidget parent_instance;
GtkTextViewPrivate *priv;
};
g_signal_connect (GTK_TEXT_VIEW (tv), "change-file", G_CALLBACK (file_changed), nb);
In each structure, its parent instance is declared at the top of the members.
So, every ancestors is included in the child instance.
This is very important.
It guarantees a child widget to derive all the features from ancestors.
The structure of `TfeTextView` is like the following diagram.
g_signal_connect (TFE_TEXT_VIEW (tv), "open-response", G_CALLBACK (open_response), nb);
![The structure of the instance TfeTextView](../image/TfeTextView.png)
## Signal emission
Signals are emitted on the object.
The type of the object is the second argument of `g_signal_newv`.
The relationship between the signal and object (type) is made up when the signal is generated.
## Generate TfeTextView instance
`g_signal_emit` is used to emit the signal.
The following is extract from `tfetexties.c`.
The function `tfe_text_view_new` generates a new TfeTextView instance.
g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS);
g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_CANCEL);
g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
@@@ tfe5/tfetextview.c tfe_text_view_new
- The first argument is the object on which the signal is emitted.
- The second argument is the signal id.
- The third argument is the detail of the signal.
"change-file" signal and "open-response" signal doesn't have details and the argument is zero when no details.
- "change-file" signal doesn't have parameter, so no fourth parameter.
- "open-response" signal has one parameter.
The fourth parameter is the parameter.
When this function is run, the following procedure is gone through.
1. Initialize GObject instance in TfeTextView instance.
2. Initialize GtkWidget instance in TfeTextView instance.
3. Initialize GtkTextView instance in TfeTextView instance.
4. Initialize TfeTextView instance.
Step one through three is done automatically.
Step four is done by the function `tfe_text_view_init`.
> (In the same way, `gtk_text_view_init`, `gtk_widget_init` and `g_object_init` is the initialization functions of GtkTextView, GtkWidget and GObject respectively.
> You can find them in the GTK or GLib source file.)
@@@ tfe5/tfetextview.c on_changed tfe_text_view_init
`tfe_text_view_init` initializes the instance.
- 8-10: Initialize `tb`, `file` and `changed`.
- 11: Set the wrap mode of GtkTextView as GTK\_WRAP\_WORD\_CHAR.
- 12: Connect "changed" signal to a handler `on_changed`.
"changed" signal is defined in GtkTextBuffer.
It is emitted when the contents in the buffer is changed.
- 2-4: `on_changed` handler records TRUE to `tv->changed` when "changed" signal is emitted.
## Functions and Classes
In Gtk, all objects derived from GObject have class and instance.
Instance is memories which has a structure defined by C structure declaration as I mentioned in the previous two subsections.
And instance can be generated two or more.
Those instances have the same structure.
However, structured memories are insufficient to define its behavior.
We need at least two things.
One is functions and the other is class.
You've already seen many functions, for example, `tfe_text_view_new` is a function to generate TfeTextView instance.
These functions are similar to object methods in object oriented languages such as Java and Ruby.
Functions are public, which means that they are expected to be used by other objects.
Class comprises mainly pointers to functions.
And the functions are used by the object itself or its children objects.
For example, GObject class is declared in `gobject.h` in GLib source files.
@@@ class_gobject.c
I'd like to explain some of the members.
There's a pointer to the function `dispose` in line 22.
void (*dispose) (GObject *object);
The declaration is a bit complicated.
The asterisk before the identifier `dispose` means pointer.
So, the pointer `disopse` points a function which has one parameter , which points a GObject structure, and returns no value because of void type.
In the same way, line 23 says `finalize` is a pointer to the function which has one paremeter, which points a GObject structure, and returns no value.
void (*finalize) (GObject *object);
Look at the declaration of `_GObjectClass` so that you would find that most of the members are pointers to functions.
- 10: A function pointed by `constructor` is called when the instance is generated. It completes the initialization of the instance.
- 22: A function pointed by `dispose` is called when the instance destructs itself. Destruction process is divided into two phases. First is called disposing and the instance releases all the references to other instances. The second is finalizing.
- 23: A funtion pointed by `finalize` finishes the destruction process.
- The other pointers point functions which are called during the instance lives.
## TfeTextView class
TfeTextView class is a structure and it includes all its ancestors' class in it.
Let's look at all the classes from GObject, which is the top level object, to TfeTextView object, which is the lowest.
GObject -- GInitiallyUnowned -- GtkWidget -- GtkTextView -- TfeTextView
The following is extracts from the source files (not exactly the same).
@@@ classes.c
- 105-107: This three lines are generated by the macro G\_DECLARE\_FINAL\_TYPE.
So, they are not written in either `tfe_text_view.h` or `tfe_text_view.c`.
- 2, 73, 106: Each derived class puts its parent class at the first member of its structure.
It is the same as instance structures.
- Class members in ancesters are open to their child class.
So, they can be changed in `tfe_text_view_class_init` function.
For example, the `dispose` pointer in GObjectClass will be overridden later in `tfe_text_view_class_init`.
(Override is an object oriented programing terminology.
Override is rewriting ancestors' class methods in the child class.)
- Some class methods are often overridden.
`set_property`, `get_property`, `dispose`, `finalize` and `constructed` are such methods.
TfeTextViewClass includes its ancsestors' class in it.
It is illustrated in the following diagram.
![The structure of TfeTextView Class](../image/TfeTextViewClass.png)
## Destruction of TfeTextView
Every Object derived from GObject has a reference count.
If an object A uses an object B, then A keeps a pointr to B in A and at the same time increaces the reference count of B by one with the function `g_object_ref (B)`.
If A doesn't need B any longer, then A discards the pointer to B (usually it is done by assigning NULL to the pointer) and decreaces the reference count of B by one with the function `g_object_unref (B)`.
If two objects A and B refer to C, then the reference count of C is two.
After A used C and if A no longer needs C, A discards the pointer to C and decreases the reference count in C by one.
Now the reference count of C is one.
In the same way, when B no longer needs C, B discards the pointer to C and decreases the reference count in C by one.
At this moment, no object refers C and the reference count of C is zero.
This means C is no longer useful.
Then C destructs itself and finally the memories allocated to C is freed.
![Reference count of B](../image/refcount.png)
The idea above is based on an assumption that an object refered by nothing has reference count of zero.
When the reference count drops to zero, the object starts its destruction process.
The destruction process is split in two phases: disposing and finalizing.
In the disposing process, the object invokes the handler pointed by `dispose` in its class to release all references to other objects.
In the finalizing process, it invokes the handler pointed by `finalize` in its class to complete the destruction process.
In the destruction process of TfeTextView, the reference count of widgets related to TfeTextView is automatically decreased.
But GFile pointed by `tv->file` needs to decrease its reference count by one.
You must write the code in the dispose handler `tfe_text_view_dispose`.
@@@ tfe5/tfetextview.c tfe_text_view_dispose
- 5,6: If `tv->file` points a GFile, decrease its reference count.
`g_clear_object` decreases the reference count and assigns NULL to `tv->file`. In dispose handlers, we usually use `g_clear_object` rather than `g_object_unref`.
- 8: invoke parent's despose handler. (This will be explained later.)
In the desposing process, the object uses the pointer in its class to call the handler.
Therefore, `tfe_text_view_dispose` needs to be registerd in the class when the TfeTextView class is initialized.
The function `tfe_text_view_class_init` is the class initialization function and it is declared in the replacement produced by `G_DEFINE_TYPE` macro.
static void
tfe_text_view_class_init (TfeTextViewClass *class) {
GObjectClass *object_class = G_OBJECT_CLASS (class);
object_class->dispose = tfe_text_view_dispose;
}
Each ancestors' class is generated before TfeTextViewClass.
Therefore, there are four classes and each class has a pointer to each dispose handler.
Look at the following diagram.
There are four classes -- GObjectClass (GInitiallyUnownedClass), GtkWidgetClass, GtkTextViewClass and TfeTextViewClass.
Each class has its own dispose handler -- `dh1`, `dh2`, `dh3` and `tfe_text_view_dispose`.
![dispose handers](../image/dispose_handler.png)
Now, look at the `tfe_text_view_dispose` program above.
It first releases the reference to GFile object pointed by `tv->file`.
Then it invokes its parent's dispose handler in line 8.
G_OBJECT_CLASS (tfe_text_view_parent_class)->dispose (gobject);
`tfe_text_view_parent_class`,which is made by `G_DEFINE_TYPE` macro, is a pointer that points the parent object class.
Therefore, `G_OBJECT_CLASS (tfe_text_view_parent_class)->dispose` points the handler `dh3` in the diagram above.
And `gobject` is a pointer to TfeTextView object which is casted as a GObject instanse.
`dh3` releases all the references to objects in the GtkTextView part (it is actually the private area pointed by `prev`) in TfeTextView instance.
After that, `dh3` calls `dh2`, and `dh2` calls `dh1`.
Finally all the references are released.