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