We’ve compiled a small editor so far. The program is also small and not complicated yet. But if it grows bigger, it will be difficult to maintain. So, we should do the followings now.
gcc
and
glib-compile-resources
. We should control them by one
building tool.When you divide C source file into several parts, each file should
contain one thing. For example, our source has two things, the
definition of TfeTextView and functions related to GtkApplication and
GtkApplicationWindow. It is a good idea to separate them into two files,
tfetextview.c
and tfe.c
.
tfetextview.c
includes the definition and functions of
TfeTextView.tfe.c
includes functions like main
,
app_activate
, app_open
and so on, which relate
to GtkApplication and GtkApplicationWindowNow we have three source files, tfetextview.c
,
tfe.c
and tfe3.ui
. The 3
of
tfe3.ui
is like a version number. Managing version with
filenames is one possible idea but it also has a problem. You need to
rewrite filename in each version and it affects to contents of source
files that refer to filenames. So, we should take 3
away
from the filename.
In tfe.c
the function tfe_text_view_new
is
invoked to create a TfeTextView instance. But it is defined in
tfetextview.c
, not tfe.c
. The lack of the
declaration (not definition) of tfe_text_view_new
makes
error when tfe.c
is compiled. The declaration is necessary
in tfe.c
. Those public information is usually written in
header files. It has .h
suffix like
tfetextview.h
. And header files are included by C source
files. For example, tfetextview.h
is included by
tfe.c
.
The source files are shown below.
tfetextview.h
#include <gtk/gtk.h>
#define TFE_TYPE_TEXT_VIEW tfe_text_view_get_type ()
G_DECLARE_FINAL_TYPE (TfeTextView, tfe_text_view, TFE, TEXT_VIEW, GtkTextView)
void
tfe_text_view_set_file (TfeTextView *tv, GFile *f);
GFile *
tfe_text_view_get_file (TfeTextView *tv);
GtkWidget *
tfe_text_view_new (void);
tfetextview.c
#include <gtk/gtk.h>
#include "tfetextview.h"
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) {
}
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));
}
tfe.c
#include <gtk/gtk.h>
#include "tfetextview.h"
static void
app_activate (GApplication *app) {
g_print ("You need a filename argument.\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;
GtkBuilder *build;
build = gtk_builder_new_from_resource ("/com/github/ToshioCP/tfe/tfe.ui");
win = GTK_WIDGET (gtk_builder_get_object (build, "win"));
gtk_window_set_application (GTK_WINDOW (win), GTK_APPLICATION (app));
nb = GTK_WIDGET (gtk_builder_get_object (build, "nb"));
g_object_unref (build);
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) {
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.tfe", 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;
}
The ui file tfe.ui
is the same as tfe3.ui
in the previous section.
tfe.gresource.xml
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/com/github/ToshioCP/tfe">
<file>tfe.ui</file>
</gresource>
</gresources>
Dividing a file makes it easy to maintain. But now we face a new problem. The building step increases.
tfe.ui
into
resources.c
.tfe.c
into tfe.o
(object
file).tfetextview.c
into
tfetextview.o
.resources.c
into
resources.o
.tfe
.Build tools manage the steps.
I’ll explain Meson and Ninja build tools.
Other possible tools are Make and Autotools. They are traditional tools but slower than Ninja. So, many developers use Meson and Ninja lately. For example, GTK 4 uses them.
You need to create meson.build
file first.
project('tfe', 'c')
gtkdep = dependency('gtk4')
gnome=import('gnome')
resources = gnome.compile_resources('resources','tfe.gresource.xml')
sourcefiles=files('tfe.c', 'tfetextview.c')
executable('tfe', sourcefiles, resources, dependencies: gtkdep, install: false)
project
defines things about the
project. The first argument is the name of the project and the second is
the programming language.dependency
defines a dependency that is
taken by pkg-config
. We put gtk4
as an
argument.import
imports a module. In line 5, the
gnome module is imported and assigned to the variable
gnome
. The gnome module provides helper tools to build GTK
programs..compile_resources
is of the gnome module
and compiles files to resources under the instruction of xml file. In
line 6, the resource filename is resources
, which means
resources.c
and resources.h
, and xml file is
tfe.gresource.xml
. This method generates C source file by
default.dependencies
, a
delimiter (:
) and a value gtkdep
. This type of
parameter is called keyword parameter or kwargs. The
value gtkdep
is defined in line 3. The last argument tells
that this project doesn’t install the executable file. So it is just
compiled in the build directory.Now run meson and ninja.
$ meson setup _build
$ ninja -C _build
meson has two arguments.
Then, the executable file tfe
is generated under the
directory _build
.
$ _build/tfe tfe.c tfetextview.c
A window appears. It includes a notebook with two pages. One is
tfe.c
and the other is tfetextview.c
.
For further information, see The Meson Build system.