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 GtkListBox and GtkTreeView.
I want to explain GtkListView and its related objects in this tutorial.
A list is a sequential data structure. For example, an ordered string sequence “one”, “two”, “three”, “four” is a list. Each element of the list is called item. A list is like an array, but in many cases it is implemented with pointers which point to the next item of the list. And it has a start point. So, each item can be referred by the index of the item (first item, second item, …, nth item, …). There are two cases. One is the index starts from one (one-based) and the other is it starts from zero (zero-based).
Gio provides GListModel interface. It is a zero-based list of the same type of GObject objects, or objects that implement the same interface. An object implements GListModel is usually 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 object maps items in the list to GListView.
The instruction to build the whole list related objects is:
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. So, you need a wrapper which is a GObject and contains a string. GtkStringObject is the wrapper object and GStringList, implements GListModel, is a list of GtkStringObject.
char *array[] = {"one", "two", "three", "four", NULL};
*stringlist = gtk_string_list_new ((const char * const *) array); GtkStringList
The function gtk_string_list_new
creates 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_append
appends an item to the listgtk_string_list_remove
removes an item from the
listgtk_string_list_get_string
gets a string in the
listSee GTK 4 API Reference, GtkStringList for further information.
I’ll explain the other list objects later.
GtkSelectionModel is an interface to support for selections. Thanks to this model, 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 GListModel. You can also create them alone and add GListModel later.
GtkListView is a widget to show GListModel items. GtkListItem is used by GtkListView to represent items of a list model. But, GtkListItem itself is not a widget, 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 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 creates or recycles GtkListItem and connects it with an item of the list model. There are two child objects of this factory, GtkSignalListItemFactory and GtkBuilderListItemFactory.
GtkSignalListItemFactory provides signals for users to configure a GtkListItem object. There are four signals.
The following program list1.c
shows the 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 (GtkListItemFactory *factory, GtkListItem *listitem, gpointer user_data) {
GtkWidget *lb = gtk_label_new (NULL);
gtk_list_item_set_child (listitem, lb);
}
static void
bind_cb (GtkSignalListItemFactory *self, GtkListItem *listitem, gpointer user_data) {
GtkWidget *lb = gtk_list_item_get_child (listitem);
GtkStringObject *strobj = gtk_list_item_get_item (listitem);
const char *text = gtk_string_object_get_string (strobj);
gtk_label_set_text (GTK_LABEL (lb), text);
}
static void
unbind_cb (GtkSignalListItemFactory *self, GtkListItem *listitem, gpointer user_data) {
/* There's nothing to do here. */
/* If you does something like setting a signal in bind_cb, */
/* then disconnecting the signal is necessary in unbind_cb. */
}
static void
teardown_cb (GtkListItemFactory *factory, GtkListItem *listitem, gpointer user_data) {
gtk_list_item_set_child (listitem, NULL);
/* When the child of listitem is set to NULL, the reference to GtkLabel will be released and lb will be destroyed. */
/* Therefore, g_object_unref () for the GtkLabel object doesn't need in the user code. */
}
/* ----- activate, open, startup handlers ----- */
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
};
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);
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));
}
static void
app_startup (GApplication *application) {
}
/* ----- 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, "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 file list1.c
is located under the directory
src/misc. Make a shell script below and save it to your bin directory.
(If you’ve installed GTK 4 from the source to $HOME/local, then your bin
directory is $Home/local/bin. Otherwise, $Home/bin is your private bin
directory.)
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/local/bin/comp # or chmod 755 $Home/bin/comp
$ comp list1
$ ./a.out
Then, list1.c
has been compiled and executed.
I think the program is not so difficult. If you feel some difficulty, read this section again, especially GtkSignalListItemFactory subsubsection.
GtkBuilderListItemFactory is another GtkListItemFactory. Its behavior is defined with 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 GtkLabel to string property of GtkStringObject. The string object is referred to by item property of GtkListItem. So, the lookup tag is like this:
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 about it 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 28.
The C source code is as follows. Its name is list2.c
and
located under src/misc directory.
#include <gtk/gtk.h>
/* ----- activate, open, startup handlers ----- */
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
};
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);
gtk_window_present (GTK_WINDOW (win));
}
static void
app_startup (GApplication *application) {
}
/* ----- 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 GtkBulderListItemFactory. 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 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.
*gtk_directory_list_new (const char *attributes, GFile *file); GtkDirectoryList
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.
*file = g_file_new_for_path (".");
GFile *dl = gtk_directory_list_new ("standard::name", file);
GtkDirectoryList (file); g_object_unref
It is not so difficult to make file listing program by changing
list2.c
in the previous subsection. One problem is that
GInfoFile doesn’t have properties. Lookup tag look for a property, so it
is useless for looking for a filename from a GFileInfo object. Instead,
closure tag is appropriate in this case. Closure tag specifies a
function and the type of the return value of the function.
char *
(GtkListItem *item, GFileInfo *info) {
get_file_name if (! G_IS_FILE_INFO (info))
return NULL;
else
return g_strdup (g_file_info_get_name (info));
}
... ...
... ...
"<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>"
<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.gtk_file_name
function first check the
info
parameter. Because it can be NULL when GListItem
item
is unbound. If its GFileInfo, then return the filename
(copy of the filename).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) {
if (! G_IS_FILE_INFO (info))
return NULL;
else
return g_strdup (g_file_info_get_name (info));
}
/* ----- activate, open, startup handlers ----- */
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);
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);
gtk_window_present (GTK_WINDOW (win));
}
static void
app_startup (GApplication *application) {
}
/* ----- 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.
-Wl
is a C compiler option that passes the following
option to the linker.--export-dynamic
is 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