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