* don't depend on hildon when it's not there
[modest] / src / modest-tny-msg-view.c
1 /* Copyright (c) 2006, Nokia Corporation
2  * All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  * * Redistributions of source code must retain the above copyright
9  *   notice, this list of conditions and the following disclaimer.
10  * * Redistributions in binary form must reproduce the above copyright
11  *   notice, this list of conditions and the following disclaimer in the
12  *   documentation and/or other materials provided with the distribution.
13  * * Neither the name of the Nokia Corporation nor the names of its
14  *   contributors may be used to endorse or promote products derived from
15  *   this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
18  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
20  * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
21  * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29
30 #include <tny-text-buffer-stream.h>
31 #include <string.h>
32 #include <regex.h>
33 #include <ctype.h>
34 #include <glib/gi18n.h>
35 #include <gtkhtml/gtkhtml.h>
36 #include <gtkhtml/gtkhtml-stream.h>
37 #include <tny-list-iface.h>
38
39 #include "modest-tny-msg-view.h"
40 #include "modest-tny-stream-gtkhtml.h"
41 #include "modest-tny-msg-actions.h"
42
43
44 /* 'private'/'protected' functions */
45 static void     modest_tny_msg_view_class_init   (ModestTnyMsgViewClass *klass);
46 static void     modest_tny_msg_view_init         (ModestTnyMsgView *obj);
47 static void     modest_tny_msg_view_finalize     (GObject *obj);
48
49
50 static GSList*  get_url_matches (GString *txt);
51 static gboolean on_link_clicked (GtkWidget *widget, const gchar *uri,
52                                  ModestTnyMsgView *msg_view);
53 static gboolean on_url_requested (GtkWidget *widget, const gchar *uri,
54                                   GtkHTMLStream *stream,
55                                   ModestTnyMsgView *msg_view);
56
57 /*
58  * we need these regexps to find URLs in plain text e-mails
59  */
60 typedef struct _UrlMatchPattern UrlMatchPattern;
61 struct _UrlMatchPattern {
62         gchar   *regex;
63         regex_t *preg;
64         gchar   *prefix;
65         
66 };
67
68 #define ATT_PREFIX "att:"
69
70 #define MAIL_VIEWER_URL_MATCH_PATTERNS  {                               \
71         { "(file|http|ftp|https)://[-A-Za-z0-9_$.+!*(),;:@%&=?/~#]+[-A-Za-z0-9_$%&=?/~#]",\
72           NULL, NULL },\
73         { "www\\.[-a-z0-9.]+[-a-z0-9](:[0-9]*)?(/[-A-Za-z0-9_$.+!*(),;:@%&=?/~#]*[^]}\\),?!;:\"]?)?",\
74           NULL, "http://" },\
75         { "ftp\\.[-a-z0-9.]+[-a-z0-9](:[0-9]*)?(/[-A-Za-z0-9_$.+!*(),;:@%&=?/~#]*[^]}\\),?!;:\"]?)?",\
76           NULL, "ftp://" },\
77         { "(voipto|callto|chatto|jabberto|xmpp):[-_a-z@0-9.\\+]+", \
78            NULL, NULL},                                             \
79         { "mailto:[-_a-z0-9.\\+]+@[-_a-z0-9.]+",                    \
80           NULL, NULL},\
81         { "[-_a-z0-9.\\+]+@[-_a-z0-9.]+",\
82           NULL, "mailto:"}\
83         }
84
85
86 /* list my signals */
87 enum {
88         LINK_CLICKED_SIGNAL,
89         ATTACHMENT_CLICKED_SIGNAL,
90         LAST_SIGNAL
91 };
92
93 typedef struct _ModestTnyMsgViewPrivate ModestTnyMsgViewPrivate;
94 struct _ModestTnyMsgViewPrivate {
95         GtkWidget *gtkhtml;
96         const TnyMsgIface *msg;
97 };
98 #define MODEST_TNY_MSG_VIEW_GET_PRIVATE(o)      (G_TYPE_INSTANCE_GET_PRIVATE((o), \
99                                                  MODEST_TYPE_TNY_MSG_VIEW, \
100                                                  ModestTnyMsgViewPrivate))
101 /* globals */
102 static GtkContainerClass *parent_class = NULL;
103
104 /* uncomment the following if you have defined any signals */
105 static guint signals[LAST_SIGNAL] = {0};
106
107 GType
108 modest_tny_msg_view_get_type (void)
109 {
110         static GType my_type = 0;
111         if (!my_type) {
112                 static const GTypeInfo my_info = {
113                         sizeof(ModestTnyMsgViewClass),
114                         NULL,           /* base init */
115                         NULL,           /* base finalize */
116                         (GClassInitFunc) modest_tny_msg_view_class_init,
117                         NULL,           /* class finalize */
118                         NULL,           /* class data */
119                         sizeof(ModestTnyMsgView),
120                         1,              /* n_preallocs */
121                         (GInstanceInitFunc) modest_tny_msg_view_init,
122                 };
123                 my_type = g_type_register_static (GTK_TYPE_SCROLLED_WINDOW,
124                                                   "ModestTnyMsgView",
125                                                   &my_info, 0);
126         }
127         return my_type;
128 }
129
130 static void
131 modest_tny_msg_view_class_init (ModestTnyMsgViewClass *klass)
132 {
133         GObjectClass *gobject_class;
134         gobject_class = (GObjectClass*) klass;
135
136         parent_class            = g_type_class_peek_parent (klass);
137         gobject_class->finalize = modest_tny_msg_view_finalize;
138
139         g_type_class_add_private (gobject_class, sizeof(ModestTnyMsgViewPrivate));
140
141                 
142         signals[LINK_CLICKED_SIGNAL] =
143                 g_signal_new ("link_clicked",
144                               G_TYPE_FROM_CLASS (gobject_class),
145                               G_SIGNAL_RUN_FIRST,
146                               G_STRUCT_OFFSET(ModestTnyMsgViewClass, link_clicked),
147                               NULL, NULL,
148                               g_cclosure_marshal_VOID__STRING,
149                               G_TYPE_NONE, 1, G_TYPE_STRING);
150         
151         signals[ATTACHMENT_CLICKED_SIGNAL] =
152                 g_signal_new ("attachment_clicked",
153                               G_TYPE_FROM_CLASS (gobject_class),
154                               G_SIGNAL_RUN_FIRST,
155                               G_STRUCT_OFFSET(ModestTnyMsgViewClass, attachment_clicked),
156                               NULL, NULL,
157                               g_cclosure_marshal_VOID__POINTER,
158                               G_TYPE_NONE, 1, G_TYPE_INT);
159         
160 }
161
162 static void
163 modest_tny_msg_view_init (ModestTnyMsgView *obj)
164 {
165         ModestTnyMsgViewPrivate *priv;
166         
167         priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE(obj);
168
169         priv->msg                     = NULL;
170         priv->gtkhtml                 = gtk_html_new();
171         
172         gtk_html_set_editable        (GTK_HTML(priv->gtkhtml), FALSE);
173         gtk_html_allow_selection     (GTK_HTML(priv->gtkhtml), TRUE);
174         gtk_html_set_caret_mode      (GTK_HTML(priv->gtkhtml), FALSE);
175         gtk_html_set_blocking        (GTK_HTML(priv->gtkhtml), FALSE);
176         gtk_html_set_images_blocking (GTK_HTML(priv->gtkhtml), FALSE);
177
178         g_signal_connect (G_OBJECT(priv->gtkhtml), "link_clicked",
179                           G_CALLBACK(on_link_clicked), obj);
180         
181         g_signal_connect (G_OBJECT(priv->gtkhtml), "url_requested",
182                           G_CALLBACK(on_url_requested), obj);
183 }
184         
185
186 static void
187 modest_tny_msg_view_finalize (GObject *obj)
188 {       
189         G_OBJECT_CLASS(parent_class)->finalize (obj);           
190 }
191
192
193 GtkWidget*
194 modest_tny_msg_view_new (const TnyMsgIface *msg)
195 {
196         GObject *obj;
197         ModestTnyMsgView* self;
198         ModestTnyMsgViewPrivate *priv;
199         
200         obj  = G_OBJECT(g_object_new(MODEST_TYPE_TNY_MSG_VIEW, NULL));
201         self = MODEST_TNY_MSG_VIEW(obj);
202         priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE (self);
203
204         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(self),
205                                        GTK_POLICY_AUTOMATIC,
206                                        GTK_POLICY_AUTOMATIC);
207
208         if (priv->gtkhtml) 
209                 gtk_container_add (GTK_CONTAINER(obj), priv->gtkhtml);
210         
211         if (msg)
212                 modest_tny_msg_view_set_message (self, msg);
213         
214         return GTK_WIDGET(self);
215 }
216
217
218 static gboolean
219 on_link_clicked (GtkWidget *widget, const gchar *uri, ModestTnyMsgView *msg_view)
220 {
221
222         int index;
223
224         g_return_val_if_fail (msg_view, FALSE);
225         
226         /* is it an attachment? */
227         if (g_str_has_prefix(uri, ATT_PREFIX)) {
228
229                 index = atoi (uri + strlen(ATT_PREFIX));
230                 
231                 if (index == 0) {
232                         /* index is 1-based, so 0 indicates an error */
233                         g_printerr ("modest: invalid attachment id: %s\n", uri);
234                         return FALSE;
235                 }
236
237                 g_signal_emit (G_OBJECT(msg_view), signals[ATTACHMENT_CLICKED_SIGNAL],
238                                0, index);
239                 return FALSE;
240         }
241
242         g_signal_emit (G_OBJECT(msg_view), signals[LINK_CLICKED_SIGNAL], 0, uri);
243
244         return FALSE;
245 }
246
247
248 static TnyMsgMimePartIface *
249 find_cid_image (const TnyMsgIface *msg, const gchar *cid)
250 {
251         TnyMsgMimePartIface *part = NULL;
252         const TnyListIface *parts;
253         TnyIteratorIface *iter;
254         
255         g_return_val_if_fail (msg, NULL);
256         g_return_val_if_fail (cid, NULL);
257         
258         parts  = tny_msg_iface_get_parts ((TnyMsgIface*)msg); // FIXME: tinymail
259         iter   = tny_list_iface_create_iterator ((TnyListIface*)parts);
260         
261         while (!tny_iterator_iface_is_done(iter)) {
262                 const gchar *part_cid;
263                 part = TNY_MSG_MIME_PART_IFACE(tny_iterator_iface_current(iter));
264                 part_cid = tny_msg_mime_part_iface_get_content_id (part);
265
266                 if (part_cid && strcmp (cid, part_cid) == 0)
267                         break;
268
269                 part = NULL;
270                 tny_iterator_iface_next (iter);
271         }
272
273         g_object_unref (G_OBJECT(iter));        
274         return part;
275 }
276
277
278 static gboolean
279 on_url_requested (GtkWidget *widget, const gchar *uri,
280                   GtkHTMLStream *stream,
281                   ModestTnyMsgView *msg_view)
282 {
283         ModestTnyMsgViewPrivate *priv;
284         priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE (msg_view);
285         
286         if (g_str_has_prefix (uri, "cid:")) {
287                 /* +4 ==> skip "cid:" */
288                 const TnyMsgMimePartIface *part = find_cid_image (priv->msg, uri + 4);
289                 if (!part) {
290                         g_printerr ("modest: '%s' not found\n", uri + 4);
291                         gtk_html_stream_close (stream, GTK_HTML_STREAM_ERROR);
292                 } else {
293                         TnyStreamIface *tny_stream =
294                                 TNY_STREAM_IFACE(modest_tny_stream_gtkhtml_new(stream));
295                         // FIXME: tinymail
296                         tny_msg_mime_part_iface_decode_to_stream ((TnyMsgMimePartIface*)part,
297                                                                   tny_stream);
298                         gtk_html_stream_close (stream, GTK_HTML_STREAM_OK);
299                 }
300         }
301
302         return TRUE;
303 }
304
305
306 typedef struct  {
307         guint offset;
308         guint len;
309         const gchar* prefix;
310 } url_match_t;
311
312
313
314 /* render the attachments as hyperlinks in html */
315 static gchar*
316 attachments_as_html (ModestTnyMsgView *self, const TnyMsgIface *msg)
317 {
318         ModestTnyMsgViewPrivate *priv;
319         GString *appendix;
320         const TnyListIface *parts;
321         TnyIteratorIface *iter;
322         gchar *html;
323         int index = 0;
324         
325         if (!msg)
326                 return NULL;
327
328         priv  = MODEST_TNY_MSG_VIEW_GET_PRIVATE (self);
329         parts = tny_msg_iface_get_parts ((TnyMsgIface*)msg);
330         // FIXME: tinymail
331         iter  = tny_list_iface_create_iterator ((TnyListIface*)parts);
332
333         appendix= g_string_new ("");
334         
335         while (!tny_iterator_iface_is_done(iter)) {
336                 TnyMsgMimePartIface *part;
337
338                 ++index; /* attachment numbers are 1-based */
339                 
340                 part = TNY_MSG_MIME_PART_IFACE(tny_iterator_iface_current (iter));
341
342                 if (tny_msg_mime_part_iface_is_attachment (part)) {
343
344                         const gchar *filename = tny_msg_mime_part_iface_get_filename(part);
345                         if (!filename)
346                                 filename = _("attachment");
347
348                         g_string_append_printf (appendix, "<a href=\"%s%d\">%s</a> \n",
349                                                 ATT_PREFIX, index, filename);                    
350                 }
351                 tny_iterator_iface_next (iter);
352         }
353         g_object_unref (G_OBJECT(iter));
354         
355         if (appendix->len == 0) 
356                 return g_string_free (appendix, TRUE);
357
358         html = g_strdup_printf ("<strong>%s:</strong> %s\n<hr>",
359                                 _("Attachments"), appendix->str);                        
360         g_string_free (appendix, TRUE);
361         
362         return html;
363 }
364
365
366
367 static void
368 hyperlinkify_plain_text (GString *txt)
369 {
370         GSList *cursor;
371         GSList *match_list = get_url_matches (txt);
372
373         /* we will work backwards, so the offsets stay valid */
374         for (cursor = match_list; cursor; cursor = cursor->next) {
375
376                 url_match_t *match = (url_match_t*) cursor->data;
377                 gchar *url  = g_strndup (txt->str + match->offset, match->len);
378                 gchar *repl = NULL; /* replacement  */
379
380                 /* the prefix is NULL: use the one that is already there */
381                 repl = g_strdup_printf ("<a href=\"%s%s\">%s</a>",
382                                         match->prefix ? match->prefix : "", url, url);
383
384                 /* replace the old thing with our hyperlink
385                  * replacement thing */
386                 g_string_erase  (txt, match->offset, match->len);
387                 g_string_insert (txt, match->offset, repl);
388                 
389                 g_free (url);
390                 g_free (repl);
391
392                 g_free (cursor->data);  
393         }
394         
395         g_slist_free (match_list);
396 }
397
398
399
400 static gchar *
401 convert_to_html (const gchar *data)
402 {
403         int              i;
404         gboolean         first_space = TRUE;
405         GString         *html;      
406         gsize           len;
407
408         if (!data)
409                 return NULL;
410
411         len = strlen (data);
412         html = g_string_sized_new (len + 100);  /* just a  guess... */
413         
414         g_string_append_printf (html,
415                                 "<html>"
416                                 "<head>"
417                                 "<meta http-equiv=\"content-type\""
418                                 " content=\"text/html; charset=utf8\">"
419                                 "</head>"
420                                 "<body><tt>");
421         
422         /* replace with special html chars where needed*/
423         for (i = 0; i != len; ++i)  {
424                 char    kar = data[i]; 
425                 switch (kar) {
426                         
427                 case 0:  break; /* ignore embedded \0s */       
428                 case '<' : g_string_append   (html, "&lt;"); break;
429                 case '>' : g_string_append   (html, "&gt;"); break;
430                 case '&' : g_string_append   (html, "&quot;"); break;
431                 case '\n': g_string_append   (html, "<br>\n"); break;
432                 default:
433                         if (kar == ' ') {
434                                 g_string_append (html, first_space ? " " : "&nbsp;");
435                                 first_space = FALSE;
436                         } else  if (kar == '\t')
437                                 g_string_append (html, "&nbsp; &nbsp;&nbsp;");
438                         else {
439                                 int charnum = 0;
440                                 first_space = TRUE;
441                                 /* optimization trick: accumulate 'normal' chars, then copy */
442                                 do {
443                                         kar = data [++charnum + i];
444                                         
445                                 } while ((i + charnum < len) &&
446                                          (kar > '>' || (kar != '<' && kar != '>'
447                                                         && kar != '&' && kar !=  ' '
448                                                         && kar != '\n' && kar != '\t')));
449                                 g_string_append_len (html, &data[i], charnum);
450                                 i += (charnum  - 1);
451                         }
452                 }
453         }
454         
455         g_string_append (html, "</tt></body></html>");
456         hyperlinkify_plain_text (html);
457
458         return g_string_free (html, FALSE);
459 }
460
461
462
463
464 static gint 
465 cmp_offsets_reverse (const url_match_t *match1, const url_match_t *match2)
466 {
467         return match2->offset - match1->offset;
468 }
469
470
471
472 /*
473  * check if the match is inside an existing match... */
474 static void
475 chk_partial_match (const url_match_t *match, int* offset)
476 {
477         if (*offset >= match->offset && *offset < match->offset + match->len)
478                 *offset = -1;
479 }
480
481 static GSList*
482 get_url_matches (GString *txt)
483 {
484         regmatch_t rm;
485         int rv, i, offset = 0;
486         GSList *match_list = NULL;
487
488         static UrlMatchPattern patterns[] = MAIL_VIEWER_URL_MATCH_PATTERNS;
489         const size_t pattern_num = sizeof(patterns)/sizeof(UrlMatchPattern);
490
491         /* initalize the regexps */
492         for (i = 0; i != pattern_num; ++i) {
493                 patterns[i].preg = g_new0 (regex_t,1);
494                 g_assert(regcomp (patterns[i].preg, patterns[i].regex,
495                                   REG_ICASE|REG_EXTENDED|REG_NEWLINE) == 0);
496         }
497         /* find all the matches */
498         for (i = 0; i != pattern_num; ++i) {
499                 offset     = 0; 
500                 while (1) {
501                         int test_offset;
502                         if ((rv = regexec (patterns[i].preg, txt->str + offset, 1, &rm, 0)) != 0) {
503                                 g_assert (rv == REG_NOMATCH); /* this should not happen */
504                                 break; /* try next regexp */ 
505                         }
506                         if (rm.rm_so == -1)
507                                 break;
508
509                         /* FIXME: optimize this */
510                         /* to avoid partial matches on something that was already found... */
511                         /* check_partial_match will put -1 in the data ptr if that is the case */
512                         test_offset = offset + rm.rm_so;
513                         g_slist_foreach (match_list, (GFunc)chk_partial_match, &test_offset);
514                         
515                         /* make a list of our matches (<offset, len, prefix> tupels)*/
516                         if (test_offset != -1) {
517                                 url_match_t *match = g_new (url_match_t,1);
518                                 match->offset = offset + rm.rm_so;
519                                 match->len    = rm.rm_eo - rm.rm_so;
520                                 match->prefix = patterns[i].prefix;
521                                 match_list = g_slist_prepend (match_list, match);
522                         }
523                         offset += rm.rm_eo;
524                 }
525         }
526
527         for (i = 0; i != pattern_num; ++i) {
528                 regfree (patterns[i].preg);
529                 g_free  (patterns[i].preg);
530         } /* don't free patterns itself -- it's static */
531         
532         /* now sort the list, so the matches are in reverse order of occurence.
533          * that way, we can do the replacements starting from the end, so we don't need
534          * to recalculate the offsets
535          */
536         match_list = g_slist_sort (match_list,
537                                    (GCompareFunc)cmp_offsets_reverse); 
538         return match_list;      
539 }
540
541
542
543 static gboolean
544 set_html_message (ModestTnyMsgView *self, const TnyMsgMimePartIface *tny_body,
545                   const TnyMsgIface *msg)
546 {
547         gchar *html_attachments;
548         TnyStreamIface *gtkhtml_stream; 
549         ModestTnyMsgViewPrivate *priv;
550         
551         g_return_val_if_fail (self, FALSE);
552         g_return_val_if_fail (tny_body, FALSE);
553         
554         priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE(self);
555
556         gtkhtml_stream =
557                 TNY_STREAM_IFACE(modest_tny_stream_gtkhtml_new
558                                  (gtk_html_begin(GTK_HTML(priv->gtkhtml))));
559         
560         tny_stream_iface_reset (gtkhtml_stream);
561         
562         html_attachments = attachments_as_html(self, msg);
563         if (html_attachments) {
564                 tny_stream_iface_write (gtkhtml_stream, html_attachments,
565                                         strlen(html_attachments));
566                 tny_stream_iface_reset (gtkhtml_stream);
567                 g_free (html_attachments);
568         }
569
570         // FIXME: tinymail
571         tny_msg_mime_part_iface_decode_to_stream ((TnyMsgMimePartIface*)tny_body,
572                                                   gtkhtml_stream);
573
574         g_object_unref (G_OBJECT(gtkhtml_stream));
575         
576         return TRUE;
577 }
578
579
580 /* this is a hack --> we use the tny_text_buffer_stream to
581  * get the message text, then write to gtkhtml 'by hand' */
582 static gboolean
583 set_text_message (ModestTnyMsgView *self, const TnyMsgMimePartIface *tny_body,
584                   const TnyMsgIface *msg)
585 {
586         GtkTextBuffer *buf;
587         GtkTextIter begin, end;
588         TnyStreamIface* txt_stream, *gtkhtml_stream;
589         gchar *txt, *html_attachments;
590         ModestTnyMsgViewPrivate *priv;
591                 
592         g_return_val_if_fail (self, FALSE);
593         g_return_val_if_fail (tny_body, FALSE);
594
595         priv           = MODEST_TNY_MSG_VIEW_GET_PRIVATE(self);
596         
597         buf            = gtk_text_buffer_new (NULL);
598         txt_stream     = TNY_STREAM_IFACE(tny_text_buffer_stream_new (buf));
599                 
600         tny_stream_iface_reset (txt_stream);
601         
602         gtkhtml_stream =
603                 TNY_STREAM_IFACE(modest_tny_stream_gtkhtml_new
604                                  (gtk_html_begin(GTK_HTML(priv->gtkhtml))));
605
606         html_attachments = attachments_as_html(self, msg);
607         if (html_attachments) {
608                 tny_stream_iface_write (gtkhtml_stream, html_attachments,
609                                         strlen(html_attachments));
610                 tny_stream_iface_reset (gtkhtml_stream);
611                 g_free (html_attachments);
612         }
613
614         // FIXME: tinymail
615         tny_msg_mime_part_iface_decode_to_stream ((TnyMsgMimePartIface*)tny_body,
616                                                   txt_stream);
617         tny_stream_iface_reset (txt_stream);            
618         
619         gtk_text_buffer_get_bounds (buf, &begin, &end);
620         txt = gtk_text_buffer_get_text (buf, &begin, &end, FALSE);
621         if (txt) {
622                 gchar *html = convert_to_html (txt);
623                 tny_stream_iface_write (gtkhtml_stream, html, strlen(html));
624                 tny_stream_iface_reset (gtkhtml_stream);
625                 g_free (txt);
626                 g_free (html);
627         }
628         
629         g_object_unref (G_OBJECT(gtkhtml_stream));
630         g_object_unref (G_OBJECT(txt_stream));
631         g_object_unref (G_OBJECT(buf));
632
633         return TRUE;
634 }
635
636
637 static gboolean
638 set_empty_message (ModestTnyMsgView *self)
639 {
640         ModestTnyMsgViewPrivate *priv;
641         
642         g_return_val_if_fail (self, FALSE);
643         priv           = MODEST_TNY_MSG_VIEW_GET_PRIVATE(self);
644
645         gtk_html_load_from_string (priv->gtkhtml, "", 1);
646         
647         return TRUE;
648 }
649
650
651 gchar *
652 modest_tny_msg_view_get_selected_text (ModestTnyMsgView *self)
653 {
654         ModestTnyMsgViewPrivate *priv;
655         gchar *sel;
656         GtkWidget *html;
657         int len;
658         GtkClipboard *clip;
659
660         g_return_val_if_fail (self, NULL);
661         priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE(self);
662         html = priv->gtkhtml;
663         
664         /* I'm sure there is a better way to check for selected text */
665         sel = gtk_html_get_selection_html(GTK_HTML(html), &len);
666         if (!sel)
667                 return NULL;
668         
669         g_free(sel);
670         
671         clip = gtk_widget_get_clipboard(html, GDK_SELECTION_PRIMARY);
672         return gtk_clipboard_wait_for_text(clip);
673 }
674
675
676 void
677 modest_tny_msg_view_set_message (ModestTnyMsgView *self, const TnyMsgIface *msg)
678 {
679         TnyMsgMimePartIface *body;
680         ModestTnyMsgViewPrivate *priv;
681
682         g_return_if_fail (self);
683         
684         priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE(self);
685
686         priv->msg = msg;
687         
688         if (!msg) {
689                 set_empty_message (self);
690                 return;
691         }
692                 
693         body = modest_tny_msg_actions_find_body_part (msg, TRUE);
694         if (body) {
695                 if (tny_msg_mime_part_iface_content_type_is (body, "text/html"))
696                         set_html_message (self, body, msg);
697                 else
698                         set_text_message (self, body, msg);
699                 return;
700         } else 
701                 set_empty_message (self);
702 }