GtkListView
GTK 4 has added new list objects GtkListView, GtkGridView and GtkColumnView. The new feature is described in Gtk API Reference – List Widget Overview.
GTK 4 has other means to implement lists. They are GtkListBox and GtkTreeView which are took over from GTK 3. There’s an article in Gtk Development blog about list widgets by Matthias Clasen. He described why GtkListView are developed to replace GtkTreeView. GtkTreeView is deprecated since version 4.10.
GtkListView, GtkGridView, GtkColumnView and related objects are described in Section 29 to 33.
Outline
A list is a sequential data structure. For example, an ordered string sequence “one”, “two”, “three”, and “four” is a list. Each element is called an item. A list is like an array, but in many cases it is implemented with pointers which point to the next items of the list. And it has a start point. So, each item can be referred to by the index of the item (first item, second item, …, nth item, …).
Gio provides GListModel interface. It is a zero-based list and its items are the same type of GObject descendants, or objects that implement the same interface. An object that implements GListModel is not a widget. So, the list is not displayed on the screen directly. There’s another object GtkListView which is a widget to display the list. The items in the list need to be connected to the items in GtkListView. GtkListItemFactory instance maps items in the list to GtkListView.
GListModel and GtkStringList
If you want to make a list of strings with GListModel, for example, “one”, “two”, “three”, “four”, note that strings can’t be items of the list. Because GListModel is a list of GObject objects and strings aren’t GObject objects. The word “GObject” here means GObject class or its derived class. So, you need a wrapper which is a GObject and contains a string. GtkStringObject is the wrapper object and GtkStringList, which implements GListModel, is a list of GtkStringObject.
char *array[] = {"one", "two", "three", "four", NULL};
GtkStringList *stringlist = gtk_string_list_new ((const char * const *) array);The function gtk_string_list_new creates a
GtkStringList object. Its items are GtkStringObject objects
which contain the strings “one”, “two”, “three” and “four”.
There are functions to add items to the list or remove items
from the list.
gtk_string_list_appendappends an item to the listgtk_string_list_removeremoves an item from the listgtk_string_list_get_stringgets a string in the list
See GTK 4 API Reference – GtkStringList for further information.
Other list objects will be explained later.
GtkSelectionModel
GtkSelectionModel is an interface to support for selection. Thanks to this model, a user can select items by clicking on them. It is implemented by GtkMultiSelection, GtkNoSelection and GtkSingleSelection objects. These three objects are usually enough to build an application. They are created with another GListModel. You can also create them alone and add a GListModel later.
- GtkMultiSelection supports multiple selection.
- GtkNoSelection supports no selection. This is a wrapper to GListModel when GtkSelectionModel is needed.
- GtkSingleSelection supports single selection.
GtkListView
GtkListView is a widget to show GListModel items. GtkListItem is used by GtkListView to represent items of a list model. But, GtkListItem is not a widget (not a descendant of GtkWidget), so a user needs to set a widget, for example GtkLabel, as a child of GtkListItem to display an item of the list model. “item” property of GtkListItem points to an object that belongs to the list model.
In case the number of items is very big, for example more than a thousand, GtkListItem is recycled and connected to another item which is newly displayed. This recycle makes the number of GtkListItem objects fairly small, less than 200. This is very effective to restrain the growth of memory consumption so that GListModel can contain lots of items, for example, more than a million items.
GtkListItemFactory
GtkListItemFactory creates or recycles GtkListItem and connects it with an item of the list model. There are two child classes of this factory, GtkSignalListItemFactory and GtkBuilderListItemFactory.
GtkSignalListItemFactory
GtkSignalListItemFactory provides signals for users to configure a GtkListItem object. There are four signals.
- “setup” is emitted to set up GtkListItem object. A user sets its child widget in the handler. For example, creates a GtkLabel widget and sets the child property of the GtkListItem to it. This setting is kept even when the GtkListItem instance is recycled (to bind to another item of GListModel).
- “bind” is emitted to bind an item in the list model to the widget. For example, a user gets the item from “item” property of the GtkListItem instance. Then gets the string of the item and sets the label property of the GtkLabel instance with the string. This signal is emitted when the GtkListItem is newly created, recycled or some changes have happened to the item of the list.
- “unbind” is emitted to unbind an item. A user undoes everything done in step 2 in the signal handler. If some object are created in step 2, they must be destroyed.
- “teardown” is emitted to undo everything done in step 1. So, the widget created in step 1 must be destroyed. After this signal, the list item will be destroyed.
The following program list1.c shows a list of
strings “one”, “two”, “three” and “four”. GtkNoSelection is
used, so user can’t select any item.
#include <gtk/gtk.h>
static void
setup_cb (GtkSignalListItemFactory *self, GtkListItem *listitem, gpointer user_data) {
GtkWidget *lb = gtk_label_new (NULL);
gtk_list_item_set_child (listitem, lb);
/* Because gtk_list_item_set_child sunk the floating reference of lb, releasing (unref) isn't necessary for lb. */
}
static void
bind_cb (GtkSignalListItemFactory *self, GtkListItem *listitem, gpointer user_data) {
GtkWidget *lb = gtk_list_item_get_child (listitem);
/* Strobj is owned by the instance. Caller mustn't change or destroy it. */
GtkStringObject *strobj = gtk_list_item_get_item (listitem);
/* The string returned by gtk_string_object_get_string is owned by the instance. */
gtk_label_set_text (GTK_LABEL (lb), gtk_string_object_get_string (strobj));
}
static void
unbind_cb (GtkSignalListItemFactory *self, GtkListItem *listitem, gpointer user_data) {
/* There's nothing to do here. */
}
static void
teardown_cb (GtkSignalListItemFactory *self, GtkListItem *listitem, gpointer user_data) {
/* There's nothing to do here. */
/* GtkListItem instance will be destroyed soon. You don't need to set the child to NULL. */
}
static void
app_activate (GApplication *application) {
GtkApplication *app = GTK_APPLICATION (application);
GtkWidget *win = gtk_application_window_new (app);
gtk_window_set_default_size (GTK_WINDOW (win), 600, 400);
GtkWidget *scr = gtk_scrolled_window_new ();
gtk_window_set_child (GTK_WINDOW (win), scr);
char *array[] = {
"one", "two", "three", "four", NULL
};
/* sl is owned by ns */
/* ns and factory are owned by lv. */
/* Therefore, you don't need to care about their destruction. */
GtkStringList *sl = gtk_string_list_new ((const char * const *) array);
GtkNoSelection *ns = gtk_no_selection_new (G_LIST_MODEL (sl));
GtkListItemFactory *factory = gtk_signal_list_item_factory_new ();
g_signal_connect (factory, "setup", G_CALLBACK (setup_cb), NULL);
g_signal_connect (factory, "bind", G_CALLBACK (bind_cb), NULL);
/* The following two lines can be left out. The handlers do nothing. */
g_signal_connect (factory, "unbind", G_CALLBACK (unbind_cb), NULL);
g_signal_connect (factory, "teardown", G_CALLBACK (teardown_cb), NULL);
GtkWidget *lv = gtk_list_view_new (GTK_SELECTION_MODEL (ns), factory);
gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), lv);
gtk_window_present (GTK_WINDOW (win));
}
/* ----- main ----- */
#define APPLICATION_ID "com.github.ToshioCP.list1"
int
main (int argc, char **argv) {
GtkApplication *app;
int stat;
app = gtk_application_new (APPLICATION_ID, G_APPLICATION_DEFAULT_FLAGS);
g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
stat = g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);
return stat;
}The file list1.c is located under the directory
/src/misc.
Make a shell script below and save it to your bin directory, for
example $HOME/bin.
gcc `pkg-config --cflags gtk4` $1.c `pkg-config --libs gtk4`Change the current directory to the directory includes
list1.c and type as follows.
$ chmod 755 $HOME/bin/comp # or chmod 755 (your bin directory)/comp
$ comp list1
$ ./a.out
Then, the following window appears.
The program is not so difficult. If you feel some difficulty, read this section again, especially GtkSignalListItemFactory subsubsection.
GtkBuilderListItemFactory
GtkBuilderListItemFactory is another GtkListItemFactory. Its behavior is defined in a UI file.
<interface>
<template class="GtkListItem">
<property name="child">
<object class="GtkLabel">
<binding name="label">
<lookup name="string" type="GtkStringObject">
<lookup name="item">GtkListItem</lookup>
</lookup>
</binding>
</object>
</property>
</template>
</interface>Template tag is used to define GtkListItem. And its child property is GtkLabel object. The factory sees this template and creates GtkLabel and sets the child property of GtkListItem. This is the same as what setup handler of GtkSignalListItemFactory did.
Then, bind the label property of the GtkLabel to the string property of a GtkStringObject. The string object refers to the item property of the GtkListItem. So, the lookup tag is like this:
label <- string <- GtkStringObject <- item <- GtkListItem
The last lookup tag has a content GtkListItem.
Usually, C type like GtkListItem doesn’t appear in
the content of tags. This is a special case. There is an
explanation in the GTK
Development Blog by Matthias Clasen.
Remember that the classname (GtkListItem) in a ui template is used as the “this” pointer referring to the object that is being instantiated.
Therefore, GtkListItem instance is used as the
this object of the lookup tag when it is evaluated.
this object will be explained in Section 31.
The C source code is as follows. Its name is
list2.c and located under /src/misc
directory.
#include <gtk/gtk.h>
static void
app_activate (GApplication *application) {
GtkApplication *app = GTK_APPLICATION (application);
gtk_window_present (gtk_application_get_active_window(app));
}
static void
app_startup (GApplication *application) {
GtkApplication *app = GTK_APPLICATION (application);
GtkWidget *win = gtk_application_window_new (app);
gtk_window_set_default_size (GTK_WINDOW (win), 600, 400);
GtkWidget *scr = gtk_scrolled_window_new ();
gtk_window_set_child (GTK_WINDOW (win), scr);
char *array[] = {
"one", "two", "three", "four", NULL
};
GtkStringList *sl = gtk_string_list_new ((const char * const *) array);
GtkSingleSelection *ss = gtk_single_selection_new (G_LIST_MODEL (sl));
const char *ui_string =
"<interface>"
"<template class=\"GtkListItem\">"
"<property name=\"child\">"
"<object class=\"GtkLabel\">"
"<binding name=\"label\">"
"<lookup name=\"string\" type=\"GtkStringObject\">"
"<lookup name=\"item\">GtkListItem</lookup>"
"</lookup>"
"</binding>"
"</object>"
"</property>"
"</template>"
"</interface>"
;
GBytes *gbytes = g_bytes_new_static (ui_string, strlen (ui_string));
GtkListItemFactory *factory = gtk_builder_list_item_factory_new_from_bytes (NULL, gbytes);
GtkWidget *lv = gtk_list_view_new (GTK_SELECTION_MODEL (ss), factory);
gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), lv);
}
/* ----- main ----- */
#define APPLICATION_ID "com.github.ToshioCP.list2"
int
main (int argc, char **argv) {
GtkApplication *app;
int stat;
app = gtk_application_new (APPLICATION_ID, G_APPLICATION_DEFAULT_FLAGS);
g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL);
g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
stat = g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);
return stat;
}No signal handler is needed for GtkBuilderListItemFactory. GtkSingleSelection is used, so user can select one item at a time.
Because this is a small program, the UI data is given as a string.
GtkDirectoryList
GtkDirectoryList is a list model containing GFileInfo objects
which are information of files under a certain directory. It
uses g_file_enumerate_children_async() to get the
GFileInfo objects. The list model is created by
gtk_directory_list_new function.
GtkDirectoryList *gtk_directory_list_new (const char *attributes, GFile *file);attributes is a comma separated list of file
attributes. File attributes are key-value pairs. A key consists
of a namespace and a name. For example, “standard::name” key is
the name of a file. “standard” means general file information.
“name” means filename. The following table shows some
example.
| key | meaning |
|---|---|
| standard::type | file type. for example, regular file, directory, symbolic link, etc. |
| standard::name | filename |
| standard::size | file size in bytes |
| access::can-read | read privilege if the user is able to read the file |
| time::modified | the time the file was last modified in seconds since the UNIX epoch |
The current directory is “.”. The following program makes
GtkDirectoryList dl and its contents are GFileInfo
objects under the current directory.
GFile *file = g_file_new_for_path (".");
GtkDirectoryList *dl = gtk_directory_list_new ("standard::name", file);
g_object_unref (file);It is not so difficult to make a file listing program by
changing list2.c in the previous subsection. One
problem is that GFileInfo doesn’t have properties. Lookup tag
look for a property, so it is useless for looking for a filename
from a GFileInfo object. Instead, a closure tag is appropriate
in this case. A closure tag specifies a function and the type of
the return value of the function.
char *
get_file_name (GtkListItem *item, GFileInfo *info) {
return G_IS_FILE_INFO (info) ? g_strdup (g_file_info_get_name (info)) : NULL;
}
... ...
... ...
"<interface>"
"<template class=\"GtkListItem\">"
"<property name=\"child\">"
"<object class=\"GtkLabel\">"
"<binding name=\"label\">"
"<closure type=\"gchararray\" function=\"get_file_name\">"
"<lookup name=\"item\">GtkListItem</lookup>"
"</closure>"
"</binding>"
"</object>"
"</property>"
"</template>"
"</interface>"- The string “gchararray” is a type name registered in the GObject type system. While the type “gchar” is the same as the C “char” type, “gchararray” specifically represents a NULL-terminated string (char *). In GtkBuilder XML files, you must use these registered type names instead of native C types to let the GValue system handle the data correctly. For example, common type names include gboolean, gchar, gint, gfloat, gdouble, and gchararray. See GObject tutorial for further information.
- Closure tag has type attribute and function attribute.
Function attribute specifies the function name and type
attribute specifies the type of the return value of the
function. The content of closure tag (it is between
<closure…> and</closure>) consists of the parameters
for the function.
<lookup name="item">GtkListItem</lookup>gives the value of the item property of the GtkListItem. This will be the second argument of the function. The first parameter is always the GListItem instance, which is a ‘this’ object. The ‘this’ object is explained in Section 31. get_file_namefunction is the callback function for the closure tag. It first checks theinfoparameter. Because it can be NULL when GListItemitemis unbounded. If it’s GFileInfo, it returns the copied filename. Because the return value (filename) ofg_file_info_get_nameis owned by GFileInfo object. So, the the string needs to be duplicated to give the ownership to the caller. Binding tag binds the “label” property of the GtkLabel to the closure tag.
The whole program (list3.c) is as follows. The
program is located in /src/misc
directory.
#include <gtk/gtk.h>
char *
get_file_name (GtkListItem *item, GFileInfo *info) {
return G_IS_FILE_INFO (info) ? g_strdup (g_file_info_get_name (info)) : NULL;
}
static void
app_activate (GApplication *application) {
GtkApplication *app = GTK_APPLICATION (application);
gtk_window_present (gtk_application_get_active_window(app));
}
static void
app_startup (GApplication *application) {
GtkApplication *app = GTK_APPLICATION (application);
GtkWidget *win = gtk_application_window_new (app);
gtk_window_set_default_size (GTK_WINDOW (win), 600, 400);
GtkWidget *scr = gtk_scrolled_window_new ();
gtk_window_set_child (GTK_WINDOW (win), scr);
GFile *file = g_file_new_for_path (".");
GtkDirectoryList *dl = gtk_directory_list_new ("standard::name", file);
g_object_unref (file);
GtkNoSelection *ns = gtk_no_selection_new (G_LIST_MODEL (dl));
const char *ui_string =
"<interface>"
"<template class=\"GtkListItem\">"
"<property name=\"child\">"
"<object class=\"GtkLabel\">"
"<binding name=\"label\">"
"<closure type=\"gchararray\" function=\"get_file_name\">"
"<lookup name=\"item\">GtkListItem</lookup>"
"</closure>"
"</binding>"
"</object>"
"</property>"
"</template>"
"</interface>"
;
GBytes *gbytes = g_bytes_new_static (ui_string, strlen (ui_string));
GtkListItemFactory *factory = gtk_builder_list_item_factory_new_from_bytes (NULL, gbytes);
GtkWidget *lv = gtk_list_view_new (GTK_SELECTION_MODEL (ns), factory);
gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), lv);
}
/* ----- main ----- */
#define APPLICATION_ID "com.github.ToshioCP.list3"
int
main (int argc, char **argv) {
GtkApplication *app;
int stat;
app = gtk_application_new (APPLICATION_ID, G_APPLICATION_DEFAULT_FLAGS);
g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL);
g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
stat = g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);
return stat;
}The ui data (xml data above) is used to build the GListItem
template at runtime. GtkBuilder refers to the symbol table to
find the function get_file_name.
Generally, a symbol table is used by a linker to link objects
to an executable file. It includes function names and their
location. A linker usually doesn’t put a symbol table into the
created executable file. But if --export-dynamic
option is given, the linker adds the symbol table to the
executable file.
To accomplish it, an option -Wl,--export-dynamic
is given to the C compiler.
-Wlis a C compiler option that passes the following option to the linker.--export-dynamicis a linker option. The following is cited from the linker document. “When creating a dynamically linked executable, add all symbols to the dynamic symbol table. The dynamic symbol table is the set of symbols which are visible from dynamic objects at run time.”
Compile and execute it.
$ gcc -Wl,--export-dynamic `pkg-config --cflags gtk4` list3.c `pkg-config --libs gtk4`
You can also make a shell script to compile
list3.c
gcc -Wl,--export-dynamic `pkg-config --cflags gtk4` $1.c `pkg-config --libs gtk4`Save this one liner to a file comp. Then, copy
it to $HOME/bin and give it executable
permission.
$ cp comp $HOME/bin/comp
$ chmod +x $HOME/bin/comp
You can compile list3.c and execute it, like
this:
$ comp list3
$ ./a.out