GtkBuilderlistItemFactory is convenient when GtkListView just shows the contents of a list. Its binding direction is always from an item of a list to a child of GtkListItem.
When it comes to dynamic connection, it’s not enough. For example, you want to edit the contents of a list. You set a child of GtkListItem to a GtkText instance so a user can edit a text with it. You need to bind an item in the list with the buffer of the GtkText. The direction is opposite from the one with GtkBuilderListItemFactory. It is from the GtkText instance to the item in the list. You can implement this with GtkSignalListItemFactory, which is more flexible than GtkBuilderListItemFactory.
Two things are shown in this section.
This section shows just a part of the source file
listeditor.c
. If you want to see the whole codes, see
src/listeditor
directory of the Gtk4 tutorial
repository.
The sample program is a list editor and data of the list are strings. It’s the same as a line editor. It reads a text file line by line. Each line is an item of the list. The list is displayed with GtkColumnView. There are two columns. The one is a button, which makes the line be a current line. If the line is the current line, the button is colored with red. The other is a string which is the contents of the corresponding item of the list.
The source files are located at src/listeditor
directory. You can compile end execute it as follows.
src/listeditor
.$ meson _build
$ ninja -C _build
$ _build/listeditor
The current line number (zero-based) is shown at the left of the tool bar. The file name is shown at the right of the write button.
The second column (GtkColumnViewColumn) sets its factory property to GtkSignalListItemFactory. It uses three signals setup, bind and unbind. The following is their sgnal handlers.
static void
setup2_cb (GtkListItemFactory *factory, GtkListItem *listitem) {
GtkWidget *text = gtk_text_new ();
gtk_list_item_set_child (listitem, GTK_WIDGET (text));
}
static void
bind2_cb (GtkListItemFactory *factory, GtkListItem *listitem) {
GtkWidget *text = gtk_list_item_get_child (listitem);
LeData *data = LE_DATA (gtk_list_item_get_item (listitem));
GtkEntryBuffer *buffer;
GBinding *bind;
buffer = gtk_text_get_buffer (GTK_TEXT (text));
gtk_entry_buffer_set_text (buffer, le_data_look_string (data), -1);
bind = g_object_bind_property (buffer, "text", data, "string", G_BINDING_DEFAULT);
g_object_set_data (G_OBJECT (listitem), "bind", bind);
}
static void
unbind2_cb (GtkListItemFactory *factory, GtkListItem *listitem) {
GBinding *bind = G_BINDING (g_object_get_data (G_OBJECT (listitem), "bind"));
g_binding_unbind(bind);
g_object_set_data (G_OBJECT (listitem), "bind", NULL);
}
setup2_cb
is a setup signal handler on the
GtkSignalListItemFactory. This factory is inserted to the factory
property of the second GtkColumnViewColumn. Te handler just creates a
GtkText instance and sets the child of listitem
to it. The
instance will be destroyed automatically when the listitem
is destroyed. So, teardown signal handler isn’t necessary.bind2_cb
is a bind signal handler. It is called
when the listitem
is bound to an item in the list. The list
items are LeData instances. LeData is defined in the file
listeditor.c
(the C source file of the list editor). It is
a child class of GObject and has two data. The one is
listitem
which points a GtkListItem instance when they are
connected. If no GtkListItem is connected, it is NULL. The other is
string
which is a content of the line.
text
is a child of the listitem
and
it is a GtkText instance. data
is an item pointed by the
listitem
. It is a LeData instance.text
.le_data_look_string (data)
. le_data_look_string returns the
string of the data and the ownership of the string is taken by the
data
. So, the caller don’t need to free the string.g_object_bind_property
binds a property and another
object property. This line binds the “text” property of the
buffer
(source) and the “string” property of the
data
(destination). It is a uni-directional binding
(G_BINDING_DEFAULT
). When a user changes the GtkText text,
the same string is immediately put into the data
. The
function returns a GBinding instance. This binding is different from
bindings of GtkExpression. This binding needs the existence of the two
properties.g_object_set_data
sets the association from the key to the
value. This line sets the association from “bind” to bind
instance on the listitem
instance. It makes possible for
the “unbind” handler to get the bind
instance.unbind2_cb
is a unbind signal handler.
bind
instance from the table in the
listitem
instance.This technique is not so complicated. You can use it when you make a cell editable application.
Next topic is to change the GtkColumnView (or GtkListView) cells dynamically. The example changes the color of the buttons, which are children of GtkListItem instances, as the current line position moves.
The line editor has the current position of the list.
The button of the current line is colored with red and otherwise white.
The current line has no relationship to GtkSingleSelection object. GtkSingleSelection selects a line on the display. The current line doesn’t need to be on the display. It is possible to be on the line out of the Window (GtkScrolledWindow). Actually, the program doesn’t use GtkSingleSelection.
It is necessary to know the corresponding GtkListItem instance from
the item in the list. It is the opposite direction from
gtk_list_item_get_item
function. To accomplish this, we set
a listitem
element of LeData to point the corresponding
GtkListItem instance. Therefore, items (LeData) in the list always know
the GtkListItem. If there’s no GtkListItem binded to the item, NULL is
assigned.
void
select_cb (GtkButton *btn, GtkListItem *listitem) {
LeWindow *win = LE_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (btn), LE_TYPE_WINDOW));
update_current (win, gtk_list_item_get_position (listitem));
}
static void
setup1_cb (GtkListItemFactory *factory, GtkListItem *listitem) {
GtkWidget *button = gtk_button_new ();
gtk_list_item_set_child (listitem, button);
g_signal_connect (button, "clicked", G_CALLBACK (select_cb), listitem);
}
static void
bind1_cb (GtkListItemFactory *factory, GtkListItem *listitem) {
LeData *data = LE_DATA (gtk_list_item_get_item (listitem));
if (data)
le_data_set_listitem (data, listitem);
}
static void
unbind1_cb (GtkListItemFactory *factory, GtkListItem *listitem) {
LeData *data = LE_DATA (gtk_list_item_get_item (listitem));
if (data)
le_data_set_listitem (data, NULL);
}
setup1_cb
is a setup signal handler on the
GtkSignalListItemFactory. This factory is inserted to the factory
property of the first GtkColumnViewColumn. It sets the child of
listitem
to a newly created GtkButton instance. The
“clicked” signal on the button is connected to the handler
select_cb
. When the listitem is destroyed, the child
(GtkButton) is also destroyed. At the same time, the connection of the
signal and handler is also destroyed. So, you don’t need teardown signal
handler.select_cb
is a “clicked” signal handler. LeWindow
is defined in listeditor.c
. It’s a child class of
GtkApplicationWindow. The handler just calls the
update_current
function. The function will be explained
later.bind1_cb
is a bind signal handler. It sets the
“listitem” element of the item (LeData) to point the
listitem
(GtkListItem instance). It makes the item possible
to find the corresponding GtkListItem instance.unbind1_cb
is an unbind signal handler. It
removes the listitem
instance from the “listitem” element
of the item. The element becomes NULL, which tells no GtkListItem is
bound. When referring GtkListItem, it needs to check the “listitem”
element whether it points a GtkListItem or not (NULL). Otherwise bad
things will happen.static void
update_current (LeWindow *win, int new) {
char *s;
LeData *data;
GtkListItem *listitem;
GtkButton *button;
const char *non_current[1] = {NULL};
const char *current[2] = {"current", NULL};
if (new >= 0)
s = g_strdup_printf ("%d", new);
else
s = "";
gtk_label_set_text (GTK_LABEL (win->position_label), s);
if (*s) // s isn't an empty string
g_free (s);
if (win->position >=0) {
data = LE_DATA (g_list_model_get_item (G_LIST_MODEL (win->liststore), win->position));
if ((listitem = le_data_get_listitem (data)) != NULL) {
button = GTK_BUTTON (gtk_list_item_get_child (listitem));
gtk_widget_set_css_classes (GTK_WIDGET (button), non_current);
g_object_unref (listitem);
}
g_object_unref (data);
}
win->position = new;
if (win->position >=0) {
data = LE_DATA (g_list_model_get_item (G_LIST_MODEL (win->liststore), win->position));
if ((listitem = le_data_get_listitem (data)) != NULL) {
button = GTK_BUTTON (gtk_list_item_get_child (listitem));
gtk_widget_set_css_classes (GTK_WIDGET (button), current);
g_object_unref (listitem);
}
g_object_unref (data);
}
}
The function update_current
does several things.
win
, which is
an instance of LeWindow class. It has some elements.
new
, which is the new current
position. At the beginning of the function, win->position points the
old position.{"current", NULL}
. It is a NULL-terminated array of
strings. Each string is a CSS class. Now the button has “current” style
class.The color of buttons are determined by the “background” CSS style. The following CSS is applied to the default GdkDisplay in advance (in the startup handler of the application).
.current {background: red;} columnview listview row button
The selectors “columnview listview row” is needed before “button” selector. Otherwise the buttons in the GtkColumnview won’t be found. The button selector has “current” class. So, the only “current” class button is colored with red. Other buttons are not colored, which means they are white.
The function gtk_widget_dispose_template
clears the
template children for the given widget. This is the opposite of
gtk_widget_init_template()
. It is a new function of GTK 4.8
version. If your GTK version is lower than 4.8, you need to modify the
program.