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