Gtk.ColumnViewCellWidget with Adw.WrapBox inside increases height even though space is enough

I am building a Gtk.ColumnView-based GTK4 application and I noticed some oddity.

First the structure: The application’s main content page is fully used by a Gtk.ColumnView. This table contains multiple columns and rows. The first row is something like a label/key and uses a Gtk.Label. The other rows contain values, most of them also Gtk.Labels, but some contain a list of labels. I wrapped those into an Adw.WrapBox, so if the column width isn’t sufficient to display them, the list can break into multiple lines.

Now the oddity: I noticed that for a specific length of the list (in my case 5 elements with each element having 5-10 characters), the Gtk.ColumnViewCellWidget increases its height even thought Adw.WrapBox has enough space to display all items in a single row:

The following is a minimal working example for replicating the problem. It requires Adwaita 1.7 or higher.

When you resize the second column in this demo app, you should notice that one or multiple rows increase their height even though there’s enough space left. How can I achieve them break only when there’s really not enough space left?

import random
import string
import sys
import gi

gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')  # >= version 1.7

from gi.repository import Gtk, Adw, Gio, GObject


class RowItem(GObject.Object):

    label = GObject.Property(type=str, default="")
    list1 = GObject.Property(type=Gtk.StringList, default=None)
    list2 = GObject.Property(type=Gtk.StringList, default=None)


class ColumnViewApp(Adw.Application):
    def __init__(self):
        super().__init__(application_id='org.example.columnviewapp')

    def do_activate(self):
        win = Adw.ApplicationWindow(application=self, title='ColumnView Example')
        win.set_default_size(600, 600)

        column_view = Gtk.ColumnView()
        column_view.set_hexpand(True)
        column_view.set_vexpand(True)
        column_view.add_css_class('data-table')

        selection = Gtk.NoSelection()

        store = Gio.ListStore.new(RowItem)
        for i in range(3):
            row_item = RowItem()
            row_item.label = f"Row{i}"
            row_item.list1 = Gtk.StringList.new([
                ''.join(random.choices(string.ascii_lowercase, k=6))
                for _ in range(5)
            ])
            row_item.list2 = Gtk.StringList.new([
                "".join(random.choices(string.ascii_lowercase, k=5))
            ])
            store.append(row_item)

        selection.set_model(store)
        column_view.set_model(selection)

        for col_index in range(3):
            factory = Gtk.SignalListItemFactory()
            factory.connect('setup', self._on_factory_setup, col_index)
            factory.connect('bind', self._on_factory_bind, col_index)

            column = Gtk.ColumnViewColumn(title=f'Col{col_index + 1}', factory=factory)
            column.set_expand(True)
            column.set_resizable(True)
            column_view.append_column(column)

        win.set_content(column_view)
        win.present()

    def _on_factory_setup(self, factory, list_item, col_index):
        if col_index == 0:
            # Col1: GtkTreeExpander with GtkLabel
            expander = Gtk.TreeExpander()
            expander.set_halign(Gtk.Align.FILL)
            expander.set_valign(Gtk.Align.CENTER)
            list_item.set_child(expander)

            label = Gtk.Label(label="")
            label.set_halign(Gtk.Align.FILL)
            label.set_valign(Gtk.Align.CENTER)
            expander.set_child(label)
        else:
            # Col2 and Col3: AdwWrapBox
            wrap_box = Adw.WrapBox()
            wrap_box.set_halign(Gtk.Align.FILL)
            wrap_box.set_valign(Gtk.Align.CENTER)
            wrap_box.set_child_spacing(8)
            wrap_box.set_line_spacing(8)
            list_item.set_child(wrap_box)

    def _on_factory_bind(self, factory, list_item, col_index):
        child = list_item.get_child()
        row_item = list_item.get_item()

        if col_index == 0:
            # Col1: Set GtkTreeExpander with GtkLabel
            label = child.get_child()
            label.set_label(row_item.label)
            return

        # Col2 and Col3: AdwWrapBox with labels
        my_list = row_item.list1 if col_index == 1 else row_item.list2
        for s in my_list:
            label = Gtk.Label(label=row_item.label)
            label.set_label(s.get_string())
            label.add_css_class("card")
            child.append(label)


app = ColumnViewApp()
sys.exit(app.run(None))

I experimented with setting properties, such as h/v align, but haven’t found the right combination yet. Many thanks for any help.

This likely indicates a layout bug in the column view. I started taking a look, but haven’t found anything specific yet.

Thank you very much. Can you reproduce it?

Figured it out, please try this MR.

Great! Thank you very much. May I ask how can I best try it out on Ubuntu? I do have to compile GTK myself, install it into a local directory, let some environment variables point to this directory and then it should work, right? Do you have any docs on that or do you even offer pre-built images (e.g., for GNOME OS)?

There’s a guide for compiling GTK from source: Gtk – 4.0: Building GTK

Briefly, on an Ubuntu system, you should be able to do something like the following:

# install all dependencies needed for building GTK 4
$ sudo apt build-dep libgtk-4-1

# in the GTK source tree, with the MR already checked out:
$ meson setup builddir
$ meson compile -C builddir

# run your test program against the just-built GTK:
$ meson devenv -C builddir python3 /path/to/example.py

meson devenv is a nifty utility of Meson that runs a given command with various environment variables set up so that the command sees the just-built project.

Thank you very much. Your MR really solves the issue.