diff --git a/src/calendar/gui/e-comp-editor.c b/src/calendar/gui/e-comp-editor.c index 4c01d1d..0f00f58 100644 --- a/src/calendar/gui/e-comp-editor.c +++ b/src/calendar/gui/e-comp-editor.c @@ -2610,6 +2610,7 @@ e_comp_editor_fill_component (ECompEditor *comp_editor, icalcomponent *component) { ECompEditorClass *comp_editor_class; + GtkWidget *focused_widget; gboolean is_valid; g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), FALSE); @@ -2619,8 +2620,30 @@ e_comp_editor_fill_component (ECompEditor *comp_editor, g_return_val_if_fail (comp_editor_class != NULL, FALSE); g_return_val_if_fail (comp_editor_class->fill_component != NULL, FALSE); + focused_widget = gtk_window_get_focus (GTK_WINDOW (comp_editor)); + if (focused_widget) { + GtkWidget *parent, *ce_widget = GTK_WIDGET (comp_editor); + + /* When a cell-renderer is focused and editing the cell content, + then unfocus it may mean to free the currently focused widget, + thus get the GtkTreeView in such cases. */ + parent = focused_widget; + while (parent = gtk_widget_get_parent (parent), parent && parent != ce_widget) { + if (GTK_IS_TREE_VIEW (parent)) { + focused_widget = parent; + break; + } + } + + /* Save any pending changes */ + gtk_window_set_focus (GTK_WINDOW (comp_editor), NULL); + } + is_valid = comp_editor_class->fill_component (comp_editor, component); + if (focused_widget) + gtk_window_set_focus (GTK_WINDOW (comp_editor), focused_widget); + if (is_valid && comp_editor->priv->validation_alert) { e_alert_response (comp_editor->priv->validation_alert, GTK_RESPONSE_CLOSE); g_clear_object (&comp_editor->priv->validation_alert); diff --git a/src/calendar/gui/e-comp-editor.c.crash-empty-attendee b/src/calendar/gui/e-comp-editor.c.crash-empty-attendee new file mode 100644 index 0000000..4c01d1d --- /dev/null +++ b/src/calendar/gui/e-comp-editor.c.crash-empty-attendee @@ -0,0 +1,3428 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. (www.redhat.com) + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation. + * + * 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 General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "evolution-config.h" + +#include +#include + +#include +#include +#include + +#include "calendar-config.h" +#include "comp-util.h" +#include "e-cal-dialogs.h" +#include "itip-utils.h" +#include "print.h" + +#include "e-comp-editor-page-general.h" +#include "e-comp-editor-page-attachments.h" +#include "e-comp-editor-event.h" +#include "e-comp-editor-memo.h" +#include "e-comp-editor-task.h" + +#include "e-comp-editor.h" + +struct _ECompEditorPrivate { + EAlertBar *alert_bar; /* not referenced */ + EActivityBar *activity_bar; /* not referenced */ + GtkNotebook *content; /* not referenced */ + + EAlert *validation_alert; + + EShell *shell; + GSettings *calendar_settings; + ESource *origin_source; + icalcomponent *component; + guint32 flags; + + EFocusTracker *focus_tracker; + GtkUIManager *ui_manager; + + GSList *pages; /* ECompEditorPage * */ + gulong show_attendees_handler_id; + + ECompEditorPageGeneral *page_general; /* special page, can be added only once; not referenced */ + + EActivity *target_client_opening; + + ECalClient *source_client; + ECalClient *target_client; + gchar *cal_email_address; + gchar *alarm_email_address; + gboolean changed; + guint updating; + gchar *title_suffix; + + ECompEditorPropertyPart *dtstart_part; + ECompEditorPropertyPart *dtend_part; + + GtkWidget *restore_focus; +}; + +enum { + PROP_0, + PROP_ALARM_EMAIL_ADDRESS, + PROP_CAL_EMAIL_ADDRESS, + PROP_CHANGED, + PROP_COMPONENT, + PROP_FLAGS, + PROP_ORIGIN_SOURCE, + PROP_SHELL, + PROP_SOURCE_CLIENT, + PROP_TARGET_CLIENT, + PROP_TITLE_SUFFIX +}; + +enum { + TIMES_CHANGED, + OBJECT_CREATED, + EDITOR_CLOSED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL]; + +static GSList *opened_editors = NULL; + +static void e_comp_editor_alert_sink_iface_init (EAlertSinkInterface *iface); + +G_DEFINE_ABSTRACT_TYPE_WITH_CODE (ECompEditor, e_comp_editor, GTK_TYPE_WINDOW, + G_IMPLEMENT_INTERFACE (E_TYPE_ALERT_SINK, e_comp_editor_alert_sink_iface_init) + G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE, NULL)) + +static void +ece_restore_focus (ECompEditor *comp_editor) +{ + g_return_if_fail (E_IS_COMP_EDITOR (comp_editor)); + + if (comp_editor->priv->restore_focus) { + gtk_widget_grab_focus (comp_editor->priv->restore_focus); + + if (GTK_IS_ENTRY (comp_editor->priv->restore_focus)) + gtk_editable_set_position (GTK_EDITABLE (comp_editor->priv->restore_focus), 0); + + comp_editor->priv->restore_focus = NULL; + } +} + +static void +e_comp_editor_enable (ECompEditor *comp_editor, + gboolean enable) +{ + GtkActionGroup *group; + GtkWidget *current_focus; + + g_return_if_fail (E_IS_COMP_EDITOR (comp_editor)); + + current_focus = gtk_window_get_focus (GTK_WINDOW (comp_editor)); + + gtk_widget_set_sensitive (GTK_WIDGET (comp_editor->priv->content), enable); + + group = e_comp_editor_get_action_group (comp_editor, "individual"); + gtk_action_group_set_sensitive (group, enable); + + group = e_comp_editor_get_action_group (comp_editor, "core"); + gtk_action_group_set_sensitive (group, enable); + + group = e_comp_editor_get_action_group (comp_editor, "editable"); + gtk_action_group_set_sensitive (group, enable); + + if (enable) { + e_comp_editor_sensitize_widgets (comp_editor); + ece_restore_focus (comp_editor); + } else { + comp_editor->priv->restore_focus = current_focus; + } +} + +static void +ece_set_attendees_for_delegation (ECalComponent *comp, + const gchar *address) +{ + icalproperty *prop; + icalparameter *param; + icalcomponent *icalcomp; + gboolean again; + + icalcomp = e_cal_component_get_icalcomponent (comp); + + for (prop = icalcomponent_get_first_property (icalcomp, ICAL_ATTENDEE_PROPERTY); + prop; + prop = again ? icalcomponent_get_first_property (icalcomp, ICAL_ATTENDEE_PROPERTY) : + icalcomponent_get_next_property (icalcomp, ICAL_ATTENDEE_PROPERTY)) { + const gchar *attendee = icalproperty_get_attendee (prop); + const gchar *delfrom = NULL; + + again = FALSE; + param = icalproperty_get_first_parameter (prop, ICAL_DELEGATEDFROM_PARAMETER); + if (param) + delfrom = icalparameter_get_delegatedfrom (param); + if (!(g_str_equal (itip_strip_mailto (attendee), address) || + ((delfrom && *delfrom) && g_str_equal (itip_strip_mailto (delfrom), address)))) { + icalcomponent_remove_property (icalcomp, prop); + icalproperty_free (prop); + again = TRUE; + } + + } +} + +/* Utility function to get the mime-attachment list from the attachment + * bar for sending the comp via itip. The list and its contents must + * be freed by the caller. + */ +static GSList * +ece_get_mime_attach_list (ECompEditor *comp_editor) +{ + ECompEditorPage *page_attachments; + EAttachmentStore *store; + GtkTreeModel *model; + GtkTreeIter iter; + struct CalMimeAttach *cal_mime_attach; + GSList *attach_list = NULL; + gboolean valid; + + g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), NULL); + + page_attachments = e_comp_editor_get_page (comp_editor, E_TYPE_COMP_EDITOR_PAGE_ATTACHMENTS); + if (!page_attachments) + return NULL; + + store = e_comp_editor_page_attachments_get_store (E_COMP_EDITOR_PAGE_ATTACHMENTS (page_attachments)); + if (!store) + return NULL; + + model = GTK_TREE_MODEL (store); + valid = gtk_tree_model_get_iter_first (model, &iter); + + while (valid) { + EAttachment *attachment; + CamelDataWrapper *wrapper; + CamelMimePart *mime_part; + CamelStream *stream; + GByteArray *byte_array; + guchar *buffer = NULL; + const gchar *description; + const gchar *disposition; + gint column_id; + + column_id = E_ATTACHMENT_STORE_COLUMN_ATTACHMENT; + gtk_tree_model_get (model, &iter, column_id, &attachment, -1); + mime_part = e_attachment_ref_mime_part (attachment); + g_object_unref (attachment); + + valid = gtk_tree_model_iter_next (model, &iter); + + if (mime_part == NULL) + continue; + + cal_mime_attach = g_malloc0 (sizeof (struct CalMimeAttach)); + wrapper = camel_medium_get_content (CAMEL_MEDIUM (mime_part)); + + byte_array = g_byte_array_new (); + stream = camel_stream_mem_new_with_byte_array (byte_array); + + camel_data_wrapper_decode_to_stream_sync ( + wrapper, stream, NULL, NULL); + buffer = g_memdup (byte_array->data, byte_array->len); + + camel_mime_part_set_content_id (mime_part, NULL); + + cal_mime_attach->encoded_data = (gchar *) buffer; + cal_mime_attach->length = byte_array->len; + cal_mime_attach->filename = + g_strdup (camel_mime_part_get_filename (mime_part)); + description = camel_mime_part_get_description (mime_part); + if (description == NULL || *description == '\0') + description = _("attachment"); + cal_mime_attach->description = g_strdup (description); + cal_mime_attach->content_type = camel_data_wrapper_get_mime_type (wrapper); + cal_mime_attach->content_id = g_strdup ( + camel_mime_part_get_content_id (mime_part)); + + disposition = camel_mime_part_get_disposition (mime_part); + cal_mime_attach->disposition = + (disposition != NULL) && + (g_ascii_strcasecmp (disposition, "inline") == 0); + + attach_list = g_slist_append (attach_list, cal_mime_attach); + + g_object_unref (mime_part); + g_object_unref (stream); + + } + + return attach_list; +} + +static void +e_comp_editor_set_component (ECompEditor *comp_editor, + const icalcomponent *component) +{ + g_return_if_fail (E_IS_COMP_EDITOR (comp_editor)); + g_return_if_fail (component != NULL); + + if (comp_editor->priv->component) + icalcomponent_free (comp_editor->priv->component); + comp_editor->priv->component = icalcomponent_new_clone ((icalcomponent *) component); + + g_warn_if_fail (comp_editor->priv->component != NULL); +} + +typedef struct _SaveData { + ECompEditor *comp_editor; + ECalClient *source_client; + ECalClient *target_client; + icalcomponent *component; + gboolean with_send; + gboolean close_after_save; + ECalObjModType recur_mod; + gboolean success; + GError *error; + gchar *alert_ident; + gchar *alert_arg_0; + + gboolean object_created; + ECalComponentItipMethod first_send; + ECalComponentItipMethod second_send; + ECalComponent *send_comp; + EActivity *send_activity; + gboolean strip_alarms; + gboolean only_new_attendees; + GSList *mime_attach_list; +} SaveData; + +static void +save_data_free (SaveData *sd) +{ + if (sd) { + e_comp_editor_enable (sd->comp_editor, TRUE); + + if (sd->success) { + if (sd->close_after_save) { + g_signal_emit (sd->comp_editor, signals[EDITOR_CLOSED], 0, TRUE, NULL); + gtk_widget_destroy (GTK_WIDGET (sd->comp_editor)); + } else { + e_comp_editor_set_component (sd->comp_editor, sd->component); + + e_comp_editor_fill_widgets (sd->comp_editor, sd->component); + + g_clear_object (&sd->comp_editor->priv->source_client); + sd->comp_editor->priv->source_client = g_object_ref (sd->target_client); + + sd->comp_editor->priv->flags = sd->comp_editor->priv->flags & (~E_COMP_EDITOR_FLAG_IS_NEW); + + e_comp_editor_sensitize_widgets (sd->comp_editor); + e_comp_editor_set_changed (sd->comp_editor, FALSE); + } + } else if (sd->alert_ident) { + e_alert_submit ( + E_ALERT_SINK (sd->comp_editor), sd->alert_ident, sd->alert_arg_0, + sd->error ? sd->error->message : _("Unknown error"), NULL); + } + + if (sd->send_activity && e_activity_get_state (sd->send_activity) != E_ACTIVITY_CANCELLED) + e_activity_set_state (sd->send_activity, E_ACTIVITY_COMPLETED); + + g_clear_object (&sd->comp_editor); + g_clear_object (&sd->source_client); + g_clear_object (&sd->target_client); + g_clear_object (&sd->send_comp); + g_clear_object (&sd->send_activity); + g_clear_error (&sd->error); + if (sd->component) + icalcomponent_free (sd->component); + g_slist_free_full (sd->mime_attach_list, itip_cal_mime_attach_free); + g_free (sd->alert_ident); + g_free (sd->alert_arg_0); + g_free (sd); + } +} + +static gboolean +ece_send_process_method (SaveData *sd, + ECalComponentItipMethod send_method, + ECalComponent *send_comp, + ESourceRegistry *registry, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSList *mime_attach_list = NULL; + + g_return_val_if_fail (sd != NULL, FALSE); + g_return_val_if_fail (E_IS_CAL_COMPONENT (send_comp), FALSE); + g_return_val_if_fail (send_method != E_CAL_COMPONENT_METHOD_NONE, FALSE); + + if (e_cal_component_has_attachments (send_comp) && + e_client_check_capability (E_CLIENT (sd->target_client), CAL_STATIC_CAPABILITY_CREATE_MESSAGES)) { + /* Clone the component with attachments set to CID:... */ + GSList *attach_list = NULL; + GSList *attach; + + /* mime_attach_list is freed by itip_send_component() */ + mime_attach_list = sd->mime_attach_list; + sd->mime_attach_list = NULL; + + for (attach = mime_attach_list; attach; attach = attach->next) { + struct CalMimeAttach *cma = (struct CalMimeAttach *) attach->data; + + attach_list = g_slist_append ( + attach_list, g_strconcat ( + "cid:", cma->content_id, NULL)); + } + + if (attach_list) { + e_cal_component_set_attachment_list (send_comp, attach_list); + + g_slist_free_full (attach_list, g_free); + } + } + + itip_send_component ( + registry, send_method, send_comp, sd->target_client, + NULL, mime_attach_list, NULL, sd->strip_alarms, + sd->only_new_attendees, FALSE, + cancellable, callback, user_data); + + return TRUE; +} + +static void +ecep_second_send_processed_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + SaveData *sd = user_data; + + g_return_if_fail (sd != NULL); + + sd->success = itip_send_component_finish (result, &sd->error); + + save_data_free (sd); +} + +static void +ecep_first_send_processed_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + SaveData *sd = user_data; + + g_return_if_fail (sd != NULL); + + sd->success = itip_send_component_finish (result, &sd->error); + if (!sd->success || sd->second_send == E_CAL_COMPONENT_METHOD_NONE) { + save_data_free (sd); + } else { + sd->success = ece_send_process_method (sd, sd->second_send, sd->send_comp, + e_shell_get_registry (sd->comp_editor->priv->shell), + e_activity_get_cancellable (sd->send_activity), + ecep_second_send_processed_cb, sd); + if (!sd->success) + save_data_free (sd); + } +} + +static void +ece_prepare_send_component_done (gpointer ptr) +{ + SaveData *sd = ptr; + + g_return_if_fail (sd != NULL); + g_return_if_fail (E_IS_COMP_EDITOR (sd->comp_editor)); + g_return_if_fail (sd->send_activity != NULL); + + sd->success = ece_send_process_method (sd, sd->first_send, sd->send_comp, + e_shell_get_registry (sd->comp_editor->priv->shell), + e_activity_get_cancellable (sd->send_activity), + ecep_first_send_processed_cb, sd); + if (!sd->success) + save_data_free (sd); +} + +static void +ece_prepare_send_component_thread (EAlertSinkThreadJobData *job_data, + gpointer user_data, + GCancellable *cancellable, + GError **error) +{ + SaveData *sd = user_data; + const gchar *alert_ident; + ECalComponent *send_comp = NULL; + guint32 flags; + ESourceRegistry *registry; + + g_return_if_fail (sd != NULL); + g_return_if_fail (E_IS_CAL_CLIENT (sd->target_client)); + g_return_if_fail (sd->component != NULL); + + while (!sd->send_activity) { + /* Give the main thread a chance to set this object + and give it a 50 milliseconds delay too */ + g_thread_yield (); + g_usleep (50000); + } + + switch (icalcomponent_isa (sd->component)) { + case ICAL_VEVENT_COMPONENT: + alert_ident = "calendar:failed-send-event"; + break; + case ICAL_VJOURNAL_COMPONENT: + alert_ident = "calendar:failed-send-memo"; + break; + case ICAL_VTODO_COMPONENT: + alert_ident = "calendar:failed-send-task"; + break; + default: + g_warning ("%s: Cannot send component of kind %d", G_STRFUNC, icalcomponent_isa (sd->component)); + sd->success = FALSE; + sd->alert_ident = g_strdup ("calendar:failed-send-event"); + return; + } + + g_free (sd->alert_ident); + sd->alert_ident = g_strdup (alert_ident); + + e_alert_sink_thread_job_set_alert_ident (job_data, alert_ident); + + flags = e_comp_editor_get_flags (sd->comp_editor); + registry = e_shell_get_registry (sd->comp_editor->priv->shell); + + if (sd->recur_mod == E_CAL_OBJ_MOD_ALL && e_cal_component_is_instance (sd->send_comp)) { + /* Ensure we send the master object, not the instance only */ + icalcomponent *icalcomp = NULL; + const gchar *uid = NULL; + + e_cal_component_get_uid (sd->send_comp, &uid); + if (e_cal_client_get_object_sync (sd->target_client, uid, NULL, &icalcomp, cancellable, NULL) && + icalcomp != NULL) { + send_comp = e_cal_component_new_from_icalcomponent (icalcomp); + } + } + + if (!send_comp) + send_comp = e_cal_component_clone (sd->send_comp); + + cal_comp_util_copy_new_attendees (send_comp, sd->send_comp); + + /* The user updates the delegated status to the Organizer, + * so remove all other attendees. */ + if ((flags & E_COMP_EDITOR_FLAG_DELEGATE) != 0) { + gchar *address; + + address = itip_get_comp_attendee (registry, send_comp, sd->target_client); + + if (address) { + ece_set_attendees_for_delegation (send_comp, address); + g_free (address); + } + } + + g_clear_object (&sd->send_comp); + sd->send_comp = send_comp; +} + +static void +ece_save_component_done (gpointer ptr) +{ + SaveData *sd = ptr; + + g_return_if_fail (sd != NULL); + g_return_if_fail (E_IS_COMP_EDITOR (sd->comp_editor)); + + if (sd->success) { + ECalComponent *comp; + gboolean delegated, is_new_meeting; + gboolean only_new_attendees = FALSE; + gboolean strip_alarms = TRUE; + guint32 flags; + + if (sd->object_created) + g_signal_emit (sd->comp_editor, signals[OBJECT_CREATED], 0, NULL); + + comp = e_cal_component_new_from_icalcomponent (icalcomponent_new_clone (sd->component)); + if (sd->comp_editor->priv->page_general) { + GSList *added_attendees; + + added_attendees = e_comp_editor_page_general_get_added_attendees (sd->comp_editor->priv->page_general); + cal_comp_util_set_added_attendees_mails (comp, added_attendees); + } + + flags = e_comp_editor_get_flags (sd->comp_editor); + is_new_meeting = (flags & E_COMP_EDITOR_FLAG_WITH_ATTENDEES) == 0 || + (flags & E_COMP_EDITOR_FLAG_IS_NEW) != 0; + delegated = (flags & E_COMP_EDITOR_FLAG_DELEGATE) != 0 && + e_cal_client_check_save_schedules (sd->target_client); + + if (delegated || (sd->with_send && e_cal_dialogs_send_component ( + GTK_WINDOW (sd->comp_editor), sd->target_client, comp, + is_new_meeting, &strip_alarms, &only_new_attendees))) { + ESourceRegistry *registry; + EActivity *activity; + + registry = e_shell_get_registry (sd->comp_editor->priv->shell); + + if (delegated) + only_new_attendees = FALSE; + + if ((itip_organizer_is_user (registry, comp, sd->target_client) || + itip_sentby_is_user (registry, comp, sd->target_client))) { + if (e_cal_component_get_vtype (comp) == E_CAL_COMPONENT_JOURNAL) + sd->first_send = E_CAL_COMPONENT_METHOD_PUBLISH; + else + sd->first_send = E_CAL_COMPONENT_METHOD_REQUEST; + } else { + sd->first_send = E_CAL_COMPONENT_METHOD_REQUEST; + + if ((flags & E_COMP_EDITOR_FLAG_DELEGATE) != 0) + sd->second_send = E_CAL_COMPONENT_METHOD_REPLY; + } + + sd->mime_attach_list = ece_get_mime_attach_list (sd->comp_editor); + sd->strip_alarms = strip_alarms; + sd->only_new_attendees = only_new_attendees; + sd->send_comp = comp; + sd->success = FALSE; + sd->alert_ident = g_strdup ("calendar:failed-send-event"); + sd->alert_arg_0 = e_util_get_source_full_name (registry, e_client_get_source (E_CLIENT (sd->target_client))); + + activity = e_alert_sink_submit_thread_job (E_ALERT_SINK (sd->comp_editor), + _("Sending notifications to attendees..."), sd->alert_ident, sd->alert_arg_0, + ece_prepare_send_component_thread, sd, ece_prepare_send_component_done); + + if (activity) + e_activity_bar_set_activity (sd->comp_editor->priv->activity_bar, activity); + + /* The thread is waiting for this to be set first */ + sd->send_activity = activity; + + return; + } + + g_clear_object (&comp); + } + + save_data_free (sd); +} + +static gboolean +ece_save_component_attachments_sync (ECalClient *cal_client, + icalcomponent *component, + GCancellable *cancellable, + GError **error) +{ + icalproperty *prop; + const gchar *local_store; + gchar *target_filename_prefix, *filename_prefix, *tmp; + gboolean success = TRUE; + + g_return_val_if_fail (E_IS_CAL_CLIENT (cal_client), FALSE); + g_return_val_if_fail (component != NULL, FALSE); + + tmp = g_strdup (icalcomponent_get_uid (component)); + e_filename_make_safe (tmp); + filename_prefix = g_strconcat (tmp, "-", NULL); + g_free (tmp); + + local_store = e_cal_client_get_local_attachment_store (cal_client); + if (local_store && *local_store && + g_mkdir_with_parents (local_store, 0700) < 0) { + g_debug ("%s: Failed to create local store directory '%s'", G_STRFUNC, local_store); + } + + target_filename_prefix = g_build_filename (local_store, filename_prefix, NULL); + + g_free (filename_prefix); + + for (prop = icalcomponent_get_first_property (component, ICAL_ATTACH_PROPERTY); + prop && success; + prop = icalcomponent_get_next_property (component, ICAL_ATTACH_PROPERTY)) { + icalattach *attach; + gchar *uri = NULL; + + attach = icalproperty_get_attach (prop); + if (!attach) + continue; + + if (icalattach_get_is_url (attach)) { + const gchar *data; + gsize buf_size; + + data = icalattach_get_url (attach); + buf_size = strlen (data); + uri = g_malloc0 (buf_size + 1); + + icalvalue_decode_ical_string (data, uri, buf_size); + } + + if (uri) { + if (g_ascii_strncasecmp (uri, "file://", 7) == 0 && + !g_str_has_prefix (uri + 7, target_filename_prefix)) { + GFile *source, *destination; + gchar *decoded_filename; + gchar *target_filename; + + decoded_filename = g_uri_unescape_string (strrchr (uri, '/') + 1, NULL); + target_filename = g_strconcat (target_filename_prefix, decoded_filename, NULL); + g_free (decoded_filename); + + source = g_file_new_for_uri (uri); + destination = g_file_new_for_path (target_filename); + + if (source && destination) { + success = g_file_copy (source, destination, G_FILE_COPY_OVERWRITE, cancellable, NULL, NULL, error); + if (success) { + g_free (uri); + uri = g_file_get_uri (destination); + + if (uri) { + icalattach *new_attach; + gsize buf_size; + gchar *buf; + + buf_size = 2 * strlen (uri) + 1; + buf = g_malloc0 (buf_size); + + icalvalue_encode_ical_string (uri, buf, buf_size); + new_attach = icalattach_new_from_url (buf); + + icalproperty_set_attach (prop, new_attach); + + icalattach_unref (new_attach); + g_free (buf); + } + } + } + + g_clear_object (&source); + g_clear_object (&destination); + g_free (target_filename); + } + + g_free (uri); + } + + success = success & !g_cancellable_set_error_if_cancelled (cancellable, error); + } + + g_free (target_filename_prefix); + + return success; +} + +static void +ece_gather_tzids_cb (icalparameter *param, + gpointer user_data) +{ + GHashTable *tzids = user_data; + const gchar *tzid; + + g_return_if_fail (param != NULL); + g_return_if_fail (tzids != NULL); + + tzid = icalparameter_get_tzid (param); + if (tzid && *tzid && g_ascii_strcasecmp (tzid, "UTC") != 0) + g_hash_table_insert (tzids, g_strdup (tzid), NULL); +} + +static gboolean +ece_save_component_add_timezones_sync (SaveData *sd, + GCancellable *cancellable, + GError **error) +{ + GHashTable *tzids; + GHashTableIter iter; + gpointer key, value; + gboolean source_is_target; + + g_return_val_if_fail (sd != NULL, FALSE); + g_return_val_if_fail (sd->component != NULL, FALSE); + g_return_val_if_fail (sd->target_client != NULL, FALSE); + + sd->success = TRUE; + + tzids = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + source_is_target = !sd->source_client || + e_source_equal (e_client_get_source (E_CLIENT (sd->target_client)), + e_client_get_source (E_CLIENT (sd->source_client))); + + icalcomponent_foreach_tzid (sd->component, ece_gather_tzids_cb, tzids); + + g_hash_table_iter_init (&iter, tzids); + while (sd->success && g_hash_table_iter_next (&iter, &key, &value)) { + const gchar *tzid = key; + icaltimezone *zone = NULL; + GError *local_error = NULL; + + if (!e_cal_client_get_timezone_sync (source_is_target ? sd->target_client : sd->source_client, + tzid, &zone, cancellable, &local_error)) { + zone = icaltimezone_get_builtin_timezone_from_tzid (tzid); + if (!zone) + zone = icaltimezone_get_builtin_timezone (tzid); + if (!zone) { + g_propagate_error (error, local_error); + local_error = NULL; + sd->success = FALSE; + break; + } + } + + sd->success = e_cal_client_add_timezone_sync (sd->target_client, zone, cancellable, error); + + g_clear_error (&local_error); + } + + g_hash_table_destroy (tzids); + + return sd->success; +} + +static void +ece_save_component_thread (EAlertSinkThreadJobData *job_data, + gpointer user_data, + GCancellable *cancellable, + GError **error) +{ + SaveData *sd = user_data; + const gchar *create_alert_ident, *modify_alert_ident, *remove_alert_ident, *get_alert_ident; + gchar *orig_uid, *new_uid = NULL; + + g_return_if_fail (sd != NULL); + g_return_if_fail (E_IS_CAL_CLIENT (sd->target_client)); + g_return_if_fail (sd->component != NULL); + + switch (icalcomponent_isa (sd->component)) { + case ICAL_VEVENT_COMPONENT: + create_alert_ident = "calendar:failed-create-event"; + modify_alert_ident = "calendar:failed-modify-event"; + remove_alert_ident = "calendar:failed-remove-event"; + get_alert_ident = "calendar:failed-get-event"; + break; + case ICAL_VJOURNAL_COMPONENT: + create_alert_ident = "calendar:failed-create-memo"; + modify_alert_ident = "calendar:failed-modify-memo"; + remove_alert_ident = "calendar:failed-remove-memo"; + get_alert_ident = "calendar:failed-get-memo"; + break; + case ICAL_VTODO_COMPONENT: + create_alert_ident = "calendar:failed-create-task"; + modify_alert_ident = "calendar:failed-modify-task"; + remove_alert_ident = "calendar:failed-remove-task"; + get_alert_ident = "calendar:failed-get-task"; + break; + default: + g_warning ("%s: Cannot save component of kind %d", G_STRFUNC, icalcomponent_isa (sd->component)); + return; + } + + sd->success = ece_save_component_add_timezones_sync (sd, cancellable, error); + if (!sd->success) { + e_alert_sink_thread_job_set_alert_ident (job_data, "calendar:failed-add-timezone"); + return; + } + + sd->success = ece_save_component_attachments_sync (sd->target_client, sd->component, cancellable, error); + if (!sd->success) { + e_alert_sink_thread_job_set_alert_ident (job_data, "calendar:failed-save-attachments"); + return; + } + + orig_uid = g_strdup (icalcomponent_get_uid (sd->component)); + + if (cal_comp_is_icalcomp_on_server_sync (sd->component, sd->target_client, cancellable, error)) { + ECalComponent *comp; + gboolean has_recurrences; + + e_alert_sink_thread_job_set_alert_ident (job_data, modify_alert_ident); + + comp = e_cal_component_new_from_icalcomponent (icalcomponent_new_clone (sd->component)); + g_return_if_fail (comp != NULL); + + has_recurrences = e_cal_util_component_has_recurrences (sd->component); + + if (has_recurrences && sd->recur_mod == E_CAL_OBJ_MOD_ALL) + sd->success = comp_util_sanitize_recurrence_master_sync (comp, sd->target_client, cancellable, error); + else + sd->success = TRUE; + + if (sd->recur_mod == E_CAL_OBJ_MOD_THIS) { + e_cal_component_set_rdate_list (comp, NULL); + e_cal_component_set_rrule_list (comp, NULL); + e_cal_component_set_exdate_list (comp, NULL); + e_cal_component_set_exrule_list (comp, NULL); + } + + sd->success = sd->success && e_cal_client_modify_object_sync ( + sd->target_client, e_cal_component_get_icalcomponent (comp), sd->recur_mod, cancellable, error); + + g_clear_object (&comp); + } else { + e_alert_sink_thread_job_set_alert_ident (job_data, create_alert_ident); + + sd->success = e_cal_client_create_object_sync (sd->target_client, sd->component, &new_uid, cancellable, error); + + if (sd->success) + sd->object_created = TRUE; + } + + if (sd->success && sd->source_client && + !e_source_equal (e_client_get_source (E_CLIENT (sd->target_client)), + e_client_get_source (E_CLIENT (sd->source_client))) && + cal_comp_is_icalcomp_on_server_sync (sd->component, sd->source_client, cancellable, NULL)) { + ECalObjModType recur_mod = E_CAL_OBJ_MOD_THIS; + + /* Comp found a new home. Remove it from old one. */ + if (e_cal_util_component_is_instance (sd->component) || + e_cal_util_component_has_recurrences (sd->component)) + recur_mod = E_CAL_OBJ_MOD_ALL; + + sd->success = e_cal_client_remove_object_sync ( + sd->source_client, orig_uid, + NULL, recur_mod, cancellable, error); + + if (!sd->success) { + gchar *source_display_name; + + source_display_name = e_util_get_source_full_name (e_shell_get_registry (sd->comp_editor->priv->shell), + e_client_get_source (E_CLIENT (sd->source_client))); + + e_alert_sink_thread_job_set_alert_ident (job_data, remove_alert_ident); + e_alert_sink_thread_job_set_alert_arg_0 (job_data, source_display_name); + + g_free (source_display_name); + } + } + + if (new_uid) { + icalcomponent_set_uid (sd->component, new_uid); + g_free (new_uid); + } + + g_free (orig_uid); + + if (sd->success && !sd->close_after_save) { + icalcomponent *comp = NULL; + gchar *uid, *rid = NULL; + + uid = g_strdup (icalcomponent_get_uid (sd->component)); + if (icalcomponent_get_first_property (sd->component, ICAL_RECURRENCEID_PROPERTY)) { + struct icaltimetype ridtt; + + ridtt = icalcomponent_get_recurrenceid (sd->component); + if (icaltime_is_valid_time (ridtt) && !icaltime_is_null_time (ridtt)) { + rid = icaltime_as_ical_string_r (ridtt); + } + } + + sd->success = e_cal_client_get_object_sync (sd->target_client, uid, rid, &comp, cancellable, error); + if (sd->success && comp) { + icalcomponent_free (sd->component); + sd->component = comp; + } else { + e_alert_sink_thread_job_set_alert_ident (job_data, get_alert_ident); + } + + g_free (uid); + g_free (rid); + } +} + +static void +ece_save_component (ECompEditor *comp_editor, + icalcomponent *component, + gboolean with_send, + gboolean close_after_save) +{ + EActivity *activity; + const gchar *summary; + ECalObjModType recur_mod = E_CAL_OBJ_MOD_THIS; + SaveData *sd; + gchar *source_display_name; + + g_return_if_fail (E_IS_COMP_EDITOR (comp_editor)); + g_return_if_fail (component != NULL); + + summary = icalcomponent_get_summary (component); + if (!summary || !*summary) { + if (!e_cal_dialogs_send_component_prompt_subject (GTK_WINDOW (comp_editor), component)) { + return; + } + } + + if (e_cal_util_component_is_instance (component)) { + if (!e_cal_dialogs_recur_icalcomp (comp_editor->priv->target_client, + component, &recur_mod, GTK_WINDOW (comp_editor), FALSE)) { + return; + } + } else if (e_cal_util_component_has_recurrences (component)) { + recur_mod = E_CAL_OBJ_MOD_ALL; + } + + e_comp_editor_enable (comp_editor, FALSE); + + sd = g_new0 (SaveData, 1); + sd->comp_editor = g_object_ref (comp_editor); + sd->source_client = comp_editor->priv->source_client ? g_object_ref (comp_editor->priv->source_client) : NULL; + sd->target_client = g_object_ref (comp_editor->priv->target_client); + sd->component = icalcomponent_new_clone (component); + sd->with_send = with_send; + sd->close_after_save = close_after_save; + sd->recur_mod = recur_mod; + sd->first_send = E_CAL_COMPONENT_METHOD_NONE; + sd->second_send = E_CAL_COMPONENT_METHOD_NONE; + sd->success = FALSE; + + source_display_name = e_util_get_source_full_name (e_shell_get_registry (comp_editor->priv->shell), + e_client_get_source (E_CLIENT (sd->target_client))); + + activity = e_alert_sink_submit_thread_job (E_ALERT_SINK (comp_editor), + _("Saving changes..."), "calendar:failed-create-event", source_display_name, + ece_save_component_thread, sd, ece_save_component_done); + + if (activity) + e_activity_bar_set_activity (comp_editor->priv->activity_bar, activity); + + g_clear_object (&activity); + g_free (source_display_name); +} + +typedef struct _OpenTargetClientData { + ECompEditor *comp_editor; + ESource *source; + gchar *extension_name; + EClient *client; + gchar *cal_email_address; + gchar *alarm_email_address; + gboolean is_target_client_change; + EActivity *activity; +} OpenTargetClientData; + +static void +open_target_client_data_free (gpointer ptr) +{ + OpenTargetClientData *otc = ptr; + + if (otc) { + if (otc->comp_editor) { + if (otc->client) { + gboolean previous_changed = e_comp_editor_get_changed (otc->comp_editor); + + e_comp_editor_set_alarm_email_address (otc->comp_editor, otc->alarm_email_address); + e_comp_editor_set_cal_email_address (otc->comp_editor, otc->cal_email_address); + e_comp_editor_set_target_client (otc->comp_editor, E_CAL_CLIENT (otc->client)); + + if (otc->is_target_client_change) + e_comp_editor_set_changed (otc->comp_editor, TRUE); + else + e_comp_editor_set_changed (otc->comp_editor, previous_changed); + } + + if (otc->comp_editor->priv->activity_bar && otc->activity) { + if (otc->activity == e_activity_bar_get_activity (otc->comp_editor->priv->activity_bar)) + e_activity_bar_set_activity (otc->comp_editor->priv->activity_bar, NULL); + + if (otc->activity == otc->comp_editor->priv->target_client_opening) + g_clear_object (&otc->comp_editor->priv->target_client_opening); + } + + if (otc->source) { + EShell *shell; + ECredentialsPrompter *credentials_prompter; + + shell = e_comp_editor_get_shell (otc->comp_editor); + credentials_prompter = e_shell_get_credentials_prompter (shell); + + e_credentials_prompter_set_auto_prompt_disabled_for (credentials_prompter, otc->source, TRUE); + } + + e_comp_editor_sensitize_widgets (otc->comp_editor); + } + + g_clear_object (&otc->comp_editor); + g_clear_object (&otc->source); + g_clear_object (&otc->client); + g_clear_object (&otc->activity); + g_free (otc->extension_name); + g_free (otc->cal_email_address); + g_free (otc->alarm_email_address); + g_free (otc); + } +} + +static void +comp_editor_open_target_client_thread (EAlertSinkThreadJobData *job_data, + gpointer user_data, + GCancellable *cancellable, + GError **error) +{ + OpenTargetClientData *otc = user_data; + EClientCache *client_cache; + + g_return_if_fail (otc != NULL); + + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + return; + + g_return_if_fail (E_IS_COMP_EDITOR (otc->comp_editor)); + g_return_if_fail (E_IS_SOURCE (otc->source)); + g_return_if_fail (otc->extension_name != NULL); + + client_cache = e_shell_get_client_cache (e_comp_editor_get_shell (otc->comp_editor)); + + otc->client = e_client_cache_get_client_sync (client_cache, otc->source, otc->extension_name, + 30, cancellable, error); + + if (otc->client) { + /* Cache some properties which require remote calls */ + + if (!g_cancellable_is_cancelled (cancellable)) { + e_client_get_capabilities (otc->client); + } + + if (!g_cancellable_is_cancelled (cancellable)) { + e_client_get_backend_property_sync (otc->client, + CAL_BACKEND_PROPERTY_CAL_EMAIL_ADDRESS, + &otc->cal_email_address, + cancellable, error); + } + + if (!g_cancellable_is_cancelled (cancellable)) { + e_client_get_backend_property_sync (otc->client, + CAL_BACKEND_PROPERTY_ALARM_EMAIL_ADDRESS, + &otc->alarm_email_address, + cancellable, error); + } + + if (g_cancellable_is_cancelled (cancellable)) + g_clear_object (&otc->client); + } +} + +typedef struct _UpdateActivityBarData { + ECompEditor *comp_editor; + EActivity *activity; +} UpdateActivityBarData; + +static void +update_activity_bar_data_free (gpointer ptr) +{ + UpdateActivityBarData *uab = ptr; + + if (uab) { + g_clear_object (&uab->comp_editor); + g_clear_object (&uab->activity); + g_free (uab); + } +} + +static gboolean +update_activity_bar_cb (gpointer user_data) +{ + UpdateActivityBarData *uab = user_data; + + g_return_val_if_fail (uab != NULL, FALSE); + g_return_val_if_fail (E_IS_COMP_EDITOR (uab->comp_editor), FALSE); + g_return_val_if_fail (E_IS_ACTIVITY (uab->activity), FALSE); + + if (uab->comp_editor->priv->target_client_opening == uab->activity && + e_activity_get_state (uab->activity) != E_ACTIVITY_CANCELLED && + e_activity_get_state (uab->activity) != E_ACTIVITY_COMPLETED) { + e_activity_bar_set_activity (uab->comp_editor->priv->activity_bar, uab->activity); + } + + return FALSE; +} + +static void +e_comp_editor_open_target_client (ECompEditor *comp_editor) +{ + OpenTargetClientData *otc; + ESource *source; + EActivity *activity; + ECredentialsPrompter *credentials_prompter; + gchar *source_display_name, *description = NULL, *alert_ident = NULL, *alert_arg_0 = NULL; + gboolean is_target_client_change; + const gchar *extension_name; + + g_return_if_fail (E_IS_COMP_EDITOR (comp_editor)); + g_return_if_fail (comp_editor->priv->page_general != NULL); + + source = e_comp_editor_page_general_ref_selected_source (comp_editor->priv->page_general); + if (!source) + return; + + if (comp_editor->priv->target_client && + e_client_get_source (E_CLIENT (comp_editor->priv->target_client)) == source) { + g_clear_object (&source); + return; + } + + if (comp_editor->priv->target_client_opening) { + e_activity_cancel (comp_editor->priv->target_client_opening); + g_clear_object (&comp_editor->priv->target_client_opening); + } + + is_target_client_change = comp_editor->priv->target_client != NULL; + g_clear_object (&comp_editor->priv->target_client); + + extension_name = e_comp_editor_page_general_get_source_extension_name (comp_editor->priv->page_general); + source_display_name = e_util_get_source_full_name ( + e_shell_get_registry (e_comp_editor_get_shell (comp_editor)), + source); + + g_return_if_fail (e_util_get_open_source_job_info (extension_name, source_display_name, + &description, &alert_ident, &alert_arg_0)); + + credentials_prompter = e_shell_get_credentials_prompter (e_comp_editor_get_shell (comp_editor)); + e_credentials_prompter_set_auto_prompt_disabled_for (credentials_prompter, source, FALSE); + + otc = g_new0 (OpenTargetClientData, 1); + otc->extension_name = g_strdup (extension_name); + otc->comp_editor = g_object_ref (comp_editor); + otc->source = g_object_ref (source); + otc->is_target_client_change = is_target_client_change; + + activity = e_alert_sink_submit_thread_job ( + E_ALERT_SINK (comp_editor), description, alert_ident, alert_arg_0, + comp_editor_open_target_client_thread, otc, + open_target_client_data_free); + + otc->activity = g_object_ref (activity); + comp_editor->priv->target_client_opening = g_object_ref (activity); + + /* Close all alerts */ + while (e_alert_bar_close_alert (comp_editor->priv->alert_bar)) { + ; + } + + if (comp_editor->priv->activity_bar) { + UpdateActivityBarData *uab; + + uab = g_new0 (UpdateActivityBarData, 1); + uab->comp_editor = g_object_ref (comp_editor); + uab->activity = g_object_ref (activity); + + /* To avoid UI flickering when the source can be opened quickly */ + g_timeout_add_seconds_full (G_PRIORITY_LOW, 1, + update_activity_bar_cb, uab, update_activity_bar_data_free); + } + + g_free (description); + g_free (alert_ident); + g_free (alert_arg_0); + g_free (source_display_name); + g_clear_object (&source); + g_clear_object (&activity); +} + +static void +e_comp_editor_update_window_title (ECompEditor *comp_editor) +{ + ECompEditorClass *comp_editor_class; + gboolean with_attendees = FALSE; + const gchar *format, *title_suffix; + gchar *title; + + g_return_if_fail (E_IS_COMP_EDITOR (comp_editor)); + + if (comp_editor->priv->page_general) + with_attendees = e_comp_editor_page_general_get_show_attendees (comp_editor->priv->page_general); + + comp_editor_class = E_COMP_EDITOR_GET_CLASS (comp_editor); + if (with_attendees) + format = comp_editor_class->title_format_with_attendees; + else + format = comp_editor_class->title_format_without_attendees; + + title_suffix = e_comp_editor_get_title_suffix (comp_editor); + + title = g_strdup_printf (format, title_suffix && *title_suffix ? title_suffix : _("No Summary")); + + gtk_window_set_icon_name (GTK_WINDOW (comp_editor), comp_editor_class->icon_name); + gtk_window_set_title (GTK_WINDOW (comp_editor), title); + + g_free (title); +} + +static void +e_comp_editor_close (ECompEditor *comp_editor) +{ + g_return_if_fail (E_IS_COMP_EDITOR (comp_editor)); + + g_signal_emit (comp_editor, signals[EDITOR_CLOSED], 0, FALSE, NULL); + + gtk_widget_destroy (GTK_WIDGET (comp_editor)); +} + +static void +e_comp_editor_save_and_close (ECompEditor *comp_editor, + gboolean can_close) +{ + g_return_if_fail (E_IS_COMP_EDITOR (comp_editor)); + + if (comp_editor->priv->component) { + icalcomponent *component = icalcomponent_new_clone (comp_editor->priv->component); + if (component && e_comp_editor_fill_component (comp_editor, component)) { + ece_save_component (comp_editor, component, TRUE, can_close); + icalcomponent_free (component); + } + } +} + +static GtkResponseType +ece_save_component_dialog (ECompEditor *comp_editor) +{ + const icalcomponent *component; + GtkWindow *parent; + + g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), GTK_RESPONSE_NO); + g_return_val_if_fail (e_comp_editor_get_component (comp_editor) != NULL, GTK_RESPONSE_NO); + + parent = GTK_WINDOW (comp_editor); + component = e_comp_editor_get_component (comp_editor); + switch (icalcomponent_isa (component)) { + case ICAL_VEVENT_COMPONENT: + if (e_comp_editor_page_general_get_show_attendees (comp_editor->priv->page_general)) + return e_alert_run_dialog_for_args (parent, "calendar:prompt-save-meeting", NULL); + else + return e_alert_run_dialog_for_args (parent, "calendar:prompt-save-appointment", NULL); + case ICAL_VTODO_COMPONENT: + return e_alert_run_dialog_for_args (parent, "calendar:prompt-save-task", NULL); + case ICAL_VJOURNAL_COMPONENT: + return e_alert_run_dialog_for_args (parent, "calendar:prompt-save-memo", NULL); + default: + return GTK_RESPONSE_NO; + } +} + +static gboolean +e_comp_editor_prompt_and_save_changes (ECompEditor *comp_editor, + gboolean with_send) +{ + icalcomponent *component; + + g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), FALSE); + + if (!e_comp_editor_get_changed (comp_editor)) + return TRUE; + + switch (ece_save_component_dialog (comp_editor)) { + case GTK_RESPONSE_YES: /* Save */ + if (e_client_is_readonly (E_CLIENT (comp_editor->priv->target_client))) { + e_alert_submit ( + E_ALERT_SINK (comp_editor), + "calendar:prompt-read-only-cal-editor", + e_source_get_display_name ( + e_client_get_source (E_CLIENT (comp_editor->priv->target_client))), + NULL); + /* don't discard changes when selected readonly calendar */ + return FALSE; + } + + if (comp_editor->priv->component && + e_comp_editor_page_general_get_show_attendees (comp_editor->priv->page_general) && + icalcomponent_isa (comp_editor->priv->component) == ICAL_VTODO_COMPONENT + && e_client_check_capability (E_CLIENT (comp_editor->priv->target_client), CAL_STATIC_CAPABILITY_NO_TASK_ASSIGNMENT)) { + e_alert_submit ( + E_ALERT_SINK (comp_editor), + "calendar:prompt-no-task-assignment-editor", + e_source_get_display_name ( + e_client_get_source (E_CLIENT (comp_editor->priv->target_client))), + NULL); + return FALSE; + } + + component = icalcomponent_new_clone (comp_editor->priv->component); + if (!e_comp_editor_fill_component (comp_editor, component)) { + icalcomponent_free (component); + return FALSE; + } + + ece_save_component (comp_editor, component, with_send, TRUE); + + return FALSE; + case GTK_RESPONSE_NO: /* Discard */ + return TRUE; + case GTK_RESPONSE_CANCEL: /* Cancel */ + default: + return FALSE; + } + + return FALSE; +} + +static void +action_close_cb (GtkAction *action, + ECompEditor *comp_editor) +{ + g_return_if_fail (E_IS_COMP_EDITOR (comp_editor)); + + if (e_comp_editor_prompt_and_save_changes (comp_editor, TRUE)) + e_comp_editor_close (comp_editor); +} + +static void +action_help_cb (GtkAction *action, + ECompEditor *comp_editor) +{ + ECompEditorClass *klass; + + g_return_if_fail (E_IS_COMP_EDITOR (comp_editor)); + + klass = E_COMP_EDITOR_GET_CLASS (comp_editor); + g_return_if_fail (klass->help_section != NULL); + + e_display_help (GTK_WINDOW (comp_editor), klass->help_section); +} + +static void +ece_print_or_preview (ECompEditor *comp_editor, + GtkPrintOperationAction print_action) +{ + icalcomponent *component; + ECalComponent *comp; + + g_return_if_fail (E_IS_COMP_EDITOR (comp_editor)); + g_return_if_fail (e_comp_editor_get_component (comp_editor) != NULL); + + component = icalcomponent_new_clone (e_comp_editor_get_component (comp_editor)); + if (!e_comp_editor_fill_component (comp_editor, component)) { + icalcomponent_free (component); + return; + } + + comp = e_cal_component_new_from_icalcomponent (component); + g_return_if_fail (comp != NULL); + + print_comp (comp, + e_comp_editor_get_target_client (comp_editor), + calendar_config_get_icaltimezone (), + calendar_config_get_24_hour_format (), + print_action); + + g_object_unref (comp); +} + +static void +action_print_cb (GtkAction *action, + ECompEditor *comp_editor) +{ + ece_print_or_preview (comp_editor, GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG); +} + +static void +action_print_preview_cb (GtkAction *action, + ECompEditor *comp_editor) +{ + ece_print_or_preview (comp_editor, GTK_PRINT_OPERATION_ACTION_PREVIEW); +} + +static void +action_save_cb (GtkAction *action, + ECompEditor *comp_editor) +{ + e_comp_editor_save_and_close (comp_editor, FALSE); +} + +static void +action_save_and_close_cb (GtkAction *action, + ECompEditor *comp_editor) +{ + e_comp_editor_save_and_close (comp_editor, TRUE); +} + +static gboolean +ece_organizer_email_address_is_user (ECompEditor *comp_editor, + EClient *client, + const gchar *email_address, + gboolean is_organizer) +{ + ESourceRegistry *registry; + const gchar *cal_email_address; + + g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), FALSE); + g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE); + + email_address = itip_strip_mailto (email_address); + + if (!email_address || !*email_address) + return FALSE; + + cal_email_address = e_comp_editor_get_cal_email_address (comp_editor); + if (cal_email_address && *cal_email_address && + g_ascii_strcasecmp (cal_email_address, email_address) == 0) { + return TRUE; + } + + if (is_organizer && e_client_check_capability (client, CAL_STATIC_CAPABILITY_ORGANIZER_NOT_EMAIL_ADDRESS)) + return FALSE; + + registry = e_shell_get_registry (e_comp_editor_get_shell (comp_editor)); + + return itip_address_is_user (registry, email_address); +} + +static gboolean +ece_organizer_is_user (ECompEditor *comp_editor, + icalcomponent *component, + EClient *client) +{ + icalproperty *prop; + const gchar *organizer; + + g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), FALSE); + g_return_val_if_fail (component != NULL, FALSE); + g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE); + + prop = icalcomponent_get_first_property (component, ICAL_ORGANIZER_PROPERTY); + if (!prop || e_client_check_capability (client, CAL_STATIC_CAPABILITY_NO_ORGANIZER)) + return FALSE; + + organizer = itip_strip_mailto (icalproperty_get_organizer (prop)); + if (!organizer || !*organizer) + return FALSE; + + return ece_organizer_email_address_is_user (comp_editor, client, organizer, TRUE); +} + +static gboolean +ece_sentby_is_user (ECompEditor *comp_editor, + icalcomponent *component, + EClient *client) +{ + icalproperty *prop; + icalparameter *param; + const gchar *sentby; + + g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), FALSE); + g_return_val_if_fail (component != NULL, FALSE); + g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE); + + prop = icalcomponent_get_first_property (component, ICAL_ORGANIZER_PROPERTY); + if (!prop || e_client_check_capability (client, CAL_STATIC_CAPABILITY_NO_ORGANIZER)) + return FALSE; + + param = icalproperty_get_first_parameter (prop, ICAL_SENTBY_PARAMETER); + if (!param) + return FALSE; + + sentby = icalparameter_get_sentby (param); + + return ece_organizer_email_address_is_user (comp_editor, client, sentby, FALSE); +} + +static void +ece_emit_times_changed_cb (ECompEditor *comp_editor) +{ + g_return_if_fail (E_IS_COMP_EDITOR (comp_editor)); + + g_signal_emit (comp_editor, signals[TIMES_CHANGED], 0, NULL); +} + +static void +ece_connect_time_parts (ECompEditor *comp_editor, + ECompEditorPropertyPart *dtstart_part, + ECompEditorPropertyPart *dtend_part) +{ + g_return_if_fail (E_IS_COMP_EDITOR (comp_editor)); + +#define update_part(x) G_STMT_START { \ + if (x) \ + g_object_ref (x); \ + if (comp_editor->priv->x) { \ + g_signal_handlers_disconnect_by_func (comp_editor->priv->x, G_CALLBACK (ece_emit_times_changed_cb), comp_editor); \ + g_clear_object (&comp_editor->priv->x); \ + } \ + if (x) { \ + comp_editor->priv->x = x; \ + g_signal_connect_swapped (comp_editor->priv->x, "changed", \ + G_CALLBACK (ece_emit_times_changed_cb), comp_editor); \ + } \ + } G_STMT_END + + update_part (dtstart_part); + update_part (dtend_part); + +#undef update_part +} + +static void +ece_sensitize_widgets (ECompEditor *comp_editor, + gboolean force_insensitive) +{ + GtkActionGroup *group; + GSList *link; + + g_return_if_fail (E_IS_COMP_EDITOR (comp_editor)); + + for (link = comp_editor->priv->pages; link; link = g_slist_next (link)) { + ECompEditorPage *page = link->data; + + g_warn_if_fail (E_IS_COMP_EDITOR_PAGE (page)); + if (!E_IS_COMP_EDITOR_PAGE (page)) + continue; + + e_comp_editor_page_sensitize_widgets (page, force_insensitive); + } + + group = e_comp_editor_get_action_group (comp_editor, "individual"); + gtk_action_group_set_sensitive (group, !force_insensitive); + + group = e_comp_editor_get_action_group (comp_editor, "editable"); + gtk_action_group_set_sensitive (group, !force_insensitive); +} + +static void +ece_fill_widgets (ECompEditor *comp_editor, + icalcomponent *component) +{ + GSList *link; + + g_return_if_fail (E_IS_COMP_EDITOR (comp_editor)); + g_return_if_fail (component != NULL); + + for (link = comp_editor->priv->pages; link; link = g_slist_next (link)) { + ECompEditorPage *page = link->data; + + g_warn_if_fail (E_IS_COMP_EDITOR_PAGE (page)); + if (!E_IS_COMP_EDITOR_PAGE (page)) + continue; + + e_comp_editor_page_fill_widgets (page, component); + } +} + +static gboolean +ece_fill_component (ECompEditor *comp_editor, + icalcomponent *component) +{ + GSList *link; + + g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), FALSE); + g_return_val_if_fail (component != NULL, FALSE); + + for (link = comp_editor->priv->pages; link; link = g_slist_next (link)) { + ECompEditorPage *page = link->data; + + g_warn_if_fail (E_IS_COMP_EDITOR_PAGE (page)); + if (!E_IS_COMP_EDITOR_PAGE (page)) + continue; + + if (!e_comp_editor_page_fill_component (page, component)) + return FALSE; + } + + return TRUE; +} + +static void +comp_editor_realize_cb (ECompEditor *comp_editor) +{ + g_return_if_fail (E_IS_COMP_EDITOR (comp_editor)); + + if (comp_editor->priv->component) { + e_comp_editor_fill_widgets (comp_editor, comp_editor->priv->component); + e_comp_editor_set_changed (comp_editor, FALSE); + } + + e_comp_editor_update_window_title (comp_editor); + e_comp_editor_sensitize_widgets (comp_editor); + + if (comp_editor->priv->page_general && comp_editor->priv->origin_source) { + e_comp_editor_page_general_set_selected_source ( + comp_editor->priv->page_general, + comp_editor->priv->origin_source); + e_comp_editor_set_changed (comp_editor, FALSE); + } + + if (comp_editor->priv->page_general) { + e_comp_editor_page_general_update_view (comp_editor->priv->page_general); + + if (!comp_editor->priv->show_attendees_handler_id) { + comp_editor->priv->show_attendees_handler_id = + e_signal_connect_notify_swapped (comp_editor->priv->page_general, + "notify::show-attendees", + G_CALLBACK (e_comp_editor_update_window_title), comp_editor); + } + } + + if (!comp_editor->priv->target_client) + e_comp_editor_open_target_client (comp_editor); +} + +static void +comp_editor_unrealize_cb (ECompEditor *comp_editor) +{ + g_return_if_fail (E_IS_COMP_EDITOR (comp_editor)); + + if (comp_editor->priv->page_general) { + e_signal_disconnect_notify_handler (comp_editor->priv->page_general, + &comp_editor->priv->show_attendees_handler_id); + } +} + +static gboolean +comp_editor_delete_event (GtkWidget *widget, + GdkEventAny *event) +{ + ECompEditor *comp_editor; + + g_return_val_if_fail (E_IS_COMP_EDITOR (widget), FALSE); + + comp_editor = E_COMP_EDITOR (widget); + + /* It's disabled when the component is being saved */ + if (gtk_widget_get_sensitive (GTK_WIDGET (comp_editor->priv->content))) + action_close_cb (NULL, comp_editor); + + return TRUE; +} + +static gboolean +comp_editor_key_press_event (GtkWidget *widget, + GdkEventKey *event) +{ + ECompEditor *comp_editor; + + g_return_val_if_fail (E_IS_COMP_EDITOR (widget), FALSE); + + comp_editor = E_COMP_EDITOR (widget); + + if (event->keyval == GDK_KEY_Escape && + !e_alert_bar_close_alert (comp_editor->priv->alert_bar)) { + GtkAction *action; + + action = e_comp_editor_get_action (comp_editor, "close"); + gtk_action_activate (action); + + return TRUE; + } + + /* Chain up to parent's method. */ + return GTK_WIDGET_CLASS (e_comp_editor_parent_class)->key_press_event (widget, event); +} + +static void +comp_editor_selected_source_notify_cb (ECompEditorPageGeneral *page_general, + GParamSpec *param, + ECompEditor *comp_editor) +{ + g_return_if_fail (E_IS_COMP_EDITOR_PAGE_GENERAL (page_general)); + g_return_if_fail (E_IS_COMP_EDITOR (comp_editor)); + g_return_if_fail (comp_editor->priv->page_general == page_general); + + e_comp_editor_open_target_client (comp_editor); +} + +static void +e_comp_editor_submit_alert (EAlertSink *alert_sink, + EAlert *alert) +{ + ECompEditor *comp_editor; + + g_return_if_fail (E_IS_COMP_EDITOR (alert_sink)); + g_return_if_fail (E_IS_ALERT (alert)); + + comp_editor = E_COMP_EDITOR (alert_sink); + + e_alert_bar_submit_alert (comp_editor->priv->alert_bar, alert); +} + +static void +e_comp_editor_set_origin_source (ECompEditor *comp_editor, + ESource *origin_source) +{ + g_return_if_fail (E_IS_COMP_EDITOR (comp_editor)); + if (origin_source) + g_return_if_fail (E_IS_SOURCE (origin_source)); + + g_clear_object (&comp_editor->priv->origin_source); + if (origin_source) + comp_editor->priv->origin_source = g_object_ref (origin_source); +} + +static void +e_comp_editor_set_shell (ECompEditor *comp_editor, + EShell *shell) +{ + g_return_if_fail (E_IS_COMP_EDITOR (comp_editor)); + g_return_if_fail (E_IS_SHELL (shell)); + + g_clear_object (&comp_editor->priv->shell); + comp_editor->priv->shell = g_object_ref (shell); +} + +static void +e_comp_editor_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_ALARM_EMAIL_ADDRESS: + e_comp_editor_set_alarm_email_address ( + E_COMP_EDITOR (object), + g_value_get_string (value)); + return; + + case PROP_CAL_EMAIL_ADDRESS: + e_comp_editor_set_cal_email_address ( + E_COMP_EDITOR (object), + g_value_get_string (value)); + return; + + case PROP_CHANGED: + e_comp_editor_set_changed ( + E_COMP_EDITOR (object), + g_value_get_boolean (value)); + return; + + case PROP_COMPONENT: + e_comp_editor_set_component ( + E_COMP_EDITOR (object), + g_value_get_pointer (value)); + return; + + case PROP_FLAGS: + e_comp_editor_set_flags ( + E_COMP_EDITOR (object), + g_value_get_uint (value)); + return; + + case PROP_ORIGIN_SOURCE: + e_comp_editor_set_origin_source ( + E_COMP_EDITOR (object), + g_value_get_object (value)); + return; + + case PROP_SHELL: + e_comp_editor_set_shell ( + E_COMP_EDITOR (object), + g_value_get_object (value)); + return; + + case PROP_SOURCE_CLIENT: + e_comp_editor_set_source_client ( + E_COMP_EDITOR (object), + g_value_get_object (value)); + return; + + case PROP_TARGET_CLIENT: + e_comp_editor_set_target_client ( + E_COMP_EDITOR (object), + g_value_get_object (value)); + return; + + case PROP_TITLE_SUFFIX: + e_comp_editor_set_title_suffix ( + E_COMP_EDITOR (object), + g_value_get_string (value)); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +e_comp_editor_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_ALARM_EMAIL_ADDRESS: + g_value_set_string ( + value, + e_comp_editor_get_alarm_email_address ( + E_COMP_EDITOR (object))); + return; + + case PROP_CAL_EMAIL_ADDRESS: + g_value_set_string ( + value, + e_comp_editor_get_cal_email_address ( + E_COMP_EDITOR (object))); + return; + + case PROP_CHANGED: + g_value_set_boolean ( + value, + e_comp_editor_get_changed ( + E_COMP_EDITOR (object))); + return; + + case PROP_COMPONENT: + g_value_set_pointer ( + value, + e_comp_editor_get_component ( + E_COMP_EDITOR (object))); + return; + + case PROP_FLAGS: + g_value_set_uint ( + value, + e_comp_editor_get_flags ( + E_COMP_EDITOR (object))); + return; + + case PROP_ORIGIN_SOURCE: + g_value_set_object ( + value, + e_comp_editor_get_origin_source ( + E_COMP_EDITOR (object))); + return; + + case PROP_SHELL: + g_value_set_object ( + value, + e_comp_editor_get_shell ( + E_COMP_EDITOR (object))); + return; + + case PROP_SOURCE_CLIENT: + g_value_set_object ( + value, + e_comp_editor_get_source_client ( + E_COMP_EDITOR (object))); + return; + + case PROP_TARGET_CLIENT: + g_value_set_object ( + value, + e_comp_editor_get_target_client ( + E_COMP_EDITOR (object))); + return; + + case PROP_TITLE_SUFFIX: + g_value_set_string ( + value, + e_comp_editor_get_title_suffix ( + E_COMP_EDITOR (object))); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +e_comp_editor_constructed (GObject *object) +{ + const gchar *ui = + "" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " \n" + " " + " " + " " + " " + " " + " " + " " + " " + " " + ""; + + GtkActionEntry core_entries[] = { + + { "close", + "window-close", + N_("_Close"), + "w", + N_("Close the current window"), + G_CALLBACK (action_close_cb) }, + + { "copy-clipboard", + "edit-copy", + N_("_Copy"), + "c", + N_("Copy the selection"), + NULL }, /* Handled by EFocusTracker */ + + { "cut-clipboard", + "edit-cut", + N_("Cu_t"), + "x", + N_("Cut the selection"), + NULL }, /* Handled by EFocusTracker */ + + { "delete-selection", + "edit-delete", + N_("_Delete"), + NULL, + N_("Delete the selection"), + NULL }, /* Handled by EFocusTracker */ + + { "help", + "help-browser", + N_("_Help"), + "F1", + N_("View help"), + G_CALLBACK (action_help_cb) }, + + { "paste-clipboard", + "edit-paste", + N_("_Paste"), + "v", + N_("Paste the clipboard"), + NULL }, /* Handled by EFocusTracker */ + + { "print", + "document-print", + N_("_Print..."), + "p", + NULL, + G_CALLBACK (action_print_cb) }, + + { "print-preview", + "document-print-preview", + N_("Pre_view..."), + NULL, + NULL, + G_CALLBACK (action_print_preview_cb) }, + + { "select-all", + "edit-select-all", + N_("Select _All"), + "a", + N_("Select all text"), + NULL }, /* Handled by EFocusTracker */ + + { "undo", + "edit-undo", + N_("_Undo"), + "z", + N_("Undo"), + NULL }, /* Handled by EFocusTracker */ + + { "redo", + "edit-redo", + N_("_Redo"), + "y", + N_("Redo"), + NULL }, /* Handled by EFocusTracker */ + + /* Menus */ + + { "classification-menu", + NULL, + N_("_Classification"), + NULL, + NULL, + NULL }, + + { "edit-menu", + NULL, + N_("_Edit"), + NULL, + NULL, + NULL }, + + { "file-menu", + NULL, + N_("_File"), + NULL, + NULL, + NULL }, + + { "help-menu", + NULL, + N_("_Help"), + NULL, + NULL, + NULL }, + + { "insert-menu", + NULL, + N_("_Insert"), + NULL, + NULL, + NULL }, + + { "options-menu", + NULL, + N_("_Options"), + NULL, + NULL, + NULL }, + + { "view-menu", + NULL, + N_("_View"), + NULL, + NULL, + NULL } + }; + + GtkActionEntry editable_entries[] = { + + { "save", + "document-save", + N_("_Save"), + "s", + N_("Save current changes"), + G_CALLBACK (action_save_cb) }, + + { "save-and-close", + NULL, + N_("Save and Close"), + NULL, + N_("Save current changes and close editor"), + G_CALLBACK (action_save_and_close_cb) } + }; + + ECompEditor *comp_editor = E_COMP_EDITOR (object); + GtkWidget *widget; + GtkBox *vbox; + GtkAction *action; + GtkActionGroup *action_group; + EFocusTracker *focus_tracker; + GError *error = NULL; + + G_OBJECT_CLASS (e_comp_editor_parent_class)->constructed (object); + + g_signal_connect (comp_editor, "key-press-event", + G_CALLBACK (e_util_check_gtk_bindings_in_key_press_event_cb), NULL); + + comp_editor->priv->calendar_settings = e_util_ref_settings ("org.gnome.evolution.calendar"); + comp_editor->priv->ui_manager = gtk_ui_manager_new (); + + gtk_window_add_accel_group ( + GTK_WINDOW (comp_editor), + gtk_ui_manager_get_accel_group (comp_editor->priv->ui_manager)); + + /* Setup Action Groups */ + + action_group = gtk_action_group_new ("individual"); + gtk_action_group_set_translation_domain ( + action_group, GETTEXT_PACKAGE); + gtk_ui_manager_insert_action_group ( + comp_editor->priv->ui_manager, action_group, 0); + g_object_unref (action_group); + + action_group = gtk_action_group_new ("core"); + gtk_action_group_set_translation_domain ( + action_group, GETTEXT_PACKAGE); + gtk_action_group_add_actions ( + action_group, core_entries, + G_N_ELEMENTS (core_entries), comp_editor); + gtk_ui_manager_insert_action_group ( + comp_editor->priv->ui_manager, action_group, 0); + g_object_unref (action_group); + + action_group = gtk_action_group_new ("editable"); + gtk_action_group_set_translation_domain ( + action_group, GETTEXT_PACKAGE); + gtk_action_group_add_actions ( + action_group, editable_entries, + G_N_ELEMENTS (editable_entries), comp_editor); + gtk_ui_manager_insert_action_group ( + comp_editor->priv->ui_manager, action_group, 0); + g_object_unref (action_group); + + action = gtk_action_group_get_action (action_group, "save-and-close"); + if (action) { + GtkAction *save_action; + GIcon *icon; + GIcon *emblemed_icon; + GEmblem *emblem; + + icon = g_themed_icon_new ("window-close"); + emblemed_icon = g_themed_icon_new ("document-save"); + emblem = g_emblem_new (emblemed_icon); + g_object_unref (emblemed_icon); + + emblemed_icon = g_emblemed_icon_new (icon, emblem); + g_object_unref (emblem); + g_object_unref (icon); + + gtk_action_set_gicon (action, emblemed_icon); + + g_object_unref (emblemed_icon); + + save_action = gtk_action_group_get_action (action_group, "save"); + e_binding_bind_property ( + save_action, "sensitive", + action, "sensitive", + G_BINDING_SYNC_CREATE); + } + + gtk_ui_manager_add_ui_from_string (comp_editor->priv->ui_manager, ui, -1, &error); + if (error != NULL) { + g_warning ("%s: %s", G_STRFUNC, error->message); + g_error_free (error); + } + + widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + g_object_set (G_OBJECT (widget), + "hexpand", TRUE, + "halign", GTK_ALIGN_FILL, + "vexpand", TRUE, + "valign", GTK_ALIGN_FILL, + NULL); + gtk_widget_show (widget); + + vbox = GTK_BOX (widget); + + gtk_container_add (GTK_CONTAINER (comp_editor), widget); + + widget = e_comp_editor_get_managed_widget (comp_editor, "/main-menu"); + gtk_box_pack_start (GTK_BOX (vbox), widget, FALSE, FALSE, 0); + gtk_widget_set_visible (widget, TRUE); + + widget = e_comp_editor_get_managed_widget (comp_editor, "/main-toolbar"); + gtk_box_pack_start (GTK_BOX (vbox), widget, FALSE, FALSE, 0); + gtk_widget_show (widget); + + gtk_style_context_add_class ( + gtk_widget_get_style_context (widget), + GTK_STYLE_CLASS_PRIMARY_TOOLBAR); + + widget = e_alert_bar_new (); + g_object_set (G_OBJECT (widget), + "hexpand", FALSE, + "halign", GTK_ALIGN_FILL, + "vexpand", FALSE, + "valign", GTK_ALIGN_START, + NULL); + + comp_editor->priv->alert_bar = E_ALERT_BAR (widget); + + gtk_box_pack_start (vbox, widget, FALSE, FALSE, 0); + + widget = e_activity_bar_new (); + g_object_set (G_OBJECT (widget), + "hexpand", FALSE, + "halign", GTK_ALIGN_FILL, + "vexpand", FALSE, + "valign", GTK_ALIGN_START, + NULL); + + comp_editor->priv->activity_bar = E_ACTIVITY_BAR (widget); + + gtk_box_pack_start (vbox, widget, FALSE, FALSE, 0); + + widget = gtk_notebook_new (); + g_object_set (G_OBJECT (widget), + "hexpand", TRUE, + "halign", GTK_ALIGN_FILL, + "vexpand", TRUE, + "valign", GTK_ALIGN_FILL, + "show-tabs", TRUE, + "show-border", FALSE, + NULL); + gtk_widget_show (widget); + + comp_editor->priv->content = GTK_NOTEBOOK (widget); + + gtk_box_pack_start (vbox, widget, TRUE, TRUE, 0); + + /* Configure an EFocusTracker to manage selection actions. */ + + focus_tracker = e_focus_tracker_new (GTK_WINDOW (comp_editor)); + + action = e_comp_editor_get_action (comp_editor, "cut-clipboard"); + e_focus_tracker_set_cut_clipboard_action (focus_tracker, action); + + action = e_comp_editor_get_action (comp_editor, "copy-clipboard"); + e_focus_tracker_set_copy_clipboard_action (focus_tracker, action); + + action = e_comp_editor_get_action (comp_editor, "paste-clipboard"); + e_focus_tracker_set_paste_clipboard_action (focus_tracker, action); + + action = e_comp_editor_get_action (comp_editor, "delete-selection"); + e_focus_tracker_set_delete_selection_action (focus_tracker, action); + + action = e_comp_editor_get_action (comp_editor, "select-all"); + e_focus_tracker_set_select_all_action (focus_tracker, action); + + action = e_comp_editor_get_action (comp_editor, "undo"); + e_focus_tracker_set_undo_action (focus_tracker, action); + + action = e_comp_editor_get_action (comp_editor, "redo"); + e_focus_tracker_set_redo_action (focus_tracker, action); + + comp_editor->priv->focus_tracker = focus_tracker; + + /* Desensitize the "save" action. */ + action = e_comp_editor_get_action (comp_editor, "save"); + gtk_action_set_sensitive (action, FALSE); + + e_binding_bind_property (comp_editor, "changed", action, "sensitive", 0); + + g_signal_connect (comp_editor, "realize", G_CALLBACK (comp_editor_realize_cb), NULL); + g_signal_connect (comp_editor, "unrealize", G_CALLBACK (comp_editor_unrealize_cb), NULL); + + gtk_application_add_window (GTK_APPLICATION (comp_editor->priv->shell), GTK_WINDOW (comp_editor)); + + e_extensible_load_extensions (E_EXTENSIBLE (comp_editor)); +} + +static void +e_comp_editor_dispose (GObject *object) +{ + ECompEditor *comp_editor = E_COMP_EDITOR (object); + + if (comp_editor->priv->page_general) { + g_signal_handlers_disconnect_by_func (comp_editor->priv->page_general, + G_CALLBACK (comp_editor_selected_source_notify_cb), comp_editor); + comp_editor->priv->page_general = NULL; + } + + if (comp_editor->priv->target_client_opening) { + e_activity_cancel (comp_editor->priv->target_client_opening); + g_clear_object (&comp_editor->priv->target_client_opening); + } + + g_slist_free_full (comp_editor->priv->pages, g_object_unref); + comp_editor->priv->pages = NULL; + + g_free (comp_editor->priv->alarm_email_address); + comp_editor->priv->alarm_email_address = NULL; + + g_free (comp_editor->priv->cal_email_address); + comp_editor->priv->cal_email_address = NULL; + + g_free (comp_editor->priv->title_suffix); + comp_editor->priv->title_suffix = NULL; + + if (comp_editor->priv->component) { + icalcomponent_free (comp_editor->priv->component); + comp_editor->priv->component = NULL; + } + + ece_connect_time_parts (comp_editor, NULL, NULL); + + g_clear_object (&comp_editor->priv->origin_source); + g_clear_object (&comp_editor->priv->shell); + g_clear_object (&comp_editor->priv->focus_tracker); + g_clear_object (&comp_editor->priv->ui_manager); + g_clear_object (&comp_editor->priv->source_client); + g_clear_object (&comp_editor->priv->target_client); + g_clear_object (&comp_editor->priv->calendar_settings); + g_clear_object (&comp_editor->priv->validation_alert); + + comp_editor->priv->activity_bar = NULL; + + opened_editors = g_slist_remove (opened_editors, comp_editor); + + G_OBJECT_CLASS (e_comp_editor_parent_class)->dispose (object); +} + +static void +e_comp_editor_init (ECompEditor *comp_editor) +{ + comp_editor->priv = G_TYPE_INSTANCE_GET_PRIVATE (comp_editor, E_TYPE_COMP_EDITOR, ECompEditorPrivate); +} + +static void +e_comp_editor_alert_sink_iface_init (EAlertSinkInterface *iface) +{ + iface->submit_alert = e_comp_editor_submit_alert; +} + +static void +e_comp_editor_class_init (ECompEditorClass *klass) +{ + GtkWidgetClass *widget_class; + GObjectClass *object_class; + + g_type_class_add_private (klass, sizeof (ECompEditorPrivate)); + + klass->sensitize_widgets = ece_sensitize_widgets; + klass->fill_widgets = ece_fill_widgets; + klass->fill_component = ece_fill_component; + + widget_class = GTK_WIDGET_CLASS (klass); + widget_class->delete_event = comp_editor_delete_event; + widget_class->key_press_event = comp_editor_key_press_event; + + object_class = G_OBJECT_CLASS (klass); + object_class->set_property = e_comp_editor_set_property; + object_class->get_property = e_comp_editor_get_property; + object_class->constructed = e_comp_editor_constructed; + object_class->dispose = e_comp_editor_dispose; + + g_object_class_install_property ( + object_class, + PROP_ALARM_EMAIL_ADDRESS, + g_param_spec_string ( + "alarm-email-address", + "Alarm Email Address", + "Target client's alarm email address", + NULL, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property ( + object_class, + PROP_CAL_EMAIL_ADDRESS, + g_param_spec_string ( + "cal-email-address", + "Calendar Email Address", + "Target client's calendar email address", + NULL, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property ( + object_class, + PROP_CHANGED, + g_param_spec_boolean ( + "changed", + "Changed", + "Whether the editor content changed", + FALSE, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property ( + object_class, + PROP_COMPONENT, + g_param_spec_pointer ( + "component", + "Component", + "icalcomponent currently edited", + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property ( + object_class, + PROP_FLAGS, + g_param_spec_uint ( + "flags", + "Flags", + "Editor flags", + 0, G_MAXUINT, 0, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property ( + object_class, + PROP_ORIGIN_SOURCE, + g_param_spec_object ( + "origin-source", + "Origin Source", + "ESource of an ECalClient the component is stored in", + E_TYPE_SOURCE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property ( + object_class, + PROP_SHELL, + g_param_spec_object ( + "shell", + "Shell", + "EShell", + E_TYPE_SHELL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property ( + object_class, + PROP_SOURCE_CLIENT, + g_param_spec_object ( + "source-client", + "Source Client", + "ECalClient, the source calendar for the component", + E_TYPE_CAL_CLIENT, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property ( + object_class, + PROP_TARGET_CLIENT, + g_param_spec_object ( + "target-client", + "Target Client", + "ECalClient currently set as the target calendar for the component", + E_TYPE_CAL_CLIENT, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property ( + object_class, + PROP_TITLE_SUFFIX, + g_param_spec_string ( + "title-suffix", + "Title Suffix", + "Window title suffix, usually summary of the component", + NULL, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + signals[TIMES_CHANGED] = g_signal_new ( + "times-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (ECompEditorClass, times_changed), + NULL, NULL, NULL, + G_TYPE_NONE, 0, + G_TYPE_NONE); + + signals[OBJECT_CREATED] = g_signal_new ( + "object-created", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (ECompEditorClass, object_created), + NULL, NULL, NULL, + G_TYPE_NONE, 0, + G_TYPE_NONE); + + signals[EDITOR_CLOSED] = g_signal_new ( + "editor-closed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (ECompEditorClass, editor_closed), + NULL, NULL, + g_cclosure_marshal_VOID__BOOLEAN, + G_TYPE_NONE, 1, G_TYPE_BOOLEAN); +} + +void +e_comp_editor_sensitize_widgets (ECompEditor *comp_editor) +{ + ECompEditorClass *comp_editor_class; + gboolean force_insensitive; + GtkWidget *current_focus; + + g_return_if_fail (E_IS_COMP_EDITOR (comp_editor)); + + comp_editor_class = E_COMP_EDITOR_GET_CLASS (comp_editor); + g_return_if_fail (comp_editor_class != NULL); + g_return_if_fail (comp_editor_class->sensitize_widgets != NULL); + + current_focus = gtk_window_get_focus (GTK_WINDOW (comp_editor)); + + force_insensitive = !comp_editor->priv->component; + + if (!force_insensitive) { + ECalClient *target_client; + + target_client = e_comp_editor_get_target_client (comp_editor); + if (target_client) { + EClient *client = E_CLIENT (target_client); + + if (e_client_is_readonly (client)) { + force_insensitive = TRUE; + } else { + if (!e_cal_util_component_has_organizer (comp_editor->priv->component) || + ece_organizer_is_user (comp_editor, comp_editor->priv->component, client) || + ece_sentby_is_user (comp_editor, comp_editor->priv->component, client)) { + comp_editor->priv->flags = comp_editor->priv->flags | E_COMP_EDITOR_FLAG_ORGANIZER_IS_USER; + } else { + comp_editor->priv->flags = comp_editor->priv->flags & (~E_COMP_EDITOR_FLAG_ORGANIZER_IS_USER); + } + } + } else { + force_insensitive = TRUE; + } + } + + comp_editor_class->sensitize_widgets (comp_editor, force_insensitive); + + if (force_insensitive) + comp_editor->priv->restore_focus = current_focus; + else + ece_restore_focus (comp_editor); +} + +void +e_comp_editor_fill_widgets (ECompEditor *comp_editor, + icalcomponent *component) +{ + ECompEditorClass *comp_editor_class; + + g_return_if_fail (E_IS_COMP_EDITOR (comp_editor)); + g_return_if_fail (component != NULL); + + comp_editor_class = E_COMP_EDITOR_GET_CLASS (comp_editor); + g_return_if_fail (comp_editor_class != NULL); + g_return_if_fail (comp_editor_class->fill_widgets != NULL); + + e_comp_editor_set_updating (comp_editor, TRUE); + + comp_editor_class->fill_widgets (comp_editor, component); + + e_comp_editor_set_updating (comp_editor, FALSE); +} + +gboolean +e_comp_editor_fill_component (ECompEditor *comp_editor, + icalcomponent *component) +{ + ECompEditorClass *comp_editor_class; + gboolean is_valid; + + g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), FALSE); + g_return_val_if_fail (component != NULL, FALSE); + + comp_editor_class = E_COMP_EDITOR_GET_CLASS (comp_editor); + g_return_val_if_fail (comp_editor_class != NULL, FALSE); + g_return_val_if_fail (comp_editor_class->fill_component != NULL, FALSE); + + is_valid = comp_editor_class->fill_component (comp_editor, component); + + if (is_valid && comp_editor->priv->validation_alert) { + e_alert_response (comp_editor->priv->validation_alert, GTK_RESPONSE_CLOSE); + g_clear_object (&comp_editor->priv->validation_alert); + } + + if (is_valid) { + ECalClient *target_client; + EClient *client = NULL; + + target_client = e_comp_editor_get_target_client (comp_editor); + if (target_client) + client = E_CLIENT (target_client); + + if (!e_cal_util_component_has_organizer (component) || (client && ( + ece_organizer_is_user (comp_editor, component, client) || + ece_sentby_is_user (comp_editor, component, client)))) { + gint sequence; + + sequence = icalcomponent_get_sequence (component); + icalcomponent_set_sequence (component, sequence + 1); + } + } + + return is_valid; +} + +void +e_comp_editor_set_validation_error (ECompEditor *comp_editor, + ECompEditorPage *error_page, + GtkWidget *error_widget, + const gchar *error_message) +{ + EAlert *alert, *previous_alert; + + g_return_if_fail (E_IS_COMP_EDITOR (comp_editor)); + g_return_if_fail (error_message != NULL); + + /* Ignore validation errors when the inner editor is currently updating. */ + if (e_comp_editor_get_updating (comp_editor)) + return; + + alert = e_alert_new ("calendar:comp-editor-failed-validate", error_message, NULL); + + e_alert_bar_add_alert (comp_editor->priv->alert_bar, alert); + + previous_alert = comp_editor->priv->validation_alert; + comp_editor->priv->validation_alert = alert; + + if (previous_alert) { + e_alert_response (previous_alert, GTK_RESPONSE_CLOSE); + g_clear_object (&previous_alert); + } + + if (error_page) + e_comp_editor_select_page (comp_editor, error_page); + + if (error_widget) + gtk_widget_grab_focus (error_widget); +} + +EShell * +e_comp_editor_get_shell (ECompEditor *comp_editor) +{ + g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), NULL); + + return comp_editor->priv->shell; +} + +GSettings * +e_comp_editor_get_settings (ECompEditor *comp_editor) +{ + g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), NULL); + + return comp_editor->priv->calendar_settings; +} + +ESource * +e_comp_editor_get_origin_source (ECompEditor *comp_editor) +{ + g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), NULL); + + return comp_editor->priv->origin_source; +} + +icalcomponent * +e_comp_editor_get_component (ECompEditor *comp_editor) +{ + g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), NULL); + + return comp_editor->priv->component; +} + +guint32 +e_comp_editor_get_flags (ECompEditor *comp_editor) +{ + g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), 0); + + return comp_editor->priv->flags; +} + +void +e_comp_editor_set_flags (ECompEditor *comp_editor, + guint32 flags) +{ + g_return_if_fail (E_IS_COMP_EDITOR (comp_editor)); + + if (comp_editor->priv->flags == flags) + return; + + comp_editor->priv->flags = flags; + + g_object_notify (G_OBJECT (comp_editor), "flags"); +} + +EFocusTracker * +e_comp_editor_get_focus_tracker (ECompEditor *comp_editor) +{ + g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), NULL); + + return comp_editor->priv->focus_tracker; +} + +GtkUIManager * +e_comp_editor_get_ui_manager (ECompEditor *comp_editor) +{ + g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), NULL); + + return comp_editor->priv->ui_manager; +} + +GtkAction * +e_comp_editor_get_action (ECompEditor *comp_editor, + const gchar *action_name) +{ + GtkUIManager *ui_manager; + + g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), NULL); + g_return_val_if_fail (action_name != NULL, NULL); + + ui_manager = e_comp_editor_get_ui_manager (comp_editor); + + return e_lookup_action (ui_manager, action_name); +} + +GtkActionGroup * +e_comp_editor_get_action_group (ECompEditor *comp_editor, + const gchar *group_name) +{ + GtkUIManager *ui_manager; + + g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), NULL); + g_return_val_if_fail (group_name != NULL, NULL); + + ui_manager = e_comp_editor_get_ui_manager (comp_editor); + + return e_lookup_action_group (ui_manager, group_name); +} + +GtkWidget * +e_comp_editor_get_managed_widget (ECompEditor *comp_editor, + const gchar *widget_path) +{ + GtkUIManager *ui_manager; + GtkWidget *widget; + + g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), NULL); + g_return_val_if_fail (widget_path != NULL, NULL); + + ui_manager = e_comp_editor_get_ui_manager (comp_editor); + widget = gtk_ui_manager_get_widget (ui_manager, widget_path); + g_return_val_if_fail (widget != NULL, NULL); + + return widget; +} + +const gchar * +e_comp_editor_get_alarm_email_address (ECompEditor *comp_editor) +{ + g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), NULL); + + return comp_editor->priv->alarm_email_address; +} + +void +e_comp_editor_set_alarm_email_address (ECompEditor *comp_editor, + const gchar *alarm_email_address) +{ + g_return_if_fail (E_IS_COMP_EDITOR (comp_editor)); + + if (g_strcmp0 (alarm_email_address, comp_editor->priv->alarm_email_address) == 0) + return; + + g_free (comp_editor->priv->alarm_email_address); + comp_editor->priv->alarm_email_address = g_strdup (alarm_email_address); + + g_object_notify (G_OBJECT (comp_editor), "alarm-email-address"); +} + +const gchar * +e_comp_editor_get_cal_email_address (ECompEditor *comp_editor) +{ + g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), NULL); + + return comp_editor->priv->cal_email_address; +} + +void +e_comp_editor_set_cal_email_address (ECompEditor *comp_editor, + const gchar *cal_email_address) +{ + g_return_if_fail (E_IS_COMP_EDITOR (comp_editor)); + + if (g_strcmp0 (cal_email_address, comp_editor->priv->cal_email_address) == 0) + return; + + g_free (comp_editor->priv->cal_email_address); + comp_editor->priv->cal_email_address = g_strdup (cal_email_address); + + g_object_notify (G_OBJECT (comp_editor), "cal-email-address"); +} + +gboolean +e_comp_editor_get_changed (ECompEditor *comp_editor) +{ + g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), FALSE); + + return comp_editor->priv->changed; +} + +void +e_comp_editor_set_changed (ECompEditor *comp_editor, + gboolean changed) +{ + g_return_if_fail (E_IS_COMP_EDITOR (comp_editor)); + + if ((changed ? 1 : 0) == (comp_editor->priv->changed ? 1 : 0)) + return; + + comp_editor->priv->changed = changed; + + g_object_notify (G_OBJECT (comp_editor), "changed"); +} + +void +e_comp_editor_ensure_changed (ECompEditor *comp_editor) +{ + g_return_if_fail (E_IS_COMP_EDITOR (comp_editor)); + + e_comp_editor_set_changed (comp_editor, TRUE); +} + +gboolean +e_comp_editor_get_updating (ECompEditor *comp_editor) +{ + g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), FALSE); + + return comp_editor->priv->updating > 0; +} + +void +e_comp_editor_set_updating (ECompEditor *comp_editor, + gboolean updating) +{ + g_return_if_fail (E_IS_COMP_EDITOR (comp_editor)); + + if (updating) { + comp_editor->priv->updating++; + } else if (comp_editor->priv->updating > 0) { + comp_editor->priv->updating--; + } else { + g_warn_if_reached (); + } +} + +ECalClient * +e_comp_editor_get_source_client (ECompEditor *comp_editor) +{ + g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), NULL); + + return comp_editor->priv->source_client; +} + +void +e_comp_editor_set_source_client (ECompEditor *comp_editor, + ECalClient *client) +{ + g_return_if_fail (E_IS_COMP_EDITOR (comp_editor)); + + if (client == comp_editor->priv->source_client) + return; + + if (client) + g_object_ref (client); + g_clear_object (&comp_editor->priv->source_client); + comp_editor->priv->source_client = client; + + g_object_notify (G_OBJECT (comp_editor), "source-client"); +} + +ECalClient * +e_comp_editor_get_target_client (ECompEditor *comp_editor) +{ + g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), NULL); + + return comp_editor->priv->target_client; +} + +void +e_comp_editor_set_target_client (ECompEditor *comp_editor, + ECalClient *client) +{ + g_return_if_fail (E_IS_COMP_EDITOR (comp_editor)); + + if (client == comp_editor->priv->target_client) + return; + + if (client) + g_object_ref (client); + g_clear_object (&comp_editor->priv->target_client); + comp_editor->priv->target_client = client; + + g_object_notify (G_OBJECT (comp_editor), "target-client"); + + if (client && !comp_editor->priv->source_client && comp_editor->priv->origin_source && + e_source_equal (e_client_get_source (E_CLIENT (client)), comp_editor->priv->origin_source)) + e_comp_editor_set_source_client (comp_editor, client); + + e_comp_editor_sensitize_widgets (comp_editor); +} + +const gchar * +e_comp_editor_get_title_suffix (ECompEditor *comp_editor) +{ + g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), NULL); + + return comp_editor->priv->title_suffix; +} + +void +e_comp_editor_set_title_suffix (ECompEditor *comp_editor, + const gchar *title_suffix) +{ + g_return_if_fail (E_IS_COMP_EDITOR (comp_editor)); + + if (g_strcmp0 (title_suffix, comp_editor->priv->title_suffix) == 0) + return; + + g_free (comp_editor->priv->title_suffix); + comp_editor->priv->title_suffix = g_strdup (title_suffix); + + g_object_notify (G_OBJECT (comp_editor), "title-suffix"); + + e_comp_editor_update_window_title (comp_editor); +} + +void +e_comp_editor_set_time_parts (ECompEditor *comp_editor, + ECompEditorPropertyPart *dtstart_part, + ECompEditorPropertyPart *dtend_part) +{ + g_return_if_fail (E_IS_COMP_EDITOR (comp_editor)); + + if (dtstart_part) + g_return_if_fail (E_IS_COMP_EDITOR_PROPERTY_PART_DATETIME (dtstart_part)); + if (dtend_part) + g_return_if_fail (E_IS_COMP_EDITOR_PROPERTY_PART_DATETIME (dtend_part)); + + ece_connect_time_parts (comp_editor, dtstart_part, dtend_part); +} + +void +e_comp_editor_get_time_parts (ECompEditor *comp_editor, + ECompEditorPropertyPart **out_dtstart_part, + ECompEditorPropertyPart **out_dtend_part) +{ + g_return_if_fail (E_IS_COMP_EDITOR (comp_editor)); + + if (out_dtstart_part) + *out_dtstart_part = comp_editor->priv->dtstart_part; + if (out_dtend_part) + *out_dtend_part = comp_editor->priv->dtend_part; +} + +/* This consumes the @page. */ +void +e_comp_editor_add_page (ECompEditor *comp_editor, + const gchar *label, + ECompEditorPage *page) +{ + ECompEditor *pages_comp_editor; + + g_return_if_fail (E_IS_COMP_EDITOR (comp_editor)); + g_return_if_fail (label != NULL); + g_return_if_fail (E_IS_COMP_EDITOR_PAGE (page)); + + pages_comp_editor = e_comp_editor_page_ref_editor (page); + if (pages_comp_editor != comp_editor) { + g_warn_if_fail (pages_comp_editor == comp_editor); + g_clear_object (&pages_comp_editor); + return; + } + + g_clear_object (&pages_comp_editor); + + /* One reference uses the GtkNotebook, the other the pages GSList */ + gtk_notebook_append_page (comp_editor->priv->content, + GTK_WIDGET (page), + gtk_label_new_with_mnemonic (label)); + + comp_editor->priv->pages = g_slist_append (comp_editor->priv->pages, g_object_ref (page)); + + g_signal_connect_swapped (page, "changed", G_CALLBACK (e_comp_editor_ensure_changed), comp_editor); + + if (E_IS_COMP_EDITOR_PAGE_GENERAL (page)) { + ECompEditorPageGeneral *page_general; + + g_return_if_fail (comp_editor->priv->page_general == NULL); + + page_general = E_COMP_EDITOR_PAGE_GENERAL (page); + + g_signal_connect (page_general, "notify::selected-source", + G_CALLBACK (comp_editor_selected_source_notify_cb), comp_editor); + + comp_editor->priv->page_general = page_general; + + if ((comp_editor->priv->flags & E_COMP_EDITOR_FLAG_WITH_ATTENDEES) != 0) { + e_comp_editor_page_general_set_show_attendees (page_general, TRUE); + } + } +} + +/* The returned pointer is owned by the @comp_editor; returns the first instance, + in order of the addition. */ +ECompEditorPage * +e_comp_editor_get_page (ECompEditor *comp_editor, + GType page_type) +{ + GSList *link; + + g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), NULL); + g_return_val_if_fail (g_type_is_a (page_type, E_TYPE_COMP_EDITOR_PAGE), NULL); + g_return_val_if_fail (page_type != E_TYPE_COMP_EDITOR_PAGE, NULL); + + for (link = comp_editor->priv->pages; link; link = g_slist_next (link)) { + ECompEditorPage *page = link->data; + + if (G_TYPE_CHECK_INSTANCE_TYPE (page, page_type)) + return page; + } + + return NULL; +} + +/* Free the returned GSList with g_slist_free(), the memebers are owned by the comp_editor */ +GSList * +e_comp_editor_get_pages (ECompEditor *comp_editor) +{ + g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), NULL); + + return g_slist_copy (comp_editor->priv->pages); +} + +void +e_comp_editor_select_page (ECompEditor *comp_editor, + ECompEditorPage *page) +{ + gint page_num; + + g_return_if_fail (E_IS_COMP_EDITOR (comp_editor)); + g_return_if_fail (E_IS_COMP_EDITOR_PAGE (page)); + + page_num = gtk_notebook_page_num (comp_editor->priv->content, GTK_WIDGET (page)); + g_return_if_fail (page_num != -1); + + gtk_notebook_set_current_page (comp_editor->priv->content, page_num); +} + +/* Unref returned pointer when done with it. */ +static EAlert * +e_comp_editor_add_alert (ECompEditor *comp_editor, + const gchar *alert_id, + const gchar *primary_text, + const gchar *secondary_text) +{ + EAlert *alert; + + g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), NULL); + g_return_val_if_fail (alert_id != NULL, NULL); + g_return_val_if_fail (primary_text != NULL || secondary_text != NULL, NULL); + + alert = e_alert_new (alert_id, + primary_text ? primary_text : "", + secondary_text ? secondary_text : "", + NULL); + + e_alert_bar_add_alert (comp_editor->priv->alert_bar, alert); + + return alert; +} + +/* Unref returned pointer when done with it. */ +EAlert * +e_comp_editor_add_information (ECompEditor *comp_editor, + const gchar *primary_text, + const gchar *secondary_text) +{ + return e_comp_editor_add_alert (comp_editor, "calendar:comp-editor-information", primary_text, secondary_text); +} + +/* Unref returned pointer when done with it. */ +EAlert * +e_comp_editor_add_warning (ECompEditor *comp_editor, + const gchar *primary_text, + const gchar *secondary_text) +{ + return e_comp_editor_add_alert (comp_editor, "calendar:comp-editor-warning", primary_text, secondary_text); +} + +/* Unref returned pointer when done with it. */ +EAlert * +e_comp_editor_add_error (ECompEditor *comp_editor, + const gchar *primary_text, + const gchar *secondary_text) +{ + return e_comp_editor_add_alert (comp_editor, "calendar:comp-editor-error", primary_text, secondary_text); +} + + +static gboolean +ece_check_start_before_end (ECompEditor *comp_editor, + struct icaltimetype *start_tt, + struct icaltimetype *end_tt, + gboolean adjust_end_time) +{ + struct icaltimetype end_tt_copy; + icaltimezone *start_zone, *end_zone; + gint duration = -1; + gint cmp; + + if ((e_comp_editor_get_flags (comp_editor) & E_COMP_EDITOR_FLAG_IS_NEW) == 0) { + icalcomponent *icomp; + + icomp = e_comp_editor_get_component (comp_editor); + if (icomp && + icalcomponent_get_first_property (icomp, ICAL_DTSTART_PROPERTY) && + (icalcomponent_get_first_property (icomp, ICAL_DTEND_PROPERTY) || + icalcomponent_get_first_property (icomp, ICAL_DUE_PROPERTY))) { + struct icaltimetype orig_start, orig_end; + + orig_start = icalcomponent_get_dtstart (icomp); + orig_end = icalcomponent_get_dtend (icomp); + + if (icaltime_is_valid_time (orig_start) && + icaltime_is_valid_time (orig_end)) { + duration = icaltime_as_timet (orig_end) - icaltime_as_timet (orig_start); + } + } + } + + start_zone = (icaltimezone *) start_tt->zone; + end_zone = (icaltimezone *) end_tt->zone; + + /* Convert the end time to the same timezone as the start time. */ + end_tt_copy = *end_tt; + + if (start_zone && end_zone && start_zone != end_zone) + icaltimezone_convert_time (&end_tt_copy, end_zone, start_zone); + + /* Now check if the start time is after the end time. If it is, + * we need to modify one of the times. */ + cmp = icaltime_compare (*start_tt, end_tt_copy); + if (cmp > 0) { + if (adjust_end_time) { + /* Try to switch only the date */ + end_tt->year = start_tt->year; + end_tt->month = start_tt->month; + end_tt->day = start_tt->day; + + end_tt_copy = *end_tt; + if (start_zone && end_zone && start_zone != end_zone) + icaltimezone_convert_time (&end_tt_copy, end_zone, start_zone); + + if (duration > 0) + icaltime_adjust (&end_tt_copy, 0, 0, 0, -duration); + + if (icaltime_compare (*start_tt, end_tt_copy) >= 0) { + *end_tt = *start_tt; + + if (duration >= 0) { + icaltime_adjust (end_tt, 0, 0, 0, duration); + } else { + /* Modify the end time, to be the start + 1 hour/day. */ + icaltime_adjust (end_tt, 0, start_tt->is_date ? 24 : 1, 0, 0); + } + + if (start_zone && end_zone && start_zone != end_zone) + icaltimezone_convert_time (end_tt, start_zone, end_zone); + } + } else { + /* Try to switch only the date */ + start_tt->year = end_tt->year; + start_tt->month = end_tt->month; + start_tt->day = end_tt->day; + + if (icaltime_compare (*start_tt, end_tt_copy) >= 0) { + *start_tt = *end_tt; + + if (duration >= 0) { + icaltime_adjust (start_tt, 0, 0, 0, -duration); + } else { + /* Modify the start time, to be the end - 1 hour/day. */ + icaltime_adjust (start_tt, 0, start_tt->is_date ? -24 : -1, 0, 0); + } + + if (start_zone && end_zone && start_zone != end_zone) + icaltimezone_convert_time (start_tt, end_zone, start_zone); + } + } + + return TRUE; + } + + return FALSE; +} + +void +e_comp_editor_ensure_start_before_end (ECompEditor *comp_editor, + ECompEditorPropertyPart *start_datetime, + ECompEditorPropertyPart *end_datetime, + gboolean change_end_datetime) +{ + ECompEditorPropertyPartDatetime *start_dtm, *end_dtm; + struct icaltimetype start_tt, end_tt; + gboolean set_dtstart = FALSE, set_dtend = FALSE; + + g_return_if_fail (E_IS_COMP_EDITOR (comp_editor)); + g_return_if_fail (E_IS_COMP_EDITOR_PROPERTY_PART_DATETIME (start_datetime)); + g_return_if_fail (E_IS_COMP_EDITOR_PROPERTY_PART_DATETIME (end_datetime)); + + start_dtm = E_COMP_EDITOR_PROPERTY_PART_DATETIME (start_datetime); + end_dtm = E_COMP_EDITOR_PROPERTY_PART_DATETIME (end_datetime); + + start_tt = e_comp_editor_property_part_datetime_get_value (start_dtm); + end_tt = e_comp_editor_property_part_datetime_get_value (end_dtm); + + if (icaltime_is_null_time (start_tt) || + icaltime_is_null_time (end_tt) || + !icaltime_is_valid_time (start_tt) || + !icaltime_is_valid_time (end_tt)) + return; + + if (start_tt.is_date || end_tt.is_date) { + /* All Day Events are simple. We just compare the dates and if + * start > end we copy one of them to the other. */ + gint cmp; + + start_tt.is_date = TRUE; + end_tt.is_date = TRUE; + + cmp = icaltime_compare_date_only (start_tt, end_tt); + + if (cmp > 0) { + if (change_end_datetime) { + end_tt = start_tt; + set_dtend = TRUE; + } else { + start_tt = end_tt; + set_dtstart = TRUE; + } + } + } else { + if (ece_check_start_before_end (comp_editor, &start_tt, &end_tt, change_end_datetime)) { + if (change_end_datetime) + set_dtend = TRUE; + else + set_dtstart = TRUE; + } + } + + if (set_dtstart || set_dtend) { + e_comp_editor_set_updating (comp_editor, TRUE); + + if (set_dtstart) + e_comp_editor_property_part_datetime_set_value (start_dtm, start_tt); + + if (set_dtend) + e_comp_editor_property_part_datetime_set_value (end_dtm, end_tt); + + e_comp_editor_set_updating (comp_editor, FALSE); + } +} + +static gboolean +e_comp_editor_holds_component (ECompEditor *comp_editor, + ESource *origin_source, + const icalcomponent *component) +{ + const gchar *component_uid, *editor_uid; + gboolean equal; + + g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), FALSE); + g_return_val_if_fail (component != NULL, FALSE); + + if (!origin_source || !comp_editor->priv->origin_source || + !e_source_equal (origin_source, comp_editor->priv->origin_source)) + return FALSE; + + component_uid = icalcomponent_get_uid ((icalcomponent *) component); + editor_uid = icalcomponent_get_uid (comp_editor->priv->component); + + if (!component_uid || !editor_uid) + return FALSE; + + equal = g_strcmp0 (component_uid, editor_uid) == 0; + if (equal) { + struct icaltimetype component_rid, editor_rid; + + component_rid = icalcomponent_get_recurrenceid ((icalcomponent *) component); + editor_rid = icalcomponent_get_recurrenceid (comp_editor->priv->component); + + if (icaltime_is_null_time (component_rid)) { + equal = icaltime_is_null_time (editor_rid); + } else if (!icaltime_is_null_time (editor_rid)) { + equal = icaltime_compare (component_rid, editor_rid) == 0; + } + } + + return equal; +} + +ECompEditor * +e_comp_editor_open_for_component (GtkWindow *parent, + EShell *shell, + ESource *origin_source, + const icalcomponent *component, + guint32 flags /* bit-or of ECompEditorFlags */) +{ + ECompEditor *comp_editor; + GType comp_editor_type; + + g_return_val_if_fail (E_IS_SHELL (shell), NULL); + if (origin_source) + g_return_val_if_fail (E_IS_SOURCE (origin_source), NULL); + g_return_val_if_fail (component != NULL, NULL); + + comp_editor = e_comp_editor_find_existing_for (origin_source, component); + if (comp_editor) { + gtk_window_present (GTK_WINDOW (comp_editor)); + return comp_editor; + } + + switch (icalcomponent_isa (component)) { + case ICAL_VEVENT_COMPONENT: + comp_editor_type = E_TYPE_COMP_EDITOR_EVENT; + break; + case ICAL_VTODO_COMPONENT: + comp_editor_type = E_TYPE_COMP_EDITOR_TASK; + break; + case ICAL_VJOURNAL_COMPONENT: + comp_editor_type = E_TYPE_COMP_EDITOR_MEMO; + break; + default: + g_warn_if_reached (); + return NULL; + } + + comp_editor = g_object_new (comp_editor_type, + "shell", shell, + "origin-source", origin_source, + "component", component, + "flags", flags, + NULL); + + opened_editors = g_slist_prepend (opened_editors, comp_editor); + + gtk_widget_show (GTK_WIDGET (comp_editor)); + + return comp_editor; +} + +ECompEditor * +e_comp_editor_find_existing_for (ESource *origin_source, + const icalcomponent *component) +{ + ECompEditor *comp_editor; + GSList *link; + + if (origin_source) + g_return_val_if_fail (E_IS_SOURCE (origin_source), NULL); + g_return_val_if_fail (component != NULL, NULL); + + for (link = opened_editors; link; link = g_slist_next (link)) { + comp_editor = link->data; + + if (!comp_editor) + continue; + + if (e_comp_editor_holds_component (comp_editor, origin_source, component)) { + gtk_window_present (GTK_WINDOW (comp_editor)); + return comp_editor; + } + } + + return NULL; +} diff --git a/src/modules/calendar/e-cal-shell-content.c b/src/modules/calendar/e-cal-shell-content.c index 87ed166..69c4e72 100644 --- a/src/modules/calendar/e-cal-shell-content.c +++ b/src/modules/calendar/e-cal-shell-content.c @@ -847,9 +847,9 @@ cal_shell_content_get_attendee_prop (icalcomponent *icalcomp, while (prop != NULL) { const gchar *attendee; - attendee = icalproperty_get_attendee (prop); + attendee = itip_strip_mailto (icalproperty_get_attendee (prop)); - if (g_str_equal (itip_strip_mailto (attendee), address)) + if (attendee && g_ascii_strcasecmp (attendee, address) == 0) return prop; prop = icalcomponent_get_next_property ( diff --git a/src/modules/calendar/e-cal-shell-content.c.crash-empty-attendee b/src/modules/calendar/e-cal-shell-content.c.crash-empty-attendee new file mode 100644 index 0000000..87ed166 --- /dev/null +++ b/src/modules/calendar/e-cal-shell-content.c.crash-empty-attendee @@ -0,0 +1,2338 @@ +/* + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * Copyright (C) 2014 Red Hat, Inc. (www.redhat.com) + * + * 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. + * + * 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 this program; if not, see . + */ + +#include "evolution-config.h" + +#include +#include +#include + +#include "calendar/gui/calendar-config.h" +#include "calendar/gui/calendar-view.h" +#include "calendar/gui/comp-util.h" +#include "calendar/gui/e-cal-list-view.h" +#include "calendar/gui/e-cal-model-calendar.h" +#include "calendar/gui/e-cal-model-memos.h" +#include "calendar/gui/e-cal-model-tasks.h" +#include "calendar/gui/e-calendar-view.h" +#include "calendar/gui/e-day-view.h" +#include "calendar/gui/e-month-view.h" +#include "calendar/gui/e-week-view.h" +#include "calendar/gui/itip-utils.h" +#include "calendar/gui/tag-calendar.h" + +#include "e-cal-base-shell-sidebar.h" +#include "e-cal-shell-view-private.h" +#include "e-cal-shell-content.h" + +#define E_CAL_SHELL_CONTENT_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_CAL_SHELL_CONTENT, ECalShellContentPrivate)) + +struct _ECalShellContentPrivate { + GtkWidget *hpaned; + GtkWidget *vpaned; + + GtkWidget *calendar_notebook; + GtkWidget *task_table; + ECalModel *task_model; + ECalDataModel *task_data_model; + + GtkWidget *memo_table; + ECalModel *memo_model; + ECalDataModel *memo_data_model; + + ETagCalendar *tag_calendar; + gulong datepicker_selection_changed_id; + gulong datepicker_range_moved_id; + + ECalViewKind current_view; + ECalendarView *views[E_CAL_VIEW_KIND_LAST]; + GDate view_start, view_end; + guint32 view_start_range_day_offset; + GDate last_range_start; /* because "date-range-changed" can be emit with no real change */ + + time_t previous_selected_start_time; + time_t previous_selected_end_time; + + gulong current_view_id_changed_id; +}; + +enum { + PROP_0, + PROP_CALENDAR_NOTEBOOK, + PROP_MEMO_TABLE, + PROP_TASK_TABLE, + PROP_CURRENT_VIEW_ID, + PROP_CURRENT_VIEW +}; + +/* Used to indicate who has the focus within the calendar view. */ +typedef enum { + FOCUS_CALENDAR_NOTEBOOK, + FOCUS_MEMO_TABLE, + FOCUS_TASK_TABLE, + FOCUS_OTHER +} FocusLocation; + +G_DEFINE_DYNAMIC_TYPE (ECalShellContent, e_cal_shell_content, E_TYPE_CAL_BASE_SHELL_CONTENT) + +static time_t +convert_to_local_zone (time_t tm, + icaltimezone *from_zone) +{ + struct icaltimetype tt; + + tt = icaltime_from_timet_with_zone (tm, FALSE, from_zone); + return icaltime_as_timet (tt); +} + +static void +cal_shell_content_update_model_and_current_view_times (ECalShellContent *cal_shell_content, + ECalModel *model, + ECalendarItem *calitem, + time_t view_start_tt, + time_t view_end_tt, + const GDate *view_start, + const GDate *view_end) +{ + ECalendarView *current_view; + EDayView *day_view = NULL; + gint day_view_selection_start_day = -1, day_view_selection_end_day = -1; + gint day_view_selection_start_row = -1, day_view_selection_end_row = -1; + gdouble day_view_scrollbar_position = 0.0; + gint syy, smm, sdd, eyy, emm, edd; + time_t visible_range_start, visible_range_end; + gboolean filters_updated = FALSE; + icaltimezone *zone; + gchar *cal_filter; + + g_return_if_fail (E_IS_CAL_SHELL_CONTENT (cal_shell_content)); + g_return_if_fail (E_IS_CAL_MODEL (model)); + g_return_if_fail (E_IS_CALENDAR_ITEM (calitem)); + + current_view = e_cal_shell_content_get_current_calendar_view (cal_shell_content); + g_return_if_fail (current_view != NULL); + + zone = e_cal_model_get_timezone (model); + cal_filter = e_cal_data_model_dup_filter (e_cal_model_get_data_model (model)); + + if (E_IS_DAY_VIEW (current_view)) { + GtkAdjustment *adjustment; + + day_view = E_DAY_VIEW (current_view); + day_view_selection_start_day = day_view->selection_start_day; + day_view_selection_end_day = day_view->selection_end_day; + day_view_selection_start_row = day_view->selection_start_row; + day_view_selection_end_row = day_view->selection_end_row; + + adjustment = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (day_view->main_canvas)); + day_view_scrollbar_position = gtk_adjustment_get_value (adjustment); + } + + g_signal_handler_block (calitem, cal_shell_content->priv->datepicker_range_moved_id); + g_signal_handler_block (calitem, cal_shell_content->priv->datepicker_selection_changed_id); + + visible_range_start = view_start_tt; + visible_range_end = view_end_tt; + + e_calendar_view_precalc_visible_time_range (current_view, view_start_tt, view_end_tt, &visible_range_start, &visible_range_end); + if (view_start_tt != visible_range_start || view_end_tt != visible_range_end) { + time_t cmp_range_start = convert_to_local_zone (visible_range_start, zone); + time_t cmp_range_end = convert_to_local_zone (visible_range_end, zone); + + if (view_start_tt != cmp_range_start || + view_end_tt != cmp_range_end - 1) { + /* Calendar views update their inner time range during e_cal_model_set_time_range() call, + while they can change it if needed (like a clamp of a week view with a week start day + not being Monday */ + GDate new_view_start, new_view_end; + + /* Midnight means the next day, which is not desired here */ + cmp_range_end--; + visible_range_end--; + + /* These times are in the correct zone already */ + time_to_gdate_with_zone (&new_view_start, cmp_range_start, NULL); + time_to_gdate_with_zone (&new_view_end, cmp_range_end, NULL); + + e_calendar_item_set_selection (calitem, &new_view_start, &new_view_end); + e_cal_shell_content_update_filters (cal_shell_content, cal_filter, visible_range_start, visible_range_end); + e_calendar_view_set_selected_time_range (current_view, cmp_range_start, cmp_range_start); + filters_updated = TRUE; + view_start_tt = cmp_range_start; + view_end_tt = cmp_range_end; + } + } + + if (!filters_updated) { + e_calendar_item_set_selection (calitem, view_start, view_end); + e_cal_shell_content_update_filters (cal_shell_content, cal_filter, view_start_tt, view_end_tt); + e_calendar_view_set_selected_time_range (current_view, view_start_tt, view_start_tt); + } + + if (day_view && day_view_selection_start_day != -1 && day_view_selection_end_day != -1 && + day_view_selection_start_row != -1 && day_view_selection_end_row != -1) { + GtkAdjustment *adjustment; + + day_view->selection_start_day = day_view_selection_start_day; + day_view->selection_end_day = day_view_selection_end_day; + day_view->selection_start_row = day_view_selection_start_row; + day_view->selection_end_row = day_view_selection_end_row; + + /* This is better than e_day_view_ensure_rows_visible(), because it keeps both + selection and the exact scrollbar position in the main canvas, which may not + always correspond to each other. */ + adjustment = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (day_view->main_canvas)); + gtk_adjustment_set_value (adjustment, day_view_scrollbar_position); + } + + gtk_widget_queue_draw (GTK_WIDGET (current_view)); + + g_free (cal_filter); + + g_signal_handler_unblock (calitem, cal_shell_content->priv->datepicker_range_moved_id); + g_signal_handler_unblock (calitem, cal_shell_content->priv->datepicker_selection_changed_id); + + if (e_calendar_item_get_date_range (calitem, &syy, &smm, &sdd, &eyy, &emm, &edd)) { + GDate range_start; + + g_date_set_dmy (&range_start, sdd, smm + 1, syy); + + cal_shell_content->priv->view_start_range_day_offset = + g_date_get_julian (&cal_shell_content->priv->view_start) - g_date_get_julian (&range_start); + } +} + +static void +e_cal_shell_content_change_view (ECalShellContent *cal_shell_content, + ECalViewKind to_view, + const GDate *view_start, + const GDate *view_end, + gboolean force_change) +{ + EShellSidebar *shell_sidebar; + EShellView *shell_view; + ECalendar *calendar; + ECalModel *model; + icaltimezone *zone; + time_t view_start_tt, view_end_tt; + gboolean view_changed = FALSE; + gint selected_days; + + g_return_if_fail (E_IS_CAL_SHELL_CONTENT (cal_shell_content)); + g_return_if_fail (to_view >= E_CAL_VIEW_KIND_DAY && to_view < E_CAL_VIEW_KIND_LAST); + g_return_if_fail (view_start != NULL); + g_return_if_fail (g_date_valid (view_start)); + g_return_if_fail (view_end != NULL); + g_return_if_fail (g_date_valid (view_end)); + + shell_view = e_shell_content_get_shell_view (E_SHELL_CONTENT (cal_shell_content)); + shell_sidebar = e_shell_view_get_shell_sidebar (shell_view); + g_return_if_fail (E_IS_CAL_BASE_SHELL_SIDEBAR (shell_sidebar)); + + calendar = e_cal_base_shell_sidebar_get_date_navigator (E_CAL_BASE_SHELL_SIDEBAR (shell_sidebar)); + g_return_if_fail (E_IS_CALENDAR (calendar)); + + model = e_cal_base_shell_content_get_model (E_CAL_BASE_SHELL_CONTENT (cal_shell_content)); + zone = e_cal_model_get_timezone (model); + view_start_tt = cal_comp_gdate_to_timet (view_start, zone); + view_end_tt = cal_comp_gdate_to_timet (view_end, zone); + + if (to_view != cal_shell_content->priv->current_view) { + g_signal_handler_block (cal_shell_content, cal_shell_content->priv->current_view_id_changed_id); + e_cal_shell_content_set_current_view_id (cal_shell_content, to_view); + g_signal_handler_unblock (cal_shell_content, cal_shell_content->priv->current_view_id_changed_id); + view_changed = TRUE; + } + + selected_days = g_date_get_julian (view_end) - g_date_get_julian (view_start) + 1; + + if (cal_shell_content->priv->current_view == E_CAL_VIEW_KIND_DAY) { + EDayView *day_view; + + day_view = E_DAY_VIEW (cal_shell_content->priv->views[E_CAL_VIEW_KIND_DAY]); + e_day_view_set_days_shown (day_view, selected_days); + } else if (cal_shell_content->priv->current_view == E_CAL_VIEW_KIND_MONTH) { + EWeekView *month_view; + + month_view = E_WEEK_VIEW (cal_shell_content->priv->views[E_CAL_VIEW_KIND_MONTH]); + e_week_view_set_weeks_shown (month_view, selected_days / 7); + } + + if (!force_change && + g_date_valid (&cal_shell_content->priv->view_start) && + g_date_valid (&cal_shell_content->priv->view_end) && + g_date_compare (&cal_shell_content->priv->view_start, view_start) == 0 && + g_date_compare (&cal_shell_content->priv->view_end, view_end) == 0) { + ECalendarItem *calitem = e_calendar_get_item (calendar); + + if (view_changed) + cal_shell_content_update_model_and_current_view_times ( + cal_shell_content, model, calitem, view_start_tt, view_end_tt, view_start, view_end); + + g_signal_handler_block (calitem, cal_shell_content->priv->datepicker_range_moved_id); + g_signal_handler_block (calitem, cal_shell_content->priv->datepicker_selection_changed_id); + + e_calendar_item_set_selection (calitem, view_start, view_end); + + g_signal_handler_unblock (calitem, cal_shell_content->priv->datepicker_range_moved_id); + g_signal_handler_unblock (calitem, cal_shell_content->priv->datepicker_selection_changed_id); + + return; + } + + cal_shell_content->priv->view_start = *view_start; + cal_shell_content->priv->view_end = *view_end; + + cal_shell_content_update_model_and_current_view_times ( + cal_shell_content, model, e_calendar_get_item (calendar), view_start_tt, view_end_tt, view_start, view_end); +} + +static void +cal_shell_content_clamp_for_whole_weeks (GDateWeekday week_start_day, + GDate *sel_start, + GDate *sel_end, + gboolean saturday_as_sunday) +{ + GDateWeekday wday; + guint32 julian_start, julian_end; + + g_return_if_fail (sel_start != NULL); + g_return_if_fail (sel_end != NULL); + + wday = g_date_get_weekday (sel_start); + + /* This is because the month/week view doesn't split weekends */ + if (saturday_as_sunday && wday == G_DATE_SATURDAY && week_start_day == G_DATE_SUNDAY) + wday = G_DATE_SUNDAY; + + if (week_start_day > wday) { + g_date_subtract_days (sel_start, wday); + wday = g_date_get_weekday (sel_start); + } + + if (week_start_day < wday) + g_date_subtract_days (sel_start, wday - week_start_day); + + julian_start = g_date_get_julian (sel_start); + julian_end = g_date_get_julian (sel_end); + + if (((julian_end - julian_start + 1) % 7) != 0) + g_date_add_days (sel_end, 7 - ((julian_end - julian_start + 1) % 7)); + + julian_end = g_date_get_julian (sel_end); + + /* Can show only up to 6 weeks */ + if ((julian_end - julian_start + 1) / 7 > 6) { + *sel_end = *sel_start; + g_date_add_days (sel_end, (7 * 6) - 1); + } + + if (g_date_compare (sel_start, sel_end) == 0) + g_date_add_days (sel_end, 6); +} + +static gboolean +cal_shell_content_weekday_within (GDateWeekday start_wday, + GDateWeekday end_wday, + GDateWeekday test_wday) +{ + gint ii; + + if (start_wday <= end_wday) + return start_wday <= test_wday && test_wday <= end_wday; + + for (ii = 0; ii < 7; ii++) { + if (start_wday == test_wday) + return TRUE; + + if (start_wday == end_wday) + break; + + start_wday = e_weekday_get_next (start_wday); + } + + return FALSE; +} + +static void +cal_shell_content_change_selection_in_current_view (ECalShellContent *cal_shell_content, + time_t sel_start_tt, + time_t sel_end_tt, + icaltimezone *zone) +{ + g_return_if_fail (E_IS_CAL_SHELL_CONTENT (cal_shell_content)); + + if (cal_shell_content->priv->current_view >= E_CAL_VIEW_KIND_DAY && + cal_shell_content->priv->current_view < E_CAL_VIEW_KIND_LAST) { + ECalendarView *view; + + view = cal_shell_content->priv->views[cal_shell_content->priv->current_view]; + + /* Preserve selected time (change only date) for these views */ + if (cal_shell_content->priv->current_view == E_CAL_VIEW_KIND_DAY || + cal_shell_content->priv->current_view == E_CAL_VIEW_KIND_WORKWEEK) { + time_t current_sel_start = (time_t) -1, current_sel_end = (time_t) -1; + + if (e_calendar_view_get_selected_time_range (view, ¤t_sel_start, ¤t_sel_end)) { + current_sel_start = icaltime_as_timet_with_zone (icaltime_from_timet_with_zone (current_sel_start, 0, zone), NULL); + current_sel_end = icaltime_as_timet_with_zone (icaltime_from_timet_with_zone (current_sel_end, 0, zone), NULL); + + sel_start_tt += current_sel_start % (24 * 60 * 60); + sel_end_tt += current_sel_end % (24 * 60 * 60); + } + } + + e_calendar_view_set_selected_time_range (view, sel_start_tt, sel_end_tt); + } +} + +static void +cal_shell_content_datepicker_selection_changed_cb (ECalendarItem *calitem, + ECalShellContent *cal_shell_content) +{ + GDate sel_start, sel_end; + guint32 selected_days, start_julian, end_julian; + icaltimezone *zone; + time_t sel_start_tt, sel_end_tt; + + g_return_if_fail (E_IS_CAL_SHELL_CONTENT (cal_shell_content)); + g_return_if_fail (E_IS_CALENDAR_ITEM (calitem)); + + g_date_clear (&sel_start, 1); + g_date_clear (&sel_end, 1); + + e_calendar_item_get_selection (calitem, &sel_start, &sel_end); + + start_julian = g_date_get_julian (&sel_start); + end_julian = g_date_get_julian (&sel_end); + + g_return_if_fail (start_julian <= end_julian); + + if (g_date_compare (&cal_shell_content->priv->view_start, &sel_start) == 0 && + g_date_compare (&cal_shell_content->priv->view_end, &sel_end) == 0) { + /* No change in the selection range */ + return; + } + + zone = e_cal_model_get_timezone (e_cal_base_shell_content_get_model (E_CAL_BASE_SHELL_CONTENT (cal_shell_content))); + sel_start_tt = cal_comp_gdate_to_timet (&sel_start, zone); + sel_end_tt = cal_comp_gdate_to_timet (&sel_end, zone); + + selected_days = end_julian - start_julian + 1; + if (selected_days == 1) { + GDateWeekday sel_start_wday, sel_end_wday, cur_start_wday, cur_end_wday; + + /* Clicked inside currently selected view range; do not do anything, + just make sure the days are selected again */ + if (g_date_compare (&cal_shell_content->priv->view_start, &sel_start) <= 0 && + g_date_compare (&sel_start, &cal_shell_content->priv->view_end) <= 0) { + sel_start = cal_shell_content->priv->view_start; + sel_end = cal_shell_content->priv->view_end; + + e_calendar_item_set_selection (calitem, &sel_start, &sel_end); + + cal_shell_content_change_selection_in_current_view (cal_shell_content, sel_start_tt, sel_end_tt, zone); + return; + } + + sel_start_wday = g_date_get_weekday (&sel_start); + sel_end_wday = g_date_get_weekday (&sel_end); + cur_start_wday = g_date_get_weekday (&cal_shell_content->priv->view_start); + cur_end_wday = g_date_get_weekday (&cal_shell_content->priv->view_end); + + if ((cal_shell_content->priv->current_view == E_CAL_VIEW_KIND_WORKWEEK || + (cal_shell_content->priv->current_view == E_CAL_VIEW_KIND_DAY && + e_day_view_get_days_shown (E_DAY_VIEW (cal_shell_content->priv->views[E_CAL_VIEW_KIND_DAY])) != 1)) && + cal_shell_content_weekday_within (cur_start_wday, cur_end_wday, sel_start_wday)) { + if (cur_start_wday < sel_start_wday) { + g_date_subtract_days (&sel_start, sel_start_wday - cur_start_wday); + } else if (cur_start_wday > sel_start_wday) { + g_date_subtract_days (&sel_start, 7 - (cur_start_wday - sel_start_wday)); + } + sel_end = sel_start; + if (cal_shell_content->priv->current_view == E_CAL_VIEW_KIND_DAY) + g_date_add_days (&sel_end, e_day_view_get_days_shown (E_DAY_VIEW (cal_shell_content->priv->views[E_CAL_VIEW_KIND_DAY])) - 1); + else + g_date_add_days (&sel_end, e_day_view_get_days_shown (E_DAY_VIEW (cal_shell_content->priv->views[E_CAL_VIEW_KIND_WORKWEEK])) - 1); + + e_cal_shell_content_change_view (cal_shell_content, cal_shell_content->priv->current_view, &sel_start, &sel_end, FALSE); + } else if (cal_shell_content->priv->current_view == E_CAL_VIEW_KIND_WEEK && + cal_shell_content_weekday_within (cur_start_wday, cur_end_wday, sel_start_wday) && + cal_shell_content_weekday_within (cur_start_wday, cur_end_wday, sel_end_wday)) { + if (cur_start_wday < sel_start_wday) + g_date_subtract_days (&sel_start, sel_start_wday - cur_start_wday); + sel_end = sel_start; + cal_shell_content_clamp_for_whole_weeks (calitem->week_start_day, &sel_start, &sel_end, TRUE); + + e_cal_shell_content_change_view (cal_shell_content, E_CAL_VIEW_KIND_WEEK, &sel_start, &sel_end, FALSE); + } else if (cal_shell_content->priv->current_view == E_CAL_VIEW_KIND_MONTH || + cal_shell_content->priv->current_view == E_CAL_VIEW_KIND_LIST) { + /* whole month */ + g_date_set_day (&sel_start, 1); + sel_end = sel_start; + g_date_set_day (&sel_end, g_date_get_days_in_month (g_date_get_month (&sel_start), g_date_get_year (&sel_start)) - 1); + cal_shell_content_clamp_for_whole_weeks (calitem->week_start_day, &sel_start, &sel_end, cal_shell_content->priv->current_view == E_CAL_VIEW_KIND_MONTH); + + e_cal_shell_content_change_view (cal_shell_content, cal_shell_content->priv->current_view, &sel_start, &sel_end, FALSE); + } else if (cal_shell_content->priv->current_view == E_CAL_VIEW_KIND_WORKWEEK) { + cal_shell_content_clamp_for_whole_weeks (calitem->week_start_day, &sel_start, &sel_end, TRUE); + e_cal_shell_content_change_view (cal_shell_content, E_CAL_VIEW_KIND_WEEK, &sel_start, &sel_end, FALSE); + } else { + e_cal_shell_content_change_view (cal_shell_content, E_CAL_VIEW_KIND_DAY, &sel_start, &sel_end, FALSE); + } + + cal_shell_content_change_selection_in_current_view (cal_shell_content, sel_start_tt, sel_end_tt, zone); + } else if (selected_days < 7) { + GDateWeekday first_work_wday; + + first_work_wday = e_cal_model_get_work_day_first (e_cal_base_shell_content_get_model (E_CAL_BASE_SHELL_CONTENT (cal_shell_content))); + + if (cal_shell_content->priv->current_view == E_CAL_VIEW_KIND_WORKWEEK && + first_work_wday == g_date_get_weekday (&sel_start) && + selected_days == e_day_view_get_days_shown (E_DAY_VIEW (cal_shell_content->priv->views[E_CAL_VIEW_KIND_WORKWEEK]))) + e_cal_shell_content_change_view (cal_shell_content, E_CAL_VIEW_KIND_WORKWEEK, &sel_start, &sel_end, FALSE); + else + e_cal_shell_content_change_view (cal_shell_content, E_CAL_VIEW_KIND_DAY, &sel_start, &sel_end, FALSE); + } else if (selected_days == 7) { + GDateWeekday sel_start_wday; + + sel_start_wday = g_date_get_weekday (&sel_start); + + if (sel_start_wday == calitem->week_start_day && + cal_shell_content->priv->current_view == E_CAL_VIEW_KIND_DAY && + e_day_view_get_days_shown (E_DAY_VIEW (cal_shell_content->priv->views[E_CAL_VIEW_KIND_DAY])) == 7) { + e_cal_shell_content_change_view (cal_shell_content, E_CAL_VIEW_KIND_DAY, &sel_start, &sel_end, FALSE); + } else { + e_cal_shell_content_change_view (cal_shell_content, E_CAL_VIEW_KIND_WEEK, &sel_start, &sel_end, FALSE); + } + } else { + if (cal_shell_content->priv->current_view == E_CAL_VIEW_KIND_LIST) { + /* whole month */ + g_date_set_day (&sel_start, 1); + sel_end = sel_start; + g_date_set_day (&sel_end, g_date_get_days_in_month (g_date_get_month (&sel_start), g_date_get_year (&sel_start))); + cal_shell_content_clamp_for_whole_weeks (calitem->week_start_day, &sel_start, &sel_end, FALSE); + + e_cal_shell_content_change_view (cal_shell_content, E_CAL_VIEW_KIND_LIST, &sel_start, &sel_end, FALSE); + } else { + cal_shell_content_clamp_for_whole_weeks (calitem->week_start_day, &sel_start, &sel_end, + cal_shell_content->priv->current_view == E_CAL_VIEW_KIND_MONTH || cal_shell_content->priv->current_view == E_CAL_VIEW_KIND_WEEK); + e_cal_shell_content_change_view (cal_shell_content, E_CAL_VIEW_KIND_MONTH, &sel_start, &sel_end, FALSE); + } + } +} + +static void +cal_shell_content_datepicker_range_moved_cb (ECalendarItem *calitem, + ECalShellContent *cal_shell_content) +{ + gint start_year, start_month, start_day, end_year, end_month, end_day; + GDate sel_start_date, sel_end_date, range_start_date; + + g_return_if_fail (E_IS_CALENDAR_ITEM (calitem)); + g_return_if_fail (E_IS_CAL_SHELL_CONTENT (cal_shell_content)); + + if (!e_calendar_item_get_date_range (calitem, &start_year, &start_month, &start_day, &end_year, &end_month, &end_day)) + return; + + g_date_set_dmy (&range_start_date, start_day, start_month + 1, start_year); + + if (g_date_valid (&cal_shell_content->priv->last_range_start) && + g_date_compare (&cal_shell_content->priv->last_range_start, &range_start_date) == 0) { + return; + } + + cal_shell_content->priv->last_range_start = range_start_date; + + g_date_clear (&sel_start_date, 1); + g_date_clear (&sel_end_date, 1); + + if (cal_shell_content->priv->view_start_range_day_offset == (guint32) -1) { + sel_start_date = cal_shell_content->priv->view_start; + sel_end_date = cal_shell_content->priv->view_end; + cal_shell_content->priv->view_start_range_day_offset = + g_date_get_julian (&cal_shell_content->priv->view_start) - g_date_get_julian (&range_start_date); + } else { + gint view_days; + + view_days = g_date_get_julian (&cal_shell_content->priv->view_end) - g_date_get_julian (&cal_shell_content->priv->view_start); + + sel_start_date = range_start_date; + g_date_add_days (&sel_start_date, cal_shell_content->priv->view_start_range_day_offset); + + sel_end_date = sel_start_date; + g_date_add_days (&sel_end_date, view_days); + } + + g_signal_handler_block (calitem, cal_shell_content->priv->datepicker_range_moved_id); + + e_calendar_item_set_selection (calitem, &sel_start_date, &sel_end_date); + + g_signal_handler_unblock (calitem, cal_shell_content->priv->datepicker_range_moved_id); +} + +static gboolean +cal_shell_content_datepicker_button_press_cb (ECalendar *calendar, + GdkEvent *event, + ECalShellContent *cal_shell_content) +{ + g_return_val_if_fail (E_IS_CAL_SHELL_CONTENT (cal_shell_content), FALSE); + + if (!event) + return FALSE; + + if (event->type == GDK_2BUTTON_PRESS) { + ECalendarItem *calitem = e_calendar_get_item (calendar); + GDate sel_start, sel_end; + + g_date_clear (&sel_start, 1); + g_date_clear (&sel_end, 1); + + e_calendar_item_get_selection (calitem, &sel_start, &sel_end); + + /* Switch to a day view on a double-click */ + e_cal_shell_content_change_view (cal_shell_content, E_CAL_VIEW_KIND_DAY, &sel_start, &sel_start, FALSE); + } + + return FALSE; +} + +static void +cal_shell_content_current_view_id_changed_cb (ECalShellContent *cal_shell_content) +{ + GDate sel_start, sel_end; + GDateWeekday work_day_first, week_start_day; + ECalModel *model; + gint ii; + + g_return_if_fail (E_IS_CAL_SHELL_CONTENT (cal_shell_content)); + + model = e_cal_base_shell_content_get_model (E_CAL_BASE_SHELL_CONTENT (cal_shell_content)); + work_day_first = e_cal_model_get_work_day_first (model); + week_start_day = e_cal_model_get_week_start_day (model); + + if (cal_shell_content->priv->previous_selected_start_time != -1 && + cal_shell_content->priv->previous_selected_end_time != -1) { + icaltimezone *zone; + + zone = e_cal_model_get_timezone (model); + time_to_gdate_with_zone (&sel_start, cal_shell_content->priv->previous_selected_start_time, zone); + time_to_gdate_with_zone (&sel_end, cal_shell_content->priv->previous_selected_end_time, zone); + } else { + sel_start = cal_shell_content->priv->view_start; + sel_end = cal_shell_content->priv->view_end; + } + + switch (cal_shell_content->priv->current_view) { + case E_CAL_VIEW_KIND_DAY: + /* Left the start & end being the current view start */ + sel_end = sel_start; + break; + case E_CAL_VIEW_KIND_WORKWEEK: + cal_shell_content_clamp_for_whole_weeks (week_start_day, &sel_start, &sel_end, FALSE); + ii = 0; + while (g_date_get_weekday (&sel_start) != work_day_first && ii < 7) { + g_date_add_days (&sel_start, 1); + ii++; + } + + sel_end = sel_start; + g_date_add_days (&sel_end, e_day_view_get_days_shown (E_DAY_VIEW (cal_shell_content->priv->views[E_CAL_VIEW_KIND_WORKWEEK])) - 1); + break; + case E_CAL_VIEW_KIND_WEEK: + sel_end = sel_start; + cal_shell_content_clamp_for_whole_weeks (week_start_day, &sel_start, &sel_end, TRUE); + break; + case E_CAL_VIEW_KIND_MONTH: + case E_CAL_VIEW_KIND_LIST: + if (g_date_get_day (&sel_start) != 1 && + (g_date_get_julian (&sel_end) - g_date_get_julian (&sel_start) + 1) / 7 >= 3 && + g_date_get_month (&sel_start) != g_date_get_month (&sel_end)) { + g_date_set_day (&sel_start, 1); + g_date_add_months (&sel_start, 1); + } else { + g_date_set_day (&sel_start, 1); + } + sel_end = sel_start; + g_date_add_months (&sel_end, 1); + g_date_subtract_days (&sel_end, 1); + cal_shell_content_clamp_for_whole_weeks (week_start_day, &sel_start, &sel_end, cal_shell_content->priv->current_view == E_CAL_VIEW_KIND_MONTH); + break; + default: + g_warn_if_reached (); + return; + } + + /* Ensure a change */ + e_cal_shell_content_change_view (cal_shell_content, cal_shell_content->priv->current_view, &sel_start, &sel_end, TRUE); + + /* Try to preserve selection between the views */ + if (cal_shell_content->priv->previous_selected_start_time != -1 && + cal_shell_content->priv->previous_selected_end_time != -1) { + if (cal_shell_content->priv->current_view >= E_CAL_VIEW_KIND_DAY && + cal_shell_content->priv->current_view < E_CAL_VIEW_KIND_LAST) { + ECalendarView *cal_view = cal_shell_content->priv->views[cal_shell_content->priv->current_view]; + + e_calendar_view_set_selected_time_range (cal_view, + cal_shell_content->priv->previous_selected_start_time, + cal_shell_content->priv->previous_selected_end_time); + } + } + + cal_shell_content->priv->previous_selected_start_time = -1; + cal_shell_content->priv->previous_selected_end_time = -1; +} + +static void +cal_shell_content_display_view_cb (ECalShellContent *cal_shell_content, + GalView *gal_view) +{ + ECalViewKind view_kind; + GType gal_view_type; + + gal_view_type = G_OBJECT_TYPE (gal_view); + + if (gal_view_type == GAL_TYPE_VIEW_ETABLE) { + ECalendarView *calendar_view; + + view_kind = E_CAL_VIEW_KIND_LIST; + calendar_view = cal_shell_content->priv->views[view_kind]; + gal_view_etable_attach_table ( + GAL_VIEW_ETABLE (gal_view), + E_CAL_LIST_VIEW (calendar_view)->table); + + } else if (gal_view_type == GAL_TYPE_VIEW_CALENDAR_DAY) { + view_kind = E_CAL_VIEW_KIND_DAY; + + } else if (gal_view_type == GAL_TYPE_VIEW_CALENDAR_WORK_WEEK) { + view_kind = E_CAL_VIEW_KIND_WORKWEEK; + + } else if (gal_view_type == GAL_TYPE_VIEW_CALENDAR_WEEK) { + view_kind = E_CAL_VIEW_KIND_WEEK; + + } else if (gal_view_type == GAL_TYPE_VIEW_CALENDAR_MONTH) { + view_kind = E_CAL_VIEW_KIND_MONTH; + + } else { + g_return_if_reached (); + } + + e_cal_shell_content_set_current_view_id (cal_shell_content, view_kind); +} + +static void +cal_shell_content_notify_view_id_cb (ECalShellContent *cal_shell_content) +{ + EShellContent *shell_content; + EShellView *shell_view; + GSettings *settings; + GtkWidget *paned; + const gchar *key; + const gchar *view_id; + + settings = e_util_ref_settings ("org.gnome.evolution.calendar"); + paned = cal_shell_content->priv->hpaned; + + shell_content = E_SHELL_CONTENT (cal_shell_content); + shell_view = e_shell_content_get_shell_view (shell_content); + view_id = e_shell_view_get_view_id (shell_view); + + if (view_id != NULL && strcmp (view_id, "Month_View") == 0) + key = "month-hpane-position"; + else + key = "hpane-position"; + + g_settings_unbind (paned, "hposition"); + + g_settings_bind ( + settings, key, + paned, "hposition", + G_SETTINGS_BIND_DEFAULT); + + g_object_unref (settings); +} + +static void +cal_shell_content_is_editing_changed_cb (gpointer cal_view_tasks_memos_table, + GParamSpec *param, + EShellView *shell_view) +{ + g_return_if_fail (E_IS_SHELL_VIEW (shell_view)); + + e_shell_view_update_actions (shell_view); +} + +static gchar * +cal_shell_content_get_pad_state_filename (EShellContent *shell_content, + ETable *table) +{ + EShellBackend *shell_backend; + EShellView *shell_view; + const gchar *config_dir, *nick = NULL; + + g_return_val_if_fail (shell_content != NULL, NULL); + g_return_val_if_fail (E_IS_SHELL_CONTENT (shell_content), NULL); + g_return_val_if_fail (table != NULL, NULL); + g_return_val_if_fail (E_IS_TABLE (table), NULL); + + if (E_IS_TASK_TABLE (table)) + nick = "TaskPad"; + else if (E_IS_MEMO_TABLE (table)) + nick = "MemoPad"; + + g_return_val_if_fail (nick != NULL, NULL); + + shell_view = e_shell_content_get_shell_view (shell_content); + shell_backend = e_shell_view_get_shell_backend (shell_view); + config_dir = e_shell_backend_get_config_dir (shell_backend); + + return g_build_filename (config_dir, nick, NULL); +} + +static void +cal_shell_content_save_table_state (EShellContent *shell_content, + ETable *table) +{ + gchar *filename; + + filename = cal_shell_content_get_pad_state_filename ( + shell_content, table); + g_return_if_fail (filename != NULL); + + e_table_save_state (table, filename); + g_free (filename); +} + +static void +cal_shell_content_load_table_state (EShellContent *shell_content, + ETable *table) +{ + gchar *filename; + + filename = cal_shell_content_get_pad_state_filename (shell_content, table); + g_return_if_fail (filename != NULL); + + e_table_load_state (table, filename); + g_free (filename); +} + +static icalproperty * +cal_shell_content_get_attendee_prop (icalcomponent *icalcomp, + const gchar *address) +{ + icalproperty *prop; + + if (address == NULL || *address == '\0') + return NULL; + + prop = icalcomponent_get_first_property ( + icalcomp, ICAL_ATTENDEE_PROPERTY); + + while (prop != NULL) { + const gchar *attendee; + + attendee = icalproperty_get_attendee (prop); + + if (g_str_equal (itip_strip_mailto (attendee), address)) + return prop; + + prop = icalcomponent_get_next_property ( + icalcomp, ICAL_ATTENDEE_PROPERTY); + } + + return NULL; +} + +static gboolean +cal_shell_content_icalcomp_is_delegated (icalcomponent *icalcomp, + const gchar *user_email) +{ + icalproperty *prop; + icalparameter *param; + const gchar *delto = NULL; + gboolean is_delegated = FALSE; + + prop = cal_shell_content_get_attendee_prop (icalcomp, user_email); + + if (prop != NULL) { + param = icalproperty_get_first_parameter ( + prop, ICAL_DELEGATEDTO_PARAMETER); + if (param != NULL) { + delto = icalparameter_get_delegatedto (param); + delto = itip_strip_mailto (delto); + } + } else + return FALSE; + + prop = cal_shell_content_get_attendee_prop (icalcomp, delto); + + if (prop != NULL) { + const gchar *delfrom = NULL; + icalparameter_partstat status = ICAL_PARTSTAT_NONE; + + param = icalproperty_get_first_parameter ( + prop, ICAL_DELEGATEDFROM_PARAMETER); + if (param != NULL) { + delfrom = icalparameter_get_delegatedfrom (param); + delfrom = itip_strip_mailto (delfrom); + } + param = icalproperty_get_first_parameter ( + prop, ICAL_PARTSTAT_PARAMETER); + if (param != NULL) + status = icalparameter_get_partstat (param); + is_delegated = + (status != ICAL_PARTSTAT_DECLINED) && + (g_strcmp0 (delfrom, user_email) == 0); + } + + return is_delegated; +} + +static guint32 +cal_shell_content_check_state (EShellContent *shell_content) +{ + EShell *shell; + EShellView *shell_view; + EShellBackend *shell_backend; + ESourceRegistry *registry; + ECalShellContent *cal_shell_content; + ECalendarView *calendar_view; + gboolean selection_is_editable = FALSE; + gboolean selection_is_instance = FALSE; + gboolean selection_is_meeting = FALSE; + gboolean selection_is_organizer = FALSE; + gboolean selection_is_recurring = FALSE; + gboolean selection_can_delegate = FALSE; + guint32 state = 0; + GList *selected; + GList *link; + guint n_selected; + + cal_shell_content = E_CAL_SHELL_CONTENT (shell_content); + + shell_view = e_shell_content_get_shell_view (shell_content); + shell_backend = e_shell_view_get_shell_backend (shell_view); + shell = e_shell_backend_get_shell (shell_backend); + registry = e_shell_get_registry (shell); + + calendar_view = e_cal_shell_content_get_current_calendar_view (cal_shell_content); + + selected = e_calendar_view_get_selected_events (calendar_view); + n_selected = g_list_length (selected); + + /* If we have a selection, assume it's + * editable until we learn otherwise. */ + if (n_selected > 0) + selection_is_editable = TRUE; + + for (link = selected; link != NULL; link = g_list_next (link)) { + ECalendarViewEvent *event = link->data; + ECalClient *client; + ECalComponent *comp; + gchar *user_email; + icalcomponent *icalcomp; + const gchar *capability; + gboolean cap_delegate_supported; + gboolean cap_delegate_to_many; + gboolean icalcomp_is_delegated; + gboolean read_only; + + if (!is_comp_data_valid (event)) + continue; + + client = event->comp_data->client; + icalcomp = event->comp_data->icalcomp; + + read_only = e_client_is_readonly (E_CLIENT (client)); + selection_is_editable &= !read_only; + + selection_is_instance |= + e_cal_util_component_is_instance (icalcomp); + + selection_is_meeting = + (n_selected == 1) && + e_cal_util_component_has_attendee (icalcomp); + + selection_is_recurring |= + e_cal_util_component_is_instance (icalcomp) || + e_cal_util_component_has_recurrences (icalcomp); + + /* XXX The rest of this is rather expensive and + * only applies if a single event is selected, + * so continue with the loop iteration if the + * rest of this is not applicable. */ + if (n_selected > 1) + continue; + + /* XXX This probably belongs in comp-util.c. */ + + comp = e_cal_component_new (); + e_cal_component_set_icalcomponent ( + comp, icalcomponent_new_clone (icalcomp)); + user_email = itip_get_comp_attendee ( + registry, comp, client); + + selection_is_organizer = + e_cal_util_component_has_organizer (icalcomp) && + itip_organizer_is_user (registry, comp, client); + + capability = CAL_STATIC_CAPABILITY_DELEGATE_SUPPORTED; + cap_delegate_supported = + e_client_check_capability ( + E_CLIENT (client), capability); + + capability = CAL_STATIC_CAPABILITY_DELEGATE_TO_MANY; + cap_delegate_to_many = + e_client_check_capability ( + E_CLIENT (client), capability); + + icalcomp_is_delegated = + (user_email != NULL) && + cal_shell_content_icalcomp_is_delegated ( + icalcomp, user_email); + + selection_can_delegate = + cap_delegate_supported && + (cap_delegate_to_many || + (!selection_is_organizer && + !icalcomp_is_delegated)); + + g_free (user_email); + g_object_unref (comp); + } + + g_list_free (selected); + + if (n_selected == 1) + state |= E_CAL_BASE_SHELL_CONTENT_SELECTION_SINGLE; + if (n_selected > 1) + state |= E_CAL_BASE_SHELL_CONTENT_SELECTION_MULTIPLE; + if (selection_is_editable) + state |= E_CAL_BASE_SHELL_CONTENT_SELECTION_IS_EDITABLE; + if (selection_is_instance) + state |= E_CAL_BASE_SHELL_CONTENT_SELECTION_IS_INSTANCE; + if (selection_is_meeting) + state |= E_CAL_BASE_SHELL_CONTENT_SELECTION_IS_MEETING; + if (selection_is_organizer) + state |= E_CAL_BASE_SHELL_CONTENT_SELECTION_IS_ORGANIZER; + if (selection_is_recurring) + state |= E_CAL_BASE_SHELL_CONTENT_SELECTION_IS_RECURRING; + if (selection_can_delegate) + state |= E_CAL_BASE_SHELL_CONTENT_SELECTION_CAN_DELEGATE; + + return state; +} + +static void +cal_shell_content_focus_search_results (EShellContent *shell_content) +{ + ECalendarView *calendar_view; + + calendar_view = e_cal_shell_content_get_current_calendar_view (E_CAL_SHELL_CONTENT (shell_content)); + + gtk_widget_grab_focus (GTK_WIDGET (calendar_view)); +} + +static time_t +cal_shell_content_get_default_time (ECalModel *model, + gpointer user_data) +{ + ECalShellContent *cal_shell_content = user_data; + icaltimezone *zone; + + g_return_val_if_fail (model != NULL, 0); + g_return_val_if_fail (E_IS_CAL_SHELL_CONTENT (cal_shell_content), 0); + + if (e_cal_shell_content_get_current_view_id (cal_shell_content) != E_CAL_VIEW_KIND_LIST) { + ECalendarView *cal_view; + time_t selected_start = (time_t) 0, selected_end = (time_t) 0; + + cal_view = e_cal_shell_content_get_current_calendar_view (cal_shell_content); + + if (cal_view && e_calendar_view_get_selected_time_range (cal_view, &selected_start, &selected_end)) + return selected_start; + } + + zone = e_cal_model_get_timezone (model); + + return icaltime_as_timet_with_zone (icaltime_current_time_with_zone (zone), zone); +} + +static void +update_adjustment (ECalShellContent *cal_shell_content, + GtkAdjustment *adjustment, + EWeekView *week_view, + gboolean move_by_week) +{ + GDate start_date, end_date; + GDate first_day_shown; + ECalModel *model; + gint week_offset; + struct icaltimetype start_tt = icaltime_null_time (); + time_t lower; + guint32 old_first_day_julian, new_first_day_julian; + icaltimezone *timezone; + gdouble value; + + e_week_view_get_first_day_shown (week_view, &first_day_shown); + + /* If we don't have a valid date set yet, just return. */ + if (!g_date_valid (&first_day_shown)) + return; + + value = gtk_adjustment_get_value (adjustment); + + /* Determine the first date shown. */ + start_date = week_view->base_date; + week_offset = floor (value + 0.5); + g_date_add_days (&start_date, week_offset * 7); + + /* Convert the old & new first days shown to julian values. */ + old_first_day_julian = g_date_get_julian (&first_day_shown); + new_first_day_julian = g_date_get_julian (&start_date); + + /* If we are already showing the date, just return. */ + if (old_first_day_julian == new_first_day_julian) + return; + + /* Convert it to a time_t. */ + start_tt.year = g_date_get_year (&start_date); + start_tt.month = g_date_get_month (&start_date); + start_tt.day = g_date_get_day (&start_date); + + model = e_cal_base_shell_content_get_model (E_CAL_BASE_SHELL_CONTENT (cal_shell_content)); + timezone = e_cal_model_get_timezone (model); + lower = icaltime_as_timet_with_zone (start_tt, timezone); + + end_date = start_date; + if (move_by_week) { + g_date_add_days (&end_date, 7 - 1); + } else { + g_date_add_days (&end_date, 7 * e_week_view_get_weeks_shown (week_view) - 1); + } + + e_week_view_set_update_base_date (week_view, FALSE); + e_cal_shell_content_change_view (cal_shell_content, cal_shell_content->priv->current_view, &start_date, &end_date, FALSE); + e_calendar_view_set_selected_time_range (E_CALENDAR_VIEW (week_view), lower, lower); + e_week_view_set_update_base_date (week_view, TRUE); +} + +static void +week_view_adjustment_changed_cb (GtkAdjustment *adjustment, + ECalShellContent *cal_shell_content) +{ + ECalendarView *view; + + g_return_if_fail (E_IS_CAL_SHELL_CONTENT (cal_shell_content)); + + view = cal_shell_content->priv->views[E_CAL_VIEW_KIND_WEEK]; + update_adjustment (cal_shell_content, adjustment, E_WEEK_VIEW (view), TRUE); +} + +static void +month_view_adjustment_changed_cb (GtkAdjustment *adjustment, + ECalShellContent *cal_shell_content) +{ + ECalendarView *view; + + g_return_if_fail (E_IS_CAL_SHELL_CONTENT (cal_shell_content)); + + view = cal_shell_content->priv->views[E_CAL_VIEW_KIND_MONTH]; + update_adjustment (cal_shell_content, adjustment, E_WEEK_VIEW (view), FALSE); +} + +static void +cal_shell_content_notify_work_day_cb (ECalModel *model, + GParamSpec *param, + ECalShellContent *cal_shell_content) +{ + GDateWeekday work_day_first, work_day_last; + + g_return_if_fail (E_IS_CAL_MODEL (model)); + g_return_if_fail (E_IS_CAL_SHELL_CONTENT (cal_shell_content)); + + if (cal_shell_content->priv->current_view != E_CAL_VIEW_KIND_WORKWEEK) + return; + + work_day_first = e_cal_model_get_work_day_first (model); + work_day_last = e_cal_model_get_work_day_last (model); + + if (work_day_first == g_date_get_weekday (&cal_shell_content->priv->view_start) && + work_day_last == g_date_get_weekday (&cal_shell_content->priv->view_end)) + return; + + cal_shell_content->priv->previous_selected_start_time = -1; + cal_shell_content->priv->previous_selected_end_time = -1; + + /* This makes sure that the selection in the datepicker corresponds + to the time range used in the Work Week view */ + cal_shell_content_current_view_id_changed_cb (cal_shell_content); +} + +static void +cal_shell_content_notify_week_start_day_cb (ECalModel *model, + GParamSpec *param, + ECalShellContent *cal_shell_content) +{ + g_return_if_fail (E_IS_CAL_MODEL (model)); + g_return_if_fail (E_IS_CAL_SHELL_CONTENT (cal_shell_content)); + + cal_shell_content->priv->previous_selected_start_time = -1; + cal_shell_content->priv->previous_selected_end_time = -1; + + /* This makes sure that the selection in the datepicker corresponds + to the time range used in the current view */ + cal_shell_content_current_view_id_changed_cb (cal_shell_content); +} + +static void +cal_shell_content_move_view_range_cb (ECalendarView *cal_view, + ECalendarViewMoveType move_type, + gint64 exact_date, + ECalShellContent *cal_shell_content) +{ + g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view)); + g_return_if_fail (E_IS_CAL_SHELL_CONTENT (cal_shell_content)); + + if (!cal_view->in_focus) + return; + + e_cal_shell_content_move_view_range (cal_shell_content, move_type, (time_t) exact_date); +} + +static void +cal_shell_content_foreign_client_opened_cb (ECalBaseShellSidebar *cal_base_shell_sidebar, + ECalClient *client, + ECalModel *model) +{ + g_return_if_fail (E_IS_CAL_CLIENT (client)); + g_return_if_fail (E_IS_CAL_MODEL (model)); + + e_cal_data_model_add_client (e_cal_model_get_data_model (model), client); +} + +static void +cal_shell_content_foreign_client_closed_cb (ECalBaseShellSidebar *cal_base_shell_sidebar, + ESource *source, + ECalModel *model) +{ + g_return_if_fail (E_IS_SOURCE (source)); + g_return_if_fail (E_IS_CAL_MODEL (model)); + + e_cal_data_model_remove_client (e_cal_model_get_data_model (model), e_source_get_uid (source)); +} + +static void +cal_shell_content_setup_foreign_sources (EShellWindow *shell_window, + const gchar *view_name, + const gchar *extension_name, + ECalModel *model) +{ + EShellSidebar *foreign_sidebar; + EShellContent *foreign_content; + EShellView *foreign_view; + ECalModel *foreign_model; + gboolean is_new_view; + + g_return_if_fail (E_IS_SHELL_WINDOW (shell_window)); + g_return_if_fail (E_IS_CAL_MODEL (model)); + + is_new_view = e_shell_window_peek_shell_view (shell_window, view_name) == NULL; + + foreign_view = e_shell_window_get_shell_view (shell_window, view_name); + g_return_if_fail (E_IS_SHELL_VIEW (foreign_view)); + + foreign_sidebar = e_shell_view_get_shell_sidebar (foreign_view); + g_return_if_fail (E_IS_CAL_BASE_SHELL_SIDEBAR (foreign_sidebar)); + + if (is_new_view) { + /* Preselect default source, when the view was not created yet */ + ESourceSelector *source_selector; + ESourceRegistry *registry; + ESource *source; + + source_selector = e_cal_base_shell_sidebar_get_selector (E_CAL_BASE_SHELL_SIDEBAR (foreign_sidebar)); + registry = e_source_selector_get_registry (source_selector); + source = e_source_registry_ref_default_for_extension_name (registry, extension_name); + + if (source) + e_source_selector_set_primary_selection (source_selector, source); + + g_clear_object (&source); + } + + g_signal_connect_object (foreign_sidebar, "client-opened", + G_CALLBACK (cal_shell_content_foreign_client_opened_cb), model, 0); + g_signal_connect_object (foreign_sidebar, "client-closed", + G_CALLBACK (cal_shell_content_foreign_client_closed_cb), model, 0); + + foreign_content = e_shell_view_get_shell_content (foreign_view); + foreign_model = e_cal_base_shell_content_get_model (E_CAL_BASE_SHELL_CONTENT (foreign_content)); + + e_binding_bind_property ( + foreign_model, "default-source-uid", + model, "default-source-uid", + G_BINDING_SYNC_CREATE); + + g_signal_connect_object (model, "row-appended", + G_CALLBACK (e_cal_base_shell_view_model_row_appended), foreign_view, G_CONNECT_SWAPPED); + + /* This makes sure that the local models for memos and tasks + in the calendar view get populated with the same sources + as those in the respective views. */ + + e_cal_base_shell_sidebar_ensure_sources_open (E_CAL_BASE_SHELL_SIDEBAR (foreign_sidebar)); +} + +static void +cal_shell_content_view_created (ECalBaseShellContent *cal_base_shell_content) +{ + ECalShellContent *cal_shell_content; + EShellView *shell_view; + EShellWindow *shell_window; + EShellSidebar *shell_sidebar; + GalViewInstance *view_instance; + ECalendar *calendar; + ECalModel *model; + ECalDataModel *data_model; + GDate date; + time_t today; + + cal_shell_content = E_CAL_SHELL_CONTENT (cal_base_shell_content); + cal_shell_content->priv->current_view = E_CAL_VIEW_KIND_DAY; + + today = time (NULL); + g_date_clear (&date, 1); + g_date_set_time_t (&date, today); + + shell_view = e_shell_content_get_shell_view (E_SHELL_CONTENT (cal_shell_content)); + shell_window = e_shell_view_get_shell_window (shell_view); + shell_sidebar = e_shell_view_get_shell_sidebar (shell_view); + g_return_if_fail (E_IS_CAL_BASE_SHELL_SIDEBAR (shell_sidebar)); + + calendar = e_cal_base_shell_sidebar_get_date_navigator (E_CAL_BASE_SHELL_SIDEBAR (shell_sidebar)); + g_return_if_fail (E_IS_CALENDAR (calendar)); + + model = e_cal_base_shell_content_get_model (E_CAL_BASE_SHELL_CONTENT (cal_shell_content)); + e_calendar_item_set_selection (e_calendar_get_item (calendar), &date, &date); + e_cal_model_set_time_range (model, today, today); + + /* Show everything known by default in the task and memo pads */ + e_cal_model_set_time_range (cal_shell_content->priv->memo_model, 0, 0); + e_cal_model_set_time_range (cal_shell_content->priv->task_model, 0, 0); + + cal_shell_content->priv->datepicker_selection_changed_id = + g_signal_connect (e_calendar_get_item (calendar), "selection-changed", + G_CALLBACK (cal_shell_content_datepicker_selection_changed_cb), cal_shell_content); + cal_shell_content->priv->datepicker_range_moved_id = + g_signal_connect (e_calendar_get_item (calendar), "date-range-moved", + G_CALLBACK (cal_shell_content_datepicker_range_moved_cb), cal_shell_content); + + g_signal_connect_after (calendar, "button-press-event", + G_CALLBACK (cal_shell_content_datepicker_button_press_cb), cal_shell_content); + + data_model = e_cal_base_shell_content_get_data_model (E_CAL_BASE_SHELL_CONTENT (cal_shell_content)); + cal_shell_content->priv->tag_calendar = e_tag_calendar_new (calendar); + e_tag_calendar_subscribe (cal_shell_content->priv->tag_calendar, data_model); + + /* Intentionally not using e_signal_connect_notify() here, no need to filter + out "false" notifications, it's dealt with them in another way */ + cal_shell_content->priv->current_view_id_changed_id = g_signal_connect ( + cal_shell_content, "notify::current-view-id", + G_CALLBACK (cal_shell_content_current_view_id_changed_cb), NULL); + + /* List of selected Task/Memo sources is taken from respective views, + which are loaded if necessary. */ + cal_shell_content_setup_foreign_sources (shell_window, "memos", E_SOURCE_EXTENSION_MEMO_LIST, + cal_shell_content->priv->memo_model); + + cal_shell_content_setup_foreign_sources (shell_window, "tasks", E_SOURCE_EXTENSION_TASK_LIST, + cal_shell_content->priv->task_model); + + /* Finally load the view instance */ + view_instance = e_shell_view_get_view_instance (shell_view); + gal_view_instance_load (view_instance); + + /* Keep the toolbar view buttons in sync with the calendar. */ + e_binding_bind_property ( + cal_shell_content, "current-view-id", + ACTION (CALENDAR_VIEW_DAY), "current-value", + G_BINDING_BIDIRECTIONAL | + G_BINDING_SYNC_CREATE); + + e_signal_connect_notify ( + model, "notify::work-day-monday", + G_CALLBACK (cal_shell_content_notify_work_day_cb), cal_shell_content); + + e_signal_connect_notify ( + model, "notify::work-day-tuesday", + G_CALLBACK (cal_shell_content_notify_work_day_cb), cal_shell_content); + + e_signal_connect_notify ( + model, "notify::work-day-wednesday", + G_CALLBACK (cal_shell_content_notify_work_day_cb), cal_shell_content); + + e_signal_connect_notify ( + model, "notify::work-day-thursday", + G_CALLBACK (cal_shell_content_notify_work_day_cb), cal_shell_content); + + e_signal_connect_notify ( + model, "notify::work-day-friday", + G_CALLBACK (cal_shell_content_notify_work_day_cb), cal_shell_content); + + e_signal_connect_notify ( + model, "notify::work-day-saturday", + G_CALLBACK (cal_shell_content_notify_work_day_cb), cal_shell_content); + + e_signal_connect_notify ( + model, "notify::work-day-sunday", + G_CALLBACK (cal_shell_content_notify_work_day_cb), cal_shell_content); + + e_signal_connect_notify ( + model, "notify::week-start-day", + G_CALLBACK (cal_shell_content_notify_week_start_day_cb), cal_shell_content); +} + +static void +e_cal_shell_content_create_calendar_views (ECalShellContent *cal_shell_content) +{ + EShellView *shell_view; + ECalModel *model; + ECalendarView *calendar_view; + GtkAdjustment *adjustment; + time_t today; + gint ii; + + g_return_if_fail (E_IS_CAL_SHELL_CONTENT (cal_shell_content)); + g_return_if_fail (cal_shell_content->priv->calendar_notebook != NULL); + g_return_if_fail (cal_shell_content->priv->views[0] == NULL); + + model = e_cal_base_shell_content_get_model (E_CAL_BASE_SHELL_CONTENT (cal_shell_content)); + + /* Day View */ + calendar_view = e_day_view_new (model); + cal_shell_content->priv->views[E_CAL_VIEW_KIND_DAY] = calendar_view; + g_object_ref_sink (calendar_view); + + /* Work Week View */ + calendar_view = e_day_view_new (model); + e_day_view_set_work_week_view (E_DAY_VIEW (calendar_view), TRUE); + e_day_view_set_days_shown (E_DAY_VIEW (calendar_view), 5); + cal_shell_content->priv->views[E_CAL_VIEW_KIND_WORKWEEK] = calendar_view; + g_object_ref_sink (calendar_view); + + /* Week View */ + calendar_view = e_week_view_new (model); + cal_shell_content->priv->views[E_CAL_VIEW_KIND_WEEK] = calendar_view; + g_object_ref_sink (calendar_view); + + adjustment = gtk_range_get_adjustment ( + GTK_RANGE (E_WEEK_VIEW (calendar_view)->vscrollbar)); + g_signal_connect ( + adjustment, "value-changed", + G_CALLBACK (week_view_adjustment_changed_cb), cal_shell_content); + + /* Month View */ + calendar_view = e_month_view_new (model); + e_week_view_set_multi_week_view (E_WEEK_VIEW (calendar_view), TRUE); + e_week_view_set_weeks_shown (E_WEEK_VIEW (calendar_view), 6); + cal_shell_content->priv->views[E_CAL_VIEW_KIND_MONTH] = calendar_view; + g_object_ref_sink (calendar_view); + + adjustment = gtk_range_get_adjustment ( + GTK_RANGE (E_WEEK_VIEW (calendar_view)->vscrollbar)); + g_signal_connect ( + adjustment, "value-changed", + G_CALLBACK (month_view_adjustment_changed_cb), cal_shell_content); + + /* List View */ + calendar_view = e_cal_list_view_new (model); + cal_shell_content->priv->views[E_CAL_VIEW_KIND_LIST] = calendar_view; + g_object_ref_sink (calendar_view); + + shell_view = e_shell_content_get_shell_view (E_SHELL_CONTENT (cal_shell_content)); + today = time (NULL); + + for (ii = 0; ii < E_CAL_VIEW_KIND_LAST; ii++) { + calendar_view = cal_shell_content->priv->views[ii]; + + calendar_view->in_focus = ii == cal_shell_content->priv->current_view; + + e_calendar_view_set_selected_time_range (calendar_view, today, today); + + e_signal_connect_notify ( + calendar_view, "notify::is-editing", + G_CALLBACK (cal_shell_content_is_editing_changed_cb), shell_view); + + g_signal_connect ( + calendar_view, "move-view-range", + G_CALLBACK (cal_shell_content_move_view_range_cb), cal_shell_content); + + gtk_notebook_append_page ( + GTK_NOTEBOOK (cal_shell_content->priv->calendar_notebook), + GTK_WIDGET (calendar_view), NULL); + gtk_widget_show (GTK_WIDGET (calendar_view)); + } +} + +static void +cal_shell_content_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_CURRENT_VIEW_ID: + e_cal_shell_content_set_current_view_id (E_CAL_SHELL_CONTENT (object), + g_value_get_int (value)); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +cal_shell_content_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_CALENDAR_NOTEBOOK: + g_value_set_object ( + value, e_cal_shell_content_get_calendar_notebook ( + E_CAL_SHELL_CONTENT (object))); + return; + + case PROP_MEMO_TABLE: + g_value_set_object ( + value, e_cal_shell_content_get_memo_table ( + E_CAL_SHELL_CONTENT (object))); + return; + + case PROP_TASK_TABLE: + g_value_set_object ( + value, e_cal_shell_content_get_task_table ( + E_CAL_SHELL_CONTENT (object))); + return; + + case PROP_CURRENT_VIEW_ID: + g_value_set_int (value, + e_cal_shell_content_get_current_view_id (E_CAL_SHELL_CONTENT (object))); + return; + + case PROP_CURRENT_VIEW: + g_value_set_object (value, + e_cal_shell_content_get_current_calendar_view (E_CAL_SHELL_CONTENT (object))); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +cal_shell_content_dispose (GObject *object) +{ + ECalShellContent *cal_shell_content = E_CAL_SHELL_CONTENT (object); + gint ii; + + if (cal_shell_content->priv->task_data_model) { + e_cal_data_model_set_disposing (cal_shell_content->priv->task_data_model, TRUE); + e_cal_data_model_unsubscribe (cal_shell_content->priv->task_data_model, + E_CAL_DATA_MODEL_SUBSCRIBER (cal_shell_content->priv->task_model)); + } + + if (cal_shell_content->priv->memo_data_model) { + e_cal_data_model_set_disposing (cal_shell_content->priv->memo_data_model, TRUE); + e_cal_data_model_unsubscribe (cal_shell_content->priv->memo_data_model, + E_CAL_DATA_MODEL_SUBSCRIBER (cal_shell_content->priv->memo_model)); + } + + if (cal_shell_content->priv->tag_calendar) { + ECalDataModel *data_model; + + data_model = e_cal_base_shell_content_get_data_model (E_CAL_BASE_SHELL_CONTENT (cal_shell_content)); + e_cal_data_model_set_disposing (data_model, TRUE); + e_tag_calendar_unsubscribe (cal_shell_content->priv->tag_calendar, data_model); + g_clear_object (&cal_shell_content->priv->tag_calendar); + } + + for (ii = 0; ii < E_CAL_VIEW_KIND_LAST; ii++) { + g_clear_object (&(cal_shell_content->priv->views[ii])); + } + + g_clear_object (&cal_shell_content->priv->hpaned); + g_clear_object (&cal_shell_content->priv->vpaned); + g_clear_object (&cal_shell_content->priv->calendar_notebook); + g_clear_object (&cal_shell_content->priv->task_table); + g_clear_object (&cal_shell_content->priv->task_model); + g_clear_object (&cal_shell_content->priv->task_data_model); + g_clear_object (&cal_shell_content->priv->memo_table); + g_clear_object (&cal_shell_content->priv->memo_model); + g_clear_object (&cal_shell_content->priv->memo_data_model); + + /* Chain up to parent's dispose() method. */ + G_OBJECT_CLASS (e_cal_shell_content_parent_class)->dispose (object); +} + +static void +cal_shell_content_constructed (GObject *object) +{ + ECalShellContent *cal_shell_content; + EShellContent *shell_content; + EShellView *shell_view; + EShellWindow *shell_window; + EShell *shell; + GalViewInstance *view_instance; + GSettings *settings; + GtkWidget *container; + GtkWidget *widget; + gchar *markup; + + /* Chain up to parent's constructed() method. */ + G_OBJECT_CLASS (e_cal_shell_content_parent_class)->constructed (object); + + cal_shell_content = E_CAL_SHELL_CONTENT (object); + shell_content = E_SHELL_CONTENT (cal_shell_content); + shell_view = e_shell_content_get_shell_view (shell_content); + shell_window = e_shell_view_get_shell_window (shell_view); + shell = e_shell_window_get_shell (shell_window); + + cal_shell_content->priv->memo_data_model = + e_cal_base_shell_content_create_new_data_model (E_CAL_BASE_SHELL_CONTENT (cal_shell_content)); + cal_shell_content->priv->memo_model = + e_cal_model_memos_new (cal_shell_content->priv->memo_data_model, e_shell_get_registry (shell), shell); + + cal_shell_content->priv->task_data_model = + e_cal_base_shell_content_create_new_data_model (E_CAL_BASE_SHELL_CONTENT (cal_shell_content)); + cal_shell_content->priv->task_model = + e_cal_model_tasks_new (cal_shell_content->priv->task_data_model, e_shell_get_registry (shell), shell); + + e_binding_bind_property ( + cal_shell_content->priv->memo_model, "timezone", + cal_shell_content->priv->memo_data_model, "timezone", + G_BINDING_SYNC_CREATE); + + e_binding_bind_property ( + cal_shell_content->priv->task_model, "timezone", + cal_shell_content->priv->task_data_model, "timezone", + G_BINDING_SYNC_CREATE); + + /* Build content widgets. */ + + container = GTK_WIDGET (object); + + widget = e_paned_new (GTK_ORIENTATION_HORIZONTAL); + gtk_container_add (GTK_CONTAINER (container), widget); + cal_shell_content->priv->hpaned = g_object_ref (widget); + gtk_widget_show (widget); + + container = cal_shell_content->priv->hpaned; + + widget = gtk_notebook_new (); + gtk_notebook_set_show_tabs (GTK_NOTEBOOK (widget), FALSE); + gtk_notebook_set_show_border (GTK_NOTEBOOK (widget), FALSE); + gtk_paned_pack1 (GTK_PANED (container), widget, TRUE, FALSE); + cal_shell_content->priv->calendar_notebook = g_object_ref (widget); + gtk_widget_show (widget); + + widget = e_paned_new (GTK_ORIENTATION_VERTICAL); + e_paned_set_fixed_resize (E_PANED (widget), FALSE); + gtk_paned_pack2 (GTK_PANED (container), widget, FALSE, TRUE); + cal_shell_content->priv->vpaned = g_object_ref (widget); + gtk_widget_show (widget); + + container = cal_shell_content->priv->calendar_notebook; + + e_cal_shell_content_create_calendar_views (cal_shell_content); + + e_binding_bind_property ( + cal_shell_content, "current-view-id", + cal_shell_content->priv->calendar_notebook, "page", + G_BINDING_SYNC_CREATE); + + container = cal_shell_content->priv->vpaned; + + widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_paned_pack1 (GTK_PANED (container), widget, TRUE, TRUE); + gtk_widget_show (widget); + + container = widget; + + widget = gtk_label_new (NULL); + markup = g_strdup_printf ("%s", _("Tasks")); + gtk_label_set_markup (GTK_LABEL (widget), markup); + gtk_box_pack_start (GTK_BOX (container), widget, FALSE, TRUE, 0); + gtk_widget_show (widget); + g_free (markup); + + widget = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy ( + GTK_SCROLLED_WINDOW (widget), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0); + gtk_widget_show (widget); + + container = widget; + + widget = e_task_table_new (shell_view, cal_shell_content->priv->task_model); + gtk_container_add (GTK_CONTAINER (container), widget); + cal_shell_content->priv->task_table = g_object_ref (widget); + gtk_widget_show (widget); + + cal_shell_content_load_table_state (shell_content, E_TABLE (widget)); + + g_signal_connect_swapped ( + widget, "open-component", + G_CALLBACK (e_cal_shell_view_taskpad_open_task), + shell_view); + + e_signal_connect_notify ( + widget, "notify::is-editing", + G_CALLBACK (cal_shell_content_is_editing_changed_cb), shell_view); + + container = cal_shell_content->priv->vpaned; + + widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_paned_pack2 (GTK_PANED (container), widget, TRUE, TRUE); + gtk_widget_show (widget); + + container = widget; + + widget = gtk_label_new (NULL); + markup = g_strdup_printf ("%s", _("Memos")); + gtk_label_set_markup (GTK_LABEL (widget), markup); + gtk_box_pack_start (GTK_BOX (container), widget, FALSE, TRUE, 0); + gtk_widget_show (widget); + g_free (markup); + + widget = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy ( + GTK_SCROLLED_WINDOW (widget), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0); + gtk_widget_show (widget); + + container = widget; + + widget = e_memo_table_new (shell_view, cal_shell_content->priv->memo_model); + gtk_container_add (GTK_CONTAINER (container), widget); + cal_shell_content->priv->memo_table = g_object_ref (widget); + gtk_widget_show (widget); + + cal_shell_content_load_table_state (shell_content, E_TABLE (widget)); + + e_cal_model_set_default_time_func (cal_shell_content->priv->memo_model, cal_shell_content_get_default_time, cal_shell_content); + + g_signal_connect_swapped ( + widget, "open-component", + G_CALLBACK (e_cal_shell_view_memopad_open_memo), + shell_view); + + e_signal_connect_notify ( + widget, "notify::is-editing", + G_CALLBACK (cal_shell_content_is_editing_changed_cb), shell_view); + + /* Prepare the view instance. */ + + view_instance = e_shell_view_new_view_instance (shell_view, NULL); + g_signal_connect_swapped ( + view_instance, "display-view", + G_CALLBACK (cal_shell_content_display_view_cb), + object); + /* Actual load happens at cal_shell_content_view_created() */ + e_shell_view_set_view_instance (shell_view, view_instance); + g_object_unref (view_instance); + + e_signal_connect_notify_swapped ( + shell_view, "notify::view-id", + G_CALLBACK (cal_shell_content_notify_view_id_cb), + cal_shell_content); + + settings = e_util_ref_settings ("org.gnome.evolution.calendar"); + + g_settings_bind ( + settings, "tag-vpane-position", + cal_shell_content->priv->vpaned, "proportion", + G_SETTINGS_BIND_DEFAULT); + + g_object_unref (settings); + + /* Cannot access shell sidebar here, thus rely on cal_shell_content_view_created() + with exact widget settings which require it */ +} + +static void +e_cal_shell_content_class_init (ECalShellContentClass *class) +{ + GObjectClass *object_class; + EShellContentClass *shell_content_class; + ECalBaseShellContentClass *cal_base_shell_content_class; + + g_type_class_add_private (class, sizeof (ECalShellContentPrivate)); + + object_class = G_OBJECT_CLASS (class); + object_class->set_property = cal_shell_content_set_property; + object_class->get_property = cal_shell_content_get_property; + object_class->dispose = cal_shell_content_dispose; + object_class->constructed = cal_shell_content_constructed; + + shell_content_class = E_SHELL_CONTENT_CLASS (class); + shell_content_class->check_state = cal_shell_content_check_state; + shell_content_class->focus_search_results = cal_shell_content_focus_search_results; + + cal_base_shell_content_class = E_CAL_BASE_SHELL_CONTENT_CLASS (class); + cal_base_shell_content_class->new_cal_model = e_cal_model_calendar_new; + cal_base_shell_content_class->view_created = cal_shell_content_view_created; + + g_object_class_install_property ( + object_class, + PROP_CALENDAR_NOTEBOOK, + g_param_spec_object ( + "calendar-notebook", + NULL, + NULL, + GTK_TYPE_NOTEBOOK, + G_PARAM_READABLE)); + + g_object_class_install_property ( + object_class, + PROP_MEMO_TABLE, + g_param_spec_object ( + "memo-table", + NULL, + NULL, + E_TYPE_MEMO_TABLE, + G_PARAM_READABLE)); + + g_object_class_install_property ( + object_class, + PROP_TASK_TABLE, + g_param_spec_object ( + "task-table", + NULL, + NULL, + E_TYPE_TASK_TABLE, + G_PARAM_READABLE)); + + g_object_class_install_property ( + object_class, + PROP_CURRENT_VIEW_ID, + g_param_spec_int ( + "current-view-id", + "Current Calendar View ID", + NULL, + E_CAL_VIEW_KIND_DAY, + E_CAL_VIEW_KIND_LAST - 1, + E_CAL_VIEW_KIND_DAY, + G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_CURRENT_VIEW, + g_param_spec_object ( + "current-view", + "Current Calendar View", + NULL, + E_TYPE_CALENDAR_VIEW, + G_PARAM_READABLE)); +} + +static void +e_cal_shell_content_class_finalize (ECalShellContentClass *class) +{ +} + +static void +e_cal_shell_content_init (ECalShellContent *cal_shell_content) +{ + time_t now; + + cal_shell_content->priv = E_CAL_SHELL_CONTENT_GET_PRIVATE (cal_shell_content); + g_date_clear (&cal_shell_content->priv->view_start, 1); + g_date_clear (&cal_shell_content->priv->view_end, 1); + g_date_clear (&cal_shell_content->priv->last_range_start, 1); + + now = time (NULL); + g_date_set_time_t (&cal_shell_content->priv->view_start, now); + g_date_set_time_t (&cal_shell_content->priv->view_end, now); + + cal_shell_content->priv->view_start_range_day_offset = (guint32) -1; + cal_shell_content->priv->previous_selected_start_time = -1; + cal_shell_content->priv->previous_selected_end_time = -1; +} + +void +e_cal_shell_content_type_register (GTypeModule *type_module) +{ + /* XXX G_DEFINE_DYNAMIC_TYPE declares a static type registration + * function, so we have to wrap it with a public function in + * order to register types from a separate compilation unit. */ + e_cal_shell_content_register_type (type_module); +} + +GtkWidget * +e_cal_shell_content_new (EShellView *shell_view) +{ + g_return_val_if_fail (E_IS_SHELL_VIEW (shell_view), NULL); + + return g_object_new ( + E_TYPE_CAL_SHELL_CONTENT, + "shell-view", shell_view, NULL); +} + +GtkNotebook * +e_cal_shell_content_get_calendar_notebook (ECalShellContent *cal_shell_content) +{ + g_return_val_if_fail (E_IS_CAL_SHELL_CONTENT (cal_shell_content), NULL); + + return GTK_NOTEBOOK (cal_shell_content->priv->calendar_notebook); +} + +EMemoTable * +e_cal_shell_content_get_memo_table (ECalShellContent *cal_shell_content) +{ + g_return_val_if_fail (E_IS_CAL_SHELL_CONTENT (cal_shell_content), NULL); + + return E_MEMO_TABLE (cal_shell_content->priv->memo_table); +} + +ETaskTable * +e_cal_shell_content_get_task_table (ECalShellContent *cal_shell_content) +{ + g_return_val_if_fail (E_IS_CAL_SHELL_CONTENT (cal_shell_content), NULL); + + return E_TASK_TABLE (cal_shell_content->priv->task_table); +} + +EShellSearchbar * +e_cal_shell_content_get_searchbar (ECalShellContent *cal_shell_content) +{ + EShellView *shell_view; + EShellContent *shell_content; + GtkWidget *widget; + + g_return_val_if_fail (E_IS_CAL_SHELL_CONTENT (cal_shell_content), NULL); + + shell_content = E_SHELL_CONTENT (cal_shell_content); + shell_view = e_shell_content_get_shell_view (shell_content); + widget = e_shell_view_get_searchbar (shell_view); + + return E_SHELL_SEARCHBAR (widget); +} + +static void +cal_shell_content_resubscribe (ECalendarView *cal_view, + ECalModel *model) +{ + ECalDataModel *data_model; + ECalDataModelSubscriber *subscriber; + time_t range_start, range_end; + gboolean is_tasks_or_memos; + + g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view)); + g_return_if_fail (E_IS_CAL_MODEL (model)); + + data_model = e_cal_model_get_data_model (model); + subscriber = E_CAL_DATA_MODEL_SUBSCRIBER (model); + is_tasks_or_memos = e_cal_model_get_component_kind (model) == ICAL_VJOURNAL_COMPONENT || + e_cal_model_get_component_kind (model) == ICAL_VTODO_COMPONENT; + + if ((!is_tasks_or_memos && e_calendar_view_get_visible_time_range (cal_view, &range_start, &range_end)) || + e_cal_data_model_get_subscriber_range (data_model, subscriber, &range_start, &range_end)) { + e_cal_data_model_unsubscribe (data_model, subscriber); + e_cal_model_remove_all_objects (model); + + if (is_tasks_or_memos) + e_cal_data_model_subscribe (data_model, subscriber, range_start, range_end); + } +} + +void +e_cal_shell_content_set_current_view_id (ECalShellContent *cal_shell_content, + ECalViewKind view_kind) +{ + time_t start_time = -1, end_time = -1; + gint ii; + + g_return_if_fail (E_IS_CAL_SHELL_CONTENT (cal_shell_content)); + g_return_if_fail (view_kind >= E_CAL_VIEW_KIND_DAY && view_kind < E_CAL_VIEW_KIND_LAST); + + if (cal_shell_content->priv->current_view == view_kind) + return; + + if (cal_shell_content->priv->current_view >= E_CAL_VIEW_KIND_DAY && + cal_shell_content->priv->current_view < E_CAL_VIEW_KIND_LAST) { + ECalendarView *cal_view = cal_shell_content->priv->views[cal_shell_content->priv->current_view]; + + if (!e_calendar_view_get_selected_time_range (cal_view, &start_time, &end_time)) { + start_time = -1; + end_time = -1; + } + } + + cal_shell_content->priv->previous_selected_start_time = start_time; + cal_shell_content->priv->previous_selected_end_time = end_time; + + for (ii = 0; ii < E_CAL_VIEW_KIND_LAST; ii++) { + ECalendarView *cal_view = cal_shell_content->priv->views[ii]; + gboolean in_focus = ii == view_kind; + gboolean focus_changed; + + if (!cal_view) { + g_warn_if_reached (); + continue; + } + + focus_changed = (cal_view->in_focus ? 1 : 0) != (in_focus ? 1 : 0); + + cal_view->in_focus = in_focus; + + if (focus_changed && in_focus) { + /* Currently focused view changed. Any events within the common time + range are not shown in the newly focused view, thus make sure it'll + contain all what it should have. */ + ECalModel *model; + + model = e_cal_base_shell_content_get_model (E_CAL_BASE_SHELL_CONTENT (cal_shell_content)); + + /* This may not cause any queries to backends with events, + because the time range should be always within the one + shown in the date picker. */ + cal_shell_content_resubscribe (cal_view, model); + + if (cal_shell_content->priv->task_table) { + ETaskTable *task_table; + + task_table = E_TASK_TABLE (cal_shell_content->priv->task_table); + cal_shell_content_resubscribe (cal_view, e_task_table_get_model (task_table)); + } + + if (cal_shell_content->priv->memo_table) { + EMemoTable *memo_table; + + memo_table = E_MEMO_TABLE (cal_shell_content->priv->memo_table); + cal_shell_content_resubscribe (cal_view, e_memo_table_get_model (memo_table)); + } + } + } + + cal_shell_content->priv->current_view = view_kind; + + g_object_notify (G_OBJECT (cal_shell_content), "current-view-id"); + + gtk_widget_queue_draw (GTK_WIDGET (cal_shell_content->priv->views[cal_shell_content->priv->current_view])); +} + +ECalViewKind +e_cal_shell_content_get_current_view_id (ECalShellContent *cal_shell_content) +{ + g_return_val_if_fail (E_IS_CAL_SHELL_CONTENT (cal_shell_content), E_CAL_VIEW_KIND_LAST); + + return cal_shell_content->priv->current_view; +} + +ECalendarView * +e_cal_shell_content_get_calendar_view (ECalShellContent *cal_shell_content, + ECalViewKind view_kind) +{ + g_return_val_if_fail (E_IS_CAL_SHELL_CONTENT (cal_shell_content), NULL); + g_return_val_if_fail (view_kind >= E_CAL_VIEW_KIND_DAY && view_kind < E_CAL_VIEW_KIND_LAST, NULL); + + return cal_shell_content->priv->views[view_kind]; +} + +ECalendarView * +e_cal_shell_content_get_current_calendar_view (ECalShellContent *cal_shell_content) +{ + g_return_val_if_fail (E_IS_CAL_SHELL_CONTENT (cal_shell_content), NULL); + + return e_cal_shell_content_get_calendar_view (cal_shell_content, + e_cal_shell_content_get_current_view_id (cal_shell_content)); +} + +void +e_cal_shell_content_save_state (ECalShellContent *cal_shell_content) +{ + ECalShellContentPrivate *priv; + + g_return_if_fail (cal_shell_content != NULL); + g_return_if_fail (E_IS_CAL_SHELL_CONTENT (cal_shell_content)); + + priv = cal_shell_content->priv; + + if (priv->task_table != NULL) + cal_shell_content_save_table_state ( + E_SHELL_CONTENT (cal_shell_content), + E_TABLE (priv->task_table)); + + if (priv->memo_table != NULL) + cal_shell_content_save_table_state ( + E_SHELL_CONTENT (cal_shell_content), + E_TABLE (priv->memo_table)); +} + +void +e_cal_shell_content_get_current_range (ECalShellContent *cal_shell_content, + time_t *range_start, + time_t *range_end) +{ + icaltimezone *zone; + + g_return_if_fail (E_IS_CAL_SHELL_CONTENT (cal_shell_content)); + g_return_if_fail (range_start != NULL); + g_return_if_fail (range_end != NULL); + + zone = e_cal_data_model_get_timezone (e_cal_base_shell_content_get_data_model ( + E_CAL_BASE_SHELL_CONTENT (cal_shell_content))); + + *range_start = cal_comp_gdate_to_timet (&(cal_shell_content->priv->view_start), zone); + *range_end = cal_comp_gdate_to_timet (&(cal_shell_content->priv->view_end), zone); +} + +void +e_cal_shell_content_get_current_range_dates (ECalShellContent *cal_shell_content, + GDate *range_start, + GDate *range_end) +{ + g_return_if_fail (E_IS_CAL_SHELL_CONTENT (cal_shell_content)); + g_return_if_fail (range_start != NULL); + g_return_if_fail (range_end != NULL); + + *range_start = cal_shell_content->priv->view_start; + *range_end = cal_shell_content->priv->view_end; +} + +static void +cal_shell_content_move_view_range_relative (ECalShellContent *cal_shell_content, + gint direction) +{ + GDate start, end; + + g_return_if_fail (E_IS_CAL_SHELL_CONTENT (cal_shell_content)); + g_return_if_fail (direction != 0); + + start = cal_shell_content->priv->view_start; + end = cal_shell_content->priv->view_end; + + switch (cal_shell_content->priv->current_view) { + case E_CAL_VIEW_KIND_DAY: + if (direction > 0) { + g_date_add_days (&start, direction); + g_date_add_days (&end, direction); + } else { + g_date_subtract_days (&start, direction * -1); + g_date_subtract_days (&end, direction * -1); + } + break; + case E_CAL_VIEW_KIND_WORKWEEK: + case E_CAL_VIEW_KIND_WEEK: + if (direction > 0) { + g_date_add_days (&start, direction * 7); + g_date_add_days (&end, direction * 7); + } else { + g_date_subtract_days (&start, direction * -7); + g_date_subtract_days (&end, direction * -7); + } + break; + case E_CAL_VIEW_KIND_MONTH: + case E_CAL_VIEW_KIND_LIST: + if (g_date_get_day (&start) != 1) { + g_date_add_months (&start, 1); + g_date_set_day (&start, 1); + } + if (direction > 0) + g_date_add_months (&start, direction); + else + g_date_subtract_months (&start, direction * -1); + end = start; + g_date_set_day (&end, g_date_get_days_in_month (g_date_get_month (&start), g_date_get_year (&start))); + g_date_add_days (&end, 6); + break; + case E_CAL_VIEW_KIND_LAST: + return; + } + + e_cal_shell_content_change_view (cal_shell_content, cal_shell_content->priv->current_view, &start, &end, FALSE); +} + +void +e_cal_shell_content_move_view_range (ECalShellContent *cal_shell_content, + ECalendarViewMoveType move_type, + time_t exact_date) +{ + ECalendar *calendar; + ECalDataModel *data_model; + EShellSidebar *shell_sidebar; + EShellView *shell_view; + struct icaltimetype tt; + icaltimezone *zone; + GDate date; + + g_return_if_fail (E_IS_CAL_SHELL_CONTENT (cal_shell_content)); + + shell_view = e_shell_content_get_shell_view (E_SHELL_CONTENT (cal_shell_content)); + shell_sidebar = e_shell_view_get_shell_sidebar (shell_view); + g_return_if_fail (E_IS_CAL_BASE_SHELL_SIDEBAR (shell_sidebar)); + + calendar = e_cal_base_shell_sidebar_get_date_navigator (E_CAL_BASE_SHELL_SIDEBAR (shell_sidebar)); + g_return_if_fail (E_IS_CALENDAR (calendar)); + g_return_if_fail (e_calendar_get_item (calendar) != NULL); + + data_model = e_cal_base_shell_content_get_data_model (E_CAL_BASE_SHELL_CONTENT (cal_shell_content)); + zone = e_cal_data_model_get_timezone (data_model); + + switch (move_type) { + case E_CALENDAR_VIEW_MOVE_PREVIOUS: + cal_shell_content_move_view_range_relative (cal_shell_content, -1); + break; + case E_CALENDAR_VIEW_MOVE_NEXT: + cal_shell_content_move_view_range_relative (cal_shell_content, +1); + break; + case E_CALENDAR_VIEW_MOVE_TO_TODAY: + tt = icaltime_current_time_with_zone (zone); + g_date_set_dmy (&date, tt.day, tt.month, tt.year); + /* one-day selection takes care of the view range move with left view kind */ + e_calendar_item_set_selection (e_calendar_get_item (calendar), &date, &date); + break; + case E_CALENDAR_VIEW_MOVE_TO_EXACT_DAY: + time_to_gdate_with_zone (&date, exact_date, zone); + e_cal_shell_content_change_view (cal_shell_content, E_CAL_VIEW_KIND_DAY, &date, &date, FALSE); + break; + } +} + +static void +cal_shell_content_update_model_filter (ECalDataModel *data_model, + ECalModel *model, + const gchar *filter, + time_t range_start, + time_t range_end) +{ + time_t tmp_start, tmp_end; + + g_return_if_fail (E_IS_CAL_DATA_MODEL (data_model)); + g_return_if_fail (E_IS_CAL_MODEL (model)); + + e_cal_data_model_freeze_views_update (data_model); + if (filter != NULL) + e_cal_data_model_set_filter (data_model, filter); + e_cal_model_set_time_range (model, range_start, range_end); + + if (!e_cal_data_model_get_subscriber_range (data_model, E_CAL_DATA_MODEL_SUBSCRIBER (model), &tmp_start, &tmp_end)) { + e_cal_data_model_subscribe (data_model, E_CAL_DATA_MODEL_SUBSCRIBER (model), range_start, range_end); + } + + e_cal_data_model_thaw_views_update (data_model); +} + +void +e_cal_shell_content_update_filters (ECalShellContent *cal_shell_content, + const gchar *cal_filter, + time_t start_range, + time_t end_range) +{ + ECalDataModel *data_model; + ECalModel *model; + + g_return_if_fail (E_IS_CAL_SHELL_CONTENT (cal_shell_content)); + + if (!cal_filter) + return; + + data_model = e_cal_base_shell_content_get_data_model (E_CAL_BASE_SHELL_CONTENT (cal_shell_content)); + model = e_cal_base_shell_content_get_model (E_CAL_BASE_SHELL_CONTENT (cal_shell_content)); + + cal_shell_content_update_model_filter (data_model, model, cal_filter, start_range, end_range); + + if (cal_shell_content->priv->task_table) { + ETaskTable *task_table; + gchar *hide_completed_tasks_sexp; + + /* Set the query on the task pad. */ + + task_table = E_TASK_TABLE (cal_shell_content->priv->task_table); + model = e_task_table_get_model (task_table); + data_model = e_cal_model_get_data_model (model); + + hide_completed_tasks_sexp = calendar_config_get_hide_completed_tasks_sexp (FALSE); + + if (hide_completed_tasks_sexp != NULL) { + if (*cal_filter) { + gchar *filter; + + filter = g_strdup_printf ("(and %s %s)", hide_completed_tasks_sexp, cal_filter); + cal_shell_content_update_model_filter (data_model, model, filter, 0, 0); + g_free (filter); + } else { + cal_shell_content_update_model_filter (data_model, model, hide_completed_tasks_sexp, 0, 0); + } + } else { + cal_shell_content_update_model_filter (data_model, model, *cal_filter ? cal_filter : "#t", 0, 0); + } + + g_free (hide_completed_tasks_sexp); + } + + if (cal_shell_content->priv->memo_table) { + EMemoTable *memo_table; + + /* Set the query on the memo pad. */ + + memo_table = E_MEMO_TABLE (cal_shell_content->priv->memo_table); + model = e_memo_table_get_model (memo_table); + data_model = e_cal_model_get_data_model (model); + + if (start_range != 0 && end_range != 0) { + icaltimezone *zone; + const gchar *default_tzloc = NULL; + time_t end = end_range; + gchar *filter; + gchar *iso_start; + gchar *iso_end; + + zone = e_cal_data_model_get_timezone (data_model); + if (zone && zone != icaltimezone_get_utc_timezone ()) + default_tzloc = icaltimezone_get_location (zone); + if (!default_tzloc) + default_tzloc = ""; + + if (start_range != (time_t) 0 && end_range != (time_t) 0) { + end = time_day_end_with_zone (end_range, zone); + } + + iso_start = isodate_from_time_t (start_range); + iso_end = isodate_from_time_t (end); + + filter = g_strdup_printf ( + "(and (or (not (has-start?)) " + "(occur-in-time-range? (make-time \"%s\") " + "(make-time \"%s\") \"%s\")) %s)", + iso_start, iso_end, default_tzloc, cal_filter); + + cal_shell_content_update_model_filter (data_model, model, filter, 0, 0); + + g_free (filter); + g_free (iso_start); + g_free (iso_end); + } else { + cal_shell_content_update_model_filter (data_model, model, *cal_filter ? cal_filter : "#t", 0, 0); + } + } +}