[gnome-todo] list-model-filter: Introduce GtdListModelFilter



commit a01863e22725569914362058d5ed37204583f00d
Author: Georges Basile Stavracas Neto <georges stavracas gmail com>
Date:   Sun Sep 9 15:43:00 2018 -0300

    list-model-filter: Introduce GtdListModelFilter
    
    It is basically a copy of DzlListModelFilter, but since I
    suspect I'll add more stuff in the future, I copied it.

 src/contrib/gtd-list-model-filter.c | 502 ++++++++++++++++++++++++++++++++++++
 src/contrib/gtd-list-model-filter.h |  44 ++++
 src/meson.build                     |   1 +
 tests/meson.build                   |  44 ++++
 tests/test-model-filter.c           | 208 +++++++++++++++
 5 files changed, 799 insertions(+)
---
diff --git a/src/contrib/gtd-list-model-filter.c b/src/contrib/gtd-list-model-filter.c
new file mode 100644
index 0000000..9e95d01
--- /dev/null
+++ b/src/contrib/gtd-list-model-filter.c
@@ -0,0 +1,502 @@
+/* gtd-list-model-filter.c
+ *
+ * Copyright (C) 2016 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "GtdListModelFilter"
+
+#include "gtd-debug.h"
+#include "gtd-list-model-filter.h"
+#include "gtd-task-list.h"
+
+typedef struct
+{
+  GSequenceIter      *child_iter;
+  GSequenceIter      *filter_iter;
+} GtdListModelFilterItem;
+
+typedef struct
+{
+  /* The list we are filtering */
+  GListModel         *child_model;
+
+  /*
+   * Both sequences point to the same GtdListModelFilterItem which
+   * contains cross-referencing stable GSequenceIter pointers.
+   * The child_seq is considered the "owner" and used to release
+   * allocated resources.
+   */
+  GSequence          *child_seq;
+  GSequence          *filter_seq;
+
+  /*
+   * Typical set of callback/closure/free function pointers and data.
+   * Called for child items to determine visibility state.
+   */
+  GtdListModelFilterFunc filter_func;
+  gpointer           filter_func_data;
+  GDestroyNotify     filter_func_data_destroy;
+
+  /*
+   * If set, we will not emit items-changed. This is useful during
+   * invalidation so that we can do a single emission for all items
+   * that have changed.
+   */
+  guint              supress_items_changed : 1;
+} GtdListModelFilterPrivate;
+
+struct _GtdListModelFilter
+{
+  GObject parent_instance;
+};
+
+static void list_model_iface_init (GListModelInterface *iface);
+
+G_DEFINE_TYPE_EXTENDED (GtdListModelFilter, gtd_list_model_filter, G_TYPE_OBJECT, 0,
+                        G_ADD_PRIVATE (GtdListModelFilter)
+                        G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL,
+                                               list_model_iface_init))
+
+enum {
+  PROP_0,
+  PROP_CHILD_MODEL,
+  N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+static guint signal_id;
+
+static void
+gtd_list_model_filter_item_free (gpointer data)
+{
+  GtdListModelFilterItem *item = data;
+
+  g_clear_pointer (&item->filter_iter, g_sequence_remove);
+  item->child_iter = NULL;
+  g_slice_free (GtdListModelFilterItem, item);
+}
+
+static gboolean
+gtd_list_model_filter_default_filter_func (GObject  *item,
+                                           gpointer  user_data)
+{
+  return TRUE;
+}
+
+/*
+ * Locates the next item in the filter sequence starting from
+ * the cross-reference found at @iter. If none are found, the
+ * end_iter for the filter sequence is returned.
+ *
+ * This returns an iter in the filter_sequence, not the child_seq.
+ *
+ * Returns: a #GSequenceIter from the filter sequence.
+ */
+static GSequenceIter *
+find_next_visible_filter_iter (GtdListModelFilter *self,
+                               GSequenceIter      *iter)
+{
+  GtdListModelFilterPrivate *priv = gtd_list_model_filter_get_instance_private (self);
+
+  g_assert (GTD_IS_LIST_MODEL_FILTER (self));
+  g_assert (iter != NULL);
+
+  for (; !g_sequence_iter_is_end (iter); iter = g_sequence_iter_next (iter))
+    {
+      GtdListModelFilterItem *item = g_sequence_get (iter);
+
+      g_assert (item->child_iter == iter);
+      g_assert (item->filter_iter == NULL ||
+                g_sequence_iter_get_sequence (item->filter_iter) == priv->filter_seq);
+
+      if (item->filter_iter != NULL)
+        return item->filter_iter;
+    }
+
+  return g_sequence_get_end_iter (priv->filter_seq);
+}
+
+static void
+child_model_items_changed (GtdListModelFilter *self,
+                           guint               position,
+                           guint               n_removed,
+                           guint               n_added,
+                           GListModel         *child_model)
+{
+  GtdListModelFilterPrivate *priv = gtd_list_model_filter_get_instance_private (self);
+  gboolean unblocked;
+  guint i;
+
+  g_assert (GTD_IS_LIST_MODEL_FILTER (self));
+  g_assert (G_IS_LIST_MODEL (child_model));
+  g_assert (priv->child_model == child_model);
+  g_assert (position <= (guint)g_sequence_get_length (priv->child_seq));
+  g_assert ((g_sequence_get_length (priv->child_seq) - n_removed + n_added) ==
+            g_list_model_get_n_items (child_model));
+
+  unblocked = !priv->supress_items_changed;
+
+  if (n_removed > 0)
+    {
+      GSequenceIter *iter = g_sequence_get_iter_at_pos (priv->child_seq, position);
+      gint first_position = -1;
+      guint count = 0;
+
+      g_assert (!g_sequence_iter_is_end (iter));
+
+      /* Small shortcut when all items are removed */
+      if (n_removed == (guint)g_sequence_get_length (priv->child_seq))
+        {
+          g_sequence_remove_range (g_sequence_get_begin_iter (priv->child_seq),
+                                   g_sequence_get_end_iter (priv->child_seq));
+          g_assert (g_sequence_is_empty (priv->child_seq));
+          g_assert (g_sequence_is_empty (priv->filter_seq));
+          goto add_new_items;
+        }
+
+      for (i = 0; i < n_removed; i++)
+        {
+          GSequenceIter *to_remove = iter;
+          GtdListModelFilterItem *item = g_sequence_get (iter);
+
+          g_assert (item != NULL);
+          g_assert (item->child_iter == iter);
+          g_assert (item->filter_iter == NULL ||
+                    g_sequence_iter_get_sequence (item->filter_iter) == priv->filter_seq);
+
+          /* If this is visible, we need to notify about removal */
+          if (unblocked && item->filter_iter != NULL)
+            {
+              if (first_position < 0)
+                first_position = g_sequence_iter_get_position (item->filter_iter);
+
+              count++;
+            }
+
+          /* Fetch the next while the iter is still valid */
+          iter = g_sequence_iter_next (iter);
+
+          /* Cascades into also removing from filter_seq. */
+          g_sequence_remove (to_remove);
+        }
+
+      if (unblocked && first_position >= 0)
+        g_list_model_items_changed (G_LIST_MODEL (self), first_position, count, 0);
+    }
+
+add_new_items:
+
+  if (n_added > 0)
+    {
+      GSequenceIter *iter = g_sequence_get_iter_at_pos (priv->child_seq, position);
+      GSequenceIter *filter_iter = find_next_visible_filter_iter (self, iter);
+      guint filter_position = g_sequence_iter_get_position (filter_iter);
+      guint count = 0;
+
+      /* Walk backwards to insert items into the filter list so that
+       * we can use the same filter_position for each items-changed
+       * signal emission.
+       */
+      for (i = position + n_added; i > position; i--)
+        {
+          GtdListModelFilterItem *item;
+          g_autoptr (GObject) instance = NULL;
+
+          item = g_slice_new0 (GtdListModelFilterItem);
+          item->filter_iter = NULL;
+          item->child_iter = g_sequence_insert_before (iter, item);
+
+          instance = g_list_model_get_item (child_model, i - 1);
+          g_assert (G_IS_OBJECT (instance));
+
+          /* Check if this item is visible */
+          if (priv->filter_func (instance, priv->filter_func_data))
+            {
+              item->filter_iter = g_sequence_insert_before (filter_iter, item);
+              count++;
+
+              /* Use this in the future for relative positioning */
+              filter_iter = item->filter_iter;
+            }
+
+          /* Insert next item before this */
+          iter = item->child_iter;
+        }
+
+      if (unblocked && count)
+        g_list_model_items_changed (G_LIST_MODEL (self), filter_position, 0, count);
+    }
+
+  g_assert ((guint)g_sequence_get_length (priv->child_seq) ==
+            g_list_model_get_n_items (child_model));
+}
+
+static void
+gtd_list_model_filter_finalize (GObject *object)
+{
+  GtdListModelFilter *self = (GtdListModelFilter *)object;
+  GtdListModelFilterPrivate *priv = gtd_list_model_filter_get_instance_private (self);
+
+  g_clear_pointer (&priv->child_seq, g_sequence_free);
+  g_clear_pointer (&priv->filter_seq, g_sequence_free);
+
+  if (priv->filter_func_data_destroy)
+    {
+      g_clear_pointer (&priv->filter_func_data, priv->filter_func_data_destroy);
+      priv->filter_func_data_destroy = NULL;
+    }
+
+  g_clear_object (&priv->child_model);
+
+  G_OBJECT_CLASS (gtd_list_model_filter_parent_class)->finalize (object);
+}
+
+static void
+gtd_list_model_filter_get_property (GObject    *object,
+                                    guint       prop_id,
+                                    GValue     *value,
+                                    GParamSpec *pspec)
+{
+  GtdListModelFilter *self = GTD_LIST_MODEL_FILTER (object);
+
+  switch (prop_id)
+    {
+    case PROP_CHILD_MODEL:
+      g_value_set_object (value, gtd_list_model_filter_get_child_model (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gtd_list_model_filter_class_init (GtdListModelFilterClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = gtd_list_model_filter_finalize;
+  object_class->get_property = gtd_list_model_filter_get_property;
+
+  properties [PROP_CHILD_MODEL] =
+    g_param_spec_object ("child-model",
+                         "Child Model",
+                         "The child model being filtered.",
+                         G_TYPE_LIST_MODEL,
+                         (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  signal_id = g_signal_lookup ("items-changed", GTD_TYPE_LIST_MODEL_FILTER);
+}
+
+static void
+gtd_list_model_filter_init (GtdListModelFilter *self)
+{
+  GtdListModelFilterPrivate *priv = gtd_list_model_filter_get_instance_private (self);
+
+  priv->filter_func = gtd_list_model_filter_default_filter_func;
+  priv->child_seq = g_sequence_new (gtd_list_model_filter_item_free);
+  priv->filter_seq = g_sequence_new (NULL);
+}
+
+static GType
+gtd_list_model_filter_get_item_type (GListModel *model)
+{
+  GtdListModelFilter *self = (GtdListModelFilter *)model;
+  GtdListModelFilterPrivate *priv = gtd_list_model_filter_get_instance_private (self);
+
+  g_assert (GTD_IS_LIST_MODEL_FILTER (self));
+
+  return g_list_model_get_item_type (priv->child_model);
+}
+
+static guint
+gtd_list_model_filter_get_n_items (GListModel *model)
+{
+  GtdListModelFilter *self = (GtdListModelFilter *)model;
+  GtdListModelFilterPrivate *priv = gtd_list_model_filter_get_instance_private (self);
+
+  g_assert (GTD_IS_LIST_MODEL_FILTER (self));
+  g_assert (priv->filter_seq != NULL);
+
+  return g_sequence_get_length (priv->filter_seq);
+}
+
+static gpointer
+gtd_list_model_filter_get_item (GListModel *model,
+                                guint       position)
+{
+  GtdListModelFilter *self = (GtdListModelFilter *)model;
+  GtdListModelFilterPrivate *priv = gtd_list_model_filter_get_instance_private (self);
+  GtdListModelFilterItem *item;
+  GSequenceIter *iter;
+  guint child_position;
+
+  g_assert (GTD_IS_LIST_MODEL_FILTER (self));
+
+  iter = g_sequence_get_iter_at_pos (priv->filter_seq, position);
+  if (g_sequence_iter_is_end (iter))
+    return NULL;
+
+  item = g_sequence_get (iter);
+  g_assert (item != NULL);
+  g_assert (item->filter_iter == iter);
+  g_assert (item->child_iter != NULL);
+  g_assert (g_sequence_iter_get_sequence (item->child_iter) == priv->child_seq);
+
+  child_position = g_sequence_iter_get_position (item->child_iter);
+
+  return g_list_model_get_item (priv->child_model, child_position);
+}
+
+static void
+list_model_iface_init (GListModelInterface *iface)
+{
+  iface->get_item_type = gtd_list_model_filter_get_item_type;
+  iface->get_n_items = gtd_list_model_filter_get_n_items;
+  iface->get_item = gtd_list_model_filter_get_item;
+}
+
+GtdListModelFilter *
+gtd_list_model_filter_new (GListModel *child_model)
+{
+  GtdListModelFilter *ret;
+  GtdListModelFilterPrivate *priv;
+
+  g_return_val_if_fail (G_IS_LIST_MODEL (child_model), NULL);
+
+  ret = g_object_new (GTD_TYPE_LIST_MODEL_FILTER, NULL);
+  priv = gtd_list_model_filter_get_instance_private (ret);
+  priv->child_model = g_object_ref (child_model);
+
+  g_signal_connect_object (child_model,
+                           "items-changed",
+                           G_CALLBACK (child_model_items_changed),
+                           ret,
+                           G_CONNECT_SWAPPED);
+
+  gtd_list_model_filter_invalidate (ret);
+
+  return ret;
+}
+
+/**
+ * gtd_list_model_filter_get_child_model:
+ * @self: A #GtdListModelFilter
+ *
+ * Gets the child model that is being filtered.
+ *
+ * Returns: (transfer none): A #GListModel.
+ */
+GListModel *
+gtd_list_model_filter_get_child_model (GtdListModelFilter *self)
+{
+  GtdListModelFilterPrivate *priv = gtd_list_model_filter_get_instance_private (self);
+
+  g_return_val_if_fail (GTD_IS_LIST_MODEL_FILTER (self), NULL);
+
+  return priv->child_model;
+}
+
+void
+gtd_list_model_filter_invalidate (GtdListModelFilter *self)
+{
+  GtdListModelFilterPrivate *priv = gtd_list_model_filter_get_instance_private (self);
+  guint n_items;
+
+  g_return_if_fail (GTD_IS_LIST_MODEL_FILTER (self));
+
+  /* We block emission while in invalidate so that we can use
+   * a single larger items-changed rather lots of small emissions.
+   */
+  priv->supress_items_changed = TRUE;
+
+  /* First determine how many items we need to synthesize as a removal */
+  n_items = g_sequence_get_length (priv->filter_seq);
+
+  /*
+   * If we have a child store, we want to rebuild our list of items
+   * from scratch, so just remove everything.
+   */
+  if (!g_sequence_is_empty (priv->child_seq))
+    g_sequence_remove_range (g_sequence_get_begin_iter (priv->child_seq),
+                             g_sequence_get_end_iter (priv->child_seq));
+
+  g_assert (g_sequence_is_empty (priv->child_seq));
+  g_assert (g_sequence_is_empty (priv->filter_seq));
+  g_assert (!priv->child_model || G_IS_LIST_MODEL (priv->child_model));
+
+  /*
+   * Now add the new items by synthesizing the addition of all the
+   * items in the list.
+   */
+  if (priv->child_model != NULL)
+    {
+      guint child_n_items;
+
+      /*
+       * Now add all the items as one shot to our list so that
+       * we get populate our sequence and filter sequence.
+       */
+      child_n_items = g_list_model_get_n_items (priv->child_model);
+      child_model_items_changed (self, 0, 0, child_n_items, priv->child_model);
+
+      g_assert ((guint)g_sequence_get_length (priv->child_seq) == child_n_items);
+      g_assert ((guint)g_sequence_get_length (priv->filter_seq) <= child_n_items);
+    }
+
+  priv->supress_items_changed = FALSE;
+
+  /* Now that we've updated our sequences, notify of all the changes
+   * as a single series of updates to the consumers.
+   */
+  if (n_items > 0 || !g_sequence_is_empty (priv->filter_seq))
+    g_list_model_items_changed (G_LIST_MODEL (self),
+                                0,
+                                n_items,
+                                g_sequence_get_length (priv->filter_seq));
+}
+
+void
+gtd_list_model_filter_set_filter_func (GtdListModelFilter     *self,
+                                       GtdListModelFilterFunc  filter_func,
+                                       gpointer                filter_func_data,
+                                       GDestroyNotify          filter_func_data_destroy)
+{
+  GtdListModelFilterPrivate *priv = gtd_list_model_filter_get_instance_private (self);
+
+  g_return_if_fail (GTD_IS_LIST_MODEL_FILTER (self));
+  g_return_if_fail (filter_func || (!filter_func_data && !filter_func_data_destroy));
+
+  if (priv->filter_func_data_destroy != NULL)
+    g_clear_pointer (&priv->filter_func_data, priv->filter_func_data_destroy);
+
+  if (filter_func != NULL)
+    {
+      priv->filter_func = filter_func;
+      priv->filter_func_data = filter_func_data;
+      priv->filter_func_data_destroy = filter_func_data_destroy;
+    }
+  else
+    {
+      priv->filter_func = gtd_list_model_filter_default_filter_func;
+      priv->filter_func_data = NULL;
+      priv->filter_func_data_destroy = NULL;
+    }
+
+  gtd_list_model_filter_invalidate (self);
+}
diff --git a/src/contrib/gtd-list-model-filter.h b/src/contrib/gtd-list-model-filter.h
new file mode 100644
index 0000000..f5c2a42
--- /dev/null
+++ b/src/contrib/gtd-list-model-filter.h
@@ -0,0 +1,44 @@
+/* gtd-list-model-filter.h
+ *
+ * Copyright © 2016 Christian Hergert <christian hergert me>
+ *             2018 Georges Basile Stavracas Neto <gbsneto gnome org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define GTD_TYPE_LIST_MODEL_FILTER (gtd_list_model_filter_get_type())
+
+typedef gboolean (*GtdListModelFilterFunc) (GObject  *object,
+                                            gpointer  user_data);
+
+G_DECLARE_FINAL_TYPE (GtdListModelFilter, gtd_list_model_filter, GTD, LIST_MODEL_FILTER, GObject)
+
+GtdListModelFilter*  gtd_list_model_filter_new                   (GListModel         *child_model);
+
+GListModel*          gtd_list_model_filter_get_child_model       (GtdListModelFilter *self);
+
+void                 gtd_list_model_filter_invalidate            (GtdListModelFilter *self);
+
+void                 gtd_list_model_filter_set_filter_func       (GtdListModelFilter     *self,
+                                                                  GtdListModelFilterFunc  filter_func,
+                                                                  gpointer                filter_func_data,
+                                                                  GDestroyNotify          
filter_func_data_destroy);
+
+G_END_DECLS
diff --git a/src/meson.build b/src/meson.build
index 4e3554d..1d67cc8 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -30,6 +30,7 @@ install_headers(headers, subdir: meson.project_name())
 ################
 
 sources = files(
+  'contrib/gtd-list-model-filter.c',
   'contrib/gtd-list-store.c',
   'engine/gtd-manager.c',
   'engine/gtd-plugin-manager.c',
diff --git a/tests/meson.build b/tests/meson.build
index e98ddd4..f40aacf 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -1,3 +1,47 @@
+################
+# Static tests #
+################
+
+static_test_env = [
+  'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()),
+  'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()),
+  'G_DEBUG=gc-friendly',
+  'GSETTINGS_BACKEND=memory',
+  'PYTHONDONTWRITEBYTECODE=yes',
+  'MALLOC_CHECK_=2',
+  'MALLOC_PERTURB_=$((${RANDOM:-256} % 256))',
+]
+
+static_test_cflags = [
+  '-fPIE',
+  '-DTEST_DATA_DIR="@0@/data"'.format(meson.current_source_dir()),
+]
+
+static_test_link_args = ['-fPIC']
+
+static_tests = [
+  'test-model-filter',
+]
+
+foreach static_test : static_tests
+
+  source = ['@0@.c'.format(static_test)]
+
+  static_test_program = executable(
+              static_test,
+                   source,
+                  c_args : static_test_cflags,
+               link_args : static_test_link_args,
+            dependencies : gnome_todo_deps,
+               link_with : libgtd,
+     include_directories : [ src_inc ],
+  )
+
+  test(static_test, static_test_program, env: static_test_env)
+endforeach
+
+
+
 #####################
 # Interactive tests #
 #####################
diff --git a/tests/test-model-filter.c b/tests/test-model-filter.c
new file mode 100644
index 0000000..ecfaaa6
--- /dev/null
+++ b/tests/test-model-filter.c
@@ -0,0 +1,208 @@
+#include "contrib/gtd-list-model-filter.h"
+#include <math.h>
+#include <string.h>
+
+#define TEST_TYPE_ITEM (test_item_get_type())
+
+struct _TestItem
+{
+  GObject p;
+  guint n;
+};
+
+G_DECLARE_FINAL_TYPE (TestItem, test_item, TEST, ITEM, GObject)
+G_DEFINE_TYPE (TestItem, test_item, G_TYPE_OBJECT)
+
+static void
+test_item_class_init (TestItemClass *klass)
+{
+}
+
+static void
+test_item_init (TestItem *self)
+{
+}
+
+static TestItem *
+test_item_new (guint n)
+{
+  TestItem *item;
+
+  item = g_object_new (TEST_TYPE_ITEM, NULL);
+  item->n = n;
+
+  return item;
+}
+
+static gboolean
+filter_func1 (GObject  *object,
+              gpointer  user_data)
+{
+  return (TEST_ITEM (object)->n & 1) == 0;
+}
+
+static gboolean
+filter_func2 (GObject  *object,
+              gpointer  user_data)
+{
+  return (TEST_ITEM (object)->n & 1) == 1;
+}
+
+static void
+test_basic (void)
+{
+  GListStore *model;
+  GtdListModelFilter *filter;
+  TestItem *item;
+  guint i;
+
+  model = g_list_store_new (TEST_TYPE_ITEM);
+  g_assert (model);
+
+  filter = gtd_list_model_filter_new (G_LIST_MODEL (model));
+  g_assert (filter);
+
+  /* Test requesting past boundary */
+  g_assert_null (g_list_model_get_item (G_LIST_MODEL (filter), 0));
+  g_assert_null (g_list_model_get_item (G_LIST_MODEL (filter), 1));
+
+  for (i = 0; i < 1000; i++)
+    {
+      g_autoptr (TestItem) val = test_item_new (i);
+
+      g_list_store_append (model, val);
+    }
+
+  /* Test requesting past boundary */
+  g_assert_null (g_list_model_get_item (G_LIST_MODEL (filter), 1000));
+
+  g_assert_cmpint (1000, ==, g_list_model_get_n_items (G_LIST_MODEL (model)));
+  g_assert_cmpint (1000, ==, g_list_model_get_n_items (G_LIST_MODEL (filter)));
+
+  g_assert_cmpint (1000, ==, g_list_model_get_n_items (G_LIST_MODEL (filter)));
+  gtd_list_model_filter_set_filter_func (filter, filter_func1, NULL, NULL);
+  g_assert_cmpint (500, ==, g_list_model_get_n_items (G_LIST_MODEL (filter)));
+
+  for (i = 0; i < 500; i++)
+    {
+      g_autoptr (TestItem) ele = g_list_model_get_item (G_LIST_MODEL (filter), i);
+
+      g_assert_nonnull (ele);
+      g_assert (TEST_IS_ITEM (ele));
+      g_assert (filter_func1 (G_OBJECT (ele), NULL));
+
+      /* filter_func1 filters odd numbers out. The even numbers that
+       * weren't filtered should keep their order.
+       */
+      g_assert (ele->n == i * 2);
+    }
+
+  for (i = 0; i < 1000; i += 2)
+    g_list_store_remove (model, 998 - i);
+
+  g_assert_cmpint (500, ==, g_list_model_get_n_items (G_LIST_MODEL (model)));
+  g_assert_cmpint (0, ==, g_list_model_get_n_items (G_LIST_MODEL (filter)));
+
+  gtd_list_model_filter_set_filter_func (filter, NULL, NULL, NULL);
+  g_assert_cmpint (500, ==, g_list_model_get_n_items (G_LIST_MODEL (filter)));
+
+  gtd_list_model_filter_set_filter_func (filter, filter_func2, NULL, NULL);
+  g_assert_cmpint (500, ==, g_list_model_get_n_items (G_LIST_MODEL (filter)));
+
+  {
+    g_autoptr (TestItem) freeme = test_item_new (1001);
+    g_list_store_append (model, freeme);
+  }
+
+  for (i = 0; i < 500; i++)
+    g_list_store_remove (model, 0);
+
+  g_assert_cmpint (1, ==, g_list_model_get_n_items (G_LIST_MODEL (model)));
+  g_assert_cmpint (1, ==, g_list_model_get_n_items (G_LIST_MODEL (filter)));
+
+  gtd_list_model_filter_set_filter_func (filter, NULL, NULL, NULL);
+  g_assert_cmpint (1, ==, g_list_model_get_n_items (G_LIST_MODEL (model)));
+  g_assert_cmpint (1, ==, g_list_model_get_n_items (G_LIST_MODEL (filter)));
+
+  item = g_list_model_get_item (G_LIST_MODEL (filter), 0);
+  g_assert (item);
+  g_assert (TEST_IS_ITEM (item));
+  g_assert_cmpint (item->n, ==, 1001);
+  g_clear_object (&item);
+
+  g_clear_object (&model);
+  g_clear_object (&filter);
+}
+
+static guint last_n_added = 0;
+static guint last_n_removed = 0;
+static guint last_changed_position = 0;
+
+static void
+model_items_changed_cb (GtdListModelFilter *filter,
+                        guint               position,
+                        guint               n_removed,
+                        guint               n_added,
+                        GListModel         *model)
+{
+  last_n_added = n_added;
+  last_n_removed = n_removed;
+  last_changed_position = position;
+}
+
+
+static void
+filter_items_changed_cb (GtdListModelFilter *filter,
+                         guint               position,
+                         guint               n_removed,
+                         guint               n_added,
+                         GListModel         *model)
+{
+  g_assert_cmpint (n_added, ==, last_n_added);
+  g_assert_cmpint (n_removed, ==, last_n_removed);
+  g_assert_cmpint (position, ==, last_changed_position);
+
+}
+
+static void
+test_items_changed (void)
+{
+  GtdListModelFilter *filter;
+  GListStore *model;
+  guint i;
+
+  model = g_list_store_new (TEST_TYPE_ITEM);
+  g_assert (model);
+
+  g_signal_connect (model, "items-changed", G_CALLBACK (model_items_changed_cb), NULL);
+
+  filter = gtd_list_model_filter_new (G_LIST_MODEL (model));
+  g_assert (filter);
+
+  g_signal_connect_after (filter, "items-changed", G_CALLBACK (filter_items_changed_cb), model);
+
+  for (i = 0; i < 100; i++)
+    {
+      g_autoptr (TestItem) val = test_item_new (i);
+      g_list_store_append (model, val);
+    }
+
+  g_assert_cmpint (100, ==, g_list_model_get_n_items (G_LIST_MODEL (model)));
+  g_assert_cmpint (100, ==, g_list_model_get_n_items (G_LIST_MODEL (filter)));
+
+  for (i = 0; i < 100; i++)
+    g_list_store_remove (model, 0);
+
+  g_clear_object (&model);
+  g_clear_object (&filter);
+}
+
+gint
+main (gint argc,
+      gchar *argv[])
+{
+  g_test_init (&argc, &argv, NULL);
+  g_test_add_func ("/Dazzle/ListModelFilter/basic", test_basic);
+  g_test_add_func ("/Dazzle/ListModelFilter/items-changed", test_items_changed);
+  return g_test_run ();
+}


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