From db44376e2804aa930c2da9df96c629b85f48949b Mon Sep 17 00:00:00 2001 From: Gerben Jan Dijkman Date: Fri, 26 May 2023 17:27:28 +0200 Subject: [PATCH] Reverted --- gui-libs/gtk/files/4447.patch | 200 - gui-libs/gtk/files/5818.patch | 6139 ----------------- ...gtk-4.11.2-r1.ebuild => gtk-4.11.2.ebuild} | 0 3 files changed, 6339 deletions(-) delete mode 100644 gui-libs/gtk/files/4447.patch delete mode 100644 gui-libs/gtk/files/5818.patch rename gui-libs/gtk/{gtk-4.11.2-r1.ebuild => gtk-4.11.2.ebuild} (100%) 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 @@ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ Settings -+ 640 -+ 480 -+ -+ -+ -+ -+ system-search-symbolic -+ -+ -+ -+ -+ -+ -+ vertical -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ 1 -+ -+ -+ -+ -+ -+ -+ -+ ]]> -+ -+ -+ -+ -+ -+ -+ -+ -+ ]]> -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -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