On pressing other bodies in attachment view, open them 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         gboolean is_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->is_other_body = FALSE;
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->header_model != NULL) {
616                 g_object_unref (priv->header_model);
617                 priv->header_model = NULL;
618         }
619
620         if (priv->remove_attachment_banner) {
621                 gtk_widget_destroy (priv->remove_attachment_banner);
622                 g_object_unref (priv->remove_attachment_banner);
623                 priv->remove_attachment_banner = NULL;
624         }
625
626         if (priv->purge_timeout > 0) {
627                 g_source_remove (priv->purge_timeout);
628                 priv->purge_timeout = 0;
629         }
630
631         if (priv->row_reference) {
632                 gtk_tree_row_reference_free (priv->row_reference);
633                 priv->row_reference = NULL;
634         }
635
636         if (priv->next_row_reference) {
637                 gtk_tree_row_reference_free (priv->next_row_reference);
638                 priv->next_row_reference = NULL;
639         }
640
641         if (priv->msg_uid) {
642                 g_free (priv->msg_uid);
643                 priv->msg_uid = NULL;
644         }
645
646         G_OBJECT_CLASS(parent_class)->finalize (obj);
647 }
648
649 static gboolean
650 select_next_valid_row (GtkTreeModel *model,
651                        GtkTreeRowReference **row_reference,
652                        gboolean cycle,
653                        gboolean is_outbox)
654 {
655         GtkTreeIter tmp_iter;
656         GtkTreePath *path;
657         GtkTreePath *next = NULL;
658         gboolean retval = FALSE, finished;
659
660         g_return_val_if_fail (gtk_tree_row_reference_valid (*row_reference), FALSE);
661
662         path = gtk_tree_row_reference_get_path (*row_reference);
663         gtk_tree_model_get_iter (model, &tmp_iter, path);
664         gtk_tree_row_reference_free (*row_reference);
665         *row_reference = NULL;
666
667         finished = FALSE;
668         do {
669                 TnyHeader *header = NULL;
670
671                 if (gtk_tree_model_iter_next (model, &tmp_iter)) {
672                         gtk_tree_model_get (model, &tmp_iter, 
673                                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, 
674                                             &header, -1);
675
676                         if (header) {
677                                 if (msg_is_visible (header, is_outbox)) {
678                                         next = gtk_tree_model_get_path (model, &tmp_iter);
679                                         *row_reference = gtk_tree_row_reference_new (model, next);
680                                         gtk_tree_path_free (next);
681                                         retval = TRUE;
682                                         finished = TRUE;
683                                 }
684                                 g_object_unref (header);
685                                 header = NULL;
686                         }
687                 } else if (cycle && gtk_tree_model_get_iter_first (model, &tmp_iter)) {
688                         next = gtk_tree_model_get_path (model, &tmp_iter);
689                         
690                         /* Ensure that we are not selecting the same */
691                         if (gtk_tree_path_compare (path, next) != 0) {
692                                 gtk_tree_model_get (model, &tmp_iter, 
693                                                     TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, 
694                                                     &header, -1);                               
695                                 if (header) {
696                                         if (msg_is_visible (header, is_outbox)) {
697                                                 *row_reference = gtk_tree_row_reference_new (model, next);
698                                                 retval = TRUE;
699                                                 finished = TRUE;
700                                         }
701                                         g_object_unref (header);
702                                         header = NULL;
703                                 }
704                         } else {
705                                 /* If we ended up in the same message
706                                    then there is no valid next
707                                    message */
708                                 finished = TRUE;
709                         }
710                         gtk_tree_path_free (next);
711                 } else {
712                         /* If there are no more messages and we don't
713                            want to start again in the first one then
714                            there is no valid next message */
715                         finished = TRUE;
716                 }
717         } while (!finished);
718
719         /* Free */
720         gtk_tree_path_free (path);
721
722         return retval;
723 }
724
725 /* TODO: This should be in _init(), with the parameters as properties. */
726 static void
727 modest_msg_view_window_construct (ModestMsgViewWindow *self, 
728                                   const gchar *modest_account_name,
729                                   const gchar *mailbox,
730                                   const gchar *msg_uid)
731 {
732         GObject *obj = NULL;
733         ModestMsgViewWindowPrivate *priv = NULL;
734         ModestWindowPrivate *parent_priv = NULL;
735         ModestDimmingRulesGroup *toolbar_rules_group = NULL;
736         ModestDimmingRulesGroup *clipboard_rules_group = NULL;
737
738         obj = G_OBJECT (self);
739         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(obj);
740         parent_priv = MODEST_WINDOW_GET_PRIVATE(obj);
741
742         priv->msg_uid = g_strdup (msg_uid);
743
744         /* Menubar */
745         parent_priv->menubar = NULL;
746
747         toolbar_rules_group = modest_dimming_rules_group_new (MODEST_DIMMING_RULES_TOOLBAR, TRUE);
748         clipboard_rules_group = modest_dimming_rules_group_new (MODEST_DIMMING_RULES_CLIPBOARD, FALSE);
749
750         setup_menu (self);
751         /* Add common dimming rules */
752         modest_dimming_rules_group_add_rules (toolbar_rules_group, 
753                                               modest_msg_view_toolbar_dimming_entries,
754                                               G_N_ELEMENTS (modest_msg_view_toolbar_dimming_entries),
755                                               MODEST_WINDOW (self));
756         modest_dimming_rules_group_add_rules (clipboard_rules_group, 
757                                               modest_msg_view_clipboard_dimming_entries,
758                                               G_N_ELEMENTS (modest_msg_view_clipboard_dimming_entries),
759                                               MODEST_WINDOW (self));
760
761         /* Insert dimming rules group for this window */
762         modest_ui_dimming_manager_insert_rules_group (parent_priv->ui_dimming_manager, toolbar_rules_group);
763         modest_ui_dimming_manager_insert_rules_group (parent_priv->ui_dimming_manager, clipboard_rules_group);
764         g_object_unref (toolbar_rules_group);
765         g_object_unref (clipboard_rules_group);
766
767         /* g_signal_connect (G_OBJECT(obj), "delete-event", G_CALLBACK(on_delete_event), obj); */
768
769         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);
770         g_signal_connect (G_OBJECT(priv->msg_view), "activate_link",
771                           G_CALLBACK (modest_ui_actions_on_msg_link_clicked), obj);
772         g_signal_connect (G_OBJECT(priv->msg_view), "link_hover",
773                           G_CALLBACK (modest_ui_actions_on_msg_link_hover), obj);
774         g_signal_connect (G_OBJECT(priv->msg_view), "attachment_clicked",
775                           G_CALLBACK (modest_ui_actions_on_msg_attachment_clicked), obj);
776         g_signal_connect (G_OBJECT(priv->msg_view), "recpt_activated",
777                           G_CALLBACK (modest_ui_actions_on_msg_recpt_activated), obj);
778         g_signal_connect (G_OBJECT(priv->msg_view), "show_details",
779                           G_CALLBACK (modest_ui_actions_on_details), obj);
780         g_signal_connect (G_OBJECT(priv->msg_view), "link_contextual",
781                           G_CALLBACK (modest_ui_actions_on_msg_link_contextual), obj);
782         g_signal_connect (G_OBJECT (priv->msg_view), "fetch_image",
783                           G_CALLBACK (on_fetch_image), obj);
784
785         g_signal_connect (G_OBJECT (obj), "key-release-event",
786                           G_CALLBACK (modest_msg_view_window_key_event),
787                           NULL);
788
789         g_signal_connect (G_OBJECT (obj), "key-press-event",
790                           G_CALLBACK (modest_msg_view_window_key_event),
791                           NULL);
792
793         g_signal_connect (G_OBJECT (obj), "move-focus",
794                           G_CALLBACK (on_move_focus), obj);
795
796         g_signal_connect (G_OBJECT (obj), "map-event",
797                           G_CALLBACK (_modest_msg_view_window_map_event),
798                           G_OBJECT (obj));
799
800         /* Mail Operation Queue */
801         priv->queue_change_handler = g_signal_connect (G_OBJECT (modest_runtime_get_mail_operation_queue ()),
802                                                        "queue-changed",
803                                                        G_CALLBACK (on_queue_changed),
804                                                        obj);
805
806         /* Account manager */
807         priv->account_removed_handler = g_signal_connect (G_OBJECT (modest_runtime_get_account_store ()),
808                                                           "account_removed",
809                                                           G_CALLBACK(on_account_removed),
810                                                           obj);
811
812         modest_window_set_active_account (MODEST_WINDOW(obj), modest_account_name);
813         modest_window_set_active_mailbox (MODEST_WINDOW(obj), mailbox);
814
815         g_signal_connect (G_OBJECT (priv->find_toolbar), "close", G_CALLBACK (modest_msg_view_window_find_toolbar_close), obj);
816         g_signal_connect (G_OBJECT (priv->find_toolbar), "search", G_CALLBACK (modest_msg_view_window_find_toolbar_search), obj);
817         priv->last_search = NULL;
818
819         modest_msg_view_window_show_toolbar (MODEST_WINDOW (obj), TRUE);
820
821         /* Init the clipboard actions dim status */
822         modest_msg_view_grab_focus(MODEST_MSG_VIEW (priv->msg_view));
823
824         update_window_title (MODEST_MSG_VIEW_WINDOW (obj));
825
826
827 }
828
829 /* FIXME: parameter checks */
830 ModestWindow *
831 modest_msg_view_window_new_with_header_model (TnyMsg *msg, 
832                                               const gchar *modest_account_name,
833                                               const gchar *mailbox,
834                                               const gchar *msg_uid,
835                                               GtkTreeModel *model, 
836                                               GtkTreeRowReference *row_reference)
837 {
838         ModestMsgViewWindow *window = NULL;
839         ModestMsgViewWindowPrivate *priv = NULL;
840         TnyFolder *header_folder = NULL;
841         ModestHeaderView *header_view = NULL;
842         ModestWindowMgr *mgr = NULL;
843
844         MODEST_DEBUG_BLOCK (
845                modest_tny_mime_part_to_string (TNY_MIME_PART (msg), 0);
846         );
847
848         mgr = modest_runtime_get_window_mgr ();
849         window = MODEST_MSG_VIEW_WINDOW (modest_window_mgr_get_msg_view_window (mgr));
850         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), NULL);
851
852         modest_msg_view_window_construct (window, modest_account_name, mailbox, msg_uid);
853
854         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
855
856         /* Remember the message list's TreeModel so we can detect changes
857          * and change the list selection when necessary: */
858         header_folder = modest_header_view_get_folder (header_view);
859         if (header_folder) {
860                 priv->is_outbox = (modest_tny_folder_guess_folder_type (header_folder) ==
861                                    TNY_FOLDER_TYPE_OUTBOX);
862                 priv->header_folder_id = tny_folder_get_id (header_folder);
863                 g_object_unref(header_folder);
864         }
865
866         /* Setup row references and connect signals */
867         priv->header_model = g_object_ref (model);
868
869         if (row_reference) {
870                 priv->row_reference = gtk_tree_row_reference_copy (row_reference);
871                 priv->next_row_reference = gtk_tree_row_reference_copy (row_reference);
872                 select_next_valid_row (model, &(priv->next_row_reference), TRUE, priv->is_outbox);
873         } else {
874                 priv->row_reference = NULL;
875                 priv->next_row_reference = NULL;
876         }
877
878         /* Connect signals */
879         priv->row_changed_handler = 
880                 g_signal_connect (GTK_TREE_MODEL(model), "row-changed",
881                                   G_CALLBACK(modest_msg_view_window_on_row_changed),
882                                   window);
883         priv->row_deleted_handler = 
884                 g_signal_connect (GTK_TREE_MODEL(model), "row-deleted",
885                                   G_CALLBACK(modest_msg_view_window_on_row_deleted),
886                                   window);
887         priv->row_inserted_handler = 
888                 g_signal_connect (GTK_TREE_MODEL(model), "row-inserted",
889                                   G_CALLBACK(modest_msg_view_window_on_row_inserted),
890                                   window);
891         priv->rows_reordered_handler = 
892                 g_signal_connect(GTK_TREE_MODEL(model), "rows-reordered",
893                                  G_CALLBACK(modest_msg_view_window_on_row_reordered),
894                                  window);
895
896         if (header_view != NULL){
897                 modest_header_view_add_observer(header_view,
898                                 MODEST_HEADER_VIEW_OBSERVER(window));
899         }
900
901         tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
902         update_window_title (MODEST_MSG_VIEW_WINDOW (window));
903
904         /* gtk_widget_show_all (GTK_WIDGET (window)); */
905         modest_msg_view_window_update_priority (window);
906         /* Check dimming rules */
907         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (window));
908         modest_ui_actions_check_menu_dimming_rules (MODEST_WINDOW (window));
909         modest_window_check_dimming_rules_group (MODEST_WINDOW (window), MODEST_DIMMING_RULES_CLIPBOARD);
910
911         return MODEST_WINDOW(window);
912 }
913
914 ModestWindow *
915 modest_msg_view_window_new_from_header_view (ModestHeaderView *header_view,
916                                              const gchar *modest_account_name,
917                                              const gchar *mailbox,
918                                              const gchar *msg_uid,
919                                              GtkTreeRowReference *row_reference)
920 {
921         ModestMsgViewWindow *window = NULL;
922         ModestMsgViewWindowPrivate *priv = NULL;
923         TnyFolder *header_folder = NULL;
924         ModestWindowMgr *mgr = NULL;
925         GtkTreePath *path;
926         GtkTreeIter iter;
927
928         mgr = modest_runtime_get_window_mgr ();
929         window = MODEST_MSG_VIEW_WINDOW (modest_window_mgr_get_msg_view_window (mgr));
930         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), NULL);
931
932         modest_msg_view_window_construct (window, modest_account_name, mailbox, msg_uid);
933
934         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
935
936         /* Remember the message list's TreeModel so we can detect changes 
937          * and change the list selection when necessary: */
938
939         if (header_view != NULL){
940                 header_folder = modest_header_view_get_folder(header_view);
941                 /* This could happen if the header folder was
942                    unseleted before opening this msg window (for
943                    example if the user selects an account in the
944                    folder view of the main window */
945                 if (header_folder) {
946                         priv->is_outbox = (modest_tny_folder_guess_folder_type (header_folder) == 
947                                            TNY_FOLDER_TYPE_OUTBOX);
948                         priv->header_folder_id = tny_folder_get_id(header_folder);
949                         g_object_unref(header_folder);
950                 }
951         }
952
953         /* Setup row references and connect signals */
954         priv->header_model = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
955         g_object_ref (priv->header_model);
956
957         if (row_reference) {
958                 priv->row_reference = gtk_tree_row_reference_copy (row_reference);
959                 priv->next_row_reference = gtk_tree_row_reference_copy (row_reference);
960                 select_next_valid_row (priv->header_model, &(priv->next_row_reference), TRUE, priv->is_outbox);
961         } else {
962                 priv->row_reference = NULL;
963                 priv->next_row_reference = NULL;
964         }
965
966         /* Connect signals */
967         priv->row_changed_handler = 
968                 g_signal_connect (GTK_TREE_MODEL(priv->header_model), "row-changed",
969                                   G_CALLBACK(modest_msg_view_window_on_row_changed),
970                                   window);
971         priv->row_deleted_handler = 
972                 g_signal_connect (GTK_TREE_MODEL(priv->header_model), "row-deleted",
973                                   G_CALLBACK(modest_msg_view_window_on_row_deleted),
974                                   window);
975         priv->row_inserted_handler = 
976                 g_signal_connect (GTK_TREE_MODEL(priv->header_model), "row-inserted",
977                                   G_CALLBACK(modest_msg_view_window_on_row_inserted),
978                                   window);
979         priv->rows_reordered_handler = 
980                 g_signal_connect(GTK_TREE_MODEL(priv->header_model), "rows-reordered",
981                                  G_CALLBACK(modest_msg_view_window_on_row_reordered),
982                                  window);
983
984         if (header_view != NULL){
985                 modest_header_view_add_observer(header_view,
986                                                 MODEST_HEADER_VIEW_OBSERVER(window));
987         }
988
989         tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), NULL);
990
991         path = gtk_tree_row_reference_get_path (row_reference);
992         if (gtk_tree_model_get_iter (priv->header_model, &iter, path)) {
993                 TnyHeader *header;
994                 gtk_tree_model_get (priv->header_model, &iter, 
995                                     TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
996                                     &header, -1);
997                 message_reader (window, priv, header, row_reference);
998                 g_object_unref (header);
999         }
1000         gtk_tree_path_free (path);
1001
1002         /* Check dimming rules */
1003         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (window));
1004         modest_ui_actions_check_menu_dimming_rules (MODEST_WINDOW (window));
1005         modest_window_check_dimming_rules_group (MODEST_WINDOW (window), MODEST_DIMMING_RULES_CLIPBOARD);
1006
1007         return MODEST_WINDOW(window);
1008 }
1009
1010 ModestWindow *
1011 modest_msg_view_window_new_for_search_result (TnyMsg *msg, 
1012                                               const gchar *modest_account_name,
1013                                               const gchar *mailbox,
1014                                               const gchar *msg_uid)
1015 {
1016         ModestMsgViewWindow *window = NULL;
1017         ModestMsgViewWindowPrivate *priv = NULL;
1018         ModestWindowMgr *mgr = NULL;
1019
1020         mgr = modest_runtime_get_window_mgr ();
1021         window = MODEST_MSG_VIEW_WINDOW (modest_window_mgr_get_msg_view_window (mgr));
1022         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), NULL);
1023         modest_msg_view_window_construct (window, modest_account_name, mailbox, msg_uid);
1024
1025         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1026
1027         /* Remember that this is a search result, 
1028          * so we can disable some UI appropriately: */
1029         priv->is_search_result = TRUE;
1030
1031         tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
1032         
1033         update_window_title (window);
1034         /* gtk_widget_show_all (GTK_WIDGET (window));*/
1035         modest_msg_view_window_update_priority (window);
1036
1037         /* Check dimming rules */
1038         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (window));
1039         modest_ui_actions_check_menu_dimming_rules (MODEST_WINDOW (window));
1040         modest_window_check_dimming_rules_group (MODEST_WINDOW (window), MODEST_DIMMING_RULES_CLIPBOARD);
1041
1042         return MODEST_WINDOW(window);
1043 }
1044
1045 ModestWindow *
1046 modest_msg_view_window_new_with_other_body (TnyMsg *msg, 
1047                                             TnyMimePart *other_body,
1048                                             const gchar *modest_account_name,
1049                                             const gchar *mailbox,
1050                                             const gchar *msg_uid)
1051 {
1052         GObject *obj = NULL;
1053         ModestMsgViewWindowPrivate *priv;       
1054         ModestWindowMgr *mgr = NULL;
1055
1056         g_return_val_if_fail (msg, NULL);
1057         mgr = modest_runtime_get_window_mgr ();
1058         obj = G_OBJECT (modest_window_mgr_get_msg_view_window (mgr));
1059         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (obj);
1060         modest_msg_view_window_construct (MODEST_MSG_VIEW_WINDOW (obj), 
1061                                           modest_account_name, mailbox, msg_uid);
1062
1063         if (other_body) {
1064                 priv->is_other_body = TRUE;
1065                 modest_msg_view_set_msg_with_other_body (MODEST_MSG_VIEW (priv->msg_view), msg, other_body);
1066         } else {
1067                 tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
1068         }
1069         update_window_title (MODEST_MSG_VIEW_WINDOW (obj));
1070
1071         /* gtk_widget_show_all (GTK_WIDGET (obj)); */
1072
1073         /* Check dimming rules */
1074         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (obj));
1075         modest_ui_actions_check_menu_dimming_rules (MODEST_WINDOW (obj));
1076         modest_window_check_dimming_rules_group (MODEST_WINDOW (obj), MODEST_DIMMING_RULES_CLIPBOARD);
1077
1078         return MODEST_WINDOW(obj);
1079 }
1080
1081 ModestWindow *
1082 modest_msg_view_window_new_for_attachment (TnyMsg *msg, 
1083                                            const gchar *modest_account_name,
1084                                            const gchar *mailbox,
1085                                            const gchar *msg_uid)
1086 {
1087         return modest_msg_view_window_new_with_other_body (msg, NULL, modest_account_name, mailbox, msg_uid);
1088 }
1089
1090 static void
1091 modest_msg_view_window_on_row_changed (GtkTreeModel *header_model,
1092                                        GtkTreePath *arg1,
1093                                        GtkTreeIter *arg2,
1094                                        ModestMsgViewWindow *window)
1095 {
1096         check_dimming_rules_after_change (window);
1097 }
1098
1099 static void 
1100 modest_msg_view_window_on_row_deleted(GtkTreeModel *header_model,
1101                                       GtkTreePath *arg1,
1102                                       ModestMsgViewWindow *window)
1103 {
1104         check_dimming_rules_after_change (window);
1105 }
1106         /* The window could have dissapeared */
1107
1108 static void
1109 check_dimming_rules_after_change (ModestMsgViewWindow *window)
1110 {
1111         modest_ui_actions_check_menu_dimming_rules (MODEST_WINDOW (window));
1112         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (window));
1113 }
1114
1115
1116 /* On insertions we check if the folder still has the message we are
1117  * showing or do not. If do not, we do nothing. Which means we are still
1118  * not attached to any header folder and thus next/prev buttons are
1119  * still dimmed. Once the message that is shown by msg-view is found, the
1120  * new model of header-view will be attached and the references will be set.
1121  * On each further insertions dimming rules will be checked. However
1122  * this requires extra CPU time at least works.
1123  * (An message might be deleted from TnyFolder and thus will not be
1124  * inserted into the model again for example if it is removed by the
1125  * imap server and the header view is refreshed.)
1126  */
1127 static void 
1128 modest_msg_view_window_on_row_inserted (GtkTreeModel *model,
1129                                         GtkTreePath *tree_path,
1130                                         GtkTreeIter *tree_iter,
1131                                         ModestMsgViewWindow *window)
1132 {
1133         ModestMsgViewWindowPrivate *priv = NULL; 
1134         TnyHeader *header = NULL;
1135
1136         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
1137         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1138
1139         g_assert (model == priv->header_model);
1140         
1141         /* Check if the newly inserted message is the same we are actually
1142          * showing. IF not, we should remain detached from the header model
1143          * and thus prev and next toolbar buttons should remain dimmed. */
1144         gtk_tree_model_get (model, tree_iter, 
1145                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, 
1146                             &header, -1);
1147
1148         if (TNY_IS_HEADER (header)) {
1149                 gchar *uid = NULL;
1150
1151                 uid = modest_tny_folder_get_header_unique_id (header);
1152                 if (!g_str_equal(priv->msg_uid, uid)) {
1153                         check_dimming_rules_after_change (window);
1154                         g_free(uid);
1155                         g_object_unref (G_OBJECT(header));
1156                         return;
1157                 }
1158                 g_free(uid);
1159                 g_object_unref(G_OBJECT(header));
1160         }
1161
1162         if (priv->row_reference) {
1163                 gtk_tree_row_reference_free (priv->row_reference); 
1164         }
1165
1166         /* Setup row_reference for the actual msg. */
1167         priv->row_reference = gtk_tree_row_reference_new (priv->header_model, tree_path);
1168         if (priv->row_reference == NULL) {
1169                 g_warning("No reference for msg header item.");
1170                 return;
1171         }
1172
1173         /* Now set up next_row_reference. */
1174         if (priv->next_row_reference) {
1175                 gtk_tree_row_reference_free (priv->next_row_reference); 
1176         }
1177
1178         priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
1179         select_next_valid_row (priv->header_model,
1180                                &(priv->next_row_reference), FALSE, priv->is_outbox);
1181
1182         /* Connect the remaining callbacks to become able to detect
1183          * changes in header-view. */
1184         priv->row_changed_handler = 
1185                 g_signal_connect (priv->header_model, "row-changed",
1186                                   G_CALLBACK (modest_msg_view_window_on_row_changed),
1187                                   window);
1188         priv->row_deleted_handler = 
1189                 g_signal_connect (priv->header_model, "row-deleted",
1190                                   G_CALLBACK (modest_msg_view_window_on_row_deleted),
1191                                   window);
1192         priv->rows_reordered_handler = 
1193                 g_signal_connect (priv->header_model, "rows-reordered",
1194                                   G_CALLBACK (modest_msg_view_window_on_row_reordered),
1195                                   window);
1196
1197         check_dimming_rules_after_change (window);      
1198 }
1199
1200 static void 
1201 modest_msg_view_window_on_row_reordered (GtkTreeModel *header_model,
1202                                          GtkTreePath *arg1,
1203                                          GtkTreeIter *arg2,
1204                                          gpointer arg3,
1205                                          ModestMsgViewWindow *window)
1206 {
1207         ModestMsgViewWindowPrivate *priv = NULL; 
1208         gboolean already_changed = FALSE;
1209
1210         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(window);
1211
1212         /* If the current row was reordered select the proper next
1213            valid row. The same if the next row reference changes */
1214         if (priv->row_reference && 
1215             gtk_tree_row_reference_valid (priv->row_reference)) {
1216                 GtkTreePath *path;
1217                 path = gtk_tree_row_reference_get_path (priv->row_reference);
1218                 if (gtk_tree_path_compare (path, arg1) == 0) {
1219                         if (priv->next_row_reference) {
1220                                 gtk_tree_row_reference_free (priv->next_row_reference);
1221                         }
1222                         priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
1223                         select_next_valid_row (header_model, &(priv->next_row_reference), FALSE, priv->is_outbox);
1224                         already_changed = TRUE;
1225                 }
1226                 gtk_tree_path_free (path);
1227         }
1228         if (!already_changed &&
1229             priv->next_row_reference &&
1230             gtk_tree_row_reference_valid (priv->next_row_reference)) {
1231                 GtkTreePath *path;
1232                 path = gtk_tree_row_reference_get_path (priv->next_row_reference);
1233                 if (gtk_tree_path_compare (path, arg1) == 0) {
1234                         if (priv->next_row_reference) {
1235                                 gtk_tree_row_reference_free (priv->next_row_reference);
1236                         }
1237                         priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
1238                         select_next_valid_row (header_model, &(priv->next_row_reference), FALSE, priv->is_outbox);
1239                 }
1240                 gtk_tree_path_free (path);
1241         }
1242         check_dimming_rules_after_change (window);
1243 }
1244
1245 /* The modest_msg_view_window_update_model_replaced implements update
1246  * function for ModestHeaderViewObserver. Checks whether the TnyFolder
1247  * actually belongs to the header-view is the same as the TnyFolder of
1248  * the message of msg-view or not. If they are different, there is
1249  * nothing to do. If they are the same, then the model has replaced and
1250  * the reference in msg-view shall be replaced from the old model to
1251  * the new model. In this case the view will be detached from it's
1252  * header folder. From this point the next/prev buttons are dimmed.
1253  */
1254 static void 
1255 modest_msg_view_window_update_model_replaced (ModestHeaderViewObserver *observer,
1256                                               GtkTreeModel *model,
1257                                               const gchar *tny_folder_id)
1258 {
1259         ModestMsgViewWindowPrivate *priv = NULL; 
1260         ModestMsgViewWindow *window = NULL;
1261
1262         g_assert(MODEST_IS_HEADER_VIEW_OBSERVER(observer));
1263         g_assert(MODEST_IS_MSG_VIEW_WINDOW(observer));
1264
1265         window = MODEST_MSG_VIEW_WINDOW(observer);
1266         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(window);
1267
1268         /* If there is an other folder in the header-view then we do
1269          * not care about it's model (msg list). Else if the
1270          * header-view shows the folder the msg shown by us is in, we
1271          * shall replace our model reference and make some check. */
1272         if(model == NULL || tny_folder_id == NULL || 
1273            (priv->header_folder_id && !g_str_equal(tny_folder_id, priv->header_folder_id)))
1274                 return;
1275
1276         /* Model is changed(replaced), so we should forget the old
1277          * one. Because there might be other references and there
1278          * might be some change on the model even if we unreferenced
1279          * it, we need to disconnect our signals here. */
1280         if (priv->header_model) {
1281                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
1282                                                   priv->row_changed_handler))
1283                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
1284                                                     priv->row_changed_handler);
1285                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
1286                                                   priv->row_deleted_handler))
1287                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
1288                                                     priv->row_deleted_handler);
1289                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
1290                                                   priv->row_inserted_handler))
1291                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
1292                                                     priv->row_inserted_handler);
1293                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
1294                                                   priv->rows_reordered_handler))
1295                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
1296                                                     priv->rows_reordered_handler);
1297
1298                 /* Frees */
1299                 if (priv->row_reference)
1300                         gtk_tree_row_reference_free (priv->row_reference);
1301                 if (priv->next_row_reference)
1302                         gtk_tree_row_reference_free (priv->next_row_reference);
1303                 g_object_unref(priv->header_model);
1304
1305                 /* Initialize */
1306                 priv->row_changed_handler = 0;
1307                 priv->row_deleted_handler = 0;
1308                 priv->row_inserted_handler = 0;
1309                 priv->rows_reordered_handler = 0;
1310                 priv->next_row_reference = NULL;
1311                 priv->row_reference = NULL;
1312                 priv->header_model = NULL;
1313         }
1314
1315         priv->header_model = g_object_ref (model);
1316
1317         /* Also we must connect to the new model for row insertions.
1318          * Only for insertions now. We will need other ones only after
1319          * the msg is show by msg-view is added to the new model. */
1320         priv->row_inserted_handler =
1321                 g_signal_connect (priv->header_model, "row-inserted",
1322                                   G_CALLBACK(modest_msg_view_window_on_row_inserted),
1323                                   window);
1324
1325         modest_ui_actions_check_menu_dimming_rules(MODEST_WINDOW(window));
1326         modest_ui_actions_check_toolbar_dimming_rules(MODEST_WINDOW(window));
1327 }
1328
1329 gboolean 
1330 modest_msg_view_window_toolbar_on_transfer_mode     (ModestMsgViewWindow *self)
1331 {
1332         ModestMsgViewWindowPrivate *priv= NULL; 
1333
1334         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), FALSE);
1335         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
1336
1337         return priv->progress_hint;
1338 }
1339
1340 TnyHeader*
1341 modest_msg_view_window_get_header (ModestMsgViewWindow *self)
1342 {
1343         ModestMsgViewWindowPrivate *priv= NULL; 
1344         TnyMsg *msg = NULL;
1345         TnyHeader *header = NULL;
1346         GtkTreePath *path = NULL;
1347         GtkTreeIter iter;
1348
1349         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), NULL);
1350         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
1351
1352         if (priv->is_other_body)
1353                 return NULL;
1354
1355         /* If the message was not obtained from a treemodel,
1356          * for instance if it was opened directly by the search UI:
1357          */
1358         if (priv->header_model == NULL || 
1359             priv->row_reference == NULL ||
1360             !gtk_tree_row_reference_valid (priv->row_reference)) {
1361                 msg = modest_msg_view_window_get_message (self);
1362                 if (msg) {
1363                         header = tny_msg_get_header (msg);
1364                         g_object_unref (msg);
1365                 }
1366                 return header;
1367         }
1368
1369         /* Get iter of the currently selected message in the header view: */
1370         path = gtk_tree_row_reference_get_path (priv->row_reference);
1371         g_return_val_if_fail (path != NULL, NULL);
1372         gtk_tree_model_get_iter (priv->header_model, 
1373                                  &iter, 
1374                                  path);
1375
1376         /* Get current message header */
1377         gtk_tree_model_get (priv->header_model, &iter, 
1378                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, 
1379                             &header, -1);
1380
1381         gtk_tree_path_free (path);
1382         return header;
1383 }
1384
1385 TnyMsg*
1386 modest_msg_view_window_get_message (ModestMsgViewWindow *self)
1387 {
1388         ModestMsgViewWindowPrivate *priv;
1389         
1390         g_return_val_if_fail (self, NULL);
1391         
1392         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
1393         
1394         return tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
1395 }
1396
1397 const gchar*
1398 modest_msg_view_window_get_message_uid (ModestMsgViewWindow *self)
1399 {
1400         ModestMsgViewWindowPrivate *priv;
1401
1402         g_return_val_if_fail (self, NULL);
1403         
1404         priv  = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
1405
1406         return (const gchar*) priv->msg_uid;
1407 }
1408
1409 static void 
1410 modest_msg_view_window_toggle_find_toolbar (GtkToggleAction *toggle,
1411                                             gpointer data)
1412 {
1413         ModestMsgViewWindow *window = MODEST_MSG_VIEW_WINDOW (data);
1414         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1415         ModestWindowPrivate *parent_priv = MODEST_WINDOW_GET_PRIVATE (window);
1416         gboolean is_active;
1417         GtkAction *action;
1418
1419         is_active = gtk_toggle_action_get_active (toggle);
1420
1421         if (is_active) {
1422                 gtk_widget_show (priv->find_toolbar);
1423                 hildon_find_toolbar_highlight_entry (HILDON_FIND_TOOLBAR (priv->find_toolbar), TRUE);
1424         } else {
1425                 gtk_widget_hide (priv->find_toolbar);
1426                 modest_msg_view_grab_focus (MODEST_MSG_VIEW (priv->msg_view));
1427         }
1428
1429         /* update the toggle buttons status */
1430         action = gtk_ui_manager_get_action (parent_priv->ui_manager, "/ToolBar/FindInMessage");
1431         if (action)
1432                 modest_utils_toggle_action_set_active_block_notify (GTK_TOGGLE_ACTION (action), is_active);
1433
1434 }
1435
1436 static void
1437 modest_msg_view_window_find_toolbar_close (GtkWidget *widget,
1438                                            ModestMsgViewWindow *obj)
1439 {
1440         GtkToggleAction *toggle;
1441         ModestWindowPrivate *parent_priv;
1442         ModestMsgViewWindowPrivate *priv;
1443
1444         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (obj);
1445         parent_priv = MODEST_WINDOW_GET_PRIVATE (obj);
1446
1447         toggle = GTK_TOGGLE_ACTION (gtk_ui_manager_get_action (parent_priv->ui_manager, "/ToolBar/FindInMessage"));
1448         gtk_toggle_action_set_active (toggle, FALSE);
1449         modest_msg_view_grab_focus (MODEST_MSG_VIEW (priv->msg_view));
1450 }
1451
1452 static void
1453 modest_msg_view_window_find_toolbar_search (GtkWidget *widget,
1454                                            ModestMsgViewWindow *obj)
1455 {
1456         gchar *current_search;
1457         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (obj);
1458
1459         if (modest_mime_part_view_is_empty (MODEST_MIME_PART_VIEW (priv->msg_view))) {
1460                 hildon_banner_show_information (NULL, NULL, _("mail_ib_nothing_to_find"));
1461                 return;
1462         }
1463
1464         g_object_get (G_OBJECT (widget), "prefix", &current_search, NULL);
1465
1466         if ((current_search == NULL) || (strcmp (current_search, "") == 0)) {
1467                 g_free (current_search);
1468                 hildon_banner_show_information (NULL, NULL, _CS("ecdg_ib_find_rep_enter_text"));
1469                 return;
1470         }
1471
1472         if ((priv->last_search == NULL) || (strcmp (priv->last_search, current_search) != 0)) {
1473                 gboolean result;
1474                 g_free (priv->last_search);
1475                 priv->last_search = g_strdup (current_search);
1476                 result = modest_isearch_view_search (MODEST_ISEARCH_VIEW (priv->msg_view),
1477                                                      priv->last_search);
1478                 if (!result) {
1479                         hildon_banner_show_information (NULL, NULL, 
1480                                                         _HL("ckct_ib_find_no_matches"));
1481                         g_free (priv->last_search);
1482                         priv->last_search = NULL;
1483                 } else {
1484                         hildon_find_toolbar_highlight_entry (HILDON_FIND_TOOLBAR (priv->find_toolbar), TRUE);
1485                 }
1486         } else {
1487                 if (!modest_isearch_view_search_next (MODEST_ISEARCH_VIEW (priv->msg_view))) {
1488                         hildon_banner_show_information (NULL, NULL, 
1489                                                         _HL("ckct_ib_find_search_complete"));
1490                         g_free (priv->last_search);
1491                         priv->last_search = NULL;
1492                 } else {
1493                         hildon_find_toolbar_highlight_entry (HILDON_FIND_TOOLBAR (priv->find_toolbar), TRUE);
1494                 }
1495         }
1496         
1497         g_free (current_search);
1498                 
1499 }
1500
1501 static void
1502 modest_msg_view_window_set_zoom (ModestWindow *window,
1503                                  gdouble zoom)
1504 {
1505         ModestMsgViewWindowPrivate *priv;
1506         ModestWindowPrivate *parent_priv;
1507      
1508         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
1509
1510         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1511         parent_priv = MODEST_WINDOW_GET_PRIVATE (window);
1512         modest_zoomable_set_zoom (MODEST_ZOOMABLE (priv->msg_view), zoom);
1513
1514 }
1515
1516 static gdouble
1517 modest_msg_view_window_get_zoom (ModestWindow *window)
1518 {
1519         ModestMsgViewWindowPrivate *priv;
1520      
1521         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), 1.0);
1522
1523         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1524         return modest_zoomable_get_zoom (MODEST_ZOOMABLE (priv->msg_view));
1525 }
1526
1527 static gboolean
1528 modest_msg_view_window_zoom_plus (ModestWindow *window)
1529 {
1530         gdouble zoom_level;
1531         ModestMsgViewWindowPrivate *priv;
1532         gint int_zoom;
1533         gchar *banner_text;
1534      
1535         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), 1.0);
1536         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1537   
1538         zoom_level =  modest_zoomable_get_zoom (MODEST_ZOOMABLE (priv->msg_view));
1539
1540         if (zoom_level >= 2.0) {
1541                 hildon_banner_show_information (NULL, NULL, 
1542                                                 _CS("ckct_ib_max_zoom_level_reached"));
1543                 return FALSE;
1544         } else if (zoom_level >= 1.5) {
1545                 zoom_level = 2.0;
1546         } else if (zoom_level >= 1.2) {
1547                 zoom_level = 1.5;
1548         } else if (zoom_level >= 1.0) {
1549                 zoom_level = 1.2;
1550         } else if (zoom_level >= 0.8) {
1551                 zoom_level = 1.0;
1552         } else if (zoom_level >= 0.5) {
1553                 zoom_level = 0.8;
1554         } else {
1555                 zoom_level = 0.5;
1556         }
1557
1558         /* set zoom level */
1559         int_zoom = (gint) rint (zoom_level*100.0+0.1);
1560         banner_text = g_strdup_printf (_HL("wdgt_ib_zoom"), int_zoom);
1561         modest_platform_information_banner (GTK_WIDGET (window), NULL, banner_text);
1562         g_free (banner_text);
1563         modest_zoomable_set_zoom (MODEST_ZOOMABLE (priv->msg_view), zoom_level);
1564
1565         return TRUE;
1566 }
1567
1568 static gboolean
1569 modest_msg_view_window_zoom_minus (ModestWindow *window)
1570 {
1571         gdouble zoom_level;
1572         ModestMsgViewWindowPrivate *priv;
1573         gint int_zoom;
1574         gchar *banner_text;
1575      
1576         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), 1.0);
1577         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1578   
1579         zoom_level =  modest_zoomable_get_zoom (MODEST_ZOOMABLE (priv->msg_view));
1580
1581         if (zoom_level <= 0.5) {
1582                 hildon_banner_show_information (NULL, NULL, 
1583                                                 _CS("ckct_ib_min_zoom_level_reached"));
1584                 return FALSE;
1585         } else if (zoom_level <= 0.8) {
1586                 zoom_level = 0.5;
1587         } else if (zoom_level <= 1.0) {
1588                 zoom_level = 0.8;
1589         } else if (zoom_level <= 1.2) {
1590                 zoom_level = 1.0;
1591         } else if (zoom_level <= 1.5) {
1592                 zoom_level = 1.2;
1593         } else if (zoom_level <= 2.0) {
1594                 zoom_level = 1.5;
1595         } else {
1596                 zoom_level = 2.0;
1597         }
1598
1599         /* set zoom level */
1600         int_zoom = (gint) rint (zoom_level*100.0+0.1);
1601         banner_text = g_strdup_printf (_HL("wdgt_ib_zoom"), int_zoom);
1602         modest_platform_information_banner (GTK_WIDGET (window), NULL, banner_text);
1603         g_free (banner_text);
1604         modest_zoomable_set_zoom (MODEST_ZOOMABLE (priv->msg_view), zoom_level);
1605
1606         return TRUE;
1607         
1608 }
1609
1610 static gboolean
1611 modest_msg_view_window_key_event (GtkWidget *window,
1612                                   GdkEventKey *event,
1613                                   gpointer userdata)
1614 {
1615         GtkWidget *focus;
1616
1617         focus = gtk_window_get_focus (GTK_WINDOW (window));
1618
1619         /* for the find toolbar case */
1620         if (focus && GTK_IS_ENTRY (focus)) {
1621                 if (event->keyval == GDK_BackSpace) {
1622                         GdkEvent *copy;
1623                         copy = gdk_event_copy ((GdkEvent *) event);
1624                         gtk_widget_event (focus, copy);
1625                         gdk_event_free (copy);
1626                         return TRUE;
1627                 } else 
1628                         return FALSE;
1629         }
1630         if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up ||
1631             event->keyval == GDK_Down || event->keyval == GDK_KP_Down ||
1632             event->keyval == GDK_Page_Up || event->keyval == GDK_KP_Page_Up ||
1633             event->keyval == GDK_Page_Down || event->keyval == GDK_KP_Page_Down ||
1634             event->keyval == GDK_Home || event->keyval == GDK_KP_Home ||
1635             event->keyval == GDK_End || event->keyval == GDK_KP_End) {
1636                 /* ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window); */
1637                 /* gboolean return_value; */
1638
1639                 if (event->type == GDK_KEY_PRESS) {
1640                         GtkScrollType scroll_type;
1641
1642                         switch (event->keyval) {
1643                         case GDK_Up: 
1644                         case GDK_KP_Up:
1645                                 scroll_type = GTK_SCROLL_STEP_UP; break;
1646                         case GDK_Down: 
1647                         case GDK_KP_Down:
1648                                 scroll_type = GTK_SCROLL_STEP_DOWN; break;
1649                         case GDK_Page_Up:
1650                         case GDK_KP_Page_Up:
1651                                 scroll_type = GTK_SCROLL_PAGE_UP; break;
1652                         case GDK_Page_Down:
1653                         case GDK_KP_Page_Down:
1654                                 scroll_type = GTK_SCROLL_PAGE_DOWN; break;
1655                         case GDK_Home:
1656                         case GDK_KP_Home:
1657                                 scroll_type = GTK_SCROLL_START; break;
1658                         case GDK_End:
1659                         case GDK_KP_End:
1660                                 scroll_type = GTK_SCROLL_END; break;
1661                         default: scroll_type = GTK_SCROLL_NONE;
1662                         }
1663
1664                         /* g_signal_emit_by_name (G_OBJECT (priv->main_scroll), "scroll-child",  */
1665                         /*                     scroll_type, FALSE, &return_value); */
1666                         return FALSE;
1667                 } else {
1668                         return FALSE;
1669                 }
1670         } else {
1671                 return FALSE;
1672         }
1673 }
1674
1675 gboolean
1676 modest_msg_view_window_last_message_selected (ModestMsgViewWindow *window)
1677 {
1678         GtkTreePath *path;
1679         ModestMsgViewWindowPrivate *priv;
1680         GtkTreeIter tmp_iter;
1681         gboolean is_last_selected;
1682
1683         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), TRUE);
1684         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1685
1686         /*if no model (so no rows at all), then virtually we are the last*/
1687         if (!priv->header_model || !priv->row_reference)
1688                 return TRUE;
1689
1690         if (!gtk_tree_row_reference_valid (priv->row_reference))
1691                 return TRUE;
1692
1693         path = gtk_tree_row_reference_get_path (priv->row_reference);
1694         if (path == NULL)
1695                 return TRUE;
1696
1697         is_last_selected = TRUE;
1698         while (is_last_selected) {
1699                 TnyHeader *header;
1700                 gtk_tree_path_next (path);
1701                 if (!gtk_tree_model_get_iter (priv->header_model, &tmp_iter, path))
1702                         break;
1703                 gtk_tree_model_get (priv->header_model, &tmp_iter,
1704                                 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1705                                 &header, -1);
1706                 if (header) {
1707                         if (msg_is_visible (header, priv->is_outbox))
1708                                 is_last_selected = FALSE;
1709                         g_object_unref(G_OBJECT(header));
1710                 }
1711         }
1712         gtk_tree_path_free (path);
1713         return is_last_selected;
1714 }
1715
1716 gboolean
1717 modest_msg_view_window_has_headers_model (ModestMsgViewWindow *window)
1718 {
1719         ModestMsgViewWindowPrivate *priv;
1720
1721         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), TRUE);
1722         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1723
1724         return priv->header_model != NULL;
1725 }
1726
1727 gboolean
1728 modest_msg_view_window_is_search_result (ModestMsgViewWindow *window)
1729 {
1730         ModestMsgViewWindowPrivate *priv;
1731
1732         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), TRUE);
1733         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1734
1735         return priv->is_search_result;
1736 }
1737
1738 static gboolean
1739 msg_is_visible (TnyHeader *header, gboolean check_outbox)
1740 {
1741         if ((tny_header_get_flags(header) & TNY_HEADER_FLAG_DELETED))
1742                 return FALSE;
1743         if (!check_outbox) {
1744                 return TRUE;
1745         } else {
1746                 ModestTnySendQueueStatus status;
1747                 status = modest_tny_all_send_queues_get_msg_status (header);
1748                 return ((status != MODEST_TNY_SEND_QUEUE_FAILED) &&
1749                         (status != MODEST_TNY_SEND_QUEUE_SENDING));
1750         }
1751 }
1752
1753 gboolean
1754 modest_msg_view_window_first_message_selected (ModestMsgViewWindow *window)
1755 {
1756         GtkTreePath *path;
1757         ModestMsgViewWindowPrivate *priv;
1758         gboolean is_first_selected;
1759         GtkTreeIter tmp_iter;
1760
1761         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), TRUE);
1762         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1763
1764         /*if no model (so no rows at all), then virtually we are the first*/
1765         if (!priv->header_model || !priv->row_reference)
1766                 return TRUE;
1767
1768         if (!gtk_tree_row_reference_valid (priv->row_reference))
1769                 return TRUE;
1770
1771         path = gtk_tree_row_reference_get_path (priv->row_reference);
1772         if (!path)
1773                 return TRUE;
1774
1775         is_first_selected = TRUE;
1776         while (is_first_selected) {
1777                 TnyHeader *header;
1778                 if(!gtk_tree_path_prev (path))
1779                         break;
1780                 /* Here the 'if' is needless for logic, but let make sure
1781                  * iter is valid for gtk_tree_model_get. */
1782                 if (!gtk_tree_model_get_iter (priv->header_model, &tmp_iter, path))
1783                         break;
1784                 gtk_tree_model_get (priv->header_model, &tmp_iter,
1785                                 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1786                                 &header, -1);
1787                 if (header) {
1788                         if (msg_is_visible (header, priv->is_outbox))
1789                                 is_first_selected = FALSE;
1790                         g_object_unref(G_OBJECT(header));
1791                 }
1792         }
1793         gtk_tree_path_free (path);
1794         return is_first_selected;
1795 }
1796
1797 typedef struct {
1798         TnyHeader *header;
1799         GtkTreeRowReference *row_reference;
1800 } MsgReaderInfo;
1801
1802 static void
1803 message_reader_performer (gboolean canceled, 
1804                           GError *err,
1805                           GtkWindow *parent_window, 
1806                           TnyAccount *account, 
1807                           gpointer user_data)
1808 {
1809         ModestMailOperation *mail_op = NULL;
1810         MsgReaderInfo *info;
1811
1812         info = (MsgReaderInfo *) user_data;
1813         if (canceled || err) {
1814                 update_window_title (MODEST_MSG_VIEW_WINDOW (parent_window));
1815                 goto frees;
1816         }
1817
1818         /* Register the header - it'll be unregistered in the callback */
1819         modest_window_mgr_register_header (modest_runtime_get_window_mgr (), info->header, NULL);
1820
1821         /* New mail operation */
1822         mail_op = modest_mail_operation_new_with_error_handling (G_OBJECT(parent_window),
1823                                                                  modest_ui_actions_disk_operations_error_handler, 
1824                                                                  NULL, NULL);
1825
1826         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (), mail_op);
1827         modest_mail_operation_get_msg (mail_op, info->header, TRUE, view_msg_cb, info->row_reference);
1828         g_object_unref (mail_op);
1829
1830         /* Update dimming rules */
1831         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (parent_window));
1832         modest_ui_actions_check_menu_dimming_rules (MODEST_WINDOW (parent_window));
1833
1834  frees:
1835         /* Frees. The row_reference will be freed by the view_msg_cb callback */
1836         g_object_unref (info->header);
1837         g_slice_free (MsgReaderInfo, info);
1838 }
1839
1840
1841 /**
1842  * Reads the message whose summary item is @header. It takes care of
1843  * several things, among others:
1844  *
1845  * If the message was not previously downloaded then ask the user
1846  * before downloading. If there is no connection launch the connection
1847  * dialog. Update toolbar dimming rules.
1848  *
1849  * Returns: TRUE if the mail operation was started, otherwise if the
1850  * user do not want to download the message, or if the user do not
1851  * want to connect, then the operation is not issued
1852  **/
1853 static gboolean
1854 message_reader (ModestMsgViewWindow *window,
1855                 ModestMsgViewWindowPrivate *priv,
1856                 TnyHeader *header,
1857                 GtkTreeRowReference *row_reference)
1858 {
1859         ModestWindowMgr *mgr;
1860         TnyAccount *account;
1861         TnyFolder *folder;
1862         MsgReaderInfo *info;
1863
1864         g_return_val_if_fail (row_reference != NULL, FALSE);
1865
1866         /* We set the header from model while we're loading */
1867         tny_header_view_set_header (TNY_HEADER_VIEW (priv->msg_view), header);
1868         gtk_window_set_title (GTK_WINDOW (window), _CS("ckdg_pb_updating"));
1869
1870         mgr = modest_runtime_get_window_mgr ();
1871         /* Msg download completed */
1872         if (!(tny_header_get_flags (header) & TNY_HEADER_FLAG_CACHED)) {
1873
1874                 /* Ask the user if he wants to download the message if
1875                    we're not online */
1876                 if (!tny_device_is_online (modest_runtime_get_device())) {
1877                         GtkResponseType response;
1878
1879                         response = modest_platform_run_confirmation_dialog (GTK_WINDOW (window),
1880                                                                             _("mcen_nc_get_msg"));
1881                         if (response == GTK_RESPONSE_CANCEL) {
1882                                 update_window_title (window);
1883                                 return FALSE;
1884                         }
1885
1886                         folder = tny_header_get_folder (header);
1887                         info = g_slice_new (MsgReaderInfo);
1888                         info->header = g_object_ref (header);
1889                         info->row_reference = gtk_tree_row_reference_copy (row_reference);
1890
1891                         /* Offer the connection dialog if necessary */
1892                         modest_platform_connect_if_remote_and_perform ((GtkWindow *) window, 
1893                                                                        TRUE,
1894                                                                        TNY_FOLDER_STORE (folder),
1895                                                                        message_reader_performer, 
1896                                                                        info);
1897                         g_object_unref (folder);
1898                         return TRUE;
1899                 }
1900         }
1901
1902         folder = tny_header_get_folder (header);
1903         account = tny_folder_get_account (folder);
1904         info = g_slice_new (MsgReaderInfo);
1905         info->header = g_object_ref (header);
1906         info->row_reference = gtk_tree_row_reference_copy (row_reference);
1907
1908         message_reader_performer (FALSE, NULL, (GtkWindow *) window, account, info);
1909         g_object_unref (account);
1910         g_object_unref (folder);
1911
1912         return TRUE;
1913 }
1914
1915 gboolean        
1916 modest_msg_view_window_select_next_message (ModestMsgViewWindow *window)
1917 {
1918         ModestMsgViewWindowPrivate *priv;
1919         GtkTreePath *path= NULL;
1920         GtkTreeIter tmp_iter;
1921         TnyHeader *header;
1922         gboolean retval = TRUE;
1923         GtkTreeRowReference *row_reference = NULL;
1924
1925         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), FALSE);
1926         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1927
1928         if (!priv->row_reference)
1929                 return FALSE;
1930
1931         /* Update the next row reference if it's not valid. This could
1932            happen if for example the header which it was pointing to,
1933            was deleted. The best place to do it is in the row-deleted
1934            handler but the tinymail model do not work like the glib
1935            tree models and reports the deletion when the row is still
1936            there */
1937         if (!gtk_tree_row_reference_valid (priv->next_row_reference)) {
1938                 if (gtk_tree_row_reference_valid (priv->row_reference)) {
1939                         priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
1940                         select_next_valid_row (priv->header_model, &(priv->next_row_reference), FALSE, priv->is_outbox);
1941                 }
1942         }
1943         if (priv->next_row_reference)
1944                 path = gtk_tree_row_reference_get_path (priv->next_row_reference);
1945         if (path == NULL)
1946                 return FALSE;
1947
1948         row_reference = gtk_tree_row_reference_copy (priv->next_row_reference);
1949
1950         gtk_tree_model_get_iter (priv->header_model,
1951                                  &tmp_iter,
1952                                  path);
1953         gtk_tree_path_free (path);
1954
1955         gtk_tree_model_get (priv->header_model, &tmp_iter, 
1956                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1957                             &header, -1);
1958         
1959         /* Read the message & show it */
1960         if (!message_reader (window, priv, header, row_reference)) {
1961                 retval = FALSE;
1962         }
1963         gtk_tree_row_reference_free (row_reference);
1964
1965         /* Free */
1966         g_object_unref (header);
1967
1968         return retval;
1969 }
1970
1971 gboolean        
1972 modest_msg_view_window_select_previous_message (ModestMsgViewWindow *window)
1973 {
1974         ModestMsgViewWindowPrivate *priv = NULL;
1975         GtkTreePath *path;
1976         gboolean finished = FALSE;
1977         gboolean retval = FALSE;
1978
1979         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), FALSE);
1980         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1981
1982         /* Return inmediatly if there is no header model */
1983         if (!priv->header_model || !priv->row_reference)
1984                 return FALSE;
1985
1986         path = gtk_tree_row_reference_get_path (priv->row_reference);
1987         while (!finished && gtk_tree_path_prev (path)) {
1988                 TnyHeader *header;
1989                 GtkTreeIter iter;
1990
1991                 gtk_tree_model_get_iter (priv->header_model, &iter, path);
1992                 gtk_tree_model_get (priv->header_model, &iter, 
1993                                     TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1994                                     &header, -1);
1995                 finished = TRUE;
1996                 if (header) {
1997                         if (msg_is_visible (header, priv->is_outbox)) {
1998                                 GtkTreeRowReference *row_reference;
1999                                 row_reference = gtk_tree_row_reference_new (priv->header_model, path);
2000                                 /* Read the message & show it */
2001                                 retval = message_reader (window, priv, header, row_reference);
2002                                 gtk_tree_row_reference_free (row_reference);
2003                         } else {
2004                                 finished = FALSE;
2005                         }
2006                         g_object_unref (header);
2007                 }
2008         }
2009
2010         gtk_tree_path_free (path);
2011         return retval;
2012 }
2013
2014 static void
2015 view_msg_cb (ModestMailOperation *mail_op, 
2016              TnyHeader *header, 
2017              gboolean canceled,
2018              TnyMsg *msg, 
2019              GError *error,
2020              gpointer user_data)
2021 {
2022         ModestMsgViewWindow *self = NULL;
2023         ModestMsgViewWindowPrivate *priv = NULL;
2024         GtkTreeRowReference *row_reference = NULL;
2025
2026         /* Unregister the header (it was registered before creating the mail operation) */
2027         modest_window_mgr_unregister_header (modest_runtime_get_window_mgr (), header);
2028
2029         row_reference = (GtkTreeRowReference *) user_data;
2030         if (canceled) {
2031                 gtk_tree_row_reference_free (row_reference);
2032                 self = (ModestMsgViewWindow *) modest_mail_operation_get_source (mail_op);
2033                 if (self) {
2034                         /* Restore window title */
2035                         update_window_title (self);
2036                         g_object_unref (self);
2037                 }
2038                 return;
2039         }
2040
2041         /* If there was any error */
2042         if (!modest_ui_actions_msg_retrieval_check (mail_op, header, msg)) {
2043                 gtk_tree_row_reference_free (row_reference);
2044                 self = (ModestMsgViewWindow *) modest_mail_operation_get_source (mail_op);
2045                 if (self) {
2046                         /* Restore window title */
2047                         update_window_title (self);
2048                         g_object_unref (self);
2049                 }
2050                 return;
2051         }
2052
2053         /* Get the window */ 
2054         self = (ModestMsgViewWindow *) modest_mail_operation_get_source (mail_op);
2055         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self));
2056         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2057
2058         /* Update the row reference */
2059         if (priv->row_reference != NULL) {
2060                 gtk_tree_row_reference_free (priv->row_reference);
2061                 priv->row_reference = gtk_tree_row_reference_copy (row_reference);
2062                 if (priv->next_row_reference != NULL) {
2063                         gtk_tree_row_reference_free (priv->next_row_reference);
2064                 }
2065                 priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
2066                 select_next_valid_row (priv->header_model, &(priv->next_row_reference), TRUE, priv->is_outbox);
2067         }
2068
2069         /* Mark header as read */
2070         if (!(tny_header_get_flags (header) & TNY_HEADER_FLAG_SEEN))
2071                 tny_header_set_flag (header, TNY_HEADER_FLAG_SEEN);
2072
2073         /* Set new message */
2074         if (priv->msg_view != NULL && TNY_IS_MSG_VIEW (priv->msg_view)) {
2075                 tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
2076                 modest_msg_view_window_update_priority (self);
2077                 update_window_title (MODEST_MSG_VIEW_WINDOW (self));
2078                 modest_msg_view_grab_focus (MODEST_MSG_VIEW (priv->msg_view));
2079         }
2080
2081         /* Set the new message uid of the window  */
2082         if (priv->msg_uid) {
2083                 g_free (priv->msg_uid);
2084                 priv->msg_uid = modest_tny_folder_get_header_unique_id (header);
2085         }
2086
2087         /* Notify the observers */
2088         g_signal_emit (G_OBJECT (self), signals[MSG_CHANGED_SIGNAL], 
2089                        0, priv->header_model, priv->row_reference);
2090
2091         /* Frees */
2092         g_object_unref (self);
2093         gtk_tree_row_reference_free (row_reference);            
2094 }
2095
2096 TnyFolderType
2097 modest_msg_view_window_get_folder_type (ModestMsgViewWindow *window)
2098 {
2099         ModestMsgViewWindowPrivate *priv;
2100         TnyMsg *msg;
2101         TnyFolderType folder_type;
2102
2103         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2104
2105         folder_type = TNY_FOLDER_TYPE_UNKNOWN;
2106
2107         msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
2108         if (msg) {
2109                 TnyFolder *folder;
2110
2111                 folder = tny_msg_get_folder (msg);
2112                 if (folder) {
2113                         folder_type = modest_tny_folder_guess_folder_type (folder);
2114                         g_object_unref (folder);
2115                 }
2116                 g_object_unref (msg);
2117         }
2118
2119         return folder_type;
2120 }
2121
2122
2123 static void
2124 modest_msg_view_window_update_priority (ModestMsgViewWindow *window)
2125 {
2126         ModestMsgViewWindowPrivate *priv;
2127         TnyHeader *header = NULL;
2128         TnyHeaderFlags flags = 0;
2129
2130         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2131
2132         if (priv->header_model && priv->row_reference) {
2133                 GtkTreeIter iter;
2134                 GtkTreePath *path = NULL;
2135
2136                 path = gtk_tree_row_reference_get_path (priv->row_reference);
2137                 g_return_if_fail (path != NULL);
2138                 gtk_tree_model_get_iter (priv->header_model, 
2139                                          &iter, 
2140                                          gtk_tree_row_reference_get_path (priv->row_reference));
2141
2142                 gtk_tree_model_get (priv->header_model, &iter, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
2143                                     &header, -1);
2144                 gtk_tree_path_free (path);
2145         } else {
2146                 TnyMsg *msg;
2147                 msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
2148                 if (msg) {
2149                         header = tny_msg_get_header (msg);
2150                         g_object_unref (msg);
2151                 }
2152         }
2153
2154         if (header) {
2155                 flags = tny_header_get_flags (header);
2156                 g_object_unref(G_OBJECT(header));
2157         }
2158
2159         modest_msg_view_set_priority (MODEST_MSG_VIEW(priv->msg_view), flags);
2160
2161 }
2162
2163 static void
2164 toolbar_resize (ModestMsgViewWindow *self)
2165 {
2166         ModestMsgViewWindowPrivate *priv = NULL;
2167         ModestWindowPrivate *parent_priv = NULL;
2168         GtkWidget *widget;
2169         gint static_button_size;
2170         ModestWindowMgr *mgr;
2171
2172         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self));
2173         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2174         parent_priv = MODEST_WINDOW_GET_PRIVATE(self);
2175
2176         mgr = modest_runtime_get_window_mgr ();
2177         static_button_size = modest_window_mgr_get_fullscreen_mode (mgr)?120:120;
2178
2179         if (parent_priv->toolbar) {
2180                 /* left size buttons */
2181                 widget = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageReply");
2182                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (widget), FALSE);
2183                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (widget), FALSE);
2184                 gtk_widget_set_size_request (GTK_WIDGET (widget), static_button_size, -1);
2185                 widget = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageMoveTo");
2186                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (widget), FALSE);
2187                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (widget), FALSE);
2188                 gtk_widget_set_size_request (GTK_WIDGET (widget), static_button_size, -1);
2189                 widget = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarDeleteMessage");
2190                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (widget), FALSE);
2191                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (widget), FALSE);
2192                 gtk_widget_set_size_request (GTK_WIDGET (widget), static_button_size, -1);
2193                 widget = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/FindInMessage");
2194                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (widget), FALSE);
2195                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (widget), FALSE);
2196                 gtk_widget_set_size_request (GTK_WIDGET (widget), static_button_size, -1);
2197
2198                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (priv->next_toolitem), TRUE);
2199                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->next_toolitem), TRUE);
2200                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (priv->prev_toolitem), TRUE);
2201                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->prev_toolitem), TRUE);
2202         }
2203 }
2204
2205 static void
2206 modest_msg_view_window_show_toolbar (ModestWindow *self,
2207                                      gboolean show_toolbar)
2208 {
2209         ModestMsgViewWindowPrivate *priv = NULL;
2210         ModestWindowPrivate *parent_priv;
2211
2212         parent_priv = MODEST_WINDOW_GET_PRIVATE(self);
2213         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2214
2215         /* Set optimized view status */
2216         priv->optimized_view = !show_toolbar;
2217
2218         if (!parent_priv->toolbar) {
2219                 parent_priv->toolbar = gtk_ui_manager_get_widget (parent_priv->ui_manager, 
2220                                                                   "/ToolBar");
2221                 gtk_toolbar_set_icon_size (GTK_TOOLBAR (parent_priv->toolbar), HILDON_ICON_SIZE_FINGER);
2222                 gtk_widget_set_no_show_all (parent_priv->toolbar, TRUE);
2223
2224                 priv->next_toolitem = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageNext");
2225                 priv->prev_toolitem = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageBack");
2226                 toolbar_resize (MODEST_MSG_VIEW_WINDOW (self));
2227
2228                 /* Add to window */
2229                 hildon_window_add_toolbar (HILDON_WINDOW (self), 
2230                                            GTK_TOOLBAR (parent_priv->toolbar));
2231
2232         }
2233
2234         if (show_toolbar) {
2235                 /* Quick hack: this prevents toolbar icons "dance" when progress bar show status is changed */ 
2236                 /* TODO: resize mode migth be GTK_RESIZE_QUEUE, in order to avoid unneccesary shows */
2237                 gtk_container_set_resize_mode (GTK_CONTAINER(parent_priv->toolbar), GTK_RESIZE_IMMEDIATE);
2238
2239                 gtk_widget_show (GTK_WIDGET (parent_priv->toolbar));
2240                 if (modest_msg_view_window_transfer_mode_enabled (MODEST_MSG_VIEW_WINDOW (self))) 
2241                         set_progress_hint (MODEST_MSG_VIEW_WINDOW (self), TRUE);
2242                 else
2243                         set_progress_hint (MODEST_MSG_VIEW_WINDOW (self), FALSE);
2244
2245         } else {
2246                 gtk_widget_set_no_show_all (parent_priv->toolbar, TRUE);
2247                 gtk_widget_hide (GTK_WIDGET (parent_priv->toolbar));
2248         }
2249 }
2250
2251 static void 
2252 modest_msg_view_window_clipboard_owner_change (GtkClipboard *clipboard,
2253                                                GdkEvent *event,
2254                                                ModestMsgViewWindow *window)
2255 {
2256         if (!GTK_WIDGET_VISIBLE (window))
2257                 return;
2258
2259         modest_window_check_dimming_rules_group (MODEST_WINDOW (window), MODEST_DIMMING_RULES_CLIPBOARD);
2260 }
2261
2262 gboolean 
2263 modest_msg_view_window_transfer_mode_enabled (ModestMsgViewWindow *self)
2264 {
2265         ModestMsgViewWindowPrivate *priv;
2266         
2267         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), FALSE); 
2268         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2269
2270         return priv->progress_hint;
2271 }
2272
2273 static gboolean
2274 observers_empty (ModestMsgViewWindow *self)
2275 {
2276         GSList *tmp = NULL;
2277         ModestMsgViewWindowPrivate *priv;
2278         gboolean is_empty = TRUE;
2279         guint pending_ops = 0;
2280  
2281         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2282         tmp = priv->progress_widgets;
2283
2284         /* Check all observers */
2285         while (tmp && is_empty)  {
2286                 pending_ops = modest_progress_object_num_pending_operations (MODEST_PROGRESS_OBJECT(tmp->data));
2287                 is_empty = pending_ops == 0;
2288                 
2289                 tmp = g_slist_next(tmp);
2290         }
2291         
2292         return is_empty;
2293 }
2294
2295 static void
2296 on_account_removed (TnyAccountStore *account_store, 
2297                     TnyAccount *account,
2298                     gpointer user_data)
2299 {
2300         /* Do nothing if it's a transport account, because we only
2301            show the messages of a store account */
2302         if (tny_account_get_account_type(account) == TNY_ACCOUNT_TYPE_STORE) {
2303                 const gchar *parent_acc = NULL;
2304                 const gchar *our_acc = NULL;
2305
2306                 our_acc = modest_window_get_active_account (MODEST_WINDOW (user_data));
2307                 parent_acc = modest_tny_account_get_parent_modest_account_name_for_server_account (account);
2308
2309                 /* Close this window if I'm showing a message of the removed account */
2310                 if (our_acc && parent_acc && strcmp (parent_acc, our_acc) == 0)
2311                         modest_ui_actions_on_close_window (NULL, MODEST_WINDOW (user_data));
2312         }
2313 }
2314
2315 static void 
2316 on_mail_operation_started (ModestMailOperation *mail_op,
2317                            gpointer user_data)
2318 {
2319         ModestMsgViewWindow *self;
2320         ModestMailOperationTypeOperation op_type;
2321         GSList *tmp;
2322         ModestMsgViewWindowPrivate *priv;
2323         GObject *source = NULL;
2324
2325         self = MODEST_MSG_VIEW_WINDOW (user_data);
2326         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2327         op_type = modest_mail_operation_get_type_operation (mail_op);
2328         tmp = priv->progress_widgets;
2329         source = modest_mail_operation_get_source(mail_op);
2330         if (G_OBJECT (self) == source) {
2331                 if (op_type == MODEST_MAIL_OPERATION_TYPE_RECEIVE) {
2332                         set_toolbar_transfer_mode(self);
2333                         while (tmp) {
2334                                 modest_progress_object_add_operation (
2335                                                 MODEST_PROGRESS_OBJECT (tmp->data),
2336                                                 mail_op);
2337                                 tmp = g_slist_next (tmp);
2338                         }
2339                 }
2340         }
2341         g_object_unref (source);
2342 }
2343
2344 static void 
2345 on_mail_operation_finished (ModestMailOperation *mail_op,
2346                             gpointer user_data)
2347 {
2348         ModestMsgViewWindow *self;
2349         ModestMailOperationTypeOperation op_type;
2350         GSList *tmp;
2351         ModestMsgViewWindowPrivate *priv;
2352         
2353         self = MODEST_MSG_VIEW_WINDOW (user_data);
2354         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2355         op_type = modest_mail_operation_get_type_operation (mail_op);
2356         tmp = priv->progress_widgets;
2357         
2358         if (op_type == MODEST_MAIL_OPERATION_TYPE_RECEIVE) {
2359                 while (tmp) {
2360                         modest_progress_object_remove_operation (MODEST_PROGRESS_OBJECT (tmp->data),
2361                                                                  mail_op);
2362                         tmp = g_slist_next (tmp);
2363                 }
2364
2365                 /* If no more operations are being observed, NORMAL mode is enabled again */
2366                 if (observers_empty (self)) {
2367                         set_progress_hint (self, FALSE);
2368                 }
2369         }
2370
2371         /* Update dimming rules. We have to do this right here
2372            and not in view_msg_cb because at that point the
2373            transfer mode is still enabled so the dimming rule
2374            won't let the user delete the message that has been
2375            readed for example */
2376         check_dimming_rules_after_change (self);
2377
2378 }
2379
2380 static void
2381 on_queue_changed (ModestMailOperationQueue *queue,
2382                   ModestMailOperation *mail_op,
2383                   ModestMailOperationQueueNotification type,
2384                   ModestMsgViewWindow *self)
2385 {       
2386         ModestMsgViewWindowPrivate *priv;
2387
2388         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2389
2390         /* If this operations was created by another window, do nothing */
2391         if (!modest_mail_operation_is_mine (mail_op, G_OBJECT(self))) 
2392             return;
2393
2394         if (type == MODEST_MAIL_OPERATION_QUEUE_OPERATION_ADDED) {
2395                 priv->sighandlers = modest_signal_mgr_connect (priv->sighandlers,
2396                                                                G_OBJECT (mail_op),
2397                                                                "operation-started",
2398                                                                G_CALLBACK (on_mail_operation_started),
2399                                                                self);
2400                 priv->sighandlers = modest_signal_mgr_connect (priv->sighandlers,
2401                                                                G_OBJECT (mail_op),
2402                                                                "operation-finished",
2403                                                                G_CALLBACK (on_mail_operation_finished),
2404                                                                self);
2405         } else if (type == MODEST_MAIL_OPERATION_QUEUE_OPERATION_REMOVED) {
2406                 priv->sighandlers = modest_signal_mgr_disconnect (priv->sighandlers,
2407                                                                   G_OBJECT (mail_op),
2408                                                                   "operation-started");
2409                 priv->sighandlers = modest_signal_mgr_disconnect (priv->sighandlers,
2410                                                                   G_OBJECT (mail_op),
2411                                                                   "operation-finished");
2412         }
2413 }
2414
2415 TnyList *
2416 modest_msg_view_window_get_attachments (ModestMsgViewWindow *win) 
2417 {
2418         ModestMsgViewWindowPrivate *priv;
2419         TnyList *selected_attachments = NULL;
2420         
2421         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (win), NULL);
2422         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (win);
2423
2424         /* In Hildon 2.2 as there's no selection we assume we have all attachments selected */
2425         selected_attachments = modest_msg_view_get_attachments (MODEST_MSG_VIEW (priv->msg_view));
2426         
2427         return selected_attachments;
2428 }
2429
2430 typedef struct {
2431         ModestMsgViewWindow *self;
2432         gchar *file_path;
2433 } DecodeAsyncHelper;
2434
2435 static void
2436 on_decode_to_stream_async_handler (TnyMimePart *mime_part, 
2437                                    gboolean cancelled, 
2438                                    TnyStream *stream, 
2439                                    GError *err, 
2440                                    gpointer user_data)
2441 {
2442         DecodeAsyncHelper *helper = (DecodeAsyncHelper *) user_data;
2443
2444         /* It could happen that the window was closed */
2445         if (GTK_WIDGET_VISIBLE (helper->self))
2446                 set_progress_hint (helper->self, FALSE);
2447
2448         if (cancelled || err) {
2449                 if (err) {
2450                         gchar *msg = g_strdup_printf (_KR("cerm_device_memory_full"), "");
2451                         modest_platform_information_banner (NULL, NULL, msg);
2452                         g_free (msg);
2453                 }
2454                 goto free;
2455         }
2456
2457         /* make the file read-only */
2458         g_chmod(helper->file_path, 0444);
2459
2460         /* Activate the file */
2461         modest_platform_activate_file (helper->file_path, tny_mime_part_get_content_type (mime_part));
2462
2463  free:
2464         /* Frees */
2465         g_object_unref (helper->self);
2466         g_free (helper->file_path);
2467         g_slice_free (DecodeAsyncHelper, helper);
2468 }
2469
2470 void
2471 modest_msg_view_window_view_attachment (ModestMsgViewWindow *window, 
2472                                         TnyMimePart *mime_part)
2473 {
2474         ModestMsgViewWindowPrivate *priv;
2475         const gchar *msg_uid;
2476         gchar *attachment_uid = NULL;
2477         gint attachment_index = 0;
2478         TnyList *attachments;
2479
2480         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
2481         g_return_if_fail (TNY_IS_MIME_PART (mime_part) || (mime_part == NULL));
2482         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2483
2484         msg_uid = modest_msg_view_window_get_message_uid (MODEST_MSG_VIEW_WINDOW (window));
2485         attachments = modest_msg_view_get_attachments (MODEST_MSG_VIEW (priv->msg_view));
2486         attachment_index = modest_list_index (attachments, (GObject *) mime_part);
2487         g_object_unref (attachments);
2488
2489         if (msg_uid && attachment_index >= 0) {
2490                 attachment_uid = g_strdup_printf ("%s/%d", msg_uid, attachment_index);
2491         }
2492
2493         if (mime_part == NULL) {
2494                 gboolean error = FALSE;
2495                 TnyList *selected_attachments = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2496                 if (selected_attachments == NULL || tny_list_get_length (selected_attachments) == 0) {
2497                         error = TRUE;
2498                 } else if (tny_list_get_length (selected_attachments) > 1) {
2499                         hildon_banner_show_information (NULL, NULL, _("mcen_ib_unable_to_display_more"));
2500                         error = TRUE;
2501                 } else {
2502                         TnyIterator *iter;
2503                         iter = tny_list_create_iterator (selected_attachments);
2504                         mime_part = (TnyMimePart *) tny_iterator_get_current (iter);
2505                         g_object_unref (iter);
2506                 }
2507                 if (selected_attachments)
2508                         g_object_unref (selected_attachments);
2509
2510                 if (error)
2511                         goto frees;
2512         } else {
2513                 g_object_ref (mime_part);
2514         }
2515
2516         if (tny_mime_part_is_purged (mime_part))
2517                 goto frees;
2518
2519         if (!modest_tny_mime_part_is_msg (mime_part) && tny_mime_part_get_filename (mime_part)) {
2520                 gchar *filepath = NULL;
2521                 const gchar *att_filename = tny_mime_part_get_filename (mime_part);
2522                 gboolean show_error_banner = FALSE;
2523                 TnyFsStream *temp_stream = NULL;
2524                 temp_stream = modest_utils_create_temp_stream (att_filename, attachment_uid,
2525                                                                &filepath);
2526
2527                 if (temp_stream != NULL) {
2528                         DecodeAsyncHelper *helper;
2529
2530                         /* Activate progress hint */
2531                         set_progress_hint (window, TRUE);
2532
2533                         helper = g_slice_new0 (DecodeAsyncHelper);
2534                         helper->self = g_object_ref (window);
2535                         helper->file_path = g_strdup (filepath);
2536
2537                         tny_mime_part_decode_to_stream_async (mime_part, TNY_STREAM (temp_stream),
2538                                                               on_decode_to_stream_async_handler,
2539                                                               NULL,
2540                                                               helper);
2541                         g_object_unref (temp_stream);
2542                         /* NOTE: files in the temporary area will be automatically
2543                          * cleaned after some time if they are no longer in use */
2544                 } else {
2545                         if (filepath) {
2546                                 const gchar *content_type;
2547                                 /* the file may already exist but it isn't writable,
2548                                  * let's try to open it anyway */
2549                                 content_type = tny_mime_part_get_content_type (mime_part);
2550                                 modest_platform_activate_file (filepath, content_type);
2551                         } else {
2552                                 g_warning ("%s: modest_utils_create_temp_stream failed", __FUNCTION__);
2553                                 show_error_banner = TRUE;
2554                         }
2555                 }
2556                 if (filepath)
2557                         g_free (filepath);
2558                 if (show_error_banner)
2559                         modest_platform_information_banner (NULL, NULL, _("mail_ib_file_operation_failed"));
2560         } else if (!modest_tny_mime_part_is_msg (mime_part)) {
2561                 ModestWindowMgr *mgr;
2562                 mgr = modest_runtime_get_window_mgr ();
2563                 ModestWindow *msg_win = NULL;
2564                 TnyMsg *current_msg;
2565
2566                 gchar *account = g_strdup (modest_window_get_active_account (MODEST_WINDOW (window)));
2567                 const gchar *mailbox = modest_window_get_active_mailbox (MODEST_WINDOW (window));
2568                 if (!account)
2569                         account = modest_account_mgr_get_default_account (modest_runtime_get_account_mgr ());
2570
2571                 current_msg = modest_msg_view_window_get_message (MODEST_MSG_VIEW_WINDOW (window));
2572                 msg_win = modest_msg_view_window_new_with_other_body (TNY_MSG (current_msg), TNY_MIME_PART (mime_part),
2573                                                                       account, mailbox, attachment_uid);
2574                 g_object_unref (current_msg);
2575
2576                 modest_window_set_zoom (MODEST_WINDOW (msg_win),
2577                                         modest_window_get_zoom (MODEST_WINDOW (window)));
2578                 if (modest_window_mgr_register_window (mgr, msg_win, MODEST_WINDOW (window)))
2579                         gtk_widget_show_all (GTK_WIDGET (msg_win));
2580                 else
2581                         gtk_widget_destroy (GTK_WIDGET (msg_win));
2582                 
2583         } else {
2584                 /* message attachment */
2585                 TnyHeader *header = NULL;
2586                 ModestWindowMgr *mgr;
2587                 ModestWindow *msg_win = NULL;
2588                 gboolean found;
2589
2590                 header = tny_msg_get_header (TNY_MSG (mime_part));
2591                 mgr = modest_runtime_get_window_mgr ();
2592                 found = modest_window_mgr_find_registered_header (mgr, header, &msg_win);
2593
2594                 if (found) {
2595                         /* if it's found, but there is no msg_win, it's probably in the process of being created;
2596                          * thus, we don't do anything */
2597                         g_warning ("window for is already being created");
2598                 } else {
2599                         /* it's not found, so create a new window for it */
2600                         modest_window_mgr_register_header (mgr, header, attachment_uid); /* register the uid before building the window */
2601                         gchar *account = g_strdup (modest_window_get_active_account (MODEST_WINDOW (window)));
2602                         const gchar *mailbox = modest_window_get_active_mailbox (MODEST_WINDOW (window));
2603                         if (!account)
2604                                 account = modest_account_mgr_get_default_account (modest_runtime_get_account_mgr ());
2605                         msg_win = modest_msg_view_window_new_for_attachment (TNY_MSG (mime_part), account, 
2606                                                                              mailbox, attachment_uid);
2607                         modest_window_set_zoom (MODEST_WINDOW (msg_win),
2608                                                 modest_window_get_zoom (MODEST_WINDOW (window)));
2609                         if (modest_window_mgr_register_window (mgr, msg_win, MODEST_WINDOW (window)))
2610                                 gtk_widget_show_all (GTK_WIDGET (msg_win));
2611                         else
2612                                 gtk_widget_destroy (GTK_WIDGET (msg_win));
2613                 }
2614         }
2615
2616  frees:
2617         if (attachment_uid)
2618                 g_free (attachment_uid);
2619         if (mime_part)
2620                 g_object_unref (mime_part);
2621 }
2622
2623 typedef struct
2624 {
2625         gchar *filename;
2626         TnyMimePart *part;
2627 } SaveMimePartPair;
2628
2629 typedef struct
2630 {
2631         GList *pairs;
2632         GnomeVFSResult result;
2633 } SaveMimePartInfo;
2634
2635 static void save_mime_part_info_free (SaveMimePartInfo *info, gboolean with_struct);
2636 static gboolean idle_save_mime_part_show_result (SaveMimePartInfo *info);
2637 static gpointer save_mime_part_to_file (SaveMimePartInfo *info);
2638 static void save_mime_parts_to_file_with_checks (SaveMimePartInfo *info);
2639
2640 static void
2641 save_mime_part_info_free (SaveMimePartInfo *info, gboolean with_struct)
2642 {
2643         GList *node;
2644
2645         for (node = info->pairs; node != NULL; node = g_list_next (node)) {
2646                 SaveMimePartPair *pair = (SaveMimePartPair *) node->data;
2647                 g_free (pair->filename);
2648                 g_object_unref (pair->part);
2649                 g_slice_free (SaveMimePartPair, pair);
2650         }
2651         g_list_free (info->pairs);
2652         info->pairs = NULL;
2653         if (with_struct) {
2654                 g_slice_free (SaveMimePartInfo, info);
2655         }
2656 }
2657
2658 static gboolean
2659 idle_save_mime_part_show_result (SaveMimePartInfo *info)
2660 {
2661         if (info->pairs != NULL) {
2662                 save_mime_part_to_file (info);
2663         } else {
2664                 /* This is a GDK lock because we are an idle callback and
2665                  * hildon_banner_show_information is or does Gtk+ code */
2666
2667                 gdk_threads_enter (); /* CHECKED */
2668                 save_mime_part_info_free (info, TRUE);
2669                 if (info->result == GNOME_VFS_OK) {
2670                         hildon_banner_show_information (NULL, NULL, _CS("sfil_ib_saved"));
2671                 } else if (info->result == GNOME_VFS_ERROR_NO_SPACE) {
2672                         gchar *msg = g_strdup_printf (_KR("cerm_device_memory_full"), "");
2673                         modest_platform_information_banner (NULL, NULL, msg);
2674                         g_free (msg);
2675                 } else {
2676                         hildon_banner_show_information (NULL, NULL, _("mail_ib_file_operation_failed"));
2677                 }
2678                 gdk_threads_leave (); /* CHECKED */
2679         }
2680
2681         return FALSE;
2682 }
2683
2684 static gpointer
2685 save_mime_part_to_file (SaveMimePartInfo *info)
2686 {
2687         GnomeVFSHandle *handle;
2688         TnyStream *stream;
2689         SaveMimePartPair *pair = (SaveMimePartPair *) info->pairs->data;
2690
2691         info->result = gnome_vfs_create (&handle, pair->filename, GNOME_VFS_OPEN_WRITE, FALSE, 0644);
2692         if (info->result == GNOME_VFS_OK) {
2693                 GError *error = NULL;
2694                 stream = tny_vfs_stream_new (handle);
2695                 if (tny_mime_part_decode_to_stream (pair->part, stream, &error) < 0) {
2696                         g_warning ("modest: could not save attachment %s: %d (%s)\n", pair->filename, error?error->code:-1, error?error->message:"Unknown error");
2697
2698                         if ((error->domain == TNY_ERROR_DOMAIN) && 
2699                             (error->code == TNY_IO_ERROR_WRITE) &&
2700                             (errno == ENOSPC)) {
2701                                 info->result = GNOME_VFS_ERROR_NO_SPACE;
2702                         } else {
2703                                 info->result = GNOME_VFS_ERROR_IO;
2704                         }
2705                 }
2706                 g_object_unref (G_OBJECT (stream));
2707                 g_object_unref (pair->part);
2708                 g_slice_free (SaveMimePartPair, pair);
2709                 info->pairs = g_list_delete_link (info->pairs, info->pairs);
2710         } else {
2711                 g_warning ("modest: could not create save attachment %s: %s\n", pair->filename, gnome_vfs_result_to_string (info->result));
2712                 save_mime_part_info_free (info, FALSE);
2713         }
2714
2715         g_idle_add ((GSourceFunc) idle_save_mime_part_show_result, info);
2716         return NULL;
2717 }
2718
2719 static void
2720 save_mime_parts_to_file_with_checks (SaveMimePartInfo *info)
2721 {
2722         gboolean is_ok = TRUE;
2723         gint replaced_files = 0;
2724         const GList *files = info->pairs;
2725         const GList *iter;
2726
2727         for (iter = files; (iter != NULL) && (replaced_files < 2); iter = g_list_next(iter)) {
2728                 SaveMimePartPair *pair = iter->data;
2729                 if (modest_utils_file_exists (pair->filename)) {
2730                         replaced_files++;
2731                 }
2732         }
2733         if (replaced_files) {
2734                 GtkWidget *confirm_overwrite_dialog;
2735
2736                 if (replaced_files == 1) {
2737                         SaveMimePartPair *pair = files->data;
2738                         const gchar *basename = strrchr (pair->filename, G_DIR_SEPARATOR) + 1;
2739
2740                         gchar *message = g_strdup_printf ("%s\n%s",
2741                                                           _FM("docm_nc_replace_file"),
2742                                                           (basename) ? basename : "");
2743                         confirm_overwrite_dialog = hildon_note_new_confirmation (NULL, message);
2744                         g_free (message);
2745                 } else {
2746                         confirm_overwrite_dialog = hildon_note_new_confirmation (NULL,
2747                                                                                  _FM("docm_nc_replace_multiple"));
2748                 }
2749                 if (gtk_dialog_run (GTK_DIALOG (confirm_overwrite_dialog)) != GTK_RESPONSE_OK)
2750                         is_ok = FALSE;
2751
2752                 gtk_widget_destroy (confirm_overwrite_dialog);
2753         }
2754
2755         if (!is_ok) {
2756                 save_mime_part_info_free (info, TRUE);
2757         } else {
2758                 g_thread_create ((GThreadFunc)save_mime_part_to_file, info, FALSE, NULL);
2759         }
2760
2761 }
2762
2763 static void
2764 save_attachments_response (GtkDialog *dialog,
2765                            gint       arg1,
2766                            gpointer   user_data)  
2767 {
2768         TnyList *mime_parts;
2769         gchar *chooser_uri;
2770         GList *files_to_save = NULL;
2771         gchar *current_folder;
2772
2773         mime_parts = TNY_LIST (user_data);
2774
2775         if (arg1 != GTK_RESPONSE_OK)
2776                 goto end;
2777
2778         chooser_uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dialog));
2779         current_folder = gtk_file_chooser_get_current_folder_uri (GTK_FILE_CHOOSER (dialog));
2780         if (current_folder && current_folder != '\0') {
2781                 GError *err = NULL;
2782                 modest_conf_set_string (modest_runtime_get_conf (), MODEST_CONF_LATEST_SAVE_ATTACHMENT_PATH, 
2783                                         current_folder,&err);
2784                 if (err != NULL) {
2785                         g_debug ("Error storing latest used folder: %s", err->message);
2786                         g_error_free (err);
2787                 }
2788         }
2789         g_free (current_folder);
2790
2791         if (!modest_utils_folder_writable (chooser_uri)) {
2792                 hildon_banner_show_information 
2793                         (NULL, NULL, _FM("sfil_ib_readonly_location"));
2794         } else {
2795                 TnyIterator *iter;
2796
2797                 iter = tny_list_create_iterator (mime_parts);
2798                 while (!tny_iterator_is_done (iter)) {
2799                         TnyMimePart *mime_part = (TnyMimePart *) tny_iterator_get_current (iter);
2800
2801                         if ((modest_tny_mime_part_is_attachment_for_modest (mime_part)) &&
2802                             !tny_mime_part_is_purged (mime_part) &&
2803                             (tny_mime_part_get_filename (mime_part) != NULL)) {
2804                                 SaveMimePartPair *pair;
2805
2806                                 pair = g_slice_new0 (SaveMimePartPair);
2807
2808                                 if (tny_list_get_length (mime_parts) > 1) {
2809                                         gchar *escaped = 
2810                                                 gnome_vfs_escape_slashes (tny_mime_part_get_filename (mime_part));
2811                                         pair->filename = g_build_filename (chooser_uri, escaped, NULL);
2812                                         g_free (escaped);
2813                                 } else {
2814                                         pair->filename = g_strdup (chooser_uri);
2815                                 }
2816                                 pair->part = mime_part;
2817                                 files_to_save = g_list_prepend (files_to_save, pair);
2818                         }
2819                         tny_iterator_next (iter);
2820                 }
2821                 g_object_unref (iter);
2822         }
2823         g_free (chooser_uri);
2824
2825         if (files_to_save != NULL) {
2826                 SaveMimePartInfo *info = g_slice_new0 (SaveMimePartInfo);
2827                 info->pairs = files_to_save;
2828                 info->result = TRUE;
2829                 save_mime_parts_to_file_with_checks (info);
2830         }
2831
2832  end:
2833         /* Free and close the dialog */
2834         g_object_unref (mime_parts);
2835         gtk_widget_destroy (GTK_WIDGET (dialog));
2836 }
2837
2838 void
2839 modest_msg_view_window_save_attachments (ModestMsgViewWindow *window, 
2840                                          TnyList *mime_parts)
2841 {
2842         ModestMsgViewWindowPrivate *priv;
2843         GtkWidget *save_dialog = NULL;
2844         gchar *conf_folder = NULL;
2845         gchar *filename = NULL;
2846         gchar *save_multiple_str = NULL;
2847
2848         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
2849         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2850
2851         if (mime_parts == NULL) {
2852                 /* In Hildon 2.2 save and delete operate over all the attachments as there's no
2853                  * selection available */
2854                 mime_parts = modest_msg_view_get_attachments (MODEST_MSG_VIEW (priv->msg_view));
2855                 if (mime_parts && !modest_maemo_utils_select_attachments (GTK_WINDOW (window), mime_parts, FALSE)) {
2856                         g_object_unref (mime_parts);
2857                         return;
2858                 }
2859                 if (mime_parts == NULL || tny_list_get_length (mime_parts) == 0) {
2860                         if (mime_parts) {
2861                                 g_object_unref (mime_parts);
2862                                 mime_parts = NULL;
2863                         }
2864                         return;
2865                 }
2866         } else {
2867                 g_object_ref (mime_parts);
2868         }
2869
2870         /* prepare dialog */
2871         if (tny_list_get_length (mime_parts) == 1) {
2872                 TnyIterator *iter;
2873                 /* only one attachment selected */
2874                 iter = tny_list_create_iterator (mime_parts);
2875                 TnyMimePart *mime_part = (TnyMimePart *) tny_iterator_get_current (iter);
2876                 g_object_unref (iter);
2877                 if (!modest_tny_mime_part_is_msg (mime_part) && 
2878                     modest_tny_mime_part_is_attachment_for_modest (mime_part) &&
2879                     !tny_mime_part_is_purged (mime_part)) {
2880                         filename = g_strdup (tny_mime_part_get_filename (mime_part));
2881                 } else {
2882                         /* TODO: show any error? */
2883                         g_warning ("Tried to save a non-file attachment");
2884                         g_object_unref (mime_parts);
2885                         return;
2886                 }
2887                 g_object_unref (mime_part);
2888         } else {
2889                 save_multiple_str = g_strdup_printf (_FM("sfil_va_number_of_objects_attachments"), 
2890                                                      tny_list_get_length (mime_parts));
2891         }
2892
2893         save_dialog = hildon_file_chooser_dialog_new (GTK_WINDOW (window), 
2894                                                       GTK_FILE_CHOOSER_ACTION_SAVE);
2895
2896         /* set folder */
2897         conf_folder = modest_conf_get_string (modest_runtime_get_conf (), MODEST_CONF_LATEST_SAVE_ATTACHMENT_PATH, NULL);
2898         if (conf_folder && conf_folder[0] != '\0') {
2899                 gtk_file_chooser_set_current_folder_uri (GTK_FILE_CHOOSER (save_dialog), conf_folder);
2900         } else {
2901                 gchar *docs_folder;
2902                 /* Set the default folder to images folder */
2903                 docs_folder = g_build_filename (g_getenv (MYDOCS_ENV), DOCS_FOLDER, NULL);
2904                 gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (save_dialog), docs_folder);
2905                 g_free (docs_folder);
2906         }
2907         g_free (conf_folder);
2908
2909         /* set filename */
2910         if (filename) {
2911                 gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (save_dialog), 
2912                                                    filename);
2913                 g_free (filename);
2914         }
2915
2916         /* if multiple, set multiple string */
2917         if (save_multiple_str) {
2918                 g_object_set (G_OBJECT (save_dialog), "save-multiple", save_multiple_str, NULL);
2919                 gtk_window_set_title (GTK_WINDOW (save_dialog), _FM("sfil_ti_save_objects_files"));
2920         }
2921
2922         /* We must run this asynchronously, because the hildon dialog
2923            performs a gtk_dialog_run by itself which leads to gdk
2924            deadlocks */
2925         g_signal_connect (save_dialog, "response", 
2926                           G_CALLBACK (save_attachments_response), mime_parts);
2927
2928         gtk_widget_show_all (save_dialog);
2929 }
2930
2931 static gboolean
2932 show_remove_attachment_information (gpointer userdata)
2933 {
2934         ModestMsgViewWindow *window = (ModestMsgViewWindow *) userdata;
2935         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2936
2937         /* We're outside the main lock */
2938         gdk_threads_enter ();
2939
2940         if (priv->remove_attachment_banner != NULL) {
2941                 gtk_widget_destroy (priv->remove_attachment_banner);
2942                 g_object_unref (priv->remove_attachment_banner);
2943         }
2944
2945         priv->remove_attachment_banner = g_object_ref (
2946                 hildon_banner_show_animation (NULL, NULL, _("mcen_me_inbox_remove_attachments")));
2947
2948         gdk_threads_leave ();
2949
2950         return FALSE;
2951 }
2952
2953 void
2954 modest_msg_view_window_remove_attachments (ModestMsgViewWindow *window, gboolean get_all)
2955 {
2956         ModestMsgViewWindowPrivate *priv;
2957         TnyList *mime_parts = NULL, *tmp;
2958         gchar *confirmation_message;
2959         gint response;
2960         gint n_attachments;
2961         TnyMsg *msg;
2962         TnyIterator *iter;
2963
2964         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
2965         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2966
2967         /* In hildon 2.2 we ignore the get_all flag as we always get all attachments. This is
2968          * because we don't have selection
2969          */
2970         mime_parts = modest_msg_view_get_attachments (MODEST_MSG_VIEW (priv->msg_view));
2971
2972         /* Remove already purged messages from mime parts list. We use
2973            a copy of the list to remove items in the original one */
2974         tmp = tny_list_copy (mime_parts);
2975         iter = tny_list_create_iterator (tmp);
2976         while (!tny_iterator_is_done (iter)) {
2977                 TnyMimePart *part = TNY_MIME_PART (tny_iterator_get_current (iter));
2978                 if (tny_mime_part_is_purged (part))
2979                         tny_list_remove (mime_parts, (GObject *) part);
2980
2981                 g_object_unref (part);
2982                 tny_iterator_next (iter);
2983         }
2984         g_object_unref (tmp);
2985         g_object_unref (iter);
2986
2987         if (!modest_maemo_utils_select_attachments (GTK_WINDOW (window), mime_parts, TRUE) ||
2988             tny_list_get_length (mime_parts) == 0) {
2989                 g_object_unref (mime_parts);
2990                 return;
2991         }
2992
2993         n_attachments = tny_list_get_length (mime_parts);
2994         if (n_attachments == 1) {
2995                 gchar *filename;
2996                 TnyMimePart *part;
2997
2998                 iter = tny_list_create_iterator (mime_parts);
2999                 part = (TnyMimePart *) tny_iterator_get_current (iter);
3000                 g_object_unref (iter);
3001                 if (modest_tny_mime_part_is_msg (part)) {
3002                         TnyHeader *header;
3003                         header = tny_msg_get_header (TNY_MSG (part));
3004                         filename = tny_header_dup_subject (header);
3005                         g_object_unref (header);
3006                         if (filename == NULL)
3007                                 filename = g_strdup (_("mail_va_no_subject"));
3008                 } else {
3009                         filename = g_strdup (tny_mime_part_get_filename (TNY_MIME_PART (part)));
3010                 }
3011                 confirmation_message = g_strdup_printf (_("mcen_nc_purge_file_text"), filename);
3012                 g_free (filename);
3013                 g_object_unref (part);
3014         } else {
3015                 confirmation_message = g_strdup_printf (ngettext("mcen_nc_purge_file_text", 
3016                                                                  "mcen_nc_purge_files_text", 
3017                                                                  n_attachments), n_attachments);
3018         }
3019         response = modest_platform_run_confirmation_dialog (GTK_WINDOW (window),
3020                                                             confirmation_message);
3021         g_free (confirmation_message);
3022
3023         if (response != GTK_RESPONSE_OK) {
3024                 g_object_unref (mime_parts);
3025                 return;
3026         }
3027
3028         priv->purge_timeout = g_timeout_add (2000, show_remove_attachment_information, window);
3029
3030         iter = tny_list_create_iterator (mime_parts);
3031         while (!tny_iterator_is_done (iter)) {
3032                 TnyMimePart *part;
3033
3034                 part = (TnyMimePart *) tny_iterator_get_current (iter);
3035                 tny_mime_part_set_purged (TNY_MIME_PART (part));
3036                 g_object_unref (part);
3037                 tny_iterator_next (iter);
3038         }
3039         g_object_unref (iter);
3040
3041         msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
3042         tny_msg_view_clear (TNY_MSG_VIEW (priv->msg_view));
3043         tny_msg_rewrite_cache (msg);
3044         tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
3045         g_object_unref (msg);
3046
3047         g_object_unref (mime_parts);
3048
3049         if (priv->purge_timeout > 0) {
3050                 g_source_remove (priv->purge_timeout);
3051                 priv->purge_timeout = 0;
3052         }
3053
3054         if (priv->remove_attachment_banner) {
3055                 gtk_widget_destroy (priv->remove_attachment_banner);
3056                 g_object_unref (priv->remove_attachment_banner);
3057                 priv->remove_attachment_banner = NULL;
3058         }
3059 }
3060
3061
3062 static void
3063 update_window_title (ModestMsgViewWindow *window)
3064 {
3065         ModestMsgViewWindowPrivate *priv;
3066         TnyMsg *msg = NULL;
3067         TnyHeader *header = NULL;
3068         gchar *subject = NULL;
3069
3070         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
3071
3072         /* Note that if the window is closed while we're retrieving
3073            the message, this widget could de deleted */
3074         if (!priv->msg_view)
3075                 return;
3076
3077         msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
3078
3079         if (msg != NULL) {
3080                 header = tny_msg_get_header (msg);
3081                 subject = tny_header_dup_subject (header);
3082                 g_object_unref (header);
3083                 g_object_unref (msg);
3084         }
3085
3086         if ((subject == NULL)||(subject[0] == '\0')) {
3087                 g_free (subject);
3088                 subject = g_strdup (_("mail_va_no_subject"));
3089         }
3090
3091         gtk_window_set_title (GTK_WINDOW (window), subject);
3092 }
3093
3094
3095 static void
3096 on_move_focus (GtkWidget *widget,
3097                GtkDirectionType direction,
3098                gpointer userdata)
3099 {
3100         g_signal_stop_emission_by_name (G_OBJECT (widget), "move-focus");
3101 }
3102
3103 static TnyStream *
3104 fetch_image_open_stream (TnyStreamCache *self, gint64 *expected_size, gchar *uri)
3105 {
3106         GnomeVFSResult result;
3107         GnomeVFSHandle *handle = NULL;
3108         GnomeVFSFileInfo *info = NULL;
3109         TnyStream *stream;
3110
3111         result = gnome_vfs_open (&handle, uri, GNOME_VFS_OPEN_READ);
3112         if (result != GNOME_VFS_OK) {
3113                 *expected_size = 0;
3114                 return NULL;
3115         }
3116