A new version of the text file editor (tfe
) will be made
in this section and the following four sections. It is
tfe5
. There are many changes from the prior version. They
are located in two directories, src/tfe5 and src/tfetextview.
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 into at least two parts, tfeapplication.c
and tfenotebook.c
.However, first of all, I’d like to focus on the object TfeTextView.
It is a child object of GtkTextView and has a new member
file
in it. The important thing is to manage the Gfile
object pointed by file
.
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:
GObject and its children are objects, which have both class and object C structures. First, think about instances. An instance is memories which has the object structure. The following is the structure of TfeTextView.
/* This typedef statement is automatically generated by the macro G_DECLARE_FINAL_TYPE */
typedef struct _TfeTextView TfeTextView;
struct _TfeTextView {
;
GtkTextView parent*file;
GFile };
The members of the structure are:
parent
is a GtkTextView C structure. It is
declared in gtktextview.h
. GtkTextView is the parent of
TfeTextView.file
is a pointer to a GFile. It can be NULL
if the TfeTextView instance has no file. The most common case is that
the instance is newly created.You can find the declaration of the structures of the ancestors in the source files in GTK or GLib. The following is extracted from the source files (not exactly the same).
typedef struct _GObject GObject;
typedef struct _GObject GInitiallyUnowned;
struct _GObject
{
;
GTypeInstance g_type_instancevolatile guint ref_count;
*qdata;
GData };
typedef struct _GtkWidget GtkWidget;
struct _GtkWidget
{
;
GInitiallyUnowned parent_instance*priv;
GtkWidgetPrivate };
typedef struct _GtkTextView GtkTextView;
struct _GtkTextView
{
;
GtkWidget parent_instance*priv;
GtkTextViewPrivate };
In each structure, its parent is declared at the top of the members.
So, all the ancestors are included in the child object. The structure of
TfeTextView
is like the following diagram.
Derivable classes (ancestor classes) have their own private data area which are not included by the structure above. For example, GtkWidget has GtkWidgetPrivate (C structure) for its private data.
Notice declarations are not definitions. So, no memories are
allocated when C structures are declared. Memories are allocated to them
from the heap area when the tfe_text_view_new
function is
called. At the same time, the ancestors’ private area allocated for the
TfeTetView. They are hidden from TfeTextView and it can’t access to them
directly. The created memory is called instance. When a TfeTextView
instance is created, it is given three data area.
TfeTextView functions can access to its instance only. The GtkWidgetPrivate and GtkTextViewPrivate are used by the ancestors’ functions. See the following example.
*tv = tfe_text_view_new ();
GtkWidget *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv)); GtkTextBuffer
The parent’s function gtk_text_view_get_buffer
accesses
the GtkTextViewPrivate data (owned by tv
). There is a
pointer, which points the GtkBuffer, in the private area and the
function returns the pointer. (Actual behavior is a bit more
complicated.)
TfeTextView instances inherit the ancestors functions like this.
A TfeTextView instance is created every time the
tfe_text_view_new
function is called. Therefore, multiple
TfeTextView instances can exist.
The function tfe_text_view_new
creates a new TfeTextView
instance.
GtkWidget *
tfe_text_view_new (void) {
return GTK_WIDGET (g_object_new (TFE_TYPE_TEXT_VIEW, "wrap-mode", GTK_WRAP_WORD_CHAR, NULL));
}
When this function is invoked, a TfeTextView instance is created and initialized. The initialization process is as follows.
priv
) in the
TfeTextView instance and GtkWidgetPrivate structure.priv
) in the
TfeTextView instance and GtkTextViewPrivate structure.file
) in the TfeTextView
instance.The step two through four is done by g_object_init
,
gtk_widget_init
and gtk_text_view_init
. They
are called by the system automatically and you don’t need to care about
them. Step five is done by the function tfe_text_view_init
in tfetextview.c
.
This function just initializes tv->file
to be
NULL
.
In Gtk, all objects derived from GObject have classes and instances (but abstract objects have only classes). Instances are memory of C structure, which are described in the previous two subsections. Each object can have more than one instance. Those instances have the same structure. Instances just have data. Therefore, it doesn’t define object’s behavior. We need at least two things. One is functions and the other is class methods.
The latest GTK 4 document classifies functions into a constructor, functions and instance methods.
gtk_(objectname)_new
. They create the objects.This tutorial uses functions
in two ways, broad or
narrow sense.
You’ve already seen many functions. For example,
TfeTextView *tfe_text_view_new (void);
is a function
(constructor) to create a TfeTextView instance.GtkTextBuffer *gtk_text_view_get_buffer (GtkTextView *textview)
is a function (instance method) to get a GtkTextBuffer from
GtkTextView.Functions are public, which means that they are expected to be used by other objects. They are similar to public methods in object oriented languages.
Class (C structure) mainly consists of pointers to C functions. They
are called class methods and used by the object itself or its
descendant objects. For example, GObject class is declared in
gobject.h
in GLib source files.
typedef struct _GObjectClass GObjectClass;
typedef struct _GObjectClass GInitiallyUnownedClass;
struct _GObjectClass
{
GTypeClass g_type_class;
/*< private >*/
GSList *construct_properties;
/*< public >*/
/* seldom overridden */
GObject* (*constructor) (GType type,
guint n_construct_properties,
GObjectConstructParam *construct_properties);
/* overridable methods */
void (*set_property) (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
void (*get_property) (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
void (*dispose) (GObject *object);
void (*finalize) (GObject *object);
/* seldom overridden */
void (*dispatch_properties_changed) (GObject *object,
guint n_pspecs,
GParamSpec **pspecs);
/* signals */
void (*notify) (GObject *object,
GParamSpec *pspec);
/* called when done constructing */
void (*constructed) (GObject *object);
/*< private >*/
gsize flags;
gsize n_construct_properties;
gpointer pspecs;
gsize n_pspecs;
/* padding */
gpointer pdummy[3];
};
There’s a pointer to the function dispose
in line
25.
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. In the same way, line
26 says finalize
is a pointer to the function which has one
parameter, 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.
constructor
is called when
the instance is created. It completes the initialization of the
instance.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.finalize
finishes the
destruction process.These functions are called class methods. The methods are open to its descendants. But not open to the objects which are not the descendants.
TfeTextView class is a structure and it includes all its ancestors’ classes in it. Therefore, classes have similar hierarchy to instances.
GObjectClass (GInitiallyUnownedClass) -- GtkWidgetClass -- GtkTextViewClass -- TfeTextViewClass
The following is extracted from the source codes (not exactly the same).
struct _GtkWidgetClass
{
GInitiallyUnownedClass parent_class;
/*< public >*/
/* basics */
void (* show) (GtkWidget *widget);
void (* hide) (GtkWidget *widget);
void (* map) (GtkWidget *widget);
void (* unmap) (GtkWidget *widget);
void (* realize) (GtkWidget *widget);
void (* unrealize) (GtkWidget *widget);
void (* root) (GtkWidget *widget);
void (* unroot) (GtkWidget *widget);
void (* size_allocate) (GtkWidget *widget,
int width,
int height,
int baseline);
void (* state_flags_changed) (GtkWidget *widget,
GtkStateFlags previous_state_flags);
void (* direction_changed) (GtkWidget *widget,
GtkTextDirection previous_direction);
/* size requests */
GtkSizeRequestMode (* get_request_mode) (GtkWidget *widget);
void (* measure) (GtkWidget *widget,
GtkOrientation orientation,
int for_size,
int *minimum,
int *natural,
int *minimum_baseline,
int *natural_baseline);
/* Mnemonics */
gboolean (* mnemonic_activate) (GtkWidget *widget,
gboolean group_cycling);
/* explicit focus */
gboolean (* grab_focus) (GtkWidget *widget);
gboolean (* focus) (GtkWidget *widget,
GtkDirectionType direction);
void (* set_focus_child) (GtkWidget *widget,
GtkWidget *child);
/* keyboard navigation */
void (* move_focus) (GtkWidget *widget,
GtkDirectionType direction);
gboolean (* keynav_failed) (GtkWidget *widget,
GtkDirectionType direction);
gboolean (* query_tooltip) (GtkWidget *widget,
int x,
int y,
gboolean keyboard_tooltip,
GtkTooltip *tooltip);
void (* compute_expand) (GtkWidget *widget,
gboolean *hexpand_p,
gboolean *vexpand_p);
void (* css_changed) (GtkWidget *widget,
GtkCssStyleChange *change);
void (* system_setting_changed) (GtkWidget *widget,
GtkSystemSetting settings);
void (* snapshot) (GtkWidget *widget,
GtkSnapshot *snapshot);
gboolean (* contains) (GtkWidget *widget,
double x,
double y);
/*< private >*/
GtkWidgetClassPrivate *priv;
gpointer padding[8];
};
struct _GtkTextViewClass
{
GtkWidgetClass parent_class;
/*< public >*/
void (* move_cursor) (GtkTextView *text_view,
GtkMovementStep step,
int count,
gboolean extend_selection);
void (* set_anchor) (GtkTextView *text_view);
void (* insert_at_cursor) (GtkTextView *text_view,
const char *str);
void (* delete_from_cursor) (GtkTextView *text_view,
GtkDeleteType type,
int count);
void (* backspace) (GtkTextView *text_view);
void (* cut_clipboard) (GtkTextView *text_view);
void (* copy_clipboard) (GtkTextView *text_view);
void (* paste_clipboard) (GtkTextView *text_view);
void (* toggle_overwrite) (GtkTextView *text_view);
GtkTextBuffer * (* create_buffer) (GtkTextView *text_view);
void (* snapshot_layer) (GtkTextView *text_view,
GtkTextViewLayer layer,
GtkSnapshot *snapshot);
gboolean (* extend_selection) (GtkTextView *text_view,
GtkTextExtendSelection granularity,
const GtkTextIter *location,
GtkTextIter *start,
GtkTextIter *end);
void (* insert_emoji) (GtkTextView *text_view);
/*< private >*/
gpointer padding[8];
};
/* The following definition is generated by the macro G_DECLARE_FINAL_TYPE */
typedef struct {
GtkTextView parent_class;
} TfeTextViewClass;
G_DECLARE_FINAL_TYPE
. So, they are not written in either
tfe_text_view.h
or tfe_text_view.c
.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 programming terminology. Override is rewriting
ancestors’ class methods in the descendant class.)set_property
,
get_property
, dispose
, finalize
and constructed
are such methods.TfeTextViewClass includes its ancestors’ class in it. It is illustrated in the following diagram.
Every Object derived from GObject has a reference count. If an object
A refers to 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, 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. 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, 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 to 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.
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 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 instances. After that,
it invokes the function pointed by finalize
in its class to
complete the destruction process.
In the destruction process, TfeTextView needs to unref the GFile
pointed by tv->file
. You must write the dispose handler
tfe_text_view_dispose
.
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);
}
tv->file
points a GFile, it decreases the
reference count of the GFile instance. The function
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
.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
G_DEFINE_TYPE
macro expansion.
static void
(TfeTextViewClass *class) {
tfe_text_view_class_init *object_class = G_OBJECT_CLASS (class);
GObjectClass
->dispose = tfe_text_view_dispose;
object_class}
Each ancestors’ class has been created before TfeTextViewClass is
created. 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
.
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.
(tfe_text_view_parent_class)->dispose (gobject); G_OBJECT_CLASS
A variable tfe_text_view_parent_class
, which is made by
G_DEFINE_FINAL_TYPE
macro, is a pointer that points the
parent object class. The variable gobject
is a pointer to
TfeTextView instance which is casted as a GObject instance. Therefore,
G_OBJECT_CLASS (tfe_text_view_parent_class)->dispose
points the handler dh3
in the diagram above. The statement
G_OBJECT_CLASS (tfe_text_view_parent_class)->dispose (gobject)
is the same as dh3 (gobject)
, which means it releases all
the reference to the other instances in the GtkTextViewPrivate in the
TfeTextView instance. After that, dh3
calls
dh2
, and dh2
calls dh1
. Finally
all the references are released.