Defining a Final Class
A Very Simple Editor
We made a very simple file viewer in the previous section.
Now we go on to rewrite it and turn it into a very simple
editor. Its source file is tfe1.c (text file editor
1) under tfe directory.
GtkTextView is a multi-line editor. So, we don’t need to write the editor from scratch. We just add two things to the file viewer:
- Pointers to GFile instances.
- A text-save function.
There are a couple of ways to store the pointers.
- Use global variables
- Make a child class of GtkTextView and its each instance holds a pointer to the GFile instance.
Using global variables is easy to implement. Define a sufficient size pointer array to GFile. For example,
GFile *f[20];The variable f[i] corresponds to the file
associated with the i-th GtkNotebookPage.
However, There are two problems. The first is the size of the array. If a user gives too many arguments (more than 20 in the example above), it is impossible to store all the pointers to the GFile instances. The second is difficulty of maintainance. We have a small program so far. But, the more the program is developed, the larger it becomes. Generally speaking, it is very difficult to maintain global variables in a big program. When you check the global variable, you need to check all the codes that use the variable.
Making a child class is a good idea in terms of maintenance. We prefer this approach over using global variables.
Note that we are thinking about “child class”, not “child widget”. Child class and child widget are totally different. Class is a term of GObject system. If you are not familiar with GObject, see:
A child class inherits everything from the parent and, in addition, extends its performance. We will define TfeTextView as a child class of GtkTextView. It has everything that GtkTextView has and adds a pointer to a GFile.
How to Define a Child Class of GtkTextView
You need to know GObject system convention. First, look at the program below.
#define TFE_TYPE_TEXT_VIEW tfe_text_view_get_type ()
G_DECLARE_FINAL_TYPE (TfeTextView, tfe_text_view, TFE, TEXT_VIEW, GtkTextView)
struct _TfeTextView
{
GtkTextView parent;
GFile *file;
};
G_DEFINE_FINAL_TYPE (TfeTextView, tfe_text_view, GTK_TYPE_TEXT_VIEW);
static void
tfe_text_view_init (TfeTextView *tv) {
}
static void
tfe_text_view_class_init (TfeTextViewClass *class) {
}
void
tfe_text_view_set_file (TfeTextView *tv, GFile *f) {
tv -> file = f;
}
GFile *
tfe_text_view_get_file (TfeTextView *tv) {
return tv -> file;
}
GtkWidget *
tfe_text_view_new (void) {
return GTK_WIDGET (g_object_new (TFE_TYPE_TEXT_VIEW, NULL));
}- TfeTextView is divided into two parts. Tfe and TextView. Tfe is called prefix or namespace. TextView is called object.
- There are three different identifier patterns. TfeTextView (camel case), tfe_text_view (this is used for functions) and TFE_TEXT_VIEW (This is used to cast a object to TfeTextView).
- First, define the TFE_TYPE_TEXT_VIEW macro as
tfe_text_view_get_type(). Macro names follow the pattern
(prefix)_TYPE_(object) and are written in uppercase. The
replacement text is always (prefix)_(object)_get_type () and are
written in lower case. This definition must be put before
G_DECLARE_FINAL_TYPEmacro. - The arguments of
G_DECLARE_FINAL_TYPEmacro are the child class name in camel case, lower case with underscore, prefix (upper case), object (upper case with underscore) and parent class name (camel case). The following two C structures are declared in the expansion of the macro.typedef struct _TfeTextView TfeTextViewtypedef struct {GtkTextViewClass parent_class; } TfeTextViewClass;
- These declarations tell us that TfeTextView and TfeTextViewClass are C structures. “TfeTextView” has two meanings, class name and C structure name. The C structure TfeTextView is called object. Similarly, TfeTextViewClass is called class.
- Declare the structure
_TfeTextView. The underscore is necessary. The first member is the parent object (C structure). Notice this is not a pointer but the object itself. The second member and after are members of the child object. TfeTextView structure has a pointer to a GFile instance as a member. G_DEFINE_FINAL_TYPEmacro. The arguments are the child object name in camel case, lower case with underscore and parent object type (prefix)_TYPE_(module). This macro is mainly used to register the new class to the type system. Type system is a base system of GObject. Every class has its own type. The types of GObject, GtkWidget and TfeTextView areG_TYPE_OBJECT,GTK_TYPE_WIDGETandTFE_TYPE_TEXT_VIEWrespectively. For example,TFE_TYPE_TEXT_VIEWis a macro and it is expanded to a functiontfe_text_view_get_type(). It returns a unique GType identifier representing the type within the GObject.- The instance init function
tfe_text_view_initis called when the instance is created. It is the same as a constructor in other object oriented languages. - The class init function
tfe_text_view_class_initis called when the class is created. - Two functions
tfe_text_view_set_fileandtfe_text_view_get_fileare public functions. Public functions are open and you can call them anywhere. They are the same as public method in other object oriented languages.tvis a pointer to the TfeTextView object (C structure). It has a memberfileand it is pointed to bytv->file. - TfeTextView instance creation function is
tfe_text_view_new. Its name is (prefix)_(object)_new. It usesg_object_newfunction to create the instance. The arguments are (prefix)_TYPE_(object), a list to initialize properties and NULL. NULL is the end mark of the property list. No property is initialized here. And the return value is cast to GtkWidget.
This program shows the overview of how to define a child class.
Close-request Signal
Imagine that you are using this editor. First, you run the editor with arguments. The arguments are filenames. The editor reads the files and shows the window with the text of files in it. Then you edit the text. After you finish editing, you click on the close button of the window and quit the editor. The editor updates files just before the window closes.
GtkWindow emits the “close-request” signal when the close
button is clicked. We will connect the signal and the handler
before_close. (A handler is a C function which is
connected to a signal.) The function before_close
is called when the signal “close-request” is emitted.
g_signal_connect (win, "close-request", G_CALLBACK (before_close), NULL);The argument win is a GtkApplicationWindow, in
which the signal “close-request” is defined, and
before_close is the handler. The
G_CALLBACK cast is necessary for the handler. The
program of before_close is as follows.
static gboolean
before_close (GtkWindow *win, GtkWidget *nb) {
GtkWidget *scr;
GtkWidget *tv;
GFile *file;
char *pathname;
GtkTextBuffer *tb;
GtkTextIter start_iter;
GtkTextIter end_iter;
char *contents;
unsigned int n;
unsigned int i;
GError *err = NULL;
n = gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb));
for (i = 0; i < n; ++i) {
scr = gtk_notebook_get_nth_page (GTK_NOTEBOOK (nb), i);
tv = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (scr));
file = tfe_text_view_get_file (TFE_TEXT_VIEW (tv));
tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
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 (file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, &err)) {
g_printerr ("%s.\n", err->message);
g_clear_error (&err);
}
g_free (contents);
g_object_unref (file);
}
return FALSE;
}The numbers on the left are line numbers.
- 15: The number of note book pages is assigned to
n. - 16-29: For loop with regard to the index to each page.
- 17-19:
scr,tvandfilehold pointers to the GtkScrolledWindow, TfeTextView and GFile. The GFile of TfeTextView was stored whenapp_openhandler was called. It will be shown later. - 20-22:
tbholds a pointer to the GtkTextBuffer of the TfeTextView. The contents of the buffer are accessed with iterators. Iterators point somewhere in the buffer. The functiongtk_text_buffer_get_boundsassigns the start and end of the buffer tostart_iterandend_iterrespectively. Then the functiongtk_text_buffer_get_textreturns the text fromstart_iterup toend_iter, which is the whole text in the buffer. - 23-26: The text is saved to the file. If it fails, error
messages are displayed. The GError instance must be freed and
the pointer
errneeds to be NULL for the next run in the loop. - 27:
contentsare freed. - 28: GFile is useless.
g_object_unrefdecreases the reference count of the GFile. Reference count will be explained in the later section (section 11). The reference count will be zero and the GFile instance will be destroyed.
Source code of tfe1.c
The following is the whole source code of
tfe1.c.
#include <gtk/gtk.h>
/* Define TfeTextView Widget which is the child class of GtkTextView */
#define TFE_TYPE_TEXT_VIEW tfe_text_view_get_type ()
G_DECLARE_FINAL_TYPE (TfeTextView, tfe_text_view, TFE, TEXT_VIEW, GtkTextView)
struct _TfeTextView
{
GtkTextView parent;
GFile *file;
};
G_DEFINE_FINAL_TYPE (TfeTextView, tfe_text_view, GTK_TYPE_TEXT_VIEW);
static void
tfe_text_view_init (TfeTextView *tv) {
tv->file = NULL;
}
static void
tfe_text_view_class_init (TfeTextViewClass *class) {
}
void
tfe_text_view_set_file (TfeTextView *tv, GFile *f) {
tv->file = f;
}
GFile *
tfe_text_view_get_file (TfeTextView *tv) {
return tv -> file;
}
GtkWidget *
tfe_text_view_new (void) {
return GTK_WIDGET (g_object_new (TFE_TYPE_TEXT_VIEW, NULL));
}
/* ---------- end of the definition of TfeTextView ---------- */
static gboolean
before_close (GtkWindow *win, GtkWidget *nb) {
GtkWidget *scr;
GtkWidget *tv;
GFile *file;
char *pathname;
GtkTextBuffer *tb;
GtkTextIter start_iter;
GtkTextIter end_iter;
char *contents;
unsigned int n;
unsigned int i;
GError *err = NULL;
n = gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb));
for (i = 0; i < n; ++i) {
scr = gtk_notebook_get_nth_page (GTK_NOTEBOOK (nb), i);
tv = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (scr));
file = tfe_text_view_get_file (TFE_TEXT_VIEW (tv));
tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
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 (file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, &err)) {
g_printerr ("%s.\n", err->message);
g_clear_error (&err);
}
g_free (contents);
g_object_unref (file);
}
return FALSE;
}
static void
app_activate (GApplication *app) {
g_print ("You need to give filenames as arguments.\n");
}
static void
app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint) {
GtkWidget *win;
GtkWidget *nb;
GtkWidget *lab;
GtkNotebookPage *nbp;
GtkWidget *scr;
GtkWidget *tv;
GtkTextBuffer *tb;
char *contents;
gsize length;
char *filename;
int i;
GError *err = NULL;
win = gtk_application_window_new (GTK_APPLICATION (app));
gtk_window_set_title (GTK_WINDOW (win), "file editor");
gtk_window_set_default_size (GTK_WINDOW (win), 600, 400);
nb = gtk_notebook_new ();
gtk_window_set_child (GTK_WINDOW (win), nb);
for (i = 0; i < n_files; i++) {
if (g_file_load_contents (files[i], NULL, &contents, &length, NULL, &err)) {
scr = gtk_scrolled_window_new ();
tv = tfe_text_view_new ();
tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
tfe_text_view_set_file (TFE_TEXT_VIEW (tv), g_file_dup (files[i]));
gtk_text_buffer_set_text (tb, contents, length);
g_free (contents);
filename = g_file_get_basename (files[i]);
lab = gtk_label_new (filename);
gtk_notebook_append_page (GTK_NOTEBOOK (nb), scr, lab);
nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr);
g_object_set (nbp, "tab-expand", TRUE, NULL);
g_free (filename);
} else {
g_printerr ("%s.\n", err->message);
g_clear_error (&err);
}
}
if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) {
g_signal_connect (win, "close-request", G_CALLBACK (before_close), nb);
gtk_window_present (GTK_WINDOW (win));
} else
gtk_window_destroy (GTK_WINDOW (win));
}
int
main (int argc, char **argv) {
GtkApplication *app;
int stat;
app = gtk_application_new ("com.github.ToshioCP.tfe1", G_APPLICATION_HANDLES_OPEN);
g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
g_signal_connect (app, "open", G_CALLBACK (app_open), NULL);
stat = g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);
return stat;
}- 109: The GFile pointer of the TfeTextView is set to the copy
of
files[i], which is a GFile created with the command line argument. The GFile object will be destroyed by the system later. So it needs to be copied before the assignment. A functiong_file_dupduplicates the GFile. Note: You can useg_object_refinstead ofg_file_dup. It just increases the reference count by 1. For reference count, see Section 11 for further information. - 124: The “close-request” signal is connected to
before_closehandler. The fourth argument is called “user data” and it will be the second argument of the signal handler. So,nbis given tobefore_closeas the second argument.
Now it’s time to compile and run.
$ cd src/tfe
$ comp tfe1
$ ./a.out taketori.txt`.
Modify the contents and close the window. Make sure that the file is modified.
Now we got a very simple editor. It’s not smart. We need more features like open, save, saveas, change font and so on. We will add them in the next section and after.