diff options
author | Dan Vrátil <dvratil@redhat.com> | 2012-03-29 00:38:11 +0800 |
---|---|---|
committer | Dan Vrátil <dvratil@redhat.com> | 2012-03-29 00:38:25 +0800 |
commit | 6d2c382788a4042d53f49a080acd11b499aa52f6 (patch) | |
tree | 3834f0836340918ba17594a603ba61c13b9929a0 | |
parent | 6bd1c6833a2c51898ac45865767dd01ba66a95c5 (diff) | |
download | gsoc2013-evolution-6d2c382788a4042d53f49a080acd11b499aa52f6.tar.gz gsoc2013-evolution-6d2c382788a4042d53f49a080acd11b499aa52f6.tar.zst gsoc2013-evolution-6d2c382788a4042d53f49a080acd11b499aa52f6.zip |
WebKit port - port formatter and mail module
51 files changed, 9805 insertions, 6751 deletions
diff --git a/data/Makefile.am b/data/Makefile.am index bb6b2c18a3..9d9a8869c4 100644 --- a/data/Makefile.am +++ b/data/Makefile.am @@ -39,6 +39,8 @@ convert_DATA = evolution.convert themedir = $(privdatadir)/theme dist_theme_DATA = \ default.css \ + webview.css \ + webview-print.css \ tab-bar-background.png \ tab-switcher.png \ tab-switcher-hover.png \ diff --git a/data/webview-print.css b/data/webview-print.css new file mode 100644 index 0000000000..a2c5292d56 --- /dev/null +++ b/data/webview-print.css @@ -0,0 +1,62 @@ +html, body { + padding: 0; + margin: 0; +} + +body { + /* Use margin so that children can safely use width=100% */ + margin: 10px; +} + +h1,h2,h3 { + color: #7f7f7f; +} + +th { + color: #7f7f7f; + text-align: left; + font-weight: normal; + vertical-align: top; +} + +.header { + color: #7f7f7f; +} + +.pre { + font-family: monospace; +} + +.part-container { + width: 100%; + background: #FFF; + margin-top: 2px; + margin-bottom: 3px; + border-width: 0px; + border-style: none; +} + +.part-container-inner-margin { + margin: 8px; +} + +/***** PRINTING *******/ + +.printing-header { + margin-bottom: 20px; +} + +.printing-header h1, +.attachments-list h1 { + font-size: 20px; +} + +.printing-header th { + text-align: right; + font-weight: bold; +} + +.attachments-list th { + font-weight: bold; +} + diff --git a/data/webview.css b/data/webview.css new file mode 100644 index 0000000000..9ff822013e --- /dev/null +++ b/data/webview.css @@ -0,0 +1,147 @@ +html, body { + padding: 0; + margin: 0; +} + +body { + /* Use margin so that children can safely use width=100% */ + margin: 10px; +} + +h1, h2, h3 { + color: #7f7f7f; +} + +th { + color: #7f7f7f; + text-align: left; + font-weight: normal; + vertical-align: top; +} + +.header { + color: #7f7f7f; +} + +.pre { + font-family: monospace; +} + +span.navigable, div.navigable, p.navigable { + cursor: pointer; + text-decoration: underline; + color: #003399; +} + +img.navigable { + cursor: pointer; + margin-right: 4px; +} + +.attachments { + background: #FFF; + border: 1px solid silver; + margin: 10px 10px 10px 10px; + border-left: 0; + border-right: 0; + border-bottom: 0; +} + +.attachment { + margin-left: 8px; + margin-right: 8px; +} + +.attachment-wrapper +{ + margin-right: 8px; +} + +.part-container { + width: 100%; + height: 100%; + background: #FFF; + margin-top: 2px; + margin-bottom: 3px; + border-width: 1px; + border-style: solid; +} + +.part-container-inner-margin { + margin: 8px; +} + +object { /* GtkWidgets */ + margin-top: 2px; + margin-bottom: 2px; +} + +.__evo-highlight { + color: purple; + font-weight: bold; +} + +/***** PRINTING *******/ + +.printing-header { + margin-bottom: 20px; +} + +.printing-header h1, +.attachments-list h1 { + font-size: 20px; +} + +.printing-header th { + text-align: right; + font-weight: bold; +} + +.attachments-list th { + font-weight: bold; +} + +/******* ITIP *********/ +.itip.icon { + float: left; + margin-right: 5px; +} + +.itip.content { + float: left; + max-width: 90%; +} + +.itip.description { + margin: 5px; +} + +.itip tr { + vertical-align: middle; + padding-top: 5px; + padding-bottom: 5px; +} + +.itip th { + color: #000; + vertical-align: middle; +} + +#table_row_summary td { + font-weight: bold; +} + +#table_row_buttons button { + line-height: 28px; + min-width: 150px; + white-space: nowrap; +} + +#table_row_buttons img { + margin-right: 5px; + vertical-align: middle; +} + +#text_row_buttons td { + text-align: center; +} diff --git a/em-format/Makefile.am b/em-format/Makefile.am index 278bcb2527..392a195044 100644 --- a/em-format/Makefile.am +++ b/em-format/Makefile.am @@ -13,7 +13,8 @@ libemformat_la_CPPFLAGS = \ -I$(top_srcdir) \ -I$(top_srcdir)/widgets \ $(EVOLUTION_DATA_SERVER_CFLAGS) \ - $(GNOME_PLATFORM_CFLAGS) + $(GNOME_PLATFORM_CFLAGS) \ + $(LIBSOUP_CFLAGS) libemformat_la_SOURCES = \ $(emformatinclude_HEADERS) \ @@ -28,6 +29,7 @@ libemformat_la_LIBADD = \ $(top_builddir)/e-util/libeutil.la \ $(top_builddir)/shell/libeshell.la \ $(EVOLUTION_DATA_SERVER_LIBS) \ - $(GNOME_PLATFORM_LIBS) + $(GNOME_PLATFORM_LIBS) \ + $(LIBSOUP_LIBS) -include $(top_srcdir)/git.mk diff --git a/em-format/em-format-quote.c b/em-format/em-format-quote.c index c3f75ec14d..4822f115d7 100644 --- a/em-format/em-format-quote.c +++ b/em-format/em-format-quote.c @@ -39,237 +39,73 @@ struct _EMFormatQuotePrivate { gchar *credits; - CamelStream *stream; EMFormatQuoteFlags flags; guint32 text_html_flags; }; static void emfq_builtin_init (EMFormatQuoteClass *efhc); -static gpointer parent_class; - -static void -emfq_dispose (GObject *object) -{ - EMFormatQuotePrivate *priv; - - priv = EM_FORMAT_QUOTE_GET_PRIVATE (object); - - if (priv->stream != NULL) { - g_object_unref (priv->stream); - priv->stream = NULL; - } - - /* Chain up to parent's dispose() method. */ - G_OBJECT_CLASS (parent_class)->dispose (object); -} - -static void -emfq_finalize (GObject *object) -{ - EMFormatQuotePrivate *priv; - - priv = EM_FORMAT_QUOTE_GET_PRIVATE (object); - - g_free (priv->credits); - - /* Chain up to parent's finalize() method. */ - G_OBJECT_CLASS (parent_class)->finalize (object); -} +static CamelMimePart * decode_inline_parts (CamelMimePart *part, GCancellable *cancellable); -static void -emfq_format_clone (EMFormat *emf, - CamelFolder *folder, - const gchar *uid, - CamelMimeMessage *msg, - EMFormat *src, - GCancellable *cancellable) -{ - EMFormatQuote *emfq = (EMFormatQuote *) emf; - const EMFormatHandler *handle; - GSettings *settings; +static void emfq_parse_text_plain (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); +static void emfq_parse_text_enriched (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); +static void emfq_parse_text_html (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); +static void emfq_parse_attachment (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); - /* Chain up to parent's format_clone() method. */ - EM_FORMAT_CLASS (parent_class)->format_clone ( - emf, folder, uid, msg, src, cancellable); +static void emfq_write_text_plain (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable); +static void emfq_write_text_enriched (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable); +static void emfq_write_text_html (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable); - g_seekable_seek ( - G_SEEKABLE (emfq->priv->stream), - 0, G_SEEK_SET, NULL, NULL); - - settings = g_settings_new ("org.gnome.evolution.mail"); - if (g_settings_get_boolean ( - settings, "composer-top-signature")) - camel_stream_write_string ( - emfq->priv->stream, "<br>\n", cancellable, NULL); - g_object_unref (settings); - handle = em_format_find_handler(emf, "x-evolution/message/prefix"); - if (handle) - handle->handler ( - emf, emfq->priv->stream, - CAMEL_MIME_PART (msg), - handle, cancellable, FALSE); - handle = em_format_find_handler(emf, "x-evolution/message/rfc822"); - if (handle) - handle->handler ( - emf, emfq->priv->stream, - CAMEL_MIME_PART (msg), - handle, cancellable, FALSE); - - camel_stream_flush (emfq->priv->stream, cancellable, NULL); - - g_signal_emit_by_name(emf, "complete"); -} - -static void -emfq_format_error (EMFormat *emf, - CamelStream *stream, - const gchar *errmsg) -{ - /* Nothing to do. */ -} +static gpointer parent_class; -static void -emfq_format_source (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - GCancellable *cancellable) +/* Decodes inline encoded parts of 'part'. The returned pointer, + * if not NULL, should be unreffed with g_object_unref(). */ +static CamelMimePart * +decode_inline_parts (CamelMimePart *part, + GCancellable *cancellable) { + CamelMultipart *mp; + CamelStream *null; CamelStream *filtered_stream; - CamelMimeFilter *html_filter; + EMInlineFilter *inline_filter; - filtered_stream = camel_stream_filter_new (stream); - html_filter = camel_mime_filter_tohtml_new ( - CAMEL_MIME_FILTER_TOHTML_CONVERT_NL | - CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES | - CAMEL_MIME_FILTER_TOHTML_ESCAPE_8BIT, 0); - camel_stream_filter_add ( - CAMEL_STREAM_FILTER (filtered_stream), html_filter); - g_object_unref (html_filter); + g_return_val_if_fail (part != NULL, NULL); - em_format_format_text ( - emf, filtered_stream, - CAMEL_DATA_WRAPPER (part), cancellable); + null = camel_stream_null_new (); + filtered_stream = camel_stream_filter_new (null); + g_object_unref (null); + inline_filter = em_inline_filter_new ( + camel_mime_part_get_encoding (part), + camel_mime_part_get_content_type (part)); + camel_stream_filter_add ( + CAMEL_STREAM_FILTER (filtered_stream), + CAMEL_MIME_FILTER (inline_filter)); + camel_data_wrapper_decode_to_stream_sync ( + camel_medium_get_content (CAMEL_MEDIUM (part)), + filtered_stream, cancellable, NULL); + camel_stream_close (filtered_stream, cancellable, NULL); g_object_unref (filtered_stream); -} - -static void -emfq_format_attachment (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const gchar *mime_type, - const EMFormatHandler *handle, - GCancellable *cancellable) -{ - EMFormatQuote *emfq = EM_FORMAT_QUOTE (emf); - gchar *text, *html; - - if (!em_format_is_inline (emf, emf->part_id->str, part, handle)) - return; - - camel_stream_write_string ( - stream, "<table border=1 cellspacing=0 cellpadding=0>" - "<tr><td><font size=-1>\n", cancellable, NULL); - - /* output some info about it */ - text = em_format_describe_part (part, mime_type); - html = camel_text_to_html ( - text, emfq->priv->text_html_flags & - CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS, 0); - camel_stream_write_string (stream, html, cancellable, NULL); - g_free (html); - g_free (text); - - camel_stream_write_string ( - stream, "</font></td></tr></table>", cancellable, NULL); - - handle->handler (emf, stream, part, handle, cancellable, FALSE); -} - -static void -emfq_base_init (EMFormatQuoteClass *class) -{ - emfq_builtin_init (class); -} - -static void -emfq_class_init (EMFormatQuoteClass *class) -{ - GObjectClass *object_class; - EMFormatClass *format_class; - - parent_class = g_type_class_peek_parent (class); - g_type_class_add_private (class, sizeof (EMFormatQuotePrivate)); - - object_class = G_OBJECT_CLASS (class); - object_class->dispose = emfq_dispose; - object_class->finalize = emfq_finalize; - - format_class = EM_FORMAT_CLASS (class); - format_class->format_clone = emfq_format_clone; - format_class->format_error = emfq_format_error; - format_class->format_source = emfq_format_source; - format_class->format_attachment = emfq_format_attachment; -} - -static void -emfq_init (EMFormatQuote *emfq) -{ - emfq->priv = EM_FORMAT_QUOTE_GET_PRIVATE (emfq); - - /* we want to convert url's etc */ - emfq->priv->text_html_flags = - CAMEL_MIME_FILTER_TOHTML_PRE | - CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS | - CAMEL_MIME_FILTER_TOHTML_CONVERT_ADDRESSES; -} -GType -em_format_quote_get_type (void) -{ - static GType type = 0; - - if (G_UNLIKELY (type == 0)) { - static const GTypeInfo type_info = { - sizeof (EMFormatQuoteClass), - (GBaseInitFunc) emfq_base_init, - (GBaseFinalizeFunc) NULL, - (GClassInitFunc) emfq_class_init, - (GClassFinalizeFunc) NULL, - NULL, /* class_data */ - sizeof (EMFormatQuote), - 0, /* n_preallocs */ - (GInstanceInitFunc) emfq_init, - NULL /* value_table */ - }; - - type = g_type_register_static ( - EM_TYPE_FORMAT, "EMFormatQuote", &type_info, 0); + if (!em_inline_filter_found_any (inline_filter)) { + g_object_unref (inline_filter); + return NULL; } - return type; -} - -EMFormatQuote * -em_format_quote_new (const gchar *credits, - CamelStream *stream, - EMFormatQuoteFlags flags) -{ - EMFormatQuote *emfq; - - g_return_val_if_fail (CAMEL_IS_STREAM (stream), NULL); - - /* Steam must also be seekable so we can reset its position. */ - g_return_val_if_fail (G_IS_SEEKABLE (stream), NULL); + mp = em_inline_filter_get_multipart (inline_filter); - emfq = g_object_new (EM_TYPE_FORMAT_QUOTE, NULL); + g_object_unref (inline_filter); - emfq->priv->credits = g_strdup (credits); - emfq->priv->stream = g_object_ref (stream); - emfq->priv->flags = flags; + if (mp) { + part = camel_mime_part_new (); + camel_medium_set_content ( + CAMEL_MEDIUM (part), CAMEL_DATA_WRAPPER (mp)); + g_object_unref (mp); + } else { + g_object_ref (part); + } - return emfq; + return part; } static void @@ -296,18 +132,18 @@ emfq_format_text_header (EMFormatQuote *emfq, if (flags & EM_FORMAT_HEADER_BOLD) g_string_append_printf ( - buffer, "<b>%s</b>: %s<br>", label, html); + buffer, "<b>%s</b>: %s<br>", label, html); else g_string_append_printf ( - buffer, "%s: %s<br>", label, html); + buffer, "%s: %s<br>", label, html); g_free (mhtml); } static const gchar *addrspec_hdrs[] = { - "Sender", "From", "Reply-To", "To", "Cc", "Bcc", - "Resent-Sender", "Resent-from", "Resent-Reply-To", - "Resent-To", "Resent-cc", "Resent-Bcc", NULL + "Sender", "From", "Reply-To", "To", "Cc", "Bcc", + "Resent-Sender", "Resent-from", "Resent-Reply-To", + "Resent-To", "Resent-cc", "Resent-Bcc", NULL }; #if 0 @@ -315,7 +151,7 @@ static const gchar *addrspec_hdrs[] = { /* For Translators only: The following strings are * used in the header table in the preview pane. */ static gchar *i18n_hdrs[] = { - N_("From"), N_("Reply-To"), N_("To"), N_("Cc"), N_("Bcc") + N_("From"), N_("Reply-To"), N_("To"), N_("Cc"), N_("Bcc") }; #endif @@ -337,18 +173,18 @@ emfq_format_address (GString *out, if (name && *name) { gchar *real, *mailaddr; - g_string_append_printf (out, "%s <", name); - /* rfc2368 for mailto syntax and url encoding extras */ + g_string_append_printf (out, "%s <", name); + /* rfc2368 for mailto syntax and url encoding extras */ if ((real = camel_header_encode_phrase ((guchar *) a->name))) { - mailaddr = g_strdup_printf ("%s <%s>", real, a->v.addr); + mailaddr = g_strdup_printf ("%s <%s>", real, a->v.addr); g_free (real); - mailto = camel_url_encode (mailaddr, "?=&()"); + mailto = camel_url_encode (mailaddr, "?=&()"); g_free (mailaddr); } else { - mailto = camel_url_encode (a->v.addr, "?=&()"); + mailto = camel_url_encode (a->v.addr, "?=&()"); } } else { - mailto = camel_url_encode (a->v.addr, "?=&()"); + mailto = camel_url_encode (a->v.addr, "?=&()"); } addr = camel_text_to_html (a->v.addr, flags, 0); g_string_append_printf ( @@ -358,15 +194,15 @@ emfq_format_address (GString *out, g_free (addr); if (name && *name) - g_string_append (out, ">"); + g_string_append (out, ">"); break; case CAMEL_HEADER_ADDRESS_GROUP: - g_string_append_printf (out, "%s: ", name); + g_string_append_printf (out, "%s: ", name); emfq_format_address (out, a->v.members); - g_string_append_printf (out, ";"); + g_string_append_printf (out, ";"); break; default: - g_warning ("Invalid address type"); + g_warning ("Invalid address type"); break; } @@ -374,7 +210,7 @@ emfq_format_address (GString *out, a = a->next; if (a) - g_string_append (out, ", "); + g_string_append (out, ", "); } } @@ -383,20 +219,20 @@ canon_header_name (gchar *name) { gchar *inptr = name; - /* canonicalise the header name... first letter is - * capitalised and any letter following a '-' also gets - * capitalised */ + /* canonicalise the header name... first letter is + * capitalised and any letter following a '-' also gets + * capitalised */ if (g_ascii_islower (*inptr)) - *inptr = g_ascii_toupper (*inptr); + *inptr = g_ascii_toupper (*inptr); inptr++; while (*inptr) { if (inptr[-1] == '-' && g_ascii_islower (*inptr)) - *inptr = g_ascii_toupper (*inptr); + *inptr = g_ascii_toupper (*inptr); else if (g_ascii_isupper (*inptr)) - *inptr = g_ascii_tolower (*inptr); + *inptr = g_ascii_tolower (*inptr); inptr++; } @@ -422,8 +258,8 @@ emfq_format_header (EMFormat *emf, strcpy (name, namein); canon_header_name (name); - /* Never quote Bcc headers */ - if (g_str_equal (name, "Bcc") || g_str_equal (name, "Resent-Bcc")) + /* Never quote Bcc headers */ + if (g_str_equal (name, "Bcc") || g_str_equal (name, "Resent-Bcc")) return; for (i = 0; addrspec_hdrs[i]; i++) { @@ -444,8 +280,8 @@ emfq_format_header (EMFormat *emf, buf = camel_header_unfold (txt); addrs = camel_header_address_decode ( - txt, emf->charset ? - emf->charset : emf->default_charset); + txt, em_format_get_charset (emf) ? + em_format_get_charset (emf) : em_format_get_default_charset (emf)); if (addrs == NULL) { g_free (buf); return; @@ -453,29 +289,29 @@ emfq_format_header (EMFormat *emf, g_free (buf); - html = g_string_new (""); + html = g_string_new (""); emfq_format_address (html, addrs); camel_header_address_unref (addrs); txt = value = html->str; g_string_free (html, FALSE); flags |= EM_FORMAT_HEADER_BOLD; is_html = TRUE; - } else if (!strcmp (name, "Subject")) { + } else if (!strcmp (name, "Subject")) { txt = camel_mime_message_get_subject (msg); - label = _("Subject"); + label = _("Subject"); flags |= EM_FORMAT_HEADER_BOLD; - } else if (!strcmp (name, "X-Evolution-Mailer")) { /* pseudo-header */ - if (!(txt = camel_medium_get_header (part, "x-mailer"))) - if (!(txt = camel_medium_get_header (part, "user-agent"))) - if (!(txt = camel_medium_get_header (part, "x-newsreader"))) - if (!(txt = camel_medium_get_header (part, "x-mimeole"))) + } else if (!strcmp (name, "X-Evolution-Mailer")) { /* pseudo-header */ + if (!(txt = camel_medium_get_header (part, "x-mailer"))) + if (!(txt = camel_medium_get_header (part, "user-agent"))) + if (!(txt = camel_medium_get_header (part, "x-newsreader"))) + if (!(txt = camel_medium_get_header (part, "x-mimeole"))) return; txt = value = camel_header_format_ctext (txt, charset); - label = _("Mailer"); + label = _("Mailer"); flags |= EM_FORMAT_HEADER_BOLD; - } else if (!strcmp (name, "Date") || !strcmp (name, "Resent-Date")) { + } else if (!strcmp (name, "Date") || !strcmp (name, "Resent-Date")) { if (!(txt = camel_medium_get_header (part, name))) return; @@ -506,10 +342,10 @@ emfq_format_headers (EMFormatQuote *emfq, return; ct = camel_mime_part_get_content_type ((CamelMimePart *) part); - charset = camel_content_type_param (ct, "charset"); + charset = camel_content_type_param (ct, "charset"); charset = camel_iconv_charset_name (charset); - /* dump selected headers */ + /* dump selected headers */ link = g_queue_peek_head_link (&emf->header_list); while (link != NULL) { EMFormatHeader *h = link->data; @@ -518,154 +354,335 @@ emfq_format_headers (EMFormatQuote *emfq, link = g_list_next (link); } - g_string_append (buffer, "<br>\n"); + g_string_append (buffer, "<br>\n"); } static void -emfq_format_message_prefix (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) +emfq_dispose (GObject *object) { - EMFormatQuote *emfq = (EMFormatQuote *) emf; + /* Chain up to parent's dispose() method. */ + G_OBJECT_CLASS (parent_class)->dispose (object); +} - if (emfq->priv->credits != NULL) { - camel_stream_write_string ( - stream, emfq->priv->credits, NULL, NULL); - camel_stream_write_string ( - stream, "<br>\n", NULL, NULL); - } +static void +emfq_finalize (GObject *object) +{ + EMFormatQuotePrivate *priv; + + priv = EM_FORMAT_QUOTE_GET_PRIVATE (object); + + g_free (priv->credits); + + /* Chain up to parent's finalize() method. */ + G_OBJECT_CLASS (parent_class)->finalize (object); } +/******************************************************************************/ static void -emfq_format_message (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) +emfq_parse_text_plain (EMFormat * emf, + CamelMimePart * part, + GString * part_id, + EMFormatParserInfo * info, + GCancellable * cancellable) { - EMFormatQuote *emfq = (EMFormatQuote *) emf; - GString *buffer; + EMFormatPURI *puri; + CamelMimePart *mp; + gint len; - buffer = g_string_sized_new (1024); + len = part_id->len; + g_string_append (part_id, ".text_plain"); - if (emfq->priv->flags & EM_FORMAT_QUOTE_CITE) - g_string_append ( - buffer, - "<!--+GtkHTML:<DATA class=\"ClueFlow\" " - "key=\"orig\" value=\"1\">-->\n" - "<blockquote type=cite>\n"); + mp = decode_inline_parts (part, cancellable); + if (mp) { - if (((CamelMimePart *) emf->message) != part) { - g_string_append_printf ( - buffer, - "%s</br>\n", - _("-------- Forwarded Message --------")); - emfq_format_headers (emfq, buffer, (CamelMedium *) part); - } else if (emfq->priv->flags & EM_FORMAT_QUOTE_HEADERS) - emfq_format_headers (emfq, buffer, (CamelMedium *) part); + if (CAMEL_IS_MULTIPART (camel_medium_get_content (CAMEL_MEDIUM (mp)))) { + em_format_parse_part (emf, mp, part_id, info, cancellable); + } - camel_stream_write ( - stream, buffer->str, buffer->len, cancellable, NULL); + g_object_unref (mp); + } - em_format_part (emf, stream, part, cancellable); + puri = em_format_puri_new (emf, sizeof (EMFormatPURI), part, part_id->str); + puri->write_func = emfq_write_text_plain; + puri->mime_type = g_strdup ("text/html"); + em_format_add_puri (emf, puri); - if (emfq->priv->flags & EM_FORMAT_QUOTE_CITE) - camel_stream_write_string ( - stream, "</blockquote><!--+GtkHTML:" - "<DATA class=\"ClueFlow\" clear=\"orig\">-->", - cancellable, NULL); + g_string_truncate (part_id, len); } -/* Decodes inline encoded parts of 'part'. The returned pointer, - * if not NULL, should be unreffed with g_object_unref(). */ -static CamelMimePart * -decode_inline_parts (CamelMimePart *part, - GCancellable *cancellable) +static void +emfq_parse_text_html (EMFormat * emf, + CamelMimePart * part, + GString * part_id, + EMFormatParserInfo * info, + GCancellable * cancellable) { - CamelMultipart *mp; - CamelStream *null; - CamelStream *filtered_stream; - EMInlineFilter *inline_filter; + EMFormatPURI *puri; + gint len; - g_return_val_if_fail (part != NULL, NULL); + len = part_id->len; + g_string_append (part_id, ".text_html"); - null = camel_stream_null_new (); - filtered_stream = camel_stream_filter_new (null); - g_object_unref (null); + puri = em_format_puri_new (emf, sizeof (EMFormatPURI), part, part_id->str); + puri->write_func = emfq_write_text_html; + puri->mime_type = g_strdup ("text/html"); + em_format_add_puri (emf, puri); - inline_filter = em_inline_filter_new ( - camel_mime_part_get_encoding (part), - camel_mime_part_get_content_type (part)); - camel_stream_filter_add ( - CAMEL_STREAM_FILTER (filtered_stream), - CAMEL_MIME_FILTER (inline_filter)); - camel_data_wrapper_decode_to_stream_sync ( - camel_medium_get_content (CAMEL_MEDIUM (part)), - filtered_stream, cancellable, NULL); - camel_stream_close (filtered_stream, cancellable, NULL); - g_object_unref (filtered_stream); + g_string_truncate (part_id, len); +} - if (!em_inline_filter_found_any (inline_filter)) { - g_object_unref (inline_filter); - return NULL; +static void +emfq_parse_text_enriched (EMFormat * emf, + CamelMimePart * part, + GString * part_id, + EMFormatParserInfo * info, + GCancellable * cancellable) +{ + EMFormatPURI *puri; + gint len; + + len = part_id->len; + g_string_append (part_id, ".text_enriched"); + + puri = em_format_puri_new (emf, sizeof (EMFormatPURI), part, part_id->str); + puri->write_func = emfq_write_text_enriched; + puri->mime_type = g_strdup ("text/html"); + em_format_add_puri (emf, puri); + + g_string_truncate (part_id, len); +} + +static void +emfq_parse_attachment (EMFormat * emf, + CamelMimePart * part, + GString * part_id, + EMFormatParserInfo * info, + GCancellable * cancellable) +{ + EMFormatPURI *puri; + gint len; + + len = part_id->len; + g_string_append (part_id, ".attachment"); + + puri = em_format_puri_new (emf, sizeof (EMFormatPURI), part, part_id->str); + puri->write_func = emfq_write_text_html; + puri->mime_type = g_strdup ("text/html"); + puri->is_attachment = TRUE; + em_format_add_puri (emf, puri); + + g_string_truncate (part_id, len); +} + +/******************************************************************************/ + +static void +emfq_write_attachment (EMFormat *emf, + EMFormatPURI *puri, + CamelStream *stream, + EMFormatWriterInfo *info, + GCancellable *cancellable) +{ + EMFormatQuote *emfq = EM_FORMAT_QUOTE (emf); + const EMFormatHandler *handler; + gchar *text, *html; + CamelContentType *ct; + const gchar *mime_type; + + ct = camel_mime_part_get_content_type (puri->part); + if (ct) { + mime_type = camel_content_type_simple (ct); + camel_content_type_unref (ct); + } else { + mime_type = "application/octet-stream"; } - mp = em_inline_filter_get_multipart (inline_filter); + handler = em_format_find_handler (emf, mime_type); - g_object_unref (inline_filter); + if (!em_format_is_inline (emf, puri->uri, puri->part, handler)) + return; - if (mp) { - part = camel_mime_part_new (); - camel_medium_set_content ( - CAMEL_MEDIUM (part), CAMEL_DATA_WRAPPER (mp)); - g_object_unref (mp); + camel_stream_write_string ( + stream, "<table border=1 cellspacing=0 cellpadding=0>" + "<tr><td><font size=-1>\n", cancellable, NULL); + + /* output some info about it */ + text = em_format_describe_part (puri->part, mime_type); + html = camel_text_to_html ( + text, emfq->priv->text_html_flags & + CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS, 0); + camel_stream_write_string (stream, html, cancellable, NULL); + g_free (html); + g_free (text); + + camel_stream_write_string ( + stream, "</font></td></tr></table>", cancellable, NULL); + + if (handler && handler->write_func) + handler->write_func (emf, puri, stream, info, cancellable); +} + +static void +emfq_base_init (EMFormatQuoteClass *klass) +{ + emfq_builtin_init (klass); +} + +static void +emfq_class_init (EMFormatQuoteClass *klass) +{ + GObjectClass *object_class; + + parent_class = g_type_class_peek_parent (klass); + g_type_class_add_private (klass, sizeof (EMFormatQuotePrivate)); + + object_class = G_OBJECT_CLASS (klass); + object_class->dispose = emfq_dispose; + object_class->finalize = emfq_finalize; +} + +static void +emfq_init (EMFormatQuote *emfq) +{ + emfq->priv = EM_FORMAT_QUOTE_GET_PRIVATE (emfq); + + /* we want to convert url's etc */ + emfq->priv->text_html_flags = + CAMEL_MIME_FILTER_TOHTML_PRE | + CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS | + CAMEL_MIME_FILTER_TOHTML_CONVERT_ADDRESSES; +} + +GType +em_format_quote_get_type (void) +{ + static GType type = 0; + + if (G_UNLIKELY (type == 0)) { + static const GTypeInfo type_info = { + sizeof (EMFormatQuoteClass), + (GBaseInitFunc) emfq_base_init, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) emfq_class_init, + (GClassFinalizeFunc) NULL, + NULL, /* class_data */ + sizeof (EMFormatQuote), + 0, /* n_preallocs */ + (GInstanceInitFunc) emfq_init, + NULL /* value_table */ + }; + + type = g_type_register_static ( + EM_TYPE_FORMAT, "EMFormatQuote", &type_info, 0); + } + + return type; +} + +EMFormatQuote * +em_format_quote_new (const gchar *credits, + CamelStream *stream, + EMFormatQuoteFlags flags) +{ + EMFormatQuote *emfq; + + g_return_val_if_fail (CAMEL_IS_STREAM (stream), NULL); + + /* Steam must also be seekable so we can reset its position. */ + g_return_val_if_fail (G_IS_SEEKABLE (stream), NULL); + + emfq = g_object_new (EM_TYPE_FORMAT_QUOTE, NULL); + + emfq->priv->credits = g_strdup (credits); + emfq->priv->flags = flags; + + return emfq; +} + +void +em_format_quote_write (EMFormatQuote * emfq, + CamelStream * stream, + GCancellable * cancellable) +{ + EMFormat *emf; + GSettings *settings; + GList *iter; + EMFormatWriterInfo info = { 0 }; + + emf = (EMFormat *) emfq; + + g_seekable_seek ( + G_SEEKABLE (stream), + 0, G_SEEK_SET, NULL, NULL); + + settings = g_settings_new ("org.gnome.evolution.mail"); + if (g_settings_get_boolean ( + settings, "composer-top-signature")) + camel_stream_write_string ( + stream, "<br>\n", cancellable, NULL); + g_object_unref (settings); + + if (emfq->priv->credits && *emfq->priv->credits) { + gchar *credits = g_strdup_printf ("%s<br/>", emfq->priv->credits); + camel_stream_write_string (stream, credits, cancellable, NULL); + g_free (credits); } else { - g_object_ref (part); + camel_stream_write_string (stream, "<br/>", cancellable, NULL); } - return part; + if (emfq->priv->flags & EM_FORMAT_QUOTE_CITE) + camel_stream_write_string (stream, + "<!--+GtkHTML:<DATA class=\"ClueFlow\" " + "key=\"orig\" value=\"1\">-->\n" + "<blockquote type=cite>\n", cancellable, NULL); + + for (iter = emf->mail_part_list; iter; iter = iter->next) { + EMFormatPURI *puri = iter->data; + + if (puri->is_attachment || !puri->write_func) + continue; + + puri = iter->data; + + if (emfq->priv->flags & EM_FORMAT_QUOTE_HEADERS) { + GString *buffer = g_string_new (""); + emfq_format_headers (emfq, buffer, (CamelMedium *) puri->part); + camel_stream_write_string (stream, buffer->str, cancellable, NULL); + g_string_free (buffer, TRUE); + } + + puri->write_func (emf, puri, stream, &info, cancellable); + } + + if (emfq->priv->flags & EM_FORMAT_QUOTE_CITE) + camel_stream_write_string ( + stream, "</blockquote><!--+GtkHTML:" + "<DATA class=\"ClueFlow\" clear=\"orig\">-->", + cancellable, NULL); } static void -emfq_text_plain (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) +emfq_write_text_plain (EMFormat *emf, + EMFormatPURI *puri, + CamelStream *stream, + EMFormatWriterInfo *info, + GCancellable *cancellable) { EMFormatQuote *emfq = EM_FORMAT_QUOTE (emf); CamelStream *filtered_stream; CamelMimeFilter *html_filter; CamelMimeFilter *sig_strip; - CamelMimePart *mp; CamelContentType *type; const gchar *format; guint32 rgb = 0x737373, flags; - if (!part) + if (!puri->part) return; - mp = decode_inline_parts (part, cancellable); - if (mp) { - if (CAMEL_IS_MULTIPART (camel_medium_get_content (CAMEL_MEDIUM (mp)))) { - em_format_part (emf, stream, mp, cancellable); - g_object_unref (mp); - - return; - } - - g_object_unref (mp); - } - flags = emfq->priv->text_html_flags; /* Check for RFC 2646 flowed text. */ - type = camel_mime_part_get_content_type (part); + type = camel_mime_part_get_content_type (puri->part); if (camel_content_type_is(type, "text", "plain") && (format = camel_content_type_param(type, "format")) && !g_ascii_strcasecmp(format, "flowed")) @@ -687,25 +704,32 @@ emfq_text_plain (EMFormat *emf, em_format_format_text ( EM_FORMAT (emfq), filtered_stream, - CAMEL_DATA_WRAPPER (part), cancellable); + CAMEL_DATA_WRAPPER (puri->part), cancellable); camel_stream_flush (filtered_stream, cancellable, NULL); g_object_unref (filtered_stream); } static void -emfq_text_enriched (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) +emfq_write_text_enriched (EMFormat *emf, + EMFormatPURI *puri, + CamelStream *stream, + EMFormatWriterInfo *info, + GCancellable *cancellable) { CamelStream *filtered_stream; CamelMimeFilter *enriched; guint32 flags = 0; + CamelContentType *ct; + const gchar *mime_type = NULL; + + ct = camel_mime_part_get_content_type (puri->part); + if (ct) { + mime_type = camel_content_type_simple (ct); + camel_content_type_unref (ct); + } - if (g_strcmp0 (info->mime_type, "text/richtext") == 0) { + if (g_strcmp0 (mime_type, "text/richtext") == 0) { flags = CAMEL_MIME_FILTER_ENRICHED_IS_RICHTEXT; camel_stream_write_string ( stream, "\n<!-- text/richtext -->\n", @@ -724,18 +748,17 @@ emfq_text_enriched (EMFormat *emf, camel_stream_write_string (stream, "<br><hr><br>", cancellable, NULL); em_format_format_text ( - emf, filtered_stream, CAMEL_DATA_WRAPPER (part), cancellable); + emf, filtered_stream, CAMEL_DATA_WRAPPER (puri->part), cancellable); camel_stream_flush (filtered_stream, cancellable, NULL); g_object_unref (filtered_stream); } static void -emfq_text_html (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) +emfq_write_text_html (EMFormat *emf, + EMFormatPURI *puri, + CamelStream *stream, + EMFormatWriterInfo *info, + GCancellable *cancellable) { EMFormatQuotePrivate *priv; @@ -757,40 +780,29 @@ emfq_text_html (EMFormat *emf, em_format_format_text ( emf, filtered_stream, - (CamelDataWrapper *) part, cancellable); + (CamelDataWrapper *) puri->part, cancellable); camel_stream_flush (filtered_stream, cancellable, NULL); g_object_unref (filtered_stream); } else { em_format_format_text ( emf, stream, - (CamelDataWrapper *) part, cancellable); + (CamelDataWrapper *) puri->part, cancellable); } } -static void -emfq_ignore (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) -{ - /* NOOP */ -} - +/****************************************************************************/ static EMFormatHandler type_builtin_table[] = { - { (gchar *) "text/plain", emfq_text_plain }, - { (gchar *) "text/enriched", emfq_text_enriched }, - { (gchar *) "text/richtext", emfq_text_enriched }, - { (gchar *) "text/html", emfq_text_html }, - { (gchar *) "text/*", emfq_text_plain }, - { (gchar *) "message/external-body", emfq_ignore }, - { (gchar *) "multipart/appledouble", emfq_ignore }, + { (gchar *) "text/plain", emfq_parse_text_plain, emfq_write_text_plain, }, + { (gchar *) "text/enriched", emfq_parse_text_enriched, emfq_write_text_enriched, }, + { (gchar *) "text/richtext", emfq_parse_text_enriched, emfq_write_text_enriched, }, + { (gchar *) "text/html", emfq_parse_text_html, emfq_write_text_html, }, + { (gchar *) "text/*", emfq_parse_text_plain, emfq_write_text_plain, }, + { (gchar *) "message/external-body", em_format_empty_parser, em_format_empty_writer, }, + { (gchar *) "multipart/appledouble", em_format_empty_parser, em_format_empty_writer, }, /* internal evolution types */ - { (gchar *) "x-evolution/evolution-rss-feed", emfq_text_html }, - { (gchar *) "x-evolution/message/rfc822", emfq_format_message }, - { (gchar *) "x-evolution/message/prefix", emfq_format_message_prefix }, + { (gchar *) "x-evolution/evolution-rss-feed", 0, emfq_write_text_html, }, + { (gchar *) "x-evolution/message/attachment", emfq_parse_attachment, emfq_write_attachment, }, }; static void @@ -798,7 +810,9 @@ emfq_builtin_init (EMFormatQuoteClass *efhc) { gint ii; + EMFormatClass *emfc = (EMFormatClass *) efhc; + for (ii = 0; ii < G_N_ELEMENTS (type_builtin_table); ii++) em_format_class_add_handler ( - EM_FORMAT_CLASS (efhc), &type_builtin_table[ii]); + emfc, &type_builtin_table[ii]); } diff --git a/em-format/em-format-quote.h b/em-format/em-format-quote.h index 5c1882eb32..be3640735a 100644 --- a/em-format/em-format-quote.h +++ b/em-format/em-format-quote.h @@ -69,6 +69,9 @@ GType em_format_quote_get_type (void); EMFormatQuote * em_format_quote_new (const gchar *credits, CamelStream *stream, EMFormatQuoteFlags flags); +void em_format_quote_write (EMFormatQuote *emfq, + CamelStream *stream, + GCancellable *cancellable); G_END_DECLS diff --git a/em-format/em-format.c b/em-format/em-format.c index d476036f77..4abe35482c 100644 --- a/em-format/em-format.c +++ b/em-format/em-format.c @@ -25,1018 +25,1577 @@ #include <config.h> #endif -#include <stdio.h> #include <string.h> - #include <gio/gio.h> #include <glib/gi18n-lib.h> +#include <libsoup/soup-uri.h> #include "em-format.h" #include "e-util/e-util.h" #include "shell/e-shell.h" #include "shell/e-shell-settings.h" +#define d(x) + #define EM_FORMAT_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), EM_TYPE_FORMAT, EMFormatPrivate)) -#define d(x) - -typedef struct _EMFormatCache EMFormatCache; - struct _EMFormatPrivate { - guint redraw_idle_id; -}; + GNode *current_node; -/* Used to cache various data/info for redraws - * The validity stuff could be cached at a higher level but this is easier - * This absolutely relies on the partid being _globally unique_ - * This is still kind of yucky, we should maintain a full tree of all this data, - * along with/as part of the puri tree */ -struct _EMFormatCache { - CamelCipherValidity *valid; /* validity copy */ - CamelMimePart *secured; /* encrypted subpart */ + CamelSession *session; - guint state:2; /* inline state */ + CamelURL *base_url; - gchar partid[1]; -}; + gchar *charset; + gchar *default_charset; + gboolean composer; -#define INLINE_UNSET (0) -#define INLINE_ON (1) -#define INLINE_OFF (2) + gint last_error; +}; -static void emf_builtin_init (EMFormatClass *); +enum { + PROP_0, + PROP_CHARSET, + PROP_DEFAULT_CHARSET, + PROP_COMPOSER, + PROP_BASE_URL +}; enum { - EMF_COMPLETE, - EMF_LAST_SIGNAL + REDRAW_REQUESTED, + LAST_SIGNAL }; +gint signals[LAST_SIGNAL]; + static gpointer parent_class; -static guint signals[EMF_LAST_SIGNAL]; -static void -emf_free_cache (EMFormatCache *efc) -{ - if (efc->valid) - camel_cipher_validity_free (efc->valid); - if (efc->secured) - g_object_unref (efc->secured); - g_free (efc); -} +/* PARSERS */ +static void emf_parse_application_xpkcs7mime (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); +static void emf_parse_application_mbox (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); +static void emf_parse_multipart_alternative (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); +static void emf_parse_multipart_appledouble (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); +static void emf_parse_multipart_encrypted (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); +static void emf_parse_multipart_mixed (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); +static void emf_parse_multipart_signed (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); +static void emf_parse_multipart_related (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); +static void emf_parse_multipart_digest (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); +static void emf_parse_message_deliverystatus (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); +static void emf_parse_inlinepgp_signed (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); +static void emf_parse_inlinepgp_encrypted (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); +static void emf_parse_message (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); +static void emf_parse_headers (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); +static void emf_parse_post_headers (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); +static void emf_parse_source (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); + +/* WRITERS */ +static void emf_write_text (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable); +static void emf_write_source (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable); +static void emf_write_error (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable); + +/**************************************************************************/ -static EMFormatCache * -emf_insert_cache (EMFormat *emf, - const gchar *partid) +static gboolean +is_secured (CamelMimePart *part) { - EMFormatCache *new; - - new = g_malloc0 (sizeof (*new) + strlen (partid)); - strcpy (new->partid, partid); - g_hash_table_insert (emf->inline_table, new->partid, new); + CamelContentType *ct = camel_mime_part_get_content_type (part); - return new; + return (camel_content_type_is (ct, "multipart", "signed") || + camel_content_type_is (ct, "multipart", "encrypted") || + camel_content_type_is (ct, "application", "x-inlinepgp-signed") || + camel_content_type_is (ct, "application", "x-inlinepgp-encrypted") || + camel_content_type_is (ct, "application", "x-pkcs7-mime") || + camel_content_type_is (ct, "application", "pkcs7-mime")); } static void -emf_clone_inlines (gpointer key, - gpointer val, - gpointer data) +preserve_charset_in_content_type (CamelMimePart *ipart, + CamelMimePart *opart) { - EMFormatCache *emfc = val, *new; + CamelDataWrapper *data_wrapper; + CamelContentType *content_type; + const gchar *charset; - new = emf_insert_cache ((EMFormat *) data, emfc->partid); - new->state = emfc->state; - if (emfc->valid) - new->valid = camel_cipher_validity_clone (emfc->valid); - if (emfc->secured) - g_object_ref ((new->secured = emfc->secured)); -} + g_return_if_fail (ipart != NULL); + g_return_if_fail (opart != NULL); -static gboolean -emf_clear_puri_node (GNode *node) -{ - GQueue *queue = node->data; - EMFormatPURI *pn; + data_wrapper = camel_medium_get_content (CAMEL_MEDIUM (ipart)); + content_type = camel_data_wrapper_get_mime_type_field (data_wrapper); - while ((pn = g_queue_pop_head (queue)) != NULL) { - if (pn->free != NULL) - pn->free (pn); - g_free (pn->uri); - g_free (pn->cid); - g_free (pn->part_id); - if (pn->part != NULL) - g_object_unref (pn->part); - g_free (pn); - } + if (content_type == NULL) + return; + + charset = camel_content_type_param (content_type, "charset"); - g_queue_free (queue); + if (charset == NULL || *charset == '\0') + return; - return FALSE; + data_wrapper = camel_medium_get_content (CAMEL_MEDIUM (opart)); + content_type = camel_data_wrapper_get_mime_type_field (data_wrapper); + + camel_content_type_set_param (content_type, "charset", charset); } -static void -emf_finalize (GObject *object) +static CamelMimePart * +get_related_display_part (CamelMimePart *part, + gint *out_displayid) { - EMFormat *emf = EM_FORMAT (object); - - if (emf->priv->redraw_idle_id > 0) - g_source_remove (emf->priv->redraw_idle_id); + CamelMultipart *mp; + CamelMimePart *body_part, *display_part = NULL; + CamelContentType *content_type; + const gchar *start; + gint i, nparts, displayid = 0; - if (emf->session) - g_object_unref (emf->session); + mp = (CamelMultipart *) camel_medium_get_content ((CamelMedium *) part); - if (emf->message) - g_object_unref (emf->message); + if (!CAMEL_IS_MULTIPART (mp)) + return NULL; - g_hash_table_destroy (emf->inline_table); + nparts = camel_multipart_get_number (mp); + content_type = camel_mime_part_get_content_type (part); + start = camel_content_type_param (content_type, "start"); + if (start && strlen (start) > 2) { + gint len; + const gchar *cid; - em_format_clear_headers (emf); - camel_cipher_validity_free (emf->valid); - g_free (emf->charset); - g_free (emf->default_charset); - g_string_free (emf->part_id, TRUE); - g_free (emf->current_message_part_id); - g_free (emf->uid); + /* strip <>'s from CID */ + len = strlen (start) - 2; + start++; - if (emf->pending_uri_table != NULL) - g_hash_table_destroy (emf->pending_uri_table); + for (i = 0; i < nparts; i++) { + body_part = camel_multipart_get_part (mp, i); + cid = camel_mime_part_get_content_id (body_part); - if (emf->pending_uri_tree != NULL) { - g_node_traverse ( - emf->pending_uri_tree, - G_IN_ORDER, G_TRAVERSE_ALL, -1, - (GNodeTraverseFunc) emf_clear_puri_node, NULL); - g_node_destroy (emf->pending_uri_tree); + if (cid && !strncmp (cid, start, len) && strlen (cid) == len) { + display_part = body_part; + displayid = i; + break; + } + } + } else { + display_part = camel_multipart_get_part (mp, 0); } - /* FIXME: check pending jobs */ + if (out_displayid) + *out_displayid = displayid; - /* Chain up to parent's finalize() method. */ - G_OBJECT_CLASS (parent_class)->finalize (object); + return display_part; } -static const EMFormatHandler * -emf_find_handler (EMFormat *emf, - const gchar *mime_type) +static gboolean +related_display_part_is_attachment (EMFormat *emf, + CamelMimePart *part) { - EMFormatClass *emfc = (EMFormatClass *) G_OBJECT_GET_CLASS (emf); + CamelMimePart *display_part; - return g_hash_table_lookup (emfc->type_handlers, mime_type); + display_part = get_related_display_part (part, NULL); + return display_part && em_format_is_attachment (emf, display_part); +} + +/**************************************************************************/ +void +em_format_empty_parser (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) +{ + /* DO NOTHING */ } +#ifdef ENABLE_SMIME static void -emf_format_clone (EMFormat *emf, - CamelFolder *folder, - const gchar *uid, - CamelMimeMessage *msg, - EMFormat *emfsource, - GCancellable *cancellable) +emf_parse_application_xpkcs7mime (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) { - /* Cancel any pending redraws. */ - if (emf->priv->redraw_idle_id > 0) { - g_source_remove (emf->priv->redraw_idle_id); - emf->priv->redraw_idle_id = 0; + CamelCipherContext *context; + CamelMimePart *opart; + CamelCipherValidity *valid; + GError *local_error = NULL; + + if (g_cancellable_is_cancelled (cancellable)) + return; + + context = camel_smime_context_new (emf->priv->session); + + opart = camel_mime_part_new (); + valid = camel_cipher_context_decrypt_sync ( + context, part, opart, cancellable, &local_error); + preserve_charset_in_content_type (part, opart); + if (valid == NULL) { + em_format_format_error ( + emf, "%s", + local_error->message ? local_error->message : + _("Could not parse S/MIME message: Unknown error")); + g_clear_error (&local_error); + } else { + EMFormatParserInfo encinfo = { + info->handler, + info->validity_type | EM_FORMAT_VALIDITY_FOUND_ENCRYPTED | EM_FORMAT_VALIDITY_FOUND_SMIME, + valid + }; + gint len = part_id->len; + + g_string_append (part_id, ".encrypted"); + em_format_parse_part (emf, opart, part_id, &encinfo, cancellable); + g_string_truncate (part_id, len); + + /* Add a widget with details about the encryption, but only when + * the encrypted isn't itself secured, in that case it has created + * the button itself */ + if (!is_secured (opart)) { + g_string_append (part_id, ".encrypted.button"); + em_format_parse_part_as (emf, part, part_id, &encinfo, + "x-evolution/message/x-secure-button", cancellable); + g_string_truncate (part_id, len); + } + + camel_cipher_validity_free (valid); } - em_format_clear_puri_tree (emf); + g_object_unref (opart); + g_object_unref (context); +} +#endif + +/* RFC 4155 */ +static void +emf_parse_application_mbox (EMFormat *emf, + CamelMimePart *mime_part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) +{ + CamelMimeParser *parser; + CamelStream *mem_stream; + camel_mime_parser_state_t state; + gint old_len; + gint messages; - if (emf != emfsource) { - g_hash_table_remove_all (emf->inline_table); - if (emfsource) { - GList *link; + if (g_cancellable_is_cancelled (cancellable)) + return; - /* We clone the current state here */ - g_hash_table_foreach (emfsource->inline_table, emf_clone_inlines, emf); - emf->mode = emfsource->mode; - g_free (emf->charset); - emf->charset = g_strdup (emfsource->charset); - g_free (emf->default_charset); - emf->default_charset = g_strdup (emfsource->default_charset); + /* Extract messages from the application/mbox part and + * render them as a flat list of messages. */ - em_format_clear_headers (emf); + /* XXX If the mbox has multiple messages, maybe render them + * as a multipart/digest so each message can be expanded + * or collapsed individually. + * + * See attachment_handler_mail_x_uid_list() for example. */ - link = g_queue_peek_head_link (&emfsource->header_list); - while (link != NULL) { - struct _EMFormatHeader *h = link->data; - em_format_add_header (emf, h->name, h->flags); - link = g_list_next (link); - } + /* XXX This is based on em_utils_read_messages_from_stream(). + * Perhaps refactor that function to return an array of + * messages instead of assuming we want to append them + * to a folder? */ + + parser = camel_mime_parser_new (); + camel_mime_parser_scan_from (parser, TRUE); + + mem_stream = camel_stream_mem_new (); + camel_data_wrapper_decode_to_stream_sync ( + camel_medium_get_content (CAMEL_MEDIUM (mime_part)), + mem_stream, NULL, NULL); + g_seekable_seek (G_SEEKABLE (mem_stream), 0, G_SEEK_SET, NULL, NULL); + camel_mime_parser_init_with_stream (parser, mem_stream, NULL); + g_object_unref (mem_stream); + + old_len = part_id->len; + + /* Extract messages from the mbox. */ + messages = 0; + state = camel_mime_parser_step (parser, NULL, NULL); + + while (state == CAMEL_MIME_PARSER_STATE_FROM) { + CamelMimeMessage *message; + + message = camel_mime_message_new (); + mime_part = CAMEL_MIME_PART (message); + + if (!camel_mime_part_construct_from_parser_sync ( + mime_part, parser, NULL, NULL)) { + g_object_unref (message); + break; } - } - /* what a mess */ - if (folder != emf->folder) { - if (emf->folder) - g_object_unref (emf->folder); - if (folder) - g_object_ref (folder); - emf->folder = folder; - } + g_string_append_printf (part_id, ".mbox.%d", messages); + em_format_parse_part_as (emf, CAMEL_MIME_PART (message), + part_id, info, "message/rfc822", cancellable); + g_string_truncate (part_id, old_len); - if (uid != emf->uid) { - g_free (emf->uid); - emf->uid = g_strdup (uid); - } + g_object_unref (message); - if (msg != emf->message) { - if (emf->message) - g_object_unref (emf->message); - if (msg) - g_object_ref (msg); - emf->message = msg; + /* Skip past CAMEL_MIME_PARSER_STATE_FROM_END. */ + camel_mime_parser_step (parser, NULL, NULL); + + state = camel_mime_parser_step (parser, NULL, NULL); + + messages++; } - g_free (emf->current_message_part_id); - emf->current_message_part_id = g_strdup ("root-message"); - g_string_truncate (emf->part_id, 0); - if (folder != NULL) - /* TODO build some string based on the folder name/location? */ - g_string_append_printf(emf->part_id, ".%p", (gpointer) folder); - if (uid != NULL) - g_string_append_printf(emf->part_id, ".%s", uid); + g_object_unref (parser); } +/* RFC 1740 */ static void -emf_format_secure (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - CamelCipherValidity *valid, - GCancellable *cancellable) +emf_parse_multipart_alternative (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) { - CamelCipherValidity *save = emf->valid_parent; - gint len; + CamelMultipart *mp; + gint i, nparts, bestid = 0; + CamelMimePart *best = NULL; - /* Note that this also requires support from higher up in the class chain - * - validity needs to be cleared when you start output - * - also needs to be cleared (but saved) whenever you start a new message. */ + if (g_cancellable_is_cancelled (cancellable)) + return; - if (emf->valid == NULL) { - emf->valid = valid; - } else { - g_queue_push_tail (&emf->valid_parent->children, valid); - camel_cipher_validity_envelope (emf->valid_parent, valid); + mp = (CamelMultipart *) camel_medium_get_content ((CamelMedium *) part); + + if (!CAMEL_IS_MULTIPART (mp)) { + emf_parse_source (emf, part, part_id, info, cancellable); + return; } - emf->valid_parent = valid; + /* as per rfc, find the last part we know how to display */ + nparts = camel_multipart_get_number (mp); + for (i = 0; i < nparts; i++) { + CamelMimePart *mpart; + CamelDataWrapper *data_wrapper; + CamelContentType *type; + CamelStream *null_stream; + gchar *mime_type; + gsize content_size; - len = emf->part_id->len; - g_string_append_printf(emf->part_id, ".secured"); - em_format_part (emf, stream, part, cancellable); - g_string_truncate (emf->part_id, len); + if (g_cancellable_is_cancelled (cancellable)) + return; - emf->valid_parent = save; -} + /* is it correct to use the passed in *part here? */ + mpart = camel_multipart_get_part (mp, i); -static gboolean -emf_busy (EMFormat *emf) -{ - return FALSE; -} + if (mpart == NULL) + continue; -static gboolean -emf_is_inline (EMFormat *emf, - const gchar *part_id, - CamelMimePart *mime_part, - const EMFormatHandler *handle) -{ - EMFormatCache *emfc; - const gchar *disposition; + /* This may block even though the stream does not. + * XXX Pretty inefficient way to test if the MIME part + * is empty. Surely there's a quicker way? */ + null_stream = camel_stream_null_new (); + data_wrapper = camel_medium_get_content (CAMEL_MEDIUM (mpart)); + camel_data_wrapper_decode_to_stream_sync ( + data_wrapper, null_stream, cancellable, NULL); + content_size = CAMEL_STREAM_NULL (null_stream)->written; + g_object_unref (null_stream); - if (handle == NULL) - return FALSE; + if (content_size == 0) + continue; - emfc = g_hash_table_lookup (emf->inline_table, part_id); - if (emfc && emfc->state != INLINE_UNSET) - return emfc->state & 1; + type = camel_mime_part_get_content_type (mpart); + mime_type = camel_content_type_simple (type); - /* Some types need to override the disposition. - * e.g. application/x-pkcs7-mime */ - if (handle->flags & EM_FORMAT_HANDLER_INLINE_DISPOSITION) - return TRUE; + camel_strdown (mime_type); - disposition = camel_mime_part_get_disposition (mime_part); - if (disposition != NULL) - return g_ascii_strcasecmp (disposition, "inline") == 0; + if (!em_format_is_attachment (emf, mpart) && + ((camel_content_type_is (type, "multipart", "related") == 0) || + !related_display_part_is_attachment (emf, mpart)) && + (em_format_find_handler (emf, mime_type) + || (best == NULL && em_format_fallback_handler (emf, mime_type)))) { + best = mpart; + bestid = i; + } - /* Otherwise, use the default for this handler type. */ - return (handle->flags & EM_FORMAT_HANDLER_INLINE) != 0; + g_free (mime_type); + } + + if (best) { + gint len = part_id->len; + + g_string_append_printf(part_id, ".alternative.%d", bestid); + em_format_parse_part (emf, best, part_id, info, cancellable); + g_string_truncate (part_id, len); + } else + emf_parse_multipart_mixed (emf, part, part_id, info, cancellable); } +/* RFC 1740 */ static void -emf_base_init (EMFormatClass *class) +emf_parse_multipart_appledouble (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) { - class->type_handlers = g_hash_table_new (g_str_hash, g_str_equal); - emf_builtin_init (class); + CamelMultipart *mp; + CamelMimePart *mime_part; + + if (g_cancellable_is_cancelled (cancellable)) + return; + + mp = (CamelMultipart *) camel_medium_get_content ((CamelMedium *) part); + + if (!CAMEL_IS_MULTIPART (mp)) { + emf_parse_source (emf, part, part_id, info, cancellable); + return; + } + + mime_part = camel_multipart_get_part (mp, 1); + if (mime_part) { + gint len; + /* try the data fork for something useful, doubtful but who knows */ + len = part_id->len; + g_string_append_printf(part_id, ".appledouble.1"); + em_format_parse_part (emf, mime_part, part_id, info, cancellable); + g_string_truncate (part_id, len); + } else { + emf_parse_source (emf, part, part_id, info, cancellable); + } } static void -emf_class_init (EMFormatClass *class) +emf_parse_multipart_encrypted (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) { - GObjectClass *object_class; + CamelCipherContext *context; + const gchar *protocol; + CamelMimePart *opart; + CamelCipherValidity *valid; + CamelMultipartEncrypted *mpe; + GError *local_error = NULL; - parent_class = g_type_class_peek_parent (class); - g_type_class_add_private (class, sizeof (EMFormatPrivate)); + if (g_cancellable_is_cancelled (cancellable)) + return; - object_class = G_OBJECT_CLASS (class); - object_class->finalize = emf_finalize; + mpe = (CamelMultipartEncrypted *) camel_medium_get_content ((CamelMedium *) part); + if (!CAMEL_IS_MULTIPART_ENCRYPTED (mpe)) { + em_format_format_error ( + emf, _("Could not parse MIME message. " + "Displaying as source.")); + emf_parse_source (emf, part, part_id, info, cancellable); + return; + } - class->find_handler = emf_find_handler; - class->format_clone = emf_format_clone; - class->format_secure = emf_format_secure; - class->busy = emf_busy; - class->is_inline = emf_is_inline; + /* Currently we only handle RFC2015-style PGP encryption. */ + protocol = camel_content_type_param ( + ((CamelDataWrapper *)mpe)->mime_type, "protocol"); + if (!protocol || g_ascii_strcasecmp (protocol, "application/pgp-encrypted") != 0) { + em_format_format_error (emf, _("Unsupported encryption type for multipart/encrypted")); + emf_parse_multipart_mixed (emf, part, part_id, info, cancellable); + return; + } - signals[EMF_COMPLETE] = g_signal_new ( - "complete", - G_OBJECT_CLASS_TYPE (class), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (EMFormatClass, complete), - NULL, NULL, - g_cclosure_marshal_VOID__VOID, - G_TYPE_NONE, 0); + context = camel_gpg_context_new (emf->priv->session); + opart = camel_mime_part_new (); + valid = camel_cipher_context_decrypt_sync ( + context, part, opart, cancellable, &local_error); + preserve_charset_in_content_type (part, opart); + if (valid == NULL) { + em_format_format_error ( + emf, local_error->message ? + _("Could not parse PGP/MIME message") : + _("Could not parse PGP/MIME message: Unknown error")); + if (local_error->message != NULL) + em_format_format_error ( + emf, "%s", local_error->message); + g_clear_error (&local_error); + emf_parse_multipart_mixed (emf, part, part_id, info, cancellable); + } else { + gint len = part_id->len; + + EMFormatParserInfo encinfo = { + info->handler, + info->validity_type | EM_FORMAT_VALIDITY_FOUND_ENCRYPTED | EM_FORMAT_VALIDITY_FOUND_PGP, + }; + + if (info->validity) + camel_cipher_validity_envelope (valid, info->validity); + + encinfo.validity = valid; + + g_string_append (part_id, ".encrypted"); + em_format_parse_part (emf, opart, part_id, &encinfo, cancellable); + g_string_truncate (part_id, len); + + /* Add a widget with details about the encryption, but only when + * the encrypted isn't itself secured, in that case it has created + * the button itself */ + if (!is_secured (opart)) { + g_string_append (part_id, ".encrypted.button"); + em_format_parse_part_as (emf, part, part_id, &encinfo, + "x-evolution/message/x-secure-button", cancellable); + g_string_truncate (part_id, len); + } + + camel_cipher_validity_free (valid); + } + + /* TODO: Make sure when we finalize this part, it is zero'd out */ + g_object_unref (opart); + g_object_unref (context); } +/* RFC 2046 */ static void -emf_init (EMFormat *emf) +emf_parse_multipart_mixed (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) { - EShell *shell; - EShellSettings *shell_settings; + CamelMultipart *mp; + gint i, nparts, len; - emf->priv = EM_FORMAT_GET_PRIVATE (emf); + if (g_cancellable_is_cancelled (cancellable)) + return; - emf->inline_table = g_hash_table_new_full ( - g_str_hash, g_str_equal, - (GDestroyNotify) NULL, - (GDestroyNotify) emf_free_cache); - emf->composer = FALSE; - emf->print = FALSE; - g_queue_init (&emf->header_list); - em_format_default_headers (emf); - emf->part_id = g_string_new(""); - emf->current_message_part_id = NULL; - emf->validity_found = 0; + mp = (CamelMultipart *) camel_medium_get_content ((CamelMedium *) part); - shell = e_shell_get_default (); - shell_settings = e_shell_get_shell_settings (shell); + if (!CAMEL_IS_MULTIPART (mp)) { + emf_parse_source (emf, part, part_id, info, cancellable); + return; + } + + len = part_id->len; + nparts = camel_multipart_get_number (mp); + for (i = 0; i < nparts; i++) { + CamelMimePart *subpart; - emf->session = e_shell_settings_get_pointer (shell_settings, "mail-session"); - g_return_if_fail (emf->session != NULL); + subpart = camel_multipart_get_part (mp, i); - g_object_ref (emf->session); + g_string_append_printf(part_id, ".mixed.%d", i); + em_format_parse_part (emf, subpart, part_id, info, cancellable); + g_string_truncate (part_id, len); + } } -GType -em_format_get_type (void) +static void +emf_parse_multipart_signed (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) { - static GType type = 0; + CamelMimePart *cpart; + CamelMultipartSigned *mps; + CamelCipherContext *cipher = NULL; + guint32 validity_type; - if (G_UNLIKELY (type == 0)) { - static const GTypeInfo type_info = { - sizeof (EMFormatClass), - (GBaseInitFunc) emf_base_init, - (GBaseFinalizeFunc) NULL, - (GClassInitFunc) emf_class_init, - (GClassFinalizeFunc) NULL, - NULL, /* class_data */ - sizeof (EMFormat), - 0, /* n_preallocs */ - (GInstanceInitFunc) emf_init, - NULL /* value_table */ - }; + if (g_cancellable_is_cancelled (cancellable)) + return; - type = g_type_register_static ( - G_TYPE_OBJECT, "EMFormat", &type_info, 0); + mps = (CamelMultipartSigned *) camel_medium_get_content ((CamelMedium *) part); + if (!CAMEL_IS_MULTIPART_SIGNED (mps) + || (cpart = camel_multipart_get_part ((CamelMultipart *) mps, + CAMEL_MULTIPART_SIGNED_CONTENT)) == NULL) { + em_format_format_error ( + emf, _("Could not parse MIME message. " + "Displaying as source.")); + emf_parse_source (emf, part, part_id, info, cancellable); + return; } - return type; -} + /* FIXME: Should be done via a plugin interface */ + /* FIXME: duplicated in em-format-html-display.c */ + if (mps->protocol) { +#ifdef ENABLE_SMIME + if (g_ascii_strcasecmp("application/x-pkcs7-signature", mps->protocol) == 0 + || g_ascii_strcasecmp("application/pkcs7-signature", mps->protocol) == 0) { + cipher = camel_smime_context_new (emf->priv->session); + validity_type = EM_FORMAT_VALIDITY_FOUND_SMIME; + } else +#endif + if (g_ascii_strcasecmp("application/pgp-signature", mps->protocol) == 0) { + cipher = camel_gpg_context_new (emf->priv->session); + validity_type = EM_FORMAT_VALIDITY_FOUND_PGP; + } + } -/** - * em_format_class_add_handler: - * @emfc: EMFormatClass - * @info: Callback information. - * - * Add a mime type handler to this class. This is only used by - * implementing classes. The @info.old pointer will automatically be - * setup to point to the old handler if one was already set. This can - * be used for overrides a fallback. - * - * When a mime type described by @info is encountered, the callback will - * be invoked. Note that @info may be extended by sub-classes if - * they require additional context information. - * - * Use a mime type of "foo/ *" to insert a fallback handler for type "foo". - **/ -void -em_format_class_add_handler (EMFormatClass *emfc, - EMFormatHandler *info) -{ - info->old = g_hash_table_lookup (emfc->type_handlers, info->mime_type); - g_hash_table_insert (emfc->type_handlers, (gpointer) info->mime_type, info); -} + if (cipher == NULL) { + em_format_format_error(emf, _("Unsupported signature format")); + emf_parse_multipart_mixed (emf, part, part_id, info, cancellable); + } else { + CamelCipherValidity *valid; + GError *local_error = NULL; -struct _class_handlers { - EMFormatClass *old; - EMFormatClass *new; -}; + valid = camel_cipher_context_verify_sync ( + cipher, part, cancellable, &local_error); + if (valid == NULL) { + em_format_format_error ( + emf, local_error->message ? + _("Error verifying signature") : + _("Unknown error verifying signature")); + if (local_error->message != NULL) + em_format_format_error ( + emf, "%s", + local_error->message); + g_clear_error (&local_error); + emf_parse_multipart_mixed (emf, part, part_id,info, cancellable); + } else { + gint i, nparts, len = part_id->len; + gboolean secured; + + EMFormatParserInfo signinfo = { + info->handler, + info->validity_type | validity_type | EM_FORMAT_VALIDITY_FOUND_SIGNED, + }; + + if (info->validity) + camel_cipher_validity_envelope (valid, info->validity); + signinfo.validity = valid; + + nparts = camel_multipart_get_number (CAMEL_MULTIPART (mps)); + secured = FALSE; + for (i = 0; i < nparts; i++) { + CamelMimePart *subpart; + subpart = camel_multipart_get_part (CAMEL_MULTIPART (mps), i); + + g_string_append_printf(part_id, ".signed.%d", i); + em_format_parse_part (emf, subpart, part_id, &signinfo, cancellable); + g_string_truncate (part_id, len); + + if (!secured) + secured = is_secured (subpart); + } -static void -merge_missing (gpointer key, - gpointer value, - gpointer userdata) -{ - struct _class_handlers *classes = (struct _class_handlers *) userdata; - EMFormatHandler *info; + /* Add a widget with details about the encryption, but only when + * the encrypted isn't itself secured, in that case it has created + * the button itself */ + if (!secured) { + g_string_append (part_id, ".signed.button"); + em_format_parse_part_as (emf, part, part_id, &signinfo, + "x-evolution/message/x-secure-button", cancellable); + g_string_truncate (part_id, len); + } - info = g_hash_table_lookup (classes->new->type_handlers, key); - if (!info) { - /* Might be from a plugin */ - g_hash_table_insert (classes->new->type_handlers, key, value); + camel_cipher_validity_free (valid); + } } + g_object_unref (cipher); } -void -em_format_merge_handler (EMFormat *new, - EMFormat *old) +/* RFC 2046 */ +static void +emf_parse_multipart_digest (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) { - EMFormatClass *oldc = (EMFormatClass *) G_OBJECT_GET_CLASS (old); - EMFormatClass *newc = (EMFormatClass *) G_OBJECT_GET_CLASS (new); - struct _class_handlers fclasses; + CamelMultipart *mp; + gint i, nparts, len; + + if (g_cancellable_is_cancelled (cancellable)) + return; - fclasses.old = oldc; - fclasses.new = newc; + mp = (CamelMultipart *) camel_medium_get_content ((CamelMedium *) part); - g_hash_table_foreach (oldc->type_handlers, merge_missing, &fclasses); + if (!CAMEL_IS_MULTIPART (mp)) { + emf_parse_source (emf, part, part_id, info, cancellable); + return; + } + len = part_id->len; + nparts = camel_multipart_get_number (mp); + for (i = 0; i < nparts; i++) { + CamelMimePart *subpart; + CamelContentType *ct; + gchar *cts; + const EMFormatHandler *handler; + + subpart = camel_multipart_get_part (mp, i); + + if (!subpart) + continue; + + g_string_append_printf(part_id, ".digest.%d", i); + + ct = camel_mime_part_get_content_type (subpart); + /* According to RFC this shouldn't happen, but who knows... */ + if (ct && !camel_content_type_is (ct, "message", "rfc822")) { + cts = camel_content_type_simple (ct); + em_format_parse_part_as (emf, part, part_id, info, cts, cancellable); + g_free (cts); + g_string_truncate (part_id, len); + continue; + } + + handler = em_format_find_handler (emf, "message/rfc822"); + if (handler && handler->parse_func) + handler->parse_func (emf, subpart, part_id, info, cancellable); + + g_string_truncate (part_id, len); + } } -/** - * em_format_class_remove_handler: - * @emfc: - * @info: - * - * Remove a handler. @info must be a value which was previously - * added. - **/ -void -em_format_class_remove_handler (EMFormatClass *emfc, - EMFormatHandler *info) +/* RFC 2387 */ +static void +emf_parse_multipart_related (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) { - EMFormatHandler *current; + CamelMultipart *mp; + CamelMimePart *body_part, *display_part = NULL; + gint i, nparts, partidlen, displayid = 0; - /* TODO: thread issues? */ + if (g_cancellable_is_cancelled (cancellable)) + return; - current = g_hash_table_lookup (emfc->type_handlers, info->mime_type); - if (current == info) { - current = info->old; - if (current) - g_hash_table_insert ( - emfc->type_handlers, - (gpointer) current->mime_type, current); - else - g_hash_table_remove ( - emfc->type_handlers, info->mime_type); - } else { - while (current && current->old != info) - current = current->old; - g_return_if_fail (current != NULL); - current->old = info->old; + mp = (CamelMultipart *) camel_medium_get_content ((CamelMedium *) part); + + if (!CAMEL_IS_MULTIPART (mp)) { + emf_parse_source (emf, part, part_id, info, cancellable); + return; + } + + display_part = get_related_display_part (part, &displayid); + + if (display_part == NULL) { + emf_parse_multipart_mixed ( + emf, part, part_id, info, cancellable); + return; + } + + /* The to-be-displayed part goes first */ + partidlen = part_id->len; + g_string_append_printf(part_id, ".related.%d", displayid); + em_format_parse_part (emf, display_part, part_id, info, cancellable); + g_string_truncate (part_id, partidlen); + + /* Process the related parts */ + nparts = camel_multipart_get_number (mp); + for (i = 0; i < nparts; i++) { + body_part = camel_multipart_get_part (mp, i); + if (body_part != display_part) { + g_string_append_printf(part_id, ".related.%d", i); + em_format_parse_part (emf, body_part, part_id, info, cancellable); + g_string_truncate (part_id, partidlen); + } } } -/** - * em_format_find_handler: - * @emf: - * @mime_type: - * - * Find a format handler by @mime_type. - * - * Return value: NULL if no handler is available. - **/ -const EMFormatHandler * -em_format_find_handler (EMFormat *emf, - const gchar *mime_type) +static void +emf_parse_message_deliverystatus (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) { - EMFormatClass *class; + EMFormatPURI *puri; + gint len; - g_return_val_if_fail (EM_IS_FORMAT (emf), NULL); - g_return_val_if_fail (mime_type != NULL, NULL); + if (g_cancellable_is_cancelled (cancellable)) + return; + + len = part_id->len; + g_string_append (part_id, ".deliverystatus"); - class = EM_FORMAT_GET_CLASS (emf); - g_return_val_if_fail (class->find_handler != NULL, NULL); + puri = em_format_puri_new (emf, sizeof (EMFormatPURI), part, part_id->str); + puri->write_func = emf_write_text; + puri->mime_type = g_strdup ("text/html"); + puri->validity = info->validity ? camel_cipher_validity_clone (info->validity) : NULL; + puri->validity_type = info->validity_type; - return class->find_handler (emf, mime_type); + g_string_truncate (part_id, len); + + em_format_add_puri (emf, puri); } -/** - * em_format_fallback_handler: - * @emf: - * @mime_type: - * - * Try to find a format handler based on the major type of the @mime_type. - * - * The subtype is replaced with "*" and a lookup performed. - * - * Return value: - **/ -const EMFormatHandler * -em_format_fallback_handler (EMFormat *emf, - const gchar *mime_type) +static void +emf_parse_inlinepgp_signed (EMFormat *emf, + CamelMimePart *ipart, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) { - gchar *mime, *s; + CamelStream *filtered_stream; + CamelMimeFilterPgp *pgp_filter; + CamelContentType *content_type; + CamelCipherContext *cipher; + CamelCipherValidity *valid; + CamelDataWrapper *dw; + CamelMimePart *opart; + CamelStream *ostream; + gchar *type; + gint len; + GError *local_error = NULL; + EMFormatParserInfo signinfo; + GByteArray *ba; - s = strchr (mime_type, '/'); - if (s == NULL) - mime = (gchar *) mime_type; - else { - gsize len = (s - mime_type) + 1; + if (g_cancellable_is_cancelled (cancellable)) + return; - mime = g_alloca (len + 2); - strncpy (mime, mime_type, len); - strcpy(mime+len, "*"); + if (!ipart) { + em_format_format_error(emf, _("Unknown error verifying signature")); + return; } - return em_format_find_handler (emf, mime); + cipher = camel_gpg_context_new (emf->priv->session); + /* Verify the signature of the message */ + valid = camel_cipher_context_verify_sync ( + cipher, ipart, cancellable, &local_error); + if (!valid) { + em_format_format_error ( + emf, local_error->message ? + _("Error verifying signature") : + _("Unknown error verifying signature")); + if (local_error->message) + em_format_format_error ( + emf, "%s", local_error->message); + emf_parse_source (emf, ipart, part_id, info, cancellable); + /* XXX I think this will loop: + * em_format_part_as(emf, stream, part, "text/plain"); */ + g_clear_error (&local_error); + g_object_unref (cipher); + return; + } + + /* Setup output stream */ + ostream = camel_stream_mem_new (); + filtered_stream = camel_stream_filter_new (ostream); + + /* Add PGP header / footer filter */ + pgp_filter = (CamelMimeFilterPgp *) camel_mime_filter_pgp_new (); + camel_stream_filter_add ( + CAMEL_STREAM_FILTER (filtered_stream), + CAMEL_MIME_FILTER (pgp_filter)); + g_object_unref (pgp_filter); + + /* Pass through the filters that have been setup */ + dw = camel_medium_get_content ((CamelMedium *) ipart); + camel_data_wrapper_decode_to_stream_sync ( + dw, (CamelStream *) filtered_stream, cancellable, NULL); + camel_stream_flush ((CamelStream *) filtered_stream, cancellable, NULL); + g_object_unref (filtered_stream); + + /* Create a new text/plain MIME part containing the signed + * content preserving the original part's Content-Type params. */ + content_type = camel_mime_part_get_content_type (ipart); + type = camel_content_type_format (content_type); + content_type = camel_content_type_decode (type); + g_free (type); + + g_free (content_type->type); + content_type->type = g_strdup ("text"); + g_free (content_type->subtype); + content_type->subtype = g_strdup ("plain"); + type = camel_content_type_format (content_type); + camel_content_type_unref (content_type); + + ba = camel_stream_mem_get_byte_array ((CamelStreamMem *) ostream); + opart = camel_mime_part_new (); + camel_mime_part_set_content (opart, (gchar *) ba->data, ba->len, type); + g_free (type); + + if (info->validity) + camel_cipher_validity_envelope (valid, info->validity); + + /* Pass it off to the real formatter */ + len = part_id->len; + g_string_append (part_id, ".inlinepgp_signed"); + signinfo.handler = info->handler; + signinfo.validity_type = info->validity_type | EM_FORMAT_VALIDITY_FOUND_SIGNED | EM_FORMAT_VALIDITY_FOUND_PGP; + signinfo.validity = valid; + em_format_parse_part (emf, opart, part_id, &signinfo, cancellable); + g_string_truncate (part_id, len); + + /* Add a widget with details about the encryption, but only when + * the encrypted isn't itself secured, in that case it has created + * the button itself */ + if (!is_secured (opart)) { + g_string_append (part_id, ".inlinepgp_signed.button"); + em_format_parse_part_as (emf, opart, part_id, &signinfo, + "x-evolution/message/x-secure-button", cancellable); + g_string_truncate (part_id, len); + } + + /* Clean Up */ + camel_cipher_validity_free (valid); + g_object_unref (dw); + g_object_unref (opart); + g_object_unref (ostream); + g_object_unref (cipher); } -/** - * em_format_add_puri: - * @emf: - * @size: - * @cid: Override the autogenerated content id. - * @part: - * @func: - * - * Add a pending-uri handler. When formatting parts that reference - * other parts, a pending-uri (PURI) can be used to track the reference. - * - * @size is used to allocate the structure, so that it can be directly - * subclassed by implementors. - * - * @cid can be used to override the key used to retreive the PURI, if NULL, - * then the content-location and the content-id of the @part are stored - * as lookup keys for the part. - * - * FIXME: This may need a free callback. - * - * Return value: A new PURI, with a referenced copy of @part, and the cid - * always set. The uri will be set if one is available. Clashes - * are resolved by forgetting the old PURI in the global index. - **/ -EMFormatPURI * -em_format_add_puri (EMFormat *emf, - gsize size, - const gchar *cid, - CamelMimePart *part, - EMFormatPURIFunc func) +static void +emf_parse_inlinepgp_encrypted (EMFormat *emf, + CamelMimePart *ipart, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) { - EMFormatPURI *puri; - const gchar *tmp; + CamelCipherContext *cipher; + CamelCipherValidity *valid; + CamelMimePart *opart; + CamelDataWrapper *dw; + gchar *mime_type; + gint len; + GError *local_error = NULL; + EMFormatParserInfo encinfo; + + if (g_cancellable_is_cancelled (cancellable)) + return; + + cipher = camel_gpg_context_new (emf->priv->session); + opart = camel_mime_part_new (); - d(printf("adding puri for part: %s\n", emf->part_id->str)); + /* Decrypt the message */ + valid = camel_cipher_context_decrypt_sync ( + cipher, ipart, opart, cancellable, &local_error); + + if (!valid) { + em_format_format_error ( + emf, _("Could not parse PGP message: ")); + if (local_error->message != NULL) + em_format_format_error ( + emf, "%s", local_error->message); + else + em_format_format_error ( + emf, _("Unknown error")); + emf_parse_source (emf, ipart, part_id, info, cancellable); + /* XXX I think this will loop: + * em_format_part_as(emf, stream, part, "text/plain"); */ - if (size < sizeof (*puri)) { - g_warning ( - "size (%" G_GSIZE_FORMAT - ") less than size of puri\n", size); - size = sizeof (*puri); + g_clear_error (&local_error); + g_object_unref (cipher); + g_object_unref (opart); + return; } - puri = g_malloc0 (size); + dw = camel_medium_get_content ((CamelMedium *) opart); + mime_type = camel_data_wrapper_get_mime_type (dw); - puri->format = emf; - puri->func = func; - puri->use_count = 0; - puri->cid = g_strdup (cid); - puri->part_id = g_strdup (emf->part_id->str); + /* this ensures to show the 'opart' as inlined, if possible */ + if (mime_type && g_ascii_strcasecmp (mime_type, "application/octet-stream") == 0) { + const gchar *snoop = em_format_snoop_type (opart); - if (part) { - g_object_ref (part); - puri->part = part; + if (snoop) + camel_data_wrapper_set_mime_type (dw, snoop); } - if (part != NULL && cid == NULL) { - tmp = camel_mime_part_get_content_id (part); - if (tmp) - puri->cid = g_strdup_printf("cid:%s", tmp); - else - puri->cid = g_strdup_printf("em-no-cid:%s", emf->part_id->str); + preserve_charset_in_content_type (ipart, opart); + g_free (mime_type); - d(printf("built cid '%s'\n", puri->cid)); + if (info->validity) + camel_cipher_validity_envelope (valid, info->validity); - /* Not quite same as old behaviour, it also put in the - * relative uri and a fallback for no parent uri. */ - tmp = camel_mime_part_get_content_location (part); - puri->uri = NULL; - if (tmp == NULL) { - /* No location, don't set a uri at all, - * html parts do this themselves. */ - } else { - if (strchr (tmp, ':') == NULL && emf->base != NULL) { - CamelURL *uri; - - uri = camel_url_new_with_base (emf->base, tmp); - puri->uri = camel_url_to_string (uri, 0); - camel_url_free (uri); - } else { - puri->uri = g_strdup (tmp); - } - } + /* Pass it off to the real formatter */ + len = part_id->len; + g_string_append (part_id, ".inlinepgp_encrypted"); + encinfo.handler = info->handler; + encinfo.validity_type = info->validity_type | EM_FORMAT_VALIDITY_FOUND_ENCRYPTED | EM_FORMAT_VALIDITY_FOUND_PGP; + encinfo.validity = valid; + em_format_parse_part (emf, opart, part_id, &encinfo, cancellable); + g_string_truncate (part_id, len); + + /* Add a widget with details about the encryption, but only when + * the encrypted isn't itself secured, in that case it has created + * the button itself */ + if (!is_secured (opart)) { + g_string_append (part_id, ".inlinepgp_encrypted.button"); + em_format_parse_part_as (emf, opart, part_id, &encinfo, + "x-evolution/message/x-secure-button", cancellable); + g_string_truncate (part_id, len); } - g_return_val_if_fail (puri->cid != NULL, NULL); - g_return_val_if_fail (emf->pending_uri_level != NULL, NULL); - g_return_val_if_fail (emf->pending_uri_table != NULL, NULL); + /* Clean Up */ + camel_cipher_validity_free (valid); + g_object_unref (opart); + g_object_unref (cipher); +} - g_queue_push_tail (emf->pending_uri_level->data, puri); +static void +emf_parse_message (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) +{ + /* Headers */ + info->force_handler = TRUE; + em_format_parse_part_as (emf, part, part_id, info, + "x-evolution/message/headers", cancellable); - if (puri->uri) - g_hash_table_insert (emf->pending_uri_table, puri->uri, puri); - g_hash_table_insert (emf->pending_uri_table, puri->cid, puri); + /* Anything that comes between headers and message body */ + info->force_handler = TRUE; + em_format_parse_part_as (emf, part, part_id, info, + "x-evolution/message/post-headers", cancellable); - return puri; + /* Begin parsing the message */ + info->force_handler = FALSE; + em_format_parse_part (emf, part, part_id, info, cancellable); } -/** - * em_format_push_level: - * @emf: - * - * This is used to build a hierarchy of visible PURI objects based on - * the structure of the message. Used by multipart/alternative formatter. - * - * FIXME: This could probably also take a uri so it can automatically update - * the base location. - **/ -void -em_format_push_level (EMFormat *emf) +static void +emf_parse_headers (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) { - GNode *node; + EMFormatPURI *puri; + gint len; - g_return_if_fail (EM_IS_FORMAT (emf)); + len = part_id->len; + g_string_append (part_id, ".headers"); - node = g_node_new (g_queue_new ()); + puri = em_format_puri_new (emf, sizeof (EMFormatPURI), part, part_id->str); + puri->write_func = info->handler->write_func; + puri->mime_type = g_strdup ("text/html"); + em_format_add_puri (emf, puri); - if (emf->pending_uri_tree == NULL) - emf->pending_uri_tree = node; - else - g_node_append (emf->pending_uri_tree, node); + g_string_truncate (part_id, len); +} - emf->pending_uri_level = node; +static void +emf_parse_post_headers (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) +{ + /* Add attachment bar */ + info->force_handler = TRUE; + em_format_parse_part_as (emf, part, part_id, info, + "x-evolution/message/attachment-bar", cancellable); } -/** - * em_format_pull_level: - * @emf: - * - * Drop a level of visibility back to the parent. Note that - * no PURI values are actually freed. - **/ +static void +emf_parse_source (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) +{ + EMFormatPURI *puri; + gint len; + + if (g_cancellable_is_cancelled (cancellable)) + return; + + len = part_id->len; + g_string_append (part_id, ".source"); + + puri = em_format_puri_new (emf, sizeof (EMFormatPURI), part, part_id->str); + puri->write_func = info->handler->write_func; + puri->mime_type = g_strdup ("text/html"); + g_string_truncate (part_id, len); + + em_format_add_puri (emf, puri); +} + +/**************************************************************************/ + void -em_format_pull_level (EMFormat *emf) +em_format_empty_writer (EMFormat *emf, + EMFormatPURI *puri, + CamelStream *stream, + EMFormatWriterInfo *info, + GCancellable *cancellable) +{ + /* DO NOTHING */ +} + +static void +emf_write_error (EMFormat * emf, + EMFormatPURI * puri, + CamelStream * stream, + EMFormatWriterInfo * info, + GCancellable * cancellable) +{ + camel_data_wrapper_decode_to_stream_sync ((CamelDataWrapper *) puri->part, + stream, cancellable, NULL); +} + +static void +emf_write_text (EMFormat *emf, + EMFormatPURI *puri, + CamelStream *stream, + EMFormatWriterInfo *info, + GCancellable *cancellable) +{ + CamelContentType *ct; + + ct = camel_mime_part_get_content_type (puri->part); + if (!camel_content_type_is (ct, "text", "plain")) { + camel_stream_write_string (stream, _("Cannot proccess non-text mime/part"), + cancellable, NULL); + return; + } + + camel_data_wrapper_decode_to_stream_sync ((CamelDataWrapper *) puri->part, + stream, cancellable, NULL); +} + +static void +emf_write_source (EMFormat *emf, + EMFormatPURI *puri, + CamelStream *stream, + EMFormatWriterInfo *info, + GCancellable *cancellable) { + GByteArray *ba; + gchar *data; + g_return_if_fail (EM_IS_FORMAT (emf)); - g_return_if_fail (emf->pending_uri_level != NULL); - emf->pending_uri_level = emf->pending_uri_level->parent; + ba = camel_data_wrapper_get_byte_array ((CamelDataWrapper *) puri->part); + + data = g_strndup ((gchar *) ba->data, ba->len); + camel_stream_write_string (stream, data, cancellable, NULL); + g_free (data); } -/** - * em_format_find_visible_puri: - * @emf: - * @uri: - * - * Search for a PURI based on the visibility defined by :push_level() - * and :pull_level(). - * - * Return value: - **/ -EMFormatPURI * -em_format_find_visible_puri (EMFormat *emf, - const gchar *uri) +/**************************************************************************/ + +static gboolean +emf_is_inline (EMFormat *emf, + const gchar *part_id, + CamelMimePart *mime_part, + const EMFormatHandler *handle) { - GNode *node; + //EMFormatCache *emfc; + const gchar *disposition; - g_return_val_if_fail (EM_IS_FORMAT (emf), NULL); - g_return_val_if_fail (uri != NULL, NULL); + if (handle == NULL) + return FALSE; - node = emf->pending_uri_level; + /* Some types need to override the disposition. + * e.g. application/x-pkcs7-mime */ + if (handle->flags & EM_FORMAT_HANDLER_INLINE_DISPOSITION) + return TRUE; - while (node != NULL) { - GQueue *queue = node->data; - GList *link; + disposition = camel_mime_part_get_disposition (mime_part); + if (disposition != NULL) + return g_ascii_strcasecmp (disposition, "inline") == 0; - link = g_queue_peek_head_link (queue); + /* Otherwise, use the default for this handler type. */ + return (handle->flags & EM_FORMAT_HANDLER_INLINE) != 0; +} - while (link != NULL) { - EMFormatPURI *pw = link->data; +/**************************************************************************/ - if (g_strcmp0 (pw->uri, uri) == 0) - return pw; +static EMFormatHandler type_handlers[] = { +#ifdef ENABLE_SMIME + { (gchar *) "application/x-pkcs7-mime", emf_parse_application_xpkcs7mime, 0, EM_FORMAT_HANDLER_INLINE_DISPOSITION }, +#endif + { (gchar *) "application/mbox", emf_parse_application_mbox, 0, EM_FORMAT_HANDLER_INLINE | EM_FORMAT_HANDLER_COMPOUND_TYPE }, + { (gchar *) "multipart/alternative", emf_parse_multipart_alternative, }, + { (gchar *) "multipart/appledouble", emf_parse_multipart_appledouble, }, + { (gchar *) "multipart/encrypted", emf_parse_multipart_encrypted, }, + { (gchar *) "multipart/mixed", emf_parse_multipart_mixed, }, + { (gchar *) "multipart/signed", emf_parse_multipart_signed, }, + { (gchar *) "multipart/related", emf_parse_multipart_related, }, + { (gchar *) "multipart/digest", emf_parse_multipart_digest, 0, EM_FORMAT_HANDLER_COMPOUND_TYPE }, + { (gchar *) "multipart/*", emf_parse_multipart_mixed, 0, EM_FORMAT_HANDLER_COMPOUND_TYPE }, + { (gchar *) "message/deliverystatus", emf_parse_message_deliverystatus, 0, }, + + /* Ignore PGP signature part */ + { (gchar *) "application/pgp-signature", em_format_empty_parser, }, + + /* Insert brokenly-named parts here */ +#ifdef ENABLE_SMIME + { (gchar *) "application/pkcs7-mime", emf_parse_application_xpkcs7mime, 0, EM_FORMAT_HANDLER_INLINE_DISPOSITION }, +#endif - if (g_strcmp0 (pw->cid, uri) == 0) - return pw; + /* internal types */ + { (gchar *) "application/x-inlinepgp-signed", emf_parse_inlinepgp_signed, }, + { (gchar *) "application/x-inlinepgp-encrypted", emf_parse_inlinepgp_encrypted, }, + { (gchar *) "x-evolution/message", emf_parse_message, 0, EM_FORMAT_HANDLER_COMPOUND_TYPE }, + { (gchar *) "x-evolution/message/headers", emf_parse_headers, }, + { (gchar *) "x-evolution/message/post-headers", emf_parse_post_headers, }, + { (gchar *) "x-evolution/message/source", emf_parse_source, emf_write_source }, +}; - link = g_list_next (link); - } +/* note: also copied in em-mailer-prefs.c */ +static const struct { + const gchar *name; + guint32 flags; +} default_headers[] = { + { N_("From"), EM_FORMAT_HEADER_BOLD }, + { N_("Reply-To"), EM_FORMAT_HEADER_BOLD }, + { N_("To"), EM_FORMAT_HEADER_BOLD }, + { N_("Cc"), EM_FORMAT_HEADER_BOLD }, + { N_("Bcc"), EM_FORMAT_HEADER_BOLD }, + { N_("Subject"), EM_FORMAT_HEADER_BOLD }, + { N_("Date"), EM_FORMAT_HEADER_BOLD }, + { N_("Newsgroups"), EM_FORMAT_HEADER_BOLD }, + { N_("Face"), 0 }, +}; + +static void +em_format_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + EMFormat *emf = EM_FORMAT (object); - node = node->parent; + switch (property_id) { + case PROP_CHARSET: + g_value_set_string ( + value, em_format_get_charset (emf)); + return; + case PROP_DEFAULT_CHARSET: + g_value_set_string ( + value, em_format_get_default_charset (emf)); + return; + case PROP_COMPOSER: + g_value_set_boolean ( + value, em_format_get_composer (emf)); + return; + case PROP_BASE_URL: + g_value_set_object ( + value, em_format_get_base_url (emf)); + return; } - return NULL; + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } -/** - * em_format_find_puri: - * @emf: - * @uri: - * - * Search for a PURI based on a uri. Both the content-id - * and content-location are checked. - * - * Return value: - **/ -EMFormatPURI * -em_format_find_puri (EMFormat *emf, - const gchar *uri) +static void +em_format_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) { - g_return_val_if_fail (EM_IS_FORMAT (emf), NULL); - g_return_val_if_fail (uri != NULL, NULL); + EMFormat *emf = EM_FORMAT (object); - g_return_val_if_fail (emf->pending_uri_table != NULL, NULL); + switch (property_id) { + case PROP_CHARSET: + em_format_set_charset (emf, + g_value_get_string (value)); + return; + case PROP_DEFAULT_CHARSET: + em_format_set_default_charset (emf, + g_value_get_string (value)); + return; + case PROP_COMPOSER: + em_format_set_composer (emf, + g_value_get_boolean (value)); + return; + case PROP_BASE_URL: + em_format_set_base_url (emf, + g_value_get_object (value)); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); - return g_hash_table_lookup (emf->pending_uri_table, uri); } -/** - * em_format_clear_puri_tree: - * @emf: - * - * For use by implementors to clear out the message structure - * data. - **/ -void -em_format_clear_puri_tree (EMFormat *emf) +static void +em_format_finalize (GObject *object) { - if (emf->pending_uri_table == NULL) - emf->pending_uri_table = - g_hash_table_new (g_str_hash, g_str_equal); + EMFormat *emf = EM_FORMAT (object); - else { - g_hash_table_remove_all (emf->pending_uri_table); + if (emf->message_uid) { + g_free (emf->message_uid); + emf->message_uid = NULL; + } + + if (emf->uri_base) { + g_free (emf->uri_base); + emf->uri_base = NULL; + } + + if (emf->message) { + g_object_unref (emf->message); + emf->message = NULL; + } + + if (emf->folder) { + g_object_unref (emf->folder); + emf->folder = NULL; + } + + if (emf->mail_part_table) { + /* This will destroy all the EMFormatPURI objects stored + * inside!!!! */ + g_hash_table_destroy (emf->mail_part_table); + emf->mail_part_table = NULL; + } + + if (emf->mail_part_list) { + g_list_free (emf->mail_part_list); + emf->mail_part_list = NULL; + } + + if (emf->priv->base_url) { + camel_url_free (emf->priv->base_url); + emf->priv->base_url = NULL; + } - g_node_traverse ( - emf->pending_uri_tree, - G_IN_ORDER, G_TRAVERSE_ALL, -1, - (GNodeTraverseFunc) emf_clear_puri_node, NULL); - g_node_destroy (emf->pending_uri_tree); + if (emf->priv->session) { + g_object_unref (emf->priv->session); + emf->priv->session = NULL; + } - emf->pending_uri_tree = NULL; - emf->pending_uri_level = NULL; + if (emf->priv->charset) { + g_free (emf->priv->charset); + emf->priv->charset = NULL; } - em_format_push_level (emf); + em_format_clear_headers (emf); + + /* Chain up to parent's finalize() method */ + G_OBJECT_CLASS (parent_class)->finalize (object); } -/* use mime_type == NULL to force showing as application/octet-stream */ -void -em_format_part_as (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const gchar *mime_type, - GCancellable *cancellable) +static void +em_format_base_init (EMFormatClass *klass) { - const EMFormatHandler *handle = NULL; - const gchar *snoop_save = emf->snoop_mime_type, *tmp; - CamelURL *base_save = emf->base, *base = NULL; - gchar *basestr = NULL; + gint i; - d(printf("format_part_as()\n")); + klass->type_handlers = g_hash_table_new (g_str_hash, g_str_equal); - emf->snoop_mime_type = NULL; + for (i = 0; i < G_N_ELEMENTS (type_handlers); i++) { + g_hash_table_insert (klass->type_handlers, + type_handlers[i].mime_type, + &type_handlers[i]); + } +} - /* RFC 2110, we keep track of content-base, and absolute content-location headers - * This is actually only required for html, but, *shrug * */ - tmp = camel_medium_get_header((CamelMedium *)part, "Content-Base"); - if (tmp == NULL) { - tmp = camel_mime_part_get_content_location (part); - if (tmp && strchr (tmp, ':') == NULL) - tmp = NULL; - } else { - tmp = basestr = camel_header_location_decode (tmp); - } - d(printf("content-base is '%s'\n", tmp?tmp:"<unset>")); - if (tmp - && (base = camel_url_new (tmp, NULL))) { - emf->base = base; - d(printf("Setting content base '%s'\n", tmp)); - } - g_free (basestr); - - if (mime_type != NULL) { - gboolean is_fallback = FALSE; - if (g_ascii_strcasecmp(mime_type, "application/octet-stream") == 0) { - emf->snoop_mime_type = mime_type = em_format_snoop_type (part); - if (mime_type == NULL) - mime_type = "application/octet-stream"; - } +static void +em_format_class_init (EMFormatClass *klass) +{ + GObjectClass *object_class; - handle = em_format_find_handler (emf, mime_type); - if (handle == NULL) { - handle = em_format_fallback_handler (emf, mime_type); - is_fallback = TRUE; - } + parent_class = g_type_class_peek_parent (klass); + + g_type_class_add_private (klass, sizeof (EMFormatPrivate)); + + klass->is_inline = emf_is_inline; + + object_class = G_OBJECT_CLASS (klass); + object_class->finalize = em_format_finalize; + object_class->get_property = em_format_get_property; + object_class->set_property = em_format_set_property; + + g_object_class_install_property (object_class, + PROP_CHARSET, + g_param_spec_string ("charset", + NULL, + NULL, + NULL, + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_DEFAULT_CHARSET, + g_param_spec_string ("default-charset", + NULL, + NULL, + NULL, + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_COMPOSER, + g_param_spec_boolean ("composer", + NULL, + NULL, + FALSE, + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_BASE_URL, + g_param_spec_pointer ("base-url", + NULL, + NULL, + G_PARAM_READWRITE)); + + signals[REDRAW_REQUESTED] = g_signal_new ( + "redraw-requested", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EMFormatClass, redraw_requested), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE,0); +} - if (handle != NULL - && !em_format_is_attachment (emf, part)) { - d(printf("running handler for type '%s'\n", mime_type)); - handle->handler ( - emf, stream, part, handle, - cancellable, is_fallback); - goto finish; - } - d(printf("this type is an attachment? '%s'\n", mime_type)); - } else { - mime_type = "application/octet-stream"; - } +static void +mail_part_table_item_free (gpointer data) +{ + GList *iter = data; + EMFormatPURI *puri = iter->data; + + em_format_puri_free (puri); +} + +static void +em_format_init (EMFormat *emf) +{ + EShell *shell; + EShellSettings *shell_settings; - EM_FORMAT_GET_CLASS (emf)->format_attachment ( - emf, stream, part, mime_type, handle, cancellable); + emf->priv = G_TYPE_INSTANCE_GET_PRIVATE (emf, + EM_TYPE_FORMAT, EMFormatPrivate); -finish: - emf->base = base_save; - emf->snoop_mime_type = snoop_save; + emf->message = NULL; + emf->folder = NULL; + emf->mail_part_list = NULL; + emf->mail_part_table = g_hash_table_new_full (g_str_hash, g_str_equal, + NULL, (GDestroyNotify) mail_part_table_item_free); + /* No need to free the key, because it's owned and free'd by the PURI */ - if (base) - camel_url_free (base); + shell = e_shell_get_default (); + shell_settings = e_shell_get_shell_settings (shell); + + emf->priv->last_error = 0; + + emf->priv->session = e_shell_settings_get_pointer (shell_settings, "mail-session"); + g_return_if_fail (emf->priv->session); + + g_object_ref (emf->priv->session); + + em_format_default_headers (emf); } -void -em_format_part (EMFormat *emf, - CamelStream *stream, - CamelMimePart *mime_part, - GCancellable *cancellable) +EMFormat * +em_format_new (void) { - gchar *mime_type; - CamelDataWrapper *dw; + EMFormat *emf = g_object_new (EM_TYPE_FORMAT, NULL); - dw = camel_medium_get_content (CAMEL_MEDIUM (mime_part)); - mime_type = camel_data_wrapper_get_mime_type (dw); - if (mime_type != NULL) { - camel_strdown (mime_type); - em_format_part_as ( - emf, stream, mime_part, mime_type, cancellable); - g_free (mime_type); - } else - em_format_part_as ( - emf, stream, mime_part, "text/plain", cancellable); + return emf; } -/** - * em_format_format_clone: - * @emf: an #EMFormat - * @folder: a #CamelFolder or %NULL - * @uid: Message UID or %NULL - * @msg: a #CamelMimeMessage or %NULL - * @emfsource: Used as a basis for user-altered layout, e.g. inline viewed - * attachments. - * @cancellable: a #GCancellable, or %NULL - * - * Format a message @msg. If @emfsource is non NULL, then the status of - * inlined expansion and so forth is copied direction from @emfsource. - * - * By passing the same value for @emf and @emfsource, you can perform - * a display refresh, or it can be used to generate an identical layout, - * e.g. to print what the user has shown inline. - **/ -void -em_format_format_clone (EMFormat *emf, - CamelFolder *folder, - const gchar *uid, - CamelMimeMessage *message, - EMFormat *source, - GCancellable *cancellable) +GType +em_format_get_type (void) { - EMFormatClass *class; + static GType type = 0; - g_return_if_fail (EM_IS_FORMAT (emf)); - g_return_if_fail (folder == NULL || CAMEL_IS_FOLDER (folder)); - g_return_if_fail (message == NULL || CAMEL_IS_MIME_MESSAGE (message)); - g_return_if_fail (source == NULL || EM_IS_FORMAT (source)); + if (G_UNLIKELY (type == 0)) { + static const GTypeInfo type_info = { + sizeof (EMFormatClass), + (GBaseInitFunc) em_format_base_init, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) em_format_class_init, + (GClassFinalizeFunc) NULL, + NULL, /* class_data */ + sizeof (EMFormat), + 0, /* n_preallocs */ + (GInstanceInitFunc) em_format_init, + NULL /* value_table */ + }; - class = EM_FORMAT_GET_CLASS (emf); - g_return_if_fail (class->format_clone != NULL); + type = g_type_register_static ( + G_TYPE_OBJECT, "EMFormat", &type_info, 0); + } - class->format_clone (emf, folder, uid, message, source, cancellable); + return type; } void -em_format_format (EMFormat *emf, - CamelFolder *folder, - const gchar *uid, - CamelMimeMessage *message, - GCancellable *cancellable) +em_format_set_charset (EMFormat *emf, + const gchar *charset) { - /* em_format_format_clone() will check the arguments. */ - em_format_format_clone (emf, folder, uid, message, NULL, cancellable); + g_return_if_fail (EM_IS_FORMAT (emf)); + + if (emf->priv->charset) + g_free (emf->priv->charset); + + emf->priv->charset = g_strdup (charset); + + g_object_notify (G_OBJECT (emf), "charset"); } -static gboolean -format_redraw_idle_cb (EMFormat *emf) +const gchar * +em_format_get_charset (EMFormat *emf) { - emf->priv->redraw_idle_id = 0; - - /* FIXME Not passing a GCancellable here. */ - em_format_format_clone ( - emf, emf->folder, emf->uid, emf->message, emf, NULL); + g_return_val_if_fail (EM_IS_FORMAT (emf), NULL); - return FALSE; + return emf->priv->charset; } void -em_format_queue_redraw (EMFormat *emf) +em_format_set_default_charset (EMFormat *emf, + const gchar *charset) { g_return_if_fail (EM_IS_FORMAT (emf)); - if (emf->priv->redraw_idle_id == 0) - emf->priv->redraw_idle_id = g_idle_add ( - (GSourceFunc) format_redraw_idle_cb, emf); + if (emf->priv->default_charset) + g_free (emf->priv->default_charset); + + emf->priv->default_charset = g_strdup (charset); + + g_object_notify (G_OBJECT (emf), "default-charset"); +} + +const gchar * +em_format_get_default_charset (EMFormat *emf) +{ + g_return_val_if_fail (EM_IS_FORMAT (emf), NULL); + + return emf->priv->default_charset; } -/** - * em_format_set_mode: - * @emf: - * @type: - * - * Set display mode, EM_FORMAT_MODE_SOURCE, EM_FORMAT_MODE_ALLHEADERS, - * or EM_FORMAT_MODE_NORMAL. - **/ void -em_format_set_mode (EMFormat *emf, - EMFormatMode mode) +em_format_set_composer (EMFormat *emf, + gboolean composer) { g_return_if_fail (EM_IS_FORMAT (emf)); - if (emf->mode == mode) + if (emf->priv->composer && composer) return; - emf->mode = mode; + emf->priv->composer = composer; - /* force redraw if type changed afterwards */ - if (emf->message != NULL) - em_format_queue_redraw (emf); + g_object_notify (G_OBJECT (emf), "composer"); +} + +gboolean +em_format_get_composer (EMFormat *emf) +{ + g_return_val_if_fail (EM_IS_FORMAT (emf), FALSE); + + return emf->priv->composer; } -/** - * em_format_set_charset: - * @emf: - * @charset: - * - * set override charset on formatter. message will be redisplayed if - * required. - **/ void -em_format_set_charset (EMFormat *emf, - const gchar *charset) +em_format_set_base_url (EMFormat *emf, + CamelURL *url) { - if ((emf->charset && charset && g_ascii_strcasecmp (emf->charset, charset) == 0) - || (emf->charset == NULL && charset == NULL) - || (emf->charset == charset)) - return; + g_return_if_fail (EM_IS_FORMAT (emf)); + g_return_if_fail (url); - g_free (emf->charset); - emf->charset = g_strdup (charset); + if (emf->priv->base_url) + camel_url_free (emf->priv->base_url); - if (emf->message) - em_format_queue_redraw (emf); + emf->priv->base_url = camel_url_copy (url); + + g_object_notify (G_OBJECT (emf), "base-url"); } -/** - * em_format_set_default_charset: - * @emf: - * @charset: - * - * Set the fallback, default system charset to use when no other charsets - * are present. Message will be redisplayed if required (and sometimes - * redisplayed when it isn't). - **/ void -em_format_set_default_charset (EMFormat *emf, - const gchar *charset) +em_format_set_base_url_string (EMFormat *emf, + const gchar *url_string) { - if ((emf->default_charset && charset && - g_ascii_strcasecmp (emf->default_charset, charset) == 0) - || (emf->default_charset == NULL && charset == NULL) - || (emf->default_charset == charset)) - return; + g_return_if_fail (EM_IS_FORMAT (emf)); + g_return_if_fail (url_string && *url_string); + + if (emf->priv->base_url) + camel_url_free (emf->priv->base_url); - g_free (emf->default_charset); - emf->default_charset = g_strdup (charset); + emf->priv->base_url = camel_url_new (url_string, NULL); - if (emf->message && emf->charset == NULL) - em_format_queue_redraw (emf); + g_object_notify (G_OBJECT (emf), "base-url"); +} + +CamelURL * +em_format_get_base_url (EMFormat *emf) +{ + g_return_val_if_fail (EM_IS_FORMAT (emf), NULL); + + return emf->priv->base_url; } /** @@ -1051,44 +1610,26 @@ em_format_clear_headers (EMFormat *emf) { EMFormatHeader *eh; - while ((eh = g_queue_pop_head (&emf->header_list)) != NULL) - g_free (eh); -} + g_return_if_fail (EM_IS_FORMAT (emf)); -/* note: also copied in em-mailer-prefs.c */ -static const struct { - const gchar *name; - guint32 flags; -} default_headers[] = { - { N_("From"), EM_FORMAT_HEADER_BOLD }, - { N_("Reply-To"), EM_FORMAT_HEADER_BOLD }, - { N_("To"), EM_FORMAT_HEADER_BOLD }, - { N_("Cc"), EM_FORMAT_HEADER_BOLD }, - { N_("Bcc"), EM_FORMAT_HEADER_BOLD }, - { N_("Subject"), EM_FORMAT_HEADER_BOLD }, - { N_("Date"), EM_FORMAT_HEADER_BOLD }, - { N_("Newsgroups"), EM_FORMAT_HEADER_BOLD }, - { N_("Face"), 0 }, -}; + while ((eh = g_queue_pop_head (&emf->header_list)) != NULL) { + em_format_header_free (eh); + } + +} -/** - * em_format_default_headers: - * @emf: - * - * Set the headers to show to the default list. - * - * From, Reply-To, To, Cc, Bcc, Subject and Date. - **/ void em_format_default_headers (EMFormat *emf) { gint ii; - em_format_clear_headers (emf); + g_return_if_fail (EM_IS_FORMAT (emf)); + /* Set the default headers */ + em_format_clear_headers (emf); for (ii = 0; ii < G_N_ELEMENTS (default_headers); ii++) em_format_add_header ( - emf, default_headers[ii].name, + emf, default_headers[ii].name, NULL, default_headers[ii].flags); } @@ -1096,6 +1637,7 @@ em_format_default_headers (EMFormat *emf) * em_format_add_header: * @emf: * @name: The name of the header, as it will appear during output. + * @value: Value of the header. Can be NULL. * @flags: EM_FORMAT_HEAD_* defines to control display attributes. * * Add a specific header to show. If any headers are set, they will @@ -1106,248 +1648,465 @@ em_format_default_headers (EMFormat *emf) void em_format_add_header (EMFormat *emf, const gchar *name, + const gchar *value, guint32 flags) { EMFormatHeader *h; - h = g_malloc (sizeof (*h) + strlen (name)); + g_return_if_fail (EM_IS_FORMAT (emf)); + g_return_if_fail (name && *name); + + h = em_format_header_new (name, value); h->flags = flags; - strcpy (h->name, name); g_queue_push_tail (&emf->header_list, h); } -/** - * em_format_is_attachment: - * @emf: - * @part: Part to check. - * - * Returns true if the part is an attachment. - * - * A part is not considered an attachment if it is a - * multipart, or a text part with no filename. It is used - * to determine if an attachment header should be displayed for - * the part. - * - * Content-Disposition is not checked. - * - * Return value: TRUE/FALSE - **/ -gint -em_format_is_attachment (EMFormat *emf, - CamelMimePart *part) +void +em_format_add_header_struct (EMFormat *emf, + EMFormatHeader *header) { - /*CamelContentType *ct = camel_mime_part_get_content_type(part);*/ - CamelDataWrapper *dw = camel_medium_get_content ((CamelMedium *) part); + g_return_if_fail (EM_IS_FORMAT (emf)); + g_return_if_fail (header && header->name); - if (!dw) - return 0; + em_format_add_header (emf, header->name, header->value, header->flags); +} - /*printf("checking is attachment %s/%s\n", ct->type, ct->subtype);*/ - return !(camel_content_type_is (dw->mime_type, "multipart", "*") - || camel_content_type_is ( - dw->mime_type, "application", "x-pkcs7-mime") - || camel_content_type_is ( - dw->mime_type, "application", "pkcs7-mime") - || camel_content_type_is ( - dw->mime_type, "application", "x-inlinepgp-signed") - || camel_content_type_is ( - dw->mime_type, "application", "x-inlinepgp-encrypted") - || camel_content_type_is ( - dw->mime_type, "x-evolution", "evolution-rss-feed") - || camel_content_type_is (dw->mime_type, "text", "calendar") - || camel_content_type_is (dw->mime_type, "text", "x-calendar") - || (camel_content_type_is (dw->mime_type, "text", "*") - && camel_mime_part_get_filename (part) == NULL)); +void +em_format_remove_header (EMFormat * emf, + const gchar *name, + const gchar *value) +{ + GList *iter = NULL; + + g_return_if_fail (EM_IS_FORMAT (emf)); + g_return_if_fail (name && *name); + + iter = g_queue_peek_head_link (&emf->header_list); + while (iter) { + EMFormatHeader *header = iter->data; + + if (!header->value || !*header->value) { + GList *next = iter->next; + if (g_strcmp0 (name, header->name) == 0) + g_queue_delete_link (&emf->header_list, iter); + + iter = next; + continue; + } + + if (value && *value) { + if ((g_strcmp0 (name, header->name) == 0) && + (g_strcmp0 (value, header->value) == 0)) + break; + } else { + if (g_strcmp0 (name, header->name) == 0) + break; + } + + iter = iter->next; + } + + if (iter) { + em_format_header_free (iter->data); + g_queue_delete_link (&emf->header_list, iter); + } +} + +void +em_format_remove_header_struct (EMFormat * emf, + const EMFormatHeader * header) +{ + g_return_if_fail (header); + + em_format_remove_header (emf, header->name, header->value); +} + +void +em_format_add_puri (EMFormat *emf, + EMFormatPURI *puri) +{ + GList *item; + + g_return_if_fail (EM_IS_FORMAT (emf)); + g_return_if_fail (puri != NULL); + + emf->mail_part_list = g_list_append (emf->mail_part_list, puri); + item = g_list_last (emf->mail_part_list); + + g_hash_table_insert (emf->mail_part_table, + puri->uri, item); + + d(printf("Added PURI %s\n", puri->uri)); +} + +EMFormatPURI * +em_format_find_puri (EMFormat *emf, + const gchar *id) +{ + GList *list_iter; + + /* First handle CIDs... */ + if (g_str_has_prefix (id, "CID:") || g_str_has_prefix (id, "cid:")) { + GHashTableIter iter; + gpointer key, value; + + g_hash_table_iter_init (&iter, emf->mail_part_table); + while (g_hash_table_iter_next (&iter, &key, &value)) { + EMFormatPURI *puri = ((GList *) value)->data; + if (g_strcmp0 (puri->cid, id) == 0) + return puri; + } + + return NULL; + } + + list_iter = g_hash_table_lookup (emf->mail_part_table, id); + if (list_iter) + return list_iter->data; + + return NULL; +} + +void +em_format_class_add_handler (EMFormatClass *emfc, + EMFormatHandler *handler) +{ + EMFormatHandler *old_handler; + + g_return_if_fail (EM_IS_FORMAT_CLASS (emfc)); + g_return_if_fail (handler); + + old_handler = g_hash_table_lookup ( + emfc->type_handlers, handler->mime_type); + + handler->old = old_handler; + + /* If parse_func or write_func of the new handler is not set, + * use function from the old handler (if it exists). + * This way we can assign a new write_func for to an existing + * parse_func */ + if (old_handler && handler->parse_func == NULL) { + handler->parse_func = old_handler->parse_func; + } + + if (old_handler && handler->write_func == NULL) { + handler->write_func = old_handler->write_func; + } + + g_hash_table_insert (emfc->type_handlers, + handler->mime_type, handler); +} + +void +em_format_class_remove_handler (EMFormatClass *emfc, + EMFormatHandler *handler) +{ + g_return_if_fail (EM_IS_FORMAT_CLASS (emfc)); + g_return_if_fail (handler); + + g_hash_table_remove (emfc->type_handlers, handler->mime_type); +} + +const EMFormatHandler * +em_format_find_handler (EMFormat *emf, + const gchar *mime_type) +{ + EMFormatClass *emfc; + gchar *s; + const EMFormatHandler *handler; + + g_return_val_if_fail (EM_IS_FORMAT (emf), NULL); + g_return_val_if_fail (mime_type && *mime_type, NULL); + + emfc = (EMFormatClass *) G_OBJECT_GET_CLASS (emf); + + s = g_ascii_strdown (mime_type, -1); + + handler = g_hash_table_lookup ( + emfc->type_handlers, s); + + g_free (s); + + return handler; } /** - * em_format_is_inline: + * em_format_fallback_handler: * @emf: - * @part: - * @part_id: format->part_id part id of this part. - * @handle: handler for this part + * @mime_type: * - * Returns true if the part should be displayed inline. Any part with - * a Content-Disposition of inline, or if the @handle has a default - * inline set, will be shown inline. + * Try to find a format handler based on the major type of the @mime_type. * - * :set_inline() called on the same part will override any calculated - * value. + * The subtype is replaced with "*" and a lookup performed. * * Return value: **/ -gboolean -em_format_is_inline (EMFormat *emf, - const gchar *part_id, - CamelMimePart *mime_part, - const EMFormatHandler *handle) +const EMFormatHandler * +em_format_fallback_handler (EMFormat *emf, + const gchar *mime_type) { - EMFormatClass *class; + gchar *mime, *s; - g_return_val_if_fail (EM_IS_FORMAT (emf), FALSE); - g_return_val_if_fail (part_id != NULL, FALSE); - g_return_val_if_fail (CAMEL_IS_MIME_PART (mime_part), FALSE); + s = strchr (mime_type, '/'); + if (s == NULL) + mime = (gchar *) mime_type; + else { + gsize len = (s - mime_type) + 1; - class = EM_FORMAT_GET_CLASS (emf); - g_return_val_if_fail (class->is_inline != NULL, FALSE); + mime = g_alloca (len + 2); + strncpy (mime, mime_type, len); + strcpy(mime+len, "*"); + } - return class->is_inline (emf, part_id, mime_part, handle); + return em_format_find_handler (emf, mime); } -/** - * em_format_set_inline: - * @emf: - * @part_id: id of part - * @state: - * - * Force the attachment @part to be expanded or hidden explictly to match - * @state. This is used only to record the change for a redraw or - * cloned layout render and does not force a redraw. - **/ void -em_format_set_inline (EMFormat *emf, - const gchar *part_id, - gint state) +em_format_parse (EMFormat *emf, + CamelMimeMessage *message, + CamelFolder *folder, + GCancellable *cancellable) { - EMFormatCache *emfc; + GString *part_id; + EMFormatPURI *puri; + EMFormatParserInfo info = { 0 }; g_return_if_fail (EM_IS_FORMAT (emf)); - g_return_if_fail (part_id != NULL); - emfc = g_hash_table_lookup (emf->inline_table, part_id); - if (emfc == NULL) { - emfc = emf_insert_cache (emf, part_id); - } else if (emfc->state != INLINE_UNSET && (emfc->state & 1) == state) + if (g_cancellable_is_cancelled (cancellable)) return; - emfc->state = state ? INLINE_ON : INLINE_OFF; + if (message) { + g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message)); + + if (emf->message) + g_object_unref (emf->message); + emf->message = g_object_ref (message); + } + + if (folder) { + g_return_if_fail (CAMEL_IS_FOLDER (folder)); - if (emf->message) - em_format_queue_redraw (emf); + if (emf->folder) + g_object_unref (emf->folder); + emf->folder = g_object_ref (folder); + } + + /* Before the actual parsing starts, let child classes prepare themselves. */ + if (EM_FORMAT_GET_CLASS (emf)->preparse) + EM_FORMAT_GET_CLASS (emf)->preparse (emf); + + part_id = g_string_new (".message"); + + /* Create a special PURI with entire message */ + puri = em_format_puri_new (emf, sizeof (EMFormatPURI), + (CamelMimePart *) emf->message, part_id->str); + puri->mime_type = g_strdup ("text/html"); + em_format_add_puri (emf, puri); + + info.force_handler = TRUE; + em_format_parse_part_as (emf, CAMEL_MIME_PART (emf->message), part_id, &info, + "x-evolution/message", cancellable); + + g_string_free (part_id, TRUE); } void -em_format_format_attachment (EMFormat *emf, - CamelStream *stream, - CamelMimePart *mime_part, - const gchar *mime_type, - const EMFormatHandler *info, - GCancellable *cancellable) +em_format_write (EMFormat *emf, + CamelStream *stream, + EMFormatWriterInfo *info, + GCancellable *cancellable) { - EMFormatClass *class; + EMFormatClass *emf_class; g_return_if_fail (EM_IS_FORMAT (emf)); g_return_if_fail (CAMEL_IS_STREAM (stream)); - g_return_if_fail (CAMEL_IS_MIME_PART (mime_part)); - g_return_if_fail (mime_type != NULL); - g_return_if_fail (info != NULL); - class = EM_FORMAT_GET_CLASS (emf); - g_return_if_fail (class->format_attachment != NULL); + emf_class = EM_FORMAT_GET_CLASS (emf); + if (emf_class->write) + emf_class->write (emf, stream, info, cancellable); +} - class->format_attachment ( - emf, stream, mime_part, mime_type, info, cancellable); +static void +emf_start_async_parser (GSimpleAsyncResult *result, + GObject *object, + GCancellable *cancellable) +{ + em_format_parse (EM_FORMAT (object), NULL, NULL, cancellable); } void -em_format_format_error (EMFormat *emf, - CamelStream *stream, - const gchar *format, - ...) +em_format_parse_async (EMFormat *emf, + CamelMimeMessage *message, + CamelFolder *folder, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) { - EMFormatClass *class; - gchar *errmsg; - va_list ap; + GSimpleAsyncResult *result; g_return_if_fail (EM_IS_FORMAT (emf)); - g_return_if_fail (CAMEL_IS_STREAM (stream)); - g_return_if_fail (format != NULL); - class = EM_FORMAT_GET_CLASS (emf); - g_return_if_fail (class->format_error != NULL); + if (g_cancellable_is_cancelled (cancellable)) + return; - va_start (ap, format); - errmsg = g_strdup_vprintf (format, ap); - class->format_error (emf, stream, errmsg); - g_free (errmsg); - va_end (ap); + if (message) { + g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message)); + + if (emf->message) + g_object_unref (emf->message); + + emf->message = g_object_ref (message); + + } + + if (folder) { + g_return_if_fail (CAMEL_IS_FOLDER (folder)); + + if (emf->folder) + g_object_unref (emf->folder); + + emf->folder = g_object_ref (folder); + + } + + result = g_simple_async_result_new (G_OBJECT (emf), callback, + user_data, em_format_parse_async); + g_simple_async_result_run_in_thread (result, emf_start_async_parser, + G_PRIORITY_DEFAULT, cancellable); } void -em_format_format_secure (EMFormat *emf, - CamelStream *stream, - CamelMimePart *mime_part, - CamelCipherValidity *valid, +em_format_parse_part_as (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + const gchar *mime_type, GCancellable *cancellable) { - EMFormatClass *class; - - g_return_if_fail (EM_IS_FORMAT (emf)); - g_return_if_fail (CAMEL_IS_STREAM (stream)); - g_return_if_fail (CAMEL_IS_MIME_PART (mime_part)); - g_return_if_fail (valid != NULL); + const EMFormatHandler *handler; + const CamelContentDisposition *disposition; + EMFormatParserInfo ninfo = { + .handler = 0, + .validity_type = info ? info->validity_type : 0, + .validity = info ? info->validity : 0, + .force_handler = 0 + }; + + /* Let everything that claims to be an attachment or inlined part to be parsed + * as an attachment. The parser will decide how to display it. */ + disposition = camel_mime_part_get_content_disposition (part); + if (!info->force_handler && disposition && + (g_strcmp0 (disposition->disposition, "attachment") == 0)) { + ninfo.is_attachment = TRUE; + handler = em_format_find_handler (emf, "x-evolution/message/attachment"); + ninfo.handler = handler; + + if (handler && handler->parse_func) + handler->parse_func (emf, part, part_id, &ninfo, cancellable); - class = EM_FORMAT_GET_CLASS (emf); - g_return_if_fail (class->format_secure != NULL); + return; + } - class->format_secure (emf, stream, mime_part, valid, cancellable); + handler = em_format_find_handler (emf, mime_type); + if (handler && handler->parse_func) { + ninfo.handler = handler; + handler->parse_func (emf, part, part_id, &ninfo, cancellable); + } else { + handler = em_format_find_handler (emf, "x-evolution/message/attachment"); + ninfo.handler = handler; - if (emf->valid_parent == NULL && emf->valid != NULL) { - camel_cipher_validity_free (emf->valid); - emf->valid = NULL; + /* When this fails, something is probably very wrong...*/ + if (handler && handler->parse_func) + handler->parse_func (emf, part, part_id, &ninfo, cancellable); } } void -em_format_format_source (EMFormat *emf, - CamelStream *stream, - CamelMimePart *mime_part, - GCancellable *cancellable) +em_format_parse_part (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) { - EMFormatClass *class; + CamelContentType *ct; + gchar *mime_type; - g_return_if_fail (EM_IS_FORMAT (emf)); - g_return_if_fail (CAMEL_IS_STREAM (stream)); - g_return_if_fail (CAMEL_IS_MIME_PART (mime_part)); + ct = camel_mime_part_get_content_type (part); + if (ct) { + mime_type = camel_content_type_simple (ct); + } else { + mime_type = (gchar *) "text/plain"; + } - class = EM_FORMAT_GET_CLASS (emf); - g_return_if_fail (class->format_source != NULL); + em_format_parse_part_as (emf, part, part_id, info, mime_type, cancellable); - class->format_source (emf, stream, mime_part, cancellable); + if (ct) + g_free (mime_type); } gboolean -em_format_busy (EMFormat *emf) +em_format_is_inline (EMFormat *emf, + const gchar *part_id, + CamelMimePart *part, + const EMFormatHandler *handler) { - EMFormatClass *class; + EMFormatClass *klass; g_return_val_if_fail (EM_IS_FORMAT (emf), FALSE); + g_return_val_if_fail (part_id && *part_id, FALSE); + g_return_val_if_fail (CAMEL_IS_MIME_PART (part), FALSE); + g_return_val_if_fail (handler, FALSE); - class = EM_FORMAT_GET_CLASS (emf); - g_return_val_if_fail (class->busy != NULL, FALSE); + klass = EM_FORMAT_GET_CLASS (emf); + g_return_val_if_fail (klass->is_inline != NULL, FALSE); + + return klass->is_inline (emf, part_id, part, handler); - return class->busy (emf); } -/* should this be virtual? */ void -em_format_format_content (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - GCancellable *cancellable) +em_format_format_error (EMFormat *emf, + const gchar *format, + ...) { - CamelDataWrapper *dw = camel_medium_get_content ((CamelMedium *) part); + EMFormatPURI *puri; + CamelMimePart *part; + const EMFormatHandler *handler; + gchar *errmsg; + gchar *uri; + va_list ap; + + g_return_if_fail (EM_IS_FORMAT (emf)); + g_return_if_fail (format != NULL); + + va_start (ap, format); + errmsg = g_strdup_vprintf (format, ap); - if (camel_content_type_is (dw->mime_type, "text", "*")) - em_format_format_text ( - emf, stream, (CamelDataWrapper *) part, cancellable); + part = camel_mime_part_new (); + camel_mime_part_set_content (part, errmsg, strlen (errmsg), "text/plain"); + g_free (errmsg); + va_end (ap); + + handler = em_format_find_handler (emf, "x-evolution/error"); + + emf->priv->last_error++; + uri = g_strdup_printf (".error.%d", emf->priv->last_error); + puri = em_format_puri_new (emf, sizeof (EMFormatPURI), part, uri); + puri->mime_type = g_strdup ("text/html"); + if (handler && handler->write_func) + puri->write_func = handler->write_func; else - camel_data_wrapper_decode_to_stream_sync ( - dw, stream, cancellable, NULL); + puri->write_func = emf_write_error; + + em_format_add_puri (emf, puri); + + g_free (uri); + g_object_unref (part); } /** - * em_format_format_content: + * em_format_format_text: * @emf: * @stream: Where to write the converted text * @part: Part whose container is to be formatted @@ -1370,8 +2129,11 @@ em_format_format_text (EMFormat *emf, gsize max; GSettings *settings; - if (emf->charset) { - charset = emf->charset; + if (g_cancellable_is_cancelled (cancellable)) + return; + + if (emf->priv->charset) { + charset = emf->priv->charset; } else if (dw->mime_type && (charset = camel_content_type_param (dw->mime_type, "charset")) && g_ascii_strncasecmp(charset, "iso-8859-", 9) == 0) { @@ -1398,7 +2160,7 @@ em_format_format_text (EMFormat *emf, charset = camel_mime_filter_windows_real_charset (windows); } else if (charset == NULL) { - charset = emf->default_charset; + charset = emf->priv->default_charset; } mem_stream = (CamelStream *) camel_stream_mem_new (); @@ -1422,8 +2184,6 @@ em_format_format_text (EMFormat *emf, g_object_unref (settings); size = camel_data_wrapper_decode_to_stream_sync ( - emf->mode == EM_FORMAT_MODE_SOURCE ? - (CamelDataWrapper *) dw : camel_medium_get_content ((CamelMedium *) dw), (CamelStream *) filter_stream, cancellable, NULL); camel_stream_flush ((CamelStream *) filter_stream, cancellable, NULL); @@ -1431,14 +2191,23 @@ em_format_format_text (EMFormat *emf, g_seekable_seek (G_SEEKABLE (mem_stream), 0, G_SEEK_SET, NULL, NULL); - if (max == -1 || size == -1 || size < (max * 1024) || emf->composer) { + if (max == -1 || size == -1 || size < (max * 1024) || emf->priv->composer) { camel_stream_write_to_stream ( mem_stream, (CamelStream *) stream, cancellable, NULL); - camel_stream_flush ((CamelStream *) stream, cancellable, NULL); + camel_stream_flush ((CamelStream *) mem_stream, cancellable, NULL); } else { - EM_FORMAT_GET_CLASS (emf)->format_optional ( - emf, stream, (CamelMimePart *) dw, - mem_stream, cancellable); + /* Parse it as an attachment */ + CamelMimePart *part = camel_mime_part_new (); + EMFormatParserInfo info = { 0 }; + GString *part_id = g_string_new (".attachment"); + camel_medium_set_content ((CamelMedium *) part, dw); + + info.is_attachment = TRUE; + em_format_parse_part_as (emf, part, part_id, &info, + "x-evolution/message/attachment", cancellable); + + g_string_free (part_id, TRUE); + g_object_unref (part); } if (windows) @@ -1452,7 +2221,7 @@ em_format_format_text (EMFormat *emf, * @part: * @mimetype: * - * Generate a simple textual description of a part, @mime_type represents the + * Generate a simple textual description of a part, @mime_type represents * the content. * * Return value: @@ -1500,909 +2269,48 @@ em_format_describe_part (CamelMimePart *part, return g_string_free (stext, FALSE); } -static void -add_validity_found (EMFormat *emf, - CamelCipherValidity *valid) -{ - g_return_if_fail (emf != NULL); - - if (!valid) - return; - - if (valid->sign.status != CAMEL_CIPHER_VALIDITY_SIGN_NONE) - emf->validity_found |= EM_FORMAT_VALIDITY_FOUND_SIGNED; - - if (valid->encrypt.status != CAMEL_CIPHER_VALIDITY_ENCRYPT_NONE) - emf->validity_found |= EM_FORMAT_VALIDITY_FOUND_ENCRYPTED; -} - -/* ********************************************************************** */ - -static void -preserve_charset_in_content_type (CamelMimePart *ipart, - CamelMimePart *opart) -{ - CamelDataWrapper *data_wrapper; - CamelContentType *content_type; - const gchar *charset; - - g_return_if_fail (ipart != NULL); - g_return_if_fail (opart != NULL); - - data_wrapper = camel_medium_get_content (CAMEL_MEDIUM (ipart)); - content_type = camel_data_wrapper_get_mime_type_field (data_wrapper); - - if (content_type == NULL) - return; - - charset = camel_content_type_param (content_type, "charset"); - - if (charset == NULL || *charset == '\0') - return; - - data_wrapper = camel_medium_get_content (CAMEL_MEDIUM (opart)); - content_type = camel_data_wrapper_get_mime_type_field (data_wrapper); - - camel_content_type_set_param (content_type, "charset", charset); -} - -#ifdef ENABLE_SMIME -static void -emf_application_xpkcs7mime (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) -{ - CamelCipherContext *context; - CamelMimePart *opart; - CamelCipherValidity *valid; - EMFormatCache *emfc; - GError *local_error = NULL; - - /* should this perhaps run off a key of ".secured" ? */ - emfc = g_hash_table_lookup (emf->inline_table, emf->part_id->str); - if (emfc && emfc->valid) { - em_format_format_secure ( - emf, stream, emfc->secured, - camel_cipher_validity_clone (emfc->valid), - cancellable); - return; - } - - context = camel_smime_context_new (emf->session); - - emf->validity_found |= - EM_FORMAT_VALIDITY_FOUND_ENCRYPTED | - EM_FORMAT_VALIDITY_FOUND_SMIME; - - opart = camel_mime_part_new (); - valid = camel_cipher_context_decrypt_sync ( - context, part, opart, cancellable, &local_error); - preserve_charset_in_content_type (part, opart); - if (valid == NULL) { - em_format_format_error ( - emf, stream, "%s", - local_error->message ? local_error->message : - _("Could not parse S/MIME message: Unknown error")); - g_clear_error (&local_error); - - em_format_part_as (emf, stream, part, NULL, cancellable); - } else { - if (emfc == NULL) - emfc = emf_insert_cache (emf, emf->part_id->str); - - emfc->valid = camel_cipher_validity_clone (valid); - g_object_ref ((emfc->secured = opart)); - - add_validity_found (emf, valid); - em_format_format_secure ( - emf, stream, opart, valid, cancellable); - } - - g_object_unref (opart); - g_object_unref (context); -} -#endif - -/* RFC 1740 */ -static void -emf_multipart_appledouble (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) -{ - CamelMultipart *mp; - CamelMimePart *mime_part; - gint len; - - mp = (CamelMultipart *) camel_medium_get_content ((CamelMedium *) part); - - if (!CAMEL_IS_MULTIPART (mp)) { - em_format_format_source (emf, stream, part, cancellable); - return; - } - - mime_part = camel_multipart_get_part (mp, 1); - if (mime_part) { - /* try the data fork for something useful, doubtful but who knows */ - len = emf->part_id->len; - g_string_append_printf(emf->part_id, ".appledouble.1"); - em_format_part (emf, stream, mime_part, cancellable); - g_string_truncate (emf->part_id, len); - } else - em_format_format_source (emf, stream, part, cancellable); - -} - -/* RFC ??? */ -static void -emf_multipart_mixed (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) -{ - CamelMultipart *mp; - gint i, nparts, len; - - mp = (CamelMultipart *) camel_medium_get_content ((CamelMedium *) part); - - if (!CAMEL_IS_MULTIPART (mp)) { - em_format_format_source (emf, stream, part, cancellable); - return; - } - - len = emf->part_id->len; - nparts = camel_multipart_get_number (mp); - for (i = 0; i < nparts; i++) { - part = camel_multipart_get_part (mp, i); - g_string_append_printf(emf->part_id, ".mixed.%d", i); - em_format_part (emf, stream, part, cancellable); - g_string_truncate (emf->part_id, len); - } -} - -static gboolean related_display_part_is_attachment - (EMFormat *emf, - CamelMimePart *part); - -/* RFC 1740 */ -static void -emf_multipart_alternative (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) -{ - CamelMultipart *mp; - gint i, nparts, bestid = 0; - CamelMimePart *best = NULL; - - mp = (CamelMultipart *) camel_medium_get_content ((CamelMedium *) part); - - if (!CAMEL_IS_MULTIPART (mp)) { - em_format_format_source (emf, stream, part, cancellable); - return; - } - - /* as per rfc, find the last part we know how to display */ - nparts = camel_multipart_get_number (mp); - for (i = 0; i < nparts; i++) { - CamelDataWrapper *data_wrapper; - CamelContentType *type; - CamelStream *null_stream; - gchar *mime_type; - gsize content_size; - - /* is it correct to use the passed in *part here? */ - part = camel_multipart_get_part (mp, i); - - if (part == NULL) - continue; - - /* This may block even though the stream does not. - * XXX Pretty inefficient way to test if the MIME part - * is empty. Surely there's a quicker way? */ - null_stream = camel_stream_null_new (); - data_wrapper = camel_medium_get_content (CAMEL_MEDIUM (part)); - camel_data_wrapper_decode_to_stream_sync ( - data_wrapper, null_stream, cancellable, NULL); - content_size = CAMEL_STREAM_NULL (null_stream)->written; - g_object_unref (null_stream); - - if (content_size == 0) - continue; - - type = camel_mime_part_get_content_type (part); - mime_type = camel_content_type_simple (type); - - camel_strdown (mime_type); - - /*if (want_plain && !strcmp (mime_type, "text/plain")) - return part;*/ - - if (!em_format_is_attachment (emf, part) && - (!camel_content_type_is (type, "multipart", "related") || - !related_display_part_is_attachment (emf, part)) && - (em_format_find_handler (emf, mime_type) - || (best == NULL && em_format_fallback_handler (emf, mime_type)))) { - best = part; - bestid = i; - } - - g_free (mime_type); - } - - if (best) { - gint len = emf->part_id->len; - - g_string_append_printf(emf->part_id, ".alternative.%d", bestid); - em_format_part (emf, stream, best, cancellable); - g_string_truncate (emf->part_id, len); - } else - emf_multipart_mixed ( - emf, stream, part, info, cancellable, is_fallback); -} - -static void -emf_multipart_encrypted (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) -{ - CamelCipherContext *context; - const gchar *protocol; - CamelMimePart *opart; - CamelCipherValidity *valid; - CamelMultipartEncrypted *mpe; - EMFormatCache *emfc; - GError *local_error = NULL; - - /* should this perhaps run off a key of ".secured" ? */ - emfc = g_hash_table_lookup (emf->inline_table, emf->part_id->str); - if (emfc && emfc->valid) { - em_format_format_secure ( - emf, stream, emfc->secured, - camel_cipher_validity_clone (emfc->valid), - cancellable); - return; - } - - mpe = (CamelMultipartEncrypted *) camel_medium_get_content ((CamelMedium *) part); - if (!CAMEL_IS_MULTIPART_ENCRYPTED (mpe)) { - em_format_format_error ( - emf, stream, _("Could not parse MIME message. " - "Displaying as source.")); - em_format_format_source (emf, stream, part, cancellable); - return; - } - - /* Currently we only handle RFC2015-style PGP encryption. */ - protocol = camel_content_type_param ( - ((CamelDataWrapper *)mpe)->mime_type, "protocol"); - if (protocol == NULL || g_ascii_strcasecmp (protocol, "application/pgp-encrypted") != 0) { - em_format_format_error ( - emf, stream, _("Unsupported encryption " - "type for multipart/encrypted")); - em_format_part_as ( - emf, stream, part, - "multipart/mixed", cancellable); - return; - } - - emf->validity_found |= - EM_FORMAT_VALIDITY_FOUND_ENCRYPTED | - EM_FORMAT_VALIDITY_FOUND_PGP; - - context = camel_gpg_context_new (emf->session); - opart = camel_mime_part_new (); - valid = camel_cipher_context_decrypt_sync ( - context, part, opart, cancellable, &local_error); - preserve_charset_in_content_type (part, opart); - if (valid == NULL) { - em_format_format_error ( - emf, stream, local_error->message ? - _("Could not parse PGP/MIME message") : - _("Could not parse PGP/MIME message: Unknown error")); - if (local_error->message != NULL) - em_format_format_error ( - emf, stream, "%s", local_error->message); - g_clear_error (&local_error); - - em_format_part_as ( - emf, stream, part, - "multipart/mixed", cancellable); - } else { - if (emfc == NULL) - emfc = emf_insert_cache (emf, emf->part_id->str); - - emfc->valid = camel_cipher_validity_clone (valid); - g_object_ref ((emfc->secured = opart)); - - add_validity_found (emf, valid); - em_format_format_secure ( - emf, stream, opart, valid, cancellable); - } - - /* TODO: Make sure when we finalize this part, it is zero'd out */ - g_object_unref (opart); - g_object_unref (context); -} - -static CamelMimePart * -get_related_display_part (CamelMimePart *part, - gint *out_displayid) -{ - CamelMultipart *mp; - CamelMimePart *body_part, *display_part = NULL; - CamelContentType *content_type; - const gchar *start; - gint i, nparts, displayid = 0; - - mp = (CamelMultipart *) camel_medium_get_content ((CamelMedium *) part); - - if (!CAMEL_IS_MULTIPART (mp)) - return NULL; - - nparts = camel_multipart_get_number (mp); - content_type = camel_mime_part_get_content_type (part); - start = camel_content_type_param (content_type, "start"); - if (start && strlen (start) > 2) { - gint len; - const gchar *cid; - - /* strip <>'s */ - len = strlen (start) - 2; - start++; - - for (i = 0; i < nparts; i++) { - body_part = camel_multipart_get_part (mp, i); - cid = camel_mime_part_get_content_id (body_part); - - if (cid && !strncmp (cid, start, len) && strlen (cid) == len) { - display_part = body_part; - displayid = i; - break; - } - } - } else { - display_part = camel_multipart_get_part (mp, 0); - } - - if (out_displayid) - *out_displayid = displayid; - - return display_part; -} - -static gboolean -related_display_part_is_attachment (EMFormat *emf, - CamelMimePart *part) -{ - CamelMimePart *display_part; - - display_part = get_related_display_part (part, NULL); - return display_part && em_format_is_attachment (emf, display_part); -} - -static void -emf_write_related (EMFormat *emf, - CamelStream *stream, - EMFormatPURI *puri, - GCancellable *cancellable) -{ - em_format_format_content (emf, stream, puri->part, cancellable); - camel_stream_close (stream, cancellable, NULL); -} - -/* RFC 2387 */ -static void -emf_multipart_related (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) -{ - CamelMultipart *mp; - CamelMimePart *body_part, *display_part = NULL; - gint i, nparts, partidlen, displayid = 0; - gchar *oldpartid; - GList *link; - - mp = (CamelMultipart *) camel_medium_get_content ((CamelMedium *) part); - - if (!CAMEL_IS_MULTIPART (mp)) { - em_format_format_source (emf, stream, part, cancellable); - return; - } - - display_part = get_related_display_part (part, &displayid); - - if (display_part == NULL) { - emf_multipart_mixed ( - emf, stream, part, info, cancellable, is_fallback); - return; - } - - em_format_push_level (emf); - - oldpartid = g_strdup (emf->part_id->str); - partidlen = emf->part_id->len; - - /* queue up the parts for possible inclusion */ - nparts = camel_multipart_get_number (mp); - for (i = 0; i < nparts; i++) { - body_part = camel_multipart_get_part (mp, i); - if (body_part != display_part) { - /* set the partid since add_puri uses it */ - g_string_append_printf(emf->part_id, ".related.%d", i); - em_format_add_puri ( - emf, sizeof (EMFormatPURI), NULL, - body_part, emf_write_related); - g_string_truncate (emf->part_id, partidlen); - } - } - - g_string_append_printf(emf->part_id, ".related.%d", displayid); - em_format_part (emf, stream, display_part, cancellable); - g_string_truncate (emf->part_id, partidlen); - camel_stream_flush (stream, NULL, NULL); - - link = g_queue_peek_head_link (emf->pending_uri_level->data); - - while (link && link->next != NULL) { - EMFormatPURI *puri = link->data; - - if (puri->use_count == 0) { - if (puri->func == emf_write_related) { - g_string_printf(emf->part_id, "%s", puri->part_id); - em_format_part ( - emf, stream, puri->part, cancellable); - } - } - - link = g_list_next (link); - } - - g_string_printf(emf->part_id, "%s", oldpartid); - g_free (oldpartid); - - em_format_pull_level (emf); -} - -static void -emf_multipart_signed (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) -{ - CamelMimePart *cpart; - CamelMultipartSigned *mps; - CamelCipherContext *cipher = NULL; - EMFormatCache *emfc; - - /* should this perhaps run off a key of ".secured" ? */ - emfc = g_hash_table_lookup (emf->inline_table, emf->part_id->str); - if (emfc && emfc->valid) { - em_format_format_secure ( - emf, stream, emfc->secured, - camel_cipher_validity_clone (emfc->valid), - cancellable); - return; - } - - mps = (CamelMultipartSigned *) camel_medium_get_content ((CamelMedium *) part); - if (!CAMEL_IS_MULTIPART_SIGNED (mps) - || (cpart = camel_multipart_get_part ((CamelMultipart *) mps, - CAMEL_MULTIPART_SIGNED_CONTENT)) == NULL) { - em_format_format_error ( - emf, stream, _("Could not parse MIME message. " - "Displaying as source.")); - em_format_format_source (emf, stream, part, cancellable); - return; - } - - /* FIXME: Should be done via a plugin interface */ - /* FIXME: duplicated in em-format-html-display.c */ - if (mps->protocol) { -#ifdef ENABLE_SMIME - if (g_ascii_strcasecmp ("application/x-pkcs7-signature", mps->protocol) == 0 - || g_ascii_strcasecmp ("application/pkcs7-signature", mps->protocol) == 0) { - cipher = camel_smime_context_new (emf->session); - emf->validity_found |= EM_FORMAT_VALIDITY_FOUND_SMIME; - } else -#endif - if (g_ascii_strcasecmp ("application/pgp-signature", mps->protocol) == 0) { - cipher = camel_gpg_context_new (emf->session); - emf->validity_found |= EM_FORMAT_VALIDITY_FOUND_PGP; - } - } - - emf->validity_found |= EM_FORMAT_VALIDITY_FOUND_SIGNED; - - if (cipher == NULL) { - em_format_format_error(emf, stream, _("Unsupported signature format")); - em_format_part_as ( - emf, stream, part, - "multipart/mixed", cancellable); - } else { - CamelCipherValidity *valid; - GError *local_error = NULL; - - valid = camel_cipher_context_verify_sync ( - cipher, part, cancellable, &local_error); - if (valid == NULL) { - em_format_format_error ( - emf, stream, local_error->message ? - _("Error verifying signature") : - _("Unknown error verifying signature")); - if (local_error->message != NULL) - em_format_format_error ( - emf, stream, "%s", - local_error->message); - g_clear_error (&local_error); - - em_format_part_as ( - emf, stream, part, - "multipart/mixed", cancellable); - } else { - if (emfc == NULL) - emfc = emf_insert_cache (emf, emf->part_id->str); - - emfc->valid = camel_cipher_validity_clone (valid); - g_object_ref ((emfc->secured = cpart)); - - add_validity_found (emf, valid); - em_format_format_secure ( - emf, stream, cpart, valid, cancellable); - } - - g_object_unref (cipher); - } -} - -/* RFC 4155 */ -static void -emf_application_mbox (EMFormat *emf, - CamelStream *stream, - CamelMimePart *mime_part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) -{ - const EMFormatHandler *handle; - CamelMimeParser *parser; - CamelStream *mem_stream; - camel_mime_parser_state_t state; - - /* Extract messages from the application/mbox part and - * render them as a flat list of messages. */ - - /* XXX If the mbox has multiple messages, maybe render them - * as a multipart/digest so each message can be expanded - * or collapsed individually. - * - * See attachment_handler_mail_x_uid_list() for example. */ - - /* XXX This is based on em_utils_read_messages_from_stream(). - * Perhaps refactor that function to return an array of - * messages instead of assuming we want to append them - * to a folder? */ - - handle = em_format_find_handler (emf, "x-evolution/message/rfc822"); - g_return_if_fail (handle != NULL); - - parser = camel_mime_parser_new (); - camel_mime_parser_scan_from (parser, TRUE); - - mem_stream = camel_stream_mem_new (); - camel_data_wrapper_decode_to_stream_sync ( - camel_medium_get_content (CAMEL_MEDIUM (mime_part)), - mem_stream, NULL, NULL); - g_seekable_seek (G_SEEKABLE (mem_stream), 0, G_SEEK_SET, NULL, NULL); - camel_mime_parser_init_with_stream (parser, mem_stream, NULL); - g_object_unref (mem_stream); - - /* Extract messages from the mbox. */ - state = camel_mime_parser_step (parser, NULL, NULL); - while (state == CAMEL_MIME_PARSER_STATE_FROM) { - CamelMimeMessage *message; - - message = camel_mime_message_new (); - mime_part = CAMEL_MIME_PART (message); - - if (!camel_mime_part_construct_from_parser_sync ( - mime_part, parser, NULL, NULL)) { - g_object_unref (message); - break; - } - - /* Render the message. */ - handle->handler ( - emf, stream, mime_part, - handle, cancellable, FALSE); - - g_object_unref (message); - - /* Skip past CAMEL_MIME_PARSER_STATE_FROM_END. */ - camel_mime_parser_step (parser, NULL, NULL); - - state = camel_mime_parser_step (parser, NULL, NULL); - } - - g_object_unref (parser); -} - -static void -emf_message_rfc822 (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) +/** + * em_format_is_attachment: + * @emf: + * @part: Part to check. + * + * Returns true if the part is an attachment. + * + * A part is not considered an attachment if it is a + * multipart, or a text part with no filename. It is used + * to determine if an attachment header should be displayed for + * the part. + * + * Content-Disposition is not checked. + * + * Return value: TRUE/FALSE + **/ +gint +em_format_is_attachment (EMFormat *emf, + CamelMimePart *part) { + /*CamelContentType *ct = camel_mime_part_get_content_type(part);*/ CamelDataWrapper *dw = camel_medium_get_content ((CamelMedium *) part); - const EMFormatHandler *handle; - gint len; - gchar *parent_message_part_id; - - if (!CAMEL_IS_MIME_MESSAGE (dw)) { - em_format_format_source (emf, stream, part, cancellable); - return; - } - - parent_message_part_id = emf->current_message_part_id; - emf->current_message_part_id = g_strdup (emf->part_id->str); - - len = emf->part_id->len; - g_string_append_printf(emf->part_id, ".rfc822"); - - handle = em_format_find_handler(emf, "x-evolution/message/rfc822"); - if (handle) - handle->handler ( - emf, stream, CAMEL_MIME_PART (dw), - handle, cancellable, FALSE); - g_string_truncate (emf->part_id, len); - - g_free (emf->current_message_part_id); - emf->current_message_part_id = parent_message_part_id; -} - -static void -emf_message_deliverystatus (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) -{ - em_format_format_text ( - emf, stream, (CamelDataWrapper *) part, cancellable); -} - -static void -emf_inlinepgp_signed (EMFormat *emf, - CamelStream *stream, - CamelMimePart *ipart, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) -{ - CamelStream *filtered_stream; - CamelMimeFilterPgp *pgp_filter; - CamelContentType *content_type; - CamelCipherContext *cipher; - CamelCipherValidity *valid; - CamelDataWrapper *dw; - CamelMimePart *opart; - CamelStream *ostream; - gchar *type; - GError *local_error = NULL; - - if (!ipart) { - em_format_format_error(emf, stream, _("Unknown error verifying signature")); - return; - } - - emf->validity_found |= - EM_FORMAT_VALIDITY_FOUND_SIGNED | - EM_FORMAT_VALIDITY_FOUND_PGP; - - cipher = camel_gpg_context_new (emf->session); - /* Verify the signature of the message */ - valid = camel_cipher_context_verify_sync ( - cipher, ipart, cancellable, &local_error); - if (!valid) { - em_format_format_error ( - emf, stream, local_error->message ? - _("Error verifying signature") : - _("Unknown error verifying signature")); - if (local_error->message) - em_format_format_error ( - emf, stream, "%s", local_error->message); - em_format_format_source (emf, stream, ipart, cancellable); - /* XXX I think this will loop: - * em_format_part_as(emf, stream, part, "text/plain"); */ - g_clear_error (&local_error); - g_object_unref (cipher); - return; - } - - /* Setup output stream */ - ostream = camel_stream_mem_new (); - filtered_stream = camel_stream_filter_new (ostream); - - /* Add PGP header / footer filter */ - pgp_filter = (CamelMimeFilterPgp *) camel_mime_filter_pgp_new (); - camel_stream_filter_add ( - CAMEL_STREAM_FILTER (filtered_stream), - CAMEL_MIME_FILTER (pgp_filter)); - g_object_unref (pgp_filter); - - /* Pass through the filters that have been setup */ - dw = camel_medium_get_content ((CamelMedium *) ipart); - camel_data_wrapper_decode_to_stream_sync ( - dw, (CamelStream *) filtered_stream, NULL, NULL); - camel_stream_flush ((CamelStream *) filtered_stream, NULL, NULL); - g_object_unref (filtered_stream); - - /* Create a new text/plain MIME part containing the signed - * content preserving the original part's Content-Type params. */ - content_type = camel_mime_part_get_content_type (ipart); - type = camel_content_type_format (content_type); - content_type = camel_content_type_decode (type); - g_free (type); - - g_free (content_type->type); - content_type->type = g_strdup ("text"); - g_free (content_type->subtype); - content_type->subtype = g_strdup ("plain"); - type = camel_content_type_format (content_type); - camel_content_type_unref (content_type); - - dw = camel_data_wrapper_new (); - camel_data_wrapper_construct_from_stream_sync (dw, ostream, NULL, NULL); - camel_data_wrapper_set_mime_type (dw, type); - g_free (type); - - opart = camel_mime_part_new (); - camel_medium_set_content ((CamelMedium *) opart, dw); - camel_data_wrapper_set_mime_type_field ( - (CamelDataWrapper *) opart, dw->mime_type); - - add_validity_found (emf, valid); - /* Pass it off to the real formatter */ - em_format_format_secure (emf, stream, opart, valid, cancellable); - - /* Clean Up */ - g_object_unref (dw); - g_object_unref (opart); - g_object_unref (ostream); - g_object_unref (cipher); -} - -static void -emf_inlinepgp_encrypted (EMFormat *emf, - CamelStream *stream, - CamelMimePart *ipart, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) -{ - CamelCipherContext *cipher; - CamelCipherValidity *valid; - CamelMimePart *opart; - CamelDataWrapper *dw; - gchar *mime_type; - GError *local_error = NULL; - - emf->validity_found |= - EM_FORMAT_VALIDITY_FOUND_ENCRYPTED | - EM_FORMAT_VALIDITY_FOUND_PGP; - - cipher = camel_gpg_context_new (emf->session); - opart = camel_mime_part_new (); - - /* Decrypt the message */ - valid = camel_cipher_context_decrypt_sync ( - cipher, ipart, opart, cancellable, &local_error); - - if (!valid) { - em_format_format_error ( - emf, stream, _("Could not parse PGP message: ")); - if (local_error->message != NULL) - em_format_format_error ( - emf, stream, "%s", local_error->message); - else - em_format_format_error ( - emf, stream, _("Unknown error")); - em_format_format_source (emf, stream, ipart, cancellable); - /* XXX I think this will loop: - * em_format_part_as(emf, stream, part, "text/plain"); */ - - g_clear_error (&local_error); - g_object_unref (cipher); - g_object_unref (opart); - return; - } - - dw = camel_medium_get_content ((CamelMedium *) opart); - mime_type = camel_data_wrapper_get_mime_type (dw); - - /* this ensures to show the 'opart' as inlined, if possible */ - if (mime_type != NULL && g_ascii_strcasecmp ( - mime_type, "application/octet-stream") == 0) { - const gchar *snoop = em_format_snoop_type (opart); - - if (snoop) - camel_data_wrapper_set_mime_type (dw, snoop); - } - - preserve_charset_in_content_type (ipart, opart); - g_free (mime_type); - - add_validity_found (emf, valid); - /* Pass it off to the real formatter */ - em_format_format_secure (emf, stream, opart, valid, cancellable); - - /* Clean Up */ - g_object_unref (opart); - g_object_unref (cipher); -} - -static EMFormatHandler type_builtin_table[] = { -#ifdef ENABLE_SMIME - { (gchar *) "application/x-pkcs7-mime", - emf_application_xpkcs7mime, - EM_FORMAT_HANDLER_INLINE_DISPOSITION }, -#endif - { (gchar *) "application/mbox", emf_application_mbox, EM_FORMAT_HANDLER_INLINE }, - { (gchar *) "multipart/alternative", emf_multipart_alternative }, - { (gchar *) "multipart/appledouble", emf_multipart_appledouble }, - { (gchar *) "multipart/encrypted", emf_multipart_encrypted }, - { (gchar *) "multipart/mixed", emf_multipart_mixed }, - { (gchar *) "multipart/signed", emf_multipart_signed }, - { (gchar *) "multipart/related", emf_multipart_related }, - { (gchar *) "multipart/*", emf_multipart_mixed }, - { (gchar *) "message/rfc822", emf_message_rfc822, EM_FORMAT_HANDLER_INLINE }, - { (gchar *) "message/news", emf_message_rfc822, EM_FORMAT_HANDLER_INLINE }, - { (gchar *) "message/delivery-status", emf_message_deliverystatus }, - { (gchar *) "message/*", emf_message_rfc822, EM_FORMAT_HANDLER_INLINE }, - - /* Insert brokenly-named parts here */ -#ifdef ENABLE_SMIME - { (gchar *) "application/pkcs7-mime", - emf_application_xpkcs7mime, - EM_FORMAT_HANDLER_INLINE_DISPOSITION }, -#endif - - /* internal types */ - { (gchar *) "application/x-inlinepgp-signed", emf_inlinepgp_signed }, - { (gchar *) "application/x-inlinepgp-encrypted", emf_inlinepgp_encrypted }, -}; - -static void -emf_builtin_init (EMFormatClass *class) -{ - gint ii; + if (!dw) + return 0; - for (ii = 0; ii < G_N_ELEMENTS (type_builtin_table); ii++) - g_hash_table_insert ( - class->type_handlers, - type_builtin_table[ii].mime_type, - &type_builtin_table[ii]); + d(printf("checking is attachment %s/%s\n", dw->mime_type->type, dw->mime_type->subtype)); + return !(camel_content_type_is (dw->mime_type, "multipart", "*") + || camel_content_type_is ( + dw->mime_type, "application", "x-pkcs7-mime") + || camel_content_type_is ( + dw->mime_type, "application", "pkcs7-mime") + || camel_content_type_is ( + dw->mime_type, "application", "x-inlinepgp-signed") + || camel_content_type_is ( + dw->mime_type, "application", "x-inlinepgp-encrypted") + || camel_content_type_is ( + dw->mime_type, "x-evolution", "evolution-rss-feed") + || camel_content_type_is (dw->mime_type, "text", "calendar") + || camel_content_type_is (dw->mime_type, "text", "x-calendar") + || (camel_content_type_is (dw->mime_type, "text", "*") + && camel_mime_part_get_filename (part) == NULL)); } /** @@ -2493,5 +2401,237 @@ em_format_snoop_type (CamelMimePart *part) return res; /* We used to load parts to check their type, we don't anymore, - * see bug #11778 for some discussion */ + * see bug #211778 for some discussion */ +} + +/** + * Construct a URI for message. + * + * The URI can contain multiple query parameters. The list of parameters must be + * NULL-terminated. Each query must contain name, GType of value and value. + * + * @param folder Folder wit the message + * @param message_uid ID of message within the \p folder + * @param first_param_name Name of first query parameter followed by GType of it's value and value. + */ +gchar * +em_format_build_mail_uri (CamelFolder *folder, + const gchar *message_uid, + const gchar *first_param_name, + ...) +{ + CamelStore *store; + gchar *uri, *tmp; + va_list ap; + const gchar *name; + const gchar *service_uid, *folder_name; + gchar separator; + + g_return_val_if_fail (message_uid && *message_uid, NULL); + + if (!folder) { + folder_name = "generic"; + service_uid = "generic"; + } else { + folder_name = camel_folder_get_full_name (folder); + store = camel_folder_get_parent_store (folder); + if (store) + service_uid = camel_service_get_uid (CAMEL_SERVICE (store)); + else + service_uid = "generic"; + } + + tmp = g_strdup_printf ("mail://%s/%s/%s", + service_uid, + folder_name, + message_uid); + + va_start (ap, first_param_name); + name = first_param_name; + separator = '?'; + while (name) { + gchar *tmp2; + gint type = va_arg (ap, gint); + switch (type) { + case G_TYPE_INT: + case G_TYPE_BOOLEAN: { + gint val = va_arg (ap, gint); + tmp2 = g_strdup_printf ("%s%c%s=%d", tmp, + separator, name, val); + break; + } + case G_TYPE_FLOAT: + case G_TYPE_DOUBLE: { + gdouble val = va_arg (ap, double); + tmp2 = g_strdup_printf ("%s%c%s=%f", tmp, + separator, name, val); + break; + } + case G_TYPE_STRING: { + gchar *val = va_arg (ap, gchar *); + gchar *escaped = soup_uri_encode (val, NULL); + tmp2 = g_strdup_printf ("%s%c%s=%s", tmp, + separator, name, escaped); + g_free (escaped); + break; + } + default: + g_warning ("Invalid param type %s", g_type_name (type)); + return NULL; + } + + g_free (tmp); + tmp = tmp2; + + if (separator == '?') + separator = '&'; + + name = va_arg (ap, gchar *); + } + va_end (ap); + + uri = tmp; + + /* For some reason, webkit won't accept URL with username, but + * without password (mail://store@host/folder/mail), so we + * will replace the '@' symbol by '/' to get URL like + * mail://store/host/folder/mail which is OK + */ + tmp = strchr (tmp, '@'); + if (tmp) { + tmp[0] = '/'; + } + + return uri; +} + +void +em_format_redraw (EMFormat *emf) +{ + g_return_if_fail (EM_IS_FORMAT (emf)); + + g_signal_emit (emf, signals[REDRAW_REQUESTED], 0); +} + +/**************************************************************************/ +EMFormatPURI * +em_format_puri_new (EMFormat *emf, + gsize puri_size, + CamelMimePart *part, + const gchar *uri) +{ + EMFormatPURI *puri; + + g_return_val_if_fail (EM_IS_FORMAT (emf), NULL); + g_return_val_if_fail (puri_size >= sizeof (EMFormatPURI), NULL); + + puri = (EMFormatPURI *) g_malloc0 (puri_size); + puri->emf = emf; + + if (part) + puri->part = g_object_ref (part); + + if (uri) + puri->uri = g_strdup (uri); + + return puri; +} + +void +em_format_puri_free (EMFormatPURI *puri) +{ + g_return_if_fail (puri); + + if (puri->part) + g_object_unref (puri->part); + + if (puri->uri) + g_free (puri->uri); + + if (puri->cid) + g_free (puri->cid); + + if (puri->mime_type) + g_free (puri->mime_type); + + if (puri->validity) + camel_cipher_validity_free (puri->validity); + + if (puri->validity_parent) + camel_cipher_validity_free (puri->validity_parent); + + if (puri->free) + puri->free (puri); + + g_free (puri); +} + +void +em_format_puri_write (EMFormatPURI *puri, + CamelStream *stream, + EMFormatWriterInfo *info, + GCancellable *cancellable) +{ + g_return_if_fail (puri); + g_return_if_fail (CAMEL_IS_STREAM (stream)); + + if (info->mode == EM_FORMAT_WRITE_MODE_SOURCE) { + const EMFormatHandler *handler; + handler = em_format_find_handler (puri->emf, "x-evolution/message/source"); + handler->write_func (puri->emf, puri, stream, info, cancellable); + return; + } + + if (puri->write_func) { + puri->write_func (puri->emf, puri, stream, info, cancellable); + } else { + const EMFormatHandler *handler; + const gchar *mime_type; + + if (puri->mime_type) { + mime_type = puri->mime_type; + } else { + mime_type = (gchar *) "plain/text"; + } + + handler = em_format_find_handler (puri->emf, mime_type); + if (handler && handler->write_func) { + handler->write_func (puri->emf, + puri, stream, info, cancellable); + } + } +} + +EMFormatHeader * +em_format_header_new (const gchar *name, + const gchar *value) +{ + EMFormatHeader *header; + + g_return_val_if_fail (name && *name, NULL); + + header = g_new0 (EMFormatHeader, 1); + header->name = g_strdup (name); + if (value && *value) + header->value = g_strdup (value); + + return header; +} + +void +em_format_header_free (EMFormatHeader * header) +{ + g_return_if_fail (header != NULL); + + if (header->name) { + g_free (header->name); + header->name = NULL; + } + + if (header->value) { + g_free (header->value); + header->value = NULL; + } + + g_free (header); } diff --git a/em-format/em-format.h b/em-format/em-format.h index cf214d81a5..712b41bce1 100644 --- a/em-format/em-format.h +++ b/em-format/em-format.h @@ -21,14 +21,11 @@ * */ -/* - Abstract class for formatting mime messages -*/ - #ifndef EM_FORMAT_H #define EM_FORMAT_H #include <camel/camel.h> +#include <gtk/gtk.h> /* Standard GObject macros */ #define EM_TYPE_FORMAT \ @@ -51,194 +48,139 @@ G_BEGIN_DECLS +#define EM_FORMAT_HEADER_BOLD (1<<0) +#define EM_FORMAT_HEADER_LAST (1<<4) /* reserve 4 slots */ + +#define EM_FORMAT_VALIDITY_FOUND_PGP (1<<0) +#define EM_FORMAT_VALIDITY_FOUND_SMIME (1<<1) +#define EM_FORMAT_VALIDITY_FOUND_SIGNED (1<<2) +#define EM_FORMAT_VALIDITY_FOUND_ENCRYPTED (1<<3) + typedef struct _EMFormat EMFormat; typedef struct _EMFormatClass EMFormatClass; typedef struct _EMFormatPrivate EMFormatPrivate; -typedef struct _EMFormatHandler EMFormatHandler; +typedef struct _EMFormatPURI EMFormatPURI; typedef struct _EMFormatHeader EMFormatHeader; +typedef struct _EMFormatHandler EMFormatHandler; +typedef struct _EMFormatParserInfo EMFormatParserInfo; +typedef struct _EMFormatWriterInfo EMFormatWriterInfo; +typedef struct _WebKitDOMElement WebKitDOMElement; -typedef void (*EMFormatFunc) (EMFormat *emf, +typedef void (*EMFormatParseFunc) (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable); +typedef void (*EMFormatWriteFunc) (EMFormat *emf, + EMFormatPURI *puri, CamelStream *stream, - CamelMimePart *mime_part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback); - -typedef enum { - EM_FORMAT_MODE_NORMAL, - EM_FORMAT_MODE_ALLHEADERS, - EM_FORMAT_MODE_SOURCE -} EMFormatMode; + EMFormatWriterInfo *info, + GCancellable *cancellable); +typedef GtkWidget * (*EMFormatWidgetFunc) (EMFormat *emf, + EMFormatPURI *puri, + GCancellable *cancellable); +typedef void (*EMailDisplayBindFunc) (WebKitDOMElement *root, + EMFormatPURI *puri); -/** - * EMFormatHandlerFlags - Format handler flags. - * - * @EM_FORMAT_HANDLER_INLINE: This type should be shown expanded - * inline by default. - * @EM_FORMAT_HANDLER_INLINE_DISPOSITION: This type should always be - * shown inline, despite what the Content-Disposition suggests. - * - **/ typedef enum { EM_FORMAT_HANDLER_INLINE = 1 << 0, - EM_FORMAT_HANDLER_INLINE_DISPOSITION = 1 << 1 + EM_FORMAT_HANDLER_INLINE_DISPOSITION = 1 << 1, + EM_FORMAT_HANDLER_COMPOUND_TYPE = 1 << 2 } EMFormatHandlerFlags; -/** - * struct _EMFormatHandler - MIME type handler. - * - * @mime_type: Type this handler handles. - * @handler: The handler callback. - * @flags: Handler flags - * @old: The last handler set on this type. Allows overrides to - * fallback to previous implementation. - * - **/ +typedef enum { + EM_FORMAT_WRITE_MODE_NORMAL= 1 << 0, + EM_FORMAT_WRITE_MODE_ALL_HEADERS = 1 << 1, + EM_FORMAT_WRITE_MODE_SOURCE = 1 << 2, + EM_FORMAT_WRITE_MODE_PRINTING = 1 << 3, + EM_FORMAT_WRITE_MODE_RAW = 1 << 4 +} EMFormatWriteMode; + struct _EMFormatHandler { gchar *mime_type; - EMFormatFunc handler; + EMFormatParseFunc parse_func; + EMFormatWriteFunc write_func; EMFormatHandlerFlags flags; EMFormatHandler *old; }; -typedef struct _EMFormatPURI EMFormatPURI; -typedef void (*EMFormatPURIFunc) (EMFormat *emf, - CamelStream *stream, - EMFormatPURI *puri, - GCancellable *cancellable); - /** - * struct _EMFormatPURI - Pending URI object. - * - * @free: May be set by allocator and will be called when no longer needed. - * @format: - * @uri: Calculated URI of the part, if the part has one in its - * Content-Location field. - * @cid: The RFC2046 Content-Id of the part. If none is present, a unique value - * is calculated from @part_id. - * @part_id: A unique identifier for each part. - * @func: Callback for when the URI is requested. The callback writes - * its data to the supplied stream. - * @part: - * @use_count: - * - * This is used for multipart/related, and other formatters which may - * need to include a reference to out-of-band data in the content - * stream. - * - * This object may be subclassed as a struct. - **/ -struct _EMFormatPURI { - void (*free)(EMFormatPURI *p); /* optional callback for freeing user-fields */ - EMFormat *format; + * Use this struct to pass additional information between + * EMFormatParseFunc's. + * Much cleaner then setting public property of EMFormat. + */ +struct _EMFormatParserInfo { + const EMFormatHandler *handler; - gchar *uri; /* will be the location of the part, may be empty */ - gchar *cid; /* will always be set, a fake one created if needed */ - gchar *part_id; /* will always be set, emf->part_id->str for this part */ + /* EM_FORMAT_VALIDITY_* flags */ + guint32 validity_type; + CamelCipherValidity *validity; - EMFormatPURIFunc func; - CamelMimePart *part; + gint is_attachment : 1; + gint force_handler: 1; +}; - guint use_count; /* used by multipart/related to see if it was accessed */ +struct _EMFormatWriterInfo { + EMFormatWriteMode mode; + gboolean headers_collapsable; + gboolean headers_collapsed; }; struct _EMFormatHeader { guint32 flags; /* E_FORMAT_HEADER_ * */ - gchar name[1]; + gchar *name; + gchar *value; }; #define EM_FORMAT_HEADER_BOLD (1<<0) #define EM_FORMAT_HEADER_LAST (1<<4) /* reserve 4 slots */ -#define EM_FORMAT_VALIDITY_FOUND_PGP (1<<0) -#define EM_FORMAT_VALIDITY_FOUND_SMIME (1<<1) -#define EM_FORMAT_VALIDITY_FOUND_SIGNED (1<<2) -#define EM_FORMAT_VALIDITY_FOUND_ENCRYPTED (1<<3) +struct _EMFormatPURI { + CamelMimePart *part; + + EMFormat *emf; + EMFormatWriteFunc write_func; + EMFormatWidgetFunc widget_func; + + /** + * Called by #EMailDisplay whenever document/frame is reloaded. + * Modules and plugins can create bindings to events of DOM + * objects they created. + */ + EMailDisplayBindFunc bind_func; + + gchar *uri; + gchar *cid; + gchar *mime_type; + + /* EM_FORMAT_VALIDITY_* flags */ + guint32 validity_type; + CamelCipherValidity *validity; + CamelCipherValidity *validity_parent; + + gboolean is_attachment; + + void (*free)(EMFormatPURI *puri); /* optional callback for freeing user-fields */ +}; -/** - * struct _EMFormat - Mail formatter object. - * - * @parent: - * @priv: - * @message: - * @folder: - * @uid: - * @part_id: - * @header_list: - * @session: - * @base url: - * @snoop_mime_type: - * @valid: - * @valid_parent: - * @inline_table: - * @pending_uri_table: - * @pending_uri_tree: - * @pending_uri_level: - * @mode: - * @charset: - * @default_charset: - * - * Most fields are private or read-only. - * - * This is the base MIME formatter class. It provides no formatting - * itself, but drives most of the basic types, including multipart / * types. - **/ struct _EMFormat { GObject parent; EMFormatPrivate *priv; - /* The current message */ CamelMimeMessage *message; - CamelFolder *folder; - gchar *uid; + gchar *message_uid; + gchar *uri_base; - /* Current part ID prefix for identifying parts directly. */ - GString *part_id; - /* part_id of the currently processing message - * (when the message has message-attachments) */ - gchar *current_message_part_id; + /* Defines order in which parts should be displayed */ + GList *mail_part_list; + /* For quick search for parts by their URI/ID */ + GHashTable *mail_part_table; /* If empty, then all. */ GQueue header_list; - - /* Used for authentication when required. */ - CamelSession *session; - - /* Content-Base header or absolute Content-Location, for any part. */ - CamelURL *base; - - /* If we snooped an application/octet-stream, what we snooped. */ - const gchar *snoop_mime_type; - - /* For validity enveloping. */ - CamelCipherValidity *valid; - CamelCipherValidity *valid_parent; - - /* For checking whether we found any signed or encrypted parts. */ - guint32 validity_found; - - /* For forcing inlining. */ - GHashTable *inline_table; - - /* Global URI lookup table for message. */ - GHashTable *pending_uri_table; - - /* This structure is used internally to form a visibility tree of - * parts in the current formatting stream. This is to implement the - * part resolution rules for RFC2387 to implement multipart/related. */ - GNode *pending_uri_tree; - - /* The current level to search from. */ - GNode *pending_uri_level; - - EMFormatMode mode; /* source/headers/etc */ - gchar *charset; /* charset override */ - gchar *default_charset; /* charset fallback */ - gboolean composer; /* formatting from composer? */ - gboolean print; /* formatting for printing? */ }; struct _EMFormatClass { @@ -246,187 +188,161 @@ struct _EMFormatClass { GHashTable *type_handlers; - /* lookup handler, default falls back to hashtable above */ - const EMFormatHandler * - (*find_handler) (EMFormat *emf, - const gchar *mime_type); - - /* start formatting a message */ - void (*format_clone) (EMFormat *emf, - CamelFolder *folder, - const gchar *uid, - CamelMimeMessage *message, - EMFormat *source, - GCancellable *cancellable); - - /* some internel error/inconsistency */ - void (*format_error) (EMFormat *emf, - CamelStream *stream, - const gchar *errmsg); - - /* use for external structured parts */ - void (*format_attachment) (EMFormat *emf, - CamelStream *stream, - CamelMimePart *mime_part, - const gchar *mime_type, - const EMFormatHandler *info, - GCancellable *cancellable); + gboolean (*is_inline) (EMFormat *emf, + const gchar *part_id, + CamelMimePart *part, + const EMFormatHandler *handler); - /* use for unparsable content */ - void (*format_source) (EMFormat *emf, - CamelStream *stream, - CamelMimePart *mime_part, - GCancellable *cancellable); - /* for outputing secure(d) content */ - void (*format_secure) (EMFormat *emf, - CamelStream *stream, - CamelMimePart *mime_part, - CamelCipherValidity *validity, - GCancellable *cancellable); + /* Write the entire message to stream */ + void (*write) (EMFormat *emf, + CamelStream *stream, + EMFormatWriterInfo *info, + GCancellable *cancellable); - /* returns true if the formatter is still busy with pending stuff */ - gboolean (*busy) (EMFormat *); - - /* Shows optional way to open messages */ - void (*format_optional) (EMFormat *emf, - CamelStream *filter_stream, - CamelMimePart *mime_part, - CamelStream *mem_stream, - GCancellable *cancellable); - - gboolean (*is_inline) (EMFormat *emf, - const gchar *part_id, - CamelMimePart *mime_part, - const EMFormatHandler *handle); + void (*preparse) (EMFormat *emf); /* signals */ - /* complete, alternative to polling busy, for asynchronous work */ - void (*complete) (EMFormat *emf); -}; + void (*redraw_requested) (EMFormat *emf); -void em_format_set_mode (EMFormat *emf, - EMFormatMode mode); -void em_format_set_charset (EMFormat *emf, - const gchar *charset); -void em_format_set_default_charset (EMFormat *emf, - const gchar *charset); - -/* also indicates to show all headers */ -void em_format_clear_headers (EMFormat *emf); - -void em_format_default_headers (EMFormat *emf); -void em_format_add_header (EMFormat *emf, - const gchar *name, - guint32 flags); - -/* FIXME: Need a 'clone' api to copy details about the current view (inlines etc) - * Or maybe it should live with sub-classes? */ - -gint em_format_is_attachment (EMFormat *emf, - CamelMimePart *part); - -gboolean em_format_is_inline (EMFormat *emf, - const gchar *part_id, - CamelMimePart *mime_part, - const EMFormatHandler *handle); -void em_format_set_inline (EMFormat *emf, - const gchar *partid, - gint state); - -gchar * em_format_describe_part (CamelMimePart *part, - const gchar *mime_type); - -/* for implementers */ -GType em_format_get_type (void); - -void em_format_class_add_handler (EMFormatClass *emfc, - EMFormatHandler *info); -void em_format_class_remove_handler (EMFormatClass *emfc, - EMFormatHandler *info); -const EMFormatHandler * - em_format_find_handler (EMFormat *emf, - const gchar *mime_type); -const EMFormatHandler * - em_format_fallback_handler (EMFormat *emf, - const gchar *mime_type); - -/* puri is short for pending uri ... really */ -EMFormatPURI * em_format_add_puri (EMFormat *emf, - gsize size, - const gchar *uri, - CamelMimePart *part, - EMFormatPURIFunc func); -EMFormatPURI * em_format_find_visible_puri (EMFormat *emf, - const gchar *uri); -EMFormatPURI * em_format_find_puri (EMFormat *emf, - const gchar *uri); -void em_format_clear_puri_tree (EMFormat *emf); -void em_format_push_level (EMFormat *emf); -void em_format_pull_level (EMFormat *emf); - -/* clones inline state/view and format, or use to redraw */ -void em_format_format_clone (EMFormat *emf, - CamelFolder *folder, - const gchar *uid, - CamelMimeMessage *message, - EMFormat *source, - GCancellable *cancellable); - -/* formats a new message */ -void em_format_format (EMFormat *emf, - CamelFolder *folder, - const gchar *uid, - CamelMimeMessage *message, - GCancellable *cancellable); -void em_format_queue_redraw (EMFormat *emf); -void em_format_format_attachment (EMFormat *emf, - CamelStream *stream, - CamelMimePart *mime_part, - const gchar *mime_type, - const EMFormatHandler *info, - GCancellable *cancellable); -void em_format_format_error (EMFormat *emf, - CamelStream *stream, - const gchar *format, - ...) G_GNUC_PRINTF (3, 4); -void em_format_format_secure (EMFormat *emf, - CamelStream *stream, - CamelMimePart *mime_part, - CamelCipherValidity *valid, - GCancellable *cancellable); -void em_format_format_source (EMFormat *emf, - CamelStream *stream, - CamelMimePart *mime_part, - GCancellable *cancellable); - -gboolean em_format_busy (EMFormat *emf); - -/* raw content only */ -void em_format_format_content (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - GCancellable *cancellable); - -/* raw content text parts - should this just be checked/done by above? */ -void em_format_format_text (EMFormat *emf, - CamelStream *stream, - CamelDataWrapper *part, - GCancellable *cancellable); - -void em_format_part_as (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const gchar *mime_type, - GCancellable *cancellable); -void em_format_part (EMFormat *emf, - CamelStream *stream, - CamelMimePart *mime_part, - GCancellable *cancellable); -void em_format_merge_handler (EMFormat *new, - EMFormat *old); - -const gchar * em_format_snoop_type (CamelMimePart *part); +}; -G_END_DECLS +EMFormat * em_format_new (void); + +GType em_format_get_type (void); + +void em_format_set_charset (EMFormat *emf, + const gchar *charset); +const gchar * em_format_get_charset (EMFormat *emf); + +void em_format_set_default_charset (EMFormat *emf, + const gchar *charset); +const gchar * em_format_get_default_charset (EMFormat *emf); + +void em_format_set_composer (EMFormat *emf, + gboolean composer); +gboolean em_format_get_composer (EMFormat *emf); + +void em_format_set_base_url (EMFormat *emf, + CamelURL *url); +void em_format_set_base_url_string (EMFormat *emf, + const gchar *url_string); +CamelURL * em_format_get_base_url (EMFormat *emf); + +void em_format_clear_headers (EMFormat *emf); + +void em_format_default_headers (EMFormat *emf); + +void em_format_add_header (EMFormat *emf, + const gchar *name, + const gchar *value, + guint32 flags); +void em_format_add_header_struct (EMFormat *emf, + EMFormatHeader *header); +void em_format_remove_header (EMFormat *emf, + const gchar *name, + const gchar *value); +void em_format_remove_header_struct (EMFormat *emf, + const EMFormatHeader *header); + +void em_format_add_puri (EMFormat *emf, + EMFormatPURI *puri); +EMFormatPURI * em_format_find_puri (EMFormat *emf, + const gchar *id); + +void em_format_class_add_handler (EMFormatClass *emfc, + EMFormatHandler *handler); +void em_format_class_remove_handler (EMFormatClass *emfc, + EMFormatHandler *handler); + +const EMFormatHandler * em_format_find_handler (EMFormat *emf, + const gchar *mime_type); +const EMFormatHandler * em_format_fallback_handler (EMFormat *emf, + const gchar *mime_type); + +void em_format_parse (EMFormat *emf, + CamelMimeMessage *message, + CamelFolder *folder, + GCancellable *cancellable); + +void em_format_write (EMFormat *emf, + CamelStream *stream, + EMFormatWriterInfo *info, + GCancellable *cancellable); + +void em_format_parse_async (EMFormat *emf, + CamelMimeMessage *message, + CamelFolder *folder, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +void em_format_parse_part (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable); +void em_format_parse_part_as (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + const gchar *mime_type, + GCancellable *cancellable); +gboolean em_format_is_inline (EMFormat *emf, + const gchar *part_id, + CamelMimePart *part, + const EMFormatHandler *handler); + +gchar * em_format_get_error_id (EMFormat *emf); + +void em_format_format_error (EMFormat *emf, + const gchar *format, + ...) G_GNUC_PRINTF (2, 3); +void em_format_format_text (EMFormat *emf, + CamelStream *stream, + CamelDataWrapper *dw, + GCancellable *cancellable); +gchar * em_format_describe_part (CamelMimePart *part, + const gchar *mime_type); +gint em_format_is_attachment (EMFormat *emf, + CamelMimePart *part); +const gchar * em_format_snoop_type (CamelMimePart *part); + +gchar * em_format_build_mail_uri (CamelFolder *folder, + const gchar *message_uid, + const gchar *part_uid, + ...) G_GNUC_NULL_TERMINATED; + +/* EMFormatParseFunc that does nothing. Use it to disable + * parsing of a specific mime type parts */ +void em_format_empty_parser (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable); + +/* EMFormatWriteFunc that does nothing. Use it to disable + * writing of a specific mime type parts */ +void em_format_empty_writer (EMFormat *emf, + EMFormatPURI *puri, + CamelStream *stream, + EMFormatWriterInfo *info, + GCancellable *cancellable); + +void em_format_redraw (EMFormat *emf); + +EMFormatPURI * em_format_puri_new (EMFormat *emf, + gsize puri_size, + CamelMimePart *part, + const gchar *uri); +void em_format_puri_free (EMFormatPURI *puri); + +void em_format_puri_write (EMFormatPURI *puri, + CamelStream *stream, + EMFormatWriterInfo *info, + GCancellable *cancellable); + +EMFormatHeader * em_format_header_new (const gchar *name, + const gchar *value); +void em_format_header_free (EMFormatHeader *header); #endif /* EM_FORMAT_H */ diff --git a/mail/Makefile.am b/mail/Makefile.am index 3a13284668..e6eccc83fe 100644 --- a/mail/Makefile.am +++ b/mail/Makefile.am @@ -22,7 +22,9 @@ libevolution_mail_la_CPPFLAGS = \ $(CERT_UI_CFLAGS) \ $(CANBERRA_CFLAGS) \ $(CLUTTER_CFLAGS) \ - $(GTKHTML_CFLAGS) \ + $(GTKHTML_CFLAGS) \ + $(JAVASCRIPTCORE_CFLAGS) \ + $(LIBSOUP_CFLAGS) \ -DEVOLUTION_DATADIR=\""$(datadir)"\" \ -DEVOLUTION_PRIVDATADIR=\""$(privdatadir)"\" \ -DEVOLUTION_ETSPECDIR=\""$(etspecdir)"\" \ @@ -56,11 +58,13 @@ mailinclude_HEADERS = \ e-mail-migrate.h \ e-mail-notebook-view.h \ e-mail-paned-view.h \ + e-mail-printer.h \ e-mail-reader-utils.h \ e-mail-reader.h \ - e-mail-ui-session.h \ + e-mail-request.h \ e-mail-sidebar.h \ e-mail-tag-editor.h \ + e-mail-ui-session.h \ e-mail-view.h \ em-account-editor.h \ em-composer-utils.h \ @@ -81,7 +85,6 @@ mailinclude_HEADERS = \ em-format-html-display.h \ em-format-html-print.h \ em-format-html.h \ - em-html-stream.h \ em-search-context.h \ em-subscription-editor.h \ em-sync-stream.h \ @@ -121,11 +124,13 @@ libevolution_mail_la_SOURCES = \ e-mail-migrate.c \ e-mail-notebook-view.c \ e-mail-paned-view.c \ + e-mail-printer.c \ e-mail-reader-utils.c \ e-mail-reader.c \ - e-mail-ui-session.c \ + e-mail-request.c \ e-mail-sidebar.c \ e-mail-tag-editor.c \ + e-mail-ui-session.c \ e-mail-view.c \ em-account-editor.c \ em-composer-utils.c \ @@ -146,7 +151,6 @@ libevolution_mail_la_SOURCES = \ em-format-html-display.c \ em-format-html-print.c \ em-format-html.c \ - em-html-stream.c \ em-search-context.c \ em-subscription-editor.c \ em-sync-stream.c \ @@ -194,7 +198,11 @@ libevolution_mail_la_LIBADD = \ $(CANBERRA_LIBS) \ $(CLUTTER_LIBS) \ $(GTKHTML_LIBS) \ - $(SMIME_LIBS) + $(JAVASCRIPTCORE_CFLAGS) \ + $(E_WIDGETS_LIBS) \ + $(SMIME_LIBS) \ + $(LIBSOUP_LIBS) \ + $(GNOME_PLATFORM_LIBS) libevolution_mail_la_LDFLAGS = -avoid-version $(NO_UNDEFINED) diff --git a/mail/e-mail-attachment-bar.c b/mail/e-mail-attachment-bar.c index 21a298c56d..7572c664cb 100644 --- a/mail/e-mail-attachment-bar.c +++ b/mail/e-mail-attachment-bar.c @@ -60,7 +60,8 @@ enum { PROP_ACTIVE_VIEW, PROP_DRAGGING, PROP_EDITABLE, - PROP_EXPANDED + PROP_EXPANDED, + PROP_STORE }; /* Forward Declarations */ @@ -78,7 +79,6 @@ G_DEFINE_TYPE_WITH_CODE ( static void mail_attachment_bar_update_status (EMailAttachmentBar *bar) { - EAttachmentView *view; EAttachmentStore *store; GtkActivatable *activatable; GtkAction *action; @@ -88,8 +88,7 @@ mail_attachment_bar_update_status (EMailAttachmentBar *bar) gchar *display_size; gchar *markup; - view = E_ATTACHMENT_VIEW (bar); - store = e_attachment_view_get_store (view); + store = E_ATTACHMENT_STORE (bar->priv->model); label = GTK_LABEL (bar->priv->status_label); num_attachments = e_attachment_store_get_num_attachments (store); @@ -120,6 +119,31 @@ mail_attachment_bar_update_status (EMailAttachmentBar *bar) } static void +mail_attachment_bar_set_store (EMailAttachmentBar *bar, + EAttachmentStore *store) +{ + g_return_if_fail (E_IS_ATTACHMENT_STORE (store)); + + bar->priv->model = g_object_ref (store); + + gtk_icon_view_set_model (GTK_ICON_VIEW (bar->priv->icon_view), + bar->priv->model); + gtk_tree_view_set_model (GTK_TREE_VIEW (bar->priv->tree_view), + bar->priv->model); + + g_signal_connect_swapped ( + bar->priv->model, "notify::num-attachments", + G_CALLBACK (mail_attachment_bar_update_status), bar); + + g_signal_connect_swapped ( + bar->priv->model, "notify::total-size", + G_CALLBACK (mail_attachment_bar_update_status), bar); + + /* Initialize */ + mail_attachment_bar_update_status (bar); +} + +static void mail_attachment_bar_set_property (GObject *object, guint property_id, const GValue *value, @@ -127,7 +151,7 @@ mail_attachment_bar_set_property (GObject *object, { switch (property_id) { case PROP_ACTIVE_VIEW: - e_mail_attachment_bar_set_active_view ( + e_mail_attachment_bar_set_active_view ( E_MAIL_ATTACHMENT_BAR (object), g_value_get_int (value)); return; @@ -149,6 +173,11 @@ mail_attachment_bar_set_property (GObject *object, E_MAIL_ATTACHMENT_BAR (object), g_value_get_boolean (value)); return; + case PROP_STORE: + mail_attachment_bar_set_store ( + E_MAIL_ATTACHMENT_BAR (object), + g_value_get_object (value)); + return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); @@ -188,6 +217,11 @@ mail_attachment_bar_get_property (GObject *object, e_mail_attachment_bar_get_expanded ( E_MAIL_ATTACHMENT_BAR (object))); return; + case PROP_STORE: + g_value_set_object ( + value, + e_mail_attachment_bar_get_store ( + E_MAIL_ATTACHMENT_BAR (object))); } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); @@ -201,8 +235,6 @@ mail_attachment_bar_dispose (GObject *object) priv = E_MAIL_ATTACHMENT_BAR_GET_PRIVATE (object); if (priv->model != NULL) { - e_attachment_store_remove_all ( - E_ATTACHMENT_STORE (priv->model)); g_object_unref (priv->model); priv->model = NULL; } @@ -347,17 +379,6 @@ mail_attachment_bar_get_private (EAttachmentView *view) return e_attachment_view_get_private (view); } -static EAttachmentStore * -mail_attachment_bar_get_store (EAttachmentView *view) -{ - EMailAttachmentBar *bar; - - bar = E_MAIL_ATTACHMENT_BAR (view); - view = E_ATTACHMENT_VIEW (bar->priv->icon_view); - - return e_attachment_view_get_store (view); -} - static GtkTreePath * mail_attachment_bar_get_path_at_pos (EAttachmentView *view, gint x, @@ -488,6 +509,17 @@ e_mail_attachment_bar_class_init (EMailAttachmentBarClass *class) G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + g_object_class_install_property ( + object_class, + PROP_STORE, + g_param_spec_object ( + "store", + "Attachment Store", + NULL, + E_TYPE_ATTACHMENT_STORE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + g_object_class_override_property ( object_class, PROP_DRAGGING, "dragging"); @@ -499,7 +531,7 @@ static void e_mail_attachment_bar_interface_init (EAttachmentViewInterface *interface) { interface->get_private = mail_attachment_bar_get_private; - interface->get_store = mail_attachment_bar_get_store; + interface->get_store = e_mail_attachment_bar_get_store; interface->get_path_at_pos = mail_attachment_bar_get_path_at_pos; interface->get_selected_paths = mail_attachment_bar_get_selected_paths; interface->path_is_selected = mail_attachment_bar_path_is_selected; @@ -520,7 +552,6 @@ e_mail_attachment_bar_init (EMailAttachmentBar *bar) GtkAction *action; bar->priv = E_MAIL_ATTACHMENT_BAR_GET_PRIVATE (bar); - bar->priv->model = e_attachment_store_new (); gtk_box_set_spacing (GTK_BOX (bar), 6); @@ -644,23 +675,18 @@ e_mail_attachment_bar_init (EMailAttachmentBar *bar) bar->priv->status_label = g_object_ref (widget); gtk_widget_show (widget); - g_signal_connect_swapped ( - bar->priv->model, "notify::num-attachments", - G_CALLBACK (mail_attachment_bar_update_status), bar); - - g_signal_connect_swapped ( - bar->priv->model, "notify::total-size", - G_CALLBACK (mail_attachment_bar_update_status), bar); - g_object_unref (size_group); } GtkWidget * -e_mail_attachment_bar_new (void) +e_mail_attachment_bar_new (EAttachmentStore *store) { + g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), NULL); + return g_object_new ( E_TYPE_MAIL_ATTACHMENT_BAR, - "editable", FALSE, NULL); + "editable", FALSE, + "store", store, NULL); } gint @@ -729,3 +755,11 @@ e_mail_attachment_bar_set_expanded (EMailAttachmentBar *bar, g_object_notify (G_OBJECT (bar), "expanded"); } + +EAttachmentStore * +e_mail_attachment_bar_get_store (EMailAttachmentBar *bar) +{ + g_return_val_if_fail (E_IS_MAIL_ATTACHMENT_BAR (bar), NULL); + + return E_ATTACHMENT_STORE (bar->priv->model); +} diff --git a/mail/e-mail-attachment-bar.h b/mail/e-mail-attachment-bar.h index 93c1b89261..b83d9733e0 100644 --- a/mail/e-mail-attachment-bar.h +++ b/mail/e-mail-attachment-bar.h @@ -60,7 +60,7 @@ struct _EMailAttachmentBarClass { }; GType e_mail_attachment_bar_get_type (void); -GtkWidget * e_mail_attachment_bar_new (void); +GtkWidget * e_mail_attachment_bar_new (EAttachmentStore *store); gint e_mail_attachment_bar_get_active_view (EMailAttachmentBar *bar); void e_mail_attachment_bar_set_active_view @@ -71,6 +71,8 @@ gboolean e_mail_attachment_bar_get_expanded void e_mail_attachment_bar_set_expanded (EMailAttachmentBar *bar, gboolean expanded); +EAttachmentStore * + e_mail_attachment_bar_get_store (EMailAttachmentBar *bar); G_END_DECLS diff --git a/mail/e-mail-browser.c b/mail/e-mail-browser.c index 0dbb3d01e3..806980d602 100644 --- a/mail/e-mail-browser.c +++ b/mail/e-mail-browser.c @@ -55,7 +55,8 @@ struct _EMailBrowserPrivate { EMailBackend *backend; GtkUIManager *ui_manager; EFocusTracker *focus_tracker; - EMFormatHTMLDisplay *formatter; + + EMFormatWriteMode mode; GtkWidget *main_menu; GtkWidget *main_toolbar; @@ -74,7 +75,8 @@ enum { PROP_GROUP_BY_THREADS, PROP_SHOW_DELETED, PROP_REPLY_STYLE, - PROP_UI_MANAGER + PROP_UI_MANAGER, + PROP_DISPLAY_MODE, }; static gpointer parent_class; @@ -260,11 +262,10 @@ static void mail_browser_message_selected_cb (EMailBrowser *browser, const gchar *uid) { - EMFormatHTML *formatter; CamelMessageInfo *info; CamelFolder *folder; EMailReader *reader; - EWebView *web_view; + EMailDisplay *display; const gchar *title; guint32 state; @@ -276,8 +277,7 @@ mail_browser_message_selected_cb (EMailBrowser *browser, return; folder = e_mail_reader_get_folder (reader); - formatter = e_mail_reader_get_formatter (reader); - web_view = em_format_html_get_web_view (formatter); + display = e_mail_reader_get_mail_display (reader); info = camel_folder_get_message_info (folder, uid); @@ -289,7 +289,7 @@ mail_browser_message_selected_cb (EMailBrowser *browser, title = _("(No Subject)"); gtk_window_set_title (GTK_WINDOW (browser), title); - gtk_widget_grab_focus (GTK_WIDGET (web_view)); + gtk_widget_grab_focus (GTK_WIDGET (display)); camel_message_info_set_flags ( info, CAMEL_MESSAGE_SEEN, CAMEL_MESSAGE_SEEN); @@ -319,7 +319,6 @@ mail_browser_popup_event_cb (EMailBrowser *browser, GdkEventButton *event, const gchar *uri) { - EMFormatHTML *formatter; EMailReader *reader; EWebView *web_view; GtkMenu *menu; @@ -329,8 +328,7 @@ mail_browser_popup_event_cb (EMailBrowser *browser, return FALSE; reader = E_MAIL_READER (browser); - formatter = e_mail_reader_get_formatter (reader); - web_view = em_format_html_get_web_view (formatter); + web_view = E_WEB_VIEW (e_mail_reader_get_mail_display (reader)); if (e_web_view_get_cursor_image (web_view) != NULL) return FALSE; @@ -415,6 +413,11 @@ mail_browser_set_property (GObject *object, E_MAIL_BROWSER (object), g_value_get_boolean (value)); return; + + case PROP_DISPLAY_MODE: + E_MAIL_BROWSER (object)->priv->mode = + g_value_get_int (value); + return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); @@ -468,6 +471,11 @@ mail_browser_get_property (GObject *object, value, e_mail_browser_get_ui_manager ( E_MAIL_BROWSER (object))); return; + + case PROP_DISPLAY_MODE: + g_value_set_int ( + value, E_MAIL_BROWSER (object)->priv->mode); + return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); @@ -495,11 +503,6 @@ mail_browser_dispose (GObject *object) priv->focus_tracker = NULL; } - if (priv->formatter != NULL) { - g_object_unref (priv->formatter); - priv->formatter = NULL; - } - if (priv->main_menu != NULL) { g_object_unref (priv->main_menu); priv->main_menu = NULL; @@ -534,14 +537,13 @@ static void mail_browser_constructed (GObject *object) { EMailBrowser *browser; - EMFormatHTML *formatter; EMailReader *reader; EMailBackend *backend; EMailSession *session; + EMailDisplay *display; EShellBackend *shell_backend; EShell *shell; EFocusTracker *focus_tracker; - ESearchBar *search_bar; GSettings *settings; GtkAccelGroup *accel_group; GtkActionGroup *action_group; @@ -549,7 +551,6 @@ mail_browser_constructed (GObject *object) GtkUIManager *ui_manager; GtkWidget *container; GtkWidget *widget; - EWebView *web_view; const gchar *domain; const gchar *id; guint merge_id; @@ -558,7 +559,6 @@ mail_browser_constructed (GObject *object) G_OBJECT_CLASS (parent_class)->constructed (object); browser = E_MAIL_BROWSER (object); - reader = E_MAIL_READER (object); backend = e_mail_reader_get_backend (reader); session = e_mail_backend_get_session (backend); @@ -575,9 +575,6 @@ mail_browser_constructed (GObject *object) gtk_application_add_window ( GTK_APPLICATION (shell), GTK_WINDOW (object)); - formatter = e_mail_reader_get_formatter (reader); - web_view = em_format_html_get_web_view (formatter); - /* The message list is a widget, but it is not shown in the browser. * Unfortunately, the widget is inseparable from its model, and the * model is all we need. */ @@ -592,15 +589,20 @@ mail_browser_constructed (GObject *object) browser->priv->message_list, "message-list-built", G_CALLBACK (mail_browser_message_list_built_cb), object); + display = g_object_new (E_TYPE_MAIL_DISPLAY, + "mode", E_MAIL_BROWSER (object)->priv->mode, NULL); + g_signal_connect_swapped ( - web_view, "popup-event", + display, "popup-event", G_CALLBACK (mail_browser_popup_event_cb), object); g_signal_connect_swapped ( - web_view, "status-message", + display, "status-message", G_CALLBACK (mail_browser_status_message_cb), object); - /* Add action groups before initializing the reader interface. */ + widget = e_preview_pane_new (E_WEB_VIEW (display)); + browser->priv->preview_pane = g_object_ref (widget); + gtk_widget_show (widget); action_group = gtk_action_group_new (ACTION_GROUP_STANDARD); gtk_action_group_set_translation_domain (action_group, domain); @@ -613,6 +615,7 @@ mail_browser_constructed (GObject *object) gtk_ui_manager_insert_action_group (ui_manager, action_group, 0); /* For easy access. Takes ownership of the reference. */ + g_object_set_data_full ( object, ACTION_GROUP_STANDARD, action_group, (GDestroyNotify) g_object_unref); @@ -664,7 +667,6 @@ mail_browser_constructed (GObject *object) container = widget; - /* Create the status bar before connecting proxy widgets. */ widget = gtk_statusbar_new (); gtk_box_pack_end (GTK_BOX (container), widget, FALSE, FALSE, 0); browser->priv->statusbar = g_object_ref (widget); @@ -682,21 +684,9 @@ mail_browser_constructed (GObject *object) gtk_style_context_add_class ( gtk_widget_get_style_context (widget), - GTK_STYLE_CLASS_PRIMARY_TOOLBAR); - - gtk_widget_show (GTK_WIDGET (web_view)); + GTK_STYLE_CLASS_PRIMARY_TOOLBAR); - widget = e_preview_pane_new (web_view); - gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0); - browser->priv->preview_pane = g_object_ref (widget); - gtk_widget_show (widget); - - search_bar = e_preview_pane_get_search_bar (E_PREVIEW_PANE (widget)); - - g_signal_connect_swapped ( - search_bar, "changed", - G_CALLBACK (em_format_queue_redraw), - browser->priv->formatter); + gtk_container_add (GTK_CONTAINER (container), browser->priv->preview_pane); /* Bind GObject properties to GSettings keys. */ @@ -713,8 +703,6 @@ mail_browser_constructed (GObject *object) e_plugin_ui_register_manager (ui_manager, id, object); e_plugin_ui_enable_manager (ui_manager, id); - e_mail_reader_connect_headers (E_MAIL_READER (reader)); - e_extensible_load_extensions (E_EXTENSIBLE (object)); } @@ -772,14 +760,15 @@ mail_browser_get_hide_deleted (EMailReader *reader) return !e_mail_browser_get_show_deleted (browser); } -static EMFormatHTML * -mail_browser_get_formatter (EMailReader *reader) +static EMailDisplay * +mail_browser_get_mail_display (EMailReader *reader) { - EMailBrowser *browser; + EMailBrowserPrivate *priv; - browser = E_MAIL_BROWSER (reader); + priv = E_MAIL_BROWSER_GET_PRIVATE (E_MAIL_BROWSER (reader)); - return EM_FORMAT_HTML (browser->priv->formatter); + return E_MAIL_DISPLAY (e_preview_pane_get_web_view ( + E_PREVIEW_PANE (priv->preview_pane))); } static GtkWidget * @@ -916,6 +905,18 @@ e_mail_browser_class_init (EMailBrowserClass *class) "Show deleted messages", FALSE, G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_DISPLAY_MODE, + g_param_spec_int ( + "display-mode", + "Display Mode", + NULL, + 0, + G_MAXINT, + EM_FORMAT_WRITE_MODE_NORMAL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); } static void @@ -923,7 +924,7 @@ e_mail_browser_reader_init (EMailReaderInterface *interface) { interface->get_action_group = mail_browser_get_action_group; interface->get_backend = mail_browser_get_backend; - interface->get_formatter = mail_browser_get_formatter; + interface->get_mail_display = mail_browser_get_mail_display; interface->get_hide_deleted = mail_browser_get_hide_deleted; interface->get_message_list = mail_browser_get_message_list; interface->get_popup_menu = mail_browser_get_popup_menu; @@ -936,7 +937,6 @@ static void e_mail_browser_init (EMailBrowser *browser) { browser->priv = E_MAIL_BROWSER_GET_PRIVATE (browser); - browser->priv->formatter = em_format_html_display_new (); gtk_window_set_title (GTK_WINDOW (browser), _("Evolution")); gtk_window_set_default_size (GTK_WINDOW (browser), 600, 400); @@ -948,13 +948,22 @@ e_mail_browser_init (EMailBrowser *browser) } GtkWidget * -e_mail_browser_new (EMailBackend *backend) +e_mail_browser_new (EMailBackend *backend, + CamelFolder *folder, + const gchar *msg_uid, + EMFormatWriteMode mode) { + GtkWidget *widget; + g_return_val_if_fail (E_IS_MAIL_BACKEND (backend), NULL); - return g_object_new ( + widget= g_object_new ( E_TYPE_MAIL_BROWSER, - "backend", backend, NULL); + "backend", backend, + "display-mode", mode, + NULL); + + return widget; } void diff --git a/mail/e-mail-browser.h b/mail/e-mail-browser.h index c09c85b1c8..88f8174a9d 100644 --- a/mail/e-mail-browser.h +++ b/mail/e-mail-browser.h @@ -24,6 +24,7 @@ #include <mail/e-mail-backend.h> #include <misc/e-focus-tracker.h> +#include <mail/e-mail-display.h> /* Standard GObject macros */ #define E_TYPE_MAIL_BROWSER \ @@ -60,7 +61,10 @@ struct _EMailBrowserClass { }; GType e_mail_browser_get_type (void); -GtkWidget * e_mail_browser_new (EMailBackend *backend); +GtkWidget * e_mail_browser_new (EMailBackend *backend, + CamelFolder *folder, + const gchar *message_uid, + EMFormatWriteMode mode); void e_mail_browser_close (EMailBrowser *browser); gboolean e_mail_browser_get_show_deleted (EMailBrowser *browser); void e_mail_browser_set_show_deleted (EMailBrowser *browser, diff --git a/mail/e-mail-display.c b/mail/e-mail-display.c index 7461e595f3..07f45ad461 100644 --- a/mail/e-mail-display.c +++ b/mail/e-mail-display.c @@ -23,14 +23,33 @@ #include <config.h> #endif +#define LIBSOUP_USE_UNSTABLE_REQUEST_API + #include "e-mail-display.h" #include <glib/gi18n.h> +#include <gdk/gdk.h> +#include "e-util/e-marshal.h" #include "e-util/e-util.h" #include "e-util/e-plugin-ui.h" #include "mail/em-composer-utils.h" #include "mail/em-utils.h" +#include "mail/e-mail-request.h" +#include "mail/em-format-html-display.h" +#include "mail/e-mail-attachment-bar.h" +#include "widgets/misc/e-attachment-button.h" + +#include <camel/camel.h> + +#include <libsoup/soup.h> +#include <libsoup/soup-requester.h> + +#include <JavaScriptCore/JavaScript.h> + +#define d(x) + +G_DEFINE_TYPE (EMailDisplay, e_mail_display, E_TYPE_WEB_VIEW) #define E_MAIL_DISPLAY_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ @@ -38,13 +57,29 @@ struct _EMailDisplayPrivate { EMFormatHTML *formatter; + + EMFormatWriteMode mode; + gboolean headers_collapsable; + gboolean headers_collapsed; + + GtkActionGroup *mailto_actions; + GtkActionGroup *images_actions; + + gint force_image_load: 1; }; enum { PROP_0, - PROP_FORMATTER + PROP_FORMATTER, + PROP_MODE, + PROP_HEADERS_COLLAPSABLE, + PROP_HEADERS_COLLAPSED, }; +static gpointer parent_class; + +static CamelDataCache *emd_global_http_cache = 0; + static const gchar *ui = "<ui>" " <popup name='context'>" @@ -61,6 +96,15 @@ static const gchar *ui = " </popup>" "</ui>"; +static const gchar *image_ui = +"<ui>" +" <popup name='context'>" +" <placeholder name='custom-actions-2'>" +" <menuitem action='image-save'/>" +" </placeholder>" +" </popup>" +"</ui>"; + static GtkActionEntry mailto_entries[] = { { "add-to-address-book", @@ -88,7 +132,7 @@ static GtkActionEntry mailto_entries[] = { NULL, N_("Send _Reply To..."), NULL, - N_("Send a reply message to this address"), + N_("Send a reply message to this address"), NULL /* Handled by EMailReader */ }, /*** Menus ***/ @@ -101,7 +145,54 @@ static GtkActionEntry mailto_entries[] = { NULL } }; -G_DEFINE_TYPE (EMailDisplay, e_mail_display, E_TYPE_WEB_VIEW) +static GtkActionEntry image_entries[] = { + + { "image-save", + GTK_STOCK_SAVE, + N_("Save _Image..."), + NULL, + N_("Save the image to a file"), + NULL /* Handled by EMailReader */ }, + +}; + +static void +mail_display_webview_update_actions (EWebView *web_view, + gpointer user_data) +{ + const gchar *image_src; + gboolean visible; + GtkAction *action; + + g_return_if_fail (web_view != NULL); + + image_src = e_web_view_get_cursor_image_src (web_view); + visible = image_src && g_str_has_prefix (image_src, "cid:"); + if (!visible && image_src) { + CamelStream *image_stream; + + image_stream = camel_data_cache_get (emd_global_http_cache, "http", image_src, NULL); + + visible = image_stream != NULL; + + if (image_stream) + g_object_unref (image_stream); + } + + action = e_web_view_get_action (web_view, "image-save"); + if (action) + gtk_action_set_visible (action, visible); +} + +static void +formatter_image_loading_policy_changed_cb (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + EMailDisplay *display = user_data; + + e_mail_display_load_images (display); +} static void mail_display_update_formatter_colors (EMailDisplay *display) @@ -115,6 +206,9 @@ mail_display_update_formatter_colors (EMailDisplay *display) state = gtk_widget_get_state (GTK_WIDGET (display)); formatter = display->priv->formatter; + if (!display->priv->formatter) + return; + style = gtk_widget_get_style (GTK_WIDGET (display)); if (style == NULL) return; @@ -156,6 +250,21 @@ mail_display_set_property (GObject *object, E_MAIL_DISPLAY (object), g_value_get_object (value)); return; + case PROP_MODE: + e_mail_display_set_mode ( + E_MAIL_DISPLAY (object), + g_value_get_int (value)); + return; + case PROP_HEADERS_COLLAPSABLE: + e_mail_display_set_headers_collapsable ( + E_MAIL_DISPLAY (object), + g_value_get_boolean (value)); + return; + case PROP_HEADERS_COLLAPSED: + e_mail_display_set_headers_collapsed ( + E_MAIL_DISPLAY (object), + g_value_get_boolean (value)); + return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); @@ -173,6 +282,21 @@ mail_display_get_property (GObject *object, value, e_mail_display_get_formatter ( E_MAIL_DISPLAY (object))); return; + case PROP_MODE: + g_value_set_int ( + value, e_mail_display_get_mode ( + E_MAIL_DISPLAY (object))); + return; + case PROP_HEADERS_COLLAPSABLE: + g_value_set_boolean ( + value, e_mail_display_get_headers_collapsable ( + E_MAIL_DISPLAY (object))); + return; + case PROP_HEADERS_COLLAPSED: + g_value_set_boolean ( + value, e_mail_display_get_headers_collapsed ( + E_MAIL_DISPLAY (object))); + return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); @@ -191,14 +315,14 @@ mail_display_dispose (GObject *object) } /* Chain up to parent's dispose() method. */ - G_OBJECT_CLASS (e_mail_display_parent_class)->dispose (object); + G_OBJECT_CLASS (parent_class)->dispose (object); } static void mail_display_realize (GtkWidget *widget) { /* Chain up to parent's realize() method. */ - GTK_WIDGET_CLASS (e_mail_display_parent_class)->realize (widget); + GTK_WIDGET_CLASS (parent_class)->realize (widget); mail_display_update_formatter_colors (E_MAIL_DISPLAY (widget)); } @@ -207,62 +331,28 @@ static void mail_display_style_set (GtkWidget *widget, GtkStyle *previous_style) { - EMailDisplayPrivate *priv; + EMailDisplay *display = E_MAIL_DISPLAY (widget); - priv = E_MAIL_DISPLAY_GET_PRIVATE (widget); + mail_display_update_formatter_colors (display); /* Chain up to parent's style_set() method. */ - GTK_WIDGET_CLASS (e_mail_display_parent_class)-> - style_set (widget, previous_style); - - mail_display_update_formatter_colors (E_MAIL_DISPLAY (widget)); - em_format_queue_redraw (EM_FORMAT (priv->formatter)); -} - -static void -mail_display_load_string (EWebView *web_view, - const gchar *string) -{ - EMailDisplayPrivate *priv; - - priv = E_MAIL_DISPLAY_GET_PRIVATE (web_view); - g_return_if_fail (priv->formatter != NULL); - - if (em_format_busy (EM_FORMAT (priv->formatter))) - return; - - /* Chain up to parent's load_string() method. */ - E_WEB_VIEW_CLASS (e_mail_display_parent_class)-> - load_string (web_view, string); -} - -static void -mail_display_url_requested (GtkHTML *html, - const gchar *uri, - GtkHTMLStream *stream) -{ - /* XXX Sadly, we must block the default method - * until EMFormatHTML is made asynchronous. */ + GTK_WIDGET_CLASS (parent_class)->style_set (widget, previous_style); } static gboolean mail_display_process_mailto (EWebView *web_view, - const gchar *mailto_uri) + const gchar *mailto_uri, + gpointer user_data) { - g_return_val_if_fail (web_view != NULL, FALSE); + g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE); g_return_val_if_fail (mailto_uri != NULL, FALSE); - g_return_val_if_fail (E_IS_MAIL_DISPLAY (web_view), FALSE); if (g_ascii_strncasecmp (mailto_uri, "mailto:", 7) == 0) { - EMailDisplayPrivate *priv; EMFormat *format; CamelFolder *folder = NULL; EShell *shell; - priv = E_MAIL_DISPLAY_GET_PRIVATE (web_view); - g_return_val_if_fail (priv->formatter != NULL, FALSE); - - format = EM_FORMAT (priv->formatter); + format = (EMFormat *) E_MAIL_DISPLAY (web_view)->priv->formatter; if (format != NULL && format->folder != NULL) folder = format->folder; @@ -277,79 +367,825 @@ mail_display_process_mailto (EWebView *web_view, return FALSE; } -static void -mail_display_link_clicked (GtkHTML *html, - const gchar *uri) +static gboolean +mail_display_link_clicked (WebKitWebView *web_view, + WebKitWebFrame *frame, + WebKitNetworkRequest *request, + WebKitWebNavigationAction *navigation_action, + WebKitWebPolicyDecision *policy_decision, + gpointer user_data) { - EMailDisplayPrivate *priv; + EMailDisplay *display; + const gchar *uri = webkit_network_request_get_uri (request); - priv = E_MAIL_DISPLAY_GET_PRIVATE (html); - g_return_if_fail (priv->formatter != NULL); - - if (g_str_has_prefix (uri, "##")) { - guint32 flags; - - flags = priv->formatter->header_wrap_flags; - - if (strcmp (uri, "##TO##") == 0) { - if (!(flags & EM_FORMAT_HTML_HEADER_TO)) - flags |= EM_FORMAT_HTML_HEADER_TO; - else - flags &= ~EM_FORMAT_HTML_HEADER_TO; - } else if (strcmp (uri, "##CC##") == 0) { - if (!(flags & EM_FORMAT_HTML_HEADER_CC)) - flags |= EM_FORMAT_HTML_HEADER_CC; - else - flags &= ~EM_FORMAT_HTML_HEADER_CC; - } else if (strcmp (uri, "##BCC##") == 0) { - if (!(flags & EM_FORMAT_HTML_HEADER_BCC)) - flags |= EM_FORMAT_HTML_HEADER_BCC; - else - flags &= ~EM_FORMAT_HTML_HEADER_BCC; - } else if (strcmp (uri, "##HEADERS##") == 0) { - EMFormatHTMLHeadersState state; - - state = em_format_html_get_headers_state ( - priv->formatter); - - if (state == EM_FORMAT_HTML_HEADERS_STATE_COLLAPSED) - state = EM_FORMAT_HTML_HEADERS_STATE_EXPANDED; - else - state = EM_FORMAT_HTML_HEADERS_STATE_COLLAPSED; - - em_format_html_set_headers_state ( - priv->formatter, state); - } + display = E_MAIL_DISPLAY (web_view); + if (display->priv->formatter == NULL) + return FALSE; - priv->formatter->header_wrap_flags = flags; - em_format_queue_redraw (EM_FORMAT (priv->formatter)); - - } else if (mail_display_process_mailto (E_WEB_VIEW (html), uri)) { + if (mail_display_process_mailto (E_WEB_VIEW (web_view), uri, NULL)) { /* do nothing, function handled the "mailto:" uri already */ - } else if (*uri == '#') - gtk_html_jump_to_anchor (html, uri + 1); + webkit_web_policy_decision_ignore (policy_decision); + return TRUE; - else if (g_ascii_strncasecmp (uri, "thismessage:", 12) == 0) + } else if (g_ascii_strncasecmp (uri, "thismessage:", 12) == 0) { /* ignore */ ; + webkit_web_policy_decision_ignore (policy_decision); + return TRUE; - else if (g_ascii_strncasecmp (uri, "cid:", 4) == 0) + } else if (g_ascii_strncasecmp (uri, "cid:", 4) == 0) { /* ignore */ ; + webkit_web_policy_decision_ignore (policy_decision); + return TRUE; + + } + + /* Let webkit handle it */ + return FALSE; +} + +static void +webkit_request_load_from_file (WebKitNetworkRequest *request, + const gchar *path) +{ + gchar *data = NULL; + gsize length = 0; + gboolean status; + gchar *b64, *new_uri; + gchar *ct; + + status = g_file_get_contents (path, &data, &length, NULL); + if (!status) + return; + + b64 = g_base64_encode ((guchar *) data, length); + ct = g_content_type_guess (path, NULL, 0, NULL); + + new_uri = g_strdup_printf ("data:%s;base64,%s", ct, b64); + webkit_network_request_set_uri (request, new_uri); + + g_free (b64); + g_free (new_uri); + g_free (ct); + g_free (data); +} + +static void +mail_display_resource_requested (WebKitWebView *web_view, + WebKitWebFrame *frame, + WebKitWebResource *resource, + WebKitNetworkRequest *request, + WebKitNetworkResponse *response, + gpointer user_data) +{ + EMailDisplay *display = E_MAIL_DISPLAY (web_view); + EMFormat *formatter = EM_FORMAT (display->priv->formatter); + const gchar *uri = webkit_network_request_get_uri (request); + + if (!formatter) { + webkit_network_request_set_uri (request, "invalid://uri"); + return; + } + + /* Redirect cid:part_id to mail://mail_id/cid:part_id */ + if (g_str_has_prefix (uri, "cid:")) { + + /* Always write raw content of CID object */ + gchar *new_uri = em_format_build_mail_uri (formatter->folder, + formatter->message_uid, + "part_id", G_TYPE_STRING, uri, + "mode", G_TYPE_INT, EM_FORMAT_WRITE_MODE_RAW, NULL); + + webkit_network_request_set_uri (request, new_uri); + + g_free (new_uri); + + /* WebKit won't allow to load a local file when displaing "remote" mail://, + protocol, so we need to handle this manually */ + } else if (g_str_has_prefix (uri, "file:")) { + gchar *path; + + path = g_filename_from_uri (uri, NULL, NULL); + if (!path) + return; + + webkit_request_load_from_file (request, path); + + g_free (path); + + /* Redirect http(s) request to evo-http(s) protocol. See EMailRequest for + * further details about this. */ + } else if (g_str_has_prefix (uri, "http:") || g_str_has_prefix (uri, "https")) { + + gchar *new_uri, *mail_uri, *enc; + SoupURI *soup_uri; + GHashTable *query; + gchar *uri_md5; + CamelStream *stream; + + /* Open Evolution's cache */ + uri_md5 = g_compute_checksum_for_string (G_CHECKSUM_MD5, uri, -1); + stream = camel_data_cache_get ( + emd_global_http_cache, "http", uri_md5, NULL); + g_free (uri_md5); + + /* If the URI is not cached and we are not allowed to load it + * then redirect to invalid URI, so that webkit would display + * a native placeholder for it. */ + if (!stream && !display->priv->force_image_load && + !em_format_html_can_load_images (display->priv->formatter)) { + webkit_network_request_set_uri (request, "invalid://protocol"); + return; + } + + new_uri = g_strconcat ("evo-", uri, NULL); + mail_uri = em_format_build_mail_uri (formatter->folder, + formatter->message_uid, NULL, NULL); + + soup_uri = soup_uri_new (new_uri); + if (soup_uri->query) { + query = soup_form_decode (soup_uri->query); + } else { + query = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, g_free); + } + enc = soup_uri_encode (mail_uri, NULL); + g_hash_table_insert (query, g_strdup ("__evo-mail"), enc); + + if (display->priv->force_image_load) { + g_hash_table_insert (query, + g_strdup ("__evo-load-images"), + g_strdup ("true")); + } + + g_free (mail_uri); + + soup_uri_set_query_from_form (soup_uri, query); + g_free (new_uri); + + new_uri = soup_uri_to_string (soup_uri, FALSE); + webkit_network_request_set_uri (request, new_uri); + + g_free (new_uri); + soup_uri_free (soup_uri); + g_hash_table_unref (query); + } +} + +static WebKitDOMElement * +find_element_by_id (WebKitDOMDocument *document, + const gchar *id) +{ + WebKitDOMNodeList *frames; + WebKitDOMElement *element; + gulong i, length; + + /* Try to look up the element in this DOM document */ + element = webkit_dom_document_get_element_by_id (document, id); + if (element) + return element; + + /* If the element is not here then recursively scan all frames */ + frames = webkit_dom_document_get_elements_by_tag_name(document, "iframe"); + length = webkit_dom_node_list_get_length (frames); + for (i = 0; i < length; i++) { + + WebKitDOMHTMLIFrameElement *iframe = + WEBKIT_DOM_HTML_IFRAME_ELEMENT ( + webkit_dom_node_list_item (frames, i)); + + WebKitDOMDocument *frame_doc = + webkit_dom_html_iframe_element_get_content_document (iframe); + + WebKitDOMElement *el = + find_element_by_id (frame_doc, id); + + if (el) + return el; + } + + return NULL; +} + +static void +mail_display_plugin_widget_resize (GObject *object, + gpointer dummy, + EMailDisplay *display) +{ + GtkWidget *widget; + WebKitDOMElement *parent_element; + gchar *dim; + gint height; + + widget = GTK_WIDGET (object); + gtk_widget_get_preferred_height (widget, &height, NULL); + parent_element = g_object_get_data (object, "parent_element"); + + if (!parent_element || !WEBKIT_DOM_IS_ELEMENT (parent_element)) { + d(printf("%s: %s does not have (valid) parent element!\n", + G_STRFUNC, (gchar *) g_object_get_data (object, "uri"))); + return; + } + + /* Int -> Str */ + dim = g_strdup_printf ("%d", height); + + /* Set height of the containment <object> to match height of the + * GtkWidget it contains */ + webkit_dom_html_object_element_set_height ( + WEBKIT_DOM_HTML_OBJECT_ELEMENT (parent_element), dim); + g_free (dim); +} + +static void +mail_display_plugin_widget_realize_cb (GtkWidget *widget, + gpointer user_data) +{ + /* Initial resize of the <object> element when the widget + * is displayed for the first time. */ + mail_display_plugin_widget_resize (G_OBJECT (widget), NULL, user_data); +} + +static void +plugin_widget_set_parent_element (GtkWidget *widget, + EMailDisplay *display) +{ + const gchar *uri; + WebKitDOMDocument *document; + WebKitDOMElement *element; + + uri = g_object_get_data (G_OBJECT (widget), "uri"); + if (!uri || !*uri) + return; + + document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (display)); + element = find_element_by_id (document, uri); + + if (!element || !WEBKIT_DOM_IS_ELEMENT (element)) { + g_warning ("Failed to find parent <object> for '%s' - no ID set?", uri); + return; + } + + /* Assign the WebKitDOMElement to "parent_element" data of the GtkWidget + * and the GtkWidget to "widget" data of the DOM Element */ + g_object_set_data (G_OBJECT (widget), "parent_element", element); + g_object_set_data (G_OBJECT (element), "widget", widget); +} + +static void +attachment_button_expanded (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + EAttachmentButton *button = E_ATTACHMENT_BUTTON (object); + WebKitDOMElement *attachment = user_data; + WebKitDOMCSSStyleDeclaration *css; + gboolean expanded; + + d(printf("Attachment button %s (%p) expansion state toggled!\n", + (gchar *) g_object_get_data (object, "uri"), object)); + + expanded = e_attachment_button_get_expanded (button) && + gtk_widget_get_visible (GTK_WIDGET (button)); + + if (!WEBKIT_DOM_IS_ELEMENT (attachment)) { + d(printf("%s: Parent element for button %s does not exist!\n", + G_STRFUNC, (gchar *) g_object_get_data (object, "uri"))); + return; + } + + /* Show or hide the DIV which contains the attachment (iframe, image...) */ + css = webkit_dom_element_get_style (attachment); + webkit_dom_css_style_declaration_set_property ( + css, "display", expanded ? "block" : "none", "", NULL); +} + +static void +constraint_widget_visibility (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + GtkWidget *widget = GTK_WIDGET (object); + EAttachmentButton *button = user_data; + + gboolean can_show = e_attachment_button_get_expanded (button); + gboolean is_visible = gtk_widget_get_visible (widget); + + if (is_visible && !can_show) + gtk_widget_hide (widget); + else if (!is_visible && can_show) + gtk_widget_show (widget); + + /* Otherwise it's OK */ +} + +static void +bind_iframe_content_visibility (EAttachmentButton *button, + WebKitDOMElement *iframe) +{ + WebKitDOMDocument *document; + WebKitDOMNodeList *nodes; + gulong i, length; + + if (!WEBKIT_DOM_IS_HTML_IFRAME_ELEMENT (iframe)) + return; + + document = webkit_dom_html_iframe_element_get_content_document ( + WEBKIT_DOM_HTML_IFRAME_ELEMENT (iframe)); + nodes = webkit_dom_document_get_elements_by_tag_name (document, "object"); + length = webkit_dom_node_list_get_length (nodes); - else { - /* Chain up to parent's link_clicked() method. */ - GTK_HTML_CLASS (e_mail_display_parent_class)-> - link_clicked (html, uri); + d(printf("Found %ld objects within iframe %s\n", length, + webkit_dom_html_iframe_element_get_name ( + WEBKIT_DOM_HTML_IFRAME_ELEMENT (iframe)))); + + /* Iterate through all <object>s and bind visibility of their widget + * with expanded-state of related attachment button */ + for (i = 0; i < length; i++) { + + WebKitDOMNode *node = webkit_dom_node_list_item (nodes, i); + GtkWidget *widget; + + widget = g_object_get_data (G_OBJECT (node), "widget"); + if (!widget) + continue; + + d(printf("Binding visibility of widget %s (%p) with button %s (%p)\n", + (gchar *) g_object_get_data (G_OBJECT (widget), "uri"), widget, + (gchar *) g_object_get_data (G_OBJECT (button), "uri"), button)); + + g_object_bind_property ( + button, "expanded", + widget, "visible", + G_BINDING_SYNC_CREATE); + + /* Ensure that someone won't attempt to _show() the widget when + * it is supposed to be hidden and vice versa. */ + g_signal_connect (widget, "notify::visible", + G_CALLBACK (constraint_widget_visibility), button); } } static void +bind_attachment_iframe_visibility (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + WebKitWebFrame *webframe; + const gchar *frame_name; + gchar *button_uri; + WebKitDOMDocument *document; + WebKitDOMElement *attachment; + WebKitDOMElement *button_element; + WebKitDOMNodeList *nodes; + gulong i, length; + GtkWidget *button; + + /* Whenever an <iframe> is loaded, bind visibility of all GtkWidgets + * the document within the <iframe> contains with "expanded" property + * of the EAttachmentButton */ + + webframe = WEBKIT_WEB_FRAME (object); + if (webkit_web_frame_get_load_status (webframe) != WEBKIT_LOAD_FINISHED) + return; + + frame_name = webkit_web_frame_get_name (webframe); + + d(printf("Rebinding visibility of frame %s because it's URL changed\n", + frame_name)); + + /* Get DOMDocument of the main document */ + document = webkit_web_view_get_dom_document ( + webkit_web_frame_get_web_view (webframe)); + if (!document) + return; + + /* Find the <DIV> containing the <iframe> and related EAttachmentButton + * within the DOM */ + attachment = find_element_by_id (document, frame_name); + if (!attachment) + return; + + button_uri = g_strconcat (frame_name, ".attachment_button", NULL); + button_element = find_element_by_id (document, button_uri); + g_free (button_uri); + if (!button_element) + return; + + button = g_object_get_data (G_OBJECT (button_element), "widget"); + + /* Get <iframe> representing the attachment content */ + nodes = webkit_dom_element_get_elements_by_tag_name (attachment, "iframe"); + length = webkit_dom_node_list_get_length (nodes); + for (i = 0; i < length; i++) { + + WebKitDOMNode *node = + webkit_dom_node_list_item (nodes, i); + + if (!WEBKIT_DOM_IS_HTML_IFRAME_ELEMENT (node)) + continue; + + /* Bind visibility of all GtkWidget within the + * iframe with "expanded" property of the button */ + bind_iframe_content_visibility ( + E_ATTACHMENT_BUTTON (button), + WEBKIT_DOM_ELEMENT (node)); + } +} + +static GtkWidget * +mail_display_plugin_widget_requested (WebKitWebView *web_view, + gchar *mime_type, + gchar *uri, + GHashTable *param, + gpointer user_data) +{ + EMFormat *emf; + EMailDisplay *display; + EMFormatPURI *puri; + GtkWidget *widget; + gchar *puri_uri; + + puri_uri = g_hash_table_lookup (param, "data"); + if (!puri_uri || !g_str_has_prefix (uri, "mail://")) + return NULL; + + display = E_MAIL_DISPLAY (web_view); + emf = (EMFormat *) display->priv->formatter; + + puri = em_format_find_puri (emf, puri_uri); + if (!puri) { + return NULL; + } + + if (puri->widget_func) + widget = puri->widget_func (emf, puri, NULL); + else + widget = NULL; + + if (!widget) + return NULL; + + if (E_IS_ATTACHMENT_BUTTON (widget)) { + /* Attachment button has URI different then the actual PURI because + * that URI identifies the attachment itself */ + gchar *button_uri = g_strconcat (puri_uri, ".attachment_button", NULL); + g_object_set_data_full (G_OBJECT (widget), "uri", + button_uri, (GDestroyNotify) g_free); + } else { + g_object_set_data_full (G_OBJECT (widget), "uri", + g_strdup (puri_uri), (GDestroyNotify) g_free); + } + + /* Set widget's <object> container as GObject data "parent_element" */ + plugin_widget_set_parent_element (widget, display); + + /* Resizing a GtkWidget requires changing size of parent + * <object> HTML element in DOM. */ + g_signal_connect (widget, "realize", + G_CALLBACK (mail_display_plugin_widget_realize_cb), display); + g_signal_connect (widget, "size-allocate", + G_CALLBACK (mail_display_plugin_widget_resize), display); + + /* Embed the attachment bar into the GtkBox before we do anything + * further with the widget. */ + if (E_IS_MAIL_ATTACHMENT_BAR (widget)) { + + /* When EMailAttachmentBar is expanded/collapsed it does not + * emit size-allocate signal despite it changes it's height. */ + GtkWidget *box = NULL; + + /* Only when packed in box, EMailAttachmentBar reports correct + * height */ + box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_box_pack_start (GTK_BOX (box), widget, FALSE, FALSE, 0); + + g_signal_connect (widget, "notify::expanded", + G_CALLBACK (mail_display_plugin_widget_resize), display); + g_signal_connect (widget, "notify::active-view", + G_CALLBACK (mail_display_plugin_widget_resize), display); + + /* Show the EAttachmentBar but not the containing layout */ + gtk_widget_show (widget); + + widget = box; + + } else if (E_IS_ATTACHMENT_BUTTON (widget)) { + + /* Bind visibility of DOM element containing related + * attachment with 'expanded' property of this + * attachment button. */ + WebKitDOMElement *attachment; + WebKitDOMDocument *document; + + document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (display)); + attachment = find_element_by_id (document, puri_uri); + if (!attachment) { + e_attachment_button_set_expandable ( + E_ATTACHMENT_BUTTON (widget), FALSE); + } else { + const CamelContentDisposition *disposition; + WebKitDOMNodeList *nodes; + gulong i, length; + + /* Show/hide the attachment when the EAttachmentButton + * is expanded/collapsed or shown/hidden */ + g_signal_connect_data (widget, "notify::expanded", + G_CALLBACK (attachment_button_expanded), + g_object_ref (attachment), (GClosureNotify) g_object_unref, 0); + g_signal_connect_data (widget, "notify::visible", + G_CALLBACK (attachment_button_expanded), + g_object_ref (attachment), (GClosureNotify) g_object_unref, 0); + /* Initial synchronization */ + attachment_button_expanded (G_OBJECT (widget), + NULL, attachment); + + /* Find all <iframes> within the attachment and bind + * it's visiblity to expanded state of the attachment btn */ + nodes = webkit_dom_element_get_elements_by_tag_name ( + attachment, "iframe"); + length = webkit_dom_node_list_get_length (nodes); + for (i = 0; i < length; i++) { + + WebKitDOMNode *node = + webkit_dom_node_list_item (nodes, i); + + if (!WEBKIT_DOM_IS_HTML_IFRAME_ELEMENT (node)) + continue; + + bind_iframe_content_visibility ( + E_ATTACHMENT_BUTTON (widget), + WEBKIT_DOM_ELEMENT (node)); + } + + /* Expand inlined attachments */ + disposition = + camel_mime_part_get_content_disposition (puri->part); + if (disposition && + g_ascii_strncasecmp ( + disposition->disposition, "inline", 6) == 0) { + + e_attachment_button_set_expanded ( + E_ATTACHMENT_BUTTON (widget), TRUE); + } + } + } + + d(printf("Created widget %s (%p)\n", puri_uri, widget)); + return widget; +} + +static void +toggle_headers_visibility (WebKitDOMElement *button, + WebKitDOMEvent *event, + WebKitWebView *web_view) +{ + WebKitDOMDocument *document; + WebKitDOMElement *short_headers, *full_headers; + WebKitDOMCSSStyleDeclaration *css_short, *css_full; + gboolean expanded; + const gchar *path; + + document = webkit_web_view_get_dom_document (web_view); + + short_headers = webkit_dom_document_get_element_by_id ( + document, "__evo-short-headers"); + if (!short_headers) + return; + + css_short = webkit_dom_element_get_style (short_headers); + + full_headers = webkit_dom_document_get_element_by_id ( + document, "__evo-full-headers"); + if (!full_headers) + return; + + css_full = webkit_dom_element_get_style (full_headers); + + expanded = (g_strcmp0 (webkit_dom_css_style_declaration_get_property_value ( + css_full, "display"), "block") == 0); + + webkit_dom_css_style_declaration_set_property (css_full, "display", + expanded ? "none" : "block", "", NULL); + webkit_dom_css_style_declaration_set_property (css_short, "display", + expanded ? "block" : "none", "", NULL); + + if (expanded) + path = "evo-file://" EVOLUTION_IMAGESDIR "/plus.png"; + else + path = "evo-file://" EVOLUTION_IMAGESDIR "/minus.png"; + + webkit_dom_html_image_element_set_src ( + WEBKIT_DOM_HTML_IMAGE_ELEMENT (button), path); + + e_mail_display_set_headers_collapsed (E_MAIL_DISPLAY (web_view), expanded); + + d(printf("Headers %s!\n", expanded ? "collapsed" : "expanded")); +} + +static const gchar* addresses[] = { "to", "cc", "bcc" }; + +static void +toggle_address_visibility (WebKitDOMElement *button, + WebKitDOMEvent *event, + const gchar *address) +{ + WebKitDOMElement *full_addr, *ellipsis; + WebKitDOMCSSStyleDeclaration *css_full, *css_ellipsis; + WebKitDOMDocument *document; + gchar *id; + const gchar *path; + gboolean expanded; + + document = webkit_dom_node_get_owner_document (WEBKIT_DOM_NODE (button)); + + id = g_strconcat ("__evo-moreaddr-", address, NULL); + full_addr = webkit_dom_document_get_element_by_id (document, id); + g_free (id); + + if (!full_addr) + return; + + css_full = webkit_dom_element_get_style (full_addr); + + id = g_strconcat ("__evo-moreaddr-ellipsis-", address, NULL); + ellipsis = webkit_dom_document_get_element_by_id (document, id); + g_free (id); + + if (!ellipsis) + return; + + css_ellipsis = webkit_dom_element_get_style (ellipsis); + + expanded = (g_strcmp0 ( + webkit_dom_css_style_declaration_get_property_value ( + css_full, "display"), "inline") == 0); + + webkit_dom_css_style_declaration_set_property ( + css_full, "display", (expanded ? "none" : "inline"), "", NULL); + webkit_dom_css_style_declaration_set_property ( + css_ellipsis, "display", (expanded ? "inline" : "none"), "", NULL); + + if (expanded) { + path = "evo-file://" EVOLUTION_IMAGESDIR "/plus.png"; + } else { + path = "evo-file://" EVOLUTION_IMAGESDIR "/minus.png"; + } + + if (!WEBKIT_DOM_IS_HTML_IMAGE_ELEMENT (button)) { + id = g_strconcat ("__evo-moreaddr-img-", address, NULL); + button = webkit_dom_document_get_element_by_id (document, id); + g_free (id); + + if (!button) + return; + } + + webkit_dom_html_image_element_set_src ( + WEBKIT_DOM_HTML_IMAGE_ELEMENT (button), path); + +} + +static void +setup_DOM_bindings (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + WebKitWebView *web_view; + WebKitWebFrame *frame; + WebKitLoadStatus load_status; + WebKitDOMDocument *document; + WebKitDOMElement *button; + gint i = 0; + + frame = WEBKIT_WEB_FRAME (object); + load_status = webkit_web_frame_get_load_status (frame); + if (load_status != WEBKIT_LOAD_FINISHED) + return; + + web_view = webkit_web_frame_get_web_view (frame); + document = webkit_web_view_get_dom_document (web_view); + + button = webkit_dom_document_get_element_by_id ( + document, "__evo-collapse-headers-img"); + if (!button) + return; + + d(printf("Conntecting to __evo-collapsable-headers-img::click event\n")); + + webkit_dom_event_target_add_event_listener ( + WEBKIT_DOM_EVENT_TARGET (button), "click", + G_CALLBACK (toggle_headers_visibility), FALSE, web_view); + + for (i = 0; i < 3; i++) { + gchar *id; + id = g_strconcat ("__evo-moreaddr-img-", addresses[i], NULL); + button = webkit_dom_document_get_element_by_id (document, id); + g_free (id); + + if (!button) + continue; + + webkit_dom_event_target_add_event_listener ( + WEBKIT_DOM_EVENT_TARGET (button), "click", + G_CALLBACK (toggle_address_visibility), FALSE, + (gpointer) addresses[i]); + + id = g_strconcat ("__evo-moreaddr-ellipsis-", addresses[i], NULL); + button = webkit_dom_document_get_element_by_id (document, id); + g_free (id); + + if (!button) + continue; + + webkit_dom_event_target_add_event_listener ( + WEBKIT_DOM_EVENT_TARGET (button), "click", + G_CALLBACK (toggle_address_visibility), FALSE, + (gpointer) addresses[i]); + } +} + +static void +puri_bind_dom (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + WebKitWebFrame *frame; + WebKitLoadStatus load_status; + WebKitWebView *web_view; + WebKitDOMDocument *document; + EMailDisplay *display; + GList *iter; + EMFormat *emf; + const gchar *frame_puri; + + frame = WEBKIT_WEB_FRAME (object); + load_status = webkit_web_frame_get_load_status (frame); + + if (load_status != WEBKIT_LOAD_FINISHED) + return; + + frame_puri = webkit_web_frame_get_name (frame); + web_view = webkit_web_frame_get_web_view (frame); + display = E_MAIL_DISPLAY (web_view); + + emf = EM_FORMAT (display->priv->formatter); + if (!emf) + return; + + iter = g_hash_table_lookup ( + emf->mail_part_table, + webkit_web_frame_get_name (frame)); + + document = webkit_web_view_get_dom_document (web_view); + + while (iter) { + + EMFormatPURI *puri = iter->data; + + if (!puri) + continue; + + /* Iterate only the PURI rendered in the frame and all it's "subPURIs" */ + if (!g_str_has_prefix (puri->uri, frame_puri)) + break; + + if (puri->bind_func) { + WebKitDOMElement *el = find_element_by_id (document, puri->uri); + if (el) { + d(printf("bind_func for %s\n", puri->uri)); + puri->bind_func (el, puri); + } + } + + iter = iter->next; + } +} + +static void +mail_display_frame_created (WebKitWebView *web_view, + WebKitWebFrame *frame, + gpointer user_data) +{ + d(printf("Frame %s created!\n", webkit_web_frame_get_name (frame))); + + /* Re-bind visibility of this newly created <iframe> with + * related EAttachmentButton whenever content of this <iframe> is + * (re)loaded */ + g_signal_connect (frame, "notify::load-status", + G_CALLBACK (bind_attachment_iframe_visibility), NULL); + + /* Call bind_func of all PURIs written in this frame */ + g_signal_connect (frame, "notify::load-status", + G_CALLBACK (puri_bind_dom), NULL); +} + +static void e_mail_display_class_init (EMailDisplayClass *class) { GObjectClass *object_class; GtkWidgetClass *widget_class; - EWebViewClass *web_view_class; - GtkHTMLClass *html_class; + parent_class = g_type_class_peek_parent (class); g_type_class_add_private (class, sizeof (EMailDisplayPrivate)); object_class = G_OBJECT_CLASS (class); @@ -361,14 +1197,6 @@ e_mail_display_class_init (EMailDisplayClass *class) widget_class->realize = mail_display_realize; widget_class->style_set = mail_display_style_set; - web_view_class = E_WEB_VIEW_CLASS (class); - web_view_class->load_string = mail_display_load_string; - web_view_class->process_mailto = mail_display_process_mailto; - - html_class = GTK_HTML_CLASS (class); - html_class->url_requested = mail_display_url_requested; - html_class->link_clicked = mail_display_link_clicked; - g_object_class_install_property ( object_class, PROP_FORMATTER, @@ -378,38 +1206,122 @@ e_mail_display_class_init (EMailDisplayClass *class) NULL, EM_TYPE_FORMAT_HTML, G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_MODE, + g_param_spec_int ( + "mode", + "Display Mode", + NULL, + 0, + G_MAXINT, + EM_FORMAT_WRITE_MODE_NORMAL, + G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_HEADERS_COLLAPSABLE, + g_param_spec_boolean ( + "headers-collapsable", + "Headers Collapsable", + NULL, + FALSE, + G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_HEADERS_COLLAPSED, + g_param_spec_boolean ( + "headers-collapsed", + "Headers Collapsed", + NULL, + FALSE, + G_PARAM_READWRITE)); } static void e_mail_display_init (EMailDisplay *display) { - EWebView *web_view; GtkUIManager *ui_manager; - GtkActionGroup *action_group; GError *error = NULL; - - web_view = E_WEB_VIEW (display); + SoupSession *session; + SoupSessionFeature *feature; + const gchar *user_cache_dir; + WebKitWebSettings *settings; + WebKitWebFrame *main_frame; display->priv = E_MAIL_DISPLAY_GET_PRIVATE (display); - /* EWebView's action groups are added during its instance - * initialization function (like what we're in now), so it - * is safe to fetch them this early in construction. */ - action_group = e_web_view_get_action_group (web_view, "mailto"); - - /* We don't actually handle the actions we're adding. - * EMailReader handles them. How devious is that? */ - gtk_action_group_add_actions ( - action_group, mailto_entries, - G_N_ELEMENTS (mailto_entries), display); - - /* Because we are loading from a hard-coded string, there is - * no chance of I/O errors. Failure here implies a malformed - * UI definition. Full stop. */ - ui_manager = e_web_view_get_ui_manager (web_view); + display->priv->force_image_load = FALSE; + display->priv->mailto_actions = gtk_action_group_new ("mailto"); + gtk_action_group_add_actions (display->priv->mailto_actions, mailto_entries, + G_N_ELEMENTS (mailto_entries), NULL); + + display->priv->images_actions = gtk_action_group_new ("image"); + gtk_action_group_add_actions (display->priv->images_actions, image_entries, + G_N_ELEMENTS (image_entries), NULL); + + webkit_web_view_set_full_content_zoom (WEBKIT_WEB_VIEW (display), TRUE); + + settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (display)); + g_object_set (settings, "enable-frame-flattening", TRUE, NULL); + + g_signal_connect (display, "navigation-policy-decision-requested", + G_CALLBACK (mail_display_link_clicked), NULL); + g_signal_connect (display, "resource-request-starting", + G_CALLBACK (mail_display_resource_requested), NULL); + g_signal_connect (display, "process-mailto", + G_CALLBACK (mail_display_process_mailto), NULL); + g_signal_connect (display, "update-actions", + G_CALLBACK (mail_display_webview_update_actions), NULL); + g_signal_connect (display, "create-plugin-widget", + G_CALLBACK (mail_display_plugin_widget_requested), NULL); + g_signal_connect (display, "frame-created", + G_CALLBACK (mail_display_frame_created), NULL); + + main_frame = webkit_web_view_get_main_frame (WEBKIT_WEB_VIEW (display)); + g_signal_connect (main_frame, "notify::load-status", + G_CALLBACK (setup_DOM_bindings), NULL); + main_frame = webkit_web_view_get_main_frame (WEBKIT_WEB_VIEW (display)); + g_signal_connect (main_frame, "notify::load-status", + G_CALLBACK (puri_bind_dom), NULL); + + /* Because we are loading from a hard-coded string, there is + * no chance of I/O errors. Failure here implies a malformed + * UI definition. Full stop. */ + ui_manager = e_web_view_get_ui_manager (E_WEB_VIEW (display)); + gtk_ui_manager_insert_action_group (ui_manager, display->priv->mailto_actions, 0); gtk_ui_manager_add_ui_from_string (ui_manager, ui, -1, &error); - if (error != NULL) - g_error ("%s", error->message); + + if (error != NULL) { + g_error ("%s", error->message); + g_error_free (error); + } + + error = NULL; + gtk_ui_manager_insert_action_group (ui_manager, display->priv->images_actions, 0); + gtk_ui_manager_add_ui_from_string (ui_manager, image_ui, -1, &error); + + if (error != NULL) { + g_error ("%s", error->message); + g_error_free (error); + } + + /* Register our own handler for our own mail:// protocol */ + session = webkit_get_default_session (); + feature = SOUP_SESSION_FEATURE (soup_requester_new ()); + soup_session_feature_add_feature (feature, E_TYPE_MAIL_REQUEST); + soup_session_add_feature (session, feature); + g_object_unref (feature); + + /* cache expiry - 2 hour access, 1 day max */ + user_cache_dir = e_get_user_cache_dir (); + emd_global_http_cache = camel_data_cache_new (user_cache_dir, NULL); + if (emd_global_http_cache) { + camel_data_cache_set_expire_age (emd_global_http_cache, 24 * 60 * 60); + camel_data_cache_set_expire_access (emd_global_http_cache, 2 * 60 * 60); + } } EMFormatHTML * @@ -427,10 +1339,264 @@ e_mail_display_set_formatter (EMailDisplay *display, g_return_if_fail (E_IS_MAIL_DISPLAY (display)); g_return_if_fail (EM_IS_FORMAT_HTML (formatter)); - if (display->priv->formatter != NULL) + g_object_ref (formatter); + + if (display->priv->formatter != NULL) { + /* The formatter might still exist after unrefing it, so + * we need to stop listening to it's request for redrawing */ + g_signal_handlers_disconnect_by_func ( + display->priv->formatter, e_mail_display_reload, display); g_object_unref (display->priv->formatter); + } + + display->priv->formatter = formatter; + + mail_display_update_formatter_colors (display); - display->priv->formatter = g_object_ref (formatter); + g_signal_connect (formatter, "notify::image-loading-policy", + G_CALLBACK (formatter_image_loading_policy_changed_cb), display); + g_signal_connect_swapped (formatter, "redraw-requested", + G_CALLBACK (e_mail_display_reload), display); + g_signal_connect_swapped (formatter, "notify::charset", + G_CALLBACK (e_mail_display_reload), display); g_object_notify (G_OBJECT (display), "formatter"); } + +EMFormatWriteMode +e_mail_display_get_mode (EMailDisplay *display) +{ + g_return_val_if_fail (E_IS_MAIL_DISPLAY (display), + EM_FORMAT_WRITE_MODE_NORMAL); + + return display->priv->mode; +} + +void +e_mail_display_set_mode (EMailDisplay *display, + EMFormatWriteMode mode) +{ + g_return_if_fail (E_IS_MAIL_DISPLAY (display)); + + if (display->priv->mode == mode) + return; + + display->priv->mode = mode; + + e_mail_display_reload (display); + + g_object_notify (G_OBJECT (display), "mode"); +} + +gboolean +e_mail_display_get_headers_collapsable (EMailDisplay *display) +{ + g_return_val_if_fail (E_IS_MAIL_DISPLAY (display), FALSE); + + return display->priv->headers_collapsable; +} + +void +e_mail_display_set_headers_collapsable (EMailDisplay *display, + gboolean collapsable) +{ + g_return_if_fail (E_IS_MAIL_DISPLAY (display)); + + if (display->priv->headers_collapsable == collapsable) + return; + + display->priv->headers_collapsable = collapsable; + e_mail_display_reload (display); + + g_object_notify (G_OBJECT (display), "headers-collapsable"); +} + +gboolean +e_mail_display_get_headers_collapsed (EMailDisplay *display) +{ + g_return_val_if_fail (E_IS_MAIL_DISPLAY (display), FALSE); + + if (display->priv->headers_collapsable) + return display->priv->headers_collapsed; + + return FALSE; +} + +void +e_mail_display_set_headers_collapsed (EMailDisplay *display, + gboolean collapsed) +{ + g_return_if_fail (E_IS_MAIL_DISPLAY (display)); + + if (display->priv->headers_collapsed == collapsed) + return; + + display->priv->headers_collapsed = collapsed; + + g_object_notify (G_OBJECT (display), "headers-collapsed"); +} + +void +e_mail_display_load (EMailDisplay *display, + const gchar *msg_uri) +{ + EMFormat *emf; + gchar *uri; + + g_return_if_fail (E_IS_MAIL_DISPLAY (display)); + + display->priv->force_image_load = FALSE; + + emf = EM_FORMAT (display->priv->formatter); + + uri = em_format_build_mail_uri (emf->folder, emf->message_uid, + "mode", G_TYPE_INT, display->priv->mode, + "headers_collapsable", G_TYPE_BOOLEAN, display->priv->headers_collapsable, + "headers_collapsed", G_TYPE_BOOLEAN, display->priv->headers_collapsed, + NULL); + + e_web_view_load_uri (E_WEB_VIEW (display), uri); + + g_free (uri); +} + +void +e_mail_display_reload (EMailDisplay *display) +{ + EWebView *web_view; + const gchar *uri; + gchar *base; + GString *new_uri; + GHashTable *table; + GHashTableIter table_iter; + gpointer key, val; + gchar separator; + + g_return_if_fail (E_IS_MAIL_DISPLAY (display)); + + web_view = E_WEB_VIEW (display); + uri = e_web_view_get_uri (web_view); + + if (!uri || !*uri) + return; + + if (strstr(uri, "?") == NULL) { + e_web_view_reload (web_view); + return; + } + + base = g_strndup (uri, strstr (uri, "?") - uri); + new_uri = g_string_new (base); + g_free (base); + + table = soup_form_decode (strstr (uri, "?") + 1); + g_hash_table_insert (table, g_strdup ("mode"), g_strdup_printf ("%d", display->priv->mode)); + g_hash_table_insert (table, g_strdup ("headers_collapsable"), g_strdup_printf ("%d", display->priv->headers_collapsable)); + g_hash_table_insert (table, g_strdup ("headers_collapsed"), g_strdup_printf ("%d", display->priv->headers_collapsed)); + + g_hash_table_iter_init (&table_iter, table); + separator = '?'; + while (g_hash_table_iter_next (&table_iter, &key, &val)) { + g_string_append_printf (new_uri, "%c%s=%s", separator, + (gchar *) key, (gchar *) val); + + if (separator == '?') + separator = '&'; + } + + e_web_view_load_uri (web_view, new_uri->str); + + g_string_free (new_uri, TRUE); + g_hash_table_destroy (table); +} + +GtkAction * +e_mail_display_get_action (EMailDisplay *display, + const gchar *action_name) +{ + GtkAction *action; + + g_return_val_if_fail (E_IS_MAIL_DISPLAY (display), NULL); + g_return_val_if_fail (action_name != NULL, NULL); + + action = gtk_action_group_get_action (display->priv->mailto_actions, action_name); + if (!action) + action = gtk_action_group_get_action (display->priv->images_actions, action_name); + + return action; +} + +void +e_mail_display_set_status (EMailDisplay *display, + const gchar *status) +{ + gchar *str; + + g_return_if_fail (E_IS_MAIL_DISPLAY (display)); + + str = g_strdup_printf ( + "<!DOCTYPE>" + "<html>" + "<head><title>Evolution Mail Display</title></head>" + "<body>" + "<table border=\"0\" width=\"100%%\" height=\"100%%\">" + "<tr height=\"100%%\" valign=\"middle\">" + "<td width=\"100%%\" align=\"center\">" + "<strong>%s</strong>" + "</td>" + "</tr>" + "</table>" + "</body>" + "</html>", status); + + e_web_view_load_string (E_WEB_VIEW (display), str); + g_free (str); + + gtk_widget_show_all (GTK_WIDGET (display)); +} + +gchar * +e_mail_display_get_selection_plain_text (EMailDisplay *display, + gint *len) +{ + EWebView *web_view; + WebKitWebFrame *frame; + const gchar *frame_name; + GValue value = {0}; + GType type; + const gchar *str; + + g_return_val_if_fail (E_IS_MAIL_DISPLAY (display), NULL); + + web_view = E_WEB_VIEW (display); + frame = webkit_web_view_get_focused_frame (WEBKIT_WEB_VIEW (web_view)); + frame_name = webkit_web_frame_get_name (frame); + + type = e_web_view_frame_exec_script (web_view, frame_name, "window.getSelection().toString()", &value); + g_return_val_if_fail (type == G_TYPE_STRING, NULL); + + str = g_value_get_string (&value); + + if (len) + *len = strlen (str); + + return g_strdup (str); +} + +void +e_mail_display_load_images (EMailDisplay *display) +{ + g_return_if_fail (E_IS_MAIL_DISPLAY (display)); + + display->priv->force_image_load = TRUE; + e_web_view_reload (E_WEB_VIEW (display)); +} + +void +e_mail_display_set_force_load_images (EMailDisplay *display, + gboolean force_load_images) +{ + g_return_if_fail (E_IS_MAIL_DISPLAY (display)); + + display->priv->force_image_load = force_load_images; +} diff --git a/mail/e-mail-display.h b/mail/e-mail-display.h index 1b71a9db7f..cbac1e37bb 100644 --- a/mail/e-mail-display.h +++ b/mail/e-mail-display.h @@ -22,8 +22,9 @@ #ifndef E_MAIL_DISPLAY_H #define E_MAIL_DISPLAY_H -#include <mail/em-format-html.h> -#include <misc/e-web-view.h> +#include <widgets/misc/e-web-view.h> +#include <widgets/misc/e-search-bar.h> +#include "em-format-html.h" /* Standard GObject macros */ #define E_TYPE_MAIL_DISPLAY \ @@ -51,18 +52,53 @@ typedef struct _EMailDisplayClass EMailDisplayClass; typedef struct _EMailDisplayPrivate EMailDisplayPrivate; struct _EMailDisplay { - EWebView parent; + EWebView web_view; EMailDisplayPrivate *priv; }; struct _EMailDisplayClass { EWebViewClass parent_class; + }; -GType e_mail_display_get_type (void); -EMFormatHTML * e_mail_display_get_formatter (EMailDisplay *display); -void e_mail_display_set_formatter (EMailDisplay *display, - EMFormatHTML *formatter); +GType e_mail_display_get_type (void); +EMFormatHTML * e_mail_display_get_formatter (EMailDisplay *display); +void e_mail_display_set_formatter (EMailDisplay *display, + EMFormatHTML *formatter); + +void e_mail_display_set_mode (EMailDisplay *display, + EMFormatWriteMode mode); +EMFormatWriteMode e_mail_display_get_mode (EMailDisplay *display); +void e_mail_display_set_headers_collapsable + (EMailDisplay *display, + gboolean collapsable); +gboolean e_mail_display_get_headers_collapsable + (EMailDisplay *display); +void e_mail_display_set_headers_collapsed + (EMailDisplay *display, + gboolean collapsed); +gboolean e_mail_display_get_headers_collapsed + (EMailDisplay *display); + +void e_mail_display_load (EMailDisplay *display, + const gchar *msg_uri); +void e_mail_display_reload (EMailDisplay *display); + +GtkAction * e_mail_display_get_action (EMailDisplay *display, + const gchar *action_name); + +void e_mail_display_set_status (EMailDisplay *display, + const gchar *status); + +gchar * e_mail_display_get_selection_plain_text + (EMailDisplay *display, + gint *len); + +void e_mail_display_load_images (EMailDisplay *display); + +void e_mail_display_set_force_load_images + (EMailDisplay *display, + gboolean force_load_images); G_END_DECLS diff --git a/mail/e-mail-notebook-view.c b/mail/e-mail-notebook-view.c index 64995bc8ef..86b47f6b92 100644 --- a/mail/e-mail-notebook-view.c +++ b/mail/e-mail-notebook-view.c @@ -900,21 +900,17 @@ mail_notebook_view_get_backend (EMailReader *reader) return E_MAIL_BACKEND (shell_backend); } -static EMFormatHTML * -mail_notebook_view_get_formatter (EMailReader *reader) +static EMailDisplay * +mail_notebook_view_get_mail_display (EMailReader *reader) { - EMailNotebookView *notebook_view; - EMailView *current_view; + EMailNotebookViewPrivate *priv; - notebook_view = E_MAIL_NOTEBOOK_VIEW (reader); - current_view = notebook_view->priv->current_view; + priv = E_MAIL_NOTEBOOK_VIEW (reader)->priv; - if (current_view == NULL) + if (priv->current_view == NULL) return NULL; - reader = E_MAIL_READER (current_view); - - return e_mail_reader_get_formatter (reader); + return e_mail_reader_get_mail_display (E_MAIL_READER (priv->current_view)); } static gboolean @@ -1458,7 +1454,7 @@ e_mail_notebook_view_reader_init (EMailReaderInterface *interface) interface->get_action_group = mail_notebook_view_get_action_group; interface->get_alert_sink = mail_notebook_view_get_alert_sink; interface->get_backend = mail_notebook_view_get_backend; - interface->get_formatter = mail_notebook_view_get_formatter; + interface->get_mail_display = mail_notebook_view_get_mail_display; interface->get_hide_deleted = mail_notebook_view_get_hide_deleted; interface->get_message_list = mail_notebook_view_get_message_list; interface->get_popup_menu = mail_notebook_view_get_popup_menu; diff --git a/mail/e-mail-paned-view.c b/mail/e-mail-paned-view.c index e1779a8c3e..5c7b356fca 100644 --- a/mail/e-mail-paned-view.c +++ b/mail/e-mail-paned-view.c @@ -59,8 +59,9 @@ struct _EMailPanedViewPrivate { GtkWidget *scrolled_window; GtkWidget *message_list; GtkWidget *preview_pane; + GtkWidget *search_bar; - EMFormatHTMLDisplay *formatter; + EMailDisplay *display; GalViewInstance *view_instance; /* ETable scrolling hack */ @@ -358,11 +359,6 @@ mail_paned_view_dispose (GObject *object) priv->preview_pane = NULL; } - if (priv->formatter != NULL) { - g_object_unref (priv->formatter); - priv->formatter = NULL; - } - if (priv->view_instance != NULL) { g_object_unref (priv->view_instance); priv->view_instance = NULL; @@ -427,14 +423,14 @@ mail_paned_view_get_backend (EMailReader *reader) return E_MAIL_BACKEND (shell_backend); } -static EMFormatHTML * -mail_paned_view_get_formatter (EMailReader *reader) +static EMailDisplay * +mail_paned_view_get_mail_display (EMailReader *reader) { - EMailPanedView *paned_view; + EMailPanedViewPrivate *priv; - paned_view = E_MAIL_PANED_VIEW (reader); + priv = E_MAIL_PANED_VIEW (reader)->priv; - return EM_FORMAT_HTML (paned_view->priv->formatter); + return priv->display; } static gboolean @@ -622,7 +618,6 @@ mail_paned_view_constructed (GObject *object) EShellView *shell_view; EShell *shell; EShellSettings *shell_settings; - ESearchBar *search_bar; EMailReader *reader; EMailBackend *backend; EMailSession *session; @@ -630,10 +625,11 @@ mail_paned_view_constructed (GObject *object) GtkWidget *message_list; GtkWidget *container; GtkWidget *widget; - EWebView *web_view; priv = E_MAIL_PANED_VIEW_GET_PRIVATE (object); - priv->formatter = em_format_html_display_new (); + + priv->display = g_object_new (E_TYPE_MAIL_DISPLAY, + "headers-collapsable", TRUE, NULL); view = E_MAIL_VIEW (object); shell_view = e_mail_view_get_shell_view (view); @@ -645,17 +641,9 @@ mail_paned_view_constructed (GObject *object) backend = E_MAIL_BACKEND (shell_backend); session = e_mail_backend_get_session (backend); - /* Make headers collapsable and store state of headers in config file */ - em_format_html_set_headers_collapsable ( - EM_FORMAT_HTML (priv->formatter), TRUE); - g_object_bind_property ( - shell_settings, "paned-view-headers-state", - priv->formatter, "headers-state", - G_BINDING_BIDIRECTIONAL | - G_BINDING_SYNC_CREATE); - - web_view = em_format_html_get_web_view ( - EM_FORMAT_HTML (priv->formatter)); + g_object_bind_property (shell_settings, "paned-view-headers-state", + priv->display, "headers-collapsed", + G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); /* Build content widgets. */ @@ -692,11 +680,10 @@ mail_paned_view_constructed (GObject *object) container = priv->paned; - gtk_widget_show (GTK_WIDGET (web_view)); - - widget = e_preview_pane_new (web_view); + widget = e_preview_pane_new (E_WEB_VIEW (priv->display)); gtk_paned_pack2 (GTK_PANED (container), widget, FALSE, FALSE); priv->preview_pane = g_object_ref (widget); + gtk_widget_show (GTK_WIDGET (priv->display)); gtk_widget_show (widget); g_object_bind_property ( @@ -704,12 +691,6 @@ mail_paned_view_constructed (GObject *object) widget, "visible", G_BINDING_SYNC_CREATE); - search_bar = e_preview_pane_get_search_bar (E_PREVIEW_PANE (widget)); - - g_signal_connect_swapped ( - search_bar, "changed", - G_CALLBACK (em_format_queue_redraw), priv->formatter); - /* Load the view instance. */ e_mail_view_update_view_instance (E_MAIL_VIEW (object)); @@ -733,8 +714,6 @@ mail_paned_view_constructed (GObject *object) G_CALLBACK (mail_paned_view_restore_state_cb), object); - e_mail_reader_connect_headers (reader); - /* Do this after creating the message list. Our * set_preview_visible() method relies on it. */ e_mail_view_set_preview_visible (view, TRUE); @@ -749,26 +728,23 @@ static void mail_paned_view_set_search_strings (EMailView *view, GSList *search_strings) { + EMailDisplay *display; + EWebView *web_view; EMailReader *reader; - EPreviewPane *preview_pane; - ESearchBar *search_bar; - ESearchingTokenizer *tokenizer; reader = E_MAIL_READER (view); - preview_pane = e_mail_reader_get_preview_pane (reader); - search_bar = e_preview_pane_get_search_bar (preview_pane); - tokenizer = e_search_bar_get_tokenizer (search_bar); + display = e_mail_reader_get_mail_display (reader); + if (!display) + return; + + web_view = E_WEB_VIEW (display); - e_searching_tokenizer_set_secondary_case_sensitivity (tokenizer, FALSE); - e_searching_tokenizer_set_secondary_search_string (tokenizer, NULL); + e_web_view_clear_highlights (web_view); while (search_strings != NULL) { - e_searching_tokenizer_add_secondary_search_string ( - tokenizer, search_strings->data); + e_web_view_add_highlight (web_view, search_strings->data); search_strings = g_slist_next (search_strings); } - - e_search_bar_changed (search_bar); } static GalViewInstance * @@ -1047,7 +1023,7 @@ e_mail_paned_view_reader_init (EMailReaderInterface *interface) interface->get_action_group = mail_paned_view_get_action_group; interface->get_alert_sink = mail_paned_view_get_alert_sink; interface->get_backend = mail_paned_view_get_backend; - interface->get_formatter = mail_paned_view_get_formatter; + interface->get_mail_display = mail_paned_view_get_mail_display; interface->get_hide_deleted = mail_paned_view_get_hide_deleted; interface->get_message_list = mail_paned_view_get_message_list; interface->get_popup_menu = mail_paned_view_get_popup_menu; @@ -1092,6 +1068,14 @@ e_mail_paned_view_hide_message_list_pane (EMailPanedView *view, gtk_widget_hide (view->priv->scrolled_window); } +GtkWidget * +e_mail_paned_view_get_preview (EMailPanedView *view) +{ + g_return_val_if_fail (E_IS_MAIL_PANED_VIEW (view), NULL); + + return GTK_WIDGET (mail_paned_view_get_mail_display (E_MAIL_READER (view))); +} + void e_mail_paned_view_set_enable_show_folder (EMailPanedView *view, gboolean set) diff --git a/mail/e-mail-paned-view.h b/mail/e-mail-paned-view.h index 3226b394f1..5e6879ae94 100644 --- a/mail/e-mail-paned-view.h +++ b/mail/e-mail-paned-view.h @@ -71,6 +71,7 @@ GtkWidget * e_mail_paned_view_new (EShellView *shell_view); void e_mail_paned_view_hide_message_list_pane (EMailPanedView *view, gboolean visible); +GtkWidget * e_mail_paned_view_get_preview (EMailPanedView *view); void e_mail_paned_view_set_enable_show_folder (EMailPanedView *view, gboolean set); diff --git a/mail/e-mail-printer.c b/mail/e-mail-printer.c new file mode 100644 index 0000000000..f0fb8412a5 --- /dev/null +++ b/mail/e-mail-printer.c @@ -0,0 +1,859 @@ +/* + * 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/> + * + * Copyright (C) 2011 Dan Vratil <dvratil@redhat.com> + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <glib/gi18n.h> +#include <gtk/gtk.h> + +#include <e-util/e-print.h> +#include <e-util/e-marshal.h> + +#include <webkit/webkitdom.h> + +#include "e-mail-printer.h" +#include "em-format-html-print.h" +#include "e-mail-display.h" + +static gpointer parent_class = NULL; + +enum { + BUTTON_SELECT_ALL, + BUTTON_SELECT_NONE, + BUTTON_TOP, + BUTTON_UP, + BUTTON_DOWN, + BUTTON_BOTTOM, + BUTTONS_COUNT +}; + +#define w(x) + +struct _EMailPrinterPrivate { + EMFormatHTMLPrint *efhp; + + gboolean export_mode; + + GtkListStore *headers; + + WebKitWebView *webview; /* WebView to print from */ + gchar *uri; + GtkWidget *buttons[BUTTONS_COUNT]; + GtkWidget *treeview; + + GtkPrintOperation *operation; +}; + +G_DEFINE_TYPE ( + EMailPrinter, + e_mail_printer, + G_TYPE_OBJECT); + +enum { + PROP_0, + PROP_PRINT_FORMATTER +}; + +enum { + SIGNAL_DONE, + LAST_SIGNAL +}; + +enum { + COLUMN_ACTIVE, + COLUMN_HEADER_NAME, + COLUMN_HEADER_VALUE, + COLUMN_HEADER_STRUCT, + LAST_COLUMN +}; + +static guint signals[LAST_SIGNAL]; + +static gint +emp_header_name_equal (const EMFormatHeader *h1, + const EMFormatHeader *h2) +{ + if ((h2->value == NULL) || (h1->value == NULL)) { + return g_strcmp0 (h1->name, h2->name); + } else { + if ((g_strcmp0 (h1->name, h2->name) == 0) && + (g_strcmp0 (h1->value, h2->value) == 0)) + return 0; + else + return 1; + } +} + +static void +emp_draw_footer (GtkPrintOperation *operation, + GtkPrintContext *context, + gint page_nr) +{ + PangoFontDescription *desc; + PangoLayout *layout; + gint n_pages; + gdouble width, height; + gchar *text; + cairo_t *cr; + + cr = gtk_print_context_get_cairo_context (context); + width = gtk_print_context_get_width (context); + height = gtk_print_context_get_height (context); + + g_object_get (operation, "n-pages", &n_pages, NULL); + text = g_strdup_printf (_("Page %d of %d"), page_nr + 1, n_pages); + + cairo_set_source_rgb (cr, 0.1, 0.1, 0.1); + cairo_fill (cr); + + desc = pango_font_description_from_string ("Sans Regular 10"); + layout = gtk_print_context_create_pango_layout (context); + pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER); + pango_layout_set_font_description (layout, desc); + pango_layout_set_text (layout, text, -1); + pango_layout_set_width (layout, width * PANGO_SCALE); + pango_font_description_free (desc); + + cairo_move_to (cr, 0, height + 5); + pango_cairo_show_layout (cr, layout); + + g_object_unref (layout); + g_free (text); +} + +static void +emp_printing_done (GtkPrintOperation *operation, + GtkPrintOperationResult result, + gpointer user_data) +{ + EMailPrinter *emp = user_data; + + g_signal_emit (emp, signals[SIGNAL_DONE], 0, operation, result); +} + +static void +emp_start_printing (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + WebKitWebView *web_view; + WebKitWebFrame *frame; + WebKitLoadStatus load_status; + EMailPrinter *emp = user_data; + + web_view = WEBKIT_WEB_VIEW (object); + load_status = webkit_web_view_get_load_status (web_view); + + if (load_status != WEBKIT_LOAD_FINISHED) + return; + + frame = webkit_web_view_get_main_frame (web_view); + + if (emp->priv->export_mode) { + gtk_print_operation_set_export_filename ( + emp->priv->operation, + emp->priv->efhp->export_filename); + webkit_web_frame_print_full ( + frame, emp->priv->operation, + GTK_PRINT_OPERATION_ACTION_EXPORT, NULL); + } else { + webkit_web_frame_print_full + (frame, emp->priv->operation, + GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG, NULL); + } + +} + +static void +emp_run_print_operation (EMailPrinter *emp) +{ + EMFormat *emf; + SoupSession *session; + GHashTable *formatters; + gchar *mail_uri; + + emf = EM_FORMAT (emp->priv->efhp); + mail_uri = em_format_build_mail_uri (emf->folder, emf->message_uid, NULL, NULL); + + /* It's safe to assume that session exists and contains formatters table, + * because at least the message we are about to print now must be already + * there */ + session = webkit_get_default_session (); + formatters = g_object_get_data (G_OBJECT (session), "formatters"); + g_hash_table_insert (formatters, g_strdup (mail_uri), emp->priv->efhp); + + /* Print_layout is a special EMPart created by EMFormatHTMLPrint */ + if (emp->priv->uri) + g_free (emp->priv->uri); + + emp->priv->uri = g_strconcat (mail_uri, "?part_id=print_layout&__evo-load-images=1", NULL); + + if (emp->priv->webview == NULL) { + emp->priv->webview = g_object_new (E_TYPE_MAIL_DISPLAY, NULL); + e_web_view_set_enable_frame_flattening (E_WEB_VIEW (emp->priv->webview), FALSE); + e_mail_display_set_force_load_images ( + E_MAIL_DISPLAY (emp->priv->webview), TRUE); + g_object_ref_sink (emp->priv->webview); + g_signal_connect (emp->priv->webview, "notify::load-status", + G_CALLBACK (emp_start_printing), emp); + + w ({ + GtkWidget *window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + GtkWidget *sw = gtk_scrolled_window_new (NULL, NULL); + gtk_container_add (GTK_CONTAINER (window), sw); + gtk_container_add (GTK_CONTAINER (sw), + GTK_WIDGET (emp->priv->webview)); + gtk_widget_show_all (window); + }); + } + + e_mail_display_set_formatter (E_MAIL_DISPLAY (emp->priv->webview), + (EMFormatHTML *) emp->priv->efhp); + + webkit_web_view_load_uri (emp->priv->webview, emp->priv->uri); + + g_free (mail_uri); +} + +static void +set_header_visible (EMailPrinter *emp, + EMFormatHeader *header, + gint index, + gboolean visible) +{ + WebKitDOMDocument *document; + WebKitDOMNodeList *headers; + WebKitDOMElement *element; + WebKitDOMCSSStyleDeclaration *style; + + document = webkit_web_view_get_dom_document (emp->priv->webview); + headers = webkit_dom_document_get_elements_by_class_name (document, "header-item"); + + g_return_if_fail (index < webkit_dom_node_list_get_length (headers)); + + element = WEBKIT_DOM_ELEMENT (webkit_dom_node_list_item (headers, index)); + style = webkit_dom_element_get_style (element); + webkit_dom_css_style_declaration_set_property (style, + "display", (visible ? "table-row" : "none"), "", NULL); +} + +static void +header_active_renderer_toggled_cb (GtkCellRendererToggle *renderer, + gchar *path, + EMailPrinter *emp) +{ + GtkTreeIter iter; + GtkTreePath *p; + gboolean active; + EMFormatHeader *header; + gint *indices; + + gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (emp->priv->headers), + &iter, path); + + gtk_tree_model_get (GTK_TREE_MODEL (emp->priv->headers), &iter, + COLUMN_ACTIVE, &active, -1); + gtk_tree_model_get (GTK_TREE_MODEL (emp->priv->headers), &iter, + COLUMN_HEADER_STRUCT, &header, -1); + gtk_list_store_set (GTK_LIST_STORE (emp->priv->headers), &iter, + COLUMN_ACTIVE, !active, -1); + + p = gtk_tree_path_new_from_string (path); + indices = gtk_tree_path_get_indices (p); + set_header_visible (emp, header, indices[0], !active); + gtk_tree_path_free (p); +} + +static void +emp_headers_tab_toggle_selection (GtkWidget *button, + gpointer user_data) +{ + EMailPrinter *emp = user_data; + GtkTreeIter iter; + gboolean select; + + if (button == emp->priv->buttons[BUTTON_SELECT_ALL]) + select = TRUE; + else if (button == emp->priv->buttons[BUTTON_SELECT_NONE]) + select = FALSE; + else + return; + + if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (emp->priv->headers), &iter)) + return; + + do { + EMFormatHeader *header; + GtkTreePath *path; + gint *indices; + + gtk_tree_model_get (GTK_TREE_MODEL (emp->priv->headers), &iter, + COLUMN_HEADER_STRUCT, &header, -1); + gtk_list_store_set (GTK_LIST_STORE (emp->priv->headers), &iter, + COLUMN_ACTIVE, select, -1); + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (emp->priv->headers), &iter); + indices = gtk_tree_path_get_indices (path); + set_header_visible (emp, header, indices[0], select); + gtk_tree_path_free (path); + + } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (emp->priv->headers), &iter)); +} + +static void +emp_headers_tab_selection_changed (GtkTreeSelection *selection, + gpointer user_data) +{ + EMailPrinter *emp = user_data; + gboolean enabled; + GList *selected_rows; + GtkTreeIter iter; + GtkTreeModel *model; + GtkTreePath *path; + + if (gtk_tree_selection_count_selected_rows (selection) == 0) { + gtk_widget_set_sensitive (emp->priv->buttons[BUTTON_TOP], FALSE); + gtk_widget_set_sensitive (emp->priv->buttons[BUTTON_UP], FALSE); + gtk_widget_set_sensitive (emp->priv->buttons[BUTTON_DOWN], FALSE); + gtk_widget_set_sensitive (emp->priv->buttons[BUTTON_BOTTOM], FALSE); + + return; + } + + model = GTK_TREE_MODEL (emp->priv->headers); + selected_rows = gtk_tree_selection_get_selected_rows (selection, &model); + + path = gtk_tree_path_copy (selected_rows->data); + enabled = gtk_tree_path_prev (path); + gtk_widget_set_sensitive (emp->priv->buttons[BUTTON_TOP], enabled); + gtk_widget_set_sensitive (emp->priv->buttons[BUTTON_UP], enabled); + + gtk_tree_model_get_iter (model, &iter, g_list_last (selected_rows)->data); + enabled = gtk_tree_model_iter_next (model, &iter); + gtk_widget_set_sensitive (emp->priv->buttons[BUTTON_DOWN], enabled); + gtk_widget_set_sensitive (emp->priv->buttons[BUTTON_BOTTOM], enabled); + + g_list_foreach (selected_rows, (GFunc) gtk_tree_path_free, NULL); + g_list_free (selected_rows); + gtk_tree_path_free (path); +} + +static void +emp_headers_tab_move (GtkWidget *button, + gpointer user_data) +{ + EMailPrinter *emp = user_data; + GtkTreeSelection *selection; + GList *selected_rows, *references, *l; + GtkTreePath *path; + GtkTreeModel *model; + GtkTreeIter iter; + GtkTreeRowReference *selection_middle; + gint *indices; + + WebKitDOMDocument *document; + WebKitDOMNodeList *headers; + WebKitDOMNode *header, *parent; + + model = GTK_TREE_MODEL (emp->priv->headers); + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (emp->priv->treeview)); + selected_rows = gtk_tree_selection_get_selected_rows (selection, &model); + + /* The order of header rows in the HMTL document should be in sync with + order of headers in the listview and in efhp->headers_list */ + document = webkit_web_view_get_dom_document (emp->priv->webview); + headers = webkit_dom_document_get_elements_by_class_name (document, "header-item"); + + l = g_list_nth (selected_rows, g_list_length (selected_rows) / 2); + selection_middle = gtk_tree_row_reference_new (model, l->data); + + references = NULL; + for (l = selected_rows; l; l = l->next) { + references = g_list_prepend (references, + gtk_tree_row_reference_new (model, l->data)); + } + + if (button == emp->priv->buttons[BUTTON_TOP]) { + + for (l = references; l; l = l->next) { + /* Move the rows in the view */ + path = gtk_tree_row_reference_get_path (l->data); + gtk_tree_model_get_iter (model, &iter, path); + gtk_list_store_move_after (emp->priv->headers, &iter, NULL); + + /* Move the header row in HTML document */ + indices = gtk_tree_path_get_indices (path); + header = webkit_dom_node_list_item (headers, indices[0]); + parent = webkit_dom_node_get_parent_node (header); + webkit_dom_node_remove_child (parent, header, NULL); + webkit_dom_node_insert_before (parent, header, + webkit_dom_node_get_first_child (parent), NULL); + + gtk_tree_path_free (path); + } + + } else if (button == emp->priv->buttons[BUTTON_UP]) { + + GtkTreeIter *iter_prev; + WebKitDOMNode *node2; + + references = g_list_reverse (references); + + for (l = references; l; l = l->next) { + + path = gtk_tree_row_reference_get_path (l->data); + gtk_tree_model_get_iter (model, &iter, path); + iter_prev = gtk_tree_iter_copy (&iter); + gtk_tree_model_iter_previous (model, iter_prev); + + gtk_list_store_move_before (emp->priv->headers, &iter, iter_prev); + + indices = gtk_tree_path_get_indices (path); + header = webkit_dom_node_list_item (headers, indices[0]); + node2 = webkit_dom_node_get_previous_sibling (header); + parent = webkit_dom_node_get_parent_node (header); + + webkit_dom_node_remove_child (parent, header, NULL); + webkit_dom_node_insert_before (parent, header, node2, NULL); + + gtk_tree_path_free (path); + gtk_tree_iter_free (iter_prev); + } + + } else if (button == emp->priv->buttons[BUTTON_DOWN]) { + + GtkTreeIter *iter_next; + WebKitDOMNode *node2; + + for (l = references; l; l = l->next) { + + path = gtk_tree_row_reference_get_path (l->data); + gtk_tree_model_get_iter (model, &iter, path); + iter_next = gtk_tree_iter_copy (&iter); + gtk_tree_model_iter_next (model, iter_next); + + gtk_list_store_move_after (emp->priv->headers, &iter, iter_next); + + indices = gtk_tree_path_get_indices (path); + header = webkit_dom_node_list_item (headers, indices[0]); + node2 = webkit_dom_node_get_next_sibling (header); + parent = webkit_dom_node_get_parent_node (header); + + webkit_dom_node_remove_child (parent, header, NULL); + webkit_dom_node_insert_before (parent, header, + webkit_dom_node_get_next_sibling (node2), NULL); + + gtk_tree_path_free (path); + gtk_tree_iter_free (iter_next); + } + + } else if (button == emp->priv->buttons[BUTTON_BOTTOM]) { + + references = g_list_reverse (references); + + for (l = references; l; l = l->next) { + path = gtk_tree_row_reference_get_path (l->data); + gtk_tree_model_get_iter (model, &iter, path); + gtk_list_store_move_before (emp->priv->headers, &iter, NULL); + + /* Move the header row in HTML document */ + indices = gtk_tree_path_get_indices (path); + header = webkit_dom_node_list_item (headers, indices[0]); + parent = webkit_dom_node_get_parent_node (header); + webkit_dom_node_remove_child (parent, header, NULL); + webkit_dom_node_append_child (parent, header, NULL); + + gtk_tree_path_free (path); + } + }; + + g_list_foreach (references, (GFunc) gtk_tree_row_reference_free, NULL); + g_list_free (references); + + /* Keep the selection in middle of the screen */ + path = gtk_tree_row_reference_get_path (selection_middle); + gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (emp->priv->treeview), + path, COLUMN_ACTIVE, TRUE, 0.5, 0.5); + gtk_tree_path_free (path); + gtk_tree_row_reference_free (selection_middle); + + g_list_foreach (selected_rows, (GFunc) gtk_tree_path_free, NULL); + g_list_free (selected_rows); + + emp_headers_tab_selection_changed (selection, user_data); +} + +static GtkWidget * +emp_create_headers_tab (GtkPrintOperation *operation, + EMailPrinter *emp) +{ + GtkWidget *vbox, *hbox, *scw, *button; + GtkTreeView *view; + GtkTreeSelection *selection; + GtkTreeViewColumn *column; + GtkCellRenderer *renderer; + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5); + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 5); + gtk_box_pack_end (GTK_BOX (hbox), vbox, FALSE, FALSE, 5); + + emp->priv->treeview = gtk_tree_view_new_with_model ( + GTK_TREE_MODEL (emp->priv->headers)); + view = GTK_TREE_VIEW (emp->priv->treeview); + selection = gtk_tree_view_get_selection (view); + gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE); + g_signal_connect (selection, "changed", + G_CALLBACK (emp_headers_tab_selection_changed), emp); + + renderer = gtk_cell_renderer_toggle_new (); + g_signal_connect (renderer, "toggled", + G_CALLBACK (header_active_renderer_toggled_cb), emp); + column = gtk_tree_view_column_new_with_attributes ( + _("Print"), renderer, + "active", COLUMN_ACTIVE, NULL); + gtk_tree_view_append_column (view, column); + + renderer = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes ( + _("Header Name"), renderer, + "text", COLUMN_HEADER_NAME, NULL); + gtk_tree_view_append_column (view, column); + + renderer = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes ( + _("Header Value"), renderer, + "text", COLUMN_HEADER_VALUE, NULL); + gtk_tree_view_append_column (view, column); + + scw = gtk_scrolled_window_new (NULL, NULL); + gtk_container_add (GTK_CONTAINER (scw), GTK_WIDGET (view)); + gtk_box_pack_start (GTK_BOX (hbox), scw, TRUE, TRUE, 0); + + button = gtk_button_new_from_stock (GTK_STOCK_SELECT_ALL); + emp->priv->buttons[BUTTON_SELECT_ALL] = button; + g_signal_connect (button, "clicked", + G_CALLBACK (emp_headers_tab_toggle_selection), emp); + gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, TRUE, 5); + + button = gtk_button_new_from_stock (GTK_STOCK_CLEAR); + emp->priv->buttons[BUTTON_SELECT_NONE] = button; + g_signal_connect (button, "clicked", + G_CALLBACK (emp_headers_tab_toggle_selection), emp); + gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, TRUE, 5); + + button = gtk_button_new_from_stock (GTK_STOCK_GOTO_TOP); + emp->priv->buttons[BUTTON_TOP] = button; + gtk_widget_set_sensitive (button, FALSE); + g_signal_connect (button, "clicked", + G_CALLBACK (emp_headers_tab_move), emp); + gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, TRUE, 5); + + button = gtk_button_new_from_stock (GTK_STOCK_GO_UP); + emp->priv->buttons[BUTTON_UP] = button; + gtk_widget_set_sensitive (button, FALSE); + g_signal_connect (button, "clicked", + G_CALLBACK (emp_headers_tab_move), emp); + gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, TRUE, 5); + + button = gtk_button_new_from_stock (GTK_STOCK_GO_DOWN); + emp->priv->buttons[BUTTON_DOWN] = button; + gtk_widget_set_sensitive (button, FALSE); + g_signal_connect (button, "clicked", + G_CALLBACK (emp_headers_tab_move), emp); + gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, TRUE, 5); + + button = gtk_button_new_from_stock (GTK_STOCK_GOTO_BOTTOM); + emp->priv->buttons[BUTTON_BOTTOM] = button; + gtk_widget_set_sensitive (button, FALSE); + g_signal_connect (button, "clicked", + G_CALLBACK (emp_headers_tab_move), emp); + gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, TRUE, 5); + + gtk_print_operation_set_custom_tab_label (operation, _("Headers")); + gtk_widget_show_all (hbox); + + return hbox; +} + +static void +emp_set_formatter (EMailPrinter *emp, + EMFormatHTMLPrint *formatter) +{ + EMFormat *emf = (EMFormat *) formatter; + CamelMediumHeader *header; + GArray *headers; + gint i; + GtkTreeIter last_known; + + g_return_if_fail (EM_IS_FORMAT_HTML_PRINT (formatter)); + + g_object_ref (formatter); + + if (emp->priv->efhp) + g_object_unref (emp->priv->efhp); + + emp->priv->efhp = formatter; + + if (emp->priv->headers) + g_object_unref (emp->priv->headers); + emp->priv->headers = gtk_list_store_new (5, + G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_INT); + + headers = camel_medium_get_headers (CAMEL_MEDIUM (emf->message)); + if (!headers) + return; + + for (i = 0; i < headers->len; i++) { + GtkTreeIter iter; + GList *found_header; + EMFormatHeader *emfh; + + header = &g_array_index (headers, CamelMediumHeader, i); + emfh = em_format_header_new (header->name, header->value); + + found_header = g_queue_find_custom (&EM_FORMAT (formatter)->header_list, + emfh, (GCompareFunc) emp_header_name_equal); + + if (!found_header) { + emfh->flags |= EM_FORMAT_HTML_HEADER_HIDDEN; + em_format_add_header_struct (EM_FORMAT (formatter), emfh); + gtk_list_store_append (emp->priv->headers, &iter); + } else { + if (gtk_list_store_iter_is_valid (emp->priv->headers, &last_known)) + gtk_list_store_insert_after (emp->priv->headers, &iter, &last_known); + else + gtk_list_store_insert_after (emp->priv->headers, &iter, NULL); + + last_known = iter; + } + + gtk_list_store_set (emp->priv->headers, &iter, + COLUMN_ACTIVE, (found_header != NULL), + COLUMN_HEADER_NAME, emfh->name, + COLUMN_HEADER_VALUE, emfh->value, + COLUMN_HEADER_STRUCT, emfh, -1); + } + + camel_medium_free_headers (CAMEL_MEDIUM (emf->message), headers); +} + +static void +emp_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + EMailPrinter *emp = E_MAIL_PRINTER (object); + + switch (property_id) { + + case PROP_PRINT_FORMATTER: + emp_set_formatter (emp, g_value_get_object (value)); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +emp_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + EMailPrinter *emp = E_MAIL_PRINTER (object); + + switch (property_id) { + + case PROP_PRINT_FORMATTER: + g_value_set_object (value, + e_mail_printer_get_print_formatter (emp)); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +emp_finalize (GObject *object) +{ + EMailPrinterPrivate *priv = E_MAIL_PRINTER (object)->priv; + + if (priv->efhp) { + g_object_unref (priv->efhp); + priv->efhp = NULL; + } + + if (priv->headers) { + GtkTreeIter iter; + + if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (priv->headers), &iter)) { + do { + EMFormatHeader *header = NULL; + gtk_tree_model_get (GTK_TREE_MODEL (priv->headers), &iter, + COLUMN_HEADER_STRUCT, &header, -1); + em_format_header_free (header); + } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (priv->headers), &iter)); + } + g_object_unref (priv->headers); + priv->headers = NULL; + } + + if (priv->webview) { + g_object_unref (priv->webview); + priv->webview = NULL; + } + + if (priv->uri) { + g_free (priv->uri); + priv->uri = NULL; + } + + if (priv->operation) { + g_object_unref (priv->operation); + priv->operation = NULL; + } + + /* Chain up to parent's finalize() method. */ + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +e_mail_printer_class_init (EMailPrinterClass *klass) +{ + GObjectClass *object_class; + + parent_class = g_type_class_peek_parent (klass); + g_type_class_add_private (klass, sizeof (EMailPrinterPrivate)); + + object_class = G_OBJECT_CLASS (klass); + object_class->set_property = emp_set_property; + object_class->get_property = emp_get_property; + object_class->finalize = emp_finalize; + + g_object_class_install_property ( + object_class, + PROP_PRINT_FORMATTER, + g_param_spec_object ( + "print-formatter", + NULL, + NULL, + EM_TYPE_FORMAT_HTML_PRINT, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + signals[SIGNAL_DONE] = g_signal_new ("done", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (EMailPrinterClass, done), + NULL, NULL, + e_marshal_VOID__OBJECT_INT, + G_TYPE_NONE, 2, + GTK_TYPE_PRINT_OPERATION, G_TYPE_INT); +} + +static void +e_mail_printer_init (EMailPrinter *emp) +{ + emp->priv = G_TYPE_INSTANCE_GET_PRIVATE ( + emp, E_TYPE_MAIL_PRINTER, EMailPrinterPrivate); + + emp->priv->efhp = NULL; + emp->priv->headers = NULL; + emp->priv->webview = NULL; +} + +EMailPrinter * +e_mail_printer_new (EMFormatHTML * source) +{ + EMailPrinter *emp; + EMFormatHTMLPrint *efhp; + + efhp = em_format_html_print_new (source); + + emp = g_object_new (E_TYPE_MAIL_PRINTER, + "print-formatter", efhp, NULL); + + g_object_unref (efhp); + + return emp; +} + +void +e_mail_printer_print (EMailPrinter *emp, + gboolean export_mode, + GCancellable *cancellable) +{ + g_return_if_fail (E_IS_MAIL_PRINTER (emp)); + + if (emp->priv->operation) + g_object_unref (emp->priv->operation); + emp->priv->operation = e_print_operation_new (); + gtk_print_operation_set_unit (emp->priv->operation, GTK_UNIT_PIXEL); + + gtk_print_operation_set_show_progress (emp->priv->operation, TRUE); + g_signal_connect (emp->priv->operation, "create-custom-widget", + G_CALLBACK (emp_create_headers_tab), emp); + g_signal_connect (emp->priv->operation, "done", + G_CALLBACK (emp_printing_done), emp); + g_signal_connect (emp->priv->operation, "draw-page", + G_CALLBACK (emp_draw_footer), NULL); + + emp->priv->export_mode = export_mode; + + if (cancellable) + g_signal_connect_swapped (cancellable, "cancelled", + G_CALLBACK (gtk_print_operation_cancel), emp->priv->operation); + + emp_run_print_operation (emp); +} + +const gchar * +e_mail_printer_get_export_filename (EMailPrinter *printer) +{ + g_return_val_if_fail (E_IS_MAIL_PRINTER (printer), NULL); + + if (!printer->priv->efhp) + return NULL; + + return printer->priv->efhp->export_filename; +} + +void +e_mail_printer_set_export_filename (EMailPrinter *printer, + const gchar *filename) +{ + g_return_if_fail (E_IS_MAIL_PRINTER (printer)); + g_return_if_fail (printer->priv->efhp != NULL); + + if (printer->priv->efhp->export_filename && *printer->priv->efhp->export_filename) + g_free (printer->priv->efhp->export_filename); + + printer->priv->efhp->export_filename = g_strdup (filename); +} + +EMFormatHTMLPrint * +e_mail_printer_get_print_formatter (EMailPrinter *emp) +{ + g_return_val_if_fail (E_IS_MAIL_PRINTER (emp), NULL); + + return emp->priv->efhp; +} + diff --git a/mail/e-mail-printer.h b/mail/e-mail-printer.h new file mode 100644 index 0000000000..fcd163ec78 --- /dev/null +++ b/mail/e-mail-printer.h @@ -0,0 +1,85 @@ +/* + * Class for printing emails + * + * 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/> + * + * Copyright (C) 2011 Dan Vratil <dvratil@redhat.com> + */ + +#ifndef E_MAIL_PRINTER_H +#define E_MAIL_PRINTER_H + +#include "mail/em-format-html-print.h" + +/* Standard GObject macros */ +#define E_TYPE_MAIL_PRINTER \ + (e_mail_printer_get_type ()) +#define E_MAIL_PRINTER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_MAIL_PRINTER, EMailPrinter)) +#define E_MAIL_PRINTER_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_MAIL_PRINTER, EMailPrinterClass)) +#define E_IS_MAIL_PRINTER(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_MAIL_PRINTER)) +#define E_IS_MAIL_PRINTER_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_MAIL_PRINTER_CLASS)) +#define E_MAIL_PRINTER_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_MAIL_PRINTER, EMailPrinterClass)) + +G_BEGIN_DECLS + +typedef struct _EMailPrinter EMailPrinter; +typedef struct _EMailPrinterClass EMailPrinterClass; +typedef struct _EMailPrinterPrivate EMailPrinterPrivate; + +struct _EMailPrinter { + GObject parent; + EMailPrinterPrivate *priv; +}; + +struct _EMailPrinterClass { + GObjectClass parent_class; + + void (*done) (EMailPrinter *printer, + GtkPrintOperation *operation, + GtkPrintOperationResult *result, + gpointer user_data); +}; + +GType e_mail_printer_get_type (void); + +EMailPrinter * e_mail_printer_new (EMFormatHTML *source); + +void e_mail_printer_print (EMailPrinter *printer, + gboolean export, + GCancellable *cancellable); + +void e_mail_printer_set_export_filename + (EMailPrinter *printer, + const gchar *filename); + +const gchar * e_mail_printer_get_export_filename + (EMailPrinter *printer); + +EMFormatHTMLPrint * + e_mail_printer_get_print_formatter + (EMailPrinter *printer); + +G_END_DECLS + +#endif /* E_MAIL_PRINTER_H */ diff --git a/mail/e-mail-reader-utils.c b/mail/e-mail-reader-utils.c index bc87295f60..5956ab209d 100644 --- a/mail/e-mail-reader-utils.c +++ b/mail/e-mail-reader-utils.c @@ -44,6 +44,7 @@ #include "mail/e-mail-backend.h" #include "mail/e-mail-browser.h" +#include "mail/e-mail-printer.h" #include "mail/em-composer-utils.h" #include "mail/em-format-html-print.h" #include "mail/em-utils.h" @@ -412,7 +413,8 @@ e_mail_reader_open_selected (EMailReader *reader) const gchar *uid = views->pdata[ii]; GtkWidget *browser; - browser = e_mail_browser_new (backend); + browser = e_mail_browser_new (backend, folder, uid, + EM_FORMAT_WRITE_MODE_NORMAL); e_mail_reader_set_folder (E_MAIL_READER (browser), folder); e_mail_reader_set_message (E_MAIL_READER (browser), uid); copy_tree_state (reader, E_MAIL_READER (browser)); @@ -430,93 +432,74 @@ e_mail_reader_open_selected (EMailReader *reader) return ii; } -/* Helper for e_mail_reader_print() */ -static void -mail_reader_print_cb (CamelFolder *folder, - GAsyncResult *result, - AsyncContext *context) +static gboolean +destroy_printing_activity (EActivity *activity) { - EAlertSink *alert_sink; - CamelMimeMessage *message; - EMFormatHTML *formatter; - EMFormatHTMLPrint *html_print; - GError *error = NULL; - - alert_sink = e_activity_get_alert_sink (context->activity); + g_object_unref (activity); - message = camel_folder_get_message_finish (folder, result, &error); + return FALSE; +} - if (e_activity_handle_cancellation (context->activity, error)) { - g_warn_if_fail (message == NULL); - async_context_free (context); - g_error_free (error); - return; +static void +printing_done_cb (EMailPrinter *printer, + GtkPrintOperation *operation, + GtkPrintOperationResult result, + gpointer user_data) +{ + EActivity *activity = user_data; - } else if (error != NULL) { - g_warn_if_fail (message == NULL); - e_alert_submit ( - alert_sink, "mail:no-retrieve-message", - error->message, NULL); - async_context_free (context); - g_error_free (error); - return; - } + if (result == GTK_PRINT_OPERATION_RESULT_ERROR) { - g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message)); + EAlertSink *alert_sink; + GError *error = NULL; - formatter = e_mail_reader_get_formatter (context->reader); + alert_sink = e_activity_get_alert_sink (activity); + gtk_print_operation_get_error (operation, &error); - html_print = em_format_html_print_new ( - formatter, context->print_action); - em_format_merge_handler ( - EM_FORMAT (html_print), EM_FORMAT (formatter)); - em_format_html_print_message ( - html_print, message, folder, context->message_uid); - g_object_unref (html_print); + if (error != NULL) { + e_alert_submit (alert_sink, "mail:printing-failed", + error->message, NULL); + g_error_free (error); + } - g_object_unref (message); + g_object_unref (activity); + g_object_unref (printer); + return; + } - e_activity_set_state (context->activity, E_ACTIVITY_COMPLETED); + /* Set activity as completed, and keep it displayed for a few seconds + * so that user can actually see the the printing was sucesfully finished. */ + e_activity_set_state (activity, E_ACTIVITY_COMPLETED); + g_timeout_add_seconds_full (G_PRIORITY_DEFAULT, 3, + (GSourceFunc) destroy_printing_activity, activity, NULL); - async_context_free (context); + g_object_unref (printer); } void e_mail_reader_print (EMailReader *reader, GtkPrintOperationAction action) { + EMailDisplay *display; + EMailPrinter *printer; + EMFormatHTML *formatter; EActivity *activity; - AsyncContext *context; GCancellable *cancellable; - CamelFolder *folder; - GPtrArray *uids; - const gchar *message_uid; g_return_if_fail (E_IS_MAIL_READER (reader)); - folder = e_mail_reader_get_folder (reader); - g_return_if_fail (CAMEL_IS_FOLDER (folder)); - - /* XXX Learn to handle len > 1. */ - uids = e_mail_reader_get_selected_uids (reader); - g_return_if_fail (uids != NULL && uids->len == 1); - message_uid = g_ptr_array_index (uids, 0); + display = e_mail_reader_get_mail_display (reader); + formatter = e_mail_display_get_formatter (display); activity = e_mail_reader_new_activity (reader); + e_activity_set_text (activity, _("Printing")); + e_activity_set_state (activity, E_ACTIVITY_RUNNING); cancellable = e_activity_get_cancellable (activity); - context = g_slice_new0 (AsyncContext); - context->activity = activity; - context->reader = g_object_ref (reader); - context->message_uid = g_strdup (message_uid); - context->print_action = action; - - camel_folder_get_message ( - folder, message_uid, G_PRIORITY_DEFAULT, - cancellable, (GAsyncReadyCallback) - mail_reader_print_cb, context); - - em_utils_uids_free (uids); + printer = e_mail_printer_new (formatter); + g_signal_connect (printer, "done", + G_CALLBACK (printing_done_cb), activity); + e_mail_printer_print (printer, FALSE, cancellable); } static void @@ -763,6 +746,7 @@ mail_reader_get_message_ready_cb (CamelFolder *folder, EMailBackend *backend; EAlertSink *alert_sink; EMFormatHTML *formatter; + EMailDisplay *display; CamelMimeMessage *message; GError *error = NULL; @@ -790,8 +774,8 @@ mail_reader_get_message_ready_cb (CamelFolder *folder, backend = e_mail_reader_get_backend (context->reader); shell = e_shell_backend_get_shell (E_SHELL_BACKEND (backend)); - - formatter = e_mail_reader_get_formatter (context->reader); + display = e_mail_reader_get_mail_display (context->reader); + formatter = e_mail_display_get_formatter (display); em_utils_reply_to_message ( shell, message, @@ -814,6 +798,7 @@ e_mail_reader_reply_to_message (EMailReader *reader, EShell *shell; EMailBackend *backend; EShellBackend *shell_backend; + EMailDisplay *display; EMFormatHTML *formatter; GtkWidget *message_list; CamelMimeMessage *new_message; @@ -834,14 +819,15 @@ e_mail_reader_reply_to_message (EMailReader *reader, backend = e_mail_reader_get_backend (reader); folder = e_mail_reader_get_folder (reader); - formatter = e_mail_reader_get_formatter (reader); + display = e_mail_reader_get_mail_display (reader); + formatter = e_mail_display_get_formatter (display); message_list = e_mail_reader_get_message_list (reader); reply_style = e_mail_reader_get_reply_style (reader); shell_backend = E_SHELL_BACKEND (backend); shell = e_shell_backend_get_shell (shell_backend); - web_view = em_format_html_get_web_view (formatter); + web_view = E_WEB_VIEW (display); if (reply_type == E_MAIL_REPLY_TO_RECIPIENT) { const gchar *uri; @@ -885,7 +871,8 @@ e_mail_reader_reply_to_message (EMailReader *reader, if (!e_web_view_is_selection_active (web_view)) goto whole_message; - selection = gtk_html_get_selection_html (GTK_HTML (web_view), &length); + selection = e_web_view_get_selection_html (web_view); + length = strlen (selection); if (selection == NULL || *selection == '\0') goto whole_message; @@ -1397,20 +1384,18 @@ static void headers_changed_cb (GConfClient *client, guint cnxn_id, GConfEntry *entry, - EMailReader *reader) + EMFormat *emf) { - EMFormatHTML *formatter; GSList *header_config_list, *p; g_return_if_fail (client != NULL); - g_return_if_fail (reader != NULL); - - formatter = e_mail_reader_get_formatter (reader); + g_return_if_fail (EM_IS_FORMAT (emf)); header_config_list = gconf_client_get_list ( client, "/apps/evolution/mail/display/headers", GCONF_VALUE_STRING, NULL); - em_format_clear_headers (EM_FORMAT (formatter)); + + em_format_clear_headers (emf); for (p = header_config_list; p; p = g_slist_next (p)) { EMailReaderHeader *h; gchar *xml = (gchar *) p->data; @@ -1418,21 +1403,20 @@ headers_changed_cb (GConfClient *client, h = e_mail_reader_header_from_xml (xml); if (h && h->enabled) em_format_add_header ( - EM_FORMAT (formatter), - h->name, EM_FORMAT_HEADER_BOLD); + emf, h->name, NULL, EM_FORMAT_HEADER_BOLD); e_mail_reader_header_free (h); } if (!header_config_list) - em_format_default_headers (EM_FORMAT (formatter)); + em_format_default_headers (emf); g_slist_foreach (header_config_list, (GFunc) g_free, NULL); g_slist_free (header_config_list); /* force a redraw */ - if (EM_FORMAT (formatter)->message) - em_format_queue_redraw (EM_FORMAT (formatter)); + if (emf->message) + em_format_redraw (emf); } static void @@ -1458,7 +1442,8 @@ remove_header_notify_cb (gpointer data) * updates the EMFormat whenever it changes and on this call too. **/ void -e_mail_reader_connect_headers (EMailReader *reader) +e_mail_reader_connect_headers (EMailReader *reader, + EMFormat *emf) { GConfClient *client; guint notify_id; @@ -1471,13 +1456,13 @@ e_mail_reader_connect_headers (EMailReader *reader) notify_id = gconf_client_notify_add ( client, "/apps/evolution/mail/display/headers", (GConfClientNotifyFunc) headers_changed_cb, - reader, NULL, NULL); + emf, NULL, NULL); g_object_set_data_full ( - G_OBJECT (reader), "reader-header-notify-id", + G_OBJECT (emf), "reader-header-notify-id", GINT_TO_POINTER (notify_id), remove_header_notify_cb); - headers_changed_cb (client, 0, NULL, reader); + headers_changed_cb (client, 0, NULL, emf); g_object_unref (client); } diff --git a/mail/e-mail-reader-utils.h b/mail/e-mail-reader-utils.h index bb4671ba21..6913d0964c 100644 --- a/mail/e-mail-reader-utils.h +++ b/mail/e-mail-reader-utils.h @@ -67,7 +67,8 @@ EMailReaderHeader * gchar * e_mail_reader_header_to_xml (EMailReaderHeader *header); void e_mail_reader_header_free (EMailReaderHeader *header); -void e_mail_reader_connect_headers (EMailReader *reader); +void e_mail_reader_connect_headers (EMailReader *reader, + EMFormat *emf); G_END_DECLS diff --git a/mail/e-mail-reader.c b/mail/e-mail-reader.c index 53d4c31b84..0106b6ed53 100644 --- a/mail/e-mail-reader.c +++ b/mail/e-mail-reader.c @@ -48,13 +48,13 @@ #include "mail/e-mail-backend.h" #include "mail/e-mail-browser.h" -#include "mail/e-mail-display.h" #include "mail/e-mail-reader-utils.h" #include "mail/e-mail-view.h" #include "mail/em-composer-utils.h" #include "mail/em-event.h" #include "mail/em-folder-selector.h" #include "mail/em-folder-tree.h" +#include "mail/em-format-html-display.h" #include "mail/em-utils.h" #include "mail/mail-autofilter.h" #include "mail/mail-vfolder-ui.h" @@ -70,6 +70,8 @@ ((EMailReaderPrivate *) g_object_get_qdata \ (G_OBJECT (obj), quark_private)) +#define d(x) + typedef struct _EMailReaderClosure EMailReaderClosure; typedef struct _EMailReaderPrivate EMailReaderPrivate; @@ -125,6 +127,13 @@ static guint signals[LAST_SIGNAL]; G_DEFINE_INTERFACE (EMailReader, e_mail_reader, G_TYPE_INITIALLY_UNOWNED) static void +mail_reader_set_display_formatter_for_message (EMailReader *reader, + EMailDisplay *display, + const gchar *message_uid, + CamelMimeMessage *message, + CamelFolder *folder); + +static void mail_reader_closure_free (EMailReaderClosure *closure) { if (closure->reader != NULL) @@ -201,7 +210,6 @@ action_add_to_address_book_cb (GtkAction *action, EShell *shell; EMailBackend *backend; EShellBackend *shell_backend; - EMFormatHTML *formatter; CamelInternetAddress *cia; EWebView *web_view; CamelURL *curl; @@ -211,9 +219,10 @@ action_add_to_address_book_cb (GtkAction *action, /* This action is defined in EMailDisplay. */ backend = e_mail_reader_get_backend (reader); - formatter = e_mail_reader_get_formatter (reader); - web_view = em_format_html_get_web_view (formatter); + web_view = E_WEB_VIEW (e_mail_reader_get_mail_display (reader)); + if (!web_view) + return; uri = e_web_view_get_selected_uri (web_view); g_return_if_fail (uri != NULL); @@ -247,21 +256,136 @@ exit: } static void +attachment_load_finish (EAttachment *attachment, + GAsyncResult *result, + GFile *file) +{ + EShell *shell; + GtkWindow *parent; + + e_attachment_load_finish (attachment, result, NULL); + + shell = e_shell_get_default (); + parent = e_shell_get_active_window (shell); + + e_attachment_save_async ( + attachment, file, (GAsyncReadyCallback) + e_attachment_save_handle_error, parent); + + g_object_unref (file); +} + +static void +action_mail_image_save_cb (GtkAction *action, + EMailReader *reader) +{ + EMailDisplay *display; + EWebView *web_view; + EMFormat *emf; + const gchar *image_src; + CamelMimePart *part; + EAttachment *attachment; + GFile *file; + + display = e_mail_reader_get_mail_display (reader); + web_view = E_WEB_VIEW (display); + + if (!E_IS_WEB_VIEW (web_view)) + return; + + image_src = e_web_view_get_cursor_image_src (web_view); + if (!image_src) + return; + + emf = EM_FORMAT (e_mail_display_get_formatter (display)); + g_return_if_fail (emf != NULL); + g_return_if_fail (emf->message != NULL); + + if (g_str_has_prefix (image_src, "cid:")) { + part = camel_mime_message_get_part_by_content_id ( + emf->message, image_src + 4); + g_return_if_fail (part != NULL); + + g_object_ref (part); + } else { + CamelStream *image_stream; + CamelDataWrapper *dw; + CamelDataCache *cache; + const gchar *filename; + const gchar *user_cache_dir; + + /* Open cache and find the file there */ + user_cache_dir = e_get_user_cache_dir (); + cache = camel_data_cache_new (user_cache_dir, NULL); + image_stream = camel_data_cache_get (cache, "http", image_src, NULL); + if (!image_stream) { + g_object_unref (cache); + return; + } + + filename = strrchr (image_src, '/'); + if (filename && strchr (filename, '?')) + filename = NULL; + else if (filename) + filename = filename + 1; + + part = camel_mime_part_new (); + if (filename) + camel_mime_part_set_filename (part, filename); + + dw = camel_data_wrapper_new (); + camel_data_wrapper_set_mime_type ( + dw, "application/octet-stream"); + camel_data_wrapper_construct_from_stream_sync ( + dw, image_stream, NULL, NULL); + camel_medium_set_content (CAMEL_MEDIUM (part), dw); + g_object_unref (dw); + + camel_mime_part_set_encoding ( + part, CAMEL_TRANSFER_ENCODING_BASE64); + + g_object_unref (image_stream); + g_object_unref (cache); + } + + file = e_shell_run_save_dialog ( + e_shell_get_default (), + _("Save Image"), camel_mime_part_get_filename (part), + NULL, NULL, NULL); + if (file == NULL) { + g_object_unref (part); + return; + } + + attachment = e_attachment_new (); + e_attachment_set_mime_part (attachment, part); + + e_attachment_load_async ( + attachment, (GAsyncReadyCallback) + attachment_load_finish, file); + + g_object_unref (part); +} + +static void action_mail_charset_cb (GtkRadioAction *action, GtkRadioAction *current, EMailReader *reader) { + EMailDisplay *display; EMFormatHTML *formatter; const gchar *charset; if (action != current) return; - formatter = e_mail_reader_get_formatter (reader); + display = e_mail_reader_get_mail_display (reader); + formatter = e_mail_display_get_formatter (display); charset = g_object_get_data (G_OBJECT (action), "charset"); /* Charset for "Default" action will be NULL. */ - em_format_set_charset (EM_FORMAT (formatter), charset); + if (formatter) + em_format_set_charset (EM_FORMAT (formatter), charset); } static void @@ -438,38 +562,38 @@ static void action_mail_flag_clear_cb (GtkAction *action, EMailReader *reader) { - EMFormatHTML *formatter; + EMailDisplay *display; CamelFolder *folder; GtkWindow *window; GPtrArray *uids; folder = e_mail_reader_get_folder (reader); - formatter = e_mail_reader_get_formatter (reader); + display = e_mail_reader_get_mail_display (reader); uids = e_mail_reader_get_selected_uids (reader); window = e_mail_reader_get_window (reader); em_utils_flag_for_followup_clear (window, folder, uids); - em_format_queue_redraw (EM_FORMAT (formatter)); + e_mail_display_reload (display); } static void action_mail_flag_completed_cb (GtkAction *action, EMailReader *reader) { - EMFormatHTML *formatter; + EMailDisplay *display; CamelFolder *folder; GtkWindow *window; GPtrArray *uids; folder = e_mail_reader_get_folder (reader); - formatter = e_mail_reader_get_formatter (reader); + display = e_mail_reader_get_mail_display (reader); uids = e_mail_reader_get_selected_uids (reader); window = e_mail_reader_get_window (reader); em_utils_flag_for_followup_completed (window, folder, uids); - em_format_queue_redraw (EM_FORMAT (formatter)); + e_mail_display_reload (display); } static void @@ -663,11 +787,11 @@ static void action_mail_load_images_cb (GtkAction *action, EMailReader *reader) { - EMFormatHTML *formatter; + EMailDisplay *display; - formatter = e_mail_reader_get_formatter (reader); + display = e_mail_reader_get_mail_display (reader); - em_format_html_load_images (formatter); + e_mail_display_load_images (display); } static void @@ -1576,20 +1700,14 @@ static void action_mail_show_all_headers_cb (GtkToggleAction *action, EMailReader *reader) { - EMFormatHTML *formatter; - EMFormatMode mode; + EMailDisplay *display; - formatter = e_mail_reader_get_formatter (reader); - - if (!formatter) - return; + display = e_mail_reader_get_mail_display (reader); if (gtk_toggle_action_get_active (action)) - mode = EM_FORMAT_MODE_ALLHEADERS; + e_mail_display_set_mode (display, EM_FORMAT_WRITE_MODE_ALL_HEADERS); else - mode = EM_FORMAT_MODE_NORMAL; - - em_format_set_mode (EM_FORMAT (formatter), mode); + e_mail_display_set_mode (display, EM_FORMAT_WRITE_MODE_NORMAL); } static void @@ -1597,8 +1715,9 @@ action_mail_show_source_cb (GtkAction *action, EMailReader *reader) { EMailBackend *backend; - EMFormatHTML *formatter; + EMailDisplay *display; CamelFolder *folder; + CamelMimeMessage *message; GtkWidget *browser; GPtrArray *uids; const gchar *message_uid; @@ -1610,16 +1729,16 @@ action_mail_show_source_cb (GtkAction *action, g_return_if_fail (uids != NULL && uids->len == 1); message_uid = g_ptr_array_index (uids, 0); - browser = e_mail_browser_new (backend); - reader = E_MAIL_READER (browser); - formatter = e_mail_reader_get_formatter (reader); + message = camel_folder_get_message_sync (folder, message_uid, NULL, NULL); - if (formatter != NULL) - em_format_set_mode ( - EM_FORMAT (formatter), EM_FORMAT_MODE_SOURCE); + browser = e_mail_browser_new (backend, NULL, NULL, EM_FORMAT_WRITE_MODE_SOURCE); + e_mail_reader_set_folder (E_MAIL_READER (browser), folder); + e_mail_reader_set_message (E_MAIL_READER (browser), message_uid); + + display = e_mail_reader_get_mail_display (E_MAIL_READER (browser)); + mail_reader_set_display_formatter_for_message ( + E_MAIL_READER (browser), display, message_uid, message, folder); - e_mail_reader_set_folder (reader, folder); - e_mail_reader_set_message (reader, message_uid); gtk_widget_show (browser); em_utils_uids_free (uids); @@ -1671,39 +1790,33 @@ static void action_mail_zoom_100_cb (GtkAction *action, EMailReader *reader) { - EMFormatHTML *formatter; - EWebView *web_view; + EMailDisplay *display; - formatter = e_mail_reader_get_formatter (reader); - web_view = em_format_html_get_web_view (formatter); + display = e_mail_reader_get_mail_display (reader); - e_web_view_zoom_100 (web_view); + webkit_web_view_set_zoom_level (WEBKIT_WEB_VIEW (display), 1.0); } static void action_mail_zoom_in_cb (GtkAction *action, EMailReader *reader) { - EMFormatHTML *formatter; - EWebView *web_view; + EMailDisplay *display; - formatter = e_mail_reader_get_formatter (reader); - web_view = em_format_html_get_web_view (formatter); + display = e_mail_reader_get_mail_display (reader); - e_web_view_zoom_in (web_view); + webkit_web_view_zoom_in (WEBKIT_WEB_VIEW (display)); } static void action_mail_zoom_out_cb (GtkAction *action, EMailReader *reader) { - EMFormatHTML *formatter; - EWebView *web_view; + EMailDisplay *display; - formatter = e_mail_reader_get_formatter (reader); - web_view = em_format_html_get_web_view (formatter); + display = e_mail_reader_get_mail_display (reader); - e_web_view_zoom_out (web_view); + webkit_web_view_zoom_out (WEBKIT_WEB_VIEW (display)); } static void @@ -1712,7 +1825,6 @@ action_search_folder_recipient_cb (GtkAction *action, { EMailBackend *backend; EMailSession *session; - EMFormatHTML *formatter; EWebView *web_view; CamelFolder *folder; CamelURL *curl; @@ -1721,9 +1833,7 @@ action_search_folder_recipient_cb (GtkAction *action, /* This action is defined in EMailDisplay. */ folder = e_mail_reader_get_folder (reader); - formatter = e_mail_reader_get_formatter (reader); - - web_view = em_format_html_get_web_view (formatter); + web_view = E_WEB_VIEW (e_mail_reader_get_mail_display (reader)); uri = e_web_view_get_selected_uri (web_view); g_return_if_fail (uri != NULL); @@ -1753,7 +1863,6 @@ action_search_folder_sender_cb (GtkAction *action, { EMailBackend *backend; EMailSession *session; - EMFormatHTML *formatter; EWebView *web_view; CamelFolder *folder; CamelURL *curl; @@ -1762,9 +1871,7 @@ action_search_folder_sender_cb (GtkAction *action, /* This action is defined in EMailDisplay. */ folder = e_mail_reader_get_folder (reader); - formatter = e_mail_reader_get_formatter (reader); - - web_view = em_format_html_get_web_view (formatter); + web_view = E_WEB_VIEW (e_mail_reader_get_mail_display (reader)); uri = e_web_view_get_selected_uri (web_view); g_return_if_fail (uri != NULL); @@ -2494,6 +2601,7 @@ mail_reader_message_seen_cb (EMailReaderClosure *closure) EMailReader *reader; GtkWidget *message_list; EMFormatHTML *formatter; + EMailDisplay *display; CamelMimeMessage *message; const gchar *current_uid; const gchar *message_uid; @@ -2502,19 +2610,20 @@ mail_reader_message_seen_cb (EMailReaderClosure *closure) reader = closure->reader; message_uid = closure->message_uid; - formatter = e_mail_reader_get_formatter (reader); + display = e_mail_reader_get_mail_display (reader); + formatter = e_mail_display_get_formatter (display); message_list = e_mail_reader_get_message_list (reader); if (e_tree_is_dragging (E_TREE (message_list))) return FALSE; - current_uid = EM_FORMAT (formatter)->uid; - uid_is_current &= (g_strcmp0 (current_uid, message_uid) == 0); - current_uid = MESSAGE_LIST (message_list)->cursor_uid; uid_is_current &= (g_strcmp0 (current_uid, message_uid) == 0); - message = EM_FORMAT (formatter)->message; + if (formatter) + message = EM_FORMAT (formatter)->message; + else + message = NULL; if (uid_is_current && message != NULL) g_signal_emit ( @@ -2535,7 +2644,6 @@ schedule_timeout_mark_seen (EMailReader *reader) gboolean schedule_timeout; gint timeout_interval; const gchar *message_uid; - backend = e_mail_reader_get_backend (reader); message_list = MESSAGE_LIST (e_mail_reader_get_message_list (reader)); shell_backend = E_SHELL_BACKEND (backend); @@ -2669,56 +2777,47 @@ static gboolean mail_reader_message_selected_timeout_cb (EMailReader *reader) { EMailReaderPrivate *priv; - EMFormatHTML *formatter; + EMailDisplay *display; GtkWidget *message_list; - EPreviewPane *preview_pane; CamelFolder *folder; const gchar *cursor_uid; const gchar *format_uid; + EMFormat *formatter; priv = E_MAIL_READER_GET_PRIVATE (reader); folder = e_mail_reader_get_folder (reader); - formatter = e_mail_reader_get_formatter (reader); message_list = e_mail_reader_get_message_list (reader); - preview_pane = e_mail_reader_get_preview_pane (reader); + display = e_mail_reader_get_mail_display (reader); + formatter = EM_FORMAT (e_mail_display_get_formatter (display)); cursor_uid = MESSAGE_LIST (message_list)->cursor_uid; - format_uid = EM_FORMAT (formatter)->uid; - - e_preview_pane_clear_alerts (preview_pane); + format_uid = formatter ? formatter->message_uid : NULL; if (MESSAGE_LIST (message_list)->last_sel_single) { - gboolean preview_visible; + GtkWidget *widget; + gboolean display_visible; gboolean selected_uid_changed; /* Decide whether to download the full message now. */ + widget = GTK_WIDGET (display); + display_visible = gtk_widget_get_mapped (widget); - preview_visible = - gtk_widget_get_mapped (GTK_WIDGET (preview_pane)); - selected_uid_changed = g_strcmp0 (cursor_uid, format_uid); + selected_uid_changed = (g_strcmp0 (cursor_uid, format_uid) != 0); - if (preview_visible && selected_uid_changed) { + if (display_visible && selected_uid_changed) { EMailReaderClosure *closure; GCancellable *cancellable; EActivity *activity; - EWebView *web_view; gchar *string; - web_view = e_preview_pane_get_web_view (preview_pane); - - string = g_strdup_printf ( - _("Retrieving message '%s'"), cursor_uid); -#if HAVE_CLUTTER - if (!e_shell_get_express_mode (e_shell_get_default ())) - e_web_view_load_string (web_view, string); -#else - e_web_view_load_string (web_view, string); -#endif + string = g_strdup_printf (_("Retrieving message '%s'"), cursor_uid); + e_mail_display_set_status (display, string); g_free (string); activity = e_mail_reader_new_activity (reader); + e_activity_set_text (activity, _("Retrieving message")); cancellable = e_activity_get_cancellable (activity); closure = g_slice_new0 (EMailReaderClosure); @@ -2736,9 +2835,6 @@ mail_reader_message_selected_timeout_cb (EMailReader *reader) priv->retrieving_message = g_object_ref (cancellable); } } else { - /* FIXME Need to pass a GCancellable. */ - em_format_format ( - EM_FORMAT (formatter), NULL, NULL, NULL, NULL); priv->restoring_message_selection = FALSE; } @@ -2869,7 +2965,7 @@ mail_reader_set_folder (EMailReader *reader, CamelFolder *folder) { EMailReaderPrivate *priv; - EMFormatHTML *formatter; + EMailDisplay *display; CamelFolder *previous_folder; GtkWidget *message_list; EMailBackend *backend; @@ -2879,7 +2975,7 @@ mail_reader_set_folder (EMailReader *reader, priv = E_MAIL_READER_GET_PRIVATE (reader); backend = e_mail_reader_get_backend (reader); - formatter = e_mail_reader_get_formatter (reader); + display = e_mail_reader_get_mail_display (reader); message_list = e_mail_reader_get_message_list (reader); previous_folder = e_mail_reader_get_folder (reader); @@ -2899,8 +2995,7 @@ mail_reader_set_folder (EMailReader *reader, em_utils_folder_is_outbox (folder) || em_utils_folder_is_sent (folder)); - /* FIXME Need to pass a GCancellable. */ - em_format_format (EM_FORMAT (formatter), NULL, NULL, NULL, NULL); + e_web_view_clear (E_WEB_VIEW (display)); priv->folder_was_just_selected = (folder != NULL); @@ -2931,18 +3026,128 @@ mail_reader_folder_loaded (EMailReader *reader) e_mail_reader_update_actions (reader, state); } +struct _formatter_weak_ref_closure { + GHashTable *formatters; + gchar *mail_uri; +}; + +static void +formatter_weak_ref_cb (struct _formatter_weak_ref_closure *data, + EMFormat *formatter) +{ + /* When this callback is called, the formatter is being finalized + * so we only remove it from the formatters table. */ + g_hash_table_remove (data->formatters, + data->mail_uri); + + d(printf("Destroying formatter %p (%s)\n", formatter, data->mail_uri)); + + /* Destroying the formatter will prevent this callback + * being called, so we can remove the closure data as well */ + g_hash_table_unref (data->formatters); + g_free (data->mail_uri); + g_free (data); +} + +struct format_parser_async_closure_ { + EMailDisplay *display; + EActivity *activity; +}; + +static void +format_parser_async_done_cb (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + EMFormat *emf = EM_FORMAT (source); + struct format_parser_async_closure_ *closure = user_data; + + e_mail_display_set_formatter (closure->display, EM_FORMAT_HTML (emf)); + e_mail_display_load (closure->display, emf->uri_base); + + g_object_unref (closure->activity); + g_object_unref (closure->display); + g_free (closure); + + g_object_unref (result); + + /* Remove the reference added when formatter was created, + * so that only owners are EMailDisplays */ + g_object_unref (emf); +} + +static void +mail_reader_set_display_formatter_for_message (EMailReader *reader, + EMailDisplay *display, + const gchar *message_uid, + CamelMimeMessage *message, + CamelFolder *folder) +{ + SoupSession *session; + GHashTable *formatters; + EMFormat *formatter; + gchar *mail_uri; + + mail_uri = em_format_build_mail_uri (folder, message_uid, NULL, NULL); + + session = webkit_get_default_session (); + formatters = g_object_get_data (G_OBJECT (session), "formatters"); + if (!formatters) { + formatters = g_hash_table_new_full (g_str_hash, g_str_equal, + (GDestroyNotify) g_free, NULL); + g_object_set_data (G_OBJECT (session), "formatters", formatters); + } + + if ((formatter = g_hash_table_lookup (formatters, mail_uri)) == NULL) { + struct _formatter_weak_ref_closure *formatter_data = + g_new0 (struct _formatter_weak_ref_closure, 1); + + struct format_parser_async_closure_ *closure; + + formatter_data->formatters = g_hash_table_ref (formatters); + formatter_data->mail_uri = g_strdup (mail_uri); + + formatter = EM_FORMAT (em_format_html_display_new ()); + + /* When no EMailDisplay holds reference to the formatter, then + * the formatter can be destroyed. */ + g_object_weak_ref (G_OBJECT (formatter), + (GWeakNotify) formatter_weak_ref_cb, formatter_data); + + formatter->message_uid = g_strdup (message_uid); + formatter->uri_base = g_strdup (mail_uri); + + e_mail_reader_connect_headers (reader, formatter); + + closure = g_new0 (struct format_parser_async_closure_, 1); + closure->activity = e_mail_reader_new_activity (reader); + e_activity_set_text (closure->activity, _("Parsing message")); + closure->display = g_object_ref (display); + + em_format_parse_async (formatter, message, folder, + e_activity_get_cancellable (closure->activity), + format_parser_async_done_cb, closure); + + /* Don't free the mail_uri!! */ + g_hash_table_insert (formatters, mail_uri, formatter); + } else { + e_mail_display_set_formatter (display, EM_FORMAT_HTML (formatter)); + e_mail_display_load (display, formatter->uri_base); + + g_free (mail_uri); + } +} + static void mail_reader_message_loaded (EMailReader *reader, const gchar *message_uid, CamelMimeMessage *message) { EMailReaderPrivate *priv; - EMFormatHTML *formatter; GtkWidget *message_list; EMailBackend *backend; CamelFolder *folder; - EWebView *web_view; - EPreviewPane *preview_pane; + EMailDisplay *display; EShellBackend *shell_backend; EShell *shell; EMEvent *event; @@ -2953,15 +3158,12 @@ mail_reader_message_loaded (EMailReader *reader, folder = e_mail_reader_get_folder (reader); backend = e_mail_reader_get_backend (reader); - formatter = e_mail_reader_get_formatter (reader); + display = e_mail_reader_get_mail_display (reader); message_list = e_mail_reader_get_message_list (reader); - preview_pane = e_mail_reader_get_preview_pane (reader); shell_backend = E_SHELL_BACKEND (backend); shell = e_shell_backend_get_shell (shell_backend); - web_view = e_preview_pane_get_web_view (preview_pane); - /** @Event: message.reading * @Title: Viewing a message * @Target: EMEventTargetMessage @@ -2975,10 +3177,8 @@ mail_reader_message_loaded (EMailReader *reader, (EEvent *) event, "message.reading", (EEventTarget *) target); - /* FIXME Need to pass a GCancellable. */ - em_format_format ( - EM_FORMAT (formatter), folder, - message_uid, message, NULL); + mail_reader_set_display_formatter_for_message ( + reader, display, message_uid, message, folder); /* Reset the shell view icon. */ e_shell_event (shell, "mail-icon", (gpointer) "evolution-mail"); @@ -2996,7 +3196,7 @@ mail_reader_message_loaded (EMailReader *reader, g_clear_error (&error); } else if (error != NULL) { e_alert_submit ( - E_ALERT_SINK (web_view), + E_ALERT_SINK (display), "mail:no-retrieve-message", error->message, NULL); g_error_free (error); @@ -3597,14 +3797,13 @@ e_mail_reader_init (EMailReader *reader, gboolean init_actions, gboolean connect_signals) { - EMFormatHTML *formatter; EMenuToolAction *menu_tool_action; - EWebView *web_view; GtkActionGroup *action_group; GtkWidget *message_list; GtkAction *action; gboolean sensitive; const gchar *action_name; + EMailDisplay *display; #ifndef G_OS_WIN32 GSettings *settings; @@ -3612,10 +3811,8 @@ e_mail_reader_init (EMailReader *reader, g_return_if_fail (E_IS_MAIL_READER (reader)); - formatter = e_mail_reader_get_formatter (reader); message_list = e_mail_reader_get_message_list (reader); - - web_view = em_format_html_get_web_view (formatter); + display = e_mail_reader_get_mail_display (reader); if (!init_actions) goto connect_signals; @@ -3755,30 +3952,38 @@ e_mail_reader_init (EMailReader *reader, gtk_action_set_is_important (action, TRUE); gtk_action_set_short_label (action, _("Reply")); + display = e_mail_reader_get_mail_display (reader); + action_name = "add-to-address-book"; - action = e_web_view_get_action (web_view, action_name); + action = e_mail_display_get_action (display, action_name); g_signal_connect ( action, "activate", G_CALLBACK (action_add_to_address_book_cb), reader); action_name = "send-reply"; - action = e_web_view_get_action (web_view, action_name); + action = e_mail_display_get_action (display, action_name); g_signal_connect ( action, "activate", G_CALLBACK (action_mail_reply_recipient_cb), reader); action_name = "search-folder-recipient"; - action = e_web_view_get_action (web_view, action_name); + action = e_mail_display_get_action (display, action_name); g_signal_connect ( action, "activate", G_CALLBACK (action_search_folder_recipient_cb), reader); action_name = "search-folder-sender"; - action = e_web_view_get_action (web_view, action_name); + action = e_mail_display_get_action (display, action_name); g_signal_connect ( action, "activate", G_CALLBACK (action_search_folder_sender_cb), reader); + action_name = "image-save"; + action = e_mail_display_get_action (display, action_name); + g_signal_connect ( + action, "activate", + G_CALLBACK (action_mail_image_save_cb), reader); + #ifndef G_OS_WIN32 /* Lockdown integration. */ @@ -3821,7 +4026,7 @@ e_mail_reader_init (EMailReader *reader, g_object_bind_property ( action, "active", - web_view, "caret-mode", + display, "caret-mode", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); @@ -3832,7 +4037,7 @@ connect_signals: /* Connect signals. */ g_signal_connect_swapped ( - web_view, "key-press-event", + display, "key-press-event", G_CALLBACK (mail_reader_key_press_event_cb), reader); g_signal_connect_swapped ( @@ -4157,17 +4362,17 @@ e_mail_reader_get_backend (EMailReader *reader) return interface->get_backend (reader); } -EMFormatHTML * -e_mail_reader_get_formatter (EMailReader *reader) +EMailDisplay * +e_mail_reader_get_mail_display (EMailReader *reader) { EMailReaderInterface *interface; g_return_val_if_fail (E_IS_MAIL_READER (reader), NULL); interface = E_MAIL_READER_GET_INTERFACE (reader); - g_return_val_if_fail (interface->get_formatter != NULL, NULL); + g_return_val_if_fail (interface->get_mail_display != NULL, NULL); - return interface->get_formatter (reader); + return interface->get_mail_display (reader); } gboolean diff --git a/mail/e-mail-reader.h b/mail/e-mail-reader.h index 233e52b1f9..6f03fc0cfe 100644 --- a/mail/e-mail-reader.h +++ b/mail/e-mail-reader.h @@ -31,7 +31,7 @@ #include <camel/camel.h> #include <libevolution-utils/e-alert-sink.h> #include <mail/e-mail-backend.h> -#include <mail/em-format-html.h> +#include <mail/e-mail-display.h> #include <misc/e-preview-pane.h> /* Standard GObject macros */ @@ -96,7 +96,7 @@ struct _EMailReaderInterface { EMailReaderActionGroup group); EAlertSink * (*get_alert_sink) (EMailReader *reader); EMailBackend * (*get_backend) (EMailReader *reader); - EMFormatHTML * (*get_formatter) (EMailReader *reader); + EMailDisplay * (*get_mail_display) (EMailReader *reader); gboolean (*get_hide_deleted) (EMailReader *reader); GtkWidget * (*get_message_list) (EMailReader *reader); GtkMenu * (*get_popup_menu) (EMailReader *reader); @@ -141,7 +141,7 @@ GtkActionGroup * EMailReaderActionGroup group); EAlertSink * e_mail_reader_get_alert_sink (EMailReader *reader); EMailBackend * e_mail_reader_get_backend (EMailReader *reader); -EMFormatHTML * e_mail_reader_get_formatter (EMailReader *reader); +EMailDisplay * e_mail_reader_get_mail_display (EMailReader *reader); gboolean e_mail_reader_get_hide_deleted (EMailReader *reader); GtkWidget * e_mail_reader_get_message_list (EMailReader *reader); guint e_mail_reader_open_selected_mail diff --git a/mail/e-mail-request.c b/mail/e-mail-request.c new file mode 100644 index 0000000000..34f1845a3c --- /dev/null +++ b/mail/e-mail-request.c @@ -0,0 +1,771 @@ +#define LIBSOUP_USE_UNSTABLE_REQUEST_API + +#include "e-mail-request.h" + +#include <libsoup/soup.h> +#include <libsoup/soup-requester.h> +#include <libsoup/soup-request-http.h> + +#include <glib/gi18n.h> +#include <camel/camel.h> + +#include "em-format-html.h" + +#include <e-util/e-icon-factory.h> +#include <e-util/e-util.h> + +#define d(x) +#define dd(x) + +G_DEFINE_TYPE (EMailRequest, e_mail_request, SOUP_TYPE_REQUEST) + +struct _EMailRequestPrivate { + EMFormatHTML *efh; + + CamelStream *output_stream; + EMFormatPURI *puri; + gchar *mime_type; + + gint content_length; + + GHashTable *uri_query; + + gchar *ret_mime_type; +}; + +static void +handle_mail_request (GSimpleAsyncResult *res, + GObject *object, + GCancellable *cancellable) +{ + EMailRequest *request = E_MAIL_REQUEST (object); + EMFormatHTML *efh = request->priv->efh; + EMFormat *emf = EM_FORMAT (efh); + GInputStream *stream; + GByteArray *ba; + gchar *part_id; + EMFormatWriterInfo info = {0}; + gchar *val; + + if (g_cancellable_is_cancelled (cancellable)) + return; + + if (request->priv->output_stream != NULL) { + g_object_unref (request->priv->output_stream); + } + + request->priv->output_stream = camel_stream_mem_new (); + + val = g_hash_table_lookup (request->priv->uri_query, "headers_collapsed"); + if (val) + info.headers_collapsed = atoi (val); + + val = g_hash_table_lookup (request->priv->uri_query, "headers_collapsable"); + if (val) + info.headers_collapsable = atoi (val); + + val = g_hash_table_lookup (request->priv->uri_query, "mode"); + if (val) + info.mode = atoi (val); + + part_id = g_hash_table_lookup (request->priv->uri_query, "part_id"); + if (part_id) { + /* original part_id is owned by the GHashTable */ + part_id = soup_uri_decode (part_id); + request->priv->puri = em_format_find_puri (emf, part_id); + + if (request->priv->puri) { + em_format_puri_write (request->priv->puri, + request->priv->output_stream, &info, NULL); + } else { + g_warning ("Failed to lookup requested part '%s' - this should not happen!", part_id); + } + + g_free (part_id); + } else { + + em_format_write (emf, request->priv->output_stream, &info, NULL); + } + + /* Convert the GString to GInputStream and send it back to WebKit */ + ba = camel_stream_mem_get_byte_array (CAMEL_STREAM_MEM (request->priv->output_stream)); + if (!ba->data) { + gchar *data = g_strdup_printf(_("Failed to load part '%s'"), part_id); + dd(printf("%s", data)); + g_byte_array_append (ba, (guchar *) data, strlen (data)); + g_free (data); + } else { + dd ({ + gchar *d = g_strndup ((gchar *) ba->data, ba->len); + printf("%s", d); + g_free (d); + }); + } + + stream = g_memory_input_stream_new_from_data ( + (gchar *) ba->data, ba->len, NULL); + g_simple_async_result_set_op_res_gpointer (res, stream, NULL); +} + +static void +handle_file_request (GSimpleAsyncResult *res, + GObject *object, + GCancellable *cancellable) +{ + EMailRequest *request = E_MAIL_REQUEST (object); + SoupURI *uri; + GInputStream *stream; + gchar *contents; + gsize length; + + if (g_cancellable_is_cancelled (cancellable)) + return; + + uri = soup_request_get_uri (SOUP_REQUEST (request)); + + if (g_file_get_contents (uri->path, &contents, &length, NULL)) { + + request->priv->mime_type = g_content_type_guess (uri->path, NULL, 0, NULL); + request->priv->content_length = length; + + stream = g_memory_input_stream_new_from_data ( + contents, length, (GDestroyNotify) g_free); + g_simple_async_result_set_op_res_gpointer (res, stream, NULL); + } +} + +struct http_request_async_data { + GMainLoop *loop; + GCancellable *cancellable; + CamelDataCache *cache; + gchar *cache_key; + + GInputStream *stream; + CamelStream *cache_stream; + gchar *content_type; + goffset content_length; + + gchar *buff; +}; + +static void +http_request_write_to_cache (GInputStream *stream, + GAsyncResult *res, + struct http_request_async_data *data) +{ + GError *error; + gssize len; + + error = NULL; + len = g_input_stream_read_finish (stream, res, &error); + + /* Error while reading data */ + if (len == -1) { + g_message ("Error while reading input stream: %s", + error ? error->message : "Unknown error"); + g_clear_error (&error); + + g_main_loop_quit (data->loop); + + if (data->buff) + g_free (data->buff); + + /* Don't keep broken data in cache */ + camel_data_cache_remove (data->cache, "http", data->cache_key, NULL); + return; + } + + /* EOF */ + if (len == 0) { + camel_stream_close (data->cache_stream, data->cancellable, NULL); + + if (data->buff) + g_free (data->buff); + + g_main_loop_quit (data->loop); + return; + } + + if (!data->cache_stream) { + + if (data->buff) + g_free (data->buff); + + g_main_loop_quit (data->loop); + return; + } + + /* Write chunk to cache and read another block of data. */ + camel_stream_write (data->cache_stream, data->buff, len, + data->cancellable, NULL); + + g_input_stream_read_async (stream, data->buff, 4096, + G_PRIORITY_DEFAULT, data->cancellable, + (GAsyncReadyCallback) http_request_write_to_cache, data); +} + +static void +http_request_finished (SoupRequest *request, + GAsyncResult *res, + struct http_request_async_data *data) +{ + GError *error; + SoupMessage *message; + + error = NULL; + data->stream = soup_request_send_finish (request, res, &error); + + if (!data->stream) { + g_warning("HTTP request failed: %s", error ? error->message: "Unknown error"); + g_clear_error (&error); + g_main_loop_quit (data->loop); + return; + } + + message = soup_request_http_get_message (SOUP_REQUEST_HTTP (request)); + if (!SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) { + g_warning ("HTTP request failed: HTTP code %d", message->status_code); + g_main_loop_quit (data->loop); + g_object_unref (message); + return; + } + + g_object_unref (message); + + data->content_length = soup_request_get_content_length (request); + data->content_type = g_strdup (soup_request_get_content_type (request)); + + if (!data->cache_stream) { + g_main_loop_quit (data->loop); + return; + } + + data->buff = g_malloc (4096); + g_input_stream_read_async (data->stream, data->buff, 4096, + G_PRIORITY_DEFAULT, data->cancellable, + (GAsyncReadyCallback) http_request_write_to_cache, data); + +} + +static void +handle_http_request (GSimpleAsyncResult *res, + GObject *object, + GCancellable *cancellable) +{ + EMailRequest *request = E_MAIL_REQUEST (object); + SoupURI *soup_uri; + gchar *evo_uri, *uri; + GInputStream *stream; + gboolean force_load_images = FALSE; + gchar *uri_md5; + + const gchar *user_cache_dir; + CamelDataCache *cache; + CamelStream *cache_stream; + + gssize len; + gchar *buff; + + GHashTable *query; + + /* Remove the __evo-mail query */ + soup_uri = soup_request_get_uri (SOUP_REQUEST (request)); + query = soup_form_decode (soup_uri->query); + g_hash_table_remove (query, "__evo-mail"); + + /* Remove __evo-load-images if present (and in such case set + * force_load_images to TRUE) */ + force_load_images = g_hash_table_remove (query, "__evo-load-images"); + + soup_uri_set_query_from_form (soup_uri, query); + g_hash_table_unref (query); + + evo_uri = soup_uri_to_string (soup_uri, FALSE); + + /* Remove the "evo-" prefix from scheme */ + if (evo_uri && (strlen (evo_uri) > 5)) { + uri = g_strdup (&evo_uri[4]); + g_free (evo_uri); + } + + g_return_if_fail (uri && *uri); + + /* Use MD5 hash of the URI as a filname of the resourec cache file. + * We were previously using the URI as a filename but the URI is + * sometimes too long for a filename. */ + uri_md5 = g_compute_checksum_for_string (G_CHECKSUM_MD5, uri, -1); + + /* Open Evolution's cache */ + user_cache_dir = e_get_user_cache_dir (); + cache = camel_data_cache_new (user_cache_dir, NULL); + if (cache) { + camel_data_cache_set_expire_age (cache, 24 * 60 * 60); + camel_data_cache_set_expire_access (cache, 2 * 60 * 60); + } + + /* Found item in cache! */ + cache_stream = camel_data_cache_get (cache, "http", uri_md5, NULL); + if (cache_stream) { + + stream = g_memory_input_stream_new (); + + request->priv->content_length = 0; + + buff = g_malloc (4096); + while ((len = camel_stream_read (cache_stream, buff, 4096, + cancellable, NULL)) > 0) { + + g_memory_input_stream_add_data (G_MEMORY_INPUT_STREAM (stream), + buff, len, g_free); + request->priv->content_length += len; + + buff = g_malloc (4096); + } + + g_object_unref (cache_stream); + + /* When succesfully read some data from cache then + * get mimetype and return the stream to WebKit. + * Otherwise try to fetch the resource again from the network. */ + if ((len != -1) && (request->priv->content_length > 0)) { + GFile *file; + GFileInfo *info; + gchar *path; + + path = camel_data_cache_get_filename (cache, "http", uri_md5, NULL); + file = g_file_new_for_path (path); + info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, + 0, cancellable, NULL); + + request->priv->mime_type = g_strdup ( + g_file_info_get_content_type (info)); + + d(printf ("'%s' found in cache (%d bytes, %s)\n", + uri, request->priv->content_length, + request->priv->mime_type)); + + g_object_unref (info); + g_object_unref (file); + g_free (path); + + /* Set result and quit the thread */ + g_simple_async_result_set_op_res_gpointer (res, stream, NULL); + + goto cleanup; + } else { + d(printf("Failed to load '%s' from cache.\n", uri)); + } + } + + /* Item not found in cache, but image loading policy allows us to fetch + * it from the interwebs */ + if (force_load_images || em_format_html_can_load_images (request->priv->efh)) { + + SoupRequester *requester; + SoupRequest *http_request; + SoupSession *session; + GMainContext *context; + GError *error; + + struct http_request_async_data data = { 0 }; + + context = g_main_context_get_thread_default (); + session = soup_session_async_new_with_options ( + SOUP_SESSION_ASYNC_CONTEXT, context, NULL); + + requester = soup_requester_new (); + soup_session_add_feature (session, SOUP_SESSION_FEATURE (requester)); + + http_request = soup_requester_request (requester, uri, NULL); + + error = NULL; + data.loop = g_main_loop_new (context, TRUE); + data.cancellable = cancellable; + data.cache = cache; + data.cache_key = uri_md5; + data.cache_stream = camel_data_cache_add (cache, "http", uri_md5, &error); + + if (!data.cache_stream) { + g_warning ("Failed to create cache file for '%s': %s", + uri, error ? error->message : "Unknown error"); + g_clear_error (&error); + } + + /* Send the request and waint in mainloop until it's finished + * and copied to cache */ + d(printf(" '%s' not in cache, sending HTTP request\n", uri)); + soup_request_send_async (http_request, cancellable, + (GAsyncReadyCallback) http_request_finished, &data); + + g_main_loop_run (data.loop); + d(printf (" '%s' fetched from internet and (hopefully) stored in" + " cache\n", uri)); + + g_main_loop_unref (data.loop); + + g_object_unref (session); + + g_object_unref (http_request); + g_object_unref (requester); + + stream = data.stream; + if (!stream) + goto cleanup; + + request->priv->content_length = data.content_length; + request->priv->mime_type = data.content_type; + + g_simple_async_result_set_op_res_gpointer (res, stream, NULL); + + goto cleanup; + + } + +cleanup: + g_free (uri); + g_free (uri_md5); +} + +static void +handle_stock_request (GSimpleAsyncResult *res, + GObject *object, + GCancellable *cancellable) +{ + EMailRequest *request; + SoupURI *uri; + GtkIconTheme *icon_theme; + GtkIconInfo *icon_info; + const gchar *file; + gchar *a_size; + gssize size; + gchar *buffer; + gsize buff_len; + GtkStyleContext *context; + GtkWidgetPath *path; + GtkIconSet *set; + + request = E_MAIL_REQUEST (object); + uri = soup_request_get_uri (SOUP_REQUEST (object)); + + if (request->priv->uri_query) { + a_size = g_hash_table_lookup (request->priv->uri_query, "size"); + } else { + a_size = NULL; + } + + if (!a_size) { + size = GTK_ICON_SIZE_BUTTON; + } else { + size = atoi (a_size); + } + + /* Try style context first */ + context = gtk_style_context_new (); + path = gtk_widget_path_new (); + gtk_widget_path_append_type (path, GTK_TYPE_WINDOW); + gtk_widget_path_append_type (path, GTK_TYPE_BUTTON); + gtk_style_context_set_path (context, path); + + set = gtk_style_context_lookup_icon_set (context, uri->host); + if (!set) { + /* Fallback to icon theme */ + icon_theme = gtk_icon_theme_get_default (); + icon_info = gtk_icon_theme_lookup_icon ( + icon_theme, uri->host, size, + GTK_ICON_LOOKUP_USE_BUILTIN); + if (!icon_info) { + gtk_widget_path_free (path); + g_object_unref (context); + return; + } + + file = gtk_icon_info_get_filename (icon_info); + buffer = NULL; + if (file) { + if (g_file_get_contents (file, &buffer, &buff_len, NULL)) { + + request->priv->mime_type = + g_content_type_guess (file, NULL, 0, NULL); + request->priv->content_length = buff_len; + } + + } else { + GdkPixbuf *pixbuf; + + pixbuf = gtk_icon_info_get_builtin_pixbuf (icon_info); + if (pixbuf) { + gdk_pixbuf_save_to_buffer ( + pixbuf, &buffer, + &buff_len, "png", NULL, NULL); + + request->priv->mime_type = g_strdup("image/png"); + request->priv->content_length = buff_len; + + g_object_unref (pixbuf); + } + } + + gtk_icon_info_free (icon_info); + + } else { + GdkPixbuf *pixbuf; + + pixbuf = gtk_icon_set_render_icon_pixbuf (set, context, size); + gdk_pixbuf_save_to_buffer ( + pixbuf, &buffer, + &buff_len, "png", NULL, NULL); + + request->priv->mime_type = g_strdup("image/png"); + request->priv->content_length = buff_len; + + g_object_unref (pixbuf); + } + + if (buffer) { + GInputStream *stream; + stream = g_memory_input_stream_new_from_data ( + buffer, buff_len, (GDestroyNotify) g_free); + g_simple_async_result_set_op_res_gpointer (res, stream, NULL); + } + + gtk_widget_path_free (path); + g_object_unref (context); + +} + +static void +e_mail_request_init (EMailRequest *request) +{ + request->priv = G_TYPE_INSTANCE_GET_PRIVATE ( + request, E_TYPE_MAIL_REQUEST, EMailRequestPrivate); + + request->priv->efh = NULL; + request->priv->output_stream = NULL; + request->priv->uri_query = NULL; + request->priv->puri = NULL; + request->priv->mime_type = NULL; + request->priv->content_length = 0; +} + +static void +mail_request_finalize (GObject *object) +{ + EMailRequest *request = E_MAIL_REQUEST (object); + + if (request->priv->output_stream) { + g_object_unref (request->priv->output_stream); + request->priv->output_stream = NULL; + } + + if (request->priv->mime_type) { + g_free (request->priv->mime_type); + request->priv->mime_type = NULL; + } + + if (request->priv->uri_query) { + g_hash_table_destroy (request->priv->uri_query); + request->priv->uri_query = NULL; + } + + if (request->priv->ret_mime_type) { + g_free (request->priv->ret_mime_type); + request->priv->ret_mime_type = NULL; + } + + G_OBJECT_CLASS (e_mail_request_parent_class)->finalize (object); +} + +static gboolean +mail_request_check_uri (SoupRequest *request, + SoupURI *uri, + GError **error) +{ + return ((strcmp (uri->scheme, "mail") == 0) || + (strcmp (uri->scheme, "evo-file") == 0) || + (strcmp (uri->scheme, "evo-http") == 0) || + (strcmp (uri->scheme, "evo-https") == 0) || + (strcmp (uri->scheme, "gtk-stock") == 0)); +} + +static void +mail_request_send_async (SoupRequest *request, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + SoupSession *session; + EMailRequest *emr = E_MAIL_REQUEST (request); + GSimpleAsyncResult *result; + SoupURI *uri; + GHashTable *formatters; + + session = soup_request_get_session (request); + uri = soup_request_get_uri (request); + + d(printf("received request for %s\n", soup_uri_to_string (uri, FALSE))); + + /* WebKit won't allow us to load data through local file:// protocol + * when using "remote" mail:// protocol, so we have evo-file:// + * which WebKit thinks it's remote, but in fact it behaves like + * oridnary file:// */ + if (g_strcmp0 (uri->scheme, "evo-file") == 0) { + + result = g_simple_async_result_new (G_OBJECT (request), callback, + user_data, mail_request_send_async); + g_simple_async_result_run_in_thread (result, handle_file_request, + G_PRIORITY_DEFAULT, cancellable); + + return; + } + + if (uri->query) { + emr->priv->uri_query = soup_form_decode (uri->query); + } else { + emr->priv->uri_query = NULL; + } + + formatters = g_object_get_data (G_OBJECT (session), "formatters"); + g_return_if_fail (formatters != NULL); + + /* Get HTML content of given PURI part */ + if (g_strcmp0 (uri->scheme, "mail") == 0) { + gchar *uri_str; + + uri_str = g_strdup_printf ("%s://%s%s", uri->scheme, uri->host, uri->path); + emr->priv->efh = g_hash_table_lookup (formatters, uri_str); + g_free (uri_str); + + g_return_if_fail (emr->priv->efh); + + result = g_simple_async_result_new (G_OBJECT (request), callback, + user_data, mail_request_send_async); + g_simple_async_result_run_in_thread (result, handle_mail_request, + G_PRIORITY_DEFAULT, cancellable); + + return; + + /* For http and https requests we have this evo-http(s) protocol. + * We first try to lookup the data in local cache and when not found, + * we send standard http(s) request to fetch them. But only when image + * loading policy allows us. */ + } else if ((g_strcmp0 (uri->scheme, "evo-http") == 0) || + (g_strcmp0 (uri->scheme, "evo-https") == 0)) { + + gchar *mail_uri; + const gchar *enc = g_hash_table_lookup (emr->priv->uri_query, + "__evo-mail"); + + g_return_if_fail (enc && *enc); + + mail_uri = soup_uri_decode (enc); + + emr->priv->efh = g_hash_table_lookup (formatters, mail_uri); + g_free (mail_uri); + + g_return_if_fail (emr->priv->efh); + + result = g_simple_async_result_new (G_OBJECT (request), callback, + user_data, mail_request_send_async); + g_simple_async_result_run_in_thread (result, handle_http_request, + G_PRIORITY_DEFAULT, cancellable); + + return; + + } else if ((g_strcmp0 (uri->scheme, "gtk-stock") == 0)) { + + result = g_simple_async_result_new (G_OBJECT (request), callback, + user_data, mail_request_send_async); + g_simple_async_result_run_in_thread (result, handle_stock_request, + G_PRIORITY_DEFAULT, cancellable); + + return; + } +} + +static GInputStream * +mail_request_send_finish (SoupRequest *request, + GAsyncResult *result, + GError **error) +{ + GInputStream *stream; + + stream = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result)); + g_object_unref (result); + + /* Reset the stream before passing it back to webkit */ + if (stream && G_IS_SEEKABLE (stream)) + g_seekable_seek (G_SEEKABLE (stream), 0, G_SEEK_SET, NULL, NULL); + else /* We must always return something */ + stream = g_memory_input_stream_new (); + + return stream; +} + +static goffset +mail_request_get_content_length (SoupRequest *request) +{ + EMailRequest *emr = E_MAIL_REQUEST (request); + GByteArray *ba; + gint content_length = 0; + + if (emr->priv->content_length > 0) + content_length = emr->priv->content_length; + else if (emr->priv->output_stream) { + ba = camel_stream_mem_get_byte_array (CAMEL_STREAM_MEM (emr->priv->output_stream)); + if (ba) { + content_length = ba->len; + } + } + + d(printf("Content-Length: %d bytes\n", content_length)); + return content_length; +} + +static const gchar * +mail_request_get_content_type (SoupRequest *request) +{ + EMailRequest *emr = E_MAIL_REQUEST (request); + gchar *mime_type; + + if (emr->priv->mime_type) { + mime_type = g_strdup (emr->priv->mime_type); + } else if (!emr->priv->puri) { + mime_type = g_strdup ("text/html"); + } else if (!emr->priv->puri->mime_type) { + CamelContentType *ct = camel_mime_part_get_content_type (emr->priv->puri->part); + mime_type = camel_content_type_simple (ct); + } else { + mime_type = g_strdup (emr->priv->puri->mime_type); + } + + if (g_strcmp0 (mime_type, "text/html") == 0) { + emr->priv->ret_mime_type = g_strconcat (mime_type, "; charset=\"UTF-8\"", NULL); + g_free (mime_type); + } else { + emr->priv->ret_mime_type = mime_type; + } + + d(printf("Content-Type: %s\n", emr->priv->ret_mime_type)); + + return emr->priv->ret_mime_type; +} + +static const char *data_schemes[] = { "mail", "evo-file", "evo-http", "evo-https", "gtk-stock", NULL }; + +static void +e_mail_request_class_init (EMailRequestClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + SoupRequestClass *request_class = SOUP_REQUEST_CLASS (class); + + g_type_class_add_private (class, sizeof (EMailRequestPrivate)); + + object_class->finalize = mail_request_finalize; + + request_class->schemes = data_schemes; + request_class->send_async = mail_request_send_async; + request_class->send_finish = mail_request_send_finish; + request_class->get_content_type = mail_request_get_content_type; + request_class->get_content_length = mail_request_get_content_length; + request_class->check_uri = mail_request_check_uri; +} diff --git a/mail/e-mail-request.h b/mail/e-mail-request.h new file mode 100644 index 0000000000..923362101e --- /dev/null +++ b/mail/e-mail-request.h @@ -0,0 +1,36 @@ +#ifndef E_MAIL_REQUEST_H +#define E_MAIL_REQUEST_H + +#define LIBSOUP_USE_UNSTABLE_REQUEST_API + +#include <libsoup/soup.h> +#include <libsoup/soup-request.h> + +G_BEGIN_DECLS + +#define E_TYPE_MAIL_REQUEST (e_mail_request_get_type ()) +#define E_MAIL_REQUEST(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), E_TYPE_MAIL_REQUEST, EMailRequest)) +#define E_MAIL_REQUEST_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), E_TYPE_MAIL_REQUEST, EMailRequestClass)) +#define E_IS_MAIL_REQUEST(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), E_TYPE_MAIL_REQUEST)) +#define E_IS_MAIL_REQUEST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), E_TYPE_MAIL_REQUEST)) +#define E_MAIL_REQUEST_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), E_TYPE_MAIL_REQUEST, EMailRequestClass)) + +typedef struct _EMailRequest EMailRequest; +typedef struct _EMailRequestClass EMailRequestClass; +typedef struct _EMailRequestPrivate EMailRequestPrivate; + +struct _EMailRequest { + SoupRequest parent; + + EMailRequestPrivate *priv; +}; + +struct _EMailRequestClass { + SoupRequestClass parent; +}; + +GType e_mail_request_get_type (void); + +G_END_DECLS + +#endif /* E_MAIL_REQUEST_H */ diff --git a/mail/em-account-editor.c b/mail/em-account-editor.c index d53289d3c5..74a3d5bc92 100644 --- a/mail/em-account-editor.c +++ b/mail/em-account-editor.c @@ -2523,9 +2523,9 @@ emae_refresh_providers (EMAccountEditor *emae, GTK_COMBO_BOX (combo_box), service->protocol); /* make sure at least something is selected; - this applies for cases when user changed from provider which was - store and transport together, to a store provider only (like from - exchange to imap provider), which left unselected transport type + * this applies for cases when user changed from provider which was + * store and transport together, to a store provider only (like from + * exchange to imap provider), which left unselected transport type */ if (gtk_combo_box_get_active (GTK_COMBO_BOX (combo_box)) == -1) gtk_combo_box_set_active (GTK_COMBO_BOX (combo_box), 0); diff --git a/mail/em-composer-utils.c b/mail/em-composer-utils.c index 745609fbc0..f4b8560f81 100644 --- a/mail/em-composer-utils.c +++ b/mail/em-composer-utils.c @@ -51,12 +51,14 @@ #include <composer/e-composer-actions.h> #include <composer/e-composer-post-header.h> +#include "e-mail-printer.h" #include "em-utils.h" #include "em-composer-utils.h" #include "em-folder-selector.h" #include "em-folder-tree.h" #include "em-format-html.h" #include "em-format-html-print.h" +#include "em-format-html-display.h" #include "em-format-quote.h" #include "em-event.h" #include "mail-send-recv.h" @@ -929,17 +931,38 @@ em_utils_composer_save_to_outbox_cb (EMsgComposer *composer, } static void +composer_print_done_cb (EMailPrinter *emp, + GtkPrintOperation *operation, + GtkPrintOperationResult result, + gpointer user_data) +{ + EMFormat *emf = user_data; + g_object_unref (emf); + g_object_unref (emp); +} + +static void em_utils_composer_print_cb (EMsgComposer *composer, GtkPrintOperationAction action, CamelMimeMessage *message, EActivity *activity, EMailSession *session) { - EMFormatHTMLPrint *efhp; + EMailPrinter *emp; + EMFormatHTMLDisplay *efhd; + + efhd = em_format_html_display_new (); + ((EMFormat *) efhd)->message_uid = g_strdup (camel_mime_message_get_message_id (message)); + + /* Parse the message */ + em_format_parse ((EMFormat *) efhd, message, NULL, NULL); + + /* Use EMailPrinter and WebKit to print the message */ + emp = e_mail_printer_new ((EMFormatHTML *) efhd); + g_signal_connect (emp, "done", + G_CALLBACK (composer_print_done_cb), efhd); - efhp = em_format_html_print_new (NULL, action); - em_format_html_print_message (efhp, message, NULL, NULL); - g_object_unref (efhp); + e_mail_printer_print (emp, FALSE, NULL); } /* Composing messages... */ diff --git a/mail/em-format-hook.c b/mail/em-format-hook.c index 233805561b..7ceeb20e4d 100644 --- a/mail/em-format-hook.c +++ b/mail/em-format-hook.c @@ -61,24 +61,23 @@ static const EPluginHookTargetKey emfh_flag_map[] = { G_DEFINE_TYPE (EMFormatHook, em_format_hook, E_TYPE_PLUGIN_HOOK) static void -emfh_format_format (EMFormat *md, - CamelStream *stream, - CamelMimePart *part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) +emfh_parse_part (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) { - struct _EMFormatHookItem *item = (EMFormatHookItem *) info; + struct _EMFormatHookItem *item = (EMFormatHookItem *) info->handler; if (item->hook->hook.plugin->enabled) { EMFormatHookTarget target = { - md, stream, part, item + emf, part, part_id, info }; e_plugin_invoke (item->hook->hook.plugin, item->format, &target); - } else if (info->old) { - info->old->handler ( - md, stream, part, info->old, cancellable, FALSE); + } else if (info->handler->old) { + info->handler->old->parse_func ( + emf, part, part_id, info, cancellable); } } @@ -116,7 +115,7 @@ emfh_construct_item (EPluginHook *eph, item->handler.flags = e_plugin_hook_mask(root, emfh_flag_map, "flags"); item->format = e_plugin_xml_prop(root, "format"); - item->handler.handler = emfh_format_format; + item->handler.parse_func = emfh_parse_part; item->hook = emfh; if (item->handler.mime_type == NULL || item->format == NULL) diff --git a/mail/em-format-hook.h b/mail/em-format-hook.h index ad0745bc2e..09076c2f48 100644 --- a/mail/em-format-hook.h +++ b/mail/em-format-hook.h @@ -40,9 +40,9 @@ typedef void (*EMFormatHookFunc)(struct _EPlugin *plugin, EMFormatHookTarget *da struct _EMFormatHookTarget { struct _EMFormat *format; - CamelStream *stream; CamelMimePart *part; - struct _EMFormatHookItem *item; + GString *part_id; + EMFormatParserInfo *info; }; struct _EMFormatHookItem { diff --git a/mail/em-format-html-display.c b/mail/em-format-html-display.c index 013e1ac731..8f20169636 100644 --- a/mail/em-format-html-display.c +++ b/mail/em-format-html-display.c @@ -38,9 +38,6 @@ #undef interface #endif -#include <gtkhtml/gtkhtml.h> -#include <gtkhtml/gtkhtml-embedded.h> - #include <glib/gi18n.h> #include <e-util/e-util.h> @@ -53,6 +50,8 @@ #include <shell/e-shell.h> #include <shell/e-shell-utils.h> +#include <libedataserver/e-flag.h> + #if defined (HAVE_NSS) && defined (ENABLE_SMIME) #include "certificate-viewer.h" #include "e-cert-db.h" @@ -65,6 +64,8 @@ #include "widgets/misc/e-attachment.h" #include "widgets/misc/e-attachment-button.h" #include "widgets/misc/e-attachment-view.h" +#include "shell/e-shell.h" +#include "shell/e-shell-window.h" #define EM_FORMAT_HTML_DISPLAY_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ @@ -77,16 +78,9 @@ ((obj), EM_TYPE_FORMAT_HTML_DISPLAY, EMFormatHTMLDisplayPrivate)) struct _EMFormatHTMLDisplayPrivate { - GHashTable *attachment_views; /* weak reference; message_part_id->EAttachmentView */ - gboolean attachment_expanded; -}; -struct _smime_pobject { - EMFormatHTMLPObject object; + EAttachmentView *last_view; - gint signature; - CamelCipherValidity *valid; - GtkWidget *widget; }; /* TODO: move the dialogue elsehwere */ @@ -111,78 +105,100 @@ static const struct { { "stock_lock-ok", N_("Encrypted, strong"), N_("This message is encrypted, with a strong encryption algorithm. It would be very difficult for an outsider to view the content of this message in a practical amount of time.") }, }; -static const gchar *smime_sign_colour[5] = { - "", " bgcolor=\"#88bb88\"", " bgcolor=\"#bb8888\"", " bgcolor=\"#e8d122\"","" +static const GdkRGBA smime_sign_colour[5] = { + { 0 }, { 0.53, 0.73, 0.53, 1 }, { 0.73, 0.53, 0.53, 1 }, { 0.91, 0.82, 0.13, 1 }, { 0 }, }; -static void efhd_attachment_frame (EMFormat *emf, CamelStream *stream, EMFormatPURI *puri, GCancellable *cancellable); -static void efhd_message_add_bar (EMFormat *emf, CamelStream *stream, CamelMimePart *part, const EMFormatHandler *info); -static gboolean efhd_attachment_button (EMFormatHTML *efh, GtkHTMLEmbedded *eb, EMFormatHTMLPObject *pobject); -static gboolean efhd_attachment_optional (EMFormatHTML *efh, GtkHTMLEmbedded *eb, EMFormatHTMLPObject *object); +static void efhd_message_prefix (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); +static void efhd_message_add_bar (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); +static void efhd_parse_attachment (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); +static void efhd_parse_secure (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); +static void efhd_parse_optional (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); + +static GtkWidget * efhd_attachment_bar (EMFormat *emf, EMFormatPURI *puri, GCancellable *cancellable); +static GtkWidget * efhd_attachment_button (EMFormat *emf, EMFormatPURI *puri, GCancellable *cancellable); +static GtkWidget * efhd_attachment_optional (EMFormat *emf, EMFormatPURI *puri, GCancellable *cancellable); + +static void efhd_write_attachment_bar (EMFormat *emf, EMFormatPURI *emp, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable); +static void efhd_write_attachment (EMFormat *emf, EMFormatPURI *emp, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable); +static void efhd_write_secure_button (EMFormat *emf, EMFormatPURI *emp, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable); + static void efhd_free_attach_puri_data (EMFormatPURI *puri); -struct _attach_puri { - EMFormatPURI puri; +static void efhd_builtin_init (EMFormatHTMLDisplayClass *efhc); + +static gpointer parent_class; - const EMFormatHandler *handle; +static EAttachmentStore * +find_parent_attachment_store (EMFormatHTMLDisplay *efhd, + const gchar *part_id) +{ + EMFormat *emf = (EMFormat *) efhd; + EMFormatAttachmentBarPURI *abp; + gchar *tmp, *pos; + GList *item; - const gchar *snoop_mime_type; + tmp = g_strdup (part_id); - /* for the > and V buttons */ - GtkWidget *forward, *down; - /* currently no way to correlate this data to the frame :( */ - GtkHTML *frame; - guint shown : 1; + do { + gchar *id; - /* Embedded Frame */ - GtkHTMLEmbedded *html; + pos = g_strrstr (tmp, "."); + if (!pos) + break; - /* Attachment */ - EAttachment *attachment; - gchar *attachment_view_part_id; + g_free (tmp); + tmp = g_strndup (part_id, pos - tmp); + id = g_strdup_printf ("%s.attachment-bar", tmp); - /* image stuff */ - gint fit_width; - gint fit_height; - GtkImage *image; - GtkWidget *event_box; + item = g_hash_table_lookup (emf->mail_part_table, id); - /* Optional Text Mem Stream */ - CamelStreamMem *mstream; + g_free (id); - /* Signed / Encrypted */ - camel_cipher_validity_sign_t sign; - camel_cipher_validity_encrypt_t encrypt; -}; + } while (pos && !item); -static void efhd_message_prefix (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback); + g_free (tmp); -static void efhd_builtin_init (EMFormatHTMLDisplayClass *efhc); + abp = (EMFormatAttachmentBarPURI *) item->data; -G_DEFINE_TYPE ( - EMFormatHTMLDisplay, - em_format_html_display, - EM_TYPE_FORMAT_HTML) + if (abp) + return abp->store; + else + return NULL; +} static void -efhd_xpkcs7mime_free (EMFormatHTMLPObject *o) +efhd_attachment_bar_puri_free (EMFormatPURI *puri) { - struct _smime_pobject *po = (struct _smime_pobject *) o; + EMFormatAttachmentBarPURI *abp; - if (po->widget) - gtk_widget_destroy (po->widget); - camel_cipher_validity_free (po->valid); + abp = (EMFormatAttachmentBarPURI *) puri; + + if (abp->store) { + g_object_unref (abp->store); + abp->store = NULL; + } +} + +static void +efhd_xpkcs7mime_free (EMFormatPURI *puri) +{ + EMFormatSMIMEPURI *sp = (EMFormatSMIMEPURI *) puri; + + if (sp->widget) + gtk_widget_destroy (sp->widget); + + if (sp->description) + g_free (sp->description); + + if (sp->valid) + camel_cipher_validity_free (sp->valid); } static void efhd_xpkcs7mime_info_response (GtkWidget *widget, guint button, - struct _smime_pobject *po) + EMFormatSMIMEPURI *po) { gtk_widget_destroy (widget); po->widget = NULL; @@ -191,7 +207,7 @@ efhd_xpkcs7mime_info_response (GtkWidget *widget, #if defined (HAVE_NSS) && defined (ENABLE_SMIME) static void efhd_xpkcs7mime_viewcert_clicked (GtkWidget *button, - struct _smime_pobject *po) + EMFormatSMIMEPURI *po) { CamelCipherCertInfo *info = g_object_get_data((GObject *)button, "e-cert-info"); ECert *ec = NULL; @@ -221,7 +237,7 @@ efhd_xpkcs7mime_viewcert_clicked (GtkWidget *button, static void efhd_xpkcs7mime_add_cert_table (GtkWidget *grid, GQueue *certlist, - struct _smime_pobject *po) + EMFormatSMIMEPURI *po) { GList *head, *link; GtkTable *table; @@ -283,9 +299,9 @@ efhd_xpkcs7mime_add_cert_table (GtkWidget *grid, static void efhd_xpkcs7mime_validity_clicked (GtkWidget *button, - EMFormatHTMLPObject *pobject) + EMFormatPURI *puri) { - struct _smime_pobject *po = (struct _smime_pobject *) pobject; + EMFormatSMIMEPURI *po = (EMFormatSMIMEPURI *) puri; GtkBuilder *builder; GtkWidget *grid, *w; @@ -367,20 +383,19 @@ efhd_xpkcs7mime_validity_clicked (GtkWidget *button, g_object_unref (builder); g_signal_connect ( - po->widget, "response", + po->widget, "response", G_CALLBACK (efhd_xpkcs7mime_info_response), po); gtk_widget_show (po->widget); } -static gboolean -efhd_xpkcs7mime_button (EMFormatHTML *efh, - GtkHTMLEmbedded *eb, - EMFormatHTMLPObject *pobject) +static GtkWidget * +efhd_xpkcs7mime_button (EMFormat *emf, + EMFormatPURI *puri, + GCancellable *cancellable) { - GtkWidget *container; - GtkWidget *widget; - struct _smime_pobject *po = (struct _smime_pobject *) pobject; + GtkWidget *box, *button, *layout, *widget; + EMFormatSMIMEPURI *po = (EMFormatSMIMEPURI *) puri; const gchar *icon_name; /* FIXME: need to have it based on encryption and signing too */ @@ -389,450 +404,436 @@ efhd_xpkcs7mime_button (EMFormatHTML *efh, else icon_name = smime_encrypt_table[po->valid->encrypt.status].icon; - container = GTK_WIDGET (eb); + box = gtk_event_box_new (); + if (po->valid->sign.status != 0) + gtk_widget_override_background_color (box, GTK_STATE_FLAG_NORMAL, + &smime_sign_colour[po->valid->sign.status]); - widget = gtk_button_new (); - g_signal_connect ( - widget, "clicked", - G_CALLBACK (efhd_xpkcs7mime_validity_clicked), pobject); - gtk_container_add (GTK_CONTAINER (container), widget); - gtk_widget_show (widget); + layout = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5); + gtk_container_add (GTK_CONTAINER (box), layout); - container = widget; + button = gtk_button_new (); + gtk_box_pack_start (GTK_BOX (layout), button, FALSE, FALSE, 0); + g_signal_connect (button, "clicked", + G_CALLBACK (efhd_xpkcs7mime_validity_clicked), puri); widget = gtk_image_new_from_icon_name ( - icon_name, GTK_ICON_SIZE_LARGE_TOOLBAR); - gtk_container_add (GTK_CONTAINER (container), widget); - gtk_widget_show (widget); + icon_name, GTK_ICON_SIZE_LARGE_TOOLBAR); + gtk_button_set_image (GTK_BUTTON (button), widget); - return TRUE; -} + widget = gtk_label_new (po->description); + gtk_box_pack_start (GTK_BOX (layout), widget, FALSE, FALSE, 0); -static gboolean -remove_attachment_view_cb (gpointer message_part_id, - gpointer attachment_view, - gpointer gone_attachment_view) -{ - return attachment_view == gone_attachment_view; + gtk_widget_show_all (box); + + return box; } +struct attachment_load_data { + EAttachment *attachment; + EFlag *flag; +}; + static void -efhd_attachment_view_gone_cb (gpointer efh, - GObject *gone_attachment_view) +attachment_loaded (EAttachment *attachment, + GAsyncResult *res, + gpointer user_data) { - EMFormatHTMLDisplay *efhd = EM_FORMAT_HTML_DISPLAY (efh); + struct attachment_load_data *data = user_data; + EShell *shell; + GtkWindow *window; - g_return_if_fail (efhd != NULL); + shell = e_shell_get_default (); + window = e_shell_get_active_window (shell); + if (!E_IS_SHELL_WINDOW (window)) + window = NULL; - g_hash_table_foreach_remove ( - efhd->priv->attachment_views, - remove_attachment_view_cb, - gone_attachment_view); -} + e_attachment_load_handle_error (data->attachment, res, window); -static void -weak_unref_attachment_view_cb (gpointer message_part_id, - gpointer attachment_view, - gpointer efh) -{ - g_object_weak_unref ( - G_OBJECT (attachment_view), - efhd_attachment_view_gone_cb, efh); + e_flag_set (data->flag); } -static void -efhd_format_clone (EMFormat *emf, - CamelFolder *folder, - const gchar *uid, - CamelMimeMessage *msg, - EMFormat *src, - GCancellable *cancellable) +/* Idle callback */ +static gboolean +load_attachment_idle (struct attachment_load_data *data) { - EMFormatHTMLDisplay *efhd; - - efhd = EM_FORMAT_HTML_DISPLAY (emf); - g_return_if_fail (efhd != NULL); + e_attachment_load_async (data->attachment, + (GAsyncReadyCallback) attachment_loaded, data); - g_hash_table_foreach (efhd->priv->attachment_views, weak_unref_attachment_view_cb, efhd); - g_hash_table_remove_all (efhd->priv->attachment_views); - - if (emf != src) - EM_FORMAT_HTML (emf)->header_wrap_flags = 0; - - /* Chain up to parent's format_clone() method. */ - EM_FORMAT_CLASS (em_format_html_display_parent_class)-> - format_clone (emf, folder, uid, msg, src, cancellable); + return FALSE; } static void -efhd_format_attachment (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const gchar *mime_type, - const EMFormatHandler *handle, - GCancellable *cancellable) +efhd_parse_attachment (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) { - GString *buffer; - gchar *classid, *text, *html; - struct _attach_puri *info; - - classid = g_strdup_printf ("attachment%s", emf->part_id->str); - info = (struct _attach_puri *) em_format_add_puri ( - emf, sizeof (*info), classid, part, efhd_attachment_frame); - info->puri.free = efhd_free_attach_puri_data; - info->attachment_view_part_id = g_strdup (emf->current_message_part_id); - em_format_html_add_pobject ( - EM_FORMAT_HTML (emf), sizeof (EMFormatHTMLPObject), - classid, part, efhd_attachment_button); - info->handle = handle; - info->shown = em_format_is_inline ( - emf, info->puri.part_id, info->puri.part, handle); - info->snoop_mime_type = emf->snoop_mime_type; - info->attachment = e_attachment_new (); - e_attachment_set_mime_part (info->attachment, info->puri.part); - - if (emf->valid) { - info->sign = emf->valid->sign.status; - info->encrypt = emf->valid->encrypt.status; + gchar *text, *html; + EMFormatHTMLDisplay *efhd = (EMFormatHTMLDisplay *) emf; + EMFormatAttachmentPURI *puri; + EAttachmentStore *store; + const EMFormatHandler *handler; + CamelContentType *ct; + gchar *mime_type; + gint len; + const gchar *cid; + guint32 size; + struct attachment_load_data *load_data; + gboolean can_show = FALSE; + + if (g_cancellable_is_cancelled (cancellable)) + return; + + len = part_id->len; + g_string_append (part_id, ".attachment"); + + /* Try to find handler for the mime part */ + ct = camel_mime_part_get_content_type (part); + if (ct) { + mime_type = camel_content_type_simple (ct); + handler = em_format_find_handler (emf, mime_type); } - buffer = g_string_sized_new (1024); - - g_string_append_printf ( - buffer, EM_FORMAT_HTML_VPAD - "<table cellspacing=0 cellpadding=0>" - "<tr><td>" - "<table width=10 cellspacing=0 cellpadding=0>" - "<tr><td></td><tr>" - "</table>" - "</td>" - "<td><object classid=\"%s\"></object></td>" - "<td><table width=3 cellspacing=0 cellpadding=0>" - "<tr><td></td></tr>" - "</table></td>" - "<td><font size=-1>", - classid); - - /* output some info about it */ /* FIXME: should we look up mime_type from object again? */ text = em_format_describe_part (part, mime_type); html = camel_text_to_html ( text, EM_FORMAT_HTML (emf)->text_html_flags & CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS, 0); - g_string_append (buffer, html); - g_free (html); g_free (text); + g_free (mime_type); + + puri = (EMFormatAttachmentPURI *) em_format_puri_new ( + emf, sizeof (EMFormatAttachmentPURI), part, part_id->str); + puri->puri.free = efhd_free_attach_puri_data; + puri->puri.write_func = efhd_write_attachment; + puri->puri.widget_func = efhd_attachment_button; + puri->shown = (handler && em_format_is_inline (emf, part_id->str, part, handler)); + puri->snoop_mime_type = em_format_snoop_type (part); + puri->attachment = e_attachment_new (); + puri->attachment_view_part_id = NULL; + puri->description = html; + puri->handle = handler; + if (info->validity) + puri->puri.validity = camel_cipher_validity_clone (info->validity); + + cid = camel_mime_part_get_content_id (part); + if (cid) + puri->puri.cid = g_strdup_printf ("cid:%s", cid); + + if (handler) { + CamelContentType *ct; + + /* This mime_type is important for WebKit to determine content type. + * We have converted text/ * to text/html, other (binary) formats remained + * untouched. */ + ct = camel_content_type_decode (handler->mime_type); + if (g_strcmp0 (ct->type, "text") == 0) + puri->puri.mime_type = g_strdup ("text/html"); + else + puri->puri.mime_type = camel_content_type_simple (ct); + camel_content_type_unref (ct); + } - g_string_append ( - buffer, - "</font></td>" - "</tr><tr></table>\n" - EM_FORMAT_HTML_VPAD); - - camel_stream_write ( - stream, buffer->str, buffer->len, cancellable, NULL); - - g_string_free (buffer, TRUE); + em_format_add_puri (emf, (EMFormatPURI *) puri); + + /* Though it is an attachment, we still might be able to parse it and + * so discover some parts that we might be even able to display. */ + if (handler && handler->parse_func && (handler->parse_func != efhd_parse_attachment) && + ((handler->flags & EM_FORMAT_HANDLER_COMPOUND_TYPE) || + (handler->flags & EM_FORMAT_HANDLER_INLINE_DISPOSITION))) { + GList *i; + EMFormatParserInfo attachment_info = { .handler = handler, + .is_attachment = TRUE }; + handler->parse_func (emf, puri->puri.part, part_id, &attachment_info, cancellable); + + i = g_hash_table_lookup (emf->mail_part_table, part_id->str); + if (i->next && i->next->data) { + EMFormatPURI *p = i->next->data; + puri->attachment_view_part_id = g_strdup (p->uri); + can_show = TRUE; + } + } - if (handle && info->shown) - handle->handler ( - emf, stream, part, handle, cancellable, FALSE); + e_attachment_set_mime_part (puri->attachment, part); + e_attachment_set_shown (puri->attachment, puri->shown); + if (puri->puri.validity) { + e_attachment_set_signed (puri->attachment, puri->puri.validity->sign.status); + e_attachment_set_encrypted (puri->attachment, puri->puri.validity->encrypt.status); + } + e_attachment_set_can_show (puri->attachment, + can_show || (puri->handle && puri->handle->write_func)); - g_free (classid); -} + store = find_parent_attachment_store (efhd, part_id->str); + e_attachment_store_add_attachment (store, puri->attachment); -static void -efhd_format_optional (EMFormat *emf, - CamelStream *fstream, - CamelMimePart *part, - CamelStream *mstream, - GCancellable *cancellable) -{ - gchar *classid, *html; - struct _attach_puri *info; - CamelStream *stream = NULL; - GString *buffer; + if (emf->folder && emf->folder->summary && emf->message_uid) { + CamelDataWrapper *dw = camel_medium_get_content (CAMEL_MEDIUM (puri->puri.part)); + GByteArray *ba; + ba = camel_data_wrapper_get_byte_array (dw); + if (ba) { + size = ba->len; - if (CAMEL_IS_STREAM_FILTER (fstream)) - stream = camel_stream_filter_get_source ( - CAMEL_STREAM_FILTER (fstream)); - if (stream == NULL) - stream = fstream; - - classid = g_strdup_printf ("optional%s", emf->part_id->str); - info = (struct _attach_puri *) em_format_add_puri ( - emf, sizeof (*info), classid, part, efhd_attachment_frame); - info->puri.free = efhd_free_attach_puri_data; - info->attachment_view_part_id = g_strdup (emf->current_message_part_id); - em_format_html_add_pobject ( - EM_FORMAT_HTML (emf), sizeof (EMFormatHTMLPObject), - classid, part, efhd_attachment_optional); - info->handle = em_format_find_handler (emf, "text/plain"); - info->shown = FALSE; - info->snoop_mime_type = "text/plain"; - info->attachment = e_attachment_new (); - e_attachment_set_mime_part (info->attachment, info->puri.part); - info->mstream = (CamelStreamMem *) g_object_ref (mstream); - if (emf->valid) { - info->sign = emf->valid->sign.status; - info->encrypt = emf->valid->encrypt.status; + if (camel_mime_part_get_encoding (puri->puri.part) == CAMEL_TRANSFER_ENCODING_BASE64) + size = size / 1.37; + } } - buffer = g_string_sized_new (1024); + load_data = g_new0 (struct attachment_load_data, 1); + load_data->attachment = g_object_ref (puri->attachment); + load_data->flag = e_flag_new (); - g_string_append ( - buffer, EM_FORMAT_HTML_VPAD - "<table cellspacing=0 cellpadding=0><tr><td>" - "<h3><font size=-1 color=red>"); + e_flag_clear (load_data->flag); - html = camel_text_to_html ( - _("Evolution cannot render this email as it is too " - "large to process. You can view it unformatted or " - "with an external text editor."), - EM_FORMAT_HTML (emf)->text_html_flags & - CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS, 0); - g_string_append (buffer, html); - g_free (html); + /* e_attachment_load_async must be called from main thread */ + g_idle_add ((GSourceFunc) load_attachment_idle, load_data); - g_string_append_printf ( - buffer, - "</font></h3></td></tr></table>\n" - "<table cellspacing=0 cellpadding=0><tr>" - "<td><object classid=\"%s\"></object>" - "</td></tr></table>" EM_FORMAT_HTML_VPAD, - classid); + e_flag_wait (load_data->flag); - camel_stream_write ( - stream, buffer->str, buffer->len, cancellable, NULL); + e_flag_free (load_data->flag); + g_object_unref (load_data->attachment); + g_free (load_data); - g_string_free (buffer, TRUE); + if (size != 0) { + GFileInfo *fileinfo; + + fileinfo = e_attachment_get_file_info (puri->attachment); + g_file_info_set_size (fileinfo, size); + e_attachment_set_file_info (puri->attachment, fileinfo); + } - g_free (classid); + g_string_truncate (part_id, len); } static void -efhd_format_secure (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - CamelCipherValidity *valid, - GCancellable *cancellable) +efhd_parse_optional (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) { - EMFormatClass *format_class; + EMFormatAttachmentPURI *puri; + gint len; + + len = part_id->len; + g_string_append (part_id, ".optional"); + + puri = (EMFormatAttachmentPURI *) em_format_puri_new ( + emf, sizeof (EMFormatAttachmentPURI), part, part_id->str); + puri->puri.free = efhd_free_attach_puri_data; + puri->puri.write_func = efhd_write_attachment; + puri->puri.widget_func = efhd_attachment_optional; + puri->attachment_view_part_id = g_strdup (part_id->str); + puri->handle = em_format_find_handler (emf, "text/plain"); + puri->shown = FALSE; + puri->snoop_mime_type = "text/plain"; + puri->attachment = e_attachment_new (); + e_attachment_set_mime_part (puri->attachment, puri->puri.part); + puri->description = g_strdup(_("Evolution cannot render this email as it is too " + "large to process. You can view it unformatted or " + "with an external text editor.")); + + puri->mstream = CAMEL_STREAM_MEM (camel_stream_mem_new ()); + camel_data_wrapper_decode_to_stream_sync ((CamelDataWrapper *) part, + (CamelStream *) puri->mstream, cancellable, NULL); + + if (info->validity) { + puri->puri.validity = camel_cipher_validity_clone (info->validity); + } - format_class = g_type_class_peek (EM_TYPE_FORMAT); - format_class->format_secure (emf, stream, part, valid, cancellable); + em_format_add_puri (emf, (EMFormatPURI *) puri); - if (emf->valid == valid - && (valid->encrypt.status != CAMEL_CIPHER_VALIDITY_ENCRYPT_NONE - || valid->sign.status != CAMEL_CIPHER_VALIDITY_SIGN_NONE)) { + g_string_truncate (part_id, len); +} + +static void +efhd_parse_secure (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) +{ + if (info->validity + && (info->validity->encrypt.status != CAMEL_CIPHER_VALIDITY_ENCRYPT_NONE + || info->validity->sign.status != CAMEL_CIPHER_VALIDITY_SIGN_NONE)) { GString *buffer; - gchar *classid; - struct _smime_pobject *pobj; + EMFormatSMIMEPURI *pobj; - buffer = g_string_sized_new (1024); + pobj = (EMFormatSMIMEPURI *) em_format_puri_new ( + emf, sizeof (EMFormatSMIMEPURI), part, part_id->str); + pobj->puri.free = efhd_xpkcs7mime_free; + pobj->valid = camel_cipher_validity_clone (info->validity); + pobj->puri.widget_func = efhd_xpkcs7mime_button; + pobj->puri.write_func = efhd_write_secure_button; - g_string_append_printf ( - buffer, - "<table border=0 width=\"100%%\" " - "cellpadding=3 cellspacing=0%s><tr>", - smime_sign_colour[valid->sign.status]); + em_format_add_puri (emf, (EMFormatPURI *) pobj); - classid = g_strdup_printf ( - "smime:///em-format-html/%s/icon/signed", - emf->part_id->str); - pobj = (struct _smime_pobject *) em_format_html_add_pobject ( - EM_FORMAT_HTML (emf), sizeof (*pobj), - classid, part, efhd_xpkcs7mime_button); - pobj->valid = camel_cipher_validity_clone (valid); - pobj->object.free = efhd_xpkcs7mime_free; - g_string_append_printf ( - buffer, - "<td valign=center><object classid=\"%s\">" - "</object></td><td width=100%% valign=center>", - classid); - g_free (classid); + buffer = g_string_new (""); - if (valid->sign.status != CAMEL_CIPHER_VALIDITY_SIGN_NONE) { + if (info->validity->sign.status != CAMEL_CIPHER_VALIDITY_SIGN_NONE) { const gchar *desc; gint status; - status = valid->sign.status; + status = info->validity->sign.status; desc = smime_sign_table[status].shortdesc; g_string_append (buffer, gettext (desc)); em_format_html_format_cert_infos ( - &valid->sign.signers, buffer); + &info->validity->sign.signers, buffer); } - if (valid->encrypt.status != CAMEL_CIPHER_VALIDITY_ENCRYPT_NONE) { + if (info->validity->encrypt.status != CAMEL_CIPHER_VALIDITY_ENCRYPT_NONE) { const gchar *desc; gint status; - if (valid->sign.status != CAMEL_CIPHER_VALIDITY_SIGN_NONE) - g_string_append (buffer, "<br>"); + if (info->validity->sign.status != CAMEL_CIPHER_VALIDITY_SIGN_NONE) + g_string_append (buffer, "\n"); - status = valid->encrypt.status; + status = info->validity->encrypt.status; desc = smime_encrypt_table[status].shortdesc; g_string_append (buffer, gettext (desc)); } - g_string_append (buffer, "</td></tr></table>"); - - camel_stream_write ( - stream, buffer->str, buffer->len, cancellable, NULL); - - g_string_free (buffer, TRUE); + pobj->description = g_string_free (buffer, FALSE); } } +/******************************************************************************/ static void -attachment_load_finish (EAttachment *attachment, - GAsyncResult *result, - GFile *file) +efhd_write_attachment_bar (EMFormat *emf, + EMFormatPURI *puri, + CamelStream *stream, + EMFormatWriterInfo *info, + GCancellable *cancellable) { - EShell *shell; - GtkWindow *parent; + EMFormatAttachmentBarPURI *efab = (EMFormatAttachmentBarPURI *) puri; + gchar *str; - e_attachment_load_finish (attachment, result, NULL); + if (info->mode == EM_FORMAT_WRITE_MODE_PRINTING) + return; - shell = e_shell_get_default (); - parent = e_shell_get_active_window (shell); + if (e_attachment_store_get_num_attachments (efab->store) == 0) + return; - e_attachment_save_async ( - attachment, file, (GAsyncReadyCallback) - e_attachment_save_handle_error, parent); + str = g_strdup_printf ( + "<object type=\"application/x-attachment-bar\" " + "height=\"20\" width=\"100%%\" " + "id=\"%s\"data=\"%s\"></object>", puri->uri, puri->uri); - g_object_unref (file); + camel_stream_write_string (stream, str, cancellable, NULL); + + g_free (str); } static void -action_image_save_cb (GtkAction *action, - EMFormatHTMLDisplay *efhd) +efhd_write_attachment (EMFormat *emf, + EMFormatPURI *puri, + CamelStream *stream, + EMFormatWriterInfo *info, + GCancellable *cancellable) { - EWebView *web_view; - EMFormat *emf; - const gchar *image_src; - CamelMimePart *part; - EAttachment *attachment; - GFile *file; + gchar *str, *desc; + const gchar *mime_type; + gchar *button_id; - web_view = em_format_html_get_web_view (EM_FORMAT_HTML (efhd)); - g_return_if_fail (web_view != NULL); + EMFormatAttachmentPURI *efa = (EMFormatAttachmentPURI *) puri; - image_src = e_web_view_get_cursor_image_src (web_view); - if (!image_src) - return; + /* If the attachment is requested as RAW, then call the handler directly + * and do not append any other code. */ + if ((info->mode == EM_FORMAT_WRITE_MODE_RAW) && + efa->handle && efa->handle->write_func) { - emf = EM_FORMAT (efhd); - g_return_if_fail (emf != NULL); - g_return_if_fail (emf->message != NULL); + efa->handle->write_func (emf, puri, stream, info, cancellable); + return; + } - if (g_str_has_prefix (image_src, "cid:")) { - part = camel_mime_message_get_part_by_content_id ( - emf->message, image_src + 4); - g_return_if_fail (part != NULL); + if (info->mode == EM_FORMAT_WRITE_MODE_PRINTING) { - g_object_ref (part); - } else { - CamelStream *image_stream; - CamelDataWrapper *dw; - const gchar *filename; - - image_stream = em_format_html_get_cached_image ( - EM_FORMAT_HTML (efhd), image_src); - if (!image_stream) - return; - - filename = strrchr (image_src, '/'); - if (filename && strchr (filename, '?')) - filename = NULL; - else if (filename) - filename = filename + 1; - - part = camel_mime_part_new (); - if (filename) - camel_mime_part_set_filename (part, filename); - - dw = camel_data_wrapper_new (); - camel_data_wrapper_set_mime_type ( - dw, "application/octet-stream"); - camel_data_wrapper_construct_from_stream_sync ( - dw, image_stream, NULL, NULL); - camel_medium_set_content (CAMEL_MEDIUM (part), dw); - g_object_unref (dw); - - camel_mime_part_set_encoding ( - part, CAMEL_TRANSFER_ENCODING_BASE64); - - g_object_unref (image_stream); - } + if (efa->handle && efa->handle->write_func) + efa->handle->write_func (emf, puri, stream, info, cancellable); - file = e_shell_run_save_dialog ( - e_shell_get_default (), - _("Save Image"), camel_mime_part_get_filename (part), - NULL, NULL, NULL); - if (file == NULL) { - g_object_unref (part); return; } - attachment = e_attachment_new (); - e_attachment_set_mime_part (attachment, part); + if (efa->handle) + mime_type = efa->handle->mime_type; + else + mime_type = efa->snoop_mime_type; + + button_id = g_strconcat (puri->uri, ".attachment_button", NULL); + + desc = em_format_describe_part (puri->part, mime_type); + str = g_strdup_printf ( + "<div class=\"attachment\">" + "<table width=\"100%%\" border=\"0\">" + "<tr valign=\"middle\">" + "<td align=\"left\" width=\"100\">" + "<object type=\"application/x-attachment-button\" " + "height=\"20\" width=\"100\" data=\"%s\" id=\"%s\"></object>" + "</td>" + "<td align=\"left\">%s</td>" + "</tr>", puri->uri, button_id, desc); + + camel_stream_write_string (stream, str, cancellable, NULL); + g_free (desc); + g_free (button_id); + g_free (str); + + /* If we know how to write the attachment, then do it */ + if ((efa->handle && efa->handle->write_func) || + (efa->attachment_view_part_id)) { + + str = g_strdup_printf ( + "<tr><td colspan=\"2\">" + "<div class=\"attachment-wrapper\" id=\"%s\">", + puri->uri); + + camel_stream_write_string (stream, str, cancellable, NULL); + g_free (str); + + if (efa->handle->write_func) { + efa->handle->write_func ( + emf, puri, stream, info, cancellable); + } else if (efa->attachment_view_part_id) { + EMFormatPURI *p; + + p = em_format_find_puri ( + emf, efa->attachment_view_part_id); + if (p && p->write_func) + p->write_func (emf, p, stream, info, cancellable); + } - e_attachment_load_async ( - attachment, (GAsyncReadyCallback) - attachment_load_finish, file); + camel_stream_write_string (stream, "</div></td></tr>", cancellable, NULL); + } - g_object_unref (part); + camel_stream_write_string (stream, "</table></div>", cancellable, NULL); } static void -efhd_web_view_update_actions_cb (EWebView *web_view, - EMFormatHTMLDisplay *efhd) +efhd_write_secure_button (EMFormat *emf, + EMFormatPURI *puri, + CamelStream *stream, + EMFormatWriterInfo *info, + GCancellable *cancellable) { - const gchar *image_src; - gboolean visible; - GtkAction *action; - - g_return_if_fail (web_view != NULL); + gchar *str; - image_src = e_web_view_get_cursor_image_src (web_view); - visible = image_src && g_str_has_prefix (image_src, "cid:"); - if (!visible && image_src) { - CamelStream *image_stream; + if ((info->mode != EM_FORMAT_WRITE_MODE_NORMAL) && + (info->mode != EM_FORMAT_WRITE_MODE_RAW)) + return; - image_stream = em_format_html_get_cached_image ( - EM_FORMAT_HTML (efhd), image_src); - visible = image_stream != NULL; + str = g_strdup_printf ( + "<object type=\"application/x-secure-button\" " + "height=\"20\" width=\"100%%\" " + "data=\"%s\" id=\"%s\"></object>", puri->uri, puri->uri); - if (image_stream) - g_object_unref (image_stream); - } + camel_stream_write_string (stream, str, cancellable, NULL); - action = e_web_view_get_action (web_view, "efhd-image-save"); - if (action) - gtk_action_set_visible (action, visible); + g_free (str); } -static GtkActionEntry image_entries[] = { - { "efhd-image-save", - GTK_STOCK_SAVE, - N_("Save _Image..."), - NULL, - N_("Save the image to a file"), - G_CALLBACK (action_image_save_cb) } -}; - -static const gchar *image_ui = - "<ui>" - " <popup name='context'>" - " <placeholder name='custom-actions-2'>" - " <menuitem action='efhd-image-save'/>" - " </placeholder>" - " </popup>" - "</ui>"; - static void efhd_finalize (GObject *object) { @@ -841,84 +842,77 @@ efhd_finalize (GObject *object) efhd = EM_FORMAT_HTML_DISPLAY (object); g_return_if_fail (efhd != NULL); - if (efhd->priv->attachment_views) { - g_hash_table_foreach ( - efhd->priv->attachment_views, - weak_unref_attachment_view_cb, efhd); - g_hash_table_destroy (efhd->priv->attachment_views); - efhd->priv->attachment_views = NULL; - } - /* Chain up to parent's finalize() method. */ - G_OBJECT_CLASS (em_format_html_display_parent_class)-> - finalize (object); + G_OBJECT_CLASS (parent_class)->finalize (object); } static void -em_format_html_display_class_init (EMFormatHTMLDisplayClass *class) +efhd_preparse (EMFormat *emf) +{ + EMFormatHTMLDisplay *efhd = (EMFormatHTMLDisplay *) emf; + + efhd->priv->last_view = NULL; +} + +static void +efhd_class_init (EMFormatHTMLDisplayClass *class) { GObjectClass *object_class; - EMFormatClass *format_class; EMFormatHTMLClass *format_html_class; + EMFormatClass *format_class; + parent_class = g_type_class_peek_parent (class); g_type_class_add_private (class, sizeof (EMFormatHTMLDisplayPrivate)); object_class = G_OBJECT_CLASS (class); object_class->finalize = efhd_finalize; - format_class = EM_FORMAT_CLASS (class); - format_class->format_clone = efhd_format_clone; - format_class->format_attachment = efhd_format_attachment; - format_class->format_optional = efhd_format_optional; - format_class->format_secure = efhd_format_secure; - format_html_class = EM_FORMAT_HTML_CLASS (class); format_html_class->html_widget_type = E_TYPE_MAIL_DISPLAY; + format_class = EM_FORMAT_CLASS (class); + format_class->preparse = efhd_preparse; + efhd_builtin_init (class); } static void -em_format_html_display_init (EMFormatHTMLDisplay *efhd) +efhd_init (EMFormatHTMLDisplay *efhd) { - EWebView *web_view; - GtkActionGroup *image_actions; - GtkUIManager *ui_manager; - GError *error = NULL; - - web_view = em_format_html_get_web_view (EM_FORMAT_HTML (efhd)); - efhd->priv = EM_FORMAT_HTML_DISPLAY_GET_PRIVATE (efhd); - efhd->priv->attachment_views = g_hash_table_new_full ( - g_str_hash, g_str_equal, g_free, NULL); - efhd->priv->attachment_expanded = FALSE; - - e_mail_display_set_formatter ( - E_MAIL_DISPLAY (web_view), EM_FORMAT_HTML (efhd)); - /* we want to convert url's etc */ + /* we want to convert url's etc */ EM_FORMAT_HTML (efhd)->text_html_flags |= CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS | CAMEL_MIME_FILTER_TOHTML_CONVERT_ADDRESSES; - image_actions = e_web_view_get_action_group (web_view, "image"); - g_return_if_fail (image_actions != NULL); - - gtk_action_group_add_actions ( - image_actions, image_entries, - G_N_ELEMENTS (image_entries), efhd); +} - /* Because we are loading from a hard-coded string, there is - * no chance of I/O errors. Failure here implies a malformed - * UI definition. Full stop. */ - ui_manager = e_web_view_get_ui_manager (web_view); - gtk_ui_manager_add_ui_from_string (ui_manager, image_ui, -1, &error); - if (error != NULL) - g_error ("%s", error->message); +GType +em_format_html_display_get_type (void) +{ + static GType type = 0; + + if (G_UNLIKELY (type == 0)) { + static const GTypeInfo type_info = { + sizeof (EMFormatHTMLDisplayClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) efhd_class_init, + (GClassFinalizeFunc) NULL, + NULL, /* class_data */ + sizeof (EMFormatHTMLDisplay), + 0, /* n_preallocs */ + (GInstanceInitFunc) efhd_init, + NULL /* value_table */ + }; + + type = g_type_register_static ( + EM_TYPE_FORMAT_HTML, "EMFormatHTMLDisplay", + &type_info, 0); + } - g_signal_connect ( - web_view, "update-actions", - G_CALLBACK (efhd_web_view_update_actions_cb), efhd); + return type; } EMFormatHTMLDisplay * @@ -930,8 +924,11 @@ em_format_html_display_new (void) /* ********************************************************************** */ static EMFormatHandler type_builtin_table[] = { - { (gchar *) "x-evolution/message/prefix", efhd_message_prefix }, - { (gchar *) "x-evolution/message/post-header", (EMFormatFunc)efhd_message_add_bar } + { (gchar *) "x-evolution/message/prefix", efhd_message_prefix, }, + { (gchar *) "x-evolution/message/attachment-bar", (EMFormatParseFunc) efhd_message_add_bar, efhd_write_attachment_bar, }, + { (gchar *) "x-evolution/message/attachment", efhd_parse_attachment, efhd_write_attachment, }, + { (gchar *) "x-evolution/message/x-secure-button", efhd_parse_secure, efhd_write_secure_button, }, + { (gchar *) "x-evolution/message/optional", efhd_parse_optional, }, }; static void @@ -939,81 +936,51 @@ efhd_builtin_init (EMFormatHTMLDisplayClass *efhc) { gint i; - for (i = 0; i < G_N_ELEMENTS (type_builtin_table); i++) - em_format_class_add_handler ((EMFormatClass *) efhc, &type_builtin_table[i]); -} - -static void -efhd_write_image (EMFormat *emf, - CamelStream *stream, - EMFormatPURI *puri, - GCancellable *cancellable) -{ - CamelDataWrapper *dw = camel_medium_get_content ((CamelMedium *) puri->part); + EMFormatClass *emfc = (EMFormatClass *) efhc; - /* TODO: identical to efh_write_image */ - d(printf("writing image '%s'\n", puri->cid)); - camel_data_wrapper_decode_to_stream_sync ( - dw, stream, cancellable, NULL); - camel_stream_close (stream, cancellable, NULL); + for (i = 0; i < G_N_ELEMENTS (type_builtin_table); i++) + em_format_class_add_handler (emfc, &type_builtin_table[i]); } static void efhd_message_prefix (EMFormat *emf, - CamelStream *stream, CamelMimePart *part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) { const gchar *flag, *comp, *due; time_t date; gchar *iconpath, *due_date_str; GString *buffer; + EMFormatAttachmentPURI *puri; - if (emf->folder == NULL || emf->uid == NULL - || (flag = camel_folder_get_message_user_tag(emf->folder, emf->uid, "follow-up")) == NULL + if (emf->folder == NULL || emf->message_uid == NULL + || (flag = camel_folder_get_message_user_tag(emf->folder, emf->message_uid, "follow-up")) == NULL || flag[0] == 0) return; - buffer = g_string_sized_new (1024); + puri = (EMFormatAttachmentPURI *) em_format_puri_new ( + emf, sizeof (EMFormatAttachmentPURI), part, ".message_prefix"); - /* header displayed for message-flags in mail display */ - g_string_append ( - buffer, - "<table border=1 width=\"100%%\" " - "cellspacing=2 cellpadding=2><tr>"); + puri->attachment_view_part_id = g_strdup (part_id->str); - comp = camel_folder_get_message_user_tag(emf->folder, emf->uid, "completed-on"); + comp = camel_folder_get_message_user_tag(emf->folder, emf->message_uid, "completed-on"); iconpath = e_icon_factory_get_icon_filename (comp && comp[0] ? "stock_mail-flag-for-followup-done" : "stock_mail-flag-for-followup", GTK_ICON_SIZE_MENU); if (iconpath) { - CamelMimePart *iconpart; - - iconpart = em_format_html_file_part ( - (EMFormatHTML *)emf, "image/png", - iconpath, cancellable); - g_free (iconpath); - if (iconpart) { - gchar *classid; - - classid = g_strdup_printf ( - "icon:///em-format-html-display/%s/%s", - emf->part_id->str, - comp && comp[0] ? "comp" : "uncomp"); - g_string_append_printf ( - buffer, - "<td align=\"left\">" - "<img src=\"%s\"></td>", - classid); - (void) em_format_add_puri ( - emf, sizeof (EMFormatPURI), - classid, iconpart, efhd_write_image); - g_free (classid); - g_object_unref (iconpart); - } + gchar *classid; + + classid = g_strdup_printf ( + "icon:///em-format-html-display/%s/%s", + part_id->str, + comp && comp[0] ? "comp" : "uncomp"); + + puri->puri.uri = classid; + + g_free (classid); } - g_string_append (buffer, "<td align=\"left\" width=\"100%%\">"); + buffer = g_string_new (""); if (comp && comp[0]) { date = camel_header_decode_date (comp, NULL); @@ -1024,7 +991,7 @@ efhd_message_prefix (EMFormat *emf, flag, _("Completed on"), due_date_str ? due_date_str : "???"); g_free (due_date_str); - } else if ((due = camel_folder_get_message_user_tag(emf->folder, emf->uid, "due-by")) != NULL && due[0]) { + } else if ((due = camel_folder_get_message_user_tag(emf->folder, emf->message_uid, "due-by")) != NULL && due[0]) { time_t now; date = camel_header_decode_date (due, NULL); @@ -1032,7 +999,7 @@ efhd_message_prefix (EMFormat *emf, if (now > date) g_string_append_printf ( buffer, - "<b>%s</b> ", + "<b>%s</b> ", _("Overdue:")); due_date_str = e_datetime_format_format ( @@ -1048,270 +1015,84 @@ efhd_message_prefix (EMFormat *emf, g_string_append (buffer, flag); } - g_string_append (buffer, "</td></tr></table>"); - - camel_stream_write ( - stream, buffer->str, buffer->len, cancellable, NULL); - - g_string_free (buffer, TRUE); -} - -/* ********************************************************************** */ - -static void -efhd_attachment_button_expanded (EAttachmentButton *button, - GParamSpec *pspec, - struct _attach_puri *info) -{ - EMFormatHTML *efh; - EMFormatHTMLDisplay *efhd; - - /* FIXME The PURI struct seems to have some lifecycle issues, - * because casting info->puri.format to an EMFormatHTML - * can lead to crashes. So we hack around it. */ - efh = g_object_get_data (G_OBJECT (button), "efh"); - g_return_if_fail (EM_IS_FORMAT_HTML (efh)); - - if (efh->state == EM_FORMAT_HTML_STATE_RENDERING) - return; - - info->shown = e_attachment_button_get_expanded (button); - - em_format_set_inline ( - info->puri.format, info->puri.part_id, info->shown); - - efhd = (EMFormatHTMLDisplay *) efh; - g_return_if_fail (EM_IS_FORMAT_HTML_DISPLAY (efhd)); - - efhd->priv->attachment_expanded = TRUE; -} - -/* ********************************************************************** */ - -static void -attachment_button_realized (GtkWidget *widget) -{ - EMFormatHTML *efh = g_object_get_data (G_OBJECT (widget), "efh"); - EMFormatHTMLDisplay *efhd; - g_return_if_fail (EM_IS_FORMAT_HTML (efh)); - - efhd = (EMFormatHTMLDisplay *) efh; - g_return_if_fail (EM_IS_FORMAT_HTML_DISPLAY (efhd)); - - gtk_widget_grab_focus (widget); - efhd->priv->attachment_expanded = FALSE; + puri->description = g_string_free (buffer, FALSE); } /* ********************************************************************** */ /* attachment button callback */ -static gboolean -efhd_attachment_button (EMFormatHTML *efh, - GtkHTMLEmbedded *eb, - EMFormatHTMLPObject *pobject) +static GtkWidget * +efhd_attachment_button (EMFormat *emf, + EMFormatPURI *puri, + GCancellable *cancellable) { - EMFormatHTMLDisplay *efhd = (EMFormatHTMLDisplay *) efh; - struct _attach_puri *info; - EAttachmentView *view; - EAttachmentStore *store; - EAttachment *attachment; - EWebView *web_view; + EMFormatAttachmentPURI *info = (EMFormatAttachmentPURI *) puri; GtkWidget *widget; - gpointer parent; - EMFormat *emf = (EMFormat *) efh; - guint32 size = 0; /* FIXME: handle default shown case */ d(printf("adding attachment button/content\n")); - if (emf->folder && emf->folder->summary && emf->uid) { - CamelMessageInfo *mi; - - mi = camel_folder_summary_get (emf->folder->summary, emf->uid); - if (mi) { - const CamelMessageContentInfo *ci; - - ci = camel_folder_summary_guess_content_info (mi, camel_mime_part_get_content_type (pobject->part)); - if (ci) { - size = ci->size; - /* what if its not encoded in base64 ? is it a case to consider? */ - if (ci->encoding && !g_ascii_strcasecmp (ci->encoding, "base64")) - size = size / 1.37; - } - camel_message_info_free (mi); - } - } - - info = (struct _attach_puri *) em_format_find_puri ((EMFormat *) efh, pobject->classid); + if (g_cancellable_is_cancelled (cancellable)) + return NULL; if (!info || info->forward) { g_warning ("unable to expand the attachment\n"); - return TRUE; + return NULL; } - attachment = info->attachment; - e_attachment_set_shown (attachment, info->shown); - e_attachment_set_signed (attachment, info->sign); - e_attachment_set_encrypted (attachment, info->encrypt); - e_attachment_set_can_show (attachment, info->handle != NULL); - - web_view = em_format_html_get_web_view (efh); - g_return_val_if_fail (web_view != NULL, TRUE); - parent = gtk_widget_get_toplevel (GTK_WIDGET (web_view)); - parent = gtk_widget_is_toplevel (parent) ? parent : NULL; - - view = em_format_html_display_get_attachment_view (efhd, info->attachment_view_part_id); - g_return_val_if_fail (view != NULL, TRUE); - gtk_widget_show (GTK_WIDGET (view)); - - store = e_attachment_view_get_store (view); - e_attachment_store_add_attachment (store, info->attachment); - - e_attachment_load_async ( - info->attachment, (GAsyncReadyCallback) - e_attachment_load_handle_error, parent); - if (size != 0) { - GFileInfo *fileinfo; - - fileinfo = e_attachment_get_file_info (info->attachment); - g_file_info_set_size (fileinfo, size); - e_attachment_set_file_info (info->attachment, fileinfo); - } - - widget = e_attachment_button_new (view); + widget = e_attachment_button_new (); + g_object_set_data (G_OBJECT (widget), "uri", puri->uri); e_attachment_button_set_attachment ( - E_ATTACHMENT_BUTTON (widget), attachment); + E_ATTACHMENT_BUTTON (widget), info->attachment); + e_attachment_button_set_view ( + E_ATTACHMENT_BUTTON (widget), + EM_FORMAT_HTML_DISPLAY (emf)->priv->last_view); + gtk_widget_set_can_focus (widget, TRUE); - gtk_container_add (GTK_CONTAINER (eb), widget); gtk_widget_show (widget); - /* FIXME Not sure why the expanded callback can't just use - * info->puri.format, but there seems to be lifecycle - * issues with the PURI struct. Maybe it should have - * a reference count? */ - g_object_set_data (G_OBJECT (widget), "efh", efh); - - g_signal_connect ( - widget, "notify::expanded", - G_CALLBACK (efhd_attachment_button_expanded), info); - - /* If the button is created, then give it focus after - * it is realized, so that user can use arrow keys to scroll - * message */ - if (efhd->priv->attachment_expanded) { - g_signal_connect ( - widget, "realize", - G_CALLBACK (attachment_button_realized), NULL); - } - - return TRUE; -} - -static void -efhd_attachment_frame (EMFormat *emf, - CamelStream *stream, - EMFormatPURI *puri, - GCancellable *cancellable) -{ - struct _attach_puri *info = (struct _attach_puri *) puri; - - if (info->shown) - info->handle->handler ( - emf, stream, info->puri.part, - info->handle, cancellable, FALSE); - - camel_stream_close (stream, cancellable, NULL); -} - -static void -set_size_request_cb (gpointer message_part_id, - gpointer widget, - gpointer width) -{ - gtk_widget_set_size_request (widget, GPOINTER_TO_INT (width), -1); + return widget; } -static void -efhd_bar_resize (EMFormatHTML *efh, - GtkAllocation *event) +static GtkWidget * +efhd_attachment_bar (EMFormat *emf, + EMFormatPURI *puri, + GCancellable *cancellable) { - EMFormatHTMLDisplayPrivate *priv; - GtkAllocation allocation; - EWebView *web_view; + EMFormatAttachmentBarPURI *abp = (EMFormatAttachmentBarPURI *) puri; GtkWidget *widget; - gint width; - priv = EM_FORMAT_HTML_DISPLAY_GET_PRIVATE (efh); + widget = e_mail_attachment_bar_new (abp->store); + EM_FORMAT_HTML_DISPLAY (emf)->priv->last_view = (EAttachmentView *) widget; - web_view = em_format_html_get_web_view (efh); - - widget = GTK_WIDGET (web_view); - gtk_widget_get_allocation (widget, &allocation); - width = allocation.width - 12; - - if (width > 0) { - g_hash_table_foreach (priv->attachment_views, set_size_request_cb, GINT_TO_POINTER (width)); - } + return widget; } -static gboolean -efhd_add_bar (EMFormatHTML *efh, - GtkHTMLEmbedded *eb, - EMFormatHTMLPObject *pobject) +static void +efhd_message_add_bar (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) { - EMFormatHTMLDisplayPrivate *priv; - GtkWidget *widget; - - /* XXX See note in efhd_message_add_bar(). */ - if (!EM_IS_FORMAT_HTML_DISPLAY (efh)) - return FALSE; - - g_return_val_if_fail (pobject != NULL && pobject->classid != NULL, FALSE); - g_return_val_if_fail (g_str_has_prefix (pobject->classid, "attachment-bar:"), FALSE); - - priv = EM_FORMAT_HTML_DISPLAY_GET_PRIVATE (efh); + EMFormatAttachmentBarPURI *puri; + gint len; - widget = e_mail_attachment_bar_new (); - gtk_container_add (GTK_CONTAINER (eb), widget); - - g_hash_table_insert (priv->attachment_views, g_strdup (strchr (pobject->classid, ':') + 1), widget); - g_object_weak_ref (G_OBJECT (widget), efhd_attachment_view_gone_cb, efh); - gtk_widget_hide (widget); + if (g_cancellable_is_cancelled (cancellable)) + return; - g_signal_connect_swapped ( - eb, "size-allocate", - G_CALLBACK (efhd_bar_resize), efh); + len = part_id->len; + g_string_append (part_id, ".attachment-bar"); + puri = (EMFormatAttachmentBarPURI *) em_format_puri_new ( + emf, sizeof (EMFormatAttachmentBarPURI), part, part_id->str); + puri->puri.write_func = efhd_write_attachment_bar; + puri->puri.widget_func = efhd_attachment_bar; + puri->puri.free = efhd_attachment_bar_puri_free; + puri->store = E_ATTACHMENT_STORE (e_attachment_store_new ()); - return TRUE; -} + em_format_add_puri (emf, (EMFormatPURI *) puri); -static void -efhd_message_add_bar (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const EMFormatHandler *info) -{ - gchar *classid; - gchar *content; - - classid = g_strdup_printf ( - "attachment-bar:%s", emf->current_message_part_id); - - /* XXX Apparently this installs the callback for -all- - * EMFormatHTML subclasses, not just this subclass. - * Bad idea. So we have to filter out other types - * in the callback. */ - em_format_html_add_pobject ( - EM_FORMAT_HTML (emf), - sizeof (EMFormatHTMLPObject), - classid, part, efhd_add_bar); - - content = g_strdup_printf ( - "<td><object classid=\"%s\"></object></td>", classid); - camel_stream_write_string (stream, content, NULL, NULL); - g_free (content); - - g_free (classid); + g_string_truncate (part_id, len); } static void @@ -1329,41 +1110,28 @@ efhd_optional_button_show (GtkWidget *widget, } } -static void -efhd_resize (GtkWidget *w, - GtkAllocation *event, - EMFormatHTML *efh) -{ - EWebView *web_view; - GtkAllocation allocation; - - web_view = em_format_html_get_web_view (efh); - gtk_widget_get_allocation (GTK_WIDGET (web_view), &allocation); - gtk_widget_set_size_request (w, allocation.width - 48, 250); -} - /* optional render attachment button callback */ -static gboolean -efhd_attachment_optional (EMFormatHTML *efh, - GtkHTMLEmbedded *eb, - EMFormatHTMLPObject *pobject) +static GtkWidget * +efhd_attachment_optional (EMFormat *efh, + EMFormatPURI *puri, + GCancellable *cancellable) { - struct _attach_puri *info; GtkWidget *hbox, *vbox, *button, *mainbox, *scroll, *label, *img; AtkObject *a11y; GtkWidget *view; - GtkAllocation allocation; GtkTextBuffer *buffer; GByteArray *byte_array; - EWebView *web_view; + EMFormatAttachmentPURI *info = (EMFormatAttachmentPURI *) puri; + + if (g_cancellable_is_cancelled (cancellable)) + return NULL; /* FIXME: handle default shown case */ d(printf("adding attachment button/content for optional rendering\n")); - info = (struct _attach_puri *) em_format_find_puri ((EMFormat *) efh, pobject->classid); if (!info || info->forward) { g_warning ("unable to expand the attachment\n"); - return TRUE; + return NULL; } scroll = gtk_scrolled_window_new (NULL, NULL); @@ -1428,28 +1196,19 @@ efhd_attachment_optional (EMFormatHTML *efh, gtk_box_pack_start (GTK_BOX (vbox), scroll, TRUE, TRUE, 6); gtk_widget_show (GTK_WIDGET (view)); - web_view = em_format_html_get_web_view (efh); - gtk_widget_get_allocation (GTK_WIDGET (web_view), &allocation); - gtk_widget_set_size_request (scroll, allocation.width - 48, 250); - g_signal_connect ( - scroll, "size_allocate", - G_CALLBACK (efhd_resize), efh); - gtk_widget_show (scroll); - if (!info->shown) gtk_widget_hide (scroll); gtk_widget_show (vbox); - gtk_container_add (GTK_CONTAINER (eb), vbox); info->handle = NULL; - return TRUE; + return view; } static void efhd_free_attach_puri_data (EMFormatPURI *puri) { - struct _attach_puri *info = (struct _attach_puri *) puri; + EMFormatAttachmentPURI *info = (EMFormatAttachmentPURI *) puri; g_return_if_fail (puri != NULL); @@ -1458,23 +1217,18 @@ efhd_free_attach_puri_data (EMFormatPURI *puri) info->attachment = NULL; } - g_free (info->attachment_view_part_id); - info->attachment_view_part_id = NULL; -} - -/* returned object owned by html_display, thus do not unref it */ -EAttachmentView * -em_format_html_display_get_attachment_view (EMFormatHTMLDisplay *html_display, - const gchar *message_part_id) -{ - gpointer aview; - - g_return_val_if_fail (EM_IS_FORMAT_HTML_DISPLAY (html_display), NULL); - g_return_val_if_fail (message_part_id != NULL, NULL); + if (info->description) { + g_free (info->description); + info->description = NULL; + } - /* it should be added in efhd_add_bar() with this message_part_id */ - aview = g_hash_table_lookup (html_display->priv->attachment_views, message_part_id); - g_return_val_if_fail (aview != NULL, NULL); + if (info->attachment_view_part_id) { + g_free (info->attachment_view_part_id); + info->attachment_view_part_id = NULL; + } - return E_ATTACHMENT_VIEW (aview); + if (info->mstream) { + g_object_unref (info->mstream); + info->mstream = NULL; + } } diff --git a/mail/em-format-html-display.h b/mail/em-format-html-display.h index ec29698d46..c1b22eec58 100644 --- a/mail/em-format-html-display.h +++ b/mail/em-format-html-display.h @@ -52,6 +52,51 @@ G_BEGIN_DECLS typedef struct _EMFormatHTMLDisplay EMFormatHTMLDisplay; typedef struct _EMFormatHTMLDisplayClass EMFormatHTMLDisplayClass; typedef struct _EMFormatHTMLDisplayPrivate EMFormatHTMLDisplayPrivate; +typedef struct _EMFormatAttachmentBarPURI EMFormatAttachmentBarPURI; +typedef struct _EMFormatAttachmentPURI EMFormatAttachmentPURI; +typedef struct _EMFormatSMIMEPURI EMFormatSMIMEPURI; + +struct _EMFormatAttachmentBarPURI { + EMFormatPURI puri; + + EAttachmentStore *store; +}; + +struct _EMFormatAttachmentPURI { + EMFormatPURI puri; + + const EMFormatHandler *handle; + + const gchar *snoop_mime_type; + + /* for the > and V buttons */ + GtkWidget *forward, *down; + guint shown : 1; + + /* Attachment */ + EAttachment *attachment; + gchar *attachment_view_part_id; + gchar *description; + + /* image stuff */ + gint fit_width; + gint fit_height; + GtkImage *image; + GtkWidget *event_box; + + /* Optional Text Mem Stream */ + CamelStreamMem *mstream; +}; + +struct _EMFormatSMIMEPURI { + EMFormatPURI puri; + + gchar *description; + + gint signature; + CamelCipherValidity *valid; + GtkWidget *widget; +}; struct _EMFormatHTMLDisplay { EMFormatHTML parent; @@ -67,10 +112,6 @@ struct _EMFormatHTMLDisplayClass { GType em_format_html_display_get_type (void); EMFormatHTMLDisplay * em_format_html_display_new (void); -EAttachmentView * - em_format_html_display_get_attachment_view - (EMFormatHTMLDisplay *html_display, - const gchar *message_part_id); G_END_DECLS #endif /* EM_FORMAT_HTML_DISPLAY_H */ diff --git a/mail/em-format-html-print.c b/mail/em-format-html-print.c index 0de9e2303d..2f9e556d9e 100644 --- a/mail/em-format-html-print.c +++ b/mail/em-format-html-print.c @@ -28,33 +28,411 @@ #include <string.h> #include <glib/gi18n.h> #include <gtk/gtk.h> -#include <gtkhtml/gtkhtml.h> +#include "em-format-html-print.h" +#include "em-format-html-display.h" +#include "e-mail-attachment-bar.h" #include <e-util/e-print.h> - -#include <libemail-utils/mail-mt.h> +#include <e-util/e-util.h> +#include <widgets/misc/e-attachment-store.h> #include <libemail-engine/mail-ops.h> #include "em-format-html-print.h" -G_DEFINE_TYPE ( - EMFormatHTMLPrint, - em_format_html_print, - EM_TYPE_FORMAT_HTML) +#define d(x) + +static gpointer parent_class = NULL; + +struct _EMFormatHTMLPrintPrivate { + + EMFormatHTML *original_formatter; + EMFormatPURI *top_level_puri; + + /* List of attachment PURIs */ + GList *attachments; + +}; + +enum { + PROP_0, + PROP_ORIGINAL_FORMATTER +}; + +static void efhp_write_print_layout (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable); +static void efhp_write_headers (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable); +static void efhp_write_inline_attachment (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable); + +static void +efhp_write_attachments_list (EMFormatHTMLPrint *efhp, + CamelStream *stream, + EMFormatWriterInfo *info, + GCancellable *cancellable) +{ + GString *str; + GList *iter; + + if (!efhp->priv->attachments) + return; + + str = g_string_new ( + "<table border=\"0\" cellspacing=\"5\" cellpadding=\"0\" " + "class=\"attachments-list\" >\n"); + g_string_append_printf (str, + "<tr><th colspan=\"2\"><h1>%s</h1></td></tr>\n" + "<tr><th>%s</th><th>%s</th></tr>\n", + _("Attachments"), _("Name"), _("Size")); + + for (iter = efhp->priv->attachments; iter; iter = iter->next) { + EMFormatPURI *puri = iter->data; + EAttachment *attachment; + GFileInfo *fi; + gchar *name, *size; + GByteArray *ba; + CamelDataWrapper *dw; + + attachment = ((EMFormatAttachmentPURI *) puri)->attachment; + fi = e_attachment_get_file_info (attachment); + if (!fi) + continue; + + if (e_attachment_get_description (attachment) && + *e_attachment_get_description (attachment)) { + name = g_strdup_printf ("%s (%s)", + e_attachment_get_description (attachment), + g_file_info_get_display_name (fi)); + } else { + name = g_strdup (g_file_info_get_display_name (fi)); + } + + dw = camel_medium_get_content ((CamelMedium *) puri->part); + ba = camel_data_wrapper_get_byte_array (dw); + size = g_format_size (ba->len); + + g_string_append_printf (str, "<tr><td>%s</td><td>%s</td></tr>\n", + name, size); + + g_free (name); + g_free (size); + } + + g_string_append (str, "</table>\n"); + + camel_stream_write_string (stream, str->str, cancellable, NULL); + g_string_free (str, TRUE); +} + +static void +efhp_write_headers (EMFormat *emf, + EMFormatPURI *puri, + CamelStream *stream, + EMFormatWriterInfo *info, + GCancellable *cancellable) +{ + struct _camel_header_raw raw_header; + GString *str, *tmp; + gchar *subject; + const gchar *buf; + EMFormatPURI *p; + GList *iter; + gint attachments_count; + gchar *puri_prefix; + + buf = camel_medium_get_header (CAMEL_MEDIUM (puri->part), "subject"); + subject = camel_header_decode_string (buf, "UTF-8"); + str = g_string_new ("<table border=\"0\" cellspacing=\"5\" " \ + "cellpadding=\"0\" class=\"printing-header\">\n"); + g_string_append_printf ( + str, + "<tr class=\"header-item\">" + "<td colspan=\"2\"><h1>%s</h1></td>" + "</tr>\n", + subject); + g_free (subject); + + for (iter = g_queue_peek_head_link (&emf->header_list); iter; iter = iter->next) { + + EMFormatHeader *header = iter->data; + raw_header.name = header->name; + + /* Skip 'Subject' header, it's already displayed. */ + if (g_ascii_strncasecmp (header->name, "Subject", 7) == 0) + continue; + + if (header->value && *header->value) { + raw_header.value = header->value; + em_format_html_format_header (emf, str, + CAMEL_MEDIUM (puri->part), &raw_header, + header->flags | EM_FORMAT_HTML_HEADER_NOLINKS, + "UTF-8"); + } else { + raw_header.value = g_strdup (camel_medium_get_header ( + CAMEL_MEDIUM (emf->message), header->name)); + + if (raw_header.value && *raw_header.value) { + em_format_html_format_header (emf, str, + CAMEL_MEDIUM (puri->part), &raw_header, + header->flags | EM_FORMAT_HTML_HEADER_NOLINKS, + "UTF-8"); + } + + if (raw_header.value) + g_free (raw_header.value); + } + } + + /* Get prefix of this PURI */ + puri_prefix = g_strndup (puri->uri, g_strrstr (puri->uri, ".") - puri->uri); + + /* Add encryption/signature header */ + raw_header.name = _("Security"); + tmp = g_string_new (""); + /* Find first secured part. */ + for (iter = emf->mail_part_list, puri; iter; iter = iter->next) { + + p = iter->data; + + if (p->validity_type == 0) + continue; + + if (!g_str_has_prefix (p->uri, puri_prefix)) + continue; + + if ((p->validity_type & EM_FORMAT_VALIDITY_FOUND_PGP) && + (p->validity_type & EM_FORMAT_VALIDITY_FOUND_SIGNED)) { + g_string_append (tmp, _("GPG signed")); + } + if ((p->validity_type & EM_FORMAT_VALIDITY_FOUND_PGP) && + (p->validity_type & EM_FORMAT_VALIDITY_FOUND_ENCRYPTED)) { + if (tmp->len > 0) g_string_append (tmp, ", "); + g_string_append (tmp, _("GPG encrpyted")); + } + if ((p->validity_type & EM_FORMAT_VALIDITY_FOUND_SMIME) && + (p->validity_type & EM_FORMAT_VALIDITY_FOUND_SIGNED)) { + + if (tmp->len > 0) g_string_append (tmp, ", "); + g_string_append (tmp, _("S/MIME signed")); + } + if ((p->validity_type & EM_FORMAT_VALIDITY_FOUND_SMIME) && + (p->validity_type & EM_FORMAT_VALIDITY_FOUND_ENCRYPTED)) { + + if (tmp->len > 0) g_string_append (tmp, ", "); + g_string_append (tmp, _("S/MIME encrpyted")); + } + + break; + } + + if (tmp->len > 0) { + raw_header.value = tmp->str; + em_format_html_format_header (emf, str, CAMEL_MEDIUM (p->part), + &raw_header, EM_FORMAT_HEADER_BOLD | EM_FORMAT_HTML_HEADER_NOLINKS, "UTF-8"); + } + g_string_free (tmp, TRUE); + + /* Count attachments and display the number as a header */ + attachments_count = 0; + + for (iter = emf->mail_part_list; iter; iter = iter ? iter->next : iter) { + + p = iter->data; + + if (!g_str_has_prefix (p->uri, puri_prefix)) + continue; + + if ((p->is_attachment || g_str_has_suffix(p->uri, ".attachment")) && + (!p->cid)) { + attachments_count++; + /* EFHD sometimes creates two PURIs per attachment! */ + if (iter->next && iter->next->data) { + EMFormatPURI *p2 = iter->next->data; + if (g_str_has_prefix (p2->uri, p->uri)) + iter = iter->next; + } + } + } + if (attachments_count > 0) { + raw_header.name = _("Attachments"); + raw_header.value = g_strdup_printf ("%d", attachments_count); + em_format_html_format_header (emf, str, CAMEL_MEDIUM (puri->part), + &raw_header, EM_FORMAT_HEADER_BOLD | EM_FORMAT_HTML_HEADER_NOLINKS, "UTF-8"); + g_free (raw_header.value); + } + + g_string_append (str, "</table>"); + + camel_stream_write_string (stream, str->str, cancellable, NULL); + g_string_free (str, TRUE); + g_free (puri_prefix); +} + +static void +efhp_write_inline_attachment (EMFormat *emf, + EMFormatPURI *puri, + CamelStream *stream, + EMFormatWriterInfo *info, + GCancellable *cancellable) +{ + gchar *name; + EMFormatAttachmentPURI *att_puri = (EMFormatAttachmentPURI *) puri; + EAttachment *attachment; + GFileInfo *fi; + + attachment = att_puri->attachment; + fi = e_attachment_get_file_info (attachment); + + if (e_attachment_get_description (attachment) && + *e_attachment_get_description (attachment)) { + name = g_strdup_printf ("<h2>Attachment: %s (%s)</h2>\n", + e_attachment_get_description (attachment), + g_file_info_get_display_name (fi)); + } else { + name = g_strdup_printf ("<h2>Attachment: %s</h2>\n", + g_file_info_get_display_name (fi)); + } + + camel_stream_write_string (stream, name, cancellable, NULL); + g_free (name); +} + +static void +efhp_write_print_layout (EMFormat *emf, + EMFormatPURI *puri, + CamelStream *stream, + EMFormatWriterInfo *info, + GCancellable *cancellable) +{ + GList *iter; + EMFormatWriterInfo print_info = { + EM_FORMAT_WRITE_MODE_PRINTING, FALSE, FALSE }; + EMFormatHTMLPrint *efhp = EM_FORMAT_HTML_PRINT (emf); + + g_list_free (efhp->priv->attachments); + efhp->priv->attachments = NULL; + + camel_stream_write_string (stream, + "<!DOCTYPE HTML>\n<html>\n" + "<head>\n<meta name=\"generator\" content=\"Evolution Mail Component\" />\n" + "<title>Evolution Mail Display</title>\n" + "<link type=\"text/css\" rel=\"stylesheet\" media=\"print\" " + "href=\"evo-file://" EVOLUTION_PRIVDATADIR "/theme/webview-print.css\" />\n" + "</head>\n" + "<body style=\"background: #FFF; color: #000;\">", + cancellable, NULL); + + for (iter = emf->mail_part_list; iter != NULL; iter = iter ? iter->next : iter) { + + EMFormatPURI *puri = iter->data; + + if (g_str_has_suffix (puri->uri, "print_layout")) + continue; + + /* To late to change .headers writer_func, do it manually. */ + if (g_str_has_suffix (puri->uri, ".headers")) { + efhp_write_headers (emf, puri, stream, &print_info, cancellable); + continue; + } + + if (g_str_has_suffix (puri->uri, ".rfc822")) { + + puri->write_func (emf, puri, stream, &print_info, cancellable); + + while (iter && !g_str_has_suffix (puri->uri, ".rfc822.end")) { + + iter = iter->next; + if (iter) + puri = iter->data; + } + + if (!iter) + break; + + continue; + + } + + if (puri->is_attachment || g_str_has_suffix (puri->uri, ".attachment")) { + const EMFormatHandler *handler; + CamelContentType *ct; + gchar *mime_type; + + if (puri->cid && g_ascii_strncasecmp (puri->cid, "cid:", 4) == 0) + continue; + + ct = camel_mime_part_get_content_type (puri->part); + mime_type = camel_content_type_simple (ct); + + handler = em_format_find_handler (puri->emf, mime_type); + d(printf("Handler for PURI %s (%s): %s\n", puri->uri, mime_type, + handler ? handler->mime_type : "(null)")); + g_free (mime_type); + + efhp->priv->attachments = + g_list_append (efhp->priv->attachments, puri); + + /* If we can't inline this attachment, skip it */ + if (handler && puri->write_func) { + efhp_write_inline_attachment (puri->emf, puri, + stream, &print_info, cancellable); + + if (iter->next && iter->next->data) { + EMFormatPURI *p; + p = iter->next->data; + + /* Has the next PURI the same prefix? */ + if (p->write_func && + g_str_has_prefix (p->uri, puri->uri)) { + p->write_func (emf, p, stream, + &print_info, cancellable); + iter = iter->next; + } else { + if (puri->write_func) { + puri->write_func (emf, puri, + stream, &print_info, + cancellable); + } + } + } + } + + continue; + } + + /* Ignore widget parts and unwritable non-attachment parts */ + if (puri->write_func == NULL) + continue; + + /* Passed all tests, probably a regular part - display it */ + puri->write_func (puri->emf, puri, stream, &print_info, cancellable); + + } + + efhp_write_attachments_list (efhp, stream, &print_info, cancellable); + + camel_stream_write_string (stream, "</body></html>", cancellable, NULL); +} static void efhp_finalize (GObject *object) { - EMFormatHTMLPrint *efhp = (EMFormatHTMLPrint *) object; + EMFormatHTMLPrint *efhp = EM_FORMAT_HTML_PRINT (object); - g_free (efhp->export_filename); - efhp->export_filename = NULL; - gtk_widget_destroy (efhp->window); - if (efhp->source != NULL) - g_object_unref (efhp->source); + if (efhp->priv->original_formatter) { + g_object_unref (efhp->priv->original_formatter); + efhp->priv->original_formatter = NULL; + } + + if (efhp->priv->top_level_puri) { + em_format_puri_free (efhp->priv->top_level_puri); + efhp->priv->top_level_puri = NULL; + } + + if (efhp->priv->attachments) { + g_list_free (efhp->priv->attachments); + efhp->priv->attachments = NULL; + } /* Chain up to parent's finalize() method. */ - G_OBJECT_CLASS (em_format_html_print_parent_class)->finalize (object); + G_OBJECT_CLASS (parent_class)->finalize (object); } static gboolean @@ -68,173 +446,182 @@ efhp_is_inline (EMFormat *emf, } static void -em_format_html_print_class_init (EMFormatHTMLPrintClass *class) +efhp_set_orig_formatter (EMFormatHTMLPrint *efhp, + EMFormat *formatter) { - GObjectClass *object_class; - EMFormatClass *format_class; - - object_class = G_OBJECT_CLASS (class); - object_class->finalize = efhp_finalize; - - format_class = EM_FORMAT_CLASS (class); - format_class->is_inline = efhp_is_inline; + EMFormat *emfp, *emfs; + EMFormatPURI *puri; + GHashTableIter iter; + gpointer key, value; + + efhp->priv->original_formatter = g_object_ref (formatter); + + emfp = EM_FORMAT (efhp); + emfs = EM_FORMAT (formatter); + + emfp->mail_part_list = g_list_copy (emfs->mail_part_list); + + /* Make a shallow copy of the table. This table will NOT destroy + * the PURIs when free'd! */ + if (emfp->mail_part_table) + g_hash_table_unref (emfp->mail_part_table); + + emfp->mail_part_table = g_hash_table_new (g_str_hash, g_str_equal); + g_hash_table_iter_init (&iter, emfs->mail_part_table); + while (g_hash_table_iter_next (&iter, &key, &value)) + g_hash_table_insert (emfp->mail_part_table, key, value); + + if (emfs->folder) + emfp->folder = g_object_ref (emfs->folder); + emfp->message_uid = g_strdup (emfs->message_uid); + emfp->message = g_object_ref (emfs->message); + + /* Add a generic PURI that will write a HTML layout + * for all the parts */ + puri = em_format_puri_new (EM_FORMAT (efhp), + sizeof (EMFormatPURI), NULL, "print_layout"); + puri->write_func = efhp_write_print_layout; + puri->mime_type = g_strdup ("text/html"); + em_format_add_puri (EM_FORMAT (efhp), puri); + efhp->priv->top_level_puri = puri; } +static EMFormatHandler type_builtin_table[] = { + { (gchar *) "x-evolution/message/headers", 0, efhp_write_headers, }, +}; + static void -em_format_html_print_init (EMFormatHTMLPrint *efhp) +efhp_builtin_init (EMFormatHTMLPrintClass *efhc) { - EWebView *web_view; + EMFormatClass *emfc; + gint ii; - web_view = em_format_html_get_web_view (EM_FORMAT_HTML (efhp)); + emfc = (EMFormatClass *) efhc; - /* gtk widgets don't like to be realized outside top level widget - * so we put new html widget into gtk window */ - efhp->window = gtk_window_new (GTK_WINDOW_TOPLEVEL); - gtk_container_add (GTK_CONTAINER (efhp->window), GTK_WIDGET (web_view)); - gtk_widget_realize (GTK_WIDGET (web_view)); - efhp->parent.show_icon = FALSE; - ((EMFormat *) efhp)->print = TRUE; - - efhp->export_filename = NULL; - efhp->async = TRUE; + for (ii = 0; ii < G_N_ELEMENTS (type_builtin_table); ii++) + em_format_class_add_handler ( + emfc, &type_builtin_table[ii]); } -EMFormatHTMLPrint * -em_format_html_print_new (EMFormatHTML *source, - GtkPrintOperationAction action) +static void +efhp_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) { - EMFormatHTMLPrint *efhp; + switch (prop_id) { - efhp = g_object_new (EM_TYPE_FORMAT_HTML_PRINT, NULL); - if (source != NULL) - efhp->source = g_object_ref (source); - efhp->action = action; + case PROP_ORIGINAL_FORMATTER: + efhp_set_orig_formatter ( + EM_FORMAT_HTML_PRINT (object), + (EMFormat *) g_value_get_object (value)); + return; + } - return efhp; + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } -static gint -efhp_calc_footer_height (GtkHTML *html, - GtkPrintOperation *operation, - GtkPrintContext *context) +static void +efhp_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) { - PangoContext *pango_context; - PangoFontDescription *desc; - PangoFontMetrics *metrics; - gint footer_height; + EMFormatHTMLPrintPrivate *priv; - pango_context = gtk_print_context_create_pango_context (context); - desc = pango_font_description_from_string ("Sans Regular 10"); + priv = EM_FORMAT_HTML_PRINT (object)->priv; - metrics = pango_context_get_metrics ( - pango_context, desc, pango_language_get_default ()); - footer_height = - pango_font_metrics_get_ascent (metrics) + - pango_font_metrics_get_descent (metrics); - pango_font_metrics_unref (metrics); + switch (prop_id) { - pango_font_description_free (desc); - g_object_unref (pango_context); + case PROP_ORIGINAL_FORMATTER: + g_value_set_pointer (value, + priv->original_formatter); + return; + } - return footer_height; + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } static void -efhp_draw_footer (GtkHTML *html, - GtkPrintOperation *operation, - GtkPrintContext *context, - gint page_nr, - PangoRectangle *rec) +em_format_html_print_base_init (EMFormatHTMLPrintClass *klass) { - PangoFontDescription *desc; - PangoLayout *layout; - gdouble x, y; - gint n_pages; - gchar *text; - cairo_t *cr; - - g_object_get (operation, "n-pages", &n_pages, NULL); - text = g_strdup_printf (_("Page %d of %d"), page_nr + 1, n_pages); - - desc = pango_font_description_from_string ("Sans Regular 10"); - layout = gtk_print_context_create_pango_layout (context); - pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER); - pango_layout_set_font_description (layout, desc); - pango_layout_set_text (layout, text, -1); - pango_layout_set_width (layout, rec->width); - - x = pango_units_to_double (rec->x); - y = pango_units_to_double (rec->y); - - cr = gtk_print_context_get_cairo_context (context); - - cairo_save (cr); - cairo_set_source_rgb (cr, .0, .0, .0); - cairo_move_to (cr, x, y); - pango_cairo_show_layout (cr, layout); - cairo_restore (cr); - - g_object_unref (layout); - pango_font_description_free (desc); - - g_free (text); + efhp_builtin_init (klass); } static void -emfhp_complete (EMFormatHTMLPrint *efhp) +em_format_html_print_class_init (EMFormatHTMLPrintClass *klass) { - GtkPrintOperation *operation; - EWebView *web_view; - GError *error = NULL; + GObjectClass *object_class; + EMFormatClass *format_class; - web_view = em_format_html_get_web_view (EM_FORMAT_HTML (efhp)); + parent_class = g_type_class_peek_parent (klass); + g_type_class_add_private (klass, sizeof (EMFormatHTMLPrintPrivate)); - operation = e_print_operation_new (); + object_class = G_OBJECT_CLASS (klass); + object_class->finalize = efhp_finalize; + object_class->set_property = efhp_set_property; + object_class->get_property = efhp_get_property; - if (efhp->action == GTK_PRINT_OPERATION_ACTION_EXPORT) - gtk_print_operation_set_export_filename (operation, efhp->export_filename); + format_class = EM_FORMAT_CLASS (klass); + format_class->is_inline = efhp_is_inline; - gtk_html_print_operation_run ( - GTK_HTML (web_view), - operation, efhp->action, NULL, - (GtkHTMLPrintCalcHeight) NULL, - (GtkHTMLPrintCalcHeight) efhp_calc_footer_height, - (GtkHTMLPrintDrawFunc) NULL, - (GtkHTMLPrintDrawFunc) efhp_draw_footer, - NULL, &error); + g_object_class_install_property ( + object_class, + PROP_ORIGINAL_FORMATTER, + g_param_spec_object ( + "original-formatter", + NULL, + NULL, + EM_TYPE_FORMAT, + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY)); +} - g_object_unref (operation); +static void +em_format_html_print_init (EMFormatHTMLPrint *efhp) +{ + efhp->priv = G_TYPE_INSTANCE_GET_PRIVATE ( + efhp, EM_TYPE_FORMAT_HTML_PRINT, EMFormatHTMLPrintPrivate); + + efhp->priv->attachments = NULL; + efhp->export_filename = NULL; } -void -em_format_html_print_message (EMFormatHTMLPrint *efhp, - CamelMimeMessage *message, - CamelFolder *folder, - const gchar *message_uid) +GType +em_format_html_print_get_type (void) { - g_return_if_fail (EM_IS_FORMAT_HTML_PRINT (efhp)); - g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message)); - - /* Wrap flags to display all entries by default.*/ - EM_FORMAT_HTML (efhp)->header_wrap_flags |= - EM_FORMAT_HTML_HEADER_TO | - EM_FORMAT_HTML_HEADER_CC | - EM_FORMAT_HTML_HEADER_BCC; - - if (efhp->async) { - g_signal_connect ( - efhp, "complete", G_CALLBACK (emfhp_complete), efhp); - - /* FIXME Not passing a GCancellable here. */ - em_format_format_clone ( - (EMFormat *) efhp, - folder, message_uid, message, - (EMFormat *) efhp->source, NULL); - } else { - em_format_html_clone_sync ( - folder, message_uid, message, - (EMFormatHTML *) efhp, - (EMFormat *) efhp->source); - emfhp_complete (efhp); + static GType type = 0; + + if (G_UNLIKELY (type == 0)) { + static const GTypeInfo type_info = { + sizeof (EMFormatHTMLPrintClass), + (GBaseInitFunc) em_format_html_print_base_init, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) em_format_html_print_class_init, + (GClassFinalizeFunc) NULL, + NULL, /* class_data */ + sizeof (EMFormatHTMLPrint), + 0, /* n_preallocs */ + (GInstanceInitFunc) em_format_html_print_init, + NULL /* value_table */ + }; + + type = g_type_register_static ( + em_format_html_get_type(), "EMFormatHTMLPrint", + &type_info, 0); } + + return type; +} + +EMFormatHTMLPrint * +em_format_html_print_new (EMFormatHTML *source) +{ + EMFormatHTMLPrint *efhp; + + efhp = g_object_new (EM_TYPE_FORMAT_HTML_PRINT, + "original-formatter", source, + NULL); + + return efhp; } diff --git a/mail/em-format-html-print.h b/mail/em-format-html-print.h index 5f08b6ef82..d9fe1ff6d4 100644 --- a/mail/em-format-html-print.h +++ b/mail/em-format-html-print.h @@ -45,20 +45,12 @@ G_BEGIN_DECLS typedef struct _EMFormatHTMLPrint EMFormatHTMLPrint; typedef struct _EMFormatHTMLPrintClass EMFormatHTMLPrintClass; +typedef struct _EMFormatHTMLPrintPrivate EMFormatHTMLPrintPrivate; struct _EMFormatHTMLPrint { EMFormatHTML parent; - - /* Used to realize the gtkhtml in a toplevel. */ - GtkWidget *window; - - /* Used for print_message. */ - EMFormatHTML *source; - - GtkPrintOperationAction action; + EMFormatHTMLPrintPrivate *priv; gchar *export_filename; - - gboolean async; }; struct _EMFormatHTMLPrintClass { @@ -67,12 +59,7 @@ struct _EMFormatHTMLPrintClass { GType em_format_html_print_get_type (void); EMFormatHTMLPrint * - em_format_html_print_new (EMFormatHTML *source, - GtkPrintOperationAction action); -void em_format_html_print_message (EMFormatHTMLPrint *efhp, - CamelMimeMessage *message, - CamelFolder *folder, - const gchar *uid); + em_format_html_print_new (EMFormatHTML *source); G_END_DECLS diff --git a/mail/em-format-html.c b/mail/em-format-html.c index 3c130e63d4..c481693eed 100644 --- a/mail/em-format-html.c +++ b/mail/em-format-html.c @@ -24,6 +24,8 @@ #include <config.h> #endif +#define _GNU_SOURCE /* Enable strcasestr in string.h */ + #include <stdio.h> #include <string.h> #include <sys/types.h> @@ -52,20 +54,20 @@ #include <shell/e-shell.h> -#include <gtkhtml/gtkhtml.h> -#include <gtkhtml/gtkhtml-stream.h> - #include <glib/gi18n.h> -#include <libemail-utils/mail-mt.h> +#include <JavaScriptCore/JavaScript.h> +#include <webkit/webkit.h> +#include <libemail-utils/mail-mt.h> #include <libemail-engine/e-mail-enumtypes.h> #include <libemail-engine/e-mail-utils.h> #include <libemail-engine/mail-config.h> #include "em-format-html.h" -#include "em-html-stream.h" #include "em-utils.h" +#include "e-mail-display.h" +#include <em-format/em-inline-filter.h> #define EM_FORMAT_HTML_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ @@ -73,45 +75,19 @@ #define d(x) -#define EM_FORMAT_HTML_GET_PRIVATE(obj) \ - (G_TYPE_INSTANCE_GET_PRIVATE \ - ((obj), EM_TYPE_FORMAT_HTML, EMFormatHTMLPrivate)) - -#define EFM_MESSAGE_START_ANAME "evolution#message#start" -#define EFH_MESSAGE_START "<A name=\"" EFM_MESSAGE_START_ANAME "\"></A>" - -struct _EMFormatHTMLCache { - CamelMultipart *textmp; - - gchar partid[1]; -}; - struct _EMFormatHTMLPrivate { - EWebView *web_view; - - CamelMimeMessage *last_part; /* not reffed, DO NOT dereference */ - volatile gint format_id; /* format thread id */ - guint format_timeout_id; - struct _format_msg *format_timeout_msg; - - /* Table that re-maps text parts into a mutlipart/mixed, EMFormatHTMLCache * */ - GHashTable *text_inline_parts; - - GQueue pending_jobs; - GMutex *lock; - GdkColor colors[EM_FORMAT_HTML_NUM_COLOR_TYPES]; EMailImageLoadingPolicy image_loading_policy; - EMFormatHTMLHeadersState headers_state; - gboolean headers_collapsable; - - guint load_images_now : 1; + guint can_load_images : 1; guint only_local_photos : 1; guint show_sender_photo : 1; guint show_real_date : 1; + guint animate_images : 1; }; +static gpointer parent_class; + enum { PROP_0, PROP_BODY_COLOR, @@ -125,345 +101,1214 @@ enum { PROP_SHOW_SENDER_PHOTO, PROP_SHOW_REAL_DATE, PROP_TEXT_COLOR, - PROP_WEB_VIEW, - PROP_HEADERS_STATE, - PROP_HEADERS_COLLAPSABLE + PROP_ANIMATE_IMAGES }; -static void efh_url_requested (GtkHTML *html, const gchar *url, GtkHTMLStream *handle, EMFormatHTML *efh); -static gboolean efh_object_requested (GtkHTML *html, GtkHTMLEmbedded *eb, EMFormatHTML *efh); -static void efh_gtkhtml_destroy (GtkHTML *html, EMFormatHTML *efh); +#define EFM_MESSAGE_START_ANAME "evolution_message_start" +#define EFH_MESSAGE_START "<A name=\"" EFM_MESSAGE_START_ANAME "\"></A>" -static void efh_format_message (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback); +static void efh_parse_image (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); +static void efh_parse_text_enriched (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); +static void efh_parse_text_plain (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); +static void efh_parse_text_html (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); +static void efh_parse_message_external (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); +static void efh_parse_message_deliverystatus (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); +static void efh_parse_message_rfc822 (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); + +static void efh_write_image (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable); +static void efh_write_text_enriched (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable); +static void efh_write_text_plain (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable); +static void efh_write_text_html (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable); +static void efh_write_source (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable); +static void efh_write_headers (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable); +static void efh_write_attachment (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable); +static void efh_write_error (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable); +static void efh_write_message_rfc822 (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable); + +static void efh_format_full_headers (EMFormatHTML *efh, GString *buffer, CamelMedium *part, gboolean all_headers, gboolean visible, GCancellable *cancellable); +static void efh_format_short_headers (EMFormatHTML *efh, GString *buffer, CamelMedium *part, gboolean visible, GCancellable *cancellable); + +static void efh_write_message (EMFormat *emf, GList *puris, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable); + +/*****************************************************************************/ +static void +efh_parse_image (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) +{ + EMFormatPURI *puri; + const gchar *tmp; + gchar *cid; + gint len; -static void efh_format_secure (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - CamelCipherValidity *valid, - GCancellable *cancellable); + if (g_cancellable_is_cancelled (cancellable)) + return; -static void efh_builtin_init (EMFormatHTMLClass *efhc); + tmp = camel_mime_part_get_content_id (part); + if (!tmp) { + em_format_parse_part_as (emf, part, part_id, info, + "x-evolution/message/attachment", cancellable); + return; + } -static void efh_write_image (EMFormat *emf, - CamelStream *stream, - EMFormatPURI *puri, - GCancellable *cancellable); + cid = g_strdup_printf ("cid:%s", tmp); + len = part_id->len; + g_string_append (part_id, ".image"); + puri = em_format_puri_new (emf, sizeof (EMFormatPURI), part, part_id->str); + puri->cid = cid; + puri->write_func = efh_write_image; + puri->mime_type = g_strdup (info->handler->mime_type); + puri->is_attachment = TRUE; + puri->validity = info->validity ? camel_cipher_validity_clone (info->validity) : NULL; + puri->validity_type = info->validity_type; -static gpointer parent_class; -static CamelDataCache *emfh_http_cache; + em_format_add_puri (emf, puri); + g_string_truncate (part_id, len); +} -#define EMFH_HTTP_CACHE_PATH "http" +static void +efh_parse_text_enriched (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) +{ + EMFormatPURI *puri; + const gchar *tmp; + gchar *cid; + gint len; -/* Sigh, this is so we have a cancellable, async rendering thread */ -struct _format_msg { - MailMsg base; + if (g_cancellable_is_cancelled (cancellable)) + return; - EMFormatHTML *format; - EMFormat *format_source; - EMHTMLStream *estream; - CamelFolder *folder; - gchar *uid; - CamelMimeMessage *message; - gboolean cancelled; -}; + tmp = camel_mime_part_get_content_id (part); + if (!tmp) { + cid = g_strdup_printf ("em-no-cid:%s", part_id->str); + } else { + cid = g_strdup_printf ("cid:%s", tmp); + } -static gchar * -efh_format_desc (struct _format_msg *m) + len = part_id->len; + g_string_append (part_id, ".text_enriched"); + puri = em_format_puri_new (emf, sizeof (EMFormatPURI), part, part_id->str); + puri->cid = cid; + puri->mime_type = g_strdup (info->handler->mime_type); + puri->write_func = efh_write_text_enriched; + puri->validity = info->validity ? camel_cipher_validity_clone (info->validity) : NULL; + puri->validity_type = info->validity_type; + puri->is_attachment = info->is_attachment; + + em_format_add_puri (emf, puri); + g_string_truncate (part_id, len); +} + +static void +efh_parse_text_plain (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) { - return g_strdup(_("Formatting message")); + EMFormatPURI *puri; + CamelStream *filtered_stream, *null; + CamelMultipart *mp; + CamelDataWrapper *dw; + CamelContentType *type; + gint i, count, len; + EMInlineFilter *inline_filter; + gboolean charset_added = FALSE; + const gchar *snoop_type = NULL; + + if (g_cancellable_is_cancelled (cancellable)) + return; + + dw = camel_medium_get_content ((CamelMedium *) part); + if (!dw) + return; + + /* This scans the text part for inline-encoded data, creates + * a multipart of all the parts inside it. */ + + /* FIXME: We should discard this multipart if it only contains + * the original text, but it makes this hash lookup more complex */ + + /* TODO: We could probably put this in the superclass, since + * no knowledge of html is required - but this messes with + * filters a bit. Perhaps the superclass should just deal with + * html anyway and be done with it ... */ + + if (!dw->mime_type) + snoop_type = em_format_snoop_type (part); + + /* if we had to snoop the part type to get here, then + * use that as the base type, yuck */ + if (snoop_type == NULL + || (type = camel_content_type_decode (snoop_type)) == NULL) { + type = dw->mime_type; + camel_content_type_ref (type); + } + + if (dw->mime_type && type != dw->mime_type && camel_content_type_param (dw->mime_type, "charset")) { + camel_content_type_set_param (type, "charset", camel_content_type_param (dw->mime_type, "charset")); + charset_added = TRUE; + } + + null = camel_stream_null_new (); + filtered_stream = camel_stream_filter_new (null); + g_object_unref (null); + inline_filter = em_inline_filter_new (camel_mime_part_get_encoding (part), type); + camel_stream_filter_add ( + CAMEL_STREAM_FILTER (filtered_stream), + CAMEL_MIME_FILTER (inline_filter)); + camel_data_wrapper_decode_to_stream_sync ( + dw, (CamelStream *) filtered_stream, cancellable, NULL); + camel_stream_close ((CamelStream *) filtered_stream, cancellable, NULL); + g_object_unref (filtered_stream); + + mp = em_inline_filter_get_multipart (inline_filter); + + if (charset_added) { + camel_content_type_set_param (type, "charset", NULL); + } + + g_object_unref (inline_filter); + camel_content_type_unref (type); + + /* We handle our made-up multipart here, so we don't recursively call ourselves */ + len = part_id->len; + count = camel_multipart_get_number (mp); + for (i = 0; i < count; i++) { + CamelMimePart *newpart = camel_multipart_get_part (mp, i); + + if (!newpart) + continue; + + type = camel_mime_part_get_content_type (newpart); + if (camel_content_type_is (type, "text", "*") && (!camel_content_type_is (type, "text", "calendar"))) { + gint s_len = part_id->len; + + g_string_append (part_id, ".plain_text"); + puri = em_format_puri_new (emf, sizeof (EMFormatPURI), newpart, part_id->str); + puri->write_func = efh_write_text_plain; + puri->mime_type = g_strdup ("text/html"); + puri->validity = info->validity ? camel_cipher_validity_clone (info->validity) : NULL; + puri->validity_type = info->validity_type; + puri->is_attachment = info->is_attachment; + g_string_truncate (part_id, s_len); + em_format_add_puri (emf, puri); + } else { + g_string_append_printf (part_id, ".inline.%d", i); + em_format_parse_part (emf, CAMEL_MIME_PART (newpart), part_id, info, cancellable); + g_string_truncate (part_id, len); + } + } + + g_object_unref (mp); } static void -efh_format_exec (struct _format_msg *m, - GCancellable *cancellable, - GError **error) +efh_parse_text_html (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) { - EMFormat *format; - CamelStream *stream; - struct _EMFormatHTMLJob *job; - GNode *puri_level; + EMFormatPURI *puri; + const gchar *location; + gchar *cid = NULL; CamelURL *base; + gint len; + + if (g_cancellable_is_cancelled (cancellable)) + return; + + base = em_format_get_base_url (emf); + location = camel_mime_part_get_content_location (part); + if (location == NULL) { + if (base) + cid = camel_url_to_string (base, 0); + else + cid = g_strdup (part_id->str); + } else { + if (strchr (location, ':') == NULL && base != NULL) { + CamelURL *uri; + + uri = camel_url_new_with_base (base, location); + cid = camel_url_to_string (uri, 0); + camel_url_free (uri); + } else { + cid = g_strdup (location); + } + } + + len = part_id->len; + g_string_append (part_id, ".text_html"); + puri = em_format_puri_new (emf, sizeof (EMFormatPURI), part, part_id->str); + puri->write_func = efh_write_text_html; + puri->validity = info->validity ? camel_cipher_validity_clone (info->validity) : NULL; + puri->validity_type = info->validity_type; + puri->is_attachment = info->is_attachment; + + em_format_add_puri (emf, puri); + g_string_truncate (part_id, len); + + if (cid) + g_free (cid); +} + +static void +efh_parse_message_external (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) +{ + EMFormatPURI *puri; + CamelMimePart *newpart; + CamelContentType *type; + const gchar *access_type; + gchar *url = NULL, *desc = NULL; gchar *content; + gint len; - if (m->format->priv->web_view == NULL) { - m->cancelled = TRUE; + if (g_cancellable_is_cancelled (cancellable)) return; + + newpart = camel_mime_part_new (); + + /* needs to be cleaner */ + type = camel_mime_part_get_content_type (part); + access_type = camel_content_type_param (type, "access-type"); + if (!access_type) { + const gchar *msg = _("Malformed external-body part"); + camel_mime_part_set_content (newpart, msg, strlen (msg), + "text/plain"); + goto addPart; } - format = EM_FORMAT (m->format); - stream = CAMEL_STREAM (m->estream); + if (!g_ascii_strcasecmp(access_type, "ftp") || + !g_ascii_strcasecmp(access_type, "anon-ftp")) { + const gchar *name, *site, *dir, *mode; + gchar *path; + gchar ftype[16]; + + name = camel_content_type_param (type, "name"); + site = camel_content_type_param (type, "site"); + dir = camel_content_type_param (type, "directory"); + mode = camel_content_type_param (type, "mode"); + if (name == NULL || site == NULL) + goto fail; + + /* Generate the path. */ + if (dir) + path = g_strdup_printf("/%s/%s", *dir=='/'?dir+1:dir, name); + else + path = g_strdup_printf("/%s", *name=='/'?name+1:name); + + if (mode && *mode) + sprintf(ftype, ";type=%c", *mode); + else + ftype[0] = 0; + + url = g_strdup_printf ("ftp://%s%s%s", site, path, ftype); + g_free (path); + desc = g_strdup_printf (_("Pointer to FTP site (%s)"), url); + } else if (!g_ascii_strcasecmp (access_type, "local-file")) { + const gchar *name, *site; + + name = camel_content_type_param (type, "name"); + site = camel_content_type_param (type, "site"); + if (name == NULL) + goto fail; + url = g_filename_to_uri (name, NULL, NULL); + if (site) + desc = g_strdup_printf(_("Pointer to local file (%s) valid at site \"%s\""), name, site); + else + desc = g_strdup_printf(_("Pointer to local file (%s)"), name); + } else if (!g_ascii_strcasecmp (access_type, "URL")) { + const gchar *urlparam; + gchar *s, *d; + + /* RFC 2017 */ + urlparam = camel_content_type_param (type, "url"); + if (urlparam == NULL) + goto fail; + + /* For obscure MIMEy reasons, the URL may be split into words */ + url = g_strdup (urlparam); + s = d = url; + while (*s) { + if (!isspace ((guchar) * s)) + *d++ = *s; + s++; + } + *d = 0; + desc = g_strdup_printf (_("Pointer to remote data (%s)"), url); + } else + goto fail; + + content = g_strdup_printf ("<a href=\"%s\">%s</a>", url, desc); + camel_mime_part_set_content (newpart, content, strlen (content), "text/html"); + g_free (content); + + g_free (url); + g_free (desc); + +fail: content = g_strdup_printf ( - "<!doctype html public \"-//W3C//DTD HTML 4.0 TRANSITIONAL//EN\">\n<html>\n" - "<head>\n<meta name=\"generator\" content=\"Evolution Mail Component\">\n</head>\n" - "<body bgcolor =\"#%06x\" text=\"#%06x\" marginwidth=6 marginheight=6>\n", - e_color_to_value ( - &m->format->priv->colors[ - EM_FORMAT_HTML_COLOR_BODY]), - e_color_to_value ( - &m->format->priv->colors[ - EM_FORMAT_HTML_COLOR_HEADER])); - camel_stream_write_string (stream, content, cancellable, NULL); + _("Pointer to unknown external data (\"%s\" type)"), + access_type); + camel_mime_part_set_content (newpart, content, strlen (content), "text/plain"); g_free (content); - /* <insert top-header stuff here> */ +addPart: + len = part_id->len; + g_string_append (part_id, ".msg_external"); + puri = em_format_puri_new (emf, sizeof (EMFormatPURI), part, part_id->str); + puri->write_func = efh_write_text_html; + puri->mime_type = g_strdup ("text/html"); - if (format->mode == EM_FORMAT_MODE_SOURCE) { - em_format_format_source ( - format, stream, - (CamelMimePart *) m->message, cancellable); - } else { - const EMFormatHandler *handle; - const gchar *mime_type; - - mime_type = "x-evolution/message/prefix"; - handle = em_format_find_handler (format, mime_type); - - if (handle != NULL) - handle->handler ( - format, stream, - CAMEL_MIME_PART (m->message), handle, - cancellable, FALSE); - - mime_type = "x-evolution/message/rfc822"; - handle = em_format_find_handler (format, mime_type); - - if (handle != NULL) - handle->handler ( - format, stream, - CAMEL_MIME_PART (m->message), handle, - cancellable, FALSE); - } + em_format_add_puri (emf, puri); + g_string_truncate (part_id, len); +} - camel_stream_flush (stream, cancellable, NULL); +static void +efh_parse_message_deliverystatus (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) +{ + EMFormatPURI *puri; + gint len; - puri_level = format->pending_uri_level; - base = format->base; + if (g_cancellable_is_cancelled (cancellable)) + return; - do { - /* now dispatch any added tasks ... */ - g_mutex_lock (m->format->priv->lock); - while ((job = g_queue_pop_head (&m->format->priv->pending_jobs))) { - g_mutex_unlock (m->format->priv->lock); - - /* This is an implicit check to see if the gtkhtml has been destroyed */ - if (m->format->priv->web_view == NULL) - g_cancellable_cancel (cancellable); - - /* call jobs even if cancelled, so they can clean up resources */ - format->pending_uri_level = job->puri_level; - if (job->base) - format->base = job->base; - job->callback (job, cancellable); - format->base = base; - - /* clean up the job */ - g_object_unref (job->stream); - if (job->base) - camel_url_free (job->base); - g_free (job); - - g_mutex_lock (m->format->priv->lock); - } - g_mutex_unlock (m->format->priv->lock); - - if (m->estream) { - /* Closing this base stream can queue more jobs, so we need - * to check the list again after we've finished */ - d(printf("out of jobs, closing root stream\n")); - camel_stream_write_string ( - (CamelStream *) m->estream, - "</body>\n</html>\n", cancellable, NULL); - camel_stream_close ((CamelStream *) m->estream, cancellable, NULL); - if (g_cancellable_is_cancelled (cancellable)) { - m->cancelled = TRUE; - m->estream->sync.cancel = TRUE; - } - g_object_unref (m->estream); - m->estream = NULL; - } + len = part_id->len; + g_string_append (part_id, ".deliverystatus"); + puri = em_format_puri_new (emf, sizeof (EMFormatPURI), part, part_id->str); + puri->write_func = efh_write_source; + puri->mime_type = g_strdup ("text/html"); + puri->validity = info->validity ? camel_cipher_validity_clone (info->validity) : NULL; + puri->validity_type = info->validity_type; + puri->is_attachment = info->is_attachment; + + em_format_add_puri (emf, puri); + g_string_truncate (part_id, len); +} + +static void +efh_parse_message_rfc822 (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) +{ + CamelDataWrapper *dw; + CamelMimePart *opart; + CamelStream *stream; + CamelMimeParser *parser; + gint len; + EMFormatParserInfo oinfo = *info; + EMFormatPURI *puri; + + len = part_id->len; + g_string_append (part_id, ".rfc822"); + + /* Create an empty PURI that will represent start of the RFC message */ + puri = em_format_puri_new (emf, sizeof (EMFormatPURI), part, part_id->str); + puri->write_func = efh_write_message_rfc822; + puri->mime_type = g_strdup ("text/html"); + puri->is_attachment = info->is_attachment; + em_format_add_puri (emf, puri); + + /* Now parse the message, creating multiple sub-PURIs */ + stream = camel_stream_mem_new (); + dw = camel_medium_get_content ((CamelMedium *) part); + camel_data_wrapper_write_to_stream_sync (dw, stream, cancellable, NULL); + g_seekable_seek (G_SEEKABLE (stream), 0, G_SEEK_SET, cancellable, NULL); + + parser = camel_mime_parser_new (); + camel_mime_parser_init_with_stream (parser, stream, NULL); - } while (!g_queue_is_empty (&m->format->priv->pending_jobs)); + opart = camel_mime_part_new (); + camel_mime_part_construct_from_parser_sync (opart, parser, cancellable, NULL); - d(printf("out of jobs, done\n")); + em_format_parse_part_as (emf, opart, part_id, &oinfo, + "x-evolution/message", cancellable); - format->pending_uri_level = puri_level; - m->cancelled = m->cancelled || g_cancellable_is_cancelled (cancellable); + /* Add another generic PURI that represents end of the RFC message. + * The em_format_write() function will skip all PURIs between the ".rfc822" + * PURI and ".rfc822.end" PURI as they will be rendered in an <iframe> */ + g_string_append (part_id, ".end"); + puri = em_format_puri_new (emf, sizeof (EMFormatPURI), NULL, part_id->str); + em_format_add_puri (emf, puri); + + g_string_truncate (part_id, len); + + g_object_unref (opart); + g_object_unref (parser); + g_object_unref (stream); } +/*****************************************************************************/ + static void -efh_format_done (struct _format_msg *m) +efh_write_image (EMFormat *emf, + EMFormatPURI *puri, + CamelStream *stream, + EMFormatWriterInfo *info, + GCancellable *cancellable) { - d(printf("formatting finished\n")); + gchar *content; + EMFormatHTML *efh; + CamelDataWrapper *dw; + GByteArray *ba; + + if (g_cancellable_is_cancelled (cancellable)) + return; + + efh = (EMFormatHTML *) emf; + + dw = camel_medium_get_content (CAMEL_MEDIUM (puri->part)); + g_return_if_fail (dw); + + ba = camel_data_wrapper_get_byte_array (dw); + + if (info->mode == EM_FORMAT_WRITE_MODE_RAW) { - m->format->priv->format_id = -1; - m->format->priv->load_images_now = FALSE; - m->format->state = EM_FORMAT_HTML_STATE_NONE; - g_signal_emit_by_name(m->format, "complete"); + if (!efh->priv->animate_images) { + + gchar *buff; + gsize len; + gchar *data; + GByteArray anim; + + data = g_strndup ((gchar *) ba->data, (gsize) ba->len); + anim.data = g_base64_decode (data, (gsize *) &(anim.len)); + g_free (data); + + em_format_html_animation_extract_frame (&anim, &buff, &len); + + camel_stream_write (stream, buff, len, cancellable, NULL); + + g_free (buff); + g_free (anim.data); + + } else { + CamelStream *stream_filter; + CamelMimeFilter *filter; + + stream_filter = camel_stream_filter_new (stream); + filter = camel_mime_filter_basic_new ( + CAMEL_MIME_FILTER_BASIC_BASE64_DEC); + + camel_stream_write ( + stream_filter, + (gchar *) ba->data, ba->len, + cancellable, NULL); + g_object_unref (stream_filter); + g_object_unref (filter); + } + + } else { + + gchar *buffer; + + if (!efh->priv->animate_images) { + + gchar *buff; + gsize len; + gchar *data; + GByteArray raw_data; + + data = g_strndup ((gchar *) ba->data, ba->len); + raw_data.data = (guint8 *) g_base64_decode ( + data, (gsize *) &(raw_data.len)); + g_free (data); + + em_format_html_animation_extract_frame (&raw_data, &buff, &len); + + content = g_base64_encode ((guchar *) buff, len); + g_free (buff); + g_free (raw_data.data); + + } else { + content = g_strndup ((gchar *) ba->data, ba->len); + } + + /* The image is already base64-encrypted so we can directly + * paste it to the output */ + buffer = g_strdup_printf ( + "<img src=\"data:%s;base64,%s\" style=\"max-width: 100%%;\" />", + puri->mime_type ? puri->mime_type : "image/*", content); + + camel_stream_write_string (stream, buffer, cancellable, NULL); + g_free (buffer); + g_free (content); + } } static void -efh_format_free (struct _format_msg *m) +efh_write_text_enriched (EMFormat *emf, + EMFormatPURI *puri, + CamelStream *stream, + EMFormatWriterInfo *info, + GCancellable *cancellable) { - d(printf("formatter freed\n")); - g_object_unref (m->format); - if (m->estream) { - camel_stream_close ((CamelStream *) m->estream, NULL, NULL); - if (m->cancelled) - m->estream->sync.cancel = TRUE; - g_object_unref (m->estream); + EMFormatHTML *efh = EM_FORMAT_HTML (emf); + CamelStream *filtered_stream; + CamelMimeFilter *enriched; + guint32 flags = 0; + GString *buffer; + CamelContentType *ct; + gchar *mime_type = NULL; + + if (g_cancellable_is_cancelled (cancellable)) + return; + + ct = camel_mime_part_get_content_type (puri->part); + if (ct) { + mime_type = camel_content_type_simple (ct); } - if (m->folder) - g_object_unref (m->folder); - g_free (m->uid); - if (m->message) - g_object_unref (m->message); - if (m->format_source) - g_object_unref (m->format_source); + + if (!g_strcmp0(mime_type, "text/richtext")) { + flags = CAMEL_MIME_FILTER_ENRICHED_IS_RICHTEXT; + camel_stream_write_string ( + stream, "\n<!-- text/richtext -->\n", + cancellable, NULL); + } else { + camel_stream_write_string ( + stream, "\n<!-- text/enriched -->\n", + cancellable, NULL); + } + + if (mime_type) + g_free (mime_type); + + enriched = camel_mime_filter_enriched_new (flags); + filtered_stream = camel_stream_filter_new (stream); + camel_stream_filter_add ( + CAMEL_STREAM_FILTER (filtered_stream), enriched); + g_object_unref (enriched); + + buffer = g_string_new (""); + + g_string_append_printf (buffer, + "<div class=\"part-container\" style=\"border-color: #%06x; " + "background-color: #%06x; color: #%06x;\">" + "<div class=\"part-container-inner-margin\">\n", + e_color_to_value (&efh->priv->colors[ + EM_FORMAT_HTML_COLOR_FRAME]), + e_color_to_value (&efh->priv->colors[ + EM_FORMAT_HTML_COLOR_CONTENT]), + e_color_to_value (&efh->priv->colors[ + EM_FORMAT_HTML_COLOR_TEXT])); + + camel_stream_write_string (stream, buffer->str, cancellable, NULL); + g_string_free (buffer, TRUE); + + em_format_format_text ( + emf, (CamelStream *) filtered_stream, + (CamelDataWrapper *) puri->part, cancellable); + + g_object_unref (filtered_stream); + camel_stream_write_string (stream, "</div></div>", cancellable, NULL); } -static MailMsgInfo efh_format_info = { - sizeof (struct _format_msg), - (MailMsgDescFunc) efh_format_desc, - (MailMsgExecFunc) efh_format_exec, - (MailMsgDoneFunc) efh_format_done, - (MailMsgFreeFunc) efh_format_free -}; +static void +efh_write_text_plain (EMFormat *emf, + EMFormatPURI *puri, + CamelStream *stream, + EMFormatWriterInfo *info, + GCancellable *cancellable) +{ + CamelDataWrapper *dw; + CamelStream *filtered_stream; + CamelMimeFilter *html_filter; + EMFormatHTML *efh = (EMFormatHTML *) emf; + gchar *content; + const gchar *format; + guint32 flags; + guint32 rgb; + + if (g_cancellable_is_cancelled (cancellable)) + return; + + flags = efh->text_html_flags; + + dw = camel_medium_get_content (CAMEL_MEDIUM (puri->part)); + + /* Check for RFC 2646 flowed text. */ + if (camel_content_type_is(dw->mime_type, "text", "plain") + && (format = camel_content_type_param(dw->mime_type, "format")) + && !g_ascii_strcasecmp(format, "flowed")) + flags |= CAMEL_MIME_FILTER_TOHTML_FORMAT_FLOWED; + + rgb = e_color_to_value ( + &efh->priv->colors[EM_FORMAT_HTML_COLOR_CITATION]); + filtered_stream = camel_stream_filter_new (stream); + html_filter = camel_mime_filter_tohtml_new (flags, rgb); + camel_stream_filter_add ( + CAMEL_STREAM_FILTER (filtered_stream), html_filter); + g_object_unref (html_filter); + + content = g_strdup_printf ( + "<div class=\"part-container\" style=\"border-color: #%06x; " + "background-color: #%06x; color: #%06x;\">" + "<div class=\"part-container-inner-margin\">\n", + e_color_to_value (&efh->priv->colors[ + EM_FORMAT_HTML_COLOR_FRAME]), + e_color_to_value (&efh->priv->colors[ + EM_FORMAT_HTML_COLOR_CONTENT]), + e_color_to_value (&efh->priv->colors[ + EM_FORMAT_HTML_COLOR_TEXT])); + + camel_stream_write_string (stream, content, cancellable, NULL); + em_format_format_text (emf, filtered_stream, (CamelDataWrapper *) puri->part, cancellable); + + g_object_unref (filtered_stream); + g_free (content); -static gboolean -efh_format_helper (struct _format_msg *m, - gboolean async) + camel_stream_write_string (stream, "</div></div>\n", cancellable, NULL); +} + +static gchar * +get_tag (const gchar *tag_name, + gchar *opening, + gchar *closing) { - GtkHTMLStream *hstream; - EMFormatHTML *efh = m->format; - struct _EMFormatHTMLPrivate *p = efh->priv; - EWebView *web_view; + gchar *t; + gboolean has_end; + + for (t = closing - 1; t != opening; t--) { + if (*t != ' ') + break; + } - web_view = em_format_html_get_web_view (m->format); + /* Not a pair tag */ + if (*t == '/') + return g_strndup (opening, closing - opening + 1); - if (web_view == NULL) { - mail_msg_unref (m); - return FALSE; + for (t = closing; t && *t; t++) { + if (*t == '<') + break; } - if (async) { - d(printf("timeout called ...\n")); - if (p->format_id != -1) { - d(printf(" still waiting for cancellation to take effect, waiting ...\n")); - return TRUE; + do { + if (*t == '/') { + has_end = TRUE; + break; } - } - g_return_val_if_fail (g_queue_is_empty (&p->pending_jobs), FALSE); + if (*t == '>') { + has_end = FALSE; + break; + } + + t++; + + } while (t && *t); + + /* Broken HTML? */ + if (!has_end) + return g_strndup (opening, closing - opening + 1); + + do { + if ((*t != ' ') && (*t != '/')) + break; + + t++; + } while (t && *t); - d(printf(" ready to go, firing off format thread\n")); + if (g_strncasecmp (t, tag_name, strlen (tag_name)) == 0) { - /* call super-class to kick it off */ - /* FIXME Not passing a GCancellable here. */ - EM_FORMAT_CLASS (parent_class)->format_clone ( - EM_FORMAT (efh), m->folder, m->uid, - m->message, m->format_source, NULL); - em_format_html_clear_pobject (m->format); + closing = strstr (t, ">"); - /* FIXME: method off EMFormat? */ - if (((EMFormat *) efh)->valid) { - camel_cipher_validity_free (((EMFormat *) efh)->valid); - ((EMFormat *) efh)->valid = NULL; - ((EMFormat *) efh)->valid_parent = NULL; + return g_strndup (opening, closing - opening + strlen (tag_name)); } - if (m->message == NULL) { - hstream = gtk_html_begin (GTK_HTML (web_view)); - gtk_html_stream_close (hstream, GTK_HTML_STREAM_OK); - mail_msg_unref (m); - p->last_part = NULL; - } else { - efh->state = EM_FORMAT_HTML_STATE_RENDERING; -#if HAVE_CLUTTER - if (p->last_part != m->message && !e_shell_get_express_mode (e_shell_get_default ())) { -#else - if (p->last_part != m->message) { -#endif - hstream = gtk_html_begin (GTK_HTML (web_view)); - gtk_html_stream_printf (hstream, "<h5>%s</h5>", _("Formatting Message...")); - gtk_html_stream_close (hstream, GTK_HTML_STREAM_OK); - } + /* Broken HTML? */ + return g_strndup (opening, closing - opening + 1); +} - hstream = NULL; - m->estream = (EMHTMLStream *) em_html_stream_new ( - GTK_HTML (web_view), hstream); +static void +efh_write_text_html (EMFormat *emf, + EMFormatPURI *puri, + CamelStream *stream, + EMFormatWriterInfo *info, + GCancellable *cancellable) +{ + EMFormatHTML *efh = (EMFormatHTML *) emf; - if (p->last_part == m->message) { - em_html_stream_set_flags (m->estream, - GTK_HTML_BEGIN_KEEP_SCROLL | GTK_HTML_BEGIN_KEEP_IMAGES - | GTK_HTML_BEGIN_BLOCK_UPDATES | GTK_HTML_BEGIN_BLOCK_IMAGES); - } else { - /* clear cache of inline-scanned text parts */ - g_hash_table_remove_all (p->text_inline_parts); + if (g_cancellable_is_cancelled (cancellable)) + return; + + if (info->mode == EM_FORMAT_WRITE_MODE_RAW) { + em_format_format_text (emf, stream, + (CamelDataWrapper *) puri->part, cancellable); + + } else if (info->mode == EM_FORMAT_WRITE_MODE_PRINTING) { + GString *string; + GByteArray *ba; + gchar *pos; + GList *tags, *iter; + gboolean valid; + gchar *tag; + const gchar *document_end; + gint length; + gint i; + CamelStream *decoded_stream; + + decoded_stream = camel_stream_mem_new (); + em_format_format_text (emf, decoded_stream, + (CamelDataWrapper *) puri->part, cancellable); + g_seekable_seek (G_SEEKABLE (decoded_stream), 0, G_SEEK_SET, cancellable, NULL); + + ba = camel_stream_mem_get_byte_array (CAMEL_STREAM_MEM (decoded_stream)); + string = g_string_new_len ((gchar *) ba->data, ba->len); + + g_object_unref (decoded_stream); + + tags = NULL; + pos = string->str; + valid = FALSE; + do { + gchar *closing; + gchar *opening; + + pos = strstr (pos + 1, "<"); + if (!pos) + break; + + opening = pos; + closing = strstr (pos, ">"); + + /* Find where the actual tag name begins */ + for (tag = pos + 1; tag && *tag; tag++) { + if (*tag != ' ') + break; + } + + if (g_ascii_strncasecmp (tag, "style", 5) == 0) { + tags = g_list_append ( + tags, + get_tag ("style", opening, closing)); + } else if (g_ascii_strncasecmp (tag, "script", 6) == 0) { + tags = g_list_append ( + tags, + get_tag ("script", opening, closing)); + } else if (g_ascii_strncasecmp (tag, "link", 4) == 0) { + tags = g_list_append ( + tags, + get_tag ("link", opening, closing)); + } else if (g_ascii_strncasecmp (tag, "body", 4) == 0) { + valid = TRUE; + break; + } + + } while (TRUE); - p->last_part = m->message; + /* Something's wrong, let's write the entire HTML and hope + * that WebKit can handle it */ + if (!valid) { + EMFormatWriterInfo i = *info; + i.mode = EM_FORMAT_WRITE_MODE_RAW; + efh_write_text_html (emf, puri, stream, &i, cancellable); + return; } - efh->priv->format_id = m->base.seq; + /* include the "body" as well -----v */ + g_string_erase (string, 0, tag - string->str + 4); + g_string_prepend (string, "<div "); - if (async) { - mail_msg_unordered_push (m); - } else { - efh_format_exec (m, NULL, NULL); + for (iter = tags; iter; iter = iter->next) { + g_string_prepend (string, iter->data); } - } - efh->priv->format_timeout_id = 0; - efh->priv->format_timeout_msg = NULL; + g_list_free_full (tags, g_free); + + /* that's reversed </body></html>... */ + document_end = ">lmth/<>ydob/<"; + length = strlen (document_end); + tag = string->str + string->len - 1; + i = 0; + valid = FALSE; + while (i < length - 1) { + gchar c; - return FALSE; + if (g_ascii_isspace (*tag)) { + tag--; + continue; + } + + if ((*tag >= 'A') && (*tag <= 'Z')) + c = *tag + 32; + else + c = *tag; + + if (c == document_end[i]) { + tag--; + i++; + valid = TRUE; + continue; + } + + valid = FALSE; + } + + if (valid) + g_string_truncate (string, tag - string->str); + + camel_stream_write_string (stream, string->str, cancellable, NULL); + + g_string_free (string, TRUE); + } else { + gchar *str; + gchar *uri; + + uri = em_format_build_mail_uri ( + emf->folder, emf->message_uid, + "part_id", G_TYPE_STRING, puri->uri, + "mode", G_TYPE_INT, EM_FORMAT_WRITE_MODE_RAW, + NULL); + + str = g_strdup_printf ( + "<div class=\"part-container\" style=\"border-color: #%06x; " + "background-color: #%06x;\">" + "<div class=\"part-container-inner-margin\">\n" + "<iframe width=\"100%%\" height=\"auto\"" + " frameborder=\"0\" src=\"%s\"></iframe>" + "</div></div>", + e_color_to_value (&efh->priv->colors[EM_FORMAT_HTML_COLOR_FRAME]), + e_color_to_value (&efh->priv->colors[EM_FORMAT_HTML_COLOR_CONTENT]), + uri); + + camel_stream_write_string (stream, str, cancellable, NULL); + + g_free (str); + g_free (uri); + } } static void -efh_free_cache (struct _EMFormatHTMLCache *efhc) +efh_write_source (EMFormat *emf, + EMFormatPURI *puri, + CamelStream *stream, + EMFormatWriterInfo *info, + GCancellable *cancellable) { - if (efhc->textmp) - g_object_unref (efhc->textmp); - g_free (efhc); + EMFormatHTML *efh = (EMFormatHTML *) emf; + GString *buffer; + CamelStream *filtered_stream; + CamelMimeFilter *filter; + CamelDataWrapper *dw = (CamelDataWrapper *) puri->part; + + filtered_stream = camel_stream_filter_new (stream); + + filter = camel_mime_filter_tohtml_new ( + CAMEL_MIME_FILTER_TOHTML_CONVERT_NL | + CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES | + CAMEL_MIME_FILTER_TOHTML_PRESERVE_8BIT, 0); + camel_stream_filter_add ( + CAMEL_STREAM_FILTER (filtered_stream), filter); + g_object_unref (filter); + + buffer = g_string_new (""); + + g_string_append_printf ( + buffer, "<div class=\"part-container\" style=\"background: #%06x; color: #%06x;\" >", + e_color_to_value ( + &efh->priv->colors[ + EM_FORMAT_HTML_COLOR_BODY]), + e_color_to_value ( + &efh->priv->colors[ + EM_FORMAT_HTML_COLOR_HEADER])); + + camel_stream_write_string ( + stream, buffer->str, cancellable, NULL); + camel_stream_write_string ( + stream, "<code class=\"pre\">", cancellable, NULL); + camel_data_wrapper_write_to_stream_sync (dw, filtered_stream, + cancellable, NULL); + camel_stream_write_string ( + stream, "</code>", cancellable, NULL); + + g_object_unref (filtered_stream); + g_string_free (buffer, TRUE); } static void -efh_gtkhtml_destroy (GtkHTML *html, - EMFormatHTML *efh) +efh_write_headers (EMFormat *emf, + EMFormatPURI *puri, + CamelStream *stream, + EMFormatWriterInfo *info, + GCancellable *cancellable) { - if (efh->priv->format_timeout_id != 0) { - g_source_remove (efh->priv->format_timeout_id); - efh->priv->format_timeout_id = 0; - mail_msg_unref (efh->priv->format_timeout_msg); - efh->priv->format_timeout_msg = NULL; + GString *buffer; + EMFormatHTML *efh = (EMFormatHTML *) emf; + gint bg_color; + + if (!puri->part) + return; + + buffer = g_string_new (""); + + if (info->mode & EM_FORMAT_WRITE_MODE_PRINTING) { + GdkColor white = { 0, G_MAXUINT16, G_MAXUINT16, G_MAXUINT16 }; + bg_color = e_color_to_value (&white); + } else { + bg_color = e_color_to_value (&efh->priv->colors[EM_FORMAT_HTML_COLOR_BODY]); + } + + g_string_append_printf ( + buffer, + "<div class=\"headers\" style=\"background: #%06x;\">" + "<table border=\"0\" width=\"100%%\" style=\"color: #%06x;\">\n" + "<tr><td valign=\"top\" width=\"16\">\n", + bg_color, + e_color_to_value (&efh->priv->colors[EM_FORMAT_HTML_COLOR_HEADER])); + + if (info->headers_collapsable) { + g_string_append_printf (buffer, + "<img src=\"evo-file://%s/%s\" class=\"navigable\" " + "id=\"__evo-collapse-headers-img\" />" + "</td><td>", + EVOLUTION_IMAGESDIR, + (info->headers_collapsed) ? "plus.png" : "minus.png"); + + efh_format_short_headers (efh, buffer, (CamelMedium *) puri->part, + info->headers_collapsed, + cancellable); } - /* This probably works ... */ - if (efh->priv->format_id != -1) - mail_msg_cancel (efh->priv->format_id); + efh_format_full_headers (efh, buffer, (CamelMedium *) puri->part, + (info->mode == EM_FORMAT_WRITE_MODE_ALL_HEADERS), + !info->headers_collapsed, + cancellable); + + g_string_append (buffer, "</td></tr></table></div>"); + + camel_stream_write_string (stream, buffer->str, cancellable, NULL); - if (efh->priv->web_view != NULL) { - g_object_unref (efh->priv->web_view); - efh->priv->web_view = NULL; + g_string_free (buffer, true); +} + +static void +efh_write_error (EMFormat *emf, + EMFormatPURI *puri, + CamelStream *stream, + EMFormatWriterInfo *info, + GCancellable *cancellable) +{ + CamelStream *filtered_stream; + CamelMimeFilter *filter; + CamelDataWrapper *dw; + + dw = camel_medium_get_content ((CamelMedium *) puri->part); + + camel_stream_write_string (stream, "<em><font color=\"red\">", cancellable, NULL); + + filtered_stream = camel_stream_filter_new (stream); + filter = camel_mime_filter_tohtml_new (CAMEL_MIME_FILTER_TOHTML_CONVERT_NL | + CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS, 0); + camel_stream_filter_add (CAMEL_STREAM_FILTER (filtered_stream), filter); + g_object_unref (filter); + + camel_data_wrapper_decode_to_stream_sync (dw, filtered_stream, cancellable, NULL); + + g_object_unref (filtered_stream); + + camel_stream_write_string (stream, "</font></em><br>", cancellable, NULL); +} + +static void +efh_write_message_rfc822 (EMFormat *emf, + EMFormatPURI *puri, + CamelStream *stream, + EMFormatWriterInfo *info, + GCancellable *cancellable) +{ + if (info->mode == EM_FORMAT_WRITE_MODE_RAW) { + + GList *puris; + GList *iter; + + /* Create a new fake list of PURIs which will contain only + * PURIs from this message. */ + iter = g_hash_table_lookup (emf->mail_part_table, puri->uri); + if (!iter || !iter->next) + return; + + iter = iter->next; + puris = NULL; + while (iter) { + + EMFormatPURI *p; + p = iter->data; + + if (g_str_has_suffix (p->uri, ".rfc822.end")) + break; + + puris = g_list_append (puris, p); + iter = iter->next; + + }; + + efh_write_message (emf, puris, stream, info, cancellable); + + g_list_free (puris); + + } else if (info->mode == EM_FORMAT_WRITE_MODE_PRINTING) { + + GList *iter; + gboolean can_write = FALSE; + + iter = g_hash_table_lookup (emf->mail_part_table, puri->uri); + if (!iter || !iter->next) + return; + + /* Skip everything before attachment bar, inclusive */\ + iter = iter->next; + while (iter) { + + EMFormatPURI *p = iter->data; + + /* EMFormatHTMLPrint has registered a special writer + * for headers, try to find it and use it. */ + if (g_str_has_suffix (p->uri, ".headers")) { + + const EMFormatHandler *handler; + + handler = em_format_find_handler ( + emf, "x-evolution/message/headers"); + if (handler && handler->write_func) + handler->write_func (emf, p, stream, info, cancellable); + + iter = iter->next; + continue; + } + + if (g_str_has_suffix (p->uri, ".rfc822.end")) + break; + + if (g_str_has_suffix (p->uri, ".attachment-bar")) + can_write = TRUE; + + if (can_write && p->write_func) { + p->write_func ( + emf, p, stream, info, cancellable); + } + + iter = iter->next; + } + + } else { + gchar *str; + gchar *uri; + + EMFormatHTML *efh = (EMFormatHTML *) emf; + EMFormatPURI *p; + GList *iter; + + iter = g_hash_table_lookup (emf->mail_part_table, puri->uri); + if (!iter || !iter->next) + return; + + iter = iter->next; + p = iter->data; + + uri = em_format_build_mail_uri (emf->folder, emf->message_uid, + "part_id", G_TYPE_STRING, p->uri, + "mode", G_TYPE_INT, EM_FORMAT_WRITE_MODE_RAW, + NULL); + + str = g_strdup_printf ( + "<div class=\"part-container\" style=\"border-color: #%06x; " + "background-color: #%06x;\">" + "<div class=\"part-container-inner-margin\">\n" + "<iframe width=\"100%%\" height=\"auto\"" + " frameborder=\"0\" src=\"%s\" name=\"%s\"></iframe>" + "</div></div>", + e_color_to_value (&efh->priv->colors[EM_FORMAT_HTML_COLOR_FRAME]), + e_color_to_value (&efh->priv->colors[EM_FORMAT_HTML_COLOR_CONTENT]), + uri, puri->uri); + + camel_stream_write_string (stream, str, cancellable, NULL); + + g_free (str); + g_free (uri); } + } -static struct _EMFormatHTMLCache * -efh_insert_cache (EMFormatHTML *efh, - const gchar *partid) +/*****************************************************************************/ + +/* Notes: + * + * image/tiff is omitted because it's a multi-page image format, but + * gdk-pixbuf unconditionally renders the first page only, and doesn't + * even indicate through meta-data whether multiple pages are present + * (see bug 335959). Therefore, make no attempt to render TIFF images + * inline and defer to an application that can handle multi-page TIFF + * files properly like Evince or Gimp. Once the referenced bug is + * fixed we can reevaluate this policy. + */ +static EMFormatHandler type_builtin_table[] = { + { (gchar *) "image/gif", efh_parse_image, efh_write_image, }, + { (gchar *) "image/jpeg", efh_parse_image, efh_write_image, }, + { (gchar *) "image/png", efh_parse_image, efh_write_image, }, + { (gchar *) "image/x-png", efh_parse_image, efh_write_image, }, + { (gchar *) "image/x-bmp", efh_parse_image, efh_write_image, }, + { (gchar *) "image/bmp", efh_parse_image, efh_write_image, }, + { (gchar *) "image/svg", efh_parse_image, efh_write_image, }, + { (gchar *) "image/x-cmu-raster", efh_parse_image, efh_write_image, }, + { (gchar *) "image/x-ico", efh_parse_image, efh_write_image, }, + { (gchar *) "image/x-portable-anymap", efh_parse_image, efh_write_image, }, + { (gchar *) "image/x-portable-bitmap", efh_parse_image, efh_write_image, }, + { (gchar *) "image/x-portable-graymap", efh_parse_image, efh_write_image, }, + { (gchar *) "image/x-portable-pixmap", efh_parse_image, efh_write_image, }, + { (gchar *) "image/x-xpixmap", efh_parse_image, efh_write_image, }, + { (gchar *) "text/enriched", efh_parse_text_enriched, efh_write_text_enriched, }, + { (gchar *) "text/plain", efh_parse_text_plain, efh_write_text_plain, }, + { (gchar *) "text/html", efh_parse_text_html, efh_write_text_html, }, + { (gchar *) "text/richtext", efh_parse_text_enriched, efh_write_text_enriched, }, + { (gchar *) "text/*", efh_parse_text_plain, efh_write_text_plain, }, + { (gchar *) "message/rfc822", efh_parse_message_rfc822, efh_write_message_rfc822, EM_FORMAT_HANDLER_INLINE | EM_FORMAT_HANDLER_COMPOUND_TYPE }, + { (gchar *) "message/news", efh_parse_message_rfc822, 0, EM_FORMAT_HANDLER_INLINE | EM_FORMAT_HANDLER_COMPOUND_TYPE }, + { (gchar *) "message/delivery-status", efh_parse_message_deliverystatus, efh_write_text_plain, }, + { (gchar *) "message/external-body", efh_parse_message_external, efh_write_text_plain, }, + { (gchar *) "message/*", efh_parse_message_rfc822, 0, EM_FORMAT_HANDLER_INLINE }, + + /* This is where one adds those busted, non-registered types, + * that some idiot mailer writers out there decide to pull out + * of their proverbials at random. */ + { (gchar *) "image/jpg", efh_parse_image, efh_write_image, }, + { (gchar *) "image/pjpeg", efh_parse_image, efh_write_image, }, + + /* special internal types */ + { (gchar *) "x-evolution/message/rfc822", 0, efh_write_text_plain, }, + { (gchar *) "x-evolution/message/headers", 0, efh_write_headers, }, + { (gchar *) "x-evolution/message/source", 0, efh_write_source, }, + { (gchar *) "x-evolution/message/attachment", 0, efh_write_attachment, }, + { (gchar *) "x-evolution/message/error", 0, efh_write_error, }, +}; + +static void +efh_builtin_init (EMFormatHTMLClass *efhc) { - struct _EMFormatHTMLCache *efhc; + EMFormatClass *emfc; + gint ii; - efhc = g_malloc0 (sizeof (*efh) + strlen (partid)); - strcpy (efhc->partid, partid); - g_hash_table_insert (efh->priv->text_inline_parts, efhc->partid, efhc); + emfc = (EMFormatClass *) efhc; - return efhc; + for (ii = 0; ii < G_N_ELEMENTS (type_builtin_table); ii++) + em_format_class_add_handler ( + emfc, &type_builtin_table[ii]); } static void @@ -544,15 +1389,12 @@ efh_set_property (GObject *object, EM_FORMAT_HTML_COLOR_TEXT, g_value_get_boxed (value)); return; - case PROP_HEADERS_STATE: - em_format_html_set_headers_state ( - EM_FORMAT_HTML (object), - g_value_get_int (value)); - return; - case PROP_HEADERS_COLLAPSABLE: - em_format_html_set_headers_collapsable ( + + case PROP_ANIMATE_IMAGES: + em_format_html_set_animate_images ( EM_FORMAT_HTML (object), g_value_get_boolean (value)); + return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); @@ -645,21 +1487,11 @@ efh_get_property (GObject *object, &color); g_value_set_boxed (value, &color); return; - - case PROP_WEB_VIEW: - g_value_set_object ( - value, em_format_html_get_web_view ( - EM_FORMAT_HTML (object))); - return; - case PROP_HEADERS_STATE: - g_value_set_int ( - value, em_format_html_get_headers_state ( - EM_FORMAT_HTML (object))); - return; - case PROP_HEADERS_COLLAPSABLE: + case PROP_ANIMATE_IMAGES: g_value_set_boolean ( - value, em_format_html_get_headers_collapsable ( + value, em_format_html_get_animate_images ( EM_FORMAT_HTML (object))); + return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); @@ -668,230 +1500,198 @@ efh_get_property (GObject *object, static void efh_finalize (GObject *object) { - EMFormatHTML *efh = EM_FORMAT_HTML (object); - - em_format_html_clear_pobject (efh); - efh_gtkhtml_destroy (GTK_HTML (efh->priv->web_view), efh); - - g_hash_table_destroy (efh->priv->text_inline_parts); - - g_mutex_free (efh->priv->lock); - /* Chain up to parent's finalize() method. */ G_OBJECT_CLASS (parent_class)->finalize (object); } -static gboolean -efh_format_timeout (struct _format_msg *m) -{ - return efh_format_helper (m, TRUE); -} - -void -em_format_html_clone_sync (CamelFolder *folder, - const gchar *uid, - CamelMimeMessage *msg, - EMFormatHTML *efh, - EMFormat *source) -{ - struct _format_msg *m; - - m = mail_msg_new (&efh_format_info); - m->format = g_object_ref (efh); - if (source) - m->format_source = g_object_ref (source); - m->folder = g_object_ref (folder); - m->uid = g_strdup (uid); - m->message = g_object_ref (msg); - - efh_format_helper (m, FALSE); - efh_format_free (m); -} - static void -efh_format_clone (EMFormat *emf, - CamelFolder *folder, - const gchar *uid, - CamelMimeMessage *msg, - EMFormat *emfsource, - GCancellable *cancellable) +efh_write_attachment (EMFormat *emf, + EMFormatPURI *puri, + CamelStream *stream, + EMFormatWriterInfo *info, + GCancellable *cancellable) { - EMFormatHTML *efh = EM_FORMAT_HTML (emf); - struct _format_msg *m; - - /* How to sub-class ? Might need to adjust api ... */ - - if (efh->priv->web_view == NULL) - return; + gchar *text, *html; + CamelContentType *ct; + gchar *mime_type; + const EMFormatHandler *handler; - d(printf("efh_format called\n")); - if (efh->priv->format_timeout_id != 0) { - d(printf(" timeout for last still active, removing ...\n")); - g_source_remove (efh->priv->format_timeout_id); - efh->priv->format_timeout_id = 0; - mail_msg_unref (efh->priv->format_timeout_msg); - efh->priv->format_timeout_msg = NULL; - } + /* we display all inlined attachments only */ - if (emfsource != NULL) - g_object_ref (emfsource); + /* this could probably be cleaned up ... */ + camel_stream_write_string ( + stream, + "<table border=1 cellspacing=0 cellpadding=0><tr><td>" + "<table width=10 cellspacing=0 cellpadding=0>" + "<tr><td></td></tr></table></td>" + "<td><table width=3 cellspacing=0 cellpadding=0>" + "<tr><td></td></tr></table></td><td><font size=-1>\n", + cancellable, NULL); - if (folder != NULL) - g_object_ref (folder); + ct = camel_mime_part_get_content_type (puri->part); + mime_type = camel_content_type_simple (ct); - if (msg != NULL) - g_object_ref (msg); + /* output some info about it */ + text = em_format_describe_part (puri->part, mime_type); + html = camel_text_to_html ( + text, ((EMFormatHTML *) emf)->text_html_flags & + CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS, 0); + camel_stream_write_string (stream, html, cancellable, NULL); + g_free (html); + g_free (text); - m = mail_msg_new (&efh_format_info); - m->format = g_object_ref (emf); - m->format_source = emfsource; - m->folder = folder; - m->uid = g_strdup (uid); - m->message = msg; + camel_stream_write_string ( + stream, "</font></td></tr><tr></table>", cancellable, NULL); - if (efh->priv->format_id == -1) { - d(printf(" idle, forcing format\n")); - efh_format_timeout (m); - } else { - d(printf(" still busy, cancelling and queuing wait\n")); - /* cancel and poll for completion */ - mail_msg_cancel (efh->priv->format_id); - efh->priv->format_timeout_msg = m; - efh->priv->format_timeout_id = g_timeout_add ( - 100, (GSourceFunc) efh_format_timeout, m); + handler = em_format_find_handler (emf, mime_type); + if (handler && handler->write_func && handler->write_func != efh_write_attachment) { + if (em_format_is_inline (emf, puri->uri, puri->part, handler)) + handler->write_func (emf, puri, stream, info, cancellable); } + + g_free (mime_type); } static void -efh_format_error (EMFormat *emf, - CamelStream *stream, - const gchar *txt) +efh_preparse (EMFormat *emf) { - GString *buffer; - gchar *html; - - buffer = g_string_new ("<em><font color=\"red\">"); - - html = camel_text_to_html ( - txt, CAMEL_MIME_FILTER_TOHTML_CONVERT_NL | - CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS, 0); - g_string_append (buffer, html); - g_free (html); + CamelInternetAddress *addr; - g_string_append (buffer, "</font></em><br>"); + EMFormatHTML *efh = EM_FORMAT_HTML (emf); - camel_stream_write (stream, buffer->str, buffer->len, NULL, NULL); + if (!emf->message) { + efh->priv->can_load_images = FALSE; + return; + } - g_string_free (buffer, TRUE); + addr = camel_mime_message_get_from (emf->message); + efh->priv->can_load_images = em_utils_in_addressbook (addr, FALSE); } static void -efh_format_source (EMFormat *emf, +efh_write_message (EMFormat *emf, + GList *puris, CamelStream *stream, - CamelMimePart *part, + EMFormatWriterInfo *info, GCancellable *cancellable) { - CamelStream *filtered_stream; - CamelMimeFilter *filter; - CamelDataWrapper *dw = (CamelDataWrapper *) part; + GList *iter; + EMFormatHTML *efh; + gchar *header; - filtered_stream = camel_stream_filter_new (stream); + efh = (EMFormatHTML *) emf; - filter = camel_mime_filter_tohtml_new ( - CAMEL_MIME_FILTER_TOHTML_CONVERT_NL | - CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES | - CAMEL_MIME_FILTER_TOHTML_PRESERVE_8BIT, 0); - camel_stream_filter_add ( - CAMEL_STREAM_FILTER (filtered_stream), filter); - g_object_unref (filter); + header = g_strdup_printf ( + "<!DOCTYPE HTML>\n<html>\n" + "<head>\n<meta name=\"generator\" content=\"Evolution Mail Component\" />\n" + "<title>Evolution Mail Display</title>\n" + "<link type=\"text/css\" rel=\"stylesheet\" href=\"evo-file://" EVOLUTION_PRIVDATADIR "/theme/webview.css\" />\n" + "<style type=\"text/css\">\n" + " table th { color: #000; font-weight: bold; }\n" + "</style>\n" + "</head><body bgcolor=\"#%06x\">", + e_color_to_value (&efh->priv->colors[ + EM_FORMAT_HTML_COLOR_BODY])); - camel_stream_write_string ( - stream, "<table><tr><td><tt>", cancellable, NULL); - em_format_format_text (emf, filtered_stream, dw, cancellable); - camel_stream_write_string ( - stream, "</tt></td></tr></table>", cancellable, NULL); + camel_stream_write_string (stream, header, cancellable, NULL); + g_free (header); - g_object_unref (filtered_stream); -} + if (info->mode == EM_FORMAT_WRITE_MODE_SOURCE) { -static void -efh_format_attachment (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const gchar *mime_type, - const EMFormatHandler *handle, - GCancellable *cancellable) -{ - gchar *text, *html; + efh_write_source (emf, emf->mail_part_list->data, + stream, info, cancellable); - /* we display all inlined attachments only */ + camel_stream_write_string (stream, "</body></html>", cancellable, NULL); + return; + } - /* this could probably be cleaned up ... */ - camel_stream_write_string ( - stream, - "<table border=1 cellspacing=0 cellpadding=0><tr><td>" - "<table width=10 cellspacing=0 cellpadding=0>" - "<tr><td></td></tr></table></td>" - "<td><table width=3 cellspacing=0 cellpadding=0>" - "<tr><td></td></tr></table></td><td><font size=-1>\n", - cancellable, NULL); + for (iter = puris; iter; iter = iter->next) { - /* output some info about it */ - text = em_format_describe_part (part, mime_type); - html = camel_text_to_html ( - text, ((EMFormatHTML *) emf)->text_html_flags & - CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS, 0); - camel_stream_write_string (stream, html, cancellable, NULL); - g_free (html); - g_free (text); + EMFormatPURI *puri = iter->data; - camel_stream_write_string ( - stream, "</font></td></tr><tr></table>", cancellable, NULL); + if (!puri) + continue; - if (handle && em_format_is_inline (emf, emf->part_id->str, part, handle)) - handle->handler (emf, stream, part, handle, cancellable, FALSE); -} + /* If current PURI has suffix .rfc822 then iterate through all + * subsequent PURIs until PURI with suffix .rfc822.end is found. + * These skipped PURIs contain entire RFC message which will + * be written in <iframe> as attachment. + */ + if (g_str_has_suffix (puri->uri, ".rfc822")) { + + /* If the PURI is not an attachment, then we must + * inline it here otherwise it would not be displayed. */ + if (!puri->is_attachment && puri->write_func) { + /* efh_write_message_rfc822 starts parsing _after_ + * the passed PURI, so we must give it previous PURI here */ + EMFormatPURI *p; + if (!iter->prev) + continue; -static gboolean -efh_busy (EMFormat *emf) -{ - EMFormatHTMLPrivate *priv; + p = iter->prev->data; + puri->write_func (emf, p, stream, info, cancellable); + } + + while (iter && !g_str_has_suffix (puri->uri, ".rfc822.end")) { + + iter = iter->next; + if (iter) + puri = iter->data; - priv = EM_FORMAT_HTML_GET_PRIVATE (emf); + d(printf(".rfc822 - skipping %s\n", puri->uri)); + } + + /* Skip the .rfc822.end PURI as well. */ + if (!iter) + break; - return (priv->format_id != -1); + continue; + } + + if (puri->write_func && !puri->is_attachment) { + puri->write_func (emf, puri, stream, info, cancellable); + d(printf("Writing PURI %s\n", puri->uri)); + } else { + d(printf("Skipping PURI %s\n", puri->uri)); + } + } + + camel_stream_write_string (stream, "</body></html>", cancellable, NULL); +} + +static void +efh_write (EMFormat *emf, + CamelStream *stream, + EMFormatWriterInfo *info, + GCancellable *cancellable) +{ + efh_write_message (emf, emf->mail_part_list, stream, info, cancellable); } + static void -efh_base_init (EMFormatHTMLClass *class) +efh_base_init (EMFormatHTMLClass *klass) { - efh_builtin_init (class); + efh_builtin_init (klass); } static void -efh_class_init (EMFormatHTMLClass *class) +efh_class_init (EMFormatHTMLClass *klass) { GObjectClass *object_class; - EMFormatClass *format_class; - const gchar *user_cache_dir; + EMFormatClass *emf_class; + + parent_class = g_type_class_peek_parent (klass); + g_type_class_add_private (klass, sizeof (EMFormatHTMLPrivate)); - parent_class = g_type_class_peek_parent (class); - g_type_class_add_private (class, sizeof (EMFormatHTMLPrivate)); + emf_class = EM_FORMAT_CLASS (klass); + emf_class->preparse = efh_preparse; + emf_class->write = efh_write; - object_class = G_OBJECT_CLASS (class); + object_class = G_OBJECT_CLASS (klass); object_class->set_property = efh_set_property; object_class->get_property = efh_get_property; object_class->finalize = efh_finalize; - format_class = EM_FORMAT_CLASS (class); - format_class->format_clone = efh_format_clone; - format_class->format_error = efh_format_error; - format_class->format_source = efh_format_source; - format_class->format_attachment = efh_format_attachment; - format_class->format_secure = efh_format_secure; - format_class->busy = efh_busy; - - class->html_widget_type = E_TYPE_WEB_VIEW; - g_object_class_install_property ( object_class, PROP_BODY_COLOR, @@ -982,7 +1782,7 @@ efh_class_init (EMFormatHTMLClass *class) "show-sender-photo", "Show Sender Photo", NULL, - TRUE, + FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); @@ -1009,79 +1809,24 @@ efh_class_init (EMFormatHTMLClass *class) g_object_class_install_property ( object_class, - PROP_WEB_VIEW, - g_param_spec_object ( - "web-view", - "Web View", - NULL, - E_TYPE_WEB_VIEW, - G_PARAM_READABLE)); - - g_object_class_install_property ( - object_class, - PROP_HEADERS_STATE, - g_param_spec_int ( - "headers-state", - "Headers state", - NULL, - EM_FORMAT_HTML_HEADERS_STATE_EXPANDED, - EM_FORMAT_HTML_HEADERS_STATE_COLLAPSED, - EM_FORMAT_HTML_HEADERS_STATE_EXPANDED, - G_PARAM_READWRITE)); - - g_object_class_install_property ( - object_class, - PROP_HEADERS_STATE, + PROP_ANIMATE_IMAGES, g_param_spec_boolean ( - "headers-collapsable", - NULL, + "animate-images", + "Animate images", NULL, FALSE, G_PARAM_READWRITE)); - - /* cache expiry - 2 hour access, 1 day max */ - user_cache_dir = e_get_user_cache_dir (); - emfh_http_cache = camel_data_cache_new (user_cache_dir, NULL); - if (emfh_http_cache) { - camel_data_cache_set_expire_age (emfh_http_cache, 24 *60 *60); - camel_data_cache_set_expire_access (emfh_http_cache, 2 *60 *60); - } } static void efh_init (EMFormatHTML *efh, - EMFormatHTMLClass *class) + EMFormatHTMLClass *klass) { - EWebView *web_view; GdkColor *color; efh->priv = EM_FORMAT_HTML_GET_PRIVATE (efh); g_queue_init (&efh->pending_object_list); - g_queue_init (&efh->priv->pending_jobs); - efh->priv->lock = g_mutex_new (); - efh->priv->format_id = -1; - efh->priv->text_inline_parts = g_hash_table_new_full ( - g_str_hash, g_str_equal, - (GDestroyNotify) NULL, - (GDestroyNotify) efh_free_cache); - - web_view = g_object_new (class->html_widget_type, NULL); - efh->priv->web_view = g_object_ref_sink (web_view); - - gtk_html_set_blocking (GTK_HTML (web_view), FALSE); - gtk_html_set_caret_first_focus_anchor ( - GTK_HTML (web_view), EFM_MESSAGE_START_ANAME); - gtk_html_set_default_content_type ( - GTK_HTML (web_view), "text/html; charset=utf-8"); - e_web_view_set_editable (web_view, FALSE); - - g_signal_connect ( - web_view, "url-requested", - G_CALLBACK (efh_url_requested), efh); - g_signal_connect ( - web_view, "object-requested", - G_CALLBACK (efh_object_requested), efh); color = &efh->priv->colors[EM_FORMAT_HTML_COLOR_BODY]; gdk_color_parse ("#eeeeee", color); @@ -1103,7 +1848,6 @@ efh_init (EMFormatHTML *efh, CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES | CAMEL_MIME_FILTER_TOHTML_MARK_CITATION; efh->show_icon = TRUE; - efh->state = EM_FORMAT_HTML_STATE_NONE; e_extensible_load_extensions (E_EXTENSIBLE (efh)); } @@ -1144,28 +1888,7 @@ em_format_html_get_type (void) return type; } -EWebView * -em_format_html_get_web_view (EMFormatHTML *efh) -{ - g_return_val_if_fail (EM_IS_FORMAT_HTML (efh), NULL); - - return efh->priv->web_view; -} - -void -em_format_html_load_images (EMFormatHTML *efh) -{ - g_return_if_fail (EM_IS_FORMAT_HTML (efh)); - - if (efh->priv->image_loading_policy == E_MAIL_IMAGE_LOADING_POLICY_ALWAYS) - return; - - /* This will remain set while we're still - * rendering the same message, then it wont be. */ - efh->priv->load_images_now = TRUE; - em_format_queue_redraw (EM_FORMAT (efh)); -} - +/*****************************************************************************/ void em_format_html_get_color (EMFormatHTML *efh, EMFormatHTMLColorType type, @@ -1338,42 +2061,23 @@ em_format_html_set_show_real_date (EMFormatHTML *efh, g_object_notify (G_OBJECT (efh), "show-real-date"); } -EMFormatHTMLHeadersState -em_format_html_get_headers_state (EMFormatHTML *efh) -{ - g_return_val_if_fail (EM_IS_FORMAT_HTML (efh), EM_FORMAT_HTML_HEADERS_STATE_EXPANDED); - - return efh->priv->headers_state; -} - -void -em_format_html_set_headers_state (EMFormatHTML *efh, - EMFormatHTMLHeadersState state) -{ - g_return_if_fail (EM_IS_FORMAT_HTML (efh)); - - efh->priv->headers_state = state; - - g_object_notify (G_OBJECT (efh), "headers-state"); -} - gboolean -em_format_html_get_headers_collapsable (EMFormatHTML *efh) +em_format_html_get_animate_images (EMFormatHTML *efh) { g_return_val_if_fail (EM_IS_FORMAT_HTML (efh), FALSE); - return efh->priv->headers_collapsable; + return efh->priv->animate_images; } void -em_format_html_set_headers_collapsable (EMFormatHTML *efh, - gboolean collapsable) +em_format_html_set_animate_images (EMFormatHTML *efh, + gboolean animate_images) { g_return_if_fail (EM_IS_FORMAT_HTML (efh)); - efh->priv->headers_collapsable = collapsable; + efh->priv->animate_images = animate_images; - g_object_notify (G_OBJECT (efh), "headers-collapsable"); + g_object_notify (G_OBJECT (efh), "animate-images"); } CamelMimePart * @@ -1407,1184 +2111,60 @@ em_format_html_file_part (EMFormatHTML *efh, return part; } -/* all this api is a pain in the bum ... */ - -EMFormatHTMLPObject * -em_format_html_add_pobject (EMFormatHTML *efh, - gsize size, - const gchar *classid, - CamelMimePart *part, - EMFormatHTMLPObjectFunc func) -{ - EMFormatHTMLPObject *pobj; - - if (size < sizeof (EMFormatHTMLPObject)) { - g_warning ("size is less than the size of EMFormatHTMLPObject\n"); - size = sizeof (EMFormatHTMLPObject); - } - - pobj = g_malloc0 (size); - if (classid) - pobj->classid = g_strdup (classid); - else - pobj->classid = g_strdup_printf("e-object:///%s", ((EMFormat *)efh)->part_id->str); - - pobj->format = efh; - pobj->func = func; - pobj->part = part; - - g_queue_push_tail (&efh->pending_object_list, pobj); - - return pobj; -} - -EMFormatHTMLPObject * -em_format_html_find_pobject (EMFormatHTML *emf, - const gchar *classid) -{ - GList *link; - - g_return_val_if_fail (EM_IS_FORMAT_HTML (emf), NULL); - g_return_val_if_fail (classid != NULL, NULL); - - link = g_queue_peek_head_link (&emf->pending_object_list); - - while (link != NULL) { - EMFormatHTMLPObject *pw = link->data; - - if (!strcmp (pw->classid, classid)) - return pw; - - link = g_list_next (link); - } - - return NULL; -} - -EMFormatHTMLPObject * -em_format_html_find_pobject_func (EMFormatHTML *emf, - CamelMimePart *part, - EMFormatHTMLPObjectFunc func) -{ - GList *link; - - g_return_val_if_fail (EM_IS_FORMAT_HTML (emf), NULL); - - link = g_queue_peek_head_link (&emf->pending_object_list); - - while (link != NULL) { - EMFormatHTMLPObject *pw = link->data; - - if (pw->func == func && pw->part == part) - return pw; - - link = g_list_next (link); - } - - return NULL; -} - -void -em_format_html_remove_pobject (EMFormatHTML *emf, - EMFormatHTMLPObject *pobject) -{ - g_return_if_fail (EM_IS_FORMAT_HTML (emf)); - g_return_if_fail (pobject != NULL); - - g_queue_remove (&emf->pending_object_list, pobject); - - if (pobject->free != NULL) - pobject->free (pobject); - - g_free (pobject->classid); - g_free (pobject); -} - -void -em_format_html_clear_pobject (EMFormatHTML *emf) -{ - g_return_if_fail (EM_IS_FORMAT_HTML (emf)); - - while (!g_queue_is_empty (&emf->pending_object_list)) { - EMFormatHTMLPObject *pobj; - - pobj = g_queue_pop_head (&emf->pending_object_list); - em_format_html_remove_pobject (emf, pobj); - } -} - -struct _EMFormatHTMLJob * -em_format_html_job_new (EMFormatHTML *emfh, - void (*callback) (struct _EMFormatHTMLJob *job, - GCancellable *cancellable), - gpointer data) -{ - struct _EMFormatHTMLJob *job = g_malloc0 (sizeof (*job)); - - job->format = emfh; - job->puri_level = ((EMFormat *) emfh)->pending_uri_level; - job->callback = callback; - job->u.data = data; - if (((EMFormat *) emfh)->base) - job->base = camel_url_copy (((EMFormat *) emfh)->base); - - return job; -} - void -em_format_html_job_queue (EMFormatHTML *emfh, - struct _EMFormatHTMLJob *job) -{ - g_mutex_lock (emfh->priv->lock); - g_queue_push_tail (&emfh->priv->pending_jobs, job); - g_mutex_unlock (emfh->priv->lock); -} - -/* ********************************************************************** */ - -static void -emfh_getpuri (struct _EMFormatHTMLJob *job, - GCancellable *cancellable) -{ - d(printf(" running getpuri task\n")); - if (!g_cancellable_is_cancelled (cancellable)) - job->u.puri->func ( - EM_FORMAT (job->format), job->stream, - job->u.puri, cancellable); -} - -static void -emfh_configure_stream_for_proxy (CamelHttpStream *stream, - const gchar *uri) -{ - EProxy *proxy; - SoupURI *proxy_uri; - gchar *basic; - gchar *basic64; - const gchar *user = ""; - const gchar *password = ""; - - proxy = em_utils_get_proxy (); - - if (!e_proxy_require_proxy_for_uri (proxy, uri)) - return; - - proxy_uri = e_proxy_peek_uri_for (proxy, uri); - - if (proxy_uri == NULL) - return; - - if (proxy_uri->user != NULL) - user = proxy_uri->user; - - if (proxy_uri->password != NULL) - password = proxy_uri->password; - - if (*user == '\0' && *password == '\0') - return; - - basic = g_strdup_printf ("%s:%s", user, password); - basic64 = g_base64_encode ((guchar *) basic, strlen (basic)); - camel_http_stream_set_proxy_authpass (stream, basic64); - g_free (basic64); - g_free (basic); -} - -static void -emfh_gethttp (struct _EMFormatHTMLJob *job, - GCancellable *cancellable) -{ - CamelStream *cistream = NULL, *costream = NULL, *instream = NULL; - CamelURL *url; - CamelHttpStream *tmp_stream; - gssize n, total = 0, pc_complete = 0, nread = 0; - gchar buffer[1500]; - const gchar *length; - - if (g_cancellable_is_cancelled (cancellable) - || (url = camel_url_new (job->u.uri, NULL)) == NULL) - goto badurl; - - d(printf(" running load uri task: %s\n", job->u.uri)); - - if (emfh_http_cache) - instream = cistream = camel_data_cache_get (emfh_http_cache, EMFH_HTTP_CACHE_PATH, job->u.uri, NULL); - - if (instream == NULL) { - EMailImageLoadingPolicy policy; - - policy = em_format_html_get_image_loading_policy (job->format); - - if (!(job->format->priv->load_images_now - || policy == E_MAIL_IMAGE_LOADING_POLICY_ALWAYS - || (policy == E_MAIL_IMAGE_LOADING_POLICY_SOMETIMES - && em_utils_in_addressbook ((CamelInternetAddress *) camel_mime_message_get_from (job->format->parent.message), FALSE)))) { - /* TODO: Ideally we would put the http requests into - * another queue and only send them out if the user - * selects 'load images', when they do. The problem - * is how to maintain this state with multiple - * renderings, and how to adjust the thread - * dispatch/setup routine to handle it */ - camel_url_free (url); - goto done; - } - - instream = camel_http_stream_new (CAMEL_HTTP_METHOD_GET, ((EMFormat *) job->format)->session, url); - camel_http_stream_set_user_agent((CamelHttpStream *) instream, "CamelHttpStream/1.0 Evolution/" VERSION); - emfh_configure_stream_for_proxy ((CamelHttpStream *) instream, job->u.uri); - - camel_operation_push_message ( - cancellable, _("Retrieving '%s'"), job->u.uri); - tmp_stream = (CamelHttpStream *) instream; - if (camel_stream_read (CAMEL_STREAM (instream), NULL, 0, cancellable, NULL) == 0) { - CamelContentType *content_type; - - content_type = camel_http_stream_get_content_type (tmp_stream); - length = camel_header_raw_find(&tmp_stream->headers, "Content-Length", NULL); - d(printf(" Content-Length: %s\n", length)); - if (length != NULL) - total = atoi (length); - camel_content_type_unref (content_type); - } - } else - camel_operation_push_message ( - cancellable, _("Retrieving '%s'"), job->u.uri); - - camel_url_free (url); - - if (instream == NULL) - goto done; - - if (emfh_http_cache != NULL && cistream == NULL) - costream = camel_data_cache_add (emfh_http_cache, EMFH_HTTP_CACHE_PATH, job->u.uri, NULL); - - do { - if (g_cancellable_is_cancelled (cancellable)) { - n = -1; - break; - } - /* FIXME: progress reporting in percentage, can we get the length always? do we care? */ - n = camel_stream_read (instream, buffer, sizeof (buffer), cancellable, NULL); - if (n > 0) { - nread += n; - /* If we didn't get a valid Content-Length header, do not try to calculate percentage */ - if (total != 0) { - pc_complete = ((nread * 100) / total); - camel_operation_progress (cancellable, pc_complete); - } - d(printf(" read %d bytes\n", n)); - if (costream && camel_stream_write (costream, buffer, n, cancellable, NULL) == -1) { - n = -1; - break; - } - - camel_stream_write (job->stream, buffer, n, cancellable, NULL); - } - } while (n > 0); - - /* indicates success */ - if (n == 0) - camel_stream_close (job->stream, cancellable, NULL); - - if (costream) { - /* do not store broken files in a cache */ - if (n != 0) - camel_data_cache_remove (emfh_http_cache, EMFH_HTTP_CACHE_PATH, job->u.uri, NULL); - g_object_unref (costream); - } - - g_object_unref (instream); -done: - camel_operation_pop_message (cancellable); -badurl: - g_free (job->u.uri); -} - -/* ********************************************************************** */ - -static void -efh_url_requested (GtkHTML *html, - const gchar *url, - GtkHTMLStream *handle, - EMFormatHTML *efh) -{ - EMFormatPURI *puri; - struct _EMFormatHTMLJob *job = NULL; - - d(printf("url requested, html = %p, url '%s'\n", html, url)); - - puri = em_format_find_visible_puri ((EMFormat *) efh, url); - if (puri) { - CamelDataWrapper *dw = camel_medium_get_content ((CamelMedium *) puri->part); - CamelContentType *ct = dw ? dw->mime_type : NULL; - - /* GtkHTML only handles text and images. - * application/octet-stream parts are the only ones - * which are snooped for other content. So only try - * to pass these to it - any other types are badly - * formed or intentionally malicious emails. They - * will still show as attachments anyway */ - - if (ct && (camel_content_type_is(ct, "text", "*") - || camel_content_type_is(ct, "image", "*") - || camel_content_type_is(ct, "application", "octet-stream"))) { - puri->use_count++; - - d(printf(" adding puri job\n")); - job = em_format_html_job_new (efh, emfh_getpuri, puri); - } else { - d(printf(" part is unknown type '%s', not using\n", ct?camel_content_type_format(ct):"<unset>")); - gtk_html_stream_close (handle, GTK_HTML_STREAM_ERROR); - } - } else if (g_ascii_strncasecmp(url, "http:", 5) == 0 || g_ascii_strncasecmp(url, "https:", 6) == 0) { - d(printf(" adding job, get %s\n", url)); - job = em_format_html_job_new (efh, emfh_gethttp, g_strdup (url)); - } else if (g_str_has_prefix (url, "file://")) { - gchar *data = NULL; - gsize length = 0; - gboolean status; - gchar *path; - - path = g_filename_from_uri (url, NULL, NULL); - g_return_if_fail (path != NULL); - - status = g_file_get_contents (path, &data, &length, NULL); - if (status) - gtk_html_stream_write (handle, data, length); - - gtk_html_stream_close (handle, status ? GTK_HTML_STREAM_OK : GTK_HTML_STREAM_ERROR); - g_free (data); - g_free (path); - } else { - d(printf("HTML Includes reference to unknown uri '%s'\n", url)); - gtk_html_stream_close (handle, GTK_HTML_STREAM_ERROR); - } - - if (job) { - job->stream = em_html_stream_new (html, handle); - em_format_html_job_queue (efh, job); - } - - g_signal_stop_emission_by_name (html, "url-requested"); -} - -static gboolean -efh_object_requested (GtkHTML *html, - GtkHTMLEmbedded *eb, - EMFormatHTML *efh) -{ - EMFormatHTMLPObject *pobject; - gint res = FALSE; - - if (eb->classid == NULL) - return FALSE; - - pobject = em_format_html_find_pobject (efh, eb->classid); - if (pobject) { - /* This stops recursion of the part */ - g_queue_remove (&efh->pending_object_list, pobject); - res = pobject->func (efh, eb, pobject); - g_queue_push_head (&efh->pending_object_list, pobject); - } else { - d(printf("HTML Includes reference to unknown object '%s'\n", eb->classid)); - } - - return res; -} - -/* ********************************************************************** */ -#include "em-format/em-inline-filter.h" - -/* FIXME: This is duplicated in em-format-html-display, should be exported or in security module */ -static const struct { - const gchar *icon, *shortdesc; -} smime_sign_table[5] = { - { "stock_signature-bad", N_("Unsigned") }, - { "stock_signature-ok", N_("Valid signature") }, - { "stock_signature-bad", N_("Invalid signature") }, - { "stock_signature", N_("Valid signature, but cannot verify sender") }, - { "stock_signature-bad", N_("Signature exists, but need public key") }, -}; - -static const struct { - const gchar *icon, *shortdesc; -} smime_encrypt_table[4] = { - { "stock_lock-broken", N_("Unencrypted") }, - { "stock_lock", N_("Encrypted, weak"),}, - { "stock_lock-ok", N_("Encrypted") }, - { "stock_lock-ok", N_("Encrypted, strong") }, -}; - -static const gchar *smime_sign_colour[4] = { - "", " bgcolor=\"#88bb88\"", " bgcolor=\"#bb8888\"", " bgcolor=\"#e8d122\"" -}; - -/* TODO: this could probably be virtual on em-format-html - * then we only need one version of each type handler */ -static void -efh_format_secure (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - CamelCipherValidity *valid, - GCancellable *cancellable) -{ - EMFormatClass *format_class; - - format_class = EM_FORMAT_CLASS (parent_class); - g_return_if_fail (format_class->format_secure != NULL); - format_class->format_secure (emf, stream, part, valid, cancellable); - - /* To explain, if the validity is the same, then we are the - * base validity and now have a combined sign/encrypt validity - * we can display. Primarily a new verification context is - * created when we have an embeded message. */ - if (emf->valid == valid - && (valid->encrypt.status != CAMEL_CIPHER_VALIDITY_ENCRYPT_NONE - || valid->sign.status != CAMEL_CIPHER_VALIDITY_SIGN_NONE)) { - gchar *classid, *iconpath; - const gchar *icon; - CamelMimePart *iconpart; - GString *buffer; - - buffer = g_string_sized_new (1024); - - g_string_append_printf ( - buffer, - "<table border=0 width=\"100%%\" " - "cellpadding=3 cellspacing=0%s><tr>", - smime_sign_colour[valid->sign.status]); - - classid = g_strdup_printf ( - "smime:///em-format-html/%s/icon/signed", - emf->part_id->str); - g_string_append_printf ( - buffer, - "<td valign=\"top\"><img src=\"%s\"></td>" - "<td valign=\"top\" width=\"100%%\">", classid); - - if (valid->sign.status != 0) - icon = smime_sign_table[valid->sign.status].icon; - else - icon = smime_encrypt_table[valid->encrypt.status].icon; - iconpath = e_icon_factory_get_icon_filename (icon, GTK_ICON_SIZE_DIALOG); - iconpart = em_format_html_file_part((EMFormatHTML *)emf, "image/png", iconpath, cancellable); - if (iconpart) { - (void) em_format_add_puri (emf, sizeof (EMFormatPURI), classid, iconpart, efh_write_image); - g_object_unref (iconpart); - } - g_free (iconpath); - g_free (classid); - - if (valid->sign.status != CAMEL_CIPHER_VALIDITY_SIGN_NONE) { - g_string_append ( - buffer, _(smime_sign_table[valid->sign.status].shortdesc)); - - em_format_html_format_cert_infos ( - &valid->sign.signers, buffer); - } - - if (valid->encrypt.status != CAMEL_CIPHER_VALIDITY_ENCRYPT_NONE) { - if (valid->sign.status != CAMEL_CIPHER_VALIDITY_SIGN_NONE) - g_string_append (buffer, "<br>"); - - g_string_append ( - buffer, _(smime_encrypt_table[valid->encrypt.status].shortdesc)); - } - - g_string_append (buffer, "</td></tr></table>"); - - camel_stream_write ( - stream, buffer->str, - buffer->len, cancellable, NULL); - - g_string_free (buffer, TRUE); - } -} - -static void -efh_text_plain (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) -{ - EMFormatHTML *efh = EM_FORMAT_HTML (emf); - CamelStream *filtered_stream; - CamelMimeFilter *html_filter; - CamelMultipart *mp; - CamelDataWrapper *dw; - CamelContentType *type; - const gchar *format; - guint32 flags; - guint32 rgb; - gint i, count, len; - struct _EMFormatHTMLCache *efhc; - - flags = efh->text_html_flags; - - dw = camel_medium_get_content ((CamelMedium *) part); - if (!dw) - return; - - /* Check for RFC 2646 flowed text. */ - if (camel_content_type_is(dw->mime_type, "text", "plain") - && (format = camel_content_type_param(dw->mime_type, "format")) - && !g_ascii_strcasecmp(format, "flowed")) - flags |= CAMEL_MIME_FILTER_TOHTML_FORMAT_FLOWED; - - /* This scans the text part for inline-encoded data, creates - * a multipart of all the parts inside it. */ - - /* FIXME: We should discard this multipart if it only contains - * the original text, but it makes this hash lookup more complex */ - - /* TODO: We could probably put this in the superclass, since - * no knowledge of html is required - but this messes with - * filters a bit. Perhaps the superclass should just deal with - * html anyway and be done with it ... */ - - efhc = g_hash_table_lookup ( - efh->priv->text_inline_parts, - emf->part_id->str); - - if (efhc == NULL || (mp = efhc->textmp) == NULL) { - EMInlineFilter *inline_filter; - CamelStream *null; - CamelContentType *ct; - gboolean charset_added = FALSE; - - /* if we had to snoop the part type to get here, then - * use that as the base type, yuck */ - if (emf->snoop_mime_type == NULL - || (ct = camel_content_type_decode (emf->snoop_mime_type)) == NULL) { - ct = dw->mime_type; - camel_content_type_ref (ct); - } - - if (dw->mime_type && ct != dw->mime_type && camel_content_type_param (dw->mime_type, "charset")) { - camel_content_type_set_param (ct, "charset", camel_content_type_param (dw->mime_type, "charset")); - charset_added = TRUE; - } - - null = camel_stream_null_new (); - filtered_stream = camel_stream_filter_new (null); - g_object_unref (null); - inline_filter = em_inline_filter_new (camel_mime_part_get_encoding (part), ct); - camel_stream_filter_add ( - CAMEL_STREAM_FILTER (filtered_stream), - CAMEL_MIME_FILTER (inline_filter)); - camel_data_wrapper_decode_to_stream_sync ( - dw, (CamelStream *) filtered_stream, cancellable, NULL); - camel_stream_close ((CamelStream *) filtered_stream, cancellable, NULL); - g_object_unref (filtered_stream); - - mp = em_inline_filter_get_multipart (inline_filter); - if (efhc == NULL) - efhc = efh_insert_cache (efh, emf->part_id->str); - efhc->textmp = mp; - - if (charset_added) { - camel_content_type_set_param (ct, "charset", NULL); - } - - g_object_unref (inline_filter); - camel_content_type_unref (ct); - } - - rgb = e_color_to_value ( - &efh->priv->colors[EM_FORMAT_HTML_COLOR_CITATION]); - filtered_stream = camel_stream_filter_new (stream); - html_filter = camel_mime_filter_tohtml_new (flags, rgb); - camel_stream_filter_add ( - CAMEL_STREAM_FILTER (filtered_stream), html_filter); - g_object_unref (html_filter); - - /* We handle our made-up multipart here, so we don't recursively call ourselves */ - - len = emf->part_id->len; - count = camel_multipart_get_number (mp); - for (i = 0; i < count; i++) { - CamelMimePart *newpart = camel_multipart_get_part (mp, i); - - if (!newpart) - continue; - - type = camel_mime_part_get_content_type (newpart); - if (camel_content_type_is (type, "text", "*") && (is_fallback || !camel_content_type_is (type, "text", "calendar"))) { - gchar *content; - - content = g_strdup_printf ( - "<div style=\"border: solid #%06x 1px; " - "background-color: #%06x; padding: 10px; " - "color: #%06x;\">\n<tt>\n" EFH_MESSAGE_START, - e_color_to_value ( - &efh->priv->colors[ - EM_FORMAT_HTML_COLOR_FRAME]), - e_color_to_value ( - &efh->priv->colors[ - EM_FORMAT_HTML_COLOR_CONTENT]), - e_color_to_value ( - &efh->priv->colors[ - EM_FORMAT_HTML_COLOR_TEXT])); - camel_stream_write_string ( - stream, content, cancellable, NULL); - g_free (content); - - em_format_format_text ( - emf, filtered_stream, - (CamelDataWrapper *) newpart, - cancellable); - camel_stream_flush ((CamelStream *) filtered_stream, cancellable, NULL); - camel_stream_write_string (stream, "</tt>\n", cancellable, NULL); - camel_stream_write_string (stream, "</div>\n", cancellable, NULL); - } else { - g_string_append_printf (emf->part_id, ".inline.%d", i); - em_format_part ( - emf, stream, newpart, cancellable); - g_string_truncate (emf->part_id, len); - } - } - - g_object_unref (filtered_stream); -} - -static void -efh_text_enriched (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) -{ - EMFormatHTML *efh = EM_FORMAT_HTML (emf); - CamelStream *filtered_stream; - CamelMimeFilter *enriched; - guint32 flags = 0; - gchar *content; - - if (!strcmp(info->mime_type, "text/richtext")) { - flags = CAMEL_MIME_FILTER_ENRICHED_IS_RICHTEXT; - camel_stream_write_string ( - stream, "\n<!-- text/richtext -->\n", - cancellable, NULL); - } else { - camel_stream_write_string ( - stream, "\n<!-- text/enriched -->\n", - cancellable, NULL); - } - - enriched = camel_mime_filter_enriched_new (flags); - filtered_stream = camel_stream_filter_new (stream); - camel_stream_filter_add ( - CAMEL_STREAM_FILTER (filtered_stream), enriched); - g_object_unref (enriched); - - content = g_strdup_printf ( - "<div style=\"border: solid #%06x 1px; " - "background-color: #%06x; padding: 10px; " - "color: #%06x;\">\n" EFH_MESSAGE_START, - e_color_to_value ( - &efh->priv->colors[ - EM_FORMAT_HTML_COLOR_FRAME]), - e_color_to_value ( - &efh->priv->colors[ - EM_FORMAT_HTML_COLOR_CONTENT]), - e_color_to_value ( - &efh->priv->colors[ - EM_FORMAT_HTML_COLOR_TEXT])); - camel_stream_write_string (stream, content, cancellable, NULL); - g_free (content); - - em_format_format_text ( - emf, (CamelStream *) filtered_stream, - (CamelDataWrapper *) part, cancellable); - - g_object_unref (filtered_stream); - camel_stream_write_string (stream, "</div>", cancellable, NULL); -} - -static void -efh_write_text_html (EMFormat *emf, - CamelStream *stream, - EMFormatPURI *puri, - GCancellable *cancellable) +em_format_html_format_cert_infos (GQueue *cert_infos, + GString *output_buffer) { -#if d(!)0 - CamelStream *out; - gint fd; - CamelDataWrapper *dw; - - fd = dup (STDOUT_FILENO); - out = camel_stream_fs_new_with_fd (fd); - printf("writing text content to frame '%s'\n", puri->cid); - dw = camel_medium_get_content (puri->part); - if (dw) - camel_data_wrapper_write_to_stream (dw, out); - g_object_unref (out); -#endif - em_format_format_text ( - emf, stream, (CamelDataWrapper *) puri->part, cancellable); -} + GQueue valid = G_QUEUE_INIT; + GList *head, *link; -static void -efh_text_html (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) -{ - EMFormatHTML *efh = EM_FORMAT_HTML (emf); - const gchar *location; - gchar *cid = NULL; - gchar *content; + g_return_if_fail (cert_infos != NULL); + g_return_if_fail (output_buffer != NULL); - content = g_strdup_printf ( - "<div style=\"border: solid #%06x 1px; " - "background-color: #%06x; color: #%06x;\">\n" - "<!-- text/html -->\n" EFH_MESSAGE_START, - e_color_to_value ( - &efh->priv->colors[ - EM_FORMAT_HTML_COLOR_FRAME]), - e_color_to_value ( - &efh->priv->colors[ - EM_FORMAT_HTML_COLOR_CONTENT]), - e_color_to_value ( - &efh->priv->colors[ - EM_FORMAT_HTML_COLOR_TEXT])); - camel_stream_write_string (stream, content, cancellable, NULL); - g_free (content); + head = g_queue_peek_head_link (cert_infos); - /* TODO: perhaps we don't need to calculate this anymore now base is handled better */ - /* calculate our own location string so add_puri doesn't do it - * for us. our iframes are special cases, we need to use the - * proper base url to access them, but other children parts - * shouldn't blindly inherit the container's location. */ - location = camel_mime_part_get_content_location (part); - if (location == NULL) { - if (emf->base) - cid = camel_url_to_string (emf->base, 0); - else - cid = g_strdup (emf->part_id->str); - } else { - if (strchr (location, ':') == NULL && emf->base != NULL) { - CamelURL *uri; + /* Make sure we have a valid CamelCipherCertInfo before + * appending anything to the output buffer, so we don't + * end up with "()". */ + for (link = head; link != NULL; link = g_list_next (link)) { + CamelCipherCertInfo *cinfo = link->data; - uri = camel_url_new_with_base (emf->base, location); - cid = camel_url_to_string (uri, 0); - camel_url_free (uri); - } else { - cid = g_strdup (location); + if ((cinfo->name != NULL && *cinfo->name != '\0') || + (cinfo->email != NULL && *cinfo->email != '\0')) { + g_queue_push_tail (&valid, cinfo); } } - em_format_add_puri ( - emf, sizeof (EMFormatPURI), cid, - part, efh_write_text_html); - d(printf("adding iframe, location %s\n", cid)); - content = g_strdup_printf ( - "<iframe src=\"%s\" frameborder=0 scrolling=no>" - "could not get %s</iframe>\n</div>\n", cid, cid); - camel_stream_write_string (stream, content, cancellable, NULL); - g_free (content); - g_free (cid); -} - -/* This is a lot of code for something useless ... */ -static void -efh_message_external (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) -{ - CamelContentType *type; - const gchar *access_type; - gchar *url = NULL, *desc = NULL; - gchar *content; - - if (!part) { - camel_stream_write_string ( - stream, _("Unknown external-body part."), - cancellable, NULL); - return; - } - - /* needs to be cleaner */ - type = camel_mime_part_get_content_type (part); - access_type = camel_content_type_param (type, "access-type"); - if (!access_type) { - camel_stream_write_string ( - stream, _("Malformed external-body part."), - cancellable, NULL); - return; - } - - if (!g_ascii_strcasecmp(access_type, "ftp") || - !g_ascii_strcasecmp(access_type, "anon-ftp")) { - const gchar *name, *site, *dir, *mode; - gchar *path; - gchar ftype[16]; - - name = camel_content_type_param (type, "name"); - site = camel_content_type_param (type, "site"); - dir = camel_content_type_param (type, "directory"); - mode = camel_content_type_param (type, "mode"); - if (name == NULL || site == NULL) - goto fail; - - /* Generate the path. */ - if (dir) - path = g_strdup_printf("/%s/%s", *dir=='/'?dir+1:dir, name); - else - path = g_strdup_printf("/%s", *name=='/'?name+1:name); - - if (mode && *mode) - sprintf(ftype, ";type=%c", *mode); - else - ftype[0] = 0; - - url = g_strdup_printf ("ftp://%s%s%s", site, path, ftype); - g_free (path); - desc = g_strdup_printf (_("Pointer to FTP site (%s)"), url); - } else if (!g_ascii_strcasecmp (access_type, "local-file")) { - const gchar *name, *site; - - name = camel_content_type_param (type, "name"); - site = camel_content_type_param (type, "site"); - if (name == NULL) - goto fail; - - url = g_filename_to_uri (name, NULL, NULL); - if (site) - desc = g_strdup_printf(_("Pointer to local file (%s) valid at site \"%s\""), name, site); - else - desc = g_strdup_printf(_("Pointer to local file (%s)"), name); - } else if (!g_ascii_strcasecmp (access_type, "URL")) { - const gchar *urlparam; - gchar *s, *d; - - /* RFC 2017 */ - - urlparam = camel_content_type_param (type, "url"); - if (urlparam == NULL) - goto fail; - - /* For obscure MIMEy reasons, the URL may be split into words */ - url = g_strdup (urlparam); - s = d = url; - while (*s) { - /* FIXME: use camel_isspace */ - if (!isspace ((guchar) * s)) - *d++ = *s; - s++; - } - *d = 0; - desc = g_strdup_printf (_("Pointer to remote data (%s)"), url); - } else - goto fail; - - content = g_strdup_printf ("<a href=\"%s\">%s</a>", url, desc); - camel_stream_write_string (stream, content, cancellable, NULL); - g_free (content); - - g_free (url); - g_free (desc); - - return; - -fail: - content = g_strdup_printf ( - _("Pointer to unknown external data (\"%s\" type)"), - access_type); - camel_stream_write_string (stream, content, cancellable, NULL); - g_free (content); -} - -static void -efh_message_deliverystatus (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) -{ - EMFormatHTML *efh = EM_FORMAT_HTML (emf); - CamelStream *filtered_stream; - CamelMimeFilter *html_filter; - guint32 rgb = 0x737373; - gchar *content; - - /* Yuck, this is copied from efh_text_plain */ - content = g_strdup_printf ( - "<div style=\"border: solid #%06x 1px; " - "background-color: #%06x; padding: 10px; " - "color: #%06x;\">\n", - e_color_to_value ( - &efh->priv->colors[ - EM_FORMAT_HTML_COLOR_FRAME]), - e_color_to_value ( - &efh->priv->colors[ - EM_FORMAT_HTML_COLOR_CONTENT]), - e_color_to_value ( - &efh->priv->colors[ - EM_FORMAT_HTML_COLOR_TEXT])); - camel_stream_write_string (stream, content, cancellable, NULL); - g_free (content); - - filtered_stream = camel_stream_filter_new (stream); - html_filter = camel_mime_filter_tohtml_new (efh->text_html_flags, rgb); - camel_stream_filter_add ( - CAMEL_STREAM_FILTER (filtered_stream), html_filter); - g_object_unref (html_filter); - - camel_stream_write_string (stream, "<tt>\n" EFH_MESSAGE_START, cancellable, NULL); - em_format_format_text ( - emf, filtered_stream, - (CamelDataWrapper *) part, cancellable); - camel_stream_flush (filtered_stream, cancellable, NULL); - camel_stream_write_string (stream, "</tt>\n", cancellable, NULL); - - camel_stream_write_string (stream, "</div>", cancellable, NULL); -} - -static void -emfh_write_related (EMFormat *emf, - CamelStream *stream, - EMFormatPURI *puri, - GCancellable *cancellable) -{ - em_format_format_content (emf, stream, puri->part, cancellable); - - camel_stream_close (stream, cancellable, NULL); -} - -static void -emfh_multipart_related_check (struct _EMFormatHTMLJob *job, - GCancellable *cancellable) -{ - EMFormat *format; - GList *link; - gchar *oldpartid; - - if (g_cancellable_is_cancelled (cancellable)) + if (g_queue_is_empty (&valid)) return; - format = EM_FORMAT (job->format); + g_string_append (output_buffer, " ("); - d(printf(" running multipart/related check task\n")); - oldpartid = g_strdup (format->part_id->str); + while (!g_queue_is_empty (&valid)) { + CamelCipherCertInfo *cinfo; - link = g_queue_peek_head_link (job->puri_level->data); + cinfo = g_queue_pop_head (&valid); - if (!link) { - g_string_printf (format->part_id, "%s", oldpartid); - g_free (oldpartid); - return; - } + if (cinfo->name != NULL && *cinfo->name != '\0') { + g_string_append (output_buffer, cinfo->name); - while (link != NULL) { - EMFormatPURI *puri = link->data; - - if (puri->use_count == 0) { - d(printf("part '%s' '%s' used '%d'\n", puri->uri?puri->uri:"", puri->cid, puri->use_count)); - if (puri->func == emfh_write_related) { - g_string_printf (format->part_id, "%s", puri->part_id); - /* FIXME Not passing a GCancellable here. */ - em_format_part ( - format, CAMEL_STREAM (job->stream), - puri->part, NULL); + if (cinfo->email != NULL && *cinfo->email != '\0') { + g_string_append (output_buffer, " <"); + g_string_append (output_buffer, cinfo->email); + g_string_append (output_buffer, ">"); } - /* else it was probably added by a previous format this loop */ - } - - link = g_list_next (link); - } - - g_string_printf (format->part_id, "%s", oldpartid); - g_free (oldpartid); -} - -/* RFC 2387 */ -static void -efh_multipart_related (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) -{ - CamelMultipart *mp = (CamelMultipart *) camel_medium_get_content ((CamelMedium *) part); - CamelMimePart *body_part, *display_part = NULL; - CamelContentType *content_type; - const gchar *start; - gint i, nparts, partidlen, displayid = 0; - struct _EMFormatHTMLJob *job; - - if (!CAMEL_IS_MULTIPART (mp)) { - em_format_format_source (emf, stream, part, cancellable); - return; - } - - nparts = camel_multipart_get_number (mp); - content_type = camel_mime_part_get_content_type (part); - start = camel_content_type_param (content_type, "start"); - if (start && strlen (start) > 2) { - gint len; - const gchar *cid; - /* strip <>'s */ - len = strlen (start) - 2; - start++; - - for (i = 0; i < nparts; i++) { - body_part = camel_multipart_get_part (mp, i); - cid = camel_mime_part_get_content_id (body_part); - - if (cid && !strncmp (cid, start, len) && strlen (cid) == len) { - display_part = body_part; - displayid = i; - break; - } + } else if (cinfo->email != NULL && *cinfo->email != '\0') { + g_string_append (output_buffer, cinfo->email); } - } else { - display_part = camel_multipart_get_part (mp, 0); - } - if (display_part == NULL) { - em_format_part_as ( - emf, stream, part, - "multipart/mixed", cancellable); - return; - } - - em_format_push_level (emf); - - partidlen = emf->part_id->len; - - /* queue up the parts for possible inclusion */ - for (i = 0; i < nparts; i++) { - body_part = camel_multipart_get_part (mp, i); - if (body_part != display_part) { - g_string_append_printf(emf->part_id, "related.%d", i); - em_format_add_puri (emf, sizeof (EMFormatPURI), NULL, body_part, emfh_write_related); - g_string_truncate (emf->part_id, partidlen); - d(printf(" part '%s' '%s' added\n", puri->uri?puri->uri:"", puri->cid)); - } + if (!g_queue_is_empty (&valid)) + g_string_append (output_buffer, ", "); } - g_string_append_printf(emf->part_id, "related.%d", displayid); - em_format_part (emf, stream, display_part, cancellable); - g_string_truncate (emf->part_id, partidlen); - camel_stream_flush (stream, cancellable, NULL); - - /* queue a job to check for un-referenced parts to add as attachments */ - job = em_format_html_job_new ( - EM_FORMAT_HTML (emf), emfh_multipart_related_check, NULL); - job->stream = stream; - g_object_ref (stream); - em_format_html_job_queue ((EMFormatHTML *) emf, job); - - em_format_pull_level (emf); -} - -static void -efh_write_image (EMFormat *emf, - CamelStream *stream, - EMFormatPURI *puri, - GCancellable *cancellable) -{ - CamelDataWrapper *dw = camel_medium_get_content ((CamelMedium *) puri->part); - - d(printf("writing image '%s'\n", puri->cid)); - camel_data_wrapper_decode_to_stream_sync ( - dw, stream, cancellable, NULL); - camel_stream_close (stream, cancellable, NULL); -} - -static void -efh_image (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) -{ - EMFormatPURI *puri; - gchar *content; - - puri = em_format_add_puri ( - emf, sizeof (EMFormatPURI), NULL, part, efh_write_image); - - content = g_strdup_printf ( - "<img hspace=10 vspace=10 src=\"%s\">", puri->cid); - camel_stream_write_string (stream, content, cancellable, NULL); - g_free (content); -} - -/* Notes: - * - * image/tiff is omitted because it's a multi-page image format, but - * gdk-pixbuf unconditionally renders the first page only, and doesn't - * even indicate through meta-data whether multiple pages are present - * (see bug 335959). Therefore, make no attempt to render TIFF images - * inline and defer to an application that can handle multi-page TIFF - * files properly like Evince or Gimp. Once the referenced bug is - * fixed we can reevaluate this policy. - */ -static EMFormatHandler type_builtin_table[] = { - { (gchar *) "image/gif", efh_image }, - { (gchar *) "image/jpeg", efh_image }, - { (gchar *) "image/png", efh_image }, - { (gchar *) "image/x-png", efh_image }, - { (gchar *) "image/x-bmp", efh_image }, - { (gchar *) "image/bmp", efh_image }, - { (gchar *) "image/svg", efh_image }, - { (gchar *) "image/x-cmu-raster", efh_image }, - { (gchar *) "image/x-ico", efh_image }, - { (gchar *) "image/x-portable-anymap", efh_image }, - { (gchar *) "image/x-portable-bitmap", efh_image }, - { (gchar *) "image/x-portable-graymap", efh_image }, - { (gchar *) "image/x-portable-pixmap", efh_image }, - { (gchar *) "image/x-xpixmap", efh_image }, - { (gchar *) "text/enriched", efh_text_enriched }, - { (gchar *) "text/plain", efh_text_plain }, - { (gchar *) "text/html", efh_text_html }, - { (gchar *) "text/richtext", efh_text_enriched }, - { (gchar *) "text/*", efh_text_plain }, - { (gchar *) "message/external-body", efh_message_external }, - { (gchar *) "message/delivery-status", efh_message_deliverystatus }, - { (gchar *) "multipart/related", efh_multipart_related }, - - /* This is where one adds those busted, non-registered types, - * that some idiot mailer writers out there decide to pull out - * of their proverbials at random. */ - - { (gchar *) "image/jpg", efh_image }, - { (gchar *) "image/pjpeg", efh_image }, - - /* special internal types */ - - { (gchar *) "x-evolution/message/rfc822", efh_format_message } -}; - -static void -efh_builtin_init (EMFormatHTMLClass *efhc) -{ - EMFormatClass *efc; - gint ii; - - efc = (EMFormatClass *) efhc; - - for (ii = 0; ii < G_N_ELEMENTS (type_builtin_table); ii++) - em_format_class_add_handler ( - efc, &type_builtin_table[ii]); + g_string_append_c (output_buffer, ')'); } -/* ********************************************************************** */ - static void efh_format_text_header (EMFormatHTML *emfh, GString *buffer, @@ -2608,37 +2188,34 @@ efh_format_text_header (EMFormatHTML *emfh, html = value; is_rtl = gtk_widget_get_default_direction () == GTK_TEXT_DIR_RTL; - if (emfh->simple_headers) { - fmt = "<b>%s</b>: %s<br>"; + + if (flags & EM_FORMAT_HTML_HEADER_NOCOLUMNS) { + if (flags & EM_FORMAT_HEADER_BOLD) { + fmt = "<tr class=\"header-item\" style=\"display: %s\"><td><b>%s:</b> %s</td></tr>"; + } else { + fmt = "<tr class=\"header-item\" style=\"display: %s\"><td>%s: %s</td></tr>"; + } + } else if (flags & EM_FORMAT_HTML_HEADER_NODEC) { + if (is_rtl) + fmt = "<tr class=\"header-item rtl\" style=\"display: %s\"><td align=\"right\" valign=\"top\" width=\"100%%\">%2$s</td><th valign=top align=\"left\" nowrap>%1$s<b> </b></th></tr>"; + else + fmt = "<tr class=\"header-item\" style=\"display: %s\"><th align=\"right\" valign=\"top\" nowrap>%s<b> </b></th><td valign=top>%s</td></tr>"; } else { - if (flags & EM_FORMAT_HTML_HEADER_NOCOLUMNS) { - if (flags & EM_FORMAT_HEADER_BOLD) { - fmt = "<tr><td><b>%s:</b> %s</td></tr>"; - } else { - fmt = "<tr><td>%s: %s</td></tr>"; - } - } else if (flags & EM_FORMAT_HTML_HEADER_NODEC) { + if (flags & EM_FORMAT_HEADER_BOLD) { if (is_rtl) - fmt = "<tr><td align=\"right\" valign=\"top\" width=\"100%%\">%2$s</td><th valign=top align=\"left\" nowrap>%1$s<b> </b></th></tr>"; + fmt = "<tr class=\"header-item rtl\" style=\"display: %s\"><td align=\"right\" valign=\"top\" width=\"100%%\">%2$s</td><th align=\"left\" nowrap>%1$s:<b> </b></th></tr>"; else - fmt = "<tr><th align=\"right\" valign=\"top\" nowrap>%s<b> </b></th><td valign=top>%s</td></tr>"; + fmt = "<tr class=\"header-item\" style=\"display: %s\"><th align=\"right\" valign=\"top\" nowrap>%s:<b> </b></th><td>%s</td></tr>"; } else { - - if (flags & EM_FORMAT_HEADER_BOLD) { - if (is_rtl) - fmt = "<tr><td align=\"right\" valign=\"top\" width=\"100%%\">%2$s</td><th align=\"left\" nowrap>%1$s:<b> </b></th></tr>"; - else - fmt = "<tr><th align=\"right\" valign=\"top\" nowrap>%s:<b> </b></th><td>%s</td></tr>"; - } else { - if (is_rtl) - fmt = "<tr><td align=\"right\" valign=\"top\" width=\"100%\">%2$s</td><td align=\"left\" nowrap>%1$s:<b> </b></td></tr>"; - else - fmt = "<tr><td align=\"right\" valign=\"top\" nowrap>%s:<b> </b></td><td>%s</td></tr>"; - } + if (is_rtl) + fmt = "<tr class=\"header-item rtl\" style=\"display: %s\"><td align=\"right\" valign=\"top\" width=\"100%\">%2$s</td><td align=\"left\" nowrap>%1$s:<b> </b></td></tr>"; + else + fmt = "<tr class=\"header-item\" style=\"display: %s\"><td align=\"right\" valign=\"top\" nowrap>%s:<b> </b></td><td>%s</td></tr>"; } } - g_string_append_printf (buffer, fmt, label, html); + g_string_append_printf (buffer, fmt, + (flags & EM_FORMAT_HTML_HEADER_HIDDEN ? "none" : "table-row"), label, html); g_free (mhtml); } @@ -2653,22 +2230,15 @@ static gchar * efh_format_address (EMFormatHTML *efh, GString *out, struct _camel_header_address *a, - gchar *field) + gchar *field, + gboolean no_links) { guint32 flags = CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES; gchar *name, *mailto, *addr; gint i = 0; - gboolean wrap = FALSE; gchar *str = NULL; gint limit = mail_config_get_address_count (); - if (field ) { - if ((!strcmp (field, _("To")) && !(efh->header_wrap_flags & EM_FORMAT_HTML_HEADER_TO)) - || (!strcmp (field, _("Cc")) && !(efh->header_wrap_flags & EM_FORMAT_HTML_HEADER_CC)) - || (!strcmp (field, _("Bcc")) && !(efh->header_wrap_flags & EM_FORMAT_HTML_HEADER_BCC))) - wrap = TRUE; - } - while (a) { if (a->name) name = camel_text_to_html (a->name, flags, 0); @@ -2700,7 +2270,10 @@ efh_format_address (EMFormatHTML *efh, mailto = camel_url_encode (a->v.addr, "?=&()"); } addr = camel_text_to_html (a->v.addr, flags, 0); - g_string_append_printf (out, "<a href=\"mailto:%s\">%s</a>", mailto, addr); + if (no_links) + g_string_append_printf (out, "%s", addr); + else + g_string_append_printf (out, "<a href=\"mailto:%s\">%s</a>", mailto, addr); g_free (mailto); g_free (addr); @@ -2709,7 +2282,7 @@ efh_format_address (EMFormatHTML *efh, break; case CAMEL_HEADER_ADDRESS_GROUP: g_string_append_printf (out, "%s: ", name); - efh_format_address (efh, out, a->v.members, field); + efh_format_address (efh, out, a->v.members, field, no_links); g_string_append_printf (out, ";"); break; default: @@ -2725,48 +2298,51 @@ efh_format_address (EMFormatHTML *efh, g_string_append (out, ", "); /* Let us add a '...' if we have more addresses */ - if (limit > 0 && wrap && a && (i > (limit - 1))) { - gchar *evolution_imagesdir = g_filename_to_uri (EVOLUTION_IMAGESDIR, NULL, NULL); - - if (!strcmp (field, _("To"))) { - g_string_append (out, "<a href=\"##TO##\">...</a>"); - str = g_strdup_printf ("<a href=\"##TO##\"><img src=\"%s/plus.png\"></a> ", evolution_imagesdir); - } - else if (!strcmp (field, _("Cc"))) { - g_string_append (out, "<a href=\"##CC##\">...</a>"); - str = g_strdup_printf ("<a href=\"##CC##\"><img src=\"%s/plus.png\"></a> ", evolution_imagesdir); + if (limit > 0 && (i == limit - 1)) { + const gchar *id = NULL; + + if (strcmp (field, _("To")) == 0) { + id = "to"; + } else if (strcmp (field, _("Cc")) == 0) { + id = "cc"; + } else if (strcmp (field, _("Bcc")) == 0) { + id = "bcc"; } - else if (!strcmp (field, _("Bcc"))) { - g_string_append (out, "<a href=\"##BCC##\">...</a>"); - str = g_strdup_printf ("<a href=\"##BCC##\"><img src=\"%s/plus.png\"></a> ", evolution_imagesdir); - } - - g_free (evolution_imagesdir); - if (str) - return str; + if (id) { + g_string_append_printf (out, + "<span id=\"__evo-moreaddr-%s\" " + "style=\"display: none;\">", id); + str = g_strdup_printf ( + "<img src=\"evo-file://%s/plus.png\" " + "id=\"__evo-moreaddr-img-%s\" class=\"navigable\">", + EVOLUTION_IMAGESDIR, id); + } } - } - if (limit > 0 && i > (limit)) { - gchar *evolution_imagesdir = g_filename_to_uri (EVOLUTION_IMAGESDIR, NULL, NULL); + if (str) { + const gchar *id = NULL; - if (!strcmp (field, _("To"))) { - str = g_strdup_printf ("<a href=\"##TO##\"><img src=\"%s/minus.png\"></a> ", evolution_imagesdir); - } - else if (!strcmp (field, _("Cc"))) { - str = g_strdup_printf ("<a href=\"##CC##\"><img src=\"%s/minus.png\"></a> ", evolution_imagesdir); - } - else if (!strcmp (field, _("Bcc"))) { - str = g_strdup_printf ("<a href=\"##BCC##\"><img src=\"%s/minus.png\"></a> ", evolution_imagesdir); + if (strcmp (field, _("To")) == 0) { + id = "to"; + } else if (strcmp (field, _("Cc")) == 0) { + id = "cc"; + } else if (strcmp (field, _("Bcc")) == 0) { + id = "bcc"; } - g_free (evolution_imagesdir); + if (id) { + g_string_append_printf (out, + "</span>" + "<span class=\"navigable\" " + "id=\"__evo-moreaddr-ellipsis-%s\" " + "style=\"display: inline;\">...</span>", + id); + } } return str; - } static void @@ -2793,15 +2369,15 @@ canon_header_name (gchar *name) } } -static void -efh_format_header (EMFormat *emf, - GString *buffer, - CamelMedium *part, - struct _camel_header_raw *header, - guint32 flags, - const gchar *charset) +void +em_format_html_format_header (EMFormat *emf, + GString *buffer, + CamelMedium *part, + struct _camel_header_raw *header, + guint32 flags, + const gchar *charset) { - EMFormatHTML *efh = (EMFormatHTML *) emf; + EMFormatHTML *efh = EM_FORMAT_HTML (emf); gchar *name, *buf, *value = NULL; const gchar *label, *txt; gboolean addrspec = FALSE; @@ -2825,9 +2401,11 @@ efh_format_header (EMFormat *emf, struct _camel_header_address *addrs; GString *html; gchar *img; + const gchar *charset = em_format_get_charset (emf) ? + em_format_get_charset (emf) : em_format_get_default_charset (emf); buf = camel_header_unfold (header->value); - if (!(addrs = camel_header_address_decode (buf, emf->charset ? emf->charset : emf->default_charset))) { + if (!(addrs = camel_header_address_decode (buf, charset))) { g_free (buf); return; } @@ -2835,7 +2413,8 @@ efh_format_header (EMFormat *emf, g_free (buf); html = g_string_new(""); - img = efh_format_address (efh, html, addrs, (gchar *) label); + img = efh_format_address (efh, html, addrs, (gchar *) label, + (flags & EM_FORMAT_HTML_HEADER_NOLINKS)); if (img) { str_field = g_strdup_printf ("%s%s:", img, label); @@ -2921,7 +2500,11 @@ efh_format_header (EMFormat *emf, html = g_string_new(""); scan = ng; while (scan) { - g_string_append_printf(html, "<a href=\"news:%s\">%s</a>", scan->newsgroup, scan->newsgroup); + if (flags & EM_FORMAT_HTML_HEADER_NOLINKS) + g_string_append_printf (html, "%s", scan->newsgroup); + else + g_string_append_printf(html, "<a href=\"news:%s\">%s</a>", + scan->newsgroup, scan->newsgroup); scan = scan->next; if (scan) g_string_append_printf(html, ", "); @@ -2949,100 +2532,129 @@ efh_format_header (EMFormat *emf, } static void -efh_format_headers (EMFormatHTML *efh, - GString *buffer, - CamelMedium *part, - GCancellable *cancellable) +efh_format_short_headers (EMFormatHTML *efh, + GString *buffer, + CamelMedium *part, + gboolean visible, + GCancellable *cancellable) { - EMFormat *emf = (EMFormat *) efh; + EMFormat *emf = EM_FORMAT (efh); const gchar *charset; CamelContentType *ct; - struct _camel_header_raw *header; - gboolean have_icon = FALSE; - const gchar *photo_name = NULL; - CamelInternetAddress *cia = NULL; - gboolean face_decoded = FALSE, contact_has_photo = FALSE; - guchar *face_header_value = NULL; - gsize face_header_len = 0; - gchar *header_sender = NULL, *header_from = NULL, *name; - gboolean mail_from_delegate = FALSE; const gchar *hdr_charset; gchar *evolution_imagesdir; + gchar *subject = NULL; + struct _camel_header_address *addrs = NULL; + struct _camel_header_raw *header; + GString *from; + gboolean is_rtl; - if (!part) + if (cancellable && g_cancellable_is_cancelled (cancellable)) return; ct = camel_mime_part_get_content_type ((CamelMimePart *) part); charset = camel_content_type_param (ct, "charset"); charset = camel_iconv_charset_name (charset); + hdr_charset = em_format_get_charset (emf) ? + em_format_get_charset (emf) : em_format_get_default_charset (emf); - if (!efh->simple_headers) - g_string_append_printf ( - buffer, "<font color=\"#%06x\">\n" - "<table cellpadding=\"0\" width=\"100%%\">", - e_color_to_value ( - &efh->priv->colors[ - EM_FORMAT_HTML_COLOR_HEADER])); - - hdr_charset = emf->charset ? emf->charset : emf->default_charset; evolution_imagesdir = g_filename_to_uri (EVOLUTION_IMAGESDIR, NULL, NULL); + from = g_string_new (""); - /* If the header is collapsed, display just subject and sender in one row and leave */ - if (efh->priv->headers_state == EM_FORMAT_HTML_HEADERS_STATE_COLLAPSED && efh->priv->headers_collapsable) { - gchar *subject = NULL; - struct _camel_header_address *addrs = NULL; - GString *from = g_string_new (""); + g_string_append_printf (buffer, + "<table cellspacing=\"0\" cellpadding=\"0\" border=\"0\" " + "id=\"__evo-short-headers\" style=\"display: %s\">", + visible ? "block" : "none"); - header = ((CamelMimePart *) part)->headers; - while (header) { - if (!g_ascii_strcasecmp (header->name, "From")) { - GString *tmp; - if (!(addrs = camel_header_address_decode (header->value, hdr_charset))) { - header = header->next; - continue; - } - tmp = g_string_new (""); - efh_format_address (efh, tmp, addrs, header->name); - - if (tmp->len) - g_string_printf (from, _("From: %s"), tmp->str); - g_string_free (tmp, TRUE); - } else if (!g_ascii_strcasecmp (header->name, "Subject")) { - gchar *buf = NULL; - buf = camel_header_unfold (header->value); - g_free (subject); - subject = camel_header_decode_string (buf, hdr_charset); - g_free (buf); + header = ((CamelMimePart *) part)->headers; + while (header) { + if (!g_ascii_strcasecmp (header->name, "From")) { + GString *tmp; + if (!(addrs = camel_header_address_decode (header->value, hdr_charset))) { + header = header->next; + continue; } - header = header->next; + tmp = g_string_new (""); + efh_format_address (efh, tmp, addrs, header->name, FALSE); + + if (tmp->len) + g_string_printf (from, _("From: %s"), tmp->str); + g_string_free (tmp, TRUE); + + } else if (!g_ascii_strcasecmp (header->name, "Subject")) { + gchar *buf = NULL; + subject = camel_header_unfold (header->value); + buf = camel_header_decode_string (subject, hdr_charset); + g_free (subject); + subject = camel_text_to_html (buf, CAMEL_MIME_FILTER_TOHTML_PRESERVE_8BIT, 0); + g_free (buf); } + header = header->next; + } + is_rtl = gtk_widget_get_default_direction () == GTK_TEXT_DIR_RTL; + if (is_rtl) { g_string_append_printf ( buffer, - "<tr>" - "<td width=\"20\" valign=\"top\">" - "<a href=\"##HEADERS##\">" - "<img src=\"%s/plus.png\">" - "</a></td>" - "<td><strong>%s</strong> %s%s%s</td>" - "</tr>", - evolution_imagesdir, - subject ? subject : _("(no subject)"), - from->len ? "(" : "", - from->str, - from->len ? ")" : ""); - - g_free (subject); - if (addrs) - camel_header_address_list_clear (&addrs); - g_string_free (from, TRUE); + "<tr><td width=\"100%%\" align=\"right\">%s%s%s <strong>%s</strong></td></tr>", + from->len ? "(" : "", from->str, from->len ? ")" : "", + subject ? subject : _("(no subject)")); + } else { + g_string_append_printf ( + buffer, + "<tr><td><strong>%s</strong> %s%s%s</td></tr>", + subject ? subject : _("(no subject)"), + from->len ? "(" : "", from->str, from->len ? ")" : ""); + } + + g_string_append (buffer, "</table>"); + + g_free (subject); + if (addrs) + camel_header_address_list_clear (&addrs); - g_string_append (buffer, "</table>"); + g_string_free (from, TRUE); + g_free (evolution_imagesdir); +} - g_free (evolution_imagesdir); +static void +efh_format_full_headers (EMFormatHTML *efh, + GString *buffer, + CamelMedium *part, + gboolean all_headers, + gboolean visible, + GCancellable *cancellable) +{ + EMFormat *emf = EM_FORMAT (efh); + const gchar *charset; + CamelContentType *ct; + struct _camel_header_raw *header; + gboolean have_icon = FALSE; + const gchar *photo_name = NULL; + CamelInternetAddress *cia = NULL; + gboolean face_decoded = FALSE, contact_has_photo = FALSE; + guchar *face_header_value = NULL; + gsize face_header_len = 0; + gchar *header_sender = NULL, *header_from = NULL, *name; + gboolean mail_from_delegate = FALSE; + const gchar *hdr_charset; + gchar *evolution_imagesdir; + if (cancellable && g_cancellable_is_cancelled (cancellable)) return; - } + + ct = camel_mime_part_get_content_type ((CamelMimePart *) part); + charset = camel_content_type_param (ct, "charset"); + charset = camel_iconv_charset_name (charset); + hdr_charset = em_format_get_charset (emf) ? + em_format_get_charset (emf) : em_format_get_default_charset (emf); + + evolution_imagesdir = g_filename_to_uri (EVOLUTION_IMAGESDIR, NULL, NULL); + + g_string_append_printf (buffer, + "<table cellspacing=\"0\" cellpadding=\"0\" border=\"0\" " + "id=\"__evo-full-headers\" style=\"display: %s\" width=\"100%%\">", + visible ? "block" : "none"); header = ((CamelMimePart *) part)->headers; while (header) { @@ -3054,7 +2666,7 @@ efh_format_headers (EMFormatHTML *efh, break; html = g_string_new(""); - name = efh_format_address (efh, html, addrs, header->name); + name = efh_format_address (efh, html, addrs, header->name, FALSE); header_sender = html->str; camel_header_address_list_clear (&addrs); @@ -3069,7 +2681,7 @@ efh_format_headers (EMFormatHTML *efh, break; html = g_string_new(""); - name = efh_format_address (efh, html, addrs, header->name); + name = efh_format_address (efh, html, addrs, header->name, FALSE); header_from = html->str; camel_header_address_list_clear (&addrs); @@ -3113,50 +2725,15 @@ efh_format_headers (EMFormatHTML *efh, g_free (header_sender); g_free (header_from); - if (gtk_widget_get_default_direction () == GTK_TEXT_DIR_RTL) { - if (efh->priv->headers_collapsable) - g_string_append_printf ( - buffer, - "<tr>" - "<td valign=\"top\" width=\"20\">" - "<a href=\"##HEADERS##\">" - "<img src=\"%s/minus.png\">" - "</a></td>" - "<td><table width=\"100%%\" border=0 " - "cellpadding=\"0\">\n", - evolution_imagesdir); - else - g_string_append ( - buffer, - "<tr><td>" - "<table width=\"100%%\" border=0 " - "cellpadding=\"0\">\n"); - - } else { - if (efh->priv->headers_collapsable) - g_string_append_printf ( - buffer, - "<tr>" - "<td valign=\"top\" width=\"20\">" - "<a href=\"##HEADERS##\">" - "<img src=\"%s/minus.png\">" - "</a></td>" - "<td><table border=0 cellpadding=\"0\">\n", - evolution_imagesdir); - else - g_string_append ( - buffer, - "<tr><td>" - "<table border=0 cellpadding=\"0\">\n"); - } + g_string_append (buffer, "<tr><td><table border=0 cellpadding=\"0\">\n"); g_free (evolution_imagesdir); /* dump selected headers */ - if (emf->mode == EM_FORMAT_MODE_ALLHEADERS) { + if (all_headers) { header = ((CamelMimePart *) part)->headers; while (header) { - efh_format_header ( + em_format_html_format_header ( emf, buffer, part, header, EM_FORMAT_HTML_HEADER_NOCOLUMNS, charset); header = header->next; @@ -3205,7 +2782,7 @@ efh_format_headers (EMFormatHTML *efh, xmailer.value = use_header->value; mailer_shown = TRUE; - efh_format_header ( + em_format_html_format_header ( emf, buffer, part, &xmailer, h->flags, charset); if (strstr(use_header->value, "Evolution")) @@ -3226,7 +2803,7 @@ efh_format_headers (EMFormatHTML *efh, face_decoded = TRUE; /* Showing an encoded "Face" header makes little sense */ } else if (!g_ascii_strcasecmp (header->name, h->name) && !face) { - efh_format_header ( + em_format_html_format_header ( emf, buffer, part, header, h->flags, charset); } @@ -3238,214 +2815,162 @@ efh_format_headers (EMFormatHTML *efh, } } - if (!efh->simple_headers) { - g_string_append (buffer, "</table></td>"); - - if (photo_name) { - gchar *classid; - CamelMimePart *photopart; - gboolean only_local_photo; - - cia = camel_internet_address_new (); - camel_address_decode ((CamelAddress *) cia, (const gchar *) photo_name); - only_local_photo = em_format_html_get_only_local_photos (efh); - photopart = em_utils_contact_photo (cia, only_local_photo); - - if (photopart) { - contact_has_photo = TRUE; - classid = g_strdup_printf ( - "icon:///em-format-html/%s/photo/header", - emf->part_id->str); - g_string_append_printf ( - buffer, - "<td align=\"right\" valign=\"top\">" - "<img width=64 src=\"%s\"></td>", - classid); - em_format_add_puri (emf, sizeof (EMFormatPURI), classid, - photopart, efh_write_image); - g_object_unref (photopart); - - g_free (classid); - } - g_object_unref (cia); - } + g_string_append (buffer, "</table></td>"); - if (!contact_has_photo && face_decoded) { - gchar *classid; - CamelMimePart *part; - - part = camel_mime_part_new (); - camel_mime_part_set_content ( - (CamelMimePart *) part, - (const gchar *) face_header_value, - face_header_len, "image/png"); - classid = g_strdup_printf ( - "icon:///em-format-html/face/photo/header"); - g_string_append_printf ( - buffer, - "<td align=\"right\" valign=\"top\">" - "<img width=48 src=\"%s\"></td>", - classid); - em_format_add_puri ( - emf, sizeof (EMFormatPURI), - classid, part, efh_write_image); - g_object_unref (part); - } + if (photo_name) { + const gchar *classid; + CamelMimePart *photopart; + gboolean only_local_photo; - if (have_icon && efh->show_icon) { - GtkIconInfo *icon_info; - gchar *classid; - CamelMimePart *iconpart = NULL; + cia = camel_internet_address_new (); + camel_address_decode ((CamelAddress *) cia, (const gchar *) photo_name); + only_local_photo = em_format_html_get_only_local_photos (efh); + photopart = em_utils_contact_photo (cia, only_local_photo); - classid = g_strdup_printf ( - "icon:///em-format-html/%s/icon/header", - emf->part_id->str); + if (photopart) { + EMFormatPURI *puri; + contact_has_photo = TRUE; + classid = "icon:///em-format-html/headers/photo"; g_string_append_printf ( buffer, "<td align=\"right\" valign=\"top\">" - "<img width=16 height=16 src=\"%s\"></td>", + "<img width=64 src=\"%s\"></td>", classid); - - icon_info = gtk_icon_theme_lookup_icon ( - gtk_icon_theme_get_default (), - "evolution", 16, GTK_ICON_LOOKUP_NO_SVG); - if (icon_info != NULL) { - iconpart = em_format_html_file_part ( - (EMFormatHTML *) emf, "image/png", - gtk_icon_info_get_filename (icon_info), - cancellable); - gtk_icon_info_free (icon_info); - } - - if (iconpart) { - em_format_add_puri ( - emf, sizeof (EMFormatPURI), - classid, iconpart, efh_write_image); - g_object_unref (iconpart); - } - g_free (classid); + puri = em_format_puri_new ( + emf, sizeof (EMFormatPURI), photopart, classid); + puri->write_func = efh_write_image; + em_format_add_puri (emf, puri); + g_object_unref (photopart); } - - g_string_append (buffer, "</tr></table>\n</font>\n"); + g_object_unref (cia); } -} - -static void -efh_format_message (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) -{ - const EMFormatHandler *handle; - GString *buffer; - - /* TODO: make this validity stuff a method */ - EMFormatHTML *efh = (EMFormatHTML *) emf; - CamelCipherValidity *save = emf->valid, *save_parent = emf->valid_parent; - - emf->valid = NULL; - emf->valid_parent = NULL; - - buffer = g_string_sized_new (1024); - if (emf->message != (CamelMimeMessage *) part) - g_string_append (buffer, "<blockquote>\n"); - - if (!efh->hide_headers) - efh_format_headers ( - efh, buffer, CAMEL_MEDIUM (part), cancellable); - - camel_stream_write ( - stream, buffer->str, buffer->len, cancellable, NULL); - - g_string_free (buffer, TRUE); + if (!contact_has_photo && face_decoded) { + const gchar *classid; + CamelMimePart *part; + EMFormatPURI *puri; + + part = camel_mime_part_new (); + camel_mime_part_set_content ( + (CamelMimePart *) part, + (const gchar *) face_header_value, + face_header_len, "image/png"); + classid = "icon:///em-format-html/headers/face/photo"; + g_string_append_printf ( + buffer, + "<td align=\"right\" valign=\"top\">" + "<img width=48 src=\"%s\"></td>", + classid); - handle = em_format_find_handler(emf, "x-evolution/message/post-header"); - if (handle) - handle->handler ( - emf, stream, part, handle, cancellable, FALSE); + puri = em_format_puri_new ( + emf, sizeof (EMFormatPURI), part, classid); + puri->write_func = efh_write_image; + em_format_add_puri (emf, puri); - camel_stream_write_string ( - stream, EM_FORMAT_HTML_VPAD, cancellable, NULL); - em_format_part (emf, stream, part, cancellable); + g_object_unref (part); + g_free (face_header_value); + } - if (emf->message != (CamelMimeMessage *) part) - camel_stream_write_string ( - stream, "</blockquote>\n", cancellable, NULL); + if (have_icon && efh->show_icon) { + GtkIconInfo *icon_info; + const gchar *classid; + CamelMimePart *iconpart = NULL; + EMFormatPURI *puri; - camel_cipher_validity_free (emf->valid); + classid = "icon:///em-format-html/header/icon"; + g_string_append_printf ( + buffer, + "<td align=\"right\" valign=\"top\">" + "<img width=16 height=16 src=\"%s\"></td>", + classid); + icon_info = gtk_icon_theme_lookup_icon ( + gtk_icon_theme_get_default (), + "evolution", 16, GTK_ICON_LOOKUP_NO_SVG); + if (icon_info != NULL) { + iconpart = em_format_html_file_part ( + (EMFormatHTML *) emf, "image/png", + gtk_icon_info_get_filename (icon_info), + cancellable); + gtk_icon_info_free (icon_info); + } + if (iconpart) { + puri = em_format_puri_new ( + emf, sizeof (EMFormatPURI), iconpart, classid); + puri->write_func = efh_write_image; + em_format_add_puri (emf, puri); + g_object_unref (iconpart); + } + } - emf->valid = save; - emf->valid_parent = save_parent; + g_string_append (buffer, "</tr></table>"); } -void -em_format_html_format_cert_infos (GQueue *cert_infos, - GString *output_buffer) +gboolean +em_format_html_can_load_images (EMFormatHTML *efh) { - GQueue valid = G_QUEUE_INIT; - GList *head, *link; + g_return_val_if_fail (EM_IS_FORMAT_HTML (efh), FALSE); - g_return_if_fail (cert_infos != NULL); - g_return_if_fail (output_buffer != NULL); + return ((efh->priv->image_loading_policy == E_MAIL_IMAGE_LOADING_POLICY_ALWAYS) || + ((efh->priv->image_loading_policy == E_MAIL_IMAGE_LOADING_POLICY_SOMETIMES) && + efh->priv->can_load_images)); +} - head = g_queue_peek_head_link (cert_infos); +void +em_format_html_animation_extract_frame (const GByteArray *anim, + gchar **frame, + gsize *len) +{ + GdkPixbufLoader *loader; + GdkPixbufAnimation *animation; + GdkPixbuf *frame_buf; + + /* GIF89a (GIF image signature) */ + const gchar GIF_HEADER[] = { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61 }; + const gint GIF_HEADER_LEN = sizeof (GIF_HEADER); + + /* NETSCAPE2.0 (extension describing animated GIF, starts on 0x310) */ + const gchar GIF_APPEXT[] = { 0x4E, 0x45, 0x54, 0x53, 0x43, 0x41, + 0x50, 0x45, 0x32, 0x2E, 0x30 }; + const gint GIF_APPEXT_LEN = sizeof (GIF_APPEXT); + + /* Check if the image is an animated GIF. We don't care about any + * other animated formats (APNG or MNG) as WebKit does not support them + * and displays only the first frame. */ + if ((anim->len < 0x331) + || (memcmp (anim->data, GIF_HEADER, GIF_HEADER_LEN) != 0) + || (memcmp (&anim->data[0x310], GIF_APPEXT, GIF_APPEXT_LEN) != 0)) { + + *frame = g_memdup (anim->data, anim->len); + *len = anim->len; + return; + } - /* Make sure we have a valid CamelCipherCertInfo before - * appending anything to the output buffer, so we don't - * end up with "()". */ - for (link = head; link != NULL; link = g_list_next (link)) { - CamelCipherCertInfo *cinfo = link->data; + loader = gdk_pixbuf_loader_new (); + gdk_pixbuf_loader_write (loader, (guchar *) anim->data, anim->len, NULL); + gdk_pixbuf_loader_close (loader, NULL); + animation = gdk_pixbuf_loader_get_animation (loader); + if (!animation) { - if ((cinfo->name != NULL && *cinfo->name != '\0') || - (cinfo->email != NULL && *cinfo->email != '\0')) - g_queue_push_tail (&valid, cinfo); + *frame = g_memdup (anim->data, anim->len); + *len = anim->len; + g_object_unref (loader); + return; } - if (g_queue_is_empty (&valid)) + /* Extract first frame */ + frame_buf = gdk_pixbuf_animation_get_static_image (animation); + if (!frame_buf) { + *frame = g_memdup (anim->data, anim->len); + *len = anim->len; + g_object_unref (loader); + g_object_unref (animation); return; - - g_string_append (output_buffer, " ("); - - while (!g_queue_is_empty (&valid)) { - CamelCipherCertInfo *cinfo; - - cinfo = g_queue_pop_head (&valid); - - if (cinfo->name != NULL && *cinfo->name != '\0') { - g_string_append (output_buffer, cinfo->name); - - if (cinfo->email != NULL && *cinfo->email != '\0') { - g_string_append (output_buffer, " <"); - g_string_append (output_buffer, cinfo->email); - g_string_append (output_buffer, ">"); - } - - } else if (cinfo->email != NULL && *cinfo->email != '\0') { - g_string_append (output_buffer, cinfo->email); - } - - if (!g_queue_is_empty (&valid)) - g_string_append (output_buffer, ", "); } - g_string_append_c (output_buffer, ')'); -} + /* Unforunatelly, GdkPixbuf cannot save to GIF, but WebKit does not + * have any trouble displaying PNG image despite the part having + * image/gif mime-type */ + gdk_pixbuf_save_to_buffer (frame_buf, frame, len, "png", NULL, NULL); -/* unref returned pointer with g_object_unref(), if not NULL */ -CamelStream * -em_format_html_get_cached_image (EMFormatHTML *efh, - const gchar *image_uri) -{ - g_return_val_if_fail (efh != NULL, NULL); - g_return_val_if_fail (image_uri != NULL, NULL); - - if (!emfh_http_cache) - return NULL; - - return camel_data_cache_get ( - emfh_http_cache, EMFH_HTTP_CACHE_PATH, image_uri, NULL); + g_object_unref (loader); } - diff --git a/mail/em-format-html.h b/mail/em-format-html.h index bc6a171255..9749c37f31 100644 --- a/mail/em-format-html.h +++ b/mail/em-format-html.h @@ -30,7 +30,6 @@ #include <em-format/em-format.h> #include <misc/e-web-view.h> -#include <gtkhtml/gtkhtml-embedded.h> #include <libemail-engine/e-mail-enums.h> /* Standard GObject macros */ @@ -57,6 +56,7 @@ G_BEGIN_DECLS typedef struct _EMFormatHTML EMFormatHTML; typedef struct _EMFormatHTMLClass EMFormatHTMLClass; typedef struct _EMFormatHTMLPrivate EMFormatHTMLPrivate; +typedef struct _EMFormatWidgetPURI EMFormatWidgetPURI; enum _em_format_html_header_flags { EM_FORMAT_HTML_HEADER_TO = 1 << 0, @@ -65,16 +65,6 @@ enum _em_format_html_header_flags { }; typedef enum { - EM_FORMAT_HTML_STATE_NONE = 0, - EM_FORMAT_HTML_STATE_RENDERING -} EMFormatHTMLState; - -typedef enum { - EM_FORMAT_HTML_HEADERS_STATE_EXPANDED = 0, /* Default value */ - EM_FORMAT_HTML_HEADERS_STATE_COLLAPSED -} EMFormatHTMLHeadersState; - -typedef enum { EM_FORMAT_HTML_COLOR_BODY, /* header area background */ EM_FORMAT_HTML_COLOR_CITATION, /* citation font color */ EM_FORMAT_HTML_COLOR_CONTENT, /* message area background */ @@ -84,94 +74,13 @@ typedef enum { EM_FORMAT_HTML_NUM_COLOR_TYPES } EMFormatHTMLColorType; -/* A HTMLJob will be executed in another thread, in sequence. - * It's job is to write to its stream, close it if successful, - * then exit. */ - -typedef struct _EMFormatHTMLJob EMFormatHTMLJob; - -typedef void (*EMFormatHTMLJobCallback) (EMFormatHTMLJob *job, - GCancellable *cancellable); - -/** - * struct _EMFormatHTMLJob - A formatting job. - * - * @format: Set by allocation function. - * @stream: Free for use by caller. - * @puri_level: Set by allocation function. - * @base: Set by allocation function, used to save state. - * @callback: This callback will always be invoked, only once, even if the user - * cancelled the display. So the callback should free any extra data - * it allocated every time it is called. - * @u: Union data, free for caller to use. - * - * This object is used to queue a long-running-task which cannot be - * processed in the primary thread. When its turn comes, the job will - * be de-queued and the @callback invoked to perform its processing, - * restoring various state to match the original state. This is used - * for image loading and other internal tasks. - * - * This object is struct-subclassable. Only em_format_html_job_new() - * may be used to allocate these. - **/ -struct _EMFormatHTMLJob { - EMFormatHTML *format; - CamelStream *stream; - - /* We need to track the state of the visibility tree at - * the point this uri was generated */ - GNode *puri_level; - CamelURL *base; - - EMFormatHTMLJobCallback callback; - union { - gchar *uri; - CamelMedium *msg; - EMFormatPURI *puri; - GNode *puri_level; - gpointer data; - } u; -}; - -/* Pending object (classid: url) */ -typedef struct _EMFormatHTMLPObject EMFormatHTMLPObject; - -typedef gboolean - (*EMFormatHTMLPObjectFunc) (EMFormatHTML *md, - GtkHTMLEmbedded *eb, - EMFormatHTMLPObject *pobject); - -/** - * struct _EMFormatHTMLPObject - Pending object. - * - * @free: Invoked when the object is no longer needed. - * @format: The parent formatter. - * @classid: The assigned class id as passed to add_pobject(). - * @func: Callback function. - * @part: The part as passed to add_pobject(). - * - * This structure is used to track OBJECT tags which have been - * inserted into the HTML stream. When GtkHTML requests them the - * @func will be invoked to create the embedded widget. - * - * This object is struct-subclassable. Only - * em_format_html_add_pobject() may be used to allocate these. - **/ -struct _EMFormatHTMLPObject { - void (*free)(EMFormatHTMLPObject *); - EMFormatHTML *format; - - gchar *classid; - - EMFormatHTMLPObjectFunc func; - CamelMimePart *part; -}; - #define EM_FORMAT_HTML_HEADER_NOCOLUMNS (EM_FORMAT_HEADER_LAST) /* header already in html format */ #define EM_FORMAT_HTML_HEADER_HTML (EM_FORMAT_HEADER_LAST<<1) #define EM_FORMAT_HTML_HEADER_NODEC (EM_FORMAT_HEADER_LAST<<2) +#define EM_FORMAT_HTML_HEADER_NOLINKS (EM_FORMAT_HEADER_LAST<<3) +#define EM_FORMAT_HTML_HEADER_HIDDEN (EM_FORMAT_HEADER_LAST<<4) #define EM_FORMAT_HTML_HEADER_LAST (EM_FORMAT_HEADER_LAST<<8) @@ -197,14 +106,13 @@ struct _EMFormatHTMLPObject { * @load_http:2: * @load_http_now:1: * @mark_citations:1: - * @simple_headers:1: * @hide_headers:1: * @show_icon:1: * * Most of these fields are private or read-only. * * The base HTML formatter object. This object drives HTML generation - * into a GtkHTML parser. It also handles text to HTML conversion, + * into a WebKit parser. It also handles text to HTML conversion, * multipart/related objects and inline images. **/ struct _EMFormatHTML { @@ -216,12 +124,9 @@ struct _EMFormatHTML { GSList *headers; guint32 text_html_flags; /* default flags for text to html conversion */ - guint simple_headers:1; /* simple header format, no box/table */ guint hide_headers:1; /* no headers at all */ guint show_icon:1; /* show an icon when the sender used Evo */ guint32 header_wrap_flags; - - EMFormatHTMLState state; /* actual state of the object */ }; struct _EMFormatHTMLClass { @@ -231,8 +136,6 @@ struct _EMFormatHTMLClass { }; GType em_format_html_get_type (void); -EWebView * em_format_html_get_web_view (EMFormatHTML *efh); -void em_format_html_load_images (EMFormatHTML *efh); void em_format_html_get_color (EMFormatHTML *efh, EMFormatHTMLColorType type, GdkColor *color); @@ -260,65 +163,61 @@ gboolean em_format_html_get_show_sender_photo void em_format_html_set_show_sender_photo (EMFormatHTML *efh, gboolean show_sender_photo); - -/* retrieves a pseudo-part icon wrapper for a file */ -CamelMimePart * em_format_html_file_part (EMFormatHTML *efh, - const gchar *mime_type, - const gchar *filename, - GCancellable *cancellable); - -/* for implementers */ -EMFormatHTMLPObject * - em_format_html_add_pobject (EMFormatHTML *efh, - gsize size, - const gchar *classid, - CamelMimePart *part, - EMFormatHTMLPObjectFunc func); -EMFormatHTMLPObject * - em_format_html_find_pobject (EMFormatHTML *efh, - const gchar *classid); -EMFormatHTMLPObject * - em_format_html_find_pobject_func - (EMFormatHTML *efh, - CamelMimePart *part, - EMFormatHTMLPObjectFunc func); -void em_format_html_remove_pobject (EMFormatHTML *efh, - EMFormatHTMLPObject *pobject); -void em_format_html_clear_pobject (EMFormatHTML *efh); -EMFormatHTMLJob * - em_format_html_job_new (EMFormatHTML *efh, - EMFormatHTMLJobCallback callback, - gpointer data); -void em_format_html_job_queue (EMFormatHTML *efh, - EMFormatHTMLJob *job); +gboolean em_format_html_get_animate_images + (EMFormatHTML *efh); +void em_format_html_set_animate_images + (EMFormatHTML *efh, + gboolean animate_images); void em_format_html_clone_sync (CamelFolder *folder, const gchar *message_uid, CamelMimeMessage *message, EMFormatHTML *efh, EMFormat *source); - gboolean em_format_html_get_show_real_date (EMFormatHTML *efh); void em_format_html_set_show_real_date (EMFormatHTML *efh, gboolean show_real_date); -EMFormatHTMLHeadersState - em_format_html_get_headers_state - (EMFormatHTML *efh); -void em_format_html_set_headers_state - (EMFormatHTML *efh, - EMFormatHTMLHeadersState state); -gboolean em_format_html_get_headers_collapsable - (EMFormatHTML *efh); -void em_format_html_set_headers_collapsable - (EMFormatHTML *efh, - gboolean collapsable); + +/* retrieves a pseudo-part icon wrapper for a file */ +CamelMimePart * em_format_html_file_part (EMFormatHTML *efh, + const gchar *mime_type, + const gchar *filename, + GCancellable *cancellable); + void em_format_html_format_cert_infos (GQueue *cert_infos, GString *output_buffer); -CamelStream * em_format_html_get_cached_image (EMFormatHTML *efh, - const gchar *image_uri); +void em_format_html_format_message (EMFormatHTML *efh, + CamelStream *stream, + GCancellable *cancellable); + +void em_format_html_format_message_part + (EMFormatHTML *efh, + const gchar *part_id, + CamelStream *stream, + GCancellable *cancellable); + +void em_format_html_format_headers (EMFormatHTML *efh, + CamelStream *stream, + CamelMedium *part, + gboolean all_headers, + GCancellable *cancellable); +void em_format_html_format_header (EMFormat *emf, + GString *buffer, + CamelMedium *part, + struct _camel_header_raw *header, + guint32 flags, + const gchar *charset); + +gboolean em_format_html_can_load_images (EMFormatHTML *efh); + +void em_format_html_animation_extract_frame + (const GByteArray *anim, + gchar **frame, + gsize *len); + G_END_DECLS #endif /* EM_FORMAT_HTML_H */ diff --git a/mail/em-html-stream.c b/mail/em-html-stream.c deleted file mode 100644 index a633946541..0000000000 --- a/mail/em-html-stream.c +++ /dev/null @@ -1,182 +0,0 @@ -/* - * - * 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: - * Jeffrey Stedfast <fejj@ximian.com> - * Michael Zucchi <notzed@ximian.com> - * - * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) - * - */ - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include <stdio.h> -#include <gtk/gtk.h> -#include <glib/gi18n-lib.h> -#include "em-html-stream.h" - -#define d(x) - -G_DEFINE_TYPE (EMHTMLStream, em_html_stream, EM_TYPE_SYNC_STREAM) - -static void -html_stream_cleanup (EMHTMLStream *emhs) -{ - if (emhs->sync.cancel && emhs->html_stream) - gtk_html_stream_close ( - emhs->html_stream, GTK_HTML_STREAM_ERROR); - - emhs->html_stream = NULL; - emhs->sync.cancel = TRUE; - g_signal_handler_disconnect (emhs->html, emhs->destroy_id); - g_object_unref (emhs->html); - emhs->html = NULL; -} - -static void -html_stream_gtkhtml_destroy (GtkHTML *html, - EMHTMLStream *emhs) -{ - emhs->sync.cancel = TRUE; - html_stream_cleanup (emhs); -} - -static void -html_stream_dispose (GObject *object) -{ - EMHTMLStream *emhs = EM_HTML_STREAM (object); - - if (emhs->html_stream) { - /* set 'in finalize' flag */ - camel_stream_close (CAMEL_STREAM (emhs), NULL, NULL); - } -} - -static gssize -html_stream_sync_write (CamelStream *stream, - const gchar *buffer, - gsize n, - GError **error) -{ - EMHTMLStream *emhs = EM_HTML_STREAM (stream); - - if (emhs->html == NULL) { - g_set_error ( - error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, - _("No HTML stream available")); - return -1; - } - - if (emhs->html_stream == NULL) - emhs->html_stream = gtk_html_begin_full ( - emhs->html, NULL, NULL, emhs->flags); - - gtk_html_stream_write (emhs->html_stream, buffer, n); - - return (gssize) n; -} - -static gint -html_stream_sync_flush (CamelStream *stream, - GError **error) -{ - EMHTMLStream *emhs = (EMHTMLStream *) stream; - - if (emhs->html_stream == NULL) { - g_set_error ( - error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, - _("No HTML stream available")); - return -1; - } - - gtk_html_flush (emhs->html); - - return 0; -} - -static gint -html_stream_sync_close (CamelStream *stream, - GError **error) -{ - EMHTMLStream *emhs = (EMHTMLStream *) stream; - - if (emhs->html_stream == NULL) { - g_set_error ( - error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, - _("No HTML stream available")); - return -1; - } - - gtk_html_stream_close (emhs->html_stream, GTK_HTML_STREAM_OK); - html_stream_cleanup (emhs); - - return 0; -} - -static void -em_html_stream_class_init (EMHTMLStreamClass *class) -{ - GObjectClass *object_class; - EMSyncStreamClass *sync_stream_class; - - object_class = G_OBJECT_CLASS (class); - object_class->dispose = html_stream_dispose; - - sync_stream_class = EM_SYNC_STREAM_CLASS (class); - sync_stream_class->sync_write = html_stream_sync_write; - sync_stream_class->sync_flush = html_stream_sync_flush; - sync_stream_class->sync_close = html_stream_sync_close; -} - -static void -em_html_stream_init (EMHTMLStream *emhs) -{ -} - -/* TODO: Could pass NULL for html_stream, and do a gtk_html_begin - * on first data -> less flashing */ -CamelStream * -em_html_stream_new (GtkHTML *html, - GtkHTMLStream *html_stream) -{ - EMHTMLStream *new; - - g_return_val_if_fail (GTK_IS_HTML (html), NULL); - - new = g_object_new (EM_TYPE_HTML_STREAM, NULL); - new->html_stream = html_stream; - new->html = g_object_ref (html); - new->flags = 0; - new->destroy_id = g_signal_connect ( - html, "destroy", - G_CALLBACK (html_stream_gtkhtml_destroy), new); - - em_sync_stream_set_buffer_size (&new->sync, 8192); - - return CAMEL_STREAM (new); -} - -void -em_html_stream_set_flags (EMHTMLStream *emhs, - GtkHTMLBeginFlags flags) -{ - g_return_if_fail (EM_IS_HTML_STREAM (emhs)); - - emhs->flags = flags; -} diff --git a/mail/em-html-stream.h b/mail/em-html-stream.h deleted file mode 100644 index 24d32f76d8..0000000000 --- a/mail/em-html-stream.h +++ /dev/null @@ -1,77 +0,0 @@ -/* - * - * 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: - * Jeffrey Stedfast <fejj@ximian.com> - * - * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) - * - */ - -#ifndef EM_HTML_STREAM_H -#define EM_HTML_STREAM_H - -#include <gtkhtml/gtkhtml.h> -#include <gtkhtml/gtkhtml-stream.h> -#include <mail/em-sync-stream.h> - -/* Standard GObject macros */ -#define EM_TYPE_HTML_STREAM \ - (em_html_stream_get_type ()) -#define EM_HTML_STREAM(obj) \ - (G_TYPE_CHECK_INSTANCE_CAST \ - ((obj), EM_TYPE_HTML_STREAM, EMHTMLStream)) -#define EM_HTML_STREAM_CLASS(cls) \ - (G_TYPE_CHECK_CLASS_CAST \ - ((cls), EM_TYPE_HTML_STREAM, EMHTMLStreamClass)) -#define EM_IS_HTML_STREAM(obj) \ - (G_TYPE_CHECK_INSTANCE_TYPE \ - ((obj), EM_TYPE_HTML_STREAM)) -#define EM_IS_HTML_STREAM_CLASS(cls) \ - (G_TYPE_CHECK_CLASS_TYPE \ - ((cls), EM_TYPE_HTML_STREAM)) -#define EM_HTML_STREAM_GET_CLASS(obj) \ - (G_TYPE_INSTANCE_GET_CLASS \ - ((obj), EM_TYPE_HTML_STREAM, EMHTMLStreamClass)) - -G_BEGIN_DECLS - -typedef struct _EMHTMLStream EMHTMLStream; -typedef struct _EMHTMLStreamClass EMHTMLStreamClass; - -struct _EMHTMLStream { - EMSyncStream sync; - - guint destroy_id; - GtkHTML *html; - GtkHTMLStream *html_stream; - GtkHTMLBeginFlags flags; -}; - -struct _EMHTMLStreamClass { - EMSyncStreamClass parent_class; - -}; - -GType em_html_stream_get_type (void); -CamelStream * em_html_stream_new (GtkHTML *html, - GtkHTMLStream *html_stream); -void em_html_stream_set_flags (EMHTMLStream *emhs, - GtkHTMLBeginFlags flags); - -G_END_DECLS - -#endif /* EM_HTML_STREAM_H */ diff --git a/mail/em-utils.c b/mail/em-utils.c index 4d74b80a13..a9b57125a2 100644 --- a/mail/em-utils.c +++ b/mail/em-utils.c @@ -72,9 +72,11 @@ #include "e-mail-tag-editor.h" #include "em-composer-utils.h" -#include "em-format-quote.h" +#include "em-format-html-display.h" #include "em-format-html-print.h" #include "em-utils.h" +#include "e-mail-printer.h" +#include "em-format/em-format-quote.h" /* XXX This is a dirty hack on a dirty hack. We really need * to rework or get rid of the functions that use this. */ @@ -380,7 +382,7 @@ em_utils_flag_for_followup (EMailReader *reader, EMailBackend *backend; EShellSettings *shell_settings; EShellBackend *shell_backend; - EMFormatHTML *formatter; + EMailDisplay *display; GtkWidget *editor; GtkWindow *window; CamelTag *tags; @@ -470,8 +472,8 @@ em_utils_flag_for_followup (EMailReader *reader, camel_folder_thaw (folder); camel_tag_list_free (&tags); - formatter = e_mail_reader_get_formatter (reader); - em_format_queue_redraw (EM_FORMAT (formatter)); + display = e_mail_reader_get_mail_display (reader); + e_mail_display_reload (display); exit: /* XXX We shouldn't be freeing this. */ @@ -616,26 +618,44 @@ em_utils_write_messages_to_stream (CamelFolder *folder, return res; } +static void +do_print_msg_to_file (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + + EMFormatHTML *efh = EM_FORMAT_HTML (source); + gchar *filename = user_data; + + EMailPrinter *printer; + + printer = e_mail_printer_new (efh); + e_mail_printer_set_export_filename (printer, filename); + g_signal_connect_swapped (printer, "done", + G_CALLBACK (g_object_unref), printer); + + e_mail_printer_print (printer, TRUE, NULL); + + g_object_unref (efh); +} + static gboolean em_utils_print_messages_to_file (CamelFolder *folder, const gchar *uid, const gchar *filename) { - EMFormatHTMLPrint *efhp; + EMFormatHTMLDisplay *efhd; CamelMimeMessage *message; message = camel_folder_get_message_sync (folder, uid, NULL, NULL); if (message == NULL) return FALSE; - efhp = em_format_html_print_new (NULL, GTK_PRINT_OPERATION_ACTION_EXPORT); - efhp->export_filename = g_strdup (filename); - efhp->async = FALSE; + efhd = em_format_html_display_new (); + ((EMFormat *) efhd)->message_uid = g_strdup (uid); - em_format_html_print_message (efhp, message, folder, uid); - - g_object_unref (efhp); - g_object_unref (message); + em_format_parse_async ((EMFormat *) efhd, message, folder, NULL, + (GAsyncReadyCallback) do_print_msg_to_file, g_strdup (filename)); return TRUE; } @@ -1173,7 +1193,7 @@ em_utils_message_to_html (CamelMimeMessage *message, camel_stream_mem_set_byte_array (CAMEL_STREAM_MEM (mem), buf); emfq = em_format_quote_new (credits, mem, flags); - ((EMFormat *) emfq)->composer = TRUE; + em_format_set_composer ((EMFormat *) emfq, TRUE); if (!source) { GSettings *settings; @@ -1189,10 +1209,30 @@ em_utils_message_to_html (CamelMimeMessage *message, } /* FIXME Not passing a GCancellable here. */ - em_format_format_clone ( - EM_FORMAT (emfq), NULL, NULL, message, source, NULL); - if (validity_found) - *validity_found = ((EMFormat *)emfq)->validity_found; + em_format_parse (EM_FORMAT (emfq), message, NULL, NULL); + + if (validity_found) { + GList *iter; + EMFormat *emf = (EMFormat *) emfq; + + if (validity_found) + *validity_found = 0; + + /* Return all found validities */ + for (iter = emf->mail_part_list; iter; iter = iter->next) { + + EMFormatPURI *puri = iter->data; + if (!puri) + continue; + + if (*validity_found && puri->validity_type) + *validity_found |= puri->validity_type; + } + + } + + em_format_quote_write (emfq, mem, NULL); + g_object_unref (emfq); if (append && *append) diff --git a/mail/mail.error.xml b/mail/mail.error.xml index 2a516d9bfa..b5a714ff18 100644 --- a/mail/mail.error.xml +++ b/mail/mail.error.xml @@ -520,5 +520,10 @@ An mbox account will be created to preserve the old mbox folders. You can delete <_secondary xml:space="preserve">The attachment named {0} is a hidden file and may contain sensitive data. Please review it before sending.</_secondary> </error> + <error id="printing-failed" type="error"> + <_primary>Printing failed.</_primary> + <_secondary>The printer replied "{0}".</_secondary> + </error> + </error-list> diff --git a/modules/Makefile.am b/modules/Makefile.am index ee3cdfdb1a..671215b952 100644 --- a/modules/Makefile.am +++ b/modules/Makefile.am @@ -35,6 +35,7 @@ SUBDIRS = \ plugin-manager \ spamassassin \ startup-wizard \ + web-inspector \ $(MONO_DIR) \ $(PYTHON_DIR) \ $(ONLINE_ACCOUNTS_DIR) \ diff --git a/modules/addressbook/e-book-shell-content.c b/modules/addressbook/e-book-shell-content.c index 0183512f5b..f3457e8999 100644 --- a/modules/addressbook/e-book-shell-content.c +++ b/modules/addressbook/e-book-shell-content.c @@ -286,20 +286,11 @@ book_shell_content_constructed (GObject *object) EAB_CONTACT_DISPLAY (widget), EAB_CONTACT_DISPLAY_RENDER_NORMAL); - eab_contact_display_set_orientation ( - EAB_CONTACT_DISPLAY (widget), - priv->orientation); - eab_contact_display_set_show_maps ( EAB_CONTACT_DISPLAY (widget), priv->preview_show_maps); g_object_bind_property ( - object, "orientation", - widget, "orientation", - G_BINDING_SYNC_CREATE); - - g_object_bind_property ( object, "preview-show-maps", widget, "show-maps", G_BINDING_SYNC_CREATE); diff --git a/modules/mail/e-mail-config-format-html.c b/modules/mail/e-mail-config-format-html.c index 31ca88b744..dad5f9a518 100644 --- a/modules/mail/e-mail-config-format-html.c +++ b/modules/mail/e-mail-config-format-html.c @@ -76,6 +76,11 @@ mail_config_format_html_constructed (GObject *object) extensible, "show-real-date", G_BINDING_SYNC_CREATE); + g_object_bind_property ( + shell_settings, "mail-show-animated-images", + extensible, "animate-images", + G_BINDING_SYNC_CREATE); + /* Chain up to parent's constructed() method. */ G_OBJECT_CLASS (parent_class)->constructed (object); } diff --git a/modules/mail/e-mail-config-web-view.c b/modules/mail/e-mail-config-web-view.c index 80f3f65c0f..813256efd1 100644 --- a/modules/mail/e-mail-config-web-view.c +++ b/modules/mail/e-mail-config-web-view.c @@ -151,12 +151,6 @@ mail_config_web_view_realize (GtkWidget *widget, { g_object_bind_property ( extension->shell_settings, - "mail-show-animated-images", - widget, "animate", - G_BINDING_SYNC_CREATE); - - g_object_bind_property ( - extension->shell_settings, "composer-inline-spelling", widget, "inline-spelling", G_BINDING_SYNC_CREATE); diff --git a/modules/mail/e-mail-shell-backend.c b/modules/mail/e-mail-shell-backend.c index 25902f34df..a30c0240de 100644 --- a/modules/mail/e-mail-shell-backend.c +++ b/modules/mail/e-mail-shell-backend.c @@ -835,37 +835,70 @@ e_mail_labels_get_filter_options (void) return g_slist_reverse (list); } +static void +message_parsed_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + EMFormatHTML *formatter = EM_FORMAT_HTML (source_object); + GObject *preview = user_data; + EMailDisplay *display; + + display = g_object_get_data (preview, "mbox-imp-display"); + e_mail_display_set_formatter (display, formatter); + e_mail_display_load (display, EM_FORMAT (formatter)->uri_base); +} + /* utility functions for mbox importer */ static void mbox_create_preview_cb (GObject *preview, GtkWidget **preview_widget) { - EMFormatHTMLDisplay *format; - EWebView *web_view; + EMailDisplay *display; g_return_if_fail (preview != NULL); g_return_if_fail (preview_widget != NULL); - format = em_format_html_display_new (); - g_object_set_data_full ( - preview, "mbox-imp-formatter", format, g_object_unref); - web_view = em_format_html_get_web_view (EM_FORMAT_HTML (format)); + display = g_object_new (E_TYPE_MAIL_DISPLAY, NULL); + g_object_set_data_full (preview, "mbox-imp-display", + g_object_ref (display), g_object_unref); - *preview_widget = GTK_WIDGET (web_view); + *preview_widget = GTK_WIDGET (display); } static void mbox_fill_preview_cb (GObject *preview, CamelMimeMessage *msg) { - EMFormatHTMLDisplay *format; + EMailDisplay *display; + EMFormat *formatter; + GHashTable *formatters; + SoupSession *session; + gchar *mail_uri; g_return_if_fail (preview != NULL); g_return_if_fail (msg != NULL); - format = g_object_get_data (preview, "mbox-imp-formatter"); - g_return_if_fail (format != NULL); + display = g_object_get_data (preview, "mbox-imp-display"); + g_return_if_fail (display != NULL); + + session = webkit_get_default_session (); + formatters = g_object_get_data (G_OBJECT (session), "formatters"); + if (!formatters) { + formatters = g_hash_table_new_full (g_str_hash, g_str_equal, + (GDestroyNotify) g_free, NULL); + g_object_set_data (G_OBJECT (session), "formatters", formatters); + } + + mail_uri = em_format_build_mail_uri (NULL, msg->message_id, NULL, NULL); + + formatter = EM_FORMAT (em_format_html_display_new ()); + formatter->message_uid = g_strdup (msg->message_id); + formatter->uri_base = g_strdup (mail_uri); + + /* Don't free the mail_uri!! */ + g_hash_table_insert (formatters, mail_uri, formatter); - /* FIXME Not passing a GCancellable here. */ - em_format_format (EM_FORMAT (format), NULL, NULL, msg, NULL); + em_format_parse_async (formatter, msg, NULL, NULL, + message_parsed_cb, preview); } diff --git a/modules/mail/e-mail-shell-content.c b/modules/mail/e-mail-shell-content.c index 5bb60e3784..1bf6d4e29a 100644 --- a/modules/mail/e-mail-shell-content.c +++ b/modules/mail/e-mail-shell-content.c @@ -307,8 +307,8 @@ mail_shell_content_get_backend (EMailReader *reader) return e_mail_reader_get_backend (reader); } -static EMFormatHTML * -mail_shell_content_get_formatter (EMailReader *reader) +static EMailDisplay * +mail_shell_content_get_mail_display (EMailReader *reader) { EMailShellContent *mail_shell_content; @@ -317,8 +317,7 @@ mail_shell_content_get_formatter (EMailReader *reader) /* Forward this to our internal EMailView, which * also implements the EMailReader interface. */ reader = E_MAIL_READER (mail_shell_content->priv->mail_view); - - return e_mail_reader_get_formatter (reader); + return e_mail_reader_get_mail_display (reader); } static gboolean @@ -464,7 +463,7 @@ e_mail_shell_content_reader_init (EMailReaderInterface *interface) { interface->get_action_group = mail_shell_content_get_action_group; interface->get_backend = mail_shell_content_get_backend; - interface->get_formatter = mail_shell_content_get_formatter; + interface->get_mail_display = mail_shell_content_get_mail_display; interface->get_hide_deleted = mail_shell_content_get_hide_deleted; interface->get_message_list = mail_shell_content_get_message_list; interface->get_popup_menu = mail_shell_content_get_popup_menu; diff --git a/modules/mail/e-mail-shell-view-actions.c b/modules/mail/e-mail-shell-view-actions.c index b39d22d13d..5487920c74 100644 --- a/modules/mail/e-mail-shell-view-actions.c +++ b/modules/mail/e-mail-shell-view-actions.c @@ -891,14 +891,16 @@ action_mail_smart_backward_cb (GtkAction *action, EMailShellContent *mail_shell_content; EMailShellSidebar *mail_shell_sidebar; EMFolderTree *folder_tree; - EMFormatHTML *formatter; EMailReader *reader; EMailView *mail_view; GtkWidget *message_list; GtkToggleAction *toggle_action; - EWebView *web_view; + GtkWidget *window; + GtkAdjustment *adj; + EMailDisplay *display; gboolean caret_mode; gboolean magic_spacebar; + gdouble value; /* This implements the so-called "Magic Backspace". */ @@ -914,7 +916,7 @@ action_mail_smart_backward_cb (GtkAction *action, folder_tree = e_mail_shell_sidebar_get_folder_tree (mail_shell_sidebar); reader = E_MAIL_READER (mail_view); - formatter = e_mail_reader_get_formatter (reader); + display = e_mail_reader_get_mail_display (reader); message_list = e_mail_reader_get_message_list (reader); magic_spacebar = e_shell_settings_get_boolean ( @@ -923,32 +925,43 @@ action_mail_smart_backward_cb (GtkAction *action, toggle_action = GTK_TOGGLE_ACTION (ACTION (MAIL_CARET_MODE)); caret_mode = gtk_toggle_action_get_active (toggle_action); - web_view = em_format_html_get_web_view (formatter); - - if (e_web_view_scroll_backward (web_view)) + window = gtk_widget_get_parent (GTK_WIDGET (display)); + if (!GTK_IS_SCROLLED_WINDOW (window)) return; - if (caret_mode || !magic_spacebar) - return; + adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (window)); + value = gtk_adjustment_get_value (adj); + if (value == 0) { - /* XXX Are two separate calls really necessary? */ + if (caret_mode || !magic_spacebar) + return; - if (message_list_select ( - MESSAGE_LIST (message_list), - MESSAGE_LIST_SELECT_PREVIOUS, - 0, CAMEL_MESSAGE_SEEN)) - return; + /* XXX Are two separate calls really necessary? */ - if (message_list_select ( - MESSAGE_LIST (message_list), - MESSAGE_LIST_SELECT_PREVIOUS | - MESSAGE_LIST_SELECT_WRAP, 0, - CAMEL_MESSAGE_SEEN)) - return; + if (message_list_select ( + MESSAGE_LIST (message_list), + MESSAGE_LIST_SELECT_PREVIOUS, + 0, CAMEL_MESSAGE_SEEN)) + return; + + if (message_list_select ( + MESSAGE_LIST (message_list), + MESSAGE_LIST_SELECT_PREVIOUS | + MESSAGE_LIST_SELECT_WRAP, + 0, CAMEL_MESSAGE_SEEN)) + return; + + em_folder_tree_select_next_path (folder_tree, TRUE); - em_folder_tree_select_prev_path (folder_tree, TRUE); + gtk_widget_grab_focus (message_list); - gtk_widget_grab_focus (message_list); + } else { + + gtk_adjustment_set_value (adj, + value - gtk_adjustment_get_page_increment (adj)); + + return; + } } static void @@ -962,14 +975,17 @@ action_mail_smart_forward_cb (GtkAction *action, EMailShellContent *mail_shell_content; EMailShellSidebar *mail_shell_sidebar; EMFolderTree *folder_tree; - EMFormatHTML *formatter; EMailReader *reader; EMailView *mail_view; GtkWidget *message_list; + GtkWidget *window; + GtkAdjustment *adj; GtkToggleAction *toggle_action; - EWebView *web_view; + EMailDisplay *display; gboolean caret_mode; gboolean magic_spacebar; + gdouble value; + gdouble upper; /* This implements the so-called "Magic Spacebar". */ @@ -985,7 +1001,7 @@ action_mail_smart_forward_cb (GtkAction *action, folder_tree = e_mail_shell_sidebar_get_folder_tree (mail_shell_sidebar); reader = E_MAIL_READER (mail_view); - formatter = e_mail_reader_get_formatter (reader); + display = e_mail_reader_get_mail_display (reader); message_list = e_mail_reader_get_message_list (reader); magic_spacebar = e_shell_settings_get_boolean ( @@ -994,32 +1010,44 @@ action_mail_smart_forward_cb (GtkAction *action, toggle_action = GTK_TOGGLE_ACTION (ACTION (MAIL_CARET_MODE)); caret_mode = gtk_toggle_action_get_active (toggle_action); - web_view = em_format_html_get_web_view (formatter); - - if (e_web_view_scroll_forward (web_view)) + window = gtk_widget_get_parent (GTK_WIDGET (display)); + if (!GTK_IS_SCROLLED_WINDOW (window)) return; - if (caret_mode || !magic_spacebar) - return; + adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (window)); + value = gtk_adjustment_get_value (adj); + upper = gtk_adjustment_get_upper (adj); + if (value + gtk_adjustment_get_page_size (adj) >= upper) { - /* XXX Are two separate calls really necessary? */ + if (caret_mode || !magic_spacebar) + return; - if (message_list_select ( - MESSAGE_LIST (message_list), - MESSAGE_LIST_SELECT_NEXT, - 0, CAMEL_MESSAGE_SEEN)) - return; + /* XXX Are two separate calls really necessary? */ - if (message_list_select ( - MESSAGE_LIST (message_list), - MESSAGE_LIST_SELECT_NEXT | - MESSAGE_LIST_SELECT_WRAP, - 0, CAMEL_MESSAGE_SEEN)) - return; + if (message_list_select ( + MESSAGE_LIST (message_list), + MESSAGE_LIST_SELECT_NEXT, + 0, CAMEL_MESSAGE_SEEN)) + return; + + if (message_list_select ( + MESSAGE_LIST (message_list), + MESSAGE_LIST_SELECT_NEXT | + MESSAGE_LIST_SELECT_WRAP, + 0, CAMEL_MESSAGE_SEEN)) + return; + + em_folder_tree_select_next_path (folder_tree, TRUE); - em_folder_tree_select_next_path (folder_tree, TRUE); + gtk_widget_grab_focus (message_list); - gtk_widget_grab_focus (message_list); + } else { + + gtk_adjustment_set_value (adj, + value + gtk_adjustment_get_page_increment (adj)); + + return; + } } static void diff --git a/modules/mail/e-mail-shell-view-private.c b/modules/mail/e-mail-shell-view-private.c index 8838f1a1ae..9a58f9d2b7 100644 --- a/modules/mail/e-mail-shell-view-private.c +++ b/modules/mail/e-mail-shell-view-private.c @@ -331,11 +331,10 @@ mail_shell_view_popup_event_cb (EMailShellView *mail_shell_view, const gchar *uri) { EMailShellContent *mail_shell_content; - EMFormatHTML *formatter; + EMailDisplay *display; EShellView *shell_view; EMailReader *reader; EMailView *mail_view; - EWebView *web_view; GtkMenu *menu; if (uri != NULL) @@ -345,10 +344,9 @@ mail_shell_view_popup_event_cb (EMailShellView *mail_shell_view, mail_view = e_mail_shell_content_get_mail_view (mail_shell_content); reader = E_MAIL_READER (mail_view); - formatter = e_mail_reader_get_formatter (reader); - web_view = em_format_html_get_web_view (formatter); + display = e_mail_reader_get_mail_display (reader); - if (e_web_view_get_cursor_image (web_view) != NULL) + if (e_web_view_get_cursor_image (E_WEB_VIEW (display)) != NULL) return FALSE; menu = e_mail_reader_get_popup_menu (reader); @@ -368,76 +366,19 @@ mail_shell_view_popup_event_cb (EMailShellView *mail_shell_view, } static void -mail_shell_view_scroll_cb (EMailShellView *mail_shell_view, - GtkOrientation orientation, - GtkScrollType scroll_type, - gfloat position, - GtkHTML *html) -{ - EShell *shell; - EShellView *shell_view; - EShellWindow *shell_window; - EShellSettings *shell_settings; - EMailShellContent *mail_shell_content; - EMailReader *reader; - EMailView *mail_view; - EWebView *web_view; - GtkWidget *message_list; - gboolean magic_spacebar; - - web_view = E_WEB_VIEW (html); - - if (html->binding_handled || e_web_view_get_caret_mode (web_view)) - return; - - if (orientation != GTK_ORIENTATION_VERTICAL) - return; - - shell_view = E_SHELL_VIEW (mail_shell_view); - shell_window = e_shell_view_get_shell_window (shell_view); - shell = e_shell_window_get_shell (shell_window); - shell_settings = e_shell_get_shell_settings (shell); - - magic_spacebar = e_shell_settings_get_boolean ( - shell_settings, "mail-magic-spacebar"); - - if (!magic_spacebar) - return; - - mail_shell_content = mail_shell_view->priv->mail_shell_content; - mail_view = e_mail_shell_content_get_mail_view (mail_shell_content); - - reader = E_MAIL_READER (mail_view); - message_list = e_mail_reader_get_message_list (reader); - - if (scroll_type == GTK_SCROLL_PAGE_FORWARD) - message_list_select ( - MESSAGE_LIST (message_list), - MESSAGE_LIST_SELECT_NEXT, - 0, CAMEL_MESSAGE_SEEN); - else - message_list_select ( - MESSAGE_LIST (message_list), - MESSAGE_LIST_SELECT_PREVIOUS, - 0, CAMEL_MESSAGE_SEEN); -} - -static void mail_shell_view_reader_changed_cb (EMailShellView *mail_shell_view, EMailReader *reader) { GtkWidget *message_list; - EMFormatHTML *formatter; - EWebView *web_view; + EMailDisplay *display; EShellView *shell_view; EShellTaskbar *shell_taskbar; shell_view = E_SHELL_VIEW (mail_shell_view); shell_taskbar = e_shell_view_get_shell_taskbar (shell_view); - formatter = e_mail_reader_get_formatter (reader); + display = e_mail_reader_get_mail_display (reader); message_list = e_mail_reader_get_message_list (reader); - web_view = em_format_html_get_web_view (formatter); e_shell_view_update_actions (E_SHELL_VIEW (mail_shell_view)); e_mail_shell_view_update_sidebar (mail_shell_view); @@ -464,23 +405,17 @@ mail_shell_view_reader_changed_cb (EMailShellView *mail_shell_view, mail_shell_view, G_CONNECT_SWAPPED); g_signal_connect_object ( - web_view, "key-press-event", + display, "key-press-event", G_CALLBACK (mail_shell_view_key_press_event_cb), mail_shell_view, G_CONNECT_SWAPPED); g_signal_connect_object ( - web_view, "popup-event", + display, "popup-event", G_CALLBACK (mail_shell_view_popup_event_cb), mail_shell_view, G_CONNECT_SWAPPED); g_signal_connect_object ( - web_view, "scroll", - G_CALLBACK (mail_shell_view_scroll_cb), - mail_shell_view, - G_CONNECT_AFTER | G_CONNECT_SWAPPED); - - g_signal_connect_object ( - web_view, "status-message", + display, "status-message", G_CALLBACK (e_shell_taskbar_set_message), shell_taskbar, G_CONNECT_SWAPPED); } @@ -634,7 +569,6 @@ e_mail_shell_view_private_constructed (EMailShellView *mail_shell_view) EShellTaskbar *shell_taskbar; EShellWindow *shell_window; EShellSearchbar *searchbar; - EMFormatHTML *formatter; EMFolderTree *folder_tree; EActionComboBox *combo_box; ERuleContext *context; @@ -647,7 +581,7 @@ e_mail_shell_view_private_constructed (EMailShellView *mail_shell_view) EMailSession *session; EMailReader *reader; EMailView *mail_view; - EWebView *web_view; + EMailDisplay *display; const gchar *source; guint merge_id; gint ii = 0; @@ -691,7 +625,7 @@ e_mail_shell_view_private_constructed (EMailShellView *mail_shell_view) combo_box = e_shell_searchbar_get_scope_combo_box (searchbar); reader = E_MAIL_READER (shell_content); - formatter = e_mail_reader_get_formatter (reader); + display = e_mail_reader_get_mail_display (reader); message_list = e_mail_reader_get_message_list (reader); em_folder_tree_set_selectable_widget (folder_tree, message_list); @@ -710,8 +644,6 @@ e_mail_shell_view_private_constructed (EMailShellView *mail_shell_view) G_CALLBACK (mail_shell_view_search_filter_changed_cb), mail_shell_view, G_CONNECT_SWAPPED); - web_view = em_format_html_get_web_view (formatter); - g_signal_connect_object ( folder_tree, "folder-selected", G_CALLBACK (mail_shell_view_folder_tree_selected_cb), @@ -784,23 +716,17 @@ e_mail_shell_view_private_constructed (EMailShellView *mail_shell_view) mail_shell_view, G_CONNECT_SWAPPED); g_signal_connect_object ( - web_view, "key-press-event", + display, "key-press-event", G_CALLBACK (mail_shell_view_key_press_event_cb), mail_shell_view, G_CONNECT_SWAPPED); g_signal_connect_object ( - web_view, "popup-event", + display, "popup-event", G_CALLBACK (mail_shell_view_popup_event_cb), mail_shell_view, G_CONNECT_SWAPPED); g_signal_connect_object ( - web_view, "scroll", - G_CALLBACK (mail_shell_view_scroll_cb), - mail_shell_view, - G_CONNECT_AFTER | G_CONNECT_SWAPPED); - - g_signal_connect_object ( - web_view, "status-message", + display, "status-message", G_CALLBACK (e_shell_taskbar_set_message), shell_taskbar, G_CONNECT_SWAPPED); diff --git a/modules/mail/em-mailer-prefs.c b/modules/mail/em-mailer-prefs.c index 3f5e371296..d81656f997 100644 --- a/modules/mail/em-mailer-prefs.c +++ b/modules/mail/em-mailer-prefs.c @@ -529,10 +529,10 @@ toggle_button_init (EMMailerPrefs *prefs, const gchar *key, GCallback toggled) { - gboolean bool; + gboolean v_bool; - bool = g_settings_get_boolean (prefs->settings, key); - gtk_toggle_button_set_active (toggle, not ? !bool : bool); + v_bool = g_settings_get_boolean (prefs->settings, key); + gtk_toggle_button_set_active (toggle, not ? !v_bool : v_bool); if (toggled) { g_object_set_data ((GObject *) toggle, "key", (gpointer) key); diff --git a/modules/web-inspector/Makefile.am b/modules/web-inspector/Makefile.am new file mode 100644 index 0000000000..f8a1fc9340 --- /dev/null +++ b/modules/web-inspector/Makefile.am @@ -0,0 +1,23 @@ +module_LTLIBRARIES = libevolution-module-web-inspector.la + +libevolution_module_web_inspector_la_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + -I$(top_srcdir) \ + -I$(top_srcdir)/widgets \ + -DG_LOG_DOMAIN=\"evolution-web-inspector\" \ + $(EVOLUTION_DATA_SERVER_CFLAGS) \ + $(GNOME_PLATFORM_CFLAGS) + +libevolution_module_web_inspector_la_SOURCES = \ + evolution-web-inspector.c + +libevolution_module_web_inspector_la_LIBADD = \ + $(top_builddir)/e-util/libeutil.la \ + $(top_builddir)/widgets/misc/libemiscwidgets.la \ + $(EVOLUTION_DATA_SERVER_LIBS) \ + $(GNOME_PLATFORM_LIBS) + +libevolution_module_web_inspector_la_LDFLAGS = \ + -module -avoid-version $(NO_UNDEFINED) + +-include $(top_srcdir)/git.mk diff --git a/modules/web-inspector/evolution-web-inspector.c b/modules/web-inspector/evolution-web-inspector.c new file mode 100644 index 0000000000..27a21e0693 --- /dev/null +++ b/modules/web-inspector/evolution-web-inspector.c @@ -0,0 +1,185 @@ +/* + * evolution-web-inspector.c + * + * 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/> + * + */ + +#include <config.h> +#include <glib/gi18n-lib.h> + +#include <misc/e-web-view.h> +#include <libebackend/e-extension.h> + +/* Standard GObject macros */ +#define E_TYPE_WEB_INSPECTOR \ + (e_web_inspector_get_type ()) +#define E_WEB_INSPECTOR(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_WEB_INSPECTOR, EWebInspector)) + +typedef struct _EWebInspector EWebInspector; +typedef struct _EWebInspectorClass EWebInspectorClass; + +struct _EWebInspector { + EExtension parent; +}; + +struct _EWebInspectorClass { + EExtensionClass parent_class; +}; + +static const gchar *ui = +"<ui>" +" <popup name='context'>" +" <placeholder name='inspect-menu' >" +" <menuitem action='inspect'/>" +" </placeholder>" +" </popup>" +"</ui>"; + +/* Module Entry Points */ +void e_module_load (GTypeModule *type_module); +void e_module_unload (GTypeModule *type_module); + +/* Forward Declarations */ +GType e_web_inspector_get_type (void); + +G_DEFINE_DYNAMIC_TYPE (EWebInspector, e_web_inspector, E_TYPE_EXTENSION) + +static EWebView * +web_inspector_get_web_view (EWebInspector *extension) +{ + EExtensible *extensible; + + extensible = e_extension_get_extensible (E_EXTENSION (extension)); + + return E_WEB_VIEW (extensible); +} + +static void +web_inspector_action_inspect_cb (GtkAction *action, + EWebInspector *extension) +{ + WebKitWebInspector *inspector; + EWebView *web_view; + + web_view = web_inspector_get_web_view (extension); + inspector = webkit_web_view_get_inspector (WEBKIT_WEB_VIEW (web_view)); + + webkit_web_inspector_show (inspector); +} + +static GtkActionEntry inspect_entries[] = { + + { "inspect", + NULL, + N_("_Inspect..."), + NULL, + N_("Inspect the HTML content (debugging feature)"), + G_CALLBACK (web_inspector_action_inspect_cb) } +}; + +static WebKitWebView * +web_inspector_inspect_web_view_cb (WebKitWebInspector *inspector, + EWebInspector *extension) +{ + GtkWidget *web_view; + GtkWidget *window; + const gchar *title; + + title = _("Evolution Web Inspector"); + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_title (GTK_WINDOW (window), title); + gtk_widget_set_size_request (window, 600, 400); + gtk_widget_show (window); + + web_view = webkit_web_view_new (); + gtk_container_add (GTK_CONTAINER (window), web_view); + gtk_widget_show (web_view); + + return WEBKIT_WEB_VIEW (web_view); +} + +static void +web_inspector_constructed (GObject *object) +{ + EWebInspector *extension; + WebKitWebSettings *settings; + WebKitWebInspector *inspector; + GtkActionGroup *action_group; + GtkUIManager *ui_manager; + EWebView *web_view; + GError *error = NULL; + + extension = E_WEB_INSPECTOR (object); + web_view = web_inspector_get_web_view (extension); + + ui_manager = e_web_view_get_ui_manager (web_view); + action_group = e_web_view_get_action_group (web_view, "standard"); + + settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (web_view)); + g_object_set (settings, "enable-developer-extras", TRUE, NULL); + + inspector = webkit_web_view_get_inspector (WEBKIT_WEB_VIEW (web_view)); + + g_signal_connect ( + inspector, "inspect-web-view", + G_CALLBACK (web_inspector_inspect_web_view_cb), extension); + + gtk_action_group_add_actions ( + action_group, inspect_entries, + G_N_ELEMENTS (inspect_entries), extension); + + /* Because we are loading from a hard-coded string, there is + * no chance of I/O errors. Failure here implies a malformed + * UI definition. Full stop. */ + gtk_ui_manager_add_ui_from_string (ui_manager, ui, -1, &error); + if (error != NULL) + g_error ("%s", error->message); +} + +static void +e_web_inspector_class_init (EWebInspectorClass *class) +{ + GObjectClass *object_class; + EExtensionClass *extension_class; + + object_class = G_OBJECT_CLASS (class); + object_class->constructed = web_inspector_constructed; + + extension_class = E_EXTENSION_CLASS (class); + extension_class->extensible_type = E_TYPE_WEB_VIEW; +} + +static void +e_web_inspector_class_finalize (EWebInspectorClass *class) +{ +} + +static void +e_web_inspector_init (EWebInspector *extension) +{ +} + +G_MODULE_EXPORT void +e_module_load (GTypeModule *type_module) +{ + e_web_inspector_register_type (type_module); +} + +G_MODULE_EXPORT void +e_module_unload (GTypeModule *type_module) +{ +} |