1 /* gtd-task-row.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 "GtdTaskRow"
20 
21 #include "gtd-debug.h"
22 #include "gtd-edit-pane.h"
23 #include "gtd-manager.h"
24 #include "gtd-markdown-renderer.h"
25 #include "gtd-provider.h"
26 #include "gtd-star-widget.h"
27 #include "gtd-task-row.h"
28 #include "gtd-task.h"
29 #include "gtd-task-list.h"
30 #include "gtd-task-list-view.h"
31 #include "gtd-utils-private.h"
32 #include "gtd-widget.h"
33 
34 #include <glib/gi18n.h>
35 #include <gdk/gdk.h>
36 #include <gtk/gtk.h>
37 
38 struct _GtdTaskRow
39 {
40   GtdWidget           parent;
41 
42   /*<private>*/
43   GtkWidget          *content_box;
44   GtkWidget          *main_box;
45 
46   GtkWidget          *done_check;
47   GtkWidget          *edit_panel_revealer;
48   GtkWidget          *header_event_box;
49   GtdStarWidget      *star_widget;
50   GtkWidget          *title_entry;
51 
52   /* task widgets */
53   GtkLabel           *task_date_label;
54   GtkLabel           *task_list_label;
55 
56   /* dnd widgets */
57   GtkWidget          *dnd_box;
58   GtkWidget          *dnd_icon;
59   gint                clicked_x;
60   gint                clicked_y;
61 
62   /* data */
63   GtdTask            *task;
64 
65   GtdEditPane        *edit_pane;
66 
67   GtdMarkdownRenderer *renderer;
68   GPtrArray           *bindings;
69 
70   gboolean            active;
71   gboolean            changed;
72 };
73 
74 #define PRIORITY_ICON_SIZE 8
75 
76 static void          on_star_widget_activated_cb                 (GtdStarWidget      *star_widget,
77                                                                   GParamSpec         *pspec,
78                                                                   GtdTaskRow         *self);
79 
80 G_DEFINE_TYPE (GtdTaskRow, gtd_task_row, GTD_TYPE_WIDGET)
81 
82 enum
83 {
84   ENTER,
85   EXIT,
86   REMOVE_TASK,
87   NUM_SIGNALS
88 };
89 
90 enum
91 {
92   PROP_0,
93   PROP_RENDERER,
94   PROP_TASK,
95   LAST_PROP
96 };
97 
98 static guint signals[NUM_SIGNALS] = { 0, };
99 
100 
101 /*
102  * Auxiliary methods
103  */
104 
105 static gboolean
date_to_label_binding_cb(GBinding * binding,const GValue * from_value,GValue * to_value,gpointer user_data)106 date_to_label_binding_cb (GBinding     *binding,
107                           const GValue *from_value,
108                           GValue       *to_value,
109                           gpointer      user_data)
110 {
111   g_autofree gchar *new_label = NULL;
112   GDateTime *dt;
113 
114   g_return_val_if_fail (GTD_IS_TASK_ROW (user_data), FALSE);
115 
116   dt = g_value_get_boxed (from_value);
117 
118   if (dt)
119     {
120       g_autoptr (GDateTime) today = g_date_time_new_now_local ();
121 
122       if (g_date_time_get_year (dt) == g_date_time_get_year (today) &&
123           g_date_time_get_month (dt) == g_date_time_get_month (today))
124         {
125           if (g_date_time_get_day_of_month (dt) == g_date_time_get_day_of_month (today))
126             {
127               new_label = g_strdup (_("Today"));
128             }
129           else if (g_date_time_get_day_of_month (dt) == g_date_time_get_day_of_month (today) + 1)
130             {
131               new_label = g_strdup (_("Tomorrow"));
132             }
133           else if (g_date_time_get_day_of_month (dt) == g_date_time_get_day_of_month (today) - 1)
134             {
135               new_label = g_strdup (_("Yesterday"));
136             }
137           else if (g_date_time_get_day_of_year (dt) > g_date_time_get_day_of_month (today) &&
138                    g_date_time_get_day_of_year (dt) < g_date_time_get_day_of_month (today) + 7)
139             {
140               new_label = g_date_time_format (dt, "%A");
141             }
142           else
143             {
144               new_label = g_date_time_format (dt, "%x");
145             }
146         }
147       else
148         {
149           new_label = g_date_time_format (dt, "%x");
150         }
151     }
152   else
153     {
154       new_label = g_strdup ("");
155     }
156 
157   g_value_set_string (to_value, new_label);
158 
159   return TRUE;
160 }
161 
162 static GtkWidget*
create_transient_row(GtdTaskRow * self)163 create_transient_row (GtdTaskRow *self)
164 {
165   GtdTaskRow *new_row;
166 
167   new_row = GTD_TASK_ROW (gtd_task_row_new (self->task, self->renderer));
168 
169   gtk_widget_set_size_request (GTK_WIDGET (new_row),
170                                gtk_widget_get_allocated_width (GTK_WIDGET (self)),
171                                -1);
172 
173   gtk_revealer_set_reveal_child (GTK_REVEALER (new_row->edit_panel_revealer), self->active);
174 
175   gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (new_row)), "background");
176   gtk_widget_set_opacity (GTK_WIDGET (new_row), 0.66);
177 
178   return GTK_WIDGET (new_row);
179 }
180 
181 
182 /*
183  * Callbacks
184  */
185 
186 static void
on_task_updated_cb(GObject * object,GAsyncResult * result,gpointer user_data)187 on_task_updated_cb (GObject      *object,
188                     GAsyncResult *result,
189                     gpointer      user_data)
190 {
191   g_autoptr (GError) error = NULL;
192 
193   gtd_provider_update_task_finish (GTD_PROVIDER (object), result, &error);
194 
195   if (error)
196     {
197       g_warning ("Error updating task: %s", error->message);
198       return;
199     }
200 }
201 
202 static void
on_remove_task_cb(GtdEditPane * edit_panel,GtdTask * task,GtdTaskRow * self)203 on_remove_task_cb (GtdEditPane *edit_panel,
204                    GtdTask     *task,
205                    GtdTaskRow  *self)
206 {
207   g_signal_emit (self, signals[REMOVE_TASK], 0);
208 }
209 
210 static void
on_button_press_event_cb(GtkGestureClick * gesture,gint n_press,gdouble x,gdouble y,GtdTaskRow * self)211 on_button_press_event_cb (GtkGestureClick *gesture,
212                           gint             n_press,
213                           gdouble          x,
214                           gdouble          y,
215                           GtdTaskRow      *self)
216 {
217   GtkWidget *widget;
218   gdouble real_x;
219   gdouble real_y;
220 
221   widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
222 
223   gtk_widget_translate_coordinates (widget,
224                                     GTK_WIDGET (self),
225                                     x,
226                                     y,
227                                     &real_x,
228                                     &real_y);
229 
230   self->clicked_x = real_x;
231   self->clicked_y = real_y;
232 
233   GTD_TRACE_MSG ("GtkGestureClick:pressed received from a %s at %.1lf,%.1lf (%.1lf,%.1lf)",
234                  G_OBJECT_TYPE_NAME (widget),
235                  x,
236                  y,
237                  real_x,
238                  real_y);
239 }
240 
241 static GdkContentProvider*
on_drag_prepare_cb(GtkDragSource * source,gdouble x,gdouble y,GtdTaskRow * self)242 on_drag_prepare_cb (GtkDragSource *source,
243                     gdouble        x,
244                     gdouble        y,
245                     GtdTaskRow    *self)
246 {
247   GTD_ENTRY;
248   GTD_RETURN (gdk_content_provider_new_typed (GTD_TYPE_TASK, self->task));
249 }
250 
251 static void
on_drag_begin_cb(GtkDragSource * source,GdkDrag * drag,GtdTaskRow * self)252 on_drag_begin_cb (GtkDragSource *source,
253                   GdkDrag       *drag,
254                   GtdTaskRow    *self)
255 {
256   GtkWidget *drag_icon;
257   GtkWidget *new_row;
258   GtkWidget *widget;
259 
260   GTD_ENTRY;
261 
262   widget = GTK_WIDGET (self);
263 
264   gtk_widget_set_cursor_from_name (widget, "grabbing");
265 
266   new_row = create_transient_row (self);
267   drag_icon = gtk_drag_icon_get_for_drag (drag);
268   gtk_drag_icon_set_child (GTK_DRAG_ICON (drag_icon), new_row);
269   gdk_drag_set_hotspot (drag, self->clicked_x, self->clicked_y);
270 
271   gtk_widget_hide (widget);
272 
273   GTD_EXIT;
274 }
275 
276 static gboolean
on_drag_cancelled_cb(GtkDragSource * source,GdkDrag * drag,GdkDragCancelReason result,GtdTaskRow * self)277 on_drag_cancelled_cb (GtkDragSource       *source,
278                       GdkDrag             *drag,
279                       GdkDragCancelReason  result,
280                       GtdTaskRow          *self)
281 {
282   GTD_ENTRY;
283 
284   gtk_widget_set_cursor_from_name (GTK_WIDGET (self), NULL);
285   gtk_widget_show (GTK_WIDGET (self));
286 
287   GTD_RETURN (FALSE);
288 }
289 
290 static void
on_complete_check_toggled_cb(GtkCheckButton * button,GtdTaskRow * self)291 on_complete_check_toggled_cb (GtkCheckButton *button,
292                               GtdTaskRow     *self)
293 {
294   GTD_ENTRY;
295 
296   g_assert (GTD_IS_TASK (self->task));
297 
298   gtd_task_set_complete (self->task, gtk_check_button_get_active (button));
299   gtd_provider_update_task (gtd_task_get_provider (self->task),
300                             self->task,
301                             NULL,
302                             on_task_updated_cb,
303                             self);
304 
305   GTD_EXIT;
306 }
307 
308 static void
on_complete_changed_cb(GtdTaskRow * self,GParamSpec * pspec,GtdTask * task)309 on_complete_changed_cb (GtdTaskRow *self,
310                         GParamSpec *pspec,
311                         GtdTask    *task)
312 {
313   GtkStyleContext *context;
314   gboolean complete;
315 
316   complete = gtd_task_get_complete (task);
317   context = gtk_widget_get_style_context (GTK_WIDGET (self));
318 
319   if (complete)
320     gtk_style_context_add_class (context, "complete");
321   else
322     gtk_style_context_remove_class (context, "complete");
323 
324   /* Update the toggle button as well */
325   g_signal_handlers_block_by_func (self->done_check, on_complete_check_toggled_cb, self);
326   gtk_check_button_set_active (GTK_CHECK_BUTTON (self->done_check), complete);
327   g_signal_handlers_unblock_by_func (self->done_check, on_complete_check_toggled_cb, self);
328 }
329 
330 static void
on_task_important_changed_cb(GtdTask * task,GParamSpec * pspec,GtdTaskRow * self)331 on_task_important_changed_cb (GtdTask    *task,
332                               GParamSpec *pspec,
333                               GtdTaskRow *self)
334 {
335   g_signal_handlers_block_by_func (self->star_widget, on_star_widget_activated_cb, self);
336 
337   gtd_star_widget_set_active (self->star_widget, gtd_task_get_important (task));
338 
339   g_signal_handlers_unblock_by_func (self->star_widget, on_star_widget_activated_cb, self);
340 }
341 
342 static void
on_star_widget_activated_cb(GtdStarWidget * star_widget,GParamSpec * pspec,GtdTaskRow * self)343 on_star_widget_activated_cb (GtdStarWidget *star_widget,
344                              GParamSpec    *pspec,
345                              GtdTaskRow    *self)
346 {
347   g_signal_handlers_block_by_func (self->task, on_task_important_changed_cb, self);
348 
349   gtd_task_set_important (self->task, gtd_star_widget_get_active (star_widget));
350   gtd_provider_update_task (gtd_task_get_provider (self->task),
351                             self->task,
352                             NULL,
353                             on_task_updated_cb,
354                             self);
355 
356   g_signal_handlers_unblock_by_func (self->task, on_task_important_changed_cb, self);
357 }
358 
359 static gboolean
on_key_pressed_cb(GtkEventControllerKey * controller,guint keyval,guint keycode,GdkModifierType modifiers,GtdTaskRow * self)360 on_key_pressed_cb (GtkEventControllerKey *controller,
361                    guint                  keyval,
362                    guint                  keycode,
363                    GdkModifierType        modifiers,
364                    GtdTaskRow            *self)
365 {
366   GTD_ENTRY;
367 
368   /* Exit when pressing Esc without modifiers */
369   if (keyval == GDK_KEY_Escape && !(modifiers & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)))
370     {
371       GTD_TRACE_MSG ("Escape pressed, closing task…");
372       gtd_task_row_set_active (self, FALSE);
373     }
374 
375   GTD_RETURN (GDK_EVENT_PROPAGATE);
376 }
377 
378 static void
on_task_changed_cb(GtdTaskRow * self)379 on_task_changed_cb (GtdTaskRow  *self)
380 {
381   g_debug ("Task changed");
382 
383   self->changed = TRUE;
384 }
385 
386 
387 /*
388  * GObject overrides
389  */
390 
391 static void
gtd_task_row_finalize(GObject * object)392 gtd_task_row_finalize (GObject *object)
393 {
394   GtdTaskRow *self = GTD_TASK_ROW (object);
395 
396   if (self->changed)
397     {
398       if (self->task)
399         {
400           gtd_provider_update_task (gtd_task_get_provider (self->task),
401                                     self->task,
402                                     NULL,
403                                     on_task_updated_cb,
404                                     self);
405         }
406       self->changed = FALSE;
407     }
408 
409   g_clear_object (&self->task);
410 
411   G_OBJECT_CLASS (gtd_task_row_parent_class)->finalize (object);
412 }
413 
414 static void
gtd_task_row_dispose(GObject * object)415 gtd_task_row_dispose (GObject *object)
416 {
417   GtdTaskRow *self;
418   GtdTask *task;
419 
420   self = GTD_TASK_ROW (object);
421   task = self->task;
422 
423   g_clear_pointer (&self->bindings, g_ptr_array_unref);
424 
425   if (task)
426     g_signal_handlers_disconnect_by_func (task, on_complete_changed_cb, self);
427 
428   G_OBJECT_CLASS (gtd_task_row_parent_class)->dispose (object);
429 }
430 
431 static void
gtd_task_row_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)432 gtd_task_row_get_property (GObject    *object,
433                            guint       prop_id,
434                            GValue     *value,
435                            GParamSpec *pspec)
436 {
437   GtdTaskRow *self = GTD_TASK_ROW (object);
438 
439   switch (prop_id)
440     {
441     case PROP_RENDERER:
442       g_value_set_object (value, self->renderer);
443       break;
444 
445     case PROP_TASK:
446       g_value_set_object (value, self->task);
447       break;
448 
449     default:
450       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
451     }
452 }
453 
454 static void
gtd_task_row_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)455 gtd_task_row_set_property (GObject      *object,
456                            guint         prop_id,
457                            const GValue *value,
458                            GParamSpec   *pspec)
459 {
460   GtdTaskRow *self = GTD_TASK_ROW (object);
461 
462   switch (prop_id)
463     {
464     case PROP_RENDERER:
465       self->renderer = g_value_get_object (value);
466       break;
467 
468     case PROP_TASK:
469       gtd_task_row_set_task (self, g_value_get_object (value));
470       break;
471 
472     default:
473       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
474     }
475 }
476 
477 static void
gtd_task_row_class_init(GtdTaskRowClass * klass)478 gtd_task_row_class_init (GtdTaskRowClass *klass)
479 {
480   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
481   GObjectClass *object_class = G_OBJECT_CLASS (klass);
482 
483   object_class->dispose = gtd_task_row_dispose;
484   object_class->finalize = gtd_task_row_finalize;
485   object_class->get_property = gtd_task_row_get_property;
486   object_class->set_property = gtd_task_row_set_property;
487 
488   /**
489    * GtdTaskRow::renderer:
490    *
491    * The internal markdown renderer.
492    */
493   g_object_class_install_property (
494           object_class,
495           PROP_RENDERER,
496           g_param_spec_object ("renderer",
497                                "Renderer",
498                                "Renderer",
499                                GTD_TYPE_MARKDOWN_RENDERER,
500                                G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
501 
502   /**
503    * GtdTaskRow::task:
504    *
505    * The task that this row represents, or %NULL.
506    */
507   g_object_class_install_property (
508           object_class,
509           PROP_TASK,
510           g_param_spec_object ("task",
511                                "Task of the row",
512                                "The task that this row represents",
513                                GTD_TYPE_TASK,
514                                G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
515 
516   /**
517    * GtdTaskRow::enter:
518    *
519    * Emitted when the row is focused and in the editing state.
520    */
521   signals[ENTER] = g_signal_new ("enter",
522                                  GTD_TYPE_TASK_ROW,
523                                  G_SIGNAL_RUN_LAST,
524                                  0,
525                                  NULL,
526                                  NULL,
527                                  NULL,
528                                  G_TYPE_NONE,
529                                  0);
530 
531   /**
532    * GtdTaskRow::exit:
533    *
534    * Emitted when the row is unfocused and leaves the editing state.
535    */
536   signals[EXIT] = g_signal_new ("exit",
537                                 GTD_TYPE_TASK_ROW,
538                                 G_SIGNAL_RUN_LAST,
539                                 0,
540                                 NULL,
541                                 NULL,
542                                 NULL,
543                                 G_TYPE_NONE,
544                                 0);
545 
546   /**
547    * GtdTaskRow::remove-task:
548    *
549    * Emitted when the user wants to delete the task represented by this row.
550    */
551   signals[REMOVE_TASK] = g_signal_new ("remove-task",
552                                        GTD_TYPE_TASK_ROW,
553                                        G_SIGNAL_RUN_LAST,
554                                        0,
555                                        NULL,
556                                        NULL,
557                                        NULL,
558                                        G_TYPE_NONE,
559                                        0);
560 
561   gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/todo/ui/gtd-task-row.ui");
562 
563   gtk_widget_class_bind_template_child (widget_class, GtdTaskRow, content_box);
564   gtk_widget_class_bind_template_child (widget_class, GtdTaskRow, dnd_box);
565   gtk_widget_class_bind_template_child (widget_class, GtdTaskRow, dnd_icon);
566   gtk_widget_class_bind_template_child (widget_class, GtdTaskRow, done_check);
567   gtk_widget_class_bind_template_child (widget_class, GtdTaskRow, edit_panel_revealer);
568   gtk_widget_class_bind_template_child (widget_class, GtdTaskRow, header_event_box);
569   gtk_widget_class_bind_template_child (widget_class, GtdTaskRow, main_box);
570   gtk_widget_class_bind_template_child (widget_class, GtdTaskRow, star_widget);
571   gtk_widget_class_bind_template_child (widget_class, GtdTaskRow, task_date_label);
572   gtk_widget_class_bind_template_child (widget_class, GtdTaskRow, task_list_label);
573   gtk_widget_class_bind_template_child (widget_class, GtdTaskRow, title_entry);
574 
575   gtk_widget_class_bind_template_callback (widget_class, on_button_press_event_cb);
576   gtk_widget_class_bind_template_callback (widget_class, on_complete_check_toggled_cb);
577   gtk_widget_class_bind_template_callback (widget_class, on_key_pressed_cb);
578   gtk_widget_class_bind_template_callback (widget_class, on_remove_task_cb);
579   gtk_widget_class_bind_template_callback (widget_class, on_task_changed_cb);
580 
581   gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
582   gtk_widget_class_set_css_name (widget_class, "taskrow");
583 }
584 
585 static void
gtd_task_row_init(GtdTaskRow * self)586 gtd_task_row_init (GtdTaskRow *self)
587 {
588   GtkDragSource *drag_source;
589 
590   self->bindings = g_ptr_array_new_with_free_func ((GDestroyNotify) g_binding_unbind);
591   self->active = FALSE;
592 
593   gtk_widget_init_template (GTK_WIDGET (self));
594 
595   drag_source = gtk_drag_source_new ();
596   gtk_drag_source_set_actions (drag_source, GDK_ACTION_MOVE);
597 
598   g_signal_connect (drag_source, "prepare", G_CALLBACK (on_drag_prepare_cb), self);
599   g_signal_connect (drag_source, "drag-begin", G_CALLBACK (on_drag_begin_cb), self);
600   g_signal_connect (drag_source, "drag-cancel", G_CALLBACK (on_drag_cancelled_cb), self);
601 
602   gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (drag_source));
603 
604   gtk_widget_set_cursor_from_name (self->dnd_icon, "grab");
605   gtk_widget_set_cursor_from_name (self->header_event_box, "pointer");
606 }
607 
608 GtkWidget*
gtd_task_row_new(GtdTask * task,GtdMarkdownRenderer * renderer)609 gtd_task_row_new (GtdTask             *task,
610                   GtdMarkdownRenderer *renderer)
611 {
612   return g_object_new (GTD_TYPE_TASK_ROW,
613                        "task", task,
614                        "renderer", renderer,
615                        NULL);
616 }
617 
618 void
gtd_task_row_set_task(GtdTaskRow * self,GtdTask * task)619 gtd_task_row_set_task (GtdTaskRow *self,
620                        GtdTask    *task)
621 {
622   GBinding *binding;
623   GtdTask *old_task;
624 
625   g_return_if_fail (GTD_IS_TASK_ROW (self));
626 
627   old_task = self->task;
628 
629   if (old_task)
630     {
631       g_signal_handlers_disconnect_by_func (old_task, on_complete_changed_cb, self);
632       g_signal_handlers_disconnect_by_func (old_task, on_task_important_changed_cb, self);
633       g_signal_handlers_disconnect_by_func (old_task, on_star_widget_activated_cb, self);
634       g_ptr_array_set_size (self->bindings, 0);
635     }
636 
637   g_set_object (&self->task, task);
638 
639   if (task)
640     {
641       gtk_label_set_label (self->task_list_label,
642                            gtd_task_list_get_name (gtd_task_get_list (task)));
643 
644       g_signal_handlers_block_by_func (self->title_entry, on_task_changed_cb, self);
645       g_signal_handlers_block_by_func (self->done_check, on_complete_check_toggled_cb, self);
646 
647       binding = g_object_bind_property (task,
648                                         "loading",
649                                         self,
650                                         "sensitive",
651                                         G_BINDING_DEFAULT |
652                                         G_BINDING_INVERT_BOOLEAN |
653                                         G_BINDING_SYNC_CREATE);
654       g_ptr_array_add (self->bindings, binding);
655 
656       binding = g_object_bind_property (task,
657                                         "title",
658                                         self->title_entry,
659                                         "text",
660                                         G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
661       g_ptr_array_add (self->bindings, binding);
662 
663       binding = g_object_bind_property_full (task,
664                                              "due-date",
665                                              self->task_date_label,
666                                              "label",
667                                              G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE,
668                                              date_to_label_binding_cb,
669                                              NULL,
670                                              self,
671                                              NULL);
672       g_ptr_array_add (self->bindings, binding);
673 
674       on_complete_changed_cb (self, NULL, task);
675       g_signal_connect_object (task,
676                                "notify::complete",
677                                G_CALLBACK (on_complete_changed_cb),
678                                self,
679                                G_CONNECT_SWAPPED);
680 
681       on_task_important_changed_cb (task, NULL, self);
682       g_signal_connect_object (task,
683                                "notify::important",
684                                G_CALLBACK (on_task_important_changed_cb),
685                                self,
686                                0);
687 
688       g_signal_connect_object (self->star_widget,
689                                "notify::active",
690                                G_CALLBACK (on_star_widget_activated_cb),
691                                self,
692                                0);
693 
694       g_signal_handlers_unblock_by_func (self->done_check, on_complete_check_toggled_cb, self);
695       g_signal_handlers_unblock_by_func (self->title_entry, on_task_changed_cb, self);
696     }
697 }
698 
699 /**
700  * gtd_task_row_get_task:
701  * @row: a #GtdTaskRow
702  *
703  * Retrieves the #GtdTask that @row manages, or %NULL if none
704  * is set.
705  *
706  * Returns: (transfer none): the internal task of @row
707  */
708 GtdTask*
gtd_task_row_get_task(GtdTaskRow * row)709 gtd_task_row_get_task (GtdTaskRow *row)
710 {
711   g_return_val_if_fail (GTD_IS_TASK_ROW (row), NULL);
712 
713   return row->task;
714 }
715 
716 /**
717  * gtd_task_row_set_list_name_visible:
718  * @row: a #GtdTaskRow
719  * @show_list_name: %TRUE to show the list name, %FALSE to hide it
720  *
721  * Sets @row's list name label visibility to @show_list_name.
722  */
723 void
gtd_task_row_set_list_name_visible(GtdTaskRow * row,gboolean show_list_name)724 gtd_task_row_set_list_name_visible (GtdTaskRow *row,
725                                     gboolean    show_list_name)
726 {
727   g_return_if_fail (GTD_IS_TASK_ROW (row));
728 
729   gtk_widget_set_visible (GTK_WIDGET (row->task_list_label), show_list_name);
730 }
731 
732 /**
733  * gtd_task_row_set_due_date_visible:
734  * @row: a #GtdTaskRow
735  * @show_due_date: %TRUE to show the due, %FALSE to hide it
736  *
737  * Sets @row's due date label visibility to @show_due_date.
738  */
739 void
gtd_task_row_set_due_date_visible(GtdTaskRow * row,gboolean show_due_date)740 gtd_task_row_set_due_date_visible (GtdTaskRow *row,
741                                    gboolean    show_due_date)
742 {
743   g_return_if_fail (GTD_IS_TASK_ROW (row));
744 
745   gtk_widget_set_visible (GTK_WIDGET (row->task_date_label), show_due_date);
746 }
747 
748 gboolean
gtd_task_row_get_active(GtdTaskRow * self)749 gtd_task_row_get_active (GtdTaskRow *self)
750 {
751   g_return_val_if_fail (GTD_IS_TASK_ROW (self), FALSE);
752 
753   return self->active;
754 }
755 
756 void
gtd_task_row_set_active(GtdTaskRow * self,gboolean active)757 gtd_task_row_set_active (GtdTaskRow *self,
758                          gboolean    active)
759 {
760   g_return_if_fail (GTD_IS_TASK_ROW (self));
761 
762   if (self->active == active)
763     return;
764 
765   self->active = active;
766 
767   /* Create or destroy the edit panel */
768   if (active && !self->edit_pane)
769     {
770       GTD_TRACE_MSG ("Creating edit pane");
771 
772       self->edit_pane = GTD_EDIT_PANE (gtd_edit_pane_new ());
773       gtd_edit_pane_set_markdown_renderer (self->edit_pane, self->renderer);
774       gtd_edit_pane_set_task (self->edit_pane, self->task);
775 
776       gtk_revealer_set_child (GTK_REVEALER (self->edit_panel_revealer), GTK_WIDGET (self->edit_pane));
777       gtk_widget_show (GTK_WIDGET (self->edit_pane));
778 
779       g_signal_connect_swapped (self->edit_pane, "changed", G_CALLBACK (on_task_changed_cb), self);
780       g_signal_connect (self->edit_pane, "remove-task", G_CALLBACK (on_remove_task_cb), self);
781     }
782   else if (!active && self->edit_pane)
783     {
784       GTD_TRACE_MSG ("Destroying edit pane");
785 
786       gtk_revealer_set_child (GTK_REVEALER (self->edit_panel_revealer), NULL);
787       self->edit_pane = NULL;
788     }
789 
790   /* And reveal or hide it */
791   gtk_revealer_set_reveal_child (GTK_REVEALER (self->edit_panel_revealer), active);
792 
793   /* Save the task if it is not being loaded */
794   if (!active && !gtd_object_get_loading (GTD_OBJECT (self->task)) && self->changed)
795     {
796       g_debug ("Saving task…");
797 
798       gtd_provider_update_task (gtd_task_get_provider (self->task),
799                                 self->task,
800                                 NULL,
801                                 on_task_updated_cb,
802                                 self);
803       self->changed = FALSE;
804     }
805 
806   if (active)
807     gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (self)), "active");
808   else
809     gtk_style_context_remove_class (gtk_widget_get_style_context (GTK_WIDGET (self)), "active");
810 
811   g_signal_emit (self, active ? signals[ENTER] : signals[EXIT], 0);
812 }
813 
814 void
gtd_task_row_set_sizegroups(GtdTaskRow * self,GtkSizeGroup * name_group,GtkSizeGroup * date_group)815 gtd_task_row_set_sizegroups (GtdTaskRow   *self,
816                              GtkSizeGroup *name_group,
817                              GtkSizeGroup *date_group)
818 {
819   gtk_size_group_add_widget (name_group, GTK_WIDGET (self->task_list_label));
820   gtk_size_group_add_widget (name_group, GTK_WIDGET (self->task_date_label));
821 }
822