[calls] Add various gtk list models



commit 01aa8c04c2abb05496664e6c7c127851d0136c76
Author: Evangelos Ribeiro Tzaras <devrtz fortysixandtwo eu>
Date:   Wed Jul 6 02:21:17 2022 +0200

    Add various gtk list models
    
    This is an exact copy from GTK4.
    as grabbed by chattys commit 1ed5084fb965908e3ee0304781b0de06479c869b
    
    Slightly adapted for Calls.
    
    Based on GTKs 01bd4cc4e18a1ea697fe61791ba710d0d55e8290

 src/gtklistmodels/gtk.h                 |   8 +
 src/gtklistmodels/gtkcustomfilter.c     | 157 ++++++
 src/gtklistmodels/gtkcustomfilter.h     |  61 +++
 src/gtklistmodels/gtkcustomsorter.c     | 165 ++++++
 src/gtklistmodels/gtkcustomsorter.h     |  50 ++
 src/gtklistmodels/gtkfilter.c           | 181 +++++++
 src/gtklistmodels/gtkfilter.h           | 122 +++++
 src/gtklistmodels/gtkfilterlistmodel.c  | 916 ++++++++++++++++++++++++++++++++
 src/gtklistmodels/gtkfilterlistmodel.h  |  59 ++
 src/gtklistmodels/gtkflattenlistmodel.c | 541 +++++++++++++++++++
 src/gtklistmodels/gtkflattenlistmodel.h |  51 ++
 src/gtklistmodels/gtkintl.h             |  22 +
 src/gtklistmodels/gtkprivate.h          |  38 ++
 src/gtklistmodels/gtkrbtree.c           | 800 ++++++++++++++++++++++++++++
 src/gtklistmodels/gtkrbtreeprivate.h    |  75 +++
 src/gtklistmodels/gtkslicelistmodel.c   | 529 ++++++++++++++++++
 src/gtklistmodels/gtkslicelistmodel.h   |  65 +++
 src/gtklistmodels/gtksorter.c           | 207 ++++++++
 src/gtklistmodels/gtksorter.h           | 131 +++++
 src/gtklistmodels/gtksortlistmodel.c    | 595 +++++++++++++++++++++
 src/gtklistmodels/gtksortlistmodel.h    |  61 +++
 src/gtklistmodels/gtktypebuiltins.c     |  89 ++++
 src/gtklistmodels/gtktypebuiltins.h     |  18 +
 23 files changed, 4941 insertions(+)
---
diff --git a/src/gtklistmodels/gtk.h b/src/gtklistmodels/gtk.h
new file mode 100644
index 00000000..5bde461c
--- /dev/null
+++ b/src/gtklistmodels/gtk.h
@@ -0,0 +1,8 @@
+#include "gtksorter.h"
+#include "gtkcustomsorter.h"
+#include "gtkfilter.h"
+#include "gtkcustomfilter.h"
+#include "gtksortlistmodel.h"
+#include "gtkfilterlistmodel.h"
+#include "gtkflattenlistmodel.h"
+#include "gtkslicelistmodel.h"
diff --git a/src/gtklistmodels/gtkcustomfilter.c b/src/gtklistmodels/gtkcustomfilter.c
new file mode 100644
index 00000000..7bd72e37
--- /dev/null
+++ b/src/gtklistmodels/gtkcustomfilter.c
@@ -0,0 +1,157 @@
+/*
+ * Copyright © 2019 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+/* #include "config.h" */
+
+#include "gtkcustomfilter.h"
+
+#include "gtkintl.h"
+#include "gtktypebuiltins.h"
+
+/**
+ * SECTION:gtkcustomfilter
+ * @Title: GtkCustomFilter
+ * @Short_description: Filtering with callbacks
+ *
+ * #GtkCustomFilter is a #GtkFilter that uses a callback to determine whether
+ * to include an item or not.
+ */
+struct _GtkCustomFilter
+{
+  GtkFilter parent_instance;
+
+  GtkCustomFilterFunc match_func;
+  gpointer user_data;
+  GDestroyNotify user_destroy;
+};
+
+G_DEFINE_TYPE (GtkCustomFilter, gtk_custom_filter, GTK_TYPE_FILTER)
+
+static gboolean
+gtk_custom_filter_match (GtkFilter *filter,
+                         gpointer   item)
+{
+  GtkCustomFilter *self = GTK_CUSTOM_FILTER (filter);
+
+  if (!self->match_func)
+    return TRUE;
+
+  return self->match_func (item, self->user_data);
+}
+
+static GtkFilterMatch
+gtk_custom_filter_get_strictness (GtkFilter *filter)
+{
+  GtkCustomFilter *self = GTK_CUSTOM_FILTER (filter);
+
+  if (!self->match_func)
+    return GTK_FILTER_MATCH_ALL;
+
+  return GTK_FILTER_MATCH_SOME;
+}
+
+static void
+gtk_custom_filter_dispose (GObject *object)
+{
+  GtkCustomFilter *self = GTK_CUSTOM_FILTER (object);
+
+  if (self->user_destroy)
+    self->user_destroy (self->user_data);
+
+  G_OBJECT_CLASS (gtk_custom_filter_parent_class)->dispose (object);
+}
+
+static void
+gtk_custom_filter_class_init (GtkCustomFilterClass *class)
+{
+  GtkFilterClass *filter_class = GTK_FILTER_CLASS (class);
+  GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+  filter_class->match = gtk_custom_filter_match;
+  filter_class->get_strictness = gtk_custom_filter_get_strictness;
+
+  object_class->dispose = gtk_custom_filter_dispose;
+}
+
+static void
+gtk_custom_filter_init (GtkCustomFilter *self)
+{
+}
+
+/**
+ * gtk_custom_filter_new:
+ * @match_func: (nullable): function to filter items
+ * @user_data: (nullable): user data to pass to @match_func
+ * @user_destroy: destory notify
+ *
+ * Creates a new filter using the given @match_func to filter
+ * items.
+ *
+ * If the filter func changes its filtering behavior,
+ * gtk_filter_changed() needs to be called.
+ *
+ * Returns: a new #GtkFilter
+ **/
+GtkFilter *
+gtk_custom_filter_new (GtkCustomFilterFunc match_func,
+                       gpointer            user_data,
+                       GDestroyNotify      user_destroy)
+{
+  GtkCustomFilter *result;
+
+  result = g_object_new (GTK_TYPE_CUSTOM_FILTER, NULL);
+
+  gtk_custom_filter_set_filter_func (result, match_func, user_data, user_destroy);
+
+  return GTK_FILTER (result);
+}
+
+/**
+ * gtk_custom_filter_set_filter_func:
+ * @self: a #GtkCustomFilter
+ * @match_func: (nullable): function to filter items
+ * @user_data: (nullable): user data to pass to @match_func
+ * @user_destroy: destory notify
+ *
+ * Sets (or unsets) the function used for filtering items.
+ * 
+ * If the filter func changes its filtering behavior,
+ * gtk_filter_changed() needs to be called.
+ *
+ * If a previous function was set, its @user_destroy will be
+ * called now.
+ **/
+void
+gtk_custom_filter_set_filter_func (GtkCustomFilter     *self,
+                                   GtkCustomFilterFunc  match_func,
+                                   gpointer             user_data,
+                                   GDestroyNotify       user_destroy)
+{
+  g_return_if_fail (GTK_IS_CUSTOM_FILTER (self));
+  g_return_if_fail (match_func || (user_data == NULL && !user_destroy));
+
+  if (self->user_destroy)
+    self->user_destroy (self->user_data);
+
+  self->match_func = match_func;
+  self->user_data = user_data;
+  self->user_destroy = user_destroy;
+
+  gtk_filter_changed (GTK_FILTER (self), GTK_FILTER_CHANGE_DIFFERENT);
+}
diff --git a/src/gtklistmodels/gtkcustomfilter.h b/src/gtklistmodels/gtkcustomfilter.h
new file mode 100644
index 00000000..90f6751e
--- /dev/null
+++ b/src/gtklistmodels/gtkcustomfilter.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright © 2019 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#ifndef __GTK_CUSTOM_FILTER_H__
+#define __GTK_CUSTOM_FILTER_H__
+
+#define GTK_COMPILATION
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gtk/gtk.h> can be included directly."
+#endif
+
+#include "gtkfilter.h"
+
+G_BEGIN_DECLS
+
+/**
+ * GtkCustomFilterFunc:
+ * @item: (type GObject): The item to be matched
+ * @user_data: user data
+ *
+ * User function that is called to determine if the @item should be matched.
+ * If the filter matches the item, this function must return %TRUE. If the
+ * item should be filtered out, %FALSE must be returned.
+ *
+ * Returns: %TRUE to keep the item around
+ */
+typedef gboolean (* GtkCustomFilterFunc) (gpointer item, gpointer user_data);
+
+#define GTK_TYPE_CUSTOM_FILTER             (gtk_custom_filter_get_type ())
+GDK_AVAILABLE_IN_ALL
+G_DECLARE_FINAL_TYPE (GtkCustomFilter, gtk_custom_filter, GTK, CUSTOM_FILTER, GtkFilter)
+GDK_AVAILABLE_IN_ALL
+GtkFilter *             gtk_custom_filter_new                   (GtkCustomFilterFunc     match_func,
+                                                                 gpointer                user_data,
+                                                                 GDestroyNotify          user_destroy);
+
+GDK_AVAILABLE_IN_ALL
+void                    gtk_custom_filter_set_filter_func       (GtkCustomFilter        *self,
+                                                                 GtkCustomFilterFunc     match_func,
+                                                                 gpointer                user_data,
+                                                                 GDestroyNotify          user_destroy);
+
+G_END_DECLS
+
+#endif /* __GTK_CUSTOM_FILTER_H__ */
diff --git a/src/gtklistmodels/gtkcustomsorter.c b/src/gtklistmodels/gtkcustomsorter.c
new file mode 100644
index 00000000..e70c6c8b
--- /dev/null
+++ b/src/gtklistmodels/gtkcustomsorter.c
@@ -0,0 +1,165 @@
+/*
+ * Copyright © 2019 Matthias Clasen
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Matthias Clasen <mclasen redhat com>
+ */
+
+/* #include "config.h" */
+
+#include "gtkcustomsorter.h"
+
+#include "gtkintl.h"
+#include "gtktypebuiltins.h"
+
+/**
+ * SECTION:gtkcustomsorter
+ * @Title: GtkCustomSorter
+ * @Short_description: Sorting with a callback
+ *
+ * GtkCustomSorter is a #GtkSorter implementation that sorts
+ * via a traditional #GCompareDataFunc callback.
+ */
+struct _GtkCustomSorter
+{
+  GtkSorter parent_instance;
+
+  GCompareDataFunc sort_func;
+  gpointer user_data;
+  GDestroyNotify user_destroy;
+};
+
+G_DEFINE_TYPE (GtkCustomSorter, gtk_custom_sorter, GTK_TYPE_SORTER)
+
+static inline GtkOrdering
+gtk_ordering_from_cmpfunc (int cmpfunc_result)
+{
+  return (GtkOrdering) ((cmpfunc_result > 0) - (cmpfunc_result < 0));
+}
+
+static GtkOrdering
+gtk_custom_sorter_compare (GtkSorter *sorter,
+                           gpointer   item1,
+                           gpointer   item2)
+{
+  GtkCustomSorter *self = GTK_CUSTOM_SORTER (sorter);
+
+  if (!self->sort_func)
+    return GTK_ORDERING_EQUAL;
+
+  return gtk_ordering_from_cmpfunc (self->sort_func (item1, item2, self->user_data));
+}
+
+static GtkSorterOrder
+gtk_custom_sorter_get_order (GtkSorter *sorter)
+{
+  GtkCustomSorter *self = GTK_CUSTOM_SORTER (sorter);
+
+  if (!self->sort_func)
+    return GTK_SORTER_ORDER_NONE;
+
+  return GTK_SORTER_ORDER_PARTIAL;
+}
+
+static void
+gtk_custom_sorter_dispose (GObject *object)
+{
+  GtkCustomSorter *self = GTK_CUSTOM_SORTER (object);
+
+  if (self->user_destroy)
+    self->user_destroy (self->user_data);
+
+  self->sort_func = NULL;
+  self->user_destroy = NULL;
+  self->user_data = NULL;
+
+  G_OBJECT_CLASS (gtk_custom_sorter_parent_class)->dispose (object);
+}
+
+static void
+gtk_custom_sorter_class_init (GtkCustomSorterClass *class)
+{
+  GtkSorterClass *sorter_class = GTK_SORTER_CLASS (class);
+  GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+  sorter_class->compare = gtk_custom_sorter_compare;
+  sorter_class->get_order = gtk_custom_sorter_get_order;
+
+  object_class->dispose = gtk_custom_sorter_dispose;
+}
+
+static void
+gtk_custom_sorter_init (GtkCustomSorter *self)
+{
+}
+
+/**
+ * gtk_custom_sorter_new:
+ * @sort_func: the #GCompareDataFunc to use for sorting
+ * @user_data: (nullable): user data to pass to @sort_func
+ * @user_destroy: (nullable): destroy notify for @user_data
+ *
+ * Creates a new #GtkSorter that works by calling
+ * @sort_func to compare items.
+ *
+ * Returns: a new #GTkSorter
+ */ 
+GtkSorter *
+gtk_custom_sorter_new (GCompareDataFunc sort_func,
+                       gpointer         user_data,
+                       GDestroyNotify   user_destroy)
+{
+  GtkCustomSorter *sorter;
+
+  sorter = g_object_new (GTK_TYPE_CUSTOM_SORTER, NULL);
+
+  gtk_custom_sorter_set_sort_func (sorter, sort_func, user_data, user_destroy);
+
+  return GTK_SORTER (sorter);
+}
+
+/**
+ * gtk_custom_sorter_set_sort_func:
+ * @self: a #GtkCustomSorter
+ * @sort_func: (nullable): function to sort items
+ * @user_data: (nullable): user data to pass to @match_func
+ * @user_destroy: destory notify
+ *
+ * Sets (or unsets) the function used for sorting items.
+ * 
+ * If the sort func changes its sorting behavior,
+ * gtk_sorter_changed() needs to be called.
+ *
+ * If a previous function was set, its @user_destroy will be
+ * called now.
+ **/
+void
+gtk_custom_sorter_set_sort_func (GtkCustomSorter  *self,
+                                 GCompareDataFunc  sort_func,
+                                 gpointer          user_data,
+                                 GDestroyNotify    user_destroy)
+{
+  g_return_if_fail (GTK_IS_CUSTOM_SORTER (self));
+  g_return_if_fail (sort_func || (user_data == NULL && !user_destroy));
+
+  if (self->user_destroy)
+    self->user_destroy (self->user_data);
+
+  self->sort_func = sort_func;
+  self->user_data = user_data;
+  self->user_destroy = user_destroy;
+
+  gtk_sorter_changed (GTK_SORTER (self), GTK_SORTER_CHANGE_DIFFERENT);
+}
diff --git a/src/gtklistmodels/gtkcustomsorter.h b/src/gtklistmodels/gtkcustomsorter.h
new file mode 100644
index 00000000..1adf56a5
--- /dev/null
+++ b/src/gtklistmodels/gtkcustomsorter.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright © 2019 Matthias Clasen
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Matthias Clasen <mclasen redhat com>
+ */
+
+#ifndef __GTK_CUSTOM_SORTER_H__
+#define __GTK_CUSTOM_SORTER_H__
+
+#define GTK_COMPILATION
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gtk/gtk.h> can be included directly."
+#endif
+
+/* #include <gtk/gtkexpression.h> */
+#include "gtksorter.h"
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_CUSTOM_SORTER             (gtk_custom_sorter_get_type ())
+GDK_AVAILABLE_IN_ALL
+G_DECLARE_FINAL_TYPE (GtkCustomSorter, gtk_custom_sorter, GTK, CUSTOM_SORTER, GtkSorter)
+
+GDK_AVAILABLE_IN_ALL
+GtkSorter *             gtk_custom_sorter_new                   (GCompareDataFunc        sort_func,
+                                                                 gpointer                user_data,
+                                                                 GDestroyNotify          user_destroy);
+
+GDK_AVAILABLE_IN_ALL
+void                    gtk_custom_sorter_set_sort_func         (GtkCustomSorter        *self,
+                                                                 GCompareDataFunc        sort_func,
+                                                                 gpointer                user_data,
+                                                                 GDestroyNotify          user_destroy);
+
+G_END_DECLS
+
+#endif /* __GTK_CUSTOM_SORTER_H__ */
diff --git a/src/gtklistmodels/gtkfilter.c b/src/gtklistmodels/gtkfilter.c
new file mode 100644
index 00000000..0ece82d1
--- /dev/null
+++ b/src/gtklistmodels/gtkfilter.c
@@ -0,0 +1,181 @@
+/*
+ * Copyright © 2019 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+/* #include "config.h" */
+
+#include "gtkfilter.h"
+
+#include "gtkintl.h"
+#include "gtktypebuiltins.h"
+
+/**
+ * SECTION:gtkfilter
+ * @Title: GtkFilter
+ * @Short_description: Filtering items
+ * @See_also: #GtkFilerListModel
+ *
+ * #GtkFilter is the way to describe filters to be used in #GtkFilterListModel.
+ * 
+ * The model will use a filter to determine if it should filter items or not
+ * by calling gtk_filter_match() for each item and only keeping the ones
+ * visible that the function returns %TRUE for.
+ *
+ * Filters may change what items they match through their lifetime. In that
+ * case, they can call gtk_filter_changed() which will emit the #GtkFilter:changed
+ * signal to notify that previous filter results are no longer valid and that
+ * items should be checked via gtk_filter_match() again.
+ *
+ * GTK provides various premade filter implementations for common filtering
+ * operations. These filters often include properties that can be linked to
+ * various widgets to easily allow searches.  
+ *
+ * However, in particular for large lists or complex search methods, it is
+ * also possible to subclass #GtkFilter and provide one's own filter.
+ */
+
+enum {
+  CHANGED,
+  LAST_SIGNAL
+};
+
+G_DEFINE_TYPE (GtkFilter, gtk_filter, G_TYPE_OBJECT)
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static gboolean
+gtk_filter_default_match (GtkFilter *self,
+                          gpointer   item)
+{
+  g_critical ("Filter of type '%s' does not implement GtkFilter::match", G_OBJECT_TYPE_NAME (self));
+
+  return FALSE;
+}
+
+static GtkFilterMatch
+gtk_filter_default_get_strictness (GtkFilter *self)
+{
+  return GTK_FILTER_MATCH_SOME;
+}
+
+static void
+gtk_filter_class_init (GtkFilterClass *class)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (class);
+
+  class->match = gtk_filter_default_match;
+  class->get_strictness = gtk_filter_default_get_strictness;
+
+  /**
+   * GtkFilter:changed:
+   * @self: The #GtkFilter
+   * @change: how the filter changed
+   *
+   * This signal is emitted whenever the filter changed. Users of the filter
+   * should then check items again via gtk_filter_match().
+   *
+   * #GtkFilterListModel handles this signal automatically.
+   *
+   * Depending on the @change parameter, not all items need to be changed, but
+   * only some. Refer to the #GtkFilterChange documentation for details.
+   */
+  signals[CHANGED] =
+    g_signal_new (I_("changed"),
+                  G_TYPE_FROM_CLASS (gobject_class),
+                  G_SIGNAL_RUN_LAST,
+                  0,
+                  NULL, NULL,
+                  g_cclosure_marshal_VOID__ENUM,
+                  G_TYPE_NONE, 1,
+                  GTK_TYPE_FILTER_CHANGE);
+  g_signal_set_va_marshaller (signals[CHANGED],
+                              G_TYPE_FROM_CLASS (gobject_class),
+                              g_cclosure_marshal_VOID__ENUMv);
+}
+
+static void
+gtk_filter_init (GtkFilter *self)
+{
+}
+
+/**
+ * gtk_filter_match:
+ * @self: a #GtkFilter
+ * @item: (type GObject) (transfer none): The item to check
+ *
+ * Checks if the given @item is matched by the filter or not. 
+ *
+ * Returns: %TRUE if the filter matches the item and a filter model should
+ *     keep it, %FALSE if not.
+ */
+gboolean
+gtk_filter_match (GtkFilter *self,
+                  gpointer   item)
+{
+  g_return_val_if_fail (GTK_IS_FILTER (self), FALSE);
+  g_return_val_if_fail (item != NULL, FALSE);
+
+  return GTK_FILTER_GET_CLASS (self)->match (self, item);
+}
+
+/**
+ * gtk_filter_get_strictness:
+ * @self: a #GtkFilter
+ *
+ * Gets the known strictness of @filters. If the strictness is not known,
+ * %GTK_FILTER_MATCH_SOME is returned.
+ *
+ * This value may change after emission of the GtkFilter:changed signal.
+ *
+ * This function is meant purely for optimization purposes, filters can
+ * choose to omit implementing it, but #GtkFilterListModel uses it.
+ *
+ * Returns: the strictness of @self
+ **/
+GtkFilterMatch
+gtk_filter_get_strictness (GtkFilter *self)
+{
+  g_return_val_if_fail (GTK_IS_FILTER (self), GTK_FILTER_MATCH_SOME);
+
+  return GTK_FILTER_GET_CLASS (self)->get_strictness (self);
+}
+
+/**
+ * gtk_filter_changed:
+ * @self: a #GtkFilter
+ * @change: How the filter changed
+ *
+ * Emits the #GtkFilter:changed signal to notify all users of the filter that
+ * the filter changed. Users of the filter should then check items again via
+ * gtk_filter_match().
+ *
+ * Depending on the @change parameter, not all items need to be changed, but
+ * only some. Refer to the #GtkFilterChange documentation for details.
+ *
+ * This function is intended for implementors of #GtkFilter subclasses and
+ * should not be called from other functions.
+ */
+void
+gtk_filter_changed (GtkFilter       *self,
+                    GtkFilterChange  change)
+{
+  g_return_if_fail (GTK_IS_FILTER (self));
+
+  g_signal_emit (self, signals[CHANGED], 0, change);
+}
+
diff --git a/src/gtklistmodels/gtkfilter.h b/src/gtklistmodels/gtkfilter.h
new file mode 100644
index 00000000..f34cc90c
--- /dev/null
+++ b/src/gtklistmodels/gtkfilter.h
@@ -0,0 +1,122 @@
+/*
+ * Copyright © 2019 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#ifndef __GTK_FILTER_H__
+#define __GTK_FILTER_H__
+
+#define GTK_COMPILATION
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gtk/gtk.h> can be included directly."
+#endif
+
+#include <gdk/gdk.h>
+
+G_BEGIN_DECLS
+
+/**
+ * GtkFilterMatch:
+ * @GTK_FILTER_MATCH_SOME: The filter matches some items,
+ *     gtk_filter_match() may return %TRUE or %FALSE
+ * @GTK_FILTER_MATCH_NONE: The filter does not match any item,
+ *     gtk_filter_match() will always return %FALSE.
+ * @GTK_FILTER_MATCH_ALL: The filter matches all items,
+ *     gtk_filter_match() will alays return %TRUE.
+ *
+ * Describes the known strictness of a filter.
+ *
+ * Note that for filters where the strictness is not known,
+ * %@GTK_FILTER_MATCH_SOME is always an acceptable value,
+ * even if a filter does match all or no items.
+ */
+typedef enum {
+  GTK_FILTER_MATCH_SOME = 0,
+  GTK_FILTER_MATCH_NONE,
+  GTK_FILTER_MATCH_ALL
+} GtkFilterMatch;
+
+/**
+ * GtkFilterChange:
+ * @GTK_FILTER_CHANGE_DIFFERENT: The filter change cannot be
+ *     described with any of the other enumeration values.
+ * @GTK_FILTER_CHANGE_LESS_STRICT: The filter is less strict than
+ *     it was before: All items that it used to return %TRUE for
+ *     still return %TRUE, others now may, too.
+ * @GTK_FILTER_CHANGE_MORE_STRICT: The filter is more strict than
+ *     it was before: All items that it used to return %FALSE for
+ *     still return %FALSE, others now may, too.
+ *
+ * Describes changes in a filter in more detail and allows objects
+ * using the filter to optimize refiltering items.
+ *
+ * If you are writing an implementation and are not sure which
+ * value to pass, @GTK_FILTER_CHANGE_DIFFERENT is always a correct
+ * choice.
+ */
+typedef enum {
+  GTK_FILTER_CHANGE_DIFFERENT = 0,
+  GTK_FILTER_CHANGE_LESS_STRICT,
+  GTK_FILTER_CHANGE_MORE_STRICT,
+} GtkFilterChange;
+
+#define GTK_TYPE_FILTER             (gtk_filter_get_type ())
+
+/**
+ * GtkFilter:
+ *
+ * The object describing a filter.
+ */
+GDK_AVAILABLE_IN_ALL
+G_DECLARE_DERIVABLE_TYPE (GtkFilter, gtk_filter, GTK, FILTER, GObject)
+
+struct _GtkFilterClass
+{
+  GObjectClass parent_class;
+
+  gboolean              (* match)                               (GtkFilter              *self,
+                                                                 gpointer                item);
+
+  /* optional */
+  GtkFilterMatch        (* get_strictness)                      (GtkFilter              *self);
+
+  /* Padding for future expansion */
+  void (*_gtk_reserved1) (void);
+  void (*_gtk_reserved2) (void);
+  void (*_gtk_reserved3) (void);
+  void (*_gtk_reserved4) (void);
+  void (*_gtk_reserved5) (void);
+  void (*_gtk_reserved6) (void);
+  void (*_gtk_reserved7) (void);
+  void (*_gtk_reserved8) (void);
+};
+
+GDK_AVAILABLE_IN_ALL
+gboolean                gtk_filter_match                        (GtkFilter              *self,
+                                                                 gpointer                item);
+GDK_AVAILABLE_IN_ALL
+GtkFilterMatch          gtk_filter_get_strictness               (GtkFilter              *self);
+
+/* for filter implementations */
+GDK_AVAILABLE_IN_ALL
+void                    gtk_filter_changed                      (GtkFilter              *self,
+                                                                 GtkFilterChange         change);
+
+
+G_END_DECLS
+
+#endif /* __GTK_FILTER_H__ */
diff --git a/src/gtklistmodels/gtkfilterlistmodel.c b/src/gtklistmodels/gtkfilterlistmodel.c
new file mode 100644
index 00000000..a9bd13bd
--- /dev/null
+++ b/src/gtklistmodels/gtkfilterlistmodel.c
@@ -0,0 +1,916 @@
+/*
+ * Copyright © 2018 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+/* #include "config.h" */
+
+#include "gtkfilterlistmodel.h"
+
+#include "gtkrbtreeprivate.h"
+#include "gtkintl.h"
+#include "gtkprivate.h"
+
+/**
+ * SECTION:gtkfilterlistmodel
+ * @title: GtkFilterListModel
+ * @short_description: A list model that filters its items
+ * @see_also: #GListModel, #GtkFilter
+ *
+ * #GtkFilterListModel is a list model that filters a given other
+ * listmodel.
+ * It hides some elements from the other model according to
+ * criteria given by a #GtkFilter.
+ */
+
+enum {
+  PROP_0,
+  PROP_FILTER,
+  PROP_ITEM_TYPE,
+  PROP_MODEL,
+  NUM_PROPERTIES
+};
+
+typedef struct _FilterNode FilterNode;
+typedef struct _FilterAugment FilterAugment;
+
+struct _FilterNode
+{
+  guint visible : 1;
+};
+
+struct _FilterAugment
+{
+  guint n_items;
+  guint n_visible;
+};
+
+struct _GtkFilterListModel
+{
+  GObject parent_instance;
+
+  GType item_type;
+  GListModel *model;
+  GtkFilter *filter;
+  GtkFilterMatch strictness;
+
+  GtkRbTree *items; /* NULL if strictness != GTK_FILTER_MATCH_SOME */
+};
+
+struct _GtkFilterListModelClass
+{
+  GObjectClass parent_class;
+};
+
+static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
+
+static void
+gtk_filter_list_model_augment (GtkRbTree *filter,
+                               gpointer   _aug,
+                               gpointer   _node,
+                               gpointer   left,
+                               gpointer   right)
+{
+  FilterNode *node = _node;
+  FilterAugment *aug = _aug;
+
+  aug->n_items = 1;
+  aug->n_visible = node->visible ? 1 : 0;
+
+  if (left)
+    {
+      FilterAugment *left_aug = gtk_rb_tree_get_augment (filter, left);
+      aug->n_items += left_aug->n_items;
+      aug->n_visible += left_aug->n_visible;
+    }
+  if (right)
+    {
+      FilterAugment *right_aug = gtk_rb_tree_get_augment (filter, right);
+      aug->n_items += right_aug->n_items;
+      aug->n_visible += right_aug->n_visible;
+    }
+}
+
+static FilterNode *
+gtk_filter_list_model_get_nth_filtered (GtkRbTree *tree,
+                                        guint      position,
+                                        guint     *out_unfiltered)
+{
+  FilterNode *node, *tmp;
+  guint unfiltered;
+
+  node = gtk_rb_tree_get_root (tree);
+  unfiltered = 0;
+
+  while (node)
+    {
+      tmp = gtk_rb_tree_node_get_left (node);
+      if (tmp)
+        {
+          FilterAugment *aug = gtk_rb_tree_get_augment (tree, tmp);
+          if (position < aug->n_visible)
+            {
+              node = tmp;
+              continue;
+            }
+          position -= aug->n_visible;
+          unfiltered += aug->n_items;
+        }
+
+      if (node->visible)
+        {
+          if (position == 0)
+            break;
+          position--;
+        }
+
+      unfiltered++;
+
+      node = gtk_rb_tree_node_get_right (node);
+    }
+
+  if (out_unfiltered)
+    *out_unfiltered = unfiltered;
+
+  return node;
+}
+
+static FilterNode *
+gtk_filter_list_model_get_nth (GtkRbTree *tree,
+                               guint      position,
+                               guint     *out_filtered)
+{
+  FilterNode *node, *tmp;
+  guint filtered;
+
+  node = gtk_rb_tree_get_root (tree);
+  filtered = 0;
+
+  while (node)
+    {
+      tmp = gtk_rb_tree_node_get_left (node);
+      if (tmp)
+        {
+          FilterAugment *aug = gtk_rb_tree_get_augment (tree, tmp);
+          if (position < aug->n_items)
+            {
+              node = tmp;
+              continue;
+            }
+          position -= aug->n_items;
+          filtered += aug->n_visible;
+        }
+
+      if (position == 0)
+        break;
+
+      position--;
+      if (node->visible)
+        filtered++;
+
+      node = gtk_rb_tree_node_get_right (node);
+    }
+
+  if (out_filtered)
+    *out_filtered = filtered;
+
+  return node;
+}
+
+static GType
+gtk_filter_list_model_get_item_type (GListModel *list)
+{
+  GtkFilterListModel *self = GTK_FILTER_LIST_MODEL (list);
+
+  return self->item_type;
+}
+
+static guint
+gtk_filter_list_model_get_n_items (GListModel *list)
+{
+  GtkFilterListModel *self = GTK_FILTER_LIST_MODEL (list);
+  FilterAugment *aug;
+  FilterNode *node;
+
+  switch (self->strictness)
+    {
+    case GTK_FILTER_MATCH_NONE:
+      return 0;
+
+    case GTK_FILTER_MATCH_ALL:
+      return g_list_model_get_n_items (self->model);
+
+    default:
+      g_assert_not_reached ();
+      G_GNUC_FALLTHROUGH;
+    case GTK_FILTER_MATCH_SOME:
+      break;
+    }
+
+  node = gtk_rb_tree_get_root (self->items);
+  if (node == NULL)
+    return 0;
+
+  aug = gtk_rb_tree_get_augment (self->items, node);
+  return aug->n_visible;
+}
+
+static gpointer
+gtk_filter_list_model_get_item (GListModel *list,
+                                guint       position)
+{
+  GtkFilterListModel *self = GTK_FILTER_LIST_MODEL (list);
+  guint unfiltered;
+
+  switch (self->strictness)
+    {
+    case GTK_FILTER_MATCH_NONE:
+      return NULL;
+
+    case GTK_FILTER_MATCH_ALL:
+      unfiltered = position;
+      break;
+
+    default:
+      g_assert_not_reached ();
+      G_GNUC_FALLTHROUGH;
+    case GTK_FILTER_MATCH_SOME:
+      gtk_filter_list_model_get_nth_filtered (self->items, position, &unfiltered);
+      break;
+    }
+
+  return g_list_model_get_item (self->model, unfiltered);
+}
+
+static void
+gtk_filter_list_model_model_init (GListModelInterface *iface)
+{
+  iface->get_item_type = gtk_filter_list_model_get_item_type;
+  iface->get_n_items = gtk_filter_list_model_get_n_items;
+  iface->get_item = gtk_filter_list_model_get_item;
+}
+
+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))
+
+static gboolean
+gtk_filter_list_model_run_filter (GtkFilterListModel *self,
+                                  guint               position)
+{
+  gpointer item;
+  gboolean visible;
+
+  /* all other cases should have beeen optimized away */
+  g_assert (self->strictness == GTK_FILTER_MATCH_SOME);
+
+  item = g_list_model_get_item (self->model, position);
+  visible = gtk_filter_match (self->filter, item);
+  g_object_unref (item);
+
+  return visible;
+}
+
+static guint
+gtk_filter_list_model_add_items (GtkFilterListModel *self,
+                                 FilterNode         *after,
+                                 guint               position,
+                                 guint               n_items)
+{
+  FilterNode *node;
+  guint i, n_visible;
+
+  n_visible = 0;
+  
+  for (i = 0; i < n_items; i++)
+    {
+      node = gtk_rb_tree_insert_before (self->items, after);
+      node->visible = gtk_filter_list_model_run_filter (self, position + i);
+      if (node->visible)
+        n_visible++;
+    }
+
+  return n_visible;
+}
+
+static void
+gtk_filter_list_model_items_changed_cb (GListModel         *model,
+                                        guint               position,
+                                        guint               removed,
+                                        guint               added,
+                                        GtkFilterListModel *self)
+{
+  FilterNode *node;
+  guint i, filter_position, filter_removed, filter_added;
+
+  switch (self->strictness)
+    {
+    case GTK_FILTER_MATCH_NONE:
+      return;
+
+    case GTK_FILTER_MATCH_ALL:
+      g_list_model_items_changed (G_LIST_MODEL (self), position, removed, added);
+      return;
+
+    default:
+      g_assert_not_reached ();
+      G_GNUC_FALLTHROUGH;
+    case GTK_FILTER_MATCH_SOME:
+      break;
+    }
+
+  node = gtk_filter_list_model_get_nth (self->items, position, &filter_position);
+
+  filter_removed = 0;
+  for (i = 0; i < removed; i++)
+    {
+      FilterNode *next = gtk_rb_tree_node_get_next (node);
+      if (node->visible)
+        filter_removed++;
+      gtk_rb_tree_remove (self->items, node);
+      node = next;
+    }
+
+  filter_added = gtk_filter_list_model_add_items (self, node, position, added);
+
+  if (filter_removed > 0 || filter_added > 0)
+    g_list_model_items_changed (G_LIST_MODEL (self), filter_position, filter_removed, filter_added);
+}
+
+static void
+gtk_filter_list_model_set_property (GObject      *object,
+                                    guint         prop_id,
+                                    const GValue *value,
+                                    GParamSpec   *pspec)
+{
+  GtkFilterListModel *self = GTK_FILTER_LIST_MODEL (object);
+
+  switch (prop_id)
+    {
+    case PROP_FILTER:
+      gtk_filter_list_model_set_filter (self, g_value_get_object (value));
+      break;
+
+    case PROP_ITEM_TYPE:
+      self->item_type = g_value_get_gtype (value);
+      break;
+
+    case PROP_MODEL:
+      gtk_filter_list_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_filter_list_model_get_property (GObject     *object,
+                                    guint        prop_id,
+                                    GValue      *value,
+                                    GParamSpec  *pspec)
+{
+  GtkFilterListModel *self = GTK_FILTER_LIST_MODEL (object);
+
+  switch (prop_id)
+    {
+    case PROP_FILTER:
+      g_value_set_object (value, self->filter);
+      break;
+
+    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_filter_list_model_clear_model (GtkFilterListModel *self)
+{
+  if (self->model == NULL)
+    return;
+
+  g_signal_handlers_disconnect_by_func (self->model, gtk_filter_list_model_items_changed_cb, self);
+  g_clear_object (&self->model);
+  if (self->items)
+    gtk_rb_tree_remove_all (self->items);
+}
+
+/*<private>
+ * gtk_filter_list_model_find_filtered:
+ * @self: a #GtkFilterListModel
+ * @start: (out) (caller-allocates): number of unfiltered items
+ *     at start of list
+ * @end: (out) (caller-allocates): number of unfiltered items
+ *     at end of list
+ * @n_items: (out) (caller-allocates): number of unfiltered items in
+ *     list
+ *
+ * Checks if elements in self->items are filtered out and returns
+ * the range that they occupy.  
+ * This function is intended to be used for GListModel::items-changed
+ * emissions, so it is called in an intermediate state for @self.
+ *
+ * Returns: %TRUE if elements are filtered out, %FALSE if none are
+ **/
+static gboolean
+gtk_filter_list_model_find_filtered (GtkFilterListModel *self,
+                                     guint              *start,
+                                     guint              *end,
+                                     guint              *n_items)
+{
+  FilterNode *root, *node, *tmp;
+  FilterAugment *aug;
+
+  if (self->items == NULL || self->model == NULL)
+    return FALSE;
+
+  root = gtk_rb_tree_get_root (self->items);
+  if (root == NULL)
+    return FALSE; /* empty parent model */
+
+  aug = gtk_rb_tree_get_augment (self->items, root);
+  if (aug->n_items == aug->n_visible)
+    return FALSE; /* all items visible */
+
+  /* find first filtered */
+  *start = 0;
+  *end = 0;
+  *n_items = aug->n_visible;
+
+  node = root;
+  while (node)
+    {
+      tmp = gtk_rb_tree_node_get_left (node);
+      if (tmp)
+        {
+          aug = gtk_rb_tree_get_augment (self->items, tmp);
+          if (aug->n_visible < aug->n_items)
+            {
+              node = tmp;
+              continue;
+            }
+          *start += aug->n_items;
+        }
+
+      if (!node->visible)
+        break;
+
+      (*start)++;
+
+      node = gtk_rb_tree_node_get_right (node);
+    }
+
+  /* find last filtered by doing everything the opposite way */
+  node = root;
+  while (node)
+    {
+      tmp = gtk_rb_tree_node_get_right (node);
+      if (tmp)
+        {
+          aug = gtk_rb_tree_get_augment (self->items, tmp);
+          if (aug->n_visible < aug->n_items)
+            {
+              node = tmp;
+              continue;
+            }
+          *end += aug->n_items;
+        }
+
+      if (!node->visible)
+        break;
+
+      (*end)++;
+
+      node = gtk_rb_tree_node_get_left (node);
+    }
+
+  return TRUE;
+}
+
+static void
+gtk_filter_list_model_refilter (GtkFilterListModel *self);
+
+static void
+gtk_filter_list_model_update_strictness_and_refilter (GtkFilterListModel *self)
+{
+  GtkFilterMatch new_strictness;
+
+  if (self->model == NULL)
+    new_strictness = GTK_FILTER_MATCH_NONE;
+  else if (self->filter == NULL)
+    new_strictness = GTK_FILTER_MATCH_ALL;
+  else
+    new_strictness = gtk_filter_get_strictness (self->filter);
+
+  /* don't set self->strictness yet so get_n_items() and friends return old values */
+
+  switch (new_strictness)
+    {
+    case GTK_FILTER_MATCH_NONE:
+      {
+        guint n_before = g_list_model_get_n_items (G_LIST_MODEL (self));
+        g_clear_pointer (&self->items, gtk_rb_tree_unref);
+        self->strictness = new_strictness;
+        if (n_before > 0)
+          g_list_model_items_changed (G_LIST_MODEL (self), 0, n_before, 0);
+      }
+      break;
+
+    case GTK_FILTER_MATCH_ALL:
+      switch (self->strictness)
+        {
+        case GTK_FILTER_MATCH_NONE:
+          self->strictness = new_strictness;
+          g_list_model_items_changed (G_LIST_MODEL (self), 0, 0, g_list_model_get_n_items (self->model));
+          break;
+        case GTK_FILTER_MATCH_ALL:
+          self->strictness = new_strictness;
+          break;
+        default:
+        case GTK_FILTER_MATCH_SOME:
+          {
+            guint start, end, n_before, n_after;
+            self->strictness = new_strictness;
+            if (gtk_filter_list_model_find_filtered (self, &start, &end, &n_before))
+              {
+                n_after = g_list_model_get_n_items (G_LIST_MODEL (self));
+                g_clear_pointer (&self->items, gtk_rb_tree_unref);
+                g_list_model_items_changed (G_LIST_MODEL (self), start, n_before - end - start, n_after - 
end - start);
+              }
+            else
+              {
+                g_clear_pointer (&self->items, gtk_rb_tree_unref);
+              }
+          }
+          break;
+        }
+      break;
+    
+    default:
+      g_assert_not_reached ();
+      G_GNUC_FALLTHROUGH;
+    case GTK_FILTER_MATCH_SOME:
+      switch (self->strictness)
+        {
+        case GTK_FILTER_MATCH_NONE:
+          {
+            guint n_after;
+            self->strictness = new_strictness;
+            self->items = gtk_rb_tree_new (FilterNode,
+                                           FilterAugment,
+                                           gtk_filter_list_model_augment,
+                                           NULL, NULL);
+            n_after = gtk_filter_list_model_add_items (self, NULL, 0, g_list_model_get_n_items 
(self->model));
+            if (n_after > 0)
+              g_list_model_items_changed (G_LIST_MODEL (self), 0, 0, n_after);
+          }
+          break;
+        case GTK_FILTER_MATCH_ALL:
+          {
+            guint start, end, n_before, n_after;
+            self->strictness = new_strictness;
+            self->items = gtk_rb_tree_new (FilterNode,
+                                           FilterAugment,
+                                           gtk_filter_list_model_augment,
+                                           NULL, NULL);
+            n_before = g_list_model_get_n_items (self->model);
+            gtk_filter_list_model_add_items (self, NULL, 0, n_before);
+            if (gtk_filter_list_model_find_filtered (self, &start, &end, &n_after))
+              g_list_model_items_changed (G_LIST_MODEL (self), start, n_before - end - start, n_after - end 
- start);
+          }
+          break;
+        default:
+        case GTK_FILTER_MATCH_SOME:
+          gtk_filter_list_model_refilter (self);
+          break;
+        }
+    }
+}
+
+static void
+gtk_filter_list_model_filter_changed_cb (GtkFilter          *filter,
+                                         GtkFilterChange     change,
+                                         GtkFilterListModel *self)
+{
+  gtk_filter_list_model_update_strictness_and_refilter (self);
+}
+
+static void
+gtk_filter_list_model_clear_filter (GtkFilterListModel *self)
+{
+  if (self->filter == NULL)
+    return;
+
+  g_signal_handlers_disconnect_by_func (self->filter, gtk_filter_list_model_filter_changed_cb, self);
+  g_clear_object (&self->filter);
+}
+
+static void
+gtk_filter_list_model_dispose (GObject *object)
+{
+  GtkFilterListModel *self = GTK_FILTER_LIST_MODEL (object);
+
+  gtk_filter_list_model_clear_model (self);
+  gtk_filter_list_model_clear_filter (self);
+  g_clear_pointer (&self->items, gtk_rb_tree_unref);
+
+  G_OBJECT_CLASS (gtk_filter_list_model_parent_class)->dispose (object);
+}
+
+static void
+gtk_filter_list_model_class_init (GtkFilterListModelClass *class)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (class);
+
+  gobject_class->set_property = gtk_filter_list_model_set_property;
+  gobject_class->get_property = gtk_filter_list_model_get_property;
+  gobject_class->dispose = gtk_filter_list_model_dispose;
+
+  /**
+   * GtkFilterListModel:filter:
+   *
+   * The filter for this model
+   */
+  properties[PROP_FILTER] =
+      g_param_spec_object ("filter",
+                           P_("Filter"),
+                           P_("The filter set for this model"),
+                           GTK_TYPE_FILTER,
+                           GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  /**
+   * GtkFilterListModel: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);
+
+  /**
+   * GtkFilterListModel:model:
+   *
+   * The model being filtered
+   */
+  properties[PROP_MODEL] =
+      g_param_spec_object ("model",
+                           P_("Model"),
+                           P_("The model being filtered"),
+                           G_TYPE_LIST_MODEL,
+                           GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY);
+
+  g_object_class_install_properties (gobject_class, NUM_PROPERTIES, properties);
+}
+
+static void
+gtk_filter_list_model_init (GtkFilterListModel *self)
+{
+  self->strictness = GTK_FILTER_MATCH_NONE;
+}
+
+/**
+ * gtk_filter_list_model_new:
+ * @model: the model to sort
+ * @filter: (allow-none): filter or %NULL to not filter items
+ *
+ * Creates a new #GtkFilterListModel that will filter @model using the given
+ * @filter.
+ *
+ * Returns: a new #GtkFilterListModel
+ **/
+GtkFilterListModel *
+gtk_filter_list_model_new (GListModel *model,
+                           GtkFilter  *filter)
+{
+  GtkFilterListModel *result;
+
+  g_return_val_if_fail (G_IS_LIST_MODEL (model), NULL);
+
+  result = g_object_new (GTK_TYPE_FILTER_LIST_MODEL,
+                         "item-type", g_list_model_get_item_type (model),
+                         "model", model,
+                         "filter", filter,
+                         NULL);
+
+  return result;
+}
+
+/**
+ * gtk_filter_list_model_new_for_type:
+ * @item_type: the type of the items that will be returned
+ *
+ * Creates a new empty filter list model set up to return items of type @item_type.
+ * It is up to the application to set a proper filter and model to ensure
+ * the item type is matched.
+ *
+ * Returns: a new #GtkFilterListModel
+ **/
+GtkFilterListModel *
+gtk_filter_list_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_FILTER_LIST_MODEL,
+                       "item-type", item_type,
+                       NULL);
+}
+
+/**
+ * gtk_filter_list_model_set_filter:
+ * @self: a #GtkFilterListModel
+ * @filter: (allow-none) (transfer none): filter to use or %NULL to not filter items
+ *
+ * Sets the filter used to filter items.
+ **/
+void
+gtk_filter_list_model_set_filter (GtkFilterListModel *self,
+                                  GtkFilter          *filter)
+{
+  g_return_if_fail (GTK_IS_FILTER_LIST_MODEL (self));
+  g_return_if_fail (filter == NULL || GTK_IS_FILTER (filter));
+
+  if (self->filter == filter)
+    return;
+
+  gtk_filter_list_model_clear_filter (self);
+
+  if (filter)
+    {
+      self->filter = g_object_ref (filter);
+      g_signal_connect (filter, "changed", G_CALLBACK (gtk_filter_list_model_filter_changed_cb), self);
+      gtk_filter_list_model_filter_changed_cb (filter, GTK_FILTER_CHANGE_DIFFERENT, self);
+    }
+  else
+    {
+      gtk_filter_list_model_update_strictness_and_refilter (self);
+    }
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FILTER]);
+}
+
+/**
+ * gtk_filter_list_model_get_filter:
+ * @self: a #GtkFilterListModel
+ *
+ * Gets the #GtkFilter currently set on @self.
+ *
+ * Returns: (nullable) (transfer none): The filter currently in use
+ *     or %NULL if the list isn't filtered
+ **/
+GtkFilter *
+gtk_filter_list_model_get_filter (GtkFilterListModel *self)
+{
+  g_return_val_if_fail (GTK_IS_FILTER_LIST_MODEL (self), FALSE);
+
+  return self->filter;
+}
+
+/**
+ * gtk_filter_list_model_set_model:
+ * @self: a #GtkFilterListModel
+ * @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_filter_list_model_set_model (GtkFilterListModel *self,
+                                 GListModel         *model)
+{
+  guint removed, added;
+
+  g_return_if_fail (GTK_IS_FILTER_LIST_MODEL (self));
+  g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model));
+  /* Note: We don't check for matching item type here, we just assume the
+   * filter func takes care of filtering wrong items. */
+
+  if (self->model == model)
+    return;
+
+  removed = g_list_model_get_n_items (G_LIST_MODEL (self));
+  gtk_filter_list_model_clear_model (self);
+
+  if (model)
+    {
+      self->model = g_object_ref (model);
+      g_signal_connect (model, "items-changed", G_CALLBACK (gtk_filter_list_model_items_changed_cb), self);
+      if (removed == 0)
+        {
+          self->strictness = GTK_FILTER_MATCH_NONE;
+          gtk_filter_list_model_update_strictness_and_refilter (self);
+          added = 0;
+        }
+      else if (self->items)
+        added = gtk_filter_list_model_add_items (self, NULL, 0, g_list_model_get_n_items (model));
+      else
+        added = g_list_model_get_n_items (model);
+    }
+  else
+    {
+      self->strictness = GTK_FILTER_MATCH_NONE;
+      added = 0;
+    }
+
+  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_filter_list_model_get_model:
+ * @self: a #GtkFilterListModel
+ *
+ * Gets the model currently filtered or %NULL if none.
+ *
+ * Returns: (nullable) (transfer none): The model that gets filtered
+ **/
+GListModel *
+gtk_filter_list_model_get_model (GtkFilterListModel *self)
+{
+  g_return_val_if_fail (GTK_IS_FILTER_LIST_MODEL (self), NULL);
+
+  return self->model;
+}
+
+static void
+gtk_filter_list_model_refilter (GtkFilterListModel *self)
+{
+  FilterNode *node;
+  guint i, first_change, last_change;
+  guint n_is_visible, n_was_visible;
+  gboolean visible;
+
+  g_return_if_fail (GTK_IS_FILTER_LIST_MODEL (self));
+  
+  if (self->items == NULL || self->model == NULL)
+    return;
+
+  first_change = G_MAXUINT;
+  last_change = 0;
+  n_is_visible = 0;
+  n_was_visible = 0;
+  for (i = 0, node = gtk_rb_tree_get_first (self->items);
+       node != NULL;
+       i++, node = gtk_rb_tree_node_get_next (node))
+    {
+      visible = gtk_filter_list_model_run_filter (self, i);
+      if (visible == node->visible)
+        {
+          if (visible)
+            {
+              n_is_visible++;
+              n_was_visible++;
+            }
+          continue;
+        }
+
+      node->visible = visible;
+      gtk_rb_tree_node_mark_dirty (node);
+      first_change = MIN (n_is_visible, first_change);
+      if (visible)
+        n_is_visible++;
+      else
+        n_was_visible++;
+      last_change = MAX (n_is_visible, last_change);
+    }
+
+  if (first_change <= last_change)
+    {
+      g_list_model_items_changed (G_LIST_MODEL (self),
+                                  first_change,
+                                  last_change - first_change + n_was_visible - n_is_visible,
+                                  last_change - first_change);
+    }
+}
+
diff --git a/src/gtklistmodels/gtkfilterlistmodel.h b/src/gtklistmodels/gtkfilterlistmodel.h
new file mode 100644
index 00000000..bc4bd7ff
--- /dev/null
+++ b/src/gtklistmodels/gtkfilterlistmodel.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright © 2018 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#ifndef __GTK_FILTER_LIST_MODEL_H__
+#define __GTK_FILTER_LIST_MODEL_H__
+
+
+#define GTK_COMPILATION
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gtk/gtk.h> can be included directly."
+#endif
+
+#include <gio/gio.h>
+#include "gtkfilter.h"
+
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_FILTER_LIST_MODEL (gtk_filter_list_model_get_type ())
+
+GDK_AVAILABLE_IN_ALL
+G_DECLARE_FINAL_TYPE (GtkFilterListModel, gtk_filter_list_model, GTK, FILTER_LIST_MODEL, GObject)
+
+GDK_AVAILABLE_IN_ALL
+GtkFilterListModel *    gtk_filter_list_model_new               (GListModel             *model,
+                                                                 GtkFilter              *filter);
+GDK_AVAILABLE_IN_ALL
+GtkFilterListModel *    gtk_filter_list_model_new_for_type      (GType                   item_type);
+
+GDK_AVAILABLE_IN_ALL
+void                    gtk_filter_list_model_set_filter        (GtkFilterListModel     *self,
+                                                                 GtkFilter              *filter);
+GDK_AVAILABLE_IN_ALL
+GtkFilter *             gtk_filter_list_model_get_filter        (GtkFilterListModel     *self);
+GDK_AVAILABLE_IN_ALL
+void                    gtk_filter_list_model_set_model         (GtkFilterListModel     *self,
+                                                                 GListModel             *model);
+GDK_AVAILABLE_IN_ALL
+GListModel *            gtk_filter_list_model_get_model         (GtkFilterListModel     *self);
+
+G_END_DECLS
+
+#endif /* __GTK_FILTER_LIST_MODEL_H__ */
diff --git a/src/gtklistmodels/gtkflattenlistmodel.c b/src/gtklistmodels/gtkflattenlistmodel.c
new file mode 100644
index 00000000..bb6d614f
--- /dev/null
+++ b/src/gtklistmodels/gtkflattenlistmodel.c
@@ -0,0 +1,541 @@
+/*
+ * Copyright © 2018 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+/* #include "config.h" */
+
+#include "gtkflattenlistmodel.h"
+
+#include "gtkrbtreeprivate.h"
+#include "gtkintl.h"
+#include "gtkprivate.h"
+
+/**
+ * SECTION:gtkflattenlistmodel
+ * @title: GtkFlattenListModel
+ * @short_description: A list model that flattens a list of lists
+ * @see_also: #GListModel
+ *
+ * #GtkFlattenListModel is a list model that takes a list model containing
+ * list models and flattens it into a single model.
+ *
+ * Another term for this is concatenation: #GtkFlattenListModel takes a
+ * list of lists and concatenates them into a single list.
+ */
+
+enum {
+  PROP_0,
+  PROP_ITEM_TYPE,
+  PROP_MODEL,
+  NUM_PROPERTIES
+};
+
+typedef struct _FlattenNode FlattenNode;
+typedef struct _FlattenAugment FlattenAugment;
+
+struct _FlattenNode
+{
+  GListModel *model;
+  GtkFlattenListModel *list;
+};
+
+struct _FlattenAugment
+{
+  guint n_items;
+  guint n_models;
+};
+
+struct _GtkFlattenListModel
+{
+  GObject parent_instance;
+
+  GType item_type;
+  GListModel *model;
+  GtkRbTree *items; /* NULL if model == NULL */
+};
+
+struct _GtkFlattenListModelClass
+{
+  GObjectClass parent_class;
+};
+
+static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
+
+static FlattenNode *
+gtk_flatten_list_model_get_nth (GtkRbTree *tree,
+                                guint         position,
+                                guint        *model_position)
+{
+  FlattenNode *node, *tmp;
+  guint model_n_items;
+
+  node = gtk_rb_tree_get_root (tree);
+
+  while (node)
+    {
+      tmp = gtk_rb_tree_node_get_left (node);
+      if (tmp)
+        {
+          FlattenAugment *aug = gtk_rb_tree_get_augment (tree, tmp);
+          if (position < aug->n_items)
+            {
+              node = tmp;
+              continue;
+            }
+          position -= aug->n_items;
+        }
+
+      model_n_items = g_list_model_get_n_items (node->model);
+      if (position < model_n_items)
+        break;
+      position -= model_n_items;
+
+      node = gtk_rb_tree_node_get_right (node);
+    }
+
+  if (model_position)
+    *model_position = node ? position : 0;
+
+  return node;
+}
+
+static FlattenNode *
+gtk_flatten_list_model_get_nth_model (GtkRbTree *tree,
+                                      guint         position,
+                                      guint        *items_before)
+{
+  FlattenNode *node, *tmp;
+  guint before;
+
+  node = gtk_rb_tree_get_root (tree);
+  before = 0;
+
+  while (node)
+    {
+      tmp = gtk_rb_tree_node_get_left (node);
+      if (tmp)
+        {
+          FlattenAugment *aug = gtk_rb_tree_get_augment (tree, tmp);
+          if (position < aug->n_models)
+            {
+              node = tmp;
+              continue;
+            }
+          position -= aug->n_models;
+          before += aug->n_items;
+        }
+
+      if (position == 0)
+        break;
+      position--;
+      before += g_list_model_get_n_items (node->model);
+
+      node = gtk_rb_tree_node_get_right (node);
+    }
+
+  if (items_before)
+    *items_before = before;
+
+  return node;
+}
+
+static GType
+gtk_flatten_list_model_get_item_type (GListModel *list)
+{
+  GtkFlattenListModel *self = GTK_FLATTEN_LIST_MODEL (list);
+
+  return self->item_type;
+}
+
+static guint
+gtk_flatten_list_model_get_n_items (GListModel *list)
+{
+  GtkFlattenListModel *self = GTK_FLATTEN_LIST_MODEL (list);
+  FlattenAugment *aug;
+  FlattenNode *node;
+
+  if (!self->items)
+    return 0;
+
+  node = gtk_rb_tree_get_root (self->items);
+  if (node == NULL)
+    return 0;
+
+  aug = gtk_rb_tree_get_augment (self->items, node);
+  return aug->n_items;
+}
+
+static gpointer
+gtk_flatten_list_model_get_item (GListModel *list,
+                                 guint       position)
+{
+  GtkFlattenListModel *self = GTK_FLATTEN_LIST_MODEL (list);
+  FlattenNode *node;
+  guint model_pos;
+
+  if (!self->items)
+    return NULL;
+
+  node = gtk_flatten_list_model_get_nth (self->items, position, &model_pos);
+  if (node == NULL)
+    return NULL;
+
+  return g_list_model_get_item (node->model, model_pos);
+}
+
+static void
+gtk_flatten_list_model_model_init (GListModelInterface *iface)
+{
+  iface->get_item_type = gtk_flatten_list_model_get_item_type;
+  iface->get_n_items = gtk_flatten_list_model_get_n_items;
+  iface->get_item = gtk_flatten_list_model_get_item;
+}
+
+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))
+
+static void
+gtk_flatten_list_model_items_changed_cb (GListModel          *model,
+                                         guint                position,
+                                         guint                removed,
+                                         guint                added,
+                                         gpointer             _node)
+{
+  FlattenNode *node = _node, *parent, *left;
+  GtkFlattenListModel *self = node->list;
+  guint real_position;
+
+  gtk_rb_tree_node_mark_dirty (node);
+  real_position = position;
+
+  left = gtk_rb_tree_node_get_left (node);
+  if (left)
+    {
+      FlattenAugment *aug = gtk_rb_tree_get_augment (self->items, left);
+      real_position += aug->n_items;
+    }
+
+  for (;
+       (parent = gtk_rb_tree_node_get_parent (node)) != NULL;
+       node = parent)
+    {
+      left = gtk_rb_tree_node_get_left (parent);
+      if (left != node)
+        {
+          if (left)
+            {
+              FlattenAugment *aug = gtk_rb_tree_get_augment (self->items, left);
+              real_position += aug->n_items;
+            }
+          real_position += g_list_model_get_n_items (parent->model);
+        }
+    }
+
+  g_list_model_items_changed (G_LIST_MODEL (self), real_position, removed, added);
+}
+
+static void
+gtk_flatten_list_model_clear_node (gpointer _node)
+{
+  FlattenNode *node= _node;
+
+  g_signal_handlers_disconnect_by_func (node->model, gtk_flatten_list_model_items_changed_cb, node);
+  g_object_unref (node->model);
+}
+
+static void
+gtk_flatten_list_model_augment (GtkRbTree *flatten,
+                                gpointer   _aug,
+                                gpointer   _node,
+                                gpointer   left,
+                                gpointer   right)
+{
+  FlattenNode *node = _node;
+  FlattenAugment *aug = _aug;
+
+  aug->n_items = g_list_model_get_n_items (node->model);
+  aug->n_models = 1;
+
+  if (left)
+    {
+      FlattenAugment *left_aug = gtk_rb_tree_get_augment (flatten, left);
+      aug->n_items += left_aug->n_items;
+      aug->n_models += left_aug->n_models;
+    }
+  if (right)
+    {
+      FlattenAugment *right_aug = gtk_rb_tree_get_augment (flatten, right);
+      aug->n_items += right_aug->n_items;
+      aug->n_models += right_aug->n_models;
+    }
+}
+
+static guint
+gtk_flatten_list_model_add_items (GtkFlattenListModel *self,
+                                  FlattenNode         *after,
+                                  guint                position,
+                                  guint                n)
+{
+  FlattenNode *node;
+  guint added, i;
+
+  added = 0;
+  for (i = 0; i < n; i++)
+    {
+      node = gtk_rb_tree_insert_before (self->items, after);
+      node->model = g_list_model_get_item (self->model, position + i);
+      g_warn_if_fail (g_type_is_a (g_list_model_get_item_type (node->model), self->item_type));
+      g_signal_connect (node->model,
+                        "items-changed",
+                        G_CALLBACK (gtk_flatten_list_model_items_changed_cb),
+                        node);
+      node->list = self;
+      added +=g_list_model_get_n_items (node->model);
+    }
+
+  return added;
+}
+
+static void
+gtk_flatten_list_model_set_property (GObject      *object,
+                                     guint         prop_id,
+                                     const GValue *value,
+                                     GParamSpec   *pspec)
+{
+  GtkFlattenListModel *self = GTK_FLATTEN_LIST_MODEL (object);
+
+  switch (prop_id)
+    {
+    case PROP_ITEM_TYPE:
+      self->item_type = g_value_get_gtype (value);
+      break;
+
+    case PROP_MODEL:
+      gtk_flatten_list_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_flatten_list_model_get_property (GObject     *object,
+                                     guint        prop_id,
+                                     GValue      *value,
+                                     GParamSpec  *pspec)
+{
+  GtkFlattenListModel *self = GTK_FLATTEN_LIST_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_flatten_list_model_model_items_changed_cb (GListModel          *model,
+                                               guint                position,
+                                               guint                removed,
+                                               guint                added,
+                                               GtkFlattenListModel *self)
+{
+  FlattenNode *node;
+  guint i, real_position, real_removed, real_added;
+
+  node = gtk_flatten_list_model_get_nth_model (self->items, position, &real_position);
+
+  real_removed = 0;
+  for (i = 0; i < removed; i++)
+    {
+      FlattenNode *next = gtk_rb_tree_node_get_next (node);
+      real_removed += g_list_model_get_n_items (node->model);
+      gtk_rb_tree_remove (self->items, node);
+      node = next;
+    }
+
+  real_added = gtk_flatten_list_model_add_items (self, node, position, added);
+
+  if (real_removed > 0 || real_added > 0)
+    g_list_model_items_changed (G_LIST_MODEL (self), real_position, real_removed, real_added);
+}
+
+static void
+gtk_flatten_list_clear_model (GtkFlattenListModel *self)
+{
+  if (self->model)
+    {
+      g_signal_handlers_disconnect_by_func (self->model, gtk_flatten_list_model_model_items_changed_cb, 
self);
+      g_clear_object (&self->model);
+      g_clear_pointer (&self->items, gtk_rb_tree_unref);
+    }
+}
+
+static void
+gtk_flatten_list_model_dispose (GObject *object)
+{
+  GtkFlattenListModel *self = GTK_FLATTEN_LIST_MODEL (object);
+
+  gtk_flatten_list_clear_model (self);
+
+  G_OBJECT_CLASS (gtk_flatten_list_model_parent_class)->dispose (object);
+}
+
+static void
+gtk_flatten_list_model_class_init (GtkFlattenListModelClass *class)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (class);
+
+  gobject_class->set_property = gtk_flatten_list_model_set_property;
+  gobject_class->get_property = gtk_flatten_list_model_get_property;
+  gobject_class->dispose = gtk_flatten_list_model_dispose;
+
+  /**
+   * GtkFlattenListModel:item-type:
+   *
+   * The #GTpe 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);
+
+  /**
+   * GtkFlattenListModel:model:
+   *
+   * The model being flattened
+   */
+  properties[PROP_MODEL] =
+      g_param_spec_object ("model",
+                           P_("Model"),
+                           P_("The model being flattened"),
+                           G_TYPE_LIST_MODEL,
+                           GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  g_object_class_install_properties (gobject_class, NUM_PROPERTIES, properties);
+}
+
+static void
+gtk_flatten_list_model_init (GtkFlattenListModel *self)
+{
+}
+
+/**
+ * gtk_flatten_list_model_new:
+ * @item_type: The type of items in the to-be-flattened models
+ * @model: (nullable) (transfer none): the item to be flattened
+ *
+ * Creates a new #GtkFlattenListModel that flattens @list. The
+ * models returned by @model must conform to the given @item_type,
+ * either by having an identical type or a subtype.
+ *
+ * Returns: a new #GtkFlattenListModel
+ **/
+GtkFlattenListModel *
+gtk_flatten_list_model_new (GType       item_type,
+                            GListModel *model)
+{
+  GtkFlattenListModel *result;
+
+  g_return_val_if_fail (g_type_is_a (item_type, G_TYPE_OBJECT), NULL);
+  g_return_val_if_fail (model == NULL || G_IS_LIST_MODEL (model), NULL);
+
+  result = g_object_new (GTK_TYPE_FLATTEN_LIST_MODEL,
+                         "item-type", item_type,
+                         "model", model,
+                         NULL);
+
+  return result;
+}
+
+/**
+ * gtk_flatten_list_model_set_model:
+ * @self: a #GtkFlattenListModel
+ * @model: (nullable) (transfer none): the new model or %NULL
+ *
+ * Sets a new model to be flattened. The model must contain items of
+ * #GtkListModel that conform to the item type of @self.
+ **/
+void
+gtk_flatten_list_model_set_model (GtkFlattenListModel *self,
+                                  GListModel          *model)
+{
+  guint removed, added = 0;
+
+  g_return_if_fail (GTK_IS_FLATTEN_LIST_MODEL (self));
+  g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model));
+  if (model)
+    {
+      g_return_if_fail (g_type_is_a (g_list_model_get_item_type (model), G_TYPE_LIST_MODEL));
+    }
+
+  if (self->model == model)
+    return;
+
+  removed = g_list_model_get_n_items (G_LIST_MODEL (self)); 
+  gtk_flatten_list_clear_model (self);
+
+  self->model = model;
+
+  if (model)
+    {
+      g_object_ref (model);
+      g_signal_connect (model, "items-changed", G_CALLBACK (gtk_flatten_list_model_model_items_changed_cb), 
self);
+      self->items = gtk_rb_tree_new (FlattenNode,
+                                     FlattenAugment,
+                                     gtk_flatten_list_model_augment,
+                                     gtk_flatten_list_model_clear_node,
+                                     NULL);
+
+      added = gtk_flatten_list_model_add_items (self, NULL, 0, g_list_model_get_n_items (model));
+    }
+
+  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_flatten_list_model_get_model:
+ * @self: a #GtkFlattenListModel
+ *
+ * Gets the model set via gtk_flatten_list_model_set_model().
+ *
+ * Returns: (nullable) (transfer none): The model flattened by @self
+ **/
+GListModel *
+gtk_flatten_list_model_get_model (GtkFlattenListModel *self)
+{
+  g_return_val_if_fail (GTK_IS_FLATTEN_LIST_MODEL (self), NULL);
+
+  return self->model;
+}
diff --git a/src/gtklistmodels/gtkflattenlistmodel.h b/src/gtklistmodels/gtkflattenlistmodel.h
new file mode 100644
index 00000000..59f0d61d
--- /dev/null
+++ b/src/gtklistmodels/gtkflattenlistmodel.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright © 2018 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#ifndef __GTK_FLATTEN_LIST_MODEL_H__
+#define __GTK_FLATTEN_LIST_MODEL_H__
+
+
+#define GTK_COMPILATION
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gtk/gtk.h> can be included directly."
+#endif
+
+#include <gdk/gdk.h>
+
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_FLATTEN_LIST_MODEL (gtk_flatten_list_model_get_type ())
+
+GDK_AVAILABLE_IN_ALL
+G_DECLARE_FINAL_TYPE (GtkFlattenListModel, gtk_flatten_list_model, GTK, FLATTEN_LIST_MODEL, GObject)
+
+GDK_AVAILABLE_IN_ALL
+GtkFlattenListModel *    gtk_flatten_list_model_new             (GType                   item_type,
+                                                                 GListModel             *model);
+
+GDK_AVAILABLE_IN_ALL
+void                    gtk_flatten_list_model_set_model        (GtkFlattenListModel    *self,
+                                                                 GListModel             *model);
+GDK_AVAILABLE_IN_ALL
+GListModel *            gtk_flatten_list_model_get_model        (GtkFlattenListModel    *self);
+
+G_END_DECLS
+
+#endif /* __GTK_FLATTEN_LIST_MODEL_H__ */
diff --git a/src/gtklistmodels/gtkintl.h b/src/gtklistmodels/gtkintl.h
new file mode 100644
index 00000000..4931cb66
--- /dev/null
+++ b/src/gtklistmodels/gtkintl.h
@@ -0,0 +1,22 @@
+#ifndef __GTKINTL_H__
+#define __GTKINTL_H__
+
+#ifndef GETTEXT_PACKAGE
+# define GETTEXT_PACKAGE
+#endif
+#include <glib/gi18n-lib.h>
+
+#ifndef G_GNUC_FALLTHROUGH
+#define G_GNUC_FALLTHROUGH __attribute__((fallthrough))
+#endif
+
+#ifdef ENABLE_NLS
+#define P_(String) g_dgettext(GETTEXT_PACKAGE "-properties",String)
+#else 
+#define P_(String) (String)
+#endif
+
+/* not really I18N-related, but also a string marker macro */
+#define I_(string) g_intern_static_string (string)
+
+#endif
diff --git a/src/gtklistmodels/gtkprivate.h b/src/gtklistmodels/gtkprivate.h
new file mode 100644
index 00000000..8dc95138
--- /dev/null
+++ b/src/gtklistmodels/gtkprivate.h
@@ -0,0 +1,38 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
+ * file for a list of people on the GTK+ Team.  See the ChangeLog
+ * files for a list of changes.  These files are distributed with
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
+ */
+
+#ifndef __GTK_PRIVATE_H__
+#define __GTK_PRIVATE_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GTK_PARAM_READABLE G_PARAM_READABLE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB
+#define GTK_PARAM_WRITABLE G_PARAM_WRITABLE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB
+#define GTK_PARAM_READWRITE G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB
+
+G_END_DECLS
+
+#endif /* __GTK_PRIVATE_H__ */
diff --git a/src/gtklistmodels/gtkrbtree.c b/src/gtklistmodels/gtkrbtree.c
new file mode 100644
index 00000000..fd96de95
--- /dev/null
+++ b/src/gtklistmodels/gtkrbtree.c
@@ -0,0 +1,800 @@
+/* gtkrbtree.c
+ * Copyright (C) 2000  Red Hat, Inc.,  Jonathan Blandford <jrb redhat com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* #include "config.h" */
+
+#include "gtkrbtreeprivate.h"
+
+/* #include "gtkdebug.h" */
+
+/* Define the following to print adds and removals to stdout.
+ * The format of the printout will be suitable for addition as a new test to
+ * testsuite/gtk/rbtree-crash.c
+ * by just grepping the printouts from the relevant rbtree.
+ *
+ * This is meant to be a trivial way to add rbtree tests to the testsuite.
+ */
+#undef DUMP_MODIFICATION
+
+typedef struct _GtkRbNode GtkRbNode;
+
+struct _GtkRbTree
+{
+  guint ref_count;
+
+  gsize element_size;
+  gsize augment_size;
+  GtkRbTreeAugmentFunc augment_func;
+  GDestroyNotify clear_func;
+  GDestroyNotify clear_augment_func;
+
+  GtkRbNode *root;
+};
+
+struct _GtkRbNode
+{
+  guint red :1;
+  guint dirty :1;
+
+  GtkRbNode *left;
+  GtkRbNode *right;
+  /* The difference between tree and parent here is that we OR the tree with 1 and because
+   * pointers are always multiples of 4, we can know if we've stored a parent or the tree here */
+  union {
+    gpointer parent_or_tree;
+    GtkRbNode *parent;
+    GtkRbTree *tree;
+  };
+};
+
+#define NODE_FROM_POINTER(ptr) ((GtkRbNode *) ((ptr) ? (((guchar *) (ptr)) - sizeof (GtkRbNode)) : NULL))
+#define NODE_TO_POINTER(node) ((gpointer) ((node) ? (((guchar *) (node)) + sizeof (GtkRbNode)) : NULL))
+#define NODE_TO_AUG_POINTER(tree, node) ((gpointer) ((node) ? (((guchar *) (node)) + sizeof (GtkRbNode) + 
(tree)->element_size) : NULL))
+
+static inline gboolean
+is_root (GtkRbNode *node)
+{
+  return GPOINTER_TO_SIZE (node->parent_or_tree) & 1 ? TRUE : FALSE;
+}
+
+static inline GtkRbNode *
+parent (GtkRbNode *node)
+{
+  if (is_root (node))
+    return NULL;
+  else
+    return node->parent;
+}
+
+static GtkRbTree *
+tree (GtkRbNode *node)
+{
+  while (!is_root (node))
+    node = parent (node);
+
+  return GSIZE_TO_POINTER (GPOINTER_TO_SIZE (node->tree) & ~1);
+}
+
+static void
+set_parent (GtkRbTree *tree,
+            GtkRbNode *node,
+            GtkRbNode *new_parent)
+{
+
+  if (new_parent != NULL)
+    {
+      node->parent = new_parent;
+    }
+  else
+    {
+      node->tree = GSIZE_TO_POINTER (GPOINTER_TO_SIZE (tree) | 1);
+      tree->root = node;
+    }
+}
+
+static inline gsize
+gtk_rb_node_get_size (GtkRbTree *tree)
+{
+  return sizeof (GtkRbNode) + tree->element_size + tree->augment_size;
+}
+
+static GtkRbNode *
+gtk_rb_node_new (GtkRbTree *tree)
+{
+  GtkRbNode *result;
+
+  result = g_slice_alloc0 (gtk_rb_node_get_size (tree));
+
+  result->red = TRUE;
+  result->dirty = TRUE;
+
+  return result;
+}
+
+static void
+gtk_rb_node_free (GtkRbTree *tree,
+                  GtkRbNode *node)
+{
+  if (tree->clear_func)
+    tree->clear_func (NODE_TO_POINTER (node));
+  if (tree->clear_augment_func)
+    tree->clear_augment_func (NODE_TO_AUG_POINTER (tree, node));
+
+  g_slice_free1 (gtk_rb_node_get_size (tree), node);
+}
+
+static void
+gtk_rb_node_free_deep (GtkRbTree *tree,
+                       GtkRbNode *node)
+{
+  GtkRbNode *right = node->right;
+
+  if (node->left)
+    gtk_rb_node_free_deep (tree, node->left);
+
+  gtk_rb_node_free (tree, node);
+
+  if (right)
+    gtk_rb_node_free_deep (tree, right);
+}
+
+static void
+gtk_rb_node_mark_dirty (GtkRbNode *node,
+                        gboolean   mark_parent)
+{
+  if (node->dirty)
+    return;
+  
+  node->dirty = TRUE;
+
+  if (mark_parent && parent (node))
+    gtk_rb_node_mark_dirty (parent (node), TRUE);
+}
+
+static void
+gtk_rb_node_clean (GtkRbTree *tree,
+                   GtkRbNode *node)
+{
+  if (!node->dirty)
+    return;
+
+  node->dirty = FALSE;
+  if (tree->augment_func)
+    tree->augment_func (tree,
+                        NODE_TO_AUG_POINTER (tree, node),
+                        NODE_TO_POINTER (node),
+                        NODE_TO_POINTER (node->left),
+                        NODE_TO_POINTER (node->right));
+}
+
+static GtkRbNode *
+gtk_rb_node_get_first (GtkRbNode *node)
+{
+  while (node->left)
+    node = node->left;
+
+  return node;
+}
+
+static GtkRbNode *
+gtk_rb_node_get_last (GtkRbNode *node)
+{
+  while (node->right)
+    node = node->right;
+
+  return node;
+}
+
+static GtkRbNode *
+gtk_rb_node_get_previous (GtkRbNode *node)
+{
+  GtkRbNode *p;
+
+  if (node->left)
+    return gtk_rb_node_get_last (node->left);
+
+  for (p = parent (node); p != NULL; p = parent (node))
+    {
+      if (p->right == node)
+        return p;
+
+      node = p;
+    }
+
+  return NULL;
+}
+
+static GtkRbNode *
+gtk_rb_node_get_next (GtkRbNode *node)
+{
+  GtkRbNode *p;
+
+  if (node->right)
+    return gtk_rb_node_get_first (node->right);
+
+  for (p = parent (node); p != NULL; p = parent (node))
+    {
+      if (p->left == node)
+        return p;
+
+      node = p;
+    }
+
+  return NULL;
+}
+
+#ifdef DUMP_MODIFICATION
+static guint
+position (GtkRbTree *tree,
+          GtkRbNode *node)
+{
+  GtkRbNode *n;
+  guint i;
+
+  i = 0;
+  for (n = gtk_rb_node_get_first (tree->root);
+       n != node;
+       n = gtk_rb_node_get_next (n))
+    i++;
+
+  return i;
+}
+#endif
+
+static void
+gtk_rb_node_rotate_left (GtkRbTree *tree,
+                         GtkRbNode *node)
+{
+  GtkRbNode *right, *p;
+
+  right = node->right;
+  p = parent (node);
+
+  node->right = right->left;
+  if (right->left)
+    set_parent (tree, right->left, node);
+
+  set_parent (tree, right, p);
+  if (p)
+    {
+      if (node == p->left)
+       p->left = right;
+      else
+       p->right = right;
+    }
+
+  right->left = node;
+  set_parent (tree, node, right);
+
+  gtk_rb_node_mark_dirty (node, FALSE);
+  gtk_rb_node_mark_dirty (right, FALSE);
+}
+
+static void
+gtk_rb_node_rotate_right (GtkRbTree *tree,
+                          GtkRbNode *node)
+{
+  GtkRbNode *left, *p;
+
+  left = node->left;
+  p = parent (node);
+
+  node->left = left->right;
+  if (left->right)
+    set_parent (tree, left->right, node);
+
+  set_parent (tree, left, p);
+  if (p)
+    {
+      if (node == p->right)
+       p->right = left;
+      else
+       p->left = left;
+    }
+
+  /* link node and left */
+  left->right = node;
+  set_parent (tree, node, left);
+
+  gtk_rb_node_mark_dirty (node, FALSE);
+  gtk_rb_node_mark_dirty (left, FALSE);
+}
+
+static gboolean
+is_red (GtkRbNode *node_or_null)
+{
+  if (node_or_null == NULL)
+    return FALSE;
+  else
+    return node_or_null->red;
+}
+
+static inline gboolean
+is_black (GtkRbNode *node_or_null)
+{
+  return !is_red (node_or_null);
+}
+
+static void
+set_black (GtkRbNode *node_or_null)
+{
+  if (node_or_null == NULL)
+    return;
+
+  node_or_null->red = FALSE;
+}
+
+static void
+set_red (GtkRbNode *node_or_null)
+{
+  if (node_or_null == NULL)
+    return;
+
+  node_or_null->red = TRUE;
+}
+
+static void
+gtk_rb_tree_insert_fixup (GtkRbTree *tree,
+                          GtkRbNode *node)
+{
+  GtkRbNode *p;
+
+  /* check Red-Black properties */
+  for (p = parent (node);
+       p && is_red (p);
+       p = parent (node))
+    {
+      GtkRbNode *pp = parent (p);
+
+      /* we have a violation */
+      g_assert (pp);
+
+      if (p == pp->left)
+       {
+         GtkRbNode *uncle = pp->right;
+
+         if (is_red (uncle))
+           {
+             /* uncle is red */
+             set_black (p);
+              set_black (uncle);
+              set_red (pp);
+             node = pp;
+           }
+         else
+           {
+             /* uncle is black */
+             if (node == p->right)
+               {
+                 /* make node a left child */
+                 node = p;
+                 gtk_rb_node_rotate_left (tree, node);
+                  p = parent (node);
+                  pp = parent (p);
+               }
+             /* recolor and rotate */
+              set_black (p);
+              set_red (pp);
+             gtk_rb_node_rotate_right (tree, pp);
+           }
+       }
+      else
+       {
+         /* mirror image of above code */
+         GtkRbNode *uncle = pp->left;
+
+         if (is_red (uncle))
+           {
+             /* uncle is red */
+              set_black (p);
+              set_black (uncle);
+              set_red (pp);
+             node = pp;
+           }
+         else
+           {
+              /* uncle is black */
+             if (node == p->left)
+               {
+                 node = p;
+                 gtk_rb_node_rotate_right (tree, node);
+                  p = parent (node);
+                  pp = parent (p);
+               }
+             set_black (p);
+             set_red (pp);
+             gtk_rb_node_rotate_left (tree, pp);
+           }
+       }
+    }
+
+  set_black (tree->root);
+}
+
+static void
+gtk_rb_tree_remove_node_fixup (GtkRbTree *tree,
+                               GtkRbNode *node,
+                               GtkRbNode *p)
+{
+  while (node != tree->root && is_black (node))
+    {
+      if (node == p->left)
+       {
+         GtkRbNode *w = p->right;
+
+         if (is_red (w))
+           {
+             set_black (w);
+              set_red (p);
+             gtk_rb_node_rotate_left (tree, p);
+             w = p->right;
+           }
+         if (is_black (w->left) && is_black (w->right))
+           {
+             set_red (w);
+             node = p;
+           }
+         else
+           {
+             if (is_black (w->right))
+               {
+                 set_black (w->left);
+                 set_red (w);
+                 gtk_rb_node_rotate_right (tree, w);
+                 w = p->right;
+               }
+             w->red = p->red;
+             set_black (p);
+              set_black (w->right);
+             gtk_rb_node_rotate_left (tree, p);
+             node = tree->root;
+           }
+       }
+      else
+       {
+         GtkRbNode *w = p->left;
+         if (is_red (w))
+           {
+             set_black (w);
+             set_red (p);
+             gtk_rb_node_rotate_right (tree, p);
+             w = p->left;
+           }
+         if (is_black (w->right) && is_black (w->left))
+           {
+             set_red (w);
+             node = p;
+           }
+         else
+           {
+             if (is_black (w->left))
+               {
+                 set_black (w->right);
+                 set_red (w);
+                 gtk_rb_node_rotate_left (tree, w);
+                 w = p->left;
+               }
+             w->red = p->red;
+             set_black (p);
+             set_black (w->left);
+             gtk_rb_node_rotate_right (tree, p);
+             node = tree->root;
+           }
+       }
+
+      p = parent (node);
+    }
+
+  set_black (node);
+}
+
+GtkRbTree *
+gtk_rb_tree_new_for_size (gsize                element_size,
+                          gsize                augment_size,
+                          GtkRbTreeAugmentFunc augment_func,
+                          GDestroyNotify       clear_func,
+                          GDestroyNotify       clear_augment_func)
+{
+  GtkRbTree *tree;
+
+  tree = g_slice_new0 (GtkRbTree);
+  tree->ref_count = 1;
+
+  tree->element_size = element_size;
+  tree->augment_size = augment_size;
+  tree->augment_func = augment_func;
+  tree->clear_func = clear_func;
+  tree->clear_augment_func = clear_augment_func;
+
+  return tree;
+}
+
+GtkRbTree *
+gtk_rb_tree_ref (GtkRbTree *tree)
+{
+  tree->ref_count++;
+
+  return tree;
+}
+
+void
+gtk_rb_tree_unref (GtkRbTree *tree)
+{
+  tree->ref_count--;
+  if (tree->ref_count > 0)
+    return;
+
+  if (tree->root)
+    gtk_rb_node_free_deep (tree, tree->root);
+    
+  g_slice_free (GtkRbTree, tree);
+}
+
+gpointer
+gtk_rb_tree_get_first (GtkRbTree *tree)
+{
+  if (tree->root == NULL)
+    return NULL;
+
+  return NODE_TO_POINTER (gtk_rb_node_get_first (tree->root));
+}
+
+gpointer
+gtk_rb_tree_get_last (GtkRbTree *tree)
+{
+  if (tree->root == NULL)
+    return NULL;
+
+  return NODE_TO_POINTER (gtk_rb_node_get_last (tree->root));
+}
+
+gpointer
+gtk_rb_tree_node_get_previous (gpointer node)
+{
+  return NODE_TO_POINTER (gtk_rb_node_get_previous (NODE_FROM_POINTER (node)));
+}
+
+gpointer
+gtk_rb_tree_node_get_next (gpointer node)
+{
+  return NODE_TO_POINTER (gtk_rb_node_get_next (NODE_FROM_POINTER (node)));
+}
+
+gpointer
+gtk_rb_tree_get_root (GtkRbTree *tree)
+{
+  return NODE_TO_POINTER (tree->root);
+}
+
+gpointer
+gtk_rb_tree_node_get_parent (gpointer node)
+{
+  return NODE_TO_POINTER (parent (NODE_FROM_POINTER (node)));
+}
+
+gpointer
+gtk_rb_tree_node_get_left (gpointer node)
+{
+  return NODE_TO_POINTER (NODE_FROM_POINTER (node)->left);
+}
+
+gpointer
+gtk_rb_tree_node_get_right (gpointer node)
+{
+  return NODE_TO_POINTER (NODE_FROM_POINTER (node)->right);
+}
+
+gpointer
+gtk_rb_tree_get_augment (GtkRbTree *tree,
+                         gpointer   node)
+{
+  GtkRbNode *rbnode = NODE_FROM_POINTER (node);
+
+  gtk_rb_node_clean (tree, rbnode);
+
+  return NODE_TO_AUG_POINTER (tree, rbnode);
+}
+
+GtkRbTree *
+gtk_rb_tree_node_get_tree (gpointer node)
+{
+  return tree (NODE_FROM_POINTER (node));
+}
+
+void
+gtk_rb_tree_node_mark_dirty (gpointer node)
+{
+  gtk_rb_node_mark_dirty (NODE_FROM_POINTER (node), TRUE);
+}
+
+gpointer
+gtk_rb_tree_insert_before (GtkRbTree *tree,
+                           gpointer   node)
+{
+  GtkRbNode *result;
+
+
+  if (tree->root == NULL)
+    {
+#ifdef DUMP_MODIFICATION
+      g_print ("add (tree, 0); /* 0x%p */\n", tree);
+#endif /* DUMP_MODIFICATION */
+
+      g_assert (node == NULL);
+
+      result = gtk_rb_node_new (tree);
+      tree->root = result;
+    }
+  else if (node == NULL)
+    {
+      return gtk_rb_tree_insert_after (tree, gtk_rb_tree_get_last (tree));
+    }
+  else
+    {
+      GtkRbNode *current = NODE_FROM_POINTER (node);
+
+#ifdef DUMP_MODIFICATION
+      g_print ("add (tree, %u); /* 0x%p */\n", position (tree, current), tree);
+#endif /* DUMP_MODIFICATION */
+
+      /* setup new node */
+      result = gtk_rb_node_new (tree);
+
+      if (current->left)
+        {
+          current = gtk_rb_node_get_last (current->left);
+          current->right = result;
+        }
+      else
+        {
+          current->left = result;
+        }
+      set_parent (tree, result, current);
+      gtk_rb_node_mark_dirty (current, TRUE);
+    }
+
+  gtk_rb_tree_insert_fixup (tree, result);
+
+  return NODE_TO_POINTER (result);
+}
+
+gpointer
+gtk_rb_tree_insert_after (GtkRbTree *tree,
+                          gpointer   node)
+{
+  GtkRbNode *current, *result;
+
+  if (node == NULL)
+    return gtk_rb_tree_insert_before (tree, gtk_rb_tree_get_first (tree));
+
+  current = NODE_FROM_POINTER (node);
+
+#ifdef DUMP_MODIFICATION
+  g_print ("add (tree, %u); /* 0x%p */\n", position (tree, current) + 1, tree);
+#endif /* DUMP_MODIFICATION */
+
+  /* setup new node */
+  result = gtk_rb_node_new (tree);
+
+  if (current->right)
+    {
+      current = gtk_rb_node_get_first (current->right);
+      current->left = result;
+    }
+  else
+    {
+      current->right = result;
+    }
+  set_parent (tree, result, current);
+  gtk_rb_node_mark_dirty (current, TRUE);
+
+  gtk_rb_tree_insert_fixup (tree, result);
+
+  return NODE_TO_POINTER (result);
+}
+
+void
+gtk_rb_tree_remove (GtkRbTree *tree,
+                    gpointer   node)
+{
+  GtkRbNode *x, *y, *p, *real_node;
+  
+  real_node = NODE_FROM_POINTER (node);
+
+#ifdef DUMP_MODIFICATION
+  g_print ("delete (tree, %u); /* 0x%p */\n", position (tree, real_node), tree);
+#endif /* DUMP_MODIFICATION */
+
+  y = real_node;
+  if (y->left && y->right)
+    {
+      y = y->right;
+
+      while (y->left)
+       y = y->left;
+    }
+
+  /* x is y's only child, or nil */
+  if (y->left)
+    x = y->left;
+  else
+    x = y->right;
+
+  /* remove y from the parent chain */
+  p = parent (y);
+  if (x != NULL)
+    set_parent (tree, x, p);
+  if (p)
+    {
+      if (y == p->left)
+       p->left = x;
+      else
+       p->right = x;
+      gtk_rb_node_mark_dirty (p, TRUE);
+    }
+  else
+    {
+      if (x == NULL)
+        tree->root = NULL;
+    }
+
+  /* We need to clean up the validity of the tree.
+   */
+  if (is_black (y))
+    gtk_rb_tree_remove_node_fixup (tree, x, p);
+
+  if (y != real_node)
+    {
+      /* Move the node over */
+      if (is_red (real_node) != is_red (y))
+       y->red = !y->red;
+
+      y->left = real_node->left;
+      if (y->left)
+        set_parent (tree, y->left, y);
+      y->right = real_node->right;
+      if (y->right)
+        set_parent (tree, y->right, y);
+      p = parent (real_node);
+      set_parent (tree, y, p);
+      if (p)
+        {
+          if (p->left == real_node)
+            p->left = y;
+          else
+            p->right = y;
+          gtk_rb_node_mark_dirty (p, TRUE);
+        }
+      gtk_rb_node_mark_dirty (y, TRUE);
+    }
+
+  gtk_rb_node_free (tree, real_node);
+}
+
+void
+gtk_rb_tree_remove_all (GtkRbTree *tree)
+{
+#ifdef DUMP_MODIFICATION
+      g_print ("delete_all (tree); /* 0x%p */\n", tree);
+#endif /* DUMP_MODIFICATION */
+
+  if (tree->root)
+    gtk_rb_node_free_deep (tree, tree->root);
+
+  tree->root = NULL;
+}
+
diff --git a/src/gtklistmodels/gtkrbtreeprivate.h b/src/gtklistmodels/gtkrbtreeprivate.h
new file mode 100644
index 00000000..45aba5cc
--- /dev/null
+++ b/src/gtklistmodels/gtkrbtreeprivate.h
@@ -0,0 +1,75 @@
+/* gtkrbtree.h
+ * Copyright (C) 2000  Red Hat, Inc.,  Jonathan Blandford <jrb redhat com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* A Red-Black Tree implementation used specifically by GtkTreeView.
+ */
+#ifndef __GTK_RB_TREE_H__
+#define __GTK_RB_TREE_H__
+
+#include <glib.h>
+
+
+G_BEGIN_DECLS
+
+
+typedef struct _GtkRbTree GtkRbTree;
+
+typedef void            (* GtkRbTreeAugmentFunc)        (GtkRbTree               *tree,
+                                                         gpointer                 node_augment,
+                                                         gpointer                 node,
+                                                         gpointer                 left,
+                                                         gpointer                 right);
+
+GtkRbTree *          gtk_rb_tree_new_for_size           (gsize                    element_size,
+                                                         gsize                    augment_size,
+                                                         GtkRbTreeAugmentFunc     augment_func,
+                                                         GDestroyNotify           clear_func,
+                                                         GDestroyNotify           clear_augment_func);
+#define gtk_rb_tree_new(type, augment_type, augment_func, clear_func, clear_augment_func) \
+  gtk_rb_tree_new_for_size (sizeof (type), sizeof (augment_type), (augment_func), (clear_func), 
(clear_augment_func))
+
+GtkRbTree *          gtk_rb_tree_ref                    (GtkRbTree               *tree);
+void                 gtk_rb_tree_unref                  (GtkRbTree               *tree);
+
+gpointer             gtk_rb_tree_get_root               (GtkRbTree               *tree);
+gpointer             gtk_rb_tree_get_first              (GtkRbTree               *tree);
+gpointer             gtk_rb_tree_get_last               (GtkRbTree               *tree);
+
+gpointer             gtk_rb_tree_node_get_previous      (gpointer                 node);
+gpointer             gtk_rb_tree_node_get_next          (gpointer                 node);
+gpointer             gtk_rb_tree_node_get_parent        (gpointer                 node);
+gpointer             gtk_rb_tree_node_get_left          (gpointer                 node);
+gpointer             gtk_rb_tree_node_get_right         (gpointer                 node);
+GtkRbTree *          gtk_rb_tree_node_get_tree          (gpointer                 node);
+void                 gtk_rb_tree_node_mark_dirty        (gpointer                 node);
+
+gpointer             gtk_rb_tree_get_augment            (GtkRbTree               *tree,
+                                                         gpointer                 node);
+
+gpointer             gtk_rb_tree_insert_before          (GtkRbTree               *tree,
+                                                         gpointer                 node);
+gpointer             gtk_rb_tree_insert_after           (GtkRbTree               *tree,
+                                                         gpointer                 node);
+void                 gtk_rb_tree_remove                 (GtkRbTree               *tree,
+                                                         gpointer                 node);
+void                 gtk_rb_tree_remove_all             (GtkRbTree               *tree);
+
+
+G_END_DECLS
+
+
+#endif /* __GTK_RB_TREE_H__ */
diff --git a/src/gtklistmodels/gtkslicelistmodel.c b/src/gtklistmodels/gtkslicelistmodel.c
new file mode 100644
index 00000000..fca2a68d
--- /dev/null
+++ b/src/gtklistmodels/gtkslicelistmodel.c
@@ -0,0 +1,529 @@
+/*
+ * Copyright © 2018 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+/* #include "config.h" */
+
+#include "gtkslicelistmodel.h"
+
+#include "gtkintl.h"
+#include "gtkprivate.h"
+
+/**
+ * SECTION:gtkslicelistmodel
+ * @title: GtkSliceListModel
+ * @short_description: A list model that presents a slice out of a larger list
+ * @see_also: #GListModel
+ *
+ * #GtkSliceListModel is a list model that takes a list model and presents a slice of
+ * that model.
+ *
+ * This is useful when implementing paging by setting the size to the number of elements
+ * per page and updating the offset whenever a different page is opened.
+ */
+
+#define DEFAULT_SIZE 10
+
+enum {
+  PROP_0,
+  PROP_ITEM_TYPE,
+  PROP_MODEL,
+  PROP_OFFSET,
+  PROP_SIZE,
+  NUM_PROPERTIES
+};
+
+struct _GtkSliceListModel
+{
+  GObject parent_instance;
+
+  GType item_type;
+  GListModel *model;
+  guint offset;
+  guint size;
+
+  guint n_items;
+};
+
+struct _GtkSliceListModelClass
+{
+  GObjectClass parent_class;
+};
+
+static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
+
+static GType
+gtk_slice_list_model_get_item_type (GListModel *list)
+{
+  GtkSliceListModel *self = GTK_SLICE_LIST_MODEL (list);
+
+  return self->item_type;
+}
+
+static guint
+gtk_slice_list_model_get_n_items (GListModel *list)
+{
+  GtkSliceListModel *self = GTK_SLICE_LIST_MODEL (list);
+  guint n_items;
+  
+  if (self->model == NULL)
+    return 0;
+
+  /* XXX: This can be done without calling g_list_model_get_n_items() on the parent model
+   * by checking if model.get_item(offset + size) != NULL */
+  n_items = g_list_model_get_n_items (self->model);
+  if (n_items <= self->offset)
+    return 0;
+
+  n_items -= self->offset;
+  return MIN (n_items, self->size);
+}
+
+static gpointer
+gtk_slice_list_model_get_item (GListModel *list,
+                               guint       position)
+{
+  GtkSliceListModel *self = GTK_SLICE_LIST_MODEL (list);
+
+  if (self->model == NULL)
+    return NULL;
+
+  if (position >= self->size)
+    return NULL;
+
+  return g_list_model_get_item (self->model, position + self->offset);
+}
+
+static void
+gtk_slice_list_model_model_init (GListModelInterface *iface)
+{
+  iface->get_item_type = gtk_slice_list_model_get_item_type;
+  iface->get_n_items = gtk_slice_list_model_get_n_items;
+  iface->get_item = gtk_slice_list_model_get_item;
+}
+
+G_DEFINE_TYPE_WITH_CODE (GtkSliceListModel, gtk_slice_list_model, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_slice_list_model_model_init))
+
+static void
+gtk_slice_list_model_items_changed_cb (GListModel        *model,
+                                       guint              position,
+                                       guint              removed,
+                                       guint              added,
+                                       GtkSliceListModel *self)
+{
+  if (position >= self->offset + self->size)
+    return;
+
+  if (position < self->offset)
+    {
+      guint skip = MIN (removed, added);
+      skip = MIN (skip, self->offset - position);
+
+      position += skip;
+      removed -= skip;
+      added -= skip;
+    }
+
+  if (removed == added)
+    {
+      guint changed = removed;
+
+      if (changed == 0)
+        return;
+
+      g_assert (position >= self->offset);
+      position -= self->offset;
+      changed = MIN (changed, self->size - position);
+
+      g_list_model_items_changed (G_LIST_MODEL (self), position, changed, changed);
+    }
+  else
+    {
+      guint n_after, n_before;
+      guint skip;
+
+      if (position > self->offset)
+        skip = position - self->offset;
+      else
+        skip = 0;
+
+      n_after = g_list_model_get_n_items (self->model);
+      n_before = n_after - added + removed;
+      n_after = CLAMP (n_after, self->offset, self->offset + self->size) - self->offset;
+      n_before = CLAMP (n_before, self->offset, self->offset + self->size) - self->offset;
+
+      g_list_model_items_changed (G_LIST_MODEL (self), skip, n_before - skip, n_after - skip);
+    }
+}
+
+static void
+gtk_slice_list_model_set_property (GObject      *object,
+                                   guint         prop_id,
+                                   const GValue *value,
+                                   GParamSpec   *pspec)
+{
+  GtkSliceListModel *self = GTK_SLICE_LIST_MODEL (object);
+
+  switch (prop_id)
+    {
+    case PROP_ITEM_TYPE:
+      self->item_type = g_value_get_gtype (value);
+      break;
+
+    case PROP_MODEL:
+      gtk_slice_list_model_set_model (self, g_value_get_object (value));
+      break;
+
+    case PROP_OFFSET:
+      gtk_slice_list_model_set_offset (self, g_value_get_uint (value));
+      break;
+
+    case PROP_SIZE:
+      gtk_slice_list_model_set_size (self, g_value_get_uint (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void 
+gtk_slice_list_model_get_property (GObject     *object,
+                                   guint        prop_id,
+                                   GValue      *value,
+                                   GParamSpec  *pspec)
+{
+  GtkSliceListModel *self = GTK_SLICE_LIST_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;
+
+    case PROP_OFFSET:
+      g_value_set_uint (value, self->offset);
+      break;
+
+    case PROP_SIZE:
+      g_value_set_uint (value, self->size);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+gtk_slice_list_model_clear_model (GtkSliceListModel *self)
+{
+  if (self->model == NULL)
+    return;
+
+  g_signal_handlers_disconnect_by_func (self->model, gtk_slice_list_model_items_changed_cb, self);
+  g_clear_object (&self->model);
+}
+
+static void
+gtk_slice_list_model_dispose (GObject *object)
+{
+  GtkSliceListModel *self = GTK_SLICE_LIST_MODEL (object);
+
+  gtk_slice_list_model_clear_model (self);
+
+  G_OBJECT_CLASS (gtk_slice_list_model_parent_class)->dispose (object);
+};
+
+static void
+gtk_slice_list_model_class_init (GtkSliceListModelClass *class)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (class);
+
+  gobject_class->set_property = gtk_slice_list_model_set_property;
+  gobject_class->get_property = gtk_slice_list_model_get_property;
+  gobject_class->dispose = gtk_slice_list_model_dispose;
+
+  /**
+   * GtkSliceListModel: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);
+
+  /**
+   * GtkSliceListModel:model:
+   *
+   * Child model to take slice from
+   */
+  properties[PROP_MODEL] =
+      g_param_spec_object ("model",
+                           P_("Model"),
+                           P_("Child model to take slice from"),
+                           G_TYPE_LIST_MODEL,
+                           GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  /**
+   * GtkSliceListModel:offset:
+   *
+   * Offset of slice
+   */
+  properties[PROP_OFFSET] =
+      g_param_spec_uint ("offset",
+                         P_("Offset"),
+                         P_("Offset of slice"),
+                         0, G_MAXUINT, 0,
+                         GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  /**
+   * GtkSliceListModel:size:
+   *
+   * Maximum size of slice
+   */
+  properties[PROP_SIZE] =
+      g_param_spec_uint ("size",
+                         P_("Size"),
+                         P_("Maximum size of slice"),
+                         0, G_MAXUINT, DEFAULT_SIZE,
+                         GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  g_object_class_install_properties (gobject_class, NUM_PROPERTIES, properties);
+}
+
+static void
+gtk_slice_list_model_init (GtkSliceListModel *self)
+{
+  self->size = DEFAULT_SIZE;
+}
+
+/**
+ * gtk_slice_list_model_new:
+ * @model: (transfer none): The model to use
+ * @offset: the offset of the slice
+ * @size: maximum size of the slice
+ *
+ * Creates a new slice model that presents the slice from @offset to
+ * @offset + @size our of the given @model.
+ *
+ * Returns: A new #GtkSliceListModel
+ **/
+GtkSliceListModel *
+gtk_slice_list_model_new (GListModel *model,
+                          guint       offset,
+                          guint       size)
+{
+  g_return_val_if_fail (G_IS_LIST_MODEL (model), NULL);
+
+  return g_object_new (GTK_TYPE_SLICE_LIST_MODEL,
+                       "item-type", g_list_model_get_item_type (model),
+                       "model", model,
+                       "offset", offset,
+                       "size", size,
+                       NULL);
+}
+
+/**
+ * gtk_slice_list_model_new_for_type:
+ * @item_type: the type of items
+ *
+ * Creates a new empty #GtkSliceListModel for the given @item_type that
+ * can be set up later.
+ *
+ * Returns: a new empty #GtkSliceListModel
+ **/
+GtkSliceListModel *
+gtk_slice_list_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_SLICE_LIST_MODEL,
+                       "item-type", item_type,
+                       NULL);
+}
+
+/**
+ * gtk_slice_list_model_set_model:
+ * @self: a #GtkSliceListModel
+ * @model: (allow-none): The model to be sliced
+ *
+ * Sets the model to show a slice of. The model's item type must conform
+ * to @self's item type.
+ *
+ **/
+void
+gtk_slice_list_model_set_model (GtkSliceListModel *self,
+                                GListModel      *model)
+{
+  guint removed, added;
+
+  g_return_if_fail (GTK_IS_SLICE_LIST_MODEL (self));
+  g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model));
+
+  if (self->model == model)
+    return;
+
+  removed = g_list_model_get_n_items (G_LIST_MODEL (self));
+  gtk_slice_list_model_clear_model (self);
+
+  if (model)
+    {
+      self->model = g_object_ref (model);
+      g_signal_connect (model, "items-changed", G_CALLBACK (gtk_slice_list_model_items_changed_cb), self);
+      added = g_list_model_get_n_items (G_LIST_MODEL (self));
+    }
+  else
+    {
+      added = 0;
+    }
+
+  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_slice_list_model_get_model:
+ * @self: a #GtkSliceListModel
+ *
+ * Gets the model that is curently being used or %NULL if none.
+ *
+ * Returns: (nullable) (transfer none): The model in use
+ **/
+GListModel *
+gtk_slice_list_model_get_model (GtkSliceListModel *self)
+{
+  g_return_val_if_fail (GTK_IS_SLICE_LIST_MODEL (self), NULL);
+
+  return self->model;
+}
+
+/**
+ * gtk_slice_list_model_set_offset:
+ * @self: a #GtkSliceListModel
+ * @offset: the new offset to use
+ *
+ * Sets the offset into the original model for this slice.
+ *
+ * If the offset is too large for the sliced model,
+ * @self will end up empty. 
+ **/
+void
+gtk_slice_list_model_set_offset (GtkSliceListModel *self,
+                                 guint              offset)
+{
+  guint before, after;
+
+  g_return_if_fail (GTK_IS_SLICE_LIST_MODEL (self));
+
+  if (self->offset == offset)
+    return;
+
+  before = g_list_model_get_n_items (G_LIST_MODEL (self));
+
+  self->offset = offset;
+
+  after = g_list_model_get_n_items (G_LIST_MODEL (self));
+
+  if (before > 0 || after > 0)
+    g_list_model_items_changed (G_LIST_MODEL (self), 0, before, after);
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_OFFSET]);
+}
+
+/**
+ * gtk_slice_list_model_get_offset:
+ * @self: a #GtkSliceListModel
+ *
+ * Gets the offset set via gtk_slice_list_model_set_offset()
+ *
+ * Returns: The offset
+ **/
+guint
+gtk_slice_list_model_get_offset (GtkSliceListModel *self)
+{
+  g_return_val_if_fail (GTK_IS_SLICE_LIST_MODEL (self), 0);
+
+  return self->offset;
+}
+
+/**
+ * gtk_slice_list_model_set_size:
+ * @self: a #GtkSliceListModel
+ * @size: the maximum size
+ *
+ * Sets the maximum size. @self will never have more items
+ * than @size.
+ *
+ * It can however have fewer items if the offset is too large or
+ * the model sliced from doesn't have enough items.
+ */
+void
+gtk_slice_list_model_set_size (GtkSliceListModel *self,
+                               guint              size)
+{
+  guint before, after;
+
+  g_return_if_fail (GTK_IS_SLICE_LIST_MODEL (self));
+
+  if (self->size == size)
+    return;
+
+  before = g_list_model_get_n_items (G_LIST_MODEL (self));
+
+  self->size = size;
+
+  after = g_list_model_get_n_items (G_LIST_MODEL (self));
+
+  if (before > after)
+    g_list_model_items_changed (G_LIST_MODEL (self), after, before - after, 0);
+  else if (before < after)
+    g_list_model_items_changed (G_LIST_MODEL (self), before, 0, after - before);
+  /* else nothing */
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SIZE]);
+}
+
+/**
+ * gtk_slice_list_model_get_size:
+ * @self: a #GtkSliceListModel
+ *
+ * Gets the size set via gtk_slice_list_model_set_size().
+ *
+ * Returns: The size
+ **/
+guint
+gtk_slice_list_model_get_size (GtkSliceListModel *self)
+{
+  g_return_val_if_fail (GTK_IS_SLICE_LIST_MODEL (self), DEFAULT_SIZE);
+
+  return self->size;
+}
+
+
diff --git a/src/gtklistmodels/gtkslicelistmodel.h b/src/gtklistmodels/gtkslicelistmodel.h
new file mode 100644
index 00000000..45d698f5
--- /dev/null
+++ b/src/gtklistmodels/gtkslicelistmodel.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright © 2018 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#ifndef __GTK_SLICE_LIST_MODEL_H__
+#define __GTK_SLICE_LIST_MODEL_H__
+
+
+#define GTK_COMPILATION
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gtk/gtk.h> can be included directly."
+#endif
+
+#include <gio/gio.h>
+#include <gtk/gtkwidget.h>
+
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_SLICE_LIST_MODEL (gtk_slice_list_model_get_type ())
+
+GDK_AVAILABLE_IN_ALL
+G_DECLARE_FINAL_TYPE (GtkSliceListModel, gtk_slice_list_model, GTK, SLICE_LIST_MODEL, GObject)
+
+GDK_AVAILABLE_IN_ALL
+GtkSliceListModel *     gtk_slice_list_model_new                (GListModel             *model,
+                                                                 guint                   offset,
+                                                                 guint                   size);
+GDK_AVAILABLE_IN_ALL
+GtkSliceListModel *     gtk_slice_list_model_new_for_type       (GType                   item_type);
+
+GDK_AVAILABLE_IN_ALL
+void                    gtk_slice_list_model_set_model          (GtkSliceListModel      *self,
+                                                                 GListModel             *model);
+GDK_AVAILABLE_IN_ALL
+GListModel *            gtk_slice_list_model_get_model          (GtkSliceListModel      *self);
+GDK_AVAILABLE_IN_ALL
+void                    gtk_slice_list_model_set_offset         (GtkSliceListModel      *self,
+                                                                 guint                   offset);
+GDK_AVAILABLE_IN_ALL
+guint                   gtk_slice_list_model_get_offset         (GtkSliceListModel      *self);
+GDK_AVAILABLE_IN_ALL
+void                    gtk_slice_list_model_set_size           (GtkSliceListModel      *self,
+                                                                 guint                   size);
+GDK_AVAILABLE_IN_ALL
+guint                   gtk_slice_list_model_get_size           (GtkSliceListModel      *self);
+
+G_END_DECLS
+
+#endif /* __GTK_SLICE_LIST_MODEL_H__ */
diff --git a/src/gtklistmodels/gtksorter.c b/src/gtklistmodels/gtksorter.c
new file mode 100644
index 00000000..96176cb6
--- /dev/null
+++ b/src/gtklistmodels/gtksorter.c
@@ -0,0 +1,207 @@
+/*
+ * Copyright © 2019 Matthias Clasen
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Matthias Clasen <mclasen redhat com>
+ */
+
+/* #include "config.h" */
+
+#include "gtksorter.h"
+
+#include "gtkintl.h"
+#include "gtktypebuiltins.h"
+
+/**
+ * SECTION:gtksorter
+ * @title: GtkSorter
+ * @Short_description: Sorting items
+ * @See_also: #GtkSortListModel
+ *
+ * #GtkSorter is the way to describe sorting criteria.
+ * Its primary user is #GtkSortListModel.
+ *
+ * The model will use a sorter to determine the order in which its items should appear
+ * by calling gtk_sorter_compare() for pairs of items.
+ *
+ * Sorters may change their sorting behavior through their lifetime. In that case,
+ * they call gtk_sorter_changed(), which will emit the #GtkSorter::changed signal to
+ * notify that the sort order is no longer valid and should be updated by calling
+ * gtk_sorter_compare() again.
+ *
+ * GTK provides various pre-made sorter implementations for common sorting operations.
+ * #GtkColumnView has built-in support for sorting lists via the #GtkColumnViewColumn:sorter
+ * property, where the user can change the sorting by clicking on list headers.
+ *
+ * Of course, in particular for large lists, it is also possible to subclass #GtkSorter
+ * and provide one's own sorter.
+ */
+
+enum {
+  CHANGED,
+  LAST_SIGNAL
+};
+
+G_DEFINE_TYPE (GtkSorter, gtk_sorter, G_TYPE_OBJECT)
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static GtkOrdering
+gtk_sorter_default_compare (GtkSorter *self,
+                            gpointer   item1,
+                            gpointer   item2)
+{
+  g_critical ("Sorter of type '%s' does not implement GtkSorter::compare", G_OBJECT_TYPE_NAME (self));
+
+  return GTK_ORDERING_EQUAL;
+}
+
+static GtkSorterOrder
+gtk_sorter_default_get_order (GtkSorter *self)
+{
+  return GTK_SORTER_ORDER_PARTIAL;
+}
+
+static void
+gtk_sorter_class_init (GtkSorterClass *class)
+{
+  class->compare = gtk_sorter_default_compare;
+  class->get_order = gtk_sorter_default_get_order;
+
+  /**
+   * GtkSorter::changed:
+   * @self: The #GtkSorter
+   * @change: how the sorter changed
+   *
+   * This signal is emitted whenever the sorter changed. Users of the sorter
+   * should then update the sort order again via gtk_sorter_compare().
+   *
+   * #GtkSortListModel handles this signal automatically.
+   *
+   * Depending on the @change parameter, it may be possible to update
+   * the sort order without a full resorting. Refer to the #GtkSorterChange
+   * documentation for details.
+   */
+  signals[CHANGED] =
+    g_signal_new (I_("changed"),
+                  G_TYPE_FROM_CLASS (class),
+                  G_SIGNAL_RUN_LAST,
+                  0,
+                  NULL, NULL,
+                  g_cclosure_marshal_VOID__ENUM,
+                  G_TYPE_NONE, 1,
+                  GTK_TYPE_SORTER_CHANGE);
+  g_signal_set_va_marshaller (signals[CHANGED],
+                              G_TYPE_FROM_CLASS (class),
+                              g_cclosure_marshal_VOID__ENUMv);
+}
+
+static void
+gtk_sorter_init (GtkSorter *self)
+{
+}
+
+/**
+ * gtk_sorter_compare:
+ * @self: a #GtkSorter
+ * @item1: (type GObject) (transfer none): first item to compare
+ * @item2: (type GObject) (transfer none): second item to compare
+ *
+ * Compares two given items according to the sort order implemented
+ * by the sorter.
+ *
+ * Sorters implement a partial order:
+ * * It is reflexive, ie a = a
+ * * It is antisymmetric, ie if a < b and b < a, then a = b
+ * * It is transitive, ie given any 3 items with a ≤ b and b ≤ c,
+ *   then a ≤ c
+ * 
+ * The sorter  may signal it conforms to additional constraints
+ * via the return value of gtk_sorter_get_order().
+ *
+ * Returns: %GTK_ORDERING_EQUAL if @item1 == @item2,
+ *     %GTK_ORDERING_SMALLER if @item1 < @item2,
+ *     %GTK_ORDERING_LARGER if @item1 > @item2
+ */
+GtkOrdering
+gtk_sorter_compare (GtkSorter *self,
+                    gpointer   item1,
+                    gpointer   item2)
+{
+  GtkOrdering result;
+
+  g_return_val_if_fail (GTK_IS_SORTER (self), GTK_ORDERING_EQUAL);
+  g_return_val_if_fail (item1 && item2, GTK_ORDERING_EQUAL);
+
+  if (item1 == item2)
+    return GTK_ORDERING_EQUAL;
+
+  result = GTK_SORTER_GET_CLASS (self)->compare (self, item1, item2);
+
+#ifdef G_ENABLE_DEBUG
+  if (result < -1 || result > 1)
+    {
+      g_critical ("A sorter of type \"%s\" returned %d, which is not a valid GtkOrdering result.\n"
+                  "Did you forget to call gtk_ordering_from_cmpfunc()?",
+                  G_OBJECT_TYPE_NAME (self), (int) result);
+    }
+#endif
+
+  return result;
+}
+
+/**
+ * gtk_sorter_get_order:
+ * @self: a #GtkSorter
+ *
+ * Gets the order that @self conforms to. See #GtkSorterOrder for details
+ * of the possible return values.
+ *
+ * This function is intended to allow optimizations.
+ *
+ * Returns: The order
+ **/
+GtkSorterOrder
+gtk_sorter_get_order (GtkSorter *self)
+{
+  g_return_val_if_fail (GTK_IS_SORTER (self), GTK_SORTER_ORDER_PARTIAL);
+
+  return GTK_SORTER_GET_CLASS (self)->get_order (self);
+}
+
+/**
+ * gtk_sorter_changed:
+ * @self: a #GtkSorter
+ * @change: How the sorter changed
+ *
+ * Emits the #GtkSorter::changed signal to notify all users of the sorter
+ * that it has changed. Users of the sorter should then update the sort
+ * order via gtk_sorter_compare().
+ *
+ * Depending on the @change parameter, it may be possible to update
+ * the sort order without a full resorting. Refer to the #GtkSorterChange
+ * documentation for details.
+ *
+ * This function is intended for implementors of #GtkSorter subclasses and
+ * should not be called from other functions.
+ */
+void
+gtk_sorter_changed (GtkSorter       *self,
+                    GtkSorterChange  change)
+{
+  g_return_if_fail (GTK_IS_SORTER (self));
+
+  g_signal_emit (self, signals[CHANGED], 0, change);
+}
diff --git a/src/gtklistmodels/gtksorter.h b/src/gtklistmodels/gtksorter.h
new file mode 100644
index 00000000..36da4f90
--- /dev/null
+++ b/src/gtklistmodels/gtksorter.h
@@ -0,0 +1,131 @@
+/*
+ * Copyright © 2019 Matthias Clasen
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Matthias Clasen <mclasen redhat com>
+ */
+
+#ifndef __GTK_SORTER_H__
+#define __GTK_SORTER_H__
+
+#define GTK_COMPILATION
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gtk/gtk.h> can be included directly."
+#endif
+
+#include <gdk/gdk.h>
+#include <gtk/gtkenums.h>
+
+G_BEGIN_DECLS
+
+/**
+ * GtkSorterOrder:
+ * @GTK_SORTER_ORDER_PARTIAL: A partial order. And #GtkOrdering is possible.
+ * @GTK_SORTER_ORDER_INVALID: An invalid order. gtk_sorter_compare() will
+ *     always return %GTK_ORDERING_INVALID if both items are unequal.
+ * @GTK_SORTER_ORDER_NONE: No order, all elements are considered equal.
+ *     gtk_sorter_compare() will only return %GTK_ORDERING_EQUAL or
+ *     %GTK_ORDERING_INVALID.
+ * @GTK_SORTER_ORDER_TOTAL: A total order. gtk_sorter_compare() will only
+ *     return %GTK_ORDERING_EQUAL if an item is compared with itself. Two
+ *     different items will never cause this value to be returned.
+ *
+ * Describes the type of order that a #GtkSorter may describe.
+ */
+typedef enum {
+  GTK_SORTER_ORDER_PARTIAL,
+  GTK_SORTER_ORDER_NONE,
+  GTK_SORTER_ORDER_TOTAL
+} GtkSorterOrder;
+
+typedef enum {
+  GTK_ORDERING_SMALLER = -1,
+  GTK_ORDERING_EQUAL = 0,
+  GTK_ORDERING_LARGER = 1
+} GtkOrdering;
+
+/**
+ * GtkSorterChange:
+ * @GTK_SORTER_CHANGE_DIFFERENT: The sorter change cannot be described
+ *     by any of the other enumeration values
+ * @GTK_SORTER_CHANGE_INVERTED: The sort order was inverted. Comparisons
+ *     that returned %GTK_ORDERING_SMALLER now return %GTK_ORDERING_LARGER
+ *     and vice versa. Other comparisons return the same values as before.
+ * @GTK_SORTER_CHANGE_LESS_STRICT: The sorter is less strict: Comparisons
+ *     may now return %GTK_ORDERING_EQUAL that did not do so before.
+ * @GTK_SORTER_CHANGE_MORE_STRICT: The sorter is more strict: Comparisons
+ *     that did return %GTK_ORDERING_EQUAL may not do so anymore.
+ *
+ * Describes changes in a sorter in more detail and allows users
+ * to optimize resorting.
+ */
+typedef enum {
+  GTK_SORTER_CHANGE_DIFFERENT,
+  GTK_SORTER_CHANGE_INVERTED,
+  GTK_SORTER_CHANGE_LESS_STRICT,
+  GTK_SORTER_CHANGE_MORE_STRICT
+} GtkSorterChange;
+
+#define GTK_TYPE_SORTER             (gtk_sorter_get_type ())
+
+GDK_AVAILABLE_IN_ALL
+G_DECLARE_DERIVABLE_TYPE (GtkSorter, gtk_sorter, GTK, SORTER, GObject)
+
+/**
+ * GtkSorterClass
+ * @compare: Compare two items. See gtk_sorter_compare() for details.
+ * @get_order: Get the #GtkSorderOrder that applies to the current sorter.
+ *     If unimplemented, it returns %GTK_SORTER_ORDER_PARTIAL.
+ *
+ * The virtual table for #GtkSorter.
+ */
+struct _GtkSorterClass
+{
+  GObjectClass parent_class;
+
+  GtkOrdering           (* compare)                             (GtkSorter              *self,
+                                                                 gpointer                item1,
+                                                                 gpointer                item2);
+
+  /* optional */
+  GtkSorterOrder        (* get_order)                           (GtkSorter              *self);
+
+  /* Padding for future expansion */
+  void (*_gtk_reserved1) (void);
+  void (*_gtk_reserved2) (void);
+  void (*_gtk_reserved3) (void);
+  void (*_gtk_reserved4) (void);
+  void (*_gtk_reserved5) (void);
+  void (*_gtk_reserved6) (void);
+  void (*_gtk_reserved7) (void);
+  void (*_gtk_reserved8) (void);
+};
+
+GDK_AVAILABLE_IN_ALL
+GtkOrdering             gtk_sorter_compare                      (GtkSorter              *self,
+                                                                 gpointer                item1,
+                                                                 gpointer                item2);
+GDK_AVAILABLE_IN_ALL
+GtkSorterOrder          gtk_sorter_get_order                    (GtkSorter              *self);
+
+/* for sorter implementations */
+GDK_AVAILABLE_IN_ALL
+void                    gtk_sorter_changed                      (GtkSorter              *self,
+                                                                 GtkSorterChange         change);
+
+G_END_DECLS
+
+#endif /* __GTK_SORTER_H__ */
+
diff --git a/src/gtklistmodels/gtksortlistmodel.c b/src/gtklistmodels/gtksortlistmodel.c
new file mode 100644
index 00000000..bf030e11
--- /dev/null
+++ b/src/gtklistmodels/gtksortlistmodel.c
@@ -0,0 +1,595 @@
+/*
+ * Copyright © 2018 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+/* #include "config.h" */
+
+#include "gtksortlistmodel.h"
+
+#include "gtkintl.h"
+#include "gtkprivate.h"
+
+/**
+ * SECTION:gtksortlistmodel
+ * @title: GtkSortListModel
+ * @short_description: A list model that sorts its items
+ * @see_also: #GListModel, #GtkSorter
+ *
+ * #GtkSortListModel is a list model that takes a list model and
+ * sorts its elements according to a #GtkSorter.
+ *
+ * #GtkSortListModel is a generic model and because of that it
+ * cannot take advantage of any external knowledge when sorting.
+ * If you run into performance issues with #GtkSortListModel, it
+ * is strongly recommended that you write your own sorting list
+ * model.
+ */
+
+enum {
+  PROP_0,
+  PROP_ITEM_TYPE,
+  PROP_MODEL,
+  PROP_SORTER,
+  NUM_PROPERTIES
+};
+
+typedef struct _GtkSortListEntry GtkSortListEntry;
+
+struct _GtkSortListModel
+{
+  GObject parent_instance;
+
+  GType item_type;
+  GListModel *model;
+  GtkSorter *sorter;
+
+  GSequence *sorted; /* NULL if known unsorted */
+  GSequence *unsorted; /* NULL if known unsorted */
+};
+
+struct _GtkSortListModelClass
+{
+  GObjectClass parent_class;
+};
+
+struct _GtkSortListEntry
+{
+  GSequenceIter *sorted_iter;
+  GSequenceIter *unsorted_iter;
+  gpointer item; /* holds ref */
+};
+
+static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
+
+static void
+gtk_sort_list_entry_free (gpointer data)
+{
+  GtkSortListEntry *entry = data;
+
+  g_object_unref (entry->item);
+
+  g_slice_free (GtkSortListEntry, entry);
+}
+
+static GType
+gtk_sort_list_model_get_item_type (GListModel *list)
+{
+  GtkSortListModel *self = GTK_SORT_LIST_MODEL (list);
+
+  return self->item_type;
+}
+
+static guint
+gtk_sort_list_model_get_n_items (GListModel *list)
+{
+  GtkSortListModel *self = GTK_SORT_LIST_MODEL (list);
+
+  if (self->model == NULL)
+    return 0;
+
+  return g_list_model_get_n_items (self->model);
+}
+
+static gpointer
+gtk_sort_list_model_get_item (GListModel *list,
+                              guint       position)
+{
+  GtkSortListModel *self = GTK_SORT_LIST_MODEL (list);
+  GSequenceIter *iter;
+  GtkSortListEntry *entry;
+
+  if (self->model == NULL)
+    return NULL;
+
+  if (self->unsorted == NULL)
+    return g_list_model_get_item (self->model, position);
+
+  iter = g_sequence_get_iter_at_pos (self->sorted, position);
+  if (g_sequence_iter_is_end (iter))
+    return NULL;
+
+  entry = g_sequence_get (iter);
+
+  return g_object_ref (entry->item);
+}
+
+static void
+gtk_sort_list_model_model_init (GListModelInterface *iface)
+{
+  iface->get_item_type = gtk_sort_list_model_get_item_type;
+  iface->get_n_items = gtk_sort_list_model_get_n_items;
+  iface->get_item = gtk_sort_list_model_get_item;
+}
+
+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))
+
+static void
+gtk_sort_list_model_remove_items (GtkSortListModel *self,
+                                  guint             position,
+                                  guint             n_items,
+                                  guint            *unmodified_start,
+                                  guint            *unmodified_end)
+{
+  GSequenceIter *unsorted_iter;
+  guint i, pos, start, end, length_before;
+
+  start = end = length_before = g_sequence_get_length (self->sorted);
+  unsorted_iter = g_sequence_get_iter_at_pos (self->unsorted, position);
+
+  for (i = 0; i < n_items ; i++)
+    {
+      GtkSortListEntry *entry;
+      GSequenceIter *next;
+
+      next = g_sequence_iter_next (unsorted_iter);
+
+      entry = g_sequence_get (unsorted_iter);
+      pos = g_sequence_iter_get_position (entry->sorted_iter);
+      start = MIN (start, pos);
+      end = MIN (end, length_before - i - 1 - pos);
+
+      g_sequence_remove (entry->unsorted_iter);
+      g_sequence_remove (entry->sorted_iter);
+
+      unsorted_iter = next;
+    }
+
+  *unmodified_start = start;
+  *unmodified_end = end;
+}
+
+static int
+_sort_func (gconstpointer item1,
+            gconstpointer item2,
+            gpointer      data)
+{
+  GtkSortListEntry *entry1 = (GtkSortListEntry *) item1;
+  GtkSortListEntry *entry2 = (GtkSortListEntry *) item2;
+  GtkOrdering result;
+
+  result = gtk_sorter_compare (GTK_SORTER (data), entry1->item, entry2->item);
+
+  if (result == GTK_ORDERING_EQUAL)
+    result = g_sequence_iter_compare (entry1->unsorted_iter, entry2->unsorted_iter);
+
+  return result;
+}
+
+static void
+gtk_sort_list_model_add_items (GtkSortListModel *self,
+                               guint             position,
+                               guint             n_items,
+                               guint            *unmodified_start,
+                               guint            *unmodified_end)
+{
+  GSequenceIter *unsorted_end;
+  guint i, pos, start, end, length_before;
+
+  unsorted_end = g_sequence_get_iter_at_pos (self->unsorted, position);
+  start = end = length_before = g_sequence_get_length (self->sorted);
+
+  for (i = 0; i < n_items; i++)
+    {
+      GtkSortListEntry *entry = g_slice_new0 (GtkSortListEntry);
+
+      entry->item = g_list_model_get_item (self->model, position + i);
+      entry->unsorted_iter = g_sequence_insert_before (unsorted_end, entry);
+      entry->sorted_iter = g_sequence_insert_sorted (self->sorted, entry, _sort_func, self->sorter);
+      if (unmodified_start != NULL || unmodified_end != NULL)
+        {
+          pos = g_sequence_iter_get_position (entry->sorted_iter);
+          start = MIN (start, pos);
+          end = MIN (end, length_before + i - pos);
+        }
+    }
+
+  if (unmodified_start)
+    *unmodified_start = start;
+  if (unmodified_end)
+    *unmodified_end = end;
+}
+
+static void
+gtk_sort_list_model_items_changed_cb (GListModel       *model,
+                                      guint             position,
+                                      guint             removed,
+                                      guint             added,
+                                      GtkSortListModel *self)
+{
+  guint n_items, start, end, start2, end2;
+
+  if (removed == 0 && added == 0)
+    return;
+
+  if (self->sorted == NULL)
+    {
+      g_list_model_items_changed (G_LIST_MODEL (self), position, removed, added);
+      return;
+    }
+
+  gtk_sort_list_model_remove_items (self, position, removed, &start, &end);
+  gtk_sort_list_model_add_items (self, position, added, &start2, &end2);
+  start = MIN (start, start2);
+  end = MIN (end, end2);
+
+  n_items = g_sequence_get_length (self->sorted) - start - end;
+  g_list_model_items_changed (G_LIST_MODEL (self), start, n_items - added + removed, n_items);
+}
+
+static void
+gtk_sort_list_model_set_property (GObject      *object,
+                                  guint         prop_id,
+                                  const GValue *value,
+                                  GParamSpec   *pspec)
+{
+  GtkSortListModel *self = GTK_SORT_LIST_MODEL (object);
+
+  switch (prop_id)
+    {
+    case PROP_ITEM_TYPE:
+      self->item_type = g_value_get_gtype (value);
+      break;
+
+    case PROP_MODEL:
+      gtk_sort_list_model_set_model (self, g_value_get_object (value));
+      break;
+
+    case PROP_SORTER:
+      gtk_sort_list_model_set_sorter (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void 
+gtk_sort_list_model_get_property (GObject     *object,
+                                  guint        prop_id,
+                                  GValue      *value,
+                                  GParamSpec  *pspec)
+{
+  GtkSortListModel *self = GTK_SORT_LIST_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;
+
+    case PROP_SORTER:
+      g_value_set_object (value, self->sorter);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void gtk_sort_list_model_resort (GtkSortListModel *self);
+
+static void
+gtk_sort_list_model_sorter_changed_cb (GtkSorter        *sorter,
+                                       int               change,
+                                       GtkSortListModel *self)
+{
+  gtk_sort_list_model_resort (self);
+}
+
+static void
+gtk_sort_list_model_clear_model (GtkSortListModel *self)
+{
+  if (self->model == NULL)
+    return;
+
+  g_signal_handlers_disconnect_by_func (self->model, gtk_sort_list_model_items_changed_cb, self);
+  g_clear_object (&self->model);
+  g_clear_pointer (&self->sorted, g_sequence_free);
+  g_clear_pointer (&self->unsorted, g_sequence_free);
+}
+
+static void
+gtk_sort_list_model_clear_sorter (GtkSortListModel *self)
+{
+  if (self->sorter == NULL)
+    return;
+
+  g_signal_handlers_disconnect_by_func (self->sorter, gtk_sort_list_model_sorter_changed_cb, self);
+  g_clear_object (&self->sorter);
+}
+
+static void
+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);
+
+  G_OBJECT_CLASS (gtk_sort_list_model_parent_class)->dispose (object);
+};
+
+static void
+gtk_sort_list_model_class_init (GtkSortListModelClass *class)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (class);
+
+  gobject_class->set_property = gtk_sort_list_model_set_property;
+  gobject_class->get_property = gtk_sort_list_model_get_property;
+  gobject_class->dispose = gtk_sort_list_model_dispose;
+
+  /**
+   * GtkSortListModel:sorter:
+   *
+   * The sorter for this model
+   */
+  properties[PROP_SORTER] =
+      g_param_spec_object ("sorter",
+                            P_("Sorter"),
+                            P_("The sorter for this model"),
+                            GTK_TYPE_SORTER,
+                            GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  /**
+   * GtkSortListModel:item-type:
+   *
+   * The #GType for items of this model
+   */
+  properties[PROP_ITEM_TYPE] =
+      g_param_spec_gtype ("item-type",
+                          P_("Item type"),
+                          P_("The type of items of this list"),
+                          G_TYPE_OBJECT,
+                          GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY);
+
+  /**
+   * GtkSortListModel:model:
+   *
+   * The model being sorted
+   */
+  properties[PROP_MODEL] =
+      g_param_spec_object ("model",
+                           P_("Model"),
+                           P_("The model being sorted"),
+                           G_TYPE_LIST_MODEL,
+                           GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY);
+
+  g_object_class_install_properties (gobject_class, NUM_PROPERTIES, properties);
+}
+
+static void
+gtk_sort_list_model_init (GtkSortListModel *self)
+{
+}
+
+/**
+ * gtk_sort_list_model_new:
+ * @model: the model to sort
+ * @sorter: (allow-none): the #GtkSorter to sort @model with
+ *
+ * Creates a new sort list model that uses the @sorter to sort @model.
+ *
+ * Returns: a new #GtkSortListModel
+ **/
+GtkSortListModel *
+gtk_sort_list_model_new (GListModel *model,
+                         GtkSorter  *sorter)
+{
+  GtkSortListModel *result;
+
+  g_return_val_if_fail (G_IS_LIST_MODEL (model), NULL);
+  g_return_val_if_fail (sorter == NULL || GTK_IS_SORTER (sorter), NULL);
+
+  result = g_object_new (GTK_TYPE_SORT_LIST_MODEL,
+                         "item-type", g_list_model_get_item_type (model),
+                         "model", model,
+                         "sorter", sorter,
+                         NULL);
+
+  return result;
+}
+
+/**
+ * gtk_sort_list_model_new_for_type:
+ * @item_type: the type of the items that will be returned
+ *
+ * Creates a new empty sort list model set up to return items of type @item_type.
+ * It is up to the application to set a proper sort function and model to ensure
+ * the item type is matched.
+ *
+ * Returns: a new #GtkSortListModel
+ **/
+GtkSortListModel *
+gtk_sort_list_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_SORT_LIST_MODEL,
+                       "item-type", item_type,
+                       NULL);
+}
+
+static void
+gtk_sort_list_model_create_sequences (GtkSortListModel *self)
+{
+  if (self->sorter == NULL || self->model == NULL)
+    return;
+
+  self->sorted = g_sequence_new (gtk_sort_list_entry_free);
+  self->unsorted = g_sequence_new (NULL);
+
+  gtk_sort_list_model_add_items (self, 0, g_list_model_get_n_items (self->model), NULL, NULL);
+}
+
+/**
+ * gtk_sort_list_model_set_model:
+ * @self: a #GtkSortListModel
+ * @model: (allow-none): The model to be sorted
+ *
+ * Sets the model to be sorted. The @model's item type must conform to
+ * the item type of @self.
+ **/
+void
+gtk_sort_list_model_set_model (GtkSortListModel *self,
+                               GListModel       *model)
+{
+  guint removed, added;
+
+  g_return_if_fail (GTK_IS_SORT_LIST_MODEL (self));
+  g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model));
+  if (model)
+    {
+      g_return_if_fail (g_type_is_a (g_list_model_get_item_type (model), self->item_type));
+    }
+
+  if (self->model == model)
+    return;
+
+  removed = g_list_model_get_n_items (G_LIST_MODEL (self));
+  gtk_sort_list_model_clear_model (self);
+
+  if (model)
+    {
+      self->model = g_object_ref (model);
+      g_signal_connect (model, "items-changed", G_CALLBACK (gtk_sort_list_model_items_changed_cb), self);
+      added = g_list_model_get_n_items (model);
+
+      gtk_sort_list_model_create_sequences (self);
+    }
+  else
+    added = 0;
+  
+  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_sort_list_model_get_model:
+ * @self: a #GtkSortListModel
+ *
+ * Gets the model currently sorted or %NULL if none.
+ *
+ * Returns: (nullable) (transfer none): The model that gets sorted
+ **/
+GListModel *
+gtk_sort_list_model_get_model (GtkSortListModel *self)
+{
+  g_return_val_if_fail (GTK_IS_SORT_LIST_MODEL (self), NULL);
+
+  return self->model;
+}
+
+static void
+gtk_sort_list_model_resort (GtkSortListModel *self)
+{
+  guint n_items;
+
+  g_return_if_fail (GTK_IS_SORT_LIST_MODEL (self));
+  
+  if (self->sorted == NULL)
+    return;
+
+  n_items = g_list_model_get_n_items (self->model);
+  if (n_items <= 1)
+    return;
+
+  g_sequence_sort (self->sorted, _sort_func, self->sorter);
+
+  g_list_model_items_changed (G_LIST_MODEL (self), 0, n_items, n_items);
+}
+
+/**
+ * gtk_sort_list_model_set_sorter:
+ * @self: a #GtkSortListModel
+ * @sorter: (allow-none): the #GtkSorter to sort @model with
+ *
+ * Sets a new sorter on @self.
+ */
+void
+gtk_sort_list_model_set_sorter (GtkSortListModel *self,
+                                GtkSorter        *sorter)
+{
+  guint n_items;
+
+  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 (sorter)
+    {
+      self->sorter = g_object_ref (sorter);
+      g_signal_connect (sorter, "changed", G_CALLBACK (gtk_sort_list_model_sorter_changed_cb), self);
+    }
+
+  g_clear_pointer (&self->unsorted, g_sequence_free);
+  g_clear_pointer (&self->sorted, g_sequence_free);
+  
+  gtk_sort_list_model_create_sequences (self);
+    
+  n_items = g_list_model_get_n_items (G_LIST_MODEL (self));
+  if (n_items > 1)
+    g_list_model_items_changed (G_LIST_MODEL (self), 0, n_items, n_items);
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SORTER]);
+}
+
+/**
+ * gtk_sort_list_model_get_sorter:
+ * @self: a #GtkSortLisTModel
+ *
+ * Gets the sorter that is used to sort @self.
+ *
+ * Returns: (nullable) (transfer none): the sorter of #self
+ */
+GtkSorter *
+gtk_sort_list_model_get_sorter (GtkSortListModel *self)
+{
+  g_return_val_if_fail (GTK_IS_SORT_LIST_MODEL (self), NULL);
+
+  return self->sorter;
+}
diff --git a/src/gtklistmodels/gtksortlistmodel.h b/src/gtklistmodels/gtksortlistmodel.h
new file mode 100644
index 00000000..17f6e9a1
--- /dev/null
+++ b/src/gtklistmodels/gtksortlistmodel.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright © 2018 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#ifndef __GTK_SORT_LIST_MODEL_H__
+#define __GTK_SORT_LIST_MODEL_H__
+
+
+#define GTK_COMPILATION
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gtk/gtk.h> can be included directly."
+#endif
+
+#include <gio/gio.h>
+#include <gtk/gtkwidget.h>
+#include "gtksorter.h"
+
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_SORT_LIST_MODEL (gtk_sort_list_model_get_type ())
+
+GDK_AVAILABLE_IN_ALL
+G_DECLARE_FINAL_TYPE (GtkSortListModel, gtk_sort_list_model, GTK, SORT_LIST_MODEL, GObject)
+
+GDK_AVAILABLE_IN_ALL
+GtkSortListModel *      gtk_sort_list_model_new                 (GListModel            *model,
+                                                                 GtkSorter             *sorter);
+GDK_AVAILABLE_IN_ALL
+GtkSortListModel *      gtk_sort_list_model_new_for_type        (GType                  item_type);
+
+GDK_AVAILABLE_IN_ALL
+void                    gtk_sort_list_model_set_sorter          (GtkSortListModel       *self,
+                                                                 GtkSorter              *sorter);
+GDK_AVAILABLE_IN_ALL
+GtkSorter *             gtk_sort_list_model_get_sorter          (GtkSortListModel       *self);
+
+GDK_AVAILABLE_IN_ALL
+void                    gtk_sort_list_model_set_model           (GtkSortListModel       *self,
+                                                                 GListModel             *model);
+GDK_AVAILABLE_IN_ALL
+GListModel *            gtk_sort_list_model_get_model           (GtkSortListModel       *self);
+
+G_END_DECLS
+
+#endif /* __GTK_SORT_LIST_MODEL_H__ */
diff --git a/src/gtklistmodels/gtktypebuiltins.c b/src/gtklistmodels/gtktypebuiltins.c
new file mode 100644
index 00000000..fef1b283
--- /dev/null
+++ b/src/gtklistmodels/gtktypebuiltins.c
@@ -0,0 +1,89 @@
+#include <gtk/gtk.h>
+#include "gtkprivate.h"
+
+#include "gtktypebuiltins.h"
+
+/* enumerations from "gtksorter.h" */
+GType
+gtk_sorter_order_get_type (void)
+{
+  static volatile gsize g_define_type_id__volatile = 0;
+
+  if (g_once_init_enter (&g_define_type_id__volatile))
+    {
+      static const GEnumValue values[] = {
+        { GTK_SORTER_ORDER_PARTIAL, "GTK_SORTER_ORDER_PARTIAL", "partial" },
+        { GTK_SORTER_ORDER_NONE, "GTK_SORTER_ORDER_NONE", "none" },
+        { GTK_SORTER_ORDER_TOTAL, "GTK_SORTER_ORDER_TOTAL", "total" },
+        { 0, NULL, NULL }
+      };
+      GType g_define_type_id =
+        g_enum_register_static (g_intern_static_string ("GtkSorterOrder"), values);
+      g_once_init_leave (&g_define_type_id__volatile, g_define_type_id);
+    }
+
+  return g_define_type_id__volatile;
+}
+GType
+gtk_sorter_change_get_type (void)
+{
+  static volatile gsize g_define_type_id__volatile = 0;
+
+  if (g_once_init_enter (&g_define_type_id__volatile))
+    {
+      static const GEnumValue values[] = {
+        { GTK_SORTER_CHANGE_DIFFERENT, "GTK_SORTER_CHANGE_DIFFERENT", "different" },
+        { GTK_SORTER_CHANGE_INVERTED, "GTK_SORTER_CHANGE_INVERTED", "inverted" },
+        { GTK_SORTER_CHANGE_LESS_STRICT, "GTK_SORTER_CHANGE_LESS_STRICT", "less-strict" },
+        { GTK_SORTER_CHANGE_MORE_STRICT, "GTK_SORTER_CHANGE_MORE_STRICT", "more-strict" },
+        { 0, NULL, NULL }
+      };
+      GType g_define_type_id =
+        g_enum_register_static (g_intern_static_string ("GtkSorterChange"), values);
+      g_once_init_leave (&g_define_type_id__volatile, g_define_type_id);
+    }
+
+  return g_define_type_id__volatile;
+}
+
+/* enumerations from "gtkfilter.h" */
+GType
+gtk_filter_match_get_type (void)
+{
+  static volatile gsize g_define_type_id__volatile = 0;
+
+  if (g_once_init_enter (&g_define_type_id__volatile))
+    {
+      static const GEnumValue values[] = {
+        { GTK_FILTER_MATCH_SOME, "GTK_FILTER_MATCH_SOME", "some" },
+        { GTK_FILTER_MATCH_NONE, "GTK_FILTER_MATCH_NONE", "none" },
+        { GTK_FILTER_MATCH_ALL, "GTK_FILTER_MATCH_ALL", "all" },
+        { 0, NULL, NULL }
+      };
+      GType g_define_type_id =
+        g_enum_register_static (g_intern_static_string ("GtkFilterMatch"), values);
+      g_once_init_leave (&g_define_type_id__volatile, g_define_type_id);
+    }
+
+  return g_define_type_id__volatile;
+}
+GType
+gtk_filter_change_get_type (void)
+{
+  static volatile gsize g_define_type_id__volatile = 0;
+
+  if (g_once_init_enter (&g_define_type_id__volatile))
+    {
+      static const GEnumValue values[] = {
+        { GTK_FILTER_CHANGE_DIFFERENT, "GTK_FILTER_CHANGE_DIFFERENT", "different" },
+        { GTK_FILTER_CHANGE_LESS_STRICT, "GTK_FILTER_CHANGE_LESS_STRICT", "less-strict" },
+        { GTK_FILTER_CHANGE_MORE_STRICT, "GTK_FILTER_CHANGE_MORE_STRICT", "more-strict" },
+        { 0, NULL, NULL }
+      };
+      GType g_define_type_id =
+        g_enum_register_static (g_intern_static_string ("GtkFilterChange"), values);
+      g_once_init_leave (&g_define_type_id__volatile, g_define_type_id);
+    }
+
+  return g_define_type_id__volatile;
+}
diff --git a/src/gtklistmodels/gtktypebuiltins.h b/src/gtklistmodels/gtktypebuiltins.h
new file mode 100644
index 00000000..a7d9bb28
--- /dev/null
+++ b/src/gtklistmodels/gtktypebuiltins.h
@@ -0,0 +1,18 @@
+#include <glib-object.h>
+#include <gdk/gdk.h>
+
+#include "gtksorter.h"
+#include "gtkfilter.h"
+
+/* enumerations from "gtksorter.h" */
+GDK_AVAILABLE_IN_ALL GType gtk_sorter_order_get_type (void) G_GNUC_CONST;
+#define GTK_TYPE_SORTER_ORDER (gtk_sorter_order_get_type ())
+GDK_AVAILABLE_IN_ALL GType gtk_sorter_change_get_type (void) G_GNUC_CONST;
+#define GTK_TYPE_SORTER_CHANGE (gtk_sorter_change_get_type ())
+
+/* enumerations from "gtkfilter.h" */
+GDK_AVAILABLE_IN_ALL GType gtk_filter_match_get_type (void) G_GNUC_CONST;
+#define GTK_TYPE_FILTER_MATCH (gtk_filter_match_get_type ())
+GDK_AVAILABLE_IN_ALL GType gtk_filter_change_get_type (void) G_GNUC_CONST;
+#define GTK_TYPE_FILTER_CHANGE (gtk_filter_change_get_type ())
+


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]