* small cleanups
[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         /* TODO! */
190 }
191
192 GtkWidget*
193 modest_tny_msg_view_new (const TnyMsgIface *msg)
194 {
195         GObject *obj;
196         ModestTnyMsgView* self;
197         ModestTnyMsgViewPrivate *priv;
198         
199         obj  = G_OBJECT(g_object_new(MODEST_TYPE_TNY_MSG_VIEW, NULL));
200         self = MODEST_TNY_MSG_VIEW(obj);
201         priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE (self);
202
203         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(self),
204                                        GTK_POLICY_AUTOMATIC,
205                                        GTK_POLICY_AUTOMATIC);
206
207         if (priv->gtkhtml) 
208                 gtk_container_add (GTK_CONTAINER(obj), priv->gtkhtml);
209         
210         if (msg)
211                 modest_tny_msg_view_set_message (self, msg);
212         
213         return GTK_WIDGET(self);
214 }
215
216
217 static gboolean
218 on_link_clicked (GtkWidget *widget, const gchar *uri, ModestTnyMsgView *msg_view)
219 {
220
221         int index;
222
223         g_return_val_if_fail (msg_view, FALSE);
224         
225         /* is it an attachment? */
226         if (g_str_has_prefix(uri, ATT_PREFIX)) {
227
228                 index = atoi (uri + strlen(ATT_PREFIX));
229                 
230                 if (index == 0) {
231                         /* index is 1-based, so 0 indicates an error */
232                         g_printerr ("modest: invalid attachment id: %s\n", uri);
233                         return FALSE;
234                 }
235
236                 g_signal_emit (G_OBJECT(msg_view), signals[ATTACHMENT_CLICKED_SIGNAL],
237                                0, index);
238                 return FALSE;
239         }
240
241         g_signal_emit (G_OBJECT(msg_view), signals[LINK_CLICKED_SIGNAL], 0, uri);
242
243         return FALSE;
244 }
245
246
247 static TnyMsgMimePartIface *
248 find_cid_image (const TnyMsgIface *msg, const gchar *cid)
249 {
250         TnyMsgMimePartIface *part = NULL;
251         const TnyListIface *parts;
252         TnyIteratorIface *iter;
253         
254         g_return_val_if_fail (msg, NULL);
255         g_return_val_if_fail (cid, NULL);
256         
257         parts  = tny_msg_iface_get_parts ((TnyMsgIface*)msg); // FIXME: tinymail
258         iter   = tny_list_iface_create_iterator ((TnyListIface*)parts);
259         
260         while (!tny_iterator_iface_is_done(iter)) {
261                 const gchar *part_cid;
262                 part = TNY_MSG_MIME_PART_IFACE(tny_iterator_iface_current(iter));
263                 part_cid = tny_msg_mime_part_iface_get_content_id (part);
264
265                 if (part_cid && strcmp (cid, part_cid) == 0)
266                         break;
267
268                 part = NULL;
269                 tny_iterator_iface_next (iter);
270         }
271
272         g_object_unref (G_OBJECT(iter));        
273         return part;
274 }
275
276
277 static gboolean
278 on_url_requested (GtkWidget *widget, const gchar *uri,
279                   GtkHTMLStream *stream,
280                   ModestTnyMsgView *msg_view)
281 {
282         ModestTnyMsgViewPrivate *priv;
283         priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE (msg_view);
284         
285         if (g_str_has_prefix (uri, "cid:")) {
286                 /* +4 ==> skip "cid:" */
287                 const TnyMsgMimePartIface *part = find_cid_image (priv->msg, uri + 4);
288                 if (!part) {
289                         g_printerr ("modest: '%s' not found\n", uri + 4);
290                         gtk_html_stream_close (stream, GTK_HTML_STREAM_ERROR);
291                 } else {
292                         TnyStreamIface *tny_stream =
293                                 TNY_STREAM_IFACE(modest_tny_stream_gtkhtml_new(stream));
294                         // FIXME: tinymail
295                         tny_msg_mime_part_iface_decode_to_stream ((TnyMsgMimePartIface*)part,
296                                                                   tny_stream);
297                         gtk_html_stream_close (stream, GTK_HTML_STREAM_OK);
298                 }
299         }
300
301         return TRUE;
302 }
303
304
305 typedef struct  {
306         guint offset;
307         guint len;
308         const gchar* prefix;
309 } url_match_t;
310
311
312
313 /* render the attachments as hyperlinks in html */
314 static gchar*
315 attachments_as_html (ModestTnyMsgView *self, const TnyMsgIface *msg)
316 {
317         ModestTnyMsgViewPrivate *priv;
318         GString *appendix;
319         const TnyListIface *parts;
320         TnyIteratorIface *iter;
321         gchar *html;
322         int index = 0;
323         
324         if (!msg)
325                 return NULL;
326
327         priv  = MODEST_TNY_MSG_VIEW_GET_PRIVATE (self);
328         parts = tny_msg_iface_get_parts ((TnyMsgIface*)msg);
329         // FIXME: tinymail
330         iter  = tny_list_iface_create_iterator ((TnyListIface*)parts);
331
332         appendix= g_string_new ("");
333         
334         while (!tny_iterator_iface_is_done(iter)) {
335                 TnyMsgMimePartIface *part;
336
337                 ++index; /* attachment numbers are 1-based */
338                 
339                 part = TNY_MSG_MIME_PART_IFACE(tny_iterator_iface_current (iter));
340
341                 if (tny_msg_mime_part_iface_is_attachment (part)) {
342
343                         const gchar *filename = tny_msg_mime_part_iface_get_filename(part);
344                         if (!filename)
345                                 filename = _("attachment");
346
347                         g_string_append_printf (appendix, "<a href=\"%s%d\">%s</a> \n",
348                                                 ATT_PREFIX, index, filename);                    
349                 }
350                 tny_iterator_iface_next (iter);
351         }
352         g_object_unref (G_OBJECT(iter));
353         
354         if (appendix->len == 0) 
355                 return g_string_free (appendix, TRUE);
356
357         html = g_strdup_printf ("<strong>%s:</strong> %s\n<hr>",
358                                 _("Attachments"), appendix->str);                        
359         g_string_free (appendix, TRUE);
360         
361         return html;
362 }
363
364
365
366 static void
367 hyperlinkify_plain_text (GString *txt)
368 {
369         GSList *cursor;
370         GSList *match_list = get_url_matches (txt);
371
372         /* we will work backwards, so the offsets stay valid */
373         for (cursor = match_list; cursor; cursor = cursor->next) {
374
375                 url_match_t *match = (url_match_t*) cursor->data;
376                 gchar *url  = g_strndup (txt->str + match->offset, match->len);
377                 gchar *repl = NULL; /* replacement  */
378
379                 /* the prefix is NULL: use the one that is already there */
380                 repl = g_strdup_printf ("<a href=\"%s%s\">%s</a>",
381                                         match->prefix ? match->prefix : "", url, url);
382
383                 /* replace the old thing with our hyperlink
384                  * replacement thing */
385                 g_string_erase  (txt, match->offset, match->len);
386                 g_string_insert (txt, match->offset, repl);
387                 
388                 g_free (url);
389                 g_free (repl);
390
391                 g_free (cursor->data);  
392         }
393         
394         g_slist_free (match_list);
395 }
396
397
398
399 static gchar *
400 convert_to_html (const gchar *data)
401 {
402         int              i;
403         gboolean         first_space = TRUE;
404         GString         *html;      
405         gsize           len;
406
407         if (!data)
408                 return NULL;
409
410         len = strlen (data);
411         html = g_string_sized_new (len + 100);  /* just a  guess... */
412         
413         g_string_append_printf (html,
414                                 "<html>"
415                                 "<head>"
416                                 "<meta http-equiv=\"content-type\""
417                                 " content=\"text/html; charset=utf8\">"
418                                 "</head>"
419                                 "<body><tt>");
420         
421         /* replace with special html chars where needed*/
422         for (i = 0; i != len; ++i)  {
423                 char    kar = data[i]; 
424                 switch (kar) {
425                         
426                 case 0:  break; /* ignore embedded \0s */       
427                 case '<' : g_string_append   (html, "&lt;"); break;
428                 case '>' : g_string_append   (html, "&gt;"); break;
429                 case '&' : g_string_append   (html, "&quot;"); break;
430                 case '\n': g_string_append   (html, "<br>\n"); break;
431                 default:
432                         if (kar == ' ') {
433                                 g_string_append (html, first_space ? " " : "&nbsp;");
434                                 first_space = FALSE;
435                         } else  if (kar == '\t')
436                                 g_string_append (html, "&nbsp; &nbsp;&nbsp;");
437                         else {
438                                 int charnum = 0;
439                                 first_space = TRUE;
440                                 /* optimization trick: accumulate 'normal' chars, then copy */
441                                 do {
442                                         kar = data [++charnum + i];
443                                         
444                                 } while ((i + charnum < len) &&
445                                          (kar > '>' || (kar != '<' && kar != '>'
446                                                         && kar != '&' && kar !=  ' '
447                                                         && kar != '\n' && kar != '\t')));
448                                 g_string_append_len (html, &data[i], charnum);
449                                 i += (charnum  - 1);
450                         }
451                 }
452         }
453         
454         g_string_append (html, "</tt></body></html>");
455         hyperlinkify_plain_text (html);
456
457         return g_string_free (html, FALSE);
458 }
459
460
461
462
463 static gint 
464 cmp_offsets_reverse (const url_match_t *match1, const url_match_t *match2)
465 {
466         return match2->offset - match1->offset;
467 }
468
469
470
471 /*
472  * check if the match is inside an existing match... */
473 static void
474 chk_partial_match (const url_match_t *match, int* offset)
475 {
476         if (*offset >= match->offset && *offset < match->offset + match->len)
477                 *offset = -1;
478 }
479
480 static GSList*
481 get_url_matches (GString *txt)
482 {
483         regmatch_t rm;
484         int rv, i, offset = 0;
485         GSList *match_list = NULL;
486
487         static UrlMatchPattern patterns[] = MAIL_VIEWER_URL_MATCH_PATTERNS;
488         const size_t pattern_num = sizeof(patterns)/sizeof(UrlMatchPattern);
489
490         /* initalize the regexps */
491         for (i = 0; i != pattern_num; ++i) {
492                 patterns[i].preg = g_new0 (regex_t,1);
493                 g_assert(regcomp (patterns[i].preg, patterns[i].regex,
494                                   REG_ICASE|REG_EXTENDED|REG_NEWLINE) == 0);
495         }
496         /* find all the matches */
497         for (i = 0; i != pattern_num; ++i) {
498                 offset     = 0; 
499                 while (1) {
500                         int test_offset;
501                         if ((rv = regexec (patterns[i].preg, txt->str + offset, 1, &rm, 0)) != 0) {
502                                 g_assert (rv == REG_NOMATCH); /* this should not happen */
503                                 break; /* try next regexp */ 
504                         }
505                         if (rm.rm_so == -1)
506                                 break;
507
508                         /* FIXME: optimize this */
509                         /* to avoid partial matches on something that was already found... */
510                         /* check_partial_match will put -1 in the data ptr if that is the case */
511                         test_offset = offset + rm.rm_so;
512                         g_slist_foreach (match_list, (GFunc)chk_partial_match, &test_offset);
513                         
514                         /* make a list of our matches (<offset, len, prefix> tupels)*/
515                         if (test_offset != -1) {
516                                 url_match_t *match = g_new (url_match_t,1);
517                                 match->offset = offset + rm.rm_so;
518                                 match->len    = rm.rm_eo - rm.rm_so;
519                                 match->prefix = patterns[i].prefix;
520                                 match_list = g_slist_prepend (match_list, match);
521                         }
522                         offset += rm.rm_eo;
523                 }
524         }
525
526         for (i = 0; i != pattern_num; ++i) {
527                 regfree (patterns[i].preg);
528                 g_free  (patterns[i].preg);
529         } /* don't free patterns itself -- it's static */
530         
531         /* now sort the list, so the matches are in reverse order of occurence.
532          * that way, we can do the replacements starting from the end, so we don't need
533          * to recalculate the offsets
534          */
535         match_list = g_slist_sort (match_list,
536                                    (GCompareFunc)cmp_offsets_reverse); 
537         return match_list;      
538 }
539
540
541
542 static gboolean
543 set_html_message (ModestTnyMsgView *self, const TnyMsgMimePartIface *tny_body,
544                   const TnyMsgIface *msg)
545 {
546         gchar *html_attachments;
547         TnyStreamIface *gtkhtml_stream; 
548         ModestTnyMsgViewPrivate *priv;
549         
550         g_return_val_if_fail (self, FALSE);
551         g_return_val_if_fail (tny_body, FALSE);
552         
553         priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE(self);
554
555         gtkhtml_stream =
556                 TNY_STREAM_IFACE(modest_tny_stream_gtkhtml_new
557                                  (gtk_html_begin(GTK_HTML(priv->gtkhtml))));
558         
559         tny_stream_iface_reset (gtkhtml_stream);
560         
561         html_attachments = attachments_as_html(self, msg);
562         if (html_attachments) {
563                 tny_stream_iface_write (gtkhtml_stream, html_attachments,
564                                         strlen(html_attachments));
565                 tny_stream_iface_reset (gtkhtml_stream);
566                 g_free (html_attachments);
567         }
568
569         // FIXME: tinymail
570         tny_msg_mime_part_iface_decode_to_stream ((TnyMsgMimePartIface*)tny_body,
571                                                   gtkhtml_stream);
572
573         g_object_unref (G_OBJECT(gtkhtml_stream));
574         
575         return TRUE;
576 }
577
578
579 /* this is a hack --> we use the tny_text_buffer_stream to
580  * get the message text, then write to gtkhtml 'by hand' */
581 static gboolean
582 set_text_message (ModestTnyMsgView *self, const TnyMsgMimePartIface *tny_body,
583                   const TnyMsgIface *msg)
584 {
585         GtkTextBuffer *buf;
586         GtkTextIter begin, end;
587         TnyStreamIface* txt_stream, *gtkhtml_stream;
588         gchar *txt, *html_attachments;
589         ModestTnyMsgViewPrivate *priv;
590                 
591         g_return_val_if_fail (self, FALSE);
592         g_return_val_if_fail (tny_body, FALSE);
593
594         priv           = MODEST_TNY_MSG_VIEW_GET_PRIVATE(self);
595         
596         buf            = gtk_text_buffer_new (NULL);
597         txt_stream     = TNY_STREAM_IFACE(tny_text_buffer_stream_new (buf));
598                 
599         tny_stream_iface_reset (txt_stream);
600         
601         gtkhtml_stream =
602                 TNY_STREAM_IFACE(modest_tny_stream_gtkhtml_new
603                                  (gtk_html_begin(GTK_HTML(priv->gtkhtml))));
604
605         html_attachments = attachments_as_html(self, msg);
606         if (html_attachments) {
607                 tny_stream_iface_write (gtkhtml_stream, html_attachments,
608                                         strlen(html_attachments));
609                 tny_stream_iface_reset (gtkhtml_stream);
610                 g_free (html_attachments);
611         }
612
613         // FIXME: tinymail
614         tny_msg_mime_part_iface_decode_to_stream ((TnyMsgMimePartIface*)tny_body,
615                                                   txt_stream);
616         tny_stream_iface_reset (txt_stream);            
617         
618         gtk_text_buffer_get_bounds (buf, &begin, &end);
619         txt = gtk_text_buffer_get_text (buf, &begin, &end, FALSE);
620         if (txt) {
621                 gchar *html = convert_to_html (txt);
622                 tny_stream_iface_write (gtkhtml_stream, html, strlen(html));
623                 tny_stream_iface_reset (gtkhtml_stream);
624                 g_free (txt);
625                 g_free (html);
626         }
627         
628         g_object_unref (G_OBJECT(gtkhtml_stream));
629         g_object_unref (G_OBJECT(txt_stream));
630         g_object_unref (G_OBJECT(buf));
631
632         return TRUE;
633 }
634
635
636 static gboolean
637 set_empty_message (ModestTnyMsgView *self)
638 {
639         ModestTnyMsgViewPrivate *priv;
640         
641         g_return_val_if_fail (self, FALSE);
642         priv           = MODEST_TNY_MSG_VIEW_GET_PRIVATE(self);
643
644         gtk_html_load_from_string (priv->gtkhtml, "", 1);
645         
646         return TRUE;
647 }
648
649
650 gchar *
651 modest_tny_msg_view_get_selected_text (ModestTnyMsgView *self)
652 {
653         ModestTnyMsgViewPrivate *priv;
654         gchar *sel;
655         GtkWidget *html;
656         int len;
657         GtkClipboard *clip;
658
659         g_return_val_if_fail (self, NULL);
660         priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE(self);
661         html = priv->gtkhtml;
662         
663         /* I'm sure there is a better way to check for selected text */
664         sel = gtk_html_get_selection_html(GTK_HTML(html), &len);
665         if (!sel)
666                 return NULL;
667         
668         g_free(sel);
669         
670         clip = gtk_widget_get_clipboard(html, GDK_SELECTION_PRIMARY);
671         return gtk_clipboard_wait_for_text(clip);
672 }
673
674
675 void
676 modest_tny_msg_view_set_message (ModestTnyMsgView *self, const TnyMsgIface *msg)
677 {
678         TnyMsgMimePartIface *body;
679         ModestTnyMsgViewPrivate *priv;
680
681         g_return_if_fail (self);
682         
683         priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE(self);
684
685         priv->msg = msg;
686         
687         if (!msg) {
688                 set_empty_message (self);
689                 return;
690         }
691                 
692         body = modest_tny_msg_actions_find_body_part (msg, TRUE);
693         if (body) {
694                 if (tny_msg_mime_part_iface_content_type_is (body, "text/html"))
695                         set_html_message (self, body, msg);
696                 else
697                         set_text_message (self, body, msg);
698                 return;
699         } else 
700                 set_empty_message (self);
701 }