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
File viewer

The function main has only two changes from the previous version.

  • G_APPLICATION_DEFAULT_FLAGS is replaced by G_APPLICATION_HANDLES_OPEN
  • g_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_CHAR in 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_name converts 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.

GtkNotebook

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, lab and nbp are 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 with g_filename_display_name before setting the label text. The strings filename_bin and filename_utf8 are 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_page returns 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_set is 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 like g_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.