summaryrefslogtreecommitdiff
path: root/src/core/gtd-task-list.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/gtd-task-list.c')
-rw-r--r--src/core/gtd-task-list.c1161
1 files changed, 1161 insertions, 0 deletions
diff --git a/src/core/gtd-task-list.c b/src/core/gtd-task-list.c
new file mode 100644
index 0000000..2e71a9a
--- /dev/null
+++ b/src/core/gtd-task-list.c
@@ -0,0 +1,1161 @@
+/* gtd-task-list.c
+ *
+ * Copyright (C) 2015-2020 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/>.
+ */
+
+#define G_LOG_DOMAIN "GtdTaskList"
+
+#include "gtd-debug.h"
+#include "gtd-provider.h"
+#include "gtd-task.h"
+#include "gtd-task-list.h"
+
+#include <glib/gi18n.h>
+
+/**
+ * SECTION:gtd-task-list
+ * @short_description:a list of tasks
+ * @title:GtdTaskList
+ * @stability:Unstable
+ * @see_also:#GtdTask
+ *
+ * A #GtdTaskList represents a task list, and contains a list of tasks, a
+ * color, a name and the provider who generated it.
+ *
+ * Only a #GtdProvider can create a #GtdTaskList. Equally, a #GtdTaskList
+ * is only valid when associated with a #GtdProvider.
+ *
+ * It implements #GListModel, and can be used as the model for #GtkListBox.
+ */
+
+typedef struct
+{
+ GtdTask *task;
+ GTask *gtask;
+} ImportingTaskData;
+
+typedef struct
+{
+ GtdProvider *provider;
+ GdkRGBA *color;
+
+ GHashTable *task_to_uid;
+ GHashTable *tasks;
+ GSequence *sorted_tasks;
+ guint n_tasks;
+
+ guint freeze_counter;
+
+ gchar *name;
+ gboolean removable;
+ gboolean archived;
+} GtdTaskListPrivate;
+
+
+static gint compare_tasks_cb (gconstpointer a,
+ gconstpointer b,
+ gpointer user_data);
+
+static void task_changed_cb (GtdTask *task,
+ GParamSpec *pspec,
+ GtdTaskList *self);
+
+static void g_list_model_iface_init (GListModelInterface *iface);
+
+
+G_DEFINE_TYPE_WITH_CODE (GtdTaskList, gtd_task_list, GTD_TYPE_OBJECT,
+ G_ADD_PRIVATE (GtdTaskList)
+ G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, g_list_model_iface_init))
+
+enum
+{
+ TASK_ADDED,
+ TASK_REMOVED,
+ TASK_UPDATED,
+ NUM_SIGNALS
+};
+
+enum
+{
+ PROP_0,
+ PROP_ARCHIVED,
+ PROP_COLOR,
+ PROP_IS_REMOVABLE,
+ PROP_NAME,
+ PROP_PROVIDER,
+ N_PROPS
+};
+
+static guint signals[NUM_SIGNALS] = { 0, };
+static GParamSpec *properties[N_PROPS] = { NULL, };
+
+
+/*
+ * Auxiliary functions
+ */
+
+static void
+update_task_uid (GtdTaskList *self,
+ GtdTask *task)
+{
+ GtdTaskListPrivate *priv;
+ GSequenceIter *iter;
+ const gchar *old_uid;
+ gchar *new_uid;
+
+ priv = gtd_task_list_get_instance_private (self);
+
+ g_debug ("Updating uid of task '%s'", gtd_task_get_title (task));
+
+ new_uid = g_strdup (gtd_object_get_uid (GTD_OBJECT (task)));
+
+ old_uid = g_hash_table_lookup (priv->task_to_uid, task);
+ iter = g_hash_table_lookup (priv->tasks, old_uid);
+
+ g_assert (g_sequence_get (iter) == task);
+
+ g_hash_table_remove (priv->tasks, old_uid);
+
+ g_hash_table_insert (priv->task_to_uid, task, new_uid);
+ g_hash_table_insert (priv->tasks, new_uid, iter);
+}
+
+static guint
+add_task (GtdTaskList *self,
+ GtdTask *task)
+{
+ GtdTaskListPrivate *priv;
+ GSequenceIter *iter;
+ gchar *uid;
+
+ priv = gtd_task_list_get_instance_private (self);
+
+ uid = g_strdup (gtd_object_get_uid (GTD_OBJECT (task)));
+ iter = g_sequence_insert_sorted (priv->sorted_tasks,
+ g_object_ref (task),
+ compare_tasks_cb,
+ NULL);
+
+ g_hash_table_insert (priv->task_to_uid, task, uid);
+ g_hash_table_insert (priv->tasks, uid, iter);
+
+ g_signal_connect (task, "notify", G_CALLBACK (task_changed_cb), self);
+
+ priv->n_tasks++;
+
+ g_signal_emit (self, signals[TASK_ADDED], 0, task);
+
+ return g_sequence_iter_get_position (iter);
+}
+
+static guint
+remove_task (GtdTaskList *self,
+ GtdTask *task)
+{
+ GtdTaskListPrivate *priv;
+ GSequenceIter *iter;
+ const gchar *uid;
+ guint position;
+
+ priv = gtd_task_list_get_instance_private (self);
+
+ g_signal_handlers_disconnect_by_func (task, task_changed_cb, self);
+
+ uid = gtd_object_get_uid (GTD_OBJECT (task));
+ iter = g_hash_table_lookup (priv->tasks, uid);
+ position = g_sequence_iter_get_position (iter);
+
+ g_hash_table_remove (priv->task_to_uid, task);
+ g_hash_table_remove (priv->tasks, uid);
+
+ g_sequence_remove (iter);
+
+ priv->n_tasks--;
+
+ g_signal_emit (self, signals[TASK_REMOVED], 0, task);
+
+ return position;
+}
+
+
+/*
+ * Callbacks
+ */
+
+static void
+on_import_old_task_removed_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GtdTask *new_task;
+ GTask *gtask;
+ g_autoptr (GError) error = NULL;
+
+ gtask = user_data;
+
+ gtd_provider_remove_task_finish (GTD_PROVIDER (object), result, &error);
+
+ if (error)
+ {
+ g_warning ("Error removing task: %s", error->message);
+ return;
+ }
+
+ new_task = g_task_get_task_data (gtask);
+ g_task_return_pointer (gtask, g_object_ref (new_task), g_object_unref);
+}
+
+static void
+on_task_updated_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ g_autoptr (GError) error = NULL;
+
+ gtd_provider_update_task_finish (GTD_PROVIDER (object), result, &error);
+
+ if (error)
+ {
+ g_warning ("Error updating task: %s", error->message);
+ return;
+ }
+}
+
+static void
+on_import_new_task_added_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ g_autoptr (GtdTask) new_task = NULL;
+ g_autoptr (GError) error = NULL;
+ GTask *gtask;
+ GtdProvider *provider;
+ GtdTask *importing_task;
+ GtdTaskList *list;
+ ImportingTaskData *data;
+
+ GTD_ENTRY;
+
+ data = user_data;
+ gtask = data->gtask;
+ importing_task = data->task;
+
+ new_task = gtd_provider_create_task_finish (GTD_PROVIDER (object), result, &error);
+ g_task_set_task_data (gtask, g_object_ref (new_task), g_object_unref);
+ list = gtd_task_get_list (new_task);
+
+ if (error)
+ {
+ g_warning ("Error creating task: %s", error->message);
+ GTD_RETURN ();
+ }
+
+ gtd_task_set_complete (new_task, gtd_task_get_complete (importing_task));
+ gtd_task_set_description (new_task, gtd_task_get_description (importing_task));
+ gtd_task_set_due_date (new_task, gtd_task_get_due_date (importing_task));
+ gtd_task_set_important (new_task, gtd_task_get_important (importing_task));
+ gtd_task_set_title (new_task, gtd_task_get_title (importing_task));
+
+ gtd_task_list_update_task (list, new_task);
+
+ provider = gtd_task_get_provider (importing_task);
+
+ gtd_provider_remove_task (provider,
+ importing_task,
+ NULL,
+ on_import_old_task_removed_cb,
+ gtask);
+
+ g_free (data);
+
+ GTD_EXIT;
+}
+
+static gint
+compare_tasks_cb (gconstpointer a,
+ gconstpointer b,
+ gpointer user_data)
+{
+ return gtd_task_compare ((GtdTask*) a, (GtdTask*) b);
+}
+
+static void
+task_changed_cb (GtdTask *task,
+ GParamSpec *pspec,
+ GtdTaskList *self)
+{
+ GtdTaskListPrivate *priv;
+ GSequenceIter *iter;
+ guint old_position;
+ guint new_position;
+
+ GTD_ENTRY;
+
+ priv = gtd_task_list_get_instance_private (self);
+
+ if (g_strcmp0 (g_param_spec_get_name (pspec), "loading") == 0)
+ GTD_RETURN ();
+
+ if (g_strcmp0 (g_param_spec_get_name (pspec), "uid") == 0)
+ {
+ update_task_uid (self, task);
+ GTD_RETURN ();
+ }
+
+ /* Don't update when the list is frozen */
+ if (priv->freeze_counter > 0)
+ GTD_RETURN ();
+
+ iter = g_hash_table_lookup (priv->tasks, gtd_object_get_uid (GTD_OBJECT (task)));
+
+ old_position = g_sequence_iter_get_position (iter);
+ g_sequence_sort_changed (iter, compare_tasks_cb, NULL);
+ new_position = g_sequence_iter_get_position (iter);
+
+ if (old_position != new_position)
+ {
+ GTD_TRACE_MSG ("Old position: %u, New position: %u", old_position, new_position);
+
+ g_list_model_items_changed (G_LIST_MODEL (self), old_position, 1, 0);
+ g_list_model_items_changed (G_LIST_MODEL (self), new_position, 0, 1);
+ }
+
+ GTD_EXIT;
+}
+
+
+/*
+ * GListModel iface
+ */
+
+static GType
+gtd_list_model_get_type (GListModel *model)
+{
+ return GTD_TYPE_TASK;
+}
+
+static guint
+gtd_list_model_get_n_items (GListModel *model)
+{
+ GtdTaskListPrivate *priv = gtd_task_list_get_instance_private (GTD_TASK_LIST (model));
+ return priv->n_tasks;
+}
+
+static gpointer
+gtd_list_model_get_item (GListModel *model,
+ guint i)
+{
+ GtdTaskListPrivate *priv;
+ GSequenceIter *iter;
+ GtdTask *task;
+
+ priv = gtd_task_list_get_instance_private (GTD_TASK_LIST (model));
+ iter = g_sequence_get_iter_at_pos (priv->sorted_tasks, i);
+ task = g_sequence_get (iter);
+
+ return g_object_ref (task);
+}
+
+static void
+g_list_model_iface_init (GListModelInterface *iface)
+{
+ iface->get_item_type = gtd_list_model_get_type;
+ iface->get_n_items = gtd_list_model_get_n_items;
+ iface->get_item = gtd_list_model_get_item;
+}
+
+
+/*
+ * GtdTaskList overrides
+ */
+
+static gboolean
+gtd_task_list_real_get_archived (GtdTaskList *self)
+{
+ GtdTaskListPrivate *priv = gtd_task_list_get_instance_private (self);
+
+ return priv->archived;
+}
+
+static void
+gtd_task_list_real_set_archived (GtdTaskList *self,
+ gboolean archived)
+{
+ GtdTaskListPrivate *priv = gtd_task_list_get_instance_private (self);
+
+ priv->archived = archived;
+}
+
+
+/*
+ * GObject overrides
+ */
+
+static void
+gtd_task_list_finalize (GObject *object)
+{
+ GtdTaskList *self = (GtdTaskList*) object;
+ GtdTaskListPrivate *priv = gtd_task_list_get_instance_private (self);
+
+ g_clear_object (&priv->provider);
+
+ g_clear_pointer (&priv->color, gdk_rgba_free);
+ g_clear_pointer (&priv->name, g_free);
+ g_clear_pointer (&priv->sorted_tasks, g_sequence_free);
+ g_clear_pointer (&priv->tasks, g_hash_table_destroy);
+ g_clear_pointer (&priv->task_to_uid, g_hash_table_destroy);
+
+ G_OBJECT_CLASS (gtd_task_list_parent_class)->finalize (object);
+}
+
+static void
+gtd_task_list_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtdTaskList *self = GTD_TASK_LIST (object);
+ GtdTaskListPrivate *priv = gtd_task_list_get_instance_private (self);
+
+ switch (prop_id)
+ {
+ case PROP_ARCHIVED:
+ g_value_set_boolean (value, gtd_task_list_get_archived (self));
+ break;
+
+ case PROP_COLOR:
+ {
+ GdkRGBA *color = gtd_task_list_get_color (self);
+ g_value_set_boxed (value, color);
+ gdk_rgba_free (color);
+ break;
+ }
+
+ case PROP_IS_REMOVABLE:
+ g_value_set_boolean (value, gtd_task_list_is_removable (self));
+ break;
+
+ case PROP_NAME:
+ g_value_set_string (value, priv->name);
+ break;
+
+ case PROP_PROVIDER:
+ g_value_set_object (value, priv->provider);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gtd_task_list_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GtdTaskList *self = GTD_TASK_LIST (object);
+
+ switch (prop_id)
+ {
+ case PROP_ARCHIVED:
+ gtd_task_list_set_archived (self, g_value_get_boolean (value));
+ break;
+
+ case PROP_COLOR:
+ gtd_task_list_set_color (self, g_value_get_boxed (value));
+ break;
+
+ case PROP_IS_REMOVABLE:
+ gtd_task_list_set_is_removable (self, g_value_get_boolean (value));
+ break;
+
+ case PROP_NAME:
+ gtd_task_list_set_name (self, g_value_get_string (value));
+ break;
+
+ case PROP_PROVIDER:
+ gtd_task_list_set_provider (self, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gtd_task_list_class_init (GtdTaskListClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gtd_task_list_finalize;
+ object_class->get_property = gtd_task_list_get_property;
+ object_class->set_property = gtd_task_list_set_property;
+
+ klass->get_archived = gtd_task_list_real_get_archived;
+ klass->set_archived = gtd_task_list_real_set_archived;
+
+ /**
+ * GtdTaskList::archived:
+ *
+ * Whether the task list is archived or not.
+ */
+ properties[PROP_ARCHIVED] = g_param_spec_boolean ("archived",
+ "Whether the list is archived",
+ "Whether the list is archived or not",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GtdTaskList::color:
+ *
+ * The color of the list.
+ */
+ properties[PROP_COLOR] = g_param_spec_boxed ("color",
+ "Color of the list",
+ "The color of the list",
+ GDK_TYPE_RGBA,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GtdTaskList::is-removable:
+ *
+ * Whether the task list can be removed from the system.
+ */
+ properties[PROP_IS_REMOVABLE] = g_param_spec_boolean ("is-removable",
+ "Whether the task list is removable",
+ "Whether the task list can be removed from the system",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GtdTaskList::name:
+ *
+ * The display name of the list.
+ */
+ properties[PROP_NAME] = g_param_spec_string ("name",
+ "Name of the list",
+ "The name of the list",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GtdTaskList::provider:
+ *
+ * The data provider of the list.
+ */
+ properties[PROP_PROVIDER] = g_param_spec_object ("provider",
+ "Provider of the list",
+ "The provider that handles the list",
+ GTD_TYPE_PROVIDER,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+
+ /**
+ * GtdTaskList::task-added:
+ * @list: a #GtdTaskList
+ * @task: a #GtdTask
+ *
+ * The ::task-added signal is emitted after a #GtdTask
+ * is added to the list.
+ */
+ signals[TASK_ADDED] = g_signal_new ("task-added",
+ GTD_TYPE_TASK_LIST,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GtdTaskListClass, task_added),
+ NULL,
+ NULL,
+ NULL,
+ G_TYPE_NONE,
+ 1,
+ GTD_TYPE_TASK);
+
+ /**
+ * GtdTaskList::task-removed:
+ * @list: a #GtdTaskList
+ * @task: a #GtdTask
+ *
+ * The ::task-removed signal is emitted after a #GtdTask
+ * is removed from the list.
+ */
+ signals[TASK_REMOVED] = g_signal_new ("task-removed",
+ GTD_TYPE_TASK_LIST,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GtdTaskListClass, task_removed),
+ NULL,
+ NULL,
+ NULL,
+ G_TYPE_NONE,
+ 1,
+ GTD_TYPE_TASK);
+
+ /**
+ * GtdTaskList::task-updated:
+ * @list: a #GtdTaskList
+ * @task: a #GtdTask
+ *
+ * The ::task-updated signal is emitted after a #GtdTask
+ * in the list is updated.
+ */
+ signals[TASK_UPDATED] = g_signal_new ("task-updated",
+ GTD_TYPE_TASK_LIST,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GtdTaskListClass, task_updated),
+ NULL,
+ NULL,
+ NULL,
+ G_TYPE_NONE,
+ 1,
+ GTD_TYPE_TASK);
+}
+
+static void
+gtd_task_list_init (GtdTaskList *self)
+{
+ GtdTaskListPrivate *priv = gtd_task_list_get_instance_private (self);
+
+ priv->task_to_uid = g_hash_table_new (g_str_hash, g_str_equal);
+ priv->tasks = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+ priv->sorted_tasks = g_sequence_new (g_object_unref);
+}
+
+/**
+ * gtd_task_list_new:
+ * @provider: (nullable): a #GtdProvider
+ *
+ * Creates a new list.
+ *
+ * Returns: (transfer full): the new #GtdTaskList
+ */
+GtdTaskList *
+gtd_task_list_new (GtdProvider *provider)
+{
+ return g_object_new (GTD_TYPE_TASK_LIST,
+ "provider", provider,
+ NULL);
+}
+
+/**
+ * gtd_task_list_get_color:
+ * @list: a #GtdTaskList
+ *
+ * Retrieves the color of %list. It is guarantee that it always returns a
+ * color, given a valid #GtdTaskList.
+ *
+ * Returns: (transfer full): the color of %list. Free with %gdk_rgba_free after use.
+ */
+GdkRGBA*
+gtd_task_list_get_color (GtdTaskList *list)
+{
+ GtdTaskListPrivate *priv;
+ GdkRGBA rgba;
+
+ g_return_val_if_fail (GTD_IS_TASK_LIST (list), NULL);
+
+ priv = gtd_task_list_get_instance_private (list);
+
+ if (!priv->color)
+ {
+ gdk_rgba_parse (&rgba, "#ffffff");
+ priv->color = gdk_rgba_copy (&rgba);
+ }
+
+ return gdk_rgba_copy (priv->color);
+}
+
+/**
+ * gtd_task_list_set_color:
+ * @list: a #GtdTaskList
+ * #color: a #GdkRGBA
+ *
+ * sets the color of @list.
+ */
+void
+gtd_task_list_set_color (GtdTaskList *list,
+ const GdkRGBA *color)
+{
+ GtdTaskListPrivate *priv;
+ GdkRGBA *current_color;
+
+ g_return_if_fail (GTD_IS_TASK_LIST (list));
+
+ priv = gtd_task_list_get_instance_private (list);
+ current_color = gtd_task_list_get_color (list);
+
+ if (!gdk_rgba_equal (current_color, color))
+ {
+ g_clear_pointer (&priv->color, gdk_rgba_free);
+ priv->color = gdk_rgba_copy (color);
+
+ g_object_notify (G_OBJECT (list), "color");
+ }
+
+ gdk_rgba_free (current_color);
+}
+
+/**
+ * gtd_task_list_get_name:
+ * @list: a #GtdTaskList
+ *
+ * Retrieves the user-visible name of @list, or %NULL.
+ *
+ * Returns: (transfer none): the internal name of @list. Do not free
+ * after use.
+ */
+const gchar*
+gtd_task_list_get_name (GtdTaskList *list)
+{
+ GtdTaskListPrivate *priv;
+
+ g_return_val_if_fail (GTD_IS_TASK_LIST (list), NULL);
+
+ priv = gtd_task_list_get_instance_private (list);
+
+ return priv->name;
+}
+
+/**
+ * gtd_task_list_set_name:
+ * @list: a #GtdTaskList
+ * @name: (nullable): the name of @list
+ *
+ * Sets the @list name to @name.
+ */
+void
+gtd_task_list_set_name (GtdTaskList *list,
+ const gchar *name)
+{
+ GtdTaskListPrivate *priv;
+
+ g_assert (GTD_IS_TASK_LIST (list));
+
+ priv = gtd_task_list_get_instance_private (list);
+
+ if (g_strcmp0 (priv->name, name) != 0)
+ {
+ g_free (priv->name);
+ priv->name = g_strdup (name);
+
+ g_object_notify (G_OBJECT (list), "name");
+ }
+}
+
+/**
+ * gtd_task_list_get_provider:
+ * @list: a #GtdTaskList
+ *
+ * Retrieves the #GtdProvider who owns this list.
+ *
+ * Returns: (transfer none): a #GtdProvider
+ */
+GtdProvider*
+gtd_task_list_get_provider (GtdTaskList *list)
+{
+ GtdTaskListPrivate *priv;
+
+ g_return_val_if_fail (GTD_IS_TASK_LIST (list), NULL);
+
+ priv = gtd_task_list_get_instance_private (list);
+
+ return priv->provider;
+}
+
+/**
+ * gtd_task_list_set_provider:
+ * @self: a #GtdTaskList
+ * @provider: (nullable): a #GtdProvider, or %NULL
+ *
+ * Sets the provider of this tasklist.
+ */
+void
+gtd_task_list_set_provider (GtdTaskList *self,
+ GtdProvider *provider)
+{
+ GtdTaskListPrivate *priv;
+
+ g_assert (GTD_IS_TASK_LIST (self));
+
+ priv = gtd_task_list_get_instance_private (self);
+
+ if (g_set_object (&priv->provider, provider))
+ g_object_notify (G_OBJECT (self), "provider");
+}
+
+/**
+ * gtd_task_list_add_task:
+ * @list: a #GtdTaskList
+ * @task: a #GtdTask
+ *
+ * Adds @task to @list.
+ */
+void
+gtd_task_list_add_task (GtdTaskList *self,
+ GtdTask *task)
+{
+ guint position;
+
+ g_assert (GTD_IS_TASK_LIST (self));
+ g_assert (GTD_IS_TASK (task));
+ g_assert (!gtd_task_list_contains (self, task));
+
+ position = add_task (self, task);
+ g_list_model_items_changed (G_LIST_MODEL (self), position, 0, 1);
+}
+
+/**
+ * gtd_task_list_update_task:
+ * @list: a #GtdTaskList
+ * @task: a #GtdTask
+ *
+ * Updates @task at @list.
+ */
+void
+gtd_task_list_update_task (GtdTaskList *self,
+ GtdTask *task)
+{
+ GtdTaskListPrivate *priv;
+ GSequenceIter *iter;
+
+ g_return_if_fail (GTD_IS_TASK_LIST (self));
+ g_return_if_fail (GTD_IS_TASK (task));
+
+ priv = gtd_task_list_get_instance_private (self);
+
+ g_return_if_fail (gtd_task_list_contains (self, task));
+
+ iter = g_hash_table_lookup (priv->tasks, gtd_object_get_uid (GTD_OBJECT (task)));
+
+ g_list_model_items_changed (G_LIST_MODEL (self),
+ g_sequence_iter_get_position (iter),
+ 1,
+ 1);
+
+ g_signal_emit (self, signals[TASK_UPDATED], 0, task);
+}
+
+/**
+ * gtd_task_list_remove_task:
+ * @list: a #GtdTaskList
+ * @task: a #GtdTask
+ *
+ * Removes @task from @list if it's inside the list.
+ */
+void
+gtd_task_list_remove_task (GtdTaskList *list,
+ GtdTask *task)
+{
+ guint position;
+
+ g_assert (GTD_IS_TASK_LIST (list));
+ g_assert (GTD_IS_TASK (task));
+ g_assert (gtd_task_list_contains (list, task));
+
+ position = remove_task (list, task);
+ g_list_model_items_changed (G_LIST_MODEL (list), position, 1, 0);
+}
+
+/**
+ * gtd_task_list_contains:
+ * @list: a #GtdTaskList
+ * @task: a #GtdTask
+ *
+ * Checks if @task is inside @list.
+ *
+ * Returns: %TRUE if @list contains @task, %FALSE otherwise
+ */
+gboolean
+gtd_task_list_contains (GtdTaskList *list,
+ GtdTask *task)
+{
+ GtdTaskListPrivate *priv;
+
+ g_assert (GTD_IS_TASK_LIST (list));
+ g_assert (GTD_IS_TASK (task));
+
+ priv = gtd_task_list_get_instance_private (list);
+
+ return g_hash_table_contains (priv->tasks, gtd_object_get_uid (GTD_OBJECT (task)));
+}
+
+/**
+ * gtd_task_list_get_is_removable:
+ * @list: a #GtdTaskList
+ *
+ * Retrieves whether @list can be removed or not.
+ *
+ * Returns: %TRUE if the @list can be removed, %FALSE otherwise
+ */
+gboolean
+gtd_task_list_is_removable (GtdTaskList *list)
+{
+ GtdTaskListPrivate *priv;
+
+ g_return_val_if_fail (GTD_IS_TASK_LIST (list), FALSE);
+
+ priv = gtd_task_list_get_instance_private (list);
+
+ return priv->removable;
+}
+
+/**
+ * gtd_task_list_set_is_removable:
+ * @list: a #GtdTaskList
+ * @is_removable: %TRUE if @list can be deleted, %FALSE otherwise
+ *
+ * Sets whether @list can be deleted or not.
+ */
+void
+gtd_task_list_set_is_removable (GtdTaskList *list,
+ gboolean is_removable)
+{
+ GtdTaskListPrivate *priv;
+
+ g_return_if_fail (GTD_IS_TASK_LIST (list));
+
+ priv = gtd_task_list_get_instance_private (list);
+
+ if (priv->removable != is_removable)
+ {
+ priv->removable = is_removable;
+
+ g_object_notify (G_OBJECT (list), "is-removable");
+ }
+}
+
+/**
+ * gtd_task_list_get_task_by_id:
+ * @self: a #GtdTaskList
+ * @id: the id of the task
+ *
+ * Retrieves a task from @self with the given @id.
+ *
+ * Returns: (transfer none)(nullable): a #GtdTask, or %NULL
+ */
+GtdTask*
+gtd_task_list_get_task_by_id (GtdTaskList *self,
+ const gchar *id)
+{
+ GtdTaskListPrivate *priv;
+ GSequenceIter *iter;
+
+ g_return_val_if_fail (GTD_IS_TASK_LIST (self), NULL);
+
+ priv = gtd_task_list_get_instance_private (self);
+ iter = g_hash_table_lookup (priv->tasks, id);
+
+ if (!iter)
+ return NULL;
+
+ return g_sequence_get (iter);
+}
+
+/**
+ * gtd_task_list_move_task_to_position:
+ * @self: a #GtdTaskList
+ * @task: a #GtdTask
+ * @new_position: the new position of @task inside @self
+ *
+ * Moves @task to @new_position, and repositions the elements
+ * in between as well.
+ *
+ * @task must belong to @self.
+ */
+void
+gtd_task_list_move_task_to_position (GtdTaskList *self,
+ GtdTask *task,
+ guint new_position)
+{
+
+ GtdTaskListPrivate *priv = gtd_task_list_get_instance_private (self);
+ GSequenceIter *new_position_iter;
+ GSequenceIter *iter;
+ guint old_position;
+ guint length;
+ guint start;
+ guint i;
+
+
+ g_return_if_fail (GTD_IS_TASK_LIST (self));
+ g_return_if_fail (GTD_IS_TASK (task));
+ g_return_if_fail (gtd_task_list_contains (self, task));
+ g_return_if_fail (g_list_model_get_n_items (G_LIST_MODEL (self)) >= new_position);
+
+ iter = g_hash_table_lookup (priv->tasks, gtd_object_get_uid (GTD_OBJECT (task)));
+ old_position = g_sequence_iter_get_position (iter);
+
+ if (old_position == new_position)
+ return;
+
+ GTD_TRACE_MSG ("Moving task '%s' (%s) from %u to %u",
+ gtd_task_get_title (task),
+ gtd_object_get_uid (GTD_OBJECT (task)),
+ old_position,
+ new_position);
+
+ /* Update the GSequence */
+ new_position_iter = new_position < old_position ?
+ g_sequence_get_iter_at_pos (priv->sorted_tasks, new_position) :
+ g_sequence_get_iter_at_pos (priv->sorted_tasks, new_position + 1);
+ g_sequence_move (iter, new_position_iter);
+
+ /* Update the 'position' property of all tasks in between */
+ priv->freeze_counter++;
+
+ length = ABS ((gint) new_position - (gint64) old_position) + 1;
+ start = MIN (old_position, new_position);
+ iter = g_sequence_get_iter_at_pos (priv->sorted_tasks, start);
+
+ for (i = 0; i < length; i++)
+ {
+ GtdTask *aux = g_sequence_get (iter);
+
+ g_signal_handlers_block_by_func (aux, task_changed_cb, self);
+ gtd_task_set_position (aux, start + i);
+ g_signal_handlers_unblock_by_func (aux, task_changed_cb, self);
+
+ gtd_provider_update_task (priv->provider, aux, NULL, on_task_updated_cb, self);
+
+ iter = g_sequence_iter_next (iter);
+ }
+
+ g_list_model_items_changed (G_LIST_MODEL (self), old_position, 1, 0);
+ g_list_model_items_changed (G_LIST_MODEL (self), new_position, 0, 1);
+
+ priv->freeze_counter--;
+}
+
+/**
+ * gtd_task_list_get_archived:
+ * @self: a #GtdTaskList
+ *
+ * Retrieves whether @self is archived or not. Archived task lists
+ * are hidden by default, and new tasks cannot be added.
+ *
+ * Returns: %TRUE if @self is archived, %FALSE otherwise.
+ */
+gboolean
+gtd_task_list_get_archived (GtdTaskList *self)
+{
+ g_return_val_if_fail (GTD_IS_TASK_LIST (self), FALSE);
+
+ return GTD_TASK_LIST_GET_CLASS (self)->get_archived (self);
+}
+
+/**
+ * gtd_task_list_set_archived:
+ * @self: a #GtdTaskList
+ * @archived: whether @self is archived or not
+ *
+ * Sets the "archive" property of @self to @archived.
+ */
+void
+gtd_task_list_set_archived (GtdTaskList *self,
+ gboolean archived)
+{
+ gboolean was_archived;
+
+ g_return_if_fail (GTD_IS_TASK_LIST (self));
+
+ was_archived = gtd_task_list_get_archived (self);
+
+ if (archived == was_archived)
+ return;
+
+ GTD_TASK_LIST_GET_CLASS (self)->set_archived (self, archived);
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ARCHIVED]);
+}
+
+/**
+ * gtd_task_list_import_task:
+ * @self: a #GtdTaskList
+ * @task: a #GtdTask
+ *
+ * Imports task into @self
+ */
+void
+gtd_task_list_import_task (GtdTaskList *self,
+ GtdTask *task,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr (GTask) gtask = NULL;
+ GtdProvider *provider;
+ ImportingTaskData *data;
+
+ g_return_if_fail (GTD_IS_TASK_LIST (self));
+
+ gtask = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_source_tag (gtask, gtd_task_list_import_task);
+
+ provider = gtd_task_get_provider (task);
+
+ data = g_new0 (ImportingTaskData, 1);
+ data->gtask = g_object_ref (gtask);
+ data->task = g_object_ref (task);
+
+ gtd_provider_create_task (provider,
+ self,
+ gtd_task_get_title (task),
+ gtd_task_get_due_date (task),
+ NULL,
+ on_import_new_task_added_cb,
+ data);
+}
+
+/**
+ * gtd_task_list_import_task_finish:
+ * @self: a #GtdTaskList
+ * @result: a #GAsyncResult
+ * @error: a #GError
+ *
+ * Imports task into @self
+ *
+ * Returns: (transfer full): a #GTask
+ */
+GTask *
+gtd_task_list_import_task_finish (GtdTaskList *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (g_task_is_valid (result, self), NULL);
+ g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == gtd_task_list_import_task, NULL);
+
+ return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+
+/**
+ * gtd_task_list_is_inbox:
+ * @self: a #GtdTaskList
+ *
+ * Retrieves whether @self is the inbox task list of its provider.
+ *
+ * Returns: %TRUE if @self is the inbox of it's provider, %FALSE otherwise.
+ */
+gboolean
+gtd_task_list_is_inbox (GtdTaskList *self)
+{
+ GtdTaskListPrivate *priv;
+
+ g_return_val_if_fail (GTD_IS_TASK_LIST (self), FALSE);
+
+ priv = gtd_task_list_get_instance_private (self);
+
+ return self == gtd_provider_get_inbox (priv->provider);
+}