X-Git-Url: http://git.maemo.org/git/?a=blobdiff_plain;f=src%2Fmodest-tny-msg-view.c;h=daeeca4f23a1a927297144492f63c0e63afe0473;hb=e010a0ade7cf553914b9288887b622e20cf8a882;hp=b70bd7987c6713ebaa47368917b9d0715baa2181;hpb=4796c2c2a348402649bdd01eb45dc5f547f4bcdd;p=modest diff --git a/src/modest-tny-msg-view.c b/src/modest-tny-msg-view.c index b70bd79..daeeca4 100644 --- a/src/modest-tny-msg-view.c +++ b/src/modest-tny-msg-view.c @@ -1,14 +1,45 @@ -/* modest-tny-msg-view.c */ - -/* insert (c)/licensing information) */ +/* Copyright (c) 2006, Nokia Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Nokia Corporation nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ -#include "modest-tny-msg-view.h" -#include "modest-tny-stream-gtkhtml.h" #include #include #include #include #include +#include +#include +#include + +#include "modest-tny-msg-view.h" +#include "modest-tny-stream-gtkhtml.h" +#include "modest-tny-msg-actions.h" + /* 'private'/'protected' functions */ static void modest_tny_msg_view_class_init (ModestTnyMsgViewClass *klass); @@ -17,7 +48,11 @@ static void modest_tny_msg_view_finalize (GObject *obj); static GSList* get_url_matches (GString *txt); - +static gboolean on_link_clicked (GtkWidget *widget, const gchar *uri, + ModestTnyMsgView *msg_view); +static gboolean on_url_requested (GtkWidget *widget, const gchar *uri, + GtkHTMLStream *stream, + ModestTnyMsgView *msg_view); /* * we need these regexps to find URLs in plain text e-mails @@ -26,9 +61,13 @@ typedef struct _UrlMatchPattern UrlMatchPattern; struct _UrlMatchPattern { gchar *regex; regex_t *preg; - gchar *prefix; + gchar *prefix; + }; -#define MAIL_VIEWER_URL_MATCH_PATTERNS {\ + +#define ATT_PREFIX "att:" + +#define MAIL_VIEWER_URL_MATCH_PATTERNS { \ { "(file|http|ftp|https)://[-A-Za-z0-9_$.+!*(),;:@%&=?/~#]+[-A-Za-z0-9_$%&=?/~#]",\ NULL, NULL },\ { "www\\.[-a-z0-9.]+[-a-z0-9](:[0-9]*)?(/[-A-Za-z0-9_$.+!*(),;:@%&=?/~#]*[^]}\\),?!;:\"]?)?",\ @@ -46,14 +85,15 @@ struct _UrlMatchPattern { /* list my signals */ enum { - /* MY_SIGNAL_1, */ - /* MY_SIGNAL_2, */ + LINK_CLICKED_SIGNAL, + ATTACHMENT_CLICKED_SIGNAL, LAST_SIGNAL }; typedef struct _ModestTnyMsgViewPrivate ModestTnyMsgViewPrivate; struct _ModestTnyMsgViewPrivate { GtkWidget *gtkhtml; + const TnyMsgIface *msg; }; #define MODEST_TNY_MSG_VIEW_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE((o), \ MODEST_TYPE_TNY_MSG_VIEW, \ @@ -62,7 +102,7 @@ struct _ModestTnyMsgViewPrivate { static GtkContainerClass *parent_class = NULL; /* uncomment the following if you have defined any signals */ -/* static guint signals[LAST_SIGNAL] = {0}; */ +static guint signals[LAST_SIGNAL] = {0}; GType modest_tny_msg_view_get_type (void) @@ -97,6 +137,26 @@ modest_tny_msg_view_class_init (ModestTnyMsgViewClass *klass) gobject_class->finalize = modest_tny_msg_view_finalize; g_type_class_add_private (gobject_class, sizeof(ModestTnyMsgViewPrivate)); + + + signals[LINK_CLICKED_SIGNAL] = + g_signal_new ("link_clicked", + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET(ModestTnyMsgViewClass, link_clicked), + NULL, NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, 1, G_TYPE_STRING); + + signals[ATTACHMENT_CLICKED_SIGNAL] = + g_signal_new ("attachment_clicked", + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET(ModestTnyMsgViewClass, attachment_clicked), + NULL, NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, 1, G_TYPE_INT); + } static void @@ -104,50 +164,142 @@ modest_tny_msg_view_init (ModestTnyMsgView *obj) { ModestTnyMsgViewPrivate *priv; - gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(obj), - GTK_POLICY_AUTOMATIC, - GTK_POLICY_AUTOMATIC); - - priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE(obj); - priv->gtkhtml = gtk_html_new(); + + priv->msg = NULL; + priv->gtkhtml = gtk_html_new(); + gtk_html_set_editable (GTK_HTML(priv->gtkhtml), FALSE); - gtk_html_allow_selection (GTK_HTML(priv->gtkhtml), TRUE); - gtk_html_set_caret_mode (GTK_HTML(priv->gtkhtml), TRUE); - gtk_html_set_blocking (GTK_HTML(priv->gtkhtml), FALSE); - gtk_html_set_images_blocking (GTK_HTML(priv->gtkhtml), FALSE); + gtk_html_allow_selection (GTK_HTML(priv->gtkhtml), TRUE); + gtk_html_set_caret_mode (GTK_HTML(priv->gtkhtml), FALSE); + gtk_html_set_blocking (GTK_HTML(priv->gtkhtml), FALSE); + gtk_html_set_images_blocking (GTK_HTML(priv->gtkhtml), FALSE); - gtk_widget_show (priv->gtkhtml); - gtk_container_add (GTK_CONTAINER(obj), priv->gtkhtml); + g_signal_connect (G_OBJECT(priv->gtkhtml), "link_clicked", + G_CALLBACK(on_link_clicked), obj); + + g_signal_connect (G_OBJECT(priv->gtkhtml), "url_requested", + G_CALLBACK(on_url_requested), obj); } static void modest_tny_msg_view_finalize (GObject *obj) { - ModestTnyMsgViewPrivate *priv; - priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE(obj); - - if (priv->gtkhtml) - g_object_unref (G_OBJECT(priv->gtkhtml)); + /* TODO! */ } GtkWidget* -modest_tny_msg_view_new (TnyMsgIface *msg) +modest_tny_msg_view_new (const TnyMsgIface *msg) { GObject *obj; ModestTnyMsgView* self; - + ModestTnyMsgViewPrivate *priv; + obj = G_OBJECT(g_object_new(MODEST_TYPE_TNY_MSG_VIEW, NULL)); self = MODEST_TNY_MSG_VIEW(obj); + priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE (self); + + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(self), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + + if (priv->gtkhtml) + gtk_container_add (GTK_CONTAINER(obj), priv->gtkhtml); if (msg) modest_tny_msg_view_set_message (self, msg); - + return GTK_WIDGET(self); } +static gboolean +on_link_clicked (GtkWidget *widget, const gchar *uri, ModestTnyMsgView *msg_view) +{ + + int index; + + g_return_val_if_fail (msg_view, FALSE); + + /* is it an attachment? */ + if (g_str_has_prefix(uri, ATT_PREFIX)) { + + index = atoi (uri + strlen(ATT_PREFIX)); + + if (index == 0) { + /* index is 1-based, so 0 indicates an error */ + g_printerr ("modest: invalid attachment id: %s\n", uri); + return FALSE; + } + + g_signal_emit (G_OBJECT(msg_view), signals[ATTACHMENT_CLICKED_SIGNAL], + 0, index); + return FALSE; + } + + g_signal_emit (G_OBJECT(msg_view), signals[LINK_CLICKED_SIGNAL], 0, uri); + + return FALSE; +} + + +static TnyMsgMimePartIface * +find_cid_image (const TnyMsgIface *msg, const gchar *cid) +{ + TnyMsgMimePartIface *part = NULL; + const TnyListIface *parts; + TnyIteratorIface *iter; + + g_return_val_if_fail (msg, NULL); + g_return_val_if_fail (cid, NULL); + + parts = tny_msg_iface_get_parts ((TnyMsgIface*)msg); // FIXME: tinymail + iter = tny_list_iface_create_iterator ((TnyListIface*)parts); + + while (!tny_iterator_iface_is_done(iter)) { + const gchar *part_cid; + part = TNY_MSG_MIME_PART_IFACE(tny_iterator_iface_current(iter)); + part_cid = tny_msg_mime_part_iface_get_content_id (part); + + if (part_cid && strcmp (cid, part_cid) == 0) + break; + + part = NULL; + tny_iterator_iface_next (iter); + } + + g_object_unref (G_OBJECT(iter)); + return part; +} + + +static gboolean +on_url_requested (GtkWidget *widget, const gchar *uri, + GtkHTMLStream *stream, + ModestTnyMsgView *msg_view) +{ + ModestTnyMsgViewPrivate *priv; + priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE (msg_view); + + if (g_str_has_prefix (uri, "cid:")) { + /* +4 ==> skip "cid:" */ + const TnyMsgMimePartIface *part = find_cid_image (priv->msg, uri + 4); + if (!part) { + g_printerr ("modest: '%s' not found\n", uri + 4); + gtk_html_stream_close (stream, GTK_HTML_STREAM_ERROR); + } else { + TnyStreamIface *tny_stream = + TNY_STREAM_IFACE(modest_tny_stream_gtkhtml_new(stream)); + // FIXME: tinymail + tny_msg_mime_part_iface_decode_to_stream ((TnyMsgMimePartIface*)part, + tny_stream); + gtk_html_stream_close (stream, GTK_HTML_STREAM_OK); + } + } + + return TRUE; +} typedef struct { @@ -157,6 +309,60 @@ typedef struct { } url_match_t; + +/* render the attachments as hyperlinks in html */ +static gchar* +attachments_as_html (ModestTnyMsgView *self, const TnyMsgIface *msg) +{ + ModestTnyMsgViewPrivate *priv; + GString *appendix; + const TnyListIface *parts; + TnyIteratorIface *iter; + gchar *html; + int index = 0; + + if (!msg) + return NULL; + + priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE (self); + parts = tny_msg_iface_get_parts ((TnyMsgIface*)msg); + // FIXME: tinymail + iter = tny_list_iface_create_iterator ((TnyListIface*)parts); + + appendix= g_string_new (""); + + while (!tny_iterator_iface_is_done(iter)) { + TnyMsgMimePartIface *part; + + ++index; /* attachment numbers are 1-based */ + + part = TNY_MSG_MIME_PART_IFACE(tny_iterator_iface_current (iter)); + + if (tny_msg_mime_part_iface_is_attachment (part)) { + + const gchar *filename = tny_msg_mime_part_iface_get_filename(part); + if (!filename) + filename = _("attachment"); + + g_string_append_printf (appendix, "%s \n", + ATT_PREFIX, index, filename); + } + tny_iterator_iface_next (iter); + } + g_object_unref (G_OBJECT(iter)); + + if (appendix->len == 0) + return g_string_free (appendix, TRUE); + + html = g_strdup_printf ("%s: %s\n
", + _("Attachments"), appendix->str); + g_string_free (appendix, TRUE); + + return html; +} + + + static void hyperlinkify_plain_text (GString *txt) { @@ -181,8 +387,10 @@ hyperlinkify_plain_text (GString *txt) g_free (url); g_free (repl); - + + g_free (cursor->data); } + g_slist_free (match_list); } @@ -242,7 +450,7 @@ convert_to_html (const gchar *data) } } } - + g_string_append (html, ""); hyperlinkify_plain_text (html); @@ -329,60 +537,38 @@ get_url_matches (GString *txt) return match_list; } -static gboolean -fill_gtkhtml_with_txt (GtkHTML* gtkhtml, const gchar* txt) -{ - gchar *html; - - g_return_val_if_fail (gtkhtml, FALSE); - g_return_val_if_fail (txt, FALSE); - - html = convert_to_html (txt); - gtk_html_load_from_string (gtkhtml, html, strlen(html)); - g_free (html); - return TRUE; -} - - - -static TnyMsgMimePartIface * -find_body_part (TnyMsgIface *msg, const gchar *mime_type) -{ - TnyMsgMimePartIface *part = NULL; - GList *parts; - - g_return_val_if_fail (msg, NULL); - g_return_val_if_fail (mime_type, NULL); - - parts = (GList*) tny_msg_iface_get_parts (msg); - while (parts && !part) { - part = TNY_MSG_MIME_PART_IFACE(parts->data); - if (!tny_msg_mime_part_iface_content_type_is (part, mime_type)) - part = NULL; - parts = parts->next; - } - - return part; -} static gboolean -set_html_message (ModestTnyMsgView *self, TnyMsgMimePartIface *tny_body) +set_html_message (ModestTnyMsgView *self, const TnyMsgMimePartIface *tny_body, + const TnyMsgIface *msg) { + gchar *html_attachments; TnyStreamIface *gtkhtml_stream; ModestTnyMsgViewPrivate *priv; - + g_return_val_if_fail (self, FALSE); g_return_val_if_fail (tny_body, FALSE); priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE(self); gtkhtml_stream = - TNY_STREAM_IFACE(modest_tny_stream_gtkhtml_new(GTK_HTML(priv->gtkhtml))); + TNY_STREAM_IFACE(modest_tny_stream_gtkhtml_new + (gtk_html_begin(GTK_HTML(priv->gtkhtml)))); tny_stream_iface_reset (gtkhtml_stream); - tny_msg_mime_part_iface_decode_to_stream (tny_body, gtkhtml_stream); - tny_stream_iface_reset (gtkhtml_stream); + + html_attachments = attachments_as_html(self, msg); + if (html_attachments) { + tny_stream_iface_write (gtkhtml_stream, html_attachments, + strlen(html_attachments)); + tny_stream_iface_reset (gtkhtml_stream); + g_free (html_attachments); + } + + // FIXME: tinymail + tny_msg_mime_part_iface_decode_to_stream ((TnyMsgMimePartIface*)tny_body, + gtkhtml_stream); g_object_unref (G_OBJECT(gtkhtml_stream)); @@ -393,42 +579,86 @@ set_html_message (ModestTnyMsgView *self, TnyMsgMimePartIface *tny_body) /* this is a hack --> we use the tny_text_buffer_stream to * get the message text, then write to gtkhtml 'by hand' */ static gboolean -set_text_message (ModestTnyMsgView *self, TnyMsgMimePartIface *tny_body) +set_text_message (ModestTnyMsgView *self, const TnyMsgMimePartIface *tny_body, + const TnyMsgIface *msg) { GtkTextBuffer *buf; GtkTextIter begin, end; - TnyStreamIface* txt_stream; - gchar *txt; + TnyStreamIface* txt_stream, *gtkhtml_stream; + gchar *txt, *html_attachments; ModestTnyMsgViewPrivate *priv; g_return_val_if_fail (self, FALSE); g_return_val_if_fail (tny_body, FALSE); priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE(self); - + buf = gtk_text_buffer_new (NULL); txt_stream = TNY_STREAM_IFACE(tny_text_buffer_stream_new (buf)); tny_stream_iface_reset (txt_stream); - tny_msg_mime_part_iface_decode_to_stream (tny_body, txt_stream); + + gtkhtml_stream = + TNY_STREAM_IFACE(modest_tny_stream_gtkhtml_new + (gtk_html_begin(GTK_HTML(priv->gtkhtml)))); + + html_attachments = attachments_as_html(self, msg); + if (html_attachments) { + tny_stream_iface_write (gtkhtml_stream, html_attachments, + strlen(html_attachments)); + tny_stream_iface_reset (gtkhtml_stream); + g_free (html_attachments); + } + + // FIXME: tinymail + tny_msg_mime_part_iface_decode_to_stream ((TnyMsgMimePartIface*)tny_body, + txt_stream); tny_stream_iface_reset (txt_stream); gtk_text_buffer_get_bounds (buf, &begin, &end); txt = gtk_text_buffer_get_text (buf, &begin, &end, FALSE); + if (txt) { + gchar *html = convert_to_html (txt); + tny_stream_iface_write (gtkhtml_stream, html, strlen(html)); + tny_stream_iface_reset (gtkhtml_stream); + g_free (txt); + g_free (html); + } - fill_gtkhtml_with_txt (GTK_HTML(priv->gtkhtml), txt); - + g_object_unref (G_OBJECT(gtkhtml_stream)); g_object_unref (G_OBJECT(txt_stream)); g_object_unref (G_OBJECT(buf)); - g_free (txt); return TRUE; } +gchar * +modest_tny_msg_view_get_selected_text (ModestTnyMsgView *self) +{ + ModestTnyMsgViewPrivate *priv; + gchar *sel; + GtkWidget *html; + int len; + GtkClipboard *clip; + + g_return_val_if_fail (self, NULL); + priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE(self); + html = priv->gtkhtml; + + /* I'm sure there is a better way to check for selected text */ + sel = gtk_html_get_selection_html(GTK_HTML(html), &len); + if (!sel) + return NULL; + + g_free(sel); + + clip = gtk_widget_get_clipboard(html, GDK_SELECTION_PRIMARY); + return gtk_clipboard_wait_for_text(clip); +} void -modest_tny_msg_view_set_message (ModestTnyMsgView *self, TnyMsgIface *msg) +modest_tny_msg_view_set_message (ModestTnyMsgView *self, const TnyMsgIface *msg) { TnyMsgMimePartIface *body; ModestTnyMsgViewPrivate *priv; @@ -437,27 +667,16 @@ modest_tny_msg_view_set_message (ModestTnyMsgView *self, TnyMsgIface *msg) priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE(self); - fill_gtkhtml_with_txt (GTK_HTML(priv->gtkhtml), ""); - - if (!msg) - return; + priv->msg = msg; - body = find_body_part (msg, "text/html"); + body = modest_tny_msg_actions_find_body_part (msg, TRUE); if (body) { - set_html_message (self, body); + if (tny_msg_mime_part_iface_content_type_is (body, "text/html")) + set_html_message (self, body, msg); + else + set_text_message (self, body, msg); return; - } - - body = find_body_part (msg, "text/plain"); - if (body) { - set_text_message (self, body); - return; - } - - /* hmmmmm */ - fill_gtkhtml_with_txt (GTK_HTML(priv->gtkhtml), - _("Unsupported message type")); + } else { + /* nothing to show */ + } } - - -