Notify gtkhtml streams when we're replacing or destroying contents
[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
48 /* gobject structure methods */
49 static void    modest_gtkhtml_mime_part_view_class_init (ModestGtkhtmlMimePartViewClass *klass);
50 static void    tny_mime_part_view_init                  (gpointer g, gpointer iface_data);
51 static void    modest_mime_part_view_init               (gpointer g, gpointer iface_data);
52 static void    modest_zoomable_init                     (gpointer g, gpointer iface_data);
53 static void    modest_isearch_view_init                 (gpointer g, gpointer iface_data);
54 static void    modest_gtkhtml_mime_part_view_init       (ModestGtkhtmlMimePartView *self);
55 static void    modest_gtkhtml_mime_part_view_finalize   (GObject *self);
56 static void    modest_gtkhtml_mime_part_view_dispose    (GObject *self);
57
58 /* GtkHTML signal handlers */
59 static gboolean  on_link_clicked  (GtkWidget *widget, const gchar *uri, ModestGtkhtmlMimePartView *self);
60 static gboolean  on_url           (GtkWidget *widget, const gchar *uri, ModestGtkhtmlMimePartView *self);
61 static gboolean  on_url_requested (GtkWidget *widget, const gchar *uri, GtkHTMLStream *stream,
62                                    ModestGtkhtmlMimePartView *self);
63 static void      on_notify_style  (GObject *obj, GParamSpec *spec, gpointer userdata);
64 static gboolean  update_style     (ModestGtkhtmlMimePartView *self);
65 /* TnyMimePartView implementation */
66 static void modest_gtkhtml_mime_part_view_clear (TnyMimePartView *self);
67 static void modest_gtkhtml_mime_part_view_clear_default (TnyMimePartView *self);
68 static void modest_gtkhtml_mime_part_view_set_part (TnyMimePartView *self, TnyMimePart *part);
69 static void modest_gtkhtml_mime_part_view_set_part_default (TnyMimePartView *self, TnyMimePart *part);
70 static TnyMimePart* modest_gtkhtml_mime_part_view_get_part (TnyMimePartView *self);
71 static TnyMimePart* modest_gtkhtml_mime_part_view_get_part_default (TnyMimePartView *self);
72 /* ModestMimePartView implementation */
73 static gboolean modest_gtkhtml_mime_part_view_is_empty (ModestMimePartView *self);
74 static gboolean modest_gtkhtml_mime_part_view_is_empty_default (ModestMimePartView *self);
75 static gboolean modest_gtkhtml_mime_part_view_get_view_images (ModestMimePartView *self);
76 static gboolean modest_gtkhtml_mime_part_view_get_view_images_default (ModestMimePartView *self);
77 static void     modest_gtkhtml_mime_part_view_set_view_images (ModestMimePartView *self, gboolean view_images);
78 static void     modest_gtkhtml_mime_part_view_set_view_images_default (ModestMimePartView *self, gboolean view_images);
79 static gboolean modest_gtkhtml_mime_part_view_has_external_images (ModestMimePartView *self);
80 static gboolean modest_gtkhtml_mime_part_view_has_external_images_default (ModestMimePartView *self);
81 /* ModestZoomable implementation */
82 static gdouble modest_gtkhtml_mime_part_view_get_zoom (ModestZoomable *self);
83 static void modest_gtkhtml_mime_part_view_set_zoom (ModestZoomable *self, gdouble value);
84 static gboolean modest_gtkhtml_mime_part_view_zoom_minus (ModestZoomable *self);
85 static gboolean modest_gtkhtml_mime_part_view_zoom_plus (ModestZoomable *self);
86 static gdouble modest_gtkhtml_mime_part_view_get_zoom_default (ModestZoomable *self);
87 static void modest_gtkhtml_mime_part_view_set_zoom_default (ModestZoomable *self, gdouble value);
88 static gboolean modest_gtkhtml_mime_part_view_zoom_minus_default (ModestZoomable *self);
89 static gboolean modest_gtkhtml_mime_part_view_zoom_plus_default (ModestZoomable *self);
90 /* ModestISearchView implementation */
91 static gboolean modest_gtkhtml_mime_part_view_search                    (ModestISearchView *self, const gchar *string);
92 static gboolean modest_gtkhtml_mime_part_view_search_next               (ModestISearchView *self);
93 static gboolean modest_gtkhtml_mime_part_view_get_selection_area        (ModestISearchView *self, gint *x, gint *y, 
94                                                                          gint *width, gint *height);
95 static gboolean modest_gtkhtml_mime_part_view_search_default            (ModestISearchView *self, const gchar *string);
96 static gboolean modest_gtkhtml_mime_part_view_search_next_default       (ModestISearchView *self);
97 static gboolean modest_gtkhtml_mime_part_view_get_selection_area_default (ModestISearchView *self, gint *x, gint *y, 
98                                                                           gint *width, gint *height);
99
100
101 /* internal api */
102 static TnyMimePart  *get_part   (ModestGtkhtmlMimePartView *self);
103 static void          set_html_part   (ModestGtkhtmlMimePartView *self, TnyMimePart *part);
104 static void          set_text_part   (ModestGtkhtmlMimePartView *self, TnyMimePart *part);
105 static void          set_empty_part  (ModestGtkhtmlMimePartView *self);
106 static void          set_part   (ModestGtkhtmlMimePartView *self, TnyMimePart *part);
107 static gboolean      is_empty   (ModestGtkhtmlMimePartView *self);
108 static gboolean      get_view_images   (ModestGtkhtmlMimePartView *self);
109 static void          set_view_images   (ModestGtkhtmlMimePartView *self, gboolean view_images);
110 static gboolean      has_external_images   (ModestGtkhtmlMimePartView *self);
111 static void          set_zoom   (ModestGtkhtmlMimePartView *self, gdouble zoom);
112 static gdouble       get_zoom   (ModestGtkhtmlMimePartView *self);
113 static gboolean      has_contents_receiver (gpointer engine, const gchar *data,
114                                             size_t len, gboolean *has_contents);
115 static gboolean      search             (ModestGtkhtmlMimePartView *self, const gchar *string);
116 static gboolean      search_next        (ModestGtkhtmlMimePartView *self);
117 static gboolean      get_selection_area (ModestGtkhtmlMimePartView *self, gint *x, gint *y,
118                                          gint *width, gint *height);
119
120 typedef struct _ModestGtkhtmlMimePartViewPrivate ModestGtkhtmlMimePartViewPrivate;
121 struct _ModestGtkhtmlMimePartViewPrivate {
122         TnyMimePart *part;
123         gdouble current_zoom;
124         gboolean view_images;
125         gboolean has_external_images;
126         GSList *sighandlers;
127 };
128
129 #define MODEST_GTKHTML_MIME_PART_VIEW_GET_PRIVATE(o)      (G_TYPE_INSTANCE_GET_PRIVATE((o), \
130                                                                                        MODEST_TYPE_GTKHTML_MIME_PART_VIEW, \
131                                                                                        ModestGtkhtmlMimePartViewPrivate))
132
133 enum {
134         STOP_STREAMS_SIGNAL,
135         LAST_SIGNAL
136 };
137
138 static GtkHTMLClass *parent_class = NULL;
139
140 static guint signals[LAST_SIGNAL] = {0};
141
142 GtkWidget *
143 modest_gtkhtml_mime_part_view_new ()
144 {
145         return g_object_new (MODEST_TYPE_GTKHTML_MIME_PART_VIEW, NULL);
146 }
147
148 /* GOBJECT IMPLEMENTATION */
149 GType
150 modest_gtkhtml_mime_part_view_get_type (void)
151 {
152         static GType my_type = 0;
153         if (!my_type) {
154                 static const GTypeInfo my_info = {
155                         sizeof(ModestGtkhtmlMimePartViewClass),
156                         NULL,           /* base init */
157                         NULL,           /* base finalize */
158                         (GClassInitFunc) modest_gtkhtml_mime_part_view_class_init,
159                         NULL,           /* class finalize */
160                         NULL,           /* class data */
161                         sizeof(ModestGtkhtmlMimePartView),
162                         1,              /* n_preallocs */
163                         (GInstanceInitFunc) modest_gtkhtml_mime_part_view_init,
164                         NULL
165                 };
166
167                 static const GInterfaceInfo tny_mime_part_view_info = 
168                 {
169                   (GInterfaceInitFunc) tny_mime_part_view_init, /* interface_init */
170                   NULL,         /* interface_finalize */
171                   NULL          /* interface_data */
172                 };
173
174                 static const GInterfaceInfo modest_mime_part_view_info = 
175                 {
176                   (GInterfaceInitFunc) modest_mime_part_view_init, /* interface_init */
177                   NULL,         /* interface_finalize */
178                   NULL          /* interface_data */
179                 };
180
181                 static const GInterfaceInfo modest_zoomable_info = 
182                 {
183                   (GInterfaceInitFunc) modest_zoomable_init, /* interface_init */
184                   NULL,         /* interface_finalize */
185                   NULL          /* interface_data */
186                 };
187
188                 static const GInterfaceInfo modest_isearch_view_info = 
189                 {
190                   (GInterfaceInitFunc) modest_isearch_view_init, /* interface_init */
191                   NULL,         /* interface_finalize */
192                   NULL          /* interface_data */
193                 };
194
195                 my_type = g_type_register_static (GTK_TYPE_HTML,
196                                                   "ModestGtkhtmlMimePartView",
197                                                   &my_info, 0);
198
199                 g_type_add_interface_static (my_type, TNY_TYPE_MIME_PART_VIEW, 
200                         &tny_mime_part_view_info);
201
202                 g_type_add_interface_static (my_type, MODEST_TYPE_MIME_PART_VIEW, 
203                         &modest_mime_part_view_info);
204
205                 g_type_add_interface_static (my_type, MODEST_TYPE_ZOOMABLE, 
206                         &modest_zoomable_info);
207                 g_type_add_interface_static (my_type, MODEST_TYPE_ISEARCH_VIEW, 
208                         &modest_isearch_view_info);
209         }
210         return my_type;
211 }
212
213 static void
214 modest_gtkhtml_mime_part_view_class_init (ModestGtkhtmlMimePartViewClass *klass)
215 {
216         GObjectClass *gobject_class;
217         GtkBindingSet *binding_set;
218
219         gobject_class = (GObjectClass*) klass;
220
221         parent_class            = g_type_class_peek_parent (klass);
222         gobject_class->dispose = modest_gtkhtml_mime_part_view_dispose;
223         gobject_class->finalize = modest_gtkhtml_mime_part_view_finalize;
224
225         klass->get_part_func = modest_gtkhtml_mime_part_view_get_part_default;
226         klass->set_part_func = modest_gtkhtml_mime_part_view_set_part_default;
227         klass->clear_func = modest_gtkhtml_mime_part_view_clear_default;
228         klass->is_empty_func = modest_gtkhtml_mime_part_view_is_empty_default;
229         klass->get_view_images_func = modest_gtkhtml_mime_part_view_get_view_images_default;
230         klass->set_view_images_func = modest_gtkhtml_mime_part_view_set_view_images_default;
231         klass->has_external_images_func = modest_gtkhtml_mime_part_view_has_external_images_default;
232         klass->get_zoom_func = modest_gtkhtml_mime_part_view_get_zoom_default;
233         klass->set_zoom_func = modest_gtkhtml_mime_part_view_set_zoom_default;
234         klass->zoom_minus_func = modest_gtkhtml_mime_part_view_zoom_minus_default;
235         klass->zoom_plus_func = modest_gtkhtml_mime_part_view_zoom_plus_default;
236         klass->search_func = modest_gtkhtml_mime_part_view_search_default;
237         klass->search_next_func = modest_gtkhtml_mime_part_view_search_next_default;
238         klass->get_selection_area_func = modest_gtkhtml_mime_part_view_get_selection_area_default;
239
240         binding_set = gtk_binding_set_by_class (klass);
241         gtk_binding_entry_skip (binding_set, GDK_Down, 0);
242         gtk_binding_entry_skip (binding_set, GDK_Up, 0);
243         gtk_binding_entry_skip (binding_set, GDK_KP_Up, 0);
244         gtk_binding_entry_skip (binding_set, GDK_KP_Down, 0);
245         gtk_binding_entry_skip (binding_set, GDK_Page_Down, 0);
246         gtk_binding_entry_skip (binding_set, GDK_Page_Up, 0);
247         gtk_binding_entry_skip (binding_set, GDK_KP_Page_Up, 0);
248         gtk_binding_entry_skip (binding_set, GDK_KP_Page_Down, 0);
249         gtk_binding_entry_skip (binding_set, GDK_Home, 0);
250         gtk_binding_entry_skip (binding_set, GDK_End, 0);
251         gtk_binding_entry_skip (binding_set, GDK_KP_Home, 0);
252         gtk_binding_entry_skip (binding_set, GDK_KP_End, 0);
253         
254         g_type_class_add_private (gobject_class, sizeof(ModestGtkhtmlMimePartViewPrivate));
255
256         signals[STOP_STREAMS_SIGNAL] = 
257                 g_signal_new ("stop-streams",
258                               G_TYPE_FROM_CLASS (gobject_class),
259                               G_SIGNAL_RUN_FIRST,
260                               G_STRUCT_OFFSET (ModestGtkhtmlMimePartViewClass,stop_streams),
261                               NULL, NULL,
262                               g_cclosure_marshal_VOID__VOID,
263                               G_TYPE_NONE, 0);
264
265 }
266
267 static void    
268 modest_gtkhtml_mime_part_view_init (ModestGtkhtmlMimePartView *self)
269 {
270         ModestGtkhtmlMimePartViewPrivate *priv = MODEST_GTKHTML_MIME_PART_VIEW_GET_PRIVATE (self);
271         GdkColor base;
272         GdkColor text;
273
274         gtk_html_set_editable        (GTK_HTML(self), FALSE);
275         gtk_html_allow_selection     (GTK_HTML(self), TRUE);
276         gtk_html_set_caret_mode      (GTK_HTML(self), FALSE);
277         gtk_html_set_blocking        (GTK_HTML(self), TRUE);
278         gtk_html_set_images_blocking (GTK_HTML(self), TRUE);
279
280         gdk_color_parse ("#fff", &base);
281         gdk_color_parse ("#000", &text);
282         gtk_widget_modify_base (GTK_WIDGET (self), GTK_STATE_NORMAL, &base);
283         gtk_widget_modify_text (GTK_WIDGET (self), GTK_STATE_NORMAL, &text);
284
285         priv->sighandlers = modest_signal_mgr_connect (priv->sighandlers,
286                                                        G_OBJECT(self), "link_clicked",
287                                                        G_CALLBACK(on_link_clicked), self);
288         priv->sighandlers = modest_signal_mgr_connect (priv->sighandlers,
289                                                        G_OBJECT(self), "url_requested",
290                                                        G_CALLBACK(on_url_requested), self);
291         priv->sighandlers = modest_signal_mgr_connect (priv->sighandlers,
292                                                        G_OBJECT(self), "on_url",
293                                                        G_CALLBACK(on_url), self);
294         priv->sighandlers = modest_signal_mgr_connect (priv->sighandlers,
295                                                        G_OBJECT(self), "notify::style",
296                                                        G_CALLBACK (on_notify_style), (gpointer) self);
297
298         priv->part = NULL;
299         priv->current_zoom = 1.0;
300         priv->view_images = FALSE;
301         priv->has_external_images = FALSE;
302 }
303
304 static void
305 modest_gtkhtml_mime_part_view_finalize (GObject *obj)
306 {
307         ModestGtkhtmlMimePartViewPrivate *priv = MODEST_GTKHTML_MIME_PART_VIEW_GET_PRIVATE (obj);
308
309         modest_signal_mgr_disconnect_all_and_destroy (priv->sighandlers);
310         priv->sighandlers = NULL;
311
312         G_OBJECT_CLASS (parent_class)->finalize (obj);
313 }
314
315 static void
316 modest_gtkhtml_mime_part_view_dispose (GObject *obj)
317 {
318         ModestGtkhtmlMimePartViewPrivate *priv = MODEST_GTKHTML_MIME_PART_VIEW_GET_PRIVATE (obj);
319
320         g_signal_emit (G_OBJECT (obj), signals[STOP_STREAMS_SIGNAL], 0);
321
322         if (priv->part) {
323                 g_object_unref (priv->part);
324                 priv->part = NULL;
325         }
326
327         G_OBJECT_CLASS (parent_class)->dispose (obj);
328 }
329
330 /* GTKHTML SIGNALS HANDLERS */
331
332 static void 
333 on_notify_style (GObject *obj, GParamSpec *spec, gpointer userdata)
334 {
335         if (strcmp ("style", spec->name) == 0) {
336                 g_idle_add_full (G_PRIORITY_DEFAULT, (GSourceFunc) update_style, 
337                                  g_object_ref (obj), g_object_unref);
338                 gtk_widget_queue_draw (GTK_WIDGET (obj));
339         }
340 }
341
342 gboolean
343 same_color (GdkColor *a, GdkColor *b)
344 {
345         return ((a->red == b->red) && 
346                 (a->green == b->green) && 
347                 (a->blue == b->blue));
348 }
349
350 static gboolean
351 update_style (ModestGtkhtmlMimePartView *self)
352 {
353         GdkColor base;
354         GdkColor text;
355         GtkRcStyle *rc_style;
356
357         gdk_threads_enter ();
358
359         if (GTK_WIDGET_VISIBLE (self)) {
360                 rc_style = gtk_widget_get_modifier_style (GTK_WIDGET (self));
361
362                 gdk_color_parse ("#fff", &base);
363                 gdk_color_parse ("#000", &text);
364
365                 if (!same_color (&(rc_style->base[GTK_STATE_NORMAL]), &base) &&
366                     !same_color (&(rc_style->text[GTK_STATE_NORMAL]), &text)) {
367
368                         rc_style->base[GTK_STATE_NORMAL] = base;
369                         rc_style->text[GTK_STATE_NORMAL] = text;
370                         gtk_widget_modify_style (GTK_WIDGET (self), rc_style);
371                 }
372         }
373
374         gdk_threads_leave ();
375
376         return FALSE;
377 }
378
379
380
381 static gboolean
382 on_link_clicked (GtkWidget *widget, const gchar *uri, ModestGtkhtmlMimePartView *self)
383 {
384         gboolean result;
385         g_return_val_if_fail (MODEST_IS_GTKHTML_MIME_PART_VIEW (self), FALSE);
386
387         g_signal_emit_by_name (G_OBJECT (self), "activate-link", uri, &result);
388         return result;
389 }
390
391 static gboolean
392 on_url (GtkWidget *widget, const gchar *uri, ModestGtkhtmlMimePartView *self)
393 {
394         gboolean result;
395         g_return_val_if_fail (MODEST_IS_GTKHTML_MIME_PART_VIEW (self), FALSE);
396
397         g_signal_emit_by_name (G_OBJECT (self), "link-hover", uri, &result);
398         return result;
399 }
400
401 typedef struct {
402         gpointer buffer;
403         GtkHTML *html;
404         GtkHTMLStream *stream;
405 } ImageFetcherInfo;
406
407 static gboolean
408 on_url_requested (GtkWidget *widget, const gchar *uri, GtkHTMLStream *stream, 
409                   ModestGtkhtmlMimePartView *self)
410 {
411         gboolean result;
412         TnyStream *tny_stream;
413         g_return_val_if_fail (MODEST_IS_GTKHTML_MIME_PART_VIEW (self), FALSE);
414
415         if (g_str_has_prefix (uri, "http:")) {
416                 ModestGtkhtmlMimePartViewPrivate *priv = MODEST_GTKHTML_MIME_PART_VIEW_GET_PRIVATE (self);
417
418                 if (!priv->view_images)
419                         priv->has_external_images = TRUE;
420         }
421                         
422         tny_stream = TNY_STREAM (modest_tny_stream_gtkhtml_new (stream, GTK_HTML (widget)));
423         g_signal_emit_by_name (MODEST_MIME_PART_VIEW (self), "fetch-url", uri, tny_stream, &result);
424         g_object_unref (tny_stream);
425         return result;
426 }
427
428 /* INTERNAL API */
429
430 static void
431 set_html_part (ModestGtkhtmlMimePartView *self, TnyMimePart *part)
432 {
433         GtkHTMLStream *gtkhtml_stream;
434         TnyStream *tny_stream;  
435         
436         g_return_if_fail (self);
437         g_return_if_fail (part);
438
439         g_signal_emit (G_OBJECT (self), signals[STOP_STREAMS_SIGNAL], 0);
440         
441         gtkhtml_stream = gtk_html_begin(GTK_HTML(self));
442
443         tny_stream     = TNY_STREAM(modest_tny_stream_gtkhtml_new (gtkhtml_stream, GTK_HTML (self)));
444         tny_stream_reset (tny_stream);
445
446         tny_mime_part_decode_to_stream ((TnyMimePart*)part, tny_stream, NULL);
447         tny_stream_close (tny_stream);
448         g_object_unref (G_OBJECT(tny_stream));
449 }
450
451 static void
452 set_text_part (ModestGtkhtmlMimePartView *self, TnyMimePart *part)
453 {
454         TnyStream* text_to_html_stream, *tny_stream;
455         GtkHTMLStream *gtkhtml_stream;
456         
457         g_return_if_fail (self);
458         g_return_if_fail (part);
459
460         g_signal_emit (G_OBJECT (self), signals[STOP_STREAMS_SIGNAL], 0);
461         
462         gtkhtml_stream = gtk_html_begin(GTK_HTML(self)); 
463         tny_stream =  TNY_STREAM(modest_tny_stream_gtkhtml_new (gtkhtml_stream, GTK_HTML (self)));
464         text_to_html_stream = TNY_STREAM (modest_stream_text_to_html_new (tny_stream));
465         modest_stream_text_to_html_set_linkify_limit (MODEST_STREAM_TEXT_TO_HTML (text_to_html_stream), 64*1024);
466         modest_stream_text_to_html_set_full_limit (MODEST_STREAM_TEXT_TO_HTML (text_to_html_stream), 640*1024);
467         
468         // FIXME: tinymail
469         tny_mime_part_decode_to_stream ((TnyMimePart*)part, text_to_html_stream, NULL);
470         tny_stream_write (text_to_html_stream, "\n", 1);
471         tny_stream_reset (text_to_html_stream);         
472         tny_stream_close (text_to_html_stream);
473         
474         g_object_unref (G_OBJECT(text_to_html_stream));
475         g_object_unref (G_OBJECT(tny_stream));
476         /* gtk_html_stream_destroy (gtkhtml_stream); */
477 }
478
479 static void
480 set_empty_part (ModestGtkhtmlMimePartView *self)
481 {
482         g_return_if_fail (self);
483
484         g_signal_emit (G_OBJECT (self), signals[STOP_STREAMS_SIGNAL], 0);
485         
486         gtk_html_load_from_string (GTK_HTML(self),
487                                    "", 1);
488 }
489
490 static void
491 set_part (ModestGtkhtmlMimePartView *self, TnyMimePart *part)
492 {
493         ModestGtkhtmlMimePartViewPrivate *priv;
494
495         g_return_if_fail (self);
496         
497         priv = MODEST_GTKHTML_MIME_PART_VIEW_GET_PRIVATE(self);
498         priv->has_external_images = FALSE;
499
500         if (part != priv->part) {
501                 if (priv->part)
502                         g_object_unref (G_OBJECT(priv->part));
503                 if (part)
504                         g_object_ref   (G_OBJECT(part));
505                 priv->part = part;
506         }
507         
508         if (!part) {
509                 set_empty_part (self);
510                 return;
511         }
512
513         if (tny_mime_part_content_type_is (part, "text/html")) {
514                 set_html_part (self, part);
515         } else {
516                 if (tny_mime_part_content_type_is (part, "message/rfc822")) {
517                         gchar *header_content_type, *header_content_type_lower;
518                         header_content_type = modest_tny_mime_part_get_header_value (part, "Content-Type");
519                         if (header_content_type) {
520                                 header_content_type = g_strstrip (header_content_type);
521                                 header_content_type_lower = g_ascii_strdown (header_content_type, -1);
522
523                                 if (!g_ascii_strcasecmp (header_content_type_lower, "text/html"))
524                                         set_html_part (self, part);
525                                 else 
526                                         set_text_part (self, part);
527
528                                 g_free (header_content_type_lower);
529                                 g_free (header_content_type);
530                         } else {
531                                 set_text_part (self, part);
532                         }
533                 } else {
534                         set_text_part (self, part);
535                 }
536         }
537
538 }
539
540 static TnyMimePart*
541 get_part (ModestGtkhtmlMimePartView *self)
542 {
543         TnyMimePart *part;
544
545         g_return_val_if_fail (MODEST_IS_GTKHTML_MIME_PART_VIEW (self), NULL);
546
547         part = MODEST_GTKHTML_MIME_PART_VIEW_GET_PRIVATE(self)->part;
548
549         if (part)
550                 g_object_ref (part);
551         
552         return part;
553 }
554
555 static gboolean
556 has_contents_receiver (gpointer engine, const gchar *data,
557                        size_t len, gboolean *has_contents)
558 {
559         if (len > 1 || ((len == 1)&&(data[0]!='\n'))) {
560                 *has_contents = TRUE;
561                 return FALSE;
562         }
563         return TRUE;
564 }
565
566 static gboolean      
567 is_empty   (ModestGtkhtmlMimePartView *self)
568 {
569         /* TODO: Find some gtkhtml API to check whether there is any (visible, non markup)
570          * text in the message:
571          */
572         gboolean has_contents = FALSE;
573
574         gtk_html_export (GTK_HTML (self), "text/plain", 
575                          (GtkHTMLSaveReceiverFn) has_contents_receiver, &has_contents);
576         
577         return !has_contents;
578 }
579
580 static gboolean      
581 get_view_images   (ModestGtkhtmlMimePartView *self)
582 {
583         ModestGtkhtmlMimePartViewPrivate *priv;
584
585         g_return_val_if_fail (MODEST_IS_GTKHTML_MIME_PART_VIEW (self), FALSE);
586
587         priv = MODEST_GTKHTML_MIME_PART_VIEW_GET_PRIVATE (self);
588         return priv->view_images;
589 }
590
591 static void
592 set_view_images   (ModestGtkhtmlMimePartView *self, gboolean view_images)
593 {
594         ModestGtkhtmlMimePartViewPrivate *priv;
595
596         g_return_if_fail (MODEST_IS_GTKHTML_MIME_PART_VIEW (self));
597
598         priv = MODEST_GTKHTML_MIME_PART_VIEW_GET_PRIVATE (self);
599         priv->view_images = view_images;
600 }
601
602 static gboolean      
603 has_external_images   (ModestGtkhtmlMimePartView *self)
604 {
605         ModestGtkhtmlMimePartViewPrivate *priv;
606
607         g_return_val_if_fail (MODEST_IS_GTKHTML_MIME_PART_VIEW (self), FALSE);
608
609         priv = MODEST_GTKHTML_MIME_PART_VIEW_GET_PRIVATE (self);
610         return priv->has_external_images;
611 }
612
613 static void
614 set_zoom (ModestGtkhtmlMimePartView *self, gdouble zoom)
615 {
616         ModestGtkhtmlMimePartViewPrivate *priv;
617
618         g_return_if_fail (MODEST_IS_GTKHTML_MIME_PART_VIEW (self));
619
620         priv = MODEST_GTKHTML_MIME_PART_VIEW_GET_PRIVATE (self);
621         priv->current_zoom = zoom;
622         gtk_html_set_magnification (GTK_HTML(self), zoom);
623
624         gtk_widget_queue_resize (GTK_WIDGET (self));
625 }
626
627 static gdouble
628 get_zoom (ModestGtkhtmlMimePartView *self)
629 {
630         ModestGtkhtmlMimePartViewPrivate *priv;
631
632         g_return_val_if_fail (MODEST_IS_GTKHTML_MIME_PART_VIEW (self), 1.0);
633
634         priv = MODEST_GTKHTML_MIME_PART_VIEW_GET_PRIVATE (self);
635
636         return priv->current_zoom;
637 }
638
639 static gboolean
640 search (ModestGtkhtmlMimePartView *self, 
641         const gchar *string)
642 {
643         return gtk_html_engine_search (GTK_HTML (self),
644                                        string,
645                                        FALSE,   /* case sensitive */
646                                        TRUE,    /* forward */
647                                        FALSE);  /* regexp */
648 }
649
650 static gboolean
651 search_next (ModestGtkhtmlMimePartView *self)
652 {
653         return gtk_html_engine_search_next (GTK_HTML (self));
654 }
655
656 static gboolean
657 get_selection_area (ModestGtkhtmlMimePartView *self, 
658                     gint *x, gint *y,
659                     gint *width, gint *height)
660 {
661 #ifdef HAVE_GTK_HTML_GET_SELECTION_AREA
662         gtk_html_get_selection_area (GTK_HTML (self), x, y, width, height);
663         return TRUE;
664 #else
665         return FALSE;
666 #endif
667 }
668
669
670 /* TNY MIME PART IMPLEMENTATION */
671
672 static void
673 tny_mime_part_view_init (gpointer g, gpointer iface_data)
674 {
675         TnyMimePartViewIface *klass = (TnyMimePartViewIface *)g;
676
677         klass->get_part = modest_gtkhtml_mime_part_view_get_part;
678         klass->set_part = modest_gtkhtml_mime_part_view_set_part;
679         klass->clear = modest_gtkhtml_mime_part_view_clear;
680
681         return;
682 }
683
684 static TnyMimePart* 
685 modest_gtkhtml_mime_part_view_get_part (TnyMimePartView *self)
686 {
687         return MODEST_GTKHTML_MIME_PART_VIEW_GET_CLASS (self)->get_part_func (self);
688 }
689
690
691 static TnyMimePart* 
692 modest_gtkhtml_mime_part_view_get_part_default (TnyMimePartView *self)
693 {
694         return TNY_MIME_PART (get_part (MODEST_GTKHTML_MIME_PART_VIEW (self)));
695 }
696
697 static void
698 modest_gtkhtml_mime_part_view_set_part (TnyMimePartView *self,
699                                         TnyMimePart *part)
700 {
701         MODEST_GTKHTML_MIME_PART_VIEW_GET_CLASS (self)->set_part_func (self, part);
702 }
703
704 static void
705 modest_gtkhtml_mime_part_view_set_part_default (TnyMimePartView *self,
706                                                 TnyMimePart *part)
707 {
708         g_return_if_fail ((part == NULL) || TNY_IS_MIME_PART (part));
709
710         set_part (MODEST_GTKHTML_MIME_PART_VIEW (self), part);
711 }
712
713 static void
714 modest_gtkhtml_mime_part_view_clear (TnyMimePartView *self)
715 {
716         MODEST_GTKHTML_MIME_PART_VIEW_GET_CLASS (self)->clear_func (self);
717 }
718
719 static void
720 modest_gtkhtml_mime_part_view_clear_default (TnyMimePartView *self)
721 {
722         set_part (MODEST_GTKHTML_MIME_PART_VIEW (self), NULL);
723 }
724
725 /* MODEST MIME PART VIEW IMPLEMENTATION */
726
727 static void
728 modest_mime_part_view_init (gpointer g, gpointer iface_data)
729 {
730         ModestMimePartViewIface *klass = (ModestMimePartViewIface *)g;
731
732         klass->is_empty_func = modest_gtkhtml_mime_part_view_is_empty;
733         klass->get_view_images_func = modest_gtkhtml_mime_part_view_get_view_images;
734         klass->set_view_images_func = modest_gtkhtml_mime_part_view_set_view_images;
735         klass->has_external_images_func = modest_gtkhtml_mime_part_view_has_external_images;
736
737         return;
738 }
739
740 static gboolean
741 modest_gtkhtml_mime_part_view_is_empty (ModestMimePartView *self)
742 {
743         return MODEST_GTKHTML_MIME_PART_VIEW_GET_CLASS (self)->is_empty_func (self);
744 }
745
746 static gboolean
747 modest_gtkhtml_mime_part_view_get_view_images (ModestMimePartView *self)
748 {
749         return MODEST_GTKHTML_MIME_PART_VIEW_GET_CLASS (self)->get_view_images_func (self);
750 }
751
752 static void
753 modest_gtkhtml_mime_part_view_set_view_images (ModestMimePartView *self, gboolean view_images)
754 {
755         MODEST_GTKHTML_MIME_PART_VIEW_GET_CLASS (self)->set_view_images_func (self, view_images);
756 }
757
758 static gboolean
759 modest_gtkhtml_mime_part_view_has_external_images (ModestMimePartView *self)
760 {
761         return MODEST_GTKHTML_MIME_PART_VIEW_GET_CLASS (self)->has_external_images_func (self);
762 }
763
764 static gboolean
765 modest_gtkhtml_mime_part_view_is_empty_default (ModestMimePartView *self)
766 {
767         return is_empty (MODEST_GTKHTML_MIME_PART_VIEW (self));
768 }
769
770 static gboolean
771 modest_gtkhtml_mime_part_view_get_view_images_default (ModestMimePartView *self)
772 {
773         return get_view_images (MODEST_GTKHTML_MIME_PART_VIEW (self));
774 }
775
776 static void
777 modest_gtkhtml_mime_part_view_set_view_images_default (ModestMimePartView *self, gboolean view_images)
778 {
779         set_view_images (MODEST_GTKHTML_MIME_PART_VIEW (self), view_images);
780 }
781
782 static gboolean
783 modest_gtkhtml_mime_part_view_has_external_images_default (ModestMimePartView *self)
784 {
785         return has_external_images (MODEST_GTKHTML_MIME_PART_VIEW (self));
786 }
787
788
789 /* MODEST ZOOMABLE IMPLEMENTATION */
790 static void
791 modest_zoomable_init (gpointer g, gpointer iface_data)
792 {
793         ModestZoomableIface *klass = (ModestZoomableIface *)g;
794         
795         klass->get_zoom_func = modest_gtkhtml_mime_part_view_get_zoom;
796         klass->set_zoom_func = modest_gtkhtml_mime_part_view_set_zoom;
797         klass->zoom_minus_func = modest_gtkhtml_mime_part_view_zoom_minus;
798         klass->zoom_plus_func = modest_gtkhtml_mime_part_view_zoom_plus;
799
800         return;
801 }
802
803 static gdouble
804 modest_gtkhtml_mime_part_view_get_zoom (ModestZoomable *self)
805 {
806         return MODEST_GTKHTML_MIME_PART_VIEW_GET_CLASS (self)->get_zoom_func (self);
807 }
808
809 static gdouble
810 modest_gtkhtml_mime_part_view_get_zoom_default (ModestZoomable *self)
811 {
812         return get_zoom (MODEST_GTKHTML_MIME_PART_VIEW (self));
813 }
814
815 static void
816 modest_gtkhtml_mime_part_view_set_zoom (ModestZoomable *self, gdouble value)
817 {
818         MODEST_GTKHTML_MIME_PART_VIEW_GET_CLASS (self)->set_zoom_func (self, value);
819 }
820
821 static void
822 modest_gtkhtml_mime_part_view_set_zoom_default (ModestZoomable *self, gdouble value)
823 {
824         set_zoom (MODEST_GTKHTML_MIME_PART_VIEW (self), value);
825 }
826
827 static gboolean
828 modest_gtkhtml_mime_part_view_zoom_minus (ModestZoomable *self)
829 {
830         return MODEST_GTKHTML_MIME_PART_VIEW_GET_CLASS (self)->zoom_minus_func (self);
831 }
832
833 static gboolean
834 modest_gtkhtml_mime_part_view_zoom_minus_default (ModestZoomable *self)
835 {
836         /* operation not supported in ModestGtkhtmlMimePartView */
837         return FALSE;
838 }
839
840 static gboolean
841 modest_gtkhtml_mime_part_view_zoom_plus (ModestZoomable *self)
842 {
843         return MODEST_GTKHTML_MIME_PART_VIEW_GET_CLASS (self)->zoom_plus_func (self);
844 }
845
846 static gboolean
847 modest_gtkhtml_mime_part_view_zoom_plus_default (ModestZoomable *self)
848 {
849         /* operation not supported in ModestGtkhtmlMimePartView */
850         return FALSE;
851 }
852
853 /* ISEARCH VIEW IMPLEMENTATION */
854 static void
855 modest_isearch_view_init (gpointer g, gpointer iface_data)
856 {
857         ModestISearchViewIface *klass = (ModestISearchViewIface *)g;
858         
859         klass->search_func = modest_gtkhtml_mime_part_view_search;
860         klass->search_next_func = modest_gtkhtml_mime_part_view_search_next;
861         klass->get_selection_area_func = modest_gtkhtml_mime_part_view_get_selection_area;
862
863         return;
864 }
865
866 static gboolean 
867 modest_gtkhtml_mime_part_view_search (ModestISearchView *self, const gchar *string)
868 {
869         return MODEST_GTKHTML_MIME_PART_VIEW_GET_CLASS (self)->search_func (self, string);
870 }
871
872 static gboolean 
873 modest_gtkhtml_mime_part_view_search_default (ModestISearchView *self, const gchar *string)
874 {
875         return search (MODEST_GTKHTML_MIME_PART_VIEW (self), string);
876 }
877
878 static gboolean 
879 modest_gtkhtml_mime_part_view_search_next(ModestISearchView *self)
880 {
881         return MODEST_GTKHTML_MIME_PART_VIEW_GET_CLASS (self)->search_next_func (self);
882 }
883
884 static gboolean 
885 modest_gtkhtml_mime_part_view_search_next_default (ModestISearchView *self)
886 {
887         return search_next (MODEST_GTKHTML_MIME_PART_VIEW (self));
888 }
889
890 static gboolean 
891 modest_gtkhtml_mime_part_view_get_selection_area (ModestISearchView *self, gint *x, gint *y, 
892                                                   gint *width, gint *height)
893 {
894         return MODEST_GTKHTML_MIME_PART_VIEW_GET_CLASS (self)->get_selection_area_func (self, x, y, width, height);
895 }
896
897 static gboolean 
898 modest_gtkhtml_mime_part_view_get_selection_area_default (ModestISearchView *self, gint *x, gint *y, 
899                                                           gint *width, gint *height)
900 {
901         return get_selection_area (MODEST_GTKHTML_MIME_PART_VIEW (self), x, y, width, height);
902 }