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