Handle properly zoom in hildon 2.2
[modest] / src / hildon2 / modest-msg-view-window.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 #include <glib/gi18n.h>
30 #include <string.h>
31 #include <tny-account-store.h>
32 #include <tny-simple-list.h>
33 #include <tny-msg.h>
34 #include <tny-mime-part.h>
35 #include <tny-vfs-stream.h>
36 #include <tny-error.h>
37 #include "modest-marshal.h"
38 #include "modest-platform.h"
39 #include <modest-utils.h>
40 #include <modest-maemo-utils.h>
41 #include <modest-tny-msg.h>
42 #include <modest-msg-view-window.h>
43 #include <modest-main-window-ui.h>
44 #include "modest-msg-view-window-ui-dimming.h"
45 #include <modest-widget-memory.h>
46 #include <modest-progress-object.h>
47 #include <modest-runtime.h>
48 #include <modest-window-priv.h>
49 #include <modest-tny-folder.h>
50 #include <modest-text-utils.h>
51 #include <modest-account-mgr-helpers.h>
52 #include <hildon/hildon-pannable-area.h>
53 #include <hildon/hildon-picker-dialog.h>
54 #include <hildon/hildon-app-menu.h>
55 #include "modest-defs.h"
56 #include "modest-hildon-includes.h"
57 #include "modest-ui-dimming-manager.h"
58 #include <gdk/gdkkeysyms.h>
59 #include <modest-tny-account.h>
60 #include <modest-mime-part-view.h>
61 #include <modest-isearch-view.h>
62 #include <modest-tny-mime-part.h>
63 #include <modest-address-book.h>
64 #include <math.h>
65 #include <errno.h>
66 #include <glib/gstdio.h>
67 #include <modest-debug.h>
68 #include <modest-header-window.h>
69 #include <gdk/gdk.h>
70 #include <gdk/gdkx.h>
71 #include <X11/Xatom.h>
72 #include <X11/XKBlib.h>
73 #include <X11/Xdmcp.h>
74
75 #define MYDOCS_ENV "MYDOCSDIR"
76 #define DOCS_FOLDER ".documents"
77
78 typedef struct _ModestMsgViewWindowPrivate ModestMsgViewWindowPrivate;
79 struct _ModestMsgViewWindowPrivate {
80
81         GtkWidget   *msg_view;
82         GtkWidget   *main_scroll;
83         GtkWidget   *find_toolbar;
84         gchar       *last_search;
85
86         /* Progress observers */
87         GSList           *progress_widgets;
88
89         /* Tollbar items */
90         GtkWidget   *prev_toolitem;
91         GtkWidget   *next_toolitem;
92         gboolean    progress_hint;
93
94         /* Optimized view enabled */
95         gboolean optimized_view;
96
97         /* Whether this was created via the *_new_for_search_result() function. */
98         gboolean is_search_result;
99
100         /* Whether the message is in outbox */
101         gboolean is_outbox;
102
103         /* A reference to the @model of the header view 
104          * to allow selecting previous/next messages,
105          * if the message is currently selected in the header view.
106          */
107         const gchar *header_folder_id;
108         GtkTreeModel *header_model;
109         GtkTreeRowReference *row_reference;
110         GtkTreeRowReference *next_row_reference;
111
112         gulong clipboard_change_handler;
113         gulong queue_change_handler;
114         gulong account_removed_handler;
115         gulong row_changed_handler;
116         gulong row_deleted_handler;
117         gulong row_inserted_handler;
118         gulong rows_reordered_handler;
119
120         guint purge_timeout;
121         GtkWidget *remove_attachment_banner;
122
123         gchar *msg_uid;
124
125         GSList *sighandlers;
126 };
127
128 static void  modest_msg_view_window_class_init   (ModestMsgViewWindowClass *klass);
129 static void  modest_msg_view_window_init         (ModestMsgViewWindow *obj);
130 static void  modest_header_view_observer_init(
131                 ModestHeaderViewObserverIface *iface_class);
132 static void  modest_msg_view_window_finalize     (GObject *obj);
133 static void  modest_msg_view_window_toggle_find_toolbar (GtkToggleAction *obj,
134                                                          gpointer data);
135 static void  modest_msg_view_window_find_toolbar_close (GtkWidget *widget,
136                                                         ModestMsgViewWindow *obj);
137 static void  modest_msg_view_window_find_toolbar_search (GtkWidget *widget,
138                                                         ModestMsgViewWindow *obj);
139
140 static void modest_msg_view_window_disconnect_signals (ModestWindow *self);
141
142 static gdouble modest_msg_view_window_get_zoom (ModestWindow *window);
143 static void modest_msg_view_window_set_zoom (ModestWindow *window,
144                                              gdouble zoom);
145 static gboolean modest_msg_view_window_zoom_minus (ModestWindow *window);
146 static gboolean modest_msg_view_window_zoom_plus (ModestWindow *window);
147 static gboolean modest_msg_view_window_key_event (GtkWidget *window,
148                                                   GdkEventKey *event,
149                                                   gpointer userdata);
150 static void modest_msg_view_window_update_priority (ModestMsgViewWindow *window);
151
152 static void modest_msg_view_window_show_toolbar   (ModestWindow *window,
153                                                    gboolean show_toolbar);
154
155 static void modest_msg_view_window_clipboard_owner_change (GtkClipboard *clipboard,
156                                                            GdkEvent *event,
157                                                            ModestMsgViewWindow *window);
158
159 static void modest_msg_view_window_on_row_changed (GtkTreeModel *header_model,
160                                                    GtkTreePath *arg1,
161                                                    GtkTreeIter *arg2,
162                                                    ModestMsgViewWindow *window);
163
164 static void modest_msg_view_window_on_row_deleted (GtkTreeModel *header_model,
165                                                    GtkTreePath *arg1,
166                                                    ModestMsgViewWindow *window);
167
168 static void modest_msg_view_window_on_row_inserted (GtkTreeModel *header_model,
169                                                     GtkTreePath *tree_path,
170                                                     GtkTreeIter *tree_iter,
171                                                     ModestMsgViewWindow *window);
172
173 static void modest_msg_view_window_on_row_reordered (GtkTreeModel *header_model,
174                                                      GtkTreePath *arg1,
175                                                      GtkTreeIter *arg2,
176                                                      gpointer arg3,
177                                                      ModestMsgViewWindow *window);
178
179 static void modest_msg_view_window_update_model_replaced (ModestHeaderViewObserver *window,
180                                                           GtkTreeModel *model,
181                                                           const gchar *tny_folder_id);
182
183 static void on_queue_changed    (ModestMailOperationQueue *queue,
184                                  ModestMailOperation *mail_op,
185                                  ModestMailOperationQueueNotification type,
186                                  ModestMsgViewWindow *self);
187
188 static void on_account_removed  (TnyAccountStore *account_store, 
189                                  TnyAccount *account,
190                                  gpointer user_data);
191
192 static void on_move_focus (GtkWidget *widget,
193                            GtkDirectionType direction,
194                            gpointer userdata);
195
196 static void view_msg_cb         (ModestMailOperation *mail_op, 
197                                  TnyHeader *header, 
198                                  gboolean canceled,
199                                  TnyMsg *msg, 
200                                  GError *error,
201                                  gpointer user_data);
202
203 static void set_progress_hint    (ModestMsgViewWindow *self, 
204                                   gboolean enabled);
205
206 static void update_window_title (ModestMsgViewWindow *window);
207
208 static gboolean set_toolbar_transfer_mode     (ModestMsgViewWindow *self); 
209 static void init_window (ModestMsgViewWindow *obj);
210
211 static gboolean msg_is_visible (TnyHeader *header, gboolean check_outbox);
212
213 static void check_dimming_rules_after_change (ModestMsgViewWindow *window);
214
215 static gboolean on_fetch_image (ModestMsgView *msgview,
216                                 const gchar *uri,
217                                 TnyStream *stream,
218                                 ModestMsgViewWindow *window);
219
220 static gboolean modest_msg_view_window_scroll_child (ModestMsgViewWindow *self,
221                                                      GtkScrollType scroll_type,
222                                                      gboolean horizontal,
223                                                      gpointer userdata);
224 static gboolean message_reader (ModestMsgViewWindow *window,
225                                 ModestMsgViewWindowPrivate *priv,
226                                 TnyHeader *header,
227                                 GtkTreeRowReference *row_reference);
228
229 static void setup_menu (ModestMsgViewWindow *self);
230 static gboolean _modest_msg_view_window_map_event (GtkWidget *widget,
231                                                    GdkEvent *event,
232                                                    gpointer userdata);
233 static void _make_zoom_buttons_grabeable (GtkWidget* widget);
234
235
236 /* list my signals */
237 enum {
238         MSG_CHANGED_SIGNAL,
239         SCROLL_CHILD_SIGNAL,
240         LAST_SIGNAL
241 };
242
243 static const GtkToggleActionEntry msg_view_toggle_action_entries [] = {
244         { "FindInMessage",    MODEST_TOOLBAR_ICON_FIND,    N_("qgn_toolb_gene_find"), NULL, NULL, G_CALLBACK (modest_msg_view_window_toggle_find_toolbar), FALSE },
245 };
246
247
248 #define MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(o)      (G_TYPE_INSTANCE_GET_PRIVATE((o), \
249                                                     MODEST_TYPE_MSG_VIEW_WINDOW, \
250                                                     ModestMsgViewWindowPrivate))
251 /* globals */
252 static GtkWindowClass *parent_class = NULL;
253
254 /* uncomment the following if you have defined any signals */
255 static guint signals[LAST_SIGNAL] = {0};
256
257 GType
258 modest_msg_view_window_get_type (void)
259 {
260         static GType my_type = 0;
261         if (!my_type) {
262                 static const GTypeInfo my_info = {
263                         sizeof(ModestMsgViewWindowClass),
264                         NULL,           /* base init */
265                         NULL,           /* base finalize */
266                         (GClassInitFunc) modest_msg_view_window_class_init,
267                         NULL,           /* class finalize */
268                         NULL,           /* class data */
269                         sizeof(ModestMsgViewWindow),
270                         1,              /* n_preallocs */
271                         (GInstanceInitFunc) modest_msg_view_window_init,
272                         NULL
273                 };
274                 my_type = g_type_register_static (MODEST_TYPE_HILDON2_WINDOW,
275                                                   "ModestMsgViewWindow",
276                                                   &my_info, 0);
277
278                 static const GInterfaceInfo modest_header_view_observer_info = 
279                 {
280                         (GInterfaceInitFunc) modest_header_view_observer_init,
281                         NULL,         /* interface_finalize */
282                         NULL          /* interface_data */
283                 };
284
285                 g_type_add_interface_static (my_type,
286                                 MODEST_TYPE_HEADER_VIEW_OBSERVER,
287                                 &modest_header_view_observer_info);
288         }
289         return my_type;
290 }
291
292 static void
293 save_state (ModestWindow *self)
294 {
295         modest_widget_memory_save (modest_runtime_get_conf (),
296                                    G_OBJECT(self), 
297                                    MODEST_CONF_MSG_VIEW_WINDOW_KEY);
298 }
299
300 static 
301 gboolean modest_msg_view_window_scroll_child (ModestMsgViewWindow *self,
302                                               GtkScrollType scroll_type,
303                                               gboolean horizontal,
304                                               gpointer userdata)
305 {
306         ModestMsgViewWindowPrivate *priv;
307         gboolean return_value;
308
309         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
310         g_signal_emit_by_name (priv->main_scroll, "scroll-child", scroll_type, horizontal, &return_value);
311         return return_value;
312 }
313
314 static void
315 add_scroll_binding (GtkBindingSet *binding_set,
316                     guint keyval,
317                     GtkScrollType scroll)
318 {
319         guint keypad_keyval = keyval - GDK_Left + GDK_KP_Left;
320         
321         gtk_binding_entry_add_signal (binding_set, keyval, 0,
322                                       "scroll_child", 2,
323                                       GTK_TYPE_SCROLL_TYPE, scroll,
324                                       G_TYPE_BOOLEAN, FALSE);
325         gtk_binding_entry_add_signal (binding_set, keypad_keyval, 0,
326                                       "scroll_child", 2,
327                                       GTK_TYPE_SCROLL_TYPE, scroll,
328                                       G_TYPE_BOOLEAN, FALSE);
329 }
330
331 static void
332 modest_msg_view_window_class_init (ModestMsgViewWindowClass *klass)
333 {
334         GObjectClass *gobject_class;
335         HildonWindowClass *hildon_window_class;
336         ModestWindowClass *modest_window_class;
337         GtkBindingSet *binding_set;
338
339         gobject_class = (GObjectClass*) klass;
340         hildon_window_class = (HildonWindowClass *) klass;
341         modest_window_class = (ModestWindowClass *) klass;
342
343         parent_class            = g_type_class_peek_parent (klass);
344         gobject_class->finalize = modest_msg_view_window_finalize;
345
346         modest_window_class->set_zoom_func = modest_msg_view_window_set_zoom;
347         modest_window_class->get_zoom_func = modest_msg_view_window_get_zoom;
348         modest_window_class->zoom_plus_func = modest_msg_view_window_zoom_plus;
349         modest_window_class->zoom_minus_func = modest_msg_view_window_zoom_minus;
350         modest_window_class->show_toolbar_func = modest_msg_view_window_show_toolbar;
351         modest_window_class->disconnect_signals_func = modest_msg_view_window_disconnect_signals;
352
353         modest_window_class->save_state_func = save_state;
354
355         klass->scroll_child = modest_msg_view_window_scroll_child;
356
357         signals[MSG_CHANGED_SIGNAL] =
358                 g_signal_new ("msg-changed",
359                               G_TYPE_FROM_CLASS (gobject_class),
360                               G_SIGNAL_RUN_FIRST,
361                               G_STRUCT_OFFSET (ModestMsgViewWindowClass, msg_changed),
362                               NULL, NULL,
363                               modest_marshal_VOID__POINTER_POINTER,
364                               G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER);
365
366         signals[SCROLL_CHILD_SIGNAL] =
367                 g_signal_new ("scroll-child",
368                               G_TYPE_FROM_CLASS (gobject_class),
369                               G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
370                               G_STRUCT_OFFSET (ModestMsgViewWindowClass, scroll_child),
371                               NULL, NULL,
372                               modest_marshal_BOOLEAN__ENUM_BOOLEAN,
373                               G_TYPE_BOOLEAN, 2, GTK_TYPE_SCROLL_TYPE, G_TYPE_BOOLEAN);
374
375         binding_set = gtk_binding_set_by_class (klass);
376         add_scroll_binding (binding_set, GDK_Up, GTK_SCROLL_STEP_UP);
377         add_scroll_binding (binding_set, GDK_Down, GTK_SCROLL_STEP_DOWN);
378         add_scroll_binding (binding_set, GDK_Page_Up, GTK_SCROLL_PAGE_UP);
379         add_scroll_binding (binding_set, GDK_Page_Down, GTK_SCROLL_PAGE_DOWN);
380         add_scroll_binding (binding_set, GDK_Home, GTK_SCROLL_START);
381         add_scroll_binding (binding_set, GDK_End, GTK_SCROLL_END);
382
383         g_type_class_add_private (gobject_class, sizeof(ModestMsgViewWindowPrivate));
384
385 }
386
387 static void modest_header_view_observer_init(
388                 ModestHeaderViewObserverIface *iface_class)
389 {
390         iface_class->update_func = modest_msg_view_window_update_model_replaced;
391 }
392
393 static void
394 modest_msg_view_window_init (ModestMsgViewWindow *obj)
395 {
396         ModestMsgViewWindowPrivate *priv;
397         ModestWindowPrivate *parent_priv = NULL;
398         GtkActionGroup *action_group = NULL;
399         GError *error = NULL;
400         GdkPixbuf *window_icon;
401
402         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(obj);
403         parent_priv = MODEST_WINDOW_GET_PRIVATE(obj);
404         parent_priv->ui_manager = gtk_ui_manager_new();
405
406         action_group = gtk_action_group_new ("ModestMsgViewWindowActions");
407         gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE);
408
409         /* Add common actions */
410         gtk_action_group_add_actions (action_group,
411                                       modest_action_entries,
412                                       G_N_ELEMENTS (modest_action_entries),
413                                       obj);
414         gtk_action_group_add_toggle_actions (action_group,
415                                              msg_view_toggle_action_entries,
416                                              G_N_ELEMENTS (msg_view_toggle_action_entries),
417                                              obj);
418
419         gtk_ui_manager_insert_action_group (parent_priv->ui_manager, action_group, 0);
420         g_object_unref (action_group);
421
422         /* Load the UI definition */
423         gtk_ui_manager_add_ui_from_file (parent_priv->ui_manager, MODEST_UIDIR "modest-msg-view-window-ui.xml",
424                                          &error);
425         if (error) {
426                 g_printerr ("modest: could not merge modest-msg-view-window-ui.xml: %s\n", error->message);
427                 g_error_free (error);
428                 error = NULL;
429         }
430         /* ****** */
431
432         /* Add accelerators */
433         gtk_window_add_accel_group (GTK_WINDOW (obj), 
434                                     gtk_ui_manager_get_accel_group (parent_priv->ui_manager));
435         
436         priv->is_search_result = FALSE;
437         priv->is_outbox = FALSE;
438
439         priv->msg_view      = NULL;
440         priv->header_model  = NULL;
441         priv->header_folder_id  = NULL;
442         priv->clipboard_change_handler = 0;
443         priv->queue_change_handler = 0;
444         priv->account_removed_handler = 0;
445         priv->row_changed_handler = 0;
446         priv->row_deleted_handler = 0;
447         priv->row_inserted_handler = 0;
448         priv->rows_reordered_handler = 0;
449         priv->progress_hint = FALSE;
450
451         priv->optimized_view  = FALSE;
452         priv->purge_timeout = 0;
453         priv->remove_attachment_banner = NULL;
454         priv->msg_uid = NULL;
455         
456         priv->sighandlers = NULL;
457         
458         /* Init window */
459         init_window (MODEST_MSG_VIEW_WINDOW(obj));
460         
461         /* Set window icon */
462         window_icon = modest_platform_get_icon (MODEST_APP_MSG_VIEW_ICON, MODEST_ICON_SIZE_BIG); 
463         if (window_icon) {
464                 gtk_window_set_icon (GTK_WINDOW (obj), window_icon);
465                 g_object_unref (window_icon);
466         }       
467         
468         hildon_program_add_window (hildon_program_get_instance(),
469                                    HILDON_WINDOW(obj));
470
471 }
472
473
474 static gboolean
475 set_toolbar_transfer_mode (ModestMsgViewWindow *self)
476 {
477         ModestMsgViewWindowPrivate *priv = NULL;
478         
479         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), FALSE);
480
481         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
482
483         set_progress_hint (self, TRUE);
484         
485         return FALSE;
486 }
487
488 static void 
489 set_progress_hint (ModestMsgViewWindow *self, 
490                    gboolean enabled)
491 {
492         ModestWindowPrivate *parent_priv;
493         ModestMsgViewWindowPrivate *priv;
494
495         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self));
496
497         parent_priv = MODEST_WINDOW_GET_PRIVATE(self);
498         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
499                         
500         /* Sets current progress hint */
501         priv->progress_hint = enabled;
502
503         if (GTK_WIDGET_VISIBLE (self)) {
504                 hildon_gtk_window_set_progress_indicator (GTK_WINDOW (self), enabled?1:0);
505         }
506
507 }
508
509
510 static void
511 init_window (ModestMsgViewWindow *obj)
512 {
513         GtkWidget *main_vbox;
514         ModestMsgViewWindowPrivate *priv;
515
516         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(obj);
517
518         priv->msg_view = GTK_WIDGET (tny_platform_factory_new_msg_view (modest_tny_platform_factory_get_instance ()));
519         modest_msg_view_set_shadow_type (MODEST_MSG_VIEW (priv->msg_view), GTK_SHADOW_NONE);
520         main_vbox = gtk_vbox_new  (FALSE, 6);
521 #ifdef MODEST_TOOLKIT_HILDON2
522         priv->main_scroll = hildon_pannable_area_new ();
523         gtk_container_add (GTK_CONTAINER (priv->main_scroll), priv->msg_view);
524 #else
525 #ifdef MODEST_USE_MOZEMBED
526         priv->main_scroll = priv->msg_view;
527         gtk_widget_set_size_request (priv->msg_view, -1, 1600);
528 #else
529         priv->main_scroll = gtk_scrolled_window_new (NULL, NULL);
530         gtk_container_add (GTK_CONTAINER (priv->main_scroll), priv->msg_view);
531 #endif
532         gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->main_scroll), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
533         gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (priv->main_scroll), GTK_SHADOW_NONE);
534         modest_maemo_set_thumbable_scrollbar (GTK_SCROLLED_WINDOW(priv->main_scroll), TRUE);
535
536 #endif
537         gtk_box_pack_start (GTK_BOX(main_vbox), priv->main_scroll, TRUE, TRUE, 0);
538         gtk_container_add   (GTK_CONTAINER(obj), main_vbox);
539
540         priv->find_toolbar = hildon_find_toolbar_new (NULL);
541         hildon_window_add_toolbar (HILDON_WINDOW (obj), GTK_TOOLBAR (priv->find_toolbar));
542         gtk_widget_set_no_show_all (priv->find_toolbar, TRUE);
543
544         /* NULL-ize fields if the window is destroyed */
545         g_signal_connect (priv->msg_view, "destroy", G_CALLBACK (gtk_widget_destroyed), &(priv->msg_view));
546
547         gtk_widget_show_all (GTK_WIDGET(main_vbox));
548 }
549
550 static void
551 modest_msg_view_window_disconnect_signals (ModestWindow *self)
552 {
553         ModestMsgViewWindowPrivate *priv;
554         GtkWidget *header_view = NULL;
555         GtkWindow *parent_window = NULL;
556         
557         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
558
559         if (gtk_clipboard_get (GDK_SELECTION_PRIMARY) &&
560             g_signal_handler_is_connected (gtk_clipboard_get (GDK_SELECTION_PRIMARY),
561                                            priv->clipboard_change_handler)) 
562                 g_signal_handler_disconnect (gtk_clipboard_get (GDK_SELECTION_PRIMARY), 
563                                              priv->clipboard_change_handler);
564
565         if (g_signal_handler_is_connected (G_OBJECT (modest_runtime_get_mail_operation_queue ()), 
566                                            priv->queue_change_handler))
567                 g_signal_handler_disconnect (G_OBJECT (modest_runtime_get_mail_operation_queue ()), 
568                                              priv->queue_change_handler);
569
570         if (g_signal_handler_is_connected (G_OBJECT (modest_runtime_get_account_store ()), 
571                                            priv->account_removed_handler))
572                 g_signal_handler_disconnect (G_OBJECT (modest_runtime_get_account_store ()), 
573                                              priv->account_removed_handler);
574
575         if (priv->header_model) {
576                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
577                                                   priv->row_changed_handler))
578                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
579                                                     priv->row_changed_handler);
580                 
581                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
582                                                   priv->row_deleted_handler))
583                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
584                                              priv->row_deleted_handler);
585                 
586                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
587                                                   priv->row_inserted_handler))
588                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
589                                                     priv->row_inserted_handler);
590                 
591                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
592                                                   priv->rows_reordered_handler))
593                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
594                                                     priv->rows_reordered_handler);
595         }
596
597         modest_signal_mgr_disconnect_all_and_destroy (priv->sighandlers);
598         priv->sighandlers = NULL;
599
600         parent_window = gtk_window_get_transient_for (GTK_WINDOW (self));
601         if (parent_window && MODEST_IS_HEADER_WINDOW (parent_window)) {
602                 header_view = GTK_WIDGET (modest_header_window_get_header_view (MODEST_HEADER_WINDOW (parent_window)));
603                 if (header_view) {
604                         modest_header_view_remove_observer(MODEST_HEADER_VIEW (header_view),
605                                                            MODEST_HEADER_VIEW_OBSERVER(self));
606                 }
607         }
608 }       
609
610 static void
611 modest_msg_view_window_finalize (GObject *obj)
612 {
613         ModestMsgViewWindowPrivate *priv;
614
615         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (obj);
616
617         /* Sanity check: shouldn't be needed, the window mgr should
618            call this function before */
619         modest_msg_view_window_disconnect_signals (MODEST_WINDOW (obj));
620
621         if (priv->header_model != NULL) {
622                 g_object_unref (priv->header_model);
623                 priv->header_model = NULL;
624         }
625
626         if (priv->remove_attachment_banner) {
627                 gtk_widget_destroy (priv->remove_attachment_banner);
628                 g_object_unref (priv->remove_attachment_banner);
629                 priv->remove_attachment_banner = NULL;
630         }
631
632         if (priv->purge_timeout > 0) {
633                 g_source_remove (priv->purge_timeout);
634                 priv->purge_timeout = 0;
635         }
636
637         if (priv->row_reference) {
638                 gtk_tree_row_reference_free (priv->row_reference);
639                 priv->row_reference = NULL;
640         }
641
642         if (priv->next_row_reference) {
643                 gtk_tree_row_reference_free (priv->next_row_reference);
644                 priv->next_row_reference = NULL;
645         }
646
647         if (priv->msg_uid) {
648                 g_free (priv->msg_uid);
649                 priv->msg_uid = NULL;
650         }
651
652         G_OBJECT_CLASS(parent_class)->finalize (obj);
653 }
654
655 static gboolean
656 select_next_valid_row (GtkTreeModel *model,
657                        GtkTreeRowReference **row_reference,
658                        gboolean cycle,
659                        gboolean is_outbox)
660 {
661         GtkTreeIter tmp_iter;
662         GtkTreePath *path;
663         GtkTreePath *next = NULL;
664         gboolean retval = FALSE, finished;
665
666         g_return_val_if_fail (gtk_tree_row_reference_valid (*row_reference), FALSE);
667
668         path = gtk_tree_row_reference_get_path (*row_reference);
669         gtk_tree_model_get_iter (model, &tmp_iter, path);
670         gtk_tree_row_reference_free (*row_reference);
671         *row_reference = NULL;
672
673         finished = FALSE;
674         do {
675                 TnyHeader *header = NULL;
676
677                 if (gtk_tree_model_iter_next (model, &tmp_iter)) {
678                         gtk_tree_model_get (model, &tmp_iter, 
679                                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, 
680                                             &header, -1);
681
682                         if (header) {
683                                 if (msg_is_visible (header, is_outbox)) {
684                                         next = gtk_tree_model_get_path (model, &tmp_iter);
685                                         *row_reference = gtk_tree_row_reference_new (model, next);
686                                         gtk_tree_path_free (next);
687                                         retval = TRUE;
688                                         finished = TRUE;
689                                 }
690                                 g_object_unref (header);
691                                 header = NULL;
692                         }
693                 } else if (cycle && gtk_tree_model_get_iter_first (model, &tmp_iter)) {
694                         next = gtk_tree_model_get_path (model, &tmp_iter);
695                         
696                         /* Ensure that we are not selecting the same */
697                         if (gtk_tree_path_compare (path, next) != 0) {
698                                 gtk_tree_model_get (model, &tmp_iter, 
699                                                     TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, 
700                                                     &header, -1);                               
701                                 if (header) {
702                                         if (msg_is_visible (header, is_outbox)) {
703                                                 *row_reference = gtk_tree_row_reference_new (model, next);
704                                                 retval = TRUE;
705                                                 finished = TRUE;
706                                         }
707                                         g_object_unref (header);
708                                         header = NULL;
709                                 }
710                         } else {
711                                 /* If we ended up in the same message
712                                    then there is no valid next
713                                    message */
714                                 finished = TRUE;
715                         }
716                         gtk_tree_path_free (next);
717                 } else {
718                         /* If there are no more messages and we don't
719                            want to start again in the first one then
720                            there is no valid next message */
721                         finished = TRUE;
722                 }
723         } while (!finished);
724
725         /* Free */
726         gtk_tree_path_free (path);
727
728         return retval;
729 }
730
731 /* TODO: This should be in _init(), with the parameters as properties. */
732 static void
733 modest_msg_view_window_construct (ModestMsgViewWindow *self, 
734                                   const gchar *modest_account_name,
735                                   const gchar *mailbox,
736                                   const gchar *msg_uid)
737 {
738         GObject *obj = NULL;
739         ModestMsgViewWindowPrivate *priv = NULL;
740         ModestWindowPrivate *parent_priv = NULL;
741         ModestDimmingRulesGroup *toolbar_rules_group = NULL;
742         ModestDimmingRulesGroup *clipboard_rules_group = NULL;
743
744         obj = G_OBJECT (self);
745         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(obj);
746         parent_priv = MODEST_WINDOW_GET_PRIVATE(obj);
747
748         priv->msg_uid = g_strdup (msg_uid);
749
750         /* Menubar */
751         parent_priv->menubar = NULL;
752
753         toolbar_rules_group = modest_dimming_rules_group_new (MODEST_DIMMING_RULES_TOOLBAR, TRUE);
754         clipboard_rules_group = modest_dimming_rules_group_new (MODEST_DIMMING_RULES_CLIPBOARD, FALSE);
755
756         setup_menu (self);
757         /* Add common dimming rules */
758         modest_dimming_rules_group_add_rules (toolbar_rules_group, 
759                                               modest_msg_view_toolbar_dimming_entries,
760                                               G_N_ELEMENTS (modest_msg_view_toolbar_dimming_entries),
761                                               MODEST_WINDOW (self));
762         modest_dimming_rules_group_add_rules (clipboard_rules_group, 
763                                               modest_msg_view_clipboard_dimming_entries,
764                                               G_N_ELEMENTS (modest_msg_view_clipboard_dimming_entries),
765                                               MODEST_WINDOW (self));
766
767         /* Insert dimming rules group for this window */
768         modest_ui_dimming_manager_insert_rules_group (parent_priv->ui_dimming_manager, toolbar_rules_group);
769         modest_ui_dimming_manager_insert_rules_group (parent_priv->ui_dimming_manager, clipboard_rules_group);
770         g_object_unref (toolbar_rules_group);
771         g_object_unref (clipboard_rules_group);
772
773         /* g_signal_connect (G_OBJECT(obj), "delete-event", G_CALLBACK(on_delete_event), obj); */
774
775         priv->clipboard_change_handler = g_signal_connect (G_OBJECT (gtk_clipboard_get (GDK_SELECTION_PRIMARY)), "owner-change", G_CALLBACK (modest_msg_view_window_clipboard_owner_change), obj);
776         g_signal_connect (G_OBJECT(priv->msg_view), "activate_link",
777                           G_CALLBACK (modest_ui_actions_on_msg_link_clicked), obj);
778         g_signal_connect (G_OBJECT(priv->msg_view), "link_hover",
779                           G_CALLBACK (modest_ui_actions_on_msg_link_hover), obj);
780         g_signal_connect (G_OBJECT(priv->msg_view), "attachment_clicked",
781                           G_CALLBACK (modest_ui_actions_on_msg_attachment_clicked), obj);
782         g_signal_connect (G_OBJECT(priv->msg_view), "recpt_activated",
783                           G_CALLBACK (modest_ui_actions_on_msg_recpt_activated), obj);
784         g_signal_connect (G_OBJECT(priv->msg_view), "show_details",
785                           G_CALLBACK (modest_ui_actions_on_details), obj);
786         g_signal_connect (G_OBJECT(priv->msg_view), "link_contextual",
787                           G_CALLBACK (modest_ui_actions_on_msg_link_contextual), obj);
788         g_signal_connect (G_OBJECT (priv->msg_view), "fetch_image",
789                           G_CALLBACK (on_fetch_image), obj);
790
791         g_signal_connect (G_OBJECT (obj), "key-release-event",
792                           G_CALLBACK (modest_msg_view_window_key_event),
793                           NULL);
794
795         g_signal_connect (G_OBJECT (obj), "key-press-event",
796                           G_CALLBACK (modest_msg_view_window_key_event),
797                           NULL);
798
799         g_signal_connect (G_OBJECT (obj), "move-focus",
800                           G_CALLBACK (on_move_focus), obj);
801
802         g_signal_connect (G_OBJECT (obj), "map-event",
803                           G_CALLBACK (_modest_msg_view_window_map_event),
804                           G_OBJECT (obj));
805
806         /* Mail Operation Queue */
807         priv->queue_change_handler = g_signal_connect (G_OBJECT (modest_runtime_get_mail_operation_queue ()),
808                                                        "queue-changed",
809                                                        G_CALLBACK (on_queue_changed),
810                                                        obj);
811
812         /* Account manager */
813         priv->account_removed_handler = g_signal_connect (G_OBJECT (modest_runtime_get_account_store ()),
814                                                           "account_removed",
815                                                           G_CALLBACK(on_account_removed),
816                                                           obj);
817
818         modest_window_set_active_account (MODEST_WINDOW(obj), modest_account_name);
819         modest_window_set_active_mailbox (MODEST_WINDOW(obj), mailbox);
820
821         g_signal_connect (G_OBJECT (priv->find_toolbar), "close", G_CALLBACK (modest_msg_view_window_find_toolbar_close), obj);
822         g_signal_connect (G_OBJECT (priv->find_toolbar), "search", G_CALLBACK (modest_msg_view_window_find_toolbar_search), obj);
823         priv->last_search = NULL;
824
825         modest_msg_view_window_show_toolbar (MODEST_WINDOW (obj), TRUE);
826
827         /* Init the clipboard actions dim status */
828         modest_msg_view_grab_focus(MODEST_MSG_VIEW (priv->msg_view));
829
830         update_window_title (MODEST_MSG_VIEW_WINDOW (obj));
831
832
833 }
834
835 /* FIXME: parameter checks */
836 ModestWindow *
837 modest_msg_view_window_new_with_header_model (TnyMsg *msg, 
838                                               const gchar *modest_account_name,
839                                               const gchar *mailbox,
840                                               const gchar *msg_uid,
841                                               GtkTreeModel *model, 
842                                               GtkTreeRowReference *row_reference)
843 {
844         ModestMsgViewWindow *window = NULL;
845         ModestMsgViewWindowPrivate *priv = NULL;
846         TnyFolder *header_folder = NULL;
847         ModestHeaderView *header_view = NULL;
848         ModestWindow *main_window = NULL;
849         ModestWindowMgr *mgr = NULL;
850
851         MODEST_DEBUG_BLOCK (
852                modest_tny_mime_part_to_string (TNY_MIME_PART (msg), 0);
853         );
854
855         mgr = modest_runtime_get_window_mgr ();
856         window = MODEST_MSG_VIEW_WINDOW (modest_window_mgr_get_msg_view_window (mgr));
857         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), NULL);
858
859         modest_msg_view_window_construct (window, modest_account_name, mailbox, msg_uid);
860
861         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
862
863         /* Remember the message list's TreeModel so we can detect changes 
864          * and change the list selection when necessary: */
865
866         main_window = modest_window_mgr_get_main_window(mgr, FALSE); /* don't create */
867         if (main_window) {
868                 header_view = MODEST_HEADER_VIEW(modest_main_window_get_child_widget(
869                                                          MODEST_MAIN_WINDOW(main_window),
870                                                          MODEST_MAIN_WINDOW_WIDGET_TYPE_HEADER_VIEW));
871         }
872         
873         if (header_view != NULL){
874                 header_folder = modest_header_view_get_folder(header_view);
875                 /* This could happen if the header folder was
876                    unseleted before opening this msg window (for
877                    example if the user selects an account in the
878                    folder view of the main window */
879                 if (header_folder) {
880                         priv->is_outbox = (modest_tny_folder_guess_folder_type (header_folder) == TNY_FOLDER_TYPE_OUTBOX);
881                         priv->header_folder_id = tny_folder_get_id(header_folder);
882                         g_assert(priv->header_folder_id != NULL);
883                         g_object_unref(header_folder);
884                 }
885         }
886
887         /* Setup row references and connect signals */
888         priv->header_model = g_object_ref (model);
889
890         if (row_reference) {
891                 priv->row_reference = gtk_tree_row_reference_copy (row_reference);
892                 priv->next_row_reference = gtk_tree_row_reference_copy (row_reference);
893                 select_next_valid_row (model, &(priv->next_row_reference), TRUE, priv->is_outbox);
894         } else {
895                 priv->row_reference = NULL;
896                 priv->next_row_reference = NULL;
897         }
898
899         /* Connect signals */
900         priv->row_changed_handler = 
901                 g_signal_connect (GTK_TREE_MODEL(model), "row-changed",
902                                   G_CALLBACK(modest_msg_view_window_on_row_changed),
903                                   window);
904         priv->row_deleted_handler = 
905                 g_signal_connect (GTK_TREE_MODEL(model), "row-deleted",
906                                   G_CALLBACK(modest_msg_view_window_on_row_deleted),
907                                   window);
908         priv->row_inserted_handler = 
909                 g_signal_connect (GTK_TREE_MODEL(model), "row-inserted",
910                                   G_CALLBACK(modest_msg_view_window_on_row_inserted),
911                                   window);
912         priv->rows_reordered_handler = 
913                 g_signal_connect(GTK_TREE_MODEL(model), "rows-reordered",
914                                  G_CALLBACK(modest_msg_view_window_on_row_reordered),
915                                  window);
916
917         if (header_view != NULL){
918                 modest_header_view_add_observer(header_view,
919                                 MODEST_HEADER_VIEW_OBSERVER(window));
920         }
921
922         tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
923         update_window_title (MODEST_MSG_VIEW_WINDOW (window));
924
925         /* gtk_widget_show_all (GTK_WIDGET (window)); */
926         modest_msg_view_window_update_priority (window);
927         /* Check dimming rules */
928         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (window));
929         modest_ui_actions_check_menu_dimming_rules (MODEST_WINDOW (window));
930         modest_window_check_dimming_rules_group (MODEST_WINDOW (window), MODEST_DIMMING_RULES_CLIPBOARD);
931
932         return MODEST_WINDOW(window);
933 }
934
935 ModestWindow *
936 modest_msg_view_window_new_from_header_view (ModestHeaderView *header_view, 
937                                               const gchar *modest_account_name,
938                                              const gchar *mailbox,
939                                               const gchar *msg_uid,
940                                               GtkTreeRowReference *row_reference)
941 {
942         ModestMsgViewWindow *window = NULL;
943         ModestMsgViewWindowPrivate *priv = NULL;
944         TnyFolder *header_folder = NULL;
945         ModestWindowMgr *mgr = NULL;
946         GtkTreePath *path;
947         GtkTreeIter iter;
948
949         mgr = modest_runtime_get_window_mgr ();
950         window = MODEST_MSG_VIEW_WINDOW (modest_window_mgr_get_msg_view_window (mgr));
951         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), NULL);
952
953         modest_msg_view_window_construct (window, modest_account_name, mailbox, msg_uid);
954
955         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
956
957         /* Remember the message list's TreeModel so we can detect changes 
958          * and change the list selection when necessary: */
959
960         if (header_view != NULL){
961                 header_folder = modest_header_view_get_folder(header_view);
962                 /* This could happen if the header folder was
963                    unseleted before opening this msg window (for
964                    example if the user selects an account in the
965                    folder view of the main window */
966                 if (header_folder) {
967                         priv->is_outbox = (modest_tny_folder_guess_folder_type (header_folder) == TNY_FOLDER_TYPE_OUTBOX);
968                         priv->header_folder_id = tny_folder_get_id(header_folder);
969                         g_assert(priv->header_folder_id != NULL);
970                         g_object_unref(header_folder);
971                 }
972         }
973
974         /* Setup row references and connect signals */
975         priv->header_model = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
976         g_object_ref (priv->header_model);
977
978         if (row_reference) {
979                 priv->row_reference = gtk_tree_row_reference_copy (row_reference);
980                 priv->next_row_reference = gtk_tree_row_reference_copy (row_reference);
981                 select_next_valid_row (priv->header_model, &(priv->next_row_reference), TRUE, priv->is_outbox);
982         } else {
983                 priv->row_reference = NULL;
984                 priv->next_row_reference = NULL;
985         }
986
987         /* Connect signals */
988         priv->row_changed_handler = 
989                 g_signal_connect (GTK_TREE_MODEL(priv->header_model), "row-changed",
990                                   G_CALLBACK(modest_msg_view_window_on_row_changed),
991                                   window);
992         priv->row_deleted_handler = 
993                 g_signal_connect (GTK_TREE_MODEL(priv->header_model), "row-deleted",
994                                   G_CALLBACK(modest_msg_view_window_on_row_deleted),
995                                   window);
996         priv->row_inserted_handler = 
997                 g_signal_connect (GTK_TREE_MODEL(priv->header_model), "row-inserted",
998                                   G_CALLBACK(modest_msg_view_window_on_row_inserted),
999                                   window);
1000         priv->rows_reordered_handler = 
1001                 g_signal_connect(GTK_TREE_MODEL(priv->header_model), "rows-reordered",
1002                                  G_CALLBACK(modest_msg_view_window_on_row_reordered),
1003                                  window);
1004
1005         if (header_view != NULL){
1006                 modest_header_view_add_observer(header_view,
1007                                 MODEST_HEADER_VIEW_OBSERVER(window));
1008         }
1009
1010         tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), NULL);
1011
1012         path = gtk_tree_row_reference_get_path (row_reference);
1013         if (gtk_tree_model_get_iter (priv->header_model, &iter, path)) {
1014                 TnyHeader *header;
1015                 gtk_tree_model_get (priv->header_model, &iter, 
1016                                     TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1017                                     &header, -1);
1018                 message_reader (window, priv, header, row_reference);
1019         }
1020         gtk_tree_path_free (path);
1021
1022         /* Check dimming rules */
1023         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (window));
1024         modest_ui_actions_check_menu_dimming_rules (MODEST_WINDOW (window));
1025         modest_window_check_dimming_rules_group (MODEST_WINDOW (window), MODEST_DIMMING_RULES_CLIPBOARD);
1026
1027         return MODEST_WINDOW(window);
1028 }
1029
1030 ModestWindow *
1031 modest_msg_view_window_new_for_search_result (TnyMsg *msg, 
1032                                               const gchar *modest_account_name,
1033                                               const gchar *mailbox,
1034                                               const gchar *msg_uid)
1035 {
1036         ModestMsgViewWindow *window = NULL;
1037         ModestMsgViewWindowPrivate *priv = NULL;
1038         ModestWindowMgr *mgr = NULL;
1039
1040         mgr = modest_runtime_get_window_mgr ();
1041         window = MODEST_MSG_VIEW_WINDOW (modest_window_mgr_get_msg_view_window (mgr));
1042         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), NULL);
1043         modest_msg_view_window_construct (window, modest_account_name, mailbox, msg_uid);
1044
1045         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1046
1047         /* Remember that this is a search result, 
1048          * so we can disable some UI appropriately: */
1049         priv->is_search_result = TRUE;
1050
1051         tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
1052         
1053         update_window_title (window);
1054         /* gtk_widget_show_all (GTK_WIDGET (window));*/
1055         modest_msg_view_window_update_priority (window);
1056
1057         /* Check dimming rules */
1058         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (window));
1059         modest_ui_actions_check_menu_dimming_rules (MODEST_WINDOW (window));
1060         modest_window_check_dimming_rules_group (MODEST_WINDOW (window), MODEST_DIMMING_RULES_CLIPBOARD);
1061
1062         return MODEST_WINDOW(window);
1063 }
1064
1065 ModestWindow *
1066 modest_msg_view_window_new_for_attachment (TnyMsg *msg, 
1067                                            const gchar *modest_account_name,
1068                                            const gchar *mailbox,
1069                                            const gchar *msg_uid)
1070 {
1071         GObject *obj = NULL;
1072         ModestMsgViewWindowPrivate *priv;       
1073         ModestWindowMgr *mgr = NULL;
1074
1075         g_return_val_if_fail (msg, NULL);
1076         mgr = modest_runtime_get_window_mgr ();
1077         obj = G_OBJECT (modest_window_mgr_get_msg_view_window (mgr));
1078         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (obj);
1079         modest_msg_view_window_construct (MODEST_MSG_VIEW_WINDOW (obj), 
1080                                           modest_account_name, mailbox, msg_uid);
1081
1082         tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
1083         update_window_title (MODEST_MSG_VIEW_WINDOW (obj));
1084
1085         /* gtk_widget_show_all (GTK_WIDGET (obj)); */
1086
1087         /* Check dimming rules */
1088         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (obj));
1089         modest_ui_actions_check_menu_dimming_rules (MODEST_WINDOW (obj));
1090         modest_window_check_dimming_rules_group (MODEST_WINDOW (obj), MODEST_DIMMING_RULES_CLIPBOARD);
1091
1092         return MODEST_WINDOW(obj);
1093 }
1094
1095 static void
1096 modest_msg_view_window_on_row_changed (GtkTreeModel *header_model,
1097                                        GtkTreePath *arg1,
1098                                        GtkTreeIter *arg2,
1099                                        ModestMsgViewWindow *window)
1100 {
1101         check_dimming_rules_after_change (window);
1102 }
1103
1104 static void 
1105 modest_msg_view_window_on_row_deleted(GtkTreeModel *header_model,
1106                                       GtkTreePath *arg1,
1107                                       ModestMsgViewWindow *window)
1108 {
1109         check_dimming_rules_after_change (window);
1110 }
1111         /* The window could have dissapeared */
1112
1113 static void
1114 check_dimming_rules_after_change (ModestMsgViewWindow *window)
1115 {
1116         modest_ui_actions_check_menu_dimming_rules (MODEST_WINDOW (window));
1117         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (window));
1118 }
1119
1120
1121 /* On insertions we check if the folder still has the message we are
1122  * showing or do not. If do not, we do nothing. Which means we are still
1123  * not attached to any header folder and thus next/prev buttons are
1124  * still dimmed. Once the message that is shown by msg-view is found, the
1125  * new model of header-view will be attached and the references will be set.
1126  * On each further insertions dimming rules will be checked. However
1127  * this requires extra CPU time at least works.
1128  * (An message might be deleted from TnyFolder and thus will not be
1129  * inserted into the model again for example if it is removed by the
1130  * imap server and the header view is refreshed.)
1131  */
1132 static void 
1133 modest_msg_view_window_on_row_inserted (GtkTreeModel *model,
1134                                         GtkTreePath *tree_path,
1135                                         GtkTreeIter *tree_iter,
1136                                         ModestMsgViewWindow *window)
1137 {
1138         ModestMsgViewWindowPrivate *priv = NULL; 
1139         TnyHeader *header = NULL;
1140
1141         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
1142         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1143
1144         g_assert (model == priv->header_model);
1145         
1146         /* Check if the newly inserted message is the same we are actually
1147          * showing. IF not, we should remain detached from the header model
1148          * and thus prev and next toolbar buttons should remain dimmed. */
1149         gtk_tree_model_get (model, tree_iter, 
1150                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, 
1151                             &header, -1);
1152
1153         if (TNY_IS_HEADER (header)) {
1154                 gchar *uid = NULL;
1155
1156                 uid = modest_tny_folder_get_header_unique_id (header);
1157                 if (!g_str_equal(priv->msg_uid, uid)) {
1158                         check_dimming_rules_after_change (window);
1159                         g_free(uid);
1160                         g_object_unref (G_OBJECT(header));
1161                         return;
1162                 }
1163                 g_free(uid);
1164                 g_object_unref(G_OBJECT(header));
1165         }
1166
1167         if (priv->row_reference) {
1168                 gtk_tree_row_reference_free (priv->row_reference); 
1169         }
1170
1171         /* Setup row_reference for the actual msg. */
1172         priv->row_reference = gtk_tree_row_reference_new (priv->header_model, tree_path);
1173         if (priv->row_reference == NULL) {
1174                 g_warning("No reference for msg header item.");
1175                 return;
1176         }
1177
1178         /* Now set up next_row_reference. */
1179         if (priv->next_row_reference) {
1180                 gtk_tree_row_reference_free (priv->next_row_reference); 
1181         }
1182
1183         priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
1184         select_next_valid_row (priv->header_model,
1185                                &(priv->next_row_reference), FALSE, priv->is_outbox);
1186
1187         /* Connect the remaining callbacks to become able to detect
1188          * changes in header-view. */
1189         priv->row_changed_handler = 
1190                 g_signal_connect (priv->header_model, "row-changed",
1191                                   G_CALLBACK (modest_msg_view_window_on_row_changed),
1192                                   window);
1193         priv->row_deleted_handler = 
1194                 g_signal_connect (priv->header_model, "row-deleted",
1195                                   G_CALLBACK (modest_msg_view_window_on_row_deleted),
1196                                   window);
1197         priv->rows_reordered_handler = 
1198                 g_signal_connect (priv->header_model, "rows-reordered",
1199                                   G_CALLBACK (modest_msg_view_window_on_row_reordered),
1200                                   window);
1201
1202         check_dimming_rules_after_change (window);      
1203 }
1204
1205 static void 
1206 modest_msg_view_window_on_row_reordered (GtkTreeModel *header_model,
1207                                          GtkTreePath *arg1,
1208                                          GtkTreeIter *arg2,
1209                                          gpointer arg3,
1210                                          ModestMsgViewWindow *window)
1211 {
1212         ModestMsgViewWindowPrivate *priv = NULL; 
1213         gboolean already_changed = FALSE;
1214
1215         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(window);
1216
1217         /* If the current row was reordered select the proper next
1218            valid row. The same if the next row reference changes */
1219         if (priv->row_reference && 
1220             gtk_tree_row_reference_valid (priv->row_reference)) {
1221                 GtkTreePath *path;
1222                 path = gtk_tree_row_reference_get_path (priv->row_reference);
1223                 if (gtk_tree_path_compare (path, arg1) == 0) {
1224                         if (priv->next_row_reference) {
1225                                 gtk_tree_row_reference_free (priv->next_row_reference);
1226                         }
1227                         priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
1228                         select_next_valid_row (header_model, &(priv->next_row_reference), FALSE, priv->is_outbox);
1229                         already_changed = TRUE;
1230                 }
1231                 gtk_tree_path_free (path);
1232         }
1233         if (!already_changed &&
1234             priv->next_row_reference &&
1235             gtk_tree_row_reference_valid (priv->next_row_reference)) {
1236                 GtkTreePath *path;
1237                 path = gtk_tree_row_reference_get_path (priv->next_row_reference);
1238                 if (gtk_tree_path_compare (path, arg1) == 0) {
1239                         if (priv->next_row_reference) {
1240                                 gtk_tree_row_reference_free (priv->next_row_reference);
1241                         }
1242                         priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
1243                         select_next_valid_row (header_model, &(priv->next_row_reference), FALSE, priv->is_outbox);
1244                 }
1245                 gtk_tree_path_free (path);
1246         }
1247         check_dimming_rules_after_change (window);
1248 }
1249
1250 /* The modest_msg_view_window_update_model_replaced implements update
1251  * function for ModestHeaderViewObserver. Checks whether the TnyFolder
1252  * actually belongs to the header-view is the same as the TnyFolder of
1253  * the message of msg-view or not. If they are different, there is
1254  * nothing to do. If they are the same, then the model has replaced and
1255  * the reference in msg-view shall be replaced from the old model to
1256  * the new model. In this case the view will be detached from it's
1257  * header folder. From this point the next/prev buttons are dimmed.
1258  */
1259 static void 
1260 modest_msg_view_window_update_model_replaced (ModestHeaderViewObserver *observer,
1261                                               GtkTreeModel *model,
1262                                               const gchar *tny_folder_id)
1263 {
1264         ModestMsgViewWindowPrivate *priv = NULL; 
1265         ModestMsgViewWindow *window = NULL;
1266
1267         g_assert(MODEST_IS_HEADER_VIEW_OBSERVER(observer));
1268         g_assert(MODEST_IS_MSG_VIEW_WINDOW(observer));
1269
1270         window = MODEST_MSG_VIEW_WINDOW(observer);
1271         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(window);
1272
1273         /* If there is an other folder in the header-view then we do
1274          * not care about it's model (msg list). Else if the
1275          * header-view shows the folder the msg shown by us is in, we
1276          * shall replace our model reference and make some check. */
1277         if(model == NULL || tny_folder_id == NULL || 
1278            (priv->header_folder_id && !g_str_equal(tny_folder_id, priv->header_folder_id)))
1279                 return;
1280
1281         /* Model is changed(replaced), so we should forget the old
1282          * one. Because there might be other references and there
1283          * might be some change on the model even if we unreferenced
1284          * it, we need to disconnect our signals here. */
1285         if (priv->header_model) {
1286                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
1287                                                   priv->row_changed_handler))
1288                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
1289                                                     priv->row_changed_handler);
1290                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
1291                                                   priv->row_deleted_handler))
1292                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
1293                                                     priv->row_deleted_handler);
1294                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
1295                                                   priv->row_inserted_handler))
1296                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
1297                                                     priv->row_inserted_handler);
1298                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
1299                                                   priv->rows_reordered_handler))
1300                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
1301                                                     priv->rows_reordered_handler);
1302
1303                 /* Frees */
1304                 if (priv->row_reference)
1305                         gtk_tree_row_reference_free (priv->row_reference);
1306                 if (priv->next_row_reference)
1307                         gtk_tree_row_reference_free (priv->next_row_reference);
1308                 g_object_unref(priv->header_model);
1309
1310                 /* Initialize */
1311                 priv->row_changed_handler = 0;
1312                 priv->row_deleted_handler = 0;
1313                 priv->row_inserted_handler = 0;
1314                 priv->rows_reordered_handler = 0;
1315                 priv->next_row_reference = NULL;
1316                 priv->row_reference = NULL;
1317                 priv->header_model = NULL;
1318         }
1319
1320         priv->header_model = g_object_ref (model);
1321
1322         /* Also we must connect to the new model for row insertions.
1323          * Only for insertions now. We will need other ones only after
1324          * the msg is show by msg-view is added to the new model. */
1325         priv->row_inserted_handler =
1326                 g_signal_connect (priv->header_model, "row-inserted",
1327                                   G_CALLBACK(modest_msg_view_window_on_row_inserted),
1328                                   window);
1329
1330         modest_ui_actions_check_menu_dimming_rules(MODEST_WINDOW(window));
1331         modest_ui_actions_check_toolbar_dimming_rules(MODEST_WINDOW(window));
1332 }
1333
1334 gboolean 
1335 modest_msg_view_window_toolbar_on_transfer_mode     (ModestMsgViewWindow *self)
1336 {
1337         ModestMsgViewWindowPrivate *priv= NULL; 
1338
1339         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), FALSE);
1340         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
1341
1342         return priv->progress_hint;
1343 }
1344
1345 TnyHeader*
1346 modest_msg_view_window_get_header (ModestMsgViewWindow *self)
1347 {
1348         ModestMsgViewWindowPrivate *priv= NULL; 
1349         TnyMsg *msg = NULL;
1350         TnyHeader *header = NULL;
1351         GtkTreePath *path = NULL;
1352         GtkTreeIter iter;
1353
1354         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), NULL);
1355         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
1356
1357         /* If the message was not obtained from a treemodel,
1358          * for instance if it was opened directly by the search UI:
1359          */
1360         if (priv->header_model == NULL || 
1361             priv->row_reference == NULL ||
1362             !gtk_tree_row_reference_valid (priv->row_reference)) {
1363                 msg = modest_msg_view_window_get_message (self);
1364                 if (msg) {
1365                         header = tny_msg_get_header (msg);
1366                         g_object_unref (msg);
1367                 }
1368                 return header;
1369         }
1370
1371         /* Get iter of the currently selected message in the header view: */
1372         path = gtk_tree_row_reference_get_path (priv->row_reference);
1373         g_return_val_if_fail (path != NULL, NULL);
1374         gtk_tree_model_get_iter (priv->header_model, 
1375                                  &iter, 
1376                                  path);
1377
1378         /* Get current message header */
1379         gtk_tree_model_get (priv->header_model, &iter, 
1380                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, 
1381                             &header, -1);
1382
1383         gtk_tree_path_free (path);
1384         return header;
1385 }
1386
1387 TnyMsg*
1388 modest_msg_view_window_get_message (ModestMsgViewWindow *self)
1389 {
1390         ModestMsgViewWindowPrivate *priv;
1391         
1392         g_return_val_if_fail (self, NULL);
1393         
1394         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
1395         
1396         return tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
1397 }
1398
1399 const gchar*
1400 modest_msg_view_window_get_message_uid (ModestMsgViewWindow *self)
1401 {
1402         ModestMsgViewWindowPrivate *priv;
1403
1404         g_return_val_if_fail (self, NULL);
1405         
1406         priv  = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
1407
1408         return (const gchar*) priv->msg_uid;
1409 }
1410
1411 static void 
1412 modest_msg_view_window_toggle_find_toolbar (GtkToggleAction *toggle,
1413                                             gpointer data)
1414 {
1415         ModestMsgViewWindow *window = MODEST_MSG_VIEW_WINDOW (data);
1416         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1417         ModestWindowPrivate *parent_priv = MODEST_WINDOW_GET_PRIVATE (window);
1418         gboolean is_active;
1419         GtkAction *action;
1420
1421         is_active = gtk_toggle_action_get_active (toggle);
1422
1423         if (is_active) {
1424                 gtk_widget_show (priv->find_toolbar);
1425                 hildon_find_toolbar_highlight_entry (HILDON_FIND_TOOLBAR (priv->find_toolbar), TRUE);
1426         } else {
1427                 gtk_widget_hide (priv->find_toolbar);
1428                 modest_msg_view_grab_focus (MODEST_MSG_VIEW (priv->msg_view));
1429         }
1430
1431         /* update the toggle buttons status */
1432         action = gtk_ui_manager_get_action (parent_priv->ui_manager, "/ToolBar/FindInMessage");
1433         if (action)
1434                 modest_utils_toggle_action_set_active_block_notify (GTK_TOGGLE_ACTION (action), is_active);
1435
1436 }
1437
1438 static void
1439 modest_msg_view_window_find_toolbar_close (GtkWidget *widget,
1440                                            ModestMsgViewWindow *obj)
1441 {
1442         GtkToggleAction *toggle;
1443         ModestWindowPrivate *parent_priv;
1444         ModestMsgViewWindowPrivate *priv;
1445
1446         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (obj);
1447         parent_priv = MODEST_WINDOW_GET_PRIVATE (obj);
1448
1449         toggle = GTK_TOGGLE_ACTION (gtk_ui_manager_get_action (parent_priv->ui_manager, "/ToolBar/FindInMessage"));
1450         gtk_toggle_action_set_active (toggle, FALSE);
1451         modest_msg_view_grab_focus (MODEST_MSG_VIEW (priv->msg_view));
1452 }
1453
1454 static void
1455 modest_msg_view_window_find_toolbar_search (GtkWidget *widget,
1456                                            ModestMsgViewWindow *obj)
1457 {
1458         gchar *current_search;
1459         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (obj);
1460
1461         if (modest_mime_part_view_is_empty (MODEST_MIME_PART_VIEW (priv->msg_view))) {
1462                 hildon_banner_show_information (NULL, NULL, _("mail_ib_nothing_to_find"));
1463                 return;
1464         }
1465
1466         g_object_get (G_OBJECT (widget), "prefix", &current_search, NULL);
1467
1468         if ((current_search == NULL) || (strcmp (current_search, "") == 0)) {
1469                 g_free (current_search);
1470                 hildon_banner_show_information (NULL, NULL, _CS("ecdg_ib_find_rep_enter_text"));
1471                 return;
1472         }
1473
1474         if ((priv->last_search == NULL) || (strcmp (priv->last_search, current_search) != 0)) {
1475                 gboolean result;
1476                 g_free (priv->last_search);
1477                 priv->last_search = g_strdup (current_search);
1478                 result = modest_isearch_view_search (MODEST_ISEARCH_VIEW (priv->msg_view),
1479                                                      priv->last_search);
1480                 if (!result) {
1481                         hildon_banner_show_information (NULL, NULL, 
1482                                                         _HL("ckct_ib_find_no_matches"));
1483                         g_free (priv->last_search);
1484                         priv->last_search = NULL;
1485                 } else {
1486                         hildon_find_toolbar_highlight_entry (HILDON_FIND_TOOLBAR (priv->find_toolbar), TRUE);
1487                 }
1488         } else {
1489                 if (!modest_isearch_view_search_next (MODEST_ISEARCH_VIEW (priv->msg_view))) {
1490                         hildon_banner_show_information (NULL, NULL, 
1491                                                         _HL("ckct_ib_find_search_complete"));
1492                         g_free (priv->last_search);
1493                         priv->last_search = NULL;
1494                 } else {
1495                         hildon_find_toolbar_highlight_entry (HILDON_FIND_TOOLBAR (priv->find_toolbar), TRUE);
1496                 }
1497         }
1498         
1499         g_free (current_search);
1500                 
1501 }
1502
1503 static void
1504 modest_msg_view_window_set_zoom (ModestWindow *window,
1505                                  gdouble zoom)
1506 {
1507         ModestMsgViewWindowPrivate *priv;
1508         ModestWindowPrivate *parent_priv;
1509      
1510         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
1511
1512         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1513         parent_priv = MODEST_WINDOW_GET_PRIVATE (window);
1514         modest_zoomable_set_zoom (MODEST_ZOOMABLE (priv->msg_view), zoom);
1515
1516 }
1517
1518 static gdouble
1519 modest_msg_view_window_get_zoom (ModestWindow *window)
1520 {
1521         ModestMsgViewWindowPrivate *priv;
1522      
1523         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), 1.0);
1524
1525         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1526         return modest_zoomable_get_zoom (MODEST_ZOOMABLE (priv->msg_view));
1527 }
1528
1529 static gboolean
1530 modest_msg_view_window_zoom_plus (ModestWindow *window)
1531 {
1532         gdouble zoom_level;
1533         ModestMsgViewWindowPrivate *priv;
1534         gint int_zoom;
1535         gchar *banner_text;
1536      
1537         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), 1.0);
1538         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1539   
1540         zoom_level =  modest_zoomable_get_zoom (MODEST_ZOOMABLE (priv->msg_view));
1541
1542         if (zoom_level >= 2.0) {
1543                 hildon_banner_show_information (NULL, NULL, 
1544                                                 _CS("ckct_ib_max_zoom_level_reached"));
1545                 return FALSE;
1546         } else if (zoom_level >= 1.5) {
1547                 zoom_level = 2.0;
1548         } else if (zoom_level >= 1.2) {
1549                 zoom_level = 1.5;
1550         } else if (zoom_level >= 1.0) {
1551                 zoom_level = 1.2;
1552         } else if (zoom_level >= 0.8) {
1553                 zoom_level = 1.0;
1554         } else if (zoom_level >= 0.5) {
1555                 zoom_level = 0.8;
1556         } else {
1557                 zoom_level = 0.5;
1558         }
1559
1560         /* set zoom level */
1561         int_zoom = (gint) rint (zoom_level*100.0+0.1);
1562         banner_text = g_strdup_printf (_HL("wdgt_ib_zoom"), int_zoom);
1563         modest_platform_information_banner (GTK_WIDGET (window), NULL, banner_text);
1564         g_free (banner_text);
1565         modest_zoomable_set_zoom (MODEST_ZOOMABLE (priv->msg_view), zoom_level);
1566
1567         return TRUE;
1568 }
1569
1570 static gboolean
1571 modest_msg_view_window_zoom_minus (ModestWindow *window)
1572 {
1573         gdouble zoom_level;
1574         ModestMsgViewWindowPrivate *priv;
1575         gint int_zoom;
1576         gchar *banner_text;
1577      
1578         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), 1.0);
1579         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1580   
1581         zoom_level =  modest_zoomable_get_zoom (MODEST_ZOOMABLE (priv->msg_view));
1582
1583         if (zoom_level <= 0.5) {
1584                 hildon_banner_show_information (NULL, NULL, 
1585                                                 _CS("ckct_ib_min_zoom_level_reached"));
1586                 return FALSE;
1587         } else if (zoom_level <= 0.8) {
1588                 zoom_level = 0.5;
1589         } else if (zoom_level <= 1.0) {
1590                 zoom_level = 0.8;
1591         } else if (zoom_level <= 1.2) {
1592                 zoom_level = 1.0;
1593         } else if (zoom_level <= 1.5) {
1594                 zoom_level = 1.2;
1595         } else if (zoom_level <= 2.0) {
1596                 zoom_level = 1.5;
1597         } else {
1598                 zoom_level = 2.0;
1599         }
1600
1601         /* set zoom level */
1602         int_zoom = (gint) rint (zoom_level*100.0+0.1);
1603         banner_text = g_strdup_printf (_HL("wdgt_ib_zoom"), int_zoom);
1604         modest_platform_information_banner (GTK_WIDGET (window), NULL, banner_text);
1605         g_free (banner_text);
1606         modest_zoomable_set_zoom (MODEST_ZOOMABLE (priv->msg_view), zoom_level);
1607
1608         return TRUE;
1609         
1610 }
1611
1612 static gboolean
1613 modest_msg_view_window_key_event (GtkWidget *window,
1614                                   GdkEventKey *event,
1615                                   gpointer userdata)
1616 {
1617         GtkWidget *focus;
1618
1619         focus = gtk_window_get_focus (GTK_WINDOW (window));
1620
1621         if (event->keyval == HILDON_HARDKEY_DECREASE && event->type == GDK_KEY_PRESS) {
1622                 modest_ui_actions_on_zoom_minus (NULL, MODEST_WINDOW (window));
1623                 return TRUE;
1624         }
1625         if (event->keyval == HILDON_HARDKEY_INCREASE && event->type == GDK_KEY_PRESS) {
1626                 modest_ui_actions_on_zoom_plus (NULL, MODEST_WINDOW (window));
1627                 return TRUE;
1628         }
1629
1630         /* for the find toolbar case */
1631         if (focus && GTK_IS_ENTRY (focus)) {
1632                 if (event->keyval == GDK_BackSpace) {
1633                         GdkEvent *copy;
1634                         copy = gdk_event_copy ((GdkEvent *) event);
1635                         gtk_widget_event (focus, copy);
1636                         gdk_event_free (copy);
1637                         return TRUE;
1638                 } else 
1639                         return FALSE;
1640         }
1641         if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up ||
1642             event->keyval == GDK_Down || event->keyval == GDK_KP_Down ||
1643             event->keyval == GDK_Page_Up || event->keyval == GDK_KP_Page_Up ||
1644             event->keyval == GDK_Page_Down || event->keyval == GDK_KP_Page_Down ||
1645             event->keyval == GDK_Home || event->keyval == GDK_KP_Home ||
1646             event->keyval == GDK_End || event->keyval == GDK_KP_End) {
1647                 /* ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window); */
1648                 /* gboolean return_value; */
1649
1650                 if (event->type == GDK_KEY_PRESS) {
1651                         GtkScrollType scroll_type;
1652
1653                         switch (event->keyval) {
1654                         case GDK_Up: 
1655                         case GDK_KP_Up:
1656                                 scroll_type = GTK_SCROLL_STEP_UP; break;
1657                         case GDK_Down: 
1658                         case GDK_KP_Down:
1659                                 scroll_type = GTK_SCROLL_STEP_DOWN; break;
1660                         case GDK_Page_Up:
1661                         case GDK_KP_Page_Up:
1662                                 scroll_type = GTK_SCROLL_PAGE_UP; break;
1663                         case GDK_Page_Down:
1664                         case GDK_KP_Page_Down:
1665                                 scroll_type = GTK_SCROLL_PAGE_DOWN; break;
1666                         case GDK_Home:
1667                         case GDK_KP_Home:
1668                                 scroll_type = GTK_SCROLL_START; break;
1669                         case GDK_End:
1670                         case GDK_KP_End:
1671                                 scroll_type = GTK_SCROLL_END; break;
1672                         default: scroll_type = GTK_SCROLL_NONE;
1673                         }
1674
1675                         /* g_signal_emit_by_name (G_OBJECT (priv->main_scroll), "scroll-child",  */
1676                         /*                     scroll_type, FALSE, &return_value); */
1677                         return FALSE;
1678                 } else {
1679                         return FALSE;
1680                 }
1681         } else {
1682                 return FALSE;
1683         }
1684 }
1685
1686 gboolean
1687 modest_msg_view_window_last_message_selected (ModestMsgViewWindow *window)
1688 {
1689         GtkTreePath *path;
1690         ModestMsgViewWindowPrivate *priv;
1691         GtkTreeIter tmp_iter;
1692         gboolean is_last_selected;
1693
1694         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), TRUE);
1695         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1696
1697         /*if no model (so no rows at all), then virtually we are the last*/
1698         if (!priv->header_model || !priv->row_reference)
1699                 return TRUE;
1700
1701         if (!gtk_tree_row_reference_valid (priv->row_reference))
1702                 return TRUE;
1703
1704         path = gtk_tree_row_reference_get_path (priv->row_reference);
1705         if (path == NULL)
1706                 return TRUE;
1707
1708         is_last_selected = TRUE;
1709         while (is_last_selected) {
1710                 TnyHeader *header;
1711                 gtk_tree_path_next (path);
1712                 if (!gtk_tree_model_get_iter (priv->header_model, &tmp_iter, path))
1713                         break;
1714                 gtk_tree_model_get (priv->header_model, &tmp_iter,
1715                                 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1716                                 &header, -1);
1717                 if (header) {
1718                         if (msg_is_visible (header, priv->is_outbox))
1719                                 is_last_selected = FALSE;
1720                         g_object_unref(G_OBJECT(header));
1721                 }
1722         }
1723         gtk_tree_path_free (path);
1724         return is_last_selected;
1725 }
1726
1727 gboolean
1728 modest_msg_view_window_has_headers_model (ModestMsgViewWindow *window)
1729 {
1730         ModestMsgViewWindowPrivate *priv;
1731
1732         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), TRUE);
1733         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1734
1735         return priv->header_model != NULL;
1736 }
1737
1738 gboolean
1739 modest_msg_view_window_is_search_result (ModestMsgViewWindow *window)
1740 {
1741         ModestMsgViewWindowPrivate *priv;
1742
1743         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), TRUE);
1744         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1745
1746         return priv->is_search_result;
1747 }
1748
1749 static gboolean
1750 msg_is_visible (TnyHeader *header, gboolean check_outbox)
1751 {
1752         if ((tny_header_get_flags(header) & TNY_HEADER_FLAG_DELETED))
1753                 return FALSE;
1754         if (!check_outbox) {
1755                 return TRUE;
1756         } else {
1757                 ModestTnySendQueueStatus status;
1758                 status = modest_tny_all_send_queues_get_msg_status (header);
1759                 return ((status != MODEST_TNY_SEND_QUEUE_FAILED) &&
1760                         (status != MODEST_TNY_SEND_QUEUE_SENDING));
1761         }
1762 }
1763
1764 gboolean
1765 modest_msg_view_window_first_message_selected (ModestMsgViewWindow *window)
1766 {
1767         GtkTreePath *path;
1768         ModestMsgViewWindowPrivate *priv;
1769         gboolean is_first_selected;
1770         GtkTreeIter tmp_iter;
1771
1772         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), TRUE);
1773         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1774
1775         /*if no model (so no rows at all), then virtually we are the first*/
1776         if (!priv->header_model || !priv->row_reference)
1777                 return TRUE;
1778
1779         if (!gtk_tree_row_reference_valid (priv->row_reference))
1780                 return TRUE;
1781
1782         path = gtk_tree_row_reference_get_path (priv->row_reference);
1783         if (!path)
1784                 return TRUE;
1785
1786         is_first_selected = TRUE;
1787         while (is_first_selected) {
1788                 TnyHeader *header;
1789                 if(!gtk_tree_path_prev (path))
1790                         break;
1791                 /* Here the 'if' is needless for logic, but let make sure
1792                  * iter is valid for gtk_tree_model_get. */
1793                 if (!gtk_tree_model_get_iter (priv->header_model, &tmp_iter, path))
1794                         break;
1795                 gtk_tree_model_get (priv->header_model, &tmp_iter,
1796                                 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1797                                 &header, -1);
1798                 if (header) {
1799                         if (msg_is_visible (header, priv->is_outbox))
1800                                 is_first_selected = FALSE;
1801                         g_object_unref(G_OBJECT(header));
1802                 }
1803         }
1804         gtk_tree_path_free (path);
1805         return is_first_selected;
1806 }
1807
1808 typedef struct {
1809         TnyHeader *header;
1810         GtkTreeRowReference *row_reference;
1811 } MsgReaderInfo;
1812
1813 static void
1814 message_reader_performer (gboolean canceled, 
1815                           GError *err,
1816                           GtkWindow *parent_window, 
1817                           TnyAccount *account, 
1818                           gpointer user_data)
1819 {
1820         ModestMailOperation *mail_op = NULL;
1821         MsgReaderInfo *info;
1822
1823         info = (MsgReaderInfo *) user_data;
1824         if (canceled || err) {
1825                 update_window_title (MODEST_MSG_VIEW_WINDOW (parent_window));
1826                 goto frees;
1827         }
1828
1829         /* Register the header - it'll be unregistered in the callback */
1830         modest_window_mgr_register_header (modest_runtime_get_window_mgr (), info->header, NULL);
1831
1832         /* New mail operation */
1833         mail_op = modest_mail_operation_new_with_error_handling (G_OBJECT(parent_window),
1834                                                                  modest_ui_actions_disk_operations_error_handler, 
1835                                                                  NULL, NULL);
1836
1837         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (), mail_op);
1838         modest_mail_operation_get_msg (mail_op, info->header, TRUE, view_msg_cb, info->row_reference);
1839         g_object_unref (mail_op);
1840
1841         /* Update dimming rules */
1842         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (parent_window));
1843         modest_ui_actions_check_menu_dimming_rules (MODEST_WINDOW (parent_window));
1844
1845  frees:
1846         /* Frees. The row_reference will be freed by the view_msg_cb callback */
1847         g_object_unref (info->header);
1848         g_slice_free (MsgReaderInfo, info);
1849 }
1850
1851
1852 /**
1853  * Reads the message whose summary item is @header. It takes care of
1854  * several things, among others:
1855  *
1856  * If the message was not previously downloaded then ask the user
1857  * before downloading. If there is no connection launch the connection
1858  * dialog. Update toolbar dimming rules.
1859  *
1860  * Returns: TRUE if the mail operation was started, otherwise if the
1861  * user do not want to download the message, or if the user do not
1862  * want to connect, then the operation is not issued
1863  **/
1864 static gboolean
1865 message_reader (ModestMsgViewWindow *window,
1866                 ModestMsgViewWindowPrivate *priv,
1867                 TnyHeader *header,
1868                 GtkTreeRowReference *row_reference)
1869 {
1870         ModestWindowMgr *mgr;
1871         TnyAccount *account;
1872         TnyFolder *folder;
1873         MsgReaderInfo *info;
1874
1875         g_return_val_if_fail (row_reference != NULL, FALSE);
1876
1877         mgr = modest_runtime_get_window_mgr ();
1878         /* Msg download completed */
1879         if (!(tny_header_get_flags (header) & TNY_HEADER_FLAG_CACHED)) {
1880
1881                 /* We set the header from model while we're loading */
1882                 tny_header_view_set_header (TNY_HEADER_VIEW (priv->msg_view), header);
1883                 gtk_window_set_title (GTK_WINDOW (window), _CS("ckdg_pb_updating"));
1884
1885                 /* Ask the user if he wants to download the message if
1886                    we're not online */
1887                 if (!tny_device_is_online (modest_runtime_get_device())) {
1888                         GtkResponseType response;
1889
1890                         response = modest_platform_run_confirmation_dialog (GTK_WINDOW (window),
1891                                                                             _("mcen_nc_get_msg"));
1892                         if (response == GTK_RESPONSE_CANCEL) {
1893                                 update_window_title (window);
1894                                 return FALSE;
1895                         }
1896
1897                         folder = tny_header_get_folder (header);
1898                         info = g_slice_new (MsgReaderInfo);
1899                         info->header = g_object_ref (header);
1900                         info->row_reference = gtk_tree_row_reference_copy (row_reference);
1901
1902                         /* Offer the connection dialog if necessary */
1903                         modest_platform_connect_if_remote_and_perform ((GtkWindow *) window, 
1904                                                                        TRUE,
1905                                                                        TNY_FOLDER_STORE (folder),
1906                                                                        message_reader_performer, 
1907                                                                        info);
1908                         g_object_unref (folder);
1909                         return TRUE;
1910                 }
1911         }
1912         
1913         folder = tny_header_get_folder (header);
1914         account = tny_folder_get_account (folder);
1915         info = g_slice_new (MsgReaderInfo);
1916         info->header = g_object_ref (header);
1917         info->row_reference = gtk_tree_row_reference_copy (row_reference);
1918         
1919         message_reader_performer (FALSE, NULL, (GtkWindow *) window, account, info);
1920         g_object_unref (account);
1921         g_object_unref (folder);
1922
1923         return TRUE;
1924 }
1925
1926 gboolean        
1927 modest_msg_view_window_select_next_message (ModestMsgViewWindow *window)
1928 {
1929         ModestMsgViewWindowPrivate *priv;
1930         GtkTreePath *path= NULL;
1931         GtkTreeIter tmp_iter;
1932         TnyHeader *header;
1933         gboolean retval = TRUE;
1934         GtkTreeRowReference *row_reference = NULL;
1935
1936         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), FALSE);
1937         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1938
1939         if (!priv->row_reference)
1940                 return FALSE;
1941
1942         /* Update the next row reference if it's not valid. This could
1943            happen if for example the header which it was pointing to,
1944            was deleted. The best place to do it is in the row-deleted
1945            handler but the tinymail model do not work like the glib
1946            tree models and reports the deletion when the row is still
1947            there */
1948         if (!gtk_tree_row_reference_valid (priv->next_row_reference)) {
1949                 if (gtk_tree_row_reference_valid (priv->row_reference)) {
1950                         priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
1951                         select_next_valid_row (priv->header_model, &(priv->next_row_reference), FALSE, priv->is_outbox);
1952                 }
1953         }
1954         if (priv->next_row_reference)
1955                 path = gtk_tree_row_reference_get_path (priv->next_row_reference);
1956         if (path == NULL)
1957                 return FALSE;
1958
1959         row_reference = gtk_tree_row_reference_copy (priv->next_row_reference);
1960
1961         gtk_tree_model_get_iter (priv->header_model,
1962                                  &tmp_iter,
1963                                  path);
1964         gtk_tree_path_free (path);
1965
1966         gtk_tree_model_get (priv->header_model, &tmp_iter, 
1967                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1968                             &header, -1);
1969         
1970         /* Read the message & show it */
1971         if (!message_reader (window, priv, header, row_reference)) {
1972                 retval = FALSE;
1973         }
1974         gtk_tree_row_reference_free (row_reference);
1975
1976         /* Free */
1977         g_object_unref (header);
1978
1979         return retval;
1980 }
1981
1982 gboolean        
1983 modest_msg_view_window_select_previous_message (ModestMsgViewWindow *window)
1984 {
1985         ModestMsgViewWindowPrivate *priv = NULL;
1986         GtkTreePath *path;
1987         gboolean finished = FALSE;
1988         gboolean retval = FALSE;
1989
1990         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), FALSE);
1991         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1992
1993         /* Return inmediatly if there is no header model */
1994         if (!priv->header_model || !priv->row_reference)
1995                 return FALSE;
1996
1997         path = gtk_tree_row_reference_get_path (priv->row_reference);
1998         while (!finished && gtk_tree_path_prev (path)) {
1999                 TnyHeader *header;
2000                 GtkTreeIter iter;
2001
2002                 gtk_tree_model_get_iter (priv->header_model, &iter, path);
2003                 gtk_tree_model_get (priv->header_model, &iter, 
2004                                     TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
2005                                     &header, -1);
2006                 finished = TRUE;
2007                 if (header) {
2008                         if (msg_is_visible (header, priv->is_outbox)) {
2009                                 GtkTreeRowReference *row_reference;
2010                                 row_reference = gtk_tree_row_reference_new (priv->header_model, path);
2011                                 /* Read the message & show it */
2012                                 retval = message_reader (window, priv, header, row_reference);
2013                                 gtk_tree_row_reference_free (row_reference);
2014                         } else {
2015                                 finished = FALSE;
2016                         }
2017                         g_object_unref (header);
2018                 }
2019         }
2020
2021         gtk_tree_path_free (path);
2022         return retval;
2023 }
2024
2025 static void
2026 view_msg_cb (ModestMailOperation *mail_op, 
2027              TnyHeader *header, 
2028              gboolean canceled,
2029              TnyMsg *msg, 
2030              GError *error,
2031              gpointer user_data)
2032 {
2033         ModestMsgViewWindow *self = NULL;
2034         ModestMsgViewWindowPrivate *priv = NULL;
2035         GtkTreeRowReference *row_reference = NULL;
2036
2037         /* Unregister the header (it was registered before creating the mail operation) */
2038         modest_window_mgr_unregister_header (modest_runtime_get_window_mgr (), header);
2039
2040         row_reference = (GtkTreeRowReference *) user_data;
2041         if (canceled) {
2042                 gtk_tree_row_reference_free (row_reference);
2043                 self = (ModestMsgViewWindow *) modest_mail_operation_get_source (mail_op);
2044                 if (self) {
2045                         /* Restore window title */
2046                         update_window_title (self);
2047                         g_object_unref (self);
2048                 }
2049                 return;
2050         }
2051
2052         /* If there was any error */
2053         if (!modest_ui_actions_msg_retrieval_check (mail_op, header, msg)) {
2054                 gtk_tree_row_reference_free (row_reference);
2055                 self = (ModestMsgViewWindow *) modest_mail_operation_get_source (mail_op);
2056                 if (self) {
2057                         /* Restore window title */
2058                         update_window_title (self);
2059                         g_object_unref (self);
2060                 }
2061                 return;
2062         }
2063
2064         /* Get the window */ 
2065         self = (ModestMsgViewWindow *) modest_mail_operation_get_source (mail_op);
2066         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self));
2067         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2068
2069         /* Update the row reference */
2070         if (priv->row_reference != NULL) {
2071                 gtk_tree_row_reference_free (priv->row_reference);
2072                 priv->row_reference = gtk_tree_row_reference_copy (row_reference);
2073                 if (priv->next_row_reference != NULL) {
2074                         gtk_tree_row_reference_free (priv->next_row_reference);
2075                 }
2076                 priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
2077                 select_next_valid_row (priv->header_model, &(priv->next_row_reference), TRUE, priv->is_outbox);
2078         }
2079
2080         /* Mark header as read */
2081         if (!(tny_header_get_flags (header) & TNY_HEADER_FLAG_SEEN))
2082                 tny_header_set_flag (header, TNY_HEADER_FLAG_SEEN);
2083
2084         /* Set new message */
2085         if (priv->msg_view != NULL && TNY_IS_MSG_VIEW (priv->msg_view)) {
2086                 tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
2087                 modest_msg_view_window_update_priority (self);
2088                 update_window_title (MODEST_MSG_VIEW_WINDOW (self));
2089                 modest_msg_view_grab_focus (MODEST_MSG_VIEW (priv->msg_view));
2090         }
2091
2092         /* Set the new message uid of the window  */
2093         if (priv->msg_uid) {
2094                 g_free (priv->msg_uid);
2095                 priv->msg_uid = modest_tny_folder_get_header_unique_id (header);
2096         }
2097
2098         /* Notify the observers */
2099         g_signal_emit (G_OBJECT (self), signals[MSG_CHANGED_SIGNAL], 
2100                        0, priv->header_model, priv->row_reference);
2101
2102         /* Frees */
2103         g_object_unref (self);
2104         gtk_tree_row_reference_free (row_reference);            
2105 }
2106
2107 TnyFolderType
2108 modest_msg_view_window_get_folder_type (ModestMsgViewWindow *window)
2109 {
2110         ModestMsgViewWindowPrivate *priv;
2111         TnyMsg *msg;
2112         TnyFolderType folder_type;
2113
2114         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2115
2116         folder_type = TNY_FOLDER_TYPE_UNKNOWN;
2117
2118         msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
2119         if (msg) {
2120                 TnyFolder *folder;
2121
2122                 folder = tny_msg_get_folder (msg);
2123                 if (folder) {
2124                         folder_type = modest_tny_folder_guess_folder_type (folder);
2125                         g_object_unref (folder);
2126                 }
2127                 g_object_unref (msg);
2128         }
2129
2130         return folder_type;
2131 }
2132
2133
2134 static void
2135 modest_msg_view_window_update_priority (ModestMsgViewWindow *window)
2136 {
2137         ModestMsgViewWindowPrivate *priv;
2138         TnyHeader *header = NULL;
2139         TnyHeaderFlags flags = 0;
2140
2141         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2142
2143         if (priv->header_model && priv->row_reference) {
2144                 GtkTreeIter iter;
2145                 GtkTreePath *path = NULL;
2146
2147                 path = gtk_tree_row_reference_get_path (priv->row_reference);
2148                 g_return_if_fail (path != NULL);
2149                 gtk_tree_model_get_iter (priv->header_model, 
2150                                          &iter, 
2151                                          gtk_tree_row_reference_get_path (priv->row_reference));
2152
2153                 gtk_tree_model_get (priv->header_model, &iter, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
2154                                     &header, -1);
2155                 gtk_tree_path_free (path);
2156         } else {
2157                 TnyMsg *msg;
2158                 msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
2159                 if (msg) {
2160                         header = tny_msg_get_header (msg);
2161                         g_object_unref (msg);
2162                 }
2163         }
2164
2165         if (header) {
2166                 flags = tny_header_get_flags (header);
2167                 g_object_unref(G_OBJECT(header));
2168         }
2169
2170         modest_msg_view_set_priority (MODEST_MSG_VIEW(priv->msg_view), flags);
2171
2172 }
2173
2174 static void
2175 toolbar_resize (ModestMsgViewWindow *self)
2176 {
2177         ModestMsgViewWindowPrivate *priv = NULL;
2178         ModestWindowPrivate *parent_priv = NULL;
2179         GtkWidget *widget;
2180         gint static_button_size;
2181         ModestWindowMgr *mgr;
2182
2183         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self));
2184         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2185         parent_priv = MODEST_WINDOW_GET_PRIVATE(self);
2186
2187         mgr = modest_runtime_get_window_mgr ();
2188         static_button_size = modest_window_mgr_get_fullscreen_mode (mgr)?120:120;
2189
2190         if (parent_priv->toolbar) {
2191                 /* left size buttons */
2192                 widget = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageReply");
2193                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (widget), FALSE);
2194                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (widget), FALSE);
2195                 gtk_widget_set_size_request (GTK_WIDGET (widget), static_button_size, -1);
2196                 widget = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageMoveTo");
2197                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (widget), FALSE);
2198                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (widget), FALSE);
2199                 gtk_widget_set_size_request (GTK_WIDGET (widget), static_button_size, -1);
2200                 widget = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarDeleteMessage");
2201                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (widget), FALSE);
2202                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (widget), FALSE);
2203                 gtk_widget_set_size_request (GTK_WIDGET (widget), static_button_size, -1);
2204                 widget = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/FindInMessage");
2205                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (widget), FALSE);
2206                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (widget), FALSE);
2207                 gtk_widget_set_size_request (GTK_WIDGET (widget), static_button_size, -1);
2208
2209                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (priv->next_toolitem), TRUE);
2210                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->next_toolitem), TRUE);
2211                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (priv->prev_toolitem), TRUE);
2212                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->prev_toolitem), TRUE);
2213         }
2214 }
2215
2216 static void
2217 modest_msg_view_window_show_toolbar (ModestWindow *self,
2218                                      gboolean show_toolbar)
2219 {
2220         ModestMsgViewWindowPrivate *priv = NULL;
2221         ModestWindowPrivate *parent_priv;
2222
2223         parent_priv = MODEST_WINDOW_GET_PRIVATE(self);
2224         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2225
2226         /* Set optimized view status */
2227         priv->optimized_view = !show_toolbar;
2228
2229         if (!parent_priv->toolbar) {
2230                 parent_priv->toolbar = gtk_ui_manager_get_widget (parent_priv->ui_manager, 
2231                                                                   "/ToolBar");
2232                 gtk_toolbar_set_icon_size (GTK_TOOLBAR (parent_priv->toolbar), HILDON_ICON_SIZE_FINGER);
2233                 gtk_widget_set_no_show_all (parent_priv->toolbar, TRUE);
2234
2235                 priv->next_toolitem = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageNext");
2236                 priv->prev_toolitem = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageBack");
2237                 toolbar_resize (MODEST_MSG_VIEW_WINDOW (self));
2238
2239                 /* Add to window */
2240                 hildon_window_add_toolbar (HILDON_WINDOW (self), 
2241                                            GTK_TOOLBAR (parent_priv->toolbar));
2242
2243         }
2244
2245         if (show_toolbar) {
2246                 /* Quick hack: this prevents toolbar icons "dance" when progress bar show status is changed */ 
2247                 /* TODO: resize mode migth be GTK_RESIZE_QUEUE, in order to avoid unneccesary shows */
2248                 gtk_container_set_resize_mode (GTK_CONTAINER(parent_priv->toolbar), GTK_RESIZE_IMMEDIATE);
2249
2250                 gtk_widget_show (GTK_WIDGET (parent_priv->toolbar));
2251                 if (modest_msg_view_window_transfer_mode_enabled (MODEST_MSG_VIEW_WINDOW (self))) 
2252                         set_progress_hint (MODEST_MSG_VIEW_WINDOW (self), TRUE);
2253                 else
2254                         set_progress_hint (MODEST_MSG_VIEW_WINDOW (self), FALSE);
2255
2256         } else {
2257                 gtk_widget_set_no_show_all (parent_priv->toolbar, TRUE);
2258                 gtk_widget_hide (GTK_WIDGET (parent_priv->toolbar));
2259         }
2260 }
2261
2262 static void 
2263 modest_msg_view_window_clipboard_owner_change (GtkClipboard *clipboard,
2264                                                GdkEvent *event,
2265                                                ModestMsgViewWindow *window)
2266 {
2267         if (!GTK_WIDGET_VISIBLE (window))
2268                 return;
2269
2270         modest_window_check_dimming_rules_group (MODEST_WINDOW (window), MODEST_DIMMING_RULES_CLIPBOARD);
2271 }
2272
2273 gboolean 
2274 modest_msg_view_window_transfer_mode_enabled (ModestMsgViewWindow *self)
2275 {
2276         ModestMsgViewWindowPrivate *priv;
2277         
2278         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), FALSE); 
2279         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2280
2281         return priv->progress_hint;
2282 }
2283
2284 static gboolean
2285 observers_empty (ModestMsgViewWindow *self)
2286 {
2287         GSList *tmp = NULL;
2288         ModestMsgViewWindowPrivate *priv;
2289         gboolean is_empty = TRUE;
2290         guint pending_ops = 0;
2291  
2292         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2293         tmp = priv->progress_widgets;
2294
2295         /* Check all observers */
2296         while (tmp && is_empty)  {
2297                 pending_ops = modest_progress_object_num_pending_operations (MODEST_PROGRESS_OBJECT(tmp->data));
2298                 is_empty = pending_ops == 0;
2299                 
2300                 tmp = g_slist_next(tmp);
2301         }
2302         
2303         return is_empty;
2304 }
2305
2306 static void
2307 on_account_removed (TnyAccountStore *account_store, 
2308                     TnyAccount *account,
2309                     gpointer user_data)
2310 {
2311         /* Do nothing if it's a transport account, because we only
2312            show the messages of a store account */
2313         if (tny_account_get_account_type(account) == TNY_ACCOUNT_TYPE_STORE) {
2314                 const gchar *parent_acc = NULL;
2315                 const gchar *our_acc = NULL;
2316
2317                 our_acc = modest_window_get_active_account (MODEST_WINDOW (user_data));
2318                 parent_acc = modest_tny_account_get_parent_modest_account_name_for_server_account (account);
2319
2320                 /* Close this window if I'm showing a message of the removed account */
2321                 if (our_acc && parent_acc && strcmp (parent_acc, our_acc) == 0)
2322                         modest_ui_actions_on_close_window (NULL, MODEST_WINDOW (user_data));
2323         }
2324 }
2325
2326 static void 
2327 on_mail_operation_started (ModestMailOperation *mail_op,
2328                            gpointer user_data)
2329 {
2330         ModestMsgViewWindow *self;
2331         ModestMailOperationTypeOperation op_type;
2332         GSList *tmp;
2333         ModestMsgViewWindowPrivate *priv;
2334         GObject *source = NULL;
2335
2336         self = MODEST_MSG_VIEW_WINDOW (user_data);
2337         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2338         op_type = modest_mail_operation_get_type_operation (mail_op);
2339         tmp = priv->progress_widgets;
2340         source = modest_mail_operation_get_source(mail_op);
2341         if (G_OBJECT (self) == source) {
2342                 if (op_type == MODEST_MAIL_OPERATION_TYPE_RECEIVE) {
2343                         set_toolbar_transfer_mode(self);
2344                         while (tmp) {
2345                                 modest_progress_object_add_operation (
2346                                                 MODEST_PROGRESS_OBJECT (tmp->data),
2347                                                 mail_op);
2348                                 tmp = g_slist_next (tmp);
2349                         }
2350                 }
2351         }
2352         g_object_unref (source);
2353 }
2354
2355 static void 
2356 on_mail_operation_finished (ModestMailOperation *mail_op,
2357                             gpointer user_data)
2358 {
2359         ModestMsgViewWindow *self;
2360         ModestMailOperationTypeOperation op_type;
2361         GSList *tmp;
2362         ModestMsgViewWindowPrivate *priv;
2363         
2364         self = MODEST_MSG_VIEW_WINDOW (user_data);
2365         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2366         op_type = modest_mail_operation_get_type_operation (mail_op);
2367         tmp = priv->progress_widgets;
2368         
2369         if (op_type == MODEST_MAIL_OPERATION_TYPE_RECEIVE) {
2370                 while (tmp) {
2371                         modest_progress_object_remove_operation (MODEST_PROGRESS_OBJECT (tmp->data),
2372                                                                  mail_op);
2373                         tmp = g_slist_next (tmp);
2374                 }
2375
2376                 /* If no more operations are being observed, NORMAL mode is enabled again */
2377                 if (observers_empty (self)) {
2378                         set_progress_hint (self, FALSE);
2379                 }
2380         }
2381
2382         /* Update dimming rules. We have to do this right here
2383            and not in view_msg_cb because at that point the
2384            transfer mode is still enabled so the dimming rule
2385            won't let the user delete the message that has been
2386            readed for example */
2387         check_dimming_rules_after_change (self);
2388
2389 }
2390
2391 static void
2392 on_queue_changed (ModestMailOperationQueue *queue,
2393                   ModestMailOperation *mail_op,
2394                   ModestMailOperationQueueNotification type,
2395                   ModestMsgViewWindow *self)
2396 {       
2397         ModestMsgViewWindowPrivate *priv;
2398
2399         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2400
2401         /* If this operations was created by another window, do nothing */
2402         if (!modest_mail_operation_is_mine (mail_op, G_OBJECT(self))) 
2403             return;
2404
2405         if (type == MODEST_MAIL_OPERATION_QUEUE_OPERATION_ADDED) {
2406                 priv->sighandlers = modest_signal_mgr_connect (priv->sighandlers,
2407                                                                G_OBJECT (mail_op),
2408                                                                "operation-started",
2409                                                                G_CALLBACK (on_mail_operation_started),
2410                                                                self);
2411                 priv->sighandlers = modest_signal_mgr_connect (priv->sighandlers,
2412                                                                G_OBJECT (mail_op),
2413                                                                "operation-finished",
2414                                                                G_CALLBACK (on_mail_operation_finished),
2415                                                                self);
2416         } else if (type == MODEST_MAIL_OPERATION_QUEUE_OPERATION_REMOVED) {
2417                 priv->sighandlers = modest_signal_mgr_disconnect (priv->sighandlers,
2418                                                                   G_OBJECT (mail_op),
2419                                                                   "operation-started");
2420                 priv->sighandlers = modest_signal_mgr_disconnect (priv->sighandlers,
2421                                                                   G_OBJECT (mail_op),
2422                                                                   "operation-finished");
2423         }
2424 }
2425
2426 TnyList *
2427 modest_msg_view_window_get_attachments (ModestMsgViewWindow *win) 
2428 {
2429         ModestMsgViewWindowPrivate *priv;
2430         TnyList *selected_attachments = NULL;
2431         
2432         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (win), NULL);
2433         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (win);
2434
2435         /* In Hildon 2.2 as there's no selection we assume we have all attachments selected */
2436         selected_attachments = modest_msg_view_get_attachments (MODEST_MSG_VIEW (priv->msg_view));
2437         
2438         return selected_attachments;
2439 }
2440
2441 static void
2442 on_decode_to_stream_async_handler (TnyMimePart *mime_part, 
2443                                    gboolean cancelled, 
2444                                    TnyStream *stream, 
2445                                    GError *err, 
2446                                    gpointer user_data)
2447 {
2448         gchar *filepath = (gchar *) user_data;
2449
2450         if (cancelled || err) {
2451                 if (err) {
2452                         modest_platform_information_banner (NULL, NULL,
2453                                                             _KR("cerm_device_memory_full"));
2454                 }
2455                 goto free;
2456         }
2457
2458         /* make the file read-only */
2459         g_chmod(filepath, 0444);
2460
2461         /* Activate the file */
2462         modest_platform_activate_file (filepath, tny_mime_part_get_content_type (mime_part));
2463
2464  free:
2465         /* Frees */
2466         g_free (filepath);
2467 }
2468
2469 void
2470 modest_msg_view_window_view_attachment (ModestMsgViewWindow *window, 
2471                                         TnyMimePart *mime_part)
2472 {
2473         ModestMsgViewWindowPrivate *priv;
2474         const gchar *msg_uid;
2475         gchar *attachment_uid = NULL;
2476         gint attachment_index = 0;
2477         TnyList *attachments;
2478
2479         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
2480         g_return_if_fail (TNY_IS_MIME_PART (mime_part) || (mime_part == NULL));
2481         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2482
2483         msg_uid = modest_msg_view_window_get_message_uid (MODEST_MSG_VIEW_WINDOW (window));
2484         attachments = modest_msg_view_get_attachments (MODEST_MSG_VIEW (priv->msg_view));
2485         attachment_index = modest_list_index (attachments, (GObject *) mime_part);
2486         g_object_unref (attachments);
2487         
2488         if (msg_uid && attachment_index >= 0) {
2489                 attachment_uid = g_strdup_printf ("%s/%d", msg_uid, attachment_index);
2490         }
2491
2492         if (mime_part == NULL) {
2493                 gboolean error = FALSE;
2494                 TnyList *selected_attachments = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2495                 if (selected_attachments == NULL || tny_list_get_length (selected_attachments) == 0) {
2496                         error = TRUE;
2497                 } else if (tny_list_get_length (selected_attachments) > 1) {
2498                         hildon_banner_show_information (NULL, NULL, _("mcen_ib_unable_to_display_more"));
2499                         error = TRUE;
2500                 } else {
2501                         TnyIterator *iter;
2502                         iter = tny_list_create_iterator (selected_attachments);
2503                         mime_part = (TnyMimePart *) tny_iterator_get_current (iter);
2504                         g_object_unref (iter);
2505                 }
2506                 if (selected_attachments)
2507                         g_object_unref (selected_attachments);
2508
2509                 if (error)
2510                         goto frees;
2511         } else {
2512                 g_object_ref (mime_part);
2513         }
2514
2515         if (tny_mime_part_is_purged (mime_part))
2516                 goto frees;
2517
2518         if (!modest_tny_mime_part_is_msg (mime_part)) {
2519                 gchar *filepath = NULL;
2520                 const gchar *att_filename = tny_mime_part_get_filename (mime_part);
2521                 gboolean show_error_banner = FALSE;
2522                 TnyFsStream *temp_stream = NULL;
2523                 temp_stream = modest_utils_create_temp_stream (att_filename, attachment_uid,
2524                                                                &filepath);
2525
2526                 if (temp_stream != NULL) {
2527                         tny_mime_part_decode_to_stream_async (mime_part, TNY_STREAM (temp_stream), 
2528                                                               on_decode_to_stream_async_handler, 
2529                                                               NULL,
2530                                                               g_strdup (filepath));
2531                         g_object_unref (temp_stream);
2532                         /* NOTE: files in the temporary area will be automatically
2533                          * cleaned after some time if they are no longer in use */
2534                 } else {
2535                         if (filepath) {
2536                                 const gchar *content_type;
2537                                 /* the file may already exist but it isn't writable,
2538                                  * let's try to open it anyway */
2539                                 content_type = tny_mime_part_get_content_type (mime_part);
2540                                 modest_platform_activate_file (filepath, content_type);
2541                         } else {
2542                                 g_warning ("%s: modest_utils_create_temp_stream failed", __FUNCTION__);
2543                                 show_error_banner = TRUE;
2544                         }
2545                 }
2546                 if (filepath)
2547                         g_free (filepath);
2548                 if (show_error_banner)
2549                         modest_platform_information_banner (NULL, NULL, _("mail_ib_file_operation_failed"));
2550         } else {
2551                 /* message attachment */
2552                 TnyHeader *header = NULL;
2553                 ModestWindowMgr *mgr;
2554                 ModestWindow *msg_win = NULL;
2555                 gboolean found;
2556
2557                 header = tny_msg_get_header (TNY_MSG (mime_part));
2558                 mgr = modest_runtime_get_window_mgr ();
2559                 found = modest_window_mgr_find_registered_header (mgr, header, &msg_win);
2560
2561                 if (found) {
2562                         /* if it's found, but there is no msg_win, it's probably in the process of being created;
2563                          * thus, we don't do anything */
2564                         g_warning ("window for is already being created");
2565                 } else {
2566                         /* it's not found, so create a new window for it */
2567                         modest_window_mgr_register_header (mgr, header, attachment_uid); /* register the uid before building the window */
2568                         gchar *account = g_strdup (modest_window_get_active_account (MODEST_WINDOW (window)));
2569                         const gchar *mailbox = modest_window_get_active_mailbox (MODEST_WINDOW (window));
2570                         if (!account)
2571                                 account = modest_account_mgr_get_default_account (modest_runtime_get_account_mgr ());
2572                         msg_win = modest_msg_view_window_new_for_attachment (TNY_MSG (mime_part), account, 
2573                                                                              mailbox, attachment_uid);
2574                         modest_window_set_zoom (MODEST_WINDOW (msg_win),
2575                                                 modest_window_get_zoom (MODEST_WINDOW (window)));
2576                         if (modest_window_mgr_register_window (mgr, msg_win, MODEST_WINDOW (window)))
2577                                 gtk_widget_show_all (GTK_WIDGET (msg_win));
2578                         else
2579                                 gtk_widget_destroy (GTK_WIDGET (msg_win));
2580                 }
2581         }
2582
2583  frees:
2584         if (attachment_uid)
2585                 g_free (attachment_uid);
2586         if (mime_part)
2587                 g_object_unref (mime_part);
2588 }
2589
2590 typedef struct
2591 {
2592         gchar *filename;
2593         TnyMimePart *part;
2594 } SaveMimePartPair;
2595
2596 typedef struct
2597 {
2598         GList *pairs;
2599         GnomeVFSResult result;
2600 } SaveMimePartInfo;
2601
2602 static void save_mime_part_info_free (SaveMimePartInfo *info, gboolean with_struct);
2603 static gboolean idle_save_mime_part_show_result (SaveMimePartInfo *info);
2604 static gpointer save_mime_part_to_file (SaveMimePartInfo *info);
2605 static void save_mime_parts_to_file_with_checks (SaveMimePartInfo *info);
2606
2607 static void
2608 save_mime_part_info_free (SaveMimePartInfo *info, gboolean with_struct)
2609 {
2610         GList *node;
2611
2612         for (node = info->pairs; node != NULL; node = g_list_next (node)) {
2613                 SaveMimePartPair *pair = (SaveMimePartPair *) node->data;
2614                 g_free (pair->filename);
2615                 g_object_unref (pair->part);
2616                 g_slice_free (SaveMimePartPair, pair);
2617         }
2618         g_list_free (info->pairs);
2619         info->pairs = NULL;
2620         if (with_struct) {
2621                 g_slice_free (SaveMimePartInfo, info);
2622         }
2623 }
2624
2625 static gboolean
2626 idle_save_mime_part_show_result (SaveMimePartInfo *info)
2627 {
2628         if (info->pairs != NULL) {
2629                 save_mime_part_to_file (info);
2630         } else {
2631                 /* This is a GDK lock because we are an idle callback and
2632                  * hildon_banner_show_information is or does Gtk+ code */
2633
2634                 gdk_threads_enter (); /* CHECKED */
2635                 save_mime_part_info_free (info, TRUE);
2636                 if (info->result == GNOME_VFS_OK) {
2637                         hildon_banner_show_information (NULL, NULL, _CS("sfil_ib_saved"));
2638                 } else if (info->result == GNOME_VFS_ERROR_NO_SPACE) {
2639                         hildon_banner_show_information (NULL, NULL, 
2640                                                         _KR("cerm_device_memory_full"));
2641                 } else {
2642                         hildon_banner_show_information (NULL, NULL, _("mail_ib_file_operation_failed"));
2643                 }
2644                 gdk_threads_leave (); /* CHECKED */
2645         }
2646
2647         return FALSE;
2648 }
2649
2650 static gpointer
2651 save_mime_part_to_file (SaveMimePartInfo *info)
2652 {
2653         GnomeVFSHandle *handle;
2654         TnyStream *stream;
2655         SaveMimePartPair *pair = (SaveMimePartPair *) info->pairs->data;
2656
2657         info->result = gnome_vfs_create (&handle, pair->filename, GNOME_VFS_OPEN_WRITE, FALSE, 0644);
2658         if (info->result == GNOME_VFS_OK) {
2659                 GError *error = NULL;
2660                 stream = tny_vfs_stream_new (handle);
2661                 if (tny_mime_part_decode_to_stream (pair->part, stream, &error) < 0) {
2662                         g_warning ("modest: could not save attachment %s: %d (%s)\n", pair->filename, error?error->code:-1, error?error->message:"Unknown error");
2663
2664                         if ((error->domain == TNY_ERROR_DOMAIN) && 
2665                             (error->code == TNY_IO_ERROR_WRITE) &&
2666                             (errno == ENOSPC)) {
2667                                 info->result = GNOME_VFS_ERROR_NO_SPACE;
2668                         } else {
2669                                 info->result = GNOME_VFS_ERROR_IO;
2670                         }
2671                 }
2672                 g_object_unref (G_OBJECT (stream));
2673                 g_object_unref (pair->part);
2674                 g_slice_free (SaveMimePartPair, pair);
2675                 info->pairs = g_list_delete_link (info->pairs, info->pairs);
2676         } else {
2677                 g_warning ("modest: could not create save attachment %s: %s\n", pair->filename, gnome_vfs_result_to_string (info->result));
2678                 save_mime_part_info_free (info, FALSE);
2679         }
2680
2681         g_idle_add ((GSourceFunc) idle_save_mime_part_show_result, info);
2682         return NULL;
2683 }
2684
2685 static void
2686 save_mime_parts_to_file_with_checks (SaveMimePartInfo *info)
2687 {
2688         gboolean is_ok = TRUE;
2689         gint replaced_files = 0;
2690         const GList *files = info->pairs;
2691         const GList *iter;
2692
2693         for (iter = files; (iter != NULL) && (replaced_files < 2); iter = g_list_next(iter)) {
2694                 SaveMimePartPair *pair = iter->data;
2695                 if (modest_utils_file_exists (pair->filename)) {
2696                         replaced_files++;
2697                 }
2698         }
2699         if (replaced_files) {
2700                 GtkWidget *confirm_overwrite_dialog;
2701                 const gchar *message = (replaced_files == 1) ?
2702                         _FM("docm_nc_replace_file") : _FM("docm_nc_replace_multiple");
2703                 confirm_overwrite_dialog = hildon_note_new_confirmation (NULL, message);
2704                 if (gtk_dialog_run (GTK_DIALOG (confirm_overwrite_dialog)) != GTK_RESPONSE_OK) {
2705                         is_ok = FALSE;
2706                 }
2707                 gtk_widget_destroy (confirm_overwrite_dialog);
2708         }
2709
2710         if (!is_ok) {
2711                 save_mime_part_info_free (info, TRUE);
2712         } else {
2713                 g_thread_create ((GThreadFunc)save_mime_part_to_file, info, FALSE, NULL);
2714         }
2715
2716 }
2717
2718 static void
2719 save_attachments_response (GtkDialog *dialog,
2720                            gint       arg1,
2721                            gpointer   user_data)  
2722 {
2723         TnyList *mime_parts;
2724         gchar *chooser_uri;
2725         GList *files_to_save = NULL;
2726         gchar *current_folder;
2727
2728         mime_parts = TNY_LIST (user_data);
2729
2730         if (arg1 != GTK_RESPONSE_OK)
2731                 goto end;
2732
2733         chooser_uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dialog));
2734         current_folder = gtk_file_chooser_get_current_folder_uri (GTK_FILE_CHOOSER (dialog));
2735         if (current_folder && current_folder != '\0') {
2736                 GError *err = NULL;
2737                 modest_conf_set_string (modest_runtime_get_conf (), MODEST_CONF_LATEST_SAVE_ATTACHMENT_PATH, 
2738                                         current_folder,&err);
2739                 if (err != NULL) {
2740                         g_debug ("Error storing latest used folder: %s", err->message);
2741                         g_error_free (err);
2742                 }
2743         }
2744         g_free (current_folder);
2745
2746         if (!modest_utils_folder_writable (chooser_uri)) {
2747                 hildon_banner_show_information 
2748                         (NULL, NULL, _FM("sfil_ib_readonly_location"));
2749         } else {
2750                 TnyIterator *iter;
2751
2752                 iter = tny_list_create_iterator (mime_parts);
2753                 while (!tny_iterator_is_done (iter)) {
2754                         TnyMimePart *mime_part = (TnyMimePart *) tny_iterator_get_current (iter);
2755
2756                         if ((modest_tny_mime_part_is_attachment_for_modest (mime_part)) &&
2757                             !tny_mime_part_is_purged (mime_part) &&
2758                             (tny_mime_part_get_filename (mime_part) != NULL)) {
2759                                 SaveMimePartPair *pair;
2760
2761                                 pair = g_slice_new0 (SaveMimePartPair);
2762
2763                                 if (tny_list_get_length (mime_parts) > 1) {
2764                                         gchar *escaped = 
2765                                                 gnome_vfs_escape_slashes (tny_mime_part_get_filename (mime_part));
2766                                         pair->filename = g_build_filename (chooser_uri, escaped, NULL);
2767                                         g_free (escaped);
2768                                 } else {
2769                                         pair->filename = g_strdup (chooser_uri);
2770                                 }
2771                                 pair->part = mime_part;
2772                                 files_to_save = g_list_prepend (files_to_save, pair);
2773                         }
2774                         tny_iterator_next (iter);
2775                 }
2776                 g_object_unref (iter);
2777         }
2778         g_free (chooser_uri);
2779
2780         if (files_to_save != NULL) {
2781                 SaveMimePartInfo *info = g_slice_new0 (SaveMimePartInfo);
2782                 info->pairs = files_to_save;
2783                 info->result = TRUE;
2784                 save_mime_parts_to_file_with_checks (info);
2785         }
2786
2787  end:
2788         /* Free and close the dialog */
2789         g_object_unref (mime_parts);
2790         gtk_widget_destroy (GTK_WIDGET (dialog));
2791 }
2792
2793 void
2794 modest_msg_view_window_save_attachments (ModestMsgViewWindow *window, 
2795                                          TnyList *mime_parts)
2796 {
2797         ModestMsgViewWindowPrivate *priv;
2798         GtkWidget *save_dialog = NULL;
2799         gchar *conf_folder = NULL;
2800         gchar *filename = NULL;
2801         gchar *save_multiple_str = NULL;
2802
2803         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
2804         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2805
2806         if (mime_parts == NULL) {
2807                 /* In Hildon 2.2 save and delete operate over all the attachments as there's no
2808                  * selection available */
2809                 mime_parts = modest_msg_view_get_attachments (MODEST_MSG_VIEW (priv->msg_view));
2810                 if (mime_parts && !modest_maemo_utils_select_attachments (GTK_WINDOW (window), mime_parts, FALSE)) {
2811                         g_object_unref (mime_parts);
2812                         return;
2813                 }
2814                 if (mime_parts == NULL || tny_list_get_length (mime_parts) == 0) {
2815                         if (mime_parts) {
2816                                 g_object_unref (mime_parts);
2817                                 mime_parts = NULL;
2818                         }
2819                         return;
2820                 }
2821         } else {
2822                 g_object_ref (mime_parts);
2823         }
2824
2825         /* prepare dialog */
2826         if (tny_list_get_length (mime_parts) == 1) {
2827                 TnyIterator *iter;
2828                 /* only one attachment selected */
2829                 iter = tny_list_create_iterator (mime_parts);
2830                 TnyMimePart *mime_part = (TnyMimePart *) tny_iterator_get_current (iter);
2831                 g_object_unref (iter);
2832                 if (!modest_tny_mime_part_is_msg (mime_part) && 
2833                     modest_tny_mime_part_is_attachment_for_modest (mime_part) &&
2834                     !tny_mime_part_is_purged (mime_part)) {
2835                         filename = g_strdup (tny_mime_part_get_filename (mime_part));
2836                 } else {
2837                         /* TODO: show any error? */
2838                         g_warning ("Tried to save a non-file attachment");
2839                         g_object_unref (mime_parts);
2840                         return;
2841                 }
2842                 g_object_unref (mime_part);
2843         } else {
2844                 save_multiple_str = g_strdup_printf (_FM("sfil_va_number_of_objects_attachments"), 
2845                                                      tny_list_get_length (mime_parts));
2846         }
2847
2848         save_dialog = hildon_file_chooser_dialog_new (GTK_WINDOW (window), 
2849                                                       GTK_FILE_CHOOSER_ACTION_SAVE);
2850
2851         /* set folder */
2852         conf_folder = modest_conf_get_string (modest_runtime_get_conf (), MODEST_CONF_LATEST_SAVE_ATTACHMENT_PATH, NULL);
2853         if (conf_folder && conf_folder[0] != '\0') {
2854                 gtk_file_chooser_set_current_folder_uri (GTK_FILE_CHOOSER (save_dialog), conf_folder);
2855         } else {
2856                 gchar *docs_folder;
2857                 /* Set the default folder to images folder */
2858                 docs_folder = g_build_filename (g_getenv (MYDOCS_ENV), DOCS_FOLDER, NULL);
2859                 gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (save_dialog), docs_folder);
2860                 g_free (docs_folder);
2861         }
2862         g_free (conf_folder);
2863
2864         /* set filename */
2865         if (filename) {
2866                 gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (save_dialog), 
2867                                                    filename);
2868                 g_free (filename);
2869         }
2870
2871         /* if multiple, set multiple string */
2872         if (save_multiple_str) {
2873                 g_object_set (G_OBJECT (save_dialog), "save-multiple", save_multiple_str, NULL);
2874                 gtk_window_set_title (GTK_WINDOW (save_dialog), _FM("sfil_ti_save_objects_files"));
2875         }
2876
2877         /* We must run this asynchronously, because the hildon dialog
2878            performs a gtk_dialog_run by itself which leads to gdk
2879            deadlocks */
2880         g_signal_connect (save_dialog, "response", 
2881                           G_CALLBACK (save_attachments_response), mime_parts);
2882
2883         gtk_widget_show_all (save_dialog);
2884 }
2885
2886 static gboolean
2887 show_remove_attachment_information (gpointer userdata)
2888 {
2889         ModestMsgViewWindow *window = (ModestMsgViewWindow *) userdata;
2890         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2891
2892         /* We're outside the main lock */
2893         gdk_threads_enter ();
2894
2895         if (priv->remove_attachment_banner != NULL) {
2896                 gtk_widget_destroy (priv->remove_attachment_banner);
2897                 g_object_unref (priv->remove_attachment_banner);
2898         }
2899
2900         priv->remove_attachment_banner = g_object_ref (
2901                 hildon_banner_show_animation (NULL, NULL, _("mcen_me_inbox_remove_attachments")));
2902
2903         gdk_threads_leave ();
2904
2905         return FALSE;
2906 }
2907
2908 void
2909 modest_msg_view_window_remove_attachments (ModestMsgViewWindow *window, gboolean get_all)
2910 {
2911         ModestMsgViewWindowPrivate *priv;
2912         TnyList *mime_parts = NULL;
2913         gchar *confirmation_message;
2914         gint response;
2915         gint n_attachments;
2916         TnyMsg *msg;
2917         TnyIterator *iter;
2918
2919         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
2920         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2921
2922         /* In hildon 2.2 we ignore the get_all flag as we always get all attachments. This is
2923          * because we don't have selection
2924          */
2925         mime_parts = modest_msg_view_get_attachments (MODEST_MSG_VIEW (priv->msg_view));
2926                 
2927         /* Remove already purged messages from mime parts list */
2928         iter = tny_list_create_iterator (mime_parts);
2929         while (!tny_iterator_is_done (iter)) {
2930                 TnyMimePart *part = TNY_MIME_PART (tny_iterator_get_current (iter));
2931                 tny_iterator_next (iter);
2932                 if (tny_mime_part_is_purged (part)) {
2933                         tny_list_remove (mime_parts, (GObject *) part);
2934                 }
2935                 g_object_unref (part);
2936         }
2937         g_object_unref (iter);
2938
2939         if (!modest_maemo_utils_select_attachments (GTK_WINDOW (window), mime_parts, TRUE) ||
2940             tny_list_get_length (mime_parts) == 0) {
2941                 g_object_unref (mime_parts);
2942                 return;
2943         }
2944
2945         n_attachments = tny_list_get_length (mime_parts);
2946         if (n_attachments == 1) {
2947                 gchar *filename;
2948                 TnyMimePart *part;
2949
2950                 iter = tny_list_create_iterator (mime_parts);
2951                 part = (TnyMimePart *) tny_iterator_get_current (iter);
2952                 g_object_unref (iter);
2953                 if (modest_tny_mime_part_is_msg (part)) {
2954                         TnyHeader *header;
2955                         header = tny_msg_get_header (TNY_MSG (part));
2956                         filename = tny_header_dup_subject (header);
2957                         g_object_unref (header);
2958                         if (filename == NULL)
2959                                 filename = g_strdup (_("mail_va_no_subject"));
2960                 } else {
2961                         filename = g_strdup (tny_mime_part_get_filename (TNY_MIME_PART (part)));
2962                 }
2963                 confirmation_message = g_strdup_printf (_("mcen_nc_purge_file_text"), filename);
2964                 g_free (filename);
2965                 g_object_unref (part);
2966         } else {
2967                 confirmation_message = g_strdup_printf (ngettext("mcen_nc_purge_file_text", 
2968                                                                  "mcen_nc_purge_files_text", 
2969                                                                  n_attachments), n_attachments);
2970         }
2971         response = modest_platform_run_confirmation_dialog (GTK_WINDOW (window),
2972                                                             confirmation_message);
2973         g_free (confirmation_message);
2974
2975         if (response != GTK_RESPONSE_OK) {
2976                 g_object_unref (mime_parts);
2977                 return;
2978         }
2979
2980         priv->purge_timeout = g_timeout_add (2000, show_remove_attachment_information, window);
2981         
2982         iter = tny_list_create_iterator (mime_parts);
2983         while (!tny_iterator_is_done (iter)) {
2984                 TnyMimePart *part;
2985
2986                 part = (TnyMimePart *) tny_iterator_get_current (iter);
2987                 tny_mime_part_set_purged (TNY_MIME_PART (part));
2988                 g_object_unref (part);
2989                 tny_iterator_next (iter);
2990         }
2991         g_object_unref (iter);
2992
2993         msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
2994         tny_msg_view_clear (TNY_MSG_VIEW (priv->msg_view));
2995         tny_msg_rewrite_cache (msg);
2996         tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
2997         g_object_unref (msg);
2998
2999         g_object_unref (mime_parts);
3000
3001         if (priv->purge_timeout > 0) {
3002                 g_source_remove (priv->purge_timeout);
3003                 priv->purge_timeout = 0;
3004         }
3005
3006         if (priv->remove_attachment_banner) {
3007                 gtk_widget_destroy (priv->remove_attachment_banner);
3008                 g_object_unref (priv->remove_attachment_banner);
3009                 priv->remove_attachment_banner = NULL;
3010         }
3011
3012
3013 }
3014
3015
3016 static void
3017 update_window_title (ModestMsgViewWindow *window)
3018 {
3019         ModestMsgViewWindowPrivate *priv;
3020         TnyMsg *msg = NULL;
3021         TnyHeader *header = NULL;
3022         gchar *subject = NULL;
3023
3024         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
3025
3026         /* Note that if the window is closed while we're retrieving
3027            the message, this widget could de deleted */
3028         if (!priv->msg_view)
3029                 return;
3030
3031         msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
3032
3033         if (msg != NULL) {
3034                 header = tny_msg_get_header (msg);
3035                 subject = tny_header_dup_subject (header);
3036                 g_object_unref (header);
3037                 g_object_unref (msg);
3038         }
3039
3040         if ((subject == NULL)||(subject[0] == '\0')) {
3041                 g_free (subject);
3042                 subject = g_strdup (_("mail_va_no_subject"));
3043         }
3044
3045         gtk_window_set_title (GTK_WINDOW (window), subject);
3046 }
3047
3048
3049 static void
3050 on_move_focus (GtkWidget *widget,
3051                GtkDirectionType direction,
3052                gpointer userdata)
3053 {
3054         g_signal_stop_emission_by_name (G_OBJECT (widget), "move-focus");
3055 }
3056
3057 static TnyStream *
3058 fetch_image_open_stream (TnyStreamCache *self, gint64 *expected_size, gchar *uri)
3059 {
3060         GnomeVFSResult result;
3061         GnomeVFSHandle *handle = NULL;
3062         GnomeVFSFileInfo *info = NULL;
3063         TnyStream *stream;
3064
3065         result = gnome_vfs_open (&handle, uri, GNOME_VFS_OPEN_READ);
3066         if (result != GNOME_VFS_OK) {
3067                 *expected_size = 0;
3068                 return NULL;
3069         }
3070         
3071         info = gnome_vfs_file_info_new ();
3072         result = gnome_vfs_get_file_info_from_handle (handle, info, GNOME_VFS_FILE_INFO_DEFAULT);
3073         if (result != GNOME_VFS_OK || ! (info->valid_fields & GNOME_VFS_FILE_INFO_FIELDS_SIZE)) {
3074                 /* We put a "safe" default size for going to cache */
3075                 *expected_size = (300*1024);
3076         } else {
3077                 *expected_size = info->size;
3078         }
3079         gnome_vfs_file_info_unref (info);
3080
3081         stream = tny_vfs_stream_new (handle);
3082
3083         return stream;
3084
3085 }
3086
3087 typedef struct {
3088         gchar *uri;
3089         gchar *cache_id;
3090         TnyStream *output_stream;
3091         GtkWidget *msg_view;
3092 } FetchImageData;
3093
3094 gboolean
3095 on_fetch_image_idle_refresh_view (gpointer userdata)
3096 {
3097
3098         FetchImageData *fidata = (FetchImageData *) userdata;
3099         g_message ("REFRESH VIEW");
3100
3101         gdk_threads_enter ();
3102         if (GTK_WIDGET_DRAWABLE (fidata->msg_view)) {
3103                 g_message ("QUEUING DRAW");
3104                 gtk_widget_queue_draw (fidata->msg_view);
3105         }
3106         gdk_threads_leave ();
3107
3108         g_object_unref (fidata->msg_view);
3109         g_slice_free (FetchImageData, fidata);
3110         return FALSE;
3111 }
3112
3113 static gpointer
3114 on_fetch_image_thread (gpointer userdata)
3115 {
3116         FetchImageData *fidata = (FetchImageData *) userdata;
3117         TnyStreamCache *cache;
3118         TnyStream *cache_stream;
3119
3120         cache = modest_runtime_get_images_cache ();
3121         cache_stream = 
3122                 tny_stream_cache_get_stream (cache, 
3123                                              fidata->cache_id, 
3124                                              (TnyStreamCacheOpenStreamFetcher) fetch_image_open_stream, 
3125                                              (gpointer) fidata->uri);
3126         g_free (fidata->cache_id);
3127         g_free (fidata->uri);
3128
3129         if (cache_stream != NULL) {
3130                 char buffer[4096];
3131
3132                 while (G_LIKELY (!tny_stream_is_eos (cache_stream))) {
3133                         gssize nb_read;
3134
3135                         nb_read = tny_stream_read (cache_stream, buffer, sizeof (buffer));
3136                         if (G_UNLIKELY (nb_read < 0)) {
3137                                 break;
3138                         } else if (G_LIKELY (nb_read > 0)) {
3139                                 gssize nb_written = 0;
3140
3141                                 while (G_UNLIKELY (nb_written < nb_read)) {
3142                                         gssize len;
3143
3144                                         len = tny_stream_write (fidata->output_stream, buffer + nb_written,
3145                                                                 nb_read - nb_written);
3146                                         if (G_UNLIKELY (len < 0))
3147                                                 break;
3148                                         nb_written += len;
3149                                 }
3150                         }
3151                 }
3152                 tny_stream_close (cache_stream);
3153                 g_object_unref (cache_stream);
3154         }
3155
3156         tny_stream_close (fidata->output_stream);
3157         g_object_unref (fidata->output_stream);
3158
3159         g_idle_add (on_fetch_image_idle_refresh_view, fidata);
3160
3161         return NULL;
3162 }
3163
3164 static gboolean
3165 on_fetch_image (ModestMsgView *msgview,
3166                 const gchar *uri,
3167                 TnyStream *stream,
3168                 ModestMsgViewWindow *window)
3169 {
3170         const gchar *current_account;
3171         ModestMsgViewWindowPrivate *priv;
3172         FetchImageData *fidata;
3173
3174         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
3175
3176         current_account = modest_window_get_active_account (MODEST_WINDOW (window));
3177
3178         fidata = g_slice_new0 (FetchImageData);
3179         fidata->msg_view = g_object_ref (msgview);
3180         fidata->uri = g_strdup (uri);
3181         fidata->cache_id = modest_images_cache_get_id (current_account, uri);
3182         fidata->output_stream = g_object_ref (stream);
3183
3184         if (g_thread_create (on_fetch_image_thread, fidata, FALSE, NULL) == NULL) {
3185                 g_object_unref (fidata->output_stream);
3186                 g_free (fidata->cache_id);
3187                 g_free (fidata->uri);
3188                 g_object_unref (fidata->msg_view);
3189                 g_slice_free (FetchImageData, fidata);
3190                 tny_stream_close (stream);
3191                 return FALSE;
3192         }
3193
3194         return TRUE;
3195 }
3196
3197 static void 
3198 setup_menu (ModestMsgViewWindow *self)
3199 {
3200         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW(self));
3201
3202         /* Settings menu buttons */
3203         modest_hildon2_window_add_to_menu (MODEST_HILDON2_WINDOW (self), _("mcen_me_inbox_replytoall"), NULL,
3204                                            APP_MENU_CALLBACK (modest_ui_actions_on_reply_all),
3205                                            MODEST_DIMMING_CALLBACK (modest_ui_dimming_rules_on_reply_msg));
3206         modest_hildon2_window_add_to_menu (MODEST_HILDON2_WINDOW (self), _("mcen_me_inbox_forward"), "<Control>d",
3207                                            APP_MENU_CALLBACK (modest_ui_actions_on_forward),
3208                                            MODEST_DIMMING_CALLBACK (modest_ui_dimming_rules_on_reply_msg));
3209
3210         modest_hildon2_window_add_to_menu (MODEST_HILDON2_WINDOW (self), _("mcen_me_inbox_mark_as_read"), NULL,
3211                                            APP_MENU_CALLBACK (modest_ui_actions_on_mark_as_read),
3212                                            MODEST_DIMMING_CALLBACK (modest_ui_dimming_rules_on_mark_as_read_msg_in_view));
3213         modest_hildon2_window_add_to_menu (MODEST_HILDON2_WINDOW (self), _("mcen_me_inbox_mark_as_unread"), NULL,
3214                                            APP_MENU_CALLBACK (modest_ui_actions_on_mark_as_unread),
3215                                            MODEST_DIMMING_CALLBACK (modest_ui_dimming_rules_on_mark_as_unread_msg_in_view));
3216
3217         modest_hildon2_window_add_to_menu (MODEST_HILDON2_WINDOW (self), _("mcen_me_viewer_save_attachments"), NULL,
3218                                            APP_MENU_CALLBACK (modest_ui_actions_save_attachments),
3219                                            MODEST_DIMMING_CALLBACK (modest_ui_dimming_rules_on_save_attachments));
3220         modest_hildon2_window_add_to_menu (MODEST_HILDON2_WINDOW (self), _("mcen_me_inbox_remove_attachments"), NULL,
3221                                            APP_MENU_CALLBACK (modest_ui_actions_remove_attachments),
3222                                            MODEST_DIMMING_CALLBACK (modest_ui_dimming_rules_on_remove_attachments));
3223
3224         modest_hildon2_window_add_to_menu (MODEST_HILDON2_WINDOW (self), _("mcen_me_new_message"), "<Control>n",
3225                                            APP_MENU_CALLBACK (modest_ui_actions_on_new_msg),
3226                                            MODEST_DIMMING_CALLBACK (modest_ui_dimming_rules_on_new_msg));
3227         modest_hildon2_window_add_to_menu (MODEST_HILDON2_WINDOW (self), _("mcen_me_viewer_addtocontacts"), NULL,
3228                                            APP_MENU_CALLBACK (modest_ui_actions_add_to_contacts),
3229                                            MODEST_DIMMING_CALLBACK (modest_ui_dimming_rules_on_add_to_contacts));
3230 }
3231
3232 void
3233 modest_msg_view_window_add_to_contacts (ModestMsgViewWindow *self)
3234 {
3235         ModestMsgViewWindowPrivate *priv;
3236         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
3237         GSList *recipients = NULL;
3238         TnyMsg *msg = NULL;
3239         gboolean contacts_to_add = FALSE;
3240
3241         msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
3242         if (msg == NULL) {
3243                 TnyHeader *header;
3244
3245                 header = modest_msg_view_window_get_header (self);
3246                 if (header == NULL)
3247                         return;
3248                 recipients = modest_tny_msg_header_get_all_recipients_list (header);
3249                 g_object_unref (header);
3250         } else {
3251                 recipients = modest_tny_msg_get_all_recipients_list (msg);
3252                 g_object_unref (msg);
3253         }
3254
3255         if (recipients != NULL) {
3256                 GtkWidget *picker_dialog;
3257                 GtkWidget *selector;
3258                 GSList *node;
3259                 gchar *selected = NULL;
3260
3261                 selector = hildon_touch_selector_new_text ();
3262                 g_object_ref (selector);
3263
3264                 for (node = recipients; node != NULL; node = g_slist_next (node)) {
3265                         if (!modest_address_book_has_address ((const gchar *) node->data)) {
3266                                 hildon_touch_selector_append_text (HILDON_TOUCH_SELECTOR (selector), 
3267                                                                    (const gchar *) node->data);
3268                                 contacts_to_add = TRUE;
3269                         }
3270                 }
3271
3272                 if (contacts_to_add) {
3273                         gint picker_result;
3274
3275                         picker_dialog = hildon_picker_dialog_new (GTK_WINDOW (self));
3276                         gtk_window_set_title (GTK_WINDOW (picker_dialog), _("mcen_me_viewer_addtocontacts"));
3277
3278                         hildon_picker_dialog_set_selector (HILDON_PICKER_DIALOG (picker_dialog), 
3279                                                            HILDON_TOUCH_SELECTOR (selector));
3280                         
3281                         picker_result = gtk_dialog_run (GTK_DIALOG (picker_dialog));
3282
3283                         if (picker_result == GTK_RESPONSE_OK) {
3284                                 selected = hildon_touch_selector_get_current_text (HILDON_TOUCH_SELECTOR (selector));
3285                         }
3286                         gtk_widget_destroy (picker_dialog);
3287
3288                         if (selected)
3289                                 modest_address_book_add_address (selected);
3290                         g_free (selected);
3291
3292                 } else {
3293
3294                         g_object_unref (selector);
3295
3296                 }
3297         }
3298         
3299         if (recipients) {g_slist_foreach (recipients, (GFunc) g_free, NULL); g_slist_free (recipients);}
3300 }
3301
3302 static gboolean 
3303 _modest_msg_view_window_map_event (GtkWidget *widget,
3304                                    GdkEvent *event,
3305                                    gpointer userdata)
3306 {
3307         ModestMsgViewWindow *self = (ModestMsgViewWindow *) userdata;
3308         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
3309
3310         if (priv->progress_hint) {
3311                 hildon_gtk_window_set_progress_indicator (GTK_WINDOW (self), TRUE);
3312         }
3313
3314         _make_zoom_buttons_grabeable (GTK_WIDGET (self));
3315         return FALSE;
3316 }
3317
3318 static void
3319 _make_zoom_buttons_grabeable (GtkWidget* widget)
3320 {
3321     GdkDisplay *display;
3322     Atom atom;
3323     unsigned long val = 1;
3324
3325     display = gdk_drawable_get_display (widget->window);
3326     atom = gdk_x11_get_xatom_by_name_for_display (display, "_HILDON_ZOOM_KEY_ATOM");
3327     XChangeProperty (GDK_DISPLAY_XDISPLAY (display),
3328                      GDK_WINDOW_XID (widget->window), atom,
3329                      XA_INTEGER, 32, PropModeReplace,
3330                      (unsigned char *) &val, 1);
3331 }