Updated Spanish translation
[evolution.git] / composer / e-msg-composer.c
blob0bed05abf5d6817a6553c5ddb241ca1ecb1d24d2
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 #ifdef HAVE_CONFIG_H
27 #include <config.h>
28 #endif
30 #include <stdlib.h>
31 #include <string.h>
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #include <sys/time.h>
35 #include <unistd.h>
36 #include <ctype.h>
37 #include <fcntl.h>
38 #include <enchant/enchant.h>
40 #include "e-composer-from-header.h"
41 #include "e-composer-private.h"
43 #include <em-format/e-mail-part.h>
44 #include <em-format/e-mail-parser.h>
45 #include <em-format/e-mail-formatter-quote.h>
47 #include <shell/e-shell.h>
49 #include <libemail-engine/libemail-engine.h>
51 typedef struct _AsyncContext AsyncContext;
53 struct _AsyncContext {
54 EActivity *activity;
56 CamelMimeMessage *message;
57 CamelDataWrapper *top_level_part;
58 CamelDataWrapper *text_plain_part;
60 ESource *source;
61 CamelSession *session;
62 CamelInternetAddress *from;
64 CamelTransferEncoding plain_encoding;
65 GtkPrintOperationAction print_action;
67 GPtrArray *recipients;
69 guint skip_content : 1;
70 guint need_thread : 1;
71 guint pgp_sign : 1;
72 guint pgp_encrypt : 1;
73 guint smime_sign : 1;
74 guint smime_encrypt : 1;
77 /* Flags for building a message. */
78 typedef enum {
79 COMPOSER_FLAG_HTML_CONTENT = 1 << 0,
80 COMPOSER_FLAG_SAVE_OBJECT_DATA = 1 << 1,
81 COMPOSER_FLAG_PRIORITIZE_MESSAGE = 1 << 2,
82 COMPOSER_FLAG_REQUEST_READ_RECEIPT = 1 << 3,
83 COMPOSER_FLAG_PGP_SIGN = 1 << 4,
84 COMPOSER_FLAG_PGP_ENCRYPT = 1 << 5,
85 COMPOSER_FLAG_SMIME_SIGN = 1 << 6,
86 COMPOSER_FLAG_SMIME_ENCRYPT = 1 << 7,
87 COMPOSER_FLAG_HTML_MODE = 1 << 8,
88 COMPOSER_FLAG_SAVE_DRAFT = 1 << 9
89 } ComposerFlags;
91 enum {
92 PROP_0,
93 PROP_BUSY,
94 PROP_EDITOR,
95 PROP_FOCUS_TRACKER,
96 PROP_SHELL
99 enum {
100 PRESEND,
101 SEND,
102 SAVE_TO_DRAFTS,
103 SAVE_TO_OUTBOX,
104 PRINT,
105 LAST_SIGNAL
108 enum DndTargetType {
109 DND_TARGET_TYPE_TEXT_URI_LIST,
110 DND_TARGET_TYPE_MOZILLA_URL,
111 DND_TARGET_TYPE_TEXT_HTML,
112 DND_TARGET_TYPE_UTF8_STRING,
113 DND_TARGET_TYPE_TEXT_PLAIN,
114 DND_TARGET_TYPE_STRING,
115 DND_TARGET_TYPE_TEXT_PLAIN_UTF8
118 static GtkTargetEntry drag_dest_targets[] = {
119 { (gchar *) "text/uri-list", 0, DND_TARGET_TYPE_TEXT_URI_LIST },
120 { (gchar *) "_NETSCAPE_URL", 0, DND_TARGET_TYPE_MOZILLA_URL },
121 { (gchar *) "text/html", 0, DND_TARGET_TYPE_TEXT_HTML },
122 { (gchar *) "UTF8_STRING", 0, DND_TARGET_TYPE_UTF8_STRING },
123 { (gchar *) "text/plain", 0, DND_TARGET_TYPE_TEXT_PLAIN },
124 { (gchar *) "STRING", 0, DND_TARGET_TYPE_STRING },
125 { (gchar *) "text/plain;charset=utf-8", 0, DND_TARGET_TYPE_TEXT_PLAIN_UTF8 },
128 static guint signals[LAST_SIGNAL];
130 /* used by e_msg_composer_add_message_attachments () */
131 static void add_attachments_from_multipart (EMsgComposer *composer,
132 CamelMultipart *multipart,
133 gboolean just_inlines,
134 gint depth);
136 /* used by e_msg_composer_new_with_message () */
137 static void handle_multipart (EMsgComposer *composer,
138 CamelMultipart *multipart,
139 gboolean keep_signature,
140 GCancellable *cancellable,
141 gint depth);
142 static void handle_multipart_alternative (EMsgComposer *composer,
143 CamelMultipart *multipart,
144 gboolean keep_signature,
145 GCancellable *cancellable,
146 gint depth);
147 static void handle_multipart_encrypted (EMsgComposer *composer,
148 CamelMimePart *multipart,
149 gboolean keep_signature,
150 GCancellable *cancellable,
151 gint depth);
152 static void handle_multipart_signed (EMsgComposer *composer,
153 CamelMultipart *multipart,
154 gboolean keep_signature,
155 GCancellable *cancellable,
156 gint depth);
158 G_DEFINE_TYPE_WITH_CODE (
159 EMsgComposer,
160 e_msg_composer,
161 GTK_TYPE_WINDOW,
162 G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE, NULL))
164 static void
165 async_context_free (AsyncContext *context)
167 if (context->activity != NULL)
168 g_object_unref (context->activity);
170 if (context->message != NULL)
171 g_object_unref (context->message);
173 if (context->top_level_part != NULL)
174 g_object_unref (context->top_level_part);
176 if (context->text_plain_part != NULL)
177 g_object_unref (context->text_plain_part);
179 if (context->source != NULL)
180 g_object_unref (context->source);
182 if (context->session != NULL)
183 g_object_unref (context->session);
185 if (context->from != NULL)
186 g_object_unref (context->from);
188 if (context->recipients != NULL)
189 g_ptr_array_free (context->recipients, TRUE);
191 g_slice_free (AsyncContext, context);
195 * emcu_part_to_html:
196 * @part:
198 * Converts a mime part's contents into html text. If @credits is given,
199 * then it will be used as an attribution string, and the
200 * content will be cited. Otherwise no citation or attribution
201 * will be performed.
203 * Return Value: The part in displayable html format.
205 static gchar *
206 emcu_part_to_html (EMsgComposer *composer,
207 CamelMimePart *part,
208 gssize *len,
209 gboolean keep_signature,
210 GCancellable *cancellable)
212 CamelSession *session;
213 GOutputStream *stream;
214 gchar *text;
215 EMailParser *parser;
216 EMailFormatter *formatter;
217 EMailPartList *part_list;
218 GString *part_id;
219 EShell *shell;
220 GtkWindow *window;
221 gsize n_bytes_written = 0;
222 GQueue queue = G_QUEUE_INIT;
224 shell = e_shell_get_default ();
225 window = e_shell_get_active_window (shell);
227 session = e_msg_composer_ref_session (composer);
229 part_list = e_mail_part_list_new (NULL, NULL, NULL);
231 part_id = g_string_sized_new (0);
232 parser = e_mail_parser_new (session);
233 e_mail_parser_parse_part (
234 parser, part, part_id, cancellable, &queue);
235 while (!g_queue_is_empty (&queue)) {
236 EMailPart *mail_part = g_queue_pop_head (&queue);
238 if (!e_mail_part_get_is_attachment (mail_part) &&
239 !mail_part->is_hidden)
240 e_mail_part_list_add_part (part_list, mail_part);
242 g_object_unref (mail_part);
244 g_string_free (part_id, TRUE);
245 g_object_unref (parser);
246 g_object_unref (session);
248 if (e_mail_part_list_is_empty (part_list)) {
249 g_object_unref (part_list);
250 return NULL;
253 stream = g_memory_output_stream_new_resizable ();
255 formatter = e_mail_formatter_quote_new (
256 NULL, keep_signature ? E_MAIL_FORMATTER_QUOTE_FLAG_KEEP_SIG : 0);
257 e_mail_formatter_update_style (
258 formatter,
259 gtk_widget_get_state_flags (GTK_WIDGET (window)));
261 e_mail_formatter_format_sync (
262 formatter, part_list, stream,
263 0, E_MAIL_FORMATTER_MODE_PRINTING, cancellable);
265 g_object_unref (formatter);
266 g_object_unref (part_list);
268 g_output_stream_write_all (stream, "", 1, &n_bytes_written, NULL, NULL);
270 g_output_stream_close (stream, NULL, NULL);
272 text = g_memory_output_stream_steal_data (
273 G_MEMORY_OUTPUT_STREAM (stream));
275 if (len != NULL)
276 *len = strlen (text);
278 g_object_unref (stream);
280 return text;
283 /* copy of mail_tool_remove_xevolution_headers */
284 static struct _camel_header_raw *
285 emcu_remove_xevolution_headers (CamelMimeMessage *message)
287 struct _camel_header_raw *scan, *list = NULL;
289 for (scan = ((CamelMimePart *) message)->headers; scan; scan = scan->next)
290 if (!strncmp (scan->name, "X-Evolution", 11))
291 camel_header_raw_append (&list, scan->name, scan->value, scan->offset);
293 for (scan = list; scan; scan = scan->next)
294 camel_medium_remove_header ((CamelMedium *) message, scan->name);
296 return list;
299 static EDestination **
300 destination_list_to_vector_sized (GList *list,
301 gint n)
303 EDestination **destv;
304 gint i = 0;
306 if (n == -1)
307 n = g_list_length (list);
309 if (n == 0)
310 return NULL;
312 destv = g_new (EDestination *, n + 1);
313 while (list != NULL && i < n) {
314 destv[i] = E_DESTINATION (list->data);
315 list->data = NULL;
316 i++;
317 list = g_list_next (list);
319 destv[i] = NULL;
321 return destv;
324 static EDestination **
325 destination_list_to_vector (GList *list)
327 return destination_list_to_vector_sized (list, -1);
330 #define LINE_LEN 72
332 static gboolean
333 text_requires_quoted_printable (const gchar *text,
334 gsize len)
336 const gchar *p;
337 gsize pos;
339 if (!text)
340 return FALSE;
342 if (len == -1)
343 len = strlen (text);
345 if (len >= 5 && strncmp (text, "From ", 5) == 0)
346 return TRUE;
348 for (p = text, pos = 0; pos + 6 <= len; pos++, p++) {
349 if (*p == '\n' && strncmp (p + 1, "From ", 5) == 0)
350 return TRUE;
353 return FALSE;
356 static CamelTransferEncoding
357 best_encoding (GByteArray *buf,
358 const gchar *charset)
360 gchar *in, *out, outbuf[256], *ch;
361 gsize inlen, outlen;
362 gint status, count = 0;
363 iconv_t cd;
365 if (!charset)
366 return -1;
368 cd = camel_iconv_open (charset, "utf-8");
369 if (cd == (iconv_t) -1)
370 return -1;
372 in = (gchar *) buf->data;
373 inlen = buf->len;
374 do {
375 out = outbuf;
376 outlen = sizeof (outbuf);
377 status = camel_iconv (cd, (const gchar **) &in, &inlen, &out, &outlen);
378 for (ch = out - 1; ch >= outbuf; ch--) {
379 if ((guchar) *ch > 127)
380 count++;
382 } while (status == (gsize) -1 && errno == E2BIG);
383 camel_iconv_close (cd);
385 if (status == (gsize) -1 || status > 0)
386 return -1;
388 if ((count == 0) && (buf->len < LINE_LEN) &&
389 !text_requires_quoted_printable (
390 (const gchar *) buf->data, buf->len))
391 return CAMEL_TRANSFER_ENCODING_7BIT;
392 else if (count <= buf->len * 0.17)
393 return CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE;
394 else
395 return CAMEL_TRANSFER_ENCODING_BASE64;
398 static gchar *
399 best_charset (GByteArray *buf,
400 const gchar *default_charset,
401 CamelTransferEncoding *encoding)
403 const gchar *charset;
405 /* First try US-ASCII */
406 *encoding = best_encoding (buf, "US-ASCII");
407 if (*encoding == CAMEL_TRANSFER_ENCODING_7BIT)
408 return NULL;
410 /* Next try the user-specified charset for this message */
411 *encoding = best_encoding (buf, default_charset);
412 if (*encoding != -1)
413 return g_strdup (default_charset);
415 /* Now try the user's default charset from the mail config */
416 charset = e_composer_get_default_charset ();
417 *encoding = best_encoding (buf, charset);
418 if (*encoding != -1)
419 return g_strdup (charset);
421 /* Try to find something that will work */
422 charset = camel_charset_best (
423 (const gchar *) buf->data, buf->len);
424 if (charset == NULL) {
425 *encoding = CAMEL_TRANSFER_ENCODING_7BIT;
426 return NULL;
429 *encoding = best_encoding (buf, charset);
431 return g_strdup (charset);
434 /* These functions builds a CamelMimeMessage for the message that the user has
435 * composed in 'composer'.
438 static void
439 set_recipients_from_destv (CamelMimeMessage *msg,
440 EDestination **to_destv,
441 EDestination **cc_destv,
442 EDestination **bcc_destv,
443 gboolean redirect)
445 CamelInternetAddress *to_addr;
446 CamelInternetAddress *cc_addr;
447 CamelInternetAddress *bcc_addr;
448 CamelInternetAddress *target;
449 const gchar *text_addr, *header;
450 gboolean seen_hidden_list = FALSE;
451 gint i;
453 to_addr = camel_internet_address_new ();
454 cc_addr = camel_internet_address_new ();
455 bcc_addr = camel_internet_address_new ();
457 for (i = 0; to_destv != NULL && to_destv[i] != NULL; ++i) {
458 text_addr = e_destination_get_address (to_destv[i]);
460 if (text_addr && *text_addr) {
461 target = to_addr;
462 if (e_destination_is_evolution_list (to_destv[i])
463 && !e_destination_list_show_addresses (to_destv[i])) {
464 target = bcc_addr;
465 seen_hidden_list = TRUE;
468 if (camel_address_decode (CAMEL_ADDRESS (target), text_addr) <= 0)
469 camel_internet_address_add (target, "", text_addr);
473 for (i = 0; cc_destv != NULL && cc_destv[i] != NULL; ++i) {
474 text_addr = e_destination_get_address (cc_destv[i]);
475 if (text_addr && *text_addr) {
476 target = cc_addr;
477 if (e_destination_is_evolution_list (cc_destv[i])
478 && !e_destination_list_show_addresses (cc_destv[i])) {
479 target = bcc_addr;
480 seen_hidden_list = TRUE;
483 if (camel_address_decode (CAMEL_ADDRESS (target), text_addr) <= 0)
484 camel_internet_address_add (target, "", text_addr);
488 for (i = 0; bcc_destv != NULL && bcc_destv[i] != NULL; ++i) {
489 text_addr = e_destination_get_address (bcc_destv[i]);
490 if (text_addr && *text_addr) {
491 if (camel_address_decode (CAMEL_ADDRESS (bcc_addr), text_addr) <= 0)
492 camel_internet_address_add (bcc_addr, "", text_addr);
496 if (redirect)
497 header = CAMEL_RECIPIENT_TYPE_RESENT_TO;
498 else
499 header = CAMEL_RECIPIENT_TYPE_TO;
501 if (camel_address_length (CAMEL_ADDRESS (to_addr)) > 0) {
502 camel_mime_message_set_recipients (msg, header, to_addr);
503 } else if (seen_hidden_list) {
504 camel_medium_set_header (
505 CAMEL_MEDIUM (msg), header, "Undisclosed-Recipient:;");
508 header = redirect ? CAMEL_RECIPIENT_TYPE_RESENT_CC : CAMEL_RECIPIENT_TYPE_CC;
509 if (camel_address_length (CAMEL_ADDRESS (cc_addr)) > 0) {
510 camel_mime_message_set_recipients (msg, header, cc_addr);
513 header = redirect ? CAMEL_RECIPIENT_TYPE_RESENT_BCC : CAMEL_RECIPIENT_TYPE_BCC;
514 if (camel_address_length (CAMEL_ADDRESS (bcc_addr)) > 0) {
515 camel_mime_message_set_recipients (msg, header, bcc_addr);
518 g_object_unref (to_addr);
519 g_object_unref (cc_addr);
520 g_object_unref (bcc_addr);
523 static void
524 build_message_headers (EMsgComposer *composer,
525 CamelMimeMessage *message,
526 gboolean redirect)
528 EComposerHeaderTable *table;
529 EComposerHeader *header;
530 ESource *source;
531 const gchar *subject;
532 const gchar *reply_to;
533 const gchar *uid;
535 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
536 g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
538 table = e_msg_composer_get_header_table (composer);
540 uid = e_composer_header_table_get_identity_uid (table);
541 source = e_composer_header_table_ref_source (table, uid);
543 /* Subject: */
544 subject = e_composer_header_table_get_subject (table);
545 camel_mime_message_set_subject (message, subject);
547 if (source != NULL) {
548 CamelMedium *medium;
549 CamelInternetAddress *addr;
550 ESourceMailSubmission *ms;
551 EComposerHeader *composer_header;
552 const gchar *extension_name;
553 const gchar *header_name;
554 const gchar *name, *address = NULL;
555 const gchar *transport_uid;
556 const gchar *sent_folder;
558 composer_header = e_composer_header_table_get_header (table, E_COMPOSER_HEADER_FROM);
559 if (e_composer_from_header_get_override_visible (E_COMPOSER_FROM_HEADER (composer_header))) {
560 name = e_composer_header_table_get_from_name (table);
561 address = e_composer_header_table_get_from_address (table);
563 if (address && !*address)
564 address = NULL;
567 if (!address) {
568 ESourceMailIdentity *mail_identity;
570 mail_identity = e_source_get_extension (source, E_SOURCE_EXTENSION_MAIL_IDENTITY);
572 name = e_source_mail_identity_get_name (mail_identity);
573 address = e_source_mail_identity_get_address (mail_identity);
576 extension_name = E_SOURCE_EXTENSION_MAIL_SUBMISSION;
577 ms = e_source_get_extension (source, extension_name);
579 sent_folder = e_source_mail_submission_get_sent_folder (ms);
580 transport_uid = e_source_mail_submission_get_transport_uid (ms);
582 medium = CAMEL_MEDIUM (message);
584 /* From: / Resent-From: */
585 addr = camel_internet_address_new ();
586 camel_internet_address_add (addr, name, address);
587 if (redirect) {
588 gchar *value;
590 value = camel_address_encode (CAMEL_ADDRESS (addr));
591 camel_medium_set_header (medium, "Resent-From", value);
592 g_free (value);
593 } else {
594 camel_mime_message_set_from (message, addr);
596 g_object_unref (addr);
598 /* X-Evolution-Identity */
599 header_name = "X-Evolution-Identity";
600 camel_medium_set_header (medium, header_name, uid);
602 /* X-Evolution-Fcc */
603 header_name = "X-Evolution-Fcc";
604 camel_medium_set_header (medium, header_name, sent_folder);
606 /* X-Evolution-Transport */
607 header_name = "X-Evolution-Transport";
608 camel_medium_set_header (medium, header_name, transport_uid);
610 g_object_unref (source);
613 /* Reply-To: */
614 reply_to = e_composer_header_table_get_reply_to (table);
615 if (reply_to != NULL && *reply_to != '\0') {
616 CamelInternetAddress *addr;
618 addr = camel_internet_address_new ();
620 if (camel_address_unformat (CAMEL_ADDRESS (addr), reply_to) > 0)
621 camel_mime_message_set_reply_to (message, addr);
623 g_object_unref (addr);
626 /* To:, Cc:, Bcc: */
627 header = e_composer_header_table_get_header (
628 table, E_COMPOSER_HEADER_TO);
629 if (e_composer_header_get_visible (header)) {
630 EDestination **to, **cc, **bcc;
632 to = e_composer_header_table_get_destinations_to (table);
633 cc = e_composer_header_table_get_destinations_cc (table);
634 bcc = e_composer_header_table_get_destinations_bcc (table);
636 set_recipients_from_destv (message, to, cc, bcc, redirect);
638 e_destination_freev (to);
639 e_destination_freev (cc);
640 e_destination_freev (bcc);
643 /* Date: */
644 camel_mime_message_set_date (message, CAMEL_MESSAGE_DATE_CURRENT, 0);
646 /* X-Evolution-PostTo: */
647 header = e_composer_header_table_get_header (
648 table, E_COMPOSER_HEADER_POST_TO);
649 if (e_composer_header_get_visible (header)) {
650 CamelMedium *medium;
651 const gchar *name = "X-Evolution-PostTo";
652 GList *list, *iter;
654 medium = CAMEL_MEDIUM (message);
655 camel_medium_remove_header (medium, name);
657 list = e_composer_header_table_get_post_to (table);
658 for (iter = list; iter != NULL; iter = iter->next) {
659 gchar *folder = iter->data;
660 camel_medium_add_header (medium, name, folder);
661 g_free (folder);
663 g_list_free (list);
667 static CamelCipherHash
668 account_hash_algo_to_camel_hash (const gchar *hash_algo)
670 CamelCipherHash res = CAMEL_CIPHER_HASH_DEFAULT;
672 if (hash_algo && *hash_algo) {
673 if (g_ascii_strcasecmp (hash_algo, "sha1") == 0)
674 res = CAMEL_CIPHER_HASH_SHA1;
675 else if (g_ascii_strcasecmp (hash_algo, "sha256") == 0)
676 res = CAMEL_CIPHER_HASH_SHA256;
677 else if (g_ascii_strcasecmp (hash_algo, "sha384") == 0)
678 res = CAMEL_CIPHER_HASH_SHA384;
679 else if (g_ascii_strcasecmp (hash_algo, "sha512") == 0)
680 res = CAMEL_CIPHER_HASH_SHA512;
683 return res;
686 static void
687 composer_add_charset_filter (CamelStream *stream,
688 const gchar *charset)
690 CamelMimeFilter *filter;
692 filter = camel_mime_filter_charset_new ("UTF-8", charset);
693 camel_stream_filter_add (CAMEL_STREAM_FILTER (stream), filter);
694 g_object_unref (filter);
697 static void
698 composer_add_quoted_printable_filter (CamelStream *stream)
700 CamelMimeFilter *filter;
702 filter = camel_mime_filter_basic_new (CAMEL_MIME_FILTER_BASIC_QP_ENC);
703 camel_stream_filter_add (CAMEL_STREAM_FILTER (stream), filter);
704 g_object_unref (filter);
706 filter = camel_mime_filter_canon_new (CAMEL_MIME_FILTER_CANON_FROM);
707 camel_stream_filter_add (CAMEL_STREAM_FILTER (stream), filter);
708 g_object_unref (filter);
711 /* Helper for composer_build_message_thread() */
712 static gboolean
713 composer_build_message_pgp (AsyncContext *context,
714 GCancellable *cancellable,
715 GError **error)
717 ESourceOpenPGP *extension;
718 CamelCipherContext *cipher;
719 CamelDataWrapper *content;
720 CamelMimePart *mime_part;
721 const gchar *extension_name;
722 const gchar *pgp_key_id;
723 const gchar *signing_algorithm;
724 gboolean always_trust;
725 gboolean encrypt_to_self;
727 /* Return silently if we're not signing or encrypting with PGP. */
728 if (!context->pgp_sign && !context->pgp_encrypt)
729 return TRUE;
731 extension_name = E_SOURCE_EXTENSION_OPENPGP;
732 extension = e_source_get_extension (context->source, extension_name);
734 always_trust = e_source_openpgp_get_always_trust (extension);
735 encrypt_to_self = e_source_openpgp_get_encrypt_to_self (extension);
736 pgp_key_id = e_source_openpgp_get_key_id (extension);
737 signing_algorithm = e_source_openpgp_get_signing_algorithm (extension);
739 mime_part = camel_mime_part_new ();
741 camel_medium_set_content (
742 CAMEL_MEDIUM (mime_part),
743 context->top_level_part);
745 if (context->top_level_part == context->text_plain_part)
746 camel_mime_part_set_encoding (
747 mime_part, context->plain_encoding);
749 g_object_unref (context->top_level_part);
750 context->top_level_part = NULL;
752 if (pgp_key_id == NULL || *pgp_key_id == '\0')
753 camel_internet_address_get (
754 context->from, 0, NULL, &pgp_key_id);
756 if (context->pgp_sign) {
757 CamelMimePart *npart;
758 gboolean success;
760 npart = camel_mime_part_new ();
762 cipher = camel_gpg_context_new (context->session);
763 camel_gpg_context_set_always_trust (
764 CAMEL_GPG_CONTEXT (cipher), always_trust);
766 success = camel_cipher_context_sign_sync (
767 cipher, pgp_key_id,
768 account_hash_algo_to_camel_hash (signing_algorithm),
769 mime_part, npart, cancellable, error);
771 g_object_unref (cipher);
773 g_object_unref (mime_part);
775 if (!success) {
776 g_object_unref (npart);
777 return FALSE;
780 mime_part = npart;
783 if (context->pgp_encrypt) {
784 CamelMimePart *npart;
785 gboolean success;
787 npart = camel_mime_part_new ();
789 /* Check to see if we should encrypt to self.
790 * NB: Gets removed immediately after use. */
791 if (encrypt_to_self && pgp_key_id != NULL)
792 g_ptr_array_add (
793 context->recipients,
794 g_strdup (pgp_key_id));
796 cipher = camel_gpg_context_new (context->session);
797 camel_gpg_context_set_always_trust (
798 CAMEL_GPG_CONTEXT (cipher), always_trust);
800 success = camel_cipher_context_encrypt_sync (
801 cipher, pgp_key_id, context->recipients,
802 mime_part, npart, cancellable, error);
804 g_object_unref (cipher);
806 if (encrypt_to_self && pgp_key_id != NULL)
807 g_ptr_array_set_size (
808 context->recipients,
809 context->recipients->len - 1);
811 g_object_unref (mime_part);
813 if (!success) {
814 g_object_unref (npart);
815 return FALSE;
818 mime_part = npart;
821 content = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
822 context->top_level_part = g_object_ref (content);
824 g_object_unref (mime_part);
826 return TRUE;
829 #ifdef HAVE_SSL
830 static gboolean
831 composer_build_message_smime (AsyncContext *context,
832 GCancellable *cancellable,
833 GError **error)
835 ESourceSMIME *extension;
836 CamelCipherContext *cipher;
837 CamelMimePart *mime_part;
838 const gchar *extension_name;
839 const gchar *signing_algorithm;
840 const gchar *signing_certificate;
841 const gchar *encryption_certificate;
842 gboolean encrypt_to_self;
843 gboolean have_signing_certificate;
844 gboolean have_encryption_certificate;
846 /* Return silently if we're not signing or encrypting with S/MIME. */
847 if (!context->smime_sign && !context->smime_encrypt)
848 return TRUE;
850 extension_name = E_SOURCE_EXTENSION_SMIME;
851 extension = e_source_get_extension (context->source, extension_name);
853 encrypt_to_self =
854 e_source_smime_get_encrypt_to_self (extension);
856 signing_algorithm =
857 e_source_smime_get_signing_algorithm (extension);
859 signing_certificate =
860 e_source_smime_get_signing_certificate (extension);
862 encryption_certificate =
863 e_source_smime_get_encryption_certificate (extension);
865 have_signing_certificate =
866 (signing_certificate != NULL) &&
867 (*signing_certificate != '\0');
869 have_encryption_certificate =
870 (encryption_certificate != NULL) &&
871 (*encryption_certificate != '\0');
873 if (context->smime_sign && !have_signing_certificate) {
874 g_set_error (
875 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
876 _("Cannot sign outgoing message: "
877 "No signing certificate set for "
878 "this account"));
879 return FALSE;
882 if (context->smime_encrypt && !have_encryption_certificate) {
883 g_set_error (
884 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
885 _("Cannot encrypt outgoing message: "
886 "No encryption certificate set for "
887 "this account"));
888 return FALSE;
891 mime_part = camel_mime_part_new ();
893 camel_medium_set_content (
894 CAMEL_MEDIUM (mime_part),
895 context->top_level_part);
897 if (context->top_level_part == context->text_plain_part)
898 camel_mime_part_set_encoding (
899 mime_part, context->plain_encoding);
901 g_object_unref (context->top_level_part);
902 context->top_level_part = NULL;
904 if (context->smime_sign) {
905 CamelMimePart *npart;
906 gboolean success;
908 npart = camel_mime_part_new ();
910 cipher = camel_smime_context_new (context->session);
912 /* if we're also encrypting, envelope-sign rather than clear-sign */
913 if (context->smime_encrypt) {
914 camel_smime_context_set_sign_mode (
915 (CamelSMIMEContext *) cipher,
916 CAMEL_SMIME_SIGN_ENVELOPED);
917 camel_smime_context_set_encrypt_key (
918 (CamelSMIMEContext *) cipher,
919 TRUE, encryption_certificate);
920 } else if (have_encryption_certificate) {
921 camel_smime_context_set_encrypt_key (
922 (CamelSMIMEContext *) cipher,
923 TRUE, encryption_certificate);
926 success = camel_cipher_context_sign_sync (
927 cipher, signing_certificate,
928 account_hash_algo_to_camel_hash (signing_algorithm),
929 mime_part, npart, cancellable, error);
931 g_object_unref (cipher);
933 g_object_unref (mime_part);
935 if (!success) {
936 g_object_unref (npart);
937 return FALSE;
940 mime_part = npart;
943 if (context->smime_encrypt) {
944 gboolean success;
946 /* Check to see if we should encrypt to self.
947 * NB: Gets removed immediately after use. */
948 if (encrypt_to_self)
949 g_ptr_array_add (
950 context->recipients, g_strdup (
951 encryption_certificate));
953 cipher = camel_smime_context_new (context->session);
954 camel_smime_context_set_encrypt_key (
955 (CamelSMIMEContext *) cipher, TRUE,
956 encryption_certificate);
958 success = camel_cipher_context_encrypt_sync (
959 cipher, NULL,
960 context->recipients, mime_part,
961 CAMEL_MIME_PART (context->message),
962 cancellable, error);
964 g_object_unref (cipher);
966 if (!success)
967 return FALSE;
969 if (encrypt_to_self)
970 g_ptr_array_set_size (
971 context->recipients,
972 context->recipients->len - 1);
975 /* we replaced the message directly, we don't want to do reparenting foo */
976 if (context->smime_encrypt) {
977 context->skip_content = TRUE;
978 } else {
979 CamelDataWrapper *content;
981 content = camel_medium_get_content (
982 CAMEL_MEDIUM (mime_part));
983 context->top_level_part = g_object_ref (content);
986 g_object_unref (mime_part);
988 return TRUE;
990 #endif
992 static void
993 composer_build_message_thread (GSimpleAsyncResult *simple,
994 EMsgComposer *composer,
995 GCancellable *cancellable)
997 AsyncContext *context;
998 GError *error = NULL;
1000 context = g_simple_async_result_get_op_res_gpointer (simple);
1002 /* Setup working recipient list if we're encrypting. */
1003 if (context->pgp_encrypt || context->smime_encrypt) {
1004 gint ii, jj;
1006 const gchar *types[] = {
1007 CAMEL_RECIPIENT_TYPE_TO,
1008 CAMEL_RECIPIENT_TYPE_CC,
1009 CAMEL_RECIPIENT_TYPE_BCC
1012 context->recipients = g_ptr_array_new_with_free_func (
1013 (GDestroyNotify) g_free);
1014 for (ii = 0; ii < G_N_ELEMENTS (types); ii++) {
1015 CamelInternetAddress *addr;
1016 const gchar *address;
1018 addr = camel_mime_message_get_recipients (
1019 context->message, types[ii]);
1020 for (jj = 0; camel_internet_address_get (addr, jj, NULL, &address); jj++)
1021 g_ptr_array_add (
1022 context->recipients,
1023 g_strdup (address));
1027 if (!composer_build_message_pgp (context, cancellable, &error)) {
1028 g_simple_async_result_take_error (simple, error);
1029 return;
1032 #if defined (HAVE_NSS)
1033 if (!composer_build_message_smime (context, cancellable, &error)) {
1034 g_simple_async_result_take_error (simple, error);
1035 return;
1037 #endif /* HAVE_NSS */
1040 static void
1041 composer_add_evolution_composer_mode_header (CamelMedium *medium,
1042 EMsgComposer *composer)
1044 gboolean html_mode;
1045 EHTMLEditor *editor;
1046 EHTMLEditorView *view;
1048 editor = e_msg_composer_get_editor (composer);
1049 view = e_html_editor_get_view (editor);
1050 html_mode = e_html_editor_view_get_html_mode (view);
1052 camel_medium_add_header (
1053 medium,
1054 "X-Evolution-Composer-Mode",
1055 html_mode ? "text/html" : "text/plain");
1058 static void
1059 composer_add_evolution_format_header (CamelMedium *medium,
1060 ComposerFlags flags)
1062 GString *string;
1064 string = g_string_sized_new (128);
1066 if ((flags & COMPOSER_FLAG_HTML_CONTENT) || (flags & COMPOSER_FLAG_SAVE_DRAFT))
1067 g_string_append (string, "text/html");
1068 else
1069 g_string_append (string, "text/plain");
1071 if (flags & COMPOSER_FLAG_PGP_SIGN)
1072 g_string_append (string, ", pgp-sign");
1074 if (flags & COMPOSER_FLAG_PGP_ENCRYPT)
1075 g_string_append (string, ", pgp-encrypt");
1077 if (flags & COMPOSER_FLAG_SMIME_SIGN)
1078 g_string_append (string, ", smime-sign");
1080 if (flags & COMPOSER_FLAG_SMIME_ENCRYPT)
1081 g_string_append (string, ", smime-encrypt");
1083 camel_medium_add_header (
1084 medium, "X-Evolution-Format", string->str);
1086 g_string_free (string, TRUE);
1089 static void
1090 composer_build_message (EMsgComposer *composer,
1091 ComposerFlags flags,
1092 gint io_priority,
1093 GCancellable *cancellable,
1094 GAsyncReadyCallback callback,
1095 gpointer user_data)
1097 EMsgComposerPrivate *priv;
1098 GSimpleAsyncResult *simple;
1099 AsyncContext *context;
1100 EAttachmentView *view;
1101 EAttachmentStore *store;
1102 EComposerHeaderTable *table;
1103 CamelDataWrapper *html;
1104 ESourceMailIdentity *mi;
1105 const gchar *extension_name;
1106 const gchar *iconv_charset = NULL;
1107 const gchar *identity_uid;
1108 const gchar *organization;
1109 CamelMultipart *body = NULL;
1110 CamelContentType *type;
1111 CamelStream *stream;
1112 CamelStream *mem_stream;
1113 CamelMimePart *part;
1114 GByteArray *data;
1115 ESource *source;
1116 gchar *charset, *message_uid;
1117 const gchar *from_domain;
1118 gint i;
1120 priv = composer->priv;
1121 table = e_msg_composer_get_header_table (composer);
1122 view = e_msg_composer_get_attachment_view (composer);
1123 store = e_attachment_view_get_store (view);
1125 identity_uid = e_composer_header_table_get_identity_uid (table);
1126 source = e_composer_header_table_ref_source (table, identity_uid);
1127 g_return_if_fail (source != NULL);
1129 /* Do all the non-blocking work here, and defer
1130 * any blocking operations to a separate thread. */
1132 context = g_slice_new0 (AsyncContext);
1133 context->source = source; /* takes the reference */
1134 context->session = e_msg_composer_ref_session (composer);
1135 context->from = e_msg_composer_get_from (composer);
1137 if (!(flags & COMPOSER_FLAG_SAVE_DRAFT)) {
1138 if (flags & COMPOSER_FLAG_PGP_SIGN)
1139 context->pgp_sign = TRUE;
1141 if (flags & COMPOSER_FLAG_PGP_ENCRYPT)
1142 context->pgp_encrypt = TRUE;
1144 if (flags & COMPOSER_FLAG_SMIME_SIGN)
1145 context->smime_sign = TRUE;
1147 if (flags & COMPOSER_FLAG_SMIME_ENCRYPT)
1148 context->smime_encrypt = TRUE;
1151 context->need_thread =
1152 context->pgp_sign || context->pgp_encrypt ||
1153 context->smime_sign || context->smime_encrypt;
1155 simple = g_simple_async_result_new (
1156 G_OBJECT (composer), callback,
1157 user_data, composer_build_message);
1159 g_simple_async_result_set_check_cancellable (simple, cancellable);
1161 g_simple_async_result_set_op_res_gpointer (
1162 simple, context, (GDestroyNotify) async_context_free);
1164 /* If this is a redirected message, just tweak the headers. */
1165 if (priv->redirect) {
1166 context->skip_content = TRUE;
1167 context->message = g_object_ref (priv->redirect);
1168 build_message_headers (composer, context->message, TRUE);
1169 g_simple_async_result_complete (simple);
1170 g_object_unref (simple);
1171 return;
1174 context->message = camel_mime_message_new ();
1176 if (context->from && camel_internet_address_get (context->from, 0, NULL, &from_domain)) {
1177 const gchar *at = strchr (from_domain, '@');
1178 if (at)
1179 from_domain = at + 1;
1180 else
1181 from_domain = NULL;
1182 } else {
1183 from_domain = NULL;
1186 if (!from_domain || !*from_domain)
1187 from_domain = "localhost";
1189 message_uid = camel_header_msgid_generate (from_domain);
1191 /* Explicitly generate a Message-ID header here so it's
1192 * consistent for all outbound streams (SMTP, Fcc, etc). */
1193 camel_mime_message_set_message_id (context->message, message_uid);
1194 g_free (message_uid);
1196 build_message_headers (composer, context->message, FALSE);
1197 for (i = 0; i < priv->extra_hdr_names->len; i++) {
1198 camel_medium_add_header (
1199 CAMEL_MEDIUM (context->message),
1200 priv->extra_hdr_names->pdata[i],
1201 priv->extra_hdr_values->pdata[i]);
1204 extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY;
1205 mi = e_source_get_extension (source, extension_name);
1206 organization = e_source_mail_identity_get_organization (mi);
1208 /* Disposition-Notification-To */
1209 if (flags & COMPOSER_FLAG_REQUEST_READ_RECEIPT) {
1210 const gchar *mdn_address;
1212 mdn_address = e_source_mail_identity_get_reply_to (mi);
1213 if (mdn_address == NULL)
1214 mdn_address = e_source_mail_identity_get_address (mi);
1215 if (mdn_address != NULL)
1216 camel_medium_add_header (
1217 CAMEL_MEDIUM (context->message),
1218 "Disposition-Notification-To", mdn_address);
1221 /* X-Priority */
1222 if (flags & COMPOSER_FLAG_PRIORITIZE_MESSAGE)
1223 camel_medium_add_header (
1224 CAMEL_MEDIUM (context->message),
1225 "X-Priority", "1");
1227 /* Organization */
1228 if (organization != NULL && *organization != '\0') {
1229 gchar *encoded_organization;
1231 encoded_organization = camel_header_encode_string (
1232 (const guchar *) organization);
1233 camel_medium_set_header (
1234 CAMEL_MEDIUM (context->message),
1235 "Organization", encoded_organization);
1236 g_free (encoded_organization);
1239 if (flags & COMPOSER_FLAG_SAVE_DRAFT) {
1240 gchar *text;
1241 EHTMLEditor *editor;
1242 EHTMLEditorView *view;
1243 EHTMLEditorSelection *selection;
1245 editor = e_msg_composer_get_editor (composer);
1246 view = e_html_editor_get_view (editor);
1247 selection = e_html_editor_view_get_selection (view);
1249 /* X-Evolution-Format */
1250 composer_add_evolution_format_header (
1251 CAMEL_MEDIUM (context->message), flags);
1253 /* X-Evolution-Composer-Mode */
1254 composer_add_evolution_composer_mode_header (
1255 CAMEL_MEDIUM (context->message), composer);
1257 data = g_byte_array_new ();
1259 e_html_editor_view_embed_styles (view);
1260 e_html_editor_selection_save (selection);
1262 text = e_html_editor_view_get_text_html_for_drafts (view);
1264 e_html_editor_view_remove_embed_styles (view);
1265 e_html_editor_selection_restore (selection);
1266 e_html_editor_view_force_spell_check_in_viewport (view);
1268 g_byte_array_append (data, (guint8 *) text, strlen (text));
1270 g_free (text);
1272 type = camel_content_type_new ("text", "html");
1273 camel_content_type_set_param (type, "charset", "utf-8");
1274 iconv_charset = camel_iconv_charset_name ("utf-8");
1276 goto wrap_drafts_html;
1279 /* Build the text/plain part. */
1281 if (priv->mime_body) {
1282 if (text_requires_quoted_printable (priv->mime_body, -1)) {
1283 context->plain_encoding =
1284 CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE;
1285 } else {
1286 context->plain_encoding = CAMEL_TRANSFER_ENCODING_7BIT;
1287 for (i = 0; priv->mime_body[i]; i++) {
1288 if ((guchar) priv->mime_body[i] > 127) {
1289 context->plain_encoding =
1290 CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE;
1291 break;
1296 data = g_byte_array_new ();
1297 g_byte_array_append (
1298 data, (const guint8 *) priv->mime_body,
1299 strlen (priv->mime_body));
1300 type = camel_content_type_decode (priv->mime_type);
1302 } else {
1303 gchar *text;
1304 EHTMLEditor *editor;
1305 EHTMLEditorView *view;
1307 editor = e_msg_composer_get_editor (composer);
1308 view = e_html_editor_get_view (editor);
1309 data = g_byte_array_new ();
1310 text = e_html_editor_view_get_text_plain (view);
1311 g_byte_array_append (data, (guint8 *) text, strlen (text));
1312 g_free (text);
1314 type = camel_content_type_new ("text", "plain");
1315 charset = best_charset (
1316 data, priv->charset, &context->plain_encoding);
1317 if (charset != NULL) {
1318 camel_content_type_set_param (type, "charset", charset);
1319 iconv_charset = camel_iconv_charset_name (charset);
1320 g_free (charset);
1324 wrap_drafts_html:
1325 mem_stream = camel_stream_mem_new_with_byte_array (data);
1326 stream = camel_stream_filter_new (mem_stream);
1327 g_object_unref (mem_stream);
1329 /* Convert the stream to the appropriate charset. */
1330 if (iconv_charset && g_ascii_strcasecmp (iconv_charset, "UTF-8") != 0)
1331 composer_add_charset_filter (stream, iconv_charset);
1333 /* Encode the stream to quoted-printable if necessary. */
1334 if (context->plain_encoding == CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE)
1335 composer_add_quoted_printable_filter (stream);
1337 /* Construct the content object. This does not block since
1338 * we're constructing the data wrapper from a memory stream. */
1339 context->top_level_part = camel_data_wrapper_new ();
1340 camel_data_wrapper_construct_from_stream_sync (
1341 context->top_level_part, stream, NULL, NULL);
1342 g_object_unref (stream);
1344 context->text_plain_part = g_object_ref (context->top_level_part);
1346 /* Avoid re-encoding the data when adding it to a MIME part. */
1347 if (context->plain_encoding == CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE)
1348 context->top_level_part->encoding = context->plain_encoding;
1350 camel_data_wrapper_set_mime_type_field (
1351 context->top_level_part, type);
1353 camel_content_type_unref (type);
1355 /* Build the text/html part, and wrap it and the text/plain part
1356 * in a multipart/alternative part. Additionally, if there are
1357 * inline images then wrap the multipart/alternative part along
1358 * with the images in a multipart/related part.
1360 * So the structure of all this will be:
1362 * multipart/related
1363 * multipart/alternative
1364 * text/plain
1365 * text/html
1366 * image/<<whatever>>
1367 * image/<<whatever>>
1368 * ...
1371 if ((flags & COMPOSER_FLAG_HTML_CONTENT) != 0 &&
1372 !(flags & COMPOSER_FLAG_SAVE_DRAFT)) {
1373 gchar *text;
1374 guint count;
1375 gsize length;
1376 gboolean pre_encode;
1377 EHTMLEditor *editor;
1378 EHTMLEditorView *view;
1379 GList *inline_images = NULL;
1381 editor = e_msg_composer_get_editor (composer);
1382 view = e_html_editor_get_view (editor);
1384 data = g_byte_array_new ();
1385 text = e_html_editor_view_get_text_html (view, from_domain, &inline_images);
1386 length = strlen (text);
1387 g_byte_array_append (data, (guint8 *) text, (guint) length);
1388 pre_encode = text_requires_quoted_printable (text, length);
1389 g_free (text);
1391 mem_stream = camel_stream_mem_new_with_byte_array (data);
1392 stream = camel_stream_filter_new (mem_stream);
1393 g_object_unref (mem_stream);
1395 if (pre_encode)
1396 composer_add_quoted_printable_filter (stream);
1398 /* Construct the content object. This does not block since
1399 * we're constructing the data wrapper from a memory stream. */
1400 html = camel_data_wrapper_new ();
1401 camel_data_wrapper_construct_from_stream_sync (
1402 html, stream, NULL, NULL);
1403 g_object_unref (stream);
1405 camel_data_wrapper_set_mime_type (
1406 html, "text/html; charset=utf-8");
1408 /* Avoid re-encoding the data when adding it to a MIME part. */
1409 if (pre_encode)
1410 html->encoding =
1411 CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE;
1413 /* Build the multipart/alternative */
1414 body = camel_multipart_new ();
1415 camel_data_wrapper_set_mime_type (
1416 CAMEL_DATA_WRAPPER (body), "multipart/alternative");
1417 camel_multipart_set_boundary (body, NULL);
1419 /* Add the text/plain part. */
1420 part = camel_mime_part_new ();
1421 camel_medium_set_content (
1422 CAMEL_MEDIUM (part), context->top_level_part);
1423 camel_mime_part_set_encoding (part, context->plain_encoding);
1424 camel_multipart_add_part (body, part);
1425 g_object_unref (part);
1427 /* Add the text/html part. */
1428 part = camel_mime_part_new ();
1429 camel_medium_set_content (CAMEL_MEDIUM (part), html);
1430 camel_mime_part_set_encoding (
1431 part, CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE);
1432 camel_multipart_add_part (body, part);
1433 g_object_unref (part);
1435 g_object_unref (context->top_level_part);
1436 g_object_unref (html);
1438 /* If there are inlined images, construct a multipart/related
1439 * containing the multipart/alternative and the images. */
1440 count = g_list_length (inline_images);
1441 if (count > 0) {
1442 guint ii;
1443 CamelMultipart *html_with_images;
1445 html_with_images = camel_multipart_new ();
1446 camel_data_wrapper_set_mime_type (
1447 CAMEL_DATA_WRAPPER (html_with_images),
1448 "multipart/related; "
1449 "type=\"multipart/alternative\"");
1450 camel_multipart_set_boundary (html_with_images, NULL);
1452 part = camel_mime_part_new ();
1453 camel_medium_set_content (
1454 CAMEL_MEDIUM (part),
1455 CAMEL_DATA_WRAPPER (body));
1456 camel_multipart_add_part (html_with_images, part);
1457 g_object_unref (part);
1459 g_object_unref (body);
1461 for (ii = 0; ii < count; ii++) {
1462 CamelMimePart *part = g_list_nth_data (inline_images, ii);
1463 camel_multipart_add_part (
1464 html_with_images, part);
1467 context->top_level_part =
1468 CAMEL_DATA_WRAPPER (html_with_images);
1469 } else {
1470 context->top_level_part =
1471 CAMEL_DATA_WRAPPER (body);
1473 g_list_free_full (inline_images, g_object_unref);
1476 /* If there are attachments, wrap what we've built so far
1477 * along with the attachments in a multipart/mixed part. */
1478 if (e_attachment_store_get_num_attachments (store) > 0) {
1479 CamelMultipart *multipart = camel_multipart_new ();
1481 /* Generate a random boundary. */
1482 camel_multipart_set_boundary (multipart, NULL);
1484 part = camel_mime_part_new ();
1485 camel_medium_set_content (
1486 CAMEL_MEDIUM (part),
1487 context->top_level_part);
1488 if (context->top_level_part == context->text_plain_part)
1489 camel_mime_part_set_encoding (
1490 part, context->plain_encoding);
1491 camel_multipart_add_part (multipart, part);
1492 g_object_unref (part);
1494 e_attachment_store_add_to_multipart (
1495 store, multipart, priv->charset);
1497 g_object_unref (context->top_level_part);
1498 context->top_level_part = CAMEL_DATA_WRAPPER (multipart);
1501 /* Run any blocking operations in a separate thread. */
1502 if (context->need_thread)
1503 g_simple_async_result_run_in_thread (
1504 simple, (GSimpleAsyncThreadFunc)
1505 composer_build_message_thread,
1506 io_priority, cancellable);
1507 else
1508 g_simple_async_result_complete (simple);
1510 g_object_unref (simple);
1513 static CamelMimeMessage *
1514 composer_build_message_finish (EMsgComposer *composer,
1515 GAsyncResult *result,
1516 GError **error)
1518 GSimpleAsyncResult *simple;
1519 AsyncContext *context;
1521 g_return_val_if_fail (
1522 g_simple_async_result_is_valid (
1523 result, G_OBJECT (composer), composer_build_message), NULL);
1525 simple = G_SIMPLE_ASYNC_RESULT (result);
1526 context = g_simple_async_result_get_op_res_gpointer (simple);
1528 if (g_simple_async_result_propagate_error (simple, error))
1529 return NULL;
1531 /* Finalize some details before returning. */
1533 if (!context->skip_content)
1534 camel_medium_set_content (
1535 CAMEL_MEDIUM (context->message),
1536 context->top_level_part);
1538 if (context->top_level_part == context->text_plain_part)
1539 camel_mime_part_set_encoding (
1540 CAMEL_MIME_PART (context->message),
1541 context->plain_encoding);
1543 return g_object_ref (context->message);
1546 /* Signatures */
1548 static void
1549 set_editor_text (EMsgComposer *composer,
1550 const gchar *text,
1551 gboolean is_html,
1552 gboolean set_signature)
1554 EHTMLEditor *editor;
1555 EHTMLEditorView *view;
1557 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
1558 g_return_if_fail (text != NULL);
1560 editor = e_msg_composer_get_editor (composer);
1561 view = e_html_editor_get_view (editor);
1563 if (is_html)
1564 e_html_editor_view_set_text_html (view, text);
1565 else
1566 e_html_editor_view_set_text_plain (view, text);
1568 if (set_signature)
1569 e_composer_update_signature (composer);
1572 /* Miscellaneous callbacks. */
1574 static void
1575 attachment_store_changed_cb (EMsgComposer *composer)
1577 EHTMLEditor *editor;
1578 EHTMLEditorView *view;
1580 /* Mark the editor as changed so it prompts about unsaved
1581 * changes on close. */
1582 editor = e_msg_composer_get_editor (composer);
1583 if (editor) {
1584 view = e_html_editor_get_view (editor);
1585 e_html_editor_view_set_changed (view, TRUE);
1589 static void
1590 msg_composer_subject_changed_cb (EMsgComposer *composer)
1592 EComposerHeaderTable *table;
1593 const gchar *subject;
1595 table = e_msg_composer_get_header_table (composer);
1596 subject = e_composer_header_table_get_subject (table);
1598 if (subject == NULL || *subject == '\0')
1599 subject = _("Compose Message");
1601 gtk_window_set_title (GTK_WINDOW (composer), subject);
1604 static void
1605 msg_composer_mail_identity_changed_cb (EMsgComposer *composer)
1607 EHTMLEditor *editor;
1608 EHTMLEditorView *view;
1609 EMailSignatureComboBox *combo_box;
1610 ESourceMailComposition *mc;
1611 ESourceOpenPGP *pgp;
1612 ESourceSMIME *smime;
1613 EComposerHeaderTable *table;
1614 GtkToggleAction *action;
1615 ESource *source;
1616 gboolean active;
1617 gboolean can_sign;
1618 gboolean pgp_sign;
1619 gboolean pgp_encrypt;
1620 gboolean smime_sign;
1621 gboolean smime_encrypt;
1622 gboolean is_message_from_edit_as_new;
1623 const gchar *extension_name;
1624 const gchar *uid;
1626 table = e_msg_composer_get_header_table (composer);
1627 uid = e_composer_header_table_get_identity_uid (table);
1629 /* Silently return if no identity is selected. */
1630 if (uid == NULL)
1631 return;
1633 source = e_composer_header_table_ref_source (table, uid);
1634 g_return_if_fail (source != NULL);
1636 extension_name = E_SOURCE_EXTENSION_MAIL_COMPOSITION;
1637 mc = e_source_get_extension (source, extension_name);
1639 extension_name = E_SOURCE_EXTENSION_OPENPGP;
1640 pgp = e_source_get_extension (source, extension_name);
1641 pgp_sign = e_source_openpgp_get_sign_by_default (pgp);
1642 pgp_encrypt = e_source_openpgp_get_encrypt_by_default (pgp);
1644 extension_name = E_SOURCE_EXTENSION_SMIME;
1645 smime = e_source_get_extension (source, extension_name);
1646 smime_sign = e_source_smime_get_sign_by_default (smime);
1647 smime_encrypt = e_source_smime_get_encrypt_by_default (smime);
1649 can_sign =
1650 (composer->priv->mime_type == NULL) ||
1651 e_source_mail_composition_get_sign_imip (mc) ||
1652 (g_ascii_strncasecmp (
1653 composer->priv->mime_type,
1654 "text/calendar", 13) != 0);
1656 editor = e_msg_composer_get_editor (composer);
1657 view = e_html_editor_get_view (editor);
1658 is_message_from_edit_as_new =
1659 e_html_editor_view_is_message_from_edit_as_new (view);
1661 action = GTK_TOGGLE_ACTION (ACTION (PGP_SIGN));
1662 active = gtk_toggle_action_get_active (action);
1663 active &= is_message_from_edit_as_new;
1664 active |= (can_sign && pgp_sign);
1665 gtk_toggle_action_set_active (action, active);
1667 action = GTK_TOGGLE_ACTION (ACTION (PGP_ENCRYPT));
1668 active = gtk_toggle_action_get_active (action);
1669 active &= is_message_from_edit_as_new;
1670 active |= pgp_encrypt;
1671 gtk_toggle_action_set_active (action, active);
1673 action = GTK_TOGGLE_ACTION (ACTION (SMIME_SIGN));
1674 active = gtk_toggle_action_get_active (action);
1675 active &= is_message_from_edit_as_new;
1676 active |= (can_sign && smime_sign);
1677 gtk_toggle_action_set_active (action, active);
1679 action = GTK_TOGGLE_ACTION (ACTION (SMIME_ENCRYPT));
1680 active = gtk_toggle_action_get_active (action);
1681 active &= is_message_from_edit_as_new;
1682 active |= smime_encrypt;
1683 gtk_toggle_action_set_active (action, active);
1685 combo_box = e_composer_header_table_get_signature_combo_box (table);
1686 e_mail_signature_combo_box_set_identity_uid (combo_box, uid);
1688 g_object_unref (source);
1691 static void
1692 msg_composer_paste_clipboard_targets_cb (GtkClipboard *clipboard,
1693 GdkAtom *targets,
1694 gint n_targets,
1695 EMsgComposer *composer)
1697 if (targets == NULL || n_targets < 0)
1698 return;
1700 /* Order is important here to ensure common use cases are
1701 * handled correctly. See GNOME bug #603715 for details. */
1703 if (gtk_targets_include_uri (targets, n_targets)) {
1704 e_composer_paste_uris (composer, clipboard);
1705 return;
1708 if (e_targets_include_html (targets, n_targets)) {
1709 e_composer_paste_html (composer, clipboard);
1710 return;
1713 if (gtk_targets_include_text (targets, n_targets)) {
1714 e_composer_paste_text (composer, clipboard);
1715 return;
1718 if (gtk_targets_include_image (targets, n_targets, TRUE)) {
1719 e_composer_paste_image (composer, clipboard);
1720 return;
1724 static void
1725 msg_composer_paste_primary_clipboard_cb (EHTMLEditorView *view,
1726 EMsgComposer *composer)
1728 GtkClipboard *clipboard;
1730 clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
1732 gtk_clipboard_request_targets (
1733 clipboard, (GtkClipboardTargetsReceivedFunc)
1734 msg_composer_paste_clipboard_targets_cb, composer);
1736 g_signal_stop_emission_by_name (view, "paste-primary-clipboard");
1739 static void
1740 msg_composer_paste_clipboard_cb (EHTMLEditorView *view,
1741 EMsgComposer *composer)
1743 GtkClipboard *clipboard;
1745 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1747 gtk_clipboard_request_targets (
1748 clipboard, (GtkClipboardTargetsReceivedFunc)
1749 msg_composer_paste_clipboard_targets_cb, composer);
1751 g_signal_stop_emission_by_name (view, "paste-clipboard");
1754 static gboolean
1755 msg_composer_drag_motion_cb (GtkWidget *widget,
1756 GdkDragContext *context,
1757 gint x,
1758 gint y,
1759 guint time,
1760 EMsgComposer *composer)
1762 GtkWidget *source_widget;
1763 EHTMLEditor *editor = e_msg_composer_get_editor (composer);
1764 EHTMLEditorView *editor_view = e_html_editor_get_view (editor);
1766 source_widget = gtk_drag_get_source_widget (context);
1767 /* When we are doind DnD just inside the web view, the DnD is supposed
1768 * to move things around. */
1769 if (E_IS_HTML_EDITOR_VIEW (source_widget)) {
1770 if ((gpointer) editor_view == (gpointer) source_widget) {
1771 gdk_drag_status (context, GDK_ACTION_MOVE, time);
1773 return FALSE;
1777 gdk_drag_status (context, GDK_ACTION_COPY, time);
1779 return FALSE;
1782 static gboolean
1783 msg_composer_drag_drop_cb (GtkWidget *widget,
1784 GdkDragContext *context,
1785 gint x,
1786 gint y,
1787 guint time,
1788 EMsgComposer *composer)
1790 GdkAtom target;
1791 GtkWidget *source_widget;
1793 /* When we are doind DnD just inside the web view, the DnD is supposed
1794 * to move things around. */
1795 source_widget = gtk_drag_get_source_widget (context);
1796 if (E_IS_HTML_EDITOR_VIEW (source_widget)) {
1797 EHTMLEditor *editor = e_msg_composer_get_editor (composer);
1798 EHTMLEditorView *editor_view = e_html_editor_get_view (editor);
1800 if ((gpointer) editor_view == (gpointer) source_widget)
1801 return FALSE;
1804 target = gtk_drag_dest_find_target (widget, context, NULL);
1805 if (target == GDK_NONE)
1806 gdk_drag_status (context, 0, time);
1807 else {
1808 /* Prevent WebKit from pasting the URI of file into the view. */
1809 if (composer->priv->dnd_is_uri)
1810 g_signal_stop_emission_by_name (widget, "drag-drop");
1812 composer->priv->dnd_is_uri = FALSE;
1814 gdk_drag_status (context, GDK_ACTION_COPY, time);
1815 composer->priv->drop_occured = TRUE;
1816 gtk_drag_get_data (widget, context, target, time);
1819 return FALSE;
1822 static void
1823 msg_composer_drag_data_received_after_cb (GtkWidget *widget,
1824 GdkDragContext *context,
1825 gint x,
1826 gint y,
1827 GtkSelectionData *selection,
1828 guint info,
1829 guint time,
1830 EMsgComposer *composer)
1832 EHTMLEditor *editor;
1833 EHTMLEditorView *view;
1834 WebKitDOMDocument *document;
1835 WebKitDOMDOMWindow *dom_window;
1836 WebKitDOMDOMSelection *dom_selection;
1838 if (!composer->priv->drop_occured)
1839 return;
1841 composer->priv->drop_occured = FALSE;
1843 editor = e_msg_composer_get_editor (composer);
1844 view = e_html_editor_get_view (editor);
1846 document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
1847 dom_window = webkit_dom_document_get_default_view (document);
1848 dom_selection = webkit_dom_dom_window_get_selection (dom_window);
1850 /* When text is DnD'ed into the view, WebKit will select it. So let's
1851 * collapse it to its end to have the caret after the DnD'ed text. */
1852 webkit_dom_dom_selection_collapse_to_end (dom_selection, NULL);
1854 e_html_editor_view_check_magic_links (view, FALSE);
1855 /* Also force spell check on view. */
1856 e_html_editor_view_force_spell_check_in_viewport (view);
1859 static gchar *
1860 next_uri (guchar **uri_list,
1861 gint *len,
1862 gint *list_len)
1864 guchar *uri, *begin;
1866 begin = *uri_list;
1867 *len = 0;
1868 while (**uri_list && **uri_list != '\n' && **uri_list != '\r' && *list_len) {
1869 (*uri_list) ++;
1870 (*len) ++;
1871 (*list_len) --;
1874 uri = (guchar *) g_strndup ((gchar *) begin, *len);
1876 while ((!**uri_list || **uri_list == '\n' || **uri_list == '\r') && *list_len) {
1877 (*uri_list) ++;
1878 (*list_len) --;
1881 return (gchar *) uri;
1884 static void
1885 msg_composer_drag_data_received_cb (GtkWidget *widget,
1886 GdkDragContext *context,
1887 gint x,
1888 gint y,
1889 GtkSelectionData *selection,
1890 guint info,
1891 guint time,
1892 EMsgComposer *composer)
1894 EHTMLEditor *editor;
1895 EHTMLEditorView *html_editor_view;
1896 EHTMLEditorSelection *editor_selection;
1897 gboolean html_mode, same_widget = FALSE;
1898 GtkWidget *source_widget;
1900 editor = e_msg_composer_get_editor (composer);
1901 html_editor_view = e_html_editor_get_view (editor);
1902 html_mode = e_html_editor_view_get_html_mode (html_editor_view);
1903 editor_selection = e_html_editor_view_get_selection (html_editor_view);
1905 /* When we are doind DnD just inside the web view, the DnD is supposed
1906 * to move things around. */
1907 source_widget = gtk_drag_get_source_widget (context);
1908 if (E_IS_HTML_EDITOR_VIEW (source_widget)) {
1909 EHTMLEditor *editor = e_msg_composer_get_editor (composer);
1910 EHTMLEditorView *editor_view = e_html_editor_get_view (editor);
1912 if ((gpointer) editor_view == (gpointer) source_widget)
1913 same_widget = TRUE;
1916 /* Leave DnD inside the view on WebKit. */
1917 if (composer->priv->drop_occured && same_widget) {
1918 gdk_drag_status (context, 0, time);
1919 return;
1922 if (!composer->priv->drop_occured) {
1923 if (!same_widget) {
1924 /* Check if we are DnD'ing some URI, if so WebKit will
1925 * insert the URI into the view and we have to prevent it
1926 * from doing that. */
1927 if (info == DND_TARGET_TYPE_TEXT_URI_LIST) {
1928 gchar **uris;
1930 uris = gtk_selection_data_get_uris (selection);
1931 composer->priv->dnd_is_uri = uris != NULL;
1932 g_strfreev (uris);
1935 return;
1938 composer->priv->dnd_is_uri = FALSE;
1940 /* Leave the text on WebKit to handle it. */
1941 if (info == DND_TARGET_TYPE_UTF8_STRING ||
1942 info == DND_TARGET_TYPE_STRING ||
1943 info == DND_TARGET_TYPE_TEXT_PLAIN ||
1944 info == DND_TARGET_TYPE_TEXT_PLAIN_UTF8) {
1945 gdk_drag_status (context, 0, time);
1946 return;
1949 if (info == DND_TARGET_TYPE_TEXT_HTML) {
1950 const guchar *data;
1951 gint length;
1952 gint list_len, len;
1953 gchar *text;
1955 data = gtk_selection_data_get_data (selection);
1956 length = gtk_selection_data_get_length (selection);
1958 if (!data || length < 0) {
1959 gtk_drag_finish (context, FALSE, FALSE, time);
1960 return;
1963 e_html_editor_selection_set_on_point (editor_selection, x, y);
1965 list_len = length;
1966 do {
1967 text = next_uri ((guchar **) &data, &len, &list_len);
1968 e_html_editor_selection_insert_html (editor_selection, text);
1969 g_free (text);
1970 } while (list_len);
1972 e_html_editor_view_check_magic_links (html_editor_view, FALSE);
1973 e_html_editor_view_force_spell_check_in_viewport (html_editor_view);
1975 e_html_editor_selection_scroll_to_caret (editor_selection);
1977 gtk_drag_finish (context, TRUE, FALSE, time);
1978 return;
1981 /* HTML mode has a few special cases for drops... */
1982 /* If we're receiving URIs and -all- the URIs point to
1983 * image files, we want the image(s) to be inserted in
1984 * the message body. */
1985 if (html_mode && e_composer_selection_is_image_uris (composer, selection)) {
1986 const guchar *data;
1987 gint length;
1988 gint list_len, len;
1989 gchar *uri;
1991 data = gtk_selection_data_get_data (selection);
1992 length = gtk_selection_data_get_length (selection);
1994 if (!data || length < 0) {
1995 gtk_drag_finish (context, FALSE, FALSE, time);
1996 return;
1999 e_html_editor_selection_set_on_point (editor_selection, x, y);
2001 list_len = length;
2002 do {
2003 uri = next_uri ((guchar **) &data, &len, &list_len);
2004 e_html_editor_selection_insert_image (editor_selection, uri);
2005 g_free (uri);
2006 } while (list_len);
2008 gtk_drag_finish (context, TRUE, FALSE, time);
2009 } else if (html_mode && e_composer_selection_is_base64_uris (composer, selection)) {
2010 const guchar *data;
2011 gint length;
2012 gint list_len, len;
2013 gchar *uri;
2015 data = gtk_selection_data_get_data (selection);
2016 length = gtk_selection_data_get_length (selection);
2018 if (!data || length < 0) {
2019 gtk_drag_finish (context, FALSE, FALSE, time);
2020 return;
2023 e_html_editor_selection_set_on_point (editor_selection, x, y);
2025 list_len = length;
2026 do {
2027 uri = next_uri ((guchar **) &data, &len, &list_len);
2028 e_html_editor_selection_insert_image (editor_selection, uri);
2029 g_free (uri);
2030 } while (list_len);
2032 gtk_drag_finish (context, TRUE, FALSE, time);
2033 } else {
2034 EAttachmentView *view = e_msg_composer_get_attachment_view (composer);
2036 /* Forward the data to the attachment view. Note that calling
2037 * e_attachment_view_drag_data_received() will not work because
2038 * that function only handles the case where all the other drag
2039 * handlers have failed. */
2040 e_attachment_paned_drag_data_received (
2041 E_ATTACHMENT_PANED (view),
2042 context, x, y, selection, info, time);
2046 static void
2047 msg_composer_notify_header_cb (EMsgComposer *composer)
2049 EHTMLEditor *editor;
2050 EHTMLEditorView *view;
2052 editor = e_msg_composer_get_editor (composer);
2053 view = e_html_editor_get_view (editor);
2054 e_html_editor_view_set_changed (view, TRUE);
2057 static gboolean
2058 msg_composer_delete_event_cb (EMsgComposer *composer)
2060 EShell *shell;
2061 GtkApplication *application;
2062 GList *windows;
2064 shell = e_msg_composer_get_shell (composer);
2066 /* If the "async" action group is insensitive, it means an
2067 * asynchronous operation is in progress. Block the event. */
2068 if (!gtk_action_group_get_sensitive (composer->priv->async_actions))
2069 return TRUE;
2071 application = GTK_APPLICATION (shell);
2072 windows = gtk_application_get_windows (application);
2074 if (g_list_length (windows) == 1) {
2075 /* This is the last watched window, use the quit
2076 * mechanism to have a draft saved properly */
2077 e_shell_quit (shell, E_SHELL_QUIT_ACTION);
2078 } else {
2079 /* There are more watched windows opened,
2080 * invoke only a close action */
2081 gtk_action_activate (ACTION (CLOSE));
2084 return TRUE;
2087 static void
2088 msg_composer_realize_cb (EMsgComposer *composer)
2090 GSettings *settings;
2091 GtkAction *action;
2093 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
2095 action = ACTION (TOOLBAR_PGP_SIGN);
2096 if (gtk_action_get_visible (action) && !gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
2097 gtk_action_set_visible (action, FALSE);
2099 action = ACTION (TOOLBAR_PGP_ENCRYPT);
2100 if (gtk_action_get_visible (action) && !gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
2101 gtk_action_set_visible (action, FALSE);
2103 action = ACTION (TOOLBAR_SMIME_SIGN);
2104 if (gtk_action_get_visible (action) && !gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
2105 gtk_action_set_visible (action, FALSE);
2107 action = ACTION (TOOLBAR_SMIME_ENCRYPT);
2108 if (gtk_action_get_visible (action) && !gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
2109 gtk_action_set_visible (action, FALSE);
2111 settings = e_util_ref_settings ("org.gnome.evolution.mail");
2113 if (g_settings_get_boolean (settings, "composer-toolbar-show-sign-encrypt")) {
2114 EComposerHeaderTable *table;
2115 ESource *source;
2116 const gchar *identity_uid;
2118 table = e_msg_composer_get_header_table (composer);
2119 identity_uid = e_composer_header_table_get_identity_uid (table);
2120 source = e_composer_header_table_ref_source (table, identity_uid);
2122 if (source) {
2123 if (e_source_has_extension (source, E_SOURCE_EXTENSION_OPENPGP)) {
2124 gchar *key_id;
2126 key_id = e_source_openpgp_dup_key_id (e_source_get_extension (source, E_SOURCE_EXTENSION_OPENPGP));
2128 if (key_id && *key_id) {
2129 action = ACTION (TOOLBAR_PGP_SIGN);
2130 gtk_action_set_visible (action, TRUE);
2132 action = ACTION (TOOLBAR_PGP_ENCRYPT);
2133 gtk_action_set_visible (action, TRUE);
2136 g_free (key_id);
2139 if (e_source_has_extension (source, E_SOURCE_EXTENSION_SMIME)) {
2140 ESourceSMIME *smime_extension;
2141 gchar *certificate;
2143 smime_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_SMIME);
2145 certificate = e_source_smime_dup_signing_certificate (smime_extension);
2146 if (certificate && *certificate)
2147 gtk_action_set_visible (ACTION (TOOLBAR_SMIME_SIGN), TRUE);
2148 g_free (certificate);
2150 certificate = e_source_smime_dup_encryption_certificate (smime_extension);
2151 if (certificate && *certificate)
2152 gtk_action_set_visible (ACTION (TOOLBAR_SMIME_ENCRYPT), TRUE);
2153 g_free (certificate);
2156 g_clear_object (&source);
2160 g_clear_object (&settings);
2163 static void
2164 msg_composer_prepare_for_quit_cb (EShell *shell,
2165 EActivity *activity,
2166 EMsgComposer *composer)
2168 if (e_msg_composer_is_exiting (composer)) {
2169 /* needs save draft first */
2170 g_object_ref (activity);
2171 g_object_weak_ref (
2172 G_OBJECT (composer), (GWeakNotify)
2173 g_object_unref, activity);
2174 gtk_action_activate (ACTION (SAVE_DRAFT));
2178 static void
2179 msg_composer_quit_requested_cb (EShell *shell,
2180 EShellQuitReason reason,
2181 EMsgComposer *composer)
2183 if (e_msg_composer_is_exiting (composer)) {
2184 g_signal_handlers_disconnect_by_func (
2185 shell, msg_composer_quit_requested_cb, composer);
2186 g_signal_handlers_disconnect_by_func (
2187 shell, msg_composer_prepare_for_quit_cb, composer);
2188 } else if (!e_msg_composer_can_close (composer, FALSE) &&
2189 !e_msg_composer_is_exiting (composer)) {
2190 e_shell_cancel_quit (shell);
2194 static void
2195 msg_composer_set_shell (EMsgComposer *composer,
2196 EShell *shell)
2198 g_return_if_fail (E_IS_SHELL (shell));
2199 g_return_if_fail (composer->priv->shell == NULL);
2201 composer->priv->shell = shell;
2203 g_object_add_weak_pointer (
2204 G_OBJECT (shell), &composer->priv->shell);
2207 static void
2208 msg_composer_set_property (GObject *object,
2209 guint property_id,
2210 const GValue *value,
2211 GParamSpec *pspec)
2213 switch (property_id) {
2214 case PROP_SHELL:
2215 msg_composer_set_shell (
2216 E_MSG_COMPOSER (object),
2217 g_value_get_object (value));
2218 return;
2221 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
2224 static void
2225 msg_composer_get_property (GObject *object,
2226 guint property_id,
2227 GValue *value,
2228 GParamSpec *pspec)
2230 switch (property_id) {
2231 case PROP_BUSY:
2232 g_value_set_boolean (
2233 value, e_msg_composer_is_busy (
2234 E_MSG_COMPOSER (object)));
2235 return;
2237 case PROP_FOCUS_TRACKER:
2238 g_value_set_object (
2239 value, e_msg_composer_get_focus_tracker (
2240 E_MSG_COMPOSER (object)));
2241 return;
2243 case PROP_SHELL:
2244 g_value_set_object (
2245 value, e_msg_composer_get_shell (
2246 E_MSG_COMPOSER (object)));
2247 return;
2250 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
2253 static void
2254 msg_composer_finalize (GObject *object)
2256 EMsgComposer *composer = E_MSG_COMPOSER (object);
2258 e_composer_private_finalize (composer);
2260 /* Chain up to parent's finalize() method. */
2261 G_OBJECT_CLASS (e_msg_composer_parent_class)->finalize (object);
2264 static void
2265 msg_composer_gallery_drag_data_get (GtkIconView *icon_view,
2266 GdkDragContext *context,
2267 GtkSelectionData *selection_data,
2268 guint target_type,
2269 guint time)
2271 GtkTreePath *path;
2272 GtkCellRenderer *cell;
2273 GtkTreeModel *model;
2274 GtkTreeIter iter;
2275 GdkAtom target;
2276 gchar *str_data;
2278 if (!gtk_icon_view_get_cursor (icon_view, &path, &cell))
2279 return;
2281 target = gtk_selection_data_get_target (selection_data);
2283 model = gtk_icon_view_get_model (icon_view);
2284 gtk_tree_model_get_iter (model, &iter, path);
2285 gtk_tree_model_get (model, &iter, 1, &str_data, -1);
2286 gtk_tree_path_free (path);
2288 /* only supports "text/uri-list" */
2289 gtk_selection_data_set (
2290 selection_data, target, 8,
2291 (guchar *) str_data, strlen (str_data));
2292 g_free (str_data);
2295 static void
2296 composer_notify_activity_cb (EActivityBar *activity_bar,
2297 GParamSpec *pspec,
2298 EMsgComposer *composer)
2300 EHTMLEditor *editor;
2301 EHTMLEditorView *view;
2302 WebKitWebView *web_view;
2303 gboolean editable;
2304 gboolean busy;
2306 busy = (e_activity_bar_get_activity (activity_bar) != NULL);
2308 if (busy == composer->priv->busy)
2309 return;
2311 composer->priv->busy = busy;
2313 if (busy)
2314 e_msg_composer_save_focused_widget (composer);
2316 editor = e_msg_composer_get_editor (composer);
2317 view = e_html_editor_get_view (editor);
2318 web_view = WEBKIT_WEB_VIEW (view);
2320 if (busy) {
2321 editable = webkit_web_view_get_editable (web_view);
2322 webkit_web_view_set_editable (web_view, FALSE);
2323 composer->priv->saved_editable = editable;
2324 } else {
2325 editable = composer->priv->saved_editable;
2326 webkit_web_view_set_editable (web_view, editable);
2329 g_object_notify (G_OBJECT (composer), "busy");
2331 if (!busy)
2332 e_msg_composer_restore_focus_on_composer (composer);
2335 static void
2336 msg_composer_constructed (GObject *object)
2338 EShell *shell;
2339 EMsgComposer *composer;
2340 EActivityBar *activity_bar;
2341 EAttachmentView *view;
2342 EAttachmentStore *store;
2343 EComposerHeaderTable *table;
2344 EHTMLEditor *editor;
2345 EHTMLEditorView *html_editor_view;
2346 GtkUIManager *ui_manager;
2347 GtkToggleAction *action;
2348 GtkTargetList *target_list;
2349 GtkTargetEntry *targets;
2350 gint n_targets;
2351 GSettings *settings;
2352 const gchar *id;
2353 gboolean active;
2355 composer = E_MSG_COMPOSER (object);
2357 shell = e_msg_composer_get_shell (composer);
2359 e_composer_private_constructed (composer);
2361 editor = e_msg_composer_get_editor (composer);
2362 html_editor_view = e_html_editor_get_view (editor);
2363 ui_manager = e_html_editor_get_ui_manager (editor);
2364 view = e_msg_composer_get_attachment_view (composer);
2365 table = E_COMPOSER_HEADER_TABLE (composer->priv->header_table);
2367 gtk_window_set_title (GTK_WINDOW (composer), _("Compose Message"));
2368 gtk_window_set_icon_name (GTK_WINDOW (composer), "mail-message-new");
2369 gtk_window_set_default_size (GTK_WINDOW (composer), 600, 500);
2371 g_signal_connect (
2372 object, "delete-event",
2373 G_CALLBACK (msg_composer_delete_event_cb), NULL);
2375 g_signal_connect (
2376 object, "realize",
2377 G_CALLBACK (msg_composer_realize_cb), NULL);
2379 gtk_application_add_window (
2380 GTK_APPLICATION (shell), GTK_WINDOW (object));
2382 g_signal_connect (
2383 shell, "quit-requested",
2384 G_CALLBACK (msg_composer_quit_requested_cb), composer);
2386 g_signal_connect (
2387 shell, "prepare-for-quit",
2388 G_CALLBACK (msg_composer_prepare_for_quit_cb), composer);
2390 /* Restore Persistent State */
2392 e_restore_window (
2393 GTK_WINDOW (composer),
2394 "/org/gnome/evolution/mail/composer-window/",
2395 E_RESTORE_WINDOW_SIZE);
2397 activity_bar = e_html_editor_get_activity_bar (editor);
2398 g_signal_connect (
2399 activity_bar, "notify::activity",
2400 G_CALLBACK (composer_notify_activity_cb), composer);
2403 /* Honor User Preferences */
2405 /* FIXME This should be an EMsgComposer property. */
2406 settings = e_util_ref_settings ("org.gnome.evolution.mail");
2407 action = GTK_TOGGLE_ACTION (ACTION (REQUEST_READ_RECEIPT));
2408 active = g_settings_get_boolean (settings, "composer-request-receipt");
2409 gtk_toggle_action_set_active (action, active);
2411 action = GTK_TOGGLE_ACTION (ACTION (UNICODE_SMILEYS));
2412 active = g_settings_get_boolean (settings, "composer-unicode-smileys");
2413 gtk_toggle_action_set_active (action, active);
2414 g_object_unref (settings);
2416 /* Clipboard Support */
2418 g_signal_connect (
2419 html_editor_view, "paste-clipboard",
2420 G_CALLBACK (msg_composer_paste_clipboard_cb), composer);
2422 g_signal_connect (
2423 html_editor_view, "paste-primary-clipboard",
2424 G_CALLBACK (msg_composer_paste_primary_clipboard_cb), composer);
2426 /* Drag-and-Drop Support */
2428 g_signal_connect (
2429 html_editor_view, "drag-motion",
2430 G_CALLBACK (msg_composer_drag_motion_cb), composer);
2432 g_signal_connect (
2433 html_editor_view, "drag-drop",
2434 G_CALLBACK (msg_composer_drag_drop_cb), composer);
2436 g_signal_connect (
2437 html_editor_view, "drag-data-received",
2438 G_CALLBACK (msg_composer_drag_data_received_cb), composer);
2440 /* Used for fixing various stuff after WebKit processed the DnD data. */
2441 g_signal_connect_after (
2442 html_editor_view, "drag-data-received",
2443 G_CALLBACK (msg_composer_drag_data_received_after_cb), composer);
2445 g_signal_connect (
2446 composer->priv->gallery_icon_view, "drag-data-get",
2447 G_CALLBACK (msg_composer_gallery_drag_data_get), NULL);
2449 /* Configure Headers */
2451 composer->priv->notify_destinations_bcc_handler = e_signal_connect_notify_swapped (
2452 table, "notify::destinations-bcc",
2453 G_CALLBACK (msg_composer_notify_header_cb), composer);
2454 composer->priv->notify_destinations_cc_handler = e_signal_connect_notify_swapped (
2455 table, "notify::destinations-cc",
2456 G_CALLBACK (msg_composer_notify_header_cb), composer);
2457 composer->priv->notify_destinations_to_handler = e_signal_connect_notify_swapped (
2458 table, "notify::destinations-to",
2459 G_CALLBACK (msg_composer_notify_header_cb), composer);
2460 composer->priv->notify_identity_uid_handler = e_signal_connect_notify_swapped (
2461 table, "notify::identity-uid",
2462 G_CALLBACK (msg_composer_mail_identity_changed_cb), composer);
2463 composer->priv->notify_reply_to_handler = e_signal_connect_notify_swapped (
2464 table, "notify::reply-to",
2465 G_CALLBACK (msg_composer_notify_header_cb), composer);
2466 composer->priv->notify_signature_uid_handler = e_signal_connect_notify_swapped (
2467 table, "notify::signature-uid",
2468 G_CALLBACK (e_composer_update_signature), composer);
2469 composer->priv->notify_subject_changed_handler = e_signal_connect_notify_swapped (
2470 table, "notify::subject",
2471 G_CALLBACK (msg_composer_subject_changed_cb), composer);
2472 composer->priv->notify_subject_handler = e_signal_connect_notify_swapped (
2473 table, "notify::subject",
2474 G_CALLBACK (msg_composer_notify_header_cb), composer);
2476 msg_composer_mail_identity_changed_cb (composer);
2478 /* Attachments */
2480 store = e_attachment_view_get_store (view);
2482 g_signal_connect_swapped (
2483 store, "row-deleted",
2484 G_CALLBACK (attachment_store_changed_cb), composer);
2486 g_signal_connect_swapped (
2487 store, "row-inserted",
2488 G_CALLBACK (attachment_store_changed_cb), composer);
2490 /* Initialization may have tripped the "changed" state. */
2491 e_html_editor_view_set_changed (html_editor_view, FALSE);
2493 target_list = e_attachment_view_get_target_list (view);
2494 targets = gtk_target_table_new_from_list (target_list, &n_targets);
2496 target_list = gtk_drag_dest_get_target_list (GTK_WIDGET (html_editor_view));
2498 gtk_target_list_add_table (target_list, drag_dest_targets, G_N_ELEMENTS (drag_dest_targets));
2499 gtk_target_list_add_table (target_list, targets, n_targets);
2501 gtk_target_table_free (targets, n_targets);
2503 id = "org.gnome.evolution.composer";
2504 e_plugin_ui_register_manager (ui_manager, id, composer);
2505 e_plugin_ui_enable_manager (ui_manager, id);
2507 e_extensible_load_extensions (E_EXTENSIBLE (composer));
2509 e_msg_composer_set_body_text (composer, "", TRUE);
2510 /* Chain up to parent's constructed() method. */
2511 G_OBJECT_CLASS (e_msg_composer_parent_class)->constructed (object);
2514 static void
2515 msg_composer_dispose (GObject *object)
2517 EMsgComposer *composer = E_MSG_COMPOSER (object);
2518 EMsgComposerPrivate *priv = E_MSG_COMPOSER_GET_PRIVATE (composer);
2519 EShell *shell;
2521 if (priv->address_dialog != NULL) {
2522 gtk_widget_destroy (priv->address_dialog);
2523 priv->address_dialog = NULL;
2526 /* FIXME Our EShell is already unreferenced. */
2527 shell = e_shell_get_default ();
2529 g_signal_handlers_disconnect_by_func (
2530 shell, msg_composer_quit_requested_cb, composer);
2531 g_signal_handlers_disconnect_by_func (
2532 shell, msg_composer_prepare_for_quit_cb, composer);
2534 if (priv->header_table != NULL) {
2535 EComposerHeaderTable *table;
2537 table = E_COMPOSER_HEADER_TABLE (composer->priv->header_table);
2539 e_signal_disconnect_notify_handler (
2540 table, &priv->notify_destinations_bcc_handler);
2541 e_signal_disconnect_notify_handler (
2542 table, &priv->notify_destinations_cc_handler);
2543 e_signal_disconnect_notify_handler (
2544 table, &priv->notify_destinations_to_handler);
2545 e_signal_disconnect_notify_handler (
2546 table, &priv->notify_identity_uid_handler);
2547 e_signal_disconnect_notify_handler (
2548 table, &priv->notify_reply_to_handler);
2549 e_signal_disconnect_notify_handler (
2550 table, &priv->notify_destinations_to_handler);
2551 e_signal_disconnect_notify_handler (
2552 table, &priv->notify_subject_changed_handler);
2555 e_composer_private_dispose (composer);
2557 /* Chain up to parent's dispose() method. */
2558 G_OBJECT_CLASS (e_msg_composer_parent_class)->dispose (object);
2561 static void
2562 msg_composer_map (GtkWidget *widget)
2564 EMsgComposer *composer;
2565 EComposerHeaderTable *table;
2566 GtkWidget *input_widget;
2567 EHTMLEditor *editor;
2568 EHTMLEditorView *view;
2569 const gchar *text;
2571 /* Chain up to parent's map() method. */
2572 GTK_WIDGET_CLASS (e_msg_composer_parent_class)->map (widget);
2574 composer = E_MSG_COMPOSER (widget);
2575 editor = e_msg_composer_get_editor (composer);
2576 table = e_msg_composer_get_header_table (composer);
2578 /* If the 'To' field is empty, focus it. */
2579 input_widget =
2580 e_composer_header_table_get_header (
2581 table, E_COMPOSER_HEADER_TO)->input_widget;
2582 text = gtk_entry_get_text (GTK_ENTRY (input_widget));
2583 if (gtk_widget_get_visible (input_widget) && (text == NULL || *text == '\0')) {
2584 gtk_widget_grab_focus (input_widget);
2585 return;
2588 /* If not, check the 'Subject' field. */
2589 input_widget =
2590 e_composer_header_table_get_header (
2591 table, E_COMPOSER_HEADER_SUBJECT)->input_widget;
2592 text = gtk_entry_get_text (GTK_ENTRY (input_widget));
2593 if (gtk_widget_get_visible (input_widget) && (text == NULL || *text == '\0')) {
2594 gtk_widget_grab_focus (input_widget);
2595 return;
2598 /* Jump to the editor as a last resort. */
2599 view = e_html_editor_get_view (editor);
2600 gtk_widget_grab_focus (GTK_WIDGET (view));
2603 static gboolean
2604 msg_composer_key_press_event (GtkWidget *widget,
2605 GdkEventKey *event)
2607 EMsgComposer *composer;
2608 GtkWidget *input_widget;
2609 EHTMLEditor *editor;
2610 EHTMLEditorView *view;
2612 composer = E_MSG_COMPOSER (widget);
2613 editor = e_msg_composer_get_editor (composer);
2614 view = e_html_editor_get_view (editor);
2616 input_widget =
2617 e_composer_header_table_get_header (
2618 e_msg_composer_get_header_table (composer),
2619 E_COMPOSER_HEADER_SUBJECT)->input_widget;
2621 #ifdef HAVE_XFREE
2622 if (event->keyval == XF86XK_Send) {
2623 e_msg_composer_send (composer);
2624 return TRUE;
2626 #endif /* HAVE_XFREE */
2628 if (event->keyval == GDK_KEY_Escape) {
2629 gtk_action_activate (ACTION (CLOSE));
2630 return TRUE;
2633 if (event->keyval == GDK_KEY_Tab && gtk_widget_is_focus (input_widget)) {
2634 gtk_widget_grab_focus (GTK_WIDGET (view));
2635 return TRUE;
2638 if (gtk_widget_is_focus (GTK_WIDGET (view))) {
2639 if (event->keyval == GDK_KEY_ISO_Left_Tab) {
2640 gboolean view_processed = FALSE;
2642 g_signal_emit_by_name (view, "key-press-event", event, &view_processed);
2644 if (!view_processed)
2645 gtk_widget_grab_focus (input_widget);
2647 return TRUE;
2650 if ((((event)->state & GDK_SHIFT_MASK) &&
2651 ((event)->keyval == GDK_KEY_Insert)) ||
2652 (((event)->state & GDK_CONTROL_MASK) &&
2653 ((event)->keyval == GDK_KEY_v))) {
2654 g_signal_emit_by_name (
2655 WEBKIT_WEB_VIEW (view), "paste-clipboard");
2656 return TRUE;
2659 if (((event)->state & GDK_CONTROL_MASK) &&
2660 ((event)->keyval == GDK_KEY_Insert)) {
2661 g_signal_emit_by_name (
2662 WEBKIT_WEB_VIEW (view), "copy-clipboard");
2663 return TRUE;
2666 if (((event)->state & GDK_CONTROL_MASK) &&
2667 ((event)->keyval == GDK_KEY_z)) {
2668 e_html_editor_view_undo (view);
2669 return TRUE;
2672 if (((event)->state & (GDK_CONTROL_MASK)) &&
2673 ((event)->keyval == GDK_KEY_Z)) {
2674 e_html_editor_view_redo (view);
2675 return TRUE;
2678 if (((event)->state & GDK_SHIFT_MASK) &&
2679 ((event)->keyval == GDK_KEY_Delete)) {
2680 g_signal_emit_by_name (
2681 WEBKIT_WEB_VIEW (view), "cut-clipboard");
2682 return TRUE;
2686 /* Chain up to parent's key_press_event() method. */
2687 return GTK_WIDGET_CLASS (e_msg_composer_parent_class)->
2688 key_press_event (widget, event);
2691 static gboolean
2692 msg_composer_presend (EMsgComposer *composer)
2694 /* This keeps the signal accumulator at TRUE. */
2695 return TRUE;
2698 static gboolean
2699 msg_composer_accumulator_false_abort (GSignalInvocationHint *ihint,
2700 GValue *return_accu,
2701 const GValue *handler_return,
2702 gpointer dummy)
2704 gboolean v_boolean;
2706 v_boolean = g_value_get_boolean (handler_return);
2707 g_value_set_boolean (return_accu, v_boolean);
2709 /* FALSE means abort the signal emission. */
2710 return v_boolean;
2714 * e_msg_composer_is_busy:
2715 * @composer: an #EMsgComposer
2717 * Returns %TRUE only while an #EActivity is in progress.
2719 * Returns: whether @composer is busy
2721 gboolean
2722 e_msg_composer_is_busy (EMsgComposer *composer)
2724 g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE);
2726 return composer->priv->busy;
2729 static void
2730 e_msg_composer_class_init (EMsgComposerClass *class)
2732 GObjectClass *object_class;
2733 GtkWidgetClass *widget_class;
2735 g_type_class_add_private (class, sizeof (EMsgComposerPrivate));
2737 object_class = G_OBJECT_CLASS (class);
2738 object_class->set_property = msg_composer_set_property;
2739 object_class->get_property = msg_composer_get_property;
2740 object_class->dispose = msg_composer_dispose;
2741 object_class->finalize = msg_composer_finalize;
2742 object_class->constructed = msg_composer_constructed;
2744 widget_class = GTK_WIDGET_CLASS (class);
2745 widget_class->map = msg_composer_map;
2746 widget_class->key_press_event = msg_composer_key_press_event;
2748 class->presend = msg_composer_presend;
2750 g_object_class_install_property (
2751 object_class,
2752 PROP_BUSY,
2753 g_param_spec_boolean (
2754 "busy",
2755 "Busy",
2756 "Whether an activity is in progress",
2757 FALSE,
2758 G_PARAM_READABLE |
2759 G_PARAM_STATIC_STRINGS));
2761 g_object_class_install_property (
2762 object_class,
2763 PROP_FOCUS_TRACKER,
2764 g_param_spec_object (
2765 "focus-tracker",
2766 NULL,
2767 NULL,
2768 E_TYPE_FOCUS_TRACKER,
2769 G_PARAM_READABLE));
2771 g_object_class_install_property (
2772 object_class,
2773 PROP_SHELL,
2774 g_param_spec_object (
2775 "shell",
2776 "Shell",
2777 "The EShell singleton",
2778 E_TYPE_SHELL,
2779 G_PARAM_READWRITE |
2780 G_PARAM_CONSTRUCT_ONLY));
2782 signals[PRESEND] = g_signal_new (
2783 "presend",
2784 G_OBJECT_CLASS_TYPE (class),
2785 G_SIGNAL_RUN_LAST,
2786 G_STRUCT_OFFSET (EMsgComposerClass, presend),
2787 msg_composer_accumulator_false_abort,
2788 NULL,
2789 e_marshal_BOOLEAN__VOID,
2790 G_TYPE_BOOLEAN, 0);
2792 signals[SEND] = g_signal_new (
2793 "send",
2794 G_OBJECT_CLASS_TYPE (class),
2795 G_SIGNAL_RUN_LAST,
2796 G_STRUCT_OFFSET (EMsgComposerClass, send),
2797 NULL, NULL,
2798 e_marshal_VOID__OBJECT_OBJECT,
2799 G_TYPE_NONE, 2,
2800 CAMEL_TYPE_MIME_MESSAGE,
2801 E_TYPE_ACTIVITY);
2803 signals[SAVE_TO_DRAFTS] = g_signal_new (
2804 "save-to-drafts",
2805 G_OBJECT_CLASS_TYPE (class),
2806 G_SIGNAL_RUN_LAST,
2807 G_STRUCT_OFFSET (EMsgComposerClass, save_to_drafts),
2808 NULL, NULL,
2809 e_marshal_VOID__OBJECT_OBJECT,
2810 G_TYPE_NONE, 2,
2811 CAMEL_TYPE_MIME_MESSAGE,
2812 E_TYPE_ACTIVITY);
2814 signals[SAVE_TO_OUTBOX] = g_signal_new (
2815 "save-to-outbox",
2816 G_OBJECT_CLASS_TYPE (class),
2817 G_SIGNAL_RUN_LAST,
2818 G_STRUCT_OFFSET (EMsgComposerClass, save_to_outbox),
2819 NULL, NULL,
2820 e_marshal_VOID__OBJECT_OBJECT,
2821 G_TYPE_NONE, 2,
2822 CAMEL_TYPE_MIME_MESSAGE,
2823 E_TYPE_ACTIVITY);
2825 signals[PRINT] = g_signal_new (
2826 "print",
2827 G_OBJECT_CLASS_TYPE (class),
2828 G_SIGNAL_RUN_LAST,
2829 0, NULL, NULL,
2830 e_marshal_VOID__ENUM_OBJECT_OBJECT,
2831 G_TYPE_NONE, 3,
2832 GTK_TYPE_PRINT_OPERATION_ACTION,
2833 CAMEL_TYPE_MIME_MESSAGE,
2834 E_TYPE_ACTIVITY);
2837 static void
2838 e_msg_composer_init (EMsgComposer *composer)
2840 EHTMLEditorView *view;
2842 composer->priv = E_MSG_COMPOSER_GET_PRIVATE (composer);
2844 composer->priv->editor = g_object_ref_sink (e_html_editor_new ());
2845 view = e_html_editor_get_view (composer->priv->editor);
2846 e_html_editor_view_set_is_editting_message (view, TRUE);
2850 * e_msg_composer_new:
2851 * @shell: an #EShell
2853 * Create a new message composer widget.
2855 * Returns: A pointer to the newly created widget
2857 EMsgComposer *
2858 e_msg_composer_new (EShell *shell)
2860 g_return_val_if_fail (E_IS_SHELL (shell), NULL);
2862 return g_object_new (
2863 E_TYPE_MSG_COMPOSER,
2864 "shell", shell, NULL);
2868 * e_msg_composer_get_editor:
2869 * @composer: an #EMsgComposer
2871 * Returns @composer's internal #EHTMLEditor instance.
2873 * Returns: an #EHTMLEditor
2875 EHTMLEditor *
2876 e_msg_composer_get_editor (EMsgComposer *composer)
2878 g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
2880 return composer->priv->editor;
2883 EFocusTracker *
2884 e_msg_composer_get_focus_tracker (EMsgComposer *composer)
2886 g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
2888 return composer->priv->focus_tracker;
2891 static void
2892 e_msg_composer_set_pending_body (EMsgComposer *composer,
2893 gchar *text,
2894 gssize length,
2895 gboolean is_html)
2897 g_object_set_data_full (
2898 G_OBJECT (composer), "body:text_mime_type",
2899 GINT_TO_POINTER (is_html), NULL);
2900 g_object_set_data_full (
2901 G_OBJECT (composer), "body:text",
2902 text, (GDestroyNotify) g_free);
2905 static void
2906 e_msg_composer_flush_pending_body (EMsgComposer *composer)
2908 const gchar *body;
2909 gboolean is_html;
2911 body = g_object_get_data (G_OBJECT (composer), "body:text");
2912 is_html = GPOINTER_TO_INT (
2913 g_object_get_data (G_OBJECT (composer), "body:text_mime_type"));
2915 if (body != NULL)
2916 set_editor_text (composer, body, is_html, FALSE);
2918 g_object_set_data (G_OBJECT (composer), "body:text", NULL);
2921 static void
2922 add_attachments_handle_mime_part (EMsgComposer *composer,
2923 CamelMimePart *mime_part,
2924 gboolean just_inlines,
2925 gboolean related,
2926 gint depth)
2928 CamelContentType *content_type;
2929 CamelDataWrapper *wrapper;
2930 EHTMLEditor *editor;
2931 EHTMLEditorView *view;
2933 if (!mime_part)
2934 return;
2936 content_type = camel_mime_part_get_content_type (mime_part);
2937 wrapper = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
2938 editor = e_msg_composer_get_editor (composer);
2939 view = e_html_editor_get_view (editor);
2941 if (CAMEL_IS_MULTIPART (wrapper)) {
2942 /* another layer of multipartness... */
2943 add_attachments_from_multipart (
2944 composer, (CamelMultipart *) wrapper,
2945 just_inlines, depth + 1);
2946 } else if (just_inlines) {
2947 if (camel_mime_part_get_content_id (mime_part) ||
2948 camel_mime_part_get_content_location (mime_part))
2949 e_html_editor_view_add_inline_image_from_mime_part (
2950 view, mime_part);
2951 } else if (related && camel_content_type_is (content_type, "image", "*")) {
2952 e_html_editor_view_add_inline_image_from_mime_part (view, mime_part);
2953 } else if (camel_content_type_is (content_type, "text", "*") &&
2954 camel_mime_part_get_filename (mime_part) == NULL) {
2955 /* Do nothing if this is a text/anything without a
2956 * filename, otherwise attach it too. */
2957 } else {
2958 e_msg_composer_attach (composer, mime_part);
2962 static void
2963 add_attachments_from_multipart (EMsgComposer *composer,
2964 CamelMultipart *multipart,
2965 gboolean just_inlines,
2966 gint depth)
2968 /* find appropriate message attachments to add to the composer */
2969 CamelMimePart *mime_part;
2970 gboolean related;
2971 gint i, nparts;
2973 related = camel_content_type_is (
2974 CAMEL_DATA_WRAPPER (multipart)->mime_type,
2975 "multipart", "related");
2977 if (CAMEL_IS_MULTIPART_SIGNED (multipart)) {
2978 mime_part = camel_multipart_get_part (
2979 multipart, CAMEL_MULTIPART_SIGNED_CONTENT);
2980 add_attachments_handle_mime_part (
2981 composer, mime_part, just_inlines, related, depth);
2982 } else if (CAMEL_IS_MULTIPART_ENCRYPTED (multipart)) {
2983 /* XXX What should we do in this case? */
2984 } else {
2985 nparts = camel_multipart_get_number (multipart);
2987 for (i = 0; i < nparts; i++) {
2988 mime_part = camel_multipart_get_part (multipart, i);
2989 add_attachments_handle_mime_part (
2990 composer, mime_part, just_inlines,
2991 related, depth);
2997 * e_msg_composer_add_message_attachments:
2998 * @composer: the composer to add the attachments to.
2999 * @message: the source message to copy the attachments from.
3000 * @just_inlines: whether to attach all attachments or just add
3001 * inline images.
3003 * Walk through all the mime parts in @message and add them to the composer
3004 * specified in @composer.
3006 void
3007 e_msg_composer_add_message_attachments (EMsgComposer *composer,
3008 CamelMimeMessage *message,
3009 gboolean just_inlines)
3011 CamelDataWrapper *wrapper;
3013 wrapper = camel_medium_get_content (CAMEL_MEDIUM (message));
3014 if (!CAMEL_IS_MULTIPART (wrapper))
3015 return;
3017 add_attachments_from_multipart (
3018 composer, (CamelMultipart *) wrapper, just_inlines, 0);
3021 static void
3022 handle_multipart_signed (EMsgComposer *composer,
3023 CamelMultipart *multipart,
3024 gboolean keep_signature,
3025 GCancellable *cancellable,
3026 gint depth)
3028 CamelContentType *content_type;
3029 CamelDataWrapper *content;
3030 CamelMimePart *mime_part;
3031 GtkToggleAction *action = NULL;
3032 const gchar *protocol;
3034 content = CAMEL_DATA_WRAPPER (multipart);
3035 content_type = camel_data_wrapper_get_mime_type_field (content);
3036 protocol = camel_content_type_param (content_type, "protocol");
3038 if (protocol == NULL) {
3039 action = NULL;
3040 } else if (g_ascii_strcasecmp (protocol, "application/pgp-signature") == 0) {
3041 if (!gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (ACTION (SMIME_SIGN))) &&
3042 !gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (ACTION (SMIME_ENCRYPT))))
3043 action = GTK_TOGGLE_ACTION (ACTION (PGP_SIGN));
3044 } else if (g_ascii_strcasecmp (protocol, "application/x-pkcs7-signature") == 0) {
3045 if (!gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (ACTION (PGP_SIGN))) &&
3046 !gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (ACTION (PGP_ENCRYPT))))
3047 action = GTK_TOGGLE_ACTION (ACTION (SMIME_SIGN));
3050 if (action)
3051 gtk_toggle_action_set_active (action, TRUE);
3053 mime_part = camel_multipart_get_part (
3054 multipart, CAMEL_MULTIPART_SIGNED_CONTENT);
3056 if (mime_part == NULL)
3057 return;
3059 content_type = camel_mime_part_get_content_type (mime_part);
3060 content = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
3062 if (CAMEL_IS_MULTIPART (content)) {
3063 multipart = CAMEL_MULTIPART (content);
3065 /* Note: depth is preserved here because we're not
3066 * counting multipart/signed as a multipart, instead
3067 * we want to treat the content part as our mime part
3068 * here. */
3070 if (CAMEL_IS_MULTIPART_SIGNED (content)) {
3071 /* Handle the signed content and configure
3072 * the composer to sign outgoing messages. */
3073 handle_multipart_signed (
3074 composer, multipart, keep_signature, cancellable, depth);
3076 } else if (CAMEL_IS_MULTIPART_ENCRYPTED (content)) {
3077 /* Decrypt the encrypted content and configure
3078 * the composer to encrypt outgoing messages. */
3079 handle_multipart_encrypted (
3080 composer, mime_part, keep_signature, cancellable, depth);
3082 } else if (camel_content_type_is (content_type, "multipart", "alternative")) {
3083 /* This contains the text/plain and text/html
3084 * versions of the message body. */
3085 handle_multipart_alternative (
3086 composer, multipart, keep_signature, cancellable, depth);
3088 } else {
3089 /* There must be attachments... */
3090 handle_multipart (
3091 composer, multipart, keep_signature, cancellable, depth);
3094 } else if (camel_content_type_is (content_type, "text", "*")) {
3095 gchar *html;
3096 gssize length;
3098 html = emcu_part_to_html (
3099 composer, mime_part, &length, keep_signature, cancellable);
3100 if (html)
3101 e_msg_composer_set_pending_body (composer, html, length, TRUE);
3103 } else {
3104 e_msg_composer_attach (composer, mime_part);
3108 static void
3109 handle_multipart_encrypted (EMsgComposer *composer,
3110 CamelMimePart *multipart,
3111 gboolean keep_signature,
3112 GCancellable *cancellable,
3113 gint depth)
3115 CamelContentType *content_type;
3116 CamelCipherContext *cipher;
3117 CamelDataWrapper *content;
3118 CamelMimePart *mime_part;
3119 CamelSession *session;
3120 CamelCipherValidity *valid;
3121 GtkToggleAction *action = NULL;
3122 const gchar *protocol;
3124 content_type = camel_mime_part_get_content_type (multipart);
3125 protocol = camel_content_type_param (content_type, "protocol");
3127 if (protocol && g_ascii_strcasecmp (protocol, "application/pgp-encrypted") == 0) {
3128 if (!gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (ACTION (SMIME_SIGN))) &&
3129 !gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (ACTION (SMIME_ENCRYPT))))
3130 action = GTK_TOGGLE_ACTION (ACTION (PGP_ENCRYPT));
3131 } else if (content_type && (
3132 camel_content_type_is (content_type, "application", "x-pkcs7-mime")
3133 || camel_content_type_is (content_type, "application", "pkcs7-mime"))) {
3134 if (!gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (ACTION (PGP_SIGN))) &&
3135 !gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (ACTION (PGP_ENCRYPT))))
3136 action = GTK_TOGGLE_ACTION (ACTION (SMIME_ENCRYPT));
3139 if (action)
3140 gtk_toggle_action_set_active (action, TRUE);
3142 session = e_msg_composer_ref_session (composer);
3143 cipher = camel_gpg_context_new (session);
3144 mime_part = camel_mime_part_new ();
3145 valid = camel_cipher_context_decrypt_sync (
3146 cipher, multipart, mime_part, cancellable, NULL);
3147 g_object_unref (cipher);
3148 g_object_unref (session);
3150 if (valid == NULL)
3151 return;
3153 camel_cipher_validity_free (valid);
3155 content_type = camel_mime_part_get_content_type (mime_part);
3157 content = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
3159 if (CAMEL_IS_MULTIPART (content)) {
3160 CamelMultipart *content_multipart = CAMEL_MULTIPART (content);
3162 /* Note: depth is preserved here because we're not
3163 * counting multipart/encrypted as a multipart, instead
3164 * we want to treat the content part as our mime part
3165 * here. */
3167 if (CAMEL_IS_MULTIPART_SIGNED (content)) {
3168 /* Handle the signed content and configure the
3169 * composer to sign outgoing messages. */
3170 handle_multipart_signed (
3171 composer, content_multipart, keep_signature, cancellable, depth);
3173 } else if (CAMEL_IS_MULTIPART_ENCRYPTED (content)) {
3174 /* Decrypt the encrypted content and configure the
3175 * composer to encrypt outgoing messages. */
3176 handle_multipart_encrypted (
3177 composer, mime_part, keep_signature, cancellable, depth);
3179 } else if (camel_content_type_is (content_type, "multipart", "alternative")) {
3180 /* This contains the text/plain and text/html
3181 * versions of the message body. */
3182 handle_multipart_alternative (
3183 composer, content_multipart, keep_signature, cancellable, depth);
3185 } else {
3186 /* There must be attachments... */
3187 handle_multipart (
3188 composer, content_multipart, keep_signature, cancellable, depth);
3191 } else if (camel_content_type_is (content_type, "text", "*")) {
3192 gchar *html;
3193 gssize length;
3195 html = emcu_part_to_html (
3196 composer, mime_part, &length, keep_signature, cancellable);
3197 if (html)
3198 e_msg_composer_set_pending_body (composer, html, length, TRUE);
3200 } else {
3201 e_msg_composer_attach (composer, mime_part);
3204 g_object_unref (mime_part);
3207 static void
3208 handle_multipart_alternative (EMsgComposer *composer,
3209 CamelMultipart *multipart,
3210 gboolean keep_signature,
3211 GCancellable *cancellable,
3212 gint depth)
3214 /* Find the text/html part and set the composer body to its content */
3215 CamelMimePart *text_part = NULL, *fallback_text_part = NULL;
3216 gint i, nparts;
3218 nparts = camel_multipart_get_number (multipart);
3220 for (i = 0; i < nparts; i++) {
3221 CamelContentType *content_type;
3222 CamelDataWrapper *content;
3223 CamelMimePart *mime_part;
3225 mime_part = camel_multipart_get_part (multipart, i);
3227 if (!mime_part)
3228 continue;
3230 content_type = camel_mime_part_get_content_type (mime_part);
3231 content = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
3233 if (CAMEL_IS_MULTIPART (content)) {
3234 CamelMultipart *mp;
3236 mp = CAMEL_MULTIPART (content);
3238 if (CAMEL_IS_MULTIPART_SIGNED (content)) {
3239 /* Handle the signed content and configure
3240 * the composer to sign outgoing messages. */
3241 handle_multipart_signed (
3242 composer, mp, keep_signature, cancellable, depth + 1);
3244 } else if (CAMEL_IS_MULTIPART_ENCRYPTED (content)) {
3245 /* Decrypt the encrypted content and configure
3246 * the composer to encrypt outgoing messages. */
3247 handle_multipart_encrypted (
3248 composer, mime_part, keep_signature,
3249 cancellable, depth + 1);
3251 } else {
3252 /* Depth doesn't matter so long as we
3253 * don't pass 0. */
3254 handle_multipart (
3255 composer, mp, keep_signature, cancellable, depth + 1);
3258 } else if (camel_content_type_is (content_type, "text", "html")) {
3259 /* text/html is preferable, so once we find it we're done... */
3260 text_part = mime_part;
3261 break;
3262 } else if (camel_content_type_is (content_type, "text", "*")) {
3263 /* anyt text part not text/html is second rate so the first
3264 * text part we find isn't necessarily the one we'll use. */
3265 if (!text_part)
3266 text_part = mime_part;
3268 /* this is when prefer-plain filters out text/html part, then
3269 * the text/plain should be used */
3270 if (camel_content_type_is (content_type, "text", "plain"))
3271 fallback_text_part = mime_part;
3272 } else {
3273 e_msg_composer_attach (composer, mime_part);
3277 if (text_part) {
3278 gchar *html;
3279 gssize length;
3281 html = emcu_part_to_html (
3282 composer, text_part, &length, keep_signature, cancellable);
3283 if (!html && fallback_text_part)
3284 html = emcu_part_to_html (
3285 composer, fallback_text_part, &length, keep_signature, cancellable);
3286 if (html)
3287 e_msg_composer_set_pending_body (composer, html, length, TRUE);
3291 static void
3292 handle_multipart (EMsgComposer *composer,
3293 CamelMultipart *multipart,
3294 gboolean keep_signature,
3295 GCancellable *cancellable,
3296 gint depth)
3298 gint i, nparts;
3300 nparts = camel_multipart_get_number (multipart);
3302 for (i = 0; i < nparts; i++) {
3303 CamelContentType *content_type;
3304 CamelDataWrapper *content;
3305 CamelMimePart *mime_part;
3307 mime_part = camel_multipart_get_part (multipart, i);
3309 if (!mime_part)
3310 continue;
3312 content_type = camel_mime_part_get_content_type (mime_part);
3313 content = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
3315 if (CAMEL_IS_MULTIPART (content)) {
3316 CamelMultipart *mp;
3318 mp = CAMEL_MULTIPART (content);
3320 if (CAMEL_IS_MULTIPART_SIGNED (content)) {
3321 /* Handle the signed content and configure
3322 * the composer to sign outgoing messages. */
3323 handle_multipart_signed (
3324 composer, mp, keep_signature, cancellable, depth + 1);
3326 } else if (CAMEL_IS_MULTIPART_ENCRYPTED (content)) {
3327 /* Decrypt the encrypted content and configure
3328 * the composer to encrypt outgoing messages. */
3329 handle_multipart_encrypted (
3330 composer, mime_part, keep_signature,
3331 cancellable, depth + 1);
3333 } else if (camel_content_type_is (
3334 content_type, "multipart", "alternative")) {
3335 handle_multipart_alternative (
3336 composer, mp, keep_signature, cancellable, depth + 1);
3338 } else {
3339 /* Depth doesn't matter so long as we
3340 * don't pass 0. */
3341 handle_multipart (
3342 composer, mp, keep_signature, cancellable, depth + 1);
3345 } else if (depth == 0 && i == 0) {
3346 EHTMLEditor *editor;
3347 gboolean is_message_from_draft, is_html = FALSE;
3348 gchar *html = NULL;
3349 gssize length = 0;
3351 editor = e_msg_composer_get_editor (composer);
3352 is_message_from_draft = e_html_editor_view_is_message_from_draft (
3353 e_html_editor_get_view (editor));
3354 is_html = camel_content_type_is (content_type, "text", "html");
3356 /* Since the first part is not multipart/alternative,
3357 * this must be the body. */
3359 /* If we are opening message from Drafts */
3360 if (is_message_from_draft) {
3361 /* Extract the body */
3362 CamelDataWrapper *dw;
3364 dw = camel_medium_get_content ((CamelMedium *) mime_part);
3365 if (dw) {
3366 CamelStream *mem = camel_stream_mem_new ();
3367 GByteArray *bytes;
3369 camel_data_wrapper_decode_to_stream_sync (dw, mem, cancellable, NULL);
3370 camel_stream_close (mem, cancellable, NULL);
3372 bytes = camel_stream_mem_get_byte_array (CAMEL_STREAM_MEM (mem));
3373 if (bytes && bytes->len)
3374 html = g_strndup ((const gchar *) bytes->data, bytes->len);
3376 g_object_unref (mem);
3378 } else {
3379 is_html = TRUE;
3380 html = emcu_part_to_html (
3381 composer, mime_part, &length, keep_signature, cancellable);
3384 if (html)
3385 e_msg_composer_set_pending_body (composer, html, length, is_html);
3387 } else if (camel_mime_part_get_content_id (mime_part) ||
3388 camel_mime_part_get_content_location (mime_part)) {
3389 /* special in-line attachment */
3390 EHTMLEditor *editor;
3392 editor = e_msg_composer_get_editor (composer);
3393 e_html_editor_view_add_inline_image_from_mime_part (
3394 e_html_editor_get_view (editor), mime_part);
3396 } else {
3397 /* normal attachment */
3398 e_msg_composer_attach (composer, mime_part);
3403 static void
3404 set_signature_gui (EMsgComposer *composer)
3406 EHTMLEditor *editor;
3407 EHTMLEditorView *view;
3408 WebKitDOMDocument *document;
3409 WebKitDOMNodeList *nodes;
3410 EComposerHeaderTable *table;
3411 EMailSignatureComboBox *combo_box;
3412 gchar *uid;
3413 gulong ii, length;
3415 table = e_msg_composer_get_header_table (composer);
3416 combo_box = e_composer_header_table_get_signature_combo_box (table);
3418 editor = e_msg_composer_get_editor (composer);
3419 view = e_html_editor_get_view (editor);
3420 document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
3422 uid = NULL;
3423 nodes = webkit_dom_document_get_elements_by_class_name (
3424 document, "-x-evo-signature");
3425 length = webkit_dom_node_list_get_length (nodes);
3426 for (ii = 0; ii < length; ii++) {
3427 WebKitDOMNode *node;
3428 gchar *id;
3430 node = webkit_dom_node_list_item (nodes, ii);
3431 id = webkit_dom_element_get_id (WEBKIT_DOM_ELEMENT (node));
3432 if (id && (strlen (id) == 1) && (*id == '1')) {
3433 uid = webkit_dom_element_get_attribute (
3434 WEBKIT_DOM_ELEMENT (node), "name");
3435 g_free (id);
3436 g_object_unref (node);
3437 break;
3439 g_free (id);
3440 g_object_unref (node);
3443 g_object_unref (nodes);
3445 /* The combo box active ID is the signature's ESource UID. */
3446 if (uid != NULL) {
3447 gtk_combo_box_set_active_id (GTK_COMBO_BOX (combo_box), uid);
3448 g_free (uid);
3452 static void
3453 composer_add_auto_recipients (ESource *source,
3454 const gchar *property_name,
3455 GHashTable *hash_table)
3457 ESourceMailComposition *extension;
3458 CamelInternetAddress *inet_addr;
3459 const gchar *extension_name;
3460 gchar *comma_separated_addrs;
3461 gchar **addr_array = NULL;
3462 gint length, ii;
3463 gint retval;
3465 extension_name = E_SOURCE_EXTENSION_MAIL_COMPOSITION;
3466 extension = e_source_get_extension (source, extension_name);
3468 g_object_get (extension, property_name, &addr_array, NULL);
3470 if (addr_array == NULL)
3471 return;
3473 inet_addr = camel_internet_address_new ();
3474 comma_separated_addrs = g_strjoinv (", ", addr_array);
3476 retval = camel_address_decode (
3477 CAMEL_ADDRESS (inet_addr), comma_separated_addrs);
3479 g_free (comma_separated_addrs);
3480 g_strfreev (addr_array);
3482 if (retval == -1)
3483 return;
3485 length = camel_address_length (CAMEL_ADDRESS (inet_addr));
3487 for (ii = 0; ii < length; ii++) {
3488 const gchar *name;
3489 const gchar *addr;
3491 if (camel_internet_address_get (inet_addr, ii, &name, &addr))
3492 g_hash_table_add (hash_table, g_strdup (addr));
3495 g_object_unref (inet_addr);
3499 * e_msg_composer_new_with_message:
3500 * @shell: an #EShell
3501 * @message: The message to use as the source
3502 * @keep_signature: Keep message signature, if any
3503 * @override_identity_uid: (allow none): Optional identity UID to use, or %NULL
3504 * @cancellable: optional #GCancellable object, or %NULL
3506 * Create a new message composer widget.
3508 * Note: Designed to work only for messages constructed using Evolution.
3510 * Returns: A pointer to the newly created widget
3512 EMsgComposer *
3513 e_msg_composer_new_with_message (EShell *shell,
3514 CamelMimeMessage *message,
3515 gboolean keep_signature,
3516 const gchar *override_identity_uid,
3517 GCancellable *cancellable)
3519 CamelInternetAddress *from, *to, *cc, *bcc;
3520 GList *To = NULL, *Cc = NULL, *Bcc = NULL, *postto = NULL;
3521 const gchar *format, *subject, *composer_mode;
3522 EDestination **Tov, **Ccv, **Bccv;
3523 GHashTable *auto_cc, *auto_bcc;
3524 CamelContentType *content_type;
3525 struct _camel_header_raw *headers;
3526 CamelDataWrapper *content;
3527 EMsgComposer *composer;
3528 EMsgComposerPrivate *priv;
3529 EComposerHeaderTable *table;
3530 ESource *source = NULL;
3531 EHTMLEditor *editor;
3532 EHTMLEditorView *view;
3533 GtkToggleAction *action;
3534 struct _camel_header_raw *xev;
3535 gchar *identity_uid;
3536 gint len, i;
3537 gboolean is_message_from_draft = FALSE;
3539 g_return_val_if_fail (E_IS_SHELL (shell), NULL);
3541 headers = CAMEL_MIME_PART (message)->headers;
3542 while (headers != NULL) {
3543 gchar *value;
3545 if (strcmp (headers->name, "X-Evolution-PostTo") == 0) {
3546 value = g_strstrip (g_strdup (headers->value));
3547 postto = g_list_append (postto, value);
3550 headers = headers->next;
3553 composer = e_msg_composer_new (shell);
3554 priv = E_MSG_COMPOSER_GET_PRIVATE (composer);
3555 editor = e_msg_composer_get_editor (composer);
3556 table = e_msg_composer_get_header_table (composer);
3557 view = e_html_editor_get_view (editor);
3559 if (postto) {
3560 e_composer_header_table_set_post_to_list (table, postto);
3561 g_list_foreach (postto, (GFunc) g_free, NULL);
3562 g_list_free (postto);
3563 postto = NULL;
3566 if (override_identity_uid && *override_identity_uid) {
3567 identity_uid = (gchar *) override_identity_uid;
3568 } else {
3569 /* Restore the mail identity preference. */
3570 identity_uid = (gchar *) camel_medium_get_header (
3571 CAMEL_MEDIUM (message), "X-Evolution-Identity");
3572 if (!identity_uid) {
3573 /* for backward compatibility */
3574 identity_uid = (gchar *) camel_medium_get_header (
3575 CAMEL_MEDIUM (message), "X-Evolution-Account");
3577 if (!identity_uid) {
3578 source = em_utils_guess_mail_identity_with_recipients (
3579 e_shell_get_registry (shell), message, NULL, NULL);
3580 if (source)
3581 identity_uid = e_source_dup_uid (source);
3585 if (identity_uid != NULL && !source) {
3586 identity_uid = g_strstrip (g_strdup (identity_uid));
3587 source = e_composer_header_table_ref_source (
3588 table, identity_uid);
3591 auto_cc = g_hash_table_new_full (
3592 (GHashFunc) camel_strcase_hash,
3593 (GEqualFunc) camel_strcase_equal,
3594 (GDestroyNotify) g_free,
3595 (GDestroyNotify) NULL);
3597 auto_bcc = g_hash_table_new_full (
3598 (GHashFunc) camel_strcase_hash,
3599 (GEqualFunc) camel_strcase_equal,
3600 (GDestroyNotify) g_free,
3601 (GDestroyNotify) NULL);
3603 if (source != NULL) {
3604 composer_add_auto_recipients (source, "cc", auto_cc);
3605 composer_add_auto_recipients (source, "bcc", auto_bcc);
3608 to = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_TO);
3609 cc = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_CC);
3610 bcc = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_BCC);
3612 len = CAMEL_ADDRESS (to)->addresses->len;
3613 for (i = 0; i < len; i++) {
3614 const gchar *name, *addr;
3616 if (camel_internet_address_get (to, i, &name, &addr)) {
3617 EDestination *dest = e_destination_new ();
3618 e_destination_set_name (dest, name);
3619 e_destination_set_email (dest, addr);
3620 To = g_list_append (To, dest);
3624 Tov = destination_list_to_vector (To);
3625 g_list_free (To);
3627 len = CAMEL_ADDRESS (cc)->addresses->len;
3628 for (i = 0; i < len; i++) {
3629 const gchar *name, *addr;
3631 if (camel_internet_address_get (cc, i, &name, &addr)) {
3632 EDestination *dest = e_destination_new ();
3633 e_destination_set_name (dest, name);
3634 e_destination_set_email (dest, addr);
3636 if (g_hash_table_contains (auto_cc, addr))
3637 e_destination_set_auto_recipient (dest, TRUE);
3639 Cc = g_list_append (Cc, dest);
3643 Ccv = destination_list_to_vector (Cc);
3644 g_hash_table_destroy (auto_cc);
3645 g_list_free (Cc);
3647 len = CAMEL_ADDRESS (bcc)->addresses->len;
3648 for (i = 0; i < len; i++) {
3649 const gchar *name, *addr;
3651 if (camel_internet_address_get (bcc, i, &name, &addr)) {
3652 EDestination *dest = e_destination_new ();
3653 e_destination_set_name (dest, name);
3654 e_destination_set_email (dest, addr);
3656 if (g_hash_table_contains (auto_bcc, addr))
3657 e_destination_set_auto_recipient (dest, TRUE);
3659 Bcc = g_list_append (Bcc, dest);
3663 Bccv = destination_list_to_vector (Bcc);
3664 g_hash_table_destroy (auto_bcc);
3665 g_list_free (Bcc);
3667 if (source != NULL)
3668 g_object_unref (source);
3670 subject = camel_mime_message_get_subject (message);
3672 e_composer_header_table_set_identity_uid (table, identity_uid);
3673 e_composer_header_table_set_destinations_to (table, Tov);
3674 e_composer_header_table_set_destinations_cc (table, Ccv);
3675 e_composer_header_table_set_destinations_bcc (table, Bccv);
3676 e_composer_header_table_set_subject (table, subject);
3678 g_free (identity_uid);
3680 e_destination_freev (Tov);
3681 e_destination_freev (Ccv);
3682 e_destination_freev (Bccv);
3684 from = camel_mime_message_get_from (message);
3685 if ((!override_identity_uid || !*override_identity_uid) && from) {
3686 const gchar *name = NULL, *address = NULL;
3688 if (camel_address_length (CAMEL_ADDRESS (from)) == 1 &&
3689 camel_internet_address_get (from, 0, &name, &address)) {
3690 EComposerFromHeader *header_from;
3691 const gchar *filled_name, *filled_address;
3693 header_from = E_COMPOSER_FROM_HEADER (e_composer_header_table_get_header (table, E_COMPOSER_HEADER_FROM));
3695 filled_name = e_composer_from_header_get_name (header_from);
3696 filled_address = e_composer_from_header_get_address (header_from);
3698 if (name && !*name)
3699 name = NULL;
3701 if (address && !*address)
3702 address = NULL;
3704 if (g_strcmp0 (filled_name, name) != 0 ||
3705 g_strcmp0 (filled_address, address) != 0) {
3706 e_composer_from_header_set_name (header_from, name);
3707 e_composer_from_header_set_address (header_from, address);
3708 e_composer_from_header_set_override_visible (header_from, TRUE);
3713 /* Restore the format editing preference */
3714 format = camel_medium_get_header (
3715 CAMEL_MEDIUM (message), "X-Evolution-Format");
3717 composer_mode = camel_medium_get_header (
3718 CAMEL_MEDIUM (message), "X-Evolution-Composer-Mode");
3720 if (composer_mode && *composer_mode) {
3721 is_message_from_draft = TRUE;
3722 e_html_editor_view_set_is_message_from_draft (view, TRUE);
3725 if (format != NULL) {
3726 gchar **flags;
3728 while (*format && camel_mime_is_lwsp (*format))
3729 format++;
3731 flags = g_strsplit (format, ", ", 0);
3732 for (i = 0; flags[i]; i++) {
3733 if (g_ascii_strcasecmp (flags[i], "text/html") == 0) {
3734 if (composer_mode && g_ascii_strcasecmp (composer_mode, "text/html") == 0) {
3735 e_html_editor_view_set_html_mode (
3736 view, TRUE);
3737 } else {
3738 e_html_editor_view_set_html_mode (
3739 view, FALSE);
3741 } else if (g_ascii_strcasecmp (flags[i], "text/plain") == 0) {
3742 if (composer_mode && g_ascii_strcasecmp (composer_mode, "text/html") == 0) {
3743 e_html_editor_view_set_html_mode (
3744 view, TRUE);
3745 } else {
3746 e_html_editor_view_set_html_mode (
3747 view, FALSE);
3749 } else if (g_ascii_strcasecmp (flags[i], "pgp-sign") == 0) {
3750 action = GTK_TOGGLE_ACTION (ACTION (PGP_SIGN));
3751 gtk_toggle_action_set_active (action, TRUE);
3752 } else if (g_ascii_strcasecmp (flags[i], "pgp-encrypt") == 0) {
3753 action = GTK_TOGGLE_ACTION (ACTION (PGP_ENCRYPT));
3754 gtk_toggle_action_set_active (action, TRUE);
3755 } else if (g_ascii_strcasecmp (flags[i], "smime-sign") == 0) {
3756 action = GTK_TOGGLE_ACTION (ACTION (SMIME_SIGN));
3757 gtk_toggle_action_set_active (action, TRUE);
3758 } else if (g_ascii_strcasecmp (flags[i], "smime-encrypt") == 0) {
3759 action = GTK_TOGGLE_ACTION (ACTION (SMIME_ENCRYPT));
3760 gtk_toggle_action_set_active (action, TRUE);
3763 g_strfreev (flags);
3766 /* Remove any other X-Evolution-* headers that may have been set */
3767 xev = emcu_remove_xevolution_headers (message);
3768 camel_header_raw_clear (&xev);
3770 /* Check for receipt request */
3771 if (camel_medium_get_header (
3772 CAMEL_MEDIUM (message), "Disposition-Notification-To")) {
3773 action = GTK_TOGGLE_ACTION (ACTION (REQUEST_READ_RECEIPT));
3774 gtk_toggle_action_set_active (action, TRUE);
3777 /* Check for mail priority */
3778 if (camel_medium_get_header (CAMEL_MEDIUM (message), "X-Priority")) {
3779 action = GTK_TOGGLE_ACTION (ACTION (PRIORITIZE_MESSAGE));
3780 gtk_toggle_action_set_active (action, TRUE);
3783 /* set extra headers */
3784 headers = CAMEL_MIME_PART (message)->headers;
3785 while (headers) {
3786 if (g_ascii_strcasecmp (headers->name, "References") == 0 ||
3787 g_ascii_strcasecmp (headers->name, "In-Reply-To") == 0) {
3788 g_ptr_array_add (
3789 composer->priv->extra_hdr_names,
3790 g_strdup (headers->name));
3791 g_ptr_array_add (
3792 composer->priv->extra_hdr_values,
3793 g_strdup (headers->value));
3796 headers = headers->next;
3799 /* Restore the attachments and body text */
3800 content = camel_medium_get_content (CAMEL_MEDIUM (message));
3801 if (CAMEL_IS_MULTIPART (content)) {
3802 CamelMimePart *mime_part;
3803 CamelMultipart *multipart;
3805 multipart = CAMEL_MULTIPART (content);
3806 mime_part = CAMEL_MIME_PART (message);
3807 content_type = camel_mime_part_get_content_type (mime_part);
3809 if (CAMEL_IS_MULTIPART_SIGNED (content)) {
3810 /* Handle the signed content and configure the
3811 * composer to sign outgoing messages. */
3812 handle_multipart_signed (
3813 composer, multipart, keep_signature, cancellable, 0);
3815 } else if (CAMEL_IS_MULTIPART_ENCRYPTED (content)) {
3816 /* Decrypt the encrypted content and configure the
3817 * composer to encrypt outgoing messages. */
3818 handle_multipart_encrypted (
3819 composer, mime_part, keep_signature, cancellable, 0);
3821 } else if (camel_content_type_is (
3822 content_type, "multipart", "alternative")) {
3823 /* This contains the text/plain and text/html
3824 * versions of the message body. */
3825 handle_multipart_alternative (
3826 composer, multipart, keep_signature, cancellable, 0);
3828 } else {
3829 /* There must be attachments... */
3830 handle_multipart (
3831 composer, multipart, keep_signature, cancellable, 0);
3833 } else {
3834 CamelMimePart *mime_part;
3835 gboolean is_html = FALSE;
3836 gchar *html = NULL;
3837 gssize length = 0;
3839 mime_part = CAMEL_MIME_PART (message);
3840 content_type = camel_mime_part_get_content_type (mime_part);
3841 is_html = camel_content_type_is (content_type, "text", "html");
3843 if (content_type != NULL && (
3844 camel_content_type_is (
3845 content_type, "application", "x-pkcs7-mime") ||
3846 camel_content_type_is (
3847 content_type, "application", "pkcs7-mime"))) {
3849 gtk_toggle_action_set_active (
3850 GTK_TOGGLE_ACTION (
3851 ACTION (SMIME_ENCRYPT)), TRUE);
3854 /* If we are opening message from Drafts */
3855 if (is_message_from_draft) {
3856 /* Extract the body */
3857 CamelDataWrapper *dw;
3859 dw = camel_medium_get_content ((CamelMedium *) mime_part);
3860 if (dw) {
3861 CamelStream *mem = camel_stream_mem_new ();
3862 GByteArray *bytes;
3864 camel_data_wrapper_decode_to_stream_sync (dw, mem, cancellable, NULL);
3865 camel_stream_close (mem, cancellable, NULL);
3867 bytes = camel_stream_mem_get_byte_array (CAMEL_STREAM_MEM (mem));
3868 if (bytes && bytes->len)
3869 html = g_strndup ((const gchar *) bytes->data, bytes->len);
3871 g_object_unref (mem);
3873 } else {
3874 is_html = TRUE;
3875 html = emcu_part_to_html (
3876 composer, CAMEL_MIME_PART (message),
3877 &length, keep_signature, cancellable);
3879 e_msg_composer_set_pending_body (composer, html, length, is_html);
3882 e_html_editor_view_set_is_message_from_edit_as_new (view, TRUE);
3883 priv->set_signature_from_message = TRUE;
3885 /* We wait until now to set the body text because we need to
3886 * ensure that the attachment bar has all the attachments before
3887 * we request them. */
3888 e_msg_composer_flush_pending_body (composer);
3890 set_signature_gui (composer);
3892 return composer;
3896 * e_msg_composer_new_redirect:
3897 * @shell: an #EShell
3898 * @message: The message to use as the source
3900 * Create a new message composer widget.
3902 * Returns: A pointer to the newly created widget
3904 EMsgComposer *
3905 e_msg_composer_new_redirect (EShell *shell,
3906 CamelMimeMessage *message,
3907 const gchar *identity_uid,
3908 GCancellable *cancellable)
3910 EMsgComposer *composer;
3911 EComposerHeaderTable *table;
3912 EHTMLEditor *editor;
3913 EHTMLEditorView *view;
3914 const gchar *subject;
3916 g_return_val_if_fail (E_IS_SHELL (shell), NULL);
3917 g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL);
3919 composer = e_msg_composer_new_with_message (
3920 shell, message, TRUE, identity_uid, cancellable);
3921 table = e_msg_composer_get_header_table (composer);
3923 subject = camel_mime_message_get_subject (message);
3925 composer->priv->redirect = message;
3926 g_object_ref (message);
3928 e_composer_header_table_set_subject (table, subject);
3930 editor = e_msg_composer_get_editor (composer);
3931 view = e_html_editor_get_view (editor);
3932 webkit_web_view_set_editable (WEBKIT_WEB_VIEW (view), FALSE);
3934 return composer;
3938 * e_msg_composer_ref_session:
3939 * @composer: an #EMsgComposer
3941 * Returns the mail module's global #CamelSession instance. Calling
3942 * this function will load the mail module if it isn't already loaded.
3944 * The returned #CamelSession is referenced for thread-safety and must
3945 * be unreferenced with g_object_unref() when finished with it.
3947 * Returns: the mail module's #CamelSession
3949 CamelSession *
3950 e_msg_composer_ref_session (EMsgComposer *composer)
3952 EShell *shell;
3953 EShellBackend *shell_backend;
3954 CamelSession *session = NULL;
3956 g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
3958 shell = e_msg_composer_get_shell (composer);
3959 shell_backend = e_shell_get_backend_by_name (shell, "mail");
3961 g_object_get (shell_backend, "session", &session, NULL);
3962 g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
3964 return session;
3968 * e_msg_composer_get_shell:
3969 * @composer: an #EMsgComposer
3971 * Returns the #EShell that was passed to e_msg_composer_new().
3973 * Returns: the #EShell
3975 EShell *
3976 e_msg_composer_get_shell (EMsgComposer *composer)
3978 g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
3980 return E_SHELL (composer->priv->shell);
3983 static void
3984 msg_composer_send_cb (EMsgComposer *composer,
3985 GAsyncResult *result,
3986 AsyncContext *context)
3988 CamelMimeMessage *message;
3989 EAlertSink *alert_sink;
3990 EHTMLEditor *editor;
3991 EHTMLEditorView *view;
3992 GError *error = NULL;
3994 alert_sink = e_activity_get_alert_sink (context->activity);
3996 message = e_msg_composer_get_message_finish (composer, result, &error);
3998 if (e_activity_handle_cancellation (context->activity, error)) {
3999 g_warn_if_fail (message == NULL);
4000 async_context_free (context);
4001 g_error_free (error);
4003 gtk_window_present (GTK_WINDOW (composer));
4004 return;
4007 if (error != NULL) {
4008 g_warn_if_fail (message == NULL);
4009 e_alert_submit (
4010 alert_sink,
4011 "mail-composer:no-build-message",
4012 error->message, NULL);
4013 async_context_free (context);
4014 g_error_free (error);
4016 gtk_window_present (GTK_WINDOW (composer));
4017 return;
4020 g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
4022 /* The callback can set editor 'changed' if anything failed. */
4023 editor = e_msg_composer_get_editor (composer);
4024 view = e_html_editor_get_view (editor);
4025 e_html_editor_view_set_changed (view, TRUE);
4027 g_signal_emit (
4028 composer, signals[SEND], 0,
4029 message, context->activity);
4031 g_object_unref (message);
4033 async_context_free (context);
4037 * e_msg_composer_send:
4038 * @composer: an #EMsgComposer
4040 * Send the message in @composer.
4042 void
4043 e_msg_composer_send (EMsgComposer *composer)
4045 EHTMLEditor *editor;
4046 AsyncContext *context;
4047 GCancellable *cancellable;
4048 gboolean proceed_with_send = TRUE;
4050 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
4052 /* This gives the user a chance to abort the send. */
4053 g_signal_emit (composer, signals[PRESEND], 0, &proceed_with_send);
4055 if (!proceed_with_send) {
4056 gtk_window_present (GTK_WINDOW (composer));
4057 return;
4060 editor = e_msg_composer_get_editor (composer);
4062 context = g_slice_new0 (AsyncContext);
4063 context->activity = e_html_editor_new_activity (editor);
4065 cancellable = e_activity_get_cancellable (context->activity);
4067 e_msg_composer_get_message (
4068 composer, G_PRIORITY_DEFAULT, cancellable,
4069 (GAsyncReadyCallback) msg_composer_send_cb,
4070 context);
4073 static void
4074 msg_composer_save_to_drafts_cb (EMsgComposer *composer,
4075 GAsyncResult *result,
4076 AsyncContext *context)
4078 CamelMimeMessage *message;
4079 EAlertSink *alert_sink;
4080 EHTMLEditor *editor;
4081 EHTMLEditorView *view;
4082 GError *error = NULL;
4084 alert_sink = e_activity_get_alert_sink (context->activity);
4086 message = e_msg_composer_get_message_draft_finish (
4087 composer, result, &error);
4089 if (e_activity_handle_cancellation (context->activity, error)) {
4090 g_warn_if_fail (message == NULL);
4091 async_context_free (context);
4092 g_error_free (error);
4094 if (e_msg_composer_is_exiting (composer)) {
4095 gtk_window_present (GTK_WINDOW (composer));
4096 composer->priv->application_exiting = FALSE;
4099 return;
4102 if (error != NULL) {
4103 g_warn_if_fail (message == NULL);
4104 e_alert_submit (
4105 alert_sink,
4106 "mail-composer:no-build-message",
4107 error->message, NULL);
4108 async_context_free (context);
4109 g_error_free (error);
4111 if (e_msg_composer_is_exiting (composer)) {
4112 gtk_window_present (GTK_WINDOW (composer));
4113 composer->priv->application_exiting = FALSE;
4116 return;
4119 g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
4121 /* The callback can set editor 'changed' if anything failed. */
4122 editor = e_msg_composer_get_editor (composer);
4123 view = e_html_editor_get_view (editor);
4124 e_html_editor_view_set_changed (view, FALSE);
4126 g_signal_emit (
4127 composer, signals[SAVE_TO_DRAFTS],
4128 0, message, context->activity);
4130 g_object_unref (message);
4132 if (e_msg_composer_is_exiting (composer))
4133 g_object_weak_ref (
4134 G_OBJECT (context->activity),
4135 (GWeakNotify) gtk_widget_destroy, composer);
4137 async_context_free (context);
4141 * e_msg_composer_save_to_drafts:
4142 * @composer: an #EMsgComposer
4144 * Save the message in @composer to the selected account's Drafts folder.
4146 void
4147 e_msg_composer_save_to_drafts (EMsgComposer *composer)
4149 EHTMLEditor *editor;
4150 AsyncContext *context;
4151 GCancellable *cancellable;
4153 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
4155 editor = e_msg_composer_get_editor (composer);
4157 context = g_slice_new0 (AsyncContext);
4158 context->activity = e_html_editor_new_activity (editor);
4160 cancellable = e_activity_get_cancellable (context->activity);
4162 e_msg_composer_get_message_draft (
4163 composer, G_PRIORITY_DEFAULT, cancellable,
4164 (GAsyncReadyCallback) msg_composer_save_to_drafts_cb,
4165 context);
4168 static void
4169 msg_composer_save_to_outbox_cb (EMsgComposer *composer,
4170 GAsyncResult *result,
4171 AsyncContext *context)
4173 CamelMimeMessage *message;
4174 EAlertSink *alert_sink;
4175 EHTMLEditor *editor;
4176 EHTMLEditorView *view;
4177 GError *error = NULL;
4179 alert_sink = e_activity_get_alert_sink (context->activity);
4181 message = e_msg_composer_get_message_finish (composer, result, &error);
4183 if (e_activity_handle_cancellation (context->activity, error)) {
4184 g_warn_if_fail (message == NULL);
4185 async_context_free (context);
4186 g_error_free (error);
4187 return;
4190 if (error != NULL) {
4191 g_warn_if_fail (message == NULL);
4192 e_alert_submit (
4193 alert_sink,
4194 "mail-composer:no-build-message",
4195 error->message, NULL);
4196 async_context_free (context);
4197 g_error_free (error);
4198 return;
4201 g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
4203 g_signal_emit (
4204 composer, signals[SAVE_TO_OUTBOX],
4205 0, message, context->activity);
4207 g_object_unref (message);
4209 async_context_free (context);
4211 editor = e_msg_composer_get_editor (composer);
4212 view = e_html_editor_get_view (editor);
4213 e_html_editor_view_set_changed (view, FALSE);
4217 * e_msg_composer_save_to_outbox:
4218 * @composer: an #EMsgComposer
4220 * Save the message in @composer to the local Outbox folder.
4222 void
4223 e_msg_composer_save_to_outbox (EMsgComposer *composer)
4225 EHTMLEditor *editor;
4226 AsyncContext *context;
4227 GCancellable *cancellable;
4228 gboolean proceed_with_save = TRUE;
4230 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
4232 /* This gives the user a chance to abort the save. */
4233 g_signal_emit (composer, signals[PRESEND], 0, &proceed_with_save);
4235 if (!proceed_with_save)
4236 return;
4238 editor = e_msg_composer_get_editor (composer);
4240 context = g_slice_new0 (AsyncContext);
4241 context->activity = e_html_editor_new_activity (editor);
4243 cancellable = e_activity_get_cancellable (context->activity);
4245 e_msg_composer_get_message (
4246 composer, G_PRIORITY_DEFAULT, cancellable,
4247 (GAsyncReadyCallback) msg_composer_save_to_outbox_cb,
4248 context);
4251 static void
4252 msg_composer_print_cb (EMsgComposer *composer,
4253 GAsyncResult *result,
4254 AsyncContext *context)
4256 CamelMimeMessage *message;
4257 EAlertSink *alert_sink;
4258 GError *error = NULL;
4260 alert_sink = e_activity_get_alert_sink (context->activity);
4262 message = e_msg_composer_get_message_print_finish (
4263 composer, result, &error);
4265 if (e_activity_handle_cancellation (context->activity, error)) {
4266 g_warn_if_fail (message == NULL);
4267 async_context_free (context);
4268 g_error_free (error);
4269 return;
4272 if (error != NULL) {
4273 g_warn_if_fail (message == NULL);
4274 async_context_free (context);
4275 e_alert_submit (
4276 alert_sink,
4277 "mail-composer:no-build-message",
4278 error->message, NULL);
4279 g_error_free (error);
4280 return;
4283 g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
4285 g_signal_emit (
4286 composer, signals[PRINT], 0,
4287 context->print_action, message, context->activity);
4289 g_object_unref (message);
4291 async_context_free (context);
4295 * e_msg_composer_print:
4296 * @composer: an #EMsgComposer
4297 * @print_action: the print action to start
4299 * Print the message in @composer.
4301 void
4302 e_msg_composer_print (EMsgComposer *composer,
4303 GtkPrintOperationAction print_action)
4305 EHTMLEditor *editor;
4306 AsyncContext *context;
4307 GCancellable *cancellable;
4309 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
4311 editor = e_msg_composer_get_editor (composer);
4313 context = g_slice_new0 (AsyncContext);
4314 context->activity = e_html_editor_new_activity (editor);
4315 context->print_action = print_action;
4317 cancellable = e_activity_get_cancellable (context->activity);
4319 e_msg_composer_get_message_print (
4320 composer, G_PRIORITY_DEFAULT, cancellable,
4321 (GAsyncReadyCallback) msg_composer_print_cb,
4322 context);
4325 static GList *
4326 add_recipients (GList *list,
4327 const gchar *recips)
4329 CamelInternetAddress *cia;
4330 const gchar *name, *addr;
4331 gint num, i;
4333 cia = camel_internet_address_new ();
4334 num = camel_address_decode (CAMEL_ADDRESS (cia), recips);
4336 for (i = 0; i < num; i++) {
4337 if (camel_internet_address_get (cia, i, &name, &addr)) {
4338 EDestination *dest = e_destination_new ();
4339 e_destination_set_name (dest, name);
4340 e_destination_set_email (dest, addr);
4342 list = g_list_append (list, dest);
4346 g_object_unref (cia);
4348 return list;
4351 static gboolean
4352 list_contains_addr (const GList *list,
4353 EDestination *dest)
4355 g_return_val_if_fail (dest != NULL, FALSE);
4357 while (list != NULL) {
4358 if (e_destination_equal (dest, list->data))
4359 return TRUE;
4361 list = list->next;
4364 return FALSE;
4367 static void
4368 merge_cc_bcc (EDestination **addrv,
4369 GList **merge_into,
4370 const GList *to,
4371 const GList *cc,
4372 const GList *bcc)
4374 gint ii;
4376 for (ii = 0; addrv && addrv[ii]; ii++) {
4377 if (!list_contains_addr (to, addrv[ii]) &&
4378 !list_contains_addr (cc, addrv[ii]) &&
4379 !list_contains_addr (bcc, addrv[ii])) {
4380 *merge_into = g_list_append (
4381 *merge_into, g_object_ref (addrv[ii]));
4386 static void
4387 merge_always_cc_and_bcc (EComposerHeaderTable *table,
4388 const GList *to,
4389 GList **cc,
4390 GList **bcc)
4392 EDestination **addrv;
4394 g_return_if_fail (table != NULL);
4395 g_return_if_fail (cc != NULL);
4396 g_return_if_fail (bcc != NULL);
4398 addrv = e_composer_header_table_get_destinations_cc (table);
4399 merge_cc_bcc (addrv, cc, to, *cc, *bcc);
4400 e_destination_freev (addrv);
4402 addrv = e_composer_header_table_get_destinations_bcc (table);
4403 merge_cc_bcc (addrv, bcc, to, *cc, *bcc);
4404 e_destination_freev (addrv);
4407 static const gchar *blacklist[] = { ".", "etc", ".." };
4409 static gboolean
4410 file_is_blacklisted (const gchar *argument)
4412 GFile *file;
4413 gboolean blacklisted = FALSE;
4414 guint ii, jj, n_parts;
4415 gchar *filename;
4416 gchar **parts;
4418 /* The "attach" argument may be a URI or local path. Normalize
4419 * it to a local path if we can. We only blacklist local files. */
4420 file = g_file_new_for_commandline_arg (argument);
4421 filename = g_file_get_path (file);
4422 g_object_unref (file);
4424 if (filename == NULL)
4425 return FALSE;
4427 parts = g_strsplit (filename, G_DIR_SEPARATOR_S, -1);
4428 n_parts = g_strv_length (parts);
4430 for (ii = 0; ii < G_N_ELEMENTS (blacklist); ii++) {
4431 for (jj = 0; jj < n_parts; jj++) {
4432 if (g_str_has_prefix (parts[jj], blacklist[ii])) {
4433 blacklisted = TRUE;
4434 break;
4439 if (blacklisted) {
4440 gchar *base_dir;
4442 /* Don't blacklist files in trusted base directories. */
4443 if (g_str_has_prefix (filename, g_get_user_data_dir ()))
4444 blacklisted = FALSE;
4445 if (g_str_has_prefix (filename, g_get_user_cache_dir ()))
4446 blacklisted = FALSE;
4447 if (g_str_has_prefix (filename, g_get_user_config_dir ()))
4448 blacklisted = FALSE;
4450 /* Apparently KDE still uses ~/.kde heavily, and some
4451 * distributions use ~/.kde4 to distinguish KDE4 data
4452 * from KDE3 data. Trust these directories as well. */
4454 base_dir = g_build_filename (g_get_home_dir (), ".kde", NULL);
4455 if (g_str_has_prefix (filename, base_dir))
4456 blacklisted = FALSE;
4457 g_free (base_dir);
4459 base_dir = g_build_filename (g_get_home_dir (), ".kde4", NULL);
4460 if (g_str_has_prefix (filename, base_dir))
4461 blacklisted = FALSE;
4462 g_free (base_dir);
4465 g_strfreev (parts);
4466 g_free (filename);
4468 return blacklisted;
4471 static void
4472 handle_mailto (EMsgComposer *composer,
4473 const gchar *mailto)
4475 EAttachmentView *view;
4476 EAttachmentStore *store;
4477 EComposerHeaderTable *table;
4478 GList *to = NULL, *cc = NULL, *bcc = NULL;
4479 EDestination **tov, **ccv, **bccv;
4480 gchar *subject = NULL, *body = NULL;
4481 gchar *header, *content, *buf;
4482 gsize nread, nwritten;
4483 const gchar *p;
4484 gint len, clen;
4486 table = e_msg_composer_get_header_table (composer);
4487 view = e_msg_composer_get_attachment_view (composer);
4488 store = e_attachment_view_get_store (view);
4490 buf = g_strdup (mailto);
4492 /* Parse recipients (everything after ':' until '?' or eos). */
4493 p = buf + 7;
4494 len = strcspn (p, "?");
4495 if (len) {
4496 content = g_strndup (p, len);
4497 camel_url_decode (content);
4498 to = add_recipients (to, content);
4499 g_free (content);
4502 p += len;
4503 if (*p == '?') {
4504 p++;
4506 while (*p) {
4507 len = strcspn (p, "=&");
4509 /* If it's malformed, give up. */
4510 if (p[len] != '=')
4511 break;
4513 header = (gchar *) p;
4514 header[len] = '\0';
4515 p += len + 1;
4517 clen = strcspn (p, "&");
4519 content = g_strndup (p, clen);
4521 if (!g_ascii_strcasecmp (header, "to")) {
4522 camel_url_decode (content);
4523 to = add_recipients (to, content);
4524 } else if (!g_ascii_strcasecmp (header, "cc")) {
4525 camel_url_decode (content);
4526 cc = add_recipients (cc, content);
4527 } else if (!g_ascii_strcasecmp (header, "bcc")) {
4528 camel_url_decode (content);
4529 bcc = add_recipients (bcc, content);
4530 } else if (!g_ascii_strcasecmp (header, "subject")) {
4531 g_free (subject);
4532 camel_url_decode (content);
4533 if (g_utf8_validate (content, -1, NULL)) {
4534 subject = content;
4535 content = NULL;
4536 } else {
4537 subject = g_locale_to_utf8 (
4538 content, clen, &nread,
4539 &nwritten, NULL);
4540 if (subject) {
4541 subject = g_realloc (subject, nwritten + 1);
4542 subject[nwritten] = '\0';
4545 } else if (!g_ascii_strcasecmp (header, "body")) {
4546 g_free (body);
4547 camel_url_decode (content);
4548 if (g_utf8_validate (content, -1, NULL)) {
4549 body = content;
4550 content = NULL;
4551 } else {
4552 body = g_locale_to_utf8 (
4553 content, clen, &nread,
4554 &nwritten, NULL);
4555 if (body) {
4556 body = g_realloc (body, nwritten + 1);
4557 body[nwritten] = '\0';
4560 } else if (!g_ascii_strcasecmp (header, "attach") ||
4561 !g_ascii_strcasecmp (header, "attachment")) {
4562 EAttachment *attachment;
4564 camel_url_decode (content);
4565 if (file_is_blacklisted (content))
4566 e_alert_submit (
4567 E_ALERT_SINK (e_msg_composer_get_editor (composer)),
4568 "mail:blacklisted-file",
4569 content, NULL);
4570 if (g_ascii_strncasecmp (content, "file:", 5) == 0)
4571 attachment = e_attachment_new_for_uri (content);
4572 else
4573 attachment = e_attachment_new_for_path (content);
4574 e_attachment_store_add_attachment (store, attachment);
4575 e_attachment_load_async (
4576 attachment, (GAsyncReadyCallback)
4577 e_attachment_load_handle_error, composer);
4578 g_object_unref (attachment);
4579 } else if (!g_ascii_strcasecmp (header, "from")) {
4580 /* Ignore */
4581 } else if (!g_ascii_strcasecmp (header, "reply-to")) {
4582 /* ignore */
4583 } else {
4584 /* add an arbitrary header? */
4585 camel_url_decode (content);
4586 e_msg_composer_add_header (composer, header, content);
4589 g_free (content);
4591 p += clen;
4592 if (*p == '&') {
4593 p++;
4594 if (!g_ascii_strncasecmp (p, "amp;", 4))
4595 p += 4;
4600 g_free (buf);
4602 merge_always_cc_and_bcc (table, to, &cc, &bcc);
4604 tov = destination_list_to_vector (to);
4605 ccv = destination_list_to_vector (cc);
4606 bccv = destination_list_to_vector (bcc);
4608 g_list_free (to);
4609 g_list_free (cc);
4610 g_list_free (bcc);
4612 e_composer_header_table_set_destinations_to (table, tov);
4613 e_composer_header_table_set_destinations_cc (table, ccv);
4614 e_composer_header_table_set_destinations_bcc (table, bccv);
4616 e_destination_freev (tov);
4617 e_destination_freev (ccv);
4618 e_destination_freev (bccv);
4620 e_composer_header_table_set_subject (table, subject);
4621 g_free (subject);
4623 if (body) {
4624 gchar *html_body;
4626 html_body = camel_text_to_html (body, CAMEL_MIME_FILTER_TOHTML_PRE, 0);
4627 set_editor_text (composer, html_body, TRUE, TRUE);
4628 g_free (html_body);
4633 * e_msg_composer_new_from_url:
4634 * @shell: an #EShell
4635 * @url: a mailto URL
4637 * Create a new message composer widget, and fill in fields as
4638 * defined by the provided URL.
4640 EMsgComposer *
4641 e_msg_composer_new_from_url (EShell *shell,
4642 const gchar *url)
4644 EMsgComposer *composer;
4646 g_return_val_if_fail (E_IS_SHELL (shell), NULL);
4647 g_return_val_if_fail (g_ascii_strncasecmp (url, "mailto:", 7) == 0, NULL);
4649 composer = e_msg_composer_new (shell);
4651 handle_mailto (composer, url);
4653 return composer;
4657 * e_msg_composer_set_body_text:
4658 * @composer: a composer object
4659 * @text: the HTML text to initialize the editor with
4660 * @update_signature: whether update signature in the text after setting it;
4661 * Might be usually called with TRUE.
4663 * Loads the given HTML text into the editor.
4665 void
4666 e_msg_composer_set_body_text (EMsgComposer *composer,
4667 const gchar *text,
4668 gboolean update_signature)
4670 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
4671 g_return_if_fail (text != NULL);
4673 /* Every usage of e_msg_composer_set_body_text is called with HTML text */
4674 set_editor_text (composer, text, TRUE, update_signature);
4678 * e_msg_composer_set_body:
4679 * @composer: a composer object
4680 * @body: the data to initialize the composer with
4681 * @mime_type: the MIME type of data
4683 * Loads the given data into the composer as the message body.
4685 void
4686 e_msg_composer_set_body (EMsgComposer *composer,
4687 const gchar *body,
4688 const gchar *mime_type)
4690 EMsgComposerPrivate *priv = composer->priv;
4691 EComposerHeaderTable *table;
4692 EHTMLEditor *editor;
4693 EHTMLEditorView *view;
4694 ESource *source;
4695 const gchar *identity_uid;
4696 const gchar *content;
4698 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
4700 editor = e_msg_composer_get_editor (composer);
4701 view = e_html_editor_get_view (editor);
4702 table = e_msg_composer_get_header_table (composer);
4704 /* Disable signature */
4705 priv->disable_signature = TRUE;
4707 identity_uid = e_composer_header_table_get_identity_uid (table);
4708 source = e_composer_header_table_ref_source (table, identity_uid);
4710 content = _("The composer contains a non-text message body, which cannot be edited.");
4711 set_editor_text (composer, content, TRUE, FALSE);
4713 e_html_editor_view_set_html_mode (view, FALSE);
4714 webkit_web_view_set_editable (WEBKIT_WEB_VIEW (view), FALSE);
4716 g_free (priv->mime_body);
4717 priv->mime_body = g_strdup (body);
4718 g_free (priv->mime_type);
4719 priv->mime_type = g_strdup (mime_type);
4721 if (g_ascii_strncasecmp (priv->mime_type, "text/calendar", 13) == 0) {
4722 ESourceMailComposition *extension;
4723 const gchar *extension_name;
4725 extension_name = E_SOURCE_EXTENSION_MAIL_COMPOSITION;
4726 extension = e_source_get_extension (source, extension_name);
4728 if (!e_source_mail_composition_get_sign_imip (extension)) {
4729 GtkToggleAction *action;
4731 action = GTK_TOGGLE_ACTION (ACTION (PGP_SIGN));
4732 gtk_toggle_action_set_active (action, FALSE);
4734 action = GTK_TOGGLE_ACTION (ACTION (SMIME_SIGN));
4735 gtk_toggle_action_set_active (action, FALSE);
4739 g_object_unref (source);
4743 * e_msg_composer_add_header:
4744 * @composer: an #EMsgComposer
4745 * @name: the header's name
4746 * @value: the header's value
4748 * Adds a new custom header created from @name and @value. The header
4749 * is not shown in the user interface but will be added to the resulting
4750 * MIME message when sending or saving.
4752 void
4753 e_msg_composer_add_header (EMsgComposer *composer,
4754 const gchar *name,
4755 const gchar *value)
4757 EMsgComposerPrivate *priv;
4759 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
4760 g_return_if_fail (name != NULL);
4761 g_return_if_fail (value != NULL);
4763 priv = composer->priv;
4765 g_ptr_array_add (priv->extra_hdr_names, g_strdup (name));
4766 g_ptr_array_add (priv->extra_hdr_values, g_strdup (value));
4770 * e_msg_composer_set_header:
4771 * @composer: an #EMsgComposer
4772 * @name: the header's name
4773 * @value: the header's value
4775 * Replaces all custom headers matching @name that were added with
4776 * e_msg_composer_add_header() or e_msg_composer_set_header(), with
4777 * a new custom header created from @name and @value. The header is
4778 * not shown in the user interface but will be added to the resulting
4779 * MIME message when sending or saving.
4781 void
4782 e_msg_composer_set_header (EMsgComposer *composer,
4783 const gchar *name,
4784 const gchar *value)
4786 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
4787 g_return_if_fail (name != NULL);
4788 g_return_if_fail (value != NULL);
4790 e_msg_composer_remove_header (composer, name);
4791 e_msg_composer_add_header (composer, name, value);
4795 * e_msg_composer_remove_header:
4796 * @composer: an #EMsgComposer
4797 * @name: the header's name
4799 * Removes all custom headers matching @name that were added with
4800 * e_msg_composer_add_header() or e_msg_composer_set_header().
4802 void
4803 e_msg_composer_remove_header (EMsgComposer *composer,
4804 const gchar *name)
4806 EMsgComposerPrivate *priv;
4807 guint ii;
4809 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
4810 g_return_if_fail (name != NULL);
4812 priv = composer->priv;
4814 for (ii = 0; ii < priv->extra_hdr_names->len; ii++) {
4815 if (g_strcmp0 (priv->extra_hdr_names->pdata[ii], name) == 0) {
4816 g_free (priv->extra_hdr_names->pdata[ii]);
4817 g_free (priv->extra_hdr_values->pdata[ii]);
4818 g_ptr_array_remove_index (priv->extra_hdr_names, ii);
4819 g_ptr_array_remove_index (priv->extra_hdr_values, ii);
4825 * e_msg_composer_set_draft_headers:
4826 * @composer: an #EMsgComposer
4827 * @folder_uri: folder URI of the last saved draft
4828 * @message_uid: message UID of the last saved draft
4830 * Add special X-Evolution-Draft headers to remember the most recently
4831 * saved draft message, even across Evolution sessions. These headers
4832 * can be used to mark the draft message for deletion after saving a
4833 * newer draft or sending the composed message.
4835 void
4836 e_msg_composer_set_draft_headers (EMsgComposer *composer,
4837 const gchar *folder_uri,
4838 const gchar *message_uid)
4840 const gchar *header_name;
4842 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
4843 g_return_if_fail (folder_uri != NULL);
4844 g_return_if_fail (message_uid != NULL);
4846 header_name = "X-Evolution-Draft-Folder";
4847 e_msg_composer_set_header (composer, header_name, folder_uri);
4849 header_name = "X-Evolution-Draft-Message";
4850 e_msg_composer_set_header (composer, header_name, message_uid);
4854 * e_msg_composer_set_source_headers:
4855 * @composer: an #EMsgComposer
4856 * @folder_uri: folder URI of the source message
4857 * @message_uid: message UID of the source message
4858 * @flags: flags to set on the source message after sending
4860 * Add special X-Evolution-Source headers to remember the message being
4861 * forwarded or replied to, even across Evolution sessions. These headers
4862 * can be used to set appropriate flags on the source message after sending
4863 * the composed message.
4865 void
4866 e_msg_composer_set_source_headers (EMsgComposer *composer,
4867 const gchar *folder_uri,
4868 const gchar *message_uid,
4869 CamelMessageFlags flags)
4871 GString *buffer;
4872 const gchar *header_name;
4874 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
4875 g_return_if_fail (folder_uri != NULL);
4876 g_return_if_fail (message_uid != NULL);
4878 buffer = g_string_sized_new (32);
4880 if (flags & CAMEL_MESSAGE_ANSWERED)
4881 g_string_append (buffer, "ANSWERED ");
4882 if (flags & CAMEL_MESSAGE_ANSWERED_ALL)
4883 g_string_append (buffer, "ANSWERED_ALL ");
4884 if (flags & CAMEL_MESSAGE_FORWARDED)
4885 g_string_append (buffer, "FORWARDED ");
4886 if (flags & CAMEL_MESSAGE_SEEN)
4887 g_string_append (buffer, "SEEN ");
4889 header_name = "X-Evolution-Source-Folder";
4890 e_msg_composer_set_header (composer, header_name, folder_uri);
4892 header_name = "X-Evolution-Source-Message";
4893 e_msg_composer_set_header (composer, header_name, message_uid);
4895 header_name = "X-Evolution-Source-Flags";
4896 e_msg_composer_set_header (composer, header_name, buffer->str);
4898 g_string_free (buffer, TRUE);
4902 * e_msg_composer_attach:
4903 * @composer: a composer object
4904 * @mime_part: the #CamelMimePart to attach
4906 * Attaches @attachment to the message being composed in the composer.
4908 void
4909 e_msg_composer_attach (EMsgComposer *composer,
4910 CamelMimePart *mime_part)
4912 EAttachmentView *view;
4913 EAttachmentStore *store;
4914 EAttachment *attachment;
4916 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
4917 g_return_if_fail (CAMEL_IS_MIME_PART (mime_part));
4919 view = e_msg_composer_get_attachment_view (composer);
4920 store = e_attachment_view_get_store (view);
4922 attachment = e_attachment_new ();
4923 e_attachment_set_mime_part (attachment, mime_part);
4924 e_attachment_store_add_attachment (store, attachment);
4925 e_attachment_load_async (
4926 attachment, (GAsyncReadyCallback)
4927 e_attachment_load_handle_error, composer);
4928 g_object_unref (attachment);
4931 static void
4932 composer_get_message_ready (EMsgComposer *composer,
4933 GAsyncResult *result,
4934 GSimpleAsyncResult *simple)
4936 CamelMimeMessage *message;
4937 GError *error = NULL;
4939 message = composer_build_message_finish (composer, result, &error);
4941 if (message != NULL)
4942 g_simple_async_result_set_op_res_gpointer (
4943 simple, message, (GDestroyNotify) g_object_unref);
4945 if (error != NULL) {
4946 g_warn_if_fail (message == NULL);
4947 g_simple_async_result_take_error (simple, error);
4950 g_simple_async_result_complete (simple);
4952 g_object_unref (simple);
4956 * e_msg_composer_get_message:
4957 * @composer: an #EMsgComposer
4959 * Retrieve the message edited by the user as a #CamelMimeMessage. The
4960 * #CamelMimeMessage object is created on the fly; subsequent calls to this
4961 * function will always create new objects from scratch.
4963 void
4964 e_msg_composer_get_message (EMsgComposer *composer,
4965 gint io_priority,
4966 GCancellable *cancellable,
4967 GAsyncReadyCallback callback,
4968 gpointer user_data)
4970 GSimpleAsyncResult *simple;
4971 GtkAction *action;
4972 ComposerFlags flags = 0;
4973 EHTMLEditor *editor;
4974 EHTMLEditorView *view;
4976 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
4978 editor = e_msg_composer_get_editor (composer);
4979 view = e_html_editor_get_view (editor);
4981 simple = g_simple_async_result_new (
4982 G_OBJECT (composer), callback,
4983 user_data, e_msg_composer_get_message);
4985 g_simple_async_result_set_check_cancellable (simple, cancellable);
4987 if (e_html_editor_view_get_html_mode (view))
4988 flags |= COMPOSER_FLAG_HTML_CONTENT;
4990 action = ACTION (PRIORITIZE_MESSAGE);
4991 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
4992 flags |= COMPOSER_FLAG_PRIORITIZE_MESSAGE;
4994 action = ACTION (REQUEST_READ_RECEIPT);
4995 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
4996 flags |= COMPOSER_FLAG_REQUEST_READ_RECEIPT;
4998 action = ACTION (PGP_SIGN);
4999 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
5000 flags |= COMPOSER_FLAG_PGP_SIGN;
5002 action = ACTION (PGP_ENCRYPT);
5003 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
5004 flags |= COMPOSER_FLAG_PGP_ENCRYPT;
5006 #ifdef HAVE_NSS
5007 action = ACTION (SMIME_SIGN);
5008 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
5009 flags |= COMPOSER_FLAG_SMIME_SIGN;
5011 action = ACTION (SMIME_ENCRYPT);
5012 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
5013 flags |= COMPOSER_FLAG_SMIME_ENCRYPT;
5014 #endif
5016 composer_build_message (
5017 composer, flags, io_priority,
5018 cancellable, (GAsyncReadyCallback)
5019 composer_get_message_ready, simple);
5022 CamelMimeMessage *
5023 e_msg_composer_get_message_finish (EMsgComposer *composer,
5024 GAsyncResult *result,
5025 GError **error)
5027 GSimpleAsyncResult *simple;
5028 CamelMimeMessage *message;
5030 g_return_val_if_fail (
5031 g_simple_async_result_is_valid (
5032 result, G_OBJECT (composer),
5033 e_msg_composer_get_message), NULL);
5035 simple = G_SIMPLE_ASYNC_RESULT (result);
5036 message = g_simple_async_result_get_op_res_gpointer (simple);
5038 if (g_simple_async_result_propagate_error (simple, error))
5039 return NULL;
5041 g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL);
5043 return g_object_ref (message);
5046 void
5047 e_msg_composer_get_message_print (EMsgComposer *composer,
5048 gint io_priority,
5049 GCancellable *cancellable,
5050 GAsyncReadyCallback callback,
5051 gpointer user_data)
5053 GSimpleAsyncResult *simple;
5054 ComposerFlags flags = 0;
5056 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
5058 simple = g_simple_async_result_new (
5059 G_OBJECT (composer), callback,
5060 user_data, e_msg_composer_get_message_print);
5062 g_simple_async_result_set_check_cancellable (simple, cancellable);
5064 flags |= COMPOSER_FLAG_HTML_CONTENT;
5065 flags |= COMPOSER_FLAG_SAVE_OBJECT_DATA;
5067 composer_build_message (
5068 composer, flags, io_priority,
5069 cancellable, (GAsyncReadyCallback)
5070 composer_get_message_ready, simple);
5073 CamelMimeMessage *
5074 e_msg_composer_get_message_print_finish (EMsgComposer *composer,
5075 GAsyncResult *result,
5076 GError **error)
5078 GSimpleAsyncResult *simple;
5079 CamelMimeMessage *message;
5081 g_return_val_if_fail (
5082 g_simple_async_result_is_valid (
5083 result, G_OBJECT (composer),
5084 e_msg_composer_get_message_print), NULL);
5086 simple = G_SIMPLE_ASYNC_RESULT (result);
5087 message = g_simple_async_result_get_op_res_gpointer (simple);
5089 if (g_simple_async_result_propagate_error (simple, error))
5090 return NULL;
5092 g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL);
5094 return g_object_ref (message);
5097 void
5098 e_msg_composer_get_message_draft (EMsgComposer *composer,
5099 gint io_priority,
5100 GCancellable *cancellable,
5101 GAsyncReadyCallback callback,
5102 gpointer user_data)
5104 EHTMLEditor *editor;
5105 EHTMLEditorView *view;
5106 GSimpleAsyncResult *simple;
5107 ComposerFlags flags = COMPOSER_FLAG_SAVE_DRAFT;
5108 GtkAction *action;
5110 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
5112 simple = g_simple_async_result_new (
5113 G_OBJECT (composer), callback,
5114 user_data, e_msg_composer_get_message_draft);
5116 g_simple_async_result_set_check_cancellable (simple, cancellable);
5118 editor = e_msg_composer_get_editor (composer);
5119 view = e_html_editor_get_view (editor);
5120 /* We need to remember composer mode */
5121 if (e_html_editor_view_get_html_mode (view))
5122 flags |= COMPOSER_FLAG_HTML_MODE;
5123 /* We want to save HTML content everytime when we save as draft */
5124 flags |= COMPOSER_FLAG_SAVE_DRAFT;
5126 action = ACTION (PRIORITIZE_MESSAGE);
5127 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
5128 flags |= COMPOSER_FLAG_PRIORITIZE_MESSAGE;
5130 action = ACTION (REQUEST_READ_RECEIPT);
5131 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
5132 flags |= COMPOSER_FLAG_REQUEST_READ_RECEIPT;
5134 action = ACTION (PGP_SIGN);
5135 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
5136 flags |= COMPOSER_FLAG_PGP_SIGN;
5138 action = ACTION (PGP_ENCRYPT);
5139 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
5140 flags |= COMPOSER_FLAG_PGP_ENCRYPT;
5142 #ifdef HAVE_NSS
5143 action = ACTION (SMIME_SIGN);
5144 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
5145 flags |= COMPOSER_FLAG_SMIME_SIGN;
5147 action = ACTION (SMIME_ENCRYPT);
5148 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
5149 flags |= COMPOSER_FLAG_SMIME_ENCRYPT;
5150 #endif
5152 composer_build_message (
5153 composer, flags, io_priority,
5154 cancellable, (GAsyncReadyCallback)
5155 composer_get_message_ready, simple);
5158 CamelMimeMessage *
5159 e_msg_composer_get_message_draft_finish (EMsgComposer *composer,
5160 GAsyncResult *result,
5161 GError **error)
5163 GSimpleAsyncResult *simple;
5164 CamelMimeMessage *message;
5166 g_return_val_if_fail (
5167 g_simple_async_result_is_valid (
5168 result, G_OBJECT (composer),
5169 e_msg_composer_get_message_draft), NULL);
5171 simple = G_SIMPLE_ASYNC_RESULT (result);
5172 message = g_simple_async_result_get_op_res_gpointer (simple);
5174 if (g_simple_async_result_propagate_error (simple, error))
5175 return NULL;
5177 g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL);
5179 return g_object_ref (message);
5182 CamelInternetAddress *
5183 e_msg_composer_get_from (EMsgComposer *composer)
5185 CamelInternetAddress *inet_address = NULL;
5186 ESourceMailIdentity *mail_identity;
5187 EComposerHeaderTable *table;
5188 ESource *source;
5189 const gchar *extension_name;
5190 const gchar *uid;
5191 gchar *name;
5192 gchar *address;
5194 g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
5196 table = e_msg_composer_get_header_table (composer);
5198 uid = e_composer_header_table_get_identity_uid (table);
5199 source = e_composer_header_table_ref_source (table, uid);
5200 g_return_val_if_fail (source != NULL, NULL);
5202 extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY;
5203 mail_identity = e_source_get_extension (source, extension_name);
5205 name = e_source_mail_identity_dup_name (mail_identity);
5206 address = e_source_mail_identity_dup_address (mail_identity);
5208 g_object_unref (source);
5210 if (name != NULL && address != NULL) {
5211 inet_address = camel_internet_address_new ();
5212 camel_internet_address_add (inet_address, name, address);
5215 g_free (name);
5216 g_free (address);
5218 return inet_address;
5221 CamelInternetAddress *
5222 e_msg_composer_get_reply_to (EMsgComposer *composer)
5224 CamelInternetAddress *address;
5225 EComposerHeaderTable *table;
5226 const gchar *reply_to;
5228 g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
5230 table = e_msg_composer_get_header_table (composer);
5232 reply_to = e_composer_header_table_get_reply_to (table);
5233 if (reply_to == NULL || *reply_to == '\0')
5234 return NULL;
5236 address = camel_internet_address_new ();
5237 if (camel_address_unformat (CAMEL_ADDRESS (address), reply_to) == -1) {
5238 g_object_unref (address);
5239 address = NULL;
5242 return address;
5246 * e_msg_composer_get_raw_message_text_without_signature:
5248 * Returns the text/plain of the message from composer without signature
5250 GByteArray *
5251 e_msg_composer_get_raw_message_text_without_signature (EMsgComposer *composer)
5253 EHTMLEditor *editor;
5254 EHTMLEditorView *view;
5255 GByteArray *array;
5256 gint ii, length;
5257 WebKitDOMDocument *document;
5258 WebKitDOMNodeList *list;
5260 g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
5262 editor = e_msg_composer_get_editor (composer);
5263 view = e_html_editor_get_view (editor);
5264 document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
5265 array = g_byte_array_new ();
5267 list = webkit_dom_document_query_selector_all (
5268 document, "body > *:not(.-x-evo-signature-wrapper)", NULL);
5269 length = webkit_dom_node_list_get_length (list);
5270 for (ii = 0; ii < length; ii++) {
5271 gchar *text;
5272 WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
5274 text = webkit_dom_html_element_get_inner_text (
5275 WEBKIT_DOM_HTML_ELEMENT (node));
5276 g_byte_array_append (array, (guint8 *) text, strlen (text));
5277 g_free (text);
5278 g_object_unref (node);
5281 g_object_unref (list);
5283 return array;
5287 * e_msg_composer_get_raw_message_text:
5289 * Returns the text/plain of the message from composer
5291 GByteArray *
5292 e_msg_composer_get_raw_message_text (EMsgComposer *composer)
5294 EHTMLEditor *editor;
5295 EHTMLEditorView *view;
5296 GByteArray *array;
5297 gchar *text;
5298 WebKitDOMDocument *document;
5299 WebKitDOMHTMLElement *body;
5301 g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
5303 editor = e_msg_composer_get_editor (composer);
5304 view = e_html_editor_get_view (editor);
5305 document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
5306 body = webkit_dom_document_get_body (document);
5308 array = g_byte_array_new ();
5309 text = webkit_dom_html_element_get_inner_text (body);
5310 g_byte_array_append (array, (guint8 *) text, strlen (text));
5311 g_free (text);
5313 return array;
5316 gboolean
5317 e_msg_composer_is_exiting (EMsgComposer *composer)
5319 g_return_val_if_fail (composer != NULL, FALSE);
5321 return composer->priv->application_exiting;
5324 void
5325 e_msg_composer_request_close (EMsgComposer *composer)
5327 g_return_if_fail (composer != NULL);
5329 composer->priv->application_exiting = TRUE;
5332 /* Returns whether can close the composer immediately. It will return FALSE
5333 * also when saving to drafts, but the e_msg_composer_is_exiting will return
5334 * TRUE for this case. can_save_draft means whether can save draft
5335 * immediately, or rather keep it on the caller (when FALSE). If kept on the
5336 * folder, then returns FALSE and sets interval variable to return TRUE in
5337 * e_msg_composer_is_exiting. */
5338 gboolean
5339 e_msg_composer_can_close (EMsgComposer *composer,
5340 gboolean can_save_draft)
5342 gboolean res = FALSE;
5343 EHTMLEditor *editor;
5344 EHTMLEditorView *view;
5345 EComposerHeaderTable *table;
5346 GdkWindow *window;
5347 GtkWidget *widget;
5348 const gchar *subject, *message_name;
5349 gint response;
5351 widget = GTK_WIDGET (composer);
5352 editor = e_msg_composer_get_editor (composer);
5353 view = e_html_editor_get_view (editor);
5355 /* this means that there is an async operation running,
5356 * in which case the composer cannot be closed */
5357 if (!gtk_action_group_get_sensitive (composer->priv->async_actions))
5358 return FALSE;
5360 if (!e_html_editor_view_get_changed (view))
5361 return TRUE;
5363 window = gtk_widget_get_window (widget);
5364 gdk_window_raise (window);
5366 table = e_msg_composer_get_header_table (composer);
5367 subject = e_composer_header_table_get_subject (table);
5369 if (subject == NULL || *subject == '\0')
5370 message_name = "mail-composer:exit-unsaved-no-subject";
5371 else
5372 message_name = "mail-composer:exit-unsaved";
5374 response = e_alert_run_dialog_for_args (
5375 GTK_WINDOW (composer),
5376 message_name,
5377 subject, NULL);
5379 switch (response) {
5380 case GTK_RESPONSE_YES:
5381 gtk_widget_hide (widget);
5382 e_msg_composer_request_close (composer);
5383 if (can_save_draft)
5384 gtk_action_activate (ACTION (SAVE_DRAFT));
5385 break;
5387 case GTK_RESPONSE_NO:
5388 res = TRUE;
5389 break;
5391 case GTK_RESPONSE_CANCEL:
5392 break;
5395 return res;
5398 EComposerHeaderTable *
5399 e_msg_composer_get_header_table (EMsgComposer *composer)
5401 g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
5403 return E_COMPOSER_HEADER_TABLE (composer->priv->header_table);
5406 EAttachmentView *
5407 e_msg_composer_get_attachment_view (EMsgComposer *composer)
5409 g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
5411 return E_ATTACHMENT_VIEW (composer->priv->attachment_paned);
5414 void
5415 e_save_spell_languages (const GList *spell_dicts)
5417 GSettings *settings;
5418 GPtrArray *lang_array;
5420 /* Build a list of spell check language codes. */
5421 lang_array = g_ptr_array_new ();
5422 while (spell_dicts != NULL) {
5423 ESpellDictionary *dict = spell_dicts->data;
5424 const gchar *language_code;
5426 language_code = e_spell_dictionary_get_code (dict);
5427 g_ptr_array_add (lang_array, (gpointer) language_code);
5429 spell_dicts = g_list_next (spell_dicts);
5432 g_ptr_array_add (lang_array, NULL);
5434 /* Save the language codes to GSettings. */
5435 settings = e_util_ref_settings ("org.gnome.evolution.mail");
5436 g_settings_set_strv (
5437 settings, "composer-spell-languages",
5438 (const gchar * const *) lang_array->pdata);
5439 g_object_unref (settings);
5441 g_ptr_array_free (lang_array, TRUE);
5444 void
5445 e_msg_composer_is_from_new_message (EMsgComposer *composer,
5446 gboolean is_from_new_message)
5448 g_return_if_fail (composer != NULL);
5450 composer->priv->is_from_new_message = is_from_new_message;
5453 void
5454 e_msg_composer_save_focused_widget (EMsgComposer *composer)
5456 GtkWidget *widget;
5458 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
5460 widget = gtk_window_get_focus (GTK_WINDOW (composer));
5461 composer->priv->focused_entry = widget;
5463 if (E_IS_HTML_EDITOR_VIEW (widget)) {
5464 EHTMLEditorSelection *selection;
5466 selection = e_html_editor_view_get_selection (
5467 E_HTML_EDITOR_VIEW (widget));
5469 e_html_editor_selection_save (selection);
5472 if (GTK_IS_EDITABLE (widget)) {
5473 gtk_editable_get_selection_bounds (
5474 GTK_EDITABLE (widget),
5475 &composer->priv->focused_entry_selection_start,
5476 &composer->priv->focused_entry_selection_end);
5480 void
5481 e_msg_composer_restore_focus_on_composer (EMsgComposer *composer)
5483 GtkWidget *widget = composer->priv->focused_entry;
5485 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
5487 if (!widget)
5488 return;
5490 gtk_window_set_focus (GTK_WINDOW (composer), widget);
5492 if (GTK_IS_EDITABLE (widget)) {
5493 gtk_editable_select_region (
5494 GTK_EDITABLE (widget),
5495 composer->priv->focused_entry_selection_start,
5496 composer->priv->focused_entry_selection_end);
5499 if (E_IS_HTML_EDITOR_VIEW (widget)) {
5500 EHTMLEditorSelection *selection;
5502 e_html_editor_view_force_spell_check_in_viewport (E_HTML_EDITOR_VIEW (widget));
5504 selection = e_html_editor_view_get_selection (
5505 E_HTML_EDITOR_VIEW (widget));
5507 e_html_editor_selection_restore (selection);
5510 composer->priv->focused_entry = NULL;