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