1 /*
2 *
3 * Evolution calendar - Utilities for tagging ECalendar widgets
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU Lesser General Public License as published by
7 * the Free Software Foundation.
8 *
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this program; if not, see <http://www.gnu.org/licenses/>.
16 *
17 *
18 * Authors:
19 * Damon Chaplin <damon@ximian.com>
20 * Federico Mena-Quintero <federico@ximian.com>
21 *
22 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
23 *
24 */
25
26 #include "evolution-config.h"
27
28 #include "shell/e-shell.h"
29 #include "calendar-config.h"
30 #include "comp-util.h"
31 #include "e-cal-data-model-subscriber.h"
32 #include "tag-calendar.h"
33
34 struct _ETagCalendarPrivate
35 {
36 ECalendar *calendar; /* weak-referenced */
37 ECalendarItem *calitem; /* weak-referenced */
38 ECalDataModel *data_model; /* not referenced, due to circular dependency */
39 gboolean recur_events_italic;
40
41 GHashTable *objects; /* ObjectInfo ~> 1 (unused) */
42 GHashTable *dates; /* julian date ~> DateInfo */
43
44 guint32 range_start_julian;
45 guint32 range_end_julian;
46 };
47
48 enum {
49 PROP_0,
50 PROP_CALENDAR,
51 PROP_RECUR_EVENTS_ITALIC
52 };
53
54 static void e_tag_calendar_cal_data_model_subscriber_init (ECalDataModelSubscriberInterface *iface);
55
56 G_DEFINE_TYPE_WITH_CODE (ETagCalendar, e_tag_calendar, G_TYPE_OBJECT,
57 G_IMPLEMENT_INTERFACE (E_TYPE_CAL_DATA_MODEL_SUBSCRIBER, e_tag_calendar_cal_data_model_subscriber_init))
58
59 typedef struct {
60 gconstpointer client;
61 ECalComponentId *id;
62 gboolean is_transparent; /* neither of the two means is_single */
63 gboolean is_recurring;
64 guint32 start_julian;
65 guint32 end_julian;
66 } ObjectInfo;
67
68 typedef struct {
69 guint n_transparent;
70 guint n_recurring;
71 guint n_single;
72 } DateInfo;
73
74 static guint
object_info_hash(gconstpointer v)75 object_info_hash (gconstpointer v)
76 {
77 const ObjectInfo *oinfo = v;
78
79 if (!v)
80 return 0;
81
82 return g_direct_hash (oinfo->client) ^ e_cal_component_id_hash (oinfo->id);
83 }
84
85 /* component-related equality, for hash tables */
86 static gboolean
object_info_equal(gconstpointer v1,gconstpointer v2)87 object_info_equal (gconstpointer v1,
88 gconstpointer v2)
89 {
90 const ObjectInfo *oinfo1 = v1;
91 const ObjectInfo *oinfo2 = v2;
92
93 if (oinfo1 == oinfo2)
94 return TRUE;
95
96 if (!oinfo1 || !oinfo2)
97 return FALSE;
98
99 return oinfo1->client == oinfo2->client &&
100 e_cal_component_id_equal (oinfo1->id, oinfo2->id);
101 }
102
103 /* date-related equality, for drawing changes */
104 static gboolean
object_info_data_equal(ObjectInfo * o1,ObjectInfo * o2)105 object_info_data_equal (ObjectInfo *o1,
106 ObjectInfo *o2)
107 {
108 if (o1 == o2)
109 return TRUE;
110
111 if (!o1 || !o2)
112 return FALSE;
113
114 return (o1->is_transparent ? 1: 0) == (o2->is_transparent ? 1 : 0) &&
115 (o1->start_julian ? 1: 0) == (o2->is_recurring ? 1 : 0) &&
116 (o1->start_julian == o2->start_julian) &&
117 (o1->end_julian == o2->end_julian);
118 }
119
120 static ObjectInfo *
object_info_new(ECalClient * client,ECalComponentId * id,gboolean is_transparent,gboolean is_recurring,guint32 start_julian,guint32 end_julian)121 object_info_new (ECalClient *client,
122 ECalComponentId *id, /* will be consumed */
123 gboolean is_transparent,
124 gboolean is_recurring,
125 guint32 start_julian,
126 guint32 end_julian)
127 {
128 ObjectInfo *oinfo;
129
130 g_return_val_if_fail (client != NULL, NULL);
131 g_return_val_if_fail (id != NULL, NULL);
132
133 oinfo = g_slice_new0 (ObjectInfo);
134 oinfo->client = client;
135 oinfo->id = id;
136 oinfo->is_transparent = is_transparent;
137 oinfo->is_recurring = is_recurring;
138 oinfo->start_julian = start_julian;
139 oinfo->end_julian = end_julian;
140
141 return oinfo;
142 }
143
144 static void
object_info_free(gpointer ptr)145 object_info_free (gpointer ptr)
146 {
147 ObjectInfo *oinfo = ptr;
148
149 if (oinfo) {
150 e_cal_component_id_free (oinfo->id);
151 g_slice_free (ObjectInfo, oinfo);
152 }
153 }
154
155 static DateInfo *
date_info_new(void)156 date_info_new (void)
157 {
158 return g_slice_new0 (DateInfo);
159 }
160
161 static void
date_info_free(gpointer ptr)162 date_info_free (gpointer ptr)
163 {
164 DateInfo *dinfo = ptr;
165
166 if (dinfo)
167 g_slice_free (DateInfo, dinfo);
168 }
169
170 static gboolean
date_info_update(DateInfo * dinfo,ObjectInfo * oinfo,gboolean inc)171 date_info_update (DateInfo *dinfo,
172 ObjectInfo *oinfo,
173 gboolean inc)
174 {
175 gint nn = inc ? +1 : -1;
176 gboolean ui_changed = FALSE;
177
178 g_return_val_if_fail (dinfo != NULL, FALSE);
179 g_return_val_if_fail (oinfo != NULL, FALSE);
180
181 if (oinfo->is_transparent) {
182 dinfo->n_transparent += nn;
183 ui_changed = ui_changed || (inc && dinfo->n_transparent == 1) || (!inc && dinfo->n_transparent == 0);
184 } else if (oinfo->is_recurring) {
185 dinfo->n_recurring += nn;
186 ui_changed = ui_changed || (inc && dinfo->n_recurring == 1) || (!inc && dinfo->n_recurring == 0);
187 } else {
188 dinfo->n_single += nn;
189 ui_changed = ui_changed || (inc && dinfo->n_single == 1) || (!inc && dinfo->n_single == 0);
190 }
191
192 return ui_changed;
193 }
194
195 static guint8
date_info_get_style(DateInfo * dinfo,gboolean recur_events_italic)196 date_info_get_style (DateInfo *dinfo,
197 gboolean recur_events_italic)
198 {
199 guint8 style = 0;
200
201 g_return_val_if_fail (dinfo != NULL, 0);
202
203 if (dinfo->n_transparent > 0 ||
204 (recur_events_italic && dinfo->n_recurring > 0))
205 style |= E_CALENDAR_ITEM_MARK_ITALIC;
206
207 if (dinfo->n_single > 0 ||
208 (!recur_events_italic && dinfo->n_recurring > 0))
209 style |= E_CALENDAR_ITEM_MARK_BOLD;
210
211 return style;
212 }
213
214 static gint32
encode_ymd_to_julian(gint year,gint month,gint day)215 encode_ymd_to_julian (gint year,
216 gint month,
217 gint day)
218 {
219 GDate dt;
220
221 g_date_clear (&dt, 1);
222 g_date_set_dmy (&dt, day, month, year);
223
224 return g_date_get_julian (&dt);
225 }
226
227 static guint32
encode_timet_to_julian(time_t t,gboolean is_date,const ICalTimezone * zone)228 encode_timet_to_julian (time_t t,
229 gboolean is_date,
230 const ICalTimezone *zone)
231 {
232 ICalTime *tt;
233 guint32 res;
234
235 if (!t)
236 return 0;
237
238 tt = i_cal_time_new_from_timet_with_zone (t, is_date, (ICalTimezone *) zone);
239
240 if (!tt || !i_cal_time_is_valid_time (tt) || i_cal_time_is_null_time (tt)) {
241 g_clear_object (&tt);
242 return 0;
243 }
244
245 res = encode_ymd_to_julian (i_cal_time_get_year (tt),
246 i_cal_time_get_month (tt),
247 i_cal_time_get_day (tt));
248
249 g_clear_object (&tt);
250
251 return res;
252 }
253
254 static void
decode_julian(guint32 julian,gint * year,gint * month,gint * day)255 decode_julian (guint32 julian,
256 gint *year,
257 gint *month,
258 gint *day)
259 {
260 GDate dt;
261
262 g_date_clear (&dt, 1);
263 g_date_set_julian (&dt, julian);
264
265 *year = g_date_get_year (&dt);
266 *month = g_date_get_month (&dt);
267 *day = g_date_get_day (&dt);
268 }
269
270 static void
tag_calendar_date_cb(gpointer key,gpointer value,gpointer user_data)271 tag_calendar_date_cb (gpointer key,
272 gpointer value,
273 gpointer user_data)
274 {
275 ETagCalendar *tag_calendar = user_data;
276 DateInfo *dinfo = value;
277 gint year, month, day;
278
279 decode_julian (GPOINTER_TO_UINT (key), &year, &month, &day);
280
281 e_calendar_item_mark_day (tag_calendar->priv->calitem, year, month - 1, day,
282 date_info_get_style (dinfo, tag_calendar->priv->recur_events_italic), FALSE);
283 }
284
285 static void
e_tag_calendar_remark_days(ETagCalendar * tag_calendar)286 e_tag_calendar_remark_days (ETagCalendar *tag_calendar)
287 {
288 g_return_if_fail (E_IS_TAG_CALENDAR (tag_calendar));
289 g_return_if_fail (tag_calendar->priv->calitem != NULL);
290
291 e_calendar_item_clear_marks (tag_calendar->priv->calitem);
292
293 g_hash_table_foreach (tag_calendar->priv->dates, tag_calendar_date_cb, tag_calendar);
294 }
295
296 static time_t
e_tag_calendar_date_to_timet(gint year,gint month,gint day,const ICalTimezone * with_zone)297 e_tag_calendar_date_to_timet (gint year,
298 gint month,
299 gint day,
300 const ICalTimezone *with_zone)
301 {
302 GDate *date;
303 time_t tt;
304
305 date = g_date_new_dmy (day, month, year);
306 g_return_val_if_fail (date != NULL, (time_t) -1);
307
308 tt = cal_comp_gdate_to_timet (date, with_zone);
309
310 g_date_free (date);
311
312 return tt;
313 }
314
315 static void
e_tag_calendar_date_range_changed_cb(ETagCalendar * tag_calendar)316 e_tag_calendar_date_range_changed_cb (ETagCalendar *tag_calendar)
317 {
318 gint start_year, start_month, start_day, end_year, end_month, end_day;
319 time_t range_start, range_end;
320
321 g_return_if_fail (E_IS_TAG_CALENDAR (tag_calendar));
322
323 if (!tag_calendar->priv->data_model ||
324 !tag_calendar->priv->calitem)
325 return;
326
327 g_return_if_fail (E_IS_CALENDAR_ITEM (tag_calendar->priv->calitem));
328
329 /* This can fail on start, when the ECalendarItem wasn't updated (drawn) yet */
330 if (!e_calendar_item_get_date_range (tag_calendar->priv->calitem,
331 &start_year, &start_month, &start_day, &end_year, &end_month, &end_day))
332 return;
333
334 start_month++;
335 end_month++;
336
337 range_start = e_tag_calendar_date_to_timet (start_year, start_month, start_day, NULL);
338 range_end = e_tag_calendar_date_to_timet (end_year, end_month, end_day, NULL);
339
340 tag_calendar->priv->range_start_julian = encode_ymd_to_julian (start_year, start_month, start_day);
341 tag_calendar->priv->range_end_julian = encode_ymd_to_julian (end_year, end_month, end_day);
342
343 /* Range change causes removal of marks in the calendar */
344 e_tag_calendar_remark_days (tag_calendar);
345
346 e_cal_data_model_subscribe (tag_calendar->priv->data_model,
347 E_CAL_DATA_MODEL_SUBSCRIBER (tag_calendar),
348 range_start, range_end);
349 }
350
351 static gboolean
e_tag_calendar_query_tooltip_cb(ECalendar * calendar,gint x,gint y,gboolean keayboard_mode,GtkTooltip * tooltip,ETagCalendar * tag_calendar)352 e_tag_calendar_query_tooltip_cb (ECalendar *calendar,
353 gint x,
354 gint y,
355 gboolean keayboard_mode,
356 GtkTooltip *tooltip,
357 ETagCalendar *tag_calendar)
358 {
359 GDate date;
360 gint32 julian, events;
361 DateInfo *date_info;
362 gchar *msg;
363
364 g_return_val_if_fail (E_IS_CALENDAR (calendar), FALSE);
365 g_return_val_if_fail (E_IS_TAG_CALENDAR (tag_calendar), FALSE);
366 g_return_val_if_fail (GTK_IS_TOOLTIP (tooltip), FALSE);
367
368 if (!e_calendar_item_convert_position_to_date (e_calendar_get_item (calendar), x, y, &date))
369 return FALSE;
370
371 julian = encode_ymd_to_julian (g_date_get_year (&date), g_date_get_month (&date), g_date_get_day (&date));
372 date_info = g_hash_table_lookup (tag_calendar->priv->dates, GINT_TO_POINTER (julian));
373
374 if (!date_info)
375 return FALSE;
376
377 events = date_info->n_transparent + date_info->n_recurring + date_info->n_single;
378
379 if (events <= 0)
380 return FALSE;
381
382 msg = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE, "%d event", "%d events", events), events);
383
384 gtk_tooltip_set_text (tooltip, msg);
385
386 g_free (msg);
387
388 return TRUE;
389 }
390
391 static void
get_component_julian_range(ECalClient * client,ECalComponent * comp,guint32 * start_julian,guint32 * end_julian)392 get_component_julian_range (ECalClient *client,
393 ECalComponent *comp,
394 guint32 *start_julian,
395 guint32 *end_julian)
396 {
397 ICalTime *instance_start = NULL, *instance_end = NULL;
398 time_t start_tt, end_tt;
399 const ICalTimezone *zone;
400
401 g_return_if_fail (client != NULL);
402 g_return_if_fail (comp != NULL);
403
404 zone = calendar_config_get_icaltimezone ();
405
406 cal_comp_get_instance_times (client, e_cal_component_get_icalcomponent (comp),
407 zone, &instance_start, &instance_end, NULL);
408
409 start_tt = i_cal_time_as_timet_with_zone (instance_start, i_cal_time_get_timezone (instance_start));
410 end_tt = i_cal_time_as_timet_with_zone (instance_end, i_cal_time_get_timezone (instance_end));
411
412 *start_julian = encode_timet_to_julian (start_tt, i_cal_time_is_date (instance_start), zone);
413 *end_julian = encode_timet_to_julian (end_tt - (end_tt == start_tt ? 0 : 1), i_cal_time_is_date (instance_end), zone);
414
415 g_clear_object (&instance_start);
416 g_clear_object (&instance_end);
417 }
418
419 static void
e_tag_calendar_update_by_oinfo(ETagCalendar * tag_calendar,ObjectInfo * oinfo,gboolean inc)420 e_tag_calendar_update_by_oinfo (ETagCalendar *tag_calendar,
421 ObjectInfo *oinfo,
422 gboolean inc)
423 {
424 ECalendarItem *calitem;
425 guint32 dt, start_julian, end_julian;
426 DateInfo *dinfo;
427
428 g_return_if_fail (tag_calendar->priv->calitem != NULL);
429
430 calitem = tag_calendar->priv->calitem;
431 g_return_if_fail (calitem != NULL);
432
433 if (!oinfo)
434 return;
435
436 start_julian = oinfo->start_julian;
437 end_julian = oinfo->end_julian;
438
439 if (inc) {
440 if (start_julian < tag_calendar->priv->range_start_julian)
441 start_julian = tag_calendar->priv->range_start_julian;
442
443 if (end_julian > tag_calendar->priv->range_end_julian)
444 end_julian = tag_calendar->priv->range_end_julian;
445 }
446
447 for (dt = start_julian; dt <= end_julian; dt++) {
448 dinfo = g_hash_table_lookup (tag_calendar->priv->dates, GUINT_TO_POINTER (dt));
449
450 if (!dinfo) {
451 if (!inc)
452 continue;
453
454 dinfo = date_info_new ();
455 g_hash_table_insert (tag_calendar->priv->dates, GUINT_TO_POINTER (dt), dinfo);
456 }
457
458 if (date_info_update (dinfo, oinfo, inc)) {
459 gint year, month, day;
460 guint8 style;
461
462 decode_julian (dt, &year, &month, &day);
463 style = date_info_get_style (dinfo, tag_calendar->priv->recur_events_italic);
464
465 e_calendar_item_mark_day (calitem, year, month - 1, day, style, FALSE);
466
467 if (!style && !inc)
468 g_hash_table_remove (tag_calendar->priv->dates, GUINT_TO_POINTER (dt));
469 }
470 }
471 }
472
473 static void
e_tag_calendar_update_component_dates(ETagCalendar * tag_calendar,ObjectInfo * old_oinfo,ObjectInfo * new_oinfo)474 e_tag_calendar_update_component_dates (ETagCalendar *tag_calendar,
475 ObjectInfo *old_oinfo,
476 ObjectInfo *new_oinfo)
477 {
478 g_return_if_fail (tag_calendar->priv->calitem != NULL);
479
480 e_tag_calendar_update_by_oinfo (tag_calendar, old_oinfo, FALSE);
481 e_tag_calendar_update_by_oinfo (tag_calendar, new_oinfo, TRUE);
482 }
483
484 static void
e_tag_calendar_data_subscriber_component_added(ECalDataModelSubscriber * subscriber,ECalClient * client,ECalComponent * comp)485 e_tag_calendar_data_subscriber_component_added (ECalDataModelSubscriber *subscriber,
486 ECalClient *client,
487 ECalComponent *comp)
488 {
489 ETagCalendar *tag_calendar;
490 ECalComponentTransparency transparency;
491 guint32 start_julian = 0, end_julian = 0;
492 ObjectInfo *oinfo;
493
494 g_return_if_fail (E_IS_TAG_CALENDAR (subscriber));
495
496 tag_calendar = E_TAG_CALENDAR (subscriber);
497
498 get_component_julian_range (client, comp, &start_julian, &end_julian);
499 if (start_julian == 0 || end_julian == 0)
500 return;
501
502 transparency = e_cal_component_get_transparency (comp);
503
504 oinfo = object_info_new (client, e_cal_component_get_id (comp),
505 transparency == E_CAL_COMPONENT_TRANSP_TRANSPARENT,
506 e_cal_component_is_instance (comp),
507 start_julian, end_julian);
508
509 e_tag_calendar_update_component_dates (tag_calendar, NULL, oinfo);
510
511 g_hash_table_replace (tag_calendar->priv->objects, oinfo, NULL);
512 }
513
514 static void
e_tag_calendar_data_subscriber_component_modified(ECalDataModelSubscriber * subscriber,ECalClient * client,ECalComponent * comp)515 e_tag_calendar_data_subscriber_component_modified (ECalDataModelSubscriber *subscriber,
516 ECalClient *client,
517 ECalComponent *comp)
518 {
519 ETagCalendar *tag_calendar;
520 ECalComponentTransparency transparency;
521 guint32 start_julian = 0, end_julian = 0;
522 gpointer orig_key, orig_value;
523 ObjectInfo *old_oinfo = NULL, *new_oinfo;
524
525 g_return_if_fail (E_IS_TAG_CALENDAR (subscriber));
526
527 tag_calendar = E_TAG_CALENDAR (subscriber);
528
529 get_component_julian_range (client, comp, &start_julian, &end_julian);
530 if (start_julian == 0 || end_julian == 0)
531 return;
532
533 transparency = e_cal_component_get_transparency (comp);
534
535 new_oinfo = object_info_new (client, e_cal_component_get_id (comp),
536 transparency == E_CAL_COMPONENT_TRANSP_TRANSPARENT,
537 e_cal_component_is_instance (comp),
538 start_julian, end_julian);
539
540 if (!g_hash_table_lookup_extended (tag_calendar->priv->objects, new_oinfo, &orig_key, &orig_value)) {
541 object_info_free (new_oinfo);
542 return;
543 }
544
545 old_oinfo = orig_key;
546
547 if (object_info_data_equal (old_oinfo, new_oinfo)) {
548 object_info_free (new_oinfo);
549 return;
550 }
551
552 e_tag_calendar_update_component_dates (tag_calendar, old_oinfo, new_oinfo);
553
554 /* it also frees old_oinfo */
555 g_hash_table_replace (tag_calendar->priv->objects, new_oinfo, NULL);
556 }
557
558 static void
e_tag_calendar_data_subscriber_component_removed(ECalDataModelSubscriber * subscriber,ECalClient * client,const gchar * uid,const gchar * rid)559 e_tag_calendar_data_subscriber_component_removed (ECalDataModelSubscriber *subscriber,
560 ECalClient *client,
561 const gchar *uid,
562 const gchar *rid)
563 {
564 ETagCalendar *tag_calendar;
565 ECalComponentId *id;
566 gpointer orig_key, orig_value;
567 ObjectInfo fake_oinfo, *old_oinfo;
568
569 g_return_if_fail (E_IS_TAG_CALENDAR (subscriber));
570
571 tag_calendar = E_TAG_CALENDAR (subscriber);
572
573 id = e_cal_component_id_new (uid, rid);
574
575 /* only these two values are used for GHashTable compare */
576 fake_oinfo.client = client;
577 fake_oinfo.id = id;
578
579 if (!g_hash_table_lookup_extended (tag_calendar->priv->objects, &fake_oinfo, &orig_key, &orig_value)) {
580 e_cal_component_id_free (id);
581 return;
582 }
583
584 old_oinfo = orig_key;
585
586 e_tag_calendar_update_component_dates (tag_calendar, old_oinfo, NULL);
587
588 g_hash_table_remove (tag_calendar->priv->objects, old_oinfo);
589
590 e_cal_component_id_free (id);
591 }
592
593 static void
e_tag_calendar_data_subscriber_freeze(ECalDataModelSubscriber * subscriber)594 e_tag_calendar_data_subscriber_freeze (ECalDataModelSubscriber *subscriber)
595 {
596 /* Ignore freezes here */
597 }
598
599 static void
e_tag_calendar_data_subscriber_thaw(ECalDataModelSubscriber * subscriber)600 e_tag_calendar_data_subscriber_thaw (ECalDataModelSubscriber *subscriber)
601 {
602 /* Ignore freezes here */
603 }
604
605 static void
e_tag_calendar_set_calendar(ETagCalendar * tag_calendar,ECalendar * calendar)606 e_tag_calendar_set_calendar (ETagCalendar *tag_calendar,
607 ECalendar *calendar)
608 {
609 g_return_if_fail (E_IS_TAG_CALENDAR (tag_calendar));
610 g_return_if_fail (E_IS_CALENDAR (calendar));
611 g_return_if_fail (e_calendar_get_item (calendar) != NULL);
612 g_return_if_fail (tag_calendar->priv->calendar == NULL);
613
614 tag_calendar->priv->calendar = calendar;
615 tag_calendar->priv->calitem = e_calendar_get_item (calendar);
616
617 g_object_weak_ref (G_OBJECT (tag_calendar->priv->calendar),
618 (GWeakNotify) g_nullify_pointer, &tag_calendar->priv->calendar);
619 g_object_weak_ref (G_OBJECT (tag_calendar->priv->calitem),
620 (GWeakNotify) g_nullify_pointer, &tag_calendar->priv->calitem);
621 }
622
623 static void
e_tag_calendar_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)624 e_tag_calendar_set_property (GObject *object,
625 guint property_id,
626 const GValue *value,
627 GParamSpec *pspec)
628 {
629 switch (property_id) {
630 case PROP_CALENDAR:
631 e_tag_calendar_set_calendar (
632 E_TAG_CALENDAR (object),
633 g_value_get_object (value));
634 return;
635
636 case PROP_RECUR_EVENTS_ITALIC:
637 e_tag_calendar_set_recur_events_italic (
638 E_TAG_CALENDAR (object),
639 g_value_get_boolean (value));
640 return;
641 }
642
643 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
644 }
645
646 static void
e_tag_calendar_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)647 e_tag_calendar_get_property (GObject *object,
648 guint property_id,
649 GValue *value,
650 GParamSpec *pspec)
651 {
652 switch (property_id) {
653 case PROP_CALENDAR:
654 g_value_set_object (value,
655 e_tag_calendar_get_calendar (E_TAG_CALENDAR (object)));
656 return;
657
658 case PROP_RECUR_EVENTS_ITALIC:
659 g_value_set_boolean (value,
660 e_tag_calendar_get_recur_events_italic (E_TAG_CALENDAR (object)));
661 return;
662 }
663
664 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
665 }
666
667 static void
e_tag_calendar_constructed(GObject * object)668 e_tag_calendar_constructed (GObject *object)
669 {
670 ETagCalendar *tag_calendar = E_TAG_CALENDAR (object);
671 GSettings *settings;
672
673 /* Chain up to parent's constructed() method. */
674 G_OBJECT_CLASS (e_tag_calendar_parent_class)->constructed (object);
675
676 g_return_if_fail (tag_calendar->priv->calendar != NULL);
677 g_return_if_fail (tag_calendar->priv->calitem != NULL);
678
679 g_signal_connect_swapped (tag_calendar->priv->calitem, "date-range-changed",
680 G_CALLBACK (e_tag_calendar_date_range_changed_cb), tag_calendar);
681
682 g_signal_connect (tag_calendar->priv->calendar, "query-tooltip",
683 G_CALLBACK (e_tag_calendar_query_tooltip_cb), tag_calendar);
684
685 gtk_widget_set_has_tooltip (GTK_WIDGET (tag_calendar->priv->calendar), TRUE);
686
687 settings = e_util_ref_settings ("org.gnome.evolution.calendar");
688
689 g_settings_bind (
690 settings, "recur-events-italic",
691 tag_calendar, "recur-events-italic",
692 G_SETTINGS_BIND_DEFAULT | G_SETTINGS_BIND_NO_SENSITIVITY);
693
694 g_object_unref (settings);
695 }
696
697 static void
e_tag_calendar_dispose(GObject * object)698 e_tag_calendar_dispose (GObject *object)
699 {
700 ETagCalendar *tag_calendar = E_TAG_CALENDAR (object);
701
702 if (tag_calendar->priv->calendar != NULL) {
703 g_signal_handlers_disconnect_by_func (e_calendar_get_item (tag_calendar->priv->calendar),
704 G_CALLBACK (e_tag_calendar_date_range_changed_cb), tag_calendar);
705 g_signal_handlers_disconnect_by_func (tag_calendar->priv->calendar,
706 G_CALLBACK (e_tag_calendar_query_tooltip_cb), tag_calendar);
707 g_object_weak_unref (G_OBJECT (tag_calendar->priv->calendar),
708 (GWeakNotify) g_nullify_pointer, &tag_calendar->priv->calendar);
709 tag_calendar->priv->calendar = NULL;
710 }
711
712 if (tag_calendar->priv->calitem != NULL) {
713 g_object_weak_unref (G_OBJECT (tag_calendar->priv->calitem),
714 (GWeakNotify) g_nullify_pointer, &tag_calendar->priv->calitem);
715 tag_calendar->priv->calitem = NULL;
716 }
717
718 if (tag_calendar->priv->data_model)
719 e_tag_calendar_unsubscribe (tag_calendar, tag_calendar->priv->data_model);
720
721 /* Chain up to parent's dispose() method. */
722 G_OBJECT_CLASS (e_tag_calendar_parent_class)->dispose (object);
723 }
724
725 static void
e_tag_calendar_finalize(GObject * object)726 e_tag_calendar_finalize (GObject *object)
727 {
728 ETagCalendar *tag_calendar = E_TAG_CALENDAR (object);
729
730 g_warn_if_fail (tag_calendar->priv->data_model == NULL);
731
732 g_hash_table_destroy (tag_calendar->priv->objects);
733 g_hash_table_destroy (tag_calendar->priv->dates);
734
735 /* Chain up to parent's finalize() method. */
736 G_OBJECT_CLASS (e_tag_calendar_parent_class)->finalize (object);
737 }
738
739 static void
e_tag_calendar_class_init(ETagCalendarClass * class)740 e_tag_calendar_class_init (ETagCalendarClass *class)
741 {
742 GObjectClass *object_class;
743
744 g_type_class_add_private (class, sizeof (ETagCalendarPrivate));
745
746 object_class = G_OBJECT_CLASS (class);
747 object_class->set_property = e_tag_calendar_set_property;
748 object_class->get_property = e_tag_calendar_get_property;
749 object_class->constructed = e_tag_calendar_constructed;
750 object_class->dispose = e_tag_calendar_dispose;
751 object_class->finalize = e_tag_calendar_finalize;
752
753 g_object_class_install_property (
754 object_class,
755 PROP_CALENDAR,
756 g_param_spec_object (
757 "calendar",
758 "Calendar",
759 NULL,
760 E_TYPE_CALENDAR,
761 G_PARAM_READWRITE |
762 G_PARAM_CONSTRUCT_ONLY));
763
764 g_object_class_install_property (
765 object_class,
766 PROP_RECUR_EVENTS_ITALIC,
767 g_param_spec_boolean (
768 "recur-events-italic",
769 "Recur Events Italic",
770 NULL,
771 FALSE,
772 G_PARAM_READWRITE));
773 }
774
775 static void
e_tag_calendar_cal_data_model_subscriber_init(ECalDataModelSubscriberInterface * iface)776 e_tag_calendar_cal_data_model_subscriber_init (ECalDataModelSubscriberInterface *iface)
777 {
778 iface->component_added = e_tag_calendar_data_subscriber_component_added;
779 iface->component_modified = e_tag_calendar_data_subscriber_component_modified;
780 iface->component_removed = e_tag_calendar_data_subscriber_component_removed;
781 iface->freeze = e_tag_calendar_data_subscriber_freeze;
782 iface->thaw = e_tag_calendar_data_subscriber_thaw;
783 }
784
785 static void
e_tag_calendar_init(ETagCalendar * tag_calendar)786 e_tag_calendar_init (ETagCalendar *tag_calendar)
787 {
788 tag_calendar->priv = G_TYPE_INSTANCE_GET_PRIVATE (tag_calendar, E_TYPE_TAG_CALENDAR, ETagCalendarPrivate);
789
790 tag_calendar->priv->objects = g_hash_table_new_full (
791 object_info_hash,
792 object_info_equal,
793 object_info_free,
794 NULL);
795
796 tag_calendar->priv->dates = g_hash_table_new_full (
797 g_direct_hash,
798 g_direct_equal,
799 NULL,
800 date_info_free);
801 }
802
803 ETagCalendar *
e_tag_calendar_new(ECalendar * calendar)804 e_tag_calendar_new (ECalendar *calendar)
805 {
806 return g_object_new (E_TYPE_TAG_CALENDAR, "calendar", calendar, NULL);
807 }
808
809 ECalendar *
e_tag_calendar_get_calendar(ETagCalendar * tag_calendar)810 e_tag_calendar_get_calendar (ETagCalendar *tag_calendar)
811 {
812 g_return_val_if_fail (E_IS_TAG_CALENDAR (tag_calendar), NULL);
813
814 return tag_calendar->priv->calendar;
815 }
816
817 gboolean
e_tag_calendar_get_recur_events_italic(ETagCalendar * tag_calendar)818 e_tag_calendar_get_recur_events_italic (ETagCalendar *tag_calendar)
819 {
820 g_return_val_if_fail (E_IS_TAG_CALENDAR (tag_calendar), FALSE);
821
822 return tag_calendar->priv->recur_events_italic;
823 }
824
825 void
e_tag_calendar_set_recur_events_italic(ETagCalendar * tag_calendar,gboolean recur_events_italic)826 e_tag_calendar_set_recur_events_italic (ETagCalendar *tag_calendar,
827 gboolean recur_events_italic)
828 {
829 g_return_if_fail (E_IS_TAG_CALENDAR (tag_calendar));
830
831 if ((tag_calendar->priv->recur_events_italic ? 1 : 0) == (recur_events_italic ? 1 : 0))
832 return;
833
834 tag_calendar->priv->recur_events_italic = recur_events_italic;
835
836 g_object_notify (G_OBJECT (tag_calendar), "recur-events-italic");
837
838 e_tag_calendar_remark_days (tag_calendar);
839 }
840
841 void
e_tag_calendar_subscribe(ETagCalendar * tag_calendar,ECalDataModel * data_model)842 e_tag_calendar_subscribe (ETagCalendar *tag_calendar,
843 ECalDataModel *data_model)
844 {
845 g_return_if_fail (E_IS_TAG_CALENDAR (tag_calendar));
846 g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
847 g_return_if_fail (tag_calendar->priv->data_model != data_model);
848
849 /* if the reference is held by the priv->data_model, then
850 an unsubscribe may cause free of the tag_calendar */
851 g_object_ref (tag_calendar);
852
853 if (tag_calendar->priv->data_model)
854 e_tag_calendar_unsubscribe (tag_calendar, tag_calendar->priv->data_model);
855
856 tag_calendar->priv->data_model = data_model;
857 e_tag_calendar_date_range_changed_cb (tag_calendar);
858
859 g_object_unref (tag_calendar);
860 }
861
862 void
e_tag_calendar_unsubscribe(ETagCalendar * tag_calendar,ECalDataModel * data_model)863 e_tag_calendar_unsubscribe (ETagCalendar *tag_calendar,
864 ECalDataModel *data_model)
865 {
866 g_return_if_fail (E_IS_TAG_CALENDAR (tag_calendar));
867 g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
868 g_return_if_fail (tag_calendar->priv->data_model == data_model);
869
870 e_cal_data_model_unsubscribe (data_model, E_CAL_DATA_MODEL_SUBSCRIBER (tag_calendar));
871 tag_calendar->priv->data_model = NULL;
872
873 /* calitem can be NULL during dispose of an ECalBaseShellContents */
874 if (tag_calendar->priv->calitem)
875 e_calendar_item_clear_marks (tag_calendar->priv->calitem);
876
877 g_hash_table_remove_all (tag_calendar->priv->objects);
878 g_hash_table_remove_all (tag_calendar->priv->dates);
879 }
880
881 struct calendar_tag_closure {
882 ECalendarItem *calitem;
883 ICalTimezone *zone;
884 time_t start_time;
885 time_t end_time;
886
887 gboolean skip_transparent_events;
888 gboolean recur_events_italic;
889 };
890
891 static void
calendar_tag_closure_free(gpointer ptr)892 calendar_tag_closure_free (gpointer ptr)
893 {
894 struct calendar_tag_closure *closure = ptr;
895
896 if (closure)
897 g_slice_free (struct calendar_tag_closure, closure);
898 }
899
900 /* Clears all the tags in a calendar and fills a closure structure with the
901 * necessary information for iterating over occurrences. Returns FALSE if
902 * the calendar has no dates shown. */
903 static gboolean
prepare_tag(ECalendar * ecal,struct calendar_tag_closure * closure,ICalTimezone * zone,gboolean clear_first)904 prepare_tag (ECalendar *ecal,
905 struct calendar_tag_closure *closure,
906 ICalTimezone *zone,
907 gboolean clear_first)
908 {
909 gint start_year, start_month, start_day;
910 gint end_year, end_month, end_day;
911 ICalTime *start_tt = NULL;
912 ICalTime *end_tt = NULL;
913
914 if (clear_first)
915 e_calendar_item_clear_marks (e_calendar_get_item (ecal));
916
917 if (!e_calendar_item_get_date_range (
918 e_calendar_get_item (ecal),
919 &start_year, &start_month, &start_day,
920 &end_year, &end_month, &end_day))
921 return FALSE;
922
923 start_tt = i_cal_time_new_null_time ();
924 i_cal_time_set_date (start_tt,
925 start_year,
926 start_month + 1,
927 start_day);
928
929 end_tt = i_cal_time_new_null_time ();
930 i_cal_time_set_date (end_tt,
931 end_year,
932 end_month + 1,
933 end_day);
934
935 i_cal_time_adjust (end_tt, 1, 0, 0, 0);
936
937 closure->calitem = e_calendar_get_item (ecal);
938
939 if (zone != NULL)
940 closure->zone = zone;
941 else
942 closure->zone = calendar_config_get_icaltimezone ();
943
944 closure->start_time =
945 i_cal_time_as_timet_with_zone (start_tt, closure->zone);
946 closure->end_time =
947 i_cal_time_as_timet_with_zone (end_tt, closure->zone);
948
949 g_clear_object (&start_tt);
950 g_clear_object (&end_tt);
951
952 return TRUE;
953 }
954
955 /* Marks the specified range in an ECalendar;
956 * called from e_cal_generate_instances() */
957 static gboolean
tag_calendar_cb(ICalComponent * comp,ICalTime * instance_start,ICalTime * instance_end,gpointer user_data,GCancellable * cancellable,GError ** error)958 tag_calendar_cb (ICalComponent *comp,
959 ICalTime *instance_start,
960 ICalTime *instance_end,
961 gpointer user_data,
962 GCancellable *cancellable,
963 GError **error)
964 {
965 struct calendar_tag_closure *closure = user_data;
966 ICalPropertyTransp transp = I_CAL_TRANSP_NONE;
967 ICalProperty *prop;
968 guint8 style = 0;
969
970 /* If we are skipping TRANSPARENT events, return if the event is
971 * transparent. */
972 prop = i_cal_component_get_first_property (comp, I_CAL_TRANSP_PROPERTY);
973 if (prop) {
974 transp = i_cal_property_get_transp (prop);
975 g_object_unref (prop);
976 }
977
978 if (transp == I_CAL_TRANSP_TRANSPARENT ||
979 transp == I_CAL_TRANSP_TRANSPARENTNOCONFLICT) {
980 if (closure->skip_transparent_events)
981 return TRUE;
982
983 style = E_CALENDAR_ITEM_MARK_ITALIC;
984 } else if (closure->recur_events_italic && e_cal_util_component_is_instance (comp)) {
985 style = E_CALENDAR_ITEM_MARK_ITALIC;
986 } else {
987 style = E_CALENDAR_ITEM_MARK_BOLD;
988 }
989
990 e_calendar_item_mark_days (
991 closure->calitem,
992 i_cal_time_get_year (instance_start),
993 i_cal_time_get_month (instance_start) - 1,
994 i_cal_time_get_day (instance_start),
995 i_cal_time_get_year (instance_end),
996 i_cal_time_get_month (instance_end) - 1,
997 i_cal_time_get_day (instance_end),
998 style, TRUE);
999
1000 return TRUE;
1001 }
1002
1003 /**
1004 * tag_calendar_by_comp:
1005 * @ecal: Calendar widget to tag.
1006 * @comp: A calendar component object.
1007 * @clear_first: Whether the #ECalendar should be cleared of any marks first.
1008 *
1009 * Tags an #ECalendar widget with any occurrences of a specific calendar
1010 * component that occur within the calendar's current time range.
1011 * Note that TRANSPARENT events are also tagged here.
1012 *
1013 * If comp_is_on_server is FALSE, it will try to resolve TZIDs using builtin
1014 * timezones first, before querying the server, since the timezones may not
1015 * have been added to the calendar on the server yet.
1016 **/
1017 void
tag_calendar_by_comp(ECalendar * ecal,ECalComponent * comp,ECalClient * client,ICalTimezone * display_zone,gboolean clear_first,gboolean comp_is_on_server,gboolean can_recur_events_italic,GCancellable * cancellable)1018 tag_calendar_by_comp (ECalendar *ecal,
1019 ECalComponent *comp,
1020 ECalClient *client,
1021 ICalTimezone *display_zone,
1022 gboolean clear_first,
1023 gboolean comp_is_on_server,
1024 gboolean can_recur_events_italic,
1025 GCancellable *cancellable)
1026 {
1027 GSettings *settings;
1028 struct calendar_tag_closure closure;
1029
1030 g_return_if_fail (E_IS_CALENDAR (ecal));
1031 g_return_if_fail (E_IS_CAL_COMPONENT (comp));
1032
1033 /* If the ECalendar isn't visible, we just return. */
1034 if (!gtk_widget_get_visible (GTK_WIDGET (ecal)))
1035 return;
1036
1037 if (!prepare_tag (ecal, &closure, display_zone, clear_first))
1038 return;
1039
1040 settings = e_util_ref_settings ("org.gnome.evolution.calendar");
1041
1042 closure.skip_transparent_events = FALSE;
1043 closure.recur_events_italic =
1044 can_recur_events_italic &&
1045 g_settings_get_boolean (settings, "recur-events-italic");
1046
1047 g_object_unref (settings);
1048
1049 if (comp_is_on_server) {
1050 struct calendar_tag_closure *alloced_closure;
1051
1052 alloced_closure = g_slice_new0 (struct calendar_tag_closure);
1053
1054 *alloced_closure = closure;
1055
1056 e_cal_client_generate_instances_for_object (
1057 client, e_cal_component_get_icalcomponent (comp),
1058 closure.start_time, closure.end_time, cancellable,
1059 tag_calendar_cb,
1060 alloced_closure, calendar_tag_closure_free);
1061 } else {
1062 ICalTime *start, *end;
1063
1064 start = i_cal_time_new_from_timet_with_zone (closure.start_time, FALSE, display_zone);
1065 end = i_cal_time_new_from_timet_with_zone (closure.end_time, FALSE, display_zone);
1066
1067 e_cal_recur_generate_instances_sync (e_cal_component_get_icalcomponent (comp),
1068 start, end, tag_calendar_cb, &closure,
1069 e_cal_client_tzlookup_cb, client,
1070 display_zone, cancellable, NULL);
1071
1072 g_clear_object (&start);
1073 g_clear_object (&end);
1074 }
1075 }
1076