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