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