Bug 793125 - Crash due to popup menus left attached too long
[evolution.git] / src / calendar / gui / e-calendar-view.c
blob00c90a22e549ac318c29c8b83a5eb0f10f2065b1
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.
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.
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/>.
15 * Authors:
16 * Rodrigo Moya <rodrigo@ximian.com>
18 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
22 #include "evolution-config.h"
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>
32 #include <shell/e-shell.h>
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"
48 #define E_CALENDAR_VIEW_GET_PRIVATE(obj) \
49 (G_TYPE_INSTANCE_GET_PRIVATE \
50 ((obj), E_TYPE_CALENDAR_VIEW, ECalendarViewPrivate))
52 struct _ECalendarViewPrivate {
53 /* The calendar model we are monitoring */
54 ECalModel *model;
56 gint time_divisions;
57 GSList *selected_cut_list;
59 GtkTargetList *copy_target_list;
60 GtkTargetList *paste_target_list;
62 /* All keyboard devices are grabbed
63 * while a tooltip window is shown. */
64 GQueue grabbed_keyboards;
67 enum {
68 PROP_0,
69 PROP_COPY_TARGET_LIST,
70 PROP_MODEL,
71 PROP_PASTE_TARGET_LIST,
72 PROP_TIME_DIVISIONS,
73 PROP_IS_EDITING
76 /* FIXME Why are we emitting these event signals here? Can't the model just be listened to? */
77 /* Signal IDs */
78 enum {
79 POPUP_EVENT,
80 SELECTION_CHANGED,
81 SELECTED_TIME_CHANGED,
82 TIMEZONE_CHANGED,
83 EVENT_CHANGED,
84 EVENT_ADDED,
85 OPEN_EVENT,
86 MOVE_VIEW_RANGE,
87 LAST_SIGNAL
90 static guint signals[LAST_SIGNAL];
92 static void calendar_view_selectable_init (ESelectableInterface *iface);
94 G_DEFINE_ABSTRACT_TYPE_WITH_CODE (
95 ECalendarView, e_calendar_view, GTK_TYPE_GRID,
96 G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE, NULL)
97 G_IMPLEMENT_INTERFACE (E_TYPE_SELECTABLE, calendar_view_selectable_init));
99 static void
100 calendar_view_add_retract_data (ECalComponent *comp,
101 const gchar *retract_comment,
102 ECalObjModType mod)
104 icalcomponent *icalcomp = NULL;
105 icalproperty *icalprop = NULL;
107 icalcomp = e_cal_component_get_icalcomponent (comp);
108 if (retract_comment && *retract_comment)
109 icalprop = icalproperty_new_x (retract_comment);
110 else
111 icalprop = icalproperty_new_x ("0");
112 icalproperty_set_x_name (icalprop, "X-EVOLUTION-RETRACT-COMMENT");
113 icalcomponent_add_property (icalcomp, icalprop);
115 if (mod == E_CAL_OBJ_MOD_ALL)
116 icalprop = icalproperty_new_x ("All");
117 else
118 icalprop = icalproperty_new_x ("This");
119 icalproperty_set_x_name (icalprop, "X-EVOLUTION-RECUR-MOD");
120 icalcomponent_add_property (icalcomp, icalprop);
123 static gboolean
124 calendar_view_check_for_retract (ECalComponent *comp,
125 ECalClient *client)
127 ECalComponentOrganizer organizer;
128 const gchar *strip;
129 gchar *email = NULL;
130 gboolean ret_val;
132 if (!e_cal_component_has_attendees (comp))
133 return FALSE;
135 if (!e_cal_client_check_save_schedules (client))
136 return FALSE;
138 e_cal_component_get_organizer (comp, &organizer);
139 strip = itip_strip_mailto (organizer.value);
141 ret_val =
142 e_client_get_backend_property_sync (E_CLIENT (client), CAL_BACKEND_PROPERTY_CAL_EMAIL_ADDRESS, &email, NULL, NULL) &&
143 (g_ascii_strcasecmp (email, strip) == 0);
145 g_free (email);
147 return ret_val;
150 static void
151 calendar_view_delete_event (ECalendarView *cal_view,
152 ECalendarViewEvent *event,
153 gboolean only_occurrence)
155 ECalModel *model;
156 ECalComponent *comp;
157 ECalComponentVType vtype;
158 ESourceRegistry *registry;
159 gboolean delete = TRUE;
161 if (!is_comp_data_valid (event))
162 return;
164 model = e_calendar_view_get_model (cal_view);
165 registry = e_cal_model_get_registry (model);
167 comp = e_cal_component_new ();
168 e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (event->comp_data->icalcomp));
169 vtype = e_cal_component_get_vtype (comp);
171 /*FIXME remove it once the we dont set the recurrence id for all the generated instances */
172 if (!only_occurrence && !e_cal_client_check_recurrences_no_master (event->comp_data->client))
173 e_cal_component_set_recurid (comp, NULL);
175 /*FIXME Retract should be moved to Groupwise features plugin */
176 if (calendar_view_check_for_retract (comp, event->comp_data->client)) {
177 gchar *retract_comment = NULL;
178 gboolean retract = FALSE;
180 delete = e_cal_dialogs_prompt_retract (GTK_WIDGET (cal_view), comp, &retract_comment, &retract);
181 if (retract) {
182 icalcomponent *icalcomp;
184 calendar_view_add_retract_data (comp, retract_comment, E_CAL_OBJ_MOD_ALL);
185 icalcomp = e_cal_component_get_icalcomponent (comp);
186 icalcomponent_set_method (icalcomp, ICAL_METHOD_CANCEL);
188 e_cal_ops_send_component (model, event->comp_data->client, icalcomp);
190 } else if (e_cal_model_get_confirm_delete (model))
191 delete = e_cal_dialogs_delete_component (
192 comp, FALSE, 1, vtype, GTK_WIDGET (cal_view));
194 if (delete) {
195 const gchar *uid;
196 gchar *rid;
198 rid = e_cal_component_get_recurid_as_string (comp);
200 if (itip_has_any_attendees (comp) &&
201 (itip_organizer_is_user (registry, comp, event->comp_data->client) ||
202 itip_sentby_is_user (registry, comp, event->comp_data->client))
203 && e_cal_dialogs_cancel_component ((GtkWindow *) gtk_widget_get_toplevel (GTK_WIDGET (cal_view)),
204 event->comp_data->client,
205 comp, TRUE)) {
206 if (only_occurrence && !e_cal_component_is_instance (comp)) {
207 ECalComponentRange range;
209 /* set the recurrence ID of the object we send */
210 range.type = E_CAL_COMPONENT_RANGE_SINGLE;
211 e_cal_component_get_dtstart (comp, &range.datetime);
212 range.datetime.value->is_date = 1;
213 e_cal_component_set_recurid (comp, &range);
215 e_cal_component_free_datetime (&range.datetime);
218 itip_send_component_with_model (model, E_CAL_COMPONENT_METHOD_CANCEL,
219 comp, event->comp_data->client, NULL, NULL,
220 NULL, TRUE, FALSE, FALSE);
223 e_cal_component_get_uid (comp, &uid);
224 if (!uid || !*uid) {
225 g_object_unref (comp);
226 g_free (rid);
227 return;
230 if (only_occurrence) {
231 if (e_cal_component_is_instance (comp)) {
232 e_cal_ops_remove_component (model, event->comp_data->client, uid, rid, E_CAL_OBJ_MOD_THIS, FALSE);
233 } else {
234 struct icaltimetype instance_rid;
235 ECalComponentDateTime dt;
236 icaltimezone *zone = NULL;
238 e_cal_component_get_dtstart (comp, &dt);
240 if (dt.tzid) {
241 GError *local_error = NULL;
243 e_cal_client_get_timezone_sync (event->comp_data->client, dt.tzid, &zone, NULL, &local_error);
244 if (local_error != NULL) {
245 zone = e_calendar_view_get_timezone (cal_view);
246 g_clear_error (&local_error);
248 } else {
249 zone = e_calendar_view_get_timezone (cal_view);
252 e_cal_component_free_datetime (&dt);
254 instance_rid = icaltime_from_timet_with_zone (
255 event->comp_data->instance_start,
256 TRUE, zone ? zone : icaltimezone_get_utc_timezone ());
257 e_cal_util_remove_instances (event->comp_data->icalcomp, instance_rid, E_CAL_OBJ_MOD_THIS);
258 e_cal_ops_modify_component (model, event->comp_data->client, event->comp_data->icalcomp,
259 E_CAL_OBJ_MOD_THIS, E_CAL_OPS_SEND_FLAG_DONT_SEND);
261 } else if (e_cal_util_component_is_instance (event->comp_data->icalcomp) ||
262 e_cal_util_component_has_recurrences (event->comp_data->icalcomp))
263 e_cal_ops_remove_component (model, event->comp_data->client, uid, rid, E_CAL_OBJ_MOD_ALL, FALSE);
264 else
265 e_cal_ops_remove_component (model, event->comp_data->client, uid, NULL, E_CAL_OBJ_MOD_THIS, FALSE);
267 g_free (rid);
270 g_object_unref (comp);
273 static void
274 calendar_view_set_model (ECalendarView *calendar_view,
275 ECalModel *model)
277 g_return_if_fail (calendar_view->priv->model == NULL);
278 g_return_if_fail (E_IS_CAL_MODEL (model));
280 calendar_view->priv->model = g_object_ref (model);
283 static void
284 calendar_view_set_property (GObject *object,
285 guint property_id,
286 const GValue *value,
287 GParamSpec *pspec)
289 switch (property_id) {
290 case PROP_MODEL:
291 calendar_view_set_model (
292 E_CALENDAR_VIEW (object),
293 g_value_get_object (value));
294 return;
296 case PROP_TIME_DIVISIONS:
297 e_calendar_view_set_time_divisions (
298 E_CALENDAR_VIEW (object),
299 g_value_get_int (value));
300 return;
303 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
306 static void
307 calendar_view_get_property (GObject *object,
308 guint property_id,
309 GValue *value,
310 GParamSpec *pspec)
312 switch (property_id) {
313 case PROP_COPY_TARGET_LIST:
314 g_value_set_boxed (
315 value, e_calendar_view_get_copy_target_list (
316 E_CALENDAR_VIEW (object)));
317 return;
319 case PROP_MODEL:
320 g_value_set_object (
321 value, e_calendar_view_get_model (
322 E_CALENDAR_VIEW (object)));
323 return;
325 case PROP_PASTE_TARGET_LIST:
326 g_value_set_boxed (
327 value, e_calendar_view_get_paste_target_list (
328 E_CALENDAR_VIEW (object)));
329 return;
331 case PROP_TIME_DIVISIONS:
332 g_value_set_int (
333 value, e_calendar_view_get_time_divisions (
334 E_CALENDAR_VIEW (object)));
335 return;
337 case PROP_IS_EDITING:
338 g_value_set_boolean (value, e_calendar_view_is_editing (E_CALENDAR_VIEW (object)));
339 return;
342 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
345 static void
346 calendar_view_dispose (GObject *object)
348 ECalendarViewPrivate *priv;
350 priv = E_CALENDAR_VIEW_GET_PRIVATE (object);
352 if (priv->model != NULL) {
353 g_signal_handlers_disconnect_matched (
354 priv->model, G_SIGNAL_MATCH_DATA,
355 0, 0, NULL, NULL, object);
356 g_object_unref (priv->model);
357 priv->model = NULL;
360 if (priv->copy_target_list != NULL) {
361 gtk_target_list_unref (priv->copy_target_list);
362 priv->copy_target_list = NULL;
365 if (priv->paste_target_list != NULL) {
366 gtk_target_list_unref (priv->paste_target_list);
367 priv->paste_target_list = NULL;
370 if (priv->selected_cut_list) {
371 g_slist_foreach (priv->selected_cut_list, (GFunc) g_object_unref, NULL);
372 g_slist_free (priv->selected_cut_list);
373 priv->selected_cut_list = NULL;
376 while (!g_queue_is_empty (&priv->grabbed_keyboards)) {
377 GdkDevice *keyboard;
378 keyboard = g_queue_pop_head (&priv->grabbed_keyboards);
379 gdk_device_ungrab (keyboard, GDK_CURRENT_TIME);
380 g_object_unref (keyboard);
383 /* Chain up to parent's dispose() method. */
384 G_OBJECT_CLASS (e_calendar_view_parent_class)->dispose (object);
387 static void
388 calendar_view_constructed (GObject *object)
390 /* Do this after calendar_view_init() so extensions can query
391 * the GType accurately. See GInstanceInitFunc documentation
392 * for details of the problem. */
393 e_extensible_load_extensions (E_EXTENSIBLE (object));
395 /* Chain up to parent's constructed() method. */
396 G_OBJECT_CLASS (e_calendar_view_parent_class)->constructed (object);
399 static void
400 calendar_view_update_actions (ESelectable *selectable,
401 EFocusTracker *focus_tracker,
402 GdkAtom *clipboard_targets,
403 gint n_clipboard_targets)
405 ECalendarView *view;
406 GtkAction *action;
407 GtkTargetList *target_list;
408 GList *list, *iter;
409 gboolean can_paste = FALSE;
410 gboolean sources_are_editable = TRUE;
411 gboolean recurring = FALSE;
412 gboolean is_editing;
413 gboolean sensitive;
414 const gchar *tooltip;
415 gint n_selected;
416 gint ii;
418 view = E_CALENDAR_VIEW (selectable);
419 is_editing = e_calendar_view_is_editing (view);
421 list = e_calendar_view_get_selected_events (view);
422 n_selected = g_list_length (list);
424 for (iter = list; iter != NULL; iter = iter->next) {
425 ECalendarViewEvent *event = iter->data;
426 ECalClient *client;
427 icalcomponent *icalcomp;
429 if (event == NULL || event->comp_data == NULL)
430 continue;
432 client = event->comp_data->client;
433 icalcomp = event->comp_data->icalcomp;
435 sources_are_editable = sources_are_editable && !e_client_is_readonly (E_CLIENT (client));
437 recurring |=
438 e_cal_util_component_is_instance (icalcomp) ||
439 e_cal_util_component_has_recurrences (icalcomp);
442 g_list_free (list);
444 target_list = e_selectable_get_paste_target_list (selectable);
445 for (ii = 0; ii < n_clipboard_targets && !can_paste; ii++)
446 can_paste = gtk_target_list_find (
447 target_list, clipboard_targets[ii], NULL);
449 action = e_focus_tracker_get_cut_clipboard_action (focus_tracker);
450 sensitive = (n_selected > 0) && sources_are_editable && !is_editing;
451 tooltip = _("Cut selected events to the clipboard");
452 gtk_action_set_sensitive (action, sensitive);
453 gtk_action_set_tooltip (action, tooltip);
455 action = e_focus_tracker_get_copy_clipboard_action (focus_tracker);
456 sensitive = (n_selected > 0) && !is_editing;
457 tooltip = _("Copy selected events to the clipboard");
458 gtk_action_set_sensitive (action, sensitive);
459 gtk_action_set_tooltip (action, tooltip);
461 action = e_focus_tracker_get_paste_clipboard_action (focus_tracker);
462 sensitive = sources_are_editable && can_paste && !is_editing;
463 tooltip = _("Paste events from the clipboard");
464 gtk_action_set_sensitive (action, sensitive);
465 gtk_action_set_tooltip (action, tooltip);
467 action = e_focus_tracker_get_delete_selection_action (focus_tracker);
468 sensitive = (n_selected > 0) && sources_are_editable && !recurring && !is_editing;
469 tooltip = _("Delete selected events");
470 gtk_action_set_sensitive (action, sensitive);
471 gtk_action_set_tooltip (action, tooltip);
474 static void
475 calendar_view_cut_clipboard (ESelectable *selectable)
477 ECalendarView *cal_view;
478 ECalendarViewPrivate *priv;
479 GList *selected, *l;
481 cal_view = E_CALENDAR_VIEW (selectable);
482 priv = cal_view->priv;
484 selected = e_calendar_view_get_selected_events (cal_view);
485 if (!selected)
486 return;
488 e_selectable_copy_clipboard (selectable);
490 for (l = selected; l != NULL; l = g_list_next (l)) {
491 ECalendarViewEvent *event = (ECalendarViewEvent *) l->data;
493 priv->selected_cut_list = g_slist_prepend (priv->selected_cut_list, g_object_ref (event->comp_data));
496 g_list_free (selected);
499 static void
500 add_related_timezones (icalcomponent *des_icalcomp,
501 icalcomponent *src_icalcomp,
502 ECalClient *client)
504 icalproperty_kind look_in[] = {
505 ICAL_DTSTART_PROPERTY,
506 ICAL_DTEND_PROPERTY,
507 ICAL_NO_PROPERTY
509 gint i;
511 g_return_if_fail (des_icalcomp != NULL);
512 g_return_if_fail (src_icalcomp != NULL);
513 g_return_if_fail (client != NULL);
515 for (i = 0; look_in[i] != ICAL_NO_PROPERTY; i++) {
516 icalproperty *prop = icalcomponent_get_first_property (src_icalcomp, look_in[i]);
518 if (prop) {
519 icalparameter *par = icalproperty_get_first_parameter (prop, ICAL_TZID_PARAMETER);
521 if (par) {
522 const gchar *tzid = icalparameter_get_tzid (par);
524 if (tzid) {
525 GError *error = NULL;
526 icaltimezone *zone = NULL;
528 e_cal_client_get_timezone_sync (
529 client, tzid, &zone, NULL, &error);
530 if (error != NULL) {
531 g_warning (
532 "%s: Cannot get timezone for '%s'. %s",
533 G_STRFUNC, tzid, error->message);
534 g_error_free (error);
535 } else if (zone &&
536 icalcomponent_get_timezone (des_icalcomp, icaltimezone_get_tzid (zone)) == NULL) {
537 /* do not duplicate timezones in the component */
538 icalcomponent *vtz_comp;
540 vtz_comp = icaltimezone_get_component (zone);
541 if (vtz_comp)
542 icalcomponent_add_component (des_icalcomp, icalcomponent_new_clone (vtz_comp));
550 static void
551 calendar_view_copy_clipboard (ESelectable *selectable)
553 ECalendarView *cal_view;
554 ECalendarViewPrivate *priv;
555 GList *selected, *l;
556 gchar *comp_str;
557 icalcomponent *vcal_comp;
558 icalcomponent *new_icalcomp;
559 ECalendarViewEvent *event;
560 GtkClipboard *clipboard;
562 cal_view = E_CALENDAR_VIEW (selectable);
563 priv = cal_view->priv;
565 selected = e_calendar_view_get_selected_events (cal_view);
566 if (!selected)
567 return;
569 if (priv->selected_cut_list) {
570 g_slist_foreach (priv->selected_cut_list, (GFunc) g_object_unref, NULL);
571 g_slist_free (priv->selected_cut_list);
572 priv->selected_cut_list = NULL;
575 /* create top-level VCALENDAR component and add VTIMEZONE's */
576 vcal_comp = e_cal_util_new_top_level ();
577 for (l = selected; l != NULL; l = l->next) {
578 event = (ECalendarViewEvent *) l->data;
580 if (event && is_comp_data_valid (event)) {
581 e_cal_util_add_timezones_from_component (vcal_comp, event->comp_data->icalcomp);
583 add_related_timezones (vcal_comp, event->comp_data->icalcomp, event->comp_data->client);
587 for (l = selected; l != NULL; l = l->next) {
588 event = (ECalendarViewEvent *) l->data;
590 if (!is_comp_data_valid (event))
591 continue;
593 new_icalcomp = icalcomponent_new_clone (event->comp_data->icalcomp);
595 /* do not remove RECURRENCE-IDs from copied objects */
596 icalcomponent_add_component (vcal_comp, new_icalcomp);
599 comp_str = icalcomponent_as_ical_string_r (vcal_comp);
601 /* copy the VCALENDAR to the clipboard */
602 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
603 e_clipboard_set_calendar (clipboard, comp_str, -1);
604 gtk_clipboard_store (clipboard);
606 /* free memory */
607 icalcomponent_free (vcal_comp);
608 g_free (comp_str);
609 g_list_free (selected);
612 static void
613 calendar_view_component_created_cb (ECalModel *model,
614 ECalClient *client,
615 icalcomponent *original_icalcomp,
616 const gchar *new_uid,
617 gpointer user_data)
619 gboolean strip_alarms = TRUE;
620 ECalComponent *comp;
621 ESourceRegistry *registry;
622 GtkWidget *toplevel = user_data;
624 comp = e_cal_component_new_from_icalcomponent (icalcomponent_new_clone (original_icalcomp));
625 g_return_if_fail (comp != NULL);
627 registry = e_cal_model_get_registry (model);
629 if (new_uid)
630 e_cal_component_set_uid (comp, new_uid);
632 if (itip_has_any_attendees (comp) &&
633 (itip_organizer_is_user (registry, comp, client) ||
634 itip_sentby_is_user (registry, comp, client)) &&
635 e_cal_dialogs_send_component ((GtkWindow *) toplevel, client, comp, TRUE, &strip_alarms, NULL)) {
636 itip_send_component_with_model (model, E_CAL_COMPONENT_METHOD_REQUEST,
637 comp, client, NULL, NULL, NULL, strip_alarms, FALSE, FALSE);
640 g_object_unref (comp);
643 static void
644 e_calendar_view_add_event_sync (ECalModel *model,
645 ECalClient *client,
646 time_t dtstart,
647 icaltimezone *default_zone,
648 icalcomponent *icalcomp,
649 gboolean all_day,
650 gboolean is_day_view,
651 gint time_division,
652 GtkWidget *top_level)
654 ECalComponent *comp;
655 struct icaltimetype itime, old_dtstart, old_dtend;
656 time_t tt_start, tt_end, new_dtstart = 0;
657 struct icaldurationtype ic_dur, ic_oneday;
658 gchar *uid;
659 gint start_offset, end_offset;
660 gboolean all_day_event = FALSE;
662 start_offset = 0;
663 end_offset = 0;
665 old_dtstart = icalcomponent_get_dtstart (icalcomp);
666 tt_start = icaltime_as_timet (old_dtstart);
667 old_dtend = icalcomponent_get_dtend (icalcomp);
668 tt_end = icaltime_as_timet (old_dtend);
669 ic_dur = icaldurationtype_from_int (tt_end - tt_start);
671 if (icaldurationtype_as_int (ic_dur) > 60 *60 *24) {
672 /* This is a long event */
673 start_offset = old_dtstart.hour * 60 + old_dtstart.minute;
674 end_offset = old_dtstart.hour * 60 + old_dtend.minute;
677 ic_oneday = icaldurationtype_null_duration ();
678 ic_oneday.days = 1;
680 if (is_day_view) {
681 if (start_offset == 0 && end_offset == 0 && all_day)
682 all_day_event = TRUE;
684 if (all_day_event) {
685 ic_dur = ic_oneday;
686 } else if (icaldurationtype_as_int (ic_dur) >= 60 *60 *24 && !all_day) {
687 /* copy & paste from top canvas to main canvas */
688 ic_dur = icaldurationtype_from_int (time_division * 60);
691 if (all_day)
692 new_dtstart = dtstart + start_offset * 60;
693 else
694 new_dtstart = dtstart;
695 } else {
696 if (old_dtstart.is_date && old_dtend.is_date
697 && memcmp (&ic_dur, &ic_oneday, sizeof (ic_dur)) == 0) {
698 all_day_event = TRUE;
699 new_dtstart = dtstart;
700 } else {
701 icaltimetype new_time = icaltime_from_timet_with_zone (dtstart, FALSE, default_zone);
703 new_time.hour = old_dtstart.hour;
704 new_time.minute = old_dtstart.minute;
705 new_time.second = old_dtstart.second;
707 new_dtstart = icaltime_as_timet_with_zone (new_time, old_dtstart.zone ? old_dtstart.zone : default_zone);
711 itime = icaltime_from_timet_with_zone (new_dtstart, FALSE, old_dtstart.zone ? old_dtstart.zone : default_zone);
712 /* set the timezone properly */
713 itime.zone = old_dtstart.zone ? old_dtstart.zone : default_zone;
714 if (all_day_event)
715 itime.is_date = TRUE;
716 icalcomponent_set_dtstart (icalcomp, itime);
718 itime.is_date = FALSE;
719 itime = icaltime_add (itime, ic_dur);
720 if (all_day_event)
721 itime.is_date = TRUE;
722 icalcomponent_set_dtend (icalcomp, itime);
724 /* The new uid stuff can go away once we actually set it in the backend */
725 uid = e_util_generate_uid ();
726 comp = e_cal_component_new ();
727 e_cal_component_set_icalcomponent (
728 comp, icalcomponent_new_clone (icalcomp));
729 e_cal_component_set_uid (comp, uid);
730 g_free (uid);
732 e_cal_component_commit_sequence (comp);
734 e_cal_ops_create_component (model, client, e_cal_component_get_icalcomponent (comp),
735 calendar_view_component_created_cb, g_object_ref (top_level), g_object_unref);
737 g_object_unref (comp);
740 typedef struct {
741 ECalendarView *cal_view;
742 GSList *selected_cut_list; /* ECalModelComponent * */
743 GSList *copied_uids; /* gchar * */
744 gchar *ical_str;
745 time_t selection_start;
746 time_t selection_end;
747 gboolean is_day_view;
748 gint time_division;
749 GtkWidget *top_level;
750 gboolean success;
751 ECalClient *client;
752 } PasteClipboardData;
754 static void
755 paste_clipboard_data_free (gpointer ptr)
757 PasteClipboardData *pcd = ptr;
759 if (pcd) {
760 if (pcd->success && pcd->copied_uids && pcd->selected_cut_list) {
761 ECalModel *model;
762 ESourceRegistry *registry;
763 GSList *link;
765 model = e_calendar_view_get_model (pcd->cal_view);
766 registry = e_cal_model_get_registry (model);
768 for (link = pcd->selected_cut_list; link != NULL; link = g_slist_next (link)) {
769 ECalModelComponent *comp_data = (ECalModelComponent *) link->data;
770 ECalComponent *comp;
771 const gchar *uid;
772 GSList *found = NULL;
774 /* Remove them one by one after ensuring it has been copied to the destination successfully */
775 found = g_slist_find_custom (pcd->copied_uids, icalcomponent_get_uid (comp_data->icalcomp), (GCompareFunc) strcmp);
776 if (!found)
777 continue;
779 g_free (found->data);
780 pcd->copied_uids = g_slist_delete_link (pcd->copied_uids, found);
782 comp = e_cal_component_new ();
783 e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (comp_data->icalcomp));
785 if (itip_has_any_attendees (comp) &&
786 (itip_organizer_is_user (registry, comp, comp_data->client) ||
787 itip_sentby_is_user (registry, comp, comp_data->client))
788 && e_cal_dialogs_cancel_component ((GtkWindow *) pcd->top_level, comp_data->client, comp, TRUE))
789 itip_send_component_with_model (model, E_CAL_COMPONENT_METHOD_CANCEL,
790 comp, comp_data->client, NULL, NULL, NULL, TRUE, FALSE, TRUE);
792 e_cal_component_get_uid (comp, &uid);
793 if (e_cal_component_is_instance (comp)) {
794 gchar *rid = NULL;
796 /* when cutting detached instances, only cut that instance */
797 rid = e_cal_component_get_recurid_as_string (comp);
798 e_cal_ops_remove_component (model, comp_data->client, uid, rid, E_CAL_OBJ_MOD_THIS, TRUE);
799 g_free (rid);
800 } else {
801 e_cal_ops_remove_component (model, comp_data->client, uid, NULL, E_CAL_OBJ_MOD_ALL, FALSE);
804 g_object_unref (comp);
808 if (pcd->success && pcd->client) {
809 ECalModel *model;
811 model = e_calendar_view_get_model (pcd->cal_view);
812 e_cal_model_emit_object_created (model, pcd->client);
815 g_clear_object (&pcd->cal_view);
816 g_clear_object (&pcd->top_level);
817 g_clear_object (&pcd->client);
818 g_slist_free_full (pcd->selected_cut_list, g_object_unref);
819 g_slist_free_full (pcd->copied_uids, g_free);
820 g_free (pcd->ical_str);
821 g_free (pcd);
825 static void
826 cal_view_paste_clipboard_thread (EAlertSinkThreadJobData *job_data,
827 gpointer user_data,
828 GCancellable *cancellable,
829 GError **error)
831 PasteClipboardData *pcd = user_data;
832 icalcomponent *icalcomp;
833 icalcomponent_kind kind;
834 icaltimezone *default_zone;
835 ECalModel *model;
836 ESourceRegistry *registry;
837 ESource *source = NULL, *default_source = NULL;
838 EClientCache *client_cache;
839 EClient *e_client;
840 ECalClient *client = NULL;
841 const gchar *message;
842 const gchar *extension_name;
843 gchar *display_name;
844 guint copied_components = 1;
845 gboolean all_day;
846 GError *local_error = NULL;
848 g_return_if_fail (pcd != NULL);
850 icalcomp = icalparser_parse_string (pcd->ical_str);
851 if (!icalcomp) {
852 g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
853 _("Pasted text doesn’t contain valid iCalendar data"));
854 return;
857 model = e_calendar_view_get_model (pcd->cal_view);
858 registry = e_cal_model_get_registry (model);
860 switch (e_cal_model_get_component_kind (model)) {
861 case ICAL_VEVENT_COMPONENT:
862 default_source = e_source_registry_ref_default_calendar (registry);
863 extension_name = E_SOURCE_EXTENSION_CALENDAR;
864 message = _("Default calendar not found");
865 break;
866 case ICAL_VJOURNAL_COMPONENT:
867 default_source = e_source_registry_ref_default_memo_list (registry);
868 extension_name = E_SOURCE_EXTENSION_MEMO_LIST;
869 message = _("Default memo list not found");
870 break;
871 case ICAL_VTODO_COMPONENT:
872 default_source = e_source_registry_ref_default_task_list (registry);
873 extension_name = E_SOURCE_EXTENSION_TASK_LIST;
874 message = _("Default task list not found");
875 break;
876 default:
877 g_warn_if_reached ();
878 goto out;
881 source = e_source_registry_ref_source (registry, e_cal_model_get_default_source_uid (model));
882 if (!source) {
883 source = default_source;
884 default_source = NULL;
887 if (!source) {
888 const gchar *default_source_uid = e_cal_model_get_default_source_uid (model);
890 e_alert_sink_thread_job_set_alert_arg_0 (job_data, default_source_uid ? default_source_uid : "");
891 g_set_error_literal (&local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, message);
893 return;
896 display_name = e_util_get_source_full_name (registry, source);
897 e_alert_sink_thread_job_set_alert_arg_0 (job_data, display_name);
898 g_free (display_name);
899 client_cache = e_cal_model_get_client_cache (model);
901 e_client = e_client_cache_get_client_sync (client_cache, source, extension_name, 30, cancellable, &local_error);
902 if (!e_client) {
903 e_util_propagate_open_source_job_error (job_data, extension_name, local_error, error);
904 goto out;
907 client = E_CAL_CLIENT (e_client);
908 kind = icalcomponent_isa (icalcomp);
909 default_zone = e_cal_model_get_timezone (model);
910 all_day = pcd->selection_end - pcd->selection_start == 60 * 60 * 24;
911 copied_components = 0;
913 if (kind == ICAL_VCALENDAR_COMPONENT) {
914 icalcomponent *subcomp;
916 /* add timezones first, to have them ready */
917 for (subcomp = icalcomponent_get_first_component (icalcomp, ICAL_VTIMEZONE_COMPONENT);
918 subcomp;
919 subcomp = icalcomponent_get_next_component (icalcomp, ICAL_VTIMEZONE_COMPONENT)) {
920 icaltimezone *zone;
922 zone = icaltimezone_new ();
923 icaltimezone_set_component (zone, subcomp);
925 if (!e_cal_client_add_timezone_sync (client, zone, cancellable, error)) {
926 icaltimezone_free (zone, 1);
927 goto out;
930 icaltimezone_free (zone, 1);
933 for (subcomp = icalcomponent_get_first_component (icalcomp, ICAL_VEVENT_COMPONENT);
934 subcomp;
935 subcomp = icalcomponent_get_next_component (icalcomp, ICAL_VEVENT_COMPONENT)) {
936 if (e_cal_util_component_has_recurrences (subcomp)) {
937 icalproperty *icalprop = icalcomponent_get_first_property (subcomp, ICAL_RRULE_PROPERTY);
938 if (icalprop)
939 icalproperty_remove_parameter_by_name (icalprop, "X-EVOLUTION-ENDDATE");
942 e_calendar_view_add_event_sync (model, client, pcd->selection_start, default_zone, subcomp, all_day,
943 pcd->is_day_view, pcd->time_division, pcd->top_level);
945 copied_components++;
946 if (pcd->selected_cut_list)
947 pcd->copied_uids = g_slist_prepend (pcd->copied_uids, g_strdup (icalcomponent_get_uid (subcomp)));
949 } else if (kind == e_cal_model_get_component_kind (model)) {
950 e_calendar_view_add_event_sync (model, client, pcd->selection_start, default_zone, icalcomp, all_day,
951 pcd->is_day_view, pcd->time_division, pcd->top_level);
953 copied_components++;
954 if (pcd->selected_cut_list)
955 pcd->copied_uids = g_slist_prepend (pcd->copied_uids, g_strdup (icalcomponent_get_uid (icalcomp)));
958 pcd->success = !g_cancellable_is_cancelled (cancellable);
959 pcd->client = g_object_ref (client);
961 out:
962 if (!copied_components && !g_cancellable_is_cancelled (cancellable) && error && !*error)
963 g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("No suitable component found"));
965 icalcomponent_free (icalcomp);
966 g_clear_object (&source);
967 g_clear_object (&default_source);
968 g_clear_object (&client);
971 static void
972 calendar_view_paste_clipboard (ESelectable *selectable)
974 ECalModel *model;
975 ECalendarView *cal_view;
976 GtkClipboard *clipboard;
978 cal_view = E_CALENDAR_VIEW (selectable);
980 model = e_calendar_view_get_model (cal_view);
982 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
984 /* Paste text into an event being edited. */
985 if (gtk_clipboard_wait_is_text_available (clipboard)) {
986 ECalendarViewClass *class;
988 class = E_CALENDAR_VIEW_GET_CLASS (cal_view);
989 g_return_if_fail (class->paste_text != NULL);
991 class->paste_text (cal_view);
993 /* Paste iCalendar data into the view. */
994 } else if (e_clipboard_wait_is_calendar_available (clipboard)) {
995 PasteClipboardData *pcd;
996 ECalDataModel *data_model;
997 GCancellable *cancellable;
998 const gchar *alert_ident = NULL;
1000 switch (e_cal_model_get_component_kind (model)) {
1001 case ICAL_VEVENT_COMPONENT:
1002 alert_ident = "calendar:failed-create-event";
1003 break;
1004 case ICAL_VJOURNAL_COMPONENT:
1005 alert_ident = "calendar:failed-create-memo";
1006 break;
1007 case ICAL_VTODO_COMPONENT:
1008 alert_ident = "calendar:failed-create-task";
1009 break;
1010 default:
1011 g_warn_if_reached ();
1012 return;
1015 pcd = g_new0 (PasteClipboardData, 1);
1016 pcd->cal_view = g_object_ref (cal_view);
1017 pcd->selected_cut_list = cal_view->priv->selected_cut_list;
1018 cal_view->priv->selected_cut_list = NULL;
1019 pcd->copied_uids = NULL; /* gchar * */
1020 pcd->ical_str = e_clipboard_wait_for_calendar (clipboard);
1021 g_warn_if_fail (e_calendar_view_get_selected_time_range (cal_view, &pcd->selection_start, &pcd->selection_end));
1022 pcd->is_day_view = E_IS_DAY_VIEW (cal_view);
1023 if (pcd->is_day_view)
1024 pcd->time_division = e_calendar_view_get_time_divisions (cal_view);
1025 pcd->top_level = gtk_widget_get_toplevel (GTK_WIDGET (cal_view));
1026 if (pcd->top_level)
1027 g_object_ref (pcd->top_level);
1028 pcd->success = FALSE;
1029 pcd->client = NULL;
1031 data_model = e_cal_model_get_data_model (model);
1033 cancellable = e_cal_data_model_submit_thread_job (data_model, _("Pasting iCalendar data"), alert_ident,
1034 NULL, cal_view_paste_clipboard_thread, pcd, paste_clipboard_data_free);
1036 g_clear_object (&cancellable);
1040 static void
1041 calendar_view_delete_selection (ESelectable *selectable)
1043 ECalendarView *cal_view;
1044 GList *selected, *iter;
1046 cal_view = E_CALENDAR_VIEW (selectable);
1048 selected = e_calendar_view_get_selected_events (cal_view);
1050 for (iter = selected; iter != NULL; iter = iter->next) {
1051 ECalendarViewEvent *event = iter->data;
1053 /* XXX Why would this ever be NULL? */
1054 if (event == NULL)
1055 continue;
1057 calendar_view_delete_event (cal_view, event, FALSE);
1060 g_list_free (selected);
1063 static void
1064 e_calendar_view_class_init (ECalendarViewClass *class)
1066 GObjectClass *object_class;
1067 GtkWidgetClass *widget_class;
1068 GtkBindingSet *binding_set;
1070 g_type_class_add_private (class, sizeof (ECalendarViewPrivate));
1072 object_class = G_OBJECT_CLASS (class);
1073 object_class->set_property = calendar_view_set_property;
1074 object_class->get_property = calendar_view_get_property;
1075 object_class->dispose = calendar_view_dispose;
1076 object_class->constructed = calendar_view_constructed;
1078 class->selection_changed = NULL;
1079 class->selected_time_changed = NULL;
1080 class->event_changed = NULL;
1081 class->event_added = NULL;
1083 class->get_selected_events = NULL;
1084 class->get_selected_time_range = NULL;
1085 class->set_selected_time_range = NULL;
1086 class->get_visible_time_range = NULL;
1087 class->precalc_visible_time_range = NULL;
1088 class->update_query = NULL;
1089 class->open_event = e_calendar_view_open_event;
1090 class->paste_text = NULL;
1092 /* Inherited from ESelectableInterface */
1093 g_object_class_override_property (
1094 object_class,
1095 PROP_COPY_TARGET_LIST,
1096 "copy-target-list");
1098 g_object_class_install_property (
1099 object_class,
1100 PROP_MODEL,
1101 g_param_spec_object (
1102 "model",
1103 "Model",
1104 NULL,
1105 E_TYPE_CAL_MODEL,
1106 G_PARAM_READWRITE |
1107 G_PARAM_CONSTRUCT_ONLY));
1109 /* Inherited from ESelectableInterface */
1110 g_object_class_override_property (
1111 object_class,
1112 PROP_PASTE_TARGET_LIST,
1113 "paste-target-list");
1115 g_object_class_install_property (
1116 object_class,
1117 PROP_TIME_DIVISIONS,
1118 g_param_spec_int (
1119 "time-divisions",
1120 "Time Divisions",
1121 NULL,
1122 G_MININT,
1123 G_MAXINT,
1125 G_PARAM_READWRITE));
1127 g_object_class_install_property (
1128 object_class,
1129 PROP_IS_EDITING,
1130 g_param_spec_boolean (
1131 "is-editing",
1132 "Whether is in an editing mode",
1133 "Whether is in an editing mode",
1134 FALSE,
1135 G_PARAM_READABLE));
1137 signals[POPUP_EVENT] = g_signal_new (
1138 "popup-event",
1139 G_TYPE_FROM_CLASS (class),
1140 G_SIGNAL_RUN_FIRST,
1141 G_STRUCT_OFFSET (ECalendarViewClass, popup_event),
1142 NULL, NULL,
1143 g_cclosure_marshal_VOID__BOXED,
1144 G_TYPE_NONE, 1,
1145 GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
1147 signals[SELECTION_CHANGED] = g_signal_new (
1148 "selection-changed",
1149 G_TYPE_FROM_CLASS (class),
1150 G_SIGNAL_RUN_LAST,
1151 G_STRUCT_OFFSET (ECalendarViewClass, selection_changed),
1152 NULL, NULL,
1153 g_cclosure_marshal_VOID__VOID,
1154 G_TYPE_NONE, 0);
1156 signals[SELECTED_TIME_CHANGED] = g_signal_new (
1157 "selected-time-changed",
1158 G_TYPE_FROM_CLASS (class),
1159 G_SIGNAL_RUN_LAST,
1160 G_STRUCT_OFFSET (ECalendarViewClass, selected_time_changed),
1161 NULL, NULL,
1162 g_cclosure_marshal_VOID__VOID,
1163 G_TYPE_NONE, 0);
1165 signals[TIMEZONE_CHANGED] = g_signal_new (
1166 "timezone-changed",
1167 G_TYPE_FROM_CLASS (class),
1168 G_SIGNAL_RUN_LAST,
1169 G_STRUCT_OFFSET (ECalendarViewClass, timezone_changed),
1170 NULL, NULL,
1171 e_marshal_VOID__POINTER_POINTER,
1172 G_TYPE_NONE, 2,
1173 G_TYPE_POINTER,
1174 G_TYPE_POINTER);
1176 signals[EVENT_CHANGED] = g_signal_new (
1177 "event-changed",
1178 G_TYPE_FROM_CLASS (object_class),
1179 G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
1180 G_STRUCT_OFFSET (ECalendarViewClass, event_changed),
1181 NULL, NULL,
1182 g_cclosure_marshal_VOID__POINTER,
1183 G_TYPE_NONE, 1,
1184 G_TYPE_POINTER);
1186 signals[EVENT_ADDED] = g_signal_new (
1187 "event-added",
1188 G_TYPE_FROM_CLASS (object_class),
1189 G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
1190 G_STRUCT_OFFSET (ECalendarViewClass, event_added),
1191 NULL, NULL,
1192 g_cclosure_marshal_VOID__POINTER,
1193 G_TYPE_NONE, 1,
1194 G_TYPE_POINTER);
1196 signals[OPEN_EVENT] = g_signal_new (
1197 "open-event",
1198 G_TYPE_FROM_CLASS (class),
1199 G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
1200 G_STRUCT_OFFSET (ECalendarViewClass, open_event),
1201 NULL, NULL,
1202 g_cclosure_marshal_VOID__VOID,
1203 G_TYPE_NONE, 0);
1205 signals[MOVE_VIEW_RANGE] = g_signal_new (
1206 "move-view-range",
1207 G_TYPE_FROM_CLASS (object_class),
1208 G_SIGNAL_RUN_LAST,
1209 G_STRUCT_OFFSET (ECalendarViewClass, move_view_range),
1210 NULL, NULL,
1211 NULL, /* default marshal */
1212 G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_INT64);
1214 /* Key bindings */
1216 binding_set = gtk_binding_set_by_class (class);
1218 gtk_binding_entry_add_signal (
1219 binding_set, GDK_KEY_o, GDK_CONTROL_MASK, "open-event", 0);
1221 /* init the accessibility support for e_day_view */
1222 widget_class = GTK_WIDGET_CLASS (class);
1223 gtk_widget_class_set_accessible_type (widget_class, EA_TYPE_CAL_VIEW);
1226 static void
1227 e_calendar_view_init (ECalendarView *calendar_view)
1229 GtkTargetList *target_list;
1231 calendar_view->priv = E_CALENDAR_VIEW_GET_PRIVATE (calendar_view);
1233 /* Set this early to avoid a divide-by-zero during init. */
1234 calendar_view->priv->time_divisions = 30;
1236 target_list = gtk_target_list_new (NULL, 0);
1237 e_target_list_add_calendar_targets (target_list, 0);
1238 calendar_view->priv->copy_target_list = target_list;
1240 target_list = gtk_target_list_new (NULL, 0);
1241 e_target_list_add_calendar_targets (target_list, 0);
1242 calendar_view->priv->paste_target_list = target_list;
1245 static void
1246 calendar_view_selectable_init (ESelectableInterface *iface)
1248 iface->update_actions = calendar_view_update_actions;
1249 iface->cut_clipboard = calendar_view_cut_clipboard;
1250 iface->copy_clipboard = calendar_view_copy_clipboard;
1251 iface->paste_clipboard = calendar_view_paste_clipboard;
1252 iface->delete_selection = calendar_view_delete_selection;
1255 void
1256 e_calendar_view_popup_event (ECalendarView *calendar_view,
1257 GdkEvent *button_event)
1259 g_return_if_fail (E_IS_CALENDAR_VIEW (calendar_view));
1260 g_return_if_fail (button_event != NULL);
1262 g_signal_emit (calendar_view, signals[POPUP_EVENT], 0, button_event);
1265 ECalModel *
1266 e_calendar_view_get_model (ECalendarView *cal_view)
1268 g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), NULL);
1270 return cal_view->priv->model;
1273 icaltimezone *
1274 e_calendar_view_get_timezone (ECalendarView *cal_view)
1276 g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), NULL);
1277 return e_cal_model_get_timezone (cal_view->priv->model);
1280 void
1281 e_calendar_view_set_timezone (ECalendarView *cal_view,
1282 icaltimezone *zone)
1284 icaltimezone *old_zone;
1286 g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view));
1288 old_zone = e_cal_model_get_timezone (cal_view->priv->model);
1289 if (old_zone == zone)
1290 return;
1292 e_cal_model_set_timezone (cal_view->priv->model, zone);
1293 g_signal_emit (
1294 cal_view, signals[TIMEZONE_CHANGED], 0,
1295 old_zone, zone);
1298 GtkTargetList *
1299 e_calendar_view_get_copy_target_list (ECalendarView *cal_view)
1301 g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), NULL);
1303 return cal_view->priv->copy_target_list;
1306 GtkTargetList *
1307 e_calendar_view_get_paste_target_list (ECalendarView *cal_view)
1309 g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), NULL);
1311 return cal_view->priv->paste_target_list;
1314 gint
1315 e_calendar_view_get_time_divisions (ECalendarView *cal_view)
1317 g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), 0);
1319 return cal_view->priv->time_divisions;
1322 void
1323 e_calendar_view_set_time_divisions (ECalendarView *cal_view,
1324 gint time_divisions)
1326 g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view));
1328 if (cal_view->priv->time_divisions == time_divisions)
1329 return;
1331 cal_view->priv->time_divisions = time_divisions;
1333 g_object_notify (G_OBJECT (cal_view), "time-divisions");
1336 GList *
1337 e_calendar_view_get_selected_events (ECalendarView *cal_view)
1339 ECalendarViewClass *class;
1341 g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), NULL);
1343 class = E_CALENDAR_VIEW_GET_CLASS (cal_view);
1344 g_return_val_if_fail (class->get_selected_events != NULL, NULL);
1346 return class->get_selected_events (cal_view);
1349 gboolean
1350 e_calendar_view_get_selected_time_range (ECalendarView *cal_view,
1351 time_t *start_time,
1352 time_t *end_time)
1354 ECalendarViewClass *class;
1356 g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), FALSE);
1358 class = E_CALENDAR_VIEW_GET_CLASS (cal_view);
1359 g_return_val_if_fail (class->get_selected_time_range != NULL, FALSE);
1361 return class->get_selected_time_range (cal_view, start_time, end_time);
1364 void
1365 e_calendar_view_set_selected_time_range (ECalendarView *cal_view,
1366 time_t start_time,
1367 time_t end_time)
1369 ECalendarViewClass *class;
1371 g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view));
1373 /* Not all views implement this, so return silently. */
1374 class = E_CALENDAR_VIEW_GET_CLASS (cal_view);
1375 if (class->set_selected_time_range == NULL)
1376 return;
1378 class->set_selected_time_range (cal_view, start_time, end_time);
1381 gboolean
1382 e_calendar_view_get_visible_time_range (ECalendarView *cal_view,
1383 time_t *start_time,
1384 time_t *end_time)
1386 ECalendarViewClass *class;
1388 g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), FALSE);
1390 class = E_CALENDAR_VIEW_GET_CLASS (cal_view);
1391 g_return_val_if_fail (class->get_visible_time_range != NULL, FALSE);
1393 return class->get_visible_time_range (cal_view, start_time, end_time);
1396 void
1397 e_calendar_view_precalc_visible_time_range (ECalendarView *cal_view,
1398 time_t in_start_time,
1399 time_t in_end_time,
1400 time_t *out_start_time,
1401 time_t *out_end_time)
1403 ECalendarViewClass *class;
1405 g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view));
1407 /* Not all views implement this, so return silently. */
1408 class = E_CALENDAR_VIEW_GET_CLASS (cal_view);
1409 if (class->precalc_visible_time_range == NULL)
1410 return;
1412 class->precalc_visible_time_range (cal_view, in_start_time, in_end_time, out_start_time, out_end_time);
1415 void
1416 e_calendar_view_update_query (ECalendarView *cal_view)
1418 ECalendarViewClass *class;
1420 g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view));
1422 class = E_CALENDAR_VIEW_GET_CLASS (cal_view);
1423 g_return_if_fail (class->update_query != NULL);
1425 class->update_query (cal_view);
1428 void
1429 e_calendar_view_delete_selected_occurrence (ECalendarView *cal_view)
1431 ECalendarViewEvent *event;
1432 GList *selected;
1434 selected = e_calendar_view_get_selected_events (cal_view);
1435 if (!selected)
1436 return;
1438 event = (ECalendarViewEvent *) selected->data;
1439 if (is_comp_data_valid (event)) {
1440 calendar_view_delete_event (cal_view, event, TRUE);
1443 g_list_free (selected);
1446 void
1447 e_calendar_view_open_event (ECalendarView *cal_view)
1449 GList *selected;
1451 selected = e_calendar_view_get_selected_events (cal_view);
1452 if (selected) {
1453 ECalendarViewEvent *event = (ECalendarViewEvent *) selected->data;
1454 if (event && is_comp_data_valid (event))
1455 e_calendar_view_edit_appointment (cal_view, event->comp_data->client, event->comp_data->icalcomp, EDIT_EVENT_AUTODETECT);
1457 g_list_free (selected);
1462 * e_calendar_view_new_appointment_full
1463 * @cal_view: an #ECalendarView
1464 * @all_day: Whether create all day event or not.
1465 * @meeting: This is a meeting or an appointment.
1466 * @no_past_date: Don't create event in past date, use actual date instead
1467 * (if %TRUE).
1469 * Opens an event editor dialog for a new appointment. The appointment's
1470 * start and end times are set to the currently selected time range in
1471 * the calendar view.
1473 * When the selection is for all day and we don't need @all_day event,
1474 * then this do a rounding to the actual hour for actual day (today) and
1475 * to the 'day begins' from preferences in other selected day.
1477 void
1478 e_calendar_view_new_appointment_full (ECalendarView *cal_view,
1479 gboolean all_day,
1480 gboolean meeting,
1481 gboolean no_past_date)
1483 ECalModel *model;
1484 time_t dtstart, dtend, now;
1485 gboolean do_rounding = FALSE;
1487 g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view));
1489 model = e_calendar_view_get_model (cal_view);
1491 now = time (NULL);
1493 if (!e_calendar_view_get_selected_time_range (cal_view, &dtstart, &dtend)) {
1494 dtstart = now;
1495 dtend = dtstart + 3600;
1498 if (no_past_date && dtstart < now) {
1499 dtend = time_day_begin (now) + (dtend - dtstart);
1500 dtstart = time_day_begin (now);
1501 do_rounding = TRUE;
1504 /* We either need rounding or don't want to set all_day for this, we will rather use actual */
1505 /* time in this cases; dtstart should be a midnight in this case */
1506 if (do_rounding || (!all_day && (dtend - dtstart) == (60 * 60 * 24))) {
1507 struct tm local = *localtime (&now);
1508 gint time_div = e_calendar_view_get_time_divisions (cal_view);
1509 gint hours, mins;
1511 if (!time_div) /* Possible if your settings values aren't so nice */
1512 time_div = 30;
1514 if (time_day_begin (now) == time_day_begin (dtstart)) {
1515 /* same day as today */
1516 hours = local.tm_hour;
1517 mins = local.tm_min;
1519 /* round minutes to nearest time division, up or down */
1520 if ((mins % time_div) >= time_div / 2)
1521 mins += time_div;
1522 mins = (mins - (mins % time_div));
1523 } else {
1524 /* other day than today */
1525 hours = e_cal_model_get_work_day_start_hour (model);
1526 mins = e_cal_model_get_work_day_start_minute (model);
1529 dtstart = dtstart + (60 * 60 * hours) + (mins * 60);
1530 dtend = dtstart + (time_div * 60);
1533 e_cal_ops_new_component_editor_from_model (
1534 e_calendar_view_get_model (cal_view), NULL,
1535 dtstart, dtend, meeting, all_day);
1538 void
1539 e_calendar_view_new_appointment (ECalendarView *cal_view)
1541 g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view));
1543 e_calendar_view_new_appointment_full (cal_view, FALSE, FALSE, FALSE);
1546 /* Ensures the calendar is selected */
1547 static void
1548 object_created_cb (ECompEditor *comp_editor,
1549 ECalendarView *cal_view)
1551 e_cal_model_emit_object_created (e_calendar_view_get_model (cal_view), e_comp_editor_get_target_client (comp_editor));
1554 ECompEditor *
1555 e_calendar_view_open_event_with_flags (ECalendarView *cal_view,
1556 ECalClient *client,
1557 icalcomponent *icalcomp,
1558 guint32 flags)
1560 ECompEditor *comp_editor;
1561 EShell *shell;
1563 /* FIXME ECalendarView should own an EShell pointer. */
1564 shell = e_shell_get_default ();
1566 comp_editor = e_comp_editor_find_existing_for (e_client_get_source (E_CLIENT (client)), icalcomp);
1567 if (!comp_editor) {
1568 GtkWidget *toplevel;
1570 toplevel = gtk_widget_get_toplevel (GTK_WIDGET (cal_view));
1571 if (!GTK_IS_WINDOW (toplevel))
1572 toplevel = NULL;
1574 comp_editor = e_comp_editor_open_for_component (GTK_WINDOW (toplevel),
1575 shell, e_client_get_source (E_CLIENT (client)), icalcomp, flags);
1577 g_signal_connect (
1578 comp_editor, "object-created",
1579 G_CALLBACK (object_created_cb), cal_view);
1582 gtk_window_present (GTK_WINDOW (comp_editor));
1584 return comp_editor;
1588 * e_calendar_view_edit_appointment
1589 * @cal_view: A calendar view.
1590 * @client: Calendar client.
1591 * @icalcomp: The object to be edited.
1592 * @mode: one of #EEditEventMode
1594 * Opens an editor window to allow the user to edit the selected
1595 * object.
1597 void
1598 e_calendar_view_edit_appointment (ECalendarView *cal_view,
1599 ECalClient *client,
1600 icalcomponent *icalcomp,
1601 EEditEventMode mode)
1603 ECalModel *model;
1604 ESourceRegistry *registry;
1605 guint32 flags = 0;
1607 g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view));
1608 g_return_if_fail (E_IS_CAL_CLIENT (client));
1609 g_return_if_fail (icalcomp != NULL);
1611 model = e_calendar_view_get_model (cal_view);
1612 registry = e_cal_model_get_registry (model);
1614 if ((mode == EDIT_EVENT_AUTODETECT && icalcomponent_get_first_property (icalcomp, ICAL_ATTENDEE_PROPERTY) != NULL)
1615 || mode == EDIT_EVENT_FORCE_MEETING) {
1616 ECalComponent *comp = e_cal_component_new ();
1617 e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (icalcomp));
1618 flags |= E_COMP_EDITOR_FLAG_WITH_ATTENDEES;
1619 if (itip_organizer_is_user (registry, comp, client) ||
1620 itip_sentby_is_user (registry, comp, client) ||
1621 !e_cal_component_has_attendees (comp))
1622 flags |= E_COMP_EDITOR_FLAG_ORGANIZER_IS_USER;
1623 g_object_unref (comp);
1626 e_calendar_view_open_event_with_flags (cal_view, client, icalcomp, flags);
1629 static void
1630 tooltip_ungrab (ECalendarView *view,
1631 guint32 event_time)
1633 GdkDevice *keyboard;
1635 while (!g_queue_is_empty (&view->priv->grabbed_keyboards)) {
1636 keyboard = g_queue_pop_head (&view->priv->grabbed_keyboards);
1637 gdk_device_ungrab (keyboard, event_time);
1638 g_object_unref (keyboard);
1642 static gboolean
1643 tooltip_key_event (GtkWidget *tooltip,
1644 GdkEvent *key_event,
1645 ECalendarView *view)
1647 GtkWidget *widget;
1649 widget = g_object_get_data (G_OBJECT (view), "tooltip-window");
1650 if (widget == NULL)
1651 return TRUE;
1653 tooltip_ungrab (view, gdk_event_get_time (key_event));
1655 gtk_widget_destroy (widget);
1656 g_object_set_data (G_OBJECT (view), "tooltip-window", NULL);
1658 return FALSE;
1661 static gchar *
1662 get_label (struct icaltimetype *tt,
1663 icaltimezone *f_zone,
1664 icaltimezone *t_zone)
1666 struct tm tmp_tm;
1668 tmp_tm = icaltimetype_to_tm_with_zone (tt, f_zone, t_zone);
1670 return e_datetime_format_format_tm ("calendar", "table", tt->is_date ? DTFormatKindDate : DTFormatKindDateTime, &tmp_tm);
1673 void
1674 e_calendar_view_move_tip (GtkWidget *widget,
1675 gint x,
1676 gint y)
1678 GtkAllocation allocation;
1679 GtkRequisition requisition;
1680 GdkDisplay *display;
1681 GdkScreen *screen;
1682 GdkScreen *pointer_screen;
1683 GdkRectangle monitor;
1684 GdkDeviceManager *device_manager;
1685 GdkDevice *pointer;
1686 gint monitor_num, px, py;
1687 gint w, h;
1689 gtk_widget_get_preferred_size (widget, &requisition, NULL);
1690 w = requisition.width;
1691 h = requisition.height;
1693 screen = gtk_widget_get_screen (widget);
1694 display = gdk_screen_get_display (screen);
1695 device_manager = gdk_display_get_device_manager (display);
1696 pointer = gdk_device_manager_get_client_pointer (device_manager);
1698 gdk_device_get_position (pointer, &pointer_screen, &px, &py);
1699 if (pointer_screen != screen) {
1700 px = x;
1701 py = y;
1703 monitor_num = gdk_screen_get_monitor_at_point (screen, px, py);
1704 gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
1706 if ((x + w) > monitor.x + monitor.width)
1707 x -= (x + w) - (monitor.x + monitor.width);
1708 else if (x < monitor.x)
1709 x = monitor.x;
1711 gtk_widget_get_allocation (widget, &allocation);
1713 if ((y + h + allocation.height + 4) > monitor.y + monitor.height)
1714 y = y - h - 36;
1716 gtk_window_move (GTK_WINDOW (widget), x, y);
1717 gtk_widget_show (widget);
1720 static void
1721 tooltip_window_destroyed_cb (gpointer user_data,
1722 GObject *gone)
1724 ECalendarView *view = user_data;
1726 tooltip_ungrab (view, GDK_CURRENT_TIME);
1727 g_object_unref (view);
1731 * It is expected to show the tooltips in this below format
1733 * <B>SUBJECT OF THE MEETING</B>
1734 * Organiser: NameOfTheUser<email@ofuser.com>
1735 * Location: PlaceOfTheMeeting
1736 * Time : DateAndTime (xx Minutes)
1737 * Status: Accepted: X Declined: Y ...
1740 gboolean
1741 e_calendar_view_get_tooltips (const ECalendarViewEventData *data)
1743 GtkWidget *label, *box, *hbox, *ebox, *frame, *toplevel;
1744 const gchar *str;
1745 gchar *tmp, *tmp1 = NULL, *tmp2 = NULL;
1746 ECalComponentOrganizer organiser;
1747 ECalComponentDateTime dtstart, dtend;
1748 icalcomponent *clone_comp;
1749 time_t t_start, t_end;
1750 ECalendarViewEvent *pevent;
1751 GtkWidget *widget;
1752 GdkWindow *window;
1753 GdkDisplay *display;
1754 GdkDeviceManager *device_manager;
1755 GdkRGBA bg_rgba, fg_rgba;
1756 GQueue *grabbed_keyboards;
1757 ECalComponent *newcomp = e_cal_component_new ();
1758 icaltimezone *zone, *default_zone;
1759 ECalModel *model;
1760 ECalClient *client = NULL;
1761 GList *list, *link;
1762 gboolean free_text = FALSE;
1764 /* This function is a timeout callback. */
1766 g_return_val_if_fail (data != NULL, FALSE);
1767 g_return_val_if_fail (E_IS_CALENDAR_VIEW (data->cal_view), FALSE);
1769 e_utils_get_theme_color (GTK_WIDGET (data->cal_view), "theme_selected_bg_color", E_UTILS_DEFAULT_THEME_SELECTED_BG_COLOR, &bg_rgba);
1770 e_utils_get_theme_color (GTK_WIDGET (data->cal_view), "theme_selected_fg_color", E_UTILS_DEFAULT_THEME_SELECTED_FG_COLOR, &fg_rgba);
1772 model = e_calendar_view_get_model (data->cal_view);
1774 /* Delete any stray tooltip if left */
1775 widget = g_object_get_data (
1776 G_OBJECT (data->cal_view), "tooltip-window");
1777 if (GTK_IS_WIDGET (widget))
1778 gtk_widget_destroy (widget);
1780 default_zone = e_calendar_view_get_timezone (data->cal_view);
1781 pevent = data->get_view_event (data->cal_view, data->day, data->event_num);
1783 if (!is_comp_data_valid (pevent))
1784 return FALSE;
1786 client = pevent->comp_data->client;
1788 clone_comp = icalcomponent_new_clone (pevent->comp_data->icalcomp);
1789 if (!e_cal_component_set_icalcomponent (newcomp, clone_comp))
1790 g_warning ("couldn't update calendar component with modified data from backend\n");
1792 box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
1794 str = e_calendar_view_get_icalcomponent_summary (pevent->comp_data->client, pevent->comp_data->icalcomp, &free_text);
1796 if (!(str && *str)) {
1797 g_object_unref (newcomp);
1798 gtk_widget_destroy (box);
1800 return FALSE;
1803 tmp = g_markup_printf_escaped ("<b>%s</b>", str);
1804 label = gtk_label_new (NULL);
1805 gtk_label_set_line_wrap ((GtkLabel *) label, TRUE);
1806 gtk_label_set_markup ((GtkLabel *) label, tmp);
1808 if (free_text) {
1809 g_free ((gchar *) str);
1810 str = NULL;
1813 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
1814 gtk_box_pack_start ((GtkBox *) hbox, label, FALSE, FALSE, 0);
1815 ebox = gtk_event_box_new ();
1816 gtk_container_add ((GtkContainer *) ebox, hbox);
1817 gtk_widget_override_background_color (ebox, GTK_STATE_FLAG_NORMAL, &bg_rgba);
1818 gtk_widget_override_color (label, GTK_STATE_FLAG_NORMAL, &fg_rgba);
1820 gtk_box_pack_start ((GtkBox *) box, ebox, FALSE, FALSE, 0);
1821 g_free (tmp);
1823 e_cal_component_get_organizer (newcomp, &organiser);
1824 if (organiser.cn) {
1825 gchar *ptr;
1826 ptr = strchr (organiser.value, ':');
1828 if (ptr) {
1829 ptr++;
1830 /* To Translators: It will display "Organiser: NameOfTheUser <email@ofuser.com>" */
1831 tmp = g_strdup_printf (_("Organizer: %s <%s>"), organiser.cn, ptr);
1833 else
1834 /* With SunOne accouts, there may be no ':' in organiser.value*/
1835 tmp = g_strdup_printf (_("Organizer: %s"), organiser.cn);
1837 label = gtk_label_new (tmp);
1838 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
1839 gtk_box_pack_start ((GtkBox *) hbox, label, FALSE, FALSE, 0);
1840 ebox = gtk_event_box_new ();
1841 gtk_container_add ((GtkContainer *) ebox, hbox);
1842 gtk_box_pack_start ((GtkBox *) box, ebox, FALSE, FALSE, 0);
1844 g_free (tmp);
1847 e_cal_component_get_location (newcomp, &str);
1849 if (str) {
1850 /* To Translators: It will display "Location: PlaceOfTheMeeting" */
1851 tmp = g_markup_printf_escaped (_("Location: %s"), str);
1852 label = gtk_label_new (NULL);
1853 gtk_widget_set_halign (label, GTK_ALIGN_START);
1854 gtk_misc_set_alignment ((GtkMisc *) label, 0.0, 0.0);
1855 gtk_label_set_markup ((GtkLabel *) label, tmp);
1856 gtk_label_set_line_wrap ((GtkLabel *) label, TRUE);
1857 gtk_label_set_max_width_chars ((GtkLabel *) label, 80);
1858 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
1859 gtk_box_pack_start ((GtkBox *) hbox, label, FALSE, FALSE, 0);
1860 ebox = gtk_event_box_new ();
1861 gtk_container_add ((GtkContainer *) ebox, hbox);
1862 gtk_box_pack_start ((GtkBox *) box, ebox, FALSE, FALSE, 0);
1863 g_free (tmp);
1865 e_cal_component_get_dtstart (newcomp, &dtstart);
1866 e_cal_component_get_dtend (newcomp, &dtend);
1868 if (dtstart.tzid) {
1869 zone = icalcomponent_get_timezone (e_cal_component_get_icalcomponent (newcomp), dtstart.tzid);
1870 if (!zone)
1871 e_cal_client_get_timezone_sync (client, dtstart.tzid, &zone, NULL, NULL);
1873 if (!zone)
1874 zone = default_zone;
1876 } else {
1877 zone = NULL;
1880 if (dtstart.value) {
1881 t_start = icaltime_as_timet_with_zone (*dtstart.value, zone);
1882 if (dtend.value)
1883 t_end = icaltime_as_timet_with_zone (*dtend.value, zone);
1884 else
1885 t_end = t_start;
1887 tmp1 = get_label (dtstart.value, zone, default_zone);
1888 tmp = calculate_time (t_start, t_end);
1890 /* To Translators: It will display "Time: ActualStartDateAndTime (DurationOfTheMeeting)"*/
1891 tmp2 = g_strdup_printf (_("Time: %s %s"), tmp1, tmp);
1892 if (zone && !cal_comp_util_compare_event_timezones (newcomp, client, default_zone)) {
1893 g_free (tmp);
1894 g_free (tmp1);
1896 tmp1 = get_label (dtstart.value, zone, zone);
1897 tmp = g_strconcat (tmp2, "\n\t[ ", tmp1, " ", icaltimezone_get_display_name (zone), " ]", NULL);
1898 } else {
1899 g_free (tmp);
1900 tmp = tmp2;
1901 tmp2 = NULL;
1903 } else {
1904 tmp = NULL;
1907 e_cal_component_free_datetime (&dtstart);
1908 e_cal_component_free_datetime (&dtend);
1910 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
1911 gtk_box_pack_start ((GtkBox *) hbox, gtk_label_new_with_mnemonic (tmp), FALSE, FALSE, 0);
1912 ebox = gtk_event_box_new ();
1913 gtk_container_add ((GtkContainer *) ebox, hbox);
1914 gtk_box_pack_start ((GtkBox *) box, ebox, FALSE, FALSE, 0);
1916 g_free (tmp);
1917 g_free (tmp2);
1918 g_free (tmp1);
1920 tmp = e_cal_model_get_attendees_status_info (
1921 model, newcomp, pevent->comp_data->client);
1922 if (tmp) {
1923 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
1924 gtk_box_pack_start ((GtkBox *) hbox, gtk_label_new (tmp), FALSE, FALSE, 0);
1925 ebox = gtk_event_box_new ();
1926 gtk_container_add ((GtkContainer *) ebox, hbox);
1927 gtk_box_pack_start ((GtkBox *) box, ebox, FALSE, FALSE, 0);
1929 g_free (tmp);
1932 tmp = cal_comp_util_get_attendee_comments (e_cal_component_get_icalcomponent (newcomp));
1933 if (tmp) {
1934 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
1935 gtk_box_pack_start ((GtkBox *) hbox, gtk_label_new (tmp), FALSE, FALSE, 0);
1936 ebox = gtk_event_box_new ();
1937 gtk_container_add ((GtkContainer *) ebox, hbox);
1938 gtk_box_pack_start ((GtkBox *) box, ebox, FALSE, FALSE, 0);
1940 g_free (tmp);
1943 pevent->tooltip = gtk_window_new (GTK_WINDOW_POPUP);
1945 toplevel = gtk_widget_get_toplevel (GTK_WIDGET (data->cal_view));
1946 if (GTK_IS_WINDOW (toplevel)) {
1947 gtk_window_group_add_window (gtk_window_get_group (GTK_WINDOW (toplevel)), GTK_WINDOW (pevent->tooltip));
1948 gtk_window_set_transient_for (GTK_WINDOW (pevent->tooltip), GTK_WINDOW (toplevel));
1951 frame = gtk_frame_new (NULL);
1952 gtk_frame_set_shadow_type ((GtkFrame *) frame, GTK_SHADOW_IN);
1954 gtk_window_set_type_hint (GTK_WINDOW (pevent->tooltip), GDK_WINDOW_TYPE_HINT_TOOLTIP);
1955 gtk_window_move ((GtkWindow *) pevent->tooltip, pevent->x +16, pevent->y + 16);
1956 gtk_container_add ((GtkContainer *) frame, box);
1957 gtk_container_add ((GtkContainer *) pevent->tooltip, frame);
1959 gtk_widget_show_all (pevent->tooltip);
1961 e_calendar_view_move_tip (pevent->tooltip, pevent->x +16, pevent->y + 16);
1963 /* Grab all keyboard devices. A key press from
1964 * any of them will dismiss the tooltip window. */
1966 window = gtk_widget_get_window (pevent->tooltip);
1967 display = gdk_window_get_display (window);
1968 device_manager = gdk_display_get_device_manager (display);
1970 grabbed_keyboards = &data->cal_view->priv->grabbed_keyboards;
1971 g_warn_if_fail (g_queue_is_empty (grabbed_keyboards));
1973 list = gdk_device_manager_list_devices (
1974 device_manager, GDK_DEVICE_TYPE_MASTER);
1976 for (link = list; link != NULL; link = g_list_next (link)) {
1977 GdkDevice *device = GDK_DEVICE (link->data);
1978 GdkGrabStatus grab_status;
1980 if (gdk_device_get_source (device) != GDK_SOURCE_KEYBOARD)
1981 continue;
1983 grab_status = gdk_device_grab (
1984 device,
1985 window,
1986 GDK_OWNERSHIP_NONE,
1987 FALSE,
1988 GDK_KEY_PRESS_MASK |
1989 GDK_KEY_RELEASE_MASK,
1990 NULL,
1991 GDK_CURRENT_TIME);
1993 if (grab_status == GDK_GRAB_SUCCESS)
1994 g_queue_push_tail (
1995 grabbed_keyboards,
1996 g_object_ref (device));
1999 g_list_free (list);
2001 g_signal_connect (
2002 pevent->tooltip, "key-press-event",
2003 G_CALLBACK (tooltip_key_event), data->cal_view);
2004 pevent->timeout = -1;
2006 g_object_weak_ref (G_OBJECT (pevent->tooltip), tooltip_window_destroyed_cb, g_object_ref (data->cal_view));
2007 g_object_set_data (G_OBJECT (data->cal_view), "tooltip-window", pevent->tooltip);
2008 g_object_unref (newcomp);
2010 return FALSE;
2013 static gboolean
2014 icalcomp_contains_category (icalcomponent *icalcomp,
2015 const gchar *category)
2017 icalproperty *property;
2019 g_return_val_if_fail (icalcomp != NULL && category != NULL, FALSE);
2021 for (property = icalcomponent_get_first_property (icalcomp, ICAL_CATEGORIES_PROPERTY);
2022 property != NULL;
2023 property = icalcomponent_get_next_property (icalcomp, ICAL_CATEGORIES_PROPERTY)) {
2024 gchar *value = icalproperty_get_value_as_string_r (property);
2026 if (value && strcmp (category, value) == 0) {
2027 g_free (value);
2028 return TRUE;
2030 g_free (value);
2033 return FALSE;
2036 /* e_calendar_view_get_icalcomponent_summary returns summary of calcomp,
2037 * and for type of birthday or anniversary it append number of years since
2038 * beginning. In this case, the free_text is set to TRUE and caller need
2039 * to g_free returned string, otherwise free_text is set to FALSE and
2040 * returned value is owned by calcomp.
2043 const gchar *
2044 e_calendar_view_get_icalcomponent_summary (ECalClient *client,
2045 icalcomponent *icalcomp,
2046 gboolean *free_text)
2048 const gchar *summary;
2050 g_return_val_if_fail (icalcomp != NULL && free_text != NULL, NULL);
2052 *free_text = FALSE;
2053 summary = icalcomponent_get_summary (icalcomp);
2055 if (icalcomp_contains_category (icalcomp, _("Birthday")) ||
2056 icalcomp_contains_category (icalcomp, _("Anniversary"))) {
2057 icalproperty *xprop;
2059 for (xprop = icalcomponent_get_first_property (icalcomp, ICAL_X_PROPERTY);
2060 xprop;
2061 xprop = icalcomponent_get_next_property (icalcomp, ICAL_X_PROPERTY)) {
2062 const gchar *xname = icalproperty_get_x_name (xprop);
2064 if (xname && g_ascii_strcasecmp (xname, "X-EVOLUTION-SINCE-YEAR") == 0) {
2065 struct icaltimetype dtnow;
2066 gint since_year;
2067 gchar *str;
2069 str = icalproperty_get_value_as_string_r (xprop);
2070 since_year = str ? atoi (str) : 0;
2071 g_free (str);
2073 dtnow = icalcomponent_get_dtstart (icalcomp);
2075 if (since_year > 0 && dtnow.year - since_year > 0) {
2076 summary = g_strdup_printf ("%s (%d)", summary ? summary : "", dtnow.year - since_year);
2077 *free_text = summary != NULL;
2080 break;
2085 return summary;
2088 /* A callback for e_cal_ops_create_component(), whose @user_data is an ECalendarView instance */
2089 void
2090 e_calendar_view_component_created_cb (ECalModel *model,
2091 ECalClient *client,
2092 icalcomponent *original_icalcomp,
2093 const gchar *new_uid,
2094 gpointer user_data)
2096 ECalendarView *cal_view = user_data;
2098 g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view));
2100 e_cal_model_emit_object_created (model, client);
2104 void
2105 draw_curved_rectangle (cairo_t *cr,
2106 gdouble x0,
2107 gdouble y0,
2108 gdouble rect_width,
2109 gdouble rect_height,
2110 gdouble radius)
2112 gdouble x1, y1;
2114 x1 = x0 + rect_width;
2115 y1 = y0 + rect_height;
2117 if (!rect_width || !rect_height)
2118 return;
2119 if (rect_width / 2 < radius) {
2120 if (rect_height / 2 < radius) {
2121 cairo_move_to (cr, x0, (y0 + y1) / 2);
2122 cairo_curve_to (cr, x0 ,y0, x0, y0, (x0 + x1) / 2, y0);
2123 cairo_curve_to (cr, x1, y0, x1, y0, x1, (y0 + y1) / 2);
2124 cairo_curve_to (cr, x1, y1, x1, y1, (x1 + x0) / 2, y1);
2125 cairo_curve_to (cr, x0, y1, x0, y1, x0, (y0 + y1) / 2);
2126 } else {
2127 cairo_move_to (cr, x0, y0 + radius);
2128 cairo_curve_to (cr, x0 ,y0, x0, y0, (x0 + x1) / 2, y0);
2129 cairo_curve_to (cr, x1, y0, x1, y0, x1, y0 + radius);
2130 cairo_line_to (cr, x1 , y1 - radius);
2131 cairo_curve_to (cr, x1, y1, x1, y1, (x1 + x0) / 2, y1);
2132 cairo_curve_to (cr, x0, y1, x0, y1, x0, y1- radius);
2134 } else {
2135 if (rect_height / 2 < radius) {
2136 cairo_move_to (cr, x0, (y0 + y1) / 2);
2137 cairo_curve_to (cr, x0 , y0, x0 , y0, x0 + radius, y0);
2138 cairo_line_to (cr, x1 - radius, y0);
2139 cairo_curve_to (cr, x1, y0, x1, y0, x1, (y0 + y1) / 2);
2140 cairo_curve_to (cr, x1, y1, x1, y1, x1 - radius, y1);
2141 cairo_line_to (cr, x0 + radius, y1);
2142 cairo_curve_to (cr, x0, y1, x0, y1, x0, (y0 + y1) / 2);
2143 } else {
2144 cairo_move_to (cr, x0, y0 + radius);
2145 cairo_curve_to (cr, x0 , y0, x0 , y0, x0 + radius, y0);
2146 cairo_line_to (cr, x1 - radius, y0);
2147 cairo_curve_to (cr, x1, y0, x1, y0, x1, y0 + radius);
2148 cairo_line_to (cr, x1 , y1 - radius);
2149 cairo_curve_to (cr, x1, y1, x1, y1, x1 - radius, y1);
2150 cairo_line_to (cr, x0 + radius, y1);
2151 cairo_curve_to (cr, x0, y1, x0, y1, x0, y1- radius);
2154 cairo_close_path (cr);
2157 /* returns either light or dark yellow, based on the base_background,
2158 * which is the default background color */
2159 GdkColor
2160 get_today_background (const GdkColor base_background)
2162 GdkColor res = base_background;
2164 if (res.red > 0x7FFF) {
2165 /* light yellow for a light theme */
2166 res.red = 0xFFFF;
2167 res.green = 0xFFFF;
2168 res.blue = 0xC0C0;
2169 } else {
2170 /* dark yellow for a dark theme */
2171 res.red = 0x3F3F;
2172 res.green = 0x3F3F;
2173 res.blue = 0x0000;
2176 return res;
2179 gboolean
2180 is_comp_data_valid_func (ECalendarViewEvent *event,
2181 const gchar *location)
2183 g_return_val_if_fail (location != NULL, FALSE);
2185 if (!event) {
2186 g_warning ("%s: event is NULL", location);
2187 return FALSE;
2190 if (!event->comp_data) {
2191 g_warning ("%s: event's (%p) comp_data is NULL", location, event);
2192 return FALSE;
2195 return TRUE;
2198 gboolean
2199 is_array_index_in_bounds_func (GArray *array,
2200 gint index,
2201 const gchar *location)
2203 g_return_val_if_fail (location != NULL, FALSE);
2205 if (!array) {
2206 g_warning ("%s: array is NULL", location);
2207 return FALSE;
2210 if (index < 0 || index >= array->len) {
2211 g_warning ("%s: index %d is out of bounds [0,%d) at array %p", location, index, array->len, array);
2212 return FALSE;
2215 return TRUE;
2218 gboolean
2219 e_calendar_view_is_editing (ECalendarView *cal_view)
2221 static gboolean in = FALSE;
2222 gboolean is_editing = FALSE;
2224 g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), FALSE);
2226 /* this should be called from the main thread only,
2227 * and each descendant overrides the property,
2228 * thus might cause no call recursion */
2229 if (in) {
2230 g_warn_if_reached ();
2231 return FALSE;
2234 in = TRUE;
2236 g_object_get (G_OBJECT (cal_view), "is-editing", &is_editing, NULL);
2238 in = FALSE;
2240 return is_editing;
2243 /* Returns text description of the current view. */
2244 gchar *
2245 e_calendar_view_get_description_text (ECalendarView *cal_view)
2247 time_t start_time, end_time;
2248 struct tm start_tm, end_tm;
2249 struct icaltimetype start_tt, end_tt;
2250 icaltimezone *zone;
2251 gchar buffer[1024] = { 0 };
2252 gchar end_buffer[512] = { 0 };
2254 g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), NULL);
2256 if (!e_calendar_view_get_visible_time_range (cal_view, &start_time, &end_time))
2257 return NULL;
2259 zone = e_cal_model_get_timezone (cal_view->priv->model);
2261 start_tt = icaltime_from_timet_with_zone (start_time, FALSE, zone);
2262 start_tm.tm_year = start_tt.year - 1900;
2263 start_tm.tm_mon = start_tt.month - 1;
2264 start_tm.tm_mday = start_tt.day;
2265 start_tm.tm_hour = start_tt.hour;
2266 start_tm.tm_min = start_tt.minute;
2267 start_tm.tm_sec = start_tt.second;
2268 start_tm.tm_isdst = -1;
2269 start_tm.tm_wday = time_day_of_week (start_tt.day, start_tt.month - 1, start_tt.year);
2271 /* Subtract one from end_time so we don't get an extra day. */
2272 end_tt = icaltime_from_timet_with_zone (end_time - 1, FALSE, zone);
2273 end_tm.tm_year = end_tt.year - 1900;
2274 end_tm.tm_mon = end_tt.month - 1;
2275 end_tm.tm_mday = end_tt.day;
2276 end_tm.tm_hour = end_tt.hour;
2277 end_tm.tm_min = end_tt.minute;
2278 end_tm.tm_sec = end_tt.second;
2279 end_tm.tm_isdst = -1;
2280 end_tm.tm_wday = time_day_of_week (end_tt.day, end_tt.month - 1, end_tt.year);
2282 if (E_IS_MONTH_VIEW (cal_view) || E_IS_CAL_LIST_VIEW (cal_view)) {
2283 if (start_tm.tm_year == end_tm.tm_year) {
2284 if (start_tm.tm_mon == end_tm.tm_mon) {
2285 e_utf8_strftime (buffer, sizeof (buffer),
2286 "%d", &start_tm);
2287 e_utf8_strftime (end_buffer, sizeof (end_buffer),
2288 _("%d %b %Y"), &end_tm);
2289 strcat (buffer, " - ");
2290 strcat (buffer, end_buffer);
2291 } else {
2292 e_utf8_strftime (buffer, sizeof (buffer),
2293 _("%d %b"), &start_tm);
2294 e_utf8_strftime (end_buffer, sizeof (end_buffer),
2295 _("%d %b %Y"), &end_tm);
2296 strcat (buffer, " - ");
2297 strcat (buffer, end_buffer);
2299 } else {
2300 e_utf8_strftime (
2301 buffer, sizeof (buffer),
2302 _("%d %b %Y"), &start_tm);
2303 e_utf8_strftime (
2304 end_buffer, sizeof (end_buffer),
2305 _("%d %b %Y"), &end_tm);
2306 strcat (buffer, " - ");
2307 strcat (buffer, end_buffer);
2309 } else {
2310 if (start_tm.tm_year == end_tm.tm_year &&
2311 start_tm.tm_mon == end_tm.tm_mon &&
2312 start_tm.tm_mday == end_tm.tm_mday) {
2313 e_utf8_strftime (
2314 buffer, sizeof (buffer),
2315 _("%A %d %b %Y"), &start_tm);
2316 } else if (start_tm.tm_year == end_tm.tm_year) {
2317 e_utf8_strftime (
2318 buffer, sizeof (buffer),
2319 _("%a %d %b"), &start_tm);
2320 e_utf8_strftime (
2321 end_buffer, sizeof (end_buffer),
2322 _("%a %d %b %Y"), &end_tm);
2323 strcat (buffer, " - ");
2324 strcat (buffer, end_buffer);
2325 } else {
2326 e_utf8_strftime (
2327 buffer, sizeof (buffer),
2328 _("%a %d %b %Y"), &start_tm);
2329 e_utf8_strftime (
2330 end_buffer, sizeof (end_buffer),
2331 _("%a %d %b %Y"), &end_tm);
2332 strcat (buffer, " - ");
2333 strcat (buffer, end_buffer);
2337 return g_strdup (buffer);
2340 void
2341 e_calendar_view_move_view_range (ECalendarView *cal_view,
2342 ECalendarViewMoveType mode_type,
2343 time_t exact_date)
2345 g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view));
2347 g_signal_emit (cal_view, signals[MOVE_VIEW_RANGE], 0, mode_type, (gint64) exact_date);