Bug 794204 - Links in mailto: body not detected when composing in HTML
[evolution.git] / src / composer / e-msg-composer.c
blobdd29675b459a3cc3d92f17382f1ab5bd94783281
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * This program is free software; you can redistribute it and/or modify it
4 * under the terms of the GNU Lesser General Public License as published by
5 * the Free Software Foundation.
7 * This program is distributed in the hope that it will be useful, but
8 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
9 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
10 * for more details.
12 * You should have received a copy of the GNU Lesser General Public License
13 * along with this program; if not, see <http://www.gnu.org/licenses/>.
16 * Authors:
17 * Ettore Perazzoli (ettore@ximian.com)
18 * Jeffrey Stedfast (fejj@ximian.com)
19 * Miguel de Icaza (miguel@ximian.com)
20 * Radek Doulik (rodo@ximian.com)
22 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
26 #include "evolution-config.h"
28 #include <stdlib.h>
29 #include <string.h>
30 #include <sys/types.h>
31 #include <sys/stat.h>
32 #include <sys/time.h>
33 #include <unistd.h>
34 #include <ctype.h>
35 #include <fcntl.h>
36 #include <enchant.h>
38 #include "e-composer-from-header.h"
39 #include "e-composer-text-header.h"
40 #include "e-composer-private.h"
42 #include <em-format/e-mail-part.h>
43 #include <em-format/e-mail-parser.h>
44 #include <em-format/e-mail-formatter-quote.h>
46 #include <shell/e-shell.h>
48 #include <libemail-engine/libemail-engine.h>
50 typedef struct _AsyncContext AsyncContext;
52 struct _AsyncContext {
53 EActivity *activity;
55 CamelMimeMessage *message;
56 CamelDataWrapper *top_level_part;
57 CamelDataWrapper *text_plain_part;
59 ESource *source;
60 CamelSession *session;
61 CamelInternetAddress *from;
63 CamelTransferEncoding plain_encoding;
64 GtkPrintOperationAction print_action;
66 GPtrArray *recipients;
68 guint skip_content : 1;
69 guint need_thread : 1;
70 guint pgp_sign : 1;
71 guint pgp_encrypt : 1;
72 guint smime_sign : 1;
73 guint smime_encrypt : 1;
76 /* Flags for building a message. */
77 typedef enum {
78 COMPOSER_FLAG_HTML_CONTENT = 1 << 0,
79 COMPOSER_FLAG_SAVE_OBJECT_DATA = 1 << 1,
80 COMPOSER_FLAG_PRIORITIZE_MESSAGE = 1 << 2,
81 COMPOSER_FLAG_REQUEST_READ_RECEIPT = 1 << 3,
82 COMPOSER_FLAG_PGP_SIGN = 1 << 4,
83 COMPOSER_FLAG_PGP_ENCRYPT = 1 << 5,
84 COMPOSER_FLAG_SMIME_SIGN = 1 << 6,
85 COMPOSER_FLAG_SMIME_ENCRYPT = 1 << 7,
86 COMPOSER_FLAG_HTML_MODE = 1 << 8,
87 COMPOSER_FLAG_SAVE_DRAFT = 1 << 9
88 } ComposerFlags;
90 enum {
91 PROP_0,
92 PROP_BUSY,
93 PROP_EDITOR,
94 PROP_FOCUS_TRACKER,
95 PROP_SHELL,
96 PROP_IS_REPLY_OR_FORWARD
99 enum {
100 PRESEND,
101 SEND,
102 SAVE_TO_DRAFTS,
103 SAVE_TO_OUTBOX,
104 PRINT,
105 BEFORE_DESTROY,
106 LAST_SIGNAL
109 static GtkTargetEntry drag_dest_targets[] = {
110 { (gchar *) "text/uri-list", 0, E_DND_TARGET_TYPE_TEXT_URI_LIST },
111 { (gchar *) "_NETSCAPE_URL", 0, E_DND_TARGET_TYPE_MOZILLA_URL },
112 { (gchar *) "text/html", 0, E_DND_TARGET_TYPE_TEXT_HTML },
113 { (gchar *) "UTF8_STRING", 0, E_DND_TARGET_TYPE_UTF8_STRING },
114 { (gchar *) "text/plain", 0, E_DND_TARGET_TYPE_TEXT_PLAIN },
115 { (gchar *) "STRING", 0, E_DND_TARGET_TYPE_STRING },
116 { (gchar *) "text/plain;charset=utf-8", 0, E_DND_TARGET_TYPE_TEXT_PLAIN_UTF8 },
119 static guint signals[LAST_SIGNAL];
121 /* used by e_msg_composer_add_message_attachments () */
122 static void add_attachments_from_multipart (EMsgComposer *composer,
123 CamelMultipart *multipart,
124 gboolean just_inlines,
125 gint depth);
127 /* used by e_msg_composer_setup_with_message () */
128 static void handle_multipart (EMsgComposer *composer,
129 CamelMultipart *multipart,
130 gboolean keep_signature,
131 GCancellable *cancellable,
132 gint depth);
133 static void handle_multipart_alternative (EMsgComposer *composer,
134 CamelMultipart *multipart,
135 gboolean keep_signature,
136 GCancellable *cancellable,
137 gint depth);
138 static void handle_multipart_encrypted (EMsgComposer *composer,
139 CamelMimePart *multipart,
140 gboolean keep_signature,
141 GCancellable *cancellable,
142 gint depth);
143 static void handle_multipart_signed (EMsgComposer *composer,
144 CamelMultipart *multipart,
145 gboolean keep_signature,
146 GCancellable *cancellable,
147 gint depth);
149 G_DEFINE_TYPE_WITH_CODE (
150 EMsgComposer,
151 e_msg_composer,
152 GTK_TYPE_WINDOW,
153 G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE, NULL))
155 static void
156 async_context_free (AsyncContext *context)
158 g_clear_object (&context->activity);
159 g_clear_object (&context->message);
160 g_clear_object (&context->top_level_part);
161 g_clear_object (&context->text_plain_part);
162 g_clear_object (&context->source);
163 g_clear_object (&context->session);
164 g_clear_object (&context->from);
166 if (context->recipients != NULL)
167 g_ptr_array_free (context->recipients, TRUE);
169 g_slice_free (AsyncContext, context);
173 * emcu_part_to_html:
174 * @part:
176 * Converts a mime part's contents into html text. If @credits is given,
177 * then it will be used as an attribution string, and the
178 * content will be cited. Otherwise no citation or attribution
179 * will be performed.
181 * Return Value: The part in displayable html format.
183 static gchar *
184 emcu_part_to_html (EMsgComposer *composer,
185 CamelMimePart *part,
186 gssize *len,
187 gboolean keep_signature,
188 GCancellable *cancellable)
190 CamelSession *session;
191 GOutputStream *stream;
192 gchar *text;
193 EMailParser *parser;
194 EMailFormatter *formatter;
195 EMailPartList *part_list;
196 GString *part_id;
197 EShell *shell;
198 GtkWindow *window;
199 gsize n_bytes_written = 0;
200 GQueue queue = G_QUEUE_INIT;
202 shell = e_shell_get_default ();
203 window = e_shell_get_active_window (shell);
205 session = e_msg_composer_ref_session (composer);
207 part_list = e_mail_part_list_new (NULL, NULL, NULL);
209 part_id = g_string_sized_new (0);
210 parser = e_mail_parser_new (session);
211 e_mail_parser_parse_part (
212 parser, part, part_id, cancellable, &queue);
213 while (!g_queue_is_empty (&queue)) {
214 EMailPart *mail_part = g_queue_pop_head (&queue);
216 if (!e_mail_part_get_is_attachment (mail_part) &&
217 !mail_part->is_hidden)
218 e_mail_part_list_add_part (part_list, mail_part);
220 g_object_unref (mail_part);
222 g_string_free (part_id, TRUE);
223 g_object_unref (parser);
224 g_object_unref (session);
226 if (e_mail_part_list_is_empty (part_list)) {
227 g_object_unref (part_list);
228 return NULL;
231 stream = g_memory_output_stream_new_resizable ();
233 formatter = e_mail_formatter_quote_new (
234 NULL, keep_signature ? E_MAIL_FORMATTER_QUOTE_FLAG_KEEP_SIG : 0);
235 e_mail_formatter_update_style (
236 formatter,
237 gtk_widget_get_state_flags (GTK_WIDGET (window)));
239 e_mail_formatter_format_sync (
240 formatter, part_list, stream,
241 0, E_MAIL_FORMATTER_MODE_PRINTING, cancellable);
243 g_object_unref (formatter);
244 g_object_unref (part_list);
246 g_output_stream_write_all (stream, "", 1, &n_bytes_written, NULL, NULL);
248 g_output_stream_close (stream, NULL, NULL);
250 text = g_memory_output_stream_steal_data (
251 G_MEMORY_OUTPUT_STREAM (stream));
253 if (len != NULL)
254 *len = strlen (text);
256 g_object_unref (stream);
258 return text;
261 static EDestination **
262 destination_list_to_vector_sized (GList *list,
263 gint n)
265 EDestination **destv;
266 gint i = 0;
268 if (n == -1)
269 n = g_list_length (list);
271 if (n == 0)
272 return NULL;
274 destv = g_new (EDestination *, n + 1);
275 while (list != NULL && i < n) {
276 destv[i] = E_DESTINATION (list->data);
277 list->data = NULL;
278 i++;
279 list = g_list_next (list);
281 destv[i] = NULL;
283 return destv;
286 static EDestination **
287 destination_list_to_vector (GList *list)
289 return destination_list_to_vector_sized (list, -1);
292 #define LINE_LEN 72
294 static gboolean
295 text_requires_quoted_printable (const gchar *text,
296 gsize len)
298 const gchar *p;
299 gsize pos;
301 if (!text)
302 return FALSE;
304 if (len == -1)
305 len = strlen (text);
307 if (len >= 5 && strncmp (text, "From ", 5) == 0)
308 return TRUE;
310 for (p = text, pos = 0; pos + 6 <= len; pos++, p++) {
311 if (*p == '\n' && strncmp (p + 1, "From ", 5) == 0)
312 return TRUE;
315 return FALSE;
318 static gboolean
319 best_encoding (GByteArray *buf,
320 const gchar *charset,
321 CamelTransferEncoding *encoding)
323 gchar *in, *out, outbuf[256], *ch;
324 gsize inlen, outlen;
325 gint status, count = 0;
326 iconv_t cd;
328 if (!charset)
329 return FALSE;
331 cd = camel_iconv_open (charset, "utf-8");
332 if (cd == (iconv_t) -1)
333 return FALSE;
335 in = (gchar *) buf->data;
336 inlen = buf->len;
337 do {
338 out = outbuf;
339 outlen = sizeof (outbuf);
340 status = camel_iconv (cd, (const gchar **) &in, &inlen, &out, &outlen);
341 for (ch = out - 1; ch >= outbuf; ch--) {
342 if ((guchar) *ch > 127)
343 count++;
345 } while (status == (gsize) -1 && errno == E2BIG);
346 camel_iconv_close (cd);
348 if (status == (gsize) -1 || status > 0)
349 return FALSE;
351 if ((count == 0) && (buf->len < LINE_LEN) &&
352 !text_requires_quoted_printable (
353 (const gchar *) buf->data, buf->len))
354 *encoding = CAMEL_TRANSFER_ENCODING_7BIT;
355 else if (count <= buf->len * 0.17)
356 *encoding = CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE;
357 else
358 *encoding = CAMEL_TRANSFER_ENCODING_BASE64;
360 return TRUE;
363 static gchar *
364 best_charset (GByteArray *buf,
365 const gchar *default_charset,
366 CamelTransferEncoding *encoding)
368 const gchar *charset;
370 /* First try US-ASCII */
371 if (best_encoding (buf, "US-ASCII", encoding) &&
372 *encoding == CAMEL_TRANSFER_ENCODING_7BIT)
373 return NULL;
375 /* Next try the user-specified charset for this message */
376 if (best_encoding (buf, default_charset, encoding))
377 return g_strdup (default_charset);
379 /* Now try the user's default charset from the mail config */
380 charset = e_composer_get_default_charset ();
381 if (best_encoding (buf, charset, encoding))
382 return g_strdup (charset);
384 /* Try to find something that will work */
385 charset = camel_charset_best (
386 (const gchar *) buf->data, buf->len);
387 if (charset == NULL) {
388 *encoding = CAMEL_TRANSFER_ENCODING_7BIT;
389 return NULL;
392 if (!best_encoding (buf, charset, encoding))
393 *encoding = CAMEL_TRANSFER_ENCODING_BASE64;
395 return g_strdup (charset);
398 /* These functions builds a CamelMimeMessage for the message that the user has
399 * composed in 'composer'.
402 static void
403 set_recipients_from_destv (CamelMimeMessage *msg,
404 EDestination **to_destv,
405 EDestination **cc_destv,
406 EDestination **bcc_destv,
407 gboolean redirect)
409 CamelInternetAddress *to_addr;
410 CamelInternetAddress *cc_addr;
411 CamelInternetAddress *bcc_addr;
412 CamelInternetAddress *target;
413 const gchar *text_addr, *header;
414 gboolean seen_hidden_list = FALSE;
415 gint i;
417 to_addr = camel_internet_address_new ();
418 cc_addr = camel_internet_address_new ();
419 bcc_addr = camel_internet_address_new ();
421 for (i = 0; to_destv != NULL && to_destv[i] != NULL; ++i) {
422 text_addr = e_destination_get_address (to_destv[i]);
424 if (text_addr && *text_addr) {
425 target = to_addr;
426 if (e_destination_is_evolution_list (to_destv[i])
427 && !e_destination_list_show_addresses (to_destv[i])) {
428 target = bcc_addr;
429 seen_hidden_list = TRUE;
432 if (camel_address_decode (CAMEL_ADDRESS (target), text_addr) <= 0)
433 camel_internet_address_add (target, "", text_addr);
437 for (i = 0; cc_destv != NULL && cc_destv[i] != NULL; ++i) {
438 text_addr = e_destination_get_address (cc_destv[i]);
439 if (text_addr && *text_addr) {
440 target = cc_addr;
441 if (e_destination_is_evolution_list (cc_destv[i])
442 && !e_destination_list_show_addresses (cc_destv[i])) {
443 target = bcc_addr;
444 seen_hidden_list = TRUE;
447 if (camel_address_decode (CAMEL_ADDRESS (target), text_addr) <= 0)
448 camel_internet_address_add (target, "", text_addr);
452 for (i = 0; bcc_destv != NULL && bcc_destv[i] != NULL; ++i) {
453 text_addr = e_destination_get_address (bcc_destv[i]);
454 if (text_addr && *text_addr) {
455 if (camel_address_decode (CAMEL_ADDRESS (bcc_addr), text_addr) <= 0)
456 camel_internet_address_add (bcc_addr, "", text_addr);
460 if (redirect)
461 header = CAMEL_RECIPIENT_TYPE_RESENT_TO;
462 else
463 header = CAMEL_RECIPIENT_TYPE_TO;
465 if (camel_address_length (CAMEL_ADDRESS (to_addr)) > 0) {
466 camel_mime_message_set_recipients (msg, header, to_addr);
467 } else if (seen_hidden_list) {
468 camel_medium_set_header (
469 CAMEL_MEDIUM (msg), header, "Undisclosed-Recipient:;");
472 header = redirect ? CAMEL_RECIPIENT_TYPE_RESENT_CC : CAMEL_RECIPIENT_TYPE_CC;
473 if (camel_address_length (CAMEL_ADDRESS (cc_addr)) > 0) {
474 camel_mime_message_set_recipients (msg, header, cc_addr);
477 header = redirect ? CAMEL_RECIPIENT_TYPE_RESENT_BCC : CAMEL_RECIPIENT_TYPE_BCC;
478 if (camel_address_length (CAMEL_ADDRESS (bcc_addr)) > 0) {
479 camel_mime_message_set_recipients (msg, header, bcc_addr);
482 g_object_unref (to_addr);
483 g_object_unref (cc_addr);
484 g_object_unref (bcc_addr);
487 static void
488 build_message_headers (EMsgComposer *composer,
489 CamelMimeMessage *message,
490 gboolean redirect)
492 EComposerHeaderTable *table;
493 EComposerHeader *header;
494 ESource *source;
495 gchar *alias_name = NULL, *alias_address = NULL, *uid;
496 const gchar *subject;
497 const gchar *reply_to;
499 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
500 g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
502 table = e_msg_composer_get_header_table (composer);
504 uid = e_composer_header_table_dup_identity_uid (table, &alias_name, &alias_address);
505 if (uid)
506 source = e_composer_header_table_ref_source (table, uid);
507 else
508 source = NULL;
510 /* Subject: */
511 subject = e_composer_header_table_get_subject (table);
512 camel_mime_message_set_subject (message, subject);
514 if (source != NULL) {
515 CamelMedium *medium;
516 CamelInternetAddress *addr;
517 ESourceMailSubmission *ms;
518 EComposerHeader *composer_header;
519 const gchar *extension_name;
520 const gchar *header_name;
521 const gchar *name = NULL, *address = NULL;
522 const gchar *transport_uid;
523 const gchar *sent_folder = NULL;
524 gboolean is_from_override = FALSE;
526 composer_header = e_composer_header_table_get_header (table, E_COMPOSER_HEADER_FROM);
527 if (e_composer_from_header_get_override_visible (E_COMPOSER_FROM_HEADER (composer_header))) {
528 name = e_composer_header_table_get_from_name (table);
529 address = e_composer_header_table_get_from_address (table);
531 if (address && !*address) {
532 name = NULL;
533 address = NULL;
536 is_from_override = address != NULL;
539 if (!address) {
540 if (alias_name)
541 name = alias_name;
542 if (alias_address)
543 address = alias_address;
546 if (!is_from_override && (!address || !name || !*name)) {
547 ESourceMailIdentity *mail_identity;
549 mail_identity = e_source_get_extension (source, E_SOURCE_EXTENSION_MAIL_IDENTITY);
551 if (!name || !*name)
552 name = e_source_mail_identity_get_name (mail_identity);
554 if (!address)
555 address = e_source_mail_identity_get_address (mail_identity);
558 extension_name = E_SOURCE_EXTENSION_MAIL_SUBMISSION;
559 ms = e_source_get_extension (source, extension_name);
561 if (e_source_mail_submission_get_use_sent_folder (ms))
562 sent_folder = e_source_mail_submission_get_sent_folder (ms);
563 transport_uid = e_source_mail_submission_get_transport_uid (ms);
565 medium = CAMEL_MEDIUM (message);
567 /* From: / Resent-From: */
568 addr = camel_internet_address_new ();
569 camel_internet_address_add (addr, name, address);
570 if (redirect) {
571 gchar *value;
573 value = camel_address_encode (CAMEL_ADDRESS (addr));
574 camel_medium_set_header (medium, "Resent-From", value);
575 g_free (value);
576 } else {
577 camel_mime_message_set_from (message, addr);
579 g_object_unref (addr);
581 /* X-Evolution-Identity */
582 header_name = "X-Evolution-Identity";
583 camel_medium_set_header (medium, header_name, uid);
585 /* X-Evolution-Fcc */
586 header_name = "X-Evolution-Fcc";
587 camel_medium_set_header (medium, header_name, sent_folder);
589 /* X-Evolution-Transport */
590 header_name = "X-Evolution-Transport";
591 camel_medium_set_header (medium, header_name, transport_uid);
593 g_object_unref (source);
596 /* Reply-To: */
597 reply_to = e_composer_header_table_get_reply_to (table);
598 if (reply_to != NULL && *reply_to != '\0') {
599 CamelInternetAddress *addr;
601 addr = camel_internet_address_new ();
603 if (camel_address_unformat (CAMEL_ADDRESS (addr), reply_to) > 0)
604 camel_mime_message_set_reply_to (message, addr);
606 g_object_unref (addr);
609 /* To:, Cc:, Bcc: */
610 header = e_composer_header_table_get_header (
611 table, E_COMPOSER_HEADER_TO);
612 if (e_composer_header_get_visible (header)) {
613 EDestination **to, **cc, **bcc;
615 to = e_composer_header_table_get_destinations_to (table);
616 cc = e_composer_header_table_get_destinations_cc (table);
617 bcc = e_composer_header_table_get_destinations_bcc (table);
619 set_recipients_from_destv (message, to, cc, bcc, redirect);
621 e_destination_freev (to);
622 e_destination_freev (cc);
623 e_destination_freev (bcc);
626 /* Date: */
627 camel_mime_message_set_date (message, CAMEL_MESSAGE_DATE_CURRENT, 0);
629 /* X-Evolution-PostTo: */
630 header = e_composer_header_table_get_header (
631 table, E_COMPOSER_HEADER_POST_TO);
632 if (e_composer_header_get_visible (header)) {
633 CamelMedium *medium;
634 const gchar *name = "X-Evolution-PostTo";
635 GList *list, *iter;
637 medium = CAMEL_MEDIUM (message);
638 camel_medium_remove_header (medium, name);
640 list = e_composer_header_table_get_post_to (table);
641 for (iter = list; iter != NULL; iter = iter->next) {
642 gchar *folder = iter->data;
643 camel_medium_add_header (medium, name, folder);
644 g_free (folder);
646 g_list_free (list);
649 g_free (uid);
650 g_free (alias_name);
651 g_free (alias_address);
654 static CamelCipherHash
655 account_hash_algo_to_camel_hash (const gchar *hash_algo)
657 CamelCipherHash res = CAMEL_CIPHER_HASH_DEFAULT;
659 if (hash_algo && *hash_algo) {
660 if (g_ascii_strcasecmp (hash_algo, "sha1") == 0)
661 res = CAMEL_CIPHER_HASH_SHA1;
662 else if (g_ascii_strcasecmp (hash_algo, "sha256") == 0)
663 res = CAMEL_CIPHER_HASH_SHA256;
664 else if (g_ascii_strcasecmp (hash_algo, "sha384") == 0)
665 res = CAMEL_CIPHER_HASH_SHA384;
666 else if (g_ascii_strcasecmp (hash_algo, "sha512") == 0)
667 res = CAMEL_CIPHER_HASH_SHA512;
670 return res;
673 static void
674 composer_add_charset_filter (CamelStream *stream,
675 const gchar *charset)
677 CamelMimeFilter *filter;
679 filter = camel_mime_filter_charset_new ("UTF-8", charset);
680 camel_stream_filter_add (CAMEL_STREAM_FILTER (stream), filter);
681 g_object_unref (filter);
684 static void
685 composer_add_quoted_printable_filter (CamelStream *stream)
687 CamelMimeFilter *filter;
689 filter = camel_mime_filter_basic_new (CAMEL_MIME_FILTER_BASIC_QP_ENC);
690 camel_stream_filter_add (CAMEL_STREAM_FILTER (stream), filter);
691 g_object_unref (filter);
694 /* Helper for composer_build_message_thread() */
695 static gboolean
696 composer_build_message_pgp (AsyncContext *context,
697 GCancellable *cancellable,
698 GError **error)
700 ESourceOpenPGP *extension;
701 CamelCipherContext *cipher;
702 CamelDataWrapper *content;
703 CamelMimePart *mime_part;
704 const gchar *extension_name;
705 const gchar *pgp_key_id;
706 const gchar *signing_algorithm;
707 gboolean always_trust;
708 gboolean encrypt_to_self;
709 gboolean prefer_inline;
711 /* Return silently if we're not signing or encrypting with PGP. */
712 if (!context->pgp_sign && !context->pgp_encrypt)
713 return TRUE;
715 extension_name = E_SOURCE_EXTENSION_OPENPGP;
716 extension = e_source_get_extension (context->source, extension_name);
718 always_trust = e_source_openpgp_get_always_trust (extension);
719 encrypt_to_self = e_source_openpgp_get_encrypt_to_self (extension);
720 prefer_inline = e_source_openpgp_get_prefer_inline (extension);
721 pgp_key_id = e_source_openpgp_get_key_id (extension);
722 signing_algorithm = e_source_openpgp_get_signing_algorithm (extension);
724 mime_part = camel_mime_part_new ();
726 camel_medium_set_content (
727 CAMEL_MEDIUM (mime_part),
728 context->top_level_part);
730 if (context->top_level_part == context->text_plain_part)
731 camel_mime_part_set_encoding (
732 mime_part, context->plain_encoding);
734 g_object_unref (context->top_level_part);
735 context->top_level_part = NULL;
737 if (pgp_key_id == NULL || *pgp_key_id == '\0')
738 camel_internet_address_get (
739 context->from, 0, NULL, &pgp_key_id);
741 if (context->pgp_sign) {
742 CamelMimePart *npart;
743 gboolean success;
745 npart = camel_mime_part_new ();
747 cipher = camel_gpg_context_new (context->session);
748 camel_gpg_context_set_always_trust (CAMEL_GPG_CONTEXT (cipher), always_trust);
749 camel_gpg_context_set_prefer_inline (CAMEL_GPG_CONTEXT (cipher), prefer_inline);
751 success = camel_cipher_context_sign_sync (
752 cipher, pgp_key_id,
753 account_hash_algo_to_camel_hash (signing_algorithm),
754 mime_part, npart, cancellable, error);
756 g_object_unref (cipher);
758 g_object_unref (mime_part);
760 if (!success) {
761 g_object_unref (npart);
762 return FALSE;
765 mime_part = npart;
768 if (context->pgp_encrypt) {
769 CamelMimePart *npart;
770 gboolean success;
772 npart = camel_mime_part_new ();
774 /* Check to see if we should encrypt to self.
775 * NB: Gets removed immediately after use. */
776 if (encrypt_to_self && pgp_key_id != NULL)
777 g_ptr_array_add (
778 context->recipients,
779 g_strdup (pgp_key_id));
781 cipher = camel_gpg_context_new (context->session);
782 camel_gpg_context_set_always_trust (CAMEL_GPG_CONTEXT (cipher), always_trust);
783 camel_gpg_context_set_prefer_inline (CAMEL_GPG_CONTEXT (cipher), prefer_inline);
785 success = camel_cipher_context_encrypt_sync (
786 cipher, pgp_key_id, context->recipients,
787 mime_part, npart, cancellable, error);
789 g_object_unref (cipher);
791 if (encrypt_to_self && pgp_key_id != NULL)
792 g_ptr_array_set_size (
793 context->recipients,
794 context->recipients->len - 1);
796 g_object_unref (mime_part);
798 if (!success) {
799 g_object_unref (npart);
800 return FALSE;
803 mime_part = npart;
806 content = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
807 context->top_level_part = g_object_ref (content);
809 g_object_unref (mime_part);
811 return TRUE;
814 #ifdef ENABLE_SMIME
815 static gboolean
816 composer_build_message_smime (AsyncContext *context,
817 GCancellable *cancellable,
818 GError **error)
820 ESourceSMIME *extension;
821 CamelCipherContext *cipher;
822 CamelMimePart *mime_part;
823 const gchar *extension_name;
824 const gchar *signing_algorithm;
825 const gchar *signing_certificate;
826 const gchar *encryption_certificate;
827 gboolean encrypt_to_self;
828 gboolean have_signing_certificate;
829 gboolean have_encryption_certificate;
831 /* Return silently if we're not signing or encrypting with S/MIME. */
832 if (!context->smime_sign && !context->smime_encrypt)
833 return TRUE;
835 extension_name = E_SOURCE_EXTENSION_SMIME;
836 extension = e_source_get_extension (context->source, extension_name);
838 encrypt_to_self =
839 e_source_smime_get_encrypt_to_self (extension);
841 signing_algorithm =
842 e_source_smime_get_signing_algorithm (extension);
844 signing_certificate =
845 e_source_smime_get_signing_certificate (extension);
847 encryption_certificate =
848 e_source_smime_get_encryption_certificate (extension);
850 have_signing_certificate =
851 (signing_certificate != NULL) &&
852 (*signing_certificate != '\0');
854 have_encryption_certificate =
855 (encryption_certificate != NULL) &&
856 (*encryption_certificate != '\0');
858 if (context->smime_sign && !have_signing_certificate) {
859 g_set_error (
860 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
861 _("Cannot sign outgoing message: "
862 "No signing certificate set for "
863 "this account"));
864 return FALSE;
867 if (context->smime_encrypt && !have_encryption_certificate) {
868 g_set_error (
869 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
870 _("Cannot encrypt outgoing message: "
871 "No encryption certificate set for "
872 "this account"));
873 return FALSE;
876 mime_part = camel_mime_part_new ();
878 camel_medium_set_content (
879 CAMEL_MEDIUM (mime_part),
880 context->top_level_part);
882 if (context->top_level_part == context->text_plain_part)
883 camel_mime_part_set_encoding (
884 mime_part, context->plain_encoding);
886 g_object_unref (context->top_level_part);
887 context->top_level_part = NULL;
889 if (context->smime_sign) {
890 CamelMimePart *npart;
891 gboolean success;
893 npart = camel_mime_part_new ();
895 cipher = camel_smime_context_new (context->session);
897 /* if we're also encrypting, envelope-sign rather than clear-sign */
898 if (context->smime_encrypt) {
899 camel_smime_context_set_sign_mode (
900 (CamelSMIMEContext *) cipher,
901 CAMEL_SMIME_SIGN_ENVELOPED);
902 camel_smime_context_set_encrypt_key (
903 (CamelSMIMEContext *) cipher,
904 TRUE, encryption_certificate);
905 } else if (have_encryption_certificate) {
906 camel_smime_context_set_encrypt_key (
907 (CamelSMIMEContext *) cipher,
908 TRUE, encryption_certificate);
911 success = camel_cipher_context_sign_sync (
912 cipher, signing_certificate,
913 account_hash_algo_to_camel_hash (signing_algorithm),
914 mime_part, npart, cancellable, error);
916 g_object_unref (cipher);
918 g_object_unref (mime_part);
920 if (!success) {
921 g_object_unref (npart);
922 return FALSE;
925 mime_part = npart;
928 if (context->smime_encrypt) {
929 gboolean success;
931 /* Check to see if we should encrypt to self.
932 * NB: Gets removed immediately after use. */
933 if (encrypt_to_self)
934 g_ptr_array_add (
935 context->recipients, g_strdup (
936 encryption_certificate));
938 cipher = camel_smime_context_new (context->session);
939 camel_smime_context_set_encrypt_key (
940 (CamelSMIMEContext *) cipher, TRUE,
941 encryption_certificate);
943 success = camel_cipher_context_encrypt_sync (
944 cipher, NULL,
945 context->recipients, mime_part,
946 CAMEL_MIME_PART (context->message),
947 cancellable, error);
949 g_object_unref (cipher);
951 if (!success)
952 return FALSE;
954 if (encrypt_to_self)
955 g_ptr_array_set_size (
956 context->recipients,
957 context->recipients->len - 1);
960 /* we replaced the message directly, we don't want to do reparenting foo */
961 if (context->smime_encrypt) {
962 context->skip_content = TRUE;
963 } else {
964 CamelDataWrapper *content;
966 content = camel_medium_get_content (
967 CAMEL_MEDIUM (mime_part));
968 context->top_level_part = g_object_ref (content);
971 g_object_unref (mime_part);
973 return TRUE;
975 #endif
977 static void
978 composer_build_message_thread (GSimpleAsyncResult *simple,
979 EMsgComposer *composer,
980 GCancellable *cancellable)
982 AsyncContext *context;
983 GError *error = NULL;
985 context = g_simple_async_result_get_op_res_gpointer (simple);
987 /* Setup working recipient list if we're encrypting. */
988 if (context->pgp_encrypt || context->smime_encrypt) {
989 gint ii, jj;
991 const gchar *types[] = {
992 CAMEL_RECIPIENT_TYPE_TO,
993 CAMEL_RECIPIENT_TYPE_CC,
994 CAMEL_RECIPIENT_TYPE_BCC
997 context->recipients = g_ptr_array_new_with_free_func (
998 (GDestroyNotify) g_free);
999 for (ii = 0; ii < G_N_ELEMENTS (types); ii++) {
1000 CamelInternetAddress *addr;
1001 const gchar *address;
1003 addr = camel_mime_message_get_recipients (
1004 context->message, types[ii]);
1005 for (jj = 0; camel_internet_address_get (addr, jj, NULL, &address); jj++)
1006 g_ptr_array_add (
1007 context->recipients,
1008 g_strdup (address));
1012 if (!composer_build_message_pgp (context, cancellable, &error)) {
1013 g_simple_async_result_take_error (simple, error);
1014 return;
1017 #if defined (ENABLE_SMIME)
1018 if (!composer_build_message_smime (context, cancellable, &error)) {
1019 g_simple_async_result_take_error (simple, error);
1020 return;
1022 #endif /* ENABLE_SMIME */
1025 static void
1026 composer_add_evolution_composer_mode_header (CamelMedium *medium,
1027 EMsgComposer *composer)
1029 gboolean html_mode;
1030 EHTMLEditor *editor;
1031 EContentEditor *cnt_editor;
1033 editor = e_msg_composer_get_editor (composer);
1034 cnt_editor = e_html_editor_get_content_editor (editor);
1035 html_mode = e_content_editor_get_html_mode (cnt_editor);
1037 camel_medium_add_header (
1038 medium,
1039 "X-Evolution-Composer-Mode",
1040 html_mode ? "text/html" : "text/plain");
1043 static void
1044 composer_add_evolution_format_header (CamelMedium *medium,
1045 ComposerFlags flags)
1047 GString *string;
1049 string = g_string_sized_new (128);
1051 if ((flags & COMPOSER_FLAG_HTML_CONTENT) || (flags & COMPOSER_FLAG_SAVE_DRAFT))
1052 g_string_append (string, "text/html");
1053 else
1054 g_string_append (string, "text/plain");
1056 if (flags & COMPOSER_FLAG_PGP_SIGN)
1057 g_string_append (string, ", pgp-sign");
1059 if (flags & COMPOSER_FLAG_PGP_ENCRYPT)
1060 g_string_append (string, ", pgp-encrypt");
1062 if (flags & COMPOSER_FLAG_SMIME_SIGN)
1063 g_string_append (string, ", smime-sign");
1065 if (flags & COMPOSER_FLAG_SMIME_ENCRYPT)
1066 g_string_append (string, ", smime-encrypt");
1068 camel_medium_add_header (
1069 medium, "X-Evolution-Format", string->str);
1071 g_string_free (string, TRUE);
1074 static void
1075 composer_build_message (EMsgComposer *composer,
1076 ComposerFlags flags,
1077 gint io_priority,
1078 GCancellable *cancellable,
1079 GAsyncReadyCallback callback,
1080 gpointer user_data)
1082 EMsgComposerPrivate *priv;
1083 GSimpleAsyncResult *simple;
1084 AsyncContext *context;
1085 EAttachmentView *view;
1086 EAttachmentStore *store;
1087 EComposerHeaderTable *table;
1088 CamelDataWrapper *html;
1089 ESourceMailIdentity *mi;
1090 const gchar *extension_name;
1091 const gchar *iconv_charset = NULL;
1092 const gchar *organization;
1093 gchar *identity_uid;
1094 CamelMultipart *body = NULL;
1095 CamelContentType *type;
1096 CamelStream *stream;
1097 CamelStream *mem_stream;
1098 CamelMimePart *part;
1099 GByteArray *data;
1100 ESource *source;
1101 gchar *charset, *message_uid;
1102 const gchar *from_domain;
1103 gint i;
1105 priv = composer->priv;
1106 table = e_msg_composer_get_header_table (composer);
1107 view = e_msg_composer_get_attachment_view (composer);
1108 store = e_attachment_view_get_store (view);
1110 identity_uid = e_composer_header_table_dup_identity_uid (table, NULL, NULL);
1111 if (identity_uid) {
1112 source = e_composer_header_table_ref_source (table, identity_uid);
1113 g_free (identity_uid);
1115 g_warn_if_fail (source != NULL);
1116 } else {
1117 source = NULL;
1120 /* Do all the non-blocking work here, and defer
1121 * any blocking operations to a separate thread. */
1123 context = g_slice_new0 (AsyncContext);
1124 context->source = source; /* takes the reference */
1125 context->session = e_msg_composer_ref_session (composer);
1126 context->from = e_msg_composer_get_from (composer);
1128 if (!(flags & COMPOSER_FLAG_SAVE_DRAFT)) {
1129 if (flags & COMPOSER_FLAG_PGP_SIGN)
1130 context->pgp_sign = TRUE;
1132 if (flags & COMPOSER_FLAG_PGP_ENCRYPT)
1133 context->pgp_encrypt = TRUE;
1135 if (flags & COMPOSER_FLAG_SMIME_SIGN)
1136 context->smime_sign = TRUE;
1138 if (flags & COMPOSER_FLAG_SMIME_ENCRYPT)
1139 context->smime_encrypt = TRUE;
1142 context->need_thread =
1143 context->pgp_sign || context->pgp_encrypt ||
1144 context->smime_sign || context->smime_encrypt;
1146 simple = g_simple_async_result_new (
1147 G_OBJECT (composer), callback,
1148 user_data, composer_build_message);
1150 g_simple_async_result_set_check_cancellable (simple, cancellable);
1152 g_simple_async_result_set_op_res_gpointer (
1153 simple, context, (GDestroyNotify) async_context_free);
1155 /* If this is a redirected message, just tweak the headers. */
1156 if (priv->redirect) {
1157 context->skip_content = TRUE;
1158 context->message = g_object_ref (priv->redirect);
1159 build_message_headers (composer, context->message, TRUE);
1160 g_simple_async_result_complete (simple);
1161 g_object_unref (simple);
1162 return;
1165 context->message = camel_mime_message_new ();
1167 if (context->from && camel_internet_address_get (context->from, 0, NULL, &from_domain)) {
1168 const gchar *at = strchr (from_domain, '@');
1169 if (at)
1170 from_domain = at + 1;
1171 else
1172 from_domain = NULL;
1173 } else {
1174 from_domain = NULL;
1177 if (!from_domain || !*from_domain)
1178 from_domain = "localhost";
1180 message_uid = camel_header_msgid_generate (from_domain);
1182 /* Explicitly generate a Message-ID header here so it's
1183 * consistent for all outbound streams (SMTP, Fcc, etc). */
1184 camel_mime_message_set_message_id (context->message, message_uid);
1185 g_free (message_uid);
1187 build_message_headers (composer, context->message, FALSE);
1188 for (i = 0; i < priv->extra_hdr_names->len; i++) {
1189 camel_medium_add_header (
1190 CAMEL_MEDIUM (context->message),
1191 priv->extra_hdr_names->pdata[i],
1192 priv->extra_hdr_values->pdata[i]);
1195 if (source) {
1196 extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY;
1197 mi = e_source_get_extension (source, extension_name);
1198 organization = e_source_mail_identity_get_organization (mi);
1200 /* Disposition-Notification-To */
1201 if (flags & COMPOSER_FLAG_REQUEST_READ_RECEIPT) {
1202 EComposerHeader *header;
1203 const gchar *mdn_address;
1205 header = e_composer_header_table_get_header (table, E_COMPOSER_HEADER_REPLY_TO);
1206 mdn_address = e_composer_text_header_get_text (E_COMPOSER_TEXT_HEADER (header));
1208 if (!mdn_address || !*mdn_address) {
1209 header = e_composer_header_table_get_header (table, E_COMPOSER_HEADER_FROM);
1210 mdn_address = e_composer_from_header_get_address (E_COMPOSER_FROM_HEADER (header));
1213 if (!mdn_address || !*mdn_address)
1214 mdn_address = e_source_mail_identity_get_reply_to (mi);
1215 if (mdn_address == NULL)
1216 mdn_address = e_source_mail_identity_get_address (mi);
1217 if (mdn_address != NULL)
1218 camel_medium_add_header (
1219 CAMEL_MEDIUM (context->message),
1220 "Disposition-Notification-To", mdn_address);
1223 /* Organization */
1224 if (organization != NULL && *organization != '\0') {
1225 gchar *encoded_organization;
1227 encoded_organization = camel_header_encode_string (
1228 (const guchar *) organization);
1229 camel_medium_set_header (
1230 CAMEL_MEDIUM (context->message),
1231 "Organization", encoded_organization);
1232 g_free (encoded_organization);
1236 /* X-Priority */
1237 if (flags & COMPOSER_FLAG_PRIORITIZE_MESSAGE)
1238 camel_medium_add_header (
1239 CAMEL_MEDIUM (context->message),
1240 "X-Priority", "1");
1242 /* Build the text/plain part. */
1244 if (priv->mime_body) {
1245 if (text_requires_quoted_printable (priv->mime_body, -1)) {
1246 context->plain_encoding =
1247 CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE;
1248 } else {
1249 context->plain_encoding = CAMEL_TRANSFER_ENCODING_7BIT;
1250 for (i = 0; priv->mime_body[i]; i++) {
1251 if ((guchar) priv->mime_body[i] > 127) {
1252 context->plain_encoding =
1253 CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE;
1254 break;
1259 data = g_byte_array_new ();
1260 g_byte_array_append (
1261 data, (const guint8 *) priv->mime_body,
1262 strlen (priv->mime_body));
1263 type = camel_content_type_decode (priv->mime_type);
1265 } else {
1266 gchar *text;
1267 EHTMLEditor *editor;
1268 EContentEditor *cnt_editor;
1270 editor = e_msg_composer_get_editor (composer);
1271 cnt_editor = e_html_editor_get_content_editor (editor);
1272 data = g_byte_array_new ();
1274 text = e_content_editor_get_content (
1275 cnt_editor,
1276 E_CONTENT_EDITOR_GET_TEXT_PLAIN |
1277 E_CONTENT_EDITOR_GET_PROCESSED,
1278 NULL, NULL);
1280 if (!text) {
1281 g_warning ("%s: Failed to retrieve text/plain processed content", G_STRFUNC);
1282 text = g_strdup ("");
1285 g_byte_array_append (data, (guint8 *) text, strlen (text));
1286 g_free (text);
1288 type = camel_content_type_new ("text", "plain");
1289 charset = best_charset (
1290 data, priv->charset, &context->plain_encoding);
1291 if (charset != NULL) {
1292 camel_content_type_set_param (type, "charset", charset);
1293 iconv_charset = camel_iconv_charset_name (charset);
1294 g_free (charset);
1298 mem_stream = camel_stream_mem_new_with_byte_array (data);
1299 stream = camel_stream_filter_new (mem_stream);
1300 g_object_unref (mem_stream);
1302 /* Convert the stream to the appropriate charset. */
1303 if (iconv_charset && g_ascii_strcasecmp (iconv_charset, "UTF-8") != 0)
1304 composer_add_charset_filter (stream, iconv_charset);
1306 /* Encode the stream to quoted-printable if necessary. */
1307 if (context->plain_encoding == CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE)
1308 composer_add_quoted_printable_filter (stream);
1310 /* Construct the content object. This does not block since
1311 * we're constructing the data wrapper from a memory stream. */
1312 context->top_level_part = camel_data_wrapper_new ();
1313 camel_data_wrapper_construct_from_stream_sync (
1314 context->top_level_part, stream, NULL, NULL);
1315 g_object_unref (stream);
1317 context->text_plain_part = g_object_ref (context->top_level_part);
1319 /* Avoid re-encoding the data when adding it to a MIME part. */
1320 if (context->plain_encoding == CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE)
1321 camel_data_wrapper_set_encoding (context->top_level_part, context->plain_encoding);
1323 camel_data_wrapper_set_mime_type_field (
1324 context->top_level_part, type);
1326 camel_content_type_unref (type);
1328 /* Build the text/html part, and wrap it and the text/plain part
1329 * in a multipart/alternative part. Additionally, if there are
1330 * inline images then wrap the multipart/alternative part along
1331 * with the images in a multipart/related part.
1333 * So the structure of all this will be:
1335 * multipart/related
1336 * multipart/alternative
1337 * text/plain
1338 * text/html
1339 * image/<<whatever>>
1340 * image/<<whatever>>
1341 * ...
1344 if ((flags & COMPOSER_FLAG_HTML_CONTENT) != 0 ||
1345 (flags & COMPOSER_FLAG_SAVE_DRAFT) != 0) {
1346 gchar *text;
1347 gsize length;
1348 gboolean pre_encode;
1349 EHTMLEditor *editor;
1350 EContentEditor *cnt_editor;
1351 GSList *inline_images_parts = NULL, *link;
1353 editor = e_msg_composer_get_editor (composer);
1354 cnt_editor = e_html_editor_get_content_editor (editor);
1356 data = g_byte_array_new ();
1357 if ((flags & COMPOSER_FLAG_SAVE_DRAFT) != 0) {
1358 /* X-Evolution-Format */
1359 composer_add_evolution_format_header (
1360 CAMEL_MEDIUM (context->message), flags);
1362 /* X-Evolution-Composer-Mode */
1363 composer_add_evolution_composer_mode_header (
1364 CAMEL_MEDIUM (context->message), composer);
1366 text = e_content_editor_get_content (
1367 cnt_editor,
1368 E_CONTENT_EDITOR_GET_TEXT_HTML |
1369 E_CONTENT_EDITOR_GET_INLINE_IMAGES,
1370 from_domain, &inline_images_parts);
1372 if (!text) {
1373 g_warning ("%s: Failed to retrieve draft content", G_STRFUNC);
1374 text = g_strdup ("");
1376 } else {
1377 text = e_content_editor_get_content (
1378 cnt_editor,
1379 E_CONTENT_EDITOR_GET_TEXT_HTML |
1380 E_CONTENT_EDITOR_GET_PROCESSED |
1381 E_CONTENT_EDITOR_GET_INLINE_IMAGES,
1382 from_domain, &inline_images_parts);
1384 if (!text) {
1385 g_warning ("%s: Failed to retrieve HTML processed content", G_STRFUNC);
1386 text = g_strdup ("");
1390 length = strlen (text);
1391 g_byte_array_append (data, (guint8 *) text, (guint) length);
1392 pre_encode = text_requires_quoted_printable (text, length);
1393 g_free (text);
1395 mem_stream = camel_stream_mem_new_with_byte_array (data);
1396 stream = camel_stream_filter_new (mem_stream);
1397 g_object_unref (mem_stream);
1399 if (pre_encode)
1400 composer_add_quoted_printable_filter (stream);
1402 /* Construct the content object. This does not block since
1403 * we're constructing the data wrapper from a memory stream. */
1404 html = camel_data_wrapper_new ();
1405 camel_data_wrapper_construct_from_stream_sync (
1406 html, stream, NULL, NULL);
1407 g_object_unref (stream);
1409 camel_data_wrapper_set_mime_type (
1410 html, "text/html; charset=utf-8");
1412 /* Avoid re-encoding the data when adding it to a MIME part. */
1413 if (pre_encode)
1414 camel_data_wrapper_set_encoding (html, CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE);
1416 /* Build the multipart/alternative */
1417 body = camel_multipart_new ();
1418 camel_data_wrapper_set_mime_type (
1419 CAMEL_DATA_WRAPPER (body), "multipart/alternative");
1420 camel_multipart_set_boundary (body, NULL);
1422 /* Add the text/plain part. */
1423 part = camel_mime_part_new ();
1424 camel_medium_set_content (
1425 CAMEL_MEDIUM (part), context->top_level_part);
1426 camel_mime_part_set_encoding (part, context->plain_encoding);
1427 camel_multipart_add_part (body, part);
1428 g_object_unref (part);
1430 /* Add the text/html part. */
1431 part = camel_mime_part_new ();
1432 camel_medium_set_content (CAMEL_MEDIUM (part), html);
1433 camel_mime_part_set_encoding (
1434 part, CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE);
1435 camel_multipart_add_part (body, part);
1436 g_object_unref (part);
1438 g_object_unref (context->top_level_part);
1439 g_object_unref (html);
1441 /* If there are inlined images, construct a multipart/related
1442 * containing the multipart/alternative and the images. */
1443 if (inline_images_parts) {
1444 CamelMultipart *html_with_images;
1446 html_with_images = camel_multipart_new ();
1447 camel_data_wrapper_set_mime_type (
1448 CAMEL_DATA_WRAPPER (html_with_images),
1449 "multipart/related; "
1450 "type=\"multipart/alternative\"");
1451 camel_multipart_set_boundary (html_with_images, NULL);
1453 part = camel_mime_part_new ();
1454 camel_medium_set_content (
1455 CAMEL_MEDIUM (part),
1456 CAMEL_DATA_WRAPPER (body));
1457 camel_multipart_add_part (html_with_images, part);
1458 g_object_unref (part);
1460 g_object_unref (body);
1462 for (link = inline_images_parts; link; link = g_slist_next (link)) {
1463 CamelMimePart *part = link->data;
1465 camel_multipart_add_part (html_with_images, part);
1468 context->top_level_part =
1469 CAMEL_DATA_WRAPPER (html_with_images);
1470 } else {
1471 context->top_level_part =
1472 CAMEL_DATA_WRAPPER (body);
1474 g_slist_free_full (inline_images_parts, g_object_unref);
1477 /* If there are attachments, wrap what we've built so far
1478 * along with the attachments in a multipart/mixed part. */
1479 if (e_attachment_store_get_num_attachments (store) > 0) {
1480 CamelMultipart *multipart = camel_multipart_new ();
1482 /* Generate a random boundary. */
1483 camel_multipart_set_boundary (multipart, NULL);
1485 part = camel_mime_part_new ();
1486 camel_medium_set_content (
1487 CAMEL_MEDIUM (part),
1488 context->top_level_part);
1489 if (context->top_level_part == context->text_plain_part)
1490 camel_mime_part_set_encoding (
1491 part, context->plain_encoding);
1492 camel_multipart_add_part (multipart, part);
1493 g_object_unref (part);
1495 e_attachment_store_add_to_multipart (
1496 store, multipart, priv->charset);
1498 g_object_unref (context->top_level_part);
1499 context->top_level_part = CAMEL_DATA_WRAPPER (multipart);
1502 /* Run any blocking operations in a separate thread. */
1503 if (context->need_thread)
1504 g_simple_async_result_run_in_thread (
1505 simple, (GSimpleAsyncThreadFunc)
1506 composer_build_message_thread,
1507 io_priority, cancellable);
1508 else
1509 g_simple_async_result_complete (simple);
1511 g_object_unref (simple);
1514 static CamelMimeMessage *
1515 composer_build_message_finish (EMsgComposer *composer,
1516 GAsyncResult *result,
1517 GError **error)
1519 GSimpleAsyncResult *simple;
1520 AsyncContext *context;
1522 g_return_val_if_fail (
1523 g_simple_async_result_is_valid (
1524 result, G_OBJECT (composer), composer_build_message), NULL);
1526 simple = G_SIMPLE_ASYNC_RESULT (result);
1527 context = g_simple_async_result_get_op_res_gpointer (simple);
1529 if (g_simple_async_result_propagate_error (simple, error))
1530 return NULL;
1532 /* Finalize some details before returning. */
1534 if (!context->skip_content) {
1535 if (context->top_level_part != context->text_plain_part &&
1536 CAMEL_IS_MIME_PART (context->top_level_part)) {
1537 CamelDataWrapper *content;
1538 CamelMedium *imedium, *omedium;
1539 const CamelNameValueArray *headers;
1541 imedium = CAMEL_MEDIUM (context->top_level_part);
1542 omedium = CAMEL_MEDIUM (context->message);
1544 content = camel_medium_get_content (imedium);
1545 camel_medium_set_content (omedium, content);
1546 camel_data_wrapper_set_encoding (CAMEL_DATA_WRAPPER (omedium), camel_data_wrapper_get_encoding (CAMEL_DATA_WRAPPER (imedium)));
1548 headers = camel_medium_get_headers (imedium);
1549 if (headers) {
1550 gint ii, length;
1551 length = camel_name_value_array_get_length (headers);
1553 for (ii = 0; ii < length; ii++) {
1554 const gchar *header_name = NULL;
1555 const gchar *header_value = NULL;
1557 if (camel_name_value_array_get (headers, ii, &header_name, &header_value))
1558 camel_medium_set_header (omedium, header_name, header_value);
1561 } else {
1562 camel_medium_set_content (
1563 CAMEL_MEDIUM (context->message),
1564 context->top_level_part);
1568 if (context->top_level_part == context->text_plain_part) {
1569 camel_mime_part_set_encoding (
1570 CAMEL_MIME_PART (context->message),
1571 context->plain_encoding);
1574 return g_object_ref (context->message);
1577 /* Signatures */
1579 static void
1580 set_editor_text (EMsgComposer *composer,
1581 const gchar *text,
1582 gboolean is_html,
1583 gboolean set_signature)
1585 EHTMLEditor *editor;
1586 EContentEditor *cnt_editor;
1588 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
1589 g_return_if_fail (text != NULL);
1591 editor = e_msg_composer_get_editor (composer);
1592 cnt_editor = e_html_editor_get_content_editor (editor);
1594 if (is_html)
1595 e_content_editor_insert_content (
1596 cnt_editor,
1597 text,
1598 E_CONTENT_EDITOR_INSERT_TEXT_HTML |
1599 E_CONTENT_EDITOR_INSERT_REPLACE_ALL);
1600 else
1601 e_content_editor_insert_content (
1602 cnt_editor,
1603 text,
1604 E_CONTENT_EDITOR_INSERT_TEXT_PLAIN |
1605 E_CONTENT_EDITOR_INSERT_REPLACE_ALL);
1607 if (set_signature)
1608 e_composer_update_signature (composer);
1611 /* Miscellaneous callbacks. */
1613 static void
1614 attachment_store_changed_cb (EMsgComposer *composer)
1616 EHTMLEditor *editor;
1618 /* Mark the editor as changed so it prompts about unsaved
1619 * changes on close. */
1620 editor = e_msg_composer_get_editor (composer);
1621 if (editor) {
1622 EContentEditor *cnt_editor;
1624 cnt_editor = e_html_editor_get_content_editor (editor);
1625 e_content_editor_set_changed (cnt_editor, TRUE);
1629 static void
1630 msg_composer_subject_changed_cb (EMsgComposer *composer)
1632 EComposerHeaderTable *table;
1633 const gchar *subject;
1635 table = e_msg_composer_get_header_table (composer);
1636 subject = e_composer_header_table_get_subject (table);
1638 if (subject == NULL || *subject == '\0')
1639 subject = _("Compose Message");
1641 gtk_window_set_title (GTK_WINDOW (composer), subject);
1644 static void
1645 msg_composer_mail_identity_changed_cb (EMsgComposer *composer)
1647 EMailSignatureComboBox *combo_box;
1648 ESourceMailComposition *mc;
1649 ESourceOpenPGP *pgp;
1650 ESourceSMIME *smime;
1651 EComposerHeaderTable *table;
1652 EContentEditor *cnt_editor;
1653 GtkToggleAction *action;
1654 ESource *source;
1655 gboolean active;
1656 gboolean can_sign;
1657 gboolean pgp_sign;
1658 gboolean pgp_encrypt;
1659 gboolean smime_sign;
1660 gboolean smime_encrypt;
1661 gboolean composer_realized;
1662 gboolean was_disable_signature, unset_signature = FALSE;
1663 const gchar *extension_name;
1664 const gchar *active_signature_id;
1665 gchar *uid, *alias_name = NULL, *alias_address = NULL, *pgp_keyid, *smime_cert;
1667 cnt_editor = e_html_editor_get_content_editor (e_msg_composer_get_editor (composer));
1668 table = e_msg_composer_get_header_table (composer);
1669 uid = e_composer_header_table_dup_identity_uid (table, &alias_name, &alias_address);
1671 /* Silently return if no identity is selected. */
1672 if (!uid) {
1673 e_content_editor_set_start_bottom (cnt_editor, E_THREE_STATE_INCONSISTENT);
1674 e_content_editor_set_top_signature (cnt_editor, E_THREE_STATE_INCONSISTENT);
1676 g_free (alias_name);
1677 g_free (alias_address);
1678 return;
1681 source = e_composer_header_table_ref_source (table, uid);
1682 g_return_if_fail (source != NULL);
1684 extension_name = E_SOURCE_EXTENSION_MAIL_COMPOSITION;
1685 mc = e_source_get_extension (source, extension_name);
1687 e_content_editor_set_start_bottom (cnt_editor,
1688 e_source_mail_composition_get_start_bottom (mc));
1689 e_content_editor_set_top_signature (cnt_editor,
1690 e_source_mail_composition_get_top_signature (mc));
1692 extension_name = E_SOURCE_EXTENSION_OPENPGP;
1693 pgp = e_source_get_extension (source, extension_name);
1694 pgp_keyid = e_source_openpgp_dup_key_id (pgp);
1695 pgp_sign = pgp_keyid && *pgp_keyid && e_source_openpgp_get_sign_by_default (pgp);
1696 pgp_encrypt = pgp_keyid && *pgp_keyid && e_source_openpgp_get_encrypt_by_default (pgp);
1697 g_free (pgp_keyid);
1699 extension_name = E_SOURCE_EXTENSION_SMIME;
1700 smime = e_source_get_extension (source, extension_name);
1701 smime_cert = e_source_smime_dup_signing_certificate (smime);
1702 smime_sign = smime_cert && *smime_cert && e_source_smime_get_sign_by_default (smime);
1703 g_free (smime_cert);
1704 smime_cert = e_source_smime_dup_encryption_certificate (smime);
1705 smime_encrypt = smime_cert && *smime_cert && e_source_smime_get_encrypt_by_default (smime);
1706 g_free (smime_cert);
1708 can_sign =
1709 (composer->priv->mime_type == NULL) ||
1710 e_source_mail_composition_get_sign_imip (mc) ||
1711 (g_ascii_strncasecmp (
1712 composer->priv->mime_type,
1713 "text/calendar", 13) != 0);
1715 /* Preserve options only if the composer was realized, otherwise an account
1716 change according to current folder or similar reasons can cause the options
1717 to be set, when the default account has it set, but the other not. */
1718 composer_realized = gtk_widget_get_realized (GTK_WIDGET (composer));
1720 action = GTK_TOGGLE_ACTION (ACTION (PGP_SIGN));
1721 active = composer_realized && gtk_toggle_action_get_active (action);
1722 active |= (can_sign && pgp_sign);
1723 gtk_toggle_action_set_active (action, active);
1725 action = GTK_TOGGLE_ACTION (ACTION (PGP_ENCRYPT));
1726 active = composer_realized && gtk_toggle_action_get_active (action);
1727 active |= pgp_encrypt;
1728 gtk_toggle_action_set_active (action, active);
1730 action = GTK_TOGGLE_ACTION (ACTION (SMIME_SIGN));
1731 active = composer_realized && gtk_toggle_action_get_active (action);
1732 active |= (can_sign && smime_sign);
1733 gtk_toggle_action_set_active (action, active);
1735 action = GTK_TOGGLE_ACTION (ACTION (SMIME_ENCRYPT));
1736 active = composer_realized && gtk_toggle_action_get_active (action);
1737 active |= smime_encrypt;
1738 gtk_toggle_action_set_active (action, active);
1740 was_disable_signature = composer->priv->disable_signature;
1742 if (e_msg_composer_get_is_reply_or_forward (composer)) {
1743 GSettings *settings;
1745 settings = e_util_ref_settings ("org.gnome.evolution.mail");
1746 unset_signature = g_settings_get_boolean (settings, "composer-signature-in-new-only");
1747 g_object_unref (settings);
1750 combo_box = e_composer_header_table_get_signature_combo_box (table);
1752 if (unset_signature)
1753 composer->priv->disable_signature = TRUE;
1755 e_mail_signature_combo_box_set_identity (combo_box, uid, alias_name, alias_address);
1757 if (unset_signature)
1758 gtk_combo_box_set_active_id (GTK_COMBO_BOX (combo_box), "none");
1760 composer->priv->disable_signature = was_disable_signature;
1762 g_object_unref (source);
1763 g_free (uid);
1765 active_signature_id = gtk_combo_box_get_active_id (GTK_COMBO_BOX (combo_box));
1766 if (unset_signature || g_strcmp0 (active_signature_id, E_MAIL_SIGNATURE_AUTOGENERATED_UID) == 0)
1767 e_composer_update_signature (composer);
1769 g_free (alias_name);
1770 g_free (alias_address);
1773 static void
1774 msg_composer_paste_clipboard_targets_cb (GtkClipboard *clipboard,
1775 GdkAtom *targets,
1776 gint n_targets,
1777 EMsgComposer *composer)
1779 EHTMLEditor *editor;
1780 EContentEditor *cnt_editor;
1782 if (targets == NULL || n_targets < 0)
1783 return;
1785 editor = e_msg_composer_get_editor (composer);
1786 cnt_editor = e_html_editor_get_content_editor (editor);
1788 if (!e_content_editor_get_html_mode (cnt_editor) &&
1789 gtk_targets_include_image (targets, n_targets, TRUE)) {
1790 e_composer_paste_image (composer, clipboard);
1791 return;
1794 if (gtk_targets_include_uri (targets, n_targets)) {
1795 e_composer_paste_uris (composer, clipboard);
1796 return;
1799 /* Order is important here to ensure common use cases are
1800 * handled correctly. See GNOME bug #603715 for details. */
1801 if (gtk_targets_include_text (targets, n_targets) ||
1802 e_targets_include_html (targets, n_targets)) {
1803 if (composer->priv->last_signal_was_paste_primary) {
1804 e_content_editor_paste_primary (cnt_editor);
1805 } else
1806 e_content_editor_paste (cnt_editor);
1807 return;
1810 if (composer->priv->last_signal_was_paste_primary) {
1811 e_content_editor_paste_primary (cnt_editor);
1812 } else
1813 e_content_editor_paste (cnt_editor);
1816 static gboolean
1817 msg_composer_paste_primary_clipboard_cb (EContentEditor *cnt_editor,
1818 EMsgComposer *composer)
1820 GtkClipboard *clipboard;
1821 GdkAtom *targets = NULL;
1822 gint n_targets;
1824 clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
1826 composer->priv->last_signal_was_paste_primary = TRUE;
1828 if (gtk_clipboard_wait_for_targets (clipboard, &targets, &n_targets)) {
1829 msg_composer_paste_clipboard_targets_cb (clipboard, targets, n_targets, composer);
1830 g_free (targets);
1833 return TRUE;
1836 static gboolean
1837 msg_composer_paste_clipboard_cb (EContentEditor *cnt_editor,
1838 EMsgComposer *composer)
1840 GtkClipboard *clipboard;
1841 GdkAtom *targets = NULL;
1842 gint n_targets;
1844 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1846 composer->priv->last_signal_was_paste_primary = FALSE;
1848 if (gtk_clipboard_wait_for_targets (clipboard, &targets, &n_targets)) {
1849 msg_composer_paste_clipboard_targets_cb (clipboard, targets, n_targets, composer);
1850 g_free (targets);
1853 return TRUE;
1856 static void
1857 msg_composer_drag_data_received_cb (GtkWidget *widget,
1858 GdkDragContext *context,
1859 gint x,
1860 gint y,
1861 GtkSelectionData *selection,
1862 guint info,
1863 guint time,
1864 EMsgComposer *composer)
1866 EHTMLEditor *editor;
1867 EContentEditor *cnt_editor;
1868 gboolean html_mode, is_move;
1870 editor = e_msg_composer_get_editor (composer);
1871 cnt_editor = e_html_editor_get_content_editor (editor);
1872 html_mode = e_content_editor_get_html_mode (cnt_editor);
1874 g_signal_handler_disconnect (cnt_editor, composer->priv->drag_data_received_handler_id);
1875 composer->priv->drag_data_received_handler_id = 0;
1877 is_move = gdk_drag_context_get_selected_action (context) == GDK_ACTION_MOVE;
1879 /* HTML mode has a few special cases for drops... */
1880 /* If we're receiving URIs and -all- the URIs point to
1881 * image files, we want the image(s) to be inserted in
1882 * the message body. */
1883 if (html_mode &&
1884 (e_composer_selection_is_image_uris (composer, selection) ||
1885 e_composer_selection_is_base64_uris (composer, selection))) {
1886 const guchar *data;
1887 gint length;
1888 gint list_len, len;
1889 gchar *uri;
1891 data = gtk_selection_data_get_data (selection);
1892 length = gtk_selection_data_get_length (selection);
1894 if (!data || length < 0) {
1895 gtk_drag_finish (context, FALSE, FALSE, time);
1896 return;
1899 e_content_editor_move_caret_on_coordinates (cnt_editor, x, y, FALSE);
1901 list_len = length;
1902 do {
1903 uri = e_util_next_uri_from_uri_list ((guchar **) &data, &len, &list_len);
1904 e_content_editor_insert_image (cnt_editor, uri);
1905 g_free (uri);
1906 } while (list_len);
1908 gtk_drag_finish (context, TRUE, is_move, time);
1909 } else {
1910 EAttachmentView *attachment_view =
1911 e_msg_composer_get_attachment_view (composer);
1912 /* Forward the data to the attachment view. Note that calling
1913 * e_attachment_view_drag_data_received() will not work because
1914 * that function only handles the case where all the other drag
1915 * handlers have failed. */
1916 e_attachment_paned_drag_data_received (
1917 E_ATTACHMENT_PANED (attachment_view),
1918 context, x, y, selection, info, time);
1922 static gboolean
1923 msg_composer_drag_drop_cb (GtkWidget *widget,
1924 GdkDragContext *context,
1925 gint x,
1926 gint y,
1927 guint time,
1928 EMsgComposer *composer)
1930 GdkAtom target = gtk_drag_dest_find_target (widget, context, NULL);
1932 if (target == GDK_NONE) {
1933 gdk_drag_status (context, 0, time);
1934 } else {
1935 composer->priv->drag_data_received_handler_id = g_signal_connect (
1936 E_CONTENT_EDITOR (widget), "drag-data-received",
1937 G_CALLBACK (msg_composer_drag_data_received_cb), composer);
1939 gtk_drag_get_data (widget, context, target, time);
1941 return TRUE;
1944 return FALSE;
1947 static void
1948 msg_composer_drop_handled_cb (EContentEditor *cnt_editor,
1949 EMsgComposer *composer)
1951 if (composer->priv->drag_data_received_handler_id != 0) {
1952 g_signal_handler_disconnect (cnt_editor, composer->priv->drag_data_received_handler_id);
1953 composer->priv->drag_data_received_handler_id = 0;
1957 static void
1958 msg_composer_drag_begin_cb (GtkWidget *widget,
1959 GdkDragContext *context,
1960 EMsgComposer *composer)
1962 if (composer->priv->drag_data_received_handler_id != 0) {
1963 g_signal_handler_disconnect (E_CONTENT_EDITOR( widget), composer->priv->drag_data_received_handler_id);
1964 composer->priv->drag_data_received_handler_id = 0;
1968 static void
1969 msg_composer_notify_header_cb (EMsgComposer *composer)
1971 EContentEditor *cnt_editor;
1972 EHTMLEditor *editor;
1974 editor = e_msg_composer_get_editor (composer);
1975 cnt_editor = e_html_editor_get_content_editor (editor);
1976 e_content_editor_set_changed (cnt_editor, TRUE);
1979 static gboolean
1980 msg_composer_delete_event_cb (EMsgComposer *composer)
1982 /* If the "async" action group is insensitive, it means an
1983 * asynchronous operation is in progress. Block the event. */
1984 if (!gtk_action_group_get_sensitive (composer->priv->async_actions))
1985 return TRUE;
1987 gtk_action_activate (ACTION (CLOSE));
1989 return TRUE;
1992 static void
1993 msg_composer_realize_cb (EMsgComposer *composer)
1995 GSettings *settings;
1996 GtkAction *action;
1998 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
2000 action = ACTION (TOOLBAR_PGP_SIGN);
2001 if (gtk_action_get_visible (action) && !gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
2002 gtk_action_set_visible (action, FALSE);
2004 action = ACTION (TOOLBAR_PGP_ENCRYPT);
2005 if (gtk_action_get_visible (action) && !gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
2006 gtk_action_set_visible (action, FALSE);
2008 action = ACTION (TOOLBAR_SMIME_SIGN);
2009 if (gtk_action_get_visible (action) && !gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
2010 gtk_action_set_visible (action, FALSE);
2012 action = ACTION (TOOLBAR_SMIME_ENCRYPT);
2013 if (gtk_action_get_visible (action) && !gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
2014 gtk_action_set_visible (action, FALSE);
2016 settings = e_util_ref_settings ("org.gnome.evolution.mail");
2018 if (g_settings_get_boolean (settings, "composer-toolbar-show-sign-encrypt")) {
2019 EComposerHeaderTable *table;
2020 ESource *source;
2021 gchar *identity_uid;
2023 table = e_msg_composer_get_header_table (composer);
2024 identity_uid = e_composer_header_table_dup_identity_uid (table, NULL, NULL);
2025 source = e_composer_header_table_ref_source (table, identity_uid);
2027 if (source) {
2028 if (e_source_has_extension (source, E_SOURCE_EXTENSION_OPENPGP)) {
2029 gchar *key_id;
2031 key_id = e_source_openpgp_dup_key_id (e_source_get_extension (source, E_SOURCE_EXTENSION_OPENPGP));
2033 if (key_id && *key_id) {
2034 action = ACTION (TOOLBAR_PGP_SIGN);
2035 gtk_action_set_visible (action, TRUE);
2037 action = ACTION (TOOLBAR_PGP_ENCRYPT);
2038 gtk_action_set_visible (action, TRUE);
2041 g_free (key_id);
2044 if (e_source_has_extension (source, E_SOURCE_EXTENSION_SMIME)) {
2045 ESourceSMIME *smime_extension;
2046 gchar *certificate;
2048 smime_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_SMIME);
2050 certificate = e_source_smime_dup_signing_certificate (smime_extension);
2051 if (certificate && *certificate)
2052 gtk_action_set_visible (ACTION (TOOLBAR_SMIME_SIGN), TRUE);
2053 g_free (certificate);
2055 certificate = e_source_smime_dup_encryption_certificate (smime_extension);
2056 if (certificate && *certificate)
2057 gtk_action_set_visible (ACTION (TOOLBAR_SMIME_ENCRYPT), TRUE);
2058 g_free (certificate);
2061 g_clear_object (&source);
2064 g_free (identity_uid);
2067 g_clear_object (&settings);
2070 static void
2071 msg_composer_prepare_for_quit_cb (EShell *shell,
2072 EActivity *activity,
2073 EMsgComposer *composer)
2075 if (e_msg_composer_is_exiting (composer)) {
2076 /* needs save draft first */
2077 g_object_ref (activity);
2078 g_object_weak_ref (
2079 G_OBJECT (composer), (GWeakNotify)
2080 g_object_unref, activity);
2081 gtk_action_activate (ACTION (SAVE_DRAFT));
2085 static void
2086 msg_composer_quit_requested_cb (EShell *shell,
2087 EShellQuitReason reason,
2088 EMsgComposer *composer)
2090 if (e_msg_composer_is_exiting (composer)) {
2091 g_signal_handlers_disconnect_by_func (
2092 shell, msg_composer_quit_requested_cb, composer);
2093 g_signal_handlers_disconnect_by_func (
2094 shell, msg_composer_prepare_for_quit_cb, composer);
2095 } else if (!e_msg_composer_can_close (composer, FALSE) &&
2096 !e_msg_composer_is_exiting (composer)) {
2097 e_shell_cancel_quit (shell);
2101 static void
2102 msg_composer_set_editor (EMsgComposer *composer,
2103 EHTMLEditor *editor)
2105 g_return_if_fail (E_IS_HTML_EDITOR (editor));
2106 g_return_if_fail (composer->priv->editor == NULL);
2108 composer->priv->editor = g_object_ref_sink (editor);
2111 static void
2112 msg_composer_set_shell (EMsgComposer *composer,
2113 EShell *shell)
2115 g_return_if_fail (E_IS_SHELL (shell));
2116 g_return_if_fail (composer->priv->shell == NULL);
2118 composer->priv->shell = shell;
2120 g_object_add_weak_pointer (
2121 G_OBJECT (shell), &composer->priv->shell);
2124 static void
2125 msg_composer_set_property (GObject *object,
2126 guint property_id,
2127 const GValue *value,
2128 GParamSpec *pspec)
2130 switch (property_id) {
2131 case PROP_EDITOR:
2132 msg_composer_set_editor (
2133 E_MSG_COMPOSER (object),
2134 g_value_get_object (value));
2135 return;
2137 case PROP_IS_REPLY_OR_FORWARD:
2138 e_msg_composer_set_is_reply_or_forward (
2139 E_MSG_COMPOSER (object),
2140 g_value_get_boolean (value));
2141 return;
2143 case PROP_SHELL:
2144 msg_composer_set_shell (
2145 E_MSG_COMPOSER (object),
2146 g_value_get_object (value));
2147 return;
2150 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
2153 static void
2154 msg_composer_get_property (GObject *object,
2155 guint property_id,
2156 GValue *value,
2157 GParamSpec *pspec)
2159 switch (property_id) {
2160 case PROP_BUSY:
2161 g_value_set_boolean (
2162 value, e_msg_composer_is_busy (
2163 E_MSG_COMPOSER (object)));
2164 return;
2166 case PROP_EDITOR:
2167 g_value_set_object (
2168 value, e_msg_composer_get_editor (
2169 E_MSG_COMPOSER (object)));
2170 return;
2172 case PROP_FOCUS_TRACKER:
2173 g_value_set_object (
2174 value, e_msg_composer_get_focus_tracker (
2175 E_MSG_COMPOSER (object)));
2176 return;
2178 case PROP_IS_REPLY_OR_FORWARD:
2179 g_value_set_boolean (
2180 value, e_msg_composer_get_is_reply_or_forward (
2181 E_MSG_COMPOSER (object)));
2182 return;
2184 case PROP_SHELL:
2185 g_value_set_object (
2186 value, e_msg_composer_get_shell (
2187 E_MSG_COMPOSER (object)));
2188 return;
2191 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
2194 static void
2195 msg_composer_finalize (GObject *object)
2197 EMsgComposer *composer = E_MSG_COMPOSER (object);
2199 e_composer_private_finalize (composer);
2201 /* Chain up to parent's finalize() method. */
2202 G_OBJECT_CLASS (e_msg_composer_parent_class)->finalize (object);
2205 static void
2206 msg_composer_gallery_drag_data_get (GtkIconView *icon_view,
2207 GdkDragContext *context,
2208 GtkSelectionData *selection_data,
2209 guint target_type,
2210 guint time)
2212 GtkTreePath *path;
2213 GtkCellRenderer *cell;
2214 GtkTreeModel *model;
2215 GtkTreeIter iter;
2216 GdkAtom target;
2217 gchar *str_data;
2219 if (!gtk_icon_view_get_cursor (icon_view, &path, &cell))
2220 return;
2222 target = gtk_selection_data_get_target (selection_data);
2224 model = gtk_icon_view_get_model (icon_view);
2225 gtk_tree_model_get_iter (model, &iter, path);
2226 gtk_tree_model_get (model, &iter, 1, &str_data, -1);
2227 gtk_tree_path_free (path);
2229 /* only supports "text/uri-list" */
2230 gtk_selection_data_set (
2231 selection_data, target, 8,
2232 (guchar *) str_data, strlen (str_data));
2233 g_free (str_data);
2236 static void
2237 composer_notify_activity_cb (EActivityBar *activity_bar,
2238 GParamSpec *pspec,
2239 EMsgComposer *composer)
2241 EHTMLEditor *editor;
2242 EContentEditor *cnt_editor;
2243 gboolean editable = TRUE;
2244 gboolean busy;
2246 busy = (e_activity_bar_get_activity (activity_bar) != NULL);
2248 if (busy == composer->priv->busy)
2249 return;
2251 composer->priv->busy = busy;
2253 if (busy)
2254 e_msg_composer_save_focused_widget (composer);
2256 editor = e_msg_composer_get_editor (composer);
2257 cnt_editor = e_html_editor_get_content_editor (editor);
2259 if (busy) {
2260 editable = e_content_editor_is_editable (cnt_editor);
2261 e_content_editor_set_editable (cnt_editor, FALSE);
2262 composer->priv->saved_editable = editable;
2263 } else {
2264 editable = composer->priv->saved_editable;
2265 e_content_editor_set_editable (cnt_editor, editable);
2268 g_object_notify (G_OBJECT (composer), "busy");
2270 if (!busy)
2271 e_msg_composer_restore_focus_on_composer (composer);
2274 static void
2275 msg_composer_constructed (GObject *object)
2277 EShell *shell;
2278 EMsgComposer *composer;
2279 EActivityBar *activity_bar;
2280 EAttachmentView *attachment_view;
2281 EAttachmentStore *store;
2282 EComposerHeaderTable *table;
2283 EHTMLEditor *editor;
2284 EContentEditor *cnt_editor;
2285 GtkUIManager *ui_manager;
2286 GtkToggleAction *action;
2287 GtkTargetList *target_list;
2288 GtkTargetEntry *targets;
2289 gint n_targets;
2290 GSettings *settings;
2291 const gchar *id;
2292 gboolean active;
2294 /* Chain up to parent's constructed() method. */
2295 G_OBJECT_CLASS (e_msg_composer_parent_class)->constructed (object);
2297 composer = E_MSG_COMPOSER (object);
2299 g_return_if_fail (E_IS_HTML_EDITOR (composer->priv->editor));
2301 shell = e_msg_composer_get_shell (composer);
2303 e_composer_private_constructed (composer);
2305 editor = e_msg_composer_get_editor (composer);
2306 cnt_editor = e_html_editor_get_content_editor (editor);
2307 ui_manager = e_html_editor_get_ui_manager (editor);
2308 attachment_view = e_msg_composer_get_attachment_view (composer);
2309 table = E_COMPOSER_HEADER_TABLE (composer->priv->header_table);
2311 gtk_window_set_title (GTK_WINDOW (composer), _("Compose Message"));
2312 gtk_window_set_icon_name (GTK_WINDOW (composer), "mail-message-new");
2313 gtk_window_set_default_size (GTK_WINDOW (composer), 600, 500);
2315 g_signal_connect (
2316 object, "delete-event",
2317 G_CALLBACK (msg_composer_delete_event_cb), NULL);
2319 g_signal_connect (
2320 object, "realize",
2321 G_CALLBACK (msg_composer_realize_cb), NULL);
2323 gtk_application_add_window (
2324 GTK_APPLICATION (shell), GTK_WINDOW (object));
2326 g_signal_connect (
2327 shell, "quit-requested",
2328 G_CALLBACK (msg_composer_quit_requested_cb), composer);
2330 g_signal_connect (
2331 shell, "prepare-for-quit",
2332 G_CALLBACK (msg_composer_prepare_for_quit_cb), composer);
2334 /* Restore Persistent State */
2336 e_restore_window (
2337 GTK_WINDOW (composer),
2338 "/org/gnome/evolution/mail/composer-window/",
2339 E_RESTORE_WINDOW_SIZE);
2341 activity_bar = e_html_editor_get_activity_bar (editor);
2342 g_signal_connect (
2343 activity_bar, "notify::activity",
2344 G_CALLBACK (composer_notify_activity_cb), composer);
2347 /* Honor User Preferences */
2349 /* FIXME This should be an EMsgComposer property. */
2350 settings = e_util_ref_settings ("org.gnome.evolution.mail");
2351 action = GTK_TOGGLE_ACTION (ACTION (REQUEST_READ_RECEIPT));
2352 active = g_settings_get_boolean (settings, "composer-request-receipt");
2353 gtk_toggle_action_set_active (action, active);
2355 g_object_unref (settings);
2357 /* Clipboard Support */
2359 g_signal_connect (
2360 cnt_editor, "paste-clipboard",
2361 G_CALLBACK (msg_composer_paste_clipboard_cb), composer);
2363 g_signal_connect (
2364 cnt_editor, "paste-primary-clipboard",
2365 G_CALLBACK (msg_composer_paste_primary_clipboard_cb), composer);
2367 /* Drag-and-Drop Support */
2368 g_signal_connect (
2369 cnt_editor, "drag-drop",
2370 G_CALLBACK (msg_composer_drag_drop_cb), composer);
2372 g_signal_connect (
2373 cnt_editor, "drag-begin",
2374 G_CALLBACK (msg_composer_drag_begin_cb), composer);
2376 g_signal_connect (
2377 cnt_editor, "drop-handled",
2378 G_CALLBACK (msg_composer_drop_handled_cb), composer);
2380 g_signal_connect (
2381 composer->priv->gallery_icon_view, "drag-data-get",
2382 G_CALLBACK (msg_composer_gallery_drag_data_get), NULL);
2384 /* Configure Headers */
2386 composer->priv->notify_destinations_bcc_handler = e_signal_connect_notify_swapped (
2387 table, "notify::destinations-bcc",
2388 G_CALLBACK (msg_composer_notify_header_cb), composer);
2389 composer->priv->notify_destinations_cc_handler = e_signal_connect_notify_swapped (
2390 table, "notify::destinations-cc",
2391 G_CALLBACK (msg_composer_notify_header_cb), composer);
2392 composer->priv->notify_destinations_to_handler = e_signal_connect_notify_swapped (
2393 table, "notify::destinations-to",
2394 G_CALLBACK (msg_composer_notify_header_cb), composer);
2395 /* Do not use e_signal_connect_notify_swapped() here, it it avoids notification
2396 when the property didn't change, but it's about the consolidated property,
2397 identity uid, name and address, where only one of the three can change. */
2398 composer->priv->notify_identity_uid_handler = g_signal_connect_swapped (
2399 table, "notify::identity-uid",
2400 G_CALLBACK (msg_composer_mail_identity_changed_cb), composer);
2401 composer->priv->notify_reply_to_handler = e_signal_connect_notify_swapped (
2402 table, "notify::reply-to",
2403 G_CALLBACK (msg_composer_notify_header_cb), composer);
2404 composer->priv->notify_signature_uid_handler = e_signal_connect_notify_swapped (
2405 table, "notify::signature-uid",
2406 G_CALLBACK (e_composer_update_signature), composer);
2407 composer->priv->notify_subject_changed_handler = e_signal_connect_notify_swapped (
2408 table, "notify::subject",
2409 G_CALLBACK (msg_composer_subject_changed_cb), composer);
2410 composer->priv->notify_subject_handler = e_signal_connect_notify_swapped (
2411 table, "notify::subject",
2412 G_CALLBACK (msg_composer_notify_header_cb), composer);
2414 msg_composer_mail_identity_changed_cb (composer);
2416 /* Attachments */
2418 store = e_attachment_view_get_store (attachment_view);
2420 g_signal_connect_swapped (
2421 store, "row-deleted",
2422 G_CALLBACK (attachment_store_changed_cb), composer);
2424 g_signal_connect_swapped (
2425 store, "row-inserted",
2426 G_CALLBACK (attachment_store_changed_cb), composer);
2428 /* Initialization may have tripped the "changed" state. */
2429 e_content_editor_set_changed (cnt_editor, FALSE);
2431 target_list = e_attachment_view_get_target_list (attachment_view);
2432 targets = gtk_target_table_new_from_list (target_list, &n_targets);
2434 target_list = gtk_drag_dest_get_target_list (GTK_WIDGET (cnt_editor));
2436 gtk_target_list_add_table (target_list, drag_dest_targets, G_N_ELEMENTS (drag_dest_targets));
2437 gtk_target_list_add_table (target_list, targets, n_targets);
2439 gtk_target_table_free (targets, n_targets);
2441 id = "org.gnome.evolution.composer";
2442 e_plugin_ui_register_manager (ui_manager, id, composer);
2443 e_plugin_ui_enable_manager (ui_manager, id);
2445 e_extensible_load_extensions (E_EXTENSIBLE (composer));
2447 e_msg_composer_set_body_text (composer, "", TRUE);
2450 static void
2451 msg_composer_dispose (GObject *object)
2453 EMsgComposer *composer = E_MSG_COMPOSER (object);
2454 EMsgComposerPrivate *priv = E_MSG_COMPOSER_GET_PRIVATE (composer);
2455 EShell *shell;
2457 if (priv->address_dialog != NULL) {
2458 gtk_widget_destroy (priv->address_dialog);
2459 priv->address_dialog = NULL;
2462 /* FIXME Our EShell is already unreferenced. */
2463 shell = e_shell_get_default ();
2465 g_signal_handlers_disconnect_by_func (
2466 shell, msg_composer_quit_requested_cb, composer);
2467 g_signal_handlers_disconnect_by_func (
2468 shell, msg_composer_prepare_for_quit_cb, composer);
2470 if (priv->header_table != NULL) {
2471 EComposerHeaderTable *table;
2473 table = E_COMPOSER_HEADER_TABLE (composer->priv->header_table);
2475 e_signal_disconnect_notify_handler (
2476 table, &priv->notify_destinations_bcc_handler);
2477 e_signal_disconnect_notify_handler (
2478 table, &priv->notify_destinations_cc_handler);
2479 e_signal_disconnect_notify_handler (
2480 table, &priv->notify_destinations_to_handler);
2481 e_signal_disconnect_notify_handler (
2482 table, &priv->notify_identity_uid_handler);
2483 e_signal_disconnect_notify_handler (
2484 table, &priv->notify_reply_to_handler);
2485 e_signal_disconnect_notify_handler (
2486 table, &priv->notify_destinations_to_handler);
2487 e_signal_disconnect_notify_handler (
2488 table, &priv->notify_subject_changed_handler);
2491 e_composer_private_dispose (composer);
2493 /* Chain up to parent's dispose() method. */
2494 G_OBJECT_CLASS (e_msg_composer_parent_class)->dispose (object);
2497 static void
2498 msg_composer_map (GtkWidget *widget)
2500 EMsgComposer *composer;
2501 EComposerHeaderTable *table;
2502 GtkWidget *input_widget;
2503 EHTMLEditor *editor;
2504 EContentEditor *cnt_editor;
2505 const gchar *text;
2507 /* Chain up to parent's map() method. */
2508 GTK_WIDGET_CLASS (e_msg_composer_parent_class)->map (widget);
2510 composer = E_MSG_COMPOSER (widget);
2511 editor = e_msg_composer_get_editor (composer);
2512 table = e_msg_composer_get_header_table (composer);
2514 /* If the 'To' field is empty, focus it. */
2515 input_widget =
2516 e_composer_header_table_get_header (
2517 table, E_COMPOSER_HEADER_TO)->input_widget;
2518 text = gtk_entry_get_text (GTK_ENTRY (input_widget));
2519 if (gtk_widget_get_visible (input_widget) && (text == NULL || *text == '\0')) {
2520 gtk_widget_grab_focus (input_widget);
2521 return;
2524 /* If not, check the 'Subject' field. */
2525 input_widget =
2526 e_composer_header_table_get_header (
2527 table, E_COMPOSER_HEADER_SUBJECT)->input_widget;
2528 text = gtk_entry_get_text (GTK_ENTRY (input_widget));
2529 if (gtk_widget_get_visible (input_widget) && (text == NULL || *text == '\0')) {
2530 gtk_widget_grab_focus (input_widget);
2531 return;
2534 /* Jump to the editor as a last resort. */
2535 cnt_editor = e_html_editor_get_content_editor (editor);
2536 gtk_widget_grab_focus (GTK_WIDGET (cnt_editor));
2539 static gboolean
2540 msg_composer_key_press_event (GtkWidget *widget,
2541 GdkEventKey *event)
2543 EMsgComposer *composer;
2544 GtkWidget *input_widget;
2545 EHTMLEditor *editor;
2546 EContentEditor *cnt_editor;
2548 composer = E_MSG_COMPOSER (widget);
2549 editor = e_msg_composer_get_editor (composer);
2550 cnt_editor = e_html_editor_get_content_editor (editor);
2552 input_widget =
2553 e_composer_header_table_get_header (
2554 e_msg_composer_get_header_table (composer),
2555 E_COMPOSER_HEADER_SUBJECT)->input_widget;
2557 #ifdef HAVE_XFREE
2558 if (event->keyval == XF86XK_Send) {
2559 e_msg_composer_send (composer);
2560 return TRUE;
2562 #endif /* HAVE_XFREE */
2564 if (event->keyval == GDK_KEY_Escape) {
2565 gtk_action_activate (ACTION (CLOSE));
2566 return TRUE;
2569 if (event->keyval == GDK_KEY_Tab && gtk_widget_is_focus (input_widget)) {
2570 gtk_widget_grab_focus (GTK_WIDGET (cnt_editor));
2571 return TRUE;
2574 if (gtk_widget_is_focus (GTK_WIDGET (cnt_editor))) {
2575 if (event->keyval == GDK_KEY_ISO_Left_Tab) {
2576 gboolean view_processed = FALSE;
2578 g_signal_emit_by_name (cnt_editor, "key-press-event", event, &view_processed);
2580 if (!view_processed)
2581 gtk_widget_grab_focus (input_widget);
2583 return TRUE;
2587 if (e_util_check_gtk_bindings_in_key_press_event_cb (widget, (GdkEvent *) event))
2588 return TRUE;
2590 /* Chain up to parent's key_press_event() method. */
2591 return GTK_WIDGET_CLASS (e_msg_composer_parent_class)->
2592 key_press_event (widget, event);
2595 static gboolean
2596 msg_composer_presend (EMsgComposer *composer)
2598 /* This keeps the signal accumulator at TRUE. */
2599 return TRUE;
2602 static gboolean
2603 msg_composer_accumulator_false_abort (GSignalInvocationHint *ihint,
2604 GValue *return_accu,
2605 const GValue *handler_return,
2606 gpointer dummy)
2608 gboolean v_boolean;
2610 v_boolean = g_value_get_boolean (handler_return);
2611 g_value_set_boolean (return_accu, v_boolean);
2613 /* FALSE means abort the signal emission. */
2614 return v_boolean;
2618 * e_msg_composer_is_busy:
2619 * @composer: an #EMsgComposer
2621 * Returns %TRUE only while an #EActivity is in progress.
2623 * Returns: whether @composer is busy
2625 gboolean
2626 e_msg_composer_is_busy (EMsgComposer *composer)
2628 g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE);
2630 return composer->priv->busy;
2633 static void
2634 e_msg_composer_class_init (EMsgComposerClass *class)
2636 GObjectClass *object_class;
2637 GtkWidgetClass *widget_class;
2639 g_type_class_add_private (class, sizeof (EMsgComposerPrivate));
2641 object_class = G_OBJECT_CLASS (class);
2642 object_class->set_property = msg_composer_set_property;
2643 object_class->get_property = msg_composer_get_property;
2644 object_class->dispose = msg_composer_dispose;
2645 object_class->finalize = msg_composer_finalize;
2646 object_class->constructed = msg_composer_constructed;
2648 widget_class = GTK_WIDGET_CLASS (class);
2649 widget_class->map = msg_composer_map;
2650 widget_class->key_press_event = msg_composer_key_press_event;
2652 class->presend = msg_composer_presend;
2654 g_object_class_install_property (
2655 object_class,
2656 PROP_BUSY,
2657 g_param_spec_boolean (
2658 "busy",
2659 "Busy",
2660 "Whether an activity is in progress",
2661 FALSE,
2662 G_PARAM_READABLE |
2663 G_PARAM_STATIC_STRINGS));
2665 g_object_class_install_property (
2666 object_class,
2667 PROP_EDITOR,
2668 g_param_spec_object (
2669 "editor",
2670 NULL,
2671 NULL,
2672 E_TYPE_HTML_EDITOR,
2673 G_PARAM_READWRITE |
2674 G_PARAM_CONSTRUCT_ONLY));
2676 g_object_class_install_property (
2677 object_class,
2678 PROP_FOCUS_TRACKER,
2679 g_param_spec_object (
2680 "focus-tracker",
2681 NULL,
2682 NULL,
2683 E_TYPE_FOCUS_TRACKER,
2684 G_PARAM_READABLE));
2686 g_object_class_install_property (
2687 object_class,
2688 PROP_IS_REPLY_OR_FORWARD,
2689 g_param_spec_boolean (
2690 "is-reply-or-forward",
2691 "Is Reply Or Forward",
2692 "Whether the composed message is a reply or a forward message",
2693 FALSE,
2694 G_PARAM_READWRITE |
2695 G_PARAM_STATIC_STRINGS));
2697 g_object_class_install_property (
2698 object_class,
2699 PROP_SHELL,
2700 g_param_spec_object (
2701 "shell",
2702 "Shell",
2703 "The EShell singleton",
2704 E_TYPE_SHELL,
2705 G_PARAM_READWRITE |
2706 G_PARAM_CONSTRUCT_ONLY));
2708 signals[PRESEND] = g_signal_new (
2709 "presend",
2710 G_OBJECT_CLASS_TYPE (class),
2711 G_SIGNAL_RUN_LAST,
2712 G_STRUCT_OFFSET (EMsgComposerClass, presend),
2713 msg_composer_accumulator_false_abort,
2714 NULL,
2715 e_marshal_BOOLEAN__VOID,
2716 G_TYPE_BOOLEAN, 0);
2718 signals[SEND] = g_signal_new (
2719 "send",
2720 G_OBJECT_CLASS_TYPE (class),
2721 G_SIGNAL_RUN_LAST,
2722 G_STRUCT_OFFSET (EMsgComposerClass, send),
2723 NULL, NULL,
2724 e_marshal_VOID__OBJECT_OBJECT,
2725 G_TYPE_NONE, 2,
2726 CAMEL_TYPE_MIME_MESSAGE,
2727 E_TYPE_ACTIVITY);
2729 signals[SAVE_TO_DRAFTS] = g_signal_new (
2730 "save-to-drafts",
2731 G_OBJECT_CLASS_TYPE (class),
2732 G_SIGNAL_RUN_LAST,
2733 G_STRUCT_OFFSET (EMsgComposerClass, save_to_drafts),
2734 NULL, NULL,
2735 e_marshal_VOID__OBJECT_OBJECT,
2736 G_TYPE_NONE, 2,
2737 CAMEL_TYPE_MIME_MESSAGE,
2738 E_TYPE_ACTIVITY);
2740 signals[SAVE_TO_OUTBOX] = g_signal_new (
2741 "save-to-outbox",
2742 G_OBJECT_CLASS_TYPE (class),
2743 G_SIGNAL_RUN_LAST,
2744 G_STRUCT_OFFSET (EMsgComposerClass, save_to_outbox),
2745 NULL, NULL,
2746 e_marshal_VOID__OBJECT_OBJECT,
2747 G_TYPE_NONE, 2,
2748 CAMEL_TYPE_MIME_MESSAGE,
2749 E_TYPE_ACTIVITY);
2751 signals[PRINT] = g_signal_new (
2752 "print",
2753 G_OBJECT_CLASS_TYPE (class),
2754 G_SIGNAL_RUN_LAST,
2755 0, NULL, NULL,
2756 e_marshal_VOID__ENUM_OBJECT_OBJECT,
2757 G_TYPE_NONE, 3,
2758 GTK_TYPE_PRINT_OPERATION_ACTION,
2759 CAMEL_TYPE_MIME_MESSAGE,
2760 E_TYPE_ACTIVITY);
2762 signals[BEFORE_DESTROY] = g_signal_new (
2763 "before-destroy",
2764 G_OBJECT_CLASS_TYPE (class),
2765 G_SIGNAL_RUN_LAST,
2766 0, NULL, NULL,
2767 g_cclosure_marshal_VOID__VOID,
2768 G_TYPE_NONE, 0,
2769 G_TYPE_NONE);
2772 void
2773 e_composer_emit_before_destroy (EMsgComposer *composer)
2775 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
2777 g_signal_emit (composer, signals[BEFORE_DESTROY], 0);
2780 static void
2781 e_msg_composer_init (EMsgComposer *composer)
2783 composer->priv = E_MSG_COMPOSER_GET_PRIVATE (composer);
2786 static void
2787 e_msg_composer_editor_created_cb (GObject *source_object,
2788 GAsyncResult *result,
2789 gpointer user_data)
2791 GtkWidget *editor;
2792 ESimpleAsyncResult *eresult = user_data;
2793 GError *error = NULL;
2795 g_return_if_fail (E_IS_SIMPLE_ASYNC_RESULT (eresult));
2797 editor = e_html_editor_new_finish (result, &error);
2798 if (error) {
2799 g_warning ("%s: Failed to create HTML editor: %s", G_STRFUNC, error->message);
2800 g_clear_error (&error);
2801 } else {
2802 e_simple_async_result_set_op_pointer (eresult, editor, NULL);
2803 e_simple_async_result_complete (eresult);
2806 g_object_unref (eresult);
2810 * e_msg_composer_new:
2811 * @shell: an #EShell
2812 * @callback: called when the composer is ready
2813 * @user_data: user data passed to @callback
2815 * Asynchronously creates an #EMsgComposer. The operation is finished
2816 * with e_msg_composer_new_finish() called from within the @callback.
2818 * Since: 3.22
2820 void
2821 e_msg_composer_new (EShell *shell,
2822 GAsyncReadyCallback callback,
2823 gpointer user_data)
2825 ESimpleAsyncResult *eresult;
2827 g_return_if_fail (E_IS_SHELL (shell));
2828 g_return_if_fail (callback != NULL);
2830 eresult = e_simple_async_result_new (NULL, callback, user_data, e_msg_composer_new);
2831 e_simple_async_result_set_user_data (eresult, g_object_ref (shell), g_object_unref);
2833 e_html_editor_new (e_msg_composer_editor_created_cb, eresult);
2837 * e_msg_composer_new_finish:
2838 * @result: a #GAsyncResult provided by the callback from e_msg_composer_new()
2839 * @error: optional #GError for errors
2841 * Finishes call of e_msg_composer_new().
2843 * Since: 3.22
2845 EMsgComposer *
2846 e_msg_composer_new_finish (GAsyncResult *result,
2847 GError **error)
2849 ESimpleAsyncResult *eresult;
2850 EHTMLEditor *html_editor;
2852 g_return_val_if_fail (E_IS_SIMPLE_ASYNC_RESULT (result), NULL);
2853 g_return_val_if_fail (g_async_result_is_tagged (result, e_msg_composer_new), NULL);
2855 eresult = E_SIMPLE_ASYNC_RESULT (result);
2857 html_editor = e_simple_async_result_get_op_pointer (eresult);
2858 g_return_val_if_fail (E_IS_HTML_EDITOR (html_editor), NULL);
2860 return g_object_new (E_TYPE_MSG_COMPOSER,
2861 "shell", e_simple_async_result_get_user_data (eresult),
2862 "editor", html_editor,
2863 NULL);
2867 * e_msg_composer_get_editor:
2868 * @composer: an #EMsgComposer
2870 * Returns @composer's internal #EHTMLEditor instance.
2872 * Returns: an #EHTMLEditor
2874 EHTMLEditor *
2875 e_msg_composer_get_editor (EMsgComposer *composer)
2877 g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
2879 return composer->priv->editor;
2882 EFocusTracker *
2883 e_msg_composer_get_focus_tracker (EMsgComposer *composer)
2885 g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
2887 return composer->priv->focus_tracker;
2890 static void
2891 e_msg_composer_set_pending_body (EMsgComposer *composer,
2892 gchar *text,
2893 gssize length,
2894 gboolean is_html)
2896 g_object_set_data_full (
2897 G_OBJECT (composer), "body:text_mime_type",
2898 GINT_TO_POINTER (is_html), NULL);
2899 g_object_set_data_full (
2900 G_OBJECT (composer), "body:text",
2901 text, (GDestroyNotify) g_free);
2904 static void
2905 e_msg_composer_flush_pending_body (EMsgComposer *composer)
2907 const gchar *body;
2908 gboolean is_html;
2910 body = g_object_get_data (G_OBJECT (composer), "body:text");
2911 is_html = GPOINTER_TO_INT (
2912 g_object_get_data (G_OBJECT (composer), "body:text_mime_type"));
2914 if (body != NULL)
2915 set_editor_text (composer, body, is_html, FALSE);
2917 g_object_set_data (G_OBJECT (composer), "body:text", NULL);
2920 static void
2921 add_attachments_handle_mime_part (EMsgComposer *composer,
2922 CamelMimePart *mime_part,
2923 gboolean just_inlines,
2924 gboolean related,
2925 gint depth)
2927 CamelContentType *content_type;
2928 CamelDataWrapper *wrapper;
2929 EHTMLEditor *editor;
2930 EContentEditor *cnt_editor;
2932 if (!mime_part)
2933 return;
2935 content_type = camel_mime_part_get_content_type (mime_part);
2936 wrapper = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
2937 editor = e_msg_composer_get_editor (composer);
2938 cnt_editor = e_html_editor_get_content_editor (editor);
2940 if (CAMEL_IS_MULTIPART (wrapper)) {
2941 /* another layer of multipartness... */
2942 add_attachments_from_multipart (
2943 composer, (CamelMultipart *) wrapper,
2944 just_inlines, depth + 1);
2945 } else if (just_inlines) {
2946 if (camel_mime_part_get_content_id (mime_part) ||
2947 camel_mime_part_get_content_location (mime_part))
2948 e_content_editor_insert_image_from_mime_part (
2949 cnt_editor, mime_part);
2950 } else if (related && camel_content_type_is (content_type, "image", "*")) {
2951 e_content_editor_insert_image_from_mime_part (cnt_editor, mime_part);
2952 } else if (camel_content_type_is (content_type, "text", "*") &&
2953 camel_mime_part_get_filename (mime_part) == NULL) {
2954 /* Do nothing if this is a text/anything without a
2955 * filename, otherwise attach it too. */
2956 } else {
2957 e_msg_composer_attach (composer, mime_part);
2961 static void
2962 add_attachments_from_multipart (EMsgComposer *composer,
2963 CamelMultipart *multipart,
2964 gboolean just_inlines,
2965 gint depth)
2967 /* find appropriate message attachments to add to the composer */
2968 CamelMimePart *mime_part;
2969 gboolean related;
2970 gint i, nparts;
2972 related = camel_content_type_is (
2973 camel_data_wrapper_get_mime_type_field (CAMEL_DATA_WRAPPER (multipart)),
2974 "multipart", "related");
2976 if (CAMEL_IS_MULTIPART_SIGNED (multipart)) {
2977 mime_part = camel_multipart_get_part (
2978 multipart, CAMEL_MULTIPART_SIGNED_CONTENT);
2979 add_attachments_handle_mime_part (
2980 composer, mime_part, just_inlines, related, depth);
2981 } else if (CAMEL_IS_MULTIPART_ENCRYPTED (multipart)) {
2982 /* XXX What should we do in this case? */
2983 } else {
2984 nparts = camel_multipart_get_number (multipart);
2986 for (i = 0; i < nparts; i++) {
2987 mime_part = camel_multipart_get_part (multipart, i);
2988 add_attachments_handle_mime_part (
2989 composer, mime_part, just_inlines,
2990 related, depth);
2996 * e_msg_composer_add_message_attachments:
2997 * @composer: the composer to add the attachments to.
2998 * @message: the source message to copy the attachments from.
2999 * @just_inlines: whether to attach all attachments or just add
3000 * inline images.
3002 * Walk through all the mime parts in @message and add them to the composer
3003 * specified in @composer.
3005 void
3006 e_msg_composer_add_message_attachments (EMsgComposer *composer,
3007 CamelMimeMessage *message,
3008 gboolean just_inlines)
3010 CamelDataWrapper *wrapper;
3012 wrapper = camel_medium_get_content (CAMEL_MEDIUM (message));
3013 if (!CAMEL_IS_MULTIPART (wrapper))
3014 return;
3016 add_attachments_from_multipart (
3017 composer, (CamelMultipart *) wrapper, just_inlines, 0);
3020 static void
3021 handle_multipart_signed (EMsgComposer *composer,
3022 CamelMultipart *multipart,
3023 gboolean keep_signature,
3024 GCancellable *cancellable,
3025 gint depth)
3027 CamelContentType *content_type;
3028 CamelDataWrapper *content;
3029 CamelMimePart *mime_part;
3030 GtkToggleAction *action = NULL;
3031 const gchar *protocol;
3033 content = CAMEL_DATA_WRAPPER (multipart);
3034 content_type = camel_data_wrapper_get_mime_type_field (content);
3035 protocol = camel_content_type_param (content_type, "protocol");
3037 if (protocol == NULL) {
3038 action = NULL;
3039 } else if (g_ascii_strcasecmp (protocol, "application/pgp-signature") == 0) {
3040 if (!gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (ACTION (SMIME_SIGN))) &&
3041 !gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (ACTION (SMIME_ENCRYPT))))
3042 action = GTK_TOGGLE_ACTION (ACTION (PGP_SIGN));
3043 } else if (g_ascii_strcasecmp (protocol, "application/x-pkcs7-signature") == 0) {
3044 if (!gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (ACTION (PGP_SIGN))) &&
3045 !gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (ACTION (PGP_ENCRYPT))))
3046 action = GTK_TOGGLE_ACTION (ACTION (SMIME_SIGN));
3049 if (action)
3050 gtk_toggle_action_set_active (action, TRUE);
3052 mime_part = camel_multipart_get_part (
3053 multipart, CAMEL_MULTIPART_SIGNED_CONTENT);
3055 if (mime_part == NULL)
3056 return;
3058 content_type = camel_mime_part_get_content_type (mime_part);
3059 content = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
3061 if (CAMEL_IS_MULTIPART (content)) {
3062 multipart = CAMEL_MULTIPART (content);
3064 /* Note: depth is preserved here because we're not
3065 * counting multipart/signed as a multipart, instead
3066 * we want to treat the content part as our mime part
3067 * here. */
3069 if (CAMEL_IS_MULTIPART_SIGNED (content)) {
3070 /* Handle the signed content and configure
3071 * the composer to sign outgoing messages. */
3072 handle_multipart_signed (
3073 composer, multipart, keep_signature, cancellable, depth);
3075 } else if (CAMEL_IS_MULTIPART_ENCRYPTED (content)) {
3076 /* Decrypt the encrypted content and configure
3077 * the composer to encrypt outgoing messages. */
3078 handle_multipart_encrypted (
3079 composer, mime_part, keep_signature, cancellable, depth);
3081 } else if (camel_content_type_is (content_type, "multipart", "alternative")) {
3082 /* This contains the text/plain and text/html
3083 * versions of the message body. */
3084 handle_multipart_alternative (
3085 composer, multipart, keep_signature, cancellable, depth);
3087 } else {
3088 /* There must be attachments... */
3089 handle_multipart (
3090 composer, multipart, keep_signature, cancellable, depth);
3093 } else if (camel_content_type_is (content_type, "text", "*")) {
3094 gchar *html;
3095 gssize length;
3097 html = emcu_part_to_html (
3098 composer, mime_part, &length, keep_signature, cancellable);
3099 if (html)
3100 e_msg_composer_set_pending_body (composer, html, length, TRUE);
3102 } else {
3103 e_msg_composer_attach (composer, mime_part);
3107 static void
3108 handle_multipart_encrypted (EMsgComposer *composer,
3109 CamelMimePart *multipart,
3110 gboolean keep_signature,
3111 GCancellable *cancellable,
3112 gint depth)
3114 CamelContentType *content_type;
3115 CamelCipherContext *cipher;
3116 CamelDataWrapper *content;
3117 CamelMimePart *mime_part;
3118 CamelSession *session;
3119 CamelCipherValidity *valid;
3120 GtkToggleAction *action = NULL;
3121 const gchar *protocol;
3123 content_type = camel_mime_part_get_content_type (multipart);
3124 protocol = camel_content_type_param (content_type, "protocol");
3126 if (protocol && g_ascii_strcasecmp (protocol, "application/pgp-encrypted") == 0) {
3127 if (!gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (ACTION (SMIME_SIGN))) &&
3128 !gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (ACTION (SMIME_ENCRYPT))))
3129 action = GTK_TOGGLE_ACTION (ACTION (PGP_ENCRYPT));
3130 } else if (content_type && (
3131 camel_content_type_is (content_type, "application", "x-pkcs7-mime")
3132 || camel_content_type_is (content_type, "application", "pkcs7-mime"))) {
3133 if (!gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (ACTION (PGP_SIGN))) &&
3134 !gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (ACTION (PGP_ENCRYPT))))
3135 action = GTK_TOGGLE_ACTION (ACTION (SMIME_ENCRYPT));
3138 if (action)
3139 gtk_toggle_action_set_active (action, TRUE);
3141 session = e_msg_composer_ref_session (composer);
3142 cipher = camel_gpg_context_new (session);
3143 mime_part = camel_mime_part_new ();
3144 valid = camel_cipher_context_decrypt_sync (
3145 cipher, multipart, mime_part, cancellable, NULL);
3146 g_object_unref (cipher);
3147 g_object_unref (session);
3149 if (valid == NULL)
3150 return;
3152 camel_cipher_validity_free (valid);
3154 content_type = camel_mime_part_get_content_type (mime_part);
3156 content = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
3158 if (CAMEL_IS_MULTIPART (content)) {
3159 CamelMultipart *content_multipart = CAMEL_MULTIPART (content);
3161 /* Note: depth is preserved here because we're not
3162 * counting multipart/encrypted as a multipart, instead
3163 * we want to treat the content part as our mime part
3164 * here. */
3166 if (CAMEL_IS_MULTIPART_SIGNED (content)) {
3167 /* Handle the signed content and configure the
3168 * composer to sign outgoing messages. */
3169 handle_multipart_signed (
3170 composer, content_multipart, keep_signature, cancellable, depth);
3172 } else if (CAMEL_IS_MULTIPART_ENCRYPTED (content)) {
3173 /* Decrypt the encrypted content and configure the
3174 * composer to encrypt outgoing messages. */
3175 handle_multipart_encrypted (
3176 composer, mime_part, keep_signature, cancellable, depth);
3178 } else if (camel_content_type_is (content_type, "multipart", "alternative")) {
3179 /* This contains the text/plain and text/html
3180 * versions of the message body. */
3181 handle_multipart_alternative (
3182 composer, content_multipart, keep_signature, cancellable, depth);
3184 } else {
3185 /* There must be attachments... */
3186 handle_multipart (
3187 composer, content_multipart, keep_signature, cancellable, depth);
3190 } else if (camel_content_type_is (content_type, "text", "*")) {
3191 gchar *html;
3192 gssize length;
3194 html = emcu_part_to_html (
3195 composer, mime_part, &length, keep_signature, cancellable);
3196 if (html)
3197 e_msg_composer_set_pending_body (composer, html, length, TRUE);
3199 } else {
3200 e_msg_composer_attach (composer, mime_part);
3203 g_object_unref (mime_part);
3206 static void
3207 handle_multipart_alternative (EMsgComposer *composer,
3208 CamelMultipart *multipart,
3209 gboolean keep_signature,
3210 GCancellable *cancellable,
3211 gint depth)
3213 /* Find the text/html part and set the composer body to its content */
3214 CamelMimePart *text_part = NULL, *fallback_text_part = NULL;
3215 gint i, nparts;
3217 nparts = camel_multipart_get_number (multipart);
3219 for (i = 0; i < nparts; i++) {
3220 CamelContentType *content_type;
3221 CamelDataWrapper *content;
3222 CamelMimePart *mime_part;
3224 mime_part = camel_multipart_get_part (multipart, i);
3226 if (!mime_part)
3227 continue;
3229 content_type = camel_mime_part_get_content_type (mime_part);
3230 content = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
3232 if (CAMEL_IS_MULTIPART (content)) {
3233 CamelMultipart *mp;
3235 mp = CAMEL_MULTIPART (content);
3237 if (CAMEL_IS_MULTIPART_SIGNED (content)) {
3238 /* Handle the signed content and configure
3239 * the composer to sign outgoing messages. */
3240 handle_multipart_signed (
3241 composer, mp, keep_signature, cancellable, depth + 1);
3243 } else if (CAMEL_IS_MULTIPART_ENCRYPTED (content)) {
3244 /* Decrypt the encrypted content and configure
3245 * the composer to encrypt outgoing messages. */
3246 handle_multipart_encrypted (
3247 composer, mime_part, keep_signature,
3248 cancellable, depth + 1);
3250 } else {
3251 /* Depth doesn't matter so long as we
3252 * don't pass 0. */
3253 handle_multipart (
3254 composer, mp, keep_signature, cancellable, depth + 1);
3257 } else if (camel_content_type_is (content_type, "text", "html")) {
3258 /* text/html is preferable, so once we find it we're done... */
3259 text_part = mime_part;
3260 break;
3261 } else if (camel_content_type_is (content_type, "text", "*")) {
3262 /* anyt text part not text/html is second rate so the first
3263 * text part we find isn't necessarily the one we'll use. */
3264 if (!text_part)
3265 text_part = mime_part;
3267 /* this is when prefer-plain filters out text/html part, then
3268 * the text/plain should be used */
3269 if (camel_content_type_is (content_type, "text", "plain"))
3270 fallback_text_part = mime_part;
3271 } else {
3272 e_msg_composer_attach (composer, mime_part);
3276 if (text_part) {
3277 gchar *html;
3278 gssize length;
3280 html = emcu_part_to_html (
3281 composer, text_part, &length, keep_signature, cancellable);
3282 if (!html && fallback_text_part)
3283 html = emcu_part_to_html (
3284 composer, fallback_text_part, &length, keep_signature, cancellable);
3285 if (html)
3286 e_msg_composer_set_pending_body (composer, html, length, TRUE);
3290 static void
3291 handle_multipart (EMsgComposer *composer,
3292 CamelMultipart *multipart,
3293 gboolean keep_signature,
3294 GCancellable *cancellable,
3295 gint depth)
3297 gint i, nparts;
3299 nparts = camel_multipart_get_number (multipart);
3301 for (i = 0; i < nparts; i++) {
3302 CamelContentType *content_type;
3303 CamelDataWrapper *content;
3304 CamelMimePart *mime_part;
3306 mime_part = camel_multipart_get_part (multipart, i);
3308 if (!mime_part)
3309 continue;
3311 content_type = camel_mime_part_get_content_type (mime_part);
3312 content = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
3314 if (CAMEL_IS_MULTIPART (content)) {
3315 CamelMultipart *mp;
3317 mp = CAMEL_MULTIPART (content);
3319 if (CAMEL_IS_MULTIPART_SIGNED (content)) {
3320 /* Handle the signed content and configure
3321 * the composer to sign outgoing messages. */
3322 handle_multipart_signed (
3323 composer, mp, keep_signature, cancellable, depth + 1);
3325 } else if (CAMEL_IS_MULTIPART_ENCRYPTED (content)) {
3326 /* Decrypt the encrypted content and configure
3327 * the composer to encrypt outgoing messages. */
3328 handle_multipart_encrypted (
3329 composer, mime_part, keep_signature,
3330 cancellable, depth + 1);
3332 } else if (camel_content_type_is (
3333 content_type, "multipart", "alternative")) {
3334 handle_multipart_alternative (
3335 composer, mp, keep_signature, cancellable, depth + 1);
3337 } else {
3338 /* Depth doesn't matter so long as we
3339 * don't pass 0. */
3340 handle_multipart (
3341 composer, mp, keep_signature, cancellable, depth + 1);
3344 } else if (depth == 0 && i == 0) {
3345 gchar *html = NULL;
3346 gssize length = 0;
3348 /* Since the first part is not multipart/alternative,
3349 * this must be the body. */
3350 html = emcu_part_to_html (
3351 composer, mime_part, &length, keep_signature, cancellable);
3353 e_msg_composer_set_pending_body (composer, html, length, TRUE);
3355 } else if (camel_mime_part_get_content_id (mime_part) ||
3356 camel_mime_part_get_content_location (mime_part)) {
3357 /* special in-line attachment */
3358 EHTMLEditor *editor;
3359 EContentEditor *cnt_editor;
3361 editor = e_msg_composer_get_editor (composer);
3362 cnt_editor = e_html_editor_get_content_editor (editor);
3364 e_content_editor_insert_image_from_mime_part (cnt_editor, mime_part);
3365 } else {
3366 /* normal attachment */
3367 e_msg_composer_attach (composer, mime_part);
3372 static void
3373 set_signature_gui (EMsgComposer *composer)
3375 EHTMLEditor *editor;
3376 EContentEditor *cnt_editor;
3377 EComposerHeaderTable *table;
3378 EMailSignatureComboBox *combo_box;
3379 gchar *uid = NULL;
3381 table = e_msg_composer_get_header_table (composer);
3382 combo_box = e_composer_header_table_get_signature_combo_box (table);
3384 editor = e_msg_composer_get_editor (composer);
3385 cnt_editor = e_html_editor_get_content_editor (editor);
3387 if ((uid = e_content_editor_get_current_signature_uid (cnt_editor))) {
3388 /* The combo box active ID is the signature's ESource UID. */
3389 gtk_combo_box_set_active_id (GTK_COMBO_BOX (combo_box), uid);
3393 static void
3394 composer_add_auto_recipients (ESource *source,
3395 const gchar *property_name,
3396 GHashTable *hash_table)
3398 ESourceMailComposition *extension;
3399 CamelInternetAddress *inet_addr;
3400 const gchar *extension_name;
3401 gchar *comma_separated_addrs;
3402 gchar **addr_array = NULL;
3403 gint length, ii;
3404 gint retval;
3406 extension_name = E_SOURCE_EXTENSION_MAIL_COMPOSITION;
3407 extension = e_source_get_extension (source, extension_name);
3409 g_object_get (extension, property_name, &addr_array, NULL);
3411 if (addr_array == NULL)
3412 return;
3414 inet_addr = camel_internet_address_new ();
3415 comma_separated_addrs = g_strjoinv (", ", addr_array);
3417 retval = camel_address_decode (
3418 CAMEL_ADDRESS (inet_addr), comma_separated_addrs);
3420 g_free (comma_separated_addrs);
3421 g_strfreev (addr_array);
3423 if (retval == -1)
3424 return;
3426 length = camel_address_length (CAMEL_ADDRESS (inet_addr));
3428 for (ii = 0; ii < length; ii++) {
3429 const gchar *name;
3430 const gchar *addr;
3432 if (camel_internet_address_get (inet_addr, ii, &name, &addr))
3433 g_hash_table_add (hash_table, g_strdup (addr));
3436 g_object_unref (inet_addr);
3440 * e_msg_composer_setup_with_message:
3441 * @composer: an #EMsgComposer
3442 * @message: The message to use as the source
3443 * @keep_signature: Keep message signature, if any
3444 * @override_identity_uid: (allow none): Optional identity UID to use, or %NULL
3445 * @override_alias_name: (nullable): an alias name to use together with the override_identity_uid, or %NULL
3446 * @override_alias_address: (nullable): an alias address to use together with the override_identity_uid, or %NULL
3447 * @cancellable: optional #GCancellable object, or %NULL
3449 * Sets up the message @composer with a specific @message.
3451 * Note: Designed to work only for messages constructed using Evolution.
3453 * Since: 3.22
3455 void
3456 e_msg_composer_setup_with_message (EMsgComposer *composer,
3457 CamelMimeMessage *message,
3458 gboolean keep_signature,
3459 const gchar *override_identity_uid,
3460 const gchar *override_alias_name,
3461 const gchar *override_alias_address,
3462 GCancellable *cancellable)
3464 CamelInternetAddress *from, *to, *cc, *bcc;
3465 GList *To = NULL, *Cc = NULL, *Bcc = NULL, *postto = NULL;
3466 const gchar *format, *subject, *composer_mode;
3467 EDestination **Tov, **Ccv, **Bccv;
3468 GHashTable *auto_cc, *auto_bcc;
3469 CamelContentType *content_type;
3470 const CamelNameValueArray *headers;
3471 CamelDataWrapper *content;
3472 EMsgComposerPrivate *priv;
3473 EComposerHeaderTable *table;
3474 ESource *source = NULL;
3475 EHTMLEditor *editor;
3476 EContentEditor *cnt_editor;
3477 GtkToggleAction *action;
3478 gchar *identity_uid;
3479 gint len, i;
3480 guint jj, jjlen;
3481 gboolean is_message_from_draft = FALSE;
3483 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
3485 headers = camel_medium_get_headers (CAMEL_MEDIUM (message));
3486 jjlen = camel_name_value_array_get_length (headers);
3487 for (jj = 0; jj < jjlen; jj++) {
3488 const gchar *header_name = NULL, *header_value = NULL;
3489 gchar *value;
3491 if (!camel_name_value_array_get (headers, jj, &header_name, &header_value) ||
3492 !header_name)
3493 continue;
3495 if (g_ascii_strcasecmp (header_name, "X-Evolution-PostTo") == 0) {
3496 value = g_strstrip (g_strdup (header_value));
3497 postto = g_list_append (postto, value);
3501 priv = E_MSG_COMPOSER_GET_PRIVATE (composer);
3502 table = e_msg_composer_get_header_table (composer);
3503 editor = e_msg_composer_get_editor (composer);
3504 cnt_editor = e_html_editor_get_content_editor (editor);
3506 if (postto) {
3507 e_composer_header_table_set_post_to_list (table, postto);
3508 g_list_foreach (postto, (GFunc) g_free, NULL);
3509 g_list_free (postto);
3510 postto = NULL;
3513 if (override_identity_uid && *override_identity_uid) {
3514 identity_uid = (gchar *) override_identity_uid;
3515 } else {
3516 /* Restore the mail identity preference. */
3517 identity_uid = (gchar *) camel_medium_get_header (
3518 CAMEL_MEDIUM (message), "X-Evolution-Identity");
3519 if (!identity_uid) {
3520 /* for backward compatibility */
3521 identity_uid = (gchar *) camel_medium_get_header (
3522 CAMEL_MEDIUM (message), "X-Evolution-Account");
3524 if (!identity_uid) {
3525 source = em_utils_guess_mail_identity_with_recipients (
3526 e_shell_get_registry (e_msg_composer_get_shell (composer)), message, NULL, NULL, NULL, NULL);
3527 if (source)
3528 identity_uid = e_source_dup_uid (source);
3532 if (identity_uid != NULL && !source) {
3533 identity_uid = g_strstrip (g_strdup (identity_uid));
3534 source = e_composer_header_table_ref_source (
3535 table, identity_uid);
3538 auto_cc = g_hash_table_new_full (
3539 (GHashFunc) camel_strcase_hash,
3540 (GEqualFunc) camel_strcase_equal,
3541 (GDestroyNotify) g_free,
3542 (GDestroyNotify) NULL);
3544 auto_bcc = g_hash_table_new_full (
3545 (GHashFunc) camel_strcase_hash,
3546 (GEqualFunc) camel_strcase_equal,
3547 (GDestroyNotify) g_free,
3548 (GDestroyNotify) NULL);
3550 if (source != NULL) {
3551 composer_add_auto_recipients (source, "cc", auto_cc);
3552 composer_add_auto_recipients (source, "bcc", auto_bcc);
3555 to = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_TO);
3556 cc = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_CC);
3557 bcc = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_BCC);
3559 len = camel_address_length (CAMEL_ADDRESS (to));
3560 for (i = 0; i < len; i++) {
3561 const gchar *name, *addr;
3563 if (camel_internet_address_get (to, i, &name, &addr)) {
3564 EDestination *dest = e_destination_new ();
3565 e_destination_set_name (dest, name);
3566 e_destination_set_email (dest, addr);
3567 To = g_list_append (To, dest);
3571 Tov = destination_list_to_vector (To);
3572 g_list_free (To);
3574 len = camel_address_length (CAMEL_ADDRESS (cc));
3575 for (i = 0; i < len; i++) {
3576 const gchar *name, *addr;
3578 if (camel_internet_address_get (cc, i, &name, &addr)) {
3579 EDestination *dest = e_destination_new ();
3580 e_destination_set_name (dest, name);
3581 e_destination_set_email (dest, addr);
3583 if (g_hash_table_contains (auto_cc, addr))
3584 e_destination_set_auto_recipient (dest, TRUE);
3586 Cc = g_list_append (Cc, dest);
3590 Ccv = destination_list_to_vector (Cc);
3591 g_hash_table_destroy (auto_cc);
3592 g_list_free (Cc);
3594 len = camel_address_length (CAMEL_ADDRESS (bcc));
3595 for (i = 0; i < len; i++) {
3596 const gchar *name, *addr;
3598 if (camel_internet_address_get (bcc, i, &name, &addr)) {
3599 EDestination *dest = e_destination_new ();
3600 e_destination_set_name (dest, name);
3601 e_destination_set_email (dest, addr);
3603 if (g_hash_table_contains (auto_bcc, addr))
3604 e_destination_set_auto_recipient (dest, TRUE);
3606 Bcc = g_list_append (Bcc, dest);
3610 Bccv = destination_list_to_vector (Bcc);
3611 g_hash_table_destroy (auto_bcc);
3612 g_list_free (Bcc);
3614 if (source != NULL)
3615 g_object_unref (source);
3617 subject = camel_mime_message_get_subject (message);
3619 e_composer_header_table_set_destinations_to (table, Tov);
3620 e_composer_header_table_set_destinations_cc (table, Ccv);
3621 e_composer_header_table_set_destinations_bcc (table, Bccv);
3622 e_composer_header_table_set_subject (table, subject);
3624 e_destination_freev (Tov);
3625 e_destination_freev (Ccv);
3626 e_destination_freev (Bccv);
3628 from = camel_mime_message_get_from (message);
3629 if ((!override_identity_uid || !*override_identity_uid) && from) {
3630 const gchar *name = NULL, *address = NULL;
3632 if (camel_address_length (CAMEL_ADDRESS (from)) == 1 &&
3633 camel_internet_address_get (from, 0, &name, &address)) {
3634 EComposerFromHeader *header_from;
3635 const gchar *filled_name, *filled_address;
3637 /* First try whether such alias exists... */
3638 e_composer_header_table_set_identity_uid (table, identity_uid, name, address);
3640 header_from = E_COMPOSER_FROM_HEADER (e_composer_header_table_get_header (table, E_COMPOSER_HEADER_FROM));
3642 filled_name = e_composer_from_header_get_name (header_from);
3643 filled_address = e_composer_from_header_get_address (header_from);
3645 if (name && !*name)
3646 name = NULL;
3648 if (address && !*address)
3649 address = NULL;
3651 if (g_strcmp0 (filled_name, name) != 0 ||
3652 g_strcmp0 (filled_address, address) != 0) {
3653 /* ... and if not, then reset to the main identity address */
3654 e_composer_header_table_set_identity_uid (table, identity_uid, NULL, NULL);
3655 e_composer_from_header_set_name (header_from, name);
3656 e_composer_from_header_set_address (header_from, address);
3657 e_composer_from_header_set_override_visible (header_from, TRUE);
3659 } else {
3660 e_composer_header_table_set_identity_uid (table, identity_uid, NULL, NULL);
3662 } else {
3663 e_composer_header_table_set_identity_uid (table, identity_uid, override_alias_name, override_alias_address);
3666 g_free (identity_uid);
3668 /* Restore the format editing preference */
3669 format = camel_medium_get_header (
3670 CAMEL_MEDIUM (message), "X-Evolution-Format");
3672 composer_mode = camel_medium_get_header (
3673 CAMEL_MEDIUM (message), "X-Evolution-Composer-Mode");
3675 if (composer_mode && *composer_mode)
3676 is_message_from_draft = TRUE;
3678 if (format != NULL) {
3679 gchar **flags;
3681 while (*format && camel_mime_is_lwsp (*format))
3682 format++;
3684 flags = g_strsplit (format, ", ", 0);
3685 for (i = 0; flags[i]; i++) {
3686 if (g_ascii_strcasecmp (flags[i], "text/html") == 0 ||
3687 g_ascii_strcasecmp (flags[i], "text/plain") == 0) {
3688 gboolean html_mode;
3690 html_mode = composer_mode && !g_ascii_strcasecmp (composer_mode, "text/html");
3691 e_content_editor_set_html_mode (cnt_editor, html_mode);
3692 } else if (g_ascii_strcasecmp (flags[i], "pgp-sign") == 0) {
3693 action = GTK_TOGGLE_ACTION (ACTION (PGP_SIGN));
3694 gtk_toggle_action_set_active (action, TRUE);
3695 } else if (g_ascii_strcasecmp (flags[i], "pgp-encrypt") == 0) {
3696 action = GTK_TOGGLE_ACTION (ACTION (PGP_ENCRYPT));
3697 gtk_toggle_action_set_active (action, TRUE);
3698 } else if (g_ascii_strcasecmp (flags[i], "smime-sign") == 0) {
3699 action = GTK_TOGGLE_ACTION (ACTION (SMIME_SIGN));
3700 gtk_toggle_action_set_active (action, TRUE);
3701 } else if (g_ascii_strcasecmp (flags[i], "smime-encrypt") == 0) {
3702 action = GTK_TOGGLE_ACTION (ACTION (SMIME_ENCRYPT));
3703 gtk_toggle_action_set_active (action, TRUE);
3706 g_strfreev (flags);
3709 if (is_message_from_draft || (
3710 camel_medium_get_header (CAMEL_MEDIUM (message), "X-Evolution-Identity") &&
3711 camel_medium_get_header (CAMEL_MEDIUM (message), "X-Evolution-Transport"))) {
3712 const gchar *reply_to;
3714 reply_to = camel_medium_get_header (CAMEL_MEDIUM (message), "Reply-To");
3716 if (reply_to)
3717 e_composer_header_table_set_reply_to (table, reply_to);
3720 /* Remove any other X-Evolution-* headers that may have been set */
3721 camel_name_value_array_free (mail_tool_remove_xevolution_headers (message));
3723 /* Check for receipt request */
3724 if (camel_medium_get_header (
3725 CAMEL_MEDIUM (message), "Disposition-Notification-To")) {
3726 action = GTK_TOGGLE_ACTION (ACTION (REQUEST_READ_RECEIPT));
3727 gtk_toggle_action_set_active (action, TRUE);
3730 /* Check for mail priority */
3731 if (camel_medium_get_header (CAMEL_MEDIUM (message), "X-Priority")) {
3732 action = GTK_TOGGLE_ACTION (ACTION (PRIORITIZE_MESSAGE));
3733 gtk_toggle_action_set_active (action, TRUE);
3736 /* set extra headers */
3737 headers = camel_medium_get_headers (CAMEL_MEDIUM (message));
3738 jjlen = camel_name_value_array_get_length (headers);
3739 for (jj = 0; jj < jjlen; jj++) {
3740 const gchar *header_name = NULL, *header_value = NULL;
3742 if (!camel_name_value_array_get (headers, jj, &header_name, &header_value) || !header_name)
3743 continue;
3745 if (g_ascii_strcasecmp (header_name, "References") == 0 ||
3746 g_ascii_strcasecmp (header_name, "In-Reply-To") == 0) {
3747 g_ptr_array_add (
3748 composer->priv->extra_hdr_names,
3749 g_strdup (header_name));
3750 g_ptr_array_add (
3751 composer->priv->extra_hdr_values,
3752 camel_header_unfold (header_value));
3756 /* Restore the attachments and body text */
3757 content = camel_medium_get_content (CAMEL_MEDIUM (message));
3758 if (CAMEL_IS_MULTIPART (content)) {
3759 CamelMimePart *mime_part;
3760 CamelMultipart *multipart;
3762 multipart = CAMEL_MULTIPART (content);
3763 mime_part = CAMEL_MIME_PART (message);
3764 content_type = camel_mime_part_get_content_type (mime_part);
3766 if (CAMEL_IS_MULTIPART_SIGNED (content)) {
3767 /* Handle the signed content and configure the
3768 * composer to sign outgoing messages. */
3769 handle_multipart_signed (
3770 composer, multipart, keep_signature, cancellable, 0);
3772 } else if (CAMEL_IS_MULTIPART_ENCRYPTED (content)) {
3773 /* Decrypt the encrypted content and configure the
3774 * composer to encrypt outgoing messages. */
3775 handle_multipart_encrypted (
3776 composer, mime_part, keep_signature, cancellable, 0);
3778 } else if (camel_content_type_is (
3779 content_type, "multipart", "alternative")) {
3780 /* This contains the text/plain and text/html
3781 * versions of the message body. */
3782 handle_multipart_alternative (
3783 composer, multipart, keep_signature, cancellable, 0);
3785 } else {
3786 /* There must be attachments... */
3787 handle_multipart (
3788 composer, multipart, keep_signature, cancellable, 0);
3790 } else {
3791 CamelMimePart *mime_part;
3792 gboolean is_html = FALSE;
3793 gchar *html = NULL;
3794 gssize length = 0;
3796 mime_part = CAMEL_MIME_PART (message);
3797 content_type = camel_mime_part_get_content_type (mime_part);
3798 is_html = camel_content_type_is (content_type, "text", "html");
3800 if (content_type != NULL && (
3801 camel_content_type_is (
3802 content_type, "application", "x-pkcs7-mime") ||
3803 camel_content_type_is (
3804 content_type, "application", "pkcs7-mime"))) {
3806 gtk_toggle_action_set_active (
3807 GTK_TOGGLE_ACTION (
3808 ACTION (SMIME_ENCRYPT)), TRUE);
3811 /* If we are opening message from Drafts */
3812 if (is_message_from_draft) {
3813 /* Extract the body */
3814 CamelDataWrapper *dw;
3816 dw = camel_medium_get_content ((CamelMedium *) mime_part);
3817 if (dw) {
3818 CamelStream *mem = camel_stream_mem_new ();
3819 GByteArray *bytes;
3821 camel_data_wrapper_decode_to_stream_sync (dw, mem, cancellable, NULL);
3822 camel_stream_close (mem, cancellable, NULL);
3824 bytes = camel_stream_mem_get_byte_array (CAMEL_STREAM_MEM (mem));
3825 if (bytes && bytes->len)
3826 html = g_strndup ((const gchar *) bytes->data, bytes->len);
3828 g_object_unref (mem);
3830 } else {
3831 is_html = TRUE;
3832 html = emcu_part_to_html (
3833 composer, CAMEL_MIME_PART (message),
3834 &length, keep_signature, cancellable);
3836 e_msg_composer_set_pending_body (composer, html, length, is_html);
3839 priv->set_signature_from_message = TRUE;
3841 /* We wait until now to set the body text because we need to
3842 * ensure that the attachment bar has all the attachments before
3843 * we request them. */
3844 e_msg_composer_flush_pending_body (composer);
3846 set_signature_gui (composer);
3850 * e_msg_composer_setup_redirect:
3851 * @composer: an #EMsgComposer
3852 * @message: The message to use as the source
3853 * @identity_uid: (nullable): an identity UID to use, if any
3854 * @alias_name: (nullable): an alias name to use together with the identity_uid, or %NULL
3855 * @alias_address: (nullable): an alias address to use together with the identity_uid, or %NULL
3856 * @cancellable: an optional #GCancellable
3858 * Sets up the message @composer as a redirect of the @message.
3860 * Since: 3.22
3862 void
3863 e_msg_composer_setup_redirect (EMsgComposer *composer,
3864 CamelMimeMessage *message,
3865 const gchar *identity_uid,
3866 const gchar *alias_name,
3867 const gchar *alias_address,
3868 GCancellable *cancellable)
3870 EComposerHeaderTable *table;
3871 EHTMLEditor *editor;
3872 EContentEditor *cnt_editor;
3873 const gchar *subject;
3875 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
3876 g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
3878 e_msg_composer_setup_with_message (composer, message, TRUE, identity_uid, alias_name, alias_address, cancellable);
3880 table = e_msg_composer_get_header_table (composer);
3881 subject = camel_mime_message_get_subject (message);
3883 composer->priv->redirect = message;
3884 g_object_ref (message);
3886 e_composer_header_table_set_subject (table, subject);
3888 editor = e_msg_composer_get_editor (composer);
3889 cnt_editor = e_html_editor_get_content_editor (editor);
3890 e_content_editor_set_editable (cnt_editor, FALSE);
3894 * e_msg_composer_ref_session:
3895 * @composer: an #EMsgComposer
3897 * Returns the mail module's global #CamelSession instance. Calling
3898 * this function will load the mail module if it isn't already loaded.
3900 * The returned #CamelSession is referenced for thread-safety and must
3901 * be unreferenced with g_object_unref() when finished with it.
3903 * Returns: the mail module's #CamelSession
3905 CamelSession *
3906 e_msg_composer_ref_session (EMsgComposer *composer)
3908 EShell *shell;
3909 EShellBackend *shell_backend;
3910 CamelSession *session = NULL;
3912 g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
3914 shell = e_msg_composer_get_shell (composer);
3915 shell_backend = e_shell_get_backend_by_name (shell, "mail");
3917 g_object_get (shell_backend, "session", &session, NULL);
3918 g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
3920 return session;
3924 * e_msg_composer_get_shell:
3925 * @composer: an #EMsgComposer
3927 * Returns the #EShell that was passed to e_msg_composer_new().
3929 * Returns: the #EShell
3931 EShell *
3932 e_msg_composer_get_shell (EMsgComposer *composer)
3934 g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
3936 return E_SHELL (composer->priv->shell);
3939 static void
3940 msg_composer_send_cb (EMsgComposer *composer,
3941 GAsyncResult *result,
3942 AsyncContext *context)
3944 CamelMimeMessage *message;
3945 EAlertSink *alert_sink;
3946 EHTMLEditor *editor;
3947 EContentEditor *cnt_editor;
3948 GError *error = NULL;
3950 alert_sink = e_activity_get_alert_sink (context->activity);
3952 message = e_msg_composer_get_message_finish (composer, result, &error);
3954 if (e_activity_handle_cancellation (context->activity, error)) {
3955 g_warn_if_fail (message == NULL);
3956 async_context_free (context);
3957 g_error_free (error);
3959 gtk_window_present (GTK_WINDOW (composer));
3960 return;
3963 if (error != NULL) {
3964 g_warn_if_fail (message == NULL);
3965 e_alert_submit (
3966 alert_sink,
3967 "mail-composer:no-build-message",
3968 error->message, NULL);
3969 async_context_free (context);
3970 g_error_free (error);
3972 gtk_window_present (GTK_WINDOW (composer));
3973 return;
3976 g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
3978 /* The callback can set editor 'changed' if anything failed. */
3979 editor = e_msg_composer_get_editor (composer);
3980 cnt_editor = e_html_editor_get_content_editor (editor);
3981 e_content_editor_set_changed (cnt_editor, TRUE);
3983 composer->priv->is_sending_message = TRUE;
3985 g_signal_emit (
3986 composer, signals[SEND], 0,
3987 message, context->activity);
3989 composer->priv->is_sending_message = FALSE;
3991 g_object_unref (message);
3993 async_context_free (context);
3997 * e_msg_composer_send:
3998 * @composer: an #EMsgComposer
4000 * Send the message in @composer.
4002 void
4003 e_msg_composer_send (EMsgComposer *composer)
4005 EHTMLEditor *editor;
4006 AsyncContext *context;
4007 GCancellable *cancellable;
4008 gboolean proceed_with_send = TRUE;
4010 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
4012 /* This gives the user a chance to abort the send. */
4013 g_signal_emit (composer, signals[PRESEND], 0, &proceed_with_send);
4015 if (!proceed_with_send) {
4016 gtk_window_present (GTK_WINDOW (composer));
4017 return;
4020 editor = e_msg_composer_get_editor (composer);
4022 context = g_slice_new0 (AsyncContext);
4023 context->activity = e_html_editor_new_activity (editor);
4025 cancellable = e_activity_get_cancellable (context->activity);
4027 e_msg_composer_get_message (
4028 composer, G_PRIORITY_DEFAULT, cancellable,
4029 (GAsyncReadyCallback) msg_composer_send_cb,
4030 context);
4033 static void
4034 msg_composer_save_to_drafts_done_cb (gpointer user_data,
4035 GObject *gone_object)
4037 EMsgComposer *composer = user_data;
4038 EHTMLEditor *editor;
4039 EContentEditor *cnt_editor;
4041 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
4043 editor = e_msg_composer_get_editor (composer);
4044 cnt_editor = e_html_editor_get_content_editor (editor);
4046 if (e_msg_composer_is_exiting (composer) &&
4047 !e_content_editor_get_changed (cnt_editor)) {
4048 e_composer_emit_before_destroy (composer);
4049 gtk_widget_destroy (GTK_WIDGET (composer));
4050 } else if (e_msg_composer_is_exiting (composer)) {
4051 gtk_widget_set_sensitive (GTK_WIDGET (composer), TRUE);
4052 gtk_window_present (GTK_WINDOW (composer));
4053 composer->priv->application_exiting = FALSE;
4057 static void
4058 msg_composer_save_to_drafts_cb (EMsgComposer *composer,
4059 GAsyncResult *result,
4060 AsyncContext *context)
4062 CamelMimeMessage *message;
4063 EAlertSink *alert_sink;
4064 EHTMLEditor *editor;
4065 EContentEditor *cnt_editor;
4066 GError *error = NULL;
4068 alert_sink = e_activity_get_alert_sink (context->activity);
4070 message = e_msg_composer_get_message_draft_finish (
4071 composer, result, &error);
4073 if (e_activity_handle_cancellation (context->activity, error)) {
4074 g_warn_if_fail (message == NULL);
4075 async_context_free (context);
4076 g_error_free (error);
4078 if (e_msg_composer_is_exiting (composer)) {
4079 gtk_window_present (GTK_WINDOW (composer));
4080 composer->priv->application_exiting = FALSE;
4083 return;
4086 if (error != NULL) {
4087 g_warn_if_fail (message == NULL);
4088 e_alert_submit (
4089 alert_sink,
4090 "mail-composer:no-build-message",
4091 error->message, NULL);
4092 async_context_free (context);
4093 g_error_free (error);
4095 if (e_msg_composer_is_exiting (composer)) {
4096 gtk_window_present (GTK_WINDOW (composer));
4097 composer->priv->application_exiting = FALSE;
4100 return;
4103 g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
4105 /* The callback can set editor 'changed' if anything failed. */
4106 editor = e_msg_composer_get_editor (composer);
4107 cnt_editor = e_html_editor_get_content_editor (editor);
4108 e_content_editor_set_changed (cnt_editor, TRUE);
4110 g_signal_emit (
4111 composer, signals[SAVE_TO_DRAFTS],
4112 0, message, context->activity);
4114 g_object_unref (message);
4116 if (e_msg_composer_is_exiting (composer))
4117 g_object_weak_ref (
4118 G_OBJECT (context->activity),
4119 msg_composer_save_to_drafts_done_cb, composer);
4121 async_context_free (context);
4125 * e_msg_composer_save_to_drafts:
4126 * @composer: an #EMsgComposer
4128 * Save the message in @composer to the selected account's Drafts folder.
4130 void
4131 e_msg_composer_save_to_drafts (EMsgComposer *composer)
4133 EHTMLEditor *editor;
4134 AsyncContext *context;
4135 GCancellable *cancellable;
4137 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
4139 editor = e_msg_composer_get_editor (composer);
4141 context = g_slice_new0 (AsyncContext);
4142 context->activity = e_html_editor_new_activity (editor);
4144 cancellable = e_activity_get_cancellable (context->activity);
4146 e_msg_composer_get_message_draft (
4147 composer, G_PRIORITY_DEFAULT, cancellable,
4148 (GAsyncReadyCallback) msg_composer_save_to_drafts_cb,
4149 context);
4152 static void
4153 msg_composer_save_to_outbox_cb (EMsgComposer *composer,
4154 GAsyncResult *result,
4155 AsyncContext *context)
4157 CamelMimeMessage *message;
4158 EAlertSink *alert_sink;
4159 EHTMLEditor *editor;
4160 EContentEditor *cnt_editor;
4161 GError *error = NULL;
4163 alert_sink = e_activity_get_alert_sink (context->activity);
4165 message = e_msg_composer_get_message_finish (composer, result, &error);
4167 if (e_activity_handle_cancellation (context->activity, error)) {
4168 g_warn_if_fail (message == NULL);
4169 async_context_free (context);
4170 g_error_free (error);
4171 return;
4174 if (error != NULL) {
4175 g_warn_if_fail (message == NULL);
4176 e_alert_submit (
4177 alert_sink,
4178 "mail-composer:no-build-message",
4179 error->message, NULL);
4180 async_context_free (context);
4181 g_error_free (error);
4182 return;
4185 g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
4187 g_signal_emit (
4188 composer, signals[SAVE_TO_OUTBOX],
4189 0, message, context->activity);
4191 g_object_unref (message);
4193 async_context_free (context);
4195 editor = e_msg_composer_get_editor (composer);
4196 cnt_editor = e_html_editor_get_content_editor (editor);
4197 e_content_editor_set_changed (cnt_editor, TRUE);
4201 * e_msg_composer_save_to_outbox:
4202 * @composer: an #EMsgComposer
4204 * Save the message in @composer to the local Outbox folder.
4206 void
4207 e_msg_composer_save_to_outbox (EMsgComposer *composer)
4209 EHTMLEditor *editor;
4210 AsyncContext *context;
4211 GCancellable *cancellable;
4213 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
4215 if (!composer->priv->is_sending_message) {
4216 gboolean proceed_with_save = TRUE;
4218 /* This gives the user a chance to abort the save. */
4219 g_signal_emit (composer, signals[PRESEND], 0, &proceed_with_save);
4221 if (!proceed_with_save)
4222 return;
4225 editor = e_msg_composer_get_editor (composer);
4227 context = g_slice_new0 (AsyncContext);
4228 context->activity = e_html_editor_new_activity (editor);
4230 cancellable = e_activity_get_cancellable (context->activity);
4232 e_msg_composer_get_message (
4233 composer, G_PRIORITY_DEFAULT, cancellable,
4234 (GAsyncReadyCallback) msg_composer_save_to_outbox_cb,
4235 context);
4238 static void
4239 msg_composer_print_cb (EMsgComposer *composer,
4240 GAsyncResult *result,
4241 AsyncContext *context)
4243 CamelMimeMessage *message;
4244 EAlertSink *alert_sink;
4245 GError *error = NULL;
4247 alert_sink = e_activity_get_alert_sink (context->activity);
4249 message = e_msg_composer_get_message_print_finish (
4250 composer, result, &error);
4252 if (e_activity_handle_cancellation (context->activity, error)) {
4253 g_warn_if_fail (message == NULL);
4254 async_context_free (context);
4255 g_error_free (error);
4256 return;
4259 if (error != NULL) {
4260 g_warn_if_fail (message == NULL);
4261 async_context_free (context);
4262 e_alert_submit (
4263 alert_sink,
4264 "mail-composer:no-build-message",
4265 error->message, NULL);
4266 g_error_free (error);
4267 return;
4270 g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
4272 g_signal_emit (
4273 composer, signals[PRINT], 0,
4274 context->print_action, message, context->activity);
4276 g_object_unref (message);
4278 async_context_free (context);
4282 * e_msg_composer_print:
4283 * @composer: an #EMsgComposer
4284 * @print_action: the print action to start
4286 * Print the message in @composer.
4288 void
4289 e_msg_composer_print (EMsgComposer *composer,
4290 GtkPrintOperationAction print_action)
4292 EHTMLEditor *editor;
4293 AsyncContext *context;
4294 GCancellable *cancellable;
4296 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
4298 editor = e_msg_composer_get_editor (composer);
4300 context = g_slice_new0 (AsyncContext);
4301 context->activity = e_html_editor_new_activity (editor);
4302 context->print_action = print_action;
4304 cancellable = e_activity_get_cancellable (context->activity);
4306 e_msg_composer_get_message_print (
4307 composer, G_PRIORITY_DEFAULT, cancellable,
4308 (GAsyncReadyCallback) msg_composer_print_cb,
4309 context);
4312 static GList *
4313 add_recipients (GList *list,
4314 const gchar *recips)
4316 CamelInternetAddress *cia;
4317 const gchar *name, *addr;
4318 gint num, i;
4320 cia = camel_internet_address_new ();
4321 num = camel_address_decode (CAMEL_ADDRESS (cia), recips);
4323 for (i = 0; i < num; i++) {
4324 if (camel_internet_address_get (cia, i, &name, &addr)) {
4325 EDestination *dest = e_destination_new ();
4326 e_destination_set_name (dest, name);
4327 e_destination_set_email (dest, addr);
4329 list = g_list_append (list, dest);
4333 g_object_unref (cia);
4335 return list;
4338 static gboolean
4339 list_contains_addr (const GList *list,
4340 EDestination *dest)
4342 g_return_val_if_fail (dest != NULL, FALSE);
4344 while (list != NULL) {
4345 if (e_destination_equal (dest, list->data))
4346 return TRUE;
4348 list = list->next;
4351 return FALSE;
4354 static void
4355 merge_cc_bcc (EDestination **addrv,
4356 GList **merge_into,
4357 const GList *to,
4358 const GList *cc,
4359 const GList *bcc)
4361 gint ii;
4363 for (ii = 0; addrv && addrv[ii]; ii++) {
4364 if (!list_contains_addr (to, addrv[ii]) &&
4365 !list_contains_addr (cc, addrv[ii]) &&
4366 !list_contains_addr (bcc, addrv[ii])) {
4367 *merge_into = g_list_append (
4368 *merge_into, g_object_ref (addrv[ii]));
4373 static void
4374 merge_always_cc_and_bcc (EComposerHeaderTable *table,
4375 const GList *to,
4376 GList **cc,
4377 GList **bcc)
4379 EDestination **addrv;
4381 g_return_if_fail (table != NULL);
4382 g_return_if_fail (cc != NULL);
4383 g_return_if_fail (bcc != NULL);
4385 addrv = e_composer_header_table_get_destinations_cc (table);
4386 merge_cc_bcc (addrv, cc, to, *cc, *bcc);
4387 e_destination_freev (addrv);
4389 addrv = e_composer_header_table_get_destinations_bcc (table);
4390 merge_cc_bcc (addrv, bcc, to, *cc, *bcc);
4391 e_destination_freev (addrv);
4394 static const gchar *blacklist[] = { ".", "etc", ".." };
4396 static gboolean
4397 file_is_blacklisted (const gchar *argument)
4399 GFile *file;
4400 gboolean blacklisted = FALSE;
4401 guint ii, jj, n_parts;
4402 gchar *filename;
4403 gchar **parts;
4405 /* The "attach" argument may be a URI or local path. Normalize
4406 * it to a local path if we can. We only blacklist local files. */
4407 file = g_file_new_for_commandline_arg (argument);
4408 filename = g_file_get_path (file);
4409 g_object_unref (file);
4411 if (filename == NULL)
4412 return FALSE;
4414 parts = g_strsplit (filename, G_DIR_SEPARATOR_S, -1);
4415 n_parts = g_strv_length (parts);
4417 for (ii = 0; ii < G_N_ELEMENTS (blacklist); ii++) {
4418 for (jj = 0; jj < n_parts; jj++) {
4419 if (g_str_has_prefix (parts[jj], blacklist[ii])) {
4420 blacklisted = TRUE;
4421 break;
4426 if (blacklisted) {
4427 gchar *base_dir;
4429 /* Don't blacklist files in trusted base directories. */
4430 if (g_str_has_prefix (filename, g_get_user_data_dir ()))
4431 blacklisted = FALSE;
4432 if (g_str_has_prefix (filename, g_get_user_cache_dir ()))
4433 blacklisted = FALSE;
4434 if (g_str_has_prefix (filename, g_get_user_config_dir ()))
4435 blacklisted = FALSE;
4437 /* Apparently KDE still uses ~/.kde heavily, and some
4438 * distributions use ~/.kde4 to distinguish KDE4 data
4439 * from KDE3 data. Trust these directories as well. */
4441 base_dir = g_build_filename (g_get_home_dir (), ".kde", NULL);
4442 if (g_str_has_prefix (filename, base_dir))
4443 blacklisted = FALSE;
4444 g_free (base_dir);
4446 base_dir = g_build_filename (g_get_home_dir (), ".kde4", NULL);
4447 if (g_str_has_prefix (filename, base_dir))
4448 blacklisted = FALSE;
4449 g_free (base_dir);
4452 g_strfreev (parts);
4453 g_free (filename);
4455 return blacklisted;
4458 static void
4459 handle_mailto (EMsgComposer *composer,
4460 const gchar *mailto)
4462 EAttachmentView *view;
4463 EAttachmentStore *store;
4464 EComposerHeaderTable *table;
4465 GList *to = NULL, *cc = NULL, *bcc = NULL;
4466 EDestination **tov, **ccv, **bccv;
4467 gchar *subject = NULL, *body = NULL;
4468 gchar *header, *content, *buf;
4469 gsize nread, nwritten;
4470 const gchar *p;
4471 gint len, clen;
4473 table = e_msg_composer_get_header_table (composer);
4474 view = e_msg_composer_get_attachment_view (composer);
4475 store = e_attachment_view_get_store (view);
4477 buf = g_strdup (mailto);
4479 /* Parse recipients (everything after ':' and up to three leading forward slashes until '?' or eos). */
4480 p = buf + 7;
4482 while (*p == '/' && p - buf < 10)
4483 p++;
4485 len = strcspn (p, "?");
4486 if (len) {
4487 content = g_strndup (p, len);
4488 camel_url_decode (content);
4489 to = add_recipients (to, content);
4490 g_free (content);
4493 p += len;
4494 if (*p == '?') {
4495 p++;
4497 while (*p) {
4498 len = strcspn (p, "=&");
4500 /* If it's malformed, give up. */
4501 if (p[len] != '=')
4502 break;
4504 header = (gchar *) p;
4505 header[len] = '\0';
4506 p += len + 1;
4508 clen = strcspn (p, "&");
4510 content = g_strndup (p, clen);
4512 if (!g_ascii_strcasecmp (header, "to")) {
4513 camel_url_decode (content);
4514 to = add_recipients (to, content);
4515 } else if (!g_ascii_strcasecmp (header, "cc")) {
4516 camel_url_decode (content);
4517 cc = add_recipients (cc, content);
4518 } else if (!g_ascii_strcasecmp (header, "bcc")) {
4519 camel_url_decode (content);
4520 bcc = add_recipients (bcc, content);
4521 } else if (!g_ascii_strcasecmp (header, "subject")) {
4522 g_free (subject);
4523 camel_url_decode (content);
4524 if (g_utf8_validate (content, -1, NULL)) {
4525 subject = content;
4526 content = NULL;
4527 } else {
4528 subject = g_locale_to_utf8 (
4529 content, clen, &nread,
4530 &nwritten, NULL);
4531 if (subject) {
4532 subject = g_realloc (subject, nwritten + 1);
4533 subject[nwritten] = '\0';
4536 } else if (!g_ascii_strcasecmp (header, "body")) {
4537 g_free (body);
4538 camel_url_decode (content);
4539 if (g_utf8_validate (content, -1, NULL)) {
4540 body = content;
4541 content = NULL;
4542 } else {
4543 body = g_locale_to_utf8 (
4544 content, clen, &nread,
4545 &nwritten, NULL);
4546 if (body) {
4547 body = g_realloc (body, nwritten + 1);
4548 body[nwritten] = '\0';
4551 } else if (!g_ascii_strcasecmp (header, "attach") ||
4552 !g_ascii_strcasecmp (header, "attachment")) {
4553 EAttachment *attachment;
4555 camel_url_decode (content);
4556 if (file_is_blacklisted (content))
4557 e_alert_submit (
4558 E_ALERT_SINK (e_msg_composer_get_editor (composer)),
4559 "mail:blacklisted-file",
4560 content, NULL);
4561 if (g_ascii_strncasecmp (content, "file:", 5) == 0)
4562 attachment = e_attachment_new_for_uri (content);
4563 else
4564 attachment = e_attachment_new_for_path (content);
4565 e_attachment_store_add_attachment (store, attachment);
4566 e_attachment_load_async (
4567 attachment, (GAsyncReadyCallback)
4568 e_attachment_load_handle_error, composer);
4569 g_object_unref (attachment);
4570 } else if (!g_ascii_strcasecmp (header, "from")) {
4571 /* Ignore */
4572 } else if (!g_ascii_strcasecmp (header, "reply-to")) {
4573 /* ignore */
4574 } else {
4575 /* add an arbitrary header? */
4576 camel_url_decode (content);
4577 e_msg_composer_add_header (composer, header, content);
4580 g_free (content);
4582 p += clen;
4583 if (*p == '&') {
4584 p++;
4585 if (!g_ascii_strncasecmp (p, "amp;", 4))
4586 p += 4;
4591 g_free (buf);
4593 merge_always_cc_and_bcc (table, to, &cc, &bcc);
4595 tov = destination_list_to_vector (to);
4596 ccv = destination_list_to_vector (cc);
4597 bccv = destination_list_to_vector (bcc);
4599 g_list_free (to);
4600 g_list_free (cc);
4601 g_list_free (bcc);
4603 e_composer_header_table_set_destinations_to (table, tov);
4604 e_composer_header_table_set_destinations_cc (table, ccv);
4605 e_composer_header_table_set_destinations_bcc (table, bccv);
4607 e_destination_freev (tov);
4608 e_destination_freev (ccv);
4609 e_destination_freev (bccv);
4611 e_composer_header_table_set_subject (table, subject);
4612 g_free (subject);
4614 if (body) {
4615 GSettings *settings;
4616 gchar *html_body;
4617 guint32 flags = 0;
4619 settings = e_util_ref_settings ("org.gnome.evolution.mail");
4621 if (g_settings_get_boolean (settings, "composer-magic-links")) {
4622 flags |= CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS | CAMEL_MIME_FILTER_TOHTML_CONVERT_ADDRESSES;
4625 if (g_settings_get_boolean (settings, "composer-mailto-body-in-pre"))
4626 flags |= CAMEL_MIME_FILTER_TOHTML_PRE;
4627 else
4628 flags |= CAMEL_MIME_FILTER_TOHTML_CONVERT_NL | CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES;
4630 g_clear_object (&settings);
4632 html_body = camel_text_to_html (body, flags, 0);
4633 set_editor_text (composer, html_body, TRUE, TRUE);
4634 g_free (html_body);
4639 * e_msg_composer_setup_from_url:
4640 * @composer: an #EMsgComposer
4641 * @url: a mailto URL
4643 * Sets up the message @composer content as defined by the provided URL.
4645 * Since: 3.22
4647 void
4648 e_msg_composer_setup_from_url (EMsgComposer *composer,
4649 const gchar *url)
4651 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
4652 g_return_if_fail (g_ascii_strncasecmp (url, "mailto:", 7) == 0);
4654 handle_mailto (composer, url);
4658 * e_msg_composer_set_body_text:
4659 * @composer: a composer object
4660 * @text: the HTML text to initialize the editor with
4661 * @update_signature: whether update signature in the text after setting it;
4662 * Might be usually called with TRUE.
4664 * Loads the given HTML text into the editor.
4666 void
4667 e_msg_composer_set_body_text (EMsgComposer *composer,
4668 const gchar *text,
4669 gboolean update_signature)
4671 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
4672 g_return_if_fail (text != NULL);
4674 /* Every usage of e_msg_composer_set_body_text is called with HTML text */
4675 set_editor_text (composer, text, TRUE, update_signature);
4679 * e_msg_composer_set_body:
4680 * @composer: a composer object
4681 * @body: the data to initialize the composer with
4682 * @mime_type: the MIME type of data
4684 * Loads the given data into the composer as the message body.
4686 void
4687 e_msg_composer_set_body (EMsgComposer *composer,
4688 const gchar *body,
4689 const gchar *mime_type)
4691 EMsgComposerPrivate *priv = composer->priv;
4692 EComposerHeaderTable *table;
4693 EHTMLEditor *editor;
4694 EContentEditor *cnt_editor;
4695 ESource *source;
4696 gchar *identity_uid;
4697 const gchar *content;
4699 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
4701 editor = e_msg_composer_get_editor (composer);
4702 cnt_editor = e_html_editor_get_content_editor (editor);
4703 table = e_msg_composer_get_header_table (composer);
4705 /* Disable signature */
4706 priv->disable_signature = TRUE;
4708 identity_uid = e_composer_header_table_dup_identity_uid (table, NULL, NULL);
4709 source = e_composer_header_table_ref_source (table, identity_uid);
4711 content = _("The composer contains a non-text message body, which cannot be edited.");
4712 set_editor_text (composer, content, TRUE, FALSE);
4714 e_content_editor_set_html_mode (cnt_editor, FALSE);
4715 e_content_editor_set_editable (cnt_editor, FALSE);
4717 g_free (priv->mime_body);
4718 priv->mime_body = g_strdup (body);
4719 g_free (priv->mime_type);
4720 priv->mime_type = g_strdup (mime_type);
4722 if (g_ascii_strncasecmp (priv->mime_type, "text/calendar", 13) == 0) {
4723 ESourceMailComposition *extension;
4724 const gchar *extension_name;
4726 extension_name = E_SOURCE_EXTENSION_MAIL_COMPOSITION;
4727 extension = e_source_get_extension (source, extension_name);
4729 if (!e_source_mail_composition_get_sign_imip (extension)) {
4730 GtkToggleAction *action;
4732 action = GTK_TOGGLE_ACTION (ACTION (PGP_SIGN));
4733 gtk_toggle_action_set_active (action, FALSE);
4735 action = GTK_TOGGLE_ACTION (ACTION (SMIME_SIGN));
4736 gtk_toggle_action_set_active (action, FALSE);
4740 g_object_unref (source);
4741 g_free (identity_uid);
4745 * e_msg_composer_add_header:
4746 * @composer: an #EMsgComposer
4747 * @name: the header's name
4748 * @value: the header's value
4750 * Adds a new custom header created from @name and @value. The header
4751 * is not shown in the user interface but will be added to the resulting
4752 * MIME message when sending or saving.
4754 void
4755 e_msg_composer_add_header (EMsgComposer *composer,
4756 const gchar *name,
4757 const gchar *value)
4759 EMsgComposerPrivate *priv;
4761 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
4762 g_return_if_fail (name != NULL);
4763 g_return_if_fail (value != NULL);
4765 priv = composer->priv;
4767 g_ptr_array_add (priv->extra_hdr_names, g_strdup (name));
4768 g_ptr_array_add (priv->extra_hdr_values, g_strdup (value));
4772 * e_msg_composer_set_header:
4773 * @composer: an #EMsgComposer
4774 * @name: the header's name
4775 * @value: the header's value
4777 * Replaces all custom headers matching @name that were added with
4778 * e_msg_composer_add_header() or e_msg_composer_set_header(), with
4779 * a new custom header created from @name and @value. The header is
4780 * not shown in the user interface but will be added to the resulting
4781 * MIME message when sending or saving.
4783 void
4784 e_msg_composer_set_header (EMsgComposer *composer,
4785 const gchar *name,
4786 const gchar *value)
4788 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
4789 g_return_if_fail (name != NULL);
4790 g_return_if_fail (value != NULL);
4792 e_msg_composer_remove_header (composer, name);
4793 e_msg_composer_add_header (composer, name, value);
4797 * e_msg_composer_remove_header:
4798 * @composer: an #EMsgComposer
4799 * @name: the header's name
4801 * Removes all custom headers matching @name that were added with
4802 * e_msg_composer_add_header() or e_msg_composer_set_header().
4804 void
4805 e_msg_composer_remove_header (EMsgComposer *composer,
4806 const gchar *name)
4808 EMsgComposerPrivate *priv;
4809 guint ii;
4811 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
4812 g_return_if_fail (name != NULL);
4814 priv = composer->priv;
4816 for (ii = 0; ii < priv->extra_hdr_names->len; ii++) {
4817 if (g_strcmp0 (priv->extra_hdr_names->pdata[ii], name) == 0) {
4818 g_free (priv->extra_hdr_names->pdata[ii]);
4819 g_free (priv->extra_hdr_values->pdata[ii]);
4820 g_ptr_array_remove_index (priv->extra_hdr_names, ii);
4821 g_ptr_array_remove_index (priv->extra_hdr_values, ii);
4827 * e_msg_composer_get_header:
4828 * @composer: an #EMsgComposer
4829 * @name: the header's name
4830 * @index: index of the header, 0-based
4832 * Returns header value of the header named @name previously added
4833 * by e_msg_composer_add_header() or set by e_msg_composer_set_header().
4834 * The @index is which header index to return. Returns %NULL on error
4835 * or when the given index of the header couldn't be found.
4837 * Returns: stored header value or NULL, if couldn't be found.
4839 * Since: 3.20
4841 const gchar *
4842 e_msg_composer_get_header (EMsgComposer *composer,
4843 const gchar *name,
4844 gint index)
4846 EMsgComposerPrivate *priv;
4847 guint ii;
4849 g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
4850 g_return_val_if_fail (name != NULL, NULL);
4852 priv = composer->priv;
4854 for (ii = 0; ii < priv->extra_hdr_names->len; ii++) {
4855 if (g_strcmp0 (priv->extra_hdr_names->pdata[ii], name) == 0) {
4856 if (index <= 0)
4857 return priv->extra_hdr_values->pdata[ii];
4859 index--;
4863 return NULL;
4867 * e_msg_composer_set_draft_headers:
4868 * @composer: an #EMsgComposer
4869 * @folder_uri: folder URI of the last saved draft
4870 * @message_uid: message UID of the last saved draft
4872 * Add special X-Evolution-Draft headers to remember the most recently
4873 * saved draft message, even across Evolution sessions. These headers
4874 * can be used to mark the draft message for deletion after saving a
4875 * newer draft or sending the composed message.
4877 void
4878 e_msg_composer_set_draft_headers (EMsgComposer *composer,
4879 const gchar *folder_uri,
4880 const gchar *message_uid)
4882 const gchar *header_name;
4884 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
4885 g_return_if_fail (folder_uri != NULL);
4886 g_return_if_fail (message_uid != NULL);
4888 header_name = "X-Evolution-Draft-Folder";
4889 e_msg_composer_set_header (composer, header_name, folder_uri);
4891 header_name = "X-Evolution-Draft-Message";
4892 e_msg_composer_set_header (composer, header_name, message_uid);
4896 * e_msg_composer_set_source_headers:
4897 * @composer: an #EMsgComposer
4898 * @folder_uri: folder URI of the source message
4899 * @message_uid: message UID of the source message
4900 * @flags: flags to set on the source message after sending
4902 * Add special X-Evolution-Source headers to remember the message being
4903 * forwarded or replied to, even across Evolution sessions. These headers
4904 * can be used to set appropriate flags on the source message after sending
4905 * the composed message.
4907 void
4908 e_msg_composer_set_source_headers (EMsgComposer *composer,
4909 const gchar *folder_uri,
4910 const gchar *message_uid,
4911 CamelMessageFlags flags)
4913 GString *buffer;
4914 const gchar *header_name;
4916 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
4917 g_return_if_fail (folder_uri != NULL);
4918 g_return_if_fail (message_uid != NULL);
4920 buffer = g_string_sized_new (32);
4922 if (flags & CAMEL_MESSAGE_ANSWERED)
4923 g_string_append (buffer, "ANSWERED ");
4924 if (flags & CAMEL_MESSAGE_ANSWERED_ALL)
4925 g_string_append (buffer, "ANSWERED_ALL ");
4926 if (flags & CAMEL_MESSAGE_FORWARDED)
4927 g_string_append (buffer, "FORWARDED ");
4928 if (flags & CAMEL_MESSAGE_SEEN)
4929 g_string_append (buffer, "SEEN ");
4931 header_name = "X-Evolution-Source-Folder";
4932 e_msg_composer_set_header (composer, header_name, folder_uri);
4934 header_name = "X-Evolution-Source-Message";
4935 e_msg_composer_set_header (composer, header_name, message_uid);
4937 header_name = "X-Evolution-Source-Flags";
4938 e_msg_composer_set_header (composer, header_name, buffer->str);
4940 g_string_free (buffer, TRUE);
4944 * e_msg_composer_attach:
4945 * @composer: a composer object
4946 * @mime_part: the #CamelMimePart to attach
4948 * Attaches @attachment to the message being composed in the composer.
4950 void
4951 e_msg_composer_attach (EMsgComposer *composer,
4952 CamelMimePart *mime_part)
4954 EAttachmentView *view;
4955 EAttachmentStore *store;
4956 EAttachment *attachment;
4958 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
4959 g_return_if_fail (CAMEL_IS_MIME_PART (mime_part));
4961 view = e_msg_composer_get_attachment_view (composer);
4962 store = e_attachment_view_get_store (view);
4964 attachment = e_attachment_new ();
4965 e_attachment_set_mime_part (attachment, mime_part);
4966 e_attachment_store_add_attachment (store, attachment);
4967 e_attachment_load_async (
4968 attachment, (GAsyncReadyCallback)
4969 e_attachment_load_handle_error, composer);
4970 g_object_unref (attachment);
4973 static void
4974 composer_get_message_ready (EMsgComposer *composer,
4975 GAsyncResult *result,
4976 GSimpleAsyncResult *simple)
4978 CamelMimeMessage *message;
4979 GError *error = NULL;
4981 message = composer_build_message_finish (composer, result, &error);
4983 if (message != NULL)
4984 g_simple_async_result_set_op_res_gpointer (
4985 simple, message, (GDestroyNotify) g_object_unref);
4987 if (error != NULL) {
4988 g_warn_if_fail (message == NULL);
4989 g_simple_async_result_take_error (simple, error);
4992 g_simple_async_result_complete (simple);
4994 g_object_unref (simple);
4998 * e_msg_composer_get_message:
4999 * @composer: an #EMsgComposer
5001 * Retrieve the message edited by the user as a #CamelMimeMessage. The
5002 * #CamelMimeMessage object is created on the fly; subsequent calls to this
5003 * function will always create new objects from scratch.
5005 void
5006 e_msg_composer_get_message (EMsgComposer *composer,
5007 gint io_priority,
5008 GCancellable *cancellable,
5009 GAsyncReadyCallback callback,
5010 gpointer user_data)
5012 GSimpleAsyncResult *simple;
5013 GtkAction *action;
5014 ComposerFlags flags = 0;
5015 EHTMLEditor *editor;
5016 EContentEditor *cnt_editor;
5018 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
5020 editor = e_msg_composer_get_editor (composer);
5021 cnt_editor = e_html_editor_get_content_editor (editor);
5023 simple = g_simple_async_result_new (
5024 G_OBJECT (composer), callback,
5025 user_data, e_msg_composer_get_message);
5027 g_simple_async_result_set_check_cancellable (simple, cancellable);
5029 if (e_content_editor_get_html_mode (cnt_editor))
5030 flags |= COMPOSER_FLAG_HTML_CONTENT;
5032 action = ACTION (PRIORITIZE_MESSAGE);
5033 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
5034 flags |= COMPOSER_FLAG_PRIORITIZE_MESSAGE;
5036 action = ACTION (REQUEST_READ_RECEIPT);
5037 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
5038 flags |= COMPOSER_FLAG_REQUEST_READ_RECEIPT;
5040 action = ACTION (PGP_SIGN);
5041 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
5042 flags |= COMPOSER_FLAG_PGP_SIGN;
5044 action = ACTION (PGP_ENCRYPT);
5045 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
5046 flags |= COMPOSER_FLAG_PGP_ENCRYPT;
5048 #ifdef ENABLE_SMIME
5049 action = ACTION (SMIME_SIGN);
5050 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
5051 flags |= COMPOSER_FLAG_SMIME_SIGN;
5053 action = ACTION (SMIME_ENCRYPT);
5054 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
5055 flags |= COMPOSER_FLAG_SMIME_ENCRYPT;
5056 #endif
5058 composer_build_message (
5059 composer, flags, io_priority,
5060 cancellable, (GAsyncReadyCallback)
5061 composer_get_message_ready, simple);
5064 CamelMimeMessage *
5065 e_msg_composer_get_message_finish (EMsgComposer *composer,
5066 GAsyncResult *result,
5067 GError **error)
5069 GSimpleAsyncResult *simple;
5070 CamelMimeMessage *message;
5072 g_return_val_if_fail (
5073 g_simple_async_result_is_valid (
5074 result, G_OBJECT (composer),
5075 e_msg_composer_get_message), NULL);
5077 simple = G_SIMPLE_ASYNC_RESULT (result);
5078 message = g_simple_async_result_get_op_res_gpointer (simple);
5080 if (g_simple_async_result_propagate_error (simple, error))
5081 return NULL;
5083 g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL);
5085 return g_object_ref (message);
5088 void
5089 e_msg_composer_get_message_print (EMsgComposer *composer,
5090 gint io_priority,
5091 GCancellable *cancellable,
5092 GAsyncReadyCallback callback,
5093 gpointer user_data)
5095 GSimpleAsyncResult *simple;
5096 ComposerFlags flags = 0;
5098 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
5100 simple = g_simple_async_result_new (
5101 G_OBJECT (composer), callback,
5102 user_data, e_msg_composer_get_message_print);
5104 g_simple_async_result_set_check_cancellable (simple, cancellable);
5106 flags |= COMPOSER_FLAG_HTML_CONTENT;
5107 flags |= COMPOSER_FLAG_SAVE_OBJECT_DATA;
5109 composer_build_message (
5110 composer, flags, io_priority,
5111 cancellable, (GAsyncReadyCallback)
5112 composer_get_message_ready, simple);
5115 CamelMimeMessage *
5116 e_msg_composer_get_message_print_finish (EMsgComposer *composer,
5117 GAsyncResult *result,
5118 GError **error)
5120 GSimpleAsyncResult *simple;
5121 CamelMimeMessage *message;
5123 g_return_val_if_fail (
5124 g_simple_async_result_is_valid (
5125 result, G_OBJECT (composer),
5126 e_msg_composer_get_message_print), NULL);
5128 simple = G_SIMPLE_ASYNC_RESULT (result);
5129 message = g_simple_async_result_get_op_res_gpointer (simple);
5131 if (g_simple_async_result_propagate_error (simple, error))
5132 return NULL;
5134 g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL);
5136 return g_object_ref (message);
5139 void
5140 e_msg_composer_get_message_draft (EMsgComposer *composer,
5141 gint io_priority,
5142 GCancellable *cancellable,
5143 GAsyncReadyCallback callback,
5144 gpointer user_data)
5146 EHTMLEditor *editor;
5147 EContentEditor *cnt_editor;
5148 GSimpleAsyncResult *simple;
5149 ComposerFlags flags = COMPOSER_FLAG_SAVE_DRAFT;
5150 GtkAction *action;
5152 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
5154 simple = g_simple_async_result_new (
5155 G_OBJECT (composer), callback,
5156 user_data, e_msg_composer_get_message_draft);
5158 g_simple_async_result_set_check_cancellable (simple, cancellable);
5160 editor = e_msg_composer_get_editor (composer);
5161 cnt_editor = e_html_editor_get_content_editor (editor);
5162 /* We need to remember composer mode */
5163 if (e_content_editor_get_html_mode (cnt_editor))
5164 flags |= COMPOSER_FLAG_HTML_MODE;
5165 /* We want to save HTML content everytime when we save as draft */
5166 flags |= COMPOSER_FLAG_SAVE_DRAFT;
5168 action = ACTION (PRIORITIZE_MESSAGE);
5169 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
5170 flags |= COMPOSER_FLAG_PRIORITIZE_MESSAGE;
5172 action = ACTION (REQUEST_READ_RECEIPT);
5173 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
5174 flags |= COMPOSER_FLAG_REQUEST_READ_RECEIPT;
5176 action = ACTION (PGP_SIGN);
5177 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
5178 flags |= COMPOSER_FLAG_PGP_SIGN;
5180 action = ACTION (PGP_ENCRYPT);
5181 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
5182 flags |= COMPOSER_FLAG_PGP_ENCRYPT;
5184 #ifdef ENABLE_SMIME
5185 action = ACTION (SMIME_SIGN);
5186 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
5187 flags |= COMPOSER_FLAG_SMIME_SIGN;
5189 action = ACTION (SMIME_ENCRYPT);
5190 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
5191 flags |= COMPOSER_FLAG_SMIME_ENCRYPT;
5192 #endif
5194 composer_build_message (
5195 composer, flags, io_priority,
5196 cancellable, (GAsyncReadyCallback)
5197 composer_get_message_ready, simple);
5200 CamelMimeMessage *
5201 e_msg_composer_get_message_draft_finish (EMsgComposer *composer,
5202 GAsyncResult *result,
5203 GError **error)
5205 GSimpleAsyncResult *simple;
5206 CamelMimeMessage *message;
5208 g_return_val_if_fail (
5209 g_simple_async_result_is_valid (
5210 result, G_OBJECT (composer),
5211 e_msg_composer_get_message_draft), NULL);
5213 simple = G_SIMPLE_ASYNC_RESULT (result);
5214 message = g_simple_async_result_get_op_res_gpointer (simple);
5216 if (g_simple_async_result_propagate_error (simple, error))
5217 return NULL;
5219 g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL);
5221 return g_object_ref (message);
5224 CamelInternetAddress *
5225 e_msg_composer_get_from (EMsgComposer *composer)
5227 CamelInternetAddress *inet_address = NULL;
5228 ESourceMailIdentity *mail_identity;
5229 EComposerHeaderTable *table;
5230 ESource *source;
5231 const gchar *extension_name;
5232 gchar *uid, *alias_name = NULL, *alias_address = NULL;
5233 gchar *name;
5234 gchar *address;
5236 g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
5238 table = e_msg_composer_get_header_table (composer);
5240 uid = e_composer_header_table_dup_identity_uid (table, &alias_name, &alias_address);
5241 if (!uid)
5242 return NULL;
5244 source = e_composer_header_table_ref_source (table, uid);
5245 g_return_val_if_fail (source != NULL, NULL);
5247 extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY;
5248 mail_identity = e_source_get_extension (source, extension_name);
5250 if (alias_name) {
5251 name = alias_name;
5252 alias_name = NULL;
5253 } else {
5254 name = e_source_mail_identity_dup_name (mail_identity);
5257 if (!name)
5258 name = e_source_mail_identity_dup_name (mail_identity);
5260 if (alias_address) {
5261 address = alias_address;
5262 alias_address = NULL;
5263 } else {
5264 address = e_source_mail_identity_dup_address (mail_identity);
5267 g_object_unref (source);
5269 if (address != NULL) {
5270 inet_address = camel_internet_address_new ();
5271 camel_internet_address_add (inet_address, name, address);
5274 g_free (uid);
5275 g_free (name);
5276 g_free (address);
5277 g_free (alias_name);
5278 g_free (alias_address);
5280 return inet_address;
5283 CamelInternetAddress *
5284 e_msg_composer_get_reply_to (EMsgComposer *composer)
5286 CamelInternetAddress *address;
5287 EComposerHeaderTable *table;
5288 const gchar *reply_to;
5290 g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
5292 table = e_msg_composer_get_header_table (composer);
5294 reply_to = e_composer_header_table_get_reply_to (table);
5295 if (reply_to == NULL || *reply_to == '\0')
5296 return NULL;
5298 address = camel_internet_address_new ();
5299 if (camel_address_unformat (CAMEL_ADDRESS (address), reply_to) == -1) {
5300 g_object_unref (address);
5301 address = NULL;
5304 return address;
5308 * e_msg_composer_get_raw_message_text_without_signature:
5310 * Returns the text/plain of the message from composer without signature
5312 GByteArray *
5313 e_msg_composer_get_raw_message_text_without_signature (EMsgComposer *composer)
5315 EHTMLEditor *editor;
5316 EContentEditor *cnt_editor;
5317 gchar *content;
5319 g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
5321 editor = e_msg_composer_get_editor (composer);
5322 cnt_editor = e_html_editor_get_content_editor (editor);
5324 content = e_content_editor_get_content (
5325 cnt_editor,
5326 E_CONTENT_EDITOR_GET_BODY |
5327 E_CONTENT_EDITOR_GET_TEXT_PLAIN |
5328 E_CONTENT_EDITOR_GET_EXCLUDE_SIGNATURE,
5329 NULL, NULL);
5331 if (!content) {
5332 g_warning ("%s: Failed to retrieve content", G_STRFUNC);
5333 content = g_strdup ("");
5336 return g_byte_array_new_take ((guint8 *) content, strlen (content));
5340 * e_msg_composer_get_raw_message_text:
5342 * Returns the text/plain of the message from composer
5344 GByteArray *
5345 e_msg_composer_get_raw_message_text (EMsgComposer *composer)
5347 EHTMLEditor *editor;
5348 EContentEditor *cnt_editor;
5349 gchar *content;
5351 g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
5353 editor = e_msg_composer_get_editor (composer);
5354 cnt_editor = e_html_editor_get_content_editor (editor);
5356 content = e_content_editor_get_content (
5357 cnt_editor,
5358 E_CONTENT_EDITOR_GET_BODY |
5359 E_CONTENT_EDITOR_GET_TEXT_PLAIN,
5360 NULL, NULL);
5362 if (!content) {
5363 g_warning ("%s: Failed to retrieve content", G_STRFUNC);
5364 content = g_strdup ("");
5367 return g_byte_array_new_take ((guint8 *) content, strlen (content));
5370 gboolean
5371 e_msg_composer_is_exiting (EMsgComposer *composer)
5373 g_return_val_if_fail (composer != NULL, FALSE);
5375 return composer->priv->application_exiting;
5378 void
5379 e_msg_composer_request_close (EMsgComposer *composer)
5381 g_return_if_fail (composer != NULL);
5383 composer->priv->application_exiting = TRUE;
5386 /* Returns whether can close the composer immediately. It will return FALSE
5387 * also when saving to drafts, but the e_msg_composer_is_exiting will return
5388 * TRUE for this case. can_save_draft means whether can save draft
5389 * immediately, or rather keep it on the caller (when FALSE). If kept on the
5390 * folder, then returns FALSE and sets interval variable to return TRUE in
5391 * e_msg_composer_is_exiting. */
5392 gboolean
5393 e_msg_composer_can_close (EMsgComposer *composer,
5394 gboolean can_save_draft)
5396 gboolean res = FALSE;
5397 EHTMLEditor *editor;
5398 EContentEditor *cnt_editor;
5399 EComposerHeaderTable *table;
5400 GdkWindow *window;
5401 GtkWidget *widget;
5402 const gchar *subject, *message_name;
5403 gint response;
5405 widget = GTK_WIDGET (composer);
5406 editor = e_msg_composer_get_editor (composer);
5407 cnt_editor = e_html_editor_get_content_editor (editor);
5409 /* this means that there is an async operation running,
5410 * in which case the composer cannot be closed */
5411 if (!gtk_action_group_get_sensitive (composer->priv->async_actions))
5412 return FALSE;
5414 if (!e_content_editor_get_changed (cnt_editor) ||
5415 e_content_editor_is_malfunction (cnt_editor))
5416 return TRUE;
5418 window = gtk_widget_get_window (widget);
5419 gdk_window_raise (window);
5421 table = e_msg_composer_get_header_table (composer);
5422 subject = e_composer_header_table_get_subject (table);
5424 if (subject == NULL || *subject == '\0')
5425 message_name = "mail-composer:exit-unsaved-no-subject";
5426 else
5427 message_name = "mail-composer:exit-unsaved";
5429 response = e_alert_run_dialog_for_args (
5430 GTK_WINDOW (composer),
5431 message_name,
5432 subject, NULL);
5434 switch (response) {
5435 case GTK_RESPONSE_YES:
5436 e_msg_composer_request_close (composer);
5437 if (can_save_draft)
5438 gtk_action_activate (ACTION (SAVE_DRAFT));
5439 break;
5441 case GTK_RESPONSE_NO:
5442 res = TRUE;
5443 break;
5445 case GTK_RESPONSE_CANCEL:
5446 break;
5449 return res;
5452 EComposerHeaderTable *
5453 e_msg_composer_get_header_table (EMsgComposer *composer)
5455 g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
5457 return E_COMPOSER_HEADER_TABLE (composer->priv->header_table);
5460 EAttachmentView *
5461 e_msg_composer_get_attachment_view (EMsgComposer *composer)
5463 g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
5465 return E_ATTACHMENT_VIEW (composer->priv->attachment_paned);
5468 void
5469 e_save_spell_languages (const GList *spell_dicts)
5471 GSettings *settings;
5472 GPtrArray *lang_array;
5474 /* Build a list of spell check language codes. */
5475 lang_array = g_ptr_array_new ();
5477 while (spell_dicts != NULL) {
5478 ESpellDictionary *dict = spell_dicts->data;
5479 const gchar *language_code;
5481 language_code = e_spell_dictionary_get_code (dict);
5482 g_ptr_array_add (lang_array, (gpointer) language_code);
5484 spell_dicts = g_list_next (spell_dicts);
5487 g_ptr_array_add (lang_array, NULL);
5489 /* Save the language codes to GSettings. */
5490 settings = e_util_ref_settings ("org.gnome.evolution.mail");
5491 g_settings_set_strv (
5492 settings, "composer-spell-languages",
5493 (const gchar * const *) lang_array->pdata);
5494 g_object_unref (settings);
5496 g_ptr_array_free (lang_array, TRUE);
5499 void
5500 e_msg_composer_save_focused_widget (EMsgComposer *composer)
5502 GtkWidget *widget;
5504 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
5506 widget = gtk_window_get_focus (GTK_WINDOW (composer));
5507 composer->priv->focused_entry = widget;
5509 if (E_IS_CONTENT_EDITOR (widget))
5510 e_content_editor_selection_save (E_CONTENT_EDITOR (widget));
5512 if (GTK_IS_EDITABLE (widget)) {
5513 gtk_editable_get_selection_bounds (
5514 GTK_EDITABLE (widget),
5515 &composer->priv->focused_entry_selection_start,
5516 &composer->priv->focused_entry_selection_end);
5520 void
5521 e_msg_composer_restore_focus_on_composer (EMsgComposer *composer)
5523 GtkWidget *widget = composer->priv->focused_entry;
5525 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
5527 if (!widget)
5528 return;
5530 gtk_window_set_focus (GTK_WINDOW (composer), widget);
5532 if (GTK_IS_EDITABLE (widget)) {
5533 gtk_editable_select_region (
5534 GTK_EDITABLE (widget),
5535 composer->priv->focused_entry_selection_start,
5536 composer->priv->focused_entry_selection_end);
5539 if (E_IS_CONTENT_EDITOR (widget)) {
5540 EContentEditor *cnt_editor = E_CONTENT_EDITOR (widget);
5541 e_content_editor_selection_restore (cnt_editor);
5544 composer->priv->focused_entry = NULL;
5547 gboolean
5548 e_msg_composer_get_is_reply_or_forward (EMsgComposer *composer)
5550 g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE);
5552 return composer->priv->is_reply_or_forward;
5555 void
5556 e_msg_composer_set_is_reply_or_forward (EMsgComposer *composer,
5557 gboolean is_reply_or_forward)
5559 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
5561 if ((composer->priv->is_reply_or_forward ? 1 : 0) == (is_reply_or_forward ? 1 : 0))
5562 return;
5564 composer->priv->is_reply_or_forward = is_reply_or_forward;
5566 g_object_notify (G_OBJECT (composer), "is-reply-or-forward");
5568 msg_composer_mail_identity_changed_cb (composer);