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