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