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