1 /* gcal-month-view.c
2  *
3  * Copyright © 2015 Erick Pérez Castellanos
4  *             2017-2020 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program. If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #define G_LOG_DOMAIN "GcalMonthView"
21 
22 #include "config.h"
23 #include "gcal-application.h"
24 #include "gcal-clock.h"
25 #include "gcal-context.h"
26 #include "gcal-debug.h"
27 #include "gcal-month-cell.h"
28 #include "gcal-month-popover.h"
29 #include "gcal-month-view.h"
30 #include "gcal-timeline-subscriber.h"
31 #include "gcal-utils.h"
32 #include "gcal-view.h"
33 
34 #include <glib/gi18n.h>
35 
36 typedef struct
37 {
38   GtkWidget          *event_widget;
39   gboolean            visible;
40   guint8              length;
41   guint8              height;
42   guint8              cell;
43 } GcalEventBlock;
44 
45 struct _GcalMonthView
46 {
47   GtkContainer        parent;
48 
49   GcalMonthPopover   *overflow_popover;
50 
51   GdkWindow          *event_window;
52 
53   /* Header widgets */
54   GtkWidget          *header;
55   GtkWidget          *label_0;
56   GtkWidget          *label_1;
57   GtkWidget          *label_2;
58   GtkWidget          *label_3;
59   GtkWidget          *label_4;
60   GtkWidget          *label_5;
61   GtkWidget          *label_6;
62   GtkWidget          *month_label;
63   GtkWidget          *year_label;
64   GtkWidget          *weekday_label[7];
65 
66   /* Grid widgets */
67   GtkWidget          *grid;
68   GtkWidget          *month_cell[6][7]; /* unowned */
69 
70   /*
71    * Hash to keep children widgets (all of them, parent widgets and its parts if there's any),
72    * uuid as key and a list of all the instances of the event as value. Here, the first widget
73    * on the list is the master, and the rest are the parts. Note: the master is a part itself.
74    */
75   GHashTable         *children;
76 
77   /*
78    * Hash containig single-cell events, day of the month, on month-view, month of the year on
79    * year-view as key anda list of the events that belongs to this cell
80    */
81   GHashTable         *single_cell_children;
82 
83   /*
84    * A sorted list containig multiday events. This one contains only parents events, to find out
85    * its parts @children will be used.
86    */
87   GList              *multi_cell_children;
88 
89   /*
90    * Hash containing cells that who has overflow per list of hidden widgets.
91    */
92   GHashTable         *overflow_cells;
93 
94   /*
95    * the cell on which its drawn the first day of the month, in the first row, 0 for the first
96    * cell, 1 for the second, and so on, this takes first_weekday into account already.
97    */
98   gint                days_delay;
99 
100   /*
101    * The cell whose keyboard focus is on.
102    */
103   gint                keyboard_cell;
104 
105   /*
106    * first day of the week according to user locale, being
107    * 0 for Sunday, 1 for Monday and so on */
108   gint                first_weekday;
109 
110   /*
111    * The start & end dates of the selection. We use datetimes to allow the user to navigate between
112    * months using the keyboard.
113    */
114   GDateTime          *start_mark_cell;
115   GDateTime          *end_mark_cell;
116 
117   /* Storage for the accumulated scrolling */
118   gdouble             scroll_value;
119   guint               update_grid_id;
120 
121   /* property */
122   GDateTime          *date;
123   GcalContext        *context;
124 
125   gboolean            pending_event_allocation;
126 };
127 
128 
129 static void          gcal_view_interface_init                    (GcalViewInterface  *iface);
130 
131 static void          gtk_buildable_interface_init                (GtkBuildableIface  *iface);
132 
133 static void          on_event_activated_cb                       (GcalEventWidget    *widget,
134                                                                   GcalMonthView      *self);
135 
136 static void          on_event_widget_visibility_changed_cb       (GtkWidget          *event_widget,
137                                                                   GParamSpec         *pspec,
138                                                                   GcalMonthView      *self);
139 
140 static void          on_month_cell_show_overflow_popover_cb      (GcalMonthCell      *cell,
141                                                                   GtkWidget          *button,
142                                                                   GcalMonthView      *self);
143 
144 static void          gcal_timeline_subscriber_interface_init     (GcalTimelineSubscriberInterface *iface);
145 
146 
147 G_DEFINE_TYPE_WITH_CODE (GcalMonthView, gcal_month_view, GTK_TYPE_CONTAINER,
148                          G_IMPLEMENT_INTERFACE (GCAL_TYPE_VIEW, gcal_view_interface_init)
149                          G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
150                                                 gtk_buildable_interface_init)
151                          G_IMPLEMENT_INTERFACE (GCAL_TYPE_TIMELINE_SUBSCRIBER,
152                                                 gcal_timeline_subscriber_interface_init));
153 
154 enum
155 {
156   PROP_0,
157   PROP_DATE,
158   PROP_CONTEXT,
159   N_PROPS
160 };
161 
162 
163 /*
164  * Auxiliary functions
165  */
166 
167 static inline void
cancel_selection(GcalMonthView * self)168 cancel_selection (GcalMonthView *self)
169 {
170   g_clear_pointer (&self->start_mark_cell, g_date_time_unref);
171   g_clear_pointer (&self->end_mark_cell, g_date_time_unref);
172 }
173 
174 static void
activate_event(GcalMonthView * self,GcalEventWidget * event_widget)175 activate_event (GcalMonthView   *self,
176                 GcalEventWidget *event_widget)
177 {
178   cancel_selection (self);
179   gcal_month_popover_popdown (self->overflow_popover);
180 
181   g_signal_emit_by_name (self, "event-activated", event_widget);
182 }
183 
184 static void
setup_child_widget(GcalMonthView * self,GtkWidget * widget)185 setup_child_widget (GcalMonthView *self,
186                     GtkWidget     *widget)
187 {
188   if (!gtk_widget_get_parent (widget))
189     gtk_widget_set_parent (widget, GTK_WIDGET (self));
190 
191   g_signal_connect_object (widget, "activate", G_CALLBACK (on_event_activated_cb), self, 0);
192   g_signal_connect_object (widget, "notify::visible", G_CALLBACK (on_event_widget_visibility_changed_cb), self, 0);
193 }
194 
195 static gboolean
emit_create_event(GcalMonthView * self)196 emit_create_event (GcalMonthView *self)
197 {
198   GtkAllocation alloc;
199   GDateTime *end_dt,*start_dt;
200   gboolean should_clear_end;
201   gdouble x, y;
202   gint cell;
203 
204   GCAL_ENTRY;
205 
206   if (!self->start_mark_cell || !self->end_mark_cell)
207     GCAL_RETURN (FALSE);
208 
209   should_clear_end = FALSE;
210   start_dt = self->start_mark_cell;
211   end_dt = self->end_mark_cell;
212 
213   /* Swap dates if start > end */
214   if (g_date_time_compare (start_dt, end_dt) > 0)
215     {
216       GDateTime *aux;
217       aux = start_dt;
218       start_dt = end_dt;
219       end_dt = aux;
220     }
221 
222   /* Only setup an end date when days are different */
223   if (!g_date_time_equal (start_dt, end_dt))
224     {
225       GDateTime *tmp_dt;
226 
227       tmp_dt = g_date_time_new_local (g_date_time_get_year (end_dt),
228                                       g_date_time_get_month (end_dt),
229                                       g_date_time_get_day_of_month (end_dt),
230                                       0, 0, 0);
231       end_dt = g_date_time_add_days (tmp_dt, 1);
232 
233       should_clear_end = TRUE;
234 
235       g_clear_pointer (&tmp_dt, g_date_time_unref);
236     }
237 
238   /* Get the corresponding GcalMonthCell */
239   cell = g_date_time_get_day_of_month (self->end_mark_cell) + self->days_delay - 1;
240 
241   gtk_widget_get_allocation (self->month_cell[cell / 7][cell % 7], &alloc);
242 
243   x = alloc.x + alloc.width / 2.0;
244   y = alloc.y + alloc.height / 2.0;
245 
246   g_signal_emit_by_name (self, "create-event", start_dt, end_dt, x, y);
247 
248   if (should_clear_end)
249     g_clear_pointer (&end_dt, g_date_time_unref);
250 
251   GCAL_RETURN (TRUE);
252 }
253 
254 static GtkWidget*
get_month_cell_at_position(GcalMonthView * self,gdouble x,gdouble y,gint * cell)255 get_month_cell_at_position (GcalMonthView *self,
256                             gdouble        x,
257                             gdouble        y,
258                             gint          *cell)
259 {
260   gint i;
261 
262   if (y < gtk_widget_get_allocated_height (self->header))
263     return NULL;
264 
265   for (i = 0; i < 42; i++)
266     {
267       GtkAllocation alloc;
268       guint row, col;
269 
270       row = i / 7;
271       col = i % 7;
272 
273       gtk_widget_get_allocation (self->month_cell[row][col], &alloc);
274 
275       if (x >= alloc.x && x < alloc.x + alloc.width &&
276           y >= alloc.y && y < alloc.y + alloc.height)
277         {
278           if (cell)
279             *cell = i;
280 
281           return self->month_cell[row][col];
282         }
283     }
284 
285   if (cell)
286     *cell = -1;
287 
288   return NULL;
289 }
290 
291 static void
calculate_event_cells(GcalMonthView * self,GcalEvent * event,gint * out_first_cell,gint * out_last_cell)292 calculate_event_cells (GcalMonthView *self,
293                        GcalEvent     *event,
294                        gint          *out_first_cell,
295                        gint          *out_last_cell)
296 {
297   gboolean all_day;
298 
299   all_day = gcal_event_get_all_day (event);
300 
301   /* Start date */
302   if (out_first_cell)
303     {
304       g_autoptr (GDateTime) start_date = NULL;
305       gint first_cell;
306 
307       first_cell = 1;
308       start_date = gcal_event_get_date_start (event);
309       start_date = all_day ? g_date_time_ref (start_date) : g_date_time_to_local (start_date);
310 
311       if (g_date_time_get_year (start_date) == g_date_time_get_year (self->date) &&
312           g_date_time_get_month (start_date) == g_date_time_get_month (self->date))
313         {
314           first_cell = g_date_time_get_day_of_month (start_date);
315         }
316 
317       first_cell += self->days_delay - 1;
318 
319       *out_first_cell = first_cell;
320     }
321 
322   /*
323    * The logic for the end date is the same, except that we have to check
324    * if the event is all day or not.
325    */
326   if (out_last_cell)
327     {
328       g_autoptr (GDateTime) end_date = NULL;
329       gint last_cell;
330 
331       last_cell = gcal_date_time_get_days_in_month (self->date);
332       end_date = gcal_event_get_date_end (event);
333       end_date = all_day ? g_date_time_ref (end_date) : g_date_time_to_local (end_date);
334 
335       if (g_date_time_get_year (end_date) == g_date_time_get_year (self->date) &&
336           g_date_time_get_month (end_date) == g_date_time_get_month (self->date))
337         {
338           last_cell = g_date_time_get_day_of_month (end_date);
339 
340           /* If the event is all day, we have to subtract 1 to find the the real date */
341           if (all_day)
342             last_cell--;
343         }
344 
345       last_cell += self->days_delay - 1;
346 
347       *out_last_cell = last_cell;
348     }
349 }
350 
351 static GPtrArray*
calculate_multiday_event_blocks(GcalMonthView * self,GtkWidget * event_widget,gdouble * vertical_cell_space,gdouble * size_left,gint * events_at_day,gint * allocated_events_at_day)352 calculate_multiday_event_blocks (GcalMonthView *self,
353                                  GtkWidget     *event_widget,
354                                  gdouble       *vertical_cell_space,
355                                  gdouble       *size_left,
356                                  gint          *events_at_day,
357                                  gint          *allocated_events_at_day)
358 {
359   GcalEventBlock *block;
360   GcalEvent *event;
361   GPtrArray *blocks;
362   gboolean was_visible;
363   gdouble old_y;
364   gdouble y;
365   gint first_cell;
366   gint last_cell;
367   gint i;
368 
369   GCAL_ENTRY;
370 
371   old_y  = -1.0;
372   was_visible = FALSE;
373 
374   /* Get the event cells */
375   event = gcal_event_widget_get_event (GCAL_EVENT_WIDGET (event_widget));
376   calculate_event_cells (self, event, &first_cell, &last_cell);
377 
378   blocks = g_ptr_array_new_full (last_cell - first_cell + 1, g_free);
379   block = NULL;
380 
381   GCAL_TRACE_MSG ("Positioning '%s' (multiday) from %d to %d", gcal_event_get_summary (event), first_cell, last_cell);
382 
383   for (i = first_cell; i <= last_cell; i++)
384     {
385       GtkStyleContext *context;
386       GtkBorder margin;
387       gboolean visible_at_range;
388       gboolean different_row;
389       gboolean will_overflow;
390       gdouble real_height;
391       gint remaining_events;
392       gint minimum_height;
393 
394       real_height = size_left[i];
395 
396       /* Calculate the minimum event height */
397       context = gtk_widget_get_style_context (GTK_WIDGET (event_widget));
398 
399       gtk_style_context_get_margin (context, gtk_style_context_get_state (context), &margin);
400       gtk_widget_get_preferred_height (GTK_WIDGET (event_widget), &minimum_height, NULL);
401 
402       minimum_height += margin.top + margin.bottom;
403 
404       /* Count this event at this cell */
405       different_row = i / 7 != (i - 1) / 7;
406       remaining_events = events_at_day[i] - allocated_events_at_day[i];
407       will_overflow = remaining_events * minimum_height > real_height;
408 
409       if (will_overflow)
410         real_height -= gcal_month_cell_get_overflow_height (GCAL_MONTH_CELL (self->month_cell[i / 7][i % 7]));
411 
412       visible_at_range = real_height >= minimum_height;
413 
414       if (visible_at_range)
415         allocated_events_at_day[i]++;
416 
417       y = vertical_cell_space[i] - size_left[i];
418 
419       /* At the first iteration, make was_visible and visible_at_range equal */
420       if (i == first_cell)
421         was_visible = visible_at_range;
422 
423       if (!block || y != old_y || different_row || was_visible != visible_at_range)
424         {
425           GCAL_TRACE_MSG ("Breaking event at cell %d", i);
426 
427           /* Only create a new event widget after the first one is consumed */
428           if (block)
429             {
430               GcalEvent *event;
431               GList *aux;
432 
433               GCAL_TRACE_MSG ("Cloning event widget");
434 
435               event = gcal_event_widget_get_event (GCAL_EVENT_WIDGET (event_widget));
436 
437               event_widget = gcal_event_widget_clone (GCAL_EVENT_WIDGET (event_widget));
438               gtk_widget_show (event_widget);
439               setup_child_widget (self, event_widget);
440 
441               aux = g_hash_table_lookup (self->children, gcal_event_get_uid (event));
442               aux = g_list_append (aux, event_widget);
443             }
444 
445           old_y = y;
446 
447           /* Add a new block */
448           block = g_new (GcalEventBlock, 1);
449           block->event_widget = event_widget;
450           block->visible = visible_at_range;
451           block->height = minimum_height;
452           block->length = 1;
453           block->cell = i;
454 
455           g_ptr_array_add (blocks, block);
456         }
457       else
458         {
459           block->length++;
460         }
461 
462       was_visible = visible_at_range;
463     }
464 
465   GCAL_RETURN (blocks);
466 }
467 
468 static void
cleanup_overflow_information(GcalMonthView * self)469 cleanup_overflow_information (GcalMonthView *self)
470 {
471   g_autoptr (GList) widgets = NULL;
472   GList *aux = NULL;
473   GList *l = NULL;
474 
475   /* Remove every widget' parts, but the master widget */
476   widgets = g_hash_table_get_values (self->children);
477 
478   for (aux = widgets; aux; aux = g_list_next (aux))
479     l = g_list_concat (l, g_list_copy (g_list_next (aux->data)));
480 
481   g_list_free_full (l, (GDestroyNotify) gtk_widget_destroy);
482 
483   /* Clean overflow information */
484   g_hash_table_remove_all (self->overflow_cells);
485 }
486 
487 static void
remove_cell_border_and_padding(GtkWidget * cell,gdouble * x,gdouble * y,gdouble * width)488 remove_cell_border_and_padding (GtkWidget *cell,
489                                 gdouble   *x,
490                                 gdouble   *y,
491                                 gdouble   *width)
492 {
493   GtkStyleContext *cell_context;
494   GtkBorder cell_padding;
495   GtkBorder cell_border;
496   gint header_height;
497 
498   cell_context = gtk_widget_get_style_context (cell);
499   gtk_style_context_get_border (cell_context, gtk_style_context_get_state (cell_context), &cell_border);
500   gtk_style_context_get_padding (cell_context, gtk_style_context_get_state (cell_context), &cell_padding);
501 
502   header_height = gcal_month_cell_get_header_height (GCAL_MONTH_CELL (cell));
503 
504   if (x)
505     *x += cell_border.left + cell_padding.left;
506 
507   if (y)
508     *y += cell_border.top + cell_padding.top + header_height;
509 
510   if (width)
511     {
512       *width -= cell_border.left + cell_border.right;
513       *width -= cell_padding.left + cell_padding.right;
514     }
515 }
516 
517 static void
count_events_per_day(GcalMonthView * self,gint * events_per_day)518 count_events_per_day (GcalMonthView *self,
519                       gint          *events_per_day)
520 {
521   GHashTableIter iter;
522   gpointer key;
523   GList *children;
524   GList *l;
525 
526   /* Multiday events */
527   for (l = self->multi_cell_children; l; l = l->next)
528     {
529       GcalEvent *event;
530       gint first_cell;
531       gint last_cell;
532       gint i;
533 
534       if (!gtk_widget_get_visible (l->data))
535         continue;
536 
537       event = gcal_event_widget_get_event (l->data);
538 
539       calculate_event_cells (self, event, &first_cell, &last_cell);
540 
541       for (i = first_cell; i <= last_cell; i++)
542          events_per_day[i]++;
543     }
544 
545   /* Single day events */
546   g_hash_table_iter_init (&iter, self->single_cell_children);
547   while (g_hash_table_iter_next (&iter, &key, (gpointer*) &children))
548     {
549       gint cell;
550 
551       cell = GPOINTER_TO_INT (key) + self->days_delay - 1;
552 
553       for (l = children; l; l = l->next)
554         {
555           if (!gtk_widget_get_visible (l->data))
556             continue;
557 
558           events_per_day[cell]++;
559         }
560     }
561 }
562 
563 static gdouble
get_real_event_widget_height(GtkWidget * widget)564 get_real_event_widget_height (GtkWidget *widget)
565 {
566   gint min_height;
567 
568   gtk_widget_get_preferred_height (widget, &min_height, NULL);
569 
570   min_height += gtk_widget_get_margin_top (widget);
571   min_height += gtk_widget_get_margin_bottom (widget);
572 
573   return min_height;
574 }
575 
576 static inline gboolean
month_view_contains_event(GcalRange * month_range,GcalEvent * event)577 month_view_contains_event (GcalRange *month_range,
578                            GcalEvent *event)
579 {
580   GcalRangeOverlap overlap;
581 
582   overlap = gcal_range_calculate_overlap (month_range, gcal_event_get_range (event), NULL);
583   return overlap != GCAL_RANGE_NO_OVERLAP;
584 }
585 
586 static void
allocate_multiday_events(GcalMonthView * self,gdouble * vertical_cell_space,gdouble * size_left,gint * events_at_day,gint * allocated_events_at_day)587 allocate_multiday_events (GcalMonthView *self,
588                           gdouble       *vertical_cell_space,
589                           gdouble       *size_left,
590                           gint          *events_at_day,
591                           gint          *allocated_events_at_day)
592 {
593   g_autoptr (GcalRange) month_range = NULL;
594   GtkAllocation event_allocation;
595   GtkBorder margin;
596   GList *l;
597   gboolean is_rtl;
598 
599   is_rtl = gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL;
600   month_range = gcal_timeline_subscriber_get_range (GCAL_TIMELINE_SUBSCRIBER (self));
601 
602   for (l = self->multi_cell_children; l; l = g_list_next (l))
603     {
604       g_autoptr (GPtrArray) blocks = NULL;
605       GtkStyleContext *child_context;
606       GtkWidget *child_widget;
607       GcalEvent *event;
608       gint block_idx;
609 
610       child_widget = (GtkWidget*) l->data;
611 
612       if (!gtk_widget_get_visible (child_widget))
613         continue;
614 
615       event = gcal_event_widget_get_event (l->data);
616       child_context = gtk_widget_get_style_context (l->data);
617 
618       if (!month_view_contains_event (month_range, event))
619         continue;
620 
621       /*
622        * Multiday events can "break" following these rules:
623        *
624        *  - Break when line changed
625        *  - Break when the vertical position in the cells changed
626        *  - Break when the previous part's visibility is different than the current
627        */
628       blocks = calculate_multiday_event_blocks (self,
629                                                 child_widget,
630                                                 vertical_cell_space,
631                                                 size_left,
632                                                 events_at_day,
633                                                 allocated_events_at_day);
634 
635       for (block_idx = 0; block_idx < blocks->len; block_idx++)
636         {
637           g_autoptr (GDateTime) dt_start = NULL;
638           g_autoptr (GDateTime) dt_end = NULL;
639           GcalEventBlock *block;
640           GtkAllocation first_cell_allocation;
641           GtkAllocation last_cell_allocation;
642           GtkWidget *last_month_cell;
643           GtkWidget *month_cell;
644           GList *aux;
645           gdouble pos_x;
646           gdouble pos_y;
647           gdouble width;
648           gint last_block_cell;
649           gint minimum_height;
650           gint length;
651           gint cell;
652           gint day;
653           gint j;
654 
655           block = g_ptr_array_index (blocks, block_idx);
656           length = block->length;
657           cell = block->cell;
658           last_block_cell = cell + length - 1;
659           day = cell - self->days_delay + 1;
660 
661           child_widget = block->event_widget;
662           child_context = gtk_widget_get_style_context (child_widget);
663 
664           /* No space left, add to the overflow and continue */
665           if (!block->visible)
666             {
667               gint idx;
668 
669               gtk_widget_set_child_visible (child_widget, FALSE);
670 
671               for (idx = cell; idx < cell + length; idx++)
672                 {
673                   aux = g_hash_table_lookup (self->overflow_cells, GINT_TO_POINTER (idx));
674                   aux = g_list_append (aux, child_widget);
675 
676                   if (g_list_length (aux) == 1)
677                     g_hash_table_insert (self->overflow_cells, GINT_TO_POINTER (idx), aux);
678                   else
679                     g_hash_table_replace (self->overflow_cells, GINT_TO_POINTER (idx), g_list_copy (aux));
680                 }
681 
682               continue;
683             }
684 
685 
686           /* Retrieve the cell widget. On RTL languages, swap the first and last cells */
687           month_cell = self->month_cell[cell / 7][cell % 7];
688           last_month_cell = self->month_cell[last_block_cell / 7][last_block_cell % 7];
689 
690           if (is_rtl)
691             {
692               GtkWidget *aux = month_cell;
693               month_cell = last_month_cell;
694               last_month_cell = aux;
695             }
696 
697           gtk_widget_get_allocation (month_cell, &first_cell_allocation);
698           gtk_widget_get_allocation (last_month_cell, &last_cell_allocation);
699 
700           gtk_widget_set_child_visible (child_widget, TRUE);
701 
702           /*
703            * Setup the widget's start date as the first day of the row,
704            * and the widget's end date as the last day of the row. We don't
705            * have to worry about the dates, since GcalEventWidget performs
706            * some checks and only applies the dates when it's valid.
707            */
708           dt_start = g_date_time_new (g_date_time_get_timezone (gcal_event_get_date_start (event)),
709                                       g_date_time_get_year (self->date),
710                                       g_date_time_get_month (self->date),
711                                       day,
712                                       0, 0, 0);
713 
714           /* FIXME: use end date's timezone here */
715           dt_end = g_date_time_add_days (dt_start, length);
716 
717           gcal_event_widget_set_date_start (GCAL_EVENT_WIDGET (child_widget), dt_start);
718           gcal_event_widget_set_date_end (GCAL_EVENT_WIDGET (child_widget), dt_end);
719 
720           /* Position and allocate the child widget */
721           gtk_style_context_get_margin (gtk_widget_get_style_context (child_widget),
722                                         gtk_style_context_get_state (child_context),
723                                         &margin);
724 
725 
726           pos_x = first_cell_allocation.x + margin.left;
727           pos_y = first_cell_allocation.y + margin.top;
728           width = last_cell_allocation.x + last_cell_allocation.width - first_cell_allocation.x;
729 
730           remove_cell_border_and_padding (month_cell, &pos_x, &pos_y, &width);
731 
732           /*
733            * We can only get the minimum height after making all these calculations,
734            * otherwise GTK complains about allocating without calling get_preferred_height.
735            */
736           minimum_height = get_real_event_widget_height (child_widget);
737 
738           event_allocation.x = pos_x;
739           event_allocation.y = pos_y + vertical_cell_space[cell] - size_left[cell];
740           event_allocation.width = width - (margin.left + margin.right);
741           event_allocation.height = minimum_height;
742 
743           gtk_widget_size_allocate (child_widget, &event_allocation);
744 
745           /* update size_left */
746           for (j = 0; j < length; j++)
747             {
748               size_left[cell + j] -= minimum_height;
749               size_left[cell + j] -= margin.top + margin.bottom;
750             }
751         }
752     }
753 }
754 
755 static void
allocate_single_day_events(GcalMonthView * self,gdouble * vertical_cell_space,gdouble * size_left,gint * events_at_day,gint * allocated_events_at_day)756 allocate_single_day_events (GcalMonthView *self,
757                             gdouble       *vertical_cell_space,
758                             gdouble       *size_left,
759                             gint          *events_at_day,
760                             gint          *allocated_events_at_day)
761 {
762   g_autoptr (GcalRange) month_range = NULL;
763   GHashTableIter iter;
764   GtkAllocation event_allocation;
765   GtkAllocation cell_allocation;
766   GtkBorder margin;
767   gpointer key, value;
768 
769   month_range = gcal_timeline_subscriber_get_range (GCAL_TIMELINE_SUBSCRIBER (self));
770 
771   g_hash_table_iter_init (&iter, self->single_cell_children);
772 
773   while (g_hash_table_iter_next (&iter, &key, &value))
774     {
775       GtkWidget *month_cell;
776       GList *aux;
777       GList *l;
778       gboolean will_overflow;
779       gint cell;
780       gint day;
781 
782       day = GPOINTER_TO_INT (key);
783       cell = day + self->days_delay - 1;
784       month_cell = self->month_cell[cell / 7][cell % 7];
785 
786       gtk_widget_get_allocation (month_cell, &cell_allocation);
787 
788       l = (GList*) value;
789       for (aux = l; aux; aux = g_list_next (aux))
790         {
791           GcalEvent *event;
792           GtkStyleContext *child_context;
793           GtkWidget *child_widget;
794           gdouble real_height;
795           gdouble pos_y;
796           gdouble pos_x;
797           gdouble width;
798           gint remaining_events;
799           gint minimum_height;
800 
801           child_widget = aux->data;
802           event = gcal_event_widget_get_event (GCAL_EVENT_WIDGET (child_widget));
803 
804           if (!gtk_widget_get_visible (child_widget))
805             continue;
806 
807           if (!month_view_contains_event (month_range, event))
808             continue;
809 
810           child_context = gtk_widget_get_style_context (child_widget);
811 
812           gtk_style_context_get_margin (child_context, gtk_style_context_get_state (child_context), &margin);
813           minimum_height = get_real_event_widget_height (child_widget) + margin.top + margin.bottom;
814 
815           /* Check for overflow */
816           remaining_events = events_at_day[cell] - allocated_events_at_day[cell];
817           will_overflow = remaining_events * minimum_height >= size_left[cell];
818           real_height = size_left[cell];
819 
820           if (will_overflow)
821             real_height -= gcal_month_cell_get_overflow_height (GCAL_MONTH_CELL (month_cell));
822 
823           /* No space left, add to the overflow and continue */
824           if (real_height < minimum_height)
825             {
826               gtk_widget_set_child_visible (child_widget, FALSE);
827 
828               l = g_hash_table_lookup (self->overflow_cells, GINT_TO_POINTER (cell));
829               l = g_list_append (l, child_widget);
830 
831               if (g_list_length (l) == 1)
832                 g_hash_table_insert (self->overflow_cells, GINT_TO_POINTER (cell), l);
833               else
834                 g_hash_table_replace (self->overflow_cells, GINT_TO_POINTER (cell), g_list_copy (l));
835 
836               continue;
837             }
838 
839           allocated_events_at_day[cell]++;
840 
841           gtk_widget_set_child_visible (child_widget, TRUE);
842 
843           pos_x = cell_allocation.x + margin.left;
844           pos_y = cell_allocation.y + margin.top;
845           width = cell_allocation.width;
846 
847           remove_cell_border_and_padding (month_cell, &pos_x, &pos_y, &width);
848 
849           event_allocation.x = pos_x;
850           event_allocation.y = pos_y + vertical_cell_space[cell] - size_left[cell];
851           event_allocation.width = width - (margin.left + margin.right);
852           event_allocation.height = minimum_height;
853 
854           gtk_widget_set_child_visible (child_widget, TRUE);
855           gtk_widget_size_allocate (child_widget, &event_allocation);
856 
857           size_left[cell] -= minimum_height + margin.top + margin.bottom;
858         }
859     }
860 }
861 
862 static void
setup_header_widget(GcalMonthView * self,GtkWidget * widget)863 setup_header_widget (GcalMonthView *self,
864                      GtkWidget     *widget)
865 {
866   self->header = widget;
867   gtk_widget_set_parent (widget, GTK_WIDGET (self));
868 }
869 
870 static void
setup_month_grid(GcalMonthView * self,GtkWidget * widget)871 setup_month_grid (GcalMonthView *self,
872                   GtkWidget     *widget)
873 {
874   guint row, col;
875 
876   self->grid = widget;
877   gtk_widget_set_parent (widget, GTK_WIDGET (self));
878 
879   for (row = 0; row < 6; row++)
880     {
881       for (col = 0; col < 7; col++)
882         {
883           GtkWidget *cell;
884 
885           cell = gcal_month_cell_new ();
886 
887           g_signal_connect_object (cell, "show-overflow", G_CALLBACK (on_month_cell_show_overflow_popover_cb), self, 0);
888 
889           self->month_cell[row][col] = cell;
890 
891           gtk_grid_attach (GTK_GRID (widget), cell, col, row, 1, 1);
892         }
893     }
894 }
895 
896 static GcalWeatherInfo*
get_weather_info_for_cell(GcalMonthView * self,guint cell)897 get_weather_info_for_cell (GcalMonthView *self,
898                            guint          cell)
899 {
900   GcalWeatherService *weather_service;
901   GcalMonthCell *first_cell;
902   GPtrArray *weather_infos;
903   GDateTime *first_dt;
904   GDate first;
905   guint i;
906 
907   if (!self->date)
908     return NULL;
909 
910   weather_service = gcal_context_get_weather_service (self->context);
911   weather_infos = gcal_weather_service_get_weather_infos (weather_service);
912 
913   first_cell = GCAL_MONTH_CELL (self->month_cell[0][0]);
914   first_dt = gcal_month_cell_get_date (first_cell);
915 
916   g_date_set_dmy (&first,
917                   g_date_time_get_day_of_month (first_dt),
918                   g_date_time_get_month (first_dt),
919                   g_date_time_get_year (first_dt));
920 
921 
922   for (i = 0; weather_infos && i < weather_infos->len; i++)
923     {
924       GcalWeatherInfo *info;
925       GDate weather_date;
926       gint day_difference;
927 
928       info = g_ptr_array_index (weather_infos, i);
929 
930       gcal_weather_info_get_date (info, &weather_date);
931       day_difference = g_date_days_between (&first, &weather_date);
932 
933       if (day_difference == cell)
934         return info;
935     }
936 
937   return NULL;
938 }
939 
940 static void
update_weather(GcalMonthView * self,gboolean clear_old)941 update_weather (GcalMonthView *self,
942                 gboolean       clear_old)
943 {
944   guint row;
945   guint col;
946 
947   g_return_if_fail (GCAL_IS_MONTH_VIEW (self));
948 
949   for (row = 0; row < 6; row++)
950     {
951       for (col = 0; col < 7; col++)
952         {
953           GcalMonthCell *cell = GCAL_MONTH_CELL (self->month_cell[row][col]);
954           gcal_month_cell_set_weather (cell,  get_weather_info_for_cell (self, row * 7 + col));
955         }
956     }
957 }
958 
959 static gboolean
update_month_cells(GcalMonthView * self)960 update_month_cells (GcalMonthView *self)
961 {
962   g_autoptr (GDateTime) dt = NULL;
963   gboolean show_last_row;
964   guint row, col;
965 
966   show_last_row = gcal_date_time_get_days_in_month (self->date) + self->days_delay > 35;
967   dt = g_date_time_new_local (g_date_time_get_year (self->date),
968                               g_date_time_get_month (self->date),
969                               1, 0, 0, 0);
970 
971   for (row = 0; row < 6; row++)
972     {
973       for (col = 0; col < 7; col++)
974         {
975           g_autoptr (GDateTime) cell_date = NULL;
976           GcalMonthCell *cell;
977           GDateTime *selection_start;
978           GDateTime *selection_end;
979           GList *l;
980           gboolean different_month;
981           gboolean selected;
982           guint day;
983 
984           cell = GCAL_MONTH_CELL (self->month_cell[row][col]);
985           day = row * 7 + col;
986           selected = FALSE;
987           l = NULL;
988 
989           /* Cell date */
990           cell_date = g_date_time_add_days (dt, row * 7 + col - self->days_delay);
991 
992           gcal_month_cell_set_date (cell, cell_date);
993 
994           /* Different month */
995           different_month = day < self->days_delay ||
996                             day - self->days_delay >= gcal_date_time_get_days_in_month (self->date);
997 
998           gcal_month_cell_set_different_month (cell, different_month);
999 
1000           /* If the last row is empty, hide it */
1001           gtk_widget_set_visible (GTK_WIDGET (cell), show_last_row || row < 5);
1002 
1003           if (different_month)
1004             {
1005               gcal_month_cell_set_selected (cell, FALSE);
1006               gcal_month_cell_set_overflow (cell, 0);
1007               continue;
1008             }
1009 
1010           /* Overflow */
1011           if (g_hash_table_contains (self->overflow_cells, GINT_TO_POINTER (day)))
1012             l = g_hash_table_lookup (self->overflow_cells, GINT_TO_POINTER (day));
1013 
1014           gcal_month_cell_set_overflow (cell, l ? g_list_length (l) : 0);
1015 
1016           /* Selection */
1017           selection_start = self->start_mark_cell;
1018           selection_end = self->end_mark_cell;
1019 
1020           if (selection_start)
1021             {
1022               if (!selection_end)
1023                 selection_end = selection_start;
1024 
1025               /* Swap dates if end is before start */
1026               if (gcal_date_time_compare_date (selection_start, selection_end) < 0)
1027                 {
1028                   GDateTime *aux = selection_end;
1029                   selection_end = selection_start;
1030                   selection_start = aux;
1031                 }
1032 
1033               selected = gcal_date_time_compare_date (selection_start, gcal_month_cell_get_date (cell)) >= 0 &&
1034                          gcal_date_time_compare_date (selection_end, gcal_month_cell_get_date (cell)) <= 0;
1035             }
1036 
1037           gcal_month_cell_set_selected (cell, selected);
1038         }
1039     }
1040 
1041   update_weather (self, FALSE);
1042 
1043   self->update_grid_id = 0;
1044 
1045   return G_SOURCE_REMOVE;
1046 }
1047 
1048 static void
queue_update_month_cells(GcalMonthView * self)1049 queue_update_month_cells (GcalMonthView *self)
1050 {
1051   if (self->update_grid_id > 0)
1052     return;
1053 
1054   self->update_grid_id = g_idle_add ((GSourceFunc) update_month_cells, self);
1055 }
1056 
1057 static void
update_header_labels(GcalMonthView * self)1058 update_header_labels (GcalMonthView *self)
1059 {
1060   gchar year_str[10] = { 0, };
1061 
1062   g_snprintf (year_str, 10, "%d", g_date_time_get_year (self->date));
1063 
1064   gtk_label_set_label (GTK_LABEL (self->month_label), gcal_get_month_name (g_date_time_get_month (self->date) - 1));
1065   gtk_label_set_label (GTK_LABEL (self->year_label), year_str);
1066 }
1067 
1068 static inline void
update_weekday_labels(GcalMonthView * self)1069 update_weekday_labels (GcalMonthView *self)
1070 {
1071   gint i;
1072 
1073   for (i = 0; i < 7; i++)
1074     {
1075       g_autofree gchar *weekday_name = NULL;
1076 
1077       weekday_name = g_utf8_strup (gcal_get_weekday ((i + self->first_weekday) % 7), -1);
1078 
1079       gtk_label_set_label (GTK_LABEL (self->weekday_label[i]), weekday_name);
1080     }
1081 }
1082 
1083 
1084 /*
1085  * Callbacks
1086  */
1087 
1088 static void
add_new_event_button_cb(GtkWidget * button,gpointer user_data)1089 add_new_event_button_cb (GtkWidget *button,
1090                          gpointer   user_data)
1091 {
1092   GcalMonthView *self;
1093   GDateTime *start_date;
1094   gint day;
1095 
1096   self = GCAL_MONTH_VIEW (user_data);
1097 
1098   gcal_month_popover_popdown (self->overflow_popover);
1099 
1100   day = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (self->overflow_popover), "selected-day"));
1101   start_date = g_date_time_new_local (g_date_time_get_year (self->date),
1102                                       g_date_time_get_month (self->date),
1103                                       day, 0, 0, 0);
1104 
1105   g_signal_emit_by_name (GCAL_VIEW (user_data), "create-event-detailed", start_date, NULL);
1106 
1107   g_date_time_unref (start_date);
1108 }
1109 
1110 static void
on_event_activated_cb(GcalEventWidget * widget,GcalMonthView * self)1111 on_event_activated_cb (GcalEventWidget *widget,
1112                        GcalMonthView   *self)
1113 {
1114   activate_event (self, widget);
1115 }
1116 
1117 static void
on_event_widget_visibility_changed_cb(GtkWidget * event_widget,GParamSpec * pspec,GcalMonthView * self)1118 on_event_widget_visibility_changed_cb (GtkWidget     *event_widget,
1119                                        GParamSpec    *pspec,
1120                                        GcalMonthView *self)
1121 {
1122   self->pending_event_allocation = TRUE;
1123   gtk_widget_queue_resize (GTK_WIDGET (self));
1124 }
1125 
1126 static void
on_month_cell_show_overflow_popover_cb(GcalMonthCell * cell,GtkWidget * button,GcalMonthView * self)1127 on_month_cell_show_overflow_popover_cb (GcalMonthCell *cell,
1128                                         GtkWidget     *button,
1129                                         GcalMonthView *self)
1130 {
1131   GcalMonthPopover *popover;
1132 
1133   popover = GCAL_MONTH_POPOVER (self->overflow_popover);
1134 
1135   cancel_selection (self);
1136 
1137   gcal_month_popover_set_relative_to (popover, GTK_WIDGET (cell));
1138   gcal_month_popover_set_date (popover, gcal_month_cell_get_date (cell));
1139   gcal_month_popover_popup (popover);
1140 }
1141 
1142 static void
on_month_popover_event_activated_cb(GcalMonthPopover * month_popover,GcalEventWidget * event_widget,GcalMonthView * self)1143 on_month_popover_event_activated_cb (GcalMonthPopover *month_popover,
1144                                      GcalEventWidget  *event_widget,
1145                                      GcalMonthView    *self)
1146 {
1147   activate_event (self, event_widget);
1148 }
1149 
1150 static void
on_weather_service_weather_changed_cb(GcalWeatherService * weather_service,GcalMonthView * self)1151 on_weather_service_weather_changed_cb (GcalWeatherService *weather_service,
1152                                        GcalMonthView      *self)
1153 {
1154   update_weather (self, TRUE);
1155 }
1156 
1157 
1158 /*
1159  * GcalView interface
1160  */
1161 
1162 static GDateTime*
gcal_month_view_get_date(GcalView * view)1163 gcal_month_view_get_date (GcalView *view)
1164 {
1165   GcalMonthView *self = GCAL_MONTH_VIEW (view);
1166 
1167   return self->date;
1168 }
1169 
1170 static void
gcal_month_view_set_date(GcalView * view,GDateTime * date)1171 gcal_month_view_set_date (GcalView  *view,
1172                           GDateTime *date)
1173 {
1174   g_autofree gchar *new_date_string = NULL;
1175   GcalMonthView *self;
1176 
1177   GCAL_ENTRY;
1178 
1179   self = GCAL_MONTH_VIEW (view);
1180 
1181   gcal_set_date_time (&self->date, date);
1182 
1183   self->days_delay = (time_day_of_week (1, g_date_time_get_month (self->date) - 1, g_date_time_get_year (self->date)) - self->first_weekday + 7) % 7;
1184   self->keyboard_cell = self->days_delay + (g_date_time_get_day_of_month (self->date) - 1);
1185 
1186   new_date_string = g_date_time_format (date, "%x %X %z");
1187   GCAL_TRACE_MSG ("new date: %s", new_date_string);
1188 
1189   update_header_labels (self);
1190   update_month_cells (self);
1191 
1192   gcal_timeline_subscriber_range_changed (GCAL_TIMELINE_SUBSCRIBER (view));
1193 
1194   GCAL_EXIT;
1195 }
1196 
1197 static void
gcal_month_view_clear_marks(GcalView * view)1198 gcal_month_view_clear_marks (GcalView *view)
1199 {
1200   cancel_selection (GCAL_MONTH_VIEW (view));
1201   update_month_cells (GCAL_MONTH_VIEW (view));
1202 
1203   gtk_widget_queue_allocate (GTK_WIDGET (view));
1204 }
1205 
1206 static GList*
gcal_month_view_get_children_by_uuid(GcalView * view,GcalRecurrenceModType mod,const gchar * uuid)1207 gcal_month_view_get_children_by_uuid (GcalView              *view,
1208                                       GcalRecurrenceModType  mod,
1209                                       const gchar           *uuid)
1210 {
1211   GHashTableIter iter;
1212   GcalMonthView *self;
1213   GList *children;
1214   GList *tmp;
1215 
1216   self = GCAL_MONTH_VIEW (view);
1217   children = NULL;
1218 
1219   g_hash_table_iter_init (&iter, self->children);
1220 
1221   while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &tmp))
1222     children = g_list_concat (children, g_list_copy (tmp));
1223 
1224   return filter_event_list_by_uid_and_modtype (children, mod, uuid);
1225 }
1226 
1227 static GDateTime*
gcal_month_view_get_next_date(GcalView * view)1228 gcal_month_view_get_next_date (GcalView *view)
1229 {
1230   GcalMonthView *self = GCAL_MONTH_VIEW (view);
1231 
1232   g_assert (self->date != NULL);
1233   return g_date_time_add_months (self->date, 1);
1234 }
1235 
1236 
1237 static GDateTime*
gcal_month_view_get_previous_date(GcalView * view)1238 gcal_month_view_get_previous_date (GcalView *view)
1239 {
1240   GcalMonthView *self = GCAL_MONTH_VIEW (view);
1241 
1242   g_assert (self->date != NULL);
1243   return g_date_time_add_months (self->date, -1);
1244 }
1245 
1246 static void
gcal_view_interface_init(GcalViewInterface * iface)1247 gcal_view_interface_init (GcalViewInterface *iface)
1248 {
1249   iface->get_date = gcal_month_view_get_date;
1250   iface->set_date = gcal_month_view_set_date;
1251   iface->clear_marks = gcal_month_view_clear_marks;
1252   iface->get_children_by_uuid = gcal_month_view_get_children_by_uuid;
1253   iface->get_next_date = gcal_month_view_get_next_date;
1254   iface->get_previous_date = gcal_month_view_get_previous_date;
1255 }
1256 
1257 
1258 /*
1259  * GtkBuildable interface
1260  */
1261 
1262 static void
gcal_month_view_add_child(GtkBuildable * buildable,GtkBuilder * builder,GObject * child,const gchar * type)1263 gcal_month_view_add_child (GtkBuildable *buildable,
1264                            GtkBuilder   *builder,
1265                            GObject      *child,
1266                            const gchar  *type)
1267 {
1268   GcalMonthView *self = GCAL_MONTH_VIEW (buildable);
1269 
1270   if (type && strcmp (type, "header") == 0)
1271     setup_header_widget  (self, GTK_WIDGET (child));
1272   else if (type && strcmp (type, "grid") == 0)
1273     setup_month_grid  (self, GTK_WIDGET (child));
1274   else
1275     GTK_BUILDER_WARN_INVALID_CHILD_TYPE (buildable, type);
1276 }
1277 
1278 static void
gtk_buildable_interface_init(GtkBuildableIface * iface)1279 gtk_buildable_interface_init (GtkBuildableIface *iface)
1280 {
1281   iface->add_child = gcal_month_view_add_child;
1282 }
1283 
1284 
1285 /*
1286  * GcalTimelineSubscriber iface
1287  */
1288 
1289 static GcalRange*
gcal_month_view_get_range(GcalTimelineSubscriber * subscriber)1290 gcal_month_view_get_range (GcalTimelineSubscriber *subscriber)
1291 {
1292   g_autoptr (GDateTime) month_start = NULL;
1293   g_autoptr (GDateTime) month_end = NULL;
1294   GcalMonthView *self;
1295 
1296   self = GCAL_MONTH_VIEW (subscriber);
1297   month_start = g_date_time_new_local (g_date_time_get_year (self->date),
1298                                        g_date_time_get_month (self->date),
1299                                        1, 0, 0, 0);
1300   month_end = g_date_time_add_months (month_start, 1);
1301 
1302   return gcal_range_new (month_start, month_end, GCAL_RANGE_DEFAULT);
1303 }
1304 
1305 static void
gcal_month_view_add_event(GcalTimelineSubscriber * subscriber,GcalEvent * event)1306 gcal_month_view_add_event (GcalTimelineSubscriber *subscriber,
1307                            GcalEvent              *event)
1308 {
1309   GcalMonthView *self;
1310   GcalCalendar *calendar;
1311   GtkWidget *event_widget;
1312 
1313   self = GCAL_MONTH_VIEW (subscriber);
1314   calendar = gcal_event_get_calendar (event);
1315 
1316   event_widget = gcal_event_widget_new (self->context, event);
1317   gcal_event_widget_set_read_only (GCAL_EVENT_WIDGET (event_widget), gcal_calendar_is_read_only (calendar));
1318 
1319   gtk_widget_show (event_widget);
1320   gtk_container_add (GTK_CONTAINER (subscriber), event_widget);
1321 
1322   self->pending_event_allocation = TRUE;
1323 }
1324 
1325 static void
gcal_month_view_update_event(GcalTimelineSubscriber * subscriber,GcalEvent * event)1326 gcal_month_view_update_event (GcalTimelineSubscriber *subscriber,
1327                               GcalEvent              *event)
1328 {
1329   GcalMonthView *self;
1330   GtkWidget *new_widget;
1331   GList *l;
1332 
1333   GCAL_ENTRY;
1334 
1335   self = GCAL_MONTH_VIEW (subscriber);
1336 
1337   l = g_hash_table_lookup (self->children, gcal_event_get_uid (event));
1338 
1339   if (!l)
1340     {
1341       g_warning ("%s: Widget with uuid: %s not found in view: %s",
1342                  G_STRFUNC,
1343                  gcal_event_get_uid (event),
1344                  gtk_widget_get_name (GTK_WIDGET (subscriber)));
1345       return;
1346     }
1347 
1348   /* Destroy the old event widget (split event widgets will be destroyed too) */
1349   gtk_widget_destroy (l->data);
1350 
1351   /* Create and add the new event widget */
1352   new_widget = gcal_event_widget_new (self->context, event);
1353   gtk_widget_show (new_widget);
1354   gtk_container_add (GTK_CONTAINER (subscriber), new_widget);
1355 
1356   self->pending_event_allocation = TRUE;
1357 
1358   GCAL_EXIT;
1359 }
1360 
1361 static void
gcal_month_view_remove_event(GcalTimelineSubscriber * subscriber,GcalEvent * event)1362 gcal_month_view_remove_event (GcalTimelineSubscriber *subscriber,
1363                               GcalEvent              *event)
1364 {
1365   GcalMonthView *self;
1366   const gchar *uuid;
1367   GList *l;
1368 
1369   GCAL_ENTRY;
1370 
1371   self = GCAL_MONTH_VIEW (subscriber);
1372   uuid = gcal_event_get_uid (event);
1373   l = g_hash_table_lookup (self->children, uuid);
1374 
1375   if (!l)
1376     {
1377       g_warning ("%s: Widget with uuid: %s not found in view: %s",
1378                  G_STRFUNC,
1379                  uuid,
1380                  gtk_widget_get_name (GTK_WIDGET (subscriber)));
1381       GCAL_RETURN ();
1382     }
1383 
1384   gtk_widget_destroy (l->data);
1385 
1386   self->pending_event_allocation = TRUE;
1387 
1388   GCAL_EXIT;
1389 }
1390 
1391 static void
gcal_timeline_subscriber_interface_init(GcalTimelineSubscriberInterface * iface)1392 gcal_timeline_subscriber_interface_init (GcalTimelineSubscriberInterface *iface)
1393 {
1394   iface->get_range = gcal_month_view_get_range;
1395   iface->add_event = gcal_month_view_add_event;
1396   iface->update_event = gcal_month_view_update_event;
1397   iface->remove_event = gcal_month_view_remove_event;
1398 }
1399 
1400 
1401 /*
1402  * GtkContainer overrides
1403  */
1404 
1405 static inline guint
get_child_cell(GcalMonthView * self,GcalEventWidget * child)1406 get_child_cell (GcalMonthView   *self,
1407                 GcalEventWidget *child)
1408 {
1409   GcalEvent *event;
1410   GDateTime *dt;
1411   gint cell;
1412 
1413   event = gcal_event_widget_get_event (child);
1414   dt = gcal_event_get_date_start (event);
1415 
1416   /* Don't adjust the date when the event is all day */
1417   if (gcal_event_get_all_day (event))
1418     {
1419       cell = g_date_time_get_day_of_month (dt);
1420     }
1421   else
1422     {
1423       dt = g_date_time_to_local (dt);
1424       cell = g_date_time_get_day_of_month (dt);
1425 
1426       g_clear_pointer (&dt, g_date_time_unref);
1427     }
1428 
1429   return cell;
1430 }
1431 
1432 static void
gcal_month_view_add(GtkContainer * container,GtkWidget * widget)1433 gcal_month_view_add (GtkContainer *container,
1434                      GtkWidget    *widget)
1435 {
1436   GcalMonthView *self;
1437   GcalEvent *event;
1438   const gchar *uuid;
1439   GList *l = NULL;
1440 
1441   g_return_if_fail (GCAL_IS_EVENT_WIDGET (widget));
1442   g_return_if_fail (gtk_widget_get_parent (widget) == NULL);
1443 
1444   self = GCAL_MONTH_VIEW (container);
1445   event = gcal_event_widget_get_event (GCAL_EVENT_WIDGET (widget));
1446   uuid = gcal_event_get_uid (event);
1447 
1448   /* inserting in all children hash */
1449   if (g_hash_table_lookup (self->children, uuid) != NULL)
1450     {
1451       g_warning ("Event with uuid: %s already added", uuid);
1452       gtk_widget_destroy (widget);
1453       return;
1454     }
1455 
1456   l = g_list_append (l, widget);
1457   g_hash_table_insert (self->children, g_strdup (uuid), l);
1458 
1459   if (gcal_event_is_multiday (event))
1460     {
1461       self->multi_cell_children = g_list_insert_sorted (self->multi_cell_children,
1462                                                         widget,
1463                                                         (GCompareFunc) gcal_event_widget_sort_events);
1464     }
1465   else
1466     {
1467       guint cell_idx;
1468 
1469       cell_idx = get_child_cell (self, GCAL_EVENT_WIDGET (widget));
1470 
1471       l = g_hash_table_lookup (self->single_cell_children, GINT_TO_POINTER (cell_idx));
1472       l = g_list_insert_sorted (l, widget, (GCompareFunc) gcal_event_widget_compare_by_start_date);
1473 
1474       if (g_list_length (l) != 1)
1475         g_hash_table_steal (self->single_cell_children, GINT_TO_POINTER (cell_idx));
1476 
1477       g_hash_table_insert (self->single_cell_children, GINT_TO_POINTER (cell_idx), l);
1478     }
1479 
1480   setup_child_widget (self, widget);
1481 }
1482 
1483 static void
gcal_month_view_remove(GtkContainer * container,GtkWidget * widget)1484 gcal_month_view_remove (GtkContainer *container,
1485                         GtkWidget    *widget)
1486 {
1487   GcalMonthView *self;
1488   GtkWidget *master_widget;
1489   GcalEvent *event;
1490   GList *l, *aux;
1491   const gchar *uuid;
1492 
1493   g_return_if_fail (gtk_widget_get_parent (widget) == GTK_WIDGET (container));
1494 
1495   if (!GCAL_IS_EVENT_WIDGET (widget))
1496     goto out;
1497 
1498   self = GCAL_MONTH_VIEW (container);
1499   event = gcal_event_widget_get_event (GCAL_EVENT_WIDGET (widget));
1500   uuid = gcal_event_get_uid (event);
1501 
1502   l = g_hash_table_lookup (self->children, uuid);
1503 
1504   if (l)
1505     {
1506       gtk_widget_unparent (widget);
1507 
1508       master_widget = (GtkWidget*) l->data;
1509       if (widget == master_widget)
1510         {
1511           if (g_list_find (self->multi_cell_children, widget) != NULL)
1512             {
1513               self->multi_cell_children = g_list_remove (self->multi_cell_children, widget);
1514 
1515               aux = g_list_next (l);
1516               if (aux != NULL)
1517                 {
1518                   l->next = NULL;
1519                   aux->prev = NULL;
1520                   g_list_foreach (aux, (GFunc) gtk_widget_unparent, NULL);
1521                   g_list_free (aux);
1522                 }
1523             }
1524           else
1525             {
1526               GHashTableIter iter;
1527               gpointer key, value;
1528 
1529               /*
1530                * When an event is changed, we can't rely on it's old date
1531                * to remove the corresponding widget. Because of that, we have
1532                * to iter through all the widgets to see which one matches
1533                */
1534               g_hash_table_iter_init (&iter, self->single_cell_children);
1535 
1536               while (g_hash_table_iter_next (&iter, &key, &value))
1537                 {
1538                   gboolean should_break;
1539 
1540                   should_break = FALSE;
1541 
1542                   for (aux = value; aux != NULL; aux = aux->next)
1543                     {
1544                       if (aux->data == widget)
1545                         {
1546                           aux = g_list_remove (g_list_copy (value), widget);
1547 
1548                           /*
1549                            * If we removed the event and there's no event left for
1550                            * the day, remove the key from the table. If there are
1551                            * events for that day, replace the list.
1552                            */
1553                           if (!aux)
1554                             g_hash_table_remove (self->single_cell_children, key);
1555                           else
1556                             g_hash_table_replace (self->single_cell_children, key, aux);
1557 
1558                           should_break = TRUE;
1559 
1560                           break;
1561                         }
1562                     }
1563 
1564                   if (should_break)
1565                     break;
1566                 }
1567             }
1568         }
1569 
1570       l = g_list_remove (g_list_copy (l), widget);
1571 
1572       if (!l)
1573         g_hash_table_remove (self->children, uuid);
1574       else
1575         g_hash_table_replace (self->children, g_strdup (uuid), l);
1576     }
1577 
1578 out:
1579   gtk_widget_queue_resize (GTK_WIDGET (container));
1580 }
1581 
1582 static void
gcal_month_view_forall(GtkContainer * container,gboolean include_internals,GtkCallback callback,gpointer callback_data)1583 gcal_month_view_forall (GtkContainer *container,
1584                         gboolean      include_internals,
1585                         GtkCallback   callback,
1586                         gpointer      callback_data)
1587 {
1588   GcalMonthView *self;
1589   GList *l, *l2, *aux = NULL;
1590 
1591   self = GCAL_MONTH_VIEW (container);
1592 
1593   /* Header */
1594   if (self->header)
1595     (*callback) (self->header, callback_data);
1596 
1597   /* Grid */
1598   if (self->grid)
1599     (*callback) (self->grid, callback_data);
1600 
1601   /* Event widgets */
1602   l2 = g_hash_table_get_values (self->children);
1603 
1604   for (l = l2; l != NULL; l = g_list_next (l))
1605     aux = g_list_concat (aux, g_list_reverse (g_list_copy (l->data)));
1606 
1607   g_list_free (l2);
1608 
1609   l = aux;
1610   while (aux)
1611     {
1612       GtkWidget *widget = (GtkWidget*) aux->data;
1613       aux = aux->next;
1614 
1615       (*callback) (widget, callback_data);
1616     }
1617 
1618   g_list_free (l);
1619 }
1620 
1621 
1622 /*
1623  * GtkWidget overrides
1624  */
1625 
1626 static void
gcal_month_view_realize(GtkWidget * widget)1627 gcal_month_view_realize (GtkWidget *widget)
1628 {
1629   GcalMonthView *self;
1630   GdkWindow *parent_window;
1631   GdkWindowAttr attributes;
1632   gint attributes_mask;
1633   GtkAllocation allocation;
1634 
1635   self = GCAL_MONTH_VIEW (widget);
1636   gtk_widget_set_realized (widget, TRUE);
1637 
1638   parent_window = gtk_widget_get_parent_window (widget);
1639   gtk_widget_set_window (widget, g_object_ref (parent_window));
1640 
1641   gtk_widget_get_allocation (widget, &allocation);
1642 
1643   attributes.window_type = GDK_WINDOW_CHILD;
1644   attributes.wclass = GDK_INPUT_ONLY;
1645   attributes.x = allocation.x;
1646   attributes.y = allocation.y;
1647   attributes.width = allocation.width;
1648   attributes.height = allocation.height;
1649   attributes.event_mask = gtk_widget_get_events (widget);
1650   attributes.event_mask |= (GDK_BUTTON_PRESS_MASK |
1651                             GDK_BUTTON_RELEASE_MASK |
1652                             GDK_BUTTON1_MOTION_MASK |
1653                             GDK_POINTER_MOTION_HINT_MASK |
1654                             GDK_POINTER_MOTION_MASK |
1655                             GDK_ENTER_NOTIFY_MASK |
1656                             GDK_LEAVE_NOTIFY_MASK |
1657                             GDK_SCROLL_MASK |
1658                             GDK_SMOOTH_SCROLL_MASK);
1659   attributes_mask = GDK_WA_X | GDK_WA_Y;
1660 
1661   self->event_window = gdk_window_new (parent_window,
1662                                        &attributes,
1663                                        attributes_mask);
1664   gtk_widget_register_window (widget, self->event_window);
1665 }
1666 
1667 static void
gcal_month_view_unrealize(GtkWidget * widget)1668 gcal_month_view_unrealize (GtkWidget *widget)
1669 {
1670   GcalMonthView *self = GCAL_MONTH_VIEW (widget);
1671 
1672   if (self->event_window)
1673     {
1674       gtk_widget_unregister_window (widget, self->event_window);
1675       gdk_window_destroy (self->event_window);
1676       self->event_window = NULL;
1677     }
1678 
1679   GTK_WIDGET_CLASS (gcal_month_view_parent_class)->unrealize (widget);
1680 }
1681 
1682 static void
gcal_month_view_map(GtkWidget * widget)1683 gcal_month_view_map (GtkWidget *widget)
1684 {
1685   GcalMonthView *self = GCAL_MONTH_VIEW (widget);
1686 
1687   if (self->event_window)
1688     gdk_window_show (self->event_window);
1689 
1690   GTK_WIDGET_CLASS (gcal_month_view_parent_class)->map (widget);
1691 }
1692 
1693 static void
gcal_month_view_unmap(GtkWidget * widget)1694 gcal_month_view_unmap (GtkWidget *widget)
1695 {
1696   GcalMonthView *self = GCAL_MONTH_VIEW (widget);
1697 
1698   if (self->event_window)
1699     gdk_window_hide (self->event_window);
1700 
1701   GTK_WIDGET_CLASS (gcal_month_view_parent_class)->unmap (widget);
1702 }
1703 
1704 static void
gcal_month_view_size_allocate(GtkWidget * widget,GtkAllocation * allocation)1705 gcal_month_view_size_allocate (GtkWidget     *widget,
1706                                GtkAllocation *allocation)
1707 {
1708   GtkAllocation child_allocation;
1709   GtkAllocation old_alloc;
1710   GcalMonthView *self;
1711   gint header_height;
1712   gint grid_height;
1713   gint i;
1714 
1715   GCAL_ENTRY;
1716 
1717   self = GCAL_MONTH_VIEW (widget);
1718 
1719   /* Allocate the widget */
1720   gtk_widget_get_allocation (widget, &old_alloc);
1721   gtk_widget_set_allocation (widget, allocation);
1722 
1723   if (gtk_widget_get_realized (widget))
1724     gdk_window_move_resize (self->event_window, allocation->x, allocation->y, allocation->width, allocation->height);
1725 
1726   /* Header */
1727   gtk_widget_get_preferred_height (self->header, &header_height, NULL);
1728 
1729   child_allocation.x = allocation->x;
1730   child_allocation.y = allocation->y;
1731   child_allocation.width = allocation->width;
1732   child_allocation.height = header_height;
1733 
1734   gtk_widget_size_allocate (self->header, &child_allocation);
1735 
1736   /* Grid */
1737   gtk_widget_get_preferred_height (self->grid, &grid_height, NULL);
1738 
1739   child_allocation.x = allocation->x;
1740   child_allocation.y = allocation->y + header_height;
1741   child_allocation.width = allocation->width;
1742   child_allocation.height = MAX (allocation->height - header_height, grid_height);
1743 
1744   gtk_widget_size_allocate (self->grid, &child_allocation);
1745 
1746   /*
1747    * At this point, the internal widgets (grid and header) already received the allocation they
1748    * asked for, and are happy. Now it comes the tricky part: when GTK is allocating sizes (here),
1749    * we cannot show or hide or add or remove widgets. Doing that can potentially trigger an infinite
1750    * allocation cycle, and this function will keep running forever nonstop, the CPU will melt, the
1751    * fans will scream and users will cry in anger.
1752    *
1753    * Thus, to avoid the infinite allocation cycle, we *only* update the event widgets when something
1754    * actually changed - either a new event widget was added (pending_event_allocation) or the size
1755    * of the Month view changed.
1756    *
1757    * The following code can be read as the pseudo-code:
1758    *
1759    *   if (something changed)
1760    *     recalculate and recreate event widgets;
1761    */
1762   if (self->pending_event_allocation ||
1763       allocation->width != old_alloc.width ||
1764       allocation->height != old_alloc.height)
1765     {
1766       gdouble vertical_cell_space [42];
1767       gdouble size_left [42];
1768       gint allocated_events_at_day [42] = { 0, };
1769       gint events_at_day [42] = { 0, };
1770 
1771       /* Remove every widget' parts, but the master widget */
1772       cleanup_overflow_information (self);
1773 
1774       /* Event widgets */
1775       count_events_per_day (self, events_at_day);
1776 
1777       for (i = 0; i < 42; i++)
1778         {
1779           gint h = gcal_month_cell_get_content_space (GCAL_MONTH_CELL (self->month_cell[i / 7][i % 7]));
1780 
1781           vertical_cell_space[i] = h;
1782           size_left[i] = h;
1783         }
1784 
1785       /* Allocate multidays events before single day events, as they have a higher priority */
1786       allocate_multiday_events (self, vertical_cell_space, size_left, events_at_day, allocated_events_at_day);
1787       allocate_single_day_events (self, vertical_cell_space, size_left, events_at_day, allocated_events_at_day);
1788     }
1789 
1790   queue_update_month_cells (self);
1791 
1792   self->pending_event_allocation = FALSE;
1793 
1794   GCAL_EXIT;
1795 }
1796 
1797 static gboolean
gcal_month_view_button_press(GtkWidget * widget,GdkEventButton * event)1798 gcal_month_view_button_press (GtkWidget      *widget,
1799                               GdkEventButton *event)
1800 {
1801   GcalMonthView *self;
1802   gdouble x, y;
1803   gint days, clicked_cell;
1804 
1805   GCAL_ENTRY;
1806 
1807   self = GCAL_MONTH_VIEW (widget);
1808   days = self->days_delay + gcal_date_time_get_days_in_month (self->date);
1809 
1810   /* The event may have come from a child widget, so make it relative to the month view */
1811   if (!gcal_translate_child_window_position (widget, event->window, event->x, event->y, &x, &y))
1812     return GDK_EVENT_PROPAGATE;
1813 
1814   get_month_cell_at_position (self, x, y, &clicked_cell);
1815 
1816   if (clicked_cell >= self->days_delay && clicked_cell < days)
1817     {
1818       g_clear_pointer (&self->start_mark_cell, g_date_time_unref);
1819 
1820       self->keyboard_cell = clicked_cell;
1821       self->start_mark_cell = g_date_time_new_local (g_date_time_get_year (self->date),
1822                                                      g_date_time_get_month (self->date),
1823                                                      self->keyboard_cell - self->days_delay + 1,
1824                                                      0, 0, 0);
1825 
1826       update_month_cells (self);
1827     }
1828 
1829   GCAL_RETURN (GDK_EVENT_PROPAGATE);
1830 }
1831 
1832 static gboolean
gcal_month_view_motion_notify_event(GtkWidget * widget,GdkEventMotion * event)1833 gcal_month_view_motion_notify_event (GtkWidget      *widget,
1834                                      GdkEventMotion *event)
1835 {
1836   GcalMonthView *self;
1837   gdouble x, y;
1838   gint days;
1839   gint new_end_cell;
1840 
1841   GCAL_ENTRY;
1842 
1843   self = GCAL_MONTH_VIEW (widget);
1844   days = self->days_delay + gcal_date_time_get_days_in_month (self->date);
1845 
1846   if (!gcal_translate_child_window_position (widget, event->window, event->x, event->y, &x, &y))
1847     GCAL_RETURN (GDK_EVENT_PROPAGATE);
1848 
1849   get_month_cell_at_position (self, x, y, &new_end_cell);
1850 
1851   if (self->start_mark_cell)
1852     {
1853       if (!(event->state & GDK_BUTTON_PRESS_MASK))
1854         GCAL_RETURN (GDK_EVENT_STOP);
1855 
1856       if (new_end_cell < self->days_delay || new_end_cell >= days)
1857         GCAL_RETURN (GDK_EVENT_PROPAGATE);
1858 
1859       /* Let the keyboard focus track the pointer */
1860       self->keyboard_cell = new_end_cell;
1861 
1862       g_clear_pointer (&self->end_mark_cell, g_date_time_unref);
1863       self->end_mark_cell = g_date_time_new_local (g_date_time_get_year (self->date),
1864                                                    g_date_time_get_month (self->date),
1865                                                    new_end_cell - self->days_delay + 1,
1866                                                    0, 0, 0);
1867 
1868       update_month_cells (self);
1869 
1870       GCAL_RETURN (GDK_EVENT_STOP);
1871     }
1872   else
1873     {
1874       if (gtk_widget_is_visible (GTK_WIDGET (self->overflow_popover)))
1875         GCAL_RETURN (GDK_EVENT_PROPAGATE);
1876     }
1877 
1878   GCAL_RETURN (GDK_EVENT_PROPAGATE);
1879 }
1880 
1881 static gboolean
gcal_month_view_button_release(GtkWidget * widget,GdkEventButton * event)1882 gcal_month_view_button_release (GtkWidget      *widget,
1883                                 GdkEventButton *event)
1884 {
1885   GcalMonthView *self;
1886   gdouble x, y;
1887   gint days, current_day;
1888 
1889   GCAL_ENTRY;
1890 
1891   self = GCAL_MONTH_VIEW (widget);
1892   days = self->days_delay + gcal_date_time_get_days_in_month (self->date);
1893 
1894   if (!gcal_translate_child_window_position (widget, event->window, event->x, event->y, &x, &y))
1895     GCAL_RETURN (GDK_EVENT_PROPAGATE);
1896 
1897   get_month_cell_at_position (self, x, y, &current_day);
1898 
1899   if (current_day >= self->days_delay && current_day < days)
1900     {
1901       g_autoptr (GDateTime) new_active_date = NULL;
1902       gboolean valid;
1903 
1904       self->keyboard_cell = current_day;
1905       new_active_date = g_date_time_new_local (g_date_time_get_year (self->date),
1906                                                g_date_time_get_month (self->date),
1907                                                current_day - self->days_delay + 1,
1908                                                0, 0, 0);
1909 
1910       gcal_set_date_time (&self->end_mark_cell, new_active_date);
1911       gcal_set_date_time (&self->date, new_active_date);
1912 
1913       /* First, make sure to show the popover */
1914       valid = emit_create_event (self);
1915 
1916       update_month_cells (self);
1917 
1918       /* Then update the active date */
1919       g_object_notify (G_OBJECT (self), "active-date");
1920 
1921       GCAL_RETURN (valid);
1922     }
1923   else
1924     {
1925       /* If the button is released over an invalid cell, entirely cancel the selection */
1926       cancel_selection (GCAL_MONTH_VIEW (widget));
1927 
1928       gtk_widget_queue_resize (widget);
1929 
1930       GCAL_RETURN (GDK_EVENT_PROPAGATE);
1931     }
1932 }
1933 
1934 static void
gcal_month_view_direction_changed(GtkWidget * widget,GtkTextDirection previous_direction)1935 gcal_month_view_direction_changed (GtkWidget        *widget,
1936                                    GtkTextDirection  previous_direction)
1937 {
1938   GcalMonthView *self = GCAL_MONTH_VIEW (widget);
1939 
1940   self->pending_event_allocation = TRUE;
1941 
1942   gtk_widget_queue_resize (widget);
1943 }
1944 
1945 static gboolean
gcal_month_view_key_press(GtkWidget * widget,GdkEventKey * event)1946 gcal_month_view_key_press (GtkWidget   *widget,
1947                            GdkEventKey *event)
1948 {
1949   GcalMonthView *self;
1950   gboolean create_event;
1951   gboolean selection;
1952   gboolean valid_key;
1953   gboolean is_ltr;
1954   gint days_in_month;
1955   gint min, max, diff, month_change, current_day;
1956   gint row, col;
1957 
1958   g_return_val_if_fail (GCAL_IS_MONTH_VIEW (widget), FALSE);
1959 
1960   self = GCAL_MONTH_VIEW (widget);
1961   selection = event->state & GDK_SHIFT_MASK;
1962   create_event = FALSE;
1963   valid_key = FALSE;
1964   diff = 0;
1965   month_change = 0;
1966   days_in_month = gcal_date_time_get_days_in_month (self->date);
1967   current_day = self->keyboard_cell - self->days_delay + 1;
1968   min = self->days_delay;
1969   max = self->days_delay + days_in_month - 1;
1970   is_ltr = gtk_widget_get_direction (widget) != GTK_TEXT_DIR_RTL;
1971 
1972   /*
1973    * If it's starting the selection right now, it should mark the current keyboard
1974    * focused cell as the start, and then update the end mark after updating the
1975    * focused cell.
1976    */
1977   if (selection && self->start_mark_cell == NULL)
1978     {
1979       self->start_mark_cell = g_date_time_new_local (g_date_time_get_year (self->date),
1980                                                      g_date_time_get_month (self->date),
1981                                                      current_day,
1982                                                      0, 0, 0);
1983     }
1984 
1985   switch (event->keyval)
1986     {
1987     case GDK_KEY_Up:
1988       valid_key = TRUE;
1989       diff = -7;
1990       break;
1991 
1992     case GDK_KEY_Down:
1993       valid_key = TRUE;
1994       diff = 7;
1995       break;
1996 
1997     case GDK_KEY_Left:
1998       valid_key = TRUE;
1999       diff = is_ltr ? -1 : 1;
2000       break;
2001 
2002     case GDK_KEY_Right:
2003       valid_key = TRUE;
2004       diff = is_ltr ? 1 : -1;
2005       break;
2006 
2007     case GDK_KEY_Return:
2008       /*
2009        * If it's not on the selection mode (i.e. shift is not pressed), we should
2010        * simulate it by changing the start & end selected cells = keyboard cell.
2011        */
2012       if (!selection && !self->start_mark_cell && !self->end_mark_cell)
2013         {
2014           g_autoptr (GDateTime) new_mark = NULL;
2015 
2016           new_mark = g_date_time_new_local (g_date_time_get_year (self->date),
2017                                             g_date_time_get_month (self->date),
2018                                             current_day,
2019                                             0, 0, 0);
2020           self->start_mark_cell = g_object_ref (new_mark);
2021           self->end_mark_cell = g_object_ref (new_mark);
2022         }
2023 
2024       create_event = TRUE;
2025       break;
2026 
2027     case GDK_KEY_Escape:
2028       cancel_selection (GCAL_MONTH_VIEW (widget));
2029       break;
2030 
2031     default:
2032       return GDK_EVENT_PROPAGATE;
2033     }
2034 
2035   if (self->keyboard_cell + diff <= max && self->keyboard_cell + diff >= min)
2036     {
2037       self->keyboard_cell += diff;
2038     }
2039   else
2040     {
2041       g_autoptr (GDateTime) new_month = NULL;
2042 
2043       month_change = self->keyboard_cell + diff > max ? 1 : -1;
2044       new_month = g_date_time_add_months (self->date, month_change);
2045 
2046       self->days_delay = (time_day_of_week (1, g_date_time_get_month (new_month) - 1, g_date_time_get_year (new_month)) - self->first_weekday + 7) % 7;
2047 
2048       /*
2049        * Set keyboard cell value to the sum or difference of days delay of successive
2050        * month or last day of preceeding month and overload value depending on
2051        * month_change. Overload value is the equal to the deviation of the value
2052        * of keboard_cell from the min or max value of the current month depending
2053        * on the overload point.
2054        */
2055       if (month_change == 1)
2056         self->keyboard_cell = self->days_delay + self->keyboard_cell + diff - max - 1;
2057       else
2058         self->keyboard_cell = self->days_delay + gcal_date_time_get_days_in_month (new_month) - min + self->keyboard_cell + diff;
2059     }
2060 
2061   /* Focus the selected month cell */
2062   row = self->keyboard_cell / 7;
2063   col = self->keyboard_cell % 7;
2064 
2065   gtk_widget_grab_focus (self->month_cell[row][col]);
2066 
2067   current_day = self->keyboard_cell - self->days_delay + 1;
2068   //self->date->day = current_day;
2069 
2070   /*
2071    * We can only emit the :create-event signal ~after~ grabbing the focus, otherwise
2072    * the popover is instantly hidden.
2073    */
2074   if (create_event)
2075     emit_create_event (self);
2076 
2077   g_object_notify (G_OBJECT (widget), "active-date");
2078 
2079   if (selection)
2080     {
2081       self->end_mark_cell = g_date_time_new_local (g_date_time_get_year (self->date),
2082                                                    g_date_time_get_month (self->date),
2083                                                    current_day,
2084                                                    0, 0, 0);
2085     }
2086   else if (!selection && valid_key)
2087     {
2088       /* Cancel selection if SHIFT is not pressed */
2089       cancel_selection (GCAL_MONTH_VIEW (widget));
2090     }
2091 
2092   return GDK_EVENT_STOP;
2093 }
2094 
2095 static gboolean
gcal_month_view_scroll_event(GtkWidget * widget,GdkEventScroll * scroll_event)2096 gcal_month_view_scroll_event (GtkWidget      *widget,
2097                               GdkEventScroll *scroll_event)
2098 {
2099   GcalMonthView *self = GCAL_MONTH_VIEW (widget);
2100 
2101   /*
2102    * If we accumulated enough scrolling, change the month. Otherwise, we'd scroll
2103    * waaay too fast.
2104    */
2105   if (should_change_date_for_scroll (&self->scroll_value, scroll_event))
2106     {
2107       g_autoptr (GDateTime) new_date = NULL;
2108       gint diff;
2109 
2110       diff = self->scroll_value > 0 ? 1 : -1;
2111       new_date = g_date_time_add_months (self->date, diff);
2112 
2113       gcal_clear_date_time (&self->date);
2114       self->date = g_steal_pointer (&new_date);
2115       self->scroll_value = 0;
2116 
2117       gtk_widget_queue_draw (widget);
2118 
2119       g_object_notify (G_OBJECT (widget), "active-date");
2120     }
2121 
2122   return GDK_EVENT_STOP;
2123 }
2124 
2125 
2126 /*
2127  * GObject overrides
2128  */
2129 
2130 static void
gcal_month_view_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)2131 gcal_month_view_set_property (GObject       *object,
2132                               guint          property_id,
2133                               const GValue  *value,
2134                               GParamSpec    *pspec)
2135 {
2136   GcalMonthView *self = (GcalMonthView *) object;
2137   gint i;
2138 
2139   switch (property_id)
2140     {
2141     case PROP_DATE:
2142       gcal_view_set_date (GCAL_VIEW (object), g_value_get_boxed (value));
2143       break;
2144 
2145     case PROP_CONTEXT:
2146       g_assert (self->context == NULL);
2147       self->context = g_value_dup_object (value);
2148 
2149       for (i = 0; i < 42; i++)
2150         gcal_month_cell_set_context (GCAL_MONTH_CELL (self->month_cell[i / 7][i % 7]), self->context);
2151 
2152       g_signal_connect_object (gcal_context_get_clock (self->context),
2153                                "day-changed",
2154                                G_CALLBACK (update_month_cells),
2155                                self,
2156                                G_CONNECT_SWAPPED);
2157 
2158       g_signal_connect_object (gcal_context_get_weather_service (self->context),
2159                                "weather-changed",
2160                                G_CALLBACK (on_weather_service_weather_changed_cb),
2161                                self,
2162                                0);
2163       update_weather (self, TRUE);
2164       g_object_notify (object, "context");
2165       break;
2166 
2167     default:
2168       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
2169       break;
2170     }
2171 }
2172 
2173 static void
gcal_month_view_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)2174 gcal_month_view_get_property (GObject       *object,
2175                               guint          property_id,
2176                               GValue        *value,
2177                               GParamSpec    *pspec)
2178 {
2179   GcalMonthView *self = GCAL_MONTH_VIEW (object);
2180 
2181   switch (property_id)
2182     {
2183     case PROP_DATE:
2184       g_value_set_boxed (value, self->date);
2185       break;
2186 
2187     case PROP_CONTEXT:
2188       g_value_set_object (value, self->context);
2189       break;
2190 
2191     default:
2192       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
2193       break;
2194     }
2195 }
2196 
2197 static void
gcal_month_view_finalize(GObject * object)2198 gcal_month_view_finalize (GObject *object)
2199 {
2200   GcalMonthView *self = GCAL_MONTH_VIEW (object);
2201 
2202   gcal_clear_date_time (&self->date);
2203   g_clear_pointer (&self->children, g_hash_table_destroy);
2204   g_clear_pointer (&self->single_cell_children, g_hash_table_destroy);
2205   g_clear_pointer (&self->overflow_cells, g_hash_table_destroy);
2206   g_clear_pointer (&self->multi_cell_children, g_list_free);
2207 
2208   g_clear_object (&self->context);
2209 
2210   if (self->update_grid_id > 0)
2211     {
2212       g_source_remove (self->update_grid_id);
2213       self->update_grid_id = 0;
2214     }
2215 
2216   /* Chain up to parent's finalize() method. */
2217   G_OBJECT_CLASS (gcal_month_view_parent_class)->finalize (object);
2218 }
2219 
2220 static void
gcal_month_view_class_init(GcalMonthViewClass * klass)2221 gcal_month_view_class_init (GcalMonthViewClass *klass)
2222 {
2223   GObjectClass *object_class;
2224   GtkWidgetClass *widget_class;
2225   GtkContainerClass *container_class;
2226 
2227   object_class = G_OBJECT_CLASS (klass);
2228   object_class->set_property = gcal_month_view_set_property;
2229   object_class->get_property = gcal_month_view_get_property;
2230   object_class->finalize = gcal_month_view_finalize;
2231 
2232   widget_class = GTK_WIDGET_CLASS (klass);
2233   widget_class->realize = gcal_month_view_realize;
2234   widget_class->unrealize = gcal_month_view_unrealize;
2235   widget_class->map = gcal_month_view_map;
2236   widget_class->unmap = gcal_month_view_unmap;
2237   widget_class->size_allocate = gcal_month_view_size_allocate;
2238   widget_class->button_press_event = gcal_month_view_button_press;
2239   widget_class->motion_notify_event = gcal_month_view_motion_notify_event;
2240   widget_class->button_release_event = gcal_month_view_button_release;
2241   widget_class->direction_changed = gcal_month_view_direction_changed;
2242   widget_class->key_press_event = gcal_month_view_key_press;
2243   widget_class->scroll_event = gcal_month_view_scroll_event;
2244 
2245   container_class = GTK_CONTAINER_CLASS (klass);
2246   container_class->add = gcal_month_view_add;
2247   container_class->remove = gcal_month_view_remove;
2248   container_class->forall = gcal_month_view_forall;
2249 
2250   g_object_class_override_property (object_class, PROP_DATE, "active-date");
2251   g_object_class_override_property (object_class, PROP_CONTEXT, "context");
2252 
2253   gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/calendar/ui/views/gcal-month-view.ui");
2254 
2255   gtk_widget_class_bind_template_child (widget_class, GcalMonthView, label_0);
2256   gtk_widget_class_bind_template_child (widget_class, GcalMonthView, label_1);
2257   gtk_widget_class_bind_template_child (widget_class, GcalMonthView, label_2);
2258   gtk_widget_class_bind_template_child (widget_class, GcalMonthView, label_3);
2259   gtk_widget_class_bind_template_child (widget_class, GcalMonthView, label_4);
2260   gtk_widget_class_bind_template_child (widget_class, GcalMonthView, label_5);
2261   gtk_widget_class_bind_template_child (widget_class, GcalMonthView, label_6);
2262   gtk_widget_class_bind_template_child (widget_class, GcalMonthView, month_label);
2263   gtk_widget_class_bind_template_child (widget_class, GcalMonthView, year_label);
2264 
2265   gtk_widget_class_bind_template_callback (widget_class, add_new_event_button_cb);
2266 
2267   gtk_widget_class_set_css_name (widget_class, "calendar-view");
2268 
2269   g_type_ensure (GCAL_TYPE_MONTH_POPOVER);
2270 }
2271 
2272 static void
gcal_month_view_init(GcalMonthView * self)2273 gcal_month_view_init (GcalMonthView *self)
2274 {
2275   gtk_widget_init_template (GTK_WIDGET (self));
2276 
2277   gtk_widget_set_has_window (GTK_WIDGET (self), FALSE);
2278 
2279   self->children = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_list_free);
2280   self->single_cell_children = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) g_list_free);
2281   self->overflow_cells = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) g_list_free);
2282   self->pending_event_allocation = FALSE;
2283 
2284   /* First weekday */
2285   self->first_weekday = get_first_weekday ();
2286 
2287   /* Weekday header labels */
2288   self->weekday_label[0] = self->label_0;
2289   self->weekday_label[1] = self->label_1;
2290   self->weekday_label[2] = self->label_2;
2291   self->weekday_label[3] = self->label_3;
2292   self->weekday_label[4] = self->label_4;
2293   self->weekday_label[5] = self->label_5;
2294   self->weekday_label[6] = self->label_6;
2295 
2296   update_weekday_labels (self);
2297 
2298   /* Overflow popover */
2299   self->overflow_popover = (GcalMonthPopover*) gcal_month_popover_new ();
2300 
2301   g_object_bind_property (self,
2302                           "context",
2303                           self->overflow_popover,
2304                           "context",
2305                           G_BINDING_DEFAULT);
2306 
2307   g_signal_connect_object (self->overflow_popover, "event-activated", G_CALLBACK (on_month_popover_event_activated_cb), self, 0);
2308 }
2309 
2310