* when sending, messages with and without attachments are now handled differently...
[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         else
172                 g_error("argh1");
173         
174         if (msg)
175                 modest_tny_msg_view_set_message (self, msg);
176         else
177                 g_message("empty");
178         
179         modest_tny_msg_view_set_show_attachments_inline_flag(self, show_attachments_inline);
180
181         return GTK_WIDGET(self);
182 }
183
184
185 static gboolean
186 on_link_clicked (GtkWidget *widget, const gchar *uri,
187                                  ModestTnyMsgView *msg_view)
188 {
189         
190         if (g_str_has_prefix(uri, ATTACHMENT_ID_LINK)) {
191                 /* save or open attachment */
192                 g_message ("link-to-save: %s", uri); /* FIXME */
193                 return TRUE;
194         }
195         g_message ("link clicked: %s", uri); /* FIXME */
196         return FALSE;
197         
198 }
199
200
201 static TnyMsgMimePartIface *
202 find_cid_image (TnyMsgIface *msg, const gchar *cid)
203 {
204         TnyMsgMimePartIface *part = NULL;
205         GList *parts;
206
207         g_return_val_if_fail (msg, NULL);
208         g_return_val_if_fail (cid, NULL);
209         
210         parts  = (GList*) tny_msg_iface_get_parts (msg);
211         while (parts && !part) {
212                 const gchar *part_cid;
213                 part = TNY_MSG_MIME_PART_IFACE(parts->data);
214                 part_cid = tny_msg_mime_part_iface_get_content_id (part);
215                 printf("CMP:%s:%s\n", cid, part_cid);
216                 if (part_cid && strcmp (cid, part_cid) == 0)
217                         return part; /* we found it! */
218                 
219                 part = NULL;
220                 parts = parts->next;
221         }
222         
223         return part;
224 }
225
226
227 static TnyMsgMimePartIface *
228 find_attachment_by_filename (TnyMsgIface *msg, const gchar *fn)
229 {
230         TnyMsgMimePartIface *part = NULL;
231         GList *parts;
232         gchar *dummy;
233         gint pos;
234
235         g_return_val_if_fail (msg, NULL);
236         g_return_val_if_fail (fn, NULL);
237         
238         parts  = (GList*) tny_msg_iface_get_parts (msg);
239         pos = virtual_filename_get_pos(fn);
240         
241         if ((pos < 0) || (pos >= g_list_length(parts)))
242                 return NULL;
243         
244         part = g_list_nth_data(parts, pos);
245         
246         dummy = construct_virtual_filename_from_mime_part(part, pos);
247         if (strcmp(dummy, fn) == 0) {
248                 g_free(dummy);
249                 return part;
250         } else {
251                 g_free(dummy);
252                 return NULL;
253         }
254 }
255
256
257 static gboolean
258 on_url_requested (GtkWidget *widget, const gchar *uri,
259                   GtkHTMLStream *stream,
260                   ModestTnyMsgView *msg_view)
261 {
262         
263         ModestTnyMsgViewPrivate *priv;
264         priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE (msg_view);
265
266         g_message ("url requested: %s", uri);
267         
268         if (!modest_tny_msg_view_get_show_attachments_inline_flag(msg_view))
269                 return TRUE; /* debatable */
270         
271         if (g_str_has_prefix (uri, "cid:")) {
272                 /* +4 ==> skip "cid:" */
273                 
274                 TnyMsgMimePartIface *part = find_cid_image (priv->msg, uri + 4);
275                 if (!part) {
276                         g_message ("%s not found", uri + 4);
277                         gtk_html_stream_close (stream, GTK_HTML_STREAM_ERROR);
278                 } else {
279                         TnyStreamIface *tny_stream =
280                                 TNY_STREAM_IFACE(modest_tny_stream_gtkhtml_new(stream));
281                         tny_msg_mime_part_iface_decode_to_stream (part,tny_stream);
282                         gtk_html_stream_close (stream, GTK_HTML_STREAM_OK);
283                 }
284         } else if (g_str_has_prefix (uri, ATTACHMENT_ID_INLINE)) {
285                 TnyMsgMimePartIface *part;
286                 part = find_attachment_by_filename (priv->msg, uri);
287                 if (!part) {
288                         g_message ("%s not found", uri);
289                         gtk_html_stream_close (stream, GTK_HTML_STREAM_ERROR);
290                 } else {
291                         TnyStreamIface *tny_stream =
292                                 TNY_STREAM_IFACE(modest_tny_stream_gtkhtml_new(stream));
293                         tny_msg_mime_part_iface_decode_to_stream (part,tny_stream);
294                         gtk_html_stream_close (stream, GTK_HTML_STREAM_OK);
295                 }
296         }
297         return TRUE;
298 }
299
300
301 typedef struct  {
302         guint offset;
303         guint len;
304         const gchar* prefix;
305 } url_match_t;
306
307
308 static gchar *
309 secure_filename(const gchar *fn)
310 {
311         gchar *tmp, *p;
312         GString *s;
313         
314         s = g_string_new("");
315 #if 1 || DEBUG
316         tmp = g_strdup(fn);
317         for (p = tmp; p[0] ; p++ ) {
318                 p[0] &= 0x5f; /* 01011111 */
319                 p[0] |= 0x40; /* 01000000 */
320         }
321         g_string_printf(s, "0x%x:%s", g_str_hash(fn), tmp);
322         g_free(tmp);
323         return g_string_free(s, FALSE);
324 #else
325         g_string_printf(s, "0x%x", g_str_hash(fn));
326         return g_string_free(s, FALSE);
327 #endif
328 }
329         
330         
331 static gchar *
332 construct_virtual_filename(const gchar *filename,
333                            const gint position,
334                            const gchar *id,
335                            const gboolean active)
336 {
337         GString *s;
338         gchar *fn;
339         
340         if (position < 0)
341                 return g_strdup("AttachmentInvalid");
342
343         s = g_string_new("");
344         if (active)
345                 g_string_append(s, ATTACHMENT_ID_INLINE);
346         else
347                 g_string_append(s, ATTACHMENT_ID_LINK);
348         g_string_append_printf(s, ":%d:", position);
349         if (id)
350                 g_string_append(s, id);
351         g_string_append_c(s, ':');
352         
353         fn = secure_filename(filename);
354         if (fn)
355                 g_string_append(s, fn);
356         g_free(fn);
357         g_string_append_c(s, ':');
358         return g_string_free(s, FALSE);
359 }
360
361
362 static gchar *
363 construct_virtual_filename_from_mime_part(TnyMsgMimePartIface *msg, const gint position)
364 {
365         const gchar *id, *filename;
366         const gboolean active = TRUE;
367         
368         filename = tny_msg_mime_part_iface_get_filename(
369                                                                         TNY_MSG_MIME_PART_IFACE(msg));
370         if (!filename)
371                 filename = "[unknown]";
372         id = tny_msg_mime_part_iface_get_content_id(
373                                                                         TNY_MSG_MIME_PART_IFACE(msg));
374         
375         return construct_virtual_filename(filename, position, id, active);
376 }
377
378 const gchar *
379 get_next_token(const gchar *s, gint *len)
380 {
381         gchar *i1, *i2;
382         i1 = (char *) s;
383         i2 = (char *) s;
384         
385         while (i2[0]) {
386                 if (i2[0] == ':')
387                         break;
388                 i2++;
389         }
390         if (!i2[0])
391                 return NULL;
392         *len = i2 - i1;
393         return ++i2;
394 }
395
396 /* maybe I should use libregexp */
397 gint
398 virtual_filename_get_pos(const gchar *filename)
399 {
400         const gchar *i1, *i2;
401         gint len, pos;
402         GString *dummy;
403         
404         /* check prefix */
405         if ((!g_str_has_prefix(filename, ATTACHMENT_ID_INLINE ":")) &&
406             (!g_str_has_prefix(filename, ATTACHMENT_ID_LINK ":")))
407                 return -1;
408
409         i2 = filename;
410         i2 = get_next_token(i2, &len);
411         i1 = i2;
412                 
413         /* get position */
414         i2 = get_next_token(i2, &len);
415         if (i2 == NULL)
416                 return -1;
417         dummy = g_string_new_len(i1, len);
418         pos = atoi(dummy->str);
419         g_string_free(dummy, FALSE);
420         return pos;
421 }       
422
423
424 static gchar *
425 attachments_as_html(ModestTnyMsgView *self, TnyMsgIface *msg)
426 {
427         ModestTnyMsgViewPrivate *priv;
428         gboolean attachments_found = FALSE;
429         GString *appendix;
430         const GList *attachment_list, *attachment;
431         const gchar *content_type, *filename, *id;
432         gchar *virtual_filename;
433         
434         if (!msg)
435                 return g_malloc0(1);
436         
437         priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE (self);
438         
439         appendix = g_string_new("");
440         g_string_printf(appendix, "<HTML><BODY>\n<hr><h5>%s:</h5>\n", _("Attachments"));
441         
442         attachment_list = tny_msg_iface_get_parts(msg);
443         attachment = attachment_list;
444         while (attachment) {
445                 filename = "";
446                 content_type = tny_msg_mime_part_iface_get_content_type(
447                                                                                 TNY_MSG_MIME_PART_IFACE(attachment->data));
448                 if (!content_type)
449                         continue;
450
451                 if ((strcmp("image/jpeg", content_type) == 0) ||
452                         (strcmp("image/gif",  content_type) == 0)) {
453                         filename = tny_msg_mime_part_iface_get_filename(
454                                 TNY_MSG_MIME_PART_IFACE(attachment->data));
455                         if (!filename)
456                                 filename = "[unknown]";
457                         else
458                                 attachments_found = TRUE;
459                         id = tny_msg_mime_part_iface_get_content_id(
460                                                                                 TNY_MSG_MIME_PART_IFACE(attachment->data));
461                         if (modest_tny_msg_view_get_show_attachments_inline_flag(self)) {
462                                 virtual_filename = construct_virtual_filename(filename,
463                                         g_list_position((GList *)attachment_list, (GList *) attachment),
464                                         id, TRUE);
465                                 g_string_append_printf(appendix, "<IMG src=\"%s\">\n<BR>", virtual_filename);
466                                 g_free(virtual_filename);
467                         }
468                         virtual_filename = construct_virtual_filename(filename,
469                                         g_list_position((GList *)attachment_list, (GList *) attachment),
470                                         id, FALSE);
471                         g_string_append_printf(appendix,
472                                 "<A href=\"%s\">%s</A>: %s<BR>\n",
473                                 virtual_filename, filename, content_type);
474                         g_free(virtual_filename);
475                 }
476                 attachment = attachment->next;
477         }
478         g_string_append(appendix, "</BODY></HTML>");
479         if (!attachments_found)
480                 g_string_assign(appendix, "");
481         return g_string_free(appendix, FALSE);
482 }
483
484 static void
485 hyperlinkify_plain_text (GString *txt)
486 {
487         GSList *cursor;
488         GSList *match_list = get_url_matches (txt);
489
490         /* we will work backwards, so the offsets stay valid */
491         for (cursor = match_list; cursor; cursor = cursor->next) {
492
493                 url_match_t *match = (url_match_t*) cursor->data;
494                 gchar *url  = g_strndup (txt->str + match->offset, match->len);
495                 gchar *repl = NULL; /* replacement  */
496
497                 /* the prefix is NULL: use the one that is already there */
498                 repl = g_strdup_printf ("<a href=\"%s%s\">%s</a>",
499                                         match->prefix ? match->prefix : "", url, url);
500
501                 /* replace the old thing with our hyperlink
502                  * replacement thing */
503                 g_string_erase  (txt, match->offset, match->len);
504                 g_string_insert (txt, match->offset, repl);
505                 
506                 g_free (url);
507                 g_free (repl);
508
509                 g_free (cursor->data);  
510         }
511         
512         g_slist_free (match_list);
513 }
514
515
516
517 static gchar *
518 convert_to_html (const gchar *data)
519 {
520         int              i;
521         gboolean         first_space = TRUE;
522         GString         *html;      
523         gsize           len;
524
525         if (!data)
526                 return NULL;
527
528         len = strlen (data);
529         html = g_string_sized_new (len + 100);  /* just a  guess... */
530         
531         g_string_append_printf (html,
532                                 "<html>"
533                                 "<head>"
534                                 "<meta http-equiv=\"content-type\""
535                                 " content=\"text/html; charset=utf8\">"
536                                 "</head>"
537                                 "<body><tt>");
538         
539         /* replace with special html chars where needed*/
540         for (i = 0; i != len; ++i)  {
541                 char    kar = data[i]; 
542                 switch (kar) {
543                         
544                 case 0:  break; /* ignore embedded \0s */       
545                 case '<' : g_string_append   (html, "&lt;"); break;
546                 case '>' : g_string_append   (html, "&gt;"); break;
547                 case '&' : g_string_append   (html, "&quot;"); break;
548                 case '\n': g_string_append   (html, "<br>\n"); break;
549                 default:
550                         if (kar == ' ') {
551                                 g_string_append (html, first_space ? " " : "&nbsp;");
552                                 first_space = FALSE;
553                         } else  if (kar == '\t')
554                                 g_string_append (html, "&nbsp; &nbsp;&nbsp;");
555                         else {
556                                 int charnum = 0;
557                                 first_space = TRUE;
558                                 /* optimization trick: accumulate 'normal' chars, then copy */
559                                 do {
560                                         kar = data [++charnum + i];
561                                         
562                                 } while ((i + charnum < len) &&
563                                          (kar > '>' || (kar != '<' && kar != '>'
564                                                         && kar != '&' && kar !=  ' '
565                                                         && kar != '\n' && kar != '\t')));
566                                 g_string_append_len (html, &data[i], charnum);
567                                 i += (charnum  - 1);
568                         }
569                 }
570         }
571         
572         g_string_append (html, "</tt></body></html>");
573         hyperlinkify_plain_text (html);
574
575         return g_string_free (html, FALSE);
576 }
577
578
579
580
581 static gint 
582 cmp_offsets_reverse (const url_match_t *match1, const url_match_t *match2)
583 {
584         return match2->offset - match1->offset;
585 }
586
587
588
589 /*
590  * check if the match is inside an existing match... */
591 static void
592 chk_partial_match (const url_match_t *match, int* offset)
593 {
594         if (*offset >= match->offset && *offset < match->offset + match->len)
595                 *offset = -1;
596 }
597
598 static GSList*
599 get_url_matches (GString *txt)
600 {
601         regmatch_t rm;
602         int rv, i, offset = 0;
603         GSList *match_list = NULL;
604
605         static UrlMatchPattern patterns[] = MAIL_VIEWER_URL_MATCH_PATTERNS;
606         const size_t pattern_num = sizeof(patterns)/sizeof(UrlMatchPattern);
607
608         /* initalize the regexps */
609         for (i = 0; i != pattern_num; ++i) {
610                 patterns[i].preg = g_new0 (regex_t,1);
611                 g_assert(regcomp (patterns[i].preg, patterns[i].regex,
612                                   REG_ICASE|REG_EXTENDED|REG_NEWLINE) == 0);
613         }
614         /* find all the matches */
615         for (i = 0; i != pattern_num; ++i) {
616                 offset     = 0; 
617                 while (1) {
618                         int test_offset;
619                         if ((rv = regexec (patterns[i].preg, txt->str + offset, 1, &rm, 0)) != 0) {
620                                 g_assert (rv == REG_NOMATCH); /* this should not happen */
621                                 break; /* try next regexp */ 
622                         }
623                         if (rm.rm_so == -1)
624                                 break;
625
626                         /* FIXME: optimize this */
627                         /* to avoid partial matches on something that was already found... */
628                         /* check_partial_match will put -1 in the data ptr if that is the case */
629                         test_offset = offset + rm.rm_so;
630                         g_slist_foreach (match_list, (GFunc)chk_partial_match, &test_offset);
631                         
632                         /* make a list of our matches (<offset, len, prefix> tupels)*/
633                         if (test_offset != -1) {
634                                 url_match_t *match = g_new (url_match_t,1);
635                                 match->offset = offset + rm.rm_so;
636                                 match->len    = rm.rm_eo - rm.rm_so;
637                                 match->prefix = patterns[i].prefix;
638                                 match_list = g_slist_prepend (match_list, match);
639                         }
640                         offset += rm.rm_eo;
641                 }
642         }
643
644         for (i = 0; i != pattern_num; ++i) {
645                 regfree (patterns[i].preg);
646                 g_free  (patterns[i].preg);
647         } /* don't free patterns itself -- it's static */
648         
649         /* now sort the list, so the matches are in reverse order of occurence.
650          * that way, we can do the replacements starting from the end, so we don't need
651          * to recalculate the offsets
652          */
653         match_list = g_slist_sort (match_list,
654                                    (GCompareFunc)cmp_offsets_reverse); 
655         return match_list;      
656 }
657
658 static gboolean
659 fill_gtkhtml_with_txt (ModestTnyMsgView *self, GtkHTML* gtkhtml, const gchar* txt, TnyMsgIface *msg)
660 {
661         GString *html;
662         gchar *html_attachments;
663         
664         g_return_val_if_fail (gtkhtml, FALSE);
665         g_return_val_if_fail (txt, FALSE);
666
667         html = g_string_new(convert_to_html (txt));
668         html_attachments = attachments_as_html(self, msg);
669         g_string_append(html, html_attachments);
670
671         gtk_html_load_from_string (gtkhtml, html->str, html->len);
672         g_string_free (html, TRUE);
673         g_free(html_attachments);
674
675         return TRUE;
676 }
677
678
679
680 static gboolean
681 set_html_message (ModestTnyMsgView *self, TnyMsgMimePartIface *tny_body, TnyMsgIface *msg)
682 {
683         gchar *html_attachments;
684         TnyStreamIface *gtkhtml_stream; 
685         ModestTnyMsgViewPrivate *priv;
686         
687         g_return_val_if_fail (self, FALSE);
688         g_return_val_if_fail (tny_body, FALSE);
689         
690         priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE(self);
691
692         gtkhtml_stream =
693                 TNY_STREAM_IFACE(modest_tny_stream_gtkhtml_new
694                                  (gtk_html_begin(GTK_HTML(priv->gtkhtml))));
695         
696         tny_stream_iface_reset (gtkhtml_stream);
697         tny_msg_mime_part_iface_decode_to_stream (tny_body, gtkhtml_stream);
698         html_attachments = attachments_as_html(self, msg);
699         tny_stream_iface_write (gtkhtml_stream, html_attachments, strlen(html_attachments));
700         tny_stream_iface_reset (gtkhtml_stream);
701
702         g_object_unref (G_OBJECT(gtkhtml_stream));
703         g_free (html_attachments);
704         
705         return TRUE;
706 }
707
708
709 /* this is a hack --> we use the tny_text_buffer_stream to
710  * get the message text, then write to gtkhtml 'by hand' */
711 static gboolean
712 set_text_message (ModestTnyMsgView *self, TnyMsgMimePartIface *tny_body, TnyMsgIface *msg)
713 {
714         GtkTextBuffer *buf;
715         GtkTextIter begin, end;
716         TnyStreamIface* txt_stream;
717         gchar *txt;
718         ModestTnyMsgViewPrivate *priv;
719                 
720         g_return_val_if_fail (self, FALSE);
721         g_return_val_if_fail (tny_body, FALSE);
722
723         priv           = MODEST_TNY_MSG_VIEW_GET_PRIVATE(self);
724         
725         buf            = gtk_text_buffer_new (NULL);
726         txt_stream     = TNY_STREAM_IFACE(tny_text_buffer_stream_new (buf));
727                 
728         tny_stream_iface_reset (txt_stream);
729         tny_msg_mime_part_iface_decode_to_stream (tny_body, txt_stream);
730         tny_stream_iface_reset (txt_stream);            
731         
732         gtk_text_buffer_get_bounds (buf, &begin, &end);
733         txt = gtk_text_buffer_get_text (buf, &begin, &end, FALSE);
734         
735         fill_gtkhtml_with_txt (self, GTK_HTML(priv->gtkhtml), txt, msg);
736
737         g_object_unref (G_OBJECT(txt_stream));
738         g_object_unref (G_OBJECT(buf));
739
740         g_free (txt);
741         return TRUE;
742 }
743
744 gchar *
745 modest_tny_msg_view_get_selected_text (ModestTnyMsgView *self)
746 {
747         ModestTnyMsgViewPrivate *priv;
748         gchar *sel;
749         GtkWidget *html;
750         int len;
751         GtkClipboard *clip;
752
753         g_return_val_if_fail (self, NULL);
754         priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE(self);
755         html = priv->gtkhtml;
756         
757         /* I'm sure there is a better way to check for selected text */
758         sel = gtk_html_get_selection_html(GTK_HTML(html), &len);
759         if (!sel)
760                 return NULL;
761         
762         g_free(sel);
763         
764         clip = gtk_widget_get_clipboard(html, GDK_SELECTION_PRIMARY);
765         return gtk_clipboard_wait_for_text(clip);
766 }
767
768 void
769 modest_tny_msg_view_set_message (ModestTnyMsgView *self, TnyMsgIface *msg)
770 {
771         TnyMsgMimePartIface *body;
772         ModestTnyMsgViewPrivate *priv;
773
774         g_return_if_fail (self);
775         
776         priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE(self);
777
778         priv->msg = msg;
779         
780         fill_gtkhtml_with_txt (self, GTK_HTML(priv->gtkhtml), "", msg);
781
782         if (!msg) 
783                 return;
784         
785         body = modest_tny_msg_actions_find_body_part (msg, "text/html");
786         if (body) {
787                 set_html_message (self, body, msg);
788                 return;
789         }
790         
791         body = modest_tny_msg_actions_find_body_part (msg, "text/plain");
792         if (body) {
793                 set_text_message (self, body, msg);
794                 return;
795         }
796
797         /* hmmmmm */
798         fill_gtkhtml_with_txt (self, GTK_HTML(priv->gtkhtml),
799                                 _("Unsupported message type"), msg);
800 }
801
802 void
803 modest_tny_msg_view_redraw (ModestTnyMsgView *self)
804 {
805         ModestTnyMsgViewPrivate *priv;
806
807         g_return_if_fail (self);
808         priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE(self);
809         modest_tny_msg_view_set_message(self, priv->msg);
810 }
811
812 gboolean
813 modest_tny_msg_view_get_show_attachments_inline_flag (ModestTnyMsgView *self)
814 {
815         ModestTnyMsgViewPrivate *priv;
816
817         g_return_val_if_fail (self, FALSE);
818         priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE(self);
819         return priv->show_attachments_inline;
820 }
821
822 gboolean
823 modest_tny_msg_view_set_show_attachments_inline_flag (ModestTnyMsgView *self, gboolean flag)
824 {
825         ModestTnyMsgViewPrivate *priv;
826         gboolean oldflag;
827
828         g_return_val_if_fail (self, FALSE);
829         priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE(self);
830         oldflag = priv->show_attachments_inline;
831         priv->show_attachments_inline = flag;
832         if (priv->show_attachments_inline != oldflag)
833                 modest_tny_msg_view_redraw(self);
834         return priv->show_attachments_inline;
835 }