diff --git a/src/gtklistmodels/gtk.h b/src/gtklistmodels/gtk.h
new file mode 100644
index 0000000..5bde461
--- /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 0000000..7bd72e3
--- /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 .
+ *
+ * Authors: Benjamin Otte
+ */
+
+/* #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 0000000..90f6751
--- /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 .
+ *
+ * Authors: Benjamin Otte
+ */
+
+#ifndef __GTK_CUSTOM_FILTER_H__
+#define __GTK_CUSTOM_FILTER_H__
+
+#define GTK_COMPILATION
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only 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 0000000..e70c6c8
--- /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 .
+ *
+ * Authors: Matthias Clasen
+ */
+
+/* #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 0000000..1adf56a
--- /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 .
+ *
+ * Authors: Matthias Clasen
+ */
+
+#ifndef __GTK_CUSTOM_SORTER_H__
+#define __GTK_CUSTOM_SORTER_H__
+
+#define GTK_COMPILATION
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only can be included directly."
+#endif
+
+/* #include */
+#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 0000000..0ece82d
--- /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 .
+ *
+ * Authors: Benjamin Otte
+ */
+
+/* #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 0000000..f34cc90
--- /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 .
+ *
+ * Authors: Benjamin Otte
+ */
+
+#ifndef __GTK_FILTER_H__
+#define __GTK_FILTER_H__
+
+#define GTK_COMPILATION
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only can be included directly."
+#endif
+
+#include
+
+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 0000000..a9bd13b
--- /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 .
+ *
+ * Authors: Benjamin Otte
+ */
+
+/* #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);
+}
+
+/*
+ * 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 0000000..bc4bd7f
--- /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 .
+ *
+ * Authors: Benjamin Otte
+ */
+
+#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 can be included directly."
+#endif
+
+#include
+#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 0000000..bb6d614
--- /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 .
+ *
+ * Authors: Benjamin Otte
+ */
+
+/* #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 0000000..59f0d61
--- /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 .
+ *
+ * Authors: Benjamin Otte
+ */
+
+#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 can be included directly."
+#endif
+
+#include
+
+
+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 0000000..4931cb6
--- /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
+
+#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 0000000..8dc9513
--- /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 .
+ */
+
+/*
+ * 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
+
+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 0000000..fd96de9
--- /dev/null
+++ b/src/gtklistmodels/gtkrbtree.c
@@ -0,0 +1,800 @@
+/* gtkrbtree.c
+ * Copyright (C) 2000 Red Hat, Inc., Jonathan Blandford
+ *
+ * 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 .
+ */
+
+/* #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 0000000..45aba5c
--- /dev/null
+++ b/src/gtklistmodels/gtkrbtreeprivate.h
@@ -0,0 +1,75 @@
+/* gtkrbtree.h
+ * Copyright (C) 2000 Red Hat, Inc., Jonathan Blandford
+ *
+ * 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 .
+ */
+
+/* A Red-Black Tree implementation used specifically by GtkTreeView.
+ */
+#ifndef __GTK_RB_TREE_H__
+#define __GTK_RB_TREE_H__
+
+#include
+
+
+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 0000000..fca2a68
--- /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 .
+ *
+ * Authors: Benjamin Otte
+ */
+
+/* #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 0000000..45d698f
--- /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 .
+ *
+ * Authors: Benjamin Otte
+ */
+
+#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 can be included directly."
+#endif
+
+#include
+#include
+
+
+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 0000000..96176cb
--- /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 .
+ *
+ * Authors: Matthias Clasen
+ */
+
+/* #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 0000000..36da4f9
--- /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 .
+ *
+ * Authors: Matthias Clasen
+ */
+
+#ifndef __GTK_SORTER_H__
+#define __GTK_SORTER_H__
+
+#define GTK_COMPILATION
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only can be included directly."
+#endif
+
+#include
+#include
+
+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 0000000..bf030e1
--- /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 .
+ *
+ * Authors: Benjamin Otte
+ */
+
+/* #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 0000000..17f6e9a
--- /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 .
+ *
+ * Authors: Benjamin Otte
+ */
+
+#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 can be included directly."
+#endif
+
+#include
+#include
+#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 0000000..fef1b28
--- /dev/null
+++ b/src/gtklistmodels/gtktypebuiltins.c
@@ -0,0 +1,89 @@
+#include
+#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 0000000..a7d9bb2
--- /dev/null
+++ b/src/gtklistmodels/gtktypebuiltins.h
@@ -0,0 +1,18 @@
+#include
+#include
+
+#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 ())
+