GtkGridView is similar to GtkListView. It displays a GListModel as a grid, which is like a square tessellation.
This is often seen when you use a file browser like GNOME Files (Nautilus).
In this section, let’s make a very simple file browser
list4
. It just shows the files in the current directory.
And a user can choose list or grid by clicking on buttons in the tool
bar. Each item in the list or grid has an icon and a filename. In
addition, list4
provides the way to open the
tfe
text editor to show a text file. A user can do that by
double clicking on an item or pressing enter key when an item is
selected.
GtkDirectoryList implements GListModel and it contains information of files in a certain directory. The items of the list are GFileInfo objects.
In the list4
source files, GtkDirectoryList is described
in a ui file and built by GtkBuilder. The GtkDirectoryList instance is
assigned to the “model” property of a GtkSingleSelection instance. And
the GtkSingleSelection instance is assigned to the “model” property of a
GListView or GGridView instance.
GtkListView (model property) => GtkSingleSelection (model property) => GtkDirectoryList
GtkGridView (model property) => GtkSingleSelection (model property) => GtkDirectoryList
The following is a 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="directorylist">
<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 is attributes of GFileInfo such as “standard::name”, “standard::icon” and “standard::content-type”.
GtkGridView uses the same GtkSingleSelection instance
(singleselection
). So, its model property is set to it.
The window is built with the following ui file. (See the screenshot at the beginning of this section).
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkApplicationWindow" id="win">
<property name="title">file list</property>
<property name="default-width">600</property>
<property name="default-height">400</property>
<child>
<object class="GtkBox" id="boxv">
<property name="orientation">GTK_ORIENTATION_VERTICAL</property>
<child>
<object class="GtkBox" id="boxh">
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
<child>
<object class="GtkLabel" id="dmy1">
<property name="hexpand">TRUE</property>
</object>
</child>
<child>
<object class="GtkButton" id="btnlist">
<property name="name">btnlist</property>
<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="GtkButton" id="btngrid">
<property name="name">btngrid</property>
<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" id="dmy2">
<property name="width-chars">10</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkScrolledWindow" id="scr">
<property name="hexpand">TRUE</property>
<property name="vexpand">TRUE</property>
</object>
</child>
</object>
</child>
</object>
<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>
</interface>
The file consists of two parts. The first part begins at the line 3 and ends at line 57. This part is the widgets from the top level window to the scrolled window. It also includes two buttons. The second part begins at line 58 and ends at line 71. This is the part of GtkListView and GtkGridView.
btnlist
and btngrid
.
These two buttons work as selection buttons to switch from list to grid
and vice versa. These two buttons are connected to a stateful action
win.view
. This action has a parameter. Such action consists
of prefix, action name and parameter. The prefix of the action is
win
, which means the action belongs to the top level
window. The prefix gives the scope of the action. The action name is
view
. The parameters are list
or
grid
, which show the state of the action. A parameter is
also called a target, because it is a target to which the action changes
its state. We often write the detailed action like “win.view::list” or
“win.view::grid”.The action view
is created, connected to the “activate”
signal handler and inserted to the window (action map) as follows.
= g_simple_action_new_stateful ("view", g_variant_type_new("s"), g_variant_new_string ("list"));
act_view (act_view, "activate", G_CALLBACK (view_activated), NULL);
g_signal_connect (G_ACTION_MAP (win), G_ACTION (act_view)); g_action_map_add_action
The signal handler view_activated
will be explained
later.
Each view (GtkListView and GtkGridView) has its own factory because its items have different structure of widgets. The factories are GtkBuilderListItemFactory objects. Their ui files are as follows.
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 above are almost same. The difference is:
$ 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>
Two properties “gicon” (property of GtkImage) and “label” (property
of GtkLabel) are in the ui files above. Because GFileInfo doesn’t have
properties correspond to icon or filename, the factory uses closure tag
to bind “gicon” and “label” properties to GFileInfo information. A
function get_icon
gets GIcon from the GFileInfo object. And
a function get_file_name
gets a filename from the GFileInfo
object.
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) {
return G_IS_FILE_INFO (info) ? g_strdup (g_file_info_get_name (info)) : NULL;
}
One important thing is the ownership of the return values. The return
value is owned by the caller. So, g_obect_ref
or
g_strdup
is necessary.
An activate signal handler view_activate
switches the
view. It does two things.
static void
view_activated(GSimpleAction *action, GVariant *parameter) {
const char *view = g_variant_get_string (parameter, NULL);
const char *other;
char *css;
if (strcmp (view, "list") == 0) {
other = "grid";
gtk_scrolled_window_set_child (scr, GTK_WIDGET (list));
}else {
other = "list";
gtk_scrolled_window_set_child (scr, GTK_WIDGET (grid));
}
css = g_strdup_printf ("button#btn%s {background: silver;} button#btn%s {background: white;}", view, other);
gtk_css_provider_load_from_data (provider, css, -1);
g_free (css);
g_action_change_state (G_ACTION (action), parameter);
}
The second parameter of this handler is the target of the clicked button. Its type is GVariant.
btnlist
has been clicked, then
parameter
is a GVariant of the string “list”.btngrid
has been clicked, then
parameter
is a GVariant of the string “grid”.The third parameter user_data
points NULL and it is
ignored here.
g_variant_get_string
gets the string from the
GVariant variable.scr
. The function
gtk_scrolled_window_set_child
decreases the reference count
of the old child by one. And it increases the reference count of the new
child by one.Views (GtkListView and GtkGridView) have an “activate” signal. It is emitted when an item in the view is double clicked or the enter key is pressed. You can do anything you like by connecting the “activate” signal to the handler.
The example list4
launches tfe
text file
editor if the item of the list is a text file.
static void
(GtkListView *list, int position, gpointer user_data) {
list_activate *info = G_FILE_INFO (g_list_model_get_item (G_LIST_MODEL (gtk_list_view_get_model (list)), position));
GFileInfo (info);
launch_tfe_with_file }
static void
(GtkGridView *grid, int position, gpointer user_data) {
grid_activate *info = G_FILE_INFO (g_list_model_get_item (G_LIST_MODEL (gtk_grid_view_get_model (grid)), position));
GFileInfo (info);
launch_tfe_with_file }
... ...
... ...
(GTK_LIST_VIEW (list), "activate", G_CALLBACK (list_activate), NULL);
g_signal_connect (GTK_GRID_VIEW (grid), "activate", G_CALLBACK (grid_activate), NULL); g_signal_connect
The second parameter of each handler is the position of the item
(GFileInfo) of the GListModel. So you can get the item with
g_list_model_get_item
function.
The function launch_tfe_with_file
gets a file from the
GFileInfo instance. If the file is a text file, it launches
tfe
with the file.
GFileInfo has information about file type. The file type is like
“text/plain”, “text/x-csrc” and so on. It is called content type.
Content type can be got with g_file_info_get_content_type
function.
static void
launch_tfe_with_file (GFileInfo *info) {
GError *err = NULL;
GFile *file;
GList *files = NULL;
const char *content_type;
const char *text_type = "text/";
GAppInfo *appinfo;
int i;
if (! info)
return;
content_type = g_file_info_get_content_type (info);
#ifdef debug
g_print ("%s\n", content_type);
#endif
if (! content_type)
return;
for (i=0;i<5;++i) { /* compare the first 5 characters */
if (content_type[i] != text_type[i])
return;
}
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);
return;
}
err = NULL;
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);
}
#define debug 1
iat line 6 in
the source file.tfe
application.
GAppInfo is an interface and the variable appinfo
points a
GDesktopAppInfo instance. GAppInfo is a collection of information of
applications.tfe
) with an argument
file
. g_app_info_launch
has four parameters.
The first parameter is GAppInfo object. The second parameter is a list
of GFile objects. In this function, only one GFile instance is given to
tfe
, but you can give more arguments. The third parameter
is GAppLaunchContext, but this program gives NULL instead. The last
parameter is the pointer to the pointer to a GError.g_list_free_full
frees the memories used by the
list and items.If your distribution supports GTK 4, using
g_app_info_launch_default_for_uri
is convenient. The
function automatically determines the default application from the file
and launches it. For example, if the file is text, then it launches
GNOME text editor with the file. Such feature comes from desktop.
The source files are located in src/list4 directory. To compile and execute list4, type as follows.
$ cd list4 # or cd src/list4. It depends your current directory.
$ meson setup _build
$ ninja -C _build
$ _build/list4
Then a file list appears as a list style. Click on a button on the
tool bar so that you can change the style to grid or back to list.
Double click “list4.c” item, then tfe
text editor runs with
the argument “list4.c”. The following is the screenshot.
GtkBuilderListItemFactory has “gbytes” property. The property
contains a byte sequence of ui data. If you use this property, you can
put the contents of factory_list.ui
and
factory_grid.ui
into list4.ui
. The following
shows a part of the new ui file (list5.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>
</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> </
CDATA section begins with “<[CDATA[” and ends with ”]]>”. The contents of CDATA section is recognized as a string. Any character, even if it is a key syntax marker such as ‘<’ or ‘>’, is recognized literally. Therefore, the text between “<[CDATA[” and ”]]>” is inserted to “bytes” property as it is.
This method decreases the number of ui files. But, the new ui file is a bit complicated especially for the beginners. If you feel some difficulty, it is better for you to separate the ui file.
A directory src/list5 includes the ui file above.