We made a very simple file viewer in the previous section. Now we go
on to rewrite it and turn it into 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:
There are a couple of ways to store the pointers.
Using global variables is easy to implement. Define a sufficient size pointer array to GFile. For example,
*f[20]; GFile
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 to maintain the program. We have a small program so far. But, the more you develop the program, the bigger its size grows. 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. And we prefer it rather than a global variable.
Be careful 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.
You need to know GObject system convention. First, look at the program below.
#define TFE_TYPE_TEXT_VIEW tfe_text_view_get_type ()
(TfeTextView, tfe_text_view, TFE, TEXT_VIEW, GtkTextView)
G_DECLARE_FINAL_TYPE
struct _TfeTextView
{
;
GtkTextView parent*file;
GFile };
(TfeTextView, tfe_text_view, GTK_TYPE_TEXT_VIEW);
G_DEFINE_TYPE
static void
(TfeTextView *tv) {
tfe_text_view_init }
static void
(TfeTextViewClass *class) {
tfe_text_view_class_init }
void
(TfeTextView *tv, GFile *f) {
tfe_text_view_set_file -> file = f;
tv }
*
GFile (TfeTextView *tv) {
tfe_text_view_get_file return tv -> file;
}
*
GtkWidget (void) {
tfe_text_view_new return GTK_WIDGET (g_object_new (TFE_TYPE_TEXT_VIEW, NULL));
}
typedef struct _TfeTextView TfeTextView
typedef struct {GtkTextViewClass parent_class; } TfeTextViewClass;
tv
is a pointer to the TfeTextView object (C
structure). It has a member file
and it is pointed by
tv->file
.tfe_text_view_new
. Its name is (prefix)_(object)_new. It
uses g_object_new function 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 casted to GtkWidget.This program shows the outline how to define a child class.
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 exit the editor. The editor updates files just before the window closes.
GtkWindow emits the “close-request” signal before it closes. 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.
(win, "close-request", G_CALLBACK (before_close), NULL); g_signal_connect
The argument win
is a 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.
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;
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, NULL)) {
if ((pathname = g_file_get_path (file)) != NULL) {
g_printerr ("Can't save %s.", pathname);
g_free (pathname);
} else
g_printerr ("Pathname not exist.\n");
}
g_free (contents);
g_object_unref (file);
}
return FALSE;
}
The numbers on the left of items are line numbers in the source code.
nb
is assigned to
n
.scr
, tv
and file
is
assigned pointers to the GtkScrolledWindow, TfeTextView and GFile. The
GFile of TfeTextView was stored when app_open
handler was
called. It will be shown later.tb
is assigned the GtkTextBuffer of the
TfeTextView. The buffer is accessed with iterators. Iterators points
somewhere in the buffer. The function
gtk_text_buffer_get_bounds
assigns the start and end of the
buffer to start_iter
and end_iter
respectively. Then the function gtk_text_buffer_get_text
returns the text between start_iter
and
end_iter
, which is the whole text in the buffer.contents
are freed.g_object_unref
decreases the
reference count of the GFile. Reference count will be explained in the
later section. The reference count will be zero in this program and the
GFile instance will destroy itself.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_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;
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, NULL)) {
if ((pathname = g_file_get_path (file)) != NULL) {
g_printerr ("Can't save %s.", pathname);
g_free (pathname);
} else
g_printerr ("Pathname not exist.\n");
}
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;
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, NULL)) {
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 if ((filename = g_file_get_path (files[i])) != NULL) {
g_print ("No such file: %s.\n", filename);
g_free (filename);
} else
g_print ("No valid file is given\n");
}
if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) {
g_signal_connect (win, "close-request", G_CALLBACK (before_close), nb);
gtk_widget_show (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;
}
files[i]
, which is a GFile created with the command line
argument. But the GFile will be destroyed by the system later. So it
needs to be copied before the assignment. g_file_dup
duplicates the GFile.before_close
handler. The fourth argument is called “user
data” and it will be the second argument of the signal handler. So,
nb
is given to before_close
as 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.