How do I make a GtkBuilderListItemFactory closure to access its function in code?

Hi,

I’m currently building an app based on the default “Gnome Application model” in Gnome Builder. My app will include a listview as its main component.

While trying to display the content of a basic GtkDirectoryList, I cannot get my factory to work with closure functions. I’ve spent the most part of the last two days on this problem, so I resort to asking help here before going crazy.

I simply replaced the main “label” container with this in the ui file:

[…]
    <property name="content">
      <object class="GtkScrolledWindow" id="scrolledwindow">
        <property name="hexpand" >TRUE</property>
        <property name="vexpand" >TRUE</property>
        <child>
          <object class="GtkListView" id="listview">
            <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>
            <signal name="activate" handler="listview_activate"/>
            <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>
        </child>
      </object>
    </property>
[…]

Then I included the widgets in my code and created the needed functions (get_icon and get_file_name):

[…]

struct _MyAppWindow
{
  AdwApplicationWindow  parent_instance;

  /* Template widgets */
  GtkSearchEntry      *searchentry;
  GtkScrolledWindow   *scrolledwindow;
  GtkListView         *listview;
  GtkSingleSelection  *singleselection;
  GtkDirectoryList    *directorylist;
};

static 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;
}

static 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
myapp_window_class_init (MyAppWindowClass \*klass)
{
  GtkWidgetClass \*widget_class = GTK_WIDGET_CLASS (klass);
  
  gtk_widget_class_set_template_from_resource (widget_class, “/com/example/MyApp/myapp-window.ui”);
  gtk_widget_class_bind_template_child (widget_class, MyAppWindow, searchentry);
  gtk_widget_class_bind_template_child (widget_class, MyAppWindow, scrolledwindow);
  gtk_widget_class_bind_template_child (widget_class, MyAppWindow, listview);
  gtk_widget_class_bind_template_child (widget_class, MyAppWindow, singleselection);
  gtk_widget_class_bind_template_child (widget_class, MyAppWindow, directorylist);
}

static void
myapp_window_init (MyAppWindow *self)
{
  GFile *file;

  gtk_widget_init_template (GTK_WIDGET (self) );

  file = g_file_new_for_path (".");
  gtk_directory_list_set_file (GTK_DIRECTORY_LIST (self->directorylist), file);
  g_object_unref (file);
}

When I run the app, the factory fails to populate the listview (I do get empty rows created), with these errors (one per file) in logs:

Gtk-CRITICAL **: 21:48:55.247: Error building template for list item: .:0:0: No function named `get_icon`.

It is an error I encountered previously when building an app directly with gcc, but I could fix it by passing the gcc -Wl,--export-dynamic options.

Since Gnome Builder is using meson builds, I suppose I am missing a meson option somewhere, so how can I get my app to compile correctly ?

Thanks in advance

You’ll want to bind the callbacks with gtk_widget_class_bind_template_callback:

gtk_widget_class_bind_template_callback (widget_class, get_icon);
gtk_widget_class_bind_template_callback (widget_class, get_file_name);

Thanks a lot @monster . I can’t believe it is that simple, yet I didn’t see it in the documentation.

Shouldn’t the official documentation (Gtk.Expression) explicitly mention that those functions are to be treated as callbacks?

I’m indeed a bit surprised it isn’t mentioned anywhere (from what I can see). I’d say the best place to document it is at Building composite widgets from template XML, since that’s where signal callbacks are documented as well.

“Callback” does not mean “signal handler”: it means any function referred to by name in the UI definition file, and by pointer in the code.

1 Like