1 /* gtd-sidebar.c
2  *
3  * Copyright 2018-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  * SPDX-License-Identifier: GPL-3.0-or-later
19  */
20 
21 #define G_LOG_DOMAIN "GtdSidebar"
22 
23 #include "gtd-debug.h"
24 #include "gtd-manager.h"
25 #include "gtd-max-size-layout.h"
26 #include "gtd-notification.h"
27 #include "gtd-panel.h"
28 #include "gtd-provider.h"
29 #include "gtd-sidebar.h"
30 #include "gtd-sidebar-list-row.h"
31 #include "gtd-sidebar-panel-row.h"
32 #include "gtd-sidebar-provider-row.h"
33 #include "gtd-task-list.h"
34 #include "gtd-task-list-panel.h"
35 #include "gtd-utils.h"
36 
37 #include <glib/gi18n.h>
38 
39 struct _GtdSidebar
40 {
41   GtdWidget           parent;
42 
43   GtkListBox         *archive_listbox;
44   GtkListBoxRow      *archive_row;
45   GtkListBox         *listbox;
46   GtkStack           *stack;
47 
48   GtkStack           *panel_stack;
49   GtdPanel           *task_list_panel;
50 
51   GSimpleActionGroup *action_group;
52 };
53 
G_DEFINE_TYPE(GtdSidebar,gtd_sidebar,GTD_TYPE_WIDGET)54 G_DEFINE_TYPE (GtdSidebar, gtd_sidebar, GTD_TYPE_WIDGET)
55 
56 
57 /*
58  * Auxiliary methods
59  */
60 
61 static gboolean
62 activate_row_below (GtdSidebar        *self,
63                     GtdSidebarListRow *current_row)
64 {
65   GtkWidget *next_row;
66   GtkWidget *parent;
67   GtkWidget *child;
68   gboolean after_deleted;
69 
70   parent = gtk_widget_get_parent (GTK_WIDGET (current_row));
71   after_deleted = FALSE;
72   next_row = NULL;
73 
74   for (child = gtk_widget_get_first_child (parent);
75        child;
76        child = gtk_widget_get_next_sibling (child))
77     {
78       if (child == (GtkWidget*) current_row)
79         {
80           after_deleted = TRUE;
81           continue;
82         }
83 
84       if (!gtk_widget_get_visible (child) ||
85           !gtk_list_box_row_get_activatable (GTK_LIST_BOX_ROW (child)))
86         {
87           continue;
88         }
89 
90       next_row = child;
91 
92       if (after_deleted)
93         break;
94     }
95 
96   if (next_row)
97     g_signal_emit_by_name (next_row, "activate");
98 
99   return next_row != NULL;
100 }
101 
102 static void
add_task_list(GtdSidebar * self,GtdTaskList * list)103 add_task_list (GtdSidebar  *self,
104                GtdTaskList *list)
105 {
106   if (gtd_task_list_is_inbox (list))
107     return;
108 
109   g_debug ("Adding task list '%s'", gtd_task_list_get_name (list));
110 
111   if (!gtd_task_list_get_archived (list))
112     {
113       gtk_list_box_prepend (self->listbox, gtd_sidebar_list_row_new (list));
114       gtk_list_box_invalidate_filter (self->listbox);
115     }
116   else
117     {
118       gtk_list_box_prepend (self->archive_listbox, gtd_sidebar_list_row_new (list));
119       gtk_list_box_invalidate_filter (self->archive_listbox);
120     }
121 }
122 
123 static void
add_panel(GtdSidebar * self,GtdPanel * panel)124 add_panel (GtdSidebar *self,
125            GtdPanel   *panel)
126 {
127   GtkWidget *row;
128 
129   g_debug ("Adding panel '%s'", gtd_panel_get_panel_name (panel));
130 
131   row = gtd_sidebar_panel_row_new (panel);
132 
133   gtk_list_box_prepend (self->listbox, row);
134 }
135 
136 static void
add_provider(GtdSidebar * self,GtdProvider * provider)137 add_provider (GtdSidebar  *self,
138               GtdProvider *provider)
139 {
140   g_debug ("Adding provider '%s'", gtd_provider_get_name (provider));
141 
142   gtk_list_box_prepend (self->listbox, gtd_sidebar_provider_row_new (provider));
143   gtk_list_box_prepend (self->archive_listbox, gtd_sidebar_provider_row_new (provider));
144 }
145 
146 static gint
compare_panels(GtdSidebarPanelRow * row_a,GtdSidebarPanelRow * row_b)147 compare_panels (GtdSidebarPanelRow *row_a,
148                 GtdSidebarPanelRow *row_b)
149 {
150   GtdPanel *panel_a;
151   GtdPanel *panel_b;
152 
153   panel_a = gtd_sidebar_panel_row_get_panel (row_a);
154   panel_b = gtd_sidebar_panel_row_get_panel (row_b);
155 
156   return gtd_panel_get_priority (panel_b) - gtd_panel_get_priority (panel_a);
157 }
158 
159 static gint
compare_providers(GtdSidebarProviderRow * row_a,GtdSidebarProviderRow * row_b)160 compare_providers (GtdSidebarProviderRow *row_a,
161                    GtdSidebarProviderRow *row_b)
162 {
163   GtdProvider *provider_a;
164   GtdProvider *provider_b;
165 
166   provider_a = gtd_sidebar_provider_row_get_provider (row_a);
167   provider_b = gtd_sidebar_provider_row_get_provider (row_b);
168 
169   return gtd_provider_compare (provider_a, provider_b);
170 }
171 
172 static gint
compare_lists(GtdSidebarListRow * row_a,GtdSidebarListRow * row_b)173 compare_lists (GtdSidebarListRow *row_a,
174                GtdSidebarListRow *row_b)
175 {
176   GtdTaskList *list_a;
177   GtdTaskList *list_b;
178   gint result;
179 
180   list_a = gtd_sidebar_list_row_get_task_list (row_a);
181   list_b = gtd_sidebar_list_row_get_task_list (row_b);
182 
183   /* First, compare by their providers */
184   result = gtd_provider_compare (gtd_task_list_get_provider (list_a), gtd_task_list_get_provider (list_b));
185 
186   if (result != 0)
187     return result;
188 
189   return gtd_collate_compare_strings (gtd_task_list_get_name (list_a), gtd_task_list_get_name (list_b));
190 }
191 
192 typedef gpointer (*GetDataFunc) (gpointer data);
193 
194 static gpointer
get_row_internal(GtdSidebar * self,GtkListBox * listbox,GType type,GetDataFunc get_data_func,gpointer data)195 get_row_internal (GtdSidebar  *self,
196                   GtkListBox  *listbox,
197                   GType        type,
198                   GetDataFunc  get_data_func,
199                   gpointer     data)
200 {
201   GtkWidget *child;
202 
203   for (child = gtk_widget_get_first_child (GTK_WIDGET (listbox));
204        child;
205        child = gtk_widget_get_next_sibling (child))
206     {
207       if (g_type_is_a (G_OBJECT_TYPE (child), type) && get_data_func (child) == data)
208           return child;
209     }
210 
211   return NULL;
212 }
213 
214 static GtkListBoxRow*
get_row_for_panel(GtdSidebar * self,GtdPanel * panel)215 get_row_for_panel (GtdSidebar *self,
216                    GtdPanel   *panel)
217 {
218   return get_row_internal (self,
219                            self->listbox,
220                            GTD_TYPE_SIDEBAR_PANEL_ROW,
221                            (GetDataFunc) gtd_sidebar_panel_row_get_panel,
222                            panel);
223 }
224 
225 static GtkListBoxRow*
get_row_for_provider(GtdSidebar * self,GtkListBox * listbox,GtdProvider * provider)226 get_row_for_provider (GtdSidebar  *self,
227                       GtkListBox  *listbox,
228                       GtdProvider *provider)
229 {
230   return get_row_internal (self,
231                            listbox,
232                            GTD_TYPE_SIDEBAR_PROVIDER_ROW,
233                            (GetDataFunc) gtd_sidebar_provider_row_get_provider,
234                            provider);
235 }
236 
237 static GtkListBoxRow*
get_row_for_task_list(GtdSidebar * self,GtkListBox * listbox,GtdTaskList * list)238 get_row_for_task_list (GtdSidebar  *self,
239                        GtkListBox  *listbox,
240                        GtdTaskList *list)
241 {
242   return get_row_internal (self,
243                            listbox,
244                            GTD_TYPE_SIDEBAR_LIST_ROW,
245                            (GetDataFunc) gtd_sidebar_list_row_get_task_list,
246                            list);
247 }
248 
249 static void
activate_appropriate_row(GtdSidebar * self,GtkListBoxRow * row)250 activate_appropriate_row (GtdSidebar    *self,
251                           GtkListBoxRow *row)
252 {
253   GtkListBoxRow *to_be_activated;
254 
255   if (activate_row_below (self, GTD_SIDEBAR_LIST_ROW (row)))
256     return;
257 
258   gtk_widget_activate_action (GTK_WIDGET (self),
259                               "task-lists-workspace.toggle-archive",
260                               "b",
261                               FALSE);
262 
263   to_be_activated = gtk_list_box_get_row_at_index (self->listbox, 0);
264   g_signal_emit_by_name (to_be_activated, "activate");
265 }
266 
267 /*
268  * Callbacks
269  */
270 
271 static void
on_action_move_up_activated_cb(GSimpleAction * simple,GVariant * parameters,gpointer user_data)272 on_action_move_up_activated_cb (GSimpleAction *simple,
273                                 GVariant      *parameters,
274                                 gpointer       user_data)
275 {
276   GtkListBoxRow *selected_row;
277   GtkListBoxRow *previous_row;
278   GtdSidebar *self;
279   gint selected_row_index;
280 
281   GTD_ENTRY;
282 
283   self = GTD_SIDEBAR (user_data);
284   selected_row = gtk_list_box_get_selected_row (self->listbox);
285   g_assert (selected_row != NULL);
286 
287   selected_row_index = gtk_list_box_row_get_index (selected_row);
288   if (selected_row_index == 0)
289     return;
290 
291   do
292     {
293       previous_row = gtk_list_box_get_row_at_index (self->listbox,
294                                                     --selected_row_index);
295     }
296   while (previous_row &&
297          (previous_row == self->archive_row ||
298           !gtk_list_box_row_get_activatable (previous_row)));
299 
300 
301   if (previous_row)
302     g_signal_emit_by_name (previous_row, "activate");
303 
304   GTD_EXIT;
305 }
306 
307 static void
on_action_move_down_activated_cb(GSimpleAction * simple,GVariant * parameters,gpointer user_data)308 on_action_move_down_activated_cb (GSimpleAction *simple,
309                                   GVariant      *parameters,
310                                   gpointer       user_data)
311 {
312   GtkListBoxRow *selected_row;
313   GtkListBoxRow *next_row;
314   GtdSidebar *self;
315   gint selected_row_index;
316 
317   GTD_ENTRY;
318 
319   self = GTD_SIDEBAR (user_data);
320   selected_row = gtk_list_box_get_selected_row (self->listbox);
321   g_assert (selected_row != NULL);
322 
323   selected_row_index = gtk_list_box_row_get_index (selected_row);
324 
325   do
326     {
327       next_row = gtk_list_box_get_row_at_index (self->listbox,
328                                                 ++selected_row_index);
329     }
330   while (next_row &&
331          (next_row == self->archive_row ||
332           !gtk_list_box_row_get_activatable (next_row)));
333 
334 
335   if (next_row)
336     g_signal_emit_by_name (next_row, "activate");
337 
338   GTD_EXIT;
339 }
340 
341 static void
on_panel_added_cb(GtdManager * manager,GtdPanel * panel,GtdSidebar * self)342 on_panel_added_cb (GtdManager *manager,
343                    GtdPanel   *panel,
344                    GtdSidebar *self)
345 {
346   add_panel (self, panel);
347 }
348 
349 static void
on_panel_removed_cb(GtdManager * manager,GtdPanel * panel,GtdSidebar * self)350 on_panel_removed_cb (GtdManager *manager,
351                      GtdPanel   *panel,
352                      GtdSidebar *self)
353 {
354   GtkListBoxRow *row = get_row_for_panel (self, panel);
355 
356   g_debug ("Removing panel '%s'", gtd_panel_get_panel_name (panel));
357 
358   if (row)
359     gtk_list_box_remove (self->listbox, GTK_WIDGET (row));
360 }
361 
362 static void
on_provider_task_list_removed_cb(GObject * source,GAsyncResult * result,gpointer user_data)363 on_provider_task_list_removed_cb (GObject      *source,
364                                   GAsyncResult *result,
365                                   gpointer      user_data)
366 {
367   g_autoptr (GError) error = NULL;
368 
369   gtd_provider_remove_task_list_finish (GTD_PROVIDER (source), result, &error);
370 }
371 
372 static void
delete_list_cb(GtdNotification * notification,gpointer user_data)373 delete_list_cb (GtdNotification *notification,
374                 gpointer         user_data)
375 {
376   GtdTaskList *list;
377   GtdProvider *provider;
378 
379   list = GTD_TASK_LIST (user_data);
380   provider = gtd_task_list_get_provider (list);
381 
382   g_assert (provider != NULL);
383   g_assert (gtd_task_list_is_removable (list));
384 
385   gtd_provider_remove_task_list (provider,
386                                  list,
387                                  NULL,
388                                  on_provider_task_list_removed_cb,
389                                  NULL);
390 }
391 
392 static void
undo_delete_list_cb(GtdNotification * notification,gpointer user_data)393 undo_delete_list_cb (GtdNotification *notification,
394                      gpointer         user_data)
395 {
396   g_assert (GTD_IS_SIDEBAR_LIST_ROW (user_data));
397 
398   gtk_widget_show (GTK_WIDGET (user_data));
399 }
400 
401 static void
on_task_list_panel_list_deleted_cb(GtdTaskListPanel * panel,GtdTaskList * list,GtdSidebar * self)402 on_task_list_panel_list_deleted_cb (GtdTaskListPanel *panel,
403                                     GtdTaskList      *list,
404                                     GtdSidebar       *self)
405 {
406   GtdSidebarListRow *row;
407   GtdNotification *notification;
408   g_autofree gchar *title = NULL;
409 
410   if (gtd_task_list_get_archived (list))
411     row = (GtdSidebarListRow*) get_row_for_task_list (self, self->archive_listbox, list);
412   else
413     row = (GtdSidebarListRow*) get_row_for_task_list (self, self->listbox, list);
414 
415   g_assert (row != NULL && GTD_IS_SIDEBAR_LIST_ROW (row));
416 
417   GTD_TRACE_MSG ("Removing task list row from sidebar");
418 
419   title = g_strdup_printf (_("Task list <b>%s</b> removed"), gtd_task_list_get_name (list));
420   notification = gtd_notification_new (title, 6000.0);
421   gtd_notification_set_primary_action (notification, delete_list_cb, list);
422   gtd_notification_set_secondary_action (notification, _("Undo"), undo_delete_list_cb, row);
423 
424   gtd_manager_send_notification (gtd_manager_get_default (), notification);
425 
426   /*
427    * If the deleted list is selected, go to the next one (or previous, if
428    * there are no other task list after this one).
429    */
430   if (gtk_list_box_row_is_selected (GTK_LIST_BOX_ROW (row)))
431     activate_appropriate_row (self, GTK_LIST_BOX_ROW (row));
432 
433   gtk_widget_hide (GTK_WIDGET (row));
434 }
435 
436 static void
on_listbox_row_activated_cb(GtkListBox * panels_listbox,GtkListBoxRow * row,GtdSidebar * self)437 on_listbox_row_activated_cb (GtkListBox    *panels_listbox,
438                              GtkListBoxRow *row,
439                              GtdSidebar    *self)
440 {
441   if (GTD_IS_SIDEBAR_PANEL_ROW (row))
442     {
443       GtdPanel *panel = gtd_sidebar_panel_row_get_panel (GTD_SIDEBAR_PANEL_ROW (row));
444 
445       gtk_widget_activate_action (GTK_WIDGET (self),
446                                   "task-lists-workspace.activate-panel",
447                                   "(sv)",
448                                   gtd_panel_get_panel_name (panel),
449                                   g_variant_new_maybe (G_VARIANT_TYPE_VARIANT, NULL));
450     }
451   else if (GTD_IS_SIDEBAR_PROVIDER_ROW (row))
452     {
453       /* Do nothing */
454     }
455   else if (GTD_IS_SIDEBAR_LIST_ROW (row))
456     {
457       GVariantBuilder builder;
458       GtdProvider *provider;
459       GtdTaskList *list;
460 
461       list = gtd_sidebar_list_row_get_task_list (GTD_SIDEBAR_LIST_ROW (row));
462       provider = gtd_task_list_get_provider (list);
463 
464       g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
465       g_variant_builder_add (&builder, "{sv}",
466                              "provider-id",
467                              g_variant_new_string (gtd_provider_get_id (provider)));
468       g_variant_builder_add (&builder, "{sv}",
469                              "task-list-id",
470                              g_variant_new_string (gtd_object_get_uid (GTD_OBJECT (list))));
471 
472       gtk_widget_activate_action (GTK_WIDGET (self),
473                                   "task-lists-workspace.activate-panel",
474                                   "(sv)",
475                                   "task-list-panel",
476                                   g_variant_builder_end (&builder));
477     }
478   else if (row == self->archive_row)
479     {
480       gtk_widget_activate_action (GTK_WIDGET (self),
481                                   "task-lists-workspace.toggle-archive",
482                                   "b",
483                                   TRUE);
484     }
485   else
486     {
487       g_assert_not_reached ();
488     }
489 }
490 
491 static void
on_panel_stack_visible_child_changed_cb(GtkStack * panel_stack,GParamSpec * pspec,GtdSidebar * self)492 on_panel_stack_visible_child_changed_cb (GtkStack   *panel_stack,
493                                          GParamSpec *pspec,
494                                          GtdSidebar *self)
495 {
496   GtkListBoxRow *panel_row;
497   GtkListBox *listbox;
498   GtdPanel *visible_panel;
499 
500   GTD_ENTRY;
501 
502   g_assert (GTD_IS_PANEL (gtk_stack_get_visible_child (panel_stack)));
503 
504   visible_panel = GTD_PANEL (gtk_stack_get_visible_child (panel_stack));
505   listbox = self->listbox;
506 
507   /*
508    * If the currently visible panel is the tasklist panel, we
509    * should choose the tasklist that is visible. Otherwise,
510    * just select the panel.
511    */
512   if (visible_panel == self->task_list_panel)
513     {
514       GtdTaskList *task_list;
515 
516       task_list = gtd_task_list_panel_get_task_list (GTD_TASK_LIST_PANEL (self->task_list_panel));
517       g_assert (task_list != NULL);
518 
519       panel_row = get_row_for_task_list (self, self->listbox, task_list);
520 
521       if (!panel_row)
522         {
523           panel_row = get_row_for_task_list (self, self->archive_listbox, task_list);
524           listbox = self->archive_listbox;
525         }
526     }
527   else
528     {
529       panel_row = get_row_for_panel (self, visible_panel);
530     }
531 
532   /* Select the row if it's not already selected*/
533   if (!gtk_list_box_row_is_selected (panel_row))
534     gtk_list_box_select_row (listbox, panel_row);
535 
536   GTD_EXIT;
537 }
538 
539 static void
on_provider_added_cb(GtdManager * manager,GtdProvider * provider,GtdSidebar * self)540 on_provider_added_cb (GtdManager  *manager,
541                       GtdProvider *provider,
542                       GtdSidebar  *self)
543 {
544   add_provider (self, provider);
545 }
546 
547 static void
on_provider_removed_cb(GtdManager * manager,GtdProvider * provider,GtdSidebar * self)548 on_provider_removed_cb (GtdManager  *manager,
549                         GtdProvider *provider,
550                         GtdSidebar  *self)
551 {
552   GtkListBoxRow *row;
553 
554   g_debug ("Removing provider '%s'", gtd_provider_get_name (provider));
555 
556   row = get_row_for_provider (self, self->listbox, provider);
557   gtk_list_box_remove (self->listbox, GTK_WIDGET (row));
558 
559   row = get_row_for_provider (self, self->archive_listbox, provider);
560   gtk_list_box_remove (self->archive_listbox, GTK_WIDGET (row));
561 }
562 
563 
564 static void
on_task_list_added_cb(GtdManager * manager,GtdTaskList * list,GtdSidebar * self)565 on_task_list_added_cb (GtdManager  *manager,
566                        GtdTaskList *list,
567                        GtdSidebar  *self)
568 {
569   add_task_list (self, list);
570 }
571 
572 static void
on_task_list_changed_cb(GtdManager * manager,GtdTaskList * list,GtdSidebar * self)573 on_task_list_changed_cb (GtdManager  *manager,
574                          GtdTaskList *list,
575                          GtdSidebar  *self)
576 {
577   GtkListBoxRow *row;
578   GtkListBox *listbox;
579   gboolean archived;
580 
581   archived = gtd_task_list_get_archived (list);
582   listbox = archived ? self->archive_listbox : self->listbox;
583   row = get_row_for_task_list (self, listbox, list);
584 
585   /*
586    * The task was either archived or unarchived; remove it and add to
587    * the appropriate listbox.
588    */
589   if (!row)
590     {
591       listbox = archived ? self->listbox : self->archive_listbox;
592       row = get_row_for_task_list (self, listbox, list);
593 
594       if (!row)
595         goto out;
596 
597       /* Change to another panel or taklist */
598       if (gtk_list_box_row_is_selected (row))
599         activate_appropriate_row (self, row);
600 
601       /* Destroy the old row */
602       gtk_list_box_remove (listbox, GTK_WIDGET (row));
603 
604       /* Add a new row */
605       add_task_list (self, list);
606     }
607 
608 out:
609   gtk_list_box_invalidate_filter (listbox);
610 }
611 
612 static void
on_task_list_removed_cb(GtdManager * manager,GtdTaskList * list,GtdSidebar * self)613 on_task_list_removed_cb (GtdManager  *manager,
614                          GtdTaskList *list,
615                          GtdSidebar  *self)
616 {
617   GtkListBoxRow *row;
618   GtkListBox *listbox;
619 
620   g_debug ("Removing task list '%s'", gtd_task_list_get_name (list));
621 
622   g_assert (!gtd_task_list_is_inbox (list));
623 
624   if (!gtd_task_list_get_archived (list))
625     listbox = self->listbox;
626   else
627     listbox = self->archive_listbox;
628 
629   row = get_row_for_task_list (self, listbox, list);
630   if (!row)
631     return;
632 
633   gtk_widget_unparent (GTK_WIDGET (row));
634   gtk_list_box_invalidate_filter (listbox);
635 }
636 
637 static gboolean
filter_archive_listbox_cb(GtkListBoxRow * row,gpointer user_data)638 filter_archive_listbox_cb (GtkListBoxRow *row,
639                            gpointer       user_data)
640 {
641   if (GTD_IS_SIDEBAR_LIST_ROW (row))
642     {
643       GtdTaskList *list;
644 
645       list = gtd_sidebar_list_row_get_task_list (GTD_SIDEBAR_LIST_ROW (row));
646       return gtd_task_list_get_archived (list);
647     }
648   else if (GTD_IS_SIDEBAR_PROVIDER_ROW (row))
649     {
650       g_autoptr (GList) lists = NULL;
651       GtdProvider *provider;
652       GList *l;
653 
654       provider = gtd_sidebar_provider_row_get_provider (GTD_SIDEBAR_PROVIDER_ROW (row));
655       lists = gtd_provider_get_task_lists (provider);
656 
657       for (l = lists; l; l = l->next)
658         {
659           if (gtd_task_list_get_archived (l->data))
660             return TRUE;
661         }
662 
663       return FALSE;
664     }
665   else
666     {
667       g_assert_not_reached ();
668     }
669 
670   return FALSE;
671 }
672 
673 static gboolean
filter_listbox_cb(GtkListBoxRow * row,gpointer user_data)674 filter_listbox_cb (GtkListBoxRow *row,
675                    gpointer       user_data)
676 {
677   GtdTaskList *list;
678 
679   if (!GTD_IS_SIDEBAR_LIST_ROW (row))
680     return TRUE;
681 
682   list = gtd_sidebar_list_row_get_task_list (GTD_SIDEBAR_LIST_ROW (row));
683   return !gtd_task_list_get_archived (list);
684 }
685 
686 static gint
sort_listbox_cb(GtkListBoxRow * row_a,GtkListBoxRow * row_b,gpointer user_data)687 sort_listbox_cb (GtkListBoxRow *row_a,
688                  GtkListBoxRow *row_b,
689                  gpointer       user_data)
690 {
691   GtdSidebar *self = GTD_SIDEBAR (user_data);
692 
693   /* Special-case the Archive row */
694   if (row_a == self->archive_row || row_b == self->archive_row)
695     {
696       if (GTD_IS_SIDEBAR_PANEL_ROW (row_b))
697         return 1;
698       else
699         return -1;
700     }
701 
702   if (G_OBJECT_TYPE (row_a) != G_OBJECT_TYPE (row_b))
703     {
704       gint result;
705 
706       /* Panels go above everything else */
707       if (GTD_IS_SIDEBAR_PANEL_ROW (row_b) != GTD_IS_SIDEBAR_PANEL_ROW (row_a))
708         return GTD_IS_SIDEBAR_PANEL_ROW (row_b) - GTD_IS_SIDEBAR_PANEL_ROW (row_a);
709 
710       /*
711        * At this point, we know that row_a and row_b are either provider rows, or
712        * tasklist rows. We also know that they're different, i.e. if row_a is a
713        * provider row, row_b will be a list one, and vice-versa.
714        */
715       if (GTD_IS_SIDEBAR_PROVIDER_ROW (row_a))
716         {
717           GtdProvider *provider_a;
718           GtdTaskList *list_b;
719 
720           provider_a = gtd_sidebar_provider_row_get_provider (GTD_SIDEBAR_PROVIDER_ROW (row_a));
721           list_b = gtd_sidebar_list_row_get_task_list (GTD_SIDEBAR_LIST_ROW (row_b));
722 
723           /*
724            * If the providers are different, respect the provider order. If the providers are the
725            * same, we must put the provider row above the tasklist row.
726            */
727           result = gtd_provider_compare (provider_a, gtd_task_list_get_provider (list_b));
728 
729           if (result != 0)
730             return result;
731 
732           return -1;
733         }
734       else
735         {
736           GtdTaskList *list_a;
737           GtdProvider *provider_b;
738 
739           list_a = gtd_sidebar_list_row_get_task_list (GTD_SIDEBAR_LIST_ROW (row_a));
740           provider_b = gtd_sidebar_provider_row_get_provider (GTD_SIDEBAR_PROVIDER_ROW (row_b));
741 
742           /* See comment above */
743           result = gtd_provider_compare (gtd_task_list_get_provider (list_a), provider_b);
744 
745           if (result != 0)
746             return result;
747 
748           return 1;
749         }
750     }
751   else
752     {
753       /*
754        * We only reach this section of the code if both rows are of the same type,
755        * so it doesn't matter which one we get the type from.
756        */
757 
758       if (GTD_IS_SIDEBAR_PANEL_ROW (row_a))
759         return compare_panels (GTD_SIDEBAR_PANEL_ROW (row_a), GTD_SIDEBAR_PANEL_ROW (row_b));
760 
761       if (GTD_IS_SIDEBAR_PROVIDER_ROW (row_a))
762         return compare_providers (GTD_SIDEBAR_PROVIDER_ROW (row_a), GTD_SIDEBAR_PROVIDER_ROW (row_b));
763 
764       if (GTD_IS_SIDEBAR_LIST_ROW (row_a))
765         return compare_lists (GTD_SIDEBAR_LIST_ROW (row_a), GTD_SIDEBAR_LIST_ROW (row_b));
766     }
767 
768   return 0;
769 }
770 
771 
772 /*
773  * GObject overrides
774  */
775 
776 static void
gtd_sidebar_constructed(GObject * object)777 gtd_sidebar_constructed (GObject *object)
778 {
779   g_autoptr (GList) providers = NULL;
780   GListModel *lists;
781   GtdManager *manager;
782   GtdSidebar *self;
783   GList *l;
784   guint i;
785 
786   self = (GtdSidebar *)object;
787   manager = gtd_manager_get_default ();
788 
789   G_OBJECT_CLASS (gtd_sidebar_parent_class)->constructed (object);
790 
791   /* Add providers */
792   providers = gtd_manager_get_providers (manager);
793 
794   for (l = providers; l; l = l->next)
795     add_provider (self, l->data);
796 
797   g_signal_connect (manager, "provider-added", G_CALLBACK (on_provider_added_cb), self);
798   g_signal_connect (manager, "provider-removed", G_CALLBACK (on_provider_removed_cb), self);
799 
800   /* Add task lists */
801   lists = gtd_manager_get_task_lists_model (manager);
802 
803   for (i = 0; i < g_list_model_get_n_items (lists); i++)
804     {
805       g_autoptr (GtdTaskList) list = g_list_model_get_item (lists, i);
806 
807       add_task_list (self, list);
808     }
809 
810   g_signal_connect (manager, "list-added", G_CALLBACK (on_task_list_added_cb), self);
811   g_signal_connect (manager, "list-changed", G_CALLBACK (on_task_list_changed_cb), self);
812   g_signal_connect (manager, "list-removed", G_CALLBACK (on_task_list_removed_cb), self);
813 }
814 
815 static void
gtd_sidebar_class_init(GtdSidebarClass * klass)816 gtd_sidebar_class_init (GtdSidebarClass *klass)
817 {
818   GObjectClass *object_class = G_OBJECT_CLASS (klass);
819   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
820 
821   object_class->constructed = gtd_sidebar_constructed;
822 
823   g_type_ensure (GTD_TYPE_MAX_SIZE_LAYOUT);
824 
825   gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/todo/plugins/task-lists-workspace/gtd-sidebar.ui");
826 
827   gtk_widget_class_bind_template_child (widget_class, GtdSidebar, archive_listbox);
828   gtk_widget_class_bind_template_child (widget_class, GtdSidebar, archive_row);
829   gtk_widget_class_bind_template_child (widget_class, GtdSidebar, listbox);
830   gtk_widget_class_bind_template_child (widget_class, GtdSidebar, stack);
831 
832   gtk_widget_class_bind_template_callback (widget_class, on_listbox_row_activated_cb);
833 
834   gtk_widget_class_set_css_name (widget_class, "sidebar");
835 }
836 
837 static void
gtd_sidebar_init(GtdSidebar * self)838 gtd_sidebar_init (GtdSidebar *self)
839 {
840   static const GActionEntry entries[] = {
841     { "move-up", on_action_move_up_activated_cb },
842     { "move-down", on_action_move_down_activated_cb },
843   };
844   gtk_widget_init_template (GTK_WIDGET (self));
845 
846   gtk_list_box_set_sort_func (self->listbox, sort_listbox_cb, self, NULL);
847   gtk_list_box_set_filter_func (self->listbox, filter_listbox_cb, self, NULL);
848 
849   gtk_list_box_set_sort_func (self->archive_listbox, sort_listbox_cb, self, NULL);
850   gtk_list_box_set_filter_func (self->archive_listbox, filter_archive_listbox_cb, self, NULL);
851 
852   self->action_group = g_simple_action_group_new ();
853 
854   g_action_map_add_action_entries (G_ACTION_MAP (self->action_group),
855                                    entries,
856                                    G_N_ELEMENTS (entries),
857                                    self);
858 
859   gtk_widget_insert_action_group (GTK_WIDGET (self),
860                                   "sidebar",
861                                   G_ACTION_GROUP (self->action_group));
862 }
863 
864 void
gtd_sidebar_set_panel_stack(GtdSidebar * self,GtkStack * stack)865 gtd_sidebar_set_panel_stack (GtdSidebar *self,
866                              GtkStack   *stack)
867 {
868   g_return_if_fail (GTD_IS_SIDEBAR (self));
869   g_return_if_fail (GTK_IS_STACK (stack));
870 
871   g_assert (self->panel_stack == NULL);
872 
873   self->panel_stack = g_object_ref (stack);
874 
875   g_signal_connect_object (stack,
876                            "notify::visible-child",
877                            G_CALLBACK (on_panel_stack_visible_child_changed_cb),
878                            self,
879                            0);
880 }
881 
882 
883 void
gtd_sidebar_set_task_list_panel(GtdSidebar * self,GtdPanel * task_list_panel)884 gtd_sidebar_set_task_list_panel (GtdSidebar *self,
885                                  GtdPanel   *task_list_panel)
886 {
887   g_return_if_fail (GTD_IS_SIDEBAR (self));
888   g_return_if_fail (GTD_IS_PANEL (task_list_panel));
889 
890   g_assert (self->task_list_panel == NULL);
891 
892   self->task_list_panel = g_object_ref (task_list_panel);
893   g_signal_connect_object (self->task_list_panel,
894                            "list-deleted",
895                            G_CALLBACK (on_task_list_panel_list_deleted_cb),
896                            self,
897                            0);
898 }
899 
900 void
gtd_sidebar_activate(GtdSidebar * self)901 gtd_sidebar_activate (GtdSidebar *self)
902 {
903   GtkListBoxRow *first_row;
904 
905   g_assert (GTD_IS_SIDEBAR (self));
906 
907   first_row = gtk_list_box_get_row_at_index (self->listbox, 0);
908   g_signal_emit_by_name (first_row, "activate");
909 }
910 
911 void
gtd_sidebar_set_archive_visible(GtdSidebar * self,gboolean show_archive)912 gtd_sidebar_set_archive_visible (GtdSidebar *self,
913                                  gboolean    show_archive)
914 {
915   g_assert (GTD_IS_SIDEBAR (self));
916 
917   if (show_archive)
918     gtk_stack_set_visible_child (self->stack, GTK_WIDGET (self->archive_listbox));
919   else
920     gtk_stack_set_visible_child (self->stack, GTK_WIDGET (self->listbox));
921 }
922 
923 void
gtd_sidebar_connect(GtdSidebar * self,GtkWidget * workspace)924 gtd_sidebar_connect (GtdSidebar *self,
925                      GtkWidget  *workspace)
926 {
927   g_signal_connect (workspace, "panel-added", G_CALLBACK (on_panel_added_cb), self);
928   g_signal_connect (workspace, "panel-removed", G_CALLBACK (on_panel_removed_cb), self);
929 }
930