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