* cleanup find_body_part a bit, and check if a part is not an attachment,
[modest] / src / modest-tny-msg-view.c
1 /* modest-tny-msg-view.c */
2
3 /* insert (c)/licensing information) */
4
5 #include "modest-tny-msg-view.h"
6 #include "modest-tny-stream-gtkhtml.h"
7 #include "modest-tny-msg-actions.h"
8
9 #include <tny-text-buffer-stream.h>
10 #include <string.h>
11 #include <regex.h>
12 #include <ctype.h>
13 #include <glib/gi18n.h>
14 #include <gtkhtml/gtkhtml.h>
15 #include <gtkhtml/gtkhtml-stream.h>
16
17 /* 'private'/'protected' functions */
18 static void     modest_tny_msg_view_class_init   (ModestTnyMsgViewClass *klass);
19 static void     modest_tny_msg_view_init         (ModestTnyMsgView *obj);
20 static void     modest_tny_msg_view_finalize     (GObject *obj);
21
22
23 static GSList*  get_url_matches (GString *txt);
24 static gboolean fill_gtkhtml_with_txt (ModestTnyMsgView *self, GtkHTML* gtkhtml, const gchar* txt, TnyMsgIface *msg);
25
26 static gboolean on_link_clicked (GtkWidget *widget, const gchar *uri,
27                                  ModestTnyMsgView *msg_view);
28 static gboolean on_url_requested (GtkWidget *widget, const gchar *uri,
29                                   GtkHTMLStream *stream,
30                                   ModestTnyMsgView *msg_view);
31 static gchar *construct_virtual_filename(const gchar *filename, const gint position, const gchar *id, const gboolean active);
32 static gchar *construct_virtual_filename_from_mime_part(TnyMsgMimePartIface *msg, const gint position);
33
34 #define ATTACHMENT_ID_INLINE "attachment-inline"
35 #define ATTACHMENT_ID_LINK "attachment-link"
36
37 gint virtual_filename_get_pos(const gchar *filename);
38 /*
39  * we need these regexps to find URLs in plain text e-mails
40  */
41 typedef struct _UrlMatchPattern UrlMatchPattern;
42 struct _UrlMatchPattern {
43         gchar   *regex;
44         regex_t *preg;
45         gchar   *prefix;
46         
47 };
48 #define MAIL_VIEWER_URL_MATCH_PATTERNS  {\
49         { "(file|http|ftp|https)://[-A-Za-z0-9_$.+!*(),;:@%&=?/~#]+[-A-Za-z0-9_$%&=?/~#]",\
50           NULL, NULL },\
51         { "www\\.[-a-z0-9.]+[-a-z0-9](:[0-9]*)?(/[-A-Za-z0-9_$.+!*(),;:@%&=?/~#]*[^]}\\),?!;:\"]?)?",\
52           NULL, "http://" },\
53         { "ftp\\.[-a-z0-9.]+[-a-z0-9](:[0-9]*)?(/[-A-Za-z0-9_$.+!*(),;:@%&=?/~#]*[^]}\\),?!;:\"]?)?",\
54           NULL, "ftp://" },\
55         { "(voipto|callto|chatto|jabberto|xmpp):[-_a-z@0-9.\\+]+", \
56            NULL, NULL},                                             \
57         { "mailto:[-_a-z0-9.\\+]+@[-_a-z0-9.]+",                    \
58           NULL, NULL},\
59         { "[-_a-z0-9.\\+]+@[-_a-z0-9.]+",\
60           NULL, "mailto:"}\
61         }
62
63
64 /* list my signals */
65 enum {
66         /* MY_SIGNAL_1, */
67         /* MY_SIGNAL_2, */
68         LAST_SIGNAL
69 };
70
71 typedef struct _ModestTnyMsgViewPrivate ModestTnyMsgViewPrivate;
72 struct _ModestTnyMsgViewPrivate {
73         GtkWidget *gtkhtml;
74         TnyMsgIface *msg;
75         gboolean show_attachments_inline;
76 };
77 #define MODEST_TNY_MSG_VIEW_GET_PRIVATE(o)      (G_TYPE_INSTANCE_GET_PRIVATE((o), \
78                                                  MODEST_TYPE_TNY_MSG_VIEW, \
79                                                  ModestTnyMsgViewPrivate))
80 /* globals */
81 static GtkContainerClass *parent_class = NULL;
82
83 /* uncomment the following if you have defined any signals */
84 /* static guint signals[LAST_SIGNAL] = {0}; */
85
86 GType
87 modest_tny_msg_view_get_type (void)
88 {
89         static GType my_type = 0;
90         if (!my_type) {
91                 static const GTypeInfo my_info = {
92                         sizeof(ModestTnyMsgViewClass),
93                         NULL,           /* base init */
94                         NULL,           /* base finalize */
95                         (GClassInitFunc) modest_tny_msg_view_class_init,
96                         NULL,           /* class finalize */
97                         NULL,           /* class data */
98                         sizeof(ModestTnyMsgView),
99                         1,              /* n_preallocs */
100                         (GInstanceInitFunc) modest_tny_msg_view_init,
101                 };
102                 my_type = g_type_register_static (GTK_TYPE_SCROLLED_WINDOW,
103                                                   "ModestTnyMsgView",
104                                                   &my_info, 0);
105         }
106         return my_type;
107 }
108
109 static void
110 modest_tny_msg_view_class_init (ModestTnyMsgViewClass *klass)
111 {
112         GObjectClass *gobject_class;
113         gobject_class = (GObjectClass*) klass;
114
115         parent_class            = g_type_class_peek_parent (klass);
116         gobject_class->finalize = modest_tny_msg_view_finalize;
117
118         g_type_class_add_private (gobject_class, sizeof(ModestTnyMsgViewPrivate));
119 }
120
121 static void
122 modest_tny_msg_view_init (ModestTnyMsgView *obj)
123 {
124         ModestTnyMsgViewPrivate *priv;
125         
126         priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE(obj);
127
128         priv->msg = NULL;
129         
130         priv->gtkhtml = gtk_html_new();
131         
132         priv->show_attachments_inline = FALSE;
133
134         gtk_html_set_editable        (GTK_HTML(priv->gtkhtml), FALSE);
135         gtk_html_allow_selection     (GTK_HTML(priv->gtkhtml), TRUE);
136         gtk_html_set_caret_mode      (GTK_HTML(priv->gtkhtml), FALSE);
137         gtk_html_set_blocking        (GTK_HTML(priv->gtkhtml), FALSE);
138         gtk_html_set_images_blocking (GTK_HTML(priv->gtkhtml), FALSE);
139
140         g_signal_connect (G_OBJECT(priv->gtkhtml), "link_clicked",
141                           G_CALLBACK(on_link_clicked), obj);
142         
143         g_signal_connect (G_OBJECT(priv->gtkhtml), "url_requested",
144                           G_CALLBACK(on_url_requested), obj);
145 }
146         
147
148 static void
149 modest_tny_msg_view_finalize (GObject *obj)
150 {       
151         /* TODO! */
152 }
153
154 GtkWidget*
155 modest_tny_msg_view_new (TnyMsgIface *msg, const gboolean show_attachments_inline)
156 {
157         GObject *obj;
158         ModestTnyMsgView* self;
159         ModestTnyMsgViewPrivate *priv;
160         
161         obj  = G_OBJECT(g_object_new(MODEST_TYPE_TNY_MSG_VIEW, NULL));
162         self = MODEST_TNY_MSG_VIEW(obj);
163         priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE (self);
164
165         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(self),
166                                        GTK_POLICY_AUTOMATIC,
167                                        GTK_POLICY_AUTOMATIC);
168
169         if (priv->gtkhtml) 
170                 gtk_container_add (GTK_CONTAINER(obj), priv->gtkhtml);
171         
172         if (msg)
173                 modest_tny_msg_view_set_message (self, msg);
174         
175         modest_tny_msg_view_set_show_attachments_inline_flag(self, show_attachments_inline);
176
177         return GTK_WIDGET(self);
178 }
179
180
181 static gboolean
182 on_link_clicked (GtkWidget *widget, const gchar *uri,
183                                  ModestTnyMsgView *msg_view)
184 {
185         
186         if (g_str_has_prefix(uri, ATTACHMENT_ID_LINK)) {
187                 /* save or open attachment */
188                 g_message ("link-to-save: %s", uri); /* FIXME */
189                 return TRUE;
190         }
191         g_message ("link clicked: %s", uri); /* FIXME */
192         return FALSE;
193         
194 }
195
196
197 static TnyMsgMimePartIface *
198 find_cid_image (TnyMsgIface *msg, const gchar *cid)
199 {
200         TnyMsgMimePartIface *part = NULL;
201         GList *parts;
202
203         g_return_val_if_fail (msg, NULL);
204         g_return_val_if_fail (cid, NULL);
205         
206         parts  = (GList*) tny_msg_iface_get_parts (msg);
207         while (parts && !part) {
208                 const gchar *part_cid;
209                 part = TNY_MSG_MIME_PART_IFACE(parts->data);
210                 part_cid = tny_msg_mime_part_iface_get_content_id (part);
211                 printf("CMP:%s:%s\n", cid, part_cid);
212                 if (part_cid && strcmp (cid, part_cid) == 0)
213                         return part; /* we found it! */
214                 
215                 part = NULL;
216                 parts = parts->next;
217         }
218         
219         return part;
220 }
221
222
223 static TnyMsgMimePartIface *
224 find_attachment_by_filename (TnyMsgIface *msg, const gchar *fn)
225 {
226         TnyMsgMimePartIface *part = NULL;
227         GList *parts;
228         gchar *dummy;
229         gint pos;
230
231         g_return_val_if_fail (msg, NULL);
232         g_return_val_if_fail (fn, NULL);
233         
234         parts  = (GList*) tny_msg_iface_get_parts (msg);
235         pos = virtual_filename_get_pos(fn);
236         
237         if ((pos < 0) || (pos >= g_list_length(parts)))
238                 return NULL;
239         
240         part = g_list_nth_data(parts, pos);
241         
242         dummy = construct_virtual_filename_from_mime_part(part, pos);
243         if (strcmp(dummy, fn) == 0) {
244                 g_free(dummy);
245                 return part;
246         } else {
247                 g_free(dummy);
248                 return NULL;
249         }
250 }
251
252
253 static gboolean
254 on_url_requested (GtkWidget *widget, const gchar *uri,
255                   GtkHTMLStream *stream,
256                   ModestTnyMsgView *msg_view)
257 {
258         
259         ModestTnyMsgViewPrivate *priv;
260         priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE (msg_view);
261
262         g_message ("url requested: %s", uri);
263         
264         if (!modest_tny_msg_view_get_show_attachments_inline_flag(msg_view))
265                 return TRUE; /* debatable */
266         
267         if (g_str_has_prefix (uri, "cid:")) {
268                 /* +4 ==> skip "cid:" */
269                 
270                 TnyMsgMimePartIface *part = find_cid_image (priv->msg, uri + 4);
271                 if (!part) {
272                         g_message ("%s not found", uri + 4);
273                         gtk_html_stream_close (stream, GTK_HTML_STREAM_ERROR);
274                 } else {
275                         TnyStreamIface *tny_stream =
276                                 TNY_STREAM_IFACE(modest_tny_stream_gtkhtml_new(stream));
277                         tny_msg_mime_part_iface_decode_to_stream (part,tny_stream);
278                         gtk_html_stream_close (stream, GTK_HTML_STREAM_OK);
279                 }
280         } else if (g_str_has_prefix (uri, ATTACHMENT_ID_INLINE)) {
281                 TnyMsgMimePartIface *part;
282                 part = find_attachment_by_filename (priv->msg, uri);
283                 if (!part) {
284                         g_message ("%s not found", uri);
285                         gtk_html_stream_close (stream, GTK_HTML_STREAM_ERROR);
286                 } else {
287                         TnyStreamIface *tny_stream =
288                                 TNY_STREAM_IFACE(modest_tny_stream_gtkhtml_new(stream));
289                         tny_msg_mime_part_iface_decode_to_stream (part,tny_stream);
290                         gtk_html_stream_close (stream, GTK_HTML_STREAM_OK);
291                 }
292         }
293         return TRUE;
294 }
295
296
297 typedef struct  {
298         guint offset;
299         guint len;
300         const gchar* prefix;
301 } url_match_t;
302
303
304 static gchar *
305 secure_filename(const gchar *fn)
306 {
307         gchar *tmp, *p;
308         GString *s;
309         
310         s = g_string_new("");
311 #if 1 || DEBUG
312         tmp = g_strdup(fn);
313         for (p = tmp; p[0] ; p++ ) {
314                 p[0] &= 0x5f; /* 01011111 */
315                 p[0] |= 0x40; /* 01000000 */
316         }
317         g_string_printf(s, "0x%x:%s", g_str_hash(fn), tmp);
318         g_free(tmp);
319         return g_string_free(s, FALSE);
320 #else
321         g_string_printf(s, "0x%x", g_str_hash(fn));
322         return g_string_free(s, FALSE);
323 #endif
324 }
325         
326         
327 static gchar *
328 construct_virtual_filename(const gchar *filename,
329                            const gint position,
330                            const gchar *id,
331                            const gboolean active)
332 {
333         GString *s;
334         gchar *fn;
335         
336         if (position < 0)
337                 return g_strdup("AttachmentInvalid");
338
339         s = g_string_new("");
340         if (active)
341                 g_string_append(s, ATTACHMENT_ID_INLINE);
342         else
343                 g_string_append(s, ATTACHMENT_ID_LINK);
344         g_string_append_printf(s, ":%d:", position);
345         if (id)
346                 g_string_append(s, id);
347         g_string_append_c(s, ':');
348         
349         fn = secure_filename(filename);
350         if (fn)
351                 g_string_append(s, fn);
352         g_free(fn);
353         g_string_append_c(s, ':');
354         return g_string_free(s, FALSE);
355 }
356
357
358 static gchar *
359 construct_virtual_filename_from_mime_part(TnyMsgMimePartIface *msg, const gint position)
360 {
361         const gchar *id, *filename;
362         const gboolean active = TRUE;
363         
364         filename = tny_msg_mime_part_iface_get_filename(
365                                                                         TNY_MSG_MIME_PART_IFACE(msg));
366         if (!filename)
367                 filename = "[unknown]";
368         id = tny_msg_mime_part_iface_get_content_id(
369                                                                         TNY_MSG_MIME_PART_IFACE(msg));
370         
371         return construct_virtual_filename(filename, position, id, active);
372 }
373
374 const gchar *
375 get_next_token(const gchar *s, gint *len)
376 {
377         gchar *i1, *i2;
378         i1 = (char *) s;
379         i2 = (char *) s;
380         
381         while (i2[0]) {
382                 if (i2[0] == ':')
383                         break;
384                 i2++;
385         }
386         if (!i2[0])
387                 return NULL;
388         *len = i2 - i1;
389         return ++i2;
390 }
391
392 /* maybe I should use libregexp */
393 gint
394 virtual_filename_get_pos(const gchar *filename)
395 {
396         const gchar *i1, *i2;
397         gint len, pos;
398         GString *dummy;
399         
400         /* check prefix */
401         if ((!g_str_has_prefix(filename, ATTACHMENT_ID_INLINE ":")) &&
402             (!g_str_has_prefix(filename, ATTACHMENT_ID_LINK ":")))
403                 return -1;
404
405         i2 = filename;
406         i2 = get_next_token(i2, &len);
407         i1 = i2;
408                 
409         /* get position */
410         i2 = get_next_token(i2, &len);
411         if (i2 == NULL)
412                 return -1;
413         dummy = g_string_new_len(i1, len);
414         pos = atoi(dummy->str);
415         g_string_free(dummy, FALSE);
416         return pos;
417 }       
418
419
420 static gchar *
421 attachments_as_html(ModestTnyMsgView *self, TnyMsgIface *msg)
422 {
423         ModestTnyMsgViewPrivate *priv;
424         gboolean attachments_found = FALSE;
425         GString *appendix;
426         const GList *attachment_list, *attachment;
427         const gchar *content_type, *filename, *id;
428         gchar *virtual_filename;
429         
430         if (!msg)
431                 return g_malloc0(1);
432         
433         priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE (self);
434         
435         appendix = g_string_new("");
436         g_string_printf(appendix, "<HTML><BODY>\n<hr><h5>%s:</h5>\n", _("Attachments"));
437         
438         attachment_list = tny_msg_iface_get_parts(msg);
439         attachment = attachment_list;
440         while (attachment) {
441                 filename = "";
442                 content_type = tny_msg_mime_part_iface_get_content_type(
443                                                                                 TNY_MSG_MIME_PART_IFACE(attachment->data));
444                 if (!content_type)
445                         continue;
446
447                 if ((strcmp("image/jpeg", content_type) == 0) ||
448                         (strcmp("image/gif",  content_type) == 0)) {
449                         filename = tny_msg_mime_part_iface_get_filename(
450                                 TNY_MSG_MIME_PART_IFACE(attachment->data));
451                         if (!filename)
452                                 filename = "[unknown]";
453                         else
454                                 attachments_found = TRUE;
455                         id = tny_msg_mime_part_iface_get_content_id(
456                                                                                 TNY_MSG_MIME_PART_IFACE(attachment->data));
457                         if (modest_tny_msg_view_get_show_attachments_inline_flag(self)) {
458                                 virtual_filename = construct_virtual_filename(filename,
459                                         g_list_position((GList *)attachment_list, (GList *) attachment),
460                                         id, TRUE);
461                                 g_string_append_printf(appendix, "<IMG src=\"%s\">\n<BR>", virtual_filename);
462                                 g_free(virtual_filename);
463                         }
464                         virtual_filename = construct_virtual_filename(filename,
465                                         g_list_position((GList *)attachment_list, (GList *) attachment),
466                                         id, FALSE);
467                         g_string_append_printf(appendix,
468                                 "<A href=\"%s\">%s</A>: %s<BR>\n",
469                                 virtual_filename, filename, content_type);
470                         g_free(virtual_filename);
471                 }
472                 attachment = attachment->next;
473         }
474         g_string_append(appendix, "</BODY></HTML>");
475         if (!attachments_found)
476                 g_string_assign(appendix, "");
477         return g_string_free(appendix, FALSE);
478 }
479
480 static void
481 hyperlinkify_plain_text (GString *txt)
482 {
483         GSList *cursor;
484         GSList *match_list = get_url_matches (txt);
485
486         /* we will work backwards, so the offsets stay valid */
487         for (cursor = match_list; cursor; cursor = cursor->next) {
488
489                 url_match_t *match = (url_match_t*) cursor->data;
490                 gchar *url  = g_strndup (txt->str + match->offset, match->len);
491                 gchar *repl = NULL; /* replacement  */
492
493                 /* the prefix is NULL: use the one that is already there */
494                 repl = g_strdup_printf ("<a href=\"%s%s\">%s</a>",
495                                         match->prefix ? match->prefix : "", url, url);
496
497                 /* replace the old thing with our hyperlink
498                  * replacement thing */
499                 g_string_erase  (txt, match->offset, match->len);
500                 g_string_insert (txt, match->offset, repl);
501                 
502                 g_free (url);
503                 g_free (repl);
504
505                 g_free (cursor->data);  
506         }
507         
508         g_slist_free (match_list);
509 }
510
511
512
513 static gchar *
514 convert_to_html (const gchar *data)
515 {
516         int              i;
517         gboolean         first_space = TRUE;
518         GString         *html;      
519         gsize           len;
520
521         if (!data)
522                 return NULL;
523
524         len = strlen (data);
525         html = g_string_sized_new (len + 100);  /* just a  guess... */
526         
527         g_string_append_printf (html,
528                                 "<html>"
529                                 "<head>"
530                                 "<meta http-equiv=\"content-type\""
531                                 " content=\"text/html; charset=utf8\">"
532                                 "</head>"
533                                 "<body><tt>");
534         
535         /* replace with special html chars where needed*/
536         for (i = 0; i != len; ++i)  {
537                 char    kar = data[i]; 
538                 switch (kar) {
539                         
540                 case 0:  break; /* ignore embedded \0s */       
541                 case '<' : g_string_append   (html, "&lt;"); break;
542                 case '>' : g_string_append   (html, "&gt;"); break;
543                 case '&' : g_string_append   (html, "&quot;"); break;
544                 case '\n': g_string_append   (html, "<br>\n"); break;
545                 default:
546                         if (kar == ' ') {
547                                 g_string_append (html, first_space ? " " : "&nbsp;");
548                                 first_space = FALSE;
549                         } else  if (kar == '\t')
550                                 g_string_append (html, "&nbsp; &nbsp;&nbsp;");
551                         else {
552                                 int charnum = 0;
553                                 first_space = TRUE;
554                                 /* optimization trick: accumulate 'normal' chars, then copy */
555                                 do {
556                                         kar = data [++charnum + i];
557                                         
558                                 } while ((i + charnum < len) &&
559                                          (kar > '>' || (kar != '<' && kar != '>'
560                                                         && kar != '&' && kar !=  ' '
561                                                         && kar != '\n' && kar != '\t')));
562                                 g_string_append_len (html, &data[i], charnum);
563                                 i += (charnum  - 1);
564                         }
565                 }
566         }
567         
568         g_string_append (html, "</tt></body></html>");
569         hyperlinkify_plain_text (html);
570
571         return g_string_free (html, FALSE);
572 }
573
574
575
576
577 static gint 
578 cmp_offsets_reverse (const url_match_t *match1, const url_match_t *match2)
579 {
580         return match2->offset - match1->offset;
581 }
582
583
584
585 /*
586  * check if the match is inside an existing match... */
587 static void
588 chk_partial_match (const url_match_t *match, int* offset)
589 {
590         if (*offset >= match->offset && *offset < match->offset + match->len)
591                 *offset = -1;
592 }
593
594 static GSList*
595 get_url_matches (GString *txt)
596 {
597         regmatch_t rm;
598         int rv, i, offset = 0;
599         GSList *match_list = NULL;
600
601         static UrlMatchPattern patterns[] = MAIL_VIEWER_URL_MATCH_PATTERNS;
602         const size_t pattern_num = sizeof(patterns)/sizeof(UrlMatchPattern);
603
604         /* initalize the regexps */
605         for (i = 0; i != pattern_num; ++i) {
606                 patterns[i].preg = g_new0 (regex_t,1);
607                 g_assert(regcomp (patterns[i].preg, patterns[i].regex,
608                                   REG_ICASE|REG_EXTENDED|REG_NEWLINE) == 0);
609         }
610         /* find all the matches */
611         for (i = 0; i != pattern_num; ++i) {
612                 offset     = 0; 
613                 while (1) {
614                         int test_offset;
615                         if ((rv = regexec (patterns[i].preg, txt->str + offset, 1, &rm, 0)) != 0) {
616                                 g_assert (rv == REG_NOMATCH); /* this should not happen */
617                                 break; /* try next regexp */ 
618                         }
619                         if (rm.rm_so == -1)
620                                 break;
621
622                         /* FIXME: optimize this */
623                         /* to avoid partial matches on something that was already found... */
624                         /* check_partial_match will put -1 in the data ptr if that is the case */
625                         test_offset = offset + rm.rm_so;
626                         g_slist_foreach (match_list, (GFunc)chk_partial_match, &test_offset);
627                         
628                         /* make a list of our matches (<offset, len, prefix> tupels)*/
629                         if (test_offset != -1) {
630                                 url_match_t *match = g_new (url_match_t,1);
631                                 match->offset = offset + rm.rm_so;
632                                 match->len    = rm.rm_eo - rm.rm_so;
633                                 match->prefix = patterns[i].prefix;
634                                 match_list = g_slist_prepend (match_list, match);
635                         }
636                         offset += rm.rm_eo;
637                 }
638         }
639
640         for (i = 0; i != pattern_num; ++i) {
641                 regfree (patterns[i].preg);
642                 g_free  (patterns[i].preg);
643         } /* don't free patterns itself -- it's static */
644         
645         /* now sort the list, so the matches are in reverse order of occurence.
646          * that way, we can do the replacements starting from the end, so we don't need
647          * to recalculate the offsets
648          */
649         match_list = g_slist_sort (match_list,
650                                    (GCompareFunc)cmp_offsets_reverse); 
651         return match_list;      
652 }
653
654 static gboolean
655 fill_gtkhtml_with_txt (ModestTnyMsgView *self, GtkHTML* gtkhtml, const gchar* txt, TnyMsgIface *msg)
656 {
657         GString *html;
658         gchar *html_attachments;
659         
660         g_return_val_if_fail (gtkhtml, FALSE);
661         g_return_val_if_fail (txt, FALSE);
662
663         html = g_string_new(convert_to_html (txt));
664         html_attachments = attachments_as_html(self, msg);
665         g_string_append(html, html_attachments);
666
667         gtk_html_load_from_string (gtkhtml, html->str, html->len);
668         g_string_free (html, TRUE);
669         g_free(html_attachments);
670
671         return TRUE;
672 }
673
674
675
676 static gboolean
677 set_html_message (ModestTnyMsgView *self, TnyMsgMimePartIface *tny_body, TnyMsgIface *msg)
678 {
679         gchar *html_attachments;
680         TnyStreamIface *gtkhtml_stream; 
681         ModestTnyMsgViewPrivate *priv;
682         
683         g_return_val_if_fail (self, FALSE);
684         g_return_val_if_fail (tny_body, FALSE);
685         
686         priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE(self);
687
688         gtkhtml_stream =
689                 TNY_STREAM_IFACE(modest_tny_stream_gtkhtml_new
690                                  (gtk_html_begin(GTK_HTML(priv->gtkhtml))));
691         
692         tny_stream_iface_reset (gtkhtml_stream);
693         tny_msg_mime_part_iface_decode_to_stream (tny_body, gtkhtml_stream);
694         html_attachments = attachments_as_html(self, msg);
695         tny_stream_iface_write (gtkhtml_stream, html_attachments, strlen(html_attachments));
696         tny_stream_iface_reset (gtkhtml_stream);
697
698         g_object_unref (G_OBJECT(gtkhtml_stream));
699         g_free (html_attachments);
700         
701         return TRUE;
702 }
703
704
705 /* this is a hack --> we use the tny_text_buffer_stream to
706  * get the message text, then write to gtkhtml 'by hand' */
707 static gboolean
708 set_text_message (ModestTnyMsgView *self, TnyMsgMimePartIface *tny_body, TnyMsgIface *msg)
709 {
710         GtkTextBuffer *buf;
711         GtkTextIter begin, end;
712         TnyStreamIface* txt_stream;
713         gchar *txt;
714         ModestTnyMsgViewPrivate *priv;
715                 
716         g_return_val_if_fail (self, FALSE);
717         g_return_val_if_fail (tny_body, FALSE);
718
719         priv           = MODEST_TNY_MSG_VIEW_GET_PRIVATE(self);
720         
721         buf            = gtk_text_buffer_new (NULL);
722         txt_stream     = TNY_STREAM_IFACE(tny_text_buffer_stream_new (buf));
723                 
724         tny_stream_iface_reset (txt_stream);
725         tny_msg_mime_part_iface_decode_to_stream (tny_body, txt_stream);
726         tny_stream_iface_reset (txt_stream);            
727         
728         gtk_text_buffer_get_bounds (buf, &begin, &end);
729         txt = gtk_text_buffer_get_text (buf, &begin, &end, FALSE);
730         
731         fill_gtkhtml_with_txt (self, GTK_HTML(priv->gtkhtml), txt, msg);
732
733         g_object_unref (G_OBJECT(txt_stream));
734         g_object_unref (G_OBJECT(buf));
735
736         g_free (txt);
737         return TRUE;
738 }
739
740 gchar *
741 modest_tny_msg_view_get_selected_text (ModestTnyMsgView *self)
742 {
743         ModestTnyMsgViewPrivate *priv;
744         gchar *sel;
745         GtkWidget *html;
746         int len;
747         GtkClipboard *clip;
748
749         g_return_val_if_fail (self, NULL);
750         priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE(self);
751         html = priv->gtkhtml;
752         
753         /* I'm sure there is a better way to check for selected text */
754         sel = gtk_html_get_selection_html(GTK_HTML(html), &len);
755         if (!sel)
756                 return NULL;
757         
758         g_free(sel);
759         
760         clip = gtk_widget_get_clipboard(html, GDK_SELECTION_PRIMARY);
761         return gtk_clipboard_wait_for_text(clip);
762 }
763
764 void
765 modest_tny_msg_view_set_message (ModestTnyMsgView *self, TnyMsgIface *msg)
766 {
767         TnyMsgMimePartIface *body;
768         ModestTnyMsgViewPrivate *priv;
769
770         g_return_if_fail (self);
771         
772         priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE(self);
773
774         priv->msg = msg;
775         
776         fill_gtkhtml_with_txt (self, GTK_HTML(priv->gtkhtml), "", msg);
777         if (!msg) 
778                 return;
779         
780         body = modest_tny_msg_actions_find_body_part (msg, TRUE);
781         if (body) {
782                 if (tny_msg_mime_part_iface_content_type_is (body, "text/html"))
783                         set_html_message (self, body, msg);
784                 else
785                         set_text_message (self, body, msg);
786                 return;
787         } else {
788                 /* nothing to show */
789         }
790 }
791
792 void
793 modest_tny_msg_view_redraw (ModestTnyMsgView *self)
794 {
795         ModestTnyMsgViewPrivate *priv;
796
797         g_return_if_fail (self);
798         priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE(self);
799         modest_tny_msg_view_set_message(self, priv->msg);
800 }
801
802 gboolean
803 modest_tny_msg_view_get_show_attachments_inline_flag (ModestTnyMsgView *self)
804 {
805         ModestTnyMsgViewPrivate *priv;
806
807         g_return_val_if_fail (self, FALSE);
808         priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE(self);
809         return priv->show_attachments_inline;
810 }
811
812 gboolean
813 modest_tny_msg_view_set_show_attachments_inline_flag (ModestTnyMsgView *self, gboolean flag)
814 {
815         ModestTnyMsgViewPrivate *priv;
816         gboolean oldflag;
817
818         g_return_val_if_fail (self, FALSE);
819         priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE(self);
820         oldflag = priv->show_attachments_inline;
821         priv->show_attachments_inline = flag;
822         if (priv->show_attachments_inline != oldflag)
823                 modest_tny_msg_view_redraw(self);
824         return priv->show_attachments_inline;
825 }