Add gtk4 installation section. Bug fixed.

This commit is contained in:
Toshio Sekiya 2021-01-13 13:29:35 +09:00
parent 9ecd1797da
commit 7632aaf5cf
39 changed files with 7234 additions and 6880 deletions

View file

@ -29,12 +29,12 @@ This tutorial uses another markdown -- pandoc's markdown.
Pandoc is a converter between markdown, html, latex, word docx and so on.
This type of markdown is used to generate html and latex files in this tutorial.
### Src.md file
## Src.md file
Src.md is similar to markdown but it has two commands which isn't included in markdown syntax.
They are @@@ command and $$$ command.
@@@ C\_source\_file \[function_list\]
@@@ C_source_file [function_list]
This command includes the C source file, but if a function list is given, only the functions in the C source file are included.
If no function list is given, the command can include any text files even it is not C source file.
@ -48,7 +48,7 @@ This command executes the shell command and substitutes the strings in the stand
These two commands are carried out by scripts src2md.rb, which is described in the next subsection.
### Conversion
## Conversion
A ruby script src2md converts src.md file to md file.
@ -110,7 +110,7 @@ It is possible that these two directory don't exist before the conversion.
-latex: This directory is empty at first. A ruby script will convert src.md files to latexl files and store them in this directory.
-lib: This directory includes ruby library files.
### Src and top directories
## Src and top directories
Src directory contains src.md files and C-related source files.
The top directory, which is gtk\_tutorial directory, contains md files correspond to src.md files in src directory.
@ -124,14 +124,14 @@ Rake carries out the conversion according to the instruction written in Rakefile
In addition, `Readme.md` file, which has title, abstract and table of contents , is generated.
It doesn't have an original src.md file.
### The name of files in src directory
## The name of files in src directory
Each file in src directory is a section of the whole document.
The name of the files are "sec", number of the section and ".src.md" suffix.
For example, "sec1.src.md", "sec5.src.md" or "sec12.src.md".
They are the files correspond to section 1, section 5 and section 12 respectively.
### C source file directory
## C source file directory
Src.md files might have @@@ commands and they include C source files.
Such C source files are located in the src directory or its subdirectories.
@ -144,7 +144,7 @@ Therefore, It is a good idea to make subdirectories under src directory and put
The name of the subdirectories should be independent of section names.
It is because of renumbering, which will be explained in the next subsection.
### Renumbering
## Renumbering
Sometimes you want to insert a section.
For example, inserting it between section 4 and section 5.
@ -169,7 +169,7 @@ Rakefile has the following tasks.
Rake does renumbering before the tasks above.
### Generate markdown (GFM) files
## Generate markdown (GFM) files
Markdown files (GFM) are generated by rake.
@ -195,7 +195,7 @@ If you want to clean the directory, that means remove all the generated markdown
And `Readme.md` includes links to each markdown files.
Therefore, the repository not only stores source files but also shows the tutorial in it.
### Generate html files
## Generate html files
Src.md files can be translated to html files.
You need pandoc to do this.
@ -219,7 +219,7 @@ Every html file has stylesheet in its header.
This comes from `header` string in `Rakefile`.
You can customize the style by modifying `Rakefile`.
### Generate latex files and a pdf file
## Generate latex files and a pdf file
Src.md files can be translated to latex files.
You need pandoc to do this.

0
image/TfeTextView.ods Executable file → Normal file
View file

View file

@ -143,12 +143,12 @@ private
# tbl[0] (old number (String) is kept in the array 'tbl')
changed = true
self.each do |sec_file|
buf_n = []
buf = IO.readlines sec_file
buf.each do |line|
line.gsub!(/((S|s)ection *)#{tbl[0]}/, "\\1#{n}")
           .gsub!(/((S|s)ec *)#{tbl[0]}/, "\\1#{n}")
buf_n << line.gsub(/((S|s)ection *)#{tbl[i][0]}/, "\\1#{n}").gsub(/((S|s)ec *)#{tbl[i][0]}/, "\\1#{n}")
end
IO.write sec_file buf.join
IO.write sec_file, buf_n.join
end
end
end

14
sec1.md
View file

@ -8,7 +8,7 @@ Up: [Readme.md](Readme.md), Next: [Section 2](sec2.md)
This 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.
However, this tutorial describes only _C programs on linux_.
If you want to try the examples in the tutorial, you need:
@ -17,9 +17,15 @@ If you want to try the examples in the tutorial, you need:
- 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.
Therefore, I strongly recommend you not to install gtk4 to the computer you usually use.
Instead, Install it to another computer only used to try gtk4.
However, it might make some trouble like, for example, your pc doesn't recognize usb port
if you install them to `/usr/local`.
Therefore, I strongly recommend you not to install gtk4 to `/usr/local` on the computer you usually use.
Instead,
- Install it to another computer only used to try gtk4.
- Install it to your home directory, for example `$HOME/local`, in order to separte gtk4 from your system.
The second choice will be explained in [Section 3](sec3.md).
### Software

518
sec10.md
View file

@ -1,165 +1,421 @@
Up: [Readme.md](Readme.md), Prev: [Section 9](sec9.md), Next: [Section 11](sec11.md)
# Signals
# Instance and class
## Signals
This section and the following four sections are explanations about the next version of the text file editor (tfe).
It is tfe5.
It has many changes from the prior version.
All the sources are listed after the five sections.
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 has a new member `file` in it.
The important thing is to manage the Gfile object pointed by `file`.
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 usually 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 as 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;
GFile *file;
};
static guint tfe_text_view_signals[NUMBER_OF_SIGNALS];
The members of the structure are:
Signal registration codes are written in the class initialization function.
- `parent` is the structure of GtkTextView which is the parent object of TfeTextView.
- `file` is a pointer to GFile. It can be NULL if no file corresponds to the TfeTextView object.
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 }
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).
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 has canceled to open a file.
- `TFE_OPEN_RESPONSE_ERROR` is set when error has 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 are some similar functions like `g_signal_connect_after`, `g_signal_connect_swapped` and so on.
However, `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);
1 GtkWidget *
2 tfe_text_view_new (void) {
3 return GTK_WIDGET (g_object_new (TFE_TYPE_TEXT_VIEW, NULL));
4 }
- 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 files.
1 static void
2 tfe_text_view_init (TfeTextView *tv) {
3 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
4
5 tv->file = NULL;
6 gtk_text_buffer_set_modified (tb, FALSE);
7 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
8 }
`tfe_text_view_init` initializes the instance.
- 3: Get the pointer to GtkTextBuffer and assign it to `tb`.
- 5: Initialize `tv->file = NULL`.
- 6: Set modified bit to FALSE. That means the GtkTextBuffer has not modified.
When the buffer is modified, it will automatically toggled on the modified bit.
Whenever the buffer is saved to disk, call gtk_text_buffer_set_modified (buffer , FALSE).
- 7: Set the wrap mode of GtkTextView as GTK\_WRAP\_WORD\_CHAR.
## 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.
Instance, which is structured memories, only keeps status of the object.
Therefore, it is 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 or Ruby.
Functions are public, which means that they are expected to be used by other objects.
Class comprises mainly pointers to functions.
Those functions are used by the object itself or its descendent objects.
For example, GObject class is declared in `gobject.h` in GLib source files.
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 };
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. The first one is called disposing and the instance releases all the references to other instances. The second one is finalizing.
- 23: A funtion pointed by `finalize` finishes the destruction process.
- The other pointers point functions which are called while 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).
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 the descendent 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 descendent 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 has been generated before TfeTextViewClass is generated.
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 instance 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 9](sec9.md), Next: [Section 11](sec11.md)

456
sec11.md
View file

@ -1,363 +1,165 @@
Up: [Readme.md](Readme.md), Prev: [Section 10](sec10.md), Next: [Section 12](sec12.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, GtkWidget *win);
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 usually 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:
- `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);
1 static void
2 tfe_text_view_class_init (TfeTextViewClass *class) {
3 GObjectClass *object_class = G_OBJECT_CLASS (class);
4
5 GtkWidget *tv;
6 GtkTextBuffer *tb;
7 char *contents;
8 gsize length;
9
10 if (! g_file_load_contents (file, NULL, &contents, &length, NULL, NULL)) /* read error */
11 return NULL;
12
13 tv = tfe_text_view_new();
14 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
15 gtk_text_buffer_set_text (tb, contents, length);
16 g_free (contents);
17 TFE_TEXT_VIEW (tv)->file = g_file_dup (file);
18 return tv;
19 }
20
21 GtkWidget *
22 tfe_text_view_new (void) {
23 return GTK_WIDGET (g_object_new (TFE_TYPE_TEXT_VIEW, NULL));
24 }
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 }
- 21-24: `tfe_text_view_new`.
Just returns the value from the function `g_object_new` but casted to the pointer to GtkWidget.
Initialization is done in `tfe_text_view_init` which is called in the process of `gtk_widget_new` function.
- 1-19: `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.
- 10-11: If an error occurs when reading the file, then return NULL.
- 13-18: Generate TfeTextView and set the pointer to it to `tv`.
The pointer to GtkTextBuffer is set to `tb`
Set the contents read from the file to GtkTextBuffer `tb`.
Free the memories pointed by `contents`.
Duplicate `file` and set it to `tv->file`.
Return `tv`.
- 6-15: Register "change-file"signal.
`g_signal_newv` function is used.
This signal has no default handler (object method handler).
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).
## Save and saveas functions
The handlers are as follows.
Save and saveas functions write the contents in GtkTextBuffer to a file.
void change_file_handler (TfeTextView *tv, gpointer user_data);
void open_response_handler (TfeTextView *tv, guint parameter, gpointer user_data);
void tfe_text_view_save (TfeTextView *tv)
- 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`.
`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`.
The parameter is defined in `tfetextview.h` because it is public.
void tfe_text_view_saveas (TfeTextView *tv)
/* "open-response" signal response */
enum
{
TFE_OPEN_RESPONSE_SUCCESS,
TFE_OPEN_RESPONSE_CANCEL,
TFE_OPEN_RESPONSE_ERROR
};
`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.
- `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 has canceled to open a file.
- `TFE_OPEN_RESPONSE_ERROR` is set when error has occured.
## Signal connection
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.
A signal and a handler are connected by the function `g_signal_connect`.
There are some similar functions like `g_signal_connect_after`, `g_signal_connect_swapped` and so on.
However, `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.
1 static void
2 saveas_dialog_response (GtkWidget *dialog, gint response, TfeTextView *tv) {
3 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
4 GFile *file;
5
6 if (response == GTK_RESPONSE_ACCEPT) {
7 file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
8 if (G_IS_FILE(file)) {
9 tv->file = file;
10 gtk_text_buffer_set_modified (tb, TRUE);
11 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
12 tfe_text_view_save (TFE_TEXT_VIEW (tv));
13 }
14 }
15 gtk_window_destroy (GTK_WINDOW (dialog));
16 }
17
18 void
19 tfe_text_view_save (TfeTextView *tv) {
20 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
21
22 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
23 GtkTextIter start_iter;
24 GtkTextIter end_iter;
25 gchar *contents;
26 GtkWidget *message_dialog;
27 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
28 GError *err = NULL;
29
30 if (! gtk_text_buffer_get_modified (tb))
31 return; /* no necessary to save it */
32 else if (tv->file == NULL)
33 tfe_text_view_saveas (tv);
34 else {
35 gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
36 contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
37 if (g_file_replace_contents (tv->file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, &err))
38 gtk_text_buffer_set_modified (tb, FALSE);
39 else {
40 /* It is possible that tv->file is broken. */
41 /* It is a good idea to set tv->file to NULL. */
42 if (G_IS_FILE (tv->file))
43 g_object_unref (tv->file);
44 tv->file =NULL;
45 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
46 gtk_text_buffer_set_modified (tb, TRUE);
47 message_dialog = gtk_message_dialog_new (GTK_WINDOW (win), GTK_DIALOG_MODAL,
48 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
49 "%s.\n", err->message);
50 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
51 gtk_widget_show (message_dialog);
52 g_error_free (err);
53 }
54 }
55 }
56
57 void
58 tfe_text_view_saveas (TfeTextView *tv) {
59 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
60
61 GtkWidget *dialog;
62 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
63
64 dialog = gtk_file_chooser_dialog_new ("Save file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_SAVE,
65 "_Cancel", GTK_RESPONSE_CANCEL,
66 "_Save", GTK_RESPONSE_ACCEPT,
67 NULL);
68 g_signal_connect (dialog, "response", G_CALLBACK (saveas_dialog_response), tv);
69 gtk_widget_show (dialog);
70 }
g_signal_connect (GTK_TEXT_VIEW (tv), "change-file", G_CALLBACK (file_changed), nb);
- 18-55: `Tfe_text_view_save` function.
- 20: 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.
- 30-31: If the buffer hasn't modified, then it doesn't need to save it.
So the function returns.
- 32-33: If `tv->file` is NULL, no file has given yet.
It calls `tfe_text_view_saveas`, which lets the user to choose a file to save.
- 35-36: Get the contents of the GtkTextBuffer and set its pointer to `contents`.
- 37-38: Save the content to the file.
If it succeeds, reset the modified bit in the GtkTextBuffer.
- 39-53: If file writing fails, it assigns NULL to `tv->file`.
Emits "change-file" signal.
Shows the error message dialog (47-51).
Because the handler is `gtk_window_destroy`, the dialog disappears when user clicks on the button in the dialog.
- 57-70: `tfe_text_view_saveas` function.
It shows GtkFileChooserDialog and lets the user choose a file and give it to the signal handler.
- 64-67: 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.
- 68: connect the "response" signal of the dialog and `saveas_dialog_response` handler.
- 1-16: `saveas_dialog_response` signal handler.
- 6-14: 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`, turn on the modified bit of the GtkTextBuffer, emits "change-file" signal then call `tfe_text_view_save` to save the buffer to the file.
g_signal_connect (TFE_TEXT_VIEW (tv), "open-response", G_CALLBACK (open_response), nb);
![Saveas process](image/saveas.png)
## Signal emission
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 by calling `tfe_text_view_save`.
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.
## Open function
`g_signal_emit` is used to emit the signal.
The following is extract from `tfetexties.c`.
Open function shows GtkFileChooserDialog to the user and let them choose a file.
Then read the file and set it to GtkTextBuffer.
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);
void tfe_text_view_open (TfeTextView *tv, GtkWidget *win);
TfeTextView object `tv` has to be generated in advance.
This function is usually called just after `tv` has been generated.
And its buffer is empty, `tv->file` is NULL and `tv` has not set to the widget hierarchy.
Even if the buffer 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.
GtkWidget `win` is expected to be the top level window of the application.
It will be used as a transient parent window for the argument to the function `gtk_file_chooser_dialog_new`.
1 static void
2 open_dialog_response(GtkWidget *dialog, gint response, TfeTextView *tv) {
3 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
4 GFile *file;
5 char *contents;
6 gsize length;
7 GtkWidget *message_dialog;
8 GError *err = NULL;
9
10 if (response != GTK_RESPONSE_ACCEPT)
11 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_CANCEL);
12 else if (! G_IS_FILE (file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog))))
13 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
14 else if (! g_file_load_contents (file, NULL, &contents, &length, NULL, &err)) { /* read error */
15 if (G_IS_FILE (file))
16 g_object_unref (file);
17 message_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL,
18 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
19 "%s.\n", err->message);
20 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
21 gtk_widget_show (message_dialog);
22 g_error_free (err);
23 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
24 } else {
25 gtk_text_buffer_set_text (tb, contents, length);
26 g_free (contents);
27 if (G_IS_FILE (tv->file))
28 g_object_unref (tv->file);
29 tv->file = file;
30 gtk_text_buffer_set_modified (tb, FALSE);
31 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS);
32 }
33 gtk_window_destroy (GTK_WINDOW (dialog));
34 }
35
36 void
37 tfe_text_view_open (TfeTextView *tv, GtkWidget *win) {
38 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
39 g_return_if_fail (GTK_IS_WINDOW (win));
40
41 GtkWidget *dialog;
42
43 dialog = gtk_file_chooser_dialog_new ("Open file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_OPEN,
44 "Cancel", GTK_RESPONSE_CANCEL,
45 "Open", GTK_RESPONSE_ACCEPT,
46 NULL);
47 g_signal_connect (dialog, "response", G_CALLBACK (open_dialog_response), tv);
48 gtk_widget_show (dialog);
49 }
- 36-49: `tfe_text_view_open` function.
- 43: Generate GtkFileChooserDialog.
The title is "Open file".
Ttransient parent window is the top window of the application, which is given by the caller.
The action is open mode.
The buttons are Cancel and Open.
- 47: connect the "reponse" signal of the dialog and `open_dialog_response` signal handler.
- 48: Show the dialog.
- 1-34: `open_dialog_response` signal handler.
- 10-11: 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`.
- 12-13: 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`.
- 14-23: If an error occurs when it 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-32: 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`.
- 33: 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 14](sec14.md).
- 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.
Up: [Readme.md](Readme.md), Prev: [Section 10](sec10.md), Next: [Section 12](sec12.md)

530
sec12.md
View file

@ -1,221 +1,363 @@
Up: [Readme.md](Readme.md), Prev: [Section 11](sec11.md), Next: [Section 13](sec13.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` have 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`.
- 10-11: `notebook_page_new` generates a new GtkNotebookPage and adds GtkScrolledWindow and TfeTextView on the page.
- 7-8: `notebook_page_new_with_file` generates a new GtkNotebookPage and adds GtkScrolledWindow and TfeTextView on the page. The file is read and set into GtkTextBuffer.
The GFile `file` is copied and set in the TfeTextView object.
- 4-5: `notebook_page_open` shows a file chooser dialog. Then, user chooses a file and the file is set into GtkTextBuffer.
- 1-2: `notebook_page_save` saves the contents in GtkTextBuffer into the file, which has been set in the TfeTextView.
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`
- `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, GtkWidget *win);
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 to check the argument.
- 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 append it to 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;
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 }
## Functions to generate TfeTextView object
- 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.
TfeTextView Object is generated by `tfe_text_view_new` or `tfe_text_view_new_with_file`.
## notebook\_page\_open
GtkWidget *tfe_text_view_new (void);
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_ref_sink (tv);
8 g_object_unref (tv);
9 }else if (! G_IS_FILE (file = tfe_text_view_get_file (tv))) {
10 g_object_ref_sink (tv);
11 g_object_unref (tv);
12 }else {
13 filename = g_file_get_basename (file);
14 g_object_unref (file);
15 notebook_page_build (nb, GTK_WIDGET (tv), filename);
16 }
17 }
18
19 void
20 notebook_page_open (GtkNotebook *nb) {
21 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
22
23 GtkWidget *tv;
24
25 tv = tfe_text_view_new ();
26 g_signal_connect (TFE_TEXT_VIEW (tv), "open-response", G_CALLBACK (open_response), nb);
27 tfe_text_view_open (TFE_TEXT_VIEW (tv), gtk_widget_get_ancestor (GTK_WIDGET (nb), GTK_TYPE_WINDOW));
28 }
`tfe_text_view_new` just generates a new TfeTextView object and returns the pointer to the new object.
- 19-28: `notebook_page_open` function.
- 25: Generate TfeTextView object.
- 26: Connect the signal "open-response" and the handler `open_response`.
- 27: Call `tfe_text_view_open`.
It emits "open-response" signal to inform the status after the series of functions run.
- 1-17: `open_response` handler.
This is the post-function of `notebook_page_open`.
- 6-8: If the status is NOT `TFE_OPEN_RESPONSE_SUCCESS`, cancel what we did in `notebook_page_open`.
The object `tv` hasn't been a child widget of some other widget yet.
Such object has floating reference.
It needs to do `g_object_ref_sink` and clear the floating reference before `g_object_unref`.
- 9-11: If `tfe_text_view_get_file` returns a pointer not to point GFile, then something bad happens. Cancel what we did.
Sink and unref `tv`.
- 12-16: Otherwise, everything is okay.
Get the filename, build the contents of the page.
GtkWidget *tfe_text_view_new_with_file (GFile *file);
## notebook\_page\_save
`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.
1 void
2 notebook_page_save(GtkNotebook *nb) {
3 gint i;
4 GtkWidget *scr;
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
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 }
6 GtkTextBuffer *tb;
7 char *contents;
8 gsize length;
9
10 if (! g_file_load_contents (file, NULL, &contents, &length, NULL, NULL)) /* read error */
11 return NULL;
12
13 tv = tfe_text_view_new();
14 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
15 gtk_text_buffer_set_text (tb, contents, length);
16 g_free (contents);
17 TFE_TEXT_VIEW (tv)->file = g_file_dup (file);
18 return tv;
19 }
20
21 GtkWidget *
22 tfe_text_view_new (void) {
23 return GTK_WIDGET (g_object_new (TFE_TYPE_TEXT_VIEW, NULL));
24 }
- 7-9: Get TfeTextView belongs to the current notebook page.
- 10: Call `tfe_text_view_save`.
- 21-24: `tfe_text_view_new`.
Just returns the value from the function `g_object_new` but casted to the pointer to GtkWidget.
Initialization is done in `tfe_text_view_init` which is called in the process of `gtk_widget_new` function.
- 1-19: `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.
- 10-11: If an error occurs when reading the file, then return NULL.
- 13-18: Generate TfeTextView and set the pointer to it to `tv`.
The pointer to GtkTextBuffer is set to `tb`
Set the contents read from the file to GtkTextBuffer `tb`.
Free the memories pointed by `contents`.
Duplicate `file` and set it to `tv->file`.
Return `tv`.
## file\_changed handler
## Save and saveas functions
The function `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.
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 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 }
2 saveas_dialog_response (GtkWidget *dialog, gint response, TfeTextView *tv) {
3 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
4 GFile *file;
5
6 if (response == GTK_RESPONSE_ACCEPT) {
7 file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
8 if (G_IS_FILE(file)) {
9 tv->file = file;
10 gtk_text_buffer_set_modified (tb, TRUE);
11 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
12 tfe_text_view_save (TFE_TEXT_VIEW (tv));
13 }
14 }
15 gtk_window_destroy (GTK_WINDOW (dialog));
16 }
17
18 void
19 tfe_text_view_save (TfeTextView *tv) {
20 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
21
22 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
23 GtkTextIter start_iter;
24 GtkTextIter end_iter;
25 gchar *contents;
26 GtkWidget *message_dialog;
27 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
28 GError *err = NULL;
29
30 if (! gtk_text_buffer_get_modified (tb))
31 return; /* no necessary to save it */
32 else if (tv->file == NULL)
33 tfe_text_view_saveas (tv);
34 else {
35 gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
36 contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
37 if (g_file_replace_contents (tv->file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, &err))
38 gtk_text_buffer_set_modified (tb, FALSE);
39 else {
40 /* It is possible that tv->file is broken. */
41 /* It is a good idea to set tv->file to NULL. */
42 if (G_IS_FILE (tv->file))
43 g_object_unref (tv->file);
44 tv->file =NULL;
45 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
46 gtk_text_buffer_set_modified (tb, TRUE);
47 message_dialog = gtk_message_dialog_new (GTK_WINDOW (win), GTK_DIALOG_MODAL,
48 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
49 "%s.\n", err->message);
50 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
51 gtk_widget_show (message_dialog);
52 g_error_free (err);
53 }
54 }
55 }
56
57 void
58 tfe_text_view_saveas (TfeTextView *tv) {
59 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
60
61 GtkWidget *dialog;
62 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
63
64 dialog = gtk_file_chooser_dialog_new ("Save file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_SAVE,
65 "_Cancel", GTK_RESPONSE_CANCEL,
66 "_Save", GTK_RESPONSE_ACCEPT,
67 NULL);
68 g_signal_connect (dialog, "response", G_CALLBACK (saveas_dialog_response), tv);
69 gtk_widget_show (dialog);
70 }
- 8: Get GFile from TfeTextView.
- 9: Get GkScrolledWindow which is the parent widget of `tv`.
- 10-13: If `file` points GFile, then assign the filename of the GFile into `filename`.
Otherwise (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`.
- 18-55: `Tfe_text_view_save` function.
- 20: 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.
- 30-31: If the buffer hasn't modified, then it doesn't need to save it.
So the function returns.
- 32-33: If `tv->file` is NULL, no file has given yet.
It calls `tfe_text_view_saveas`, which lets the user to choose a file to save.
- 35-36: Get the contents of the GtkTextBuffer and set its pointer to `contents`.
- 37-38: Save the content to the file.
If it succeeds, reset the modified bit in the GtkTextBuffer.
- 39-53: If file writing fails, it assigns NULL to `tv->file`.
Emits "change-file" signal.
Shows the error message dialog (47-51).
Because the handler is `gtk_window_destroy`, the dialog disappears when user clicks on the button in the dialog.
- 57-70: `tfe_text_view_saveas` function.
It shows GtkFileChooserDialog and lets the user choose a file and give it to the signal handler.
- 64-67: 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.
- 68: connect the "response" signal of the dialog and `saveas_dialog_response` handler.
- 1-16: `saveas_dialog_response` signal handler.
- 6-14: 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`, turn on the modified bit of the GtkTextBuffer, 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 by calling `tfe_text_view_save`.
## Open function
Open function shows GtkFileChooserDialog to the user and let them choose a file.
Then read the file and set it to GtkTextBuffer.
void tfe_text_view_open (TfeTextView *tv, GtkWidget *win);
TfeTextView object `tv` has to be generated in advance.
This function is usually called just after `tv` has been generated.
And its buffer is empty, `tv->file` is NULL and `tv` has not set to the widget hierarchy.
Even if the buffer 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.
GtkWidget `win` is expected to be the top level window of the application.
It will be used as a transient parent window for the argument to the function `gtk_file_chooser_dialog_new`.
1 static void
2 open_dialog_response(GtkWidget *dialog, gint response, TfeTextView *tv) {
3 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
4 GFile *file;
5 char *contents;
6 gsize length;
7 GtkWidget *message_dialog;
8 GError *err = NULL;
9
10 if (response != GTK_RESPONSE_ACCEPT)
11 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_CANCEL);
12 else if (! G_IS_FILE (file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog))))
13 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
14 else if (! g_file_load_contents (file, NULL, &contents, &length, NULL, &err)) { /* read error */
15 if (G_IS_FILE (file))
16 g_object_unref (file);
17 message_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL,
18 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
19 "%s.\n", err->message);
20 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
21 gtk_widget_show (message_dialog);
22 g_error_free (err);
23 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
24 } else {
25 gtk_text_buffer_set_text (tb, contents, length);
26 g_free (contents);
27 if (G_IS_FILE (tv->file))
28 g_object_unref (tv->file);
29 tv->file = file;
30 gtk_text_buffer_set_modified (tb, FALSE);
31 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS);
32 }
33 gtk_window_destroy (GTK_WINDOW (dialog));
34 }
35
36 void
37 tfe_text_view_open (TfeTextView *tv, GtkWidget *win) {
38 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
39 g_return_if_fail (GTK_IS_WINDOW (win));
40
41 GtkWidget *dialog;
42
43 dialog = gtk_file_chooser_dialog_new ("Open file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_OPEN,
44 "Cancel", GTK_RESPONSE_CANCEL,
45 "Open", GTK_RESPONSE_ACCEPT,
46 NULL);
47 g_signal_connect (dialog, "response", G_CALLBACK (open_dialog_response), tv);
48 gtk_widget_show (dialog);
49 }
- 36-49: `tfe_text_view_open` function.
- 43: Generate GtkFileChooserDialog.
The title is "Open file".
Ttransient parent window is the top window of the application, which is given by the caller.
The action is open mode.
The buttons are Cancel and Open.
- 47: connect the "reponse" signal of the dialog and `open_dialog_response` signal handler.
- 48: Show the dialog.
- 1-34: `open_dialog_response` signal handler.
- 10-11: 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`.
- 12-13: 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`.
- 14-23: If an error occurs when it 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-32: 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`.
- 33: 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 15](sec15.md).
Up: [Readme.md](Readme.md), Prev: [Section 11](sec11.md), Next: [Section 13](sec13.md)

475
sec13.md
View file

@ -1,272 +1,221 @@
Up: [Readme.md](Readme.md), Prev: [Section 12](sec12.md), Next: [Section 14](sec14.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` have 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 widgets, 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, we need to close the top level window and quit the application.
First, get 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`.
- 10-11: `notebook_page_new` generates a new GtkNotebookPage and adds GtkScrolledWindow and TfeTextView on the page.
- 7-8: `notebook_page_new_with_file` generates a new GtkNotebookPage and adds GtkScrolledWindow and TfeTextView on the page. The file is read and set into GtkTextBuffer.
The GFile `file` is copied and set in the TfeTextView object.
- 4-5: `notebook_page_open` shows a file chooser dialog. Then, user chooses a file and the file is set into GtkTextBuffer.
- 1-2: `notebook_page_save` saves the contents in GtkTextBuffer into the file, which has been set in the TfeTextView.
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`
- `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 to check the argument.
- 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 append it to 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.
## 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_ref_sink (tv);
8 g_object_unref (tv);
9 }else if (! G_IS_FILE (file = tfe_text_view_get_file (tv))) {
10 g_object_ref_sink (tv);
11 g_object_unref (tv);
12 }else {
13 filename = g_file_get_basename (file);
14 g_object_unref (file);
15 notebook_page_build (nb, GTK_WIDGET (tv), filename);
16 }
17 }
18
19 void
20 notebook_page_open (GtkNotebook *nb) {
21 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
22
23 GtkWidget *tv;
24
25 tv = tfe_text_view_new ();
26 g_signal_connect (TFE_TEXT_VIEW (tv), "open-response", G_CALLBACK (open_response), nb);
27 tfe_text_view_open (TFE_TEXT_VIEW (tv), gtk_widget_get_ancestor (GTK_WIDGET (nb), GTK_TYPE_WINDOW));
28 }
- 19-28: `notebook_page_open` function.
- 25: Generate TfeTextView object.
- 26: Connect the signal "open-response" and the handler `open_response`.
- 27: Call `tfe_text_view_open`.
It emits "open-response" signal to inform the status after the series of functions run.
- 1-17: `open_response` handler.
This is the post-function of `notebook_page_open`.
- 6-8: If the status is NOT `TFE_OPEN_RESPONSE_SUCCESS`, cancel what we did in `notebook_page_open`.
The object `tv` hasn't been a child widget of some other widget yet.
Such object has floating reference.
It needs to do `g_object_ref_sink` and clear the floating reference before `g_object_unref`.
- 9-11: If `tfe_text_view_get_file` returns a pointer not to point GFile, then something bad happens. Cancel what we did.
Sink and unref `tv`.
- 12-16: Otherwise, everything is okay.
Get the filename, build the contents of the page.
## 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
The function `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 GkScrolledWindow which is the parent widget of `tv`.
- 10-13: If `file` points GFile, then assign the filename of the GFile into `filename`.
Otherwise (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`.
In this file, just the source file names are modified.
Up: [Readme.md](Readme.md), Prev: [Section 12](sec12.md), Next: [Section 14](sec14.md)

858
sec14.md
View file

@ -1,10 +1,259 @@
Up: [Readme.md](Readme.md), Prev: [Section 13](sec13.md), Next: [Section 15](sec15.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 widgets, 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, we need to close the top level window and quit the application.
First, get the top level window and call `gtk_window_destroy`.
- 26-28: Otherwise, it removes the current page.
## meson.build
1 project('tfe', 'c')
2
@ -17,608 +266,7 @@ The followings are the source files of tfe5.
9
10 executable('tfe', sourcefiles, resources, dependencies: gtkdep)
## tfe.gresource.xml
In this file, just the source file names are modified.
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_ref_sink (tv);
69 g_object_unref (tv);
70 }else if (! G_IS_FILE (file = tfe_text_view_get_file (tv))) {
71 g_object_ref_sink (tv);
72 g_object_unref (tv);
73 }else {
74 filename = g_file_get_basename (file);
75 g_object_unref (file);
76 notebook_page_build (nb, GTK_WIDGET (tv), filename);
77 }
78 }
79
80 void
81 notebook_page_open (GtkNotebook *nb) {
82 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
83
84 GtkWidget *tv;
85
86 tv = tfe_text_view_new ();
87 g_signal_connect (TFE_TEXT_VIEW (tv), "open-response", G_CALLBACK (open_response), nb);
88 tfe_text_view_open (TFE_TEXT_VIEW (tv), gtk_widget_get_ancestor (GTK_WIDGET (nb), GTK_TYPE_WINDOW));
89 }
90
91 void
92 notebook_page_new_with_file (GtkNotebook *nb, GFile *file) {
93 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
94 g_return_if_fail(G_IS_FILE (file));
95
96 GtkWidget *tv;
97 char *filename;
98
99 if ((tv = tfe_text_view_new_with_file (file)) == NULL)
100 return; /* read error */
101 filename = g_file_get_basename (file);
102 notebook_page_build (nb, tv, filename);
103 }
104
105 void
106 notebook_page_new (GtkNotebook *nb) {
107 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
108
109 GtkWidget *tv;
110 char *filename;
111
112 tv = tfe_text_view_new ();
113 filename = get_untitled ();
114 notebook_page_build (nb, tv, filename);
115 }
116
## 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, GtkWidget *win);
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 GFile *file;
7 };
8
9 G_DEFINE_TYPE (TfeTextView, tfe_text_view, GTK_TYPE_TEXT_VIEW);
10
11 enum {
12 CHANGE_FILE,
13 OPEN_RESPONSE,
14 NUMBER_OF_SIGNALS
15 };
16
17 static guint tfe_text_view_signals[NUMBER_OF_SIGNALS];
18
19 static void
20 tfe_text_view_dispose (GObject *gobject) {
21 TfeTextView *tv = TFE_TEXT_VIEW (gobject);
22
23 if (G_IS_FILE (tv->file))
24 g_clear_object (&tv->file);
25
26 G_OBJECT_CLASS (tfe_text_view_parent_class)->dispose (gobject);
27 }
28
29 static void
30 tfe_text_view_init (TfeTextView *tv) {
31 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
32
33 tv->file = NULL;
34 gtk_text_buffer_set_modified (tb, FALSE);
35 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
36 }
37
38 static void
39 tfe_text_view_class_init (TfeTextViewClass *class) {
40 GObjectClass *object_class = G_OBJECT_CLASS (class);
41
42 object_class->dispose = tfe_text_view_dispose;
43 tfe_text_view_signals[CHANGE_FILE] = g_signal_newv ("change-file",
44 G_TYPE_FROM_CLASS (class),
45 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
46 NULL /* closure */,
47 NULL /* accumulator */,
48 NULL /* accumulator data */,
49 NULL /* C marshaller */,
50 G_TYPE_NONE /* return_type */,
51 0 /* n_params */,
52 NULL /* param_types */);
53 GType param_types[] = {G_TYPE_INT};
54 tfe_text_view_signals[OPEN_RESPONSE] = g_signal_newv ("open-response",
55 G_TYPE_FROM_CLASS (class),
56 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
57 NULL /* closure */,
58 NULL /* accumulator */,
59 NULL /* accumulator data */,
60 NULL /* C marshaller */,
61 G_TYPE_NONE /* return_type */,
62 1 /* n_params */,
63 param_types);
64 }
65
66 GFile *
67 tfe_text_view_get_file (TfeTextView *tv) {
68 g_return_val_if_fail (TFE_IS_TEXT_VIEW (tv), NULL);
69
70 return g_file_dup (tv->file);
71 }
72
73 static void
74 open_dialog_response(GtkWidget *dialog, gint response, TfeTextView *tv) {
75 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
76 GFile *file;
77 char *contents;
78 gsize length;
79 GtkWidget *message_dialog;
80 GError *err = NULL;
81
82 if (response != GTK_RESPONSE_ACCEPT)
83 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_CANCEL);
84 else if (! G_IS_FILE (file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog))))
85 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
86 else if (! g_file_load_contents (file, NULL, &contents, &length, NULL, &err)) { /* read error */
87 if (G_IS_FILE (file))
88 g_object_unref (file);
89 message_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL,
90 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
91 "%s.\n", err->message);
92 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
93 gtk_widget_show (message_dialog);
94 g_error_free (err);
95 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
96 } else {
97 gtk_text_buffer_set_text (tb, contents, length);
98 g_free (contents);
99 if (G_IS_FILE (tv->file))
100 g_object_unref (tv->file);
101 tv->file = file;
102 gtk_text_buffer_set_modified (tb, FALSE);
103 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS);
104 }
105 gtk_window_destroy (GTK_WINDOW (dialog));
106 }
107
108 void
109 tfe_text_view_open (TfeTextView *tv, GtkWidget *win) {
110 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
111 g_return_if_fail (GTK_IS_WINDOW (win));
112
113 GtkWidget *dialog;
114
115 dialog = gtk_file_chooser_dialog_new ("Open file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_OPEN,
116 "Cancel", GTK_RESPONSE_CANCEL,
117 "Open", GTK_RESPONSE_ACCEPT,
118 NULL);
119 g_signal_connect (dialog, "response", G_CALLBACK (open_dialog_response), tv);
120 gtk_widget_show (dialog);
121 }
122
123 static void
124 saveas_dialog_response (GtkWidget *dialog, gint response, TfeTextView *tv) {
125 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
126 GFile *file;
127
128 if (response == GTK_RESPONSE_ACCEPT) {
129 file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
130 if (G_IS_FILE(file)) {
131 tv->file = file;
132 gtk_text_buffer_set_modified (tb, TRUE);
133 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
134 tfe_text_view_save (TFE_TEXT_VIEW (tv));
135 }
136 }
137 gtk_window_destroy (GTK_WINDOW (dialog));
138 }
139
140 void
141 tfe_text_view_save (TfeTextView *tv) {
142 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
143
144 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
145 GtkTextIter start_iter;
146 GtkTextIter end_iter;
147 gchar *contents;
148 GtkWidget *message_dialog;
149 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
150 GError *err = NULL;
151
152 if (! gtk_text_buffer_get_modified (tb))
153 return; /* no necessary to save it */
154 else if (tv->file == NULL)
155 tfe_text_view_saveas (tv);
156 else {
157 gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
158 contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
159 if (g_file_replace_contents (tv->file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, &err))
160 gtk_text_buffer_set_modified (tb, FALSE);
161 else {
162 /* It is possible that tv->file is broken. */
163 /* It is a good idea to set tv->file to NULL. */
164 if (G_IS_FILE (tv->file))
165 g_object_unref (tv->file);
166 tv->file =NULL;
167 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
168 gtk_text_buffer_set_modified (tb, TRUE);
169 message_dialog = gtk_message_dialog_new (GTK_WINDOW (win), GTK_DIALOG_MODAL,
170 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
171 "%s.\n", err->message);
172 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
173 gtk_widget_show (message_dialog);
174 g_error_free (err);
175 }
176 }
177 }
178
179 void
180 tfe_text_view_saveas (TfeTextView *tv) {
181 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
182
183 GtkWidget *dialog;
184 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
185
186 dialog = gtk_file_chooser_dialog_new ("Save file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_SAVE,
187 "_Cancel", GTK_RESPONSE_CANCEL,
188 "_Save", GTK_RESPONSE_ACCEPT,
189 NULL);
190 g_signal_connect (dialog, "response", G_CALLBACK (saveas_dialog_response), tv);
191 gtk_widget_show (dialog);
192 }
193
194 GtkWidget *
195 tfe_text_view_new_with_file (GFile *file) {
196 g_return_val_if_fail (G_IS_FILE (file), NULL);
197
198 GtkWidget *tv;
199 GtkTextBuffer *tb;
200 char *contents;
201 gsize length;
202
203 if (! g_file_load_contents (file, NULL, &contents, &length, NULL, NULL)) /* read error */
204 return NULL;
205
206 tv = tfe_text_view_new();
207 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
208 gtk_text_buffer_set_text (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 (g_object_new (TFE_TYPE_TEXT_VIEW, NULL));
217 }
218
## Total number of lines, words and charcters
$ LANG=C 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
116 321 2992 tfe5/tfenotebook.c
12 17 196 tfe5/tfenotebook.h
218 635 7769 tfe5/tfetextview.c
29 49 561 tfe5/tfetextview.h
64 105 2266 tfe5/tfe.ui
576 1507 17864 total
Up: [Readme.md](Readme.md), Prev: [Section 13](sec13.md), Next: [Section 15](sec15.md)

816
sec15.md
View file

@ -1,218 +1,624 @@
Up: [Readme.md](Readme.md), Prev: [Section 14](sec14.md), Next: [Section 16](sec16.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_ref_sink (tv);
69 g_object_unref (tv);
70 }else if (! G_IS_FILE (file = tfe_text_view_get_file (tv))) {
71 g_object_ref_sink (tv);
72 g_object_unref (tv);
73 }else {
74 filename = g_file_get_basename (file);
75 g_object_unref (file);
76 notebook_page_build (nb, GTK_WIDGET (tv), filename);
77 }
78 }
79
80 void
81 notebook_page_open (GtkNotebook *nb) {
82 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
83
84 GtkWidget *tv;
85
86 tv = tfe_text_view_new ();
87 g_signal_connect (TFE_TEXT_VIEW (tv), "open-response", G_CALLBACK (open_response), nb);
88 tfe_text_view_open (TFE_TEXT_VIEW (tv), gtk_widget_get_ancestor (GTK_WIDGET (nb), GTK_TYPE_WINDOW));
89 }
90
91 void
92 notebook_page_new_with_file (GtkNotebook *nb, GFile *file) {
93 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
94 g_return_if_fail(G_IS_FILE (file));
95
96 GtkWidget *tv;
97 char *filename;
98
99 if ((tv = tfe_text_view_new_with_file (file)) == NULL)
100 return; /* read error */
101 filename = g_file_get_basename (file);
102 notebook_page_build (nb, tv, filename);
103 }
104
105 void
106 notebook_page_new (GtkNotebook *nb) {
107 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
108
109 GtkWidget *tv;
110 char *filename;
111
112 tv = tfe_text_view_new ();
113 filename = get_untitled ();
114 notebook_page_build (nb, tv, filename);
115 }
116
## 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, GtkWidget *win);
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 GFile *file;
7 };
8
9 G_DEFINE_TYPE (TfeTextView, tfe_text_view, GTK_TYPE_TEXT_VIEW);
10
11 enum {
12 CHANGE_FILE,
13 OPEN_RESPONSE,
14 NUMBER_OF_SIGNALS
15 };
16
17 static guint tfe_text_view_signals[NUMBER_OF_SIGNALS];
18
19 static void
20 tfe_text_view_dispose (GObject *gobject) {
21 TfeTextView *tv = TFE_TEXT_VIEW (gobject);
22
23 if (G_IS_FILE (tv->file))
24 g_clear_object (&tv->file);
25
26 G_OBJECT_CLASS (tfe_text_view_parent_class)->dispose (gobject);
27 }
28
29 static void
30 tfe_text_view_init (TfeTextView *tv) {
31 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
32
33 tv->file = NULL;
34 gtk_text_buffer_set_modified (tb, FALSE);
35 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
36 }
37
38 static void
39 tfe_text_view_class_init (TfeTextViewClass *class) {
40 GObjectClass *object_class = G_OBJECT_CLASS (class);
41
42 object_class->dispose = tfe_text_view_dispose;
43 tfe_text_view_signals[CHANGE_FILE] = g_signal_newv ("change-file",
44 G_TYPE_FROM_CLASS (class),
45 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
46 NULL /* closure */,
47 NULL /* accumulator */,
48 NULL /* accumulator data */,
49 NULL /* C marshaller */,
50 G_TYPE_NONE /* return_type */,
51 0 /* n_params */,
52 NULL /* param_types */);
53 GType param_types[] = {G_TYPE_INT};
54 tfe_text_view_signals[OPEN_RESPONSE] = g_signal_newv ("open-response",
55 G_TYPE_FROM_CLASS (class),
56 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
57 NULL /* closure */,
58 NULL /* accumulator */,
59 NULL /* accumulator data */,
60 NULL /* C marshaller */,
61 G_TYPE_NONE /* return_type */,
62 1 /* n_params */,
63 param_types);
64 }
65
66 GFile *
67 tfe_text_view_get_file (TfeTextView *tv) {
68 g_return_val_if_fail (TFE_IS_TEXT_VIEW (tv), NULL);
69
70 return g_file_dup (tv->file);
71 }
72
73 static void
74 open_dialog_response(GtkWidget *dialog, gint response, TfeTextView *tv) {
75 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
76 GFile *file;
77 char *contents;
78 gsize length;
79 GtkWidget *message_dialog;
80 GError *err = NULL;
81
82 if (response != GTK_RESPONSE_ACCEPT)
83 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_CANCEL);
84 else if (! G_IS_FILE (file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog))))
85 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
86 else if (! g_file_load_contents (file, NULL, &contents, &length, NULL, &err)) { /* read error */
87 if (G_IS_FILE (file))
88 g_object_unref (file);
89 message_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL,
90 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
91 "%s.\n", err->message);
92 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
93 gtk_widget_show (message_dialog);
94 g_error_free (err);
95 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
96 } else {
97 gtk_text_buffer_set_text (tb, contents, length);
98 g_free (contents);
99 if (G_IS_FILE (tv->file))
100 g_object_unref (tv->file);
101 tv->file = file;
102 gtk_text_buffer_set_modified (tb, FALSE);
103 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS);
104 }
105 gtk_window_destroy (GTK_WINDOW (dialog));
106 }
107
108 void
109 tfe_text_view_open (TfeTextView *tv, GtkWidget *win) {
110 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
111 g_return_if_fail (GTK_IS_WINDOW (win));
112
113 GtkWidget *dialog;
114
115 dialog = gtk_file_chooser_dialog_new ("Open file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_OPEN,
116 "Cancel", GTK_RESPONSE_CANCEL,
117 "Open", GTK_RESPONSE_ACCEPT,
118 NULL);
119 g_signal_connect (dialog, "response", G_CALLBACK (open_dialog_response), tv);
120 gtk_widget_show (dialog);
121 }
122
123 static void
124 saveas_dialog_response (GtkWidget *dialog, gint response, TfeTextView *tv) {
125 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
126 GFile *file;
127
128 if (response == GTK_RESPONSE_ACCEPT) {
129 file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
130 if (G_IS_FILE(file)) {
131 tv->file = file;
132 gtk_text_buffer_set_modified (tb, TRUE);
133 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
134 tfe_text_view_save (TFE_TEXT_VIEW (tv));
135 }
136 }
137 gtk_window_destroy (GTK_WINDOW (dialog));
138 }
139
140 void
141 tfe_text_view_save (TfeTextView *tv) {
142 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
143
144 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
145 GtkTextIter start_iter;
146 GtkTextIter end_iter;
147 gchar *contents;
148 GtkWidget *message_dialog;
149 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
150 GError *err = NULL;
151
152 if (! gtk_text_buffer_get_modified (tb))
153 return; /* no necessary to save it */
154 else if (tv->file == NULL)
155 tfe_text_view_saveas (tv);
156 else {
157 gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
158 contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
159 if (g_file_replace_contents (tv->file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, &err))
160 gtk_text_buffer_set_modified (tb, FALSE);
161 else {
162 /* It is possible that tv->file is broken. */
163 /* It is a good idea to set tv->file to NULL. */
164 if (G_IS_FILE (tv->file))
165 g_object_unref (tv->file);
166 tv->file =NULL;
167 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
168 gtk_text_buffer_set_modified (tb, TRUE);
169 message_dialog = gtk_message_dialog_new (GTK_WINDOW (win), GTK_DIALOG_MODAL,
170 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
171 "%s.\n", err->message);
172 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
173 gtk_widget_show (message_dialog);
174 g_error_free (err);
175 }
176 }
177 }
178
179 void
180 tfe_text_view_saveas (TfeTextView *tv) {
181 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
182
183 GtkWidget *dialog;
184 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
185
186 dialog = gtk_file_chooser_dialog_new ("Save file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_SAVE,
187 "_Cancel", GTK_RESPONSE_CANCEL,
188 "_Save", GTK_RESPONSE_ACCEPT,
189 NULL);
190 g_signal_connect (dialog, "response", G_CALLBACK (saveas_dialog_response), tv);
191 gtk_widget_show (dialog);
192 }
193
194 GtkWidget *
195 tfe_text_view_new_with_file (GFile *file) {
196 g_return_val_if_fail (G_IS_FILE (file), NULL);
197
198 GtkWidget *tv;
199 GtkTextBuffer *tb;
200 char *contents;
201 gsize length;
202
203 if (! g_file_load_contents (file, NULL, &contents, &length, NULL, NULL)) /* read error */
204 return NULL;
205
206 tv = tfe_text_view_new();
207 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
208 gtk_text_buffer_set_text (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 (g_object_new (TFE_TYPE_TEXT_VIEW, NULL));
217 }
218
![Screenshot of menu1](image/menu1_screenshot.png)
## Total number of lines, words and charcters
$ LANG=C 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
116 321 2992 tfe5/tfenotebook.c
12 17 196 tfe5/tfenotebook.h
218 635 7769 tfe5/tfetextview.c
29 49 561 tfe5/tfetextview.h
64 105 2266 tfe5/tfe.ui
576 1507 17864 total
Up: [Readme.md](Readme.md), Prev: [Section 14](sec14.md), Next: [Section 16](sec16.md)

553
sec16.md
View file

@ -1,373 +1,218 @@
Up: [Readme.md](Readme.md), Prev: [Section 15](sec15.md), Next: [Section 17](sec17.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_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 original value, use g\_variant\_get series functions.
For example, you can get the boolean value by g\_variant_get_boolean.
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_strdup_printf` 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 identifiable 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 15](sec15.md), Next: [Section 17](sec17.md)

606
sec17.md
View file

@ -1,312 +1,312 @@
Up: [Readme.md](Readme.md), Prev: [Section 16](sec16.md)
Up: [Readme.md](Readme.md), Prev: [Section 16](sec16.md), Next: [Section 18](sec18.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_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 original value, use g\_variant\_get series functions.
For example, you can get the boolean value by g\_variant_get_boolean.
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 bothersome work as well.
Therefore, it should be automated.
You can implement them easily with GActionEntry structure and `g_action_map_add_action_entries` function.
## Stateful action with a parameter
GActionEntry 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_strdup_printf` 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.
The same goes for the other actions.
When GVariantType is generated, the type is expressed by the string.
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);
- "b" means boolean type.
- "s" means string type.
The code above does:
The following program is a simple example.
It finally output the string "s".
- Build a "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 actions to the action map `win`.
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
@ -314,28 +314,60 @@ 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
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)
- 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 identifiable 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.
Up: [Readme.md](Readme.md), Prev: [Section 16](sec16.md)
Up: [Readme.md](Readme.md), Prev: [Section 16](sec16.md), Next: [Section 18](sec18.md)

341
sec18.md Normal file
View file

@ -0,0 +1,341 @@
Up: [Readme.md](Readme.md), Prev: [Section 17](sec17.md)
# Ui file for menu and action entries
## 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 bothersome work as well.
Therefore, it should be automated.
You can implement them easily with GActionEntry structure and `g_action_map_add_action_entries` function.
GActionEntry 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`.
The same goes for the other actions.
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 a "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 actions 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 17](sec17.md)

352
sec2.md
View file

@ -1,287 +1,173 @@
Up: [Readme.md](Readme.md), Prev: [Section 1](sec1.md), Next: [Section 3](sec3.md)
# GtkApplication and GtkApplicationWindow
# Installation of gtk4 to linux distributions
## GtkApplication
### GtkApplication and g\_application\_run
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 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
The first line says that this program includes the header files of the Gtk 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 object and sets its pointer to `app`.
The meaning of the arguments will be explained later.
The function `g_application_run` invokes the GtkApplication object 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 above.
$ 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 subsection.
### 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 these.
These two causes 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 is invoked 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 inherits 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.
This section describes how to install gtk4 into linux distributions.
However, I only have an experience to install it to ubuntu 20.10.
Probably you need more than the explanation below.
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.
This tutorial including this section is without any warranty.
If you install gtk4 to your computer, do it at your own risk.
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.
They are located in ['GNOME Developer Center'](https://developer.gnome.org/).
## Prerequisite
Let's compile the source file above (`pr2.c`) and run it.
- Ubuntu 20.10. Maybe other versions of late years or other distribution might be OK.
- Packages for development such as gcc, meson, ninja, git, wget and so on.
- Dev packages necessary for each software below.
$ gcc `pkg-config --cflags gtk4` pr2.c `pkg-config --libs gtk4`
$ ./a.out
GtkApplication is activated.
$
## Installation target
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 line.
I installed gtk4 under the directory `$HOME/local`.
This is a private user area.
gcc `pkg-config --cflags gtk4` $1.c `pkg-config --libs gtk4`
Don't install it to `/usr/local` which is the default.
It is used by ubuntu applications, which are not build on gtk4.
Therefore, the risk is high and probably bad things will happen.
Actually I did it and I needed to reinstall ubuntu.
Then, save it under the directory $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.
## Glib installation
$ chmod 755 $HOME/bin/comp
$ ls -log $HOME/bin
... ... ...
-rwxr-xr-x 1 62 May 23 08:21 comp
... ... ...
Ubuntu includes glib but its version is not high enough to build gtk4.
Glib 2.66.0 or higher is required.
At present (Jan/2021), its latest version is 2.67.2.
I installed 2.67.1 which was the latest version at that time.
Download glib source files from the repository, then decompress and extract files.
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.
$ wget https://download.gnome.org/sources/glib/2.67/glib-2.67.1.tar.xz
$ tar -Jxf glib-2.67.1.tar.xz
$ comp pr2
$ ./a.out
GtkApplication is activated.
$
Some packages are required to build glib.
You can find them if you run meson.
## GtkWindow and GtkApplicationWindow
$ meson --prefix $HOME/local _build
### GtkWindow
Use apt-get and install the prerequisites.
For example,
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:
$ sudo apt-get install -y libpcre2-dev libffi-dev
1. Generate a GtkWindow.
2. Connect it to GtkApplication.
3. Show the window.
After that, compile glib.
Now rewrite the function `on_activate`.
$ rm -rf _build
$ meson --prefix $HOME/local _build
$ ninja -C _build
$ ninja -C _build install
#### Generate a GtkWindow
Set sevral environment variables so that the glib libraries installed can be used by build tools.
Make a text file below and save it as `env.sh`
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 }
# compiler
CPPFLAGS="-I$HOME/local/include"
LDFLAGS="-L$HOME/local/lib"
PKG_CONFIG_PATH="$HOME/local/lib/pkgconfig:$HOME/local/lib/x86_64-linux-gnu/pkgconfig"
export CPPFLAGS LDFLAGS PKG_CONFIG_PATH
# linker
LD_LIBRARY_PATH="$HOME/local/lib/x86_64-linux-gnu/"
PATH="$HOME/local/bin:$PATH"
export LD_LIBRARY_PATH PATH
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.
Then, use . (dot) or source command to include these commands to the current bash.
parent <-----> child
GtkWidget -- GtkWindow
$ . env.sh
GtkWindow includes GtkWidget at the top of its object.
or
![GtkWindow and GtkWidget](image/window_widget.png)
$ source env.sh
The function `gtk_window_new` is defined as follows.
This command carries out the commands in `env.sh` and changes the environment variables above in the corrent shell.
GtkWidget *
gtk_window_new (void);
## Pango installation
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.
Download and untar.
If you want to use `win` as a pointer to the GtkWindow, you need to cast it.
$ wget https://download.gnome.org/sources/pango/1.48/pango-1.48.0.tar.xz
$ tar -Jxf pango-1.48.0.tar.xz
(GtkWindow *) win
Try meson and check the required packages.
Install all the prerequisites.
Then, compile and install pango.
Or you can use `GTK_WINDOW` macro that performs a similar function.
$ meson --prefix $HOME/local _build
$ ninja -C _build
$ ninja -C _build install
GTK_WINDOW (win)
It installs Pnago-1.0.gir under `$HOME/local/share/gir-1.0`.
If you installed pango without --prefix option, then it would be located at `/usr/local/share/gir-1.0`.
This directory (/usr/local/share) is used by applications.
They find the directory by the environment variable `XDG_DATA_DIRS`.
It is a text file which keep the list of 'share' directoryes like `/usr/share`, `usr/local/share` and so on.
Now `$HOME/local/share` needs to be added to `XDG_DATA_DIRS`, or error will occur in the later compilation.
This is a recommended way.
$ export XDG_DATA_DIRS=$HOME/local/share:$XDG_DATA_DIRS
#### Connect it to GtkApplication.
## Gdk-pixbuf and gtk-doc installation
The function `gtk_window_set_application` is used to connect GtkWidow to GtkApplication.
Download and untar.
gtk_window_set_application (GTK_WINDOW (win), GTK_APPLICATION (app));
$ wget https://download.gnome.org/sources/gdk-pixbuf/2.42/gdk-pixbuf-2.42.2.tar.xz
$ tar -Jxf gdk-pixbuf-2.42.2.tar.xz
$ wget https://download.gnome.org/sources/gtk-doc/1.33/gtk-doc-1.33.1.tar.xz
$ tar -Jxf gtk-doc-1.33.1.tar.xz
You need to cast `win` to GtkWindow and `app` to GtkApplication.
`GTK_WINDOW` and `GTK_APPLICATION` macro is appropriate for that.
Same as before, install prerequisite packages, then compile and install them.
GtkApplication continues to run until the related window is destroyed.
If you didn't connect GtkWindow and GtkApplication, GtkApplication shutdowns immediately.
Because no window is connected to GtkApplication, it doesn't need to wait anything.
As it shutdowns the generated window is also destroyed.
The installation of gtk-doc put `gtk-doc.pc` under `$HOME/local/share/pkgconfig`.
This file is used by pkg-config, which is one of the build tools.
The directory needs to be added to the environment variable `PKG_CONFIG_PATH`
#### Show the window.
$ export PKG_CONFIG_PATH="$HOME/local/share/pkgconfig:$PKG_CONFIG_PATH"
The function `gtk_widget_show` is used to show the window.
## Gtk4 installation
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.
If you want the latest development version of gtk4, use git and clone the repository.
Save the program as `pr3.c` and compile and run it.
$ git clone https://gitlab.gnome.org/GNOME/gtk.git
$ comp pr3
$ ./a.out
If you want a stable version of gtk4, then download it from [Gnome source website](https://download.gnome.org/sources/gtk/4.0/).
A small window appears.
Compile and install it.
![Screenshot of the window](image/screenshot_pr3.png)
$ meson --prefix $HOME/local _build
$ ninja -C _build
$ ninja -C _build install
Click on the close button then the window disappears and the program finishes.
## Modify env.sh
### GtkApplicationWindow
Because environment variables disappear when you log out, you need to add them again.
Modify `env.sh`.
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.
# compiler
CPPFLAGS="-I$HOME/local/include"
LDFLAGS="-L$HOME/local/lib"
PKG_CONFIG_PATH="$HOME/local/lib/pkgconfig:$HOME/local/lib/x86_64-linux-gnu/pkgconfig:$HOME/local/share/pkgconfig"
export CPPFLAGS LDFLAGS PKG_CONFIG_PATH
# linker
LD_LIBRARY_PATH="$HOME/local/lib/x86_64-linux-gnu/"
PATH="$HOME/local/bin:$PATH"
export LD_LIBRARY_PATH PATH
# gir
XDG_DATA_DIRS=$HOME/local/share:$XDG_DATA_DIRS
export XDG_DATA_DIRS
Now rewrite the program and use GtkAppliction Window.
Include this file by . (dot) command before using gtk4 libraries.
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 }
You may think you can add them in your `.profile`.
I think the environment variables above are necessary only when you compile gtk4 applications.
And it's not necessary except the case above and it might cause some bad things.
Therefore, I recommend you not to write them to your `.profile`.
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.
## Compiling gtk4 applications
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".
Before you compile gtk4 applications, define environment variables above.
$ . env.sh
After that you can compile them without anything.
For example, to compile `sample.c`, type the following.
$ gcc `pkg-config --cflags gtk4` sample.c `pkg-config --libs gtk4`
To know how to compile gtk4 applications, refer to the following sections.
![Screenshot of the window](image/screenshot_pr4.png)
Up: [Readme.md](Readme.md), Prev: [Section 1](sec1.md), Next: [Section 3](sec3.md)

559
sec3.md
View file

@ -1,318 +1,287 @@
Up: [Readme.md](Readme.md), Prev: [Section 2](sec2.md), Next: [Section 4](sec4.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 section.
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), "lb1");
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.
$ cd misc; diff pr4.c lb1.c
5a6
> GtkWidget *lab;
8c9
< gtk_window_set_title (GTK_WINDOW (win), "pr4");
---
> gtk_window_set_title (GTK_WINDOW (win), "lb1");
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 click_cb (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), "lb2");
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 (click_cb), 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) `click_cb`.
So, if `btn` is clicked, the function `click_cb` is invoked.
The suffix cb means "call back".
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 click_cb (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), "lb3");
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 (click_cb), win);
19
20 gtk_widget_show (win);
21 }
And the difference between `lb2.c` and `lb3.c` is as follows.
$ cd misc; diff lb2.c lb3.c
5c5,6
< g_print ("Clicked.\n");
---
> GtkWindow *win = GTK_WINDOW (user_data);
> gtk_window_destroy (win);
14c15
< gtk_window_set_title (GTK_WINDOW (win), "lb2");
---
> gtk_window_set_title (GTK_WINDOW (win), "lb3");
17c18
< btn = gtk_button_new_with_label ("Click me");
---
> btn = gtk_button_new_with_label ("Quit");
19c20
< g_signal_connect (btn, "clicked", G_CALLBACK (click_cb), NULL);
---
> g_signal_connect (btn, "clicked", G_CALLBACK (click_cb), 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);
35d35
<
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 click1_cb (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 click2_cb (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 header files of the Gtk 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 object and sets its pointer to `app`.
The meaning of the arguments will be explained later.
The function `g_application_run` invokes the GtkApplication object 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 above.
$ 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 subsection.
### 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 these.
These two causes 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 is invoked 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 inherits 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 (click1_cb), NULL);
37
38 btn2 = gtk_button_new_with_label ("Quit");
39 g_signal_connect (btn2, "clicked", G_CALLBACK (click2_cb), 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 }
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 the size 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.
They are located in ['GNOME Developer Center'](https://developer.gnome.org/).
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 above (`pr2.c`) 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 line.
gcc `pkg-config --cflags gtk4` $1.c `pkg-config --libs gtk4`
Then, save it under the directory $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 immediately.
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 2](sec2.md), Next: [Section 4](sec4.md)

435
sec4.md
View file

@ -1,167 +1,318 @@
Up: [Readme.md](Readme.md), Prev: [Section 3](sec3.md), Next: [Section 5](sec5.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 section.
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 cut 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), "lb1");
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.
$ cd misc; diff pr4.c lb1.c
5a6
> GtkWidget *lab;
8c9
< gtk_window_set_title (GTK_WINDOW (win), "pr4");
---
> gtk_window_set_title (GTK_WINDOW (win), "lb1");
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 click_cb (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), "lb2");
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 (click_cb), 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) `click_cb`.
So, if `btn` is clicked, the function `click_cb` is invoked.
The suffix cb means "call back".
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 click_cb (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), "lb3");
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 (click_cb), win);
19
20 gtk_widget_show (win);
21 }
And the difference between `lb2.c` and `lb3.c` is as follows.
$ cd misc; diff lb2.c lb3.c
5c5,6
< g_print ("Clicked.\n");
---
> GtkWindow *win = GTK_WINDOW (user_data);
> gtk_window_destroy (win);
14c15
< gtk_window_set_title (GTK_WINDOW (win), "lb2");
---
> gtk_window_set_title (GTK_WINDOW (win), "lb3");
17c18
< btn = gtk_button_new_with_label ("Click me");
---
> btn = gtk_button_new_with_label ("Quit");
19c20
< g_signal_connect (btn, "clicked", G_CALLBACK (click_cb), NULL);
---
> g_signal_connect (btn, "clicked", G_CALLBACK (click_cb), 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);
35d35
<
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 click1_cb (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 click2_cb (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 (click1_cb), NULL);
37
38 btn2 = gtk_button_new_with_label ("Quit");
39 g_signal_connect (btn2, "clicked", G_CALLBACK (click2_cb), 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 }
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 the size 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 characters 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 and it shows that there exists a bug in the program.
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 GtkTextView as a child of GtkScrolledWindow.
Modify `tfv1.c` and save it as `tfv2.c`.
The difference between these two files is very little.
$ cd tfv; 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 cut 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 3](sec3.md), Next: [Section 5](sec5.md)

404
sec5.md
View file

@ -1,307 +1,167 @@
Up: [Readme.md](Readme.md), Prev: [Section 4](sec4.md), Next: [Section 6](sec6.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 cut 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 characters 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 and it shows that there exists a bug in the program.
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 connects 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 GtkTextView 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.
$ cd tfv; 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 destroys 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 cut 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 the 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 4](sec4.md), Next: [Section 6](sec6.md)

585
sec6.md
View file

@ -1,352 +1,307 @@
Up: [Readme.md](Readme.md), Prev: [Section 5](sec5.md), Next: [Section 7](sec7.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 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 object derives everything from the parent 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 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 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 connects 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 destroys the window.
GFile *
tfe_text_view_get_file (TfeTextView *tv) {
return tv -> file;
}
## GtkNotebook
GtkWidget *
tfe_text_view_new (void) {
return GTK_WIDGET (g_object_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 (upper case), object (upper case with underscore) and parent object name (camel case).
- 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 g\_object\_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.
And the return value must be casted to GtkWidget.
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
After editing a file, `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 it handler.
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 the 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 for 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 char *contents;
10 unsigned int n;
11 unsigned int 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 }
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 had run. 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 (g_object_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 char *contents;
50 unsigned int n;
51 unsigned int 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.
- 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.
Up: [Readme.md](Readme.md), Prev: [Section 5](sec5.md), Next: [Section 7](sec7.md)

719
sec7.md
View file

@ -1,455 +1,352 @@
Up: [Readme.md](Readme.md), Prev: [Section 6](sec6.md), Next: [Section 8](sec8.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 win = gtk_application_window_new (GTK_APPLICATION (app));
26 gtk_window_set_title (GTK_WINDOW (win), "file editor");
27 gtk_window_set_default_size (GTK_WINDOW (win), 600, 400);
28
29 boxv = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
30 gtk_window_set_child (GTK_WINDOW (win), boxv);
31
32 boxh = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
33 gtk_box_append (GTK_BOX (boxv), boxh);
34
35 dmy1 = gtk_label_new(NULL); /* dummy label for left space */
36 gtk_label_set_width_chars (GTK_LABEL (dmy1), 10);
37 dmy2 = gtk_label_new(NULL); /* dummy label for center space */
38 gtk_widget_set_hexpand (dmy2, TRUE);
39 dmy3 = gtk_label_new(NULL); /* dummy label for right space */
40 gtk_label_set_width_chars (GTK_LABEL (dmy3), 10);
41 btnn = gtk_button_new_with_label ("New");
42 btno = gtk_button_new_with_label ("Open");
43 btns = gtk_button_new_with_label ("Save");
44 btnc = gtk_button_new_with_label ("Close");
45
46 gtk_box_append (GTK_BOX (boxh), dmy1);
47 gtk_box_append (GTK_BOX (boxh), btnn);
48 gtk_box_append (GTK_BOX (boxh), btno);
49 gtk_box_append (GTK_BOX (boxh), dmy2);
50 gtk_box_append (GTK_BOX (boxh), btns);
51 gtk_box_append (GTK_BOX (boxh), btnc);
52 gtk_box_append (GTK_BOX (boxh), dmy3);
53
54 nb = gtk_notebook_new ();
55 gtk_widget_set_hexpand (nb, TRUE);
56 gtk_widget_set_vexpand (nb, TRUE);
57 gtk_box_append (GTK_BOX (boxv), nb);
58
59 for (i = 0; i < n_files; i++) {
60 if (g_file_load_contents (files[i], NULL, &contents, &length, NULL, NULL)) {
61 scr = gtk_scrolled_window_new ();
62 tv = tfe_text_view_new ();
63 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
64 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
65 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
66
67 tfe_text_view_set_file (TFE_TEXT_VIEW (tv), g_file_dup (files[i]));
68 gtk_text_buffer_set_text (tb, contents, length);
69 g_free (contents);
70 filename = g_file_get_basename (files[i]);
71 lab = gtk_label_new (filename);
72 gtk_notebook_append_page (GTK_NOTEBOOK (nb), scr, lab);
73 nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr);
74 g_object_set (nbp, "tab-expand", TRUE, NULL);
75 g_free (filename);
76 } else {
77 filename = g_file_get_path (files[i]);
78 g_print ("No such file: %s.\n", filename);
79 g_free (filename);
80 }
81 }
82 if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) {
83 gtk_widget_show (win);
84 } else
85 gtk_window_destroy (GTK_WINDOW (win));
86 }
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.
- 25-27: Generate GtkApplicationWindow and set its title and default size.
- 29-30: 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.
- 32-33: Generate GtkBox `boxh` and append it to 'boxv' as a first child.
- 35-40: 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.
- 41-44: Generate four buttons.
- 46-52: Append these GtkLabel and GtkButton to `boxh`.
- 54-57: 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(=57-25+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 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 object derives everything from the parent 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 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 25-57 in the source code of `on_open` function.
Those two decribe the same structure of widgets.
Let's define TfeTextView 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) {
}
$ cd tfe; diff tfe2.c tfe3.c
58a59
> GtkBuilder *build;
60,103c61,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);
138c100
< 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;
}
`60,103c61,65` means 42 (=103-60+1) lines change to 5 (=65-61+1) lines.
Therefore 37 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 (g_object_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.
The source code of `tfe3.c` is stored in [src/tfe](src/tfe) directory.
If you want to see it, click the link above.
In the same way, you can get the source files below in the directory [src/tfe](src/tfe).
- 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 (upper case), object (upper case with underscore) and parent object name (camel case).
- 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 g\_object\_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.
And the return value must be casted to GtkWidget.
### Using ui string
This program is not perfect.
It has some problem.
But I don't discuss it now.
It will be modified later.
GtkBuilder can build widgets using string.
Use the function gtk\_builder\_new\_from\_string instead of gtk\_builder\_new\_from\_file.
## Close-request signal
char *uistring;
After editing a file, `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 it handler.
The function `before_close` is invoked when the signal "close-request" is emittd.
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>";
g_signal_connect (win, "close-request", G_CALLBACK (before_close), NULL);
build = gtk_builder_new_from_stringfile (uistring);
The argument win is GtkApplicationWindow, in which the signal "close-request" is defined, and before\_close is the handler.
`G_CALLBACK` cast is necessary for the handler.
The program of before\_close 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.
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 char *contents;
10 unsigned int n;
11 unsigned int 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 }
- add backslash before each double quote.
- add double quote at the left and right.
The numbers on the left of items are line numbers in the source code.
### Using Gresource
- 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 had run. 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.
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.
## Source code of tfe1.c
An xml file is necessary for the resource compiler `glib-compile-resources`.
It describes resource files.
Now I will show you all the 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>
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 (g_object_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 char *contents;
50 unsigned int n;
51 unsigned int 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
- 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.
- 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.
Save this xml text to `tfe3.gresource.xml`.
The gresource compiler `glib-compile-resources` shows its usage with the argument `--help`.
Now compile and run it.
Type `./a.out somefile` and make sure that the file is modified.
$ 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 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 6](sec6.md), Next: [Section 8](sec8.md)

701
sec8.md
View file

@ -1,173 +1,139 @@
Up: [Readme.md](Readme.md), Prev: [Section 7](sec7.md), Next: [Section 9](sec9.md)
# Build system
# Ui file and GtkBuiler
## What do we need to think about to manage big source files?
## 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)
These ideas are useful to manage big source files.
The screenshot above shows the layout.
The function `on_open` in the source code `tfe2.c` is as follows.
## Divide a C source file into two parts.
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 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`.
All the source files are listed below.
`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 }
29
30 GtkWidget *
31 tfe_text_view_new (void) {
32 return GTK_WIDGET (g_object_new (TFE_TYPE_TEXT_VIEW, NULL));
33 }
25 win = gtk_application_window_new (GTK_APPLICATION (app));
26 gtk_window_set_title (GTK_WINDOW (win), "file editor");
27 gtk_window_set_default_size (GTK_WINDOW (win), 600, 400);
28
29 boxv = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
30 gtk_window_set_child (GTK_WINDOW (win), boxv);
31
32 boxh = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
33 gtk_box_append (GTK_BOX (boxv), boxh);
34
35 dmy1 = gtk_label_new(NULL); /* dummy label for left space */
36 gtk_label_set_width_chars (GTK_LABEL (dmy1), 10);
37 dmy2 = gtk_label_new(NULL); /* dummy label for center space */
38 gtk_widget_set_hexpand (dmy2, TRUE);
39 dmy3 = gtk_label_new(NULL); /* dummy label for right space */
40 gtk_label_set_width_chars (GTK_LABEL (dmy3), 10);
41 btnn = gtk_button_new_with_label ("New");
42 btno = gtk_button_new_with_label ("Open");
43 btns = gtk_button_new_with_label ("Save");
44 btnc = gtk_button_new_with_label ("Close");
45
46 gtk_box_append (GTK_BOX (boxh), dmy1);
47 gtk_box_append (GTK_BOX (boxh), btnn);
48 gtk_box_append (GTK_BOX (boxh), btno);
49 gtk_box_append (GTK_BOX (boxh), dmy2);
50 gtk_box_append (GTK_BOX (boxh), btns);
51 gtk_box_append (GTK_BOX (boxh), btnc);
52 gtk_box_append (GTK_BOX (boxh), dmy3);
53
54 nb = gtk_notebook_new ();
55 gtk_widget_set_hexpand (nb, TRUE);
56 gtk_widget_set_vexpand (nb, TRUE);
57 gtk_box_append (GTK_BOX (boxv), nb);
58
59 for (i = 0; i < n_files; i++) {
60 if (g_file_load_contents (files[i], NULL, &contents, &length, NULL, NULL)) {
61 scr = gtk_scrolled_window_new ();
62 tv = tfe_text_view_new ();
63 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
64 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
65 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
66
67 tfe_text_view_set_file (TFE_TEXT_VIEW (tv), g_file_dup (files[i]));
68 gtk_text_buffer_set_text (tb, contents, length);
69 g_free (contents);
70 filename = g_file_get_basename (files[i]);
71 lab = gtk_label_new (filename);
72 gtk_notebook_append_page (GTK_NOTEBOOK (nb), scr, lab);
73 nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr);
74 g_object_set (nbp, "tab-expand", TRUE, NULL);
75 g_free (filename);
76 } else {
77 filename = g_file_get_path (files[i]);
78 g_print ("No such file: %s.\n", filename);
79 g_free (filename);
80 }
81 }
82 if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) {
83 gtk_widget_show (win);
84 } else
85 gtk_window_destroy (GTK_WINDOW (win));
86 }
`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 ();
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
- 25-27: Generate GtkApplicationWindow and set its title and default size.
- 29-30: 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.
- 32-33: Generate GtkBox `boxh` and append it to 'boxv' as a first child.
- 35-40: 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.
- 41-44: Generate four buttons.
- 46-52: Append these GtkLabel and GtkButton to `boxh`.
- 54-57: 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(=57-25+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">
@ -229,200 +195,261 @@ All the source files are listed below.
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 25-57 in the source code of `on_open` function.
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.
$ cd tfe; diff tfe2.c tfe3.c
58a59
> GtkBuilder *build;
60,103c61,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);
138c100
< app = gtk_application_new ("com.github.ToshioCP.tfe2", G_APPLICATION_HANDLES_OPEN);
---
> app = gtk_application_new ("com.github.ToshioCP.tfe3", G_APPLICATION_HANDLES_OPEN);
`60,103c61,65` means 42 (=103-60+1) lines change to 5 (=65-61+1) lines.
Therefore 37 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 }
The source code of `tfe3.c` is stored in [src/tfe](https://github.com/ToshioCP/Gtk4-tutorial/tree/main/src/tfe) directory.
If you want to see it, click the link above.
In the same way, you can get the source files below in the directory [src/tfe](https://github.com/ToshioCP/Gtk4-tutorial/tree/main/src/tfe).
### 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 usage 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 -o 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
You only need 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 will be long to explain it.
So I want to finish explaining make and move on to the next topic.
## 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 the shell.
- 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` is generated under the directory `_build`.
$ _build/tfe tfe.c tfetextview.c
Then the window appears.
I've shown 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 7](sec7.md), Next: [Section 9](sec9.md)

747
sec9.md
View file

@ -1,421 +1,428 @@
Up: [Readme.md](Readme.md), Prev: [Section 8](sec8.md), Next: [Section 10](sec10.md)
# Instance and class
# Build system
This section and the following four sections are explanations about the next version of the text file editor (tfe).
It is tfe5.
It has many changes from the prior version.
All the sources are listed after the five sections.
## What do we need to think about to manage big source files?
## 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.
These ideas are useful to manage big source files.
However, first of all, I'd like to focus on the object TfeTextView.
It is a child object of GtkTextView and has a new member `file` in it.
The important thing is to manage the Gfile object pointed by `file`.
## Divide a C source file into two parts.
- 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?
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`.
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:
- `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
- Organizing functions.
- How to use FileChooserDialog
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 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.
## GObject and its children
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 are objects, which have both class and instance.
First, think about instance of objects.
Instance is structured memories and the structure is described as C language structure.
The following is a structure of TfeTextView.
All the source files are listed below.
/* This typedef statement is automaticaly generated by the macro G_DECLARE_FINAL_TYPE */
typedef struct _TfeTextView TfeTextView;
`tfetextview.h`
struct _TfeTextView {
GtkTextView parent;
GFile *file;
};
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);
14
The members of the structure are:
`tfetextview.c`
- `parent` is the structure of GtkTextView which is the parent object of TfeTextView.
- `file` is a pointer to GFile. It can be NULL if no file corresponds to the TfeTextView object.
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 (g_object_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 files.
1 static void
2 tfe_text_view_init (TfeTextView *tv) {
3 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
4
5 tv->file = NULL;
6 gtk_text_buffer_set_modified (tb, FALSE);
7 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
8 }
`tfe_text_view_init` initializes the instance.
- 3: Get the pointer to GtkTextBuffer and assign it to `tb`.
- 5: Initialize `tv->file = NULL`.
- 6: Set modified bit to FALSE. That means the GtkTextBuffer has not modified.
When the buffer is modified, it will automatically toggled on the modified bit.
Whenever the buffer is saved to disk, call gtk_text_buffer_set_modified (buffer , FALSE).
- 7: Set the wrap mode of GtkTextView as GTK\_WRAP\_WORD\_CHAR.
## 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.
Instance, which is structured memories, only keeps status of the object.
Therefore, it is 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 or Ruby.
Functions are public, which means that they are expected to be used by other objects.
Class comprises mainly pointers to functions.
Those functions are used by the object itself or its descendent 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 (g_object_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 ();
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. The first one is called disposing and the instance releases all the references to other instances. The second one is finalizing.
- 23: A funtion pointed by `finalize` finishes the destruction process.
- The other pointers point functions which are called while 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 the descendent 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 descendent 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 -o 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`.
You only need 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 will be long to explain it.
So I want to finish explaining make and move on to the next topic.
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 has been generated before TfeTextViewClass is generated.
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 the shell.
- 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 instance 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` is generated under the directory `_build`.
$ _build/tfe tfe.c tfetextview.c
Then the window appears.
I've shown 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 8](sec8.md), Next: [Section 10](sec10.md)

View file

@ -6,7 +6,7 @@
This 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.
However, this tutorial describes only _C programs on linux_.
If you want to try the examples in the tutorial, you need:
@ -15,9 +15,15 @@ If you want to try the examples in the tutorial, you need:
- 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.
Therefore, I strongly recommend you not to install gtk4 to the computer you usually use.
Instead, Install it to another computer only used to try gtk4.
However, it might make some trouble like, for example, your pc doesn't recognize usb port
if you install them to `/usr/local`.
Therefore, I strongly recommend you not to install gtk4 to `/usr/local` on the computer you usually use.
Instead,
- Install it to another computer only used to try gtk4.
- Install it to your home directory, for example `$HOME/local`, in order to separte gtk4 from your system.
The second choice will be explained in [Section 3](sec3.src.md).
### Software

View file

@ -1,135 +1,255 @@
# Signals
# Instance and class
## Signals
This section and the following four sections are explanations about the next version of the text file editor (tfe).
It is tfe5.
It has many changes from the prior version.
All the sources are listed after the five sections.
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 has a new member `file` in it.
The important thing is to manage the Gfile object pointed by `file`.
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 usually 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 as 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;
GFile *file;
};
static guint tfe_text_view_signals[NUMBER_OF_SIGNALS];
The members of the structure are:
Signal registration codes are written in the class initialization function.
- `parent` is the structure of GtkTextView which is the parent object of TfeTextView.
- `file` is a pointer to GFile. It can be NULL if no file corresponds to the TfeTextView object.
@@@ tfe5/tfetextview.c tfe_text_view_class_init
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).
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 has canceled to open a file.
- `TFE_OPEN_RESPONSE_ERROR` is set when error has 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 are some similar functions like `g_signal_connect_after`, `g_signal_connect_swapped` and so on.
However, `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){width=14.39cm height=2.16cm}
## 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 files.
@@@ tfe5/tfetextview.c tfe_text_view_init
`tfe_text_view_init` initializes the instance.
- 3: Get the pointer to GtkTextBuffer and assign it to `tb`.
- 5: Initialize `tv->file = NULL`.
- 6: Set modified bit to FALSE. That means the GtkTextBuffer has not modified.
When the buffer is modified, it will automatically toggled on the modified bit.
Whenever the buffer is saved to disk, call gtk_text_buffer_set_modified (buffer , FALSE).
- 7: Set the wrap mode of GtkTextView as GTK\_WRAP\_WORD\_CHAR.
## 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.
Instance, which is structured memories, only keeps status of the object.
Therefore, it is 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 or Ruby.
Functions are public, which means that they are expected to be used by other objects.
Class comprises mainly pointers to functions.
Those functions are used by the object itself or its descendent 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. The first one is called disposing and the instance releases all the references to other instances. The second one is finalizing.
- 23: A funtion pointed by `finalize` finishes the destruction process.
- The other pointers point functions which are called while 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 the descendent 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 descendent 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){width=16.02cm height=8.34cm}
## 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){width=15.855cm height=2.475cm}
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 has been generated before TfeTextViewClass is generated.
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){width=14.925cm height=4.455cm}
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 instance 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.

View file

@ -1,183 +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 usually 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).
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.
- 21-24: `tfe_text_view_new`.
Just returns the value from the function `g_object_new` but casted to the pointer to GtkWidget.
Initialization is done in `tfe_text_view_init` which is called in the process of `gtk_widget_new` function.
- 1-19: `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.
- 10-11: If an error occurs when reading the file, then return NULL.
- 13-18: Generate TfeTextView and set the pointer to it to `tv`.
The pointer to GtkTextBuffer is set to `tb`
Set the contents read from the file to GtkTextBuffer `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 has canceled to open a file.
- `TFE_OPEN_RESPONSE_ERROR` is set when error has 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 are some similar functions like `g_signal_connect_after`, `g_signal_connect_swapped` and so on.
However, `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);
- 18-55: `Tfe_text_view_save` function.
- 20: 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.
- 30-31: If the buffer hasn't modified, then it doesn't need to save it.
So the function returns.
- 32-33: If `tv->file` is NULL, no file has given yet.
It calls `tfe_text_view_saveas`, which lets the user to choose a file to save.
- 35-36: Get the contents of the GtkTextBuffer and set its pointer to `contents`.
- 37-38: Save the content to the file.
If it succeeds, reset the modified bit in the GtkTextBuffer.
- 39-53: If file writing fails, it assigns NULL to `tv->file`.
Emits "change-file" signal.
Shows the error message dialog (47-51).
Because the handler is `gtk_window_destroy`, the dialog disappears when user clicks on the button in the dialog.
- 57-70: `tfe_text_view_saveas` function.
It shows GtkFileChooserDialog and lets the user choose a file and give it to the signal handler.
- 64-67: 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.
- 68: connect the "response" signal of the dialog and `saveas_dialog_response` handler.
- 1-16: `saveas_dialog_response` signal handler.
- 6-14: 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`, turn on the modified bit of the GtkTextBuffer, emits "change-file" signal then call `tfe_text_view_save` to save the buffer to the file.
![Saveas process](../image/saveas.png){width=10.7cm height=5.16cm}
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 by calling `tfe_text_view_save`.
## Open function
Open function shows GtkFileChooserDialog to the user and let them choose a file.
Then read the file and set it to GtkTextBuffer.
void tfe_text_view_open (TfeTextView *tv, GtkWidget *win);
TfeTextView object `tv` has to be generated in advance.
This function is usually called just after `tv` has been generated.
And its buffer is empty, `tv->file` is NULL and `tv` has not set to the widget hierarchy.
Even if the buffer 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.
GtkWidget `win` is expected to be the top level window of the application.
It will be used as a transient parent window for the argument to the function `gtk_file_chooser_dialog_new`.
@@@ tfe5/tfetextview.c open_dialog_response tfe_text_view_open
- 36-49: `tfe_text_view_open` function.
- 43: Generate GtkFileChooserDialog.
The title is "Open file".
Ttransient parent window is the top window of the application, which is given by the caller.
The action is open mode.
The buttons are Cancel and Open.
- 47: connect the "reponse" signal of the dialog and `open_dialog_response` signal handler.
- 48: Show the dialog.
- 1-34: `open_dialog_response` signal handler.
- 10-11: 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`.
- 12-13: 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`.
- 14-23: If an error occurs when it 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-32: 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`.
- 33: 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){width=12.405cm height=9.225cm}
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 14](sec14.src.md).
- 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.

View file

@ -1,104 +1,183 @@
# 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` have 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.
- 10-11: `notebook_page_new` generates a new GtkNotebookPage and adds GtkScrolledWindow and TfeTextView on the page.
- 7-8: `notebook_page_new_with_file` generates a new GtkNotebookPage and adds GtkScrolledWindow and TfeTextView on the page. The file is read and set into GtkTextBuffer.
The GFile `file` is copied and set in the TfeTextView object.
- 4-5: `notebook_page_open` shows a file chooser dialog. Then, user chooses a file and the file is set into GtkTextBuffer.
- 1-2: `notebook_page_save` saves the contents in GtkTextBuffer into the file, which has been set in the TfeTextView.
@@@ tfe5/tfe.h
You probably find that the functions above are higher level functions of
`tfetextview.h` is a header file which describes the public functions in `tfetextview.c`.
- `tfe_text_view_new`
- `tfe_text_view_new_with_file`
- `tef_text_view_open`
- `tfe_text_view_save`
@@@ tfe5/tfetextview.h
respectively.
- 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.
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.
Each function will be explained later in this section.
Now let's look at each program of the functions.
## Functions to generate TfeTextView object
## notebook\_page\_new
TfeTextView Object is generated by `tfe_text_view_new` or `tfe_text_view_new_with_file`.
@@@ tfe5/tfenotebook.c get_untitled notebook_page_build notebook_page_new
GtkWidget *tfe_text_view_new (void);
- 27-37: `notebook_page_new` function.
- 29: `g_return_if_fail` is used to check the argument.
- 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 append it to 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.
`tfe_text_view_new` just generates a new TfeTextView object and returns the pointer to the new object.
## notebook\_page\_new\_with\_file
GtkWidget *tfe_text_view_new_with_file (GFile *file);
@@@ tfe5/tfenotebook.c notebook_page_new_with_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.
- 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.
Parameter:
## notebook\_page\_open
- `file`: a pointer to the GFile object.
@@@ tfe5/tfenotebook.c open_response notebook_page_open
Return value:
- 19-28: `notebook_page_open` function.
- 25: Generate TfeTextView object.
- 26: Connect the signal "open-response" and the handler `open_response`.
- 27: Call `tfe_text_view_open`.
It emits "open-response" signal to inform the status after the series of functions run.
- 1-17: `open_response` handler.
This is the post-function of `notebook_page_open`.
- 6-8: If the status is NOT `TFE_OPEN_RESPONSE_SUCCESS`, cancel what we did in `notebook_page_open`.
The object `tv` hasn't been a child widget of some other widget yet.
Such object has floating reference.
It needs to do `g_object_ref_sink` and clear the floating reference before `g_object_unref`.
- 9-11: If `tfe_text_view_get_file` returns a pointer not to point GFile, then something bad happens. Cancel what we did.
Sink and unref `tv`.
- 12-16: Otherwise, everything is okay.
Get the filename, build the contents of the page.
- 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.
## notebook\_page\_save
Each function is defined as follows.
@@@ tfe5/tfenotebook.c notebook_page_save
@@@ tfe5/tfetextview.c tfe_text_view_new_with_file tfe_text_view_new
- 7-9: Get TfeTextView belongs to the current notebook page.
- 10: Call `tfe_text_view_save`.
- 21-24: `tfe_text_view_new`.
Just returns the value from the function `g_object_new` but casted to the pointer to GtkWidget.
Initialization is done in `tfe_text_view_init` which is called in the process of `gtk_widget_new` function.
- 1-19: `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.
- 10-11: If an error occurs when reading the file, then return NULL.
- 13-18: Generate TfeTextView and set the pointer to it to `tv`.
The pointer to GtkTextBuffer is set to `tb`
Set the contents read from the file to GtkTextBuffer `tb`.
Free the memories pointed by `contents`.
Duplicate `file` and set it to `tv->file`.
Return `tv`.
## file\_changed handler
## Save and saveas functions
The function `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.
Save and saveas functions write the contents in GtkTextBuffer to a file.
@@@ tfe5/tfenotebook.c file_changed
void tfe_text_view_save (TfeTextView *tv)
- 8: Get GFile from TfeTextView.
- 9: Get GkScrolledWindow which is the parent widget of `tv`.
- 10-13: If `file` points GFile, then assign the filename of the GFile into `filename`.
Otherwise (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` 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
- 18-55: `Tfe_text_view_save` function.
- 20: 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.
- 30-31: If the buffer hasn't modified, then it doesn't need to save it.
So the function returns.
- 32-33: If `tv->file` is NULL, no file has given yet.
It calls `tfe_text_view_saveas`, which lets the user to choose a file to save.
- 35-36: Get the contents of the GtkTextBuffer and set its pointer to `contents`.
- 37-38: Save the content to the file.
If it succeeds, reset the modified bit in the GtkTextBuffer.
- 39-53: If file writing fails, it assigns NULL to `tv->file`.
Emits "change-file" signal.
Shows the error message dialog (47-51).
Because the handler is `gtk_window_destroy`, the dialog disappears when user clicks on the button in the dialog.
- 57-70: `tfe_text_view_saveas` function.
It shows GtkFileChooserDialog and lets the user choose a file and give it to the signal handler.
- 64-67: 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.
- 68: connect the "response" signal of the dialog and `saveas_dialog_response` handler.
- 1-16: `saveas_dialog_response` signal handler.
- 6-14: 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`, turn on the modified bit of the GtkTextBuffer, emits "change-file" signal then call `tfe_text_view_save` to save the buffer to the file.
![Saveas process](../image/saveas.png){width=10.7cm height=5.16cm}
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 by calling `tfe_text_view_save`.
## Open function
Open function shows GtkFileChooserDialog to the user and let them choose a file.
Then read the file and set it to GtkTextBuffer.
void tfe_text_view_open (TfeTextView *tv, GtkWidget *win);
TfeTextView object `tv` has to be generated in advance.
This function is usually called just after `tv` has been generated.
And its buffer is empty, `tv->file` is NULL and `tv` has not set to the widget hierarchy.
Even if the buffer 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.
GtkWidget `win` is expected to be the top level window of the application.
It will be used as a transient parent window for the argument to the function `gtk_file_chooser_dialog_new`.
@@@ tfe5/tfetextview.c open_dialog_response tfe_text_view_open
- 36-49: `tfe_text_view_open` function.
- 43: Generate GtkFileChooserDialog.
The title is "Open file".
Ttransient parent window is the top window of the application, which is given by the caller.
The action is open mode.
The buttons are Cancel and Open.
- 47: connect the "reponse" signal of the dialog and `open_dialog_response` signal handler.
- 48: Show the dialog.
- 1-34: `open_dialog_response` signal handler.
- 10-11: 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`.
- 12-13: 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`.
- 14-23: If an error occurs when it 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-32: 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`.
- 33: 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){width=12.405cm height=9.225cm}
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 15](sec15.src.md).

View file

@ -1,153 +1,104 @@
# 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` have 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.
- 10-11: `notebook_page_new` generates a new GtkNotebookPage and adds GtkScrolledWindow and TfeTextView on the page.
- 7-8: `notebook_page_new_with_file` generates a new GtkNotebookPage and adds GtkScrolledWindow and TfeTextView on the page. The file is read and set into GtkTextBuffer.
The GFile `file` is copied and set in the TfeTextView object.
- 4-5: `notebook_page_open` shows a file chooser dialog. Then, user chooses a file and the file is set into GtkTextBuffer.
- 1-2: `notebook_page_save` saves the contents in GtkTextBuffer into the file, which has been set in the TfeTextView.
@@@ tfe5/tfeapplication.c main
You probably find that the functions above are higher level functions of
- 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.
- `tfe_text_view_new`
- `tfe_text_view_new_with_file`
- `tef_text_view_open`
- `tfe_text_view_save`
## statup signal handler
respectively.
"startup" signal is emitted just after the application is generated.
What the signal handler needs to do is initialization of the application.
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.
- Build the widgets using ui file.
- Connect button signals and their handlers.
- Set CSS.
Now let's look at each program of the functions.
The handler is as follows.
## notebook\_page\_new
@@@ tfe5/tfeapplication.c tfe_startup
@@@ tfe5/tfenotebook.c get_untitled notebook_page_build notebook_page_new
- 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.
- 27-37: `notebook_page_new` function.
- 29: `g_return_if_fail` is used to check the argument.
- 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 append it to 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.
## CSS in GTK
## notebook\_page\_new\_with\_file
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 notebook_page_new_with_file
### CSS nodes, selectors
- 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.
The syntax of CSS is as follws.
## notebook\_page\_open
selector { color: yellow; padding-top: 10px; ...}
@@@ tfe5/tfenotebook.c open_response notebook_page_open
Every widget has CSS node.
For example GtkTextView has `textview` node.
If you want to set style to GtkTextView, set "textview" to the selector.
- 19-28: `notebook_page_open` function.
- 25: Generate TfeTextView object.
- 26: Connect the signal "open-response" and the handler `open_response`.
- 27: Call `tfe_text_view_open`.
It emits "open-response" signal to inform the status after the series of functions run.
- 1-17: `open_response` handler.
This is the post-function of `notebook_page_open`.
- 6-8: If the status is NOT `TFE_OPEN_RESPONSE_SUCCESS`, cancel what we did in `notebook_page_open`.
The object `tv` hasn't been a child widget of some other widget yet.
Such object has floating reference.
It needs to do `g_object_ref_sink` and clear the floating reference before `g_object_unref`.
- 9-11: If `tfe_text_view_get_file` returns a pointer not to point GFile, then something bad happens. Cancel what we did.
Sink and unref `tv`.
- 12-16: Otherwise, everything is okay.
Get the filename, build the contents of the page.
textview {color: yeallow; ...}
## notebook\_page\_save
Class, ID and some other things can be applied to the selector like Web CSS. Refer GTK4 API reference for further information.
@@@ tfe5/tfenotebook.c notebook_page_save
In line 30, the CSS is a string.
- 7-9: Get TfeTextView belongs to the current notebook page.
- 10: Call `tfe_text_view_save`.
textview {padding: 10px; font-family: monospace; font-size: 12pt;}
## file\_changed handler
- 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.
The function `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.
### GtkStyleContext, GtkCSSProvider and GdkDisplay
@@@ tfe5/tfenotebook.c file_changed
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`.
- 8: Get GFile from TfeTextView.
- 9: Get GkScrolledWindow which is the parent widget of `tv`.
- 10-13: If `file` points GFile, then assign the filename of the GFile into `filename`.
Otherwise (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`.
GtkCssProvider is an object which parses CSS in order to style widgets.
To apply your CSS to widgets, 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, we need to close the top level window and quit the application.
First, get the top level window and call `gtk_window_destroy`.
- 26-28: Otherwise, it removes the current page.
## meson.build
@@@ tfe5/meson.build
In this file, just the source file names are modified.

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 widgets, 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, we need to close the top level window and quit the application.
First, get 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
In this file, just the source file names are modified.
@@@ 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
$$$
LANG=C 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){width=5.985cm height=5.055cm}
@@@ 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){width=10.23cm height=3.57cm}
## 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.
@@@ menu/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){width=12.555cm height=3.285cm}
![Screenshot of menu1](../image/menu1_screenshot.png){width=6.0cm height=5.115cm}
## Total number of lines, words and charcters
$$$
LANG=C 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,258 +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){width=5.985cm height=5.055cm}
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){width=10.23cm height=3.57cm}
- 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_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.
@@@ menu/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){width=12.555cm height=3.285cm}
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 original value, use g\_variant\_get series functions.
For example, you can get the boolean value by g\_variant_get_boolean.
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_strdup_printf` 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".
@@@ menu/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){width=6.03cm height=5.115cm}
- 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.
@@@ menu/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 identifiable 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){width=6.0cm height=5.115cm}

View file

@ -1,147 +1,258 @@
# 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_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){width=6.0cm height=5.055cm}
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 original value, use g\_variant\_get series functions.
For example, you can get the boolean value by g\_variant_get_boolean.
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 bothersome work as well.
Therefore, it should be automated.
You can implement them easily with GActionEntry structure and `g_action_map_add_action_entries` function.
## Stateful action with a parameter
GActionEntry 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_strdup_printf` 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.
The same goes for the other actions.
When GVariantType is generated, the type is expressed by the string.
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);
- "b" means boolean type.
- "s" means string type.
The code above does:
The following program is a simple example.
It finally output the string "s".
- Build a "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 actions to the action map `win`.
@@@ menu/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){width=6.03cm height=5.115cm}
@@@ 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.
@@@ menu3/meson.build
@@@ menu/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 identifiable 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.

147
src/sec18.src.md Normal file
View file

@ -0,0 +1,147 @@
# Ui file for menu and action entries
## 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){width=6.0cm height=5.055cm}
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 bothersome work as well.
Therefore, it should be automated.
You can implement them easily with GActionEntry structure and `g_action_map_add_action_entries` function.
GActionEntry 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`.
The same goes for the other actions.
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 a "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 actions 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,238 +1,169 @@
# GtkApplication and GtkApplicationWindow
# Installation of gtk4 to linux distributions
## GtkApplication
This section describes how to install gtk4 into linux distributions.
However, I only have an experience to install it to ubuntu 20.10.
Probably you need more than the explanation below.
### GtkApplication and g\_application\_run
This tutorial including this section is without any warranty.
If you install gtk4 to your computer, do it at your own risk.
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.
## Prerequisite
The basic way how to write GtkApplication is as follows.
- Ubuntu 20.10. Maybe other versions of late years or other distribution might be OK.
- Packages for development such as gcc, meson, ninja, git, wget and so on.
- Dev packages necessary for each software below.
- Generate a GtkApplication object
- Run it
## Installation target
That's all.
Very simple.
The following is the C code representing the scenario above.
I installed gtk4 under the directory `$HOME/local`.
This is a private user area.
@@@ misc/pr1.c
Don't install it to `/usr/local` which is the default.
It is used by ubuntu applications, which are not build on gtk4.
Therefore, the risk is high and probably bad things will happen.
Actually I did it and I needed to reinstall ubuntu.
The first line says that this program includes the header files of the Gtk 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 object and sets its pointer to `app`.
The meaning of the arguments will be explained later.
The function `g_application_run` invokes the GtkApplication object 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.)
## Glib installation
To compile this, the following command needs to be run.
The string pr1.c is the filename of the C source code above.
Ubuntu includes glib but its version is not high enough to build gtk4.
Glib 2.66.0 or higher is required.
At present (Jan/2021), its latest version is 2.67.2.
I installed 2.67.1 which was the latest version at that time.
Download glib source files from the repository, then decompress and extract files.
$ gcc `pkg-config --cflags gtk4` pr1.c `pkg-config --libs gtk4`
$ wget https://download.gnome.org/sources/glib/2.67/glib-2.67.1.tar.xz
$ tar -Jxf glib-2.67.1.tar.xz
The C compiler gcc generates an executable file `a.out`.
Let's run it.
Some packages are required to build glib.
You can find them if you run meson.
$ ./a.out
$ meson --prefix $HOME/local _build
(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.
$
Use apt-get and install the prerequisites.
For example,
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 subsection.
$ sudo apt-get install -y libpcre2-dev libffi-dev
### signal
After that, compile glib.
The message tells us that:
$ rm -rf _build
$ meson --prefix $HOME/local _build
$ ninja -C _build
$ ninja -C _build install
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 these.
Set sevral environment variables so that the glib libraries installed can be used by build tools.
Make a text file below and save it as `env.sh`
These two causes of the error are related to signals.
So, I will explain it to you first.
# compiler
CPPFLAGS="-I$HOME/local/include"
LDFLAGS="-L$HOME/local/lib"
PKG_CONFIG_PATH="$HOME/local/lib/pkgconfig:$HOME/local/lib/x86_64-linux-gnu/pkgconfig"
export CPPFLAGS LDFLAGS PKG_CONFIG_PATH
# linker
LD_LIBRARY_PATH="$HOME/local/lib/x86_64-linux-gnu/"
PATH="$HOME/local/bin:$PATH"
export LD_LIBRARY_PATH PATH
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 is invoked when the signal emits.
The flow is like this:
Then, use . (dot) or source command to include these commands to the current bash.
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.
$ . env.sh
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.
or
GObject -- GApplication -- GtkApplication
<---parent --->child
$ source env.sh
A child object inherits signals, functions, properties and so on from its parent object.
So, Gtkapplication also has the "activate" signal.
This command carries out the commands in `env.sh` and changes the environment variables above in the corrent shell.
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.
## Pango installation
@@@ misc/pr2.c
Download and untar.
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.
$ wget https://download.gnome.org/sources/pango/1.48/pango-1.48.0.tar.xz
$ tar -Jxf pango-1.48.0.tar.xz
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.
Try meson and check the required packages.
Install all the prerequisites.
Then, compile and install pango.
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.
$ meson --prefix $HOME/local _build
$ ninja -C _build
$ ninja -C _build install
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.
They are located in ['GNOME Developer Center'](https://developer.gnome.org/).
It installs Pnago-1.0.gir under `$HOME/local/share/gir-1.0`.
If you installed pango without --prefix option, then it would be located at `/usr/local/share/gir-1.0`.
This directory (/usr/local/share) is used by applications.
They find the directory by the environment variable `XDG_DATA_DIRS`.
It is a text file which keep the list of 'share' directoryes like `/usr/share`, `usr/local/share` and so on.
Now `$HOME/local/share` needs to be added to `XDG_DATA_DIRS`, or error will occur in the later compilation.
Let's compile the source file above (`pr2.c`) and run it.
$ export XDG_DATA_DIRS=$HOME/local/share:$XDG_DATA_DIRS
$ gcc `pkg-config --cflags gtk4` pr2.c `pkg-config --libs gtk4`
$ ./a.out
GtkApplication is activated.
$
## Gdk-pixbuf and gtk-doc installation
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 line.
Download and untar.
gcc `pkg-config --cflags gtk4` $1.c `pkg-config --libs gtk4`
$ wget https://download.gnome.org/sources/gdk-pixbuf/2.42/gdk-pixbuf-2.42.2.tar.xz
$ tar -Jxf gdk-pixbuf-2.42.2.tar.xz
$ wget https://download.gnome.org/sources/gtk-doc/1.33/gtk-doc-1.33.1.tar.xz
$ tar -Jxf gtk-doc-1.33.1.tar.xz
Then, save it under the directory $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.
Same as before, install prerequisite packages, then compile and install them.
$ chmod 755 $HOME/bin/comp
$ ls -log $HOME/bin
... ... ...
-rwxr-xr-x 1 62 May 23 08:21 comp
... ... ...
The installation of gtk-doc put `gtk-doc.pc` under `$HOME/local/share/pkgconfig`.
This file is used by pkg-config, which is one of the build tools.
The directory needs to be added to the environment variable `PKG_CONFIG_PATH`
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.
$ export PKG_CONFIG_PATH="$HOME/local/share/pkgconfig:$PKG_CONFIG_PATH"
$ comp pr2
$ ./a.out
GtkApplication is activated.
$
## Gtk4 installation
## GtkWindow and GtkApplicationWindow
If you want the latest development version of gtk4, use git and clone the repository.
### GtkWindow
$ git clone https://gitlab.gnome.org/GNOME/gtk.git
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:
If you want a stable version of gtk4, then download it from [Gnome source website](https://download.gnome.org/sources/gtk/4.0/).
1. Generate a GtkWindow.
2. Connect it to GtkApplication.
3. Show the window.
Compile and install it.
Now rewrite the function `on_activate`.
$ meson --prefix $HOME/local _build
$ ninja -C _build
$ ninja -C _build install
#### Generate a GtkWindow
## Modify env.sh
@@@ misc/pr3.c on_activate
Because environment variables disappear when you log out, you need to add them again.
Modify `env.sh`.
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.
# compiler
CPPFLAGS="-I$HOME/local/include"
LDFLAGS="-L$HOME/local/lib"
PKG_CONFIG_PATH="$HOME/local/lib/pkgconfig:$HOME/local/lib/x86_64-linux-gnu/pkgconfig:$HOME/local/share/pkgconfig"
export CPPFLAGS LDFLAGS PKG_CONFIG_PATH
# linker
LD_LIBRARY_PATH="$HOME/local/lib/x86_64-linux-gnu/"
PATH="$HOME/local/bin:$PATH"
export LD_LIBRARY_PATH PATH
# gir
XDG_DATA_DIRS=$HOME/local/share:$XDG_DATA_DIRS
export XDG_DATA_DIRS
parent <-----> child
GtkWidget -- GtkWindow
Include this file by . (dot) command before using gtk4 libraries.
GtkWindow includes GtkWidget at the top of its object.
You may think you can add them in your `.profile`.
I think the environment variables above are necessary only when you compile gtk4 applications.
And it's not necessary except the case above and it might cause some bad things.
Therefore, I recommend you not to write them to your `.profile`.
![GtkWindow and GtkWidget](../image/window_widget.png){width=9.0cm height=6.0cm}
## Compiling gtk4 applications
The function `gtk_window_new` is defined as follows.
Before you compile gtk4 applications, define environment variables above.
GtkWidget *
gtk_window_new (void);
$ . env.sh
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.
After that you can compile them without anything.
For example, to compile `sample.c`, type the following.
If you want to use `win` as a pointer to the GtkWindow, you need to cast it.
$ gcc `pkg-config --cflags gtk4` sample.c `pkg-config --libs gtk4`
(GtkWindow *) win
To know how to compile gtk4 applications, refer to the following sections.
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 immediately.
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){width=3.3cm height=3.825cm}
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){width=6.3cm height=5.325cm}

View file

@ -1,142 +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 section.
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.
@@@ misc/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 header files of the Gtk 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 object and sets its pointer to `app`.
The meaning of the arguments will be explained later.
The function `g_application_run` invokes the GtkApplication object 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 above.
$ 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){width=6.3cm height=5.325cm}
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 subsection.
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
$$$
cd misc; diff 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 these.
- 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 causes 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 is invoked 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
@@@ misc/lb2.c
A child object inherits 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) `click_cb`.
So, if `btn` is clicked, the function `click_cb` is invoked.
The suffix cb means "call back".
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){width=11.205cm height=6.945cm}
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.
@@@ misc/lb3.c click_cb 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.
They are located in ['GNOME Developer Center'](https://developer.gnome.org/).
$$$
cd misc; diff lb2.c lb3.c
$$$
Let's compile the source file above (`pr2.c`) 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 line.
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 under the directory $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){width=7.725cm height=2.055cm}
## GtkWindow and GtkApplicationWindow
Now, code it.
### GtkWindow
@@@ misc/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 the size 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){width=6.3cm height=5.325cm}
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){width=9.0cm height=6.0cm}
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 immediately.
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){width=3.3cm height=3.825cm}
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){width=6.3cm height=5.325cm}

View file

@ -1,57 +1,142 @@
# 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 section.
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.
@@@ tfv/tfv1.c
@@@ misc/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){width=6.3cm height=5.325cm}
![GtkTextView](../image/screenshot_tfv1.png){width=6.3cm height=5.325cm}
There's an I-beam pointer in the window.
You can add or delete any characters 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 and it shows that there exists a bug in the program.
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 GtkTextView 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.
$$$
cd tfv; diff tfv1.c tfv2.c
cd misc; diff 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:
@@@ tfv/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.
@@@ misc/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) `click_cb`.
So, if `btn` is clicked, the function `click_cb` is invoked.
The suffix cb means "call back".
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){width=11.205cm height=6.945cm}
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`.
@@@ misc/lb3.c click_cb on_activate
And the difference between `lb2.c` and `lb3.c` is as follows.
$$$
cd misc; 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){width=7.725cm height=2.055cm}
Now, code it.
@@@ misc/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 the size 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){width=6.3cm height=5.325cm}
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.
@@@ tfv/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){width=6.3cm height=5.325cm}
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 characters 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 and it shows that there exists a bug in the program.
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 GtkTextView 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.
$$$
cd tfv; 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
@@@ tfv/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.
@@@ tfv/tfv3.c
Save it as `tfv3.c`.
Then compile and run it.
$ comp tfv3
$ ./a.out tfv3.c
![File viewer](../image/screenshot_tfv3.png){width=6.3cm height=5.325cm}
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 connects 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 destroys the window.
## GtkNotebook
GtkNotebook is a container widget that contains multiple children with tabs in it.
![GtkNotebook](../image/screenshot_gtk_notebook.png){width=13.2cm height=5.325cm}
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`.
@@@ tfv/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 the 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,189 +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 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 object derives everything from the parent object.
![Child widget of GtkTwxtView](../image/child.png){width=9.675cm height=4.89cm}
This flag is described in the GApplication section in GIO API reference.
We will define TfeTextView as a child 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 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.
@@@ tfv/tfv3.c
Save it as `tfv3.c`.
Then compile and run it.
$ comp tfv3
$ ./a.out tfv3.c
![File viewer](../image/screenshot_tfv3.png){width=6.3cm height=5.325cm}
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 connects 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 destroys the window.
GFile *
tfe_text_view_get_file (TfeTextView *tv) {
return tv -> file;
}
## GtkNotebook
GtkWidget *
tfe_text_view_new (void) {
return GTK_WIDGET (g_object_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){width=13.2cm height=5.325cm}
- 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 (upper case), object (upper case with underscore) and parent object name (camel case).
- 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 g\_object\_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.
And the return value must be casted to GtkWidget.
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
@@@ tfv/tfv4.c
After editing a file, `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 it handler.
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 the 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 for the handler.
The program of before\_close is as follows.
GtkNotebook -- GtkNotebookPage -- (GtkScrolledWindow and GtkLabel)
@@@ tfe/tfe1.c before_close
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 had run. 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.
@@@ tfe/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.
- 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.

View file

@ -1,178 +1,189 @@
# 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){width=9.3cm height=6.825cm}
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.
@@@ tfe/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.
- 25-27: Generate GtkApplicationWindow and set its title and default size.
- 29-30: 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.
- 32-33: Generate GtkBox `boxh` and append it to 'boxv' as a first child.
- 35-40: 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.
- 41-44: Generate four buttons.
- 46-52: Append these GtkLabel and GtkButton to `boxh`.
- 54-57: 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(=57-25+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 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 object derives everything from the parent object.
![Child widget of GtkTwxtView](../image/child.png){width=9.675cm height=4.89cm}
First, let's look at the ui file `tfe3.ui` that defines a structure of the widgets.
We will define TfeTextView as a child 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.
@@@ tfe/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 25-57 in the source code of `on_open` function.
Those two decribe the same structure of widgets.
Let's define TfeTextView 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) {
}
$$$
cd tfe; diff tfe2.c tfe3.c
$$$
void
tfe_text_view_set_file (TfeTextView *tv, GFile *f) {
tv -> file = f;
}
`60,103c61,65` means 42 (=103-60+1) lines change to 5 (=65-61+1) lines.
Therefore 37 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 (g_object_new (TFE_TYPE_TEXT_VIEW, NULL));
}
@@@ tfe/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.
The source code of `tfe3.c` is stored in [src/tfe](https://github.com/ToshioCP/Gtk4-tutorial/tree/main/src/tfe) directory.
If you want to see it, click the link above.
In the same way, you can get the source files below in the directory [src/tfe](https://github.com/ToshioCP/Gtk4-tutorial/tree/main/src/tfe).
- 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 (upper case), object (upper case with underscore) and parent object name (camel case).
- 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 g\_object\_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.
And the return value must be casted to GtkWidget.
### Using ui string
This program is not perfect.
It has some problem.
But I don't discuss it now.
It will be modified later.
GtkBuilder can build widgets using string.
Use the function gtk\_builder\_new\_from\_string instead of gtk\_builder\_new\_from\_file.
## Close-request signal
char *uistring;
After editing a file, `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 it handler.
The function `before_close` is invoked when the signal "close-request" is emittd.
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>";
g_signal_connect (win, "close-request", G_CALLBACK (before_close), NULL);
build = gtk_builder_new_from_stringfile (uistring);
The argument win is GtkApplicationWindow, in which the signal "close-request" is defined, and before\_close is the handler.
`G_CALLBACK` cast is necessary for the handler.
The program of before\_close 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.
@@@ tfe/tfe1.c before_close
- add backslash before each double quote.
- add double quote at the left and right.
The numbers on the left of items are line numbers in the source code.
### Using Gresource
- 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 had run. 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.
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.
## Source code of tfe1.c
An xml file is necessary for the resource compiler `glib-compile-resources`.
It describes resource files.
Now I will show you all the source code of `tfe1`.c.
@@@ tfe/tfe3.gresource.xml
@@@ tfe/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.
- 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.
Save this xml text to `tfe3.gresource.xml`.
The gresource compiler `glib-compile-resources` shows its usage with the argument `--help`.
Now compile and run it.
Type `./a.out somefile` and make sure that the file is modified.
$$$
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 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,195 +1,178 @@
# Build system
# Ui file and GtkBuiler
## What do we need to think about to manage big source files?
## 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){width=9.3cm height=6.825cm}
These ideas are useful to manage big source files.
The screenshot above shows the layout.
The function `on_open` in the source code `tfe2.c` is as follows.
## Divide a C source file into two parts.
@@@ tfe/tfe2.c on_open
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`.
The point is how to build the window.
- `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
- 25-27: Generate GtkApplicationWindow and set its title and default size.
- 29-30: 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.
- 32-33: Generate GtkBox `boxh` and append it to 'boxv' as a first child.
- 35-40: 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.
- 41-44: Generate four buttons.
- 46-52: Append these GtkLabel and GtkButton to `boxh`.
- 54-57: 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.
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 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.
The number of lines is 33(=57-25+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?
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`.
Gtk provides GtkBuilder.
It reads ui data and builds a window.
It reduces the cumbersom work.
All the source files are listed below.
## Ui file
`tfetextview.h`
First, let's look at the ui file `tfe3.ui` that defines a structure of the widgets.
@@@ tfe4/tfetextview.h
@@@ tfe/tfe3.ui
`tfetextview.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/tfetextview.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.c`
Compare this ui file and the lines 25-57 in the source code of `on_open` function.
Those two decribe the same structure of widgets.
@@@ tfe4/tfe.c
## GtkBuilder
`tfe.ui`
GtkBuilder builds widgets based on the ui file.
@@@ tfe4/tfe.ui
GtkBuilder *build;
`tfe.gresource.xml`
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"));
@@@ tfe4/tfe.gresource.xml
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.
## Make
$$$
cd tfe; diff tfe2.c tfe3.c
$$$
Dividing a file makes it easy to maintain source files.
But now we are faced with a new problem.
The building step increases.
`60,103c61,65` means 42 (=103-60+1) lines change to 5 (=65-61+1) lines.
Therefore 37 lines are reduced.
Using ui file not only shortens C source files, but also makes the widgets' structure clear.
- 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 I'll show you the C source code `tfe3.c`.
Only functions `on_open` are shown as follows.
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.
@@@ tfe/tfe3.c on_open
Make analyzes Makefile and executes compilers.
All instructions are written in Makefile.
The source code of `tfe3.c` is stored in [src/tfe](https://github.com/ToshioCP/Gtk4-tutorial/tree/main/src/tfe) directory.
If you want to see it, click the link above.
In the same way, you can get the source files below in the directory [src/tfe](https://github.com/ToshioCP/Gtk4-tutorial/tree/main/src/tfe).
sample.o: sample.c
gcc -o sample.o sample.c
### Using ui string
The sample of Malefile above consists of three elements, `sample.o`, `sample.c` and `gcc -0 sample.o sample.c`.
GtkBuilder can build widgets using string.
Use the function gtk\_builder\_new\_from\_string instead of gtk\_builder\_new\_from\_file.
- `sample.o` is called target.
- `sample.c` is prerequisite.
- `gcc -o 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).
char *uistring;
The rule is:
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>";
If a prerequisite modified later than a target, then make executes the recipe.
build = gtk_builder_new_from_stringfile (uistring);
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.
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 Makefile for `tfe` is as follows.
- add backslash before each double quote.
- add double quote at the left and right.
@@@ tfe4/Makefile
### Using Gresource
You only need to type `make`.
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.
$ 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`
An xml file is necessary for the resource compiler `glib-compile-resources`.
It describes resource files.
I used only very basic rules to write this Makefile.
There are many more convenient methods to make it more compact.
But it will be long to explain it.
So I want to finish explaining make and move on to the next topic.
@@@ tfe/tfe3.gresource.xml
## Rake
- 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.
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.
Save this xml text to `tfe3.gresource.xml`.
The gresource compiler `glib-compile-resources` shows its usage with the argument `--help`.
- Rakefile controls the behavior of `rake`.
- You can write any ruby code in Rakefile.
$$$
LANG=C glib-compile-resources --help
$$$
Rake has task and file task, which is similar to target, prerequisite and recipe in make.
Now run the compiler.
@@@ tfe4/Rakefile
$ glib-compile-resources tfe3.gresource.xml --target=resources.c --generate-source
What `Rakefile` describes is almost same as `Makefile` in the previous subsection.
Then a C source file `resources.c` is generated.
Modify tfe3.c and save it as tfe3_r.c
- 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 the shell.
- 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.
# include "resources.c"
... ... ...
... ... ...
build = gtk_builder_new_from_resource ("/com/github/ToshioCP/tfe3/tfe3.ui");
... ... ...
... ... ...
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.
@@@ 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` is generated under the directory `_build`.
$ _build/tfe tfe.c tfetextview.c
Then the window appears.
I've shown 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,255 +1,195 @@
# Instance and class
# Build system
This section and the following four sections are explanations about the next version of the text file editor (tfe).
It is tfe5.
It has many changes from the prior version.
All the sources are listed after the five sections.
## What do we need to think about to manage big source files?
## 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.
These ideas are useful to manage big source files.
However, first of all, I'd like to focus on the object TfeTextView.
It is a child object of GtkTextView and has a new member `file` in it.
The important thing is to manage the Gfile object pointed by `file`.
## Divide a C source file into two parts.
- 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?
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`.
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:
- `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
- Organizing functions.
- How to use FileChooserDialog
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 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.
## GObject and its children
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 are objects, which have both class and instance.
First, think about instance of objects.
Instance is structured memories and the structure is described as C language structure.
The following is a structure of TfeTextView.
All the source files are listed below.
/* This typedef statement is automaticaly generated by the macro G_DECLARE_FINAL_TYPE */
typedef struct _TfeTextView TfeTextView;
`tfetextview.h`
struct _TfeTextView {
GtkTextView parent;
GFile *file;
};
@@@ tfe4/tfetextview.h
The members of the structure are:
`tfetextview.c`
- `parent` is the structure of GtkTextView which is the parent object of TfeTextView.
- `file` is a pointer to GFile. It can be NULL if no file corresponds to the TfeTextView object.
@@@ tfe4/tfetextview.c
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.c`
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.c
typedef struct _GObject GObject;
typedef struct _GObject GInitiallyUnowned;
struct _GObject
{
GTypeInstance g_type_instance;
volatile guint ref_count;
GData *qdata;
};
`tfe.ui`
typedef struct _GtkWidget GtkWidget;
struct _GtkWidget
{
GInitiallyUnowned parent_instance;
GtkWidgetPrivate *priv;
};
@@@ tfe4/tfe.ui
typedef struct _GtkTextView GtkTextView;
struct _GtkTextView
{
GtkWidget parent_instance;
GtkTextViewPrivate *priv;
};
`tfe.gresource.xml`
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.
@@@ tfe4/tfe.gresource.xml
![The structure of the instance TfeTextView](../image/TfeTextView.png){width=14.39cm height=2.16cm}
## Make
Dividing a file makes it easy to maintain source files.
But now we are faced with a new problem.
The building step increases.
## Generate TfeTextView instance
- 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 function `tfe_text_view_new` generates a new TfeTextView instance.
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.
@@@ tfe5/tfetextview.c tfe_text_view_new
Make analyzes Makefile and executes compilers.
All instructions are written in Makefile.
When this function is run, the following procedure is gone through.
sample.o: sample.c
gcc -o sample.o sample.c
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 sample of Malefile above consists of three elements, `sample.o`, `sample.c` and `gcc -0 sample.o sample.c`.
Step one through three is done automatically.
Step four is done by the function `tfe_text_view_init`.
- `sample.o` is called target.
- `sample.c` is prerequisite.
- `gcc -o 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).
> 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 files.
The rule is:
@@@ tfe5/tfetextview.c tfe_text_view_init
If a prerequisite modified later than a target, then make executes the recipe.
`tfe_text_view_init` initializes the instance.
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.
- 3: Get the pointer to GtkTextBuffer and assign it to `tb`.
- 5: Initialize `tv->file = NULL`.
- 6: Set modified bit to FALSE. That means the GtkTextBuffer has not modified.
When the buffer is modified, it will automatically toggled on the modified bit.
Whenever the buffer is saved to disk, call gtk_text_buffer_set_modified (buffer , FALSE).
- 7: Set the wrap mode of GtkTextView as GTK\_WRAP\_WORD\_CHAR.
The Makefile for `tfe` is as follows.
## Functions and Classes
@@@ tfe4/Makefile
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.
Instance, which is structured memories, only keeps status of the object.
Therefore, it is insufficient to define its behavior.
We need at least two things.
One is functions and the other is class.
You only need to type `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 or Ruby.
Functions are public, which means that they are expected to be used by other objects.
$ 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`
Class comprises mainly pointers to functions.
Those functions are used by the object itself or its descendent objects.
For example, GObject class is declared in `gobject.h` in GLib source files.
I used only very basic rules to write this Makefile.
There are many more convenient methods to make it more compact.
But it will be long to explain it.
So I want to finish explaining make and move on to the next topic.
@@@ class_gobject.c
## Rake
I'd like to explain some of the members.
There's a pointer to the function `dispose` in line 22.
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.
void (*dispose) (GObject *object);
- Rakefile controls the behavior of `rake`.
- You can write any ruby code in 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.
Rake has task and file task, which is similar to target, prerequisite and recipe in make.
void (*finalize) (GObject *object);
@@@ tfe4/Rakefile
Look at the declaration of `_GObjectClass` so that you would find that most of the members are pointers to functions.
What `Rakefile` describes is almost same as `Makefile` in the previous subsection.
- 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. The first one is called disposing and the instance releases all the references to other instances. The second one is finalizing.
- 23: A funtion pointed by `finalize` finishes the destruction process.
- The other pointers point functions which are called while the instance lives.
- 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 the shell.
- 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.
## TfeTextView class
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.
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.
## Meson and ninja
GObject -- GInitiallyUnowned -- GtkWidget -- GtkTextView -- TfeTextView
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.
The following is extracts from the source files (not exactly the same).
To use meson, you first need to write `meson.build` file.
@@@ classes.c
@@@ tfe4/meson.build
- 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 the descendent 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 descendent class.)
- Some class methods are often overridden.
`set_property`, `get_property`, `dispose`, `finalize` and `constructed` are such methods.
- 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.
TfeTextViewClass includes its ancsestors' class in it.
It is illustrated in the following diagram.
Now run meson and ninja.
![The structure of TfeTextView Class](../image/TfeTextViewClass.png){width=16.02cm height=8.34cm}
$ meson _build
$ ninja -C _build
## Destruction of TfeTextView
Then, the executable file `tfe` is generated under the directory `_build`.
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)`.
$ _build/tfe tfe.c tfetextview.c
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.
Then the window appears.
![Reference count of B](../image/refcount.png){width=15.855cm height=2.475cm}
I've shown you three build tools.
I think meson and ninja is the best choice for the present.
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 has been generated before TfeTextViewClass is generated.
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){width=14.925cm height=4.455cm}
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 instance 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.