From 5d8e439bc597159e3c9f0a8b65c0ae869dead3a8 Mon Sep 17 00:00:00 2001 From: Matthew Fennell Date: Sat, 27 Dec 2025 12:40:20 +0000 Subject: Import Upstream version 43.0 --- src/models/gtd-list-model-filter.c | 569 ++++++++++++++++++++++++++++++++++ src/models/gtd-list-model-filter.h | 44 +++ src/models/gtd-list-model-sort.c | 500 +++++++++++++++++++++++++++++ src/models/gtd-list-model-sort.h | 46 +++ src/models/gtd-list-store.c | 567 +++++++++++++++++++++++++++++++++ src/models/gtd-list-store.h | 66 ++++ src/models/gtd-task-list-view-model.c | 216 +++++++++++++ src/models/gtd-task-list-view-model.h | 43 +++ src/models/gtd-task-model-private.h | 29 ++ src/models/gtd-task-model.c | 216 +++++++++++++ src/models/gtd-task-model.h | 33 ++ 11 files changed, 2329 insertions(+) create mode 100644 src/models/gtd-list-model-filter.c create mode 100644 src/models/gtd-list-model-filter.h create mode 100644 src/models/gtd-list-model-sort.c create mode 100644 src/models/gtd-list-model-sort.h create mode 100644 src/models/gtd-list-store.c create mode 100644 src/models/gtd-list-store.h create mode 100644 src/models/gtd-task-list-view-model.c create mode 100644 src/models/gtd-task-list-view-model.h create mode 100644 src/models/gtd-task-model-private.h create mode 100644 src/models/gtd-task-model.c create mode 100644 src/models/gtd-task-model.h (limited to 'src/models') diff --git a/src/models/gtd-list-model-filter.c b/src/models/gtd-list-model-filter.c new file mode 100644 index 0000000..632c227 --- /dev/null +++ b/src/models/gtd-list-model-filter.c @@ -0,0 +1,569 @@ +/* gtd-list-model-filter.c + * + * Copyright (C) 2016 Christian Hergert + * + * 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 . + */ + +#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; + + /* cache */ + gint64 length; + gint64 last_position; + GSequenceIter *last_iter; + + /* + * 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. + */ + gboolean 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 +invalidate_cache (GtdListModelFilter *self) +{ + GtdListModelFilterPrivate *priv = gtd_list_model_filter_get_instance_private (self); + + GTD_TRACE_MSG ("Invalidating cache"); + + priv->last_iter = NULL; + priv->last_position = -1u; +} + +static void +emit_items_changed (GtdListModelFilter *self, + guint position, + guint n_removed, + guint n_added) +{ + GtdListModelFilterPrivate *priv = gtd_list_model_filter_get_instance_private (self); + + if (position <= priv->last_position) + invalidate_cache (self); + + priv->length -= n_removed; + priv->length += n_added; + + 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 (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; + + GTD_ENTRY; + + 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)); + + GTD_TRACE_MSG ("Received items-changed(%u, %u, %u)", position, n_removed, n_added); + + unblocked = !priv->supress_items_changed; + + if (n_removed > 0) + { + GSequenceIter *iter = g_sequence_get_iter_at_pos (priv->child_seq, position); + gint64 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)); + + if (unblocked) + emit_items_changed (self, 0, priv->length, 0); + + GTD_TRACE_MSG ("Removed all items"); + + GTD_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); + } + + GTD_TRACE_MSG ("Removed %u items", count); + + if (unblocked && first_position >= 0) + emit_items_changed (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; + } + + GTD_TRACE_MSG ("Added %u items (%u were filtered)", count, n_added - count); + + if (unblocked && count) + emit_items_changed (self, filter_position, 0, count); + } + + g_assert ((guint)g_sequence_get_length (priv->child_seq) == g_list_model_get_n_items (child_model)); + + GTD_EXIT; +} + +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); + priv->last_position = -1; +} + +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 priv->length; +} + +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 = NULL; + + if (priv->last_position != -1) + { + if (priv->last_position == position + 1) + iter = g_sequence_iter_prev (priv->last_iter); + else if (priv->last_position == position - 1) + iter = g_sequence_iter_next (priv->last_iter); + else if (priv->last_position == position) + iter = priv->last_iter; + } + + if (!iter) + 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; + + GTD_ENTRY; + + 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)) + emit_items_changed (self, 0, n_items, g_sequence_get_length (priv->filter_seq)); + + GTD_EXIT; +} + +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/models/gtd-list-model-filter.h b/src/models/gtd-list-model-filter.h new file mode 100644 index 0000000..f5c2a42 --- /dev/null +++ b/src/models/gtd-list-model-filter.h @@ -0,0 +1,44 @@ +/* gtd-list-model-filter.h + * + * Copyright © 2016 Christian Hergert + * 2018 Georges Basile Stavracas Neto + * + * 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 . + */ + +#pragma once + +#include + +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/models/gtd-list-model-sort.c b/src/models/gtd-list-model-sort.c new file mode 100644 index 0000000..dedbb6d --- /dev/null +++ b/src/models/gtd-list-model-sort.c @@ -0,0 +1,500 @@ +/* gtd-list-model-sort.c + * + * Copyright 2018 Georges Basile Stavracas Neto + * + * 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 . + * + * 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 */ + gint64 length; + gint64 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 = -1; +} + +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_removed; + self->length += n_added; + + 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); + + if (n_removed > 0) + { + GSequenceIter *iter; + gint64 previous_sort_position = -1; + gint64 first_position = -1; + gint64 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 == -1) + 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 != -1 && sort_position != previous_sort_position + 1) + { + emit_items_changed (self, first_position, counter, 0); + + 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); + gint64 previous_sort_position = -1; + gint64 first_position = -1; + gint64 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 == -1) + 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 != -1 && 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 != -1) + { + 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 = -1; +} + +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/models/gtd-list-model-sort.h b/src/models/gtd-list-model-sort.h new file mode 100644 index 0000000..c95db29 --- /dev/null +++ b/src/models/gtd-list-model-sort.h @@ -0,0 +1,46 @@ +/* gtd-list-model-sort.h + * + * Copyright 2018 Georges Basile Stavracas Neto + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +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/models/gtd-list-store.c b/src/models/gtd-list-store.c new file mode 100644 index 0000000..bd30bcc --- /dev/null +++ b/src/models/gtd-list-store.c @@ -0,0 +1,567 @@ +/* gtd-list-store.c + * + * Copyright 2018 Georges Basile Stavracas Neto + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#define G_LOG_DOMAIN "GtdListStore" + +#include "gtd-list-store.h" + +#include + +/** + * SECTION:gtdliststore + * @title: GtdListStore + * @short_description: A simple implementation of #GListModel + * @include: gio/gio.h + * + * #GtdListStore is a simple implementation of #GListModel that stores all + * items in memory. + * + * It provides insertions, deletions, and lookups in logarithmic time + * with a fast path for the common case of iterating the list linearly. + */ + +/** + * GtdListStore: + * + * #GtdListStore is an opaque data structure and can only be accessed + * using the following functions. + **/ + +struct _GtdListStore +{ + GObject parent; + + GType item_type; + GSequence *items; + + GHashTable *item_to_iter; + + /* cache */ + gint64 length; + gint64 last_position; + GSequenceIter *last_iter; +}; + +enum +{ + PROP_0, + PROP_ITEM_TYPE, + N_PROPERTIES +}; + +static void gtd_list_store_iface_init (GListModelInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (GtdListStore, gtd_list_store, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtd_list_store_iface_init)); + +static void +remove_item_from_sequence_cb (gpointer item, + gpointer user_data) +{ + GSequenceIter *it; + GtdListStore *store; + + store = (GtdListStore *)user_data; + it = g_hash_table_lookup (store->item_to_iter, item); + + g_hash_table_remove (store->item_to_iter, item); + g_sequence_remove (it); +} + +static void +gtd_list_store_items_changed (GtdListStore *store, + guint position, + guint removed, + guint added) +{ + /* check if the iter cache may have been invalidated */ + if (position <= store->last_position) + { + store->last_iter = NULL; + store->last_position = -1; + } + + store->length -= removed; + store->length += added; + + g_list_model_items_changed (G_LIST_MODEL (store), position, removed, added); +} + +static void +gtd_list_store_dispose (GObject *object) +{ + GtdListStore *store = GTD_LIST_STORE (object); + + g_clear_pointer (&store->item_to_iter, g_hash_table_destroy); + g_clear_pointer (&store->items, g_sequence_free); + + G_OBJECT_CLASS (gtd_list_store_parent_class)->dispose (object); +} + +static void +gtd_list_store_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GtdListStore *store = GTD_LIST_STORE (object); + + switch (property_id) + { + case PROP_ITEM_TYPE: + g_value_set_gtype (value, store->item_type); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +gtd_list_store_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GtdListStore *store = GTD_LIST_STORE (object); + + switch (property_id) + { + case PROP_ITEM_TYPE: /* construct-only */ + store->item_type = g_value_get_gtype (value); + if (!g_type_is_a (store->item_type, G_TYPE_OBJECT)) + g_critical ("GtdListStore cannot store items of type '%s'. Items must be GObjects.", + g_type_name (store->item_type)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +gtd_list_store_class_init (GtdListStoreClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gtd_list_store_dispose; + object_class->get_property = gtd_list_store_get_property; + object_class->set_property = gtd_list_store_set_property; + + /** + * GtdListStore:item-type: + * + * The type of items contained in this list store. Items must be + * subclasses of #GObject. + **/ + g_object_class_install_property (object_class, PROP_ITEM_TYPE, + g_param_spec_gtype ("item-type", "", "", G_TYPE_OBJECT, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); +} + +static GType +gtd_list_store_get_item_type (GListModel *list) +{ + GtdListStore *store = GTD_LIST_STORE (list); + + return store->item_type; +} + +static guint +gtd_list_store_get_n_items (GListModel *list) +{ + GtdListStore *store = GTD_LIST_STORE (list); + + return store->length; +} + +static gpointer +gtd_list_store_get_item (GListModel *list, + guint position) +{ + GtdListStore *store = GTD_LIST_STORE (list); + GSequenceIter *it = NULL; + + if (store->last_position != -1) + { + if (store->last_position == position + 1) + it = g_sequence_iter_prev (store->last_iter); + else if (store->last_position == position - 1) + it = g_sequence_iter_next (store->last_iter); + else if (store->last_position == position) + it = store->last_iter; + } + + if (it == NULL) + it = g_sequence_get_iter_at_pos (store->items, position); + + store->last_iter = it; + store->last_position = position; + + if (g_sequence_iter_is_end (it)) + return NULL; + else + return g_object_ref (g_sequence_get (it)); +} + +static void +gtd_list_store_iface_init (GListModelInterface *iface) +{ + iface->get_item_type = gtd_list_store_get_item_type; + iface->get_n_items = gtd_list_store_get_n_items; + iface->get_item = gtd_list_store_get_item; +} + +static void +gtd_list_store_init (GtdListStore *store) +{ + store->item_to_iter = g_hash_table_new (g_direct_hash, g_direct_equal); + store->items = g_sequence_new (g_object_unref); + store->last_position = -1; +} + +/** + * gtd_list_store_new: + * @item_type: the #GType of items in the list + * + * Creates a new #GtdListStore with items of type @item_type. @item_type + * must be a subclass of #GObject. + * + * Returns: a new #GtdListStore + */ +GtdListStore * +gtd_list_store_new (GType item_type) +{ + /* We only allow GObjects as item types right now. This might change + * in the future. + */ + g_return_val_if_fail (g_type_is_a (item_type, G_TYPE_OBJECT), NULL); + + return g_object_new (GTD_TYPE_LIST_STORE, + "item-type", item_type, + NULL); +} + +/** + * gtd_list_store_insert: + * @store: a #GtdListStore + * @position: the position at which to insert the new item + * @item: (type GObject): the new item + * + * Inserts @item into @store at @position. @item must be of type + * #GtdListStore:item-type or derived from it. @position must be smaller + * than the length of the list, or equal to it to append. + * + * This function takes a ref on @item. + * + * Use gtd_list_store_splice() to insert multiple items at the same time + * efficiently. + */ +void +gtd_list_store_insert (GtdListStore *store, + guint position, + gpointer item) +{ + GSequenceIter *new_it; + GSequenceIter *it; + + g_return_if_fail (GTD_IS_LIST_STORE (store)); + g_return_if_fail (g_type_is_a (G_OBJECT_TYPE (item), store->item_type)); + g_return_if_fail (position <= (guint) g_sequence_get_length (store->items)); + + it = g_sequence_get_iter_at_pos (store->items, position); + new_it = g_sequence_insert_before (it, g_object_ref (item)); + + g_hash_table_insert (store->item_to_iter, item, new_it); + + gtd_list_store_items_changed (store, position, 0, 1); +} + +/** + * gtd_list_store_insert_sorted: + * @store: a #GtdListStore + * @item: (type GObject): the new item + * @compare_func: (scope call): pairwise comparison function for sorting + * @user_data: (closure): user data for @compare_func + * + * Inserts @item into @store at a position to be determined by the + * @compare_func. + * + * The list must already be sorted before calling this function or the + * result is undefined. Usually you would approach this by only ever + * inserting items by way of this function. + * + * This function takes a ref on @item. + * + * Returns: the position at which @item was inserted + */ +guint +gtd_list_store_insert_sorted (GtdListStore *store, + gpointer item, + GCompareDataFunc compare_func, + gpointer user_data) +{ + GSequenceIter *it; + guint position; + + g_return_val_if_fail (GTD_IS_LIST_STORE (store), 0); + g_return_val_if_fail (g_type_is_a (G_OBJECT_TYPE (item), store->item_type), 0); + g_return_val_if_fail (compare_func != NULL, 0); + + it = g_sequence_insert_sorted (store->items, g_object_ref (item), compare_func, user_data); + position = g_sequence_iter_get_position (it); + + g_hash_table_insert (store->item_to_iter, item, it); + + gtd_list_store_items_changed (store, position, 0, 1); + + return position; +} + +/** + * gtd_list_store_sort: + * @store: a #GtdListStore + * @compare_func: (scope call): pairwise comparison function for sorting + * @user_data: (closure): user data for @compare_func + * + * Sort the items in @store according to @compare_func. + */ +void +gtd_list_store_sort (GtdListStore *store, + GCompareDataFunc compare_func, + gpointer user_data) +{ + gint n_items; + + g_return_if_fail (GTD_IS_LIST_STORE (store)); + g_return_if_fail (compare_func != NULL); + + g_sequence_sort (store->items, compare_func, user_data); + + n_items = g_sequence_get_length (store->items); + gtd_list_store_items_changed (store, 0, n_items, n_items); +} + +/** + * gtd_list_store_append: + * @store: a #GtdListStore + * @item: (type GObject): the new item + * + * Appends @item to @store. @item must be of type #GtdListStore:item-type. + * + * This function takes a ref on @item. + * + * Use gtd_list_store_splice() to append multiple items at the same time + * efficiently. + */ +void +gtd_list_store_append (GtdListStore *store, + gpointer item) +{ + GSequenceIter *it; + guint n_items; + + g_return_if_fail (GTD_IS_LIST_STORE (store)); + g_return_if_fail (g_type_is_a (G_OBJECT_TYPE (item), store->item_type)); + + n_items = g_sequence_get_length (store->items); + it = g_sequence_append (store->items, g_object_ref (item)); + + g_hash_table_insert (store->item_to_iter, item, it); + + gtd_list_store_items_changed (store, n_items, 0, 1); +} + +/** + * gtd_list_store_remove: + * @store: a #GtdListStore + * @item: the item that is to be removed + * + * Removes the item from @store. + * + * Use gtd_list_store_splice() to remove multiple items at the same time + * efficiently. + */ +void +gtd_list_store_remove (GtdListStore *store, + gpointer item) +{ + GSequenceIter *it; + guint position; + + g_return_if_fail (GTD_IS_LIST_STORE (store)); + + it = g_hash_table_lookup (store->item_to_iter, item); + if (!it) + return; + + g_return_if_fail (!g_sequence_iter_is_end (it)); + + position = g_sequence_iter_get_position (it); + + g_hash_table_remove (store->item_to_iter, item); + g_sequence_remove (it); + gtd_list_store_items_changed (store, position, 1, 0); +} + +/** + * gtd_list_store_remove_at_position: + * @store: a #GtdListStore + * @position: the position of the item that is to be removed + * + * Removes the item from @store that is at @position. @position must be + * smaller than the current length of the list. + * + * Use gtd_list_store_splice() to remove multiple items at the same time + * efficiently. + */ +void +gtd_list_store_remove_at_position (GtdListStore *store, + guint position) +{ + GSequenceIter *it; + + g_return_if_fail (GTD_IS_LIST_STORE (store)); + + it = g_sequence_get_iter_at_pos (store->items, position); + g_return_if_fail (!g_sequence_iter_is_end (it)); + + g_hash_table_remove (store->item_to_iter, g_sequence_get (it)); + g_sequence_remove (it); + gtd_list_store_items_changed (store, position, 1, 0); +} + +/** + * gtd_list_store_remove_all: + * @store: a #GtdListStore + * + * Removes all items from @store. + */ +void +gtd_list_store_remove_all (GtdListStore *store) +{ + guint n_items; + + g_return_if_fail (GTD_IS_LIST_STORE (store)); + + n_items = g_sequence_get_length (store->items); + g_sequence_remove_range (g_sequence_get_begin_iter (store->items), + g_sequence_get_end_iter (store->items)); + g_hash_table_remove_all (store->item_to_iter); + + gtd_list_store_items_changed (store, 0, n_items, 0); +} + +/** + * gtd_list_store_splice: + * @store: a #GtdListStore + * @position: the position at which to make the change + * @n_removals: the number of items to remove + * @additions: (array length=n_additions) (element-type GObject): the items to add + * @n_additions: the number of items to add + * + * Changes @store by removing @n_removals items and adding @n_additions + * items to it. @additions must contain @n_additions items of type + * #GtdListStore:item-type. %NULL is not permitted. + * + * This function is more efficient than gtd_list_store_insert() and + * gtd_list_store_remove(), because it only emits + * #GListModel::items-changed once for the change. + * + * This function takes a ref on each item in @additions. + * + * The parameters @position and @n_removals must be correct (ie: + * @position + @n_removals must be less than or equal to the length of + * the list at the time this function is called). + */ +void +gtd_list_store_splice (GtdListStore *store, + guint position, + guint n_removals, + gpointer *additions, + guint n_additions) +{ + GSequenceIter *it; + guint n_items; + + g_return_if_fail (GTD_IS_LIST_STORE (store)); + g_return_if_fail (position + n_removals >= position); /* overflow */ + + n_items = g_sequence_get_length (store->items); + g_return_if_fail (position + n_removals <= n_items); + + it = g_sequence_get_iter_at_pos (store->items, position); + + if (n_removals) + { + GSequenceIter *end; + + end = g_sequence_iter_move (it, n_removals); + g_sequence_foreach_range (it, end, remove_item_from_sequence_cb, store); + + it = end; + } + + if (n_additions) + { + guint i; + + for (i = 0; i < n_additions; i++) + { + if G_UNLIKELY (!g_type_is_a (G_OBJECT_TYPE (additions[i]), store->item_type)) + { + g_critical ("%s: item %d is a %s instead of a %s. GtdListStore is now in an undefined state.", + G_STRFUNC, i, G_OBJECT_TYPE_NAME (additions[i]), g_type_name (store->item_type)); + return; + } + + it = g_sequence_insert_before (it, g_object_ref (additions[i])); + g_hash_table_insert (store->item_to_iter, additions[i], it); + + it = g_sequence_iter_next (it); + } + } + + gtd_list_store_items_changed (store, position, n_removals, n_additions); +} + +/** + * gtd_list_store_get_item_position: + * @store: a #GtdListStore + * @item: the item to retrieve the position + * + * Retrieves the position of @items inside @store. It is a programming + * error to pass an @item that is not contained in @store. + * + * Returns: the position of @item in @store. + */ +guint +gtd_list_store_get_item_position (GtdListStore *store, + gpointer item) +{ + GSequenceIter *iter; + + g_return_val_if_fail (GTD_IS_LIST_STORE (store), 0); + + iter = g_hash_table_lookup (store->item_to_iter, item); + g_assert (iter != NULL); + + return g_sequence_iter_get_position (iter); +} diff --git a/src/models/gtd-list-store.h b/src/models/gtd-list-store.h new file mode 100644 index 0000000..84e8882 --- /dev/null +++ b/src/models/gtd-list-store.h @@ -0,0 +1,66 @@ +/* gtd-list-store.h + * + * Copyright 2018 Georges Basile Stavracas Neto + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#define GTD_TYPE_LIST_STORE (gtd_list_store_get_type()) + +G_DECLARE_FINAL_TYPE (GtdListStore, gtd_list_store, GTD, LIST_STORE, GObject) + +GtdListStore* gtd_list_store_new (GType item_type); + +void gtd_list_store_insert (GtdListStore *store, + guint position, + gpointer item); + +guint gtd_list_store_insert_sorted (GtdListStore *store, + gpointer item, + GCompareDataFunc compare_func, + gpointer user_data); + +void gtd_list_store_sort (GtdListStore *store, + GCompareDataFunc compare_func, + gpointer user_data); + +void gtd_list_store_append (GtdListStore *store, + gpointer item); + +void gtd_list_store_remove (GtdListStore *store, + gpointer item); + +void gtd_list_store_remove_at_position (GtdListStore *store, + guint position); + +void gtd_list_store_remove_all (GtdListStore *store); + +void gtd_list_store_splice (GtdListStore *store, + guint position, + guint n_removals, + gpointer *additions, + guint n_additions); + +guint gtd_list_store_get_item_position (GtdListStore *store, + gpointer item); + +G_END_DECLS diff --git a/src/models/gtd-task-list-view-model.c b/src/models/gtd-task-list-view-model.c new file mode 100644 index 0000000..d569d29 --- /dev/null +++ b/src/models/gtd-task-list-view-model.c @@ -0,0 +1,216 @@ +/* gtd-task-list-view-model.c + * + * Copyright 2020 Georges Basile Stavracas Neto + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "gtd-task-list.h" +#include "gtd-task-list-view-model.h" + + +/* + * Sentinel + */ + +struct _GtdSentinel +{ + GObject parent_instance; +}; + +G_DEFINE_TYPE (GtdSentinel, gtd_sentinel, G_TYPE_OBJECT); + +static void +gtd_sentinel_init (GtdSentinel *self) +{ +} + +static void +gtd_sentinel_class_init (GtdSentinelClass *klass) +{ +} + + + +struct _GtdTaskListViewModel +{ + GObject parent_instance; + + GtdSentinel *sentinel; + + GListModel *model; + guint n_items; +}; + +static void g_list_model_iface_init (GListModelInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (GtdTaskListViewModel, gtd_task_list_view_model, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, g_list_model_iface_init)) + + +/* + * Auxiliary methods + */ + +static void +update_n_items (GtdTaskListViewModel *self, + guint position, + guint removed, + guint added) +{ + self->n_items = self->n_items - removed + added; + g_list_model_items_changed (G_LIST_MODEL (self), position, removed, added); +} + + +/* + * Callbacks + */ + +static void +on_model_items_changed_cb (GListModel *model, + guint position, + guint removed, + guint added, + GtdTaskListViewModel *self) +{ + update_n_items (self, position, removed, added); +} + + +/* + * GListModel interface + */ + +static guint +gtd_task_list_view_model_get_n_items (GListModel *model) +{ + GtdTaskListViewModel *self = (GtdTaskListViewModel *)model; + + return self->n_items + 1; +} + +static GType +gtd_task_list_view_model_get_item_type (GListModel *model) +{ + return G_TYPE_OBJECT; +} + +static gpointer +gtd_task_list_view_model_get_item (GListModel *model, + guint position) +{ + GtdTaskListViewModel *self = (GtdTaskListViewModel *)model; + + if (gtd_task_list_view_model_is_sentinel (self, position)) + return g_object_ref (self->sentinel); + + return g_list_model_get_item (self->model, position); +} + +static void +g_list_model_iface_init (GListModelInterface *iface) +{ + iface->get_n_items = gtd_task_list_view_model_get_n_items; + iface->get_item_type = gtd_task_list_view_model_get_item_type; + iface->get_item = gtd_task_list_view_model_get_item; +} + + +/* + * GObject overrides + */ + +static void +gtd_task_list_view_model_finalize (GObject *object) +{ + GtdTaskListViewModel *self = (GtdTaskListViewModel *)object; + + g_clear_object (&self->model); + g_clear_object (&self->sentinel); + + G_OBJECT_CLASS (gtd_task_list_view_model_parent_class)->finalize (object); +} + +static void +gtd_task_list_view_model_class_init (GtdTaskListViewModelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gtd_task_list_view_model_finalize; +} + +static void +gtd_task_list_view_model_init (GtdTaskListViewModel *self) +{ + self->sentinel = g_object_new (GTD_TYPE_SENTINEL, NULL); +} + +GtdTaskListViewModel * +gtd_task_list_view_model_new (void) +{ + return g_object_new (GTD_TYPE_TASK_LIST_VIEW_MODEL, NULL); +} + +GListModel * +gtd_task_list_view_model_get_model (GtdTaskListViewModel *self) +{ + return self->model; +} + +void +gtd_task_list_view_model_set_model (GtdTaskListViewModel *self, + GListModel *model) +{ + guint old_n_items = 0; + guint new_n_items = 0; + + if (G_UNLIKELY (self->model == model)) + return; + + old_n_items = self->n_items; + + if (self->model) + { + g_signal_handlers_disconnect_by_func (self->model, + on_model_items_changed_cb, + self); + } + + g_clear_object (&self->model); + + if (model) + { + self->model = g_object_ref (model); + + g_signal_connect_object (model, + "items-changed", + G_CALLBACK (on_model_items_changed_cb), + self, + 0); + + new_n_items = g_list_model_get_n_items (model); + } + + update_n_items (self, 0, old_n_items, new_n_items); +} + +gboolean +gtd_task_list_view_model_is_sentinel (GtdTaskListViewModel *self, + guint position) +{ + return position == self->n_items; +} diff --git a/src/models/gtd-task-list-view-model.h b/src/models/gtd-task-list-view-model.h new file mode 100644 index 0000000..7323f3a --- /dev/null +++ b/src/models/gtd-task-list-view-model.h @@ -0,0 +1,43 @@ +/* gtd-task-list-view-model.h + * + * Copyright 2020 Georges Basile Stavracas Neto + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#define GTD_TYPE_SENTINEL (gtd_sentinel_get_type ()) +G_DECLARE_FINAL_TYPE (GtdSentinel, gtd_sentinel, GTD, SENTINEL, GObject) + +#define GTD_TYPE_TASK_LIST_VIEW_MODEL (gtd_task_list_view_model_get_type()) +G_DECLARE_FINAL_TYPE (GtdTaskListViewModel, gtd_task_list_view_model, GTD, TASK_LIST_VIEW_MODEL, GObject) + +GtdTaskListViewModel* gtd_task_list_view_model_new (void); + +GListModel* gtd_task_list_view_model_get_model (GtdTaskListViewModel *self); + +void gtd_task_list_view_model_set_model (GtdTaskListViewModel *self, + GListModel *model); + +gboolean gtd_task_list_view_model_is_sentinel (GtdTaskListViewModel *self, + guint position); + +G_END_DECLS diff --git a/src/models/gtd-task-model-private.h b/src/models/gtd-task-model-private.h new file mode 100644 index 0000000..7bc1c7d --- /dev/null +++ b/src/models/gtd-task-model-private.h @@ -0,0 +1,29 @@ +/* gtd-task-model-private.h + * + * Copyright 2018 Georges Basile Stavracas Neto + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "gtd-task-model.h" + +G_BEGIN_DECLS + +GtdTaskModel* _gtd_task_model_new (GtdManager *manager); + +G_END_DECLS diff --git a/src/models/gtd-task-model.c b/src/models/gtd-task-model.c new file mode 100644 index 0000000..62984e7 --- /dev/null +++ b/src/models/gtd-task-model.c @@ -0,0 +1,216 @@ +/* gtd-task-model.c + * + * Copyright 2018 Georges Basile Stavracas Neto + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#define G_LOG_DOMAIN "GtdTaskModel" + +#include "gtd-debug.h" +#include "gtd-list-store.h" +#include "gtd-manager.h" +#include "gtd-task.h" +#include "gtd-task-list.h" +#include "gtd-task-model.h" +#include "gtd-task-model-private.h" + +struct _GtdTaskModel +{ + GObject parent; + + GtkFlattenListModel *model; + + guint number_of_tasks; + + GtdManager *manager; +}; + +static void g_list_model_iface_init (GListModelInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (GtdTaskModel, gtd_task_model, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, g_list_model_iface_init)) + +enum +{ + PROP_0, + PROP_MANAGER, + N_PROPS +}; + +static GParamSpec *properties [N_PROPS]; + + +/* + * Callbacks + */ + +static void +on_model_items_changed_cb (GListModel *model, + guint position, + guint n_removed, + guint n_added, + GtdTaskModel *self) +{ + GTD_TRACE_MSG ("Child model changed with position=%u, n_removed=%u, n_added=%u", position, n_removed, n_added); + + g_list_model_items_changed (G_LIST_MODEL (self), position, n_removed, n_added); +} + + +/* + * GListModel iface + */ + +static gpointer +gtd_task_model_get_item (GListModel *model, + guint position) +{ + GtdTaskModel *self = (GtdTaskModel*) model; + + return g_list_model_get_item (G_LIST_MODEL (self->model), position); +} + +static guint +gtd_task_model_get_n_items (GListModel *model) +{ + GtdTaskModel *self = (GtdTaskModel*) model; + + return g_list_model_get_n_items (G_LIST_MODEL (self->model)); +} + +static GType +gtd_task_model_get_item_type (GListModel *model) +{ + return GTD_TYPE_TASK; +} + +static void +g_list_model_iface_init (GListModelInterface *iface) +{ + iface->get_item = gtd_task_model_get_item; + iface->get_n_items = gtd_task_model_get_n_items; + iface->get_item_type = gtd_task_model_get_item_type; +} + + +/* + * GObject overrides + */ + +static void +gtd_task_model_finalize (GObject *object) +{ + GtdTaskModel *self = (GtdTaskModel *)object; + + g_clear_object (&self->manager); + g_clear_object (&self->model); + + G_OBJECT_CLASS (gtd_task_model_parent_class)->finalize (object); +} + + +static void +gtd_task_model_constructed (GObject *object) +{ + GtdTaskModel *self = (GtdTaskModel *)object; + GListModel *model; + + g_assert (self->manager != NULL); + + model = gtd_manager_get_task_lists_model (self->manager); + + g_signal_connect_object (self->model, + "items-changed", + G_CALLBACK (on_model_items_changed_cb), + self, + 0); + gtk_flatten_list_model_set_model (self->model, model); + + G_OBJECT_CLASS (gtd_task_model_parent_class)->constructed (object); +} + +static void +gtd_task_model_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtdTaskModel *self = GTD_TASK_MODEL (object); + + switch (prop_id) + { + case PROP_MANAGER: + g_value_set_object (value, self->manager); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gtd_task_model_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtdTaskModel *self = GTD_TASK_MODEL (object); + + switch (prop_id) + { + case PROP_MANAGER: + g_assert (self->manager == NULL); + self->manager = g_value_dup_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gtd_task_model_class_init (GtdTaskModelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gtd_task_model_finalize; + object_class->constructed = gtd_task_model_constructed; + object_class->get_property = gtd_task_model_get_property; + object_class->set_property = gtd_task_model_set_property; + + properties[PROP_MANAGER] = g_param_spec_object ("manager", + "Manager", + "Manager", + GTD_TYPE_MANAGER, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +gtd_task_model_init (GtdTaskModel *self) +{ + self->model = gtk_flatten_list_model_new (NULL); +} + +GtdTaskModel* +_gtd_task_model_new (GtdManager *manager) +{ + return g_object_new (GTD_TYPE_TASK_MODEL, + "manager", manager, + NULL); +} diff --git a/src/models/gtd-task-model.h b/src/models/gtd-task-model.h new file mode 100644 index 0000000..2dc7aee --- /dev/null +++ b/src/models/gtd-task-model.h @@ -0,0 +1,33 @@ +/* gtd-task-model.h + * + * Copyright 2018 Georges Basile Stavracas Neto + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include "gtd-types.h" + +G_BEGIN_DECLS + +#define GTD_TYPE_TASK_MODEL (gtd_task_model_get_type()) + +G_DECLARE_FINAL_TYPE (GtdTaskModel, gtd_task_model, GTD, TASK_MODEL, GObject) + +G_END_DECLS -- cgit v1.2.3