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