Widgets (3)
Open Signal
G_APPLICATION_HANDLES_OPEN flag
We made a very simple editor in the previous section with GtkTextView, GtkTextBuffer and GtkScrolledWindow. We will add file-read ability to the program and improve it to a file viewer.
The simplest way to specify a filename is to use a command line argument.
$ ./a.out filename
The program will open the file and insert its contents into a 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 (GApplicationFlags) is given as an argument.
GtkApplication *
gtk_application_new (const gchar *application_id, GApplicationFlags flags);This tutorial explains only two flags,
G_APPLICATION_DEFAULT_FLAGS and
G_APPLICATION_HANDLES_OPEN.
G_APPLICATION_FLAGS_NONE was used instead of
G_APPLICATION_DEFAULT_FLAGS before GIO
version2.73.3 (GLib 2.73.3 5/Aug/2022). Now it is deprecated and
G_APPLICATION_DEFAULT_FLAGS is recommended.
For further information, see GIO API Reference – ApplicationFlags and GIO API Reference – g_application_run.
We’ve already used G_APPLICATION_DEFAULT_FLAGS,
as it is the simplest option, and no command line arguments are
allowed. If you give arguments in the command line, an error
will occur.
The flag G_APPLICATION_HANDLES_OPEN is the
second simplest option. It allows command line arguments,
specifically filenames.
app = gtk_application_new ("com.github.ToshioCP.tfv3", G_APPLICATION_HANDLES_OPEN);Open Signal
When G_APPLICATION_HANDLES_OPEN flag is given to
the application, two signals are available.
- activate signal: This signal is emitted when there’s no argument.
- open signal: This signal is emitted when there is at least one argument.
The handler of the “open” signal is defined as follows.
void
open (
GApplication* self,
gpointer files,
gint n_files,
gchar* hint,
gpointer user_data
)The parameters are:
- self: the application instance (usually GtkApplication)
- files: an array of GFiles. [array length=n_files] [element-type GFile]
- n_files: the number of the elements of
files - hint: an optional context string provided by the caller (often unused)
- user_data: user data that is set when the signal handler was connected.
File Viewer
What is a File Viewer?
A file viewer is a program that displays text files. Our file viewer is as follows.
- When arguments are given, it recognizes the first argument as a filename and opens it.
- The second argument and later are ignored.
- If there is no argument, it shows an error message and quits.
- If it successfully opens the file, it reads the content of the file, inserts it to GtkTextBuffer and shows the window.
- If it fails to open the file, it shows an error message and quit.
The program is shown below.
#include <gtk/gtk.h>
static void
app_activate (GApplication *app) {
g_printerr ("You need a filename argument.\n");
}
static void
app_open (GApplication *app, GFile ** files, int n_files, char *hint) {
GtkWidget *win;
GtkWidget *scr;
GtkWidget *tv;
GtkTextBuffer *tb;
char *contents;
gsize length;
char *filename_bin, *filename_utf8;
GError *err = NULL;
win = gtk_application_window_new (GTK_APPLICATION (app));
gtk_window_set_default_size (GTK_WINDOW (win), 800, 600);
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, &err)) {
gtk_text_buffer_set_text (tb, contents, length);
g_free (contents);
if ((filename_bin = g_file_get_basename (files[0])) != NULL) {
filename_utf8 = g_filename_display_name (filename_bin);
gtk_window_set_title (GTK_WINDOW (win), filename_utf8);
g_free (filename_bin);
g_free (filename_utf8);
}
gtk_window_present (GTK_WINDOW (win));
} else {
g_printerr ("%s.\n", err->message);
g_error_free (err);
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;
}The source code is located at /src/tfv/tfv3.c in the repository. Compile and run it.
$ comp tfv3
$ ./a.out tfv3.c
The function main has only two changes from the
previous version.
G_APPLICATION_DEFAULT_FLAGSis replaced byG_APPLICATION_HANDLES_OPENg_signal_connect (app, "open", G_CALLBACK (app_open), NULL)is added.
When the flag G_APPLICATION_HANDLES_OPEN is
given to gtk_application_new function, the
application behaves like this:
- If the application is run without command line arguments, it emits “activate” signal when it is activated.
- If the application is run with command line arguments, it emits “open” signal when it is activated.
The handler app_activate becomes very simple. It
just outputs a message and returns to the caller. Then the
application quits immediately because no window is created.
The main work is done in the handler
app_open.
- Creates GtkApplicationWindow, GtkScrolledWindow, GtkTextView and GtkTextBuffer and connects them together
- Sets wrap mode to
GTK_WRAP_WORD_CHARin GtktextView - Sets GtkTextView to non-editable because the program isn’t an editor but only a viewer
- Reads the file and inserts the text into GtkTextBuffer (this
will be explained later). The window title is set with the
filename. In general, filenames are sequences of bytes. To
display them on a screen, they must be converted into encoded
strings. The function
g_filename_display_nameconverts a filename into a valid UTF-8 string. - If the file is not opened, outputs an error message and destroys the window. This causes the application to exit.
The following is the file reading part of the program.
if (g_file_load_contents (files[0], NULL, &contents, &length, NULL, &err)) {
gtk_text_buffer_set_text (tb, contents, length);
g_free (contents);
if ((filename_bin = g_file_get_basename (files[0])) != NULL) {
filename_utf8 = g_filename_display_name (filename_bin);
gtk_window_set_title (GTK_WINDOW (win), filename_utf8);
g_free (filename_bin);
g_free (filename_utf8);
}
gtk_window_present (GTK_WINDOW (win));
} else {
g_printerr ("%s.\n", err->message);
g_error_free (err);
gtk_window_destroy (GTK_WINDOW (win));
}The function g_file_load_contents loads the file
contents into a temporary buffer, allocated automatically, and
sets contents to point to the buffer. The length of
the buffer is assigned to length. It returns
TRUE if the file’s contents are successfully
loaded. The caller takes the ownership of the buffer and is
responsible for freeing it. If an error occurs, it returns
FALSE and sets the variable err to
point to a newly created GError structure. The caller takes the
ownership of the GError structure and is responsible for freeing
it. If you want to know the details about g_file_load_contents,
see g
file load contents.
If the file is read successfully, the contents are inserted
into GtkTextBuffer. The temporary buffer contents
is then freed. The function g_file_get_basename
returns the basename of the pathname. Generally, a pathname of a
filename is a sequence of byte data. To display the filename, it
must be converted to a valid encoded string. The function
g_filename_display_name converts a filename into a
valid UTF-8 string. After the conversion, the window title is
set with the filename, the memories for filenames are freed, and
the window is shown.
If it fails, g_file_load_contents sets
err to point to a newly created GError structure.
The structure is:
struct GError {
GQuark domain;
int code;
char* message;
}The message member is the most commonly used. It
points to an error message. A function g_error_free
is used to free the memory of the structure. See GError.
The program above outputs an error message, frees
err and destroys the window and finally make the
program quit.
GtkNotebook
GtkNotebook is a container widget that contains multiple widgets with tabs. It shows only one child at a time. Another child will be shown when its tab is clicked.
The left image is the window at the startup. The file
pr1.c is shown and its filename is in the left tab.
After clicking on the right tab, the contents of the file
tfv1.c is shown (the right image).
The next source code is tfv4.c, including a
GtkNoteBook widget. The widget is inserted as a child of a
GtkApplicationWindow and contains multiple GtkScrolledWindow
widgets.
#include <gtk/gtk.h>
static void
app_activate (GApplication *app) {
g_printerr ("You need filename 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_bin, *filename_utf8;
int i;
GError *err = NULL;
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), 800, 600);
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 = 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);
filename_bin = g_file_get_basename (files[i]);
filename_utf8 = g_filename_display_name (filename_bin);
lab = gtk_label_new (filename_utf8);
g_free (filename_bin);
g_free (filename_utf8);
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 {
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.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;
}The changes are primarily located in the
app_open function. The numbers on the left below
correspond to the line numbers in the source code.
- 11-13: Variables
nb,labandnbpare defined. They point to a GtkNotebook, GtkLabel and GtkNotebookPage respectively. - 24: The window’s title is set to “file viewer”.
- 25: The default size of the window is 800x600.
- 26-27: GtkNotebook is created and set as a child of the GtkApplicationWindow.
- 29-52: For-loop. The variable
files[i]points to i-th GFile, which is created by the GtkApplication from the i-th command line argument. - 31-36: A GtkScrollledWindow and a GtkTextView are created. A GtkTextBuffer is obtained from the GtkTextView. The GtkTextView is connected to the GtkScrolledWindow as a child.
- 38-39: inserts the contents of the file into the
GtkTextBuffer and frees the memory referenced by
contents. - 40-44: Retrieves the filename from the GFile using
g_file_get_basename. The filename is a byte-sequence without a specific encoding. It needs to be converted into a UTF-8 string withg_filename_display_namebefore setting the label text. The stringsfilename_binandfilename_utf8are freed. - 45-46: Appends a GtkScrolledWindow to the GtkNotebook as a
child and the GtkLabel is set as the child’s tab. At the same
time, a GtkNoteBookPage is created automatically. The function
gtk_notebook_get_pagereturns the GtkNotebookPage of the child (GtkScrolledWindow). - 47: GtkNotebookPage has “tab-expand” property. If it is set
to TRUE then the tab expands horizontally as long as possible.
If it is FALSE, then the width of the tab is determined by the
size of the label.
g_object_setis a general function to set properties of objects. See GObject API Reference – g_object_set. - 48-50: If it fails to read the file, the error message is
shown. The function
g_clear_error (&err)works likeg_error_free (err); err = NULL;. - 53-56: If at least one page exists, the window is shown. Otherwise, the window is destroyed and the application quits.