From 654244a49d9d492e8679ccd09955a394a9da6cd2 Mon Sep 17 00:00:00 2001
From: Zander Brown <zbrown@gnome.org>
Date: Tue, 28 May 2019 17:46:33 +0100
Subject: [PATCH] Add  gtk4's list models

These are part of gtk4 so we must have an in-tree copy to use them with
gtk3. This is from gtk+4 commit ???
---
 src/gtk-list-models/gtkfilterlistmodel.c | 710 ++++++++++++++++++++
 src/gtk-list-models/gtkfilterlistmodel.h |  74 +++
 src/gtk-list-models/gtkrbtree.c          | 798 +++++++++++++++++++++++
 src/gtk-list-models/gtkrbtreeprivate.h   |  75 +++
 src/gtk-list-models/gtksortlistmodel.c   | 562 ++++++++++++++++
 src/gtk-list-models/gtksortlistmodel.h   |  61 ++
 src/gtk-list-models/meson.build          |   8 +
 src/meson.build                          |   2 +
 8 files changed, 2290 insertions(+)
 create mode 100644 src/gtk-list-models/gtkfilterlistmodel.c
 create mode 100644 src/gtk-list-models/gtkfilterlistmodel.h
 create mode 100644 src/gtk-list-models/gtkrbtree.c
 create mode 100644 src/gtk-list-models/gtkrbtreeprivate.h
 create mode 100644 src/gtk-list-models/gtksortlistmodel.c
 create mode 100644 src/gtk-list-models/gtksortlistmodel.h
 create mode 100644 src/gtk-list-models/meson.build

diff --git a/src/gtk-list-models/gtkfilterlistmodel.c b/src/gtk-list-models/gtkfilterlistmodel.c
new file mode 100644
index 000000000..6186f90e6
--- /dev/null
+++ b/src/gtk-list-models/gtkfilterlistmodel.c
@@ -0,0 +1,710 @@
+/*
+ * Copyright © 2018 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte@gnome.org>
+ */
+
+#include "config.h"
+
+#include "gtkfilterlistmodel.h"
+#include "gtkrbtreeprivate.h"
+
+#include <glib/gi18n-lib.h>
+
+/**
+ * SECTION:gtkfilterlistmodel
+ * @title: GtkFilterListModel
+ * @short_description: A list model that filters its items
+ * @see_also: #GListModel
+ *
+ * #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 #GtkFilterListModelFilterFunc.
+ */
+
+enum {
+  PROP_0,
+  PROP_HAS_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;
+  GtkFilterListModelFilterFunc filter_func;
+  gpointer user_data;
+  GDestroyNotify user_destroy;
+
+  GtkRbTree *items; /* NULL if filter_func == NULL */
+};
+
+struct _GtkFilterListModelClass
+{
+  GObjectClass parent_class;
+};
+
+static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
+
+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;
+
+  if (self->model == NULL)
+    return 0;
+
+  if (!self->items)
+    return g_list_model_get_n_items (self->model);
+
+  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;
+
+  if (self->model == NULL)
+    return NULL;
+
+  if (self->items)
+    gtk_filter_list_model_get_nth_filtered (self->items, position, &unfiltered);
+  else
+    unfiltered = position;
+
+  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;
+
+  item = g_list_model_get_item (self->model, position);
+  visible = self->filter_func (item, self->user_data);
+  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;
+
+  if (self->items == NULL)
+    {
+      g_list_model_items_changed (G_LIST_MODEL (self), position, removed, added);
+      return;
+    }
+
+  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_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_HAS_FILTER:
+      g_value_set_boolean (value, self->items != NULL);
+      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);
+}
+
+static void
+gtk_filter_list_model_dispose (GObject *object)
+{
+  GtkFilterListModel *self = GTK_FILTER_LIST_MODEL (object);
+
+  gtk_filter_list_model_clear_model (self);
+  if (self->user_destroy)
+    self->user_destroy (self->user_data);
+  self->filter_func = NULL;
+  self->user_data = NULL;
+  self->user_destroy = NULL;
+  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:has-filter:
+   *
+   * If a filter is set for this model
+   */
+  properties[PROP_HAS_FILTER] =
+      g_param_spec_boolean ("has-filter",
+                            N_("has filter"),
+                            N_("If a filter is set for this model"),
+                            FALSE,
+                            G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY);
+
+  /**
+   * GtkFilterListModel:item-type:
+   *
+   * The #GType for elements of this object
+   */
+  properties[PROP_ITEM_TYPE] =
+      g_param_spec_gtype ("item-type",
+                          N_("Item type"),
+                          N_("The type of elements of this object"),
+                          G_TYPE_OBJECT,
+                          G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY);
+
+  /**
+   * GtkFilterListModel:model:
+   *
+   * The model being filtered
+   */
+  properties[PROP_MODEL] =
+      g_param_spec_object ("model",
+                           N_("Model"),
+                           N_("The model being filtered"),
+                           G_TYPE_LIST_MODEL,
+                           G_PARAM_STATIC_STRINGS | G_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)
+{
+}
+
+
+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;
+    }
+}
+
+/**
+ * gtk_filter_list_model_new:
+ * @model: the model to sort
+ * @filter_func: (allow-none): filter function or %NULL to not filter items
+ * @user_data: user data passed to @filter_func
+ * @user_destroy: destroy notifier for @user_data
+ *
+ * Creates a new #GtkFilterListModel that will filter @model using the given
+ * @filter_func.
+ *
+ * Returns: a new #GtkFilterListModel
+ **/
+GtkFilterListModel *
+gtk_filter_list_model_new (GListModel                   *model,
+                           GtkFilterListModelFilterFunc  filter_func,
+                           gpointer                      user_data,
+                           GDestroyNotify                user_destroy)
+{
+  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,
+                         NULL);
+
+  if (filter_func)
+    gtk_filter_list_model_set_filter_func (result, filter_func, user_data, user_destroy);
+
+  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 function 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_func:
+ * @self: a #GtkFilterListModel
+ * @filter_func: (allow-none): filter function or %NULL to not filter items
+ * @user_data: user data passed to @filter_func
+ * @user_destroy: destroy notifier for @user_data
+ *
+ * Sets the function used to filter items. The function will be called for every
+ * item and if it returns %TRUE the item is considered visible.
+ **/
+void
+gtk_filter_list_model_set_filter_func (GtkFilterListModel           *self,
+                                       GtkFilterListModelFilterFunc  filter_func,
+                                       gpointer                      user_data,
+                                       GDestroyNotify                user_destroy)
+{
+  gboolean was_filtered, will_be_filtered;
+
+  g_return_if_fail (GTK_IS_FILTER_LIST_MODEL (self));
+  g_return_if_fail (filter_func != NULL || (user_data == NULL && !user_destroy));
+
+  was_filtered = self->filter_func != NULL;
+  will_be_filtered = filter_func != NULL;
+
+  if (!was_filtered && !will_be_filtered)
+    return;
+
+  if (self->user_destroy)
+    self->user_destroy (self->user_data);
+
+  self->filter_func = filter_func;
+  self->user_data = user_data;
+  self->user_destroy = user_destroy;
+  
+  if (!will_be_filtered)
+    {
+      g_clear_pointer (&self->items, gtk_rb_tree_unref);
+    }
+  else if (!was_filtered)
+    {
+      guint i, n_items;
+
+      self->items = gtk_rb_tree_new (FilterNode,
+                                     FilterAugment,
+                                     gtk_filter_list_model_augment,
+                                     NULL, NULL);
+      if (self->model)
+        {
+          n_items = g_list_model_get_n_items (self->model);
+          for (i = 0; i < n_items; i++)
+            {
+              FilterNode *node = gtk_rb_tree_insert_before (self->items, NULL);
+              node->visible = TRUE;
+            }
+        }
+    }
+
+  gtk_filter_list_model_refilter (self);
+
+  if (was_filtered != will_be_filtered)
+    g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_HAS_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 function 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 (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
+    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;
+}
+
+/**
+ * gtk_filter_list_model_has_filter:
+ * @self: a #GtkFilterListModel
+ *
+ * Checks if a filter function is currently set on @self
+ *
+ * Returns: %TRUE if a filter function is set
+ **/
+gboolean
+gtk_filter_list_model_has_filter (GtkFilterListModel *self)
+{
+  g_return_val_if_fail (GTK_IS_FILTER_LIST_MODEL (self), FALSE);
+
+  return self->filter_func != NULL;
+}
+
+/**
+ * gtk_filter_list_model_refilter:
+ * @self: a #GtkFilterListModel
+ *
+ * Causes @self to refilter all items in the model.
+ *
+ * Calling this function is necessary when data used by the filter
+ * function has changed.
+ **/
+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/gtk-list-models/gtkfilterlistmodel.h b/src/gtk-list-models/gtkfilterlistmodel.h
new file mode 100644
index 000000000..f241809f1
--- /dev/null
+++ b/src/gtk-list-models/gtkfilterlistmodel.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright © 2018 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte@gnome.org>
+ */
+
+#ifndef __GTK_FILTER_LIST_MODEL_H__
+#define __GTK_FILTER_LIST_MODEL_H__
+
+
+#include <gio/gio.h>
+#include <gtk/gtk.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)
+
+/**
+ * GtkFilterListModelFilterFunc:
+ * @item: (type GObject): The item that may be filtered
+ * @user_data: user data
+ *
+ * User function that is called to determine if the @item of the original model should be visible.
+ * If it should be visible, this function must return %TRUE. If the model should filter out the
+ * @item, %FALSE must be returned.
+ *
+ * Returns: %TRUE to keep the item around
+ */
+typedef gboolean (* GtkFilterListModelFilterFunc) (gpointer item, gpointer user_data);
+
+GDK_AVAILABLE_IN_ALL
+GtkFilterListModel *    gtk_filter_list_model_new               (GListModel             *model,
+                                                                 GtkFilterListModelFilterFunc filter_func,
+                                                                 gpointer                user_data,
+                                                                 GDestroyNotify          user_destroy);
+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_func   (GtkFilterListModel     *self,
+                                                                 GtkFilterListModelFilterFunc filter_func,
+                                                                 gpointer                user_data,
+                                                                 GDestroyNotify          user_destroy);
+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);
+GDK_AVAILABLE_IN_ALL
+gboolean                gtk_filter_list_model_has_filter        (GtkFilterListModel     *self);
+
+GDK_AVAILABLE_IN_ALL
+void                    gtk_filter_list_model_refilter          (GtkFilterListModel     *self);
+
+G_END_DECLS
+
+#endif /* __GTK_FILTER_LIST_MODEL_H__ */
diff --git a/src/gtk-list-models/gtkrbtree.c b/src/gtk-list-models/gtkrbtree.c
new file mode 100644
index 000000000..989c25a4e
--- /dev/null
+++ b/src/gtk-list-models/gtkrbtree.c
@@ -0,0 +1,798 @@
+/* gtkrbtree.c
+ * Copyright (C) 2000  Red Hat, Inc.,  Jonathan Blandford <jrb@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "gtkrbtreeprivate.h"
+
+/* 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/gtk-list-models/gtkrbtreeprivate.h b/src/gtk-list-models/gtkrbtreeprivate.h
new file mode 100644
index 000000000..45aba5cc2
--- /dev/null
+++ b/src/gtk-list-models/gtkrbtreeprivate.h
@@ -0,0 +1,75 @@
+/* gtkrbtree.h
+ * Copyright (C) 2000  Red Hat, Inc.,  Jonathan Blandford <jrb@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* A Red-Black Tree implementation used specifically by GtkTreeView.
+ */
+#ifndef __GTK_RB_TREE_H__
+#define __GTK_RB_TREE_H__
+
+#include <glib.h>
+
+
+G_BEGIN_DECLS
+
+
+typedef struct _GtkRbTree GtkRbTree;
+
+typedef void            (* GtkRbTreeAugmentFunc)        (GtkRbTree               *tree,
+                                                         gpointer                 node_augment,
+                                                         gpointer                 node,
+                                                         gpointer                 left,
+                                                         gpointer                 right);
+
+GtkRbTree *          gtk_rb_tree_new_for_size           (gsize                    element_size,
+                                                         gsize                    augment_size,
+                                                         GtkRbTreeAugmentFunc     augment_func,
+                                                         GDestroyNotify           clear_func,
+                                                         GDestroyNotify           clear_augment_func);
+#define gtk_rb_tree_new(type, augment_type, augment_func, clear_func, clear_augment_func) \
+  gtk_rb_tree_new_for_size (sizeof (type), sizeof (augment_type), (augment_func), (clear_func), (clear_augment_func))
+
+GtkRbTree *          gtk_rb_tree_ref                    (GtkRbTree               *tree);
+void                 gtk_rb_tree_unref                  (GtkRbTree               *tree);
+
+gpointer             gtk_rb_tree_get_root               (GtkRbTree               *tree);
+gpointer             gtk_rb_tree_get_first              (GtkRbTree               *tree);
+gpointer             gtk_rb_tree_get_last               (GtkRbTree               *tree);
+
+gpointer             gtk_rb_tree_node_get_previous      (gpointer                 node);
+gpointer             gtk_rb_tree_node_get_next          (gpointer                 node);
+gpointer             gtk_rb_tree_node_get_parent        (gpointer                 node);
+gpointer             gtk_rb_tree_node_get_left          (gpointer                 node);
+gpointer             gtk_rb_tree_node_get_right         (gpointer                 node);
+GtkRbTree *          gtk_rb_tree_node_get_tree          (gpointer                 node);
+void                 gtk_rb_tree_node_mark_dirty        (gpointer                 node);
+
+gpointer             gtk_rb_tree_get_augment            (GtkRbTree               *tree,
+                                                         gpointer                 node);
+
+gpointer             gtk_rb_tree_insert_before          (GtkRbTree               *tree,
+                                                         gpointer                 node);
+gpointer             gtk_rb_tree_insert_after           (GtkRbTree               *tree,
+                                                         gpointer                 node);
+void                 gtk_rb_tree_remove                 (GtkRbTree               *tree,
+                                                         gpointer                 node);
+void                 gtk_rb_tree_remove_all             (GtkRbTree               *tree);
+
+
+G_END_DECLS
+
+
+#endif /* __GTK_RB_TREE_H__ */
diff --git a/src/gtk-list-models/gtksortlistmodel.c b/src/gtk-list-models/gtksortlistmodel.c
new file mode 100644
index 000000000..efa1a4141
--- /dev/null
+++ b/src/gtk-list-models/gtksortlistmodel.c
@@ -0,0 +1,562 @@
+/*
+ * Copyright © 2018 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte@gnome.org>
+ */
+
+#include "config.h"
+
+#include "gtksortlistmodel.h"
+
+#include <glib/gi18n-lib.h>
+
+/**
+ * SECTION:gtksortlistmodel
+ * @title: GtkSortListModel
+ * @short_description: A list model that sorts its items
+ * @see_also: #GListModel
+ *
+ * #GtkSortListModel is a list model that takes a list model and
+ * sorts its elements according to a compare function.
+ *
+ * #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_HAS_SORT,
+  PROP_ITEM_TYPE,
+  PROP_MODEL,
+  NUM_PROPERTIES
+};
+
+struct _GtkSortListModel
+{
+  GObject parent_instance;
+
+  GType item_type;
+  GListModel *model;
+  GCompareDataFunc sort_func;
+  gpointer user_data;
+  GDestroyNotify user_destroy;
+
+  GSequence *sorted; /* NULL if sort_func == NULL */
+  GSequence *unsorted; /* NULL if sort_func == NULL */
+};
+
+struct _GtkSortListModelClass
+{
+  GObjectClass parent_class;
+};
+
+static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
+
+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;
+
+  if (self->sorted)
+    return g_sequence_get_length (self->sorted);
+
+  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;
+
+  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;
+
+  return g_object_ref (g_sequence_get (iter));
+}
+
+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++)
+    {
+      GSequenceIter *sorted_iter;
+      GSequenceIter *next;
+
+      next = g_sequence_iter_next (unsorted_iter);
+
+      sorted_iter = g_sequence_get (unsorted_iter);
+      pos = g_sequence_iter_get_position (sorted_iter);
+      start = MIN (start, pos);
+      end = MIN (end, length_before - i - 1 - pos);
+
+      g_sequence_remove (sorted_iter);
+      g_sequence_remove (unsorted_iter);
+
+      unsorted_iter = next;
+    }
+
+  *unmodified_start = start;
+  *unmodified_end = end;
+}
+
+static void
+gtk_sort_list_model_add_items (GtkSortListModel *self,
+                               guint             position,
+                               guint             n_items,
+                               guint            *unmodified_start,
+                               guint            *unmodified_end)
+{
+  GSequenceIter *unsorted_iter, *sorted_iter;
+  guint i, pos, start, end, length_before;
+
+  unsorted_iter = 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++)
+    {
+      gpointer item = g_list_model_get_item (self->model, position + i);
+      sorted_iter = g_sequence_insert_sorted (self->sorted, item, self->sort_func, self->user_data);
+      g_sequence_insert_before (unsorted_iter, sorted_iter);
+      if (unmodified_start != NULL || unmodified_end != NULL)
+        {
+          pos = g_sequence_iter_get_position (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;
+
+    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_HAS_SORT:
+      g_value_set_boolean (value, self->sort_func != NULL);
+      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_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_dispose (GObject *object)
+{
+  GtkSortListModel *self = GTK_SORT_LIST_MODEL (object);
+
+  gtk_sort_list_model_clear_model (self);
+  if (self->user_destroy)
+    self->user_destroy (self->user_data);
+  self->sort_func = NULL;
+  self->user_data = NULL;
+  self->user_destroy = NULL;
+
+  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:has-sort:
+   *
+   * If a sort function is set for this model
+   */
+  properties[PROP_HAS_SORT] =
+      g_param_spec_boolean ("has-sort",
+                            N_("has sort"),
+                            N_("If a sort function is set for this model"),
+                            FALSE,
+                            G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY);
+
+  /**
+   * GtkSortListModel:item-type:
+   *
+   * The #GType for items of this model
+   */
+  properties[PROP_ITEM_TYPE] =
+      g_param_spec_gtype ("item-type",
+                          N_("Item type"),
+                          N_("The type of items of this list"),
+                          G_TYPE_OBJECT,
+                          G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY);
+
+  /**
+   * GtkSortListModel:model:
+   *
+   * The model being sorted
+   */
+  properties[PROP_MODEL] =
+      g_param_spec_object ("model",
+                           N_("Model"),
+                           N_("The model being sorted"),
+                           G_TYPE_LIST_MODEL,
+                           G_PARAM_STATIC_STRINGS | G_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
+ * @sort_func: (allow-none): sort function or %NULL to not sort items
+ * @user_data: user data passed to @sort_func
+ * @user_destroy: destroy notifier for @user_data
+ *
+ * Creates a new sort list model that uses the @sort_func to sort @model.
+ *
+ * Returns: a new #GtkSortListModel
+ **/
+GtkSortListModel *
+gtk_sort_list_model_new (GListModel       *model,
+                         GCompareDataFunc  sort_func,
+                         gpointer          user_data,
+                         GDestroyNotify    user_destroy)
+{
+  GtkSortListModel *result;
+
+  g_return_val_if_fail (G_IS_LIST_MODEL (model), NULL);
+
+  result = g_object_new (GTK_TYPE_SORT_LIST_MODEL,
+                         "item-type", g_list_model_get_item_type (model),
+                         "model", model,
+                         NULL);
+
+  if (sort_func)
+    gtk_sort_list_model_set_sort_func (result, sort_func, user_data, user_destroy);
+
+  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->sort_func || self->model == NULL)
+    return;
+
+  self->sorted = g_sequence_new (g_object_unref);
+  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_sort_func:
+ * @self: a #GtkSortListModel
+ * @sort_func: (allow-none): sort function or %NULL to not sort items
+ * @user_data: user data passed to @sort_func
+ * @user_destroy: destroy notifier for @user_data
+ *
+ * Sets the function used to sort items. The function will be called for every
+ * item and must return an integer less than, equal to, or greater than zero if
+ * for two items from the model if the first item is considered to be respectively
+ * less than, equal to, or greater than the second.
+ **/
+void
+gtk_sort_list_model_set_sort_func (GtkSortListModel *self,
+                                   GCompareDataFunc  sort_func,
+                                   gpointer          user_data,
+                                   GDestroyNotify    user_destroy)
+{
+  guint n_items;
+
+  g_return_if_fail (GTK_IS_SORT_LIST_MODEL (self));
+  g_return_if_fail (sort_func != NULL || (user_data == NULL && !user_destroy));
+
+  if (!sort_func && !self->sort_func)
+    return;
+
+  if (self->user_destroy)
+    self->user_destroy (self->user_data);
+
+  g_clear_pointer (&self->unsorted, g_sequence_free);
+  g_clear_pointer (&self->sorted, g_sequence_free);
+  self->sort_func = sort_func;
+  self->user_data = user_data;
+  self->user_destroy = user_destroy;
+  
+    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_HAS_SORT]);
+}
+
+/**
+ * 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;
+}
+
+/**
+ * gtk_sort_list_model_has_sort:
+ * @self: a #GtkSortListModel
+ *
+ * Checks if a sort function is currently set on @self
+ *
+ * Returns: %TRUE if a sort function is set
+ **/
+gboolean
+gtk_sort_list_model_has_sort (GtkSortListModel *self)
+{
+  g_return_val_if_fail (GTK_IS_SORT_LIST_MODEL (self), FALSE);
+
+  return self->sort_func != NULL;
+}
+
+/**
+ * gtk_sort_list_model_resort:
+ * @self: a #GtkSortListModel
+ *
+ * Causes @self to resort all items in the model.
+ *
+ * Calling this function is necessary when data used by the sort
+ * function has changed.
+ **/
+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, self->sort_func, self->user_data);
+
+  g_list_model_items_changed (G_LIST_MODEL (self), 0, n_items, n_items);
+}
+
diff --git a/src/gtk-list-models/gtksortlistmodel.h b/src/gtk-list-models/gtksortlistmodel.h
new file mode 100644
index 000000000..c1da26842
--- /dev/null
+++ b/src/gtk-list-models/gtksortlistmodel.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright © 2018 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte@gnome.org>
+ */
+
+#ifndef __GTK_SORT_LIST_MODEL_H__
+#define __GTK_SORT_LIST_MODEL_H__
+
+
+#include <gio/gio.h>
+#include <gtk/gtk.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,
+                                                                 GCompareDataFunc        sort_func,
+                                                                 gpointer                user_data,
+                                                                 GDestroyNotify          user_destroy);
+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_sort_func       (GtkSortListModel       *self,
+                                                                 GCompareDataFunc        sort_func,
+                                                                 gpointer                user_data,
+                                                                 GDestroyNotify          user_destroy);
+GDK_AVAILABLE_IN_ALL
+gboolean                gtk_sort_list_model_has_sort            (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);
+
+GDK_AVAILABLE_IN_ALL
+void                    gtk_sort_list_model_resort              (GtkSortListModel       *self);
+
+G_END_DECLS
+
+#endif /* __GTK_SORT_LIST_MODEL_H__ */
diff --git a/src/gtk-list-models/meson.build b/src/gtk-list-models/meson.build
new file mode 100644
index 000000000..e880d15cf
--- /dev/null
+++ b/src/gtk-list-models/meson.build
@@ -0,0 +1,8 @@
+phosh_gtk_list_models_sources = [
+  'gtk-list-models/gtkfilterlistmodel.c',
+  'gtk-list-models/gtkfilterlistmodel.h',
+  'gtk-list-models/gtkrbtree.c',
+  'gtk-list-models/gtkrbtreeprivate.h',
+  'gtk-list-models/gtksortlistmodel.c',
+  'gtk-list-models/gtksortlistmodel.h',
+]
\ No newline at end of file
diff --git a/src/meson.build b/src/meson.build
index bc1096493..535eac518 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -3,6 +3,7 @@ subdir('monitor')
 subdir('osk')
 subdir('wwan')
 subdir('settings')
+subdir('gtk-list-models')
 
 phosh_resources = gnome.compile_resources(
   'phosh-resources',
@@ -34,6 +35,7 @@ libphosh_sources = [
   'favorites.h',
   phosh_resources,
   wl_proto_sources,
+  phosh_gtk_list_models_sources,
 ]
 
 phosh_sources = [
-- 
GitLab