aboutsummaryrefslogtreecommitdiffstats
path: root/calendar/gui/e-calendar-view.c
diff options
context:
space:
mode:
Diffstat (limited to 'calendar/gui/e-calendar-view.c')
-rw-r--r--calendar/gui/e-calendar-view.c2422
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;
+}