diff --git a/gui-libs/gtk/files/4447.patch b/gui-libs/gtk/files/4447.patch
deleted file mode 100644
index 5f27cdd..0000000
--- a/gui-libs/gtk/files/4447.patch
+++ /dev/null
@@ -1,200 +0,0 @@
-diff --git a/gtk/gtklistbase.c b/gtk/gtklistbase.c
-index 85827363ced093ffcf94597a5ce02b405a380965..b23e5a58078915319045102e83e254929c75df03 100644
---- a/gtk/gtklistbase.c
-+++ b/gtk/gtklistbase.c
-@@ -64,6 +64,7 @@ struct _GtkListBasePrivate
- GtkListItemManager *item_manager;
- GtkSelectionModel *model;
- GtkOrientation orientation;
-+ gboolean reversed;
- GtkAdjustment *adjustment[2];
- GtkScrollablePolicy scroll_policy[2];
-
-@@ -94,6 +95,7 @@ enum
- PROP_HADJUSTMENT,
- PROP_HSCROLL_POLICY,
- PROP_ORIENTATION,
-+ PROP_REVERSED,
- PROP_VADJUSTMENT,
- PROP_VSCROLL_POLICY,
-
-@@ -145,10 +147,23 @@ static gboolean
- gtk_list_base_adjustment_is_flipped (GtkListBase *self,
- GtkOrientation orientation)
- {
-- if (orientation == GTK_ORIENTATION_VERTICAL)
-- return FALSE;
-+ GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
-+ gboolean rtl = gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL;
-
-- return gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL;
-+ if (priv->orientation == GTK_ORIENTATION_VERTICAL)
-+ {
-+ if (orientation == GTK_ORIENTATION_VERTICAL)
-+ return priv->reversed;
-+ else
-+ return rtl;
-+ }
-+ else
-+ {
-+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
-+ return rtl ^ priv->reversed;
-+ else
-+ return false;
-+ }
- }
-
- static void
-@@ -317,8 +332,7 @@ gtk_list_base_move_focus (GtkListBase *self,
- {
- GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
-
-- if (orientation == GTK_ORIENTATION_HORIZONTAL &&
-- gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL)
-+ if (gtk_list_base_adjustment_is_flipped (self, orientation))
- steps = -steps;
-
- if (orientation == priv->orientation)
-@@ -620,6 +634,10 @@ gtk_list_base_get_property (GObject *object,
- g_value_set_enum (value, priv->scroll_policy[GTK_ORIENTATION_HORIZONTAL]);
- break;
-
-+ case PROP_REVERSED:
-+ g_value_set_boolean (value, priv->reversed);
-+ break;
-+
- case PROP_ORIENTATION:
- g_value_set_enum (value, priv->orientation);
- break;
-@@ -683,6 +701,22 @@ gtk_list_base_set_scroll_policy (GtkListBase *self,
- : properties[PROP_VSCROLL_POLICY]);
- }
-
-+static void
-+gtk_list_base_set_reversed (GtkListBase *self,
-+ gboolean reversed)
-+{
-+ GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
-+
-+ if (priv->reversed == reversed)
-+ return;
-+
-+ priv->reversed = reversed;
-+
-+ gtk_widget_queue_resize (GTK_WIDGET (self));
-+
-+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_REVERSED]);
-+}
-+
- static void
- gtk_list_base_set_property (GObject *object,
- guint property_id,
-@@ -702,6 +736,10 @@ gtk_list_base_set_property (GObject *object,
- gtk_list_base_set_scroll_policy (self, GTK_ORIENTATION_HORIZONTAL, g_value_get_enum (value));
- break;
-
-+ case PROP_REVERSED:
-+ gtk_list_base_set_reversed (self, g_value_get_boolean (value));
-+ break;
-+
- case PROP_ORIENTATION:
- {
- GtkOrientation orientation = g_value_get_enum (value);
-@@ -1141,6 +1179,18 @@ gtk_list_base_class_init (GtkListBaseClass *klass)
- g_param_spec_override ("vscroll-policy",
- g_object_interface_find_property (iface, "vscroll-policy"));
-
-+ /**
-+ * GtkListBase:reversed:
-+ *
-+ * Whether to show the list in reverse or not.
-+ *
-+ * Since: 4.8
-+ */
-+ properties[PROP_REVERSED] =
-+ g_param_spec_boolean ("reversed", NULL, NULL,
-+ FALSE,
-+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
-+
- /**
- * GtkListBase:orientation:
- *
-@@ -1318,7 +1368,7 @@ update_autoscroll (GtkListBase *self,
- else
- delta_x = 0;
-
-- if (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL)
-+ if (gtk_list_base_adjustment_is_flipped (self, GTK_ORIENTATION_HORIZONTAL))
- delta_x = - delta_x;
-
- height = gtk_widget_get_height (GTK_WIDGET (self));
-@@ -1330,6 +1380,9 @@ update_autoscroll (GtkListBase *self,
- else
- delta_y = 0;
-
-+ if (gtk_list_base_adjustment_is_flipped (self, GTK_ORIENTATION_VERTICAL))
-+ delta_y = - delta_y;
-+
- if (delta_x != 0 || delta_y != 0)
- add_autoscroll (self, delta_x, delta_y);
- else
-@@ -1365,14 +1418,20 @@ gtk_list_base_size_allocate_child (GtkListBase *self,
-
- if (gtk_list_base_get_orientation (GTK_LIST_BASE (self)) == GTK_ORIENTATION_VERTICAL)
- {
-- if (_gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_LTR)
-+ if (gtk_list_base_adjustment_is_flipped(self, GTK_ORIENTATION_HORIZONTAL))
-+ {
-+ child_allocation.x = self_width - x - width;
-+ }
-+ else
- {
- child_allocation.x = x;
-- child_allocation.y = y;
-+ }
-+ if (gtk_list_base_adjustment_is_flipped(self, GTK_ORIENTATION_VERTICAL))
-+ {
-+ child_allocation.y = self_height - y - height;
- }
- else
- {
-- child_allocation.x = self_width - x - width;
- child_allocation.y = y;
- }
- child_allocation.width = width;
-@@ -1380,14 +1439,20 @@ gtk_list_base_size_allocate_child (GtkListBase *self,
- }
- else
- {
-- if (_gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_LTR)
-+ if (gtk_list_base_adjustment_is_flipped(self, GTK_ORIENTATION_HORIZONTAL))
-+ {
-+ child_allocation.x = self_width - y - height;
-+ }
-+ else
- {
- child_allocation.x = y;
-- child_allocation.y = x;
-+ }
-+ if (gtk_list_base_adjustment_is_flipped(self, GTK_ORIENTATION_VERTICAL))
-+ {
-+ child_allocation.y = self_height - x - width;
- }
- else
- {
-- child_allocation.x = self_width - y - height;
- child_allocation.y = x;
- }
- child_allocation.width = height;
-@@ -1423,8 +1488,10 @@ gtk_list_base_widget_to_list (GtkListBase *self,
- GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
- GtkWidget *widget = GTK_WIDGET (self);
-
-- if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
-+ if (gtk_list_base_adjustment_is_flipped (self, GTK_ORIENTATION_HORIZONTAL))
- x_widget = gtk_widget_get_width (widget) - x_widget;
-+ if (gtk_list_base_adjustment_is_flipped (self, GTK_ORIENTATION_VERTICAL))
-+ y_widget = gtk_widget_get_height (widget) - y_widget;
-
- gtk_list_base_get_adjustment_values (self, OPPOSITE_ORIENTATION (priv->orientation), across_out, NULL, NULL);
- gtk_list_base_get_adjustment_values (self, priv->orientation, along_out, NULL, NULL);
diff --git a/gui-libs/gtk/files/5818.patch b/gui-libs/gtk/files/5818.patch
deleted file mode 100644
index 201dbf0..0000000
--- a/gui-libs/gtk/files/5818.patch
+++ /dev/null
@@ -1,6139 +0,0 @@
-diff --git a/demos/gtk-demo/demo.gresource.xml b/demos/gtk-demo/demo.gresource.xml
-index 615d782bfef56242d6be7258f7435f466144da55..ba87e860cef4b3cc1241c6180d63b088ab7a9181 100644
---- a/demos/gtk-demo/demo.gresource.xml
-+++ b/demos/gtk-demo/demo.gresource.xml
-@@ -195,6 +195,9 @@
-
- listview_settings.ui
-
-+
-+ listview_settings2.ui
-+
-
- ucdnames.data
-
-@@ -312,6 +315,7 @@
- listview_minesweeper.c
- listview_selections.c
- listview_settings.c
-+ listview_settings2.c
- listview_ucd.c
- listview_weather.c
- listview_words.c
-diff --git a/demos/gtk-demo/listview_settings.c b/demos/gtk-demo/listview_settings.c
-index 4e8e69bfbf4b8dd9548360e99fcaa623e0ca205d..6a7779ba12b0690777e5c88143d48238a100ee74 100644
---- a/demos/gtk-demo/listview_settings.c
-+++ b/demos/gtk-demo/listview_settings.c
-@@ -14,138 +14,7 @@
-
- #include
-
--#include
--
--/* Create an object that wraps GSettingsSchemaKey because that's a boxed type */
--typedef struct _SettingsKey SettingsKey;
--struct _SettingsKey
--{
-- GObject parent_instance;
--
-- GSettings *settings;
-- GSettingsSchemaKey *key;
--};
--
--enum {
-- PROP_0,
-- PROP_NAME,
-- PROP_SUMMARY,
-- PROP_DESCRIPTION,
-- PROP_VALUE,
-- PROP_TYPE,
-- PROP_DEFAULT_VALUE,
--
-- N_PROPS
--};
--
--#define SETTINGS_TYPE_KEY (settings_key_get_type ())
--G_DECLARE_FINAL_TYPE (SettingsKey, settings_key, SETTINGS, KEY, GObject);
--
--G_DEFINE_TYPE (SettingsKey, settings_key, G_TYPE_OBJECT);
--static GParamSpec *properties[N_PROPS] = { NULL, };
--
--static void
--settings_key_get_property (GObject *object,
-- guint property_id,
-- GValue *value,
-- GParamSpec *pspec)
--{
-- SettingsKey *self = SETTINGS_KEY (object);
--
-- switch (property_id)
-- {
-- case PROP_DESCRIPTION:
-- g_value_set_string (value, g_settings_schema_key_get_description (self->key));
-- break;
--
-- case PROP_NAME:
-- g_value_set_string (value, g_settings_schema_key_get_name (self->key));
-- break;
--
-- case PROP_SUMMARY:
-- g_value_set_string (value, g_settings_schema_key_get_summary (self->key));
-- break;
--
-- case PROP_VALUE:
-- {
-- GVariant *variant = g_settings_get_value (self->settings, g_settings_schema_key_get_name (self->key));
-- g_value_take_string (value, g_variant_print (variant, FALSE));
-- g_variant_unref (variant);
-- }
-- break;
--
-- case PROP_TYPE:
-- {
-- const GVariantType *type = g_settings_schema_key_get_value_type (self->key);
-- g_value_set_string (value, g_variant_type_peek_string (type));
-- }
-- break;
--
-- case PROP_DEFAULT_VALUE:
-- {
-- GVariant *variant = g_settings_schema_key_get_default_value (self->key);
-- g_value_take_string (value, g_variant_print (variant, FALSE));
-- g_variant_unref (variant);
-- }
-- break;
--
-- default:
-- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
-- break;
-- }
--}
--
--static void
--settings_key_finalize (GObject *object)
--{
-- SettingsKey *self = SETTINGS_KEY (object);
--
-- g_object_unref (self->settings);
-- g_settings_schema_key_unref (self->key);
--
-- G_OBJECT_CLASS (settings_key_parent_class)->finalize (object);
--}
--
--static void
--settings_key_class_init (SettingsKeyClass *klass)
--{
-- GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
--
-- gobject_class->finalize = settings_key_finalize;
-- gobject_class->get_property = settings_key_get_property;
--
-- properties[PROP_DESCRIPTION] =
-- g_param_spec_string ("description", NULL, NULL, NULL, G_PARAM_READABLE);
-- properties[PROP_NAME] =
-- g_param_spec_string ("name", NULL, NULL, NULL, G_PARAM_READABLE);
-- properties[PROP_SUMMARY] =
-- g_param_spec_string ("summary", NULL, NULL, NULL, G_PARAM_READABLE);
-- properties[PROP_VALUE] =
-- g_param_spec_string ("value", NULL, NULL, NULL, G_PARAM_READABLE);
-- properties[PROP_TYPE] =
-- g_param_spec_string ("type", NULL, NULL, NULL, G_PARAM_READABLE);
-- properties[PROP_DEFAULT_VALUE] =
-- g_param_spec_string ("default-value", NULL, NULL, NULL, G_PARAM_READABLE);
--
-- g_object_class_install_properties (gobject_class, N_PROPS, properties);
--}
--
--static void
--settings_key_init (SettingsKey *self)
--{
--}
--
--static SettingsKey *
--settings_key_new (GSettings *settings,
-- GSettingsSchemaKey *key)
--{
-- SettingsKey *result = g_object_new (SETTINGS_TYPE_KEY, NULL);
--
-- result->settings = g_object_ref (settings);
-- result->key = g_settings_schema_key_ref (key);
--
-- return result;
--}
-+#include "settings-key.h"
-
- static void
- item_value_changed (GtkEditableLabel *label,
-@@ -153,6 +22,7 @@ item_value_changed (GtkEditableLabel *label,
- GtkColumnViewCell *cell)
- {
- SettingsKey *self;
-+ GSettingsSchemaKey *key;
- const char *text;
- const GVariantType *type;
- GVariant *variant;
-@@ -163,9 +33,10 @@ item_value_changed (GtkEditableLabel *label,
- text = gtk_editable_get_text (GTK_EDITABLE (label));
-
- self = gtk_column_view_cell_get_item (cell);
-+ key = settings_key_get_key (self);
-
-- type = g_settings_schema_key_get_value_type (self->key);
-- name = g_settings_schema_key_get_name (self->key);
-+ type = g_settings_schema_key_get_value_type (key);
-+ name = g_settings_schema_key_get_name (key);
-
- variant = g_variant_parse (type, text, NULL, NULL, &error);
- if (!variant)
-@@ -175,13 +46,13 @@ item_value_changed (GtkEditableLabel *label,
- goto revert;
- }
-
-- if (!g_settings_schema_key_range_check (self->key, variant))
-+ if (!g_settings_schema_key_range_check (key, variant))
- {
- g_warning ("Not a valid value for %s", name);
- goto revert;
- }
-
-- g_settings_set_value (self->settings, name, variant);
-+ g_settings_set_value (settings_key_get_settings (self), name, variant);
- g_variant_unref (variant);
- return;
-
-diff --git a/demos/gtk-demo/listview_settings2.c b/demos/gtk-demo/listview_settings2.c
-new file mode 100644
-index 0000000000000000000000000000000000000000..4f3e87fb68f69c7a2fbf44c5276599bfa06cd704
---- /dev/null
-+++ b/demos/gtk-demo/listview_settings2.c
-@@ -0,0 +1,230 @@
-+/* Lists/Settings v2
-+ * #Keywords: GtkListHeaderFactory, GtkSectionModel
-+ *
-+ * This demo shows a settings viewer for GSettings.
-+ *
-+ * It demonstrates how to implement support for sections with GtkListView.
-+ *
-+ * It also shows how to quickly flatten a large tree of items into a list
-+ * that can be filtered to find the itmes one is looking for.
-+ */
-+
-+#include
-+
-+#include "settings-key.h"
-+
-+static void
-+item_value_changed (GtkEditableLabel *label,
-+ GParamSpec *pspec,
-+ GtkColumnViewCell *cell)
-+{
-+ SettingsKey *self;
-+ GSettingsSchemaKey *key;
-+ const char *text;
-+ const GVariantType *type;
-+ GVariant *variant;
-+ GError *error = NULL;
-+ const char *name;
-+ char *value;
-+
-+ text = gtk_editable_get_text (GTK_EDITABLE (label));
-+
-+ self = gtk_column_view_cell_get_item (cell);
-+ key = settings_key_get_key (self);
-+
-+ type = g_settings_schema_key_get_value_type (key);
-+ name = g_settings_schema_key_get_name (key);
-+
-+ variant = g_variant_parse (type, text, NULL, NULL, &error);
-+ if (!variant)
-+ {
-+ g_warning ("%s", error->message);
-+ g_clear_error (&error);
-+ goto revert;
-+ }
-+
-+ if (!g_settings_schema_key_range_check (key, variant))
-+ {
-+ g_warning ("Not a valid value for %s", name);
-+ goto revert;
-+ }
-+
-+ g_settings_set_value (settings_key_get_settings (self), name, variant);
-+ g_variant_unref (variant);
-+ return;
-+
-+revert:
-+ gtk_widget_error_bell (GTK_WIDGET (label));
-+
-+ g_object_get (self, "value", &value, NULL);
-+ gtk_editable_set_text (GTK_EDITABLE (label), value);
-+ g_free (value);
-+}
-+
-+static int
-+strvcmp (gconstpointer p1,
-+ gconstpointer p2)
-+{
-+ const char * const *s1 = p1;
-+ const char * const *s2 = p2;
-+
-+ return strcmp (*s1, *s2);
-+}
-+
-+static gpointer
-+map_settings_to_keys (gpointer item,
-+ gpointer unused)
-+{
-+ GSettings *settings = item;
-+ GSettingsSchema *schema;
-+ GListStore *store;
-+ char **keys;
-+ guint i;
-+
-+ g_object_get (settings, "settings-schema", &schema, NULL);
-+
-+ store = g_list_store_new (SETTINGS_TYPE_KEY);
-+
-+ keys = g_settings_schema_list_keys (schema);
-+
-+ for (i = 0; keys[i] != NULL; i++)
-+ {
-+ GSettingsSchemaKey *almost_there = g_settings_schema_get_key (schema, keys[i]);
-+ SettingsKey *finally = settings_key_new (settings, almost_there);
-+ g_list_store_append (store, finally);
-+ g_object_unref (finally);
-+ g_settings_schema_key_unref (almost_there);
-+ }
-+
-+ g_strfreev (keys);
-+ g_settings_schema_unref (schema);
-+ g_object_unref (settings);
-+
-+ return store;
-+}
-+
-+static GListModel *
-+create_settings_model (gpointer item,
-+ gpointer unused)
-+{
-+ GSettings *settings = item;
-+ char **schemas;
-+ GListStore *result;
-+ guint i;
-+
-+ if (settings == NULL)
-+ {
-+ g_settings_schema_source_list_schemas (g_settings_schema_source_get_default (),
-+ TRUE,
-+ &schemas,
-+ NULL);
-+ }
-+ else
-+ {
-+ schemas = g_settings_list_children (settings);
-+ }
-+
-+ if (schemas == NULL || schemas[0] == NULL)
-+ {
-+ g_free (schemas);
-+ return NULL;
-+ }
-+
-+ qsort (schemas, g_strv_length (schemas), sizeof (char *), strvcmp);
-+
-+ result = g_list_store_new (G_TYPE_SETTINGS);
-+ for (i = 0; schemas[i] != NULL; i++)
-+ {
-+ GSettings *child;
-+
-+ if (settings == NULL)
-+ child = g_settings_new (schemas[i]);
-+ else
-+ child = g_settings_get_child (settings, schemas[i]);
-+
-+ g_list_store_append (result, child);
-+ g_object_unref (child);
-+ }
-+
-+ g_strfreev (schemas);
-+
-+ return G_LIST_MODEL (result);
-+}
-+
-+static void
-+search_enabled (GtkSearchEntry *entry)
-+{
-+ gtk_editable_set_text (GTK_EDITABLE (entry), "");
-+}
-+
-+static void
-+stop_search (GtkSearchEntry *entry,
-+ gpointer data)
-+{
-+ gtk_editable_set_text (GTK_EDITABLE (entry), "");
-+}
-+
-+static GtkWidget *window = NULL;
-+
-+GtkWidget *
-+do_listview_settings2 (GtkWidget *do_widget)
-+{
-+ if (window == NULL)
-+ {
-+ GtkListView *listview;
-+ GListModel *model;
-+ GtkTreeListModel *treemodel;
-+ GtkNoSelection *selection;
-+ GtkBuilderScope *scope;
-+ GtkBuilder *builder;
-+ GError *error = NULL;
-+ GtkFilter *filter;
-+
-+ g_type_ensure (SETTINGS_TYPE_KEY);
-+
-+ scope = gtk_builder_cscope_new ();
-+ gtk_builder_cscope_add_callback (scope, search_enabled);
-+ gtk_builder_cscope_add_callback (scope, stop_search);
-+ gtk_builder_cscope_add_callback (scope, settings_key_get_search_string);
-+ gtk_builder_cscope_add_callback (scope, item_value_changed);
-+
-+ builder = gtk_builder_new ();
-+ gtk_builder_set_scope (builder, scope);
-+ g_object_unref (scope);
-+
-+ gtk_builder_add_from_resource (builder, "/listview_settings2/listview_settings2.ui", &error);
-+ g_assert_no_error (error);
-+
-+ window = GTK_WIDGET (gtk_builder_get_object (builder, "window"));
-+ gtk_window_set_display (GTK_WINDOW (window),
-+ gtk_widget_get_display (do_widget));
-+ g_object_add_weak_pointer (G_OBJECT (window), (gpointer *) &window);
-+
-+ listview = GTK_LIST_VIEW (gtk_builder_get_object (builder, "listview"));
-+ filter = GTK_FILTER (gtk_builder_get_object (builder, "filter"));
-+
-+ model = create_settings_model (NULL, NULL);
-+ treemodel = gtk_tree_list_model_new (model,
-+ TRUE,
-+ TRUE,
-+ create_settings_model,
-+ NULL,
-+ NULL);
-+ model = G_LIST_MODEL (gtk_map_list_model_new (G_LIST_MODEL (treemodel), map_settings_to_keys, NULL, NULL));
-+ model = G_LIST_MODEL (gtk_flatten_list_model_new (model));
-+ model = G_LIST_MODEL (gtk_filter_list_model_new (model, g_object_ref (filter)));
-+ selection = gtk_no_selection_new (model);
-+
-+ gtk_list_view_set_model (GTK_LIST_VIEW (listview), GTK_SELECTION_MODEL (selection));
-+ g_object_unref (selection);
-+
-+ g_object_unref (builder);
-+ }
-+
-+ if (!gtk_widget_get_visible (window))
-+ gtk_widget_set_visible (window, TRUE);
-+ else
-+ gtk_window_destroy (GTK_WINDOW (window));
-+
-+ return window;
-+}
-diff --git a/demos/gtk-demo/listview_settings2.ui b/demos/gtk-demo/listview_settings2.ui
-new file mode 100644
-index 0000000000000000000000000000000000000000..0600f6358263f0e21f742fb6798e794c455a4117
---- /dev/null
-+++ b/demos/gtk-demo/listview_settings2.ui
-@@ -0,0 +1,130 @@
-+
-+
-+
-+
-+
-diff --git a/demos/gtk-demo/meson.build b/demos/gtk-demo/meson.build
-index c03beb39409d18db2e93801b7d6c6d5c4eced859..976c3ec997da08737259f478d72840f84abaa523 100644
---- a/demos/gtk-demo/meson.build
-+++ b/demos/gtk-demo/meson.build
-@@ -57,6 +57,7 @@ demos = files([
- 'listview_minesweeper.c',
- 'listview_selections.c',
- 'listview_settings.c',
-+ 'listview_settings2.c',
- 'listview_ucd.c',
- 'listview_weather.c',
- 'listview_words.c',
-@@ -131,6 +132,7 @@ extra_demo_sources = files([
- 'demo4widget.c',
- 'pixbufpaintable.c',
- 'script-names.c',
-+ 'settings-key.c',
- 'unicode-names.c',
- 'suggestionentry.c',
- 'language-names.c',
-diff --git a/demos/gtk-demo/settings-key.c b/demos/gtk-demo/settings-key.c
-new file mode 100644
-index 0000000000000000000000000000000000000000..13c0a5d559f35d8d8e9a91529008d563d8910b8e
---- /dev/null
-+++ b/demos/gtk-demo/settings-key.c
-@@ -0,0 +1,165 @@
-+#include "settings-key.h"
-+
-+/* Create an object that wraps GSettingsSchemaKey because that's a boxed type */
-+struct _SettingsKey
-+{
-+ GObject parent_instance;
-+
-+ GSettings *settings;
-+ GSettingsSchemaKey *key;
-+};
-+
-+enum {
-+ PROP_0,
-+ PROP_NAME,
-+ PROP_SETTINGS,
-+ PROP_SUMMARY,
-+ PROP_DESCRIPTION,
-+ PROP_VALUE,
-+ PROP_TYPE,
-+ PROP_DEFAULT_VALUE,
-+
-+ N_PROPS
-+};
-+
-+G_DEFINE_TYPE (SettingsKey, settings_key, G_TYPE_OBJECT);
-+static GParamSpec *properties[N_PROPS] = { NULL, };
-+
-+static void
-+settings_key_get_property (GObject *object,
-+ guint property_id,
-+ GValue *value,
-+ GParamSpec *pspec)
-+{
-+ SettingsKey *self = SETTINGS_KEY (object);
-+
-+ switch (property_id)
-+ {
-+ case PROP_DESCRIPTION:
-+ g_value_set_string (value, g_settings_schema_key_get_description (self->key));
-+ break;
-+
-+ case PROP_NAME:
-+ g_value_set_string (value, g_settings_schema_key_get_name (self->key));
-+ break;
-+
-+ case PROP_SUMMARY:
-+ g_value_set_string (value, g_settings_schema_key_get_summary (self->key));
-+ break;
-+
-+ case PROP_VALUE:
-+ {
-+ GVariant *variant = g_settings_get_value (self->settings, g_settings_schema_key_get_name (self->key));
-+ g_value_take_string (value, g_variant_print (variant, FALSE));
-+ g_variant_unref (variant);
-+ }
-+ break;
-+
-+ case PROP_TYPE:
-+ {
-+ const GVariantType *type = g_settings_schema_key_get_value_type (self->key);
-+ g_value_set_string (value, g_variant_type_peek_string (type));
-+ }
-+ break;
-+
-+ case PROP_DEFAULT_VALUE:
-+ {
-+ GVariant *variant = g_settings_schema_key_get_default_value (self->key);
-+ g_value_take_string (value, g_variant_print (variant, FALSE));
-+ g_variant_unref (variant);
-+ }
-+ break;
-+
-+ case PROP_SETTINGS:
-+ g_value_set_object (value, self->settings);
-+ break;
-+
-+ default:
-+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
-+ break;
-+ }
-+}
-+
-+static void
-+settings_key_finalize (GObject *object)
-+{
-+ SettingsKey *self = SETTINGS_KEY (object);
-+
-+ g_object_unref (self->settings);
-+ g_settings_schema_key_unref (self->key);
-+
-+ G_OBJECT_CLASS (settings_key_parent_class)->finalize (object);
-+}
-+
-+static void
-+settings_key_class_init (SettingsKeyClass *klass)
-+{
-+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
-+
-+ gobject_class->finalize = settings_key_finalize;
-+ gobject_class->get_property = settings_key_get_property;
-+
-+ properties[PROP_DESCRIPTION] =
-+ g_param_spec_string ("description", NULL, NULL, NULL, G_PARAM_READABLE);
-+ properties[PROP_NAME] =
-+ g_param_spec_string ("name", NULL, NULL, NULL, G_PARAM_READABLE);
-+ properties[PROP_SETTINGS] =
-+ g_param_spec_object ("settings", NULL, NULL, G_TYPE_SETTINGS, G_PARAM_READABLE);
-+ properties[PROP_SUMMARY] =
-+ g_param_spec_string ("summary", NULL, NULL, NULL, G_PARAM_READABLE);
-+ properties[PROP_VALUE] =
-+ g_param_spec_string ("value", NULL, NULL, NULL, G_PARAM_READABLE);
-+ properties[PROP_TYPE] =
-+ g_param_spec_string ("type", NULL, NULL, NULL, G_PARAM_READABLE);
-+ properties[PROP_DEFAULT_VALUE] =
-+ g_param_spec_string ("default-value", NULL, NULL, NULL, G_PARAM_READABLE);
-+
-+ g_object_class_install_properties (gobject_class, N_PROPS, properties);
-+}
-+
-+static void
-+settings_key_init (SettingsKey *self)
-+{
-+}
-+
-+SettingsKey *
-+settings_key_new (GSettings *settings,
-+ GSettingsSchemaKey *key)
-+{
-+ SettingsKey *result = g_object_new (SETTINGS_TYPE_KEY, NULL);
-+
-+ result->settings = g_object_ref (settings);
-+ result->key = g_settings_schema_key_ref (key);
-+
-+ return result;
-+}
-+
-+GSettingsSchemaKey *
-+settings_key_get_key (SettingsKey *self)
-+{
-+ return self->key;
-+}
-+
-+GSettings *
-+settings_key_get_settings (SettingsKey *self)
-+{
-+ return self->settings;
-+}
-+
-+char *
-+settings_key_get_search_string (SettingsKey *self)
-+{
-+ char *schema, *result;
-+
-+ g_object_get (self->settings, "schema-id", &schema, NULL);
-+
-+ result = g_strconcat (g_settings_schema_key_get_name (self->key), " ",
-+ g_settings_schema_key_get_summary (self->key), " ",
-+ schema,
-+ NULL);
-+
-+ g_free (schema);
-+
-+ return result;
-+}
-+
-diff --git a/demos/gtk-demo/settings-key.h b/demos/gtk-demo/settings-key.h
-new file mode 100644
-index 0000000000000000000000000000000000000000..5260a84516873f2e84cab6affb41f90784daf174
---- /dev/null
-+++ b/demos/gtk-demo/settings-key.h
-@@ -0,0 +1,17 @@
-+#pragma once
-+
-+#include
-+
-+#include
-+
-+/* Create an object that wraps GSettingsSchemaKey because that's a boxed type */
-+typedef struct _SettingsKey SettingsKey;
-+#define SETTINGS_TYPE_KEY (settings_key_get_type ())
-+G_DECLARE_FINAL_TYPE (SettingsKey, settings_key, SETTINGS, KEY, GObject);
-+
-+SettingsKey * settings_key_new (GSettings *settings,
-+ GSettingsSchemaKey *key);
-+
-+GSettingsSchemaKey * settings_key_get_key (SettingsKey *self);
-+GSettings * settings_key_get_settings (SettingsKey *self);
-+char * settings_key_get_search_string (SettingsKey *self);
-diff --git a/gtk/gtk.h b/gtk/gtk.h
-index a3973437f3e68df9b3fdc73d2c73a1b903a7b546..8ad4e38902a844c4ae367f29877cce627be921ed 100644
---- a/gtk/gtk.h
-+++ b/gtk/gtk.h
-@@ -174,6 +174,7 @@
- #include
- #include
- #include
-+#include
- #include
- #include
- #include
-@@ -225,6 +226,7 @@
- #include
- #include
- #include
-+#include
- #include
- #include
- #include
-diff --git a/gtk/gtkfilterlistmodel.c b/gtk/gtkfilterlistmodel.c
-index 1804b7d12a37d20519c62a926ded3b20a902914d..7e53e52c1190a02d96ac92fbc40710e7426acfa3 100644
---- a/gtk/gtkfilterlistmodel.c
-+++ b/gtk/gtkfilterlistmodel.c
-@@ -23,6 +23,7 @@
-
- #include "gtkbitset.h"
- #include "gtkprivate.h"
-+#include "gtksectionmodelprivate.h"
-
- /**
- * GtkFilterListModel:
-@@ -135,8 +136,67 @@ gtk_filter_list_model_model_init (GListModelInterface *iface)
- iface->get_item = gtk_filter_list_model_get_item;
- }
-
-+static void
-+gtk_filter_list_model_get_section (GtkSectionModel *model,
-+ guint position,
-+ guint *out_start,
-+ guint *out_end)
-+{
-+ GtkFilterListModel *self = GTK_FILTER_LIST_MODEL (model);
-+ guint n_items;
-+ guint pos, start, end;
-+
-+ switch (self->strictness)
-+ {
-+ case GTK_FILTER_MATCH_NONE:
-+ *out_start = 0;
-+ *out_end = G_MAXUINT;
-+ return;
-+
-+ case GTK_FILTER_MATCH_ALL:
-+ gtk_list_model_get_section (self->model, position, out_start, out_end);
-+ return;
-+
-+ case GTK_FILTER_MATCH_SOME:
-+ n_items = gtk_bitset_get_size (self->matches);
-+ if (position >= n_items)
-+ {
-+ *out_start = n_items;
-+ *out_end = G_MAXUINT;
-+ return;
-+ }
-+ if (!GTK_IS_SECTION_MODEL (self->model))
-+ {
-+ *out_start = 0;
-+ *out_end = n_items;
-+ return;
-+ }
-+ break;
-+
-+ default:
-+ g_assert_not_reached ();
-+ }
-+
-+ /* if we get here, we have a section model, and are MATCH_SOME */
-+
-+ pos = gtk_bitset_get_nth (self->matches, position);
-+ gtk_section_model_get_section (GTK_SECTION_MODEL (self->model), pos, &start, &end);
-+ if (start == 0)
-+ *out_start = 0;
-+ else
-+ *out_start = gtk_bitset_get_size_in_range (self->matches, 0, start - 1);
-+ *out_end = *out_start + gtk_bitset_get_size_in_range (self->matches, start, end - 1);
-+}
-+
-+static void
-+gtk_filter_list_model_section_model_init (GtkSectionModelInterface *iface)
-+{
-+ iface->get_section = gtk_filter_list_model_get_section;
-+}
-+
- G_DEFINE_TYPE_WITH_CODE (GtkFilterListModel, gtk_filter_list_model, G_TYPE_OBJECT,
-- G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_filter_list_model_model_init))
-+ G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_filter_list_model_model_init)
-+ G_IMPLEMENT_INTERFACE (GTK_TYPE_SECTION_MODEL, gtk_filter_list_model_section_model_init))
-
- static gboolean
- gtk_filter_list_model_run_filter_on_item (GtkFilterListModel *self,
-@@ -164,7 +224,7 @@ gtk_filter_list_model_run_filter (GtkFilterListModel *self,
- gboolean more;
-
- g_return_if_fail (GTK_IS_FILTER_LIST_MODEL (self));
--
-+
- if (self->pending == NULL)
- return;
-
-@@ -355,7 +415,7 @@ gtk_filter_list_model_set_property (GObject *object,
- }
- }
-
--static void
-+static void
- gtk_filter_list_model_get_property (GObject *object,
- guint prop_id,
- GValue *value,
-@@ -503,7 +563,7 @@ gtk_filter_list_model_refilter (GtkFilterListModel *self,
- case GTK_FILTER_MATCH_SOME:
- {
- GtkBitset *old, *pending;
--
-+
- if (self->matches == NULL)
- {
- if (self->strictness == GTK_FILTER_MATCH_ALL)
-diff --git a/gtk/gtkflattenlistmodel.c b/gtk/gtkflattenlistmodel.c
-index 04cf04c6fd0ee3a8694a8d5b85ab5a1fa6d9ec5d..f80e80ff6d5512fc1dcb1d39dc358039f2f2b8d4 100644
---- a/gtk/gtkflattenlistmodel.c
-+++ b/gtk/gtkflattenlistmodel.c
-@@ -21,8 +21,8 @@
-
- #include "gtkflattenlistmodel.h"
-
-+#include "gtksectionmodel.h"
- #include "gtkrbtreeprivate.h"
--#include "gtkprivate.h"
-
- /**
- * GtkFlattenListModel:
-@@ -200,8 +200,39 @@ gtk_flatten_list_model_model_init (GListModelInterface *iface)
- iface->get_item = gtk_flatten_list_model_get_item;
- }
-
-+static void
-+gtk_flatten_list_model_get_section (GtkSectionModel *model,
-+ guint position,
-+ guint *out_start,
-+ guint *out_end)
-+{
-+ GtkFlattenListModel *self = GTK_FLATTEN_LIST_MODEL (model);
-+ FlattenNode *node;
-+ guint model_pos;
-+
-+ node = gtk_flatten_list_model_get_nth (self->items, position, &model_pos);
-+ if (node == NULL)
-+ {
-+ *out_start = gtk_flatten_list_model_get_n_items (G_LIST_MODEL (self));
-+ *out_end = G_MAXUINT;
-+ return;
-+ }
-+
-+ *out_start = position - model_pos;
-+ *out_end = position - model_pos + g_list_model_get_n_items (node->model);
-+}
-+
-+static void
-+gtk_flatten_list_model_section_model_init (GtkSectionModelInterface *iface)
-+{
-+ iface->get_section = gtk_flatten_list_model_get_section;
-+}
-+
- G_DEFINE_TYPE_WITH_CODE (GtkFlattenListModel, gtk_flatten_list_model, G_TYPE_OBJECT,
-- G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_flatten_list_model_model_init))
-+ G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL,
-+ gtk_flatten_list_model_model_init)
-+ G_IMPLEMENT_INTERFACE (GTK_TYPE_SECTION_MODEL,
-+ gtk_flatten_list_model_section_model_init))
-
- static void
- gtk_flatten_list_model_items_changed_cb (GListModel *model,
-@@ -433,7 +464,7 @@ gtk_flatten_list_model_class_init (GtkFlattenListModelClass *class)
- properties[PROP_MODEL] =
- g_param_spec_object ("model", NULL, NULL,
- G_TYPE_LIST_MODEL,
-- GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
-+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
-
- /**
- * GtkFlattenListModel:n-items:
-diff --git a/gtk/gtkgridview.c b/gtk/gtkgridview.c
-index ead43a8c8bc5a8714bce880b69d60e0a7a2a0380..274f34c3c9502a4e954d549760d10f9c673abb8f 100644
---- a/gtk/gtkgridview.c
-+++ b/gtk/gtkgridview.c
-@@ -264,8 +264,7 @@ gtk_grid_view_is_inert (GtkGridView *self)
- GtkWidget *widget = GTK_WIDGET (self);
-
- return !gtk_widget_get_visible (widget) ||
-- gtk_widget_get_root (widget) == NULL ||
-- self->factory == NULL;
-+ gtk_widget_get_root (widget) == NULL;
- }
-
- static void
-@@ -886,7 +885,7 @@ gtk_grid_view_size_allocate (GtkWidget *widget,
- {
- GtkListTile *filler;
- tile = gtk_list_item_manager_get_last (self->item_manager);
-- filler = gtk_list_tile_split (self->item_manager, tile, tile->n_items);
-+ filler = gtk_list_tile_append_filler (self->item_manager, tile);
- gtk_list_tile_set_area_position (self->item_manager,
- filler,
- column_start (self, xspacing, i),
-@@ -1330,19 +1329,12 @@ void
- gtk_grid_view_set_factory (GtkGridView *self,
- GtkListItemFactory *factory)
- {
-- gboolean was_inert;
--
- g_return_if_fail (GTK_IS_GRID_VIEW (self));
- g_return_if_fail (factory == NULL || GTK_IS_LIST_ITEM_FACTORY (factory));
-
-- was_inert = gtk_grid_view_is_inert (self);
--
- if (!g_set_object (&self->factory, factory))
- return;
-
-- if (!was_inert || !gtk_grid_view_is_inert (self))
-- gtk_grid_view_update_factories (self);
--
- g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FACTORY]);
- }
-
-diff --git a/gtk/gtklistbase.c b/gtk/gtklistbase.c
-index 0a7dbb0b025d7080dd67783cddf409c31bda774c..00ac9c571d9b8dbcf2e8c67b9d402689df2941d9 100644
---- a/gtk/gtklistbase.c
-+++ b/gtk/gtklistbase.c
-@@ -1959,11 +1959,25 @@ gtk_list_base_split_func (GtkWidget *widget,
- }
-
- static GtkListItemBase *
--gtk_list_base_create_widget_func (GtkWidget *widget)
-+gtk_list_base_create_list_widget_func (GtkWidget *widget)
- {
- return GTK_LIST_BASE_GET_CLASS (widget)->create_list_widget (GTK_LIST_BASE (widget));
- }
-
-+static void
-+gtk_list_base_prepare_section_func (GtkWidget *widget,
-+ GtkListTile *tile,
-+ guint pos)
-+{
-+ GTK_LIST_BASE_GET_CLASS (widget)->prepare_section (GTK_LIST_BASE (widget), tile, pos);
-+}
-+
-+static GtkListHeaderBase *
-+gtk_list_base_create_header_widget_func (GtkWidget *widget)
-+{
-+ return GTK_LIST_BASE_GET_CLASS (widget)->create_header_widget (GTK_LIST_BASE (widget));
-+}
-+
- static void
- gtk_list_base_init_real (GtkListBase *self,
- GtkListBaseClass *g_class)
-@@ -1973,7 +1987,9 @@ gtk_list_base_init_real (GtkListBase *self,
-
- priv->item_manager = gtk_list_item_manager_new (GTK_WIDGET (self),
- gtk_list_base_split_func,
-- gtk_list_base_create_widget_func);
-+ gtk_list_base_create_list_widget_func,
-+ gtk_list_base_prepare_section_func,
-+ gtk_list_base_create_header_widget_func);
- priv->anchor = gtk_list_item_tracker_new (priv->item_manager);
- priv->anchor_side_along = GTK_PACK_START;
- priv->anchor_side_across = GTK_PACK_START;
-diff --git a/gtk/gtklistbaseprivate.h b/gtk/gtklistbaseprivate.h
-index 7aa596ffde423557ea924003305aeb65531f1b60..fad3872cdc09433de5e255a687a3c98fe58640bf 100644
---- a/gtk/gtklistbaseprivate.h
-+++ b/gtk/gtklistbaseprivate.h
-@@ -37,6 +37,10 @@ struct _GtkListBaseClass
- GtkListTile *tile,
- guint n_items);
- GtkListItemBase * (* create_list_widget) (GtkListBase *self);
-+ void (* prepare_section) (GtkListBase *self,
-+ GtkListTile *tile,
-+ guint position);
-+ GtkListHeaderBase * (* create_header_widget) (GtkListBase *self);
-
- gboolean (* get_allocation) (GtkListBase *self,
- guint pos,
-diff --git a/gtk/gtklistheader.c b/gtk/gtklistheader.c
-new file mode 100644
-index 0000000000000000000000000000000000000000..62cfd5b4c9b63cd3835c14a8298ae72ce8ccc509
---- /dev/null
-+++ b/gtk/gtklistheader.c
-@@ -0,0 +1,381 @@
-+/*
-+ * Copyright © 2023 Benjamin Otte
-+ *
-+ * This library is free software; you can redistribute it and/or
-+ * modify it under the terms of the GNU Lesser General Public
-+ * License as published by the Free Software Foundation; either
-+ * version 2.1 of the License, or (at your option) any later version.
-+ *
-+ * This library is distributed in the hope that it will be useful,
-+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
-+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-+ * Lesser General Public License for more details.
-+ *
-+ * You should have received a copy of the GNU Lesser General Public
-+ * License along with this library. If not, see .
-+ *
-+ * Authors: Benjamin Otte
-+ */
-+
-+#include "config.h"
-+
-+#include "gtklistheaderprivate.h"
-+
-+/**
-+ * GtkListHeader:
-+ *
-+ * `GtkListHeader` is used by list widgets to represent the headers they
-+ * display.
-+ *
-+ * The `GtkListHeader`s are managed just like [class@gtk.ListItem]s via
-+ * their factory, but provide a different set of properties suitable for
-+ * managing the header instead of individual items.
-+ *
-+ * Since: 4.12
-+ */
-+
-+enum
-+{
-+ PROP_0,
-+ PROP_CHILD,
-+ PROP_END,
-+ PROP_ITEM,
-+ PROP_N_ITEMS,
-+ PROP_START,
-+
-+ N_PROPS
-+};
-+
-+G_DEFINE_TYPE (GtkListHeader, gtk_list_header, G_TYPE_OBJECT)
-+
-+static GParamSpec *properties[N_PROPS] = { NULL, };
-+
-+static void
-+gtk_list_header_dispose (GObject *object)
-+{
-+ GtkListHeader *self = GTK_LIST_HEADER (object);
-+
-+ g_assert (self->owner == NULL); /* would hold a reference */
-+ g_clear_object (&self->child);
-+
-+ G_OBJECT_CLASS (gtk_list_header_parent_class)->dispose (object);
-+}
-+
-+static void
-+gtk_list_header_get_property (GObject *object,
-+ guint property_id,
-+ GValue *value,
-+ GParamSpec *pspec)
-+{
-+ GtkListHeader *self = GTK_LIST_HEADER (object);
-+
-+ switch (property_id)
-+ {
-+ case PROP_CHILD:
-+ g_value_set_object (value, self->child);
-+ break;
-+
-+ case PROP_END:
-+ if (self->owner)
-+ g_value_set_uint (value, gtk_list_header_base_get_end (GTK_LIST_HEADER_BASE (self->owner)));
-+ else
-+ g_value_set_uint (value, GTK_INVALID_LIST_POSITION);
-+ break;
-+
-+ case PROP_ITEM:
-+ if (self->owner)
-+ g_value_set_object (value, gtk_list_header_base_get_item (GTK_LIST_HEADER_BASE (self->owner)));
-+ break;
-+
-+ case PROP_N_ITEMS:
-+ g_value_set_uint (value, gtk_list_header_get_n_items (self));
-+ break;
-+
-+ case PROP_START:
-+ if (self->owner)
-+ g_value_set_uint (value, gtk_list_header_base_get_start (GTK_LIST_HEADER_BASE (self->owner)));
-+ else
-+ g_value_set_uint (value, GTK_INVALID_LIST_POSITION);
-+ break;
-+
-+ default:
-+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
-+ break;
-+ }
-+}
-+
-+static void
-+gtk_list_header_set_property (GObject *object,
-+ guint property_id,
-+ const GValue *value,
-+ GParamSpec *pspec)
-+{
-+ GtkListHeader *self = GTK_LIST_HEADER (object);
-+
-+ switch (property_id)
-+ {
-+ case PROP_CHILD:
-+ gtk_list_header_set_child (self, g_value_get_object (value));
-+ break;
-+
-+ default:
-+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
-+ break;
-+ }
-+}
-+
-+static void
-+gtk_list_header_class_init (GtkListHeaderClass *klass)
-+{
-+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
-+
-+ gobject_class->dispose = gtk_list_header_dispose;
-+ gobject_class->get_property = gtk_list_header_get_property;
-+ gobject_class->set_property = gtk_list_header_set_property;
-+
-+ /**
-+ * GtkListHeader:child: (attributes org.gtk.Property.get=gtk_list_header_get_child org.gtk.Property.set=gtk_list_header_set_child)
-+ *
-+ * Widget used for display.
-+ *
-+ * Since: 4.12
-+ */
-+ properties[PROP_CHILD] =
-+ g_param_spec_object ("child", NULL, NULL,
-+ GTK_TYPE_WIDGET,
-+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
-+
-+ /**
-+ * GtkListHeader:end: (attributes org.gtk.Property.get=gtk_list_header_get_end)
-+ *
-+ * The first position no longer part of this section.
-+ *
-+ * Since: 4.12
-+ */
-+ properties[PROP_END] =
-+ g_param_spec_uint ("end", NULL, NULL,
-+ 0, G_MAXUINT, GTK_INVALID_LIST_POSITION,
-+ G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
-+
-+ /**
-+ * GtkListHeader:item: (attributes org.gtk.Property.get=gtk_list_header_get_item)
-+ *
-+ * The item at the start of the section.
-+ *
-+ * Since: 4.12
-+ */
-+ properties[PROP_ITEM] =
-+ g_param_spec_object ("item", NULL, NULL,
-+ G_TYPE_OBJECT,
-+ G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
-+
-+ /**
-+ * GtkListHeader:n-items: (attributes org.gtk.Property.get=gtk_list_header_get_n_items)
-+ *
-+ * Number of items in this section.
-+ *
-+ * Since: 4.12
-+ */
-+ properties[PROP_N_ITEMS] =
-+ g_param_spec_uint ("n-items", NULL, NULL,
-+ 0, G_MAXUINT, 0,
-+ G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
-+
-+ /**
-+ * GtkListHeader:start: (attributes org.gtk.Property.get=gtk_list_header_get_start)
-+ *
-+ * First position of items in this section.
-+ *
-+ * Since: 4.12
-+ */
-+ properties[PROP_START] =
-+ g_param_spec_uint ("start", NULL, NULL,
-+ 0, G_MAXUINT, GTK_INVALID_LIST_POSITION,
-+ G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
-+
-+ g_object_class_install_properties (gobject_class, N_PROPS, properties);
-+}
-+
-+static void
-+gtk_list_header_init (GtkListHeader *self)
-+{
-+}
-+
-+GtkListHeader *
-+gtk_list_header_new (void)
-+{
-+ return g_object_new (GTK_TYPE_LIST_HEADER, NULL);
-+}
-+
-+void
-+gtk_list_header_do_notify (GtkListHeader *list_header,
-+ gboolean notify_item,
-+ gboolean notify_start,
-+ gboolean notify_end,
-+ gboolean notify_n_items)
-+{
-+ GObject *object = G_OBJECT (list_header);
-+
-+ if (notify_item)
-+ g_object_notify_by_pspec (object, properties[PROP_ITEM]);
-+ if (notify_start)
-+ g_object_notify_by_pspec (object, properties[PROP_START]);
-+ if (notify_end)
-+ g_object_notify_by_pspec (object, properties[PROP_END]);
-+ if (notify_n_items)
-+ g_object_notify_by_pspec (object, properties[PROP_N_ITEMS]);
-+}
-+
-+/**
-+ * gtk_list_header_get_item: (attributes org.gtk.Method.get_property=item)
-+ * @self: a `GtkListHeader`
-+ *
-+ * Gets the model item at the start of the section.
-+ * This is the item that occupies the list model at position
-+ * [property@Gtk.ListHeader:start].
-+ *
-+ * If @self is unbound, this function returns %NULL.
-+ *
-+ * Returns: (nullable) (transfer none) (type GObject): The item displayed
-+ *
-+ * Since: 4.12
-+ **/
-+gpointer
-+gtk_list_header_get_item (GtkListHeader *self)
-+{
-+ g_return_val_if_fail (GTK_IS_LIST_HEADER (self), NULL);
-+
-+ if (self->owner)
-+ return gtk_list_header_base_get_item (GTK_LIST_HEADER_BASE (self->owner));
-+ else
-+ return NULL;
-+}
-+
-+/**
-+ * gtk_list_header_get_child: (attributes org.gtk.Method.get_property=child)
-+ * @self: a `GtkListHeader`
-+ *
-+ * Gets the child previously set via gtk_list_header_set_child() or
-+ * %NULL if none was set.
-+ *
-+ * Returns: (transfer none) (nullable): The child
-+ *
-+ * Since: 4.12
-+ */
-+GtkWidget *
-+gtk_list_header_get_child (GtkListHeader *self)
-+{
-+ g_return_val_if_fail (GTK_IS_LIST_HEADER (self), NULL);
-+
-+ return self->child;
-+}
-+
-+/**
-+ * gtk_list_header_set_child: (attributes org.gtk.Method.set_property=child)
-+ * @self: a `GtkListHeader`
-+ * @child: (nullable): The list item's child or %NULL to unset
-+ *
-+ * Sets the child to be used for this listitem.
-+ *
-+ * This function is typically called by applications when
-+ * setting up a header so that the widget can be reused when
-+ * binding it multiple times.
-+ *
-+ * Since: 4.12
-+ */
-+void
-+gtk_list_header_set_child (GtkListHeader *self,
-+ GtkWidget *child)
-+{
-+ g_return_if_fail (GTK_IS_LIST_HEADER (self));
-+ g_return_if_fail (child == NULL || gtk_widget_get_parent (child) == NULL);
-+
-+ if (self->child == child)
-+ return;
-+
-+ g_clear_object (&self->child);
-+
-+ if (child)
-+ {
-+ g_object_ref_sink (child);
-+ self->child = child;
-+ }
-+
-+ if (self->owner)
-+ gtk_list_header_widget_set_child (self->owner, child);
-+
-+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CHILD]);
-+}
-+
-+/**
-+ * gtk_list_header_get_start: (attributes org.gtk.Method.get_property=start)
-+ * @self: a `GtkListHeader`
-+ *
-+ * Gets the start position in the model of the section that @self is
-+ * currently the header for.
-+ *
-+ * If @self is unbound, %GTK_INVALID_LIST_POSITION is returned.
-+ *
-+ * Returns: The start position of the section
-+ *
-+ * Since: 4.12
-+ */
-+guint
-+gtk_list_header_get_start (GtkListHeader *self)
-+{
-+ g_return_val_if_fail (GTK_IS_LIST_HEADER (self), GTK_INVALID_LIST_POSITION);
-+
-+ if (self->owner)
-+ return gtk_list_header_base_get_start (GTK_LIST_HEADER_BASE (self->owner));
-+ else
-+ return GTK_INVALID_LIST_POSITION;
-+}
-+
-+/**
-+ * gtk_list_header_get_end: (attributes org.gtk.Method.get_property=end)
-+ * @self: a `GtkListHeader`
-+ *
-+ * Gets the end position in the model of the section that @self is
-+ * currently the header for.
-+ *
-+ * If @self is unbound, %GTK_INVALID_LIST_POSITION is returned.
-+ *
-+ * Returns: The end position of the section
-+ *
-+ * Since: 4.12
-+ */
-+guint
-+gtk_list_header_get_end (GtkListHeader *self)
-+{
-+ g_return_val_if_fail (GTK_IS_LIST_HEADER (self), GTK_INVALID_LIST_POSITION);
-+
-+ if (self->owner)
-+ return gtk_list_header_base_get_end (GTK_LIST_HEADER_BASE (self->owner));
-+ else
-+ return GTK_INVALID_LIST_POSITION;
-+}
-+
-+/**
-+ * gtk_list_header_get_n_items: (attributes org.gtk.Method.get_property=n-items)
-+ * @self: a `GtkListHeader`
-+ *
-+ * Gets the the number of items in the section.
-+ *
-+ * If @self is unbound, 0 is returned.
-+ *
-+ * Returns: The number of items in the section
-+ *
-+ * Since: 4.12
-+ */
-+guint
-+gtk_list_header_get_n_items (GtkListHeader *self)
-+{
-+ g_return_val_if_fail (GTK_IS_LIST_HEADER (self), GTK_INVALID_LIST_POSITION);
-+
-+ if (self->owner)
-+ return gtk_list_header_base_get_end (GTK_LIST_HEADER_BASE (self->owner)) -
-+ gtk_list_header_base_get_start (GTK_LIST_HEADER_BASE (self->owner));
-+ else
-+ return 0;
-+}
-+
-diff --git a/gtk/gtklistheader.h b/gtk/gtklistheader.h
-new file mode 100644
-index 0000000000000000000000000000000000000000..87862bab5967eff0af92996dd20b79fe1edcd5cc
---- /dev/null
-+++ b/gtk/gtklistheader.h
-@@ -0,0 +1,50 @@
-+/*
-+ * Copyright © 2023 Benjamin Otte
-+ *
-+ * This library is free software; you can redistribute it and/or
-+ * modify it under the terms of the GNU Lesser General Public
-+ * License as published by the Free Software Foundation; either
-+ * version 2.1 of the License, or (at your option) any later version.
-+ *
-+ * This library is distributed in the hope that it will be useful,
-+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
-+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-+ * Lesser General Public License for more details.
-+ *
-+ * You should have received a copy of the GNU Lesser General Public
-+ * License along with this library. If not, see .
-+ *
-+ * Authors: Benjamin Otte
-+ */
-+
-+#pragma once
-+
-+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
-+#error "Only can be included directly."
-+#endif
-+
-+#include
-+
-+G_BEGIN_DECLS
-+
-+#define GTK_TYPE_LIST_HEADER (gtk_list_header_get_type ())
-+GDK_AVAILABLE_IN_4_12
-+GDK_DECLARE_INTERNAL_TYPE (GtkListHeader, gtk_list_header, GTK, LIST_HEADER, GObject)
-+
-+GDK_AVAILABLE_IN_4_12
-+gpointer gtk_list_header_get_item (GtkListHeader *self);
-+GDK_AVAILABLE_IN_4_12
-+guint gtk_list_header_get_start (GtkListHeader *self) G_GNUC_PURE;
-+GDK_AVAILABLE_IN_4_12
-+guint gtk_list_header_get_end (GtkListHeader *self) G_GNUC_PURE;
-+GDK_AVAILABLE_IN_4_12
-+guint gtk_list_header_get_n_items (GtkListHeader *self) G_GNUC_PURE;
-+
-+GDK_AVAILABLE_IN_4_12
-+void gtk_list_header_set_child (GtkListHeader *self,
-+ GtkWidget *child);
-+GDK_AVAILABLE_IN_4_12
-+GtkWidget * gtk_list_header_get_child (GtkListHeader *self);
-+
-+G_END_DECLS
-+
-diff --git a/gtk/gtklistheaderbase.c b/gtk/gtklistheaderbase.c
-new file mode 100644
-index 0000000000000000000000000000000000000000..a2b5b72e1e9cd1d4173c421a28898cbb0fbf09cf
---- /dev/null
-+++ b/gtk/gtklistheaderbase.c
-@@ -0,0 +1,112 @@
-+/*
-+ * Copyright © 2023 Benjamin Otte
-+ *
-+ * This library is free software; you can redistribute it and/or
-+ * modify it under the terms of the GNU Lesser General Public
-+ * License as published by the Free Software Foundation; either
-+ * version 2.1 of the License, or (at your option) any later version.
-+ *
-+ * This library is distributed in the hope that it will be useful,
-+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
-+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-+ * Lesser General Public License for more details.
-+ *
-+ * You should have received a copy of the GNU Lesser General Public
-+ * License along with this library. If not, see .
-+ *
-+ * Authors: Benjamin Otte
-+ */
-+
-+#include "config.h"
-+
-+#include "gtklistheaderbaseprivate.h"
-+
-+typedef struct _GtkListHeaderBasePrivate GtkListHeaderBasePrivate;
-+struct _GtkListHeaderBasePrivate
-+{
-+ GObject *item;
-+ guint start;
-+ guint end;
-+};
-+
-+G_DEFINE_TYPE_WITH_PRIVATE (GtkListHeaderBase, gtk_list_header_base, GTK_TYPE_WIDGET)
-+
-+static void
-+gtk_list_header_base_default_update (GtkListHeaderBase *self,
-+ gpointer item,
-+ guint start,
-+ guint end)
-+{
-+ GtkListHeaderBasePrivate *priv = gtk_list_header_base_get_instance_private (self);
-+
-+ g_set_object (&priv->item, item);
-+ priv->start = start;
-+ priv->end = end;
-+}
-+
-+static void
-+gtk_list_header_base_dispose (GObject *object)
-+{
-+ GtkListHeaderBase *self = GTK_LIST_HEADER_BASE (object);
-+ GtkListHeaderBasePrivate *priv = gtk_list_header_base_get_instance_private (self);
-+
-+ g_clear_object (&priv->item);
-+
-+ G_OBJECT_CLASS (gtk_list_header_base_parent_class)->dispose (object);
-+}
-+
-+static void
-+gtk_list_header_base_class_init (GtkListHeaderBaseClass *klass)
-+{
-+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
-+
-+ klass->update = gtk_list_header_base_default_update;
-+
-+ gobject_class->dispose = gtk_list_header_base_dispose;
-+}
-+
-+static void
-+gtk_list_header_base_init (GtkListHeaderBase *self)
-+{
-+}
-+
-+void
-+gtk_list_header_base_update (GtkListHeaderBase *self,
-+ gpointer item,
-+ guint start,
-+ guint end)
-+{
-+ GtkListHeaderBasePrivate *priv = gtk_list_header_base_get_instance_private (self);
-+
-+ if (priv->item == item &&
-+ priv->start == start &&
-+ priv->end == end)
-+ return;
-+
-+ GTK_LIST_HEADER_BASE_GET_CLASS (self)->update (self, item, start, end);
-+}
-+
-+guint
-+gtk_list_header_base_get_start (GtkListHeaderBase *self)
-+{
-+ GtkListHeaderBasePrivate *priv = gtk_list_header_base_get_instance_private (self);
-+
-+ return priv->start;
-+}
-+
-+guint
-+gtk_list_header_base_get_end (GtkListHeaderBase *self)
-+{
-+ GtkListHeaderBasePrivate *priv = gtk_list_header_base_get_instance_private (self);
-+
-+ return priv->end;
-+}
-+
-+gpointer
-+gtk_list_header_base_get_item (GtkListHeaderBase *self)
-+{
-+ GtkListHeaderBasePrivate *priv = gtk_list_header_base_get_instance_private (self);
-+
-+ return priv->item;
-+}
-+
-diff --git a/gtk/gtklistheaderbaseprivate.h b/gtk/gtklistheaderbaseprivate.h
-new file mode 100644
-index 0000000000000000000000000000000000000000..fa3fa45f22dce2d9472be38db834f64ee98cbb83
---- /dev/null
-+++ b/gtk/gtklistheaderbaseprivate.h
-@@ -0,0 +1,63 @@
-+/*
-+ * Copyright © 2023 Benjamin Otte
-+ *
-+ * This library is free software; you can redistribute it and/or
-+ * modify it under the terms of the GNU Lesser General Public
-+ * License as published by the Free Software Foundation; either
-+ * version 2.1 of the License, or (at your option) any later version.
-+ *
-+ * This library is distributed in the hope that it will be useful,
-+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
-+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-+ * Lesser General Public License for more details.
-+ *
-+ * You should have received a copy of the GNU Lesser General Public
-+ * License along with this library. If not, see .
-+ *
-+ * Authors: Benjamin Otte
-+ */
-+
-+#pragma once
-+
-+#include "gtkwidget.h"
-+
-+G_BEGIN_DECLS
-+
-+#define GTK_TYPE_LIST_HEADER_BASE (gtk_list_header_base_get_type ())
-+#define GTK_LIST_HEADER_BASE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTK_TYPE_LIST_HEADER_BASE, GtkListHeaderBase))
-+#define GTK_LIST_HEADER_BASE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTK_TYPE_LIST_HEADER_BASE, GtkListHeaderBaseClass))
-+#define GTK_IS_LIST_HEADER_BASE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTK_TYPE_LIST_HEADER_BASE))
-+#define GTK_IS_LIST_HEADER_BASE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTK_TYPE_LIST_HEADER_BASE))
-+#define GTK_LIST_HEADER_BASE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GTK_TYPE_LIST_HEADER_BASE, GtkListHeaderBaseClass))
-+
-+typedef struct _GtkListHeaderBase GtkListHeaderBase;
-+typedef struct _GtkListHeaderBaseClass GtkListHeaderBaseClass;
-+
-+struct _GtkListHeaderBase
-+{
-+ GtkWidget parent_instance;
-+};
-+
-+struct _GtkListHeaderBaseClass
-+{
-+ GtkWidgetClass parent_class;
-+
-+ void (* update) (GtkListHeaderBase *self,
-+ gpointer item,
-+ guint start,
-+ guint end);
-+};
-+
-+GType gtk_list_header_base_get_type (void) G_GNUC_CONST;
-+
-+void gtk_list_header_base_update (GtkListHeaderBase *self,
-+ gpointer item,
-+ guint start,
-+ guint end);
-+
-+guint gtk_list_header_base_get_start (GtkListHeaderBase *self);
-+guint gtk_list_header_base_get_end (GtkListHeaderBase *self);
-+gpointer gtk_list_header_base_get_item (GtkListHeaderBase *self);
-+
-+G_END_DECLS
-+
-diff --git a/gtk/gtklistheaderprivate.h b/gtk/gtklistheaderprivate.h
-new file mode 100644
-index 0000000000000000000000000000000000000000..81aa850c8af87f402012a5e0f56a43b8f4e95518
---- /dev/null
-+++ b/gtk/gtklistheaderprivate.h
-@@ -0,0 +1,52 @@
-+/*
-+ * Copyright © 2023 Benjamin Otte
-+ *
-+ * This library is free software; you can redistribute it and/or
-+ * modify it under the terms of the GNU Lesser General Public
-+ * License as published by the Free Software Foundation; either
-+ * version 2.1 of the License, or (at your option) any later version.
-+ *
-+ * This library is distributed in the hope that it will be useful,
-+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
-+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-+ * Lesser General Public License for more details.
-+ *
-+ * You should have received a copy of the GNU Lesser General Public
-+ * License along with this library. If not, see .
-+ *
-+ * Authors: Benjamin Otte
-+ */
-+
-+#pragma once
-+
-+#include "gtklistheader.h"
-+
-+#include "gtklistheaderwidgetprivate.h"
-+
-+G_BEGIN_DECLS
-+
-+struct _GtkListHeader
-+{
-+ GObject parent_instance;
-+
-+ GtkListHeaderWidget *owner; /* has a reference */
-+
-+ GtkWidget *child;
-+};
-+
-+struct _GtkListHeaderClass
-+{
-+ GObjectClass parent_class;
-+};
-+
-+GtkListHeader * gtk_list_header_new (void);
-+
-+void gtk_list_header_do_notify (GtkListHeader *list_header,
-+ gboolean notify_item,
-+ gboolean notify_start,
-+ gboolean notify_end,
-+ gboolean notify_n_items);
-+
-+
-+G_END_DECLS
-+
-diff --git a/gtk/gtklistheaderwidget.c b/gtk/gtklistheaderwidget.c
-new file mode 100644
-index 0000000000000000000000000000000000000000..5a1a2559cedb3b669f3625ec57db83a49a630c56
---- /dev/null
-+++ b/gtk/gtklistheaderwidget.c
-@@ -0,0 +1,295 @@
-+/*
-+ * Copyright © 2023 Benjamin Otte
-+ *
-+ * This library is free software; you can redistribute it and/or
-+ * modify it under the terms of the GNU Lesser General Public
-+ * License as published by the Free Software Foundation; either
-+ * version 2.1 of the License, or (at your option) any later version.
-+ *
-+ * This library is distributed in the hope that it will be useful,
-+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
-+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-+ * Lesser General Public License for more details.
-+ *
-+ * You should have received a copy of the GNU Lesser General Public
-+ * License along with this library. If not, see .
-+ *
-+ * Authors: Benjamin Otte
-+ */
-+
-+#include "config.h"
-+
-+#include "gtklistheaderwidgetprivate.h"
-+
-+#include "gtkbinlayout.h"
-+#include "gtklistheaderprivate.h"
-+#include "gtklistitemfactoryprivate.h"
-+#include "gtklistbaseprivate.h"
-+#include "gtkwidget.h"
-+
-+typedef struct _GtkListHeaderWidgetPrivate GtkListHeaderWidgetPrivate;
-+struct _GtkListHeaderWidgetPrivate
-+{
-+ GtkListItemFactory *factory;
-+
-+ GtkListHeader *header;
-+};
-+
-+enum {
-+ PROP_0,
-+ PROP_FACTORY,
-+
-+ N_PROPS
-+};
-+
-+G_DEFINE_TYPE_WITH_PRIVATE (GtkListHeaderWidget, gtk_list_header_widget, GTK_TYPE_LIST_HEADER_BASE)
-+
-+static GParamSpec *properties[N_PROPS] = { NULL, };
-+
-+static void
-+gtk_list_header_widget_setup_func (gpointer object,
-+ gpointer data)
-+{
-+ GtkListHeaderWidget *self = GTK_LIST_HEADER_WIDGET (data);
-+ GtkListHeaderWidgetPrivate *priv = gtk_list_header_widget_get_instance_private (self);
-+ GtkListHeader *header = object;
-+
-+ priv->header = header;
-+ header->owner = self;
-+
-+ gtk_list_header_widget_set_child (self, header->child);
-+
-+ gtk_list_header_do_notify (header,
-+ gtk_list_header_base_get_item (GTK_LIST_HEADER_BASE (self)) != NULL,
-+ gtk_list_header_base_get_start (GTK_LIST_HEADER_BASE (self)) != GTK_INVALID_LIST_POSITION,
-+ gtk_list_header_base_get_end (GTK_LIST_HEADER_BASE (self)) != GTK_INVALID_LIST_POSITION,
-+ gtk_list_header_base_get_start (GTK_LIST_HEADER_BASE (self)) != gtk_list_header_base_get_end (GTK_LIST_HEADER_BASE (self)));
-+}
-+
-+static void
-+gtk_list_header_widget_setup_factory (GtkListHeaderWidget *self)
-+{
-+ GtkListHeaderWidgetPrivate *priv = gtk_list_header_widget_get_instance_private (self);
-+ GtkListHeader *header;
-+
-+ header = gtk_list_header_new ();
-+
-+ gtk_list_item_factory_setup (priv->factory,
-+ G_OBJECT (header),
-+ gtk_list_header_base_get_item (GTK_LIST_HEADER_BASE (self)) != NULL,
-+ gtk_list_header_widget_setup_func,
-+ self);
-+
-+ g_assert (priv->header == header);
-+}
-+
-+static void
-+gtk_list_header_widget_teardown_func (gpointer object,
-+ gpointer data)
-+{
-+ GtkListHeaderWidget *self = GTK_LIST_HEADER_WIDGET (data);
-+ GtkListHeaderWidgetPrivate *priv = gtk_list_header_widget_get_instance_private (self);
-+ GtkListHeader *header = object;
-+
-+ header->owner = NULL;
-+ priv->header = NULL;
-+
-+ gtk_list_header_widget_set_child (self, NULL);
-+
-+ gtk_list_header_do_notify (header,
-+ gtk_list_header_base_get_item (GTK_LIST_HEADER_BASE (self)) != NULL,
-+ gtk_list_header_base_get_start (GTK_LIST_HEADER_BASE (self)) != GTK_INVALID_LIST_POSITION,
-+ gtk_list_header_base_get_end (GTK_LIST_HEADER_BASE (self)) != GTK_INVALID_LIST_POSITION,
-+ gtk_list_header_base_get_start (GTK_LIST_HEADER_BASE (self)) != gtk_list_header_base_get_end (GTK_LIST_HEADER_BASE (self)));
-+}
-+
-+static void
-+gtk_list_header_widget_teardown_factory (GtkListHeaderWidget *self)
-+{
-+ GtkListHeaderWidgetPrivate *priv = gtk_list_header_widget_get_instance_private (self);
-+ gpointer header = priv->header;
-+
-+ gtk_list_item_factory_teardown (priv->factory,
-+ header,
-+ gtk_list_header_base_get_item (GTK_LIST_HEADER_BASE (self)) != NULL,
-+ gtk_list_header_widget_teardown_func,
-+ self);
-+
-+ g_assert (priv->header == NULL);
-+ g_object_unref (header);
-+}
-+
-+typedef struct {
-+ GtkListHeaderWidget *widget;
-+ gpointer item;
-+ guint start;
-+ guint end;
-+} GtkListHeaderWidgetUpdate;
-+
-+static void
-+gtk_list_header_widget_update_func (gpointer object,
-+ gpointer data)
-+{
-+ GtkListHeaderWidgetUpdate *update = data;
-+ GtkListHeaderWidget *self = update->widget;
-+ GtkListHeaderBase *base = GTK_LIST_HEADER_BASE (self);
-+ /* Track notify manually instead of freeze/thaw_notify for performance reasons. */
-+ gboolean notify_item, notify_start, notify_end, notify_n_items;
-+
-+ /* FIXME: It's kinda evil to notify external objects from here... */
-+ notify_item = gtk_list_header_base_get_item (base) != update->item;
-+ notify_start = gtk_list_header_base_get_start (base) != update->start;
-+ notify_end = gtk_list_header_base_get_end (base) != update->end;
-+ notify_n_items = gtk_list_header_base_get_end (base) - gtk_list_header_base_get_start (base) != update->end - update->start;
-+
-+ GTK_LIST_HEADER_BASE_CLASS (gtk_list_header_widget_parent_class)->update (base,
-+ update->item,
-+ update->start,
-+ update->end);
-+
-+ if (object)
-+ gtk_list_header_do_notify (object, notify_item, notify_start, notify_end, notify_n_items);
-+}
-+
-+static void
-+gtk_list_header_widget_update (GtkListHeaderBase *base,
-+ gpointer item,
-+ guint start,
-+ guint end)
-+{
-+ GtkListHeaderWidget *self = GTK_LIST_HEADER_WIDGET (base);
-+ GtkListHeaderWidgetPrivate *priv = gtk_list_header_widget_get_instance_private (self);
-+ GtkListHeaderWidgetUpdate update = { self, item, start, end };
-+
-+ if (priv->header)
-+ {
-+ gtk_list_item_factory_update (priv->factory,
-+ G_OBJECT (priv->header),
-+ gtk_list_header_base_get_item (GTK_LIST_HEADER_BASE (self)) != NULL,
-+ item != NULL,
-+ gtk_list_header_widget_update_func,
-+ &update);
-+ }
-+ else
-+ {
-+ gtk_list_header_widget_update_func (NULL, &update);
-+ }
-+}
-+
-+static void
-+gtk_list_header_widget_set_property (GObject *object,
-+ guint property_id,
-+ const GValue *value,
-+ GParamSpec *pspec)
-+{
-+ GtkListHeaderWidget *self = GTK_LIST_HEADER_WIDGET (object);
-+
-+ switch (property_id)
-+ {
-+ case PROP_FACTORY:
-+ gtk_list_header_widget_set_factory (self, g_value_get_object (value));
-+ break;
-+
-+ default:
-+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
-+ break;
-+ }
-+}
-+
-+static void
-+gtk_list_header_widget_clear_factory (GtkListHeaderWidget *self)
-+{
-+ GtkListHeaderWidgetPrivate *priv = gtk_list_header_widget_get_instance_private (self);
-+
-+ if (priv->factory == NULL)
-+ return;
-+
-+ if (priv->header)
-+ gtk_list_header_widget_teardown_factory (self);
-+
-+ g_clear_object (&priv->factory);
-+}
-+
-+static void
-+gtk_list_header_widget_dispose (GObject *object)
-+{
-+ GtkListHeaderWidget *self = GTK_LIST_HEADER_WIDGET (object);
-+
-+ gtk_list_header_widget_clear_factory (self);
-+
-+ G_OBJECT_CLASS (gtk_list_header_widget_parent_class)->dispose (object);
-+}
-+
-+static void
-+gtk_list_header_widget_class_init (GtkListHeaderWidgetClass *klass)
-+{
-+ GtkListHeaderBaseClass *base_class = GTK_LIST_HEADER_BASE_CLASS (klass);
-+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
-+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
-+
-+ base_class->update = gtk_list_header_widget_update;
-+
-+ gobject_class->set_property = gtk_list_header_widget_set_property;
-+ gobject_class->dispose = gtk_list_header_widget_dispose;
-+
-+ properties[PROP_FACTORY] =
-+ g_param_spec_object ("factory", NULL, NULL,
-+ GTK_TYPE_LIST_ITEM_FACTORY,
-+ G_PARAM_WRITABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
-+
-+ g_object_class_install_properties (gobject_class, N_PROPS, properties);
-+
-+ gtk_widget_class_set_css_name (widget_class, I_("header"));
-+ gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_ROW_HEADER);
-+ gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
-+}
-+
-+static void
-+gtk_list_header_widget_init (GtkListHeaderWidget *self)
-+{
-+}
-+
-+void
-+gtk_list_header_widget_set_factory (GtkListHeaderWidget *self,
-+ GtkListItemFactory *factory)
-+{
-+ GtkListHeaderWidgetPrivate *priv = gtk_list_header_widget_get_instance_private (self);
-+
-+ if (priv->factory == factory)
-+ return;
-+
-+ gtk_list_header_widget_clear_factory (self);
-+
-+ if (factory)
-+ {
-+ priv->factory = g_object_ref (factory);
-+
-+ gtk_list_header_widget_setup_factory (self);
-+ }
-+
-+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FACTORY]);
-+}
-+
-+GtkWidget *
-+gtk_list_header_widget_new (GtkListItemFactory *factory)
-+{
-+ return g_object_new (GTK_TYPE_LIST_HEADER_WIDGET,
-+ "factory", factory,
-+ NULL);
-+}
-+
-+void
-+gtk_list_header_widget_set_child (GtkListHeaderWidget *self,
-+ GtkWidget *child)
-+{
-+ GtkWidget *cur_child = gtk_widget_get_first_child (GTK_WIDGET (self));
-+
-+ if (cur_child == child)
-+ return;
-+
-+ g_clear_pointer (&cur_child, gtk_widget_unparent);
-+
-+ if (child)
-+ gtk_widget_set_parent (child, GTK_WIDGET (self));
-+}
-+
-diff --git a/gtk/gtklistheaderwidgetprivate.h b/gtk/gtklistheaderwidgetprivate.h
-new file mode 100644
-index 0000000000000000000000000000000000000000..54f3c666d8f017f5ebf405b7492f93d16a36a6a9
---- /dev/null
-+++ b/gtk/gtklistheaderwidgetprivate.h
-@@ -0,0 +1,61 @@
-+/*
-+ * Copyright © 2023 Benjamin Otte
-+ *
-+ * This library is free software; you can redistribute it and/or
-+ * modify it under the terms of the GNU Lesser General Public
-+ * License as published by the Free Software Foundation; either
-+ * version 2.1 of the License, or (at your option) any later version.
-+ *
-+ * This library is distributed in the hope that it will be useful,
-+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
-+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-+ * Lesser General Public License for more details.
-+ *
-+ * You should have received a copy of the GNU Lesser General Public
-+ * License along with this library. If not, see .
-+ *
-+ * Authors: Benjamin Otte
-+ */
-+
-+#pragma once
-+
-+#include "gtklistheaderbaseprivate.h"
-+
-+#include "gtklistitemfactory.h"
-+
-+G_BEGIN_DECLS
-+
-+#define GTK_TYPE_LIST_HEADER_WIDGET (gtk_list_header_widget_get_type ())
-+#define GTK_LIST_HEADER_WIDGET(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTK_TYPE_LIST_HEADER_WIDGET, GtkListHeaderWidget))
-+#define GTK_LIST_HEADER_WIDGET_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTK_TYPE_LIST_HEADER_WIDGET, GtkListHeaderWidgetClass))
-+#define GTK_IS_LIST_HEADER_WIDGET(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTK_TYPE_LIST_HEADER_WIDGET))
-+#define GTK_IS_LIST_HEADER_WIDGET_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTK_TYPE_LIST_HEADER_WIDGET))
-+#define GTK_LIST_HEADER_WIDGET_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GTK_TYPE_LIST_HEADER_WIDGET, GtkListHeaderWidgetClass))
-+
-+typedef struct _GtkListHeaderWidget GtkListHeaderWidget;
-+typedef struct _GtkListHeaderWidgetClass GtkListHeaderWidgetClass;
-+
-+struct _GtkListHeaderWidget
-+{
-+ GtkListHeaderBase parent_instance;
-+};
-+
-+struct _GtkListHeaderWidgetClass
-+{
-+ GtkListHeaderBaseClass parent_class;
-+};
-+
-+GType gtk_list_header_widget_get_type (void) G_GNUC_CONST;
-+
-+GtkWidget * gtk_list_header_widget_new (GtkListItemFactory *factory);
-+
-+void gtk_list_header_widget_set_factory (GtkListHeaderWidget *self,
-+ GtkListItemFactory *factory);
-+GtkListItemFactory * gtk_list_header_widget_get_factory (GtkListHeaderWidget *self);
-+
-+void gtk_list_header_widget_set_child (GtkListHeaderWidget *self,
-+ GtkWidget *child);
-+
-+
-+G_END_DECLS
-+
-diff --git a/gtk/gtklistitembase.c b/gtk/gtklistitembase.c
-index 67bbf959120b51844409f53b9b7c8d6a068e682d..cdd8fe9703dcf948c9cc1006b4d6cff650834952 100644
---- a/gtk/gtklistitembase.c
-+++ b/gtk/gtklistitembase.c
-@@ -81,6 +81,11 @@ gtk_list_item_base_update (GtkListItemBase *self,
- GtkListItemBasePrivate *priv = gtk_list_item_base_get_instance_private (self);
- gboolean was_selected;
-
-+ if (priv->position == position &&
-+ priv->item == item &&
-+ priv->selected == selected)
-+ return;
-+
- was_selected = priv->selected;
-
- GTK_LIST_ITEM_BASE_GET_CLASS (self)->update (self, position, item, selected);
-diff --git a/gtk/gtklistitemmanager.c b/gtk/gtklistitemmanager.c
-index 8ef33b5def00a13aea32b4f9ffa20a5dd8538027..2ca7f9eaf8a93ae3116d972ab0b08dc3d9730a0f 100644
---- a/gtk/gtklistitemmanager.c
-+++ b/gtk/gtklistitemmanager.c
-@@ -23,20 +23,26 @@
-
- #include "gtklistitembaseprivate.h"
- #include "gtklistitemwidgetprivate.h"
-+#include "gtksectionmodel.h"
- #include "gtkwidgetprivate.h"
-
-+typedef struct _GtkListItemChange GtkListItemChange;
-+
- struct _GtkListItemManager
- {
- GObject parent_instance;
-
- GtkWidget *widget;
- GtkSelectionModel *model;
-+ gboolean has_sections;
-
- GtkRbTree *items;
- GSList *trackers;
-
- GtkListTile * (* split_func) (GtkWidget *, GtkListTile *, guint);
- GtkListItemBase * (* create_widget) (GtkWidget *);
-+ void (* prepare_section) (GtkWidget *, GtkListTile *, guint);
-+ GtkListHeaderBase * (* create_header_widget) (GtkWidget *);
- };
-
- struct _GtkListItemManagerClass
-@@ -52,26 +58,104 @@ struct _GtkListItemTracker
- guint n_after;
- };
-
--static GtkWidget * gtk_list_item_manager_acquire_list_item (GtkListItemManager *self,
-- guint position,
-- GtkWidget *prev_sibling);
--static GtkWidget * gtk_list_item_manager_try_reacquire_list_item
-- (GtkListItemManager *self,
-- GHashTable *change,
-- guint position,
-- GtkWidget *prev_sibling);
--static void gtk_list_item_manager_update_list_item (GtkListItemManager *self,
-- GtkWidget *item,
-- guint position);
--static void gtk_list_item_manager_move_list_item (GtkListItemManager *self,
-- GtkWidget *list_item,
-- guint position,
-- GtkWidget *prev_sibling);
--static void gtk_list_item_manager_release_list_item (GtkListItemManager *self,
-- GHashTable *change,
-- GtkWidget *widget);
-+struct _GtkListItemChange
-+{
-+ GHashTable *deleted_items;
-+ GQueue recycled_items;
-+ GQueue recycled_headers;
-+};
-+
- G_DEFINE_TYPE (GtkListItemManager, gtk_list_item_manager, G_TYPE_OBJECT)
-
-+static void
-+gtk_list_item_change_init (GtkListItemChange *change)
-+{
-+ change->deleted_items = NULL;
-+ g_queue_init (&change->recycled_items);
-+ g_queue_init (&change->recycled_headers);
-+}
-+
-+static void
-+gtk_list_item_change_finish (GtkListItemChange *change)
-+{
-+ GtkWidget *widget;
-+
-+ g_clear_pointer (&change->deleted_items, g_hash_table_destroy);
-+
-+ while ((widget = g_queue_pop_head (&change->recycled_items)))
-+ gtk_widget_unparent (widget);
-+ while ((widget = g_queue_pop_head (&change->recycled_headers)))
-+ gtk_widget_unparent (widget);
-+}
-+
-+static void
-+gtk_list_item_change_recycle (GtkListItemChange *change,
-+ GtkListItemBase *widget)
-+{
-+ g_queue_push_tail (&change->recycled_items, widget);
-+}
-+
-+static void
-+gtk_list_item_change_clear_header (GtkListItemChange *change,
-+ GtkWidget **widget)
-+{
-+ if (*widget == NULL)
-+ return;
-+
-+ g_assert (GTK_IS_LIST_HEADER_BASE (*widget));
-+ g_queue_push_tail (&change->recycled_headers, *widget);
-+ *widget = NULL;
-+}
-+
-+static void
-+gtk_list_item_change_release (GtkListItemChange *change,
-+ GtkListItemBase *widget)
-+{
-+ if (change->deleted_items == NULL)
-+ change->deleted_items = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) gtk_widget_unparent);
-+
-+ if (!g_hash_table_replace (change->deleted_items, gtk_list_item_base_get_item (GTK_LIST_ITEM_BASE (widget)), widget))
-+ {
-+ g_warning ("Duplicate item detected in list. Picking one randomly.");
-+ gtk_list_item_change_recycle (change, widget);
-+ }
-+}
-+
-+static GtkListItemBase *
-+gtk_list_item_change_find (GtkListItemChange *change,
-+ gpointer item)
-+{
-+ gpointer result;
-+
-+ if (change->deleted_items && g_hash_table_steal_extended (change->deleted_items, item, NULL, &result))
-+ return result;
-+
-+ return NULL;
-+}
-+
-+static GtkListItemBase *
-+gtk_list_item_change_get (GtkListItemChange *change,
-+ gpointer item)
-+{
-+ GtkListItemBase *result;
-+
-+ result = gtk_list_item_change_find (change, item);
-+ if (result)
-+ return result;
-+
-+ result = g_queue_pop_head (&change->recycled_items);
-+ if (result)
-+ return result;
-+
-+ return NULL;
-+}
-+
-+static GtkListHeaderBase *
-+gtk_list_item_change_get_header (GtkListItemChange *change)
-+{
-+ return g_queue_pop_head (&change->recycled_headers);
-+}
-+
- static void
- potentially_empty_rectangle_union (cairo_rectangle_int_t *self,
- const cairo_rectangle_int_t *area)
-@@ -101,11 +185,36 @@ gtk_list_item_manager_augment_node (GtkRbTree *tree,
- aug->n_items = tile->n_items;
- aug->area = tile->area;
-
-+ switch (tile->type)
-+ {
-+ case GTK_LIST_TILE_HEADER:
-+ case GTK_LIST_TILE_UNMATCHED_HEADER:
-+ aug->has_header = TRUE;
-+ aug->has_footer = FALSE;
-+ break;
-+ case GTK_LIST_TILE_FOOTER:
-+ case GTK_LIST_TILE_UNMATCHED_FOOTER:
-+ aug->has_header = FALSE;
-+ aug->has_footer = TRUE;
-+ break;
-+ case GTK_LIST_TILE_ITEM:
-+ case GTK_LIST_TILE_FILLER:
-+ case GTK_LIST_TILE_REMOVED:
-+ aug->has_header = FALSE;
-+ aug->has_footer = FALSE;
-+ break;
-+ default:
-+ g_assert_not_reached ();
-+ break;
-+ }
-+
- if (left)
- {
- GtkListTileAugment *left_aug = gtk_rb_tree_get_augment (tree, left);
-
- aug->n_items += left_aug->n_items;
-+ aug->has_header |= left_aug->has_header;
-+ aug->has_footer |= left_aug->has_footer;
- potentially_empty_rectangle_union (&aug->area, &left_aug->area);
- }
-
-@@ -114,6 +223,8 @@ gtk_list_item_manager_augment_node (GtkRbTree *tree,
- GtkListTileAugment *right_aug = gtk_rb_tree_get_augment (tree, right);
-
- aug->n_items += right_aug->n_items;
-+ aug->has_header |= right_aug->has_header;
-+ aug->has_footer |= right_aug->has_footer;
- potentially_empty_rectangle_union (&aug->area, &right_aug->area);
- }
- }
-@@ -127,9 +238,11 @@ gtk_list_item_manager_clear_node (gpointer _tile)
- }
-
- GtkListItemManager *
--gtk_list_item_manager_new (GtkWidget *widget,
-- GtkListTile * (* split_func) (GtkWidget *, GtkListTile *, guint),
-- GtkListItemBase * (* create_widget) (GtkWidget *))
-+gtk_list_item_manager_new (GtkWidget *widget,
-+ GtkListTile * (* split_func) (GtkWidget *, GtkListTile *, guint),
-+ GtkListItemBase * (* create_widget) (GtkWidget *),
-+ void (* prepare_section) (GtkWidget *, GtkListTile *, guint),
-+ GtkListHeaderBase * (* create_header_widget) (GtkWidget *))
- {
- GtkListItemManager *self;
-
-@@ -141,6 +254,8 @@ gtk_list_item_manager_new (GtkWidget *widget,
- self->widget = widget;
- self->split_func = split_func;
- self->create_widget = create_widget;
-+ self->prepare_section = prepare_section;
-+ self->create_header_widget = create_header_widget;
-
- self->items = gtk_rb_tree_new_for_size (sizeof (GtkListTile),
- sizeof (GtkListTileAugment),
-@@ -151,6 +266,15 @@ gtk_list_item_manager_new (GtkWidget *widget,
- return self;
- }
-
-+static gboolean
-+gtk_list_item_manager_has_sections (GtkListItemManager *self)
-+{
-+ if (self->model == NULL || !self->has_sections)
-+ return FALSE;
-+
-+ return GTK_IS_SECTION_MODEL (self->model);
-+}
-+
- void
- gtk_list_item_manager_get_tile_bounds (GtkListItemManager *self,
- GdkRectangle *out_bounds)
-@@ -238,6 +362,110 @@ gtk_list_item_manager_get_nth (GtkListItemManager *self,
- return tile;
- }
-
-+static GtkListTile *
-+gtk_list_tile_get_header (GtkListItemManager *self,
-+ GtkListTile *tile)
-+{
-+ GtkListTileAugment *aug;
-+ GtkListTile *other;
-+ gboolean check_right = FALSE;
-+
-+ while (TRUE)
-+ {
-+ if (check_right)
-+ {
-+ other = gtk_rb_tree_node_get_right (tile);
-+ if (other)
-+ {
-+ aug = gtk_rb_tree_get_augment (self->items, other);
-+ if (aug->has_header)
-+ {
-+ check_right = TRUE;
-+ tile = other;
-+ continue;
-+ }
-+ }
-+ }
-+
-+ if (tile->type == GTK_LIST_TILE_HEADER ||
-+ tile->type == GTK_LIST_TILE_UNMATCHED_HEADER)
-+ return tile;
-+
-+ other = gtk_rb_tree_node_get_left (tile);
-+ if (other)
-+ {
-+ aug = gtk_rb_tree_get_augment (self->items, other);
-+ if (aug->has_header)
-+ {
-+ check_right = TRUE;
-+ tile = other;
-+ continue;
-+ }
-+ }
-+
-+ while ((other = gtk_rb_tree_node_get_parent (tile)))
-+ {
-+ if (gtk_rb_tree_node_get_right (other) == tile)
-+ break;
-+ tile = other;
-+ }
-+ tile = other;
-+ check_right = FALSE;
-+ }
-+}
-+
-+static GtkListTile *
-+gtk_list_tile_get_footer (GtkListItemManager *self,
-+ GtkListTile *tile)
-+{
-+ GtkListTileAugment *aug;
-+ GtkListTile *other;
-+ gboolean check_left = FALSE;
-+
-+ while (TRUE)
-+ {
-+ if (check_left)
-+ {
-+ other = gtk_rb_tree_node_get_left (tile);
-+ if (other)
-+ {
-+ aug = gtk_rb_tree_get_augment (self->items, other);
-+ if (aug->has_footer)
-+ {
-+ check_left = TRUE;
-+ tile = other;
-+ continue;
-+ }
-+ }
-+ }
-+
-+ if (tile->type == GTK_LIST_TILE_FOOTER ||
-+ tile->type == GTK_LIST_TILE_UNMATCHED_FOOTER)
-+ return tile;
-+
-+ other = gtk_rb_tree_node_get_right (tile);
-+ if (other)
-+ {
-+ aug = gtk_rb_tree_get_augment (self->items, other);
-+ if (aug->has_footer)
-+ {
-+ check_left = TRUE;
-+ tile = other;
-+ continue;
-+ }
-+ }
-+
-+ while ((other = gtk_rb_tree_node_get_parent (tile)))
-+ {
-+ if (gtk_rb_tree_node_get_left (other) == tile)
-+ break;
-+ tile = other;
-+ }
-+ tile = other;
-+ check_left = FALSE;
-+ }
-+}
-+
- /* This computes Manhattan distance */
- static int
- rectangle_distance (const cairo_rectangle_int_t *rect,
-@@ -401,6 +629,28 @@ gtk_list_tile_get_augment (GtkListItemManager *self,
- return gtk_rb_tree_get_augment (self->items, tile);
- }
-
-+static GtkListTile *
-+gtk_list_tile_get_next_skip (GtkListTile *tile)
-+{
-+ for (tile = gtk_rb_tree_node_get_next (tile);
-+ tile && (tile->type == GTK_LIST_TILE_FILLER || tile->type == GTK_LIST_TILE_REMOVED);
-+ tile = gtk_rb_tree_node_get_next (tile))
-+ { }
-+
-+ return tile;
-+}
-+
-+static GtkListTile *
-+gtk_list_tile_get_previous_skip (GtkListTile *tile)
-+{
-+ for (tile = gtk_rb_tree_node_get_previous (tile);
-+ tile && (tile->type == GTK_LIST_TILE_FILLER || tile->type == GTK_LIST_TILE_REMOVED);
-+ tile = gtk_rb_tree_node_get_previous (tile))
-+ { }
-+
-+ return tile;
-+}
-+
- /*
- * gtk_list_tile_set_area:
- * @self: the list item manager
-@@ -461,6 +711,18 @@ gtk_list_tile_set_area_size (GtkListItemManager *self,
- gtk_rb_tree_node_mark_dirty (tile);
- }
-
-+static void
-+gtk_list_tile_set_type (GtkListTile *tile,
-+ GtkListTileType type)
-+{
-+ if (tile->type == type)
-+ return;
-+
-+ g_assert (tile->widget == NULL);
-+ tile->type = type;
-+ gtk_rb_tree_node_mark_dirty (tile);
-+}
-+
- static void
- gtk_list_item_tracker_unset_position (GtkListItemManager *self,
- GtkListItemTracker *tracker)
-@@ -569,11 +831,11 @@ gtk_list_item_manager_ensure_split (GtkListItemManager *self,
-
- static void
- gtk_list_item_manager_remove_items (GtkListItemManager *self,
-- GHashTable *change,
-+ GtkListItemChange *change,
- guint position,
- guint n_items)
- {
-- GtkListTile *tile, *next;
-+ GtkListTile *tile, *header;
- guint offset;
-
- if (n_items == 0)
-@@ -582,24 +844,63 @@ gtk_list_item_manager_remove_items (GtkListItemManager *self,
- tile = gtk_list_item_manager_get_nth (self, position, &offset);
- if (offset)
- tile = gtk_list_item_manager_ensure_split (self, tile, offset);
-+ header = gtk_list_tile_get_previous_skip (tile);
-+ if (header->type != GTK_LIST_TILE_HEADER && header->type != GTK_LIST_TILE_UNMATCHED_HEADER)
-+ header = NULL;
-
- while (n_items > 0)
- {
-- if (tile->n_items > n_items)
-+ switch (tile->type)
- {
-- gtk_list_item_manager_ensure_split (self, tile, n_items);
-- g_assert (tile->n_items <= n_items);
-+ case GTK_LIST_TILE_HEADER:
-+ case GTK_LIST_TILE_UNMATCHED_HEADER:
-+ g_assert (header == NULL);
-+ header = tile;
-+ break;
-+
-+ case GTK_LIST_TILE_FOOTER:
-+ case GTK_LIST_TILE_UNMATCHED_FOOTER:
-+ if (header)
-+ {
-+ gtk_list_item_change_clear_header (change, &header->widget);
-+ gtk_list_tile_set_type (header, GTK_LIST_TILE_REMOVED);
-+ gtk_list_tile_set_type (tile, GTK_LIST_TILE_REMOVED);
-+ header = NULL;
-+ }
-+ break;
-+
-+ case GTK_LIST_TILE_ITEM:
-+ if (tile->n_items > n_items)
-+ {
-+ gtk_list_item_manager_ensure_split (self, tile, n_items);
-+ g_assert (tile->n_items <= n_items);
-+ }
-+ if (tile->widget)
-+ gtk_list_item_change_release (change, GTK_LIST_ITEM_BASE (tile->widget));
-+ tile->widget = NULL;
-+ n_items -= tile->n_items;
-+ tile->n_items = 0;
-+ gtk_list_tile_set_type (tile, GTK_LIST_TILE_REMOVED);
-+ break;
-+
-+ case GTK_LIST_TILE_FILLER:
-+ case GTK_LIST_TILE_REMOVED:
-+ default:
-+ g_assert_not_reached ();
-+ break;
- }
-
-- next = gtk_rb_tree_node_get_next (tile);
-- if (tile->widget)
-- gtk_list_item_manager_release_list_item (self, change, tile->widget);
-- tile->widget = NULL;
-- n_items -= tile->n_items;
-- tile->n_items = 0;
-- gtk_rb_tree_node_mark_dirty (tile);
-+ tile = gtk_list_tile_get_next_skip (tile);
-+ }
-
-- tile = next;
-+ if (header)
-+ {
-+ if (tile->type == GTK_LIST_TILE_FOOTER || tile->type == GTK_LIST_TILE_UNMATCHED_FOOTER)
-+ {
-+ gtk_list_item_change_clear_header (change, &header->widget);
-+ gtk_list_tile_set_type (header, GTK_LIST_TILE_REMOVED);
-+ gtk_list_tile_set_type (tile, GTK_LIST_TILE_REMOVED);
-+ }
- }
-
- gtk_widget_queue_resize (GTK_WIDGET (self->widget));
-@@ -607,23 +908,70 @@ gtk_list_item_manager_remove_items (GtkListItemManager *self,
-
- static void
- gtk_list_item_manager_add_items (GtkListItemManager *self,
-+ GtkListItemChange *change,
- guint position,
- guint n_items)
- {
- GtkListTile *tile;
- guint offset;
-+ gboolean has_sections;
-
- if (n_items == 0)
- return;
-
-+ has_sections = gtk_list_item_manager_has_sections (self);
-+
- tile = gtk_list_item_manager_get_nth (self, position, &offset);
-+ if (tile == NULL)
-+ {
-+ /* at end of list, pick the footer */
-+ for (tile = gtk_rb_tree_get_last (self->items);
-+ tile && (tile->type == GTK_LIST_TILE_REMOVED || tile->type == GTK_LIST_TILE_FILLER);
-+ tile = gtk_rb_tree_node_get_previous (tile))
-+ { }
-+
-+ if (tile == NULL)
-+ {
-+ /* empty list, there isn't even a footer yet */
-+ tile = gtk_rb_tree_insert_after (self->items, NULL);
-+ tile->type = GTK_LIST_TILE_UNMATCHED_HEADER;
-+
-+ tile = gtk_rb_tree_insert_after (self->items, tile);
-+ tile->type = GTK_LIST_TILE_UNMATCHED_FOOTER;
-+ }
-+ else if (has_sections && tile->type == GTK_LIST_TILE_FOOTER)
-+ {
-+ GtkListTile *header;
-+
-+ gtk_list_tile_set_type (tile, GTK_LIST_TILE_UNMATCHED_FOOTER);
-+
-+ header = gtk_list_tile_get_header (self, tile);
-+ gtk_list_item_change_clear_header (change, &header->widget);
-+ gtk_list_tile_set_type (header, GTK_LIST_TILE_UNMATCHED_HEADER);
-+ }
-+ }
- if (offset)
- tile = gtk_list_item_manager_ensure_split (self, tile, offset);
--
-+
- tile = gtk_rb_tree_insert_before (self->items, tile);
-+ tile->type = GTK_LIST_TILE_ITEM;
- tile->n_items = n_items;
- gtk_rb_tree_node_mark_dirty (tile);
-
-+ if (has_sections)
-+ {
-+ GtkListTile *section = gtk_list_tile_get_previous_skip (tile);
-+
-+ if (section->type == GTK_LIST_TILE_HEADER)
-+ {
-+ gtk_list_item_change_clear_header (change, §ion->widget);
-+ gtk_list_tile_set_type (section,
-+ GTK_LIST_TILE_UNMATCHED_HEADER);
-+ gtk_list_tile_set_type (gtk_list_tile_get_footer (self, section),
-+ GTK_LIST_TILE_UNMATCHED_FOOTER);
-+ }
-+ }
-+
- gtk_widget_queue_resize (GTK_WIDGET (self->widget));
- }
-
-@@ -632,7 +980,8 @@ gtk_list_item_manager_merge_list_items (GtkListItemManager *self,
- GtkListTile *first,
- GtkListTile *second)
- {
-- if (first->widget || second->widget)
-+ if (first->widget || second->widget ||
-+ first->type != GTK_LIST_TILE_ITEM || second->type != GTK_LIST_TILE_ITEM)
- return FALSE;
-
- first->n_items += second->n_items;
-@@ -653,7 +1002,7 @@ gtk_list_item_manager_merge_list_items (GtkListItemManager *self,
- * items will be given to the new tile, which will be
- * nserted after the tile.
- *
-- * It is valid for either tile to have 0 items after
-+ * It is not valid for either tile to have 0 items after
- * the split.
- *
- * Returns: The new tile
-@@ -665,9 +1014,12 @@ gtk_list_tile_split (GtkListItemManager *self,
- {
- GtkListTile *result;
-
-- g_assert (n_items <= tile->n_items);
-+ g_assert (n_items > 0);
-+ g_assert (n_items < tile->n_items);
-+ g_assert (tile->type == GTK_LIST_TILE_ITEM);
-
- result = gtk_rb_tree_insert_after (self->items, tile);
-+ result->type = GTK_LIST_TILE_ITEM;
- result->n_items = tile->n_items - n_items;
- tile->n_items = n_items;
- gtk_rb_tree_node_mark_dirty (tile);
-@@ -675,10 +1027,38 @@ gtk_list_tile_split (GtkListItemManager *self,
- return result;
- }
-
-+/*
-+ * gtk_list_tile_append_filler:
-+ * @self: the listitemmanager
-+ * @previous: tile to append to
-+ *
-+ * Appends a filler tile.
-+ *
-+ * Filler tiles don't refer to any items or header and exist
-+ * just to take up space, so that finding items by position gets
-+ * easier.
-+ *
-+ * They ave a special garbage-collection behavior, see
-+ * gtk_list_tile_gc().
-+ *
-+ * Returns: The new filler tile
-+ **/
-+GtkListTile *
-+gtk_list_tile_append_filler (GtkListItemManager *self,
-+ GtkListTile *previous)
-+{
-+ GtkListTile *result;
-+
-+ result = gtk_rb_tree_insert_after (self->items, previous);
-+ result->type = GTK_LIST_TILE_FILLER;
-+
-+ return result;
-+}
-+
- /*
- * gtk_list_tile_gc:
- * @self: the listitemmanager
-- * @tile: a tile
-+ * @tile: a tile or NULL
- *
- * Tries to get rid of tiles when they aren't needed anymore,
- * either because their referenced listitems were deleted or
-@@ -686,7 +1066,11 @@ gtk_list_tile_split (GtkListItemManager *self,
- *
- * Note that this only looks forward, but never backward.
- *
-- * Returns: The next tile
-+ * A special case here are filler tiles. They only get
-+ * collected, when they are explicitly passed in, but never
-+ * otherwise.
-+ *
-+ * Returns: The next tile or NULL if everything was gc'ed
- **/
- GtkListTile *
- gtk_list_tile_gc (GtkListItemManager *self,
-@@ -694,22 +1078,51 @@ gtk_list_tile_gc (GtkListItemManager *self,
- {
- GtkListTile *next;
-
-+ if (tile == NULL)
-+ return NULL;
-+
-+ if (tile->type == GTK_LIST_TILE_FILLER)
-+ {
-+ next = gtk_rb_tree_node_get_next (tile);
-+ gtk_rb_tree_remove (self->items, tile);
-+ tile = next;
-+ }
-+
- while (tile)
- {
- next = gtk_rb_tree_node_get_next (tile);
-+ while (next && next->type == GTK_LIST_TILE_REMOVED)
-+ {
-+ gtk_rb_tree_remove (self->items, next);
-+ next = gtk_rb_tree_node_get_next (tile);
-+ }
-
-- if (tile->n_items == 0)
-+ switch (tile->type)
- {
-+ case GTK_LIST_TILE_ITEM:
-+ g_assert (tile->n_items > 0);
-+ if (next == NULL)
-+ break;
-+ if (gtk_list_item_manager_merge_list_items (self, tile, next))
-+ continue;
-+ break;
-+
-+ case GTK_LIST_TILE_HEADER:
-+ case GTK_LIST_TILE_FOOTER:
-+ case GTK_LIST_TILE_UNMATCHED_HEADER:
-+ case GTK_LIST_TILE_UNMATCHED_FOOTER:
-+ case GTK_LIST_TILE_FILLER:
-+ break;
-+
-+ case GTK_LIST_TILE_REMOVED:
- gtk_rb_tree_remove (self->items, tile);
- tile = next;
- continue;
-- }
-
-- if (next == NULL)
-- break;
--
-- if (gtk_list_item_manager_merge_list_items (self, tile, next))
-- continue;
-+ default:
-+ g_assert_not_reached ();
-+ break;
-+ }
-
- break;
- }
-@@ -719,14 +1132,15 @@ gtk_list_tile_gc (GtkListItemManager *self,
-
- static void
- gtk_list_item_manager_release_items (GtkListItemManager *self,
-- GQueue *released)
-+ GtkListItemChange *change)
- {
- GtkListTile *tile;
- guint position, i, n_items, query_n_items;
-- gboolean tracked;
-+ gboolean tracked, deleted_section;
-
- n_items = g_list_model_get_n_items (G_LIST_MODEL (self->model));
- position = 0;
-+ deleted_section = FALSE;
-
- while (position < n_items)
- {
-@@ -742,36 +1156,134 @@ gtk_list_item_manager_release_items (GtkListItemManager *self,
- while (i < position + query_n_items)
- {
- g_assert (tile != NULL);
-- if (tile->widget)
-+ switch (tile->type)
- {
-- g_queue_push_tail (released, tile->widget);
-- tile->widget = NULL;
-+ case GTK_LIST_TILE_ITEM:
-+ if (tile->widget)
-+ {
-+ gtk_list_item_change_recycle (change, GTK_LIST_ITEM_BASE (tile->widget));
-+ tile->widget = NULL;
-+ }
-+ i += tile->n_items;
-+ break;
-+
-+ case GTK_LIST_TILE_HEADER:
-+ case GTK_LIST_TILE_UNMATCHED_HEADER:
-+ g_assert (deleted_section);
-+ gtk_list_item_change_clear_header (change, &tile->widget);
-+ G_GNUC_FALLTHROUGH;
-+ case GTK_LIST_TILE_FOOTER:
-+ case GTK_LIST_TILE_UNMATCHED_FOOTER:
-+ gtk_list_tile_set_type (tile, GTK_LIST_TILE_REMOVED);
-+ deleted_section = TRUE;
-+ break;
-+
-+ case GTK_LIST_TILE_FILLER:
-+ case GTK_LIST_TILE_REMOVED:
-+ default:
-+ g_assert_not_reached ();
-+ break;
- }
-- i += tile->n_items;
-- tile = gtk_rb_tree_node_get_next (tile);
-+ tile = gtk_list_tile_get_next_skip (tile);
-+ }
-+ if (deleted_section)
-+ {
-+ tile = gtk_list_tile_get_header (self, tile);
-+ gtk_list_item_change_clear_header (change, &tile->widget);
-+ gtk_list_tile_set_type (tile, GTK_LIST_TILE_UNMATCHED_HEADER);
-+
-+ tile = gtk_list_tile_get_footer (self, tile);
-+ gtk_list_tile_set_type (tile, GTK_LIST_TILE_UNMATCHED_FOOTER);
- }
- position += query_n_items;
- }
- }
-
-+static GtkListTile *
-+gtk_list_item_manager_insert_section (GtkListItemManager *self,
-+ guint pos,
-+ GtkListTileType footer_type,
-+ GtkListTileType header_type)
-+{
-+ GtkListTile *tile, *footer, *header;
-+ guint offset;
-+
-+ tile = gtk_list_item_manager_get_nth (self, pos, &offset);
-+ if (tile == NULL)
-+ {
-+ if (footer_type == GTK_LIST_TILE_FOOTER)
-+ {
-+ footer = gtk_rb_tree_get_last (self->items);
-+ if (footer->type != GTK_LIST_TILE_FOOTER && footer->type != GTK_LIST_TILE_UNMATCHED_FOOTER)
-+ footer = gtk_list_tile_get_previous_skip (footer);
-+ gtk_list_tile_set_type (footer, footer_type);
-+ }
-+ return NULL;
-+ }
-+
-+ if (offset)
-+ tile = gtk_list_item_manager_ensure_split (self, tile, offset);
-+
-+ header = gtk_list_tile_get_previous_skip (tile);
-+ if (header->type == GTK_LIST_TILE_HEADER || header->type == GTK_LIST_TILE_UNMATCHED_HEADER)
-+ {
-+ if (header_type == GTK_LIST_TILE_HEADER)
-+ gtk_list_tile_set_type (header, header_type);
-+ if (footer_type == GTK_LIST_TILE_FOOTER)
-+ {
-+ footer = gtk_list_tile_get_previous_skip (header);
-+ if (footer)
-+ gtk_list_tile_set_type (footer, footer_type);
-+ }
-+ }
-+ else
-+ {
-+ self->prepare_section (self->widget, tile, pos);
-+
-+ header = gtk_rb_tree_insert_before (self->items, tile);
-+ gtk_list_tile_set_type (header, header_type);
-+ footer = gtk_rb_tree_insert_before (self->items, header);
-+ gtk_list_tile_set_type (footer, footer_type);
-+ }
-+
-+ return header;
-+}
-+
-+static GtkWidget *
-+gtk_list_tile_find_widget_before (GtkListTile *tile)
-+{
-+ GtkListTile *other;
-+
-+ for (other = gtk_rb_tree_node_get_previous (tile);
-+ other;
-+ other = gtk_rb_tree_node_get_previous (other))
-+ {
-+ if (other->widget)
-+ return other->widget;
-+ }
-+
-+ return NULL;
-+}
-+
- static void
- gtk_list_item_manager_ensure_items (GtkListItemManager *self,
-- GHashTable *change,
-- guint update_start)
-+ GtkListItemChange *change,
-+ guint update_start,
-+ int update_diff)
- {
-- GtkListTile *tile, *other_tile;
-- GtkWidget *widget, *insert_after;
-+ GtkListTile *tile, *header;
-+ GtkWidget *insert_after;
- guint position, i, n_items, query_n_items, offset;
-- GQueue released = G_QUEUE_INIT;
-- gboolean tracked;
-+ gboolean tracked, has_sections;
-
- if (self->model == NULL)
- return;
-
- n_items = g_list_model_get_n_items (G_LIST_MODEL (self->model));
- position = 0;
-+ has_sections = gtk_list_item_manager_has_sections (self);
-
-- gtk_list_item_manager_release_items (self, &released);
-+ gtk_list_item_manager_release_items (self, change);
-
- while (position < n_items)
- {
-@@ -783,66 +1295,133 @@ gtk_list_item_manager_ensure_items (GtkListItemManager *self,
- }
-
- tile = gtk_list_item_manager_get_nth (self, position, &offset);
-- for (other_tile = tile;
-- other_tile && other_tile->widget == NULL;
-- other_tile = gtk_rb_tree_node_get_previous (other_tile))
-- { /* do nothing */ }
-- insert_after = other_tile ? other_tile->widget : NULL;
--
- if (offset > 0)
- tile = gtk_list_item_manager_ensure_split (self, tile, offset);
-
-- for (i = 0; i < query_n_items; i++)
-+ if (has_sections)
- {
-- g_assert (tile != NULL);
-+ header = gtk_list_tile_get_header (self, tile);
-+ if (header->type == GTK_LIST_TILE_UNMATCHED_HEADER)
-+ {
-+ guint start, end;
-+ gpointer item;
-+
-+ gtk_section_model_get_section (GTK_SECTION_MODEL (self->model), position, &start, &end);
-+ header = gtk_list_item_manager_insert_section (self,
-+ start,
-+ GTK_LIST_TILE_UNMATCHED_FOOTER,
-+ GTK_LIST_TILE_HEADER);
-+ g_assert (header->widget == NULL);
-+ header->widget = GTK_WIDGET (gtk_list_item_change_get_header (change));
-+ if (header->widget == NULL)
-+ header->widget = GTK_WIDGET (self->create_header_widget (self->widget));
-+ item = g_list_model_get_item (G_LIST_MODEL (self->model), start);
-+ gtk_list_header_base_update (GTK_LIST_HEADER_BASE (header->widget),
-+ item,
-+ start, end);
-+ g_object_unref (item);
-+ gtk_widget_insert_after (header->widget,
-+ self->widget,
-+ gtk_list_tile_find_widget_before (header));
-+
-+ gtk_list_item_manager_insert_section (self,
-+ end,
-+ GTK_LIST_TILE_FOOTER,
-+ GTK_LIST_TILE_UNMATCHED_HEADER);
-+ }
-+ else if (gtk_list_header_base_get_end (GTK_LIST_HEADER_BASE (header->widget)) > update_start)
-+ {
-+ GtkListHeaderBase *base = GTK_LIST_HEADER_BASE (header->widget);
-+ guint start = gtk_list_header_base_get_start (base);
-+ gtk_list_header_base_update (base,
-+ gtk_list_header_base_get_item (base),
-+ start > update_start ? start + update_diff : start,
-+ gtk_list_header_base_get_end (base) + update_diff);
-+ }
-+ }
-
-- while (tile->n_items == 0)
-- tile = gtk_rb_tree_node_get_next (tile);
-+ insert_after = gtk_list_tile_find_widget_before (tile);
-
-- if (tile->n_items > 1)
-- gtk_list_item_manager_ensure_split (self, tile, 1);
-+ for (i = 0; i < query_n_items;)
-+ {
-+ g_assert (tile != NULL);
-
-- if (tile->widget == NULL)
-- {
-- if (change)
-+ switch (tile->type)
-+ {
-+ case GTK_LIST_TILE_ITEM:
-+ if (tile->n_items > 1)
-+ gtk_list_item_manager_ensure_split (self, tile, 1);
-+
-+ if (tile->widget == NULL)
- {
-- tile->widget = gtk_list_item_manager_try_reacquire_list_item (self,
-- change,
-- position + i,
-- insert_after);
-+ gpointer item = g_list_model_get_item (G_LIST_MODEL (self->model), position + i);
-+ tile->widget = GTK_WIDGET (gtk_list_item_change_get (change, item));
-+ if (tile->widget == NULL)
-+ tile->widget = GTK_WIDGET (self->create_widget (self->widget));
-+ gtk_list_item_base_update (GTK_LIST_ITEM_BASE (tile->widget),
-+ position + i,
-+ item,
-+ gtk_selection_model_is_selected (self->model, position + i));
-+ gtk_widget_insert_after (tile->widget, self->widget, insert_after);
- }
-- if (tile->widget == NULL)
-+ else
- {
-- tile->widget = g_queue_pop_head (&released);
-- if (tile->widget)
-+ if (update_start <= position + i)
- {
-- gtk_list_item_manager_move_list_item (self,
-- tile->widget,
-- position + i,
-- insert_after);
-- }
-- else
-- {
-- tile->widget = gtk_list_item_manager_acquire_list_item (self,
-- position + i,
-- insert_after);
-+ gtk_list_item_base_update (GTK_LIST_ITEM_BASE (tile->widget),
-+ position + i,
-+ gtk_list_item_base_get_item (GTK_LIST_ITEM_BASE (tile->widget)),
-+ gtk_selection_model_is_selected (self->model, position + i));
- }
- }
-- }
-- else
-- {
-- if (update_start <= position + i)
-- gtk_list_item_manager_update_list_item (self, tile->widget, position + i);
-- }
-- insert_after = tile->widget;
-+ insert_after = tile->widget;
-+ i++;
-+ break;
-
-- tile = gtk_rb_tree_node_get_next (tile);
-+ case GTK_LIST_TILE_UNMATCHED_HEADER:
-+ if (has_sections)
-+ {
-+ guint start, end;
-+ gpointer item;
-+
-+ gtk_section_model_get_section (GTK_SECTION_MODEL (self->model), position + i, &start, &end);
-+
-+ gtk_list_tile_set_type (tile, GTK_LIST_TILE_HEADER);
-+ g_assert (tile->widget == NULL);
-+ tile->widget = GTK_WIDGET (gtk_list_item_change_get_header (change));
-+ if (tile->widget == NULL)
-+ tile->widget = GTK_WIDGET (self->create_header_widget (self->widget));
-+ item = g_list_model_get_item (G_LIST_MODEL (self->model), start);
-+ gtk_list_header_base_update (GTK_LIST_HEADER_BASE (tile->widget),
-+ item,
-+ start, end);
-+ g_object_unref (item);
-+ gtk_widget_insert_after (tile->widget, self->widget, insert_after);
-+ insert_after = tile->widget;
-+
-+ gtk_list_item_manager_insert_section (self,
-+ end,
-+ GTK_LIST_TILE_FOOTER,
-+ GTK_LIST_TILE_UNMATCHED_HEADER);
-+ }
-+ break;
-+
-+ case GTK_LIST_TILE_HEADER:
-+ case GTK_LIST_TILE_FOOTER:
-+ break;
-+
-+ case GTK_LIST_TILE_UNMATCHED_FOOTER:
-+ case GTK_LIST_TILE_FILLER:
-+ case GTK_LIST_TILE_REMOVED:
-+ default:
-+ g_assert_not_reached ();
-+ break;
-+ }
-+ tile = gtk_list_tile_get_next_skip (tile);
- }
-+
- position += query_n_items;
- }
--
-- while ((widget = g_queue_pop_head (&released)))
-- gtk_list_item_manager_release_list_item (self, NULL, widget);
- }
-
- static void
-@@ -852,15 +1431,15 @@ gtk_list_item_manager_model_items_changed_cb (GListModel *model,
- guint added,
- GtkListItemManager *self)
- {
-- GHashTable *change;
-+ GtkListItemChange change;
- GSList *l;
- guint n_items;
-
-+ gtk_list_item_change_init (&change);
- n_items = g_list_model_get_n_items (G_LIST_MODEL (self->model));
-- change = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify )gtk_widget_unparent);
-
-- gtk_list_item_manager_remove_items (self, change, position, removed);
-- gtk_list_item_manager_add_items (self, position, added);
-+ gtk_list_item_manager_remove_items (self, &change, position, removed);
-+ gtk_list_item_manager_add_items (self, &change, position, added);
-
- /* Check if any tracked item was removed */
- for (l = self->trackers; l; l = l->next)
-@@ -870,7 +1449,7 @@ gtk_list_item_manager_model_items_changed_cb (GListModel *model,
- if (tracker->widget == NULL)
- continue;
-
-- if (g_hash_table_lookup (change, gtk_list_item_base_get_item (tracker->widget)))
-+ if (tracker->position >= position && tracker->position < position + removed)
- break;
- }
-
-@@ -894,12 +1473,14 @@ gtk_list_item_manager_model_items_changed_cb (GListModel *model,
-
- for (i = 0; i < added; i++)
- {
-- GtkWidget *widget;
-+ GtkListItemBase *widget;
-+ gpointer item;
-+
-+ /* XXX: can we avoid temporarily allocating items on failure? */
-+ item = g_list_model_get_item (G_LIST_MODEL (self->model), position + i);
-+ widget = gtk_list_item_change_find (&change, item);
-+ g_object_unref (item);
-
-- widget = gtk_list_item_manager_try_reacquire_list_item (self,
-- change,
-- position + i,
-- insert_after);
- if (widget == NULL)
- {
- offset++;
-@@ -923,8 +1504,13 @@ gtk_list_item_manager_model_items_changed_cb (GListModel *model,
- else
- tile = gtk_list_item_manager_ensure_split (self, tile, 1);
-
-- new_tile->widget = widget;
-- insert_after = widget;
-+ new_tile->widget = GTK_WIDGET (widget);
-+ gtk_list_item_base_update (widget,
-+ position + i,
-+ item,
-+ gtk_selection_model_is_selected (self->model, position + i));
-+ gtk_widget_insert_after (new_tile->widget, self->widget, insert_after);
-+ insert_after = new_tile->widget;
- }
- }
-
-@@ -948,9 +1534,13 @@ gtk_list_item_manager_model_items_changed_cb (GListModel *model,
- }
- else if (tracker->position >= position)
- {
-- if (g_hash_table_lookup (change, gtk_list_item_base_get_item (tracker->widget)))
-+ GtkListItemBase *widget = gtk_list_item_change_find (&change, gtk_list_item_base_get_item (tracker->widget));
-+ if (widget)
- {
-- /* The item is gone. Guess a good new position */
-+ /* The item is still in the recycling pool, which means it got deleted.
-+ * Put the widget back and then guess a good new position */
-+ gtk_list_item_change_release (&change, widget);
-+
- tracker->position = position + (tracker->position - position) * added / removed;
- if (tracker->position >= n_items)
- {
-@@ -975,7 +1565,7 @@ gtk_list_item_manager_model_items_changed_cb (GListModel *model,
- }
- }
-
-- gtk_list_item_manager_ensure_items (self, change, position + added);
-+ gtk_list_item_manager_ensure_items (self, &change, position + added, added - removed);
-
- /* final loop through the trackers: Grab the missing widgets.
- * For items that had been removed and a new position was set, grab
-@@ -996,7 +1586,7 @@ gtk_list_item_manager_model_items_changed_cb (GListModel *model,
- tracker->widget = GTK_LIST_ITEM_BASE (tile->widget);
- }
-
-- g_hash_table_unref (change);
-+ gtk_list_item_change_finish (&change);
-
- gtk_widget_queue_resize (self->widget);
- }
-@@ -1024,23 +1614,32 @@ gtk_list_item_manager_model_selection_changed_cb (GListModel *model,
-
- while (n_items > 0)
- {
-- if (tile->widget)
-- gtk_list_item_manager_update_list_item (self, tile->widget, position);
-+ if (tile->widget && tile->type == GTK_LIST_TILE_ITEM)
-+ {
-+ gtk_list_item_base_update (GTK_LIST_ITEM_BASE (tile->widget),
-+ position,
-+ gtk_list_item_base_get_item (GTK_LIST_ITEM_BASE (tile->widget)),
-+ gtk_selection_model_is_selected (self->model, position));
-+ }
- position += tile->n_items;
- n_items -= MIN (n_items, tile->n_items);
-- tile = gtk_rb_tree_node_get_next (tile);
-+ tile = gtk_list_tile_get_next_skip (tile);
- }
- }
-
- static void
- gtk_list_item_manager_clear_model (GtkListItemManager *self)
- {
-+ GtkListItemChange change;
-+ GtkListTile *tile;
- GSList *l;
-
- if (self->model == NULL)
- return;
-
-- gtk_list_item_manager_remove_items (self, NULL, 0, g_list_model_get_n_items (G_LIST_MODEL (self->model)));
-+ gtk_list_item_change_init (&change);
-+ gtk_list_item_manager_remove_items (self, &change, 0, g_list_model_get_n_items (G_LIST_MODEL (self->model)));
-+ gtk_list_item_change_finish (&change);
- for (l = self->trackers; l; l = l->next)
- {
- gtk_list_item_tracker_unset_position (self, l->data);
-@@ -1053,6 +1652,15 @@ gtk_list_item_manager_clear_model (GtkListItemManager *self)
- gtk_list_item_manager_model_items_changed_cb,
- self);
- g_clear_object (&self->model);
-+
-+ /* really empty the tiles */
-+ for (tile = gtk_list_tile_gc (self, gtk_list_item_manager_get_first (self));
-+ tile;
-+ tile = gtk_list_tile_gc (self, tile))
-+ {
-+ g_assert (tile->type == GTK_LIST_TILE_FILLER);
-+ }
-+ g_assert (gtk_rb_tree_get_root (self->items) == NULL);
- }
-
- static void
-@@ -1094,6 +1702,8 @@ gtk_list_item_manager_set_model (GtkListItemManager *self,
-
- if (model)
- {
-+ GtkListItemChange change;
-+
- self->model = g_object_ref (model);
-
- g_signal_connect (model,
-@@ -1105,7 +1715,10 @@ gtk_list_item_manager_set_model (GtkListItemManager *self,
- G_CALLBACK (gtk_list_item_manager_model_selection_changed_cb),
- self);
-
-- gtk_list_item_manager_add_items (self, 0, g_list_model_get_n_items (G_LIST_MODEL (model)));
-+ gtk_list_item_change_init (&change);
-+ gtk_list_item_manager_add_items (self, &change, 0, g_list_model_get_n_items (G_LIST_MODEL (model)));
-+ gtk_list_item_manager_ensure_items (self, &change, G_MAXUINT, 0);
-+ gtk_list_item_change_finish (&change);
- }
- }
-
-@@ -1117,185 +1730,73 @@ gtk_list_item_manager_get_model (GtkListItemManager *self)
- return self->model;
- }
-
--/*
-- * gtk_list_item_manager_acquire_list_item:
-- * @self: a `GtkListItemManager`
-- * @position: the row in the model to create a list item for
-- * @prev_sibling: the widget this widget should be inserted before or %NULL
-- * if it should be the first widget
-- *
-- * Creates a list item widget to use for @position. No widget may
-- * yet exist that is used for @position.
-- *
-- * When the returned item is no longer needed, the caller is responsible
-- * for calling gtk_list_item_manager_release_list_item().
-- * A particular case is when the row at @position is removed. In that case,
-- * all list items in the removed range must be released before
-- * gtk_list_item_manager_model_changed() is called.
-- *
-- * Returns: a properly setup widget to use in @position
-- **/
--static GtkWidget *
--gtk_list_item_manager_acquire_list_item (GtkListItemManager *self,
-- guint position,
-- GtkWidget *prev_sibling)
-+void
-+gtk_list_item_manager_set_has_sections (GtkListItemManager *self,
-+ gboolean has_sections)
- {
-- GtkListItemBase *result;
-- gpointer item;
-- gboolean selected;
-+ GtkListItemChange change;
-+ GtkListTile *tile;
-+ gboolean had_sections;
-
-- g_return_val_if_fail (GTK_IS_LIST_ITEM_MANAGER (self), NULL);
-- g_return_val_if_fail (prev_sibling == NULL || GTK_IS_WIDGET (prev_sibling), NULL);
-+ if (self->has_sections == has_sections)
-+ return;
-
-- result = self->create_widget (self->widget);
-+ had_sections = gtk_list_item_manager_has_sections (self);
-
-- item = g_list_model_get_item (G_LIST_MODEL (self->model), position);
-- selected = gtk_selection_model_is_selected (self->model, position);
-- gtk_list_item_base_update (result, position, item, selected);
-- g_object_unref (item);
-- gtk_widget_insert_after (GTK_WIDGET (result), self->widget, prev_sibling);
-+ self->has_sections = has_sections;
-
-- return GTK_WIDGET (result);
--}
-+ gtk_list_item_change_init (&change);
-
--/**
-- * gtk_list_item_manager_try_acquire_list_item_from_change:
-- * @self: a `GtkListItemManager`
-- * @position: the row in the model to create a list item for
-- * @prev_sibling: the widget this widget should be inserted after or %NULL
-- * if it should be the first widget
-- *
-- * Like gtk_list_item_manager_acquire_list_item(), but only tries to acquire list
-- * items from those previously released as part of @change.
-- * If no matching list item is found, %NULL is returned and the caller should use
-- * gtk_list_item_manager_acquire_list_item().
-- *
-- * Returns: (nullable): a properly setup widget to use in @position or %NULL if
-- * no item for reuse existed
-- **/
--static GtkWidget *
--gtk_list_item_manager_try_reacquire_list_item (GtkListItemManager *self,
-- GHashTable *change,
-- guint position,
-- GtkWidget *prev_sibling)
--{
-- GtkWidget *result;
-- gpointer item;
--
-- g_return_val_if_fail (GTK_IS_LIST_ITEM_MANAGER (self), NULL);
-- g_return_val_if_fail (prev_sibling == NULL || GTK_IS_WIDGET (prev_sibling), NULL);
--
-- /* XXX: can we avoid temporarily allocating items on failure? */
-- item = g_list_model_get_item (G_LIST_MODEL (self->model), position);
-- if (g_hash_table_steal_extended (change, item, NULL, (gpointer *) &result))
-- {
-- GtkListItemBase *list_item = GTK_LIST_ITEM_BASE (result);
-- gtk_list_item_base_update (list_item,
-- position,
-- gtk_list_item_base_get_item (list_item),
-- gtk_selection_model_is_selected (self->model, position));
-- gtk_widget_insert_after (result, self->widget, prev_sibling);
-- /* XXX: Should we let the listview do this? */
-- gtk_widget_queue_resize (result);
-- }
-- else
-+ if (had_sections && !gtk_list_item_manager_has_sections (self))
- {
-- result = NULL;
-- }
-- g_object_unref (item);
--
-- return result;
--}
-+ GtkListTile *header = NULL, *footer = NULL;
-
--/**
-- * gtk_list_item_manager_move_list_item:
-- * @self: a `GtkListItemManager`
-- * @list_item: an acquired `GtkListItem` that should be moved to represent
-- * a different row
-- * @position: the new position of that list item
-- * @prev_sibling: the new previous sibling
-- *
-- * Moves the widget to represent a new position in the listmodel without
-- * releasing the item.
-- *
-- * This is most useful when scrolling.
-- **/
--static void
--gtk_list_item_manager_move_list_item (GtkListItemManager *self,
-- GtkWidget *list_item,
-- guint position,
-- GtkWidget *prev_sibling)
--{
-- gpointer item;
-- gboolean selected;
--
-- item = g_list_model_get_item (G_LIST_MODEL (self->model), position);
-- selected = gtk_selection_model_is_selected (self->model, position);
-- gtk_list_item_base_update (GTK_LIST_ITEM_BASE (list_item),
-- position,
-- item,
-- selected);
-- gtk_widget_insert_after (list_item, _gtk_widget_get_parent (list_item), prev_sibling);
-- g_object_unref (item);
--}
--
--/**
-- * gtk_list_item_manager_update_list_item:
-- * @self: a `GtkListItemManager`
-- * @item: a `GtkListItem` that has been acquired
-- * @position: the new position of that list item
-- *
-- * Updates the position of the given @item. This function must be called whenever
-- * the position of an item changes, like when new items are added before it.
-- **/
--static void
--gtk_list_item_manager_update_list_item (GtkListItemManager *self,
-- GtkWidget *item,
-- guint position)
--{
-- GtkListItemBase *list_item = GTK_LIST_ITEM_BASE (item);
-- gboolean selected;
-+ for (tile = gtk_rb_tree_get_first (self->items);
-+ tile;
-+ tile = gtk_list_tile_get_next_skip (tile))
-+ {
-+ switch (tile->type)
-+ {
-+ case GTK_LIST_TILE_HEADER:
-+ case GTK_LIST_TILE_UNMATCHED_HEADER:
-+ gtk_list_item_change_clear_header (&change, &tile->widget);
-+ if (!header)
-+ header = tile;
-+ else
-+ gtk_list_tile_set_type (tile, GTK_LIST_TILE_REMOVED);
-+ break;
-+ case GTK_LIST_TILE_FOOTER:
-+ case GTK_LIST_TILE_UNMATCHED_FOOTER:
-+ if (footer)
-+ gtk_list_tile_set_type (footer, GTK_LIST_TILE_REMOVED);
-+ footer = tile;
-+ break;
-+ case GTK_LIST_TILE_ITEM:
-+ case GTK_LIST_TILE_FILLER:
-+ case GTK_LIST_TILE_REMOVED:
-+ break;
-+ default:
-+ g_assert_not_reached ();
-+ break;
-+ }
-+ }
-+ if (header)
-+ {
-+ gtk_list_tile_set_type (header, GTK_LIST_TILE_UNMATCHED_HEADER);
-+ gtk_list_tile_set_type (footer, GTK_LIST_TILE_UNMATCHED_FOOTER);
-+ }
-+ }
-
-- g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self));
-- g_return_if_fail (GTK_IS_LIST_ITEM_BASE (item));
-+ gtk_list_item_manager_ensure_items (self, &change, G_MAXUINT, 0);
-+ gtk_list_item_change_finish (&change);
-
-- selected = gtk_selection_model_is_selected (self->model, position);
-- gtk_list_item_base_update (list_item,
-- position,
-- gtk_list_item_base_get_item (list_item),
-- selected);
-+ gtk_widget_queue_resize (self->widget);
- }
-
--/*
-- * gtk_list_item_manager_release_list_item:
-- * @self: a `GtkListItemManager`
-- * @change: (nullable): The change associated with this release or
-- * %NULL if this is a final removal
-- * @item: an item previously acquired with
-- * gtk_list_item_manager_acquire_list_item()
-- *
-- * Releases an item that was previously acquired via
-- * gtk_list_item_manager_acquire_list_item() and is no longer in use.
-- **/
--static void
--gtk_list_item_manager_release_list_item (GtkListItemManager *self,
-- GHashTable *change,
-- GtkWidget *item)
-+gboolean
-+gtk_list_item_manager_get_has_sections (GtkListItemManager *self)
- {
-- g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self));
-- g_return_if_fail (GTK_IS_LIST_ITEM_BASE (item));
--
-- if (change != NULL)
-- {
-- if (!g_hash_table_replace (change, gtk_list_item_base_get_item (GTK_LIST_ITEM_BASE (item)), item))
-- {
-- g_warning ("Duplicate item detected in list. Picking one randomly.");
-- }
--
-- return;
-- }
--
-- gtk_widget_unparent (item);
-+ return self->has_sections;
- }
-
- GtkListItemTracker *
-@@ -1318,13 +1819,17 @@ void
- gtk_list_item_tracker_free (GtkListItemManager *self,
- GtkListItemTracker *tracker)
- {
-+ GtkListItemChange change;
-+
- gtk_list_item_tracker_unset_position (self, tracker);
-
- self->trackers = g_slist_remove (self->trackers, tracker);
-
- g_free (tracker);
-
-- gtk_list_item_manager_ensure_items (self, NULL, G_MAXUINT);
-+ gtk_list_item_change_init (&change);
-+ gtk_list_item_manager_ensure_items (self, &change, G_MAXUINT, 0);
-+ gtk_list_item_change_finish (&change);
-
- gtk_widget_queue_resize (self->widget);
- }
-@@ -1336,6 +1841,7 @@ gtk_list_item_tracker_set_position (GtkListItemManager *self,
- guint n_before,
- guint n_after)
- {
-+ GtkListItemChange change;
- GtkListTile *tile;
- guint n_items;
-
-@@ -1352,7 +1858,9 @@ gtk_list_item_tracker_set_position (GtkListItemManager *self,
- tracker->n_before = n_before;
- tracker->n_after = n_after;
-
-- gtk_list_item_manager_ensure_items (self, NULL, G_MAXUINT);
-+ gtk_list_item_change_init (&change);
-+ gtk_list_item_manager_ensure_items (self, &change, G_MAXUINT, 0);
-+ gtk_list_item_change_finish (&change);
-
- tile = gtk_list_item_manager_get_nth (self, position, NULL);
- if (tile)
-diff --git a/gtk/gtklistitemmanagerprivate.h b/gtk/gtklistitemmanagerprivate.h
-index 44a9ac82ea4a4deeb60fa7486a5cd1168d5aff7f..cc92ef74a9db6145a7ffa0496d170cd4d32cd9f3 100644
---- a/gtk/gtklistitemmanagerprivate.h
-+++ b/gtk/gtklistitemmanagerprivate.h
-@@ -24,6 +24,7 @@
- #include "gtk/gtkenums.h"
-
- #include "gtk/gtklistitembaseprivate.h"
-+#include "gtk/gtklistheaderbaseprivate.h"
- #include "gtk/gtklistitemfactory.h"
- #include "gtk/gtkrbtreeprivate.h"
- #include "gtk/gtkselectionmodel.h"
-@@ -43,8 +44,20 @@ typedef struct _GtkListTile GtkListTile;
- typedef struct _GtkListTileAugment GtkListTileAugment;
- typedef struct _GtkListItemTracker GtkListItemTracker;
-
-+typedef enum
-+{
-+ GTK_LIST_TILE_ITEM,
-+ GTK_LIST_TILE_HEADER,
-+ GTK_LIST_TILE_FOOTER,
-+ GTK_LIST_TILE_UNMATCHED_HEADER,
-+ GTK_LIST_TILE_UNMATCHED_FOOTER,
-+ GTK_LIST_TILE_FILLER,
-+ GTK_LIST_TILE_REMOVED,
-+} GtkListTileType;
-+
- struct _GtkListTile
- {
-+ GtkListTileType type;
- GtkWidget *widget;
- guint n_items;
- /* area occupied by tile. May be empty if tile has no allcoation */
-@@ -54,6 +67,10 @@ struct _GtkListTile
- struct _GtkListTileAugment
- {
- guint n_items;
-+
-+ guint has_header :1;
-+ guint has_footer :1;
-+
- /* union of all areas of tile and children */
- cairo_rectangle_int_t area;
- };
-@@ -63,7 +80,9 @@ GType gtk_list_item_manager_get_type (void) G_GNUC_CO
-
- GtkListItemManager * gtk_list_item_manager_new (GtkWidget *widget,
- GtkListTile * (* split_func) (GtkWidget *, GtkListTile *, guint),
-- GtkListItemBase * (* create_widget) (GtkWidget *));
-+ GtkListItemBase * (* create_widget) (GtkWidget *),
-+ void (* prepare_section) (GtkWidget *, GtkListTile *, guint),
-+ GtkListHeaderBase * (* create_header_widget) (GtkWidget *));
-
- void gtk_list_item_manager_get_tile_bounds (GtkListItemManager *self,
- GdkRectangle *out_bounds);
-@@ -97,12 +116,17 @@ void gtk_list_tile_set_area_size (GtkListItemMana
- GtkListTile * gtk_list_tile_split (GtkListItemManager *self,
- GtkListTile *tile,
- guint n_items);
-+GtkListTile * gtk_list_tile_append_filler (GtkListItemManager *self,
-+ GtkListTile *previous);
- GtkListTile * gtk_list_tile_gc (GtkListItemManager *self,
- GtkListTile *tile);
-
- void gtk_list_item_manager_set_model (GtkListItemManager *self,
- GtkSelectionModel *model);
- GtkSelectionModel * gtk_list_item_manager_get_model (GtkListItemManager *self);
-+void gtk_list_item_manager_set_has_sections (GtkListItemManager *self,
-+ gboolean has_sections);
-+gboolean gtk_list_item_manager_get_has_sections (GtkListItemManager *self);
-
- GtkListItemTracker * gtk_list_item_tracker_new (GtkListItemManager *self);
- void gtk_list_item_tracker_free (GtkListItemManager *self,
-diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c
-index f6709be181a312bdccc00c4f4b9f09291ea719fa..ef274d93a4797afbf911e68bd9dd20a11a864377 100644
---- a/gtk/gtklistview.c
-+++ b/gtk/gtklistview.c
-@@ -23,6 +23,7 @@
-
- #include "gtkbitset.h"
- #include "gtklistbaseprivate.h"
-+#include "gtklistheaderwidgetprivate.h"
- #include "gtklistitemmanagerprivate.h"
- #include "gtklistitemwidgetprivate.h"
- #include "gtkmultiselection.h"
-@@ -145,6 +146,7 @@ enum
- PROP_0,
- PROP_ENABLE_RUBBERBAND,
- PROP_FACTORY,
-+ PROP_HEADER_FACTORY,
- PROP_MODEL,
- PROP_SHOW_SEPARATORS,
- PROP_SINGLE_CLICK_ACTIVATE,
-@@ -163,29 +165,6 @@ G_DEFINE_TYPE (GtkListView, gtk_list_view, GTK_TYPE_LIST_BASE)
- static GParamSpec *properties[N_PROPS] = { NULL, };
- static guint signals[LAST_SIGNAL] = { 0 };
-
--static void G_GNUC_UNUSED
--dump (GtkListView *self)
--{
-- GtkListTile *tile;
-- guint n_widgets, n_list_rows;
--
-- n_widgets = 0;
-- n_list_rows = 0;
-- //g_print ("ANCHOR: %u - %u\n", self->anchor_start, self->anchor_end);
-- for (tile = gtk_list_item_manager_get_first (self->item_manager);
-- tile;
-- tile = gtk_rb_tree_node_get_next (tile))
-- {
-- if (tile->widget)
-- n_widgets++;
-- n_list_rows++;
-- g_print (" %4u%s %d,%d,%d,%d\n", tile->n_items, tile->widget ? " (widget)" : "",
-- tile->area.x, tile->area.y, tile->area.width, tile->area.height);
-- }
--
-- g_print (" => %u widgets in %u list rows\n", n_widgets, n_list_rows);
--}
--
- static GtkListTile *
- gtk_list_view_split (GtkListBase *base,
- GtkListTile *tile,
-@@ -215,6 +194,13 @@ gtk_list_view_split (GtkListBase *base,
- return new_tile;
- }
-
-+static void
-+gtk_list_view_prepare_section (GtkListBase *base,
-+ GtkListTile *tile,
-+ guint position)
-+{
-+}
-+
- /* We define the listview as **inert** when the factory isn't used. */
- static gboolean
- gtk_list_view_is_inert (GtkListView *self)
-@@ -222,13 +208,13 @@ gtk_list_view_is_inert (GtkListView *self)
- GtkWidget *widget = GTK_WIDGET (self);
-
- return !gtk_widget_get_visible (widget) ||
-- gtk_widget_get_root (widget) == NULL ||
-- self->factory == NULL;
-+ gtk_widget_get_root (widget) == NULL;
- }
-
- static void
- gtk_list_view_update_factories_with (GtkListView *self,
-- GtkListItemFactory *factory)
-+ GtkListItemFactory *factory,
-+ GtkListItemFactory *header_factory)
- {
- GtkListTile *tile;
-
-@@ -236,8 +222,27 @@ gtk_list_view_update_factories_with (GtkListView *self,
- tile != NULL;
- tile = gtk_rb_tree_node_get_next (tile))
- {
-- if (tile->widget)
-- gtk_list_factory_widget_set_factory (GTK_LIST_FACTORY_WIDGET (tile->widget), factory);
-+ switch (tile->type)
-+ {
-+ case GTK_LIST_TILE_ITEM:
-+ if (tile->widget)
-+ gtk_list_factory_widget_set_factory (GTK_LIST_FACTORY_WIDGET (tile->widget), factory);
-+ break;
-+ case GTK_LIST_TILE_HEADER:
-+ if (tile->widget)
-+ gtk_list_header_widget_set_factory (GTK_LIST_HEADER_WIDGET (tile->widget), header_factory);
-+ break;
-+ case GTK_LIST_TILE_UNMATCHED_HEADER:
-+ case GTK_LIST_TILE_FOOTER:
-+ case GTK_LIST_TILE_UNMATCHED_FOOTER:
-+ case GTK_LIST_TILE_FILLER:
-+ case GTK_LIST_TILE_REMOVED:
-+ g_assert (tile->widget == NULL);
-+ break;
-+ default:
-+ g_assert_not_reached();
-+ break;
-+ }
- }
- }
-
-@@ -245,13 +250,14 @@ static void
- gtk_list_view_update_factories (GtkListView *self)
- {
- gtk_list_view_update_factories_with (self,
-- gtk_list_view_is_inert (self) ? NULL : self->factory);
-+ gtk_list_view_is_inert (self) ? NULL : self->factory,
-+ gtk_list_view_is_inert (self) ? NULL : self->header_factory);
- }
-
- static void
- gtk_list_view_clear_factories (GtkListView *self)
- {
-- gtk_list_view_update_factories_with (self, NULL);
-+ gtk_list_view_update_factories_with (self, NULL, NULL);
- }
-
- static GtkListItemBase *
-@@ -275,6 +281,20 @@ gtk_list_view_create_list_widget (GtkListBase *base)
- return GTK_LIST_ITEM_BASE (result);
- }
-
-+static GtkListHeaderBase *
-+gtk_list_view_create_header_widget (GtkListBase *base)
-+{
-+ GtkListView *self = GTK_LIST_VIEW (base);
-+ GtkListItemFactory *factory;
-+
-+ if (gtk_list_view_is_inert (self))
-+ factory = NULL;
-+ else
-+ factory = self->header_factory;
-+
-+ return GTK_LIST_HEADER_BASE (gtk_list_header_widget_new (factory));
-+}
-+
- static gboolean
- gtk_list_view_get_allocation (GtkListBase *base,
- guint pos,
-@@ -527,8 +547,11 @@ gtk_list_view_measure_list (GtkWidget *widget,
- gtk_widget_measure (tile->widget,
- orientation, for_size,
- &child_min, &child_nat, NULL, NULL);
-- g_array_append_val (min_heights, child_min);
-- g_array_append_val (nat_heights, child_nat);
-+ if (tile->type == GTK_LIST_TILE_ITEM)
-+ {
-+ g_array_append_val (min_heights, child_min);
-+ g_array_append_val (nat_heights, child_nat);
-+ }
- min += child_min;
- nat += child_nat;
- }
-@@ -622,7 +645,8 @@ gtk_list_view_size_allocate (GtkWidget *widget,
- else
- row_height = nat;
- gtk_list_tile_set_area_size (self->item_manager, tile, list_width, row_height);
-- g_array_append_val (heights, row_height);
-+ if (tile->type == GTK_LIST_TILE_ITEM)
-+ g_array_append_val (heights, row_height);
- }
-
- /* step 3: determine height of unknown items and set the positions */
-@@ -723,6 +747,10 @@ gtk_list_view_get_property (GObject *object,
- g_value_set_object (value, self->factory);
- break;
-
-+ case PROP_HEADER_FACTORY:
-+ g_value_set_object (value, self->header_factory);
-+ break;
-+
- case PROP_MODEL:
- g_value_set_object (value, gtk_list_base_get_model (GTK_LIST_BASE (self)));
- break;
-@@ -763,6 +791,10 @@ gtk_list_view_set_property (GObject *object,
- gtk_list_view_set_factory (self, g_value_get_object (value));
- break;
-
-+ case PROP_HEADER_FACTORY:
-+ gtk_list_view_set_header_factory (self, g_value_get_object (value));
-+ break;
-+
- case PROP_MODEL:
- gtk_list_view_set_model (self, g_value_get_object (value));
- break;
-@@ -812,6 +844,8 @@ gtk_list_view_class_init (GtkListViewClass *klass)
-
- list_base_class->split = gtk_list_view_split;
- list_base_class->create_list_widget = gtk_list_view_create_list_widget;
-+ list_base_class->prepare_section = gtk_list_view_prepare_section;
-+ list_base_class->create_header_widget = gtk_list_view_create_header_widget;
- list_base_class->get_allocation = gtk_list_view_get_allocation;
- list_base_class->get_items_in_rect = gtk_list_view_get_items_in_rect;
- list_base_class->get_position_from_allocation = gtk_list_view_get_position_from_allocation;
-@@ -849,6 +883,18 @@ gtk_list_view_class_init (GtkListViewClass *klass)
- GTK_TYPE_LIST_ITEM_FACTORY,
- G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
-
-+ /**
-+ * GtkListView:header-factory: (attributes org.gtk.Property.get=gtk_list_view_get_header_factory org.gtk.Property.set=gtk_list_view_set_header_factory)
-+ *
-+ * Factory for creating header widgets.
-+ *
-+ * Since: 4.12
-+ */
-+ properties[PROP_HEADER_FACTORY] =
-+ g_param_spec_object ("header-factory", NULL, NULL,
-+ GTK_TYPE_LIST_ITEM_FACTORY,
-+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
-+
- /**
- * GtkListView:model: (attributes org.gtk.Property.get=gtk_list_view_get_model org.gtk.Property.set=gtk_list_view_set_model)
- *
-@@ -1054,22 +1100,80 @@ void
- gtk_list_view_set_factory (GtkListView *self,
- GtkListItemFactory *factory)
- {
-- gboolean was_inert;
--
- g_return_if_fail (GTK_IS_LIST_VIEW (self));
- g_return_if_fail (factory == NULL || GTK_IS_LIST_ITEM_FACTORY (factory));
-
-- was_inert = gtk_list_view_is_inert (self);
--
- if (!g_set_object (&self->factory, factory))
- return;
-
-- if (!was_inert || !gtk_list_view_is_inert (self))
-- gtk_list_view_update_factories (self);
-+ gtk_list_view_update_factories (self);
-
- g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FACTORY]);
- }
-
-+/**
-+ * gtk_list_view_get_header_factory: (attributes org.gtk.Method.get_property=header-factory)
-+ * @self: a `GtkListView`
-+ *
-+ * Gets the factory that's currently used to populate section headers.
-+ *
-+ * Returns: (nullable) (transfer none): The factory in use
-+ *
-+ * Since: 4.12
-+ */
-+GtkListItemFactory *
-+gtk_list_view_get_header_factory (GtkListView *self)
-+{
-+ g_return_val_if_fail (GTK_IS_LIST_VIEW (self), NULL);
-+
-+ return self->header_factory;
-+}
-+
-+/**
-+ * gtk_list_view_set_header_factory: (attributes org.gtk.Method.set_property=header-factory)
-+ * @self: a `GtkListView`
-+ * @factory: (nullable) (transfer none): the factory to use
-+ *
-+ * Sets the `GtkListItemFactory` to use for populating the
-+ * [class@Gtk.ListHeader] objects used in section headers.
-+ *
-+ * If this factory is set to %NULL, the list will not show section headers.
-+ *
-+ * Since: 4.12
-+ */
-+void
-+gtk_list_view_set_header_factory (GtkListView *self,
-+ GtkListItemFactory *factory)
-+{
-+ gboolean had_sections;
-+
-+ g_return_if_fail (GTK_IS_LIST_VIEW (self));
-+ g_return_if_fail (factory == NULL || GTK_IS_LIST_ITEM_FACTORY (factory));
-+
-+ had_sections = gtk_list_item_manager_get_has_sections (self->item_manager);
-+
-+ if (!g_set_object (&self->header_factory, factory))
-+ return;
-+
-+ gtk_list_item_manager_set_has_sections (self->item_manager, factory != NULL);
-+
-+ if (!gtk_list_view_is_inert (self) &&
-+ had_sections && gtk_list_item_manager_get_has_sections (self->item_manager))
-+ {
-+ GtkListTile *tile;
-+
-+ for (tile = gtk_list_item_manager_get_first (self->item_manager);
-+ tile != NULL;
-+ tile = gtk_rb_tree_node_get_next (tile))
-+ {
-+ if (tile->widget && tile->type == GTK_LIST_TILE_HEADER)
-+ gtk_list_header_widget_set_factory (GTK_LIST_HEADER_WIDGET (tile->widget), factory);
-+ }
-+ }
-+
-+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_HEADER_FACTORY]);
-+}
-+
- /**
- * gtk_list_view_set_show_separators: (attributes org.gtk.Method.set_property=show-separators)
- * @self: a `GtkListView`
-@@ -1139,7 +1243,7 @@ gtk_list_view_set_single_click_activate (GtkListView *self,
- tile != NULL;
- tile = gtk_rb_tree_node_get_next (tile))
- {
-- if (tile->widget)
-+ if (tile->widget && tile->type == GTK_LIST_TILE_ITEM)
- gtk_list_factory_widget_set_single_click_activate (GTK_LIST_FACTORY_WIDGET (tile->widget), single_click_activate);
- }
-
-diff --git a/gtk/gtklistview.h b/gtk/gtklistview.h
-index b3e3afb60a1fc6634e1574edbfe87f3b89539c3a..df4901e319a1e2d84f1b3ebc62c62fa8142d5fb3 100644
---- a/gtk/gtklistview.h
-+++ b/gtk/gtklistview.h
-@@ -57,6 +57,13 @@ GDK_AVAILABLE_IN_ALL
- GtkListItemFactory *
- gtk_list_view_get_factory (GtkListView *self);
-
-+GDK_AVAILABLE_IN_4_12
-+void gtk_list_view_set_header_factory (GtkListView *self,
-+ GtkListItemFactory *factory);
-+GDK_AVAILABLE_IN_4_12
-+GtkListItemFactory *
-+ gtk_list_view_get_header_factory (GtkListView *self);
-+
- GDK_AVAILABLE_IN_ALL
- void gtk_list_view_set_show_separators (GtkListView *self,
- gboolean show_separators);
-diff --git a/gtk/gtklistviewprivate.h b/gtk/gtklistviewprivate.h
-index 002376fc1e52f1cbdee692ad34504280c6e3d16b..7dd02ce3f83db126016d2617d36a871a597988e1 100644
---- a/gtk/gtklistviewprivate.h
-+++ b/gtk/gtklistviewprivate.h
-@@ -30,6 +30,7 @@ struct _GtkListView
-
- GtkListItemManager *item_manager;
- GtkListItemFactory *factory;
-+ GtkListItemFactory *header_factory;
- gboolean show_separators;
- gboolean single_click_activate;
- };
-diff --git a/gtk/gtkmultiselection.c b/gtk/gtkmultiselection.c
-index 055472d3d45cb3aec091acc48c48e61ad2d0a97d..f549541baa6d7779f98b38ed75f4b7a08bcdda58 100644
---- a/gtk/gtkmultiselection.c
-+++ b/gtk/gtkmultiselection.c
-@@ -22,6 +22,7 @@
- #include "gtkmultiselection.h"
-
- #include "gtkbitset.h"
-+#include "gtksectionmodelprivate.h"
- #include "gtkselectionmodel.h"
-
- /**
-@@ -94,6 +95,23 @@ gtk_multi_selection_list_model_init (GListModelInterface *iface)
- iface->get_item = gtk_multi_selection_get_item;
- }
-
-+static void
-+gtk_multi_selection_get_section (GtkSectionModel *model,
-+ guint position,
-+ guint *out_start,
-+ guint *out_end)
-+{
-+ GtkMultiSelection *self = GTK_MULTI_SELECTION (model);
-+
-+ gtk_list_model_get_section (self->model, position, out_start, out_end);
-+}
-+
-+static void
-+gtk_multi_selection_section_model_init (GtkSectionModelInterface *iface)
-+{
-+ iface->get_section = gtk_multi_selection_get_section;
-+}
-+
- static gboolean
- gtk_multi_selection_is_selected (GtkSelectionModel *model,
- guint position)
-@@ -205,6 +223,8 @@ gtk_multi_selection_selection_model_init (GtkSelectionModelInterface *iface)
- G_DEFINE_TYPE_EXTENDED (GtkMultiSelection, gtk_multi_selection, G_TYPE_OBJECT, 0,
- G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL,
- gtk_multi_selection_list_model_init)
-+ G_IMPLEMENT_INTERFACE (GTK_TYPE_SECTION_MODEL,
-+ gtk_multi_selection_section_model_init)
- G_IMPLEMENT_INTERFACE (GTK_TYPE_SELECTION_MODEL,
- gtk_multi_selection_selection_model_init))
-
-diff --git a/gtk/gtknoselection.c b/gtk/gtknoselection.c
-index c0848f7c0e860479dc7efbe17e9878f05da8ca28..20bff24df292306bf787d5e4743974716a7eddf3 100644
---- a/gtk/gtknoselection.c
-+++ b/gtk/gtknoselection.c
-@@ -22,6 +22,7 @@
- #include "gtknoselection.h"
-
- #include "gtkbitset.h"
-+#include "gtksectionmodelprivate.h"
- #include "gtkselectionmodel.h"
-
- /**
-@@ -92,6 +93,23 @@ gtk_no_selection_list_model_init (GListModelInterface *iface)
- iface->get_item = gtk_no_selection_get_item;
- }
-
-+static void
-+gtk_no_selection_get_section (GtkSectionModel *model,
-+ guint position,
-+ guint *out_start,
-+ guint *out_end)
-+{
-+ GtkNoSelection *self = GTK_NO_SELECTION (model);
-+
-+ gtk_list_model_get_section (self->model, position, out_start, out_end);
-+}
-+
-+static void
-+gtk_no_selection_section_model_init (GtkSectionModelInterface *iface)
-+{
-+ iface->get_section = gtk_no_selection_get_section;
-+}
-+
- static gboolean
- gtk_no_selection_is_selected (GtkSelectionModel *model,
- guint position)
-@@ -117,6 +135,8 @@ gtk_no_selection_selection_model_init (GtkSelectionModelInterface *iface)
- G_DEFINE_TYPE_EXTENDED (GtkNoSelection, gtk_no_selection, G_TYPE_OBJECT, 0,
- G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL,
- gtk_no_selection_list_model_init)
-+ G_IMPLEMENT_INTERFACE (GTK_TYPE_SECTION_MODEL,
-+ gtk_no_selection_section_model_init)
- G_IMPLEMENT_INTERFACE (GTK_TYPE_SELECTION_MODEL,
- gtk_no_selection_selection_model_init))
-
-diff --git a/gtk/gtksectionmodel.c b/gtk/gtksectionmodel.c
-new file mode 100644
-index 0000000000000000000000000000000000000000..a5084ab340ab3bd6356cfb256f16746df298b987
---- /dev/null
-+++ b/gtk/gtksectionmodel.c
-@@ -0,0 +1,225 @@
-+/*
-+ * Copyright © 2022 Benjamin Otte
-+ *
-+ * This library is free software; you can redistribute it and/or
-+ * modify it under the terms of the GNU Lesser General Public
-+ * License as published by the Free Software Foundation; either
-+ * version 2.1 of the License, or (at your option) any later version.
-+ *
-+ * This library is distributed in the hope that it will be useful,
-+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
-+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-+ * Lesser General Public License for more details.
-+ *
-+ * You should have received a copy of the GNU Lesser General Public
-+ * License along with this library. If not, see .
-+ *
-+ * Authors: Benjamin Otte
-+ */
-+
-+#include "config.h"
-+
-+#include "gtksectionmodelprivate.h"
-+
-+#include "gtkmarshalers.h"
-+
-+/**
-+ * GtkSectionModel:
-+ *
-+ * `GtkSectionModel` is an interface that adds support for section to list models.
-+ *
-+ * This support is then used by widgets using list models to be able to group their
-+ * items into sections.
-+ *
-+ * Many GTK list models support sections inherently, or they pass through the sections
-+ * of a model they are wrapping.
-+ *
-+ * A `GtkSectionModel` groups successive items into so-called sections. List widgets
-+ * like `GtkListView` then allow displaying section headers for these sections.
-+ *
-+ * When the section groupings of a model changes, the model will emit the
-+ * [signal@Gtk.SectionModel::sections-changed] signal by calling the
-+ * [method@Gtk.SectionModel.sections_changed] function. All sections in the given range
-+ * now need to be queried again.
-+ * The [signal@Gio.ListModel::items-changed] signal has the same effect, all sections in
-+ * that range are invalidated, too.
-+ *
-+ * Since: 4.12
-+ */
-+
-+G_DEFINE_INTERFACE (GtkSectionModel, gtk_section_model, G_TYPE_LIST_MODEL)
-+
-+enum {
-+ SECTIONS_CHANGED,
-+ LAST_SIGNAL
-+};
-+
-+static guint signals[LAST_SIGNAL] = { 0 };
-+
-+static void
-+gtk_section_model_default_get_section (GtkSectionModel *self,
-+ guint position,
-+ guint *out_start,
-+ guint *out_end)
-+{
-+ guint n_items = g_list_model_get_n_items (G_LIST_MODEL (self));
-+
-+ if (position >= n_items)
-+ {
-+ *out_start = n_items;
-+ *out_end = G_MAXUINT;
-+ }
-+
-+ *out_start = 0;
-+ *out_end = n_items;
-+}
-+
-+static void
-+gtk_section_model_default_init (GtkSectionModelInterface *iface)
-+{
-+ iface->get_section = gtk_section_model_default_get_section;
-+
-+ /**
-+ * GtkSectionModel::sections-changed
-+ * @model: a `GtkSectionModel`
-+ * @position: The first item that may have changed
-+ * @n_items: number of items with changes
-+ *
-+ * Emitted when the start-of-section state of some of the items in @model changes.
-+ *
-+ * Note that this signal does not specify the new section state of the
-+ * items, they need to be queried manually. It is also not necessary for
-+ * a model to change the section state of any of the items in the section
-+ * model, though it would be rather useless to emit such a signal.
-+ *
-+ * The [signal@Gio.ListModel::items-changed] implies the effect of the
-+ * [signal@Gtk.SectionModel::sections-changed] signal for all the items
-+ * it covers.
-+ *
-+ * Since: 4.12
-+ */
-+ signals[SECTIONS_CHANGED] =
-+ g_signal_new ("sections-changed",
-+ GTK_TYPE_SECTION_MODEL,
-+ G_SIGNAL_RUN_LAST,
-+ 0,
-+ NULL, NULL,
-+ _gtk_marshal_VOID__UINT_UINT,
-+ G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
-+ g_signal_set_va_marshaller (signals[SECTIONS_CHANGED],
-+ GTK_TYPE_SECTION_MODEL,
-+ _gtk_marshal_VOID__UINT_UINTv);
-+}
-+
-+/**
-+ * gtk_section_model_get_section:
-+ * @self: a `GtkSectionModel`
-+ * @position: the position of the item to query
-+ * @out_start: (out caller-allocates): the position of the first
-+ * item in the section
-+ * @out_end: (out caller-allocates): the position of the first
-+ * item not part of the section anymore.
-+ *
-+ * Query the section that covers the given position. The number of
-+ * items in the section can be computed by `out_end - out_start`.
-+ *
-+ * If the position is larger than the number of items, a single
-+ * range from n_items to G_MAXUINT will be returned.
-+ *
-+ * Since: 4.12
-+ */
-+void
-+gtk_section_model_get_section (GtkSectionModel *self,
-+ guint position,
-+ guint *out_start,
-+ guint *out_end)
-+{
-+ GtkSectionModelInterface *iface;
-+
-+ g_return_if_fail (GTK_IS_SECTION_MODEL (self));
-+ g_return_if_fail (out_start != NULL);
-+ g_return_if_fail (out_end != NULL);
-+
-+ iface = GTK_SECTION_MODEL_GET_IFACE (self);
-+ iface->get_section (self, position, out_start, out_end);
-+
-+ g_warn_if_fail (*out_start < *out_end);
-+}
-+
-+/* A version of gtk_section_model_get_section() that handles NULL
-+ * (treats it as the empty list) and any GListModel (treats it as
-+ * a single section).
-+ **/
-+void
-+gtk_list_model_get_section (GListModel *self,
-+ guint position,
-+ guint *out_start,
-+ guint *out_end)
-+{
-+ g_return_if_fail (out_start != NULL);
-+ g_return_if_fail (out_end != NULL);
-+
-+ if (self == NULL)
-+ {
-+ *out_start = 0;
-+ *out_end = G_MAXUINT;
-+ return;
-+ }
-+
-+ g_return_if_fail (G_IS_LIST_MODEL (self));
-+
-+ if (!GTK_IS_SECTION_MODEL (self))
-+ {
-+ guint n_items = g_list_model_get_n_items (self);
-+
-+ if (position < n_items)
-+ {
-+ *out_start = 0;
-+ *out_end = G_MAXUINT;
-+ }
-+ else
-+ {
-+ *out_start = n_items;
-+ *out_end = G_MAXUINT;
-+ }
-+
-+ return;
-+ }
-+
-+ gtk_section_model_get_section (GTK_SECTION_MODEL (self), position, out_start, out_end);
-+}
-+
-+/**
-+ * gtk_section_model_section_changed:
-+ * @self: a `GtkSectionModel`
-+ * @position: the first changed item
-+ * @n_items: the number of changed items
-+ *
-+ * This function emits the [signal@Gtk.SectionModel::section-changed]
-+ * signal to notify about changes to sections. It must cover all
-+ * positions that used to be a section start or that are now a section
-+ * start. It does not have to cover all positions for which the section
-+ * has changed.
-+ *
-+ * The [signal@Gio.ListModel::items-changed] implies the effect of the
-+ * [signal@Gtk.SectionModel::section-changed] signal for all the items
-+ * it covers.
-+ *
-+ * It is recommended that when changes to the items cause section changes
-+ * in a larger range, that the larger range is included in the emission
-+ * of the [signal@Gio.ListModel::items-changed] instead of emitting
-+ * two signals.
-+ *
-+ * Since: 4.12
-+ */
-+void
-+gtk_section_model_sections_changed (GtkSectionModel *self,
-+ guint position,
-+ guint n_items)
-+{
-+ g_return_if_fail (GTK_IS_SECTION_MODEL (self));
-+ g_return_if_fail (n_items > 0);
-+ g_return_if_fail (position + n_items <= g_list_model_get_n_items (G_LIST_MODEL (self)));
-+
-+ g_signal_emit (self, signals[SECTIONS_CHANGED], 0, position, n_items);
-+}
-diff --git a/gtk/gtksectionmodel.h b/gtk/gtksectionmodel.h
-new file mode 100644
-index 0000000000000000000000000000000000000000..a242b42c20ec07c3eaae2faac3f8ecbc2879c13e
---- /dev/null
-+++ b/gtk/gtksectionmodel.h
-@@ -0,0 +1,72 @@
-+/*
-+ * Copyright © 2022 Benjamin Otte
-+ *
-+ * This library is free software; you can redistribute it and/or
-+ * modify it under the terms of the GNU Lesser General Public
-+ * License as published by the Free Software Foundation; either
-+ * version 2.1 of the License, or (at your option) any later version.
-+ *
-+ * This library is distributed in the hope that it will be useful,
-+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
-+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-+ * Lesser General Public License for more details.
-+ *
-+ * You should have received a copy of the GNU Lesser General Public
-+ * License along with this library. If not, see .
-+ *
-+ * Authors: Benjamin Otte
-+ */
-+
-+#pragma once
-+
-+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
-+#error "Only can be included directly."
-+#endif
-+
-+#include
-+
-+G_BEGIN_DECLS
-+
-+#define GTK_TYPE_SECTION_MODEL (gtk_section_model_get_type ())
-+
-+GDK_AVAILABLE_IN_4_12
-+G_DECLARE_INTERFACE (GtkSectionModel, gtk_section_model, GTK, SECTION_MODEL, GListModel)
-+
-+/**
-+ * GtkSectionModelInterface:
-+ * @get_section: Return the section that covers the given position. If
-+ * the position is outside the number of items, returns a single range from
-+ * n_items to G_MAXUINT
-+ *
-+ * The list of virtual functions for the `GtkSectionModel` interface.
-+ * No function must be implemented, but unless `GtkSectionModel::get_section()`
-+ * is implemented, the whole model will just be a single section.
-+ *
-+ * Since: 4.12
-+ */
-+struct _GtkSectionModelInterface
-+{
-+ /*< private >*/
-+ GTypeInterface g_iface;
-+
-+ /*< public >*/
-+ void (* get_section) (GtkSectionModel *self,
-+ guint position,
-+ guint *out_start,
-+ guint *out_end);
-+};
-+
-+GDK_AVAILABLE_IN_4_12
-+void gtk_section_model_get_section (GtkSectionModel *self,
-+ guint position,
-+ guint *out_start,
-+ guint *out_end);
-+
-+/* for implementations only */
-+GDK_AVAILABLE_IN_4_12
-+void gtk_section_model_sections_changed (GtkSectionModel *self,
-+ guint position,
-+ guint n_items);
-+
-+G_END_DECLS
-+
-diff --git a/gtk/gtksectionmodelprivate.h b/gtk/gtksectionmodelprivate.h
-new file mode 100644
-index 0000000000000000000000000000000000000000..e6cfb0cb1b105e3e409b531ae80bb9bb1186f155
---- /dev/null
-+++ b/gtk/gtksectionmodelprivate.h
-@@ -0,0 +1,14 @@
-+#pragma once
-+
-+#include "gtksectionmodel.h"
-+
-+G_BEGIN_DECLS
-+
-+void gtk_list_model_get_section (GListModel *self,
-+ guint position,
-+ guint *out_start,
-+ guint *out_end);
-+
-+
-+G_END_DECLS
-+
-diff --git a/gtk/gtksingleselection.c b/gtk/gtksingleselection.c
-index e4e148ec83fcb6f07a86b3d803bb9778494394b9..07f0c7eaee634cdfbf313e1338b558e49a5a7f90 100644
---- a/gtk/gtksingleselection.c
-+++ b/gtk/gtksingleselection.c
-@@ -22,6 +22,7 @@
- #include "gtksingleselection.h"
-
- #include "gtkbitset.h"
-+#include "gtksectionmodelprivate.h"
- #include "gtkselectionmodel.h"
-
- /**
-@@ -103,6 +104,23 @@ gtk_single_selection_list_model_init (GListModelInterface *iface)
- iface->get_item = gtk_single_selection_get_item;
- }
-
-+static void
-+gtk_single_selection_get_section (GtkSectionModel *model,
-+ guint position,
-+ guint *out_start,
-+ guint *out_end)
-+{
-+ GtkSingleSelection *self = GTK_SINGLE_SELECTION (model);
-+
-+ gtk_list_model_get_section (self->model, position, out_start, out_end);
-+}
-+
-+static void
-+gtk_single_selection_section_model_init (GtkSectionModelInterface *iface)
-+{
-+ iface->get_section = gtk_single_selection_get_section;
-+}
-+
- static gboolean
- gtk_single_selection_is_selected (GtkSelectionModel *model,
- guint position)
-@@ -167,6 +185,8 @@ gtk_single_selection_selection_model_init (GtkSelectionModelInterface *iface)
- G_DEFINE_TYPE_EXTENDED (GtkSingleSelection, gtk_single_selection, G_TYPE_OBJECT, 0,
- G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL,
- gtk_single_selection_list_model_init)
-+ G_IMPLEMENT_INTERFACE (GTK_TYPE_SECTION_MODEL,
-+ gtk_single_selection_section_model_init)
- G_IMPLEMENT_INTERFACE (GTK_TYPE_SELECTION_MODEL,
- gtk_single_selection_selection_model_init))
-
-diff --git a/gtk/gtksortlistmodel.c b/gtk/gtksortlistmodel.c
-index c2feb9e2502c1b5a0bb902e24c5384dcb02c43df..105dfd435853847c36cf8adffeafde27323019fd 100644
---- a/gtk/gtksortlistmodel.c
-+++ b/gtk/gtksortlistmodel.c
-@@ -22,7 +22,9 @@
- #include "gtksortlistmodel.h"
-
- #include "gtkbitset.h"
-+#include "gtkmultisorter.h"
- #include "gtkprivate.h"
-+#include "gtksectionmodel.h"
- #include "gtksorterprivate.h"
- #include "timsort/gtktimsortprivate.h"
-
-@@ -73,6 +75,13 @@
- * If you run into performance issues with `GtkSortListModel`,
- * it is strongly recommended that you write your own sorting list
- * model.
-+ *
-+ * `GtkSortListModel` allows sorting the items into sections. It
-+ * implements `GtkSectionModel` and when [property@Gtk.SortListModel:section-sorter]
-+ * is set, it will sort all items with that sorter and items comparing
-+ * equal with it will be put into the same section.
-+ * The [property@Gtk.SortListModel:sorter] will then be used to sort items
-+ * inside their sections.
- */
-
- enum {
-@@ -82,6 +91,7 @@ enum {
- PROP_MODEL,
- PROP_N_ITEMS,
- PROP_PENDING,
-+ PROP_SECTION_SORTER,
- PROP_SORTER,
- NUM_PROPERTIES
- };
-@@ -92,6 +102,8 @@ struct _GtkSortListModel
-
- GListModel *model;
- GtkSorter *sorter;
-+ GtkSorter *section_sorter;
-+ GtkSorter *real_sorter;
- gboolean incremental;
-
- GtkTimSort sort; /* ongoing sort operation */
-@@ -99,6 +111,7 @@ struct _GtkSortListModel
-
- guint n_items;
- GtkSortKeys *sort_keys;
-+ GtkSortKeys *section_sort_keys; /* we assume they are compatible with the sort keys because they're the first element */
- gsize key_size;
- gpointer keys;
- GtkBitset *missing_keys;
-@@ -174,8 +187,159 @@ gtk_sort_list_model_model_init (GListModelInterface *iface)
- iface->get_item = gtk_sort_list_model_get_item;
- }
-
-+static void
-+gtk_sort_list_model_ensure_key (GtkSortListModel *self,
-+ guint pos)
-+{
-+ gpointer item;
-+
-+ if (!gtk_bitset_contains (self->missing_keys, pos))
-+ return;
-+
-+ item = g_list_model_get_item (self->model, pos);
-+ gtk_sort_keys_init_key (self->sort_keys, item, key_from_pos (self, pos));
-+ g_object_unref (item);
-+
-+ gtk_bitset_remove (self->missing_keys, pos);
-+}
-+
-+static void
-+gtk_sort_list_model_get_section_unsorted (GtkSortListModel *self,
-+ guint position,
-+ guint *out_start,
-+ guint *out_end)
-+{
-+ gpointer *pos, *start, *end;
-+
-+ pos = &self->positions[position];
-+ gtk_sort_list_model_ensure_key (self, pos_from_key (self, *pos));
-+
-+ for (start = pos;
-+ start > self->positions;
-+ start--)
-+ {
-+ gtk_sort_list_model_ensure_key (self, pos_from_key (self, start[-1]));
-+ if (gtk_sort_keys_compare (self->section_sort_keys, start[-1], *pos) != GTK_ORDERING_EQUAL)
-+ break;
-+ }
-+
-+ for (end = pos + 1;
-+ end < &self->positions[self->n_items];
-+ end++)
-+ {
-+ gtk_sort_list_model_ensure_key (self, pos_from_key (self, *end));
-+ if (gtk_sort_keys_compare (self->section_sort_keys, *end, *pos) != GTK_ORDERING_EQUAL)
-+ break;
-+ }
-+
-+ *out_start = start - self->positions;
-+ *out_end = end - self->positions;
-+}
-+
-+static void
-+gtk_sort_list_model_get_section_sorted (GtkSortListModel *self,
-+ guint position,
-+ guint *out_start,
-+ guint *out_end)
-+{
-+ gpointer *pos;
-+ guint step, min, max, mid;
-+
-+ pos = &self->positions[position];
-+
-+ max = position;
-+ step = 1;
-+ while (max > 0)
-+ {
-+ min = max - MIN (max, step);
-+ step *= 2;
-+ if (gtk_sort_keys_compare (self->section_sort_keys, self->positions[min], *pos) == GTK_ORDERING_EQUAL)
-+ {
-+ max = min;
-+ continue;
-+ }
-+ /* now min is different, max is equal, bsearch where that changes */
-+ while (max - min > 1)
-+ {
-+ mid = (max + min) / 2;
-+ if (gtk_sort_keys_compare (self->section_sort_keys, self->positions[mid], *pos) == GTK_ORDERING_EQUAL)
-+ max = mid;
-+ else
-+ min = mid;
-+ }
-+ break;
-+ }
-+ *out_start = max;
-+
-+ min = position;
-+ step = 1;
-+ while (min < self->n_items - 1)
-+ {
-+ max = min + MIN (self->n_items - 1 - min, step);
-+ step *= 2;
-+ if (gtk_sort_keys_compare (self->section_sort_keys, self->positions[max], *pos) == GTK_ORDERING_EQUAL)
-+ {
-+ min = max;
-+ continue;
-+ }
-+ /* now min is equal, max is different, bsearch where that changes */
-+ while (max - min > 1)
-+ {
-+ mid = (max + min) / 2;
-+ if (gtk_sort_keys_compare (self->section_sort_keys, self->positions[mid], *pos) == GTK_ORDERING_EQUAL)
-+ min = mid;
-+ else
-+ max = mid;
-+ }
-+ break;
-+ }
-+ *out_end = min + 1;
-+}
-+
-+static void
-+gtk_sort_list_model_get_section (GtkSectionModel *model,
-+ guint position,
-+ guint *out_start,
-+ guint *out_end)
-+{
-+ GtkSortListModel *self = GTK_SORT_LIST_MODEL (model);
-+
-+ if (position >= self->n_items)
-+ {
-+ *out_start = self->n_items;
-+ *out_end = G_MAXUINT;
-+ return;
-+ }
-+
-+ if (self->section_sort_keys == NULL)
-+ {
-+ *out_start = 0;
-+ *out_end = self->n_items;
-+ return;
-+ }
-+
-+ /* When the list is not sorted:
-+ * - keys may not exist yet
-+ * - equal items may not be adjacent
-+ * So add a slow path that can deal with that, but is O(N).
-+ * The fast path is O(log N) and will be used for I guess
-+ * 99% of cases.
-+ */
-+ if (self->sort_cb)
-+ gtk_sort_list_model_get_section_unsorted (self, position, out_start, out_end);
-+ else
-+ gtk_sort_list_model_get_section_sorted (self, position, out_start, out_end);
-+}
-+
-+static void
-+gtk_sort_list_model_section_model_init (GtkSectionModelInterface *iface)
-+{
-+ iface->get_section = gtk_sort_list_model_get_section;
-+}
-+
- G_DEFINE_TYPE_WITH_CODE (GtkSortListModel, gtk_sort_list_model, G_TYPE_OBJECT,
-- G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_sort_list_model_model_init))
-+ G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_sort_list_model_model_init)
-+ G_IMPLEMENT_INTERFACE (GTK_TYPE_SECTION_MODEL, gtk_sort_list_model_section_model_init))
-
- static gboolean
- gtk_sort_list_model_is_sorting (GtkSortListModel *self)
-@@ -379,6 +543,7 @@ gtk_sort_list_model_clear_keys (GtkSortListModel *self)
- g_clear_pointer (&self->missing_keys, gtk_bitset_unref);
- g_clear_pointer (&self->keys, g_free);
- g_clear_pointer (&self->sort_keys, gtk_sort_keys_unref);
-+ g_clear_pointer (&self->section_sort_keys, gtk_sort_keys_unref);
- self->key_size = 0;
- }
-
-@@ -426,9 +591,9 @@ gtk_sort_list_model_clear_items (GtkSortListModel *self,
- static gboolean
- gtk_sort_list_model_should_sort (GtkSortListModel *self)
- {
-- return self->sorter != NULL &&
-+ return self->real_sorter != NULL &&
- self->model != NULL &&
-- gtk_sorter_get_order (self->sorter) != GTK_SORTER_ORDER_NONE;
-+ gtk_sorter_get_order (self->real_sorter) != GTK_SORTER_ORDER_NONE;
- }
-
- static void
-@@ -436,9 +601,12 @@ gtk_sort_list_model_create_keys (GtkSortListModel *self)
- {
- g_assert (self->keys == NULL);
- g_assert (self->sort_keys == NULL);
-+ g_assert (self->section_sort_keys == NULL);
- g_assert (self->key_size == 0);
-
-- self->sort_keys = gtk_sorter_get_keys (self->sorter);
-+ self->sort_keys = gtk_sorter_get_keys (self->real_sorter);
-+ if (self->section_sorter)
-+ self->section_sort_keys = gtk_sorter_get_keys (self->section_sorter);
- self->key_size = gtk_sort_keys_get_key_size (self->sort_keys);
- self->keys = g_malloc_n (self->n_items, self->key_size);
- self->missing_keys = gtk_bitset_new_range (0, self->n_items);
-@@ -646,6 +814,10 @@ gtk_sort_list_model_set_property (GObject *object,
- gtk_sort_list_model_set_model (self, g_value_get_object (value));
- break;
-
-+ case PROP_SECTION_SORTER:
-+ gtk_sort_list_model_set_section_sorter (self, g_value_get_object (value));
-+ break;
-+
- case PROP_SORTER:
- gtk_sort_list_model_set_sorter (self, g_value_get_object (value));
- break;
-@@ -686,6 +858,10 @@ gtk_sort_list_model_get_property (GObject *object,
- g_value_set_uint (value, gtk_sort_list_model_get_pending (self));
- break;
-
-+ case PROP_SECTION_SORTER:
-+ g_value_set_object (value, self->section_sorter);
-+ break;
-+
- case PROP_SORTER:
- g_value_set_object (value, self->sorter);
- break;
-@@ -763,13 +939,42 @@ gtk_sort_list_model_clear_model (GtkSortListModel *self)
- }
-
- static void
--gtk_sort_list_model_clear_sorter (GtkSortListModel *self)
-+gtk_sort_list_model_clear_real_sorter (GtkSortListModel *self)
- {
-- if (self->sorter == NULL)
-+ if (self->real_sorter == NULL)
- return;
-
-- g_signal_handlers_disconnect_by_func (self->sorter, gtk_sort_list_model_sorter_changed_cb, self);
-- g_clear_object (&self->sorter);
-+ g_signal_handlers_disconnect_by_func (self->real_sorter, gtk_sort_list_model_sorter_changed_cb, self);
-+ g_clear_object (&self->real_sorter);
-+}
-+
-+static void
-+gtk_sort_list_model_ensure_real_sorter (GtkSortListModel *self)
-+{
-+ if (self->sorter)
-+ {
-+ if (self->section_sorter)
-+ {
-+ GtkMultiSorter *multi;
-+
-+ multi = gtk_multi_sorter_new ();
-+ self->real_sorter = GTK_SORTER (multi);
-+ gtk_multi_sorter_append (multi, g_object_ref (self->section_sorter));
-+ gtk_multi_sorter_append (multi, g_object_ref (self->sorter));
-+ }
-+ else
-+ self->real_sorter = g_object_ref (self->sorter);
-+ }
-+ else
-+ {
-+ if (self->section_sorter)
-+ self->real_sorter = g_object_ref (self->section_sorter);
-+ }
-+
-+ if (self->real_sorter)
-+ g_signal_connect (self->real_sorter, "changed", G_CALLBACK (gtk_sort_list_model_sorter_changed_cb), self);
-+
-+ gtk_sort_list_model_sorter_changed_cb (self->real_sorter, GTK_SORTER_CHANGE_DIFFERENT, self);
- }
-
- static void
-@@ -778,7 +983,9 @@ gtk_sort_list_model_dispose (GObject *object)
- GtkSortListModel *self = GTK_SORT_LIST_MODEL (object);
-
- gtk_sort_list_model_clear_model (self);
-- gtk_sort_list_model_clear_sorter (self);
-+ gtk_sort_list_model_clear_real_sorter (self);
-+ g_clear_object (&self->section_sorter);
-+ g_clear_object (&self->sorter);
-
- G_OBJECT_CLASS (gtk_sort_list_model_parent_class)->dispose (object);
- };
-@@ -846,6 +1053,18 @@ gtk_sort_list_model_class_init (GtkSortListModelClass *class)
- 0, G_MAXUINT, 0,
- GTK_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY);
-
-+ /**
-+ * GtkSortListModel:section-sorter: (attributes org.gtk.Property.get=gtk_sort_list_model_get_section_sorter org.gtk.Property.set=gtk_sort_list_model_set_section_sorter)
-+ *
-+ * The section sorter for this model, if one is set.
-+ *
-+ * Since: 4.12
-+ */
-+ properties[PROP_SECTION_SORTER] =
-+ g_param_spec_object ("section-sorter", NULL, NULL,
-+ GTK_TYPE_SORTER,
-+ GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
-+
- /**
- * GtkSortListModel:sorter: (attributes org.gtk.Property.get=gtk_sort_list_model_get_sorter org.gtk.Property.set=gtk_sort_list_model_set_sorter)
- *
-@@ -972,15 +1191,16 @@ gtk_sort_list_model_set_sorter (GtkSortListModel *self,
- g_return_if_fail (GTK_IS_SORT_LIST_MODEL (self));
- g_return_if_fail (sorter == NULL || GTK_IS_SORTER (sorter));
-
-- gtk_sort_list_model_clear_sorter (self);
-+ if (self->sorter == sorter)
-+ return;
-+
-+ gtk_sort_list_model_clear_real_sorter (self);
-+ g_clear_object (&self->sorter);
-
- if (sorter)
-- {
-- self->sorter = g_object_ref (sorter);
-- g_signal_connect (sorter, "changed", G_CALLBACK (gtk_sort_list_model_sorter_changed_cb), self);
-- }
-+ self->sorter = g_object_ref (sorter);
-
-- gtk_sort_list_model_sorter_changed_cb (sorter, GTK_SORTER_CHANGE_DIFFERENT, self);
-+ gtk_sort_list_model_ensure_real_sorter (self);
-
- g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SORTER]);
- }
-@@ -1001,6 +1221,55 @@ gtk_sort_list_model_get_sorter (GtkSortListModel *self)
- return self->sorter;
- }
-
-+/**
-+ * gtk_sort_list_model_set_section_sorter: (attributes org.gtk.Method.set_property=section-sorter)
-+ * @self: a `GtkSortListModel`
-+ * @sorter: (nullable): the `GtkSorter` to sort @model with
-+ *
-+ * Sets a new section sorter on @self.
-+ *
-+ * Since: 4.12
-+ */
-+void
-+gtk_sort_list_model_set_section_sorter (GtkSortListModel *self,
-+ GtkSorter *sorter)
-+{
-+ g_return_if_fail (GTK_IS_SORT_LIST_MODEL (self));
-+ g_return_if_fail (sorter == NULL || GTK_IS_SORTER (sorter));
-+
-+ if (self->section_sorter == sorter)
-+ return;
-+
-+ gtk_sort_list_model_clear_real_sorter (self);
-+ g_clear_object (&self->section_sorter);
-+
-+ if (sorter)
-+ self->section_sorter = g_object_ref (sorter);
-+
-+ gtk_sort_list_model_ensure_real_sorter (self);
-+
-+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SECTION_SORTER]);
-+}
-+
-+/**
-+ * gtk_sort_list_model_get_section_sorter: (attributes org.gtk.Method.get_property=section-sorter)
-+ * @self: a `GtkSortListModel`
-+ *
-+ * Gets the section sorter that is used to sort items of @self into
-+ * sections.
-+ *
-+ * Returns: (nullable) (transfer none): the sorter of #self
-+ *
-+ * Since: 4.12
-+ */
-+GtkSorter *
-+gtk_sort_list_model_get_section_sorter (GtkSortListModel *self)
-+{
-+ g_return_val_if_fail (GTK_IS_SORT_LIST_MODEL (self), NULL);
-+
-+ return self->section_sorter;
-+}
-+
- /**
- * gtk_sort_list_model_set_incremental: (attributes org.gtk.Method.set_property=incremental)
- * @self: a `GtkSortListModel`
-diff --git a/gtk/gtksortlistmodel.h b/gtk/gtksortlistmodel.h
-index 957f5b0019f176f3cf937ea75743db44173ba197..64bb0d019cabacad124779f1648208ed4dd0972e 100644
---- a/gtk/gtksortlistmodel.h
-+++ b/gtk/gtksortlistmodel.h
-@@ -45,6 +45,12 @@ void gtk_sort_list_model_set_sorter (GtkSortListMode
- GDK_AVAILABLE_IN_ALL
- GtkSorter * gtk_sort_list_model_get_sorter (GtkSortListModel *self);
-
-+GDK_AVAILABLE_IN_4_12
-+void gtk_sort_list_model_set_section_sorter (GtkSortListModel *self,
-+ GtkSorter *sorter);
-+GDK_AVAILABLE_IN_4_12
-+GtkSorter * gtk_sort_list_model_get_section_sorter (GtkSortListModel *self);
-+
- GDK_AVAILABLE_IN_ALL
- void gtk_sort_list_model_set_model (GtkSortListModel *self,
- GListModel *model);
-diff --git a/gtk/meson.build b/gtk/meson.build
-index b3975fb3fcd0a67eb15f0a9e6812b7c8bd44282c..ec214f0d741979ee4ff1489cd57a4d83d31befab 100644
---- a/gtk/meson.build
-+++ b/gtk/meson.build
-@@ -271,6 +271,9 @@ gtk_public_sources = files([
- 'gtklinkbutton.c',
- 'gtklistbox.c',
- 'gtklistfactorywidget.c',
-+ 'gtklistheader.c',
-+ 'gtklistheaderbase.c',
-+ 'gtklistheaderwidget.c',
- 'gtklistitem.c',
- 'gtklistitembase.c',
- 'gtklistitemfactory.c',
-@@ -333,6 +336,7 @@ gtk_public_sources = files([
- 'gtkscrolledwindow.c',
- 'gtksearchbar.c',
- 'gtksearchentry.c',
-+ 'gtksectionmodel.c',
- 'gtkselectionfiltermodel.c',
- 'gtkselectionmodel.c',
- 'gtkseparator.c',
-@@ -518,6 +522,7 @@ gtk_public_headers = files([
- 'gtklinkbutton.h',
- 'gtklistbase.h',
- 'gtklistbox.h',
-+ 'gtklistheader.h',
- 'gtklistitem.h',
- 'gtklistitemfactory.h',
- 'gtklistview.h',
-@@ -565,6 +570,7 @@ gtk_public_headers = files([
- 'gtkscrolledwindow.h',
- 'gtksearchbar.h',
- 'gtksearchentry.h',
-+ 'gtksectionmodel.h',
- 'gtkselectionfiltermodel.h',
- 'gtkselectionmodel.h',
- 'gtkseparator.h',
-diff --git a/gtk/theme/Default/_common.scss b/gtk/theme/Default/_common.scss
-index 33b7172914d5e27c3896fa706a013b53739f5873..231600ecf87e8e6cf64327024ba10d669db60609 100644
---- a/gtk/theme/Default/_common.scss
-+++ b/gtk/theme/Default/_common.scss
-@@ -3377,7 +3377,7 @@ columnview row:not(:selected) cell editablelabel.editing text selection {
-
-
- .rich-list { /* rich lists usually containing other widgets than just labels/text */
-- & > row {
-+ & > row, & > header {
- padding: 8px 12px;
- min-height: 32px; /* should be tall even when only containing a label */
-
-@@ -3385,6 +3385,14 @@ columnview row:not(:selected) cell editablelabel.editing text selection {
- border-spacing: 12px;
- }
- }
-+ & > header {
-+ @extend %osd;
-+ background-color: $osd_bg_color;
-+
-+ border-bottom: 1px solid $borders-color;
-+ border-top: 1px solid $borders-color;
-+ font-weight: bold;
-+ }
- }
-
- /********************************************************
-diff --git a/testsuite/gtk/filterlistmodel-exhaustive.c b/testsuite/gtk/filterlistmodel-exhaustive.c
-index 084f1d301d9c055e30155bca9309f5a83da10be4..2af32072f58e507d56dfb036afb2283ff21a246b 100644
---- a/testsuite/gtk/filterlistmodel-exhaustive.c
-+++ b/testsuite/gtk/filterlistmodel-exhaustive.c
-@@ -43,6 +43,23 @@
- } \
- }G_STMT_END
-
-+#define assert_sections_equal(model1, model2) G_STMT_START{ \
-+ guint _i, _n, _start1, _end1, _start2, _end2; \
-+ g_assert_cmpint (g_list_model_get_n_items (G_LIST_MODEL (model1)), ==, g_list_model_get_n_items (G_LIST_MODEL (model2))); \
-+ _n = g_list_model_get_n_items (G_LIST_MODEL (model1)); \
-+ for (_i = 0; _i < _n; _i = _end1) \
-+ { \
-+ gtk_section_model_get_section (model1, _i, &_start1, &_end1); \
-+ gtk_section_model_get_section (model2, _i, &_start2, &_end2); \
-+ g_assert_cmpint (_start1, <, _end1); \
-+ g_assert_cmpint (_start2, <, _end2); \
-+ g_assert_cmpint (_start1, ==, _start2); \
-+ g_assert_cmpint (_end1, ==, _end2); \
-+ g_assert_cmpint (_i, ==, _start1); \
-+ g_assert_cmpint (_end1, <=, _n); \
-+ } \
-+}G_STMT_END
-+
- G_GNUC_UNUSED static char *
- model_to_string (GListModel *model)
- {
-@@ -469,6 +486,7 @@ test_model_changes (gconstpointer model_id)
- {
- ensure_updated ();
- assert_model_equal (G_LIST_MODEL (flatten1), G_LIST_MODEL (model2));
-+ assert_sections_equal (GTK_SECTION_MODEL (flatten1), GTK_SECTION_MODEL (model2));
- }
- }
-
-diff --git a/testsuite/gtk/filterlistmodel.c b/testsuite/gtk/filterlistmodel.c
-index b3c8303daa448f9f7519250b0b41b4de5f0e1c72..6f1465b3d1437fe028e8dcb116a07960be4c9787 100644
---- a/testsuite/gtk/filterlistmodel.c
-+++ b/testsuite/gtk/filterlistmodel.c
-@@ -323,7 +323,7 @@ test_change_filter (void)
- {
- GtkFilterListModel *filter;
- GtkFilter *custom;
--
-+
- filter = new_model (10, is_not_near, GUINT_TO_POINTER (5));
- assert_model (filter, "1 2 8 9 10");
- assert_changes (filter, "");
-@@ -457,6 +457,94 @@ test_add_remove_item (void)
- g_object_unref (filter);
- }
-
-+static int
-+sort_func (gconstpointer p1,
-+ gconstpointer p2,
-+ gpointer data)
-+{
-+ const char *s1 = gtk_string_object_get_string ((GtkStringObject *)p1);
-+ const char *s2 = gtk_string_object_get_string ((GtkStringObject *)p2);
-+
-+ /* compare just the first byte */
-+ return (int)(s1[0]) - (int)(s2[0]);
-+}
-+
-+static gboolean
-+filter_func (gpointer item,
-+ gpointer data)
-+{
-+ const char *s = gtk_string_object_get_string ((GtkStringObject *)item);
-+
-+ return s[0] == s[1];
-+}
-+
-+static void
-+test_sections (void)
-+{
-+ GtkStringList *list;
-+ const char *strings[] = {
-+ "aaa",
-+ "aab",
-+ "abc",
-+ "bbb",
-+ "bq1",
-+ "bq2",
-+ "cc",
-+ "cx",
-+ NULL
-+ };
-+ GtkSorter *sorter;
-+ GtkSortListModel *sorted;
-+ GtkSorter *section_sorter;
-+ guint s, e;
-+ GtkFilterListModel *filtered;
-+ GtkFilter *filter;
-+
-+ list = gtk_string_list_new (strings);
-+ sorter = GTK_SORTER (gtk_string_sorter_new (gtk_property_expression_new (GTK_TYPE_STRING_OBJECT, NULL, "string")));
-+ sorted = gtk_sort_list_model_new (G_LIST_MODEL (list), sorter);
-+ section_sorter = GTK_SORTER (gtk_custom_sorter_new (sort_func, NULL, NULL));
-+ gtk_sort_list_model_set_section_sorter (GTK_SORT_LIST_MODEL (sorted), section_sorter);
-+ g_object_unref (section_sorter);
-+
-+ gtk_section_model_get_section (GTK_SECTION_MODEL (sorted), 0, &s, &e);
-+ g_assert_cmpint (s, ==, 0);
-+ g_assert_cmpint (e, ==, 3);
-+ gtk_section_model_get_section (GTK_SECTION_MODEL (sorted), 3, &s, &e);
-+ g_assert_cmpint (s, ==, 3);
-+ g_assert_cmpint (e, ==, 6);
-+ gtk_section_model_get_section (GTK_SECTION_MODEL (sorted), 6, &s, &e);
-+ g_assert_cmpint (s, ==, 6);
-+ g_assert_cmpint (e, ==, 8);
-+
-+ filtered = gtk_filter_list_model_new (NULL, NULL);
-+ gtk_section_model_get_section (GTK_SECTION_MODEL (filtered), 0, &s, &e);
-+ g_assert_cmpint (s, ==, 0);
-+ g_assert_cmpint (e, ==, G_MAXUINT);
-+
-+ gtk_filter_list_model_set_model (filtered, G_LIST_MODEL (sorted));
-+ gtk_section_model_get_section (GTK_SECTION_MODEL (filtered), 0, &s, &e);
-+ g_assert_cmpint (s, ==, 0);
-+ g_assert_cmpint (e, ==, 3);
-+
-+ filter = GTK_FILTER (gtk_custom_filter_new (filter_func, NULL, NULL));
-+ gtk_filter_list_model_set_filter (filtered, filter);
-+ g_object_unref (filter);
-+
-+ gtk_section_model_get_section (GTK_SECTION_MODEL (filtered), 0, &s, &e);
-+ g_assert_cmpint (s, ==, 0);
-+ g_assert_cmpint (e, ==, 2);
-+ gtk_section_model_get_section (GTK_SECTION_MODEL (filtered), 2, &s, &e);
-+ g_assert_cmpint (s, ==, 2);
-+ g_assert_cmpint (e, ==, 3);
-+ gtk_section_model_get_section (GTK_SECTION_MODEL (filtered), 3, &s, &e);
-+ g_assert_cmpint (s, ==, 3);
-+ g_assert_cmpint (e, ==, 4);
-+
-+ g_object_unref (filtered);
-+ g_object_unref (sorted);
-+}
-+
- int
- main (int argc, char *argv[])
- {
-@@ -472,6 +560,7 @@ main (int argc, char *argv[])
- g_test_add_func ("/filterlistmodel/incremental", test_incremental);
- g_test_add_func ("/filterlistmodel/empty", test_empty);
- g_test_add_func ("/filterlistmodel/add_remove_item", test_add_remove_item);
-+ g_test_add_func ("/filterlistmodel/sections", test_sections);
-
- return g_test_run ();
- }
-diff --git a/testsuite/gtk/listitemmanager.c b/testsuite/gtk/listitemmanager.c
-new file mode 100644
-index 0000000000000000000000000000000000000000..ba5fbd54baf01a2280eebe3e519306c01c8c4718
---- /dev/null
-+++ b/testsuite/gtk/listitemmanager.c
-@@ -0,0 +1,497 @@
-+/*
-+ * Copyright © 2023 Benjamin Otte
-+ *
-+ * This library is free software; you can redistribute it and/or
-+ * modify it under the terms of the GNU Lesser General Public
-+ * License as published by the Free Software Foundation; either
-+ * version 2 of the License, or (at your option) any later version.
-+ *
-+ * This library is distributed in the hope that it will be useful,
-+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
-+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-+ * Lesser General Public License for more details.
-+ *
-+ * You should have received a copy of the GNU Lesser General Public
-+ * License along with this library. If not, see .
-+ */
-+
-+#include
-+
-+#include
-+#include "gtk/gtklistitemmanagerprivate.h"
-+#include "gtk/gtklistbaseprivate.h"
-+
-+static GListModel *
-+create_source_model (guint min_size, guint max_size)
-+{
-+ GtkStringList *list;
-+ guint i, size;
-+
-+ size = g_test_rand_int_range (min_size, max_size + 1);
-+ list = gtk_string_list_new (NULL);
-+
-+ for (i = 0; i < size; i++)
-+ gtk_string_list_append (list, g_test_rand_bit () ? "A" : "B");
-+
-+ return G_LIST_MODEL (list);
-+}
-+
-+void
-+print_list_item_manager_tiles (GtkListItemManager *items)
-+{
-+ GString *string;
-+ GtkListTile *tile;
-+
-+ string = g_string_new ("");
-+
-+ for (tile = gtk_list_item_manager_get_first (items);
-+ tile != NULL;
-+ tile = gtk_rb_tree_node_get_next (tile))
-+ {
-+ switch (tile->type)
-+ {
-+ case GTK_LIST_TILE_ITEM:
-+ if (tile->widget)
-+ g_string_append_c (string, 'W');
-+ else if (tile->n_items == 1)
-+ g_string_append_c (string, 'x');
-+ else
-+ g_string_append_printf (string, "%u,", tile->n_items);
-+ break;
-+ case GTK_LIST_TILE_HEADER:
-+ g_string_append_c (string, '[');
-+ break;
-+ case GTK_LIST_TILE_UNMATCHED_HEADER:
-+ g_string_append_c (string, '(');
-+ break;
-+ case GTK_LIST_TILE_FOOTER:
-+ g_string_append_c (string, ']');
-+ break;
-+ case GTK_LIST_TILE_UNMATCHED_FOOTER:
-+ g_string_append_c (string, ')');
-+ break;
-+
-+ case GTK_LIST_TILE_FILLER:
-+ g_string_append_c (string, '_');
-+ break;
-+ case GTK_LIST_TILE_REMOVED:
-+ g_string_append_c (string, '.');
-+ break;
-+ default:
-+ g_assert_not_reached ();
-+ break;
-+ }
-+ }
-+
-+ g_print ("%s\n", string->str);
-+
-+ g_string_free (string, TRUE);
-+}
-+
-+static void
-+check_list_item_manager (GtkListItemManager *items,
-+ GtkListItemTracker **trackers,
-+ gsize n_trackers)
-+{
-+ GListModel *model = G_LIST_MODEL (gtk_list_item_manager_get_model (items));
-+ GtkListTile *tile;
-+ guint n_items = 0;
-+ guint i;
-+ gboolean has_sections;
-+ enum {
-+ NO_SECTION,
-+ MATCHED_SECTION,
-+ UNMATCHED_SECTION
-+ } section_state = NO_SECTION;
-+
-+ has_sections = gtk_list_item_manager_get_has_sections (items);
-+
-+ for (tile = gtk_list_item_manager_get_first (items);
-+ tile != NULL;
-+ tile = gtk_rb_tree_node_get_next (tile))
-+ {
-+ switch (tile->type)
-+ {
-+ case GTK_LIST_TILE_HEADER:
-+ g_assert_cmpint (section_state, ==, NO_SECTION);
-+ g_assert_cmpint (tile->n_items, ==, 0);
-+ g_assert_true (has_sections);
-+ g_assert_true (tile->widget);
-+ section_state = MATCHED_SECTION;
-+ break;
-+
-+ case GTK_LIST_TILE_UNMATCHED_HEADER:
-+ g_assert_cmpint (section_state, ==, NO_SECTION);
-+ g_assert_cmpint (tile->n_items, ==, 0);
-+ g_assert_false (tile->widget);
-+ section_state = UNMATCHED_SECTION;
-+ break;
-+
-+ case GTK_LIST_TILE_FOOTER:
-+ g_assert_cmpint (section_state, ==, MATCHED_SECTION);
-+ g_assert_cmpint (tile->n_items, ==, 0);
-+ g_assert_true (has_sections);
-+ g_assert_false (tile->widget);
-+ section_state = NO_SECTION;
-+ break;
-+
-+ case GTK_LIST_TILE_UNMATCHED_FOOTER:
-+ g_assert_cmpint (section_state, ==, UNMATCHED_SECTION);
-+ g_assert_cmpint (tile->n_items, ==, 0);
-+ g_assert_false (tile->widget);
-+ section_state = NO_SECTION;
-+ break;
-+
-+ case GTK_LIST_TILE_ITEM:
-+ g_assert_cmpint (section_state, !=, NO_SECTION);
-+ if (tile->widget)
-+ {
-+ GObject *item = g_list_model_get_item (model, n_items);
-+ if (has_sections)
-+ g_assert_cmpint (section_state, ==, MATCHED_SECTION);
-+ else
-+ g_assert_cmpint (section_state, ==, UNMATCHED_SECTION);
-+ g_assert_cmphex (GPOINTER_TO_SIZE (item), ==, GPOINTER_TO_SIZE (gtk_list_item_base_get_item (GTK_LIST_ITEM_BASE (tile->widget))));
-+ g_object_unref (item);
-+ g_assert_cmpint (n_items, ==, gtk_list_item_base_get_position (GTK_LIST_ITEM_BASE (tile->widget)));
-+ g_assert_cmpint (tile->n_items, ==, 1);
-+ }
-+ if (tile->n_items)
-+ n_items += tile->n_items;
-+ break;
-+
-+ case GTK_LIST_TILE_FILLER:
-+ /* We don't add fillers */
-+ g_assert_not_reached ();
-+ break;
-+
-+ case GTK_LIST_TILE_REMOVED:
-+ g_assert_cmpint (tile->n_items, ==, 0);
-+ g_assert_false (tile->widget);
-+ break;
-+
-+ default:
-+ g_assert_not_reached ();
-+ break;
-+ }
-+ }
-+
-+ g_assert_cmpint (section_state, ==, NO_SECTION);
-+ g_assert_cmpint (n_items, ==, g_list_model_get_n_items (model));
-+
-+ for (i = 0; i < n_trackers; i++)
-+ {
-+ guint pos, offset;
-+
-+ pos = gtk_list_item_tracker_get_position (items, trackers[i]);
-+ if (pos == GTK_INVALID_LIST_POSITION)
-+ continue;
-+ tile = gtk_list_item_manager_get_nth (items, pos, &offset);
-+ g_assert_cmpint (tile->n_items, ==, 1);
-+ g_assert_cmpint (offset, ==, 0);
-+ g_assert_true (tile->widget);
-+ }
-+
-+ for (tile = gtk_list_tile_gc (items, gtk_list_item_manager_get_first (items));
-+ tile != NULL;
-+ tile = gtk_list_tile_gc (items, gtk_rb_tree_node_get_next (tile)))
-+ ;
-+
-+ n_items = 0;
-+
-+ for (tile = gtk_list_item_manager_get_first (items);
-+ tile != NULL;
-+ tile = gtk_rb_tree_node_get_next (tile))
-+ {
-+ switch (tile->type)
-+ {
-+ case GTK_LIST_TILE_HEADER:
-+ g_assert_cmpint (section_state, ==, NO_SECTION);
-+ g_assert_cmpint (tile->n_items, ==, 0);
-+ g_assert_true (has_sections);
-+ g_assert_true (tile->widget);
-+ section_state = MATCHED_SECTION;
-+ break;
-+
-+ case GTK_LIST_TILE_UNMATCHED_HEADER:
-+ g_assert_cmpint (section_state, ==, NO_SECTION);
-+ g_assert_cmpint (tile->n_items, ==, 0);
-+ g_assert_false (tile->widget);
-+ section_state = UNMATCHED_SECTION;
-+ break;
-+
-+ case GTK_LIST_TILE_FOOTER:
-+ g_assert_cmpint (section_state, ==, MATCHED_SECTION);
-+ g_assert_cmpint (tile->n_items, ==, 0);
-+ g_assert_true (has_sections);
-+ g_assert_false (tile->widget);
-+ section_state = NO_SECTION;
-+ break;
-+
-+ case GTK_LIST_TILE_UNMATCHED_FOOTER:
-+ g_assert_cmpint (section_state, ==, UNMATCHED_SECTION);
-+ g_assert_cmpint (tile->n_items, ==, 0);
-+ g_assert_false (tile->widget);
-+ section_state = NO_SECTION;
-+ break;
-+
-+ case GTK_LIST_TILE_ITEM:
-+ g_assert_cmpint (section_state, !=, NO_SECTION);
-+ if (tile->widget)
-+ {
-+ g_assert_cmpint (tile->n_items, ==, 1);
-+ }
-+ if (tile->n_items)
-+ n_items += tile->n_items;
-+ break;
-+
-+ case GTK_LIST_TILE_FILLER:
-+ case GTK_LIST_TILE_REMOVED:
-+ default:
-+ g_assert_not_reached ();
-+ break;
-+ }
-+ }
-+
-+ g_assert_cmpint (section_state, ==, NO_SECTION);
-+ g_assert_cmpint (n_items, ==, g_list_model_get_n_items (model));
-+
-+ for (i = 0; i < n_trackers; i++)
-+ {
-+ guint pos, offset;
-+
-+ pos = gtk_list_item_tracker_get_position (items, trackers[i]);
-+ if (pos == GTK_INVALID_LIST_POSITION)
-+ continue;
-+ tile = gtk_list_item_manager_get_nth (items, pos, &offset);
-+ g_assert_cmpint (tile->n_items, ==, 1);
-+ g_assert_cmpint (offset, ==, 0);
-+ g_assert_true (tile->widget);
-+ }
-+}
-+
-+static GtkListTile *
-+split_simple (GtkWidget *widget,
-+ GtkListTile *tile,
-+ guint n_items)
-+{
-+ GtkListItemManager *items = g_object_get_data (G_OBJECT (widget), "the-items");
-+
-+ return gtk_list_tile_split (items, tile, n_items);
-+}
-+
-+static void
-+prepare_simple (GtkWidget *widget,
-+ GtkListTile *tile,
-+ guint n_items)
-+{
-+}
-+
-+static GtkListItemBase *
-+create_simple_item (GtkWidget *widget)
-+{
-+ return g_object_new (GTK_TYPE_LIST_ITEM_BASE, NULL);
-+}
-+
-+static GtkListHeaderBase *
-+create_simple_header (GtkWidget *widget)
-+{
-+ return g_object_new (GTK_TYPE_LIST_HEADER_BASE, NULL);
-+}
-+
-+static void
-+test_create (void)
-+{
-+ GtkListItemManager *items;
-+ GtkWidget *widget;
-+
-+ widget = gtk_window_new ();
-+ items = gtk_list_item_manager_new (widget,
-+ split_simple,
-+ create_simple_item,
-+ prepare_simple,
-+ create_simple_header);
-+ g_object_set_data_full (G_OBJECT (widget), "the-items", items, g_object_unref);
-+
-+ gtk_window_destroy (GTK_WINDOW (widget));
-+}
-+
-+static void
-+test_create_with_items (void)
-+{
-+ GListModel *source;
-+ GtkNoSelection *selection;
-+ GtkListItemManager *items;
-+ GtkWidget *widget;
-+
-+ widget = gtk_window_new ();
-+ items = gtk_list_item_manager_new (widget,
-+ split_simple,
-+ create_simple_item,
-+ prepare_simple,
-+ create_simple_header);
-+ g_object_set_data_full (G_OBJECT (widget), "the-items", items, g_object_unref);
-+
-+ source = create_source_model (1, 50);
-+ selection = gtk_no_selection_new (G_LIST_MODEL (source));
-+ gtk_list_item_manager_set_model (items, GTK_SELECTION_MODEL (selection));
-+ check_list_item_manager (items, NULL, 0);
-+ gtk_list_item_manager_set_model (items, GTK_SELECTION_MODEL (selection));
-+ check_list_item_manager (items, NULL, 0);
-+
-+ g_object_unref (selection);
-+ gtk_window_destroy (GTK_WINDOW (widget));
-+}
-+
-+#define N_TRACKERS 3
-+#define N_WIDGETS_PER_TRACKER 10
-+#define N_RUNS 500
-+
-+static void
-+print_changes_cb (GListModel *model,
-+ guint position,
-+ guint removed,
-+ guint added,
-+ gpointer unused)
-+{
-+ if (!g_test_verbose ())
-+ return;
-+
-+ if (removed == 0)
-+ g_test_message ("%u/%u: adding %u items", position, g_list_model_get_n_items (model), added);
-+ else if (added == 0)
-+ g_test_message ("%u/%u: removing %u items", position, g_list_model_get_n_items (model), removed);
-+ else
-+ g_test_message ("%u/%u: removing %u and adding %u items", position, g_list_model_get_n_items (model), removed, added);
-+}
-+
-+static void
-+test_exhaustive (void)
-+{
-+ GtkListItemTracker *trackers[N_TRACKERS];
-+ GListStore *store;
-+ GtkFlattenListModel *flatten;
-+ GtkNoSelection *selection;
-+ GtkListItemManager *items;
-+ GtkWidget *widget;
-+ gsize i;
-+
-+ widget = gtk_window_new ();
-+ items = gtk_list_item_manager_new (widget,
-+ split_simple,
-+ create_simple_item,
-+ prepare_simple,
-+ create_simple_header);
-+ for (i = 0; i < N_TRACKERS; i++)
-+ trackers[i] = gtk_list_item_tracker_new (items);
-+
-+ g_object_set_data_full (G_OBJECT (widget), "the-items", items, g_object_unref);
-+
-+ store = g_list_store_new (G_TYPE_OBJECT);
-+ flatten = gtk_flatten_list_model_new (G_LIST_MODEL (store));
-+ selection = gtk_no_selection_new (G_LIST_MODEL (flatten));
-+ g_signal_connect (selection, "items-changed", G_CALLBACK (print_changes_cb), NULL);
-+ gtk_list_item_manager_set_model (items, GTK_SELECTION_MODEL (selection));
-+
-+ for (i = 0; i < N_RUNS; i++)
-+ {
-+ gboolean add = FALSE, remove = FALSE;
-+ guint position, n_items;
-+
-+ if (g_test_verbose ())
-+ print_list_item_manager_tiles (items);
-+
-+ switch (g_test_rand_int_range (0, 6))
-+ {
-+ case 0:
-+ if (g_test_verbose ())
-+ g_test_message ("GC and checking");
-+ check_list_item_manager (items, trackers, N_TRACKERS);
-+ break;
-+
-+ case 1:
-+ /* remove a model */
-+ remove = TRUE;
-+ break;
-+
-+ case 2:
-+ /* add a model */
-+ add = TRUE;
-+ break;
-+
-+ case 3:
-+ /* replace a model */
-+ remove = TRUE;
-+ add = TRUE;
-+ break;
-+
-+ case 4:
-+ n_items = g_list_model_get_n_items (G_LIST_MODEL (selection));
-+ if (n_items > 0)
-+ {
-+ guint tracker_id = g_test_rand_int_range (0, N_TRACKERS);
-+ guint pos = g_test_rand_int_range (0, n_items);
-+ guint n_before = g_test_rand_int_range (0, N_WIDGETS_PER_TRACKER / 2);
-+ guint n_after = g_test_rand_int_range (0, N_WIDGETS_PER_TRACKER / 2);
-+ if (g_test_verbose ())
-+ g_test_message ("setting tracker %u to %u -%u + %u", tracker_id, pos, n_before, n_after);
-+ gtk_list_item_tracker_set_position (items,
-+ trackers [tracker_id],
-+ pos,
-+ n_before, n_after);
-+ }
-+ break;
-+
-+ case 5:
-+ {
-+ gboolean has_sections = g_test_rand_bit ();
-+ if (g_test_verbose ())
-+ g_test_message ("Setting has_sections to %s", has_sections ? "true" : "false");
-+ gtk_list_item_manager_set_has_sections (items, has_sections);
-+ }
-+ break;
-+
-+ default:
-+ g_assert_not_reached ();
-+ break;
-+ }
-+
-+ position = g_test_rand_int_range (0, g_list_model_get_n_items (G_LIST_MODEL (store)) + 1);
-+ if (g_list_model_get_n_items (G_LIST_MODEL (store)) == position)
-+ remove = FALSE;
-+
-+ if (add)
-+ {
-+ /* We want at least one element, otherwise the filters will see no changes */
-+ GListModel *source = create_source_model (1, 50);
-+ g_list_store_splice (store,
-+ position,
-+ remove ? 1 : 0,
-+ (gpointer *) &source, 1);
-+ g_object_unref (source);
-+ }
-+ else if (remove)
-+ {
-+ g_list_store_remove (store, position);
-+ }
-+ }
-+
-+ check_list_item_manager (items, trackers, N_TRACKERS);
-+
-+ for (i = 0; i < N_TRACKERS; i++)
-+ gtk_list_item_tracker_free (items, trackers[i]);
-+ g_object_unref (selection);
-+ gtk_window_destroy (GTK_WINDOW (widget));
-+}
-+
-+int
-+main (int argc, char *argv[])
-+{
-+ gtk_test_init (&argc, &argv);
-+
-+ g_test_add_func ("/listitemmanager/create", test_create);
-+ g_test_add_func ("/listitemmanager/create_with_items", test_create_with_items);
-+ g_test_add_func ("/listitemmanager/exhaustive", test_exhaustive);
-+
-+ return g_test_run ();
-+}
-diff --git a/testsuite/gtk/meson.build b/testsuite/gtk/meson.build
-index 11560a95f479df15686d7afa12c702bc375b6b42..a3f2be717213e4954fb342e58984bc636f38c500 100644
---- a/testsuite/gtk/meson.build
-+++ b/testsuite/gtk/meson.build
-@@ -126,6 +126,7 @@ internal_tests = [
- { 'name': 'texthistory' },
- { 'name': 'fnmatch' },
- { 'name': 'a11y' },
-+ { 'name': 'listitemmanager' },
- ]
-
- is_debug = get_option('buildtype').startswith('debug')
-diff --git a/testsuite/gtk/sortlistmodel-exhaustive.c b/testsuite/gtk/sortlistmodel-exhaustive.c
-index 0ca2c68f5eab02176288541e1ced0c699d136053..e06bed8195eec9a34da7b5a670e47617c8d8734f 100644
---- a/testsuite/gtk/sortlistmodel-exhaustive.c
-+++ b/testsuite/gtk/sortlistmodel-exhaustive.c
-@@ -36,6 +36,8 @@
- if (o1 != o2) \
- { \
- char *_s = g_strdup_printf ("Objects differ at index %u out of %u", _i, _n); \
-+ g_print ("%s\n", model_to_string (model1)); \
-+ g_print ("%s\n", model_to_string (model2)); \
- g_assertion_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, _s); \
- g_free (_s); \
- } \
-@@ -45,6 +47,19 @@
- } \
- }G_STMT_END
-
-+#define assert_model_sections(model) G_STMT_START{ \
-+ guint _i, _start, _end; \
-+ _start = 0; \
-+ _end = 0; \
-+ for (_i = 0; _i < G_MAXUINT; _i = _end) \
-+ { \
-+ gtk_section_model_get_section (GTK_SECTION_MODEL (model), _i, &_start, &_end); \
-+\
-+ g_assert_cmpint (_start, ==, _i); \
-+ g_assert_cmpint (_end, >, _i); \
-+ } \
-+}G_STMT_END
-+
- G_GNUC_UNUSED static char *
- model_to_string (GListModel *model)
- {
-@@ -287,7 +302,7 @@ create_sorter (gsize id)
- /* match all As, Bs and nothing */
- sorter = GTK_SORTER (gtk_string_sorter_new (gtk_property_expression_new (GTK_TYPE_STRING_OBJECT, NULL, "string")));
- if (id == 1)
-- gtk_string_sorter_set_ignore_case (GTK_STRING_SORTER (sorter), TRUE);
-+ gtk_string_sorter_set_ignore_case (GTK_STRING_SORTER (sorter), FALSE);
- return sorter;
-
- default:
-@@ -463,6 +478,258 @@ test_stability (gconstpointer model_id)
- g_object_unref (flatten);
- }
-
-+static gboolean
-+string_is_lowercase (GtkStringObject *o)
-+{
-+ return g_ascii_islower (*gtk_string_object_get_string (o));
-+}
-+
-+/* Run:
-+ * source => section-sorter
-+ * source => sorter
-+ * and set a section sorter on the section sorter that is a subsort of
-+ * the real sorter.
-+ *
-+ * And then randomly add/remove sources and change the sorters and
-+ * see if the two sorters stay identical
-+ */
-+static void
-+test_section_sorters (gconstpointer model_id)
-+{
-+ GListStore *store;
-+ GtkFlattenListModel *flatten;
-+ GtkSortListModel *sort1, *sort2;
-+ GtkSorter *sorter;
-+ gsize i;
-+
-+ store = g_list_store_new (G_TYPE_OBJECT);
-+ flatten = gtk_flatten_list_model_new (G_LIST_MODEL (store));
-+ sort1 = create_sort_list_model (model_id, FALSE, G_LIST_MODEL (flatten), NULL);
-+ sort2 = create_sort_list_model (model_id, FALSE, G_LIST_MODEL (flatten), NULL);
-+
-+ for (i = 0; i < 500; i++)
-+ {
-+ gboolean add = FALSE, remove = FALSE;
-+ guint position;
-+
-+ switch (g_test_rand_int_range (0, 4))
-+ {
-+ case 0:
-+ /* set the same sorter, once as section sorter, once as sorter */
-+ sorter = create_random_sorter (TRUE);
-+ gtk_sort_list_model_set_section_sorter (sort1, sorter);
-+ gtk_sort_list_model_set_sorter (sort1, NULL);
-+ gtk_sort_list_model_set_sorter (sort2, sorter);
-+ g_clear_object (&sorter);
-+ break;
-+
-+ case 1:
-+ /* use a section sorter that is a more generic version of the sorter */
-+ sorter = GTK_SORTER (gtk_string_sorter_new (gtk_property_expression_new (GTK_TYPE_STRING_OBJECT, NULL, "string")));
-+ gtk_string_sorter_set_ignore_case (GTK_STRING_SORTER (sorter), FALSE);
-+ gtk_sort_list_model_set_sorter (sort1, sorter);
-+ gtk_sort_list_model_set_sorter (sort2, sorter);
-+ g_clear_object (&sorter);
-+ sorter = GTK_SORTER (gtk_numeric_sorter_new (gtk_cclosure_expression_new (G_TYPE_BOOLEAN, NULL, 0, NULL, G_CALLBACK (string_is_lowercase), NULL, NULL)));
-+ gtk_sort_list_model_set_section_sorter (sort1, sorter);
-+ g_clear_object (&sorter);
-+ break;
-+
-+ case 2:
-+ /* remove a model */
-+ remove = TRUE;
-+ break;
-+
-+ case 3:
-+ /* add a model */
-+ add = TRUE;
-+ break;
-+
-+ case 4:
-+ /* replace a model */
-+ remove = TRUE;
-+ add = TRUE;
-+ break;
-+
-+ default:
-+ g_assert_not_reached ();
-+ break;
-+ }
-+
-+ position = g_test_rand_int_range (0, g_list_model_get_n_items (G_LIST_MODEL (store)) + 1);
-+ if (g_list_model_get_n_items (G_LIST_MODEL (store)) == position)
-+ remove = FALSE;
-+
-+ if (add)
-+ {
-+ /* We want at least one element, otherwise the sorters will see no changes */
-+ GListModel *source = create_source_model (1, 50);
-+ g_list_store_splice (store,
-+ position,
-+ remove ? 1 : 0,
-+ (gpointer *) &source, 1);
-+ g_object_unref (source);
-+ }
-+ else if (remove)
-+ {
-+ g_list_store_remove (store, position);
-+ }
-+
-+ if (g_test_rand_bit ())
-+ {
-+ ensure_updated ();
-+ assert_model_equal (G_LIST_MODEL (sort1), G_LIST_MODEL (sort2));
-+ }
-+
-+ if (g_test_rand_bit ())
-+ assert_model_sections (G_LIST_MODEL (sort1));
-+ }
-+
-+ g_object_unref (sort2);
-+ g_object_unref (sort1);
-+ g_object_unref (flatten);
-+}
-+
-+/* Run:
-+ * source => sorter
-+ * And then randomly add/remove sources and change the sorters and
-+ * see if the invariants for sections keep correct.
-+ */
-+static void
-+test_sections (gconstpointer model_id)
-+{
-+ GListStore *store;
-+ GtkFlattenListModel *flatten;
-+ GtkSortListModel *sort;
-+ GtkSorter *sorter;
-+ gsize i;
-+
-+ store = g_list_store_new (G_TYPE_OBJECT);
-+ flatten = gtk_flatten_list_model_new (G_LIST_MODEL (store));
-+ sort = create_sort_list_model (model_id, FALSE, G_LIST_MODEL (flatten), NULL);
-+
-+ for (i = 0; i < 500; i++)
-+ {
-+ gboolean add = FALSE, remove = FALSE;
-+ guint position;
-+
-+ switch (g_test_rand_int_range (0, 4))
-+ {
-+ case 0:
-+ /* set the same sorter, once as section sorter, once as sorter */
-+ sorter = create_random_sorter (TRUE);
-+ gtk_sort_list_model_set_sorter (sort, sorter);
-+ g_clear_object (&sorter);
-+ break;
-+
-+ case 1:
-+ /* set the same sorter, once as section sorter, once as sorter */
-+ sorter = create_random_sorter (TRUE);
-+ gtk_sort_list_model_set_section_sorter (sort, sorter);
-+ g_clear_object (&sorter);
-+ break;
-+
-+ case 2:
-+ /* remove a model */
-+ remove = TRUE;
-+ break;
-+
-+ case 3:
-+ /* add a model */
-+ add = TRUE;
-+ break;
-+
-+ case 4:
-+ /* replace a model */
-+ remove = TRUE;
-+ add = TRUE;
-+ break;
-+
-+ default:
-+ g_assert_not_reached ();
-+ break;
-+ }
-+
-+ position = g_test_rand_int_range (0, g_list_model_get_n_items (G_LIST_MODEL (store)) + 1);
-+ if (g_list_model_get_n_items (G_LIST_MODEL (store)) == position)
-+ remove = FALSE;
-+
-+ if (add)
-+ {
-+ /* We want at least one element, otherwise the sorters will see no changes */
-+ GListModel *source = create_source_model (1, 50);
-+ g_list_store_splice (store,
-+ position,
-+ remove ? 1 : 0,
-+ (gpointer *) &source, 1);
-+ g_object_unref (source);
-+ }
-+ else if (remove)
-+ {
-+ g_list_store_remove (store, position);
-+ }
-+
-+ if (g_test_rand_bit ())
-+ ensure_updated ();
-+
-+ if (g_test_rand_bit ())
-+ {
-+ guint start, end, pos, n, sec_start, sec_end;
-+ gpointer prev_item, item;
-+
-+ n = g_list_model_get_n_items (G_LIST_MODEL (sort));
-+ sorter = gtk_sort_list_model_get_section_sorter (sort);
-+ start = end = 0;
-+ prev_item = item = NULL;
-+
-+ for (pos = 0; pos < n; pos++)
-+ {
-+ gtk_section_model_get_section (GTK_SECTION_MODEL (sort), pos, &sec_start, &sec_end);
-+ prev_item = item;
-+ item = g_list_model_get_item (G_LIST_MODEL (sort), pos);
-+ if (end <= pos)
-+ {
-+ g_assert_cmpint (pos, ==, end);
-+ /* there should be a new section */
-+ g_assert_cmpint (sec_start, ==, end);
-+ g_assert_cmpint (sec_end, >, sec_start);
-+ g_assert_cmpint (sec_end, <=, n);
-+ start = sec_start;
-+ end = sec_end;
-+ if (prev_item)
-+ {
-+ g_assert_nonnull (sorter);
-+ g_assert_cmpint (gtk_sorter_compare (sorter, prev_item, item), !=, GTK_ORDERING_EQUAL);
-+ }
-+ }
-+ else
-+ {
-+ /* the old section keeps on going */
-+ g_assert_cmpint (sec_start, ==, start);
-+ g_assert_cmpint (sec_end, ==, end);
-+ if (prev_item && sorter)
-+ g_assert_cmpint (gtk_sorter_compare (sorter, prev_item, item), ==, GTK_ORDERING_EQUAL);
-+ }
-+ g_clear_object (&prev_item);
-+ }
-+
-+ g_clear_object (&item);
-+
-+ /* for good measure, check the error condition */
-+ if (n < G_MAXINT32)
-+ {
-+ gtk_section_model_get_section (GTK_SECTION_MODEL (sort), g_test_rand_int_range (n, G_MAXINT32), &sec_start, &sec_end);
-+ g_assert_cmpint (sec_start, ==, n);
-+ g_assert_cmpint (sec_end, ==, G_MAXUINT);
-+ }
-+ sorter = NULL;
-+ }
-+ }
-+
-+ g_object_unref (sort);
-+ g_object_unref (flatten);
-+}
-+
- static void
- add_test_for_all_models (const char *name,
- GTestDataFunc test_func)
-@@ -488,6 +755,8 @@ main (int argc, char *argv[])
-
- add_test_for_all_models ("two-sorters", test_two_sorters);
- add_test_for_all_models ("stability", test_stability);
-+ add_test_for_all_models ("section-sorters", test_section_sorters);
-+ add_test_for_all_models ("sections", test_sections);
-
- return g_test_run ();
- }
-diff --git a/testsuite/gtk/sortlistmodel.c b/testsuite/gtk/sortlistmodel.c
-index 6a1753373c845357c978caeac180ef25f8447275..d2cd69926901b32ac6483711d325faef3d785635 100644
---- a/testsuite/gtk/sortlistmodel.c
-+++ b/testsuite/gtk/sortlistmodel.c
-@@ -258,7 +258,7 @@ test_create (void)
- {
- GtkSortListModel *sort;
- GListStore *store;
--
-+
- store = new_store ((guint[]) { 4, 8, 2, 6, 10, 0 });
- sort = new_model (store);
- assert_model (sort, "2 4 6 8 10");
-@@ -280,7 +280,7 @@ test_set_model (void)
- {
- GtkSortListModel *sort;
- GListStore *store;
--
-+
- sort = new_model (NULL);
- assert_model (sort, "");
- assert_changes (sort, "");
-@@ -319,7 +319,7 @@ test_set_sorter (void)
- GtkSortListModel *sort;
- GtkSorter *sorter;
- GListStore *store;
--
-+
- store = new_store ((guint[]) { 4, 8, 2, 6, 10, 0 });
- sort = new_model (store);
- assert_model (sort, "2 4 6 8 10");
-@@ -350,7 +350,7 @@ test_add_items (void)
- {
- GtkSortListModel *sort;
- GListStore *store;
--
-+
- /* add beginning */
- store = new_store ((guint[]) { 51, 99, 100, 49, 50, 0 });
- sort = new_model (store);
-@@ -390,7 +390,7 @@ test_remove_items (void)
- {
- GtkSortListModel *sort;
- GListStore *store;
--
-+
- /* remove beginning */
- store = new_store ((guint[]) { 51, 99, 100, 49, 1, 2, 50, 0 });
- sort = new_model (store);
-@@ -570,6 +570,58 @@ test_add_remove_item (void)
- g_object_unref (sort);
- }
-
-+static int
-+sort_func (gconstpointer p1,
-+ gconstpointer p2,
-+ gpointer data)
-+{
-+ const char *s1 = gtk_string_object_get_string ((GtkStringObject *)p1);
-+ const char *s2 = gtk_string_object_get_string ((GtkStringObject *)p2);
-+
-+ /* compare just the first byte */
-+ return (int)(s1[0]) - (int)(s2[0]);
-+}
-+
-+static void
-+test_sections (void)
-+{
-+ GtkStringList *list;
-+ const char *strings[] = {
-+ "aaa",
-+ "aab",
-+ "abc",
-+ "bbb",
-+ "bq1",
-+ "bq2",
-+ "cc",
-+ "cx",
-+ NULL
-+ };
-+ GtkSorter *sorter;
-+ GtkSortListModel *sorted;
-+ GtkSorter *section_sorter;
-+ guint s, e;
-+
-+ list = gtk_string_list_new (strings);
-+ sorter = GTK_SORTER (gtk_string_sorter_new (gtk_property_expression_new (GTK_TYPE_STRING_OBJECT, NULL, "string")));
-+ sorted = gtk_sort_list_model_new (G_LIST_MODEL (list), sorter);
-+ section_sorter = GTK_SORTER (gtk_custom_sorter_new (sort_func, NULL, NULL));
-+ gtk_sort_list_model_set_section_sorter (GTK_SORT_LIST_MODEL (sorted), section_sorter);
-+ g_object_unref (section_sorter);
-+
-+ gtk_section_model_get_section (GTK_SECTION_MODEL (sorted), 0, &s, &e);
-+ g_assert_cmpint (s, ==, 0);
-+ g_assert_cmpint (e, ==, 3);
-+ gtk_section_model_get_section (GTK_SECTION_MODEL (sorted), 3, &s, &e);
-+ g_assert_cmpint (s, ==, 3);
-+ g_assert_cmpint (e, ==, 6);
-+ gtk_section_model_get_section (GTK_SECTION_MODEL (sorted), 6, &s, &e);
-+ g_assert_cmpint (s, ==, 6);
-+ g_assert_cmpint (e, ==, 8);
-+
-+ g_object_unref (sorted);
-+}
-+
- int
- main (int argc, char *argv[])
- {
-@@ -589,6 +641,7 @@ main (int argc, char *argv[])
- g_test_add_func ("/sortlistmodel/incremental/remove", test_incremental_remove);
- g_test_add_func ("/sortlistmodel/oob-access", test_out_of_bounds_access);
- g_test_add_func ("/sortlistmodel/add-remove-item", test_add_remove_item);
-+ g_test_add_func ("/sortlistmodel/sections", test_sections);
-
- return g_test_run ();
- }
diff --git a/gui-libs/gtk/gtk-4.11.2-r1.ebuild b/gui-libs/gtk/gtk-4.11.2.ebuild
similarity index 100%
rename from gui-libs/gtk/gtk-4.11.2-r1.ebuild
rename to gui-libs/gtk/gtk-4.11.2.ebuild