/* * * 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: * Michael Zucchi * Rodrigo Moya * * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) * */ /* Convert a mail message into a task */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define E_SHELL_WINDOW_ACTION_CONVERT_TO_APPOINTMENT(window) \ E_SHELL_WINDOW_ACTION ((window), "mail-convert-to-appointment") #define E_SHELL_WINDOW_ACTION_CONVERT_TO_MEETING(window) \ E_SHELL_WINDOW_ACTION ((window), "mail-convert-to-meeting") #define E_SHELL_WINDOW_ACTION_CONVERT_TO_MEMO(window) \ E_SHELL_WINDOW_ACTION ((window), "mail-convert-to-memo") #define E_SHELL_WINDOW_ACTION_CONVERT_TO_TASK(window) \ E_SHELL_WINDOW_ACTION ((window), "mail-convert-to-task") gboolean mail_browser_init (GtkUIManager *ui_manager, EMailBrowser *browser); gboolean mail_shell_view_init (GtkUIManager *ui_manager, EShellView *shell_view); static CompEditor * get_component_editor (EShell *shell, ECalClient *client, ECalComponent *comp, gboolean is_new, GError **error) { ECalComponentId *id; CompEditorFlags flags = 0; CompEditor *editor = NULL; ESourceRegistry *registry; g_return_val_if_fail (E_IS_SHELL (shell), NULL); g_return_val_if_fail (E_IS_CAL_CLIENT (client), NULL); g_return_val_if_fail (E_IS_CAL_COMPONENT (comp), NULL); registry = e_shell_get_registry (shell); id = e_cal_component_get_id (comp); g_return_val_if_fail (id != NULL, NULL); g_return_val_if_fail (id->uid != NULL, NULL); if (is_new) { flags |= COMP_EDITOR_NEW_ITEM; } else { editor = comp_editor_find_instance (id->uid); } if (!editor) { if (itip_organizer_is_user (registry, comp, client)) flags |= COMP_EDITOR_USER_ORG; switch (e_cal_component_get_vtype (comp)) { case E_CAL_COMPONENT_EVENT: if (e_cal_component_has_attendees (comp)) flags |= COMP_EDITOR_MEETING; editor = event_editor_new (client, shell, flags); if (flags & COMP_EDITOR_MEETING) event_editor_show_meeting (EVENT_EDITOR (editor)); break; case E_CAL_COMPONENT_TODO: if (e_cal_component_has_attendees (comp)) flags |= COMP_EDITOR_IS_ASSIGNED; editor = task_editor_new (client, shell, flags); if (flags & COMP_EDITOR_IS_ASSIGNED) task_editor_show_assignment (TASK_EDITOR (editor)); break; case E_CAL_COMPONENT_JOURNAL: if (e_cal_component_has_organizer (comp)) flags |= COMP_EDITOR_IS_SHARED; editor = memo_editor_new (client, shell, flags); break; default: if (error) *error = e_client_error_create (E_CLIENT_ERROR_INVALID_ARG, NULL); break; } if (editor) { comp_editor_edit_comp (editor, comp); /* request save for new events */ comp_editor_set_changed (editor, is_new); } } e_cal_component_free_id (id); return editor; } static void set_attendees (ECalComponent *comp, CamelMimeMessage *message, const gchar *organizer) { GSList *attendees = NULL, *to_free = NULL; ECalComponentAttendee *ca; CamelInternetAddress *from = NULL, *to, *cc, *bcc, *arr[4]; gint len, i, j; if (message->reply_to) from = message->reply_to; else if (message->from) from = message->from; to = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_TO); cc = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_CC); bcc = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_BCC); arr[0] = from; arr[1] = to; arr[2] = cc; arr[3] = bcc; for (j = 0; j < 4; j++) { if (!arr[j]) continue; len = CAMEL_ADDRESS (arr[j])->addresses->len; for (i = 0; i < len; i++) { const gchar *name, *addr; if (camel_internet_address_get (arr[j], i, &name, &addr)) { gchar *temp; temp = g_strconcat ("mailto:", addr, NULL); if (organizer && g_ascii_strcasecmp (temp, organizer) == 0) { /* do not add organizer twice */ g_free (temp); continue; } ca = g_new0 (ECalComponentAttendee, 1); ca->value = temp; ca->cn = name; ca->cutype = ICAL_CUTYPE_INDIVIDUAL; ca->status = ICAL_PARTSTAT_NEEDSACTION; if (j == 0) { /* From */ ca->role = ICAL_ROLE_CHAIR; } else if (j == 2) { /* BCC */ ca->role = ICAL_ROLE_OPTPARTICIPANT; } else { /* all other */ ca->role = ICAL_ROLE_REQPARTICIPANT; } to_free = g_slist_prepend (to_free, temp); attendees = g_slist_append (attendees, ca); } } } e_cal_component_set_attendee_list (comp, attendees); g_slist_foreach (attendees, (GFunc) g_free, NULL); g_slist_foreach (to_free, (GFunc) g_free, NULL); g_slist_free (to_free); g_slist_free (attendees); } static const gchar * prepend_from (CamelMimeMessage *message, gchar **text) { gchar *res, *tmp, *addr = NULL; const gchar *name = NULL, *eml = NULL; CamelInternetAddress *from = NULL; g_return_val_if_fail (message != NULL, NULL); g_return_val_if_fail (text != NULL, NULL); if (message->reply_to) from = message->reply_to; else if (message->from) from = message->from; if (from && camel_internet_address_get (from, 0, &name, &eml)) addr = camel_internet_address_format_address (name, eml); /* To Translators: The full sentence looks like: "Created from a mail by John Doe " */ tmp = g_strdup_printf (_("Created from a mail by %s"), addr ? addr : ""); res = g_strconcat (tmp, "\n", *text, NULL); g_free (tmp); g_free (*text); *text = res; return res; } static void set_description (ECalComponent *comp, CamelMimeMessage *message) { CamelDataWrapper *content; CamelStream *stream; CamelContentType *type; CamelMimePart *mime_part = CAMEL_MIME_PART (message); ECalComponentText *text = NULL; GByteArray *byte_array; GSList *sl = NULL; gchar *str, *convert_str = NULL; gsize bytes_read, bytes_written; gint count = 2; content = camel_medium_get_content ((CamelMedium *) message); if (!content) return; /* * Get non-multipart content from multipart message. */ while (CAMEL_IS_MULTIPART (content) && count > 0) { mime_part = camel_multipart_get_part (CAMEL_MULTIPART (content), 0); content = camel_medium_get_content (CAMEL_MEDIUM (mime_part)); count--; } if (!mime_part) return; type = camel_mime_part_get_content_type (mime_part); if (!camel_content_type_is (type, "text", "plain")) return; byte_array = g_byte_array_new (); stream = camel_stream_mem_new_with_byte_array (byte_array); camel_data_wrapper_decode_to_stream_sync (content, stream, NULL, NULL); str = g_strndup ((gchar *) byte_array->data, byte_array->len); g_object_unref (stream); /* convert to UTF-8 string */ if (str && content->mime_type->params && content->mime_type->params->value) { convert_str = g_convert ( str, strlen (str), "UTF-8", content->mime_type->params->value, &bytes_read, &bytes_written, NULL); } text = g_new0 (ECalComponentText, 1); if (convert_str) text->value = prepend_from (message, &convert_str); else text->value = prepend_from (message, &str); text->altrep = NULL; sl = g_slist_append (sl, text); e_cal_component_set_description_list (comp, sl); g_free (str); if (convert_str) g_free (convert_str); e_cal_component_free_text_list (sl); } static gchar * set_organizer (ECalComponent *comp, CamelFolder *folder) { EShell *shell; ESource *source = NULL; ESourceRegistry *registry; ESourceMailIdentity *extension; const gchar *extension_name; const gchar *address, *name; ECalComponentOrganizer organizer = {NULL, NULL, NULL, NULL}; gchar *mailto = NULL; shell = e_shell_get_default (); registry = e_shell_get_registry (shell); if (folder != NULL) { CamelStore *store; store = camel_folder_get_parent_store (folder); source = em_utils_ref_mail_identity_for_store (registry, store); } if (source == NULL) source = e_source_registry_ref_default_mail_identity (registry); g_return_val_if_fail (source != NULL, NULL); extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY; extension = e_source_get_extension (source, extension_name); name = e_source_mail_identity_get_name (extension); address = e_source_mail_identity_get_address (extension); if (name != NULL && address != NULL) { mailto = g_strconcat ("mailto:", address, NULL); organizer.value = mailto; organizer.cn = name; e_cal_component_set_organizer (comp, &organizer); } g_object_unref (source); return mailto; } struct _att_async_cb_data { gchar **uris; EFlag *flag; }; static void attachment_load_finished (EAttachmentStore *store, GAsyncResult *result, gpointer user_data) { struct _att_async_cb_data *data = user_data; /* XXX Should be no need to check for error here. * This is just to reset state in the EAttachment. */ e_attachment_store_load_finish (store, result, NULL); e_flag_set (data->flag); } static void attachment_save_finished (EAttachmentStore *store, GAsyncResult *result, gpointer user_data) { struct _att_async_cb_data *data = user_data; gchar **uris; GError *error = NULL; uris = e_attachment_store_save_finish (store, result, &error); if (error) data->uris = NULL; else data->uris = uris; g_clear_error (&error); e_flag_set (data->flag); } static void set_attachments (ECalClient *client, ECalComponent *comp, CamelMimeMessage *message) { /* XXX Much of this is copied from CompEditor::get_attachment_list(). * Perhaps it should be split off as a separate utility? */ EAttachmentStore *store; CamelDataWrapper *content; CamelMultipart *multipart; GFile *destination; GList *attachment_list = NULL; GSList *uri_list = NULL; const gchar *comp_uid = NULL; const gchar *local_store; gchar *filename_prefix, *tmp; gint ii, n_parts; struct _att_async_cb_data cb_data; cb_data.flag = e_flag_new (); cb_data.uris = NULL; content = camel_medium_get_content ((CamelMedium *) message); if (!content || !CAMEL_IS_MULTIPART (content)) return; n_parts = camel_multipart_get_number (CAMEL_MULTIPART (content)); if (n_parts < 1) return; e_cal_component_get_uid (comp, &comp_uid); g_return_if_fail (comp_uid != NULL); 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 (client); destination = g_file_new_for_path (local_store); /* Create EAttachments from the MIME parts and add them to the * attachment store. */ multipart = CAMEL_MULTIPART (content); store = E_ATTACHMENT_STORE (e_attachment_store_new ()); for (ii = 1; ii < n_parts; ii++) { EAttachment *attachment; CamelMimePart *mime_part; attachment = e_attachment_new (); mime_part = camel_multipart_get_part (multipart, ii); e_attachment_set_mime_part (attachment, mime_part); attachment_list = g_list_append (attachment_list, attachment); } e_flag_clear (cb_data.flag); e_attachment_store_load_async ( store, attachment_list, (GAsyncReadyCallback) attachment_load_finished, &cb_data); /* Loading should be instantaneous since we already have * the full content, but we need to wait for the callback. */ e_flag_wait (cb_data.flag); g_list_foreach (attachment_list, (GFunc) g_object_unref, NULL); g_list_free (attachment_list); cb_data.uris = NULL; e_flag_clear (cb_data.flag); e_attachment_store_save_async ( store, destination, filename_prefix, (GAsyncReadyCallback) attachment_save_finished, &cb_data); g_free (filename_prefix); /* We can't return until we have results. */ e_flag_wait (cb_data.flag); if (cb_data.uris == NULL) { e_flag_free (cb_data.flag); g_warning ("No attachment URIs retrieved."); return; } /* Transfer the URI strings to the GSList. */ for (ii = 0; cb_data.uris[ii] != NULL; ii++) { uri_list = g_slist_prepend (uri_list, cb_data.uris[ii]); cb_data.uris[ii] = NULL; } e_flag_free (cb_data.flag); g_free (cb_data.uris); /* XXX Does this take ownership of the list? */ e_cal_component_set_attachment_list (comp, uri_list); e_attachment_store_remove_all (store); g_object_unref (destination); g_object_unref (store); } static void set_priority (ECalComponent *comp, CamelMimePart *part) { const gchar *prio; g_return_if_fail (comp != NULL); g_return_if_fail (part != NULL); prio = camel_header_raw_find (& (part->headers), "X-Priority", NULL); if (prio && atoi (prio) > 0) { gint priority = 1; e_cal_component_set_priority (comp, &priority); } } struct _report_error { gchar *format; gchar *param; }; static gboolean do_report_error (struct _report_error *err) { if (err) { e_notice (NULL, GTK_MESSAGE_ERROR, err->format, err->param); g_free (err->format); g_free (err->param); g_free (err); } return FALSE; } static void report_error_idle (const gchar *format, const gchar *param) { struct _report_error *err = g_new (struct _report_error, 1); err->format = g_strdup (format); err->param = g_strdup (param); g_usleep (250); g_idle_add ((GSourceFunc) do_report_error, err); } struct _manage_comp { ECalClient *client; ECalComponent *comp; icalcomponent *stored_comp; /* the one in client already */ GCond cond; GMutex mutex; gint mails_count; gint mails_done; gchar *editor_title; gboolean can_continue; }; static void free_manage_comp_struct (struct _manage_comp *mc) { g_return_if_fail (mc != NULL); g_object_unref (mc->comp); g_object_unref (mc->client); if (mc->stored_comp) icalcomponent_free (mc->stored_comp); g_mutex_clear (&mc->mutex); g_cond_clear (&mc->cond); if (mc->editor_title) g_free (mc->editor_title); g_free (mc); } static gint do_ask (const gchar *text, gboolean is_create_edit_add) { gint res; GtkWidget *dialog = gtk_message_dialog_new ( NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION, is_create_edit_add ? GTK_BUTTONS_NONE : GTK_BUTTONS_YES_NO, "%s", text); if (is_create_edit_add) { gtk_dialog_add_buttons ( GTK_DIALOG (dialog), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_EDIT, GTK_RESPONSE_YES, GTK_STOCK_NEW, GTK_RESPONSE_NO, NULL); } res = gtk_dialog_run (GTK_DIALOG (dialog)); gtk_widget_destroy (dialog); return res; } static const gchar * get_question_edit_old (ECalClientSourceType source_type) { const gchar *ask = NULL; switch (source_type) { case E_CAL_CLIENT_SOURCE_TYPE_EVENTS: ask = _("Selected calendar contains event '%s' already. Would you like to edit the old event?"); break; case E_CAL_CLIENT_SOURCE_TYPE_TASKS: ask = _("Selected task list contains task '%s' already. Would you like to edit the old task?"); break; case E_CAL_CLIENT_SOURCE_TYPE_MEMOS: ask = _("Selected memo list contains memo '%s' already. Would you like to edit the old memo?"); break; default: g_assert_not_reached (); break; } return ask; } static const gchar * get_question_add_all_mails (ECalClientSourceType source_type, gint count) { const gchar *ask = NULL; switch (source_type) { case E_CAL_CLIENT_SOURCE_TYPE_EVENTS: /* Translators: Note there are always more than 10 mails selected */ ask = ngettext ( "You have selected %d mails to be converted to events. Do you really want to add them all?", "You have selected %d mails to be converted to events. Do you really want to add them all?", count); break; case E_CAL_CLIENT_SOURCE_TYPE_TASKS: /* Translators: Note there are always more than 10 mails selected */ ask = ngettext ( "You have selected %d mails to be converted to tasks. Do you really want to add them all?", "You have selected %d mails to be converted to tasks. Do you really want to add them all?", count); break; case E_CAL_CLIENT_SOURCE_TYPE_MEMOS: /* Translators: Note there are always more than 10 mails selected */ ask = ngettext ( "You have selected %d mails to be converted to memos. Do you really want to add them all?", "You have selected %d mails to be converted to memos. Do you really want to add them all?", count); break; default: g_assert_not_reached (); break; } return ask; } static void comp_editor_closed (CompEditor *editor, gboolean accepted, struct _manage_comp *mc) { if (!mc) return; if (!accepted && mc->mails_done < mc->mails_count) mc->can_continue = (do_ask (_("Do you wish to continue converting remaining mails?"), FALSE) == GTK_RESPONSE_YES); /* Signal the do_mail_to_event thread that editor was closed and editor * for next event can be displayed (if any) */ g_cond_signal (&mc->cond); } /* * This handler takes title of the editor window and * inserts information about number of processed mails and * number of all mails to process, so the window title * will look like "Appointment (3/10) - An appoitment name" */ static void comp_editor_title_changed (GtkWidget *widget, GParamSpec *pspec, struct _manage_comp *mc) { GtkWindow *editor = GTK_WINDOW (widget); const gchar *title = gtk_window_get_title (editor); gchar *new_title; gchar *splitter; gchar *comp_name, *task_name; if (!mc) return; /* Recursion prevence */ if (mc->editor_title && g_utf8_collate (mc->editor_title, title) == 0) return; splitter = strchr (title, '-'); if (!splitter) return; comp_name = g_strndup (title, splitter - title - 1); task_name = g_strdup (splitter + 2); new_title = g_strdup_printf ( "%s (%d/%d) - %s", comp_name, mc->mails_done, mc->mails_count, task_name); /* Remember the new title, so that when gtk_window_set_title() causes * this handler to be recursively called, we can recognize that and * prevent endless recursion */ if (mc->editor_title) g_free (mc->editor_title); mc->editor_title = new_title; gtk_window_set_title (editor, new_title); g_free (comp_name); g_free (task_name); } static gboolean do_manage_comp_idle (struct _manage_comp *mc) { GError *error = NULL; ECalClientSourceType source_type = E_CAL_CLIENT_SOURCE_TYPE_LAST; ECalComponent *edit_comp = NULL; g_return_val_if_fail (mc, FALSE); source_type = e_cal_client_get_source_type (mc->client); if (source_type == E_CAL_CLIENT_SOURCE_TYPE_LAST) { free_manage_comp_struct (mc); g_warning ("mail-to-task: Incorrect call of %s, no data given", G_STRFUNC); return FALSE; } if (mc->stored_comp) { const gchar *ask = get_question_edit_old (source_type); if (ask) { gchar *msg = g_strdup_printf (ask, icalcomponent_get_summary (mc->stored_comp) ? icalcomponent_get_summary (mc->stored_comp) : _("[No Summary]")); gint chosen; chosen = do_ask (msg, TRUE); if (chosen == GTK_RESPONSE_YES) { edit_comp = e_cal_component_new (); if (!e_cal_component_set_icalcomponent (edit_comp, icalcomponent_new_clone (mc->stored_comp))) { g_object_unref (edit_comp); edit_comp = NULL; error = g_error_new ( E_CAL_CLIENT_ERROR, E_CAL_CLIENT_ERROR_INVALID_OBJECT, "%s", _("Invalid object returned from a server")); } } else if (chosen == GTK_RESPONSE_NO) { /* user wants to create a new event, thus generate a new UID */ gchar *new_uid = e_cal_component_gen_uid (); edit_comp = mc->comp; e_cal_component_set_uid (edit_comp, new_uid); e_cal_component_set_recurid (edit_comp, NULL); g_free (new_uid); } g_free (msg); } } else { edit_comp = mc->comp; } if (edit_comp) { EShell *shell; CompEditor *editor; /* FIXME Pass in the EShell instance. */ shell = e_shell_get_default (); editor = get_component_editor ( shell, mc->client, edit_comp, edit_comp == mc->comp, &error); if (editor && !error) { /* Force editor's title change */ comp_editor_title_changed (GTK_WIDGET (editor), NULL, mc); g_signal_connect ( editor, "notify::title", G_CALLBACK (comp_editor_title_changed), mc); g_signal_connect ( editor, "comp_closed", G_CALLBACK (comp_editor_closed), mc); gtk_window_present (GTK_WINDOW (editor)); if (edit_comp != mc->comp) g_object_unref (edit_comp); } else { g_warning ("Failed to create event editor: %s", error ? error->message : "Unknown error"); g_cond_signal (&mc->cond); } } else { /* User canceled editing already existing event, so treat it as if he just closed the editor window */ comp_editor_closed (NULL, FALSE, mc); } if (error) { e_notice (NULL, GTK_MESSAGE_ERROR, _("An error occurred during processing: %s"), error->message); g_clear_error (&error); } return FALSE; } typedef struct { ESource *source; ECalClientSourceType source_type; CamelFolder *folder; GPtrArray *uids; gchar *selected_text; gboolean with_attendees; }AsyncData; static gboolean do_mail_to_event (AsyncData *data) { EClient *client; CamelFolder *folder = data->folder; GPtrArray *uids = data->uids; GError *error = NULL; client = e_cal_client_connect_sync ( data->source, data->source_type, NULL, &error); /* Sanity check. */ g_return_val_if_fail ( ((client != NULL) && (error == NULL)) || ((client == NULL) && (error != NULL)), TRUE); if (error != NULL) { report_error_idle (_("Cannot open calendar. %s"), error->message); } else if (e_client_is_readonly (E_CLIENT (client))) { if (error != NULL) report_error_idle ("Check readonly failed. %s", error->message); else { switch (data->source_type) { case E_CAL_CLIENT_SOURCE_TYPE_EVENTS: report_error_idle (_("Selected calendar is read only, thus cannot create event there. Select other calendar, please."), NULL); break; case E_CAL_CLIENT_SOURCE_TYPE_TASKS: report_error_idle (_("Selected task list is read only, thus cannot create task there. Select other task list, please."), NULL); break; case E_CAL_CLIENT_SOURCE_TYPE_MEMOS: report_error_idle (_("Selected memo list is read only, thus cannot create memo there. Select other memo list, please."), NULL); break; default: g_assert_not_reached (); break; } } } else { gint i; ECalComponentDateTime dt, dt2; struct icaltimetype tt, tt2; struct _manage_comp *oldmc = NULL; #define cache_backend_prop(prop) { \ gchar *val = NULL; \ e_client_get_backend_property_sync (E_CLIENT (client), prop, &val, NULL, NULL); \ g_free (val); \ } /* precache backend properties, thus editor have them ready when needed */ cache_backend_prop (CAL_BACKEND_PROPERTY_CAL_EMAIL_ADDRESS); cache_backend_prop (CAL_BACKEND_PROPERTY_ALARM_EMAIL_ADDRESS); cache_backend_prop (CAL_BACKEND_PROPERTY_DEFAULT_OBJECT); e_client_get_capabilities (E_CLIENT (client)); #undef cache_backend_prop /* set start day of the event as today, without time - easier than looking for a calendar's time zone */ tt = icaltime_today (); dt.value = &tt; dt.tzid = NULL; tt2 = tt; icaltime_adjust (&tt2, 1, 0, 0, 0); dt2.value = &tt2; dt2.tzid = NULL; for (i = 0; i < (uids ? uids->len : 0); i++) { CamelMimeMessage *message; ECalComponent *comp; ECalComponentText text; icalproperty *icalprop; icalcomponent *icalcomp; struct _manage_comp *mc; /* retrieve the message from the CamelFolder */ /* FIXME Not passing a GCancellable or GError. */ message = camel_folder_get_message_sync ( folder, g_ptr_array_index (uids, i), NULL, NULL); if (!message) { continue; } comp = e_cal_component_new (); switch (data->source_type) { case E_CAL_CLIENT_SOURCE_TYPE_EVENTS: e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_EVENT); break; case E_CAL_CLIENT_SOURCE_TYPE_TASKS: e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_TODO); break; case E_CAL_CLIENT_SOURCE_TYPE_MEMOS: e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_JOURNAL); break; default: g_assert_not_reached (); break; } e_cal_component_set_uid (comp, camel_mime_message_get_message_id (message)); e_cal_component_set_dtstart (comp, &dt); if (data->source_type == E_CAL_CLIENT_SOURCE_TYPE_EVENTS) { /* make it an all-day event */ e_cal_component_set_dtend (comp, &dt2); } /* set the summary */ text.value = camel_mime_message_get_subject (message); text.altrep = NULL; e_cal_component_set_summary (comp, &text); /* set all fields */ if (data->selected_text) { GSList sl; text.value = data->selected_text; text.altrep = NULL; sl.next = NULL; sl.data = &text; e_cal_component_set_description_list (comp, &sl); } else set_description (comp, message); if (data->with_attendees) { gchar *organizer; /* set actual user as organizer, to be able to change event's properties */ organizer = set_organizer (comp, data->folder); set_attendees (comp, message, organizer); g_free (organizer); } /* set attachment files */ set_attachments (E_CAL_CLIENT (client), comp, message); /* priority */ set_priority (comp, CAMEL_MIME_PART (message)); /* no need to increment a sequence number, this is a new component */ e_cal_component_abort_sequence (comp); icalcomp = e_cal_component_get_icalcomponent (comp); icalprop = icalproperty_new_x ("1"); icalproperty_set_x_name (icalprop, "X-EVOLUTION-MOVE-CALENDAR"); icalcomponent_add_property (icalcomp, icalprop); mc = g_new0 (struct _manage_comp, 1); mc->client = g_object_ref (client); mc->comp = g_object_ref (comp); g_mutex_init (&mc->mutex); g_cond_init (&mc->cond); mc->mails_count = uids->len; mc->mails_done = i + 1; /* Current task */ mc->editor_title = NULL; mc->can_continue = TRUE; if (oldmc) { /* Wait for user to quit the editor created in previous iteration * before displaying next one */ gboolean can_continue; g_mutex_lock (&oldmc->mutex); g_cond_wait (&oldmc->cond, &oldmc->mutex); g_mutex_unlock (&oldmc->mutex); can_continue = oldmc->can_continue; free_manage_comp_struct (oldmc); oldmc = NULL; if (!can_continue) break; } if (!e_cal_client_get_object_sync (E_CAL_CLIENT (client), icalcomponent_get_uid (icalcomp), NULL, &(mc->stored_comp), NULL, NULL)) mc->stored_comp = NULL; /* Prioritize ahead of GTK+ redraws. */ g_idle_add_full ( G_PRIORITY_HIGH_IDLE, (GSourceFunc) do_manage_comp_idle, mc, NULL); oldmc = mc; g_object_unref (comp); g_object_unref (message); } /* Wait for the last editor and then clean up */ if (oldmc) { g_mutex_lock (&oldmc->mutex); g_cond_wait (&oldmc->cond, &oldmc->mutex); g_mutex_unlock (&oldmc->mutex); free_manage_comp_struct (oldmc); } } /* free memory */ if (client != NULL) g_object_unref (client); g_ptr_array_unref (uids); g_object_unref (folder); g_object_unref (data->source); g_free (data->selected_text); g_free (data); data = NULL; if (error != NULL) g_error_free (error); return TRUE; } static gboolean text_contains_nonwhitespace (const gchar *text, gint len) { const gchar *p; gunichar c = 0; if (!text || len <= 0) return FALSE; p = text; while (p && p - text < len) { c = g_utf8_get_char (p); if (!c) break; if (!g_unichar_isspace (c)) break; p = g_utf8_next_char (p); } return p - text < len - 1 && c != 0; } /* should be freed with g_free after done with it */ static gchar * get_selected_text (EMailReader *reader) { EMailDisplay *display; gchar *text = NULL; display = e_mail_reader_get_mail_display (reader); if (!e_web_view_is_selection_active (E_WEB_VIEW (display))) return NULL; text = e_mail_display_get_selection_plain_text (display); if (text == NULL || !text_contains_nonwhitespace (text, strlen (text))) { g_free (text); return NULL; } return text; } static void mail_to_event (ECalClientSourceType source_type, gboolean with_attendees, EMailReader *reader) { EShell *shell; EMailBackend *backend; ESourceRegistry *registry; GPtrArray *uids; ESource *source = NULL; ESource *default_source; GList *list, *iter; GtkWindow *parent; const gchar *extension_name; GError *error = NULL; parent = e_mail_reader_get_window (reader); uids = e_mail_reader_get_selected_uids (reader); /* Ask before converting 10 or more mails to events. */ if (uids->len > 10) { gchar *question; gint response; question = g_strdup_printf ( get_question_add_all_mails (source_type, uids->len), uids->len); response = do_ask (question, FALSE); g_free (question); if (response == GTK_RESPONSE_NO) { g_ptr_array_unref (uids); return; } } backend = e_mail_reader_get_backend (reader); shell = e_shell_backend_get_shell (E_SHELL_BACKEND (backend)); registry = e_shell_get_registry (shell); switch (source_type) { case E_CAL_CLIENT_SOURCE_TYPE_EVENTS: extension_name = E_SOURCE_EXTENSION_CALENDAR; default_source = e_source_registry_ref_default_calendar (registry); break; case E_CAL_CLIENT_SOURCE_TYPE_MEMOS: extension_name = E_SOURCE_EXTENSION_MEMO_LIST; default_source = e_source_registry_ref_default_memo_list (registry); break; case E_CAL_CLIENT_SOURCE_TYPE_TASKS: extension_name = E_SOURCE_EXTENSION_TASK_LIST; default_source = e_source_registry_ref_default_task_list (registry); break; default: g_return_if_reached (); } list = e_source_registry_list_sources (registry, extension_name); /* If there is only one writable source, no need to prompt the user. */ for (iter = list; iter != NULL; iter = g_list_next (iter)) { ESource *candidate = E_SOURCE (iter->data); if (e_source_get_writable (candidate)) { if (source == NULL) source = candidate; else { source = NULL; break; } } } g_list_free_full (list, (GDestroyNotify) g_object_unref); if (source == NULL) { GtkWidget *dialog; ESourceSelector *selector; /* ask the user which tasks list to save to */ dialog = e_source_selector_dialog_new ( parent, registry, extension_name); selector = e_source_selector_dialog_get_selector ( E_SOURCE_SELECTOR_DIALOG (dialog)); e_source_selector_set_primary_selection ( selector, default_source); if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK) source = e_source_selector_dialog_peek_primary_selection ( E_SOURCE_SELECTOR_DIALOG (dialog)); gtk_widget_destroy (dialog); } else if (!source && default_source) { source = default_source; } else if (!source) { e_notice (NULL, GTK_MESSAGE_ERROR, _("No writable calendar is available.")); if (error) g_error_free (error); goto exit; } if (source) { /* if a source has been selected, perform the mail2event operation */ AsyncData *data = NULL; GThread *thread = NULL; GError *error = NULL; /* Fill the elements in AsynData */ data = g_new0 (AsyncData, 1); data->source = g_object_ref (source); data->source_type = source_type; data->folder = e_mail_reader_ref_folder (reader); data->uids = g_ptr_array_ref (uids); data->with_attendees = with_attendees; if (uids->len == 1) data->selected_text = get_selected_text (reader); else data->selected_text = NULL; thread = g_thread_try_new ( NULL, (GThreadFunc) do_mail_to_event, data, &error); if (error != NULL) { g_warning (G_STRLOC ": %s", error->message); g_error_free (error); } else { g_thread_unref (thread); } } exit: g_object_unref (default_source); g_ptr_array_unref (uids); } static void action_mail_convert_to_event_cb (GtkAction *action, EMailReader *reader) { mail_to_event (E_CAL_CLIENT_SOURCE_TYPE_EVENTS, FALSE, reader); } static void action_mail_convert_to_meeting_cb (GtkAction *action, EMailReader *reader) { mail_to_event (E_CAL_CLIENT_SOURCE_TYPE_EVENTS, TRUE, reader); } static void action_mail_convert_to_memo_cb (GtkAction *action, EMailReader *reader) { mail_to_event (E_CAL_CLIENT_SOURCE_TYPE_MEMOS, FALSE, reader); } static void action_mail_convert_to_task_cb (GtkAction *action, EMailReader *reader) { mail_to_event (E_CAL_CLIENT_SOURCE_TYPE_TASKS, FALSE, reader); } /* Note, we're not using EPopupActions here because we update the state * of entire actions groups instead of individual actions. EPopupActions * just proxy the state of individual actions. */ static GtkActionEntry multi_selection_entries[] = { { "mail-convert-to-appointment", "appointment-new", N_("Create an _Appointment"), NULL, N_("Create a new event from the selected message"), G_CALLBACK (action_mail_convert_to_event_cb) }, { "mail-convert-to-memo", "stock_insert-note", N_("Create a Mem_o"), NULL, N_("Create a new memo from the selected message"), G_CALLBACK (action_mail_convert_to_memo_cb) }, { "mail-convert-to-task", "stock_todo", N_("Create a _Task"), NULL, N_("Create a new task from the selected message"), G_CALLBACK (action_mail_convert_to_task_cb) } }; static GtkActionEntry single_selection_entries[] = { { "mail-convert-to-meeting", "stock_new-meeting", N_("Create a _Meeting"), NULL, N_("Create a new meeting from the selected message"), G_CALLBACK (action_mail_convert_to_meeting_cb) } }; static void update_actions_any_cb (EMailReader *reader, guint32 state, GtkActionGroup *action_group) { gboolean sensitive; sensitive = (state & E_MAIL_READER_SELECTION_SINGLE) || (state & E_MAIL_READER_SELECTION_MULTIPLE); gtk_action_group_set_sensitive (action_group, sensitive); } static void update_actions_one_cb (EMailReader *reader, guint32 state, GtkActionGroup *action_group) { gboolean sensitive; sensitive = (state & E_MAIL_READER_SELECTION_SINGLE); gtk_action_group_set_sensitive (action_group, sensitive); } static void setup_actions (EMailReader *reader, GtkUIManager *ui_manager) { GtkActionGroup *action_group; const gchar *domain = GETTEXT_PACKAGE; action_group = gtk_action_group_new ("mail-convert-any"); gtk_action_group_set_translation_domain (action_group, domain); gtk_action_group_add_actions ( action_group, multi_selection_entries, G_N_ELEMENTS (multi_selection_entries), reader); gtk_ui_manager_insert_action_group (ui_manager, action_group, 0); g_object_unref (action_group); /* GtkUIManager now owns the action group reference. * The signal we're connecting to will only be emitted * during the GtkUIManager's lifetime, so the action * group will not disappear on us. */ g_signal_connect ( reader, "update-actions", G_CALLBACK (update_actions_any_cb), action_group); action_group = gtk_action_group_new ("mail-convert-one"); gtk_action_group_set_translation_domain (action_group, domain); gtk_action_group_add_actions ( action_group, single_selection_entries, G_N_ELEMENTS (single_selection_entries), reader); gtk_ui_manager_insert_action_group (ui_manager, action_group, 0); g_object_unref (action_group); /* GtkUIManager now owns the action group reference. * The signal we're connecting to will only be emitted * during the GtkUIManager's lifetime, so the action * group will not disappear on us. */ g_signal_connect ( reader, "update-actions", G_CALLBACK (update_actions_one_cb), action_group); } gboolean mail_browser_init (GtkUIManager *ui_manager, EMailBrowser *browser) { setup_actions (E_MAIL_READER (browser), ui_manager); return TRUE; } gboolean mail_shell_view_init (GtkUIManager *ui_manager, EShellView *shell_view) { EShellContent *shell_content; shell_content = e_shell_view_get_shell_content (shell_view); setup_actions (E_MAIL_READER (shell_content), ui_manager); return TRUE; }