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