1 /* gtd-task-list.c
2  *
3  * Copyright (C) 2015-2020 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #define G_LOG_DOMAIN "GtdTaskList"
20 
21 #include "gtd-debug.h"
22 #include "gtd-provider.h"
23 #include "gtd-task.h"
24 #include "gtd-task-list.h"
25 
26 #include <glib/gi18n.h>
27 
28 /**
29  * SECTION:gtd-task-list
30  * @short_description:a list of tasks
31  * @title:GtdTaskList
32  * @stability:Unstable
33  * @see_also:#GtdTask
34  *
35  * A #GtdTaskList represents a task list, and contains a list of tasks, a
36  * color, a name and the provider who generated it.
37  *
38  * Only a #GtdProvider can create a #GtdTaskList. Equally, a #GtdTaskList
39  * is only valid when associated with a #GtdProvider.
40  *
41  * It implements #GListModel, and can be used as the model for #GtkListBox.
42  */
43 
44 typedef struct
45 {
46   GtdProvider         *provider;
47   GdkRGBA             *color;
48 
49   GHashTable          *task_to_uid;
50   GHashTable          *tasks;
51   GSequence           *sorted_tasks;
52   guint                n_tasks;
53 
54   guint                freeze_counter;
55 
56   gchar               *name;
57   gboolean             removable;
58   gboolean             archived;
59 } GtdTaskListPrivate;
60 
61 
62 static gint          compare_tasks_cb                            (gconstpointer      a,
63                                                                   gconstpointer      b,
64                                                                   gpointer           user_data);
65 
66 static void          task_changed_cb                             (GtdTask            *task,
67                                                                   GParamSpec         *pspec,
68                                                                   GtdTaskList        *self);
69 
70 static void          g_list_model_iface_init                     (GListModelInterface *iface);
71 
72 
73 G_DEFINE_TYPE_WITH_CODE (GtdTaskList, gtd_task_list, GTD_TYPE_OBJECT,
74                          G_ADD_PRIVATE (GtdTaskList)
75                          G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, g_list_model_iface_init))
76 
77 enum
78 {
79   TASK_ADDED,
80   TASK_REMOVED,
81   TASK_UPDATED,
82   NUM_SIGNALS
83 };
84 
85 enum
86 {
87   PROP_0,
88   PROP_ARCHIVED,
89   PROP_COLOR,
90   PROP_IS_REMOVABLE,
91   PROP_NAME,
92   PROP_PROVIDER,
93   N_PROPS
94 };
95 
96 static guint signals[NUM_SIGNALS] = { 0, };
97 static GParamSpec *properties[N_PROPS] = { NULL, };
98 
99 
100 /*
101  * Auxiliary functions
102  */
103 
104 static void
update_task_uid(GtdTaskList * self,GtdTask * task)105 update_task_uid (GtdTaskList *self,
106                  GtdTask     *task)
107 {
108   GtdTaskListPrivate *priv;
109   GSequenceIter *iter;
110   const gchar *old_uid;
111   gchar *new_uid;
112 
113   priv = gtd_task_list_get_instance_private (self);
114 
115   g_debug ("Updating uid of task '%s'", gtd_task_get_title (task));
116 
117   new_uid = g_strdup (gtd_object_get_uid (GTD_OBJECT (task)));
118 
119   old_uid = g_hash_table_lookup (priv->task_to_uid, task);
120   iter = g_hash_table_lookup (priv->tasks, old_uid);
121 
122   g_assert (g_sequence_get (iter) == task);
123 
124   g_hash_table_remove (priv->tasks, old_uid);
125 
126   g_hash_table_insert (priv->task_to_uid, task, new_uid);
127   g_hash_table_insert (priv->tasks, new_uid, iter);
128 }
129 
130 static guint
add_task(GtdTaskList * self,GtdTask * task)131 add_task (GtdTaskList *self,
132           GtdTask     *task)
133 {
134   GtdTaskListPrivate *priv;
135   GSequenceIter *iter;
136   gchar *uid;
137 
138   priv = gtd_task_list_get_instance_private (self);
139 
140   uid = g_strdup (gtd_object_get_uid (GTD_OBJECT (task)));
141   iter = g_sequence_insert_sorted (priv->sorted_tasks,
142                                    g_object_ref (task),
143                                    compare_tasks_cb,
144                                    NULL);
145 
146   g_hash_table_insert (priv->task_to_uid, task, uid);
147   g_hash_table_insert (priv->tasks, uid, iter);
148 
149   g_signal_connect (task, "notify", G_CALLBACK (task_changed_cb), self);
150 
151   priv->n_tasks++;
152 
153   g_signal_emit (self, signals[TASK_ADDED], 0, task);
154 
155   return g_sequence_iter_get_position (iter);
156 }
157 
158 static guint
remove_task(GtdTaskList * self,GtdTask * task)159 remove_task (GtdTaskList *self,
160              GtdTask     *task)
161 {
162   GtdTaskListPrivate *priv;
163   GSequenceIter *iter;
164   const gchar *uid;
165   guint position;
166 
167   priv = gtd_task_list_get_instance_private (self);
168 
169   g_signal_handlers_disconnect_by_func (task, task_changed_cb, self);
170 
171   uid = gtd_object_get_uid (GTD_OBJECT (task));
172   iter = g_hash_table_lookup (priv->tasks, uid);
173   position = g_sequence_iter_get_position (iter);
174 
175   g_hash_table_remove (priv->task_to_uid, task);
176   g_hash_table_remove (priv->tasks, uid);
177 
178   g_sequence_remove (iter);
179 
180   priv->n_tasks--;
181 
182   g_signal_emit (self, signals[TASK_REMOVED], 0, task);
183 
184   return position;
185 }
186 
187 
188 /*
189  * Callbacks
190  */
191 
192 static void
on_task_updated_cb(GObject * object,GAsyncResult * result,gpointer user_data)193 on_task_updated_cb (GObject      *object,
194                     GAsyncResult *result,
195                     gpointer      user_data)
196 {
197   g_autoptr (GError) error = NULL;
198 
199   gtd_provider_update_task_finish (GTD_PROVIDER (object), result, &error);
200 
201   if (error)
202     {
203       g_warning ("Error updating task: %s", error->message);
204       return;
205     }
206 }
207 
208 static gint
compare_tasks_cb(gconstpointer a,gconstpointer b,gpointer user_data)209 compare_tasks_cb (gconstpointer a,
210                   gconstpointer b,
211                   gpointer      user_data)
212 {
213   return gtd_task_compare ((GtdTask*) a, (GtdTask*) b);
214 }
215 
216 static void
task_changed_cb(GtdTask * task,GParamSpec * pspec,GtdTaskList * self)217 task_changed_cb (GtdTask     *task,
218                  GParamSpec  *pspec,
219                  GtdTaskList *self)
220 {
221   GtdTaskListPrivate *priv;
222   GSequenceIter *iter;
223   guint old_position;
224   guint new_position;
225 
226   GTD_ENTRY;
227 
228   priv = gtd_task_list_get_instance_private (self);
229 
230   if (g_strcmp0 (g_param_spec_get_name (pspec), "loading") == 0)
231     GTD_RETURN ();
232 
233   if (g_strcmp0 (g_param_spec_get_name (pspec), "uid") == 0)
234     {
235       update_task_uid (self, task);
236       GTD_RETURN ();
237     }
238 
239   /* Don't update when the list is frozen */
240   if (priv->freeze_counter > 0)
241     GTD_RETURN ();
242 
243   iter = g_hash_table_lookup (priv->tasks, gtd_object_get_uid (GTD_OBJECT (task)));
244 
245   old_position = g_sequence_iter_get_position (iter);
246   g_sequence_sort_changed (iter, compare_tasks_cb, NULL);
247   new_position = g_sequence_iter_get_position (iter);
248 
249   if (old_position != new_position)
250     {
251       GTD_TRACE_MSG ("Old position: %u, New position: %u", old_position, new_position);
252 
253       g_list_model_items_changed (G_LIST_MODEL (self), old_position, 1, 0);
254       g_list_model_items_changed (G_LIST_MODEL (self), new_position, 0, 1);
255     }
256 
257   GTD_EXIT;
258 }
259 
260 
261 /*
262  * GListModel iface
263  */
264 
265 static GType
gtd_list_model_get_type(GListModel * model)266 gtd_list_model_get_type (GListModel *model)
267 {
268   return GTD_TYPE_TASK;
269 }
270 
271 static guint
gtd_list_model_get_n_items(GListModel * model)272 gtd_list_model_get_n_items (GListModel *model)
273 {
274   GtdTaskListPrivate *priv = gtd_task_list_get_instance_private (GTD_TASK_LIST (model));
275   return priv->n_tasks;
276 }
277 
278 static gpointer
gtd_list_model_get_item(GListModel * model,guint i)279 gtd_list_model_get_item (GListModel *model,
280                          guint       i)
281 {
282   GtdTaskListPrivate *priv;
283   GSequenceIter *iter;
284   GtdTask *task;
285 
286   priv = gtd_task_list_get_instance_private (GTD_TASK_LIST (model));
287   iter = g_sequence_get_iter_at_pos (priv->sorted_tasks, i);
288   task = g_sequence_get (iter);
289 
290   return g_object_ref (task);
291 }
292 
293 static void
g_list_model_iface_init(GListModelInterface * iface)294 g_list_model_iface_init (GListModelInterface *iface)
295 {
296   iface->get_item_type = gtd_list_model_get_type;
297   iface->get_n_items = gtd_list_model_get_n_items;
298   iface->get_item = gtd_list_model_get_item;
299 }
300 
301 
302 /*
303  * GtdTaskList overrides
304  */
305 
306 static gboolean
gtd_task_list_real_get_archived(GtdTaskList * self)307 gtd_task_list_real_get_archived (GtdTaskList *self)
308 {
309   GtdTaskListPrivate *priv = gtd_task_list_get_instance_private (self);
310 
311   return priv->archived;
312 }
313 
314 static void
gtd_task_list_real_set_archived(GtdTaskList * self,gboolean archived)315 gtd_task_list_real_set_archived (GtdTaskList *self,
316                                  gboolean     archived)
317 {
318   GtdTaskListPrivate *priv = gtd_task_list_get_instance_private (self);
319 
320   priv->archived = archived;
321 }
322 
323 
324 /*
325  * GObject overrides
326  */
327 
328 static void
gtd_task_list_finalize(GObject * object)329 gtd_task_list_finalize (GObject *object)
330 {
331   GtdTaskList *self = (GtdTaskList*) object;
332   GtdTaskListPrivate *priv = gtd_task_list_get_instance_private (self);
333 
334   g_clear_object (&priv->provider);
335 
336   g_clear_pointer (&priv->color, gdk_rgba_free);
337   g_clear_pointer (&priv->name, g_free);
338   g_clear_pointer (&priv->sorted_tasks, g_sequence_free);
339   g_clear_pointer (&priv->tasks, g_hash_table_destroy);
340   g_clear_pointer (&priv->task_to_uid, g_hash_table_destroy);
341 
342   G_OBJECT_CLASS (gtd_task_list_parent_class)->finalize (object);
343 }
344 
345 static void
gtd_task_list_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)346 gtd_task_list_get_property (GObject    *object,
347                             guint       prop_id,
348                             GValue     *value,
349                             GParamSpec *pspec)
350 {
351   GtdTaskList *self = GTD_TASK_LIST (object);
352   GtdTaskListPrivate *priv = gtd_task_list_get_instance_private (self);
353 
354   switch (prop_id)
355     {
356     case PROP_ARCHIVED:
357       g_value_set_boolean (value, gtd_task_list_get_archived (self));
358       break;
359 
360     case PROP_COLOR:
361       {
362         GdkRGBA *color = gtd_task_list_get_color (self);
363         g_value_set_boxed (value, color);
364         gdk_rgba_free (color);
365         break;
366       }
367 
368     case PROP_IS_REMOVABLE:
369       g_value_set_boolean (value, gtd_task_list_is_removable (self));
370       break;
371 
372     case PROP_NAME:
373       g_value_set_string (value, priv->name);
374       break;
375 
376     case PROP_PROVIDER:
377       g_value_set_object (value, priv->provider);
378       break;
379 
380     default:
381       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
382     }
383 }
384 
385 static void
gtd_task_list_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)386 gtd_task_list_set_property (GObject      *object,
387                             guint         prop_id,
388                             const GValue *value,
389                             GParamSpec   *pspec)
390 {
391   GtdTaskList *self = GTD_TASK_LIST (object);
392 
393   switch (prop_id)
394     {
395     case PROP_ARCHIVED:
396       gtd_task_list_set_archived (self, g_value_get_boolean (value));
397       break;
398 
399     case PROP_COLOR:
400       gtd_task_list_set_color (self, g_value_get_boxed (value));
401       break;
402 
403     case PROP_IS_REMOVABLE:
404       gtd_task_list_set_is_removable (self, g_value_get_boolean (value));
405       break;
406 
407     case PROP_NAME:
408       gtd_task_list_set_name (self, g_value_get_string (value));
409       break;
410 
411     case PROP_PROVIDER:
412       gtd_task_list_set_provider (self, g_value_get_object (value));
413       break;
414 
415     default:
416       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
417     }
418 }
419 
420 static void
gtd_task_list_class_init(GtdTaskListClass * klass)421 gtd_task_list_class_init (GtdTaskListClass *klass)
422 {
423   GObjectClass *object_class = G_OBJECT_CLASS (klass);
424 
425   object_class->finalize = gtd_task_list_finalize;
426   object_class->get_property = gtd_task_list_get_property;
427   object_class->set_property = gtd_task_list_set_property;
428 
429   klass->get_archived = gtd_task_list_real_get_archived;
430   klass->set_archived = gtd_task_list_real_set_archived;
431 
432   /**
433    * GtdTaskList::archived:
434    *
435    * Whether the task list is archived or not.
436    */
437   properties[PROP_ARCHIVED] = g_param_spec_boolean ("archived",
438                                                     "Whether the list is archived",
439                                                     "Whether the list is archived or not",
440                                                     FALSE,
441                                                     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
442   /**
443    * GtdTaskList::color:
444    *
445    * The color of the list.
446    */
447   properties[PROP_COLOR] = g_param_spec_boxed ("color",
448                                                "Color of the list",
449                                                "The color of the list",
450                                                GDK_TYPE_RGBA,
451                                                G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
452 
453   /**
454    * GtdTaskList::is-removable:
455    *
456    * Whether the task list can be removed from the system.
457    */
458   properties[PROP_IS_REMOVABLE] = g_param_spec_boolean ("is-removable",
459                                                         "Whether the task list is removable",
460                                                         "Whether the task list can be removed from the system",
461                                                         FALSE,
462                                                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
463 
464   /**
465    * GtdTaskList::name:
466    *
467    * The display name of the list.
468    */
469   properties[PROP_NAME] = g_param_spec_string ("name",
470                                                "Name of the list",
471                                                "The name of the list",
472                                                NULL,
473                                                G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
474 
475   /**
476    * GtdTaskList::provider:
477    *
478    * The data provider of the list.
479    */
480   properties[PROP_PROVIDER] =  g_param_spec_object ("provider",
481                                                     "Provider of the list",
482                                                     "The provider that handles the list",
483                                                     GTD_TYPE_PROVIDER,
484                                                     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
485 
486   g_object_class_install_properties (object_class, N_PROPS, properties);
487 
488   /**
489    * GtdTaskList::task-added:
490    * @list: a #GtdTaskList
491    * @task: a #GtdTask
492    *
493    * The ::task-added signal is emmited after a #GtdTask
494    * is added to the list.
495    */
496   signals[TASK_ADDED] = g_signal_new ("task-added",
497                                       GTD_TYPE_TASK_LIST,
498                                       G_SIGNAL_RUN_LAST,
499                                       G_STRUCT_OFFSET (GtdTaskListClass, task_added),
500                                       NULL,
501                                       NULL,
502                                       NULL,
503                                       G_TYPE_NONE,
504                                       1,
505                                       GTD_TYPE_TASK);
506 
507   /**
508    * GtdTaskList::task-removed:
509    * @list: a #GtdTaskList
510    * @task: a #GtdTask
511    *
512    * The ::task-removed signal is emmited after a #GtdTask
513    * is removed from the list.
514    */
515   signals[TASK_REMOVED] = g_signal_new ("task-removed",
516                                         GTD_TYPE_TASK_LIST,
517                                         G_SIGNAL_RUN_LAST,
518                                         G_STRUCT_OFFSET (GtdTaskListClass, task_removed),
519                                         NULL,
520                                         NULL,
521                                         NULL,
522                                         G_TYPE_NONE,
523                                         1,
524                                         GTD_TYPE_TASK);
525 
526   /**
527    * GtdTaskList::task-updated:
528    * @list: a #GtdTaskList
529    * @task: a #GtdTask
530    *
531    * The ::task-updated signal is emmited after a #GtdTask
532    * in the list is updated.
533    */
534   signals[TASK_UPDATED] = g_signal_new ("task-updated",
535                                       GTD_TYPE_TASK_LIST,
536                                       G_SIGNAL_RUN_LAST,
537                                       G_STRUCT_OFFSET (GtdTaskListClass, task_updated),
538                                       NULL,
539                                       NULL,
540                                       NULL,
541                                       G_TYPE_NONE,
542                                       1,
543                                       GTD_TYPE_TASK);
544 }
545 
546 static void
gtd_task_list_init(GtdTaskList * self)547 gtd_task_list_init (GtdTaskList *self)
548 {
549   GtdTaskListPrivate *priv = gtd_task_list_get_instance_private (self);
550 
551   priv->task_to_uid = g_hash_table_new (g_str_hash, g_str_equal);
552   priv->tasks = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
553   priv->sorted_tasks = g_sequence_new (g_object_unref);
554 }
555 
556 /**
557  * gtd_task_list_new:
558  * @provider: (nullable): a #GtdProvider
559  *
560  * Creates a new list.
561  *
562  * Returns: (transfer full): the new #GtdTaskList
563  */
564 GtdTaskList *
gtd_task_list_new(GtdProvider * provider)565 gtd_task_list_new (GtdProvider *provider)
566 {
567   return g_object_new (GTD_TYPE_TASK_LIST,
568                        "provider", provider,
569                        NULL);
570 }
571 
572 /**
573  * gtd_task_list_get_color:
574  * @list: a #GtdTaskList
575  *
576  * Retrieves the color of %list. It is guarantee that it always returns a
577  * color, given a valid #GtdTaskList.
578  *
579  * Returns: (transfer full): the color of %list. Free with %gdk_rgba_free after use.
580  */
581 GdkRGBA*
gtd_task_list_get_color(GtdTaskList * list)582 gtd_task_list_get_color (GtdTaskList *list)
583 {
584   GtdTaskListPrivate *priv;
585   GdkRGBA rgba;
586 
587   g_return_val_if_fail (GTD_IS_TASK_LIST (list), NULL);
588 
589   priv = gtd_task_list_get_instance_private (list);
590 
591   if (!priv->color)
592     {
593       gdk_rgba_parse (&rgba, "#ffffff");
594       priv->color = gdk_rgba_copy (&rgba);
595     }
596 
597   return gdk_rgba_copy (priv->color);
598 }
599 
600 /**
601  * gtd_task_list_set_color:
602  * @list: a #GtdTaskList
603  * #color: a #GdkRGBA
604  *
605  * sets the color of @list.
606  */
607 void
gtd_task_list_set_color(GtdTaskList * list,const GdkRGBA * color)608 gtd_task_list_set_color (GtdTaskList   *list,
609                          const GdkRGBA *color)
610 {
611   GtdTaskListPrivate *priv;
612   GdkRGBA *current_color;
613 
614   g_return_if_fail (GTD_IS_TASK_LIST (list));
615 
616   priv = gtd_task_list_get_instance_private (list);
617   current_color = gtd_task_list_get_color (list);
618 
619   if (!gdk_rgba_equal (current_color, color))
620     {
621       g_clear_pointer (&priv->color, gdk_rgba_free);
622       priv->color = gdk_rgba_copy (color);
623 
624       g_object_notify (G_OBJECT (list), "color");
625     }
626 
627   gdk_rgba_free (current_color);
628 }
629 
630 /**
631  * gtd_task_list_get_name:
632  * @list: a #GtdTaskList
633  *
634  * Retrieves the user-visible name of @list, or %NULL.
635  *
636  * Returns: (transfer none): the internal name of @list. Do not free
637  * after use.
638  */
639 const gchar*
gtd_task_list_get_name(GtdTaskList * list)640 gtd_task_list_get_name (GtdTaskList *list)
641 {
642   GtdTaskListPrivate *priv;
643 
644   g_return_val_if_fail (GTD_IS_TASK_LIST (list), NULL);
645 
646   priv = gtd_task_list_get_instance_private (list);
647 
648   return priv->name;
649 }
650 
651 /**
652  * gtd_task_list_set_name:
653  * @list: a #GtdTaskList
654  * @name: (nullable): the name of @list
655  *
656  * Sets the @list name to @name.
657  */
658 void
gtd_task_list_set_name(GtdTaskList * list,const gchar * name)659 gtd_task_list_set_name (GtdTaskList *list,
660                         const gchar *name)
661 {
662   GtdTaskListPrivate *priv;
663 
664   g_assert (GTD_IS_TASK_LIST (list));
665 
666   priv = gtd_task_list_get_instance_private (list);
667 
668   if (g_strcmp0 (priv->name, name) != 0)
669     {
670       g_free (priv->name);
671       priv->name = g_strdup (name);
672 
673       g_object_notify (G_OBJECT (list), "name");
674     }
675 }
676 
677 /**
678  * gtd_task_list_get_provider:
679  * @list: a #GtdTaskList
680  *
681  * Retrieves the #GtdProvider who owns this list.
682  *
683  * Returns: (transfer none): a #GtdProvider
684  */
685 GtdProvider*
gtd_task_list_get_provider(GtdTaskList * list)686 gtd_task_list_get_provider (GtdTaskList *list)
687 {
688   GtdTaskListPrivate *priv;
689 
690   g_return_val_if_fail (GTD_IS_TASK_LIST (list), NULL);
691 
692   priv = gtd_task_list_get_instance_private (list);
693 
694   return priv->provider;
695 }
696 
697 /**
698  * gtd_task_list_set_provider:
699  * @self: a #GtdTaskList
700  * @provider: (nullable): a #GtdProvider, or %NULL
701  *
702  * Sets the provider of this tasklist.
703  */
704 void
gtd_task_list_set_provider(GtdTaskList * self,GtdProvider * provider)705 gtd_task_list_set_provider (GtdTaskList *self,
706                             GtdProvider *provider)
707 {
708   GtdTaskListPrivate *priv;
709 
710   g_assert (GTD_IS_TASK_LIST (self));
711 
712   priv = gtd_task_list_get_instance_private (self);
713 
714   if (g_set_object (&priv->provider, provider))
715     g_object_notify (G_OBJECT (self), "provider");
716 }
717 
718 /**
719  * gtd_task_list_add_task:
720  * @list: a #GtdTaskList
721  * @task: a #GtdTask
722  *
723  * Adds @task to @list.
724  */
725 void
gtd_task_list_add_task(GtdTaskList * self,GtdTask * task)726 gtd_task_list_add_task (GtdTaskList *self,
727                         GtdTask     *task)
728 {
729   guint position;
730 
731   g_assert (GTD_IS_TASK_LIST (self));
732   g_assert (GTD_IS_TASK (task));
733   g_assert (!gtd_task_list_contains (self, task));
734 
735   position = add_task (self, task);
736   g_list_model_items_changed (G_LIST_MODEL (self), position, 0, 1);
737 }
738 
739 /**
740  * gtd_task_list_update_task:
741  * @list: a #GtdTaskList
742  * @task: a #GtdTask
743  *
744  * Updates @task at @list.
745  */
746 void
gtd_task_list_update_task(GtdTaskList * self,GtdTask * task)747 gtd_task_list_update_task (GtdTaskList *self,
748                            GtdTask     *task)
749 {
750   GtdTaskListPrivate *priv;
751   GSequenceIter *iter;
752 
753   g_return_if_fail (GTD_IS_TASK_LIST (self));
754   g_return_if_fail (GTD_IS_TASK (task));
755 
756   priv = gtd_task_list_get_instance_private (self);
757 
758   g_return_if_fail (gtd_task_list_contains (self, task));
759 
760   iter = g_hash_table_lookup (priv->tasks, gtd_object_get_uid (GTD_OBJECT (task)));
761 
762   g_list_model_items_changed (G_LIST_MODEL (self),
763                               g_sequence_iter_get_position (iter),
764                               1,
765                               1);
766 
767   g_signal_emit (self, signals[TASK_UPDATED], 0, task);
768 }
769 
770 /**
771  * gtd_task_list_remove_task:
772  * @list: a #GtdTaskList
773  * @task: a #GtdTask
774  *
775  * Removes @task from @list if it's inside the list.
776  */
777 void
gtd_task_list_remove_task(GtdTaskList * list,GtdTask * task)778 gtd_task_list_remove_task (GtdTaskList *list,
779                            GtdTask     *task)
780 {
781   guint position;
782 
783   g_assert (GTD_IS_TASK_LIST (list));
784   g_assert (GTD_IS_TASK (task));
785   g_assert (gtd_task_list_contains (list, task));
786 
787   position = remove_task (list, task);
788   g_list_model_items_changed (G_LIST_MODEL (list), position, 1, 0);
789 }
790 
791 /**
792  * gtd_task_list_contains:
793  * @list: a #GtdTaskList
794  * @task: a #GtdTask
795  *
796  * Checks if @task is inside @list.
797  *
798  * Returns: %TRUE if @list contains @task, %FALSE otherwise
799  */
800 gboolean
gtd_task_list_contains(GtdTaskList * list,GtdTask * task)801 gtd_task_list_contains (GtdTaskList *list,
802                         GtdTask     *task)
803 {
804   GtdTaskListPrivate *priv;
805 
806   g_assert (GTD_IS_TASK_LIST (list));
807   g_assert (GTD_IS_TASK (task));
808 
809   priv = gtd_task_list_get_instance_private (list);
810 
811   return g_hash_table_contains (priv->tasks, gtd_object_get_uid (GTD_OBJECT (task)));
812 }
813 
814 /**
815  * gtd_task_list_get_is_removable:
816  * @list: a #GtdTaskList
817  *
818  * Retrieves whether @list can be removed or not.
819  *
820  * Returns: %TRUE if the @list can be removed, %FALSE otherwise
821  */
822 gboolean
gtd_task_list_is_removable(GtdTaskList * list)823 gtd_task_list_is_removable (GtdTaskList *list)
824 {
825   GtdTaskListPrivate *priv;
826 
827   g_return_val_if_fail (GTD_IS_TASK_LIST (list), FALSE);
828 
829   priv = gtd_task_list_get_instance_private (list);
830 
831   return priv->removable;
832 }
833 
834 /**
835  * gtd_task_list_set_is_removable:
836  * @list: a #GtdTaskList
837  * @is_removable: %TRUE if @list can be deleted, %FALSE otherwise
838  *
839  * Sets whether @list can be deleted or not.
840  */
841 void
gtd_task_list_set_is_removable(GtdTaskList * list,gboolean is_removable)842 gtd_task_list_set_is_removable (GtdTaskList *list,
843                                 gboolean     is_removable)
844 {
845   GtdTaskListPrivate *priv;
846 
847   g_return_if_fail (GTD_IS_TASK_LIST (list));
848 
849   priv = gtd_task_list_get_instance_private (list);
850 
851   if (priv->removable != is_removable)
852     {
853       priv->removable = is_removable;
854 
855       g_object_notify (G_OBJECT (list), "is-removable");
856     }
857 }
858 
859 /**
860  * gtd_task_list_get_task_by_id:
861  * @list: a #GtdTaskList
862  * @id: the id of the task
863  *
864  * Retrieves a task from @self with the given @id.
865  *
866  * Returns: (transfer none)(nullable): a #GtdTask, or %NULL
867  */
868 GtdTask*
gtd_task_list_get_task_by_id(GtdTaskList * self,const gchar * id)869 gtd_task_list_get_task_by_id (GtdTaskList *self,
870                               const gchar *id)
871 {
872   GtdTaskListPrivate *priv;
873   GSequenceIter *iter;
874 
875   g_return_val_if_fail (GTD_IS_TASK_LIST (self), NULL);
876 
877   priv = gtd_task_list_get_instance_private (self);
878   iter = g_hash_table_lookup (priv->tasks, id);
879 
880   if (!iter)
881     return NULL;
882 
883   return g_sequence_get (iter);
884 }
885 
886 /**
887  * gtd_task_list_move_task_to_position:
888  * @self: a #GtdTaskList
889  * @task: a #GtdTask
890  * @new_position: the new position of @task inside @self
891  *
892  * Moves @task to @new_position, and repositions the elements
893  * in between as well.
894  *
895  * @task must belog to @self.
896  */
897 void
gtd_task_list_move_task_to_position(GtdTaskList * self,GtdTask * task,guint new_position)898 gtd_task_list_move_task_to_position (GtdTaskList *self,
899                                      GtdTask     *task,
900                                      guint        new_position)
901 {
902 
903   GtdTaskListPrivate *priv = gtd_task_list_get_instance_private (self);
904   GSequenceIter *new_position_iter;
905   GSequenceIter *iter;
906   guint old_position;
907   guint length;
908   guint start;
909   guint i;
910 
911 
912   g_return_if_fail (GTD_IS_TASK_LIST (self));
913   g_return_if_fail (GTD_IS_TASK (task));
914   g_return_if_fail (gtd_task_list_contains (self, task));
915   g_return_if_fail (g_list_model_get_n_items (G_LIST_MODEL (self)) >= new_position);
916 
917   iter = g_hash_table_lookup (priv->tasks, gtd_object_get_uid (GTD_OBJECT (task)));
918   old_position = g_sequence_iter_get_position (iter);
919 
920   if (old_position == new_position)
921     return;
922 
923   GTD_TRACE_MSG ("Moving task '%s' (%s) from %u to %u",
924                  gtd_task_get_title (task),
925                  gtd_object_get_uid (GTD_OBJECT (task)),
926                  old_position,
927                  new_position);
928 
929   /* Update the GSequence */
930   new_position_iter = new_position < old_position ?
931                       g_sequence_get_iter_at_pos (priv->sorted_tasks, new_position) :
932                       g_sequence_get_iter_at_pos (priv->sorted_tasks, new_position + 1);
933   g_sequence_move (iter, new_position_iter);
934 
935   /* Update the 'position' property of all tasks in between */
936   priv->freeze_counter++;
937 
938   length = ABS ((gint) new_position - (gint64) old_position) + 1;
939   start = MIN (old_position, new_position);
940   iter = g_sequence_get_iter_at_pos (priv->sorted_tasks, start);
941 
942   for (i = 0; i < length; i++)
943     {
944       GtdTask *aux = g_sequence_get (iter);
945 
946       g_signal_handlers_block_by_func (aux, task_changed_cb, self);
947       gtd_task_set_position (aux, start + i);
948       g_signal_handlers_unblock_by_func (aux, task_changed_cb, self);
949 
950       gtd_provider_update_task (priv->provider, aux, NULL, on_task_updated_cb, self);
951 
952       iter = g_sequence_iter_next (iter);
953     }
954 
955   g_list_model_items_changed (G_LIST_MODEL (self), old_position, 1, 0);
956   g_list_model_items_changed (G_LIST_MODEL (self), new_position, 0, 1);
957 
958   priv->freeze_counter--;
959 }
960 
961 /**
962  * gtd_task_list_get_archived:
963  * @self: a #GtdTaskList
964  *
965  * Retrieves whether @self is archived or not. Archived task lists
966  * are hidden by default, and new tasks cannot be added.
967  *
968  * Returns: %TRUE if @self is archived, %FALSE otherwise.
969  */
970 gboolean
gtd_task_list_get_archived(GtdTaskList * self)971 gtd_task_list_get_archived (GtdTaskList *self)
972 {
973   g_return_val_if_fail (GTD_IS_TASK_LIST (self), FALSE);
974 
975   return GTD_TASK_LIST_GET_CLASS (self)->get_archived (self);
976 }
977 
978 /**
979  * gtd_task_list_set_archived:
980  * @self: a #GtdTaskList
981  * @archived: whether @self is archived or not
982  *
983  * Sets the "archive" property of @self to @archived.
984  */
985 void
gtd_task_list_set_archived(GtdTaskList * self,gboolean archived)986 gtd_task_list_set_archived (GtdTaskList *self,
987                             gboolean     archived)
988 {
989   gboolean was_archived;
990 
991   g_return_if_fail (GTD_IS_TASK_LIST (self));
992 
993   was_archived = gtd_task_list_get_archived (self);
994 
995   if (archived == was_archived)
996     return;
997 
998   GTD_TASK_LIST_GET_CLASS (self)->set_archived (self, archived);
999 
1000   g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ARCHIVED]);
1001 }
1002 
1003 /**
1004  * gtd_task_list_is_inbox:
1005  * @self: a #GtdTaskList
1006  *
1007  * Retrieves whether @self is the inbox task list of its provider.
1008  *
1009  * Returns: %TRUE if @self is the inbox of it's provider, %FALSE otherwise.
1010  */
1011 gboolean
gtd_task_list_is_inbox(GtdTaskList * self)1012 gtd_task_list_is_inbox (GtdTaskList *self)
1013 {
1014   GtdTaskListPrivate *priv;
1015 
1016   g_return_val_if_fail (GTD_IS_TASK_LIST (self), FALSE);
1017 
1018   priv = gtd_task_list_get_instance_private (self);
1019 
1020   return self == gtd_provider_get_inbox (priv->provider);
1021 }
1022