diff options
Diffstat (limited to 'calendar/gui/e-calendar-view.c')
-rw-r--r-- | calendar/gui/e-calendar-view.c | 2422 |
1 files changed, 2422 insertions, 0 deletions
diff --git a/calendar/gui/e-calendar-view.c b/calendar/gui/e-calendar-view.c new file mode 100644 index 0000000000..61a88850cd --- /dev/null +++ b/calendar/gui/e-calendar-view.c @@ -0,0 +1,2422 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Authors: + * Rodrigo Moya <rodrigo@ximian.com> + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <time.h> +#include <gtk/gtk.h> +#include <glib/gi18n.h> +#include <glib/gstdio.h> +#include <gdk/gdkkeysyms.h> +#include <libebackend/libebackend.h> + +#include <shell/e-shell.h> + +#include "comp-util.h" +#include "ea-calendar.h" +#include "e-cal-model-calendar.h" +#include "e-calendar-view.h" +#include "itip-utils.h" +#include "dialogs/comp-editor-util.h" +#include "dialogs/delete-comp.h" +#include "dialogs/delete-error.h" +#include "dialogs/event-editor.h" +#include "dialogs/send-comp.h" +#include "dialogs/cancel-comp.h" +#include "dialogs/recur-comp.h" +#include "dialogs/select-source-dialog.h" +#include "dialogs/goto-dialog.h" +#include "print.h" +#include "misc.h" + +#define E_CALENDAR_VIEW_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_CALENDAR_VIEW, ECalendarViewPrivate)) + +struct _ECalendarViewPrivate { + /* The GnomeCalendar we are associated to */ + GnomeCalendar *calendar; + + /* The calendar model we are monitoring */ + ECalModel *model; + + gchar *default_category; + gint time_divisions; + GSList *selected_cut_list; + + GtkTargetList *copy_target_list; + GtkTargetList *paste_target_list; + + /* All keyboard devices are grabbed + * while a tooltip window is shown. */ + GQueue grabbed_keyboards; +}; + +enum { + PROP_0, + PROP_COPY_TARGET_LIST, + PROP_MODEL, + PROP_PASTE_TARGET_LIST, + PROP_TIME_DIVISIONS, + PROP_IS_EDITING +}; + +/* FIXME Why are we emitting these event signals here? Can't the model just be listened to? */ +/* Signal IDs */ +enum { + POPUP_EVENT, + SELECTION_CHANGED, + SELECTED_TIME_CHANGED, + TIMEZONE_CHANGED, + EVENT_CHANGED, + EVENT_ADDED, + USER_CREATED, + OPEN_EVENT, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL]; + +static void calendar_view_selectable_init (ESelectableInterface *interface); + +G_DEFINE_ABSTRACT_TYPE_WITH_CODE ( + ECalendarView, e_calendar_view, GTK_TYPE_TABLE, + G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE, NULL) + G_IMPLEMENT_INTERFACE (E_TYPE_SELECTABLE, calendar_view_selectable_init)); + +static void +calendar_view_add_retract_data (ECalComponent *comp, + const gchar *retract_comment, + CalObjModType mod) +{ + icalcomponent *icalcomp = NULL; + icalproperty *icalprop = NULL; + + icalcomp = e_cal_component_get_icalcomponent (comp); + if (retract_comment && *retract_comment) + icalprop = icalproperty_new_x (retract_comment); + else + icalprop = icalproperty_new_x ("0"); + icalproperty_set_x_name (icalprop, "X-EVOLUTION-RETRACT-COMMENT"); + icalcomponent_add_property (icalcomp, icalprop); + + if (mod == CALOBJ_MOD_ALL) + icalprop = icalproperty_new_x ("All"); + else + icalprop = icalproperty_new_x ("This"); + icalproperty_set_x_name (icalprop, "X-EVOLUTION-RECUR-MOD"); + icalcomponent_add_property (icalcomp, icalprop); +} + +static gboolean +calendar_view_check_for_retract (ECalComponent *comp, + ECalClient *client) +{ + ECalComponentOrganizer organizer; + const gchar *strip; + gchar *email = NULL; + gboolean ret_val; + + if (!e_cal_component_has_attendees (comp)) + return FALSE; + + if (!e_cal_client_check_save_schedules (client)) + return FALSE; + + e_cal_component_get_organizer (comp, &organizer); + strip = itip_strip_mailto (organizer.value); + + ret_val = + e_client_get_backend_property_sync (E_CLIENT (client), CAL_BACKEND_PROPERTY_CAL_EMAIL_ADDRESS, &email, NULL, NULL) && + (g_ascii_strcasecmp (email, strip) == 0); + + g_free (email); + + return ret_val; +} + +static void +calendar_view_delete_event (ECalendarView *cal_view, + ECalendarViewEvent *event) +{ + ECalModel *model; + ECalComponent *comp; + ECalComponentVType vtype; + ESourceRegistry *registry; + gboolean delete = TRUE; + GError *error = NULL; + + if (!is_comp_data_valid (event)) + return; + + model = e_calendar_view_get_model (cal_view); + registry = e_cal_model_get_registry (model); + + comp = e_cal_component_new (); + e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (event->comp_data->icalcomp)); + vtype = e_cal_component_get_vtype (comp); + + /*FIXME remove it once the we dont set the recurrence id for all the generated instances */ + if (!e_cal_client_check_recurrences_no_master (event->comp_data->client)) + e_cal_component_set_recurid (comp, NULL); + + /*FIXME Retract should be moved to Groupwise features plugin */ + if (calendar_view_check_for_retract (comp, event->comp_data->client)) { + gchar *retract_comment = NULL; + gboolean retract = FALSE; + + delete = prompt_retract_dialog (comp, &retract_comment, GTK_WIDGET (cal_view), &retract); + if (retract) { + GSList *users = NULL; + icalcomponent *icalcomp = NULL, *mod_comp = NULL; + + calendar_view_add_retract_data ( + comp, retract_comment, CALOBJ_MOD_ALL); + icalcomp = e_cal_component_get_icalcomponent (comp); + icalcomponent_set_method (icalcomp, ICAL_METHOD_CANCEL); + if (!e_cal_client_send_objects_sync (event->comp_data->client, icalcomp, &users, + &mod_comp, NULL, &error)) { + delete_error_dialog (error, E_CAL_COMPONENT_EVENT); + g_clear_error (&error); + error = NULL; + } else { + + if (mod_comp) + icalcomponent_free (mod_comp); + + if (users) { + g_slist_foreach (users, (GFunc) g_free, NULL); + g_slist_free (users); + } + } + } + } else if (e_cal_model_get_confirm_delete (model)) + delete = delete_component_dialog ( + comp, FALSE, 1, vtype, GTK_WIDGET (cal_view)); + + if (delete) { + const gchar *uid; + gchar *rid = NULL; + + if ((itip_organizer_is_user (registry, comp, event->comp_data->client) || + itip_sentby_is_user (registry, comp, event->comp_data->client)) + && cancel_component_dialog ((GtkWindow *) gtk_widget_get_toplevel (GTK_WIDGET (cal_view)), + event->comp_data->client, + comp, TRUE)) + itip_send_comp ( + registry, E_CAL_COMPONENT_METHOD_CANCEL, + comp, event->comp_data->client, NULL, NULL, + NULL, TRUE, FALSE); + + e_cal_component_get_uid (comp, &uid); + if (!uid || !*uid) { + g_object_unref (comp); + return; + } + rid = e_cal_component_get_recurid_as_string (comp); + if (e_cal_util_component_is_instance (event->comp_data->icalcomp) || e_cal_util_component_has_recurrences (event->comp_data->icalcomp)) + e_cal_client_remove_object_sync ( + event->comp_data->client, uid, + rid, CALOBJ_MOD_ALL, NULL, &error); + else + e_cal_client_remove_object_sync (event->comp_data->client, uid, NULL, CALOBJ_MOD_THIS, NULL, &error); + + delete_error_dialog (error, E_CAL_COMPONENT_EVENT); + g_clear_error (&error); + g_free (rid); + } + + g_object_unref (comp); +} + +static void +calendar_view_set_model (ECalendarView *calendar_view, + ECalModel *model) +{ + g_return_if_fail (calendar_view->priv->model == NULL); + g_return_if_fail (E_IS_CAL_MODEL (model)); + + calendar_view->priv->model = g_object_ref (model); +} + +static void +calendar_view_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_MODEL: + calendar_view_set_model ( + E_CALENDAR_VIEW (object), + g_value_get_object (value)); + return; + + case PROP_TIME_DIVISIONS: + e_calendar_view_set_time_divisions ( + E_CALENDAR_VIEW (object), + g_value_get_int (value)); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +calendar_view_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_COPY_TARGET_LIST: + g_value_set_boxed ( + value, e_calendar_view_get_copy_target_list ( + E_CALENDAR_VIEW (object))); + return; + + case PROP_MODEL: + g_value_set_object ( + value, e_calendar_view_get_model ( + E_CALENDAR_VIEW (object))); + return; + + case PROP_PASTE_TARGET_LIST: + g_value_set_boxed ( + value, e_calendar_view_get_paste_target_list ( + E_CALENDAR_VIEW (object))); + return; + + case PROP_TIME_DIVISIONS: + g_value_set_int ( + value, e_calendar_view_get_time_divisions ( + E_CALENDAR_VIEW (object))); + return; + + case PROP_IS_EDITING: + g_value_set_boolean (value, e_calendar_view_is_editing (E_CALENDAR_VIEW (object))); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +calendar_view_dispose (GObject *object) +{ + ECalendarViewPrivate *priv; + + priv = E_CALENDAR_VIEW_GET_PRIVATE (object); + + if (priv->model != NULL) { + g_signal_handlers_disconnect_matched ( + priv->model, G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, object); + g_object_unref (priv->model); + priv->model = NULL; + } + + if (priv->copy_target_list != NULL) { + gtk_target_list_unref (priv->copy_target_list); + priv->copy_target_list = NULL; + } + + if (priv->paste_target_list != NULL) { + gtk_target_list_unref (priv->paste_target_list); + priv->paste_target_list = NULL; + } + + if (priv->selected_cut_list) { + g_slist_foreach (priv->selected_cut_list, (GFunc) g_object_unref, NULL); + g_slist_free (priv->selected_cut_list); + priv->selected_cut_list = NULL; + } + + while (!g_queue_is_empty (&priv->grabbed_keyboards)) { + GdkDevice *keyboard; + keyboard = g_queue_pop_head (&priv->grabbed_keyboards); + gdk_device_ungrab (keyboard, GDK_CURRENT_TIME); + g_object_unref (keyboard); + } + + /* Chain up to parent's dispose() method. */ + G_OBJECT_CLASS (e_calendar_view_parent_class)->dispose (object); +} + +static void +calendar_view_finalize (GObject *object) +{ + ECalendarViewPrivate *priv; + + priv = E_CALENDAR_VIEW_GET_PRIVATE (object); + + g_free (priv->default_category); + + /* Chain up to parent's finalize() method. */ + G_OBJECT_CLASS (e_calendar_view_parent_class)->finalize (object); +} + +static void +calendar_view_constructed (GObject *object) +{ + /* Do this after calendar_view_init() so extensions can query + * the GType accurately. See GInstanceInitFunc documentation + * for details of the problem. */ + e_extensible_load_extensions (E_EXTENSIBLE (object)); + + /* Chain up to parent's constructed() method. */ + G_OBJECT_CLASS (e_calendar_view_parent_class)->constructed (object); +} + +static void +calendar_view_update_actions (ESelectable *selectable, + EFocusTracker *focus_tracker, + GdkAtom *clipboard_targets, + gint n_clipboard_targets) +{ + ECalendarView *view; + GtkAction *action; + GtkTargetList *target_list; + GList *list, *iter; + gboolean can_paste = FALSE; + gboolean sources_are_editable = TRUE; + gboolean recurring = FALSE; + gboolean is_editing; + gboolean sensitive; + const gchar *tooltip; + gint n_selected; + gint ii; + + view = E_CALENDAR_VIEW (selectable); + is_editing = e_calendar_view_is_editing (view); + + list = e_calendar_view_get_selected_events (view); + n_selected = g_list_length (list); + + for (iter = list; iter != NULL; iter = iter->next) { + ECalendarViewEvent *event = iter->data; + ECalClient *client; + icalcomponent *icalcomp; + + if (event == NULL || event->comp_data == NULL) + continue; + + client = event->comp_data->client; + icalcomp = event->comp_data->icalcomp; + + sources_are_editable = sources_are_editable && !e_client_is_readonly (E_CLIENT (client)); + + recurring |= + e_cal_util_component_is_instance (icalcomp) || + e_cal_util_component_has_recurrences (icalcomp); + } + + g_list_free (list); + + target_list = e_selectable_get_paste_target_list (selectable); + for (ii = 0; ii < n_clipboard_targets && !can_paste; ii++) + can_paste = gtk_target_list_find ( + target_list, clipboard_targets[ii], NULL); + + action = e_focus_tracker_get_cut_clipboard_action (focus_tracker); + sensitive = (n_selected > 0) && sources_are_editable && !is_editing; + tooltip = _("Cut selected events to the clipboard"); + gtk_action_set_sensitive (action, sensitive); + gtk_action_set_tooltip (action, tooltip); + + action = e_focus_tracker_get_copy_clipboard_action (focus_tracker); + sensitive = (n_selected > 0) && !is_editing; + tooltip = _("Copy selected events to the clipboard"); + gtk_action_set_sensitive (action, sensitive); + gtk_action_set_tooltip (action, tooltip); + + action = e_focus_tracker_get_paste_clipboard_action (focus_tracker); + sensitive = sources_are_editable && can_paste && !is_editing; + tooltip = _("Paste events from the clipboard"); + gtk_action_set_sensitive (action, sensitive); + gtk_action_set_tooltip (action, tooltip); + + action = e_focus_tracker_get_delete_selection_action (focus_tracker); + sensitive = (n_selected > 0) && sources_are_editable && !recurring && !is_editing; + tooltip = _("Delete selected events"); + gtk_action_set_sensitive (action, sensitive); + gtk_action_set_tooltip (action, tooltip); +} + +static void +calendar_view_cut_clipboard (ESelectable *selectable) +{ + ECalendarView *cal_view; + ECalendarViewPrivate *priv; + GList *selected, *l; + + cal_view = E_CALENDAR_VIEW (selectable); + priv = cal_view->priv; + + selected = e_calendar_view_get_selected_events (cal_view); + if (!selected) + return; + +#if 0 /* KILL-BONOBO */ + e_calendar_view_set_status_message (cal_view, _("Deleting selected objects"), -1); +#endif + + e_selectable_copy_clipboard (selectable); + + for (l = selected; l != NULL; l = g_list_next (l)) { + ECalendarViewEvent *event = (ECalendarViewEvent *) l->data; + + priv->selected_cut_list = g_slist_prepend (priv->selected_cut_list, g_object_ref (event->comp_data)); + } + +#if 0 /* KILL-BONOBO */ + e_calendar_view_set_status_message (cal_view, NULL, -1); +#endif + + g_list_free (selected); +} + +static void +add_related_timezones (icalcomponent *des_icalcomp, + icalcomponent *src_icalcomp, + ECalClient *client) +{ + icalproperty_kind look_in[] = { + ICAL_DTSTART_PROPERTY, + ICAL_DTEND_PROPERTY, + ICAL_NO_PROPERTY + }; + gint i; + + g_return_if_fail (des_icalcomp != NULL); + g_return_if_fail (src_icalcomp != NULL); + g_return_if_fail (client != NULL); + + for (i = 0; look_in[i] != ICAL_NO_PROPERTY; i++) { + icalproperty *prop = icalcomponent_get_first_property (src_icalcomp, look_in[i]); + + if (prop) { + icalparameter *par = icalproperty_get_first_parameter (prop, ICAL_TZID_PARAMETER); + + if (par) { + const gchar *tzid = icalparameter_get_tzid (par); + + if (tzid) { + GError *error = NULL; + icaltimezone *zone = NULL; + + if (!e_cal_client_get_timezone_sync (client, tzid, &zone, NULL, &error)) { + g_warning ("%s: Cannot get timezone for '%s'. %s", G_STRFUNC, tzid, error ? error->message : ""); + if (error) + g_error_free (error); + } else if (zone && + icalcomponent_get_timezone (des_icalcomp, icaltimezone_get_tzid (zone)) == NULL) { + /* do not duplicate timezones in the component */ + icalcomponent *vtz_comp; + + vtz_comp = icaltimezone_get_component (zone); + if (vtz_comp) + icalcomponent_add_component (des_icalcomp, icalcomponent_new_clone (vtz_comp)); + } + } + } + } + } +} + +static void +calendar_view_copy_clipboard (ESelectable *selectable) +{ + ECalendarView *cal_view; + ECalendarViewPrivate *priv; + GList *selected, *l; + gchar *comp_str; + icalcomponent *vcal_comp; + icalcomponent *new_icalcomp; + ECalendarViewEvent *event; + GtkClipboard *clipboard; + + cal_view = E_CALENDAR_VIEW (selectable); + priv = cal_view->priv; + + selected = e_calendar_view_get_selected_events (cal_view); + if (!selected) + return; + + if (priv->selected_cut_list) { + g_slist_foreach (priv->selected_cut_list, (GFunc) g_object_unref, NULL); + g_slist_free (priv->selected_cut_list); + priv->selected_cut_list = NULL; + } + + /* create top-level VCALENDAR component and add VTIMEZONE's */ + vcal_comp = e_cal_util_new_top_level (); + for (l = selected; l != NULL; l = l->next) { + event = (ECalendarViewEvent *) l->data; + + if (event && is_comp_data_valid (event)) { + e_cal_util_add_timezones_from_component (vcal_comp, event->comp_data->icalcomp); + + add_related_timezones (vcal_comp, event->comp_data->icalcomp, event->comp_data->client); + } + } + + for (l = selected; l != NULL; l = l->next) { + event = (ECalendarViewEvent *) l->data; + + if (!is_comp_data_valid (event)) + continue; + + new_icalcomp = icalcomponent_new_clone (event->comp_data->icalcomp); + + /* do not remove RECURRENCE-IDs from copied objects */ + icalcomponent_add_component (vcal_comp, new_icalcomp); + } + + comp_str = icalcomponent_as_ical_string_r (vcal_comp); + + /* copy the VCALENDAR to the clipboard */ + clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); + e_clipboard_set_calendar (clipboard, comp_str, -1); + gtk_clipboard_store (clipboard); + + /* free memory */ + icalcomponent_free (vcal_comp); + g_free (comp_str); + g_list_free (selected); +} + +static gboolean +clipboard_get_calendar_data (ECalendarView *cal_view, + const gchar *text, + GSList **copied_list) +{ + icalcomponent *icalcomp; + icalcomponent_kind kind; + time_t selected_time_start, selected_time_end; + icaltimezone *default_zone; + ECalClient *client; + gboolean in_top_canvas, ret = FALSE; + + g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), FALSE); + + if (!text || !*text) + return FALSE; + + icalcomp = icalparser_parse_string (text); + if (!icalcomp) + return FALSE; + + /* check the type of the component */ + /* FIXME An error dialog if we return? */ + kind = icalcomponent_isa (icalcomp); + if (kind != ICAL_VCALENDAR_COMPONENT && kind != ICAL_VEVENT_COMPONENT) + return FALSE; + + default_zone = e_cal_model_get_timezone (cal_view->priv->model); + client = e_cal_model_ref_default_client (cal_view->priv->model); + +#if 0 /* KILL-BONOBO */ + e_calendar_view_set_status_message (cal_view, _("Updating objects"), -1); +#endif + e_calendar_view_get_selected_time_range (cal_view, &selected_time_start, &selected_time_end); + + if ((selected_time_end - selected_time_start) == 60 * 60 * 24) + in_top_canvas = TRUE; + else + in_top_canvas = FALSE; + + if (kind == ICAL_VCALENDAR_COMPONENT) { + icalcomponent *subcomp; + + /* add timezones first, to have them ready */ + for (subcomp = icalcomponent_get_first_component (icalcomp, ICAL_VTIMEZONE_COMPONENT); + subcomp; + subcomp = icalcomponent_get_next_component (icalcomp, ICAL_VTIMEZONE_COMPONENT)) { + icaltimezone *zone; + GError *error = NULL; + + zone = icaltimezone_new (); + icaltimezone_set_component (zone, subcomp); + if (!e_cal_client_add_timezone_sync (client, zone, NULL, &error)) { + icalproperty *tzidprop = icalcomponent_get_first_property (subcomp, ICAL_TZID_PROPERTY); + + g_warning ("%s: Add zone '%s' failed. %s", G_STRFUNC, tzidprop ? icalproperty_get_tzid (tzidprop) : "???", error ? error->message : ""); + if (error) + g_error_free (error); + } + + icaltimezone_free (zone, 1); + } + + for (subcomp = icalcomponent_get_first_component (icalcomp, ICAL_VEVENT_COMPONENT); + subcomp; + subcomp = icalcomponent_get_next_component (icalcomp, ICAL_VEVENT_COMPONENT)) { + if (e_cal_util_component_has_recurrences (subcomp)) { + icalproperty *icalprop = icalcomponent_get_first_property (subcomp, ICAL_RRULE_PROPERTY); + if (icalprop) + icalproperty_remove_parameter_by_name (icalprop, "X-EVOLUTION-ENDDATE"); + } + + ret = e_calendar_view_add_event (cal_view, client, selected_time_start, default_zone, subcomp, in_top_canvas); + if (!ret) + break; + + if (copied_list) + *copied_list = g_slist_prepend (*copied_list, g_strdup (icalcomponent_get_uid (subcomp))); + } + + icalcomponent_free (icalcomp); + } else { + ret = e_calendar_view_add_event (cal_view, client, selected_time_start, default_zone, icalcomp, in_top_canvas); + if (ret && copied_list) + *copied_list = g_slist_prepend (*copied_list, g_strdup (icalcomponent_get_uid (icalcomp))); + } + + g_object_unref (client); + + return ret; + +#if 0 /* KILL-BONOBO */ + e_calendar_view_set_status_message (cal_view, NULL, -1); +#endif +} + +static void +calendar_view_paste_clipboard (ESelectable *selectable) +{ + ECalModel *model; + ECalendarView *cal_view; + ECalendarViewPrivate *priv; + ESourceRegistry *registry; + GtkClipboard *clipboard; + + cal_view = E_CALENDAR_VIEW (selectable); + priv = cal_view->priv; + + model = e_calendar_view_get_model (cal_view); + registry = e_cal_model_get_registry (model); + + clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); + + /* Paste text into an event being edited. */ + if (gtk_clipboard_wait_is_text_available (clipboard)) { + ECalendarViewClass *class; + + class = E_CALENDAR_VIEW_GET_CLASS (cal_view); + g_return_if_fail (class->paste_text != NULL); + + class->paste_text (cal_view); + + /* Paste iCalendar data into the view. */ + } else if (e_clipboard_wait_is_calendar_available (clipboard)) { + gchar *calendar_source; + GSList *copied_list = NULL, *l; + + calendar_source = e_clipboard_wait_for_calendar (clipboard); + + if (priv->selected_cut_list) + clipboard_get_calendar_data (cal_view, calendar_source, &copied_list); + else + clipboard_get_calendar_data (cal_view, calendar_source, NULL); + + if (copied_list && priv->selected_cut_list) { + for (l = priv->selected_cut_list; l != NULL; l = l->next) { + ECalComponent *comp; + ECalModelComponent *comp_data = (ECalModelComponent *) l->data; + const gchar *uid; + GError *error = NULL; + GSList *found = NULL; + + /* Remove them one by one after ensuring it has been copied to the destination successfully */ + found = g_slist_find_custom (copied_list, icalcomponent_get_uid (comp_data->icalcomp), (GCompareFunc) strcmp); + if (!found) + continue; + + g_free (found->data); + copied_list = g_slist_delete_link (copied_list, found); + + comp = e_cal_component_new (); + e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (comp_data->icalcomp)); + + if ((itip_organizer_is_user (registry, comp, comp_data->client) || + itip_sentby_is_user (registry, comp, comp_data->client)) + && cancel_component_dialog ((GtkWindow *) gtk_widget_get_toplevel (GTK_WIDGET (cal_view)), + comp_data->client, comp, TRUE)) + itip_send_comp ( + registry, + E_CAL_COMPONENT_METHOD_CANCEL, + comp, comp_data->client, + NULL, NULL, NULL, TRUE, FALSE); + + e_cal_component_get_uid (comp, &uid); + if (e_cal_component_is_instance (comp)) { + gchar *rid = NULL; + icalcomponent *icalcomp; + + /* when cutting detached instances, only cut that instance */ + rid = e_cal_component_get_recurid_as_string (comp); + if (e_cal_client_get_object_sync (comp_data->client, uid, rid, &icalcomp, NULL, NULL)) { + e_cal_client_remove_object_sync (comp_data->client, uid, rid, CALOBJ_MOD_THIS, NULL, &error); + icalcomponent_free (icalcomp); + } else + e_cal_client_remove_object_sync (comp_data->client, uid, NULL, CALOBJ_MOD_ALL, NULL, &error); + g_free (rid); + } else + e_cal_client_remove_object_sync (comp_data->client, uid, NULL, CALOBJ_MOD_ALL, NULL, &error); + delete_error_dialog (error, E_CAL_COMPONENT_EVENT); + + g_clear_error (&error); + g_object_unref (comp); + } + } + + if (priv->selected_cut_list) { + g_slist_foreach (priv->selected_cut_list, (GFunc) g_object_unref, NULL); + g_slist_free (priv->selected_cut_list); + } + priv->selected_cut_list = NULL; + + g_free (calendar_source); + + } +} + +static void +calendar_view_delete_selection (ESelectable *selectable) +{ + ECalendarView *cal_view; + GList *selected, *iter; + + cal_view = E_CALENDAR_VIEW (selectable); + + selected = e_calendar_view_get_selected_events (cal_view); + + for (iter = selected; iter != NULL; iter = iter->next) { + ECalendarViewEvent *event = iter->data; + + /* XXX Why would this ever be NULL? */ + if (event == NULL) + continue; + + calendar_view_delete_event (cal_view, event); + } + + g_list_free (selected); +} + +static void +e_calendar_view_class_init (ECalendarViewClass *class) +{ + GObjectClass *object_class; + GtkBindingSet *binding_set; + + g_type_class_add_private (class, sizeof (ECalendarViewPrivate)); + + object_class = G_OBJECT_CLASS (class); + object_class->set_property = calendar_view_set_property; + object_class->get_property = calendar_view_get_property; + object_class->dispose = calendar_view_dispose; + object_class->finalize = calendar_view_finalize; + object_class->constructed = calendar_view_constructed; + + class->selection_changed = NULL; + class->selected_time_changed = NULL; + class->event_changed = NULL; + class->event_added = NULL; + class->user_created = NULL; + + class->get_selected_events = NULL; + class->get_selected_time_range = NULL; + class->set_selected_time_range = NULL; + class->get_visible_time_range = NULL; + class->update_query = NULL; + class->open_event = e_calendar_view_open_event; + class->paste_text = NULL; + + /* Inherited from ESelectableInterface */ + g_object_class_override_property ( + object_class, + PROP_COPY_TARGET_LIST, + "copy-target-list"); + + g_object_class_install_property ( + object_class, + PROP_MODEL, + g_param_spec_object ( + "model", + "Model", + NULL, + E_TYPE_CAL_MODEL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + /* Inherited from ESelectableInterface */ + g_object_class_override_property ( + object_class, + PROP_PASTE_TARGET_LIST, + "paste-target-list"); + + g_object_class_install_property ( + object_class, + PROP_TIME_DIVISIONS, + g_param_spec_int ( + "time-divisions", + "Time Divisions", + NULL, + G_MININT, + G_MAXINT, + 30, + G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_IS_EDITING, + g_param_spec_boolean ( + "is-editing", + "Whether is in an editing mode", + "Whether is in an editing mode", + FALSE, + G_PARAM_READABLE)); + + signals[POPUP_EVENT] = g_signal_new ( + "popup-event", + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (ECalendarViewClass, popup_event), + NULL, NULL, + g_cclosure_marshal_VOID__BOXED, + G_TYPE_NONE, 1, + GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE); + + signals[SELECTION_CHANGED] = g_signal_new ( + "selection-changed", + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (ECalendarViewClass, selection_changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[SELECTED_TIME_CHANGED] = g_signal_new ( + "selected-time-changed", + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (ECalendarViewClass, selected_time_changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[TIMEZONE_CHANGED] = g_signal_new ( + "timezone-changed", + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (ECalendarViewClass, timezone_changed), + NULL, NULL, + e_marshal_VOID__POINTER_POINTER, + G_TYPE_NONE, 2, + G_TYPE_POINTER, + G_TYPE_POINTER); + + signals[EVENT_CHANGED] = g_signal_new ( + "event-changed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (ECalendarViewClass, event_changed), + NULL, NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, 1, + G_TYPE_POINTER); + + signals[EVENT_ADDED] = g_signal_new ( + "event-added", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (ECalendarViewClass, event_added), + NULL, NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, 1, + G_TYPE_POINTER); + + signals[USER_CREATED] = g_signal_new ( + "user-created", + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (ECalendarViewClass, user_created), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, G_TYPE_OBJECT); + + signals[OPEN_EVENT] = g_signal_new ( + "open-event", + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (ECalendarViewClass, open_event), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + /* Key bindings */ + + binding_set = gtk_binding_set_by_class (class); + + gtk_binding_entry_add_signal ( + binding_set, GDK_KEY_o, GDK_CONTROL_MASK, "open-event", 0); + + /* init the accessibility support for e_day_view */ + e_cal_view_a11y_init (); +} + +static void +e_calendar_view_init (ECalendarView *calendar_view) +{ + GtkTargetList *target_list; + + calendar_view->priv = E_CALENDAR_VIEW_GET_PRIVATE (calendar_view); + + /* Set this early to avoid a divide-by-zero during init. */ + calendar_view->priv->time_divisions = 30; + + target_list = gtk_target_list_new (NULL, 0); + e_target_list_add_calendar_targets (target_list, 0); + calendar_view->priv->copy_target_list = target_list; + + target_list = gtk_target_list_new (NULL, 0); + e_target_list_add_calendar_targets (target_list, 0); + calendar_view->priv->paste_target_list = target_list; +} + +static void +calendar_view_selectable_init (ESelectableInterface *interface) +{ + interface->update_actions = calendar_view_update_actions; + interface->cut_clipboard = calendar_view_cut_clipboard; + interface->copy_clipboard = calendar_view_copy_clipboard; + interface->paste_clipboard = calendar_view_paste_clipboard; + interface->delete_selection = calendar_view_delete_selection; +} + +void +e_calendar_view_popup_event (ECalendarView *calendar_view, + GdkEvent *button_event) +{ + g_return_if_fail (E_IS_CALENDAR_VIEW (calendar_view)); + g_return_if_fail (button_event != NULL); + + g_signal_emit (calendar_view, signals[POPUP_EVENT], 0, button_event); +} + +gboolean +e_calendar_view_add_event (ECalendarView *cal_view, + ECalClient *client, + time_t dtstart, + icaltimezone *default_zone, + icalcomponent *icalcomp, + gboolean in_top_canvas) +{ + ECalModel *model; + ECalComponent *comp; + ESourceRegistry *registry; + struct icaltimetype itime, old_dtstart, old_dtend; + time_t tt_start, tt_end, new_dtstart = 0; + struct icaldurationtype ic_dur, ic_oneday; + gchar *uid; + gint start_offset, end_offset; + gboolean all_day_event = FALSE; + GnomeCalendarViewType view_type; + gboolean ret = TRUE; + GError *error = NULL; + + model = e_calendar_view_get_model (cal_view); + registry = e_cal_model_get_registry (model); + + start_offset = 0; + end_offset = 0; + + old_dtstart = icalcomponent_get_dtstart (icalcomp); + tt_start = icaltime_as_timet (old_dtstart); + old_dtend = icalcomponent_get_dtend (icalcomp); + tt_end = icaltime_as_timet (old_dtend); + ic_dur = icaldurationtype_from_int (tt_end - tt_start); + + if (icaldurationtype_as_int (ic_dur) > 60 *60 *24) { + /* This is a long event */ + start_offset = old_dtstart.hour * 60 + old_dtstart.minute; + end_offset = old_dtstart.hour * 60 + old_dtend.minute; + } + + ic_oneday = icaldurationtype_null_duration (); + ic_oneday.days = 1; + + view_type = gnome_calendar_get_view (cal_view->priv->calendar); + + switch (view_type) { + case GNOME_CAL_DAY_VIEW: + case GNOME_CAL_WORK_WEEK_VIEW: + if (start_offset == 0 && end_offset == 0 && in_top_canvas) + all_day_event = TRUE; + + if (all_day_event) { + ic_dur = ic_oneday; + } else if (icaldurationtype_as_int (ic_dur) >= 60 *60 *24 + && !in_top_canvas) { + /* copy & paste from top canvas to main canvas */ + gint time_divisions; + + time_divisions = e_calendar_view_get_time_divisions (cal_view); + ic_dur = icaldurationtype_from_int (time_divisions * 60); + } + + if (in_top_canvas) + new_dtstart = dtstart + start_offset * 60; + else + new_dtstart = dtstart; + break; + case GNOME_CAL_WEEK_VIEW: + case GNOME_CAL_MONTH_VIEW: + case GNOME_CAL_LIST_VIEW: + if (old_dtstart.is_date && old_dtend.is_date + && memcmp (&ic_dur, &ic_oneday, sizeof (ic_dur)) == 0) { + all_day_event = TRUE; + new_dtstart = dtstart; + } else { + icaltimetype new_time = icaltime_from_timet_with_zone (dtstart, FALSE, default_zone); + + new_time.hour = old_dtstart.hour; + new_time.minute = old_dtstart.minute; + new_time.second = old_dtstart.second; + + new_dtstart = icaltime_as_timet_with_zone (new_time, old_dtstart.zone ? old_dtstart.zone : default_zone); + } + break; + default: + g_return_val_if_reached (FALSE); + } + + itime = icaltime_from_timet_with_zone (new_dtstart, FALSE, old_dtstart.zone ? old_dtstart.zone : default_zone); + /* set the timezone properly */ + itime.zone = old_dtstart.zone ? old_dtstart.zone : default_zone; + if (all_day_event) + itime.is_date = TRUE; + icalcomponent_set_dtstart (icalcomp, itime); + + itime.is_date = FALSE; + itime = icaltime_add (itime, ic_dur); + if (all_day_event) + itime.is_date = TRUE; + icalcomponent_set_dtend (icalcomp, itime); + + /* FIXME The new uid stuff can go away once we actually set it in the backend */ + uid = e_cal_component_gen_uid (); + comp = e_cal_component_new (); + e_cal_component_set_icalcomponent ( + comp, icalcomponent_new_clone (icalcomp)); + e_cal_component_set_uid (comp, uid); + g_free (uid); + + e_cal_component_commit_sequence (comp); + + uid = NULL; + if (e_cal_client_create_object_sync (client, e_cal_component_get_icalcomponent (comp), &uid, NULL, &error)) { + gboolean strip_alarms = TRUE; + + if (uid) { + e_cal_component_set_uid (comp, uid); + g_free (uid); + } + + if ((itip_organizer_is_user (registry, comp, client) || + itip_sentby_is_user (registry, comp, client)) && + send_component_dialog ( + (GtkWindow *) gtk_widget_get_toplevel ( + GTK_WIDGET (cal_view)), + client, comp, TRUE, &strip_alarms, NULL)) { + itip_send_comp ( + registry, E_CAL_COMPONENT_METHOD_REQUEST, + comp, client, NULL, NULL, NULL, strip_alarms, + FALSE); + } + } else { + g_message (G_STRLOC ": Could not create the object! %s", error ? error->message : ""); + if (error) + g_error_free (error); + ret = FALSE; + } + + g_object_unref (comp); + return ret; +} + +GnomeCalendar * +e_calendar_view_get_calendar (ECalendarView *cal_view) +{ + g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), NULL); + + return cal_view->priv->calendar; +} + +void +e_calendar_view_set_calendar (ECalendarView *cal_view, + GnomeCalendar *calendar) +{ + g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view)); + + cal_view->priv->calendar = calendar; +} + +ECalModel * +e_calendar_view_get_model (ECalendarView *cal_view) +{ + g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), NULL); + + return cal_view->priv->model; +} + +icaltimezone * +e_calendar_view_get_timezone (ECalendarView *cal_view) +{ + g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), NULL); + return e_cal_model_get_timezone (cal_view->priv->model); +} + +void +e_calendar_view_set_timezone (ECalendarView *cal_view, + icaltimezone *zone) +{ + icaltimezone *old_zone; + + g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view)); + + old_zone = e_cal_model_get_timezone (cal_view->priv->model); + if (old_zone == zone) + return; + + e_cal_model_set_timezone (cal_view->priv->model, zone); + g_signal_emit ( + cal_view, signals[TIMEZONE_CHANGED], 0, + old_zone, zone); +} + +const gchar * +e_calendar_view_get_default_category (ECalendarView *cal_view) +{ + g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), NULL); + + return cal_view->priv->default_category; +} + +/** + * e_calendar_view_set_default_category + * @cal_view: A calendar view. + * @category: Default category name or NULL for no category. + * + * Sets the default category that will be used when creating new calendar + * components from the given calendar view. + */ +void +e_calendar_view_set_default_category (ECalendarView *cal_view, + const gchar *category) +{ + g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view)); + + g_free (cal_view->priv->default_category); + cal_view->priv->default_category = g_strdup (category); +} + +GtkTargetList * +e_calendar_view_get_copy_target_list (ECalendarView *cal_view) +{ + g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), NULL); + + return cal_view->priv->copy_target_list; +} + +GtkTargetList * +e_calendar_view_get_paste_target_list (ECalendarView *cal_view) +{ + g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), NULL); + + return cal_view->priv->paste_target_list; +} + +gint +e_calendar_view_get_time_divisions (ECalendarView *cal_view) +{ + g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), 0); + + return cal_view->priv->time_divisions; +} + +void +e_calendar_view_set_time_divisions (ECalendarView *cal_view, + gint time_divisions) +{ + g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view)); + + if (cal_view->priv->time_divisions == time_divisions) + return; + + cal_view->priv->time_divisions = time_divisions; + + g_object_notify (G_OBJECT (cal_view), "time-divisions"); +} + +GList * +e_calendar_view_get_selected_events (ECalendarView *cal_view) +{ + ECalendarViewClass *class; + + g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), NULL); + + class = E_CALENDAR_VIEW_GET_CLASS (cal_view); + g_return_val_if_fail (class->get_selected_events != NULL, NULL); + + return class->get_selected_events (cal_view); +} + +gboolean +e_calendar_view_get_selected_time_range (ECalendarView *cal_view, + time_t *start_time, + time_t *end_time) +{ + ECalendarViewClass *class; + + g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), FALSE); + + class = E_CALENDAR_VIEW_GET_CLASS (cal_view); + g_return_val_if_fail (class->get_selected_time_range != NULL, FALSE); + + return class->get_selected_time_range (cal_view, start_time, end_time); +} + +void +e_calendar_view_set_selected_time_range (ECalendarView *cal_view, + time_t start_time, + time_t end_time) +{ + ECalendarViewClass *class; + + g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view)); + + /* Not all views implement this, so return silently. */ + class = E_CALENDAR_VIEW_GET_CLASS (cal_view); + if (class->set_selected_time_range == NULL) + return; + + class->set_selected_time_range (cal_view, start_time, end_time); +} + +gboolean +e_calendar_view_get_visible_time_range (ECalendarView *cal_view, + time_t *start_time, + time_t *end_time) +{ + ECalendarViewClass *class; + + g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), FALSE); + + class = E_CALENDAR_VIEW_GET_CLASS (cal_view); + g_return_val_if_fail (class->get_visible_time_range != NULL, FALSE); + + return class->get_visible_time_range (cal_view, start_time, end_time); +} + +void +e_calendar_view_update_query (ECalendarView *cal_view) +{ + ECalendarViewClass *class; + + g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view)); + + class = E_CALENDAR_VIEW_GET_CLASS (cal_view); + g_return_if_fail (class->update_query != NULL); + + class->update_query (cal_view); +} + +void +e_calendar_view_delete_selected_occurrence (ECalendarView *cal_view) +{ + GList *selected; + ECalModel *model; + ECalComponent *comp; + ECalendarViewEvent *event; + ECalComponentVType vtype; + ESourceRegistry *registry; + gboolean delete = TRUE; + GError *error = NULL; + + model = e_calendar_view_get_model (cal_view); + registry = e_cal_model_get_registry (model); + + selected = e_calendar_view_get_selected_events (cal_view); + if (!selected) + return; + event = (ECalendarViewEvent *) selected->data; + if (!is_comp_data_valid (event)) + return; + + comp = e_cal_component_new (); + e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (event->comp_data->icalcomp)); + vtype = e_cal_component_get_vtype (comp); + + /*FIXME Retract should be moved to Groupwise features plugin */ + if (calendar_view_check_for_retract (comp, event->comp_data->client)) { + gchar *retract_comment = NULL; + gboolean retract = FALSE; + + delete = prompt_retract_dialog (comp, &retract_comment, GTK_WIDGET (cal_view), &retract); + if (retract) { + GSList *users = NULL; + icalcomponent *icalcomp = NULL, *mod_comp = NULL; + + calendar_view_add_retract_data ( + comp, retract_comment, CALOBJ_MOD_THIS); + icalcomp = e_cal_component_get_icalcomponent (comp); + icalcomponent_set_method (icalcomp, ICAL_METHOD_CANCEL); + if (!e_cal_client_send_objects_sync (event->comp_data->client, icalcomp, &users, + &mod_comp, NULL, &error)) { + delete_error_dialog (error, E_CAL_COMPONENT_EVENT); + g_clear_error (&error); + error = NULL; + } else { + if (mod_comp) + icalcomponent_free (mod_comp); + if (users) { + g_slist_foreach (users, (GFunc) g_free, NULL); + g_slist_free (users); + } + } + } + } else if (e_cal_model_get_confirm_delete (model)) + delete = delete_component_dialog ( + comp, FALSE, 1, vtype, GTK_WIDGET (cal_view)); + + if (delete) { + const gchar *uid; + gchar *rid = NULL; + ECalComponentDateTime dt; + icaltimezone *zone = NULL; + gboolean is_instance = FALSE; + + e_cal_component_get_uid (comp, &uid); + e_cal_component_get_dtstart (comp, &dt); + is_instance = e_cal_component_is_instance (comp); + + if (dt.tzid) { + GError *error = NULL; + + e_cal_client_get_timezone_sync (event->comp_data->client, dt.tzid, &zone, NULL, &error); + if (error) { + zone = e_calendar_view_get_timezone (cal_view); + g_clear_error (&error); + } + } else + zone = e_calendar_view_get_timezone (cal_view); + + if (is_instance) + rid = e_cal_component_get_recurid_as_string (comp); + + e_cal_component_free_datetime (&dt); + + if ((itip_organizer_is_user (registry, comp, event->comp_data->client) || + itip_sentby_is_user (registry, comp, event->comp_data->client)) + && cancel_component_dialog ((GtkWindow *) gtk_widget_get_toplevel (GTK_WIDGET (cal_view)), + event->comp_data->client, + comp, TRUE) && !e_cal_client_check_save_schedules (event->comp_data->client)) { + if (!e_cal_component_is_instance (comp)) { + ECalComponentRange range; + + /* set the recurrence ID of the object we send */ + range.type = E_CAL_COMPONENT_RANGE_SINGLE; + e_cal_component_get_dtstart (comp, &range.datetime); + range.datetime.value->is_date = 1; + e_cal_component_set_recurid (comp, &range); + + e_cal_component_free_datetime (&range.datetime); + } + + itip_send_comp ( + registry, E_CAL_COMPONENT_METHOD_CANCEL, + comp, event->comp_data->client, NULL, NULL, + NULL, TRUE, FALSE); + } + + if (is_instance) + e_cal_client_remove_object_sync (event->comp_data->client, uid, rid, CALOBJ_MOD_THIS, NULL, &error); + else { + struct icaltimetype instance_rid; + + instance_rid = icaltime_from_timet_with_zone ( + event->comp_data->instance_start, + TRUE, zone ? zone : icaltimezone_get_utc_timezone ()); + e_cal_util_remove_instances (event->comp_data->icalcomp, instance_rid, CALOBJ_MOD_THIS); + e_cal_client_modify_object_sync (event->comp_data->client, event->comp_data->icalcomp, CALOBJ_MOD_THIS, NULL, &error); + } + + delete_error_dialog (error, E_CAL_COMPONENT_EVENT); + g_clear_error (&error); + g_free (rid); + } + + /* free memory */ + g_list_free (selected); + g_object_unref (comp); +} + +void +e_calendar_view_open_event (ECalendarView *cal_view) +{ + GList *selected; + + selected = e_calendar_view_get_selected_events (cal_view); + if (selected) { + ECalendarViewEvent *event = (ECalendarViewEvent *) selected->data; + if (event && is_comp_data_valid (event)) + e_calendar_view_edit_appointment (cal_view, event->comp_data->client, event->comp_data->icalcomp, EDIT_EVENT_AUTODETECT); + + g_list_free (selected); + } +} + +/** + * e_calendar_view_new_appointment_for + * @cal_view: A calendar view. + * @dtstart: A Unix time_t that marks the beginning of the appointment. + * @dtend: A Unix time_t that marks the end of the appointment. + * @all_day: If TRUE, the dtstart and dtend are expanded to cover + * the entire day, and the event is set to TRANSPARENT. + * @meeting: Whether the appointment is a meeting or not. + * + * Opens an event editor dialog for a new appointment. + */ +void +e_calendar_view_new_appointment_for (ECalendarView *cal_view, + time_t dtstart, + time_t dtend, + gboolean all_day, + gboolean meeting) +{ + ECalendarViewPrivate *priv; + struct icaltimetype itt; + ECalComponentDateTime dt; + ECalComponent *comp; + icalcomponent *icalcomp; + ECalComponentTransparency transparency; + ECalClient *default_client = NULL; + gpointer parent; + guint32 flags = 0; + + g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view)); + + parent = gtk_widget_get_toplevel (GTK_WIDGET (cal_view)); + parent = gtk_widget_is_toplevel (parent) ? parent : NULL; + + priv = cal_view->priv; + + default_client = e_cal_model_ref_default_client (priv->model); + g_return_if_fail (default_client != NULL); + + dt.value = &itt; + if (all_day) + dt.tzid = NULL; + else + dt.tzid = icaltimezone_get_tzid (e_cal_model_get_timezone (cal_view->priv->model)); + + icalcomp = e_cal_model_create_component_with_defaults (priv->model, all_day); + comp = e_cal_component_new (); + e_cal_component_set_icalcomponent (comp, icalcomp); + + /* DTSTART, DTEND */ + itt = icaltime_from_timet_with_zone (dtstart, FALSE, e_cal_model_get_timezone (cal_view->priv->model)); + if (all_day) { + itt.hour = itt.minute = itt.second = 0; + itt.is_date = TRUE; + } + e_cal_component_set_dtstart (comp, &dt); + + itt = icaltime_from_timet_with_zone (dtend, FALSE, e_cal_model_get_timezone (cal_view->priv->model)); + if (all_day) { + /* We round it up to the end of the day, unless it is + * already set to midnight */ + if (itt.hour != 0 || itt.minute != 0 || itt.second != 0) { + icaltime_adjust (&itt, 1, 0, 0, 0); + } + itt.hour = itt.minute = itt.second = 0; + itt.is_date = TRUE; + } + e_cal_component_set_dtend (comp, &dt); + + /* TRANSPARENCY */ + transparency = all_day ? E_CAL_COMPONENT_TRANSP_TRANSPARENT + : E_CAL_COMPONENT_TRANSP_OPAQUE; + e_cal_component_set_transparency (comp, transparency); + + /* CATEGORY */ + e_cal_component_set_categories (comp, priv->default_category); + + /* edit the object */ + e_cal_component_commit_sequence (comp); + + flags |= COMP_EDITOR_NEW_ITEM; + if (meeting) { + flags |= COMP_EDITOR_MEETING; + flags |= COMP_EDITOR_USER_ORG; + } + + e_calendar_view_open_event_with_flags ( + cal_view, default_client, icalcomp, flags); + + g_object_unref (comp); + + g_object_unref (default_client); +} + +/** + * e_calendar_view_new_appointment_full + * @cal_view: an #ECalendarView + * @all_day: Whether create all day event or not. + * @meeting: This is a meeting or an appointment. + * @no_past_date: Don't create event in past date, use actual date instead + * (if %TRUE). + * + * Opens an event editor dialog for a new appointment. The appointment's + * start and end times are set to the currently selected time range in + * the calendar view. + * + * When the selection is for all day and we don't need @all_day event, + * then this do a rounding to the actual hour for actual day (today) and + * to the 'day begins' from preferences in other selected day. + */ +void +e_calendar_view_new_appointment_full (ECalendarView *cal_view, + gboolean all_day, + gboolean meeting, + gboolean no_past_date) +{ + ECalModel *model; + time_t dtstart, dtend, now; + gboolean do_rounding = FALSE; + + g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view)); + + model = e_calendar_view_get_model (cal_view); + + now = time (NULL); + + if (!e_calendar_view_get_selected_time_range (cal_view, &dtstart, &dtend)) { + dtstart = now; + dtend = dtstart + 3600; + } + + if (no_past_date && dtstart < now) { + dtend = time_day_begin (now) + (dtend - dtstart); + dtstart = time_day_begin (now); + do_rounding = TRUE; + } + + /* We either need rounding or don't want to set all_day for this, we will rather use actual */ + /* time in this cases; dtstart should be a midnight in this case */ + if (do_rounding || (!all_day && (dtend - dtstart) == (60 * 60 * 24))) { + struct tm local = *localtime (&now); + gint time_div = e_calendar_view_get_time_divisions (cal_view); + gint hours, mins; + + if (!time_div) /* Possible if your settings values aren't so nice */ + time_div = 30; + + if (time_day_begin (now) == time_day_begin (dtstart)) { + /* same day as today */ + hours = local.tm_hour; + mins = local.tm_min; + + /* round minutes to nearest time division, up or down */ + if ((mins % time_div) >= time_div / 2) + mins += time_div; + mins = (mins - (mins % time_div)); + } else { + /* other day than today */ + hours = e_cal_model_get_work_day_start_hour (model); + mins = e_cal_model_get_work_day_start_minute (model); + } + + dtstart = dtstart + (60 * 60 * hours) + (mins * 60); + dtend = dtstart + (time_div * 60); + } + + e_calendar_view_new_appointment_for (cal_view, dtstart, dtend, all_day, meeting); +} + +void +e_calendar_view_new_appointment (ECalendarView *cal_view) +{ + g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view)); + + e_calendar_view_new_appointment_full (cal_view, FALSE, FALSE, FALSE); +} + +/* Ensures the calendar is selected */ +static void +object_created_cb (CompEditor *ce, + ECalendarView *cal_view) +{ + e_calendar_view_emit_user_created (cal_view, comp_editor_get_client (ce)); +} + +CompEditor * +e_calendar_view_open_event_with_flags (ECalendarView *cal_view, + ECalClient *client, + icalcomponent *icalcomp, + guint32 flags) +{ + CompEditor *ce; + const gchar *uid; + ECalComponent *comp; + EShell *shell; + + /* FIXME ECalendarView should own an EShell pointer. */ + shell = e_shell_get_default (); + + uid = icalcomponent_get_uid (icalcomp); + + ce = comp_editor_find_instance (uid); + if (!ce) { + ce = event_editor_new (client, shell, flags); + + g_signal_connect ( + ce, "object_created", + G_CALLBACK (object_created_cb), cal_view); + + comp = e_cal_component_new (); + e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (icalcomp)); + comp_editor_edit_comp (ce, comp); + if (flags & COMP_EDITOR_MEETING) + event_editor_show_meeting (EVENT_EDITOR (ce)); + + g_object_unref (comp); + } + + gtk_window_present (GTK_WINDOW (ce)); + + return ce; +} + +/** + * e_calendar_view_edit_appointment + * @cal_view: A calendar view. + * @client: Calendar client. + * @icalcomp: The object to be edited. + * @mode: one of #EEditEventMode + * + * Opens an editor window to allow the user to edit the selected + * object. + */ +void +e_calendar_view_edit_appointment (ECalendarView *cal_view, + ECalClient *client, + icalcomponent *icalcomp, + EEditEventMode mode) +{ + ECalModel *model; + ESourceRegistry *registry; + guint32 flags = 0; + + g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view)); + g_return_if_fail (E_IS_CAL_CLIENT (client)); + g_return_if_fail (icalcomp != NULL); + + model = e_calendar_view_get_model (cal_view); + registry = e_cal_model_get_registry (model); + + if ((mode == EDIT_EVENT_AUTODETECT && icalcomponent_get_first_property (icalcomp, ICAL_ATTENDEE_PROPERTY) != NULL) + || mode == EDIT_EVENT_FORCE_MEETING) { + ECalComponent *comp = e_cal_component_new (); + e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (icalcomp)); + flags |= COMP_EDITOR_MEETING; + if (itip_organizer_is_user (registry, comp, client) || + itip_sentby_is_user (registry, comp, client) || + !e_cal_component_has_attendees (comp)) + flags |= COMP_EDITOR_USER_ORG; + g_object_unref (comp); + } + + e_calendar_view_open_event_with_flags (cal_view, client, icalcomp, flags); +} + +void +e_calendar_view_modify_and_send (ECalendarView *cal_view, + ECalComponent *comp, + ECalClient *client, + CalObjModType mod, + GtkWindow *toplevel, + gboolean new) +{ + ECalModel *model; + ESourceRegistry *registry; + gboolean only_new_attendees = FALSE; + gboolean strip_alarms = TRUE; + + if (e_calendar_view_modify (cal_view, comp, client, mod)) { + model = e_calendar_view_get_model (cal_view); + registry = e_cal_model_get_registry (model); + + if ((itip_organizer_is_user (registry, comp, client) || + itip_sentby_is_user (registry, comp, client)) && + send_component_dialog (toplevel, client, comp, new, &strip_alarms, &only_new_attendees)) + e_calendar_view_send (cal_view, comp, client, mod, toplevel, strip_alarms, only_new_attendees); + } +} + +gboolean +e_calendar_view_modify (ECalendarView *cal_view, + ECalComponent *comp, + ECalClient *client, + CalObjModType mod) +{ + GError *error = NULL; + gboolean ret; + + g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), FALSE); + + e_cal_component_commit_sequence (comp); + + ret = e_cal_client_modify_object_sync ( + client, e_cal_component_get_icalcomponent (comp), + mod, NULL, &error); + + if (error != NULL) { + g_message ( + G_STRLOC ": Could not update the object! %s", + error->message); + + g_error_free (error); + } + + return ret; +} + +void +e_calendar_view_send (ECalendarView *cal_view, + ECalComponent *comp, + ECalClient *client, + CalObjModType mod, + GtkWindow *toplevel, + gboolean strip_alarms, + gboolean only_new_attendees) +{ + ESourceRegistry *registry; + ECalModel *model; + ECalComponent *send_comp = NULL; + + if (!itip_component_has_recipients (comp)) + return; + + if (mod == CALOBJ_MOD_ALL && e_cal_component_is_instance (comp)) { + /* Ensure we send the master object, not the instance only */ + icalcomponent *icalcomp = NULL; + const gchar *uid = NULL; + + e_cal_component_get_uid (comp, &uid); + if (e_cal_client_get_object_sync (client, uid, NULL, &icalcomp, NULL, NULL) && icalcomp) { + send_comp = e_cal_component_new (); + if (!e_cal_component_set_icalcomponent (send_comp, icalcomp)) { + icalcomponent_free (icalcomp); + g_object_unref (send_comp); + send_comp = NULL; + } else if (only_new_attendees) { + /* copy new-attendees information too if required for later use */ + comp_editor_copy_new_attendees (send_comp, comp); + } + } + } + + model = e_calendar_view_get_model (cal_view); + registry = e_cal_model_get_registry (model); + itip_send_comp ( + registry, E_CAL_COMPONENT_METHOD_REQUEST, + send_comp ? send_comp : comp, client, NULL, + NULL, NULL, strip_alarms, only_new_attendees); + + if (send_comp) + g_object_unref (send_comp); +} + +static gboolean +tooltip_grab (GtkWidget *tooltip, + GdkEvent *key_event, + ECalendarView *view) +{ + GtkWidget *widget; + GdkDevice *keyboard; + guint32 event_time; + + widget = g_object_get_data (G_OBJECT (view), "tooltip-window"); + if (widget == NULL) + return TRUE; + + event_time = gdk_event_get_time (key_event); + + while (!g_queue_is_empty (&view->priv->grabbed_keyboards)) { + keyboard = g_queue_pop_head (&view->priv->grabbed_keyboards); + gdk_device_ungrab (keyboard, event_time); + g_object_unref (keyboard); + } + + gtk_widget_destroy (widget); + g_object_set_data (G_OBJECT (view), "tooltip-window", NULL); + + return FALSE; +} + +static gchar * +get_label (struct icaltimetype *tt, + icaltimezone *f_zone, + icaltimezone *t_zone) +{ + struct tm tmp_tm; + + tmp_tm = icaltimetype_to_tm_with_zone (tt, f_zone, t_zone); + + return e_datetime_format_format_tm ("calendar", "table", DTFormatKindDateTime, &tmp_tm); +} + +void +e_calendar_view_move_tip (GtkWidget *widget, + gint x, + gint y) +{ + GtkAllocation allocation; + GtkRequisition requisition; + GdkDisplay *display; + GdkScreen *screen; + GdkScreen *pointer_screen; + GdkRectangle monitor; + GdkDeviceManager *device_manager; + GdkDevice *pointer; + gint monitor_num, px, py; + gint w, h; + + gtk_widget_get_preferred_size (widget, &requisition, NULL); + w = requisition.width; + h = requisition.height; + + screen = gtk_widget_get_screen (widget); + display = gdk_screen_get_display (screen); + device_manager = gdk_display_get_device_manager (display); + pointer = gdk_device_manager_get_client_pointer (device_manager); + + gdk_device_get_position (pointer, &pointer_screen, &px, &py); + if (pointer_screen != screen) { + px = x; + py = y; + } + monitor_num = gdk_screen_get_monitor_at_point (screen, px, py); + gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor); + + if ((x + w) > monitor.x + monitor.width) + x -= (x + w) - (monitor.x + monitor.width); + else if (x < monitor.x) + x = monitor.x; + + gtk_widget_get_allocation (widget, &allocation); + + if ((y + h + allocation.height + 4) > monitor.y + monitor.height) + y = y - h - 36; + + gtk_window_move (GTK_WINDOW (widget), x, y); + gtk_widget_show (widget); +} + +/* + * It is expected to show the tooltips in this below format + * + * <B>SUBJECT OF THE MEETING</B> + * Organiser: NameOfTheUser<email@ofuser.com> + * Location: PlaceOfTheMeeting + * Time : DateAndTime (xx Minutes) + * Status: Accepted: X Declined: Y ... + */ + +gboolean +e_calendar_view_get_tooltips (const ECalendarViewEventData *data) +{ + GtkWidget *label, *box, *hbox, *ebox, *frame; + const gchar *str; + gchar *tmp, *tmp1, *tmp2; + ECalComponentOrganizer organiser; + ECalComponentDateTime dtstart, dtend; + icalcomponent *clone_comp; + time_t t_start, t_end; + ECalendarViewEvent *pevent; + GtkStyle *style = gtk_widget_get_default_style (); + GtkWidget *widget; + GdkWindow *window; + GdkDisplay *display; + GdkDeviceManager *device_manager; + GQueue *grabbed_keyboards; + ECalComponent *newcomp = e_cal_component_new (); + icaltimezone *zone, *default_zone; + ECalModel *model; + ECalClient *client = NULL; + GList *list, *link; + gboolean free_text = FALSE; + + /* This function is a timeout callback. */ + + g_return_val_if_fail (data != NULL, FALSE); + g_return_val_if_fail (E_IS_CALENDAR_VIEW (data->cal_view), FALSE); + + model = e_calendar_view_get_model (data->cal_view); + + /* Delete any stray tooltip if left */ + widget = g_object_get_data ( + G_OBJECT (data->cal_view), "tooltip-window"); + if (GTK_IS_WIDGET (widget)) + gtk_widget_destroy (widget); + + default_zone = e_calendar_view_get_timezone (data->cal_view); + pevent = data->get_view_event (data->cal_view, data->day, data->event_num); + + if (!is_comp_data_valid (pevent)) + return FALSE; + + client = pevent->comp_data->client; + + clone_comp = icalcomponent_new_clone (pevent->comp_data->icalcomp); + if (!e_cal_component_set_icalcomponent (newcomp, clone_comp)) + g_warning ("couldn't update calendar component with modified data from backend\n"); + + box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + + str = e_calendar_view_get_icalcomponent_summary (pevent->comp_data->client, pevent->comp_data->icalcomp, &free_text); + + if (!(str && *str)) { + g_object_unref (newcomp); + gtk_widget_destroy (box); + + return FALSE; + } + + tmp = g_markup_printf_escaped ("<b>%s</b>", str); + label = gtk_label_new (NULL); + gtk_label_set_line_wrap ((GtkLabel *) label, TRUE); + gtk_label_set_markup ((GtkLabel *) label, tmp); + + if (free_text) { + g_free ((gchar *) str); + str = NULL; + } + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_pack_start ((GtkBox *) hbox, label, FALSE, FALSE, 0); + ebox = gtk_event_box_new (); + gtk_container_add ((GtkContainer *) ebox, hbox); + gtk_widget_modify_bg (ebox, GTK_STATE_NORMAL, &(style->bg[GTK_STATE_SELECTED])); + gtk_widget_modify_fg (label, GTK_STATE_NORMAL, &(style->text[GTK_STATE_SELECTED])); + + gtk_box_pack_start ((GtkBox *) box, ebox, FALSE, FALSE, 0); + g_free (tmp); + + e_cal_component_get_organizer (newcomp, &organiser); + if (organiser.cn) { + gchar *ptr; + ptr = strchr (organiser.value, ':'); + + if (ptr) { + ptr++; + /* To Translators: It will display "Organiser: NameOfTheUser <email@ofuser.com>" */ + tmp = g_strdup_printf (_("Organizer: %s <%s>"), organiser.cn, ptr); + } + else + /* With SunOne accouts, there may be no ':' in organiser.value*/ + tmp = g_strdup_printf (_("Organizer: %s"), organiser.cn); + + label = gtk_label_new (tmp); + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_pack_start ((GtkBox *) hbox, label, FALSE, FALSE, 0); + ebox = gtk_event_box_new (); + gtk_container_add ((GtkContainer *) ebox, hbox); + gtk_box_pack_start ((GtkBox *) box, ebox, FALSE, FALSE, 0); + + g_free (tmp); + } + + e_cal_component_get_location (newcomp, &str); + + if (str) { + /* To Translators: It will display "Location: PlaceOfTheMeeting" */ + tmp = g_markup_printf_escaped (_("Location: %s"), str); + label = gtk_label_new (NULL); + gtk_label_set_markup ((GtkLabel *) label, tmp); + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_pack_start ((GtkBox *) hbox, label, FALSE, FALSE, 0); + ebox = gtk_event_box_new (); + gtk_container_add ((GtkContainer *) ebox, hbox); + gtk_box_pack_start ((GtkBox *) box, ebox, FALSE, FALSE, 0); + g_free (tmp); + } + e_cal_component_get_dtstart (newcomp, &dtstart); + e_cal_component_get_dtend (newcomp, &dtend); + + if (dtstart.tzid) { + zone = icalcomponent_get_timezone (e_cal_component_get_icalcomponent (newcomp), dtstart.tzid); + if (!zone) + e_cal_client_get_timezone_sync (client, dtstart.tzid, &zone, NULL, NULL); + + if (!zone) + zone = default_zone; + + } else { + zone = NULL; + } + t_start = icaltime_as_timet_with_zone (*dtstart.value, zone); + t_end = icaltime_as_timet_with_zone (*dtend.value, zone); + + tmp1 = get_label (dtstart.value, zone, default_zone); + tmp = calculate_time (t_start, t_end); + + /* To Translators: It will display "Time: ActualStartDateAndTime (DurationOfTheMeeting)"*/ + tmp2 = g_strdup_printf (_("Time: %s %s"), tmp1, tmp); + if (zone && !cal_comp_util_compare_event_timezones (newcomp, client, default_zone)) { + g_free (tmp); + g_free (tmp1); + + tmp1 = get_label (dtstart.value, zone, zone); + tmp = g_strconcat (tmp2, "\n\t[ ", tmp1, " ", icaltimezone_get_display_name (zone), " ]", NULL); + } else { + g_free (tmp); + tmp = tmp2; + tmp2 = NULL; + } + + e_cal_component_free_datetime (&dtstart); + e_cal_component_free_datetime (&dtend); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_pack_start ((GtkBox *) hbox, gtk_label_new_with_mnemonic (tmp), FALSE, FALSE, 0); + ebox = gtk_event_box_new (); + gtk_container_add ((GtkContainer *) ebox, hbox); + gtk_box_pack_start ((GtkBox *) box, ebox, FALSE, FALSE, 0); + + g_free (tmp); + g_free (tmp2); + g_free (tmp1); + + tmp = e_cal_model_get_attendees_status_info ( + model, newcomp, pevent->comp_data->client); + if (tmp) { + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_pack_start ((GtkBox *) hbox, gtk_label_new (tmp), FALSE, FALSE, 0); + ebox = gtk_event_box_new (); + gtk_container_add ((GtkContainer *) ebox, hbox); + gtk_box_pack_start ((GtkBox *) box, ebox, FALSE, FALSE, 0); + + g_free (tmp); + } + + pevent->tooltip = gtk_window_new (GTK_WINDOW_POPUP); + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type ((GtkFrame *) frame, GTK_SHADOW_IN); + + gtk_window_set_type_hint (GTK_WINDOW (pevent->tooltip), GDK_WINDOW_TYPE_HINT_TOOLTIP); + gtk_window_move ((GtkWindow *) pevent->tooltip, pevent->x +16, pevent->y + 16); + gtk_container_add ((GtkContainer *) frame, box); + gtk_container_add ((GtkContainer *) pevent->tooltip, frame); + + gtk_widget_show_all (pevent->tooltip); + + e_calendar_view_move_tip (pevent->tooltip, pevent->x +16, pevent->y + 16); + + /* Grab all keyboard devices. A key press from + * any of them will dismiss the tooltip window. */ + + window = gtk_widget_get_window (pevent->tooltip); + display = gdk_window_get_display (window); + device_manager = gdk_display_get_device_manager (display); + + grabbed_keyboards = &data->cal_view->priv->grabbed_keyboards; + g_warn_if_fail (g_queue_is_empty (grabbed_keyboards)); + + list = gdk_device_manager_list_devices ( + device_manager, GDK_DEVICE_TYPE_MASTER); + + for (link = list; link != NULL; link = g_list_next (link)) { + GdkDevice *device = GDK_DEVICE (link->data); + GdkGrabStatus grab_status; + + if (gdk_device_get_source (device) != GDK_SOURCE_KEYBOARD) + continue; + + grab_status = gdk_device_grab ( + device, + window, + GDK_OWNERSHIP_NONE, + FALSE, + GDK_KEY_PRESS_MASK | + GDK_KEY_RELEASE_MASK, + NULL, + GDK_CURRENT_TIME); + + if (grab_status == GDK_GRAB_SUCCESS) + g_queue_push_tail ( + grabbed_keyboards, + g_object_ref (device)); + } + + g_list_free (list); + + g_signal_connect ( + pevent->tooltip, "key-press-event", + G_CALLBACK (tooltip_grab), data->cal_view); + pevent->timeout = -1; + + g_object_set_data (G_OBJECT (data->cal_view), "tooltip-window", pevent->tooltip); + g_object_unref (newcomp); + + return FALSE; +} + +static gboolean +icalcomp_contains_category (icalcomponent *icalcomp, + const gchar *category) +{ + icalproperty *property; + + g_return_val_if_fail (icalcomp != NULL && category != NULL, FALSE); + + for (property = icalcomponent_get_first_property (icalcomp, ICAL_CATEGORIES_PROPERTY); + property != NULL; + property = icalcomponent_get_next_property (icalcomp, ICAL_CATEGORIES_PROPERTY)) { + gchar *value = icalproperty_get_value_as_string_r (property); + + if (value && strcmp (category, value) == 0) { + g_free (value); + return TRUE; + } + g_free (value); + } + + return FALSE; +} + +/* e_calendar_view_get_icalcomponent_summary returns summary of calcomp, + * and for type of birthday or anniversary it append number of years since + * beginning. In this case, the free_text is set to TRUE and caller need + * to g_free returned string, otherwise free_text is set to FALSE and + * returned value is owned by calcomp. + */ + +const gchar * +e_calendar_view_get_icalcomponent_summary (ECalClient *client, + icalcomponent *icalcomp, + gboolean *free_text) +{ + const gchar *summary; + + g_return_val_if_fail (icalcomp != NULL && free_text != NULL, NULL); + + *free_text = FALSE; + summary = icalcomponent_get_summary (icalcomp); + + if (icalcomp_contains_category (icalcomp, _("Birthday")) || + icalcomp_contains_category (icalcomp, _("Anniversary"))) { + icalproperty *xprop; + + for (xprop = icalcomponent_get_first_property (icalcomp, ICAL_X_PROPERTY); + xprop; + xprop = icalcomponent_get_next_property (icalcomp, ICAL_X_PROPERTY)) { + const gchar *xname = icalproperty_get_x_name (xprop); + + if (xname && g_ascii_strcasecmp (xname, "X-EVOLUTION-SINCE-YEAR") == 0) { + struct icaltimetype dtnow; + gint since_year; + gchar *str; + + str = icalproperty_get_value_as_string_r (xprop); + since_year = str ? atoi (str) : 0; + g_free (str); + + dtnow = icalcomponent_get_dtstart (icalcomp); + + if (since_year > 0 && dtnow.year - since_year > 0) { + summary = g_strdup_printf ("%s (%d)", summary ? summary : "", dtnow.year - since_year); + *free_text = summary != NULL; + } + + break; + } + } + } + + return summary; +} + +void +e_calendar_view_emit_user_created (ECalendarView *cal_view, + ECalClient *where_was_created) +{ + g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view)); + + g_signal_emit (cal_view, signals[USER_CREATED], 0, where_was_created); +} + +void +draw_curved_rectangle (cairo_t *cr, + gdouble x0, + gdouble y0, + gdouble rect_width, + gdouble rect_height, + gdouble radius) +{ + gdouble x1, y1; + + x1 = x0 + rect_width; + y1 = y0 + rect_height; + + if (!rect_width || !rect_height) + return; + if (rect_width / 2 < radius) { + if (rect_height / 2 < radius) { + cairo_move_to (cr, x0, (y0 + y1) / 2); + cairo_curve_to (cr, x0 ,y0, x0, y0, (x0 + x1) / 2, y0); + cairo_curve_to (cr, x1, y0, x1, y0, x1, (y0 + y1) / 2); + cairo_curve_to (cr, x1, y1, x1, y1, (x1 + x0) / 2, y1); + cairo_curve_to (cr, x0, y1, x0, y1, x0, (y0 + y1) / 2); + } else { + cairo_move_to (cr, x0, y0 + radius); + cairo_curve_to (cr, x0 ,y0, x0, y0, (x0 + x1) / 2, y0); + cairo_curve_to (cr, x1, y0, x1, y0, x1, y0 + radius); + cairo_line_to (cr, x1 , y1 - radius); + cairo_curve_to (cr, x1, y1, x1, y1, (x1 + x0) / 2, y1); + cairo_curve_to (cr, x0, y1, x0, y1, x0, y1- radius); + } + } else { + if (rect_height / 2 < radius) { + cairo_move_to (cr, x0, (y0 + y1) / 2); + cairo_curve_to (cr, x0 , y0, x0 , y0, x0 + radius, y0); + cairo_line_to (cr, x1 - radius, y0); + cairo_curve_to (cr, x1, y0, x1, y0, x1, (y0 + y1) / 2); + cairo_curve_to (cr, x1, y1, x1, y1, x1 - radius, y1); + cairo_line_to (cr, x0 + radius, y1); + cairo_curve_to (cr, x0, y1, x0, y1, x0, (y0 + y1) / 2); + } else { + cairo_move_to (cr, x0, y0 + radius); + cairo_curve_to (cr, x0 , y0, x0 , y0, x0 + radius, y0); + cairo_line_to (cr, x1 - radius, y0); + cairo_curve_to (cr, x1, y0, x1, y0, x1, y0 + radius); + cairo_line_to (cr, x1 , y1 - radius); + cairo_curve_to (cr, x1, y1, x1, y1, x1 - radius, y1); + cairo_line_to (cr, x0 + radius, y1); + cairo_curve_to (cr, x0, y1, x0, y1, x0, y1- radius); + } + } + cairo_close_path (cr); +} + +/* returns either light or dark yellow, based on the base_background, + * which is the default background color */ +GdkColor +get_today_background (const GdkColor base_background) +{ + GdkColor res = base_background; + + if (res.red > 0x7FFF) { + /* light yellow for a light theme */ + res.red = 0xFFFF; + res.green = 0xFFFF; + res.blue = 0xC0C0; + } else { + /* dark yellow for a dark theme */ + res.red = 0x3F3F; + res.green = 0x3F3F; + res.blue = 0x0000; + } + + return res; +} + +gboolean +is_comp_data_valid_func (ECalendarViewEvent *event, + const gchar *location) +{ + g_return_val_if_fail (location != NULL, FALSE); + + if (!event) { + g_warning ("%s: event is NULL", location); + return FALSE; + } + + if (!event->comp_data) { + g_warning ("%s: event's (%p) comp_data is NULL", location, event); + return FALSE; + } + + return TRUE; +} + +gboolean +is_array_index_in_bounds_func (GArray *array, + gint index, + const gchar *location) +{ + g_return_val_if_fail (location != NULL, FALSE); + + if (!array) { + g_warning ("%s: array is NULL", location); + return FALSE; + } + + if (index < 0 || index >= array->len) { + g_warning ("%s: index %d is out of bounds [0,%d) at array %p", location, index, array->len, array); + return FALSE; + } + + return TRUE; +} + +gboolean +e_calendar_view_is_editing (ECalendarView *cal_view) +{ + static gboolean in = FALSE; + gboolean is_editing = FALSE; + + g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), FALSE); + + /* this should be called from the main thread only, + * and each descendant overrides the property, + * thus might cause no call recursion */ + if (in) { + g_warn_if_reached (); + return FALSE; + } + + in = TRUE; + + g_object_get (G_OBJECT (cal_view), "is-editing", &is_editing, NULL); + + in = FALSE; + + return is_editing; +} |