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