We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
DetailedList showing empty rows
### Describe the bug

Using Toga for the first time, trying to add `DetailedListBox` to my window. *Something* shows up in the box that contains the `DetailedListBox`, but it just looks like a single empty list cell. `on_select` confirms that it's a list cell.

### Steps to reproduce

Run this code:

```python
import toga
from toga.style.pack import Pack

history_data = [
    {
        'icon': toga.Icon.DEFAULT_ICON,
        'title': 'Foo',
        'subtitle': 'Bar',
        'pk': 100,
    },
]


def build(app):
    history = toga.DetailedList(
        id='foo',
        data=history_data,
        on_select=selection_handler,
    )

    history_box = toga.Box()
    history_box.add(history)

    box = toga.Box(style=Pack(padding=(5, 5)))

    box.add(history_box)

    return box


def main():
    return toga.App('DetailedList test', 'foo.bar', startup=build)


def selection_handler(widget, row):
    print('Row {} of widget {} was selected'.format(row, widget))


if __name__ == '__main__':
    main().main_loop()
```

### Expected behavior

A populated listbox shows up

### Screenshots

![image](https://user-images.githubusercontent.com/4182659/235446971-06292534-f8c3-4292-9dec-540a19f54f3f.png)


### Environment

- Operating System: Manjaro Linux
- Python version: 3.10
- Software versions:
  - Briefcase:
  - Toga: 0.3.1
  - ...


### Logs

```

```


### Additional context

- If I add other stuff to `box`, that stuff renders fine
- No matter how many rows I add in list, always seems to render just one empty cell

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `gtk/src/toga_gtk/widgets/detailedlist.py`
Content:
```
1 from ..libs import Gdk, Gio, GLib, Gtk
2 from .base import Widget
3 from .internal.buttons.refresh import RefreshButton
4 from .internal.buttons.scroll import ScrollButton
5 from .internal.rows.texticon import TextIconRow
6 
7 
8 # TODO: Verify if right clicking a row currently works with touch screens, if not,
9 # use Gtk.GestureLongPress
10 class DetailedList(Widget):
11     """Gtk DetailedList implementation.
12 
13     Gtk.ListBox inside a Gtk.ScrolledWindow.
14     """
15 
16     def create(self):
17         # Not the same as selected row. _active_row is the one with its buttons exposed.
18         self._active_row = None
19 
20         self.gtk_on_select_signal_handler = None
21 
22         self.list_box = Gtk.ListBox()
23 
24         self.list_box.set_selection_mode(Gtk.SelectionMode.SINGLE)
25 
26         self.store = Gio.ListStore()
27         # We need to provide a function that transforms whatever is in the store into
28         # a `Gtk.ListBoxRow`, but the items in the store already are `Gtk.ListBoxRow` thus
29         # the identity function.
30         self.list_box.bind_model(self.store, lambda a: a)
31 
32         self.scrolled_window = Gtk.ScrolledWindow()
33 
34         self.scrolled_window.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
35         self.scrolled_window.set_min_content_width(self.interface._MIN_WIDTH)
36         self.scrolled_window.set_min_content_height(self.interface._MIN_HEIGHT)
37 
38         self.scrolled_window.add(self.list_box)
39 
40         self.refresh_button = RefreshButton(self.scrolled_window.get_vadjustment())
41 
42         self.scroll_button = ScrollButton(self.scrolled_window.get_vadjustment())
43         self.scroll_button.set_scroll(lambda: self.scroll_to_row(-1))
44 
45         self.native = Gtk.Overlay()
46         self.native.add_overlay(self.scrolled_window)
47 
48         self.refresh_button.overlay_over(self.native)
49         self.scroll_button.overlay_over(self.native)
50 
51         self.gtk_on_select_signal_handler = self.list_box.connect(
52             "row-selected", self.gtk_on_row_selected
53         )
54 
55         self.right_click_gesture = Gtk.GestureMultiPress.new(self.list_box)
56         self.right_click_gesture.set_button(3)
57         self.right_click_gesture.set_propagation_phase(Gtk.PropagationPhase.BUBBLE)
58         self.right_click_gesture.connect("pressed", self.gtk_on_right_click)
59 
60     def row_factory(self, item):
61         """
62         Args:
63             item (:obj:`Row`)
64         Returns:
65             Returns a (:obj:`TextIconRow`)
66         """
67         return TextIconRow(self.interface.factory, self, item)
68 
69     def destroy(self):
70         self.disconnect(self.gtk_on_select_signal_handler)
71         super().destroy()
72 
73     def change_source(self, source):
74         """
75         Args:
76             source (:obj:`ListSource`)
77         """
78         self.store.remove_all()
79         for item in source:
80             self.store.append(self.row_factory(item))
81 
82         # We can't know the dimensions of each row (and thus of the list) until gtk allocates
83         # space for it. Gtk does emit `size-allocate` after allocation, but I couldn't find any
84         # guarantees that the rows have their sizes allocated in the order they are inserted
85         # in the `ListStore` and in my opinion that's unlikely to be the case.
86 
87         # Therefore we would need to wait for `size-allocate` on all rows and either update
88         # the visibility of the buttons on all `size-allocates` or have a counter and only do
89         # it on the last `size-allocate`. Obviously none of those options are desirable.
90 
91         # Fortunately functions added with `idle_add` are run when gtk is idle and thus after
92         # any size allocation. This solves our problem and from the perspective of the user
93         # happens immediately.
94 
95         # Even though we are adding the callback to the global loop, it only runs once.
96         # This is what the lambda is for. If a callback returns `False` then it's not ran again.
97         # I used a lambda because returning `False` from `self._list_items_changed()` would mean
98         # returning `False` on success.
99         GLib.idle_add(lambda: not self._list_items_changed())
100 
101     def insert(self, index, item):
102         """
103         Args:
104             index (int)
105             item (:obj:`Row`)
106         """
107         item_impl = self.row_factory(item)
108         self.store.insert(index, item_impl)
109         self.list_box.show_all()
110         self._list_items_changed()
111 
112     def change(self, item):
113         """
114         Args:
115             item (:obj:`Row`)
116         """
117         index = item._impl.get_index()
118         self.remove(item, index)
119         item_impl = self.row_factory(item)
120         self.store.insert(index, item_impl)
121 
122     def remove(self, item, index):
123         """Removes a row from the store. Doesn't remove the row from the interface.
124 
125         Args:
126             item (:obj:`Row`)
127             index (int)
128         """
129         if index is None:
130             index = item._impl.get_index()
131 
132         if self._active_row == item._impl:
133             self._active_row = None
134 
135         self.store.remove(index)
136 
137         if self.interface.on_delete is not None:
138             self.interface.on_delete(self.interface, item._impl.interface)
139 
140         item._impl.destroy()
141         self._list_items_changed()
142 
143     def clear(self):
144         self.store.remove_all()
145         self._list_items_changed()
146 
147     def get_selection(self):
148         item_impl = self.list_box.get_selected_row()
149         if item_impl is None:
150             return None
151         else:
152             return item_impl.interface
153 
154     def scroll_to_row(self, row: int):
155         item = self.store[row]
156         item.scroll_to_center()
157 
158     def set_on_refresh(self, handler: callable):
159         if handler is not None:
160             self.refresh_button.set_on_refresh(self.gtk_on_refresh_clicked)
161 
162     def set_on_select(self, handler: callable):
163         pass
164 
165     def set_on_delete(self, handler: callable):
166         pass
167 
168     def after_on_refresh(self, widget, result):
169         # No special handling required
170         pass
171 
172     def gtk_on_refresh_clicked(self):
173         if self.interface.on_refresh is not None:
174             self.interface.on_refresh(self.interface)
175 
176     def gtk_on_row_selected(self, w: Gtk.ListBox, item_impl: Gtk.ListBoxRow):
177         if self.interface.on_select is not None:
178             if item_impl is not None:
179                 self.interface.on_select(self.interface, item_impl.interface)
180             else:
181                 self.interface.on_select(self.interface, None)
182 
183         if self._active_row is not None and self._active_row != item_impl:
184             self._active_row.hide_buttons()
185             self._active_row = None
186 
187     def gtk_on_right_click(self, gesture, n_press, x, y):
188         item_impl = self.list_box.get_row_at_y(y)
189 
190         if item_impl is None:
191             return
192 
193         rect = Gdk.Rectangle()
194         rect.x, rect.y = item_impl.translate_coordinates(self.list_box, x, y)
195 
196         if self._active_row is not None and self._active_row != item_impl:
197             self._active_row.hide_buttons()
198 
199         self._active_row = item_impl
200         item_impl.on_right_click(rect)
201 
202         if self.interface.on_select is not None:
203             self.list_box.select_row(item_impl)
204 
205     def _list_items_changed(self):
206         """Some components such as the refresh button and scroll button change their
207         appearance based on how many items there are on the list or the size of the
208         items.
209 
210         If either of those things changes the buttons need to be notified to recalculate
211         their positions.
212         """
213         self.refresh_button.list_changed()
214         self.scroll_button.list_changed()
215         return True
216
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/gtk/src/toga_gtk/widgets/detailedlist.py b/gtk/src/toga_gtk/widgets/detailedlist.py
--- a/gtk/src/toga_gtk/widgets/detailedlist.py
+++ b/gtk/src/toga_gtk/widgets/detailedlist.py
@@ -1,3 +1,5 @@
+from travertino.size import at_least
+
 from ..libs import Gdk, Gio, GLib, Gtk
 from .base import Widget
 from .internal.buttons.refresh import RefreshButton
@@ -213,3 +215,7 @@
         self.refresh_button.list_changed()
         self.scroll_button.list_changed()
         return True
+
+    def rehint(self):
+        self.interface.intrinsic.width = at_least(self.interface._MIN_WIDTH)
+        self.interface.intrinsic.height = at_least(self.interface._MIN_HEIGHT)

{"golden_diff": "diff --git a/gtk/src/toga_gtk/widgets/detailedlist.py b/gtk/src/toga_gtk/widgets/detailedlist.py\n--- a/gtk/src/toga_gtk/widgets/detailedlist.py\n+++ b/gtk/src/toga_gtk/widgets/detailedlist.py\n@@ -1,3 +1,5 @@\n+from travertino.size import at_least\n+\n from ..libs import Gdk, Gio, GLib, Gtk\n from .base import Widget\n from .internal.buttons.refresh import RefreshButton\n@@ -213,3 +215,7 @@\n         self.refresh_button.list_changed()\n         self.scroll_button.list_changed()\n         return True\n+\n+    def rehint(self):\n+        self.interface.intrinsic.width = at_least(self.interface._MIN_WIDTH)\n+        self.interface.intrinsic.height = at_least(self.interface._MIN_HEIGHT)\n", "issue": "DetailedList showing empty rows\n### Describe the bug\n\nUsing Toga for the first time, trying to add `DetailedListBox` to my window. *Something* shows up in the box that contains the `DetailedListBox`, but it just looks like a single empty list cell. `on_select` confirms that it's a list cell.\n\n### Steps to reproduce\n\nRun this code:\r\n\r\n```python\r\nimport toga\r\nfrom toga.style.pack import Pack\r\n\r\nhistory_data = [\r\n    {\r\n        'icon': toga.Icon.DEFAULT_ICON,\r\n        'title': 'Foo',\r\n        'subtitle': 'Bar',\r\n        'pk': 100,\r\n    },\r\n]\r\n\r\n\r\ndef build(app):\r\n    history = toga.DetailedList(\r\n        id='foo',\r\n        data=history_data,\r\n        on_select=selection_handler,\r\n    )\r\n\r\n    history_box = toga.Box()\r\n    history_box.add(history)\r\n\r\n    box = toga.Box(style=Pack(padding=(5, 5)))\r\n\r\n    box.add(history_box)\r\n\r\n    return box\r\n\r\n\r\ndef main():\r\n    return toga.App('DetailedList test', 'foo.bar', startup=build)\r\n\r\n\r\ndef selection_handler(widget, row):\r\n    print('Row {} of widget {} was selected'.format(row, widget))\r\n\r\n\r\nif __name__ == '__main__':\r\n    main().main_loop()\r\n```\n\n### Expected behavior\n\nA populated listbox shows up\n\n### Screenshots\n\n![image](https://user-images.githubusercontent.com/4182659/235446971-06292534-f8c3-4292-9dec-540a19f54f3f.png)\r\n\n\n### Environment\n\n- Operating System: Manjaro Linux\r\n- Python version: 3.10\r\n- Software versions:\r\n  - Briefcase:\r\n  - Toga: 0.3.1\r\n  - ...\r\n\n\n### Logs\n\n```\r\n\r\n```\r\n\n\n### Additional context\n\n- If I add other stuff to `box`, that stuff renders fine\r\n- No matter how many rows I add in list, always seems to render just one empty cell\n", "before_files": [{"content": "from ..libs import Gdk, Gio, GLib, Gtk\nfrom .base import Widget\nfrom .internal.buttons.refresh import RefreshButton\nfrom .internal.buttons.scroll import ScrollButton\nfrom .internal.rows.texticon import TextIconRow\n\n\n# TODO: Verify if right clicking a row currently works with touch screens, if not,\n# use Gtk.GestureLongPress\nclass DetailedList(Widget):\n    \"\"\"Gtk DetailedList implementation.\n\n    Gtk.ListBox inside a Gtk.ScrolledWindow.\n    \"\"\"\n\n    def create(self):\n        # Not the same as selected row. _active_row is the one with its buttons exposed.\n        self._active_row = None\n\n        self.gtk_on_select_signal_handler = None\n\n        self.list_box = Gtk.ListBox()\n\n        self.list_box.set_selection_mode(Gtk.SelectionMode.SINGLE)\n\n        self.store = Gio.ListStore()\n        # We need to provide a function that transforms whatever is in the store into\n        # a `Gtk.ListBoxRow`, but the items in the store already are `Gtk.ListBoxRow` thus\n        # the identity function.\n        self.list_box.bind_model(self.store, lambda a: a)\n\n        self.scrolled_window = Gtk.ScrolledWindow()\n\n        self.scrolled_window.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)\n        self.scrolled_window.set_min_content_width(self.interface._MIN_WIDTH)\n        self.scrolled_window.set_min_content_height(self.interface._MIN_HEIGHT)\n\n        self.scrolled_window.add(self.list_box)\n\n        self.refresh_button = RefreshButton(self.scrolled_window.get_vadjustment())\n\n        self.scroll_button = ScrollButton(self.scrolled_window.get_vadjustment())\n        self.scroll_button.set_scroll(lambda: self.scroll_to_row(-1))\n\n        self.native = Gtk.Overlay()\n        self.native.add_overlay(self.scrolled_window)\n\n        self.refresh_button.overlay_over(self.native)\n        self.scroll_button.overlay_over(self.native)\n\n        self.gtk_on_select_signal_handler = self.list_box.connect(\n            \"row-selected\", self.gtk_on_row_selected\n        )\n\n        self.right_click_gesture = Gtk.GestureMultiPress.new(self.list_box)\n        self.right_click_gesture.set_button(3)\n        self.right_click_gesture.set_propagation_phase(Gtk.PropagationPhase.BUBBLE)\n        self.right_click_gesture.connect(\"pressed\", self.gtk_on_right_click)\n\n    def row_factory(self, item):\n        \"\"\"\n        Args:\n            item (:obj:`Row`)\n        Returns:\n            Returns a (:obj:`TextIconRow`)\n        \"\"\"\n        return TextIconRow(self.interface.factory, self, item)\n\n    def destroy(self):\n        self.disconnect(self.gtk_on_select_signal_handler)\n        super().destroy()\n\n    def change_source(self, source):\n        \"\"\"\n        Args:\n            source (:obj:`ListSource`)\n        \"\"\"\n        self.store.remove_all()\n        for item in source:\n            self.store.append(self.row_factory(item))\n\n        # We can't know the dimensions of each row (and thus of the list) until gtk allocates\n        # space for it. Gtk does emit `size-allocate` after allocation, but I couldn't find any\n        # guarantees that the rows have their sizes allocated in the order they are inserted\n        # in the `ListStore` and in my opinion that's unlikely to be the case.\n\n        # Therefore we would need to wait for `size-allocate` on all rows and either update\n        # the visibility of the buttons on all `size-allocates` or have a counter and only do\n        # it on the last `size-allocate`. Obviously none of those options are desirable.\n\n        # Fortunately functions added with `idle_add` are run when gtk is idle and thus after\n        # any size allocation. This solves our problem and from the perspective of the user\n        # happens immediately.\n\n        # Even though we are adding the callback to the global loop, it only runs once.\n        # This is what the lambda is for. If a callback returns `False` then it's not ran again.\n        # I used a lambda because returning `False` from `self._list_items_changed()` would mean\n        # returning `False` on success.\n        GLib.idle_add(lambda: not self._list_items_changed())\n\n    def insert(self, index, item):\n        \"\"\"\n        Args:\n            index (int)\n            item (:obj:`Row`)\n        \"\"\"\n        item_impl = self.row_factory(item)\n        self.store.insert(index, item_impl)\n        self.list_box.show_all()\n        self._list_items_changed()\n\n    def change(self, item):\n        \"\"\"\n        Args:\n            item (:obj:`Row`)\n        \"\"\"\n        index = item._impl.get_index()\n        self.remove(item, index)\n        item_impl = self.row_factory(item)\n        self.store.insert(index, item_impl)\n\n    def remove(self, item, index):\n        \"\"\"Removes a row from the store. Doesn't remove the row from the interface.\n\n        Args:\n            item (:obj:`Row`)\n            index (int)\n        \"\"\"\n        if index is None:\n            index = item._impl.get_index()\n\n        if self._active_row == item._impl:\n            self._active_row = None\n\n        self.store.remove(index)\n\n        if self.interface.on_delete is not None:\n            self.interface.on_delete(self.interface, item._impl.interface)\n\n        item._impl.destroy()\n        self._list_items_changed()\n\n    def clear(self):\n        self.store.remove_all()\n        self._list_items_changed()\n\n    def get_selection(self):\n        item_impl = self.list_box.get_selected_row()\n        if item_impl is None:\n            return None\n        else:\n            return item_impl.interface\n\n    def scroll_to_row(self, row: int):\n        item = self.store[row]\n        item.scroll_to_center()\n\n    def set_on_refresh(self, handler: callable):\n        if handler is not None:\n            self.refresh_button.set_on_refresh(self.gtk_on_refresh_clicked)\n\n    def set_on_select(self, handler: callable):\n        pass\n\n    def set_on_delete(self, handler: callable):\n        pass\n\n    def after_on_refresh(self, widget, result):\n        # No special handling required\n        pass\n\n    def gtk_on_refresh_clicked(self):\n        if self.interface.on_refresh is not None:\n            self.interface.on_refresh(self.interface)\n\n    def gtk_on_row_selected(self, w: Gtk.ListBox, item_impl: Gtk.ListBoxRow):\n        if self.interface.on_select is not None:\n            if item_impl is not None:\n                self.interface.on_select(self.interface, item_impl.interface)\n            else:\n                self.interface.on_select(self.interface, None)\n\n        if self._active_row is not None and self._active_row != item_impl:\n            self._active_row.hide_buttons()\n            self._active_row = None\n\n    def gtk_on_right_click(self, gesture, n_press, x, y):\n        item_impl = self.list_box.get_row_at_y(y)\n\n        if item_impl is None:\n            return\n\n        rect = Gdk.Rectangle()\n        rect.x, rect.y = item_impl.translate_coordinates(self.list_box, x, y)\n\n        if self._active_row is not None and self._active_row != item_impl:\n            self._active_row.hide_buttons()\n\n        self._active_row = item_impl\n        item_impl.on_right_click(rect)\n\n        if self.interface.on_select is not None:\n            self.list_box.select_row(item_impl)\n\n    def _list_items_changed(self):\n        \"\"\"Some components such as the refresh button and scroll button change their\n        appearance based on how many items there are on the list or the size of the\n        items.\n\n        If either of those things changes the buttons need to be notified to recalculate\n        their positions.\n        \"\"\"\n        self.refresh_button.list_changed()\n        self.scroll_button.list_changed()\n        return True\n", "path": "gtk/src/toga_gtk/widgets/detailedlist.py"}], "after_files": [{"content": "from travertino.size import at_least\n\nfrom ..libs import Gdk, Gio, GLib, Gtk\nfrom .base import Widget\nfrom .internal.buttons.refresh import RefreshButton\nfrom .internal.buttons.scroll import ScrollButton\nfrom .internal.rows.texticon import TextIconRow\n\n\n# TODO: Verify if right clicking a row currently works with touch screens, if not,\n# use Gtk.GestureLongPress\nclass DetailedList(Widget):\n    \"\"\"Gtk DetailedList implementation.\n\n    Gtk.ListBox inside a Gtk.ScrolledWindow.\n    \"\"\"\n\n    def create(self):\n        # Not the same as selected row. _active_row is the one with its buttons exposed.\n        self._active_row = None\n\n        self.gtk_on_select_signal_handler = None\n\n        self.list_box = Gtk.ListBox()\n\n        self.list_box.set_selection_mode(Gtk.SelectionMode.SINGLE)\n\n        self.store = Gio.ListStore()\n        # We need to provide a function that transforms whatever is in the store into\n        # a `Gtk.ListBoxRow`, but the items in the store already are `Gtk.ListBoxRow` thus\n        # the identity function.\n        self.list_box.bind_model(self.store, lambda a: a)\n\n        self.scrolled_window = Gtk.ScrolledWindow()\n\n        self.scrolled_window.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)\n        self.scrolled_window.set_min_content_width(self.interface._MIN_WIDTH)\n        self.scrolled_window.set_min_content_height(self.interface._MIN_HEIGHT)\n\n        self.scrolled_window.add(self.list_box)\n\n        self.refresh_button = RefreshButton(self.scrolled_window.get_vadjustment())\n\n        self.scroll_button = ScrollButton(self.scrolled_window.get_vadjustment())\n        self.scroll_button.set_scroll(lambda: self.scroll_to_row(-1))\n\n        self.native = Gtk.Overlay()\n        self.native.add_overlay(self.scrolled_window)\n\n        self.refresh_button.overlay_over(self.native)\n        self.scroll_button.overlay_over(self.native)\n\n        self.gtk_on_select_signal_handler = self.list_box.connect(\n            \"row-selected\", self.gtk_on_row_selected\n        )\n\n        self.right_click_gesture = Gtk.GestureMultiPress.new(self.list_box)\n        self.right_click_gesture.set_button(3)\n        self.right_click_gesture.set_propagation_phase(Gtk.PropagationPhase.BUBBLE)\n        self.right_click_gesture.connect(\"pressed\", self.gtk_on_right_click)\n\n    def row_factory(self, item):\n        \"\"\"\n        Args:\n            item (:obj:`Row`)\n        Returns:\n            Returns a (:obj:`TextIconRow`)\n        \"\"\"\n        return TextIconRow(self.interface.factory, self, item)\n\n    def destroy(self):\n        self.disconnect(self.gtk_on_select_signal_handler)\n        super().destroy()\n\n    def change_source(self, source):\n        \"\"\"\n        Args:\n            source (:obj:`ListSource`)\n        \"\"\"\n        self.store.remove_all()\n        for item in source:\n            self.store.append(self.row_factory(item))\n\n        # We can't know the dimensions of each row (and thus of the list) until gtk allocates\n        # space for it. Gtk does emit `size-allocate` after allocation, but I couldn't find any\n        # guarantees that the rows have their sizes allocated in the order they are inserted\n        # in the `ListStore` and in my opinion that's unlikely to be the case.\n\n        # Therefore we would need to wait for `size-allocate` on all rows and either update\n        # the visibility of the buttons on all `size-allocates` or have a counter and only do\n        # it on the last `size-allocate`. Obviously none of those options are desirable.\n\n        # Fortunately functions added with `idle_add` are run when gtk is idle and thus after\n        # any size allocation. This solves our problem and from the perspective of the user\n        # happens immediately.\n\n        # Even though we are adding the callback to the global loop, it only runs once.\n        # This is what the lambda is for. If a callback returns `False` then it's not ran again.\n        # I used a lambda because returning `False` from `self._list_items_changed()` would mean\n        # returning `False` on success.\n        GLib.idle_add(lambda: not self._list_items_changed())\n\n    def insert(self, index, item):\n        \"\"\"\n        Args:\n            index (int)\n            item (:obj:`Row`)\n        \"\"\"\n        item_impl = self.row_factory(item)\n        self.store.insert(index, item_impl)\n        self.list_box.show_all()\n        self._list_items_changed()\n\n    def change(self, item):\n        \"\"\"\n        Args:\n            item (:obj:`Row`)\n        \"\"\"\n        index = item._impl.get_index()\n        self.remove(item, index)\n        item_impl = self.row_factory(item)\n        self.store.insert(index, item_impl)\n\n    def remove(self, item, index):\n        \"\"\"Removes a row from the store. Doesn't remove the row from the interface.\n\n        Args:\n            item (:obj:`Row`)\n            index (int)\n        \"\"\"\n        if index is None:\n            index = item._impl.get_index()\n\n        if self._active_row == item._impl:\n            self._active_row = None\n\n        self.store.remove(index)\n\n        if self.interface.on_delete is not None:\n            self.interface.on_delete(self.interface, item._impl.interface)\n\n        item._impl.destroy()\n        self._list_items_changed()\n\n    def clear(self):\n        self.store.remove_all()\n        self._list_items_changed()\n\n    def get_selection(self):\n        item_impl = self.list_box.get_selected_row()\n        if item_impl is None:\n            return None\n        else:\n            return item_impl.interface\n\n    def scroll_to_row(self, row: int):\n        item = self.store[row]\n        item.scroll_to_center()\n\n    def set_on_refresh(self, handler: callable):\n        if handler is not None:\n            self.refresh_button.set_on_refresh(self.gtk_on_refresh_clicked)\n\n    def set_on_select(self, handler: callable):\n        pass\n\n    def set_on_delete(self, handler: callable):\n        pass\n\n    def after_on_refresh(self, widget, result):\n        # No special handling required\n        pass\n\n    def gtk_on_refresh_clicked(self):\n        if self.interface.on_refresh is not None:\n            self.interface.on_refresh(self.interface)\n\n    def gtk_on_row_selected(self, w: Gtk.ListBox, item_impl: Gtk.ListBoxRow):\n        if self.interface.on_select is not None:\n            if item_impl is not None:\n                self.interface.on_select(self.interface, item_impl.interface)\n            else:\n                self.interface.on_select(self.interface, None)\n\n        if self._active_row is not None and self._active_row != item_impl:\n            self._active_row.hide_buttons()\n            self._active_row = None\n\n    def gtk_on_right_click(self, gesture, n_press, x, y):\n        item_impl = self.list_box.get_row_at_y(y)\n\n        if item_impl is None:\n            return\n\n        rect = Gdk.Rectangle()\n        rect.x, rect.y = item_impl.translate_coordinates(self.list_box, x, y)\n\n        if self._active_row is not None and self._active_row != item_impl:\n            self._active_row.hide_buttons()\n\n        self._active_row = item_impl\n        item_impl.on_right_click(rect)\n\n        if self.interface.on_select is not None:\n            self.list_box.select_row(item_impl)\n\n    def _list_items_changed(self):\n        \"\"\"Some components such as the refresh button and scroll button change their\n        appearance based on how many items there are on the list or the size of the\n        items.\n\n        If either of those things changes the buttons need to be notified to recalculate\n        their positions.\n        \"\"\"\n        self.refresh_button.list_changed()\n        self.scroll_button.list_changed()\n        return True\n\n    def rehint(self):\n        self.interface.intrinsic.width = at_least(self.interface._MIN_WIDTH)\n        self.interface.intrinsic.height = at_least(self.interface._MIN_HEIGHT)\n", "path": "gtk/src/toga_gtk/widgets/detailedlist.py"}]}