Bug 793125 - Crash due to popup menus left attached too long
[evolution.git] / src / calendar / gui / tag-calendar.c
blob652abe0df7b26138490c269f2f9c5d25636e353f
1 /*
3 * Evolution calendar - Utilities for tagging ECalendar widgets
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.
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.
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/>.
18 * Authors:
19 * Damon Chaplin <damon@ximian.com>
20 * Federico Mena-Quintero <federico@ximian.com>
22 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
26 #include "evolution-config.h"
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"
34 struct _ETagCalendarPrivate
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;
41 GHashTable *objects; /* ObjectInfo ~> 1 (unused) */
42 GHashTable *dates; /* julian date ~> DateInfo */
44 guint32 range_start_julian;
45 guint32 range_end_julian;
48 enum {
49 PROP_0,
50 PROP_CALENDAR,
51 PROP_RECUR_EVENTS_ITALIC
54 static void e_tag_calendar_cal_data_model_subscriber_init (ECalDataModelSubscriberInterface *iface);
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))
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;
68 typedef struct {
69 guint n_transparent;
70 guint n_recurring;
71 guint n_single;
72 } DateInfo;
74 static guint
75 object_info_hash (gconstpointer v)
77 const ObjectInfo *oinfo = v;
79 if (!v)
80 return 0;
82 return g_direct_hash (oinfo->client) ^ e_cal_component_id_hash (oinfo->id);
85 /* component-related equality, for hash tables */
86 static gboolean
87 object_info_equal (gconstpointer v1,
88 gconstpointer v2)
90 const ObjectInfo *oinfo1 = v1;
91 const ObjectInfo *oinfo2 = v2;
93 if (oinfo1 == oinfo2)
94 return TRUE;
96 if (!oinfo1 || !oinfo2)
97 return FALSE;
99 return oinfo1->client == oinfo2->client &&
100 e_cal_component_id_equal (oinfo1->id, oinfo2->id);
103 /* date-related equality, for drawing changes */
104 static gboolean
105 object_info_data_equal (ObjectInfo *o1,
106 ObjectInfo *o2)
108 if (o1 == o2)
109 return TRUE;
111 if (!o1 || !o2)
112 return FALSE;
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);
120 static ObjectInfo *
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)
128 ObjectInfo *oinfo;
130 g_return_val_if_fail (client != NULL, NULL);
131 g_return_val_if_fail (id != NULL, NULL);
133 oinfo = g_new0 (ObjectInfo, 1);
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;
141 return oinfo;
144 static void
145 object_info_free (gpointer ptr)
147 ObjectInfo *oinfo = ptr;
149 if (oinfo) {
150 e_cal_component_free_id (oinfo->id);
151 g_free (oinfo);
155 static DateInfo *
156 date_info_new (void)
158 return g_new0 (DateInfo, 1);
161 static void
162 date_info_free (gpointer ptr)
164 DateInfo *dinfo = ptr;
166 if (dinfo)
167 g_free (dinfo);
170 static gboolean
171 date_info_update (DateInfo *dinfo,
172 ObjectInfo *oinfo,
173 gboolean inc)
175 gint nn = inc ? +1 : -1;
176 gboolean ui_changed = FALSE;
178 g_return_val_if_fail (dinfo != NULL, FALSE);
179 g_return_val_if_fail (oinfo != NULL, FALSE);
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);
192 return ui_changed;
195 static guint8
196 date_info_get_style (DateInfo *dinfo,
197 gboolean recur_events_italic)
199 guint8 style = 0;
201 g_return_val_if_fail (dinfo != NULL, 0);
203 if (dinfo->n_transparent > 0 ||
204 (recur_events_italic && dinfo->n_recurring > 0))
205 style |= E_CALENDAR_ITEM_MARK_ITALIC;
207 if (dinfo->n_single > 0 ||
208 (!recur_events_italic && dinfo->n_recurring > 0))
209 style |= E_CALENDAR_ITEM_MARK_BOLD;
211 return style;
214 static gint32
215 encode_ymd_to_julian (gint year,
216 gint month,
217 gint day)
219 GDate dt;
221 g_date_clear (&dt, 1);
222 g_date_set_dmy (&dt, day, month, year);
224 return g_date_get_julian (&dt);
227 static guint32
228 encode_timet_to_julian (time_t t,
229 gboolean is_date,
230 const icaltimezone *zone)
232 struct icaltimetype tt;
234 if (!t)
235 return 0;
237 tt = icaltime_from_timet_with_zone (t, is_date, zone);
239 if (!icaltime_is_valid_time (tt) || icaltime_is_null_time (tt))
240 return 0;
242 return encode_ymd_to_julian (tt.year, tt.month, tt.day);
245 static void
246 decode_julian (guint32 julian,
247 gint *year,
248 gint *month,
249 gint *day)
251 GDate dt;
253 g_date_clear (&dt, 1);
254 g_date_set_julian (&dt, julian);
256 *year = g_date_get_year (&dt);
257 *month = g_date_get_month (&dt);
258 *day = g_date_get_day (&dt);
261 static void
262 tag_calendar_date_cb (gpointer key,
263 gpointer value,
264 gpointer user_data)
266 ETagCalendar *tag_calendar = user_data;
267 DateInfo *dinfo = value;
268 gint year, month, day;
270 decode_julian (GPOINTER_TO_UINT (key), &year, &month, &day);
272 e_calendar_item_mark_day (tag_calendar->priv->calitem, year, month - 1, day,
273 date_info_get_style (dinfo, tag_calendar->priv->recur_events_italic), FALSE);
276 static void
277 e_tag_calendar_remark_days (ETagCalendar *tag_calendar)
279 g_return_if_fail (E_IS_TAG_CALENDAR (tag_calendar));
280 g_return_if_fail (tag_calendar->priv->calitem != NULL);
282 e_calendar_item_clear_marks (tag_calendar->priv->calitem);
284 g_hash_table_foreach (tag_calendar->priv->dates, tag_calendar_date_cb, tag_calendar);
287 static time_t
288 e_tag_calendar_date_to_timet (gint year,
289 gint month,
290 gint day,
291 const icaltimezone *with_zone)
293 GDate *date;
294 time_t tt;
296 date = g_date_new_dmy (day, month, year);
297 g_return_val_if_fail (date != NULL, (time_t) -1);
299 tt = cal_comp_gdate_to_timet (date, with_zone);
301 g_date_free (date);
303 return tt;
306 static void
307 e_tag_calendar_date_range_changed_cb (ETagCalendar *tag_calendar)
309 gint start_year, start_month, start_day, end_year, end_month, end_day;
310 time_t range_start, range_end;
312 g_return_if_fail (E_IS_TAG_CALENDAR (tag_calendar));
314 if (!tag_calendar->priv->data_model ||
315 !tag_calendar->priv->calitem)
316 return;
318 g_return_if_fail (E_IS_CALENDAR_ITEM (tag_calendar->priv->calitem));
320 /* This can fail on start, when the ECalendarItem wasn't updated (drawn) yet */
321 if (!e_calendar_item_get_date_range (tag_calendar->priv->calitem,
322 &start_year, &start_month, &start_day, &end_year, &end_month, &end_day))
323 return;
325 start_month++;
326 end_month++;
328 range_start = e_tag_calendar_date_to_timet (start_year, start_month, start_day, NULL);
329 range_end = e_tag_calendar_date_to_timet (end_year, end_month, end_day, NULL);
331 tag_calendar->priv->range_start_julian = encode_ymd_to_julian (start_year, start_month, start_day);
332 tag_calendar->priv->range_end_julian = encode_ymd_to_julian (end_year, end_month, end_day);
334 /* Range change causes removal of marks in the calendar */
335 e_tag_calendar_remark_days (tag_calendar);
337 e_cal_data_model_subscribe (tag_calendar->priv->data_model,
338 E_CAL_DATA_MODEL_SUBSCRIBER (tag_calendar),
339 range_start, range_end);
342 static gboolean
343 e_tag_calendar_query_tooltip_cb (ECalendar *calendar,
344 gint x,
345 gint y,
346 gboolean keayboard_mode,
347 GtkTooltip *tooltip,
348 ETagCalendar *tag_calendar)
350 GDate date;
351 gint32 julian, events;
352 DateInfo *date_info;
353 gchar *msg;
355 g_return_val_if_fail (E_IS_CALENDAR (calendar), FALSE);
356 g_return_val_if_fail (E_IS_TAG_CALENDAR (tag_calendar), FALSE);
357 g_return_val_if_fail (GTK_IS_TOOLTIP (tooltip), FALSE);
359 if (!e_calendar_item_convert_position_to_date (e_calendar_get_item (calendar), x, y, &date))
360 return FALSE;
362 julian = encode_ymd_to_julian (g_date_get_year (&date), g_date_get_month (&date), g_date_get_day (&date));
363 date_info = g_hash_table_lookup (tag_calendar->priv->dates, GINT_TO_POINTER (julian));
365 if (!date_info)
366 return FALSE;
368 events = date_info->n_transparent + date_info->n_recurring + date_info->n_single;
370 if (events <= 0)
371 return FALSE;
373 msg = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE, "%d event", "%d events", events), events);
375 gtk_tooltip_set_text (tooltip, msg);
377 g_free (msg);
379 return TRUE;
382 static void
383 get_component_julian_range (ECalClient *client,
384 ECalComponent *comp,
385 guint32 *start_julian,
386 guint32 *end_julian)
388 time_t instance_start = 0, instance_end = 0;
389 gboolean start_is_date = FALSE, end_is_date = FALSE;
390 const icaltimezone *zone;
392 g_return_if_fail (client != NULL);
393 g_return_if_fail (comp != NULL);
395 zone = calendar_config_get_icaltimezone ();
397 cal_comp_get_instance_times (client, e_cal_component_get_icalcomponent (comp),
398 zone, &instance_start, &start_is_date, &instance_end, &end_is_date, NULL);
400 *start_julian = encode_timet_to_julian (instance_start, start_is_date, zone);
401 *end_julian = encode_timet_to_julian (instance_end - (instance_end == instance_start ? 0 : 1), end_is_date, zone);
404 static void
405 e_tag_calendar_update_by_oinfo (ETagCalendar *tag_calendar,
406 ObjectInfo *oinfo,
407 gboolean inc)
409 ECalendarItem *calitem;
410 guint32 dt, start_julian, end_julian;
411 DateInfo *dinfo;
413 g_return_if_fail (tag_calendar->priv->calitem != NULL);
415 calitem = tag_calendar->priv->calitem;
416 g_return_if_fail (calitem != NULL);
418 if (!oinfo)
419 return;
421 start_julian = oinfo->start_julian;
422 end_julian = oinfo->end_julian;
424 if (inc) {
425 if (start_julian < tag_calendar->priv->range_start_julian)
426 start_julian = tag_calendar->priv->range_start_julian;
428 if (end_julian > tag_calendar->priv->range_end_julian)
429 end_julian = tag_calendar->priv->range_end_julian;
432 for (dt = start_julian; dt <= end_julian; dt++) {
433 dinfo = g_hash_table_lookup (tag_calendar->priv->dates, GUINT_TO_POINTER (dt));
435 if (!dinfo) {
436 if (!inc)
437 continue;
439 dinfo = date_info_new ();
440 g_hash_table_insert (tag_calendar->priv->dates, GUINT_TO_POINTER (dt), dinfo);
443 if (date_info_update (dinfo, oinfo, inc)) {
444 gint year, month, day;
445 guint8 style;
447 decode_julian (dt, &year, &month, &day);
448 style = date_info_get_style (dinfo, tag_calendar->priv->recur_events_italic);
450 e_calendar_item_mark_day (calitem, year, month - 1, day, style, FALSE);
452 if (!style && !inc)
453 g_hash_table_remove (tag_calendar->priv->dates, GUINT_TO_POINTER (dt));
458 static void
459 e_tag_calendar_update_component_dates (ETagCalendar *tag_calendar,
460 ObjectInfo *old_oinfo,
461 ObjectInfo *new_oinfo)
463 g_return_if_fail (tag_calendar->priv->calitem != NULL);
465 e_tag_calendar_update_by_oinfo (tag_calendar, old_oinfo, FALSE);
466 e_tag_calendar_update_by_oinfo (tag_calendar, new_oinfo, TRUE);
469 static void
470 e_tag_calendar_data_subscriber_component_added (ECalDataModelSubscriber *subscriber,
471 ECalClient *client,
472 ECalComponent *comp)
474 ETagCalendar *tag_calendar;
475 ECalComponentTransparency transparency;
476 guint32 start_julian = 0, end_julian = 0;
477 ObjectInfo *oinfo;
479 g_return_if_fail (E_IS_TAG_CALENDAR (subscriber));
481 tag_calendar = E_TAG_CALENDAR (subscriber);
483 get_component_julian_range (client, comp, &start_julian, &end_julian);
484 if (start_julian == 0 || end_julian == 0)
485 return;
487 e_cal_component_get_transparency (comp, &transparency);
489 oinfo = object_info_new (client, e_cal_component_get_id (comp),
490 transparency == E_CAL_COMPONENT_TRANSP_TRANSPARENT,
491 e_cal_component_is_instance (comp),
492 start_julian, end_julian);
494 e_tag_calendar_update_component_dates (tag_calendar, NULL, oinfo);
496 g_hash_table_insert (tag_calendar->priv->objects, oinfo, GINT_TO_POINTER (0));
499 static void
500 e_tag_calendar_data_subscriber_component_modified (ECalDataModelSubscriber *subscriber,
501 ECalClient *client,
502 ECalComponent *comp)
504 ETagCalendar *tag_calendar;
505 ECalComponentTransparency transparency;
506 guint32 start_julian = 0, end_julian = 0;
507 gpointer orig_key, orig_value;
508 ObjectInfo *old_oinfo = NULL, *new_oinfo;
510 g_return_if_fail (E_IS_TAG_CALENDAR (subscriber));
512 tag_calendar = E_TAG_CALENDAR (subscriber);
514 get_component_julian_range (client, comp, &start_julian, &end_julian);
515 if (start_julian == 0 || end_julian == 0)
516 return;
518 e_cal_component_get_transparency (comp, &transparency);
520 new_oinfo = object_info_new (client, e_cal_component_get_id (comp),
521 transparency == E_CAL_COMPONENT_TRANSP_TRANSPARENT,
522 e_cal_component_is_instance (comp),
523 start_julian, end_julian);
525 if (!g_hash_table_lookup_extended (tag_calendar->priv->objects, new_oinfo, &orig_key, &orig_value)) {
526 object_info_free (new_oinfo);
527 return;
530 old_oinfo = orig_key;
532 if (object_info_data_equal (old_oinfo, new_oinfo)) {
533 object_info_free (new_oinfo);
534 return;
537 e_tag_calendar_update_component_dates (tag_calendar, old_oinfo, new_oinfo);
539 /* it also frees old_oinfo */
540 g_hash_table_insert (tag_calendar->priv->objects, new_oinfo, GINT_TO_POINTER (0));
543 static void
544 e_tag_calendar_data_subscriber_component_removed (ECalDataModelSubscriber *subscriber,
545 ECalClient *client,
546 const gchar *uid,
547 const gchar *rid)
549 ETagCalendar *tag_calendar;
550 ECalComponentId id;
551 gpointer orig_key, orig_value;
552 ObjectInfo fake_oinfo, *old_oinfo;
554 g_return_if_fail (E_IS_TAG_CALENDAR (subscriber));
556 tag_calendar = E_TAG_CALENDAR (subscriber);
558 id.uid = (gchar *) uid;
559 id.rid = (gchar *) rid;
561 /* only these two values are used for GHashTable compare */
562 fake_oinfo.client = client;
563 fake_oinfo.id = &id;
565 if (!g_hash_table_lookup_extended (tag_calendar->priv->objects, &fake_oinfo, &orig_key, &orig_value))
566 return;
568 old_oinfo = orig_key;
570 e_tag_calendar_update_component_dates (tag_calendar, old_oinfo, NULL);
572 g_hash_table_remove (tag_calendar->priv->objects, old_oinfo);
575 static void
576 e_tag_calendar_data_subscriber_freeze (ECalDataModelSubscriber *subscriber)
578 /* Ignore freezes here */
581 static void
582 e_tag_calendar_data_subscriber_thaw (ECalDataModelSubscriber *subscriber)
584 /* Ignore freezes here */
587 static void
588 e_tag_calendar_set_calendar (ETagCalendar *tag_calendar,
589 ECalendar *calendar)
591 g_return_if_fail (E_IS_TAG_CALENDAR (tag_calendar));
592 g_return_if_fail (E_IS_CALENDAR (calendar));
593 g_return_if_fail (e_calendar_get_item (calendar) != NULL);
594 g_return_if_fail (tag_calendar->priv->calendar == NULL);
596 tag_calendar->priv->calendar = calendar;
597 tag_calendar->priv->calitem = e_calendar_get_item (calendar);
599 g_object_weak_ref (G_OBJECT (tag_calendar->priv->calendar),
600 (GWeakNotify) g_nullify_pointer, &tag_calendar->priv->calendar);
601 g_object_weak_ref (G_OBJECT (tag_calendar->priv->calitem),
602 (GWeakNotify) g_nullify_pointer, &tag_calendar->priv->calitem);
605 static void
606 e_tag_calendar_set_property (GObject *object,
607 guint property_id,
608 const GValue *value,
609 GParamSpec *pspec)
611 switch (property_id) {
612 case PROP_CALENDAR:
613 e_tag_calendar_set_calendar (
614 E_TAG_CALENDAR (object),
615 g_value_get_object (value));
616 return;
618 case PROP_RECUR_EVENTS_ITALIC:
619 e_tag_calendar_set_recur_events_italic (
620 E_TAG_CALENDAR (object),
621 g_value_get_boolean (value));
622 return;
625 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
628 static void
629 e_tag_calendar_get_property (GObject *object,
630 guint property_id,
631 GValue *value,
632 GParamSpec *pspec)
634 switch (property_id) {
635 case PROP_CALENDAR:
636 g_value_set_object (value,
637 e_tag_calendar_get_calendar (E_TAG_CALENDAR (object)));
638 return;
640 case PROP_RECUR_EVENTS_ITALIC:
641 g_value_set_boolean (value,
642 e_tag_calendar_get_recur_events_italic (E_TAG_CALENDAR (object)));
643 return;
646 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
649 static void
650 e_tag_calendar_constructed (GObject *object)
652 ETagCalendar *tag_calendar = E_TAG_CALENDAR (object);
653 GSettings *settings;
655 /* Chain up to parent's constructed() method. */
656 G_OBJECT_CLASS (e_tag_calendar_parent_class)->constructed (object);
658 g_return_if_fail (tag_calendar->priv->calendar != NULL);
659 g_return_if_fail (tag_calendar->priv->calitem != NULL);
661 g_signal_connect_swapped (tag_calendar->priv->calitem, "date-range-changed",
662 G_CALLBACK (e_tag_calendar_date_range_changed_cb), tag_calendar);
664 g_signal_connect (tag_calendar->priv->calendar, "query-tooltip",
665 G_CALLBACK (e_tag_calendar_query_tooltip_cb), tag_calendar);
667 gtk_widget_set_has_tooltip (GTK_WIDGET (tag_calendar->priv->calendar), TRUE);
669 settings = e_util_ref_settings ("org.gnome.evolution.calendar");
671 g_settings_bind (
672 settings, "recur-events-italic",
673 tag_calendar, "recur-events-italic",
674 G_SETTINGS_BIND_DEFAULT | G_SETTINGS_BIND_NO_SENSITIVITY);
676 g_object_unref (settings);
679 static void
680 e_tag_calendar_dispose (GObject *object)
682 ETagCalendar *tag_calendar = E_TAG_CALENDAR (object);
684 if (tag_calendar->priv->calendar != NULL) {
685 g_signal_handlers_disconnect_by_func (e_calendar_get_item (tag_calendar->priv->calendar),
686 G_CALLBACK (e_tag_calendar_date_range_changed_cb), tag_calendar);
687 g_signal_handlers_disconnect_by_func (tag_calendar->priv->calendar,
688 G_CALLBACK (e_tag_calendar_query_tooltip_cb), tag_calendar);
689 g_object_weak_unref (G_OBJECT (tag_calendar->priv->calendar),
690 (GWeakNotify) g_nullify_pointer, &tag_calendar->priv->calendar);
691 tag_calendar->priv->calendar = NULL;
694 if (tag_calendar->priv->calitem != NULL) {
695 g_object_weak_unref (G_OBJECT (tag_calendar->priv->calitem),
696 (GWeakNotify) g_nullify_pointer, &tag_calendar->priv->calitem);
697 tag_calendar->priv->calitem = NULL;
700 if (tag_calendar->priv->data_model)
701 e_tag_calendar_unsubscribe (tag_calendar, tag_calendar->priv->data_model);
703 /* Chain up to parent's dispose() method. */
704 G_OBJECT_CLASS (e_tag_calendar_parent_class)->dispose (object);
707 static void
708 e_tag_calendar_finalize (GObject *object)
710 ETagCalendar *tag_calendar = E_TAG_CALENDAR (object);
712 g_warn_if_fail (tag_calendar->priv->data_model == NULL);
714 g_hash_table_destroy (tag_calendar->priv->objects);
715 g_hash_table_destroy (tag_calendar->priv->dates);
717 /* Chain up to parent's finalize() method. */
718 G_OBJECT_CLASS (e_tag_calendar_parent_class)->finalize (object);
721 static void
722 e_tag_calendar_class_init (ETagCalendarClass *class)
724 GObjectClass *object_class;
726 g_type_class_add_private (class, sizeof (ETagCalendarPrivate));
728 object_class = G_OBJECT_CLASS (class);
729 object_class->set_property = e_tag_calendar_set_property;
730 object_class->get_property = e_tag_calendar_get_property;
731 object_class->constructed = e_tag_calendar_constructed;
732 object_class->dispose = e_tag_calendar_dispose;
733 object_class->finalize = e_tag_calendar_finalize;
735 g_object_class_install_property (
736 object_class,
737 PROP_CALENDAR,
738 g_param_spec_object (
739 "calendar",
740 "Calendar",
741 NULL,
742 E_TYPE_CALENDAR,
743 G_PARAM_READWRITE |
744 G_PARAM_CONSTRUCT_ONLY));
746 g_object_class_install_property (
747 object_class,
748 PROP_RECUR_EVENTS_ITALIC,
749 g_param_spec_boolean (
750 "recur-events-italic",
751 "Recur Events Italic",
752 NULL,
753 FALSE,
754 G_PARAM_READWRITE));
757 static void
758 e_tag_calendar_cal_data_model_subscriber_init (ECalDataModelSubscriberInterface *iface)
760 iface->component_added = e_tag_calendar_data_subscriber_component_added;
761 iface->component_modified = e_tag_calendar_data_subscriber_component_modified;
762 iface->component_removed = e_tag_calendar_data_subscriber_component_removed;
763 iface->freeze = e_tag_calendar_data_subscriber_freeze;
764 iface->thaw = e_tag_calendar_data_subscriber_thaw;
767 static void
768 e_tag_calendar_init (ETagCalendar *tag_calendar)
770 tag_calendar->priv = G_TYPE_INSTANCE_GET_PRIVATE (tag_calendar, E_TYPE_TAG_CALENDAR, ETagCalendarPrivate);
772 tag_calendar->priv->objects = g_hash_table_new_full (
773 object_info_hash,
774 object_info_equal,
775 object_info_free,
776 g_free);
778 tag_calendar->priv->dates = g_hash_table_new_full (
779 g_direct_hash,
780 g_direct_equal,
781 NULL,
782 date_info_free);
785 ETagCalendar *
786 e_tag_calendar_new (ECalendar *calendar)
788 return g_object_new (E_TYPE_TAG_CALENDAR, "calendar", calendar, NULL);
791 ECalendar *
792 e_tag_calendar_get_calendar (ETagCalendar *tag_calendar)
794 g_return_val_if_fail (E_IS_TAG_CALENDAR (tag_calendar), NULL);
796 return tag_calendar->priv->calendar;
799 gboolean
800 e_tag_calendar_get_recur_events_italic (ETagCalendar *tag_calendar)
802 g_return_val_if_fail (E_IS_TAG_CALENDAR (tag_calendar), FALSE);
804 return tag_calendar->priv->recur_events_italic;
807 void
808 e_tag_calendar_set_recur_events_italic (ETagCalendar *tag_calendar,
809 gboolean recur_events_italic)
811 g_return_if_fail (E_IS_TAG_CALENDAR (tag_calendar));
813 if ((tag_calendar->priv->recur_events_italic ? 1 : 0) == (recur_events_italic ? 1 : 0))
814 return;
816 tag_calendar->priv->recur_events_italic = recur_events_italic;
818 g_object_notify (G_OBJECT (tag_calendar), "recur-events-italic");
820 e_tag_calendar_remark_days (tag_calendar);
823 void
824 e_tag_calendar_subscribe (ETagCalendar *tag_calendar,
825 ECalDataModel *data_model)
827 g_return_if_fail (E_IS_TAG_CALENDAR (tag_calendar));
828 g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
829 g_return_if_fail (tag_calendar->priv->data_model != data_model);
831 /* if the reference is held by the priv->data_model, then
832 an unsubscribe may cause free of the tag_calendar */
833 g_object_ref (tag_calendar);
835 if (tag_calendar->priv->data_model)
836 e_tag_calendar_unsubscribe (tag_calendar, tag_calendar->priv->data_model);
838 tag_calendar->priv->data_model = data_model;
839 e_tag_calendar_date_range_changed_cb (tag_calendar);
841 g_object_unref (tag_calendar);
844 void
845 e_tag_calendar_unsubscribe (ETagCalendar *tag_calendar,
846 ECalDataModel *data_model)
848 g_return_if_fail (E_IS_TAG_CALENDAR (tag_calendar));
849 g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model));
850 g_return_if_fail (tag_calendar->priv->data_model == data_model);
852 e_cal_data_model_unsubscribe (data_model, E_CAL_DATA_MODEL_SUBSCRIBER (tag_calendar));
853 tag_calendar->priv->data_model = NULL;
855 /* calitem can be NULL during dispose of an ECalBaseShellContents */
856 if (tag_calendar->priv->calitem)
857 e_calendar_item_clear_marks (tag_calendar->priv->calitem);
859 g_hash_table_remove_all (tag_calendar->priv->objects);
860 g_hash_table_remove_all (tag_calendar->priv->dates);
863 struct calendar_tag_closure {
864 ECalendarItem *calitem;
865 icaltimezone *zone;
866 time_t start_time;
867 time_t end_time;
869 gboolean skip_transparent_events;
870 gboolean recur_events_italic;
873 /* Clears all the tags in a calendar and fills a closure structure with the
874 * necessary information for iterating over occurrences. Returns FALSE if
875 * the calendar has no dates shown. */
876 static gboolean
877 prepare_tag (ECalendar *ecal,
878 struct calendar_tag_closure *closure,
879 icaltimezone *zone,
880 gboolean clear_first)
882 gint start_year, start_month, start_day;
883 gint end_year, end_month, end_day;
884 struct icaltimetype start_tt = icaltime_null_time ();
885 struct icaltimetype end_tt = icaltime_null_time ();
887 if (clear_first)
888 e_calendar_item_clear_marks (e_calendar_get_item (ecal));
890 if (!e_calendar_item_get_date_range (
891 e_calendar_get_item (ecal),
892 &start_year, &start_month, &start_day,
893 &end_year, &end_month, &end_day))
894 return FALSE;
896 start_tt.year = start_year;
897 start_tt.month = start_month + 1;
898 start_tt.day = start_day;
900 end_tt.year = end_year;
901 end_tt.month = end_month + 1;
902 end_tt.day = end_day;
904 icaltime_adjust (&end_tt, 1, 0, 0, 0);
906 closure->calitem = e_calendar_get_item (ecal);
908 if (zone != NULL)
909 closure->zone = zone;
910 else
911 closure->zone = calendar_config_get_icaltimezone ();
913 closure->start_time =
914 icaltime_as_timet_with_zone (start_tt, closure->zone);
915 closure->end_time =
916 icaltime_as_timet_with_zone (end_tt, closure->zone);
918 return TRUE;
921 /* Marks the specified range in an ECalendar;
922 * called from e_cal_generate_instances() */
923 static gboolean
924 tag_calendar_cb (ECalComponent *comp,
925 time_t istart,
926 time_t iend,
927 struct calendar_tag_closure *closure)
929 struct icaltimetype start_tt, end_tt;
930 ECalComponentTransparency transparency;
931 guint8 style = 0;
933 /* If we are skipping TRANSPARENT events, return if the event is
934 * transparent. */
935 e_cal_component_get_transparency (comp, &transparency);
936 if (transparency == E_CAL_COMPONENT_TRANSP_TRANSPARENT) {
937 if (closure->skip_transparent_events)
938 return TRUE;
940 style = E_CALENDAR_ITEM_MARK_ITALIC;
941 } else if (closure->recur_events_italic && e_cal_component_is_instance (comp)) {
942 style = E_CALENDAR_ITEM_MARK_ITALIC;
943 } else {
944 style = E_CALENDAR_ITEM_MARK_BOLD;
947 start_tt = icaltime_from_timet_with_zone (istart, FALSE, closure->zone);
948 end_tt = icaltime_from_timet_with_zone (iend - 1, FALSE, closure->zone);
950 e_calendar_item_mark_days (
951 closure->calitem,
952 start_tt.year, start_tt.month - 1, start_tt.day,
953 end_tt.year, end_tt.month - 1, end_tt.day,
954 style, TRUE);
956 return TRUE;
959 /* Resolves TZIDs for the recurrence generator, for when the comp is not on
960 * the server. We need to try to use builtin timezones first, as they may not
961 * be added to the server yet. */
962 static icaltimezone *
963 resolve_tzid_cb (const gchar *tzid,
964 ECalClient *client)
966 icaltimezone *zone = NULL;
968 /* Try to find the builtin timezone first. */
969 zone = icaltimezone_get_builtin_timezone_from_tzid (tzid);
971 if (!zone && tzid) {
972 /* FIXME: Handle errors. */
973 GError *error = NULL;
975 e_cal_client_get_timezone_sync (
976 client, tzid, &zone, NULL, &error);
978 if (error != NULL) {
979 g_warning (
980 "%s: Failed to get timezone '%s': %s",
981 G_STRFUNC, tzid, error->message);
982 g_error_free (error);
986 return zone;
990 * tag_calendar_by_comp:
991 * @ecal: Calendar widget to tag.
992 * @comp: A calendar component object.
993 * @clear_first: Whether the #ECalendar should be cleared of any marks first.
995 * Tags an #ECalendar widget with any occurrences of a specific calendar
996 * component that occur within the calendar's current time range.
997 * Note that TRANSPARENT events are also tagged here.
999 * If comp_is_on_server is FALSE, it will try to resolve TZIDs using builtin
1000 * timezones first, before querying the server, since the timezones may not
1001 * have been added to the calendar on the server yet.
1003 void
1004 tag_calendar_by_comp (ECalendar *ecal,
1005 ECalComponent *comp,
1006 ECalClient *client,
1007 icaltimezone *display_zone,
1008 gboolean clear_first,
1009 gboolean comp_is_on_server,
1010 gboolean can_recur_events_italic,
1011 GCancellable *cancellable)
1013 GSettings *settings;
1014 struct calendar_tag_closure closure;
1016 g_return_if_fail (E_IS_CALENDAR (ecal));
1017 g_return_if_fail (E_IS_CAL_COMPONENT (comp));
1019 /* If the ECalendar isn't visible, we just return. */
1020 if (!gtk_widget_get_visible (GTK_WIDGET (ecal)))
1021 return;
1023 if (!prepare_tag (ecal, &closure, display_zone, clear_first))
1024 return;
1026 settings = e_util_ref_settings ("org.gnome.evolution.calendar");
1028 closure.skip_transparent_events = FALSE;
1029 closure.recur_events_italic =
1030 can_recur_events_italic &&
1031 g_settings_get_boolean (settings, "recur-events-italic");
1033 g_object_unref (settings);
1035 if (comp_is_on_server) {
1036 struct calendar_tag_closure *alloced_closure;
1038 alloced_closure = g_new0 (struct calendar_tag_closure, 1);
1040 *alloced_closure = closure;
1042 e_cal_client_generate_instances_for_object (
1043 client, e_cal_component_get_icalcomponent (comp),
1044 closure.start_time, closure.end_time, cancellable,
1045 (ECalRecurInstanceFn) tag_calendar_cb,
1046 alloced_closure, (GDestroyNotify) g_free);
1047 } else
1048 e_cal_recur_generate_instances (
1049 comp, closure.start_time, closure.end_time,
1050 (ECalRecurInstanceFn) tag_calendar_cb,
1051 &closure,
1052 (ECalRecurResolveTimezoneFn) resolve_tzid_cb,
1053 client, closure.zone);