Tfetextview.c and tfetextview.h is moved to tfetextview folder. Bugs and typo error are fixed.

This commit is contained in:
Toshio Sekiya 2021-02-06 17:26:57 +09:00
parent 204709834a
commit c3c4668597
55 changed files with 1375 additions and 1792 deletions

View file

@ -13,7 +13,7 @@ You can post it to [github issues](https://github.com/ToshioCP/Gtk4-tutorial/iss
The latest version of the tutorial is located at [Gtk4-tutorial githup repository](https://github.com/ToshioCP/Gtk4-tutorial).
You can read it without download.
1. [Prerequisite and Licence](gfm/sec1.md)
1. [Prerequisite and License](gfm/sec1.md)
1. [Installation of gtk4 to linux distributions](gfm/sec2.md)
1. [GtkApplication and GtkApplicationWindow](gfm/sec3.md)
1. [Widgets (1)](gfm/sec4.md)
@ -25,7 +25,7 @@ You can read it without download.
1. [Instance and class](gfm/sec10.md)
1. [Signals](gfm/sec11.md)
1. [Functions in TfeTextView](gfm/sec12.md)
1. [Functions with GtkNotebook](gfm/sec13.md)
1. [Functions in GtkNotebook](gfm/sec13.md)
1. [tfeapplication.c](gfm/sec14.md)
1. [tfe5 source files](gfm/sec15.md)
1. [Menu and action](gfm/sec16.md)

View file

@ -1,20 +1,20 @@
Up: [Readme.md](../Readme.md), Next: [Section 2](sec2.md)
# Prerequisite and Licence
# Prerequisite and License
## Prerequisite
### Tutorial document
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_.
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_.
If you want to try the examples in the tutorial, you need:
- PC with linux distribution like ubuntu, debian and so on.
- PC with Linux distribution like Ubuntu, Debian and so on.
- Gcc
- Gtk4. Gtk included linux distributions is version three at present.
- 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
@ -23,32 +23,32 @@ Therefore, I strongly recommend you not to install gtk4 to `/usr/local` on the c
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.
- Install it to your home directory, for example `$HOME/local`, in order to separate gtk4 from your system.
The second choice will be explained in [Section 3](sec3.md).
### Software
This repository inclludes ruby programs.
This repository includes ruby programs.
They are used to generate markdown files, html files, latex files and a pdf file.
You need:
- Linux distribution like ubuntu.
- Ruby programing language.
- Linux distribution like Ubuntu.
- Ruby programming language.
There are two ways to install it.
One is install the distribution's package.
The other is using rbenv and ruby-build.
If you want to use the latest version of ruby, use rbenv.
- Rake.
It is a gem, which is a library written in ruby.
You can install it as a package of your ditribution or use gem command.
You can install it as a package of your distribution or use gem command.
## Licence
## License
Copyright (C) 2020 ToshioCP (Toshio Sekiya)
Gtk4 tutorial repository containes the tutorial document and softwares such as converters, generators and controlers.
Gtk4 tutorial repository contains the tutorial document and software such as converters, generators and controllers.
All of them make up the 'Gtk4 tutorial' package.
This package is simply called 'Gtk4 tutorial' in the following description.
'Gtk4 tutorial' is free; you can redistribute it and/or modify it under the terms of the GNU General Public License

View file

@ -6,6 +6,7 @@ This section and the following four sections are explanations about the next ver
It is tfe5.
It has many changes from the prior version.
All the sources are listed in [Section 15](sec15.md).
They are located in two directories, [src/tfe5](../src/tfe5) and [src/tfetextview](../src/tfetextview).
## Encapsulation
@ -23,24 +24,24 @@ The important thing is to manage the Gfile object pointed by `file`.
- 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?
- How it communicates with objects outside?
You need to know at least class/instance and signals before thinking about them.
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:
- Organizing functions.
- How to use FileChooserDialog
- How to use GtkFileChooserDialog
## GObject and its children
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.
Instance is structured memories and described as C language structure.
The following is a structure of TfeTextView.
~~~C
/* This typedef statement is automaticaly generated by the macro G_DECLARE_FINAL_TYPE */
/* This typedef statement is automatically generated by the macro G_DECLARE_FINAL_TYPE */
typedef struct _TfeTextView TfeTextView;
struct _TfeTextView {
@ -51,7 +52,7 @@ struct _TfeTextView {
The members of the structure are:
- `parent` is the structure of GtkTextView which is the parent object of TfeTextView.
- `parent` is the instance 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.
@ -86,7 +87,7 @@ struct _GtkTextView
};
~~~
In each structure, its parent instance is declared at the top of the members.
In each structure, its parent instance is declared at the top of 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.
@ -108,10 +109,10 @@ The function `tfe_text_view_new` generates a new TfeTextView instance.
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.
1. Initialize GObject part in TfeTextView instance.
2. Initialize GtkWidget part in TfeTextView instance.
3. Initialize GtkTextView part in TfeTextView instance.
4. Initialize TfeTextView part in TfeTextView instance.
Step one through three is done automatically.
Step four is done by the function `tfe_text_view_init`.
@ -122,30 +123,19 @@ Step four is done by the function `tfe_text_view_init`.
~~~C
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 }
3 tv->file = NULL;
4 }
~~~
`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.
This function just initializes `tv->file` to be `NULL`.
## 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.
An instance can be generated two times or more.
Each object can have more than one instance.
Those instances have the same structure.
Instance, which is structured memories, only keeps status of the object.
Instance, which is structured memories, only keeps status of the instance.
Therefore, it is insufficient to define its behavior.
We need at least two things.
One is functions and the other is class.
@ -156,7 +146,7 @@ These functions are similar to object methods in object oriented languages such
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.
Those functions are used by the object itself or its descendant objects.
For example, GObject class is declared in `gobject.h` in GLib source files.
~~~C
@ -209,8 +199,8 @@ void (*dispose) (GObject *object);
The declaration is a bit complicated.
The asterisk before the identifier `dispose` means pointer.
So, the pointer `dispose` points to 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.
So, the pointer `dispose` points to a function which has one parameter, which points a GObject structure, and returns no value.
In the same way, line 23 says `finalize` is a pointer to the function which has one parameter, which points a GObject structure, and returns no value.
~~~C
void (*finalize) (GObject *object);
@ -219,7 +209,11 @@ 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.
- 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.
In this phase, the instance releases all the references to other instances.
The second phase is finalizing.
- 23: A function pointed by `finalize` finishes the destruction process.
- The other pointers point to functions which are called while the instance lives.
@ -347,11 +341,11 @@ The following is extracts from the source files (not exactly the same).
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 ancestors are open to the descendent class.
- Class members in ancestors are open to the descendant 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.)
(Override is an object oriented programming terminology.
Override is rewriting ancestors' class methods in the descendant class.)
- Some class methods are often overridden.
`set_property`, `get_property`, `dispose`, `finalize` and `constructed` are such methods.
@ -363,24 +357,26 @@ It is illustrated in the following diagram.
## Destruction of TfeTextView
Every Object derived from GObject has a reference count.
If an object A uses an object B, then A keeps a pointer to B in A and at the same time increases the reference count of B by one with the function `g_object_ref (B)`.
If an object A refers an object B, then A keeps a pointer to B in A and at the same time increases 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 decreases 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.
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.
In the same way, if 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.
The idea above is based on an assumption that an object referred 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.
The destruction process is spitted into two phases: disposing and finalizing.
In the disposing process, the object invokes the function pointed by `dispose` in its class to release all references to other objects.
In the finalizing process, it invokes the function pointed by `finalize` in its class to complete the destruction process.
These functions are also called handlers or methods.
For example, dispose handler or dispose method.
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.
@ -399,10 +395,11 @@ You must write the code in the dispose handler `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.)
`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: invokes parent's dispose handler. (This will be explained later.)
In the desposing process, the object uses the pointer in its class to call the handler.
In the disposing process, the object uses the pointer in its class to call the handler.
Therefore, `tfe_text_view_dispose` needs to be registered 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.
@ -422,7 +419,7 @@ 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)
![dispose handlers](../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`.
@ -434,7 +431,7 @@ 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.
And `gobject` is a pointer to TfeTextView instance which is casted as a GObject instance.
`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

@ -19,18 +19,18 @@ Then the handler, which has been connected to the signal, is invoked.
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 functions the object passively responds to the caller.
In signals the object actively sends a signal to the handler.
GObject signal can be registered, connected and emitted.
GObject signals are registered, connected and emitted.
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. Signals are registered with the object type on which they can be emitted.
The registration is done usually when the class is initialized.
2. Signals are connected to handlers by `g_connect_signal` or its family functions.
3. When Signals are emitted, the connected handlers are invoked.
Step one and three are done in the object on which the signal is emitted.
Step two is usually done outside the objects.
Step one and three are done in the object on which the signal belongs.
Step two is usually done outside the object.
## Signal registration
@ -39,11 +39,11 @@ In TfeTextView, two signals are registered.
- "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.
`tfe_text_view_open` function is not able to return the status because it uses GtkFileChooserDialog.
This signal is emitted instead of the return value of the function.
Static variable is used to store the signal ID.
If you need to register two or more signals, static array is usually used.
A static variable or array is used to store the signal ID.
A static array is used to register two or more signals.
~~~C
enum {
@ -87,7 +87,7 @@ Signal registration codes are written in the class initialization function.
27 }
~~~
- 6-15: Register "change-file"signal.
- 6-15: Registers "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.
@ -95,7 +95,7 @@ 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.
- 16-26: Registers "open-response" signal.
This signal has a parameter.
- 25: Number of the parameter.
"open-response" signal has one parameter.
@ -105,20 +105,20 @@ 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).
The handlers are as follows.
The handlers are declared as follows.
~~~C
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.
- Because "change-file" signal doesn't have parameter, the handler's parameters are a TfeTextView object and user data.
- Because "open-response" signal has one parameter, the handler's parameters are a TfeTextView object, the signal's parameter and a 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.
The values of the parameter is defined in `tfetextview.h` because they are public.
~~~C
/* "open-response" signal response */
@ -130,18 +130,22 @@ enum
};
~~~
- `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.
- The parameter is set to `TFE_OPEN_RESPONSE_SUCCESS` when `tfe_text_view_open` successfully has opened a file and loaded it.
- The parameter is set to `TFE_OPEN_RESPONSE_CANCEL` when the user has canceled to open a file.
- The parameter is set to `TFE_OPEN_RESPONSE_ERROR` when error has occurred.
## Signal connection
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.
The signals "change-file" is connected to a callback function outside of TfeTextView object.
In the same way, the signals "open-response" is connected to a callback function outside of TfeTextView object.
Those callback functions are defined by users.
In the program tfe, callback functions are defined in `tfenotebook.c`.
And their names are `file_changed` and `open_response`.
They will be explained later.
~~~C
g_signal_connect (GTK_TEXT_VIEW (tv), "change-file", G_CALLBACK (file_changed), nb);
@ -155,7 +159,7 @@ 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.
`g_signal_emit` is used to emit the signal.
A function `g_signal_emit` is used to emit the signal.
The following lines are extracted from `tfetextview.c`.
Each line is quoted from a different line.
@ -170,7 +174,7 @@ g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ER
- 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.
- "change-file" signal doesn't have parameter, so there's no fourth parameter.
- "open-response" signal has one parameter.
The fourth parameter is the parameter.

View file

@ -2,59 +2,66 @@ Up: [Readme.md](../Readme.md), Prev: [Section 11](sec11.md), Next: [Section 13]
# Functions in TfeTextView
In this section I will explain each function in TfeTextView object.
In this section I will explain functions in TfeTextView object.
### 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.
C source files `tfeapplication.c` and `tfenotebook.c` include `tfe.h` at the beginning.
~~~C
1 #include <gtk/gtk.h>
2
3 #include "tfetextview.h"
3 #include "../tfetextview/tfetextview.h"
4 #include "tfenotebook.h"
~~~
`tfetextview.h` is a header file which describes the public functions in `tfetextview.c`.
`../tfetextview/tfetextview.h` is a header file which describes the public functions in `tfetextview.c`.
~~~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)
1 #ifndef __TFE_TEXT_VIEW_H__
2 #define __TFE_TEXT_VIEW_H__
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
4 #include <gtk/gtk.h>
5
6 #define TFE_TYPE_TEXT_VIEW tfe_text_view_get_type ()
7 G_DECLARE_FINAL_TYPE (TfeTextView, tfe_text_view, TFE, TEXT_VIEW, GtkTextView)
8
9 /* "open-response" signal response */
10 enum TfeTextViewOpenResponseType
11 {
12 TFE_OPEN_RESPONSE_SUCCESS,
13 TFE_OPEN_RESPONSE_CANCEL,
14 TFE_OPEN_RESPONSE_ERROR
15 };
16
17 GFile *
18 tfe_text_view_get_file (TfeTextView *tv);
19
20 void
21 tfe_text_view_open (TfeTextView *tv, GtkWidget *win);
22
23 void
24 tfe_text_view_save (TfeTextView *tv);
25
26 void
27 tfe_text_view_saveas (TfeTextView *tv);
28
29 GtkWidget *
30 tfe_text_view_new_with_file (GFile *file);
31
32 GtkWidget *
33 tfe_text_view_new (void);
34
35 #endif /* __TFE_TEXT_VIEW_H__ */
~~~
- 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.
Each function will be explained later in this section.
- 1,2,35: Thanks to these three lines, the following lines are included only once.
- 4: Includes gtk4 header files.
The header file `gtk4` also has the same mechanism to avoid including it multiple times.
- 6-7: These two lines define TfeTextView.
- 9-15: A definition of the value of the parameter of "open-response" signal.
- 17-33: Declaration of public functions on GtkTextView.
## Functions to generate TfeTextView object
@ -71,14 +78,6 @@ GtkWidget *tfe_text_view_new_with_file (GFile *file);
~~~
`tfe_text_view_new_with_file` is given a Gfile object as the argument and it loads the file into the GtkTextBuffer object, then returns the pointer to the new object.
Parameter:
- `file`: a pointer to the GFile object.
Return value:
- A pointer to the generated TfeTextView object but it is casted to a pointer to GtkWidget.
If an error occurs during the generation process, NULL is returned.
Each function is defined as follows.
@ -110,10 +109,10 @@ Each function is defined as follows.
24 }
~~~
- 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`
- 21-24: `tfe_text_view_new` function.
Just returns the value from the function `g_object_new` but casts it to the pointer to GtkWidget.
Initialization is done in `tfe_text_view_init` which is called in the process of `g_object_new` function.
- 1-19: `tfe_text_view_new_with_file` function.
- 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.
@ -124,12 +123,14 @@ If an error occurs, the solution is usually to change the (caller) program and f
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`.
- 13: Calls the function `tfe_text_view_new`.
The function generates TfeTextView instance and returns the pointer to the instance.
- 14: Gets the pointer to GtkTextBuffer corresponds to `tv`.
The pointer is assigned to `tb`
- 15: Assigns the contents read from the file to GtkTextBuffer pointed by `tb`.
- 16: Frees the memories pointed by `contents`.
- 17: Duplicates `file` and sets `tv->file` to point it.
- 18: Returns `tv`, which is a pointer to the newly created TfeTextView instance..
## Save and saveas functions
@ -139,17 +140,18 @@ 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`.
The function `save` writes the contents in GtkTextBuffer to a file specified by `tv->file`.
If `tv->file` is NULL, then it shows GtkFileChooserDialog and prompts the user to choose a file to save.
Then it saves the contents to the file and sets `tv->file` to point the GFile instance of the file.
~~~C
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.
The function `saveas` uses GtkFileChooserDialog and prompts the user to select a existed file or specify a new file to save.
Then, the function changes `tv->file` and save the contents to the specified file.
If an error occurs, 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.
The error is managed only in the TfeTextView instance and no information is notified to the caller.
~~~C
1 static void
@ -160,121 +162,131 @@ The error is managed only in the object and no information is notified to the ca
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));
9 if (G_IS_FILE (tv->file))
10 g_object_unref (tv->file);
11 tv->file = file;
12 gtk_text_buffer_set_modified (tb, TRUE);
13 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
14 tfe_text_view_save (TFE_TEXT_VIEW (tv));
15 }
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 }
17 gtk_window_destroy (GTK_WINDOW (dialog));
18 }
19
20 void
21 tfe_text_view_save (TfeTextView *tv) {
22 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
23
24 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
25 GtkTextIter start_iter;
26 GtkTextIter end_iter;
27 gchar *contents;
28 GtkWidget *message_dialog;
29 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
30 GError *err = NULL;
31
32 if (! gtk_text_buffer_get_modified (tb))
33 return; /* no need to save it */
34 else if (tv->file == NULL)
35 tfe_text_view_saveas (tv);
36 else {
37 gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
38 contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
39 if (g_file_replace_contents (tv->file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, &err))
40 gtk_text_buffer_set_modified (tb, FALSE);
41 else {
42 /* It is possible that tv->file is broken or you don't have permission to write. */
43 /* It is a good idea to set tv->file to NULL. */
44 if (G_IS_FILE (tv->file))
45 g_object_unref (tv->file);
46 tv->file =NULL;
47 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
48 message_dialog = gtk_message_dialog_new (GTK_WINDOW (win), GTK_DIALOG_MODAL,
49 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
50 "%s.\n", err->message);
51 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
52 gtk_widget_show (message_dialog);
53 g_error_free (err);
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 }
56 }
57
58 void
59 tfe_text_view_saveas (TfeTextView *tv) {
60 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
61
62 GtkWidget *dialog;
63 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
64
65 dialog = gtk_file_chooser_dialog_new ("Save file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_SAVE,
66 "Cancel", GTK_RESPONSE_CANCEL,
67 "Save", GTK_RESPONSE_ACCEPT,
68 NULL);
69 g_signal_connect (dialog, "response", G_CALLBACK (saveas_dialog_response), tv);
70 gtk_widget_show (dialog);
71 }
~~~
- 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.
- 20-56: `Tfe_text_view_save` function.
- 22: 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.
- 32-33: 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`.
- 34-35: If `tv->file` is NULL, no file has given yet.
It calls `tfe_text_view_saveas` which prompts a user to select a file or specify a new file to save.
- 37-38: Gets the contents of the GtkTextBuffer and sets `contents` to point it.
- 39-40: Saves the content to the file.
If it succeeds, it resets the modified bit in the GtkTextBuffer.
- 42-53: If file writing fails, it assigns NULL to `tv->file`.
Emits "change-file" signal.
Shows the error message dialog (47-51).
Shows the error message dialog (48-52).
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.
Frees the GError object.
- 58-71: `tfe_text_view_saveas` function.
It shows GtkFileChooserDialog and prompts the user to choose a file.
- 65-68: Generates 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.
- 69: connects the "response" signal of the dialog and `saveas_dialog_response` handler.
- 1-18: `saveas_dialog_response` signal handler.
- 6-16: If the response is `GTK_RESPONSE_ACCEPT`, then it gets a pointer to the GFile object.
Then, it sets `tv->file` to point the GFile.
And turns on the modified bit of the GtkTextBuffer, emits "change-file" signal.
Finally, it calls `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.
One is are a function which generates GtkFileChooserDialog and the other is a signal handler.
The function just generates and shows GtkFileChooserDialog.
The rest is done by the handler.
It gets Gfile from GtkFileChooserDialog, save the buffer to the file by calling `tfe_text_view_save`.
It gets Gfile from GtkFileChooserDialog and saves 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.
Open function shows GtkFileChooserDialog to users and prompts them to choose a file.
Then it reads the file and puts the text to GtkTextBuffer.
~~~C
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.
The parameter `win` is the top window.
It will be a transient parent window of GtkFileChooserDialog when the dialog is generated..
This allows window managers to keep the dialog on top of the parent window, or center the dialog over the parent window.
It is possible to give no parent window to the dialog.
However, it is encouraged to give a parent window to dialog.
This function might be called just after `tv` has been generated.
In that case, `tv` has not been incorporated into the widget hierarchy.
Therefore it is impossible to get the top window from `tv`.
That's why the function needs `win` parameter.
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`.
This function is usually called when the buffer of `tv` is empty.
However, 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 appropriate.
Otherwise probably bad things will happen.
~~~C
1 static void
@ -308,42 +320,44 @@ It will be used as a transient parent window for the argument to the function `g
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 }
32 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
33 }
34 gtk_window_destroy (GTK_WINDOW (dialog));
35 }
36
37 void
38 tfe_text_view_open (TfeTextView *tv, GtkWidget *win) {
39 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
40 g_return_if_fail (GTK_IS_WINDOW (win));
41
42 GtkWidget *dialog;
43
44 dialog = gtk_file_chooser_dialog_new ("Open file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_OPEN,
45 "Cancel", GTK_RESPONSE_CANCEL,
46 "Open", GTK_RESPONSE_ACCEPT,
47 NULL);
48 g_signal_connect (dialog, "response", G_CALLBACK (open_dialog_response), tv);
49 gtk_widget_show (dialog);
50 }
~~~
- 36-49: `tfe_text_view_open` function.
- 43: Generate GtkFileChooserDialog.
- 37-50: `tfe_text_view_open` function.
- 44-47: Generates GtkFileChooserDialog.
The title is "Open file".
Ttransient parent window is the top window of the application, which is given by the caller.
Transient 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.
- 48: connects the "response" signal of the dialog and `open_dialog_response` signal handler.
- 49: Shows the dialog.
- 1-35: `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.
- 12-13: Gets a pointer to Gfile by `gtk_file_chooser_get_file`.
If it is not GFile, maybe an error occurred.
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.
- 14-23: If an error occurs at file reading, then it decreases the reference count of the 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-33: If the file has successfully read, then the text is inserted to GtkTextBuffer, frees the temporary buffer pointed by `contents` and sets `tv->file` to point the file (no duplication or unref is not necessary).
Then, it emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_SUCCESS` and emits "change-file" signal.
- 34: closes 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.
@ -354,12 +368,12 @@ 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`.
1. A caller gets 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.
3. It calls `tfe_text_view_open` to prompt the user to 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.
5. The handler reads the file and inserts the text into GtkTextBuffer and emits a signal to inform the response status.
6. The handler outside TfeTextView receives the signal.
## Get file function
@ -374,12 +388,13 @@ However, in Gtk4, `gtk_dialog_run`is unavailable any more.
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.
The important thing is to duplicate `tv->file`.
Otherwise, if the caller frees 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).
You can find them under [src/tfe5](../src/tfe5) and [src/tfetextview](../tfetextview) directories.
Up: [Readme.md](../Readme.md), Prev: [Section 11](sec11.md), Next: [Section 13](sec13.md)

View file

@ -1,10 +1,12 @@
Up: [Readme.md](../Readme.md), Prev: [Section 12](sec12.md), Next: [Section 14](sec14.md)
# Functions with GtkNotebook
# Functions in GtkNotebook
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.
A set of functions related to GtkNotebook are declared in `tfenotebook.h`.
The word "tfenotebook" is used only in filenames.
There's no "TfeNotebook" object.
~~~C
1 void
@ -21,13 +23,13 @@ It connects the application and TfeTextView objects.
12
~~~
This header file shows the public functions in `tfenotebook.c`.
This header file describes 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.
- 10-11: The function `notebook_page_new` generates a new GtkNotebookPage and adds GtkScrolledWindow and TfeTextView to the page.
- 7-8: The function `notebook_page_new_with_file` generates a new GtkNotebookPage and adds GtkScrolledWindow and TfeTextView to the page. A file is read and inserted into GtkTextBuffer.
The GFile `file` is copied and inserted to the TfeTextView object.
- 4-5: `notebook_page_open` shows a file chooser dialog. Then, user chooses a file and the file is inserted into GtkTextBuffer.
- 1-2: `notebook_page_save` saves the contents in GtkTextBuffer into the file, which is got from the TfeTextView.
You probably find that the functions above are higher level functions of
@ -64,45 +66,51 @@ Now let's look at each program of the functions.
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 }
18 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
19 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
20 lab = gtk_label_new (filename);
21 i = gtk_notebook_append_page (nb, scr, lab);
22 nbp = gtk_notebook_get_page (nb, scr);
23 g_object_set (nbp, "tab-expand", TRUE, NULL);
24 gtk_notebook_set_current_page (nb, i);
25 g_signal_connect (GTK_TEXT_VIEW (tv), "change-file", G_CALLBACK (file_changed), nb);
26 }
27
28 void
29 notebook_page_new (GtkNotebook *nb) {
30 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
31
32 GtkWidget *tv;
33 char *filename;
34
35 tv = tfe_text_view_new ();
36 filename = get_untitled ();
37 notebook_page_build (nb, tv, filename);
38 }
~~~
- 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", ... .
- 28-38: `notebook_page_new` function.
- 30: `g_return_if_fail` is used to check the argument.
- 35: Generates TfeTextView object.
- 36: Generates 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.
- 4-7: Increases `c` by one and if it is zero then it returns "Untitled". If it is a positive integer then the it returns "Untitled\<the integer\>", for example, "Untitled1", "Untitled2", and so on.
The function `g_strdup_printf` generates a string and it should be freed by `g_free` when it becomes useless.
The caller of `get_untitled` is in charge of freeing the string.
- 37: calls `notebook_page_build` to build the contents of the page.
- 10- 26: `notebook_page_build` function.
- 16: Generates GtkScrolledWindow.
- 18: Sets the wrap mode of `tv` to GTK_WRAP_WORD_CHAR so that lines are broken between words or graphemes.
- 19: Inserts `tv` to GtkscrolledWindow as a child.
- 20-21: Generates GtkLabel, then appends it to GtkNotebookPage.
- 22-23: Sets "tab-expand" property to TRUE.
The function g\_object\_set sets properties on an object.
The object is any object derived from GObject.
In many cases, an object has its own function to set its properties, but sometimes not.
In that case, use g\_object\_set to set the property.
- 24: Sets the current page of `nb` to `i` which is the number of the GtkNotebookPage above.
- 25: Connects "change-file" signal and `file_changed` handler.
## notebook\_page\_new\_with\_file
@ -122,9 +130,10 @@ The caller of `get_untitled` is in charge of freeing the memories of the string.
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.
- 9-10: Calls `tfe_text_view_new_with_file`.
If the function returns NULL, then it does nothing and returns.
The return value NULL means that an error has happened.
- 11-12: Gets the filename and builds the contents of the page.
## notebook\_page\_open
@ -160,20 +169,22 @@ If it returns NULL, then do nothing and return because of an error.
~~~
- 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.
- 25: Generates TfeTextView object.
- 26: Connects the signal "open-response" and the handler `open_response`.
- 27: Calls `tfe_text_view_open`.
The function emits an "open-response" signal to inform the status.
- 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`.
- 6-8: If the status is NOT `TFE_OPEN_RESPONSE_SUCCESS`, it cancels what it 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.
You need to call `g_object_ref_sink` first.
Then the floating reference is converted into an ordinary reference.
Now you call `g_object_unref` to decrease the reference count by one.
- 9-11: If `tfe_text_view_get_file` returns a pointer not to point GFile, then something bad happens.
Sink and unref `tv`.
- 12-16: Otherwise, everything is okay.
Get the filename, build the contents of the page.
Gets the filename, builds the contents of the page.
## notebook\_page\_save
@ -197,7 +208,7 @@ Get the filename, build the contents of the page.
## file\_changed handler
The function `file_changed` is a handler connected to "change-file" signal.
If `tv->file` is changed, TfeTextView emits this signal.
If the file in TfeTextView is changed, it emits this signal.
This handler changes the label of GtkNotebookPage.
~~~C
@ -221,13 +232,12 @@ This handler changes the label of GtkNotebookPage.
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`.
- 8: Gets GFile from TfeTextView.
- 9: Gets GkScrolledWindow which is the parent widget of `tv`.
- 10-13: If `file` points GFile, then assigns the filename of the GFile into `filename`.
Otherwise (file is NULL), assigns untitled string to `filename`.
- 14-15: Generates a label with the filename and inserts it into GtkNotebookPage.
- 16-17: Unrefs `file` and frees `filename`.
Up: [Readme.md](../Readme.md), Prev: [Section 12](sec12.md), Next: [Section 14](sec14.md)

View file

@ -33,19 +33,19 @@ It connects the command line given by the user and GTK application.
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.
- 6: Generates GtkApplication object.
- 8-10: Connects "startup", "activate" and "open signals to their handlers.
- 12: Runs the application.
- 13-14: releases the reference to the application and returns the status.
## startup 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.
- Builds the widgets using ui file.
- Connects button signals and their handlers.
- Sets CSS.
The handler is as follows.
@ -84,11 +84,11 @@ The handler is as follows.
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.
- 12-15: Builds widgets using ui file (resource).
Connects the top window and the application using `gtk_window_set_application`.
- 16-23: Gets buttons and connects their signals and handlers.
- 24: Releases the reference to GtkBuilder.
- 26-31: Sets 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.
@ -100,12 +100,12 @@ CSS will be explained in the next subsection.
CSS is an abbreviation 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.
You might have found that the widgets in GTK is similar to the window in a browser.
It implies that CSS can also be applied to GTK windowing system.
### CSS nodes, selectors
The syntax of CSS is as follws.
The syntax of CSS is as follows.
~~~css
selector { color: yellow; padding-top: 10px; ...}
@ -113,7 +113,7 @@ 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.
If you want to set style to GtkTextView, substitute "textview" for the selector.
~~~css
textview {color: yellow; ...}
@ -148,9 +148,9 @@ However, instead, you can add it to GdkDisplay of the window (usually top level
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.
- 29: Generates GtkCssProvider.
- 30: Puts the CSS into the provider.
- 31: Adds 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`.
@ -173,7 +173,7 @@ tfe_text_view_new (void) {
}
~~~
CSS set to the context takes precedence over the one set to the display.
CSS in the context takes precedence over CSS in the display.
## activate and open handler
@ -217,20 +217,20 @@ They just generate a new GtkNotebookPage.
~~~
- 1-14: `tfe_activate`.
- 8-10: Get GtkNotebook object.
- 12-13: Generate a new GtkNotebookPage and show the window.
- 8-10: Gets GtkNotebook object.
- 12-13: Generates 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.
- 24-26: Gets GtkNotebook object.
- 28-29: Generates GtkNotebookPage with files.
- 30-31: If opening and reading file failed and no GtkNotebookPage has generated, then it generates a empty page.
- 32: Shows the window.
These codes have become really simple thanks to tfenotebook.c and tfetextview.c.
## Primary instance
Only one GApplication instance can be run at a time per session.
The session is a bit diffcult concept and also platform-dependent, but roughly speaking, it corresponds to a graphical desktop login.
The session is a bit difficult concept and also platform-dependent, but roughly speaking, it corresponds to a graphical desktop login.
When you use your PC, you probably login first, then your desktop appears until you log off.
This is the session.
@ -313,7 +313,7 @@ First, get the top level window and call `gtk_window_destroy`.
5 gnome=import('gnome')
6 resources = gnome.compile_resources('resources','tfe.gresource.xml')
7
8 sourcefiles=files('tfeapplication.c', 'tfenotebook.c', 'tfetextview.c')
8 sourcefiles=files('tfeapplication.c', 'tfenotebook.c', '../tfetextview/tfetextview.c')
9
10 executable('tfe', sourcefiles, resources, dependencies: gtkdep)
~~~

View file

@ -41,7 +41,7 @@ It is a good practice for you to add more features.
5 gnome=import('gnome')
6 resources = gnome.compile_resources('resources','tfe.gresource.xml')
7
8 sourcefiles=files('tfeapplication.c', 'tfenotebook.c', 'tfetextview.c')
8 sourcefiles=files('tfeapplication.c', 'tfenotebook.c', '../tfetextview/tfetextview.c')
9
10 executable('tfe', sourcefiles, resources, dependencies: gtkdep)
~~~
@ -131,7 +131,7 @@ It is a good practice for you to add more features.
~~~C
1 #include <gtk/gtk.h>
2
3 #include "tfetextview.h"
3 #include "../tfetextview/tfetextview.h"
4 #include "tfenotebook.h"
~~~
@ -329,239 +329,246 @@ It is a good practice for you to add more features.
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 }
53 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
54 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
55 lab = gtk_label_new (filename);
56 i = gtk_notebook_append_page (nb, scr, lab);
57 nbp = gtk_notebook_get_page (nb, scr);
58 g_object_set (nbp, "tab-expand", TRUE, NULL);
59 gtk_notebook_set_current_page (nb, i);
60 g_signal_connect (GTK_TEXT_VIEW (tv), "change-file", G_CALLBACK (file_changed), nb);
61 }
62
63 static void
64 open_response (TfeTextView *tv, gint response, GtkNotebook *nb) {
65 GFile *file;
66 char *filename;
67
68 if (response != TFE_OPEN_RESPONSE_SUCCESS) {
69 g_object_ref_sink (tv);
70 g_object_unref (tv);
71 }else if (! G_IS_FILE (file = tfe_text_view_get_file (tv))) {
72 g_object_ref_sink (tv);
73 g_object_unref (tv);
74 }else {
75 filename = g_file_get_basename (file);
76 g_object_unref (file);
77 notebook_page_build (nb, GTK_WIDGET (tv), filename);
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
79 }
80
81 void
82 notebook_page_open (GtkNotebook *nb) {
83 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
84
85 GtkWidget *tv;
86
87 tv = tfe_text_view_new ();
88 g_signal_connect (TFE_TEXT_VIEW (tv), "open-response", G_CALLBACK (open_response), nb);
89 tfe_text_view_open (TFE_TEXT_VIEW (tv), gtk_widget_get_ancestor (GTK_WIDGET (nb), GTK_TYPE_WINDOW));
90 }
91
92 void
93 notebook_page_new_with_file (GtkNotebook *nb, GFile *file) {
94 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
95 g_return_if_fail(G_IS_FILE (file));
96
97 GtkWidget *tv;
98 char *filename;
99
100 if ((tv = tfe_text_view_new_with_file (file)) == NULL)
101 return; /* read error */
102 filename = g_file_get_basename (file);
103 notebook_page_build (nb, tv, filename);
104 }
105
106 void
107 notebook_page_new (GtkNotebook *nb) {
108 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
109
110 GtkWidget *tv;
111 char *filename;
112
113 tv = tfe_text_view_new ();
114 filename = get_untitled ();
115 notebook_page_build (nb, tv, filename);
116 }
117
~~~
## tfetextview.h
~~~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)
1 #ifndef __TFE_TEXT_VIEW_H__
2 #define __TFE_TEXT_VIEW_H__
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
4 #include <gtk/gtk.h>
5
6 #define TFE_TYPE_TEXT_VIEW tfe_text_view_get_type ()
7 G_DECLARE_FINAL_TYPE (TfeTextView, tfe_text_view, TFE, TEXT_VIEW, GtkTextView)
8
9 /* "open-response" signal response */
10 enum TfeTextViewOpenResponseType
11 {
12 TFE_OPEN_RESPONSE_SUCCESS,
13 TFE_OPEN_RESPONSE_CANCEL,
14 TFE_OPEN_RESPONSE_ERROR
15 };
16
17 GFile *
18 tfe_text_view_get_file (TfeTextView *tv);
19
20 void
21 tfe_text_view_open (TfeTextView *tv, GtkWidget *win);
22
23 void
24 tfe_text_view_save (TfeTextView *tv);
25
26 void
27 tfe_text_view_saveas (TfeTextView *tv);
28
29 GtkWidget *
30 tfe_text_view_new_with_file (GFile *file);
31
32 GtkWidget *
33 tfe_text_view_new (void);
34
35 #endif /* __TFE_TEXT_VIEW_H__ */
~~~
## tfetextview.c
~~~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);
1 #include <string.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 enum {
13 CHANGE_FILE,
14 OPEN_RESPONSE,
15 NUMBER_OF_SIGNALS
16 };
17
18 static guint tfe_text_view_signals[NUMBER_OF_SIGNALS];
19
20 static void
21 tfe_text_view_dispose (GObject *gobject) {
22 TfeTextView *tv = TFE_TEXT_VIEW (gobject);
23
24 if (G_IS_FILE (tv->file))
25 g_clear_object (&tv->file);
26
27 G_OBJECT_CLASS (tfe_text_view_parent_class)->dispose (gobject);
28 }
29
30 static void
31 tfe_text_view_init (TfeTextView *tv) {
32 tv->file = NULL;
33 }
34
35 static void
36 tfe_text_view_class_init (TfeTextViewClass *class) {
37 GObjectClass *object_class = G_OBJECT_CLASS (class);
38
39 object_class->dispose = tfe_text_view_dispose;
40 tfe_text_view_signals[CHANGE_FILE] = g_signal_newv ("change-file",
41 G_TYPE_FROM_CLASS (class),
42 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
43 NULL /* closure */,
44 NULL /* accumulator */,
45 NULL /* accumulator data */,
46 NULL /* C marshaller */,
47 G_TYPE_NONE /* return_type */,
48 0 /* n_params */,
49 NULL /* param_types */);
50 GType param_types[] = {G_TYPE_INT};
51 tfe_text_view_signals[OPEN_RESPONSE] = g_signal_newv ("open-response",
52 G_TYPE_FROM_CLASS (class),
53 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
54 NULL /* closure */,
55 NULL /* accumulator */,
56 NULL /* accumulator data */,
57 NULL /* C marshaller */,
58 G_TYPE_NONE /* return_type */,
59 1 /* n_params */,
60 param_types);
61 }
62
63 GFile *
64 tfe_text_view_get_file (TfeTextView *tv) {
65 g_return_val_if_fail (TFE_IS_TEXT_VIEW (tv), NULL);
66
67 return g_file_dup (tv->file);
68 }
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);
70 static void
71 open_dialog_response(GtkWidget *dialog, gint response, TfeTextView *tv) {
72 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
73 GFile *file;
74 char *contents;
75 gsize length;
76 GtkWidget *message_dialog;
77 GError *err = NULL;
78
79 if (response != GTK_RESPONSE_ACCEPT)
80 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_CANCEL);
81 else if (! G_IS_FILE (file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog))))
82 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
83 else if (! g_file_load_contents (file, NULL, &contents, &length, NULL, &err)) { /* read error */
84 if (G_IS_FILE (file))
85 g_object_unref (file);
86 message_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL,
87 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
88 "%s.\n", err->message);
89 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
90 gtk_widget_show (message_dialog);
91 g_error_free (err);
92 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
93 } else {
94 gtk_text_buffer_set_text (tb, contents, length);
95 g_free (contents);
96 if (G_IS_FILE (tv->file))
97 g_object_unref (tv->file);
98 tv->file = file;
99 gtk_text_buffer_set_modified (tb, FALSE);
100 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS);
101 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
102 }
103 gtk_window_destroy (GTK_WINDOW (dialog));
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));
105
106 void
107 tfe_text_view_open (TfeTextView *tv, GtkWidget *win) {
108 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
109 g_return_if_fail (GTK_IS_WINDOW (win));
110
111 GtkWidget *dialog;
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)) {
113 dialog = gtk_file_chooser_dialog_new ("Open file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_OPEN,
114 "Cancel", GTK_RESPONSE_CANCEL,
115 "Open", GTK_RESPONSE_ACCEPT,
116 NULL);
117 g_signal_connect (dialog, "response", G_CALLBACK (open_dialog_response), tv);
118 gtk_widget_show (dialog);
119 }
120
121 static void
122 saveas_dialog_response (GtkWidget *dialog, gint response, TfeTextView *tv) {
123 GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
124 GFile *file;
125
126 if (response == GTK_RESPONSE_ACCEPT) {
127 file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
128 if (G_IS_FILE(file)) {
129 if (G_IS_FILE (tv->file))
130 g_object_unref (tv->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);
@ -584,7 +591,7 @@ It is a good practice for you to add more features.
150 GError *err = NULL;
151
152 if (! gtk_text_buffer_get_modified (tb))
153 return; /* no necessary to save it */
153 return; /* no need to save it */
154 else if (tv->file == NULL)
155 tfe_text_view_saveas (tv);
156 else {
@ -593,77 +600,77 @@ It is a good practice for you to add more features.
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. */
162 /* It is possible that tv->file is broken or you don't have permission to write. */
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);
168 message_dialog = gtk_message_dialog_new (GTK_WINDOW (win), GTK_DIALOG_MODAL,
169 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
170 "%s.\n", err->message);
171 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
172 gtk_widget_show (message_dialog);
173 g_error_free (err);
174 }
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
177
178 void
179 tfe_text_view_saveas (TfeTextView *tv) {
180 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
181
182 GtkWidget *dialog;
183 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
184
185 dialog = gtk_file_chooser_dialog_new ("Save file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_SAVE,
186 "Cancel", GTK_RESPONSE_CANCEL,
187 "Save", GTK_RESPONSE_ACCEPT,
188 NULL);
189 g_signal_connect (dialog, "response", G_CALLBACK (saveas_dialog_response), tv);
190 gtk_widget_show (dialog);
191 }
192
193 GtkWidget *
194 tfe_text_view_new_with_file (GFile *file) {
195 g_return_val_if_fail (G_IS_FILE (file), NULL);
196
197 GtkWidget *tv;
198 GtkTextBuffer *tb;
199 char *contents;
200 gsize length;
201
202 if (! g_file_load_contents (file, NULL, &contents, &length, NULL, NULL)) /* read error */
203 return NULL;
204
205 tv = tfe_text_view_new();
206 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
207 gtk_text_buffer_set_text (tb, contents, length);
208 g_free (contents);
209 TFE_TEXT_VIEW (tv)->file = g_file_dup (file);
210 return tv;
211 }
212
213 GtkWidget *
214 tfe_text_view_new (void) {
215 return GTK_WIDGET (g_object_new (TFE_TYPE_TEXT_VIEW, NULL));
216 }
217
~~~
## Total number of lines, words and charcters
## Total number of lines, words and characters
$ 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
$ LANG=C wc tfe5/meson.build tfe5/tfeapplication.c tfe5/tfe.gresource.xml tfe5/tfe.h tfe5/tfenotebook.c tfe5/tfenotebook.h tfetextview/tfetextview.c tfetextview/tfetextview.h tfe5/tfe.ui
10 17 294 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
4 6 87 tfe5/tfe.h
117 325 3064 tfe5/tfenotebook.c
12 17 196 tfe5/tfenotebook.h
218 635 7769 tfe5/tfetextview.c
29 49 561 tfe5/tfetextview.h
217 637 7725 tfetextview/tfetextview.c
35 60 701 tfetextview/tfetextview.h
64 105 2266 tfe5/tfe.ui
576 1507 17864 total
582 1524 18062 total
Up: [Readme.md](../Readme.md), Prev: [Section 14](sec14.md), Next: [Section 16](sec16.md)

View file

@ -33,7 +33,7 @@ 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.
GMenuModel is an abstract object which represents a menu.
GMenu is a simple implementation of GMenuModel and a child object of GMenuModel.
GObjct -- GMenuModel -- GMenu
@ -51,7 +51,7 @@ GMenuItem and Gmenu (or GMenuModel) don't have a parent-child relationship.
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.
"Cut", "Copy", "Paste" and "Select All" are also the labels of menu items.
Other attributes will be explained later.
Some menu items have a link to another GMenu.
@ -179,18 +179,18 @@ You don' t need to care about it.
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`.
- 11-13: Generates a GtkApplicationWindow and assigns a pointer to it to `win`. And sets the title and default size.
- 15: Generates 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`.
If you don't need the parameter, pass `NULL`.
Therefore, `act_quit` has a name "quit" and no parameter.
- 16: Add the action to GtkApplication `app`.
- 16: Adds 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.
- 17: Connects "activate" signal of the action and the handler `quit_activated`.
- 19-22: Generates 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.
@ -198,19 +198,19 @@ This function is described in GMenuModel section in GIO API reference.
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`.
- 23-24: Appends `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`.
- 25: Sets 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`.
- 26-27: Appends `menu_item_menu` to `menubar`.
Then frees `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.
- 29: The menu is inserted to GtkApplication.
- 30: Sets GtkApplicationWindow to show the menubar.
- 31: Shows the window.
![menu and action](../image/menu1.png)

View file

@ -47,10 +47,10 @@ 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`.
- connects 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.
Then, the default behavior for boolean-stated actions with a NULL parameter type like `act_fullscreen` is to toggle them via the “change-state” signal.
The following is the "change-state" signal handler.
@ -69,20 +69,20 @@ fullscreen_changed(GSimpleAction *action, GVariant *value, gpointer win) {
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`.
The 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.
- Sets the state of the action to `value`.
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`.
So, you need to set the state to the new value by `g_simple_action_set_state`.
You can use "activate" signal instead ot "change-state" signal, or both signals.
You can use "activate" signal instead of "change-state" signal, or both signals.
But the way above is the simplest and best.
### GVariant
GVarient can contain boolean, string or other simple type values.
For example, the following program set TRUE to `value` whose type is GVariant.
For example, the following program assigns TRUE to `value` whose type is GVariant.
~~~C
GVariant *value = g_variant_new_boolean (TRUE);
@ -157,10 +157,10 @@ 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`.
- connects 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.
Then the default behavior is to call `g_simple_action_set_state()` to set the state to the requested value.
The following is the "activate" signal handler.
@ -183,10 +183,10 @@ 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`.
- Sets the color of the css provider.
- Frees the string `color`.
- Changes the state by `g_action_change_state`.
The function just sets the state of the action to the parameter 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`.
@ -221,7 +221,7 @@ It finally output the string "s".
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.
- prints the string to the terminal.
## Example code
The following code includes stateful actions above.
@ -349,7 +349,7 @@ The code is as follows.
- 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.
`win` has a title "menu2" and its default size is 400x300.
`lb` is named as "lb".
The name is used in CSS.
`lb` is set to `win` as a child.
@ -360,7 +360,7 @@ 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.
- 45-54: Generates GMenu and GMenuItem.
There are three sections.
- 56-61: Signals are connected to handlers.
And actions are added to GActionMap.
@ -369,15 +369,15 @@ 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.
- 63-77: Connects and builds the menus.
Useless GMenuItem are freed.
- 79-80: GMenuModel `menubar` is set to `app`.
Set show menubar property to `TRUE` in `win`.
- 79-80: GMenuModel `menubar` is inserted to `app`.
Sets 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.
You may use GtkBox as a child widget of the window and insert GtkPopoverMenubar as the first child of the box.
- 82-87: Sets CSS.
`provider` is GtkCssProvider which is defined in line three as a static variable.
Its CSS data is:
`label#lb {background-color: red;}`.
@ -390,7 +390,7 @@ 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.
- 90: Shows the window.
Up: [Readme.md](../Readme.md), Prev: [Section 16](sec16.md), Next: [Section 18](sec18.md)

View file

@ -22,7 +22,7 @@ The file starts and ends with interface tag.
`menu` tag corresponds to GMenu object.
`id` attribute defines the name of the object.
It will be refered by GtkBuilder.
It will be referred by GtkBuilder.
~~~xml
<submenu>
@ -144,7 +144,7 @@ The following is the ui file of the menu in `menu3`.
72 </interface>
~~~
The ui file is converted to the resource by the resouce compiler `glib-compile-resouces` with xml file below.
The ui file is converted to the resource by the resource compiler `glib-compile-resouces` with xml file below.
~~~xml
1 <?xml version="1.0" encoding="UTF-8"?>
@ -165,7 +165,7 @@ 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.
It is important that `builder` is unreferred after the GMenuModel `menubar` is inserted to the application.
If you do it before setting, bad thing will happen -- your computer might freeze.
## Action entry
@ -215,9 +215,9 @@ g_action_map_add_action_entries (G_ACTION_MAP (app), app_entries,
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`.
- Builds the "quit" action
- Connects the action and the "activate" signal handler `quit_activate`
- Adds the action to the action map `app`.
The same goes for the other actions.
@ -231,12 +231,12 @@ g_action_map_add_action_entries (G_ACTION_MAP (win), win_entries,
~~~
The code above does:
- Build a "fullscreen" action and "color" action.
- Connect the "fullscreen" action and the "change-state" signal handler `fullscreen_changed`
- Builds a "fullscreen" action and "color" action.
- Connects 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`
- Connects 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`.
- Adds the actions to the action map `win`.
## Example code

View file

@ -17,15 +17,15 @@ In this section, I will explain:
Cairo is a two dimensional graphics library.
First, you need to know surface, source, mask, destination, cairo context and transformation.
- surface represents an image.
- Surface represents an image.
It is like a canvas.
We can draw shapes and images with different colors on surfaces.
- source pattern, or simply source, is a kind of paint, which will be transfered to destination surface by cairo functions.
- mask is image mask used in the transference.
- destination is a target surface.
- cairo context manages the transference from source to destination through mask with its functions.
- Source pattern, or simply source, is a kind of paint, which will be transferred to destination surface by cairo functions.
- Mask is image mask used in the transference.
- Destination is a target surface.
- Cairo context manages the transference from source to destination through mask with its functions.
For example, `cairo_stroke` is a function to draw a path to the destination by the transference.
- transformation is applied before the transfer completes.
- Transformation is applied before the transfer completes.
The transformation is called affine, which is a mathematics terminology, and represented by matrix multiplication and vector addition.
Scaling, rotation, reflection, shearing and translation are examples of affine transformation.
In this section, we don't use it.
@ -37,8 +37,8 @@ Therefore, the coordinate in source and mask is the same as the coordinate in de
The instruction is as follows:
1. Create a surface.
This will be a destnation.
2. Create a cairo context with the surface and set it to the destination.
This will be a destination.
2. Create a cairo context with the surface and the surface will be the destination of the context.
3. Create a source pattern within the context.
4. Create paths, which are lines, rectangles, arcs, texts or more complicated shapes, to generate a mask.
5. Use drawing operator such as `cairo_stroke` to transfer the paint in the source to the destination.
@ -80,31 +80,31 @@ Here's a simple example code that draws a small square and save it as a png file
31 }
~~~
- 1: Include the header file of cairo.
- 1: Includes the header file of cairo.
- 12: `cairo_image_surface_create` creates an image surface.
`CAIRO_FORMAT_RGB24` is a constant which means that each pixel has red, green and blue data.
Each data has 8 bit quantity.
Modern displays have this type of color depth.
Width and hieight are pixels and given as integers.
- 13: Create cairo context.
The surface given as an argument will be set to the destination of the context.
Width and height are pixels and given as integers.
- 13: Creates cairo context.
The surface given as an argument will be the destination of the context.
- 17: `cairo_set_source_rgb` creates a source pattern, which is a solid white paint.
The second to fourth argument is red, green and blue color depth respectively.
Their type is float and the values are between zero and one.
(0,0,0) is black and (1,1,1) is white.
- 18: `cairo_paint` copies everywhere in the source to destination.
The destination is filled with white pixels by this command.
- 20: Set the source color to black.
- 20: Sets the source color to black.
- 21: `cairo_set_line_width` set the width of lines.
In this case, the line width is set to two pixels.
(It is because the transformation is identity.
If we set it different one, for example scaling with the factor three, the actual width in destnation is six (2x3=6) pixels.)
- 22: Draw a rectangle (square).
If the transformation isn't identity, for example scaling with the factor three, the actual width in destination will be six (2x3=6) pixels.)
- 22: Draws a rectangle (square).
The top-left coordinate is (width/2.0-20.0, height/2.0-20.0) and the width and height have the same length 40.0.
- 23: `cairo_stroke` transfer the source to destnation through the rectangle in mask.
- 26: Output the image to a png file `rectangle.png`.
- 27: Destroy the context. At the same time the source is destroyed.
- 28: Destroy the destnation surface.
- 23: `cairo_stroke` transfer the source to destination through the rectangle in mask.
- 26: Outputs the image to a png file `rectangle.png`.
- 27: Destroys the context. At the same time the source is destroyed.
- 28: Destroys the destination surface.
To compile this, type the following.
@ -164,10 +164,10 @@ The following is a very simple example.
The function `main` is almost same as before.
The two functions `on_activate` and `draw_function` is important in this example.
- 16: Generate a GtkDrawingArea object.
- 20,21: Set the width and height of the contents of the GtkDrawingArea widget.
- 16: Generates a GtkDrawingArea object.
- 20,21: Sets the width and height of the contents of the GtkDrawingArea widget.
These width and height is the size of the destination surface of the cairo context provided by the widget.
- 22: Set a drawng function to the widget.
- 22: Sets a drawing function of the widget.
GtkDrawingArea widget uses the function to draw the contents of itself whenever its necessary.
For example, when a user drag a mouse pointer and resize a top level window, GtkDrawingArea also changes the size.
Then, the whole window needs to be redrawn.
@ -180,16 +180,16 @@ The drawing function has five parameters.
The first parameter is the GtkDrawingArea widget which calls the drawing function.
However, you can't change any properties, for example `content-width` or `content-height`, in this function.
The second parameter is a cairo context given by the widget.
The destnation surface of the context is connected to the contents of the widget.
The destination surface of the context is connected to the contents of the widget.
What you draw to this surface will appear in the widget on the screen.
The third and fourth paranmeters are the size of the destination surface.
The third and fourth parameters are the size of the destination surface.
- 3-11: The drawing function.
- 4-5: Set the source to be white and paint the destination white.
- 7: Set the line width to be 2.
- 8: Set the source to be black.
- 9: Add a rectangle to the mask.
- 10: Draw the rectangle with black color to the destination.
- 4-5: Sets the source to be white and paint the destination white.
- 7: Sets the line width to be 2.
- 8: Sets the source to be black.
- 9: Adds a rectangle to the mask.
- 10: Draws the rectangle with black color to the destination.
Compile and run it, then a window with a black rectangle (square) appears.
Try resizing the window.

View file

@ -3,7 +3,7 @@ Up: [Readme.md](../Readme.md), Prev: [Section 1](sec1.md), Next: [Section 3](se
# Installation of gtk4 to linux distributions
This section describes how to install gtk4 into linux distributions.
However, I only have an experience to install it to ubuntu 20.10.
However, I only have an experience to install it to Ubuntu 20.10.
Probably you need more than the explanation below.
This tutorial including this section is without any warranty.
@ -24,9 +24,9 @@ If you want to install it in the system area, `/opt/gtk4` is one of good choices
[Gtk4 API Reference](https://gnome.pages.gitlab.gnome.org/gtk/gtk/gtk-building.html) gives an installation example to `/opt/gtk4`.
Don't install it to `/usr/local` which is the default.
It is used by ubuntu applications, which are not build on gtk4.
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.
Actually I did it and I needed to reinstall Ubuntu.
## Glib installation
@ -79,7 +79,7 @@ or
$ source env.sh
This command carries out the commands in `env.sh` and changes the environment variables above in the corrent shell.
This command carries out the commands in `env.sh` and changes the environment variables above in the current shell.
## Pango installation
@ -100,7 +100,7 @@ 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.
It is a text file which keep the list of 'share' directories 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.
$ export XDG_DATA_DIRS=$HOME/local/share:$XDG_DATA_DIRS

View file

@ -127,7 +127,7 @@ It has "name" attribute which is a signal name and "handler" attribute which is
Options "-WI, --export-dynamic" CFLAG is necessary when you compile the application.
You can achieve this by adding "export_dynamic: true" argument to executable function in `meson.build`.
And be careful that the handler must be defined without 'static' class.
- 54-76: Put GtkScrolledWindow and GtkDrawingArea into GtkBox.
- 54-76: Puts GtkScrolledWindow and GtkDrawingArea into GtkBox.
GtkBox has "homogeneous property with TRUE value, so the two children have the same width in the box.
TfeTextView is a child of GtkScrolledWindow.
@ -149,17 +149,13 @@ First two files are almost same as before.
The only difference is the header file in tfettextview.c.
$ diff tfe5/tfetextview.c color/tfetextview.c
1c1
< #include "tfe.h"
---
> #include "color.h"
Color.h just includes tfetextview.h.
~~~C
1 #include <gtk/gtk.h>
2
3 #include "tfetextview.h"
3 #include "../tfetextview/tfetextview.h"
~~~
# Colorapplication.c
@ -167,10 +163,10 @@ Color.h just includes tfetextview.h.
This is the main file.
It deals with:
- Build widgets by GtkBuilder.
- Set drawing function to GtkDrawingArea.
And connect a handler to "resize" signal on GtkDrawingArea.
- Implement each call back functions.
- Building widgets by GtkBuilder.
- Seting a drawing function of GtkDrawingArea.
And connecting a handler to "resize" signal on GtkDrawingArea.
- Implementing each call back functions.
Particularly, `Run` signal handler is the point in this program.
The following is `colorapplication.c`.
@ -304,18 +300,18 @@ The following is `colorapplication.c`.
The application ID is "com.github.ToshioCP.color".
`G_APPLICATION_FLAGS_NONE` is specified so no open signal handler is necessary.
- 86-106: Startup handler.
- 91-96: Build widgets.
- 91-96: Builds widgets.
The pointers of the top window, TfeTextView and GtkDrawingArea objects are stored to static variables `win`, `tv` and `da` respectively.
This is because these objects are often used in handlers.
They never be rewritten so they're thread safe.
- 97: connect "resize" signal and the handler.
- 98: set the drawing function.
- 81-84: Activate handler, which just show the widgets.
- 97: connects "resize" signal and the handler.
- 98: sets the drawing function.
- 81-84: Activates handler, which just shows the widgets.
- 73-79: The drawing function.
It just copy `surface` to destination.
It just copies `surface` to destination.
- 65-71: Resize handler.
Re-create the surface to fit the width and height of the drawing area and paint by calling the function `run`.
- 58-63: Close handler.
Re-creates the surface to fit the width and height of the drawing area and paints by calling the function `run`.
- 58-63: Closes the handler.
It destroys `surface` if it exists.
Then it destroys the top window and quits the application.
- 48-56: Open and save handler.
@ -328,9 +324,9 @@ It is important to know that the drawing function is called when it is necessary
For example, when another window is moved and uncovers part of the widget, or when the window containing it is resized.
But repaint of `surface` is not automatically notified to gtk.
Therefore, you need to call `gtk_widget_queue_draw` to redraw the widget.
- 9-40: Run function paint the surface.
- 9-40: Run function paints the surface.
First, it gets the contents of GtkTextBuffer.
Then compare it to "red", "green" and so on.
Then it compares it to "red", "green" and so on.
If it matches the color, then the surface is painted the color.
If it matches "light" or "dark", then the color of the surface is lightened or darkened respectively.
Alpha channel is used.
@ -348,7 +344,7 @@ An argument "export_dynamic: true" is added to executable function.
5 gnome=import('gnome')
6 resources = gnome.compile_resources('resources','color.gresource.xml')
7
8 sourcefiles=files('colorapplication.c', 'tfetextview.c')
8 sourcefiles=files('colorapplication.c', '../tfetextview/tfetextview.c')
9
10 executable('color', sourcefiles, resources, dependencies: gtkdep, export_dynamic: true)
~~~

View file

@ -7,7 +7,7 @@ Up: [Readme.md](../Readme.md), Prev: [Section 2](sec2.md), Next: [Section 4](se
### GtkApplication and g\_application\_run
Usually people write a programming code to make an application.
What are appications?
What are applications?
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.
@ -202,7 +202,7 @@ Now rewrite the function `on_activate`.
8 }
~~~
Widget is an abstract concept that includes all the GUI interfaces such as windows, dialogs, buttons, multiline text, containers and so on.
Widget is an abstract concept that includes all the GUI interfaces such as windows, dialogs, buttons, multi-line text, containers and so on.
And GtkWidget is a base object from which all the GUI objects derive.
parent <-----> child

View file

@ -141,9 +141,9 @@ The following program shows how to catch the signal and do something.
~~~
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`.
First, it generates a GtkButton widget `btn` with a label "Click me".
Then, adds the button to the window `win` as a child.
Finally, connects 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".
@ -233,7 +233,7 @@ 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 GtkBox and add it to GtkApplicationWindow as a child.
- Generate GtkButton and append it to GtkBox.
- Generate another GtkButton and append it to GtkBox.

View file

@ -6,7 +6,7 @@ Up: [Readme.md](../Readme.md), Prev: [Section 4](sec4.md), Next: [Section 6](se
### GtkTextView and GtkTextBuffer
GtkTextview is a widget for multiline text editing.
GtkTextview is a widget for multi-line text editing.
GtkTextBuffer is a text buffer which is connected to GtkTextView.
See a sample program `tfv1.c` below.
@ -66,9 +66,9 @@ 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.
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.
When it is set to `GTK_WRAP_WORD_CHAR`, text wraps in between words, or if that is not enough, also between graphemes.
In line 30, `tv` is set to `win` as a child.
In line 30, `tv` is added to `win` as a child.
Now compile and run it.
@ -86,8 +86,8 @@ You can solve it by putting GtkScrolledWindow between GtkApplicationWindow and G
What we need to do is:
- Generate GtkScrolledWindow and set it as a child of GtkApplicationWindow.
- Set GtkTextView as a child of GtkScrolledWindow.
- Generate GtkScrolledWindow and insert it to GtkApplicationWindow as a child.
- insert GtkTextView to GtkScrolledWindow as a child.
Modify `tfv1.c` and save it as `tfv2.c`.
The difference between these two files is very little.

View file

@ -15,7 +15,7 @@ When the program starts, we give a filename as an argument.
$ ./a.out filename
Then it opens the file and set it into GtkTextBuffer.
Then it opens the file and inserts 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.
@ -89,10 +89,10 @@ The way how to read a file using GFiles will be described in the next section.
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 it is given arguments, it recognizes the first argument as a filename and opens it.
- If opening the file succeeds, it reads the contents of the file and inserts it to GtkTextBuffer and shows the window.
- If it fails to open the file, shows an error message and quits.
- If there's no argument, it shows an error message and quits.
- If there are two or more arguments, the second one and after are ignored.
The program is as follows.
@ -177,10 +177,10 @@ 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.
- Sets wrap mode to `GTK_WRAP_WORD_CHAR` in GtktextView.
- Sets GtkTextView to non-editable because the program isn't an editor but only a viewer.
- Reads the file and inserts the text into GtkTextBuffer (this will be explained in detail later).
- If the file is not opened then outputs an error message and destroys the window. It makes the application quit.
The file reading part of the program is shown again below.
@ -199,12 +199,12 @@ if (g_file_load_contents(files[0], NULL, &contents, &length, NULL, NULL)) {
}
~~~
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`.
The function `g_file_load_contents` loads the file contents into a buffer, which is automatically allocated, and sets `contents` to point the buffer.
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 the function succeeds, it inserts the contents into GtkTextBuffer, frees the buffer memories pointed by `contents`, sets the title of the window,
frees the memories pointed by `filename` and shows the window.
If it fails, it outputs an error message and destroys the window.
## GtkNotebook
@ -300,21 +300,21 @@ Now I want to show you the program `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.
- 11-13: Variables `nb`, `lab` and `nbp` are defined and points 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.
- 27-28 GtkNotebook is generated and inserted to the GtkApplicationWindow as a child.
- 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:
- 39-42 inserts the contents of the file into GtkTextBuffer and frees the memory pointed by `contents`. Gets the filename and generates GtkLabel with the filename.
- 43: Appends 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`.
- 44: Gets GtkNotebookPage object and sets `nbp` to point the GtkNotebookPage.
- 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`
- 46: frees 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

@ -18,7 +18,7 @@ We just add two things to the file viewer.
A couple of ways are possible to get memories to keep GFile.
- Use global variables.
- make a child widget object and extend the memories allocated to the widget.
- make a child object so that it can extend the memories for the GFile object.
Using global variables is easy to implement.
Define a sufficient size array of pointers to GFile.
@ -50,12 +50,11 @@ It has everything that GtkTextView has.
For example, TfeTextView has GtkTextbuffer corresponds to GtkTextView inside TfeTextView.
And important thing is that TfeTextView can have a memory to keep a pointer to GFile.
However, to understand the general theory about gobjects is very hard especially for beginners.
However, to understand the general theory about Gobject 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 subsection.
## How to define a child widget of GtkTextView
Let's define TfeTextView object which is a child object of GtkTextView.
First, look at the program below.
@ -97,7 +96,7 @@ tfe_text_view_new (void) {
If you are curious about the background theory of this program, It's very good for you.
Because knowing the theory is very important for you to program GTK applications.
Look at GObject API reference.
Look at [GObject API reference](https://developer.gnome.org/gobject/stable/).
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.
@ -127,7 +126,7 @@ 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.
`tv` is a pointer to TfeTextView object instance which is a C-structure 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.
@ -148,7 +147,14 @@ It will be modified later.
## Close-request signal
After editing a file, `tfe1.c` writes files just before the window closes.
Imagine that you use this editor.
First, you run the editor with arguments.
The arguments are filenames.
The editor reads the files and shows its window with the text of files in it.
Then you edit the text.
After you finish editing, you exit the editor.
The editor updates 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.
@ -159,9 +165,9 @@ The function `before_close` is invoked when the signal "close-request" is emitte
g_signal_connect (win, "close-request", G_CALLBACK (before_close), NULL);
~~~
The argument win is GtkApplicationWindow, in which the signal "close-request" is defined, and before\_close is the handler.
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.
The program of `before_close` is as follows.
~~~C
1 static gboolean
@ -193,11 +199,14 @@ The program of before\_close is as follows.
The numbers on the left of items are line numbers in the source code.
- 13: Get the number of pages `nb` has.
- 13: Gets 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.
- 15-17: Gets GtkScrolledWindow, TfeTextView and a pointer to GFile.
The pointer was stored when `on_open` handler had run. It will be shown later.
- 18-20: Gets GtkTextBuffer and contents. `start_iter` and `end_iter` are 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: Writes the file.
## Source code of tfe1.c
@ -272,7 +281,7 @@ Now I will show you all the source code of `tfe1`.c.
66
67 static void
68 on_activate (GApplication *app, gpointer user_data) {
69 g_print ("You need a filename argument.\n");
69 g_print ("You need to give filenames as arguments.\n");
70 }
71
72 static void
@ -342,11 +351,11 @@ Now I will show you all the source code of `tfe1`.c.
136
~~~
- 102: set the pointer to GFile into TfeTextView.
- 102: Sets 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.
`g_file_dup` duplicates the given GFile structure.
- 118: Connects "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.

View file

@ -5,7 +5,7 @@ Up: [Readme.md](../Readme.md), Prev: [Section 7](sec7.md), Next: [Section 9](se
## New, open and save button
We made the simplest editor in the previous section.
It reads the files in `on_open` function at start-up and writes it when closing the window.
It reads the files in `on_open` function at start-up and writes them when closing the 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.
@ -107,21 +107,21 @@ The function `on_open` in the source code `tfe2.c` is as follows.
The point is how to build the window.
- 25-27: Generate GtkApplicationWindow and set its title and default size.
- 29-30: Generate GtkBox `boxv`.
- 25-27: Generates GtkApplicationWindow and sets the title and default size.
- 29-30: Generates 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.
- 32-33: Generates GtkBox `boxh` and appends it to 'boxv' as a first child.
- 35-40: Generates three dummy labels.
The labels `dmy1` and `dmy3` has a character width of ten.
The other label `dmy2` is set hexpand property TRUE.
The other label `dmy2` has hexpand property which is set to be 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.
- 41-44: Generates four buttons.
- 46-52: Appends these GtkLabel and GtkButton to `boxh`.
- 54-57: Generates GtkNotebook and sets hexpand and vexpand properties TRUE.
This makes it expand horizontally and vertically as big as possible.
It is appended to `boxv` as the second child.
The number of lines is 33(=57-25+1) to build the widgets.
@ -131,7 +131,7 @@ 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.
It reduces the cumbersome work.
## Ui file
@ -200,7 +200,8 @@ First, let's look at the ui file `tfe3.ui` that defines a structure of the widge
~~~
This is coded with XML structure.
Constructs beginning with `<` and ending with `>` are called tags, and are divided into two parts, start tag and end tag.
Constructs beginning with `<` and ending with `>` are called tags.
And there are two types of tags, 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.
@ -213,7 +214,7 @@ And the three properties of the window are defined.
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.
Those two describe the same structure of widgets.
## GtkBuilder
@ -228,7 +229,8 @@ 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_new_from_file` reads the file given as an argument.
Then, it builds the widgets and pointers to them, creates GtkBuilder object and put the pointers 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.
@ -297,8 +299,7 @@ This reduces lines in the C source file.
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.
Now I'll show you `on_open` function in the C source code `tfe3.c`.
~~~C
1 static void
@ -351,9 +352,9 @@ Only functions `on_open` are shown as follows.
48 }
~~~
The source code of `tfe3.c` is stored in [src/tfe](https://github.com/ToshioCP/Gtk4-tutorial/tree/main/src/tfe) directory.
The whole 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).
You can also get the source files below.
### Using ui string
@ -385,8 +386,8 @@ 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.
- Add backslash before each double quote.
- Add double quote at the left and right.
### Using Gresource
@ -408,10 +409,10 @@ It describes resource files.
6 </gresources>
~~~
- 2: gresources tag can include mulitple gresources (gresource tags).
- 2: `gresources` tag can include multiple 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.
- 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.
@ -450,7 +451,7 @@ 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
Modify `tfe3.c` and save it as `tfe3_r.c`.
~~~C
#include "resources.c"

View file

@ -4,13 +4,13 @@ Up: [Readme.md](../Readme.md), Prev: [Section 8](sec8.md), Next: [Section 10](s
## What do we need to think about to manage big source files?
We've managed to compile a small editor so far.
We've compiled a small editor so far.
But Some bad signs are beginning to appear.
- We have only one C source file and put everything into it.
- We've had 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.
We should control them by one building tool.
These ideas are useful to manage big source files.
@ -254,16 +254,16 @@ Dividing a file makes it easy to maintain source files.
But now we are faced with a new problem.
The building step increases.
- 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`.
- Compiling the ui file `tfe.ui` into `resources.c`.
- Compiling `tfe.c` into `tfe.o` (object file).
- Compiling `tfetextview.c` into `tfetextview.o`.
- Compiling `resources.c` into `resources.o`.
- Linking all the object files into application `tfe`.
Now build tool is necessary to manage it.
Make is one of the build tools.
It was originally created in 1976.
So it is an old and widely used program.
It was created in 1976.
It is an old and widely used program.
Make analyzes Makefile and executes compilers.
All instructions are written in Makefile.
@ -286,7 +286,7 @@ 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.
If the modification time of `sample.c` is older then the generation of `sample.o`, then no compiling is necessary, so make does nothing.
The Makefile for `tfe` is as follows.
@ -368,19 +368,19 @@ Rake has task and file task, which is similar to target, prerequisite and recipe
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.
- 3-6: Defines target file, source file and so on.
- 1, 8: Loads clean library. And defines 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.
- 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.prerequisites is an array of prerequisites.
- 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.
- 16-21: There's a loop by each element of the array of objfiles. Each object depends on corresponding source file.
- 23-25: Resource 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.
@ -410,17 +410,17 @@ To use meson, you first need to write `meson.build` file.
~~~
- 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.
The first parameter is the name of the project and the second is the programming 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`.
- 5: `import` function imports a module.
In line 5, gnome module is imported and assigned 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.
- 8: Defines 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.
@ -435,6 +435,8 @@ Then, the executable file `tfe` is generated under the directory `_build`.
$ _build/tfe tfe.c tfetextview.c
Then the window appears.
And two notebook pages are in the window.
One notebook is `tfe.c` and the other is `tfetextview.c`.
I've shown you three build tools.
I think meson and ninja is the best choice for the present.

View file

@ -152,7 +152,7 @@ def change_rel_link line, org_dir, new_dir
left = ""
right = line
while right =~ /(!?\[[^\]]*\])\(([^\)]*)\)/
left = $`
left += $`
right = $'
name = $1
link = $2

View file

@ -1,3 +1,3 @@
#include <gtk/gtk.h>
#include "tfetextview.h"
#include "../tfetextview/tfetextview.h"

View file

@ -5,6 +5,6 @@ gtkdep = dependency('gtk4')
gnome=import('gnome')
resources = gnome.compile_resources('resources','color.gresource.xml')
sourcefiles=files('colorapplication.c', 'tfetextview.c')
sourcefiles=files('colorapplication.c', '../tfetextview/tfetextview.c')
executable('color', sourcefiles, resources, dependencies: gtkdep, export_dynamic: true)

View file

@ -1,218 +0,0 @@
#include "color.h"
struct _TfeTextView
{
GtkTextView parent;
GFile *file;
};
G_DEFINE_TYPE (TfeTextView, tfe_text_view, GTK_TYPE_TEXT_VIEW);
enum {
CHANGE_FILE,
OPEN_RESPONSE,
NUMBER_OF_SIGNALS
};
static guint tfe_text_view_signals[NUMBER_OF_SIGNALS];
static void
tfe_text_view_dispose (GObject *gobject) {
TfeTextView *tv = TFE_TEXT_VIEW (gobject);
if (G_IS_FILE (tv->file))
g_clear_object (&tv->file);
G_OBJECT_CLASS (tfe_text_view_parent_class)->dispose (gobject);
}
static void
tfe_text_view_init (TfeTextView *tv) {
GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
tv->file = NULL;
gtk_text_buffer_set_modified (tb, FALSE);
gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
}
static void
tfe_text_view_class_init (TfeTextViewClass *class) {
GObjectClass *object_class = G_OBJECT_CLASS (class);
object_class->dispose = tfe_text_view_dispose;
tfe_text_view_signals[CHANGE_FILE] = g_signal_newv ("change-file",
G_TYPE_FROM_CLASS (class),
G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
NULL /* closure */,
NULL /* accumulator */,
NULL /* accumulator data */,
NULL /* C marshaller */,
G_TYPE_NONE /* return_type */,
0 /* n_params */,
NULL /* param_types */);
GType param_types[] = {G_TYPE_INT};
tfe_text_view_signals[OPEN_RESPONSE] = g_signal_newv ("open-response",
G_TYPE_FROM_CLASS (class),
G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
NULL /* closure */,
NULL /* accumulator */,
NULL /* accumulator data */,
NULL /* C marshaller */,
G_TYPE_NONE /* return_type */,
1 /* n_params */,
param_types);
}
GFile *
tfe_text_view_get_file (TfeTextView *tv) {
g_return_val_if_fail (TFE_IS_TEXT_VIEW (tv), NULL);
return g_file_dup (tv->file);
}
static void
open_dialog_response(GtkWidget *dialog, gint response, TfeTextView *tv) {
GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
GFile *file;
char *contents;
gsize length;
GtkWidget *message_dialog;
GError *err = NULL;
if (response != GTK_RESPONSE_ACCEPT)
g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_CANCEL);
else if (! G_IS_FILE (file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog))))
g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
else if (! g_file_load_contents (file, NULL, &contents, &length, NULL, &err)) { /* read error */
if (G_IS_FILE (file))
g_object_unref (file);
message_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL,
GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
"%s.\n", err->message);
g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
gtk_widget_show (message_dialog);
g_error_free (err);
g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
} else {
gtk_text_buffer_set_text (tb, contents, length);
g_free (contents);
if (G_IS_FILE (tv->file))
g_object_unref (tv->file);
tv->file = file;
gtk_text_buffer_set_modified (tb, FALSE);
g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS);
}
gtk_window_destroy (GTK_WINDOW (dialog));
}
void
tfe_text_view_open (TfeTextView *tv, GtkWidget *win) {
g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
g_return_if_fail (GTK_IS_WINDOW (win));
GtkWidget *dialog;
dialog = gtk_file_chooser_dialog_new ("Open file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_OPEN,
"Cancel", GTK_RESPONSE_CANCEL,
"Open", GTK_RESPONSE_ACCEPT,
NULL);
g_signal_connect (dialog, "response", G_CALLBACK (open_dialog_response), tv);
gtk_widget_show (dialog);
}
static void
saveas_dialog_response (GtkWidget *dialog, gint response, TfeTextView *tv) {
GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
GFile *file;
if (response == GTK_RESPONSE_ACCEPT) {
file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
if (G_IS_FILE(file)) {
tv->file = file;
gtk_text_buffer_set_modified (tb, TRUE);
g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
tfe_text_view_save (TFE_TEXT_VIEW (tv));
}
}
gtk_window_destroy (GTK_WINDOW (dialog));
}
void
tfe_text_view_save (TfeTextView *tv) {
g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
GtkTextIter start_iter;
GtkTextIter end_iter;
gchar *contents;
GtkWidget *message_dialog;
GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
GError *err = NULL;
if (! gtk_text_buffer_get_modified (tb))
return; /* no necessary to save it */
else if (tv->file == NULL)
tfe_text_view_saveas (tv);
else {
gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
if (g_file_replace_contents (tv->file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, &err))
gtk_text_buffer_set_modified (tb, FALSE);
else {
/* It is possible that tv->file is broken. */
/* It is a good idea to set tv->file to NULL. */
if (G_IS_FILE (tv->file))
g_object_unref (tv->file);
tv->file =NULL;
g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
gtk_text_buffer_set_modified (tb, TRUE);
message_dialog = gtk_message_dialog_new (GTK_WINDOW (win), GTK_DIALOG_MODAL,
GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
"%s.\n", err->message);
g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
gtk_widget_show (message_dialog);
g_error_free (err);
}
}
}
void
tfe_text_view_saveas (TfeTextView *tv) {
g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
GtkWidget *dialog;
GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
dialog = gtk_file_chooser_dialog_new ("Save file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_SAVE,
"_Cancel", GTK_RESPONSE_CANCEL,
"_Save", GTK_RESPONSE_ACCEPT,
NULL);
g_signal_connect (dialog, "response", G_CALLBACK (saveas_dialog_response), tv);
gtk_widget_show (dialog);
}
GtkWidget *
tfe_text_view_new_with_file (GFile *file) {
g_return_val_if_fail (G_IS_FILE (file), NULL);
GtkWidget *tv;
GtkTextBuffer *tb;
char *contents;
gsize length;
if (! g_file_load_contents (file, NULL, &contents, &length, NULL, NULL)) /* read error */
return NULL;
tv = tfe_text_view_new();
tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
gtk_text_buffer_set_text (tb, contents, length);
g_free (contents);
TFE_TEXT_VIEW (tv)->file = g_file_dup (file);
return tv;
}
GtkWidget *
tfe_text_view_new (void) {
return GTK_WIDGET (g_object_new (TFE_TYPE_TEXT_VIEW, NULL));
}

View file

@ -1,29 +0,0 @@
#define TFE_TYPE_TEXT_VIEW tfe_text_view_get_type ()
G_DECLARE_FINAL_TYPE (TfeTextView, tfe_text_view, TFE, TEXT_VIEW, GtkTextView)
/* "open-response" signal response */
enum
{
TFE_OPEN_RESPONSE_SUCCESS,
TFE_OPEN_RESPONSE_CANCEL,
TFE_OPEN_RESPONSE_ERROR
};
GFile *
tfe_text_view_get_file (TfeTextView *tv);
void
tfe_text_view_open (TfeTextView *tv, GtkWidget *win);
void
tfe_text_view_save (TfeTextView *tv);
void
tfe_text_view_saveas (TfeTextView *tv);
GtkWidget *
tfe_text_view_new_with_file (GFile *file);
GtkWidget *
tfe_text_view_new (void);

View file

@ -1,18 +1,18 @@
# Prerequisite and Licence
# Prerequisite and License
## Prerequisite
### Tutorial document
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_.
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_.
If you want to try the examples in the tutorial, you need:
- PC with linux distribution like ubuntu, debian and so on.
- PC with Linux distribution like Ubuntu, Debian and so on.
- Gcc
- Gtk4. Gtk included linux distributions is version three at present.
- 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
@ -21,32 +21,32 @@ Therefore, I strongly recommend you not to install gtk4 to `/usr/local` on the c
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.
- Install it to your home directory, for example `$HOME/local`, in order to separate gtk4 from your system.
The second choice will be explained in [Section 3](sec3.src.md).
### Software
This repository inclludes ruby programs.
This repository includes ruby programs.
They are used to generate markdown files, html files, latex files and a pdf file.
You need:
- Linux distribution like ubuntu.
- Ruby programing language.
- Linux distribution like Ubuntu.
- Ruby programming language.
There are two ways to install it.
One is install the distribution's package.
The other is using rbenv and ruby-build.
If you want to use the latest version of ruby, use rbenv.
- Rake.
It is a gem, which is a library written in ruby.
You can install it as a package of your ditribution or use gem command.
You can install it as a package of your distribution or use gem command.
## Licence
## License
Copyright (C) 2020 ToshioCP (Toshio Sekiya)
Gtk4 tutorial repository containes the tutorial document and softwares such as converters, generators and controlers.
Gtk4 tutorial repository contains the tutorial document and software such as converters, generators and controllers.
All of them make up the 'Gtk4 tutorial' package.
This package is simply called 'Gtk4 tutorial' in the following description.
'Gtk4 tutorial' is free; you can redistribute it and/or modify it under the terms of the GNU General Public License

View file

@ -4,6 +4,7 @@ This section and the following four sections are explanations about the next ver
It is tfe5.
It has many changes from the prior version.
All the sources are listed in [Section 15](sec15.src.md).
They are located in two directories, [src/tfe5](tfe5) and [src/tfetextview](tfetextview).
## Encapsulation
@ -21,24 +22,24 @@ The important thing is to manage the Gfile object pointed by `file`.
- 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?
- How it communicates with objects outside?
You need to know at least class/instance and signals before thinking about them.
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:
- Organizing functions.
- How to use FileChooserDialog
- How to use GtkFileChooserDialog
## GObject and its children
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.
Instance is structured memories and described as C language structure.
The following is a structure of TfeTextView.
~~~C
/* This typedef statement is automaticaly generated by the macro G_DECLARE_FINAL_TYPE */
/* This typedef statement is automatically generated by the macro G_DECLARE_FINAL_TYPE */
typedef struct _TfeTextView TfeTextView;
struct _TfeTextView {
@ -49,7 +50,7 @@ struct _TfeTextView {
The members of the structure are:
- `parent` is the structure of GtkTextView which is the parent object of TfeTextView.
- `parent` is the instance 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.
@ -84,7 +85,7 @@ struct _GtkTextView
};
~~~
In each structure, its parent instance is declared at the top of the members.
In each structure, its parent instance is declared at the top of 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.
@ -97,14 +98,14 @@ The structure of `TfeTextView` is like the following diagram.
The function `tfe_text_view_new` generates a new TfeTextView instance.
@@@ tfe5/tfetextview.c tfe_text_view_new
@@@ tfetextview/tfetextview.c tfe_text_view_new
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.
1. Initialize GObject part in TfeTextView instance.
2. Initialize GtkWidget part in TfeTextView instance.
3. Initialize GtkTextView part in TfeTextView instance.
4. Initialize TfeTextView part in TfeTextView instance.
Step one through three is done automatically.
Step four is done by the function `tfe_text_view_init`.
@ -112,24 +113,17 @@ 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
@@@ tfetextview/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.
This function just initializes `tv->file` to be `NULL`.
## 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.
An instance can be generated two times or more.
Each object can have more than one instance.
Those instances have the same structure.
Instance, which is structured memories, only keeps status of the object.
Instance, which is structured memories, only keeps status of the instance.
Therefore, it is insufficient to define its behavior.
We need at least two things.
One is functions and the other is class.
@ -140,7 +134,7 @@ These functions are similar to object methods in object oriented languages such
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.
Those functions are used by the object itself or its descendant objects.
For example, GObject class is declared in `gobject.h` in GLib source files.
@@@ class_gobject.c
@ -154,8 +148,8 @@ void (*dispose) (GObject *object);
The declaration is a bit complicated.
The asterisk before the identifier `dispose` means pointer.
So, the pointer `dispose` points to 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.
So, the pointer `dispose` points to a function which has one parameter, which points a GObject structure, and returns no value.
In the same way, line 23 says `finalize` is a pointer to the function which has one parameter, which points a GObject structure, and returns no value.
~~~C
void (*finalize) (GObject *object);
@ -164,7 +158,11 @@ 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.
- 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.
In this phase, the instance releases all the references to other instances.
The second phase is finalizing.
- 23: A function pointed by `finalize` finishes the destruction process.
- The other pointers point to functions which are called while the instance lives.
@ -183,11 +181,11 @@ The following is extracts from the source files (not exactly the same).
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 ancestors are open to the descendent class.
- Class members in ancestors are open to the descendant 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.)
(Override is an object oriented programming terminology.
Override is rewriting ancestors' class methods in the descendant class.)
- Some class methods are often overridden.
`set_property`, `get_property`, `dispose`, `finalize` and `constructed` are such methods.
@ -199,36 +197,39 @@ It is illustrated in the following diagram.
## Destruction of TfeTextView
Every Object derived from GObject has a reference count.
If an object A uses an object B, then A keeps a pointer to B in A and at the same time increases the reference count of B by one with the function `g_object_ref (B)`.
If an object A refers an object B, then A keeps a pointer to B in A and at the same time increases 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 decreases 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.
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.
In the same way, if 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.
The idea above is based on an assumption that an object referred 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.
The destruction process is spitted into two phases: disposing and finalizing.
In the disposing process, the object invokes the function pointed by `dispose` in its class to release all references to other objects.
In the finalizing process, it invokes the function pointed by `finalize` in its class to complete the destruction process.
These functions are also called handlers or methods.
For example, dispose handler or dispose method.
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
@@@ tfetextview/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.)
`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: invokes parent's dispose handler. (This will be explained later.)
In the desposing process, the object uses the pointer in its class to call the handler.
In the disposing process, the object uses the pointer in its class to call the handler.
Therefore, `tfe_text_view_dispose` needs to be registered 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.
@ -248,7 +249,7 @@ 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}
![dispose handlers](../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`.
@ -260,7 +261,7 @@ 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.
And `gobject` is a pointer to TfeTextView instance which is casted as a GObject instance.
`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

@ -17,18 +17,18 @@ Then the handler, which has been connected to the signal, is invoked.
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 functions the object passively responds to the caller.
In signals the object actively sends a signal to the handler.
GObject signal can be registered, connected and emitted.
GObject signals are registered, connected and emitted.
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. Signals are registered with the object type on which they can be emitted.
The registration is done usually when the class is initialized.
2. Signals are connected to handlers by `g_connect_signal` or its family functions.
3. When Signals are emitted, the connected handlers are invoked.
Step one and three are done in the object on which the signal is emitted.
Step two is usually done outside the objects.
Step one and three are done in the object on which the signal belongs.
Step two is usually done outside the object.
## Signal registration
@ -37,11 +37,11 @@ In TfeTextView, two signals are registered.
- "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.
`tfe_text_view_open` function is not able to return the status because it uses GtkFileChooserDialog.
This signal is emitted instead of the return value of the function.
Static variable is used to store the signal ID.
If you need to register two or more signals, static array is usually used.
A static variable or array is used to store the signal ID.
A static array is used to register two or more signals.
~~~C
enum {
@ -55,9 +55,9 @@ static guint tfe_text_view_signals[NUMBER_OF_SIGNALS];
Signal registration codes are written in the class initialization function.
@@@ tfe5/tfetextview.c tfe_text_view_class_init
@@@ tfetextview/tfetextview.c tfe_text_view_class_init
- 6-15: Register "change-file"signal.
- 6-15: Registers "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.
@ -65,7 +65,7 @@ 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.
- 16-26: Registers "open-response" signal.
This signal has a parameter.
- 25: Number of the parameter.
"open-response" signal has one parameter.
@ -75,20 +75,20 @@ 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).
The handlers are as follows.
The handlers are declared as follows.
~~~C
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.
- Because "change-file" signal doesn't have parameter, the handler's parameters are a TfeTextView object and user data.
- Because "open-response" signal has one parameter, the handler's parameters are a TfeTextView object, the signal's parameter and a 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.
The values of the parameter is defined in `tfetextview.h` because they are public.
~~~C
/* "open-response" signal response */
@ -100,18 +100,22 @@ enum
};
~~~
- `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.
- The parameter is set to `TFE_OPEN_RESPONSE_SUCCESS` when `tfe_text_view_open` successfully has opened a file and loaded it.
- The parameter is set to `TFE_OPEN_RESPONSE_CANCEL` when the user has canceled to open a file.
- The parameter is set to `TFE_OPEN_RESPONSE_ERROR` when error has occurred.
## Signal connection
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.
The signals "change-file" is connected to a callback function outside of TfeTextView object.
In the same way, the signals "open-response" is connected to a callback function outside of TfeTextView object.
Those callback functions are defined by users.
In the program tfe, callback functions are defined in `tfenotebook.c`.
And their names are `file_changed` and `open_response`.
They will be explained later.
~~~C
g_signal_connect (GTK_TEXT_VIEW (tv), "change-file", G_CALLBACK (file_changed), nb);
@ -125,7 +129,7 @@ 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.
`g_signal_emit` is used to emit the signal.
A function `g_signal_emit` is used to emit the signal.
The following lines are extracted from `tfetextview.c`.
Each line is quoted from a different line.
@ -140,7 +144,7 @@ g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ER
- 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.
- "change-file" signal doesn't have parameter, so there's no fourth parameter.
- "open-response" signal has one parameter.
The fourth parameter is the parameter.

View file

@ -1,23 +1,24 @@
# Functions in TfeTextView
In this section I will explain each function in TfeTextView object.
In this section I will explain functions in TfeTextView object.
### 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.
C source files `tfeapplication.c` and `tfenotebook.c` include `tfe.h` at the beginning.
@@@ tfe5/tfe.h
`tfetextview.h` is a header file which describes the public functions in `tfetextview.c`.
`../tfetextview/tfetextview.h` is a header file which describes the public functions in `tfetextview.c`.
@@@ tfe5/tfetextview.h
@@@ tfetextview//tfetextview.h
- 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.
Each function will be explained later in this section.
- 1,2,35: Thanks to these three lines, the following lines are included only once.
- 4: Includes gtk4 header files.
The header file `gtk4` also has the same mechanism to avoid including it multiple times.
- 6-7: These two lines define TfeTextView.
- 9-15: A definition of the value of the parameter of "open-response" signal.
- 17-33: Declaration of public functions on GtkTextView.
## Functions to generate TfeTextView object
@ -34,24 +35,16 @@ GtkWidget *tfe_text_view_new_with_file (GFile *file);
~~~
`tfe_text_view_new_with_file` is given a Gfile object as the argument and it loads the file into the GtkTextBuffer object, then returns the pointer to the new object.
Parameter:
- `file`: a pointer to the GFile object.
Return value:
- A pointer to the generated TfeTextView object but it is casted to a pointer to GtkWidget.
If an error occurs during the generation process, NULL is returned.
Each function is defined as follows.
@@@ tfe5/tfetextview.c tfe_text_view_new_with_file tfe_text_view_new
@@@ tfetextview/tfetextview.c tfe_text_view_new_with_file tfe_text_view_new
- 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`
- 21-24: `tfe_text_view_new` function.
Just returns the value from the function `g_object_new` but casts it to the pointer to GtkWidget.
Initialization is done in `tfe_text_view_init` which is called in the process of `g_object_new` function.
- 1-19: `tfe_text_view_new_with_file` function.
- 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.
@ -62,12 +55,14 @@ If an error occurs, the solution is usually to change the (caller) program and f
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`.
- 13: Calls the function `tfe_text_view_new`.
The function generates TfeTextView instance and returns the pointer to the instance.
- 14: Gets the pointer to GtkTextBuffer corresponds to `tv`.
The pointer is assigned to `tb`
- 15: Assigns the contents read from the file to GtkTextBuffer pointed by `tb`.
- 16: Frees the memories pointed by `contents`.
- 17: Duplicates `file` and sets `tv->file` to point it.
- 18: Returns `tv`, which is a pointer to the newly created TfeTextView instance..
## Save and saveas functions
@ -77,90 +72,101 @@ 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`.
The function `save` writes the contents in GtkTextBuffer to a file specified by `tv->file`.
If `tv->file` is NULL, then it shows GtkFileChooserDialog and prompts the user to choose a file to save.
Then it saves the contents to the file and sets `tv->file` to point the GFile instance of the file.
~~~C
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.
The function `saveas` uses GtkFileChooserDialog and prompts the user to select a existed file or specify a new file to save.
Then, the function changes `tv->file` and save the contents to the specified file.
If an error occurs, 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.
The error is managed only in the TfeTextView instance and no information is notified to the caller.
@@@ tfe5/tfetextview.c saveas_dialog_response tfe_text_view_save tfe_text_view_saveas
@@@ tfetextview/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.
- 20-56: `Tfe_text_view_save` function.
- 22: 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.
- 32-33: 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`.
- 34-35: If `tv->file` is NULL, no file has given yet.
It calls `tfe_text_view_saveas` which prompts a user to select a file or specify a new file to save.
- 37-38: Gets the contents of the GtkTextBuffer and sets `contents` to point it.
- 39-40: Saves the content to the file.
If it succeeds, it resets the modified bit in the GtkTextBuffer.
- 42-53: If file writing fails, it assigns NULL to `tv->file`.
Emits "change-file" signal.
Shows the error message dialog (47-51).
Shows the error message dialog (48-52).
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.
Frees the GError object.
- 58-71: `tfe_text_view_saveas` function.
It shows GtkFileChooserDialog and prompts the user to choose a file.
- 65-68: Generates 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.
- 69: connects the "response" signal of the dialog and `saveas_dialog_response` handler.
- 1-18: `saveas_dialog_response` signal handler.
- 6-16: If the response is `GTK_RESPONSE_ACCEPT`, then it gets a pointer to the GFile object.
Then, it sets `tv->file` to point the GFile.
And turns on the modified bit of the GtkTextBuffer, emits "change-file" signal.
Finally, it calls `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.
One is are a function which generates GtkFileChooserDialog and the other is a signal handler.
The function just generates and shows GtkFileChooserDialog.
The rest is done by the handler.
It gets Gfile from GtkFileChooserDialog, save the buffer to the file by calling `tfe_text_view_save`.
It gets Gfile from GtkFileChooserDialog and saves 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.
Open function shows GtkFileChooserDialog to users and prompts them to choose a file.
Then it reads the file and puts the text to GtkTextBuffer.
~~~C
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.
The parameter `win` is the top window.
It will be a transient parent window of GtkFileChooserDialog when the dialog is generated..
This allows window managers to keep the dialog on top of the parent window, or center the dialog over the parent window.
It is possible to give no parent window to the dialog.
However, it is encouraged to give a parent window to dialog.
This function might be called just after `tv` has been generated.
In that case, `tv` has not been incorporated into the widget hierarchy.
Therefore it is impossible to get the top window from `tv`.
That's why the function needs `win` parameter.
This function is usually called when the buffer of `tv` is empty.
However, 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 appropriate.
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`.
@@@ tfetextview/tfetextview.c open_dialog_response tfe_text_view_open
@@@ tfe5/tfetextview.c open_dialog_response tfe_text_view_open
- 36-49: `tfe_text_view_open` function.
- 43: Generate GtkFileChooserDialog.
- 37-50: `tfe_text_view_open` function.
- 44-47: Generates GtkFileChooserDialog.
The title is "Open file".
Ttransient parent window is the top window of the application, which is given by the caller.
Transient 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.
- 48: connects the "response" signal of the dialog and `open_dialog_response` signal handler.
- 49: Shows the dialog.
- 1-35: `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.
- 12-13: Gets a pointer to Gfile by `gtk_file_chooser_get_file`.
If it is not GFile, maybe an error occurred.
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.
- 14-23: If an error occurs at file reading, then it decreases the reference count of the 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-33: If the file has successfully read, then the text is inserted to GtkTextBuffer, frees the temporary buffer pointed by `contents` and sets `tv->file` to point the file (no duplication or unref is not necessary).
Then, it emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_SUCCESS` and emits "change-file" signal.
- 34: closes 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.
@ -171,23 +177,24 @@ 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`.
1. A caller gets 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.
3. It calls `tfe_text_view_open` to prompt the user to 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.
5. The handler reads the file and inserts the text into GtkTextBuffer and emits a signal to inform the response status.
6. The handler outside TfeTextView receives 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
@@@ tfetextview/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.
The important thing is to duplicate `tv->file`.
Otherwise, if the caller frees 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).
You can find them under [src/tfe5](tfe5) and [src/tfetextview](../tfetextview) directories.

View file

@ -1,18 +1,20 @@
# Functions with GtkNotebook
# Functions in GtkNotebook
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.
A set of functions related to GtkNotebook are declared in `tfenotebook.h`.
The word "tfenotebook" is used only in filenames.
There's no "TfeNotebook" object.
@@@ tfe5/tfenotebook.h
This header file shows the public functions in `tfenotebook.c`.
This header file describes 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.
- 10-11: The function `notebook_page_new` generates a new GtkNotebookPage and adds GtkScrolledWindow and TfeTextView to the page.
- 7-8: The function `notebook_page_new_with_file` generates a new GtkNotebookPage and adds GtkScrolledWindow and TfeTextView to the page. A file is read and inserted into GtkTextBuffer.
The GFile `file` is copied and inserted to the TfeTextView object.
- 4-5: `notebook_page_open` shows a file chooser dialog. Then, user chooses a file and the file is inserted into GtkTextBuffer.
- 1-2: `notebook_page_save` saves the contents in GtkTextBuffer into the file, which is got from the TfeTextView.
You probably find that the functions above are higher level functions of
@ -33,51 +35,59 @@ Now let's look at each program of the functions.
@@@ tfe5/tfenotebook.c get_untitled notebook_page_build notebook_page_new
- 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", ... .
- 28-38: `notebook_page_new` function.
- 30: `g_return_if_fail` is used to check the argument.
- 35: Generates TfeTextView object.
- 36: Generates 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.
- 4-7: Increases `c` by one and if it is zero then it returns "Untitled". If it is a positive integer then the it returns "Untitled\<the integer\>", for example, "Untitled1", "Untitled2", and so on.
The function `g_strdup_printf` generates a string and it should be freed by `g_free` when it becomes useless.
The caller of `get_untitled` is in charge of freeing the string.
- 37: calls `notebook_page_build` to build the contents of the page.
- 10- 26: `notebook_page_build` function.
- 16: Generates GtkScrolledWindow.
- 18: Sets the wrap mode of `tv` to GTK_WRAP_WORD_CHAR so that lines are broken between words or graphemes.
- 19: Inserts `tv` to GtkscrolledWindow as a child.
- 20-21: Generates GtkLabel, then appends it to GtkNotebookPage.
- 22-23: Sets "tab-expand" property to TRUE.
The function g\_object\_set sets properties on an object.
The object is any object derived from GObject.
In many cases, an object has its own function to set its properties, but sometimes not.
In that case, use g\_object\_set to set the property.
- 24: Sets the current page of `nb` to `i` which is the number of the GtkNotebookPage above.
- 25: Connects "change-file" signal and `file_changed` handler.
## notebook\_page\_new\_with\_file
@@@ tfe5/tfenotebook.c notebook_page_new_with_file
- 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.
- 9-10: Calls `tfe_text_view_new_with_file`.
If the function returns NULL, then it does nothing and returns.
The return value NULL means that an error has happened.
- 11-12: Gets the filename and builds the contents of the page.
## notebook\_page\_open
@@@ tfe5/tfenotebook.c open_response notebook_page_open
- 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.
- 25: Generates TfeTextView object.
- 26: Connects the signal "open-response" and the handler `open_response`.
- 27: Calls `tfe_text_view_open`.
The function emits an "open-response" signal to inform the status.
- 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`.
- 6-8: If the status is NOT `TFE_OPEN_RESPONSE_SUCCESS`, it cancels what it 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.
You need to call `g_object_ref_sink` first.
Then the floating reference is converted into an ordinary reference.
Now you call `g_object_unref` to decrease the reference count by one.
- 9-11: If `tfe_text_view_get_file` returns a pointer not to point GFile, then something bad happens.
Sink and unref `tv`.
- 12-16: Otherwise, everything is okay.
Get the filename, build the contents of the page.
Gets the filename, builds the contents of the page.
## notebook\_page\_save
@ -89,16 +99,15 @@ Get the filename, build the contents of the page.
## file\_changed handler
The function `file_changed` is a handler connected to "change-file" signal.
If `tv->file` is changed, TfeTextView emits this signal.
If the file in TfeTextView is changed, it emits this signal.
This handler changes the label of GtkNotebookPage.
@@@ tfe5/tfenotebook.c file_changed
- 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`.
- 8: Gets GFile from TfeTextView.
- 9: Gets GkScrolledWindow which is the parent widget of `tv`.
- 10-13: If `file` points GFile, then assigns the filename of the GFile into `filename`.
Otherwise (file is NULL), assigns untitled string to `filename`.
- 14-15: Generates a label with the filename and inserts it into GtkNotebookPage.
- 16-17: Unrefs `file` and frees `filename`.

View file

@ -15,29 +15,29 @@ 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.
- 6: Generates GtkApplication object.
- 8-10: Connects "startup", "activate" and "open signals to their handlers.
- 12: Runs the application.
- 13-14: releases the reference to the application and returns the status.
## startup 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.
- Builds the widgets using ui file.
- Connects button signals and their handlers.
- Sets 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.
- 12-15: Builds widgets using ui file (resource).
Connects the top window and the application using `gtk_window_set_application`.
- 16-23: Gets buttons and connects their signals and handlers.
- 24: Releases the reference to GtkBuilder.
- 26-31: Sets 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.
@ -49,12 +49,12 @@ CSS will be explained in the next subsection.
CSS is an abbreviation 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.
You might have found that the widgets in GTK is similar to the window in a browser.
It implies that CSS can also be applied to GTK windowing system.
### CSS nodes, selectors
The syntax of CSS is as follws.
The syntax of CSS is as follows.
~~~css
selector { color: yellow; padding-top: 10px; ...}
@ -62,7 +62,7 @@ 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.
If you want to set style to GtkTextView, substitute "textview" for the selector.
~~~css
textview {color: yellow; ...}
@ -97,9 +97,9 @@ However, instead, you can add it to GdkDisplay of the window (usually top level
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.
- 29: Generates GtkCssProvider.
- 30: Puts the CSS into the provider.
- 31: Adds 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`.
@ -122,7 +122,7 @@ tfe_text_view_new (void) {
}
~~~
CSS set to the context takes precedence over the one set to the display.
CSS in the context takes precedence over CSS in the display.
## activate and open handler
@ -132,20 +132,20 @@ 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.
- 8-10: Gets GtkNotebook object.
- 12-13: Generates 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.
- 24-26: Gets GtkNotebook object.
- 28-29: Generates GtkNotebookPage with files.
- 30-31: If opening and reading file failed and no GtkNotebookPage has generated, then it generates a empty page.
- 32: Shows the window.
These codes have become really simple thanks to tfenotebook.c and tfetextview.c.
## Primary instance
Only one GApplication instance can be run at a time per session.
The session is a bit diffcult concept and also platform-dependent, but roughly speaking, it corresponds to a graphical desktop login.
The session is a bit difficult concept and also platform-dependent, but roughly speaking, it corresponds to a graphical desktop login.
When you use your PC, you probably login first, then your desktop appears until you log off.
This is the session.

View file

@ -59,14 +59,15 @@ It is a good practice for you to add more features.
## tfetextview.h
@@@ tfe5/tfetextview.h
@@@ tfetextview/tfetextview.h
## tfetextview.c
@@@ tfe5/tfetextview.c
@@@ tfetextview/tfetextview.c
## Total number of lines, words and charcters
## Total number of lines, words and characters
$$$
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
LANG=C wc tfe5/meson.build tfe5/tfeapplication.c tfe5/tfe.gresource.xml tfe5/tfe.h tfe5/tfenotebook.c tfe5/tfenotebook.h tfetextview/tfetextview.c tfetextview/tfetextview.h tfe5/tfe.ui
$$$

View file

@ -31,7 +31,7 @@ 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.
GMenuModel is an abstract object which represents a menu.
GMenu is a simple implementation of GMenuModel and a child object of GMenuModel.
GObjct -- GMenuModel -- GMenu
@ -49,7 +49,7 @@ GMenuItem and Gmenu (or GMenuModel) don't have a parent-child relationship.
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.
"Cut", "Copy", "Paste" and "Select All" are also the labels of menu items.
Other attributes will be explained later.
Some menu items have a link to another GMenu.
@ -129,18 +129,18 @@ You don' t need to care about it.
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`.
- 11-13: Generates a GtkApplicationWindow and assigns a pointer to it to `win`. And sets the title and default size.
- 15: Generates 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`.
If you don't need the parameter, pass `NULL`.
Therefore, `act_quit` has a name "quit" and no parameter.
- 16: Add the action to GtkApplication `app`.
- 16: Adds 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.
- 17: Connects "activate" signal of the action and the handler `quit_activated`.
- 19-22: Generates 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.
@ -148,19 +148,19 @@ This function is described in GMenuModel section in GIO API reference.
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`.
- 23-24: Appends `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`.
- 25: Sets 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`.
- 26-27: Appends `menu_item_menu` to `menubar`.
Then frees `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.
- 29: The menu is inserted to GtkApplication.
- 30: Sets GtkApplicationWindow to show the menubar.
- 31: Shows the window.
![menu and action](../image/menu1.png){width=12.555cm height=3.285cm}

View file

@ -45,10 +45,10 @@ 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`.
- connects 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.
Then, the default behavior for boolean-stated actions with a NULL parameter type like `act_fullscreen` is to toggle them via the “change-state” signal.
The following is the "change-state" signal handler.
@ -67,20 +67,20 @@ fullscreen_changed(GSimpleAction *action, GVariant *value, gpointer win) {
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`.
The 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.
- Sets the state of the action to `value`.
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`.
So, you need to set the state to the new value by `g_simple_action_set_state`.
You can use "activate" signal instead ot "change-state" signal, or both signals.
You can use "activate" signal instead of "change-state" signal, or both signals.
But the way above is the simplest and best.
### GVariant
GVarient can contain boolean, string or other simple type values.
For example, the following program set TRUE to `value` whose type is GVariant.
For example, the following program assigns TRUE to `value` whose type is GVariant.
~~~C
GVariant *value = g_variant_new_boolean (TRUE);
@ -155,10 +155,10 @@ 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`.
- connects 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.
Then the default behavior is to call `g_simple_action_set_state()` to set the state to the requested value.
The following is the "activate" signal handler.
@ -181,10 +181,10 @@ 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`.
- Sets the color of the css provider.
- Frees the string `color`.
- Changes the state by `g_action_change_state`.
The function just sets the state of the action to the parameter 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`.
@ -210,7 +210,7 @@ It finally output the string "s".
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.
- prints the string to the terminal.
## Example code
The following code includes stateful actions above.
@ -232,7 +232,7 @@ The code is as follows.
- 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.
`win` has a title "menu2" and its default size is 400x300.
`lb` is named as "lb".
The name is used in CSS.
`lb` is set to `win` as a child.
@ -243,7 +243,7 @@ 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.
- 45-54: Generates GMenu and GMenuItem.
There are three sections.
- 56-61: Signals are connected to handlers.
And actions are added to GActionMap.
@ -252,15 +252,15 @@ 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.
- 63-77: Connects and builds the menus.
Useless GMenuItem are freed.
- 79-80: GMenuModel `menubar` is set to `app`.
Set show menubar property to `TRUE` in `win`.
- 79-80: GMenuModel `menubar` is inserted to `app`.
Sets 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.
You may use GtkBox as a child widget of the window and insert GtkPopoverMenubar as the first child of the box.
- 82-87: Sets CSS.
`provider` is GtkCssProvider which is defined in line three as a static variable.
Its CSS data is:
`label#lb {background-color: red;}`.
@ -273,5 +273,5 @@ 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.
- 90: Shows the window.

View file

@ -20,7 +20,7 @@ The file starts and ends with interface tag.
`menu` tag corresponds to GMenu object.
`id` attribute defines the name of the object.
It will be refered by GtkBuilder.
It will be referred by GtkBuilder.
~~~xml
<submenu>
@ -69,7 +69,7 @@ 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.
The ui file is converted to the resource by the resource compiler `glib-compile-resouces` with xml file below.
@@@ menu3/menu3.gresource.xml
@ -83,7 +83,7 @@ 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.
It is important that `builder` is unreferred after the GMenuModel `menubar` is inserted to the application.
If you do it before setting, bad thing will happen -- your computer might freeze.
## Action entry
@ -133,9 +133,9 @@ g_action_map_add_action_entries (G_ACTION_MAP (app), app_entries,
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`.
- Builds the "quit" action
- Connects the action and the "activate" signal handler `quit_activate`
- Adds the action to the action map `app`.
The same goes for the other actions.
@ -149,12 +149,12 @@ g_action_map_add_action_entries (G_ACTION_MAP (win), win_entries,
~~~
The code above does:
- Build a "fullscreen" action and "color" action.
- Connect the "fullscreen" action and the "change-state" signal handler `fullscreen_changed`
- Builds a "fullscreen" action and "color" action.
- Connects 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`
- Connects 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`.
- Adds the actions to the action map `win`.
## Example code

View file

@ -15,15 +15,15 @@ In this section, I will explain:
Cairo is a two dimensional graphics library.
First, you need to know surface, source, mask, destination, cairo context and transformation.
- surface represents an image.
- Surface represents an image.
It is like a canvas.
We can draw shapes and images with different colors on surfaces.
- source pattern, or simply source, is a kind of paint, which will be transfered to destination surface by cairo functions.
- mask is image mask used in the transference.
- destination is a target surface.
- cairo context manages the transference from source to destination through mask with its functions.
- Source pattern, or simply source, is a kind of paint, which will be transferred to destination surface by cairo functions.
- Mask is image mask used in the transference.
- Destination is a target surface.
- Cairo context manages the transference from source to destination through mask with its functions.
For example, `cairo_stroke` is a function to draw a path to the destination by the transference.
- transformation is applied before the transfer completes.
- Transformation is applied before the transfer completes.
The transformation is called affine, which is a mathematics terminology, and represented by matrix multiplication and vector addition.
Scaling, rotation, reflection, shearing and translation are examples of affine transformation.
In this section, we don't use it.
@ -35,8 +35,8 @@ Therefore, the coordinate in source and mask is the same as the coordinate in de
The instruction is as follows:
1. Create a surface.
This will be a destnation.
2. Create a cairo context with the surface and set it to the destination.
This will be a destination.
2. Create a cairo context with the surface and the surface will be the destination of the context.
3. Create a source pattern within the context.
4. Create paths, which are lines, rectangles, arcs, texts or more complicated shapes, to generate a mask.
5. Use drawing operator such as `cairo_stroke` to transfer the paint in the source to the destination.
@ -46,31 +46,31 @@ Here's a simple example code that draws a small square and save it as a png file
@@@ misc/cairo.c
- 1: Include the header file of cairo.
- 1: Includes the header file of cairo.
- 12: `cairo_image_surface_create` creates an image surface.
`CAIRO_FORMAT_RGB24` is a constant which means that each pixel has red, green and blue data.
Each data has 8 bit quantity.
Modern displays have this type of color depth.
Width and hieight are pixels and given as integers.
- 13: Create cairo context.
The surface given as an argument will be set to the destination of the context.
Width and height are pixels and given as integers.
- 13: Creates cairo context.
The surface given as an argument will be the destination of the context.
- 17: `cairo_set_source_rgb` creates a source pattern, which is a solid white paint.
The second to fourth argument is red, green and blue color depth respectively.
Their type is float and the values are between zero and one.
(0,0,0) is black and (1,1,1) is white.
- 18: `cairo_paint` copies everywhere in the source to destination.
The destination is filled with white pixels by this command.
- 20: Set the source color to black.
- 20: Sets the source color to black.
- 21: `cairo_set_line_width` set the width of lines.
In this case, the line width is set to two pixels.
(It is because the transformation is identity.
If we set it different one, for example scaling with the factor three, the actual width in destnation is six (2x3=6) pixels.)
- 22: Draw a rectangle (square).
If the transformation isn't identity, for example scaling with the factor three, the actual width in destination will be six (2x3=6) pixels.)
- 22: Draws a rectangle (square).
The top-left coordinate is (width/2.0-20.0, height/2.0-20.0) and the width and height have the same length 40.0.
- 23: `cairo_stroke` transfer the source to destnation through the rectangle in mask.
- 26: Output the image to a png file `rectangle.png`.
- 27: Destroy the context. At the same time the source is destroyed.
- 28: Destroy the destnation surface.
- 23: `cairo_stroke` transfer the source to destination through the rectangle in mask.
- 26: Outputs the image to a png file `rectangle.png`.
- 27: Destroys the context. At the same time the source is destroyed.
- 28: Destroys the destination surface.
To compile this, type the following.
@ -90,10 +90,10 @@ The following is a very simple example.
The function `main` is almost same as before.
The two functions `on_activate` and `draw_function` is important in this example.
- 16: Generate a GtkDrawingArea object.
- 20,21: Set the width and height of the contents of the GtkDrawingArea widget.
- 16: Generates a GtkDrawingArea object.
- 20,21: Sets the width and height of the contents of the GtkDrawingArea widget.
These width and height is the size of the destination surface of the cairo context provided by the widget.
- 22: Set a drawng function to the widget.
- 22: Sets a drawing function of the widget.
GtkDrawingArea widget uses the function to draw the contents of itself whenever its necessary.
For example, when a user drag a mouse pointer and resize a top level window, GtkDrawingArea also changes the size.
Then, the whole window needs to be redrawn.
@ -106,16 +106,16 @@ The drawing function has five parameters.
The first parameter is the GtkDrawingArea widget which calls the drawing function.
However, you can't change any properties, for example `content-width` or `content-height`, in this function.
The second parameter is a cairo context given by the widget.
The destnation surface of the context is connected to the contents of the widget.
The destination surface of the context is connected to the contents of the widget.
What you draw to this surface will appear in the widget on the screen.
The third and fourth paranmeters are the size of the destination surface.
The third and fourth parameters are the size of the destination surface.
- 3-11: The drawing function.
- 4-5: Set the source to be white and paint the destination white.
- 7: Set the line width to be 2.
- 8: Set the source to be black.
- 9: Add a rectangle to the mask.
- 10: Draw the rectangle with black color to the destination.
- 4-5: Sets the source to be white and paint the destination white.
- 7: Sets the line width to be 2.
- 8: Sets the source to be black.
- 9: Adds a rectangle to the mask.
- 10: Draws the rectangle with black color to the destination.
Compile and run it, then a window with a black rectangle (square) appears.
Try resizing the window.

View file

@ -1,7 +1,7 @@
# Installation of gtk4 to linux distributions
This section describes how to install gtk4 into linux distributions.
However, I only have an experience to install it to ubuntu 20.10.
However, I only have an experience to install it to Ubuntu 20.10.
Probably you need more than the explanation below.
This tutorial including this section is without any warranty.
@ -22,9 +22,9 @@ If you want to install it in the system area, `/opt/gtk4` is one of good choices
[Gtk4 API Reference](https://gnome.pages.gitlab.gnome.org/gtk/gtk/gtk-building.html) gives an installation example to `/opt/gtk4`.
Don't install it to `/usr/local` which is the default.
It is used by ubuntu applications, which are not build on gtk4.
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.
Actually I did it and I needed to reinstall Ubuntu.
## Glib installation
@ -77,7 +77,7 @@ or
$ source env.sh
This command carries out the commands in `env.sh` and changes the environment variables above in the corrent shell.
This command carries out the commands in `env.sh` and changes the environment variables above in the current shell.
## Pango installation
@ -98,7 +98,7 @@ 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.
It is a text file which keep the list of 'share' directories 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.
$ export XDG_DATA_DIRS=$HOME/local/share:$XDG_DATA_DIRS

View file

@ -43,7 +43,7 @@ It has "name" attribute which is a signal name and "handler" attribute which is
Options "-WI, --export-dynamic" CFLAG is necessary when you compile the application.
You can achieve this by adding "export_dynamic: true" argument to executable function in `meson.build`.
And be careful that the handler must be defined without 'static' class.
- 54-76: Put GtkScrolledWindow and GtkDrawingArea into GtkBox.
- 54-76: Puts GtkScrolledWindow and GtkDrawingArea into GtkBox.
GtkBox has "homogeneous property with TRUE value, so the two children have the same width in the box.
TfeTextView is a child of GtkScrolledWindow.
@ -70,10 +70,10 @@ Color.h just includes tfetextview.h.
This is the main file.
It deals with:
- Build widgets by GtkBuilder.
- Set drawing function to GtkDrawingArea.
And connect a handler to "resize" signal on GtkDrawingArea.
- Implement each call back functions.
- Building widgets by GtkBuilder.
- Seting a drawing function of GtkDrawingArea.
And connecting a handler to "resize" signal on GtkDrawingArea.
- Implementing each call back functions.
Particularly, `Run` signal handler is the point in this program.
The following is `colorapplication.c`.
@ -84,18 +84,18 @@ The following is `colorapplication.c`.
The application ID is "com.github.ToshioCP.color".
`G_APPLICATION_FLAGS_NONE` is specified so no open signal handler is necessary.
- 86-106: Startup handler.
- 91-96: Build widgets.
- 91-96: Builds widgets.
The pointers of the top window, TfeTextView and GtkDrawingArea objects are stored to static variables `win`, `tv` and `da` respectively.
This is because these objects are often used in handlers.
They never be rewritten so they're thread safe.
- 97: connect "resize" signal and the handler.
- 98: set the drawing function.
- 81-84: Activate handler, which just show the widgets.
- 97: connects "resize" signal and the handler.
- 98: sets the drawing function.
- 81-84: Activates handler, which just shows the widgets.
- 73-79: The drawing function.
It just copy `surface` to destination.
It just copies `surface` to destination.
- 65-71: Resize handler.
Re-create the surface to fit the width and height of the drawing area and paint by calling the function `run`.
- 58-63: Close handler.
Re-creates the surface to fit the width and height of the drawing area and paints by calling the function `run`.
- 58-63: Closes the handler.
It destroys `surface` if it exists.
Then it destroys the top window and quits the application.
- 48-56: Open and save handler.
@ -108,9 +108,9 @@ It is important to know that the drawing function is called when it is necessary
For example, when another window is moved and uncovers part of the widget, or when the window containing it is resized.
But repaint of `surface` is not automatically notified to gtk.
Therefore, you need to call `gtk_widget_queue_draw` to redraw the widget.
- 9-40: Run function paint the surface.
- 9-40: Run function paints the surface.
First, it gets the contents of GtkTextBuffer.
Then compare it to "red", "green" and so on.
Then it compares it to "red", "green" and so on.
If it matches the color, then the surface is painted the color.
If it matches "light" or "dark", then the color of the surface is lightened or darkened respectively.
Alpha channel is used.

View file

@ -5,7 +5,7 @@
### GtkApplication and g\_application\_run
Usually people write a programming code to make an application.
What are appications?
What are applications?
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.
@ -157,7 +157,7 @@ Now rewrite the function `on_activate`.
@@@ misc/pr3.c on_activate
Widget is an abstract concept that includes all the GUI interfaces such as windows, dialogs, buttons, multiline text, containers and so on.
Widget is an abstract concept that includes all the GUI interfaces such as windows, dialogs, buttons, multi-line text, containers and so on.
And GtkWidget is a base object from which all the GUI objects derive.
parent <-----> child

View file

@ -60,9 +60,9 @@ 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`.
First, it generates a GtkButton widget `btn` with a label "Click me".
Then, adds the button to the window `win` as a child.
Finally, connects 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".
@ -109,7 +109,7 @@ 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 GtkBox and add it to GtkApplicationWindow as a child.
- Generate GtkButton and append it to GtkBox.
- Generate another GtkButton and append it to GtkBox.

View file

@ -4,7 +4,7 @@
### GtkTextView and GtkTextBuffer
GtkTextview is a widget for multiline text editing.
GtkTextview is a widget for multi-line text editing.
GtkTextBuffer is a text buffer which is connected to GtkTextView.
See a sample program `tfv1.c` below.
@ -17,9 +17,9 @@ 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.
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.
When it is set to `GTK_WRAP_WORD_CHAR`, text wraps in between words, or if that is not enough, also between graphemes.
In line 30, `tv` is set to `win` as a child.
In line 30, `tv` is added to `win` as a child.
Now compile and run it.
@ -37,8 +37,8 @@ You can solve it by putting GtkScrolledWindow between GtkApplicationWindow and G
What we need to do is:
- Generate GtkScrolledWindow and set it as a child of GtkApplicationWindow.
- Set GtkTextView as a child of GtkScrolledWindow.
- Generate GtkScrolledWindow and insert it to GtkApplicationWindow as a child.
- insert GtkTextView to GtkScrolledWindow as a child.
Modify `tfv1.c` and save it as `tfv2.c`.
The difference between these two files is very little.

View file

@ -13,7 +13,7 @@ When the program starts, we give a filename as an argument.
$ ./a.out filename
Then it opens the file and set it into GtkTextBuffer.
Then it opens the file and inserts 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.
@ -87,10 +87,10 @@ The way how to read a file using GFiles will be described in the next section.
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 it is given arguments, it recognizes the first argument as a filename and opens it.
- If opening the file succeeds, it reads the contents of the file and inserts it to GtkTextBuffer and shows the window.
- If it fails to open the file, shows an error message and quits.
- If there's no argument, it shows an error message and quits.
- If there are two or more arguments, the second one and after are ignored.
The program is as follows.
@ -118,10 +118,10 @@ 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.
- Sets wrap mode to `GTK_WRAP_WORD_CHAR` in GtktextView.
- Sets GtkTextView to non-editable because the program isn't an editor but only a viewer.
- Reads the file and inserts the text into GtkTextBuffer (this will be explained in detail later).
- If the file is not opened then outputs an error message and destroys the window. It makes the application quit.
The file reading part of the program is shown again below.
@ -140,12 +140,12 @@ if (g_file_load_contents(files[0], NULL, &contents, &length, NULL, NULL)) {
}
~~~
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`.
The function `g_file_load_contents` loads the file contents into a buffer, which is automatically allocated, and sets `contents` to point the buffer.
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 the function succeeds, it inserts the contents into GtkTextBuffer, frees the buffer memories pointed by `contents`, sets the title of the window,
frees the memories pointed by `filename` and shows the window.
If it fails, it outputs an error message and destroys the window.
## GtkNotebook
@ -169,20 +169,20 @@ Now I want to show you the program `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.
- 11-13: Variables `nb`, `lab` and `nbp` are defined and points 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.
- 27-28 GtkNotebook is generated and inserted to the GtkApplicationWindow as a child.
- 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:
- 39-42 inserts the contents of the file into GtkTextBuffer and frees the memory pointed by `contents`. Gets the filename and generates GtkLabel with the filename.
- 43: Appends 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`.
- 44: Gets GtkNotebookPage object and sets `nbp` to point the GtkNotebookPage.
- 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`
- 46: frees 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

@ -16,7 +16,7 @@ We just add two things to the file viewer.
A couple of ways are possible to get memories to keep GFile.
- Use global variables.
- make a child widget object and extend the memories allocated to the widget.
- make a child object so that it can extend the memories for the GFile object.
Using global variables is easy to implement.
Define a sufficient size array of pointers to GFile.
@ -48,12 +48,11 @@ It has everything that GtkTextView has.
For example, TfeTextView has GtkTextbuffer corresponds to GtkTextView inside TfeTextView.
And important thing is that TfeTextView can have a memory to keep a pointer to GFile.
However, to understand the general theory about gobjects is very hard especially for beginners.
However, to understand the general theory about Gobject 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 subsection.
## How to define a child widget of GtkTextView
Let's define TfeTextView object which is a child object of GtkTextView.
First, look at the program below.
@ -95,7 +94,7 @@ tfe_text_view_new (void) {
If you are curious about the background theory of this program, It's very good for you.
Because knowing the theory is very important for you to program GTK applications.
Look at GObject API reference.
Look at [GObject API reference](https://developer.gnome.org/gobject/stable/).
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.
@ -125,7 +124,7 @@ 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.
`tv` is a pointer to TfeTextView object instance which is a C-structure 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.
@ -146,7 +145,14 @@ It will be modified later.
## Close-request signal
After editing a file, `tfe1.c` writes files just before the window closes.
Imagine that you use this editor.
First, you run the editor with arguments.
The arguments are filenames.
The editor reads the files and shows its window with the text of files in it.
Then you edit the text.
After you finish editing, you exit the editor.
The editor updates 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.
@ -157,19 +163,22 @@ The function `before_close` is invoked when the signal "close-request" is emitte
g_signal_connect (win, "close-request", G_CALLBACK (before_close), NULL);
~~~
The argument win is GtkApplicationWindow, in which the signal "close-request" is defined, and before\_close is the handler.
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.
The program of `before_close` is as follows.
@@@ 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.
- 13: Gets 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.
- 15-17: Gets GtkScrolledWindow, TfeTextView and a pointer to GFile.
The pointer was stored when `on_open` handler had run. It will be shown later.
- 18-20: Gets GtkTextBuffer and contents. `start_iter` and `end_iter` are 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: Writes the file.
## Source code of tfe1.c
@ -177,11 +186,11 @@ Now I will show you all the source code of `tfe1`.c.
@@@ tfe/tfe1.c
- 102: set the pointer to GFile into TfeTextView.
- 102: Sets 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.
`g_file_dup` duplicates the given GFile structure.
- 118: Connects "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.

View file

@ -3,7 +3,7 @@
## New, open and save button
We made the simplest editor in the previous section.
It reads the files in `on_open` function at start-up and writes it when closing the window.
It reads the files in `on_open` function at start-up and writes them when closing the 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.
@ -18,21 +18,21 @@ The function `on_open` in the source code `tfe2.c` is as follows.
The point is how to build the window.
- 25-27: Generate GtkApplicationWindow and set its title and default size.
- 29-30: Generate GtkBox `boxv`.
- 25-27: Generates GtkApplicationWindow and sets the title and default size.
- 29-30: Generates 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.
- 32-33: Generates GtkBox `boxh` and appends it to 'boxv' as a first child.
- 35-40: Generates three dummy labels.
The labels `dmy1` and `dmy3` has a character width of ten.
The other label `dmy2` is set hexpand property TRUE.
The other label `dmy2` has hexpand property which is set to be 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.
- 41-44: Generates four buttons.
- 46-52: Appends these GtkLabel and GtkButton to `boxh`.
- 54-57: Generates GtkNotebook and sets hexpand and vexpand properties TRUE.
This makes it expand horizontally and vertically as big as possible.
It is appended to `boxv` as the second child.
The number of lines is 33(=57-25+1) to build the widgets.
@ -42,7 +42,7 @@ 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.
It reduces the cumbersome work.
## Ui file
@ -51,7 +51,8 @@ First, let's look at the ui file `tfe3.ui` that defines a structure of the widge
@@@ tfe/tfe3.ui
This is coded with XML structure.
Constructs beginning with `<` and ending with `>` are called tags, and are divided into two parts, start tag and end tag.
Constructs beginning with `<` and ending with `>` are called tags.
And there are two types of tags, 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.
@ -64,7 +65,7 @@ And the three properties of the window are defined.
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.
Those two describe the same structure of widgets.
## GtkBuilder
@ -79,7 +80,8 @@ 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_new_from_file` reads the file given as an argument.
Then, it builds the widgets and pointers to them, creates GtkBuilder object and put the pointers 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.
@ -93,14 +95,13 @@ $$$
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.
Now I'll show you `on_open` function in the C source code `tfe3.c`.
@@@ tfe/tfe3.c on_open
The source code of `tfe3.c` is stored in [src/tfe](https://github.com/ToshioCP/Gtk4-tutorial/tree/main/src/tfe) directory.
The whole 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).
You can also get the source files below.
### Using ui string
@ -132,8 +133,8 @@ 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.
- Add backslash before each double quote.
- Add double quote at the left and right.
### Using Gresource
@ -148,10 +149,10 @@ It describes resource files.
@@@ tfe/tfe3.gresource.xml
- 2: gresources tag can include mulitple gresources (gresource tags).
- 2: `gresources` tag can include multiple 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.
- 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.
@ -167,7 +168,7 @@ 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
Modify `tfe3.c` and save it as `tfe3_r.c`.
~~~C
#include "resources.c"

View file

@ -2,13 +2,13 @@
## What do we need to think about to manage big source files?
We've managed to compile a small editor so far.
We've compiled a small editor so far.
But Some bad signs are beginning to appear.
- We have only one C source file and put everything into it.
- We've had 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.
We should control them by one building tool.
These ideas are useful to manage big source files.
@ -64,16 +64,16 @@ Dividing a file makes it easy to maintain source files.
But now we are faced with a new problem.
The building step increases.
- 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`.
- Compiling the ui file `tfe.ui` into `resources.c`.
- Compiling `tfe.c` into `tfe.o` (object file).
- Compiling `tfetextview.c` into `tfetextview.o`.
- Compiling `resources.c` into `resources.o`.
- Linking all the object files into application `tfe`.
Now build tool is necessary to manage it.
Make is one of the build tools.
It was originally created in 1976.
So it is an old and widely used program.
It was created in 1976.
It is an old and widely used program.
Make analyzes Makefile and executes compilers.
All instructions are written in Makefile.
@ -96,7 +96,7 @@ 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.
If the modification time of `sample.c` is older then the generation of `sample.o`, then no compiling is necessary, so make does nothing.
The Makefile for `tfe` is as follows.
@ -132,19 +132,19 @@ Rake has task and file task, which is similar to target, prerequisite and recipe
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.
- 3-6: Defines target file, source file and so on.
- 1, 8: Loads clean library. And defines 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.
- 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.prerequisites is an array of prerequisites.
- 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.
- 16-21: There's a loop by each element of the array of objfiles. Each object depends on corresponding source file.
- 23-25: Resource 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.
@ -163,17 +163,17 @@ 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.
The first parameter is the name of the project and the second is the programming 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`.
- 5: `import` function imports a module.
In line 5, gnome module is imported and assigned 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.
- 8: Defines 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.
@ -188,6 +188,8 @@ Then, the executable file `tfe` is generated under the directory `_build`.
$ _build/tfe tfe.c tfetextview.c
Then the window appears.
And two notebook pages are in the window.
One notebook is `tfe.c` and the other is `tfetextview.c`.
I've shown you three build tools.
I think meson and ninja is the best choice for the present.

View file

@ -66,7 +66,7 @@ before_close (GtkWindow *win, GtkWidget *nb) {
static void
on_activate (GApplication *app, gpointer user_data) {
g_print ("You need a filename argument.\n");
g_print ("You need to give filenames as arguments.\n");
}
static void

View file

@ -5,6 +5,6 @@ gtkdep = dependency('gtk4')
gnome=import('gnome')
resources = gnome.compile_resources('resources','tfe.gresource.xml')
sourcefiles=files('tfeapplication.c', 'tfenotebook.c', 'tfetextview.c')
sourcefiles=files('tfeapplication.c', 'tfenotebook.c', '../tfetextview/tfetextview.c')
executable('tfe', sourcefiles, resources, dependencies: gtkdep)

View file

@ -1,4 +1,4 @@
#include <gtk/gtk.h>
#include "tfetextview.h"
#include "../tfetextview/tfetextview.h"
#include "tfenotebook.h"

View file

@ -50,6 +50,7 @@ notebook_page_build (GtkNotebook *nb, GtkWidget *tv, char *filename) {
gint i;
scr = gtk_scrolled_window_new ();
gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
lab = gtk_label_new (filename);
i = gtk_notebook_append_page (nb, scr, lab);

View file

@ -1,218 +0,0 @@
#include "tfe.h"
struct _TfeTextView
{
GtkTextView parent;
GFile *file;
};
G_DEFINE_TYPE (TfeTextView, tfe_text_view, GTK_TYPE_TEXT_VIEW);
enum {
CHANGE_FILE,
OPEN_RESPONSE,
NUMBER_OF_SIGNALS
};
static guint tfe_text_view_signals[NUMBER_OF_SIGNALS];
static void
tfe_text_view_dispose (GObject *gobject) {
TfeTextView *tv = TFE_TEXT_VIEW (gobject);
if (G_IS_FILE (tv->file))
g_clear_object (&tv->file);
G_OBJECT_CLASS (tfe_text_view_parent_class)->dispose (gobject);
}
static void
tfe_text_view_init (TfeTextView *tv) {
GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
tv->file = NULL;
gtk_text_buffer_set_modified (tb, FALSE);
gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
}
static void
tfe_text_view_class_init (TfeTextViewClass *class) {
GObjectClass *object_class = G_OBJECT_CLASS (class);
object_class->dispose = tfe_text_view_dispose;
tfe_text_view_signals[CHANGE_FILE] = g_signal_newv ("change-file",
G_TYPE_FROM_CLASS (class),
G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
NULL /* closure */,
NULL /* accumulator */,
NULL /* accumulator data */,
NULL /* C marshaller */,
G_TYPE_NONE /* return_type */,
0 /* n_params */,
NULL /* param_types */);
GType param_types[] = {G_TYPE_INT};
tfe_text_view_signals[OPEN_RESPONSE] = g_signal_newv ("open-response",
G_TYPE_FROM_CLASS (class),
G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
NULL /* closure */,
NULL /* accumulator */,
NULL /* accumulator data */,
NULL /* C marshaller */,
G_TYPE_NONE /* return_type */,
1 /* n_params */,
param_types);
}
GFile *
tfe_text_view_get_file (TfeTextView *tv) {
g_return_val_if_fail (TFE_IS_TEXT_VIEW (tv), NULL);
return g_file_dup (tv->file);
}
static void
open_dialog_response(GtkWidget *dialog, gint response, TfeTextView *tv) {
GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
GFile *file;
char *contents;
gsize length;
GtkWidget *message_dialog;
GError *err = NULL;
if (response != GTK_RESPONSE_ACCEPT)
g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_CANCEL);
else if (! G_IS_FILE (file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog))))
g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
else if (! g_file_load_contents (file, NULL, &contents, &length, NULL, &err)) { /* read error */
if (G_IS_FILE (file))
g_object_unref (file);
message_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL,
GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
"%s.\n", err->message);
g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
gtk_widget_show (message_dialog);
g_error_free (err);
g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
} else {
gtk_text_buffer_set_text (tb, contents, length);
g_free (contents);
if (G_IS_FILE (tv->file))
g_object_unref (tv->file);
tv->file = file;
gtk_text_buffer_set_modified (tb, FALSE);
g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS);
}
gtk_window_destroy (GTK_WINDOW (dialog));
}
void
tfe_text_view_open (TfeTextView *tv, GtkWidget *win) {
g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
g_return_if_fail (GTK_IS_WINDOW (win));
GtkWidget *dialog;
dialog = gtk_file_chooser_dialog_new ("Open file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_OPEN,
"Cancel", GTK_RESPONSE_CANCEL,
"Open", GTK_RESPONSE_ACCEPT,
NULL);
g_signal_connect (dialog, "response", G_CALLBACK (open_dialog_response), tv);
gtk_widget_show (dialog);
}
static void
saveas_dialog_response (GtkWidget *dialog, gint response, TfeTextView *tv) {
GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
GFile *file;
if (response == GTK_RESPONSE_ACCEPT) {
file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
if (G_IS_FILE(file)) {
tv->file = file;
gtk_text_buffer_set_modified (tb, TRUE);
g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
tfe_text_view_save (TFE_TEXT_VIEW (tv));
}
}
gtk_window_destroy (GTK_WINDOW (dialog));
}
void
tfe_text_view_save (TfeTextView *tv) {
g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
GtkTextIter start_iter;
GtkTextIter end_iter;
gchar *contents;
GtkWidget *message_dialog;
GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
GError *err = NULL;
if (! gtk_text_buffer_get_modified (tb))
return; /* no necessary to save it */
else if (tv->file == NULL)
tfe_text_view_saveas (tv);
else {
gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
if (g_file_replace_contents (tv->file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, &err))
gtk_text_buffer_set_modified (tb, FALSE);
else {
/* It is possible that tv->file is broken. */
/* It is a good idea to set tv->file to NULL. */
if (G_IS_FILE (tv->file))
g_object_unref (tv->file);
tv->file =NULL;
g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
gtk_text_buffer_set_modified (tb, TRUE);
message_dialog = gtk_message_dialog_new (GTK_WINDOW (win), GTK_DIALOG_MODAL,
GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
"%s.\n", err->message);
g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
gtk_widget_show (message_dialog);
g_error_free (err);
}
}
}
void
tfe_text_view_saveas (TfeTextView *tv) {
g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
GtkWidget *dialog;
GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
dialog = gtk_file_chooser_dialog_new ("Save file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_SAVE,
"_Cancel", GTK_RESPONSE_CANCEL,
"_Save", GTK_RESPONSE_ACCEPT,
NULL);
g_signal_connect (dialog, "response", G_CALLBACK (saveas_dialog_response), tv);
gtk_widget_show (dialog);
}
GtkWidget *
tfe_text_view_new_with_file (GFile *file) {
g_return_val_if_fail (G_IS_FILE (file), NULL);
GtkWidget *tv;
GtkTextBuffer *tb;
char *contents;
gsize length;
if (! g_file_load_contents (file, NULL, &contents, &length, NULL, NULL)) /* read error */
return NULL;
tv = tfe_text_view_new();
tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
gtk_text_buffer_set_text (tb, contents, length);
g_free (contents);
TFE_TEXT_VIEW (tv)->file = g_file_dup (file);
return tv;
}
GtkWidget *
tfe_text_view_new (void) {
return GTK_WIDGET (g_object_new (TFE_TYPE_TEXT_VIEW, NULL));
}

View file

@ -1,29 +0,0 @@
#define TFE_TYPE_TEXT_VIEW tfe_text_view_get_type ()
G_DECLARE_FINAL_TYPE (TfeTextView, tfe_text_view, TFE, TEXT_VIEW, GtkTextView)
/* "open-response" signal response */
enum
{
TFE_OPEN_RESPONSE_SUCCESS,
TFE_OPEN_RESPONSE_CANCEL,
TFE_OPEN_RESPONSE_ERROR
};
GFile *
tfe_text_view_get_file (TfeTextView *tv);
void
tfe_text_view_open (TfeTextView *tv, GtkWidget *win);
void
tfe_text_view_save (TfeTextView *tv);
void
tfe_text_view_saveas (TfeTextView *tv);
GtkWidget *
tfe_text_view_new_with_file (GFile *file);
GtkWidget *
tfe_text_view_new (void);

View file

@ -1,3 +1,4 @@
#include <string.h>
#include "tfetextview.h"
struct _TfeTextView
@ -28,11 +29,7 @@ tfe_text_view_dispose (GObject *gobject) {
static void
tfe_text_view_init (TfeTextView *tv) {
GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
tv->file = NULL;
gtk_text_buffer_set_modified (tb, FALSE);
gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
}
static void
@ -129,6 +126,8 @@ saveas_dialog_response (GtkWidget *dialog, gint response, TfeTextView *tv) {
if (response == GTK_RESPONSE_ACCEPT) {
file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
if (G_IS_FILE(file)) {
if (G_IS_FILE (tv->file))
g_object_unref (tv->file);
tv->file = file;
gtk_text_buffer_set_modified (tb, TRUE);
g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
@ -151,7 +150,7 @@ tfe_text_view_save (TfeTextView *tv) {
GError *err = NULL;
if (! gtk_text_buffer_get_modified (tb))
return; /* no necessary to save it */
return; /* no need to save it */
else if (tv->file == NULL)
tfe_text_view_saveas (tv);
else {
@ -160,13 +159,12 @@ tfe_text_view_save (TfeTextView *tv) {
if (g_file_replace_contents (tv->file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, &err))
gtk_text_buffer_set_modified (tb, FALSE);
else {
/* It is possible that tv->file is broken. */
/* It is possible that tv->file is broken or you don't have permission to write. */
/* It is a good idea to set tv->file to NULL. */
if (G_IS_FILE (tv->file))
g_object_unref (tv->file);
tv->file =NULL;
g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
gtk_text_buffer_set_modified (tb, TRUE);
message_dialog = gtk_message_dialog_new (GTK_WINDOW (win), GTK_DIALOG_MODAL,
GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
"%s.\n", err->message);
@ -185,8 +183,8 @@ tfe_text_view_saveas (TfeTextView *tv) {
GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
dialog = gtk_file_chooser_dialog_new ("Save file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_SAVE,
"_Cancel", GTK_RESPONSE_CANCEL,
"_Save", GTK_RESPONSE_ACCEPT,
"Cancel", GTK_RESPONSE_CANCEL,
"Save", GTK_RESPONSE_ACCEPT,
NULL);
g_signal_connect (dialog, "response", G_CALLBACK (saveas_dialog_response), tv);
gtk_widget_show (dialog);

View file

@ -3,12 +3,12 @@
TfeTextView -- Child widget of GtkTextView. It holds GFile the contents of GtkTextBuffer correponds to.
## Functions
- GFile *[tfe_text_view_get_file ()](#tfe_text_view_get_file-)
- void [tfe_text_view_open ()](#tfe_text_view_open-)
- void [tfe_text_view_save ()](#tfe_text_view_save-)
- void [tfe_text_view_saveas ()](#tfe_text_view_saveas-)
- GtkWidget *[tfe_text_view_new_with_file ()](#tfe_text_view_new_with_file-)
- GtkWidget *[tfe_text_view_new ()](#tfe_text_view_new-)
- GFile *[tfe_text_view_get_file ()](#tfe_text_view_get_file)
- void [tfe_text_view_open ()](#tfe_text_view_open)
- void [tfe_text_view_save ()](#tfe_text_view_save)
- void [tfe_text_view_saveas ()](#tfe_text_view_saveas)
- GtkWidget *[tfe_text_view_new_with_file ()](#tfe_text_view_new_with_file)
- GtkWidget *[tfe_text_view_new ()](#tfe_text_view_new)
## Signals

View file

@ -7,15 +7,18 @@ exec ruby -x "$0" "$@"
require_relative 'lib/lib_src2md.rb'
def usage
$stderr.print "Usage: ruby srcd2md.rb src.md_file md_file\n"
$stderr.print "Usage: ruby srcd2md.rb src.md_file md_file width\n"
$stderr.print " The width is used to fold lines in indented or fenced code blocks.\n"
$stderr.print " If the width is negative, no lines are folded.\n"
end
if ARGV.size != 2
if ARGV.size != 3
usage
exit 1
end
srcmd = ARGV[0]
md = ARGV[1]
src2md srcmd, md, 80
width = ARGV[2]
src2md srcmd, md, width.to_i