* src/maemo/modest-msg-edit-window.c:
[modest] / src / widgets / modest-msg-view.c
1 /* Copyright (c) 2006, 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 <tny-gtk-text-buffer-stream.h>
31 #include <string.h>
32 #include <regex.h>
33 #include <ctype.h>
34 #include <stdlib.h>
35 #include <glib/gi18n.h>
36 #include <gtkhtml/gtkhtml.h>
37 #include <gtkhtml/gtkhtml-stream.h>
38 #include <gtkhtml/gtkhtml-search.h>
39 #include <gtkhtml/gtkhtml-embedded.h>
40 #include <tny-list.h>
41 #include <tny-simple-list.h>
42
43 #include <modest-tny-msg.h>
44 #include <modest-text-utils.h>
45 #include "modest-msg-view.h"
46 #include "modest-tny-stream-gtkhtml.h"
47 #include <modest-mail-header-view.h>
48 #include <modest-attachments-view.h>
49 #include <modest-marshal.h>
50
51
52 /* 'private'/'protected' functions */
53 static void     modest_msg_view_class_init   (ModestMsgViewClass *klass);
54 static void     modest_msg_view_init         (ModestMsgView *obj);
55 static void     modest_msg_view_finalize     (GObject *obj);
56 static void     modest_msg_view_destroy     (GtkObject *obj);
57 static void     set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
58 static void     get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
59
60 /* headers signals */
61 static void on_recpt_activated (ModestMailHeaderView *header_view, const gchar *address, ModestMsgView *msg_view);
62 static void on_attachment_activated (ModestAttachmentsView * att_view, TnyMimePart *mime_part, gpointer userdata);
63
64 /* GtkHtml signals */
65 static gboolean on_link_clicked (GtkWidget *widget, const gchar *uri, ModestMsgView *msg_view);
66 static gboolean on_url_requested (GtkWidget *widget, const gchar *uri, GtkHTMLStream *stream,
67                                   ModestMsgView *msg_view);
68 static gboolean on_link_hover (GtkWidget *widget, const gchar *uri, ModestMsgView *msg_view);
69 static void     on_tap_and_hold (GtkWidget *widget, gpointer userdata);
70
71 /* size allocation and drawing handlers */
72 static void get_view_allocation (ModestMsgView *msg_view, GtkAllocation *allocation);
73 static void size_request (GtkWidget *widget, GtkRequisition *req);
74 static void size_allocate (GtkWidget *widget, GtkAllocation *alloc);
75 static void realize (GtkWidget *widget);
76 static void unrealize (GtkWidget *widget);
77 static gint expose (GtkWidget *widget, GdkEventExpose *event);
78 static void reclamp_adjustment (GtkAdjustment *adj, gboolean *value_changed);
79 static void set_hadjustment_values (ModestMsgView *msg_view, gboolean *value_changed);
80 static void set_scroll_adjustments (ModestMsgView *msg_view, GtkAdjustment *hadj, GtkAdjustment *vadj);
81 static void adjustment_value_changed (GtkAdjustment *adj, gpointer data);
82 static void html_adjustment_changed (GtkAdjustment *adj, gpointer data);
83 static void disconnect_vadjustment (ModestMsgView *obj);
84 static void disconnect_hadjustment (ModestMsgView *obj);
85
86 /* GtkContainer methods */
87 static void forall (GtkContainer *container, gboolean include_internals,
88                     GtkCallback callback, gpointer userdata);
89 static void container_remove (GtkContainer *container, GtkWidget *widget);
90
91 /* list my signals */
92 enum {
93         LINK_CLICKED_SIGNAL,
94         LINK_HOVER_SIGNAL,
95         ATTACHMENT_CLICKED_SIGNAL,
96         RECPT_ACTIVATED_SIGNAL,
97         LINK_CONTEXTUAL_SIGNAL,
98         LAST_SIGNAL
99 };
100
101 /* list properties */
102 enum {
103         PROP_0,
104         PROP_HADJUSTMENT,
105         PROP_VADJUSTMENT,
106         PROP_SHADOW_TYPE
107 };
108
109 typedef struct _ModestMsgViewPrivate ModestMsgViewPrivate;
110 struct _ModestMsgViewPrivate {
111         GtkWidget   *gtkhtml;
112         GtkWidget   *mail_header_view;
113         GtkWidget   *attachments_view;
114
115         TnyMsg      *msg;
116
117         /* embedded elements */
118         GtkWidget   *headers_box;
119         GtkWidget   *html_scroll;
120         GtkWidget   *attachments_box;
121
122         /* internal adjustments for set_scroll_adjustments */
123         GtkAdjustment *hadj;
124         GtkAdjustment *vadj;
125         GtkShadowType shadow_type;
126
127         /* gdk windows for drawing */
128         GdkWindow *view_window;
129         GdkWindow *headers_window;
130         GdkWindow *html_window;
131
132         /* zoom */
133         gdouble current_zoom;
134
135         /* link click management */
136         gchar *last_url;
137
138         TnyHeaderFlags priority_flags;
139
140         gulong  sig1, sig2, sig3;
141 };
142 #define MODEST_MSG_VIEW_GET_PRIVATE(o)      (G_TYPE_INSTANCE_GET_PRIVATE((o), \
143                                                  MODEST_TYPE_MSG_VIEW, \
144                                                  ModestMsgViewPrivate))
145 /* globals */
146 static GtkContainerClass *parent_class = NULL;
147
148 /* uncomment the following if you have defined any signals */
149 static guint signals[LAST_SIGNAL] = {0};
150
151 GType
152 modest_msg_view_get_type (void)
153 {
154         static GType my_type = 0;
155         if (!my_type) {
156                 static const GTypeInfo my_info = {
157                         sizeof(ModestMsgViewClass),
158                         NULL,           /* base init */
159                         NULL,           /* base finalize */
160                         (GClassInitFunc) modest_msg_view_class_init,
161                         NULL,           /* class finalize */
162                         NULL,           /* class data */
163                         sizeof(ModestMsgView),
164                         1,              /* n_preallocs */
165                         (GInstanceInitFunc) modest_msg_view_init,
166                         NULL
167                 };
168                 my_type = g_type_register_static (GTK_TYPE_CONTAINER,
169                                                   "ModestMsgView",
170                                                   &my_info, 0);
171         }
172         return my_type;
173 }
174
175 static void
176 modest_msg_view_class_init (ModestMsgViewClass *klass)
177 {
178         GObjectClass *gobject_class;
179         GtkWidgetClass *widget_class;
180         GtkObjectClass *gtkobject_class;
181         GtkContainerClass *container_class;
182         gobject_class = (GObjectClass*) klass;
183         widget_class = (GtkWidgetClass *) klass;
184         gtkobject_class = (GtkObjectClass *) klass;
185         container_class = (GtkContainerClass *) klass;
186
187         parent_class            = g_type_class_peek_parent (klass);
188         gobject_class->finalize = modest_msg_view_finalize;
189         gobject_class->set_property = set_property;
190         gobject_class->get_property = get_property;
191         gtkobject_class->destroy = modest_msg_view_destroy;
192
193         widget_class->realize = realize;
194         widget_class->unrealize = unrealize;
195         widget_class->expose_event = expose;
196         widget_class->size_request = size_request;
197         widget_class->size_allocate = size_allocate;
198
199         container_class->forall = forall;
200         container_class->remove = container_remove;
201
202         klass->set_scroll_adjustments = set_scroll_adjustments;
203
204         g_type_class_add_private (gobject_class, sizeof(ModestMsgViewPrivate));
205
206         g_object_class_install_property (gobject_class,
207                                          PROP_HADJUSTMENT,
208                                          g_param_spec_object ("hadjustment", 
209                                                               _("Horizontal adjustment"),
210                                                               _("GtkAdjustment with information of the horizontal visible position"),
211                                                               GTK_TYPE_ADJUSTMENT,
212                                                               G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT));
213
214         g_object_class_install_property (gobject_class,
215                                          PROP_VADJUSTMENT,
216                                          g_param_spec_object ("vadjustment", 
217                                                               _("Vertical adjustment"),
218                                                               _("GtkAdjustment with information of the vertical visible position"),
219                                                               GTK_TYPE_ADJUSTMENT,
220                                                               G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT));
221
222         g_object_class_install_property (gobject_class,
223                                          PROP_SHADOW_TYPE,
224                                          g_param_spec_enum ("shadow_type", 
225                                                             _("Shadow type"),
226                                                             _("Kind of shadow that's shown around the view"),
227                                                             GTK_TYPE_SHADOW_TYPE,
228                                                             GTK_SHADOW_IN,
229                                                             G_PARAM_READABLE | G_PARAM_WRITABLE ));
230
231         signals[LINK_CLICKED_SIGNAL] =
232                 g_signal_new ("link_clicked",
233                               G_TYPE_FROM_CLASS (gobject_class),
234                               G_SIGNAL_RUN_FIRST,
235                               G_STRUCT_OFFSET(ModestMsgViewClass, link_clicked),
236                               NULL, NULL,
237                               g_cclosure_marshal_VOID__STRING,
238                               G_TYPE_NONE, 1, G_TYPE_STRING);
239         
240         signals[ATTACHMENT_CLICKED_SIGNAL] =
241                 g_signal_new ("attachment_clicked",
242                               G_TYPE_FROM_CLASS (gobject_class),
243                               G_SIGNAL_RUN_FIRST,
244                               G_STRUCT_OFFSET(ModestMsgViewClass, attachment_clicked),
245                               NULL, NULL,
246                               g_cclosure_marshal_VOID__OBJECT,
247                               G_TYPE_NONE, 1, G_TYPE_OBJECT);
248         
249         signals[LINK_HOVER_SIGNAL] =
250                 g_signal_new ("link_hover",
251                               G_TYPE_FROM_CLASS (gobject_class),
252                               G_SIGNAL_RUN_FIRST,
253                               G_STRUCT_OFFSET(ModestMsgViewClass, link_hover),
254                               NULL, NULL,
255                               g_cclosure_marshal_VOID__STRING,
256                               G_TYPE_NONE, 1, G_TYPE_STRING);
257
258         signals[RECPT_ACTIVATED_SIGNAL] =
259                 g_signal_new ("recpt_activated",
260                               G_TYPE_FROM_CLASS (gobject_class),
261                               G_SIGNAL_RUN_FIRST,
262                               G_STRUCT_OFFSET(ModestMsgViewClass, recpt_activated),
263                               NULL, NULL,
264                               g_cclosure_marshal_VOID__STRING,
265                               G_TYPE_NONE, 1, G_TYPE_STRING);
266
267         signals[LINK_CONTEXTUAL_SIGNAL] =
268                 g_signal_new ("link_contextual",
269                               G_TYPE_FROM_CLASS (gobject_class),
270                               G_SIGNAL_RUN_FIRST,
271                               G_STRUCT_OFFSET(ModestMsgViewClass, link_contextual),
272                               NULL, NULL,
273                               g_cclosure_marshal_VOID__STRING,
274                               G_TYPE_NONE, 1, G_TYPE_STRING);
275
276         widget_class->set_scroll_adjustments_signal =
277                 g_signal_new ("set_scroll_adjustments",
278                               G_OBJECT_CLASS_TYPE (gobject_class),
279                               G_SIGNAL_RUN_LAST|G_SIGNAL_ACTION,
280                               G_STRUCT_OFFSET (ModestMsgViewClass, set_scroll_adjustments),
281                               NULL, NULL,
282                               modest_marshal_VOID__POINTER_POINTER,
283                               G_TYPE_NONE, 2,
284                               GTK_TYPE_ADJUSTMENT,
285                               GTK_TYPE_ADJUSTMENT);
286 }
287
288 static void
289 set_property (GObject *object, 
290               guint prop_id, 
291               const GValue *value, 
292               GParamSpec *pspec)
293 {
294         ModestMsgView *msg_view = MODEST_MSG_VIEW (object);
295
296         switch (prop_id) {
297         case PROP_HADJUSTMENT:
298                 modest_msg_view_set_hadjustment (msg_view, g_value_get_object (value));
299                 break;
300         case PROP_VADJUSTMENT:
301                 modest_msg_view_set_vadjustment (msg_view, g_value_get_object (value));
302                 break;
303         case PROP_SHADOW_TYPE:
304                 modest_msg_view_set_shadow_type (msg_view, g_value_get_enum (value));
305                 break;
306         default:
307                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
308                 break;
309         }
310 }
311
312 static void
313 get_property (GObject *object, 
314               guint prop_id, 
315               GValue *value, 
316               GParamSpec *pspec)
317 {
318         ModestMsgView *msg_view = MODEST_MSG_VIEW (object);
319         ModestMsgViewPrivate *priv = MODEST_MSG_VIEW_GET_PRIVATE (msg_view);
320
321         switch (prop_id) {
322         case PROP_HADJUSTMENT:
323                 g_value_set_object (value, priv->hadj);
324                 break;
325         case PROP_VADJUSTMENT:
326                 g_value_set_object (value, priv->vadj);
327                 break;
328         case PROP_SHADOW_TYPE:
329                 g_value_set_enum (value, priv->shadow_type);
330                 break;
331         default:
332                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
333                 break;
334         }
335 }
336
337 static void
338 disconnect_hadjustment (ModestMsgView *msg_view)
339 {
340         ModestMsgViewPrivate *priv = MODEST_MSG_VIEW_GET_PRIVATE (msg_view);
341
342         if (priv->hadj) {
343                 g_signal_handlers_disconnect_by_func(priv->hadj, adjustment_value_changed, msg_view);
344                 g_object_unref (priv->hadj);
345                 priv->hadj = NULL;
346         }
347 }
348
349 static void
350 disconnect_vadjustment (ModestMsgView *msg_view)
351 {
352         ModestMsgViewPrivate *priv = MODEST_MSG_VIEW_GET_PRIVATE (msg_view);
353
354         if (priv->vadj) {
355                 g_signal_handlers_disconnect_by_func(priv->vadj, adjustment_value_changed, msg_view);
356                 g_object_unref (priv->vadj);
357                 priv->vadj = NULL;
358         }
359 }
360
361 static void 
362 get_view_allocation (ModestMsgView *msg_view, GtkAllocation *allocation)
363 {
364         /* This method gets the allocation of the widget in parent widget. It's the
365            real position and size of the widget */
366         ModestMsgViewPrivate *priv = MODEST_MSG_VIEW_GET_PRIVATE (msg_view);
367         
368         allocation->x = 0;
369         allocation->y = 0;
370
371         if (priv->shadow_type != GTK_SHADOW_NONE) {
372                 allocation->x = GTK_WIDGET (msg_view)->style->xthickness;
373                 allocation->y = GTK_WIDGET (msg_view)->style->ythickness;
374         }
375
376         allocation->width = MAX (1, (GTK_WIDGET (msg_view)->allocation.width) - allocation->x * 2);
377         allocation->height = MAX (1, (GTK_WIDGET (msg_view)->allocation.height) - allocation->y * 2);
378
379 }
380
381 static void 
382 reclamp_adjustment (GtkAdjustment *adj, 
383                     gboolean *value_changed)
384 {
385         gdouble value = adj->value;
386
387         /* Correct value to be inside the expected values of a scroll */
388
389         value = CLAMP (value, 0, adj->upper - adj->page_size);
390
391         if (value != adj->value) {
392                 adj->value = value;
393                 if (value_changed)
394                         *value_changed = TRUE;
395         } else if (value_changed) {
396                 *value_changed = FALSE;
397         }
398 }
399
400 static void 
401 set_hadjustment_values (ModestMsgView *msg_view,
402                         gboolean *value_changed)
403 {
404         GtkAllocation view_allocation;
405         GtkAdjustment *hadj = modest_msg_view_get_hadjustment (msg_view);
406
407         get_view_allocation (msg_view, &view_allocation);
408         hadj->page_size = view_allocation.width;
409         hadj->step_increment = view_allocation.width * 0.1;
410         hadj->page_increment = view_allocation.width * 0.9;
411
412         hadj->lower = 0;
413         hadj->upper = view_allocation.width;
414
415         reclamp_adjustment (hadj, value_changed);
416
417 }
418
419 static void 
420 set_vadjustment_values (ModestMsgView *msg_view,
421                         gboolean *value_changed)
422 {
423         GtkAllocation view_allocation;
424         GtkAdjustment *vadj = modest_msg_view_get_vadjustment (msg_view);
425         ModestMsgViewPrivate *priv = MODEST_MSG_VIEW_GET_PRIVATE (msg_view);
426         gint full_height = 0;
427         GtkAdjustment *html_vadj;
428
429         get_view_allocation (msg_view, &view_allocation);
430         vadj->page_size = view_allocation.height;
431         vadj->step_increment = view_allocation.height * 0.1;
432         vadj->page_increment = view_allocation.height * 0.9;
433
434         vadj->lower = 0;
435
436         if (priv->headers_box && GTK_WIDGET_VISIBLE(priv->headers_box)) {
437                 GtkRequisition child_requisition;
438
439                 gtk_widget_get_child_requisition (priv->headers_box, &child_requisition);
440                 full_height = child_requisition.height;
441         } else {
442                 full_height = 0;
443         }
444         
445         /* Get the real height of the embedded html */
446         if (priv->html_scroll && GTK_WIDGET_VISIBLE(priv->html_scroll)) {
447                 html_vadj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (priv->html_scroll));
448                 full_height += html_vadj->upper;
449         }
450
451         vadj->upper = MAX (view_allocation.height, full_height);
452
453         reclamp_adjustment (vadj, value_changed);
454
455 }
456
457 static void
458 set_scroll_adjustments (ModestMsgView *msg_view,
459                         GtkAdjustment *hadj,
460                         GtkAdjustment *vadj)
461 {
462         ModestMsgViewPrivate *priv = MODEST_MSG_VIEW_GET_PRIVATE (msg_view);
463         modest_msg_view_set_hadjustment (msg_view, hadj);
464         modest_msg_view_set_vadjustment (msg_view, vadj);
465
466         gtk_container_set_focus_vadjustment (GTK_CONTAINER (priv->gtkhtml), vadj);
467 }
468
469 static void
470 realize (GtkWidget *widget)
471 {
472         ModestMsgView *msg_view = MODEST_MSG_VIEW (widget);
473         ModestMsgViewPrivate *priv = MODEST_MSG_VIEW_GET_PRIVATE (msg_view);
474         GtkAdjustment *hadj = modest_msg_view_get_hadjustment (msg_view);
475         GtkAdjustment *vadj = modest_msg_view_get_vadjustment (msg_view);
476         GdkWindowAttr attributes;
477         gint event_mask;
478         gint attributes_mask;
479         GtkAllocation view_allocation;
480
481         GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
482
483         /* The structure of the GdkWindow's is:
484          *    * widget->window: the shown gdkwindow embedding all the stuff inside
485          *    * view_window: a backing store gdkwindow containing the headers and contents
486          *      being scrolled. This window should have all the visible and non visible
487          *      widgets inside.
488          *    * headers_window: gdk window for headers_box.
489          *    * html_window: gdk window for html_scroll (the scrolled window containing the
490          *      gtkhtml showing the contents of the mail).
491          */
492
493         attributes.x = widget->allocation.x;
494         attributes.y = widget->allocation.y;
495         attributes.width = widget->allocation.width;
496         attributes.height = widget->allocation.height;
497         attributes.window_type = GDK_WINDOW_CHILD;
498         attributes.wclass = GDK_INPUT_OUTPUT;
499         attributes.visual = gtk_widget_get_visual (widget);
500         attributes.colormap = gtk_widget_get_colormap (widget);
501
502         event_mask = gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK;
503         attributes.event_mask = event_mask | GDK_BUTTON_PRESS_MASK;
504         attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
505
506         widget->window = gdk_window_new (gtk_widget_get_parent_window (widget),
507                                          &attributes, attributes_mask);
508         gdk_window_set_user_data (widget->window, msg_view);
509
510         get_view_allocation (msg_view, &view_allocation);
511
512         attributes.x = view_allocation.x;
513         attributes.y = view_allocation.y;
514         attributes.width = view_allocation.width;
515         attributes.height = view_allocation.height;
516         attributes.event_mask = 0;
517         priv->view_window = gdk_window_new (widget->window, &attributes, attributes_mask);
518         gdk_window_set_user_data (priv->view_window, msg_view);
519         gdk_window_set_back_pixmap (priv->view_window, NULL, FALSE);
520
521         attributes.x = -hadj->value;
522         attributes.y = -vadj->value;
523         attributes.width = hadj->upper;
524         if (priv->headers_box)
525                 attributes.height = GTK_WIDGET (priv->headers_box)->allocation.height;
526         else
527                 attributes.height = 1;
528         attributes.event_mask = event_mask;
529
530         priv->headers_window = gdk_window_new (priv->view_window, &attributes, attributes_mask);
531         gdk_window_set_user_data (priv->headers_window, msg_view);
532
533         if (priv->headers_box)
534                 gtk_widget_set_parent_window (priv->headers_box, priv->headers_window);
535
536         attributes.x = -hadj->value;
537         if (priv->headers_box)
538                 attributes.y = GTK_WIDGET (priv->headers_box)->allocation.height - vadj->value;
539         else 
540                 attributes.y = -vadj->value;
541         attributes.width = hadj->upper;
542         if (priv->headers_box)
543                 attributes.height = vadj->upper - GTK_WIDGET (priv->headers_box)->allocation.height;
544         else
545                 attributes.height = vadj->upper;
546         attributes.event_mask = event_mask;
547
548         priv->html_window = gdk_window_new (priv->view_window, &attributes, attributes_mask);
549         gdk_window_set_user_data (priv->html_window, msg_view);
550
551         if (priv->html_scroll)
552                 gtk_widget_set_parent_window (priv->html_scroll, priv->html_window);
553
554         widget->style = gtk_style_attach (widget->style, widget->window);
555         gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
556         gtk_style_set_background (widget->style, priv->headers_window, GTK_STATE_NORMAL);
557         gtk_style_set_background (widget->style, priv->html_window, GTK_STATE_NORMAL);
558
559         gtk_paint_flat_box(widget->style, priv->headers_window, GTK_STATE_NORMAL,
560                            GTK_SHADOW_NONE, 
561                            NULL, widget, "msgviewheaders",
562                            0,0,-1,-1);
563         gtk_paint_flat_box(widget->style, priv->html_window, GTK_STATE_NORMAL,
564                            GTK_SHADOW_NONE, 
565                            NULL, widget, "msgviewcontents",
566                            0,0,-1,-1);
567
568         gdk_window_show (priv->view_window);
569         gdk_window_show (priv->headers_window);
570         gdk_window_show (priv->html_window);
571
572 }
573
574 static void
575 unrealize (GtkWidget *widget)
576 {
577         ModestMsgView *msg_view = MODEST_MSG_VIEW (widget);
578         ModestMsgViewPrivate *priv = MODEST_MSG_VIEW_GET_PRIVATE (msg_view);
579
580         gdk_window_set_user_data (priv->view_window, NULL);
581         gdk_window_destroy (priv->view_window);
582         priv->view_window = NULL;
583
584         gdk_window_set_user_data (priv->headers_window, NULL);
585         gdk_window_destroy (priv->headers_window);
586         priv->headers_window = NULL;
587
588         gdk_window_set_user_data (priv->html_window, NULL);
589         gdk_window_destroy (priv->html_window);
590         priv->html_window = NULL;
591
592         if (GTK_WIDGET_CLASS (parent_class)->unrealize)
593                 ( * GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
594
595 }
596
597 static gint
598 expose (GtkWidget *widget, 
599         GdkEventExpose *event)
600 {
601         ModestMsgView *msg_view;
602         ModestMsgViewPrivate *priv;
603
604         if (GTK_WIDGET_DRAWABLE (widget)) {
605                 msg_view = MODEST_MSG_VIEW (widget);
606                 priv = MODEST_MSG_VIEW_GET_PRIVATE (msg_view);
607                 if (event->window == widget->window) {
608                         gtk_paint_shadow (widget->style, widget->window,
609                                           GTK_STATE_NORMAL, priv->shadow_type,
610                                           &event->area, widget, "msgview",
611                                           0,0,-1,-1);
612                 } else if (event->window == priv->headers_window) {
613                         gtk_paint_flat_box(widget->style, priv->headers_window, GTK_STATE_NORMAL,
614                                            GTK_SHADOW_NONE, 
615                                            &event->area, widget, "msgviewheaders",
616                                            0,0,-1,-1);
617                 } else if (event->window == priv->html_window) {
618                         gtk_paint_flat_box(widget->style, priv->html_window, GTK_STATE_NORMAL,
619                                            GTK_SHADOW_NONE, 
620                                            &event->area, widget, "msgviewcontents",
621                                            0,0,-1,-1);
622                 }
623                 if (priv->headers_box)
624                         gtk_container_propagate_expose (GTK_CONTAINER (msg_view), priv->headers_box, event);
625                 if (priv->html_scroll)
626                         gtk_container_propagate_expose (GTK_CONTAINER (msg_view), priv->html_scroll, event);
627                 (* GTK_WIDGET_CLASS (parent_class)->expose_event) (widget, event);
628         }
629
630         return FALSE;
631 }
632
633 static void 
634 forall (GtkContainer *container, 
635         gboolean include_internals,
636         GtkCallback callback,
637         gpointer userdata)
638 {
639         ModestMsgViewPrivate *priv = MODEST_MSG_VIEW_GET_PRIVATE (container);
640         g_return_if_fail (callback != NULL);
641
642         if (priv->headers_box)
643                 (*callback) (priv->headers_box, userdata);
644         if (priv->html_scroll)
645                 (*callback) (priv->html_scroll, userdata);
646 }
647
648 static void
649 container_remove (GtkContainer *container,
650                   GtkWidget *widget)
651 {
652         gboolean was_visible = FALSE;
653         ModestMsgViewPrivate *priv = MODEST_MSG_VIEW_GET_PRIVATE (container);
654         was_visible = GTK_WIDGET_VISIBLE (widget);
655         if (widget == priv->headers_box) {
656                 gtk_widget_unparent (priv->headers_box);
657                 priv->headers_box = NULL;
658         } else if (widget == priv->html_scroll) {
659                 gtk_widget_unparent (priv->html_scroll);
660                 priv->html_scroll = NULL;
661         } else {
662                 return;
663         }
664         if (was_visible)
665                 gtk_widget_queue_resize (GTK_WIDGET(container));
666
667 }
668
669 static void
670 size_request (GtkWidget *widget,
671               GtkRequisition *req)
672 {
673         ModestMsgViewPrivate *priv = MODEST_MSG_VIEW_GET_PRIVATE (widget);
674         GtkRequisition child_req;
675
676         req->width = 0;
677         req->height = 0;
678
679         gtk_widget_size_request (priv->headers_box, &child_req);
680         req->width = child_req.width;
681         req->height += child_req.height;
682         gtk_widget_size_request (priv->html_scroll, &child_req);
683         req->width = MAX (child_req.width, req->width);
684         req->height += child_req.height;
685
686 }
687
688 static void
689 size_allocate (GtkWidget *widget,
690                GtkAllocation *allocation)
691 {
692         ModestMsgView *msg_view = MODEST_MSG_VIEW (widget);
693         ModestMsgViewPrivate *priv = MODEST_MSG_VIEW_GET_PRIVATE (msg_view);
694         gboolean hadj_value_changed, vadj_value_changed;
695         GtkAllocation headers_allocation, html_allocation, view_allocation;
696         GtkAdjustment *html_vadj;
697
698         if (GTK_WIDGET_MAPPED (widget) &&
699             priv->shadow_type != GTK_SHADOW_NONE && 
700             (allocation->width != widget->allocation.width ||
701              allocation->height != widget->allocation.height))
702                 gdk_window_invalidate_rect (widget->window, NULL, FALSE);
703
704         widget->allocation = *allocation;
705         set_hadjustment_values (msg_view, &hadj_value_changed);
706         set_vadjustment_values (msg_view, &vadj_value_changed);
707
708         get_view_allocation (msg_view, &view_allocation);
709
710         headers_allocation.x = 0;
711         headers_allocation.y = 0;
712         headers_allocation.width = view_allocation.width;
713         if (priv->headers_box)
714                 headers_allocation.height = GTK_WIDGET (priv->headers_box)->requisition.height;
715         else
716                 headers_allocation.height = 0;
717
718         html_vadj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (priv->html_scroll));
719
720         html_allocation.x = 0;
721         html_allocation.y = headers_allocation.height;
722         html_allocation.width = view_allocation.width;
723         html_allocation.height = MAX ((gint) html_vadj->upper, (gint)(priv->vadj->upper - headers_allocation.height));
724
725         if (GTK_WIDGET_REALIZED (widget)) {
726                 gdk_window_move_resize (widget->window,
727                                         allocation->x,
728                                         allocation->y,
729                                         allocation->width,
730                                         allocation->height);
731
732                 gdk_window_move_resize (priv->view_window,
733                                         view_allocation.x,
734                                         view_allocation.y,
735                                         view_allocation.width,
736                                         view_allocation.height);
737                 gdk_window_move_resize (priv->headers_window,
738                                         0,
739                                         (gint) (- priv->vadj->value),
740                                         allocation->width,
741                                         headers_allocation.height);
742                 gdk_window_move_resize (priv->html_window,
743                                         (gint) (- priv->hadj->value),
744                                         (gint) (html_allocation.y - priv->vadj->value),
745                                         (gint) priv->hadj->upper,
746                                         html_allocation.height);
747         }
748
749         if (priv->headers_box && GTK_WIDGET_VISIBLE (priv->headers_box)) {
750                 gtk_widget_size_allocate (priv->headers_box, &headers_allocation);
751         }
752         if (priv->html_scroll && GTK_WIDGET_VISIBLE (priv->html_scroll)) {
753                 html_allocation.x = 0;
754                 html_allocation.y = 0;
755                 html_allocation.width = (gint) priv->hadj->upper;
756                 html_allocation.height = (gint) priv->vadj->upper - headers_allocation.height;
757                 gtk_widget_size_allocate (priv->html_scroll, &html_allocation);
758         }
759         gtk_adjustment_changed (priv->hadj);
760         gtk_adjustment_changed (priv->vadj);
761
762         if (hadj_value_changed)
763                 gtk_adjustment_value_changed (priv->hadj);
764         if (vadj_value_changed)
765                 gtk_adjustment_value_changed (priv->vadj);
766
767 }
768
769 static void 
770 adjustment_value_changed (GtkAdjustment *adj, gpointer data)
771 {
772         ModestMsgView *msg_view = NULL;
773         ModestMsgViewPrivate *priv = NULL;
774
775         g_return_if_fail (GTK_IS_ADJUSTMENT (adj));
776         g_return_if_fail (MODEST_IS_MSG_VIEW (data));
777
778         msg_view = MODEST_MSG_VIEW (data);
779         priv = MODEST_MSG_VIEW_GET_PRIVATE (msg_view);
780
781         if (GTK_WIDGET_REALIZED (msg_view)) {
782                 GtkAdjustment *hadj = modest_msg_view_get_hadjustment (msg_view);
783                 GtkAdjustment *vadj = modest_msg_view_get_vadjustment (msg_view);
784                 gint headers_offset = 0;
785
786                 gtk_widget_queue_resize (priv->html_scroll);
787
788                 if (priv->headers_box && GTK_WIDGET_VISIBLE (priv->headers_box)) {
789                         gint old_x, old_y;
790                         gint new_x, new_y;
791                         gdk_window_get_position (priv->headers_window, &old_x, &old_y);
792                         new_x = 0;
793                         new_y = -vadj->value;
794
795                         headers_offset = GTK_WIDGET(priv->headers_box)->allocation.height;
796
797                         if (new_x != old_x || new_y != old_y) {
798                                 gdk_window_move (priv->headers_window, new_x, new_y);
799                                 gdk_window_process_updates (priv->headers_window, TRUE);
800                         }
801                 }
802                 
803                 if (priv->html_scroll && GTK_WIDGET_VISIBLE (priv->html_scroll)) {
804                         gint old_x, old_y;
805                         gint new_x, new_y;
806                         gdk_window_get_position (priv->html_window, &old_x, &old_y);
807                         new_x = -hadj->value;
808                         new_y = headers_offset - vadj->value;
809
810                         if (new_x != old_x || new_y != old_y) {
811                                 gdk_window_move (priv->html_window, new_x, new_y);
812                                 gdk_window_process_updates (priv->html_window, TRUE);
813                         }
814                 }
815                 
816         }
817 }
818
819 static void
820 html_adjustment_changed (GtkAdjustment *adj,
821                          gpointer userdata)
822 {
823         ModestMsgView *msg_view = MODEST_MSG_VIEW (userdata);
824         ModestMsgViewPrivate *priv = MODEST_MSG_VIEW_GET_PRIVATE (msg_view);
825         GtkAdjustment *html_vadj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW(priv->html_scroll));
826         gboolean vadj_changed;
827         gint new_height;
828
829         priv->html_scroll->requisition.height = html_vadj->upper;
830         priv->html_scroll->allocation.height = html_vadj->upper;
831
832         set_vadjustment_values (msg_view, &vadj_changed);
833
834         new_height = MAX ((gint) html_vadj->upper, (gint) (priv->vadj->upper - priv->headers_box->allocation.height));
835         
836         gtk_adjustment_changed (priv->vadj);
837         if (GTK_WIDGET_DRAWABLE (priv->html_scroll)) {
838                 gdk_window_resize (priv->html_window, (gint) priv->hadj->upper, (gint) new_height);
839                 gdk_window_process_updates (priv->view_window, TRUE);
840                 gtk_container_resize_children (GTK_CONTAINER (msg_view));
841         }
842         
843 }
844
845 static void
846 modest_msg_view_init (ModestMsgView *obj)
847 {
848         ModestMsgViewPrivate *priv;
849         GtkAdjustment *html_vadj;
850
851         GTK_WIDGET_UNSET_FLAGS (obj, GTK_NO_WINDOW);
852         gtk_widget_set_redraw_on_allocate (GTK_WIDGET (obj), TRUE);
853         gtk_container_set_reallocate_redraws (GTK_CONTAINER (obj), TRUE);
854         gtk_container_set_resize_mode (GTK_CONTAINER (obj), GTK_RESIZE_QUEUE);
855         
856         priv = MODEST_MSG_VIEW_GET_PRIVATE(obj);
857
858         priv->current_zoom = 1.0;
859         priv->priority_flags = 0;
860
861         priv->hadj = NULL;
862         priv->vadj = NULL;
863         priv->shadow_type = GTK_SHADOW_IN;
864         priv->view_window = NULL;
865         priv->headers_window = NULL;
866         priv->html_window = NULL;
867
868
869         gtk_widget_push_composite_child ();
870         priv->html_scroll = gtk_scrolled_window_new (NULL, NULL);
871         gtk_widget_set_composite_name (priv->html_scroll, "contents");
872         gtk_widget_pop_composite_child ();
873         gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->html_scroll), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
874
875         priv->msg                     = NULL;
876
877         priv->gtkhtml                 = gtk_html_new();
878         gtk_html_set_editable        (GTK_HTML(priv->gtkhtml), FALSE);
879         gtk_html_allow_selection     (GTK_HTML(priv->gtkhtml), TRUE);
880         gtk_html_set_caret_mode      (GTK_HTML(priv->gtkhtml), FALSE);
881         gtk_html_set_blocking        (GTK_HTML(priv->gtkhtml), FALSE);
882         gtk_html_set_images_blocking (GTK_HTML(priv->gtkhtml), FALSE);
883
884         priv->mail_header_view        = GTK_WIDGET(modest_mail_header_view_new (TRUE));
885         gtk_widget_set_no_show_all (priv->mail_header_view, TRUE);
886
887         priv->attachments_view        = GTK_WIDGET(modest_attachments_view_new (NULL));
888
889         priv->sig1 = g_signal_connect (G_OBJECT(priv->gtkhtml), "link_clicked",
890                                        G_CALLBACK(on_link_clicked), obj);
891         priv->sig2 = g_signal_connect (G_OBJECT(priv->gtkhtml), "url_requested",
892                                        G_CALLBACK(on_url_requested), obj);
893         priv->sig3 = g_signal_connect (G_OBJECT(priv->gtkhtml), "on_url",
894                                        G_CALLBACK(on_link_hover), obj);
895
896         g_signal_connect (G_OBJECT (priv->mail_header_view), "recpt-activated", 
897                           G_CALLBACK (on_recpt_activated), obj);
898
899         g_signal_connect (G_OBJECT (priv->attachments_view), "activate",
900                           G_CALLBACK (on_attachment_activated), obj);
901
902         html_vadj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW(priv->html_scroll));
903
904         g_signal_connect (G_OBJECT (html_vadj), "changed",
905                           G_CALLBACK (html_adjustment_changed), obj);
906
907 }
908         
909
910 static void
911 modest_msg_view_finalize (GObject *obj)
912 {       
913         ModestMsgViewPrivate *priv;
914         priv = MODEST_MSG_VIEW_GET_PRIVATE (obj);
915
916         if (priv->msg) {
917                 g_object_unref (G_OBJECT(priv->msg));
918                 priv->msg = NULL;
919         }
920         
921         /* we cannot disconnect sigs, because priv->gtkhtml is
922          * already dead */
923         
924         disconnect_vadjustment (MODEST_MSG_VIEW(obj));
925         disconnect_hadjustment (MODEST_MSG_VIEW(obj));
926
927         priv->gtkhtml = NULL;
928         priv->attachments_view = NULL;
929
930         G_OBJECT_CLASS(parent_class)->finalize (obj);           
931 }
932
933 static void
934 modest_msg_view_destroy (GtkObject *obj)
935 {       
936         disconnect_vadjustment (MODEST_MSG_VIEW(obj));
937         disconnect_hadjustment (MODEST_MSG_VIEW(obj));
938
939         GTK_OBJECT_CLASS(parent_class)->destroy (obj);          
940 }
941
942 GtkAdjustment *
943 modest_msg_view_get_vadjustment (ModestMsgView *msg_view)
944 {
945         ModestMsgViewPrivate *priv = MODEST_MSG_VIEW_GET_PRIVATE (msg_view);
946
947         if (!priv->vadj)
948                 modest_msg_view_set_vadjustment (msg_view, NULL);
949
950         return priv->vadj;
951         
952 }
953
954 GtkAdjustment *
955 modest_msg_view_get_hadjustment (ModestMsgView *msg_view)
956 {
957         ModestMsgViewPrivate *priv = MODEST_MSG_VIEW_GET_PRIVATE (msg_view);
958
959         if (!priv->hadj)
960                 modest_msg_view_set_hadjustment (msg_view, NULL);
961
962         return priv->hadj;
963         
964 }
965
966 void
967 modest_msg_view_set_hadjustment (ModestMsgView *msg_view, GtkAdjustment *hadj)
968 {
969         ModestMsgViewPrivate *priv = MODEST_MSG_VIEW_GET_PRIVATE (msg_view);
970         gboolean value_changed;
971
972         if (hadj && hadj == priv->hadj)
973                 return;
974
975         if (!hadj)
976                 hadj = GTK_ADJUSTMENT (gtk_adjustment_new (0.0,0.0,0.0,0.0,0.0,0.0));
977         disconnect_hadjustment (msg_view);
978         g_object_ref (G_OBJECT (hadj));
979         gtk_object_sink (GTK_OBJECT (hadj));
980         priv->hadj = hadj;
981         set_hadjustment_values (msg_view, &value_changed);
982
983         g_signal_connect (hadj, "value_changed", G_CALLBACK (adjustment_value_changed),
984                           msg_view);
985
986         gtk_adjustment_changed (hadj);
987         if (value_changed)
988                 gtk_adjustment_value_changed (hadj);
989         else
990                 adjustment_value_changed (hadj, msg_view);
991
992         g_object_notify (G_OBJECT (msg_view), "hadjustment");
993 }
994
995 void
996 modest_msg_view_set_vadjustment (ModestMsgView *msg_view, GtkAdjustment *vadj)
997 {
998         ModestMsgViewPrivate *priv = MODEST_MSG_VIEW_GET_PRIVATE (msg_view);
999         gboolean value_changed;
1000
1001         if (vadj && vadj == priv->vadj)
1002                 return;
1003
1004         if (!vadj)
1005                 vadj = (GtkAdjustment *) gtk_adjustment_new (0.0,0.0,0.0,0.0,0.0,0.0);
1006         disconnect_vadjustment (msg_view);
1007         g_object_ref (G_OBJECT (vadj));
1008         gtk_object_sink (GTK_OBJECT (vadj));
1009         priv->vadj = vadj;
1010         set_vadjustment_values (msg_view, &value_changed);
1011
1012         g_signal_connect (vadj, "value_changed", G_CALLBACK (adjustment_value_changed),
1013                           msg_view);
1014
1015         gtk_adjustment_changed (vadj);
1016         if (value_changed)
1017                 gtk_adjustment_value_changed (vadj);
1018         else
1019                 adjustment_value_changed (vadj, msg_view);
1020
1021         g_object_notify (G_OBJECT (msg_view), "vadjustment");
1022 }
1023
1024 /** 
1025  * modest_msg_view_set_shadow_type:
1026  * @msg_view: a #ModestMsgView.
1027  * @shadow_type: new shadow type.
1028  *
1029  * Sets a shadow type of the message view.
1030  **/ 
1031 void
1032 modest_msg_view_set_shadow_type (ModestMsgView *msg_view,
1033                                  GtkShadowType shadow_type)
1034 {
1035         ModestMsgViewPrivate *priv;
1036         g_return_if_fail (MODEST_IS_MSG_VIEW (msg_view));
1037
1038         priv = MODEST_MSG_VIEW_GET_PRIVATE (msg_view);
1039         
1040         if (priv->shadow_type != shadow_type) {
1041                 priv->shadow_type = shadow_type;
1042                 
1043                 if (GTK_WIDGET_VISIBLE (msg_view)) {
1044                         gtk_widget_size_allocate (GTK_WIDGET (msg_view), &(GTK_WIDGET (msg_view)->allocation));
1045                         gtk_widget_queue_draw (GTK_WIDGET (msg_view));
1046                 }
1047                 g_object_notify (G_OBJECT (msg_view), "shadow-type");
1048         }
1049 }
1050
1051 /**
1052  * modest_msg_view_get_shadow_type:
1053  * @msg_view: a #ModestMsgView
1054  *
1055  * Gets the current shadow type of the #ModestMsgView.
1056  *
1057  * Return value: the shadow type 
1058  **/
1059 GtkShadowType
1060 modest_msg_view_get_shadow_type (ModestMsgView *msg_view)
1061 {
1062         ModestMsgViewPrivate *priv;
1063         g_return_val_if_fail (MODEST_IS_MSG_VIEW (msg_view), GTK_SHADOW_NONE);
1064         priv = MODEST_MSG_VIEW_GET_PRIVATE (msg_view);
1065         
1066         return priv->shadow_type;
1067 }
1068
1069 GtkWidget*
1070 modest_msg_view_new (TnyMsg *msg)
1071 {
1072         GObject *obj;
1073         ModestMsgView* self;
1074         ModestMsgViewPrivate *priv;
1075         GtkWidget *separator;
1076         
1077         obj  = G_OBJECT(g_object_new(MODEST_TYPE_MSG_VIEW, NULL));
1078         self = MODEST_MSG_VIEW(obj);
1079         priv = MODEST_MSG_VIEW_GET_PRIVATE (self);
1080
1081         gtk_widget_push_composite_child ();
1082         priv->headers_box = gtk_vbox_new (0, FALSE);
1083         gtk_widget_set_composite_name (priv->headers_box, "headers");
1084         gtk_widget_pop_composite_child ();
1085
1086         if (priv->mail_header_view)
1087                 gtk_box_pack_start (GTK_BOX(priv->headers_box), priv->mail_header_view, FALSE, FALSE, 0);
1088         
1089         if (priv->attachments_view) {
1090                 priv->attachments_box = (GtkWidget *) modest_mail_header_view_add_custom_header (MODEST_MAIL_HEADER_VIEW (priv->mail_header_view),
1091                                                                                                  _("Attachments:"), priv->attachments_view,
1092                                                                                                  FALSE, FALSE);
1093                 gtk_widget_hide_all (priv->attachments_box);
1094 /*              gtk_widget_set_no_show_all (priv->attachments_box, TRUE); */
1095         }
1096
1097         separator = gtk_hseparator_new ();
1098         gtk_box_pack_start (GTK_BOX(priv->headers_box), separator, FALSE, FALSE, 0);
1099
1100         gtk_widget_set_parent (priv->headers_box, GTK_WIDGET (self));
1101
1102         if (priv->gtkhtml) {
1103                 gtk_container_add (GTK_CONTAINER (priv->html_scroll), priv->gtkhtml);
1104                 gtk_widget_set_parent (priv->html_scroll, GTK_WIDGET(self));
1105 #ifdef MAEMO_CHANGES
1106                 gtk_widget_tap_and_hold_setup (GTK_WIDGET (priv->gtkhtml), NULL, NULL, 0);
1107                 g_signal_connect (G_OBJECT (priv->gtkhtml), "tap-and-hold", G_CALLBACK (on_tap_and_hold), obj);
1108 #endif
1109         }
1110
1111         modest_msg_view_set_message (self, msg);
1112
1113         return GTK_WIDGET(self);
1114 }
1115
1116 #ifdef MAEMO_CHANGES
1117 static void
1118 on_tap_and_hold (GtkWidget *widget,
1119                  gpointer data)
1120 {
1121         ModestMsgView *msg_view = (ModestMsgView *) data;
1122         ModestMsgViewPrivate *priv = MODEST_MSG_VIEW_GET_PRIVATE (msg_view);
1123
1124         g_signal_emit (G_OBJECT (msg_view), signals[LINK_CONTEXTUAL_SIGNAL],
1125                        0, priv->last_url);
1126 }
1127 #endif
1128
1129 static void
1130 on_recpt_activated (ModestMailHeaderView *header_view, 
1131                     const gchar *address,
1132                     ModestMsgView * view)
1133 {
1134         g_signal_emit (G_OBJECT (view), signals[RECPT_ACTIVATED_SIGNAL], 0, address);
1135 }
1136
1137 static void
1138 on_attachment_activated (ModestAttachmentsView * att_view, TnyMimePart *mime_part, gpointer msg_view)
1139 {
1140
1141         g_signal_emit (G_OBJECT(msg_view), signals[ATTACHMENT_CLICKED_SIGNAL],
1142                        0, mime_part);
1143 }
1144
1145 static gboolean
1146 on_link_clicked (GtkWidget *widget, const gchar *uri, ModestMsgView *msg_view)
1147 {
1148         g_return_val_if_fail (msg_view, FALSE);
1149         
1150         g_signal_emit (G_OBJECT(msg_view), signals[LINK_CLICKED_SIGNAL],
1151                        0, uri);
1152
1153         return FALSE;
1154 }
1155
1156
1157 static gboolean
1158 on_link_hover (GtkWidget *widget, const gchar *uri, ModestMsgView *msg_view)
1159 {
1160         ModestMsgViewPrivate *priv = MODEST_MSG_VIEW_GET_PRIVATE (msg_view);
1161
1162         g_free (priv->last_url);
1163         priv->last_url = g_strdup (uri);
1164
1165         g_signal_emit (G_OBJECT(msg_view), signals[LINK_HOVER_SIGNAL],
1166                        0, uri);
1167
1168         return FALSE;
1169 }
1170
1171
1172
1173 static TnyMimePart *
1174 find_cid_image (TnyMsg *msg, const gchar *cid)
1175 {
1176         TnyMimePart *part = NULL;
1177         TnyList *parts;
1178         TnyIterator *iter;
1179         
1180         g_return_val_if_fail (msg, NULL);
1181         g_return_val_if_fail (cid, NULL);
1182         
1183         parts  = TNY_LIST (tny_simple_list_new());
1184
1185         tny_mime_part_get_parts (TNY_MIME_PART (msg), parts); 
1186         iter   = tny_list_create_iterator (parts);
1187         
1188         while (!tny_iterator_is_done(iter)) {
1189                 const gchar *part_cid;
1190                 part = TNY_MIME_PART(tny_iterator_get_current(iter));
1191                 part_cid = tny_mime_part_get_content_id (part);
1192
1193                 if (part_cid && strcmp (cid, part_cid) == 0)
1194                         break;
1195
1196                 g_object_unref (G_OBJECT(part));
1197         
1198                 part = NULL;
1199                 tny_iterator_next (iter);
1200         }
1201         
1202         g_object_unref (G_OBJECT(iter));        
1203         g_object_unref (G_OBJECT(parts));
1204         
1205         return part;
1206 }
1207
1208
1209 static gboolean
1210 on_url_requested (GtkWidget *widget, const gchar *uri,
1211                   GtkHTMLStream *stream, ModestMsgView *msg_view)
1212 {
1213         ModestMsgViewPrivate *priv;
1214         priv = MODEST_MSG_VIEW_GET_PRIVATE (msg_view);
1215         
1216         if (g_str_has_prefix (uri, "cid:")) {
1217                 /* +4 ==> skip "cid:" */
1218                 TnyMimePart *part = find_cid_image (priv->msg, uri + 4);
1219                 if (!part) {
1220                         g_printerr ("modest: '%s' not found\n", uri + 4);
1221                         gtk_html_stream_close (stream, GTK_HTML_STREAM_ERROR);
1222                 } else {
1223                         TnyStream *tny_stream =
1224                                 TNY_STREAM(modest_tny_stream_gtkhtml_new(stream));
1225                         tny_mime_part_decode_to_stream ((TnyMimePart*)part,
1226                                                                   tny_stream);
1227                         gtk_html_stream_close (stream, GTK_HTML_STREAM_OK);
1228         
1229                         g_object_unref (G_OBJECT(tny_stream));
1230                         g_object_unref (G_OBJECT(part));
1231                 }
1232         }
1233
1234         return TRUE;
1235 }
1236
1237 static gboolean
1238 set_html_message (ModestMsgView *self, TnyMimePart *tny_body, TnyMsg *msg)
1239 {
1240         GtkHTMLStream *gtkhtml_stream;
1241         TnyStream *tny_stream;  
1242         ModestMsgViewPrivate *priv;
1243         
1244         g_return_val_if_fail (self, FALSE);
1245         g_return_val_if_fail (tny_body, FALSE);
1246         
1247         priv = MODEST_MSG_VIEW_GET_PRIVATE(self);
1248
1249         gtkhtml_stream = gtk_html_begin(GTK_HTML(priv->gtkhtml));
1250
1251         tny_stream     = TNY_STREAM(modest_tny_stream_gtkhtml_new (gtkhtml_stream));
1252         tny_stream_reset (tny_stream);
1253
1254         tny_mime_part_decode_to_stream ((TnyMimePart*)tny_body, tny_stream);
1255         g_object_unref (G_OBJECT(tny_stream));
1256         
1257         gtk_html_stream_destroy (gtkhtml_stream);
1258         
1259         return TRUE;
1260 }
1261
1262
1263 /* FIXME: this is a hack --> we use the tny_text_buffer_stream to
1264  * get the message text, then write to gtkhtml 'by hand' */
1265 static gboolean
1266 set_text_message (ModestMsgView *self, TnyMimePart *tny_body, TnyMsg *msg)
1267 {
1268         GtkTextBuffer *buf;
1269         GtkTextIter begin, end;
1270         TnyStream* txt_stream, *tny_stream;
1271         GtkHTMLStream *gtkhtml_stream;
1272         gchar *txt;
1273         ModestMsgViewPrivate *priv;
1274                 
1275         g_return_val_if_fail (self, FALSE);
1276         g_return_val_if_fail (tny_body, FALSE);
1277
1278         priv           = MODEST_MSG_VIEW_GET_PRIVATE(self);
1279         
1280         buf            = gtk_text_buffer_new (NULL);
1281         txt_stream     = TNY_STREAM(tny_gtk_text_buffer_stream_new (buf));
1282                 
1283         tny_stream_reset (txt_stream);
1284
1285         gtkhtml_stream = gtk_html_begin(GTK_HTML(priv->gtkhtml)); 
1286         tny_stream =  TNY_STREAM(modest_tny_stream_gtkhtml_new (gtkhtml_stream));
1287         
1288         // FIXME: tinymail
1289         tny_mime_part_decode_to_stream ((TnyMimePart*)tny_body, txt_stream);
1290         tny_stream_reset (txt_stream);          
1291         
1292         gtk_text_buffer_get_bounds (buf, &begin, &end);
1293         txt = gtk_text_buffer_get_text (buf, &begin, &end, FALSE);
1294         if (txt) {
1295                 gchar *html = modest_text_utils_convert_to_html (txt);
1296                 tny_stream_write (tny_stream, html, strlen(html));
1297                 tny_stream_reset (tny_stream);
1298                 g_free (txt);
1299                 g_free (html);
1300         }
1301         
1302         g_object_unref (G_OBJECT(tny_stream));
1303         g_object_unref (G_OBJECT(txt_stream));
1304         g_object_unref (G_OBJECT(buf));
1305         
1306         gtk_html_stream_destroy (gtkhtml_stream);
1307         
1308         return TRUE;
1309 }
1310
1311
1312 static gboolean
1313 set_empty_message (ModestMsgView *self)
1314 {
1315         ModestMsgViewPrivate *priv;
1316         
1317         g_return_val_if_fail (self, FALSE);
1318         priv           = MODEST_MSG_VIEW_GET_PRIVATE(self);
1319
1320         gtk_html_load_from_string (GTK_HTML(priv->gtkhtml),
1321                                    "", 1);
1322         
1323         return TRUE;
1324 }
1325
1326
1327 void
1328 modest_msg_view_set_message (ModestMsgView *self, TnyMsg *msg)
1329 {
1330         TnyMimePart *body;
1331         ModestMsgViewPrivate *priv;
1332         TnyHeader *header;
1333         
1334         g_return_if_fail (self);
1335         
1336         priv = MODEST_MSG_VIEW_GET_PRIVATE(self);
1337         gtk_widget_set_no_show_all (priv->mail_header_view, FALSE);
1338
1339         if (msg != priv->msg) {
1340                 if (priv->msg)
1341                         g_object_unref (G_OBJECT(priv->msg));
1342                 if (msg)
1343                         g_object_ref   (G_OBJECT(msg));
1344                 priv->msg = msg;
1345         }
1346         
1347         if (!msg) {
1348                 tny_header_view_clear (TNY_HEADER_VIEW (priv->mail_header_view));
1349                 modest_attachments_view_set_message (MODEST_ATTACHMENTS_VIEW (priv->attachments_view), NULL);
1350                 gtk_widget_hide_all (priv->mail_header_view);
1351                 gtk_widget_hide_all (priv->attachments_box);
1352                 gtk_widget_set_no_show_all (priv->mail_header_view, TRUE);
1353                 set_empty_message (self);
1354                 gtk_widget_queue_resize (GTK_WIDGET(self));
1355                 gtk_widget_queue_draw (GTK_WIDGET(self));
1356                 return;
1357         }
1358
1359         header = tny_msg_get_header (msg);
1360         tny_header_view_set_header (TNY_HEADER_VIEW (priv->mail_header_view), header);
1361         g_object_unref (header);
1362
1363         modest_attachments_view_set_message (MODEST_ATTACHMENTS_VIEW(priv->attachments_view),
1364                                              msg);
1365         
1366         body = modest_tny_msg_find_body_part (msg,TRUE);
1367         if (body) {
1368                 GList *att_children;
1369                 if (tny_mime_part_content_type_is (body, "text/html"))
1370                         set_html_message (self, body, msg);
1371                 else
1372                         set_text_message (self, body, msg);
1373
1374                 att_children = gtk_container_get_children (GTK_CONTAINER (priv->attachments_view));
1375                 if (att_children != NULL) {
1376                         gtk_widget_show_all (priv->attachments_box);
1377                         g_list_free (att_children);
1378                 } else {
1379                         gtk_widget_hide_all (priv->attachments_box);
1380                 }
1381                         
1382         } else 
1383                 set_empty_message (self);
1384
1385         gtk_widget_show (priv->gtkhtml);
1386         gtk_widget_set_no_show_all (priv->attachments_box, TRUE);
1387         gtk_widget_show_all (priv->mail_header_view);
1388         gtk_widget_set_no_show_all (priv->attachments_box, FALSE);
1389 /*      gtk_widget_show_all (priv->attachments_box); */
1390 /*      gtk_widget_show_all (priv->attachments_box); */
1391         gtk_widget_set_no_show_all (priv->mail_header_view, TRUE);
1392         gtk_widget_queue_resize (GTK_WIDGET(self));
1393         gtk_widget_queue_draw (GTK_WIDGET(self));
1394
1395         if (priv->hadj != NULL)
1396                 priv->hadj->value = 0.0;
1397         if (priv->vadj != NULL)
1398                 priv->vadj->value = 0.0;
1399
1400 }
1401
1402
1403 TnyMsg*
1404 modest_msg_view_get_message (ModestMsgView *self)
1405 {
1406         g_return_val_if_fail (self, NULL);
1407         
1408         return MODEST_MSG_VIEW_GET_PRIVATE(self)->msg;
1409 }
1410
1411 gboolean 
1412 modest_msg_view_search (ModestMsgView *self, const gchar *search)
1413 {
1414         ModestMsgViewPrivate *priv;
1415         gboolean result;
1416         GtkAdjustment *vadj, *tmp_vadj;
1417         gdouble y_offset;
1418
1419         g_return_val_if_fail (MODEST_IS_MSG_VIEW (self), FALSE);
1420
1421         priv = MODEST_MSG_VIEW_GET_PRIVATE (self);
1422         vadj = gtk_layout_get_vadjustment (GTK_LAYOUT (priv->gtkhtml));
1423         g_object_ref (vadj);
1424         tmp_vadj = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, vadj->lower, vadj->upper, vadj->step_increment, 32.0, 32.0));
1425         gtk_layout_set_vadjustment (GTK_LAYOUT (priv->gtkhtml), tmp_vadj);
1426         result = gtk_html_engine_search (GTK_HTML (priv->gtkhtml),
1427                                          search,
1428                                          FALSE, TRUE, TRUE);
1429         y_offset = tmp_vadj->value;
1430         g_message ("VALUE %f", y_offset);
1431         gtk_layout_set_vadjustment (GTK_LAYOUT (priv->gtkhtml), vadj);
1432         g_object_unref (vadj);
1433
1434         return result;
1435 }
1436
1437 gboolean
1438 modest_msg_view_search_next (ModestMsgView *self)
1439 {
1440         ModestMsgViewPrivate *priv;
1441         gboolean result;
1442
1443         g_return_val_if_fail (MODEST_IS_MSG_VIEW (self), FALSE);
1444
1445         priv = MODEST_MSG_VIEW_GET_PRIVATE (self);
1446         result = gtk_html_engine_search_next (GTK_HTML (priv->gtkhtml));
1447
1448         {
1449                 GtkAdjustment *adj;
1450
1451                 adj = gtk_container_get_focus_vadjustment (GTK_CONTAINER (priv->gtkhtml));
1452                 g_message ("ADJ value %f", adj->value);
1453         }
1454
1455         return result;
1456 }
1457
1458 void
1459 modest_msg_view_set_zoom (ModestMsgView *self, gdouble zoom)
1460 {
1461         ModestMsgViewPrivate *priv;
1462
1463         g_return_if_fail (MODEST_IS_MSG_VIEW (self));
1464
1465         priv = MODEST_MSG_VIEW_GET_PRIVATE (self);
1466         priv->current_zoom = zoom;
1467         gtk_html_set_magnification (GTK_HTML(priv->gtkhtml), zoom);
1468
1469         gtk_widget_queue_resize (priv->gtkhtml);
1470 }
1471
1472 gdouble
1473 modest_msg_view_get_zoom (ModestMsgView *self)
1474 {
1475         ModestMsgViewPrivate *priv;
1476
1477         g_return_val_if_fail (MODEST_IS_MSG_VIEW (self), 1.0);
1478
1479         priv = MODEST_MSG_VIEW_GET_PRIVATE (self);
1480
1481         return priv->current_zoom;
1482 }
1483
1484 TnyHeaderFlags
1485 modest_msg_view_get_priority (ModestMsgView *self)
1486 {
1487         ModestMsgViewPrivate *priv;
1488
1489         g_return_val_if_fail (MODEST_IS_MSG_VIEW (self), 0);
1490
1491         priv = MODEST_MSG_VIEW_GET_PRIVATE (self);
1492
1493         return priv->priority_flags;
1494 }
1495
1496 void
1497 modest_msg_view_set_priority (ModestMsgView *self, TnyHeaderFlags flags)
1498 {
1499         ModestMsgViewPrivate *priv;
1500
1501         g_return_if_fail (MODEST_IS_MSG_VIEW (self));
1502
1503         priv = MODEST_MSG_VIEW_GET_PRIVATE (self);
1504
1505         priv->priority_flags = flags & (TNY_HEADER_FLAG_HIGH_PRIORITY);
1506
1507         modest_mail_header_view_set_priority (MODEST_MAIL_HEADER_VIEW (priv->mail_header_view), flags);
1508 }