d3ebe31319a75c136ad8726a52808790dcba9677
[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                 tny_header_set_flag (header, TNY_HEADER_FLAG_SEEN);
2334
2335         /* Set new message */
2336         if (priv->msg_view != NULL && TNY_IS_MSG_VIEW (priv->msg_view)) {
2337                 tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
2338                 modest_msg_view_window_update_priority (self);
2339                 update_window_title (MODEST_MSG_VIEW_WINDOW (self));
2340                 update_branding (MODEST_MSG_VIEW_WINDOW (self));
2341                 modest_msg_view_grab_focus (MODEST_MSG_VIEW (priv->msg_view));
2342         }
2343
2344         /* Set the new message uid of the window  */
2345         if (priv->msg_uid) {
2346                 g_free (priv->msg_uid);
2347                 priv->msg_uid = modest_tny_folder_get_header_unique_id (header);
2348         }
2349
2350         /* Notify the observers */
2351         g_signal_emit (G_OBJECT (self), signals[MSG_CHANGED_SIGNAL],
2352                        0, priv->header_model, priv->row_reference);
2353
2354         /* Sync the flags if the message is not opened from a header
2355            model, i.e, if it's opened from a notification */
2356         if (!priv->header_model)
2357                 sync_flags (self);
2358
2359         /* Frees */
2360         g_object_unref (self);
2361         if (row_reference)
2362                 gtk_tree_row_reference_free (row_reference);
2363 }
2364
2365 TnyFolderType
2366 modest_msg_view_window_get_folder_type (ModestMsgViewWindow *window)
2367 {
2368         ModestMsgViewWindowPrivate *priv;
2369         TnyMsg *msg;
2370         TnyFolderType folder_type;
2371
2372         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2373
2374         folder_type = TNY_FOLDER_TYPE_UNKNOWN;
2375
2376         msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
2377         if (msg) {
2378                 TnyFolder *folder;
2379
2380                 folder = tny_msg_get_folder (msg);
2381                 if (folder) {
2382                         folder_type = modest_tny_folder_guess_folder_type (folder);
2383                         g_object_unref (folder);
2384                 }
2385                 g_object_unref (msg);
2386         }
2387
2388         return folder_type;
2389 }
2390
2391
2392 static void
2393 modest_msg_view_window_update_priority (ModestMsgViewWindow *window)
2394 {
2395         ModestMsgViewWindowPrivate *priv;
2396         TnyHeader *header = NULL;
2397         TnyHeaderFlags flags = 0;
2398
2399         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2400
2401         if (priv->header_model && priv->row_reference && gtk_tree_row_reference_valid (priv->row_reference)) {
2402                 GtkTreeIter iter;
2403                 GtkTreePath *path = NULL;
2404
2405                 path = gtk_tree_row_reference_get_path (priv->row_reference);
2406                 g_return_if_fail (path != NULL);
2407                 gtk_tree_model_get_iter (priv->header_model, 
2408                                          &iter, 
2409                                          gtk_tree_row_reference_get_path (priv->row_reference));
2410
2411                 gtk_tree_model_get (priv->header_model, &iter, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
2412                                     &header, -1);
2413                 gtk_tree_path_free (path);
2414         } else {
2415                 TnyMsg *msg;
2416                 msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
2417                 if (msg) {
2418                         header = tny_msg_get_header (msg);
2419                         g_object_unref (msg);
2420                 }
2421         }
2422
2423         if (header) {
2424                 flags = tny_header_get_flags (header);
2425                 g_object_unref(G_OBJECT(header));
2426         }
2427
2428         modest_msg_view_set_priority (MODEST_MSG_VIEW(priv->msg_view), flags);
2429
2430 }
2431
2432 static void
2433 toolbar_resize (ModestMsgViewWindow *self)
2434 {
2435         ModestMsgViewWindowPrivate *priv = NULL;
2436         ModestWindowPrivate *parent_priv = NULL;
2437         GtkWidget *widget;
2438         gint static_button_size;
2439         ModestWindowMgr *mgr;
2440
2441         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self));
2442         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2443         parent_priv = MODEST_WINDOW_GET_PRIVATE(self);
2444
2445         mgr = modest_runtime_get_window_mgr ();
2446         static_button_size = modest_window_mgr_get_fullscreen_mode (mgr)?120:120;
2447
2448         if (parent_priv->toolbar) {
2449                 /* Set expandable and homogeneous tool buttons */
2450                 widget = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageReply");
2451                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (widget), TRUE);
2452                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (widget), TRUE);
2453                 widget = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageReplyAll");
2454                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (widget), TRUE);
2455                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (widget), TRUE);
2456                 widget = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageForward");
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/ToolbarDeleteMessage");
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/ToolbarDownloadExternalImages");
2463                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (widget), TRUE);
2464                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (widget), TRUE);
2465                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (priv->next_toolitem), TRUE);
2466                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->next_toolitem), TRUE);
2467                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (priv->prev_toolitem), TRUE);
2468                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->prev_toolitem), TRUE);
2469         }
2470 }
2471
2472 static void
2473 modest_msg_view_window_show_toolbar (ModestWindow *self,
2474                                      gboolean show_toolbar)
2475 {
2476         ModestMsgViewWindowPrivate *priv = NULL;
2477         ModestWindowPrivate *parent_priv;
2478
2479         parent_priv = MODEST_WINDOW_GET_PRIVATE(self);
2480         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2481
2482         /* Set optimized view status */
2483         priv->optimized_view = !show_toolbar;
2484
2485         if (!parent_priv->toolbar) {
2486                 parent_priv->toolbar = gtk_ui_manager_get_widget (parent_priv->ui_manager, 
2487                                                                   "/ToolBar");
2488
2489 #ifdef MODEST_TOOLKIT_HILDON2
2490                 gtk_toolbar_set_icon_size (GTK_TOOLBAR (parent_priv->toolbar), HILDON_ICON_SIZE_FINGER);
2491 #else
2492                 gtk_toolbar_set_icon_size (GTK_TOOLBAR (parent_priv->toolbar), GTK_ICON_SIZE_LARGE_TOOLBAR);
2493 #endif
2494                 gtk_widget_set_no_show_all (parent_priv->toolbar, TRUE);
2495
2496                 priv->next_toolitem = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageNext");
2497                 priv->prev_toolitem = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageBack");
2498                 toolbar_resize (MODEST_MSG_VIEW_WINDOW (self));
2499
2500                 modest_window_add_toolbar (MODEST_WINDOW (self), 
2501                                            GTK_TOOLBAR (parent_priv->toolbar));
2502
2503         }
2504
2505         if (show_toolbar) {
2506                 /* Quick hack: this prevents toolbar icons "dance" when progress bar show status is changed */ 
2507                 /* TODO: resize mode migth be GTK_RESIZE_QUEUE, in order to avoid unneccesary shows */
2508                 gtk_container_set_resize_mode (GTK_CONTAINER(parent_priv->toolbar), GTK_RESIZE_IMMEDIATE);
2509
2510                 gtk_widget_show (GTK_WIDGET (parent_priv->toolbar));
2511                 if (modest_msg_view_window_transfer_mode_enabled (MODEST_MSG_VIEW_WINDOW (self))) 
2512                         set_progress_hint (MODEST_MSG_VIEW_WINDOW (self), TRUE);
2513                 else
2514                         set_progress_hint (MODEST_MSG_VIEW_WINDOW (self), FALSE);
2515
2516         } else {
2517                 gtk_widget_set_no_show_all (parent_priv->toolbar, TRUE);
2518                 gtk_widget_hide (GTK_WIDGET (parent_priv->toolbar));
2519         }
2520 }
2521
2522 static void 
2523 modest_msg_view_window_clipboard_owner_change (GtkClipboard *clipboard,
2524                                                GdkEvent *event,
2525                                                ModestMsgViewWindow *window)
2526 {
2527         if (!GTK_WIDGET_VISIBLE (window))
2528                 return;
2529
2530         modest_window_check_dimming_rules_group (MODEST_WINDOW (window), MODEST_DIMMING_RULES_CLIPBOARD);
2531 }
2532
2533 gboolean 
2534 modest_msg_view_window_transfer_mode_enabled (ModestMsgViewWindow *self)
2535 {
2536         ModestMsgViewWindowPrivate *priv;
2537         
2538         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), FALSE); 
2539         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2540
2541         return priv->progress_hint;
2542 }
2543
2544 static gboolean
2545 observers_empty (ModestMsgViewWindow *self)
2546 {
2547         GSList *tmp = NULL;
2548         ModestMsgViewWindowPrivate *priv;
2549         gboolean is_empty = TRUE;
2550         guint pending_ops = 0;
2551  
2552         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2553         tmp = priv->progress_widgets;
2554
2555         /* Check all observers */
2556         while (tmp && is_empty)  {
2557                 pending_ops = modest_progress_object_num_pending_operations (MODEST_PROGRESS_OBJECT(tmp->data));
2558                 is_empty = pending_ops == 0;
2559                 
2560                 tmp = g_slist_next(tmp);
2561         }
2562         
2563         return is_empty;
2564 }
2565
2566 static void
2567 on_account_removed (TnyAccountStore *account_store, 
2568                     TnyAccount *account,
2569                     gpointer user_data)
2570 {
2571         /* Do nothing if it's a transport account, because we only
2572            show the messages of a store account */
2573         if (tny_account_get_account_type(account) == TNY_ACCOUNT_TYPE_STORE) {
2574                 const gchar *parent_acc = NULL;
2575                 const gchar *our_acc = NULL;
2576
2577                 our_acc = modest_window_get_active_account (MODEST_WINDOW (user_data));
2578                 parent_acc = modest_tny_account_get_parent_modest_account_name_for_server_account (account);
2579
2580                 /* Close this window if I'm showing a message of the removed account */
2581                 if (our_acc && parent_acc && strcmp (parent_acc, our_acc) == 0)
2582                         modest_ui_actions_on_close_window (NULL, MODEST_WINDOW (user_data));
2583         }
2584 }
2585
2586 static void 
2587 on_mail_operation_started (ModestMailOperation *mail_op,
2588                            gpointer user_data)
2589 {
2590         ModestMsgViewWindow *self;
2591         ModestMailOperationTypeOperation op_type;
2592         GSList *tmp;
2593         ModestMsgViewWindowPrivate *priv;
2594         GObject *source = NULL;
2595
2596         self = MODEST_MSG_VIEW_WINDOW (user_data);
2597         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2598         op_type = modest_mail_operation_get_type_operation (mail_op);
2599         tmp = priv->progress_widgets;
2600         source = modest_mail_operation_get_source(mail_op);
2601         if (G_OBJECT (self) == source) {
2602                 if (op_type == MODEST_MAIL_OPERATION_TYPE_RECEIVE ||
2603                     op_type == MODEST_MAIL_OPERATION_TYPE_OPEN ||
2604                     op_type == MODEST_MAIL_OPERATION_TYPE_DELETE) {
2605                         set_progress_hint (self, TRUE);
2606                         while (tmp) {
2607                                 modest_progress_object_add_operation (
2608                                                 MODEST_PROGRESS_OBJECT (tmp->data),
2609                                                 mail_op);
2610                                 tmp = g_slist_next (tmp);
2611                         }
2612                 }
2613         }
2614         g_object_unref (source);
2615
2616         /* Update dimming rules */
2617         check_dimming_rules_after_change (self);
2618 }
2619
2620 static void
2621 on_mail_operation_finished (ModestMailOperation *mail_op,
2622                             gpointer user_data)
2623 {
2624         ModestMsgViewWindow *self;
2625         ModestMailOperationTypeOperation op_type;
2626         GSList *tmp;
2627         ModestMsgViewWindowPrivate *priv;
2628
2629         self = MODEST_MSG_VIEW_WINDOW (user_data);
2630         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2631         op_type = modest_mail_operation_get_type_operation (mail_op);
2632         tmp = priv->progress_widgets;
2633
2634         if (op_type == MODEST_MAIL_OPERATION_TYPE_RECEIVE ||
2635             op_type == MODEST_MAIL_OPERATION_TYPE_OPEN ||
2636             op_type == MODEST_MAIL_OPERATION_TYPE_DELETE) {
2637                 while (tmp) {
2638                         modest_progress_object_remove_operation (MODEST_PROGRESS_OBJECT (tmp->data),
2639                                                                  mail_op);
2640                         tmp = g_slist_next (tmp);
2641                 }
2642
2643                 /* If no more operations are being observed, NORMAL mode is enabled again */
2644                 if (observers_empty (self)) {
2645                         set_progress_hint (self, FALSE);
2646                 }
2647         }
2648
2649         /* Update dimming rules. We have to do this right here
2650            and not in view_msg_cb because at that point the
2651            transfer mode is still enabled so the dimming rule
2652            won't let the user delete the message that has been
2653            readed for example */
2654         check_dimming_rules_after_change (self);
2655 }
2656
2657 static void
2658 on_queue_changed (ModestMailOperationQueue *queue,
2659                   ModestMailOperation *mail_op,
2660                   ModestMailOperationQueueNotification type,
2661                   ModestMsgViewWindow *self)
2662 {       
2663         ModestMsgViewWindowPrivate *priv;
2664
2665         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2666
2667         /* If this operations was created by another window, do nothing */
2668         if (!modest_mail_operation_is_mine (mail_op, G_OBJECT(self))) 
2669             return;
2670
2671         if (type == MODEST_MAIL_OPERATION_QUEUE_OPERATION_ADDED) {
2672                 priv->sighandlers = modest_signal_mgr_connect (priv->sighandlers,
2673                                                                G_OBJECT (mail_op),
2674                                                                "operation-started",
2675                                                                G_CALLBACK (on_mail_operation_started),
2676                                                                self);
2677                 priv->sighandlers = modest_signal_mgr_connect (priv->sighandlers,
2678                                                                G_OBJECT (mail_op),
2679                                                                "operation-finished",
2680                                                                G_CALLBACK (on_mail_operation_finished),
2681                                                                self);
2682         } else if (type == MODEST_MAIL_OPERATION_QUEUE_OPERATION_REMOVED) {
2683                 priv->sighandlers = modest_signal_mgr_disconnect (priv->sighandlers,
2684                                                                   G_OBJECT (mail_op),
2685                                                                   "operation-started");
2686                 priv->sighandlers = modest_signal_mgr_disconnect (priv->sighandlers,
2687                                                                   G_OBJECT (mail_op),
2688                                                                   "operation-finished");
2689         }
2690 }
2691
2692 TnyList *
2693 modest_msg_view_window_get_attachments (ModestMsgViewWindow *win) 
2694 {
2695         ModestMsgViewWindowPrivate *priv;
2696         TnyList *selected_attachments = NULL;
2697         
2698         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (win), NULL);
2699         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (win);
2700
2701         /* In Hildon 2.2 as there's no selection we assume we have all attachments selected */
2702         selected_attachments = modest_msg_view_get_attachments (MODEST_MSG_VIEW (priv->msg_view));
2703         
2704         return selected_attachments;
2705 }
2706
2707 typedef struct {
2708         ModestMsgViewWindow *self;
2709         gchar *file_path;
2710         gchar *attachment_uid;
2711 } DecodeAsyncHelper;
2712
2713 static void
2714 on_decode_to_stream_async_handler (TnyMimePart *mime_part, 
2715                                    gboolean cancelled, 
2716                                    TnyStream *stream, 
2717                                    GError *err, 
2718                                    gpointer user_data)
2719 {
2720         DecodeAsyncHelper *helper = (DecodeAsyncHelper *) user_data;
2721         const gchar *content_type;
2722         ModestMsgViewWindowPrivate *priv;
2723
2724         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (helper->self);
2725
2726         if (cancelled || err) {
2727                 if (err) {
2728                         gchar *msg;
2729                         if ((err->domain == TNY_ERROR_DOMAIN) && 
2730                             (err->code == TNY_IO_ERROR_WRITE) &&
2731                             (errno == ENOSPC)) {
2732                                 msg = g_strdup_printf (_KR("cerm_device_memory_full"), "");
2733                         } else {
2734                                 msg = g_strdup (_("mail_ib_file_operation_failed"));
2735                         }
2736                         modest_platform_information_banner (NULL, NULL, msg);
2737                         g_free (msg);
2738                 }
2739                 goto free;
2740         }
2741
2742         /* It could happen that the window was closed. So we
2743            assume it is a cancelation */
2744         if (!GTK_WIDGET_VISIBLE (helper->self))
2745                 goto free;
2746
2747         /* Remove the progress hint */
2748         set_progress_hint (helper->self, FALSE);
2749
2750         content_type = tny_mime_part_get_content_type (mime_part);
2751         if (content_type && g_str_has_prefix (content_type, "message/rfc822")) {
2752                 ModestWindowMgr *mgr;
2753                 ModestWindow *msg_win = NULL;
2754                 TnyMsg * msg;
2755                 gchar *account;
2756                 const gchar *mailbox;
2757                 TnyStream *file_stream;
2758                 gint fd;
2759
2760                 fd = g_open (helper->file_path, O_RDONLY, 0644);
2761                 if (fd != -1) {
2762                         TnyMsg *top_msg;
2763                         file_stream = tny_fs_stream_new (fd);
2764
2765                         mgr = modest_runtime_get_window_mgr ();
2766
2767                         account = g_strdup (modest_window_get_active_account (MODEST_WINDOW (helper->self)));
2768                         mailbox = modest_window_get_active_mailbox (MODEST_WINDOW (helper->self));
2769
2770                         if (!account)
2771                                 account = modest_account_mgr_get_default_account (modest_runtime_get_account_mgr ());
2772
2773                         msg = tny_camel_msg_new ();
2774                         tny_camel_msg_parse (TNY_CAMEL_MSG (msg), file_stream);
2775
2776                         if (priv->top_msg)
2777                                 top_msg = g_object_ref (priv->top_msg);
2778                         else
2779                                 top_msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
2780
2781                         msg_win = modest_msg_view_window_new_for_attachment (TNY_MSG (msg), top_msg, 
2782                                                                              account, mailbox, helper->attachment_uid);
2783                         if (top_msg) g_object_unref (top_msg);
2784                         modest_window_set_zoom (MODEST_WINDOW (msg_win),
2785                                                 modest_window_get_zoom (MODEST_WINDOW (helper->self)));
2786                         if (modest_window_mgr_register_window (mgr, msg_win, MODEST_WINDOW (helper->self)))
2787                                 gtk_widget_show_all (GTK_WIDGET (msg_win));
2788                         else
2789                                 gtk_widget_destroy (GTK_WIDGET (msg_win));
2790                         g_object_unref (msg);
2791                         g_object_unref (file_stream);
2792                 } else {
2793                         modest_platform_information_banner (NULL, NULL, _("mail_ib_file_operation_failed"));
2794                 }
2795
2796         } else {
2797
2798                 /* make the file read-only */
2799                 g_chmod(helper->file_path, 0444);
2800
2801                 /* Activate the file */
2802                 modest_platform_activate_file (helper->file_path, content_type);
2803         }
2804
2805  free:
2806         /* Frees */
2807         g_object_unref (helper->self);
2808         g_free (helper->file_path);
2809         g_free (helper->attachment_uid);
2810         g_slice_free (DecodeAsyncHelper, helper);
2811 }
2812
2813 static void
2814 view_attachment_connect_handler (gboolean canceled,
2815                                  GError *err,
2816                                  GtkWindow *parent_window,
2817                                  TnyAccount *account,
2818                                  TnyMimePart *part)
2819 {
2820
2821         if (canceled || err) {
2822                 g_object_unref (part);
2823                 return;
2824         }
2825
2826         modest_msg_view_window_view_attachment (MODEST_MSG_VIEW_WINDOW (parent_window),
2827                                                 part);
2828         g_object_unref (part);
2829 }
2830
2831 void
2832 modest_msg_view_window_view_attachment (ModestMsgViewWindow *window, 
2833                                         TnyMimePart *mime_part)
2834 {
2835         ModestMsgViewWindowPrivate *priv;
2836         const gchar *msg_uid;
2837         gchar *attachment_uid = NULL;
2838         gint attachment_index = 0;
2839         TnyList *attachments;
2840
2841         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
2842         g_return_if_fail (TNY_IS_MIME_PART (mime_part) || (mime_part == NULL));
2843         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2844
2845         msg_uid = modest_msg_view_window_get_message_uid (MODEST_MSG_VIEW_WINDOW (window));
2846         attachments = modest_msg_view_get_attachments (MODEST_MSG_VIEW (priv->msg_view));
2847         attachment_index = modest_list_index (attachments, (GObject *) mime_part);
2848         g_object_unref (attachments);
2849
2850         if (msg_uid && attachment_index >= 0) {
2851                 attachment_uid = g_strdup_printf ("%s/%d", msg_uid, attachment_index);
2852         }
2853
2854         if (mime_part == NULL) {
2855                 gboolean error = FALSE;
2856                 TnyList *selected_attachments = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2857                 if (selected_attachments == NULL || tny_list_get_length (selected_attachments) == 0) {
2858                         error = TRUE;
2859                 } else if (tny_list_get_length (selected_attachments) > 1) {
2860                         modest_platform_system_banner (NULL, NULL, _("mcen_ib_unable_to_display_more"));
2861                         error = TRUE;
2862                 } else {
2863                         TnyIterator *iter;
2864                         iter = tny_list_create_iterator (selected_attachments);
2865                         mime_part = (TnyMimePart *) tny_iterator_get_current (iter);
2866                         g_object_unref (iter);
2867                 }
2868                 if (selected_attachments)
2869                         g_object_unref (selected_attachments);
2870
2871                 if (error)
2872                         goto frees;
2873         } else {
2874                 g_object_ref (mime_part);
2875         }
2876
2877         if (tny_mime_part_is_purged (mime_part))
2878                 goto frees;
2879
2880         if (TNY_IS_CAMEL_BS_MIME_PART (mime_part) &&
2881             !tny_camel_bs_mime_part_is_fetched (TNY_CAMEL_BS_MIME_PART (mime_part))) {
2882                 gboolean is_merge;
2883                 TnyAccount *account;
2884
2885                 is_merge = g_str_has_prefix (priv->msg_uid, "merge:");
2886                 account = NULL;
2887                 /* Get the account */
2888                 if (!is_merge)
2889                         account = tny_account_store_find_account (TNY_ACCOUNT_STORE (modest_runtime_get_account_store ()),
2890                                                                   priv->msg_uid);
2891
2892                 if (!tny_device_is_online (modest_runtime_get_device())) {
2893                         modest_platform_connect_and_perform ((ModestWindow *) window,
2894                                                              TRUE,
2895                                                              TNY_ACCOUNT (account),
2896                                                              (ModestConnectedPerformer) view_attachment_connect_handler,
2897                                                              g_object_ref (mime_part));
2898                         goto frees;
2899                 }
2900         }
2901
2902         if (!modest_tny_mime_part_is_msg (mime_part) && tny_mime_part_get_filename (mime_part)) {
2903                 gchar *filepath = NULL;
2904                 const gchar *att_filename = tny_mime_part_get_filename (mime_part);
2905                 gboolean show_error_banner = FALSE;
2906                 TnyFsStream *temp_stream = NULL;
2907                 temp_stream = modest_utils_create_temp_stream (att_filename, attachment_uid,
2908                                                                &filepath);
2909
2910                 if (temp_stream != NULL) {
2911                         ModestAccountMgr *mgr;
2912                         DecodeAsyncHelper *helper;
2913                         gboolean decode_in_provider;
2914                         ModestProtocol *protocol;
2915                         const gchar *account; 
2916
2917                         /* Activate progress hint */
2918                         set_progress_hint (window, TRUE);
2919
2920                         helper = g_slice_new0 (DecodeAsyncHelper);
2921                         helper->self = g_object_ref (window);
2922                         helper->file_path = g_strdup (filepath);
2923                         helper->attachment_uid = g_strdup (attachment_uid);
2924
2925                         decode_in_provider = FALSE;
2926                         mgr = modest_runtime_get_account_mgr ();
2927                         account = modest_window_get_active_account (MODEST_WINDOW (window));
2928                         if (modest_account_mgr_account_is_multimailbox (mgr, account, &protocol)) {
2929                                 if (MODEST_IS_ACCOUNT_PROTOCOL (protocol)) {
2930                                         gchar *uri;
2931                                         uri = g_strconcat ("file://", filepath, NULL);
2932                                         decode_in_provider = 
2933                                                 modest_account_protocol_decode_part_to_stream_async (
2934                                                         MODEST_ACCOUNT_PROTOCOL (protocol),
2935                                                         mime_part,
2936                                                         filepath,
2937                                                         TNY_STREAM (temp_stream),
2938                                                         on_decode_to_stream_async_handler,
2939                                                         NULL,
2940                                                         helper);
2941                                         g_free (uri);
2942                                 }
2943                         }
2944
2945                         if (!decode_in_provider)
2946                                 tny_mime_part_decode_to_stream_async (mime_part, TNY_STREAM (temp_stream),
2947                                                                       on_decode_to_stream_async_handler,
2948                                                                       NULL,
2949                                                                       helper);
2950                         g_object_unref (temp_stream);
2951                         /* NOTE: files in the temporary area will be automatically
2952                          * cleaned after some time if they are no longer in use */
2953                 } else {
2954                         if (filepath) {
2955                                 const gchar *content_type;
2956                                 /* the file may already exist but it isn't writable,
2957                                  * let's try to open it anyway */
2958                                 content_type = tny_mime_part_get_content_type (mime_part);
2959                                 modest_platform_activate_file (filepath, content_type);
2960                         } else {
2961                                 g_warning ("%s: modest_utils_create_temp_stream failed", __FUNCTION__);
2962                                 show_error_banner = TRUE;
2963                         }
2964                 }
2965                 if (filepath)
2966                         g_free (filepath);
2967                 if (show_error_banner)
2968                         modest_platform_information_banner (NULL, NULL, _("mail_ib_file_operation_failed"));
2969         } else if (!modest_tny_mime_part_is_msg (mime_part)) {
2970                 ModestWindowMgr *mgr;
2971                 ModestWindow *msg_win = NULL;
2972                 TnyMsg *current_msg;
2973                 gboolean found;
2974                 TnyHeader *header;
2975
2976                 current_msg = modest_msg_view_window_get_message (MODEST_MSG_VIEW_WINDOW (window));
2977                 mgr = modest_runtime_get_window_mgr ();
2978                 header = tny_msg_get_header (TNY_MSG (current_msg));
2979                 found = modest_window_mgr_find_registered_message_uid (mgr,
2980                                                                        attachment_uid,
2981                                                                        &msg_win);
2982                 
2983                 if (found) {
2984                         g_debug ("window for this body is already being created");
2985                 } else {
2986                         TnyMsg *top_msg;
2987
2988                         /* it's not found, so create a new window for it */
2989                         modest_window_mgr_register_header (mgr, header, attachment_uid); /* register the uid before building the window */
2990                         gchar *account = g_strdup (modest_window_get_active_account (MODEST_WINDOW (window)));
2991                         const gchar *mailbox = modest_window_get_active_mailbox (MODEST_WINDOW (window));
2992                         if (!account)
2993                                 account = modest_account_mgr_get_default_account (modest_runtime_get_account_mgr ());
2994
2995                         if (priv->top_msg)
2996                                 top_msg = g_object_ref (priv->top_msg);
2997                         else
2998                                 top_msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
2999                         
3000                         msg_win = modest_msg_view_window_new_with_other_body (TNY_MSG (current_msg), TNY_MIME_PART (mime_part), top_msg,
3001                                                                               account, mailbox, attachment_uid);
3002
3003                         if (top_msg) g_object_unref (top_msg);
3004                         
3005                         modest_window_set_zoom (MODEST_WINDOW (msg_win),
3006                                                 modest_window_get_zoom (MODEST_WINDOW (window)));
3007                         if (modest_window_mgr_register_window (mgr, msg_win, MODEST_WINDOW (window)))
3008                                 gtk_widget_show_all (GTK_WIDGET (msg_win));
3009                         else
3010                                 gtk_widget_destroy (GTK_WIDGET (msg_win));
3011                 }
3012                 g_object_unref (current_msg);           
3013         } else {
3014                 /* message attachment */
3015                 TnyHeader *header = NULL;
3016                 ModestWindowMgr *mgr;
3017                 ModestWindow *msg_win = NULL;
3018                 gboolean found;
3019
3020                 header = tny_msg_get_header (TNY_MSG (mime_part));
3021                 mgr = modest_runtime_get_window_mgr ();
3022                 found = modest_window_mgr_find_registered_header (mgr, header, &msg_win);
3023
3024                 if (found) {
3025                         /* if it's found, but there is no msg_win, it's probably in the process of being created;
3026                          * thus, we don't do anything */
3027                         g_debug ("window for is already being created");
3028                 } else {
3029                         TnyMsg *top_msg;
3030                         /* it's not found, so create a new window for it */
3031                         modest_window_mgr_register_header (mgr, header, attachment_uid); /* register the uid before building the window */
3032                         gchar *account = g_strdup (modest_window_get_active_account (MODEST_WINDOW (window)));
3033                         const gchar *mailbox = modest_window_get_active_mailbox (MODEST_WINDOW (window));
3034                         if (!account)
3035                                 account = modest_account_mgr_get_default_account (modest_runtime_get_account_mgr ());
3036                         if (priv->top_msg)
3037                                 top_msg = g_object_ref (priv->top_msg);
3038                         else
3039                                 top_msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
3040                         msg_win = modest_msg_view_window_new_for_attachment (
3041                                 TNY_MSG (mime_part), top_msg, account, 
3042                                 mailbox, attachment_uid);
3043                         modest_window_set_zoom (MODEST_WINDOW (msg_win),
3044                                                 modest_window_get_zoom (MODEST_WINDOW (window)));
3045                         if (modest_window_mgr_register_window (mgr, msg_win, MODEST_WINDOW (window)))
3046                                 gtk_widget_show_all (GTK_WIDGET (msg_win));
3047                         else
3048                                 gtk_widget_destroy (GTK_WIDGET (msg_win));
3049                 }
3050         }
3051
3052  frees:
3053         if (attachment_uid)
3054                 g_free (attachment_uid);
3055         if (mime_part)
3056                 g_object_unref (mime_part);
3057 }
3058
3059 typedef struct
3060 {
3061         gchar *filename;
3062         TnyMimePart *part;
3063 } SaveMimePartPair;
3064
3065 typedef struct
3066 {
3067         GList *pairs;
3068         GnomeVFSResult result;
3069         gchar *uri;
3070         ModestMsgViewWindow *window;
3071 } SaveMimePartInfo;
3072
3073 static void save_mime_part_info_free (SaveMimePartInfo *info, gboolean with_struct);
3074 static gboolean idle_save_mime_part_show_result (SaveMimePartInfo *info);
3075 static gpointer save_mime_part_to_file (SaveMimePartInfo *info);
3076 static void save_mime_parts_to_file_with_checks (GtkWindow *parent, SaveMimePartInfo *info);
3077
3078 static void
3079 save_mime_part_info_free (SaveMimePartInfo *info, gboolean with_struct)
3080 {
3081         GList *node;
3082
3083         for (node = info->pairs; node != NULL; node = g_list_next (node)) {
3084                 SaveMimePartPair *pair = (SaveMimePartPair *) node->data;
3085                 g_free (pair->filename);
3086                 g_object_unref (pair->part);
3087                 g_slice_free (SaveMimePartPair, pair);
3088         }
3089         g_list_free (info->pairs);
3090         info->pairs = NULL;
3091         g_free (info->uri);
3092         g_object_unref (info->window);
3093         info->window = NULL;
3094         if (with_struct) {
3095                 g_slice_free (SaveMimePartInfo, info);
3096         }
3097 }
3098
3099 static gboolean
3100 idle_save_mime_part_show_result (SaveMimePartInfo *info)
3101 {
3102         /* This is a GDK lock because we are an idle callback and
3103          * modest_platform_system_banner is or does Gtk+ code */
3104
3105         gdk_threads_enter (); /* CHECKED */
3106         if (info->result == GNOME_VFS_ERROR_CANCELLED) {
3107                 /* nothing */
3108         } else if (info->result == GNOME_VFS_OK) {
3109                 modest_platform_system_banner (NULL, NULL, _CS_SAVED);
3110         } else if (info->result == GNOME_VFS_ERROR_NO_SPACE) {
3111                 gchar *msg = NULL;
3112
3113                 /* Check if the uri belongs to the external mmc */
3114                 if (g_str_has_prefix (info->uri, g_getenv (MODEST_MMC1_VOLUMEPATH_ENV)))
3115                         msg = g_strdup_printf (_KR("cerm_device_memory_full"), "");
3116                 else
3117                         msg = g_strdup (_KR("cerm_memory_card_full"));
3118                 modest_platform_information_banner (NULL, NULL, msg);
3119                 g_free (msg);
3120         } else {
3121                 modest_platform_system_banner (NULL, NULL, _("mail_ib_file_operation_failed"));
3122         }
3123         set_progress_hint (info->window, FALSE);
3124         save_mime_part_info_free (info, FALSE);
3125         gdk_threads_leave (); /* CHECKED */
3126
3127         return FALSE;
3128 }
3129
3130 static void
3131 save_mime_part_to_file_connect_handler (gboolean canceled,
3132                                         GError *err,
3133                                         GtkWindow *parent_window,
3134                                         TnyAccount *account,
3135                                         SaveMimePartInfo *info)
3136 {
3137         if (canceled || err) {
3138                 if (canceled && !err) {
3139                         info->result = GNOME_VFS_ERROR_CANCELLED;
3140                 }
3141                 g_idle_add ((GSourceFunc) idle_save_mime_part_show_result, info);
3142         } else {
3143                 g_thread_create ((GThreadFunc)save_mime_part_to_file, info, FALSE, NULL);
3144         }
3145 }
3146
3147 static gboolean
3148 save_mime_part_to_file_connect_idle (SaveMimePartInfo *info)
3149 {
3150         gboolean is_merge;
3151         TnyAccount *account;
3152         ModestMsgViewWindowPrivate *priv;
3153
3154         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (info->window);
3155
3156         is_merge = g_str_has_prefix (priv->msg_uid, "merge:");
3157         account = NULL;
3158
3159         /* Get the account */
3160         if (!is_merge)
3161                 account = tny_account_store_find_account (TNY_ACCOUNT_STORE (modest_runtime_get_account_store ()),
3162                                                           priv->msg_uid);
3163
3164         modest_platform_connect_and_perform ((ModestWindow *) info->window,
3165                                              TRUE,
3166                                              TNY_ACCOUNT (account),
3167                                              (ModestConnectedPerformer) save_mime_part_to_file_connect_handler,
3168                                              info);
3169
3170         if (account)
3171                 g_object_unref (account);
3172
3173         return FALSE;
3174 }
3175
3176 static gpointer
3177 save_mime_part_to_file (SaveMimePartInfo *info)
3178 {
3179         GnomeVFSHandle *handle;
3180         TnyStream *stream;
3181         SaveMimePartPair *pair = (SaveMimePartPair *) info->pairs->data;
3182
3183         if (TNY_IS_CAMEL_BS_MIME_PART (pair->part) &&
3184             !tny_camel_bs_mime_part_is_fetched (TNY_CAMEL_BS_MIME_PART (pair->part))) {
3185                 gboolean check_online = TRUE;
3186                 ModestMsgViewWindowPrivate *priv = NULL;
3187
3188                 /* Check if we really need to connect to save the mime part */
3189                 priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (info->window);
3190                 if (g_str_has_prefix (priv->msg_uid, "merge:")) {
3191                         check_online = FALSE;
3192                 } else {
3193                         TnyAccountStore *acc_store;
3194                         TnyAccount *account = NULL;
3195
3196                         acc_store = (TnyAccountStore*) modest_runtime_get_account_store ();
3197                         account = tny_account_store_find_account (acc_store, priv->msg_uid);
3198
3199                         if (account) {
3200                                 if (tny_account_get_connection_status (account) ==
3201                                     TNY_CONNECTION_STATUS_CONNECTED)
3202                                         check_online = FALSE;
3203                                 g_object_unref (account);
3204                         } else {
3205                                 check_online = !tny_device_is_online (tny_account_store_get_device (acc_store));
3206                         }
3207                 }
3208
3209                 if (check_online) {
3210                         g_idle_add ((GSourceFunc) save_mime_part_to_file_connect_idle, info);
3211                         return NULL;
3212                 }
3213         }
3214
3215         info->result = gnome_vfs_create (&handle, pair->filename, GNOME_VFS_OPEN_WRITE, FALSE, 0644);
3216         if (info->result == GNOME_VFS_OK) {
3217                 GError *error = NULL;
3218                 gboolean decode_in_provider;
3219                 gssize written;
3220                 ModestAccountMgr *mgr;
3221                 const gchar *account;
3222                 ModestProtocol *protocol = NULL;
3223
3224                 stream = tny_vfs_stream_new (handle);
3225
3226                 decode_in_provider = FALSE;
3227                 mgr = modest_runtime_get_account_mgr ();
3228                 account = modest_window_get_active_account (MODEST_WINDOW (info->window));
3229                 if (modest_account_mgr_account_is_multimailbox (mgr, account, &protocol)) {
3230                         if (MODEST_IS_ACCOUNT_PROTOCOL (protocol)) {
3231                                 decode_in_provider = 
3232                                         modest_account_protocol_decode_part_to_stream (
3233                                                 MODEST_ACCOUNT_PROTOCOL (protocol),
3234                                                 pair->part,
3235                                                 pair->filename,
3236                                                 stream,
3237                                                 &written,
3238                                                 &error);
3239                         }
3240                 }
3241                 if (!decode_in_provider)
3242                         written = tny_mime_part_decode_to_stream (pair->part, stream, &error);
3243
3244                 if (written < 0) {
3245                         g_warning ("modest: could not save attachment %s: %d (%s)\n", pair->filename, error?error->code:-1, error?error->message:"Unknown error");
3246
3247                         if (error && (error->domain == TNY_ERROR_DOMAIN) &&
3248                             (error->code == TNY_IO_ERROR_WRITE) &&
3249                             (errno == ENOSPC)) {
3250                                 info->result = GNOME_VFS_ERROR_NO_SPACE;
3251                         } else {
3252                                 info->result = GNOME_VFS_ERROR_IO;
3253                         }
3254                 }
3255                 g_object_unref (G_OBJECT (stream));
3256         } else {
3257                 g_warning ("Could not create save attachment %s: %s\n", 
3258                            pair->filename, gnome_vfs_result_to_string (info->result));
3259         }
3260
3261         /* Go on saving remaining files */
3262         info->pairs = g_list_remove_link (info->pairs, info->pairs);
3263         if (info->pairs != NULL) {
3264                 save_mime_part_to_file (info);
3265         } else {
3266                 g_idle_add ((GSourceFunc) idle_save_mime_part_show_result, info);
3267         }
3268
3269         return NULL;
3270 }
3271
3272 static void
3273 save_mime_parts_to_file_with_checks (GtkWindow *parent,
3274                                      SaveMimePartInfo *info)
3275 {
3276         gboolean is_ok = TRUE;
3277         gint replaced_files = 0;
3278         const GList *files = info->pairs;
3279         const GList *iter, *to_replace = NULL;
3280
3281         for (iter = files; (iter != NULL) && (replaced_files < 2); iter = g_list_next(iter)) {
3282                 SaveMimePartPair *pair = iter->data;
3283                 gchar *unescaped = g_uri_unescape_string (pair->filename, NULL);
3284
3285                 if (modest_utils_file_exists (unescaped)) {
3286                         replaced_files++;
3287                         if (replaced_files == 1)
3288                                 to_replace = iter;
3289                 }
3290                 g_free (unescaped);
3291         }
3292         if (replaced_files) {
3293                 gint response;
3294
3295                 if (replaced_files == 1) {
3296                         SaveMimePartPair *pair = to_replace->data;
3297                         const gchar *basename = strrchr (pair->filename, G_DIR_SEPARATOR) + 1;
3298                         gchar *escaped_basename, *message;
3299
3300                         escaped_basename = g_uri_unescape_string (basename, NULL);
3301                         message = g_strdup_printf ("%s\n%s",
3302                                                    _FM_REPLACE_FILE,
3303                                                    (escaped_basename) ? escaped_basename : "");
3304                         response = modest_platform_run_confirmation_dialog (parent, message);
3305                         g_free (message);
3306                         g_free (escaped_basename);
3307                 } else {
3308                         response = modest_platform_run_confirmation_dialog (parent,
3309                                                                             _FM_REPLACE_MULTIPLE);
3310                 }
3311                 if (response != GTK_RESPONSE_OK)
3312                         is_ok = FALSE;
3313         }
3314
3315         if (!is_ok) {
3316                 save_mime_part_info_free (info, TRUE);
3317         } else {
3318                 /* Start progress and launch thread */
3319                 set_progress_hint (info->window, TRUE);
3320                 g_thread_create ((GThreadFunc)save_mime_part_to_file, info, FALSE, NULL);
3321         }
3322
3323 }
3324
3325 typedef struct _SaveAttachmentsInfo {
3326         TnyList *attachments_list;
3327         ModestMsgViewWindow *window;
3328 } SaveAttachmentsInfo;
3329
3330 static void
3331 save_attachments_response (GtkDialog *dialog,
3332                            gint       arg1,
3333                            gpointer   user_data)  
3334 {
3335         TnyList *mime_parts;
3336         gchar *chooser_uri;
3337         GList *files_to_save = NULL;
3338         gchar *current_folder;
3339         SaveAttachmentsInfo *sa_info = (SaveAttachmentsInfo *) user_data;
3340
3341         mime_parts = TNY_LIST (sa_info->attachments_list);
3342
3343         if (arg1 != GTK_RESPONSE_OK)
3344                 goto end;
3345
3346         chooser_uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dialog));
3347         current_folder = gtk_file_chooser_get_current_folder_uri (GTK_FILE_CHOOSER (dialog));
3348         if (current_folder && *current_folder != '\0') {
3349                 GError *err = NULL;
3350                 modest_conf_set_string (modest_runtime_get_conf (), MODEST_CONF_LATEST_SAVE_ATTACHMENT_PATH,
3351                                         current_folder,&err);
3352                 if (err != NULL) {
3353                         g_debug ("Error storing latest used folder: %s", err->message);
3354                         g_error_free (err);
3355                 }
3356         }
3357         g_free (current_folder);
3358
3359         if (!modest_utils_folder_writable (chooser_uri)) {
3360                 const gchar *err_msg;
3361
3362 #ifdef MODEST_PLATFORM_MAEMO
3363                 if (modest_maemo_utils_in_usb_mode ()) {
3364                         err_msg = dgettext ("hildon-status-bar-usb", "usbh_ib_mmc_usb_connected");
3365                 } else {
3366                         err_msg = _FM_READ_ONLY_LOCATION;
3367                 }
3368 #else
3369                 err_msg = _FM_READ_ONLY_LOCATION;
3370 #endif
3371                 modest_platform_system_banner (NULL, NULL, err_msg);
3372         } else {
3373                 TnyIterator *iter;
3374
3375                 iter = tny_list_create_iterator (mime_parts);
3376                 while (!tny_iterator_is_done (iter)) {
3377                         TnyMimePart *mime_part = (TnyMimePart *) tny_iterator_get_current (iter);
3378
3379                         if ((modest_tny_mime_part_is_attachment_for_modest (mime_part)) &&
3380                             !tny_mime_part_is_purged (mime_part) &&
3381                             (tny_mime_part_get_filename (mime_part) != NULL)) {
3382                                 SaveMimePartPair *pair;
3383
3384                                 pair = g_slice_new0 (SaveMimePartPair);
3385
3386                                 if (tny_list_get_length (mime_parts) > 1) {
3387                                         gchar *escaped = 
3388                                                 gnome_vfs_escape_slashes (tny_mime_part_get_filename (mime_part));
3389                                         pair->filename = g_build_filename (chooser_uri, escaped, NULL);
3390                                         g_free (escaped);
3391                                 } else {
3392                                         pair->filename = g_strdup (chooser_uri);
3393                                 }
3394                                 pair->part = mime_part;
3395                                 files_to_save = g_list_prepend (files_to_save, pair);
3396                         }
3397                         tny_iterator_next (iter);
3398                 }
3399                 g_object_unref (iter);
3400         }
3401
3402         if (files_to_save != NULL) {
3403                 SaveMimePartInfo *info = g_slice_new0 (SaveMimePartInfo);
3404                 info->pairs = files_to_save;
3405                 info->result = TRUE;
3406                 info->uri = g_strdup (chooser_uri);
3407                 info->window = g_object_ref (sa_info->window);
3408                 save_mime_parts_to_file_with_checks ((GtkWindow *) dialog, info);
3409         }
3410         g_free (chooser_uri);
3411
3412  end:
3413         /* Free and close the dialog */
3414         g_object_unref (mime_parts);
3415         g_object_unref (sa_info->window);
3416         g_slice_free (SaveAttachmentsInfo, sa_info);
3417         gtk_widget_destroy (GTK_WIDGET (dialog));
3418 }
3419
3420 static gboolean
3421 msg_is_attachment (TnyList *mime_parts)
3422 {
3423         TnyIterator *iter;
3424         gboolean retval = FALSE;
3425
3426         if (tny_list_get_length (mime_parts) > 1)
3427                 return FALSE;
3428
3429         iter = tny_list_create_iterator (mime_parts);
3430         if (iter) {
3431                 TnyMimePart *part = TNY_MIME_PART (tny_iterator_get_current (iter));
3432                 if (part) {
3433                         if (TNY_IS_MSG (part))
3434                                 retval = TRUE;
3435                         g_object_unref (part);
3436                 }
3437                 g_object_unref (iter);
3438         }
3439         return retval;
3440 }
3441
3442 void
3443 modest_msg_view_window_save_attachments (ModestMsgViewWindow *window, 
3444                                          TnyList *mime_parts)
3445 {
3446         ModestMsgViewWindowPrivate *priv;
3447         GtkWidget *save_dialog = NULL;
3448         gchar *conf_folder = NULL;
3449         gchar *filename = NULL;
3450         gchar *save_multiple_str = NULL;
3451         const gchar *root_folder = "file:///";
3452
3453         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
3454         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
3455
3456         if (mime_parts == NULL) {
3457                 gboolean allow_msgs = FALSE;
3458
3459                 /* In Hildon 2.2 save and delete operate over all the attachments as there's no
3460                  * selection available */
3461                 mime_parts = modest_msg_view_get_attachments (MODEST_MSG_VIEW (priv->msg_view));
3462
3463                 /* Check if the message is composed by an unique MIME
3464                    part whose content disposition is attachment. There
3465                    could be messages like this:
3466
3467                    Date: Tue, 12 Jan 2010 20:40:59 +0000
3468                    From: <sender@example.org>
3469                    To: <recipient@example.org>
3470                    Subject: no body
3471                    Content-Type: image/jpeg
3472                    Content-Disposition: attachment; filename="bug7718.jpeg"
3473
3474                    whose unique MIME part is the message itself whose
3475                    content disposition is attachment
3476                 */
3477                 if (mime_parts && msg_is_attachment (mime_parts))
3478                         allow_msgs = TRUE;
3479
3480                 if (mime_parts && !modest_toolkit_utils_select_attachments (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (window))), mime_parts, allow_msgs)) {
3481                         g_object_unref (mime_parts);
3482                         return;
3483                 }
3484
3485                 if (mime_parts == NULL || tny_list_get_length (mime_parts) == 0) {
3486                         if (mime_parts) {
3487                                 g_object_unref (mime_parts);
3488                                 mime_parts = NULL;
3489                         }
3490                         return;
3491                 }
3492         } else {
3493                 g_object_ref (mime_parts);
3494         }
3495
3496         /* prepare dialog */
3497         if (tny_list_get_length (mime_parts) == 1) {
3498                 TnyIterator *iter;
3499                 /* only one attachment selected */
3500                 iter = tny_list_create_iterator (mime_parts);
3501                 TnyMimePart *mime_part = (TnyMimePart *) tny_iterator_get_current (iter);
3502                 g_object_unref (iter);
3503                 if (!modest_tny_mime_part_is_msg (mime_part) && 
3504                     modest_tny_mime_part_is_attachment_for_modest (mime_part) &&
3505                     !tny_mime_part_is_purged (mime_part)) {
3506                         filename = g_strdup (tny_mime_part_get_filename (mime_part));
3507                 } else {
3508                         /* TODO: show any error? */
3509                         g_warning ("%s: Tried to save a non-file attachment", __FUNCTION__);
3510                         g_object_unref (mime_parts);
3511                         return;
3512                 }
3513                 g_object_unref (mime_part);
3514         } else {
3515                 gint num = tny_list_get_length (mime_parts);
3516                 save_multiple_str = g_strdup_printf (dngettext("hildon-fm",
3517                                                                "sfil_va_number_of_objects_attachment",
3518                                                               "sfil_va_number_of_objects_attachments",
3519                                                               num), num);
3520         }
3521
3522         /* Creation of hildon file chooser dialog for saving */
3523         save_dialog = modest_toolkit_factory_create_file_chooser_dialog (modest_runtime_get_toolkit_factory (),
3524                                                                          "",
3525                                                                          (GtkWindow *) window,
3526                                                                          GTK_FILE_CHOOSER_ACTION_SAVE);
3527
3528         /* Get last used folder */
3529         conf_folder = modest_conf_get_string (modest_runtime_get_conf (),
3530                                               MODEST_CONF_LATEST_SAVE_ATTACHMENT_PATH, NULL);
3531
3532         /* File chooser stops working if we select "file:///" as current folder */
3533         if (conf_folder && g_ascii_strcasecmp (root_folder, conf_folder) != 0) {
3534                 g_free (conf_folder);
3535                 conf_folder = NULL;
3536         }
3537
3538         if (conf_folder && conf_folder[0] != '\0') {
3539                 gtk_file_chooser_set_current_folder_uri (GTK_FILE_CHOOSER (save_dialog), conf_folder);
3540         } else {
3541                 gchar *docs_folder;
3542                 /* Set the default folder to documents folder */
3543                 docs_folder = (gchar *) g_strdup(g_get_user_special_dir (G_USER_DIRECTORY_DOCUMENTS));
3544                 if (!docs_folder) {
3545                         /* fallback */
3546                         docs_folder = g_build_filename (g_getenv (MYDOCS_ENV), DOCS_FOLDER, NULL);
3547                 }
3548                 gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (save_dialog), docs_folder);
3549                 g_free (docs_folder);
3550         }
3551         g_free (conf_folder);
3552
3553         /* set filename */
3554         if (filename) {
3555                 gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (save_dialog), 
3556                                                    filename);
3557                 g_free (filename);
3558         }
3559
3560         /* if multiple, set multiple string */
3561         if (save_multiple_str) {
3562                 g_object_set (G_OBJECT (save_dialog), "save-multiple", save_multiple_str, NULL);
3563                 gtk_window_set_title (GTK_WINDOW (save_dialog), _FM_SAVE_OBJECT_FILES);
3564                 g_free (save_multiple_str);
3565         }
3566
3567         /* We must run this asynchronously, because the hildon dialog
3568            performs a gtk_dialog_run by itself which leads to gdk
3569            deadlocks */
3570         SaveAttachmentsInfo *sa_info;
3571         sa_info = g_slice_new (SaveAttachmentsInfo);
3572         sa_info->attachments_list = mime_parts;
3573         sa_info->window = g_object_ref (window);
3574         g_signal_connect (save_dialog, "response", 
3575                           G_CALLBACK (save_attachments_response), sa_info);
3576
3577         gtk_widget_show_all (save_dialog);
3578 }
3579
3580 static gboolean
3581 show_remove_attachment_information (gpointer userdata)
3582 {
3583         ModestMsgViewWindow *window = (ModestMsgViewWindow *) userdata;
3584         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
3585
3586         /* We're outside the main lock */
3587         gdk_threads_enter ();
3588
3589         if (priv->remove_attachment_banner != NULL) {
3590                 gtk_widget_destroy (priv->remove_attachment_banner);
3591                 g_object_unref (priv->remove_attachment_banner);
3592         }
3593
3594         priv->remove_attachment_banner = g_object_ref (
3595                 modest_platform_animation_banner (NULL, NULL, _("mcen_me_inbox_remove_attachments")));
3596
3597         gdk_threads_leave ();
3598
3599         return FALSE;
3600 }
3601
3602 void
3603 modest_msg_view_window_remove_attachments (ModestMsgViewWindow *window, gboolean get_all)
3604 {
3605         ModestMsgViewWindowPrivate *priv;
3606         TnyList *mime_parts = NULL, *tmp;
3607         gchar *confirmation_message;
3608         gint response;
3609         gint n_attachments;
3610         TnyMsg *msg;
3611         TnyIterator *iter;
3612
3613         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
3614         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
3615
3616 #ifdef MODEST_TOOLKIT_HILDON2
3617         /* In hildon 2.2 we ignore the get_all flag as we always get all attachments. This is
3618          * because we don't have selection
3619          */
3620         mime_parts = modest_msg_view_get_attachments (MODEST_MSG_VIEW (priv->msg_view));
3621
3622         /* Remove already purged messages from mime parts list. We use
3623            a copy of the list to remove items in the original one */
3624         tmp = tny_list_copy (mime_parts);
3625         iter = tny_list_create_iterator (tmp);
3626         while (!tny_iterator_is_done (iter)) {
3627                 TnyMimePart *part = TNY_MIME_PART (tny_iterator_get_current (iter));
3628                 if (tny_mime_part_is_purged (part))
3629                         tny_list_remove (mime_parts, (GObject *) part);
3630
3631                 g_object_unref (part);
3632                 tny_iterator_next (iter);
3633         }
3634         g_object_unref (tmp);
3635         g_object_unref (iter);
3636
3637         if (!modest_toolkit_utils_select_attachments (GTK_WINDOW (window), mime_parts, TRUE) ||
3638             tny_list_get_length (mime_parts) == 0) {
3639                 g_object_unref (mime_parts);
3640                 return;
3641         }
3642 #else
3643         /* In gtk we get only selected attachments for the operation.
3644          */
3645         mime_parts = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
3646
3647         /* Remove already purged messages from mime parts list. We use
3648            a copy of the list to remove items in the original one */
3649         tmp = tny_list_copy (mime_parts);
3650         iter = tny_list_create_iterator (tmp);
3651         while (!tny_iterator_is_done (iter)) {
3652                 TnyMimePart *part = TNY_MIME_PART (tny_iterator_get_current (iter));
3653                 if (tny_mime_part_is_purged (part))
3654                         tny_list_remove (mime_parts, (GObject *) part);
3655
3656                 g_object_unref (part);
3657                 tny_iterator_next (iter);
3658         }
3659         g_object_unref (tmp);
3660         g_object_unref (iter);
3661
3662         if (tny_list_get_length (mime_parts) == 0) {
3663                 g_object_unref (mime_parts);
3664                 return;
3665         }
3666 #endif
3667
3668         n_attachments = tny_list_get_length (mime_parts);
3669         if (n_attachments == 1) {
3670                 gchar *filename;
3671                 TnyMimePart *part;
3672
3673                 iter = tny_list_create_iterator (mime_parts);
3674                 part = (TnyMimePart *) tny_iterator_get_current (iter);
3675                 g_object_unref (iter);
3676                 if (modest_tny_mime_part_is_msg (part)) {
3677                         TnyHeader *header;
3678                         header = tny_msg_get_header (TNY_MSG (part));
3679                         filename = tny_header_dup_subject (header);
3680                         g_object_unref (header);
3681                         if (filename == NULL)
3682                                 filename = g_strdup (_("mail_va_no_subject"));
3683                 } else {
3684                         filename = g_strdup (tny_mime_part_get_filename (TNY_MIME_PART (part)));
3685                 }
3686                 confirmation_message = g_strdup_printf (_("mcen_nc_purge_file_text"), filename);
3687                 g_free (filename);
3688                 g_object_unref (part);
3689         } else {
3690                 confirmation_message = g_strdup_printf (ngettext("mcen_nc_purge_file_text", 
3691                                                                  "mcen_nc_purge_files_text", 
3692                                                                  n_attachments), n_attachments);
3693         }
3694         response = modest_platform_run_confirmation_dialog (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (window))),
3695                                                             confirmation_message);
3696         g_free (confirmation_message);
3697
3698         if (response != GTK_RESPONSE_OK) {
3699                 g_object_unref (mime_parts);
3700                 return;
3701         }
3702
3703         priv->purge_timeout = g_timeout_add (2000, show_remove_attachment_information, window);
3704
3705         iter = tny_list_create_iterator (mime_parts);
3706         while (!tny_iterator_is_done (iter)) {
3707                 TnyMimePart *part;
3708
3709                 part = (TnyMimePart *) tny_iterator_get_current (iter);
3710                 tny_mime_part_set_purged (TNY_MIME_PART (part));
3711                 g_object_unref (part);
3712                 tny_iterator_next (iter);
3713         }
3714         g_object_unref (iter);
3715
3716         msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
3717         tny_msg_view_clear (TNY_MSG_VIEW (priv->msg_view));
3718         tny_msg_rewrite_cache (msg);
3719         tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
3720         g_object_unref (msg);
3721         update_branding (MODEST_MSG_VIEW_WINDOW (window));
3722
3723         g_object_unref (mime_parts);
3724
3725         if (priv->purge_timeout > 0) {
3726                 g_source_remove (priv->purge_timeout);
3727                 priv->purge_timeout = 0;
3728         }
3729
3730         if (priv->remove_attachment_banner) {
3731                 gtk_widget_destroy (priv->remove_attachment_banner);
3732                 g_object_unref (priv->remove_attachment_banner);
3733                 priv->remove_attachment_banner = NULL;
3734         }
3735 }
3736
3737
3738 static void
3739 update_window_title (ModestMsgViewWindow *window)
3740 {
3741         ModestMsgViewWindowPrivate *priv;
3742         TnyMsg *msg = NULL;
3743         TnyHeader *header = NULL;
3744         gchar *subject = NULL;
3745
3746         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
3747
3748         /* Note that if the window is closed while we're retrieving
3749            the message, this widget could de deleted */
3750         if (!priv->msg_view)
3751                 return;
3752
3753         msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
3754
3755         if (priv->other_body) {
3756                 gchar *description;
3757
3758                 description = modest_tny_mime_part_get_header_value (priv->other_body, "Content-Description");
3759                 if (description) {
3760                         g_strstrip (description);
3761                         subject = description;
3762                 }
3763         } else if (msg != NULL) {
3764                 header = tny_msg_get_header (msg);
3765                 subject = tny_header_dup_subject (header);
3766                 g_object_unref (header);
3767                 g_object_unref (msg);
3768         }
3769
3770         if ((subject == NULL)||(subject[0] == '\0')) {
3771                 g_free (subject);
3772                 subject = g_strdup (_("mail_va_no_subject"));
3773         }
3774
3775         modest_window_set_title (MODEST_WINDOW (window), subject);
3776 }
3777
3778
3779 static void
3780 on_move_focus (GtkWidget *widget,
3781                GtkDirectionType direction,
3782                gpointer userdata)
3783 {
3784         g_signal_stop_emission_by_name (G_OBJECT (widget), "move-focus");
3785 }
3786
3787 static TnyStream *
3788 fetch_image_open_stream (TnyStreamCache *self, gint64 *expected_size, gchar *uri)
3789 {
3790         GnomeVFSResult result;
3791         GnomeVFSHandle *handle = NULL;
3792         GnomeVFSFileInfo *info = NULL;
3793         TnyStream *stream;
3794
3795         result = gnome_vfs_open (&handle, uri, GNOME_VFS_OPEN_READ);
3796         if (result != GNOME_VFS_OK) {
3797                 *expected_size = 0;
3798                 return NULL;
3799         }
3800         
3801         info = gnome_vfs_file_info_new ();
3802         result = gnome_vfs_get_file_info_from_handle (handle, info, GNOME_VFS_FILE_INFO_DEFAULT);
3803         if (result != GNOME_VFS_OK || ! (info->valid_fields & GNOME_VFS_FILE_INFO_FIELDS_SIZE)) {
3804                 /* We put a "safe" default size for going to cache */
3805                 *expected_size = (300*1024);
3806         } else {
3807                 *expected_size = info->size;
3808         }
3809         gnome_vfs_file_info_unref (info);
3810
3811         stream = tny_vfs_stream_new (handle);
3812
3813         return stream;
3814
3815 }
3816
3817 typedef struct {
3818         gchar *uri;
3819         gchar *cache_id;
3820         TnyStream *output_stream;
3821         GtkWidget *msg_view;
3822         GtkWidget *window;
3823 } FetchImageData;
3824
3825 gboolean
3826 on_fetch_image_timeout_refresh_view (gpointer userdata)
3827 {
3828         ModestMsgViewWindowPrivate *priv;
3829
3830         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (userdata);
3831         update_progress_hint (MODEST_MSG_VIEW_WINDOW (userdata));
3832         /* Note that priv->msg_view is set to NULL when this window is
3833            distroyed */
3834         if (priv->msg_view && GTK_WIDGET_DRAWABLE (priv->msg_view)) {
3835                 gtk_widget_queue_draw (GTK_WIDGET (priv->msg_view));
3836         }
3837         priv->fetch_image_redraw_handler = 0;
3838         g_object_unref (userdata);
3839         return FALSE;
3840 }
3841
3842 gboolean
3843 on_fetch_image_idle_refresh_view (gpointer userdata)
3844 {
3845
3846         FetchImageData *fidata = (FetchImageData *) userdata;
3847
3848         gdk_threads_enter ();
3849         if (GTK_WIDGET_DRAWABLE (fidata->msg_view)) {
3850                 ModestMsgViewWindowPrivate *priv;
3851
3852                 priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (fidata->window);
3853                 priv->fetching_images--;
3854                 if (priv->fetch_image_redraw_handler == 0) {
3855                         priv->fetch_image_redraw_handler = g_timeout_add (500, on_fetch_image_timeout_refresh_view, g_object_ref (fidata->window));
3856                 }
3857
3858         }
3859         gdk_threads_leave ();
3860
3861         g_object_unref (fidata->msg_view);
3862         g_object_unref (fidata->window);
3863         g_slice_free (FetchImageData, fidata);
3864         return FALSE;
3865 }
3866
3867 static gpointer
3868 on_fetch_image_thread (gpointer userdata)
3869 {
3870         FetchImageData *fidata = (FetchImageData *) userdata;
3871         TnyStreamCache *cache;
3872         TnyStream *cache_stream;
3873
3874         cache = modest_runtime_get_images_cache ();
3875         cache_stream = 
3876                 tny_stream_cache_get_stream (cache, 
3877                                              fidata->cache_id, 
3878                                              (TnyStreamCacheOpenStreamFetcher) fetch_image_open_stream, 
3879                                              (gpointer) fidata->uri);
3880         g_free (fidata->cache_id);
3881         g_free (fidata->uri);
3882
3883         if (cache_stream != NULL) {
3884                 char buffer[4096];
3885
3886                 while (G_LIKELY (!tny_stream_is_eos (cache_stream))) {
3887                         gssize nb_read;
3888
3889                         nb_read = tny_stream_read (cache_stream, buffer, sizeof (buffer));
3890                         if (G_UNLIKELY (nb_read < 0)) {
3891                                 break;
3892                         } else if (G_LIKELY (nb_read > 0)) {
3893                                 gssize nb_written = 0;
3894
3895                                 while (G_UNLIKELY (nb_written < nb_read)) {
3896                                         gssize len;
3897
3898                                         len = tny_stream_write (fidata->output_stream, buffer + nb_written,
3899                                                                 nb_read - nb_written);
3900                                         if (G_UNLIKELY (len < 0))
3901                                                 break;
3902                                         nb_written += len;
3903                                 }
3904                         }
3905                 }
3906                 tny_stream_close (cache_stream);
3907                 g_object_unref (cache_stream);
3908         }
3909
3910         tny_stream_close (fidata->output_stream);
3911         g_object_unref (fidata->output_stream);
3912
3913         g_idle_add (on_fetch_image_idle_refresh_view, fidata);
3914
3915         return NULL;
3916 }
3917
3918 static gboolean
3919 on_fetch_image (ModestMsgView *msgview,
3920                 const gchar *uri,
3921                 TnyStream *stream,
3922                 ModestMsgViewWindow *window)
3923 {
3924         const gchar *current_account;
3925         ModestMsgViewWindowPrivate *priv;
3926         FetchImageData *fidata;
3927
3928         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
3929
3930         current_account = modest_window_get_active_account (MODEST_WINDOW (window));
3931
3932         fidata = g_slice_new0 (FetchImageData);
3933         fidata->msg_view = g_object_ref (msgview);
3934         fidata->window = g_object_ref (window);
3935         fidata->uri = g_strdup (uri);
3936         fidata->cache_id = modest_images_cache_get_id (current_account, uri);
3937         fidata->output_stream = g_object_ref (stream);
3938
3939         priv->fetching_images++;
3940         if (g_thread_create (on_fetch_image_thread, fidata, FALSE, NULL) == NULL) {
3941                 g_object_unref (fidata->output_stream);
3942                 g_free (fidata->cache_id);
3943                 g_free (fidata->uri);
3944                 g_object_unref (fidata->msg_view);
3945                 g_slice_free (FetchImageData, fidata);
3946                 tny_stream_close (stream);
3947                 priv->fetching_images--;
3948                 update_progress_hint (window);
3949                 return FALSE;
3950         }
3951         update_progress_hint (window);
3952
3953         return TRUE;
3954 }
3955
3956 static void 
3957 setup_menu (ModestMsgViewWindow *self)
3958 {
3959         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW(self));
3960
3961         /* Settings menu buttons */
3962         modest_window_add_to_menu (MODEST_WINDOW (self), _("mcen_me_viewer_find"), NULL,
3963                                    MODEST_WINDOW_MENU_CALLBACK (modest_msg_view_window_show_isearch_toolbar),
3964                                    MODEST_DIMMING_CALLBACK (modest_ui_dimming_rules_on_find_in_msg));
3965
3966         modest_window_add_to_menu (MODEST_WINDOW (self),
3967                                    dngettext(GETTEXT_PACKAGE,
3968                                              "mcen_me_move_message",
3969                                              "mcen_me_move_messages",
3970                                              1),
3971                                    NULL,
3972                                    MODEST_WINDOW_MENU_CALLBACK (modest_ui_actions_on_move_to),
3973                                    MODEST_DIMMING_CALLBACK (modest_ui_dimming_rules_on_move_to));
3974
3975         modest_window_add_to_menu (MODEST_WINDOW (self), _("mcen_me_inbox_mark_as_read"), NULL,
3976                                    MODEST_WINDOW_MENU_CALLBACK (modest_ui_actions_on_mark_as_read),
3977                                    MODEST_DIMMING_CALLBACK (modest_ui_dimming_rules_on_mark_as_read_msg_in_view));
3978
3979         modest_window_add_to_menu (MODEST_WINDOW (self), _("mcen_me_inbox_mark_as_unread"), NULL,
3980                                    MODEST_WINDOW_MENU_CALLBACK (modest_ui_actions_on_mark_as_unread),
3981                                    MODEST_DIMMING_CALLBACK (modest_ui_dimming_rules_on_mark_as_unread_msg_in_view));
3982
3983         modest_window_add_to_menu (MODEST_WINDOW (self), _("mcen_me_viewer_save_attachments"), NULL,
3984                                    MODEST_WINDOW_MENU_CALLBACK (modest_ui_actions_save_attachments),
3985                                    MODEST_DIMMING_CALLBACK (modest_ui_dimming_rules_on_save_attachments));
3986         modest_window_add_to_menu (MODEST_WINDOW (self), _("mcen_me_inbox_remove_attachments"), NULL,
3987                                    MODEST_WINDOW_MENU_CALLBACK (modest_ui_actions_remove_attachments),
3988                                    MODEST_DIMMING_CALLBACK (modest_ui_dimming_rules_on_remove_attachments));
3989
3990         modest_window_add_to_menu (MODEST_WINDOW (self), _("mcen_me_new_message"), "<Control>n",
3991                                    MODEST_WINDOW_MENU_CALLBACK (modest_ui_actions_on_new_msg),
3992                                    MODEST_DIMMING_CALLBACK (modest_ui_dimming_rules_on_new_msg));
3993         modest_window_add_to_menu (MODEST_WINDOW (self), _("mcen_me_viewer_addtocontacts"), NULL,
3994                                    MODEST_WINDOW_MENU_CALLBACK (modest_ui_actions_add_to_contacts),
3995                                    MODEST_DIMMING_CALLBACK (modest_ui_dimming_rules_on_add_to_contacts));
3996
3997         modest_window_add_to_menu (MODEST_WINDOW (self), _("mcen_ti_message_properties"), NULL,
3998                                    MODEST_WINDOW_MENU_CALLBACK (modest_ui_actions_on_details),
3999                                    MODEST_DIMMING_CALLBACK (modest_ui_dimming_rules_on_details));
4000 }
4001
4002 void
4003 modest_msg_view_window_add_to_contacts (ModestMsgViewWindow *self)
4004 {
4005         ModestMsgViewWindowPrivate *priv;
4006         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
4007         GSList *recipients = NULL;
4008         TnyMsg *msg = NULL;
4009
4010         msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
4011         if (msg == NULL) {
4012                 TnyHeader *header;
4013
4014                 header = modest_msg_view_window_get_header (self);
4015                 if (header == NULL)
4016                         return;
4017                 recipients = modest_tny_msg_header_get_all_recipients_list (header);
4018                 g_object_unref (header);
4019         } else {
4020                 recipients = modest_tny_msg_get_all_recipients_list (msg);
4021                 g_object_unref (msg);
4022         }
4023
4024         if (recipients) {
4025                 /* Offer the user to add recipients to the address book */
4026                 modest_address_book_add_address_list_with_selector (recipients, (GtkWindow *) self);
4027                 g_slist_foreach (recipients, (GFunc) g_free, NULL); g_slist_free (recipients);
4028         }
4029 }
4030
4031 static gboolean 
4032 _modest_msg_view_window_map_event (GtkWidget *widget,
4033                                    GdkEvent *event,
4034                                    gpointer userdata)
4035 {
4036         ModestMsgViewWindow *self = (ModestMsgViewWindow *) userdata;
4037
4038         update_progress_hint (self);
4039
4040         return FALSE;
4041 }
4042
4043 void
4044 modest_msg_view_window_fetch_images (ModestMsgViewWindow *self)
4045 {
4046         ModestMsgViewWindowPrivate *priv;
4047         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
4048
4049         modest_msg_view_request_fetch_images (MODEST_MSG_VIEW (priv->msg_view));
4050 }
4051
4052 gboolean 
4053 modest_msg_view_window_has_blocked_external_images (ModestMsgViewWindow *self)
4054 {
4055         ModestMsgViewWindowPrivate *priv;
4056         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
4057
4058         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), FALSE);
4059
4060         return modest_msg_view_has_blocked_external_images (MODEST_MSG_VIEW (priv->msg_view));
4061 }
4062
4063 void 
4064 modest_msg_view_window_reload (ModestMsgViewWindow *self)
4065 {
4066         ModestMsgViewWindowPrivate *priv;
4067         const gchar *msg_uid;
4068         TnyHeader *header = NULL;
4069         TnyFolder *folder = NULL;
4070
4071         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self));
4072
4073         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
4074
4075         header = modest_msg_view_window_get_header (MODEST_MSG_VIEW_WINDOW (self));
4076         if (!header)
4077                 return;
4078
4079         folder = tny_header_get_folder (header);
4080         g_object_unref (header);
4081
4082         if (!folder)
4083                 return;
4084
4085         msg_uid = modest_msg_view_window_get_message_uid (self);
4086         if (msg_uid) {
4087                 GtkTreeRowReference *row_reference;
4088
4089                 if (priv->row_reference && gtk_tree_row_reference_valid (priv->row_reference)) {
4090                         row_reference = priv->row_reference;
4091                 } else {
4092                         row_reference = NULL;
4093                 }
4094                 if (!message_reader (self, priv, NULL, msg_uid, folder, row_reference))
4095                         g_warning ("Shouldn't happen, trying to reload a message failed");
4096         }
4097
4098         g_object_unref (folder);
4099 }
4100
4101 static void
4102 update_branding (ModestMsgViewWindow *self)
4103 {
4104         const gchar *account; 
4105         const gchar *mailbox;
4106         ModestAccountMgr *mgr;
4107         ModestProtocol *protocol = NULL;
4108         gchar *service_name = NULL;
4109         const GdkPixbuf *service_icon = NULL;
4110         ModestMsgViewWindowPrivate *priv;
4111
4112         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
4113
4114         account = modest_window_get_active_account (MODEST_WINDOW (self));
4115         mailbox = modest_window_get_active_mailbox (MODEST_WINDOW (self));
4116
4117         mgr = modest_runtime_get_account_mgr ();
4118
4119         if (modest_account_mgr_account_is_multimailbox (mgr, account, &protocol)) {
4120                 if (MODEST_IS_ACCOUNT_PROTOCOL (protocol)) {
4121                         service_name = modest_account_protocol_get_service_name (MODEST_ACCOUNT_PROTOCOL (protocol),
4122                                                                                  account, mailbox);
4123                         service_icon = modest_account_protocol_get_service_icon (MODEST_ACCOUNT_PROTOCOL (protocol),
4124                                                                                  account, mailbox, MODEST_ICON_SIZE_SMALL);
4125                 }
4126         }
4127
4128         modest_msg_view_set_branding (MODEST_MSG_VIEW (priv->msg_view), service_name, service_icon);
4129         g_free (service_name);
4130 }
4131
4132 static void
4133 sync_flags (ModestMsgViewWindow *self)
4134 {
4135         TnyHeader *header = NULL;
4136
4137         header = modest_msg_view_window_get_header (self);
4138         if (!header) {
4139                 TnyMsg *msg = modest_msg_view_window_get_message (self);
4140                 if (msg) {
4141                         header = tny_msg_get_header (msg);
4142                         g_object_unref (msg);
4143                 }
4144         }
4145
4146         if (header) {
4147                 TnyFolder *folder = tny_header_get_folder (header);
4148
4149                 if (folder) {
4150                         ModestMailOperation *mail_op;
4151
4152                         /* Sync folder, we need this to save the seen flag */
4153                         mail_op = modest_mail_operation_new (NULL);
4154                         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
4155                                                          mail_op);
4156                         modest_mail_operation_sync_folder (mail_op, folder, FALSE, NULL, NULL);
4157                         g_object_unref (mail_op);
4158                         g_object_unref (folder);
4159                 }
4160                 g_object_unref (header);
4161         }
4162 }
4163
4164 #ifdef MODEST_TOOLKIT_HILDON2
4165 static gboolean
4166 on_realize (GtkWidget *widget,
4167             gpointer userdata)
4168 {
4169         GdkDisplay *display;
4170         Atom atom;
4171         unsigned long val = 1;
4172
4173         display = gdk_drawable_get_display (widget->window);
4174         atom = gdk_x11_get_xatom_by_name_for_display (display, "_HILDON_ZOOM_KEY_ATOM");
4175         XChangeProperty (GDK_DISPLAY_XDISPLAY (display),
4176                          GDK_WINDOW_XID (widget->window), atom,
4177                          XA_INTEGER, 32, PropModeReplace,
4178                          (unsigned char *) &val, 1);
4179
4180         return FALSE;
4181 }
4182 #endif
4183
4184 static gboolean
4185 on_handle_calendar (ModestMsgView *msgview, TnyMimePart *calendar_part, GtkContainer *container, ModestMsgViewWindow *self)
4186 {
4187         const gchar *account_name;
4188         ModestProtocolType proto_type;
4189         ModestProtocol *protocol;
4190         gboolean retval = FALSE;
4191
4192         account_name = modest_window_get_active_account (MODEST_WINDOW (self));
4193         
4194         /* Get proto */
4195         proto_type = modest_account_mgr_get_store_protocol (modest_runtime_get_account_mgr (), 
4196                                                             account_name);
4197         protocol = 
4198                 modest_protocol_registry_get_protocol_by_type (modest_runtime_get_protocol_registry (), 
4199                                                                proto_type);
4200
4201         if (MODEST_IS_ACCOUNT_PROTOCOL (protocol)) {
4202                 retval = modest_account_protocol_handle_calendar (MODEST_ACCOUNT_PROTOCOL (protocol), MODEST_WINDOW (self),
4203                                                                   calendar_part, container);
4204         }
4205         return retval;
4206 }