[gnome-todo] list-model-sort: Introduce sorting model
- From: Georges Basile Stavracas Neto <gbsneto src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-todo] list-model-sort: Introduce sorting model
- Date: Wed, 12 Sep 2018 20:08:22 +0000 (UTC)
commit 92e8e9afa0d313ba16c97560f7d831c6fc2ed926
Author: Georges Basile Stavracas Neto <georges stavracas gmail com>
Date: Wed Sep 12 13:48:03 2018 -0300
list-model-sort: Introduce sorting model
This is a work of art. It contains all the regular model
optimizations like O(1) sequential g_list_model_get_item(),
almost no O(n*log n) scenarios, etc.
meson.build | 1 +
src/contrib/gtd-list-model-sort.c | 504 +++++++++++++++++++++++++++++++++++
src/contrib/gtd-list-model-sort.h | 46 ++++
src/gnome-todo.h | 3 +
src/meson.build | 7 +
tests/interactive/test-filter-sort.c | 289 ++++++++++++++++++++
tests/meson.build | 2 +
tests/test-model-sort.c | 143 ++++++++++
8 files changed, 995 insertions(+)
---
diff --git a/meson.build b/meson.build
index e0ba184..b35a7b2 100644
--- a/meson.build
+++ b/meson.build
@@ -163,6 +163,7 @@ pkg = import('pkgconfig')
top_inc = include_directories('.')
src_inc = include_directories(
'src',
+ 'src/contrib',
'src/engine',
'src/interfaces',
'src/notification',
diff --git a/src/contrib/gtd-list-model-sort.c b/src/contrib/gtd-list-model-sort.c
new file mode 100644
index 0000000..f859df0
--- /dev/null
+++ b/src/contrib/gtd-list-model-sort.c
@@ -0,0 +1,504 @@
+/* gtd-list-model-sort.c
+ *
+ * Copyright 2018 Georges Basile Stavracas Neto <georges stavracas gmail com>
+ *
+ * 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "GtdListModelSort"
+
+#include "gtd-debug.h"
+#include "gtd-list-model-sort.h"
+#include "gtd-task.h"
+#include "gtd-task-list.h"
+
+struct _GtdListModelSort
+{
+ GObject parent;
+
+ GListModel *child_model;
+
+ GSequence *child_seq;
+ GSequence *sort_seq;
+
+ GtdListModelCompareFunc compare_func;
+ gpointer compare_func_data;
+ GDestroyNotify compare_func_data_destroy;
+
+ gboolean supress_items_changed : 1;
+
+ /* cache */
+ guint length;
+ guint last_position;
+ GSequenceIter *last_iter;
+};
+
+static void list_model_iface_init (GListModelInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GtdListModelSort, gtd_list_model_sort, G_TYPE_OBJECT,
+ 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 void
+gtd_list_model_sort_item_free (gpointer data)
+{
+ GSequenceIter *iter = data;
+
+ g_clear_pointer (&iter, g_sequence_remove);
+}
+
+static gint
+seq_compare_func (gconstpointer a,
+ gconstpointer b,
+ gpointer user_data)
+{
+ GtdListModelSort *self = (GtdListModelSort*) user_data;
+
+ return self->compare_func (G_OBJECT (a), G_OBJECT (b), self->compare_func_data);
+}
+
+static gint
+default_compare_func (GObject *a,
+ GObject *b,
+ gpointer user_data)
+{
+ return 0;
+}
+
+
+static void
+invalidate_cache (GtdListModelSort *self)
+{
+ GTD_TRACE_MSG ("Invalidating cache");
+
+ self->last_iter = NULL;
+ self->last_position = -1u;
+}
+
+static void
+emit_items_changed (GtdListModelSort *self,
+ guint position,
+ guint n_removed,
+ guint n_added)
+{
+ if (position <= self->last_position)
+ invalidate_cache (self);
+
+ self->length += n_added;
+ self->length -= n_removed;
+
+ GTD_TRACE_MSG ("Emitting items-changed(%u, %u, %u)", position, n_removed, n_added);
+
+ g_list_model_items_changed (G_LIST_MODEL (self), position, n_removed, n_added);
+}
+
+
+static void
+child_model_items_changed (GtdListModelSort *self,
+ guint position,
+ guint n_removed,
+ guint n_added,
+ GListModel *child_model)
+{
+ guint i;
+
+ GTD_ENTRY;
+
+ g_assert (GTD_IS_LIST_MODEL_SORT (self));
+ g_assert (G_IS_LIST_MODEL (child_model));
+ g_assert (self->child_model == child_model);
+ g_assert (position <= (guint)g_sequence_get_length (self->child_seq));
+ g_assert (g_sequence_get_length (self->child_seq) - n_removed + n_added == g_list_model_get_n_items
(child_model));
+
+ GTD_TRACE_MSG ("Received items-changed(%u, %u, %u)", position, n_removed, n_added);
+
+ /* check if the iter cache may have been invalidated */
+ if (position <= self->last_position)
+ invalidate_cache (self);
+
+ if (n_removed > 0)
+ {
+ GSequenceIter *iter;
+ guint previous_sort_position = -1u;
+ guint first_position = -1u;
+ guint counter = 0;
+
+ /* Small shortcut when all items are removed */
+ if (n_removed == (guint)g_sequence_get_length (self->child_seq))
+ {
+ g_sequence_remove_range (g_sequence_get_begin_iter (self->child_seq),
+ g_sequence_get_end_iter (self->child_seq));
+
+ g_assert (g_sequence_is_empty (self->child_seq));
+ g_assert (g_sequence_is_empty (self->sort_seq));
+
+ emit_items_changed (self, 0, n_removed, 0);
+
+ goto add_new_items;
+ }
+
+ iter = g_sequence_get_iter_at_pos (self->child_seq, position);
+ g_assert (!g_sequence_iter_is_end (iter));
+
+ for (i = 0; i < n_removed; i++)
+ {
+ GSequenceIter *sort_iter = g_sequence_get (iter);
+ GSequenceIter *to_remove = iter;
+ guint sort_position;
+
+ g_assert (g_sequence_iter_get_sequence (sort_iter) == self->sort_seq);
+
+ sort_position = g_sequence_iter_get_position (sort_iter);
+
+ /* Fetch the next while the iter is still valid */
+ iter = g_sequence_iter_next (iter);
+
+ /* Cascades into also removing from sort_seq. */
+ g_sequence_remove (to_remove);
+
+ if (first_position == -1u)
+ first_position = sort_position;
+
+ /*
+ * This happens when the position in the sorted sequence is different
+ * from the position in the child sequence. We try to accumulate as many
+ * items-changed() signals as possible, but we can't do that when the
+ * order doesn't match.
+ */
+ if (previous_sort_position != -1u && sort_position != previous_sort_position + 1)
+ {
+ emit_items_changed (self, first_position, 0, counter);
+
+ first_position = sort_position;
+ counter = 0;
+ }
+
+ previous_sort_position = sort_position;
+ counter++;
+ }
+
+ /*
+ * Last items-changed() - if the child model is already sorted,
+ * only this one will be fired.
+ */
+ if (counter > 0)
+ emit_items_changed (self, first_position, counter, 0);
+ }
+
+add_new_items:
+
+ if (n_added > 0)
+ {
+ GSequenceIter *iter = g_sequence_get_iter_at_pos (self->child_seq, position);
+ guint previous_sort_position = -1u;
+ guint first_position = -1u;
+ guint counter = 0;
+
+ for (i = 0; i < n_added; i++)
+ {
+ g_autoptr (GObject) instance = NULL;
+ GSequenceIter *sort_iter;
+ guint new_sort_position;
+
+ instance = g_list_model_get_item (child_model, position + i);
+ g_assert (G_IS_OBJECT (instance));
+
+ sort_iter = g_sequence_insert_sorted (self->sort_seq,
+ g_steal_pointer (&instance),
+ seq_compare_func,
+ self);
+
+ new_sort_position = g_sequence_iter_get_position (sort_iter);
+
+ /* Insert next item before this */
+ iter = g_sequence_insert_before (iter, sort_iter);
+ iter = g_sequence_iter_next (iter);
+
+ if (first_position == -1u)
+ first_position = new_sort_position;
+
+ /*
+ * This happens when the position in the sorted sequence is different
+ * from the position in the child sequence. We try to accumulate as many
+ * items-changed() signals as possible, but we can't do that when the
+ * order doesn't match.
+ */
+ if (previous_sort_position != -1u && new_sort_position != previous_sort_position + 1)
+ {
+ emit_items_changed (self, first_position, 0, counter);
+
+ first_position = new_sort_position;
+ counter = 0;
+ }
+
+ previous_sort_position = new_sort_position;
+ counter++;
+ }
+
+ /*
+ * Last items-changed() - if the child model is already sorted,
+ * only this one will be fired.
+ */
+ if (counter > 0)
+ emit_items_changed (self, first_position, 0, counter);
+ }
+
+ g_assert ((guint)g_sequence_get_length (self->child_seq) == g_list_model_get_n_items (child_model));
+ g_assert ((guint)g_sequence_get_length (self->sort_seq) == g_list_model_get_n_items (child_model));
+
+ GTD_EXIT;
+}
+
+
+/*
+ * GListModel iface
+ */
+
+static GType
+gtd_list_model_sort_get_item_type (GListModel *model)
+{
+ GtdListModelSort *self = (GtdListModelSort*) model;
+
+ g_assert (GTD_IS_LIST_MODEL_SORT (self));
+
+ return g_list_model_get_item_type (self->child_model);
+}
+
+static guint
+gtd_list_model_sort_get_n_items (GListModel *model)
+{
+ GtdListModelSort *self = (GtdListModelSort*) model;
+
+ g_assert (GTD_IS_LIST_MODEL_SORT (self));
+ g_assert (self->sort_seq != NULL);
+
+ return self->length;
+}
+
+static gpointer
+gtd_list_model_sort_get_item (GListModel *model,
+ guint position)
+{
+ GtdListModelSort *self;
+ GSequenceIter *iter;
+ gpointer item;
+
+ g_assert (GTD_IS_LIST_MODEL_SORT (model));
+
+ self = (GtdListModelSort*) model;
+ iter = NULL;
+
+ if (self->last_position != -1u)
+ {
+ if (self->last_position == position + 1)
+ iter = g_sequence_iter_prev (self->last_iter);
+ else if (self->last_position == position - 1)
+ iter = g_sequence_iter_next (self->last_iter);
+ else if (self->last_position == position)
+ iter = self->last_iter;
+ }
+
+ if (!iter)
+ iter = g_sequence_get_iter_at_pos (self->sort_seq, position);
+
+ if (g_sequence_iter_is_end (iter))
+ return NULL;
+
+ self->last_iter = iter;
+ self->last_position = position;
+
+ item = g_sequence_get (iter);
+ g_assert (item != NULL);
+
+ return g_object_ref (G_OBJECT (item));
+}
+
+static void
+list_model_iface_init (GListModelInterface *iface)
+{
+ iface->get_item_type = gtd_list_model_sort_get_item_type;
+ iface->get_n_items = gtd_list_model_sort_get_n_items;
+ iface->get_item = gtd_list_model_sort_get_item;
+}
+
+
+/*
+ * GObject overrides
+ */
+
+static void
+gtd_list_model_sort_finalize (GObject *object)
+{
+ GtdListModelSort *self = (GtdListModelSort*) object;
+
+ g_clear_pointer (&self->child_seq, g_sequence_free);
+ g_clear_pointer (&self->sort_seq, g_sequence_free);
+
+ if (self->compare_func_data_destroy)
+ {
+ g_clear_pointer (&self->compare_func_data, self->compare_func_data_destroy);
+ self->compare_func_data_destroy = NULL;
+ }
+
+ g_clear_object (&self->child_model);
+
+ G_OBJECT_CLASS (gtd_list_model_sort_parent_class)->finalize (object);
+}
+
+static void
+gtd_list_model_sort_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtdListModelSort *self = GTD_LIST_MODEL_SORT (object);
+
+ switch (prop_id)
+ {
+ case PROP_CHILD_MODEL:
+ g_value_set_object (value, gtd_list_model_sort_get_child_model (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gtd_list_model_sort_class_init (GtdListModelSortClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gtd_list_model_sort_finalize;
+ object_class->get_property = gtd_list_model_sort_get_property;
+
+ properties [PROP_CHILD_MODEL] = g_param_spec_object ("child-model",
+ "Child Model",
+ "The child model being sorted.",
+ G_TYPE_LIST_MODEL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+gtd_list_model_sort_init (GtdListModelSort *self)
+{
+ self->compare_func = default_compare_func;
+ self->child_seq = g_sequence_new (gtd_list_model_sort_item_free);
+ self->sort_seq = g_sequence_new (g_object_unref);
+ self->last_position = -1u;
+}
+
+GtdListModelSort *
+gtd_list_model_sort_new (GListModel *child_model)
+{
+ GtdListModelSort *self;
+
+ g_return_val_if_fail (G_IS_LIST_MODEL (child_model), NULL);
+
+ self = g_object_new (GTD_TYPE_LIST_MODEL_SORT, NULL);
+ self->child_model = g_object_ref (child_model);
+
+ g_signal_connect_object (child_model,
+ "items-changed",
+ G_CALLBACK (child_model_items_changed),
+ self,
+ G_CONNECT_SWAPPED);
+
+ child_model_items_changed (self, 0, 0, g_list_model_get_n_items (child_model), child_model);
+
+ return self;
+}
+
+/**
+ * gtd_list_model_sort_get_child_model:
+ * @self: A #GtdListModelSort
+ *
+ * Gets the child model that is being sorted.
+ *
+ * Returns: (transfer none): A #GListModel.
+ */
+GListModel *
+gtd_list_model_sort_get_child_model (GtdListModelSort *self)
+{
+ g_return_val_if_fail (GTD_IS_LIST_MODEL_SORT (self), NULL);
+
+ return self->child_model;
+}
+
+void
+gtd_list_model_sort_invalidate (GtdListModelSort *self)
+{
+ guint n_items;
+
+ g_return_if_fail (GTD_IS_LIST_MODEL_SORT (self));
+
+ /* First determine how many items we need to synthesize as a removal */
+ n_items = g_sequence_get_length (self->child_seq);
+
+ g_assert (G_IS_LIST_MODEL (self->child_model));
+
+ invalidate_cache (self);
+
+ g_sequence_sort (self->sort_seq, seq_compare_func, self);
+
+ g_assert ((guint)g_sequence_get_length (self->child_seq) == n_items);
+ g_assert ((guint)g_sequence_get_length (self->sort_seq) == n_items);
+
+ if (n_items > 0)
+ emit_items_changed (self, 0, n_items, n_items);
+}
+
+void
+gtd_list_model_sort_set_sort_func (GtdListModelSort *self,
+ GtdListModelCompareFunc compare_func,
+ gpointer compare_func_data,
+ GDestroyNotify compare_func_data_destroy)
+{
+ g_return_if_fail (GTD_IS_LIST_MODEL_SORT (self));
+ g_return_if_fail (compare_func || (!compare_func_data && !compare_func_data_destroy));
+
+ GTD_ENTRY;
+
+ if (self->compare_func_data_destroy != NULL)
+ g_clear_pointer (&self->compare_func_data, self->compare_func_data_destroy);
+
+ if (compare_func != NULL)
+ {
+ self->compare_func = compare_func;
+ self->compare_func_data = compare_func_data;
+ self->compare_func_data_destroy = compare_func_data_destroy;
+ }
+ else
+ {
+ self->compare_func = default_compare_func;
+ self->compare_func_data = NULL;
+ self->compare_func_data_destroy = NULL;
+ }
+
+ gtd_list_model_sort_invalidate (self);
+
+ GTD_EXIT;
+}
diff --git a/src/contrib/gtd-list-model-sort.h b/src/contrib/gtd-list-model-sort.h
new file mode 100644
index 0000000..c95db29
--- /dev/null
+++ b/src/contrib/gtd-list-model-sort.h
@@ -0,0 +1,46 @@
+/* gtd-list-model-sort.h
+ *
+ * Copyright 2018 Georges Basile Stavracas Neto <georges stavracas gmail com>
+ *
+ * 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define GTD_TYPE_LIST_MODEL_SORT (gtd_list_model_sort_get_type())
+
+typedef gboolean (*GtdListModelCompareFunc) (GObject *a,
+ GObject *b,
+ gpointer user_data);
+
+G_DECLARE_FINAL_TYPE (GtdListModelSort, gtd_list_model_sort, GTD, LIST_MODEL_SORT, GObject)
+
+GtdListModelSort* gtd_list_model_sort_new (GListModel *child_model);
+
+GListModel* gtd_list_model_sort_get_child_model (GtdListModelSort *self);
+
+void gtd_list_model_sort_invalidate (GtdListModelSort *self);
+
+void gtd_list_model_sort_set_sort_func (GtdListModelSort *self,
+ GtdListModelCompareFunc compare_func,
+ gpointer compare_func_data,
+ GDestroyNotify
compare_func_data_destroy);
+
+G_END_DECLS
diff --git a/src/gnome-todo.h b/src/gnome-todo.h
index 3709737..f09ac26 100644
--- a/src/gnome-todo.h
+++ b/src/gnome-todo.h
@@ -21,6 +21,9 @@
#include "gtd-activatable.h"
#include "gtd-enums.h"
+#include "gtd-list-model-filter.h"
+#include "gtd-list-model-sort.h"
+#include "gtd-list-store.h"
#include "gtd-manager.h"
#include "gtd-notification.h"
#include "gtd-object.h"
diff --git a/src/meson.build b/src/meson.build
index c610f69..6948cec 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -6,6 +6,8 @@
enum_headers = files('gtd-enums.h')
headers = enum_headers + files(
+ 'contrib/gtd-list-model-filter.h',
+ 'contrib/gtd-list-store.h',
'engine/gtd-manager.h',
'interfaces/gtd-activatable.h',
'interfaces/gtd-panel.h',
@@ -31,6 +33,7 @@ install_headers(headers, subdir: meson.project_name())
sources = files(
'contrib/gtd-list-model-filter.c',
+ 'contrib/gtd-list-model-sort.c',
'contrib/gtd-list-store.c',
'contrib/gtd-task-model.c',
'engine/gtd-manager.c',
@@ -195,6 +198,10 @@ pkg.generate(
if get_option('introspection')
gir_sources = files(
+ 'contrib/gtd-list-model-filter.c',
+ 'contrib/gtd-list-model-filter.h',
+ 'contrib/gtd-list-store.c',
+ 'contrib/gtd-list-store.h',
'engine/gtd-manager.c',
'engine/gtd-manager.h',
'interfaces/gtd-activatable.c',
diff --git a/tests/interactive/test-filter-sort.c b/tests/interactive/test-filter-sort.c
new file mode 100644
index 0000000..355dfee
--- /dev/null
+++ b/tests/interactive/test-filter-sort.c
@@ -0,0 +1,289 @@
+/* test-filter-sort.c
+ *
+ * Copyright 2018 Georges Basile Stavracas Neto <georges stavracas gmail com>
+ *
+ * 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+
+#include <gtk/gtk.h>
+
+#include "contrib/gtd-list-model-filter.h"
+#include "contrib/gtd-list-model-sort.h"
+#include "contrib/gtd-task-model.h"
+#include "contrib/gtd-task-model-private.h"
+#include "logging/gtd-log.h"
+#include "dummy-provider.h"
+#include "gtd-manager.h"
+#include "gtd-manager-protected.h"
+#include "gtd-provider.h"
+#include "gtd-task.h"
+#include "gtd-task-list.h"
+#include "gtd-utils.h"
+
+
+/*
+ * Auxiliary methods
+ */
+
+static GtkWidget*
+create_bold_label_for_task_row (GtkListBoxRow *row)
+{
+ g_autofree gchar *markup = NULL;
+ GtdTask *task;
+
+ task = g_object_get_data (G_OBJECT (row), "task");
+ markup = g_strdup_printf ("<big><b>%s</b></big>", gtd_task_list_get_name (gtd_task_get_list (task)));
+
+ return g_object_new (GTK_TYPE_LABEL,
+ "margin", 6,
+ "margin-top", 18,
+ "use-markup", TRUE,
+ "label", markup,
+ "xalign", 0.0,
+ NULL);
+}
+
+static GtkWidget*
+create_label_for_string (const gchar *string)
+{
+ return g_object_new (GTK_TYPE_LABEL,
+ "label", string,
+ "hexpand", TRUE,
+ "xalign", 0.0,
+ "margin", 6,
+ "margin-start", 18,
+ NULL);
+}
+
+
+/*
+ * Callbacks
+ */
+
+static void
+header_func (GtkListBoxRow *row,
+ GtkListBoxRow *before,
+ gpointer user_data)
+{
+ GtkWidget *header = NULL;
+
+ if (!before)
+ {
+ header = create_bold_label_for_task_row (row);
+ }
+ else
+ {
+ GtdTask *before_task;
+ GtdTask *task;
+
+ before_task = g_object_get_data (G_OBJECT (before), "task");
+ task = g_object_get_data (G_OBJECT (row), "task");
+
+ if (gtd_task_get_list (task) != gtd_task_get_list (before_task))
+ header = create_bold_label_for_task_row (row);
+ }
+
+ gtk_list_box_row_set_header (row, header);
+}
+
+static gboolean
+filter_func (GObject *item,
+ gpointer user_data)
+{
+ g_autofree gchar *normalized_entry_text = NULL;
+ g_autofree gchar *normalized_task_name = NULL;
+ GtkEntry *entry;
+ GtdTask *task;
+
+ task = (GtdTask*) item;
+ entry = (GtkEntry*) user_data;
+
+ normalized_entry_text = gtd_normalize_casefold_and_unaccent (gtk_entry_get_text (entry));
+ normalized_task_name = gtd_normalize_casefold_and_unaccent (gtd_task_get_title (task));
+
+ return strstr (normalized_task_name, normalized_entry_text) != NULL;
+}
+
+static GtkWidget*
+create_task_cb (gpointer item,
+ gpointer user_data)
+{
+ GtkWidget *row;
+ GtkWidget *box;
+ GtdTask *task;
+
+ task = item;
+
+ box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 16);
+
+ gtk_container_add (GTK_CONTAINER (box), create_label_for_string (gtd_task_get_title (task)));
+
+ row = gtk_list_box_row_new ();
+ gtk_container_add (GTK_CONTAINER (row), box);
+
+ g_object_set_data (G_OBJECT (row), "task", task);
+
+ return row;
+}
+
+static gint
+sort_func (GObject *a,
+ GObject *b,
+ gpointer user_data)
+{
+ GtkToggleButton *check;
+ GtdTask *task_a;
+ GtdTask *task_b;
+
+ check = (GtkToggleButton*) user_data;
+
+ if (gtk_toggle_button_get_active (check))
+ {
+ task_a = GTD_TASK (a);
+ task_b = GTD_TASK (b);
+ }
+ else
+ {
+ task_a = GTD_TASK (b);
+ task_b = GTD_TASK (a);
+ }
+
+ if (gtd_task_get_list (task_a) == gtd_task_get_list (task_b))
+ {
+ return gtd_task_compare (task_b, task_a);
+ }
+ else
+ {
+ GtdTaskList *list_a = gtd_task_get_list (task_a);
+ GtdTaskList *list_b = gtd_task_get_list (task_b);
+
+ return g_strcmp0 (gtd_task_list_get_name (list_b), gtd_task_list_get_name (list_a));
+ }
+}
+
+static void
+on_check_active_changed_cb (GtkToggleButton *check,
+ GParamSpec *pspec,
+ GtdListModelSort *sort)
+{
+ gtd_list_model_sort_invalidate (sort);
+}
+
+static void
+on_remove_button_clicked_cb (GtkButton *button,
+ DummyProvider *provider)
+{
+ dummy_provider_randomly_remove_task (provider);
+}
+
+static void
+on_search_text_changed_cb (GtkEntry *entry,
+ GParamSpec *pspec,
+ GtdListModelFilter *filter)
+{
+ gtd_list_model_filter_invalidate (filter);
+}
+
+gint
+main (gint argc,
+ gchar *argv[])
+{
+ g_autoptr (GtdListModelFilter) filter = NULL;
+ g_autoptr (GtdListModelSort) sort = NULL;
+ g_autoptr (DummyProvider) dummy_provider = NULL;
+ GtdTaskModel *model = NULL;
+ GtkWidget *scrolledwindow = NULL;
+ GtkWidget *search_entry = NULL;
+ GtkWindow *window = NULL;
+ GtkWidget *listbox = NULL;
+ GtkWidget *button = NULL;
+ GtkWidget *check = NULL;
+ GtkWidget *hbox = NULL;
+ GtkWidget *vbox = NULL;
+
+ g_set_prgname ("test-filter-sort");
+ g_set_application_name ("GNOME To Do | Filter & Sort Test");
+
+ gtk_init ();
+ gtd_log_init ();
+
+ /* Create a DumbProvider and pre-populate it */
+ dummy_provider = dummy_provider_new ();
+ dummy_provider_generate_task_lists (dummy_provider);
+ dummy_provider_generate_task_lists (dummy_provider);
+ _gtd_manager_inject_provider (gtd_manager_get_default (), GTD_PROVIDER (dummy_provider));
+
+ /* Models */
+ model = _gtd_task_model_new (gtd_manager_get_default ());
+ filter = gtd_list_model_filter_new (G_LIST_MODEL (model));
+ sort = gtd_list_model_sort_new (G_LIST_MODEL (filter));
+
+ /* Listbox */
+ listbox = gtk_list_box_new ();
+ gtk_list_box_bind_model (GTK_LIST_BOX (listbox),
+ G_LIST_MODEL (sort),
+ create_task_cb,
+ NULL,
+ NULL);
+
+ gtk_list_box_set_header_func (GTK_LIST_BOX (listbox), header_func, NULL, NULL);
+
+ /* Scrolled window */
+ scrolledwindow = g_object_new (GTK_TYPE_SCROLLED_WINDOW,
+ "propagate-natural-height", TRUE,
+ "max-content-height", 600,
+ "hscrollbar-policy", GTK_POLICY_NEVER,
+ NULL);
+ gtk_container_add (GTK_CONTAINER (scrolledwindow), listbox);
+
+ /* Search entry */
+ search_entry = gtk_search_entry_new ();
+ gtd_list_model_filter_set_filter_func (filter, filter_func, search_entry, NULL);
+
+ g_signal_connect_object (search_entry, "notify::text", G_CALLBACK (on_search_text_changed_cb), filter, 0);
+
+ /* Reverse order checkbox */
+ check = gtk_check_button_new_with_label ("Reverse Order");
+ gtd_list_model_sort_set_sort_func (sort, sort_func, check, NULL);
+
+ g_signal_connect_object (check, "notify::active", G_CALLBACK (on_check_active_changed_cb), sort, 0);
+
+ /* Remove Tasks button */
+ button = gtk_button_new_with_label ("Randomly Remove Tasks");
+ g_signal_connect_object (button, "clicked", G_CALLBACK (on_remove_button_clicked_cb), dummy_provider, 0);
+
+ /* Horizontal box */
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
+ gtk_container_add (GTK_CONTAINER (hbox), search_entry);
+ gtk_container_add (GTK_CONTAINER (hbox), check);
+ gtk_container_add (GTK_CONTAINER (hbox), button);
+
+ /* Box */
+ vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
+ gtk_container_add (GTK_CONTAINER (vbox), hbox);
+ gtk_container_add (GTK_CONTAINER (vbox), scrolledwindow);
+
+ /* Window */
+ window = GTK_WINDOW (gtk_window_new (GTK_WINDOW_TOPLEVEL));
+ gtk_window_set_default_size (window, 800, 600);
+ gtk_container_add (GTK_CONTAINER (window), vbox);
+ gtk_window_present (window);
+
+ gtk_main ();
+
+ return 0;
+}
diff --git a/tests/meson.build b/tests/meson.build
index 33e0307..223aac6 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -50,6 +50,7 @@ static_test_link_args = ['-fPIC']
static_tests = [
'test-model-filter',
+ 'test-model-sort',
'test-task-model',
]
@@ -78,6 +79,7 @@ endforeach
interactive_tests = [
'test-colorbutton',
+ 'test-filter-sort',
'test-task-model',
]
diff --git a/tests/test-model-sort.c b/tests/test-model-sort.c
new file mode 100644
index 0000000..e4426fc
--- /dev/null
+++ b/tests/test-model-sort.c
@@ -0,0 +1,143 @@
+/* test-model-sort.c
+ *
+ * Copyright 2018 Georges Basile Stavracas Neto <georges stavracas gmail com>
+ *
+ * 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "contrib/gtd-list-model-sort.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 gint
+sort_func1 (GObject *a,
+ GObject *b,
+ gpointer user_data)
+{
+ return TEST_ITEM (a)->n - TEST_ITEM (b)->n;
+}
+
+static gint
+sort_func2 (GObject *a,
+ GObject *b,
+ gpointer user_data)
+{
+ return (TEST_ITEM (a)->n & 1) - (TEST_ITEM (b)->n & 1);
+}
+
+static void
+test_basic (void)
+{
+ GtdListModelSort *sort;
+ GListStore *model;
+ guint i;
+
+ model = g_list_store_new (TEST_TYPE_ITEM);
+ g_assert (model);
+
+ sort = gtd_list_model_sort_new (G_LIST_MODEL (model));
+ g_assert (sort);
+
+ /* Test requesting past boundary */
+ g_assert_null (g_list_model_get_item (G_LIST_MODEL (sort), 0));
+ g_assert_null (g_list_model_get_item (G_LIST_MODEL (sort), 1));
+
+ for (i = 0; i < 1000; i++)
+ {
+ g_autoptr (TestItem) val = test_item_new (1000 - i);
+ g_list_store_append (model, val);
+ }
+
+ /* Test requesting past boundary */
+ g_assert_null (g_list_model_get_item (G_LIST_MODEL (sort), 1000));
+
+ /* Ascendent sorting */
+ gtd_list_model_sort_set_sort_func (sort, sort_func1, NULL, NULL);
+
+ 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 (sort)));
+
+ for (i = 1; i < 1000; i++)
+ {
+ g_autoptr (TestItem) current = g_list_model_get_item (G_LIST_MODEL (sort), i);
+ g_autoptr (TestItem) previous = g_list_model_get_item (G_LIST_MODEL (sort), i - 1);
+
+ g_assert_cmpint (previous->n, <, current->n);
+ }
+
+ /* Odd/Even sorting */
+ gtd_list_model_sort_set_sort_func (sort, sort_func2, NULL, NULL);
+
+ 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 (sort)));
+
+ for (i = 0; i < 1000; i++)
+ {
+ g_autoptr (TestItem) current = g_list_model_get_item (G_LIST_MODEL (sort), i);
+
+ if (i < 500)
+ g_assert_cmpint (current->n % 2, ==, 0);
+ else
+ g_assert_cmpint (current->n % 2, ==, 1);
+ }
+
+ g_clear_object (&model);
+ g_clear_object (&sort);
+}
+
+gint
+main (gint argc,
+ gchar *argv[])
+{
+ g_test_init (&argc, &argv, NULL);
+
+ g_test_add_func ("/contrib/model-sort/basic", test_basic);
+
+ return g_test_run ();
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]