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