4e1116f2a946fd2fcc896feb95dcc94df25b3f8b
[modest] / src / widgets / modest-gtkhtml-mime-part-view.c
1 /* Copyright (c) 2006, 2007, Nokia Corporation
2  * All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  * * Redistributions of source code must retain the above copyright
9  *   notice, this list of conditions and the following disclaimer.
10  * * Redistributions in binary form must reproduce the above copyright
11  *   notice, this list of conditions and the following disclaimer in the
12  *   documentation and/or other materials provided with the distribution.
13  * * Neither the name of the Nokia Corporation nor the names of its
14  *   contributors may be used to endorse or promote products derived from
15  *   this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
18  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
20  * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
21  * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29
30 #include <config.h>
31 #include <widgets/modest-gtkhtml-mime-part-view.h>
32 #include <string.h>
33 #include <gtkhtml/gtkhtml-stream.h>
34 #include <gtkhtml/gtkhtml-search.h>
35 #include <tny-stream.h>
36 #include <tny-mime-part-view.h>
37 #include "modest-tny-mime-part.h"
38 #include <modest-stream-text-to-html.h>
39 #include <modest-text-utils.h>
40 #include <modest-conf.h>
41 #include <modest-runtime.h>
42 #include <widgets/modest-mime-part-view.h>
43 #include <widgets/modest-zoomable.h>
44 #include <widgets/modest-tny-stream-gtkhtml.h>
45 #include <libgnomevfs/gnome-vfs.h>
46 #include <gdk/gdkkeysyms.h>
47 #include <modest-ui-constants.h>
48
49 /* gobject structure methods */
50 static void    modest_gtkhtml_mime_part_view_class_init (ModestGtkhtmlMimePartViewClass *klass);
51 static void    tny_mime_part_view_init                  (gpointer g, gpointer iface_data);
52 static void    modest_mime_part_view_init               (gpointer g, gpointer iface_data);
53 static void    modest_zoomable_init                     (gpointer g, gpointer iface_data);
54 static void    modest_isearch_view_init                 (gpointer g, gpointer iface_data);
55 static void    modest_gtkhtml_mime_part_view_init       (ModestGtkhtmlMimePartView *self);
56 static void    modest_gtkhtml_mime_part_view_finalize   (GObject *self);
57 static void    modest_gtkhtml_mime_part_view_dispose    (GObject *self);
58
59 /* GtkHTML signal handlers */
60 static gboolean  on_link_clicked  (GtkWidget *widget, const gchar *uri, ModestGtkhtmlMimePartView *self);
61 static gboolean  on_url           (GtkWidget *widget, const gchar *uri, ModestGtkhtmlMimePartView *self);
62 static gboolean  on_url_requested (GtkWidget *widget, const gchar *uri, GtkHTMLStream *stream,
63                                    ModestGtkhtmlMimePartView *self);
64 static void      on_notify_style  (GObject *obj, GParamSpec *spec, gpointer userdata);
65 static gboolean  update_style     (ModestGtkhtmlMimePartView *self);
66 /* TnyMimePartView implementation */
67 static void modest_gtkhtml_mime_part_view_clear (TnyMimePartView *self);
68 static void modest_gtkhtml_mime_part_view_clear_default (TnyMimePartView *self);
69 static void modest_gtkhtml_mime_part_view_set_part (TnyMimePartView *self, TnyMimePart *part);
70 static void modest_gtkhtml_mime_part_view_set_part_default (TnyMimePartView *self, TnyMimePart *part);
71 static TnyMimePart* modest_gtkhtml_mime_part_view_get_part (TnyMimePartView *self);
72 static TnyMimePart* modest_gtkhtml_mime_part_view_get_part_default (TnyMimePartView *self);
73 /* ModestMimePartView implementation */
74 static gboolean modest_gtkhtml_mime_part_view_is_empty (ModestMimePartView *self);
75 static gboolean modest_gtkhtml_mime_part_view_is_empty_default (ModestMimePartView *self);
76 static gboolean modest_gtkhtml_mime_part_view_get_view_images (ModestMimePartView *self);
77 static gboolean modest_gtkhtml_mime_part_view_get_view_images_default (ModestMimePartView *self);
78 static void     modest_gtkhtml_mime_part_view_set_view_images (ModestMimePartView *self, gboolean view_images);
79 static void     modest_gtkhtml_mime_part_view_set_view_images_default (ModestMimePartView *self, gboolean view_images);
80 static gboolean modest_gtkhtml_mime_part_view_has_external_images (ModestMimePartView *self);
81 static gboolean modest_gtkhtml_mime_part_view_has_external_images_default (ModestMimePartView *self);
82 /* ModestZoomable implementation */
83 static gdouble modest_gtkhtml_mime_part_view_get_zoom (ModestZoomable *self);
84 static void modest_gtkhtml_mime_part_view_set_zoom (ModestZoomable *self, gdouble value);
85 static gboolean modest_gtkhtml_mime_part_view_zoom_minus (ModestZoomable *self);
86 static gboolean modest_gtkhtml_mime_part_view_zoom_plus (ModestZoomable *self);
87 static gdouble modest_gtkhtml_mime_part_view_get_zoom_default (ModestZoomable *self);
88 static void modest_gtkhtml_mime_part_view_set_zoom_default (ModestZoomable *self, gdouble value);
89 static gboolean modest_gtkhtml_mime_part_view_zoom_minus_default (ModestZoomable *self);
90 static gboolean modest_gtkhtml_mime_part_view_zoom_plus_default (ModestZoomable *self);
91 /* ModestISearchView implementation */
92 static gboolean modest_gtkhtml_mime_part_view_search                    (ModestISearchView *self, const gchar *string);
93 static gboolean modest_gtkhtml_mime_part_view_search_next               (ModestISearchView *self);
94 static gboolean modest_gtkhtml_mime_part_view_get_selection_area        (ModestISearchView *self, gint *x, gint *y, 
95                                                                          gint *width, gint *height);
96 static gboolean modest_gtkhtml_mime_part_view_search_default            (ModestISearchView *self, const gchar *string);
97 static gboolean modest_gtkhtml_mime_part_view_search_next_default       (ModestISearchView *self);
98 static gboolean modest_gtkhtml_mime_part_view_get_selection_area_default (ModestISearchView *self, gint *x, gint *y, 
99                                                                           gint *width, gint *height);
100
101
102 /* internal api */
103 static TnyMimePart  *get_part   (ModestGtkhtmlMimePartView *self);
104 static void          set_html_part   (ModestGtkhtmlMimePartView *self, TnyMimePart *part, const gchar *encoding);
105 static void          set_text_part   (ModestGtkhtmlMimePartView *self, TnyMimePart *part);
106 static void          set_empty_part  (ModestGtkhtmlMimePartView *self);
107 static void          set_part   (ModestGtkhtmlMimePartView *self, TnyMimePart *part);
108 static gboolean      is_empty   (ModestGtkhtmlMimePartView *self);
109 static gboolean      get_view_images   (ModestGtkhtmlMimePartView *self);
110 static void          set_view_images   (ModestGtkhtmlMimePartView *self, gboolean view_images);
111 static gboolean      has_external_images   (ModestGtkhtmlMimePartView *self);
112 static void          set_zoom   (ModestGtkhtmlMimePartView *self, gdouble zoom);
113 static gdouble       get_zoom   (ModestGtkhtmlMimePartView *self);
114 static gboolean      has_contents_receiver (gpointer engine, const gchar *data,
115                                             size_t len, gboolean *has_contents);
116 static gboolean      search             (ModestGtkhtmlMimePartView *self, const gchar *string);
117 static gboolean      search_next        (ModestGtkhtmlMimePartView *self);
118 static gboolean      get_selection_area (ModestGtkhtmlMimePartView *self, gint *x, gint *y,
119                                          gint *width, gint *height);
120
121 typedef struct _ModestGtkhtmlMimePartViewPrivate ModestGtkhtmlMimePartViewPrivate;
122 struct _ModestGtkhtmlMimePartViewPrivate {
123         TnyMimePart *part;
124         gdouble current_zoom;
125         gboolean view_images;
126         gboolean has_external_images;
127         GSList *sighandlers;
128 };
129
130 #define MODEST_GTKHTML_MIME_PART_VIEW_GET_PRIVATE(o)      (G_TYPE_INSTANCE_GET_PRIVATE((o), \
131                                                                                        MODEST_TYPE_GTKHTML_MIME_PART_VIEW, \
132                                                                                        ModestGtkhtmlMimePartViewPrivate))
133
134 enum {
135         STOP_STREAMS_SIGNAL,
136         LAST_SIGNAL
137 };
138
139 static GtkHTMLClass *parent_class = NULL;
140
141 static guint signals[LAST_SIGNAL] = {0};
142
143 GtkWidget *
144 modest_gtkhtml_mime_part_view_new ()
145 {
146         return g_object_new (MODEST_TYPE_GTKHTML_MIME_PART_VIEW, NULL);
147 }
148
149 /* GOBJECT IMPLEMENTATION */
150 GType
151 modest_gtkhtml_mime_part_view_get_type (void)
152 {
153         static GType my_type = 0;
154         if (!my_type) {
155                 static const GTypeInfo my_info = {
156                         sizeof(ModestGtkhtmlMimePartViewClass),
157                         NULL,           /* base init */
158                         NULL,           /* base finalize */
159                         (GClassInitFunc) modest_gtkhtml_mime_part_view_class_init,
160                         NULL,           /* class finalize */
161                         NULL,           /* class data */
162                         sizeof(ModestGtkhtmlMimePartView),
163                         1,              /* n_preallocs */
164                         (GInstanceInitFunc) modest_gtkhtml_mime_part_view_init,
165                         NULL
166                 };
167
168                 static const GInterfaceInfo tny_mime_part_view_info = 
169                 {
170                   (GInterfaceInitFunc) tny_mime_part_view_init, /* interface_init */
171                   NULL,         /* interface_finalize */
172                   NULL          /* interface_data */
173                 };
174
175                 static const GInterfaceInfo modest_mime_part_view_info = 
176                 {
177                   (GInterfaceInitFunc) modest_mime_part_view_init, /* interface_init */
178                   NULL,         /* interface_finalize */
179                   NULL          /* interface_data */
180                 };
181
182                 static const GInterfaceInfo modest_zoomable_info = 
183                 {
184                   (GInterfaceInitFunc) modest_zoomable_init, /* interface_init */
185                   NULL,         /* interface_finalize */
186                   NULL          /* interface_data */
187                 };
188
189                 static const GInterfaceInfo modest_isearch_view_info = 
190                 {
191                   (GInterfaceInitFunc) modest_isearch_view_init, /* interface_init */
192                   NULL,         /* interface_finalize */
193                   NULL          /* interface_data */
194                 };
195
196                 my_type = g_type_register_static (GTK_TYPE_HTML,
197                                                   "ModestGtkhtmlMimePartView",
198                                                   &my_info, 0);
199
200                 g_type_add_interface_static (my_type, TNY_TYPE_MIME_PART_VIEW, 
201                         &tny_mime_part_view_info);
202
203                 g_type_add_interface_static (my_type, MODEST_TYPE_MIME_PART_VIEW, 
204                         &modest_mime_part_view_info);
205
206                 g_type_add_interface_static (my_type, MODEST_TYPE_ZOOMABLE, 
207                         &modest_zoomable_info);
208                 g_type_add_interface_static (my_type, MODEST_TYPE_ISEARCH_VIEW, 
209                         &modest_isearch_view_info);
210         }
211         return my_type;
212 }
213
214 static void
215 modest_gtkhtml_mime_part_view_class_init (ModestGtkhtmlMimePartViewClass *klass)
216 {
217         GObjectClass *gobject_class;
218         GtkBindingSet *binding_set;
219
220         gobject_class = (GObjectClass*) klass;
221
222         parent_class            = g_type_class_peek_parent (klass);
223         gobject_class->dispose = modest_gtkhtml_mime_part_view_dispose;
224         gobject_class->finalize = modest_gtkhtml_mime_part_view_finalize;
225
226         klass->get_part_func = modest_gtkhtml_mime_part_view_get_part_default;
227         klass->set_part_func = modest_gtkhtml_mime_part_view_set_part_default;
228         klass->clear_func = modest_gtkhtml_mime_part_view_clear_default;
229         klass->is_empty_func = modest_gtkhtml_mime_part_view_is_empty_default;
230         klass->get_view_images_func = modest_gtkhtml_mime_part_view_get_view_images_default;
231         klass->set_view_images_func = modest_gtkhtml_mime_part_view_set_view_images_default;
232         klass->has_external_images_func = modest_gtkhtml_mime_part_view_has_external_images_default;
233         klass->get_zoom_func = modest_gtkhtml_mime_part_view_get_zoom_default;
234         klass->set_zoom_func = modest_gtkhtml_mime_part_view_set_zoom_default;
235         klass->zoom_minus_func = modest_gtkhtml_mime_part_view_zoom_minus_default;
236         klass->zoom_plus_func = modest_gtkhtml_mime_part_view_zoom_plus_default;
237         klass->search_func = modest_gtkhtml_mime_part_view_search_default;
238         klass->search_next_func = modest_gtkhtml_mime_part_view_search_next_default;
239         klass->get_selection_area_func = modest_gtkhtml_mime_part_view_get_selection_area_default;
240
241         binding_set = gtk_binding_set_by_class (klass);
242         gtk_binding_entry_skip (binding_set, GDK_Down, 0);
243         gtk_binding_entry_skip (binding_set, GDK_Up, 0);
244         gtk_binding_entry_skip (binding_set, GDK_KP_Up, 0);
245         gtk_binding_entry_skip (binding_set, GDK_KP_Down, 0);
246         gtk_binding_entry_skip (binding_set, GDK_Page_Down, 0);
247         gtk_binding_entry_skip (binding_set, GDK_Page_Up, 0);
248         gtk_binding_entry_skip (binding_set, GDK_KP_Page_Up, 0);
249         gtk_binding_entry_skip (binding_set, GDK_KP_Page_Down, 0);
250         gtk_binding_entry_skip (binding_set, GDK_Home, 0);
251         gtk_binding_entry_skip (binding_set, GDK_End, 0);
252         gtk_binding_entry_skip (binding_set, GDK_KP_Home, 0);
253         gtk_binding_entry_skip (binding_set, GDK_KP_End, 0);
254
255         g_type_class_add_private (gobject_class, sizeof(ModestGtkhtmlMimePartViewPrivate));
256
257         signals[STOP_STREAMS_SIGNAL] = 
258                 g_signal_new ("stop-streams",
259                               G_TYPE_FROM_CLASS (gobject_class),
260                               G_SIGNAL_RUN_FIRST,
261                               G_STRUCT_OFFSET (ModestGtkhtmlMimePartViewClass,stop_streams),
262                               NULL, NULL,
263                               g_cclosure_marshal_VOID__VOID,
264                               G_TYPE_NONE, 0);
265
266 }
267
268 static void
269 modest_gtkhtml_mime_part_view_init (ModestGtkhtmlMimePartView *self)
270 {
271         ModestGtkhtmlMimePartViewPrivate *priv = MODEST_GTKHTML_MIME_PART_VIEW_GET_PRIVATE (self);
272         GdkColor base;
273         GdkColor text;
274
275         gtk_html_set_editable        (GTK_HTML(self), FALSE);
276         gtk_html_allow_selection     (GTK_HTML(self), TRUE);
277         gtk_html_set_caret_mode      (GTK_HTML(self), FALSE);
278         gtk_html_set_blocking        (GTK_HTML(self), TRUE);
279         gtk_html_set_images_blocking (GTK_HTML(self), TRUE);
280         gtk_container_set_border_width (GTK_CONTAINER (self), MODEST_MARGIN_DEFAULT);
281
282 #ifdef MODEST_TOOLKIT_HILDON2
283 #ifdef HAVE_GTK_HTML_SET_MAX_IMAGE_SIZE
284         /* We set a maximum width of a bit less than the width of the screen, and a
285            maximum height of 2 times the full size of the window. Should be enough */
286         gtk_html_set_max_image_size (GTK_HTML (self), 720, 880);
287 #endif
288 #ifdef HAVE_GTK_HTML_SET_ALLOW_DND
289         gtk_html_set_allow_dnd       (GTK_HTML(self), FALSE);
290 #endif
291 #endif
292
293 #ifdef HAVE_GTK_HTML_SET_DEFAULT_ENGINE
294         /* Enable Content type handling */
295         gtk_html_set_default_engine (GTK_HTML (self), TRUE);
296 #endif
297
298         gdk_color_parse ("#fff", &base);
299         gdk_color_parse ("#000", &text);
300         gtk_widget_modify_base (GTK_WIDGET (self), GTK_STATE_NORMAL, &base);
301         gtk_widget_modify_text (GTK_WIDGET (self), GTK_STATE_NORMAL, &text);
302
303         priv->sighandlers = modest_signal_mgr_connect (priv->sighandlers,
304                                                        G_OBJECT(self), "link_clicked",
305                                                        G_CALLBACK(on_link_clicked), self);
306         priv->sighandlers = modest_signal_mgr_connect (priv->sighandlers,
307                                                        G_OBJECT(self), "url_requested",
308                                                        G_CALLBACK(on_url_requested), self);
309         priv->sighandlers = modest_signal_mgr_connect (priv->sighandlers,
310                                                        G_OBJECT(self), "on_url",
311                                                        G_CALLBACK(on_url), self);
312         priv->sighandlers = modest_signal_mgr_connect (priv->sighandlers,
313                                                        G_OBJECT(self), "notify::style",
314                                                        G_CALLBACK (on_notify_style), (gpointer) self);
315
316         priv->part = NULL;
317         priv->current_zoom = 1.0;
318         priv->view_images = FALSE;
319         priv->has_external_images = FALSE;
320 }
321
322 static void
323 modest_gtkhtml_mime_part_view_finalize (GObject *obj)
324 {
325         ModestGtkhtmlMimePartViewPrivate *priv = MODEST_GTKHTML_MIME_PART_VIEW_GET_PRIVATE (obj);
326
327         modest_signal_mgr_disconnect_all_and_destroy (priv->sighandlers);
328         priv->sighandlers = NULL;
329
330         G_OBJECT_CLASS (parent_class)->finalize (obj);
331 }
332
333 static void
334 modest_gtkhtml_mime_part_view_dispose (GObject *obj)
335 {
336         ModestGtkhtmlMimePartViewPrivate *priv = MODEST_GTKHTML_MIME_PART_VIEW_GET_PRIVATE (obj);
337
338         g_signal_emit (G_OBJECT (obj), signals[STOP_STREAMS_SIGNAL], 0);
339
340         if (priv->part) {
341                 g_object_unref (priv->part);
342                 priv->part = NULL;
343         }
344
345         G_OBJECT_CLASS (parent_class)->dispose (obj);
346 }
347
348 /* GTKHTML SIGNALS HANDLERS */
349
350 static void 
351 on_notify_style (GObject *obj, GParamSpec *spec, gpointer userdata)
352 {
353         if (strcmp ("style", spec->name) == 0) {
354                 g_idle_add_full (G_PRIORITY_DEFAULT, (GSourceFunc) update_style, 
355                                  g_object_ref (obj), g_object_unref);
356                 gtk_widget_queue_draw (GTK_WIDGET (obj));
357         }
358 }
359
360 gboolean
361 same_color (GdkColor *a, GdkColor *b)
362 {
363         return ((a->red == b->red) && 
364                 (a->green == b->green) && 
365                 (a->blue == b->blue));
366 }
367
368 static gboolean
369 update_style (ModestGtkhtmlMimePartView *self)
370 {
371         GdkColor base;
372         GdkColor text;
373         GtkRcStyle *rc_style;
374
375         gdk_threads_enter ();
376
377         if (GTK_WIDGET_VISIBLE (self)) {
378                 rc_style = gtk_widget_get_modifier_style (GTK_WIDGET (self));
379
380                 gdk_color_parse ("#fff", &base);
381                 gdk_color_parse ("#000", &text);
382
383                 if (!same_color (&(rc_style->base[GTK_STATE_NORMAL]), &base) &&
384                     !same_color (&(rc_style->text[GTK_STATE_NORMAL]), &text)) {
385
386                         rc_style->base[GTK_STATE_NORMAL] = base;
387                         rc_style->text[GTK_STATE_NORMAL] = text;
388                         gtk_widget_modify_style (GTK_WIDGET (self), rc_style);
389                 }
390         }
391
392         gdk_threads_leave ();
393
394         return FALSE;
395 }
396
397
398
399 static gboolean
400 on_link_clicked (GtkWidget *widget, const gchar *uri, ModestGtkhtmlMimePartView *self)
401 {
402         gboolean result;
403         g_return_val_if_fail (MODEST_IS_GTKHTML_MIME_PART_VIEW (self), FALSE);
404
405         g_signal_emit_by_name (G_OBJECT (self), "activate-link", uri, &result);
406         return result;
407 }
408
409 static gboolean
410 on_url (GtkWidget *widget, const gchar *uri, ModestGtkhtmlMimePartView *self)
411 {
412         gboolean result;
413         g_return_val_if_fail (MODEST_IS_GTKHTML_MIME_PART_VIEW (self), FALSE);
414
415         g_signal_emit_by_name (G_OBJECT (self), "link-hover", uri, &result);
416         return result;
417 }
418
419 static gboolean
420 on_url_requested (GtkWidget *widget, const gchar *uri, GtkHTMLStream *stream, 
421                   ModestGtkhtmlMimePartView *self)
422 {
423         gboolean result;
424         TnyStream *tny_stream;
425         g_return_val_if_fail (MODEST_IS_GTKHTML_MIME_PART_VIEW (self), FALSE);
426
427         if (g_str_has_prefix (uri, "http:")) {
428                 ModestGtkhtmlMimePartViewPrivate *priv = MODEST_GTKHTML_MIME_PART_VIEW_GET_PRIVATE (self);
429
430                 if (!priv->view_images)
431                         priv->has_external_images = TRUE;
432         }
433
434         tny_stream = TNY_STREAM (modest_tny_stream_gtkhtml_new (stream, GTK_HTML (widget)));
435         g_signal_emit_by_name (MODEST_MIME_PART_VIEW (self), "fetch-url", uri, tny_stream, &result);
436         g_object_unref (tny_stream);
437         return result;
438 }
439
440 /* INTERNAL API */
441 static void
442 decode_to_stream_cb (TnyMimePart *self,
443                      gboolean cancelled,
444                      TnyStream *stream,
445                      GError *err,
446                      gpointer user_data)
447 {
448         gboolean is_text = GPOINTER_TO_INT (user_data);
449
450         if (is_text) {
451                 if (tny_stream_write (stream, "\n", 1) == -1) {
452                         g_warning ("failed to write CR in %s", __FUNCTION__);
453                 }
454                 tny_stream_reset (stream);
455         }
456         tny_stream_close (stream);
457 }
458
459 static void
460 set_html_part (ModestGtkhtmlMimePartView *self, TnyMimePart *part, const gchar *encoding)
461 {
462         GtkHTMLStream *gtkhtml_stream;
463         TnyStream *tny_stream;
464         gchar *content_type;
465
466         g_return_if_fail (self);
467         g_return_if_fail (part);
468
469         g_signal_emit (G_OBJECT (self), signals[STOP_STREAMS_SIGNAL], 0);
470
471         content_type = g_strdup_printf ("text/html; charset=%s", encoding);
472         gtkhtml_stream = gtk_html_begin_full(GTK_HTML(self), NULL, content_type, 0);
473         g_free (content_type);
474
475         tny_stream     = TNY_STREAM(modest_tny_stream_gtkhtml_new (gtkhtml_stream, GTK_HTML (self)));
476         modest_tny_stream_gtkhtml_set_max_size (MODEST_TNY_STREAM_GTKHTML (tny_stream), 128*1024);
477         tny_stream_reset (tny_stream);
478
479         tny_mime_part_decode_to_stream_async (TNY_MIME_PART (part),
480                                               tny_stream, decode_to_stream_cb,
481                                               NULL, GINT_TO_POINTER (FALSE));
482         g_object_unref (tny_stream);
483 }
484
485 static void
486 set_text_part (ModestGtkhtmlMimePartView *self, TnyMimePart *part)
487 {
488         TnyStream* text_to_html_stream, *tny_stream;
489         GtkHTMLStream *gtkhtml_stream;
490
491         g_return_if_fail (self);
492         g_return_if_fail (part);
493
494         g_signal_emit (G_OBJECT (self), signals[STOP_STREAMS_SIGNAL], 0);
495
496         gtkhtml_stream = gtk_html_begin(GTK_HTML(self));
497         tny_stream =  TNY_STREAM(modest_tny_stream_gtkhtml_new (gtkhtml_stream, GTK_HTML (self)));
498         modest_tny_stream_gtkhtml_set_max_size (MODEST_TNY_STREAM_GTKHTML (tny_stream), 128*1024);
499         text_to_html_stream = TNY_STREAM (modest_stream_text_to_html_new (tny_stream));
500         modest_stream_text_to_html_set_linkify_limit (MODEST_STREAM_TEXT_TO_HTML (text_to_html_stream),
501                                                       64*1024);
502         modest_stream_text_to_html_set_full_limit (MODEST_STREAM_TEXT_TO_HTML (text_to_html_stream),
503                                                    128*1024);
504         modest_stream_text_to_html_set_line_limit (MODEST_STREAM_TEXT_TO_HTML (text_to_html_stream),
505                                                    1024);
506
507         tny_mime_part_decode_to_stream_async (TNY_MIME_PART (part),
508                                               text_to_html_stream, decode_to_stream_cb,
509                                               NULL, GINT_TO_POINTER (TRUE));
510
511         g_object_unref (G_OBJECT(text_to_html_stream));
512         g_object_unref (G_OBJECT(tny_stream));
513 }
514
515 static void
516 set_empty_part (ModestGtkhtmlMimePartView *self)
517 {
518         g_return_if_fail (self);
519
520         g_signal_emit (G_OBJECT (self), signals[STOP_STREAMS_SIGNAL], 0);
521         gtk_html_load_from_string (GTK_HTML(self), "", 1);
522 }
523
524 static void
525 set_part (ModestGtkhtmlMimePartView *self, TnyMimePart *part)
526 {
527         ModestGtkhtmlMimePartViewPrivate *priv;
528         gchar *header_content_type, *header_content_type_lower;
529         const gchar *tmp;
530         gchar *charset = NULL;
531
532         g_return_if_fail (self);
533         
534         priv = MODEST_GTKHTML_MIME_PART_VIEW_GET_PRIVATE(self);
535         priv->has_external_images = FALSE;
536
537         if (part != priv->part) {
538                 if (priv->part)
539                         g_object_unref (G_OBJECT(priv->part));
540                 if (part)
541                         g_object_ref   (G_OBJECT(part));
542                 priv->part = part;
543         }
544         
545         if (!part) {
546                 set_empty_part (self);
547                 return;
548         }
549
550         header_content_type = modest_tny_mime_part_get_header_value (part, "Content-Type");
551         if (header_content_type) {
552                 header_content_type = g_strstrip (header_content_type);
553                 header_content_type_lower = g_ascii_strdown (header_content_type, -1);
554         } else {
555                 header_content_type_lower = NULL;
556         }
557
558         if (header_content_type_lower) {
559                 tmp = strstr (header_content_type_lower, "charset=");
560                 if (tmp) {
561                         const gchar *tmp2;
562                         tmp = tmp + strlen ("charset=");
563                         
564                         tmp2 = strstr (tmp, ";");
565                         if (tmp2) {
566                                 charset = g_strndup (tmp, tmp2-tmp);
567                         } else {
568                                 charset = g_strdup (tmp);
569                         }
570                 }
571         }
572
573         if (tny_mime_part_content_type_is (part, "text/html")) {
574                 set_html_part (self, part, charset);
575         } else {
576                 if (tny_mime_part_content_type_is (part, "message/rfc822")) {
577                         if (header_content_type) {
578                                 if (g_str_has_prefix (header_content_type_lower, "text/html"))
579                                         set_html_part (self, part, charset);
580                                 else 
581                                         set_text_part (self, part);
582
583                         } else {
584                                 set_text_part (self, part);
585                         }
586                 } else {
587                         set_text_part (self, part);
588                 }
589         }
590         g_free (header_content_type_lower);
591         g_free (header_content_type);
592
593 }
594
595 static TnyMimePart*
596 get_part (ModestGtkhtmlMimePartView *self)
597 {
598         TnyMimePart *part;
599
600         g_return_val_if_fail (MODEST_IS_GTKHTML_MIME_PART_VIEW (self), NULL);
601
602         part = MODEST_GTKHTML_MIME_PART_VIEW_GET_PRIVATE(self)->part;
603
604         if (part)
605                 g_object_ref (part);
606         
607         return part;
608 }
609
610 static gboolean
611 has_contents_receiver (gpointer engine, const gchar *data,
612                        size_t len, gboolean *has_contents)
613 {
614         if (len > 1 || ((len == 1)&&(data[0]!='\n'))) {
615                 *has_contents = TRUE;
616                 return FALSE;
617         }
618         return TRUE;
619 }
620
621 static gboolean      
622 is_empty   (ModestGtkhtmlMimePartView *self)
623 {
624         /* TODO: Find some gtkhtml API to check whether there is any (visible, non markup)
625          * text in the message:
626          */
627         gboolean has_contents = FALSE;
628
629         gtk_html_export (GTK_HTML (self), "text/plain", 
630                          (GtkHTMLSaveReceiverFn) has_contents_receiver, &has_contents);
631         
632         return !has_contents;
633 }
634
635 static gboolean      
636 get_view_images   (ModestGtkhtmlMimePartView *self)
637 {
638         ModestGtkhtmlMimePartViewPrivate *priv;
639
640         g_return_val_if_fail (MODEST_IS_GTKHTML_MIME_PART_VIEW (self), FALSE);
641
642         priv = MODEST_GTKHTML_MIME_PART_VIEW_GET_PRIVATE (self);
643         return priv->view_images;
644 }
645
646 static void
647 set_view_images   (ModestGtkhtmlMimePartView *self, gboolean view_images)
648 {
649         ModestGtkhtmlMimePartViewPrivate *priv;
650
651         g_return_if_fail (MODEST_IS_GTKHTML_MIME_PART_VIEW (self));
652
653         priv = MODEST_GTKHTML_MIME_PART_VIEW_GET_PRIVATE (self);
654         priv->view_images = view_images;
655 }
656
657 static gboolean      
658 has_external_images   (ModestGtkhtmlMimePartView *self)
659 {
660         ModestGtkhtmlMimePartViewPrivate *priv;
661
662         g_return_val_if_fail (MODEST_IS_GTKHTML_MIME_PART_VIEW (self), FALSE);
663
664         priv = MODEST_GTKHTML_MIME_PART_VIEW_GET_PRIVATE (self);
665         return priv->has_external_images;
666 }
667
668 static void
669 set_zoom (ModestGtkhtmlMimePartView *self, gdouble zoom)
670 {
671         ModestGtkhtmlMimePartViewPrivate *priv;
672
673         g_return_if_fail (MODEST_IS_GTKHTML_MIME_PART_VIEW (self));
674
675         priv = MODEST_GTKHTML_MIME_PART_VIEW_GET_PRIVATE (self);
676         priv->current_zoom = zoom;
677         gtk_html_set_magnification (GTK_HTML(self), zoom);
678
679         gtk_widget_queue_resize (GTK_WIDGET (self));
680 }
681
682 static gdouble
683 get_zoom (ModestGtkhtmlMimePartView *self)
684 {
685         ModestGtkhtmlMimePartViewPrivate *priv;
686
687         g_return_val_if_fail (MODEST_IS_GTKHTML_MIME_PART_VIEW (self), 1.0);
688
689         priv = MODEST_GTKHTML_MIME_PART_VIEW_GET_PRIVATE (self);
690
691         return priv->current_zoom;
692 }
693
694 static gboolean
695 search (ModestGtkhtmlMimePartView *self, 
696         const gchar *string)
697 {
698         return gtk_html_engine_search (GTK_HTML (self),
699                                        string,
700                                        FALSE,   /* case sensitive */
701                                        TRUE,    /* forward */
702                                        FALSE);  /* regexp */
703 }
704
705 static gboolean
706 search_next (ModestGtkhtmlMimePartView *self)
707 {
708         return gtk_html_engine_search_next (GTK_HTML (self));
709 }
710
711 static gboolean
712 get_selection_area (ModestGtkhtmlMimePartView *self, 
713                     gint *x, gint *y,
714                     gint *width, gint *height)
715 {
716 #ifdef HAVE_GTK_HTML_GET_SELECTION_AREA
717         gtk_html_get_selection_area (GTK_HTML (self), x, y, width, height);
718         return TRUE;
719 #else
720         return FALSE;
721 #endif
722 }
723
724
725 /* TNY MIME PART IMPLEMENTATION */
726
727 static void
728 tny_mime_part_view_init (gpointer g, gpointer iface_data)
729 {
730         TnyMimePartViewIface *klass = (TnyMimePartViewIface *)g;
731
732         klass->get_part = modest_gtkhtml_mime_part_view_get_part;
733         klass->set_part = modest_gtkhtml_mime_part_view_set_part;
734         klass->clear = modest_gtkhtml_mime_part_view_clear;
735
736         return;
737 }
738
739 static TnyMimePart* 
740 modest_gtkhtml_mime_part_view_get_part (TnyMimePartView *self)
741 {
742         return MODEST_GTKHTML_MIME_PART_VIEW_GET_CLASS (self)->get_part_func (self);
743 }
744
745
746 static TnyMimePart* 
747 modest_gtkhtml_mime_part_view_get_part_default (TnyMimePartView *self)
748 {
749         return TNY_MIME_PART (get_part (MODEST_GTKHTML_MIME_PART_VIEW (self)));
750 }
751
752 static void
753 modest_gtkhtml_mime_part_view_set_part (TnyMimePartView *self,
754                                         TnyMimePart *part)
755 {
756         MODEST_GTKHTML_MIME_PART_VIEW_GET_CLASS (self)->set_part_func (self, part);
757 }
758
759 static void
760 modest_gtkhtml_mime_part_view_set_part_default (TnyMimePartView *self,
761                                                 TnyMimePart *part)
762 {
763         g_return_if_fail ((part == NULL) || TNY_IS_MIME_PART (part));
764
765         set_part (MODEST_GTKHTML_MIME_PART_VIEW (self), part);
766 }
767
768 static void
769 modest_gtkhtml_mime_part_view_clear (TnyMimePartView *self)
770 {
771         MODEST_GTKHTML_MIME_PART_VIEW_GET_CLASS (self)->clear_func (self);
772 }
773
774 static void
775 modest_gtkhtml_mime_part_view_clear_default (TnyMimePartView *self)
776 {
777         set_part (MODEST_GTKHTML_MIME_PART_VIEW (self), NULL);
778 }
779
780 /* MODEST MIME PART VIEW IMPLEMENTATION */
781
782 static void
783 modest_mime_part_view_init (gpointer g, gpointer iface_data)
784 {
785         ModestMimePartViewIface *klass = (ModestMimePartViewIface *)g;
786
787         klass->is_empty_func = modest_gtkhtml_mime_part_view_is_empty;
788         klass->get_view_images_func = modest_gtkhtml_mime_part_view_get_view_images;
789         klass->set_view_images_func = modest_gtkhtml_mime_part_view_set_view_images;
790         klass->has_external_images_func = modest_gtkhtml_mime_part_view_has_external_images;
791
792         return;
793 }
794
795 static gboolean
796 modest_gtkhtml_mime_part_view_is_empty (ModestMimePartView *self)
797 {
798         return MODEST_GTKHTML_MIME_PART_VIEW_GET_CLASS (self)->is_empty_func (self);
799 }
800
801 static gboolean
802 modest_gtkhtml_mime_part_view_get_view_images (ModestMimePartView *self)
803 {
804         return MODEST_GTKHTML_MIME_PART_VIEW_GET_CLASS (self)->get_view_images_func (self);
805 }
806
807 static void
808 modest_gtkhtml_mime_part_view_set_view_images (ModestMimePartView *self, gboolean view_images)
809 {
810         MODEST_GTKHTML_MIME_PART_VIEW_GET_CLASS (self)->set_view_images_func (self, view_images);
811 }
812
813 static gboolean
814 modest_gtkhtml_mime_part_view_has_external_images (ModestMimePartView *self)
815 {
816         return MODEST_GTKHTML_MIME_PART_VIEW_GET_CLASS (self)->has_external_images_func (self);
817 }
818
819 static gboolean
820 modest_gtkhtml_mime_part_view_is_empty_default (ModestMimePartView *self)
821 {
822         return is_empty (MODEST_GTKHTML_MIME_PART_VIEW (self));
823 }
824
825 static gboolean
826 modest_gtkhtml_mime_part_view_get_view_images_default (ModestMimePartView *self)
827 {
828         return get_view_images (MODEST_GTKHTML_MIME_PART_VIEW (self));
829 }
830
831 static void
832 modest_gtkhtml_mime_part_view_set_view_images_default (ModestMimePartView *self, gboolean view_images)
833 {
834         set_view_images (MODEST_GTKHTML_MIME_PART_VIEW (self), view_images);
835 }
836
837 static gboolean
838 modest_gtkhtml_mime_part_view_has_external_images_default (ModestMimePartView *self)
839 {
840         return has_external_images (MODEST_GTKHTML_MIME_PART_VIEW (self));
841 }
842
843
844 /* MODEST ZOOMABLE IMPLEMENTATION */
845 static void
846 modest_zoomable_init (gpointer g, gpointer iface_data)
847 {
848         ModestZoomableIface *klass = (ModestZoomableIface *)g;
849         
850         klass->get_zoom_func = modest_gtkhtml_mime_part_view_get_zoom;
851         klass->set_zoom_func = modest_gtkhtml_mime_part_view_set_zoom;
852         klass->zoom_minus_func = modest_gtkhtml_mime_part_view_zoom_minus;
853         klass->zoom_plus_func = modest_gtkhtml_mime_part_view_zoom_plus;
854
855         return;
856 }
857
858 static gdouble
859 modest_gtkhtml_mime_part_view_get_zoom (ModestZoomable *self)
860 {
861         return MODEST_GTKHTML_MIME_PART_VIEW_GET_CLASS (self)->get_zoom_func (self);
862 }
863
864 static gdouble
865 modest_gtkhtml_mime_part_view_get_zoom_default (ModestZoomable *self)
866 {
867         return get_zoom (MODEST_GTKHTML_MIME_PART_VIEW (self));
868 }
869
870 static void
871 modest_gtkhtml_mime_part_view_set_zoom (ModestZoomable *self, gdouble value)
872 {
873         MODEST_GTKHTML_MIME_PART_VIEW_GET_CLASS (self)->set_zoom_func (self, value);
874 }
875
876 static void
877 modest_gtkhtml_mime_part_view_set_zoom_default (ModestZoomable *self, gdouble value)
878 {
879         set_zoom (MODEST_GTKHTML_MIME_PART_VIEW (self), value);
880 }
881
882 static gboolean
883 modest_gtkhtml_mime_part_view_zoom_minus (ModestZoomable *self)
884 {
885         return MODEST_GTKHTML_MIME_PART_VIEW_GET_CLASS (self)->zoom_minus_func (self);
886 }
887
888 static gboolean
889 modest_gtkhtml_mime_part_view_zoom_minus_default (ModestZoomable *self)
890 {
891         /* operation not supported in ModestGtkhtmlMimePartView */
892         return FALSE;
893 }
894
895 static gboolean
896 modest_gtkhtml_mime_part_view_zoom_plus (ModestZoomable *self)
897 {
898         return MODEST_GTKHTML_MIME_PART_VIEW_GET_CLASS (self)->zoom_plus_func (self);
899 }
900
901 static gboolean
902 modest_gtkhtml_mime_part_view_zoom_plus_default (ModestZoomable *self)
903 {
904         /* operation not supported in ModestGtkhtmlMimePartView */
905         return FALSE;
906 }
907
908 /* ISEARCH VIEW IMPLEMENTATION */
909 static void
910 modest_isearch_view_init (gpointer g, gpointer iface_data)
911 {
912         ModestISearchViewIface *klass = (ModestISearchViewIface *)g;
913         
914         klass->search_func = modest_gtkhtml_mime_part_view_search;
915         klass->search_next_func = modest_gtkhtml_mime_part_view_search_next;
916         klass->get_selection_area_func = modest_gtkhtml_mime_part_view_get_selection_area;
917
918         return;
919 }
920
921 static gboolean 
922 modest_gtkhtml_mime_part_view_search (ModestISearchView *self, const gchar *string)
923 {
924         return MODEST_GTKHTML_MIME_PART_VIEW_GET_CLASS (self)->search_func (self, string);
925 }
926
927 static gboolean 
928 modest_gtkhtml_mime_part_view_search_default (ModestISearchView *self, const gchar *string)
929 {
930         return search (MODEST_GTKHTML_MIME_PART_VIEW (self), string);
931 }
932
933 static gboolean 
934 modest_gtkhtml_mime_part_view_search_next(ModestISearchView *self)
935 {
936         return MODEST_GTKHTML_MIME_PART_VIEW_GET_CLASS (self)->search_next_func (self);
937 }
938
939 static gboolean 
940 modest_gtkhtml_mime_part_view_search_next_default (ModestISearchView *self)
941 {
942         return search_next (MODEST_GTKHTML_MIME_PART_VIEW (self));
943 }
944
945 static gboolean 
946 modest_gtkhtml_mime_part_view_get_selection_area (ModestISearchView *self, gint *x, gint *y, 
947                                                   gint *width, gint *height)
948 {
949         return MODEST_GTKHTML_MIME_PART_VIEW_GET_CLASS (self)->get_selection_area_func (self, x, y, width, height);
950 }
951
952 static gboolean 
953 modest_gtkhtml_mime_part_view_get_selection_area_default (ModestISearchView *self, gint *x, gint *y, 
954                                                           gint *width, gint *height)
955 {
956         return get_selection_area (MODEST_GTKHTML_MIME_PART_VIEW (self), x, y, width, height);
957 }