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