0f5262cd234507955be0a14f36d76e5e127f6f99
[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
32
33 /*
34  * we need these regexps to find URLs in plain text e-mails
35  */
36 typedef struct _UrlMatchPattern UrlMatchPattern;
37 struct _UrlMatchPattern {
38         gchar   *regex;
39         regex_t *preg;
40         gchar   *prefix;
41         
42 };
43 #define MAIL_VIEWER_URL_MATCH_PATTERNS  {\
44         { "(file|http|ftp|https)://[-A-Za-z0-9_$.+!*(),;:@%&=?/~#]+[-A-Za-z0-9_$%&=?/~#]",\
45           NULL, NULL },\
46         { "www\\.[-a-z0-9.]+[-a-z0-9](:[0-9]*)?(/[-A-Za-z0-9_$.+!*(),;:@%&=?/~#]*[^]}\\),?!;:\"]?)?",\
47           NULL, "http://" },\
48         { "ftp\\.[-a-z0-9.]+[-a-z0-9](:[0-9]*)?(/[-A-Za-z0-9_$.+!*(),;:@%&=?/~#]*[^]}\\),?!;:\"]?)?",\
49           NULL, "ftp://" },\
50         { "(voipto|callto|chatto|jabberto|xmpp):[-_a-z@0-9.\\+]+", \
51            NULL, NULL},                                             \
52         { "mailto:[-_a-z0-9.\\+]+@[-_a-z0-9.]+",                    \
53           NULL, NULL},\
54         { "[-_a-z0-9.\\+]+@[-_a-z0-9.]+",\
55           NULL, "mailto:"}\
56         }
57
58
59 /* list my signals */
60 enum {
61         /* MY_SIGNAL_1, */
62         /* MY_SIGNAL_2, */
63         LAST_SIGNAL
64 };
65
66 typedef struct _ModestTnyMsgViewPrivate ModestTnyMsgViewPrivate;
67 struct _ModestTnyMsgViewPrivate {
68         GtkWidget *gtkhtml;
69         TnyMsgIface *msg;
70         gboolean show_attachments;
71 };
72 #define MODEST_TNY_MSG_VIEW_GET_PRIVATE(o)      (G_TYPE_INSTANCE_GET_PRIVATE((o), \
73                                                  MODEST_TYPE_TNY_MSG_VIEW, \
74                                                  ModestTnyMsgViewPrivate))
75 /* globals */
76 static GtkContainerClass *parent_class = NULL;
77
78 /* uncomment the following if you have defined any signals */
79 /* static guint signals[LAST_SIGNAL] = {0}; */
80
81 GType
82 modest_tny_msg_view_get_type (void)
83 {
84         static GType my_type = 0;
85         if (!my_type) {
86                 static const GTypeInfo my_info = {
87                         sizeof(ModestTnyMsgViewClass),
88                         NULL,           /* base init */
89                         NULL,           /* base finalize */
90                         (GClassInitFunc) modest_tny_msg_view_class_init,
91                         NULL,           /* class finalize */
92                         NULL,           /* class data */
93                         sizeof(ModestTnyMsgView),
94                         1,              /* n_preallocs */
95                         (GInstanceInitFunc) modest_tny_msg_view_init,
96                 };
97                 my_type = g_type_register_static (GTK_TYPE_SCROLLED_WINDOW,
98                                                   "ModestTnyMsgView",
99                                                   &my_info, 0);
100         }
101         return my_type;
102 }
103
104 static void
105 modest_tny_msg_view_class_init (ModestTnyMsgViewClass *klass)
106 {
107         GObjectClass *gobject_class;
108         gobject_class = (GObjectClass*) klass;
109
110         parent_class            = g_type_class_peek_parent (klass);
111         gobject_class->finalize = modest_tny_msg_view_finalize;
112
113         g_type_class_add_private (gobject_class, sizeof(ModestTnyMsgViewPrivate));
114 }
115
116 static void
117 modest_tny_msg_view_init (ModestTnyMsgView *obj)
118 {
119         ModestTnyMsgViewPrivate *priv;
120         
121         priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE(obj);
122
123         priv->msg = NULL;
124         
125         priv->gtkhtml = gtk_html_new();
126         
127         /* TODO: -> conf-mgr */
128         priv->show_attachments = FALSE;
129
130         gtk_html_set_editable        (GTK_HTML(priv->gtkhtml), FALSE);
131         gtk_html_allow_selection     (GTK_HTML(priv->gtkhtml), TRUE);
132         gtk_html_set_caret_mode      (GTK_HTML(priv->gtkhtml), FALSE);
133         gtk_html_set_blocking        (GTK_HTML(priv->gtkhtml), FALSE);
134         gtk_html_set_images_blocking (GTK_HTML(priv->gtkhtml), FALSE);
135         
136         g_signal_connect (G_OBJECT(priv->gtkhtml), "link_clicked",
137                           G_CALLBACK(on_link_clicked), obj);
138         
139         g_signal_connect (G_OBJECT(priv->gtkhtml), "url_requested",
140                           G_CALLBACK(on_url_requested), obj);
141 }
142         
143
144 static void
145 modest_tny_msg_view_finalize (GObject *obj)
146 {       
147         
148 }
149
150 GtkWidget*
151 modest_tny_msg_view_new (TnyMsgIface *msg)
152 {
153         GObject *obj;
154         ModestTnyMsgView* self;
155         ModestTnyMsgViewPrivate *priv;
156         
157         obj  = G_OBJECT(g_object_new(MODEST_TYPE_TNY_MSG_VIEW, NULL));
158         self = MODEST_TNY_MSG_VIEW(obj);
159         priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE (self);
160
161         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(self),
162                                        GTK_POLICY_AUTOMATIC,
163                                        GTK_POLICY_AUTOMATIC);
164
165         if (priv->gtkhtml) 
166                 gtk_container_add (GTK_CONTAINER(obj), priv->gtkhtml);  
167         
168         if (msg)
169                 modest_tny_msg_view_set_message (self, msg);
170
171         return GTK_WIDGET(self);
172 }
173
174
175
176 static gboolean
177 on_link_clicked (GtkWidget *widget, const gchar *uri,
178                                  ModestTnyMsgView *msg_view)
179 {
180         ModestTnyMsgViewPrivate *priv;
181         
182         
183         if (g_str_has_prefix(uri, "attachment:")) {
184                 priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE(msg_view);
185                 priv->show_attachments = !priv->show_attachments;
186                 modest_tny_msg_view_set_message(msg_view, priv->msg);
187                 return TRUE;
188         }
189         g_message ("link clicked: %s", uri); /* FIXME */
190         return FALSE;
191         
192 }
193
194
195
196 static TnyMsgMimePartIface *
197 find_cid_image (TnyMsgIface *msg, const gchar *cid)
198 {
199         TnyMsgMimePartIface *part = NULL;
200         GList *parts;
201
202         g_return_val_if_fail (msg, NULL);
203         g_return_val_if_fail (cid, NULL);
204         
205         parts  = (GList*) tny_msg_iface_get_parts (msg);
206         while (parts && !part) {
207                 const gchar *part_cid;
208                 part = TNY_MSG_MIME_PART_IFACE(parts->data);
209                 part_cid = tny_msg_mime_part_iface_get_content_id (part);
210                 printf("CMP:%s:%s\n", cid, part_cid);
211                 if (part_cid && strcmp (cid, part_cid) == 0)
212                         return part; /* we found it! */
213                 
214                 part = NULL;
215                 parts = parts->next;
216         }
217         
218         return part;
219 }
220
221 static TnyMsgMimePartIface *
222 find_attachment_by_filename (TnyMsgIface *msg, const gchar *fn)
223 {
224         TnyMsgMimePartIface *part = NULL;
225         GList *parts;
226
227         g_return_val_if_fail (msg, NULL);
228         g_return_val_if_fail (fn, NULL);
229         
230         parts  = (GList*) tny_msg_iface_get_parts (msg);
231         while (parts && !part) {
232                 const gchar *part_fn;
233                 part = TNY_MSG_MIME_PART_IFACE(parts->data);
234                 part_fn = tny_msg_mime_part_iface_get_filename (part);
235                 if (part_fn && strcmp (fn, part_fn) == 0)
236                         return part; /* we found it! */
237                 
238                 part = NULL;
239                 parts = parts->next;
240         }
241         
242         return part;
243 }
244
245 static gboolean
246 on_url_requested (GtkWidget *widget, const gchar *uri,
247                   GtkHTMLStream *stream,
248                   ModestTnyMsgView *msg_view)
249 {
250         
251         ModestTnyMsgViewPrivate *priv;
252         priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE (msg_view);
253
254         g_message ("url requested: %s", uri);
255         
256         if (g_str_has_prefix (uri, "cid:")) {
257                 /* +4 ==> skip "cid:" */
258                 
259                 TnyMsgMimePartIface *part = find_cid_image (priv->msg, uri + 4);
260                 if (!part) {
261                         g_message ("%s not found", uri + 4);
262                         gtk_html_stream_close (stream, GTK_HTML_STREAM_ERROR);
263                 } else {
264                         TnyStreamIface *tny_stream =
265                                 TNY_STREAM_IFACE(modest_tny_stream_gtkhtml_new(stream));
266                         tny_msg_mime_part_iface_decode_to_stream (part,tny_stream);
267                         gtk_html_stream_close (stream, GTK_HTML_STREAM_OK);
268                 }
269         } else if (g_str_has_prefix (uri, "Attachment:")) {
270                 /* +11 ==> skip... */
271                 
272                 TnyMsgMimePartIface *part = find_attachment_by_filename (priv->msg, uri + 11);
273                 if (!part) {
274                         g_message ("%s not found", uri + 11);
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         }
283         return TRUE;
284 }
285
286
287
288
289 typedef struct  {
290         guint offset;
291         guint len;
292         const gchar* prefix;
293 } url_match_t;
294
295
296 static gchar *
297 attachments_as_html(ModestTnyMsgView *self, TnyMsgIface *msg)
298 {
299         ModestTnyMsgViewPrivate *priv;
300         gboolean attachments_found = FALSE;
301         GString *appendix;
302         const GList *attachment_list, *attachment;
303         const gchar *content_type, *filename;
304         
305         if (!msg)
306                 return g_malloc0(1);
307         
308         priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE (self);
309         
310         /* CLEANUP: starting a new HTML may be unsupported */
311         appendix = g_string_new("<HTML><BODY>\n<hr><h5>Attachments:</h5>\n");
312         
313         attachment_list = tny_msg_iface_get_parts(msg);
314         attachment = attachment_list;
315         while (attachment) {
316                 filename = "";
317                 content_type = tny_msg_mime_part_iface_get_content_type(
318                                                                                 TNY_MSG_MIME_PART_IFACE(attachment->data));
319                 g_return_val_if_fail(content_type, NULL);
320                 if (      tny_msg_mime_part_iface_content_type_is(
321                                                                                 TNY_MSG_MIME_PART_IFACE(attachment->data),
322                                                                                 "image/jpeg")
323                            || tny_msg_mime_part_iface_content_type_is(
324                                                                                 TNY_MSG_MIME_PART_IFACE(attachment->data),
325                                                                                 "image/gif")) {
326                         filename = tny_msg_mime_part_iface_get_filename(
327                                                                                 TNY_MSG_MIME_PART_IFACE(attachment->data));
328                         if (!filename)
329                                 filename = "[unknown]";
330                         else
331                                 attachments_found = TRUE;
332                         if (priv->show_attachments) {
333                                 g_string_append_printf(appendix, "<IMG src=\"Attachment:%s\">\n<BR><A href=\"attachment:%s\">%s</A>\n", filename, filename, filename);
334                         } else {
335                                 g_string_append_printf(appendix, "<A href=\"attachment:%s\">%s</A>: %s<BR>\n", filename, filename, content_type);
336                         }
337                 }
338                 attachment = attachment->next;
339         }
340         g_string_append(appendix, "</BODY></HTML>");
341         if (!attachments_found)
342                 g_string_assign(appendix, "");
343         return g_string_free(appendix, FALSE);
344 }
345
346 static void
347 hyperlinkify_plain_text (GString *txt)
348 {
349         GSList *cursor;
350         GSList *match_list = get_url_matches (txt);
351
352         /* we will work backwards, so the offsets stay valid */
353         for (cursor = match_list; cursor; cursor = cursor->next) {
354
355                 url_match_t *match = (url_match_t*) cursor->data;
356                 gchar *url  = g_strndup (txt->str + match->offset, match->len);
357                 gchar *repl = NULL; /* replacement  */
358
359                 /* the prefix is NULL: use the one that is already there */
360                 repl = g_strdup_printf ("<a href=\"%s%s\">%s</a>",
361                                         match->prefix ? match->prefix : "", url, url);
362
363                 /* replace the old thing with our hyperlink
364                  * replacement thing */
365                 g_string_erase  (txt, match->offset, match->len);
366                 g_string_insert (txt, match->offset, repl);
367                 
368                 g_free (url);
369                 g_free (repl);
370
371                 g_free (cursor->data);  
372         }
373         
374         g_slist_free (match_list);
375 }
376
377
378
379 static gchar *
380 convert_to_html (const gchar *data)
381 {
382         int              i;
383         gboolean         first_space = TRUE;
384         GString         *html;      
385         gsize           len;
386
387         if (!data)
388                 return NULL;
389
390         len = strlen (data);
391         html = g_string_sized_new (len + 100);  /* just a  guess... */
392         
393         g_string_append_printf (html,
394                                 "<html>"
395                                 "<head>"
396                                 "<meta http-equiv=\"content-type\""
397                                 " content=\"text/html; charset=utf8\">"
398                                 "</head>"
399                                 "<body><tt>");
400         
401         /* replace with special html chars where needed*/
402         for (i = 0; i != len; ++i)  {
403                 char    kar = data[i]; 
404                 switch (kar) {
405                         
406                 case 0:  break; /* ignore embedded \0s */       
407                 case '<' : g_string_append   (html, "&lt;"); break;
408                 case '>' : g_string_append   (html, "&gt;"); break;
409                 case '&' : g_string_append   (html, "&quot;"); break;
410                 case '\n': g_string_append   (html, "<br>\n"); break;
411                 default:
412                         if (kar == ' ') {
413                                 g_string_append (html, first_space ? " " : "&nbsp;");
414                                 first_space = FALSE;
415                         } else  if (kar == '\t')
416                                 g_string_append (html, "&nbsp; &nbsp;&nbsp;");
417                         else {
418                                 int charnum = 0;
419                                 first_space = TRUE;
420                                 /* optimization trick: accumulate 'normal' chars, then copy */
421                                 do {
422                                         kar = data [++charnum + i];
423                                         
424                                 } while ((i + charnum < len) &&
425                                          (kar > '>' || (kar != '<' && kar != '>'
426                                                         && kar != '&' && kar !=  ' '
427                                                         && kar != '\n' && kar != '\t')));
428                                 g_string_append_len (html, &data[i], charnum);
429                                 i += (charnum  - 1);
430                         }
431                 }
432         }
433         
434         g_string_append (html, "</tt></body></html>");
435         hyperlinkify_plain_text (html);
436
437         return g_string_free (html, FALSE);
438 }
439
440
441
442
443 static gint 
444 cmp_offsets_reverse (const url_match_t *match1, const url_match_t *match2)
445 {
446         return match2->offset - match1->offset;
447 }
448
449
450
451 /*
452  * check if the match is inside an existing match... */
453 static void
454 chk_partial_match (const url_match_t *match, int* offset)
455 {
456         if (*offset >= match->offset && *offset < match->offset + match->len)
457                 *offset = -1;
458 }
459
460 static GSList*
461 get_url_matches (GString *txt)
462 {
463         regmatch_t rm;
464         int rv, i, offset = 0;
465         GSList *match_list = NULL;
466
467         static UrlMatchPattern patterns[] = MAIL_VIEWER_URL_MATCH_PATTERNS;
468         const size_t pattern_num = sizeof(patterns)/sizeof(UrlMatchPattern);
469
470         /* initalize the regexps */
471         for (i = 0; i != pattern_num; ++i) {
472                 patterns[i].preg = g_new0 (regex_t,1);
473                 g_assert(regcomp (patterns[i].preg, patterns[i].regex,
474                                   REG_ICASE|REG_EXTENDED|REG_NEWLINE) == 0);
475         }
476         /* find all the matches */
477         for (i = 0; i != pattern_num; ++i) {
478                 offset     = 0; 
479                 while (1) {
480                         int test_offset;
481                         if ((rv = regexec (patterns[i].preg, txt->str + offset, 1, &rm, 0)) != 0) {
482                                 g_assert (rv == REG_NOMATCH); /* this should not happen */
483                                 break; /* try next regexp */ 
484                         }
485                         if (rm.rm_so == -1)
486                                 break;
487
488                         /* FIXME: optimize this */
489                         /* to avoid partial matches on something that was already found... */
490                         /* check_partial_match will put -1 in the data ptr if that is the case */
491                         test_offset = offset + rm.rm_so;
492                         g_slist_foreach (match_list, (GFunc)chk_partial_match, &test_offset);
493                         
494                         /* make a list of our matches (<offset, len, prefix> tupels)*/
495                         if (test_offset != -1) {
496                                 url_match_t *match = g_new (url_match_t,1);
497                                 match->offset = offset + rm.rm_so;
498                                 match->len    = rm.rm_eo - rm.rm_so;
499                                 match->prefix = patterns[i].prefix;
500                                 match_list = g_slist_prepend (match_list, match);
501                         }
502                         offset += rm.rm_eo;
503                 }
504         }
505
506         for (i = 0; i != pattern_num; ++i) {
507                 regfree (patterns[i].preg);
508                 g_free  (patterns[i].preg);
509         } /* don't free patterns itself -- it's static */
510         
511         /* now sort the list, so the matches are in reverse order of occurence.
512          * that way, we can do the replacements starting from the end, so we don't need
513          * to recalculate the offsets
514          */
515         match_list = g_slist_sort (match_list,
516                                    (GCompareFunc)cmp_offsets_reverse); 
517         return match_list;      
518 }
519
520 static gboolean
521 fill_gtkhtml_with_txt (ModestTnyMsgView *self, GtkHTML* gtkhtml, const gchar* txt, TnyMsgIface *msg)
522 {
523         GString *html;
524         gchar *html_attachments;
525         
526         g_return_val_if_fail (gtkhtml, FALSE);
527         g_return_val_if_fail (txt, FALSE);
528
529         html = g_string_new(convert_to_html (txt));
530         html_attachments = attachments_as_html(self, msg);
531         g_string_append(html, html_attachments);
532
533         gtk_html_load_from_string (gtkhtml, html->str, html->len);
534         g_string_free (html, TRUE);
535         g_free(html_attachments);
536
537         return TRUE;
538 }
539
540
541
542 static gboolean
543 set_html_message (ModestTnyMsgView *self, TnyMsgMimePartIface *tny_body, TnyMsgIface *msg)
544 {
545         gchar *html_attachments;
546         TnyStreamIface *gtkhtml_stream; 
547         ModestTnyMsgViewPrivate *priv;
548         
549         g_return_val_if_fail (self, FALSE);
550         g_return_val_if_fail (tny_body, FALSE);
551         
552         priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE(self);
553
554         gtkhtml_stream =
555                 TNY_STREAM_IFACE(modest_tny_stream_gtkhtml_new
556                                  (gtk_html_begin(GTK_HTML(priv->gtkhtml))));
557         
558         tny_stream_iface_reset (gtkhtml_stream);
559         tny_msg_mime_part_iface_decode_to_stream (tny_body, gtkhtml_stream);
560         html_attachments = attachments_as_html(self, msg);
561         /* is this clean? */
562         gtkhtml_write(gtkhtml_stream, html_attachments, strlen(html_attachments));
563         tny_stream_iface_reset (gtkhtml_stream);
564
565         g_object_unref (G_OBJECT(gtkhtml_stream));
566         g_free (html_attachments);
567         
568         return TRUE;
569 }
570
571
572 /* this is a hack --> we use the tny_text_buffer_stream to
573  * get the message text, then write to gtkhtml 'by hand' */
574 static gboolean
575 set_text_message (ModestTnyMsgView *self, TnyMsgMimePartIface *tny_body, TnyMsgIface *msg)
576 {
577         GtkTextBuffer *buf;
578         GtkTextIter begin, end;
579         TnyStreamIface* txt_stream;
580         gchar *txt;
581         ModestTnyMsgViewPrivate *priv;
582                 
583         g_return_val_if_fail (self, FALSE);
584         g_return_val_if_fail (tny_body, FALSE);
585
586         priv           = MODEST_TNY_MSG_VIEW_GET_PRIVATE(self);
587         
588         buf            = gtk_text_buffer_new (NULL);
589         txt_stream     = TNY_STREAM_IFACE(tny_text_buffer_stream_new (buf));
590                 
591         tny_stream_iface_reset (txt_stream);
592         tny_msg_mime_part_iface_decode_to_stream (tny_body, txt_stream);
593         tny_stream_iface_reset (txt_stream);            
594         
595         gtk_text_buffer_get_bounds (buf, &begin, &end);
596         txt = gtk_text_buffer_get_text (buf, &begin, &end, FALSE);
597         
598         fill_gtkhtml_with_txt (self, GTK_HTML(priv->gtkhtml), txt, msg);
599
600         g_object_unref (G_OBJECT(txt_stream));
601         g_object_unref (G_OBJECT(buf));
602
603         g_free (txt);
604         return TRUE;
605 }
606
607 gchar *
608 modest_tny_msg_view_get_selected_text (ModestTnyMsgView *self)
609 {
610         ModestTnyMsgViewPrivate *priv;
611         gchar *sel;
612         GtkWidget *html;
613         int len;
614         GtkClipboard *clip;
615
616         g_return_val_if_fail (self, NULL);
617         priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE(self);
618         html = priv->gtkhtml;
619         
620         /* I'm sure there is a better way to check for selected text */
621         sel = gtk_html_get_selection_html(GTK_HTML(html), &len);
622         if (!sel)
623                 return NULL;
624         
625         g_free(sel);
626         
627         clip = gtk_widget_get_clipboard(html, GDK_SELECTION_PRIMARY);
628         return gtk_clipboard_wait_for_text(clip);
629 }
630
631 void
632 modest_tny_msg_view_set_message (ModestTnyMsgView *self, TnyMsgIface *msg)
633 {
634         TnyMsgMimePartIface *body;
635         ModestTnyMsgViewPrivate *priv;
636
637         g_return_if_fail (self);
638         
639         priv = MODEST_TNY_MSG_VIEW_GET_PRIVATE(self);
640
641         priv->msg = msg;
642         
643         fill_gtkhtml_with_txt (self, GTK_HTML(priv->gtkhtml), "", msg);
644
645         if (!msg) 
646                 return;
647         
648         body = modest_tny_msg_actions_find_body_part (msg, "text/html");
649         if (body) {
650                 set_html_message (self, body, msg);
651                 return;
652         }
653         
654         body = modest_tny_msg_actions_find_body_part (msg, "text/plain");
655         if (body) {
656                 set_text_message (self, body, msg);
657                 return;
658         }
659
660         /* hmmmmm */
661         fill_gtkhtml_with_txt (self, GTK_HTML(priv->gtkhtml),
662                                 _("Unsupported message type"), msg);
663 }