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