GtkGridView and the Activate Signal
GtkGridView is similar to GtkListView, but presents items from a GListModel in a two-dimensional grid layout.
This layout is common in file managers such as GNOME Files (Nautilus).
In this section, we will build a simple file browser called
list4. It displays the files in the current
directory, allowing users to switch between a list and a grid
layout using the toolbar buttons. Each item in the view shows an
icon and its filename. Furthermore, list4 allows
users to open a text file in the tfe text editor by
either double-clicking an item or pressing Enter while it is
selected. This requires tfe to be pre-installed on
your system. Please refer to Section 26 for its
installation.
GtkDirectoryList
GtkDirectoryList is an implementation of GListModel that holds information about files in a specific directory. Each item in the list is a GFileInfo object.
In the list4 example (located in src/list4
directory), GtkDirectoryList is defined in a UI file
(list4.ui) and built with GtkBuilder. This
GtkDirectoryList is then set to the “model” property of a
GtkSingleSelection, which in turn is assigned to the “model”
property of a GtkListView and GtkGridView.
GtkListView (model property) => GtkSingleSelection (model property) => GtkDirectoryList
GtkGridView (model property) => GtkSingleSelection (model property) => GtkDirectoryList
The following is part of the UI file
list4.ui.
<object class="GtkListView" id="list">
<property name="model">
<object class="GtkSingleSelection" id="singleselection">
<property name="model">
<object class="GtkDirectoryList" id="directory_list">
<property name="attributes">standard::name,standard::icon,standard::content-type</property>
</object>
</property>
</object>
</property>
</object>
・・・ ・・・ ・・・
・・・ ・・・ ・・・
<object class="GtkGridView" id="grid">
<property name="model">singleselection</property>
</object>GtkDirectoryList has an “attributes” property. It specifies which GFileInfo attributes to retrieve, such as “standard::name”, “standard::icon”, and “standard::content-type”.
standard::name: The file name.standard::icon: An icon for the file (a GIcon object).standard::content-type: The MIME type of the file.
A content type (MIME type) indicates the format of a file and is used by the desktop system to handle files properly. For example, “text/plain” represents a plain text file, and “text/x-csrc” represents C source code. (Subtypes starting with “x-” like this are non-standard types not registered with IANA, but they are widely used in practice.)
UI File for the Window
The application window is defined in the following UI file
(list4.ui). (Take a look at the screenshot at the
beginning of this section to see how it looks.)
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkApplicationWindow" id="win">
<property name="title">file list</property>
<property name="default-width">800</property>
<property name="default-height">600</property>
<child>
<object class="GtkBox">
<property name="orientation">GTK_ORIENTATION_VERTICAL</property>
<child>
<object class="GtkBox">
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
<child>
<object class="GtkLabel">
<property name="hexpand">TRUE</property>
</object>
</child>
<child>
<object class="GtkToggleButton">
<property name="action-name">win.view</property>
<property name="action-target">'list'</property>
<child>
<object class="GtkImage">
<property name="resource">/com/github/ToshioCP/list4/list.png</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkToggleButton">
<property name="action-name">win.view</property>
<property name="action-target">'grid'</property>
<child>
<object class="GtkImage">
<property name="resource">/com/github/ToshioCP/list4/grid.png</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="width-chars">10</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkStack" id="stack">
<property name="hexpand">TRUE</property>
<property name="vexpand">TRUE</property>
<child>
<object class="GtkStackPage">
<property name="name">list</property>
<property name="child">
<object class="GtkScrolledWindow">
<property name="child">
<object class="GtkListView" id="list">
<property name="model">
<object class="GtkSingleSelection" id="singleselection">
<property name="model">
<object class="GtkDirectoryList" id="directory_list">
<property name="attributes">standard::name,standard::icon,standard::content-type</property>
</object>
</property>
</object>
</property>
</object>
</property>
</object>
</property>
</object>
</child>
<child>
<object class="GtkStackPage">
<property name="name">grid</property>
<property name="child">
<object class="GtkScrolledWindow">
<property name="child">
<object class="GtkGridView" id="grid">
<property name="model">singleselection</property>
</object>
</property>
</object>
</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</interface>Inside the top-level GtkApplicationWindow (lines 3–91), a vertical GtkBox divides the layout into two main parts. The first part (lines 10–46) is a horizontal GtkBox that holds two toggle buttons for switching views. The second part (lines 47–88) is a GtkStack that holds GtkListView and GtkGridView.
- 13-17, 40-44: Two empty GtkLabel widgets used as spacers to position the two buttons nicely toward the right.
- 18-39: Two GtkToggleButton widgets to switch between the
list view and the grid view. Both buttons are connected to a
stateful action named
win.view, which takes a parameter. An action like this consists of a prefix, an action name, and a parameter (also called a target):- The prefix
windefines the action’s scope, indicating that it belongs to the top-level window (GtkApplicationWindow). - The action name is
view. - The target is either
'list'or'grid'(written as'list'and'grid'in the XML), which represents the new state the action will switch to. We often write a detailed action with its target like"win.view::list"or"win.view::grid".
- The prefix
- 20-21, 31-32: The
"action-name"and"action-target"properties belong to the GtkActionable interface, which GtkToggleButton implements. For the first button, the action name is"win.view"and the target is'list'. In general, an action target is a GVariant value (such as a string, integer, or float), so you need to write it in GVariant text format in UI files. For a string value, theGVarianttext format requires single or double quotes around the text. Because UI files are XML, single quotes must be escaped as'. Therefore, the target'list'is written as'list'. Because the toggle buttons are connected directly to the action, you don’t need to set up “clicked” signal handlers — and their active (pressed) states are updated automatically to match the action’s state. - 22-26, 33-37: The child widget of each button is a GtkImage. Its “resource” property specifies a GResource path. GtkImage reads the image data from this resource to display a custom 24x24 PNG icon.
- 47-88: A GtkStack (
id="stack") containing two pages (GtkStackPage). The first page (named “list”) contains a GtkScrolledWindow with the GtkListView (id="list"), and the second page (named “grid”) contains a GtkScrolledWindow with the GtkGridView (id="grid").
GtkStack
GtkStack is a container widget that holds multiple child widgets (called pages) and displays only one of them at a time.
It behaves much like GtkNotebook, except it does
not have built-in tabs. Instead, you control which page is
visible using external widgets, such as the toggle buttons in
our toolbar. This makes GtkStack ideal for switching between
different views in the same space — like switching between
GtkListView and GtkGridView in list4.
Inside a GtkStack, each child widget is wrapped in a GtkStackPage object:
GtkStack => GtkStackPage => Child Widget
As seen in lines 47–88 of list4.ui above, you
add GtkStackPage objects to GtkStack and set two key properties
on each page:
name: A string identifier for the page (such as “list” or “grid”).child: The widget to display on that page.
To switch to another page in C, you simply call
gtk_stack_set_visible_child_name () with the page’s
name.
- In
list4.c, the visible page is explicitly set to “list” in theapp_activatefunction (gtk_stack_set_visible_child_name (stack, "list");). This ensures that the window shows the GtkListView instance initially. - Afterwards, clicking a toggle button activates the “view”
action and triggers its signal handler,
view_activated(which receives the GtkStack instance asuser_data). This handler gets the target name (“list” or “grid”) from the parameter, switches the visible page in the GtkStack, and updates the action’s state so the toggle buttons reflect the current view.
static void
view_activated (GSimpleAction *action, GVariant *parameter, gpointer user_data) {
GtkStack *stack = GTK_STACK (user_data);
const char *view = g_variant_get_string (parameter, NULL);
gtk_stack_set_visible_child_name (stack, view);
g_action_change_state (G_ACTION (action), parameter);
}Factories
Each view (GtkListView and GtkGridView) requires its own factory because they arrange their item widgets differently. Both factories are GtkBuilderListItemFactory objects, defined by the following UI files:
factory_list.ui
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GtkListItem">
<property name="child">
<object class="GtkBox">
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
<property name="spacing">20</property>
<child>
<object class="GtkImage">
<binding name="gicon">
<closure type="GIcon" function="get_icon">
<lookup name="item">GtkListItem</lookup>
</closure>
</binding>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="hexpand">TRUE</property>
<property name="xalign">0</property>
<binding name="label">
<closure type="gchararray" function="get_file_name">
<lookup name="item">GtkListItem</lookup>
</closure>
</binding>
</object>
</child>
</object>
</property>
</template>
</interface>factory_grid.ui
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GtkListItem">
<property name="child">
<object class="GtkBox">
<property name="orientation">GTK_ORIENTATION_VERTICAL</property>
<property name="spacing">20</property>
<child>
<object class="GtkImage">
<property name="icon-size">GTK_ICON_SIZE_LARGE</property>
<binding name="gicon">
<closure type="GIcon" function="get_icon">
<lookup name="item">GtkListItem</lookup>
</closure>
</binding>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="hexpand">TRUE</property>
<property name="xalign">0.5</property>
<binding name="label">
<closure type="gchararray" function="get_file_name">
<lookup name="item">GtkListItem</lookup>
</closure>
</binding>
</object>
</child>
</object>
</property>
</template>
</interface>The two files are almost the same. The main differences are:
- The orientation of the GtkBox (horizontal for the list, vertical for the grid)
- The icon size (the grid explicitly sets icon-size to GTK_ICON_SIZE_LARGE)
- The text alignment of the GtkLabel (
xalignis0for left-aligned in the list, and0.5for centered in the grid)
$ cd list4; diff factory_list.ui factory_grid.ui
6c6
< <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
---
> <property name="orientation">GTK_ORIENTATION_VERTICAL</property>
9a10
> <property name="icon-size">GTK_ICON_SIZE_LARGE</property>
20c21
< <property name="xalign">0</property>
---
> <property name="xalign">0.5</property>
32d32
<
$
Notice the <binding> tags for the “gicon”
(on GtkImage) and “label” (on GtkLabel) properties. Because
GFileInfo does not have direct properties for its icon and
filename, the factory uses <closure> tags to
extract this data using custom C functions. The
get_icon function retrieves a GIcon from the
GFileInfo object, and the get_file_name function
retrieves the filename string.
GIcon *
get_icon (GtkListItem *item, GFileInfo *info) {
GIcon *icon;
/* g_file_info_get_icon can return NULL */
icon = G_IS_FILE_INFO (info) ? g_file_info_get_icon (info) : NULL;
return icon ? g_object_ref (icon) : NULL;
}
char *
get_file_name (GtkListItem *item, GFileInfo *info) {
const char *name;
name = G_IS_FILE_INFO (info) ? g_file_info_get_name (info) : NULL;
return name ? g_filename_display_name (name) : g_strdup ("");
}A note on memory ownership:
When using a closure binding, the caller (the factory)
expects to own the return values. This means your C functions
must return new references or copies. You will see in the C code
that we use g_object_ref () to keep the icon and
g_strdup () to copy the string, ensuring the memory
ownership is transferred correctly.
Content Type and Application Launch
When an item in the GtkListView or GtkGridView is
double-clicked (or activated with the Enter key), the “activate”
signal is emitted. In list4.c, both views are
connected to the same signal handler,
item_activated, with directory_list
passed as user_data.
item_activated retrieves the GFileInfo instance
for the activated item and checks its content type (such as
"text/plain" or "text/x-csrc"). If the
item is a text file, it launches tfe (our text file
editor) to open the file.
static void
item_activated (GtkWidget *view, guint position, gpointer user_data) {
GListModel *model = G_LIST_MODEL (user_data);
GFileInfo *info = G_FILE_INFO (g_list_model_get_item (model, position));
const char *content_type;
GAppInfo *appinfo;
GFile *file;
GList *files = NULL;
GError *err = NULL;
if (! info)
return;
content_type = g_file_info_get_content_type (info);
#ifdef debug
g_print ("%s\n", content_type);
#endif
if (content_type && g_str_has_prefix (content_type, "text/")) {
appinfo = g_app_info_create_from_commandline ("tfe", "tfe", G_APP_INFO_CREATE_NONE, &err);
if (err) {
g_printerr ("%s\n", err->message);
g_error_free (err);
} else {
file = g_file_new_for_path (g_file_info_get_name (info));
files = g_list_append (files, file);
if (! (g_app_info_launch (appinfo, files, NULL, &err))) {
g_printerr ("%s\n", err->message);
g_error_free (err);
}
g_list_free_full (files, g_object_unref);
g_object_unref (appinfo);
}
}
g_object_unref (info);
}- 3–4: Retrieves the GListModel (
directory_list) fromuser_dataand gets the GFileInfo object at the activatedposition. Becauseg_list_model_get_itemreturns a new reference to the item, we must free it withg_object_unref (info)at the end of the function (line 33). - 13: Gets the content type of the file from the GFileInfo
using
g_file_info_get_content_type. - 14–16: Prints the content type to standard output if
debugis defined. This is handy for checking the content types of various files. If you don’t need this output, remove or comment out#define debug 1at line 5 oflist4.c. - 17: Checks whether
content_typeis non-NULL and begins with"text/"usingg_str_has_prefix. Only text files proceed to launchtfe. - 18: Creates a GAppInfo object for the
tfeapplication. GAppInfo is an interface that holds information about an application, andappinfopoints to a GDesktopAppInfo instance. - 23–24: Creates a GFile object from the file’s name and
appends it to a GList (
files). Note thatg_file_info_get_name ()returns only the file’s basename (e.g. “example.txt”), not its full path. Inlist4, creating a GFile from this basename works because the GtkDirectoryList targets the current working directory (“.”). - 25: Launches
tfewith the file usingg_app_info_launch. This function takes four parameters:- The GAppInfo object representing the application to launch.
- A GList of GFile objects to pass as arguments. Here we pass only one file, but you can pass multiple files in the list.
- A GAppLaunchContext (or
NULLif not needed, as in this program). - A pointer to a
GError *for error reporting.
- 29–30:
g_list_free_full (files, g_object_unref)frees both the GList and the GFile objects inside it. Theng_object_unref (appinfo)frees the GAppInfo object. - 33: Frees the GFileInfo reference obtained in line 4.
If your distribution supports GTK 4, using
g_app_info_launch_default_for_uri is also very
convenient. This function asks the desktop environment to
automatically determine the default application for a given file
and launch it — for example, opening a text file in the system’s
default text editor (such as GNOME Text Editor).
Compilation and Execution
The source files are located in the src/list4
directory. To compile and run list4, run the
following commands:
$ cd list4 # or cd src/list4. It depends your current directory.
$ meson setup _build
$ ninja -C _build
$ _build/list4
When the application opens, the files are displayed in a list
view. Click the buttons in the toolbar to switch between the
list view and the grid view. If you double-click “list4.c” in
the list (or the grid), the tfe text editor will
launch and open the file.
Here is a screenshot of the running application:
The “bytes” Property of GtkBuilderListItemFactory
GtkBuilderListItemFactory has a “bytes” property (of type
GBytes) that holds a byte sequence of UI data. By using this
property, you can embed the contents of
factory_list.ui and factory_grid.ui
directly into the main UI file instead of keeping them as
separate files.
Here is an excerpt from the combined UI file
(list5.ui), located in the src/list5
directory:
<object class="GtkListView" id="list">
<property name="model">
<object class="GtkSingleSelection" id="singleselection">
<property name="model">
<object class="GtkDirectoryList" id="directory_list">
<property name="attributes">standard::name,standard::icon,standard::content-type</property>
</object>
</property>
</object>
</property>
<property name="factory">
<object class="GtkBuilderListItemFactory">
<property name="bytes"><![CDATA[
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GtkListItem">
<property name="child">
<object class="GtkBox">
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
<property name="spacing">20</property>
<child>
<object class="GtkImage">
<binding name="gicon">
<closure type="GIcon" function="get_icon">
<lookup name="item">GtkListItem</lookup>
</closure>
</binding>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="hexpand">TRUE</property>
<property name="xalign">0</property>
<binding name="label">
<closure type="gchararray" function="get_file_name">
<lookup name="item">GtkListItem</lookup>
</closure>
</binding>
</object>
</child>
</object>
</property>
</template>
</interface>
]]></property>
</object>
</property>
</object>A CDATA section begins with <![CDATA[ and
ends with ]]>. Everything inside a CDATA section
is treated as raw text. Special XML syntax characters like
< and > are recognized literally
rather than as markup tags, so the XML text between
<![CDATA[ and ]]> is passed
directly to the “bytes” property as-is.
Because the factories are now defined inside
list5.ui and assigned directly to the “factory”
properties of the GtkListView and GtkGridView,
list5.c no longer needs to create and set the
factories in app_activate.
While this approach reduces the number of UI files, nesting
XML inside XML makes the file harder to read, especially for
beginners. If you find it confusing, it is perfectly fine to
keep the factory UI files separate as we did in
list4.