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, ¤t_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