Bug 602612 - Add 'Alternative Reply' menu option
[evolution.git] / src / mail / em-composer-utils.c
blob2aaa3690772650d47632b06fe1f108563df3bf70
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 /*
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation.
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
11 * for more details.
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program; if not, see <http://www.gnu.org/licenses/>.
17 * Authors:
18 * Jeffrey Stedfast <fejj@ximian.com>
20 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
24 #include "evolution-config.h"
26 #include <string.h>
27 #include <gtk/gtk.h>
28 #include <glib/gi18n.h>
30 #include <e-util/e-util.h>
32 #include <libemail-engine/libemail-engine.h>
34 #include <em-format/e-mail-parser.h>
35 #include <em-format/e-mail-part-utils.h>
36 #include <em-format/e-mail-formatter-quote.h>
38 #include <shell/e-shell.h>
40 #include <composer/e-msg-composer.h>
41 #include <composer/e-composer-actions.h>
42 #include <composer/e-composer-post-header.h>
44 #include "e-mail-printer.h"
45 #include "e-mail-ui-session.h"
46 #include "e-mail-templates.h"
47 #include "e-mail-templates-store.h"
48 #include "em-utils.h"
49 #include "em-composer-utils.h"
50 #include "em-folder-selector.h"
51 #include "em-folder-tree.h"
52 #include "em-event.h"
53 #include "mail-send-recv.h"
55 #ifdef G_OS_WIN32
56 #ifdef gmtime_r
57 #undef gmtime_r
58 #endif
59 #ifdef localtime_r
60 #undef localtime_r
61 #endif
63 /* The gmtime() and localtime() in Microsoft's C library are MT-safe */
64 #define gmtime_r(tp,tmp) (gmtime(tp)?(*(tmp)=*gmtime(tp),(tmp)):0)
65 #define localtime_r(tp,tmp) (localtime(tp)?(*(tmp)=*localtime(tp),(tmp)):0)
66 #endif
68 typedef struct _AsyncContext AsyncContext;
69 typedef struct _ForwardData ForwardData;
71 struct _AsyncContext {
72 CamelMimeMessage *message;
73 EMailSession *session;
74 EMsgComposer *composer;
75 EActivity *activity;
76 gchar *folder_uri;
77 gchar *message_uid;
78 gulong num_loading_handler_id;
79 gulong cancelled_handler_id;
82 struct _ForwardData {
83 EShell *shell;
84 CamelFolder *folder;
85 GPtrArray *uids;
86 EMailForwardStyle style;
89 static void
90 async_context_free (AsyncContext *async_context)
92 if (async_context->cancelled_handler_id) {
93 GCancellable *cancellable;
95 cancellable = e_activity_get_cancellable (async_context->activity);
96 /* Cannot use g_cancellable_disconnect(), because when this is called
97 from inside the cancelled handler, then the GCancellable deadlocks. */
98 g_signal_handler_disconnect (cancellable, async_context->cancelled_handler_id);
99 async_context->cancelled_handler_id = 0;
102 if (async_context->num_loading_handler_id) {
103 EAttachmentView *view;
104 EAttachmentStore *store;
106 view = e_msg_composer_get_attachment_view (async_context->composer);
107 store = e_attachment_view_get_store (view);
109 e_signal_disconnect_notify_handler (store, &async_context->num_loading_handler_id);
112 g_clear_object (&async_context->message);
113 g_clear_object (&async_context->session);
114 g_clear_object (&async_context->composer);
115 g_clear_object (&async_context->activity);
117 g_free (async_context->folder_uri);
118 g_free (async_context->message_uid);
120 g_slice_free (AsyncContext, async_context);
123 static void
124 forward_data_free (ForwardData *data)
126 if (data->shell != NULL)
127 g_object_unref (data->shell);
129 if (data->folder != NULL)
130 g_object_unref (data->folder);
132 if (data->uids != NULL)
133 g_ptr_array_unref (data->uids);
135 g_slice_free (ForwardData, data);
138 static gboolean
139 ask_confirm_for_unwanted_html_mail (EMsgComposer *composer,
140 EDestination **recipients)
142 gboolean res;
143 GString *str;
144 gint i;
146 str = g_string_new ("");
147 for (i = 0; recipients[i] != NULL; ++i) {
148 if (!e_destination_get_html_mail_pref (recipients[i])) {
149 const gchar *name;
151 name = e_destination_get_textrep (recipients[i], FALSE);
153 g_string_append_printf (str, " %s\n", name);
157 if (str->len)
158 res = e_util_prompt_user (
159 GTK_WINDOW (composer),
160 "org.gnome.evolution.mail", "prompt-on-unwanted-html",
161 "mail:ask-send-html", str->str, NULL);
162 else
163 res = TRUE;
165 g_string_free (str, TRUE);
167 return res;
170 static gboolean
171 ask_confirm_for_empty_subject (EMsgComposer *composer)
173 return e_util_prompt_user (
174 GTK_WINDOW (composer),
175 "org.gnome.evolution.mail",
176 "prompt-on-empty-subject",
177 "mail:ask-send-no-subject", NULL);
180 static gboolean
181 ask_confirm_for_only_bcc (EMsgComposer *composer,
182 gboolean hidden_list_case)
184 /* If the user is mailing a hidden contact list, it is possible for
185 * them to create a message with only Bcc recipients without really
186 * realizing it. To try to avoid being totally confusing, I've changed
187 * this dialog to provide slightly different text in that case, to
188 * better explain what the hell is going on. */
190 return e_util_prompt_user (
191 GTK_WINDOW (composer),
192 "org.gnome.evolution.mail",
193 "prompt-on-only-bcc",
194 hidden_list_case ?
195 "mail:ask-send-only-bcc-contact" :
196 "mail:ask-send-only-bcc", NULL);
199 static gboolean
200 is_group_definition (const gchar *str)
202 const gchar *colon;
204 if (!str || !*str)
205 return FALSE;
207 colon = strchr (str, ':');
208 return colon > str && strchr (str, ';') > colon;
211 static gboolean
212 composer_presend_check_recipients (EMsgComposer *composer,
213 EMailSession *session)
215 EDestination **recipients;
216 EDestination **recipients_bcc;
217 CamelInternetAddress *cia;
218 EComposerHeaderTable *table;
219 EComposerHeader *post_to_header;
220 GString *invalid_addrs = NULL;
221 GSettings *settings;
222 gboolean check_passed = FALSE;
223 gint hidden = 0;
224 gint shown = 0;
225 gint num = 0;
226 gint num_to_cc = 0;
227 gint num_bcc = 0;
228 gint num_post = 0;
229 gint ii;
231 /* We should do all of the validity checks based on the composer,
232 * and not on the created message, as extra interaction may occur
233 * when we get the message (e.g. passphrase to sign a message). */
235 table = e_msg_composer_get_header_table (composer);
237 recipients = e_composer_header_table_get_destinations_to (table);
238 if (recipients) {
239 for (ii = 0; recipients[ii] != NULL; ii++) {
240 const gchar *addr;
242 addr = e_destination_get_address (recipients[ii]);
243 if (addr == NULL || *addr == '\0')
244 continue;
246 num_to_cc++;
249 e_destination_freev (recipients);
252 recipients = e_composer_header_table_get_destinations_cc (table);
253 if (recipients) {
254 for (ii = 0; recipients[ii] != NULL; ii++) {
255 const gchar *addr;
257 addr = e_destination_get_address (recipients[ii]);
258 if (addr == NULL || *addr == '\0')
259 continue;
261 num_to_cc++;
264 e_destination_freev (recipients);
267 recipients = e_composer_header_table_get_destinations (table);
269 cia = camel_internet_address_new ();
271 /* See which ones are visible, present, etc. */
272 for (ii = 0; recipients != NULL && recipients[ii] != NULL; ii++) {
273 const gchar *addr;
274 gint len, j;
276 addr = e_destination_get_address (recipients[ii]);
277 if (addr == NULL || *addr == '\0')
278 continue;
280 camel_address_decode (CAMEL_ADDRESS (cia), addr);
281 len = camel_address_length (CAMEL_ADDRESS (cia));
283 if (len > 0) {
284 if (!e_destination_is_evolution_list (recipients[ii])) {
285 for (j = 0; j < len; j++) {
286 const gchar *name = NULL, *eml = NULL;
288 if (!camel_internet_address_get (cia, j, &name, &eml) ||
289 !eml ||
290 strchr (eml, '@') <= eml) {
291 if (!invalid_addrs)
292 invalid_addrs = g_string_new ("");
293 else
294 g_string_append (invalid_addrs, ", ");
296 if (name)
297 g_string_append (invalid_addrs, name);
298 if (eml) {
299 g_string_append (invalid_addrs, name ? " <" : "");
300 g_string_append (invalid_addrs, eml);
301 g_string_append (invalid_addrs, name ? ">" : "");
307 camel_address_remove (CAMEL_ADDRESS (cia), -1);
308 num++;
309 if (e_destination_is_evolution_list (recipients[ii])
310 && !e_destination_list_show_addresses (recipients[ii])) {
311 hidden++;
312 } else {
313 shown++;
315 } else if (is_group_definition (addr)) {
316 /* like an address, it will not claim on only-bcc */
317 shown++;
318 num++;
319 } else if (!invalid_addrs) {
320 invalid_addrs = g_string_new (addr);
321 } else {
322 g_string_append (invalid_addrs, ", ");
323 g_string_append (invalid_addrs, addr);
327 recipients_bcc = e_composer_header_table_get_destinations_bcc (table);
328 if (recipients_bcc) {
329 for (ii = 0; recipients_bcc[ii] != NULL; ii++) {
330 const gchar *addr;
332 addr = e_destination_get_address (recipients_bcc[ii]);
333 if (addr == NULL || *addr == '\0')
334 continue;
336 camel_address_decode (CAMEL_ADDRESS (cia), addr);
337 if (camel_address_length (CAMEL_ADDRESS (cia)) > 0) {
338 camel_address_remove (CAMEL_ADDRESS (cia), -1);
339 num_bcc++;
343 e_destination_freev (recipients_bcc);
346 g_object_unref (cia);
348 post_to_header = e_composer_header_table_get_header (
349 table, E_COMPOSER_HEADER_POST_TO);
350 if (e_composer_header_get_visible (post_to_header)) {
351 GList *postlist;
353 postlist = e_composer_header_table_get_post_to (table);
354 num_post = g_list_length (postlist);
355 g_list_foreach (postlist, (GFunc) g_free, NULL);
356 g_list_free (postlist);
359 /* I'm sensing a lack of love, er, I mean recipients. */
360 if (num == 0 && num_post == 0) {
361 EHTMLEditor *editor;
363 editor = e_msg_composer_get_editor (composer);
364 e_alert_submit (E_ALERT_SINK (editor), "mail:send-no-recipients", NULL);
366 goto finished;
369 if (invalid_addrs) {
370 if (!e_util_prompt_user (
371 GTK_WINDOW (composer),
372 "org.gnome.evolution.mail",
373 "prompt-on-invalid-recip",
374 strstr (invalid_addrs->str, ", ") ?
375 "mail:ask-send-invalid-recip-multi" :
376 "mail:ask-send-invalid-recip-one",
377 invalid_addrs->str, NULL)) {
378 g_string_free (invalid_addrs, TRUE);
379 goto finished;
382 g_string_free (invalid_addrs, TRUE);
385 settings = e_util_ref_settings ("org.gnome.evolution.mail");
386 if (num_to_cc > 1 && num_to_cc >= g_settings_get_int (settings, "composer-many-to-cc-recips-num")) {
387 gchar *head;
388 gchar *msg;
390 g_clear_object (&settings);
392 head = g_strdup_printf (ngettext (
393 /* Translators: The %d is replaced with the actual count of recipients, which is always more than one. */
394 "Are you sure you want to send a message with %d To and CC recipients?",
395 "Are you sure you want to send a message with %d To and CC recipients?",
396 num_to_cc), num_to_cc);
398 msg = g_strdup_printf (ngettext (
399 /* Translators: The %d is replaced with the actual count of recipients, which is always more than one. */
400 "You are trying to send a message to %d recipients in To and CC fields."
401 " This would result in all recipients seeing the email addresses of each"
402 " other. In some cases this behaviour is undesired, especially if they"
403 " do not know each other or if privacy is a concern. Consider adding"
404 " recipients to the BCC field instead.",
405 "You are trying to send a message to %d recipients in To and CC fields."
406 " This would result in all recipients seeing the email addresses of each"
407 " other. In some cases this behaviour is undesired, especially if they"
408 " do not know each other or if privacy is a concern. Consider adding"
409 " recipients to the BCC field instead.",
410 num_to_cc), num_to_cc);
412 if (!e_util_prompt_user (
413 GTK_WINDOW (composer),
414 "org.gnome.evolution.mail",
415 "prompt-on-many-to-cc-recips",
416 "mail:ask-many-to-cc-recips",
417 head, msg, NULL)) {
418 GtkAction *action;
420 g_free (head);
421 g_free (msg);
423 action = E_COMPOSER_ACTION_VIEW_BCC (composer);
424 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), TRUE);
426 goto finished;
429 g_free (head);
430 g_free (msg);
432 g_clear_object (&settings);
434 if (num > 0 && (num == num_bcc || shown == 0)) {
435 /* this means that the only recipients are Bcc's */
436 if (!ask_confirm_for_only_bcc (composer, shown == 0))
437 goto finished;
440 check_passed = TRUE;
442 finished:
443 if (recipients != NULL)
444 e_destination_freev (recipients);
446 return check_passed;
449 static gboolean
450 composer_presend_check_identity (EMsgComposer *composer,
451 EMailSession *session)
453 EComposerHeaderTable *table;
454 ESource *source = NULL;
455 gchar *uid;
456 gboolean success = TRUE;
458 table = e_msg_composer_get_header_table (composer);
460 uid = e_composer_header_table_dup_identity_uid (table, NULL, NULL);
461 if (uid)
462 source = e_composer_header_table_ref_source (table, uid);
463 g_free (uid);
465 if (source) {
466 EClientCache *client_cache;
467 ESourceRegistry *registry;
469 client_cache = e_composer_header_table_ref_client_cache (table);
470 registry = e_client_cache_ref_registry (client_cache);
472 success = e_source_registry_check_enabled (registry, source);
473 if (!success) {
474 e_alert_submit (
475 E_ALERT_SINK (e_msg_composer_get_editor (composer)),
476 "mail:send-no-account-enabled", NULL);
479 g_object_unref (client_cache);
480 g_object_unref (registry);
481 } else {
482 success = FALSE;
483 e_alert_submit (
484 E_ALERT_SINK (e_msg_composer_get_editor (composer)),
485 "mail:send-no-account", NULL);
489 g_clear_object (&source);
491 return success;
494 static gboolean
495 composer_presend_check_plugins (EMsgComposer *composer,
496 EMailSession *session)
498 EMEvent *eme;
499 EMEventTargetComposer *target;
500 gpointer data;
502 /** @Event: composer.presendchecks
503 * @Title: Composer PreSend Checks
504 * @Target: EMEventTargetMessage
506 * composer.presendchecks is emitted during pre-checks for the
507 * message just before sending. Since the e-plugin framework
508 * doesn't provide a way to return a value from the plugin,
509 * use 'presend_check_status' to set whether the check passed.
511 eme = em_event_peek ();
512 target = em_event_target_new_composer (eme, composer, 0);
514 e_event_emit (
515 (EEvent *) eme, "composer.presendchecks",
516 (EEventTarget *) target);
518 /* A non-NULL value for this key means the check failed. */
519 data = g_object_get_data (G_OBJECT (composer), "presend_check_status");
521 /* Clear the value in case we have to run these checks again. */
522 g_object_set_data (G_OBJECT (composer), "presend_check_status", NULL);
524 return (data == NULL);
527 static gboolean
528 composer_presend_check_subject (EMsgComposer *composer,
529 EMailSession *session)
531 EComposerHeaderTable *table;
532 const gchar *subject;
533 gboolean check_passed = TRUE;
535 table = e_msg_composer_get_header_table (composer);
536 subject = e_composer_header_table_get_subject (table);
538 if (subject == NULL || subject[0] == '\0') {
539 if (!ask_confirm_for_empty_subject (composer))
540 check_passed = FALSE;
543 return check_passed;
546 static gboolean
547 composer_presend_check_unwanted_html (EMsgComposer *composer,
548 EMailSession *session)
550 EDestination **recipients;
551 EHTMLEditor *editor;
552 EContentEditor *cnt_editor;
553 EComposerHeaderTable *table;
554 GSettings *settings;
555 gboolean check_passed = TRUE;
556 gboolean html_mode;
557 gboolean send_html;
558 gboolean confirm_html;
559 gint ii;
561 settings = e_util_ref_settings ("org.gnome.evolution.mail");
563 editor = e_msg_composer_get_editor (composer);
564 cnt_editor = e_html_editor_get_content_editor (editor);
565 html_mode = e_content_editor_get_html_mode (cnt_editor);
567 table = e_msg_composer_get_header_table (composer);
568 recipients = e_composer_header_table_get_destinations (table);
570 send_html = g_settings_get_boolean (settings, "composer-send-html");
571 confirm_html = g_settings_get_boolean (settings, "prompt-on-unwanted-html");
573 /* Only show this warning if our default is to send html. If it
574 * isn't, we've manually switched into html mode in the composer
575 * and (presumably) had a good reason for doing this. */
576 if (html_mode && send_html && confirm_html && recipients != NULL) {
577 gboolean html_problem = FALSE;
579 for (ii = 0; recipients[ii] != NULL; ii++) {
580 if (!e_destination_get_html_mail_pref (recipients[ii])) {
581 html_problem = TRUE;
582 break;
586 if (html_problem) {
587 if (!ask_confirm_for_unwanted_html_mail (
588 composer, recipients))
589 check_passed = FALSE;
593 if (recipients != NULL)
594 e_destination_freev (recipients);
596 g_object_unref (settings);
598 return check_passed;
601 static void
602 composer_send_completed (GObject *source_object,
603 GAsyncResult *result,
604 gpointer user_data)
606 EActivity *activity;
607 gboolean service_unavailable;
608 gboolean set_changed = FALSE;
609 AsyncContext *async_context;
610 GError *local_error = NULL;
612 async_context = (AsyncContext *) user_data;
614 activity = async_context->activity;
616 e_mail_session_send_to_finish (
617 E_MAIL_SESSION (source_object), result, &local_error);
619 if (e_activity_handle_cancellation (activity, local_error)) {
620 set_changed = TRUE;
621 goto exit;
624 /* Check for error codes which may indicate we're offline
625 * or name resolution failed or connection attempt failed. */
626 service_unavailable =
627 g_error_matches (
628 local_error, CAMEL_SERVICE_ERROR,
629 CAMEL_SERVICE_ERROR_UNAVAILABLE) ||
630 /* name resolution failed */
631 g_error_matches (
632 local_error, G_RESOLVER_ERROR,
633 G_RESOLVER_ERROR_NOT_FOUND) ||
634 g_error_matches (
635 local_error, G_RESOLVER_ERROR,
636 G_RESOLVER_ERROR_TEMPORARY_FAILURE) ||
637 /* something internal to Camel failed */
638 g_error_matches (
639 local_error, CAMEL_SERVICE_ERROR,
640 CAMEL_SERVICE_ERROR_URL_INVALID);
641 if (service_unavailable) {
642 /* Inform the user. */
643 e_alert_run_dialog_for_args (
644 GTK_WINDOW (async_context->composer),
645 "mail-composer:saving-to-outbox", NULL);
646 e_msg_composer_save_to_outbox (async_context->composer);
647 goto exit;
650 /* Post-processing errors are shown in the shell window. */
651 if (g_error_matches (
652 local_error, E_MAIL_ERROR,
653 E_MAIL_ERROR_POST_PROCESSING)) {
654 EAlert *alert;
655 EShell *shell;
657 shell = e_msg_composer_get_shell (async_context->composer);
659 alert = e_alert_new (
660 "mail-composer:send-post-processing-error",
661 local_error->message, NULL);
662 e_shell_submit_alert (shell, alert);
663 g_object_unref (alert);
665 /* All other errors are shown in the composer window. */
666 } else if (local_error != NULL) {
667 gint response;
669 /* Clear the activity bar before
670 * presenting the error dialog. */
671 g_clear_object (&async_context->activity);
672 activity = async_context->activity;
674 response = e_alert_run_dialog_for_args (
675 GTK_WINDOW (async_context->composer),
676 "mail-composer:send-error",
677 local_error->message, NULL);
678 if (response == GTK_RESPONSE_OK) /* Try Again */
679 e_msg_composer_send (async_context->composer);
680 if (response == GTK_RESPONSE_ACCEPT) /* Save to Outbox */
681 e_msg_composer_save_to_outbox (async_context->composer);
682 set_changed = TRUE;
683 goto exit;
686 e_activity_set_state (activity, E_ACTIVITY_COMPLETED);
688 /* Wait for the EActivity's completion message to
689 * time out and then destroy the composer window. */
690 g_object_weak_ref (
691 G_OBJECT (activity), (GWeakNotify)
692 gtk_widget_destroy, async_context->composer);
694 exit:
695 g_clear_error (&local_error);
697 if (set_changed) {
698 EHTMLEditor *editor;
699 EContentEditor *cnt_editor;
701 editor = e_msg_composer_get_editor (async_context->composer);
702 cnt_editor = e_html_editor_get_content_editor (editor);
704 e_content_editor_set_changed (cnt_editor, TRUE);
706 gtk_window_present (GTK_WINDOW (async_context->composer));
709 async_context_free (async_context);
712 static void
713 em_utils_composer_real_send (EMsgComposer *composer,
714 CamelMimeMessage *message,
715 EActivity *activity,
716 EMailSession *session)
718 AsyncContext *async_context;
719 GCancellable *cancellable;
720 GSettings *settings;
722 settings = e_util_ref_settings ("org.gnome.evolution.mail");
723 if (g_settings_get_boolean (settings, "composer-use-outbox")) {
724 e_msg_composer_save_to_outbox (composer);
725 g_object_unref (settings);
726 return;
729 g_object_unref (settings);
731 if (!camel_session_get_online (CAMEL_SESSION (session))) {
732 e_alert_run_dialog_for_args (
733 GTK_WINDOW (composer),
734 "mail-composer:saving-to-outbox", NULL);
735 e_msg_composer_save_to_outbox (composer);
736 return;
739 async_context = g_slice_new0 (AsyncContext);
740 async_context->message = g_object_ref (message);
741 async_context->composer = g_object_ref (composer);
742 async_context->activity = g_object_ref (activity);
744 cancellable = e_activity_get_cancellable (activity);
746 e_mail_session_send_to (
747 session, message,
748 G_PRIORITY_DEFAULT,
749 cancellable, NULL, NULL,
750 composer_send_completed,
751 async_context);
754 static void
755 composer_num_loading_notify_cb (EAttachmentStore *store,
756 GParamSpec *param,
757 AsyncContext *async_context)
759 if (e_attachment_store_get_num_loading (store) > 0)
760 return;
762 em_utils_composer_real_send (
763 async_context->composer,
764 async_context->message,
765 async_context->activity,
766 async_context->session);
768 async_context_free (async_context);
771 static void
772 composer_wait_for_attachment_load_cancelled_cb (GCancellable *cancellable,
773 AsyncContext *async_context)
775 async_context_free (async_context);
778 static void
779 em_utils_composer_send_cb (EMsgComposer *composer,
780 CamelMimeMessage *message,
781 EActivity *activity,
782 EMailSession *session)
784 AsyncContext *async_context;
785 GCancellable *cancellable;
786 EAttachmentView *view;
787 EAttachmentStore *store;
789 view = e_msg_composer_get_attachment_view (composer);
790 store = e_attachment_view_get_store (view);
792 if (e_attachment_store_get_num_loading (store) <= 0) {
793 em_utils_composer_real_send (composer, message, activity, session);
794 return;
797 async_context = g_slice_new0 (AsyncContext);
798 async_context->session = g_object_ref (session);
799 async_context->message = g_object_ref (message);
800 async_context->composer = g_object_ref (composer);
801 async_context->activity = g_object_ref (activity);
803 cancellable = e_activity_get_cancellable (activity);
804 /* This message is never removed from the camel operation, otherwise the GtkInfoBar
805 hides itself and the user sees no feedback. */
806 camel_operation_push_message (cancellable, "%s", _("Waiting for attachments to load..."));
808 async_context->num_loading_handler_id = e_signal_connect_notify (store, "notify::num-loading",
809 G_CALLBACK (composer_num_loading_notify_cb), async_context);
810 /* Cannot use g_cancellable_connect() here, see async_context_free() */
811 async_context->cancelled_handler_id = g_signal_connect (cancellable, "cancelled",
812 G_CALLBACK (composer_wait_for_attachment_load_cancelled_cb), async_context);
815 static void
816 composer_set_no_change (EMsgComposer *composer)
818 EHTMLEditor *editor;
819 EContentEditor *cnt_editor;
821 g_return_if_fail (composer != NULL);
823 editor = e_msg_composer_get_editor (composer);
824 cnt_editor = e_html_editor_get_content_editor (editor);
826 e_content_editor_set_changed (cnt_editor, FALSE);
829 /* delete original messages from Outbox folder */
830 static void
831 manage_x_evolution_replace_outbox (EMsgComposer *composer,
832 EMailSession *session,
833 CamelMimeMessage *message,
834 GCancellable *cancellable)
836 const gchar *message_uid;
837 const gchar *header;
838 CamelFolder *outbox;
840 g_return_if_fail (composer != NULL);
841 g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
843 header = "X-Evolution-Replace-Outbox-UID";
844 message_uid = camel_medium_get_header (CAMEL_MEDIUM (message), header);
845 e_msg_composer_remove_header (composer, header);
847 if (!message_uid)
848 return;
850 outbox = e_mail_session_get_local_folder (
851 session, E_MAIL_LOCAL_FOLDER_OUTBOX);
852 g_return_if_fail (outbox != NULL);
854 camel_folder_set_message_flags (
855 outbox, message_uid,
856 CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN,
857 CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN);
859 /* ignore errors here */
860 camel_folder_synchronize_message_sync (
861 outbox, message_uid, cancellable, NULL);
864 static void
865 composer_save_to_drafts_complete (GObject *source_object,
866 GAsyncResult *result,
867 gpointer user_data)
869 EActivity *activity;
870 AsyncContext *async_context;
871 EHTMLEditor *editor;
872 EContentEditor *cnt_editor;
873 GError *local_error = NULL;
875 async_context = (AsyncContext *) user_data;
877 editor = e_msg_composer_get_editor (async_context->composer);
878 cnt_editor = e_html_editor_get_content_editor (editor);
880 /* We don't really care if this failed. If something other than
881 * cancellation happened, emit a runtime warning so the error is
882 * not completely lost. */
883 e_mail_session_handle_draft_headers_finish (
884 E_MAIL_SESSION (source_object), result, &local_error);
886 activity = async_context->activity;
888 if (e_activity_handle_cancellation (activity, local_error)) {
889 e_content_editor_set_changed (cnt_editor, TRUE);
890 g_error_free (local_error);
892 } else if (local_error != NULL) {
893 e_content_editor_set_changed (cnt_editor, TRUE);
894 g_warning ("%s", local_error->message);
895 g_error_free (local_error);
896 } else
897 e_activity_set_state (activity, E_ACTIVITY_COMPLETED);
899 /* Encode the draft message we just saved into the EMsgComposer
900 * as X-Evolution-Draft headers. The message will be marked for
901 * deletion if the user saves a newer draft message or sends the
902 * composed message. */
903 e_msg_composer_set_draft_headers (
904 async_context->composer,
905 async_context->folder_uri,
906 async_context->message_uid);
908 e_content_editor_set_changed (cnt_editor, FALSE);
910 async_context_free (async_context);
913 static void
914 composer_save_to_drafts_append_mail (AsyncContext *async_context,
915 CamelFolder *drafts_folder);
917 static void
918 composer_save_to_drafts_cleanup (GObject *source_object,
919 GAsyncResult *result,
920 gpointer user_data)
922 CamelSession *session;
923 EActivity *activity;
924 EAlertSink *alert_sink;
925 GCancellable *cancellable;
926 EHTMLEditor *editor;
927 EContentEditor *cnt_editor;
928 AsyncContext *async_context;
929 GError *local_error = NULL;
931 async_context = (AsyncContext *) user_data;
933 editor = e_msg_composer_get_editor (async_context->composer);
934 cnt_editor = e_html_editor_get_content_editor (editor);
936 activity = async_context->activity;
937 alert_sink = e_activity_get_alert_sink (activity);
938 cancellable = e_activity_get_cancellable (activity);
940 e_mail_folder_append_message_finish (
941 CAMEL_FOLDER (source_object), result,
942 &async_context->message_uid, &local_error);
944 if (e_activity_handle_cancellation (activity, local_error)) {
945 g_warn_if_fail (async_context->message_uid == NULL);
946 e_content_editor_set_changed (cnt_editor, TRUE);
947 async_context_free (async_context);
948 g_error_free (local_error);
949 return;
951 } else if (local_error != NULL) {
952 g_warn_if_fail (async_context->message_uid == NULL);
954 if (e_msg_composer_is_exiting (async_context->composer)) {
955 gint response;
957 /* If we can't retrieve the Drafts folder for the
958 * selected account, ask the user if he wants to
959 * save to the local Drafts folder instead. */
960 response = e_alert_run_dialog_for_args (
961 GTK_WINDOW (async_context->composer),
962 "mail:ask-default-drafts", local_error->message, NULL);
963 if (response != GTK_RESPONSE_YES) {
964 e_content_editor_set_changed (cnt_editor, TRUE);
965 async_context_free (async_context);
966 } else {
967 composer_save_to_drafts_append_mail (async_context, NULL);
970 g_error_free (local_error);
971 return;
974 e_alert_submit (
975 alert_sink,
976 "mail-composer:save-to-drafts-error",
977 local_error->message, NULL);
978 e_content_editor_set_changed (cnt_editor, TRUE);
979 async_context_free (async_context);
980 g_error_free (local_error);
981 return;
984 session = e_msg_composer_ref_session (async_context->composer);
986 /* Mark the previously saved draft message for deletion.
987 * Note: This is just a nice-to-have; ignore failures. */
988 e_mail_session_handle_draft_headers (
989 E_MAIL_SESSION (session),
990 async_context->message,
991 G_PRIORITY_DEFAULT, cancellable,
992 composer_save_to_drafts_complete,
993 async_context);
995 g_object_unref (session);
998 static void
999 composer_save_to_drafts_append_mail (AsyncContext *async_context,
1000 CamelFolder *drafts_folder)
1002 CamelFolder *local_drafts_folder;
1003 GCancellable *cancellable;
1004 CamelMessageInfo *info;
1006 local_drafts_folder =
1007 e_mail_session_get_local_folder (
1008 async_context->session, E_MAIL_LOCAL_FOLDER_DRAFTS);
1010 if (drafts_folder == NULL)
1011 drafts_folder = g_object_ref (local_drafts_folder);
1013 cancellable = e_activity_get_cancellable (async_context->activity);
1015 info = camel_message_info_new (NULL);
1017 camel_message_info_set_flags (info, CAMEL_MESSAGE_DRAFT | CAMEL_MESSAGE_SEEN |
1018 (camel_mime_message_has_attachment (async_context->message) ? CAMEL_MESSAGE_ATTACHMENTS : 0), ~0);
1020 camel_medium_remove_header (
1021 CAMEL_MEDIUM (async_context->message),
1022 "X-Evolution-Replace-Outbox-UID");
1024 e_mail_folder_append_message (
1025 drafts_folder, async_context->message,
1026 info, G_PRIORITY_DEFAULT, cancellable,
1027 composer_save_to_drafts_cleanup,
1028 async_context);
1030 g_clear_object (&info);
1032 g_object_unref (drafts_folder);
1035 static void
1036 composer_save_to_drafts_got_folder (GObject *source_object,
1037 GAsyncResult *result,
1038 gpointer user_data)
1040 EActivity *activity;
1041 CamelFolder *drafts_folder;
1042 EHTMLEditor *editor;
1043 EContentEditor *cnt_editor;
1044 AsyncContext *async_context;
1045 GError *local_error = NULL;
1047 async_context = (AsyncContext *) user_data;
1049 activity = async_context->activity;
1051 editor = e_msg_composer_get_editor (async_context->composer);
1052 cnt_editor = e_html_editor_get_content_editor (editor);
1054 drafts_folder = e_mail_session_uri_to_folder_finish (
1055 E_MAIL_SESSION (source_object), result, &local_error);
1057 /* Sanity check. */
1058 g_return_if_fail (
1059 ((drafts_folder != NULL) && (local_error == NULL)) ||
1060 ((drafts_folder == NULL) && (local_error != NULL)));
1062 if (e_activity_handle_cancellation (activity, local_error)) {
1063 e_content_editor_set_changed (cnt_editor, TRUE);
1064 async_context_free (async_context);
1065 g_error_free (local_error);
1066 return;
1068 } else if (local_error != NULL) {
1069 gint response;
1071 /* If we can't retrieve the Drafts folder for the
1072 * selected account, ask the user if he wants to
1073 * save to the local Drafts folder instead. */
1074 response = e_alert_run_dialog_for_args (
1075 GTK_WINDOW (async_context->composer),
1076 "mail:ask-default-drafts", local_error->message, NULL);
1078 g_error_free (local_error);
1080 if (response != GTK_RESPONSE_YES) {
1081 e_content_editor_set_changed (cnt_editor, TRUE);
1082 async_context_free (async_context);
1083 return;
1087 composer_save_to_drafts_append_mail (async_context, drafts_folder);
1090 static void
1091 em_utils_composer_save_to_drafts_cb (EMsgComposer *composer,
1092 CamelMimeMessage *message,
1093 EActivity *activity,
1094 EMailSession *session)
1096 AsyncContext *async_context;
1097 EComposerHeaderTable *table;
1098 ESource *source;
1099 const gchar *local_drafts_folder_uri;
1100 gchar *identity_uid;
1101 gchar *drafts_folder_uri = NULL;
1103 async_context = g_slice_new0 (AsyncContext);
1104 async_context->message = g_object_ref (message);
1105 async_context->session = g_object_ref (session);
1106 async_context->composer = g_object_ref (composer);
1107 async_context->activity = g_object_ref (activity);
1109 table = e_msg_composer_get_header_table (composer);
1111 identity_uid = e_composer_header_table_dup_identity_uid (table, NULL, NULL);
1112 source = e_composer_header_table_ref_source (table, identity_uid);
1114 /* Get the selected identity's preferred Drafts folder. */
1115 if (source != NULL) {
1116 ESourceMailComposition *extension;
1117 const gchar *extension_name;
1118 gchar *uri;
1120 extension_name = E_SOURCE_EXTENSION_MAIL_COMPOSITION;
1121 extension = e_source_get_extension (source, extension_name);
1122 uri = e_source_mail_composition_dup_drafts_folder (extension);
1124 drafts_folder_uri = uri;
1126 g_object_unref (source);
1129 local_drafts_folder_uri =
1130 e_mail_session_get_local_folder_uri (
1131 session, E_MAIL_LOCAL_FOLDER_DRAFTS);
1133 if (drafts_folder_uri == NULL) {
1134 async_context->folder_uri = g_strdup (local_drafts_folder_uri);
1135 composer_save_to_drafts_append_mail (async_context, NULL);
1136 } else {
1137 GCancellable *cancellable;
1139 cancellable = e_activity_get_cancellable (activity);
1140 async_context->folder_uri = g_strdup (drafts_folder_uri);
1142 e_mail_session_uri_to_folder (
1143 session, drafts_folder_uri, 0,
1144 G_PRIORITY_DEFAULT, cancellable,
1145 composer_save_to_drafts_got_folder,
1146 async_context);
1148 g_free (drafts_folder_uri);
1151 g_free (identity_uid);
1154 static void
1155 composer_save_to_outbox_completed (GObject *source_object,
1156 GAsyncResult *result,
1157 gpointer user_data)
1159 EMailSession *session;
1160 EActivity *activity;
1161 EAlertSink *alert_sink;
1162 GCancellable *cancellable;
1163 AsyncContext *async_context;
1164 GSettings *settings;
1165 GError *local_error = NULL;
1167 session = E_MAIL_SESSION (source_object);
1168 async_context = (AsyncContext *) user_data;
1170 activity = async_context->activity;
1171 alert_sink = e_activity_get_alert_sink (activity);
1172 cancellable = e_activity_get_cancellable (activity);
1174 e_mail_session_append_to_local_folder_finish (
1175 session, result, NULL, &local_error);
1177 if (e_activity_handle_cancellation (activity, local_error)) {
1178 g_error_free (local_error);
1179 goto exit;
1181 } else if (local_error != NULL) {
1182 e_alert_submit (
1183 alert_sink,
1184 "mail-composer:append-to-outbox-error",
1185 local_error->message, NULL);
1186 g_error_free (local_error);
1187 goto exit;
1190 /* special processing for Outbox folder */
1191 manage_x_evolution_replace_outbox (
1192 async_context->composer,
1193 session,
1194 async_context->message,
1195 cancellable);
1197 e_activity_set_state (activity, E_ACTIVITY_COMPLETED);
1199 /* Wait for the EActivity's completion message to
1200 * time out and then destroy the composer window. */
1201 g_object_weak_ref (
1202 G_OBJECT (activity), (GWeakNotify)
1203 gtk_widget_destroy, async_context->composer);
1205 settings = e_util_ref_settings ("org.gnome.evolution.mail");
1206 if (g_settings_get_boolean (settings, "composer-use-outbox")) {
1207 gint delay_flush = g_settings_get_int (settings, "composer-delay-outbox-flush");
1209 if (delay_flush == 0) {
1210 e_mail_session_flush_outbox (session);
1211 } else if (delay_flush > 0) {
1212 e_mail_session_schedule_outbox_flush (session, delay_flush);
1215 g_object_unref (settings);
1217 exit:
1218 async_context_free (async_context);
1221 static void
1222 em_utils_composer_save_to_outbox_cb (EMsgComposer *composer,
1223 CamelMimeMessage *message,
1224 EActivity *activity,
1225 EMailSession *session)
1227 AsyncContext *async_context;
1228 CamelMessageInfo *info;
1229 GCancellable *cancellable;
1231 async_context = g_slice_new0 (AsyncContext);
1232 async_context->message = g_object_ref (message);
1233 async_context->composer = g_object_ref (composer);
1234 async_context->activity = g_object_ref (activity);
1236 cancellable = e_activity_get_cancellable (activity);
1238 info = camel_message_info_new (NULL);
1239 camel_message_info_set_flags (info, CAMEL_MESSAGE_SEEN, ~0);
1241 e_mail_session_append_to_local_folder (
1242 session, E_MAIL_LOCAL_FOLDER_OUTBOX,
1243 message, info, G_PRIORITY_DEFAULT, cancellable,
1244 composer_save_to_outbox_completed,
1245 async_context);
1247 g_clear_object (&info);
1250 typedef struct _PrintAsyncContext {
1251 GMainLoop *main_loop;
1252 GError *error;
1253 } PrintAsyncContext;
1255 static void
1256 em_composer_utils_print_done_cb (GObject *source_object,
1257 GAsyncResult *result,
1258 gpointer user_data)
1260 PrintAsyncContext *async_context = user_data;
1262 g_return_if_fail (E_IS_MAIL_PRINTER (source_object));
1263 g_return_if_fail (async_context != NULL);
1264 g_return_if_fail (async_context->main_loop != NULL);
1266 e_mail_printer_print_finish (E_MAIL_PRINTER (source_object), result, &(async_context->error));
1268 g_main_loop_quit (async_context->main_loop);
1271 static void
1272 em_utils_composer_print_cb (EMsgComposer *composer,
1273 GtkPrintOperationAction action,
1274 CamelMimeMessage *message,
1275 EActivity *activity,
1276 EMailSession *session)
1278 EMailParser *parser;
1279 EMailPartList *parts, *reserved_parts;
1280 EMailPrinter *printer;
1281 EMailBackend *mail_backend;
1282 const gchar *message_id;
1283 GCancellable *cancellable;
1284 CamelObjectBag *parts_registry;
1285 gchar *mail_uri;
1286 PrintAsyncContext async_context;
1288 mail_backend = E_MAIL_BACKEND (e_shell_get_backend_by_name (e_msg_composer_get_shell (composer), "mail"));
1289 g_return_if_fail (mail_backend != NULL);
1291 cancellable = e_activity_get_cancellable (activity);
1292 parser = e_mail_parser_new (CAMEL_SESSION (session));
1294 message_id = camel_mime_message_get_message_id (message);
1295 parts = e_mail_parser_parse_sync (parser, NULL, message_id, message, cancellable);
1296 if (!parts) {
1297 g_clear_object (&parser);
1298 return;
1301 parts_registry = e_mail_part_list_get_registry ();
1303 mail_uri = e_mail_part_build_uri (NULL, message_id, NULL, NULL);
1304 reserved_parts = camel_object_bag_reserve (parts_registry, mail_uri);
1305 g_clear_object (&reserved_parts);
1307 camel_object_bag_add (parts_registry, mail_uri, parts);
1309 printer = e_mail_printer_new (parts, e_mail_backend_get_remote_content (mail_backend));
1311 async_context.error = NULL;
1312 async_context.main_loop = g_main_loop_new (NULL, FALSE);
1314 /* Cannot use EAsyncClosure here, it blocks the main context, which is not good here. */
1315 e_mail_printer_print (printer, action, NULL, cancellable, em_composer_utils_print_done_cb, &async_context);
1317 g_main_loop_run (async_context.main_loop);
1319 camel_object_bag_remove (parts_registry, parts);
1320 g_main_loop_unref (async_context.main_loop);
1321 g_object_unref (printer);
1322 g_object_unref (parts);
1323 g_free (mail_uri);
1325 if (e_activity_handle_cancellation (activity, async_context.error)) {
1326 g_error_free (async_context.error);
1327 } else if (async_context.error != NULL) {
1328 e_alert_submit (
1329 e_activity_get_alert_sink (activity),
1330 "mail-composer:no-build-message",
1331 async_context.error->message, NULL);
1332 g_error_free (async_context.error);
1336 static gint
1337 compare_sources_with_uids_order_cb (gconstpointer a,
1338 gconstpointer b,
1339 gpointer user_data)
1341 ESource *asource = (ESource *) a;
1342 ESource *bsource = (ESource *) b;
1343 GHashTable *uids_order = user_data;
1344 gint aindex, bindex;
1346 aindex = GPOINTER_TO_INT (g_hash_table_lookup (uids_order, e_source_get_uid (asource)));
1347 bindex = GPOINTER_TO_INT (g_hash_table_lookup (uids_order, e_source_get_uid (bsource)));
1349 if (aindex <= 0)
1350 aindex = g_hash_table_size (uids_order);
1351 if (bindex <= 0)
1352 bindex = g_hash_table_size (uids_order);
1354 return aindex - bindex;
1357 static void
1358 sort_sources_by_ui (GList **psources,
1359 gpointer user_data)
1361 EShell *shell = user_data;
1362 EShellBackend *shell_backend;
1363 EMailSession *mail_session;
1364 EMailAccountStore *account_store;
1365 GtkTreeModel *model;
1366 GtkTreeIter iter;
1367 GHashTable *uids_order;
1368 gint index = 0;
1370 g_return_if_fail (psources != NULL);
1371 g_return_if_fail (E_IS_SHELL (shell));
1373 /* nothing to sort */
1374 if (!*psources || !g_list_next (*psources))
1375 return;
1377 shell_backend = e_shell_get_backend_by_name (shell, "mail");
1378 g_return_if_fail (shell_backend != NULL);
1380 mail_session = e_mail_backend_get_session (E_MAIL_BACKEND (shell_backend));
1381 g_return_if_fail (mail_session != NULL);
1383 account_store = e_mail_ui_session_get_account_store (E_MAIL_UI_SESSION (mail_session));
1384 g_return_if_fail (account_store != NULL);
1386 model = GTK_TREE_MODEL (account_store);
1387 if (!gtk_tree_model_get_iter_first (model, &iter))
1388 return;
1390 uids_order = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
1392 do {
1393 CamelService *service = NULL;
1395 gtk_tree_model_get (model, &iter, E_MAIL_ACCOUNT_STORE_COLUMN_SERVICE, &service, -1);
1397 if (service) {
1398 index++;
1399 g_hash_table_insert (uids_order, g_strdup (camel_service_get_uid (service)), GINT_TO_POINTER (index));
1400 g_object_unref (service);
1402 } while (gtk_tree_model_iter_next (model, &iter));
1404 *psources = g_list_sort_with_data (*psources, compare_sources_with_uids_order_cb, uids_order);
1406 g_hash_table_destroy (uids_order);
1409 /* Composing messages... */
1411 static CamelMimeMessage *em_utils_get_composer_recipients_as_message (EMsgComposer *composer);
1413 static void
1414 set_up_new_composer (EMsgComposer *composer,
1415 const gchar *subject,
1416 CamelFolder *folder,
1417 CamelMimeMessage *message,
1418 const gchar *message_uid)
1420 EClientCache *client_cache;
1421 ESourceRegistry *registry;
1422 EComposerHeaderTable *table;
1423 ESource *source = NULL;
1424 gchar *identity = NULL, *identity_name = NULL, *identity_address = NULL;
1426 table = e_msg_composer_get_header_table (composer);
1428 client_cache = e_composer_header_table_ref_client_cache (table);
1429 registry = e_client_cache_ref_registry (client_cache);
1431 if (folder != NULL) {
1432 gchar *folder_uri;
1433 GList *list;
1435 if (message) {
1436 g_object_ref (message);
1437 } else if (message_uid) {
1438 message = em_utils_get_composer_recipients_as_message (composer);
1441 if (message) {
1442 EShell *shell;
1444 shell = e_msg_composer_get_shell (composer);
1446 /* Check send account override for the passed-in folder */
1447 source = em_utils_check_send_account_override (shell, message, folder, &identity_name, &identity_address);
1449 /* If not set and it's a search folder, then check the original folder */
1450 if (!source && message_uid && CAMEL_IS_VEE_FOLDER (folder)) {
1451 CamelMessageInfo *mi = camel_folder_get_message_info (folder, message_uid);
1452 if (mi) {
1453 CamelFolder *location;
1455 location = camel_vee_folder_get_location (CAMEL_VEE_FOLDER (folder), (CamelVeeMessageInfo *) mi, NULL);
1456 if (location)
1457 source = em_utils_check_send_account_override (shell, message, location, &identity_name, &identity_address);
1458 g_clear_object (&mi);
1462 /* If no send account override, then guess */
1463 if (!source) {
1464 source = em_utils_guess_mail_identity_with_recipients_and_sort (
1465 registry, message, folder, message_uid, &identity_name, &identity_address, sort_sources_by_ui, shell);
1469 /* In case of search folder, try to guess the store from
1470 the internal folders of it. If they are all from the same
1471 store, then use that store. */
1472 if (!source && CAMEL_IS_VEE_FOLDER (folder)) {
1473 GHashTable *stores, *done_folders;
1474 GSList *todo;
1476 stores = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, NULL);
1477 done_folders = g_hash_table_new (g_direct_hash, g_direct_equal);
1479 todo = g_slist_prepend (NULL, g_object_ref (folder));
1481 while (todo) {
1482 CamelVeeFolder *vfolder = todo->data;
1484 todo = g_slist_remove (todo, vfolder);
1485 if (!g_hash_table_contains (done_folders, vfolder)) {
1486 GList *folders, *llink;
1488 g_hash_table_insert (done_folders, vfolder, NULL);
1490 folders = camel_vee_folder_ref_folders (vfolder);
1491 for (llink = folders; llink; llink = g_list_next (llink)) {
1492 CamelFolder *subfolder = llink->data;
1494 if (!g_hash_table_contains (done_folders, subfolder)) {
1495 if (CAMEL_IS_VEE_FOLDER (subfolder)) {
1496 todo = g_slist_prepend (todo, g_object_ref (subfolder));
1497 } else {
1498 CamelStore *store = camel_folder_get_parent_store (subfolder);
1500 g_hash_table_insert (done_folders, subfolder, NULL);
1502 if (store) {
1503 g_hash_table_insert (stores, g_object_ref (store), NULL);
1505 if (g_hash_table_size (stores) > 1) {
1506 g_slist_free_full (todo, g_object_unref);
1507 todo = NULL;
1508 break;
1515 g_list_free_full (folders, g_object_unref);
1518 g_object_unref (vfolder);
1521 if (g_hash_table_size (stores) == 1) {
1522 GHashTableIter iter;
1523 gpointer store;
1525 g_hash_table_iter_init (&iter, stores);
1526 if (g_hash_table_iter_next (&iter, &store, NULL) && store)
1527 source = em_utils_ref_mail_identity_for_store (registry, store);
1530 g_slist_free_full (todo, g_object_unref);
1531 g_hash_table_destroy (done_folders);
1532 g_hash_table_destroy (stores);
1535 g_clear_object (&message);
1537 if (!source) {
1538 CamelStore *store;
1540 store = camel_folder_get_parent_store (folder);
1541 source = em_utils_ref_mail_identity_for_store (registry, store);
1544 folder_uri = e_mail_folder_uri_from_folder (folder);
1546 list = g_list_prepend (NULL, folder_uri);
1547 e_composer_header_table_set_post_to_list (table, list);
1548 g_list_free (list);
1550 g_free (folder_uri);
1553 if (source != NULL) {
1554 identity = e_source_dup_uid (source);
1555 g_object_unref (source);
1558 if (subject)
1559 e_composer_header_table_set_subject (table, subject);
1560 e_composer_header_table_set_identity_uid (table, identity, identity_name, identity_address);
1562 em_utils_apply_send_account_override_to_composer (composer, folder);
1564 g_free (identity);
1565 g_free (identity_name);
1566 g_free (identity_address);
1568 g_object_unref (client_cache);
1569 g_object_unref (registry);
1573 * em_utils_compose_new_message:
1574 * @composer: an #EMsgComposer
1575 * @folder: (nullable): a #CamelFolder, or %NULL
1577 * Sets up a new @composer window.
1579 * See: em_utils_compose_new_message_with_selection()
1581 * Since: 3.22
1583 void
1584 em_utils_compose_new_message (EMsgComposer *composer,
1585 CamelFolder *folder)
1587 em_utils_compose_new_message_with_selection (composer, folder, NULL);
1591 * em_utils_compose_new_message_with_selection:
1592 * @composer: an #EMsgComposer
1593 * @folder: (nullable): a #CamelFolder, or %NULL
1594 * @message_uid: (nullable): a UID of the selected message, or %NULL
1596 * Sets up a new @composer window, similar to em_utils_compose_new_message(),
1597 * but also tries to identify From account more precisely, when the @folder
1598 * is a search folder.
1600 * Since: 3.28
1602 void
1603 em_utils_compose_new_message_with_selection (EMsgComposer *composer,
1604 CamelFolder *folder,
1605 const gchar *message_uid)
1607 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
1609 if (folder)
1610 g_return_if_fail (CAMEL_IS_FOLDER (folder));
1612 set_up_new_composer (composer, "", folder, NULL, message_uid);
1613 composer_set_no_change (composer);
1615 gtk_widget_show (GTK_WIDGET (composer));
1618 static CamelMimeMessage *
1619 em_utils_get_composer_recipients_as_message (EMsgComposer *composer)
1621 CamelMimeMessage *message;
1622 EComposerHeaderTable *table;
1623 EComposerHeader *header;
1624 EDestination **destv;
1625 CamelInternetAddress *to_addr, *cc_addr, *bcc_addr, *dest_addr;
1626 const gchar *text_addr;
1627 gint ii;
1629 g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
1631 table = e_msg_composer_get_header_table (composer);
1632 header = e_composer_header_table_get_header (table, E_COMPOSER_HEADER_TO);
1634 if (!e_composer_header_get_visible (header))
1635 return NULL;
1637 message = camel_mime_message_new ();
1639 to_addr = camel_internet_address_new ();
1640 cc_addr = camel_internet_address_new ();
1641 bcc_addr = camel_internet_address_new ();
1643 /* To */
1644 dest_addr = to_addr;
1645 destv = e_composer_header_table_get_destinations_to (table);
1646 for (ii = 0; destv != NULL && destv[ii] != NULL; ii++) {
1647 text_addr = e_destination_get_address (destv[ii]);
1648 if (text_addr && *text_addr) {
1649 if (camel_address_decode (CAMEL_ADDRESS (dest_addr), text_addr) <= 0)
1650 camel_internet_address_add (dest_addr, "", text_addr);
1653 e_destination_freev (destv);
1655 /* CC */
1656 dest_addr = cc_addr;
1657 destv = e_composer_header_table_get_destinations_cc (table);
1658 for (ii = 0; destv != NULL && destv[ii] != NULL; ii++) {
1659 text_addr = e_destination_get_address (destv[ii]);
1660 if (text_addr && *text_addr) {
1661 if (camel_address_decode (CAMEL_ADDRESS (dest_addr), text_addr) <= 0)
1662 camel_internet_address_add (dest_addr, "", text_addr);
1665 e_destination_freev (destv);
1667 /* Bcc */
1668 dest_addr = bcc_addr;
1669 destv = e_composer_header_table_get_destinations_bcc (table);
1670 for (ii = 0; destv != NULL && destv[ii] != NULL; ii++) {
1671 text_addr = e_destination_get_address (destv[ii]);
1672 if (text_addr && *text_addr) {
1673 if (camel_address_decode (CAMEL_ADDRESS (dest_addr), text_addr) <= 0)
1674 camel_internet_address_add (dest_addr, "", text_addr);
1677 e_destination_freev (destv);
1679 if (camel_address_length (CAMEL_ADDRESS (to_addr)) > 0)
1680 camel_mime_message_set_recipients (message, CAMEL_RECIPIENT_TYPE_TO, to_addr);
1682 if (camel_address_length (CAMEL_ADDRESS (cc_addr)) > 0)
1683 camel_mime_message_set_recipients (message, CAMEL_RECIPIENT_TYPE_CC, cc_addr);
1685 if (camel_address_length (CAMEL_ADDRESS (bcc_addr)) > 0)
1686 camel_mime_message_set_recipients (message, CAMEL_RECIPIENT_TYPE_BCC, bcc_addr);
1688 g_object_unref (to_addr);
1689 g_object_unref (cc_addr);
1690 g_object_unref (bcc_addr);
1692 return message;
1695 typedef struct _CreateComposerData {
1696 CamelFolder *folder;
1697 const gchar *message_uid; /* In the Camel string pool */
1698 gchar *mailto;
1699 } CreateComposerData;
1701 static void
1702 create_composer_data_free (gpointer ptr)
1704 CreateComposerData *ccd = ptr;
1706 if (ccd) {
1707 g_clear_object (&ccd->folder);
1708 camel_pstring_free (ccd->message_uid);
1709 g_free (ccd->mailto);
1710 g_free (ccd);
1714 static void
1715 msg_composer_created_with_mailto_cb (GObject *source_object,
1716 GAsyncResult *result,
1717 gpointer user_data)
1719 CreateComposerData *ccd = user_data;
1720 EMsgComposer *composer;
1721 EComposerHeaderTable *table;
1722 EClientCache *client_cache;
1723 ESourceRegistry *registry;
1724 GError *error = NULL;
1726 g_return_if_fail (ccd != NULL);
1728 composer = e_msg_composer_new_finish (result, &error);
1729 if (error) {
1730 g_warning ("%s: Failed to create msg composer: %s", G_STRFUNC, error->message);
1731 g_clear_error (&error);
1732 create_composer_data_free (ccd);
1734 return;
1737 if (ccd->mailto)
1738 e_msg_composer_setup_from_url (composer, ccd->mailto);
1740 set_up_new_composer (composer, NULL, ccd->folder, NULL, ccd->message_uid);
1742 table = e_msg_composer_get_header_table (composer);
1744 client_cache = e_composer_header_table_ref_client_cache (table);
1745 registry = e_client_cache_ref_registry (client_cache);
1747 composer_set_no_change (composer);
1749 /* If a CamelFolder was given, we need to backtrack and find
1750 * the corresponding ESource with a Mail Identity extension. */
1752 if (ccd->folder) {
1753 ESource *source;
1754 CamelStore *store;
1756 store = camel_folder_get_parent_store (ccd->folder);
1757 source = em_utils_ref_mail_identity_for_store (registry, store);
1759 if (source != NULL) {
1760 const gchar *uid = e_source_get_uid (source);
1761 e_composer_header_table_set_identity_uid (table, uid, NULL, NULL);
1762 g_object_unref (source);
1766 g_object_unref (client_cache);
1767 g_object_unref (registry);
1769 gtk_window_present (GTK_WINDOW (composer));
1771 create_composer_data_free (ccd);
1775 * em_utils_compose_new_message_with_mailto:
1776 * @shell: an #EShell
1777 * @mailto: a mailto URL
1778 * @folder: a #CamelFolder, or %NULL
1780 * Opens a new composer window as a child window of @parent's toplevel
1781 * window. If @mailto is non-NULL, the composer fields will be filled in
1782 * according to the values in the mailto URL.
1784 * See: em_utils_compose_new_message_with_mailto_and_selection()
1786 void
1787 em_utils_compose_new_message_with_mailto (EShell *shell,
1788 const gchar *mailto,
1789 CamelFolder *folder)
1791 em_utils_compose_new_message_with_mailto_and_selection (shell, mailto, folder, NULL);
1795 * em_utils_compose_new_message_with_mailto_and_selection:
1796 * @shell: an #EShell
1797 * @mailto: a mailto URL
1798 * @folder: a #CamelFolder, or %NULL
1799 * @message_uid: (nullable): a UID of the selected message, or %NULL
1801 * similarly to em_utils_compose_new_message_with_mailto(), opens a new composer
1802 * window as a child window of @parent's toplevel window. If @mailto is non-NULL,
1803 * the composer fields will be filled in according to the values in the mailto URL.
1804 * It also tries to identify From account more precisely, when the @folder
1805 * is a search folder.
1807 * Since: 3.28
1809 void
1810 em_utils_compose_new_message_with_mailto_and_selection (EShell *shell,
1811 const gchar *mailto,
1812 CamelFolder *folder,
1813 const gchar *message_uid)
1815 CreateComposerData *ccd;
1817 g_return_if_fail (E_IS_SHELL (shell));
1819 if (folder)
1820 g_return_if_fail (CAMEL_IS_FOLDER (folder));
1822 ccd = g_new0 (CreateComposerData, 1);
1823 ccd->folder = folder ? g_object_ref (folder) : NULL;
1824 ccd->message_uid = camel_pstring_strdup (message_uid);
1825 ccd->mailto = g_strdup (mailto);
1827 e_msg_composer_new (shell, msg_composer_created_with_mailto_cb, ccd);
1830 static gboolean
1831 replace_variables (GSList *clues,
1832 CamelMimeMessage *message,
1833 gchar **pstr)
1835 gint i;
1836 gboolean string_changed = FALSE, count1 = FALSE;
1837 gchar *str;
1839 g_return_val_if_fail (pstr != NULL, FALSE);
1840 g_return_val_if_fail (*pstr != NULL, FALSE);
1841 g_return_val_if_fail (message != NULL, FALSE);
1843 str = *pstr;
1845 for (i = 0; i < strlen (str); i++) {
1846 const gchar *cur = str + i;
1847 if (!g_ascii_strncasecmp (cur, "$", 1)) {
1848 const gchar *end = cur + 1;
1849 gchar *out;
1850 gchar **temp_str;
1851 GSList *list;
1853 while (*end && (g_unichar_isalnum (*end) || *end == '_'))
1854 end++;
1856 out = g_strndup ((const gchar *) cur, end - cur);
1858 temp_str = g_strsplit (str, out, 2);
1860 for (list = clues; list; list = g_slist_next (list)) {
1861 gchar **temp = g_strsplit (list->data, "=", 2);
1862 if (!g_ascii_strcasecmp (temp[0], out + 1)) {
1863 g_free (str);
1864 str = g_strconcat (temp_str[0], temp[1], temp_str[1], NULL);
1865 count1 = TRUE;
1866 string_changed = TRUE;
1867 } else
1868 count1 = FALSE;
1869 g_strfreev (temp);
1872 if (!count1) {
1873 if (getenv (out + 1)) {
1874 g_free (str);
1875 str = g_strconcat (
1876 temp_str[0],
1877 getenv (out + 1),
1878 temp_str[1], NULL);
1879 count1 = TRUE;
1880 string_changed = TRUE;
1881 } else
1882 count1 = FALSE;
1885 if (!count1) {
1886 CamelInternetAddress *to;
1887 const gchar *name, *addr;
1889 to = camel_mime_message_get_recipients (
1890 message, CAMEL_RECIPIENT_TYPE_TO);
1891 if (!camel_internet_address_get (to, 0, &name, &addr))
1892 continue;
1894 if (name && g_ascii_strcasecmp ("sender_name", out + 1) == 0) {
1895 g_free (str);
1896 str = g_strconcat (temp_str[0], name, temp_str[1], NULL);
1897 count1 = TRUE;
1898 string_changed = TRUE;
1899 } else if (addr && g_ascii_strcasecmp ("sender_email", out + 1) == 0) {
1900 g_free (str);
1901 str = g_strconcat (temp_str[0], addr, temp_str[1], NULL);
1902 count1 = TRUE;
1903 string_changed = TRUE;
1907 g_strfreev (temp_str);
1908 g_free (out);
1912 *pstr = str;
1914 return string_changed;
1917 static void
1918 traverse_parts (GSList *clues,
1919 CamelMimeMessage *message,
1920 CamelDataWrapper *content)
1922 g_return_if_fail (message != NULL);
1924 if (!content)
1925 return;
1927 if (CAMEL_IS_MULTIPART (content)) {
1928 guint i, n;
1929 CamelMultipart *multipart = CAMEL_MULTIPART (content);
1930 CamelMimePart *part;
1932 n = camel_multipart_get_number (multipart);
1933 for (i = 0; i < n; i++) {
1934 part = camel_multipart_get_part (multipart, i);
1935 if (!part)
1936 continue;
1938 traverse_parts (clues, message, CAMEL_DATA_WRAPPER (part));
1940 } else if (CAMEL_IS_MIME_PART (content)) {
1941 CamelMimePart *part = CAMEL_MIME_PART (content);
1942 CamelContentType *type;
1943 CamelStream *stream;
1944 GByteArray *byte_array;
1945 gchar *str;
1947 content = camel_medium_get_content (CAMEL_MEDIUM (part));
1948 if (!content)
1949 return;
1951 if (CAMEL_IS_MULTIPART (content)) {
1952 traverse_parts (clues, message, CAMEL_DATA_WRAPPER (content));
1953 return;
1956 type = camel_mime_part_get_content_type (part);
1957 if (!camel_content_type_is (type, "text", "*"))
1958 return;
1960 byte_array = g_byte_array_new ();
1961 stream = camel_stream_mem_new_with_byte_array (byte_array);
1962 camel_data_wrapper_decode_to_stream_sync (
1963 content, stream, NULL, NULL);
1965 str = g_strndup ((gchar *) byte_array->data, byte_array->len);
1966 g_object_unref (stream);
1968 if (replace_variables (clues, message, &str)) {
1969 stream = camel_stream_mem_new_with_buffer (str, strlen (str));
1970 camel_data_wrapper_construct_from_stream_sync (
1971 content, stream, NULL, NULL);
1972 g_object_unref (stream);
1975 g_free (str);
1979 /* Editing messages... */
1981 typedef enum {
1982 QUOTING_ATTRIBUTION,
1983 QUOTING_FORWARD,
1984 QUOTING_ORIGINAL
1985 } QuotingTextEnum;
1987 static struct {
1988 const gchar * conf_key;
1989 const gchar * message;
1990 } conf_messages[] = {
1991 [QUOTING_ATTRIBUTION] =
1992 { "composer-message-attribution",
1993 /* Note to translators: this is the attribution string used
1994 * when quoting messages. Each ${Variable} gets replaced
1995 * with a value. To see a full list of available variables,
1996 * see mail/em-composer-utils.c:attribvars array. */
1997 N_("On ${AbbrevWeekdayName}, ${Year}-${Month}-${Day} at "
1998 "${24Hour}:${Minute} ${TimeZone}, ${Sender} wrote:")
2001 [QUOTING_FORWARD] =
2002 { "composer-message-forward",
2003 N_("-------- Forwarded Message --------")
2006 [QUOTING_ORIGINAL] =
2007 { "composer-message-original",
2008 N_("-----Original Message-----")
2012 static gchar *
2013 quoting_text (QuotingTextEnum type)
2015 GSettings *settings;
2016 gchar *text;
2018 settings = e_util_ref_settings ("org.gnome.evolution.mail");
2019 text = g_settings_get_string (settings, conf_messages[type].conf_key);
2020 g_object_unref (settings);
2022 if (text && *text)
2023 return text;
2025 g_free (text);
2027 return g_strdup (_(conf_messages[type].message));
2031 * em_composer_utils_get_forward_marker:
2033 * Returns: (transfer full): a text marker which is used for inline forwarded messages.
2034 * Free returned pointer with g_free(), when no longer needed.
2036 * Since: 3.24
2038 gchar *
2039 em_composer_utils_get_forward_marker (void)
2041 return quoting_text (QUOTING_FORWARD);
2045 * em_composer_utils_get_original_marker:
2047 * Returns: (transfer full): a text marker which is used for inline message replies.
2048 * Free returned pointer with g_free(), when no longer needed.
2050 * Since: 3.24
2052 gchar *
2053 em_composer_utils_get_original_marker (void)
2055 return quoting_text (QUOTING_ORIGINAL);
2058 static gboolean
2059 emcu_message_references_existing_account (CamelMimeMessage *message,
2060 EMsgComposer *composer)
2062 ESource *source;
2063 gchar *identity_uid;
2064 gboolean res = FALSE;
2066 g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), FALSE);
2067 g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE);
2069 identity_uid = (gchar *) camel_medium_get_header (CAMEL_MEDIUM (message), "X-Evolution-Identity");
2070 if (!identity_uid) {
2071 /* for backward compatibility */
2072 identity_uid = (gchar *) camel_medium_get_header (CAMEL_MEDIUM (message), "X-Evolution-Account");
2075 if (!identity_uid)
2076 return FALSE;
2078 identity_uid = g_strstrip (g_strdup (identity_uid));
2079 source = e_composer_header_table_ref_source (e_msg_composer_get_header_table (composer), identity_uid);
2081 res = source != NULL;
2083 g_clear_object (&source);
2084 g_free (identity_uid);
2086 return res;
2090 * em_utils_edit_message:
2091 * @composer: an #EMsgComposer
2092 * @folder: a #CamelFolder
2093 * @message: a #CamelMimeMessage
2094 * @message_uid: UID of @message, or %NULL
2096 * Sets up the @composer with the headers/mime-parts/etc of the @message.
2098 * Since: 3.22
2100 void
2101 em_utils_edit_message (EMsgComposer *composer,
2102 CamelFolder *folder,
2103 CamelMimeMessage *message,
2104 const gchar *message_uid,
2105 gboolean keep_signature)
2107 ESourceRegistry *registry;
2108 ESource *source;
2109 gboolean folder_is_sent;
2110 gboolean folder_is_drafts;
2111 gboolean folder_is_outbox;
2112 gboolean folder_is_templates;
2113 gchar *override_identity_uid = NULL, *override_alias_name = NULL, *override_alias_address = NULL;
2115 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
2116 g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
2117 if (folder)
2118 g_return_if_fail (CAMEL_IS_FOLDER (folder));
2120 registry = e_shell_get_registry (e_msg_composer_get_shell (composer));
2122 if (folder) {
2123 folder_is_sent = em_utils_folder_is_sent (registry, folder);
2124 folder_is_drafts = em_utils_folder_is_drafts (registry, folder);
2125 folder_is_outbox = em_utils_folder_is_outbox (registry, folder);
2126 folder_is_templates = em_utils_folder_is_templates (registry, folder);
2127 } else {
2128 folder_is_sent = FALSE;
2129 folder_is_drafts = FALSE;
2130 folder_is_outbox = FALSE;
2131 folder_is_templates = FALSE;
2134 /* Template specific code follows. */
2135 if (folder_is_templates) {
2136 CamelDataWrapper *content;
2137 GSettings *settings;
2138 gchar **strv;
2139 gint i;
2140 GSList *clue_list = NULL;
2142 settings = e_util_ref_settings ("org.gnome.evolution.plugin.templates");
2144 /* Get the list from GSettings */
2145 strv = g_settings_get_strv (settings, "template-placeholders");
2146 for (i = 0; strv[i] != NULL; i++)
2147 clue_list = g_slist_append (clue_list, g_strdup (strv[i]));
2148 g_object_unref (settings);
2149 g_strfreev (strv);
2151 content = camel_medium_get_content (CAMEL_MEDIUM (message));
2152 traverse_parts (clue_list, message, content);
2154 g_slist_foreach (clue_list, (GFunc) g_free, NULL);
2155 g_slist_free (clue_list);
2158 if (folder) {
2159 if ((!folder_is_sent && !folder_is_drafts && !folder_is_outbox && !folder_is_templates) ||
2160 (!folder_is_outbox && !folder_is_templates && !emcu_message_references_existing_account (message, composer))) {
2161 CamelStore *store;
2163 store = camel_folder_get_parent_store (folder);
2164 source = em_utils_ref_mail_identity_for_store (registry, store);
2166 if (source) {
2167 g_free (override_identity_uid);
2168 override_identity_uid = e_source_dup_uid (source);
2169 g_object_unref (source);
2173 source = em_utils_check_send_account_override (e_msg_composer_get_shell (composer), message, folder, &override_alias_name, &override_alias_address);
2174 if (source) {
2175 g_free (override_identity_uid);
2176 override_identity_uid = e_source_dup_uid (source);
2177 g_object_unref (source);
2181 e_msg_composer_setup_with_message (composer, message, keep_signature, override_identity_uid, override_alias_name, override_alias_address, NULL);
2183 g_free (override_identity_uid);
2184 g_free (override_alias_name);
2185 g_free (override_alias_address);
2187 /* Override PostTo header only if the folder is a regular folder */
2188 if (folder && !folder_is_sent && !folder_is_drafts && !folder_is_outbox && !folder_is_templates) {
2189 EComposerHeaderTable *table;
2190 gchar *folder_uri;
2191 GList *list;
2193 table = e_msg_composer_get_header_table (composer);
2195 folder_uri = e_mail_folder_uri_from_folder (folder);
2197 list = g_list_prepend (NULL, folder_uri);
2198 e_composer_header_table_set_post_to_list (table, list);
2199 g_list_free (list);
2201 g_free (folder_uri);
2204 e_msg_composer_remove_header (
2205 composer, "X-Evolution-Replace-Outbox-UID");
2207 if (message_uid != NULL && folder_is_drafts && folder) {
2208 gchar *folder_uri;
2210 folder_uri = e_mail_folder_uri_from_folder (folder);
2212 e_msg_composer_set_draft_headers (
2213 composer, folder_uri, message_uid);
2215 g_free (folder_uri);
2217 } else if (message_uid != NULL && folder_is_outbox) {
2218 e_msg_composer_set_header (
2219 composer, "X-Evolution-Replace-Outbox-UID",
2220 message_uid);
2223 composer_set_no_change (composer);
2225 gtk_widget_show (GTK_WIDGET (composer));
2228 static void
2229 emu_update_composers_security (EMsgComposer *composer,
2230 guint32 validity_found)
2232 GtkAction *action;
2233 GSettings *settings;
2234 gboolean sign_by_default;
2236 g_return_if_fail (composer != NULL);
2238 settings = e_util_ref_settings ("org.gnome.evolution.mail");
2240 sign_by_default =
2241 (validity_found & E_MAIL_PART_VALIDITY_SIGNED) != 0 &&
2242 /* FIXME This should be an EMsgComposer property. */
2243 g_settings_get_boolean (
2244 settings, "composer-sign-reply-if-signed");
2246 g_object_unref (settings);
2248 /* Pre-set only for encrypted messages, not for signed */
2249 if (sign_by_default) {
2250 action = NULL;
2252 if (validity_found & E_MAIL_PART_VALIDITY_SMIME) {
2253 if (!gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (E_COMPOSER_ACTION_PGP_SIGN (composer))) &&
2254 !gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (E_COMPOSER_ACTION_PGP_ENCRYPT (composer))))
2255 action = E_COMPOSER_ACTION_SMIME_SIGN (composer);
2256 } else {
2257 if (!gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (E_COMPOSER_ACTION_SMIME_SIGN (composer))) &&
2258 !gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (E_COMPOSER_ACTION_SMIME_ENCRYPT (composer))))
2259 action = E_COMPOSER_ACTION_PGP_SIGN (composer);
2262 if (action)
2263 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), TRUE);
2266 if (validity_found & E_MAIL_PART_VALIDITY_ENCRYPTED) {
2267 action = NULL;
2269 if (validity_found & E_MAIL_PART_VALIDITY_SMIME) {
2270 if (!gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (E_COMPOSER_ACTION_PGP_SIGN (composer))) &&
2271 !gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (E_COMPOSER_ACTION_PGP_ENCRYPT (composer))))
2272 action = E_COMPOSER_ACTION_SMIME_ENCRYPT (composer);
2273 } else {
2274 if (!gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (E_COMPOSER_ACTION_SMIME_SIGN (composer))) &&
2275 !gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (E_COMPOSER_ACTION_SMIME_ENCRYPT (composer))))
2276 action = E_COMPOSER_ACTION_PGP_ENCRYPT (composer);
2279 if (action)
2280 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), TRUE);
2284 void
2285 em_utils_get_real_folder_uri_and_message_uid (CamelFolder *folder,
2286 const gchar *uid,
2287 gchar **folder_uri,
2288 gchar **message_uid)
2290 g_return_if_fail (folder != NULL);
2291 g_return_if_fail (uid != NULL);
2292 g_return_if_fail (folder_uri != NULL);
2293 g_return_if_fail (message_uid != NULL);
2295 em_utils_get_real_folder_and_message_uid (folder, uid, NULL, folder_uri, message_uid);
2298 static void
2299 emu_add_composer_references_from_message (EMsgComposer *composer,
2300 CamelMimeMessage *message)
2302 const gchar *message_id_header;
2304 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
2305 g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
2307 message_id_header = camel_mime_message_get_message_id (message);
2308 if (message_id_header && *message_id_header) {
2309 GString *references = g_string_new ("");
2310 gint ii = 0;
2311 const gchar *value;
2312 gchar *unfolded;
2314 while (value = e_msg_composer_get_header (composer, "References", ii), value) {
2315 ii++;
2317 if (references->len)
2318 g_string_append_c (references, ' ');
2319 g_string_append (references, value);
2322 if (references->len)
2323 g_string_append_c (references, ' ');
2325 if (*message_id_header != '<')
2326 g_string_append_c (references, '<');
2328 g_string_append (references, message_id_header);
2330 if (*message_id_header != '<')
2331 g_string_append_c (references, '>');
2333 unfolded = camel_header_unfold (references->str);
2335 e_msg_composer_set_header (composer, "References", unfolded);
2337 g_string_free (references, TRUE);
2338 g_free (unfolded);
2342 static void
2343 real_update_forwarded_flag (gpointer uid,
2344 gpointer folder)
2346 if (uid && folder)
2347 camel_folder_set_message_flags (
2348 folder, uid, CAMEL_MESSAGE_FORWARDED,
2349 CAMEL_MESSAGE_FORWARDED);
2352 static void
2353 update_forwarded_flags_cb (EMsgComposer *composer,
2354 ForwardData *data)
2356 if (data && data->uids && data->folder)
2357 g_ptr_array_foreach (
2358 data->uids, real_update_forwarded_flag, data->folder);
2361 static void
2362 setup_forward_attached_callbacks (EMsgComposer *composer,
2363 CamelFolder *folder,
2364 GPtrArray *uids)
2366 ForwardData *data;
2368 if (!composer || !folder || !uids || !uids->len)
2369 return;
2371 g_object_ref (folder);
2373 data = g_slice_new0 (ForwardData);
2374 data->folder = g_object_ref (folder);
2375 data->uids = g_ptr_array_ref (uids);
2377 g_signal_connect (
2378 composer, "send",
2379 G_CALLBACK (update_forwarded_flags_cb), data);
2380 g_signal_connect (
2381 composer, "save-to-drafts",
2382 G_CALLBACK (update_forwarded_flags_cb), data);
2384 g_object_set_data_full (
2385 G_OBJECT (composer), "forward-data", data,
2386 (GDestroyNotify) forward_data_free);
2389 static void
2390 forward_non_attached (EMsgComposer *composer,
2391 CamelFolder *folder,
2392 const gchar *uid,
2393 CamelMimeMessage *message,
2394 EMailForwardStyle style)
2396 CamelSession *session;
2397 gchar *text, *forward;
2398 guint32 validity_found = 0;
2399 guint32 flags;
2401 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
2403 session = e_msg_composer_ref_session (composer);
2405 flags = E_MAIL_FORMATTER_QUOTE_FLAG_HEADERS |
2406 E_MAIL_FORMATTER_QUOTE_FLAG_KEEP_SIG;
2407 if (style == E_MAIL_FORWARD_STYLE_QUOTED)
2408 flags |= E_MAIL_FORMATTER_QUOTE_FLAG_CITE;
2410 forward = quoting_text (QUOTING_FORWARD);
2411 text = em_utils_message_to_html (session, message, forward, flags, NULL, NULL, NULL, &validity_found);
2413 if (text != NULL) {
2414 CamelDataWrapper *content;
2415 gchar *subject;
2417 subject = mail_tool_generate_forward_subject (message);
2418 set_up_new_composer (composer, subject, folder, message, uid);
2419 g_free (subject);
2421 content = camel_medium_get_content (CAMEL_MEDIUM (message));
2423 if (CAMEL_IS_MULTIPART (content))
2424 e_msg_composer_add_message_attachments (
2425 composer, message, FALSE);
2427 e_msg_composer_set_body_text (composer, text, TRUE);
2429 emu_add_composer_references_from_message (composer, message);
2431 if (uid != NULL) {
2432 gchar *folder_uri = NULL, *tmp_message_uid = NULL;
2434 em_utils_get_real_folder_uri_and_message_uid (
2435 folder, uid, &folder_uri, &tmp_message_uid);
2437 e_msg_composer_set_source_headers (
2438 composer, folder_uri, tmp_message_uid,
2439 CAMEL_MESSAGE_FORWARDED);
2441 g_free (folder_uri);
2442 g_free (tmp_message_uid);
2445 emu_update_composers_security (composer, validity_found);
2446 composer_set_no_change (composer);
2447 gtk_widget_show (GTK_WIDGET (composer));
2449 g_free (text);
2452 g_clear_object (&session);
2453 g_free (forward);
2457 * em_utils_forward_message:
2458 * @composer: an #EMsgComposer
2459 * @message: a #CamelMimeMessage to forward
2460 * @style: the forward style to use
2461 * @folder: (nullable): a #CamelFolder, or %NULL
2462 * @uid: (nullable): the UID of %message, or %NULL
2464 * Forwards @message in the given @style.
2466 * If @style is #E_MAIL_FORWARD_STYLE_ATTACHED, the new message is
2467 * created as follows. If there is more than a single message in @uids,
2468 * a multipart/digest will be constructed and attached to a new composer
2469 * window preset with the appropriate header defaults for forwarding the
2470 * first message in the list. If only one message is to be forwarded,
2471 * it is forwarded as a simple message/rfc822 attachment.
2473 * If @style is #E_MAIL_FORWARD_STYLE_INLINE, each message is forwarded
2474 * in its own composer window in 'inline' form.
2476 * If @style is #E_MAIL_FORWARD_STYLE_QUOTED, each message is forwarded
2477 * in its own composer window in 'quoted' form (each line starting with
2478 * a "> ").
2480 void
2481 em_utils_forward_message (EMsgComposer *composer,
2482 CamelMimeMessage *message,
2483 EMailForwardStyle style,
2484 CamelFolder *folder,
2485 const gchar *uid)
2487 CamelMimePart *part;
2488 gchar *subject;
2490 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
2491 g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
2493 e_msg_composer_set_is_reply_or_forward (composer, TRUE);
2495 switch (style) {
2496 case E_MAIL_FORWARD_STYLE_ATTACHED:
2497 default:
2498 part = mail_tool_make_message_attachment (message);
2499 subject = mail_tool_generate_forward_subject (message);
2501 em_utils_forward_attachment (composer, part, subject, NULL, NULL);
2503 g_object_unref (part);
2504 g_free (subject);
2505 break;
2507 case E_MAIL_FORWARD_STYLE_INLINE:
2508 case E_MAIL_FORWARD_STYLE_QUOTED:
2509 forward_non_attached (composer, folder, uid, message, style);
2510 break;
2514 void
2515 em_utils_forward_attachment (EMsgComposer *composer,
2516 CamelMimePart *part,
2517 const gchar *subject,
2518 CamelFolder *folder,
2519 GPtrArray *uids)
2521 CamelDataWrapper *content;
2523 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
2524 g_return_if_fail (CAMEL_IS_MIME_PART (part));
2526 if (folder != NULL)
2527 g_return_if_fail (CAMEL_IS_FOLDER (folder));
2529 e_msg_composer_set_is_reply_or_forward (composer, TRUE);
2531 set_up_new_composer (composer, subject, folder, NULL, NULL);
2533 e_msg_composer_attach (composer, part);
2535 content = camel_medium_get_content (CAMEL_MEDIUM (part));
2536 if (CAMEL_IS_MIME_MESSAGE (content)) {
2537 emu_add_composer_references_from_message (composer, CAMEL_MIME_MESSAGE (content));
2538 } else if (CAMEL_IS_MULTIPART (content)) {
2539 const gchar *mime_type;
2541 mime_type = camel_data_wrapper_get_mime_type (content);
2542 if (mime_type && g_ascii_strcasecmp (mime_type, "multipart/digest") == 0) {
2543 /* This is the way evolution forwards multiple messages as attachment */
2544 CamelMultipart *multipart;
2545 guint ii, nparts;
2547 multipart = CAMEL_MULTIPART (content);
2548 nparts = camel_multipart_get_number (multipart);
2550 for (ii = 0; ii < nparts; ii++) {
2551 CamelMimePart *mpart;
2553 mpart = camel_multipart_get_part (multipart, ii);
2554 mime_type = camel_data_wrapper_get_mime_type (CAMEL_DATA_WRAPPER (mpart));
2556 if (mime_type && g_ascii_strcasecmp (mime_type, "message/rfc822") == 0) {
2557 content = camel_medium_get_content (CAMEL_MEDIUM (mpart));
2559 if (CAMEL_IS_MIME_MESSAGE (content))
2560 emu_add_composer_references_from_message (composer, CAMEL_MIME_MESSAGE (content));
2566 if (uids != NULL)
2567 setup_forward_attached_callbacks (composer, folder, uids);
2569 composer_set_no_change (composer);
2571 gtk_widget_show (GTK_WIDGET (composer));
2575 * em_utils_redirect_message:
2576 * @composer: an #EMsgComposer
2577 * @message: message to redirect
2579 * Sets up the @composer to redirect @message (Note: only headers will be
2580 * editable). Adds Resent-From/Resent-To/etc headers.
2582 * Since: 3.22
2584 void
2585 em_utils_redirect_message (EMsgComposer *composer,
2586 CamelMimeMessage *message)
2588 ESourceRegistry *registry;
2589 ESource *source;
2590 EShell *shell;
2591 CamelMedium *medium;
2592 gchar *identity_uid = NULL, *alias_name = NULL, *alias_address = NULL;
2594 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
2595 g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
2597 shell = e_msg_composer_get_shell (composer);
2598 medium = CAMEL_MEDIUM (message);
2600 /* QMail will refuse to send a message if it finds one of
2601 * it's Delivered-To headers in the message, so remove all
2602 * Delivered-To headers. Fixes bug #23635. */
2603 while (camel_medium_get_header (medium, "Delivered-To"))
2604 camel_medium_remove_header (medium, "Delivered-To");
2606 while (camel_medium_get_header (medium, "Bcc"))
2607 camel_medium_remove_header (medium, "Bcc");
2609 while (camel_medium_get_header (medium, "Resent-Bcc"))
2610 camel_medium_remove_header (medium, "Resent-Bcc");
2612 registry = e_shell_get_registry (shell);
2614 /* This returns a new ESource reference. */
2615 source = em_utils_check_send_account_override (shell, message, NULL, &alias_name, &alias_address);
2616 if (!source)
2617 source = em_utils_guess_mail_identity_with_recipients_and_sort (
2618 registry, message, NULL, NULL, &alias_name, &alias_address, sort_sources_by_ui, shell);
2620 if (source != NULL) {
2621 identity_uid = e_source_dup_uid (source);
2622 g_object_unref (source);
2625 e_msg_composer_setup_redirect (composer, message, identity_uid, alias_name, alias_address, NULL);
2627 g_free (identity_uid);
2628 g_free (alias_name);
2629 g_free (alias_address);
2631 gtk_widget_show (GTK_WIDGET (composer));
2633 composer_set_no_change (composer);
2636 /* Replying to messages... */
2638 EDestination **
2639 em_utils_camel_address_to_destination (CamelInternetAddress *iaddr)
2641 EDestination *dest, **destv;
2642 gint n, i, j;
2644 if (iaddr == NULL)
2645 return NULL;
2647 if ((n = camel_address_length ((CamelAddress *) iaddr)) == 0)
2648 return NULL;
2650 destv = g_malloc (sizeof (EDestination *) * (n + 1));
2651 for (i = 0, j = 0; i < n; i++) {
2652 const gchar *name, *addr;
2654 if (camel_internet_address_get (iaddr, i, &name, &addr)) {
2655 dest = e_destination_new ();
2656 e_destination_set_name (dest, name);
2657 e_destination_set_email (dest, addr);
2659 destv[j++] = dest;
2663 if (j == 0) {
2664 g_free (destv);
2665 return NULL;
2668 destv[j] = NULL;
2670 return destv;
2673 static gchar *
2674 emcu_construct_reply_subject (const gchar *source_subject)
2676 gchar *res;
2678 if (source_subject) {
2679 GSettings *settings;
2680 gboolean skip_len = -1;
2682 if (em_utils_is_re_in_subject (source_subject, &skip_len, NULL, NULL) && skip_len > 0)
2683 source_subject = source_subject + skip_len;
2685 settings = e_util_ref_settings ("org.gnome.evolution.mail");
2686 if (g_settings_get_boolean (settings, "composer-use-localized-fwd-re")) {
2687 /* Translators: This is a reply attribution in the message reply subject. The %s is replaced with the subject of the original message. Both 'Re'-s in the 'reply-attribution' translation context should translate into the same string, the same as the ':' separator. */
2688 res = g_strdup_printf (C_("reply-attribution", "Re: %s"), source_subject);
2689 } else {
2690 /* Do not localize this string */
2691 res = g_strdup_printf ("Re: %s", source_subject);
2693 g_clear_object (&settings);
2694 } else {
2695 res = g_strdup ("");
2698 return res;
2701 static void
2702 reply_setup_composer_recipients (EMsgComposer *composer,
2703 CamelInternetAddress *to,
2704 CamelInternetAddress *cc,
2705 CamelFolder *folder,
2706 const gchar *message_uid,
2707 CamelNNTPAddress *postto)
2709 EComposerHeaderTable *table;
2710 EDestination **tov, **ccv;
2712 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
2713 if (to != NULL)
2714 g_return_if_fail (CAMEL_IS_INTERNET_ADDRESS (to));
2716 if (cc != NULL)
2717 g_return_if_fail (CAMEL_IS_INTERNET_ADDRESS (cc));
2719 /* Construct the tov/ccv */
2720 tov = em_utils_camel_address_to_destination (to);
2721 ccv = em_utils_camel_address_to_destination (cc);
2723 table = e_msg_composer_get_header_table (composer);
2724 e_composer_header_table_set_destinations_to (table, tov);
2726 /* Add destinations instead of setting, so we don't remove
2727 * automatic CC addresses that have already been added. */
2728 e_composer_header_table_add_destinations_cc (table, ccv);
2730 e_destination_freev (tov);
2731 e_destination_freev (ccv);
2733 /* Add post-to, if necessary */
2734 if (postto && camel_address_length ((CamelAddress *) postto)) {
2735 CamelFolder *use_folder = folder, *temp_folder = NULL;
2736 gchar *store_url = NULL;
2737 gchar *post;
2739 if (use_folder && CAMEL_IS_VEE_FOLDER (use_folder) && message_uid) {
2740 em_utils_get_real_folder_and_message_uid (use_folder, message_uid, &temp_folder, NULL, NULL);
2742 if (temp_folder)
2743 use_folder = temp_folder;
2746 if (use_folder) {
2747 CamelStore *parent_store;
2748 CamelService *service;
2749 CamelURL *url;
2751 parent_store = camel_folder_get_parent_store (use_folder);
2753 service = CAMEL_SERVICE (parent_store);
2754 url = camel_service_new_camel_url (service);
2756 store_url = camel_url_to_string (
2757 url, CAMEL_URL_HIDE_ALL);
2758 if (store_url[strlen (store_url) - 1] == '/')
2759 store_url[strlen (store_url) - 1] = '\0';
2761 camel_url_free (url);
2764 post = camel_address_encode ((CamelAddress *) postto);
2765 e_composer_header_table_set_post_to_base (
2766 table, store_url ? store_url : "", post);
2767 g_free (post);
2768 g_free (store_url);
2769 g_clear_object (&temp_folder);
2773 static void
2774 reply_setup_composer (EMsgComposer *composer,
2775 CamelMimeMessage *message,
2776 const gchar *identity_uid,
2777 const gchar *identity_name,
2778 const gchar *identity_address,
2779 CamelInternetAddress *to,
2780 CamelInternetAddress *cc,
2781 CamelFolder *folder,
2782 const gchar *message_uid,
2783 CamelNNTPAddress *postto)
2785 gchar *message_id, *references;
2786 EComposerHeaderTable *table;
2787 CamelMedium *medium;
2788 gchar *subject;
2790 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
2791 g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
2793 e_msg_composer_set_is_reply_or_forward (composer, TRUE);
2795 if (to != NULL)
2796 g_return_if_fail (CAMEL_IS_INTERNET_ADDRESS (to));
2798 if (cc != NULL)
2799 g_return_if_fail (CAMEL_IS_INTERNET_ADDRESS (cc));
2801 reply_setup_composer_recipients (composer, to, cc, folder, message_uid, postto);
2803 /* Set the subject of the new message. */
2804 subject = emcu_construct_reply_subject (camel_mime_message_get_subject (message));
2806 table = e_msg_composer_get_header_table (composer);
2807 e_composer_header_table_set_subject (table, subject);
2808 e_composer_header_table_set_identity_uid (table, identity_uid, identity_name, identity_address);
2810 g_free (subject);
2812 /* Add In-Reply-To and References. */
2814 medium = CAMEL_MEDIUM (message);
2815 message_id = camel_header_unfold (camel_medium_get_header (medium, "Message-ID"));
2816 references = camel_header_unfold (camel_medium_get_header (medium, "References"));
2818 if (message_id != NULL) {
2819 gchar *reply_refs;
2821 e_msg_composer_add_header (
2822 composer, "In-Reply-To", message_id);
2824 if (references)
2825 reply_refs = g_strdup_printf (
2826 "%s %s", references, message_id);
2827 else
2828 reply_refs = g_strdup (message_id);
2830 e_msg_composer_add_header (
2831 composer, "References", reply_refs);
2832 g_free (reply_refs);
2834 } else if (references != NULL) {
2835 e_msg_composer_add_header (
2836 composer, "References", references);
2839 g_free (message_id);
2840 g_free (references);
2843 static gboolean
2844 get_reply_list (CamelMimeMessage *message,
2845 CamelInternetAddress *to)
2847 const gchar *header, *p;
2848 gchar *addr;
2850 /* Examples:
2852 * List-Post: <mailto:list@host.com>
2853 * List-Post: <mailto:moderator@host.com?subject=list%20posting>
2854 * List-Post: NO (posting not allowed on this list)
2856 if (!(header = camel_medium_get_header ((CamelMedium *) message, "List-Post")))
2857 return FALSE;
2859 while (*header == ' ' || *header == '\t')
2860 header++;
2862 /* check for NO */
2863 if (!g_ascii_strncasecmp (header, "NO", 2))
2864 return FALSE;
2866 /* Search for the first mailto angle-bracket enclosed URL.
2867 * (See rfc2369, Section 2, paragraph 3 for details) */
2868 if (!(header = camel_strstrcase (header, "<mailto:")))
2869 return FALSE;
2871 header += 8;
2873 p = header;
2874 while (*p && !strchr ("?>", *p))
2875 p++;
2877 addr = g_strndup (header, p - header);
2878 camel_internet_address_add (to, NULL, addr);
2879 g_free (addr);
2881 return TRUE;
2884 gboolean
2885 em_utils_is_munged_list_message (CamelMimeMessage *message)
2887 CamelInternetAddress *reply_to, *list;
2888 gboolean result = FALSE;
2890 reply_to = camel_mime_message_get_reply_to (message);
2891 if (reply_to) {
2892 list = camel_internet_address_new ();
2894 if (get_reply_list (message, list) &&
2895 camel_address_length (CAMEL_ADDRESS (list)) ==
2896 camel_address_length (CAMEL_ADDRESS (reply_to))) {
2897 gint i;
2898 const gchar *r_name, *r_addr;
2899 const gchar *l_name, *l_addr;
2901 for (i = 0; i < camel_address_length (CAMEL_ADDRESS (list)); i++) {
2902 if (!camel_internet_address_get (reply_to, i, &r_name, &r_addr))
2903 break;
2904 if (!camel_internet_address_get (list, i, &l_name, &l_addr))
2905 break;
2906 if (strcmp (l_addr, r_addr))
2907 break;
2909 if (i == camel_address_length (CAMEL_ADDRESS (list)))
2910 result = TRUE;
2912 g_object_unref (list);
2914 return result;
2917 static CamelInternetAddress *
2918 get_reply_to (CamelMimeMessage *message)
2920 CamelInternetAddress *reply_to;
2922 reply_to = camel_mime_message_get_reply_to (message);
2923 if (reply_to) {
2924 GSettings *settings;
2925 gboolean ignore_list_reply_to;
2927 settings = e_util_ref_settings ("org.gnome.evolution.mail");
2928 ignore_list_reply_to = g_settings_get_boolean (
2929 settings, "composer-ignore-list-reply-to");
2930 g_object_unref (settings);
2932 if (ignore_list_reply_to && em_utils_is_munged_list_message (message))
2933 reply_to = NULL;
2935 if (!reply_to)
2936 reply_to = camel_mime_message_get_from (message);
2938 return reply_to;
2941 static void
2942 get_reply_sender (CamelMimeMessage *message,
2943 CamelInternetAddress *to,
2944 CamelNNTPAddress *postto)
2946 CamelInternetAddress *reply_to;
2947 CamelMedium *medium;
2948 const gchar *posthdr = NULL;
2950 medium = CAMEL_MEDIUM (message);
2952 /* check whether there is a 'Newsgroups: ' header in there */
2953 if (postto != NULL && posthdr == NULL)
2954 posthdr = camel_medium_get_header (medium, "Followup-To");
2956 if (postto != NULL && posthdr == NULL)
2957 posthdr = camel_medium_get_header (medium, "Newsgroups");
2959 if (postto != NULL && posthdr != NULL) {
2960 camel_address_decode (CAMEL_ADDRESS (postto), posthdr);
2961 return;
2964 reply_to = get_reply_to (message);
2966 if (reply_to != NULL) {
2967 const gchar *name;
2968 const gchar *addr;
2969 gint ii = 0;
2971 while (camel_internet_address_get (reply_to, ii++, &name, &addr))
2972 camel_internet_address_add (to, name, addr);
2976 void
2977 em_utils_get_reply_sender (CamelMimeMessage *message,
2978 CamelInternetAddress *to,
2979 CamelNNTPAddress *postto)
2981 get_reply_sender (message, to, postto);
2984 static void
2985 get_reply_from (CamelMimeMessage *message,
2986 CamelInternetAddress *to,
2987 CamelNNTPAddress *postto)
2989 CamelInternetAddress *from;
2990 CamelMedium *medium;
2991 const gchar *name, *addr;
2992 const gchar *posthdr = NULL;
2994 medium = CAMEL_MEDIUM (message);
2996 /* check whether there is a 'Newsgroups: ' header in there */
2997 if (postto != NULL && posthdr == NULL)
2998 posthdr = camel_medium_get_header (medium, "Followup-To");
3000 if (postto != NULL && posthdr == NULL)
3001 posthdr = camel_medium_get_header (medium, "Newsgroups");
3003 if (postto != NULL && posthdr != NULL) {
3004 camel_address_decode (CAMEL_ADDRESS (postto), posthdr);
3005 return;
3008 from = camel_mime_message_get_from (message);
3010 if (from != NULL) {
3011 gint ii = 0;
3013 while (camel_internet_address_get (from, ii++, &name, &addr))
3014 camel_internet_address_add (to, name, addr);
3018 static void
3019 get_reply_recipient (CamelMimeMessage *message,
3020 CamelInternetAddress *to,
3021 CamelNNTPAddress *postto,
3022 CamelInternetAddress *address)
3024 CamelMedium *medium;
3025 const gchar *posthdr = NULL;
3027 medium = CAMEL_MEDIUM (message);
3029 /* check whether there is a 'Newsgroups: ' header in there */
3030 if (postto != NULL && posthdr == NULL)
3031 posthdr = camel_medium_get_header (medium, "Followup-To");
3033 if (postto != NULL && posthdr == NULL)
3034 posthdr = camel_medium_get_header (medium, "Newsgroups");
3036 if (postto != NULL && posthdr != NULL) {
3037 camel_address_decode (CAMEL_ADDRESS (postto), posthdr);
3038 return;
3041 if (address != NULL) {
3042 const gchar *name;
3043 const gchar *addr;
3044 gint ii = 0;
3046 while (camel_internet_address_get (address, ii++, &name, &addr))
3047 camel_internet_address_add (to, name, addr);
3052 static void
3053 concat_unique_addrs (CamelInternetAddress *dest,
3054 CamelInternetAddress *src,
3055 GHashTable *rcpt_hash)
3057 const gchar *name, *addr;
3058 gint i;
3060 for (i = 0; camel_internet_address_get (src, i, &name, &addr); i++) {
3061 if (!g_hash_table_contains (rcpt_hash, addr)) {
3062 camel_internet_address_add (dest, name, addr);
3063 g_hash_table_insert (rcpt_hash, g_strdup (addr), NULL);
3068 static void
3069 add_source_to_recipient_hash (ESourceRegistry *registry,
3070 GHashTable *rcpt_hash,
3071 const gchar *address,
3072 ESource *source,
3073 gboolean source_is_default)
3075 ESource *cached_source;
3076 gboolean insert_source;
3078 g_return_if_fail (rcpt_hash != NULL);
3079 g_return_if_fail (E_IS_SOURCE (source));
3081 if (!address || !*address)
3082 return;
3084 cached_source = g_hash_table_lookup (rcpt_hash, address);
3086 insert_source = source_is_default || !cached_source;
3088 if (insert_source)
3089 g_hash_table_insert (rcpt_hash, g_strdup (address), g_object_ref (source));
3092 static void
3093 unref_nonull_object (gpointer ptr)
3095 if (ptr)
3096 g_object_unref (ptr);
3099 static GHashTable *
3100 generate_recipient_hash (ESourceRegistry *registry)
3102 GHashTable *rcpt_hash;
3103 ESource *default_source;
3104 GList *list, *link;
3105 const gchar *extension_name;
3107 g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
3109 rcpt_hash = g_hash_table_new_full (
3110 camel_strcase_hash,
3111 camel_strcase_equal,
3112 g_free, unref_nonull_object);
3114 default_source = e_source_registry_ref_default_mail_identity (registry);
3116 extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY;
3117 list = e_source_registry_list_sources (registry, extension_name);
3119 for (link = list; link != NULL; link = g_list_next (link)) {
3120 ESource *source = E_SOURCE (link->data);
3121 ESourceMailIdentity *extension;
3122 GHashTable *aliases;
3123 const gchar *address;
3124 gboolean source_is_default;
3126 /* No default mail identity implies there are no mail
3127 * identities at all and so we should never get here. */
3128 g_warn_if_fail (default_source != NULL);
3130 if (!e_source_registry_check_enabled (registry, source))
3131 continue;
3133 source_is_default = e_source_equal (source, default_source);
3135 extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY;
3136 extension = e_source_get_extension (source, extension_name);
3138 address = e_source_mail_identity_get_address (extension);
3140 add_source_to_recipient_hash (registry, rcpt_hash, address, source, source_is_default);
3142 aliases = e_source_mail_identity_get_aliases_as_hash_table (extension);
3143 if (aliases) {
3144 GHashTableIter iter;
3145 gpointer key;
3147 g_hash_table_iter_init (&iter, aliases);
3148 while (g_hash_table_iter_next (&iter, &key, NULL)) {
3149 address = key;
3151 add_source_to_recipient_hash (registry, rcpt_hash, address, source, source_is_default);
3154 g_hash_table_destroy (aliases);
3158 g_list_free_full (list, (GDestroyNotify) g_object_unref);
3160 if (default_source != NULL)
3161 g_object_unref (default_source);
3163 return rcpt_hash;
3166 void
3167 em_utils_get_reply_all (ESourceRegistry *registry,
3168 CamelMimeMessage *message,
3169 CamelInternetAddress *to,
3170 CamelInternetAddress *cc,
3171 CamelNNTPAddress *postto)
3173 CamelInternetAddress *reply_to;
3174 CamelInternetAddress *to_addrs;
3175 CamelInternetAddress *cc_addrs;
3176 CamelMedium *medium;
3177 const gchar *name, *addr;
3178 const gchar *posthdr = NULL;
3179 GHashTable *rcpt_hash;
3181 g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
3182 g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
3183 g_return_if_fail (CAMEL_IS_INTERNET_ADDRESS (to));
3184 g_return_if_fail (CAMEL_IS_INTERNET_ADDRESS (cc));
3186 medium = CAMEL_MEDIUM (message);
3188 /* check whether there is a 'Newsgroups: ' header in there */
3189 if (postto != NULL && posthdr == NULL)
3190 posthdr = camel_medium_get_header (medium, "Followup-To");
3192 if (postto != NULL && posthdr == NULL)
3193 posthdr = camel_medium_get_header (medium, "Newsgroups");
3195 if (postto != NULL && posthdr != NULL)
3196 camel_address_decode (CAMEL_ADDRESS (postto), posthdr);
3198 rcpt_hash = generate_recipient_hash (registry);
3200 reply_to = get_reply_to (message);
3201 to_addrs = camel_mime_message_get_recipients (
3202 message, CAMEL_RECIPIENT_TYPE_TO);
3203 cc_addrs = camel_mime_message_get_recipients (
3204 message, CAMEL_RECIPIENT_TYPE_CC);
3206 if (reply_to != NULL) {
3207 gint ii = 0;
3209 while (camel_internet_address_get (reply_to, ii++, &name, &addr)) {
3210 /* Ignore references to the Reply-To address
3211 * in the To and Cc lists. */
3212 if (addr && !g_hash_table_contains (rcpt_hash, addr)) {
3213 /* In the case we are doing a Reply-To-All,
3214 * we do not want to include the user's email
3215 * address because replying to oneself is
3216 * kinda silly. */
3217 camel_internet_address_add (to, name, addr);
3218 g_hash_table_insert (rcpt_hash, g_strdup (addr), NULL);
3223 concat_unique_addrs (to, to_addrs, rcpt_hash);
3224 concat_unique_addrs (cc, cc_addrs, rcpt_hash);
3226 /* Promote the first Cc: address to To: if To: is empty. */
3227 if (camel_address_length ((CamelAddress *) to) == 0 &&
3228 camel_address_length ((CamelAddress *) cc) > 0) {
3229 camel_internet_address_get (cc, 0, &name, &addr);
3230 camel_internet_address_add (to, name, addr);
3231 camel_address_remove ((CamelAddress *) cc, 0);
3234 /* If To: is still empty, may we removed duplicates (i.e. ourself),
3235 * so add the original To if it was set. */
3236 if (camel_address_length ((CamelAddress *) to) == 0
3237 && (camel_internet_address_get (to_addrs, 0, &name, &addr)
3238 || camel_internet_address_get (cc_addrs, 0, &name, &addr))) {
3239 camel_internet_address_add (to, name, addr);
3242 g_hash_table_destroy (rcpt_hash);
3245 enum {
3246 ATTRIB_UNKNOWN,
3247 ATTRIB_CUSTOM,
3248 ATTRIB_TIMEZONE,
3249 ATTRIB_STRFTIME,
3250 ATTRIB_TM_SEC,
3251 ATTRIB_TM_MIN,
3252 ATTRIB_TM_24HOUR,
3253 ATTRIB_TM_12HOUR,
3254 ATTRIB_TM_MDAY,
3255 ATTRIB_TM_MON,
3256 ATTRIB_TM_YEAR,
3257 ATTRIB_TM_2YEAR,
3258 ATTRIB_TM_WDAY, /* not actually used */
3259 ATTRIB_TM_YDAY
3262 typedef void (*AttribFormatter) (GString *str,
3263 const gchar *attr,
3264 CamelMimeMessage *message);
3266 static void
3267 format_sender (GString *str,
3268 const gchar *attr,
3269 CamelMimeMessage *message)
3271 CamelInternetAddress *sender;
3272 const gchar *name, *addr = NULL;
3273 gchar *tmp = NULL;
3275 sender = camel_mime_message_get_from (message);
3276 if (sender != NULL && camel_address_length (CAMEL_ADDRESS (sender)) > 0) {
3277 camel_internet_address_get (sender, 0, &name, &addr);
3279 if (name && !*name) {
3280 name = NULL;
3281 } else if (name && *name == '\"') {
3282 gint len = strlen (name);
3284 if (len == 1) {
3285 name = NULL;
3286 } else if (len > 1 && name[len - 1] == '\"') {
3287 if (len == 2) {
3288 name = NULL;
3289 } else {
3290 tmp = g_strndup (name + 1, len - 2);
3291 name = tmp;
3295 } else {
3296 name = _("an unknown sender");
3299 if (name && !strcmp (attr, "{SenderName}")) {
3300 g_string_append (str, name);
3301 } else if (addr && !strcmp (attr, "{SenderEMail}")) {
3302 g_string_append (str, addr);
3303 } else if (name && *name) {
3304 g_string_append (str, name);
3305 } else if (addr) {
3306 g_string_append (str, addr);
3309 g_free (tmp);
3312 static struct {
3313 const gchar *name;
3314 gint type;
3315 struct {
3316 const gchar *format; /* strftime or printf format */
3317 AttribFormatter formatter; /* custom formatter */
3318 } v;
3319 } attribvars[] = {
3320 { "{Sender}", ATTRIB_CUSTOM, { NULL, format_sender } },
3321 { "{SenderName}", ATTRIB_CUSTOM, { NULL, format_sender } },
3322 { "{SenderEMail}", ATTRIB_CUSTOM, { NULL, format_sender } },
3323 { "{AbbrevWeekdayName}", ATTRIB_STRFTIME, { "%a", NULL } },
3324 { "{WeekdayName}", ATTRIB_STRFTIME, { "%A", NULL } },
3325 { "{AbbrevMonthName}", ATTRIB_STRFTIME, { "%b", NULL } },
3326 { "{MonthName}", ATTRIB_STRFTIME, { "%B", NULL } },
3327 { "{AmPmUpper}", ATTRIB_STRFTIME, { "%p", NULL } },
3328 { "{AmPmLower}", ATTRIB_STRFTIME, { "%P", NULL } },
3329 { "{Day}", ATTRIB_TM_MDAY, { "%02d", NULL } }, /* %d 01-31 */
3330 { "{ Day}", ATTRIB_TM_MDAY, { "% 2d", NULL } }, /* %e 1-31 */
3331 { "{24Hour}", ATTRIB_TM_24HOUR, { "%02d", NULL } }, /* %H 00-23 */
3332 { "{12Hour}", ATTRIB_TM_12HOUR, { "%02d", NULL } }, /* %I 00-12 */
3333 { "{DayOfYear}", ATTRIB_TM_YDAY, { "%d", NULL } }, /* %j 1-366 */
3334 { "{Month}", ATTRIB_TM_MON, { "%02d", NULL } }, /* %m 01-12 */
3335 { "{Minute}", ATTRIB_TM_MIN, { "%02d", NULL } }, /* %M 00-59 */
3336 { "{Seconds}", ATTRIB_TM_SEC, { "%02d", NULL } }, /* %S 00-61 */
3337 { "{2DigitYear}", ATTRIB_TM_2YEAR, { "%02d", NULL } }, /* %y */
3338 { "{Year}", ATTRIB_TM_YEAR, { "%04d", NULL } }, /* %Y */
3339 { "{TimeZone}", ATTRIB_TIMEZONE, { "%+05d", NULL } }
3342 static gchar *
3343 attribution_format (CamelMimeMessage *message)
3345 register const gchar *inptr;
3346 const gchar *start;
3347 gint tzone, len, i;
3348 gchar buf[64], *s;
3349 GString *str;
3350 struct tm tm;
3351 time_t date;
3352 gint type;
3353 gchar *format = quoting_text (QUOTING_ATTRIBUTION);
3355 str = g_string_new ("");
3357 date = camel_mime_message_get_date (message, &tzone);
3359 if (date == CAMEL_MESSAGE_DATE_CURRENT) {
3360 /* The message has no Date: header, look at Received: */
3361 date = camel_mime_message_get_date_received (message, &tzone);
3363 if (date == CAMEL_MESSAGE_DATE_CURRENT) {
3364 /* That didn't work either, use current time */
3365 time (&date);
3366 tzone = 0;
3367 } else if (tzone == 0) {
3368 GSettings *settings;
3370 settings = e_util_ref_settings ("org.gnome.evolution.mail");
3372 if (g_settings_get_boolean (settings, "composer-reply-credits-utc-to-localtime")) {
3373 struct tm gmtm, lctm;
3374 time_t gmtt, lctt;
3376 gmtime_r (&date, &gmtm);
3377 localtime_r (&date, &lctm);
3379 gmtt = mktime (&gmtm);
3380 lctt = mktime (&lctm);
3382 tzone = (lctt - gmtt) * 100 / 3600;
3385 g_clear_object (&settings);
3388 /* Convert to UTC */
3389 date += (tzone / 100) * 60 * 60;
3390 date += (tzone % 100) * 60;
3392 gmtime_r (&date, &tm);
3394 inptr = format;
3395 while (*inptr != '\0') {
3396 start = inptr;
3397 while (*inptr && strncmp (inptr, "${", 2) != 0)
3398 inptr++;
3400 g_string_append_len (str, start, inptr - start);
3402 if (*inptr == '\0')
3403 break;
3405 start = ++inptr;
3406 while (*inptr && *inptr != '}')
3407 inptr++;
3409 if (*inptr != '}') {
3410 /* broken translation */
3411 g_string_append_len (str, "${", 2);
3412 inptr = start + 1;
3413 continue;
3416 inptr++;
3417 len = inptr - start;
3418 type = ATTRIB_UNKNOWN;
3419 for (i = 0; i < G_N_ELEMENTS (attribvars); i++) {
3420 if (!strncmp (attribvars[i].name, start, len)) {
3421 type = attribvars[i].type;
3422 break;
3426 switch (type) {
3427 case ATTRIB_CUSTOM:
3428 attribvars[i].v.formatter (
3429 str, attribvars[i].name, message);
3430 break;
3431 case ATTRIB_TIMEZONE:
3432 g_string_append_printf (
3433 str, attribvars[i].v.format, tzone);
3434 break;
3435 case ATTRIB_STRFTIME:
3436 e_utf8_strftime_match_lc_messages (
3437 buf, sizeof (buf), attribvars[i].v.format, &tm);
3438 g_string_append (str, buf);
3439 break;
3440 case ATTRIB_TM_SEC:
3441 g_string_append_printf (
3442 str, attribvars[i].v.format, tm.tm_sec);
3443 break;
3444 case ATTRIB_TM_MIN:
3445 g_string_append_printf (
3446 str, attribvars[i].v.format, tm.tm_min);
3447 break;
3448 case ATTRIB_TM_24HOUR:
3449 g_string_append_printf (
3450 str, attribvars[i].v.format, tm.tm_hour);
3451 break;
3452 case ATTRIB_TM_12HOUR:
3453 g_string_append_printf (
3454 str, attribvars[i].v.format,
3455 (tm.tm_hour + 1) % 13);
3456 break;
3457 case ATTRIB_TM_MDAY:
3458 g_string_append_printf (
3459 str, attribvars[i].v.format, tm.tm_mday);
3460 break;
3461 case ATTRIB_TM_MON:
3462 g_string_append_printf (
3463 str, attribvars[i].v.format, tm.tm_mon + 1);
3464 break;
3465 case ATTRIB_TM_YEAR:
3466 g_string_append_printf (
3467 str, attribvars[i].v.format, tm.tm_year + 1900);
3468 break;
3469 case ATTRIB_TM_2YEAR:
3470 g_string_append_printf (
3471 str, attribvars[i].v.format, tm.tm_year % 100);
3472 break;
3473 case ATTRIB_TM_WDAY:
3474 /* not actually used */
3475 g_string_append_printf (
3476 str, attribvars[i].v.format, tm.tm_wday);
3477 break;
3478 case ATTRIB_TM_YDAY:
3479 g_string_append_printf (
3480 str, attribvars[i].v.format, tm.tm_yday + 1);
3481 break;
3482 default:
3483 /* Misspelled variable? Drop the
3484 * format argument and continue. */
3485 break;
3489 s = str->str;
3490 g_string_free (str, FALSE);
3491 g_free (format);
3493 return s;
3496 static void
3497 composer_set_body (EMsgComposer *composer,
3498 CamelMimeMessage *message,
3499 EMailReplyStyle style,
3500 EMailPartList *parts_list)
3502 gchar *text, *credits, *original;
3503 CamelMimePart *part;
3504 CamelSession *session;
3505 GSettings *settings;
3506 guint32 validity_found = 0, keep_sig_flag = 0;
3508 settings = e_util_ref_settings ("org.gnome.evolution.mail");
3509 if (g_settings_get_boolean (settings, "composer-reply-keep-signature"))
3510 keep_sig_flag = E_MAIL_FORMATTER_QUOTE_FLAG_KEEP_SIG;
3511 g_clear_object (&settings);
3513 session = e_msg_composer_ref_session (composer);
3515 switch (style) {
3516 case E_MAIL_REPLY_STYLE_DO_NOT_QUOTE:
3517 /* do nothing */
3518 break;
3519 case E_MAIL_REPLY_STYLE_ATTACH:
3520 /* attach the original message as an attachment */
3521 part = mail_tool_make_message_attachment (message);
3522 e_msg_composer_attach (composer, part);
3523 g_object_unref (part);
3524 break;
3525 case E_MAIL_REPLY_STYLE_OUTLOOK:
3526 original = quoting_text (QUOTING_ORIGINAL);
3527 text = em_utils_message_to_html (
3528 session, message, original, E_MAIL_FORMATTER_QUOTE_FLAG_HEADERS | keep_sig_flag,
3529 parts_list, NULL, NULL, &validity_found);
3530 e_msg_composer_set_body_text (composer, text, TRUE);
3531 g_free (text);
3532 g_free (original);
3533 emu_update_composers_security (composer, validity_found);
3534 break;
3536 case E_MAIL_REPLY_STYLE_QUOTED:
3537 default:
3538 /* do what any sane user would want when replying... */
3539 credits = attribution_format (message);
3540 text = em_utils_message_to_html (
3541 session, message, credits, E_MAIL_FORMATTER_QUOTE_FLAG_CITE | keep_sig_flag,
3542 parts_list, NULL, NULL, &validity_found);
3543 g_free (credits);
3544 e_msg_composer_set_body_text (composer, text, TRUE);
3545 g_free (text);
3546 emu_update_composers_security (composer, validity_found);
3547 break;
3550 g_object_unref (session);
3553 gchar *
3554 em_utils_construct_composer_text (CamelSession *session,
3555 CamelMimeMessage *message,
3556 EMailPartList *parts_list)
3558 gchar *text, *credits;
3560 g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
3562 credits = attribution_format (message);
3563 text = em_utils_message_to_html (
3564 session, message, credits, E_MAIL_FORMATTER_QUOTE_FLAG_CITE,
3565 parts_list, NULL, NULL, NULL);
3566 g_free (credits);
3568 return text;
3571 static gboolean
3572 emcu_folder_is_inbox (CamelFolder *folder)
3574 CamelSession *session;
3575 CamelStore *store;
3576 gboolean is_inbox = FALSE;
3578 g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
3580 store = camel_folder_get_parent_store (folder);
3581 if (!store)
3582 return FALSE;
3584 session = camel_service_ref_session (CAMEL_SERVICE (store));
3585 if (!session)
3586 return FALSE;
3588 if (E_IS_MAIL_SESSION (session)) {
3589 MailFolderCache *folder_cache;
3590 CamelFolderInfoFlags flags = 0;
3592 folder_cache = e_mail_session_get_folder_cache (E_MAIL_SESSION (session));
3593 if (folder_cache && mail_folder_cache_get_folder_info_flags (
3594 folder_cache, store, camel_folder_get_full_name (folder), &flags)) {
3595 is_inbox = (flags & CAMEL_FOLDER_TYPE_MASK) == CAMEL_FOLDER_TYPE_INBOX;
3599 g_object_unref (session);
3601 return is_inbox;
3604 typedef struct _AltReplyContext {
3605 EShell *shell;
3606 EAlertSink *alert_sink;
3607 CamelMimeMessage *source_message;
3608 CamelFolder *folder;
3609 gchar *message_uid;
3610 CamelMimeMessage *new_message; /* When processed with a template */
3611 EMailPartList *source;
3612 EMailReplyType type;
3613 EMailReplyStyle style;
3614 guint32 flags;
3615 gboolean template_preserve_subject;
3616 } AltReplyContext;
3618 static void
3619 alt_reply_context_free (gpointer ptr)
3621 AltReplyContext *context = ptr;
3623 if (context) {
3624 g_clear_object (&context->shell);
3625 g_clear_object (&context->alert_sink);
3626 g_clear_object (&context->source_message);
3627 g_clear_object (&context->folder);
3628 g_clear_object (&context->source);
3629 g_clear_object (&context->new_message);
3630 g_free (context->message_uid);
3631 g_free (context);
3635 static void
3636 alt_reply_composer_created_cb (GObject *source_object,
3637 GAsyncResult *result,
3638 gpointer user_data)
3640 AltReplyContext *context = user_data;
3641 EMsgComposer *composer;
3642 GError *error = NULL;
3644 g_return_if_fail (context != NULL);
3646 composer = e_msg_composer_new_finish (result, &error);
3648 if (composer) {
3649 EContentEditor *cnt_editor;
3651 cnt_editor = e_html_editor_get_content_editor (e_msg_composer_get_editor (composer));
3653 if (context->new_message) {
3654 CamelInternetAddress *to = NULL, *cc = NULL;
3655 CamelNNTPAddress *postto = NULL;
3656 gboolean need_reply_all = FALSE;
3658 if ((context->flags & (E_MAIL_REPLY_FLAG_FORMAT_PLAIN | E_MAIL_REPLY_FLAG_FORMAT_HTML)) != 0) {
3659 e_content_editor_set_html_mode (cnt_editor, (context->flags & E_MAIL_REPLY_FLAG_FORMAT_HTML) != 0);
3662 em_utils_edit_message (composer, context->folder, context->new_message, context->message_uid, TRUE);
3664 if (context->type == E_MAIL_REPLY_TO_SENDER) {
3665 /* Reply to sender */
3666 to = camel_internet_address_new ();
3667 if (context->folder)
3668 postto = camel_nntp_address_new ();
3669 get_reply_sender (context->source_message, to, postto);
3670 } else if (context->type == E_MAIL_REPLY_TO_LIST) {
3671 /* Reply to list */
3672 to = camel_internet_address_new ();
3674 if (!get_reply_list (context->source_message, to)) {
3675 need_reply_all = TRUE;
3676 g_clear_object (&to);
3678 } else if (context->type != E_MAIL_REPLY_TO_ALL) {
3679 g_warn_if_reached ();
3682 if (context->type == E_MAIL_REPLY_TO_ALL || need_reply_all) {
3683 /* Reply to all */
3684 to = camel_internet_address_new ();
3685 cc = camel_internet_address_new ();
3687 if (context->folder)
3688 postto = camel_nntp_address_new ();
3690 em_utils_get_reply_all (e_shell_get_registry (context->shell), context->source_message, to, cc, postto);
3693 reply_setup_composer_recipients (composer, to, cc, context->folder, context->message_uid, postto);
3695 composer_set_no_change (composer);
3697 g_clear_object (&to);
3698 g_clear_object (&cc);
3699 g_clear_object (&postto);
3701 if (context->folder && context->message_uid) {
3702 gchar *source_folder_uri = NULL;
3703 gchar *source_message_uid = NULL;
3705 em_utils_get_real_folder_uri_and_message_uid (context->folder, context->message_uid,
3706 &source_folder_uri, &source_message_uid);
3708 if (!source_message_uid)
3709 source_message_uid = g_strdup (context->message_uid);
3711 if (source_folder_uri) {
3712 e_msg_composer_set_source_headers (composer, source_folder_uri,
3713 source_message_uid, CAMEL_MESSAGE_ANSWERED | CAMEL_MESSAGE_SEEN);
3716 g_free (source_folder_uri);
3717 g_free (source_message_uid);
3719 } else {
3720 em_utils_reply_to_message (composer, context->source_message,
3721 context->folder, context->message_uid, context->type, context->style,
3722 context->source, NULL, context->flags);
3724 } else {
3725 e_alert_submit (context->alert_sink, "mail-composer:failed-create-composer",
3726 error ? error->message : _("Unknown error"), NULL);
3729 alt_reply_context_free (context);
3730 g_clear_error (&error);
3733 static void
3734 alt_reply_template_applied_cb (GObject *source_object,
3735 GAsyncResult *result,
3736 gpointer user_data)
3738 AltReplyContext *context = user_data;
3739 GError *error = NULL;
3741 g_return_if_fail (context != NULL);
3743 context->new_message = e_mail_templates_apply_finish (source_object, result, &error);
3745 if (context->new_message) {
3746 if (context->template_preserve_subject) {
3747 gchar *subject;
3749 subject = emcu_construct_reply_subject (camel_mime_message_get_subject (context->source_message));
3750 camel_mime_message_set_subject (context->new_message, subject);
3751 g_free (subject);
3754 e_msg_composer_new (context->shell, alt_reply_composer_created_cb, context);
3755 } else {
3756 e_alert_submit (context->alert_sink, "mail:no-retrieve-message",
3757 error ? error->message : _("Unknown error"), NULL);
3758 alt_reply_context_free (context);
3761 g_clear_error (&error);
3764 static void
3765 emcu_three_state_toggled_cb (GtkToggleButton *widget,
3766 gpointer user_data)
3768 glong *phandlerid = user_data;
3770 g_return_if_fail (GTK_IS_TOGGLE_BUTTON (widget));
3771 g_return_if_fail (phandlerid != NULL);
3773 g_signal_handler_block (widget, *phandlerid);
3775 if (gtk_toggle_button_get_inconsistent (widget) &&
3776 gtk_toggle_button_get_active (widget)) {
3777 gtk_toggle_button_set_active (widget, FALSE);
3778 gtk_toggle_button_set_inconsistent (widget, FALSE);
3779 } else if (!gtk_toggle_button_get_active (widget)) {
3780 gtk_toggle_button_set_inconsistent (widget, TRUE);
3781 gtk_toggle_button_set_active (widget, FALSE);
3782 } else {
3785 g_signal_handler_unblock (widget, *phandlerid);
3788 static void
3789 emcu_connect_three_state_changer (GtkToggleButton *toggle_button)
3791 glong *phandlerid;
3793 g_return_if_fail (GTK_IS_TOGGLE_BUTTON (toggle_button));
3795 phandlerid = g_new0 (glong, 1);
3797 *phandlerid = g_signal_connect_data (toggle_button, "toggled",
3798 G_CALLBACK (emcu_three_state_toggled_cb),
3799 phandlerid, (GClosureNotify) g_free, 0);
3802 static void
3803 emcu_three_state_set_value (GtkToggleButton *toggle_button,
3804 EThreeState value)
3806 g_return_if_fail (GTK_IS_TOGGLE_BUTTON (toggle_button));
3808 if (value == E_THREE_STATE_OFF) {
3809 gtk_toggle_button_set_active (toggle_button, FALSE);
3810 gtk_toggle_button_set_inconsistent (toggle_button, FALSE);
3811 } else if (value == E_THREE_STATE_ON) {
3812 gtk_toggle_button_set_active (toggle_button, TRUE);
3813 gtk_toggle_button_set_inconsistent (toggle_button, FALSE);
3814 } else {
3815 gtk_toggle_button_set_active (toggle_button, FALSE);
3816 gtk_toggle_button_set_inconsistent (toggle_button, TRUE);
3820 static EThreeState
3821 emcu_three_state_get_value (GtkToggleButton *toggle_button)
3823 g_return_val_if_fail (GTK_IS_TOGGLE_BUTTON (toggle_button), E_THREE_STATE_INCONSISTENT);
3825 if (gtk_toggle_button_get_inconsistent (toggle_button))
3826 return E_THREE_STATE_INCONSISTENT;
3827 else if (gtk_toggle_button_get_active (toggle_button))
3828 return E_THREE_STATE_ON;
3830 return E_THREE_STATE_OFF;
3833 static GtkComboBox *
3834 emcu_create_templates_combo (EShell *shell,
3835 const gchar *folder_uri,
3836 const gchar *message_uid)
3838 GtkComboBox *combo;
3839 GtkCellRenderer *renderer;
3840 EShellBackend *shell_backend;
3841 EMailSession *mail_session;
3842 EMailTemplatesStore *templates_store;
3843 GtkTreeStore *tree_store;
3844 GtkTreeIter found_iter;
3845 gboolean found_message = FALSE;
3847 shell_backend = e_shell_get_backend_by_name (shell, "mail");
3848 g_return_val_if_fail (E_IS_MAIL_BACKEND (shell_backend), NULL);
3850 mail_session = e_mail_backend_get_session (E_MAIL_BACKEND (shell_backend));
3851 templates_store = e_mail_templates_store_ref_default (e_mail_ui_session_get_account_store (E_MAIL_UI_SESSION (mail_session)));
3853 tree_store = e_mail_templates_store_build_model (templates_store, folder_uri, message_uid, &found_message, &found_iter);
3855 combo = GTK_COMBO_BOX (gtk_combo_box_new_with_model (GTK_TREE_MODEL (tree_store)));
3857 renderer = gtk_cell_renderer_text_new ();
3858 g_object_set (G_OBJECT (renderer),
3859 "ellipsize", PANGO_ELLIPSIZE_END,
3860 "max-width-chars", 60,
3861 NULL);
3863 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), renderer, TRUE);
3864 gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), renderer,
3865 "text", E_MAIL_TEMPLATES_STORE_COLUMN_DISPLAY_NAME,
3866 NULL);
3868 g_clear_object (&templates_store);
3869 g_clear_object (&tree_store);
3871 if (found_message) {
3872 gtk_combo_box_set_active_iter (combo, &found_iter);
3873 } else {
3874 gtk_widget_set_sensitive (GTK_WIDGET (combo), FALSE);
3877 return combo;
3881 * em_utils_reply_alternative:
3882 * @parent: (nullable): a parent #GtkWindow for the question dialog
3883 * @shell: an #EShell instance used to create #EMsgComposer
3884 * @alert_sink: an #EAlertSink to put any errors to
3885 * @message: a #CamelMimeMessage
3886 * @folder: (nullable): a #CamelFolder, or %NULL
3887 * @message_uid: (nullable): the UID of @message, or %NULL
3888 * @style: the reply style to use
3889 * @source: (nullable): source to inherit view settings from
3891 * This is similar to em_utils_reply_to_message(), except it asks user to
3892 * change some settings before sending. It calls em_utils_reply_to_message()
3893 * at the end for non-templated replies.
3895 * Since: 3.30
3897 void
3898 em_utils_reply_alternative (GtkWindow *parent,
3899 EShell *shell,
3900 EAlertSink *alert_sink,
3901 CamelMimeMessage *message,
3902 CamelFolder *folder,
3903 const gchar *message_uid,
3904 EMailReplyStyle default_style,
3905 EMailPartList *source)
3907 GtkWidget *dialog, *widget, *style_label;
3908 GtkBox *hbox, *vbox;
3909 GtkRadioButton *recip_sender, *recip_list, *recip_all;
3910 GtkLabel *sender_label, *list_label, *all_label;
3911 GtkRadioButton *style_default, *style_attach, *style_inline, *style_quote, *style_no_quote;
3912 GtkToggleButton *html_format;
3913 GtkToggleButton *bottom_posting;
3914 GtkCheckButton *apply_template;
3915 GtkComboBox *templates;
3916 GtkCheckButton *preserve_message_subject;
3917 PangoAttrList *attr_list;
3918 GSettings *settings;
3919 gchar *last_tmpl_folder_uri, *last_tmpl_message_uid, *address, *text;
3920 gboolean can_reply_list = FALSE;
3921 CamelInternetAddress *to, *cc;
3922 CamelNNTPAddress *postto = NULL;
3923 gint n_addresses;
3925 g_return_if_fail (E_IS_SHELL (shell));
3926 g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
3928 settings = e_util_ref_settings ("org.gnome.evolution.mail");
3930 dialog = gtk_dialog_new_with_buttons (_("Alternative Reply"), parent,
3931 GTK_DIALOG_DESTROY_WITH_PARENT,
3932 _("_Cancel"), GTK_RESPONSE_CANCEL,
3933 _("_Reply"), GTK_RESPONSE_OK,
3934 NULL);
3936 gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
3938 vbox = GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog)));
3939 gtk_container_set_border_width (GTK_CONTAINER (vbox), 12);
3940 gtk_box_set_spacing (vbox, 2);
3942 #define add_with_indent(x) \
3943 gtk_widget_set_margin_left (GTK_WIDGET (x), 12); \
3944 gtk_box_pack_start (vbox, GTK_WIDGET (x), FALSE, FALSE, 0);
3946 widget = gtk_label_new (_("Recipients:"));
3947 g_object_set (G_OBJECT (widget),
3948 "hexpand", FALSE,
3949 "halign", GTK_ALIGN_START,
3950 NULL);
3951 gtk_box_pack_start (vbox, widget, FALSE, FALSE, 0);
3953 attr_list = pango_attr_list_new ();
3954 pango_attr_list_insert (attr_list, pango_attr_style_new (PANGO_STYLE_ITALIC));
3956 #define add_with_label(wgt, lbl) G_STMT_START { \
3957 GtkWidget *divider_label = gtk_label_new (":"); \
3958 gtk_label_set_attributes (GTK_LABEL (lbl), attr_list); \
3959 hbox = GTK_BOX (gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6)); \
3960 gtk_box_pack_start (hbox, GTK_WIDGET (wgt), FALSE, FALSE, 0); \
3961 gtk_box_pack_start (hbox, GTK_WIDGET (divider_label), FALSE, FALSE, 0); \
3962 gtk_box_pack_start (hbox, GTK_WIDGET (lbl), FALSE, FALSE, 0); \
3963 e_binding_bind_property ( \
3964 wgt, "sensitive", \
3965 divider_label, "visible", \
3966 G_BINDING_SYNC_CREATE); \
3967 e_binding_bind_property ( \
3968 wgt, "sensitive", \
3969 lbl, "visible", \
3970 G_BINDING_SYNC_CREATE); \
3971 add_with_indent (hbox); } G_STMT_END
3973 recip_sender = GTK_RADIO_BUTTON (gtk_radio_button_new_with_mnemonic (
3974 NULL, _("Reply to _Sender")));
3975 sender_label = GTK_LABEL (gtk_label_new (""));
3976 add_with_label (recip_sender, sender_label);
3978 recip_list = GTK_RADIO_BUTTON (gtk_radio_button_new_with_mnemonic (
3979 gtk_radio_button_get_group (recip_sender), _("Reply to _List")));
3980 list_label = GTK_LABEL (gtk_label_new (""));
3981 add_with_label (recip_list, list_label);
3983 recip_all = GTK_RADIO_BUTTON (gtk_radio_button_new_with_mnemonic (
3984 gtk_radio_button_get_group (recip_sender), _("Reply to _All")));
3985 all_label = GTK_LABEL (gtk_label_new (""));
3986 add_with_label (recip_all, all_label);
3988 #undef add_with_label
3990 pango_attr_list_unref (attr_list);
3992 /* One line gap between sections */
3993 widget = gtk_label_new (" ");
3994 gtk_box_pack_start (vbox, widget, FALSE, FALSE, 0);
3996 style_label = gtk_label_new (_("Reply style:"));
3997 g_object_set (G_OBJECT (style_label),
3998 "hexpand", FALSE,
3999 "halign", GTK_ALIGN_START,
4000 NULL);
4001 gtk_box_pack_start (vbox, style_label, FALSE, FALSE, 0);
4003 style_default = GTK_RADIO_BUTTON (gtk_radio_button_new_with_mnemonic (
4004 NULL, _("_Default")));
4005 add_with_indent (style_default);
4007 style_attach = GTK_RADIO_BUTTON (gtk_radio_button_new_with_mnemonic (
4008 gtk_radio_button_get_group (style_default), _("Attach_ment")));
4009 add_with_indent (style_attach);
4011 style_inline = GTK_RADIO_BUTTON (gtk_radio_button_new_with_mnemonic (
4012 gtk_radio_button_get_group (style_default), _("Inline (_Outlook style)")));
4013 add_with_indent (style_inline);
4015 style_quote = GTK_RADIO_BUTTON (gtk_radio_button_new_with_mnemonic (
4016 gtk_radio_button_get_group (style_default), _("_Quote")));
4017 add_with_indent (style_quote);
4019 style_no_quote = GTK_RADIO_BUTTON (gtk_radio_button_new_with_mnemonic (
4020 gtk_radio_button_get_group (style_default), _("Do _Not Quote")));
4021 add_with_indent (style_no_quote);
4023 /* One line gap between sections */
4024 widget = gtk_label_new (" ");
4025 gtk_box_pack_start (vbox, widget, FALSE, FALSE, 0);
4027 html_format = GTK_TOGGLE_BUTTON (gtk_check_button_new_with_mnemonic (_("_Format message in HTML")));
4028 gtk_box_pack_start (vbox, GTK_WIDGET (html_format), FALSE, FALSE, 0);
4030 bottom_posting = GTK_TOGGLE_BUTTON (gtk_check_button_new_with_mnemonic (_("Start _typing at the bottom")));
4031 gtk_box_pack_start (vbox, GTK_WIDGET (bottom_posting), FALSE, FALSE, 0);
4033 /* One line gap between sections */
4034 widget = gtk_label_new (" ");
4035 gtk_box_pack_start (vbox, widget, FALSE, FALSE, 0);
4037 apply_template = GTK_CHECK_BUTTON (gtk_check_button_new_with_mnemonic (_("Apply t_emplate")));
4038 gtk_box_pack_start (vbox, GTK_WIDGET (apply_template), FALSE, FALSE, 0);
4040 last_tmpl_folder_uri = g_settings_get_string (settings, "alt-reply-template-folder-uri");
4041 last_tmpl_message_uid = g_settings_get_string (settings, "alt-reply-template-message-uid");
4043 templates = emcu_create_templates_combo (shell, last_tmpl_folder_uri, last_tmpl_message_uid);
4044 add_with_indent (templates);
4046 g_free (last_tmpl_folder_uri);
4047 g_free (last_tmpl_message_uid);
4049 preserve_message_subject = GTK_CHECK_BUTTON (gtk_check_button_new_with_mnemonic (_("Preserve original message S_ubject")));
4050 add_with_indent (preserve_message_subject);
4052 #undef add_with_indent
4054 gtk_widget_show_all (GTK_WIDGET (vbox));
4056 #define populate_label_with_text(lbl, txt) \
4057 g_object_set (G_OBJECT (lbl), \
4058 "ellipsize", PANGO_ELLIPSIZE_END, \
4059 "max-width-chars", 50, \
4060 "label", txt ? txt : "", \
4061 "tooltip-text", txt ? txt : "", \
4062 NULL);
4064 /* Reply to sender */
4065 to = camel_internet_address_new ();
4066 if (folder)
4067 postto = camel_nntp_address_new ();
4068 get_reply_sender (message, to, postto);
4070 if (postto && camel_address_length (CAMEL_ADDRESS (postto)) > 0) {
4071 address = camel_address_format (CAMEL_ADDRESS (postto));
4072 } else {
4073 address = camel_address_format (CAMEL_ADDRESS (to));
4076 populate_label_with_text (sender_label, address);
4078 g_clear_object (&postto);
4079 g_clear_object (&to);
4080 g_free (address);
4082 /* Reply to list */
4083 to = camel_internet_address_new ();
4085 if (!get_reply_list (message, to)) {
4086 gtk_widget_set_sensitive (GTK_WIDGET (recip_list), FALSE);
4087 } else {
4088 can_reply_list = TRUE;
4090 address = camel_address_format (CAMEL_ADDRESS (to));
4091 populate_label_with_text (list_label, address);
4092 g_free (address);
4095 g_clear_object (&to);
4097 /* Reply to all */
4098 to = camel_internet_address_new ();
4099 cc = camel_internet_address_new ();
4101 if (folder)
4102 postto = camel_nntp_address_new ();
4104 em_utils_get_reply_all (e_shell_get_registry (shell), message, to, cc, postto);
4106 if (postto && camel_address_length (CAMEL_ADDRESS (postto)) > 0) {
4107 n_addresses = camel_address_length (CAMEL_ADDRESS (postto));
4108 address = camel_address_format (CAMEL_ADDRESS (postto));
4109 } else {
4110 camel_address_cat (CAMEL_ADDRESS (to), CAMEL_ADDRESS (cc));
4111 n_addresses = camel_address_length (CAMEL_ADDRESS (to));
4112 address = camel_address_format (CAMEL_ADDRESS (to));
4115 text = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE, "one recipient", "%d recipients", n_addresses), n_addresses);
4116 populate_label_with_text (all_label, text);
4117 gtk_widget_set_tooltip_text (GTK_WIDGET (all_label), address);
4119 g_clear_object (&to);
4120 g_clear_object (&cc);
4121 g_clear_object (&postto);
4122 g_free (address);
4123 g_free (text);
4125 #undef populate_label_with_text
4127 /* Prefer reply-to-list */
4128 if (g_settings_get_boolean (settings, "composer-group-reply-to-list")) {
4129 if (can_reply_list)
4130 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (recip_list), TRUE);
4131 else if (n_addresses > 1)
4132 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (recip_all), TRUE);
4133 else
4134 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (recip_sender), TRUE);
4136 /* Prefer reply-to-all */
4137 } else {
4138 if (n_addresses > 1)
4139 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (recip_all), TRUE);
4140 else if (can_reply_list)
4141 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (recip_list), TRUE);
4142 else
4143 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (recip_sender), TRUE);
4146 switch (g_settings_get_enum (settings, "alt-reply-style")) {
4147 case E_MAIL_REPLY_STYLE_UNKNOWN:
4148 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (style_default), TRUE);
4149 break;
4150 case E_MAIL_REPLY_STYLE_QUOTED:
4151 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (style_quote), TRUE);
4152 break;
4153 case E_MAIL_REPLY_STYLE_DO_NOT_QUOTE:
4154 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (style_no_quote), TRUE);
4155 break;
4156 case E_MAIL_REPLY_STYLE_ATTACH:
4157 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (style_attach), TRUE);
4158 break;
4159 case E_MAIL_REPLY_STYLE_OUTLOOK:
4160 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (style_inline), TRUE);
4161 break;
4164 emcu_three_state_set_value (html_format, g_settings_get_enum (settings, "alt-reply-html-format"));
4165 emcu_three_state_set_value (bottom_posting, g_settings_get_enum (settings, "alt-reply-start-bottom"));
4166 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (apply_template), g_settings_get_boolean (settings, "alt-reply-template-apply"));
4167 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (preserve_message_subject), g_settings_get_boolean (settings, "alt-reply-template-preserve-subject"));
4169 if (!gtk_widget_get_sensitive (GTK_WIDGET (templates)))
4170 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (apply_template), FALSE);
4172 emcu_connect_three_state_changer (html_format);
4173 emcu_connect_three_state_changer (bottom_posting);
4175 e_binding_bind_property (
4176 apply_template, "active",
4177 templates, "sensitive",
4178 G_BINDING_SYNC_CREATE);
4180 e_binding_bind_property (
4181 apply_template, "active",
4182 preserve_message_subject, "sensitive",
4183 G_BINDING_SYNC_CREATE);
4185 /* Enable the 'Reply Style' section only if not using Template */
4186 e_binding_bind_property (
4187 apply_template, "active",
4188 style_label, "sensitive",
4189 G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN);
4191 e_binding_bind_property (
4192 apply_template, "active",
4193 style_default, "sensitive",
4194 G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN);
4196 e_binding_bind_property (
4197 apply_template, "active",
4198 style_attach, "sensitive",
4199 G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN);
4201 e_binding_bind_property (
4202 apply_template, "active",
4203 style_inline, "sensitive",
4204 G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN);
4206 e_binding_bind_property (
4207 apply_template, "active",
4208 style_quote, "sensitive",
4209 G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN);
4211 e_binding_bind_property (
4212 apply_template, "active",
4213 style_no_quote, "sensitive",
4214 G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN);
4216 /* Similarly with bottom posting, which doesn't work when using Templates */
4217 e_binding_bind_property (
4218 apply_template, "active",
4219 bottom_posting, "sensitive",
4220 G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN);
4222 if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK) {
4223 GtkTreeIter iter;
4224 AltReplyContext *context;
4225 EThreeState three_state;
4226 CamelFolder *template_folder = NULL;
4227 gchar *template_message_uid = NULL;
4229 context = g_new0 (AltReplyContext, 1);
4230 context->shell = g_object_ref (shell);
4231 context->alert_sink = g_object_ref (alert_sink);
4232 context->source_message = g_object_ref (message);
4233 context->folder = folder ? g_object_ref (folder) : NULL;
4234 context->source = source ? g_object_ref (source) : NULL;
4235 context->message_uid = g_strdup (message_uid);
4236 context->style = E_MAIL_REPLY_STYLE_UNKNOWN;
4237 context->flags = E_MAIL_REPLY_FLAG_FORCE_STYLE;
4239 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (style_quote)))
4240 context->style = E_MAIL_REPLY_STYLE_QUOTED;
4241 else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (style_no_quote)))
4242 context->style = E_MAIL_REPLY_STYLE_DO_NOT_QUOTE;
4243 else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (style_attach)))
4244 context->style = E_MAIL_REPLY_STYLE_ATTACH;
4245 else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (style_inline)))
4246 context->style = E_MAIL_REPLY_STYLE_OUTLOOK;
4247 else
4248 context->flags = context->flags & (~E_MAIL_REPLY_FLAG_FORCE_STYLE);
4250 three_state = emcu_three_state_get_value (html_format);
4251 g_settings_set_enum (settings, "alt-reply-html-format", three_state);
4253 if (three_state == E_THREE_STATE_ON)
4254 context->flags |= E_MAIL_REPLY_FLAG_FORMAT_HTML;
4255 else if (three_state == E_THREE_STATE_OFF)
4256 context->flags |= E_MAIL_REPLY_FLAG_FORMAT_PLAIN;
4258 three_state = emcu_three_state_get_value (bottom_posting);
4259 g_settings_set_enum (settings, "alt-reply-start-bottom", three_state);
4261 if (three_state == E_THREE_STATE_ON)
4262 context->flags |= E_MAIL_REPLY_FLAG_BOTTOM_POSTING;
4263 else if (three_state == E_THREE_STATE_OFF)
4264 context->flags |= E_MAIL_REPLY_FLAG_TOP_POSTING;
4266 g_settings_set_enum (settings, "alt-reply-style", context->style);
4267 g_settings_set_boolean (settings, "alt-reply-template-apply", gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (apply_template)));
4268 g_settings_set_boolean (settings, "alt-reply-template-preserve-subject", gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (preserve_message_subject)));
4270 if (gtk_combo_box_get_active_iter (templates, &iter)) {
4271 gtk_tree_model_get (gtk_combo_box_get_model (templates), &iter,
4272 E_MAIL_TEMPLATES_STORE_COLUMN_FOLDER, &template_folder,
4273 E_MAIL_TEMPLATES_STORE_COLUMN_MESSAGE_UID, &template_message_uid,
4274 -1);
4277 if (template_folder) {
4278 gchar *folder_uri;
4280 folder_uri = e_mail_folder_uri_from_folder (template_folder);
4281 g_settings_set_string (settings, "alt-reply-template-folder-uri", folder_uri ? folder_uri : "");
4282 g_free (folder_uri);
4285 g_settings_set_string (settings, "alt-reply-template-message-uid", template_message_uid ? template_message_uid : "");
4287 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (recip_sender)))
4288 context->type = E_MAIL_REPLY_TO_SENDER;
4289 else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (recip_list)))
4290 context->type = E_MAIL_REPLY_TO_LIST;
4291 else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (recip_all)))
4292 context->type = E_MAIL_REPLY_TO_ALL;
4293 else
4294 g_warn_if_reached ();
4296 if (context->style == E_MAIL_REPLY_STYLE_UNKNOWN)
4297 context->style = default_style;
4299 context->template_preserve_subject = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (preserve_message_subject));
4301 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (apply_template))) {
4302 e_mail_templates_apply (context->source_message, template_folder, template_message_uid,
4303 NULL, alt_reply_template_applied_cb, context);
4305 } else {
4306 e_msg_composer_new (context->shell, alt_reply_composer_created_cb, context);
4309 g_clear_object (&template_folder);
4310 g_free (template_message_uid);
4313 gtk_widget_destroy (dialog);
4314 g_clear_object (&settings);
4318 * em_utils_reply_to_message:
4319 * @composer: an #EMsgComposer
4320 * @message: a #CamelMimeMessage
4321 * @folder: a #CamelFolder, or %NULL
4322 * @message_uid: the UID of @message, or %NULL
4323 * @type: the type of reply to create
4324 * @style: the reply style to use
4325 * @source: source to inherit view settings from
4326 * @address: used for E_MAIL_REPLY_TO_RECIPIENT @type
4327 * @reply_flags: bit-or of #EMailReplyFlags
4329 * Creates a new composer ready to reply to @message.
4331 * @folder and @message_uid may be supplied in order to update the message
4332 * flags once it has been replied to.
4334 void
4335 em_utils_reply_to_message (EMsgComposer *composer,
4336 CamelMimeMessage *message,
4337 CamelFolder *folder,
4338 const gchar *message_uid,
4339 EMailReplyType type,
4340 EMailReplyStyle style,
4341 EMailPartList *parts_list,
4342 CamelInternetAddress *address,
4343 EMailReplyFlags reply_flags)
4345 ESourceRegistry *registry;
4346 CamelInternetAddress *to, *cc;
4347 CamelNNTPAddress *postto = NULL;
4348 EShell *shell;
4349 ESourceMailCompositionReplyStyle prefer_reply_style = E_SOURCE_MAIL_COMPOSITION_REPLY_STYLE_DEFAULT;
4350 ESource *source;
4351 EContentEditor *cnt_editor;
4352 gchar *identity_uid = NULL, *identity_name = NULL, *identity_address = NULL;
4353 guint32 flags;
4355 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
4356 g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
4358 cnt_editor = e_html_editor_get_content_editor (e_msg_composer_get_editor (composer));
4360 if ((reply_flags & (E_MAIL_REPLY_FLAG_FORMAT_PLAIN | E_MAIL_REPLY_FLAG_FORMAT_HTML)) != 0) {
4361 e_content_editor_set_html_mode (cnt_editor, (reply_flags & E_MAIL_REPLY_FLAG_FORMAT_HTML) != 0);
4364 if ((reply_flags & (E_MAIL_REPLY_FLAG_TOP_POSTING | E_MAIL_REPLY_FLAG_BOTTOM_POSTING)) != 0) {
4365 e_content_editor_set_start_bottom (cnt_editor,
4366 (reply_flags & E_MAIL_REPLY_FLAG_TOP_POSTING) != 0 ?
4367 E_THREE_STATE_OFF : E_THREE_STATE_ON);
4370 to = camel_internet_address_new ();
4371 cc = camel_internet_address_new ();
4373 shell = e_msg_composer_get_shell (composer);
4374 registry = e_shell_get_registry (shell);
4376 /* This returns a new ESource reference. */
4377 source = em_utils_check_send_account_override (shell, message, folder, &identity_name, &identity_address);
4378 if (!source)
4379 source = em_utils_guess_mail_identity_with_recipients_and_sort (
4380 registry, message, folder, message_uid, &identity_name, &identity_address, sort_sources_by_ui, shell);
4381 if (source != NULL) {
4382 identity_uid = e_source_dup_uid (source);
4383 if (!(reply_flags & E_MAIL_REPLY_FLAG_FORCE_STYLE) &&
4384 e_source_has_extension (source, E_SOURCE_EXTENSION_MAIL_COMPOSITION)) {
4385 ESourceMailComposition *extension;
4387 extension = e_source_get_extension (source, E_SOURCE_EXTENSION_MAIL_COMPOSITION);
4388 prefer_reply_style = e_source_mail_composition_get_reply_style (extension);
4391 g_object_unref (source);
4394 flags = CAMEL_MESSAGE_ANSWERED | CAMEL_MESSAGE_SEEN;
4396 if (!address && (type == E_MAIL_REPLY_TO_FROM || type == E_MAIL_REPLY_TO_SENDER) &&
4397 folder && !emcu_folder_is_inbox (folder) && em_utils_folder_is_sent (registry, folder))
4398 type = E_MAIL_REPLY_TO_ALL;
4400 switch (type) {
4401 case E_MAIL_REPLY_TO_FROM:
4402 if (folder)
4403 postto = camel_nntp_address_new ();
4405 get_reply_from (message, to, postto);
4406 break;
4407 case E_MAIL_REPLY_TO_RECIPIENT:
4408 if (folder)
4409 postto = camel_nntp_address_new ();
4411 get_reply_recipient (message, to, postto, address);
4412 break;
4413 case E_MAIL_REPLY_TO_SENDER:
4414 if (folder)
4415 postto = camel_nntp_address_new ();
4417 get_reply_sender (message, to, postto);
4418 break;
4419 case E_MAIL_REPLY_TO_LIST:
4420 flags |= CAMEL_MESSAGE_ANSWERED_ALL;
4421 if (get_reply_list (message, to))
4422 break;
4423 /* falls through */
4424 case E_MAIL_REPLY_TO_ALL:
4425 flags |= CAMEL_MESSAGE_ANSWERED_ALL;
4426 if (folder)
4427 postto = camel_nntp_address_new ();
4429 em_utils_get_reply_all (registry, message, to, cc, postto);
4430 break;
4433 reply_setup_composer (composer, message, identity_uid, identity_name, identity_address, to, cc, folder, message_uid, postto);
4434 e_msg_composer_add_message_attachments (composer, message, TRUE);
4436 if (postto)
4437 g_object_unref (postto);
4438 g_object_unref (to);
4439 g_object_unref (cc);
4441 /* If there was no send-account override */
4442 if (!identity_uid) {
4443 EComposerHeaderTable *header_table;
4444 gchar *used_identity_uid;
4446 header_table = e_msg_composer_get_header_table (composer);
4447 used_identity_uid = e_composer_header_table_dup_identity_uid (header_table, NULL, NULL);
4449 if (used_identity_uid) {
4450 source = e_source_registry_ref_source (e_shell_get_registry (shell), used_identity_uid);
4451 if (source) {
4452 if (!(reply_flags & E_MAIL_REPLY_FLAG_FORCE_STYLE) &&
4453 e_source_has_extension (source, E_SOURCE_EXTENSION_MAIL_COMPOSITION)) {
4454 ESourceMailComposition *extension;
4456 extension = e_source_get_extension (source, E_SOURCE_EXTENSION_MAIL_COMPOSITION);
4457 prefer_reply_style = e_source_mail_composition_get_reply_style (extension);
4460 g_object_unref (source);
4464 g_free (used_identity_uid);
4467 switch (prefer_reply_style) {
4468 case E_SOURCE_MAIL_COMPOSITION_REPLY_STYLE_DEFAULT:
4469 /* Do nothing, keep the passed-in reply style. */
4470 break;
4471 case E_SOURCE_MAIL_COMPOSITION_REPLY_STYLE_QUOTED:
4472 style = E_MAIL_REPLY_STYLE_QUOTED;
4473 break;
4474 case E_SOURCE_MAIL_COMPOSITION_REPLY_STYLE_DO_NOT_QUOTE:
4475 style = E_MAIL_REPLY_STYLE_DO_NOT_QUOTE;
4476 break;
4477 case E_SOURCE_MAIL_COMPOSITION_REPLY_STYLE_ATTACH:
4478 style = E_MAIL_REPLY_STYLE_ATTACH;
4479 break;
4480 case E_SOURCE_MAIL_COMPOSITION_REPLY_STYLE_OUTLOOK:
4481 style = E_MAIL_REPLY_STYLE_OUTLOOK;
4482 break;
4485 composer_set_body (composer, message, style, parts_list);
4487 if (folder != NULL) {
4488 gchar *folder_uri = NULL, *tmp_message_uid = NULL;
4490 em_utils_get_real_folder_uri_and_message_uid (folder, message_uid, &folder_uri, &tmp_message_uid);
4492 e_msg_composer_set_source_headers (
4493 composer, folder_uri, tmp_message_uid, flags);
4495 g_free (folder_uri);
4496 g_free (tmp_message_uid);
4499 /* because some reply types can change recipients after the composer is populated */
4500 em_utils_apply_send_account_override_to_composer (composer, folder);
4502 /* This is required to be done (also) at the end */
4503 if ((reply_flags & (E_MAIL_REPLY_FLAG_TOP_POSTING | E_MAIL_REPLY_FLAG_BOTTOM_POSTING)) != 0) {
4504 e_content_editor_set_start_bottom (cnt_editor,
4505 (reply_flags & E_MAIL_REPLY_FLAG_TOP_POSTING) != 0 ?
4506 E_THREE_STATE_OFF : E_THREE_STATE_ON);
4509 composer_set_no_change (composer);
4511 gtk_widget_show (GTK_WIDGET (composer));
4513 g_free (identity_uid);
4514 g_free (identity_name);
4515 g_free (identity_address);
4518 static void
4519 post_header_clicked_cb (EComposerPostHeader *header,
4520 EMailSession *session)
4522 GtkTreeSelection *selection;
4523 EMFolderSelector *selector;
4524 EMFolderTreeModel *model;
4525 EMFolderTree *folder_tree;
4526 GtkWidget *dialog;
4527 GList *list;
4528 const gchar *caption;
4530 /* FIXME Limit the folder tree to the NNTP account? */
4531 model = em_folder_tree_model_get_default ();
4533 dialog = em_folder_selector_new (
4534 /* FIXME GTK_WINDOW (composer) */ NULL, model);
4536 gtk_window_set_title (GTK_WINDOW (dialog), _("Posting destination"));
4538 selector = EM_FOLDER_SELECTOR (dialog);
4539 em_folder_selector_set_can_create (selector, TRUE);
4541 caption = _("Choose folders to post the message to.");
4542 em_folder_selector_set_caption (selector, caption);
4544 folder_tree = em_folder_selector_get_folder_tree (selector);
4546 em_folder_tree_set_excluded (
4547 folder_tree,
4548 EMFT_EXCLUDE_NOSELECT |
4549 EMFT_EXCLUDE_VIRTUAL |
4550 EMFT_EXCLUDE_VTRASH);
4552 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (folder_tree));
4553 gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
4555 list = e_composer_post_header_get_folders (header);
4556 em_folder_tree_set_selected_list (folder_tree, list, FALSE);
4557 g_list_foreach (list, (GFunc) g_free, NULL);
4558 g_list_free (list);
4560 if (gtk_dialog_run (GTK_DIALOG (dialog)) != GTK_RESPONSE_OK) {
4561 /* Prevent the header's "custom" flag from being reset,
4562 * which is what the default method will do next. */
4563 g_signal_stop_emission_by_name (header, "clicked");
4564 goto exit;
4567 list = em_folder_tree_get_selected_uris (folder_tree);
4568 e_composer_post_header_set_folders (header, list);
4569 g_list_foreach (list, (GFunc) g_free, NULL);
4570 g_list_free (list);
4572 exit:
4573 gtk_widget_destroy (dialog);
4577 * em_configure_new_composer:
4578 * @composer: a newly created #EMsgComposer
4580 * Integrates a newly created #EMsgComposer into the mail backend. The
4581 * composer can't link directly to the mail backend without introducing
4582 * circular library dependencies, so this function finishes configuring
4583 * things the #EMsgComposer instance can't do itself.
4585 void
4586 em_configure_new_composer (EMsgComposer *composer,
4587 EMailSession *session)
4589 EComposerHeaderTable *table;
4590 EComposerHeaderType header_type;
4591 EComposerHeader *header;
4593 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
4594 g_return_if_fail (E_IS_MAIL_SESSION (session));
4596 header_type = E_COMPOSER_HEADER_POST_TO;
4597 table = e_msg_composer_get_header_table (composer);
4598 header = e_composer_header_table_get_header (table, header_type);
4600 g_signal_connect (
4601 composer, "presend",
4602 G_CALLBACK (composer_presend_check_recipients), session);
4604 g_signal_connect (
4605 composer, "presend",
4606 G_CALLBACK (composer_presend_check_identity), session);
4608 g_signal_connect (
4609 composer, "presend",
4610 G_CALLBACK (composer_presend_check_plugins), session);
4612 g_signal_connect (
4613 composer, "presend",
4614 G_CALLBACK (composer_presend_check_subject), session);
4616 g_signal_connect (
4617 composer, "presend",
4618 G_CALLBACK (composer_presend_check_unwanted_html), session);
4620 g_signal_connect (
4621 composer, "send",
4622 G_CALLBACK (em_utils_composer_send_cb), session);
4624 g_signal_connect (
4625 composer, "save-to-drafts",
4626 G_CALLBACK (em_utils_composer_save_to_drafts_cb), session);
4628 g_signal_connect (
4629 composer, "save-to-outbox",
4630 G_CALLBACK (em_utils_composer_save_to_outbox_cb), session);
4632 g_signal_connect (
4633 composer, "print",
4634 G_CALLBACK (em_utils_composer_print_cb), session);
4636 /* Handle "Post To:" button clicks, which displays a folder tree
4637 * widget. The composer doesn't know about folder tree widgets,
4638 * so it can't handle this itself.
4640 * Note: This is a G_SIGNAL_RUN_LAST signal, which allows us to
4641 * stop the signal emission if the user cancels or closes
4642 * the folder selector dialog. See the handler function. */
4643 g_signal_connect (
4644 header, "clicked",
4645 G_CALLBACK (post_header_clicked_cb), session);
4648 /* free returned pointer with g_object_unref(), if not NULL */
4649 ESource *
4650 em_utils_check_send_account_override (EShell *shell,
4651 CamelMimeMessage *message,
4652 CamelFolder *folder,
4653 gchar **out_alias_name,
4654 gchar **out_alias_address)
4656 EMailBackend *mail_backend;
4657 EMailSendAccountOverride *account_override;
4658 CamelInternetAddress *to = NULL, *cc = NULL, *bcc = NULL;
4659 gchar *folder_uri = NULL, *account_uid, *alias_name = NULL, *alias_address = NULL;
4660 ESource *account_source = NULL;
4661 ESourceRegistry *source_registry;
4663 g_return_val_if_fail (E_IS_SHELL (shell), NULL);
4665 if (!message && !folder)
4666 return NULL;
4668 if (message) {
4669 to = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_TO);
4670 cc = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_CC);
4671 bcc = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_BCC);
4674 mail_backend = E_MAIL_BACKEND (e_shell_get_backend_by_name (shell, "mail"));
4675 g_return_val_if_fail (mail_backend != NULL, NULL);
4677 if (folder)
4678 folder_uri = e_mail_folder_uri_from_folder (folder);
4680 source_registry = e_shell_get_registry (shell);
4681 account_override = e_mail_backend_get_send_account_override (mail_backend);
4682 account_uid = e_mail_send_account_override_get_account_uid (account_override, folder_uri, to, cc, bcc, &alias_name, &alias_address);
4684 while (account_uid) {
4685 account_source = e_source_registry_ref_source (source_registry, account_uid);
4686 if (account_source)
4687 break;
4689 /* stored send account override settings contain a reference
4690 * to a dropped account, thus cleanup it now */
4691 e_mail_send_account_override_remove_for_account_uid (account_override, account_uid, alias_name, alias_address);
4693 g_free (account_uid);
4694 g_free (alias_name);
4695 g_free (alias_address);
4697 alias_name = NULL;
4698 alias_address = NULL;
4700 account_uid = e_mail_send_account_override_get_account_uid (account_override, folder_uri, to, cc, bcc, &alias_name, &alias_address);
4703 if (out_alias_name)
4704 *out_alias_name = alias_name;
4705 else
4706 g_free (alias_name);
4708 if (out_alias_address)
4709 *out_alias_address = alias_address;
4710 else
4711 g_free (alias_address);
4713 g_free (folder_uri);
4714 g_free (account_uid);
4716 return account_source;
4719 void
4720 em_utils_apply_send_account_override_to_composer (EMsgComposer *composer,
4721 CamelFolder *folder)
4723 CamelMimeMessage *message;
4724 EComposerHeaderTable *header_table;
4725 EShell *shell;
4726 ESource *source;
4727 gchar *alias_name = NULL, *alias_address = NULL;
4729 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
4731 shell = e_msg_composer_get_shell (composer);
4732 message = em_utils_get_composer_recipients_as_message (composer);
4733 source = em_utils_check_send_account_override (shell, message, folder, &alias_name, &alias_address);
4734 g_clear_object (&message);
4736 if (!source)
4737 return;
4739 header_table = e_msg_composer_get_header_table (composer);
4740 e_composer_header_table_set_identity_uid (header_table, e_source_get_uid (source), alias_name, alias_address);
4742 g_object_unref (source);
4743 g_free (alias_name);
4744 g_free (alias_address);