[gnome-todo/gbsneto/task-model: 4/14] list-model-filter: Introduce GtdListModelFilter
- From: Georges Basile Stavracas Neto <gbsneto src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-todo/gbsneto/task-model: 4/14] list-model-filter: Introduce GtdListModelFilter
- Date: Wed, 12 Sep 2018 11:54:51 +0000 (UTC)
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]