* Implemented a new replacement for secureauth combobox called
[modest] / src / hildon2 / modest-msg-view-window.c
1 /* Copyright (c) 2006, Nokia Corporation
2  * All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  * * Redistributions of source code must retain the above copyright
9  *   notice, this list of conditions and the following disclaimer.
10  * * Redistributions in binary form must reproduce the above copyright
11  *   notice, this list of conditions and the following disclaimer in the
12  *   documentation and/or other materials provided with the distribution.
13  * * Neither the name of the Nokia Corporation nor the names of its
14  *   contributors may be used to endorse or promote products derived from
15  *   this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
18  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
20  * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
21  * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29 #include <glib/gi18n.h>
30 #include <string.h>
31 #include <tny-account-store.h>
32 #include <tny-simple-list.h>
33 #include <tny-msg.h>
34 #include <tny-mime-part.h>
35 #include <tny-vfs-stream.h>
36 #include "modest-marshal.h"
37 #include "modest-platform.h"
38 #include <modest-utils.h>
39 #include <modest-maemo-utils.h>
40 #include <modest-tny-msg.h>
41 #include <modest-msg-view-window.h>
42 #include <modest-main-window-ui.h>
43 #include "modest-msg-view-window-ui-dimming.h"
44 #include <modest-widget-memory.h>
45 #include <modest-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 "hildon/hildon-pannable-area.h"
52 #include "modest-defs.h"
53 #include "modest-hildon-includes.h"
54 #include "modest-ui-dimming-manager.h"
55 #include <gdk/gdkkeysyms.h>
56 #include <modest-tny-account.h>
57 #include <modest-mime-part-view.h>
58 #include <modest-isearch-view.h>
59 #include <modest-tny-mime-part.h>
60 #include <math.h>
61 #include <errno.h>
62 #include <glib/gstdio.h>
63 #include <modest-debug.h>
64
65 #define DEFAULT_FOLDER "MyDocs/.documents"
66
67 static void  modest_msg_view_window_class_init   (ModestMsgViewWindowClass *klass);
68 static void  modest_msg_view_window_init         (ModestMsgViewWindow *obj);
69 static void  modest_header_view_observer_init(
70                 ModestHeaderViewObserverIface *iface_class);
71 static void  modest_msg_view_window_finalize     (GObject *obj);
72 static void  modest_msg_view_window_toggle_find_toolbar (GtkToggleAction *obj,
73                                                          gpointer data);
74 static void  modest_msg_view_window_find_toolbar_close (GtkWidget *widget,
75                                                         ModestMsgViewWindow *obj);
76 static void  modest_msg_view_window_find_toolbar_search (GtkWidget *widget,
77                                                         ModestMsgViewWindow *obj);
78
79 static void modest_msg_view_window_disconnect_signals (ModestWindow *self);
80 static void modest_msg_view_window_set_zoom (ModestWindow *window,
81                                              gdouble zoom);
82 static gdouble modest_msg_view_window_get_zoom (ModestWindow *window);
83 static gboolean modest_msg_view_window_zoom_minus (ModestWindow *window);
84 static gboolean modest_msg_view_window_zoom_plus (ModestWindow *window);
85 static gboolean modest_msg_view_window_key_event (GtkWidget *window,
86                                                   GdkEventKey *event,
87                                                   gpointer userdata);
88 static gboolean modest_msg_view_window_window_state_event (GtkWidget *widget, 
89                                                            GdkEventWindowState *event, 
90                                                            gpointer userdata);
91 static void modest_msg_view_window_update_priority (ModestMsgViewWindow *window);
92
93 static void modest_msg_view_window_show_toolbar   (ModestWindow *window,
94                                                    gboolean show_toolbar);
95
96 static void modest_msg_view_window_clipboard_owner_change (GtkClipboard *clipboard,
97                                                            GdkEvent *event,
98                                                            ModestMsgViewWindow *window);
99
100 static void modest_msg_view_window_on_row_changed (GtkTreeModel *header_model,
101                                                    GtkTreePath *arg1,
102                                                    GtkTreeIter *arg2,
103                                                    ModestMsgViewWindow *window);
104
105 static void modest_msg_view_window_on_row_deleted (GtkTreeModel *header_model,
106                                                    GtkTreePath *arg1,
107                                                    ModestMsgViewWindow *window);
108
109 static void modest_msg_view_window_on_row_inserted (GtkTreeModel *header_model,
110                                                     GtkTreePath *tree_path,
111                                                     GtkTreeIter *tree_iter,
112                                                     ModestMsgViewWindow *window);
113
114 static void modest_msg_view_window_on_row_reordered (GtkTreeModel *header_model,
115                                                      GtkTreePath *arg1,
116                                                      GtkTreeIter *arg2,
117                                                      gpointer arg3,
118                                                      ModestMsgViewWindow *window);
119
120 static void modest_msg_view_window_update_model_replaced (ModestHeaderViewObserver *window,
121                                                           GtkTreeModel *model,
122                                                           const gchar *tny_folder_id);
123
124 static void cancel_progressbar  (GtkToolButton *toolbutton,
125                                  ModestMsgViewWindow *self);
126
127 static void on_queue_changed    (ModestMailOperationQueue *queue,
128                                  ModestMailOperation *mail_op,
129                                  ModestMailOperationQueueNotification type,
130                                  ModestMsgViewWindow *self);
131
132 static void on_account_removed  (TnyAccountStore *account_store, 
133                                  TnyAccount *account,
134                                  gpointer user_data);
135
136 static void on_move_focus (GtkWidget *widget,
137                            GtkDirectionType direction,
138                            gpointer userdata);
139
140 static void view_msg_cb         (ModestMailOperation *mail_op, 
141                                  TnyHeader *header, 
142                                  gboolean canceled,
143                                  TnyMsg *msg, 
144                                  GError *error,
145                                  gpointer user_data);
146
147 static void set_toolbar_mode    (ModestMsgViewWindow *self, 
148                                  ModestToolBarModes mode);
149
150 static void update_window_title (ModestMsgViewWindow *window);
151
152 static gboolean set_toolbar_transfer_mode     (ModestMsgViewWindow *self); 
153 static void init_window (ModestMsgViewWindow *obj);
154
155 static gboolean msg_is_visible (TnyHeader *header, gboolean check_outbox);
156
157 static void check_dimming_rules_after_change (ModestMsgViewWindow *window);
158
159 static gboolean on_fetch_image (ModestMsgView *msgview,
160                                 const gchar *uri,
161                                 TnyStream *stream,
162                                 ModestMsgViewWindow *window);
163
164 static gboolean modest_msg_view_window_scroll_child (ModestMsgViewWindow *self,
165                                                      GtkScrollType scroll_type,
166                                                      gboolean horizontal,
167                                                      gpointer userdata);
168
169 /* list my signals */
170 enum {
171         MSG_CHANGED_SIGNAL,
172         SCROLL_CHILD_SIGNAL,
173         LAST_SIGNAL
174 };
175
176 static const GtkToggleActionEntry msg_view_toggle_action_entries [] = {
177         { "FindInMessage",    MODEST_TOOLBAR_ICON_FIND,    N_("qgn_toolb_gene_find"), NULL, NULL, G_CALLBACK (modest_msg_view_window_toggle_find_toolbar), FALSE },
178         { "ToolsFindInMessage", NULL, N_("mcen_me_viewer_find"), "<CTRL>F", NULL, G_CALLBACK (modest_msg_view_window_toggle_find_toolbar), FALSE },
179 };
180
181 static const GtkRadioActionEntry msg_view_zoom_action_entries [] = {
182         { "Zoom50", NULL, N_("mcen_me_viewer_50"), NULL, NULL, 50 },
183         { "Zoom80", NULL, N_("mcen_me_viewer_80"), NULL, NULL, 80 },
184         { "Zoom100", NULL, N_("mcen_me_viewer_100"), NULL, NULL, 100 },
185         { "Zoom120", NULL, N_("mcen_me_viewer_120"), NULL, NULL, 120 },
186         { "Zoom150", NULL, N_("mcen_me_viewer_150"), NULL, NULL, 150 },
187         { "Zoom200", NULL, N_("mcen_me_viewer_200"), NULL, NULL, 200 }
188 };
189
190 typedef struct _ModestMsgViewWindowPrivate ModestMsgViewWindowPrivate;
191 struct _ModestMsgViewWindowPrivate {
192
193         GtkWidget   *msg_view;
194         GtkWidget   *main_scroll;
195         GtkWidget   *find_toolbar;
196         gchar       *last_search;
197
198         /* Progress observers */
199         GtkWidget        *progress_bar;
200         GSList           *progress_widgets;
201
202         /* Tollbar items */
203         GtkWidget   *progress_toolitem;
204         GtkWidget   *cancel_toolitem;
205         GtkWidget   *prev_toolitem;
206         GtkWidget   *next_toolitem;
207         ModestToolBarModes current_toolbar_mode;
208
209         /* Optimized view enabled */
210         gboolean optimized_view;
211
212         /* Whether this was created via the *_new_for_search_result() function. */
213         gboolean is_search_result;
214
215         /* Whether the message is in outbox */
216         gboolean is_outbox;
217         
218         /* A reference to the @model of the header view 
219          * to allow selecting previous/next messages,
220          * if the message is currently selected in the header view.
221          */
222         const gchar *header_folder_id;
223         GtkTreeModel *header_model;
224         GtkTreeRowReference *row_reference;
225         GtkTreeRowReference *next_row_reference;
226
227         gulong clipboard_change_handler;
228         gulong queue_change_handler;
229         gulong account_removed_handler;
230         gulong row_changed_handler;
231         gulong row_deleted_handler;
232         gulong row_inserted_handler;
233         gulong rows_reordered_handler;
234
235         guint purge_timeout;
236         GtkWidget *remove_attachment_banner;
237
238         guint progress_bar_timeout;
239
240         gchar *msg_uid;
241         
242         GSList *sighandlers;
243 };
244
245 #define MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(o)      (G_TYPE_INSTANCE_GET_PRIVATE((o), \
246                                                     MODEST_TYPE_MSG_VIEW_WINDOW, \
247                                                     ModestMsgViewWindowPrivate))
248 /* globals */
249 static GtkWindowClass *parent_class = NULL;
250
251 /* uncomment the following if you have defined any signals */
252 static guint signals[LAST_SIGNAL] = {0};
253
254 GType
255 modest_msg_view_window_get_type (void)
256 {
257         static GType my_type = 0;
258         if (!my_type) {
259                 static const GTypeInfo my_info = {
260                         sizeof(ModestMsgViewWindowClass),
261                         NULL,           /* base init */
262                         NULL,           /* base finalize */
263                         (GClassInitFunc) modest_msg_view_window_class_init,
264                         NULL,           /* class finalize */
265                         NULL,           /* class data */
266                         sizeof(ModestMsgViewWindow),
267                         1,              /* n_preallocs */
268                         (GInstanceInitFunc) modest_msg_view_window_init,
269                         NULL
270                 };
271                 my_type = g_type_register_static (MODEST_TYPE_WINDOW,
272                                                   "ModestMsgViewWindow",
273                                                   &my_info, 0);
274
275                 static const GInterfaceInfo modest_header_view_observer_info = 
276                 {
277                         (GInterfaceInitFunc) modest_header_view_observer_init,
278                         NULL,         /* interface_finalize */
279                         NULL          /* interface_data */
280                 };
281
282                 g_type_add_interface_static (my_type,
283                                 MODEST_TYPE_HEADER_VIEW_OBSERVER,
284                                 &modest_header_view_observer_info);
285         }
286         return my_type;
287 }
288
289 static void
290 save_state (ModestWindow *self)
291 {
292         modest_widget_memory_save (modest_runtime_get_conf (),
293                                    G_OBJECT(self), 
294                                    MODEST_CONF_MSG_VIEW_WINDOW_KEY);
295 }
296
297
298 static void
299 restore_settings (ModestMsgViewWindow *self)
300 {
301         ModestConf *conf;
302         ModestWindowPrivate *parent_priv = MODEST_WINDOW_GET_PRIVATE (self);
303         GtkAction *action;
304
305         conf = modest_runtime_get_conf ();
306         action = gtk_ui_manager_get_action (parent_priv->ui_manager, 
307                                             "/MenuBar/ViewMenu/ViewShowToolbarMenu/ViewShowToolbarNormalScreenMenu");
308         gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action),
309                                       modest_conf_get_bool (conf, MODEST_CONF_MSG_VIEW_WINDOW_SHOW_TOOLBAR, NULL));
310         action = gtk_ui_manager_get_action (parent_priv->ui_manager, 
311                                             "/MenuBar/ViewMenu/ViewShowToolbarMenu/ViewShowToolbarFullScreenMenu");
312         gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action),
313                                       modest_conf_get_bool (conf, MODEST_CONF_MSG_VIEW_WINDOW_SHOW_TOOLBAR_FULLSCREEN, NULL));
314         modest_widget_memory_restore (conf,
315                                       G_OBJECT(self), 
316                                       MODEST_CONF_MSG_VIEW_WINDOW_KEY);
317 }
318
319 static gboolean modest_msg_view_window_scroll_child (ModestMsgViewWindow *self,
320                                                      GtkScrollType scroll_type,
321                                                      gboolean horizontal,
322                                                      gpointer userdata)
323 {
324         ModestMsgViewWindowPrivate *priv;
325         gboolean return_value;
326
327         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
328         g_signal_emit_by_name (priv->main_scroll, "scroll-child", scroll_type, horizontal, &return_value);
329         return return_value;
330 }
331
332 static void
333 add_scroll_binding (GtkBindingSet *binding_set,
334                     guint keyval,
335                     GtkScrollType scroll)
336 {
337         guint keypad_keyval = keyval - GDK_Left + GDK_KP_Left;
338         
339         gtk_binding_entry_add_signal (binding_set, keyval, 0,
340                                       "scroll_child", 2,
341                                       GTK_TYPE_SCROLL_TYPE, scroll,
342                                       G_TYPE_BOOLEAN, FALSE);
343         gtk_binding_entry_add_signal (binding_set, keypad_keyval, 0,
344                                       "scroll_child", 2,
345                                       GTK_TYPE_SCROLL_TYPE, scroll,
346                                       G_TYPE_BOOLEAN, FALSE);
347 }
348
349 static void
350 modest_msg_view_window_class_init (ModestMsgViewWindowClass *klass)
351 {
352         GObjectClass *gobject_class;
353         ModestWindowClass *modest_window_class;
354         GtkBindingSet *binding_set;
355
356         gobject_class = (GObjectClass*) klass;
357         modest_window_class = (ModestWindowClass *) klass;
358
359         parent_class            = g_type_class_peek_parent (klass);
360         gobject_class->finalize = modest_msg_view_window_finalize;
361
362         modest_window_class->set_zoom_func = modest_msg_view_window_set_zoom;
363         modest_window_class->get_zoom_func = modest_msg_view_window_get_zoom;
364         modest_window_class->zoom_minus_func = modest_msg_view_window_zoom_minus;
365         modest_window_class->zoom_plus_func = modest_msg_view_window_zoom_plus;
366         modest_window_class->show_toolbar_func = modest_msg_view_window_show_toolbar;
367         modest_window_class->disconnect_signals_func = modest_msg_view_window_disconnect_signals;
368
369         modest_window_class->save_state_func = save_state;
370
371         klass->scroll_child = modest_msg_view_window_scroll_child;
372
373         signals[MSG_CHANGED_SIGNAL] =
374                 g_signal_new ("msg-changed",
375                               G_TYPE_FROM_CLASS (gobject_class),
376                               G_SIGNAL_RUN_FIRST,
377                               G_STRUCT_OFFSET (ModestMsgViewWindowClass, msg_changed),
378                               NULL, NULL,
379                               modest_marshal_VOID__POINTER_POINTER,
380                               G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER);
381
382         signals[SCROLL_CHILD_SIGNAL] =
383                 g_signal_new ("scroll-child",
384                               G_TYPE_FROM_CLASS (gobject_class),
385                               G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
386                               G_STRUCT_OFFSET (ModestMsgViewWindowClass, scroll_child),
387                               NULL, NULL,
388                               modest_marshal_BOOLEAN__ENUM_BOOLEAN,
389                               G_TYPE_BOOLEAN, 2, GTK_TYPE_SCROLL_TYPE, G_TYPE_BOOLEAN);
390
391         binding_set = gtk_binding_set_by_class (klass);
392         add_scroll_binding (binding_set, GDK_Up, GTK_SCROLL_STEP_UP);
393         add_scroll_binding (binding_set, GDK_Down, GTK_SCROLL_STEP_DOWN);
394         add_scroll_binding (binding_set, GDK_Page_Up, GTK_SCROLL_PAGE_UP);
395         add_scroll_binding (binding_set, GDK_Page_Down, GTK_SCROLL_PAGE_DOWN);
396         add_scroll_binding (binding_set, GDK_Home, GTK_SCROLL_START);
397         add_scroll_binding (binding_set, GDK_End, GTK_SCROLL_END);
398
399         g_type_class_add_private (gobject_class, sizeof(ModestMsgViewWindowPrivate));
400
401 }
402
403 static void modest_header_view_observer_init(
404                 ModestHeaderViewObserverIface *iface_class)
405 {
406         iface_class->update_func = modest_msg_view_window_update_model_replaced;
407 }
408
409 static void
410 modest_msg_view_window_init (ModestMsgViewWindow *obj)
411 {
412         ModestMsgViewWindowPrivate *priv;
413         ModestWindowPrivate *parent_priv = NULL;
414         GtkActionGroup *action_group = NULL;
415         GError *error = NULL;
416         GdkPixbuf *window_icon;
417
418         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(obj);
419         parent_priv = MODEST_WINDOW_GET_PRIVATE(obj);
420         parent_priv->ui_manager = gtk_ui_manager_new();
421
422         action_group = gtk_action_group_new ("ModestMsgViewWindowActions");
423         gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE);
424
425         /* Add common actions */
426         gtk_action_group_add_actions (action_group,
427                                       modest_action_entries,
428                                       G_N_ELEMENTS (modest_action_entries),
429                                       obj);
430         gtk_action_group_add_toggle_actions (action_group,
431                                              modest_toggle_action_entries,
432                                              G_N_ELEMENTS (modest_toggle_action_entries),
433                                              obj);
434         gtk_action_group_add_toggle_actions (action_group,
435                                              msg_view_toggle_action_entries,
436                                              G_N_ELEMENTS (msg_view_toggle_action_entries),
437                                              obj);
438         gtk_action_group_add_radio_actions (action_group,
439                                             msg_view_zoom_action_entries,
440                                             G_N_ELEMENTS (msg_view_zoom_action_entries),
441                                             100,
442                                             G_CALLBACK (modest_ui_actions_on_change_zoom),
443                                             obj);
444
445         gtk_ui_manager_insert_action_group (parent_priv->ui_manager, action_group, 0);
446         g_object_unref (action_group);
447
448         /* Load the UI definition */
449         gtk_ui_manager_add_ui_from_file (parent_priv->ui_manager, MODEST_UIDIR "modest-msg-view-window-ui.xml",
450                                          &error);
451         if (error) {
452                 g_printerr ("modest: could not merge modest-msg-view-window-ui.xml: %s\n", error->message);
453                 g_error_free (error);
454                 error = NULL;
455         }
456         /* ****** */
457
458         /* Add accelerators */
459         gtk_window_add_accel_group (GTK_WINDOW (obj), 
460                                     gtk_ui_manager_get_accel_group (parent_priv->ui_manager));
461         
462         priv->is_search_result = FALSE;
463         priv->is_outbox = FALSE;
464
465         priv->msg_view      = NULL;
466         priv->header_model  = NULL;
467         priv->header_folder_id  = NULL;
468         priv->clipboard_change_handler = 0;
469         priv->queue_change_handler = 0;
470         priv->account_removed_handler = 0;
471         priv->row_changed_handler = 0;
472         priv->row_deleted_handler = 0;
473         priv->row_inserted_handler = 0;
474         priv->rows_reordered_handler = 0;
475         priv->current_toolbar_mode = TOOLBAR_MODE_NORMAL;
476
477         priv->optimized_view  = FALSE;
478         priv->progress_bar_timeout = 0;
479         priv->purge_timeout = 0;
480         priv->remove_attachment_banner = NULL;
481         priv->msg_uid = NULL;
482         
483         priv->sighandlers = NULL;
484         
485         /* Init window */
486         init_window (MODEST_MSG_VIEW_WINDOW(obj));
487         
488         /* Set window icon */
489         window_icon = modest_platform_get_icon (MODEST_APP_MSG_VIEW_ICON, MODEST_ICON_SIZE_BIG); 
490         if (window_icon) {
491                 gtk_window_set_icon (GTK_WINDOW (obj), window_icon);
492                 g_object_unref (window_icon);
493         }       
494         
495         hildon_program_add_window (hildon_program_get_instance(),
496                                    HILDON_WINDOW(obj));
497
498         modest_window_mgr_register_help_id (modest_runtime_get_window_mgr(),
499                                             GTK_WINDOW(obj),"applications_email_viewer");
500 }
501
502
503 static gboolean
504 set_toolbar_transfer_mode (ModestMsgViewWindow *self)
505 {
506         ModestMsgViewWindowPrivate *priv = NULL;
507         
508         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), FALSE);
509
510         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
511
512         set_toolbar_mode (self, TOOLBAR_MODE_TRANSFER);
513         
514         if (priv->progress_bar_timeout > 0) {
515                 g_source_remove (priv->progress_bar_timeout);
516                 priv->progress_bar_timeout = 0;
517         }
518         
519         return FALSE;
520 }
521
522 static void 
523 set_toolbar_mode (ModestMsgViewWindow *self, 
524                   ModestToolBarModes mode)
525 {
526         ModestWindowPrivate *parent_priv;
527         ModestMsgViewWindowPrivate *priv;
528 /*      GtkWidget *widget = NULL; */
529
530         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self));
531
532         parent_priv = MODEST_WINDOW_GET_PRIVATE(self);
533         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
534                         
535         /* Sets current toolbar mode */
536         priv->current_toolbar_mode = mode;
537
538         /* Update toolbar dimming state */
539         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (self));
540
541         switch (mode) {
542         case TOOLBAR_MODE_NORMAL:               
543                 if (priv->progress_toolitem) {
544                         gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->progress_toolitem), FALSE);
545                         gtk_widget_hide (priv->progress_toolitem);
546                 }
547
548                 if (priv->progress_bar)
549                         gtk_widget_hide (priv->progress_bar);
550                         
551                 if (priv->cancel_toolitem)
552                         gtk_widget_hide (priv->cancel_toolitem);
553
554                 if (priv->prev_toolitem)
555                         gtk_widget_show (priv->prev_toolitem);
556                 
557                 if (priv->next_toolitem)
558                         gtk_widget_show (priv->next_toolitem);
559                         
560                 /* Hide toolbar if optimized view is enabled */
561                 if (priv->optimized_view) {
562                         gtk_widget_set_no_show_all (parent_priv->toolbar, TRUE);
563                         gtk_widget_hide (GTK_WIDGET(parent_priv->toolbar));
564                 }
565
566                 break;
567         case TOOLBAR_MODE_TRANSFER:
568                 if (priv->prev_toolitem)
569                         gtk_widget_hide (priv->prev_toolitem);
570                 
571                 if (priv->next_toolitem)
572                         gtk_widget_hide (priv->next_toolitem);
573                 
574                 if (priv->progress_bar)
575                         gtk_widget_show (priv->progress_bar);
576
577                 if (priv->progress_toolitem) {
578                         gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->progress_toolitem), TRUE);
579                         gtk_widget_show (priv->progress_toolitem);
580                 }
581                         
582                 if (priv->cancel_toolitem)
583                         gtk_widget_show (priv->cancel_toolitem);
584
585                 /* Show toolbar if it's hiden (optimized view ) */
586                 if (priv->optimized_view) {
587                         gtk_widget_set_no_show_all (parent_priv->toolbar, FALSE);
588                         gtk_widget_show (GTK_WIDGET (parent_priv->toolbar));
589                 }
590
591                 break;
592         default:
593                 g_return_if_reached ();
594         }
595
596 }
597
598
599 static void
600 init_window (ModestMsgViewWindow *obj)
601 {
602         GtkWidget *main_vbox;
603         ModestMsgViewWindowPrivate *priv;
604
605         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(obj);
606
607         priv->msg_view = GTK_WIDGET (tny_platform_factory_new_msg_view (modest_tny_platform_factory_get_instance ()));
608         modest_msg_view_set_shadow_type (MODEST_MSG_VIEW (priv->msg_view), GTK_SHADOW_NONE);
609         main_vbox = gtk_vbox_new  (FALSE, 6);
610 #ifdef MODEST_TOOLKIT_HILDON2
611         priv->main_scroll = hildon_pannable_area_new ();
612         gtk_container_add (GTK_CONTAINER (priv->main_scroll), priv->msg_view);
613 #else
614 #ifdef MODEST_USE_MOZEMBED
615         priv->main_scroll = priv->msg_view;
616         gtk_widget_set_size_request (priv->msg_view, -1, 1600);
617 #else
618         priv->main_scroll = gtk_scrolled_window_new (NULL, NULL);
619         gtk_container_add (GTK_CONTAINER (priv->main_scroll), priv->msg_view);
620 #endif
621         gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->main_scroll), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
622         gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (priv->main_scroll), GTK_SHADOW_NONE);
623         modest_maemo_set_thumbable_scrollbar (GTK_SCROLLED_WINDOW(priv->main_scroll), TRUE);
624
625 #endif
626         gtk_box_pack_start (GTK_BOX(main_vbox), priv->main_scroll, TRUE, TRUE, 0);
627         gtk_container_add   (GTK_CONTAINER(obj), main_vbox);
628
629         priv->find_toolbar = hildon_find_toolbar_new (NULL);
630         hildon_window_add_toolbar (HILDON_WINDOW (obj), GTK_TOOLBAR (priv->find_toolbar));
631         gtk_widget_set_no_show_all (priv->find_toolbar, TRUE);
632         
633         gtk_widget_show_all (GTK_WIDGET(main_vbox));
634 }
635
636 static void
637 modest_msg_view_window_disconnect_signals (ModestWindow *self)
638 {
639         ModestMsgViewWindowPrivate *priv;
640         ModestHeaderView *header_view = NULL;
641         ModestWindow *main_window = NULL;
642         
643         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
644
645         if (gtk_clipboard_get (GDK_SELECTION_PRIMARY) &&
646             g_signal_handler_is_connected (gtk_clipboard_get (GDK_SELECTION_PRIMARY),
647                                            priv->clipboard_change_handler)) 
648                 g_signal_handler_disconnect (gtk_clipboard_get (GDK_SELECTION_PRIMARY), 
649                                              priv->clipboard_change_handler);
650
651         if (g_signal_handler_is_connected (G_OBJECT (modest_runtime_get_mail_operation_queue ()), 
652                                            priv->queue_change_handler))
653                 g_signal_handler_disconnect (G_OBJECT (modest_runtime_get_mail_operation_queue ()), 
654                                              priv->queue_change_handler);
655
656         if (g_signal_handler_is_connected (G_OBJECT (modest_runtime_get_account_store ()), 
657                                            priv->account_removed_handler))
658                 g_signal_handler_disconnect (G_OBJECT (modest_runtime_get_account_store ()), 
659                                              priv->account_removed_handler);
660
661         if (priv->header_model) {
662                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
663                                                   priv->row_changed_handler))
664                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
665                                                     priv->row_changed_handler);
666                 
667                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
668                                                   priv->row_deleted_handler))
669                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
670                                              priv->row_deleted_handler);
671                 
672                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
673                                                   priv->row_inserted_handler))
674                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
675                                                     priv->row_inserted_handler);
676                 
677                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
678                                                   priv->rows_reordered_handler))
679                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
680                                                     priv->rows_reordered_handler);
681         }
682
683         modest_signal_mgr_disconnect_all_and_destroy (priv->sighandlers);
684         priv->sighandlers = NULL;
685         
686         main_window = modest_window_mgr_get_main_window (modest_runtime_get_window_mgr(),
687                                                          FALSE); /* don't create */
688         if (!main_window)
689                 return;
690         
691         header_view = MODEST_HEADER_VIEW(
692                         modest_main_window_get_child_widget(
693                                 MODEST_MAIN_WINDOW(main_window),
694                                 MODEST_MAIN_WINDOW_WIDGET_TYPE_HEADER_VIEW));
695         if (header_view == NULL)
696                 return;
697         
698         modest_header_view_remove_observer(header_view,
699                         MODEST_HEADER_VIEW_OBSERVER(self));
700 }       
701
702 static void
703 modest_msg_view_window_finalize (GObject *obj)
704 {
705         ModestMsgViewWindowPrivate *priv;
706
707         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (obj);
708
709         /* Sanity check: shouldn't be needed, the window mgr should
710            call this function before */
711         modest_msg_view_window_disconnect_signals (MODEST_WINDOW (obj));
712
713         if (priv->header_model != NULL) {
714                 g_object_unref (priv->header_model);
715                 priv->header_model = NULL;
716         }
717
718         if (priv->progress_bar_timeout > 0) {
719                 g_source_remove (priv->progress_bar_timeout);
720                 priv->progress_bar_timeout = 0;
721         }
722
723         if (priv->remove_attachment_banner) {
724                 gtk_widget_destroy (priv->remove_attachment_banner);
725                 g_object_unref (priv->remove_attachment_banner);
726                 priv->remove_attachment_banner = NULL;
727         }
728
729         if (priv->purge_timeout > 0) {
730                 g_source_remove (priv->purge_timeout);
731                 priv->purge_timeout = 0;
732         }
733
734         if (priv->row_reference) {
735                 gtk_tree_row_reference_free (priv->row_reference);
736                 priv->row_reference = NULL;
737         }
738
739         if (priv->next_row_reference) {
740                 gtk_tree_row_reference_free (priv->next_row_reference);
741                 priv->next_row_reference = NULL;
742         }
743
744         if (priv->msg_uid) {
745                 g_free (priv->msg_uid);
746                 priv->msg_uid = NULL;
747         }
748
749         G_OBJECT_CLASS(parent_class)->finalize (obj);
750 }
751
752 static gboolean
753 select_next_valid_row (GtkTreeModel *model,
754                        GtkTreeRowReference **row_reference,
755                        gboolean cycle,
756                        gboolean is_outbox)
757 {
758         GtkTreeIter tmp_iter;
759         GtkTreePath *path;
760         GtkTreePath *next = NULL;
761         gboolean retval = FALSE, finished;
762
763         g_return_val_if_fail (gtk_tree_row_reference_valid (*row_reference), FALSE);
764
765         path = gtk_tree_row_reference_get_path (*row_reference);
766         gtk_tree_model_get_iter (model, &tmp_iter, path);
767         gtk_tree_row_reference_free (*row_reference);
768         *row_reference = NULL;
769
770         finished = FALSE;
771         do {
772                 TnyHeader *header = NULL;
773
774                 if (gtk_tree_model_iter_next (model, &tmp_iter)) {
775                         gtk_tree_model_get (model, &tmp_iter, 
776                                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, 
777                                             &header, -1);
778
779                         if (header) {
780                                 if (msg_is_visible (header, is_outbox)) {
781                                         next = gtk_tree_model_get_path (model, &tmp_iter);
782                                         *row_reference = gtk_tree_row_reference_new (model, next);
783                                         retval = TRUE;
784                                         finished = TRUE;
785                                 }
786                                 g_object_unref (header);
787                                 header = NULL;
788                         }
789                 } else if (cycle && gtk_tree_model_get_iter_first (model, &tmp_iter)) {
790                         next = gtk_tree_model_get_path (model, &tmp_iter);
791                         
792                         /* Ensure that we are not selecting the same */
793                         if (gtk_tree_path_compare (path, next) != 0) {
794                                 gtk_tree_model_get (model, &tmp_iter, 
795                                                     TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, 
796                                                     &header, -1);                               
797                                 if (header) {
798                                         if (msg_is_visible (header, is_outbox)) {
799                                                 *row_reference = gtk_tree_row_reference_new (model, next);
800                                                 retval = TRUE;
801                                                 finished = TRUE;
802                                         }
803                                         g_object_unref (header);
804                                         header = NULL;
805                                 }
806                         } else {
807                                 /* If we ended up in the same message
808                                    then there is no valid next
809                                    message */
810                                 finished = TRUE;
811                         }
812                 } else {
813                         /* If there are no more messages and we don't
814                            want to start again in the first one then
815                            there is no valid next message */
816                         finished = TRUE;
817                 }
818         } while (!finished);
819
820         /* Free */
821         gtk_tree_path_free (path);
822         if (next)
823                 gtk_tree_path_free (next);
824
825         return retval;
826 }
827
828 /* TODO: This should be in _init(), with the parameters as properties. */
829 static void
830 modest_msg_view_window_construct (ModestMsgViewWindow *self, 
831                                   const gchar *modest_account_name,
832                                   const gchar *msg_uid)
833 {
834         GObject *obj = NULL;
835         ModestMsgViewWindowPrivate *priv = NULL;
836         ModestWindowPrivate *parent_priv = NULL;
837         ModestDimmingRulesGroup *menu_rules_group = NULL;
838         ModestDimmingRulesGroup *toolbar_rules_group = NULL;
839         ModestDimmingRulesGroup *clipboard_rules_group = NULL;
840
841         obj = G_OBJECT (self);
842         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(obj);
843         parent_priv = MODEST_WINDOW_GET_PRIVATE(obj);
844
845         priv->msg_uid = g_strdup (msg_uid);
846
847         /* Menubar */
848         parent_priv->menubar = modest_maemo_utils_get_manager_menubar_as_menu (parent_priv->ui_manager, "/MenuBar");
849         hildon_window_set_menu    (HILDON_WINDOW(obj), GTK_MENU(parent_priv->menubar));
850         gtk_widget_show (parent_priv->menubar);
851         parent_priv->ui_dimming_manager = modest_ui_dimming_manager_new();
852
853         menu_rules_group = modest_dimming_rules_group_new (MODEST_DIMMING_RULES_MENU, FALSE);
854         toolbar_rules_group = modest_dimming_rules_group_new (MODEST_DIMMING_RULES_TOOLBAR, TRUE);
855         clipboard_rules_group = modest_dimming_rules_group_new (MODEST_DIMMING_RULES_CLIPBOARD, FALSE);
856
857         /* Add common dimming rules */
858         modest_dimming_rules_group_add_rules (menu_rules_group, 
859                                               modest_msg_view_menu_dimming_entries,
860                                               G_N_ELEMENTS (modest_msg_view_menu_dimming_entries),
861                                               MODEST_WINDOW (self));
862         modest_dimming_rules_group_add_rules (toolbar_rules_group, 
863                                               modest_msg_view_toolbar_dimming_entries,
864                                               G_N_ELEMENTS (modest_msg_view_toolbar_dimming_entries),
865                                               MODEST_WINDOW (self));
866         modest_dimming_rules_group_add_rules (clipboard_rules_group, 
867                                               modest_msg_view_clipboard_dimming_entries,
868                                               G_N_ELEMENTS (modest_msg_view_clipboard_dimming_entries),
869                                               MODEST_WINDOW (self));
870
871         /* Insert dimming rules group for this window */
872         modest_ui_dimming_manager_insert_rules_group (parent_priv->ui_dimming_manager, menu_rules_group);
873         modest_ui_dimming_manager_insert_rules_group (parent_priv->ui_dimming_manager, toolbar_rules_group);
874         modest_ui_dimming_manager_insert_rules_group (parent_priv->ui_dimming_manager, clipboard_rules_group);
875         g_object_unref (menu_rules_group);
876         g_object_unref (toolbar_rules_group);
877         g_object_unref (clipboard_rules_group);
878
879         restore_settings (MODEST_MSG_VIEW_WINDOW(obj));
880         
881         /* g_signal_connect (G_OBJECT(obj), "delete-event", G_CALLBACK(on_delete_event), obj); */
882
883         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);
884         g_signal_connect (G_OBJECT(priv->msg_view), "activate_link",
885                           G_CALLBACK (modest_ui_actions_on_msg_link_clicked), obj);
886         g_signal_connect (G_OBJECT(priv->msg_view), "link_hover",
887                           G_CALLBACK (modest_ui_actions_on_msg_link_hover), obj);
888         g_signal_connect (G_OBJECT(priv->msg_view), "attachment_clicked",
889                           G_CALLBACK (modest_ui_actions_on_msg_attachment_clicked), obj);
890         g_signal_connect (G_OBJECT(priv->msg_view), "recpt_activated",
891                           G_CALLBACK (modest_ui_actions_on_msg_recpt_activated), obj);
892         g_signal_connect (G_OBJECT(priv->msg_view), "link_contextual",
893                           G_CALLBACK (modest_ui_actions_on_msg_link_contextual), obj);
894         g_signal_connect (G_OBJECT (priv->msg_view), "fetch_image",
895                           G_CALLBACK (on_fetch_image), obj);
896
897         g_signal_connect (G_OBJECT (obj), "key-release-event",
898                           G_CALLBACK (modest_msg_view_window_key_event),
899                           NULL);
900
901         g_signal_connect (G_OBJECT (obj), "key-press-event",
902                           G_CALLBACK (modest_msg_view_window_key_event),
903                           NULL);
904
905         g_signal_connect (G_OBJECT (obj), "window-state-event",
906                           G_CALLBACK (modest_msg_view_window_window_state_event),
907                           NULL);
908
909         g_signal_connect (G_OBJECT (obj), "move-focus",
910                           G_CALLBACK (on_move_focus), obj);
911
912         /* Mail Operation Queue */
913         priv->queue_change_handler = g_signal_connect (G_OBJECT (modest_runtime_get_mail_operation_queue ()),
914                                                        "queue-changed",
915                                                        G_CALLBACK (on_queue_changed),
916                                                        obj);
917
918         /* Account manager */
919         priv->account_removed_handler = g_signal_connect (G_OBJECT (modest_runtime_get_account_store ()),
920                                                           "account_removed",
921                                                           G_CALLBACK(on_account_removed),
922                                                           obj);
923
924         modest_window_set_active_account (MODEST_WINDOW(obj), modest_account_name);
925
926         g_signal_connect (G_OBJECT (priv->find_toolbar), "close", G_CALLBACK (modest_msg_view_window_find_toolbar_close), obj);
927         g_signal_connect (G_OBJECT (priv->find_toolbar), "search", G_CALLBACK (modest_msg_view_window_find_toolbar_search), obj);
928         priv->last_search = NULL;
929
930         /* Init the clipboard actions dim status */
931         modest_msg_view_grab_focus(MODEST_MSG_VIEW (priv->msg_view));
932
933         update_window_title (MODEST_MSG_VIEW_WINDOW (obj));
934
935
936 }
937
938 /* FIXME: parameter checks */
939 ModestWindow *
940 modest_msg_view_window_new_with_header_model (TnyMsg *msg, 
941                                               const gchar *modest_account_name,
942                                               const gchar *msg_uid,
943                                               GtkTreeModel *model, 
944                                               GtkTreeRowReference *row_reference)
945 {
946         ModestMsgViewWindow *window = NULL;
947         ModestMsgViewWindowPrivate *priv = NULL;
948         TnyFolder *header_folder = NULL;
949         ModestHeaderView *header_view = NULL;
950         ModestWindow *main_window = NULL;
951         ModestWindowMgr *mgr = NULL;
952
953         MODEST_DEBUG_BLOCK (
954                modest_tny_mime_part_to_string (TNY_MIME_PART (msg), 0);
955         );
956
957         mgr = modest_runtime_get_window_mgr ();
958         window = MODEST_MSG_VIEW_WINDOW (modest_window_mgr_get_msg_view_window (mgr));
959         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), NULL);
960
961         modest_msg_view_window_construct (window, modest_account_name, msg_uid);
962
963         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
964
965         /* Remember the message list's TreeModel so we can detect changes 
966          * and change the list selection when necessary: */
967
968         main_window = modest_window_mgr_get_main_window(mgr, FALSE); /* don't create */
969         if (main_window) {
970                 header_view = MODEST_HEADER_VIEW(modest_main_window_get_child_widget(
971                                                          MODEST_MAIN_WINDOW(main_window),
972                                                          MODEST_MAIN_WINDOW_WIDGET_TYPE_HEADER_VIEW));
973         }
974         
975         if (header_view != NULL){
976                 header_folder = modest_header_view_get_folder(header_view);
977                 /* This could happen if the header folder was
978                    unseleted before opening this msg window (for
979                    example if the user selects an account in the
980                    folder view of the main window */
981                 if (header_folder) {
982                         priv->is_outbox = (modest_tny_folder_guess_folder_type (header_folder) == TNY_FOLDER_TYPE_OUTBOX);
983                         priv->header_folder_id = tny_folder_get_id(header_folder);
984                         g_assert(priv->header_folder_id != NULL);
985                         g_object_unref(header_folder);
986                 }
987         }
988
989         /* Setup row references and connect signals */
990         priv->header_model = g_object_ref (model);
991
992         if (row_reference) {
993                 priv->row_reference = gtk_tree_row_reference_copy (row_reference);
994                 priv->next_row_reference = gtk_tree_row_reference_copy (row_reference);
995                 select_next_valid_row (model, &(priv->next_row_reference), TRUE, priv->is_outbox);
996         } else {
997                 priv->row_reference = NULL;
998                 priv->next_row_reference = NULL;
999         }
1000
1001         /* Connect signals */
1002         priv->row_changed_handler = 
1003                 g_signal_connect (GTK_TREE_MODEL(model), "row-changed",
1004                                   G_CALLBACK(modest_msg_view_window_on_row_changed),
1005                                   window);
1006         priv->row_deleted_handler = 
1007                 g_signal_connect (GTK_TREE_MODEL(model), "row-deleted",
1008                                   G_CALLBACK(modest_msg_view_window_on_row_deleted),
1009                                   window);
1010         priv->row_inserted_handler = 
1011                 g_signal_connect (GTK_TREE_MODEL(model), "row-inserted",
1012                                   G_CALLBACK(modest_msg_view_window_on_row_inserted),
1013                                   window);
1014         priv->rows_reordered_handler = 
1015                 g_signal_connect(GTK_TREE_MODEL(model), "rows-reordered",
1016                                  G_CALLBACK(modest_msg_view_window_on_row_reordered),
1017                                  window);
1018
1019         if (header_view != NULL){
1020                 modest_header_view_add_observer(header_view,
1021                                 MODEST_HEADER_VIEW_OBSERVER(window));
1022         }
1023
1024         tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
1025         update_window_title (MODEST_MSG_VIEW_WINDOW (window));
1026         gtk_widget_show_all (GTK_WIDGET (window));
1027         modest_msg_view_window_update_priority (window);
1028
1029         /* Check dimming rules */
1030         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (window));
1031         modest_ui_actions_check_menu_dimming_rules (MODEST_WINDOW (window));
1032         modest_window_check_dimming_rules_group (MODEST_WINDOW (window), MODEST_DIMMING_RULES_CLIPBOARD);
1033
1034         return MODEST_WINDOW(window);
1035 }
1036
1037 ModestWindow *
1038 modest_msg_view_window_new_for_search_result (TnyMsg *msg, 
1039                                               const gchar *modest_account_name,
1040                                               const gchar *msg_uid)
1041 {
1042         ModestMsgViewWindow *window = NULL;
1043         ModestMsgViewWindowPrivate *priv = NULL;
1044         ModestWindowMgr *mgr = NULL;
1045
1046         mgr = modest_runtime_get_window_mgr ();
1047         window = MODEST_MSG_VIEW_WINDOW (modest_window_mgr_get_msg_view_window (mgr));
1048         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), NULL);
1049         modest_msg_view_window_construct (window, modest_account_name, msg_uid);
1050
1051         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1052
1053         /* Remember that this is a search result, 
1054          * so we can disable some UI appropriately: */
1055         priv->is_search_result = TRUE;
1056
1057         tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
1058         
1059         update_window_title (window);
1060         gtk_widget_show_all (GTK_WIDGET (window));
1061         modest_msg_view_window_update_priority (window);
1062
1063         /* Check dimming rules */
1064         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (window));
1065         modest_ui_actions_check_menu_dimming_rules (MODEST_WINDOW (window));
1066         modest_window_check_dimming_rules_group (MODEST_WINDOW (window), MODEST_DIMMING_RULES_CLIPBOARD);
1067
1068         return MODEST_WINDOW(window);
1069 }
1070
1071 ModestWindow *
1072 modest_msg_view_window_new_for_attachment (TnyMsg *msg, 
1073                             const gchar *modest_account_name,
1074                             const gchar *msg_uid)
1075 {
1076         GObject *obj = NULL;
1077         ModestMsgViewWindowPrivate *priv;       
1078         ModestWindowMgr *mgr = NULL;
1079
1080         g_return_val_if_fail (msg, NULL);
1081         mgr = modest_runtime_get_window_mgr ();
1082         obj = G_OBJECT (modest_window_mgr_get_msg_view_window (mgr));
1083         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (obj);
1084         modest_msg_view_window_construct (MODEST_MSG_VIEW_WINDOW (obj), 
1085                 modest_account_name, msg_uid);
1086
1087         tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
1088         update_window_title (MODEST_MSG_VIEW_WINDOW (obj));
1089
1090         gtk_widget_show_all (GTK_WIDGET (obj));
1091
1092         /* Check dimming rules */
1093         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (obj));
1094         modest_ui_actions_check_menu_dimming_rules (MODEST_WINDOW (obj));
1095         modest_window_check_dimming_rules_group (MODEST_WINDOW (obj), MODEST_DIMMING_RULES_CLIPBOARD);
1096
1097         return MODEST_WINDOW(obj);
1098 }
1099
1100 static void
1101 modest_msg_view_window_on_row_changed (GtkTreeModel *header_model,
1102                                        GtkTreePath *arg1,
1103                                        GtkTreeIter *arg2,
1104                                        ModestMsgViewWindow *window)
1105 {
1106         check_dimming_rules_after_change (window);
1107 }
1108
1109 static void 
1110 modest_msg_view_window_on_row_deleted(GtkTreeModel *header_model,
1111                                       GtkTreePath *arg1,
1112                                       ModestMsgViewWindow *window)
1113 {
1114         check_dimming_rules_after_change (window);
1115 }
1116         /* The window could have dissapeared */
1117
1118 static void
1119 check_dimming_rules_after_change (ModestMsgViewWindow *window)
1120 {
1121         modest_ui_actions_check_menu_dimming_rules (MODEST_WINDOW (window));
1122         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (window));
1123 }
1124
1125
1126 /* On insertions we check if the folder still has the message we are
1127  * showing or do not. If do not, we do nothing. Which means we are still
1128  * not attached to any header folder and thus next/prev buttons are
1129  * still dimmed. Once the message that is shown by msg-view is found, the
1130  * new model of header-view will be attached and the references will be set.
1131  * On each further insertions dimming rules will be checked. However
1132  * this requires extra CPU time at least works.
1133  * (An message might be deleted from TnyFolder and thus will not be
1134  * inserted into the model again for example if it is removed by the
1135  * imap server and the header view is refreshed.)
1136  */
1137 static void 
1138 modest_msg_view_window_on_row_inserted (GtkTreeModel *model,
1139                                         GtkTreePath *tree_path,
1140                                         GtkTreeIter *tree_iter,
1141                                         ModestMsgViewWindow *window)
1142 {
1143         ModestMsgViewWindowPrivate *priv = NULL; 
1144         TnyHeader *header = NULL;
1145
1146         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
1147         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1148
1149         g_assert (model == priv->header_model);
1150         
1151         /* Check if the newly inserted message is the same we are actually
1152          * showing. IF not, we should remain detached from the header model
1153          * and thus prev and next toolbar buttons should remain dimmed. */
1154         gtk_tree_model_get (model, tree_iter, 
1155                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, 
1156                             &header, -1);
1157
1158         if (TNY_IS_HEADER (header)) {
1159                 gchar *uid = NULL;
1160
1161                 uid = modest_tny_folder_get_header_unique_id (header);
1162                 if (!g_str_equal(priv->msg_uid, uid)) {
1163                         check_dimming_rules_after_change (window);
1164                         g_free(uid);
1165                         g_object_unref (G_OBJECT(header));
1166                         return;
1167                 }
1168                 g_free(uid);
1169                 g_object_unref(G_OBJECT(header));
1170         }
1171
1172         if (priv->row_reference) {
1173                 gtk_tree_row_reference_free (priv->row_reference); 
1174         }
1175
1176         /* Setup row_reference for the actual msg. */
1177         priv->row_reference = gtk_tree_row_reference_new (priv->header_model, tree_path);
1178         if (priv->row_reference == NULL) {
1179                 g_warning("No reference for msg header item.");
1180                 return;
1181         }
1182
1183         /* Now set up next_row_reference. */
1184         if (priv->next_row_reference) {
1185                 gtk_tree_row_reference_free (priv->next_row_reference); 
1186         }
1187
1188         priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
1189         select_next_valid_row (priv->header_model,
1190                                &(priv->next_row_reference), FALSE, priv->is_outbox);
1191
1192         /* Connect the remaining callbacks to become able to detect
1193          * changes in header-view. */
1194         priv->row_changed_handler = 
1195                 g_signal_connect (priv->header_model, "row-changed",
1196                                   G_CALLBACK (modest_msg_view_window_on_row_changed),
1197                                   window);
1198         priv->row_deleted_handler = 
1199                 g_signal_connect (priv->header_model, "row-deleted",
1200                                   G_CALLBACK (modest_msg_view_window_on_row_deleted),
1201                                   window);
1202         priv->rows_reordered_handler = 
1203                 g_signal_connect (priv->header_model, "rows-reordered",
1204                                   G_CALLBACK (modest_msg_view_window_on_row_reordered),
1205                                   window);
1206
1207         check_dimming_rules_after_change (window);      
1208 }
1209
1210 static void 
1211 modest_msg_view_window_on_row_reordered (GtkTreeModel *header_model,
1212                                          GtkTreePath *arg1,
1213                                          GtkTreeIter *arg2,
1214                                          gpointer arg3,
1215                                          ModestMsgViewWindow *window)
1216 {
1217         ModestMsgViewWindowPrivate *priv = NULL; 
1218         gboolean already_changed = FALSE;
1219
1220         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(window);
1221
1222         /* If the current row was reordered select the proper next
1223            valid row. The same if the next row reference changes */
1224         if (priv->row_reference && 
1225             gtk_tree_row_reference_valid (priv->row_reference)) {
1226                 GtkTreePath *path;
1227                 path = gtk_tree_row_reference_get_path (priv->row_reference);
1228                 if (gtk_tree_path_compare (path, arg1) == 0) {
1229                         if (priv->next_row_reference) {
1230                                 gtk_tree_row_reference_free (priv->next_row_reference);
1231                         }
1232                         priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
1233                         select_next_valid_row (header_model, &(priv->next_row_reference), FALSE, priv->is_outbox);
1234                         already_changed = TRUE;
1235                 }
1236                 gtk_tree_path_free (path);
1237         }
1238         if (!already_changed &&
1239             priv->next_row_reference &&
1240             gtk_tree_row_reference_valid (priv->next_row_reference)) {
1241                 GtkTreePath *path;
1242                 path = gtk_tree_row_reference_get_path (priv->next_row_reference);
1243                 if (gtk_tree_path_compare (path, arg1) == 0) {
1244                         if (priv->next_row_reference) {
1245                                 gtk_tree_row_reference_free (priv->next_row_reference);
1246                         }
1247                         priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
1248                         select_next_valid_row (header_model, &(priv->next_row_reference), FALSE, priv->is_outbox);
1249                 }
1250                 gtk_tree_path_free (path);
1251         }
1252         check_dimming_rules_after_change (window);
1253 }
1254
1255 /* The modest_msg_view_window_update_model_replaced implements update
1256  * function for ModestHeaderViewObserver. Checks whether the TnyFolder
1257  * actually belongs to the header-view is the same as the TnyFolder of
1258  * the message of msg-view or not. If they are different, there is
1259  * nothing to do. If they are the same, then the model has replaced and
1260  * the reference in msg-view shall be replaced from the old model to
1261  * the new model. In this case the view will be detached from it's
1262  * header folder. From this point the next/prev buttons are dimmed.
1263  */
1264 static void 
1265 modest_msg_view_window_update_model_replaced (ModestHeaderViewObserver *observer,
1266                                               GtkTreeModel *model,
1267                                               const gchar *tny_folder_id)
1268 {
1269         ModestMsgViewWindowPrivate *priv = NULL; 
1270         ModestMsgViewWindow *window = NULL;
1271
1272         g_assert(MODEST_IS_HEADER_VIEW_OBSERVER(observer));
1273         g_assert(MODEST_IS_MSG_VIEW_WINDOW(observer));
1274
1275         window = MODEST_MSG_VIEW_WINDOW(observer);
1276         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(window);
1277
1278         /* If there is an other folder in the header-view then we do
1279          * not care about it's model (msg list). Else if the
1280          * header-view shows the folder the msg shown by us is in, we
1281          * shall replace our model reference and make some check. */
1282         if(model == NULL || tny_folder_id == NULL || 
1283            (priv->header_folder_id && !g_str_equal(tny_folder_id, priv->header_folder_id)))
1284                 return;
1285
1286         /* Model is changed(replaced), so we should forget the old
1287          * one. Because there might be other references and there
1288          * might be some change on the model even if we unreferenced
1289          * it, we need to disconnect our signals here. */
1290         if (priv->header_model) {
1291                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
1292                                                   priv->row_changed_handler))
1293                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
1294                                                     priv->row_changed_handler);
1295                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
1296                                                   priv->row_deleted_handler))
1297                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
1298                                                     priv->row_deleted_handler);
1299                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
1300                                                   priv->row_inserted_handler))
1301                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
1302                                                     priv->row_inserted_handler);
1303                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
1304                                                   priv->rows_reordered_handler))
1305                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
1306                                                     priv->rows_reordered_handler);
1307
1308                 /* Frees */
1309                 if (priv->row_reference)
1310                         gtk_tree_row_reference_free (priv->row_reference);
1311                 if (priv->next_row_reference)
1312                         gtk_tree_row_reference_free (priv->next_row_reference);
1313                 g_object_unref(priv->header_model);
1314
1315                 /* Initialize */
1316                 priv->row_changed_handler = 0;
1317                 priv->row_deleted_handler = 0;
1318                 priv->row_inserted_handler = 0;
1319                 priv->rows_reordered_handler = 0;
1320                 priv->next_row_reference = NULL;
1321                 priv->row_reference = NULL;
1322                 priv->header_model = NULL;
1323         }
1324
1325         priv->header_model = g_object_ref (model);
1326
1327         /* Also we must connect to the new model for row insertions.
1328          * Only for insertions now. We will need other ones only after
1329          * the msg is show by msg-view is added to the new model. */
1330         priv->row_inserted_handler =
1331                 g_signal_connect (priv->header_model, "row-inserted",
1332                                   G_CALLBACK(modest_msg_view_window_on_row_inserted),
1333                                   window);
1334
1335         modest_ui_actions_check_menu_dimming_rules(MODEST_WINDOW(window));
1336         modest_ui_actions_check_toolbar_dimming_rules(MODEST_WINDOW(window));
1337 }
1338
1339 gboolean 
1340 modest_msg_view_window_toolbar_on_transfer_mode     (ModestMsgViewWindow *self)
1341 {
1342         ModestMsgViewWindowPrivate *priv= NULL; 
1343
1344         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), FALSE);
1345         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
1346
1347         return priv->current_toolbar_mode == TOOLBAR_MODE_TRANSFER;
1348 }
1349
1350 TnyHeader*
1351 modest_msg_view_window_get_header (ModestMsgViewWindow *self)
1352 {
1353         ModestMsgViewWindowPrivate *priv= NULL; 
1354         TnyMsg *msg = NULL;
1355         TnyHeader *header = NULL;
1356         GtkTreePath *path = NULL;
1357         GtkTreeIter iter;
1358
1359         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), NULL);
1360         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
1361
1362         /* If the message was not obtained from a treemodel,
1363          * for instance if it was opened directly by the search UI:
1364          */
1365         if (priv->header_model == NULL || 
1366             priv->row_reference == NULL ||
1367             !gtk_tree_row_reference_valid (priv->row_reference)) {
1368                 msg = modest_msg_view_window_get_message (self);
1369                 if (msg) {
1370                         header = tny_msg_get_header (msg);
1371                         g_object_unref (msg);
1372                 }
1373                 return header;
1374         }
1375
1376         /* Get iter of the currently selected message in the header view: */
1377         path = gtk_tree_row_reference_get_path (priv->row_reference);
1378         g_return_val_if_fail (path != NULL, NULL);
1379         gtk_tree_model_get_iter (priv->header_model, 
1380                                  &iter, 
1381                                  path);
1382
1383         /* Get current message header */
1384         gtk_tree_model_get (priv->header_model, &iter, 
1385                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, 
1386                             &header, -1);
1387
1388         gtk_tree_path_free (path);
1389         return header;
1390 }
1391
1392 TnyMsg*
1393 modest_msg_view_window_get_message (ModestMsgViewWindow *self)
1394 {
1395         ModestMsgViewWindowPrivate *priv;
1396         
1397         g_return_val_if_fail (self, NULL);
1398         
1399         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
1400         
1401         return tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
1402 }
1403
1404 const gchar*
1405 modest_msg_view_window_get_message_uid (ModestMsgViewWindow *self)
1406 {
1407         ModestMsgViewWindowPrivate *priv;
1408
1409         g_return_val_if_fail (self, NULL);
1410         
1411         priv  = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
1412
1413         return (const gchar*) priv->msg_uid;
1414 }
1415
1416 static void 
1417 modest_msg_view_window_toggle_find_toolbar (GtkToggleAction *toggle,
1418                                             gpointer data)
1419 {
1420         ModestMsgViewWindow *window = MODEST_MSG_VIEW_WINDOW (data);
1421         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1422         ModestWindowPrivate *parent_priv = MODEST_WINDOW_GET_PRIVATE (window);
1423         gboolean is_active;
1424         GtkAction *action;
1425
1426         is_active = gtk_toggle_action_get_active (toggle);
1427
1428         if (is_active) {
1429                 gtk_widget_show (priv->find_toolbar);
1430                 hildon_find_toolbar_highlight_entry (HILDON_FIND_TOOLBAR (priv->find_toolbar), TRUE);
1431         } else {
1432                 gtk_widget_hide (priv->find_toolbar);
1433         }
1434
1435         /* update the toggle buttons status */
1436         action = gtk_ui_manager_get_action (parent_priv->ui_manager, "/ToolBar/FindInMessage");
1437         modest_utils_toggle_action_set_active_block_notify (GTK_TOGGLE_ACTION (action), is_active);
1438         action = gtk_ui_manager_get_action (parent_priv->ui_manager, "/MenuBar/ToolsMenu/ToolsFindInMessageMenu");
1439         modest_utils_toggle_action_set_active_block_notify (GTK_TOGGLE_ACTION (action), is_active);
1440         
1441 }
1442
1443 static void
1444 modest_msg_view_window_find_toolbar_close (GtkWidget *widget,
1445                                            ModestMsgViewWindow *obj)
1446 {
1447         GtkToggleAction *toggle;
1448         ModestWindowPrivate *parent_priv;
1449         ModestMsgViewWindowPrivate *priv;
1450
1451         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (obj);
1452         parent_priv = MODEST_WINDOW_GET_PRIVATE (obj);
1453         
1454         toggle = GTK_TOGGLE_ACTION (gtk_ui_manager_get_action (parent_priv->ui_manager, "/ToolBar/FindInMessage"));
1455         gtk_toggle_action_set_active (toggle, FALSE);
1456         modest_msg_view_grab_focus (MODEST_MSG_VIEW (priv->msg_view));
1457 }
1458
1459 static void
1460 modest_msg_view_window_find_toolbar_search (GtkWidget *widget,
1461                                            ModestMsgViewWindow *obj)
1462 {
1463         gchar *current_search;
1464         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (obj);
1465
1466         if (modest_mime_part_view_is_empty (MODEST_MIME_PART_VIEW (priv->msg_view))) {
1467                 hildon_banner_show_information (NULL, NULL, _("mail_ib_nothing_to_find"));
1468                 return;
1469         }
1470
1471         g_object_get (G_OBJECT (widget), "prefix", &current_search, NULL);
1472
1473         if ((current_search == NULL) || (strcmp (current_search, "") == 0)) {
1474                 g_free (current_search);
1475                 hildon_banner_show_information (NULL, NULL, dgettext("hildon-common-strings", "ecdg_ib_find_rep_enter_text"));
1476                 return;
1477         }
1478
1479         if ((priv->last_search == NULL) || (strcmp (priv->last_search, current_search) != 0)) {
1480                 gboolean result;
1481                 g_free (priv->last_search);
1482                 priv->last_search = g_strdup (current_search);
1483                 result = modest_isearch_view_search (MODEST_ISEARCH_VIEW (priv->msg_view),
1484                                                      priv->last_search);
1485                 if (!result) {
1486                         hildon_banner_show_information (NULL, NULL, dgettext("hildon-libs", "ckct_ib_find_no_matches"));
1487                         g_free (priv->last_search);
1488                         priv->last_search = NULL;
1489                 } else {
1490                         modest_msg_view_grab_focus (MODEST_MSG_VIEW (priv->msg_view));
1491                         hildon_find_toolbar_highlight_entry (HILDON_FIND_TOOLBAR (priv->find_toolbar), TRUE);
1492                 }
1493         } else {
1494                 if (!modest_isearch_view_search_next (MODEST_ISEARCH_VIEW (priv->msg_view))) {
1495                         hildon_banner_show_information (NULL, NULL, dgettext("hildon-libs", "ckct_ib_find_search_complete"));
1496                         g_free (priv->last_search);
1497                         priv->last_search = NULL;
1498                 } else {
1499                         modest_msg_view_grab_focus (MODEST_MSG_VIEW (priv->msg_view));
1500                         hildon_find_toolbar_highlight_entry (HILDON_FIND_TOOLBAR (priv->find_toolbar), TRUE);
1501                 }
1502         }
1503         
1504         g_free (current_search);
1505                 
1506 }
1507
1508 static void
1509 modest_msg_view_window_set_zoom (ModestWindow *window,
1510                                  gdouble zoom)
1511 {
1512         ModestMsgViewWindowPrivate *priv;
1513         ModestWindowPrivate *parent_priv;
1514         GtkAction *action = NULL;
1515         gint int_zoom = (gint) rint (zoom*100.0+0.1);
1516      
1517         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
1518
1519         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1520         parent_priv = MODEST_WINDOW_GET_PRIVATE (window);
1521         modest_zoomable_set_zoom (MODEST_ZOOMABLE (priv->msg_view), zoom);
1522
1523         action = gtk_ui_manager_get_action (parent_priv->ui_manager, 
1524                                             "/MenuBar/ViewMenu/ZoomMenu/Zoom50Menu");
1525
1526         gtk_radio_action_set_current_value (GTK_RADIO_ACTION (action), int_zoom);
1527 }
1528
1529 static gdouble
1530 modest_msg_view_window_get_zoom (ModestWindow *window)
1531 {
1532         ModestMsgViewWindowPrivate *priv;
1533      
1534         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), 1.0);
1535
1536         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1537         return modest_zoomable_get_zoom (MODEST_ZOOMABLE (priv->msg_view));
1538 }
1539
1540 static gboolean
1541 modest_msg_view_window_zoom_plus (ModestWindow *window)
1542 {
1543         ModestWindowPrivate *parent_priv;
1544         GtkRadioAction *zoom_radio_action;
1545         GSList *group, *node;
1546
1547         parent_priv = MODEST_WINDOW_GET_PRIVATE (window);
1548         zoom_radio_action = GTK_RADIO_ACTION (gtk_ui_manager_get_action (parent_priv->ui_manager, 
1549                                                                          "/MenuBar/ViewMenu/ZoomMenu/Zoom50Menu"));
1550
1551         group = gtk_radio_action_get_group (zoom_radio_action);
1552
1553         if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (group->data))) {
1554                 hildon_banner_show_information (NULL, NULL, dgettext("hildon-common-strings", "ckct_ib_max_zoom_level_reached"));
1555                 return FALSE;
1556         }
1557
1558         for (node = group; node != NULL; node = g_slist_next (node)) {
1559                 if ((node->next != NULL) && gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (node->next->data))) {
1560                         gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (node->data), TRUE);
1561                         return TRUE;
1562                 }
1563         }
1564         return FALSE;
1565 }
1566
1567 static gboolean
1568 modest_msg_view_window_zoom_minus (ModestWindow *window)
1569 {
1570         ModestWindowPrivate *parent_priv;
1571         GtkRadioAction *zoom_radio_action;
1572         GSList *group, *node;
1573
1574         parent_priv = MODEST_WINDOW_GET_PRIVATE (window);
1575         zoom_radio_action = GTK_RADIO_ACTION (gtk_ui_manager_get_action (parent_priv->ui_manager, 
1576                                                                          "/MenuBar/ViewMenu/ZoomMenu/Zoom50Menu"));
1577
1578         group = gtk_radio_action_get_group (zoom_radio_action);
1579
1580         for (node = group; node != NULL; node = g_slist_next (node)) {
1581                 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (node->data))) {
1582                         if (node->next != NULL) {
1583                                 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (node->next->data), TRUE);
1584                                 return TRUE;
1585                         } else {
1586                           hildon_banner_show_information (NULL, NULL, dgettext("hildon-common-strings", "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, 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
2558         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
2559         g_return_if_fail (TNY_IS_MIME_PART (mime_part) || (mime_part == NULL));
2560         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2561
2562         msg_uid = modest_msg_view_window_get_message_uid (MODEST_MSG_VIEW_WINDOW (window));
2563         attachments = modest_msg_view_get_attachments (MODEST_MSG_VIEW (priv->msg_view));
2564         attachment_index = modest_list_index (attachments, (GObject *) mime_part);
2565         g_object_unref (attachments);
2566         
2567         if (msg_uid && attachment_index >= 0) {
2568                 attachment_uid = g_strdup_printf ("%s/%d", msg_uid, attachment_index);
2569         }
2570
2571         if (mime_part == NULL) {
2572                 gboolean error = FALSE;
2573                 TnyList *selected_attachments = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2574                 if (selected_attachments == NULL || tny_list_get_length (selected_attachments) == 0) {
2575                         error = TRUE;
2576                 } else if (tny_list_get_length (selected_attachments) > 1) {
2577                         hildon_banner_show_information (NULL, NULL, _("mcen_ib_unable_to_display_more"));
2578                         error = TRUE;
2579                 } else {
2580                         TnyIterator *iter;
2581                         iter = tny_list_create_iterator (selected_attachments);
2582                         mime_part = (TnyMimePart *) tny_iterator_get_current (iter);
2583                         g_object_unref (iter);
2584                 }
2585                 g_object_unref (selected_attachments);
2586
2587                 if (error)
2588                         return;
2589         } else {
2590                 g_object_ref (mime_part);
2591         }
2592
2593         if (tny_mime_part_is_purged (mime_part)) {
2594                 g_object_unref (mime_part);
2595                 return;
2596         }
2597
2598         if (!modest_tny_mime_part_is_msg (mime_part)) {
2599                 gchar *filepath = NULL;
2600                 const gchar *att_filename = tny_mime_part_get_filename (mime_part);
2601                 gboolean show_error_banner = FALSE;
2602                 TnyFsStream *temp_stream = NULL;
2603                 temp_stream = modest_utils_create_temp_stream (att_filename, attachment_uid,
2604                                                                &filepath);
2605                 
2606                 if (temp_stream != NULL) {
2607                         DecodeAsyncHelper *helper = g_slice_new (DecodeAsyncHelper);
2608                         helper->filepath = g_strdup (filepath);
2609                         helper->banner = NULL;
2610                         helper->banner_idle_id = g_timeout_add (1000, decode_async_banner_idle, helper);
2611                         tny_mime_part_decode_to_stream_async (mime_part, TNY_STREAM (temp_stream), 
2612                                                               on_decode_to_stream_async_handler, 
2613                                                               NULL, 
2614                                                               helper);
2615                         g_object_unref (temp_stream);
2616                         /* NOTE: files in the temporary area will be automatically
2617                          * cleaned after some time if they are no longer in use */
2618                 } else {
2619                         if (filepath) {
2620                                 const gchar *content_type;
2621                                 /* the file may already exist but it isn't writable,
2622                                  * let's try to open it anyway */
2623                                 content_type = tny_mime_part_get_content_type (mime_part);
2624                                 modest_platform_activate_file (filepath, content_type);
2625                         } else {
2626                                 g_warning ("%s: modest_utils_create_temp_stream failed", __FUNCTION__);
2627                                 show_error_banner = TRUE;
2628                         }
2629                 }
2630                 if (filepath)
2631                         g_free (filepath);
2632                 if (show_error_banner)
2633                         modest_platform_information_banner (NULL, NULL, _("mail_ib_file_operation_failed"));
2634         } else {
2635                 /* message attachment */
2636                 TnyHeader *header = NULL;
2637                 ModestWindowMgr *mgr;
2638                 ModestWindow *msg_win = NULL;
2639                 gboolean found;
2640                 
2641                 header = tny_msg_get_header (TNY_MSG (mime_part));
2642                 mgr = modest_runtime_get_window_mgr ();         
2643                 found = modest_window_mgr_find_registered_header (mgr, header, &msg_win);
2644
2645                 if (found) {
2646                         if (msg_win)                            /* there is already a window for this uid; top it */
2647                                 gtk_window_present (GTK_WINDOW(msg_win));
2648                         else 
2649                                 /* if it's found, but there is no msg_win, it's probably in the process of being created;
2650                                  * thus, we don't do anything */
2651                                 g_warning ("window for is already being created");
2652                 } else { 
2653                         /* it's not found, so create a new window for it */
2654                         modest_window_mgr_register_header (mgr, header, attachment_uid); /* register the uid before building the window */
2655                         gchar *account = g_strdup (modest_window_get_active_account (MODEST_WINDOW (window)));
2656                         if (!account)
2657                                 account = modest_account_mgr_get_default_account (modest_runtime_get_account_mgr ());
2658                         msg_win = modest_msg_view_window_new_for_attachment (TNY_MSG (mime_part), account, attachment_uid);
2659                         modest_window_set_zoom (MODEST_WINDOW (msg_win), 
2660                                                 modest_window_get_zoom (MODEST_WINDOW (window)));
2661                         modest_window_mgr_register_window (mgr, msg_win);
2662                         gtk_widget_show_all (GTK_WIDGET (msg_win));
2663                 }
2664         }
2665         g_object_unref (mime_part);
2666 }
2667
2668 typedef struct
2669 {
2670         gchar *filename;
2671         TnyMimePart *part;
2672 } SaveMimePartPair;
2673
2674 typedef struct
2675 {
2676         GList *pairs;
2677         GtkWidget *banner;
2678         GnomeVFSResult result;
2679 } SaveMimePartInfo;
2680
2681 static void save_mime_part_info_free (SaveMimePartInfo *info, gboolean with_struct);
2682 static gboolean idle_save_mime_part_show_result (SaveMimePartInfo *info);
2683 static gpointer save_mime_part_to_file (SaveMimePartInfo *info);
2684 static void save_mime_parts_to_file_with_checks (SaveMimePartInfo *info);
2685
2686 static void 
2687 save_mime_part_info_free (SaveMimePartInfo *info, gboolean with_struct)
2688 {
2689         
2690         GList *node;
2691         for (node = info->pairs; node != NULL; node = g_list_next (node)) {
2692                 SaveMimePartPair *pair = (SaveMimePartPair *) node->data;
2693                 g_free (pair->filename);
2694                 g_object_unref (pair->part);
2695                 g_slice_free (SaveMimePartPair, pair);
2696         }
2697         g_list_free (info->pairs);
2698         info->pairs = NULL;
2699         if (with_struct) {
2700                 gtk_widget_destroy (info->banner);
2701                 g_slice_free (SaveMimePartInfo, info);
2702         }
2703 }
2704
2705 static gboolean
2706 idle_save_mime_part_show_result (SaveMimePartInfo *info)
2707 {
2708         if (info->pairs != NULL) {
2709                 save_mime_part_to_file (info);
2710         } else {
2711                 /* This is a GDK lock because we are an idle callback and
2712                  * hildon_banner_show_information is or does Gtk+ code */
2713
2714                 gdk_threads_enter (); /* CHECKED */
2715                 save_mime_part_info_free (info, TRUE);
2716                 if (info->result == GNOME_VFS_OK) {
2717                         hildon_banner_show_information (NULL, NULL, _CS("sfil_ib_saved"));
2718                 } else if (info->result == GNOME_VFS_ERROR_NO_SPACE) {
2719                         hildon_banner_show_information (NULL, NULL, dgettext("ke-recv", 
2720                                                                              "cerm_device_memory_full"));
2721                 } else {
2722                         hildon_banner_show_information (NULL, NULL, _("mail_ib_file_operation_failed"));
2723                 }
2724                 gdk_threads_leave (); /* CHECKED */
2725         }
2726
2727         return FALSE;
2728 }
2729
2730 static gpointer
2731 save_mime_part_to_file (SaveMimePartInfo *info)
2732 {
2733         GnomeVFSHandle *handle;
2734         TnyStream *stream;
2735         SaveMimePartPair *pair = (SaveMimePartPair *) info->pairs->data;
2736
2737         info->result = gnome_vfs_create (&handle, pair->filename, GNOME_VFS_OPEN_WRITE, FALSE, 0644);
2738         if (info->result == GNOME_VFS_OK) {
2739                 GError *error = NULL;
2740                 stream = tny_vfs_stream_new (handle);
2741                 if (tny_mime_part_decode_to_stream (pair->part, stream, &error) < 0) {
2742                         g_warning ("modest: could not save attachment %s: %d (%s)\n", pair->filename, error?error->code:-1, error?error->message:"Unknown error");
2743                         
2744                         info->result = GNOME_VFS_ERROR_IO;
2745                 }
2746                 g_object_unref (G_OBJECT (stream));
2747                 g_object_unref (pair->part);
2748                 g_slice_free (SaveMimePartPair, pair);
2749                 info->pairs = g_list_delete_link (info->pairs, info->pairs);
2750         } else {
2751                 g_warning ("modest: could not create save attachment %s: %s\n", pair->filename, gnome_vfs_result_to_string (info->result));
2752                 save_mime_part_info_free (info, FALSE);
2753         }
2754
2755         g_idle_add ((GSourceFunc) idle_save_mime_part_show_result, info);
2756         return NULL;
2757 }
2758
2759 static void
2760 save_mime_parts_to_file_with_checks (SaveMimePartInfo *info)
2761 {
2762         gboolean is_ok = TRUE;
2763         gint replaced_files = 0;
2764         const GList *files = info->pairs;
2765         const GList *iter;
2766
2767         for (iter = files; (iter != NULL) && (replaced_files < 2); iter = g_list_next(iter)) {
2768                 SaveMimePartPair *pair = iter->data;
2769                 if (modest_utils_file_exists (pair->filename)) {
2770                         replaced_files++;
2771                 }
2772         }
2773         if (replaced_files) {
2774                 GtkWidget *confirm_overwrite_dialog;
2775                 const gchar *message = (replaced_files == 1) ?
2776                         _FM("docm_nc_replace_file") : _FM("docm_nc_replace_multiple");
2777                 confirm_overwrite_dialog = hildon_note_new_confirmation (NULL, message);
2778                 if (gtk_dialog_run (GTK_DIALOG (confirm_overwrite_dialog)) != GTK_RESPONSE_OK) {
2779                         is_ok = FALSE;
2780                 }
2781                 gtk_widget_destroy (confirm_overwrite_dialog);
2782         }
2783
2784         if (!is_ok) {
2785                 save_mime_part_info_free (info, TRUE);
2786         } else {
2787                 GtkWidget *banner = hildon_banner_show_animation (NULL, NULL, 
2788                                                                   _CS("sfil_ib_saving"));
2789                 info->banner = banner;
2790                 g_thread_create ((GThreadFunc)save_mime_part_to_file, info, FALSE, NULL);
2791         }
2792
2793 }
2794
2795 static void
2796 save_attachments_response (GtkDialog *dialog,
2797                            gint       arg1,
2798                            gpointer   user_data)  
2799 {
2800         TnyList *mime_parts;
2801         gchar *chooser_uri;
2802         GList *files_to_save = NULL;
2803
2804         mime_parts = TNY_LIST (user_data);
2805
2806         if (arg1 != GTK_RESPONSE_OK)
2807                 goto end;
2808
2809         chooser_uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dialog));
2810
2811         if (!modest_utils_folder_writable (chooser_uri)) {
2812                 hildon_banner_show_information 
2813                         (NULL, NULL, dgettext("hildon-fm", "sfil_ib_readonly_location"));
2814         } else {
2815                 TnyIterator *iter;
2816
2817                 iter = tny_list_create_iterator (mime_parts);
2818                 while (!tny_iterator_is_done (iter)) {
2819                         TnyMimePart *mime_part = (TnyMimePart *) tny_iterator_get_current (iter);
2820
2821                         if ((modest_tny_mime_part_is_attachment_for_modest (mime_part)) &&
2822                             !tny_mime_part_is_purged (mime_part) &&
2823                             (tny_mime_part_get_filename (mime_part) != NULL)) {
2824                                 SaveMimePartPair *pair;
2825                                         
2826                                 pair = g_slice_new0 (SaveMimePartPair);
2827
2828                                 if (tny_list_get_length (mime_parts) > 1) {
2829                                         gchar *escaped = 
2830                                                 gnome_vfs_escape_slashes (tny_mime_part_get_filename (mime_part));
2831                                         pair->filename = g_build_filename (chooser_uri, escaped, NULL);
2832                                         g_free (escaped);
2833                                 } else {
2834                                         pair->filename = g_strdup (chooser_uri);
2835                                 }
2836                                 pair->part = mime_part;
2837                                 files_to_save = g_list_prepend (files_to_save, pair);
2838                         }
2839                         tny_iterator_next (iter);
2840                 }
2841                 g_object_unref (iter);
2842         }
2843         g_free (chooser_uri);
2844
2845         if (files_to_save != NULL) {
2846                 SaveMimePartInfo *info = g_slice_new0 (SaveMimePartInfo);
2847                 info->pairs = files_to_save;
2848                 info->result = TRUE;
2849                 save_mime_parts_to_file_with_checks (info);
2850         }
2851
2852  end:
2853         /* Free and close the dialog */
2854         g_object_unref (mime_parts);
2855         gtk_widget_destroy (GTK_WIDGET (dialog));
2856 }
2857
2858 void
2859 modest_msg_view_window_save_attachments (ModestMsgViewWindow *window, TnyList *mime_parts)
2860 {
2861         ModestMsgViewWindowPrivate *priv;
2862         GtkWidget *save_dialog = NULL;
2863         gchar *folder = NULL;
2864         gchar *filename = NULL;
2865         gchar *save_multiple_str = NULL;
2866
2867         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
2868         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2869
2870         if (mime_parts == NULL) {
2871                 mime_parts = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2872                 if (mime_parts == NULL || tny_list_get_length (mime_parts) == 0)
2873                         return;
2874         } else {
2875                 g_object_ref (mime_parts);
2876         }
2877
2878         /* prepare dialog */
2879         if (tny_list_get_length (mime_parts) == 1) {
2880                 TnyIterator *iter;
2881                 /* only one attachment selected */
2882                 iter = tny_list_create_iterator (mime_parts);
2883                 TnyMimePart *mime_part = (TnyMimePart *) tny_iterator_get_current (iter);
2884                 g_object_unref (iter);
2885                 if (!modest_tny_mime_part_is_msg (mime_part) && 
2886                     modest_tny_mime_part_is_attachment_for_modest (mime_part) &&
2887                     !tny_mime_part_is_purged (mime_part)) {
2888                         filename = g_strdup (tny_mime_part_get_filename (mime_part));
2889                 } else {
2890                         /* TODO: show any error? */
2891                         g_warning ("Tried to save a non-file attachment");
2892                         g_object_unref (mime_parts);
2893                         return;
2894                 }
2895                 g_object_unref (mime_part);
2896         } else {
2897                 save_multiple_str = g_strdup_printf (_FM("sfil_va_number_of_objects_attachments"), 
2898                                                      tny_list_get_length (mime_parts));
2899         }
2900         
2901         save_dialog = hildon_file_chooser_dialog_new (GTK_WINDOW (window), 
2902                                                       GTK_FILE_CHOOSER_ACTION_SAVE);
2903
2904         /* set folder */
2905         folder = g_build_filename (g_get_home_dir (), DEFAULT_FOLDER, NULL);
2906         gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (save_dialog), folder);
2907         g_free (folder);
2908
2909         /* set filename */
2910         if (filename) {
2911                 gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (save_dialog), 
2912                                                    filename);
2913                 g_free (filename);
2914         }
2915
2916         /* if multiple, set multiple string */
2917         if (save_multiple_str) {
2918                 g_object_set (G_OBJECT (save_dialog), "save-multiple", save_multiple_str, NULL);
2919                 gtk_window_set_title (GTK_WINDOW (save_dialog), _FM("sfil_ti_save_objects_files"));
2920         }
2921
2922         /* We must run this asynchronously, because the hildon dialog
2923            performs a gtk_dialog_run by itself which leads to gdk
2924            deadlocks */
2925         g_signal_connect (save_dialog, "response", 
2926                           G_CALLBACK (save_attachments_response), mime_parts);
2927
2928         gtk_widget_show_all (save_dialog);
2929 }
2930
2931 static gboolean
2932 show_remove_attachment_information (gpointer userdata)
2933 {
2934         ModestMsgViewWindow *window = (ModestMsgViewWindow *) userdata;
2935         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2936
2937         /* We're outside the main lock */
2938         gdk_threads_enter ();
2939
2940         if (priv->remove_attachment_banner != NULL) {
2941                 gtk_widget_destroy (priv->remove_attachment_banner);
2942                 g_object_unref (priv->remove_attachment_banner);
2943         }
2944
2945         priv->remove_attachment_banner = g_object_ref (
2946                 hildon_banner_show_animation (NULL, NULL, _("mcen_ib_removing_attachment")));
2947
2948         gdk_threads_leave ();
2949
2950         return FALSE;
2951 }
2952
2953 void
2954 modest_msg_view_window_remove_attachments (ModestMsgViewWindow *window, gboolean get_all)
2955 {
2956         ModestMsgViewWindowPrivate *priv;
2957         TnyList *mime_parts = NULL;
2958         gchar *confirmation_message;
2959         gint response;
2960         gint n_attachments;
2961         TnyMsg *msg;
2962         TnyIterator *iter;
2963
2964         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
2965         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2966
2967         if (get_all)
2968                 mime_parts = modest_msg_view_get_attachments (MODEST_MSG_VIEW (priv->msg_view));
2969         else
2970                 mime_parts = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2971                 
2972         /* Remove already purged messages from mime parts list */
2973         iter = tny_list_create_iterator (mime_parts);
2974         while (!tny_iterator_is_done (iter)) {
2975                 TnyMimePart *part = TNY_MIME_PART (tny_iterator_get_current (iter));
2976                 tny_iterator_next (iter);
2977                 if (tny_mime_part_is_purged (part)) {
2978                         tny_list_remove (mime_parts, (GObject *) part);
2979                 }
2980                 g_object_unref (part);
2981         }
2982         g_object_unref (iter);
2983
2984         if (tny_list_get_length (mime_parts) == 0) {
2985                 g_object_unref (mime_parts);
2986                 return;
2987         }
2988
2989         n_attachments = tny_list_get_length (mime_parts);
2990         if (n_attachments == 1) {
2991                 gchar *filename;
2992                 TnyMimePart *part;
2993
2994                 iter = tny_list_create_iterator (mime_parts);
2995                 part = (TnyMimePart *) tny_iterator_get_current (iter);
2996                 g_object_unref (iter);
2997                 if (modest_tny_mime_part_is_msg (part)) {
2998                         TnyHeader *header;
2999                         header = tny_msg_get_header (TNY_MSG (part));
3000                         filename = tny_header_dup_subject (header);
3001                         g_object_unref (header);
3002                         if (filename == NULL)
3003                                 filename = g_strdup (_("mail_va_no_subject"));
3004                 } else {
3005                         filename = g_strdup (tny_mime_part_get_filename (TNY_MIME_PART (part)));
3006                 }
3007                 confirmation_message = g_strdup_printf (_("mcen_nc_purge_file_text"), filename);
3008                 g_free (filename);
3009                 g_object_unref (part);
3010         } else {
3011                 confirmation_message = g_strdup_printf (ngettext("mcen_nc_purge_file_text", 
3012                                                                  "mcen_nc_purge_files_text", 
3013                                                                  n_attachments), n_attachments);
3014         }
3015         response = modest_platform_run_confirmation_dialog (GTK_WINDOW (window),
3016                                                             confirmation_message);
3017         g_free (confirmation_message);
3018
3019         if (response != GTK_RESPONSE_OK) {
3020                 g_object_unref (mime_parts);
3021                 return;
3022         }
3023
3024         priv->purge_timeout = g_timeout_add (2000, show_remove_attachment_information, window);
3025         
3026         iter = tny_list_create_iterator (mime_parts);
3027         while (!tny_iterator_is_done (iter)) {
3028                 TnyMimePart *part;
3029
3030                 part = (TnyMimePart *) tny_iterator_get_current (iter);
3031                 tny_mime_part_set_purged (TNY_MIME_PART (part));
3032                 g_object_unref (part);
3033                 tny_iterator_next (iter);
3034         }
3035         g_object_unref (iter);
3036
3037         msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
3038         tny_msg_view_clear (TNY_MSG_VIEW (priv->msg_view));
3039         tny_msg_rewrite_cache (msg);
3040         tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
3041         g_object_unref (msg);
3042
3043         g_object_unref (mime_parts);
3044
3045         if (priv->purge_timeout > 0) {
3046                 g_source_remove (priv->purge_timeout);
3047                 priv->purge_timeout = 0;
3048         }
3049
3050         if (priv->remove_attachment_banner) {
3051                 gtk_widget_destroy (priv->remove_attachment_banner);
3052                 g_object_unref (priv->remove_attachment_banner);
3053                 priv->remove_attachment_banner = NULL;
3054         }
3055
3056
3057 }
3058
3059
3060 static void
3061 update_window_title (ModestMsgViewWindow *window)
3062 {
3063         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
3064         TnyMsg *msg = NULL;
3065         TnyHeader *header = NULL;
3066         gchar *subject = NULL;
3067         
3068         msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
3069
3070         if (msg != NULL) {
3071                 header = tny_msg_get_header (msg);
3072                 subject = tny_header_dup_subject (header);
3073                 g_object_unref (header);
3074                 g_object_unref (msg);
3075         }
3076
3077         if ((subject == NULL)||(subject[0] == '\0')) {
3078                 g_free (subject);
3079                 subject = g_strdup (_("mail_va_no_subject"));
3080         }
3081
3082         gtk_window_set_title (GTK_WINDOW (window), subject);
3083 }
3084
3085
3086 static void on_move_focus (GtkWidget *widget,
3087                            GtkDirectionType direction,
3088                            gpointer userdata)
3089 {
3090         g_signal_stop_emission_by_name (G_OBJECT (widget), "move-focus");
3091 }
3092
3093 static TnyStream *
3094 fetch_image_open_stream (TnyStreamCache *self, gint64 *expected_size, gchar *uri)
3095 {
3096         GnomeVFSResult result;
3097         GnomeVFSHandle *handle = NULL;
3098         GnomeVFSFileInfo *info = NULL;
3099         TnyStream *stream;
3100
3101         result = gnome_vfs_open (&handle, uri, GNOME_VFS_OPEN_READ);
3102         if (result != GNOME_VFS_OK) {
3103                 *expected_size = 0;
3104                 return NULL;
3105         }
3106         
3107         info = gnome_vfs_file_info_new ();
3108         result = gnome_vfs_get_file_info_from_handle (handle, info, GNOME_VFS_FILE_INFO_DEFAULT);
3109         if (result != GNOME_VFS_OK || ! (info->valid_fields & GNOME_VFS_FILE_INFO_FIELDS_SIZE)) {
3110                 /* We put a "safe" default size for going to cache */
3111                 *expected_size = (300*1024);
3112         } else {
3113                 *expected_size = info->size;
3114         }
3115         gnome_vfs_file_info_unref (info);
3116
3117         stream = tny_vfs_stream_new (handle);
3118
3119         return stream;
3120
3121 }
3122
3123 typedef struct {
3124         gchar *uri;
3125         gchar *cache_id;
3126         TnyStream *output_stream;
3127         GtkWidget *msg_view;
3128 } FetchImageData;
3129
3130 gboolean
3131 on_fetch_image_idle_refresh_view (gpointer userdata)
3132 {
3133
3134         FetchImageData *fidata = (FetchImageData *) userdata;
3135         g_message ("REFRESH VIEW");
3136         if (GTK_WIDGET_DRAWABLE (fidata->msg_view)) {
3137                 g_message ("QUEUING DRAW");
3138                 gtk_widget_queue_draw (fidata->msg_view);
3139         }
3140         g_object_unref (fidata->msg_view);
3141         g_slice_free (FetchImageData, fidata);
3142         return FALSE;
3143 }
3144
3145 static gpointer
3146 on_fetch_image_thread (gpointer userdata)
3147 {
3148         FetchImageData *fidata = (FetchImageData *) userdata;
3149         TnyStreamCache *cache;
3150         TnyStream *cache_stream;
3151
3152         cache = modest_runtime_get_images_cache ();
3153         cache_stream = tny_stream_cache_get_stream (cache, fidata->cache_id, (TnyStreamCacheOpenStreamFetcher) fetch_image_open_stream, (gpointer) fidata->uri);
3154         g_free (fidata->cache_id);
3155         g_free (fidata->uri);
3156
3157         if (cache_stream != NULL) {
3158                 tny_stream_write_to_stream (cache_stream, fidata->output_stream);
3159                 tny_stream_close (cache_stream);
3160                 g_object_unref (cache_stream);
3161         }
3162
3163         tny_stream_close (fidata->output_stream);
3164         g_object_unref (fidata->output_stream);
3165
3166
3167         gdk_threads_enter ();
3168         g_idle_add (on_fetch_image_idle_refresh_view, fidata);
3169         gdk_threads_leave ();
3170
3171         return NULL;
3172 }
3173
3174 static gboolean
3175 on_fetch_image (ModestMsgView *msgview,
3176                 const gchar *uri,
3177                 TnyStream *stream,
3178                 ModestMsgViewWindow *window)
3179 {
3180         const gchar *current_account;
3181         ModestMsgViewWindowPrivate *priv;
3182         FetchImageData *fidata;
3183
3184         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
3185
3186         current_account = modest_window_get_active_account (MODEST_WINDOW (window));
3187
3188         fidata = g_slice_new0 (FetchImageData);
3189         fidata->msg_view = g_object_ref (msgview);
3190         fidata->uri = g_strdup (uri);
3191         fidata->cache_id = modest_images_cache_get_id (current_account, uri);
3192         fidata->output_stream = g_object_ref (stream);
3193
3194         if (g_thread_create (on_fetch_image_thread, fidata, FALSE, NULL) == NULL) {
3195                 g_object_unref (fidata->output_stream);
3196                 g_free (fidata->cache_id);
3197                 g_free (fidata->uri);
3198                 g_object_unref (fidata->msg_view);
3199                 g_slice_free (FetchImageData, fidata);
3200                 tny_stream_close (stream);
3201                 return FALSE;
3202         }
3203
3204         return TRUE;;
3205 }