After a deep refactoring, finally finished to split the window manager
[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         ModestWindowMgr *mgr;
1844         TnyAccount *account;
1845         TnyFolder *folder;
1846         MsgReaderInfo *info;
1847
1848         g_return_val_if_fail (row_reference != NULL, FALSE);
1849
1850         mgr = modest_runtime_get_window_mgr ();
1851         /* Msg download completed */
1852         if (!(tny_header_get_flags (header) & TNY_HEADER_FLAG_CACHED)) {
1853                 /* Ask the user if he wants to download the message if
1854                    we're not online */
1855                 if (!tny_device_is_online (modest_runtime_get_device())) {
1856                         GtkResponseType response;
1857
1858                         response = modest_platform_run_confirmation_dialog (GTK_WINDOW (window),
1859                                                                             _("mcen_nc_get_msg"));
1860                         if (response == GTK_RESPONSE_CANCEL)
1861                                 return FALSE;
1862                 
1863                         folder = tny_header_get_folder (header);
1864                         info = g_slice_new (MsgReaderInfo);
1865                         info->header = g_object_ref (header);
1866                         info->row_reference = gtk_tree_row_reference_copy (row_reference);
1867
1868                         /* Offer the connection dialog if necessary */
1869                         modest_platform_connect_if_remote_and_perform ((GtkWindow *) window, 
1870                                                                        TRUE,
1871                                                                        TNY_FOLDER_STORE (folder),
1872                                                                        message_reader_performer, 
1873                                                                        info);
1874                         g_object_unref (folder);
1875                         return TRUE;
1876                 }
1877         }
1878         
1879         folder = tny_header_get_folder (header);
1880         account = tny_folder_get_account (folder);
1881         info = g_slice_new (MsgReaderInfo);
1882         info->header = g_object_ref (header);
1883         info->row_reference = gtk_tree_row_reference_copy (row_reference);
1884         
1885         message_reader_performer (FALSE, NULL, (GtkWindow *) window, account, info);
1886         g_object_unref (account);
1887         g_object_unref (folder);
1888
1889         return TRUE;
1890 }
1891
1892 gboolean        
1893 modest_msg_view_window_select_next_message (ModestMsgViewWindow *window)
1894 {
1895         ModestMsgViewWindowPrivate *priv;
1896         GtkTreePath *path= NULL;
1897         GtkTreeIter tmp_iter;
1898         TnyHeader *header;
1899         gboolean retval = TRUE;
1900         GtkTreeRowReference *row_reference = NULL;
1901
1902         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), FALSE);
1903         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1904
1905         if (!priv->row_reference)
1906                 return FALSE;
1907
1908         /* Update the next row reference if it's not valid. This could
1909            happen if for example the header which it was pointing to,
1910            was deleted. The best place to do it is in the row-deleted
1911            handler but the tinymail model do not work like the glib
1912            tree models and reports the deletion when the row is still
1913            there */
1914         if (!gtk_tree_row_reference_valid (priv->next_row_reference)) {
1915                 if (gtk_tree_row_reference_valid (priv->row_reference)) {
1916                         priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
1917                         select_next_valid_row (priv->header_model, &(priv->next_row_reference), FALSE, priv->is_outbox);
1918                 }
1919         }
1920         if (priv->next_row_reference)
1921                 path = gtk_tree_row_reference_get_path (priv->next_row_reference);
1922         if (path == NULL)
1923                 return FALSE;
1924
1925         row_reference = gtk_tree_row_reference_copy (priv->next_row_reference);
1926
1927         gtk_tree_model_get_iter (priv->header_model,
1928                                  &tmp_iter,
1929                                  path);
1930         gtk_tree_path_free (path);
1931
1932         gtk_tree_model_get (priv->header_model, &tmp_iter, 
1933                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1934                             &header, -1);
1935         
1936         /* Read the message & show it */
1937         if (!message_reader (window, priv, header, row_reference)) {
1938                 retval = FALSE;
1939         }
1940         gtk_tree_row_reference_free (row_reference);
1941
1942         /* Free */
1943         g_object_unref (header);
1944
1945         return retval;
1946 }
1947
1948 gboolean        
1949 modest_msg_view_window_select_previous_message (ModestMsgViewWindow *window)
1950 {
1951         ModestMsgViewWindowPrivate *priv = NULL;
1952         GtkTreePath *path;
1953         gboolean finished = FALSE;
1954         gboolean retval = FALSE;
1955
1956         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), FALSE);
1957         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1958
1959         /* Return inmediatly if there is no header model */
1960         if (!priv->header_model || !priv->row_reference)
1961                 return FALSE;
1962
1963         path = gtk_tree_row_reference_get_path (priv->row_reference);
1964         while (!finished && gtk_tree_path_prev (path)) {
1965                 TnyHeader *header;
1966                 GtkTreeIter iter;
1967
1968                 gtk_tree_model_get_iter (priv->header_model, &iter, path);
1969                 gtk_tree_model_get (priv->header_model, &iter, 
1970                                     TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1971                                     &header, -1);
1972                 finished = TRUE;
1973                 if (header) {
1974                         if (msg_is_visible (header, priv->is_outbox)) {
1975                                 GtkTreeRowReference *row_reference;
1976                                 row_reference = gtk_tree_row_reference_new (priv->header_model, path);
1977                                 /* Read the message & show it */
1978                                 retval = message_reader (window, priv, header, row_reference);
1979                                 gtk_tree_row_reference_free (row_reference);
1980                         } else {
1981                                 finished = FALSE;
1982                         }
1983                         g_object_unref (header);
1984                 }
1985         }
1986
1987         gtk_tree_path_free (path);
1988         return retval;
1989 }
1990
1991 static void
1992 view_msg_cb (ModestMailOperation *mail_op, 
1993              TnyHeader *header, 
1994              gboolean canceled,
1995              TnyMsg *msg, 
1996              GError *error,
1997              gpointer user_data)
1998 {
1999         ModestMsgViewWindow *self = NULL;
2000         ModestMsgViewWindowPrivate *priv = NULL;
2001         GtkTreeRowReference *row_reference = NULL;
2002
2003         /* Unregister the header (it was registered before creating the mail operation) */
2004         modest_window_mgr_unregister_header (modest_runtime_get_window_mgr (), header);
2005
2006         row_reference = (GtkTreeRowReference *) user_data;
2007         if (canceled) {
2008                 gtk_tree_row_reference_free (row_reference);
2009                 return;
2010         }
2011         
2012         /* If there was any error */
2013         if (!modest_ui_actions_msg_retrieval_check (mail_op, header, msg)) {
2014                 gtk_tree_row_reference_free (row_reference);                    
2015                 return;
2016         }
2017
2018         /* Get the window */ 
2019         self = (ModestMsgViewWindow *) modest_mail_operation_get_source (mail_op);
2020         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self));
2021         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2022
2023         /* Update the row reference */
2024         if (priv->row_reference != NULL) {
2025                 gtk_tree_row_reference_free (priv->row_reference);
2026                 priv->row_reference = gtk_tree_row_reference_copy (row_reference);
2027                 if (priv->next_row_reference != NULL) {
2028                         gtk_tree_row_reference_free (priv->next_row_reference);
2029                 }
2030                 priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
2031                 select_next_valid_row (priv->header_model, &(priv->next_row_reference), TRUE, priv->is_outbox);
2032         }
2033
2034         /* Mark header as read */
2035         if (!(tny_header_get_flags (header) & TNY_HEADER_FLAG_SEEN))
2036                 tny_header_set_flag (header, TNY_HEADER_FLAG_SEEN);
2037
2038         /* Set new message */
2039         if (priv->msg_view != NULL && TNY_IS_MSG_VIEW (priv->msg_view)) {
2040                 tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
2041                 modest_msg_view_window_update_priority (self);
2042                 update_window_title (MODEST_MSG_VIEW_WINDOW (self));
2043                 modest_msg_view_grab_focus (MODEST_MSG_VIEW (priv->msg_view));
2044         }
2045
2046         /* Set the new message uid of the window  */
2047         if (priv->msg_uid) {
2048                 g_free (priv->msg_uid);
2049                 priv->msg_uid = modest_tny_folder_get_header_unique_id (header);
2050         }
2051
2052         /* Notify the observers */
2053         g_signal_emit (G_OBJECT (self), signals[MSG_CHANGED_SIGNAL], 
2054                        0, priv->header_model, priv->row_reference);
2055
2056         /* Frees */
2057         g_object_unref (self);
2058         gtk_tree_row_reference_free (row_reference);            
2059 }
2060
2061 TnyFolderType
2062 modest_msg_view_window_get_folder_type (ModestMsgViewWindow *window)
2063 {
2064         ModestMsgViewWindowPrivate *priv;
2065         TnyMsg *msg;
2066         TnyFolderType folder_type;
2067
2068         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2069
2070         folder_type = TNY_FOLDER_TYPE_UNKNOWN;
2071
2072         msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
2073         if (msg) {
2074                 TnyFolder *folder;
2075
2076                 folder = tny_msg_get_folder (msg);
2077                 if (folder) {
2078                         folder_type = modest_tny_folder_guess_folder_type (folder);
2079                         g_object_unref (folder);
2080                 }
2081                 g_object_unref (msg);
2082         }
2083
2084         return folder_type;
2085 }
2086
2087
2088 static void
2089 modest_msg_view_window_update_priority (ModestMsgViewWindow *window)
2090 {
2091         ModestMsgViewWindowPrivate *priv;
2092         TnyHeader *header = NULL;
2093         TnyHeaderFlags flags = 0;
2094
2095         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2096
2097         if (priv->header_model && priv->row_reference) {
2098                 GtkTreeIter iter;
2099                 GtkTreePath *path = NULL;
2100
2101                 path = gtk_tree_row_reference_get_path (priv->row_reference);
2102                 g_return_if_fail (path != NULL);
2103                 gtk_tree_model_get_iter (priv->header_model, 
2104                                          &iter, 
2105                                          gtk_tree_row_reference_get_path (priv->row_reference));
2106
2107                 gtk_tree_model_get (priv->header_model, &iter, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
2108                                     &header, -1);
2109                 gtk_tree_path_free (path);
2110         } else {
2111                 TnyMsg *msg;
2112                 msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
2113                 if (msg) {
2114                         header = tny_msg_get_header (msg);
2115                         g_object_unref (msg);
2116                 }
2117         }
2118
2119         if (header) {
2120                 flags = tny_header_get_flags (header);
2121                 g_object_unref(G_OBJECT(header));
2122         }
2123
2124         modest_msg_view_set_priority (MODEST_MSG_VIEW(priv->msg_view), flags);
2125
2126 }
2127
2128 static void
2129 toolbar_resize (ModestMsgViewWindow *self)
2130 {
2131         ModestMsgViewWindowPrivate *priv = NULL;
2132         ModestWindowPrivate *parent_priv = NULL;
2133         GtkWidget *widget;
2134         gint static_button_size;
2135         ModestWindowMgr *mgr;
2136
2137         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self));
2138         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2139         parent_priv = MODEST_WINDOW_GET_PRIVATE(self);
2140
2141         mgr = modest_runtime_get_window_mgr ();
2142         static_button_size = modest_window_mgr_get_fullscreen_mode (mgr)?118:108;
2143
2144         if (parent_priv->toolbar) {
2145                 /* left size buttons */
2146                 widget = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageReply");
2147                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (widget), FALSE);
2148                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (widget), FALSE);
2149                 gtk_widget_set_size_request (GTK_WIDGET (widget), static_button_size, -1);
2150                 widget = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageMoveTo");
2151                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (widget), FALSE);
2152                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (widget), FALSE);
2153                 gtk_widget_set_size_request (GTK_WIDGET (widget), static_button_size, -1);
2154                 widget = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarDeleteMessage");
2155                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (widget), FALSE);
2156                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (widget), FALSE);
2157                 gtk_widget_set_size_request (GTK_WIDGET (widget), static_button_size, -1);
2158                 widget = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/FindInMessage");
2159                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (widget), FALSE);
2160                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (widget), FALSE);
2161                 gtk_widget_set_size_request (GTK_WIDGET (widget), static_button_size, -1);
2162                 
2163                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (priv->progress_toolitem), FALSE);
2164                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->progress_toolitem), TRUE);
2165                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (priv->cancel_toolitem), FALSE);
2166                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->cancel_toolitem), FALSE);
2167                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (priv->next_toolitem), TRUE);
2168                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->next_toolitem), TRUE);
2169                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (priv->prev_toolitem), TRUE);
2170                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->prev_toolitem), TRUE);
2171         }
2172                 
2173 }
2174
2175 static gboolean
2176 modest_msg_view_window_window_state_event (GtkWidget *widget, GdkEventWindowState *event, gpointer userdata)
2177 {
2178         if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) {
2179                 ModestWindowPrivate *parent_priv;
2180                 ModestWindowMgr *mgr;
2181                 gboolean is_fullscreen;
2182                 GtkAction *fs_toggle_action;
2183                 gboolean active;
2184
2185                 mgr = modest_runtime_get_window_mgr ();
2186                 is_fullscreen = (modest_window_mgr_get_fullscreen_mode (mgr))?1:0;
2187
2188                 parent_priv = MODEST_WINDOW_GET_PRIVATE (widget);
2189                 
2190                 fs_toggle_action = gtk_ui_manager_get_action (parent_priv->ui_manager, "/MenuBar/ViewMenu/ViewToggleFullscreenMenu");
2191                 active = (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (fs_toggle_action)))?1:0;
2192                 if (is_fullscreen != active) {
2193                         gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (fs_toggle_action), is_fullscreen);
2194                 }
2195                 toolbar_resize (MODEST_MSG_VIEW_WINDOW (widget));
2196         }
2197
2198         return FALSE;
2199
2200 }
2201
2202 static void
2203 modest_msg_view_window_show_toolbar (ModestWindow *self,
2204                                      gboolean show_toolbar)
2205 {
2206         ModestMsgViewWindowPrivate *priv = NULL;
2207         ModestWindowPrivate *parent_priv;
2208         GtkWidget *reply_button = NULL, *menu = NULL;
2209         GtkWidget *placeholder = NULL;
2210         gint insert_index;
2211         const gchar *action_name;
2212         GtkAction *action;
2213         
2214         parent_priv = MODEST_WINDOW_GET_PRIVATE(self);
2215         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2216
2217         /* Set optimized view status */
2218         priv->optimized_view = !show_toolbar;
2219
2220         if (!parent_priv->toolbar) {
2221                 parent_priv->toolbar = gtk_ui_manager_get_widget (parent_priv->ui_manager, 
2222                                                                   "/ToolBar");
2223                 gtk_widget_set_no_show_all (parent_priv->toolbar, TRUE);
2224
2225                 priv->progress_toolitem = GTK_WIDGET (gtk_tool_item_new ());
2226                 priv->cancel_toolitem = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarCancel");
2227                 priv->next_toolitem = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageNext");
2228                 priv->prev_toolitem = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageBack");
2229                 toolbar_resize (MODEST_MSG_VIEW_WINDOW (self));
2230
2231                 /* Add ProgressBar (Transfer toolbar) */ 
2232                 priv->progress_bar = modest_progress_bar_new ();
2233                 gtk_widget_set_no_show_all (priv->progress_bar, TRUE);
2234                 placeholder = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ProgressbarView");
2235                 insert_index = gtk_toolbar_get_item_index(GTK_TOOLBAR (parent_priv->toolbar), GTK_TOOL_ITEM(placeholder));
2236                 gtk_container_add (GTK_CONTAINER (priv->progress_toolitem), priv->progress_bar);
2237                 gtk_toolbar_insert(GTK_TOOLBAR(parent_priv->toolbar), GTK_TOOL_ITEM (priv->progress_toolitem), insert_index);
2238                 
2239                 /* Connect cancel 'clicked' signal to abort progress mode */
2240                 g_signal_connect(priv->cancel_toolitem, "clicked",
2241                                  G_CALLBACK(cancel_progressbar),
2242                                  self);
2243                 
2244                 /* Add it to the observers list */
2245                 priv->progress_widgets = g_slist_prepend(priv->progress_widgets, priv->progress_bar);
2246
2247                 /* Add to window */
2248                 hildon_window_add_toolbar (HILDON_WINDOW (self), 
2249                                            GTK_TOOLBAR (parent_priv->toolbar));
2250
2251                 /* Set reply button tap and hold menu */        
2252                 reply_button = gtk_ui_manager_get_widget (parent_priv->ui_manager, 
2253                                                           "/ToolBar/ToolbarMessageReply");
2254                 menu = gtk_ui_manager_get_widget (parent_priv->ui_manager, 
2255                                                   "/ToolbarReplyCSM");
2256                 gtk_widget_tap_and_hold_setup (GTK_WIDGET (reply_button), menu, NULL, 0);
2257         }
2258
2259         if (show_toolbar) {
2260                 /* Quick hack: this prevents toolbar icons "dance" when progress bar show status is changed */ 
2261                 /* TODO: resize mode migth be GTK_RESIZE_QUEUE, in order to avoid unneccesary shows */
2262                 gtk_container_set_resize_mode (GTK_CONTAINER(parent_priv->toolbar), GTK_RESIZE_IMMEDIATE);
2263
2264                 gtk_widget_show (GTK_WIDGET (parent_priv->toolbar));
2265                 if (modest_msg_view_window_transfer_mode_enabled (MODEST_MSG_VIEW_WINDOW (self))) 
2266                         set_toolbar_mode (MODEST_MSG_VIEW_WINDOW (self), TOOLBAR_MODE_TRANSFER);
2267                 else
2268                         set_toolbar_mode (MODEST_MSG_VIEW_WINDOW (self), TOOLBAR_MODE_NORMAL);
2269
2270         } else {
2271                 gtk_widget_set_no_show_all (parent_priv->toolbar, TRUE);
2272                 gtk_widget_hide (GTK_WIDGET (parent_priv->toolbar));
2273         }
2274
2275         /* Update also the actions (to update the toggles in the
2276            menus), we have to do it manually because some other window
2277            of the same time could have changed it (remember that the
2278            toolbar fullscreen mode is shared by all the windows of the
2279            same type */
2280         if (modest_window_mgr_get_fullscreen_mode (modest_runtime_get_window_mgr ()))
2281                 action_name = "/MenuBar/ViewMenu/ViewShowToolbarMenu/ViewShowToolbarFullScreenMenu";
2282         else
2283                 action_name = "/MenuBar/ViewMenu/ViewShowToolbarMenu/ViewShowToolbarNormalScreenMenu";
2284
2285         action = gtk_ui_manager_get_action (parent_priv->ui_manager, action_name);
2286         modest_utils_toggle_action_set_active_block_notify (GTK_TOGGLE_ACTION (action),
2287                                                             show_toolbar);
2288 }
2289
2290 static void 
2291 modest_msg_view_window_clipboard_owner_change (GtkClipboard *clipboard,
2292                                                GdkEvent *event,
2293                                                ModestMsgViewWindow *window)
2294 {
2295         if (!GTK_WIDGET_VISIBLE (window))
2296                 return;
2297
2298         modest_window_check_dimming_rules_group (MODEST_WINDOW (window), MODEST_DIMMING_RULES_CLIPBOARD);
2299 }
2300
2301 gboolean 
2302 modest_msg_view_window_transfer_mode_enabled (ModestMsgViewWindow *self)
2303 {
2304         ModestMsgViewWindowPrivate *priv;
2305         
2306         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), FALSE); 
2307         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2308
2309         return priv->current_toolbar_mode == TOOLBAR_MODE_TRANSFER;
2310 }
2311
2312 static void
2313 cancel_progressbar (GtkToolButton *toolbutton,
2314                     ModestMsgViewWindow *self)
2315 {
2316         GSList *tmp;
2317         ModestMsgViewWindowPrivate *priv;
2318         
2319         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2320
2321         /* Get operation observers and cancel its current operation */
2322         tmp = priv->progress_widgets;
2323         while (tmp) {
2324                 modest_progress_object_cancel_current_operation (MODEST_PROGRESS_OBJECT(tmp->data));
2325                 tmp=g_slist_next(tmp);
2326         }
2327 }
2328 static gboolean
2329 observers_empty (ModestMsgViewWindow *self)
2330 {
2331         GSList *tmp = NULL;
2332         ModestMsgViewWindowPrivate *priv;
2333         gboolean is_empty = TRUE;
2334         guint pending_ops = 0;
2335  
2336         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2337         tmp = priv->progress_widgets;
2338
2339         /* Check all observers */
2340         while (tmp && is_empty)  {
2341                 pending_ops = modest_progress_object_num_pending_operations (MODEST_PROGRESS_OBJECT(tmp->data));
2342                 is_empty = pending_ops == 0;
2343                 
2344                 tmp = g_slist_next(tmp);
2345         }
2346         
2347         return is_empty;
2348 }
2349
2350 static void
2351 on_account_removed (TnyAccountStore *account_store, 
2352                     TnyAccount *account,
2353                     gpointer user_data)
2354 {
2355         /* Do nothing if it's a transport account, because we only
2356            show the messages of a store account */
2357         if (tny_account_get_account_type(account) == TNY_ACCOUNT_TYPE_STORE) {
2358                 const gchar *parent_acc = NULL;
2359                 const gchar *our_acc = NULL;
2360
2361                 our_acc = modest_window_get_active_account (MODEST_WINDOW (user_data));
2362                 parent_acc = modest_tny_account_get_parent_modest_account_name_for_server_account (account);
2363
2364                 /* Close this window if I'm showing a message of the removed account */
2365                 if (strcmp (parent_acc, our_acc) == 0)
2366                         modest_ui_actions_on_close_window (NULL, MODEST_WINDOW (user_data));
2367         }
2368 }
2369
2370 static void 
2371 on_mail_operation_started (ModestMailOperation *mail_op,
2372                            gpointer user_data)
2373 {
2374         ModestMsgViewWindow *self;
2375         ModestMailOperationTypeOperation op_type;
2376         GSList *tmp;
2377         ModestMsgViewWindowPrivate *priv;
2378         GObject *source = NULL;
2379
2380         self = MODEST_MSG_VIEW_WINDOW (user_data);
2381         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2382         op_type = modest_mail_operation_get_type_operation (mail_op);
2383         tmp = priv->progress_widgets;
2384         source = modest_mail_operation_get_source(mail_op);
2385         if (G_OBJECT (self) == source) {
2386                 if (op_type == MODEST_MAIL_OPERATION_TYPE_RECEIVE ) {
2387                         set_toolbar_transfer_mode(self);
2388                         while (tmp) {
2389                                 modest_progress_object_add_operation (
2390                                                 MODEST_PROGRESS_OBJECT (tmp->data),
2391                                                 mail_op);
2392                                 tmp = g_slist_next (tmp);
2393                         }
2394                 }
2395         }
2396         g_object_unref (source);
2397 }
2398
2399 static void 
2400 on_mail_operation_finished (ModestMailOperation *mail_op,
2401                             gpointer user_data)
2402 {
2403         ModestMsgViewWindow *self;
2404         ModestMailOperationTypeOperation op_type;
2405         GSList *tmp;
2406         ModestMsgViewWindowPrivate *priv;
2407         
2408         self = MODEST_MSG_VIEW_WINDOW (user_data);
2409         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2410         op_type = modest_mail_operation_get_type_operation (mail_op);
2411         tmp = priv->progress_widgets;
2412         
2413         if (op_type == MODEST_MAIL_OPERATION_TYPE_RECEIVE ) {
2414                 while (tmp) {
2415                         modest_progress_object_remove_operation (MODEST_PROGRESS_OBJECT (tmp->data),
2416                                                                  mail_op);
2417                         tmp = g_slist_next (tmp);
2418                 }
2419
2420                 /* If no more operations are being observed, NORMAL mode is enabled again */
2421                 if (observers_empty (self)) {
2422                         set_toolbar_mode (self, TOOLBAR_MODE_NORMAL);
2423                 }
2424
2425                 /* Update dimming rules. We have to do this right here
2426                    and not in view_msg_cb because at that point the
2427                    transfer mode is still enabled so the dimming rule
2428                    won't let the user delete the message that has been
2429                    readed for example */
2430                 modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (self));
2431                 modest_ui_actions_check_menu_dimming_rules (MODEST_WINDOW (self));
2432         }
2433 }
2434
2435 static void
2436 on_queue_changed (ModestMailOperationQueue *queue,
2437                   ModestMailOperation *mail_op,
2438                   ModestMailOperationQueueNotification type,
2439                   ModestMsgViewWindow *self)
2440 {       
2441         ModestMsgViewWindowPrivate *priv;
2442
2443         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2444
2445         /* If this operations was created by another window, do nothing */
2446         if (!modest_mail_operation_is_mine (mail_op, G_OBJECT(self))) 
2447             return;
2448
2449         if (type == MODEST_MAIL_OPERATION_QUEUE_OPERATION_ADDED) {
2450                 priv->sighandlers = modest_signal_mgr_connect (priv->sighandlers,
2451                                                                G_OBJECT (mail_op),
2452                                                                "operation-started",
2453                                                                G_CALLBACK (on_mail_operation_started),
2454                                                                self);
2455                 priv->sighandlers = modest_signal_mgr_connect (priv->sighandlers,
2456                                                                G_OBJECT (mail_op),
2457                                                                "operation-finished",
2458                                                                G_CALLBACK (on_mail_operation_finished),
2459                                                                self);
2460         } else if (type == MODEST_MAIL_OPERATION_QUEUE_OPERATION_REMOVED) {
2461                 priv->sighandlers = modest_signal_mgr_disconnect (priv->sighandlers,
2462                                                                   G_OBJECT (mail_op),
2463                                                                   "operation-started");
2464                 priv->sighandlers = modest_signal_mgr_disconnect (priv->sighandlers,
2465                                                                   G_OBJECT (mail_op),
2466                                                                   "operation-finished");
2467         }
2468 }
2469
2470 TnyList *
2471 modest_msg_view_window_get_attachments (ModestMsgViewWindow *win) 
2472 {
2473         ModestMsgViewWindowPrivate *priv;
2474         TnyList *selected_attachments = NULL;
2475         
2476         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (win), NULL);
2477         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (win);
2478
2479         selected_attachments = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2480         
2481         return selected_attachments;
2482 }
2483
2484 typedef struct {
2485         gchar *filepath;
2486         GtkWidget *banner;
2487         guint banner_idle_id;
2488 } DecodeAsyncHelper;
2489
2490 static gboolean
2491 decode_async_banner_idle (gpointer user_data)
2492 {
2493         DecodeAsyncHelper *helper = (DecodeAsyncHelper *) user_data;
2494
2495         helper->banner_idle_id = 0;
2496         helper->banner = hildon_banner_show_animation (NULL, NULL, _("mail_me_opening"));
2497         g_object_ref (helper->banner);
2498
2499         return FALSE;
2500 }
2501
2502 static void
2503 on_decode_to_stream_async_handler (TnyMimePart *mime_part, 
2504                                    gboolean cancelled, 
2505                                    TnyStream *stream, 
2506                                    GError *err, 
2507                                    gpointer user_data)
2508 {
2509         DecodeAsyncHelper *helper = (DecodeAsyncHelper *) user_data;
2510
2511         if (helper->banner_idle_id > 0) {
2512                 g_source_remove (helper->banner_idle_id);
2513                 helper->banner_idle_id = 0;
2514         }
2515         if (helper->banner) {
2516                 gtk_widget_destroy (helper->banner);
2517         }
2518         if (cancelled || err) {
2519                 modest_platform_information_banner (NULL, NULL, 
2520                                                     _("mail_ib_file_operation_failed"));
2521                 goto free;
2522         }
2523
2524         /* make the file read-only */
2525         g_chmod(helper->filepath, 0444);
2526         
2527         /* Activate the file */
2528         modest_platform_activate_file (helper->filepath, tny_mime_part_get_content_type (mime_part));
2529
2530  free:
2531         /* Frees */
2532         g_free (helper->filepath);
2533         g_object_unref (helper->banner);
2534         g_slice_free (DecodeAsyncHelper, helper);
2535 }
2536
2537 void
2538 modest_msg_view_window_view_attachment (ModestMsgViewWindow *window, 
2539                                         TnyMimePart *mime_part)
2540 {
2541         ModestMsgViewWindowPrivate *priv;
2542         const gchar *msg_uid;
2543         gchar *attachment_uid = NULL;
2544         gint attachment_index = 0;
2545         TnyList *attachments;
2546
2547         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
2548         g_return_if_fail (TNY_IS_MIME_PART (mime_part) || (mime_part == NULL));
2549         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2550
2551         msg_uid = modest_msg_view_window_get_message_uid (MODEST_MSG_VIEW_WINDOW (window));
2552         attachments = modest_msg_view_get_attachments (MODEST_MSG_VIEW (priv->msg_view));
2553         attachment_index = modest_list_index (attachments, (GObject *) mime_part);
2554         g_object_unref (attachments);
2555         
2556         if (msg_uid && attachment_index >= 0) {
2557                 attachment_uid = g_strdup_printf ("%s/%d", msg_uid, attachment_index);
2558         }
2559
2560         if (mime_part == NULL) {
2561                 gboolean error = FALSE;
2562                 TnyList *selected_attachments = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2563                 if (selected_attachments == NULL || tny_list_get_length (selected_attachments) == 0) {
2564                         error = TRUE;
2565                 } else if (tny_list_get_length (selected_attachments) > 1) {
2566                         hildon_banner_show_information (NULL, NULL, _("mcen_ib_unable_to_display_more"));
2567                         error = TRUE;
2568                 } else {
2569                         TnyIterator *iter;
2570                         iter = tny_list_create_iterator (selected_attachments);
2571                         mime_part = (TnyMimePart *) tny_iterator_get_current (iter);
2572                         g_object_unref (iter);
2573                 }
2574                 g_object_unref (selected_attachments);
2575
2576                 if (error)
2577                         return;
2578         } else {
2579                 g_object_ref (mime_part);
2580         }
2581
2582         if (tny_mime_part_is_purged (mime_part)) {
2583                 g_object_unref (mime_part);
2584                 return;
2585         }
2586
2587         if (!modest_tny_mime_part_is_msg (mime_part)) {
2588                 gchar *filepath = NULL;
2589                 const gchar *att_filename = tny_mime_part_get_filename (mime_part);
2590                 gboolean show_error_banner = FALSE;
2591                 TnyFsStream *temp_stream = NULL;
2592                 temp_stream = modest_utils_create_temp_stream (att_filename, attachment_uid,
2593                                                                &filepath);
2594                 
2595                 if (temp_stream != NULL) {
2596                         DecodeAsyncHelper *helper = g_slice_new (DecodeAsyncHelper);
2597                         helper->filepath = g_strdup (filepath);
2598                         helper->banner = NULL;
2599                         helper->banner_idle_id = g_timeout_add (1000, decode_async_banner_idle, helper);
2600                         tny_mime_part_decode_to_stream_async (mime_part, TNY_STREAM (temp_stream), 
2601                                                               on_decode_to_stream_async_handler, 
2602                                                               NULL, 
2603                                                               helper);
2604                         g_object_unref (temp_stream);
2605                         /* NOTE: files in the temporary area will be automatically
2606                          * cleaned after some time if they are no longer in use */
2607                 } else {
2608                         if (filepath) {
2609                                 const gchar *content_type;
2610                                 /* the file may already exist but it isn't writable,
2611                                  * let's try to open it anyway */
2612                                 content_type = tny_mime_part_get_content_type (mime_part);
2613                                 modest_platform_activate_file (filepath, content_type);
2614                         } else {
2615                                 g_warning ("%s: modest_utils_create_temp_stream failed", __FUNCTION__);
2616                                 show_error_banner = TRUE;
2617                         }
2618                 }
2619                 if (filepath)
2620                         g_free (filepath);
2621                 if (show_error_banner)
2622                         modest_platform_information_banner (NULL, NULL, _("mail_ib_file_operation_failed"));
2623         } else {
2624                 /* message attachment */
2625                 TnyHeader *header = NULL;
2626                 ModestWindowMgr *mgr;
2627                 ModestWindow *msg_win = NULL;
2628                 gboolean found;
2629                 
2630                 header = tny_msg_get_header (TNY_MSG (mime_part));
2631                 mgr = modest_runtime_get_window_mgr ();         
2632                 found = modest_window_mgr_find_registered_header (mgr, header, &msg_win);
2633
2634                 if (found) {
2635                         /* if it's found, but there is no msg_win, it's probably in the process of being created;
2636                          * thus, we don't do anything */
2637                         g_warning ("window for is already being created");
2638                 } else { 
2639                         /* it's not found, so create a new window for it */
2640                         modest_window_mgr_register_header (mgr, header, attachment_uid); /* register the uid before building the window */
2641                         gchar *account = g_strdup (modest_window_get_active_account (MODEST_WINDOW (window)));
2642                         if (!account)
2643                                 account = modest_account_mgr_get_default_account (modest_runtime_get_account_mgr ());
2644                         msg_win = modest_msg_view_window_new_for_attachment (TNY_MSG (mime_part), account, attachment_uid);
2645                         modest_window_set_zoom (MODEST_WINDOW (msg_win), 
2646                                                 modest_window_get_zoom (MODEST_WINDOW (window)));
2647                         modest_window_mgr_register_window (mgr, msg_win, MODEST_WINDOW (window));
2648                         gtk_widget_show_all (GTK_WIDGET (msg_win));
2649                 }
2650         }
2651         g_object_unref (mime_part);
2652 }
2653
2654 typedef struct
2655 {
2656         gchar *filename;
2657         TnyMimePart *part;
2658 } SaveMimePartPair;
2659
2660 typedef struct
2661 {
2662         GList *pairs;
2663         GtkWidget *banner;
2664         GnomeVFSResult result;
2665 } SaveMimePartInfo;
2666
2667 static void save_mime_part_info_free (SaveMimePartInfo *info, gboolean with_struct);
2668 static gboolean idle_save_mime_part_show_result (SaveMimePartInfo *info);
2669 static gpointer save_mime_part_to_file (SaveMimePartInfo *info);
2670 static void save_mime_parts_to_file_with_checks (SaveMimePartInfo *info);
2671
2672 static void 
2673 save_mime_part_info_free (SaveMimePartInfo *info, gboolean with_struct)
2674 {
2675         
2676         GList *node;
2677         for (node = info->pairs; node != NULL; node = g_list_next (node)) {
2678                 SaveMimePartPair *pair = (SaveMimePartPair *) node->data;
2679                 g_free (pair->filename);
2680                 g_object_unref (pair->part);
2681                 g_slice_free (SaveMimePartPair, pair);
2682         }
2683         g_list_free (info->pairs);
2684         info->pairs = NULL;
2685         if (with_struct) {
2686                 gtk_widget_destroy (info->banner);
2687                 g_slice_free (SaveMimePartInfo, info);
2688         }
2689 }
2690
2691 static gboolean
2692 idle_save_mime_part_show_result (SaveMimePartInfo *info)
2693 {
2694         if (info->pairs != NULL) {
2695                 save_mime_part_to_file (info);
2696         } else {
2697                 /* This is a GDK lock because we are an idle callback and
2698                  * hildon_banner_show_information is or does Gtk+ code */
2699
2700                 gdk_threads_enter (); /* CHECKED */
2701                 save_mime_part_info_free (info, TRUE);
2702                 if (info->result == GNOME_VFS_OK) {
2703                         hildon_banner_show_information (NULL, NULL, _CS("sfil_ib_saved"));
2704                 } else if (info->result == GNOME_VFS_ERROR_NO_SPACE) {
2705                         hildon_banner_show_information (NULL, NULL, dgettext("ke-recv", 
2706                                                                              "cerm_device_memory_full"));
2707                 } else {
2708                         hildon_banner_show_information (NULL, NULL, _("mail_ib_file_operation_failed"));
2709                 }
2710                 gdk_threads_leave (); /* CHECKED */
2711         }
2712
2713         return FALSE;
2714 }
2715
2716 static gpointer
2717 save_mime_part_to_file (SaveMimePartInfo *info)
2718 {
2719         GnomeVFSHandle *handle;
2720         TnyStream *stream;
2721         SaveMimePartPair *pair = (SaveMimePartPair *) info->pairs->data;
2722
2723         info->result = gnome_vfs_create (&handle, pair->filename, GNOME_VFS_OPEN_WRITE, FALSE, 0644);
2724         if (info->result == GNOME_VFS_OK) {
2725                 GError *error = NULL;
2726                 stream = tny_vfs_stream_new (handle);
2727                 if (tny_mime_part_decode_to_stream (pair->part, stream, &error) < 0) {
2728                         g_warning ("modest: could not save attachment %s: %d (%s)\n", pair->filename, error?error->code:-1, error?error->message:"Unknown error");
2729                         
2730                         info->result = GNOME_VFS_ERROR_IO;
2731                 }
2732                 g_object_unref (G_OBJECT (stream));
2733                 g_object_unref (pair->part);
2734                 g_slice_free (SaveMimePartPair, pair);
2735                 info->pairs = g_list_delete_link (info->pairs, info->pairs);
2736         } else {
2737                 g_warning ("modest: could not create save attachment %s: %s\n", pair->filename, gnome_vfs_result_to_string (info->result));
2738                 save_mime_part_info_free (info, FALSE);
2739         }
2740
2741         g_idle_add ((GSourceFunc) idle_save_mime_part_show_result, info);
2742         return NULL;
2743 }
2744
2745 static void
2746 save_mime_parts_to_file_with_checks (SaveMimePartInfo *info)
2747 {
2748         gboolean is_ok = TRUE;
2749         gint replaced_files = 0;
2750         const GList *files = info->pairs;
2751         const GList *iter;
2752
2753         for (iter = files; (iter != NULL) && (replaced_files < 2); iter = g_list_next(iter)) {
2754                 SaveMimePartPair *pair = iter->data;
2755                 if (modest_utils_file_exists (pair->filename)) {
2756                         replaced_files++;
2757                 }
2758         }
2759         if (replaced_files) {
2760                 GtkWidget *confirm_overwrite_dialog;
2761                 const gchar *message = (replaced_files == 1) ?
2762                         _FM("docm_nc_replace_file") : _FM("docm_nc_replace_multiple");
2763                 confirm_overwrite_dialog = hildon_note_new_confirmation (NULL, message);
2764                 if (gtk_dialog_run (GTK_DIALOG (confirm_overwrite_dialog)) != GTK_RESPONSE_OK) {
2765                         is_ok = FALSE;
2766                 }
2767                 gtk_widget_destroy (confirm_overwrite_dialog);
2768         }
2769
2770         if (!is_ok) {
2771                 save_mime_part_info_free (info, TRUE);
2772         } else {
2773                 GtkWidget *banner = hildon_banner_show_animation (NULL, NULL, 
2774                                                                   _CS("sfil_ib_saving"));
2775                 info->banner = banner;
2776                 g_thread_create ((GThreadFunc)save_mime_part_to_file, info, FALSE, NULL);
2777         }
2778
2779 }
2780
2781 static void
2782 save_attachments_response (GtkDialog *dialog,
2783                            gint       arg1,
2784                            gpointer   user_data)  
2785 {
2786         TnyList *mime_parts;
2787         gchar *chooser_uri;
2788         GList *files_to_save = NULL;
2789
2790         mime_parts = TNY_LIST (user_data);
2791
2792         if (arg1 != GTK_RESPONSE_OK)
2793                 goto end;
2794
2795         chooser_uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dialog));
2796
2797         if (!modest_utils_folder_writable (chooser_uri)) {
2798                 hildon_banner_show_information 
2799                         (NULL, NULL, dgettext("hildon-fm", "sfil_ib_readonly_location"));
2800         } else {
2801                 TnyIterator *iter;
2802
2803                 iter = tny_list_create_iterator (mime_parts);
2804                 while (!tny_iterator_is_done (iter)) {
2805                         TnyMimePart *mime_part = (TnyMimePart *) tny_iterator_get_current (iter);
2806
2807                         if ((modest_tny_mime_part_is_attachment_for_modest (mime_part)) &&
2808                             !tny_mime_part_is_purged (mime_part) &&
2809                             (tny_mime_part_get_filename (mime_part) != NULL)) {
2810                                 SaveMimePartPair *pair;
2811                                         
2812                                 pair = g_slice_new0 (SaveMimePartPair);
2813
2814                                 if (tny_list_get_length (mime_parts) > 1) {
2815                                         gchar *escaped = 
2816                                                 gnome_vfs_escape_slashes (tny_mime_part_get_filename (mime_part));
2817                                         pair->filename = g_build_filename (chooser_uri, escaped, NULL);
2818                                         g_free (escaped);
2819                                 } else {
2820                                         pair->filename = g_strdup (chooser_uri);
2821                                 }
2822                                 pair->part = mime_part;
2823                                 files_to_save = g_list_prepend (files_to_save, pair);
2824                         }
2825                         tny_iterator_next (iter);
2826                 }
2827                 g_object_unref (iter);
2828         }
2829         g_free (chooser_uri);
2830
2831         if (files_to_save != NULL) {
2832                 SaveMimePartInfo *info = g_slice_new0 (SaveMimePartInfo);
2833                 info->pairs = files_to_save;
2834                 info->result = TRUE;
2835                 save_mime_parts_to_file_with_checks (info);
2836         }
2837
2838  end:
2839         /* Free and close the dialog */
2840         g_object_unref (mime_parts);
2841         gtk_widget_destroy (GTK_WIDGET (dialog));
2842 }
2843
2844 void
2845 modest_msg_view_window_save_attachments (ModestMsgViewWindow *window, TnyList *mime_parts)
2846 {
2847         ModestMsgViewWindowPrivate *priv;
2848         GtkWidget *save_dialog = NULL;
2849         gchar *folder = NULL;
2850         gchar *filename = NULL;
2851         gchar *save_multiple_str = NULL;
2852
2853         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
2854         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2855
2856         if (mime_parts == NULL) {
2857                 mime_parts = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2858                 if (mime_parts == NULL || tny_list_get_length (mime_parts) == 0)
2859                         return;
2860         } else {
2861                 g_object_ref (mime_parts);
2862         }
2863
2864         /* prepare dialog */
2865         if (tny_list_get_length (mime_parts) == 1) {
2866                 TnyIterator *iter;
2867                 /* only one attachment selected */
2868                 iter = tny_list_create_iterator (mime_parts);
2869                 TnyMimePart *mime_part = (TnyMimePart *) tny_iterator_get_current (iter);
2870                 g_object_unref (iter);
2871                 if (!modest_tny_mime_part_is_msg (mime_part) && 
2872                     modest_tny_mime_part_is_attachment_for_modest (mime_part) &&
2873                     !tny_mime_part_is_purged (mime_part)) {
2874                         filename = g_strdup (tny_mime_part_get_filename (mime_part));
2875                 } else {
2876                         /* TODO: show any error? */
2877                         g_warning ("Tried to save a non-file attachment");
2878                         g_object_unref (mime_parts);
2879                         return;
2880                 }
2881                 g_object_unref (mime_part);
2882         } else {
2883                 save_multiple_str = g_strdup_printf (_FM("sfil_va_number_of_objects_attachments"), 
2884                                                      tny_list_get_length (mime_parts));
2885         }
2886         
2887         save_dialog = hildon_file_chooser_dialog_new (GTK_WINDOW (window), 
2888                                                       GTK_FILE_CHOOSER_ACTION_SAVE);
2889
2890         /* set folder */
2891         folder = g_build_filename (g_get_home_dir (), DEFAULT_FOLDER, NULL);
2892         gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (save_dialog), folder);
2893         g_free (folder);
2894
2895         /* set filename */
2896         if (filename) {
2897                 gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (save_dialog), 
2898                                                    filename);
2899                 g_free (filename);
2900         }
2901
2902         /* if multiple, set multiple string */
2903         if (save_multiple_str) {
2904                 g_object_set (G_OBJECT (save_dialog), "save-multiple", save_multiple_str, NULL);
2905                 gtk_window_set_title (GTK_WINDOW (save_dialog), _FM("sfil_ti_save_objects_files"));
2906         }
2907
2908         /* We must run this asynchronously, because the hildon dialog
2909            performs a gtk_dialog_run by itself which leads to gdk
2910            deadlocks */
2911         g_signal_connect (save_dialog, "response", 
2912                           G_CALLBACK (save_attachments_response), mime_parts);
2913
2914         gtk_widget_show_all (save_dialog);
2915 }
2916
2917 static gboolean
2918 show_remove_attachment_information (gpointer userdata)
2919 {
2920         ModestMsgViewWindow *window = (ModestMsgViewWindow *) userdata;
2921         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2922
2923         /* We're outside the main lock */
2924         gdk_threads_enter ();
2925
2926         if (priv->remove_attachment_banner != NULL) {
2927                 gtk_widget_destroy (priv->remove_attachment_banner);
2928                 g_object_unref (priv->remove_attachment_banner);
2929         }
2930
2931         priv->remove_attachment_banner = g_object_ref (
2932                 hildon_banner_show_animation (NULL, NULL, _("mcen_ib_removing_attachment")));
2933
2934         gdk_threads_leave ();
2935
2936         return FALSE;
2937 }
2938
2939 void
2940 modest_msg_view_window_remove_attachments (ModestMsgViewWindow *window, gboolean get_all)
2941 {
2942         ModestMsgViewWindowPrivate *priv;
2943         TnyList *mime_parts = NULL;
2944         gchar *confirmation_message;
2945         gint response;
2946         gint n_attachments;
2947         TnyMsg *msg;
2948         TnyIterator *iter;
2949
2950         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
2951         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2952
2953         if (get_all)
2954                 mime_parts = modest_msg_view_get_attachments (MODEST_MSG_VIEW (priv->msg_view));
2955         else
2956                 mime_parts = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2957                 
2958         /* Remove already purged messages from mime parts list */
2959         iter = tny_list_create_iterator (mime_parts);
2960         while (!tny_iterator_is_done (iter)) {
2961                 TnyMimePart *part = TNY_MIME_PART (tny_iterator_get_current (iter));
2962                 tny_iterator_next (iter);
2963                 if (tny_mime_part_is_purged (part)) {
2964                         tny_list_remove (mime_parts, (GObject *) part);
2965                 }
2966                 g_object_unref (part);
2967         }
2968         g_object_unref (iter);
2969
2970         if (tny_list_get_length (mime_parts) == 0) {
2971                 g_object_unref (mime_parts);
2972                 return;
2973         }
2974
2975         n_attachments = tny_list_get_length (mime_parts);
2976         if (n_attachments == 1) {
2977                 gchar *filename;
2978                 TnyMimePart *part;
2979
2980                 iter = tny_list_create_iterator (mime_parts);
2981                 part = (TnyMimePart *) tny_iterator_get_current (iter);
2982                 g_object_unref (iter);
2983                 if (modest_tny_mime_part_is_msg (part)) {
2984                         TnyHeader *header;
2985                         header = tny_msg_get_header (TNY_MSG (part));
2986                         filename = tny_header_dup_subject (header);
2987                         g_object_unref (header);
2988                         if (filename == NULL)
2989                                 filename = g_strdup (_("mail_va_no_subject"));
2990                 } else {
2991                         filename = g_strdup (tny_mime_part_get_filename (TNY_MIME_PART (part)));
2992                 }
2993                 confirmation_message = g_strdup_printf (_("mcen_nc_purge_file_text"), filename);
2994                 g_free (filename);
2995                 g_object_unref (part);
2996         } else {
2997                 confirmation_message = g_strdup_printf (ngettext("mcen_nc_purge_file_text", 
2998                                                                  "mcen_nc_purge_files_text", 
2999                                                                  n_attachments), n_attachments);
3000         }
3001         response = modest_platform_run_confirmation_dialog (GTK_WINDOW (window),
3002                                                             confirmation_message);
3003         g_free (confirmation_message);
3004
3005         if (response != GTK_RESPONSE_OK) {
3006                 g_object_unref (mime_parts);
3007                 return;
3008         }
3009
3010         priv->purge_timeout = g_timeout_add (2000, show_remove_attachment_information, window);
3011         
3012         iter = tny_list_create_iterator (mime_parts);
3013         while (!tny_iterator_is_done (iter)) {
3014                 TnyMimePart *part;
3015
3016                 part = (TnyMimePart *) tny_iterator_get_current (iter);
3017                 tny_mime_part_set_purged (TNY_MIME_PART (part));
3018                 g_object_unref (part);
3019                 tny_iterator_next (iter);
3020         }
3021         g_object_unref (iter);
3022
3023         msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
3024         tny_msg_view_clear (TNY_MSG_VIEW (priv->msg_view));
3025         tny_msg_rewrite_cache (msg);
3026         tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
3027         g_object_unref (msg);
3028
3029         g_object_unref (mime_parts);
3030
3031         if (priv->purge_timeout > 0) {
3032                 g_source_remove (priv->purge_timeout);
3033                 priv->purge_timeout = 0;
3034         }
3035
3036         if (priv->remove_attachment_banner) {
3037                 gtk_widget_destroy (priv->remove_attachment_banner);
3038                 g_object_unref (priv->remove_attachment_banner);
3039                 priv->remove_attachment_banner = NULL;
3040         }
3041
3042
3043 }
3044
3045
3046 static void
3047 update_window_title (ModestMsgViewWindow *window)
3048 {
3049         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
3050         TnyMsg *msg = NULL;
3051         TnyHeader *header = NULL;
3052         gchar *subject = NULL;
3053         
3054         msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
3055
3056         if (msg != NULL) {
3057                 header = tny_msg_get_header (msg);
3058                 subject = tny_header_dup_subject (header);
3059                 g_object_unref (header);
3060                 g_object_unref (msg);
3061         }
3062
3063         if ((subject == NULL)||(subject[0] == '\0')) {
3064                 g_free (subject);
3065                 subject = g_strdup (_("mail_va_no_subject"));
3066         }
3067
3068         gtk_window_set_title (GTK_WINDOW (window), subject);
3069 }
3070
3071
3072 static void on_move_focus (GtkWidget *widget,
3073                            GtkDirectionType direction,
3074                            gpointer userdata)
3075 {
3076         g_signal_stop_emission_by_name (G_OBJECT (widget), "move-focus");
3077 }
3078
3079 static TnyStream *
3080 fetch_image_open_stream (TnyStreamCache *self, gint64 *expected_size, gchar *uri)
3081 {
3082         GnomeVFSResult result;
3083         GnomeVFSHandle *handle = NULL;
3084         GnomeVFSFileInfo *info = NULL;
3085         TnyStream *stream;
3086
3087         result = gnome_vfs_open (&handle, uri, GNOME_VFS_OPEN_READ);
3088         if (result != GNOME_VFS_OK) {
3089                 *expected_size = 0;
3090                 return NULL;
3091         }
3092         
3093         info = gnome_vfs_file_info_new ();
3094         result = gnome_vfs_get_file_info_from_handle (handle, info, GNOME_VFS_FILE_INFO_DEFAULT);
3095         if (result != GNOME_VFS_OK || ! (info->valid_fields & GNOME_VFS_FILE_INFO_FIELDS_SIZE)) {
3096                 /* We put a "safe" default size for going to cache */
3097                 *expected_size = (300*1024);
3098         } else {
3099                 *expected_size = info->size;
3100         }
3101         gnome_vfs_file_info_unref (info);
3102
3103         stream = tny_vfs_stream_new (handle);
3104
3105         return stream;
3106
3107 }
3108
3109 typedef struct {
3110         gchar *uri;
3111         gchar *cache_id;
3112         TnyStream *output_stream;
3113         GtkWidget *msg_view;
3114 } FetchImageData;
3115
3116 gboolean
3117 on_fetch_image_idle_refresh_view (gpointer userdata)
3118 {
3119
3120         FetchImageData *fidata = (FetchImageData *) userdata;
3121         g_message ("REFRESH VIEW");
3122         if (GTK_WIDGET_DRAWABLE (fidata->msg_view)) {
3123                 g_message ("QUEUING DRAW");
3124                 gtk_widget_queue_draw (fidata->msg_view);
3125         }
3126         g_object_unref (fidata->msg_view);
3127         g_slice_free (FetchImageData, fidata);
3128         return FALSE;
3129 }
3130
3131 static gpointer
3132 on_fetch_image_thread (gpointer userdata)
3133 {
3134         FetchImageData *fidata = (FetchImageData *) userdata;
3135         TnyStreamCache *cache;
3136         TnyStream *cache_stream;
3137
3138         cache = modest_runtime_get_images_cache ();
3139         cache_stream = tny_stream_cache_get_stream (cache, fidata->cache_id, (TnyStreamCacheOpenStreamFetcher) fetch_image_open_stream, (gpointer) fidata->uri);
3140         g_free (fidata->cache_id);
3141         g_free (fidata->uri);
3142
3143         if (cache_stream != NULL) {
3144                 tny_stream_write_to_stream (cache_stream, fidata->output_stream);
3145                 tny_stream_close (cache_stream);
3146                 g_object_unref (cache_stream);
3147         }
3148
3149         tny_stream_close (fidata->output_stream);
3150         g_object_unref (fidata->output_stream);
3151
3152
3153         gdk_threads_enter ();
3154         g_idle_add (on_fetch_image_idle_refresh_view, fidata);
3155         gdk_threads_leave ();
3156
3157         return NULL;
3158 }
3159
3160 static gboolean
3161 on_fetch_image (ModestMsgView *msgview,
3162                 const gchar *uri,
3163                 TnyStream *stream,
3164                 ModestMsgViewWindow *window)
3165 {
3166         const gchar *current_account;
3167         ModestMsgViewWindowPrivate *priv;
3168         FetchImageData *fidata;
3169
3170         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
3171
3172         current_account = modest_window_get_active_account (MODEST_WINDOW (window));
3173
3174         fidata = g_slice_new0 (FetchImageData);
3175         fidata->msg_view = g_object_ref (msgview);
3176         fidata->uri = g_strdup (uri);
3177         fidata->cache_id = modest_images_cache_get_id (current_account, uri);
3178         fidata->output_stream = g_object_ref (stream);
3179
3180         if (g_thread_create (on_fetch_image_thread, fidata, FALSE, NULL) == NULL) {
3181                 g_object_unref (fidata->output_stream);
3182                 g_free (fidata->cache_id);
3183                 g_free (fidata->uri);
3184                 g_object_unref (fidata->msg_view);
3185                 g_slice_free (FetchImageData, fidata);
3186                 tny_stream_close (stream);
3187                 return FALSE;
3188         }
3189
3190         return TRUE;;
3191 }