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