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