From 040ff5ad09c54d215f0bced2d748e01675cc337a Mon Sep 17 00:00:00 2001 From: Dan Winship Date: Mon, 11 Sep 2000 17:12:57 +0000 Subject: Another big rewrite of this stuff. Now all (well, most) attachments get a * mail-display.c, mail-format.c: Another big rewrite of this stuff. Now all (well, most) attachments get a small icon with a description and a (non-obvious) right-click pop-up menu with options to save, open in an external program, or show/hide inline. TODO: antialias the icon, add more options to the pop-up for certain MIME types, add an icon to the headers, fix PGP to work like everything else, fix message/external-body to work again, add some icon caching action, etc, etc. svn path=/trunk/; revision=5326 --- mail/ChangeLog | 12 ++ mail/mail-display.c | 315 ++++++++++++++++++++++--------- mail/mail-format.c | 521 +++++++++++++++++++++++++--------------------------- mail/mail.h | 14 ++ 4 files changed, 505 insertions(+), 357 deletions(-) (limited to 'mail') diff --git a/mail/ChangeLog b/mail/ChangeLog index 8acbdb1d88..890a293c34 100644 --- a/mail/ChangeLog +++ b/mail/ChangeLog @@ -1,3 +1,15 @@ +2000-09-11 Dan Winship + + * mail-display.c, mail-format.c: Another big rewrite of this + stuff. Now all (well, most) attachments get a small icon with a + description and a (non-obvious) right-click pop-up menu with + options to save, open in an external program, or show/hide inline. + + TODO: antialias the icon, add more options to the pop-up for + certain MIME types, add an icon to the headers, fix PGP to work + like everything else, fix message/external-body to work again, + add some icon caching action, etc, etc. + 2000-09-09 Jeffrey Stedfast * mail-ops.c (do_fetch_mail): Use the CamelUIDCache so that we diff --git a/mail/mail-display.c b/mail/mail-display.c index 960c97d18f..521d15a3dd 100644 --- a/mail/mail-display.c +++ b/mail/mail-display.c @@ -16,6 +16,7 @@ #include #include "e-util/e-util.h" #include "e-util/e-html-utils.h" +#include "e-util/e-popup-menu.h" #include "mail-display.h" #include "mail.h" @@ -29,7 +30,7 @@ static GtkObjectClass *mail_display_parent_class; -static void redisplay (MailDisplay *md); +static void redisplay (MailDisplay *md, gboolean unscroll); /*----------------------------------------------------------------------* * Callbacks @@ -116,22 +117,92 @@ make_safe_filename (const char *prefix, CamelMimePart *part) return safe; } -static CamelMimePart * -part_for_url (const char *url, MailDisplay *md) +static void +save_data_cb (GtkWidget *widget, gpointer user_data) { - GHashTable *urls; + GtkFileSelection *file_select = (GtkFileSelection *) + gtk_widget_get_ancestor (widget, GTK_TYPE_FILE_SELECTION); - urls = g_datalist_get_data (md->data, "urls"); - g_return_val_if_fail (urls != NULL, NULL); - return g_hash_table_lookup (urls, url); + write_data_to_file (user_data, + gtk_file_selection_get_filename (file_select), + FALSE); + gtk_widget_destroy (GTK_WIDGET (file_select)); +} + +static gboolean +idle_redisplay (gpointer data) +{ + MailDisplay *md = data; + + md->idle_id = 0; + redisplay (md, FALSE); + return FALSE; +} + +static void +queue_redisplay (MailDisplay *md) +{ + if (!md->idle_id) { + md->idle_id = g_idle_add_full (G_PRIORITY_LOW, idle_redisplay, + md, NULL); + } +} + +static void +on_link_clicked (GtkHTML *html, const char *url, gpointer user_data) +{ + MailDisplay *md = user_data; + + if (!g_strncasecmp (url, "news:", 5) || + !g_strncasecmp (url, "nntp:", 5)) + g_warning ("Can't handle news URLs yet."); + else if (!g_strncasecmp (url, "mailto:", 7)) + send_to_url (url); + else if (!strcmp (url, "x-evolution-decode-pgp:")) { + g_datalist_set_data (md->data, "show_pgp", + GINT_TO_POINTER (1)); + queue_redisplay (md); + } else + gnome_url_show (url); } static void -launch_external (const char *url, MailDisplay *md) +save_cb (GtkWidget *widget, gpointer user_data) { - const char *command = url + 21; - char *tmpl, *tmpdir, *filename, *argv[2]; - CamelMimePart *part = part_for_url (url, md); + CamelMimePart *part = gtk_object_get_data (user_data, "CamelMimePart"); + GtkFileSelection *file_select; + char *filename; + + filename = make_safe_filename (g_get_home_dir (), part); + file_select = GTK_FILE_SELECTION ( + gtk_file_selection_new ("Save Attachment")); + gtk_file_selection_set_filename (file_select, filename); + g_free (filename); + + gtk_signal_connect (GTK_OBJECT (file_select->ok_button), "clicked", + GTK_SIGNAL_FUNC (save_data_cb), part); + gtk_signal_connect_object (GTK_OBJECT (file_select->cancel_button), + "clicked", + GTK_SIGNAL_FUNC (gtk_widget_destroy), + GTK_OBJECT (file_select)); + + gtk_widget_show (GTK_WIDGET (file_select)); +} + +static void +launch_cb (GtkWidget *widget, gpointer user_data) +{ + CamelMimePart *part = gtk_object_get_data (user_data, "CamelMimePart"); + GnomeVFSMimeApplication *app; + GMimeContentField *content_type; + char *mime_type, *tmpl, *tmpdir, *filename, *argv[2]; + + content_type = camel_mime_part_get_content_type (part); + mime_type = gmime_content_field_get_mime_type (content_type); + app = gnome_vfs_mime_get_default_application (mime_type); + g_free (mime_type); + + g_return_if_fail (app != NULL); tmpl = g_strdup ("/tmp/evolution.XXXXXX"); #ifdef HAVE_MKDTEMP @@ -160,7 +231,7 @@ launch_external (const char *url, MailDisplay *md) return; } - argv[0] = (char *)command; + argv[0] = app->command; argv[1] = filename; gnome_execute_async (tmpdir, 2, argv); @@ -169,82 +240,86 @@ launch_external (const char *url, MailDisplay *md) } static void -save_data_cb (GtkWidget *widget, gpointer user_data) -{ - GtkFileSelection *file_select = (GtkFileSelection *) - gtk_widget_get_ancestor (widget, GTK_TYPE_FILE_SELECTION); - - write_data_to_file (user_data, - gtk_file_selection_get_filename (file_select), - FALSE); - gtk_widget_destroy (GTK_WIDGET (file_select)); -} - -static void -save_data (const char *cid, MailDisplay *md) +inline_cb (GtkWidget *widget, gpointer user_data) { - GtkFileSelection *file_select; - char *filename; - CamelMimePart *part; - - part = part_for_url (cid, md); - g_return_if_fail (part != NULL); - filename = make_safe_filename (g_get_home_dir (), part); + MailDisplay *md = gtk_object_get_data (user_data, "MailDisplay"); + CamelMimePart *part = gtk_object_get_data (user_data, "CamelMimePart"); - file_select = GTK_FILE_SELECTION (gtk_file_selection_new ("Save Attachment")); - gtk_file_selection_set_filename (file_select, filename); - g_free (filename); - - gtk_signal_connect (GTK_OBJECT (file_select->ok_button), "clicked", - GTK_SIGNAL_FUNC (save_data_cb), part); - gtk_signal_connect_object (GTK_OBJECT (file_select->cancel_button), - "clicked", - GTK_SIGNAL_FUNC (gtk_widget_destroy), - GTK_OBJECT (file_select)); + if (mail_part_is_inline (part)) + camel_mime_part_set_disposition (part, "attachment"); + else + camel_mime_part_set_disposition (part, "inline"); - gtk_widget_show (GTK_WIDGET (file_select)); + queue_redisplay (md); } static gboolean -idle_redisplay (gpointer data) +pixmap_press (GtkWidget *ebox, GdkEventButton *event, gpointer user_data) { - MailDisplay *md = data; + EPopupMenu menu[] = { + { N_("Save to Disk..."), NULL, + GTK_SIGNAL_FUNC (save_cb), 0 }, + { N_("Open in %s..."), NULL, + GTK_SIGNAL_FUNC (launch_cb), 1 }, + { N_("View Inline"), NULL, + GTK_SIGNAL_FUNC (inline_cb), 2 }, + { NULL, NULL, NULL, 0 } + }; + CamelMimePart *part; + MailMimeHandler *handler; + int mask; - md->idle_id = 0; - redisplay (md); - return FALSE; -} + if (event->button != 3) + return FALSE; -static void -queue_redisplay (MailDisplay *md) -{ - if (!md->idle_id) { - md->idle_id = g_idle_add_full (G_PRIORITY_LOW, idle_redisplay, - md, NULL); + part = gtk_object_get_data (user_data, "CamelMimePart"); + handler = mail_lookup_handler (gtk_object_get_data (user_data, + "mime_type")); + + /* External view item */ + if (handler && handler->application) { + menu[1].name = g_strdup_printf (menu[1].name, + handler->application->name); + } else { + menu[1].name = g_strdup_printf (menu[1].name, + N_("External Viewer")); + mask |= 1; } -} -static void -on_link_clicked (GtkHTML *html, const char *url, gpointer user_data) -{ - MailDisplay *md = user_data; + /* Inline view item */ + if (handler && handler->builtin) { + if (!mail_part_is_inline (part)) { + if (handler->component) { + OAF_Property *prop; + char *name; + + prop = oaf_server_info_prop_find ( + handler->component, "name"); + if (!prop) { + prop = oaf_server_info_prop_find ( + handler->component, + "description"); + } + if (prop && prop->v._d == OAF_P_STRING) + name = prop->v._u.value_string; + else + name = "bonobo"; + menu[2].name = g_strdup_printf ( + N_("View Inline (via %s)"), name); + } else + menu[2].name = g_strdup (menu[2].name); + } else + menu[2].name = g_strdup (N_("Hide")); + } else { + menu[2].name = g_strdup (menu[2].name); + mask |= 2; + } - if (!g_strncasecmp (url, "news:", 5) || - !g_strncasecmp (url, "nntp:", 5)) - g_warning ("Can't handle news URLs yet."); - else if (!g_strncasecmp (url, "mailto:", 7)) - send_to_url (url); - else if (!strncmp (url, "cid:", 4)) - save_data (url, md); - else if (!strncmp (url, "x-evolution-external:", 21)) - launch_external (url, md); - else if (!strcmp (url, "x-evolution-decode-pgp:")) { - g_datalist_set_data (md->data, "show_pgp", - GINT_TO_POINTER (1)); - queue_redisplay (md); - } else - gnome_url_show (url); -} + e_popup_menu_run (menu, event, mask, 0, user_data); + g_free (menu[1].name); + g_free (menu[2].name); + return TRUE; +} static gboolean on_object_requested (GtkHTML *html, GtkHTMLEmbedded *eb, gpointer data) @@ -261,28 +336,77 @@ on_object_requested (GtkHTML *html, GtkHTMLEmbedded *eb, gpointer data) GByteArray *ba; CamelStream *cstream; BonoboStream *bstream; + char *cid; - if (strncmp (eb->classid, "cid:", 4) != 0) + cid = eb->classid; + if (!strncmp (cid, "popup:", 6)) + cid += 6; + if (strncmp (cid, "cid:", 4) != 0) return FALSE; + urls = g_datalist_get_data (md->data, "urls"); g_return_val_if_fail (urls != NULL, FALSE); - medium = g_hash_table_lookup (urls, eb->classid); + medium = g_hash_table_lookup (urls, cid); g_return_val_if_fail (CAMEL_IS_MEDIUM (medium), FALSE); - wrapper = camel_medium_get_content_object (medium); + + if (cid != eb->classid) { + /* This is a part wrapper */ + const char *icon; + GtkWidget *pixmap, *ebox; + + icon = gnome_vfs_mime_get_value (eb->type, "icon-filename"); + if (icon) { + pixmap = gnome_pixmap_new_from_file_at_size (icon, + 24, 24); + } else { + char *filename; + + filename = gnome_pixmap_file ("gnome-unknown.png"); + pixmap = gnome_pixmap_new_from_file_at_size (filename, + 24, 24); + g_free (filename); + } + + ebox = gtk_event_box_new (); + gtk_widget_set_sensitive (GTK_WIDGET (ebox), TRUE); + gtk_widget_add_events (GTK_WIDGET (ebox), + GDK_BUTTON_PRESS_MASK); + gtk_signal_connect (GTK_OBJECT (ebox), "button_press_event", + GTK_SIGNAL_FUNC (pixmap_press), ebox); + gtk_object_set_data (GTK_OBJECT (ebox), "MailDisplay", md); + gtk_object_set_data (GTK_OBJECT (ebox), "CamelMimePart", + medium); + gtk_object_set_data_full (GTK_OBJECT (ebox), "mime_type", + g_strdup (eb->type), + (GDestroyNotify)g_free); + + gtk_container_add (GTK_CONTAINER (ebox), pixmap); + gtk_widget_show_all (ebox); + gtk_container_add (GTK_CONTAINER (eb), ebox); + return TRUE; + } component = gnome_vfs_mime_get_default_component (eb->type); if (!component) return FALSE; embedded = bonobo_widget_new_subdoc (component->iid, NULL); - if (!embedded) + if (embedded) { + /* FIXME: as of bonobo 0.18, there's an extra + * client_site dereference in the BonoboWidget + * destruction path that we have to balance out to + * prevent problems. + */ + bonobo_object_ref (bonobo_widget_get_client_site ( + BONOBO_WIDGET (embedded))); + } else embedded = bonobo_widget_new_control (component->iid, NULL); CORBA_free (component); if (!embedded) return FALSE; - server = bonobo_widget_get_server (BONOBO_WIDGET (embedded)); + server = bonobo_widget_get_server (BONOBO_WIDGET (embedded)); persist = (Bonobo_PersistStream) bonobo_object_client_query_interface ( server, "IDL:Bonobo/PersistStream:1.0", NULL); if (persist == CORBA_OBJECT_NIL) { @@ -293,6 +417,7 @@ on_object_requested (GtkHTML *html, GtkHTMLEmbedded *eb, gpointer data) /* Write the data to a CamelStreamMem... */ ba = g_byte_array_new (); cstream = camel_stream_mem_new_with_byte_array (ba); + wrapper = camel_medium_get_content_object (medium); camel_data_wrapper_write_to_stream (wrapper, cstream); /* ...convert the CamelStreamMem to a BonoboStreamMem... */ @@ -422,9 +547,15 @@ clear_data (CamelObject *object, gpointer event_data, gpointer user_data) } static void -redisplay (MailDisplay *md) +redisplay (MailDisplay *md, gboolean unscroll) { GtkAdjustment *adj; + gfloat oldv; + + if (!unscroll) { + adj = e_scroll_frame_get_vadjustment (md->scroll); + oldv = adj->value; + } md->stream = gtk_html_begin (md->html); mail_html_write (md->html, md->stream, "%s%s", HTML_HEADER, @@ -439,13 +570,17 @@ redisplay (MailDisplay *md) gtk_html_end (md->html, md->stream, GTK_HTML_STREAM_OK); md->stream = NULL; - adj = e_scroll_frame_get_vadjustment (md->scroll); - gtk_adjustment_set_value (adj, 0); - e_scroll_frame_set_vadjustment (md->scroll, adj); - - adj = e_scroll_frame_get_hadjustment (md->scroll); - gtk_adjustment_set_value (adj, 0); - e_scroll_frame_set_hadjustment (md->scroll, adj); + if (unscroll) { + adj = e_scroll_frame_get_hadjustment (md->scroll); + gtk_adjustment_set_value (adj, 0); + e_scroll_frame_set_hadjustment (md->scroll, adj); + } else { + adj = e_scroll_frame_get_vadjustment (md->scroll); + if (oldv < adj->upper) { + gtk_adjustment_set_value (adj, oldv); + e_scroll_frame_set_vadjustment (md->scroll, adj); + } + } } /** @@ -472,7 +607,7 @@ mail_display_set_message (MailDisplay *md, CamelMedium *medium) md->current_message = (CamelMimeMessage*)medium; g_datalist_init (md->data); - redisplay (md); + redisplay (md, TRUE); if (medium) { camel_object_hook_event (CAMEL_OBJECT (medium), "finalize", clear_data, *(md->data)); diff --git a/mail/mail-format.c b/mail/mail-format.c index 3167024200..21e9773353 100644 --- a/mail/mail-format.c +++ b/mail/mail-format.c @@ -31,6 +31,7 @@ #include #include #include +#include #include /* for isprint */ #include /* for strstr */ @@ -69,9 +70,6 @@ static gboolean handle_multipart_appledouble (CamelMimePart *part, static gboolean handle_multipart_encrypted (CamelMimePart *part, const char *mime_type, MailDisplay *md); -static gboolean handle_audio (CamelMimePart *part, - const char *mime_type, - MailDisplay *md); static gboolean handle_message_rfc822 (CamelMimePart *part, const char *mime_type, MailDisplay *md); @@ -79,15 +77,9 @@ static gboolean handle_message_external_body (CamelMimePart *part, const char *mime_type, MailDisplay *md); -static gboolean handle_unknown_type (CamelMimePart *part, - const char *mime_type, - MailDisplay *md); static gboolean handle_via_bonobo (CamelMimePart *part, const char *mime_type, MailDisplay *md); -static gboolean handle_via_external (CamelMimePart *part, - const char *mime_type, - MailDisplay *md); /* writes the header info for a mime message into an html stream */ static void write_headers (CamelMimeMessage *message, MailDisplay *md); @@ -95,7 +87,6 @@ static void write_headers (CamelMimeMessage *message, MailDisplay *md); /* dispatch html printing via mimetype */ static gboolean call_handler_function (CamelMimePart *part, MailDisplay *md); - static void free_url (gpointer key, gpointer value, gpointer data) { @@ -226,21 +217,13 @@ get_url_for_icon (const char *icon_name, MailDisplay *md) } - -/* We're maintaining a hashtable of mimetypes -> functions; - * Those functions have the following signature... - */ -typedef gboolean (*mime_handler_fn) (CamelMimePart *part, - const char *mime_type, - MailDisplay *md); - -static GHashTable *mime_function_table, *mime_fallback_table; +static GHashTable *mime_handler_table, *mime_function_table; static void -setup_function_table (void) +setup_mime_tables (void) { + mime_handler_table = g_hash_table_new (g_str_hash, g_str_equal); mime_function_table = g_hash_table_new (g_str_hash, g_str_equal); - mime_fallback_table = g_hash_table_new (g_str_hash, g_str_equal); g_hash_table_insert (mime_function_table, "text/plain", handle_text_plain); @@ -251,11 +234,34 @@ setup_function_table (void) g_hash_table_insert (mime_function_table, "text/html", handle_text_html); - g_hash_table_insert (mime_function_table, "image/*", + g_hash_table_insert (mime_function_table, "image/gif", + handle_image); + g_hash_table_insert (mime_function_table, "image/jpeg", + handle_image); + g_hash_table_insert (mime_function_table, "image/png", + handle_image); + g_hash_table_insert (mime_function_table, "image/x-png", + handle_image); + g_hash_table_insert (mime_function_table, "image/tiff", + handle_image); + g_hash_table_insert (mime_function_table, "image/x-bmp", + handle_image); + g_hash_table_insert (mime_function_table, "image/bmp", + handle_image); + g_hash_table_insert (mime_function_table, "image/x-cmu-raster", + handle_image); + g_hash_table_insert (mime_function_table, "image/x-ico", + handle_image); + g_hash_table_insert (mime_function_table, "image/x-portable-anymap", + handle_image); + g_hash_table_insert (mime_function_table, "image/x-portable-bitmap", + handle_image); + g_hash_table_insert (mime_function_table, "image/x-portable-graymap", + handle_image); + g_hash_table_insert (mime_function_table, "image/x-portable-pixmap", + handle_image); + g_hash_table_insert (mime_function_table, "image/x-xpixmap", handle_image); - - g_hash_table_insert (mime_function_table, "audio/*", - handle_audio); g_hash_table_insert (mime_function_table, "message/rfc822", handle_message_rfc822); @@ -279,97 +285,221 @@ setup_function_table (void) * as text/plain (as long as you recognize the character set), * and unrecognized multipart subtypes as multipart/mixed. */ - g_hash_table_insert (mime_fallback_table, "text/*", + g_hash_table_insert (mime_function_table, "text/*", handle_text_plain); g_hash_table_insert (mime_function_table, "multipart/*", handle_multipart_mixed); } -static mime_handler_fn -lookup_handler (const char *mime_type, gboolean *generic) +static gboolean +component_supports (OAF_ServerInfo *component, const char *mime_type) +{ + OAF_Property *prop; + CORBA_sequence_CORBA_string stringv; + int i; + + prop = oaf_server_info_prop_find (component, + "bonobo:supported_mime_types"); + if (!prop || prop->v._d != OAF_P_STRINGV) + return FALSE; + + stringv = prop->v._u.value_stringv; + for (i = 0; i < stringv._length; i++) { + if (!g_strcasecmp (mime_type, stringv._buffer[i])) + return TRUE; + } + return FALSE; +} + +/** + * mail_lookup_handler: + * @mime_type: a MIME type + * + * Looks up the MIME type in its own tables and GNOME-VFS's and returns + * a MailMimeHandler structure detailing the component, application, + * and built-in handlers (if any) for that MIME type. (If the component + * is non-%NULL, the built-in handler will always be handle_via_bonobo().) + * The MailMimeHandler's @generic field is set if the match was for the + * MIME supertype rather than the exact type. + * + * Return value: a MailMimeHandler (which should not be freed), or %NULL + * if no handlers are available. + **/ +MailMimeHandler * +mail_lookup_handler (const char *mime_type) { - mime_handler_fn handler_function; + MailMimeHandler *handler; char *mime_type_main; - GnomeVFSMimeAction *action; - if (mime_function_table == NULL) - setup_function_table (); + if (mime_handler_table == NULL) + setup_mime_tables (); + + /* See if we've already found it. */ + handler = g_hash_table_lookup (mime_handler_table, mime_type); + if (handler) + return handler; + + /* No. Create a new one and look up application and full type + * handler. If we find a builtin, create the handler and + * register it. + */ + handler = g_new0 (MailMimeHandler, 1); + handler->application = + gnome_vfs_mime_get_default_application (mime_type); + handler->builtin = + g_hash_table_lookup (mime_function_table, mime_type); + + if (handler->builtin) { + handler->generic = FALSE; + goto reg; + } + + /* Try for a exact component match. */ + handler->component = gnome_vfs_mime_get_default_component (mime_type); + if (handler->component && + component_supports (handler->component, mime_type)) { + handler->generic = FALSE; + handler->builtin = handle_via_bonobo; + goto reg; + } + /* Try for a generic builtin match. */ mime_type_main = g_strdup_printf ("%.*s/*", (int)strcspn (mime_type, "/"), mime_type); + handler->builtin = g_hash_table_lookup (mime_function_table, + mime_type_main); + g_free (mime_type_main); - /* OK. There are 6 possibilities, which we try in this order: - * 1) full match in the main table - * 2) partial match in the main table - * 3) full match in gnome_vfs_mime_* - * 4) full match in the fallback table - * 5) partial match in the fallback table - * 6) partial match in gnome_vfs_mime_* - * - * Of these, 1-4 are considered exact matches, and 5 and 6 are - * considered generic. - */ - - /* Check for full match in mime_function_table. */ - handler_function = g_hash_table_lookup (mime_function_table, - mime_type); - if (!handler_function) { - handler_function = g_hash_table_lookup (mime_function_table, - mime_type_main); - if (handler_function) { - /* Optimize this for the next time through. */ - g_hash_table_insert (mime_function_table, - g_strdup (mime_type), - handler_function); + if (handler->builtin) { + handler->generic = TRUE; + if (handler->component) { + CORBA_free (handler->component); + handler->component = NULL; } + goto reg; } - if (handler_function) { - g_free (mime_type_main); - *generic = FALSE; - return handler_function; + /* Try for a generic component match. */ + if (handler->component) { + handler->generic = TRUE; + handler->builtin = handle_via_bonobo; + goto reg; } - action = gnome_vfs_mime_get_default_action_without_fallback (mime_type); - if (action) { - if (action->action_type == GNOME_VFS_MIME_ACTION_TYPE_COMPONENT) - handler_function = handle_via_bonobo; - else - handler_function = handle_via_external; - - /* Optimize this for the next time through. */ - g_hash_table_insert (mime_function_table, - g_strdup (mime_type), handler_function); - g_free (mime_type_main); - gnome_vfs_mime_action_free (action); - *generic = FALSE; - return handler_function; + /* If we at least got an application, use that. */ + if (handler->application) { + handler->generic = TRUE; + goto reg; } - handler_function = g_hash_table_lookup (mime_fallback_table, - mime_type); - if (handler_function) - *generic = FALSE; - else { - handler_function = g_hash_table_lookup (mime_fallback_table, - mime_type_main); - if (!handler_function) { - action = gnome_vfs_mime_get_default_action (mime_type_main); - if (action) { - if (action->action_type == - GNOME_VFS_MIME_ACTION_TYPE_COMPONENT) - handler_function = handle_via_bonobo; - else - handler_function = handle_via_external; - gnome_vfs_mime_action_free (action); - } - } - *generic = TRUE; + /* Nada. */ + g_free (handler); + return NULL; + + reg: + g_hash_table_insert (mime_handler_table, g_strdup (mime_type), + handler); + return handler; +} + +/* An "anonymous" MIME part is one that we shouldn't call attention + * to the existence of, but simply display. + */ +static gboolean +is_anonymous (CamelMimePart *part, const char *mime_type) +{ + if (!g_strncasecmp (mime_type, "multipart/", 10) || + !g_strncasecmp (mime_type, "message/", 8)) + return TRUE; + + if (!g_strncasecmp (mime_type, "text/", 5) && + !camel_mime_part_get_filename (part)) + return TRUE; + + return FALSE; +} + +/** + * mail_part_is_inline: + * @part: a CamelMimePart + * + * Return value: whether or not the part should/will be displayed inline. + **/ +gboolean +mail_part_is_inline (CamelMimePart *part) +{ + const char *disposition; + GMimeContentField *content_type; + const char *mime_type; + + /* If it has an explicit disposition, return that. */ + disposition = camel_mime_part_get_disposition (part); + if (disposition) + return g_strcasecmp (disposition, "inline") == 0; + + /* Certain types should default to inline. FIXME: this should + * be customizable. + */ + content_type = camel_mime_part_get_content_type (part); + mime_type = gmime_content_field_get_mime_type (content_type); + if (!g_strncasecmp (mime_type, "message/", 8)) + return TRUE; + + /* Otherwise, display it inline if it's "anonymous", and + * as an attachment otherwise. + */ + return is_anonymous (part, mime_type); +} + +static void +attachment_header (CamelMimePart *part, const char *mime_type, + gboolean is_inline, MailDisplay *md) +{ + const char *info; + char *htmlinfo; + + /* No header for anonymous inline parts. */ + if (is_inline && is_anonymous (part, mime_type)) + return; + + /* Start the table, create the pop-up object. */ + mail_html_write (md->html, md->stream, "
" + "" + "", + get_cid (part, md), mime_type); + + /* Write the MIME type */ + info = gnome_vfs_mime_get_value (mime_type, "description"); + htmlinfo = e_text_to_html (info ? info : mime_type, 0); + mail_html_write (md->html, md->stream, "%s attachment", htmlinfo); + g_free (htmlinfo); + + /* Write the name, if we have it. */ + info = camel_mime_part_get_filename (part); + if (info) { + htmlinfo = e_text_to_html (info, 0); + mail_html_write (md->html, md->stream, " (%s)", htmlinfo); + g_free (htmlinfo); } - g_free (mime_type_main); - return handler_function; + /* Write a description, if we have one. */ + info = camel_mime_part_get_description (part); + if (info) { + htmlinfo = e_text_to_html (info, E_TEXT_TO_HTML_CONVERT_URLS); + mail_html_write (md->html, md->stream, ", \"%s\"", htmlinfo); + g_free (htmlinfo); + } + +#if 0 + /* Describe the click action, if any. */ + if (action) { + mail_html_write (md->html, md->stream, + "
Click on the icon to %s.", action); + } +#endif + + mail_html_write (md->html, md->stream, "
"); } static gboolean @@ -377,18 +507,31 @@ call_handler_function (CamelMimePart *part, MailDisplay *md) { CamelDataWrapper *wrapper; char *mime_type; - mime_handler_fn handler_function = NULL; - gboolean generic, output; + MailMimeHandler *handler; + gboolean output, is_inline; wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (part)); mime_type = camel_data_wrapper_get_mime_type (wrapper); g_strdown (mime_type); - handler_function = lookup_handler (mime_type, &generic); - if (handler_function) - output = (*handler_function) (part, mime_type, md); + handler = mail_lookup_handler (mime_type); + if (!handler) { + char *id_type; + + id_type = mail_identify_mime_part (part); + if (id_type) { + g_free (mime_type); + mime_type = id_type; + handler = mail_lookup_handler (id_type); + } + } + + is_inline = mail_part_is_inline (part); + attachment_header (part, mime_type, is_inline, md); + if (handler && handler->builtin && is_inline) + output = (*handler->builtin) (part, mime_type, md); else - output = handle_unknown_type (part, mime_type, md); + output = TRUE; g_free (mime_type); return output; @@ -528,7 +671,7 @@ handle_text_plain (CamelMimePart *part, const char *mime_type, text = get_data_wrapper_text (wrapper); if (!text) return FALSE; - + /* Check for RFC 2646 flowed text. */ type = camel_mime_part_get_content_type (part); format = gmime_content_field_get_parameter (type, "format"); @@ -655,6 +798,7 @@ fake_mime_part_from_data (const char *data, int len, const char *type) part = camel_mime_part_new (); camel_medium_set_content_object (CAMEL_MEDIUM (part), wrapper); camel_object_unref (CAMEL_OBJECT (wrapper)); + camel_mime_part_set_disposition (part, "inline"); return part; } @@ -1177,7 +1321,7 @@ find_preferred_alternative (CamelMultipart *multipart, gboolean want_plain) { int i, nparts; CamelMimePart *preferred_part = NULL; - gboolean generic; + MailMimeHandler *handler; nparts = camel_multipart_get_number (multipart); for (i = 0; i < nparts; i++) { @@ -1188,8 +1332,8 @@ find_preferred_alternative (CamelMultipart *multipart, gboolean want_plain) g_strdown (mime_type); if (want_plain && !strcmp (mime_type, "text/plain")) return part; - if (lookup_handler (mime_type, &generic) && - (!preferred_part || !generic)) + handler = mail_lookup_handler (mime_type); + if (handler && (!preferred_part || !handler->generic)) preferred_part = part; g_free (mime_type); } @@ -1213,7 +1357,7 @@ handle_multipart_alternative (CamelMimePart *part, const char *mime_type, if (mime_part) return call_handler_function (mime_part, md); else - return handle_unknown_type (part, mime_type, md); + return handle_multipart_mixed (part, mime_type, md); } /* RFC 1740 */ @@ -1236,82 +1380,6 @@ handle_multipart_appledouble (CamelMimePart *part, const char *mime_type, return call_handler_function (part, md); } -static void -handle_mystery (CamelMimePart *part, MailDisplay *md, - const char *url, const char *icon_name, const char *id, - const char *action) -{ - const char *info; - char *htmlinfo; - - mail_html_write (md->html, md->stream, "
"); - - /* Draw the icon, surrounded by an if we have a URL, - * or a plain inactive border if not. - */ - if (url) { - mail_html_write (md->html, md->stream, - "", url); - } else { - mail_html_write (md->html, md->stream, - "
"); - } - mail_html_write (md->html, md->stream, "", - get_url_for_icon (icon_name, md)); - - if (url) - mail_html_write (md->html, md->stream, ""); - else - mail_html_write (md->html, md->stream, "
"); - mail_html_write (md->html, md->stream, "
%s
", id); - - /* Write a description, if we have one. */ - info = camel_mime_part_get_description (part); - if (info) { - htmlinfo = e_text_to_html (info, E_TEXT_TO_HTML_CONVERT_URLS); - mail_html_write (md->html, md->stream, "Description: %s
", - htmlinfo); - g_free (htmlinfo); - } - - /* Write the name, if we have it. */ - info = camel_mime_part_get_filename (part); - if (info) { - htmlinfo = e_text_to_html (info, 0); - mail_html_write (md->html, md->stream, "Name: %s
", - htmlinfo); - g_free (htmlinfo); - } - - /* Describe the click action, if any. */ - if (action) { - mail_html_write (md->html, md->stream, - "
Click on the icon to %s.", action); - } - - mail_html_write (md->html, md->stream, "
"); -} - -static gboolean -handle_audio (CamelMimePart *part, const char *mime_type, MailDisplay *md) -{ - char *id; - const char *desc; - - desc = gnome_vfs_mime_get_value (mime_type, "description"); - if (desc) - id = g_strdup_printf ("%s data", desc); - else { - id = g_strdup_printf ("Audio data in \"%s\" format.", - mime_type); - } - handle_mystery (part, md, get_cid (part, md), "gnome-audio2.png", - id, "play it"); - g_free (id); - - return TRUE; -} - static gboolean handle_message_rfc822 (CamelMimePart *part, const char *mime_type, MailDisplay *md) @@ -1429,103 +1497,23 @@ handle_message_external_body (CamelMimePart *part, const char *mime_type, desc = g_strdup ("Malformed external-body part."); } +#if 0 /* FIXME */ handle_mystery (part, md, url, "gnome-globe.png", desc, url ? "open it in a browser" : NULL); +#endif g_free (desc); g_free (url); return TRUE; } -static gboolean -handle_undisplayable (CamelMimePart *part, const char *mime_type, - MailDisplay *md) -{ - const char *desc; - char *id; - - desc = gnome_vfs_mime_get_value (mime_type, "description"); - if (desc) - id = g_strdup (desc); - else - id = g_strdup_printf ("Data of type \"%s\".", mime_type); - handle_mystery (part, md, get_cid (part, md), "gnome-question.png", - id, "save it to disk"); - g_free (id); - - return TRUE; -} - -static gboolean -handle_unknown_type (CamelMimePart *part, const char *mime_type, - MailDisplay *md) -{ - char *type; - - /* Don't give up quite yet. */ - type = mail_identify_mime_part (part); - if (type) { - mime_handler_fn handler_function; - gboolean generic, output; - - handler_function = lookup_handler (type, &generic); - if (handler_function && - handler_function != handle_unknown_type) { - output = (*handler_function) (part, type, md); - g_free (type); - return output; - } - } else - type = g_strdup (mime_type); - - /* OK. Give up. */ - handle_undisplayable (part, type, md); - g_free (type); - - return TRUE; -} - static gboolean handle_via_bonobo (CamelMimePart *part, const char *mime_type, MailDisplay *md) { mail_html_write (md->html, md->stream, - "", + "", get_cid (part, md), mime_type); - - /* Call handle_undisplayable to output its HTML inside the - * ... . It will only be displayed if the - * object loading fails. - */ - handle_undisplayable (part, mime_type, md); - - mail_html_write (md->html, md->stream, ""); - - return TRUE; -} - -static gboolean -handle_via_external (CamelMimePart *part, const char *mime_type, - MailDisplay *md) -{ - GnomeVFSMimeApplication *app; - const char *desc, *icon; - char *action, *url; - - app = gnome_vfs_mime_get_default_application (mime_type); - g_return_val_if_fail (app != NULL, FALSE); - - desc = gnome_vfs_mime_get_value (mime_type, "description"); - icon = gnome_vfs_mime_get_value (mime_type, "icon-filename"); - if (!icon) - icon = "gnome-unknown.png"; - action = g_strdup_printf ("open the file in %s", app->name); - url = g_strdup_printf ("x-evolution-external:%s", app->command); - handle_mystery (part, md, url, icon, desc, action); - add_url (url, part, md); - - g_free (action); - return TRUE; } @@ -1536,7 +1524,7 @@ mail_get_message_body (CamelDataWrapper *data, gboolean want_plain, gboolean *is CamelMimePart *subpart; int i, nparts; char *subtext, *old; - const char *boundary, *disp; + const char *boundary; char *text = NULL; GMimeContentField *mime_type; @@ -1587,8 +1575,7 @@ mail_get_message_body (CamelDataWrapper *data, gboolean want_plain, gboolean *is for (i = 0; i < nparts; i++) { subpart = camel_multipart_get_part (mp, i); - disp = camel_mime_part_get_disposition (subpart); - if (disp && g_strcasecmp (disp, "inline") != 0) + if (!mail_part_is_inline (subpart)) continue; data = camel_medium_get_content_object ( diff --git a/mail/mail.h b/mail/mail.h index 4b398a0f4b..029a00f603 100644 --- a/mail/mail.h +++ b/mail/mail.h @@ -18,6 +18,7 @@ */ #include +#include #include "camel/camel.h" #include "composer/e-msg-composer.h" #include "mail-config.h" @@ -43,6 +44,19 @@ char *mail_crypto_openpgp_clearsign (const char *plaintext, void mail_format_mime_message (CamelMimeMessage *mime_message, MailDisplay *md); +typedef gboolean (*MailMimeHandlerFn) (CamelMimePart *part, + const char *mime_type, + MailDisplay *md); +typedef struct { + gboolean generic; + OAF_ServerInfo *component; + GnomeVFSMimeApplication *application; + MailMimeHandlerFn builtin; +} MailMimeHandler; +MailMimeHandler *mail_lookup_handler (const char *mime_type); + +gboolean mail_part_is_inline (CamelMimePart *part); + EMsgComposer *mail_generate_reply (CamelMimeMessage *mime_message, gboolean to_all); -- cgit v1.2.3