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. All the sources are listed in Section 16. 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 at least into 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 instance. First, think about instance of objects. Instance is structured memory. THe structure is described as C language structure. The following is a structure of TfeTextView.
/* This typedef statement is automatically generated by the macro G_DECLARE_FINAL_TYPE */
typedef struct _TfeTextView TfeTextView;
struct _TfeTextView {
GtkTextView parent;
GFile *file; };
The members of the structure are:
parent
is GtkTextView which is C structure. It is declared in gtktextview.h
. GtkTextView is the parent of TfeTextView.file
is a pointer to GFile. It can be NULL if no file corresponds to the TfeTextView object.Notice the program above is the declaration of the structure, not the definition. So, no memories are allocated at this moment. They are to be allocated when tfe_text_view_new
function is invoked. The memory allocated with tfe_text_view_new
is an instance of TfeTextView object. Therefore, There can be multiple TfeTextView instances if tfe_text_view_new
is called multiple times.
You can find the declaration of the ancestors of TfeTextView in the source files 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 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 inherit all the features from ancestors. The structure of TfeTextView
is like the following diagram.
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, NULL));
}
When this function is involed, a TfeTextView instance is created and initialized. The initialization process is as follows.
The step one through three 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 four 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 class and instance (except abstract object). An instance is memory of C structure, which is described in the previous two subsections. Each object can have more than one instance. Those instances have the same structure. An instance just 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.
You’ve already seen many functions. For example, tfe_text_view_new
is a function to create a TfeTextView instance. These functions are similar to public object methods in object oriented languages such as Java or Ruby. Functions are public, which means that they are expected to be used by other objects.
Class comprises mainly pointers to functions. Those functions are used by the object itself or its 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;
/* padding */
gpointer pdummy[6];
};
I’d like to explain some of the members. There’s a pointer to the function dispose
in line 23.
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 24 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 generated. 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’ class in it.
typedef _TfeTextView TfeTextView;
struct _TfeTextView {
GtkTextView parent;
GFile *file; };
TfeTextView structure has GtkTextView type as the first member. In the same way, GtkTextView has its parent type (GtkWidget) as the first member. GtkWidget has its parent type (GtkInitiallyUnowned) as the first member. The structure of GtkInitiallyUnowned is the same as GObject. Therefore, TFeTextView includes GObject, GtkWidget and GtkTextView in itself.
GObject -- GInitiallyUnowned -- GtkWidget -- GtkTextView -- TfeTextView
The following is extracts from the source files (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, 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. 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 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
.
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, 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
.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.
static void
tfe_text_view_class_init (TfeTextViewClass *class) {
GObjectClass *object_class = G_OBJECT_CLASS (class);
object_class->dispose = tfe_text_view_dispose;
}
Each ancestors’ class has been 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.
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 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.