/* * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with the program; if not, see * * * Authors: * Ettore Perazzoli (ettore@ximian.com) * Jeffrey Stedfast (fejj@ximian.com) * Miguel de Icaza (miguel@ximian.com) * Radek Doulik (rodo@ximian.com) * * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) * */ /* TODO - Somehow users should be able to see if any file (s) are attached even when the attachment bar is not shown. Should use EventSources to keep track of global changes made to configuration values. Right now it ignores the problem olympically. Miguel. */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "e-util/e-dialog-utils.h" #include "misc/e-charset-picker.h" #include "misc/e-expander.h" #include "e-util/e-error.h" #include "e-util/e-mktemp.h" #include "e-util/e-plugin-ui.h" #include "e-util/e-util-private.h" #include "e-signature-combo-box.h" #include "shell/e-shell.h" #include #include #include #include #include #include #include #include #include #include #include #if defined (HAVE_NSS) #include #endif #include "mail/em-composer-utils.h" #include "mail/em-popup.h" #include "mail/em-utils.h" #include "mail/mail-config.h" #include "mail/mail-crypto.h" #include "mail/mail-ops.h" #include "mail/mail-tools.h" #include "e-msg-composer.h" #include "e-attachment.h" #include "e-attachment-bar.h" #include "e-composer-autosave.h" #include "e-composer-private.h" #include "e-composer-header-table.h" #ifdef HAVE_XFREE #include #endif #define d(x) #define E_MSG_COMPOSER_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), E_TYPE_MSG_COMPOSER, EMsgComposerPrivate)) #define E_MSG_COMPOSER_VISIBLE_MASK_SENDER \ (E_MSG_COMPOSER_VISIBLE_FROM | \ E_MSG_COMPOSER_VISIBLE_REPLYTO) #define E_MSG_COMPOSER_VISIBLE_MASK_BASIC \ (E_MSG_COMPOSER_VISIBLE_MASK_SENDER | \ E_MSG_COMPOSER_VISIBLE_SUBJECT) #define E_MSG_COMPOSER_VISIBLE_MASK_RECIPIENTS \ (E_MSG_COMPOSER_VISIBLE_TO | \ E_MSG_COMPOSER_VISIBLE_CC | \ E_MSG_COMPOSER_VISIBLE_BCC) #define E_MSG_COMPOSER_VISIBLE_MASK_MAIL \ (E_MSG_COMPOSER_VISIBLE_MASK_BASIC | \ E_MSG_COMPOSER_VISIBLE_MASK_RECIPIENTS) #define E_MSG_COMPOSER_VISIBLE_MASK_POST \ (E_MSG_COMPOSER_VISIBLE_MASK_BASIC | \ E_MSG_COMPOSER_VISIBLE_POSTTO) typedef enum { E_MSG_COMPOSER_VISIBLE_FROM = (1 << 0), E_MSG_COMPOSER_VISIBLE_REPLYTO = (1 << 1), E_MSG_COMPOSER_VISIBLE_TO = (1 << 2), E_MSG_COMPOSER_VISIBLE_CC = (1 << 3), E_MSG_COMPOSER_VISIBLE_BCC = (1 << 4), E_MSG_COMPOSER_VISIBLE_POSTTO = (1 << 5), E_MSG_COMPOSER_VISIBLE_SUBJECT = (1 << 7) } EMsgComposerHeaderVisibleFlags; enum { SEND, SAVE_DRAFT, LAST_SIGNAL }; enum { DND_TYPE_MESSAGE_RFC822, DND_TYPE_X_UID_LIST, DND_TYPE_TEXT_URI_LIST, DND_TYPE_NETSCAPE_URL, DND_TYPE_TEXT_VCARD, DND_TYPE_TEXT_CALENDAR }; static GtkTargetEntry drop_types[] = { { "message/rfc822", 0, DND_TYPE_MESSAGE_RFC822 }, { "x-uid-list", 0, DND_TYPE_X_UID_LIST }, { "text/uri-list", 0, DND_TYPE_TEXT_URI_LIST }, { "_NETSCAPE_URL", 0, DND_TYPE_NETSCAPE_URL }, { "text/x-vcard", 0, DND_TYPE_TEXT_VCARD }, { "text/calendar", 0, DND_TYPE_TEXT_CALENDAR } }; static struct { gchar *target; GdkAtom atom; guint32 actions; } drag_info[] = { { "message/rfc822", NULL, GDK_ACTION_COPY }, { "x-uid-list", NULL, GDK_ACTION_ASK | GDK_ACTION_MOVE | GDK_ACTION_COPY }, { "text/uri-list", NULL, GDK_ACTION_COPY }, { "_NETSCAPE_URL", NULL, GDK_ACTION_COPY }, { "text/x-vcard", NULL, GDK_ACTION_COPY }, { "text/calendar", NULL, GDK_ACTION_COPY } }; static gpointer parent_class; static guint signals[LAST_SIGNAL]; /* All the composer windows open, for bookkeeping purposes. */ static GSList *all_composers = NULL; /* local prototypes */ static GList *add_recipients (GList *list, const gchar *recips); static void handle_mailto (EMsgComposer *composer, const gchar *mailto); static void handle_uri (EMsgComposer *composer, const gchar *uri, gboolean html_dnd); /* used by e_msg_composer_add_message_attachments () */ static void add_attachments_from_multipart (EMsgComposer *composer, CamelMultipart *multipart, gboolean just_inlines, gint depth); /* used by e_msg_composer_new_with_message () */ static void handle_multipart (EMsgComposer *composer, CamelMultipart *multipart, gint depth); static void handle_multipart_alternative (EMsgComposer *composer, CamelMultipart *multipart, gint depth); static void handle_multipart_encrypted (EMsgComposer *composer, CamelMimePart *multipart, gint depth); static void handle_multipart_signed (EMsgComposer *composer, CamelMultipart *multipart, gint depth); static EDestination** destination_list_to_vector_sized (GList *list, gint n) { EDestination **destv; gint i = 0; if (n == -1) n = g_list_length (list); if (n == 0) return NULL; destv = g_new (EDestination *, n + 1); while (list != NULL && i < n) { destv[i] = E_DESTINATION (list->data); list->data = NULL; i++; list = g_list_next (list); } destv[i] = NULL; return destv; } static EDestination** destination_list_to_vector (GList *list) { return destination_list_to_vector_sized (list, -1); } #define LINE_LEN 72 static CamelTransferEncoding best_encoding (GByteArray *buf, const gchar *charset) { gchar *in, *out, outbuf[256], *ch; gsize inlen, outlen; gint status, count = 0; iconv_t cd; if (!charset) return -1; cd = camel_iconv_open (charset, "utf-8"); if (cd == (iconv_t) -1) return -1; in = (gchar *) buf->data; inlen = buf->len; do { out = outbuf; outlen = sizeof (outbuf); status = camel_iconv (cd, (const gchar **) &in, &inlen, &out, &outlen); for (ch = out - 1; ch >= outbuf; ch--) { if ((guchar) *ch > 127) count++; } } while (status == (gsize) -1 && errno == E2BIG); camel_iconv_close (cd); if (status == (gsize) -1 || status > 0) return -1; if (count == 0) return CAMEL_TRANSFER_ENCODING_7BIT; else if (count <= buf->len * 0.17) return CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE; else return CAMEL_TRANSFER_ENCODING_BASE64; } static gchar * best_charset (GByteArray *buf, const gchar *default_charset, CamelTransferEncoding *encoding) { gchar *charset; /* First try US-ASCII */ *encoding = best_encoding (buf, "US-ASCII"); if (*encoding == CAMEL_TRANSFER_ENCODING_7BIT) return NULL; /* Next try the user-specified charset for this message */ *encoding = best_encoding (buf, default_charset); if (*encoding != -1) return g_strdup (default_charset); /* Now try the user's default charset from the mail config */ charset = e_composer_get_default_charset (); *encoding = best_encoding (buf, charset); if (*encoding != -1) return charset; /* Try to find something that will work */ if (!(charset = (char *) camel_charset_best ((const gchar *)buf->data, buf->len))) { *encoding = CAMEL_TRANSFER_ENCODING_7BIT; return NULL; } *encoding = best_encoding (buf, charset); return g_strdup (charset); } static void clear_current_images (EMsgComposer *composer) { EMsgComposerPrivate *p = composer->priv; g_list_free (p->current_images); p->current_images = NULL; } void e_msg_composer_clear_inlined_table (EMsgComposer *composer) { EMsgComposerPrivate *p = composer->priv; g_hash_table_remove_all (p->inline_images); g_hash_table_remove_all (p->inline_images_by_url); } static void add_inlined_images (EMsgComposer *composer, CamelMultipart *multipart) { EMsgComposerPrivate *p = composer->priv; GList *d = p->current_images; GHashTable *added; added = g_hash_table_new (g_direct_hash, g_direct_equal); while (d) { CamelMimePart *part = d->data; if (!g_hash_table_lookup (added, part)) { camel_multipart_add_part (multipart, part); g_hash_table_insert (added, part, part); } d = d->next; } g_hash_table_destroy (added); } /* These functions builds a CamelMimeMessage for the message that the user has * composed in `composer'. */ static void set_recipients_from_destv (CamelMimeMessage *msg, EDestination **to_destv, EDestination **cc_destv, EDestination **bcc_destv, gboolean redirect) { CamelInternetAddress *to_addr; CamelInternetAddress *cc_addr; CamelInternetAddress *bcc_addr; CamelInternetAddress *target; const gchar *text_addr, *header; gboolean seen_hidden_list = FALSE; gint i; to_addr = camel_internet_address_new (); cc_addr = camel_internet_address_new (); bcc_addr = camel_internet_address_new (); for (i = 0; to_destv != NULL && to_destv[i] != NULL; ++i) { text_addr = e_destination_get_address (to_destv[i]); if (text_addr && *text_addr) { target = to_addr; if (e_destination_is_evolution_list (to_destv[i]) && !e_destination_list_show_addresses (to_destv[i])) { target = bcc_addr; seen_hidden_list = TRUE; } camel_address_decode (CAMEL_ADDRESS (target), text_addr); } } for (i = 0; cc_destv != NULL && cc_destv[i] != NULL; ++i) { text_addr = e_destination_get_address (cc_destv[i]); if (text_addr && *text_addr) { target = cc_addr; if (e_destination_is_evolution_list (cc_destv[i]) && !e_destination_list_show_addresses (cc_destv[i])) { target = bcc_addr; seen_hidden_list = TRUE; } camel_address_decode (CAMEL_ADDRESS (target), text_addr); } } for (i = 0; bcc_destv != NULL && bcc_destv[i] != NULL; ++i) { text_addr = e_destination_get_address (bcc_destv[i]); if (text_addr && *text_addr) { camel_address_decode (CAMEL_ADDRESS (bcc_addr), text_addr); } } header = redirect ? CAMEL_RECIPIENT_TYPE_RESENT_TO : CAMEL_RECIPIENT_TYPE_TO; if (camel_address_length (CAMEL_ADDRESS (to_addr)) > 0) { camel_mime_message_set_recipients (msg, header, to_addr); } else if (seen_hidden_list) { camel_medium_set_header (CAMEL_MEDIUM (msg), header, "Undisclosed-Recipient:;"); } header = redirect ? CAMEL_RECIPIENT_TYPE_RESENT_CC : CAMEL_RECIPIENT_TYPE_CC; if (camel_address_length (CAMEL_ADDRESS (cc_addr)) > 0) { camel_mime_message_set_recipients (msg, header, cc_addr); } header = redirect ? CAMEL_RECIPIENT_TYPE_RESENT_BCC : CAMEL_RECIPIENT_TYPE_BCC; if (camel_address_length (CAMEL_ADDRESS (bcc_addr)) > 0) { camel_mime_message_set_recipients (msg, header, bcc_addr); } camel_object_unref (to_addr); camel_object_unref (cc_addr); camel_object_unref (bcc_addr); } static void build_message_headers (EMsgComposer *composer, CamelMimeMessage *msg, gboolean redirect) { EComposerHeaderTable *table; EAccount *account; const gchar *subject; const gchar *reply_to; g_return_if_fail (E_IS_MSG_COMPOSER (composer)); g_return_if_fail (CAMEL_IS_MIME_MESSAGE (msg)); table = e_msg_composer_get_header_table (composer); /* Subject: */ subject = e_composer_header_table_get_subject (table); camel_mime_message_set_subject (msg, subject); /* From: / Resent-From: */ account = e_composer_header_table_get_account (table); if (account != NULL) { CamelInternetAddress *addr; const gchar *name = account->id->name; const gchar *address = account->id->address; addr = camel_internet_address_new (); camel_internet_address_add (addr, name, address); if (redirect) { gchar *value; value = camel_address_encode (CAMEL_ADDRESS (addr)); camel_medium_set_header ( CAMEL_MEDIUM (msg), "Resent-From", value); g_free (value); } else camel_mime_message_set_from (msg, addr); camel_object_unref (addr); } /* Reply-To: */ reply_to = e_composer_header_table_get_reply_to (table); if (reply_to != NULL && *reply_to != '\0') { CamelInternetAddress *addr; addr = camel_internet_address_new (); if (camel_address_unformat (CAMEL_ADDRESS (addr), reply_to) > 0) camel_mime_message_set_reply_to (msg, addr); camel_object_unref (addr); } /* To:, Cc:, Bcc: */ if (e_composer_header_table_get_header_visible (table, E_COMPOSER_HEADER_TO) || e_composer_header_table_get_header_visible (table, E_COMPOSER_HEADER_CC) || e_composer_header_table_get_header_visible (table, E_COMPOSER_HEADER_BCC)) { EDestination **to, **cc, **bcc; to = e_composer_header_table_get_destinations_to (table); cc = e_composer_header_table_get_destinations_cc (table); bcc = e_composer_header_table_get_destinations_bcc (table); set_recipients_from_destv (msg, to, cc, bcc, redirect); e_destination_freev (to); e_destination_freev (cc); e_destination_freev (bcc); } /* X-Evolution-PostTo: */ if (e_composer_header_table_get_header_visible (table, E_COMPOSER_HEADER_POST_TO)) { CamelMedium *medium = CAMEL_MEDIUM (msg); const gchar *name = "X-Evolution-PostTo"; GList *list, *iter; camel_medium_remove_header (medium, name); list = e_composer_header_table_get_post_to (table); for (iter = list; iter != NULL; iter = iter->next) { gchar *folder = iter->data; camel_medium_add_header (medium, name, folder); g_free (folder); } g_list_free (list); } } static CamelMimeMessage * build_message (EMsgComposer *composer, gboolean html_content, gboolean save_html_object_data) { GtkhtmlEditor *editor; EMsgComposerPrivate *p = composer->priv; EAttachmentBar *attachment_bar; EComposerHeaderTable *table; GtkToggleAction *action; CamelDataWrapper *plain, *html, *current; CamelTransferEncoding plain_encoding; const gchar *iconv_charset = NULL; GPtrArray *recipients = NULL; CamelMultipart *body = NULL; CamelContentType *type; CamelMimeMessage *new; CamelStream *stream; CamelMimePart *part; CamelException ex; GByteArray *data; EAccount *account; gchar *charset; gboolean pgp_sign; gboolean pgp_encrypt; gboolean smime_sign; gboolean smime_encrypt; gint i; g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL); editor = GTKHTML_EDITOR (composer); table = e_msg_composer_get_header_table (composer); account = e_composer_header_table_get_account (table); attachment_bar = E_ATTACHMENT_BAR (p->attachment_bar); /* evil kludgy hack for Redirect */ if (p->redirect) { build_message_headers (composer, p->redirect, TRUE); camel_object_ref (p->redirect); return p->redirect; } new = camel_mime_message_new (); build_message_headers (composer, new, FALSE); for (i = 0; i < p->extra_hdr_names->len; i++) { camel_medium_add_header (CAMEL_MEDIUM (new), p->extra_hdr_names->pdata[i], p->extra_hdr_values->pdata[i]); } /* Message Disposition Notification */ action = GTK_TOGGLE_ACTION (ACTION (REQUEST_READ_RECEIPT)); if (gtk_toggle_action_get_active (action)) { gchar *mdn_address = account->id->reply_to; if (!mdn_address || !*mdn_address) mdn_address = account->id->address; camel_medium_add_header ( CAMEL_MEDIUM (new), "Disposition-Notification-To", mdn_address); } /* Message Priority */ action = GTK_TOGGLE_ACTION (ACTION (PRIORITIZE_MESSAGE)); if (gtk_toggle_action_get_active (action)) camel_medium_add_header ( CAMEL_MEDIUM (new), "X-Priority", "1"); if (p->mime_body) { plain_encoding = CAMEL_TRANSFER_ENCODING_7BIT; for (i = 0; p->mime_body[i]; i++) { if ((guchar) p->mime_body[i] > 127) { plain_encoding = CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE; break; } } data = g_byte_array_new (); g_byte_array_append (data, (const guint8 *)p->mime_body, strlen (p->mime_body)); type = camel_content_type_decode (p->mime_type); } else { gchar *text; gsize length; data = g_byte_array_new (); text = gtkhtml_editor_get_text_plain (editor, &length); g_byte_array_append (data, (guint8 *) text, (guint) length); g_free (text); /* FIXME: we may want to do better than this... */ type = camel_content_type_new ("text", "plain"); if ((charset = best_charset (data, p->charset, &plain_encoding))) { camel_content_type_set_param (type, "charset", charset); iconv_charset = camel_iconv_charset_name (charset); g_free (charset); } } stream = camel_stream_mem_new_with_byte_array (data); /* convert the stream to the appropriate charset */ if (iconv_charset && g_ascii_strcasecmp (iconv_charset, "UTF-8") != 0) { CamelStreamFilter *filter_stream; CamelMimeFilterCharset *filter; filter_stream = camel_stream_filter_new_with_stream (stream); camel_object_unref (stream); stream = (CamelStream *) filter_stream; filter = camel_mime_filter_charset_new_convert ("UTF-8", iconv_charset); camel_stream_filter_add (filter_stream, (CamelMimeFilter *) filter); camel_object_unref (filter); } /* construct the content object */ plain = camel_data_wrapper_new (); camel_data_wrapper_construct_from_stream (plain, stream); camel_object_unref (stream); camel_data_wrapper_set_mime_type_field (plain, type); camel_content_type_unref (type); if (html_content) { gchar *text; gsize length; clear_current_images (composer); if (save_html_object_data) gtkhtml_editor_run_command (editor, "save-data-on"); data = g_byte_array_new (); text = gtkhtml_editor_get_text_html (editor, &length); g_byte_array_append (data, (guint8 *) text, (guint) length); g_free (text); if (save_html_object_data) gtkhtml_editor_run_command (editor, "save-data-off"); html = camel_data_wrapper_new (); stream = camel_stream_mem_new_with_byte_array (data); camel_data_wrapper_construct_from_stream (html, stream); camel_object_unref (stream); camel_data_wrapper_set_mime_type (html, "text/html; charset=utf-8"); /* Build the multipart/alternative */ body = camel_multipart_new (); camel_data_wrapper_set_mime_type (CAMEL_DATA_WRAPPER (body), "multipart/alternative"); camel_multipart_set_boundary (body, NULL); part = camel_mime_part_new (); camel_medium_set_content_object (CAMEL_MEDIUM (part), plain); camel_object_unref (plain); camel_mime_part_set_encoding (part, plain_encoding); camel_multipart_add_part (body, part); camel_object_unref (part); part = camel_mime_part_new (); camel_medium_set_content_object (CAMEL_MEDIUM (part), html); camel_object_unref (html); camel_multipart_add_part (body, part); camel_object_unref (part); /* If there are inlined images, construct a * multipart/related containing the * multipart/alternative and the images. */ if (p->current_images) { CamelMultipart *html_with_images; html_with_images = camel_multipart_new (); camel_data_wrapper_set_mime_type ( CAMEL_DATA_WRAPPER (html_with_images), "multipart/related; type=\"multipart/alternative\""); camel_multipart_set_boundary (html_with_images, NULL); part = camel_mime_part_new (); camel_medium_set_content_object (CAMEL_MEDIUM (part), CAMEL_DATA_WRAPPER (body)); camel_object_unref (body); camel_multipart_add_part (html_with_images, part); camel_object_unref (part); add_inlined_images (composer, html_with_images); clear_current_images (composer); current = CAMEL_DATA_WRAPPER (html_with_images); } else current = CAMEL_DATA_WRAPPER (body); } else current = plain; if (e_attachment_bar_get_num_attachments (attachment_bar)) { CamelMultipart *multipart = camel_multipart_new (); if (p->is_alternative) { camel_data_wrapper_set_mime_type (CAMEL_DATA_WRAPPER (multipart), "multipart/alternative"); } /* Generate a random boundary. */ camel_multipart_set_boundary (multipart, NULL); part = camel_mime_part_new (); camel_medium_set_content_object (CAMEL_MEDIUM (part), current); if (current == plain) camel_mime_part_set_encoding (part, plain_encoding); camel_object_unref (current); camel_multipart_add_part (multipart, part); camel_object_unref (part); e_attachment_bar_to_multipart (attachment_bar, multipart, p->charset); if (p->is_alternative) { for (i = camel_multipart_get_number (multipart); i > 1; i--) { part = camel_multipart_get_part (multipart, i - 1); camel_medium_remove_header (CAMEL_MEDIUM (part), "Content-Disposition"); } } current = CAMEL_DATA_WRAPPER (multipart); } camel_exception_init (&ex); action = GTK_TOGGLE_ACTION (ACTION (PGP_SIGN)); pgp_sign = gtk_toggle_action_get_active (action); action = GTK_TOGGLE_ACTION (ACTION (PGP_ENCRYPT)); pgp_encrypt = gtk_toggle_action_get_active (action); #if defined (HAVE_NSS) action = GTK_TOGGLE_ACTION (ACTION (SMIME_SIGN)); smime_sign = gtk_toggle_action_get_active (action); action = GTK_TOGGLE_ACTION (ACTION (SMIME_ENCRYPT)); smime_encrypt = gtk_toggle_action_get_active (action); #else smime_sign = FALSE; smime_encrypt = FALSE; #endif /* Setup working recipient list if we're encrypting */ if (pgp_encrypt || smime_encrypt) { gint j; const gchar *types[] = { CAMEL_RECIPIENT_TYPE_TO, CAMEL_RECIPIENT_TYPE_CC, CAMEL_RECIPIENT_TYPE_BCC }; recipients = g_ptr_array_new (); for (i = 0; i < G_N_ELEMENTS (types); i++) { const CamelInternetAddress *addr; const gchar *address; addr = camel_mime_message_get_recipients (new, types[i]); for (j=0;camel_internet_address_get (addr, j, NULL, &address); j++) g_ptr_array_add (recipients, g_strdup (address)); } } if (pgp_sign || pgp_encrypt) { const gchar *pgp_userid; CamelInternetAddress *from = NULL; CamelCipherContext *cipher; EAccount *account; part = camel_mime_part_new (); camel_medium_set_content_object (CAMEL_MEDIUM (part), current); if (current == plain) camel_mime_part_set_encoding (part, plain_encoding); camel_object_unref (current); account = e_composer_header_table_get_account (table); if (account && account->pgp_key && *account->pgp_key) { pgp_userid = account->pgp_key; } else { from = e_msg_composer_get_from (composer); camel_internet_address_get (from, 0, NULL, &pgp_userid); } if (pgp_sign) { CamelMimePart *npart = camel_mime_part_new (); cipher = mail_crypto_get_pgp_cipher_context (account); camel_cipher_sign (cipher, pgp_userid, CAMEL_CIPHER_HASH_SHA1, part, npart, &ex); camel_object_unref (cipher); if (camel_exception_is_set (&ex)) { camel_object_unref (npart); goto exception; } camel_object_unref (part); part = npart; } if (pgp_encrypt) { CamelMimePart *npart = camel_mime_part_new (); /* check to see if we should encrypt to self, NB gets removed immediately after use */ if (account && account->pgp_encrypt_to_self && pgp_userid) g_ptr_array_add (recipients, g_strdup (pgp_userid)); cipher = mail_crypto_get_pgp_cipher_context (account); camel_cipher_encrypt (cipher, pgp_userid, recipients, part, npart, &ex); camel_object_unref (cipher); if (account && account->pgp_encrypt_to_self && pgp_userid) g_ptr_array_set_size (recipients, recipients->len - 1); if (camel_exception_is_set (&ex)) { camel_object_unref (npart); goto exception; } camel_object_unref (part); part = npart; } if (from) camel_object_unref (from); current = camel_medium_get_content_object (CAMEL_MEDIUM (part)); camel_object_ref (current); camel_object_unref (part); } #if defined (HAVE_NSS) if (smime_sign || smime_encrypt) { CamelInternetAddress *from = NULL; CamelCipherContext *cipher; part = camel_mime_part_new (); camel_medium_set_content_object ((CamelMedium *)part, current); if (current == plain) camel_mime_part_set_encoding (part, plain_encoding); camel_object_unref (current); if (smime_sign && (account == NULL || account->smime_sign_key == NULL || account->smime_sign_key[0] == 0)) { camel_exception_set (&ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot sign outgoing message: No signing certificate set for this account")); goto exception; } if (smime_encrypt && (account == NULL || account->smime_sign_key == NULL || account->smime_sign_key[0] == 0)) { camel_exception_set (&ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot encrypt outgoing message: No encryption certificate set for this account")); goto exception; } if (smime_sign) { CamelMimePart *npart = camel_mime_part_new (); CamelSession *session; session = e_msg_composer_get_session (composer); cipher = camel_smime_context_new (session); /* if we're also encrypting, envelope-sign rather than clear-sign */ if (smime_encrypt) { camel_smime_context_set_sign_mode ((CamelSMIMEContext *)cipher, CAMEL_SMIME_SIGN_ENVELOPED); camel_smime_context_set_encrypt_key ((CamelSMIMEContext *)cipher, TRUE, account->smime_encrypt_key); } else if (account && account->smime_encrypt_key && *account->smime_encrypt_key) { camel_smime_context_set_encrypt_key ((CamelSMIMEContext *)cipher, TRUE, account->smime_encrypt_key); } camel_cipher_sign (cipher, account->smime_sign_key, CAMEL_CIPHER_HASH_SHA1, part, npart, &ex); camel_object_unref (cipher); if (camel_exception_is_set (&ex)) { camel_object_unref (npart); goto exception; } camel_object_unref (part); part = npart; } if (smime_encrypt) { CamelSession *session; /* check to see if we should encrypt to self, NB removed after use */ if (account->smime_encrypt_to_self) g_ptr_array_add (recipients, g_strdup (account->smime_encrypt_key)); session = e_msg_composer_get_session (composer); cipher = camel_smime_context_new (session); camel_smime_context_set_encrypt_key ((CamelSMIMEContext *)cipher, TRUE, account->smime_encrypt_key); camel_cipher_encrypt (cipher, NULL, recipients, part, (CamelMimePart *)new, &ex); camel_object_unref (cipher); if (camel_exception_is_set (&ex)) goto exception; if (account->smime_encrypt_to_self) g_ptr_array_set_size (recipients, recipients->len - 1); } if (from) camel_object_unref (from); /* we replaced the message directly, we don't want to do reparenting foo */ if (smime_encrypt) { camel_object_unref (part); goto skip_content; } else { current = camel_medium_get_content_object ((CamelMedium *)part); camel_object_ref (current); camel_object_unref (part); } } #endif /* HAVE_NSS */ camel_medium_set_content_object (CAMEL_MEDIUM (new), current); if (current == plain) camel_mime_part_set_encoding (CAMEL_MIME_PART (new), plain_encoding); camel_object_unref (current); #if defined (HAVE_NSS) skip_content: #endif if (recipients) { for (i=0; ilen; i++) g_free (recipients->pdata[i]); g_ptr_array_free (recipients, TRUE); } /* Attach whether this message was written in HTML */ camel_medium_set_header ( CAMEL_MEDIUM (new), "X-Evolution-Format", html_content ? "text/html" : "text/plain"); return new; exception: if (part != CAMEL_MIME_PART (new)) camel_object_unref (part); camel_object_unref (new); if (ex.id != CAMEL_EXCEPTION_USER_CANCEL) { e_error_run ((GtkWindow *)composer, "mail-composer:no-build-message", camel_exception_get_description (&ex), NULL); } camel_exception_clear (&ex); if (recipients) { for (i=0; ilen; i++) g_free (recipients->pdata[i]); g_ptr_array_free (recipients, TRUE); } return NULL; } /* Attachment Bar */ static void emcab_add (EPopup *ep, EPopupItem *item, gpointer data) { GtkWidget *widget = data; GtkWidget *composer; composer = gtk_widget_get_toplevel (widget); gtk_action_activate (ACTION (ATTACH)); } static void emcab_properties (EPopup *ep, EPopupItem *item, gpointer data) { EAttachmentBar *attachment_bar = data; e_attachment_bar_edit_selected (attachment_bar); } static void emcab_remove (EPopup *ep, EPopupItem *item, gpointer data) { EAttachmentBar *attachment_bar = data; e_attachment_bar_remove_selected (attachment_bar); } static void emcab_popup_position (GtkMenu *menu, int *x, int *y, gboolean *push_in, gpointer user_data) { GtkWidget *widget = user_data; GnomeIconList *icon_list = user_data; GList *selection; GnomeCanvasPixbuf *image; gdk_window_get_origin (widget->window, x, y); selection = gnome_icon_list_get_selection (icon_list); if (selection == NULL) return; image = gnome_icon_list_get_icon_pixbuf_item ( icon_list, GPOINTER_TO_INT(selection->data)); if (image == NULL) return; /* Put menu to the center of icon. */ *x += (int)(image->item.x1 + image->item.x2) / 2; *y += (int)(image->item.y1 + image->item.y2) / 2; } static void emcab_popups_free (EPopup *ep, GSList *list, gpointer data) { g_slist_free (list); } /* Popup menu handling. */ static EPopupItem emcab_popups[] = { { E_POPUP_ITEM, "10.attach", N_("_Remove"), emcab_remove, NULL, GTK_STOCK_REMOVE, EM_POPUP_ATTACHMENTS_MANY }, { E_POPUP_ITEM, "20.attach", N_("_Properties"), emcab_properties, NULL, GTK_STOCK_PROPERTIES, EM_POPUP_ATTACHMENTS_ONE }, { E_POPUP_BAR, "30.attach.00", NULL, NULL, NULL, NULL, EM_POPUP_ATTACHMENTS_MANY|EM_POPUP_ATTACHMENTS_ONE }, { E_POPUP_ITEM, "30.attach.01", N_("_Add attachment..."), emcab_add, NULL, GTK_STOCK_ADD, 0 }, }; /* if id != -1, then use it as an index for target of the popup */ static void emcab_popup (EAttachmentBar *bar, GdkEventButton *event, int id) { GSList *attachments = NULL, *menus = NULL; int i; EMPopup *emp; EMPopupTargetAttachments *t; GtkMenu *menu; attachments = e_attachment_bar_get_attachment (bar, id); for (i=0;itarget.widget = (GtkWidget *)bar; menu = e_popup_create_menu_once ((EPopup *)emp, (EPopupTarget *)t, 0); if (event == NULL) gtk_menu_popup (menu, NULL, NULL, emcab_popup_position, bar, 0, gtk_get_current_event_time ()); else gtk_menu_popup (menu, NULL, NULL, NULL, NULL, event->button, event->time); } /* Signatures */ static gchar * get_file_content (EMsgComposer *composer, const gchar *filename, gboolean want_html, guint flags, gboolean warn) { CamelStreamFilter *filtered_stream; CamelStreamMem *memstream; CamelMimeFilter *html, *charenc; CamelStream *stream; GByteArray *buffer; gchar *charset; gchar *content; gint fd; fd = g_open (filename, O_RDONLY, 0); if (fd == -1) { if (warn) e_error_run ((GtkWindow *)composer, "mail-composer:no-sig-file", filename, g_strerror (errno), NULL); return g_strdup (""); } stream = camel_stream_fs_new_with_fd (fd); if (want_html) { filtered_stream = camel_stream_filter_new_with_stream (stream); camel_object_unref (stream); html = camel_mime_filter_tohtml_new (flags, 0); camel_stream_filter_add (filtered_stream, html); camel_object_unref (html); stream = (CamelStream *) filtered_stream; } memstream = (CamelStreamMem *) camel_stream_mem_new (); buffer = g_byte_array_new (); camel_stream_mem_set_byte_array (memstream, buffer); camel_stream_write_to_stream (stream, (CamelStream *) memstream); camel_object_unref (stream); /* The newer signature UI saves signatures in UTF-8, but we still need to check that the signature is valid UTF-8 because it is possible that the user imported a signature file that is in his/her locale charset. If it's not in UTF-8 and not in the charset the composer is in (or their default mail charset) then fuck it, there's nothing we can do. */ if (buffer->len && !g_utf8_validate ((const gchar *)buffer->data, buffer->len, NULL)) { stream = (CamelStream *) memstream; memstream = (CamelStreamMem *) camel_stream_mem_new (); camel_stream_mem_set_byte_array (memstream, g_byte_array_new ()); filtered_stream = camel_stream_filter_new_with_stream (stream); camel_object_unref (stream); charset = composer && composer->priv->charset ? composer->priv->charset : NULL; charset = charset ? g_strdup (charset) : e_composer_get_default_charset (); if ((charenc = (CamelMimeFilter *) camel_mime_filter_charset_new_convert (charset, "UTF-8"))) { camel_stream_filter_add (filtered_stream, charenc); camel_object_unref (charenc); } g_free (charset); camel_stream_write_to_stream ((CamelStream *) filtered_stream, (CamelStream *) memstream); camel_object_unref (filtered_stream); g_byte_array_free (buffer, TRUE); buffer = memstream->buffer; } camel_object_unref (memstream); g_byte_array_append (buffer, (const guint8 *)"", 1); content = (char*)buffer->data; g_byte_array_free (buffer, FALSE); return content; } gchar * e_msg_composer_get_sig_file_content (const gchar *sigfile, gboolean in_html) { if (!sigfile || !*sigfile) { return NULL; } return get_file_content (NULL, sigfile, !in_html, CAMEL_MIME_FILTER_TOHTML_PRESERVE_8BIT | CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS | CAMEL_MIME_FILTER_TOHTML_CONVERT_ADDRESSES | CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES, FALSE); } static gchar * encode_signature_name (const gchar *name) { const gchar *s; gchar *ename, *e; gint len = 0; s = name; while (*s) { len ++; if (*s == '"' || *s == '.' || *s == '=') len ++; s ++; } ename = g_new (gchar, len + 1); s = name; e = ename; while (*s) { if (*s == '"') { *e = '.'; e ++; *e = '1'; e ++; } else if (*s == '=') { *e = '.'; e ++; *e = '2'; e ++; } else { *e = *s; e ++; } if (*s == '.') { *e = '.'; e ++; } s ++; } *e = 0; return ename; } static gchar * decode_signature_name (const gchar *name) { const gchar *s; gchar *dname, *d; gint len = 0; s = name; while (*s) { len ++; if (*s == '.') { s ++; if (!*s || !(*s == '.' || *s == '1' || *s == '2')) return NULL; } s ++; } dname = g_new (char, len + 1); s = name; d = dname; while (*s) { if (*s == '.') { s ++; if (!*s || !(*s == '.' || *s == '1' || *s == '2')) { g_free (dname); return NULL; } if (*s == '1') *d = '"'; else if (*s == '2') *d = '='; else *d = '.'; } else *d = *s; d ++; s ++; } *d = 0; return dname; } #define CONVERT_SPACES CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES static gchar * get_signature_html (EMsgComposer *composer) { EComposerHeaderTable *table; gchar *text = NULL, *html = NULL; ESignature *signature; gboolean format_html; table = e_msg_composer_get_header_table (composer); signature = e_composer_header_table_get_signature (table); if (!signature) return NULL; if (!signature->autogen) { if (!signature->filename) return NULL; format_html = signature->html; if (signature->script) { text = mail_config_signature_run_script (signature->filename); } else { text = e_msg_composer_get_sig_file_content (signature->filename, format_html); } } else { EAccountIdentity *id; gchar *organization; gchar *address; gchar *name; id = e_composer_header_table_get_account (table)->id; address = id->address ? camel_text_to_html (id->address, CONVERT_SPACES, 0) : NULL; name = id->name ? camel_text_to_html (id->name, CONVERT_SPACES, 0) : NULL; organization = id->organization ? camel_text_to_html (id->organization, CONVERT_SPACES, 0) : NULL; text = g_strdup_printf ("--
%s%s%s%s%s%s%s%s", name ? name : "", (address && *address) ? " <" : "", address ? address : "", (address && *address) ? ">" : "", (organization && *organization) ? "
" : "", organization ? organization : ""); g_free (address); g_free (name); g_free (organization); format_html = TRUE; } /* printf ("text: %s\n", text); */ if (text) { gchar *encoded_uid = NULL; if (signature) encoded_uid = encode_signature_name (signature->uid); /* The signature dash convention ("-- \n") is specified in the * "Son of RFC 1036": http://www.chemie.fu-berlin.de/outerspace/netnews/son-of-1036.html, * section 4.3.2. */ html = g_strdup_printf ("" "" "
" "%s%s%s%s" "
", encoded_uid ? encoded_uid : "", format_html ? "" : "
\n",
					format_html || (!strncmp ("-- \n", text, 4) || strstr (text, "\n-- \n")) ? "" : "-- \n",
					text,
					format_html ? "" : "
\n"); g_free (text); g_free (encoded_uid); text = html; } return text; } static void set_editor_text (EMsgComposer *composer, const gchar *text, gboolean set_signature) { EShell *shell; EShellSettings *shell_settings; gboolean reply_signature_on_top; gchar *body = NULL, *html = NULL; g_return_if_fail (E_IS_MSG_COMPOSER (composer)); g_return_if_fail (text != NULL); shell = e_shell_get_default (); shell_settings = e_shell_get_shell_settings (shell); /* Keeping Signatures in the beginning of composer ------------------------------------------------ Purists are gonna blast me for this. But there are so many people (read Outlook users) who want this. And Evo is an exchange-client, Outlook-replacement etc. So Here it goes :( -- Sankar */ g_object_get ( shell_settings, "composer-top-signature", &reply_signature_on_top, NULL); if (set_signature && reply_signature_on_top) { gchar *tmp = NULL; tmp = get_signature_html (composer); if (tmp) { /* Minimizing the damage. Make it just a part of the body instead of a signature */ html = strstr (tmp, "-- \n"); if (html) { /* That two consecutive - symbols followed by a space */ *(html+1) = ' '; body = g_strdup_printf ("
%s
%s", tmp, text); } else { /* HTML Signature. Make it as part of body */ body = g_strdup_printf ("
%s
%s", tmp, text); } g_free (tmp); } else { /* No signature set */ body = g_strdup_printf ("" "" "
%s", text); } } else { body = g_strdup (text); } gtkhtml_editor_set_text_html (GTKHTML_EDITOR (composer), body, -1); if (set_signature && !reply_signature_on_top) e_msg_composer_show_sig_file (composer); } /* Commands. */ static EMsgComposer * autosave_load_draft (const gchar *filename) { CamelStream *stream; CamelMimeMessage *msg; EMsgComposer *composer; g_return_val_if_fail (filename != NULL, NULL); g_warning ("autosave load filename = \"%s\"", filename); if (!(stream = camel_stream_fs_new_with_name (filename, O_RDONLY, 0))) return NULL; msg = camel_mime_message_new (); camel_data_wrapper_construct_from_stream (CAMEL_DATA_WRAPPER (msg), stream); camel_object_unref (stream); composer = e_msg_composer_new_with_message (msg); if (composer) { if (e_composer_autosave_snapshot (composer)) g_unlink (filename); g_signal_connect ( composer, "send", G_CALLBACK (em_utils_composer_send_cb), NULL); g_signal_connect ( composer, "save-draft", G_CALLBACK (em_utils_composer_save_draft_cb), NULL); gtk_widget_show (GTK_WIDGET (composer)); } return composer; } /* Miscellaneous callbacks. */ static gint attachment_bar_button_press_event_cb (EAttachmentBar *attachment_bar, GdkEventButton *event) { GnomeIconList *icon_list; gint icon_number; if (event->button != 3) return FALSE; icon_list = GNOME_ICON_LIST (attachment_bar); icon_number = gnome_icon_list_get_icon_at ( icon_list, event->x, event->y); if (icon_number >= 0) { gnome_icon_list_unselect_all (icon_list); gnome_icon_list_select_icon (icon_list, icon_number); } emcab_popup (attachment_bar, event, icon_number); return TRUE; } static void attachment_bar_changed_cb (EAttachmentBar *attachment_bar, EMsgComposer *composer) { GtkhtmlEditor *editor; GtkWidget *widget; guint attachment_num; editor = GTKHTML_EDITOR (composer); attachment_num = e_attachment_bar_get_num_attachments (attachment_bar); if (attachment_num > 0) { gchar *markup; markup = g_strdup_printf ( "%d %s", attachment_num, ngettext ( "Attachment", "Attachments", attachment_num)); widget = composer->priv->attachment_expander_num; gtk_label_set_markup (GTK_LABEL (widget), markup); g_free (markup); gtk_widget_show (composer->priv->attachment_expander_icon); widget = composer->priv->attachment_expander; gtk_expander_set_expanded (GTK_EXPANDER (widget), TRUE); } else { widget = composer->priv->attachment_expander_num; gtk_label_set_text (GTK_LABEL (widget), ""); gtk_widget_hide (composer->priv->attachment_expander_icon); widget = composer->priv->attachment_expander; gtk_expander_set_expanded (GTK_EXPANDER (widget), FALSE); } /* Mark the editor as changed so it prompts about unsaved changes on close. */ gtkhtml_editor_set_changed (editor, TRUE); } static gint attachment_bar_key_press_event_cb (EAttachmentBar *attachment_bar, GdkEventKey *event) { if (event->keyval == GDK_Delete) { e_attachment_bar_remove_selected (attachment_bar); return TRUE; } return FALSE; } static gboolean attachment_bar_popup_menu_cb (EAttachmentBar *attachment_bar) { emcab_popup (attachment_bar, NULL, -1); return TRUE; } static void attachment_expander_notify_cb (GtkExpander *expander, GParamSpec *pspec, EMsgComposer *composer) { GtkLabel *label; const gchar *text; label = GTK_LABEL (composer->priv->attachment_expander_label); /* Update the expander label */ if (gtk_expander_get_expanded (expander)) text = _("Hide _Attachment Bar"); else text = _("Show _Attachment Bar"); gtk_label_set_text_with_mnemonic (label, text); } static void msg_composer_subject_changed_cb (EMsgComposer *composer) { EComposerHeaderTable *table; const gchar *subject; table = e_msg_composer_get_header_table (composer); subject = e_composer_header_table_get_subject (table); if (subject == NULL || *subject == '\0') subject = _("Compose Message"); gtk_window_set_title (GTK_WINDOW (composer), subject); } enum { UPDATE_AUTO_CC, UPDATE_AUTO_BCC, }; static void update_auto_recipients (EComposerHeaderTable *table, gint mode, const gchar *auto_addrs) { EDestination *dest, **destv = NULL; CamelInternetAddress *iaddr; GList *list = NULL; guint length; gint i; if (auto_addrs) { iaddr = camel_internet_address_new (); if (camel_address_decode (CAMEL_ADDRESS (iaddr), auto_addrs) != -1) { for (i = 0; i < camel_address_length (CAMEL_ADDRESS (iaddr)); i++) { const gchar *name, *addr; if (!camel_internet_address_get (iaddr, i, &name, &addr)) continue; dest = e_destination_new (); e_destination_set_auto_recipient (dest, TRUE); if (name) e_destination_set_name (dest, name); if (addr) e_destination_set_email (dest, addr); list = g_list_prepend (list, dest); } } camel_object_unref (iaddr); } switch (mode) { case UPDATE_AUTO_CC: destv = e_composer_header_table_get_destinations_cc (table); break; case UPDATE_AUTO_BCC: destv = e_composer_header_table_get_destinations_bcc (table); break; default: g_return_if_reached (); } if (destv) { for (i = 0; destv[i]; i++) { if (!e_destination_is_auto_recipient (destv[i])) { dest = e_destination_copy (destv[i]); list = g_list_prepend (list, dest); } } e_destination_freev (destv); } list = g_list_reverse (list); length = g_list_length (list); destv = destination_list_to_vector_sized (list, length); g_list_free (list); switch (mode) { case UPDATE_AUTO_CC: e_composer_header_table_set_destinations_cc (table, destv); break; case UPDATE_AUTO_BCC: e_composer_header_table_set_destinations_bcc (table, destv); break; default: g_return_if_reached (); } e_destination_freev (destv); } static void msg_composer_account_changed_cb (EMsgComposer *composer) { EMsgComposerPrivate *p = composer->priv; EComposerHeaderTable *table; GtkToggleAction *action; ESignature *signature; EAccount *account; gboolean active; gboolean sensitive; const gchar *cc_addrs = NULL; const gchar *bcc_addrs = NULL; const gchar *uid; table = e_msg_composer_get_header_table (composer); account = e_composer_header_table_get_account (table); if (account == NULL) goto exit; action = GTK_TOGGLE_ACTION (ACTION (PGP_SIGN)); active = account->pgp_always_sign && (!account->pgp_no_imip_sign || p->mime_type == NULL || g_ascii_strncasecmp (p->mime_type, "text/calendar", 13) != 0); gtk_toggle_action_set_active (action, active); action = GTK_TOGGLE_ACTION (ACTION (SMIME_SIGN)); active = account->smime_sign_default; gtk_toggle_action_set_active (action, active); action = GTK_TOGGLE_ACTION (ACTION (SMIME_ENCRYPT)); active = account->smime_encrypt_default; gtk_toggle_action_set_active (action, active); if (account->always_cc) cc_addrs = account->cc_addrs; if (account->always_bcc) bcc_addrs = account->bcc_addrs; uid = account->id->sig_uid; signature = uid ? mail_config_get_signature_by_uid (uid) : NULL; e_composer_header_table_set_signature (table, signature); /* XXX This should be done more generically. The composer * should not know about particular account types. */ sensitive = (strstr (account->transport->url, "exchange") != NULL) || (strstr (account->transport->url, "groupwise") != NULL); gtk_action_set_sensitive (ACTION (SEND_OPTIONS), sensitive); exit: update_auto_recipients (table, UPDATE_AUTO_CC, cc_addrs); update_auto_recipients (table, UPDATE_AUTO_BCC, bcc_addrs); e_msg_composer_show_sig_file (composer); } static void msg_composer_account_list_changed_cb (EMsgComposer *composer) { EComposerHeaderTable *table; EAccountList *account_list; EIterator *iterator; gboolean visible = FALSE; /* Determine whether to show the "send-options" action by * examining the account list for account types that support it. * * XXX I'd prefer a more general way of doing this. The composer * should not know about particular account types. Perhaps * add a "supports advanced send options" flag to EAccount. */ table = E_COMPOSER_HEADER_TABLE (composer->priv->header_table); account_list = e_composer_header_table_get_account_list (table); iterator = e_list_get_iterator (E_LIST (account_list)); while (!visible && e_iterator_is_valid (iterator)) { EAccount *account; const gchar *url; /* XXX EIterator misuses const. */ account = (EAccount *) e_iterator_get (iterator); e_iterator_next (iterator); if (!account->enabled) continue; url = account->transport->url; visible |= (strstr (url, "exchange") != NULL); visible |= (strstr (url, "groupwise") != NULL); } gtk_action_set_visible (ACTION (SEND_OPTIONS), visible); g_object_unref (iterator); } static void msg_composer_attach_message (EMsgComposer *composer, CamelMimeMessage *msg) { CamelMimePart *mime_part; GString *description; const gchar *subject; EMsgComposerPrivate *p = composer->priv; mime_part = camel_mime_part_new (); camel_mime_part_set_disposition (mime_part, "inline"); subject = camel_mime_message_get_subject (msg); description = g_string_new (_("Attached message")); if (subject != NULL) g_string_append_printf (description, " - %s", subject); camel_mime_part_set_description (mime_part, description->str); g_string_free (description, TRUE); camel_medium_set_content_object ( (CamelMedium *) mime_part, (CamelDataWrapper *) msg); camel_mime_part_set_content_type (mime_part, "message/rfc822"); e_attachment_bar_attach_mime_part ( E_ATTACHMENT_BAR (p->attachment_bar), mime_part); camel_object_unref (mime_part); } struct _drop_data { EMsgComposer *composer; GdkDragContext *context; /* Only selection->data and selection->length are valid */ GtkSelectionData *selection; guint32 action; guint info; guint time; unsigned int move:1; unsigned int moved:1; unsigned int aborted:1; }; int e_msg_composer_get_remote_download_count (EMsgComposer *composer) { EAttachmentBar *attachment_bar; g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), 0); attachment_bar = E_ATTACHMENT_BAR (composer->priv->attachment_bar); return e_attachment_bar_get_download_count (attachment_bar); } static void drop_action (EMsgComposer *composer, GdkDragContext *context, guint32 action, GtkSelectionData *selection, guint info, guint time, gboolean html_dnd) { char *tmp, *str, **urls; CamelMimePart *mime_part; CamelStream *stream; CamelMimeMessage *msg; char *content_type; int i, success = FALSE, delete = FALSE; EMsgComposerPrivate *p = composer->priv; switch (info) { case DND_TYPE_MESSAGE_RFC822: d (printf ("dropping a message/rfc822\n")); /* write the message (s) out to a CamelStream so we can use it */ stream = camel_stream_mem_new (); camel_stream_write (stream, (const gchar *)selection->data, selection->length); camel_stream_reset (stream); msg = camel_mime_message_new (); if (camel_data_wrapper_construct_from_stream ((CamelDataWrapper *)msg, stream) != -1) { msg_composer_attach_message (composer, msg); success = TRUE; delete = action == GDK_ACTION_MOVE; } camel_object_unref (msg); camel_object_unref (stream); break; case DND_TYPE_NETSCAPE_URL: d (printf ("dropping a _NETSCAPE_URL\n")); tmp = g_strndup ((const gchar *) selection->data, selection->length); urls = g_strsplit (tmp, "\n", 2); g_free (tmp); /* _NETSCAPE_URL is represented as "URI\nTITLE" */ handle_uri (composer, urls[0], html_dnd); g_strfreev (urls); success = TRUE; break; case DND_TYPE_TEXT_URI_LIST: d (printf ("dropping a text/uri-list\n")); tmp = g_strndup ((const gchar *) selection->data, selection->length); urls = g_strsplit (tmp, "\n", 0); g_free (tmp); for (i = 0; urls[i] != NULL; i++) { str = g_strstrip (urls[i]); if (str[0] == '#' || str[0] == '\0') continue; handle_uri (composer, str, html_dnd); } g_strfreev (urls); success = TRUE; break; case DND_TYPE_TEXT_VCARD: case DND_TYPE_TEXT_CALENDAR: content_type = gdk_atom_name (selection->type); d (printf ("dropping a %s\n", content_type)); mime_part = camel_mime_part_new (); camel_mime_part_set_content (mime_part, (const gchar *)selection->data, selection->length, content_type); camel_mime_part_set_disposition (mime_part, "inline"); e_attachment_bar_attach_mime_part (E_ATTACHMENT_BAR (p->attachment_bar), mime_part); camel_object_unref (mime_part); g_free (content_type); success = TRUE; break; case DND_TYPE_X_UID_LIST: { GPtrArray *uids; char *inptr, *inend; CamelFolder *folder; CamelException ex = CAMEL_EXCEPTION_INITIALISER; /* NB: This all runs synchronously, could be very slow/hang/block the ui */ uids = g_ptr_array_new (); inptr = (char*)selection->data; inend = (char*)(selection->data + selection->length); while (inptr < inend) { char *start = inptr; while (inptr < inend && *inptr) inptr++; if (start > (char *)selection->data) g_ptr_array_add (uids, g_strndup (start, inptr-start)); inptr++; } if (uids->len > 0) { folder = mail_tool_uri_to_folder ((const gchar *)selection->data, 0, &ex); if (folder) { if (uids->len == 1) { msg = camel_folder_get_message (folder, uids->pdata[0], &ex); if (msg == NULL) goto fail; msg_composer_attach_message (composer, msg); } else { CamelMultipart *mp = camel_multipart_new (); char *desc; camel_data_wrapper_set_mime_type ((CamelDataWrapper *)mp, "multipart/digest"); camel_multipart_set_boundary (mp, NULL); for (i=0;ilen;i++) { msg = camel_folder_get_message (folder, uids->pdata[i], &ex); if (msg) { mime_part = camel_mime_part_new (); camel_mime_part_set_disposition (mime_part, "inline"); camel_medium_set_content_object ((CamelMedium *)mime_part, (CamelDataWrapper *)msg); camel_mime_part_set_content_type (mime_part, "message/rfc822"); camel_multipart_add_part (mp, mime_part); camel_object_unref (mime_part); camel_object_unref (msg); } else { camel_object_unref (mp); goto fail; } } mime_part = camel_mime_part_new (); camel_medium_set_content_object ((CamelMedium *)mime_part, (CamelDataWrapper *)mp); /* translators, this count will always be >1 */ desc = g_strdup_printf (ngettext ("Attached message", "%d attached messages", uids->len), uids->len); camel_mime_part_set_description (mime_part, desc); g_free (desc); e_attachment_bar_attach_mime_part (E_ATTACHMENT_BAR(p->attachment_bar), mime_part); camel_object_unref (mime_part); camel_object_unref (mp); } success = TRUE; delete = action == GDK_ACTION_MOVE; fail: if (camel_exception_is_set (&ex)) { char *name; camel_object_get (folder, NULL, CAMEL_FOLDER_NAME, &name, NULL); e_error_run ((GtkWindow *)composer, "mail-composer:attach-nomessages", name?name:(char *)selection->data, camel_exception_get_description (&ex), NULL); camel_object_free (folder, CAMEL_FOLDER_NAME, name); } camel_object_unref (folder); } else { e_error_run ((GtkWindow *)composer, "mail-composer:attach-nomessages", (const gchar*)selection->data, camel_exception_get_description (&ex), NULL); } camel_exception_clear (&ex); } g_ptr_array_free (uids, TRUE); break; } default: d (printf ("dropping an unknown\n")); break; } gtk_drag_finish (context, success, delete, time); } static void drop_popup_copy (EPopup *ep, EPopupItem *item, gpointer data) { struct _drop_data *m = data; drop_action ( m->composer, m->context, GDK_ACTION_COPY, m->selection, m->info, m->time, FALSE); } static void drop_popup_move (EPopup *ep, EPopupItem *item, gpointer data) { struct _drop_data *m = data; drop_action ( m->composer, m->context, GDK_ACTION_MOVE, m->selection, m->info, m->time, FALSE); } static void drop_popup_cancel (EPopup *ep, EPopupItem *item, gpointer data) { struct _drop_data *m = data; gtk_drag_finish (m->context, FALSE, FALSE, m->time); } static EPopupItem drop_popup_menu[] = { { E_POPUP_ITEM, "00.emc.02", N_("_Copy"), drop_popup_copy, NULL, "mail-copy", 0 }, { E_POPUP_ITEM, "00.emc.03", N_("_Move"), drop_popup_move, NULL, "mail-move", 0 }, { E_POPUP_BAR, "10.emc" }, { E_POPUP_ITEM, "99.emc.00", N_("Cancel _Drag"), drop_popup_cancel, NULL, NULL, 0 }, }; static void drop_popup_free (EPopup *ep, GSList *items, gpointer data) { struct _drop_data *m = data; g_slist_free (items); g_object_unref (m->context); g_object_unref (m->composer); g_free (m->selection->data); g_free (m->selection); g_free (m); } static void msg_composer_notify_header_cb (EMsgComposer *composer) { GtkhtmlEditor *editor; editor = GTKHTML_EDITOR (composer); gtkhtml_editor_set_changed (editor, TRUE); } static GObject * msg_composer_constructor (GType type, guint n_construct_properties, GObjectConstructParam *construct_properties) { EShell *shell; EShellSettings *shell_settings; GObject *object; EMsgComposer *composer; GtkToggleAction *action; GList *spell_languages; GArray *array; gboolean active; guint binding_id; /* Chain up to parent's constructor() method. */ object = G_OBJECT_CLASS (parent_class)->constructor ( type, n_construct_properties, construct_properties); composer = E_MSG_COMPOSER (object); array = composer->priv->gconf_bridge_binding_ids; shell = e_shell_get_default (); shell_settings = e_shell_get_shell_settings (shell); /* Restore Persistent State */ binding_id = gconf_bridge_bind_property ( gconf_bridge_get (), COMPOSER_GCONF_CURRENT_FOLDER_KEY, G_OBJECT (composer), "current-folder"); g_array_append_val (array, binding_id); binding_id = gconf_bridge_bind_property ( gconf_bridge_get (), COMPOSER_GCONF_VIEW_BCC_KEY, G_OBJECT (ACTION (VIEW_BCC)), "active"); g_array_append_val (array, binding_id); binding_id = gconf_bridge_bind_property ( gconf_bridge_get (), COMPOSER_GCONF_VIEW_CC_KEY, G_OBJECT (ACTION (VIEW_CC)), "active"); g_array_append_val (array, binding_id); binding_id = gconf_bridge_bind_property ( gconf_bridge_get (), COMPOSER_GCONF_VIEW_FROM_KEY, G_OBJECT (ACTION (VIEW_FROM)), "active"); g_array_append_val (array, binding_id); binding_id = gconf_bridge_bind_property ( gconf_bridge_get (), COMPOSER_GCONF_VIEW_POST_TO_KEY, G_OBJECT (ACTION (VIEW_POST_TO)), "active"); g_array_append_val (array, binding_id); binding_id = gconf_bridge_bind_property ( gconf_bridge_get (), COMPOSER_GCONF_VIEW_REPLY_TO_KEY, G_OBJECT (ACTION (VIEW_REPLY_TO)), "active"); g_array_append_val (array, binding_id); binding_id = gconf_bridge_bind_window ( gconf_bridge_get (), COMPOSER_GCONF_WINDOW_PREFIX, GTK_WINDOW (composer), TRUE, FALSE); g_array_append_val (array, binding_id); /* Honor User Preferences */ g_object_get (shell_settings, "composer-format-html", &active, NULL); gtkhtml_editor_set_html_mode (GTKHTML_EDITOR (composer), active); action = GTK_TOGGLE_ACTION (ACTION (REQUEST_READ_RECEIPT)); g_object_get ( shell_settings, "composer-request-receipt", &active, NULL); gtk_toggle_action_set_active (action, active); spell_languages = e_load_spell_languages (); gtkhtml_editor_set_spell_languages ( GTKHTML_EDITOR (composer), spell_languages); g_list_free (spell_languages); e_binding_new ( G_OBJECT (shell_settings), "composer-inline-spelling", G_OBJECT (composer), "inline-spelling"); e_binding_new ( G_OBJECT (shell_settings), "composer-magic-links", G_OBJECT (composer), "magic-links"); e_binding_new ( G_OBJECT (shell_settings), "composer-magic-smileys", G_OBJECT (composer), "magic-smileys"); return object; } static void msg_composer_dispose (GObject *object) { EMsgComposer *composer = E_MSG_COMPOSER (object); gboolean delete_file; /* If the application is exiting, keep the autosave file so we can * restore it later. Otherwise we're just closing this composer * window, and the CLOSE action has already handled any unsaved * changes, so we can safely delete the autosave file. */ delete_file = !composer->priv->application_exiting; e_composer_autosave_unregister (composer, delete_file); e_composer_private_dispose (composer); /* Chain up to parent's dispose() method. */ G_OBJECT_CLASS (parent_class)->dispose (object); } static void msg_composer_finalize (GObject *object) { EMsgComposer *composer = E_MSG_COMPOSER (object); e_composer_private_finalize (composer); /* Chain up to parent's finalize() method. */ G_OBJECT_CLASS (parent_class)->finalize (object); } static void msg_composer_destroy (GtkObject *object) { EMsgComposer *composer = E_MSG_COMPOSER (object); all_composers = g_slist_remove (all_composers, object); if (composer->priv->address_dialog != NULL) { gtk_widget_destroy (composer->priv->address_dialog); composer->priv->address_dialog = NULL; } /* Chain up to parent's destroy() method. */ GTK_OBJECT_CLASS (parent_class)->destroy (object); } static void msg_composer_map (GtkWidget *widget) { EComposerHeaderTable *table; GtkWidget *input_widget; const gchar *text; /* Chain up to parent's map() method. */ GTK_WIDGET_CLASS (parent_class)->map (widget); table = e_msg_composer_get_header_table (E_MSG_COMPOSER (widget)); /* If the 'To' field is empty, focus it. */ input_widget = e_composer_header_table_get_header ( table, E_COMPOSER_HEADER_TO)->input_widget; text = gtk_entry_get_text (GTK_ENTRY (input_widget)); if (text == NULL || *text == '\0') { gtk_widget_grab_focus (input_widget); return; } /* If not, check the 'Subject' field. */ input_widget = e_composer_header_table_get_header ( table, E_COMPOSER_HEADER_SUBJECT)->input_widget; text = gtk_entry_get_text (GTK_ENTRY (input_widget)); if (text == NULL || *text == '\0') { gtk_widget_grab_focus (input_widget); return; } /* Jump to the editor as a last resort. */ gtkhtml_editor_run_command (GTKHTML_EDITOR (widget), "grab-focus"); } static gint msg_composer_delete_event (GtkWidget *widget, GdkEventAny *event) { /* This is needed for the ACTION macro. */ EMsgComposer *composer = E_MSG_COMPOSER (widget); gtk_action_activate (ACTION (CLOSE)); return TRUE; } static gboolean msg_composer_key_press_event (GtkWidget *widget, GdkEventKey *event) { EMsgComposer *composer = E_MSG_COMPOSER (widget); GtkWidget *input_widget; GtkhtmlEditor *editor; GtkHTML *html; editor = GTKHTML_EDITOR (widget); html = gtkhtml_editor_get_html (editor); input_widget = e_composer_header_table_get_header ( e_msg_composer_get_header_table (composer), E_COMPOSER_HEADER_SUBJECT)->input_widget; #ifdef HAVE_XFREE if (event->keyval == XF86XK_Send) { g_signal_emit (G_OBJECT (composer), signals[SEND], 0); return TRUE; } #endif /* HAVE_XFREE */ if (event->keyval == GDK_Escape) { gtk_action_activate (ACTION (CLOSE)); return TRUE; } if (event->keyval == GDK_Tab && gtk_widget_is_focus (input_widget)) { gtkhtml_editor_run_command (editor, "grab-focus"); return TRUE; } if (event->keyval == GDK_ISO_Left_Tab && gtk_widget_is_focus (GTK_WIDGET (html))) { gtk_widget_grab_focus (input_widget); return TRUE; } /* Chain up to parent's key_press_event() method. */ return GTK_WIDGET_CLASS (parent_class)->key_press_event (widget, event); } static gboolean msg_composer_drag_motion (GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time) { GList *targets; GdkDragAction actions = 0; GdkDragAction chosen_action; targets = context->targets; while (targets != NULL) { gint ii; for (ii = 0; ii < G_N_ELEMENTS (drag_info); ii++) if (targets->data == (gpointer) drag_info[ii].atom) actions |= drag_info[ii].actions; targets = g_list_next (targets); } actions &= context->actions; chosen_action = context->suggested_action; /* we default to copy */ if (chosen_action == GDK_ACTION_ASK && (actions & (GDK_ACTION_MOVE|GDK_ACTION_COPY)) != (GDK_ACTION_MOVE|GDK_ACTION_COPY)) chosen_action = GDK_ACTION_COPY; gdk_drag_status (context, chosen_action, time); return (chosen_action != 0); } static void msg_composer_drag_data_received (GtkWidget *widget, GdkDragContext *context, gint x, gint y, GtkSelectionData *selection, guint info, guint time) { EMsgComposer *composer; /* Widget may be EMsgComposer or GtkHTML. */ composer = E_MSG_COMPOSER (gtk_widget_get_toplevel (widget)); if (selection->data == NULL) return; if (selection->length == -1) return; if (context->action == GDK_ACTION_ASK) { EMPopup *emp; GSList *menus = NULL; GtkMenu *menu; gint ii; struct _drop_data *m; m = g_malloc0(sizeof (*m)); m->context = g_object_ref (context); m->composer = g_object_ref (composer); m->action = context->action; m->info = info; m->time = time; m->selection = g_malloc0(sizeof (*m->selection)); m->selection->data = g_malloc (selection->length); memcpy (m->selection->data, selection->data, selection->length); m->selection->length = selection->length; emp = em_popup_new ("org.gnome.evolution.mail.composer.popup.drop"); for (ii = 0; ii < G_N_ELEMENTS (drop_popup_menu); ii++) menus = g_slist_append (menus, &drop_popup_menu[ii]); e_popup_add_items ((EPopup *)emp, menus, NULL, drop_popup_free, m); menu = e_popup_create_menu_once ((EPopup *)emp, NULL, 0); gtk_menu_popup (menu, NULL, NULL, NULL, NULL, 0, time); } else { drop_action ( composer, context, context->action, selection, info, time, !GTK_WIDGET_TOPLEVEL (widget)); } } static void msg_composer_cut_clipboard (GtkhtmlEditor *editor) { EMsgComposer *composer; GtkWidget *parent; GtkWidget *widget; composer = E_MSG_COMPOSER (editor); widget = gtk_window_get_focus (GTK_WINDOW (editor)); parent = gtk_widget_get_parent (widget); if (parent == composer->priv->header_table) { gtk_editable_cut_clipboard (GTK_EDITABLE (widget)); return; } /* Chain up to parent's cut_clipboard() method. */ GTKHTML_EDITOR_CLASS (parent_class)->cut_clipboard (editor); } static void msg_composer_copy_clipboard (GtkhtmlEditor *editor) { EMsgComposer *composer; GtkWidget *parent; GtkWidget *widget; composer = E_MSG_COMPOSER (editor); widget = gtk_window_get_focus (GTK_WINDOW (editor)); parent = gtk_widget_get_parent (widget); if (parent == composer->priv->header_table) { gtk_editable_copy_clipboard (GTK_EDITABLE (widget)); return; } /* Chain up to parent's copy_clipboard() method. */ GTKHTML_EDITOR_CLASS (parent_class)->copy_clipboard (editor); } static void msg_composer_paste_clipboard (GtkhtmlEditor *editor) { EMsgComposer *composer; GtkWidget *parent; GtkWidget *widget; GtkClipboard *clipboard; composer = E_MSG_COMPOSER (editor); widget = gtk_window_get_focus (GTK_WINDOW (editor)); parent = gtk_widget_get_parent (widget); if (parent == composer->priv->header_table) { gtk_editable_paste_clipboard (GTK_EDITABLE (widget)); return; } clipboard = gtk_widget_get_clipboard (widget, GDK_SELECTION_CLIPBOARD); if (clipboard && gtk_clipboard_wait_is_image_available (clipboard)) { GdkPixbuf *pixbuf; pixbuf = gtk_clipboard_wait_for_image (clipboard); if (pixbuf) { char *tmpl = g_strconcat (_("Image"), "-XXXXXX", NULL); char *filename = e_mktemp (tmpl); g_free (tmpl); if (filename && gdk_pixbuf_save (pixbuf, filename, "png", NULL, NULL)) { if (gtkhtml_editor_get_html_mode (editor)) { char *uri = g_strconcat ("file://", filename, NULL); /* this loads image async, thus cannot remove file from this */ gtkhtml_editor_insert_image (editor, uri); g_free (uri); } else { /* this loads image immediately, remove file from cache to free up disk space */ e_attachment_bar_attach (E_ATTACHMENT_BAR (composer->priv->attachment_bar), filename, "image/png"); g_remove (filename); } } g_free (filename); g_object_unref (pixbuf); } } else { /* Chain up to parent's paste_clipboard() method. */ GTKHTML_EDITOR_CLASS (parent_class)->paste_clipboard (editor); } } static void msg_composer_select_all (GtkhtmlEditor *editor) { EMsgComposer *composer; GtkWidget *parent; GtkWidget *widget; composer = E_MSG_COMPOSER (editor); widget = gtk_window_get_focus (GTK_WINDOW (editor)); parent = gtk_widget_get_parent (widget); if (parent == composer->priv->header_table) { gtk_editable_set_position (GTK_EDITABLE (widget), -1); gtk_editable_select_region (GTK_EDITABLE (widget), 0, -1); } else /* Chain up to the parent's select_all() method. */ GTKHTML_EDITOR_CLASS (parent_class)->select_all (editor); } static void msg_composer_command_before (GtkhtmlEditor *editor, const gchar *command) { EMsgComposer *composer; const gchar *data; composer = E_MSG_COMPOSER (editor); if (strcmp (command, "insert-paragraph") != 0) return; if (composer->priv->in_signature_insert) return; data = gtkhtml_editor_get_paragraph_data (editor, "orig"); if (data != NULL && *data == '1') { gtkhtml_editor_run_command (editor, "text-default-color"); gtkhtml_editor_run_command (editor, "italic-off"); return; }; data = gtkhtml_editor_get_paragraph_data (editor, "signature"); if (data != NULL && *data == '1') { gtkhtml_editor_run_command (editor, "text-default-color"); gtkhtml_editor_run_command (editor, "italic-off"); } } static void msg_composer_command_after (GtkhtmlEditor *editor, const gchar *command) { EMsgComposer *composer; const gchar *data; composer = E_MSG_COMPOSER (editor); if (strcmp (command, "insert-paragraph") != 0) return; if (composer->priv->in_signature_insert) return; gtkhtml_editor_run_command (editor, "italic-off"); data = gtkhtml_editor_get_paragraph_data (editor, "orig"); if (data != NULL && *data == '1') e_msg_composer_reply_indent (composer); gtkhtml_editor_set_paragraph_data (editor, "orig", "0"); data = gtkhtml_editor_get_paragraph_data (editor, "signature"); if (data == NULL || *data != '1') return; /* Clear the signature. */ if (gtkhtml_editor_is_paragraph_empty (editor)) gtkhtml_editor_set_paragraph_data (editor, "signature" ,"0"); else if (gtkhtml_editor_is_previous_paragraph_empty (editor) && gtkhtml_editor_run_command (editor, "cursor-backward")) { gtkhtml_editor_set_paragraph_data (editor, "signature", "0"); gtkhtml_editor_run_command (editor, "cursor-forward"); } gtkhtml_editor_run_command (editor, "text-default-color"); gtkhtml_editor_run_command (editor, "italic-off"); } static gchar * msg_composer_image_uri (GtkhtmlEditor *editor, const gchar *uri) { EMsgComposer *composer; GHashTable *hash_table; CamelMimePart *part; const gchar *cid; composer = E_MSG_COMPOSER (editor); hash_table = composer->priv->inline_images_by_url; part = g_hash_table_lookup (hash_table, uri); if (part == NULL && g_str_has_prefix (uri, "file:")) part = e_msg_composer_add_inline_image_from_file ( composer, uri + 5); if (part == NULL && g_str_has_prefix (uri, "cid:")) { hash_table = composer->priv->inline_images; part = g_hash_table_lookup (hash_table, uri); } if (part == NULL) return NULL; composer->priv->current_images = g_list_prepend (composer->priv->current_images, part); cid = camel_mime_part_get_content_id (part); if (cid == NULL) return NULL; return g_strconcat ("cid:", cid, NULL); } static void msg_composer_link_clicked (GtkhtmlEditor *editor, const gchar *uri) { if (uri == NULL || *uri == '\0') return; if (g_ascii_strncasecmp (uri, "mailto:", 7) == 0) return; if (g_ascii_strncasecmp (uri, "thismessage:", 12) == 0) return; if (g_ascii_strncasecmp (uri, "cid:", 4) == 0) return; e_show_uri (GTK_WINDOW (editor), uri); } static void msg_composer_object_deleted (GtkhtmlEditor *editor) { const gchar *data; if (!gtkhtml_editor_is_paragraph_empty (editor)) return; data = gtkhtml_editor_get_paragraph_data (editor, "orig"); if (data != NULL && *data == '1') { gtkhtml_editor_set_paragraph_data (editor, "orig", "0"); gtkhtml_editor_run_command (editor, "indent-zero"); gtkhtml_editor_run_command (editor, "style-normal"); gtkhtml_editor_run_command (editor, "text-default-color"); gtkhtml_editor_run_command (editor, "italic-off"); gtkhtml_editor_run_command (editor, "insert-paragraph"); gtkhtml_editor_run_command (editor, "delete-back"); } data = gtkhtml_editor_get_paragraph_data (editor, "signature"); if (data != NULL && *data == '1') gtkhtml_editor_set_paragraph_data (editor, "signature", "0"); } static void msg_composer_uri_requested (GtkhtmlEditor *editor, const gchar *uri, GtkHTMLStream *stream) { EMsgComposer *composer; GHashTable *hash_table; GByteArray *array; CamelDataWrapper *wrapper; CamelStream *camel_stream; CamelMimePart *part; GtkHTML *html; /* XXX It's unfortunate we have to expose GtkHTML structs here. * Maybe we could rework this to use a GOutputStream. */ composer = E_MSG_COMPOSER (editor); html = gtkhtml_editor_get_html (editor); hash_table = composer->priv->inline_images_by_url; part = g_hash_table_lookup (hash_table, uri); if (part == NULL) { hash_table = composer->priv->inline_images; part = g_hash_table_lookup (hash_table, uri); } if (part == NULL) { gtk_html_end (html, stream, GTK_HTML_STREAM_ERROR); return; } array = g_byte_array_new (); camel_stream = camel_stream_mem_new_with_byte_array (array); wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (part)); camel_data_wrapper_decode_to_stream (wrapper, camel_stream); gtk_html_write ( gtkhtml_editor_get_html (editor), stream, (gchar *) array->data, array->len); camel_object_unref (camel_stream); gtk_html_end (html, stream, GTK_HTML_STREAM_OK); } static void msg_composer_class_init (EMsgComposerClass *class) { GObjectClass *object_class; GtkObjectClass *gtk_object_class; GtkWidgetClass *widget_class; GtkhtmlEditorClass *editor_class; gint ii; for (ii = 0; ii < G_N_ELEMENTS (drag_info); ii++) drag_info[ii].atom = gdk_atom_intern (drag_info[ii].target, FALSE); parent_class = g_type_class_peek_parent (class); g_type_class_add_private (class, sizeof (EMsgComposerPrivate)); object_class = G_OBJECT_CLASS (class); object_class->constructor = msg_composer_constructor; object_class->dispose = msg_composer_dispose; object_class->finalize = msg_composer_finalize; gtk_object_class = GTK_OBJECT_CLASS (class); gtk_object_class->destroy = msg_composer_destroy; widget_class = GTK_WIDGET_CLASS (class); widget_class->map = msg_composer_map; widget_class->delete_event = msg_composer_delete_event; widget_class->key_press_event = msg_composer_key_press_event; widget_class->drag_motion = msg_composer_drag_motion; widget_class->drag_data_received = msg_composer_drag_data_received; editor_class = GTKHTML_EDITOR_CLASS (class); editor_class->cut_clipboard = msg_composer_cut_clipboard; editor_class->copy_clipboard = msg_composer_copy_clipboard; editor_class->paste_clipboard = msg_composer_paste_clipboard; editor_class->select_all = msg_composer_select_all; editor_class->command_before = msg_composer_command_before; editor_class->command_after = msg_composer_command_after; editor_class->image_uri = msg_composer_image_uri; editor_class->link_clicked = msg_composer_link_clicked; editor_class->object_deleted = msg_composer_object_deleted; editor_class->uri_requested = msg_composer_uri_requested; signals[SEND] = g_signal_new ( "send", E_TYPE_MSG_COMPOSER, G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); signals[SAVE_DRAFT] = g_signal_new ( "save-draft", E_TYPE_MSG_COMPOSER, G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); } static void msg_composer_init (EMsgComposer *composer) { EComposerHeaderTable *table; GtkUIManager *manager; GtkhtmlEditor *editor; GtkHTML *html; composer->priv = E_MSG_COMPOSER_GET_PRIVATE (composer); e_composer_private_init (composer); editor = GTKHTML_EDITOR (composer); html = gtkhtml_editor_get_html (editor); manager = gtkhtml_editor_get_ui_manager (editor); all_composers = g_slist_prepend (all_composers, composer); table = E_COMPOSER_HEADER_TABLE (composer->priv->header_table); gtk_window_set_title (GTK_WINDOW (composer), _("Compose Message")); gtk_window_set_icon_name (GTK_WINDOW (composer), "mail-message-new"); /* Drag-and-Drop Support */ gtk_drag_dest_set ( GTK_WIDGET (composer), GTK_DEST_DEFAULT_ALL, drop_types, G_N_ELEMENTS (drop_types), GDK_ACTION_COPY | GDK_ACTION_ASK | GDK_ACTION_MOVE); /* XXX I'm not sure why we have to explicitly configure the * attachment bar as a drag destination when CompEditor * doesn't and previous Evolution releases (2.22 and * prior) don't, but this is the only way I could figure * out how to get drag-and-drop to the attachment bar * working again. I'm probably overlooking something * simple... */ gtk_drag_dest_set ( composer->priv->attachment_bar, GTK_DEST_DEFAULT_ALL, drop_types, G_N_ELEMENTS (drop_types), GDK_ACTION_COPY | GDK_ACTION_ASK | GDK_ACTION_MOVE); g_signal_connect ( composer->priv->attachment_bar, "drag-motion", G_CALLBACK (msg_composer_drag_motion), NULL); g_signal_connect ( composer->priv->attachment_bar, "drag-data-received", G_CALLBACK (msg_composer_drag_data_received), NULL); g_signal_connect ( html, "drag-data-received", G_CALLBACK (msg_composer_drag_data_received), NULL); /* Configure Headers */ e_composer_header_table_set_account_list ( table, mail_config_get_accounts ()); e_composer_header_table_set_signature_list ( table, mail_config_get_signatures ()); g_signal_connect_swapped ( table, "notify::account", G_CALLBACK (msg_composer_account_changed_cb), composer); g_signal_connect_swapped ( table, "notify::account-list", G_CALLBACK (msg_composer_account_list_changed_cb), composer); g_signal_connect_swapped ( table, "notify::destinations-bcc", G_CALLBACK (msg_composer_notify_header_cb), composer); g_signal_connect_swapped ( table, "notify::destinations-cc", G_CALLBACK (msg_composer_notify_header_cb), composer); g_signal_connect_swapped ( table, "notify::destinations-to", G_CALLBACK (msg_composer_notify_header_cb), composer); g_signal_connect_swapped ( table, "notify::reply-to", G_CALLBACK (msg_composer_notify_header_cb), composer); g_signal_connect_swapped ( table, "notify::signature", G_CALLBACK (e_msg_composer_show_sig_file), composer); g_signal_connect_swapped ( table, "notify::subject", G_CALLBACK (msg_composer_subject_changed_cb), composer); g_signal_connect_swapped ( table, "notify::subject", G_CALLBACK (msg_composer_notify_header_cb), composer); msg_composer_account_changed_cb (composer); msg_composer_account_list_changed_cb (composer); /* Attachment Bar */ g_signal_connect ( composer->priv->attachment_bar, "button_press_event", G_CALLBACK (attachment_bar_button_press_event_cb), NULL); g_signal_connect ( composer->priv->attachment_bar, "key_press_event", G_CALLBACK (attachment_bar_key_press_event_cb), NULL); g_signal_connect ( composer->priv->attachment_bar, "popup-menu", G_CALLBACK (attachment_bar_popup_menu_cb), NULL); g_signal_connect ( composer->priv->attachment_bar, "changed", G_CALLBACK (attachment_bar_changed_cb), composer); g_signal_connect_after ( composer->priv->attachment_expander, "notify::expanded", G_CALLBACK (attachment_expander_notify_cb), composer); e_composer_autosave_register (composer); /* Initialization may have tripped the "changed" state. */ gtkhtml_editor_set_changed (editor, FALSE); e_plugin_ui_register_manager ( "org.gnome.evolution.composer", manager, composer); } GType e_msg_composer_get_type (void) { static GType type = 0; if (G_UNLIKELY (type == 0)) { static const GTypeInfo type_info = { sizeof (EMsgComposerClass), (GBaseInitFunc) NULL, (GBaseFinalizeFunc) NULL, (GClassInitFunc) msg_composer_class_init, (GClassFinalizeFunc) NULL, NULL, /* class_data */ sizeof (EMsgComposer), 0, /* n_preallocs */ (GInstanceInitFunc) msg_composer_init, NULL /* value_table */ }; type = g_type_register_static ( GTKHTML_TYPE_EDITOR, "EMsgComposer", &type_info, 0); } return type; } /* Callbacks. */ static EMsgComposer * create_composer (gint visible_mask) { EMsgComposer *composer; EComposerHeaderTable *table; GtkToggleAction *action; gboolean active; composer = g_object_new (E_TYPE_MSG_COMPOSER, NULL); table = E_COMPOSER_HEADER_TABLE (composer->priv->header_table); /* Configure View Menu */ /* If we're mailing, you cannot disable "To". */ action = GTK_TOGGLE_ACTION (ACTION (VIEW_TO)); active = visible_mask & E_MSG_COMPOSER_VISIBLE_TO; gtk_action_set_sensitive (ACTION (VIEW_TO), active); gtk_toggle_action_set_active (action, active); /* Ditto for "Post-To". */ action = GTK_TOGGLE_ACTION (ACTION (VIEW_POST_TO)); active = visible_mask & E_MSG_COMPOSER_VISIBLE_POSTTO; gtk_action_set_sensitive (ACTION (VIEW_POST_TO), active); gtk_toggle_action_set_active (action, active); /* Disable "Cc" if we're posting. */ if (!(visible_mask & E_MSG_COMPOSER_VISIBLE_CC)) { action = GTK_TOGGLE_ACTION (ACTION (VIEW_CC)); gtk_toggle_action_set_active (action, FALSE); } /* Disable "Bcc" if we're posting. */ if (!(visible_mask & E_MSG_COMPOSER_VISIBLE_BCC)) { action = GTK_TOGGLE_ACTION (ACTION (VIEW_BCC)); gtk_toggle_action_set_active (action, FALSE); } action = GTK_TOGGLE_ACTION (ACTION (VIEW_SUBJECT)); gtk_toggle_action_set_active (action, TRUE); return composer; } /** * e_msg_composer_new_with_type: * @type: the type of composer to create * * Create a new message composer widget. The type can be * E_MSG_COMPOSER_MAIL, E_MSG_COMPOSER_POST or E_MSG_COMPOSER_MAIL_POST. * * Returns: A pointer to the newly created widget **/ EMsgComposer * e_msg_composer_new_with_type (gint type) { EMsgComposer *composer; gint visible_mask; switch (type) { case E_MSG_COMPOSER_MAIL: visible_mask = E_MSG_COMPOSER_VISIBLE_MASK_MAIL; break; case E_MSG_COMPOSER_POST: visible_mask = E_MSG_COMPOSER_VISIBLE_MASK_POST; break; default: visible_mask = E_MSG_COMPOSER_VISIBLE_MASK_MAIL | E_MSG_COMPOSER_VISIBLE_MASK_POST; break; } composer = create_composer (visible_mask); set_editor_text (composer, "", TRUE); return composer; } /** * e_msg_composer_new: * * Create a new message composer widget. * * Returns: A pointer to the newly created widget **/ EMsgComposer * e_msg_composer_new (void) { return e_msg_composer_new_with_type (E_MSG_COMPOSER_MAIL); } static gboolean is_special_header (const gchar *hdr_name) { /* Note: a header is a "special header" if it has any meaning: 1. it's not a X-* header or 2. it's an X-Evolution* header */ if (g_ascii_strncasecmp (hdr_name, "X-", 2)) return TRUE; if (!g_ascii_strncasecmp (hdr_name, "X-Evolution", 11)) return TRUE; /* we can keep all other X-* headers */ return FALSE; } static void e_msg_composer_set_pending_body (EMsgComposer *composer, gchar *text, gssize length) { g_object_set_data_full ( G_OBJECT (composer), "body:text", text, (GDestroyNotify) g_free); g_object_set_data ( G_OBJECT (composer), "body:length", GSIZE_TO_POINTER (length)); } static void e_msg_composer_flush_pending_body (EMsgComposer *composer) { const gchar *body; gpointer data; gssize length; body = g_object_get_data (G_OBJECT (composer), "body:text"); data = g_object_get_data (G_OBJECT (composer), "body:length"); length = GPOINTER_TO_SIZE (data); if (body != NULL) set_editor_text (composer, body, FALSE); g_object_set_data (G_OBJECT (composer), "body:text", NULL); } static void add_attachments_handle_mime_part (EMsgComposer *composer, CamelMimePart *mime_part, gboolean just_inlines, gboolean related, gint depth) { CamelContentType *content_type; CamelDataWrapper *wrapper; if (!mime_part) return; content_type = camel_mime_part_get_content_type (mime_part); wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (mime_part)); if (CAMEL_IS_MULTIPART (wrapper)) { /* another layer of multipartness... */ add_attachments_from_multipart ( composer, (CamelMultipart *) wrapper, just_inlines, depth + 1); } else if (just_inlines) { if (camel_mime_part_get_content_id (mime_part) || camel_mime_part_get_content_location (mime_part)) e_msg_composer_add_inline_image_from_mime_part ( composer, mime_part); } else if (related && camel_content_type_is (content_type, "image", "*")) { e_msg_composer_add_inline_image_from_mime_part (composer, mime_part); } else if (camel_content_type_is (content_type, "text", "*") && camel_mime_part_get_filename (mime_part) == NULL) { /* do nothing if this is a text/anything without filename, otherwise attach it too */ } else { e_msg_composer_attach (composer, mime_part); } } static void add_attachments_from_multipart (EMsgComposer *composer, CamelMultipart *multipart, gboolean just_inlines, gint depth) { /* find appropriate message attachments to add to the composer */ CamelMimePart *mime_part; gboolean related; gint i, nparts; related = camel_content_type_is ( CAMEL_DATA_WRAPPER (multipart)->mime_type, "multipart", "related"); if (CAMEL_IS_MULTIPART_SIGNED (multipart)) { mime_part = camel_multipart_get_part ( multipart, CAMEL_MULTIPART_SIGNED_CONTENT); add_attachments_handle_mime_part ( composer, mime_part, just_inlines, related, depth); } else if (CAMEL_IS_MULTIPART_ENCRYPTED (multipart)) { /* XXX What should we do in this case? */ } else { nparts = camel_multipart_get_number (multipart); for (i = 0; i < nparts; i++) { mime_part = camel_multipart_get_part (multipart, i); add_attachments_handle_mime_part ( composer, mime_part, just_inlines, related, depth); } } } /** * e_msg_composer_add_message_attachments: * @composer: the composer to add the attachments to. * @message: the source message to copy the attachments from. * @just_inlines: whether to attach all attachments or just add * inline images. * * Walk through all the mime parts in @message and add them to the composer * specified in @composer. */ void e_msg_composer_add_message_attachments (EMsgComposer *composer, CamelMimeMessage *message, gboolean just_inlines) { CamelDataWrapper *wrapper; wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (message)); if (!CAMEL_IS_MULTIPART (wrapper)) return; add_attachments_from_multipart ( composer, (CamelMultipart *) wrapper, just_inlines, 0); } static void handle_multipart_signed (EMsgComposer *composer, CamelMultipart *multipart, gint depth) { CamelContentType *content_type; CamelDataWrapper *content; CamelMimePart *mime_part; GtkToggleAction *action; /* FIXME: make sure this isn't an s/mime signed part?? */ action = GTK_TOGGLE_ACTION (ACTION (PGP_SIGN)); gtk_toggle_action_set_active (action, TRUE); mime_part = camel_multipart_get_part ( multipart, CAMEL_MULTIPART_SIGNED_CONTENT); if (mime_part == NULL) return; content_type = camel_mime_part_get_content_type (mime_part); content = camel_medium_get_content_object (CAMEL_MEDIUM (mime_part)); if (CAMEL_IS_MULTIPART (content)) { multipart = CAMEL_MULTIPART (content); /* Note: depth is preserved here because we're not counting multipart/signed as a multipart, instead we want to treat the content part as our mime part here. */ if (CAMEL_IS_MULTIPART_SIGNED (content)) { /* handle the signed content and configure the composer to sign outgoing messages */ handle_multipart_signed (composer, multipart, depth); } else if (CAMEL_IS_MULTIPART_ENCRYPTED (content)) { /* decrypt the encrypted content and configure the composer to encrypt outgoing messages */ handle_multipart_encrypted (composer, mime_part, depth); } else if (camel_content_type_is (content_type, "multipart", "alternative")) { /* this contains the text/plain and text/html versions of the message body */ handle_multipart_alternative (composer, multipart, depth); } else { /* there must be attachments... */ handle_multipart (composer, multipart, depth); } } else if (camel_content_type_is (content_type, "text", "*")) { gchar *html; gssize length; html = em_utils_part_to_html (mime_part, &length, NULL); e_msg_composer_set_pending_body (composer, html, length); } else { e_msg_composer_attach (composer, mime_part); } } static void handle_multipart_encrypted (EMsgComposer *composer, CamelMimePart *multipart, gint depth) { CamelContentType *content_type; CamelCipherContext *cipher; CamelDataWrapper *content; CamelMimePart *mime_part; CamelException ex; CamelCipherValidity *valid; GtkToggleAction *action; /* FIXME: make sure this is a PGP/MIME encrypted part?? */ action = GTK_TOGGLE_ACTION (ACTION (PGP_ENCRYPT)); gtk_toggle_action_set_active (action, TRUE); camel_exception_init (&ex); cipher = mail_crypto_get_pgp_cipher_context (NULL); mime_part = camel_mime_part_new (); valid = camel_cipher_decrypt (cipher, multipart, mime_part, &ex); camel_object_unref (cipher); camel_exception_clear (&ex); if (valid == NULL) return; camel_cipher_validity_free (valid); content_type = camel_mime_part_get_content_type (mime_part); content = camel_medium_get_content_object (CAMEL_MEDIUM (mime_part)); if (CAMEL_IS_MULTIPART (content)) { CamelMultipart *content_multipart = CAMEL_MULTIPART (content); /* Note: depth is preserved here because we're not counting multipart/encrypted as a multipart, instead we want to treat the content part as our mime part here. */ if (CAMEL_IS_MULTIPART_SIGNED (content)) { /* handle the signed content and configure the composer to sign outgoing messages */ handle_multipart_signed (composer, content_multipart, depth); } else if (CAMEL_IS_MULTIPART_ENCRYPTED (content)) { /* decrypt the encrypted content and configure the composer to encrypt outgoing messages */ handle_multipart_encrypted (composer, mime_part, depth); } else if (camel_content_type_is (content_type, "multipart", "alternative")) { /* this contains the text/plain and text/html versions of the message body */ handle_multipart_alternative (composer, content_multipart, depth); } else { /* there must be attachments... */ handle_multipart (composer, content_multipart, depth); } } else if (camel_content_type_is (content_type, "text", "*")) { gchar *html; gssize length; html = em_utils_part_to_html (mime_part, &length, NULL); e_msg_composer_set_pending_body (composer, html, length); } else { e_msg_composer_attach (composer, mime_part); } camel_object_unref (mime_part); } static void handle_multipart_alternative (EMsgComposer *composer, CamelMultipart *multipart, gint depth) { /* Find the text/html part and set the composer body to it's contents */ CamelMimePart *text_part = NULL; gint i, nparts; nparts = camel_multipart_get_number (multipart); for (i = 0; i < nparts; i++) { CamelContentType *content_type; CamelDataWrapper *content; CamelMimePart *mime_part; mime_part = camel_multipart_get_part (multipart, i); if (!mime_part) continue; content_type = camel_mime_part_get_content_type (mime_part); content = camel_medium_get_content_object (CAMEL_MEDIUM (mime_part)); if (CAMEL_IS_MULTIPART (content)) { CamelMultipart *mp; mp = CAMEL_MULTIPART (content); if (CAMEL_IS_MULTIPART_SIGNED (content)) { /* handle the signed content and configure the composer to sign outgoing messages */ handle_multipart_signed (composer, mp, depth + 1); } else if (CAMEL_IS_MULTIPART_ENCRYPTED (content)) { /* decrypt the encrypted content and configure the composer to encrypt outgoing messages */ handle_multipart_encrypted (composer, mime_part, depth + 1); } else { /* depth doesn't matter so long as we don't pass 0 */ handle_multipart (composer, mp, depth + 1); } } else if (camel_content_type_is (content_type, "text", "html")) { /* text/html is preferable, so once we find it we're done... */ text_part = mime_part; break; } else if (camel_content_type_is (content_type, "text", "*")) { /* anyt text part not text/html is second rate so the first text part we find isn't necessarily the one we'll use. */ if (!text_part) text_part = mime_part; } else { e_msg_composer_attach (composer, mime_part); } } if (text_part) { gchar *html; gssize length; html = em_utils_part_to_html (text_part, &length, NULL); e_msg_composer_set_pending_body (composer, html, length); } } static void handle_multipart (EMsgComposer *composer, CamelMultipart *multipart, gint depth) { gint i, nparts; nparts = camel_multipart_get_number (multipart); for (i = 0; i < nparts; i++) { CamelContentType *content_type; CamelDataWrapper *content; CamelMimePart *mime_part; mime_part = camel_multipart_get_part (multipart, i); if (!mime_part) continue; content_type = camel_mime_part_get_content_type (mime_part); content = camel_medium_get_content_object (CAMEL_MEDIUM (mime_part)); if (CAMEL_IS_MULTIPART (content)) { CamelMultipart *mp; mp = CAMEL_MULTIPART (content); if (CAMEL_IS_MULTIPART_SIGNED (content)) { /* handle the signed content and configure the composer to sign outgoing messages */ handle_multipart_signed (composer, mp, depth + 1); } else if (CAMEL_IS_MULTIPART_ENCRYPTED (content)) { /* decrypt the encrypted content and configure the composer to encrypt outgoing messages */ handle_multipart_encrypted (composer, mime_part, depth + 1); } else if (camel_content_type_is (content_type, "multipart", "alternative")) { handle_multipart_alternative (composer, mp, depth + 1); } else { /* depth doesn't matter so long as we don't pass 0 */ handle_multipart (composer, mp, depth + 1); } } else if (depth == 0 && i == 0) { gchar *html; gssize length; /* Since the first part is not multipart/alternative, * this must be the body. */ html = em_utils_part_to_html (mime_part, &length, NULL); e_msg_composer_set_pending_body (composer, html, length); } else if (camel_mime_part_get_content_id (mime_part) || camel_mime_part_get_content_location (mime_part)) { /* special in-line attachment */ e_msg_composer_add_inline_image_from_mime_part (composer, mime_part); } else { /* normal attachment */ e_msg_composer_attach (composer, mime_part); } } } static void set_signature_gui (EMsgComposer *composer) { GtkhtmlEditor *editor; EComposerHeaderTable *table; ESignature *signature = NULL; const gchar *data; gchar *decoded; editor = GTKHTML_EDITOR (composer); table = e_msg_composer_get_header_table (composer); if (!gtkhtml_editor_search_by_data (editor, 1, "ClueFlow", "signature", "1")) return; data = gtkhtml_editor_get_paragraph_data (editor, "signature_name"); if (g_str_has_prefix (data, "uid:")) { decoded = decode_signature_name (data + 4); signature = mail_config_get_signature_by_uid (decoded); g_free (decoded); } else if (g_str_has_prefix (data, "name:")) { decoded = decode_signature_name (data + 5); signature = mail_config_get_signature_by_name (decoded); g_free (decoded); } e_composer_header_table_set_signature (table, signature); } /** * e_msg_composer_new_with_message: * @message: The message to use as the source * * Create a new message composer widget. * * Note: Designed to work only for messages constructed using Evolution. * * Returns: A pointer to the newly created widget **/ EMsgComposer * e_msg_composer_new_with_message (CamelMimeMessage *message) { const CamelInternetAddress *to, *cc, *bcc; GList *To = NULL, *Cc = NULL, *Bcc = NULL, *postto = NULL; const gchar *format, *subject; EDestination **Tov, **Ccv, **Bccv; GHashTable *auto_cc, *auto_bcc; CamelContentType *content_type; struct _camel_header_raw *headers; CamelDataWrapper *content; EAccount *account = NULL; gchar *account_name; EMsgComposer *composer; EComposerHeaderTable *table; GtkToggleAction *action; struct _camel_header_raw *xev; gint len, i; EMsgComposerPrivate *p; for (headers = CAMEL_MIME_PART (message)->headers;headers;headers = headers->next) { if (!strcmp (headers->name, "X-Evolution-PostTo")) postto = g_list_append (postto, g_strstrip (g_strdup (headers->value))); } if (postto != NULL) composer = create_composer (E_MSG_COMPOSER_VISIBLE_MASK_POST); else composer = create_composer (E_MSG_COMPOSER_VISIBLE_MASK_MAIL); p = composer->priv; if (!composer) { g_list_foreach (postto, (GFunc)g_free, NULL); g_list_free (postto); return NULL; } table = e_msg_composer_get_header_table (composer); if (postto) { e_composer_header_table_set_post_to_list (table, postto); g_list_foreach (postto, (GFunc)g_free, NULL); g_list_free (postto); postto = NULL; } /* Restore the Account preference */ account_name = (char *) camel_medium_get_header (CAMEL_MEDIUM (message), "X-Evolution-Account"); if (account_name) { account_name = g_strdup (account_name); g_strstrip (account_name); if ((account = mail_config_get_account_by_uid (account_name)) == NULL) /* 'old' setting */ account = mail_config_get_account_by_name (account_name); if (account) { g_free (account_name); account_name = g_strdup (account->name); } } if (postto == NULL) { auto_cc = g_hash_table_new_full ( camel_strcase_hash, camel_strcase_equal, (GDestroyNotify) g_free, (GDestroyNotify) NULL); auto_bcc = g_hash_table_new_full ( camel_strcase_hash, camel_strcase_equal, (GDestroyNotify) g_free, (GDestroyNotify) NULL); if (account) { CamelInternetAddress *iaddr; /* hash our auto-recipients for this account */ if (account->always_cc) { iaddr = camel_internet_address_new (); if (camel_address_decode (CAMEL_ADDRESS (iaddr), account->cc_addrs) != -1) { for (i = 0; i < camel_address_length (CAMEL_ADDRESS (iaddr)); i++) { const gchar *name, *addr; if (!camel_internet_address_get (iaddr, i, &name, &addr)) continue; g_hash_table_insert (auto_cc, g_strdup (addr), GINT_TO_POINTER (TRUE)); } } camel_object_unref (iaddr); } if (account->always_bcc) { iaddr = camel_internet_address_new (); if (camel_address_decode (CAMEL_ADDRESS (iaddr), account->bcc_addrs) != -1) { for (i = 0; i < camel_address_length (CAMEL_ADDRESS (iaddr)); i++) { const gchar *name, *addr; if (!camel_internet_address_get (iaddr, i, &name, &addr)) continue; g_hash_table_insert (auto_bcc, g_strdup (addr), GINT_TO_POINTER (TRUE)); } } camel_object_unref (iaddr); } } to = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_TO); cc = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_CC); bcc = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_BCC); len = CAMEL_ADDRESS (to)->addresses->len; for (i = 0; i < len; i++) { const gchar *name, *addr; if (camel_internet_address_get (to, i, &name, &addr)) { EDestination *dest = e_destination_new (); e_destination_set_name (dest, name); e_destination_set_email (dest, addr); To = g_list_append (To, dest); } } Tov = destination_list_to_vector (To); g_list_free (To); len = CAMEL_ADDRESS (cc)->addresses->len; for (i = 0; i < len; i++) { const gchar *name, *addr; if (camel_internet_address_get (cc, i, &name, &addr)) { EDestination *dest = e_destination_new (); e_destination_set_name (dest, name); e_destination_set_email (dest, addr); if (g_hash_table_lookup (auto_cc, addr)) e_destination_set_auto_recipient (dest, TRUE); Cc = g_list_append (Cc, dest); } } Ccv = destination_list_to_vector (Cc); g_hash_table_destroy (auto_cc); g_list_free (Cc); len = CAMEL_ADDRESS (bcc)->addresses->len; for (i = 0; i < len; i++) { const gchar *name, *addr; if (camel_internet_address_get (bcc, i, &name, &addr)) { EDestination *dest = e_destination_new (); e_destination_set_name (dest, name); e_destination_set_email (dest, addr); if (g_hash_table_lookup (auto_bcc, addr)) e_destination_set_auto_recipient (dest, TRUE); Bcc = g_list_append (Bcc, dest); } } Bccv = destination_list_to_vector (Bcc); g_hash_table_destroy (auto_bcc); g_list_free (Bcc); } else { Tov = NULL; Ccv = NULL; Bccv = NULL; } subject = camel_mime_message_get_subject (message); e_composer_header_table_set_account_name (table, account_name); e_composer_header_table_set_destinations_to (table, Tov); e_composer_header_table_set_destinations_cc (table, Ccv); e_composer_header_table_set_destinations_bcc (table, Bccv); e_composer_header_table_set_subject (table, subject); g_free (account_name); e_destination_freev (Tov); e_destination_freev (Ccv); e_destination_freev (Bccv); /* Restore the format editing preference */ format = camel_medium_get_header (CAMEL_MEDIUM (message), "X-Evolution-Format"); if (format) { gchar **flags; while (*format && camel_mime_is_lwsp (*format)) format++; flags = g_strsplit (format, ", ", 0); for (i=0;flags[i];i++) { printf ("restoring draft flag '%s'\n", flags[i]); if (g_ascii_strcasecmp (flags[i], "text/html") == 0) gtkhtml_editor_set_html_mode ( GTKHTML_EDITOR (composer), TRUE); else if (g_ascii_strcasecmp (flags[i], "text/plain") == 0) gtkhtml_editor_set_html_mode ( GTKHTML_EDITOR (composer), FALSE); else if (g_ascii_strcasecmp (flags[i], "pgp-sign") == 0) { action = GTK_TOGGLE_ACTION (ACTION (PGP_SIGN)); gtk_toggle_action_set_active (action, TRUE); } else if (g_ascii_strcasecmp (flags[i], "pgp-encrypt") == 0) { action = GTK_TOGGLE_ACTION (ACTION (PGP_ENCRYPT)); gtk_toggle_action_set_active (action, TRUE); } else if (g_ascii_strcasecmp (flags[i], "smime-sign") == 0) { action = GTK_TOGGLE_ACTION (ACTION (SMIME_SIGN)); gtk_toggle_action_set_active (action, TRUE); } else if (g_ascii_strcasecmp (flags[i], "smime-encrypt") == 0) { action = GTK_TOGGLE_ACTION (ACTION (SMIME_ENCRYPT)); gtk_toggle_action_set_active (action, TRUE); } } g_strfreev (flags); } /* Remove any other X-Evolution-* headers that may have been set */ xev = mail_tool_remove_xevolution_headers (message); camel_header_raw_clear (&xev); /* Check for receipt request */ if (camel_medium_get_header (CAMEL_MEDIUM (message), "Disposition-Notification-To")) { action = GTK_TOGGLE_ACTION (ACTION (REQUEST_READ_RECEIPT)); gtk_toggle_action_set_active (action, TRUE); } /* Check for mail priority */ if (camel_medium_get_header (CAMEL_MEDIUM (message), "X-Priority")) { action = GTK_TOGGLE_ACTION (ACTION (PRIORITIZE_MESSAGE)); gtk_toggle_action_set_active (action, TRUE); } /* set extra headers */ headers = CAMEL_MIME_PART (message)->headers; while (headers) { if (!is_special_header (headers->name) || !g_ascii_strcasecmp (headers->name, "References") || !g_ascii_strcasecmp (headers->name, "In-Reply-To")) { g_ptr_array_add (p->extra_hdr_names, g_strdup (headers->name)); g_ptr_array_add (p->extra_hdr_values, g_strdup (headers->value)); } headers = headers->next; } /* Restore the attachments and body text */ content = camel_medium_get_content_object (CAMEL_MEDIUM (message)); if (CAMEL_IS_MULTIPART (content)) { CamelMultipart *multipart; multipart = CAMEL_MULTIPART (content); content_type = camel_mime_part_get_content_type (CAMEL_MIME_PART (message)); if (CAMEL_IS_MULTIPART_SIGNED (content)) { /* handle the signed content and configure the composer to sign outgoing messages */ handle_multipart_signed (composer, multipart, 0); } else if (CAMEL_IS_MULTIPART_ENCRYPTED (content)) { /* decrypt the encrypted content and configure the composer to encrypt outgoing messages */ handle_multipart_encrypted (composer, CAMEL_MIME_PART (message), 0); } else if (camel_content_type_is (content_type, "multipart", "alternative")) { /* this contains the text/plain and text/html versions of the message body */ handle_multipart_alternative (composer, multipart, 0); } else { /* there must be attachments... */ handle_multipart (composer, multipart, 0); } } else { gchar *html; gssize length; html = em_utils_part_to_html ((CamelMimePart *)message, &length, NULL); e_msg_composer_set_pending_body (composer, html, length); } /* We wait until now to set the body text because we need to * ensure that the attachment bar has all the attachments before * we request them. */ e_msg_composer_flush_pending_body (composer); set_signature_gui (composer); return composer; } static void disable_editor (EMsgComposer *composer) { GtkhtmlEditor *editor; GtkAction *action; g_return_if_fail (E_IS_MSG_COMPOSER (composer)); editor = GTKHTML_EDITOR (composer); gtkhtml_editor_run_command (editor, "editable-off"); action = GTKHTML_EDITOR_ACTION_EDIT_MENU (composer); gtk_action_set_sensitive (action, FALSE); action = GTKHTML_EDITOR_ACTION_FORMAT_MENU (composer); gtk_action_set_sensitive (action, FALSE); action = GTKHTML_EDITOR_ACTION_INSERT_MENU (composer); gtk_action_set_sensitive (action, FALSE); gtk_widget_set_sensitive (composer->priv->attachment_bar, FALSE); } /** * e_msg_composer_new_redirect: * @message: The message to use as the source * * Create a new message composer widget. * * Returns: A pointer to the newly created widget **/ EMsgComposer * e_msg_composer_new_redirect (CamelMimeMessage *message, const gchar *resent_from) { EMsgComposer *composer; EComposerHeaderTable *table; const gchar *subject; g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL); composer = e_msg_composer_new_with_message (message); table = e_msg_composer_get_header_table (composer); subject = camel_mime_message_get_subject (message); composer->priv->redirect = message; camel_object_ref (message); e_composer_header_table_set_account_name (table, resent_from); e_composer_header_table_set_subject (table, subject); disable_editor (composer); return composer; } /** * e_msg_composer_get_session: * @composer: an #EMsgComposer * * Returns the mail module's global #CamelSession instance. Calling * this function will load the mail module if it isn't already loaded. * * Returns: the mail module's #CamelSession **/ CamelSession * e_msg_composer_get_session (EMsgComposer *composer) { EShellModule *shell_module; CamelSession *session; EShell *shell; g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL); shell = e_shell_get_default (); shell_module = e_shell_get_module_by_name (shell, "mail"); session = g_object_get_data (G_OBJECT (shell_module), "session"); g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL); return session; } /** * e_msg_composer_send: * @composer: an #EMsgComposer * * Send the message in @composer. **/ void e_msg_composer_send (EMsgComposer *composer) { g_return_if_fail (E_IS_MSG_COMPOSER (composer)); g_signal_emit (composer, signals[SEND], 0); } /** * e_msg_composer_save_draft: * @composer: an #EMsgComposer * * Save the message in @composer to the selected account's Drafts folder. **/ void e_msg_composer_save_draft (EMsgComposer *composer) { GtkhtmlEditor *editor; g_return_if_fail (E_IS_MSG_COMPOSER (composer)); editor = GTKHTML_EDITOR (composer); g_signal_emit (composer, signals[SAVE_DRAFT], 0); /* XXX This should be elsewhere. */ gtkhtml_editor_set_changed (editor, FALSE); e_composer_autosave_set_saved (composer, FALSE); } static GList * add_recipients (GList *list, const gchar *recips) { CamelInternetAddress *cia; const gchar *name, *addr; gint num, i; cia = camel_internet_address_new (); num = camel_address_decode (CAMEL_ADDRESS (cia), recips); for (i = 0; i < num; i++) { if (camel_internet_address_get (cia, i, &name, &addr)) { EDestination *dest = e_destination_new (); e_destination_set_name (dest, name); e_destination_set_email (dest, addr); list = g_list_append (list, dest); } } return list; } static void handle_mailto (EMsgComposer *composer, const gchar *mailto) { EMsgComposerPrivate *priv = composer->priv; EComposerHeaderTable *table; GList *to = NULL, *cc = NULL, *bcc = NULL; EDestination **tov, **ccv, **bccv; gchar *subject = NULL, *body = NULL; gchar *header, *content, *buf; gsize nread, nwritten; const gchar *p; gint len, clen; CamelURL *url; table = e_msg_composer_get_header_table (composer); buf = g_strdup (mailto); /* Parse recipients (everything after ':' until '?' or eos). */ p = buf + 7; len = strcspn (p, "?"); if (len) { content = g_strndup (p, len); camel_url_decode (content); to = add_recipients (to, content); g_free (content); } p += len; if (*p == '?') { p++; while (*p) { len = strcspn (p, "=&"); /* If it's malformed, give up. */ if (p[len] != '=') break; header = (char *) p; header[len] = '\0'; p += len + 1; clen = strcspn (p, "&"); content = g_strndup (p, clen); if (!g_ascii_strcasecmp (header, "to")) { camel_url_decode (content); to = add_recipients (to, content); } else if (!g_ascii_strcasecmp (header, "cc")) { camel_url_decode (content); cc = add_recipients (cc, content); } else if (!g_ascii_strcasecmp (header, "bcc")) { camel_url_decode (content); bcc = add_recipients (bcc, content); } else if (!g_ascii_strcasecmp (header, "subject")) { g_free (subject); camel_url_decode (content); if (g_utf8_validate (content, -1, NULL)) { subject = content; content = NULL; } else { subject = g_locale_to_utf8 (content, clen, &nread, &nwritten, NULL); if (subject) { subject = g_realloc (subject, nwritten + 1); subject[nwritten] = '\0'; } } } else if (!g_ascii_strcasecmp (header, "body")) { g_free (body); camel_url_decode (content); if (g_utf8_validate (content, -1, NULL)) { body = content; content = NULL; } else { body = g_locale_to_utf8 (content, clen, &nread, &nwritten, NULL); if (body) { body = g_realloc (body, nwritten + 1); body[nwritten] = '\0'; } } } else if (!g_ascii_strcasecmp (header, "attach") || !g_ascii_strcasecmp (header, "attachment")) { /* Change file url to absolute path */ if (!g_ascii_strncasecmp (content, "file:", 5)) { url = camel_url_new (content, NULL); e_attachment_bar_attach (E_ATTACHMENT_BAR (priv->attachment_bar), url->path, "attachment"); camel_url_free (url); } else { e_attachment_bar_attach (E_ATTACHMENT_BAR (priv->attachment_bar), content, "attachment"); } gtk_widget_show (priv->attachment_expander); gtk_widget_show (priv->attachment_scrolled_window); } else if (!g_ascii_strcasecmp (header, "from")) { /* Ignore */ } else if (!g_ascii_strcasecmp (header, "reply-to")) { /* ignore */ } else { /* add an arbitrary header? */ camel_url_decode (content); e_msg_composer_add_header (composer, header, content); } g_free (content); p += clen; if (*p == '&') { p++; if (!g_ascii_strncasecmp (p, "amp;", 4)) p += 4; } } } g_free (buf); tov = destination_list_to_vector (to); ccv = destination_list_to_vector (cc); bccv = destination_list_to_vector (bcc); g_list_free (to); g_list_free (cc); g_list_free (bcc); e_composer_header_table_set_destinations_to (table, tov); e_composer_header_table_set_destinations_cc (table, ccv); e_composer_header_table_set_destinations_bcc (table, bccv); e_destination_freev (tov); e_destination_freev (ccv); e_destination_freev (bccv); e_composer_header_table_set_subject (table, subject); g_free (subject); if (body) { gchar *htmlbody; htmlbody = camel_text_to_html (body, CAMEL_MIME_FILTER_TOHTML_PRE, 0); set_editor_text (composer, htmlbody, FALSE); g_free (htmlbody); } } static void handle_uri (EMsgComposer *composer, const gchar *uri, gboolean html_dnd) { EMsgComposerPrivate *p = composer->priv; GtkhtmlEditor *editor; gboolean html_content; editor = GTKHTML_EDITOR (composer); html_content = gtkhtml_editor_get_html_mode (editor); if (!g_ascii_strncasecmp (uri, "mailto:", 7)) { handle_mailto (composer, uri); } else { CamelURL *url = camel_url_new (uri, NULL); gchar *type; if (!url) return; if (!g_ascii_strcasecmp (url->protocol, "file")) { type = e_util_guess_mime_type (uri + strlen ("file://"), TRUE); if (!type) return; if (strncmp (type, "image", 5) || !html_dnd || (!html_content && !strncmp (type, "image", 5))) { e_attachment_bar_attach (E_ATTACHMENT_BAR (p->attachment_bar), url->path, "attachment"); } g_free (type); } else { e_attachment_bar_attach_remote_file (E_ATTACHMENT_BAR (p->attachment_bar), uri, "attachment"); } camel_url_free (url); } } /** * e_msg_composer_new_from_url: * @url: a mailto URL * * Create a new message composer widget, and fill in fields as * defined by the provided URL. **/ EMsgComposer * e_msg_composer_new_from_url (const gchar *url) { EMsgComposer *composer; g_return_val_if_fail (g_ascii_strncasecmp (url, "mailto:", 7) == 0, NULL); composer = e_msg_composer_new (); if (!composer) return NULL; handle_mailto (composer, url); return composer; } /** * e_msg_composer_set_body_text: * @composer: a composer object * @text: the HTML text to initialize the editor with * * Loads the given HTML text into the editor. **/ void e_msg_composer_set_body_text (EMsgComposer *composer, const gchar *text, gssize len) { g_return_if_fail (E_IS_MSG_COMPOSER (composer)); g_return_if_fail (text != NULL); set_editor_text (composer, text, TRUE); } /** * e_msg_composer_set_body: * @composer: a composer object * @body: the data to initialize the composer with * @mime_type: the MIME type of data * * Loads the given data ginto the composer as the message body. * This function should only be used by the CORBA composer factory. **/ void e_msg_composer_set_body (EMsgComposer *composer, const gchar *body, const gchar *mime_type) { EMsgComposerPrivate *p = composer->priv; EComposerHeaderTable *table; g_return_if_fail (E_IS_MSG_COMPOSER (composer)); table = e_msg_composer_get_header_table (composer); set_editor_text (composer, _("(The composer contains a non-text message body, which cannot be edited.)"), FALSE); gtkhtml_editor_set_html_mode (GTKHTML_EDITOR (composer), FALSE); disable_editor (composer); g_free (p->mime_body); p->mime_body = g_strdup (body); g_free (p->mime_type); p->mime_type = g_strdup (mime_type); if (g_ascii_strncasecmp (p->mime_type, "text/calendar", 13) == 0) { EAccount *account; account = e_composer_header_table_get_account (table); if (account && account->pgp_no_imip_sign) { GtkToggleAction *action; action = GTK_TOGGLE_ACTION (ACTION (PGP_SIGN)); gtk_toggle_action_set_active (action, FALSE); } } } /** * e_msg_composer_add_header: * @composer: a composer object * @name: the header name * @value: the header value * * Adds a header with @name and @value to the message. This header * may not be displayed by the composer, but will be included in * the message it outputs. **/ void e_msg_composer_add_header (EMsgComposer *composer, const gchar *name, const gchar *value) { EMsgComposerPrivate *p = composer->priv; g_return_if_fail (E_IS_MSG_COMPOSER (composer)); g_return_if_fail (name != NULL); g_return_if_fail (value != NULL); g_ptr_array_add (p->extra_hdr_names, g_strdup (name)); g_ptr_array_add (p->extra_hdr_values, g_strdup (value)); } /** * e_msg_composer_modify_header : * @composer : a composer object * @name: the header name * @change_value: the header value to put in place of the previous * value * * Searches for a header with name=@name ,if found it removes * that header and adds a new header with the @name and @change_value . * If not found then it creates a new header with @name and @change_value . **/ void e_msg_composer_modify_header (EMsgComposer *composer, const gchar *name, const gchar *change_value) { g_return_if_fail (E_IS_MSG_COMPOSER (composer)); g_return_if_fail (name != NULL); g_return_if_fail (change_value != NULL); e_msg_composer_remove_header (composer, name); e_msg_composer_add_header (composer, name, change_value); } /** * e_msg_composer_modify_header : * @composer : a composer object * @name: the header name * * Searches for the header and if found it removes it . **/ void e_msg_composer_remove_header (EMsgComposer *composer, const gchar *name) { EMsgComposerPrivate *p; gint i; g_return_if_fail (E_IS_MSG_COMPOSER (composer)); g_return_if_fail (name != NULL); p = composer->priv; for (i = 0; i < p->extra_hdr_names->len; i++) { if (strcmp (p->extra_hdr_names->pdata[i], name) == 0) { g_ptr_array_remove_index (p->extra_hdr_names, i); g_ptr_array_remove_index (p->extra_hdr_values, i); } } } /** * e_msg_composer_attach: * @composer: a composer object * @attachment: the CamelMimePart to attach * * Attaches @attachment to the message being composed in the composer. **/ void e_msg_composer_attach (EMsgComposer *composer, CamelMimePart *attachment) { EAttachmentBar *bar; EMsgComposerPrivate *p = composer->priv; g_return_if_fail (E_IS_MSG_COMPOSER (composer)); g_return_if_fail (CAMEL_IS_MIME_PART (attachment)); bar = E_ATTACHMENT_BAR (p->attachment_bar); e_attachment_bar_attach_mime_part (bar, attachment); } /** * e_msg_composer_add_inline_image_from_file: * @composer: a composer object * @filename: the name of the file containing the image * * This reads in the image in @filename and adds it to @composer * as an inline image, to be wrapped in a multipart/related. * * Returns: the newly-created CamelMimePart (which must be reffed * if the caller wants to keep its own reference), or %NULL on error. **/ CamelMimePart * e_msg_composer_add_inline_image_from_file (EMsgComposer *composer, const gchar *filename) { gchar *mime_type, *cid, *url, *name, *dec_file_name; CamelStream *stream; CamelDataWrapper *wrapper; CamelMimePart *part; EMsgComposerPrivate *p = composer->priv; dec_file_name = g_strdup (filename); camel_url_decode (dec_file_name); if (!g_file_test (dec_file_name, G_FILE_TEST_IS_REGULAR)) return NULL; stream = camel_stream_fs_new_with_name (dec_file_name, O_RDONLY, 0); if (!stream) return NULL; wrapper = camel_data_wrapper_new (); camel_data_wrapper_construct_from_stream (wrapper, stream); camel_object_unref (CAMEL_OBJECT (stream)); mime_type = e_util_guess_mime_type (dec_file_name, TRUE); if (mime_type == NULL) mime_type = g_strdup ("application/octet-stream"); camel_data_wrapper_set_mime_type (wrapper, mime_type); g_free (mime_type); part = camel_mime_part_new (); camel_medium_set_content_object (CAMEL_MEDIUM (part), wrapper); camel_object_unref (wrapper); cid = camel_header_msgid_generate (); camel_mime_part_set_content_id (part, cid); name = g_path_get_basename (dec_file_name); camel_mime_part_set_filename (part, name); g_free (name); camel_mime_part_set_encoding (part, CAMEL_TRANSFER_ENCODING_BASE64); url = g_strdup_printf ("file:%s", dec_file_name); g_hash_table_insert (p->inline_images_by_url, url, part); url = g_strdup_printf ("cid:%s", cid); g_hash_table_insert (p->inline_images, url, part); g_free (cid); g_free (dec_file_name); return part; } /** * e_msg_composer_add_inline_image_from_mime_part: * @composer: a composer object * @part: a CamelMimePart containing image data * * This adds the mime part @part to @composer as an inline image, to * be wrapped in a multipart/related. **/ void e_msg_composer_add_inline_image_from_mime_part (EMsgComposer *composer, CamelMimePart *part) { gchar *url; const gchar *location, *cid; EMsgComposerPrivate *p = composer->priv; cid = camel_mime_part_get_content_id (part); if (!cid) { camel_mime_part_set_content_id (part, NULL); cid = camel_mime_part_get_content_id (part); } url = g_strdup_printf ("cid:%s", cid); g_hash_table_insert (p->inline_images, url, part); camel_object_ref (part); location = camel_mime_part_get_content_location (part); if (location != NULL) g_hash_table_insert ( p->inline_images_by_url, g_strdup (location), part); } /** * e_msg_composer_get_message: * @composer: A message composer widget * * Retrieve the message edited by the user as a CamelMimeMessage. The * CamelMimeMessage object is created on the fly; subsequent calls to this * function will always create new objects from scratch. * * Returns: A pointer to the new CamelMimeMessage object **/ CamelMimeMessage * e_msg_composer_get_message (EMsgComposer *composer, gboolean save_html_object_data) { GtkhtmlEditor *editor; gboolean html_content; g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL); if (e_msg_composer_get_remote_download_count (composer) != 0) { if (!em_utils_prompt_user (GTK_WINDOW (composer), NULL, "mail-composer:ask-send-message-pending-download", NULL)) { return NULL; } } editor = GTKHTML_EDITOR (composer); html_content = gtkhtml_editor_get_html_mode (editor); return build_message (composer, html_content, save_html_object_data); } static gchar * msg_composer_get_message_print_helper (EMsgComposer *composer, gboolean html_content) { GtkToggleAction *action; GString *string; string = g_string_sized_new (128); if (html_content) g_string_append (string, "text/html"); else g_string_append (string, "text/plain"); action = GTK_TOGGLE_ACTION (ACTION (PGP_SIGN)); if (gtk_toggle_action_get_active (action)) g_string_append (string, ", pgp-sign"); gtk_toggle_action_set_active (action, FALSE); action = GTK_TOGGLE_ACTION (ACTION (PGP_ENCRYPT)); if (gtk_toggle_action_get_active (action)) g_string_append (string, ", pgp-encrypt"); gtk_toggle_action_set_active (action, FALSE); action = GTK_TOGGLE_ACTION (ACTION (SMIME_SIGN)); if (gtk_toggle_action_get_active (action)) g_string_append (string, ", smime-sign"); gtk_toggle_action_set_active (action, FALSE); action = GTK_TOGGLE_ACTION (ACTION (SMIME_ENCRYPT)); if (gtk_toggle_action_get_active (action)) g_string_append (string, ", smime-encrypt"); gtk_toggle_action_set_active (action, FALSE); return g_string_free (string, FALSE); } CamelMimeMessage * e_msg_composer_get_message_print (EMsgComposer *composer, gboolean save_html_object_data) { GtkhtmlEditor *editor; EMsgComposer *temp_composer; CamelMimeMessage *msg; gboolean html_content; gchar *flags; editor = GTKHTML_EDITOR (composer); html_content = gtkhtml_editor_get_html_mode (editor); msg = build_message (composer, html_content, save_html_object_data); if (msg == NULL) return NULL; temp_composer = e_msg_composer_new_with_message (msg); camel_object_unref (msg); /* Override composer flags. */ flags = msg_composer_get_message_print_helper ( temp_composer, html_content); msg = build_message (temp_composer, TRUE, save_html_object_data); if (msg != NULL) camel_medium_set_header ( CAMEL_MEDIUM (msg), "X-Evolution-Format", flags); gtk_widget_destroy (GTK_WIDGET (temp_composer)); g_free (flags); return msg; } CamelMimeMessage * e_msg_composer_get_message_draft (EMsgComposer *composer) { GtkhtmlEditor *editor; EComposerHeaderTable *table; GtkToggleAction *action; CamelMimeMessage *msg; EAccount *account; gboolean html_content; gboolean pgp_encrypt; gboolean pgp_sign; gboolean smime_encrypt; gboolean smime_sign; GString *flags; editor = GTKHTML_EDITOR (composer); table = e_msg_composer_get_header_table (composer); html_content = gtkhtml_editor_get_html_mode (editor); action = GTK_TOGGLE_ACTION (ACTION (PGP_SIGN)); pgp_sign = gtk_toggle_action_get_active (action); gtk_toggle_action_set_active (action, FALSE); action = GTK_TOGGLE_ACTION (ACTION (PGP_ENCRYPT)); pgp_encrypt = gtk_toggle_action_get_active (action); gtk_toggle_action_set_active (action, FALSE); action = GTK_TOGGLE_ACTION (ACTION (SMIME_SIGN)); smime_sign = gtk_toggle_action_get_active (action); gtk_toggle_action_set_active (action, FALSE); action = GTK_TOGGLE_ACTION (ACTION (SMIME_ENCRYPT)); smime_encrypt = gtk_toggle_action_get_active (action); gtk_toggle_action_set_active (action, FALSE); msg = build_message (composer, TRUE, TRUE); if (msg == NULL) return NULL; action = GTK_TOGGLE_ACTION (ACTION (PGP_SIGN)); gtk_toggle_action_set_active (action, pgp_sign); action = GTK_TOGGLE_ACTION (ACTION (PGP_ENCRYPT)); gtk_toggle_action_set_active (action, pgp_encrypt); action = GTK_TOGGLE_ACTION (ACTION (SMIME_SIGN)); gtk_toggle_action_set_active (action, smime_sign); action = GTK_TOGGLE_ACTION (ACTION (SMIME_ENCRYPT)); gtk_toggle_action_set_active (action, smime_encrypt); if (msg == NULL) return NULL; /* Attach account info to the draft. */ account = e_composer_header_table_get_account (table); if (account && account->name) camel_medium_set_header ( CAMEL_MEDIUM (msg), "X-Evolution-Account", account->uid); flags = g_string_new (html_content ? "text/html" : "text/plain"); /* This should probably only save the setting if it is * different from the from-account default? */ if (pgp_sign) g_string_append (flags, ", pgp-sign"); if (pgp_encrypt) g_string_append (flags, ", pgp-encrypt"); if (smime_sign) g_string_append (flags, ", smime-sign"); if (smime_encrypt) g_string_append (flags, ", smime-encrypt"); camel_medium_set_header ( CAMEL_MEDIUM (msg), "X-Evolution-Format", flags->str); g_string_free (flags, TRUE); return msg; } /** * e_msg_composer_show_sig: * @composer: A message composer widget * * Set a signature **/ void e_msg_composer_show_sig_file (EMsgComposer *composer) { GtkhtmlEditor *editor; GtkHTML *html; gchar *html_text; g_return_if_fail (E_IS_MSG_COMPOSER (composer)); editor = GTKHTML_EDITOR (composer); html = gtkhtml_editor_get_html (editor); if (composer->priv->redirect) return; composer->priv->in_signature_insert = TRUE; gtkhtml_editor_freeze (editor); gtkhtml_editor_run_command (editor, "cursor-position-save"); gtkhtml_editor_undo_begin (editor, "Set signature", "Reset signature"); /* Delete the old signature. */ gtkhtml_editor_run_command (editor, "block-selection"); gtkhtml_editor_run_command (editor, "cursor-bod"); if (gtkhtml_editor_search_by_data (editor, 1, "ClueFlow", "signature", "1")) { gtkhtml_editor_run_command (editor, "select-paragraph"); gtkhtml_editor_run_command (editor, "delete"); gtkhtml_editor_set_paragraph_data (editor, "signature", "0"); gtkhtml_editor_run_command (editor, "delete-back"); } gtkhtml_editor_run_command (editor, "unblock-selection"); html_text = get_signature_html (composer); if (html_text) { gtkhtml_editor_run_command (editor, "insert-paragraph"); if (!gtkhtml_editor_run_command (editor, "cursor-backward")) gtkhtml_editor_run_command (editor, "insert-paragraph"); else gtkhtml_editor_run_command (editor, "cursor-forward"); gtkhtml_editor_set_paragraph_data (editor, "orig", "0"); gtkhtml_editor_run_command (editor, "indent-zero"); gtkhtml_editor_run_command (editor, "style-normal"); gtkhtml_editor_insert_html (editor, html_text); g_free (html_text); } gtkhtml_editor_undo_end (editor); gtkhtml_editor_run_command (editor, "cursor-position-restore"); gtkhtml_editor_thaw (editor); composer->priv->in_signature_insert = FALSE; } CamelInternetAddress * e_msg_composer_get_from (EMsgComposer *composer) { CamelInternetAddress *address; EComposerHeaderTable *table; EAccount *account; g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL); table = e_msg_composer_get_header_table (composer); account = e_composer_header_table_get_account (table); if (account == NULL) return NULL; address = camel_internet_address_new (); camel_internet_address_add ( address, account->id->name, account->id->address); return address; } CamelInternetAddress * e_msg_composer_get_reply_to (EMsgComposer *composer) { CamelInternetAddress *address; EComposerHeaderTable *table; const gchar *reply_to; g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL); table = e_msg_composer_get_header_table (composer); reply_to = e_composer_header_table_get_reply_to (table); if (reply_to == NULL || *reply_to == '\0') return NULL; address = camel_internet_address_new (); if (camel_address_unformat (CAMEL_ADDRESS (address), reply_to) == -1) { camel_object_unref (CAMEL_OBJECT (address)); return NULL; } return address; } /** * e_msg_composer_get_raw_message_text: * * Returns the text/plain of the message from composer **/ GByteArray * e_msg_composer_get_raw_message_text (EMsgComposer *composer) { GtkhtmlEditor *editor; GByteArray *array; gchar *text; gsize length; g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL); array = g_byte_array_new (); editor = GTKHTML_EDITOR (composer); text = gtkhtml_editor_get_text_plain (editor, &length); g_byte_array_append (array, (guint8 *) text, (guint) length); g_free (text); return array; } EAttachmentBar * e_msg_composer_get_attachment_bar (EMsgComposer *composer) { g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL); return E_ATTACHMENT_BAR (composer->priv->attachment_bar); } void e_msg_composer_set_enable_autosave (EMsgComposer *composer, gboolean enabled) { e_composer_autosave_set_enabled (composer, enabled); } gboolean e_msg_composer_is_exiting (EMsgComposer *composer) { g_return_val_if_fail (composer != NULL, FALSE); return composer->priv->application_exiting; } gboolean e_msg_composer_request_close_all (void) { GSList *iter, *next; for (iter = all_composers; iter != NULL; iter = next) { EMsgComposer *composer = iter->data; /* The CLOSE action will delete this list node, * so grab the next one while we still can. */ next = iter->next; /* Try to autosave before closing. If it fails for * some reason, the CLOSE action will still detect * unsaved changes and prompt the user. * * FIXME If it /does/ prompt the user, the Cancel * button will act the same as Discard Changes, * which is misleading. */ composer->priv->application_exiting = TRUE; e_composer_autosave_snapshot (composer); gtk_action_activate (ACTION (CLOSE)); } return (all_composers == NULL); } EMsgComposer * e_msg_composer_load_from_file (const gchar *filename) { CamelStream *stream; CamelMimeMessage *msg; EMsgComposer *composer; g_return_val_if_fail (filename != NULL, NULL); stream = camel_stream_fs_new_with_name (filename, O_RDONLY, 0); if (stream == NULL) return NULL; msg = camel_mime_message_new (); camel_data_wrapper_construct_from_stream ( CAMEL_DATA_WRAPPER (msg), stream); camel_object_unref (stream); composer = e_msg_composer_new_with_message (msg); if (composer != NULL) { g_signal_connect ( composer, "send", G_CALLBACK (em_utils_composer_send_cb), NULL); g_signal_connect ( composer, "save-draft", G_CALLBACK (em_utils_composer_save_draft_cb), NULL); gtk_widget_show (GTK_WIDGET (composer)); } return composer; } void e_msg_composer_check_autosave (GtkWindow *parent) { GList *orphans = NULL; gint response; GError *error = NULL; /* Look for orphaned autosave files. */ orphans = e_composer_autosave_find_orphans (&error); if (orphans == NULL) { if (error != NULL) { g_warning ("%s", error->message); g_error_free (error); } return; } /* Ask if the user wants to recover the orphaned files. */ response = e_error_run ( parent, "mail-composer:recover-autosave", NULL); /* Based on the user's response, recover or delete them. */ while (orphans != NULL) { const gchar *filename = orphans->data; EMsgComposer *composer; if (response == GTK_RESPONSE_YES) { /* FIXME: composer is never used */ composer = autosave_load_draft (filename); } else { g_unlink (filename); } g_free (orphans->data); orphans = g_list_delete_link (orphans, orphans); } } void e_msg_composer_set_alternative (EMsgComposer *composer, gboolean alt) { GtkhtmlEditor *editor; g_return_if_fail (E_IS_MSG_COMPOSER (composer)); editor = GTKHTML_EDITOR (composer); composer->priv->is_alternative = alt; gtkhtml_editor_set_html_mode (editor, !alt); } void e_msg_composer_reply_indent (EMsgComposer *composer) { GtkhtmlEditor *editor; g_return_if_fail (E_IS_MSG_COMPOSER (composer)); editor = GTKHTML_EDITOR (composer); if (!gtkhtml_editor_is_paragraph_empty (editor)) { if (gtkhtml_editor_is_previous_paragraph_empty (editor)) gtkhtml_editor_run_command (editor, "cursor-backward"); else { gtkhtml_editor_run_command (editor, "text-default-color"); gtkhtml_editor_run_command (editor, "italic-off"); gtkhtml_editor_run_command (editor, "insert-paragraph"); return; } } gtkhtml_editor_run_command (editor, "style-normal"); gtkhtml_editor_run_command (editor, "indent-zero"); gtkhtml_editor_run_command (editor, "text-default-color"); gtkhtml_editor_run_command (editor, "italic-off"); } EComposerHeaderTable * e_msg_composer_get_header_table (EMsgComposer *composer) { g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL); return E_COMPOSER_HEADER_TABLE (composer->priv->header_table); } void e_msg_composer_set_send_options (EMsgComposer *composer, gboolean send_enable) { g_return_if_fail (E_IS_MSG_COMPOSER (composer)); composer->priv->send_invoked = send_enable; } GList * e_load_spell_languages (void) { GConfClient *client; GList *spell_languages = NULL; GSList *list; const gchar *key; GError *error = NULL; /* Ask GConf for a list of spell check language codes. */ client = gconf_client_get_default (); key = COMPOSER_GCONF_SPELL_LANGUAGES_KEY; list = gconf_client_get_list (client, key, GCONF_VALUE_STRING, &error); g_object_unref (client); /* Convert the codes to spell language structs. */ while (list != NULL) { gchar *language_code = list->data; const GtkhtmlSpellLanguage *language; language = gtkhtml_spell_language_lookup (language_code); if (language != NULL) spell_languages = g_list_prepend ( spell_languages, (gpointer) language); list = g_slist_delete_link (list, list); g_free (language_code); } spell_languages = g_list_reverse (spell_languages); /* Pick a default spell language if GConf came back empty. */ if (spell_languages == NULL) { const GtkhtmlSpellLanguage *language; language = gtkhtml_spell_language_lookup (NULL); if (language) { spell_languages = g_list_prepend ( spell_languages, (gpointer) language); /* Don't overwrite the stored spell check language * codes if there was a problem retrieving them. */ if (error == NULL) e_save_spell_languages (spell_languages); } } if (error != NULL) { g_warning ("%s", error->message); g_error_free (error); } return spell_languages; } void e_save_spell_languages (GList *spell_languages) { GConfClient *client; GSList *list = NULL; const gchar *key; GError *error = NULL; /* Build a list of spell check language codes. */ while (spell_languages != NULL) { const GtkhtmlSpellLanguage *language; const gchar *language_code; language = spell_languages->data; language_code = gtkhtml_spell_language_get_code (language); list = g_slist_prepend (list, (gpointer) language_code); spell_languages = g_list_next (spell_languages); } list = g_slist_reverse (list); /* Save the language codes to GConf. */ client = gconf_client_get_default (); key = COMPOSER_GCONF_SPELL_LANGUAGES_KEY; gconf_client_set_list (client, key, GCONF_VALUE_STRING, list, &error); g_object_unref (client); g_slist_free (list); if (error != NULL) { g_warning ("%s", error->message); g_error_free (error); } }