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