The GtkTextView, GtkTextBuffer and GtkScrolledWindow widgets have given us a minimum editor in the previous section. We will now add a function to read a file and rework the program into a file viewer. There are many ways to implement the function and because this is a tutorial for beginners, we’ll take the easiest one.
When the program starts, we will give the filename to open as an argument.
$ ./a.out filename
It will open the file and insert its contents into the GtkTextBuffer.
To do this, we need to know how GtkApplication (or GApplication) recognizes arguments. This is described in the GIO API Reference, Application.
When GtkApplication is created, a flag (with the type GApplicationFlags) is provided as an argument.
GtkApplication *const gchar *application_id, GApplicationFlags flags); gtk_application_new (
This tutorial explains only two flags, G_APPLICATION_FLAGS_NONE
and G_APPLICATION_HANDLES_OPEN
. If you want to handle command line arguments, the G_APPLICATION_HANDLES_COMMAND_LINE
flag is what you need. How to use the new application method is described in GIO API Reference, g_application_run, and the flag is described in the GIO API Reference, ApplicationFlags.
GApplicationFlags' Members
G_APPLICATION_FLAGS_NONE Default. (No argument allowed)
... ... ...
G_APPLICATION_HANDLES_OPEN This application handles opening files (in the primary instance).
... ... ...
There are ten flags in total, but we only need two of them so far. We’ve already used G_APPLICATION_FLAGS_NONE
, as it is the simplest option, and no arguments are allowed. If you provide arguments when running the application, an error will occur.
The flag G_APPLICATION_HANDLES_OPEN
is the second simplest option. It allows arguments but only files. The application assumes all the arguments are filenames and we will use this flag when creating our GtkApplication.
"com.github.ToshioCP.tfv3", G_APPLICATION_HANDLES_OPEN); app = gtk_application_new (
Now, when the application starts, two signals can be emitted.
The handler of the “open” signal is defined as follows.
void user_function (GApplication *application,
gpointer files,
gint n_files,
gchar *hint, gpointer user_data)
The parameters are:
application
— the application (usually GtkApplication)files
— an array of GFiles. [array length=n_files] [element-type GFile]n_files
— the number of the elements of files
hint
— a hint provided by the calling instance (usually it can be ignored)user_data
— user data set when the signal handler was connected.How to read a specified file (GFile) will be described next.
A file viewer is a program that displays the text file that is given as an argument. Our file viewer will work as follows.
The program which does this is shown below.
#include <gtk/gtk.h>
static void
app_activate (GApplication *app, gpointer user_data) {
g_print ("You need a filename argument.\n");
}
static void
app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) {
GtkWidget *win;
GtkWidget *scr;
GtkWidget *tv;
GtkTextBuffer *tb;
char *contents;
gsize length;
char *filename;
win = gtk_application_window_new (GTK_APPLICATION (app));
gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
scr = gtk_scrolled_window_new ();
gtk_window_set_child (GTK_WINDOW (win), scr);
tv = gtk_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_text_view_set_editable (GTK_TEXT_VIEW (tv), FALSE);
gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
if (g_file_load_contents (files[0], NULL, &contents, &length, NULL, NULL)) {
gtk_text_buffer_set_text (tb, contents, length);
g_free (contents);
if ((filename = g_file_get_basename (files[0])) != NULL) {
gtk_window_set_title (GTK_WINDOW (win), filename);
g_free (filename);
}
gtk_widget_show (win);
} else {
if ((filename = g_file_get_path (files[0])) != NULL) {
g_print ("No such file: %s.\n", filename);
g_free (filename);
}
gtk_window_destroy (GTK_WINDOW (win));
}
}
int
main (int argc, char **argv) {
GtkApplication *app;
int stat;
app = gtk_application_new ("com.github.ToshioCP.tfv3", 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;
}
Save it as tfv3.c
. Then compile and run it.
$ comp tfv3
$ ./a.out tfv3.c
Let’s explain how the program tfv3.c
works. First, the function main
has only two changes from the previous version.
G_APPLICATION_FLAGS_NONE
is replaced by G_APPLICATION_HANDLES_OPEN
; andg_signal_connect (app, "open", G_CALLBACK (on_open), NULL)
is added.Next, the handler app_activate
is added and is very simple. It just outputs the error message and the application quits immediately because no window is created.
The main functionality is the in the handler app_open
. It
GTK_WRAP_WORD_CHAR
in GtktextView;The following is the important file reading part of the program and is shown again below.
if (g_file_load_contents (files[0], NULL, &contents, &length, NULL, NULL)) {
gtk_text_buffer_set_text (tb, contents, length);
g_free (contents);if ((filename = g_file_get_basename (files[0])) != NULL) {
gtk_window_set_title (GTK_WINDOW (win), filename);
g_free (filename);
}
gtk_widget_show (win);else {
} if ((filename = g_file_get_path (files[0])) != NULL) {
"No such file: %s.\n", filename);
g_print (
g_free (filename);
}
gtk_window_destroy (GTK_WINDOW (win)); }
The function g_file_load_contents
loads the file contents into a buffer, which is automatically allocated and sets contents
to point that buffer. The length of the buffer is set to length
. It returns TRUE
if the file’s contents are successfully loaded and FALSE
if an error occurs.
If this function succeeds, it inserts the contents into GtkTextBuffer, frees the buffer pointed by contents
, sets the title of the window, frees the memories pointed by filename
and then shows the window. If it fails, it outputs an error message and destroys the window, causing the program to quit.
GtkNotebook is a container widget that uses tabs and contains multiple children. The child that is displayed depends on which tab has been selected.
Looking at the screenshots above, the left one is the window at the startup. It shows the file pr1.c
and the filename is in the left tab. After clicking on the right tab, the contents of the file tfv1.c
are shown instead. This is shown in the right screenshot.
The GtkNotebook widget is inserted as a child of GtkApplicationWindow and contains a GtkScrolledWindow for each file that is being displayed. The code to do this is given in tfv4.c
and is:
#include <gtk/gtk.h>
static void
app_activate (GApplication *app, gpointer user_data) {
g_print ("You need a filename argument.\n");
}
static void
app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) {
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 viewer");
gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
gtk_window_maximize (GTK_WINDOW (win));
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 = gtk_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_text_view_set_editable (GTK_TEXT_VIEW (tv), FALSE);
gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
gtk_text_buffer_set_text (tb, contents, length);
g_free (contents);
if ((filename = g_file_get_basename (files[i])) != NULL) {
lab = gtk_label_new (filename);
g_free (filename);
} else
lab = gtk_label_new ("");
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);
} 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)
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.tfv4", 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;
}
Most of the changes are in the function app_open
. The numbers at the left of the following items are line numbers in the source code.
nb
, lab
and nbp
are defined and will point to a new GtkNotebook, GtkLabel and GtkNotebookPage widget respectively.files[i]
is GFile object containing the i-th argument.contents
.filename
.filename
is NULL, creates GtkLabel with the empty string. GtkNotebook -- GtkNotebookPage -- GtkScrolledWindow
nbp
to point to this GtkNotebookPage.g_object_set
is a general function to set properties in objects. See GObject API Reference, g_object_set.filename
buffer is freed.filename
is NULL, the “No valid file is given” message is outputted.