Invoking virtual functions defined on an interface via pygobject

With the removal of Gtk.Container between Gtk3 and Gtk4, container widgets no longer implement add_child themselves. The Gtk.Buildable interface has an add_child function.

In gjs (well, typescript) with Gtk4, the following works:

function _setChildren(widget: Gtk.Widget, children: any[]) {
    children = children.flat(Infinity).map(ch => ch instanceof Gtk.Widget
        ? ch
        : new Gtk.Label({ visible: true, label: String(ch) }))


    for (const child of children) {
        widget.vfunc_add_child(
            dummyBulder,
            child,
            type in child ? child[type] : null,
        )
    }
}

(see: astal/lang/gjs/src/gtk4/astalify.ts at main · Aylur/astal · GitHub)

With pygobject, despite Gtk.Box (or other widgets) implementing Gtk.Buildable, I cannot find a way to call the add_child vfunc on them. Gtk.Box does not have a add_child or do_add_child function exposed. What is the intended method of calling interface vfuncs on widgets with pygobject, as is done in the gjs code? I need a generalized way to add a child to any widget implementing Gtk.Buildable in Gtk4.

First of all, all widgets implement GtkBuildable because GtkWidget implements that interface. The GtkBuildable.add_child virtual method is meant to be used to add a “child object” to a GtkBuildable instance inside UI definition files; for instance, that’s how <child> is implement by GtkWidget: GTK will call gtk_widget_set_parent() on any widget defined with <child>, but you can also use <child> to add ancillary objects like event controllers. Scaffolding widgets that can contain an arbitrary amount of widgets will override add_child() to call widget-specific API; for instance, GtkBox overrides GtkBuildable.add_child() in order to call gtk_box_append(). Another use case is for implementing specific child types, like “start” or “end” children.

If you want to implement GtkBuildable.add_child for generic children or custom child types in a widget with PyGObject, you override the virtual function in the same way as you’d override any class virtual function: by adding do_ to the name of the method. For instance (untested code):

class YourWidget(Gtk.Widget, Gtk.Buildable):
    # ...
    def do_add_child(self, builder, child, type):
        if type == 'start':
            self.add_start_child(child)
        elif type == 'end':
            self.add_end_child(child)
        else:
            # Always chain up to the parent's implementation
            Gtk.Widget.do_add_child(self, builder, child, type)

Incidentally, this is not recommended any more for child types; if your widget accepts a specific child type in the UI definition data you want to use properties instead:

<object class="MyWidget">
  <property name="start-child">
    <object class="Foo">...</object>
  </property>
  <property name="end-child">
    <object class="Bar">...</object>
  </property>

Sorry, in my original question I pasted the _getChildren function instead of the _setChildren function.

in the (now correct) gjs code, vfunc_add_child is called on a parent for each widget in an array of widgets. My issue is that pygobject does not expose gtk_buildable_add_child, however gjs does.

That’s just not a thing: gtk_buildable_add_child() is not a public symbol in GTK4. You cannot call it, and it’s not exposed in the introspection ABI that is consumed by language bindings like GJS and PyGObject. I have no idea what you’re calling.

So, let’s step back: what are you actually trying to achieve?

oh, what on earth is widget.vfunc_add_child(...) doing, then?

what I want is a generic means of setting a list of child widgets on a parent widget. effectively, I wish to port the following code for Gtk3 to Gtk4:

        def _set_children(self, children):
            children = map(lambda x: x if isinstance(x, Gtk.Widget) else Gtk.Label(visible=True, label=x), children)

            if isinstance(self, Gtk.Container):
                for child in self.get_children():
                    self.remove(child)

                for child in children:
                    self.add(child)

            else:
                raise TypeError(f"{self.__gtype_name__} is not an instance of Gtk.Container")

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.