diff options
author | Dan Winship <danw@src.gnome.org> | 2000-04-25 22:59:08 +0800 |
---|---|---|
committer | Dan Winship <danw@src.gnome.org> | 2000-04-25 22:59:08 +0800 |
commit | 9713f20bc88573b3716d1b0ba97ac2b4743d150b (patch) | |
tree | a51547aa57f535b0d92bb731ed129313c4c1a2fe | |
parent | 10fe02e0c606605fc26250cfbe099e443fb19101 (diff) | |
download | gsoc2013-evolution-9713f20bc88573b3716d1b0ba97ac2b4743d150b.tar gsoc2013-evolution-9713f20bc88573b3716d1b0ba97ac2b4743d150b.tar.gz gsoc2013-evolution-9713f20bc88573b3716d1b0ba97ac2b4743d150b.tar.bz2 gsoc2013-evolution-9713f20bc88573b3716d1b0ba97ac2b4743d150b.tar.lz gsoc2013-evolution-9713f20bc88573b3716d1b0ba97ac2b4743d150b.tar.xz gsoc2013-evolution-9713f20bc88573b3716d1b0ba97ac2b4743d150b.tar.zst gsoc2013-evolution-9713f20bc88573b3716d1b0ba97ac2b4743d150b.zip |
Redo large chunks of this. The mail display now consists of a vbox in a
* mail-display.c, mail-format.c: Redo large chunks of this. The
mail display now consists of a vbox in a scrolled window, in which
we put multiple GtkHTML objects. This means broken HTML in one
part can't corrupt other parts. The headers now scroll with the
body. Unrecognized attachments look prettier, but still don't do
anything, and will probably be changed later. We can also now
display nested message/rfc822 parts and multipart/alternatives
with multipart subparts. Oh, and text/{richtext,enriched}, since
we had all these ancient sample messages that use it and the lack
of support annoyed me. :)
Bonobo embeddables are broken right now, but I don't think that's
my fault.
svn path=/trunk/; revision=2601
-rw-r--r-- | mail/ChangeLog | 16 | ||||
-rw-r--r-- | mail/mail-display.c | 301 | ||||
-rw-r--r-- | mail/mail-display.h | 41 | ||||
-rw-r--r-- | mail/mail-format.c | 1021 | ||||
-rw-r--r-- | mail/mail-format.h | 6 |
5 files changed, 747 insertions, 638 deletions
diff --git a/mail/ChangeLog b/mail/ChangeLog index ee0f155806..ed1366124a 100644 --- a/mail/ChangeLog +++ b/mail/ChangeLog @@ -1,3 +1,19 @@ +2000-04-25 Dan Winship <danw@helixcode.com> + + * mail-display.c, mail-format.c: Redo large chunks of this. The + mail display now consists of a vbox in a scrolled window, in which + we put multiple GtkHTML objects. This means broken HTML in one + part can't corrupt other parts. The headers now scroll with the + body. Unrecognized attachments look prettier, but still don't do + anything, and will probably be changed later. We can also now + display nested message/rfc822 parts and multipart/alternatives + with multipart subparts. Oh, and text/{richtext,enriched}, since + we had all these ancient sample messages that use it and the lack + of support annoyed me. :) + + Bonobo embeddables are broken right now, but I don't think that's + my fault. + 2000-04-23 Dan Winship <danw@helixcode.com> * folder-browser-factory.c: rename "send" to "send_msg", to avoid diff --git a/mail/mail-display.c b/mail/mail-display.c index e0eb584a17..215a74c2ab 100644 --- a/mail/mail-display.c +++ b/mail/mail-display.c @@ -19,7 +19,7 @@ #include <libgnorba/gnorba.h> #include <bonobo/bonobo-stream-memory.h> -#define PARENT_TYPE (gtk_table_get_type ()) +#define PARENT_TYPE (gtk_vbox_get_type ()) static GtkObjectClass *mail_display_parent_class; @@ -102,12 +102,9 @@ embeddable_destroy_cb (GtkObject *obj, gpointer user_data) BonoboObjectClient* server; CORBA_Environment ev; - printf ("in the bonobo embeddable destroy callback\n"); be = BONOBO_WIDGET (obj); server = bonobo_widget_get_server (be); - - vf = bonobo_widget_get_view_frame (be); bonobo_control_frame_control_deactivate ( @@ -127,7 +124,16 @@ embeddable_destroy_cb (GtkObject *obj, gpointer user_data) /* gtk_object_unref (obj); */ } +static CamelStream * +cid_stream (const char *cid, CamelMimeMessage *message) +{ + CamelDataWrapper *data; + + data = gtk_object_get_data (GTK_OBJECT (message), cid); + g_return_val_if_fail (CAMEL_IS_DATA_WRAPPER (data), NULL); + return camel_data_wrapper_get_output_stream (data); +} /* * As a page is loaded, when gtkhtml comes across <object> tags, this @@ -135,27 +141,27 @@ embeddable_destroy_cb (GtkObject *obj, gpointer user_data) * our job in this function is to simply add a child widget to it. */ static void -on_object_requested (GtkHTML *html, GtkHTMLEmbedded *eb, void *unused) +on_object_requested (GtkHTML *html, GtkHTMLEmbedded *eb, gpointer data) { CamelStream *stream; GString *camel_stream_gstr; - + CamelMimeMessage *message = data; GtkWidget *bonobo_embeddable; BonoboObjectClient* server; Bonobo_PersistStream persist; CORBA_Environment ev; gchar *uid = gtk_html_embedded_get_parameter (eb, "uid"); - /* Both the classid (which specifies which bonobo object to * fire up) and the uid (which tells us where to find data to * persist from) must be available; if one of them isn't, - * print an error and bail. */ + * print an error and bail. + */ if (!uid || !eb->classid) { printf ("on_object_requested: couldn't find %s%s%s\n", - uid?"a uid":"", - (!uid && !eb->classid)?" or ":"", - eb->classid?"a classid":""); + uid ? "a uid" : "", + (!uid && !eb->classid) ? " or " : "", + eb->classid ? "a classid" : ""); return; } printf ("object requested : %s\n", eb->classid); @@ -172,13 +178,11 @@ on_object_requested (GtkHTML *html, GtkHTMLEmbedded *eb, void *unused) return; } - /* The UID should be a pointer to a CamelStream */ - if (sscanf (uid, "camel://%p", &stream) != 1) { - printf ("Couldn't get a pointer from url \"%s\"\n", uid); - gtk_object_unref (GTK_OBJECT (bonobo_embeddable)); - + if (!strncmp (uid, "cid:", 4)) { + stream = cid_stream (uid + 4, message); + g_return_if_fail (CAMEL_IS_STREAM (stream)); + } else return; - } /* Try to get a PersistStream interface from the server; if it doesn't support that interface, bail. */ @@ -219,63 +223,25 @@ on_object_requested (GtkHTML *html, GtkHTMLEmbedded *eb, void *unused) g_string_free (camel_stream_gstr, TRUE); } -static CamelMimePart * -find_cid (const char *cid, CamelMimePart *part) -{ - const char *msg_cid; - CamelDataWrapper *content; - CamelMultipart *mp; - int i, nparts; - - msg_cid = camel_mime_part_get_content_id (part); - if (msg_cid && !strcmp (cid, msg_cid)) - return part; - - content = camel_medium_get_content_object (CAMEL_MEDIUM (part)); - if (!content) - return NULL; - - if (CAMEL_IS_MIME_PART (content)) - return find_cid (cid, CAMEL_MIME_PART (content)); - else if (!CAMEL_IS_MULTIPART (content)) - return NULL; - - mp = CAMEL_MULTIPART (content); - nparts = camel_multipart_get_number (mp); - for (i = 0; i < nparts; i++) { - CamelMimePart *found_part; - - part = CAMEL_MIME_PART (camel_multipart_get_part (mp, i)); - found_part = find_cid (cid, part); - if (found_part) - return found_part; - } - - return NULL; -} - static void on_url_requested (GtkHTML *html, const char *url, GtkHTMLStreamHandle handle, gpointer user_data) { char buf[1024]; int nread; - CamelMimePart *part; - CamelDataWrapper *data; CamelStream *output; - - if (strncmp (url, "cid:", 4)) - return; - - part = gtk_object_get_data (GTK_OBJECT (html), "message"); - g_return_if_fail (part != NULL); - - part = find_cid (url + 4, part); - if (!part) + CamelMimeMessage *message = CAMEL_MIME_MESSAGE (user_data); + + if (strncmp (url, "camel:", 6) == 0) { + output = GUINT_TO_POINTER (strtoul (url + 6, NULL, 0)); + g_return_if_fail (CAMEL_IS_STREAM (output)); + } else if (strncmp (url, "cid:", 4) == 0) { + output = cid_stream (url + 4, message); + g_return_if_fail (CAMEL_IS_STREAM (output)); + } else return; - data = camel_medium_get_content_object (CAMEL_MEDIUM (part)); - output = camel_data_wrapper_get_output_stream (data); + camel_stream_reset (output); do { nread = camel_stream_read (output, buf, sizeof (buf)); if (nread > 0) @@ -283,6 +249,72 @@ on_url_requested (GtkHTML *html, const char *url, GtkHTMLStreamHandle handle, } while (!camel_stream_eos (output)); } +/* HTML part code */ +static void +html_size_req (GtkWidget *widget, GtkRequisition *requisition) +{ + requisition->height = GTK_LAYOUT (widget)->height; + requisition->width = GTK_LAYOUT (widget)->width; +} + +void +mail_html_new (GtkHTML **html, GtkHTMLStreamHandle **stream, + CamelMimeMessage *root, gboolean init) +{ + *html = GTK_HTML (gtk_html_new ()); + gtk_html_set_editable (*html, FALSE); + gtk_signal_connect (GTK_OBJECT (*html), "size_request", + GTK_SIGNAL_FUNC (html_size_req), NULL); + gtk_signal_connect (GTK_OBJECT (*html), "object_requested", + GTK_SIGNAL_FUNC (on_object_requested), root); + gtk_signal_connect (GTK_OBJECT (*html), "url_requested", + GTK_SIGNAL_FUNC (on_url_requested), root); + + *stream = gtk_html_begin (*html, ""); + if (init) { + mail_html_write (*html, *stream, HTML_HEADER + "<BODY TEXT=\"#000000\" " + "BGCOLOR=\"#FFFFFF\">\n"); + } +} + +void +mail_html_write (GtkHTML *html, GtkHTMLStreamHandle *stream, + const char *format, ...) +{ + char *buf; + va_list ap; + + va_start (ap, format); + buf = g_strdup_vprintf (format, ap); + va_end (ap); + gtk_html_write (html, stream, buf, strlen (buf)); + g_free (buf); +} + +void +mail_html_end (GtkHTML *html, GtkHTMLStreamHandle *stream, + gboolean finish, GtkBox *box) +{ + GtkWidget *scroll; + + scroll = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroll), + GTK_POLICY_NEVER, + GTK_POLICY_NEVER); + gtk_container_add (GTK_CONTAINER (scroll), GTK_WIDGET (html)); + + if (finish) + mail_html_write (html, stream, "</BODY></HTML>\n"); + gtk_html_end (html, stream, GTK_HTML_STREAM_OK); + + gtk_box_pack_start (box, scroll, FALSE, TRUE, 0); + gtk_widget_show (GTK_WIDGET (html)); + gtk_widget_show (scroll); +} + + + /** * mail_display_set_message: @@ -298,7 +330,7 @@ void mail_display_set_message (MailDisplay *mail_display, CamelMedium *medium) { - GtkHTMLStreamHandle *headers_stream, *body_stream; + GtkAdjustment *adj; /* * for the moment, camel-formatter deals only with @@ -311,52 +343,34 @@ mail_display_set_message (MailDisplay *mail_display, if (!CAMEL_IS_MIME_MESSAGE (medium)) return; - /* we were given a reference to the message in the last call - * to mail_display_set_message, free it now. */ - if (mail_display->current_message) + /* Clean up from previous message. */ + if (mail_display->current_message) { + GtkContainer *container = + GTK_CONTAINER (mail_display->inner_box); + GList *htmls; + + htmls = gtk_container_children (container); + while (htmls) { + gtk_container_remove (container, htmls->data); + htmls = htmls->next; + } + gtk_object_unref (GTK_OBJECT (mail_display->current_message)); + } mail_display->current_message = CAMEL_MIME_MESSAGE (medium); gtk_object_ref (GTK_OBJECT (medium)); - headers_stream = gtk_html_begin (mail_display->headers_html_widget, ""); - body_stream = gtk_html_begin (mail_display->body_html_widget, ""); + mail_format_mime_message (CAMEL_MIME_MESSAGE (medium), + mail_display->inner_box); - /* Convert the message into html and stream the result to the - * gtkhtml widgets. - */ - mail_write_html (headers_stream, "\n\ -<!doctype html public \"-//w3c//dtd html 4.0 transitional//en\">\n\ -<html>\n\ -<head>\n\ - <meta name=\"GENERATOR\" content=\"Evolution Mail Component (Rhon Rhon release)\">\n\ -</head>\n\ -<body text=\"#000000\" bgcolor=\"#EEEEEE\">\n\ -<font>\n\ -"); - - mail_write_html (body_stream, "\n\ -<!doctype html public \"-//w3c//dtd html 4.0 transitional//en\">\n\ -<html>\n\ -<head>\n\ - <meta name=\"GENERATOR\" content=\"Evolution Mail Component (Rhon Rhon release)\">\n\ -</head>\n\ -<body text=\"#000000\" bgcolor=\"#FFFFFF\">\n\ -"); + adj = gtk_scrolled_window_get_vadjustment (mail_display->scroll); + gtk_adjustment_set_value (adj, 0); + gtk_scrolled_window_set_vadjustment (mail_display->scroll, adj); - mail_format_mime_message (CAMEL_MIME_MESSAGE (medium), - headers_stream, - body_stream); - gtk_object_set_data (GTK_OBJECT (mail_display->body_html_widget), - "message", medium); - - mail_write_html (headers_stream, "\n</font>\n</body>\n</html>\n"); - mail_write_html (body_stream, "\n</body>\n</html>\n"); - - gtk_html_end (mail_display->headers_html_widget, headers_stream, - GTK_HTML_STREAM_OK); - gtk_html_end (mail_display->body_html_widget, body_stream, - GTK_HTML_STREAM_OK); + adj = gtk_scrolled_window_get_hadjustment (mail_display->scroll); + gtk_adjustment_set_value (adj, 0); + gtk_scrolled_window_set_hadjustment (mail_display->scroll, adj); } @@ -369,22 +383,6 @@ mail_display_init (GtkObject *object) { MailDisplay *mail_display = MAIL_DISPLAY (object); - /* create the headers html widget */ - mail_display->headers_html_widget = (GtkHTML *) gtk_html_new (); - gtk_widget_show (GTK_WIDGET (mail_display->headers_html_widget)); - - /* create the body html widget */ - mail_display->body_html_widget = (GtkHTML *) gtk_html_new (); - gtk_signal_connect (GTK_OBJECT (mail_display->body_html_widget), - "object_requested", - GTK_SIGNAL_FUNC (on_object_requested), - NULL); - gtk_signal_connect (GTK_OBJECT (mail_display->body_html_widget), - "url_requested", - GTK_SIGNAL_FUNC (on_url_requested), - NULL); - gtk_widget_show (GTK_WIDGET (mail_display->body_html_widget)); - /* various other initializations */ mail_display->current_message = NULL; } @@ -408,57 +406,33 @@ GtkWidget * mail_display_new (FolderBrowser *parent_folder_browser) { MailDisplay *mail_display = gtk_type_new (mail_display_get_type ()); - GtkTable *table = GTK_TABLE (mail_display); - GtkWidget *scroll_wnd; - GtkWidget* frame_wnd = NULL; + GtkWidget *scroll, *vbox; g_assert (parent_folder_browser); mail_display->parent_folder_browser = parent_folder_browser; - /* the table has table with 1 column and 2 lines */ - table->homogeneous = FALSE; - - gtk_table_resize (table, 1, 2); + gtk_box_set_homogeneous (GTK_BOX (mail_display), FALSE); gtk_widget_show (GTK_WIDGET (mail_display)); - - /* create a scrolled window and put the headers - * html widget inside */ - scroll_wnd = gtk_scrolled_window_new (NULL, NULL); - gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroll_wnd), - GTK_POLICY_AUTOMATIC, - GTK_POLICY_NEVER); - frame_wnd = gtk_frame_new (NULL); - gtk_frame_set_shadow_type ( - GTK_FRAME (frame_wnd), GTK_SHADOW_OUT); - gtk_widget_show (scroll_wnd); - gtk_container_add (GTK_CONTAINER (frame_wnd), scroll_wnd); - gtk_widget_show (frame_wnd); - gtk_container_add (GTK_CONTAINER (scroll_wnd), - GTK_WIDGET (mail_display->headers_html_widget)); - gtk_widget_set_usize (GTK_WIDGET (scroll_wnd), -1, 100); - /* add it on the top part of the table */ - gtk_table_attach (table, GTK_WIDGET (frame_wnd), - 0, 1, 0, 1, - GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0); - - - /* create a scrolled window and put the body - * html widget inside */ - scroll_wnd = gtk_scrolled_window_new (NULL, NULL); - gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroll_wnd), + /* For now, the box only contains a single scrolled window, + * which in turn contains a vbox itself. + */ + scroll = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); + gtk_box_pack_start_defaults (GTK_BOX (mail_display), + GTK_WIDGET (scroll)); + gtk_widget_show (GTK_WIDGET (scroll)); - gtk_widget_show (scroll_wnd); - gtk_container_add (GTK_CONTAINER (scroll_wnd), - GTK_WIDGET (mail_display->body_html_widget)); - - /* add it at the bottom part of the table */ - gtk_table_attach (table, GTK_WIDGET (scroll_wnd), - 0, 1, 1, 2, - GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); + vbox = gtk_vbox_new (FALSE, 2); + gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scroll), + vbox); + gtk_widget_show (GTK_WIDGET (vbox)); + + mail_display->scroll = GTK_SCROLLED_WINDOW (scroll); + mail_display->inner_box = GTK_BOX (vbox); return GTK_WIDGET (mail_display); } @@ -466,6 +440,3 @@ mail_display_new (FolderBrowser *parent_folder_browser) E_MAKE_TYPE (mail_display, "MailDisplay", MailDisplay, mail_display_class_init, mail_display_init, PARENT_TYPE); - - - diff --git a/mail/mail-display.h b/mail/mail-display.h index 685bd4c8f8..5fff4ec3de 100644 --- a/mail/mail-display.h +++ b/mail/mail-display.h @@ -1,20 +1,9 @@ /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ - - - - - - - - - - - #ifndef _MAIL_DISPLAY_H_ #define _MAIL_DISPLAY_H_ -#include <gtk/gtktable.h> +#include <gtk/gtkvbox.h> #include <gtkhtml/gtkhtml.h> #include "camel/camel-stream.h" #include "camel/camel-mime-message.h" @@ -28,26 +17,38 @@ #define IS_MAIL_DISPLAY_CLASS(k) (GTK_CHECK_CLASS_TYPE ((k), MAIL_DISPLAY_TYPE)) struct _MailDisplay { - GtkTable parent; + GtkVBox parent; - FolderBrowser *parent_folder_browser; + GtkScrolledWindow *scroll; + GtkBox *inner_box; - GtkHTML * headers_html_widget; - GtkHTML * body_html_widget; - + FolderBrowser *parent_folder_browser; CamelMimeMessage *current_message; }; typedef struct { - GtkTableClass parent_class; + GtkVBoxClass parent_class; } MailDisplayClass; -GtkType mail_display_get_type (void); -GtkWidget * mail_display_new (FolderBrowser *parent_folder_browser); +GtkType mail_display_get_type (void); +GtkWidget * mail_display_new (FolderBrowser *parent_folder_browser); void mail_display_set_message (MailDisplay *mail_display, CamelMedium *medium); +#define HTML_HEADER "<!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" + +void mail_html_new (GtkHTML **html, + GtkHTMLStreamHandle **stream, + CamelMimeMessage *root, + gboolean init); +void mail_html_write (GtkHTML *html, + GtkHTMLStreamHandle *stream, + const char *format, ...); +void mail_html_end (GtkHTML *html, + GtkHTMLStreamHandle *stream, + gboolean finish, + GtkBox *box); #endif /* _MAIL_DISPLAY_H_ */ diff --git a/mail/mail-format.c b/mail/mail-format.c index b5aba4ca32..08a2f98f98 100644 --- a/mail/mail-format.c +++ b/mail/mail-format.c @@ -24,300 +24,309 @@ #include <config.h> #include "mail-format.h" +#include "mail-display.h" #include "camel/hash-table-utils.h" #include <libgnome/libgnome.h> + #include <ctype.h> /* for isprint */ #include <string.h> /* for strstr */ +#include <fcntl.h> -static void handle_text_plain (CamelDataWrapper *wrapper, - GtkHTMLStreamHandle *stream, - CamelDataWrapper *root); -static void handle_text_html (CamelDataWrapper *wrapper, - GtkHTMLStreamHandle *stream, - CamelDataWrapper *root); -static void handle_image (CamelDataWrapper *wrapper, - GtkHTMLStreamHandle *stream, - CamelDataWrapper *root); -static void handle_vcard (CamelDataWrapper *wrapper, - GtkHTMLStreamHandle *stream, - CamelDataWrapper *root); -static void handle_mime_part (CamelDataWrapper *wrapper, - GtkHTMLStreamHandle *stream, - CamelDataWrapper *root); -static void handle_multipart_mixed (CamelDataWrapper *wrapper, - GtkHTMLStreamHandle *stream, - CamelDataWrapper *root); -static void handle_multipart_related (CamelDataWrapper *wrapper, - GtkHTMLStreamHandle *stream, - CamelDataWrapper *root); -static void handle_multipart_alternative(CamelDataWrapper *wrapper, - GtkHTMLStreamHandle *stream, - CamelDataWrapper *root); -static void handle_unknown_type (CamelDataWrapper *wrapper, - GtkHTMLStreamHandle *stream, - CamelDataWrapper *root); +static void handle_text_plain (CamelMimePart *part, + CamelMimeMessage *root, + GtkBox *box); +static void handle_text_enriched (CamelMimePart *part, + CamelMimeMessage *root, + GtkBox *box); +static void handle_text_html (CamelMimePart *part, + CamelMimeMessage *root, + GtkBox *box); +static void handle_image (CamelMimePart *part, + CamelMimeMessage *root, + GtkBox *box); +static void handle_multipart_mixed (CamelMimePart *part, + CamelMimeMessage *root, + GtkBox *box); +static void handle_multipart_related (CamelMimePart *part, + CamelMimeMessage *root, + GtkBox *box); +static void handle_multipart_alternative (CamelMimePart *part, + CamelMimeMessage *root, + GtkBox *box); +static void handle_audio (CamelMimePart *part, + CamelMimeMessage *root, + GtkBox *box); +static void handle_message_rfc822 (CamelMimePart *part, + CamelMimeMessage *root, + GtkBox *box); + +static void handle_unknown_type (CamelMimePart *part, + CamelMimeMessage *root, + GtkBox *box); +static void handle_via_bonobo (CamelMimePart *part, + CamelMimeMessage *root, + GtkBox *box); /* encodes some characters into their 'escaped' version; * so '<' turns into '<', and '"' turns into '"' */ -static gchar *text_to_html (const guchar *input, - guint len, - guint *encoded_len_return, - gboolean add_pre, +static gchar *text_to_html (const guchar *input, guint len, + guint *encoded_len_return, gboolean add_pre, gboolean convert_newlines_to_br); /* writes the header info for a mime message into an html stream */ -static void write_header_info_to_stream (CamelMimeMessage* mime_message, - GtkHTMLStreamHandle *stream); +static void write_headers (CamelMimeMessage *mime_message, GtkBox *box); /* dispatch html printing via mimetype */ -static void call_handler_function (CamelDataWrapper *wrapper, - gchar *mimetype_whole, - gchar *mimetype_main, - GtkHTMLStreamHandle *stream, - CamelDataWrapper *root); +static void call_handler_function (CamelMimePart *part, + CamelMimeMessage *root, + GtkBox *box); -#if 0 -/** - * camel_formatter_wrapper_to_html: - * @formatter: the camel formatter object - * @data_wrapper: the data wrapper - * @stream: byte stream where data will be written - * - * Writes a CamelDataWrapper out, as html, into a stream passed in as - * a parameter. - **/ -void camel_formatter_wrapper_to_html (CamelFormatter* formatter, - CamelDataWrapper* data_wrapper, - CamelStream* stream_out) -{ - CamelFormatterPrivate* fmt = formatter->priv; - gchar *mimetype_whole = - g_strdup_printf ("%s/%s", - data_wrapper->mime_type->type, - data_wrapper->mime_type->subtype); - - debug ("camel_formatter_wrapper_to_html: entered\n"); - g_assert (formatter && data_wrapper && stream_out); - - /* give the root CamelDataWrapper and the stream to the formatter */ - initialize_camel_formatter (formatter, data_wrapper, stream_out); - - if (stream_out) { - - /* write everything to the stream */ - camel_stream_write_string ( - fmt->stream, "<html><body bgcolor=\"white\">\n"); - call_handler_function ( - formatter, - data_wrapper, - mimetype_whole, - data_wrapper->mime_type->type); - - camel_stream_write_string (fmt->stream, "\n</body></html>\n"); - } - - g_free (mimetype_whole); -} -#endif /** * mail_format_mime_message: * @mime_message: the input mime message - * @header_stream: HTML stream to write headers to - * @body_stream: HTML stream to write data to + * @box: GtkBox to stack elements into. * - * Writes a CamelMimeMessage out, as html, into streams passed in as - * a parameter. Either stream may be #NULL. + * Writes a CamelMimeMessage out, as a series of GtkHTML objects, + * into the provided box. **/ void -mail_format_mime_message (CamelMimeMessage *mime_message, - GtkHTMLStreamHandle *header_stream, - GtkHTMLStreamHandle *body_stream) +mail_format_mime_message (CamelMimeMessage *mime_message, GtkBox *box) { g_return_if_fail (CAMEL_IS_MIME_MESSAGE (mime_message)); + g_return_if_fail (GTK_IS_BOX (box)); - /* Write the headers fields out as HTML to the header stream. */ - if (header_stream) - write_header_info_to_stream (mime_message, header_stream); - - /* Write the contents of the MIME message to the body stream. */ - if (body_stream) { - mail_write_html (body_stream, "<html><body>\n"); - call_handler_function (CAMEL_DATA_WRAPPER (mime_message), - "message/rfc822", - "message", - body_stream, - CAMEL_DATA_WRAPPER (mime_message)); - mail_write_html (body_stream, "\n</body></html>\n"); - } + write_headers (mime_message, box); + call_handler_function (CAMEL_MIME_PART (mime_message), + mime_message, box); } -/* We're maintaining a hashtable of mimetypes -> functions; - * Those functions have the following signature... - */ -typedef void (*mime_handler_fn) (CamelDataWrapper *wrapper, - GtkHTMLStreamHandle *stream, - CamelDataWrapper *root); - -static gchar* -lookup_unique_id (CamelDataWrapper *root, CamelDataWrapper *child) +static char * +get_cid (CamelMimePart *part, CamelMimeMessage *root) { - /* ** FIXME : replace this with a string representing - the location of the objetc in the tree */ - /* TODO: assert our return value != NULL */ + CamelDataWrapper *wrapper = + camel_medium_get_content_object (CAMEL_MEDIUM (part)); + char *cid; - gchar *temp_hack_uid; - - temp_hack_uid = g_strdup_printf ("%p", camel_data_wrapper_get_output_stream (child)); + /* If we have a real Content-ID, use it. If we don't, + * make a (syntactically invalid) fake one. + */ + if (camel_mime_part_get_content_id (part)) + cid = g_strdup (camel_mime_part_get_content_id (part)); + else + cid = g_strdup_printf ("@@@%p", wrapper); - return temp_hack_uid; + gtk_object_set_data (GTK_OBJECT (root), cid, wrapper); + return cid; } -static GHashTable *mime_function_table; - /* This tries to create a tag, given a mimetype and the child of a * mime message. It can return NULL if it can't match the mimetype to * a bonobo object. */ -static gchar * -get_bonobo_tag_for_object (CamelDataWrapper *wrapper, - gchar *mimetype, CamelDataWrapper *root) +static char * +get_bonobo_tag_for_object (CamelMimePart *part, CamelMimeMessage *root) { - char *uid = lookup_unique_id (root, wrapper); - const char *goad_id = gnome_mime_get_value (mimetype, - "bonobo-goad-id"); + GMimeContentField *type; + char *cid = get_cid (part, root), *mimetype; + const char *goad_id; + + type = camel_data_wrapper_get_mime_type_field ( + camel_medium_get_content_object (CAMEL_MEDIUM (part))); + mimetype = g_strdup_printf ("%s/%s", type->type, type->subtype); + goad_id = gnome_mime_get_value (mimetype, "bonobo-goad-id"); + g_free (mimetype); + + if (!goad_id) + goad_id = gnome_mime_get_value (type->type, "bonobo-goad-id"); if (goad_id) { return g_strdup_printf ("<object classid=\"%s\"> " "<param name=\"uid\" " - "value=\"camel://%s\"> </object>", - goad_id, uid); + "value=\"cid:%s\"></object>", + goad_id, cid); } else return NULL; } -/* - * This takes a mimetype, and tries to map that mimetype to a function - * or a bonobo object. - * - * - If it's mapped to a bonobo object, this function prints a tag - * into the stream, designating the bonobo object and a place that - * the bonobo object can find data to hydrate from - * - * - otherwise, the mimetype is mapped to another function, which can - * print into the stream +/* We're maintaining a hashtable of mimetypes -> functions; + * Those functions have the following signature... */ +typedef void (*mime_handler_fn) (CamelMimePart *part, CamelMimeMessage *root, GtkBox *box); + +static GHashTable *mime_function_table; +static GHashTable *mime_icon_table; + static void -call_handler_function (CamelDataWrapper *wrapper, - gchar *mimetype_whole_in, /* ex. "image/jpeg" */ - gchar *mimetype_main_in, /* ex. "image" */ - GtkHTMLStreamHandle *stream, - CamelDataWrapper *root) +setup_function_table (void) { - mime_handler_fn handler_function = NULL; - gchar *mimetype_whole = NULL; - gchar *mimetype_main = NULL; + mime_function_table = g_hash_table_new (g_strcase_hash, + g_strcase_equal); + + g_hash_table_insert (mime_function_table, "text/plain", + handle_text_plain); + g_hash_table_insert (mime_function_table, "text/richtext", + handle_text_enriched); + g_hash_table_insert (mime_function_table, "text/enriched", + handle_text_enriched); + g_hash_table_insert (mime_function_table, "text/html", + handle_text_html); + + g_hash_table_insert (mime_function_table, "image", + handle_image); + + g_hash_table_insert (mime_function_table, "audio", + handle_audio); + + g_hash_table_insert (mime_function_table, "message/rfc822", + handle_message_rfc822); + + g_hash_table_insert (mime_function_table, "multipart/alternative", + handle_multipart_alternative); + g_hash_table_insert (mime_function_table, "multipart/related", + handle_multipart_related); + g_hash_table_insert (mime_function_table, "multipart/mixed", + handle_multipart_mixed); + /* RFC 2046 says unrecognized multipart subtypes should be + * treated like multipart/mixed. + */ + g_hash_table_insert (mime_function_table, "multipart", + handle_multipart_mixed); +} - g_return_if_fail (mimetype_whole_in || mimetype_main_in); - g_return_if_fail (CAMEL_IS_DATA_WRAPPER (wrapper)); - g_return_if_fail (CAMEL_IS_DATA_WRAPPER (root)); +static CamelStream * +icon_stream (char *path) +{ + GByteArray *ba; + char buf[8192]; + int fd, nread; - if (mime_function_table == NULL) { - mime_function_table = g_hash_table_new (g_strcase_hash, - g_strcase_equal); - - /* hook up mime types to functions that handle them */ - g_hash_table_insert (mime_function_table, "text/plain", - handle_text_plain); - g_hash_table_insert (mime_function_table, "text/richtext", - handle_text_plain); - g_hash_table_insert (mime_function_table, "text/html", - handle_text_html); - g_hash_table_insert (mime_function_table, "multipart/alternative", - handle_multipart_alternative); - g_hash_table_insert (mime_function_table, "multipart/related", - handle_multipart_related); - g_hash_table_insert (mime_function_table, "multipart/mixed", - handle_multipart_mixed); - g_hash_table_insert (mime_function_table, "message/rfc822", - handle_mime_part); - g_hash_table_insert (mime_function_table, "image", - handle_image); - g_hash_table_insert (mime_function_table, "vcard", - handle_vcard); - - /* RFC 2046 says unrecognized multipart subtypes should - * be treated like multipart/mixed. - */ - g_hash_table_insert (mime_function_table, "multipart", - handle_multipart_mixed); + fd = open (path, O_RDONLY); + if (fd == -1) + return NULL; - /* Body parts don't have mime parts per se, so Camel - * sticks on the following one. - */ - g_hash_table_insert (mime_function_table, "mime/body-part", - handle_mime_part); + ba = g_byte_array_new (); + while (1) { + nread = read (fd, buf, sizeof (buf)); + if (nread < 1) + break; + g_byte_array_append (ba, buf, nread); } + close (fd); + return camel_stream_mem_new_with_byte_array (ba, CAMEL_STREAM_MEM_READ); +} + +static void +setup_icon_table (void) +{ + char *path; - /* Try to find a handler function in our own lookup table */ - if (mimetype_whole_in) { - mimetype_whole = g_strdup (mimetype_whole_in); - g_strdown (mimetype_whole); + mime_icon_table = g_hash_table_new (g_strcase_hash, + g_strcase_equal); - handler_function = g_hash_table_lookup (mime_function_table, - mimetype_whole); + path = gnome_pixmap_file ("gnome-audio2.png"); + if (path) { + g_hash_table_insert (mime_icon_table, "audio", + icon_stream (path)); + g_free (path); + } + path = gnome_pixmap_file ("gnome-graphics.png"); + if (path) { + g_hash_table_insert (mime_icon_table, "image", + icon_stream (path)); + g_free (path); } + path = gnome_pixmap_file ("gnome-qeye.png"); + if (path) { + g_hash_table_insert (mime_icon_table, "video", + icon_stream (path)); + g_free (path); + } + path = gnome_pixmap_file ("gnome-question.png"); + if (path) { + g_hash_table_insert (mime_icon_table, "unknown", + icon_stream (path)); + g_free (path); + } +} - if (mimetype_main_in && !handler_function) { - mimetype_main = g_strdup (mimetype_main_in); - g_strdown (mimetype_main); +static mime_handler_fn +lookup_handler (CamelMimePart *part) +{ + CamelDataWrapper *wrapper; + mime_handler_fn handler_function; + const char *goad_id; + char *mimetype_whole = NULL; + char *mimetype_main = NULL; + + g_return_val_if_fail (CAMEL_IS_MIME_PART (part), NULL); - handler_function = g_hash_table_lookup (mime_function_table, - mimetype_main); + if (mime_function_table == NULL) { + setup_function_table (); + setup_icon_table (); } - /* Upon failure, try to find a bonobo object to show the object */ - if (!handler_function) { - gchar *bonobo_tag = NULL; + wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (part)); - if (mimetype_whole) - bonobo_tag = get_bonobo_tag_for_object ( - wrapper, mimetype_whole, root); + /* Try to find a handler function in our own lookup table. */ + mimetype_whole = camel_data_wrapper_get_mime_type (wrapper); + g_strdown (mimetype_whole); - if (mimetype_main && !bonobo_tag) - bonobo_tag = get_bonobo_tag_for_object ( - wrapper, mimetype_main, root); + handler_function = g_hash_table_lookup (mime_function_table, + mimetype_whole); + if (handler_function) + goto out; - if (bonobo_tag) { - /* We can print a tag, and return! */ + mimetype_main = g_strdup (wrapper->mime_type->type); + g_strdown (mimetype_main); - mail_write_html (stream, bonobo_tag); - g_free (bonobo_tag); - if (mimetype_whole) - g_free (mimetype_whole); - if (mimetype_main) - g_free (mimetype_main); + handler_function = g_hash_table_lookup (mime_function_table, + mimetype_main); + if (handler_function) + goto out; - return; - } + /* See if there's a bonobo control that can show the object. */ + goad_id = gnome_mime_get_value (mimetype_whole, "bonobo-goad-id"); + if (goad_id) { + handler_function = handle_via_bonobo; + goto out; } - /* Use either a handler function we've found, or a default handler. */ - if (handler_function) - (*handler_function) (wrapper, stream, root); - else - handle_unknown_type (wrapper, stream, root); + goad_id = gnome_mime_get_value (mimetype_main, "bonobo-goad-id"); + if (goad_id) + handler_function = handle_via_bonobo; + + out: if (mimetype_whole) g_free (mimetype_whole); if (mimetype_main) g_free (mimetype_main); + + return handler_function; +} + +static void +call_handler_function (CamelMimePart *part, CamelMimeMessage *root, + GtkBox *box) +{ + CamelDataWrapper *wrapper; + mime_handler_fn handler_function = NULL; + + wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (part)); + + handler_function = lookup_handler (part); + + if (handler_function) + (*handler_function) (part, root, box); + else + handle_unknown_type (part, root, box); } -/* Convert plain text in equivalent-looking valid HTML. */ +/* Convert plain text into equivalent-looking valid HTML. */ static gchar * text_to_html (const guchar *input, guint len, guint *encoded_len_return, gboolean add_pre, @@ -397,13 +406,12 @@ text_to_html (const guchar *input, guint len, return buffer; } - static void -write_field_to_stream (const gchar *description, const gchar *value, - gboolean bold, GtkHTMLStreamHandle *stream) +write_field_to_stream (const char *description, const char *value, + gboolean bold, GtkHTML *html, + GtkHTMLStreamHandle *stream) { - gchar *s; - gchar *encoded_value; + char *encoded_value; if (value) { unsigned char *p; @@ -415,20 +423,20 @@ write_field_to_stream (const gchar *description, const gchar *value, *p = '?'; } } else - encoded_value = g_strdup (""); - - s = g_strdup_printf ("<tr valign=top><%s align=right>%s</%s>" - "<td>%s</td></tr>", bold ? "th" : "td", - description, bold ? "th" : "td", - encoded_value); - mail_write_html (stream, s); - g_free (encoded_value); - g_free (s); + encoded_value = ""; + + mail_html_write (html, stream, + "<tr valign=top><%s align=right>%s</%s>" + "<td>%s</td></tr>", bold ? "th" : "td", + description, bold ? "th" : "td", encoded_value); + if (value) + g_free (encoded_value); } static void write_recipients_to_stream (const gchar *recipient_type, const GList *recipients, gboolean bold, + GtkHTML *html, GtkHTMLStreamHandle *stream) { gchar *recipients_string = NULL; @@ -446,19 +454,24 @@ write_recipients_to_stream (const gchar *recipient_type, } write_field_to_stream (recipient_type, recipients_string, - bold, stream); + bold, html, stream); g_free (recipients_string); } static void -write_header_info_to_stream (CamelMimeMessage *mime_message, - GtkHTMLStreamHandle *stream) +write_headers (CamelMimeMessage *mime_message, GtkBox *box) { const GList *recipients; + GtkHTML *html; + GtkHTMLStreamHandle *stream; + + mail_html_new (&html, &stream, mime_message, FALSE); + mail_html_write (html, stream, "%s%s", HTML_HEADER, + "<BODY TEXT=\"#000000\" BGCOLOR=\"#EEEEEE\">\n"); - mail_write_html (stream, "<table>"); + mail_html_write (html, stream, "<table>"); /* A few fields will probably be available from the mime_message; * for each one that's available, write it to the output stream @@ -467,26 +480,28 @@ write_header_info_to_stream (CamelMimeMessage *mime_message, write_field_to_stream ("From:", camel_mime_message_get_from (mime_message), - TRUE, stream); + TRUE, html, stream); if (camel_mime_message_get_reply_to (mime_message)) { write_field_to_stream ("Reply-To:", camel_mime_message_get_reply_to (mime_message), - FALSE, stream); + FALSE, html, stream); } write_recipients_to_stream ("To:", camel_mime_message_get_recipients (mime_message, CAMEL_RECIPIENT_TYPE_TO), - TRUE, stream); + TRUE, html, stream); recipients = camel_mime_message_get_recipients (mime_message, CAMEL_RECIPIENT_TYPE_CC); if (recipients) - write_recipients_to_stream ("Cc:", recipients, TRUE, stream); + write_recipients_to_stream ("Cc:", recipients, TRUE, html, stream); write_field_to_stream ("Subject:", camel_mime_message_get_subject (mime_message), - TRUE, stream); + TRUE, html, stream); + + mail_html_write (html, stream, "</table>"); - mail_write_html (stream, "</table>"); + mail_html_end (html, stream, TRUE, box); } #define MIME_TYPE_WHOLE(a) (gmime_content_field_get_mime_type ( \ @@ -500,27 +515,20 @@ write_header_info_to_stream (CamelMimeMessage *mime_message, *----------------------------------------------------------------------*/ static void -handle_text_plain (CamelDataWrapper *wrapper, GtkHTMLStreamHandle *stream, - CamelDataWrapper *root) +handle_text_plain (CamelMimePart *part, CamelMimeMessage *root, GtkBox *box) { - gchar *text; + GtkHTML *html; + GtkHTMLStreamHandle *stream; + CamelDataWrapper *wrapper = + camel_medium_get_content_object (CAMEL_MEDIUM (part)); + char *text; CamelStream *wrapper_output_stream; gchar tmp_buffer[4096]; gint nb_bytes_read; gboolean empty_text = TRUE; - - mail_write_html (stream, "\n<!-- text/plain below -->\n"); - mail_write_html (stream, "<pre>\n"); - - /* FIXME: text/richtext is not difficult to translate into HTML */ - if (strcmp (wrapper->mime_type->subtype, "richtext") == 0) { - mail_write_html (stream, "<center><b>" - "<table bgcolor=\"b0b0ff\" cellpadding=3>" - "<tr><td>Warning: the following " - "richtext may not be formatted correctly. " - "</b></td></tr></table></center><br>"); - } + mail_html_new (&html, &stream, root, TRUE); + mail_html_write (html, stream, "\n<!-- text/plain -->\n<pre>\n"); /* Get the output stream of the data wrapper. */ wrapper_output_stream = camel_data_wrapper_get_output_stream (wrapper); @@ -529,7 +537,8 @@ handle_text_plain (CamelDataWrapper *wrapper, GtkHTMLStreamHandle *stream, do { /* Read next chunk of text. */ nb_bytes_read = camel_stream_read (wrapper_output_stream, - tmp_buffer, 4096); + tmp_buffer, + sizeof (tmp_buffer)); /* If there's any text, write it to the stream. */ if (nb_bytes_read > 0) { @@ -542,33 +551,167 @@ handle_text_plain (CamelDataWrapper *wrapper, GtkHTMLStreamHandle *stream, nb_bytes_read, &returned_strlen, FALSE, FALSE); - mail_write_html (stream, text); + gtk_html_write (html, stream, text, + returned_strlen); g_free (text); } } while (!camel_stream_eos (wrapper_output_stream)); if (empty_text) - mail_write_html (stream, "<b>(empty)</b>"); + mail_html_write (html, stream, "<b>(empty)</b>"); + + mail_html_write (html, stream, "</pre>\n"); + mail_html_end (html, stream, TRUE, box); +} + +/* text/enriched (RFC 1896) or text/richtext (included in RFC 1341) */ +static void +handle_text_enriched (CamelMimePart *part, CamelMimeMessage *root, GtkBox *box) +{ + static GHashTable *translations = NULL; + GtkHTML *html; + GtkHTMLStreamHandle *stream; + CamelDataWrapper *wrapper = + camel_medium_get_content_object (CAMEL_MEDIUM (part)); + CamelStream *memstream; + GByteArray *ba; + char *p; + int len, nofill = 0; + + if (!translations) { + translations = g_hash_table_new (g_strcase_hash, + g_strcase_equal); + g_hash_table_insert (translations, "bold", "<b>"); + g_hash_table_insert (translations, "/bold", "</b>"); + g_hash_table_insert (translations, "italic", "<i>"); + g_hash_table_insert (translations, "/italic", "</i>"); + g_hash_table_insert (translations, "fixed", "<tt>"); + g_hash_table_insert (translations, "/fixed", "</tt>"); + g_hash_table_insert (translations, "smaller", "<font size=-1>"); + g_hash_table_insert (translations, "/smaller", "</font>"); + g_hash_table_insert (translations, "bigger", "<font size=+1>"); + g_hash_table_insert (translations, "/bigger", "</font>"); + g_hash_table_insert (translations, "underline", "<u>"); + g_hash_table_insert (translations, "/underline", "</u>"); + g_hash_table_insert (translations, "center", "<p align=center>"); + g_hash_table_insert (translations, "/center", "</p>"); + g_hash_table_insert (translations, "flushleft", "<p align=left>"); + g_hash_table_insert (translations, "/flushleft", "</p>"); + g_hash_table_insert (translations, "flushright", "<p align=right>"); + g_hash_table_insert (translations, "/flushright", "</p>"); + g_hash_table_insert (translations, "excerpt", "<blockquote>"); + g_hash_table_insert (translations, "/excerpt", "</blockquote>"); + g_hash_table_insert (translations, "paragraph", "<p>"); + g_hash_table_insert (translations, "signature", "<address>"); + g_hash_table_insert (translations, "/signature", "</address>"); + g_hash_table_insert (translations, "comment", "<!-- "); + g_hash_table_insert (translations, "/comment", " -->"); + g_hash_table_insert (translations, "param", "<!-- "); + g_hash_table_insert (translations, "/param", " -->"); + g_hash_table_insert (translations, "nl", "<br>"); + g_hash_table_insert (translations, "np", "<hr>"); + } + + mail_html_new (&html, &stream, root, TRUE); + mail_html_write (html, stream, "\n<!-- text/enriched -->\n"); + + ba = g_byte_array_new (); + memstream = camel_stream_mem_new_with_byte_array (ba, CAMEL_STREAM_MEM_WRITE); + camel_data_wrapper_write_to_stream (wrapper, memstream); + g_byte_array_append (ba, "", 1); + + p = ba->data; + + while (p) { + len = strcspn (p, " <>&\n"); + if (len) + gtk_html_write (html, stream, p, len); + + p += len; + if (!*p) + break; + + switch (*p++) { + case ' ': + while (*p == ' ') { + mail_html_write (html, stream, " "); + p++; + } + mail_html_write (html, stream, " "); + break; + + case '\n': + mail_html_write (html, stream, " "); + if (nofill <= 0) { + while (*p == '\n') { + mail_html_write (html, stream, "<br>"); + p++; + } + } + break; + + case '>': + mail_html_write (html, stream, ">"); + break; + + case '&': + mail_html_write (html, stream, "&"); + break; - mail_write_html (stream, "</pre>\n"); + case '<': + if (*p == '<') { + mail_html_write (html, stream, "<"); + break; + } + + if (strncmp (p, "lt>", 3) == 0) + mail_html_write (html, stream, "<"); + else if (strncmp (p, "nofill>", 7) == 0) { + nofill++; + mail_html_write (html, stream, "<pre>"); + } else if (strncmp (p, "/nofill>", 8) == 0) { + nofill--; + mail_html_write (html, stream, "</pre>"); + } else { + char *copy, *match; + + len = strcspn (p, ">"); + copy = g_strndup (p, len); + match = g_hash_table_lookup (translations, + copy); + g_free (copy); + if (match) + mail_html_write (html, stream, match); + } + + p = strchr (p, '>'); + if (p) + p++; + } + } + + mail_html_end (html, stream, TRUE, box); } static void -handle_text_html (CamelDataWrapper *wrapper, GtkHTMLStreamHandle *stream, - CamelDataWrapper *root) +handle_text_html (CamelMimePart *part, CamelMimeMessage *root, GtkBox *box) { + GtkHTML *html; + GtkHTMLStreamHandle *stream; + CamelDataWrapper *wrapper = + camel_medium_get_content_object (CAMEL_MEDIUM (part)); CamelStream *wrapper_output_stream; gchar tmp_buffer[4096]; gint nb_bytes_read; gboolean empty_text = TRUE; + mail_html_new (&html, &stream, root, FALSE); + mail_html_write (html, stream, "\n<!-- text/html -->\n"); + /* Get the output stream of the data wrapper. */ wrapper_output_stream = camel_data_wrapper_get_output_stream (wrapper); camel_stream_reset (wrapper_output_stream); - /* Write the header. */ - mail_write_html (stream, "\n<!-- text/html below -->\n"); - do { /* Read next chunk of text. */ nb_bytes_read = camel_stream_read (wrapper_output_stream, @@ -579,109 +722,40 @@ handle_text_html (CamelDataWrapper *wrapper, GtkHTMLStreamHandle *stream, empty_text = FALSE; /* Write the buffer to the html stream */ - gtk_html_stream_write (stream, tmp_buffer, - nb_bytes_read); + gtk_html_write (html, stream, tmp_buffer, + nb_bytes_read); } } while (!camel_stream_eos (wrapper_output_stream)); if (empty_text) - mail_write_html (stream, "<b>(empty)</b>"); -} - -static void -handle_image (CamelDataWrapper *wrapper, GtkHTMLStreamHandle *stream, - CamelDataWrapper *root) -{ - gchar *uuid; - gchar *tag; - - uuid = lookup_unique_id (root, wrapper); - - mail_write_html (stream, "\n<!-- image below -->\n"); - tag = g_strdup_printf ("<img src=\"camel://%s\">\n", uuid); - mail_write_html (stream, tag); - g_free (uuid); - g_free (tag); -} - -static void -handle_vcard (CamelDataWrapper *wrapper, GtkHTMLStreamHandle *stream, - CamelDataWrapper *root) -{ - mail_write_html (stream, "\n<!-- vcard below -->\n"); - - /* FIXME: do something here. */ -} - -static void -handle_mime_part (CamelDataWrapper *wrapper, GtkHTMLStreamHandle *stream, - CamelDataWrapper *root) -{ - CamelMimePart *mime_part; - CamelDataWrapper *message_contents; - gchar *whole_mime_type; - - g_return_if_fail (CAMEL_IS_MIME_PART (wrapper)); - - mime_part = CAMEL_MIME_PART (wrapper); - message_contents = - camel_medium_get_content_object (CAMEL_MEDIUM (mime_part)); - - g_assert (message_contents); - - mail_write_html (stream, "\n<!-- mime message below -->\n"); - -// mail_write_html (stream, -// "<table width=95% border=1><tr><td>\n\n"); + mail_html_write (html, stream, "<b>(empty)</b>"); - /* dispatch the correct handler function for the mime type */ - whole_mime_type = MIME_TYPE_WHOLE (mime_part); - call_handler_function (message_contents, - whole_mime_type, - MIME_TYPE_MAIN (mime_part), - stream, root); - g_free (whole_mime_type); - - /* close up the table we opened */ -// mail_write_html (stream, -// "\n\n</td></tr></table>\n\n"); + mail_html_end (html, stream, FALSE, box); } - -/* called for each body part in a multipart/mixed */ static void -display_camel_body_part (CamelMimeBodyPart *body_part, - GtkHTMLStreamHandle *stream, - CamelDataWrapper *root) +handle_image (CamelMimePart *part, CamelMimeMessage *root, GtkBox *box) { - gchar *whole_mime_type; - - CamelDataWrapper* contents = - camel_medium_get_content_object (CAMEL_MEDIUM (body_part)); - - whole_mime_type = MIME_TYPE_WHOLE (body_part); - call_handler_function (contents, whole_mime_type, - MIME_TYPE_MAIN (body_part), - stream, root); - g_free (whole_mime_type); - - mail_write_html (stream, "\n<hr>\n"); + GtkHTML *html; + GtkHTMLStreamHandle *stream; + char *cid; + + cid = get_cid (part, root); + mail_html_new (&html, &stream, root, TRUE); + mail_html_write (html, stream, "<img src=\"cid:%s\">", cid); + mail_html_end (html, stream, TRUE, box); + g_free (cid); } - -/* Our policy here is this: - (1) print text/(plain|html) parts found - (2) print vcards and images inline - (3) treat all other parts as attachments */ static void -handle_multipart_mixed (CamelDataWrapper *wrapper, - GtkHTMLStreamHandle *stream, CamelDataWrapper *root) +handle_multipart_mixed (CamelMimePart *part, CamelMimeMessage *root, GtkBox *box) { + CamelDataWrapper *wrapper = + camel_medium_get_content_object (CAMEL_MEDIUM (part)); CamelMultipart *mp; int i, nparts; g_return_if_fail (CAMEL_IS_MULTIPART (wrapper)); - mp = CAMEL_MULTIPART (wrapper); nparts = camel_multipart_get_number (mp); @@ -689,151 +763,202 @@ handle_multipart_mixed (CamelDataWrapper *wrapper, CamelMimeBodyPart *body_part = camel_multipart_get_part (mp, i); - display_camel_body_part (body_part, stream, root); + call_handler_function (CAMEL_MIME_PART (body_part), root, box); } } /* As seen in RFC 2387! */ -/* FIXME: camel doesn't currently set CamelMultipart::parent, so we - * can use it here. - */ static void -handle_multipart_related (CamelDataWrapper *wrapper, - GtkHTMLStreamHandle *stream, - CamelDataWrapper *root) +handle_multipart_related (CamelMimePart *part, CamelMimeMessage *root, GtkBox *box) { + CamelDataWrapper *wrapper = + camel_medium_get_content_object (CAMEL_MEDIUM (part)); CamelMultipart *mp; - CamelMimeBodyPart *body_part; -#if 0 - GMimeContentField *parent_content_type; - const char *start, *cid; + CamelMimeBodyPart *body_part, *display_part = NULL; + GMimeContentField *content_type; + const char *start; int i, nparts; -#endif g_return_if_fail (CAMEL_IS_MULTIPART (wrapper)); - mp = CAMEL_MULTIPART (wrapper); -#if 0 - parent_content_type = - camel_mime_part_get_content_type ( - camel_multipart_get_parent (mp)); + nparts = camel_multipart_get_number (mp); - start = gmime_content_field_get_parameter (parent_content_type, - "start"); + content_type = camel_mime_part_get_content_type (part); + start = gmime_content_field_get_parameter (content_type, "start"); if (start) { - nparts = camel_multipart_get_number (mp); + int len; + + /* The "start" parameter includes <>s, which Content-Id + * does not. + */ + len = strlen (start) - 2; + for (i = 0; i < nparts; i++) { - CamelMimeBodyPart *body_part = - camel_multipart_get_part (mp, i); + const char *cid; + body_part = camel_multipart_get_part (mp, i); cid = camel_mime_part_get_content_id ( CAMEL_MIME_PART (body_part)); - if (!strcmp (cid, start)) { - display_camel_body_part (body_part, stream, - root); - return; + if (!strncmp (cid, start + 1, len) && + strlen (cid) == len) { + display_part = body_part; + break; } } - /* Oops. Hrmph. */ - handle_multipart_mixed (wrapper, stream, root); + if (!display_part) { + /* Oops. Hrmph. */ + handle_multipart_mixed (part, root, box); + } + } else { + /* No start parameter, so it defaults to the first part. */ + display_part = camel_multipart_get_part (mp, 0); } -#endif - /* No start parameter, so it defaults to the first part. */ - body_part = camel_multipart_get_part (mp, 0); - display_camel_body_part (body_part, stream, root); + /* Record the Content-IDs of any non-displayed parts. */ + for (i = 0; i < nparts; i++) { + char *cid; + + body_part = camel_multipart_get_part (mp, i); + if (body_part == display_part) + continue; + + part = CAMEL_MIME_PART (body_part); + cid = get_cid (part, root); + g_free (cid); + } + + /* Now, display the displayed part. */ + call_handler_function (CAMEL_MIME_PART (display_part), root, box); } -/* multipart/alternative helper function -- - * Returns NULL if no displayable msg is found - */ +/* RFC 2046 says "display the last part that you are able to display". */ static CamelMimePart * find_preferred_alternative (CamelMultipart *multipart) { int i, nparts; - CamelMimePart* html_part = NULL; - CamelMimePart* plain_part = NULL; + CamelMimePart *preferred_part = NULL; - /* Find out out many parts are in it. */ nparts = camel_multipart_get_number (multipart); - - /* FIXME: DO LEAF-LOOKUP HERE FOR OTHER MIME-TYPES!!! */ - for (i = 0; i < nparts; i++) { - CamelMimeBodyPart *body_part = - camel_multipart_get_part (multipart, i); - - if (strcasecmp (MIME_TYPE_MAIN (body_part), "text") != 0) - continue; + CamelMimePart *part = CAMEL_MIME_PART ( + camel_multipart_get_part (multipart, i)); - if (strcasecmp (MIME_TYPE_SUB (body_part), "plain") == 0) - plain_part = CAMEL_MIME_PART (body_part); - else if (strcasecmp (MIME_TYPE_SUB (body_part), "html") == 0) - html_part = CAMEL_MIME_PART (body_part); + if (lookup_handler (part)) + preferred_part = part; } - if (html_part) - return html_part; - if (plain_part) - return plain_part; - return NULL; + return preferred_part; } -/* The current policy for multipart/alternative is this: - * - * if (we find a text/html body part) - * we print it - * else if (we find a text/plain body part) - * we print it - * else - * we print nothing - */ static void -handle_multipart_alternative (CamelDataWrapper *wrapper, - GtkHTMLStreamHandle *stream, - CamelDataWrapper *root) +handle_multipart_alternative (CamelMimePart *part, CamelMimeMessage *root, + GtkBox *box) { - CamelMultipart *multipart = CAMEL_MULTIPART (wrapper); + CamelDataWrapper *wrapper = + camel_medium_get_content_object (CAMEL_MEDIUM (part)); + CamelMultipart *multipart; CamelMimePart *mime_part; - gchar *whole_mime_type; + + g_return_if_fail (CAMEL_IS_MULTIPART (wrapper)); + multipart = CAMEL_MULTIPART (wrapper); mime_part = find_preferred_alternative (multipart); - if (mime_part) { - CamelDataWrapper *contents = - camel_medium_get_content_object ( - CAMEL_MEDIUM (mime_part)); - - whole_mime_type = MIME_TYPE_WHOLE (mime_part); - call_handler_function (contents, whole_mime_type, - MIME_TYPE_MAIN (mime_part), - stream, root); - g_free (whole_mime_type); - } + if (mime_part) + call_handler_function (mime_part, root, box); + else + handle_unknown_type (part, root, box); +} + +void +stream_destroy_notify (gpointer data) +{ + camel_stream_close (data); } static void -handle_unknown_type (CamelDataWrapper *wrapper, - GtkHTMLStreamHandle *stream, - CamelDataWrapper *root) +handle_mystery (CamelMimePart *part, CamelMimeMessage *root, GtkBox *box, + char *icon_cid, char *id, char *action) { - gchar *tag; - char *uid = lookup_unique_id (root, wrapper); + GtkHTML *html; + GtkHTMLStreamHandle *stream; + char *cid; + + cid = get_cid (part, root); + mail_html_new (&html, &stream, root, TRUE); + mail_html_write (html, stream, "<table><tr><td><a href=\"camel:%p\">" + "<img src=\"camel:%p\"></a></td>" + "<td>%s<br><br>Click on the icon to %s." + "</td></tr></table>", get_cid (part, root), + icon_cid, id, action); + mail_html_end (html, stream, TRUE, box); +} - tag = g_strdup_printf ("<a href=\"camel://%s\">click-me-to-save</a>\n", - uid); +static void +handle_audio (CamelMimePart *part, CamelMimeMessage *root, GtkBox *box) +{ + char *cid, *id; - mail_write_html (stream, tag); + cid = g_hash_table_lookup (mime_icon_table, "audio"); + id = g_strdup_printf ("Audio data in \"%s\" format.", + camel_mime_part_get_content_type (part)->subtype); + handle_mystery (part, root, box, cid, id, "play it"); + g_free (id); } +static void +handle_message_rfc822 (CamelMimePart *part, CamelMimeMessage *root, + GtkBox *box) +{ + CamelDataWrapper *wrapper = + camel_medium_get_content_object (CAMEL_MEDIUM (part)); + GtkWidget *subbox, *frame; + + g_return_if_fail (CAMEL_IS_MIME_MESSAGE (wrapper)); + + subbox = gtk_vbox_new (FALSE, 2); + mail_format_mime_message (CAMEL_MIME_MESSAGE (wrapper), + GTK_BOX (subbox)); + + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); + gtk_container_set_border_width (GTK_CONTAINER (frame), 8); + gtk_box_pack_start (box, frame, FALSE, FALSE, 0); + gtk_container_add (GTK_CONTAINER (frame), subbox); + gtk_widget_show_all (frame); +} -void -mail_write_html (GtkHTMLStreamHandle *stream, const char *data) +static void +handle_unknown_type (CamelMimePart *part, CamelMimeMessage *root, GtkBox *box) { - gtk_html_stream_write (stream, data, strlen (data)); + char *cid, *id; + + cid = g_hash_table_lookup (mime_icon_table, "unknown"); + id = g_strdup_printf ("Unknown data of type \"%s/%s\".", + camel_mime_part_get_content_type (part)->type, + camel_mime_part_get_content_type (part)->subtype); + handle_mystery (part, root, box, cid, id, "save it to disk"); + g_free (id); } +static void +handle_via_bonobo (CamelMimePart *part, CamelMimeMessage *root, GtkBox *box) +{ + GtkHTML *html; + GtkHTMLStreamHandle *stream; + char *bonobo_tag = get_bonobo_tag_for_object (part, root); + + g_return_if_fail (bonobo_tag); + + mail_html_new (&html, &stream, root, TRUE); + mail_html_write (html, stream, bonobo_tag); + mail_html_end (html, stream, TRUE, box); + + g_free (bonobo_tag); +} + + static char * get_data_wrapper_text (CamelDataWrapper *data) { @@ -860,7 +985,7 @@ reply_body (CamelDataWrapper *data, gboolean *html) CamelMimePart *subpart; int i, nparts; char *subtext, *old; - const char *boundary, *disp; + const char *boundary, *disp, *subtype; char *text = NULL; GMimeContentField *mime_type; @@ -901,8 +1026,9 @@ reply_body (CamelDataWrapper *data, gboolean *html) */ for (i = 0; i < nparts; i++) { subpart = CAMEL_MIME_PART (camel_multipart_get_part (mp, i)); + subtype = camel_mime_part_get_content_type (subpart)->subtype; - if (strcasecmp (MIME_TYPE_SUB (subpart), "html") == 0) + if (strcasecmp (subtype, "html") == 0) return reply_body (camel_medium_get_content_object (CAMEL_MEDIUM (subpart)), html); } @@ -1108,8 +1234,7 @@ mail_generate_forward (CamelMimeMessage *mime_message, camel_stream_close (stream); composer = E_MSG_COMPOSER (e_msg_composer_new ()); - e_msg_composer_attachment_bar_attach (composer->attachment_bar, - tmpfile); + e_msg_composer_attachment_bar_attach (E_MSG_COMPOSER_ATTACHMENT_BAR (composer->attachment_bar), tmpfile); g_free (tmpfile); /* FIXME: should we default a subject? */ diff --git a/mail/mail-format.h b/mail/mail-format.h index 22c5f9deb2..39265014d6 100644 --- a/mail/mail-format.h +++ b/mail/mail-format.h @@ -34,11 +34,7 @@ extern "C" { #include "camel/camel.h" #include "composer/e-msg-composer.h" -void mail_format_mime_message (CamelMimeMessage *mime_message, - GtkHTMLStreamHandle *header_stream, - GtkHTMLStreamHandle *body_stream); - -void mail_write_html (GtkHTMLStreamHandle *stream, const char *data); +void mail_format_mime_message (CamelMimeMessage *mime_message, GtkBox *box); EMsgComposer *mail_generate_reply (CamelMimeMessage *mime_message, gboolean to_all); |