Files
evolution/composer/e-msg-composer.c
Matthew Barnes c881b5bc5e Simplify EActivity.
With unintrusive error dialogs gone, we can cut some unnecessary bits
out of EActivity.

I'm also adding a new enum property called "state", which is one of:

    E_ACTIVITY_RUNNING
    E_ACTIVITY_WAITING
    E_ACTIVITY_CANCELLED
    E_ACTIVITY_COMPLETED

The state of an activity must be explicitly changed.  In particular,
when the user cancels an activity the state should be set only after
confirming the operation has been cancelled and not when cancellation
is requested (e.g. after receiving a G_IO_ERROR_CANCELLED, not when
the GCancellable emits "cancelled").  EActivityBar and EActivityProxy
widgets have been updated to make this distinction clearer in the UI.

E_ACTIVITY_WAITING will be used when activities have to be queued and
dispatched in sequence, which I haven't written yet.
2010-10-22 14:21:22 -04:00

4820 lines
130 KiB
C

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* 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 <http://www.gnu.org/licenses/>
*
*
* 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)
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>
#include <ctype.h>
#include <fcntl.h>
#include "e-util/e-account-utils.h"
#include "e-util/e-alert-dialog.h"
#include "e-util/e-dialog-utils.h"
#include "e-util/e-signature-utils.h"
#include "e-util/e-util-private.h"
#include "em-format/em-format.h"
#include "em-format/em-format-quote.h"
#include "e-composer-private.h"
typedef struct _AsyncContext AsyncContext;
struct _AsyncContext {
EActivity *activity;
CamelMimeMessage *message;
CamelDataWrapper *top_level_part;
CamelDataWrapper *text_plain_part;
EAccount *account;
CamelSession *session;
CamelInternetAddress *from;
CamelTransferEncoding plain_encoding;
GtkPrintOperationAction print_action;
GPtrArray *recipients;
guint skip_content : 1;
guint need_thread : 1;
guint pgp_sign : 1;
guint pgp_encrypt : 1;
guint smime_sign : 1;
guint smime_encrypt : 1;
};
/* Flags for building a message. */
typedef enum {
COMPOSER_FLAG_HTML_CONTENT = 1 << 0,
COMPOSER_FLAG_SAVE_OBJECT_DATA = 1 << 1,
COMPOSER_FLAG_PRIORITIZE_MESSAGE = 1 << 2,
COMPOSER_FLAG_REQUEST_READ_RECEIPT = 1 << 3,
COMPOSER_FLAG_PGP_SIGN = 1 << 4,
COMPOSER_FLAG_PGP_ENCRYPT = 1 << 5,
COMPOSER_FLAG_SMIME_SIGN = 1 << 6,
COMPOSER_FLAG_SMIME_ENCRYPT = 1 << 7
} ComposerFlags;
enum {
PROP_0,
PROP_FOCUS_TRACKER,
PROP_SHELL
};
enum {
PRESEND,
SEND,
SAVE_DRAFT,
PRINT,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL];
/* 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,
GCancellable *cancellable,
gint depth);
static void handle_multipart_alternative (EMsgComposer *composer,
CamelMultipart *multipart,
GCancellable *cancellable,
gint depth);
static void handle_multipart_encrypted (EMsgComposer *composer,
CamelMimePart *multipart,
GCancellable *cancellable,
gint depth);
static void handle_multipart_signed (EMsgComposer *composer,
CamelMultipart *multipart,
GCancellable *cancellable,
gint depth);
static void e_msg_composer_alert_sink_init (EAlertSinkInterface *interface);
G_DEFINE_TYPE_WITH_CODE (
EMsgComposer,
e_msg_composer,
GTKHTML_TYPE_EDITOR,
G_IMPLEMENT_INTERFACE (
E_TYPE_ALERT_SINK, e_msg_composer_alert_sink_init)
G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE, NULL))
static void
async_context_free (AsyncContext *context)
{
if (context->activity != NULL)
g_object_unref (context->activity);
if (context->message != NULL)
g_object_unref (context->message);
if (context->top_level_part != NULL)
g_object_unref (context->top_level_part);
if (context->text_plain_part != NULL)
g_object_unref (context->text_plain_part);
if (context->account != NULL)
g_object_unref (context->account);
if (context->session != NULL)
g_object_unref (context->session);
if (context->from != NULL)
g_object_unref (context->from);
if (context->recipients != NULL)
g_ptr_array_free (context->recipients, TRUE);
g_slice_free (AsyncContext, context);
}
/**
* emcu_part_to_html:
* @part:
*
* Converts a mime part's contents into html text. If @credits is given,
* then it will be used as an attribution string, and the
* content will be cited. Otherwise no citation or attribution
* will be performed.
*
* Return Value: The part in displayable html format.
**/
static gchar *
emcu_part_to_html (CamelMimePart *part,
gssize *len,
EMFormat *source,
GCancellable *cancellable)
{
EMFormatQuote *emfq;
CamelStreamMem *mem;
GByteArray *buf;
gchar *text;
buf = g_byte_array_new ();
mem = (CamelStreamMem *) camel_stream_mem_new ();
camel_stream_mem_set_byte_array (mem, buf);
emfq = em_format_quote_new (NULL, (CamelStream *)mem, EM_FORMAT_QUOTE_KEEP_SIG);
((EMFormat *) emfq)->composer = TRUE;
if (source) {
/* Copy over things we can, other things are internal.
* XXX Perhaps need different api than 'clone'. */
if (source->default_charset)
em_format_set_default_charset (
(EMFormat *) emfq, source->default_charset);
if (source->charset)
em_format_set_default_charset (
(EMFormat *) emfq, source->charset);
}
em_format_part (
EM_FORMAT (emfq), CAMEL_STREAM (mem), part, cancellable);
g_object_unref (emfq);
camel_stream_write((CamelStream *) mem, "", 1, cancellable, NULL);
g_object_unref (mem);
text = (gchar *)buf->data;
if (len)
*len = buf->len-1;
g_byte_array_free (buf, FALSE);
return text;
}
/* copy of mail_tool_remove_xevolution_headers */
static struct _camel_header_raw *
emcu_remove_xevolution_headers (CamelMimeMessage *message)
{
struct _camel_header_raw *scan, *list = NULL;
for (scan = ((CamelMimePart *)message)->headers;scan;scan=scan->next)
if (!strncmp(scan->name, "X-Evolution", 11))
camel_header_raw_append (&list, scan->name, scan->value, scan->offset);
for (scan=list;scan;scan=scan->next)
camel_medium_remove_header ((CamelMedium *)message, scan->name);
return list;
}
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 gboolean
text_requires_quoted_printable (const gchar *text, gsize len)
{
const gchar *p;
gsize pos;
if (!text)
return FALSE;
if (len == -1)
len = strlen (text);
if (len >= 5 && strncmp (text, "From ", 5) == 0)
return TRUE;
for (p = text, pos = 0; pos + 6 <= len; pos++, p++) {
if (*p == '\n' && strncmp (p + 1, "From ", 5) == 0)
return TRUE;
}
return FALSE;
}
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) && (buf->len < LINE_LEN) &&
!text_requires_quoted_printable (
(const gchar *) buf->data, buf->len))
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 = (gchar *) 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;
}
if (camel_address_decode (CAMEL_ADDRESS (target), text_addr) <= 0)
camel_internet_address_add (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;
}
if (camel_address_decode (CAMEL_ADDRESS (target), text_addr) <= 0)
camel_internet_address_add (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) {
if (camel_address_decode (CAMEL_ADDRESS (bcc_addr), text_addr) <= 0)
camel_internet_address_add (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);
}
g_object_unref (to_addr);
g_object_unref (cc_addr);
g_object_unref (bcc_addr);
}
static void
build_message_headers (EMsgComposer *composer,
CamelMimeMessage *msg,
gboolean redirect)
{
EComposerHeaderTable *table;
EComposerHeader *header;
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);
g_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);
g_object_unref (addr);
}
/* To:, Cc:, Bcc: */
header = e_composer_header_table_get_header (
table, E_COMPOSER_HEADER_TO);
if (e_composer_header_get_visible (header)) {
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: */
header = e_composer_header_table_get_header (
table, E_COMPOSER_HEADER_POST_TO);
if (e_composer_header_get_visible (header)) {
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 CamelCipherHash
account_hash_algo_to_camel_hash (const gchar *hash_algo)
{
CamelCipherHash res = CAMEL_CIPHER_HASH_DEFAULT;
if (hash_algo && *hash_algo) {
if (g_ascii_strcasecmp (hash_algo, "sha1") == 0)
res = CAMEL_CIPHER_HASH_SHA1;
else if (g_ascii_strcasecmp (hash_algo, "sha256") == 0)
res = CAMEL_CIPHER_HASH_SHA256;
else if (g_ascii_strcasecmp (hash_algo, "sha384") == 0)
res = CAMEL_CIPHER_HASH_SHA384;
else if (g_ascii_strcasecmp (hash_algo, "sha512") == 0)
res = CAMEL_CIPHER_HASH_SHA512;
}
return res;
}
static void
composer_add_charset_filter (CamelStream *stream,
const gchar *charset)
{
CamelMimeFilter *filter;
filter = camel_mime_filter_charset_new ("UTF-8", charset);
camel_stream_filter_add (CAMEL_STREAM_FILTER (stream), filter);
g_object_unref (filter);
}
static void
composer_add_quoted_printable_filter (CamelStream *stream)
{
CamelMimeFilter *filter;
filter = camel_mime_filter_basic_new (CAMEL_MIME_FILTER_BASIC_QP_ENC);
camel_stream_filter_add (CAMEL_STREAM_FILTER (stream), filter);
g_object_unref (filter);
filter = camel_mime_filter_canon_new (CAMEL_MIME_FILTER_CANON_FROM);
camel_stream_filter_add (CAMEL_STREAM_FILTER (stream), filter);
g_object_unref (filter);
}
/* Helper for composer_build_message_thread() */
static gboolean
composer_build_message_pgp (AsyncContext *context,
GCancellable *cancellable,
GError **error)
{
CamelCipherContext *cipher;
CamelDataWrapper *content;
CamelMimePart *mime_part;
const gchar *pgp_userid;
gboolean have_pgp_key;
/* Return silently if we're not signing or encrypting with PGP. */
if (!context->pgp_sign && !context->pgp_encrypt)
return TRUE;
have_pgp_key =
(context->account != NULL) &&
(context->account->pgp_key != NULL) &&
(context->account->pgp_key[0] != '\0');
mime_part = camel_mime_part_new ();
camel_medium_set_content (
CAMEL_MEDIUM (mime_part),
context->top_level_part);
if (context->top_level_part == context->text_plain_part)
camel_mime_part_set_encoding (
mime_part, context->plain_encoding);
g_object_unref (context->top_level_part);
context->top_level_part = NULL;
if (have_pgp_key)
pgp_userid = context->account->pgp_key;
else
camel_internet_address_get (
context->from, 0, NULL, &pgp_userid);
if (context->pgp_sign) {
CamelMimePart *npart;
gboolean success;
npart = camel_mime_part_new ();
cipher = camel_gpg_context_new (context->session);
if (context->account != NULL)
camel_gpg_context_set_always_trust (
CAMEL_GPG_CONTEXT (cipher),
context->account->pgp_always_trust);
success = camel_cipher_context_sign_sync (
cipher, pgp_userid,
account_hash_algo_to_camel_hash (
(context->account != NULL) ?
e_account_get_string (context->account,
E_ACCOUNT_PGP_HASH_ALGORITHM) : NULL),
mime_part, npart, cancellable, error);
g_object_unref (cipher);
g_object_unref (mime_part);
if (!success) {
g_object_unref (npart);
return FALSE;
}
mime_part = npart;
}
if (context->pgp_encrypt) {
CamelMimePart *npart;
gboolean encrypt_to_self;
gboolean success;
encrypt_to_self =
(context->account != NULL) &&
(context->account->pgp_encrypt_to_self) &&
(pgp_userid != NULL);
npart = camel_mime_part_new ();
/* Check to see if we should encrypt to self.
* NB gets removed immediately after use */
if (encrypt_to_self)
g_ptr_array_add (
context->recipients,
g_strdup (pgp_userid));
cipher = camel_gpg_context_new (context->session);
if (context->account != NULL)
camel_gpg_context_set_always_trust (
CAMEL_GPG_CONTEXT (cipher),
context->account->pgp_always_trust);
success = camel_cipher_context_encrypt_sync (
cipher, pgp_userid, context->recipients,
mime_part, npart, cancellable, error);
g_object_unref (cipher);
if (encrypt_to_self)
g_ptr_array_set_size (
context->recipients,
context->recipients->len - 1);
g_object_unref (mime_part);
if (!success) {
g_object_unref (npart);
return FALSE;
}
mime_part = npart;
}
content = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
context->top_level_part = g_object_ref (content);
g_object_unref (mime_part);
return TRUE;
}
#ifdef HAVE_SSL
static gboolean
composer_build_message_smime (AsyncContext *context,
GCancellable *cancellable,
GError **error)
{
CamelCipherContext *cipher;
CamelMimePart *mime_part;
gboolean have_signing_certificate;
gboolean have_encryption_certificate;
/* Return silently if we're not signing or encrypting with S/MIME. */
if (!context->smime_sign && !context->smime_encrypt)
return TRUE;
have_signing_certificate =
(context->account != NULL) &&
(context->account->smime_sign_key != NULL) &&
(context->account->smime_sign_key[0] != '\0');
have_encryption_certificate =
(context->account != NULL) &&
(context->account->smime_encrypt_key != NULL) &&
(context->account->smime_encrypt_key[0] != '\0');
if (context->smime_sign && !have_signing_certificate) {
g_set_error (
error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
_("Cannot sign outgoing message: "
"No signing certificate set for "
"this account"));
return FALSE;
}
if (context->smime_encrypt && !have_encryption_certificate) {
g_set_error (
error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
_("Cannot encrypt outgoing message: "
"No encryption certificate set for "
"this account"));
return FALSE;
}
mime_part = camel_mime_part_new ();
camel_medium_set_content (
CAMEL_MEDIUM (mime_part),
context->top_level_part);
if (context->top_level_part == context->text_plain_part)
camel_mime_part_set_encoding (
mime_part, context->plain_encoding);
g_object_unref (context->top_level_part);
context->top_level_part = NULL;
if (context->smime_sign) {
CamelMimePart *npart;
gboolean success;
npart = camel_mime_part_new ();
cipher = camel_smime_context_new (context->session);
/* if we're also encrypting, envelope-sign rather than clear-sign */
if (context->smime_encrypt) {
camel_smime_context_set_sign_mode (
(CamelSMIMEContext *) cipher,
CAMEL_SMIME_SIGN_ENVELOPED);
camel_smime_context_set_encrypt_key (
(CamelSMIMEContext *) cipher, TRUE,
context->account->smime_encrypt_key);
} else if (have_encryption_certificate) {
camel_smime_context_set_encrypt_key (
(CamelSMIMEContext *) cipher, TRUE,
context->account->smime_encrypt_key);
}
success = camel_cipher_context_sign_sync (
cipher, context->account->smime_sign_key,
account_hash_algo_to_camel_hash (
(context->account != NULL) ?
e_account_get_string (context->account,
E_ACCOUNT_SMIME_HASH_ALGORITHM) : NULL),
mime_part, npart, cancellable, error);
g_object_unref (cipher);
g_object_unref (mime_part);
if (!success) {
g_object_unref (npart);
return FALSE;
}
mime_part = npart;
}
if (context->smime_encrypt) {
gboolean success;
/* check to see if we should encrypt to self, NB removed after use */
if (context->account->smime_encrypt_to_self)
g_ptr_array_add (
context->recipients, g_strdup (
context->account->smime_encrypt_key));
cipher = camel_smime_context_new (context->session);
camel_smime_context_set_encrypt_key (
(CamelSMIMEContext *) cipher, TRUE,
context->account->smime_encrypt_key);
success = camel_cipher_context_encrypt_sync (
cipher, NULL,
context->recipients, mime_part,
CAMEL_MIME_PART (context->message),
cancellable, error);
g_object_unref (cipher);
if (!success)
return FALSE;
if (context->account->smime_encrypt_to_self)
g_ptr_array_set_size (
context->recipients,
context->recipients->len - 1);
}
/* we replaced the message directly, we don't want to do reparenting foo */
if (context->smime_encrypt) {
context->skip_content = TRUE;
} else {
CamelDataWrapper *content;
content = camel_medium_get_content (
CAMEL_MEDIUM (mime_part));
context->top_level_part = g_object_ref (content);
}
g_object_unref (mime_part);
return TRUE;
}
#endif
static void
composer_build_message_thread (GSimpleAsyncResult *simple,
EMsgComposer *composer,
GCancellable *cancellable)
{
AsyncContext *context;
GError *error = NULL;
context = g_simple_async_result_get_op_res_gpointer (simple);
/* Setup working recipient list if we're encrypting. */
if (context->pgp_encrypt || context->smime_encrypt) {
gint ii, jj;
const gchar *types[] = {
CAMEL_RECIPIENT_TYPE_TO,
CAMEL_RECIPIENT_TYPE_CC,
CAMEL_RECIPIENT_TYPE_BCC
};
context->recipients = g_ptr_array_new_with_free_func (
(GDestroyNotify) g_free);
for (ii = 0; ii < G_N_ELEMENTS (types); ii++) {
CamelInternetAddress *addr;
const gchar *address;
addr = camel_mime_message_get_recipients (
context->message, types[ii]);
for (jj = 0; camel_internet_address_get (addr, jj, NULL, &address); jj++)
g_ptr_array_add (
context->recipients,
g_strdup (address));
}
}
if (!composer_build_message_pgp (context, cancellable, &error)) {
g_simple_async_result_set_from_error (simple, error);
g_error_free (error);
return;
}
#if defined (HAVE_NSS)
if (!composer_build_message_smime (context, cancellable, &error)) {
g_simple_async_result_set_from_error (simple, error);
g_error_free (error);
return;
}
#endif /* HAVE_NSS */
}
static void
composer_add_evolution_format_header (CamelMedium *medium,
ComposerFlags flags)
{
GString *string;
string = g_string_sized_new (128);
if (flags & COMPOSER_FLAG_HTML_CONTENT)
g_string_append (string, "text/html");
else
g_string_append (string, "text/plain");
if (flags & COMPOSER_FLAG_PGP_SIGN)
g_string_append (string, ", pgp-sign");
if (flags & COMPOSER_FLAG_PGP_ENCRYPT)
g_string_append (string, ", pgp-encrypt");
if (flags & COMPOSER_FLAG_SMIME_SIGN)
g_string_append (string, ", smime-sign");
if (flags & COMPOSER_FLAG_SMIME_ENCRYPT)
g_string_append (string, ", smime-encrypt");
camel_medium_add_header (
medium, "X-Evolution-Format", string->str);
g_string_free (string, TRUE);
}
static void
composer_build_message (EMsgComposer *composer,
ComposerFlags flags,
gint io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
EMsgComposerPrivate *priv;
GSimpleAsyncResult *simple;
AsyncContext *context;
GtkhtmlEditor *editor;
EAttachmentView *view;
EAttachmentStore *store;
EComposerHeaderTable *table;
CamelDataWrapper *html;
const gchar *iconv_charset = NULL;
CamelMultipart *body = NULL;
CamelContentType *type;
CamelSession *session;
CamelStream *stream;
CamelStream *mem_stream;
CamelMimePart *part;
GByteArray *data;
EAccount *account;
gchar *charset;
gint i;
priv = composer->priv;
editor = GTKHTML_EDITOR (composer);
table = e_msg_composer_get_header_table (composer);
account = e_composer_header_table_get_account (table);
view = e_msg_composer_get_attachment_view (composer);
store = e_attachment_view_get_store (view);
session = e_msg_composer_get_session (composer);
/* Do all the non-blocking work here, and defer
* any blocking operations to a separate thread. */
context = g_slice_new0 (AsyncContext);
context->account = g_object_ref (account);
context->session = g_object_ref (session);
context->from = e_msg_composer_get_from (composer);
if (flags & COMPOSER_FLAG_PGP_SIGN)
context->pgp_sign = TRUE;
if (flags & COMPOSER_FLAG_PGP_ENCRYPT)
context->pgp_encrypt = TRUE;
if (flags & COMPOSER_FLAG_SMIME_SIGN)
context->smime_sign = TRUE;
if (flags & COMPOSER_FLAG_SMIME_ENCRYPT)
context->smime_encrypt = TRUE;
context->need_thread =
context->pgp_sign || context->pgp_encrypt ||
context->smime_sign || context->smime_encrypt;
simple = g_simple_async_result_new (
G_OBJECT (composer), callback,
user_data, composer_build_message);
g_simple_async_result_set_op_res_gpointer (
simple, context, (GDestroyNotify) async_context_free);
/* If this is a redirected message, just tweak the headers. */
if (priv->redirect) {
context->message = g_object_ref (priv->redirect);
build_message_headers (composer, context->message, TRUE);
g_simple_async_result_complete (simple);
g_object_unref (simple);
return;
}
context->message = camel_mime_message_new ();
build_message_headers (composer, context->message, FALSE);
for (i = 0; i < priv->extra_hdr_names->len; i++) {
camel_medium_add_header (
CAMEL_MEDIUM (context->message),
priv->extra_hdr_names->pdata[i],
priv->extra_hdr_values->pdata[i]);
}
/* Disposition-Notification-To */
if (flags & COMPOSER_FLAG_REQUEST_READ_RECEIPT) {
gchar *mdn_address = account->id->reply_to;
if (!mdn_address || !*mdn_address)
mdn_address = account->id->address;
camel_medium_add_header (
CAMEL_MEDIUM (context->message),
"Disposition-Notification-To", mdn_address);
}
/* X-Priority */
if (flags & COMPOSER_FLAG_PRIORITIZE_MESSAGE)
camel_medium_add_header (
CAMEL_MEDIUM (context->message),
"X-Priority", "1");
if (account != NULL) {
/* X-Evolution-Account */
camel_medium_set_header (
CAMEL_MEDIUM (context->message),
"X-Evolution-Account", account->uid);
/* X-Evolution-Fcc */
camel_medium_set_header (
CAMEL_MEDIUM (context->message),
"X-Evolution-Fcc", account->sent_folder_uri);
/* X-Evolution-Transport */
camel_medium_set_header (
CAMEL_MEDIUM (context->message),
"X-Evolution-Transport", account->transport->url);
/* Organization */
if (account->id->organization != NULL) {
gchar *organization;
organization = camel_header_encode_string (
(const guchar *) account->id->organization);
camel_medium_set_header (
CAMEL_MEDIUM (context->message),
"Organization", organization);
g_free (organization);
}
}
/* X-Evolution-Format */
composer_add_evolution_format_header (
CAMEL_MEDIUM (context->message), flags);
/* Build the text/plain part. */
if (priv->mime_body) {
if (text_requires_quoted_printable (priv->mime_body, -1)) {
context->plain_encoding =
CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE;
} else {
context->plain_encoding = CAMEL_TRANSFER_ENCODING_7BIT;
for (i = 0; priv->mime_body[i]; i++) {
if ((guchar) priv->mime_body[i] > 127) {
context->plain_encoding =
CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE;
break;
}
}
}
data = g_byte_array_new ();
g_byte_array_append (
data, (const guint8 *) priv->mime_body,
strlen (priv->mime_body));
type = camel_content_type_decode (priv->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);
type = camel_content_type_new ("text", "plain");
charset = best_charset (
data, priv->charset, &context->plain_encoding);
if (charset != NULL) {
camel_content_type_set_param (type, "charset", charset);
iconv_charset = camel_iconv_charset_name (charset);
g_free (charset);
}
}
mem_stream = camel_stream_mem_new_with_byte_array (data);
stream = camel_stream_filter_new (mem_stream);
g_object_unref (mem_stream);
/* Convert the stream to the appropriate charset. */
if (iconv_charset && g_ascii_strcasecmp (iconv_charset, "UTF-8") != 0)
composer_add_charset_filter (stream, iconv_charset);
/* Encode the stream to quoted-printable if necessary. */
if (context->plain_encoding == CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE)
composer_add_quoted_printable_filter (stream);
/* Construct the content object. This does not block since
* we're constructing the data wrapper from a memory stream. */
context->top_level_part = camel_data_wrapper_new ();
camel_data_wrapper_construct_from_stream_sync (
context->top_level_part, stream, NULL, NULL);
g_object_unref (stream);
context->text_plain_part = g_object_ref (context->top_level_part);
/* Avoid re-encoding the data when adding it to a MIME part. */
if (context->plain_encoding == CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE)
context->top_level_part->encoding = context->plain_encoding;
camel_data_wrapper_set_mime_type_field (
context->top_level_part, type);
camel_content_type_unref (type);
/* Build the text/html part, and wrap it and the text/plain part
* in a multipart/alternative part. Additionally, if there are
* inline images then wrap the multipart/alternative part along
* with the images in a multipart/related part.
*
* So the structure of all this will be:
*
* multipart/related
* multipart/alternative
* text/plain
* text/html
* image/<<whatever>>
* image/<<whatever>>
* ...
*/
if (flags & COMPOSER_FLAG_HTML_CONTENT) {
gchar *text;
gsize length;
gboolean pre_encode;
clear_current_images (composer);
if (flags & COMPOSER_FLAG_SAVE_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);
pre_encode = text_requires_quoted_printable (text, length);
g_free (text);
if (flags & COMPOSER_FLAG_SAVE_OBJECT_DATA)
gtkhtml_editor_run_command (editor, "save-data-off");
mem_stream = camel_stream_mem_new_with_byte_array (data);
stream = camel_stream_filter_new (mem_stream);
g_object_unref (mem_stream);
if (pre_encode)
composer_add_quoted_printable_filter (stream);
/* Construct the content object. This does not block since
* we're constructing the data wrapper from a memory stream. */
html = camel_data_wrapper_new ();
camel_data_wrapper_construct_from_stream_sync (
html, stream, NULL, NULL);
g_object_unref (stream);
camel_data_wrapper_set_mime_type (
html, "text/html; charset=utf-8");
/* Avoid re-encoding the data when adding it to a MIME part. */
if (pre_encode)
html->encoding =
CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE;
/* 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);
/* Add the text/plain part. */
part = camel_mime_part_new ();
camel_medium_set_content (
CAMEL_MEDIUM (part), context->top_level_part);
camel_mime_part_set_encoding (part, context->plain_encoding);
camel_multipart_add_part (body, part);
g_object_unref (part);
/* Add the text/html part. */
part = camel_mime_part_new ();
camel_medium_set_content (CAMEL_MEDIUM (part), html);
camel_mime_part_set_encoding (
part, CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE);
camel_multipart_add_part (body, part);
g_object_unref (part);
g_object_unref (context->top_level_part);
g_object_unref (html);
/* If there are inlined images, construct a multipart/related
* containing the multipart/alternative and the images. */
if (priv->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 (
CAMEL_MEDIUM (part),
CAMEL_DATA_WRAPPER (body));
camel_multipart_add_part (html_with_images, part);
g_object_unref (part);
g_object_unref (body);
add_inlined_images (composer, html_with_images);
clear_current_images (composer);
context->top_level_part =
CAMEL_DATA_WRAPPER (html_with_images);
} else
context->top_level_part =
CAMEL_DATA_WRAPPER (body);
}
/* If there are attachments, wrap what we've built so far
* along with the attachments in a multipart/mixed part. */
if (e_attachment_store_get_num_attachments (store) > 0) {
CamelMultipart *multipart = camel_multipart_new ();
/* Generate a random boundary. */
camel_multipart_set_boundary (multipart, NULL);
part = camel_mime_part_new ();
camel_medium_set_content (
CAMEL_MEDIUM (part),
context->top_level_part);
if (context->top_level_part == context->text_plain_part)
camel_mime_part_set_encoding (
part, context->plain_encoding);
camel_multipart_add_part (multipart, part);
g_object_unref (part);
e_attachment_store_add_to_multipart (
store, multipart, priv->charset);
g_object_unref (context->top_level_part);
context->top_level_part = CAMEL_DATA_WRAPPER (multipart);
}
/* Run any blocking operations in a separate thread. */
if (context->need_thread)
g_simple_async_result_run_in_thread (
simple, (GSimpleAsyncThreadFunc)
composer_build_message_thread,
io_priority, cancellable);
else
g_simple_async_result_complete (simple);
g_object_unref (simple);
}
static CamelMimeMessage *
composer_build_message_finish (EMsgComposer *composer,
GAsyncResult *result,
GError **error)
{
GSimpleAsyncResult *simple;
AsyncContext *context;
g_return_val_if_fail (
g_simple_async_result_is_valid (
result, G_OBJECT (composer), composer_build_message), NULL);
simple = G_SIMPLE_ASYNC_RESULT (result);
context = g_simple_async_result_get_op_res_gpointer (simple);
if (g_simple_async_result_propagate_error (simple, error))
return NULL;
/* Finalize some details before returning. */
if (!context->skip_content)
camel_medium_set_content (
CAMEL_MEDIUM (context->message),
context->top_level_part);
if (context->top_level_part == context->text_plain_part)
camel_mime_part_set_encoding (
CAMEL_MIME_PART (context->message),
context->plain_encoding);
return g_object_ref (context->message);
}
/* Signatures */
static gchar *
encode_signature_uid (ESignature *signature)
{
const gchar *uid;
const gchar *s;
gchar *ename, *e;
gint len = 0;
uid = e_signature_get_uid (signature);
s = uid;
while (*s) {
len++;
if (*s == '"' || *s == '.' || *s == '=')
len++;
s++;
}
ename = g_new (gchar, len + 1);
s = uid;
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;
}
static gboolean
is_top_signature (EMsgComposer *composer)
{
EShell *shell;
EShellSettings *shell_settings;
EMsgComposerPrivate *priv = E_MSG_COMPOSER_GET_PRIVATE (composer);
g_return_val_if_fail (priv != NULL, FALSE);
/* The composer had been created from a stored message, thus the
* signature placement is either there already, or pt it at the
* bottom regardless of a preferences (which is for reply anyway,
* not for Edit as new) */
if (priv->is_from_message)
return FALSE;
shell = e_msg_composer_get_shell (composer);
shell_settings = e_shell_get_shell_settings (shell);
return e_shell_settings_get_boolean (shell_settings, "composer-top-signature");
}
static gboolean
add_signature_delim (EMsgComposer *composer)
{
EShell *shell;
EShellSettings *shell_settings;
shell = e_msg_composer_get_shell (composer);
shell_settings = e_shell_get_shell_settings (shell);
return !e_shell_settings_get_boolean (shell_settings, "composer-no-signature-delim");
}
#define CONVERT_SPACES CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES
#define NO_SIGNATURE_TEXT \
"<!--+GtkHTML:<DATA class=\"ClueFlow\" " \
" key=\"signature\" " \
" value=\"1\">-->" \
"<!--+GtkHTML:<DATA class=\"ClueFlow\" " \
" key=\"signature_name\" " \
" value=\"uid:Noname\">--><BR>"
static gchar *
get_signature_html (EMsgComposer *composer)
{
EComposerHeaderTable *table;
gchar *text = NULL, *html = NULL;
ESignature *signature;
gboolean format_html, add_delim;
table = e_msg_composer_get_header_table (composer);
signature = e_composer_header_table_get_signature (table);
if (!signature)
return NULL;
add_delim = add_signature_delim (composer);
if (!e_signature_get_autogenerated (signature)) {
const gchar *filename;
filename = e_signature_get_filename (signature);
if (filename == NULL)
return NULL;
format_html = e_signature_get_is_html (signature);
if (e_signature_get_is_script (signature))
text = e_run_signature_script (filename);
else
text = e_read_signature_file (signature, TRUE, NULL);
} else {
EAccount *account;
EAccountIdentity *id;
gchar *organization;
gchar *address;
gchar *name;
account = e_composer_header_table_get_account (table);
if (!account)
return NULL;
id = account->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%s",
add_delim ? "-- \n<BR>" : "",
name ? name : "",
(address && *address) ? " &lt;<A HREF=\"mailto:" : "",
address ? address : "",
(address && *address) ? "\">" : "",
address ? address : "",
(address && *address) ? "</A>&gt;" : "",
(organization && *organization) ? "<BR>" : "",
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;
const gchar *sig_delim = format_html ? "-- \n<BR>" : "-- \n";
const gchar *sig_delim_ent = format_html ? "\n-- \n<BR>" : "\n-- \n";
if (signature)
encoded_uid = encode_signature_uid (signature);
/* The signature dash convention ("-- \n") is specified
* in the "Son of RFC 1036", section 4.3.2.
* http://www.chemie.fu-berlin.de/outerspace/netnews/son-of-1036.html
*/
html = g_strdup_printf (
"<!--+GtkHTML:<DATA class=\"ClueFlow\" key=\"signature\" value=\"1\">-->"
"<!--+GtkHTML:<DATA class=\"ClueFlow\" key=\"signature_name\" value=\"uid:%s\">-->"
"<TABLE WIDTH=\"100%%\" CELLSPACING=\"0\" CELLPADDING=\"0\"><TR><TD>"
"%s%s%s%s"
"%s</TD></TR></TABLE>",
encoded_uid ? encoded_uid : "",
format_html ? "" : "<PRE>\n",
!add_delim ? "" :
(!strncmp (
sig_delim, text, strlen (sig_delim)) ||
strstr (text, sig_delim_ent))
? "" : sig_delim,
text,
format_html ? "" : "</PRE>\n",
is_top_signature (composer) ? "<BR>" : "");
g_free (text);
g_free (encoded_uid);
text = html;
}
return text;
}
static void
set_editor_text (EMsgComposer *composer,
const gchar *text,
gboolean set_signature)
{
gchar *body = NULL;
g_return_if_fail (E_IS_MSG_COMPOSER (composer));
g_return_if_fail (text != NULL);
/*
*
* 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
*
*/
if (is_top_signature (composer)) {
/* put marker to the top */
body = g_strdup_printf ("<BR>" NO_SIGNATURE_TEXT "%s", text);
} else {
/* no marker => to the bottom */
body = g_strdup_printf ("%s<BR>", text);
}
gtkhtml_editor_set_text_html (GTKHTML_EDITOR (composer), body, -1);
if (set_signature)
e_msg_composer_show_sig_file (composer);
g_free (body);
}
/* Miscellaneous callbacks. */
static void
attachment_store_changed_cb (EMsgComposer *composer)
{
GtkhtmlEditor *editor;
/* Mark the editor as changed so it prompts about unsaved
* changes on close. */
editor = GTKHTML_EDITOR (composer);
gtkhtml_editor_set_changed (editor, TRUE);
}
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);
}
static void
msg_composer_account_changed_cb (EMsgComposer *composer)
{
EMsgComposerPrivate *p = composer->priv;
EComposerHeaderTable *table;
GtkToggleAction *action;
ESignature *signature;
EAccount *account;
gboolean active, can_sign;
const gchar *uid;
table = e_msg_composer_get_header_table (composer);
account = e_composer_header_table_get_account (table);
if (account == NULL)
goto exit;
can_sign = (!account->pgp_no_imip_sign || p->mime_type == NULL ||
g_ascii_strncasecmp (p->mime_type, "text/calendar", 13) != 0);
action = GTK_TOGGLE_ACTION (ACTION (PGP_SIGN));
active = account->pgp_always_sign && can_sign;
gtk_toggle_action_set_active (action, active);
action = GTK_TOGGLE_ACTION (ACTION (SMIME_SIGN));
active = account->smime_sign_default && can_sign;
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);
uid = account->id->sig_uid;
signature = uid ? e_get_signature_by_uid (uid) : NULL;
e_composer_header_table_set_signature (table, signature);
exit:
e_msg_composer_show_sig_file (composer);
}
static void
msg_composer_paste_clipboard_targets_cb (GtkClipboard *clipboard,
GdkAtom *targets,
gint n_targets,
EMsgComposer *composer)
{
GtkhtmlEditor *editor;
gboolean html_mode;
editor = GTKHTML_EDITOR (composer);
html_mode = gtkhtml_editor_get_html_mode (editor);
/* Order is important here to ensure common use cases are
* handled correctly. See GNOME bug #603715 for details. */
if (gtk_targets_include_uri (targets, n_targets)) {
e_composer_paste_uris (composer, clipboard);
return;
}
/* Only paste HTML content in HTML mode. */
if (html_mode) {
if (e_targets_include_html (targets, n_targets)) {
e_composer_paste_html (composer, clipboard);
return;
}
}
if (gtk_targets_include_text (targets, n_targets)) {
e_composer_paste_text (composer, clipboard);
return;
}
if (gtk_targets_include_image (targets, n_targets, TRUE)) {
e_composer_paste_image (composer, clipboard);
return;
}
}
static void
msg_composer_paste_clipboard_cb (EWebView *web_view,
EMsgComposer *composer)
{
GtkClipboard *clipboard;
clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
gtk_clipboard_request_targets (
clipboard, (GtkClipboardTargetsReceivedFunc)
msg_composer_paste_clipboard_targets_cb, composer);
g_signal_stop_emission_by_name (web_view, "paste-clipboard");
}
static void
msg_composer_realize_gtkhtml_cb (GtkWidget *widget,
EMsgComposer *composer)
{
EAttachmentView *view;
GtkTargetList *target_list;
GtkTargetEntry *targets;
gint n_targets;
/* XXX GtkHTML doesn't set itself up as a drag destination until
* it's realized, and we need to amend to its target list so
* it will accept the same drag targets as the attachment bar.
* Do this any earlier and GtkHTML will just overwrite us. */
/* When redirecting a message, the message body is not
* editable and therefore cannot be a drag destination. */
if (!e_web_view_get_editable (E_WEB_VIEW (widget)))
return;
view = e_msg_composer_get_attachment_view (composer);
target_list = e_attachment_view_get_target_list (view);
targets = gtk_target_table_new_from_list (target_list, &n_targets);
target_list = gtk_drag_dest_get_target_list (widget);
gtk_target_list_add_table (target_list, targets, n_targets);
gtk_target_table_free (targets, n_targets);
}
static gboolean
msg_composer_drag_motion_cb (GtkWidget *widget,
GdkDragContext *context,
gint x,
gint y,
guint time,
EMsgComposer *composer)
{
EAttachmentView *view;
view = e_msg_composer_get_attachment_view (composer);
/* Stop the signal from propagating to GtkHtml. */
g_signal_stop_emission_by_name (widget, "drag-motion");
return e_attachment_view_drag_motion (view, context, x, y, time);
}
static void
msg_composer_drag_data_received_cb (GtkWidget *widget,
GdkDragContext *context,
gint x,
gint y,
GtkSelectionData *selection,
guint info,
guint time,
EMsgComposer *composer)
{
EAttachmentView *view;
view = e_msg_composer_get_attachment_view (composer);
/* Forward the data to the attachment view. Note that calling
* e_attachment_view_drag_data_received() will not work because
* that function only handles the case where all the other drag
* handlers have failed. */
e_attachment_paned_drag_data_received (
E_ATTACHMENT_PANED (view),
context, x, y, selection, info, time);
/* Stop the signal from propagating to GtkHtml. */
g_signal_stop_emission_by_name (widget, "drag-data-received");
}
static void
msg_composer_notify_header_cb (EMsgComposer *composer)
{
GtkhtmlEditor *editor;
editor = GTKHTML_EDITOR (composer);
gtkhtml_editor_set_changed (editor, TRUE);
}
static gboolean
msg_composer_delete_event_cb (EMsgComposer *composer)
{
EShell *shell;
shell = e_msg_composer_get_shell (composer);
/* If the "async" action group is insensitive, it means an
* asynchronous operation is in progress. Block the event. */
if (!gtk_action_group_get_sensitive (composer->priv->async_actions))
return TRUE;
if (g_list_length (e_shell_get_watched_windows (shell)) == 1) {
/* This is the last watched window, use the quit
* mechanism to have a draft saved properly */
e_shell_quit (shell, E_SHELL_QUIT_ACTION);
} else {
/* There are more watched windows opened,
* invoke only a close action */
gtk_action_activate (ACTION (CLOSE));
}
return TRUE;
}
static void
msg_composer_prepare_for_quit_cb (EShell *shell,
EActivity *activity,
EMsgComposer *composer)
{
if (e_msg_composer_is_exiting (composer)) {
/* needs save draft first */
g_object_ref (activity);
g_object_weak_ref (
G_OBJECT (composer), (GWeakNotify)
g_object_unref, activity);
gtk_action_activate (ACTION (SAVE_DRAFT));
}
}
static void
msg_composer_quit_requested_cb (EShell *shell,
EShellQuitReason reason,
EMsgComposer *composer)
{
if (e_msg_composer_is_exiting (composer)) {
g_signal_handlers_disconnect_by_func (
shell, msg_composer_quit_requested_cb, composer);
g_signal_handlers_disconnect_by_func (
shell, msg_composer_prepare_for_quit_cb, composer);
} else if (!e_msg_composer_can_close (composer, FALSE) &&
!e_msg_composer_is_exiting (composer)) {
e_shell_cancel_quit (shell);
}
}
static void
msg_composer_set_shell (EMsgComposer *composer,
EShell *shell)
{
g_return_if_fail (E_IS_SHELL (shell));
g_return_if_fail (composer->priv->shell == NULL);
composer->priv->shell = shell;
g_object_add_weak_pointer (
G_OBJECT (shell), &composer->priv->shell);
}
static void
msg_composer_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
switch (property_id) {
case PROP_SHELL:
msg_composer_set_shell (
E_MSG_COMPOSER (object),
g_value_get_object (value));
return;
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
msg_composer_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
switch (property_id) {
case PROP_FOCUS_TRACKER:
g_value_set_object (
value, e_msg_composer_get_focus_tracker (
E_MSG_COMPOSER (object)));
return;
case PROP_SHELL:
g_value_set_object (
value, e_msg_composer_get_shell (
E_MSG_COMPOSER (object)));
return;
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
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 (e_msg_composer_parent_class)->finalize (object);
}
static void
msg_composer_constructed (GObject *object)
{
EShell *shell;
EShellSettings *shell_settings;
GtkhtmlEditor *editor;
EMsgComposer *composer;
EAttachmentView *view;
EAttachmentStore *store;
EComposerHeaderTable *table;
EWebView *web_view;
GtkUIManager *ui_manager;
GtkToggleAction *action;
GArray *array;
const gchar *id;
gboolean active;
guint binding_id;
editor = GTKHTML_EDITOR (object);
composer = E_MSG_COMPOSER (object);
shell = e_msg_composer_get_shell (composer);
shell_settings = e_shell_get_shell_settings (shell);
if (e_shell_get_express_mode (shell)) {
GtkWindow *parent = e_shell_get_active_window (shell);
gtk_window_set_transient_for (GTK_WINDOW (composer), parent);
}
e_composer_private_constructed (composer);
web_view = e_msg_composer_get_web_view (composer);
ui_manager = gtkhtml_editor_get_ui_manager (editor);
view = e_msg_composer_get_attachment_view (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");
g_signal_connect (
object, "delete-event",
G_CALLBACK (msg_composer_delete_event_cb), NULL);
e_shell_adapt_window_size (shell, GTK_WINDOW (composer));
e_shell_watch_window (shell, GTK_WINDOW (object));
g_signal_connect (
shell, "quit-requested",
G_CALLBACK (msg_composer_quit_requested_cb), composer);
g_signal_connect (
shell, "prepare-for-quit",
G_CALLBACK (msg_composer_prepare_for_quit_cb), composer);
/* Restore Persistent State */
array = composer->priv->gconf_bridge_binding_ids;
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_window (
gconf_bridge_get (),
COMPOSER_GCONF_WINDOW_PREFIX,
GTK_WINDOW (composer), TRUE, FALSE);
g_array_append_val (array, binding_id);
/* Honor User Preferences */
action = GTK_TOGGLE_ACTION (ACTION (REQUEST_READ_RECEIPT));
active = e_shell_settings_get_boolean (
shell_settings, "composer-request-receipt");
gtk_toggle_action_set_active (action, active);
/* Clipboard Support */
g_signal_connect (
web_view, "paste-clipboard",
G_CALLBACK (msg_composer_paste_clipboard_cb), composer);
/* Drag-and-Drop Support */
g_signal_connect (
web_view, "realize",
G_CALLBACK (msg_composer_realize_gtkhtml_cb), composer);
g_signal_connect (
web_view, "drag-motion",
G_CALLBACK (msg_composer_drag_motion_cb), composer);
g_signal_connect (
web_view, "drag-data-received",
G_CALLBACK (msg_composer_drag_data_received_cb), composer);
/* Configure Headers */
e_composer_header_table_set_account_list (
table, e_get_account_list ());
e_composer_header_table_set_signature_list (
table, e_get_signature_list ());
g_signal_connect_swapped (
table, "notify::account",
G_CALLBACK (msg_composer_account_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);
/* Attachments */
store = e_attachment_view_get_store (view);
g_signal_connect_swapped (
store, "row-deleted",
G_CALLBACK (attachment_store_changed_cb), composer);
g_signal_connect_swapped (
store, "row-inserted",
G_CALLBACK (attachment_store_changed_cb), composer);
/* Initialization may have tripped the "changed" state. */
gtkhtml_editor_set_changed (editor, FALSE);
id = "org.gnome.evolution.composer";
e_plugin_ui_register_manager (ui_manager, id, composer);
e_plugin_ui_enable_manager (ui_manager, id);
e_extensible_load_extensions (E_EXTENSIBLE (composer));
}
static void
msg_composer_dispose (GObject *object)
{
EMsgComposer *composer = E_MSG_COMPOSER (object);
EShell *shell;
if (composer->priv->address_dialog != NULL) {
gtk_widget_destroy (composer->priv->address_dialog);
composer->priv->address_dialog = NULL;
}
/* FIXME Our EShell is already unreferenced. */
shell = e_shell_get_default ();
g_signal_handlers_disconnect_by_func (
shell, msg_composer_quit_requested_cb, composer);
g_signal_handlers_disconnect_by_func (
shell, msg_composer_prepare_for_quit_cb, composer);
e_composer_private_dispose (composer);
/* Chain up to parent's dispose() method. */
if (G_OBJECT_CLASS (e_msg_composer_parent_class)->dispose)
G_OBJECT_CLASS (e_msg_composer_parent_class)->dispose (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 (e_msg_composer_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 gboolean
msg_composer_key_press_event (GtkWidget *widget,
GdkEventKey *event)
{
EMsgComposer *composer = E_MSG_COMPOSER (widget);
GtkWidget *input_widget;
GtkhtmlEditor *editor;
EWebView *web_view;
editor = GTKHTML_EDITOR (widget);
composer = E_MSG_COMPOSER (widget);
web_view = e_msg_composer_get_web_view (composer);
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) {
e_msg_composer_send (composer);
return TRUE;
}
#endif /* HAVE_XFREE */
if (event->keyval == GDK_KEY_Escape) {
gtk_action_activate (ACTION (CLOSE));
return TRUE;
}
if (event->keyval == GDK_KEY_Tab && gtk_widget_is_focus (input_widget)) {
gtkhtml_editor_run_command (editor, "grab-focus");
return TRUE;
}
if (event->keyval == GDK_KEY_ISO_Left_Tab &&
gtk_widget_is_focus (GTK_WIDGET (web_view))) {
gtk_widget_grab_focus (input_widget);
return TRUE;
}
/* Chain up to parent's key_press_event() method. */
return GTK_WIDGET_CLASS (e_msg_composer_parent_class)->
key_press_event (widget, event);
}
static void
msg_composer_cut_clipboard (GtkhtmlEditor *editor)
{
/* Do nothing. EFocusTracker handles this. */
}
static void
msg_composer_copy_clipboard (GtkhtmlEditor *editor)
{
/* Do nothing. EFocusTracker handles this. */
}
static void
msg_composer_paste_clipboard (GtkhtmlEditor *editor)
{
/* Do nothing. EFocusTracker handles this. */
}
static void
msg_composer_select_all (GtkhtmlEditor *editor)
{
/* Do nothing. EFocusTracker handles this. */
}
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 gboolean
msg_composer_presend (EMsgComposer *composer)
{
/* This keeps the signal accumulator at TRUE. */
return TRUE;
}
static void
msg_composer_submit_alert (EAlertSink *alert_sink,
EAlert *alert)
{
EMsgComposerPrivate *priv;
EAlertBar *alert_bar;
GtkWidget *dialog;
GtkWindow *parent;
priv = E_MSG_COMPOSER_GET_PRIVATE (alert_sink);
switch (e_alert_get_message_type (alert)) {
case GTK_MESSAGE_INFO:
case GTK_MESSAGE_WARNING:
case GTK_MESSAGE_ERROR:
alert_bar = E_ALERT_BAR (priv->alert_bar);
e_alert_bar_add_alert (alert_bar, alert);
break;
default:
parent = GTK_WINDOW (alert_sink);
dialog = e_alert_dialog_new (parent, alert);
gtk_dialog_run (GTK_DIALOG (dialog));
gtk_widget_destroy (dialog);
break;
}
}
static gboolean
msg_composer_accumulator_false_abort (GSignalInvocationHint *ihint,
GValue *return_accu,
const GValue *handler_return,
gpointer dummy)
{
gboolean v_boolean;
v_boolean = g_value_get_boolean (handler_return);
g_value_set_boolean (return_accu, v_boolean);
/* FALSE means abort the signal emission. */
return v_boolean;
}
static void
e_msg_composer_class_init (EMsgComposerClass *class)
{
GObjectClass *object_class;
GtkWidgetClass *widget_class;
GtkhtmlEditorClass *editor_class;
g_type_class_add_private (class, sizeof (EMsgComposerPrivate));
object_class = G_OBJECT_CLASS (class);
object_class->set_property = msg_composer_set_property;
object_class->get_property = msg_composer_get_property;
object_class->dispose = msg_composer_dispose;
object_class->finalize = msg_composer_finalize;
object_class->constructed = msg_composer_constructed;
widget_class = GTK_WIDGET_CLASS (class);
widget_class->map = msg_composer_map;
widget_class->key_press_event = msg_composer_key_press_event;
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;
class->presend = msg_composer_presend;
g_object_class_install_property (
object_class,
PROP_FOCUS_TRACKER,
g_param_spec_object (
"focus-tracker",
NULL,
NULL,
E_TYPE_FOCUS_TRACKER,
G_PARAM_READABLE));
g_object_class_install_property (
object_class,
PROP_SHELL,
g_param_spec_object (
"shell",
"Shell",
"The EShell singleton",
E_TYPE_SHELL,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY));
signals[PRESEND] = g_signal_new (
"presend",
G_OBJECT_CLASS_TYPE (class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (EMsgComposerClass, presend),
msg_composer_accumulator_false_abort,
NULL,
e_marshal_BOOLEAN__VOID,
G_TYPE_BOOLEAN, 0);
signals[SEND] = g_signal_new (
"send",
G_OBJECT_CLASS_TYPE (class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (EMsgComposerClass, send),
NULL, NULL,
e_marshal_VOID__OBJECT_OBJECT,
G_TYPE_NONE, 2,
CAMEL_TYPE_MIME_MESSAGE,
E_TYPE_ACTIVITY);
signals[SAVE_DRAFT] = g_signal_new (
"save-draft",
G_OBJECT_CLASS_TYPE (class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (EMsgComposerClass, save_draft),
NULL, NULL,
e_marshal_VOID__OBJECT_OBJECT,
G_TYPE_NONE, 2,
CAMEL_TYPE_MIME_MESSAGE,
E_TYPE_ACTIVITY);
signals[PRINT] = g_signal_new (
"print",
G_OBJECT_CLASS_TYPE (class),
G_SIGNAL_RUN_LAST,
0, NULL, NULL,
e_marshal_VOID__ENUM_OBJECT_OBJECT,
G_TYPE_NONE, 3,
GTK_TYPE_PRINT_OPERATION_ACTION,
CAMEL_TYPE_MIME_MESSAGE,
E_TYPE_ACTIVITY);
}
static void
e_msg_composer_alert_sink_init (EAlertSinkInterface *interface)
{
interface->submit_alert = msg_composer_submit_alert;
}
static void
e_msg_composer_init (EMsgComposer *composer)
{
composer->priv = E_MSG_COMPOSER_GET_PRIVATE (composer);
}
/* Callbacks. */
/**
* e_msg_composer_new:
* @shell: an #EShell
*
* Create a new message composer widget.
*
* Returns: A pointer to the newly created widget
**/
EMsgComposer *
e_msg_composer_new (EShell *shell)
{
g_return_val_if_fail (E_IS_SHELL (shell), NULL);
return g_object_new (
E_TYPE_MSG_COMPOSER,
"html", e_web_view_new (), "shell", shell, NULL);
}
EFocusTracker *
e_msg_composer_get_focus_tracker (EMsgComposer *composer)
{
g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
return composer->priv->focus_tracker;
}
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);
}
static void
e_msg_composer_flush_pending_body (EMsgComposer *composer)
{
const gchar *body;
body = g_object_get_data (G_OBJECT (composer), "body:text");
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 (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 a
* 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 (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,
GCancellable *cancellable,
gint depth)
{
CamelContentType *content_type;
CamelDataWrapper *content;
CamelMimePart *mime_part;
GtkToggleAction *action = NULL;
const gchar *protocol;
content = CAMEL_DATA_WRAPPER (multipart);
content_type = camel_data_wrapper_get_mime_type_field (content);
protocol = camel_content_type_param (content_type, "protocol");
if (protocol == NULL)
action = NULL;
else if (g_ascii_strcasecmp (protocol, "application/pgp-signature") == 0)
action = GTK_TOGGLE_ACTION (ACTION (PGP_SIGN));
else if (g_ascii_strcasecmp (protocol, "application/x-pkcs7-signature") == 0)
action = GTK_TOGGLE_ACTION (ACTION (SMIME_SIGN));
if (action)
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 (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, cancellable, 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, cancellable, 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, cancellable, depth);
} else {
/* There must be attachments... */
handle_multipart (
composer, multipart, cancellable, depth);
}
} else if (camel_content_type_is (content_type, "text", "*")) {
gchar *html;
gssize length;
html = emcu_part_to_html (mime_part, &length, NULL, cancellable);
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,
GCancellable *cancellable,
gint depth)
{
CamelContentType *content_type;
CamelCipherContext *cipher;
CamelDataWrapper *content;
CamelMimePart *mime_part;
CamelSession *session;
CamelCipherValidity *valid;
GtkToggleAction *action = NULL;
const gchar *protocol;
content_type = camel_mime_part_get_content_type (multipart);
protocol = camel_content_type_param (content_type, "protocol");
if (protocol && g_ascii_strcasecmp (protocol, "application/pgp-encrypted") == 0)
action = GTK_TOGGLE_ACTION (ACTION (PGP_ENCRYPT));
else if (content_type && (
camel_content_type_is (content_type, "application", "x-pkcs7-mime")
|| camel_content_type_is (content_type, "application", "pkcs7-mime")))
action = GTK_TOGGLE_ACTION (ACTION (SMIME_ENCRYPT));
if (action)
gtk_toggle_action_set_active (action, TRUE);
session = e_msg_composer_get_session (composer);
cipher = camel_gpg_context_new (session);
mime_part = camel_mime_part_new ();
valid = camel_cipher_context_decrypt_sync (
cipher, multipart, mime_part, cancellable, NULL);
g_object_unref (cipher);
if (valid == NULL)
return;
camel_cipher_validity_free (valid);
content_type = camel_mime_part_get_content_type (mime_part);
content = camel_medium_get_content (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, cancellable, 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, cancellable, 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, cancellable, depth);
} else {
/* There must be attachments... */
handle_multipart (
composer, content_multipart, cancellable, depth);
}
} else if (camel_content_type_is (content_type, "text", "*")) {
gchar *html;
gssize length;
html = emcu_part_to_html (mime_part, &length, NULL, cancellable);
e_msg_composer_set_pending_body (composer, html, length);
} else {
e_msg_composer_attach (composer, mime_part);
}
g_object_unref (mime_part);
}
static void
handle_multipart_alternative (EMsgComposer *composer,
CamelMultipart *multipart,
GCancellable *cancellable,
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 (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, cancellable, 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,
cancellable, depth + 1);
} else {
/* Depth doesn't matter so long as we
* don't pass 0. */
handle_multipart (
composer, mp, cancellable, 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 = emcu_part_to_html (text_part, &length, NULL, cancellable);
e_msg_composer_set_pending_body (composer, html, length);
}
}
static void
handle_multipart (EMsgComposer *composer,
CamelMultipart *multipart,
GCancellable *cancellable,
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 (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, cancellable, 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,
cancellable, depth + 1);
} else if (camel_content_type_is (content_type, "multipart", "alternative")) {
handle_multipart_alternative (
composer, mp, cancellable, depth + 1);
} else {
/* Depth doesn't matter so long as we
* don't pass 0. */
handle_multipart (
composer, mp, cancellable, 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 = emcu_part_to_html (
mime_part, &length, NULL, cancellable);
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 = e_get_signature_by_uid (decoded);
g_free (decoded);
} else if (g_str_has_prefix (data, "name:")) {
decoded = decode_signature_name (data + 5);
signature = e_get_signature_by_name (decoded);
g_free (decoded);
}
e_composer_header_table_set_signature (table, signature);
}
/**
* e_msg_composer_new_with_message:
* @shell: an #EShell
* @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 (EShell *shell,
CamelMimeMessage *message,
GCancellable *cancellable)
{
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;
EMsgComposerPrivate *priv;
EComposerHeaderTable *table;
GtkToggleAction *action;
struct _camel_header_raw *xev;
gint len, i;
g_return_val_if_fail (E_IS_SHELL (shell), NULL);
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)));
}
composer = e_msg_composer_new (shell);
priv = E_MSG_COMPOSER_GET_PRIVATE (composer);
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 = (gchar *) camel_medium_get_header (
CAMEL_MEDIUM (message), "X-Evolution-Account");
if (account_name) {
account_name = g_strdup (account_name);
g_strstrip (account_name);
account = e_get_account_by_uid (account_name);
if (account == NULL)
/* XXX Backwards compatibility */
account = e_get_account_by_name (account_name);
if (account != NULL) {
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));
}
}
g_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));
}
}
g_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++) {
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 = emcu_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 (g_ascii_strcasecmp (headers->name, "References") == 0 ||
g_ascii_strcasecmp (headers->name, "In-Reply-To") == 0) {
g_ptr_array_add (
composer->priv->extra_hdr_names,
g_strdup (headers->name));
g_ptr_array_add (
composer->priv->extra_hdr_values,
g_strdup (headers->value));
}
headers = headers->next;
}
/* Restore the attachments and body text */
content = camel_medium_get_content (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, cancellable, 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),
cancellable, 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, cancellable, 0);
} else {
/* There must be attachments... */
handle_multipart (
composer, multipart, cancellable, 0);
}
} else {
gchar *html;
gssize length;
content_type = camel_mime_part_get_content_type (CAMEL_MIME_PART (message));
if (content_type && (
camel_content_type_is (content_type, "application", "x-pkcs7-mime")
|| camel_content_type_is (content_type, "application", "pkcs7-mime")))
gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (ACTION (SMIME_ENCRYPT)), TRUE);
html = emcu_part_to_html (
CAMEL_MIME_PART (message), &length, NULL, cancellable);
e_msg_composer_set_pending_body (composer, html, length);
}
priv->is_from_message = TRUE;
/* 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;
}
/**
* e_msg_composer_new_redirect:
* @shell: an #EShell
* @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 (EShell *shell,
CamelMimeMessage *message,
const gchar *resent_from,
GCancellable *cancellable)
{
EMsgComposer *composer;
EComposerHeaderTable *table;
EWebView *web_view;
const gchar *subject;
g_return_val_if_fail (E_IS_SHELL (shell), NULL);
g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL);
composer = e_msg_composer_new_with_message (
shell, message, cancellable);
table = e_msg_composer_get_header_table (composer);
subject = camel_mime_message_get_subject (message);
composer->priv->redirect = message;
g_object_ref (message);
e_composer_header_table_set_account_name (table, resent_from);
e_composer_header_table_set_subject (table, subject);
web_view = e_msg_composer_get_web_view (composer);
e_web_view_set_editable (web_view, FALSE);
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)
{
EShell *shell;
EShellSettings *shell_settings;
CamelSession *session;
g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
shell = e_msg_composer_get_shell (composer);
shell_settings = e_shell_get_shell_settings (shell);
session = e_shell_settings_get_pointer (shell_settings, "mail-session");
g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
return session;
}
/**
* e_msg_composer_get_shell:
* @composer: an #EMsgComposer
*
* Returns the #EShell that was passed to e_msg_composer_new().
*
* Returns: the #EShell
**/
EShell *
e_msg_composer_get_shell (EMsgComposer *composer)
{
g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
return E_SHELL (composer->priv->shell);
}
/**
* e_msg_composer_get_web_view:
* @composer: an #EMsgComposer
*
* Returns the #EWebView widget in @composer.
*
* Returns: the #EWebView
**/
EWebView *
e_msg_composer_get_web_view (EMsgComposer *composer)
{
GtkHTML *html;
GtkhtmlEditor *editor;
g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
/* This is a convenience function to avoid
* repeating this awkwardness everywhere */
editor = GTKHTML_EDITOR (composer);
html = gtkhtml_editor_get_html (editor);
return E_WEB_VIEW (html);
}
static void
msg_composer_send_cb (EMsgComposer *composer,
GAsyncResult *result,
AsyncContext *context)
{
CamelMimeMessage *message;
GtkhtmlEditor *editor;
GError *error = NULL;
message = e_msg_composer_get_message_finish (composer, result, &error);
/* Ignore cancellations. */
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
g_warn_if_fail (message == NULL);
async_context_free (context);
g_error_free (error);
return;
}
if (error != NULL) {
g_warn_if_fail (message == NULL);
async_context_free (context);
e_alert_submit (
GTK_WIDGET (composer),
"mail-composer:no-build-message",
error->message, NULL);
g_error_free (error);
return;
}
g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
g_signal_emit (
composer, signals[SEND], 0,
message, context->activity);
g_object_unref (message);
async_context_free (context);
/* XXX This should be elsewhere. */
editor = GTKHTML_EDITOR (composer);
gtkhtml_editor_set_changed (editor, FALSE);
}
/**
* e_msg_composer_send:
* @composer: an #EMsgComposer
*
* Send the message in @composer.
**/
void
e_msg_composer_send (EMsgComposer *composer)
{
AsyncContext *context;
EActivityBar *activity_bar;
GCancellable *cancellable;
gboolean proceed_with_send = TRUE;
g_return_if_fail (E_IS_MSG_COMPOSER (composer));
/* This gives the user a chance to abort the send. */
g_signal_emit (composer, signals[PRESEND], 0, &proceed_with_send);
if (!proceed_with_send)
return;
context = g_slice_new0 (AsyncContext);
context->activity = e_composer_activity_new (composer);
cancellable = camel_operation_new ();
e_activity_set_cancellable (context->activity, cancellable);
g_object_unref (cancellable);
activity_bar = E_ACTIVITY_BAR (composer->priv->activity_bar);
e_activity_bar_set_activity (activity_bar, context->activity);
e_msg_composer_get_message (
composer, G_PRIORITY_DEFAULT, cancellable,
(GAsyncReadyCallback) msg_composer_send_cb,
context);
}
static void
msg_composer_save_draft_cb (EMsgComposer *composer,
GAsyncResult *result,
AsyncContext *context)
{
CamelMimeMessage *message;
GtkhtmlEditor *editor;
GError *error = NULL;
message = e_msg_composer_get_message_draft_finish (
composer, result, &error);
/* Ignore cancellations. */
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
g_warn_if_fail (message == NULL);
async_context_free (context);
g_error_free (error);
return;
}
if (error != NULL) {
g_warn_if_fail (message == NULL);
async_context_free (context);
e_alert_submit (
GTK_WIDGET (composer),
"mail-composer:no-build-message",
error->message, NULL);
g_error_free (error);
return;
}
g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
g_signal_emit (
composer, signals[SAVE_DRAFT], 0,
message, context->activity);
g_object_unref (message);
async_context_free (context);
/* XXX This should be elsewhere. */
editor = GTKHTML_EDITOR (composer);
gtkhtml_editor_set_changed (editor, FALSE);
}
/**
* 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)
{
AsyncContext *context;
EActivityBar *activity_bar;
GCancellable *cancellable;
g_return_if_fail (E_IS_MSG_COMPOSER (composer));
context = g_slice_new0 (AsyncContext);
context->activity = e_composer_activity_new (composer);
cancellable = camel_operation_new ();
e_activity_set_cancellable (context->activity, cancellable);
g_object_unref (cancellable);
activity_bar = E_ACTIVITY_BAR (composer->priv->activity_bar);
e_activity_bar_set_activity (activity_bar, context->activity);
e_msg_composer_get_message_draft (
composer, G_PRIORITY_DEFAULT, cancellable,
(GAsyncReadyCallback) msg_composer_save_draft_cb,
context);
}
static void
msg_composer_print_cb (EMsgComposer *composer,
GAsyncResult *result,
AsyncContext *context)
{
CamelMimeMessage *message;
GError *error = NULL;
message = e_msg_composer_get_message_print_finish (
composer, result, &error);
/* Ignore cancellations. */
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
g_warn_if_fail (message == NULL);
async_context_free (context);
g_error_free (error);
return;
}
if (error != NULL) {
g_warn_if_fail (message == NULL);
async_context_free (context);
e_alert_submit (
GTK_WIDGET (composer),
"mail-composer:no-build-message",
error->message, NULL);
g_error_free (error);
return;
}
g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
g_signal_emit (
composer, signals[PRINT], 0,
context->print_action, message, context->activity);
g_object_unref (message);
async_context_free (context);
}
/**
* e_msg_composer_print:
* @composer: an #EMsgComposer
* @print_action: the print action to start
*
* Print the message in @composer.
**/
void
e_msg_composer_print (EMsgComposer *composer,
GtkPrintOperationAction print_action)
{
AsyncContext *context;
EActivityBar *activity_bar;
GCancellable *cancellable;
g_return_if_fail (E_IS_MSG_COMPOSER (composer));
context = g_slice_new0 (AsyncContext);
context->activity = e_composer_activity_new (composer);
context->print_action = print_action;
cancellable = camel_operation_new ();
e_activity_set_cancellable (context->activity, cancellable);
g_object_unref (cancellable);
activity_bar = E_ACTIVITY_BAR (composer->priv->activity_bar);
e_activity_bar_set_activity (activity_bar, context->activity);
e_msg_composer_get_message_print (
composer, G_PRIORITY_DEFAULT, cancellable,
(GAsyncReadyCallback) msg_composer_print_cb,
context);
}
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)
{
EAttachmentView *view;
EAttachmentStore *store;
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;
table = e_msg_composer_get_header_table (composer);
view = e_msg_composer_get_attachment_view (composer);
store = e_attachment_view_get_store (view);
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 = (gchar *) 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")) {
EAttachment *attachment;
camel_url_decode (content);
if (g_ascii_strncasecmp (content, "file:", 5) == 0)
attachment = e_attachment_new_for_uri (content);
else
attachment = e_attachment_new_for_path (content);
e_attachment_store_add_attachment (store, attachment);
e_attachment_load_async (
attachment, (GAsyncReadyCallback)
e_attachment_load_handle_error, composer);
g_object_unref (attachment);
} 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, TRUE);
g_free (htmlbody);
}
}
/**
* e_msg_composer_new_from_url:
* @shell: an #EShell
* @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 (EShell *shell,
const gchar *url)
{
EMsgComposer *composer;
g_return_val_if_fail (E_IS_SHELL (shell), NULL);
g_return_val_if_fail (g_ascii_strncasecmp (url, "mailto:", 7) == 0, NULL);
composer = e_msg_composer_new (shell);
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 into the composer as the message body.
**/
void
e_msg_composer_set_body (EMsgComposer *composer,
const gchar *body,
const gchar *mime_type)
{
EMsgComposerPrivate *p = composer->priv;
EComposerHeaderTable *table;
EWebView *web_view;
gchar *buff;
g_return_if_fail (E_IS_MSG_COMPOSER (composer));
table = e_msg_composer_get_header_table (composer);
buff = g_markup_printf_escaped ("<b>%s</b>",
_("The composer contains a non-text "
"message body, which cannot be edited."));
set_editor_text (composer, buff, FALSE);
g_free (buff);
gtkhtml_editor_set_html_mode (GTKHTML_EDITOR (composer), FALSE);
web_view = e_msg_composer_get_web_view (composer);
e_web_view_set_editable (web_view, FALSE);
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);
action = GTK_TOGGLE_ACTION (ACTION (SMIME_SIGN));
gtk_toggle_action_set_active (action, FALSE);
}
}
}
/**
* e_msg_composer_add_header:
* @composer: an #EMsgComposer
* @name: the header's name
* @value: the header's value
*
* Adds a new custom header created from @name and @value. The header
* is not shown in the user interface but will be added to the resulting
* MIME message when sending or saving.
**/
void
e_msg_composer_add_header (EMsgComposer *composer,
const gchar *name,
const gchar *value)
{
EMsgComposerPrivate *priv;
g_return_if_fail (E_IS_MSG_COMPOSER (composer));
g_return_if_fail (name != NULL);
g_return_if_fail (value != NULL);
priv = composer->priv;
g_ptr_array_add (priv->extra_hdr_names, g_strdup (name));
g_ptr_array_add (priv->extra_hdr_values, g_strdup (value));
}
/**
* e_msg_composer_set_header:
* @composer: an #EMsgComposer
* @name: the header's name
* @value: the header's value
*
* Replaces all custom headers matching @name that were added with
* e_msg_composer_add_header() or e_msg_composer_set_header(), with
* a new custom header created from @name and @value. The header is
* not shown in the user interface but will be added to the resulting
* MIME message when sending or saving.
**/
void
e_msg_composer_set_header (EMsgComposer *composer,
const gchar *name,
const gchar *value)
{
g_return_if_fail (E_IS_MSG_COMPOSER (composer));
g_return_if_fail (name != NULL);
g_return_if_fail (value != NULL);
e_msg_composer_remove_header (composer, name);
e_msg_composer_add_header (composer, name, value);
}
/**
* e_msg_composer_remove_header:
* @composer: an #EMsgComposer
* @name: the header's name
*
* Removes all custom headers matching @name that were added with
* e_msg_composer_add_header() or e_msg_composer_set_header().
**/
void
e_msg_composer_remove_header (EMsgComposer *composer,
const gchar *name)
{
EMsgComposerPrivate *priv;
guint ii;
g_return_if_fail (E_IS_MSG_COMPOSER (composer));
g_return_if_fail (name != NULL);
priv = composer->priv;
for (ii = 0; ii < priv->extra_hdr_names->len; ii++) {
if (g_strcmp0 (priv->extra_hdr_names->pdata[ii], name) == 0) {
g_free (priv->extra_hdr_names->pdata[ii]);
g_free (priv->extra_hdr_values->pdata[ii]);
g_ptr_array_remove_index (priv->extra_hdr_names, ii);
g_ptr_array_remove_index (priv->extra_hdr_values, ii);
}
}
}
/**
* e_msg_composer_set_draft_headers:
* @composer: an #EMsgComposer
* @folder_uri: folder URI of the last saved draft
* @message_uid: message UID of the last saved draft
*
* Add special X-Evolution-Draft headers to remember the most recently
* saved draft message, even across Evolution sessions. These headers
* can be used to mark the draft message for deletion after saving a
* newer draft or sending the composed message.
**/
void
e_msg_composer_set_draft_headers (EMsgComposer *composer,
const gchar *folder_uri,
const gchar *message_uid)
{
const gchar *header_name;
g_return_if_fail (E_IS_MSG_COMPOSER (composer));
g_return_if_fail (folder_uri != NULL);
g_return_if_fail (message_uid != NULL);
header_name = "X-Evolution-Draft-Folder";
e_msg_composer_set_header (composer, header_name, folder_uri);
header_name = "X-Evolution-Draft-Message";
e_msg_composer_set_header (composer, header_name, message_uid);
}
/**
* e_msg_composer_set_source_headers:
* @composer: an #EMsgComposer
* @folder_uri: folder URI of the source message
* @message_uid: message UID of the source message
* @flags: flags to set on the source message after sending
*
* Add special X-Evolution-Source headers to remember the message being
* forwarded or replied to, even across Evolution sessions. These headers
* can be used to set appropriate flags on the source message after sending
* the composed message.
**/
void
e_msg_composer_set_source_headers (EMsgComposer *composer,
const gchar *folder_uri,
const gchar *message_uid,
CamelMessageFlags flags)
{
GString *buffer;
const gchar *header_name;
g_return_if_fail (E_IS_MSG_COMPOSER (composer));
g_return_if_fail (folder_uri != NULL);
g_return_if_fail (message_uid != NULL);
buffer = g_string_sized_new (32);
if (flags & CAMEL_MESSAGE_ANSWERED)
g_string_append (buffer, "ANSWERED ");
if (flags & CAMEL_MESSAGE_ANSWERED_ALL)
g_string_append (buffer, "ANSWERED_ALL ");
if (flags & CAMEL_MESSAGE_FORWARDED)
g_string_append (buffer, "FORWARDED ");
if (flags & CAMEL_MESSAGE_SEEN)
g_string_append (buffer, "SEEN ");
header_name = "X-Evolution-Source-Folder";
e_msg_composer_set_header (composer, header_name, folder_uri);
header_name = "X-Evolution-Source-Message";
e_msg_composer_set_header (composer, header_name, message_uid);
header_name = "X-Evolution-Source-Flags";
e_msg_composer_set_header (composer, header_name, buffer->str);
g_string_free (buffer, TRUE);
}
/**
* e_msg_composer_attach:
* @composer: a composer object
* @mime_part: the #CamelMimePart to attach
*
* Attaches @attachment to the message being composed in the composer.
**/
void
e_msg_composer_attach (EMsgComposer *composer,
CamelMimePart *mime_part)
{
EAttachmentView *view;
EAttachmentStore *store;
EAttachment *attachment;
g_return_if_fail (E_IS_MSG_COMPOSER (composer));
g_return_if_fail (CAMEL_IS_MIME_PART (mime_part));
view = e_msg_composer_get_attachment_view (composer);
store = e_attachment_view_get_store (view);
attachment = e_attachment_new ();
e_attachment_set_mime_part (attachment, mime_part);
e_attachment_store_add_attachment (store, attachment);
e_attachment_load_async (
attachment, (GAsyncReadyCallback)
e_attachment_load_handle_error, composer);
g_object_unref (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, NULL);
if (!stream)
return NULL;
wrapper = camel_data_wrapper_new ();
camel_data_wrapper_construct_from_stream_sync (
wrapper, stream, NULL, NULL);
g_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 (CAMEL_MEDIUM (part), wrapper);
g_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);
g_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);
}
static void
composer_get_message_ready (EMsgComposer *composer,
GAsyncResult *result,
GSimpleAsyncResult *simple)
{
CamelMimeMessage *message;
GError *error = NULL;
message = composer_build_message_finish (composer, result, &error);
if (message != NULL)
g_simple_async_result_set_op_res_gpointer (
simple, message, (GDestroyNotify) g_object_unref);
if (error != NULL) {
g_warn_if_fail (message == NULL);
g_simple_async_result_set_from_error (simple, error);
g_error_free (error);
}
g_simple_async_result_complete (simple);
g_object_unref (simple);
}
/**
* e_msg_composer_get_message:
* @composer: an #EMsgComposer
*
* 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.
**/
void
e_msg_composer_get_message (EMsgComposer *composer,
gint io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GSimpleAsyncResult *simple;
GtkAction *action;
ComposerFlags flags = 0;
g_return_if_fail (E_IS_MSG_COMPOSER (composer));
simple = g_simple_async_result_new (
G_OBJECT (composer), callback,
user_data, e_msg_composer_get_message);
if (gtkhtml_editor_get_html_mode (GTKHTML_EDITOR (composer)))
flags |= COMPOSER_FLAG_HTML_CONTENT;
action = ACTION (PRIORITIZE_MESSAGE);
if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
flags |= COMPOSER_FLAG_PRIORITIZE_MESSAGE;
action = ACTION (REQUEST_READ_RECEIPT);
if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
flags |= COMPOSER_FLAG_REQUEST_READ_RECEIPT;
action = ACTION (PGP_SIGN);
if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
flags |= COMPOSER_FLAG_PGP_SIGN;
action = ACTION (PGP_ENCRYPT);
if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
flags |= COMPOSER_FLAG_PGP_ENCRYPT;
#ifdef HAVE_NSS
action = ACTION (SMIME_SIGN);
if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
flags |= COMPOSER_FLAG_SMIME_SIGN;
action = ACTION (SMIME_ENCRYPT);
if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
flags |= COMPOSER_FLAG_SMIME_ENCRYPT;
#endif
composer_build_message (
composer, flags, io_priority,
cancellable, (GAsyncReadyCallback)
composer_get_message_ready, simple);
}
CamelMimeMessage *
e_msg_composer_get_message_finish (EMsgComposer *composer,
GAsyncResult *result,
GError **error)
{
GSimpleAsyncResult *simple;
CamelMimeMessage *message;
g_return_val_if_fail (
g_simple_async_result_is_valid (
result, G_OBJECT (composer),
e_msg_composer_get_message), NULL);
simple = G_SIMPLE_ASYNC_RESULT (result);
message = g_simple_async_result_get_op_res_gpointer (simple);
if (g_simple_async_result_propagate_error (simple, error))
return NULL;
g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL);
return g_object_ref (message);
}
void
e_msg_composer_get_message_print (EMsgComposer *composer,
gint io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GSimpleAsyncResult *simple;
ComposerFlags flags = 0;
g_return_if_fail (E_IS_MSG_COMPOSER (composer));
simple = g_simple_async_result_new (
G_OBJECT (composer), callback,
user_data, e_msg_composer_get_message_print);
flags |= COMPOSER_FLAG_HTML_CONTENT;
flags |= COMPOSER_FLAG_SAVE_OBJECT_DATA;
composer_build_message (
composer, flags, io_priority,
cancellable, (GAsyncReadyCallback)
composer_get_message_ready, simple);
}
CamelMimeMessage *
e_msg_composer_get_message_print_finish (EMsgComposer *composer,
GAsyncResult *result,
GError **error)
{
GSimpleAsyncResult *simple;
CamelMimeMessage *message;
g_return_val_if_fail (
g_simple_async_result_is_valid (
result, G_OBJECT (composer),
e_msg_composer_get_message_print), NULL);
simple = G_SIMPLE_ASYNC_RESULT (result);
message = g_simple_async_result_get_op_res_gpointer (simple);
if (g_simple_async_result_propagate_error (simple, error))
return NULL;
g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL);
return g_object_ref (message);
}
void
e_msg_composer_get_message_draft (EMsgComposer *composer,
gint io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GSimpleAsyncResult *simple;
ComposerFlags flags = 0;
g_return_if_fail (E_IS_MSG_COMPOSER (composer));
simple = g_simple_async_result_new (
G_OBJECT (composer), callback,
user_data, e_msg_composer_get_message_draft);
if (gtkhtml_editor_get_html_mode (GTKHTML_EDITOR (composer)))
flags |= COMPOSER_FLAG_HTML_CONTENT;
composer_build_message (
composer, flags, io_priority,
cancellable, (GAsyncReadyCallback)
composer_get_message_ready, simple);
}
CamelMimeMessage *
e_msg_composer_get_message_draft_finish (EMsgComposer *composer,
GAsyncResult *result,
GError **error)
{
GSimpleAsyncResult *simple;
CamelMimeMessage *message;
g_return_val_if_fail (
g_simple_async_result_is_valid (
result, G_OBJECT (composer),
e_msg_composer_get_message_draft), NULL);
simple = G_SIMPLE_ASYNC_RESULT (result);
message = g_simple_async_result_get_op_res_gpointer (simple);
if (g_simple_async_result_propagate_error (simple, error))
return NULL;
g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL);
return g_object_ref (message);
}
/**
* e_msg_composer_show_sig:
* @composer: A message composer widget
*
* Set a signature
**/
void
e_msg_composer_show_sig_file (EMsgComposer *composer)
{
GtkhtmlEditor *editor;
gchar *html_text;
g_return_if_fail (E_IS_MSG_COMPOSER (composer));
editor = GTKHTML_EDITOR (composer);
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);
} else if (is_top_signature (composer)) {
/* insert paragraph after the signature ClueFlow things */
if (gtkhtml_editor_run_command (editor, "cursor-forward"))
gtkhtml_editor_run_command (editor, "insert-paragraph");
}
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) {
g_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;
}
gboolean
e_msg_composer_is_exiting (EMsgComposer *composer)
{
g_return_val_if_fail (composer != NULL, FALSE);
return composer->priv->application_exiting;
}
void
e_msg_composer_request_close (EMsgComposer *composer)
{
g_return_if_fail (composer != NULL);
composer->priv->application_exiting = TRUE;
}
/* Returns whether can close the composer immediately. It will return FALSE
* also when saving to drafts, but the e_msg_composer_is_exiting will return
* TRUE for this case. can_save_draft means whether can save draft
* immediately, or rather keep it on the caller (when FALSE). If kept on the
* folder, then returns FALSE and sets interval variable to return TRUE in
* e_msg_composer_is_exiting. */
gboolean
e_msg_composer_can_close (EMsgComposer *composer,
gboolean can_save_draft)
{
gboolean res = FALSE;
GtkhtmlEditor *editor;
EComposerHeaderTable *table;
GdkWindow *window;
GtkWidget *widget;
const gchar *subject;
gint response;
editor = GTKHTML_EDITOR (composer);
widget = GTK_WIDGET (composer);
if (!gtkhtml_editor_get_changed (editor))
return TRUE;
window = gtk_widget_get_window (widget);
gdk_window_raise (window);
table = e_msg_composer_get_header_table (composer);
subject = e_composer_header_table_get_subject (table);
if (subject == NULL || *subject == '\0')
subject = _("Untitled Message");
response = e_alert_run_dialog_for_args (
GTK_WINDOW (composer),
"mail-composer:exit-unsaved",
subject, NULL);
switch (response) {
case GTK_RESPONSE_YES:
gtk_widget_hide (widget);
e_msg_composer_request_close (composer);
if (can_save_draft)
gtk_action_activate (ACTION (SAVE_DRAFT));
break;
case GTK_RESPONSE_NO:
res = TRUE;
break;
case GTK_RESPONSE_CANCEL:
break;
}
return res;
}
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);
}
EAttachmentView *
e_msg_composer_get_attachment_view (EMsgComposer *composer)
{
g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
return E_ATTACHMENT_VIEW (composer->priv->attachment_paned);
}
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);
}
}