Fixes several G_CRITICALS
[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 #ifdef MODEST_PLATFORM_MAEMO
75 #include <modest-maemo-utils.h>
76 #endif
77
78 #define MYDOCS_ENV "MYDOCSDIR"
79 #define DOCS_FOLDER ".documents"
80
81 typedef struct _ModestMsgViewWindowPrivate ModestMsgViewWindowPrivate;
82 struct _ModestMsgViewWindowPrivate {
83
84         GtkWidget   *msg_view;
85         GtkWidget   *main_scroll;
86         GtkWidget   *isearch_toolbar;
87         gchar       *last_search;
88
89         /* Progress observers */
90         GSList           *progress_widgets;
91
92         /* Tollbar items */
93         GtkWidget   *prev_toolitem;
94         GtkWidget   *next_toolitem;
95         gboolean    progress_hint;
96         gint        fetching_images;
97
98         /* Optimized view enabled */
99         gboolean optimized_view;
100
101         /* Whether this was created via the *_new_for_search_result() function. */
102         gboolean is_search_result;
103
104         /* Whether the message is in outbox */
105         gboolean is_outbox;
106
107         /* A reference to the @model of the header view 
108          * to allow selecting previous/next messages,
109          * if the message is currently selected in the header view.
110          */
111         const gchar *header_folder_id;
112         GtkTreeModel *header_model;
113         GtkTreeRowReference *row_reference;
114         GtkTreeRowReference *next_row_reference;
115
116         gulong clipboard_change_handler;
117         gulong queue_change_handler;
118         gulong account_removed_handler;
119         gulong row_changed_handler;
120         gulong row_deleted_handler;
121         gulong row_inserted_handler;
122         gulong rows_reordered_handler;
123
124         guint purge_timeout;
125         GtkWidget *remove_attachment_banner;
126
127         gchar *msg_uid;
128         TnyMimePart *other_body;
129
130         GSList *sighandlers;
131 };
132
133 static void  modest_msg_view_window_class_init   (ModestMsgViewWindowClass *klass);
134 static void  modest_msg_view_window_init         (ModestMsgViewWindow *obj);
135 static void  modest_header_view_observer_init    (ModestHeaderViewObserverIface *iface_class);
136 static void  modest_msg_view_window_finalize     (GObject *obj);
137 static void  modest_msg_view_window_show_isearch_toolbar   (GtkWidget *obj, gpointer data);
138 static void  modest_msg_view_window_isearch_toolbar_close  (GtkWidget *widget,
139                                                             ModestMsgViewWindow *obj);
140 static void  modest_msg_view_window_isearch_toolbar_search (GtkWidget *widget,
141                                                             ModestMsgViewWindow *obj);
142 static void  modest_msg_view_window_toggle_isearch_toolbar (GtkWidget *obj,
143                                                             gpointer data);
144 static void modest_msg_view_window_disconnect_signals (ModestWindow *self);
145
146 static gdouble modest_msg_view_window_get_zoom    (ModestWindow *window);
147 static void modest_msg_view_window_set_zoom       (ModestWindow *window,
148                                                    gdouble zoom);
149 static gboolean modest_msg_view_window_zoom_minus (ModestWindow *window);
150 static gboolean modest_msg_view_window_zoom_plus  (ModestWindow *window);
151 static gboolean modest_msg_view_window_key_event  (GtkWidget *window,
152                                                    GdkEventKey *event,
153                                                    gpointer userdata);
154 static void modest_msg_view_window_update_priority (ModestMsgViewWindow *window);
155
156 static void modest_msg_view_window_show_toolbar   (ModestWindow *window,
157                                                    gboolean show_toolbar);
158
159 static void modest_msg_view_window_clipboard_owner_change (GtkClipboard *clipboard,
160                                                            GdkEvent *event,
161                                                            ModestMsgViewWindow *window);
162
163 static void modest_msg_view_window_on_row_changed (GtkTreeModel *header_model,
164                                                    GtkTreePath *arg1,
165                                                    GtkTreeIter *arg2,
166                                                    ModestMsgViewWindow *window);
167
168 static void modest_msg_view_window_on_row_deleted (GtkTreeModel *header_model,
169                                                    GtkTreePath *arg1,
170                                                    ModestMsgViewWindow *window);
171
172 static void modest_msg_view_window_on_row_inserted (GtkTreeModel *header_model,
173                                                     GtkTreePath *tree_path,
174                                                     GtkTreeIter *tree_iter,
175                                                     ModestMsgViewWindow *window);
176
177 static void modest_msg_view_window_on_row_reordered (GtkTreeModel *header_model,
178                                                      GtkTreePath *arg1,
179                                                      GtkTreeIter *arg2,
180                                                      gpointer arg3,
181                                                      ModestMsgViewWindow *window);
182
183 static void modest_msg_view_window_update_model_replaced (ModestHeaderViewObserver *window,
184                                                           GtkTreeModel *model,
185                                                           const gchar *tny_folder_id);
186
187 static void on_queue_changed    (ModestMailOperationQueue *queue,
188                                  ModestMailOperation *mail_op,
189                                  ModestMailOperationQueueNotification type,
190                                  ModestMsgViewWindow *self);
191
192 static void on_account_removed  (TnyAccountStore *account_store, 
193                                  TnyAccount *account,
194                                  gpointer user_data);
195
196 static void on_move_focus (GtkWidget *widget,
197                            GtkDirectionType direction,
198                            gpointer userdata);
199
200 static void view_msg_cb         (ModestMailOperation *mail_op, 
201                                  TnyHeader *header, 
202                                  gboolean canceled,
203                                  TnyMsg *msg, 
204                                  GError *error,
205                                  gpointer user_data);
206
207 static void set_progress_hint    (ModestMsgViewWindow *self, 
208                                   gboolean enabled);
209
210 static void update_window_title (ModestMsgViewWindow *window);
211
212 static void init_window (ModestMsgViewWindow *obj);
213
214 static gboolean msg_is_visible (TnyHeader *header, gboolean check_outbox);
215
216 static void check_dimming_rules_after_change (ModestMsgViewWindow *window);
217
218 static gboolean on_fetch_image (ModestMsgView *msgview,
219                                 const gchar *uri,
220                                 TnyStream *stream,
221                                 ModestMsgViewWindow *window);
222
223 static gboolean modest_msg_view_window_scroll_child (ModestMsgViewWindow *self,
224                                                      GtkScrollType scroll_type,
225                                                      gboolean horizontal,
226                                                      gpointer userdata);
227 static gboolean message_reader (ModestMsgViewWindow *window,
228                                 ModestMsgViewWindowPrivate *priv,
229                                 TnyHeader *header,
230                                 const gchar *msg_uid,
231                                 TnyFolder *folder,
232                                 GtkTreeRowReference *row_reference);
233
234 static void setup_menu (ModestMsgViewWindow *self);
235 static gboolean _modest_msg_view_window_map_event (GtkWidget *widget,
236                                                    GdkEvent *event,
237                                                    gpointer userdata);
238 static void update_branding (ModestMsgViewWindow *self);
239 static void sync_flags      (ModestMsgViewWindow *self);
240
241 /* list my signals */
242 enum {
243         MSG_CHANGED_SIGNAL,
244         SCROLL_CHILD_SIGNAL,
245         LAST_SIGNAL
246 };
247
248 static const GtkActionEntry msg_view_toolbar_action_entries [] = {
249
250         /* Toolbar items */
251         { "ToolbarMessageReply",      MODEST_STOCK_REPLY,     N_("mcen_me_inbox_reply"),      "<CTRL>R", NULL,  G_CALLBACK (modest_ui_actions_on_reply) },
252         { "ToolbarMessageReplyAll",   MODEST_STOCK_REPLY_ALL,     N_("mcen_me_inbox_replytoall"),         NULL, NULL,  G_CALLBACK (modest_ui_actions_on_reply_all) },
253         { "ToolbarMessageForward",    MODEST_STOCK_FORWARD,     N_("mcen_me_inbox_forward"),      NULL, NULL,  G_CALLBACK (modest_ui_actions_on_forward) },
254         { "ToolbarDeleteMessage",     MODEST_STOCK_DELETE,     N_("qgn_toolb_gene_deletebutton"),             NULL, NULL,  G_CALLBACK (modest_ui_actions_on_delete_message_or_folder) },
255         { "ToolbarMessageBack",       MODEST_TOOLBAR_ICON_PREV,    N_("qgn_toolb_gene_back"),         NULL, NULL, G_CALLBACK (modest_ui_actions_on_prev) },
256         { "ToolbarMessageNext",    MODEST_TOOLBAR_ICON_NEXT, N_("qgn_toolb_gene_forward"),      NULL, NULL, G_CALLBACK (modest_ui_actions_on_next) },
257         { "ToolbarDownloadExternalImages", MODEST_TOOLBAR_ICON_DOWNLOAD_IMAGES, N_("mail_bd_external_images"),      NULL, NULL,  G_CALLBACK (modest_ui_actions_on_fetch_images) },
258 };
259
260 static const GtkToggleActionEntry msg_view_toggle_action_entries [] = {
261         { "FindInMessage",    MODEST_TOOLBAR_ICON_FIND,    N_("qgn_toolb_gene_find"), "<CTRL>F", NULL, G_CALLBACK (modest_msg_view_window_toggle_isearch_toolbar), FALSE },
262 };
263
264 #define MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(o)      (G_TYPE_INSTANCE_GET_PRIVATE((o), \
265                                                     MODEST_TYPE_MSG_VIEW_WINDOW, \
266                                                     ModestMsgViewWindowPrivate))
267 /* globals */
268 static GtkWindowClass *parent_class = NULL;
269
270 /* uncomment the following if you have defined any signals */
271 static guint signals[LAST_SIGNAL] = {0};
272
273 GType
274 modest_msg_view_window_get_type (void)
275 {
276         static GType my_type = 0;
277         if (!my_type) {
278                 static const GTypeInfo my_info = {
279                         sizeof(ModestMsgViewWindowClass),
280                         NULL,           /* base init */
281                         NULL,           /* base finalize */
282                         (GClassInitFunc) modest_msg_view_window_class_init,
283                         NULL,           /* class finalize */
284                         NULL,           /* class data */
285                         sizeof(ModestMsgViewWindow),
286                         1,              /* n_preallocs */
287                         (GInstanceInitFunc) modest_msg_view_window_init,
288                         NULL
289                 };
290 #ifndef MODEST_TOOLKIT_HILDON2
291                 my_type = g_type_register_static (MODEST_TYPE_SHELL_WINDOW,
292                                                   "ModestMsgViewWindow",
293                                                   &my_info, 0);
294 #else
295                 my_type = g_type_register_static (MODEST_TYPE_HILDON2_WINDOW,
296                                                   "ModestMsgViewWindow",
297                                                   &my_info, 0);
298 #endif
299
300                 static const GInterfaceInfo modest_header_view_observer_info = 
301                 {
302                         (GInterfaceInitFunc) modest_header_view_observer_init,
303                         NULL,         /* interface_finalize */
304                         NULL          /* interface_data */
305                 };
306
307                 g_type_add_interface_static (my_type,
308                                 MODEST_TYPE_HEADER_VIEW_OBSERVER,
309                                 &modest_header_view_observer_info);
310         }
311         return my_type;
312 }
313
314 static void
315 save_state (ModestWindow *self)
316 {
317         modest_widget_memory_save (modest_runtime_get_conf (),
318                                    G_OBJECT(self),
319                                    MODEST_CONF_MSG_VIEW_WINDOW_KEY);
320 }
321
322 static gboolean
323 modest_msg_view_window_scroll_child (ModestMsgViewWindow *self,
324                                      GtkScrollType scroll_type,
325                                      gboolean horizontal,
326                                      gpointer userdata)
327 {
328         ModestMsgViewWindowPrivate *priv;
329         gint step = 0;
330
331         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
332
333         switch (scroll_type) {
334         case GTK_SCROLL_STEP_UP:
335                 step = -1;
336                 break;
337         case GTK_SCROLL_STEP_DOWN:
338                 step = +1;
339                 break;
340         case GTK_SCROLL_PAGE_UP:
341                 step = -6;
342                 break;
343         case GTK_SCROLL_PAGE_DOWN:
344                 step = +6;
345                 break;
346         case GTK_SCROLL_START:
347                 step = -100;
348                 break;
349         case GTK_SCROLL_END:
350                 step = +100;
351                 break;
352         default:
353                 step = 0;
354         }
355
356         if (step)
357                 modest_scrollable_scroll ((ModestScrollable *) priv->main_scroll, 0, step);
358
359         return (gboolean) step;
360 }
361
362 static void
363 add_scroll_binding (GtkBindingSet *binding_set,
364                     guint keyval,
365                     GtkScrollType scroll)
366 {
367         guint keypad_keyval = keyval - GDK_Left + GDK_KP_Left;
368
369         gtk_binding_entry_add_signal (binding_set, keyval, 0,
370                                       "scroll_child", 2,
371                                       GTK_TYPE_SCROLL_TYPE, scroll,
372                                       G_TYPE_BOOLEAN, FALSE);
373         gtk_binding_entry_add_signal (binding_set, keypad_keyval, 0,
374                                       "scroll_child", 2,
375                                       GTK_TYPE_SCROLL_TYPE, scroll,
376                                       G_TYPE_BOOLEAN, FALSE);
377 }
378
379 static void
380 modest_msg_view_window_class_init (ModestMsgViewWindowClass *klass)
381 {
382         GObjectClass *gobject_class;
383         ModestWindowClass *modest_window_class;
384         GtkBindingSet *binding_set;
385
386         gobject_class = (GObjectClass*) klass;
387         modest_window_class = (ModestWindowClass *) klass;
388
389         parent_class            = g_type_class_peek_parent (klass);
390         gobject_class->finalize = modest_msg_view_window_finalize;
391
392         modest_window_class->set_zoom_func = modest_msg_view_window_set_zoom;
393         modest_window_class->get_zoom_func = modest_msg_view_window_get_zoom;
394         modest_window_class->zoom_plus_func = modest_msg_view_window_zoom_plus;
395         modest_window_class->zoom_minus_func = modest_msg_view_window_zoom_minus;
396         modest_window_class->show_toolbar_func = modest_msg_view_window_show_toolbar;
397         modest_window_class->disconnect_signals_func = modest_msg_view_window_disconnect_signals;
398
399         modest_window_class->save_state_func = save_state;
400
401         klass->scroll_child = modest_msg_view_window_scroll_child;
402
403         signals[MSG_CHANGED_SIGNAL] =
404                 g_signal_new ("msg-changed",
405                               G_TYPE_FROM_CLASS (gobject_class),
406                               G_SIGNAL_RUN_FIRST,
407                               G_STRUCT_OFFSET (ModestMsgViewWindowClass, msg_changed),
408                               NULL, NULL,
409                               modest_marshal_VOID__POINTER_POINTER,
410                               G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER);
411
412         signals[SCROLL_CHILD_SIGNAL] =
413                 g_signal_new ("scroll-child",
414                               G_TYPE_FROM_CLASS (gobject_class),
415                               G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
416                               G_STRUCT_OFFSET (ModestMsgViewWindowClass, scroll_child),
417                               NULL, NULL,
418                               modest_marshal_BOOLEAN__ENUM_BOOLEAN,
419                               G_TYPE_BOOLEAN, 2, GTK_TYPE_SCROLL_TYPE, G_TYPE_BOOLEAN);
420
421         binding_set = gtk_binding_set_by_class (klass);
422         add_scroll_binding (binding_set, GDK_Up, GTK_SCROLL_STEP_UP);
423         add_scroll_binding (binding_set, GDK_Down, GTK_SCROLL_STEP_DOWN);
424         add_scroll_binding (binding_set, GDK_Page_Up, GTK_SCROLL_PAGE_UP);
425         add_scroll_binding (binding_set, GDK_Page_Down, GTK_SCROLL_PAGE_DOWN);
426         add_scroll_binding (binding_set, GDK_Home, GTK_SCROLL_START);
427         add_scroll_binding (binding_set, GDK_End, GTK_SCROLL_END);
428
429         g_type_class_add_private (gobject_class, sizeof(ModestMsgViewWindowPrivate));
430
431 }
432
433 static void modest_header_view_observer_init(
434                 ModestHeaderViewObserverIface *iface_class)
435 {
436         iface_class->update_func = modest_msg_view_window_update_model_replaced;
437 }
438
439 static void
440 modest_msg_view_window_init (ModestMsgViewWindow *obj)
441 {
442         ModestMsgViewWindowPrivate *priv;
443         ModestWindowPrivate *parent_priv = NULL;
444         GtkActionGroup *action_group = NULL;
445         GError *error = NULL;
446
447         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(obj);
448         parent_priv = MODEST_WINDOW_GET_PRIVATE(obj);
449         parent_priv->ui_manager = gtk_ui_manager_new();
450
451         action_group = gtk_action_group_new ("ModestMsgViewWindowActions");
452         gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE);
453
454         /* Add common actions */
455         gtk_action_group_add_actions (action_group,
456                                       msg_view_toolbar_action_entries,
457                                       G_N_ELEMENTS (msg_view_toolbar_action_entries),
458                                       obj);
459         gtk_action_group_add_toggle_actions (action_group,
460                                              msg_view_toggle_action_entries,
461                                              G_N_ELEMENTS (msg_view_toggle_action_entries),
462                                              obj);
463
464         gtk_ui_manager_insert_action_group (parent_priv->ui_manager, action_group, 0);
465         g_object_unref (action_group);
466
467         /* Load the UI definition */
468         gtk_ui_manager_add_ui_from_file (parent_priv->ui_manager, MODEST_UIDIR "modest-msg-view-window-ui.xml",
469                                          &error);
470         if (error) {
471                 g_printerr ("modest: could not merge modest-msg-view-window-ui.xml: %s\n", error->message);
472                 g_error_free (error);
473                 error = NULL;
474         }
475         
476         priv->is_search_result = FALSE;
477         priv->is_outbox = FALSE;
478
479         priv->msg_view      = NULL;
480         priv->header_model  = NULL;
481         priv->header_folder_id  = NULL;
482         priv->clipboard_change_handler = 0;
483         priv->queue_change_handler = 0;
484         priv->account_removed_handler = 0;
485         priv->row_changed_handler = 0;
486         priv->row_deleted_handler = 0;
487         priv->row_inserted_handler = 0;
488         priv->rows_reordered_handler = 0;
489         priv->progress_hint = FALSE;
490         priv->fetching_images = 0;
491
492         priv->optimized_view  = FALSE;
493         priv->purge_timeout = 0;
494         priv->remove_attachment_banner = NULL;
495         priv->msg_uid = NULL;
496         priv->other_body = NULL;
497         
498         priv->sighandlers = NULL;
499         
500         /* Init window */
501         init_window (MODEST_MSG_VIEW_WINDOW(obj));
502
503 }
504
505 static void
506 update_progress_hint (ModestMsgViewWindow *self)
507 {
508         ModestMsgViewWindowPrivate *priv;
509         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
510
511         if (GTK_WIDGET_VISIBLE (self)) {
512                 modest_window_show_progress (MODEST_WINDOW (self),
513                                              (priv->progress_hint || (priv->fetching_images > 0))?1:0);
514         }
515 }
516
517 static void 
518 set_progress_hint (ModestMsgViewWindow *self, 
519                    gboolean enabled)
520 {
521         ModestWindowPrivate *parent_priv;
522         ModestMsgViewWindowPrivate *priv;
523
524         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self));
525
526         parent_priv = MODEST_WINDOW_GET_PRIVATE(self);
527         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
528                         
529         /* Sets current progress hint */
530         priv->progress_hint = enabled;
531
532         update_progress_hint (self);
533
534 }
535
536
537 static void
538 init_window (ModestMsgViewWindow *obj)
539 {
540         GtkWidget *main_vbox;
541         ModestMsgViewWindowPrivate *priv;
542
543         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(obj);
544
545         priv->msg_view = GTK_WIDGET (tny_platform_factory_new_msg_view (modest_tny_platform_factory_get_instance ()));
546         modest_msg_view_set_shadow_type (MODEST_MSG_VIEW (priv->msg_view), GTK_SHADOW_NONE);
547         main_vbox = gtk_vbox_new  (FALSE, 6);
548
549         priv->main_scroll = modest_toolkit_factory_create_scrollable (modest_runtime_get_toolkit_factory ());
550         modest_scrollable_set_horizontal_policy (MODEST_SCROLLABLE (priv->main_scroll), GTK_POLICY_AUTOMATIC);
551         g_object_set (G_OBJECT (priv->main_scroll),
552                       "movement-mode", MODEST_MOVEMENT_MODE_BOTH,
553                       "horizontal-max-overshoot", 0,
554                       NULL);
555         gtk_container_add (GTK_CONTAINER (priv->main_scroll), priv->msg_view);
556         gtk_box_pack_start (GTK_BOX(main_vbox), priv->main_scroll, TRUE, TRUE, 0);
557         gtk_container_add   (GTK_CONTAINER(obj), main_vbox);
558
559         /* NULL-ize fields if the window is destroyed */
560         g_signal_connect (priv->msg_view, "destroy", G_CALLBACK (gtk_widget_destroyed), &(priv->msg_view));
561
562         gtk_widget_show_all (GTK_WIDGET(main_vbox));
563 }
564
565 static void
566 modest_msg_view_window_disconnect_signals (ModestWindow *self)
567 {
568         ModestMsgViewWindowPrivate *priv;
569         GtkWidget *header_view = NULL;
570         GtkWindow *parent_window = NULL;
571         
572         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
573
574         if (gtk_clipboard_get (GDK_SELECTION_PRIMARY) &&
575             g_signal_handler_is_connected (gtk_clipboard_get (GDK_SELECTION_PRIMARY),
576                                            priv->clipboard_change_handler)) 
577                 g_signal_handler_disconnect (gtk_clipboard_get (GDK_SELECTION_PRIMARY), 
578                                              priv->clipboard_change_handler);
579
580         if (g_signal_handler_is_connected (G_OBJECT (modest_runtime_get_mail_operation_queue ()), 
581                                            priv->queue_change_handler))
582                 g_signal_handler_disconnect (G_OBJECT (modest_runtime_get_mail_operation_queue ()), 
583                                              priv->queue_change_handler);
584
585         if (g_signal_handler_is_connected (G_OBJECT (modest_runtime_get_account_store ()), 
586                                            priv->account_removed_handler))
587                 g_signal_handler_disconnect (G_OBJECT (modest_runtime_get_account_store ()), 
588                                              priv->account_removed_handler);
589
590         if (priv->header_model) {
591                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
592                                                   priv->row_changed_handler))
593                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
594                                                     priv->row_changed_handler);
595                 
596                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
597                                                   priv->row_deleted_handler))
598                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
599                                              priv->row_deleted_handler);
600                 
601                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
602                                                   priv->row_inserted_handler))
603                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
604                                                     priv->row_inserted_handler);
605                 
606                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
607                                                   priv->rows_reordered_handler))
608                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
609                                                     priv->rows_reordered_handler);
610         }
611
612         modest_signal_mgr_disconnect_all_and_destroy (priv->sighandlers);
613         priv->sighandlers = NULL;
614
615         parent_window = gtk_window_get_transient_for (GTK_WINDOW (self));
616         if (parent_window && MODEST_IS_HEADER_WINDOW (parent_window)) {
617                 header_view = GTK_WIDGET (modest_header_window_get_header_view (MODEST_HEADER_WINDOW (parent_window)));
618                 if (header_view) {
619                         modest_header_view_remove_observer(MODEST_HEADER_VIEW (header_view),
620                                                            MODEST_HEADER_VIEW_OBSERVER(self));
621                 }
622         }
623 }
624
625 static void
626 modest_msg_view_window_finalize (GObject *obj)
627 {
628         ModestMsgViewWindowPrivate *priv;
629
630         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (obj);
631
632         /* Sanity check: shouldn't be needed, the window mgr should
633            call this function before */
634         modest_msg_view_window_disconnect_signals (MODEST_WINDOW (obj));
635
636         if (priv->other_body != NULL) {
637                 g_object_unref (priv->other_body);
638                 priv->other_body = NULL;
639         }
640
641         if (priv->header_model != NULL) {
642                 g_object_unref (priv->header_model);
643                 priv->header_model = NULL;
644         }
645
646         if (priv->remove_attachment_banner) {
647                 gtk_widget_destroy (priv->remove_attachment_banner);
648                 g_object_unref (priv->remove_attachment_banner);
649                 priv->remove_attachment_banner = NULL;
650         }
651
652         if (priv->purge_timeout > 0) {
653                 g_source_remove (priv->purge_timeout);
654                 priv->purge_timeout = 0;
655         }
656
657         if (priv->row_reference) {
658                 gtk_tree_row_reference_free (priv->row_reference);
659                 priv->row_reference = NULL;
660         }
661
662         if (priv->next_row_reference) {
663                 gtk_tree_row_reference_free (priv->next_row_reference);
664                 priv->next_row_reference = NULL;
665         }
666
667         if (priv->msg_uid) {
668                 g_free (priv->msg_uid);
669                 priv->msg_uid = NULL;
670         }
671
672         G_OBJECT_CLASS(parent_class)->finalize (obj);
673 }
674
675 static gboolean
676 select_next_valid_row (GtkTreeModel *model,
677                        GtkTreeRowReference **row_reference,
678                        gboolean cycle,
679                        gboolean is_outbox)
680 {
681         GtkTreeIter tmp_iter;
682         GtkTreePath *path;
683         GtkTreePath *next = NULL;
684         gboolean retval = FALSE, finished;
685
686         g_return_val_if_fail (gtk_tree_row_reference_valid (*row_reference), FALSE);
687
688         path = gtk_tree_row_reference_get_path (*row_reference);
689         gtk_tree_model_get_iter (model, &tmp_iter, path);
690         gtk_tree_row_reference_free (*row_reference);
691         *row_reference = NULL;
692
693         finished = FALSE;
694         do {
695                 TnyHeader *header = NULL;
696
697                 if (gtk_tree_model_iter_next (model, &tmp_iter)) {
698                         gtk_tree_model_get (model, &tmp_iter, 
699                                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, 
700                                             &header, -1);
701
702                         if (header) {
703                                 if (msg_is_visible (header, is_outbox)) {
704                                         next = gtk_tree_model_get_path (model, &tmp_iter);
705                                         *row_reference = gtk_tree_row_reference_new (model, next);
706                                         gtk_tree_path_free (next);
707                                         retval = TRUE;
708                                         finished = TRUE;
709                                 }
710                                 g_object_unref (header);
711                                 header = NULL;
712                         }
713                 } else if (cycle && gtk_tree_model_get_iter_first (model, &tmp_iter)) {
714                         next = gtk_tree_model_get_path (model, &tmp_iter);
715                         
716                         /* Ensure that we are not selecting the same */
717                         if (gtk_tree_path_compare (path, next) != 0) {
718                                 gtk_tree_model_get (model, &tmp_iter, 
719                                                     TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, 
720                                                     &header, -1);                               
721                                 if (header) {
722                                         if (msg_is_visible (header, is_outbox)) {
723                                                 *row_reference = gtk_tree_row_reference_new (model, next);
724                                                 retval = TRUE;
725                                                 finished = TRUE;
726                                         }
727                                         g_object_unref (header);
728                                         header = NULL;
729                                 }
730                         } else {
731                                 /* If we ended up in the same message
732                                    then there is no valid next
733                                    message */
734                                 finished = TRUE;
735                         }
736                         gtk_tree_path_free (next);
737                 } else {
738                         /* If there are no more messages and we don't
739                            want to start again in the first one then
740                            there is no valid next message */
741                         finished = TRUE;
742                 }
743         } while (!finished);
744
745         /* Free */
746         gtk_tree_path_free (path);
747
748         return retval;
749 }
750
751 /* TODO: This should be in _init(), with the parameters as properties. */
752 static void
753 modest_msg_view_window_construct (ModestMsgViewWindow *self, 
754                                   const gchar *modest_account_name,
755                                   const gchar *mailbox,
756                                   const gchar *msg_uid)
757 {
758         GObject *obj = NULL;
759         ModestMsgViewWindowPrivate *priv = NULL;
760         ModestWindowPrivate *parent_priv = NULL;
761         ModestDimmingRulesGroup *toolbar_rules_group = NULL;
762         ModestDimmingRulesGroup *clipboard_rules_group = NULL;
763
764         obj = G_OBJECT (self);
765         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(obj);
766         parent_priv = MODEST_WINDOW_GET_PRIVATE(obj);
767
768         priv->msg_uid = g_strdup (msg_uid);
769
770         /* Menubar */
771         parent_priv->menubar = NULL;
772
773         toolbar_rules_group = modest_dimming_rules_group_new (MODEST_DIMMING_RULES_TOOLBAR, TRUE);
774         clipboard_rules_group = modest_dimming_rules_group_new (MODEST_DIMMING_RULES_CLIPBOARD, FALSE);
775
776         setup_menu (self);
777         /* Add common dimming rules */
778         modest_dimming_rules_group_add_rules (toolbar_rules_group, 
779                                               modest_msg_view_toolbar_dimming_entries,
780                                               G_N_ELEMENTS (modest_msg_view_toolbar_dimming_entries),
781                                               MODEST_WINDOW (self));
782         modest_dimming_rules_group_add_rules (clipboard_rules_group, 
783                                               modest_msg_view_clipboard_dimming_entries,
784                                               G_N_ELEMENTS (modest_msg_view_clipboard_dimming_entries),
785                                               MODEST_WINDOW (self));
786
787         /* Insert dimming rules group for this window */
788         modest_ui_dimming_manager_insert_rules_group (parent_priv->ui_dimming_manager, toolbar_rules_group);
789         modest_ui_dimming_manager_insert_rules_group (parent_priv->ui_dimming_manager, clipboard_rules_group);
790         g_object_unref (toolbar_rules_group);
791         g_object_unref (clipboard_rules_group);
792
793         /* g_signal_connect (G_OBJECT(obj), "delete-event", G_CALLBACK(on_delete_event), obj); */
794
795         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);
796         g_signal_connect (G_OBJECT(priv->msg_view), "activate_link",
797                           G_CALLBACK (modest_ui_actions_on_msg_link_clicked), obj);
798         g_signal_connect (G_OBJECT(priv->msg_view), "link_hover",
799                           G_CALLBACK (modest_ui_actions_on_msg_link_hover), obj);
800         g_signal_connect (G_OBJECT(priv->msg_view), "attachment_clicked",
801                           G_CALLBACK (modest_ui_actions_on_msg_attachment_clicked), obj);
802         g_signal_connect (G_OBJECT(priv->msg_view), "recpt_activated",
803                           G_CALLBACK (modest_ui_actions_on_msg_recpt_activated), obj);
804         g_signal_connect (G_OBJECT(priv->msg_view), "show_details",
805                           G_CALLBACK (modest_ui_actions_on_details), obj);
806         g_signal_connect (G_OBJECT(priv->msg_view), "link_contextual",
807                           G_CALLBACK (modest_ui_actions_on_msg_link_contextual), obj);
808         g_signal_connect (G_OBJECT(priv->msg_view), "limit_error",
809                           G_CALLBACK (modest_ui_actions_on_limit_error), obj);
810         g_signal_connect (G_OBJECT (priv->msg_view), "fetch_image",
811                           G_CALLBACK (on_fetch_image), obj);
812
813         g_signal_connect (G_OBJECT (obj), "key-release-event",
814                           G_CALLBACK (modest_msg_view_window_key_event),
815                           NULL);
816
817         g_signal_connect (G_OBJECT (obj), "key-press-event",
818                           G_CALLBACK (modest_msg_view_window_key_event),
819                           NULL);
820
821         g_signal_connect (G_OBJECT (obj), "move-focus",
822                           G_CALLBACK (on_move_focus), obj);
823
824         g_signal_connect (G_OBJECT (obj), "map-event",
825                           G_CALLBACK (_modest_msg_view_window_map_event),
826                           G_OBJECT (obj));
827
828         /* Mail Operation Queue */
829         priv->queue_change_handler = g_signal_connect (G_OBJECT (modest_runtime_get_mail_operation_queue ()),
830                                                        "queue-changed",
831                                                        G_CALLBACK (on_queue_changed),
832                                                        obj);
833
834         /* Account manager */
835         priv->account_removed_handler = g_signal_connect (G_OBJECT (modest_runtime_get_account_store ()),
836                                                           "account_removed",
837                                                           G_CALLBACK(on_account_removed),
838                                                           obj);
839
840         modest_window_set_active_account (MODEST_WINDOW(obj), modest_account_name);
841         modest_window_set_active_mailbox (MODEST_WINDOW(obj), mailbox);
842
843         /* First add out toolbar ... */
844         modest_msg_view_window_show_toolbar (MODEST_WINDOW (obj), TRUE);
845
846         priv->isearch_toolbar = modest_toolkit_factory_create_isearch_toolbar (modest_runtime_get_toolkit_factory (),
847                                                                                NULL);
848         modest_window_add_toolbar (MODEST_WINDOW (obj), GTK_TOOLBAR (priv->isearch_toolbar));
849         gtk_widget_set_no_show_all (priv->isearch_toolbar, TRUE);
850         g_signal_connect (G_OBJECT (priv->isearch_toolbar), "isearch-close", 
851                           G_CALLBACK (modest_msg_view_window_isearch_toolbar_close), obj);
852         g_signal_connect (G_OBJECT (priv->isearch_toolbar), "isearch-search", 
853                           G_CALLBACK (modest_msg_view_window_isearch_toolbar_search), obj);
854         priv->last_search = NULL;
855
856         /* Init the clipboard actions dim status */
857         modest_msg_view_grab_focus(MODEST_MSG_VIEW (priv->msg_view));
858
859         update_window_title (MODEST_MSG_VIEW_WINDOW (obj));
860
861
862 }
863
864 /* FIXME: parameter checks */
865 ModestWindow *
866 modest_msg_view_window_new_with_header_model (TnyMsg *msg, 
867                                               const gchar *modest_account_name,
868                                               const gchar *mailbox,
869                                               const gchar *msg_uid,
870                                               GtkTreeModel *model, 
871                                               GtkTreeRowReference *row_reference)
872 {
873         ModestMsgViewWindow *window = NULL;
874         ModestMsgViewWindowPrivate *priv = NULL;
875         TnyFolder *header_folder = NULL;
876         ModestHeaderView *header_view = NULL;
877         ModestWindowMgr *mgr = NULL;
878
879         MODEST_DEBUG_BLOCK (
880                modest_tny_mime_part_to_string (TNY_MIME_PART (msg), 0);
881         );
882
883         mgr = modest_runtime_get_window_mgr ();
884         window = MODEST_MSG_VIEW_WINDOW (modest_window_mgr_get_msg_view_window (mgr));
885         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), NULL);
886
887         modest_msg_view_window_construct (window, modest_account_name, mailbox, msg_uid);
888
889         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
890
891         /* Remember the message list's TreeModel so we can detect changes
892          * and change the list selection when necessary: */
893         header_folder = modest_header_view_get_folder (header_view);
894         if (header_folder) {
895                 priv->is_outbox = (modest_tny_folder_guess_folder_type (header_folder) ==
896                                    TNY_FOLDER_TYPE_OUTBOX);
897                 priv->header_folder_id = tny_folder_get_id (header_folder);
898                 g_object_unref(header_folder);
899         }
900
901         /* Setup row references and connect signals */
902         priv->header_model = g_object_ref (model);
903
904         if (row_reference && gtk_tree_row_reference_valid (row_reference)) {
905                 priv->row_reference = gtk_tree_row_reference_copy (row_reference);
906                 priv->next_row_reference = gtk_tree_row_reference_copy (row_reference);
907                 select_next_valid_row (model, &(priv->next_row_reference), TRUE, priv->is_outbox);
908         } else {
909                 priv->row_reference = NULL;
910                 priv->next_row_reference = NULL;
911         }
912
913         /* Connect signals */
914         priv->row_changed_handler = 
915                 g_signal_connect (GTK_TREE_MODEL(model), "row-changed",
916                                   G_CALLBACK(modest_msg_view_window_on_row_changed),
917                                   window);
918         priv->row_deleted_handler = 
919                 g_signal_connect (GTK_TREE_MODEL(model), "row-deleted",
920                                   G_CALLBACK(modest_msg_view_window_on_row_deleted),
921                                   window);
922         priv->row_inserted_handler = 
923                 g_signal_connect (GTK_TREE_MODEL(model), "row-inserted",
924                                   G_CALLBACK(modest_msg_view_window_on_row_inserted),
925                                   window);
926         priv->rows_reordered_handler = 
927                 g_signal_connect(GTK_TREE_MODEL(model), "rows-reordered",
928                                  G_CALLBACK(modest_msg_view_window_on_row_reordered),
929                                  window);
930
931         if (header_view != NULL){
932                 modest_header_view_add_observer(header_view,
933                                 MODEST_HEADER_VIEW_OBSERVER(window));
934         }
935
936         tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
937         update_window_title (MODEST_MSG_VIEW_WINDOW (window));
938         update_branding (MODEST_MSG_VIEW_WINDOW (window));
939
940         /* gtk_widget_show_all (GTK_WIDGET (window)); */
941         modest_msg_view_window_update_priority (window);
942         /* Check dimming rules */
943         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (window));
944         modest_ui_actions_check_menu_dimming_rules (MODEST_WINDOW (window));
945         modest_window_check_dimming_rules_group (MODEST_WINDOW (window), MODEST_DIMMING_RULES_CLIPBOARD);
946
947         return MODEST_WINDOW(window);
948 }
949
950 ModestWindow *
951 modest_msg_view_window_new_from_uid (const gchar *modest_account_name,
952                                      const gchar *mailbox,
953                                      const gchar *msg_uid)
954 {
955         ModestMsgViewWindow *window = NULL;
956         ModestMsgViewWindowPrivate *priv = NULL;
957         ModestWindowMgr *mgr = NULL;
958         gboolean is_merge;
959         TnyAccount *account = NULL;
960
961         mgr = modest_runtime_get_window_mgr ();
962         window = MODEST_MSG_VIEW_WINDOW (modest_window_mgr_get_msg_view_window (mgr));
963         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), NULL);
964
965         modest_msg_view_window_construct (window, modest_account_name, mailbox, msg_uid);
966
967         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
968
969         is_merge = g_str_has_prefix (msg_uid, "merge:");
970
971         /* Get the account */
972         if (!is_merge)
973                 account = tny_account_store_find_account (TNY_ACCOUNT_STORE (modest_runtime_get_account_store ()),
974                                                           msg_uid);
975
976         if (is_merge || account) {
977                 TnyFolder *folder = NULL;
978
979                 /* Try to get the message, if it's already downloaded
980                    we don't need to connect */
981                 if (account) {
982                         folder = modest_tny_folder_store_find_folder_from_uri (TNY_FOLDER_STORE (account), msg_uid);
983                 } else {
984                         ModestTnyAccountStore *account_store;
985                         ModestTnyLocalFoldersAccount *local_folders_account;
986
987                         account_store = modest_runtime_get_account_store ();
988                         local_folders_account = MODEST_TNY_LOCAL_FOLDERS_ACCOUNT (
989                                 modest_tny_account_store_get_local_folders_account (account_store));
990                         folder = modest_tny_local_folders_account_get_merged_outbox (local_folders_account);
991                         g_object_unref (local_folders_account);
992                 }
993                 if (folder) {
994                         TnyDevice *device;
995                         gboolean device_online;
996
997                         device = modest_runtime_get_device();
998                         device_online = tny_device_is_online (device);
999                         if (device_online) {
1000                                 message_reader (window, priv, NULL, msg_uid, folder, NULL);
1001                         } else {
1002                                 TnyMsg *msg = tny_folder_find_msg (folder, msg_uid, NULL);
1003                                 if (msg) {
1004                                         tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
1005                                         update_window_title (MODEST_MSG_VIEW_WINDOW (window));
1006                                         update_branding (MODEST_MSG_VIEW_WINDOW (window));
1007                                         g_object_unref (msg);
1008                                         /* Sync flags to server */
1009                                         sync_flags (MODEST_MSG_VIEW_WINDOW (window));
1010                                 } else {
1011                                         message_reader (window, priv, NULL, msg_uid, folder, NULL);
1012                                 }
1013                         }
1014                         g_object_unref (folder);
1015                 }
1016
1017         }
1018
1019         /* Check dimming rules */
1020         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (window));
1021         modest_ui_actions_check_menu_dimming_rules (MODEST_WINDOW (window));
1022         modest_window_check_dimming_rules_group (MODEST_WINDOW (window), MODEST_DIMMING_RULES_CLIPBOARD);
1023
1024         return MODEST_WINDOW(window);
1025 }
1026
1027 ModestWindow *
1028 modest_msg_view_window_new_from_header_view (ModestHeaderView *header_view,
1029                                              const gchar *modest_account_name,
1030                                              const gchar *mailbox,
1031                                              const gchar *msg_uid,
1032                                              GtkTreeRowReference *row_reference)
1033 {
1034         ModestMsgViewWindow *window = NULL;
1035         ModestMsgViewWindowPrivate *priv = NULL;
1036         TnyFolder *header_folder = NULL;
1037         ModestWindowMgr *mgr = NULL;
1038         GtkTreePath *path;
1039         GtkTreeIter iter;
1040
1041         mgr = modest_runtime_get_window_mgr ();
1042         window = MODEST_MSG_VIEW_WINDOW (modest_window_mgr_get_msg_view_window (mgr));
1043         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), NULL);
1044
1045         modest_msg_view_window_construct (window, modest_account_name, mailbox, msg_uid);
1046
1047         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1048
1049         /* Remember the message list's TreeModel so we can detect changes 
1050          * and change the list selection when necessary: */
1051
1052         if (header_view != NULL){
1053                 header_folder = modest_header_view_get_folder(header_view);
1054                 /* This could happen if the header folder was
1055                    unseleted before opening this msg window (for
1056                    example if the user selects an account in the
1057                    folder view of the main window */
1058                 if (header_folder) {
1059                         priv->is_outbox = (modest_tny_folder_guess_folder_type (header_folder) == 
1060                                            TNY_FOLDER_TYPE_OUTBOX);
1061                         priv->header_folder_id = tny_folder_get_id(header_folder);
1062                         g_object_unref(header_folder);
1063                 }
1064         }
1065
1066         /* Setup row references and connect signals */
1067         priv->header_model = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
1068         g_object_ref (priv->header_model);
1069
1070         if (row_reference && gtk_tree_row_reference_valid (row_reference)) {
1071                 priv->row_reference = gtk_tree_row_reference_copy (row_reference);
1072                 priv->next_row_reference = gtk_tree_row_reference_copy (row_reference);
1073                 select_next_valid_row (priv->header_model, &(priv->next_row_reference), TRUE, priv->is_outbox);
1074         } else {
1075                 priv->row_reference = NULL;
1076                 priv->next_row_reference = NULL;
1077         }
1078
1079         /* Connect signals */
1080         priv->row_changed_handler = 
1081                 g_signal_connect (GTK_TREE_MODEL(priv->header_model), "row-changed",
1082                                   G_CALLBACK(modest_msg_view_window_on_row_changed),
1083                                   window);
1084         priv->row_deleted_handler = 
1085                 g_signal_connect (GTK_TREE_MODEL(priv->header_model), "row-deleted",
1086                                   G_CALLBACK(modest_msg_view_window_on_row_deleted),
1087                                   window);
1088         priv->row_inserted_handler = 
1089                 g_signal_connect (GTK_TREE_MODEL(priv->header_model), "row-inserted",
1090                                   G_CALLBACK(modest_msg_view_window_on_row_inserted),
1091                                   window);
1092         priv->rows_reordered_handler = 
1093                 g_signal_connect(GTK_TREE_MODEL(priv->header_model), "rows-reordered",
1094                                  G_CALLBACK(modest_msg_view_window_on_row_reordered),
1095                                  window);
1096
1097         if (header_view != NULL){
1098                 modest_header_view_add_observer(header_view,
1099                                                 MODEST_HEADER_VIEW_OBSERVER(window));
1100         }
1101
1102         tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), NULL);
1103         update_branding (MODEST_MSG_VIEW_WINDOW (window));
1104
1105         if (priv->row_reference) {
1106                 path = gtk_tree_row_reference_get_path (priv->row_reference);
1107                 if (gtk_tree_model_get_iter (priv->header_model, &iter, path)) {
1108                         TnyHeader *header;
1109                         gtk_tree_model_get (priv->header_model, &iter, 
1110                                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1111                                             &header, -1);
1112                         message_reader (window, priv, header, NULL, NULL, priv->row_reference);
1113                         g_object_unref (header);
1114                 }
1115                 gtk_tree_path_free (path);
1116         }
1117         /* Check dimming rules */
1118         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (window));
1119         modest_ui_actions_check_menu_dimming_rules (MODEST_WINDOW (window));
1120         modest_window_check_dimming_rules_group (MODEST_WINDOW (window), MODEST_DIMMING_RULES_CLIPBOARD);
1121
1122         return MODEST_WINDOW(window);
1123 }
1124
1125 ModestWindow *
1126 modest_msg_view_window_new_for_search_result (TnyMsg *msg,
1127                                               const gchar *modest_account_name,
1128                                               const gchar *mailbox,
1129                                               const gchar *msg_uid)
1130 {
1131         ModestMsgViewWindow *window = NULL;
1132         ModestMsgViewWindowPrivate *priv = NULL;
1133         ModestWindowMgr *mgr = NULL;
1134
1135         mgr = modest_runtime_get_window_mgr ();
1136         window = MODEST_MSG_VIEW_WINDOW (modest_window_mgr_get_msg_view_window (mgr));
1137         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), NULL);
1138         modest_msg_view_window_construct (window, modest_account_name, mailbox, msg_uid);
1139
1140         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1141
1142         /* Remember that this is a search result, 
1143          * so we can disable some UI appropriately: */
1144         priv->is_search_result = TRUE;
1145
1146         tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
1147         update_branding (MODEST_MSG_VIEW_WINDOW (window));
1148         
1149         update_window_title (window);
1150         /* gtk_widget_show_all (GTK_WIDGET (window));*/
1151         modest_msg_view_window_update_priority (window);
1152
1153         /* Check dimming rules */
1154         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (window));
1155         modest_ui_actions_check_menu_dimming_rules (MODEST_WINDOW (window));
1156         modest_window_check_dimming_rules_group (MODEST_WINDOW (window), MODEST_DIMMING_RULES_CLIPBOARD);
1157
1158         return MODEST_WINDOW(window);
1159 }
1160
1161 gboolean
1162 modest_msg_view_window_is_other_body (ModestMsgViewWindow *self)
1163 {
1164         ModestMsgViewWindowPrivate *priv = NULL;
1165
1166         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), FALSE);
1167         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
1168
1169         return (priv->other_body != NULL);
1170 }
1171
1172 ModestWindow *
1173 modest_msg_view_window_new_with_other_body (TnyMsg *msg, 
1174                                             TnyMimePart *other_body,
1175                                             const gchar *modest_account_name,
1176                                             const gchar *mailbox,
1177                                             const gchar *msg_uid)
1178 {
1179         GObject *obj = NULL;
1180         ModestMsgViewWindowPrivate *priv;       
1181         ModestWindowMgr *mgr = NULL;
1182
1183         g_return_val_if_fail (msg, NULL);
1184         mgr = modest_runtime_get_window_mgr ();
1185         obj = G_OBJECT (modest_window_mgr_get_msg_view_window (mgr));
1186         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (obj);
1187         modest_msg_view_window_construct (MODEST_MSG_VIEW_WINDOW (obj), 
1188                                           modest_account_name, mailbox, msg_uid);
1189
1190         if (other_body) {
1191                 priv->other_body = g_object_ref (other_body);
1192                 modest_msg_view_set_msg_with_other_body (MODEST_MSG_VIEW (priv->msg_view), msg, other_body);
1193         } else {
1194                 tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
1195         }
1196         update_window_title (MODEST_MSG_VIEW_WINDOW (obj));
1197         update_branding (MODEST_MSG_VIEW_WINDOW (obj));
1198
1199         /* gtk_widget_show_all (GTK_WIDGET (obj)); */
1200
1201         /* Check dimming rules */
1202         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (obj));
1203         modest_ui_actions_check_menu_dimming_rules (MODEST_WINDOW (obj));
1204         modest_window_check_dimming_rules_group (MODEST_WINDOW (obj), MODEST_DIMMING_RULES_CLIPBOARD);
1205
1206         return MODEST_WINDOW(obj);
1207 }
1208
1209 ModestWindow *
1210 modest_msg_view_window_new_for_attachment (TnyMsg *msg, 
1211                                            const gchar *modest_account_name,
1212                                            const gchar *mailbox,
1213                                            const gchar *msg_uid)
1214 {
1215         return modest_msg_view_window_new_with_other_body (msg, NULL, modest_account_name, mailbox, msg_uid);
1216 }
1217
1218 static void
1219 modest_msg_view_window_on_row_changed (GtkTreeModel *header_model,
1220                                        GtkTreePath *arg1,
1221                                        GtkTreeIter *arg2,
1222                                        ModestMsgViewWindow *window)
1223 {
1224         check_dimming_rules_after_change (window);
1225 }
1226
1227 static void 
1228 modest_msg_view_window_on_row_deleted(GtkTreeModel *header_model,
1229                                       GtkTreePath *arg1,
1230                                       ModestMsgViewWindow *window)
1231 {
1232         check_dimming_rules_after_change (window);
1233 }
1234         /* The window could have dissapeared */
1235
1236 static void
1237 check_dimming_rules_after_change (ModestMsgViewWindow *window)
1238 {
1239         modest_ui_actions_check_menu_dimming_rules (MODEST_WINDOW (window));
1240         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (window));
1241 }
1242
1243
1244 /* On insertions we check if the folder still has the message we are
1245  * showing or do not. If do not, we do nothing. Which means we are still
1246  * not attached to any header folder and thus next/prev buttons are
1247  * still dimmed. Once the message that is shown by msg-view is found, the
1248  * new model of header-view will be attached and the references will be set.
1249  * On each further insertions dimming rules will be checked. However
1250  * this requires extra CPU time at least works.
1251  * (An message might be deleted from TnyFolder and thus will not be
1252  * inserted into the model again for example if it is removed by the
1253  * imap server and the header view is refreshed.)
1254  */
1255 static void 
1256 modest_msg_view_window_on_row_inserted (GtkTreeModel *model,
1257                                         GtkTreePath *tree_path,
1258                                         GtkTreeIter *tree_iter,
1259                                         ModestMsgViewWindow *window)
1260 {
1261         ModestMsgViewWindowPrivate *priv = NULL; 
1262         TnyHeader *header = NULL;
1263
1264         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
1265         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1266
1267         g_assert (model == priv->header_model);
1268         
1269         /* Check if the newly inserted message is the same we are actually
1270          * showing. IF not, we should remain detached from the header model
1271          * and thus prev and next toolbar buttons should remain dimmed. */
1272         gtk_tree_model_get (model, tree_iter, 
1273                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, 
1274                             &header, -1);
1275
1276         if (TNY_IS_HEADER (header)) {
1277                 gchar *uid = NULL;
1278
1279                 uid = modest_tny_folder_get_header_unique_id (header);
1280                 if (!g_str_equal(priv->msg_uid, uid)) {
1281                         check_dimming_rules_after_change (window);
1282                         g_free(uid);
1283                         g_object_unref (G_OBJECT(header));
1284                         return;
1285                 }
1286                 g_free(uid);
1287                 g_object_unref(G_OBJECT(header));
1288         }
1289
1290         if (priv->row_reference) {
1291                 gtk_tree_row_reference_free (priv->row_reference); 
1292         }
1293
1294         /* Setup row_reference for the actual msg. */
1295         priv->row_reference = gtk_tree_row_reference_new (priv->header_model, tree_path);
1296         if (priv->row_reference == NULL) {
1297                 g_warning("%s: No reference for msg header item.", __FUNCTION__);
1298                 return;
1299         }
1300
1301         /* Now set up next_row_reference. */
1302         if (priv->next_row_reference) {
1303                 gtk_tree_row_reference_free (priv->next_row_reference); 
1304         }
1305
1306         priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
1307         select_next_valid_row (priv->header_model,
1308                                &(priv->next_row_reference), FALSE, priv->is_outbox);
1309
1310         /* Connect the remaining callbacks to become able to detect
1311          * changes in header-view. */
1312         priv->row_changed_handler = 
1313                 g_signal_connect (priv->header_model, "row-changed",
1314                                   G_CALLBACK (modest_msg_view_window_on_row_changed),
1315                                   window);
1316         priv->row_deleted_handler = 
1317                 g_signal_connect (priv->header_model, "row-deleted",
1318                                   G_CALLBACK (modest_msg_view_window_on_row_deleted),
1319                                   window);
1320         priv->rows_reordered_handler = 
1321                 g_signal_connect (priv->header_model, "rows-reordered",
1322                                   G_CALLBACK (modest_msg_view_window_on_row_reordered),
1323                                   window);
1324
1325         check_dimming_rules_after_change (window);      
1326 }
1327
1328 static void 
1329 modest_msg_view_window_on_row_reordered (GtkTreeModel *header_model,
1330                                          GtkTreePath *arg1,
1331                                          GtkTreeIter *arg2,
1332                                          gpointer arg3,
1333                                          ModestMsgViewWindow *window)
1334 {
1335         ModestMsgViewWindowPrivate *priv = NULL;
1336         gboolean already_changed = FALSE;
1337
1338         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(window);
1339
1340         /* If the current row was reordered select the proper next
1341            valid row. The same if the next row reference changes */
1342         if (!priv->row_reference ||
1343             !gtk_tree_row_reference_valid (priv->row_reference))
1344                 return;
1345
1346         if (priv->next_row_reference &&
1347             gtk_tree_row_reference_valid (priv->next_row_reference)) {
1348                 GtkTreePath *cur, *next;
1349                 /* Check that the order is still the correct one */
1350                 cur = gtk_tree_row_reference_get_path (priv->row_reference);
1351                 next = gtk_tree_row_reference_get_path (priv->next_row_reference);
1352                 gtk_tree_path_next (cur);
1353                 if (gtk_tree_path_compare (cur, next) != 0) {
1354                         gtk_tree_row_reference_free (priv->next_row_reference);
1355                         priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
1356                         select_next_valid_row (header_model, &(priv->next_row_reference), FALSE, priv->is_outbox);
1357                         already_changed = TRUE;
1358                 }
1359                 gtk_tree_path_free (cur);
1360                 gtk_tree_path_free (next);
1361         } else {
1362                 if (priv->next_row_reference)
1363                         gtk_tree_row_reference_free (priv->next_row_reference);
1364                 /* Update next row reference */
1365                 priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
1366                 select_next_valid_row (header_model, &(priv->next_row_reference), FALSE, priv->is_outbox);
1367                 already_changed = TRUE;
1368         }
1369
1370         check_dimming_rules_after_change (window);
1371 }
1372
1373 /* The modest_msg_view_window_update_model_replaced implements update
1374  * function for ModestHeaderViewObserver. Checks whether the TnyFolder
1375  * actually belongs to the header-view is the same as the TnyFolder of
1376  * the message of msg-view or not. If they are different, there is
1377  * nothing to do. If they are the same, then the model has replaced and
1378  * the reference in msg-view shall be replaced from the old model to
1379  * the new model. In this case the view will be detached from it's
1380  * header folder. From this point the next/prev buttons are dimmed.
1381  */
1382 static void 
1383 modest_msg_view_window_update_model_replaced (ModestHeaderViewObserver *observer,
1384                                               GtkTreeModel *model,
1385                                               const gchar *tny_folder_id)
1386 {
1387         ModestMsgViewWindowPrivate *priv = NULL; 
1388         ModestMsgViewWindow *window = NULL;
1389
1390         g_assert(MODEST_IS_HEADER_VIEW_OBSERVER(observer));
1391         g_assert(MODEST_IS_MSG_VIEW_WINDOW(observer));
1392
1393         window = MODEST_MSG_VIEW_WINDOW(observer);
1394         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(window);
1395
1396         /* If there is an other folder in the header-view then we do
1397          * not care about it's model (msg list). Else if the
1398          * header-view shows the folder the msg shown by us is in, we
1399          * shall replace our model reference and make some check. */
1400         if(model == NULL || tny_folder_id == NULL || 
1401            (priv->header_folder_id && !g_str_equal(tny_folder_id, priv->header_folder_id)))
1402                 return;
1403
1404         /* Model is changed(replaced), so we should forget the old
1405          * one. Because there might be other references and there
1406          * might be some change on the model even if we unreferenced
1407          * it, we need to disconnect our signals here. */
1408         if (priv->header_model) {
1409                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
1410                                                   priv->row_changed_handler))
1411                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
1412                                                     priv->row_changed_handler);
1413                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
1414                                                   priv->row_deleted_handler))
1415                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
1416                                                     priv->row_deleted_handler);
1417                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
1418                                                   priv->row_inserted_handler))
1419                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
1420                                                     priv->row_inserted_handler);
1421                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
1422                                                   priv->rows_reordered_handler))
1423                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
1424                                                     priv->rows_reordered_handler);
1425
1426                 /* Frees */
1427                 if (priv->row_reference)
1428                         gtk_tree_row_reference_free (priv->row_reference);
1429                 if (priv->next_row_reference)
1430                         gtk_tree_row_reference_free (priv->next_row_reference);
1431                 g_object_unref(priv->header_model);
1432
1433                 /* Initialize */
1434                 priv->row_changed_handler = 0;
1435                 priv->row_deleted_handler = 0;
1436                 priv->row_inserted_handler = 0;
1437                 priv->rows_reordered_handler = 0;
1438                 priv->next_row_reference = NULL;
1439                 priv->row_reference = NULL;
1440                 priv->header_model = NULL;
1441         }
1442
1443         priv->header_model = g_object_ref (model);
1444
1445         /* Also we must connect to the new model for row insertions.
1446          * Only for insertions now. We will need other ones only after
1447          * the msg is show by msg-view is added to the new model. */
1448         priv->row_inserted_handler =
1449                 g_signal_connect (priv->header_model, "row-inserted",
1450                                   G_CALLBACK(modest_msg_view_window_on_row_inserted),
1451                                   window);
1452
1453         modest_ui_actions_check_menu_dimming_rules(MODEST_WINDOW(window));
1454         modest_ui_actions_check_toolbar_dimming_rules(MODEST_WINDOW(window));
1455 }
1456
1457 gboolean 
1458 modest_msg_view_window_toolbar_on_transfer_mode     (ModestMsgViewWindow *self)
1459 {
1460         ModestMsgViewWindowPrivate *priv= NULL; 
1461
1462         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), FALSE);
1463         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
1464
1465         return priv->progress_hint;
1466 }
1467
1468 TnyHeader*
1469 modest_msg_view_window_get_header (ModestMsgViewWindow *self)
1470 {
1471         ModestMsgViewWindowPrivate *priv= NULL; 
1472         TnyMsg *msg = NULL;
1473         TnyHeader *header = NULL;
1474         GtkTreePath *path = NULL;
1475         GtkTreeIter iter;
1476
1477         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), NULL);
1478         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
1479
1480         /* If the message was not obtained from a treemodel,
1481          * for instance if it was opened directly by the search UI:
1482          */
1483         if (priv->header_model == NULL || 
1484             priv->row_reference == NULL ||
1485             !gtk_tree_row_reference_valid (priv->row_reference)) {
1486                 msg = modest_msg_view_window_get_message (self);
1487                 if (msg) {
1488                         header = tny_msg_get_header (msg);
1489                         g_object_unref (msg);
1490                 }
1491                 return header;
1492         }
1493
1494         /* Get iter of the currently selected message in the header view: */
1495         path = gtk_tree_row_reference_get_path (priv->row_reference);
1496         g_return_val_if_fail (path != NULL, NULL);
1497         gtk_tree_model_get_iter (priv->header_model, 
1498                                  &iter, 
1499                                  path);
1500
1501         /* Get current message header */
1502         gtk_tree_model_get (priv->header_model, &iter, 
1503                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, 
1504                             &header, -1);
1505
1506         gtk_tree_path_free (path);
1507         return header;
1508 }
1509
1510 TnyMsg*
1511 modest_msg_view_window_get_message (ModestMsgViewWindow *self)
1512 {
1513         ModestMsgViewWindowPrivate *priv;
1514         
1515         g_return_val_if_fail (self, NULL);
1516         
1517         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
1518         
1519         return tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
1520 }
1521
1522 const gchar*
1523 modest_msg_view_window_get_message_uid (ModestMsgViewWindow *self)
1524 {
1525         ModestMsgViewWindowPrivate *priv;
1526
1527         g_return_val_if_fail (self, NULL);
1528         
1529         priv  = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
1530
1531         return (const gchar*) priv->msg_uid;
1532 }
1533
1534 /* Used for the Ctrl+F accelerator */
1535 static void
1536 modest_msg_view_window_toggle_isearch_toolbar (GtkWidget *obj,
1537                                                gpointer data)
1538 {
1539         ModestMsgViewWindow *window = MODEST_MSG_VIEW_WINDOW (data);
1540         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1541
1542         if (GTK_WIDGET_VISIBLE (priv->isearch_toolbar)) {
1543                 modest_msg_view_window_isearch_toolbar_close (obj, data);
1544        } else {
1545                 modest_msg_view_window_show_isearch_toolbar (obj, data);
1546        }
1547 }
1548
1549 /* Handler for menu option */
1550 static void
1551 modest_msg_view_window_show_isearch_toolbar (GtkWidget *obj,
1552                                              gpointer data)
1553 {
1554         ModestMsgViewWindow *window = MODEST_MSG_VIEW_WINDOW (data);
1555         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1556
1557         gtk_widget_show (priv->isearch_toolbar);
1558         modest_isearch_toolbar_highlight_entry (MODEST_ISEARCH_TOOLBAR (priv->isearch_toolbar), TRUE);
1559 }
1560
1561 /* Handler for click on the "X" close button in isearch toolbar */
1562 static void
1563 modest_msg_view_window_isearch_toolbar_close (GtkWidget *widget,
1564                                               ModestMsgViewWindow *obj)
1565 {
1566         ModestMsgViewWindowPrivate *priv;
1567
1568         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (obj);
1569
1570         /* Hide toolbar */
1571         gtk_widget_hide (priv->isearch_toolbar);
1572         modest_msg_view_grab_focus (MODEST_MSG_VIEW (priv->msg_view));
1573 }
1574
1575 static void
1576 modest_msg_view_window_isearch_toolbar_search (GtkWidget *widget,
1577                                                ModestMsgViewWindow *obj)
1578 {
1579         const gchar *current_search;
1580         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (obj);
1581
1582         if (modest_mime_part_view_is_empty (MODEST_MIME_PART_VIEW (priv->msg_view))) {
1583                 modest_platform_system_banner (NULL, NULL, _("mail_ib_nothing_to_find"));
1584                 return;
1585         }
1586
1587         current_search = modest_isearch_toolbar_get_search (MODEST_ISEARCH_TOOLBAR (widget));
1588
1589         if ((current_search == NULL) || (strcmp (current_search, "") == 0)) {
1590                 modest_platform_system_banner (NULL, NULL, _CS("ecdg_ib_find_rep_enter_text"));
1591                 return;
1592         }
1593
1594         if ((priv->last_search == NULL) || (strcmp (priv->last_search, current_search) != 0)) {
1595                 gboolean result;
1596                 g_free (priv->last_search);
1597                 priv->last_search = g_strdup (current_search);
1598                 result = modest_isearch_view_search (MODEST_ISEARCH_VIEW (priv->msg_view),
1599                                                      priv->last_search);
1600                 if (!result) {
1601                         modest_platform_system_banner (NULL, NULL, 
1602                                                         _HL("ckct_ib_find_no_matches"));
1603                         g_free (priv->last_search);
1604                         priv->last_search = NULL;
1605                 } else {
1606                         modest_isearch_toolbar_highlight_entry (MODEST_ISEARCH_TOOLBAR (priv->isearch_toolbar), TRUE);
1607                 }
1608         } else {
1609                 if (!modest_isearch_view_search_next (MODEST_ISEARCH_VIEW (priv->msg_view))) {
1610                         modest_platform_system_banner (NULL, NULL, 
1611                                                         _HL("ckct_ib_find_search_complete"));
1612                         g_free (priv->last_search);
1613                         priv->last_search = NULL;
1614                 } else {
1615                         modest_isearch_toolbar_highlight_entry (MODEST_ISEARCH_TOOLBAR (priv->isearch_toolbar), TRUE);
1616                 }
1617         }
1618         
1619 }
1620
1621 static void
1622 modest_msg_view_window_set_zoom (ModestWindow *window,
1623                                  gdouble zoom)
1624 {
1625         ModestMsgViewWindowPrivate *priv;
1626         ModestWindowPrivate *parent_priv;
1627      
1628         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
1629
1630         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1631         parent_priv = MODEST_WINDOW_GET_PRIVATE (window);
1632         modest_zoomable_set_zoom (MODEST_ZOOMABLE (priv->msg_view), zoom);
1633
1634 }
1635
1636 static gdouble
1637 modest_msg_view_window_get_zoom (ModestWindow *window)
1638 {
1639         ModestMsgViewWindowPrivate *priv;
1640      
1641         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), 1.0);
1642
1643         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1644         return modest_zoomable_get_zoom (MODEST_ZOOMABLE (priv->msg_view));
1645 }
1646
1647 static gboolean
1648 modest_msg_view_window_zoom_plus (ModestWindow *window)
1649 {
1650         gdouble zoom_level;
1651         ModestMsgViewWindowPrivate *priv;
1652         gint int_zoom;
1653         gchar *banner_text;
1654      
1655         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), 1.0);
1656         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1657   
1658         zoom_level =  modest_zoomable_get_zoom (MODEST_ZOOMABLE (priv->msg_view));
1659
1660         if (zoom_level >= 2.0) {
1661                 modest_platform_system_banner (NULL, NULL, 
1662                                                 _CS("ckct_ib_max_zoom_level_reached"));
1663                 return FALSE;
1664         } else if (zoom_level >= 1.5) {
1665                 zoom_level = 2.0;
1666         } else if (zoom_level >= 1.2) {
1667                 zoom_level = 1.5;
1668         } else if (zoom_level >= 1.0) {
1669                 zoom_level = 1.2;
1670         } else if (zoom_level >= 0.8) {
1671                 zoom_level = 1.0;
1672         } else if (zoom_level >= 0.5) {
1673                 zoom_level = 0.8;
1674         } else {
1675                 zoom_level = 0.5;
1676         }
1677
1678         /* set zoom level */
1679         int_zoom = (gint) rint (zoom_level*100.0+0.1);
1680         banner_text = g_strdup_printf (_HL("wdgt_ib_zoom"), int_zoom);
1681         modest_platform_information_banner (GTK_WIDGET (window), NULL, banner_text);
1682         g_free (banner_text);
1683         modest_zoomable_set_zoom (MODEST_ZOOMABLE (priv->msg_view), zoom_level);
1684
1685         return TRUE;
1686 }
1687
1688 static gboolean
1689 modest_msg_view_window_zoom_minus (ModestWindow *window)
1690 {
1691         gdouble zoom_level;
1692         ModestMsgViewWindowPrivate *priv;
1693         gint int_zoom;
1694         gchar *banner_text;
1695      
1696         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), 1.0);
1697         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1698   
1699         zoom_level =  modest_zoomable_get_zoom (MODEST_ZOOMABLE (priv->msg_view));
1700
1701         if (zoom_level <= 0.5) {
1702                 modest_platform_system_banner (NULL, NULL, 
1703                                                 _CS("ckct_ib_min_zoom_level_reached"));
1704                 return FALSE;
1705         } else if (zoom_level <= 0.8) {
1706                 zoom_level = 0.5;
1707         } else if (zoom_level <= 1.0) {
1708                 zoom_level = 0.8;
1709         } else if (zoom_level <= 1.2) {
1710                 zoom_level = 1.0;
1711         } else if (zoom_level <= 1.5) {
1712                 zoom_level = 1.2;
1713         } else if (zoom_level <= 2.0) {
1714                 zoom_level = 1.5;
1715         } else {
1716                 zoom_level = 2.0;
1717         }
1718
1719         /* set zoom level */
1720         int_zoom = (gint) rint (zoom_level*100.0+0.1);
1721         banner_text = g_strdup_printf (_HL("wdgt_ib_zoom"), int_zoom);
1722         modest_platform_information_banner (GTK_WIDGET (window), NULL, banner_text);
1723         g_free (banner_text);
1724         modest_zoomable_set_zoom (MODEST_ZOOMABLE (priv->msg_view), zoom_level);
1725
1726         return TRUE;
1727 }
1728
1729 static gboolean
1730 modest_msg_view_window_key_event (GtkWidget *window,
1731                                   GdkEventKey *event,
1732                                   gpointer userdata)
1733 {
1734         GtkWidget *focus;
1735
1736         focus = gtk_window_get_focus (GTK_WINDOW (window));
1737
1738         /* for the isearch toolbar case */
1739         if (focus && GTK_IS_ENTRY (focus)) {
1740                 if (event->keyval == GDK_BackSpace) {
1741                         GdkEvent *copy;
1742                         copy = gdk_event_copy ((GdkEvent *) event);
1743                         gtk_widget_event (focus, copy);
1744                         gdk_event_free (copy);
1745                         return TRUE;
1746                 } else {
1747                         return FALSE;
1748                 }
1749         }
1750         return FALSE;
1751 }
1752
1753 gboolean
1754 modest_msg_view_window_last_message_selected (ModestMsgViewWindow *window)
1755 {
1756         GtkTreePath *path;
1757         ModestMsgViewWindowPrivate *priv;
1758         GtkTreeIter tmp_iter;
1759         gboolean is_last_selected;
1760
1761         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), TRUE);
1762         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1763
1764         /*if no model (so no rows at all), then virtually we are the last*/
1765         if (!priv->header_model || !priv->row_reference)
1766                 return TRUE;
1767
1768         if (!gtk_tree_row_reference_valid (priv->row_reference))
1769                 return TRUE;
1770
1771         path = gtk_tree_row_reference_get_path (priv->row_reference);
1772         if (path == NULL)
1773                 return TRUE;
1774
1775         is_last_selected = TRUE;
1776         while (is_last_selected) {
1777                 TnyHeader *header;
1778                 gtk_tree_path_next (path);
1779                 if (!gtk_tree_model_get_iter (priv->header_model, &tmp_iter, path))
1780                         break;
1781                 gtk_tree_model_get (priv->header_model, &tmp_iter,
1782                                 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1783                                 &header, -1);
1784                 if (header) {
1785                         if (msg_is_visible (header, priv->is_outbox))
1786                                 is_last_selected = FALSE;
1787                         g_object_unref(G_OBJECT(header));
1788                 }
1789         }
1790         gtk_tree_path_free (path);
1791         return is_last_selected;
1792 }
1793
1794 gboolean
1795 modest_msg_view_window_has_headers_model (ModestMsgViewWindow *window)
1796 {
1797         ModestMsgViewWindowPrivate *priv;
1798
1799         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), TRUE);
1800         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1801
1802         return priv->header_model != NULL;
1803 }
1804
1805 gboolean
1806 modest_msg_view_window_is_search_result (ModestMsgViewWindow *window)
1807 {
1808         ModestMsgViewWindowPrivate *priv;
1809
1810         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), TRUE);
1811         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1812
1813         return priv->is_search_result;
1814 }
1815
1816 static gboolean
1817 msg_is_visible (TnyHeader *header, gboolean check_outbox)
1818 {
1819         if ((tny_header_get_flags(header) & TNY_HEADER_FLAG_DELETED))
1820                 return FALSE;
1821         if (!check_outbox) {
1822                 return TRUE;
1823         } else {
1824                 ModestTnySendQueueStatus status;
1825                 status = modest_tny_all_send_queues_get_msg_status (header);
1826                 return ((status != MODEST_TNY_SEND_QUEUE_FAILED) &&
1827                         (status != MODEST_TNY_SEND_QUEUE_SENDING));
1828         }
1829 }
1830
1831 gboolean
1832 modest_msg_view_window_first_message_selected (ModestMsgViewWindow *window)
1833 {
1834         GtkTreePath *path;
1835         ModestMsgViewWindowPrivate *priv;
1836         gboolean is_first_selected;
1837         GtkTreeIter tmp_iter;
1838
1839         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), TRUE);
1840         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1841
1842         /*if no model (so no rows at all), then virtually we are the first*/
1843         if (!priv->header_model || !priv->row_reference)
1844                 return TRUE;
1845
1846         if (!gtk_tree_row_reference_valid (priv->row_reference))
1847                 return TRUE;
1848
1849         path = gtk_tree_row_reference_get_path (priv->row_reference);
1850         if (!path)
1851                 return TRUE;
1852
1853         is_first_selected = TRUE;
1854         while (is_first_selected) {
1855                 TnyHeader *header;
1856                 if(!gtk_tree_path_prev (path))
1857                         break;
1858                 /* Here the 'if' is needless for logic, but let make sure
1859                  * iter is valid for gtk_tree_model_get. */
1860                 if (!gtk_tree_model_get_iter (priv->header_model, &tmp_iter, path))
1861                         break;
1862                 gtk_tree_model_get (priv->header_model, &tmp_iter,
1863                                 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1864                                 &header, -1);
1865                 if (header) {
1866                         if (msg_is_visible (header, priv->is_outbox))
1867                                 is_first_selected = FALSE;
1868                         g_object_unref(G_OBJECT(header));
1869                 }
1870         }
1871         gtk_tree_path_free (path);
1872         return is_first_selected;
1873 }
1874
1875 typedef struct {
1876         TnyHeader *header;
1877         gchar *msg_uid;
1878         TnyFolder *folder;
1879         GtkTreeRowReference *row_reference;
1880 } MsgReaderInfo;
1881
1882 static void
1883 message_reader_performer (gboolean canceled, 
1884                           GError *err,
1885                           GtkWindow *parent_window, 
1886                           TnyAccount *account, 
1887                           gpointer user_data)
1888 {
1889         ModestMailOperation *mail_op = NULL;
1890         MsgReaderInfo *info;
1891
1892         info = (MsgReaderInfo *) user_data;
1893         if (canceled || err) {
1894                 update_window_title (MODEST_MSG_VIEW_WINDOW (parent_window));
1895                 modest_ui_actions_on_close_window (NULL, MODEST_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 }