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