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