/* * Evolution calendar - Framework for a calendar component editor dialog * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with the program; if not, see * * * Authors: * Federico Mena-Quintero * * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include "../print.h" #include "../comp-util.h" #include "save-comp.h" #include "delete-comp.h" #include "send-comp.h" #include "changed-comp.h" #include "cancel-comp.h" #include "recur-comp.h" #include "comp-editor.h" #include "comp-editor-util.h" #include "../calendar-config-keys.h" #define COMP_EDITOR_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), TYPE_COMP_EDITOR, CompEditorPrivate)) #define d(x) /* Private part of the CompEditor structure */ struct _CompEditorPrivate { gpointer shell; /* weak pointer */ GSettings *calendar_settings; /* EFocusTracker keeps selection actions up-to-date. */ EFocusTracker *focus_tracker; /* Each CompEditor window gets its own GtkWindowGroup, so it * doesn't block the main window or other CompEditor windows. */ GtkWindowGroup *window_group; /* Client to use */ ECalClient *cal_client; /* Source client (where comp lives currently) */ ECalClient *source_client; /* View to listen for changes */ ECalClientView *view; GCancellable *view_cancellable; /* Calendar object/uid we are editing; this is an internal copy */ ECalComponent *comp; /* The pages we have */ GList *pages; /* Notebook to hold the pages */ GtkNotebook *notebook; /* Attachment handling */ GtkWidget *attachment_view; /* Manages menus and toolbars */ GtkUIManager *ui_manager; gchar *summary; guint32 attachment_bar_visible : 1; /* TODO use this flags for setting all the boolean variables * below */ CompEditorFlags flags; icaltimezone *zone; gboolean use_24_hour_format; GDateWeekday week_start_day; gint work_day_end_hour; gint work_day_end_minute; gint work_day_start_hour; gint work_day_start_minute; gboolean changed; gboolean needs_send; gboolean saved; CalObjModType mod; gboolean existing_org; gboolean user_org; gboolean is_group_item; gboolean warned; }; enum { PROP_0, PROP_CHANGED, PROP_CLIENT, PROP_FLAGS, PROP_FOCUS_TRACKER, PROP_SHELL, PROP_SUMMARY, PROP_TIMEZONE, PROP_USE_24_HOUR_FORMAT, PROP_WEEK_START_DAY, PROP_WORK_DAY_END_HOUR, PROP_WORK_DAY_END_MINUTE, PROP_WORK_DAY_START_HOUR, PROP_WORK_DAY_START_MINUTE }; static const gchar *ui = "" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " \n" " \n" " \n" " " " \n" " " ""; static void comp_editor_show_help (CompEditor *editor); static void real_edit_comp (CompEditor *editor, ECalComponent *comp); static gboolean real_send_comp (CompEditor *editor, ECalComponentItipMethod method, gboolean strip_alarms); static gboolean prompt_and_save_changes (CompEditor *editor, gboolean send); static void close_dialog (CompEditor *editor); static void page_dates_changed_cb (CompEditor *editor, CompEditorPageDates *dates, CompEditorPage *page); static void obj_modified_cb (ECalClientView *view, const GSList *objs, CompEditor *editor); static void obj_removed_cb (ECalClientView *view, const GSList *uids, CompEditor *editor); G_DEFINE_TYPE_WITH_CODE ( CompEditor, comp_editor, GTK_TYPE_WINDOW, G_IMPLEMENT_INTERFACE (E_TYPE_ALERT_SINK, NULL) G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE, NULL)) enum { OBJECT_CREATED, COMP_CLOSED, LAST_SIGNAL }; static guint signals[LAST_SIGNAL]; static GList *active_editors; static void comp_editor_weak_notify_cb (gpointer unused, GObject *where_the_object_was) { active_editors = g_list_remove (active_editors, where_the_object_was); } static void attachment_store_changed_cb (CompEditor *editor) { /* Mark the editor as changed so it prompts about unsaved * changes on close */ comp_editor_set_changed (editor, TRUE); } static void attachment_save_finished (EAttachmentStore *store, GAsyncResult *result, gpointer user_data) { GtkWidget *dialog; const gchar *primary_text; gchar **uris; GError *error = NULL; struct { gchar **uris; gboolean done; GtkWindow *parent; } *status = user_data; uris = e_attachment_store_save_finish (store, result, &error); status->uris = uris; status->done = TRUE; if (uris != NULL) goto exit; /* Ignore cancellations. */ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) goto exit; primary_text = _("Could not save attachments"); dialog = gtk_message_dialog_new_with_markup ( status->parent, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "%s", primary_text); gtk_message_dialog_format_secondary_text ( GTK_MESSAGE_DIALOG (dialog), "%s", error->message); gtk_dialog_run (GTK_DIALOG (dialog)); gtk_widget_destroy (dialog); exit: if (error != NULL) g_error_free (error); g_object_unref (status->parent); } static GSList * get_attachment_list (CompEditor *editor) { EAttachmentStore *store; EAttachmentView *view; GFile *destination; GSList *list = NULL; const gchar *comp_uid = NULL; const gchar *local_store; gchar *filename_prefix, *tmp; gint ii; struct { gchar **uris; gboolean done; GtkWindow *parent; } status; e_cal_component_get_uid (editor->priv->comp, &comp_uid); g_return_val_if_fail (comp_uid != NULL, NULL); status.uris = NULL; status.done = FALSE; status.parent = g_object_ref (editor); view = E_ATTACHMENT_VIEW (editor->priv->attachment_view); store = e_attachment_view_get_store (view); tmp = g_strdup (comp_uid); e_filename_make_safe (tmp); filename_prefix = g_strconcat (tmp, "-", NULL); g_free (tmp); local_store = e_cal_client_get_local_attachment_store (editor->priv->cal_client); destination = g_file_new_for_path (local_store); e_attachment_store_save_async ( store, destination, filename_prefix, (GAsyncReadyCallback) attachment_save_finished, &status); g_object_unref (destination); g_free (filename_prefix); /* We can't return until we have results, so crank * the main loop until the callback gets triggered. */ while (!status.done) gtk_main_iteration (); if (status.uris == NULL) return NULL; /* Transfer the URI strings to the GSList. */ for (ii = 0; status.uris[ii] != NULL; ii++) { list = g_slist_prepend (list, status.uris[ii]); status.uris[ii] = NULL; } g_free (status.uris); return g_slist_reverse (list); } /* This sets the focus to the toplevel, so any field being edited is committed. * FIXME: In future we may also want to check some of the fields are valid, * e.g. the EDateEdit fields. */ static void commit_all_fields (CompEditor *editor) { gtk_window_set_focus (GTK_WINDOW (editor), NULL); } static void changes_view_ready_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { CompEditor *editor = user_data; ECalClientView *view = NULL; gboolean success; GError *error = NULL; g_return_if_fail (editor != NULL); success = e_cal_client_get_view_finish ( E_CAL_CLIENT (source_object), result, &view, &error); if (!success) view = NULL; if (view) { editor->priv->view = view; g_signal_connect ( view, "objects_modified", G_CALLBACK (obj_modified_cb), editor); g_signal_connect ( view, "objects_removed", G_CALLBACK (obj_removed_cb), editor); e_cal_client_view_start (view, &error); if (error != NULL) { g_warning ( "%s: Failed to start view: %s", G_STRFUNC, error->message); g_error_free (error); } } else if (error) { if (!g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_CANCELLED) && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) g_warning ( "%s: Failed to get view: %s", G_STRFUNC, error->message); g_error_free (error); } } static void listen_for_changes (CompEditor *editor) { CompEditorPrivate *priv; const gchar *uid = NULL; priv = editor->priv; /* Discard change listener */ if (priv->view_cancellable) { g_cancellable_cancel (priv->view_cancellable); g_object_unref (priv->view_cancellable); priv->view_cancellable = NULL; } if (priv->view) { g_signal_handlers_disconnect_matched ( priv->view, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, editor); g_object_unref (priv->view); priv->view = NULL; } /* Listen for changes */ if (priv->comp) e_cal_component_get_uid (priv->comp, &uid); if (uid) { gchar *query; priv->view_cancellable = g_cancellable_new (); query = g_strdup_printf ("(uid? \"%s\")", uid); e_cal_client_get_view ( priv->source_client, query, priv->view_cancellable, changes_view_ready_cb, editor); g_free (query); } } static void send_timezone (gpointer key, gpointer value, gpointer user_data) { icaltimezone *zone = value; CompEditor *editor = user_data; GError *error = NULL; e_cal_client_add_timezone_sync (editor->priv->cal_client, zone, NULL, &error); if (error != NULL) { g_warning ( "%s: Failed to add timezone: %s", G_STRFUNC, error->message); g_error_free (error); } } static gboolean save_comp (CompEditor *editor) { CompEditorPrivate *priv; CompEditorFlags flags; ECalComponent *clone; ESourceRegistry *registry; EShell *shell; GList *l; gboolean result; GError *error = NULL; GHashTable *timezones; const gchar *orig_uid = NULL; gchar *orig_uid_copy; icalcomponent *icalcomp; priv = editor->priv; if (!priv->changed) return TRUE; flags = comp_editor_get_flags (editor); shell = comp_editor_get_shell (editor); registry = e_shell_get_registry (shell); /* Stop listening because we are about to change things */ if (priv->view) { g_signal_handlers_disconnect_matched ( priv->view, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, editor); g_object_unref (priv->view); priv->view = NULL; } /* Update on the server */ timezones = g_hash_table_new (g_str_hash, g_str_equal); clone = e_cal_component_clone (priv->comp); comp_editor_copy_new_attendees (clone, priv->comp); for (l = priv->pages; l != NULL; l = l->next) { if (IS_COMP_EDITOR_PAGE (l->data) && !comp_editor_page_fill_component (l->data, clone)) { g_object_unref (clone); g_hash_table_destroy (timezones); comp_editor_show_page (editor, COMP_EDITOR_PAGE (l->data)); return FALSE; } /* retrieve all timezones */ if (IS_COMP_EDITOR_PAGE (l->data)) comp_editor_page_fill_timezones (l->data, timezones); } /* If we are not the organizer, we don't update the sequence number */ if (!e_cal_component_has_organizer (clone) || itip_organizer_is_user (registry, clone, priv->cal_client) || itip_sentby_is_user (registry, clone, priv->cal_client)) e_cal_component_commit_sequence (clone); else e_cal_component_abort_sequence (clone); g_object_unref (priv->comp); priv->comp = clone; e_cal_component_get_uid (priv->comp, &orig_uid); /* Make a copy of it, because call of e_cal_create_object() * rewrites the internal uid. */ orig_uid_copy = g_strdup (orig_uid); /* send timezones */ g_hash_table_foreach (timezones, (GHFunc) send_timezone, editor); g_hash_table_destroy (timezones); /* Attachments*/ e_cal_component_set_attachment_list ( priv->comp, get_attachment_list (editor)); icalcomp = e_cal_component_get_icalcomponent (priv->comp); /* send the component to the server */ if (!cal_comp_is_on_server (priv->comp, priv->cal_client)) { gchar *uid = NULL; result = e_cal_client_create_object_sync ( priv->cal_client, icalcomp, &uid, NULL, &error); if (result) { icalcomponent_set_uid (icalcomp, uid); g_free (uid); g_signal_emit_by_name (editor, "object_created"); } } else { gboolean has_recurrences; has_recurrences = e_cal_component_has_recurrences (priv->comp); if (has_recurrences && priv->mod == CALOBJ_MOD_ALL) comp_util_sanitize_recurrence_master ( priv->comp, priv->cal_client); if (priv->mod == CALOBJ_MOD_THIS) { e_cal_component_set_rdate_list (priv->comp, NULL); e_cal_component_set_rrule_list (priv->comp, NULL); e_cal_component_set_exdate_list (priv->comp, NULL); e_cal_component_set_exrule_list (priv->comp, NULL); } result = e_cal_client_modify_object_sync ( priv->cal_client, icalcomp, priv->mod, NULL, &error); if (priv->mod == CALOBJ_MOD_THIS) { if (result && ((flags & COMP_EDITOR_DELEGATE) || !e_cal_component_has_organizer (clone) || itip_organizer_is_user (registry, clone, priv->cal_client) || itip_sentby_is_user (registry, clone, priv->cal_client))) e_cal_component_commit_sequence (clone); else e_cal_component_abort_sequence (clone); } } /* If the delay delivery is set, the items will not be created in * the server immediately, so we need not show them in the view. * They will appear as soon as the server creates it after the * delay period. */ if (result && e_cal_component_has_attendees (priv->comp)) { gboolean delay_set = FALSE; icalproperty *icalprop; icalprop = icalcomponent_get_first_property (icalcomp, ICAL_X_PROPERTY); while (icalprop) { const gchar *x_name; x_name = icalproperty_get_x_name (icalprop); if (!strcmp (x_name, "X-EVOLUTION-OPTIONS-DELAY")) { delay_set = TRUE; break; } icalprop = icalcomponent_get_next_property (icalcomp, ICAL_X_PROPERTY); } if (delay_set) { g_free (orig_uid_copy); return TRUE; } } if (!result) { GtkWidget *dialog; dialog = gtk_message_dialog_new ( NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "%s", (error != NULL) ? error->message : _("Could not update object")); gtk_dialog_run (GTK_DIALOG (dialog)); gtk_widget_destroy (dialog); if (error) g_error_free (error); g_free (orig_uid_copy); return FALSE; } else { if (priv->source_client && !e_source_equal (e_client_get_source (E_CLIENT (priv->cal_client)), e_client_get_source (E_CLIENT (priv->source_client))) && cal_comp_is_on_server (priv->comp, priv->source_client)) { /* Comp found a new home. Remove it from old one. */ GError *error = NULL; if (e_cal_component_is_instance (priv->comp) || e_cal_component_has_recurrences (priv->comp)) e_cal_client_remove_object_sync ( priv->source_client, orig_uid_copy, NULL, CALOBJ_MOD_ALL, NULL, &error); else e_cal_client_remove_object_sync ( priv->source_client, orig_uid_copy, NULL, CALOBJ_MOD_THIS, NULL, &error); if (error != NULL) { g_warning ( "%s: Failed to remove object: %s", G_STRFUNC, error->message); g_error_free (error); } /* Let priv->source_client point to new home, * so we can move it again this session. */ g_object_unref (priv->source_client); priv->source_client = g_object_ref (priv->cal_client); listen_for_changes (editor); } comp_editor_set_changed (editor, FALSE); priv->saved = TRUE; } g_free (orig_uid_copy); return TRUE; } static gboolean save_comp_with_send (CompEditor *editor) { CompEditorPrivate *priv; CompEditorFlags flags; ESourceRegistry *registry; EShell *shell; gboolean send, delegated, only_new_attendees = FALSE; gboolean delegate; gboolean strip_alarms = TRUE; priv = editor->priv; flags = comp_editor_get_flags (editor); shell = comp_editor_get_shell (editor); registry = e_shell_get_registry (shell); send = priv->changed && priv->needs_send; delegate = flags & COMP_EDITOR_DELEGATE; if (delegate) { icalcomponent *icalcomp = e_cal_component_get_icalcomponent (priv->comp); icalproperty *icalprop; icalprop = icalproperty_new_x ("1"); icalproperty_set_x_name (icalprop, "X-EVOLUTION-DELEGATED"); icalcomponent_add_property (icalcomp, icalprop); } if (!save_comp (editor)) return FALSE; delegated = delegate && !e_cal_client_check_save_schedules (priv->cal_client); if (delegated || (send && send_component_dialog ( (GtkWindow *) editor, priv->cal_client, priv->comp, !priv->existing_org, &strip_alarms, !priv->existing_org ? NULL : &only_new_attendees))) { if (delegated) only_new_attendees = FALSE; comp_editor_set_flags ( editor, (comp_editor_get_flags (editor) & (~COMP_EDITOR_SEND_TO_NEW_ATTENDEES_ONLY)) | (only_new_attendees ? COMP_EDITOR_SEND_TO_NEW_ATTENDEES_ONLY : 0)); if ((itip_organizer_is_user (registry, priv->comp, priv->cal_client) || itip_sentby_is_user (registry, priv->comp, priv->cal_client))) { if (e_cal_component_get_vtype (priv->comp) == E_CAL_COMPONENT_JOURNAL) return comp_editor_send_comp ( editor, E_CAL_COMPONENT_METHOD_PUBLISH, strip_alarms); else return comp_editor_send_comp ( editor, E_CAL_COMPONENT_METHOD_REQUEST, strip_alarms); } else { if (!comp_editor_send_comp ( editor, E_CAL_COMPONENT_METHOD_REQUEST, strip_alarms)) return FALSE; if (delegate) return comp_editor_send_comp ( editor, E_CAL_COMPONENT_METHOD_REPLY, strip_alarms); } } return TRUE; } static void update_window_border (CompEditor *editor, const gchar *description) { const gchar *icon_name; const gchar *format; gchar *title; if (editor->priv->comp == NULL) { title = g_strdup (_("Edit Appointment")); icon_name = "x-office-calendar"; goto exit; } else switch (e_cal_component_get_vtype (editor->priv->comp)) { case E_CAL_COMPONENT_EVENT: if (editor->priv->is_group_item) format = _("Meeting - %s"); else format = _("Appointment - %s"); icon_name = "appointment-new"; break; case E_CAL_COMPONENT_TODO: if (editor->priv->is_group_item) format = _("Assigned Task - %s"); else format = _("Task - %s"); icon_name = "stock_task"; break; case E_CAL_COMPONENT_JOURNAL: format = _("Memo - %s"); icon_name = "stock_insert-note"; break; default: g_return_if_reached (); } if (description == NULL || *description == '\0') { ECalComponentText text; e_cal_component_get_summary (editor->priv->comp, &text); description = text.value; } if (description == NULL || *description == '\0') description = _("No Summary"); title = g_strdup_printf (format, description); exit: gtk_window_set_icon_name (GTK_WINDOW (editor), icon_name); gtk_window_set_title (GTK_WINDOW (editor), title); g_free (title); } static void action_attach_cb (GtkAction *action, CompEditor *editor) { EAttachmentStore *store; EAttachmentView *view; view = E_ATTACHMENT_VIEW (editor->priv->attachment_view); store = e_attachment_view_get_store (view); e_attachment_store_run_load_dialog (store, GTK_WINDOW (editor)); } static void action_classification_cb (GtkRadioAction *action, GtkRadioAction *current, CompEditor *editor) { comp_editor_set_changed (editor, TRUE); } static void action_close_cb (GtkAction *action, CompEditor *editor) { commit_all_fields (editor); if (prompt_and_save_changes (editor, TRUE)) close_dialog (editor); } static void action_help_cb (GtkAction *action, CompEditor *editor) { comp_editor_show_help (editor); } static void action_print_cb (GtkAction *action, CompEditor *editor) { CompEditorPrivate *priv = editor->priv; ECalComponent *comp; GList *l; icalcomponent *component; icalcomponent *clone; icaltimezone *zone; gboolean use_24_hour_format; comp = e_cal_component_new (); component = e_cal_component_get_icalcomponent (priv->comp); clone = icalcomponent_new_clone (component); e_cal_component_set_icalcomponent (comp, clone); for (l = priv->pages; l != NULL; l = l->next) comp_editor_page_fill_component (l->data, comp); zone = comp_editor_get_timezone (editor); use_24_hour_format = comp_editor_get_use_24_hour_format (editor); print_comp ( comp, priv->cal_client, zone, use_24_hour_format, GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG); g_object_unref (comp); } static void action_print_preview_cb (GtkAction *action, CompEditor *editor) { CompEditorPrivate *priv = editor->priv; ECalComponent *comp; GList *l; icalcomponent *component; icalcomponent *clone; icaltimezone *zone; gboolean use_24_hour_format; comp = e_cal_component_new (); component = e_cal_component_get_icalcomponent (priv->comp); clone = icalcomponent_new_clone (component); e_cal_component_set_icalcomponent (comp, clone); for (l = priv->pages; l != NULL; l = l->next) comp_editor_page_fill_component (l->data, comp); zone = comp_editor_get_timezone (editor); use_24_hour_format = comp_editor_get_use_24_hour_format (editor); print_comp ( comp, priv->cal_client, zone, use_24_hour_format, GTK_PRINT_OPERATION_ACTION_PREVIEW); g_object_unref (comp); } static gboolean remove_event_dialog (ECalClient *client, ECalComponent *comp, GtkWindow *parent) { GtkWidget *dialog; gboolean ret; g_return_val_if_fail (E_IS_CAL_COMPONENT (comp), TRUE); dialog = gtk_message_dialog_new ( parent, 0, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, "%s", _("Keep original item?")); gtk_window_set_resizable (GTK_WINDOW (dialog), TRUE); ret = gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_YES; gtk_widget_destroy (dialog); return ret; } static void save_and_close_editor (CompEditor *editor, gboolean can_close) { CompEditorPrivate *priv = editor->priv; EAttachmentStore *store; EAttachmentView *view; ECalComponentText text; gboolean delegated = FALSE; gboolean correct = FALSE; ECalComponent *comp; const gchar *uid = NULL; view = E_ATTACHMENT_VIEW (priv->attachment_view); store = e_attachment_view_get_store (view); if (e_attachment_store_get_num_loading (store) > 0) { gboolean response = 1; /*FIXME: Cannot use mail functions from calendar!!!! */ #if 0 ECalComponentVType vtype = e_cal_component_get_vtype (editor->priv->comp); if (vtype == E_CAL_COMPONENT_EVENT) response = em_utils_prompt_user ( (GtkWindow *) widget, NULL, "calendar:ask-send-event-pending-download", NULL); else response = em_utils_prompt_user ( (GtkWindow *) widget, NULL, "calendar:ask-send-task-pending-download", NULL); #endif if (!response) return; } if (e_client_is_readonly (E_CLIENT (priv->cal_client))) { e_alert_submit ( E_ALERT_SINK (editor), "calendar:prompt-read-only-cal-editor", e_source_get_display_name ( e_client_get_source (E_CLIENT (priv->cal_client))), NULL); return; } if ((comp_editor_get_flags (editor) & COMP_EDITOR_IS_ASSIGNED) != 0 && e_cal_component_get_vtype (priv->comp) == E_CAL_COMPONENT_TODO && e_client_check_capability (E_CLIENT (priv->cal_client), CAL_STATIC_CAPABILITY_NO_TASK_ASSIGNMENT)) { e_alert_submit ( E_ALERT_SINK (editor), "calendar:prompt-no-task-assignment-editor", e_source_get_display_name ( e_client_get_source (E_CLIENT (priv->cal_client))), NULL); return; } commit_all_fields (editor); if (e_cal_component_has_recurrences (priv->comp)) { if (!recur_component_dialog ( priv->cal_client, priv->comp, &priv->mod, GTK_WINDOW (editor), delegated)) return; } else if (e_cal_component_is_instance (priv->comp)) priv->mod = CALOBJ_MOD_THIS; comp = comp_editor_get_current_comp (editor, &correct); e_cal_component_get_summary (comp, &text); g_object_unref (comp); if (!correct) return; if (!text.value) if (!send_component_prompt_subject ( (GtkWindow *) editor, priv->cal_client, priv->comp)) return; gtk_widget_set_sensitive (GTK_WIDGET (editor), FALSE); if (save_comp_with_send (editor)) { CompEditorFlags flags; gboolean delegate; flags = comp_editor_get_flags (editor); delegate = flags & COMP_EDITOR_DELEGATE; if (delegate && !remove_event_dialog ( priv->cal_client, priv->comp, GTK_WINDOW (editor))) { GError *error = NULL; e_cal_component_get_uid (priv->comp, &uid); if (e_cal_component_is_instance (priv->comp) || e_cal_component_has_recurrences (priv->comp)) { gchar *rid; rid = e_cal_component_get_recurid_as_string (priv->comp); e_cal_client_remove_object_sync ( priv->cal_client, uid, rid, priv->mod, NULL, &error); g_free (rid); } else e_cal_client_remove_object_sync ( priv->cal_client, uid, NULL, CALOBJ_MOD_THIS, NULL, &error); g_clear_error (&error); } } else correct = FALSE; gtk_widget_set_sensitive (GTK_WIDGET (editor), TRUE); if (correct) { if (can_close) close_dialog (editor); else { ECalComponent *comp; ECalClientSourceType source_type; icalcomponent *icalcomp = NULL; const gchar *uid = NULL; gchar *rid = NULL; GError *error = NULL; comp_editor_set_changed (editor, FALSE); /* * A server can modify the event on save. Considering this, it is needed to fetch the updated * version of the event from server, updating the component, then user can keep editing the * event */ e_cal_component_get_uid (priv->comp, &uid); rid = e_cal_component_get_recurid_as_string (priv->comp); source_type = e_cal_client_get_source_type (priv->cal_client); if (!e_cal_client_get_object_sync (priv->cal_client, uid, rid, &icalcomp, NULL, &error)) { if (error != NULL) { switch (source_type) { case (E_CAL_CLIENT_SOURCE_TYPE_TASKS): g_warning ("Unable to retrieve saved component from the task list, returned error was: %s", error->message); break; case (E_CAL_CLIENT_SOURCE_TYPE_MEMOS): g_warning ("Unable to retrieve saved component from the memo list, returned error was: %s", error->message); break; case (E_CAL_CLIENT_SOURCE_TYPE_EVENTS): default: g_warning ("Unable to retrieve saved component from the calendar, returned error was: %s", error->message); break; } g_clear_error (&error); } else { switch (source_type) { case (E_CAL_CLIENT_SOURCE_TYPE_TASKS): g_warning ("Unable to retrieve saved component from the task list"); break; case (E_CAL_CLIENT_SOURCE_TYPE_MEMOS): g_warning ("Unable to retrieve saved component from the memo list"); break; case (E_CAL_CLIENT_SOURCE_TYPE_EVENTS): default: g_warning ("Unable to retrieve saved component from the calendar"); break; } } e_notice ( GTK_WINDOW (editor), GTK_MESSAGE_ERROR, _("Unable to synchronize with the server")); } else { comp = e_cal_component_new (); if (e_cal_component_set_icalcomponent (comp, icalcomp)) { gboolean has_recurrences; has_recurrences = e_cal_component_has_recurrences (comp); if (has_recurrences && priv->mod == CALOBJ_MOD_ALL) comp_util_sanitize_recurrence_master (comp, priv->cal_client); comp_editor_edit_comp (editor, comp); } else { switch (source_type) { case (E_CAL_CLIENT_SOURCE_TYPE_TASKS): g_warning ("Unable to update the editor with the retrieved component from the task list"); break; case (E_CAL_CLIENT_SOURCE_TYPE_MEMOS): g_warning ("Unable to update the editor with the retrieved component from the memo list"); break; case (E_CAL_CLIENT_SOURCE_TYPE_EVENTS): default: g_warning ("Unable to update the editor with the retrieved component from the calendar"); break; } e_notice ( GTK_WINDOW (editor), GTK_MESSAGE_ERROR, _("Unable to synchronize with the server")); icalcomponent_free (icalcomp); } g_object_unref (comp); } g_free (rid); } } } static void action_save_cb (GtkAction *action, CompEditor *editor) { save_and_close_editor (editor, FALSE); } static void action_save_and_close_cb (GtkAction *action, CompEditor *editor) { save_and_close_editor (editor, TRUE); } static void action_view_categories_cb (GtkToggleAction *action, CompEditor *editor) { CompEditorClass *class; gboolean active; class = COMP_EDITOR_GET_CLASS (editor); active = gtk_toggle_action_get_active (action); if (class->show_categories != NULL) class->show_categories (editor, active); } static void action_view_role_cb (GtkToggleAction *action, CompEditor *editor) { CompEditorClass *class; gboolean active; class = COMP_EDITOR_GET_CLASS (editor); active = gtk_toggle_action_get_active (action); if (class->show_role != NULL) class->show_role (editor, active); } static void action_view_rsvp_cb (GtkToggleAction *action, CompEditor *editor) { CompEditorClass *class; gboolean active; class = COMP_EDITOR_GET_CLASS (editor); active = gtk_toggle_action_get_active (action); if (class->show_rsvp != NULL) class->show_rsvp (editor, active); } static void action_view_status_cb (GtkToggleAction *action, CompEditor *editor) { CompEditorClass *class; gboolean active; class = COMP_EDITOR_GET_CLASS (editor); active = gtk_toggle_action_get_active (action); if (class->show_status != NULL) class->show_status (editor, active); } static void action_view_time_zone_cb (GtkToggleAction *action, CompEditor *editor) { CompEditorClass *class; gboolean active; class = COMP_EDITOR_GET_CLASS (editor); active = gtk_toggle_action_get_active (action); if (class->show_time_zone != NULL) class->show_time_zone (editor, active); } static void action_view_type_cb (GtkToggleAction *action, CompEditor *editor) { CompEditorClass *class; gboolean active; class = COMP_EDITOR_GET_CLASS (editor); active = gtk_toggle_action_get_active (action); if (class->show_type != NULL) class->show_type (editor, active); } static GtkActionEntry core_entries[] = { { "close", GTK_STOCK_CLOSE, NULL, NULL, N_("Close the current window"), G_CALLBACK (action_close_cb) }, { "copy-clipboard", GTK_STOCK_COPY, NULL, NULL, N_("Copy the selection"), NULL }, /* Handled by EFocusTracker */ { "cut-clipboard", GTK_STOCK_CUT, NULL, NULL, N_("Cut the selection"), NULL }, /* Handled by EFocusTracker */ { "delete-selection", GTK_STOCK_DELETE, NULL, NULL, N_("Delete the selection"), NULL }, /* Handled by EFocusTracker */ { "help", GTK_STOCK_HELP, NULL, NULL, N_("View help"), G_CALLBACK (action_help_cb) }, { "paste-clipboard", GTK_STOCK_PASTE, NULL, NULL, N_("Paste the clipboard"), NULL }, /* Handled by EFocusTracker */ { "print", GTK_STOCK_PRINT, NULL, "p", NULL, G_CALLBACK (action_print_cb) }, { "print-preview", GTK_STOCK_PRINT_PREVIEW, NULL, NULL, NULL, G_CALLBACK (action_print_preview_cb) }, { "save", GTK_STOCK_SAVE, NULL, NULL, 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) }, { "select-all", GTK_STOCK_SELECT_ALL, NULL, "a", N_("Select all text"), 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 } }; static GtkActionEntry individual_entries[] = { { "attach", "mail-attachment", N_("_Attachment..."), "m", N_("Attach a file"), G_CALLBACK (action_attach_cb) } }; static GtkToggleActionEntry core_toggle_entries[] = { { "view-categories", NULL, N_("_Categories"), NULL, N_("Toggles whether to display categories"), G_CALLBACK (action_view_categories_cb), FALSE }, { "view-time-zone", "stock_timezone", N_("Time _Zone"), NULL, N_("Toggles whether the time zone is displayed"), G_CALLBACK (action_view_time_zone_cb), FALSE } }; static GtkRadioActionEntry classification_radio_entries[] = { { "classify-public", NULL, N_("Pu_blic"), NULL, N_("Classify as public"), E_CAL_COMPONENT_CLASS_PUBLIC }, { "classify-private", NULL, N_("_Private"), NULL, N_("Classify as private"), E_CAL_COMPONENT_CLASS_PRIVATE }, { "classify-confidential", NULL, N_("_Confidential"), NULL, N_("Classify as confidential"), E_CAL_COMPONENT_CLASS_CONFIDENTIAL } }; static GtkToggleActionEntry coordinated_toggle_entries[] = { { "view-role", NULL, N_("R_ole Field"), NULL, N_("Toggles whether the Role field is displayed"), G_CALLBACK (action_view_role_cb), FALSE }, { "view-rsvp", NULL, N_("_RSVP"), NULL, N_("Toggles whether the RSVP field is displayed"), G_CALLBACK (action_view_rsvp_cb), FALSE }, { "view-status", NULL, N_("_Status Field"), NULL, N_("Toggles whether the Status field is displayed"), G_CALLBACK (action_view_status_cb), FALSE }, { "view-type", NULL, N_("_Type Field"), NULL, N_("Toggles whether the Attendee Type is displayed"), G_CALLBACK (action_view_type_cb), FALSE } }; static void comp_editor_set_shell (CompEditor *editor, EShell *shell) { g_return_if_fail (E_IS_SHELL (shell)); g_return_if_fail (editor->priv->shell == NULL); editor->priv->shell = shell; g_object_add_weak_pointer (G_OBJECT (shell), &editor->priv->shell); } static void comp_editor_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_CHANGED: comp_editor_set_changed ( COMP_EDITOR (object), g_value_get_boolean (value)); return; case PROP_CLIENT: comp_editor_set_client ( COMP_EDITOR (object), g_value_get_object (value)); return; case PROP_FLAGS: comp_editor_set_flags ( COMP_EDITOR (object), g_value_get_int (value)); return; case PROP_SHELL: comp_editor_set_shell ( COMP_EDITOR (object), g_value_get_object (value)); return; case PROP_SUMMARY: comp_editor_set_summary ( COMP_EDITOR (object), g_value_get_string (value)); return; case PROP_TIMEZONE: comp_editor_set_timezone ( COMP_EDITOR (object), g_value_get_pointer (value)); return; case PROP_USE_24_HOUR_FORMAT: comp_editor_set_use_24_hour_format ( COMP_EDITOR (object), g_value_get_boolean (value)); return; case PROP_WEEK_START_DAY: comp_editor_set_week_start_day ( COMP_EDITOR (object), g_value_get_enum (value)); return; case PROP_WORK_DAY_END_HOUR: comp_editor_set_work_day_end_hour ( COMP_EDITOR (object), g_value_get_int (value)); return; case PROP_WORK_DAY_END_MINUTE: comp_editor_set_work_day_end_minute ( COMP_EDITOR (object), g_value_get_int (value)); return; case PROP_WORK_DAY_START_HOUR: comp_editor_set_work_day_start_hour ( COMP_EDITOR (object), g_value_get_int (value)); return; case PROP_WORK_DAY_START_MINUTE: comp_editor_set_work_day_start_minute ( COMP_EDITOR (object), g_value_get_int (value)); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void comp_editor_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_CHANGED: g_value_set_boolean ( value, comp_editor_get_changed ( COMP_EDITOR (object))); return; case PROP_CLIENT: g_value_set_object ( value, comp_editor_get_client ( COMP_EDITOR (object))); return; case PROP_FLAGS: g_value_set_int ( value, comp_editor_get_flags ( COMP_EDITOR (object))); return; case PROP_FOCUS_TRACKER: g_value_set_object ( value, comp_editor_get_focus_tracker ( COMP_EDITOR (object))); return; case PROP_SHELL: g_value_set_object ( value, comp_editor_get_shell ( COMP_EDITOR (object))); return; case PROP_SUMMARY: g_value_set_string ( value, comp_editor_get_summary ( COMP_EDITOR (object))); return; case PROP_TIMEZONE: g_value_set_pointer ( value, comp_editor_get_timezone ( COMP_EDITOR (object))); return; case PROP_USE_24_HOUR_FORMAT: g_value_set_boolean ( value, comp_editor_get_use_24_hour_format ( COMP_EDITOR (object))); return; case PROP_WEEK_START_DAY: g_value_set_enum ( value, comp_editor_get_week_start_day ( COMP_EDITOR (object))); return; case PROP_WORK_DAY_END_HOUR: g_value_set_int ( value, comp_editor_get_work_day_end_hour ( COMP_EDITOR (object))); return; case PROP_WORK_DAY_END_MINUTE: g_value_set_int ( value, comp_editor_get_work_day_end_minute ( COMP_EDITOR (object))); return; case PROP_WORK_DAY_START_HOUR: g_value_set_int ( value, comp_editor_get_work_day_start_hour ( COMP_EDITOR (object))); return; case PROP_WORK_DAY_START_MINUTE: g_value_set_int ( value, comp_editor_get_work_day_start_minute ( COMP_EDITOR (object))); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void unref_page_cb (gpointer editor_page, gpointer comp_editor) { if (IS_COMP_EDITOR_PAGE (editor_page)) { GtkWidget *page_widget; CompEditorPage *page = COMP_EDITOR_PAGE (editor_page); CompEditor *editor = COMP_EDITOR (comp_editor); g_return_if_fail (page != NULL); g_return_if_fail (editor != NULL); page_widget = comp_editor_page_get_widget (page); g_signal_handlers_disconnect_matched ( page_widget, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, page); } g_signal_handlers_disconnect_matched ( editor_page, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, comp_editor); g_object_unref (editor_page); } static void comp_editor_dispose (GObject *object) { CompEditorPrivate *priv; priv = COMP_EDITOR_GET_PRIVATE (object); if (priv->shell != NULL) { g_object_remove_weak_pointer ( G_OBJECT (priv->shell), &priv->shell); priv->shell = NULL; } if (priv->focus_tracker != NULL) { g_object_unref (priv->focus_tracker); priv->focus_tracker = NULL; } if (priv->window_group != NULL) { g_object_unref (priv->window_group); priv->window_group = NULL; } if (priv->cal_client) { g_object_unref (priv->cal_client); priv->cal_client = NULL; } if (priv->source_client) { g_object_unref (priv->source_client); priv->source_client = NULL; } if (priv->view_cancellable) { g_cancellable_cancel (priv->view_cancellable); g_object_unref (priv->view_cancellable); priv->view_cancellable = NULL; } if (priv->view) { g_signal_handlers_disconnect_matched ( priv->view, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, object); g_object_unref (priv->view); priv->view = NULL; } if (priv->attachment_view) { EAttachmentStore *store; store = e_attachment_view_get_store ( E_ATTACHMENT_VIEW (priv->attachment_view)); g_signal_handlers_disconnect_matched ( store, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, object); g_object_unref (priv->attachment_view); priv->attachment_view = NULL; } /* We want to destroy the pages after the widgets get destroyed, * since they have lots of signal handlers connected to the widgets * with the pages as the data. */ g_list_foreach (priv->pages, (GFunc) unref_page_cb, object); g_list_free (priv->pages); priv->pages = NULL; if (priv->comp) { g_object_unref (priv->comp); priv->comp = NULL; } if (priv->ui_manager != NULL) { g_object_unref (priv->ui_manager); priv->ui_manager = NULL; } /* Chain up to parent's dispose() method. */ G_OBJECT_CLASS (comp_editor_parent_class)->dispose (object); } static void comp_editor_finalize (GObject *object) { CompEditorPrivate *priv; priv = COMP_EDITOR_GET_PRIVATE (object); g_object_unref (priv->calendar_settings); g_free (priv->summary); /* Chain up to parent's finalize() method. */ G_OBJECT_CLASS (comp_editor_parent_class)->finalize (object); } static void comp_editor_constructed (GObject *object) { e_extensible_load_extensions (E_EXTENSIBLE (object)); /* Chain up to parent's constructed() method. */ G_OBJECT_CLASS (comp_editor_parent_class)->constructed (object); } static void comp_editor_bind_settings (CompEditor *editor) { GtkAction *action; g_return_if_fail (editor != NULL); action = comp_editor_get_action (editor, "view-categories"); g_settings_bind ( editor->priv->calendar_settings, "editor-show-categories", action, "active", G_SETTINGS_BIND_DEFAULT); action = comp_editor_get_action (editor, "view-role"); g_settings_bind ( editor->priv->calendar_settings, "editor-show-role", action, "active", G_SETTINGS_BIND_DEFAULT); action = comp_editor_get_action (editor, "view-rsvp"); g_settings_bind ( editor->priv->calendar_settings, "editor-show-rsvp", action, "active", G_SETTINGS_BIND_DEFAULT); action = comp_editor_get_action (editor, "view-status"); g_settings_bind ( editor->priv->calendar_settings, "editor-show-status", action, "active", G_SETTINGS_BIND_DEFAULT); action = comp_editor_get_action (editor, "view-time-zone"); g_settings_bind ( editor->priv->calendar_settings, "editor-show-timezone", action, "active", G_SETTINGS_BIND_DEFAULT); action = comp_editor_get_action (editor, "view-type"); g_settings_bind ( editor->priv->calendar_settings, "editor-show-type", action, "active", G_SETTINGS_BIND_DEFAULT); } static gboolean comp_editor_delete_event (GtkWidget *widget, GdkEventAny *event) { CompEditor *editor; editor = COMP_EDITOR (widget); commit_all_fields (editor); if (prompt_and_save_changes (editor, TRUE)) close_dialog (editor); return TRUE; } static gboolean comp_editor_key_press_event (GtkWidget *widget, GdkEventKey *event) { CompEditor *editor; editor = COMP_EDITOR (widget); if (event->keyval == GDK_KEY_Escape) { commit_all_fields (editor); if (prompt_and_save_changes (editor, TRUE)) close_dialog (editor); return TRUE; } /* Chain up to parent's key_press_event() method. */ return GTK_WIDGET_CLASS (comp_editor_parent_class)-> key_press_event (widget, event); } static gboolean comp_editor_drag_motion (GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time) { CompEditorPrivate *priv; EAttachmentView *view; priv = COMP_EDITOR_GET_PRIVATE (widget); view = E_ATTACHMENT_VIEW (priv->attachment_view); return e_attachment_view_drag_motion (view, context, x, y, time); } static void comp_editor_drag_data_received (GtkWidget *widget, GdkDragContext *context, gint x, gint y, GtkSelectionData *selection, guint info, guint time) { CompEditorPrivate *priv; EAttachmentView *view; priv = COMP_EDITOR_GET_PRIVATE (widget); view = E_ATTACHMENT_VIEW (priv->attachment_view); /* Forward the data to the attachment view. Note that calling * e_attachment_view_drag_data_received() will not work because * that function only handles the case where all the other drag * handlers have failed. */ e_attachment_paned_drag_data_received ( E_ATTACHMENT_PANED (view), context, x, y, selection, info, time); } static void comp_editor_class_init (CompEditorClass *class) { GObjectClass *object_class; GtkWidgetClass *widget_class; g_type_class_add_private (class, sizeof (CompEditorPrivate)); object_class = G_OBJECT_CLASS (class); object_class->set_property = comp_editor_set_property; object_class->get_property = comp_editor_get_property; object_class->dispose = comp_editor_dispose; object_class->finalize = comp_editor_finalize; object_class->constructed = comp_editor_constructed; widget_class = GTK_WIDGET_CLASS (class); widget_class->delete_event = comp_editor_delete_event; widget_class->key_press_event = comp_editor_key_press_event; widget_class->drag_motion = comp_editor_drag_motion; widget_class->drag_data_received = comp_editor_drag_data_received; class->help_section = "memos-usage"; class->edit_comp = real_edit_comp; class->send_comp = real_send_comp; class->object_created = NULL; g_object_class_install_property ( object_class, PROP_CHANGED, g_param_spec_boolean ( "changed", NULL, NULL, FALSE, G_PARAM_READWRITE)); g_object_class_install_property ( object_class, PROP_CLIENT, g_param_spec_object ( "client", NULL, NULL, E_TYPE_CAL_CLIENT, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); /* FIXME: Use a proper flags type instead of int. */ g_object_class_install_property ( object_class, PROP_FLAGS, g_param_spec_int ( "flags", NULL, NULL, G_MININT, G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property ( object_class, PROP_FOCUS_TRACKER, g_param_spec_object ( "focus-tracker", NULL, NULL, E_TYPE_FOCUS_TRACKER, G_PARAM_READABLE)); g_object_class_install_property ( object_class, PROP_SHELL, g_param_spec_object ( "shell", NULL, NULL, E_TYPE_SHELL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property ( object_class, PROP_SUMMARY, g_param_spec_string ( "summary", NULL, NULL, NULL, G_PARAM_READWRITE)); g_object_class_install_property ( object_class, PROP_TIMEZONE, g_param_spec_pointer ( "timezone", "Time Zone", NULL, G_PARAM_READWRITE)); g_object_class_install_property ( object_class, PROP_USE_24_HOUR_FORMAT, g_param_spec_boolean ( "use-24-hour-format", "Use 24-hour Format", NULL, FALSE, G_PARAM_READWRITE)); g_object_class_install_property ( object_class, PROP_WEEK_START_DAY, g_param_spec_enum ( "week-start-day", "Week Start Day", NULL, E_TYPE_DATE_WEEKDAY, G_DATE_MONDAY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property ( object_class, PROP_WORK_DAY_END_HOUR, g_param_spec_int ( "work-day-end-hour", "Work Day End Hour", NULL, 0, 23, 0, G_PARAM_READWRITE)); g_object_class_install_property ( object_class, PROP_WORK_DAY_END_MINUTE, g_param_spec_int ( "work-day-end-minute", "Work Day End Minute", NULL, 0, 59, 0, G_PARAM_READWRITE)); g_object_class_install_property ( object_class, PROP_WORK_DAY_START_HOUR, g_param_spec_int ( "work-day-start-hour", "Work Day Start Hour", NULL, 0, 23, 0, G_PARAM_READWRITE)); g_object_class_install_property ( object_class, PROP_WORK_DAY_START_MINUTE, g_param_spec_int ( "work-day-start-minute", "Work Day Start Minute", NULL, 0, 59, 0, G_PARAM_READWRITE)); signals[OBJECT_CREATED] = g_signal_new ( "object_created", G_TYPE_FROM_CLASS (class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (CompEditorClass, object_created), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); signals[COMP_CLOSED] = g_signal_new ( "comp_closed", G_TYPE_FROM_CLASS (class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (CompEditorClass, comp_closed), NULL, NULL, g_cclosure_marshal_VOID__BOOLEAN, G_TYPE_NONE, 1, G_TYPE_BOOLEAN); } static void comp_editor_init (CompEditor *editor) { CompEditorPrivate *priv; EAttachmentView *view; EAttachmentStore *store; EFocusTracker *focus_tracker; GdkDragAction drag_actions; GtkTargetList *target_list; GtkTargetEntry *targets; GtkActionGroup *action_group; GtkActionGroup *action_group_2; GtkAction *action; GtkWidget *container; GtkWidget *widget; GtkWindow *window; EShell *shell; gboolean express_mode; gint n_targets; GError *error = NULL; /* FIXME We already have a 'shell' property. Move stuff * that depends on it to a constructed() method. */ shell = e_shell_get_default (); express_mode = e_shell_get_express_mode (shell); editor->priv = priv = COMP_EDITOR_GET_PRIVATE (editor); g_object_weak_ref ( G_OBJECT (editor), (GWeakNotify) comp_editor_weak_notify_cb, NULL); active_editors = g_list_prepend (active_editors, editor); priv->calendar_settings = g_settings_new ("org.gnome.evolution.calendar"); /* Each editor window gets its own window group. */ window = GTK_WINDOW (editor); priv->window_group = gtk_window_group_new (); gtk_window_group_add_window (priv->window_group, window); priv->pages = NULL; priv->changed = FALSE; priv->needs_send = FALSE; priv->mod = CALOBJ_MOD_ALL; priv->existing_org = FALSE; priv->user_org = FALSE; priv->warned = FALSE; priv->is_group_item = FALSE; priv->saved = FALSE; priv->ui_manager = gtk_ui_manager_new (); gtk_window_add_accel_group ( GTK_WINDOW (editor), gtk_ui_manager_get_accel_group (priv->ui_manager)); /* Setup Action Groups */ 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), editor); gtk_action_group_add_toggle_actions ( action_group, core_toggle_entries, G_N_ELEMENTS (core_toggle_entries), editor); gtk_ui_manager_insert_action_group ( 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 (GTK_STOCK_CLOSE); emblemed_icon = g_themed_icon_new (GTK_STOCK_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"); g_object_bind_property ( save_action, "sensitive", action, "sensitive", G_BINDING_SYNC_CREATE); } action_group = gtk_action_group_new ("individual"); gtk_action_group_set_translation_domain ( action_group, GETTEXT_PACKAGE); gtk_action_group_add_actions ( action_group, individual_entries, G_N_ELEMENTS (individual_entries), editor); gtk_action_group_add_radio_actions ( action_group, classification_radio_entries, G_N_ELEMENTS (classification_radio_entries), E_CAL_COMPONENT_CLASS_PUBLIC, G_CALLBACK (action_classification_cb), editor); gtk_ui_manager_insert_action_group ( 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_ui_manager_insert_action_group ( priv->ui_manager, action_group, 0); g_object_unref (action_group); action_group = gtk_action_group_new ("coordinated"); gtk_action_group_set_translation_domain ( action_group, GETTEXT_PACKAGE); gtk_action_group_add_toggle_actions ( action_group, coordinated_toggle_entries, G_N_ELEMENTS (coordinated_toggle_entries), editor); gtk_ui_manager_insert_action_group ( priv->ui_manager, action_group, 0); g_object_unref (action_group); /* Configure an EFocusTracker to manage selection actions. */ focus_tracker = e_focus_tracker_new (GTK_WINDOW (editor)); action = comp_editor_get_action (editor, "cut-clipboard"); e_focus_tracker_set_cut_clipboard_action (focus_tracker, action); action = comp_editor_get_action (editor, "copy-clipboard"); e_focus_tracker_set_copy_clipboard_action (focus_tracker, action); action = comp_editor_get_action (editor, "paste-clipboard"); e_focus_tracker_set_paste_clipboard_action (focus_tracker, action); action = comp_editor_get_action (editor, "delete-selection"); e_focus_tracker_set_delete_selection_action (focus_tracker, action); action = comp_editor_get_action (editor, "select-all"); e_focus_tracker_set_select_all_action (focus_tracker, action); priv->focus_tracker = focus_tracker; /* Fine Tuning */ action = comp_editor_get_action (editor, "attach"); g_object_set (action, "short-label", _("Attach"), NULL); /* Desensitize the "save" action. */ action = comp_editor_get_action (editor, "save"); gtk_action_set_sensitive (action, FALSE); gtk_ui_manager_add_ui_from_string (priv->ui_manager, ui, -1, &error); if (error != NULL) { g_warning ("%s: %s", G_STRFUNC, error->message); g_error_free (error); } /* Setup Widgets */ container = GTK_WIDGET (editor); widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); gtk_container_add (GTK_CONTAINER (container), widget); gtk_widget_show (widget); container = widget; widget = comp_editor_get_managed_widget (editor, "/main-menu"); gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0); gtk_widget_set_visible (widget, TRUE); widget = comp_editor_get_managed_widget (editor, "/main-toolbar"); gtk_box_pack_start (GTK_BOX (container), 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_attachment_paned_new (); e_attachment_paned_set_resize_toplevel ( E_ATTACHMENT_PANED (widget), TRUE); gtk_container_set_border_width (GTK_CONTAINER (widget), 6); gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0); priv->attachment_view = g_object_ref (widget); gtk_widget_show (widget); if (express_mode) { widget = e_attachment_paned_get_view_combo ( E_ATTACHMENT_PANED (widget)); gtk_widget_hide (widget); } container = e_attachment_paned_get_content_area ( E_ATTACHMENT_PANED (priv->attachment_view)); widget = gtk_notebook_new (); gtk_notebook_set_show_tabs (GTK_NOTEBOOK (widget), express_mode); gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0); priv->notebook = GTK_NOTEBOOK (widget); gtk_widget_show (widget); /* Drag-and-Drop Support */ view = E_ATTACHMENT_VIEW (priv->attachment_view); target_list = e_attachment_view_get_target_list (view); drag_actions = e_attachment_view_get_drag_actions (view); targets = gtk_target_table_new_from_list (target_list, &n_targets); gtk_drag_dest_set ( GTK_WIDGET (editor), GTK_DEST_DEFAULT_ALL, targets, n_targets, drag_actions); gtk_target_table_free (targets, n_targets); gtk_window_set_type_hint ( GTK_WINDOW (editor), GDK_WINDOW_TYPE_HINT_NORMAL); action_group = comp_editor_get_action_group (editor, "individual"); action_group_2 = e_attachment_view_get_action_group (view, "editable"); g_object_bind_property ( action_group, "sensitive", action_group_2, "sensitive", G_BINDING_SYNC_CREATE); /* Listen for attachment store changes. */ store = e_attachment_view_get_store (view); g_signal_connect_swapped ( store, "row-deleted", G_CALLBACK (attachment_store_changed_cb), editor); g_signal_connect_swapped ( store, "row-inserted", G_CALLBACK (attachment_store_changed_cb), editor); comp_editor_bind_settings (editor); gtk_application_add_window ( GTK_APPLICATION (shell), GTK_WINDOW (editor)); } static gboolean prompt_and_save_changes (CompEditor *editor, gboolean send) { CompEditorPrivate *priv; gboolean correct = FALSE; ECalComponent *comp; ECalComponentText text; priv = editor->priv; if (!priv->changed) return TRUE; switch (save_component_dialog (GTK_WINDOW (editor), priv->comp)) { case GTK_RESPONSE_YES: /* Save */ if (e_client_is_readonly (E_CLIENT (priv->cal_client))) { e_alert_submit ( E_ALERT_SINK (editor), "calendar:prompt-read-only-cal-editor", e_source_get_display_name ( e_client_get_source (E_CLIENT (priv->cal_client))), NULL); /* don't discard changes when selected readonly calendar */ return FALSE; } if ((comp_editor_get_flags (editor) & COMP_EDITOR_IS_ASSIGNED) != 0 && e_cal_component_get_vtype (priv->comp) == E_CAL_COMPONENT_TODO && e_client_check_capability (E_CLIENT (priv->cal_client), CAL_STATIC_CAPABILITY_NO_TASK_ASSIGNMENT)) { e_alert_submit ( E_ALERT_SINK (editor), "calendar:prompt-no-task-assignment-editor", e_source_get_display_name ( e_client_get_source (E_CLIENT (priv->cal_client))), NULL); return FALSE; } comp = comp_editor_get_current_comp (editor, &correct); e_cal_component_get_summary (comp, &text); g_object_unref (comp); if (!correct) return FALSE; if (!text.value) if (!send_component_prompt_subject ( (GtkWindow *) editor, priv->cal_client, priv->comp)) return FALSE; if (e_cal_component_is_instance (priv->comp)) if (!recur_component_dialog ( priv->cal_client, priv->comp, &priv->mod, GTK_WINDOW (editor), FALSE)) return FALSE; if (send && save_comp_with_send (editor)) return TRUE; else if (!send && save_comp (editor)) return TRUE; else return FALSE; case GTK_RESPONSE_NO: /* Discard */ return TRUE; case GTK_RESPONSE_CANCEL: /* Cancel */ default: return FALSE; } } /* Menu callbacks */ static void comp_editor_show_help (CompEditor *editor) { CompEditorClass *class; class = COMP_EDITOR_GET_CLASS (editor); g_return_if_fail (class->help_section != NULL); e_display_help (GTK_WINDOW (editor), class->help_section); } /* Closes the dialog box and emits the appropriate signals */ static void close_dialog (CompEditor *editor) { CompEditorPrivate *priv = editor->priv; g_signal_emit_by_name (editor, "comp_closed", priv->saved); /* FIXME Unfortunately we do this here because otherwise corba * calls happen during destruction and we might get a change * notification back when we are in an inconsistent state */ if (priv->view) g_signal_handlers_disconnect_matched ( priv->view, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, editor); gtk_widget_destroy (GTK_WIDGET (editor)); } static gint comp_editor_compare (CompEditor *editor_a, const gchar *uid_b) { const gchar *uid_a = NULL; e_cal_component_get_uid (editor_a->priv->comp, &uid_a); return g_strcmp0 (uid_a, uid_b); } void comp_editor_set_existing_org (CompEditor *editor, gboolean existing_org) { g_return_if_fail (IS_COMP_EDITOR (editor)); editor->priv->existing_org = existing_org; } gboolean comp_editor_get_existing_org (CompEditor *editor) { g_return_val_if_fail (IS_COMP_EDITOR (editor), FALSE); return editor->priv->existing_org; } void comp_editor_set_user_org (CompEditor *editor, gboolean user_org) { g_return_if_fail (IS_COMP_EDITOR (editor)); editor->priv->user_org = user_org; } gboolean comp_editor_get_user_org (CompEditor *editor) { g_return_val_if_fail (IS_COMP_EDITOR (editor), FALSE); return editor->priv->user_org; } void comp_editor_set_group_item (CompEditor *editor, gboolean group_item) { g_return_if_fail (IS_COMP_EDITOR (editor)); editor->priv->is_group_item = group_item; } gboolean comp_editor_get_group_item (CompEditor *editor) { g_return_val_if_fail (IS_COMP_EDITOR (editor), FALSE); return editor->priv->is_group_item; } void comp_editor_set_classification (CompEditor *editor, ECalComponentClassification classification) { GtkAction *action; g_return_if_fail (IS_COMP_EDITOR (editor)); switch (classification) { case E_CAL_COMPONENT_CLASS_PUBLIC: case E_CAL_COMPONENT_CLASS_PRIVATE: case E_CAL_COMPONENT_CLASS_CONFIDENTIAL: break; default: classification = E_CAL_COMPONENT_CLASS_PUBLIC; break; } action = comp_editor_get_action (editor, "classify-public"); gtk_radio_action_set_current_value ( GTK_RADIO_ACTION (action), classification); } ECalComponentClassification comp_editor_get_classification (CompEditor *editor) { GtkAction *action; g_return_val_if_fail (IS_COMP_EDITOR (editor), 0); action = comp_editor_get_action (editor, "classify-public"); return gtk_radio_action_get_current_value (GTK_RADIO_ACTION (action)); } EShell * comp_editor_get_shell (CompEditor *editor) { g_return_val_if_fail (IS_COMP_EDITOR (editor), NULL); return editor->priv->shell; } void comp_editor_set_summary (CompEditor *editor, const gchar *summary) { gboolean show_warning; g_return_if_fail (IS_COMP_EDITOR (editor)); if (g_strcmp0 (editor->priv->summary, summary) == 0) return; g_free (editor->priv->summary); editor->priv->summary = g_strdup (summary); show_warning = !editor->priv->warned && !(editor->priv->flags & COMP_EDITOR_DELEGATE) && editor->priv->existing_org && !editor->priv->user_org && !(editor->priv->flags & COMP_EDITOR_NEW_ITEM); if (show_warning) { e_notice ( editor->priv->notebook, GTK_MESSAGE_INFO, _("Changes made to this item may be " "discarded if an update arrives")); editor->priv->warned = TRUE; } update_window_border (editor, summary); g_object_notify (G_OBJECT (editor), "summary"); } const gchar * comp_editor_get_summary (CompEditor *editor) { g_return_val_if_fail (IS_COMP_EDITOR (editor), NULL); return editor->priv->summary; } icaltimezone * comp_editor_get_timezone (CompEditor *editor) { g_return_val_if_fail (IS_COMP_EDITOR (editor), NULL); return editor->priv->zone; } void comp_editor_set_timezone (CompEditor *editor, icaltimezone *zone) { g_return_if_fail (IS_COMP_EDITOR (editor)); if (editor->priv->zone == zone) return; editor->priv->zone = zone; g_object_notify (G_OBJECT (editor), "timezone"); } gboolean comp_editor_get_use_24_hour_format (CompEditor *editor) { g_return_val_if_fail (IS_COMP_EDITOR (editor), FALSE); return editor->priv->use_24_hour_format; } void comp_editor_set_use_24_hour_format (CompEditor *editor, gboolean use_24_hour_format) { g_return_if_fail (IS_COMP_EDITOR (editor)); if (editor->priv->use_24_hour_format == use_24_hour_format) return; editor->priv->use_24_hour_format = use_24_hour_format; g_object_notify (G_OBJECT (editor), "use-24-hour-format"); } GDateWeekday comp_editor_get_week_start_day (CompEditor *editor) { g_return_val_if_fail (IS_COMP_EDITOR (editor), 0); return editor->priv->week_start_day; } void comp_editor_set_week_start_day (CompEditor *editor, GDateWeekday week_start_day) { g_return_if_fail (IS_COMP_EDITOR (editor)); g_return_if_fail (g_date_valid_weekday (week_start_day)); if (week_start_day == editor->priv->week_start_day) return; editor->priv->week_start_day = week_start_day; g_object_notify (G_OBJECT (editor), "week-start-day"); } gint comp_editor_get_work_day_end_hour (CompEditor *editor) { g_return_val_if_fail (IS_COMP_EDITOR (editor), 0); return editor->priv->work_day_end_hour; } void comp_editor_set_work_day_end_hour (CompEditor *editor, gint work_day_end_hour) { g_return_if_fail (IS_COMP_EDITOR (editor)); if (editor->priv->work_day_end_hour == work_day_end_hour) return; editor->priv->work_day_end_hour = work_day_end_hour; g_object_notify (G_OBJECT (editor), "work-day-end-hour"); } gint comp_editor_get_work_day_end_minute (CompEditor *editor) { g_return_val_if_fail (IS_COMP_EDITOR (editor), 0); return editor->priv->work_day_end_minute; } void comp_editor_set_work_day_end_minute (CompEditor *editor, gint work_day_end_minute) { g_return_if_fail (IS_COMP_EDITOR (editor)); if (editor->priv->work_day_end_minute == work_day_end_minute) return; editor->priv->work_day_end_minute = work_day_end_minute; g_object_notify (G_OBJECT (editor), "work-day-end-minute"); } gint comp_editor_get_work_day_start_hour (CompEditor *editor) { g_return_val_if_fail (IS_COMP_EDITOR (editor), 0); return editor->priv->work_day_start_hour; } void comp_editor_set_work_day_start_hour (CompEditor *editor, gint work_day_start_hour) { g_return_if_fail (IS_COMP_EDITOR (editor)); if (editor->priv->work_day_start_hour == work_day_start_hour) return; editor->priv->work_day_start_hour = work_day_start_hour; g_object_notify (G_OBJECT (editor), "work-day-start-hour"); } gint comp_editor_get_work_day_start_minute (CompEditor *editor) { g_return_val_if_fail (IS_COMP_EDITOR (editor), 0); return editor->priv->work_day_start_minute; } void comp_editor_set_work_day_start_minute (CompEditor *editor, gint work_day_start_minute) { g_return_if_fail (IS_COMP_EDITOR (editor)); if (editor->priv->work_day_start_minute == work_day_start_minute) return; editor->priv->work_day_start_minute = work_day_start_minute; g_object_notify (G_OBJECT (editor), "work-day-start-minute"); } /** * comp_editor_set_changed: * @editor: A component editor * @changed: Value to set the changed state to * * Set the dialog changed state to the given value **/ void comp_editor_set_changed (CompEditor *editor, gboolean changed) { GtkAction *action; gboolean show_warning; g_return_if_fail (IS_COMP_EDITOR (editor)); /* always process below changes, because other parts of * the editor listen for 'changed' notifications to update * its widgets, thus do it even the value actually didn't change */ if (editor->priv->changed != changed) { editor->priv->changed = changed; action = comp_editor_get_action (editor, "save"); g_return_if_fail (action != NULL); gtk_action_set_sensitive (action, changed); } show_warning = changed && !editor->priv->warned && !(editor->priv->flags & COMP_EDITOR_DELEGATE) && editor->priv->existing_org && !editor->priv->user_org && !(editor->priv->flags & COMP_EDITOR_NEW_ITEM); if (show_warning) { e_notice ( editor->priv->notebook, GTK_MESSAGE_INFO, _("Changes made to this item may be " "discarded if an update arrives")); editor->priv->warned = TRUE; } g_object_notify (G_OBJECT (editor), "changed"); } /** * comp_editor_get_changed: * @editor: A component editor * * Gets the changed state of the dialog * * Return value: A boolean indicating if the dialog is in a changed * state **/ gboolean comp_editor_get_changed (CompEditor *editor) { g_return_val_if_fail (IS_COMP_EDITOR (editor), FALSE); return editor->priv->changed; } EFocusTracker * comp_editor_get_focus_tracker (CompEditor *editor) { g_return_val_if_fail (IS_COMP_EDITOR (editor), NULL); return editor->priv->focus_tracker; } void comp_editor_set_flags (CompEditor *editor, CompEditorFlags flags) { g_return_if_fail (IS_COMP_EDITOR (editor)); if (editor->priv->flags == flags) return; editor->priv->flags = flags; editor->priv->user_org = flags & COMP_EDITOR_USER_ORG; g_object_notify (G_OBJECT (editor), "flags"); } CompEditorFlags comp_editor_get_flags (CompEditor *editor) { g_return_val_if_fail (IS_COMP_EDITOR (editor), 0); return editor->priv->flags; } GtkUIManager * comp_editor_get_ui_manager (CompEditor *editor) { g_return_val_if_fail (IS_COMP_EDITOR (editor), NULL); return editor->priv->ui_manager; } GtkAction * comp_editor_get_action (CompEditor *editor, const gchar *action_name) { GtkUIManager *ui_manager; g_return_val_if_fail (IS_COMP_EDITOR (editor), NULL); g_return_val_if_fail (action_name != NULL, NULL); ui_manager = comp_editor_get_ui_manager (editor); return e_lookup_action (ui_manager, action_name); } GtkActionGroup * comp_editor_get_action_group (CompEditor *editor, const gchar *group_name) { GtkUIManager *ui_manager; g_return_val_if_fail (IS_COMP_EDITOR (editor), NULL); g_return_val_if_fail (group_name != NULL, NULL); ui_manager = comp_editor_get_ui_manager (editor); return e_lookup_action_group (ui_manager, group_name); } GtkWidget * comp_editor_get_managed_widget (CompEditor *editor, const gchar *widget_path) { GtkUIManager *ui_manager; GtkWidget *widget; g_return_val_if_fail (IS_COMP_EDITOR (editor), NULL); g_return_val_if_fail (widget_path != NULL, NULL); ui_manager = comp_editor_get_ui_manager (editor); widget = gtk_ui_manager_get_widget (ui_manager, widget_path); g_return_val_if_fail (widget != NULL, NULL); return widget; } CompEditor * comp_editor_find_instance (const gchar *uid) { GList *link; g_return_val_if_fail (uid != NULL, NULL); link = g_list_find_custom ( active_editors, uid, (GCompareFunc) comp_editor_compare); return (link != NULL) ? link->data : NULL; } /** * comp_editor_set_needs_send: * @editor: A component editor * @needs_send: Value to set the needs send state to * * Set the dialog needs send state to the given value **/ void comp_editor_set_needs_send (CompEditor *editor, gboolean needs_send) { g_return_if_fail (IS_COMP_EDITOR (editor)); editor->priv->needs_send = needs_send; } /** * comp_editor_get_needs_send: * @editor: A component editor * * Gets the needs send state of the dialog * * Return value: A boolean indicating if the dialog is in a needs send * state **/ gboolean comp_editor_get_needs_send (CompEditor *editor) { g_return_val_if_fail (IS_COMP_EDITOR (editor), FALSE); return editor->priv->needs_send; } static void page_mapped_cb (GtkWidget *page_widget, CompEditorPage *page) { GtkWidget *toplevel; toplevel = gtk_widget_get_toplevel (page_widget); if (!GTK_IS_WINDOW (toplevel)) return; if (page->accel_group) { gtk_window_add_accel_group ( GTK_WINDOW (toplevel), page->accel_group); } } static void page_unmapped_cb (GtkWidget *page_widget, CompEditorPage *page) { GtkWidget *toplevel; toplevel = gtk_widget_get_toplevel (page_widget); if (!GTK_IS_WINDOW (toplevel)) return; if (page->accel_group) { gtk_window_remove_accel_group (GTK_WINDOW (toplevel), page->accel_group); } } /** * comp_editor_append_widget: * @editor: A component editor * @page: A component editor page * @label: Label of the page. Should be NULL if add is FALSE. * @add: Add's the page into the notebook if TRUE * * Appends a page to the notebook if add is TRUE else * just adds it to the list of pages. **/ void comp_editor_append_widget (CompEditor *editor, GtkWidget *page, const gchar *label, gboolean add) { CompEditorPrivate *priv; GtkWidget *label_widget = NULL; g_return_if_fail (IS_COMP_EDITOR (editor)); priv = editor->priv; g_object_ref (page); if (label) label_widget = gtk_label_new_with_mnemonic (label); priv->pages = g_list_append (priv->pages, page); if (add) { gtk_notebook_append_page (priv->notebook, page, label_widget); gtk_container_child_set ( GTK_CONTAINER (priv->notebook), page, "tab-fill", FALSE, "tab-expand", FALSE, NULL); } /* Listen for when the page is mapped/unmapped so we can * install/uninstall the appropriate GtkAccelGroup. g_signal_connect ( page, "map", G_CALLBACK (page_mapped_cb), page); g_signal_connect ( page, "unmap", G_CALLBACK (page_unmapped_cb), page); */ } /** * comp_editor_append_page: * @editor: A component editor * @page: A component editor page * @label: Label of the page. Should be NULL if add is FALSE. * @add: Add's the page into the notebook if TRUE * * Appends a page to the notebook if add is TRUE else * just adds it to the list of pages. **/ void comp_editor_append_page (CompEditor *editor, CompEditorPage *page, const gchar *label, gboolean add) { CompEditorPrivate *priv; GtkWidget *page_widget; GtkWidget *label_widget = NULL; gboolean is_first_page; g_return_if_fail (IS_COMP_EDITOR (editor)); g_return_if_fail (IS_COMP_EDITOR_PAGE (page)); priv = editor->priv; g_object_ref (page); /* If we are editing something, fill the widgets with current info */ if (priv->comp != NULL) { ECalComponent *comp; comp = comp_editor_get_current_comp (editor, NULL); comp_editor_page_fill_widgets (page, comp); g_object_unref (comp); } page_widget = comp_editor_page_get_widget (page); g_return_if_fail (page_widget != NULL); if (label) label_widget = gtk_label_new_with_mnemonic (label); is_first_page = (priv->pages == NULL); priv->pages = g_list_append (priv->pages, page); if (add) { gtk_notebook_append_page ( priv->notebook, page_widget, label_widget); gtk_container_child_set ( GTK_CONTAINER (priv->notebook), page_widget, "tab-fill", FALSE, "tab-expand", FALSE, NULL); } /* Listen for things happening on the page */ g_signal_connect_swapped ( page, "dates_changed", G_CALLBACK (page_dates_changed_cb), editor); /* Listen for when the page is mapped/unmapped so we can * install/uninstall the appropriate GtkAccelGroup. */ g_signal_connect ( page_widget, "map", G_CALLBACK (page_mapped_cb), page); g_signal_connect ( page_widget, "unmap", G_CALLBACK (page_unmapped_cb), page); /* The first page is the main page of the editor, so we ask it to focus * its main widget. */ if (is_first_page) comp_editor_page_focus_main_widget (page); } /** * comp_editor_remove_page: * @editor: A component editor * @page: A component editor page * * Removes the page from the component editor **/ void comp_editor_remove_page (CompEditor *editor, CompEditorPage *page) { CompEditorPrivate *priv; GtkWidget *page_widget; gint page_num; g_return_if_fail (IS_COMP_EDITOR (editor)); g_return_if_fail (IS_COMP_EDITOR_PAGE (page)); priv = editor->priv; page_widget = comp_editor_page_get_widget (page); page_num = gtk_notebook_page_num (priv->notebook, page_widget); if (page_num == -1) return; /* Disconnect all the signals added in append_page(). */ g_signal_handlers_disconnect_matched ( page, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, editor); g_signal_handlers_disconnect_matched ( page_widget, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, page); gtk_notebook_remove_page (priv->notebook, page_num); priv->pages = g_list_remove (priv->pages, page); g_object_unref (page); } /** * comp_editor_show_page: * @editor: * @page: * * **/ void comp_editor_show_page (CompEditor *editor, CompEditorPage *page) { CompEditorPrivate *priv; GtkWidget *page_widget; gint page_num; g_return_if_fail (IS_COMP_EDITOR (editor)); g_return_if_fail (IS_COMP_EDITOR_PAGE (page)); priv = editor->priv; page_widget = comp_editor_page_get_widget (page); page_num = gtk_notebook_page_num (priv->notebook, page_widget); gtk_notebook_set_current_page (priv->notebook, page_num); } /** * comp_editor_set_client: * @editor: A component editor * @cal_client: The calendar client to use * * Sets the calendar client used by the editor to update components **/ void comp_editor_set_client (CompEditor *editor, ECalClient *cal_client) { g_return_if_fail (IS_COMP_EDITOR (editor)); g_return_if_fail (cal_client == NULL || E_IS_CAL_CLIENT (cal_client)); if (editor->priv->cal_client == cal_client) return; if (cal_client != NULL) g_object_ref (cal_client); if (editor->priv->cal_client != NULL) g_object_unref (editor->priv->cal_client); editor->priv->cal_client = cal_client; if (editor->priv->source_client == NULL && cal_client != NULL) editor->priv->source_client = g_object_ref (cal_client); g_object_notify (G_OBJECT (editor), "client"); } /** * comp_editor_get_client: * @editor: A component editor * * Returns the calendar client of the editor * * Return value: The calendar client of the editor **/ ECalClient * comp_editor_get_client (CompEditor *editor) { g_return_val_if_fail (IS_COMP_EDITOR (editor), NULL); return editor->priv->cal_client; } static void attachment_loaded_cb (EAttachment *attachment, GAsyncResult *result, GtkWindow *parent) { GFileInfo *file_info; const gchar *display_name; const gchar *uid; gchar *new_name; /* Prior to 2.27.2, attachment files were named: * * '-' * ------------------------------------- * (one long filename) * * Here we fix the display name if this form is detected so we * don't show the component UID in the user interface. If the * user saves changes in the editor, the attachment will be * written to disk as: * * / * --------------- ----------------- * (directory) (original name) * * So this is a lazy migration from the old form to the new. */ file_info = e_attachment_ref_file_info (attachment); if (file_info == NULL) { /* failed to load an attachment file */ e_attachment_load_handle_error (attachment, result, parent); return; } display_name = g_file_info_get_display_name (file_info); uid = g_object_get_data (G_OBJECT (attachment), "uid"); if (g_str_has_prefix (display_name, uid)) { new_name = g_strdup (display_name + strlen (uid) + 1); g_file_info_set_display_name (file_info, new_name); g_object_notify (G_OBJECT (attachment), "file-info"); g_free (new_name); } g_object_unref (file_info); e_attachment_load_handle_error (attachment, result, parent); } static void set_attachment_list (CompEditor *editor, GSList *uri_list) { EAttachmentStore *store; EAttachmentView *view; const gchar *uid = NULL; GSList *iter; view = E_ATTACHMENT_VIEW (editor->priv->attachment_view); store = e_attachment_view_get_store (view); if (e_attachment_store_get_num_attachments (store) > 0) { /* To prevent repopulating the * bar due to redraw functions in fill_widget. * Assumes it can be set only once. */ return; } /* XXX What an awkward API this is. Takes a return location * for a constant string instead of just returning it. */ e_cal_component_get_uid (editor->priv->comp, &uid); for (iter = uri_list; iter != NULL; iter = iter->next) { EAttachment *attachment; attachment = e_attachment_new_for_uri (iter->data); e_attachment_store_add_attachment (store, attachment); g_object_set_data_full ( G_OBJECT (attachment), "uid", g_strdup (uid), (GDestroyNotify) g_free); e_attachment_load_async ( attachment, (GAsyncReadyCallback) attachment_loaded_cb, editor); g_object_unref (attachment); } } static void fill_widgets (CompEditor *editor) { EAttachmentStore *store; EAttachmentView *view; CompEditorPrivate *priv; GtkAction *action; GList *iter; view = E_ATTACHMENT_VIEW (editor->priv->attachment_view); store = e_attachment_view_get_store (view); priv = editor->priv; /*Check if attachments are available here and set them*/ if (e_cal_component_has_attachments (priv->comp)) { GSList *attachment_list = NULL; e_cal_component_get_attachment_list (priv->comp, &attachment_list); g_signal_handlers_block_by_func ( store, G_CALLBACK (attachment_store_changed_cb), editor); set_attachment_list (editor, attachment_list); g_signal_handlers_unblock_by_func ( store, G_CALLBACK (attachment_store_changed_cb), editor); g_slist_foreach (attachment_list, (GFunc) g_free, NULL); g_slist_free (attachment_list); } action = comp_editor_get_action (editor, "classify-public"); g_signal_handlers_block_by_func ( action, G_CALLBACK (action_classification_cb), editor); for (iter = priv->pages; iter != NULL; iter = iter->next) { if (IS_COMP_EDITOR_PAGE (iter->data)) comp_editor_page_fill_widgets (iter->data, priv->comp); } g_signal_handlers_unblock_by_func ( action, G_CALLBACK (action_classification_cb), editor); } static void real_edit_comp (CompEditor *editor, ECalComponent *comp) { CompEditorPrivate *priv; g_return_if_fail (IS_COMP_EDITOR (editor)); priv = editor->priv; if (priv->comp) { g_object_unref (priv->comp); priv->comp = NULL; } if (comp) { priv->comp = e_cal_component_clone (comp); comp_editor_copy_new_attendees (priv->comp, comp); } priv->existing_org = e_cal_component_has_organizer (comp); priv->warned = FALSE; update_window_border (editor, NULL); fill_widgets (editor); comp_editor_set_changed (editor, FALSE); listen_for_changes (editor); } /* TODO These functions should be available in e-cal-component.c */ static void set_attendees_for_delegation (ECalComponent *comp, const gchar *address, ECalComponentItipMethod method) { icalproperty *prop; icalparameter *param; icalcomponent *icalcomp; icalcomp = e_cal_component_get_icalcomponent (comp); for (prop = icalcomponent_get_first_property (icalcomp, ICAL_ATTENDEE_PROPERTY); prop; prop = icalcomponent_get_next_property (icalcomp, ICAL_ATTENDEE_PROPERTY)) { const gchar *attendee = icalproperty_get_attendee (prop); const gchar *delfrom = NULL; 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); } } } static void get_users_from_memo_comp (ECalComponent *comp, GSList **users) { icalcomponent *icalcomp; icalproperty *icalprop; const gchar *attendees = NULL; gchar **emails, **iter; icalcomp = e_cal_component_get_icalcomponent (comp); for (icalprop = icalcomponent_get_first_property (icalcomp, ICAL_X_PROPERTY); icalprop != NULL; icalprop = icalcomponent_get_next_property (icalcomp, ICAL_X_PROPERTY)) { const gchar *x_name; x_name = icalproperty_get_x_name (icalprop); if (g_str_equal (x_name, "X-EVOLUTION-RECIPIENTS")) break; } if (icalprop) { attendees = icalproperty_get_x (icalprop); emails = g_strsplit (attendees, ";", -1); iter = emails; while (*iter) { *users = g_slist_append (*users, g_strdup (*iter)); iter++; } g_strfreev (emails); } } static gboolean real_send_comp (CompEditor *editor, ECalComponentItipMethod method, gboolean strip_alarms) { CompEditorPrivate *priv; CompEditorFlags flags; EShell *shell; ESourceRegistry *registry; ECalComponent *send_comp = NULL; gchar *address = NULL; GSList *users = NULL; g_return_val_if_fail (IS_COMP_EDITOR (editor), FALSE); priv = editor->priv; flags = comp_editor_get_flags (editor); shell = comp_editor_get_shell (editor); registry = e_shell_get_registry (shell); if (priv->mod == CALOBJ_MOD_ALL && e_cal_component_is_instance (priv->comp)) { /* Ensure we send the master object, not the instance only */ icalcomponent *icalcomp = NULL; const gchar *uid = NULL; e_cal_component_get_uid (priv->comp, &uid); if (e_cal_client_get_object_sync (priv->cal_client, uid, NULL, &icalcomp, NULL, NULL) && icalcomp) { send_comp = e_cal_component_new (); if (!e_cal_component_set_icalcomponent (send_comp, icalcomp)) { icalcomponent_free (icalcomp); g_object_unref (send_comp); send_comp = NULL; } } } if (!send_comp) send_comp = e_cal_component_clone (priv->comp); comp_editor_copy_new_attendees (send_comp, priv->comp); if (e_cal_component_get_vtype (send_comp) == E_CAL_COMPONENT_JOURNAL) get_users_from_memo_comp (send_comp, &users); /* The user updates the delegated status to the Organizer, * so remove all other attendees. */ if (flags & COMP_EDITOR_DELEGATE) { address = itip_get_comp_attendee ( registry, send_comp, priv->cal_client); if (address) set_attendees_for_delegation (send_comp, address, method); } if (!e_cal_component_has_attachments (priv->comp) || e_client_check_capability ( E_CLIENT (priv->cal_client), CAL_STATIC_CAPABILITY_CREATE_MESSAGES)) { if (itip_send_comp ( registry, method, send_comp, priv->cal_client, NULL, NULL, users, strip_alarms, priv->flags & COMP_EDITOR_SEND_TO_NEW_ATTENDEES_ONLY)) { g_object_unref (send_comp); return TRUE; } } else { /* Clone the component with attachments set to CID:... */ GSList *attach_list = NULL; GSList *mime_attach_list, *attach; /* mime_attach_list is freed by itip_send_comp */ mime_attach_list = comp_editor_get_mime_attach_list (editor); 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_foreach (attach_list, (GFunc) g_free, NULL); g_slist_free (attach_list); } if (itip_send_comp ( registry, method, send_comp, priv->cal_client, NULL, mime_attach_list, users, strip_alarms, priv->flags & COMP_EDITOR_SEND_TO_NEW_ATTENDEES_ONLY)) { gboolean saved = save_comp (editor); g_object_unref (send_comp); if (!saved) comp_editor_set_changed (editor, TRUE); return saved; } } g_object_unref (send_comp); g_free (address); comp_editor_set_changed (editor, TRUE); return FALSE; } /** * comp_editor_edit_comp: * @editor: A component editor * @comp: A calendar component * * Starts the editor editing the given component **/ void comp_editor_edit_comp (CompEditor *editor, ECalComponent *comp) { CompEditorClass *class; g_return_if_fail (IS_COMP_EDITOR (editor)); g_return_if_fail (E_IS_CAL_COMPONENT (comp)); class = COMP_EDITOR_GET_CLASS (editor); if (class->edit_comp) class->edit_comp (editor, comp); } ECalComponent * comp_editor_get_comp (CompEditor *editor) { g_return_val_if_fail (IS_COMP_EDITOR (editor), NULL); return editor->priv->comp; } /** * comp_editor_get_current_comp * @editor: a #CompEditor * @correct: Set this no non-%NULL if you are interested to know if all * pages reported success when filling component. * * Returns: Newly allocated component, should be unref-ed by g_object_unref(). **/ ECalComponent * comp_editor_get_current_comp (CompEditor *editor, gboolean *correct) { CompEditorPrivate *priv; ECalComponent *comp; GList *l; gboolean all_ok = TRUE; g_return_val_if_fail (IS_COMP_EDITOR (editor), NULL); priv = editor->priv; comp = e_cal_component_clone (priv->comp); comp_editor_copy_new_attendees (comp, priv->comp); if (priv->changed) { for (l = priv->pages; l != NULL; l = l->next) { if (IS_COMP_EDITOR_PAGE (l->data)) all_ok = comp_editor_page_fill_component (l->data, comp) && all_ok; } } if (correct) *correct = all_ok; return comp; } /** * comp_editor_save_comp: * @editor: * * **/ gboolean comp_editor_save_comp (CompEditor *editor, gboolean send) { return prompt_and_save_changes (editor, send); } /** * comp_editor_delete_comp: * @editor: * * **/ void comp_editor_delete_comp (CompEditor *editor) { CompEditorPrivate *priv; const gchar *uid; g_return_if_fail (IS_COMP_EDITOR (editor)); priv = editor->priv; e_cal_component_get_uid (priv->comp, &uid); if (e_cal_component_is_instance (priv->comp) || e_cal_component_has_recurrences (priv->comp)) e_cal_client_remove_object_sync ( priv->cal_client, uid, NULL, CALOBJ_MOD_ALL, NULL, NULL); else e_cal_client_remove_object_sync ( priv->cal_client, uid, NULL, CALOBJ_MOD_THIS, NULL, NULL); close_dialog (editor); } /** * comp_editor_send_comp: * @editor: * @method: * * **/ gboolean comp_editor_send_comp (CompEditor *editor, ECalComponentItipMethod method, gboolean strip_alarms) { CompEditorClass *class; g_return_val_if_fail (IS_COMP_EDITOR (editor), FALSE); class = COMP_EDITOR_GET_CLASS (editor); if (class->send_comp) return class->send_comp (editor, method, strip_alarms); return FALSE; } gboolean comp_editor_close (CompEditor *editor) { gboolean close; g_return_val_if_fail (IS_COMP_EDITOR (editor), FALSE); commit_all_fields (editor); close = prompt_and_save_changes (editor, TRUE); if (close) close_dialog (editor); return close; } /* 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. */ GSList * comp_editor_get_mime_attach_list (CompEditor *editor) { EAttachmentStore *store; EAttachmentView *view; GtkTreeModel *model; GtkTreeIter iter; struct CalMimeAttach *cal_mime_attach; GSList *attach_list = NULL; gboolean valid; view = E_ATTACHMENT_VIEW (editor->priv->attachment_view); store = e_attachment_view_get_store (view); 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 = g_strdup ( 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 page_dates_changed_cb (CompEditor *editor, CompEditorPageDates *dates, CompEditorPage *page) { CompEditorPrivate *priv = editor->priv; GList *l; for (l = priv->pages; l != NULL; l = l->next) if (page != (CompEditorPage *) l->data && IS_COMP_EDITOR_PAGE (l->data)) comp_editor_page_set_dates (l->data, dates); if (!priv->warned && priv->existing_org && !priv->user_org && !(editor->priv->flags & COMP_EDITOR_NEW_ITEM)) { e_notice ( priv->notebook, GTK_MESSAGE_INFO, _("Changes made to this item may be discarded " "if an update arrives")); priv->warned = TRUE; } } static void obj_modified_cb (ECalClientView *view, const GSList *objects, CompEditor *editor) { CompEditorPrivate *priv; ECalComponent *comp = NULL; priv = editor->priv; /* We queried based on a specific UID so we definitely changed */ if (changed_component_dialog ( (GtkWindow *) editor, priv->comp, FALSE, priv->changed)) { icalcomponent *icalcomp = icalcomponent_new_clone (objects->data); comp = e_cal_component_new (); if (e_cal_component_set_icalcomponent (comp, icalcomp)) { comp_editor_edit_comp (editor, comp); } else { GtkWidget *dialog; dialog = gtk_message_dialog_new ( NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "%s", _("Unable to use current version!")); gtk_dialog_run (GTK_DIALOG (dialog)); gtk_widget_destroy (dialog); icalcomponent_free (icalcomp); } g_object_unref (comp); } } static void obj_removed_cb (ECalClientView *view, const GSList *uids, CompEditor *editor) { CompEditorPrivate *priv = editor->priv; if (changed_component_dialog ( GTK_WINDOW (editor), priv->comp, TRUE, priv->changed)) close_dialog (editor); }