diff --git a/demos/gtk-demo/demo.gresource.xml b/demos/gtk-demo/demo.gresource.xml
index f547947245..3ae9bf0be2 100644
--- a/demos/gtk-demo/demo.gresource.xml
+++ b/demos/gtk-demo/demo.gresource.xml
@@ -140,6 +140,7 @@
color.names.txt
+ listview_colors.css
shortcuts.ui
diff --git a/demos/gtk-demo/listview_colors.c b/demos/gtk-demo/listview_colors.c
index 18f8d427c9..6aa0cf6efc 100644
--- a/demos/gtk-demo/listview_colors.c
+++ b/demos/gtk-demo/listview_colors.c
@@ -627,6 +627,23 @@ setup_listitem_cb (GtkListItemFactory *factory,
gtk_expression_unref (color_expression);
}
+static void
+setup_selection_listitem_cb (GtkListItemFactory *factory,
+ GtkListItem *list_item)
+{
+ GtkWidget *picture;
+ GtkExpression *color_expression, *expression;
+
+ expression = gtk_constant_expression_new (GTK_TYPE_LIST_ITEM, list_item);
+ color_expression = gtk_property_expression_new (GTK_TYPE_LIST_ITEM, expression, "item");
+
+ picture = gtk_picture_new ();
+ gtk_widget_set_size_request (picture, 8, 8);
+ gtk_expression_bind (color_expression, picture, "paintable", NULL);
+
+ gtk_list_item_set_child (list_item, picture);
+}
+
static void
set_title (gpointer item,
const char *title)
@@ -777,6 +794,47 @@ bind_number_item (GtkSignalListItemFactory *factory,
g_free (string);
}
+static void
+update_selection_count (GListModel *model,
+ guint position,
+ guint removed,
+ guint added,
+ gpointer data)
+{
+ char *text;
+ text = g_strdup_printf ("%u", g_list_model_get_n_items (model));
+ gtk_label_set_label (GTK_LABEL (data), text);
+ g_free (text);
+}
+
+static void
+update_selection_average (GListModel *model,
+ guint position,
+ guint removed,
+ guint added,
+ gpointer data)
+{
+ guint n = g_list_model_get_n_items (model);
+ GdkRGBA c = { 0, 0, 0, 1 };
+ guint i;
+ GtkColor *color;
+
+ for (i = 0; i < n; i++)
+ {
+ color = g_list_model_get_item (model, i);
+
+ c.red += color->color.red;
+ c.green += color->color.green;
+ c.blue += color->color.blue;
+
+ g_object_unref (color);
+ }
+
+ color = gtk_color_new ("", c.red / n, c.green / n, c.blue / n);
+ gtk_picture_set_paintable (GTK_PICTURE (data), GDK_PAINTABLE (color));
+ g_object_unref (color);
+}
+
static GtkWidget *window = NULL;
GtkWidget *
@@ -797,6 +855,22 @@ do_listview_colors (GtkWidget *do_widget)
PangoAttrList *attrs;
char *string;
guint len;
+ GtkWidget *selection_view;
+ GListModel *selection_filter;
+ GListModel *no_selection;
+ GtkWidget *grid;
+ GtkWidget *selection_size_label;
+ GtkWidget *selection_average_picture;
+ GtkWidget *selection_info_toggle;
+ GtkWidget *selection_info_revealer;
+ GtkCssProvider *provider;
+
+ provider = gtk_css_provider_new ();
+ gtk_css_provider_load_from_resource (provider, "/listview_colors/listview_colors.css");
+ gtk_style_context_add_provider_for_display (gdk_display_get_default (),
+ GTK_STYLE_PROVIDER (provider),
+ 800);
+ g_object_unref (provider);
window = gtk_window_new ();
gtk_window_set_title (GTK_WINDOW (window), "Colors");
@@ -809,14 +883,85 @@ do_listview_colors (GtkWidget *do_widget)
gtk_widget_get_display (do_widget));
g_object_add_weak_pointer (G_OBJECT (window), (gpointer*)&window);
+ box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+ gtk_window_set_child (GTK_WINDOW (window), box);
+
+ selection_info_revealer = gtk_revealer_new ();
+ gtk_box_append (GTK_BOX (box), selection_info_revealer);
+
+ grid = gtk_grid_new ();
+ gtk_revealer_set_child (GTK_REVEALER (selection_info_revealer), grid);
+ gtk_widget_set_margin_start (grid, 10);
+ gtk_widget_set_margin_end (grid, 10);
+ gtk_widget_set_margin_top (grid, 10);
+ gtk_widget_set_margin_bottom (grid, 10);
+ gtk_grid_set_row_spacing (GTK_GRID (grid), 10);
+ gtk_grid_set_column_spacing (GTK_GRID (grid), 10);
+
+ label = gtk_label_new ("Selection");
+ gtk_widget_set_hexpand (label, TRUE);
+ gtk_widget_add_css_class (label, "title-3");
+ gtk_grid_attach (GTK_GRID (grid), label, 0, 0, 5, 1);
+
+ gtk_grid_attach (GTK_GRID (grid), gtk_label_new ("Size:"), 0, 2, 1, 1);
+
+ selection_size_label = gtk_label_new ("0");
+ gtk_grid_attach (GTK_GRID (grid), selection_size_label, 1, 2, 1, 1);
+
+ gtk_grid_attach (GTK_GRID (grid), gtk_label_new ("Average:"), 2, 2, 1, 1);
+
+ selection_average_picture = gtk_picture_new ();
+ gtk_widget_set_size_request (selection_average_picture, 32, 32);
+ gtk_grid_attach (GTK_GRID (grid), selection_average_picture, 3, 2, 1, 1);
+
+ label = gtk_label_new ("");
+ gtk_widget_set_hexpand (label, TRUE);
+ gtk_grid_attach (GTK_GRID (grid), label, 4, 2, 1, 1);
+
sw = gtk_scrolled_window_new ();
- gtk_window_set_child (GTK_WINDOW (window), sw);
+ gtk_widget_set_hexpand (sw, TRUE);
+
+ gtk_grid_attach (GTK_GRID (grid), sw, 0, 1, 5, 1);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+ GTK_POLICY_NEVER,
+ GTK_POLICY_AUTOMATIC);
+
+ factory = gtk_signal_list_item_factory_new ();
+ g_signal_connect (factory, "setup", G_CALLBACK (setup_selection_listitem_cb), NULL);
+ selection_view = gtk_grid_view_new_with_factory (factory);
+ gtk_widget_add_css_class (selection_view, "compact");
+ gtk_grid_view_set_max_columns (GTK_GRID_VIEW (selection_view), 200);
+ gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), selection_view);
+
+ sw = gtk_scrolled_window_new ();
+ gtk_box_append (GTK_BOX (box), sw);
gridview = create_color_grid ();
gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), gridview);
+ gtk_widget_set_hexpand (sw, TRUE);
+ gtk_widget_set_vexpand (sw, TRUE);
model = gtk_grid_view_get_model (GTK_GRID_VIEW (gridview));
+
+ selection_filter = G_LIST_MODEL (gtk_selection_filter_model_new (GTK_SELECTION_MODEL (model)));
+ g_signal_connect (selection_filter, "items-changed", G_CALLBACK (update_selection_count), selection_size_label);
+ g_signal_connect (selection_filter, "items-changed", G_CALLBACK (update_selection_average), selection_average_picture);
+
+ no_selection = G_LIST_MODEL (gtk_no_selection_new (selection_filter));
+ gtk_grid_view_set_model (GTK_GRID_VIEW (selection_view), no_selection);
+ g_object_unref (selection_filter);
+ g_object_unref (no_selection);
+
g_object_get (model, "model", &model, NULL);
+ selection_info_toggle = gtk_toggle_button_new ();
+ gtk_button_set_icon_name (GTK_BUTTON (selection_info_toggle), "emblem-important-symbolic");
+ gtk_widget_set_tooltip_text (selection_info_toggle, "Show selection info");
+ gtk_header_bar_pack_start (GTK_HEADER_BAR (header), selection_info_toggle);
+
+ g_object_bind_property (selection_info_toggle, "active",
+ selection_info_revealer, "reveal-child",
+ G_BINDING_DEFAULT);
+
button = gtk_button_new_with_mnemonic ("_Refill");
g_signal_connect (button, "clicked",
G_CALLBACK (refill),
diff --git a/demos/gtk-demo/listview_colors.css b/demos/gtk-demo/listview_colors.css
new file mode 100644
index 0000000000..e5d3d0b1e4
--- /dev/null
+++ b/demos/gtk-demo/listview_colors.css
@@ -0,0 +1,3 @@
+.view.compact > child {
+ padding: 1px;
+}
diff --git a/docs/reference/gtk/gtk4-docs.xml b/docs/reference/gtk/gtk4-docs.xml
index f6a46015cc..643fc0ebc1 100644
--- a/docs/reference/gtk/gtk4-docs.xml
+++ b/docs/reference/gtk/gtk4-docs.xml
@@ -74,6 +74,7 @@
+
diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt
index 5fefc4d1da..208151976c 100644
--- a/docs/reference/gtk/gtk4-sections.txt
+++ b/docs/reference/gtk/gtk4-sections.txt
@@ -355,6 +355,9 @@ gtk_bitset_is_empty
gtk_bitset_equals
gtk_bitset_get_minimum
gtk_bitset_get_maximum
+gtk_bitset_get_size
+gtk_bitset_get_size_in_range
+gtk_bitset_get_nth
gtk_bitset_remove_all
gtk_bitset_add
@@ -7610,3 +7613,13 @@ gtk_string_list_get_string
GtkStringObject
gtk_string_object_get_string
+
+
+gtkselectionfiltermodel
+GtkSelectionFilterModel
+GtkSelectionFilterModel
+gtk_selection_filter_model_new
+gtk_selection_filter_model_new_for_type
+gtk_selection_filter_model_set_model
+gtk_selection_filter_model_get_model
+
diff --git a/docs/reference/gtk/gtk4.types.in b/docs/reference/gtk/gtk4.types.in
index d61305ae2e..a890a5167b 100644
--- a/docs/reference/gtk/gtk4.types.in
+++ b/docs/reference/gtk/gtk4.types.in
@@ -184,6 +184,7 @@ gtk_scrollbar_get_type
gtk_scrolled_window_get_type
gtk_search_bar_get_type
gtk_search_entry_get_type
+gtk_selection_filter_model_get_type
gtk_selection_model_get_type
gtk_separator_get_type
gtk_settings_get_type
diff --git a/gtk/gtk.h b/gtk/gtk.h
index ed85587d73..50c23caf26 100644
--- a/gtk/gtk.h
+++ b/gtk/gtk.h
@@ -213,6 +213,7 @@
#include
#include
#include
+#include
#include
#include
#include
diff --git a/gtk/gtkbitset.c b/gtk/gtkbitset.c
index f2cf9ff446..c69efd0cf7 100644
--- a/gtk/gtkbitset.c
+++ b/gtk/gtkbitset.c
@@ -193,6 +193,76 @@ gtk_bitset_get_maximum (const GtkBitset *self)
return roaring_bitmap_maximum (&self->roaring);
}
+/**
+ * gtk_bitset_get_size:
+ * @self: a #GtkBitSet
+ *
+ * Gets the number of values that were added to the set.
+ * For example, if the set is empty, 0 is returned.
+ *
+ * Note that this function returns a #guint64, because when all values are
+ * set, the return value is #G_MAXUINT + 1. Unless you are sure this cannot
+ * happen (it can't with #GListModel), be sure to use a 64bit type.
+ *
+ * Returns: The number of values in the set.
+ **/
+guint64
+gtk_bitset_get_size (const GtkBitset *self)
+{
+ g_return_val_if_fail (self != NULL, 0);
+
+ return roaring_bitmap_get_cardinality (&self->roaring);
+}
+
+/**
+ * gtk_bitset_get_size_in_range:
+ * @self: a #GtkBitSet
+ * @first: the first element to include
+ * @last: the last element to include
+ *
+ * Gets the number of values that are part of the set from @first to @last
+ * (inclusive).
+ *
+ * Note that this function returns a #guint64, because when all values are
+ * set, the return value is #G_MAXUINT + 1. Unless you are sure this cannot
+ * happen (it can't with #GListModel), be sure to use a 64bit type.
+ *
+ * Returns: The number of values in the set from @first to @last.
+ **/
+guint64
+gtk_bitset_get_size_in_range (const GtkBitset *self,
+ guint first,
+ guint last)
+{
+ g_return_val_if_fail (self != NULL, 0);
+ g_return_val_if_fail (last >= first, 0);
+
+ return roaring_bitmap_range_cardinality (&self->roaring, first, ((uint64_t) last) + 1);
+}
+
+/**
+ * gtk_bitset_get_nth:
+ * @self: a #GtkBitset
+ * @nth: index of the item to get
+ *
+ * Returns the value of the @nth item in self.
+ *
+ * If @nth is >= the size of @self, 0 is returned.
+ *
+ * Returns: the value of the @nth item in @self
+ **/
+guint
+gtk_bitset_get_nth (const GtkBitset *self,
+ guint nth)
+{
+ uint32_t result;
+
+ if (!roaring_bitmap_select (&self->roaring, nth, &result))
+ return 0;
+
+ return result;
+}
+
/**
* gtk_bitset_new_empty:
*
diff --git a/gtk/gtkbitset.h b/gtk/gtkbitset.h
index fc26e0d7de..1fcbf6faeb 100644
--- a/gtk/gtkbitset.h
+++ b/gtk/gtkbitset.h
@@ -48,6 +48,15 @@ GDK_AVAILABLE_IN_ALL
gboolean gtk_bitset_equals (const GtkBitset *self,
const GtkBitset *other);
GDK_AVAILABLE_IN_ALL
+guint64 gtk_bitset_get_size (const GtkBitset *self);
+GDK_AVAILABLE_IN_ALL
+guint64 gtk_bitset_get_size_in_range (const GtkBitset *self,
+ guint first,
+ guint last);
+GDK_AVAILABLE_IN_ALL
+guint gtk_bitset_get_nth (const GtkBitset *self,
+ guint nth);
+GDK_AVAILABLE_IN_ALL
guint gtk_bitset_get_minimum (const GtkBitset *self);
GDK_AVAILABLE_IN_ALL
guint gtk_bitset_get_maximum (const GtkBitset *self);
diff --git a/gtk/gtkfilterlistmodel.c b/gtk/gtkfilterlistmodel.c
index 97db24fa82..af29154fcb 100644
--- a/gtk/gtkfilterlistmodel.c
+++ b/gtk/gtkfilterlistmodel.c
@@ -684,7 +684,7 @@ gtk_filter_list_model_class_init (GtkFilterListModelClass *class)
P_("Model"),
P_("The model being filtered"),
G_TYPE_LIST_MODEL,
- GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY);
+ GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
g_object_class_install_properties (gobject_class, NUM_PROPERTIES, properties);
}
diff --git a/gtk/gtkselectionfiltermodel.c b/gtk/gtkselectionfiltermodel.c
new file mode 100644
index 0000000000..5cf094e1a3
--- /dev/null
+++ b/gtk/gtkselectionfiltermodel.c
@@ -0,0 +1,376 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * 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: Matthias Clasen
+ */
+
+#include "config.h"
+
+#include "gtkselectionfiltermodel.h"
+#include "gtkbitset.h"
+
+#include "gtkintl.h"
+#include "gtkprivate.h"
+
+/**
+ * SECTION:gtkselectionfiltermodel
+ * @title: GtkSelectionFilterModel
+ * @short_description: A list model that turns a selection in a model
+ * @see_also: #GtkSelectionModel
+ *
+ * #GtkSelectionFilterModel is a list model that presents the
+ * selected items in a #GtkSelectionModel as its own list model.
+ */
+
+enum {
+ PROP_0,
+ PROP_ITEM_TYPE,
+ PROP_MODEL,
+ NUM_PROPERTIES
+};
+
+struct _GtkSelectionFilterModel
+{
+ GObject parent_instance;
+
+ GType item_type;
+ GtkSelectionModel *model;
+ GtkBitset *selection;
+};
+
+struct _GtkSelectionFilterModelClass
+{
+ GObjectClass parent_class;
+};
+
+static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
+
+static GType
+gtk_selection_filter_model_get_item_type (GListModel *list)
+{
+ GtkSelectionFilterModel *self = GTK_SELECTION_FILTER_MODEL (list);
+
+ return self->item_type;
+}
+
+static guint
+gtk_selection_filter_model_get_n_items (GListModel *list)
+{
+ GtkSelectionFilterModel *self = GTK_SELECTION_FILTER_MODEL (list);
+
+ if (self->selection)
+ return gtk_bitset_get_size (self->selection);
+
+ return 0;
+}
+
+static gpointer
+gtk_selection_filter_model_get_item (GListModel *list,
+ guint position)
+{
+ GtkSelectionFilterModel *self = GTK_SELECTION_FILTER_MODEL (list);
+
+ position = gtk_bitset_get_nth (self->selection, position);
+
+ return g_list_model_get_item (G_LIST_MODEL (self->model), position);
+}
+
+static void
+gtk_selection_filter_model_list_model_init (GListModelInterface *iface)
+{
+ iface->get_item_type = gtk_selection_filter_model_get_item_type;
+ iface->get_n_items = gtk_selection_filter_model_get_n_items;
+ iface->get_item = gtk_selection_filter_model_get_item;
+}
+
+G_DEFINE_TYPE_WITH_CODE (GtkSelectionFilterModel, gtk_selection_filter_model, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_selection_filter_model_list_model_init))
+
+static void
+gtk_selection_filter_model_items_changed_cb (GListModel *model,
+ guint position,
+ guint removed,
+ guint added,
+ GtkSelectionFilterModel *self)
+{
+ GtkBitset *selection;
+ guint sel_position;
+ guint sel_removed;
+ guint sel_added;
+
+ selection = gtk_selection_model_get_selection (self->model);
+
+ sel_position = gtk_bitset_get_size_in_range (self->selection, 0, position - 1);
+ sel_removed = gtk_bitset_get_size_in_range (self->selection, position, position + removed);
+ sel_added = gtk_bitset_get_size_in_range (selection, position, position + added);
+
+ gtk_bitset_unref (self->selection);
+ self->selection = gtk_bitset_copy (selection);
+
+ gtk_bitset_unref (selection);
+
+ if (sel_removed > 0 || sel_added > 0)
+ g_list_model_items_changed (G_LIST_MODEL (self), sel_position, sel_removed, sel_added);
+}
+
+static void
+gtk_selection_filter_model_selection_changed_cb (GListModel *model,
+ guint position,
+ guint n_items,
+ GtkSelectionFilterModel *self)
+{
+ GtkBitset *selection;
+ guint sel_position;
+ guint sel_removed;
+ guint sel_added;
+
+ selection = gtk_selection_model_get_selection (self->model);
+
+ sel_position = gtk_bitset_get_size_in_range (self->selection, 0, position - 1);
+ sel_removed = gtk_bitset_get_size_in_range (self->selection, position, position + n_items);
+ sel_added = gtk_bitset_get_size_in_range (selection, position, position + n_items);
+
+ gtk_bitset_unref (self->selection);
+ self->selection = gtk_bitset_copy (selection);
+
+ gtk_bitset_unref (selection);
+
+ if (sel_removed > 0 || sel_added > 0)
+ g_list_model_items_changed (G_LIST_MODEL (self), sel_position, sel_removed, sel_added);
+}
+
+static void
+gtk_selection_filter_model_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GtkSelectionFilterModel *self = GTK_SELECTION_FILTER_MODEL (object);
+
+ switch (prop_id)
+ {
+ case PROP_ITEM_TYPE:
+ self->item_type = g_value_get_gtype (value);
+ break;
+
+ case PROP_MODEL:
+ gtk_selection_filter_model_set_model (self, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gtk_selection_filter_model_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtkSelectionFilterModel *self = GTK_SELECTION_FILTER_MODEL (object);
+
+ switch (prop_id)
+ {
+ case PROP_ITEM_TYPE:
+ g_value_set_gtype (value, self->item_type);
+ break;
+
+ case PROP_MODEL:
+ g_value_set_object (value, self->model);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gtk_selection_filter_model_clear_model (GtkSelectionFilterModel *self)
+{
+ if (self->model == NULL)
+ return;
+
+ g_signal_handlers_disconnect_by_func (self->model, gtk_selection_filter_model_items_changed_cb, self);
+ g_signal_handlers_disconnect_by_func (self->model, gtk_selection_filter_model_selection_changed_cb, self);
+
+ g_clear_object (&self->model);
+ g_clear_pointer (&self->selection, gtk_bitset_unref);
+}
+
+static void
+gtk_selection_filter_model_dispose (GObject *object)
+{
+ GtkSelectionFilterModel *self = GTK_SELECTION_FILTER_MODEL (object);
+
+ gtk_selection_filter_model_clear_model (self);
+
+ G_OBJECT_CLASS (gtk_selection_filter_model_parent_class)->dispose (object);
+}
+
+static void
+gtk_selection_filter_model_class_init (GtkSelectionFilterModelClass *class)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (class);
+
+ gobject_class->set_property = gtk_selection_filter_model_set_property;
+ gobject_class->get_property = gtk_selection_filter_model_get_property;
+ gobject_class->dispose = gtk_selection_filter_model_dispose;
+
+ /**
+ * GtkSelectionFilterModel:item-type:
+ *
+ * The #GType for elements of this object
+ */
+ properties[PROP_ITEM_TYPE] =
+ g_param_spec_gtype ("item-type",
+ P_("Item type"),
+ P_("The type of elements of this object"),
+ G_TYPE_OBJECT,
+ GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GtkSelectionFilterModel:model:
+ *
+ * The model being filtered
+ */
+ properties[PROP_MODEL] =
+ g_param_spec_object ("model",
+ P_("Model"),
+ P_("The model being filtered"),
+ GTK_TYPE_SELECTION_MODEL,
+ GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (gobject_class, NUM_PROPERTIES, properties);
+}
+
+static void
+gtk_selection_filter_model_init (GtkSelectionFilterModel *self)
+{
+}
+
+/**
+ * gtk_selection_filter_model_new:
+ * @model: the selection model to filter
+ *
+ * Creates a new #GtkSelectionFilterModel that will include the
+ * selected items from the underlying selection model.
+ *
+ * Returns: a new #GtkSelectionFilterModel
+ **/
+GtkSelectionFilterModel *
+gtk_selection_filter_model_new (GtkSelectionModel *model)
+{
+ GtkSelectionFilterModel *result;
+
+ g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), NULL);
+
+ result = g_object_new (GTK_TYPE_SELECTION_FILTER_MODEL,
+ "item-type", g_list_model_get_item_type (G_LIST_MODEL (model)),
+ "model", model,
+ NULL);
+
+ return result;
+}
+
+/**
+ * gtk_selection_filter_model_new_for_type:
+ * @item_type: the type of the items that will be returned
+ *
+ * Creates a new empty selection filter model set up to return items
+ * of type @item_type. It is up to the application to set a proper
+ * selection model to ensure the item type is matched.
+ *
+ * Returns: a new #GtkSelectionFilterModel
+ **/
+GtkSelectionFilterModel *
+gtk_selection_filter_model_new_for_type (GType item_type)
+{
+ g_return_val_if_fail (g_type_is_a (item_type, G_TYPE_OBJECT), NULL);
+
+ return g_object_new (GTK_TYPE_SELECTION_FILTER_MODEL,
+ "item-type", item_type,
+ NULL);
+}
+
+/**
+ * gtk_selection_filter_model_set_model:
+ * @self: a #GtkSelectionFilterModel
+ * @model: (allow-none): The model to be filtered
+ *
+ * Sets the model to be filtered.
+ *
+ * Note that GTK makes no effort to ensure that @model conforms to
+ * the item type of @self. It assumes that the caller knows what they
+ * are doing and have set up an appropriate filter to ensure that item
+ * types match.
+ **/
+void
+gtk_selection_filter_model_set_model (GtkSelectionFilterModel *self,
+ GtkSelectionModel *model)
+{
+ guint removed, added;
+
+ g_return_if_fail (GTK_IS_SELECTION_FILTER_MODEL (self));
+ g_return_if_fail (model == NULL || GTK_IS_SELECTION_MODEL (model));
+ g_return_if_fail (model == NULL || g_type_is_a (g_list_model_get_item_type (G_LIST_MODEL (model)),
+ self->item_type));
+
+ if (self->model == model)
+ return;
+
+ removed = g_list_model_get_n_items (G_LIST_MODEL (self));
+ gtk_selection_filter_model_clear_model (self);
+
+ if (model)
+ {
+ GtkBitset *selection;
+
+ self->model = g_object_ref (model);
+
+ selection = gtk_selection_model_get_selection (self->model);
+ self->selection = gtk_bitset_copy (selection);
+ gtk_bitset_unref (selection);
+
+ g_signal_connect (model, "items-changed", G_CALLBACK (gtk_selection_filter_model_items_changed_cb), self);
+ g_signal_connect (model, "selection-changed", G_CALLBACK (gtk_selection_filter_model_selection_changed_cb), self);
+ }
+
+ added = g_list_model_get_n_items (G_LIST_MODEL (self));
+
+ if (removed > 0 || added > 0)
+ g_list_model_items_changed (G_LIST_MODEL (self), 0, removed, added);
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]);
+}
+
+/**
+ * gtk_selection_filter_model_get_model:
+ * @self: a #GtkSelectionFilterModel
+ *
+ * Gets the model currently filtered or %NULL if none.
+ *
+ * Returns: (nullable) (transfer none): The model that gets filtered
+ **/
+GtkSelectionModel *
+gtk_selection_filter_model_get_model (GtkSelectionFilterModel *self)
+{
+ g_return_val_if_fail (GTK_IS_SELECTION_FILTER_MODEL (self), NULL);
+
+ return self->model;
+}
diff --git a/gtk/gtkselectionfiltermodel.h b/gtk/gtkselectionfiltermodel.h
new file mode 100644
index 0000000000..69ab1df37f
--- /dev/null
+++ b/gtk/gtkselectionfiltermodel.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * 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: Matthias Clasen
+ */
+
+#ifndef __GTK_SELECTION_FILTER_MODEL_H__
+#define __GTK_SELECTION_FILTER_MODEL_H__
+
+
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only can be included directly."
+#endif
+
+#include
+#include
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_SELECTION_FILTER_MODEL (gtk_selection_filter_model_get_type ())
+
+GDK_AVAILABLE_IN_ALL
+G_DECLARE_FINAL_TYPE (GtkSelectionFilterModel, gtk_selection_filter_model, GTK, SELECTION_FILTER_MODEL, GObject)
+
+GDK_AVAILABLE_IN_ALL
+GtkSelectionFilterModel * gtk_selection_filter_model_new (GtkSelectionModel *model);
+
+GDK_AVAILABLE_IN_ALL
+GtkSelectionFilterModel * gtk_selection_filter_model_new_for_type (GType item_type);
+
+GDK_AVAILABLE_IN_ALL
+void gtk_selection_filter_model_set_model (GtkSelectionFilterModel *self,
+ GtkSelectionModel *model);
+GDK_AVAILABLE_IN_ALL
+GtkSelectionModel * gtk_selection_filter_model_get_model (GtkSelectionFilterModel *self);
+
+G_END_DECLS
+
+#endif /* __GTK_SELECTION_FILTER_MODEL_H__ */
diff --git a/gtk/meson.build b/gtk/meson.build
index 22b274cba0..02ba60a842 100644
--- a/gtk/meson.build
+++ b/gtk/meson.build
@@ -348,6 +348,7 @@ gtk_public_sources = files([
'gtkscrolledwindow.c',
'gtksearchbar.c',
'gtksearchentry.c',
+ 'gtkselectionfiltermodel.c',
'gtkselectionmodel.c',
'gtkseparator.c',
'gtksettings.c',
@@ -617,6 +618,7 @@ gtk_public_headers = files([
'gtkscrolledwindow.h',
'gtksearchbar.h',
'gtksearchentry.h',
+ 'gtkselectionfiltermodel.h',
'gtkselectionmodel.h',
'gtkseparator.h',
'gtksettings.h',