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