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