1 /*
2  * This program is free software; you can redistribute it and/or modify it
3  * under the terms of the GNU Lesser General Public License as published by
4  * the Free Software Foundation.
5  *
6  * This program is distributed in the hope that it will be useful, but
7  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
8  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
9  * for more details.
10  *
11  * You should have received a copy of the GNU Lesser General Public License
12  * along with this program; if not, see <http://www.gnu.org/licenses/>.
13  *
14  *
15  * Authors:
16  *		Rodrigo Moya <rodrigo@ximian.com>
17  *
18  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
19  *
20  */
21 
22 #include "evolution-config.h"
23 
24 #include <string.h>
25 #include <time.h>
26 #include <gtk/gtk.h>
27 #include <glib/gi18n.h>
28 #include <glib/gstdio.h>
29 #include <gdk/gdkkeysyms.h>
30 #include <libebackend/libebackend.h>
31 
32 #include <shell/e-shell.h>
33 
34 #include "comp-util.h"
35 #include "ea-cal-view.h"
36 #include "ea-calendar.h"
37 #include "e-cal-dialogs.h"
38 #include "e-cal-list-view.h"
39 #include "e-cal-model-calendar.h"
40 #include "e-cal-ops.h"
41 #include "e-calendar-view.h"
42 #include "e-day-view.h"
43 #include "e-month-view.h"
44 #include "itip-utils.h"
45 #include "misc.h"
46 #include "print.h"
47 
48 #define E_CALENDAR_VIEW_GET_PRIVATE(obj) \
49 	(G_TYPE_INSTANCE_GET_PRIVATE \
50 	((obj), E_TYPE_CALENDAR_VIEW, ECalendarViewPrivate))
51 
52 struct _ECalendarViewPrivate {
53 	/* The calendar model we are monitoring */
54 	ECalModel *model;
55 
56 	gint time_divisions;
57 	GSList *selected_cut_list;
58 
59 	GtkTargetList *copy_target_list;
60 	GtkTargetList *paste_target_list;
61 
62 	gboolean allow_direct_summary_edit;
63 };
64 
65 enum {
66 	PROP_0,
67 	PROP_COPY_TARGET_LIST,
68 	PROP_MODEL,
69 	PROP_PASTE_TARGET_LIST,
70 	PROP_TIME_DIVISIONS,
71 	PROP_IS_EDITING,
72 	PROP_ALLOW_DIRECT_SUMMARY_EDIT
73 };
74 
75 /* FIXME Why are we emitting these event signals here? Can't the model just be listened to? */
76 /* Signal IDs */
77 enum {
78 	POPUP_EVENT,
79 	SELECTION_CHANGED,
80 	SELECTED_TIME_CHANGED,
81 	TIMEZONE_CHANGED,
82 	EVENT_CHANGED,
83 	EVENT_ADDED,
84 	OPEN_EVENT,
85 	MOVE_VIEW_RANGE,
86 	LAST_SIGNAL
87 };
88 
89 static guint signals[LAST_SIGNAL];
90 
91 static void calendar_view_selectable_init (ESelectableInterface *iface);
92 
93 G_DEFINE_ABSTRACT_TYPE_WITH_CODE (
94 	ECalendarView, e_calendar_view, GTK_TYPE_GRID,
95 	G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE, NULL)
96 	G_IMPLEMENT_INTERFACE (E_TYPE_SELECTABLE, calendar_view_selectable_init));
97 
98 static void
calendar_view_add_retract_data(ECalComponent * comp,const gchar * retract_comment,ECalObjModType mod)99 calendar_view_add_retract_data (ECalComponent *comp,
100                                 const gchar *retract_comment,
101                                 ECalObjModType mod)
102 {
103 	ICalComponent *icomp;
104 	ICalProperty *prop;
105 
106 	icomp = e_cal_component_get_icalcomponent (comp);
107 	if (retract_comment && *retract_comment)
108 		prop = i_cal_property_new_x (retract_comment);
109 	else
110 		prop = i_cal_property_new_x ("0");
111 	i_cal_property_set_x_name (prop, "X-EVOLUTION-RETRACT-COMMENT");
112 	i_cal_component_take_property (icomp, prop);
113 
114 	if (mod == E_CAL_OBJ_MOD_ALL)
115 		prop = i_cal_property_new_x ("All");
116 	else if (mod == E_CAL_OBJ_MOD_THIS_AND_FUTURE)
117 		prop = i_cal_property_new_x ("ThisAndFuture");
118 	else
119 		prop = i_cal_property_new_x ("This");
120 	i_cal_property_set_x_name (prop, "X-EVOLUTION-RECUR-MOD");
121 	i_cal_component_take_property (icomp, prop);
122 }
123 
124 static gboolean
calendar_view_check_for_retract(ECalComponent * comp,ECalClient * client)125 calendar_view_check_for_retract (ECalComponent *comp,
126                                  ECalClient *client)
127 {
128 	ECalComponentOrganizer *organizer;
129 	const gchar *strip;
130 	gchar *email = NULL;
131 	gboolean ret_val;
132 
133 	if (!e_cal_component_has_attendees (comp))
134 		return FALSE;
135 
136 	if (!e_cal_client_check_save_schedules (client))
137 		return FALSE;
138 
139 	organizer = e_cal_component_get_organizer (comp);
140 	if (!organizer)
141 		return FALSE;
142 
143 	strip = itip_strip_mailto (e_cal_component_organizer_get_value (organizer));
144 
145 	ret_val =
146 		e_client_get_backend_property_sync (E_CLIENT (client), E_CAL_BACKEND_PROPERTY_CAL_EMAIL_ADDRESS, &email, NULL, NULL) &&
147 		(g_ascii_strcasecmp (email, strip) == 0);
148 
149 	g_free (email);
150 
151 	e_cal_component_organizer_free (organizer);
152 
153 	return ret_val;
154 }
155 
156 static void
calendar_view_delete_event(ECalendarView * cal_view,ECalendarViewEvent * event,gboolean only_occurrence,ECalObjModType mod)157 calendar_view_delete_event (ECalendarView *cal_view,
158                             ECalendarViewEvent *event,
159 			    gboolean only_occurrence,
160 			    ECalObjModType mod)
161 {
162 	ECalModel *model;
163 	ECalComponent *comp;
164 	ECalComponentVType vtype;
165 	ESourceRegistry *registry;
166 	ECalClient *client;
167 	ICalComponent *icalcomp;
168 	time_t instance_start;
169 	gboolean delete = TRUE;
170 
171 	if (!is_comp_data_valid (event))
172 		return;
173 
174 	model = e_calendar_view_get_model (cal_view);
175 	registry = e_cal_model_get_registry (model);
176 
177 	comp = e_cal_component_new ();
178 	e_cal_component_set_icalcomponent (comp, i_cal_component_clone (event->comp_data->icalcomp));
179 	vtype = e_cal_component_get_vtype (comp);
180 
181 	/* Remember structure values, because the 'event' can be freed while the question dialog is opened */
182 	instance_start = event->comp_data->instance_start;
183 	client = g_object_ref (event->comp_data->client);
184 	icalcomp = e_cal_component_get_icalcomponent (comp);
185 
186 	/*FIXME remove it once the we dont set the recurrence id for all the generated instances */
187 	if (!only_occurrence && !e_cal_client_check_recurrences_no_master (client))
188 		e_cal_component_set_recurid (comp, NULL);
189 
190 	/*FIXME Retract should be moved to Groupwise features plugin */
191 	if (calendar_view_check_for_retract (comp, client)) {
192 		gchar *retract_comment = NULL;
193 		gboolean retract = FALSE;
194 
195 		delete = e_cal_dialogs_prompt_retract (GTK_WIDGET (cal_view), comp, &retract_comment, &retract);
196 		if (retract) {
197 			ICalComponent *icomp;
198 
199 			calendar_view_add_retract_data (comp, retract_comment, mod);
200 			icomp = e_cal_component_get_icalcomponent (comp);
201 			i_cal_component_set_method (icomp, I_CAL_METHOD_CANCEL);
202 
203 			e_cal_ops_send_component (model, client, icomp);
204 		}
205 	} else if (e_cal_model_get_confirm_delete (model))
206 		delete = e_cal_dialogs_delete_component (
207 			comp, FALSE, 1, vtype, GTK_WIDGET (cal_view));
208 
209 	if (delete) {
210 		const gchar *uid;
211 		gchar *rid;
212 
213 		rid = e_cal_component_get_recurid_as_string (comp);
214 
215 		if (itip_has_any_attendees (comp) &&
216 		    (itip_organizer_is_user (registry, comp, client) ||
217 		     itip_sentby_is_user (registry, comp, client))
218 		    && e_cal_dialogs_cancel_component ((GtkWindow *) gtk_widget_get_toplevel (GTK_WIDGET (cal_view)),
219 						client,
220 						comp, TRUE)) {
221 			if (only_occurrence && !e_cal_component_is_instance (comp)) {
222 				ECalComponentRange *range;
223 				ECalComponentDateTime *dtstart;
224 
225 				dtstart = e_cal_component_get_dtstart (comp);
226 				i_cal_time_set_is_date (e_cal_component_datetime_get_value (dtstart), 1);
227 
228 				/* set the recurrence ID of the object we send */
229 				range = e_cal_component_range_new_take (mod == E_CAL_OBJ_MOD_THIS_AND_FUTURE ?
230 					E_CAL_COMPONENT_RANGE_THISFUTURE : E_CAL_COMPONENT_RANGE_SINGLE, dtstart);
231 				e_cal_component_set_recurid (comp, range);
232 
233 				e_cal_component_range_free (range);
234 			} else if (only_occurrence && mod == E_CAL_OBJ_MOD_THIS_AND_FUTURE) {
235 				ECalComponentRange *range;
236 
237 				range = e_cal_component_get_recurid (comp);
238 				e_cal_component_range_set_kind (range, E_CAL_COMPONENT_RANGE_THISFUTURE);
239 				e_cal_component_set_recurid (comp, range);
240 				e_cal_component_range_free (range);
241 			}
242 
243 			itip_send_component_with_model (model, I_CAL_METHOD_CANCEL,
244 				comp, client, NULL, NULL,
245 				NULL, E_ITIP_SEND_COMPONENT_FLAG_STRIP_ALARMS);
246 		}
247 
248 		uid = e_cal_component_get_uid (comp);
249 		if (!uid || !*uid) {
250 			g_object_unref (comp);
251 			g_free (rid);
252 			return;
253 		}
254 
255 		if (only_occurrence) {
256 			if (e_cal_component_is_instance (comp)) {
257 				e_cal_ops_remove_component (model, client, uid, rid, mod, FALSE);
258 			} else {
259 				ICalTime *instance_rid;
260 				ICalTimezone *zone = NULL;
261 				ECalComponentDateTime *dt;
262 
263 				dt = e_cal_component_get_dtstart (comp);
264 
265 				if (dt && e_cal_component_datetime_get_tzid (dt)) {
266 					GError *local_error = NULL;
267 
268 					if (!e_cal_client_get_timezone_sync (client,
269 						e_cal_component_datetime_get_tzid (dt), &zone, NULL, &local_error))
270 						zone = NULL;
271 
272 					if (local_error != NULL) {
273 						zone = e_calendar_view_get_timezone (cal_view);
274 						g_clear_error (&local_error);
275 					}
276 				} else {
277 					zone = e_calendar_view_get_timezone (cal_view);
278 				}
279 
280 				e_cal_component_datetime_free (dt);
281 
282 				instance_rid = i_cal_time_new_from_timet_with_zone (
283 					instance_start,
284 					TRUE, zone ? zone : i_cal_timezone_get_utc_timezone ());
285 				e_cal_util_remove_instances_ex (icalcomp, instance_rid, mod,
286 					e_cal_client_tzlookup_cb, client);
287 				e_cal_ops_modify_component (model, client, icalcomp,
288 					E_CAL_OBJ_MOD_THIS, E_CAL_OPS_SEND_FLAG_DONT_SEND);
289 
290 				g_clear_object (&instance_rid);
291 			}
292 		} else if (e_cal_util_component_is_instance (icalcomp) ||
293 			   e_cal_util_component_has_recurrences (icalcomp))
294 			e_cal_ops_remove_component (model, client, uid, rid, E_CAL_OBJ_MOD_ALL, FALSE);
295 		else
296 			e_cal_ops_remove_component (model, client, uid, NULL, E_CAL_OBJ_MOD_THIS, FALSE);
297 
298 		g_free (rid);
299 	}
300 
301 	g_clear_object (&client);
302 	g_object_unref (comp);
303 }
304 
305 static void
calendar_view_set_model(ECalendarView * calendar_view,ECalModel * model)306 calendar_view_set_model (ECalendarView *calendar_view,
307                          ECalModel *model)
308 {
309 	g_return_if_fail (calendar_view->priv->model == NULL);
310 	g_return_if_fail (E_IS_CAL_MODEL (model));
311 
312 	calendar_view->priv->model = g_object_ref (model);
313 }
314 
315 static void
calendar_view_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)316 calendar_view_set_property (GObject *object,
317                             guint property_id,
318                             const GValue *value,
319                             GParamSpec *pspec)
320 {
321 	switch (property_id) {
322 		case PROP_MODEL:
323 			calendar_view_set_model (
324 				E_CALENDAR_VIEW (object),
325 				g_value_get_object (value));
326 			return;
327 
328 		case PROP_TIME_DIVISIONS:
329 			e_calendar_view_set_time_divisions (
330 				E_CALENDAR_VIEW (object),
331 				g_value_get_int (value));
332 			return;
333 
334 		case PROP_ALLOW_DIRECT_SUMMARY_EDIT:
335 			e_calendar_view_set_allow_direct_summary_edit (
336 				E_CALENDAR_VIEW (object),
337 				g_value_get_boolean (value));
338 			return;
339 	}
340 
341 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
342 }
343 
344 static void
calendar_view_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)345 calendar_view_get_property (GObject *object,
346                             guint property_id,
347                             GValue *value,
348                             GParamSpec *pspec)
349 {
350 	switch (property_id) {
351 		case PROP_COPY_TARGET_LIST:
352 			g_value_set_boxed (
353 				value, e_calendar_view_get_copy_target_list (
354 				E_CALENDAR_VIEW (object)));
355 			return;
356 
357 		case PROP_MODEL:
358 			g_value_set_object (
359 				value, e_calendar_view_get_model (
360 				E_CALENDAR_VIEW (object)));
361 			return;
362 
363 		case PROP_PASTE_TARGET_LIST:
364 			g_value_set_boxed (
365 				value, e_calendar_view_get_paste_target_list (
366 				E_CALENDAR_VIEW (object)));
367 			return;
368 
369 		case PROP_TIME_DIVISIONS:
370 			g_value_set_int (
371 				value, e_calendar_view_get_time_divisions (
372 				E_CALENDAR_VIEW (object)));
373 			return;
374 
375 		case PROP_IS_EDITING:
376 			g_value_set_boolean (value, e_calendar_view_is_editing (E_CALENDAR_VIEW (object)));
377 			return;
378 
379 		case PROP_ALLOW_DIRECT_SUMMARY_EDIT:
380 			g_value_set_boolean (value, e_calendar_view_get_allow_direct_summary_edit (E_CALENDAR_VIEW (object)));
381 			return;
382 	}
383 
384 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
385 }
386 
387 static void
calendar_view_dispose(GObject * object)388 calendar_view_dispose (GObject *object)
389 {
390 	ECalendarViewPrivate *priv;
391 
392 	priv = E_CALENDAR_VIEW_GET_PRIVATE (object);
393 
394 	if (priv->model != NULL) {
395 		g_signal_handlers_disconnect_matched (
396 			priv->model, G_SIGNAL_MATCH_DATA,
397 			0, 0, NULL, NULL, object);
398 		g_object_unref (priv->model);
399 		priv->model = NULL;
400 	}
401 
402 	g_clear_pointer (&priv->copy_target_list, gtk_target_list_unref);
403 	g_clear_pointer (&priv->paste_target_list, gtk_target_list_unref);
404 
405 	if (priv->selected_cut_list) {
406 		g_slist_foreach (priv->selected_cut_list, (GFunc) g_object_unref, NULL);
407 		g_slist_free (priv->selected_cut_list);
408 		priv->selected_cut_list = NULL;
409 	}
410 
411 	/* Chain up to parent's dispose() method. */
412 	G_OBJECT_CLASS (e_calendar_view_parent_class)->dispose (object);
413 }
414 
415 static gboolean
calendar_view_key_press_event_cb(GtkWidget * view,GdkEvent * key_event,gpointer user_data)416 calendar_view_key_press_event_cb (GtkWidget *view,
417 				  GdkEvent *key_event,
418 				  gpointer user_data)
419 {
420 	e_calendar_view_destroy_tooltip (E_CALENDAR_VIEW (view));
421 
422 	return FALSE;
423 }
424 
425 static void
calendar_view_constructed(GObject * object)426 calendar_view_constructed (GObject *object)
427 {
428 	/* Chain up to parent's method. */
429 	G_OBJECT_CLASS (e_calendar_view_parent_class)->constructed (object);
430 
431 	/* Do this after calendar_view_init() so extensions can query
432 	 * the GType accurately.  See GInstanceInitFunc documentation
433 	 * for details of the problem. */
434 	e_extensible_load_extensions (E_EXTENSIBLE (object));
435 
436 	g_signal_connect (object, "key-press-event",
437 		G_CALLBACK (calendar_view_key_press_event_cb), NULL);
438 }
439 
440 static void
calendar_view_update_actions(ESelectable * selectable,EFocusTracker * focus_tracker,GdkAtom * clipboard_targets,gint n_clipboard_targets)441 calendar_view_update_actions (ESelectable *selectable,
442                               EFocusTracker *focus_tracker,
443                               GdkAtom *clipboard_targets,
444                               gint n_clipboard_targets)
445 {
446 	ECalendarView *view;
447 	GtkAction *action;
448 	GtkTargetList *target_list;
449 	GList *list, *iter;
450 	gboolean can_paste = FALSE;
451 	gboolean sources_are_editable = TRUE;
452 	gboolean recurring = FALSE;
453 	gboolean is_editing;
454 	gboolean sensitive;
455 	const gchar *tooltip;
456 	gint n_selected;
457 	gint ii;
458 
459 	view = E_CALENDAR_VIEW (selectable);
460 	is_editing = e_calendar_view_is_editing (view);
461 
462 	list = e_calendar_view_get_selected_events (view);
463 	n_selected = g_list_length (list);
464 
465 	for (iter = list; iter != NULL; iter = iter->next) {
466 		ECalendarViewEvent *event = iter->data;
467 		ECalClient *client;
468 		ICalComponent *icomp;
469 
470 		if (event == NULL || event->comp_data == NULL)
471 			continue;
472 
473 		client = event->comp_data->client;
474 		icomp = event->comp_data->icalcomp;
475 
476 		sources_are_editable = sources_are_editable && !e_client_is_readonly (E_CLIENT (client));
477 
478 		recurring |=
479 			e_cal_util_component_is_instance (icomp) ||
480 			e_cal_util_component_has_recurrences (icomp);
481 	}
482 
483 	g_list_free (list);
484 
485 	target_list = e_selectable_get_paste_target_list (selectable);
486 	for (ii = 0; ii < n_clipboard_targets && !can_paste; ii++)
487 		can_paste = gtk_target_list_find (
488 			target_list, clipboard_targets[ii], NULL);
489 
490 	action = e_focus_tracker_get_cut_clipboard_action (focus_tracker);
491 	sensitive = (n_selected > 0) && sources_are_editable && !is_editing;
492 	tooltip = _("Cut selected events to the clipboard");
493 	gtk_action_set_sensitive (action, sensitive);
494 	gtk_action_set_tooltip (action, tooltip);
495 
496 	action = e_focus_tracker_get_copy_clipboard_action (focus_tracker);
497 	sensitive = (n_selected > 0) && !is_editing;
498 	tooltip = _("Copy selected events to the clipboard");
499 	gtk_action_set_sensitive (action, sensitive);
500 	gtk_action_set_tooltip (action, tooltip);
501 
502 	action = e_focus_tracker_get_paste_clipboard_action (focus_tracker);
503 	sensitive = sources_are_editable && can_paste && !is_editing;
504 	tooltip = _("Paste events from the clipboard");
505 	gtk_action_set_sensitive (action, sensitive);
506 	gtk_action_set_tooltip (action, tooltip);
507 
508 	action = e_focus_tracker_get_delete_selection_action (focus_tracker);
509 	sensitive = (n_selected > 0) && sources_are_editable && !recurring && !is_editing;
510 	tooltip = _("Delete selected events");
511 	gtk_action_set_sensitive (action, sensitive);
512 	gtk_action_set_tooltip (action, tooltip);
513 }
514 
515 static void
calendar_view_cut_clipboard(ESelectable * selectable)516 calendar_view_cut_clipboard (ESelectable *selectable)
517 {
518 	ECalendarView *cal_view;
519 	ECalendarViewPrivate *priv;
520 	GList *selected, *l;
521 
522 	cal_view = E_CALENDAR_VIEW (selectable);
523 	priv = cal_view->priv;
524 
525 	selected = e_calendar_view_get_selected_events (cal_view);
526 	if (!selected)
527 		return;
528 
529 	e_selectable_copy_clipboard (selectable);
530 
531 	for (l = selected; l != NULL; l = g_list_next (l)) {
532 		ECalendarViewEvent *event = (ECalendarViewEvent *) l->data;
533 
534 		priv->selected_cut_list = g_slist_prepend (priv->selected_cut_list, g_object_ref (event->comp_data));
535 	}
536 
537 	g_list_free (selected);
538 }
539 
540 static void
add_related_timezones(ICalComponent * des_icomp,ICalComponent * src_icomp,ECalClient * client)541 add_related_timezones (ICalComponent *des_icomp,
542 		       ICalComponent *src_icomp,
543 		       ECalClient *client)
544 {
545 	ICalPropertyKind look_in[] = {
546 		I_CAL_DTSTART_PROPERTY,
547 		I_CAL_DTEND_PROPERTY,
548 		I_CAL_NO_PROPERTY
549 	};
550 	gint ii;
551 
552 	g_return_if_fail (des_icomp != NULL);
553 	g_return_if_fail (src_icomp != NULL);
554 	g_return_if_fail (client != NULL);
555 
556 	for (ii = 0; look_in[ii] != I_CAL_NO_PROPERTY; ii++) {
557 		ICalProperty *prop = i_cal_component_get_first_property (src_icomp, look_in[ii]);
558 
559 		if (prop) {
560 			ICalParameter *par = i_cal_property_get_first_parameter (prop, I_CAL_TZID_PARAMETER);
561 
562 			if (par) {
563 				const gchar *tzid = i_cal_parameter_get_tzid (par);
564 
565 				if (tzid) {
566 					GError *error = NULL;
567 					ICalTimezone *zone = NULL;
568 
569 					if (!e_cal_client_get_timezone_sync (client, tzid, &zone, NULL, &error))
570 						zone = NULL;
571 					if (error != NULL) {
572 						g_warning (
573 							"%s: Cannot get timezone for '%s'. %s",
574 							G_STRFUNC, tzid, error->message);
575 						g_error_free (error);
576 					} else if (zone) {
577 						ICalTimezone *existing_zone;
578 
579 						/* do not duplicate timezones in the component */
580 						existing_zone = i_cal_component_get_timezone (des_icomp, i_cal_timezone_get_tzid (zone));
581 						if (existing_zone) {
582 							g_object_unref (existing_zone);
583 						} else {
584 							ICalComponent *vtz_comp;
585 
586 							vtz_comp = i_cal_timezone_get_component (zone);
587 							if (vtz_comp) {
588 								i_cal_component_take_component (des_icomp, i_cal_component_clone (vtz_comp));
589 								g_object_unref (vtz_comp);
590 							}
591 						}
592 					}
593 				}
594 
595 				g_object_unref (par);
596 			}
597 
598 			g_object_unref (prop);
599 		}
600 	}
601 }
602 
603 static void
calendar_view_copy_clipboard(ESelectable * selectable)604 calendar_view_copy_clipboard (ESelectable *selectable)
605 {
606 	ECalendarView *cal_view;
607 	ECalendarViewPrivate *priv;
608 	GList *selected, *l;
609 	gchar *comp_str;
610 	ICalComponent *vcal_comp;
611 	ECalendarViewEvent *event;
612 	GtkClipboard *clipboard;
613 
614 	cal_view = E_CALENDAR_VIEW (selectable);
615 	priv = cal_view->priv;
616 
617 	selected = e_calendar_view_get_selected_events (cal_view);
618 	if (!selected)
619 		return;
620 
621 	if (priv->selected_cut_list) {
622 		g_slist_foreach (priv->selected_cut_list, (GFunc) g_object_unref, NULL);
623 		g_slist_free (priv->selected_cut_list);
624 		priv->selected_cut_list = NULL;
625 	}
626 
627 	/* create top-level VCALENDAR component and add VTIMEZONE's */
628 	vcal_comp = e_cal_util_new_top_level ();
629 	for (l = selected; l != NULL; l = l->next) {
630 		event = (ECalendarViewEvent *) l->data;
631 
632 		if (event && is_comp_data_valid (event)) {
633 			e_cal_util_add_timezones_from_component (vcal_comp, event->comp_data->icalcomp);
634 
635 			add_related_timezones (vcal_comp, event->comp_data->icalcomp, event->comp_data->client);
636 		}
637 	}
638 
639 	for (l = selected; l != NULL; l = l->next) {
640 		ICalComponent *new_icomp;
641 
642 		event = (ECalendarViewEvent *) l->data;
643 
644 		if (!is_comp_data_valid (event))
645 			continue;
646 
647 		new_icomp = i_cal_component_clone (event->comp_data->icalcomp);
648 
649 		/* do not remove RECURRENCE-IDs from copied objects */
650 		i_cal_component_take_component (vcal_comp, new_icomp);
651 	}
652 
653 	comp_str = i_cal_component_as_ical_string (vcal_comp);
654 
655 	/* copy the VCALENDAR to the clipboard */
656 	clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
657 	e_clipboard_set_calendar (clipboard, comp_str, -1);
658 	gtk_clipboard_store (clipboard);
659 
660 	/* free memory */
661 	g_object_unref (vcal_comp);
662 	g_free (comp_str);
663 	g_list_free (selected);
664 }
665 
666 static void
calendar_view_component_created_cb(ECalModel * model,ECalClient * client,ICalComponent * original_icomp,const gchar * new_uid,gpointer user_data)667 calendar_view_component_created_cb (ECalModel *model,
668 				    ECalClient *client,
669 				    ICalComponent *original_icomp,
670 				    const gchar *new_uid,
671 				    gpointer user_data)
672 {
673 	gboolean strip_alarms = TRUE;
674 	ECalComponent *comp;
675 	ESourceRegistry *registry;
676 	GtkWidget *toplevel = user_data;
677 
678 	comp = e_cal_component_new_from_icalcomponent (i_cal_component_clone (original_icomp));
679 	g_return_if_fail (comp != NULL);
680 
681 	registry = e_cal_model_get_registry (model);
682 
683 	if (new_uid)
684 		e_cal_component_set_uid (comp, new_uid);
685 
686 	if (itip_has_any_attendees (comp) &&
687 	    (itip_organizer_is_user (registry, comp, client) ||
688 	     itip_sentby_is_user (registry, comp, client)) &&
689 	     e_cal_dialogs_send_component ((GtkWindow *) toplevel, client, comp, TRUE, &strip_alarms, NULL)) {
690 		itip_send_component_with_model (model, I_CAL_METHOD_REQUEST,
691 			comp, client, NULL, NULL, NULL, (strip_alarms ? E_ITIP_SEND_COMPONENT_FLAG_STRIP_ALARMS : 0));
692 	}
693 
694 	g_object_unref (comp);
695 }
696 
697 static void
e_calendar_view_add_event_sync(ECalModel * model,ECalClient * client,time_t dtstart,ICalTimezone * default_zone,ICalComponent * icomp,gboolean all_day,gboolean is_day_view,gint time_division,GtkWidget * top_level)698 e_calendar_view_add_event_sync (ECalModel *model,
699 				ECalClient *client,
700 				time_t dtstart,
701 				ICalTimezone *default_zone,
702 				ICalComponent *icomp,
703 				gboolean all_day,
704 				gboolean is_day_view,
705 				gint time_division,
706 				GtkWidget *top_level)
707 {
708 	ECalComponent *comp;
709 	ICalTime *itime, *btime, *old_dtstart, *old_dtend;
710 	ICalDuration *ic_dur, *ic_oneday;
711 	ICalTimezone *old_dtstart_zone;
712 	time_t tt_start, tt_end, new_dtstart = 0;
713 	gchar *uid;
714 	gint start_offset, end_offset;
715 	gboolean all_day_event = FALSE;
716 
717 	start_offset = 0;
718 	end_offset = 0;
719 
720 	old_dtstart = i_cal_component_get_dtstart (icomp);
721 	tt_start = i_cal_time_as_timet (old_dtstart);
722 	old_dtend = i_cal_component_get_dtend (icomp);
723 	tt_end = i_cal_time_as_timet (old_dtend);
724 	ic_dur = i_cal_duration_new_from_int (tt_end - tt_start);
725 
726 	if (i_cal_duration_as_int (ic_dur) > 60 * 60 * 24) {
727 		/* This is a long event */
728 		start_offset = i_cal_time_get_hour (old_dtstart) * 60 + i_cal_time_get_minute (old_dtstart);
729 		end_offset = i_cal_time_get_hour (old_dtstart) * 60 + i_cal_time_get_minute (old_dtend);
730 	}
731 
732 	ic_oneday = i_cal_duration_new_null_duration ();
733 	i_cal_duration_set_days (ic_oneday, 1);
734 
735 	old_dtstart_zone = i_cal_time_get_timezone (old_dtstart);
736 	if (!old_dtstart_zone)
737 		old_dtstart_zone = default_zone;
738 
739 	if (is_day_view) {
740 		if (start_offset == 0 && end_offset == 0 && all_day)
741 			all_day_event = TRUE;
742 
743 		if (all_day_event) {
744 			g_clear_object (&ic_dur);
745 			ic_dur = g_object_ref (ic_oneday);
746 		} else if (i_cal_duration_as_int (ic_dur) >= 60 * 60 * 24 && !all_day) {
747 			g_clear_object (&ic_dur);
748 			/* copy & paste from top canvas to main canvas */
749 			ic_dur = i_cal_duration_new_from_int (time_division * 60);
750 		}
751 
752 		if (all_day)
753 			new_dtstart = dtstart + start_offset * 60;
754 		else
755 			new_dtstart = dtstart;
756 	} else {
757 		if (i_cal_time_is_date (old_dtstart) && i_cal_time_is_date (old_dtend) &&
758 		    i_cal_duration_as_int (ic_dur) == i_cal_duration_as_int (ic_oneday)) {
759 			all_day_event = TRUE;
760 			new_dtstart = dtstart;
761 		} else {
762 			ICalTime *new_time = i_cal_time_new_from_timet_with_zone (dtstart, FALSE, default_zone);
763 
764 			i_cal_time_set_hour (new_time, i_cal_time_get_hour (old_dtstart));
765 			i_cal_time_set_minute (new_time, i_cal_time_get_minute (old_dtstart));
766 			i_cal_time_set_second (new_time, i_cal_time_get_second (old_dtstart));
767 
768 			new_dtstart = i_cal_time_as_timet_with_zone (new_time, old_dtstart_zone);
769 
770 			g_clear_object (&new_time);
771 		}
772 	}
773 
774 	itime = i_cal_time_new_from_timet_with_zone (new_dtstart, FALSE, old_dtstart_zone);
775 	/* set the timezone properly */
776 	i_cal_time_set_timezone (itime, old_dtstart_zone);
777 	if (all_day_event)
778 		i_cal_time_set_is_date (itime, TRUE);
779 	i_cal_component_set_dtstart (icomp, itime);
780 
781 	i_cal_time_set_is_date (itime, FALSE);
782 	btime = i_cal_time_add (itime, ic_dur);
783 	if (all_day_event)
784 		i_cal_time_set_is_date (btime, TRUE);
785 	i_cal_component_set_dtend (icomp, btime);
786 
787 	g_clear_object (&itime);
788 	g_clear_object (&btime);
789 	g_clear_object (&old_dtstart);
790 	g_clear_object (&old_dtend);
791 	g_clear_object (&ic_dur);
792 	g_clear_object (&ic_oneday);
793 
794 	/* The new uid stuff can go away once we actually set it in the backend */
795 	uid = e_util_generate_uid ();
796 	comp = e_cal_component_new_from_icalcomponent (i_cal_component_clone (icomp));
797 	e_cal_component_set_uid (comp, uid);
798 	g_free (uid);
799 
800 	e_cal_component_commit_sequence (comp);
801 
802 	e_cal_ops_create_component (model, client, e_cal_component_get_icalcomponent (comp),
803 		calendar_view_component_created_cb, g_object_ref (top_level), g_object_unref);
804 
805 	g_object_unref (comp);
806 }
807 
808 typedef struct {
809 	ECalendarView *cal_view;
810 	GSList *selected_cut_list; /* ECalModelComponent * */
811 	GSList *copied_uids; /* gchar * */
812 	gchar *ical_str;
813 	time_t selection_start;
814 	time_t selection_end;
815 	gboolean is_day_view;
816 	gint time_division;
817 	GtkWidget *top_level;
818 	gboolean success;
819 	ECalClient *client;
820 } PasteClipboardData;
821 
822 static void
paste_clipboard_data_free(gpointer ptr)823 paste_clipboard_data_free (gpointer ptr)
824 {
825 	PasteClipboardData *pcd = ptr;
826 
827 	if (pcd) {
828 		if (pcd->success && pcd->copied_uids && pcd->selected_cut_list) {
829 			ECalModel *model;
830 			ESourceRegistry *registry;
831 			GSList *link;
832 
833 			model = e_calendar_view_get_model (pcd->cal_view);
834 			registry = e_cal_model_get_registry (model);
835 
836 			for (link = pcd->selected_cut_list; link != NULL; link = g_slist_next (link)) {
837 				ECalModelComponent *comp_data = (ECalModelComponent *) link->data;
838 				ECalComponent *comp;
839 				const gchar *uid;
840 				GSList *found = NULL;
841 
842 				/* Remove them one by one after ensuring it has been copied to the destination successfully */
843 				found = g_slist_find_custom (pcd->copied_uids, i_cal_component_get_uid (comp_data->icalcomp), (GCompareFunc) strcmp);
844 				if (!found)
845 					continue;
846 
847 				g_free (found->data);
848 				pcd->copied_uids = g_slist_delete_link (pcd->copied_uids, found);
849 
850 				comp = e_cal_component_new_from_icalcomponent (i_cal_component_clone (comp_data->icalcomp));
851 
852 				if (itip_has_any_attendees (comp) &&
853 				    (itip_organizer_is_user (registry, comp, comp_data->client) ||
854 				    itip_sentby_is_user (registry, comp, comp_data->client))
855 				    && e_cal_dialogs_cancel_component ((GtkWindow *) pcd->top_level, comp_data->client, comp, TRUE))
856 					itip_send_component_with_model (model, I_CAL_METHOD_CANCEL,
857 						comp, comp_data->client, NULL, NULL, NULL,
858 						E_ITIP_SEND_COMPONENT_FLAG_STRIP_ALARMS | E_ITIP_SEND_COMPONENT_FLAG_ENSURE_MASTER_OBJECT);
859 
860 				uid = e_cal_component_get_uid (comp);
861 				if (e_cal_component_is_instance (comp)) {
862 					gchar *rid = NULL;
863 
864 					/* when cutting detached instances, only cut that instance */
865 					rid = e_cal_component_get_recurid_as_string (comp);
866 					e_cal_ops_remove_component (model, comp_data->client, uid, rid, E_CAL_OBJ_MOD_THIS, TRUE);
867 					g_free (rid);
868 				} else {
869 					e_cal_ops_remove_component (model, comp_data->client, uid, NULL, E_CAL_OBJ_MOD_ALL, FALSE);
870 				}
871 
872 				g_object_unref (comp);
873 			}
874 		}
875 
876 		if (pcd->success && pcd->client) {
877 			ECalModel *model;
878 
879 			model = e_calendar_view_get_model (pcd->cal_view);
880 			e_cal_model_emit_object_created (model, pcd->client);
881 		}
882 
883 		g_clear_object (&pcd->cal_view);
884 		g_clear_object (&pcd->top_level);
885 		g_clear_object (&pcd->client);
886 		g_slist_free_full (pcd->selected_cut_list, g_object_unref);
887 		g_slist_free_full (pcd->copied_uids, g_free);
888 		g_free (pcd->ical_str);
889 		g_slice_free (PasteClipboardData, pcd);
890 	}
891 }
892 
893 static void
cal_view_paste_clipboard_thread(EAlertSinkThreadJobData * job_data,gpointer user_data,GCancellable * cancellable,GError ** error)894 cal_view_paste_clipboard_thread (EAlertSinkThreadJobData *job_data,
895 				 gpointer user_data,
896 				 GCancellable *cancellable,
897 				 GError **error)
898 {
899 	PasteClipboardData *pcd = user_data;
900 	ICalComponent *icomp;
901 	ICalComponentKind kind;
902 	ICalTimezone *default_zone;
903 	ECalModel *model;
904 	ESourceRegistry *registry;
905 	ESource *source = NULL, *default_source = NULL;
906 	EClientCache *client_cache;
907 	EClient *e_client;
908 	ECalClient *client = NULL;
909 	const gchar *message;
910 	const gchar *extension_name;
911 	gchar *display_name;
912 	guint copied_components = 1;
913 	gboolean all_day;
914 	GError *local_error = NULL;
915 
916 	g_return_if_fail (pcd != NULL);
917 
918 	icomp = i_cal_parser_parse_string (pcd->ical_str);
919 	if (!icomp) {
920 		g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
921 			_("Pasted text doesn’t contain valid iCalendar data"));
922 		return;
923 	}
924 
925 	model = e_calendar_view_get_model (pcd->cal_view);
926 	registry = e_cal_model_get_registry (model);
927 
928 	switch (e_cal_model_get_component_kind (model)) {
929 		case I_CAL_VEVENT_COMPONENT:
930 			default_source = e_source_registry_ref_default_calendar (registry);
931 			extension_name = E_SOURCE_EXTENSION_CALENDAR;
932 			message = _("Default calendar not found");
933 			break;
934 		case I_CAL_VJOURNAL_COMPONENT:
935 			default_source = e_source_registry_ref_default_memo_list (registry);
936 			extension_name = E_SOURCE_EXTENSION_MEMO_LIST;
937 			message = _("Default memo list not found");
938 			break;
939 		case I_CAL_VTODO_COMPONENT:
940 			default_source = e_source_registry_ref_default_task_list (registry);
941 			extension_name = E_SOURCE_EXTENSION_TASK_LIST;
942 			message = _("Default task list not found");
943 			break;
944 		default:
945 			g_warn_if_reached ();
946 			goto out;
947 	}
948 
949 	source = e_source_registry_ref_source (registry, e_cal_model_get_default_source_uid (model));
950 	if (!source) {
951 		source = default_source;
952 		default_source = NULL;
953 	}
954 
955 	if (!source) {
956 		const gchar *default_source_uid = e_cal_model_get_default_source_uid (model);
957 
958 		e_alert_sink_thread_job_set_alert_arg_0 (job_data, default_source_uid ? default_source_uid : "");
959 		g_set_error_literal (&local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, message);
960 
961 		return;
962 	}
963 
964 	display_name = e_util_get_source_full_name (registry, source);
965 	e_alert_sink_thread_job_set_alert_arg_0 (job_data, display_name);
966 	g_free (display_name);
967 	client_cache = e_cal_model_get_client_cache (model);
968 
969 	e_client = e_client_cache_get_client_sync (client_cache, source, extension_name, 30, cancellable, &local_error);
970 	if (!e_client) {
971 		e_util_propagate_open_source_job_error (job_data, extension_name, local_error, error);
972 		goto out;
973 	}
974 
975 	client = E_CAL_CLIENT (e_client);
976 	kind = i_cal_component_isa (icomp);
977 	default_zone = e_cal_model_get_timezone (model);
978 	all_day = pcd->selection_end - pcd->selection_start == 60 * 60 * 24;
979 	copied_components = 0;
980 
981 	if (kind == I_CAL_VCALENDAR_COMPONENT) {
982 		ICalComponent *subcomp;
983 
984 		/* add timezones first, to have them ready */
985 		for (subcomp = i_cal_component_get_first_component (icomp, I_CAL_VTIMEZONE_COMPONENT);
986 		     subcomp;
987 		     g_object_unref (subcomp), subcomp = i_cal_component_get_next_component (icomp, I_CAL_VTIMEZONE_COMPONENT)) {
988 			ICalTimezone *zone;
989 
990 			zone = i_cal_timezone_new ();
991 			i_cal_timezone_set_component (zone, i_cal_component_clone (subcomp));
992 
993 			if (!e_cal_client_add_timezone_sync (client, zone, cancellable, error)) {
994 				g_object_unref (subcomp);
995 				g_object_unref (zone);
996 				goto out;
997 			}
998 
999 			g_object_unref (zone);
1000 		}
1001 
1002 		for (subcomp = i_cal_component_get_first_component (icomp, I_CAL_VEVENT_COMPONENT);
1003 		     subcomp;
1004 		     g_object_unref (subcomp), subcomp = i_cal_component_get_next_component (icomp, I_CAL_VEVENT_COMPONENT)) {
1005 			if (e_cal_util_component_has_recurrences (subcomp)) {
1006 				ICalProperty *prop = i_cal_component_get_first_property (subcomp, I_CAL_RRULE_PROPERTY);
1007 				if (prop) {
1008 					i_cal_property_remove_parameter_by_name (prop, "X-EVOLUTION-ENDDATE");
1009 					g_object_unref (prop);
1010 				}
1011 			}
1012 
1013 			e_calendar_view_add_event_sync (model, client, pcd->selection_start, default_zone, subcomp, all_day,
1014 				pcd->is_day_view, pcd->time_division, pcd->top_level);
1015 
1016 			copied_components++;
1017 			if (pcd->selected_cut_list)
1018 				pcd->copied_uids = g_slist_prepend (pcd->copied_uids, g_strdup (i_cal_component_get_uid (subcomp)));
1019 		}
1020 	} else if (kind == e_cal_model_get_component_kind (model)) {
1021 		e_calendar_view_add_event_sync (model, client, pcd->selection_start, default_zone, icomp, all_day,
1022 			pcd->is_day_view, pcd->time_division, pcd->top_level);
1023 
1024 		copied_components++;
1025 		if (pcd->selected_cut_list)
1026 			pcd->copied_uids = g_slist_prepend (pcd->copied_uids, g_strdup (i_cal_component_get_uid (icomp)));
1027 	}
1028 
1029 	pcd->success = !g_cancellable_is_cancelled (cancellable);
1030 	pcd->client = g_object_ref (client);
1031 
1032  out:
1033 	if (!copied_components && !g_cancellable_is_cancelled (cancellable) && error && !*error)
1034 		g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("No suitable component found"));
1035 
1036 	g_clear_object (&icomp);
1037 	g_clear_object (&source);
1038 	g_clear_object (&default_source);
1039 	g_clear_object (&client);
1040 }
1041 
1042 static void
calendar_view_paste_clipboard(ESelectable * selectable)1043 calendar_view_paste_clipboard (ESelectable *selectable)
1044 {
1045 	ECalModel *model;
1046 	ECalendarView *cal_view;
1047 	GtkClipboard *clipboard;
1048 
1049 	cal_view = E_CALENDAR_VIEW (selectable);
1050 
1051 	model = e_calendar_view_get_model (cal_view);
1052 
1053 	clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1054 
1055 	/* Paste text into an event being edited. */
1056 	if (gtk_clipboard_wait_is_text_available (clipboard)) {
1057 		ECalendarViewClass *class;
1058 
1059 		class = E_CALENDAR_VIEW_GET_CLASS (cal_view);
1060 		g_return_if_fail (class->paste_text != NULL);
1061 
1062 		class->paste_text (cal_view);
1063 
1064 	/* Paste iCalendar data into the view. */
1065 	} else if (e_clipboard_wait_is_calendar_available (clipboard)) {
1066 		PasteClipboardData *pcd;
1067 		ECalDataModel *data_model;
1068 		GCancellable *cancellable;
1069 		const gchar *alert_ident = NULL;
1070 
1071 		switch (e_cal_model_get_component_kind (model)) {
1072 			case I_CAL_VEVENT_COMPONENT:
1073 				alert_ident = "calendar:failed-create-event";
1074 				break;
1075 			case I_CAL_VJOURNAL_COMPONENT:
1076 				alert_ident = "calendar:failed-create-memo";
1077 				break;
1078 			case I_CAL_VTODO_COMPONENT:
1079 				alert_ident = "calendar:failed-create-task";
1080 				break;
1081 			default:
1082 				g_warn_if_reached ();
1083 				return;
1084 		}
1085 
1086 		pcd = g_slice_new0 (PasteClipboardData);
1087 		pcd->cal_view = g_object_ref (cal_view);
1088 		pcd->selected_cut_list = cal_view->priv->selected_cut_list;
1089 		cal_view->priv->selected_cut_list = NULL;
1090 		pcd->copied_uids = NULL; /* gchar * */
1091 		pcd->ical_str = e_clipboard_wait_for_calendar (clipboard);
1092 		g_warn_if_fail (e_calendar_view_get_selected_time_range (cal_view, &pcd->selection_start, &pcd->selection_end));
1093 		pcd->is_day_view = E_IS_DAY_VIEW (cal_view);
1094 		if (pcd->is_day_view)
1095 			pcd->time_division = e_calendar_view_get_time_divisions (cal_view);
1096 		pcd->top_level = gtk_widget_get_toplevel (GTK_WIDGET (cal_view));
1097 		if (pcd->top_level)
1098 			g_object_ref (pcd->top_level);
1099 		pcd->success = FALSE;
1100 		pcd->client = NULL;
1101 
1102 		data_model = e_cal_model_get_data_model (model);
1103 
1104 		cancellable = e_cal_data_model_submit_thread_job (data_model, _("Pasting iCalendar data"), alert_ident,
1105 			NULL, cal_view_paste_clipboard_thread, pcd, paste_clipboard_data_free);
1106 
1107 		g_clear_object (&cancellable);
1108 	}
1109 }
1110 
1111 static void
calendar_view_delete_selection(ESelectable * selectable)1112 calendar_view_delete_selection (ESelectable *selectable)
1113 {
1114 	ECalendarView *cal_view;
1115 	GList *selected, *iter;
1116 
1117 	cal_view = E_CALENDAR_VIEW (selectable);
1118 
1119 	selected = e_calendar_view_get_selected_events (cal_view);
1120 
1121 	for (iter = selected; iter != NULL; iter = iter->next) {
1122 		ECalendarViewEvent *event = iter->data;
1123 
1124 		/* XXX Why would this ever be NULL? */
1125 		if (event == NULL)
1126 			continue;
1127 
1128 		calendar_view_delete_event (cal_view, event, FALSE, E_CAL_OBJ_MOD_ALL);
1129 	}
1130 
1131 	g_list_free (selected);
1132 }
1133 
1134 static gchar *
calendar_view_get_description_text(ECalendarView * cal_view)1135 calendar_view_get_description_text (ECalendarView *cal_view)
1136 {
1137 	time_t start_time, end_time;
1138 	struct tm start_tm, end_tm;
1139 	ICalTime *tt;
1140 	ICalTimezone *zone;
1141 	gchar start_buffer[512] = { 0 };
1142 	gchar end_buffer[512] = { 0 };
1143 
1144 	g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), NULL);
1145 
1146 
1147 	if (!e_calendar_view_get_visible_time_range (cal_view, &start_time, &end_time))
1148 		return NULL;
1149 
1150 	zone = e_cal_model_get_timezone (cal_view->priv->model);
1151 
1152 	tt = i_cal_time_new_from_timet_with_zone (start_time, FALSE, zone);
1153 	start_tm = e_cal_util_icaltime_to_tm (tt);
1154 	g_clear_object (&tt);
1155 
1156 	/* Subtract one from end_time so we don't get an extra day. */
1157 	tt = i_cal_time_new_from_timet_with_zone (end_time - 1, FALSE, zone);
1158 	end_tm = e_cal_util_icaltime_to_tm (tt);
1159 	g_clear_object (&tt);
1160 
1161 	if (E_IS_MONTH_VIEW (cal_view) || E_IS_CAL_LIST_VIEW (cal_view)) {
1162 		if (start_tm.tm_year == end_tm.tm_year) {
1163 			if (start_tm.tm_mon == end_tm.tm_mon) {
1164 				e_utf8_strftime (start_buffer, sizeof (start_buffer), "%d", &start_tm);
1165 				e_utf8_strftime (end_buffer, sizeof (end_buffer), _("%d %b %Y"), &end_tm);
1166 			} else {
1167 				e_utf8_strftime (start_buffer, sizeof (start_buffer), _("%d %b"), &start_tm);
1168 				e_utf8_strftime (end_buffer, sizeof (end_buffer), _("%d %b %Y"), &end_tm);
1169 			}
1170 		} else {
1171 			e_utf8_strftime (start_buffer, sizeof (start_buffer), _("%d %b %Y"), &start_tm);
1172 			e_utf8_strftime (end_buffer, sizeof (end_buffer), _("%d %b %Y"), &end_tm);
1173 		}
1174 	} else {
1175 		if (start_tm.tm_year == end_tm.tm_year &&
1176 			start_tm.tm_mon == end_tm.tm_mon &&
1177 			start_tm.tm_mday == end_tm.tm_mday) {
1178 			e_utf8_strftime (start_buffer, sizeof (start_buffer), _("%A %d %b %Y"), &start_tm);
1179 		} else if (start_tm.tm_year == end_tm.tm_year) {
1180 			e_utf8_strftime (start_buffer, sizeof (start_buffer), _("%a %d %b"), &start_tm);
1181 			e_utf8_strftime (end_buffer, sizeof (end_buffer), _("%a %d %b %Y"), &end_tm);
1182 		} else {
1183 			e_utf8_strftime (start_buffer, sizeof (start_buffer), _("%a %d %b %Y"), &start_tm);
1184 			e_utf8_strftime (end_buffer, sizeof (end_buffer), _("%a %d %b %Y"), &end_tm);
1185 		}
1186 	}
1187 
1188 	if (*start_buffer && *end_buffer)
1189 		return g_strdup_printf ("%s - %s", start_buffer, end_buffer);
1190 
1191 	return g_strdup_printf ("%s%s", start_buffer, end_buffer);
1192 }
1193 
1194 static void
e_calendar_view_class_init(ECalendarViewClass * class)1195 e_calendar_view_class_init (ECalendarViewClass *class)
1196 {
1197 	GObjectClass *object_class;
1198 	GtkWidgetClass *widget_class;
1199 	GtkBindingSet *binding_set;
1200 
1201 	g_type_class_add_private (class, sizeof (ECalendarViewPrivate));
1202 
1203 	object_class = G_OBJECT_CLASS (class);
1204 	object_class->set_property = calendar_view_set_property;
1205 	object_class->get_property = calendar_view_get_property;
1206 	object_class->dispose = calendar_view_dispose;
1207 	object_class->constructed = calendar_view_constructed;
1208 
1209 	class->selection_changed = NULL;
1210 	class->selected_time_changed = NULL;
1211 	class->event_changed = NULL;
1212 	class->event_added = NULL;
1213 
1214 	class->get_selected_events = NULL;
1215 	class->get_selected_time_range = NULL;
1216 	class->set_selected_time_range = NULL;
1217 	class->get_visible_time_range = NULL;
1218 	class->precalc_visible_time_range = NULL;
1219 	class->update_query = NULL;
1220 	class->open_event = e_calendar_view_open_event;
1221 	class->paste_text = NULL;
1222 	class->get_description_text = calendar_view_get_description_text;
1223 
1224 	/* Inherited from ESelectableInterface */
1225 	g_object_class_override_property (
1226 		object_class,
1227 		PROP_COPY_TARGET_LIST,
1228 		"copy-target-list");
1229 
1230 	g_object_class_install_property (
1231 		object_class,
1232 		PROP_MODEL,
1233 		g_param_spec_object (
1234 			"model",
1235 			"Model",
1236 			NULL,
1237 			E_TYPE_CAL_MODEL,
1238 			G_PARAM_READWRITE |
1239 			G_PARAM_CONSTRUCT_ONLY));
1240 
1241 	/* Inherited from ESelectableInterface */
1242 	g_object_class_override_property (
1243 		object_class,
1244 		PROP_PASTE_TARGET_LIST,
1245 		"paste-target-list");
1246 
1247 	g_object_class_install_property (
1248 		object_class,
1249 		PROP_TIME_DIVISIONS,
1250 		g_param_spec_int (
1251 			"time-divisions",
1252 			"Time Divisions",
1253 			NULL,
1254 			G_MININT,
1255 			G_MAXINT,
1256 			30,
1257 			G_PARAM_READWRITE));
1258 
1259 	g_object_class_install_property (
1260 		object_class,
1261 		PROP_IS_EDITING,
1262 		g_param_spec_boolean (
1263 			"is-editing",
1264 			"Whether is in an editing mode",
1265 			"Whether is in an editing mode",
1266 			FALSE,
1267 			G_PARAM_READABLE));
1268 
1269 	g_object_class_install_property (
1270 		object_class,
1271 		PROP_ALLOW_DIRECT_SUMMARY_EDIT,
1272 		g_param_spec_boolean (
1273 			"allow-direct-summary-edit",
1274 			"Whether can edit event Summary directly",
1275 			NULL,
1276 			FALSE,
1277 			G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
1278 
1279 	signals[POPUP_EVENT] = g_signal_new (
1280 		"popup-event",
1281 		G_TYPE_FROM_CLASS (class),
1282 		G_SIGNAL_RUN_FIRST,
1283 		G_STRUCT_OFFSET (ECalendarViewClass, popup_event),
1284 		NULL, NULL,
1285 		g_cclosure_marshal_VOID__BOXED,
1286 		G_TYPE_NONE, 1,
1287 		GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
1288 
1289 	signals[SELECTION_CHANGED] = g_signal_new (
1290 		"selection-changed",
1291 		G_TYPE_FROM_CLASS (class),
1292 		G_SIGNAL_RUN_LAST,
1293 		G_STRUCT_OFFSET (ECalendarViewClass, selection_changed),
1294 		NULL, NULL,
1295 		g_cclosure_marshal_VOID__VOID,
1296 		G_TYPE_NONE, 0);
1297 
1298 	signals[SELECTED_TIME_CHANGED] = g_signal_new (
1299 		"selected-time-changed",
1300 		G_TYPE_FROM_CLASS (class),
1301 		G_SIGNAL_RUN_LAST,
1302 		G_STRUCT_OFFSET (ECalendarViewClass, selected_time_changed),
1303 		NULL, NULL,
1304 		g_cclosure_marshal_VOID__VOID,
1305 		G_TYPE_NONE, 0);
1306 
1307 	signals[TIMEZONE_CHANGED] = g_signal_new (
1308 		"timezone-changed",
1309 		G_TYPE_FROM_CLASS (class),
1310 		G_SIGNAL_RUN_LAST,
1311 		G_STRUCT_OFFSET (ECalendarViewClass, timezone_changed),
1312 		NULL, NULL,
1313 		e_marshal_VOID__OBJECT_OBJECT,
1314 		G_TYPE_NONE, 2,
1315 		I_CAL_TYPE_TIMEZONE,
1316 		I_CAL_TYPE_TIMEZONE);
1317 
1318 	signals[EVENT_CHANGED] = g_signal_new (
1319 		"event-changed",
1320 		G_TYPE_FROM_CLASS (object_class),
1321 		G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
1322 		G_STRUCT_OFFSET (ECalendarViewClass, event_changed),
1323 		NULL, NULL,
1324 		g_cclosure_marshal_VOID__POINTER,
1325 		G_TYPE_NONE, 1,
1326 		G_TYPE_POINTER);
1327 
1328 	signals[EVENT_ADDED] = g_signal_new (
1329 		"event-added",
1330 		G_TYPE_FROM_CLASS (object_class),
1331 		G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
1332 		G_STRUCT_OFFSET (ECalendarViewClass, event_added),
1333 		NULL, NULL,
1334 		g_cclosure_marshal_VOID__POINTER,
1335 		G_TYPE_NONE, 1,
1336 		G_TYPE_POINTER);
1337 
1338 	signals[OPEN_EVENT] = g_signal_new (
1339 		"open-event",
1340 		G_TYPE_FROM_CLASS (class),
1341 		G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
1342 		G_STRUCT_OFFSET (ECalendarViewClass, open_event),
1343 		NULL, NULL,
1344 		g_cclosure_marshal_VOID__VOID,
1345 		G_TYPE_NONE, 0);
1346 
1347 	signals[MOVE_VIEW_RANGE] = g_signal_new (
1348 		"move-view-range",
1349 		G_TYPE_FROM_CLASS (object_class),
1350 		G_SIGNAL_RUN_LAST,
1351 		G_STRUCT_OFFSET (ECalendarViewClass, move_view_range),
1352 		NULL, NULL,
1353 		NULL, /* default marshal */
1354 		G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_INT64);
1355 
1356 	/* Key bindings */
1357 
1358 	binding_set = gtk_binding_set_by_class (class);
1359 
1360 	gtk_binding_entry_add_signal (
1361 		binding_set, GDK_KEY_o, GDK_CONTROL_MASK, "open-event", 0);
1362 
1363 	/* init the accessibility support for e_day_view */
1364 	widget_class = GTK_WIDGET_CLASS (class);
1365 	gtk_widget_class_set_accessible_type (widget_class, EA_TYPE_CAL_VIEW);
1366 }
1367 
1368 static void
e_calendar_view_init(ECalendarView * calendar_view)1369 e_calendar_view_init (ECalendarView *calendar_view)
1370 {
1371 	GtkTargetList *target_list;
1372 
1373 	calendar_view->priv = E_CALENDAR_VIEW_GET_PRIVATE (calendar_view);
1374 
1375 	/* Set this early to avoid a divide-by-zero during init. */
1376 	calendar_view->priv->time_divisions = 30;
1377 
1378 	target_list = gtk_target_list_new (NULL, 0);
1379 	e_target_list_add_calendar_targets (target_list, 0);
1380 	calendar_view->priv->copy_target_list = target_list;
1381 
1382 	target_list = gtk_target_list_new (NULL, 0);
1383 	e_target_list_add_calendar_targets (target_list, 0);
1384 	calendar_view->priv->paste_target_list = target_list;
1385 }
1386 
1387 static void
calendar_view_selectable_init(ESelectableInterface * iface)1388 calendar_view_selectable_init (ESelectableInterface *iface)
1389 {
1390 	iface->update_actions = calendar_view_update_actions;
1391 	iface->cut_clipboard = calendar_view_cut_clipboard;
1392 	iface->copy_clipboard = calendar_view_copy_clipboard;
1393 	iface->paste_clipboard = calendar_view_paste_clipboard;
1394 	iface->delete_selection = calendar_view_delete_selection;
1395 }
1396 
1397 void
e_calendar_view_popup_event(ECalendarView * calendar_view,GdkEvent * button_event)1398 e_calendar_view_popup_event (ECalendarView *calendar_view,
1399                              GdkEvent *button_event)
1400 {
1401 	g_return_if_fail (E_IS_CALENDAR_VIEW (calendar_view));
1402 	g_return_if_fail (button_event != NULL);
1403 
1404 	g_signal_emit (calendar_view, signals[POPUP_EVENT], 0, button_event);
1405 }
1406 
1407 ECalModel *
e_calendar_view_get_model(ECalendarView * cal_view)1408 e_calendar_view_get_model (ECalendarView *cal_view)
1409 {
1410 	g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), NULL);
1411 
1412 	return cal_view->priv->model;
1413 }
1414 
1415 ICalTimezone *
e_calendar_view_get_timezone(ECalendarView * cal_view)1416 e_calendar_view_get_timezone (ECalendarView *cal_view)
1417 {
1418 	g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), NULL);
1419 
1420 	return e_cal_model_get_timezone (cal_view->priv->model);
1421 }
1422 
1423 void
e_calendar_view_set_timezone(ECalendarView * cal_view,const ICalTimezone * zone)1424 e_calendar_view_set_timezone (ECalendarView *cal_view,
1425 			      const ICalTimezone *zone)
1426 {
1427 	ICalTimezone *old_zone;
1428 
1429 	g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view));
1430 
1431 	old_zone = e_cal_model_get_timezone (cal_view->priv->model);
1432 	if (old_zone == zone)
1433 		return;
1434 
1435 	if (old_zone)
1436 		g_object_ref (old_zone);
1437 
1438 	e_cal_model_set_timezone (cal_view->priv->model, zone);
1439 	g_signal_emit (
1440 		cal_view, signals[TIMEZONE_CHANGED], 0,
1441 		old_zone, zone);
1442 
1443 	g_clear_object (&old_zone);
1444 }
1445 
1446 GtkTargetList *
e_calendar_view_get_copy_target_list(ECalendarView * cal_view)1447 e_calendar_view_get_copy_target_list (ECalendarView *cal_view)
1448 {
1449 	g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), NULL);
1450 
1451 	return cal_view->priv->copy_target_list;
1452 }
1453 
1454 GtkTargetList *
e_calendar_view_get_paste_target_list(ECalendarView * cal_view)1455 e_calendar_view_get_paste_target_list (ECalendarView *cal_view)
1456 {
1457 	g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), NULL);
1458 
1459 	return cal_view->priv->paste_target_list;
1460 }
1461 
1462 gint
e_calendar_view_get_time_divisions(ECalendarView * cal_view)1463 e_calendar_view_get_time_divisions (ECalendarView *cal_view)
1464 {
1465 	g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), 30);
1466 
1467 	return cal_view->priv->time_divisions;
1468 }
1469 
1470 void
e_calendar_view_set_time_divisions(ECalendarView * cal_view,gint time_divisions)1471 e_calendar_view_set_time_divisions (ECalendarView *cal_view,
1472                                     gint time_divisions)
1473 {
1474 	g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view));
1475 
1476 	/* To avoid division-by-zero and negative values on places where this is used */
1477 	if (time_divisions <= 0)
1478 		time_divisions = 30;
1479 
1480 	if (cal_view->priv->time_divisions == time_divisions)
1481 		return;
1482 
1483 	cal_view->priv->time_divisions = time_divisions;
1484 
1485 	g_object_notify (G_OBJECT (cal_view), "time-divisions");
1486 }
1487 
1488 GList *
e_calendar_view_get_selected_events(ECalendarView * cal_view)1489 e_calendar_view_get_selected_events (ECalendarView *cal_view)
1490 {
1491 	ECalendarViewClass *class;
1492 
1493 	g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), NULL);
1494 
1495 	class = E_CALENDAR_VIEW_GET_CLASS (cal_view);
1496 	g_return_val_if_fail (class->get_selected_events != NULL, NULL);
1497 
1498 	return class->get_selected_events (cal_view);
1499 }
1500 
1501 gboolean
e_calendar_view_get_selected_time_range(ECalendarView * cal_view,time_t * start_time,time_t * end_time)1502 e_calendar_view_get_selected_time_range (ECalendarView *cal_view,
1503                                          time_t *start_time,
1504                                          time_t *end_time)
1505 {
1506 	ECalendarViewClass *class;
1507 
1508 	g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), FALSE);
1509 
1510 	class = E_CALENDAR_VIEW_GET_CLASS (cal_view);
1511 	g_return_val_if_fail (class->get_selected_time_range != NULL, FALSE);
1512 
1513 	return class->get_selected_time_range (cal_view, start_time, end_time);
1514 }
1515 
1516 void
e_calendar_view_set_selected_time_range(ECalendarView * cal_view,time_t start_time,time_t end_time)1517 e_calendar_view_set_selected_time_range (ECalendarView *cal_view,
1518                                          time_t start_time,
1519                                          time_t end_time)
1520 {
1521 	ECalendarViewClass *class;
1522 
1523 	g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view));
1524 
1525 	/* Not all views implement this, so return silently. */
1526 	class = E_CALENDAR_VIEW_GET_CLASS (cal_view);
1527 	if (class->set_selected_time_range == NULL)
1528 		return;
1529 
1530 	class->set_selected_time_range (cal_view, start_time, end_time);
1531 }
1532 
1533 gboolean
e_calendar_view_get_visible_time_range(ECalendarView * cal_view,time_t * start_time,time_t * end_time)1534 e_calendar_view_get_visible_time_range (ECalendarView *cal_view,
1535                                         time_t *start_time,
1536                                         time_t *end_time)
1537 {
1538 	ECalendarViewClass *class;
1539 
1540 	g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), FALSE);
1541 
1542 	class = E_CALENDAR_VIEW_GET_CLASS (cal_view);
1543 	g_return_val_if_fail (class->get_visible_time_range != NULL, FALSE);
1544 
1545 	return class->get_visible_time_range (cal_view, start_time, end_time);
1546 }
1547 
1548 void
e_calendar_view_precalc_visible_time_range(ECalendarView * cal_view,time_t in_start_time,time_t in_end_time,time_t * out_start_time,time_t * out_end_time)1549 e_calendar_view_precalc_visible_time_range (ECalendarView *cal_view,
1550 					    time_t in_start_time,
1551 					    time_t in_end_time,
1552 					    time_t *out_start_time,
1553 					    time_t *out_end_time)
1554 {
1555 	ECalendarViewClass *class;
1556 
1557 	g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view));
1558 
1559 	/* Not all views implement this, so return silently. */
1560 	class = E_CALENDAR_VIEW_GET_CLASS (cal_view);
1561 	if (class->precalc_visible_time_range == NULL)
1562 		return;
1563 
1564 	class->precalc_visible_time_range (cal_view, in_start_time, in_end_time, out_start_time, out_end_time);
1565 }
1566 
1567 void
e_calendar_view_update_query(ECalendarView * cal_view)1568 e_calendar_view_update_query (ECalendarView *cal_view)
1569 {
1570 	ECalendarViewClass *class;
1571 
1572 	g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view));
1573 
1574 	class = E_CALENDAR_VIEW_GET_CLASS (cal_view);
1575 	g_return_if_fail (class->update_query != NULL);
1576 
1577 	class->update_query (cal_view);
1578 }
1579 
1580 void
e_calendar_view_delete_selected_occurrence(ECalendarView * cal_view,ECalObjModType mod)1581 e_calendar_view_delete_selected_occurrence (ECalendarView *cal_view,
1582 					    ECalObjModType mod)
1583 {
1584 	ECalendarViewEvent *event;
1585 	GList *selected;
1586 
1587 	g_return_if_fail (mod == E_CAL_OBJ_MOD_THIS || mod == E_CAL_OBJ_MOD_THIS_AND_FUTURE);
1588 
1589 	selected = e_calendar_view_get_selected_events (cal_view);
1590 	if (!selected)
1591 		return;
1592 
1593 	event = (ECalendarViewEvent *) selected->data;
1594 	if (is_comp_data_valid (event)) {
1595 		calendar_view_delete_event (cal_view, event, TRUE, mod);
1596 	}
1597 
1598 	g_list_free (selected);
1599 }
1600 
1601 void
e_calendar_view_open_event(ECalendarView * cal_view)1602 e_calendar_view_open_event (ECalendarView *cal_view)
1603 {
1604 	GList *selected;
1605 
1606 	selected = e_calendar_view_get_selected_events (cal_view);
1607 	if (selected) {
1608 		ECalendarViewEvent *event = (ECalendarViewEvent *) selected->data;
1609 		if (event && is_comp_data_valid (event))
1610 			e_calendar_view_edit_appointment (cal_view, event->comp_data->client, event->comp_data->icalcomp, EDIT_EVENT_AUTODETECT);
1611 
1612 		g_list_free (selected);
1613 	}
1614 }
1615 
1616 /**
1617  * e_calendar_view_new_appointment
1618  * @cal_view: an #ECalendarView
1619  * @flags: bit-or of ENewAppointmentFlags
1620  *
1621  * Opens an event editor dialog for a new appointment. The appointment's
1622  * start and end times are set to the currently selected time range in
1623  * the calendar view, unless the flags contain E_NEW_APPOINTMENT_FLAG_FORCE_CURRENT_TIME,
1624  * in which case the current time is used.
1625  *
1626  * When the selection is for all day and we don't need all day event,
1627  * then this does a rounding to the actual hour for actual day (today) and
1628  * to the 'day begins' from preferences in other selected day.
1629  */
1630 void
e_calendar_view_new_appointment(ECalendarView * cal_view,guint32 flags)1631 e_calendar_view_new_appointment (ECalendarView *cal_view,
1632 				 guint32 flags)
1633 {
1634 	ECalModel *model;
1635 	time_t dtstart, dtend, now;
1636 	gboolean do_rounding = FALSE;
1637 	gboolean all_day = (flags & E_NEW_APPOINTMENT_FLAG_ALL_DAY) != 0;
1638 	gboolean meeting = (flags & E_NEW_APPOINTMENT_FLAG_MEETING) != 0;
1639 	gboolean no_past_date = (flags & E_NEW_APPOINTMENT_FLAG_NO_PAST_DATE) != 0;
1640 
1641 	g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view));
1642 
1643 	model = e_calendar_view_get_model (cal_view);
1644 
1645 	now = time (NULL);
1646 
1647 	if ((flags & E_NEW_APPOINTMENT_FLAG_FORCE_CURRENT_TIME) != 0 ||
1648 	    !e_calendar_view_get_selected_time_range (cal_view, &dtstart, &dtend)) {
1649 		dtstart = now;
1650 		dtend = dtstart + 3600;
1651 	}
1652 
1653 	if (no_past_date && dtstart <= now) {
1654 		dtend = time_day_begin (now) + (dtend - dtstart);
1655 		dtstart = time_day_begin (now);
1656 		do_rounding = TRUE;
1657 	}
1658 
1659 	/* We either need rounding or don't want to set all_day for this, we will rather use actual */
1660 	/* time in this cases; dtstart should be a midnight in this case */
1661 	if (do_rounding || (!all_day && (dtend - dtstart) == (60 * 60 * 24))) {
1662 		struct tm local = *localtime (&now);
1663 		gint time_div = e_calendar_view_get_time_divisions (cal_view);
1664 		gint hours, mins;
1665 
1666 		if (!time_div) /* Possible if your settings values aren't so nice */
1667 			time_div = 30;
1668 
1669 		if (time_day_begin (now) == time_day_begin (dtstart)) {
1670 			/* same day as today */
1671 			hours = local.tm_hour;
1672 			mins = local.tm_min;
1673 
1674 			/* round minutes to nearest time division, up or down */
1675 			if ((mins % time_div) >= time_div / 2)
1676 				mins += time_div;
1677 			mins = (mins - (mins % time_div));
1678 		} else {
1679 			/* other day than today */
1680 			hours = e_cal_model_get_work_day_start_hour (model);
1681 			mins = e_cal_model_get_work_day_start_minute (model);
1682 		}
1683 
1684 		dtstart = dtstart + (60 * 60 * hours) + (mins * 60);
1685 		if (no_past_date && dtstart <= now)
1686 			dtstart += ((((now - dtstart) / 60 / time_div)) + time_div) * 60;
1687 		dtend = dtstart + (time_div * 60);
1688 	}
1689 
1690 	e_cal_ops_new_component_editor_from_model (
1691 		e_calendar_view_get_model (cal_view), NULL,
1692 		dtstart, dtend, meeting, all_day);
1693 }
1694 
1695 /* Ensures the calendar is selected */
1696 static void
object_created_cb(ECompEditor * comp_editor,ECalendarView * cal_view)1697 object_created_cb (ECompEditor *comp_editor,
1698                    ECalendarView *cal_view)
1699 {
1700 	e_cal_model_emit_object_created (e_calendar_view_get_model (cal_view), e_comp_editor_get_target_client (comp_editor));
1701 }
1702 
1703 ECompEditor *
e_calendar_view_open_event_with_flags(ECalendarView * cal_view,ECalClient * client,ICalComponent * icomp,guint32 flags)1704 e_calendar_view_open_event_with_flags (ECalendarView *cal_view,
1705 				       ECalClient *client,
1706 				       ICalComponent *icomp,
1707 				       guint32 flags)
1708 {
1709 	ECompEditor *comp_editor;
1710 	EShell *shell;
1711 
1712 	/* FIXME ECalendarView should own an EShell pointer. */
1713 	shell = e_shell_get_default ();
1714 
1715 	comp_editor = e_comp_editor_find_existing_for (e_client_get_source (E_CLIENT (client)), icomp);
1716 	if (!comp_editor) {
1717 		GtkWidget *toplevel;
1718 
1719 		toplevel = gtk_widget_get_toplevel (GTK_WIDGET (cal_view));
1720 		if (!GTK_IS_WINDOW (toplevel))
1721 			toplevel = NULL;
1722 
1723 		comp_editor = e_comp_editor_open_for_component (GTK_WINDOW (toplevel),
1724 			shell, e_client_get_source (E_CLIENT (client)), icomp, flags);
1725 
1726 		g_signal_connect (
1727 			comp_editor, "object-created",
1728 			G_CALLBACK (object_created_cb), cal_view);
1729 	}
1730 
1731 	gtk_window_present (GTK_WINDOW (comp_editor));
1732 
1733 	return comp_editor;
1734 }
1735 
1736 /**
1737  * e_calendar_view_edit_appointment
1738  * @cal_view: A calendar view.
1739  * @client: Calendar client.
1740  * @icomp: The object to be edited.
1741  * @mode: one of #EEditEventMode
1742  *
1743  * Opens an editor window to allow the user to edit the selected
1744  * object.
1745  */
1746 void
e_calendar_view_edit_appointment(ECalendarView * cal_view,ECalClient * client,ICalComponent * icomp,EEditEventMode mode)1747 e_calendar_view_edit_appointment (ECalendarView *cal_view,
1748 				  ECalClient *client,
1749 				  ICalComponent *icomp,
1750 				  EEditEventMode mode)
1751 {
1752 	ECalModel *model;
1753 	ESourceRegistry *registry;
1754 	guint32 flags = 0;
1755 
1756 	g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view));
1757 	g_return_if_fail (E_IS_CAL_CLIENT (client));
1758 	g_return_if_fail (icomp != NULL);
1759 
1760 	model = e_calendar_view_get_model (cal_view);
1761 	registry = e_cal_model_get_registry (model);
1762 
1763 	if ((mode == EDIT_EVENT_AUTODETECT && e_cal_util_component_has_attendee (icomp))
1764 	    || mode == EDIT_EVENT_FORCE_MEETING) {
1765 		ECalComponent *comp = e_cal_component_new_from_icalcomponent (i_cal_component_clone (icomp));
1766 		flags |= E_COMP_EDITOR_FLAG_WITH_ATTENDEES;
1767 		if (itip_organizer_is_user (registry, comp, client) ||
1768 		    itip_sentby_is_user (registry, comp, client) ||
1769 		    !e_cal_component_has_attendees (comp))
1770 			flags |= E_COMP_EDITOR_FLAG_ORGANIZER_IS_USER;
1771 		g_object_unref (comp);
1772 	}
1773 
1774 	e_calendar_view_open_event_with_flags (cal_view, client, icomp, flags);
1775 }
1776 
1777 static gboolean
tooltip_key_event(GtkWidget * tooltip,GdkEvent * key_event,ECalendarView * view)1778 tooltip_key_event (GtkWidget *tooltip,
1779 		   GdkEvent *key_event,
1780 		   ECalendarView *view)
1781 {
1782 	GtkWidget *widget;
1783 
1784 	widget = g_object_get_data (G_OBJECT (view), "tooltip-window");
1785 	if (widget == NULL)
1786 		return TRUE;
1787 
1788 	gtk_widget_destroy (widget);
1789 	g_object_set_data (G_OBJECT (view), "tooltip-window", NULL);
1790 
1791 	return FALSE;
1792 }
1793 
1794 static gchar *
get_label(ICalTime * tt,ICalTimezone * f_zone,ICalTimezone * t_zone)1795 get_label (ICalTime *tt,
1796 	   ICalTimezone *f_zone,
1797 	   ICalTimezone *t_zone)
1798 {
1799 	struct tm tmp_tm;
1800 
1801 	tmp_tm = e_cal_util_icaltime_to_tm_with_zone (tt, f_zone, t_zone);
1802 
1803 	return e_datetime_format_format_tm ("calendar", "table", i_cal_time_is_date (tt) ? DTFormatKindDate : DTFormatKindDateTime, &tmp_tm);
1804 }
1805 
1806 void
e_calendar_view_move_tip(GtkWidget * widget,gint x,gint y)1807 e_calendar_view_move_tip (GtkWidget *widget,
1808                           gint x,
1809                           gint y)
1810 {
1811 	GtkAllocation allocation;
1812 	GdkDisplay *display;
1813 	GdkScreen *screen;
1814 	GdkScreen *pointer_screen;
1815 	GdkRectangle monitor;
1816 	GdkDeviceManager *device_manager;
1817 	GdkDevice *pointer;
1818 	gint monitor_num, px, py;
1819 	gint w, h;
1820 
1821 	gtk_widget_get_allocation (widget, &allocation);
1822 	w = allocation.width;
1823 	h = allocation.height;
1824 
1825 	screen = gtk_widget_get_screen (widget);
1826 	display = gdk_screen_get_display (screen);
1827 	device_manager = gdk_display_get_device_manager (display);
1828 	pointer = gdk_device_manager_get_client_pointer (device_manager);
1829 
1830 	gdk_device_get_position (pointer, &pointer_screen, &px, &py);
1831 	if (pointer_screen != screen) {
1832 		px = x;
1833 		py = y;
1834 	}
1835 	monitor_num = gdk_screen_get_monitor_at_point (screen, px, py);
1836 	gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
1837 
1838 	x += E_CALENDAR_VIEW_TOOLTIP_OFFSET;
1839 
1840 	if ((x + w) > monitor.x + monitor.width)
1841 		x -= (x + w) - (monitor.x + monitor.width);
1842 	else if (x < monitor.x)
1843 		x = monitor.x;
1844 
1845 	if (x < monitor.x)
1846 		x = monitor.x;
1847 
1848 	y += E_CALENDAR_VIEW_TOOLTIP_OFFSET;
1849 
1850 	/* Place above the cursor only if it cannot fit below it and there's enough space above */
1851 
1852 	if (y + h > monitor.y + monitor.height && y - 2 * E_CALENDAR_VIEW_TOOLTIP_OFFSET - monitor.y > h)
1853 		y = y - h - 2 * E_CALENDAR_VIEW_TOOLTIP_OFFSET;
1854 
1855 	if (y < monitor.y)
1856 		y = monitor.y;
1857 
1858 	gtk_window_move (GTK_WINDOW (widget), x, y);
1859 	gtk_widget_show (widget);
1860 }
1861 
1862 void
e_calendar_view_destroy_tooltip(ECalendarView * cal_view)1863 e_calendar_view_destroy_tooltip (ECalendarView *cal_view)
1864 {
1865 	GtkWidget *widget;
1866 	GObject *object;
1867 
1868 	g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view));
1869 
1870 	object = G_OBJECT (cal_view);
1871 	widget = g_object_get_data (object, "tooltip-window");
1872 
1873 	if (widget) {
1874 		gtk_widget_destroy (widget);
1875 		g_object_set_data (object, "tooltip-window", NULL);
1876 	}
1877 }
1878 
1879 /*
1880  * It is expected to show the tooltips in this below format
1881  *
1882  *	<B>SUBJECT OF THE MEETING</B>
1883  *	Organiser: NameOfTheUser<email@ofuser.com>
1884  *	Location: PlaceOfTheMeeting
1885  *	Time : DateAndTime (xx Minutes)
1886  *      Status: Accepted: X   Declined: Y   ...
1887  *      \n<description>
1888  */
1889 
1890 gboolean
e_calendar_view_get_tooltips(const ECalendarViewEventData * data)1891 e_calendar_view_get_tooltips (const ECalendarViewEventData *data)
1892 {
1893 	GtkWidget *label, *box, *hbox, *ebox, *frame, *toplevel;
1894 	gchar *tmp, *tmp1 = NULL, *tmp2 = NULL;
1895 	ECalComponentOrganizer *organizer;
1896 	ECalComponentDateTime *dtstart, *dtend;
1897 	time_t t_start, t_end;
1898 	ECalendarViewEvent *pevent;
1899 	GtkWidget *widget;
1900 	GdkWindow *window;
1901 	GdkDisplay *display;
1902 	GdkSeat *seat;
1903 	GdkRGBA bg_rgba, fg_rgba;
1904 	ECalComponent *newcomp;
1905 	ICalTimezone *zone, *default_zone;
1906 	ECalModel *model;
1907 	ECalClient *client = NULL;
1908 	const gchar *description;
1909 
1910 	/* This function is a timeout callback. */
1911 
1912 	g_return_val_if_fail (data != NULL, FALSE);
1913 	g_return_val_if_fail (E_IS_CALENDAR_VIEW (data->cal_view), FALSE);
1914 
1915 	e_utils_get_theme_color (GTK_WIDGET (data->cal_view), "theme_selected_bg_color", E_UTILS_DEFAULT_THEME_SELECTED_BG_COLOR, &bg_rgba);
1916 	e_utils_get_theme_color (GTK_WIDGET (data->cal_view), "theme_selected_fg_color", E_UTILS_DEFAULT_THEME_SELECTED_FG_COLOR, &fg_rgba);
1917 
1918 	model = e_calendar_view_get_model (data->cal_view);
1919 
1920 	/* Delete any stray tooltip if left */
1921 	widget = g_object_get_data (
1922 		G_OBJECT (data->cal_view), "tooltip-window");
1923 	if (GTK_IS_WIDGET (widget))
1924 		gtk_widget_destroy (widget);
1925 
1926 	default_zone = e_calendar_view_get_timezone (data->cal_view);
1927 	pevent = data->get_view_event (data->cal_view, data->day, data->event_num);
1928 
1929 	if (!is_comp_data_valid (pevent))
1930 		return FALSE;
1931 
1932 	client = pevent->comp_data->client;
1933 
1934 	newcomp = e_cal_component_new_from_icalcomponent (i_cal_component_clone (pevent->comp_data->icalcomp));
1935 	if (!newcomp) {
1936 		g_warning ("couldn't update calendar component with modified data from backend\n");
1937 		return FALSE;
1938 	}
1939 
1940 	box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
1941 
1942 	tmp1 = e_calendar_view_dup_component_summary (pevent->comp_data->icalcomp);
1943 
1944 	if (!(tmp1 && *tmp1)) {
1945 		g_object_unref (newcomp);
1946 		gtk_widget_destroy (box);
1947 		g_free (tmp1);
1948 
1949 		return FALSE;
1950 	}
1951 
1952 	tmp = g_markup_printf_escaped ("<b>%s</b>", tmp1);
1953 	label = gtk_label_new (NULL);
1954 	gtk_label_set_line_wrap ((GtkLabel *) label, TRUE);
1955 	gtk_label_set_width_chars (GTK_LABEL (label), 20);
1956 	gtk_label_set_markup ((GtkLabel *) label, tmp);
1957 
1958 	g_free (tmp1);
1959 	tmp1 = NULL;
1960 
1961 	hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
1962 	gtk_box_pack_start ((GtkBox *) hbox, label, FALSE, FALSE, 0);
1963 	ebox = gtk_event_box_new ();
1964 	gtk_container_add ((GtkContainer *) ebox, hbox);
1965 	gtk_widget_override_background_color (ebox, GTK_STATE_FLAG_NORMAL, &bg_rgba);
1966 	gtk_widget_override_color (label, GTK_STATE_FLAG_NORMAL, &fg_rgba);
1967 
1968 	gtk_box_pack_start ((GtkBox *) box, ebox, FALSE, FALSE, 0);
1969 	g_free (tmp);
1970 
1971 	organizer = e_cal_component_get_organizer (newcomp);
1972 	if (organizer && e_cal_component_organizer_get_cn (organizer)) {
1973 		const gchar *email;
1974 
1975 		email = itip_strip_mailto (e_cal_component_organizer_get_value (organizer));
1976 
1977 		if (email) {
1978 			/* To Translators: It will display "Organiser: NameOfTheUser <email@ofuser.com>" */
1979 			tmp = g_strdup_printf (_("Organizer: %s <%s>"), e_cal_component_organizer_get_cn (organizer), email);
1980 		} else {
1981 			/* With SunOne accounts, there may be no ':' in organiser.value*/
1982 			tmp = g_strdup_printf (_("Organizer: %s"), e_cal_component_organizer_get_cn (organizer));
1983 		}
1984 
1985 		label = gtk_label_new (tmp);
1986 		hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
1987 		gtk_box_pack_start ((GtkBox *) hbox, label, FALSE, FALSE, 0);
1988 		ebox = gtk_event_box_new ();
1989 		gtk_container_add ((GtkContainer *) ebox, hbox);
1990 		gtk_box_pack_start ((GtkBox *) box, ebox, FALSE, FALSE, 0);
1991 
1992 		g_free (tmp);
1993 	}
1994 
1995 	e_cal_component_organizer_free (organizer);
1996 
1997 	tmp1 = e_cal_component_get_location (newcomp);
1998 
1999 	if (tmp1) {
2000 		/* Translators: It will display "Location: PlaceOfTheMeeting" */
2001 		tmp = g_markup_printf_escaped (_("Location: %s"), tmp1);
2002 		label = gtk_label_new (NULL);
2003 		gtk_widget_set_halign (label, GTK_ALIGN_START);
2004 		gtk_misc_set_alignment ((GtkMisc *) label, 0.0, 0.0);
2005 		gtk_label_set_markup ((GtkLabel *) label, tmp);
2006 		gtk_label_set_line_wrap ((GtkLabel *) label, TRUE);
2007 		gtk_label_set_width_chars (GTK_LABEL (label), 20);
2008 		gtk_label_set_max_width_chars ((GtkLabel *) label, 80);
2009 		hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
2010 		gtk_box_pack_start ((GtkBox *) hbox, label, FALSE, FALSE, 0);
2011 		ebox = gtk_event_box_new ();
2012 		gtk_container_add ((GtkContainer *) ebox, hbox);
2013 		gtk_box_pack_start ((GtkBox *) box, ebox, FALSE, FALSE, 0);
2014 		g_free (tmp);
2015 	}
2016 
2017 	g_free (tmp1);
2018 	tmp1 = NULL;
2019 
2020 	dtstart = e_cal_component_get_dtstart (newcomp);
2021 	dtend = e_cal_component_get_dtend (newcomp);
2022 
2023 	if (dtstart && e_cal_component_datetime_get_tzid (dtstart)) {
2024 		zone = i_cal_component_get_timezone (e_cal_component_get_icalcomponent (newcomp), e_cal_component_datetime_get_tzid (dtstart));
2025 		if (!zone &&
2026 		    !e_cal_client_get_timezone_sync (client, e_cal_component_datetime_get_tzid (dtstart), &zone, NULL, NULL))
2027 			zone = NULL;
2028 
2029 		if (!zone)
2030 			zone = default_zone;
2031 
2032 	} else {
2033 		zone = default_zone;
2034 	}
2035 
2036 	if (dtstart && e_cal_component_datetime_get_value (dtstart)) {
2037 		t_start = i_cal_time_as_timet_with_zone (e_cal_component_datetime_get_value (dtstart), zone);
2038 
2039 		if (dtend && e_cal_component_datetime_get_value (dtend)) {
2040 			ICalTimezone *end_zone = default_zone;
2041 
2042 			if (e_cal_component_datetime_get_tzid (dtend)) {
2043 				end_zone = i_cal_component_get_timezone (e_cal_component_get_icalcomponent (newcomp), e_cal_component_datetime_get_tzid (dtend));
2044 				if (!end_zone &&
2045 				    !e_cal_client_get_timezone_sync (client, e_cal_component_datetime_get_tzid (dtend), &end_zone, NULL, NULL))
2046 					end_zone = NULL;
2047 
2048 				if (!end_zone)
2049 					end_zone = default_zone;
2050 			}
2051 
2052 			t_end = i_cal_time_as_timet_with_zone (e_cal_component_datetime_get_value (dtend), end_zone);
2053 		} else
2054 			t_end = t_start;
2055 
2056 		tmp1 = get_label (e_cal_component_datetime_get_value (dtstart), zone, default_zone);
2057 		tmp = calculate_time (t_start, t_end);
2058 
2059 		/* To Translators: It will display "Time: ActualStartDateAndTime (DurationOfTheMeeting)"*/
2060 		tmp2 = g_strdup_printf (_("Time: %s %s"), tmp1, tmp);
2061 		if (zone && !cal_comp_util_compare_event_timezones (newcomp, client, default_zone)) {
2062 			g_free (tmp);
2063 			g_free (tmp1);
2064 
2065 			tmp1 = get_label (e_cal_component_datetime_get_value (dtstart), zone, zone);
2066 			tmp = g_strconcat (tmp2, "\n\t[ ", tmp1, " ", i_cal_timezone_get_display_name (zone), " ]", NULL);
2067 		} else {
2068 			g_free (tmp);
2069 			tmp = tmp2;
2070 			tmp2 = NULL;
2071 		}
2072 	} else {
2073 		tmp = NULL;
2074 	}
2075 
2076 	e_cal_component_datetime_free (dtstart);
2077 	e_cal_component_datetime_free (dtend);
2078 
2079 	hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
2080 	gtk_box_pack_start ((GtkBox *) hbox, gtk_label_new_with_mnemonic (tmp), FALSE, FALSE, 0);
2081 	ebox = gtk_event_box_new ();
2082 	gtk_container_add ((GtkContainer *) ebox, hbox);
2083 	gtk_box_pack_start ((GtkBox *) box, ebox, FALSE, FALSE, 0);
2084 
2085 	g_free (tmp);
2086 	g_free (tmp2);
2087 	g_free (tmp1);
2088 
2089 	tmp = e_cal_model_get_attendees_status_info (
2090 		model, newcomp, pevent->comp_data->client);
2091 	if (tmp) {
2092 		hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
2093 		gtk_box_pack_start ((GtkBox *) hbox, gtk_label_new (tmp), FALSE, FALSE, 0);
2094 		ebox = gtk_event_box_new ();
2095 		gtk_container_add ((GtkContainer *) ebox, hbox);
2096 		gtk_box_pack_start ((GtkBox *) box, ebox, FALSE, FALSE, 0);
2097 
2098 		g_free (tmp);
2099 	}
2100 
2101 	tmp = cal_comp_util_get_attendee_comments (e_cal_component_get_icalcomponent (newcomp));
2102 	if (tmp) {
2103 		hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
2104 		gtk_box_pack_start ((GtkBox *) hbox, gtk_label_new (tmp), FALSE, FALSE, 0);
2105 		ebox = gtk_event_box_new ();
2106 		gtk_container_add ((GtkContainer *) ebox, hbox);
2107 		gtk_box_pack_start ((GtkBox *) box, ebox, FALSE, FALSE, 0);
2108 
2109 		g_free (tmp);
2110 	}
2111 
2112 	description = i_cal_component_get_description (e_cal_component_get_icalcomponent (newcomp));
2113 	if (description && *description && g_utf8_validate (description, -1, NULL) &&
2114 	    !g_str_equal (description, "\r") &&
2115 	    !g_str_equal (description, "\n") &&
2116 	    !g_str_equal (description, "\r\n")) {
2117 		#define MAX_TOOLTIP_DESCRIPTION_LEN 1024
2118 		glong len;
2119 
2120 		tmp = NULL;
2121 
2122 		len = g_utf8_strlen (description, -1);
2123 		if (len > MAX_TOOLTIP_DESCRIPTION_LEN) {
2124 			GString *str;
2125 			const gchar *end;
2126 
2127 			end = g_utf8_offset_to_pointer (description, MAX_TOOLTIP_DESCRIPTION_LEN);
2128 			str = g_string_new_len (description, end - description);
2129 			g_string_append (str, _("…"));
2130 
2131 			tmp = g_string_free (str, FALSE);
2132 		}
2133 
2134 		hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
2135 		gtk_widget_set_margin_top (hbox, 12);
2136 		label = gtk_label_new (tmp ? tmp : description);
2137 		g_object_set (G_OBJECT (label),
2138 			"wrap", TRUE,
2139 			"width-chars", 80,
2140 			"max-width-chars", 100,
2141 			"xalign", 0.0,
2142 			NULL);
2143 
2144 		gtk_box_pack_start ((GtkBox *) hbox, label, FALSE, FALSE, 0);
2145 		ebox = gtk_event_box_new ();
2146 		gtk_container_add ((GtkContainer *) ebox, hbox);
2147 		gtk_box_pack_start ((GtkBox *) box, ebox, FALSE, FALSE, 0);
2148 
2149 		g_free (tmp);
2150 	}
2151 
2152 	pevent->tooltip = gtk_window_new (GTK_WINDOW_POPUP);
2153 
2154 	toplevel = gtk_widget_get_toplevel (GTK_WIDGET (data->cal_view));
2155 	if (GTK_IS_WINDOW (toplevel)) {
2156 		gtk_window_group_add_window (gtk_window_get_group (GTK_WINDOW (toplevel)), GTK_WINDOW (pevent->tooltip));
2157 		gtk_window_set_transient_for (GTK_WINDOW (pevent->tooltip), GTK_WINDOW (toplevel));
2158 	}
2159 
2160 	frame = gtk_frame_new (NULL);
2161 	gtk_frame_set_shadow_type ((GtkFrame *) frame, GTK_SHADOW_IN);
2162 
2163 	gtk_window_set_type_hint (GTK_WINDOW (pevent->tooltip), GDK_WINDOW_TYPE_HINT_TOOLTIP);
2164 	gtk_window_move ((GtkWindow *) pevent->tooltip, pevent->x + E_CALENDAR_VIEW_TOOLTIP_OFFSET, pevent->y + E_CALENDAR_VIEW_TOOLTIP_OFFSET);
2165 	gtk_container_add ((GtkContainer *) frame, box);
2166 	gtk_container_add ((GtkContainer *) pevent->tooltip, frame);
2167 
2168 	gtk_widget_show_all (pevent->tooltip);
2169 
2170 	e_calendar_view_move_tip (pevent->tooltip, pevent->x, pevent->y);
2171 
2172 	window = gtk_widget_get_window (pevent->tooltip);
2173 	display = gdk_window_get_display (window);
2174 	seat = gdk_display_get_default_seat (display);
2175 
2176 	/* Grab all keyboard devices.  A key press from
2177 	 * any of them will dismiss the tooltip window. */
2178 	gdk_seat_grab (seat, window, GDK_SEAT_CAPABILITY_KEYBOARD, TRUE, NULL, NULL, NULL, NULL);
2179 
2180 	g_signal_connect (
2181 		pevent->tooltip, "key-press-event",
2182 		G_CALLBACK (tooltip_key_event), data->cal_view);
2183 	pevent->timeout = -1;
2184 
2185 	g_object_set_data (G_OBJECT (data->cal_view), "tooltip-window", pevent->tooltip);
2186 	g_object_unref (newcomp);
2187 
2188 	return FALSE;
2189 }
2190 
2191 static gboolean
icomp_contains_category(ICalComponent * icomp,const gchar * category)2192 icomp_contains_category (ICalComponent *icomp,
2193 			 const gchar *category)
2194 {
2195 	ICalProperty *prop;
2196 
2197 	g_return_val_if_fail (icomp != NULL && category != NULL, FALSE);
2198 
2199 	for (prop = i_cal_component_get_first_property (icomp, I_CAL_CATEGORIES_PROPERTY);
2200 	     prop;
2201 	     g_object_unref (prop), prop = i_cal_component_get_next_property (icomp, I_CAL_CATEGORIES_PROPERTY)) {
2202 		const gchar *value = i_cal_property_get_categories (prop);
2203 
2204 		if (g_strcmp0 (category, value) == 0) {
2205 			g_object_unref (prop);
2206 			return TRUE;
2207 		}
2208 	}
2209 
2210 	return FALSE;
2211 }
2212 
2213 /* e_calendar_view_dup_component_summary returns summary of icomp,
2214  * and for type of birthday or anniversary it appends number of years since
2215  * beginning. Free the returned string with g_free(), when no longer needed.
2216  */
2217 gchar *
e_calendar_view_dup_component_summary(ICalComponent * icomp)2218 e_calendar_view_dup_component_summary (ICalComponent *icomp)
2219 {
2220 	const gchar *summary;
2221 	gchar *res = NULL;
2222 
2223 	g_return_val_if_fail (icomp != NULL, NULL);
2224 
2225 	summary = i_cal_component_get_summary (icomp);
2226 
2227 	if (icomp_contains_category (icomp, _("Birthday")) ||
2228 	    icomp_contains_category (icomp, _("Anniversary"))) {
2229 		gchar *since_year_str;
2230 
2231 		since_year_str = e_cal_util_component_dup_x_property (icomp, "X-EVOLUTION-SINCE-YEAR");
2232 
2233 		if (since_year_str) {
2234 			ICalTime *dtstart;
2235 			gint since_year;
2236 
2237 			since_year = atoi (since_year_str);
2238 
2239 			dtstart = i_cal_component_get_dtstart (icomp);
2240 
2241 			if (since_year > 0 && dtstart && i_cal_time_is_valid_time (dtstart) &&
2242 			    i_cal_time_get_year (dtstart) - since_year > 0) {
2243 				/* Translators: the '%s' stands for a component summary, the '%d' for the years.
2244 				   The string is used for Birthday & Anniversary events where the first year is
2245 				   know, constructing a summary which also shows how many years the birthday or
2246 				   anniversary is for. Example: "Birthday: John Doe (13)" */
2247 				res = g_strdup_printf (C_("BirthdaySummary", "%s (%d)"), summary ? summary : "", i_cal_time_get_year (dtstart) - since_year);
2248 			}
2249 
2250 			g_clear_object (&dtstart);
2251 			g_free (since_year_str);
2252 		}
2253 	}
2254 
2255 	if (!res)
2256 		res = g_strdup (summary ? summary : "");
2257 
2258 	e_cal_model_until_sanitize_text_value (res, -1);
2259 
2260 	return res;
2261 }
2262 
2263 /* A callback for e_cal_ops_create_component(), whose @user_data is an ECalendarView instance */
2264 void
e_calendar_view_component_created_cb(ECalModel * model,ECalClient * client,ICalComponent * original_icomp,const gchar * new_uid,gpointer user_data)2265 e_calendar_view_component_created_cb (ECalModel *model,
2266 				      ECalClient *client,
2267 				      ICalComponent *original_icomp,
2268 				      const gchar *new_uid,
2269 				      gpointer user_data)
2270 {
2271 	ECalendarView *cal_view = user_data;
2272 
2273 	g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view));
2274 
2275 	e_cal_model_emit_object_created (model, client);
2276 }
2277 
2278 void
draw_curved_rectangle(cairo_t * cr,gdouble x0,gdouble y0,gdouble rect_width,gdouble rect_height,gdouble radius)2279 draw_curved_rectangle (cairo_t *cr,
2280                        gdouble x0,
2281                        gdouble y0,
2282                        gdouble rect_width,
2283                        gdouble rect_height,
2284                        gdouble radius)
2285 {
2286 	gdouble x1, y1;
2287 
2288 	x1 = x0 + rect_width;
2289 	y1 = y0 + rect_height;
2290 
2291 	if (!rect_width || !rect_height)
2292 	    return;
2293 	if (rect_width / 2 < radius) {
2294 	    if (rect_height / 2 < radius) {
2295 		cairo_move_to  (cr, x0, (y0 + y1) / 2);
2296 		cairo_curve_to (cr, x0 ,y0, x0, y0, (x0 + x1) / 2, y0);
2297 		cairo_curve_to (cr, x1, y0, x1, y0, x1, (y0 + y1) / 2);
2298 		cairo_curve_to (cr, x1, y1, x1, y1, (x1 + x0) / 2, y1);
2299 		cairo_curve_to (cr, x0, y1, x0, y1, x0, (y0 + y1) / 2);
2300 	    } else {
2301 		cairo_move_to  (cr, x0, y0 + radius);
2302 		cairo_curve_to (cr, x0 ,y0, x0, y0, (x0 + x1) / 2, y0);
2303 		cairo_curve_to (cr, x1, y0, x1, y0, x1, y0 + radius);
2304 		cairo_line_to (cr, x1 , y1 - radius);
2305 		cairo_curve_to (cr, x1, y1, x1, y1, (x1 + x0) / 2, y1);
2306 		cairo_curve_to (cr, x0, y1, x0, y1, x0, y1- radius);
2307 		}
2308 	} else {
2309 	    if (rect_height / 2 < radius) {
2310 		cairo_move_to  (cr, x0, (y0 + y1) / 2);
2311 		cairo_curve_to (cr, x0 , y0, x0 , y0, x0 + radius, y0);
2312 		cairo_line_to (cr, x1 - radius, y0);
2313 		cairo_curve_to (cr, x1, y0, x1, y0, x1, (y0 + y1) / 2);
2314 		cairo_curve_to (cr, x1, y1, x1, y1, x1 - radius, y1);
2315 		cairo_line_to (cr, x0 + radius, y1);
2316 		cairo_curve_to (cr, x0, y1, x0, y1, x0, (y0 + y1) / 2);
2317 	    } else {
2318 		cairo_move_to  (cr, x0, y0 + radius);
2319 		cairo_curve_to (cr, x0 , y0, x0 , y0, x0 + radius, y0);
2320 		cairo_line_to (cr, x1 - radius, y0);
2321 		cairo_curve_to (cr, x1, y0, x1, y0, x1, y0 + radius);
2322 		cairo_line_to (cr, x1 , y1 - radius);
2323 		cairo_curve_to (cr, x1, y1, x1, y1, x1 - radius, y1);
2324 		cairo_line_to (cr, x0 + radius, y1);
2325 		cairo_curve_to (cr, x0, y1, x0, y1, x0, y1- radius);
2326 		}
2327 	}
2328 	cairo_close_path (cr);
2329 }
2330 
2331 /* returns either light or dark yellow, based on the base_background,
2332  * which is the default background color */
2333 GdkColor
get_today_background(const GdkColor base_background)2334 get_today_background (const GdkColor base_background)
2335 {
2336 	GdkColor res = base_background;
2337 
2338 	if (res.red > 0x7FFF) {
2339 		/* light yellow for a light theme */
2340 		res.red = 0xFFFF;
2341 		res.green = 0xFFFF;
2342 		res.blue = 0xC0C0;
2343 	} else {
2344 		/* dark yellow for a dark theme */
2345 		res.red = 0x3F3F;
2346 		res.green = 0x3F3F;
2347 		res.blue = 0x0000;
2348 	}
2349 
2350 	return res;
2351 }
2352 
2353 gboolean
is_comp_data_valid_func(ECalendarViewEvent * event,const gchar * location)2354 is_comp_data_valid_func (ECalendarViewEvent *event,
2355                          const gchar *location)
2356 {
2357 	g_return_val_if_fail (location != NULL, FALSE);
2358 
2359 	if (!event) {
2360 		g_warning ("%s: event is NULL", location);
2361 		return FALSE;
2362 	}
2363 
2364 	if (!event->comp_data) {
2365 		g_warning ("%s: event's (%p) comp_data is NULL", location, event);
2366 		return FALSE;
2367 	}
2368 
2369 	return TRUE;
2370 }
2371 
2372 gboolean
is_array_index_in_bounds_func(GArray * array,gint index,const gchar * location)2373 is_array_index_in_bounds_func (GArray *array,
2374                                gint index,
2375                                const gchar *location)
2376 {
2377 	g_return_val_if_fail (location != NULL, FALSE);
2378 
2379 	if (!array) {
2380 		g_warning ("%s: array is NULL", location);
2381 		return FALSE;
2382 	}
2383 
2384 	if (index < 0 || index >= array->len) {
2385 		g_warning ("%s: index %d is out of bounds [0,%d) at array %p", location, index, array->len, array);
2386 		return FALSE;
2387 	}
2388 
2389 	return TRUE;
2390 }
2391 
2392 gboolean
e_calendar_view_is_editing(ECalendarView * cal_view)2393 e_calendar_view_is_editing (ECalendarView *cal_view)
2394 {
2395 	static gboolean in = FALSE;
2396 	gboolean is_editing = FALSE;
2397 
2398 	g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), FALSE);
2399 
2400 	/* this should be called from the main thread only,
2401 	 * and each descendant overrides the property,
2402 	 * thus might cause no call recursion */
2403 	if (in) {
2404 		g_warn_if_reached ();
2405 		return FALSE;
2406 	}
2407 
2408 	in = TRUE;
2409 
2410 	g_object_get (G_OBJECT (cal_view), "is-editing", &is_editing, NULL);
2411 
2412 	in = FALSE;
2413 
2414 	return is_editing;
2415 }
2416 
2417 /* Returns text description of the current view. */
2418 gchar *
e_calendar_view_get_description_text(ECalendarView * cal_view)2419 e_calendar_view_get_description_text (ECalendarView *cal_view)
2420 {
2421 	ECalendarViewClass *klass;
2422 
2423 	g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), NULL);
2424 
2425 	klass = E_CALENDAR_VIEW_GET_CLASS (cal_view);
2426 	g_return_val_if_fail (klass != NULL, NULL);
2427 
2428 	if (klass->get_description_text)
2429 		return klass->get_description_text (cal_view);
2430 
2431 	return NULL;
2432 }
2433 
2434 void
e_calendar_view_move_view_range(ECalendarView * cal_view,ECalendarViewMoveType mode_type,time_t exact_date)2435 e_calendar_view_move_view_range (ECalendarView *cal_view,
2436 				 ECalendarViewMoveType mode_type,
2437 				 time_t exact_date)
2438 {
2439 	g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view));
2440 
2441 	g_signal_emit (cal_view, signals[MOVE_VIEW_RANGE], 0, mode_type, (gint64) exact_date);
2442 }
2443 
2444 gboolean
e_calendar_view_get_allow_direct_summary_edit(ECalendarView * cal_view)2445 e_calendar_view_get_allow_direct_summary_edit (ECalendarView *cal_view)
2446 {
2447 	g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), FALSE);
2448 
2449 	return cal_view->priv->allow_direct_summary_edit;
2450 }
2451 
2452 void
e_calendar_view_set_allow_direct_summary_edit(ECalendarView * cal_view,gboolean allow)2453 e_calendar_view_set_allow_direct_summary_edit (ECalendarView *cal_view,
2454 					       gboolean allow)
2455 {
2456 	g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view));
2457 
2458 	if ((cal_view->priv->allow_direct_summary_edit ? 1 : 0) == (allow ? 1 : 0))
2459 		return;
2460 
2461 	cal_view->priv->allow_direct_summary_edit = allow;
2462 
2463 	g_object_notify (G_OBJECT (cal_view), "allow-direct-summary-edit");
2464 }
2465