562b989f454666c7dd520f3a2658a9d1fa6b19eb
[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                 modest_msg_view_grab_focus (MODEST_MSG_VIEW (priv->msg_view));
1434         }
1435
1436         /* update the toggle buttons status */
1437         action = gtk_ui_manager_get_action (parent_priv->ui_manager, "/ToolBar/FindInMessage");
1438         modest_utils_toggle_action_set_active_block_notify (GTK_TOGGLE_ACTION (action), is_active);
1439         action = gtk_ui_manager_get_action (parent_priv->ui_manager, "/MenuBar/ToolsMenu/ToolsFindInMessageMenu");
1440         modest_utils_toggle_action_set_active_block_notify (GTK_TOGGLE_ACTION (action), is_active);
1441         
1442 }
1443
1444 static void
1445 modest_msg_view_window_find_toolbar_close (GtkWidget *widget,
1446                                            ModestMsgViewWindow *obj)
1447 {
1448         GtkToggleAction *toggle;
1449         ModestWindowPrivate *parent_priv;
1450         ModestMsgViewWindowPrivate *priv;
1451
1452         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (obj);
1453         parent_priv = MODEST_WINDOW_GET_PRIVATE (obj);
1454         
1455         toggle = GTK_TOGGLE_ACTION (gtk_ui_manager_get_action (parent_priv->ui_manager, "/ToolBar/FindInMessage"));
1456         gtk_toggle_action_set_active (toggle, FALSE);
1457         modest_msg_view_grab_focus (MODEST_MSG_VIEW (priv->msg_view));
1458 }
1459
1460 static void
1461 modest_msg_view_window_find_toolbar_search (GtkWidget *widget,
1462                                            ModestMsgViewWindow *obj)
1463 {
1464         gchar *current_search;
1465         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (obj);
1466
1467         if (modest_mime_part_view_is_empty (MODEST_MIME_PART_VIEW (priv->msg_view))) {
1468                 hildon_banner_show_information (NULL, NULL, _("mail_ib_nothing_to_find"));
1469                 return;
1470         }
1471
1472         g_object_get (G_OBJECT (widget), "prefix", &current_search, NULL);
1473
1474         if ((current_search == NULL) || (strcmp (current_search, "") == 0)) {
1475                 g_free (current_search);
1476                 hildon_banner_show_information (NULL, NULL, dgettext("hildon-common-strings", "ecdg_ib_find_rep_enter_text"));
1477                 return;
1478         }
1479
1480         if ((priv->last_search == NULL) || (strcmp (priv->last_search, current_search) != 0)) {
1481                 gboolean result;
1482                 g_free (priv->last_search);
1483                 priv->last_search = g_strdup (current_search);
1484                 result = modest_isearch_view_search (MODEST_ISEARCH_VIEW (priv->msg_view),
1485                                                      priv->last_search);
1486                 if (!result) {
1487                         hildon_banner_show_information (NULL, NULL, dgettext("hildon-libs", "ckct_ib_find_no_matches"));
1488                         g_free (priv->last_search);
1489                         priv->last_search = NULL;
1490                 } else {
1491                         modest_msg_view_grab_focus (MODEST_MSG_VIEW (priv->msg_view));
1492                         hildon_find_toolbar_highlight_entry (HILDON_FIND_TOOLBAR (priv->find_toolbar), TRUE);
1493                 }
1494         } else {
1495                 if (!modest_isearch_view_search_next (MODEST_ISEARCH_VIEW (priv->msg_view))) {
1496                         hildon_banner_show_information (NULL, NULL, dgettext("hildon-libs", "ckct_ib_find_search_complete"));
1497                         g_free (priv->last_search);
1498                         priv->last_search = NULL;
1499                 } else {
1500                         modest_msg_view_grab_focus (MODEST_MSG_VIEW (priv->msg_view));
1501                         hildon_find_toolbar_highlight_entry (HILDON_FIND_TOOLBAR (priv->find_toolbar), TRUE);
1502                 }
1503         }
1504         
1505         g_free (current_search);
1506                 
1507 }
1508
1509 static void
1510 modest_msg_view_window_set_zoom (ModestWindow *window,
1511                                  gdouble zoom)
1512 {
1513         ModestMsgViewWindowPrivate *priv;
1514         ModestWindowPrivate *parent_priv;
1515         GtkAction *action = NULL;
1516         gint int_zoom = (gint) rint (zoom*100.0+0.1);
1517      
1518         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
1519
1520         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1521         parent_priv = MODEST_WINDOW_GET_PRIVATE (window);
1522         modest_zoomable_set_zoom (MODEST_ZOOMABLE (priv->msg_view), zoom);
1523
1524         action = gtk_ui_manager_get_action (parent_priv->ui_manager, 
1525                                             "/MenuBar/ViewMenu/ZoomMenu/Zoom50Menu");
1526
1527         gtk_radio_action_set_current_value (GTK_RADIO_ACTION (action), int_zoom);
1528 }
1529
1530 static gdouble
1531 modest_msg_view_window_get_zoom (ModestWindow *window)
1532 {
1533         ModestMsgViewWindowPrivate *priv;
1534      
1535         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), 1.0);
1536
1537         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1538         return modest_zoomable_get_zoom (MODEST_ZOOMABLE (priv->msg_view));
1539 }
1540
1541 static gboolean
1542 modest_msg_view_window_zoom_plus (ModestWindow *window)
1543 {
1544         ModestWindowPrivate *parent_priv;
1545         GtkRadioAction *zoom_radio_action;
1546         GSList *group, *node;
1547
1548         parent_priv = MODEST_WINDOW_GET_PRIVATE (window);
1549         zoom_radio_action = GTK_RADIO_ACTION (gtk_ui_manager_get_action (parent_priv->ui_manager, 
1550                                                                          "/MenuBar/ViewMenu/ZoomMenu/Zoom50Menu"));
1551
1552         group = gtk_radio_action_get_group (zoom_radio_action);
1553
1554         if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (group->data))) {
1555                 hildon_banner_show_information (NULL, NULL, dgettext("hildon-common-strings", "ckct_ib_max_zoom_level_reached"));
1556                 return FALSE;
1557         }
1558
1559         for (node = group; node != NULL; node = g_slist_next (node)) {
1560                 if ((node->next != NULL) && gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (node->next->data))) {
1561                         gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (node->data), TRUE);
1562                         return TRUE;
1563                 }
1564         }
1565         return FALSE;
1566 }
1567
1568 static gboolean
1569 modest_msg_view_window_zoom_minus (ModestWindow *window)
1570 {
1571         ModestWindowPrivate *parent_priv;
1572         GtkRadioAction *zoom_radio_action;
1573         GSList *group, *node;
1574
1575         parent_priv = MODEST_WINDOW_GET_PRIVATE (window);
1576         zoom_radio_action = GTK_RADIO_ACTION (gtk_ui_manager_get_action (parent_priv->ui_manager, 
1577                                                                          "/MenuBar/ViewMenu/ZoomMenu/Zoom50Menu"));
1578
1579         group = gtk_radio_action_get_group (zoom_radio_action);
1580
1581         for (node = group; node != NULL; node = g_slist_next (node)) {
1582                 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (node->data))) {
1583                         if (node->next != NULL) {
1584                                 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (node->next->data), TRUE);
1585                                 return TRUE;
1586                         } else {
1587                           hildon_banner_show_information (NULL, NULL, dgettext("hildon-common-strings", "ckct_ib_min_zoom_level_reached"));
1588                                 return FALSE;
1589                         }
1590                         break;
1591                 }
1592         }
1593         return FALSE;
1594 }
1595
1596 static gboolean
1597 modest_msg_view_window_key_event (GtkWidget *window,
1598                                   GdkEventKey *event,
1599                                   gpointer userdata)
1600 {
1601         GtkWidget *focus;
1602
1603         focus = gtk_window_get_focus (GTK_WINDOW (window));
1604
1605         /* for the find toolbar case */
1606         if (focus && GTK_IS_ENTRY (focus)) {
1607                 if (event->keyval == GDK_BackSpace) {
1608                         GdkEvent *copy;
1609                         copy = gdk_event_copy ((GdkEvent *) event);
1610                         gtk_widget_event (focus, copy);
1611                         gdk_event_free (copy);
1612                         return TRUE;
1613                 } else 
1614                         return FALSE;
1615         }
1616         if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up ||
1617             event->keyval == GDK_Down || event->keyval == GDK_KP_Down ||
1618             event->keyval == GDK_Page_Up || event->keyval == GDK_KP_Page_Up ||
1619             event->keyval == GDK_Page_Down || event->keyval == GDK_KP_Page_Down ||
1620             event->keyval == GDK_Home || event->keyval == GDK_KP_Home ||
1621             event->keyval == GDK_End || event->keyval == GDK_KP_End) {
1622                 /* ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window); */
1623                 /* gboolean return_value; */
1624
1625                 if (event->type == GDK_KEY_PRESS) {
1626                         GtkScrollType scroll_type;
1627                         
1628                         switch (event->keyval) {
1629                         case GDK_Up: 
1630                         case GDK_KP_Up:
1631                                 scroll_type = GTK_SCROLL_STEP_UP; break;
1632                         case GDK_Down: 
1633                         case GDK_KP_Down:
1634                                 scroll_type = GTK_SCROLL_STEP_DOWN; break;
1635                         case GDK_Page_Up:
1636                         case GDK_KP_Page_Up:
1637                                 scroll_type = GTK_SCROLL_PAGE_UP; break;
1638                         case GDK_Page_Down:
1639                         case GDK_KP_Page_Down:
1640                                 scroll_type = GTK_SCROLL_PAGE_DOWN; break;
1641                         case GDK_Home:
1642                         case GDK_KP_Home:
1643                                 scroll_type = GTK_SCROLL_START; break;
1644                         case GDK_End:
1645                         case GDK_KP_End:
1646                                 scroll_type = GTK_SCROLL_END; break;
1647                         default: scroll_type = GTK_SCROLL_NONE;
1648                         }
1649                         
1650                         /* g_signal_emit_by_name (G_OBJECT (priv->main_scroll), "scroll-child",  */
1651                         /*                     scroll_type, FALSE, &return_value); */
1652                         return FALSE;
1653                 } else {
1654                         return FALSE;
1655                 }
1656         } else {
1657                 return FALSE;
1658         }
1659 }
1660
1661 gboolean
1662 modest_msg_view_window_last_message_selected (ModestMsgViewWindow *window)
1663 {
1664         GtkTreePath *path;
1665         ModestMsgViewWindowPrivate *priv;
1666         GtkTreeIter tmp_iter;
1667         gboolean is_last_selected;
1668
1669         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), TRUE);
1670         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1671
1672         /*if no model (so no rows at all), then virtually we are the last*/
1673         if (!priv->header_model || !priv->row_reference)
1674                 return TRUE;
1675
1676         if (!gtk_tree_row_reference_valid (priv->row_reference))
1677                 return TRUE;
1678
1679         path = gtk_tree_row_reference_get_path (priv->row_reference);
1680         if (path == NULL)
1681                 return TRUE;
1682
1683         is_last_selected = TRUE;
1684         while (is_last_selected) {
1685                 TnyHeader *header;
1686                 gtk_tree_path_next (path);
1687                 if (!gtk_tree_model_get_iter (priv->header_model, &tmp_iter, path))
1688                         break;
1689                 gtk_tree_model_get (priv->header_model, &tmp_iter,
1690                                 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1691                                 &header, -1);
1692                 if (header) {
1693                         if (msg_is_visible (header, priv->is_outbox))
1694                                 is_last_selected = FALSE;
1695                         g_object_unref(G_OBJECT(header));
1696                 }
1697         }
1698         gtk_tree_path_free (path);
1699         return is_last_selected;
1700 }
1701
1702 gboolean
1703 modest_msg_view_window_has_headers_model (ModestMsgViewWindow *window)
1704 {
1705         ModestMsgViewWindowPrivate *priv;
1706
1707         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), TRUE);
1708         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1709
1710         return priv->header_model != NULL;
1711 }
1712
1713 gboolean
1714 modest_msg_view_window_is_search_result (ModestMsgViewWindow *window)
1715 {
1716         ModestMsgViewWindowPrivate *priv;
1717
1718         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), TRUE);
1719         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1720
1721         return priv->is_search_result;
1722 }
1723
1724 static gboolean
1725 msg_is_visible (TnyHeader *header, gboolean check_outbox)
1726 {
1727         if ((tny_header_get_flags(header) & TNY_HEADER_FLAG_DELETED))
1728                 return FALSE;
1729         if (!check_outbox) {
1730                 return TRUE;
1731         } else {
1732                 ModestTnySendQueueStatus status;
1733                 status = modest_tny_all_send_queues_get_msg_status (header);
1734                 return ((status != MODEST_TNY_SEND_QUEUE_FAILED) &&
1735                         (status != MODEST_TNY_SEND_QUEUE_SENDING));
1736         }
1737 }
1738
1739 gboolean
1740 modest_msg_view_window_first_message_selected (ModestMsgViewWindow *window)
1741 {
1742         GtkTreePath *path;
1743         ModestMsgViewWindowPrivate *priv;
1744         gboolean is_first_selected;
1745         GtkTreeIter tmp_iter;
1746
1747         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), TRUE);
1748         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1749
1750         /*if no model (so no rows at all), then virtually we are the first*/
1751         if (!priv->header_model || !priv->row_reference)
1752                 return TRUE;
1753
1754         if (!gtk_tree_row_reference_valid (priv->row_reference))
1755                 return TRUE;
1756
1757         path = gtk_tree_row_reference_get_path (priv->row_reference);
1758         if (!path)
1759                 return TRUE;
1760
1761         is_first_selected = TRUE;
1762         while (is_first_selected) {
1763                 TnyHeader *header;
1764                 if(!gtk_tree_path_prev (path))
1765                         break;
1766                 /* Here the 'if' is needless for logic, but let make sure
1767                  * iter is valid for gtk_tree_model_get. */
1768                 if (!gtk_tree_model_get_iter (priv->header_model, &tmp_iter, path))
1769                         break;
1770                 gtk_tree_model_get (priv->header_model, &tmp_iter,
1771                                 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1772                                 &header, -1);
1773                 if (header) {
1774                         if (msg_is_visible (header, priv->is_outbox))
1775                                 is_first_selected = FALSE;
1776                         g_object_unref(G_OBJECT(header));
1777                 }
1778         }
1779         gtk_tree_path_free (path);
1780         return is_first_selected;
1781 }
1782
1783 typedef struct {
1784         TnyHeader *header;
1785         GtkTreeRowReference *row_reference;
1786 } MsgReaderInfo;
1787
1788 static void
1789 message_reader_performer (gboolean canceled, 
1790                           GError *err,
1791                           GtkWindow *parent_window, 
1792                           TnyAccount *account, 
1793                           gpointer user_data)
1794 {
1795         ModestMailOperation *mail_op = NULL;
1796         MsgReaderInfo *info;
1797
1798         info = (MsgReaderInfo *) user_data;
1799         if (canceled || err) {
1800                 goto frees;
1801         }
1802
1803         /* Register the header - it'll be unregistered in the callback */
1804         modest_window_mgr_register_header (modest_runtime_get_window_mgr (), info->header, NULL);
1805
1806         /* New mail operation */
1807         mail_op = modest_mail_operation_new_with_error_handling (G_OBJECT(parent_window),
1808                                                                  modest_ui_actions_disk_operations_error_handler, 
1809                                                                  NULL, NULL);
1810                                 
1811         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (), mail_op);
1812         modest_mail_operation_get_msg (mail_op, info->header, TRUE, view_msg_cb, info->row_reference);
1813         g_object_unref (mail_op);
1814
1815         /* Update dimming rules */
1816         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (parent_window));
1817         modest_ui_actions_check_menu_dimming_rules (MODEST_WINDOW (parent_window));
1818
1819  frees:
1820         /* Frees. The row_reference will be freed by the view_msg_cb callback */
1821         g_object_unref (info->header);
1822         g_slice_free (MsgReaderInfo, info);
1823 }
1824
1825
1826 /**
1827  * Reads the message whose summary item is @header. It takes care of
1828  * several things, among others:
1829  *
1830  * If the message was not previously downloaded then ask the user
1831  * before downloading. If there is no connection launch the connection
1832  * dialog. Update toolbar dimming rules.
1833  *
1834  * Returns: TRUE if the mail operation was started, otherwise if the
1835  * user do not want to download the message, or if the user do not
1836  * want to connect, then the operation is not issued
1837  **/
1838 static gboolean
1839 message_reader (ModestMsgViewWindow *window,
1840                 ModestMsgViewWindowPrivate *priv,
1841                 TnyHeader *header,
1842                 GtkTreeRowReference *row_reference)
1843 {
1844         ModestWindowMgr *mgr;
1845         TnyAccount *account;
1846         TnyFolder *folder;
1847         MsgReaderInfo *info;
1848
1849         g_return_val_if_fail (row_reference != NULL, FALSE);
1850
1851         mgr = modest_runtime_get_window_mgr ();
1852         /* Msg download completed */
1853         if (!(tny_header_get_flags (header) & TNY_HEADER_FLAG_CACHED)) {
1854                 /* Ask the user if he wants to download the message if
1855                    we're not online */
1856                 if (!tny_device_is_online (modest_runtime_get_device())) {
1857                         GtkResponseType response;
1858
1859                         response = modest_platform_run_confirmation_dialog (GTK_WINDOW (window),
1860                                                                             _("mcen_nc_get_msg"));
1861                         if (response == GTK_RESPONSE_CANCEL)
1862                                 return FALSE;
1863                 
1864                         folder = tny_header_get_folder (header);
1865                         info = g_slice_new (MsgReaderInfo);
1866                         info->header = g_object_ref (header);
1867                         info->row_reference = gtk_tree_row_reference_copy (row_reference);
1868
1869                         /* Offer the connection dialog if necessary */
1870                         modest_platform_connect_if_remote_and_perform ((GtkWindow *) window, 
1871                                                                        TRUE,
1872                                                                        TNY_FOLDER_STORE (folder),
1873                                                                        message_reader_performer, 
1874                                                                        info);
1875                         g_object_unref (folder);
1876                         return TRUE;
1877                 }
1878         }
1879         
1880         folder = tny_header_get_folder (header);
1881         account = tny_folder_get_account (folder);
1882         info = g_slice_new (MsgReaderInfo);
1883         info->header = g_object_ref (header);
1884         info->row_reference = gtk_tree_row_reference_copy (row_reference);
1885         
1886         message_reader_performer (FALSE, NULL, (GtkWindow *) window, account, info);
1887         g_object_unref (account);
1888         g_object_unref (folder);
1889
1890         return TRUE;
1891 }
1892
1893 gboolean        
1894 modest_msg_view_window_select_next_message (ModestMsgViewWindow *window)
1895 {
1896         ModestMsgViewWindowPrivate *priv;
1897         GtkTreePath *path= NULL;
1898         GtkTreeIter tmp_iter;
1899         TnyHeader *header;
1900         gboolean retval = TRUE;
1901         GtkTreeRowReference *row_reference = NULL;
1902
1903         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), FALSE);
1904         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1905
1906         if (!priv->row_reference)
1907                 return FALSE;
1908
1909         /* Update the next row reference if it's not valid. This could
1910            happen if for example the header which it was pointing to,
1911            was deleted. The best place to do it is in the row-deleted
1912            handler but the tinymail model do not work like the glib
1913            tree models and reports the deletion when the row is still
1914            there */
1915         if (!gtk_tree_row_reference_valid (priv->next_row_reference)) {
1916                 if (gtk_tree_row_reference_valid (priv->row_reference)) {
1917                         priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
1918                         select_next_valid_row (priv->header_model, &(priv->next_row_reference), FALSE, priv->is_outbox);
1919                 }
1920         }
1921         if (priv->next_row_reference)
1922                 path = gtk_tree_row_reference_get_path (priv->next_row_reference);
1923         if (path == NULL)
1924                 return FALSE;
1925
1926         row_reference = gtk_tree_row_reference_copy (priv->next_row_reference);
1927
1928         gtk_tree_model_get_iter (priv->header_model,
1929                                  &tmp_iter,
1930                                  path);
1931         gtk_tree_path_free (path);
1932
1933         gtk_tree_model_get (priv->header_model, &tmp_iter, 
1934                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1935                             &header, -1);
1936         
1937         /* Read the message & show it */
1938         if (!message_reader (window, priv, header, row_reference)) {
1939                 retval = FALSE;
1940         }
1941         gtk_tree_row_reference_free (row_reference);
1942
1943         /* Free */
1944         g_object_unref (header);
1945
1946         return retval;
1947 }
1948
1949 gboolean        
1950 modest_msg_view_window_select_previous_message (ModestMsgViewWindow *window)
1951 {
1952         ModestMsgViewWindowPrivate *priv = NULL;
1953         GtkTreePath *path;
1954         gboolean finished = FALSE;
1955         gboolean retval = FALSE;
1956
1957         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), FALSE);
1958         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1959
1960         /* Return inmediatly if there is no header model */
1961         if (!priv->header_model || !priv->row_reference)
1962                 return FALSE;
1963
1964         path = gtk_tree_row_reference_get_path (priv->row_reference);
1965         while (!finished && gtk_tree_path_prev (path)) {
1966                 TnyHeader *header;
1967                 GtkTreeIter iter;
1968
1969                 gtk_tree_model_get_iter (priv->header_model, &iter, path);
1970                 gtk_tree_model_get (priv->header_model, &iter, 
1971                                     TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1972                                     &header, -1);
1973                 finished = TRUE;
1974                 if (header) {
1975                         if (msg_is_visible (header, priv->is_outbox)) {
1976                                 GtkTreeRowReference *row_reference;
1977                                 row_reference = gtk_tree_row_reference_new (priv->header_model, path);
1978                                 /* Read the message & show it */
1979                                 retval = message_reader (window, priv, header, row_reference);
1980                                 gtk_tree_row_reference_free (row_reference);
1981                         } else {
1982                                 finished = FALSE;
1983                         }
1984                         g_object_unref (header);
1985                 }
1986         }
1987
1988         gtk_tree_path_free (path);
1989         return retval;
1990 }
1991
1992 static void
1993 view_msg_cb (ModestMailOperation *mail_op, 
1994              TnyHeader *header, 
1995              gboolean canceled,
1996              TnyMsg *msg, 
1997              GError *error,
1998              gpointer user_data)
1999 {
2000         ModestMsgViewWindow *self = NULL;
2001         ModestMsgViewWindowPrivate *priv = NULL;
2002         GtkTreeRowReference *row_reference = NULL;
2003
2004         /* Unregister the header (it was registered before creating the mail operation) */
2005         modest_window_mgr_unregister_header (modest_runtime_get_window_mgr (), header);
2006
2007         row_reference = (GtkTreeRowReference *) user_data;
2008         if (canceled) {
2009                 gtk_tree_row_reference_free (row_reference);
2010                 return;
2011         }
2012         
2013         /* If there was any error */
2014         if (!modest_ui_actions_msg_retrieval_check (mail_op, header, msg)) {
2015                 gtk_tree_row_reference_free (row_reference);                    
2016                 return;
2017         }
2018
2019         /* Get the window */ 
2020         self = (ModestMsgViewWindow *) modest_mail_operation_get_source (mail_op);
2021         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self));
2022         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2023
2024         /* Update the row reference */
2025         if (priv->row_reference != NULL) {
2026                 gtk_tree_row_reference_free (priv->row_reference);
2027                 priv->row_reference = gtk_tree_row_reference_copy (row_reference);
2028                 if (priv->next_row_reference != NULL) {
2029                         gtk_tree_row_reference_free (priv->next_row_reference);
2030                 }
2031                 priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
2032                 select_next_valid_row (priv->header_model, &(priv->next_row_reference), TRUE, priv->is_outbox);
2033         }
2034
2035         /* Mark header as read */
2036         if (!(tny_header_get_flags (header) & TNY_HEADER_FLAG_SEEN))
2037                 tny_header_set_flag (header, TNY_HEADER_FLAG_SEEN);
2038
2039         /* Set new message */
2040         if (priv->msg_view != NULL && TNY_IS_MSG_VIEW (priv->msg_view)) {
2041                 tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
2042                 modest_msg_view_window_update_priority (self);
2043                 update_window_title (MODEST_MSG_VIEW_WINDOW (self));
2044                 modest_msg_view_grab_focus (MODEST_MSG_VIEW (priv->msg_view));
2045         }
2046
2047         /* Set the new message uid of the window  */
2048         if (priv->msg_uid) {
2049                 g_free (priv->msg_uid);
2050                 priv->msg_uid = modest_tny_folder_get_header_unique_id (header);
2051         }
2052
2053         /* Notify the observers */
2054         g_signal_emit (G_OBJECT (self), signals[MSG_CHANGED_SIGNAL], 
2055                        0, priv->header_model, priv->row_reference);
2056
2057         /* Frees */
2058         g_object_unref (self);
2059         gtk_tree_row_reference_free (row_reference);            
2060 }
2061
2062 TnyFolderType
2063 modest_msg_view_window_get_folder_type (ModestMsgViewWindow *window)
2064 {
2065         ModestMsgViewWindowPrivate *priv;
2066         TnyMsg *msg;
2067         TnyFolderType folder_type;
2068
2069         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2070
2071         folder_type = TNY_FOLDER_TYPE_UNKNOWN;
2072
2073         msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
2074         if (msg) {
2075                 TnyFolder *folder;
2076
2077                 folder = tny_msg_get_folder (msg);
2078                 if (folder) {
2079                         folder_type = modest_tny_folder_guess_folder_type (folder);
2080                         g_object_unref (folder);
2081                 }
2082                 g_object_unref (msg);
2083         }
2084
2085         return folder_type;
2086 }
2087
2088
2089 static void
2090 modest_msg_view_window_update_priority (ModestMsgViewWindow *window)
2091 {
2092         ModestMsgViewWindowPrivate *priv;
2093         TnyHeader *header = NULL;
2094         TnyHeaderFlags flags = 0;
2095
2096         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2097
2098         if (priv->header_model && priv->row_reference) {
2099                 GtkTreeIter iter;
2100                 GtkTreePath *path = NULL;
2101
2102                 path = gtk_tree_row_reference_get_path (priv->row_reference);
2103                 g_return_if_fail (path != NULL);
2104                 gtk_tree_model_get_iter (priv->header_model, 
2105                                          &iter, 
2106                                          gtk_tree_row_reference_get_path (priv->row_reference));
2107
2108                 gtk_tree_model_get (priv->header_model, &iter, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
2109                                     &header, -1);
2110                 gtk_tree_path_free (path);
2111         } else {
2112                 TnyMsg *msg;
2113                 msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
2114                 if (msg) {
2115                         header = tny_msg_get_header (msg);
2116                         g_object_unref (msg);
2117                 }
2118         }
2119
2120         if (header) {
2121                 flags = tny_header_get_flags (header);
2122                 g_object_unref(G_OBJECT(header));
2123         }
2124
2125         modest_msg_view_set_priority (MODEST_MSG_VIEW(priv->msg_view), flags);
2126
2127 }
2128
2129 static void
2130 toolbar_resize (ModestMsgViewWindow *self)
2131 {
2132         ModestMsgViewWindowPrivate *priv = NULL;
2133         ModestWindowPrivate *parent_priv = NULL;
2134         GtkWidget *widget;
2135         gint static_button_size;
2136         ModestWindowMgr *mgr;
2137
2138         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self));
2139         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2140         parent_priv = MODEST_WINDOW_GET_PRIVATE(self);
2141
2142         mgr = modest_runtime_get_window_mgr ();
2143         static_button_size = modest_window_mgr_get_fullscreen_mode (mgr)?118:108;
2144
2145         if (parent_priv->toolbar) {
2146                 /* left size buttons */
2147                 widget = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageReply");
2148                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (widget), FALSE);
2149                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (widget), FALSE);
2150                 gtk_widget_set_size_request (GTK_WIDGET (widget), static_button_size, -1);
2151                 widget = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageMoveTo");
2152                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (widget), FALSE);
2153                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (widget), FALSE);
2154                 gtk_widget_set_size_request (GTK_WIDGET (widget), static_button_size, -1);
2155                 widget = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarDeleteMessage");
2156                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (widget), FALSE);
2157                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (widget), FALSE);
2158                 gtk_widget_set_size_request (GTK_WIDGET (widget), static_button_size, -1);
2159                 widget = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/FindInMessage");
2160                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (widget), FALSE);
2161                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (widget), FALSE);
2162                 gtk_widget_set_size_request (GTK_WIDGET (widget), static_button_size, -1);
2163                 
2164                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (priv->progress_toolitem), FALSE);
2165                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->progress_toolitem), TRUE);
2166                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (priv->cancel_toolitem), FALSE);
2167                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->cancel_toolitem), FALSE);
2168                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (priv->next_toolitem), TRUE);
2169                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->next_toolitem), TRUE);
2170                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (priv->prev_toolitem), TRUE);
2171                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->prev_toolitem), TRUE);
2172         }
2173                 
2174 }
2175
2176 static gboolean
2177 modest_msg_view_window_window_state_event (GtkWidget *widget, GdkEventWindowState *event, gpointer userdata)
2178 {
2179         if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) {
2180                 ModestWindowPrivate *parent_priv;
2181                 ModestWindowMgr *mgr;
2182                 gboolean is_fullscreen;
2183                 GtkAction *fs_toggle_action;
2184                 gboolean active;
2185
2186                 mgr = modest_runtime_get_window_mgr ();
2187                 is_fullscreen = (modest_window_mgr_get_fullscreen_mode (mgr))?1:0;
2188
2189                 parent_priv = MODEST_WINDOW_GET_PRIVATE (widget);
2190                 
2191                 fs_toggle_action = gtk_ui_manager_get_action (parent_priv->ui_manager, "/MenuBar/ViewMenu/ViewToggleFullscreenMenu");
2192                 active = (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (fs_toggle_action)))?1:0;
2193                 if (is_fullscreen != active) {
2194                         gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (fs_toggle_action), is_fullscreen);
2195                 }
2196                 toolbar_resize (MODEST_MSG_VIEW_WINDOW (widget));
2197         }
2198
2199         return FALSE;
2200
2201 }
2202
2203 static void
2204 modest_msg_view_window_show_toolbar (ModestWindow *self,
2205                                      gboolean show_toolbar)
2206 {
2207         ModestMsgViewWindowPrivate *priv = NULL;
2208         ModestWindowPrivate *parent_priv;
2209         GtkWidget *reply_button = NULL, *menu = NULL;
2210         GtkWidget *placeholder = NULL;
2211         gint insert_index;
2212         const gchar *action_name;
2213         GtkAction *action;
2214         
2215         parent_priv = MODEST_WINDOW_GET_PRIVATE(self);
2216         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2217
2218         /* Set optimized view status */
2219         priv->optimized_view = !show_toolbar;
2220
2221         if (!parent_priv->toolbar) {
2222                 parent_priv->toolbar = gtk_ui_manager_get_widget (parent_priv->ui_manager, 
2223                                                                   "/ToolBar");
2224                 gtk_widget_set_no_show_all (parent_priv->toolbar, TRUE);
2225
2226                 priv->progress_toolitem = GTK_WIDGET (gtk_tool_item_new ());
2227                 priv->cancel_toolitem = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarCancel");
2228                 priv->next_toolitem = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageNext");
2229                 priv->prev_toolitem = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageBack");
2230                 toolbar_resize (MODEST_MSG_VIEW_WINDOW (self));
2231
2232                 /* Add ProgressBar (Transfer toolbar) */ 
2233                 priv->progress_bar = modest_progress_bar_new ();
2234                 gtk_widget_set_no_show_all (priv->progress_bar, TRUE);
2235                 placeholder = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ProgressbarView");
2236                 insert_index = gtk_toolbar_get_item_index(GTK_TOOLBAR (parent_priv->toolbar), GTK_TOOL_ITEM(placeholder));
2237                 gtk_container_add (GTK_CONTAINER (priv->progress_toolitem), priv->progress_bar);
2238                 gtk_toolbar_insert(GTK_TOOLBAR(parent_priv->toolbar), GTK_TOOL_ITEM (priv->progress_toolitem), insert_index);
2239                 
2240                 /* Connect cancel 'clicked' signal to abort progress mode */
2241                 g_signal_connect(priv->cancel_toolitem, "clicked",
2242                                  G_CALLBACK(cancel_progressbar),
2243                                  self);
2244                 
2245                 /* Add it to the observers list */
2246                 priv->progress_widgets = g_slist_prepend(priv->progress_widgets, priv->progress_bar);
2247
2248                 /* Add to window */
2249                 hildon_window_add_toolbar (HILDON_WINDOW (self), 
2250                                            GTK_TOOLBAR (parent_priv->toolbar));
2251
2252                 /* Set reply button tap and hold menu */        
2253                 reply_button = gtk_ui_manager_get_widget (parent_priv->ui_manager, 
2254                                                           "/ToolBar/ToolbarMessageReply");
2255                 menu = gtk_ui_manager_get_widget (parent_priv->ui_manager, 
2256                                                   "/ToolbarReplyCSM");
2257                 gtk_widget_tap_and_hold_setup (GTK_WIDGET (reply_button), menu, NULL, 0);
2258         }
2259
2260         if (show_toolbar) {
2261                 /* Quick hack: this prevents toolbar icons "dance" when progress bar show status is changed */ 
2262                 /* TODO: resize mode migth be GTK_RESIZE_QUEUE, in order to avoid unneccesary shows */
2263                 gtk_container_set_resize_mode (GTK_CONTAINER(parent_priv->toolbar), GTK_RESIZE_IMMEDIATE);
2264
2265                 gtk_widget_show (GTK_WIDGET (parent_priv->toolbar));
2266                 if (modest_msg_view_window_transfer_mode_enabled (MODEST_MSG_VIEW_WINDOW (self))) 
2267                         set_toolbar_mode (MODEST_MSG_VIEW_WINDOW (self), TOOLBAR_MODE_TRANSFER);
2268                 else
2269                         set_toolbar_mode (MODEST_MSG_VIEW_WINDOW (self), TOOLBAR_MODE_NORMAL);
2270
2271         } else {
2272                 gtk_widget_set_no_show_all (parent_priv->toolbar, TRUE);
2273                 gtk_widget_hide (GTK_WIDGET (parent_priv->toolbar));
2274         }
2275
2276         /* Update also the actions (to update the toggles in the
2277            menus), we have to do it manually because some other window
2278            of the same time could have changed it (remember that the
2279            toolbar fullscreen mode is shared by all the windows of the
2280            same type */
2281         if (modest_window_mgr_get_fullscreen_mode (modest_runtime_get_window_mgr ()))
2282                 action_name = "/MenuBar/ViewMenu/ViewShowToolbarMenu/ViewShowToolbarFullScreenMenu";
2283         else
2284                 action_name = "/MenuBar/ViewMenu/ViewShowToolbarMenu/ViewShowToolbarNormalScreenMenu";
2285
2286         action = gtk_ui_manager_get_action (parent_priv->ui_manager, action_name);
2287         modest_utils_toggle_action_set_active_block_notify (GTK_TOGGLE_ACTION (action),
2288                                                             show_toolbar);
2289 }
2290
2291 static void 
2292 modest_msg_view_window_clipboard_owner_change (GtkClipboard *clipboard,
2293                                                GdkEvent *event,
2294                                                ModestMsgViewWindow *window)
2295 {
2296         if (!GTK_WIDGET_VISIBLE (window))
2297                 return;
2298
2299         modest_window_check_dimming_rules_group (MODEST_WINDOW (window), MODEST_DIMMING_RULES_CLIPBOARD);
2300 }
2301
2302 gboolean 
2303 modest_msg_view_window_transfer_mode_enabled (ModestMsgViewWindow *self)
2304 {
2305         ModestMsgViewWindowPrivate *priv;
2306         
2307         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), FALSE); 
2308         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2309
2310         return priv->current_toolbar_mode == TOOLBAR_MODE_TRANSFER;
2311 }
2312
2313 static void
2314 cancel_progressbar (GtkToolButton *toolbutton,
2315                     ModestMsgViewWindow *self)
2316 {
2317         GSList *tmp;
2318         ModestMsgViewWindowPrivate *priv;
2319         
2320         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2321
2322         /* Get operation observers and cancel its current operation */
2323         tmp = priv->progress_widgets;
2324         while (tmp) {
2325                 modest_progress_object_cancel_current_operation (MODEST_PROGRESS_OBJECT(tmp->data));
2326                 tmp=g_slist_next(tmp);
2327         }
2328 }
2329 static gboolean
2330 observers_empty (ModestMsgViewWindow *self)
2331 {
2332         GSList *tmp = NULL;
2333         ModestMsgViewWindowPrivate *priv;
2334         gboolean is_empty = TRUE;
2335         guint pending_ops = 0;
2336  
2337         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2338         tmp = priv->progress_widgets;
2339
2340         /* Check all observers */
2341         while (tmp && is_empty)  {
2342                 pending_ops = modest_progress_object_num_pending_operations (MODEST_PROGRESS_OBJECT(tmp->data));
2343                 is_empty = pending_ops == 0;
2344                 
2345                 tmp = g_slist_next(tmp);
2346         }
2347         
2348         return is_empty;
2349 }
2350
2351 static void
2352 on_account_removed (TnyAccountStore *account_store, 
2353                     TnyAccount *account,
2354                     gpointer user_data)
2355 {
2356         /* Do nothing if it's a transport account, because we only
2357            show the messages of a store account */
2358         if (tny_account_get_account_type(account) == TNY_ACCOUNT_TYPE_STORE) {
2359                 const gchar *parent_acc = NULL;
2360                 const gchar *our_acc = NULL;
2361
2362                 our_acc = modest_window_get_active_account (MODEST_WINDOW (user_data));
2363                 parent_acc = modest_tny_account_get_parent_modest_account_name_for_server_account (account);
2364
2365                 /* Close this window if I'm showing a message of the removed account */
2366                 if (strcmp (parent_acc, our_acc) == 0)
2367                         modest_ui_actions_on_close_window (NULL, MODEST_WINDOW (user_data));
2368         }
2369 }
2370
2371 static void 
2372 on_mail_operation_started (ModestMailOperation *mail_op,
2373                            gpointer user_data)
2374 {
2375         ModestMsgViewWindow *self;
2376         ModestMailOperationTypeOperation op_type;
2377         GSList *tmp;
2378         ModestMsgViewWindowPrivate *priv;
2379         GObject *source = NULL;
2380
2381         self = MODEST_MSG_VIEW_WINDOW (user_data);
2382         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2383         op_type = modest_mail_operation_get_type_operation (mail_op);
2384         tmp = priv->progress_widgets;
2385         source = modest_mail_operation_get_source(mail_op);
2386         if (G_OBJECT (self) == source) {
2387                 if (op_type == MODEST_MAIL_OPERATION_TYPE_RECEIVE ) {
2388                         set_toolbar_transfer_mode(self);
2389                         while (tmp) {
2390                                 modest_progress_object_add_operation (
2391                                                 MODEST_PROGRESS_OBJECT (tmp->data),
2392                                                 mail_op);
2393                                 tmp = g_slist_next (tmp);
2394                         }
2395                 }
2396         }
2397         g_object_unref (source);
2398 }
2399
2400 static void 
2401 on_mail_operation_finished (ModestMailOperation *mail_op,
2402                             gpointer user_data)
2403 {
2404         ModestMsgViewWindow *self;
2405         ModestMailOperationTypeOperation op_type;
2406         GSList *tmp;
2407         ModestMsgViewWindowPrivate *priv;
2408         
2409         self = MODEST_MSG_VIEW_WINDOW (user_data);
2410         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2411         op_type = modest_mail_operation_get_type_operation (mail_op);
2412         tmp = priv->progress_widgets;
2413         
2414         if (op_type == MODEST_MAIL_OPERATION_TYPE_RECEIVE ) {
2415                 while (tmp) {
2416                         modest_progress_object_remove_operation (MODEST_PROGRESS_OBJECT (tmp->data),
2417                                                                  mail_op);
2418                         tmp = g_slist_next (tmp);
2419                 }
2420
2421                 /* If no more operations are being observed, NORMAL mode is enabled again */
2422                 if (observers_empty (self)) {
2423                         set_toolbar_mode (self, TOOLBAR_MODE_NORMAL);
2424                 }
2425
2426                 /* Update dimming rules. We have to do this right here
2427                    and not in view_msg_cb because at that point the
2428                    transfer mode is still enabled so the dimming rule
2429                    won't let the user delete the message that has been
2430                    readed for example */
2431                 modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (self));
2432                 modest_ui_actions_check_menu_dimming_rules (MODEST_WINDOW (self));
2433         }
2434 }
2435
2436 static void
2437 on_queue_changed (ModestMailOperationQueue *queue,
2438                   ModestMailOperation *mail_op,
2439                   ModestMailOperationQueueNotification type,
2440                   ModestMsgViewWindow *self)
2441 {       
2442         ModestMsgViewWindowPrivate *priv;
2443
2444         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2445
2446         /* If this operations was created by another window, do nothing */
2447         if (!modest_mail_operation_is_mine (mail_op, G_OBJECT(self))) 
2448             return;
2449
2450         if (type == MODEST_MAIL_OPERATION_QUEUE_OPERATION_ADDED) {
2451                 priv->sighandlers = modest_signal_mgr_connect (priv->sighandlers,
2452                                                                G_OBJECT (mail_op),
2453                                                                "operation-started",
2454                                                                G_CALLBACK (on_mail_operation_started),
2455                                                                self);
2456                 priv->sighandlers = modest_signal_mgr_connect (priv->sighandlers,
2457                                                                G_OBJECT (mail_op),
2458                                                                "operation-finished",
2459                                                                G_CALLBACK (on_mail_operation_finished),
2460                                                                self);
2461         } else if (type == MODEST_MAIL_OPERATION_QUEUE_OPERATION_REMOVED) {
2462                 priv->sighandlers = modest_signal_mgr_disconnect (priv->sighandlers,
2463                                                                   G_OBJECT (mail_op),
2464                                                                   "operation-started");
2465                 priv->sighandlers = modest_signal_mgr_disconnect (priv->sighandlers,
2466                                                                   G_OBJECT (mail_op),
2467                                                                   "operation-finished");
2468         }
2469 }
2470
2471 TnyList *
2472 modest_msg_view_window_get_attachments (ModestMsgViewWindow *win) 
2473 {
2474         ModestMsgViewWindowPrivate *priv;
2475         TnyList *selected_attachments = NULL;
2476         
2477         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (win), NULL);
2478         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (win);
2479
2480         selected_attachments = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2481         
2482         return selected_attachments;
2483 }
2484
2485 typedef struct {
2486         gchar *filepath;
2487         GtkWidget *banner;
2488         guint banner_idle_id;
2489 } DecodeAsyncHelper;
2490
2491 static gboolean
2492 decode_async_banner_idle (gpointer user_data)
2493 {
2494         DecodeAsyncHelper *helper = (DecodeAsyncHelper *) user_data;
2495
2496         helper->banner_idle_id = 0;
2497         helper->banner = hildon_banner_show_animation (NULL, NULL, _("mail_me_opening"));
2498         g_object_ref (helper->banner);
2499
2500         return FALSE;
2501 }
2502
2503 static void
2504 on_decode_to_stream_async_handler (TnyMimePart *mime_part, 
2505                                    gboolean cancelled, 
2506                                    TnyStream *stream, 
2507                                    GError *err, 
2508                                    gpointer user_data)
2509 {
2510         DecodeAsyncHelper *helper = (DecodeAsyncHelper *) user_data;
2511
2512         if (helper->banner_idle_id > 0) {
2513                 g_source_remove (helper->banner_idle_id);
2514                 helper->banner_idle_id = 0;
2515         }
2516         if (helper->banner) {
2517                 gtk_widget_destroy (helper->banner);
2518         }
2519         if (cancelled || err) {
2520                 modest_platform_information_banner (NULL, NULL, 
2521                                                     _("mail_ib_file_operation_failed"));
2522                 goto free;
2523         }
2524
2525         /* make the file read-only */
2526         g_chmod(helper->filepath, 0444);
2527         
2528         /* Activate the file */
2529         modest_platform_activate_file (helper->filepath, tny_mime_part_get_content_type (mime_part));
2530
2531  free:
2532         /* Frees */
2533         g_free (helper->filepath);
2534         g_object_unref (helper->banner);
2535         g_slice_free (DecodeAsyncHelper, helper);
2536 }
2537
2538 void
2539 modest_msg_view_window_view_attachment (ModestMsgViewWindow *window, 
2540                                         TnyMimePart *mime_part)
2541 {
2542         ModestMsgViewWindowPrivate *priv;
2543         const gchar *msg_uid;
2544         gchar *attachment_uid = NULL;
2545         gint attachment_index = 0;
2546         TnyList *attachments;
2547
2548         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
2549         g_return_if_fail (TNY_IS_MIME_PART (mime_part) || (mime_part == NULL));
2550         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2551
2552         msg_uid = modest_msg_view_window_get_message_uid (MODEST_MSG_VIEW_WINDOW (window));
2553         attachments = modest_msg_view_get_attachments (MODEST_MSG_VIEW (priv->msg_view));
2554         attachment_index = modest_list_index (attachments, (GObject *) mime_part);
2555         g_object_unref (attachments);
2556         
2557         if (msg_uid && attachment_index >= 0) {
2558                 attachment_uid = g_strdup_printf ("%s/%d", msg_uid, attachment_index);
2559         }
2560
2561         if (mime_part == NULL) {
2562                 gboolean error = FALSE;
2563                 TnyList *selected_attachments = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2564                 if (selected_attachments == NULL || tny_list_get_length (selected_attachments) == 0) {
2565                         error = TRUE;
2566                 } else if (tny_list_get_length (selected_attachments) > 1) {
2567                         hildon_banner_show_information (NULL, NULL, _("mcen_ib_unable_to_display_more"));
2568                         error = TRUE;
2569                 } else {
2570                         TnyIterator *iter;
2571                         iter = tny_list_create_iterator (selected_attachments);
2572                         mime_part = (TnyMimePart *) tny_iterator_get_current (iter);
2573                         g_object_unref (iter);
2574                 }
2575                 g_object_unref (selected_attachments);
2576
2577                 if (error)
2578                         return;
2579         } else {
2580                 g_object_ref (mime_part);
2581         }
2582
2583         if (tny_mime_part_is_purged (mime_part)) {
2584                 g_object_unref (mime_part);
2585                 return;
2586         }
2587
2588         if (!modest_tny_mime_part_is_msg (mime_part)) {
2589                 gchar *filepath = NULL;
2590                 const gchar *att_filename = tny_mime_part_get_filename (mime_part);
2591                 gboolean show_error_banner = FALSE;
2592                 TnyFsStream *temp_stream = NULL;
2593                 temp_stream = modest_utils_create_temp_stream (att_filename, attachment_uid,
2594                                                                &filepath);
2595                 
2596                 if (temp_stream != NULL) {
2597                         DecodeAsyncHelper *helper = g_slice_new (DecodeAsyncHelper);
2598                         helper->filepath = g_strdup (filepath);
2599                         helper->banner = NULL;
2600                         helper->banner_idle_id = g_timeout_add (1000, decode_async_banner_idle, helper);
2601                         tny_mime_part_decode_to_stream_async (mime_part, TNY_STREAM (temp_stream), 
2602                                                               on_decode_to_stream_async_handler, 
2603                                                               NULL, 
2604                                                               helper);
2605                         g_object_unref (temp_stream);
2606                         /* NOTE: files in the temporary area will be automatically
2607                          * cleaned after some time if they are no longer in use */
2608                 } else {
2609                         if (filepath) {
2610                                 const gchar *content_type;
2611                                 /* the file may already exist but it isn't writable,
2612                                  * let's try to open it anyway */
2613                                 content_type = tny_mime_part_get_content_type (mime_part);
2614                                 modest_platform_activate_file (filepath, content_type);
2615                         } else {
2616                                 g_warning ("%s: modest_utils_create_temp_stream failed", __FUNCTION__);
2617                                 show_error_banner = TRUE;
2618                         }
2619                 }
2620                 if (filepath)
2621                         g_free (filepath);
2622                 if (show_error_banner)
2623                         modest_platform_information_banner (NULL, NULL, _("mail_ib_file_operation_failed"));
2624         } else {
2625                 /* message attachment */
2626                 TnyHeader *header = NULL;
2627                 ModestWindowMgr *mgr;
2628                 ModestWindow *msg_win = NULL;
2629                 gboolean found;
2630                 
2631                 header = tny_msg_get_header (TNY_MSG (mime_part));
2632                 mgr = modest_runtime_get_window_mgr ();         
2633                 found = modest_window_mgr_find_registered_header (mgr, header, &msg_win);
2634
2635                 if (found) {
2636                         /* if it's found, but there is no msg_win, it's probably in the process of being created;
2637                          * thus, we don't do anything */
2638                         g_warning ("window for is already being created");
2639                 } else { 
2640                         /* it's not found, so create a new window for it */
2641                         modest_window_mgr_register_header (mgr, header, attachment_uid); /* register the uid before building the window */
2642                         gchar *account = g_strdup (modest_window_get_active_account (MODEST_WINDOW (window)));
2643                         if (!account)
2644                                 account = modest_account_mgr_get_default_account (modest_runtime_get_account_mgr ());
2645                         msg_win = modest_msg_view_window_new_for_attachment (TNY_MSG (mime_part), account, attachment_uid);
2646                         modest_window_set_zoom (MODEST_WINDOW (msg_win), 
2647                                                 modest_window_get_zoom (MODEST_WINDOW (window)));
2648                         modest_window_mgr_register_window (mgr, msg_win, MODEST_WINDOW (window));
2649                         /* gtk_widget_show_all (GTK_WIDGET (msg_win));*/
2650                 }
2651         }
2652         g_object_unref (mime_part);
2653 }
2654
2655 typedef struct
2656 {
2657         gchar *filename;
2658         TnyMimePart *part;
2659 } SaveMimePartPair;
2660
2661 typedef struct
2662 {
2663         GList *pairs;
2664         GtkWidget *banner;
2665         GnomeVFSResult result;
2666 } SaveMimePartInfo;
2667
2668 static void save_mime_part_info_free (SaveMimePartInfo *info, gboolean with_struct);
2669 static gboolean idle_save_mime_part_show_result (SaveMimePartInfo *info);
2670 static gpointer save_mime_part_to_file (SaveMimePartInfo *info);
2671 static void save_mime_parts_to_file_with_checks (SaveMimePartInfo *info);
2672
2673 static void 
2674 save_mime_part_info_free (SaveMimePartInfo *info, gboolean with_struct)
2675 {
2676         
2677         GList *node;
2678         for (node = info->pairs; node != NULL; node = g_list_next (node)) {
2679                 SaveMimePartPair *pair = (SaveMimePartPair *) node->data;
2680                 g_free (pair->filename);
2681                 g_object_unref (pair->part);
2682                 g_slice_free (SaveMimePartPair, pair);
2683         }
2684         g_list_free (info->pairs);
2685         info->pairs = NULL;
2686         if (with_struct) {
2687                 gtk_widget_destroy (info->banner);
2688                 g_slice_free (SaveMimePartInfo, info);
2689         }
2690 }
2691
2692 static gboolean
2693 idle_save_mime_part_show_result (SaveMimePartInfo *info)
2694 {
2695         if (info->pairs != NULL) {
2696                 save_mime_part_to_file (info);
2697         } else {
2698                 /* This is a GDK lock because we are an idle callback and
2699                  * hildon_banner_show_information is or does Gtk+ code */
2700
2701                 gdk_threads_enter (); /* CHECKED */
2702                 save_mime_part_info_free (info, TRUE);
2703                 if (info->result == GNOME_VFS_OK) {
2704                         hildon_banner_show_information (NULL, NULL, _CS("sfil_ib_saved"));
2705                 } else if (info->result == GNOME_VFS_ERROR_NO_SPACE) {
2706                         hildon_banner_show_information (NULL, NULL, dgettext("ke-recv", 
2707                                                                              "cerm_device_memory_full"));
2708                 } else {
2709                         hildon_banner_show_information (NULL, NULL, _("mail_ib_file_operation_failed"));
2710                 }
2711                 gdk_threads_leave (); /* CHECKED */
2712         }
2713
2714         return FALSE;
2715 }
2716
2717 static gpointer
2718 save_mime_part_to_file (SaveMimePartInfo *info)
2719 {
2720         GnomeVFSHandle *handle;
2721         TnyStream *stream;
2722         SaveMimePartPair *pair = (SaveMimePartPair *) info->pairs->data;
2723
2724         info->result = gnome_vfs_create (&handle, pair->filename, GNOME_VFS_OPEN_WRITE, FALSE, 0644);
2725         if (info->result == GNOME_VFS_OK) {
2726                 GError *error = NULL;
2727                 stream = tny_vfs_stream_new (handle);
2728                 if (tny_mime_part_decode_to_stream (pair->part, stream, &error) < 0) {
2729                         g_warning ("modest: could not save attachment %s: %d (%s)\n", pair->filename, error?error->code:-1, error?error->message:"Unknown error");
2730                         
2731                         info->result = GNOME_VFS_ERROR_IO;
2732                 }
2733                 g_object_unref (G_OBJECT (stream));
2734                 g_object_unref (pair->part);
2735                 g_slice_free (SaveMimePartPair, pair);
2736                 info->pairs = g_list_delete_link (info->pairs, info->pairs);
2737         } else {
2738                 g_warning ("modest: could not create save attachment %s: %s\n", pair->filename, gnome_vfs_result_to_string (info->result));
2739                 save_mime_part_info_free (info, FALSE);
2740         }
2741
2742         g_idle_add ((GSourceFunc) idle_save_mime_part_show_result, info);
2743         return NULL;
2744 }
2745
2746 static void
2747 save_mime_parts_to_file_with_checks (SaveMimePartInfo *info)
2748 {
2749         gboolean is_ok = TRUE;
2750         gint replaced_files = 0;
2751         const GList *files = info->pairs;
2752         const GList *iter;
2753
2754         for (iter = files; (iter != NULL) && (replaced_files < 2); iter = g_list_next(iter)) {
2755                 SaveMimePartPair *pair = iter->data;
2756                 if (modest_utils_file_exists (pair->filename)) {
2757                         replaced_files++;
2758                 }
2759         }
2760         if (replaced_files) {
2761                 GtkWidget *confirm_overwrite_dialog;
2762                 const gchar *message = (replaced_files == 1) ?
2763                         _FM("docm_nc_replace_file") : _FM("docm_nc_replace_multiple");
2764                 confirm_overwrite_dialog = hildon_note_new_confirmation (NULL, message);
2765                 if (gtk_dialog_run (GTK_DIALOG (confirm_overwrite_dialog)) != GTK_RESPONSE_OK) {
2766                         is_ok = FALSE;
2767                 }
2768                 gtk_widget_destroy (confirm_overwrite_dialog);
2769         }
2770
2771         if (!is_ok) {
2772                 save_mime_part_info_free (info, TRUE);
2773         } else {
2774                 GtkWidget *banner = hildon_banner_show_animation (NULL, NULL, 
2775                                                                   _CS("sfil_ib_saving"));
2776                 info->banner = banner;
2777                 g_thread_create ((GThreadFunc)save_mime_part_to_file, info, FALSE, NULL);
2778         }
2779
2780 }
2781
2782 static void
2783 save_attachments_response (GtkDialog *dialog,
2784                            gint       arg1,
2785                            gpointer   user_data)  
2786 {
2787         TnyList *mime_parts;
2788         gchar *chooser_uri;
2789         GList *files_to_save = NULL;
2790
2791         mime_parts = TNY_LIST (user_data);
2792
2793         if (arg1 != GTK_RESPONSE_OK)
2794                 goto end;
2795
2796         chooser_uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dialog));
2797
2798         if (!modest_utils_folder_writable (chooser_uri)) {
2799                 hildon_banner_show_information 
2800                         (NULL, NULL, dgettext("hildon-fm", "sfil_ib_readonly_location"));
2801         } else {
2802                 TnyIterator *iter;
2803
2804                 iter = tny_list_create_iterator (mime_parts);
2805                 while (!tny_iterator_is_done (iter)) {
2806                         TnyMimePart *mime_part = (TnyMimePart *) tny_iterator_get_current (iter);
2807
2808                         if ((modest_tny_mime_part_is_attachment_for_modest (mime_part)) &&
2809                             !tny_mime_part_is_purged (mime_part) &&
2810                             (tny_mime_part_get_filename (mime_part) != NULL)) {
2811                                 SaveMimePartPair *pair;
2812                                         
2813                                 pair = g_slice_new0 (SaveMimePartPair);
2814
2815                                 if (tny_list_get_length (mime_parts) > 1) {
2816                                         gchar *escaped = 
2817                                                 gnome_vfs_escape_slashes (tny_mime_part_get_filename (mime_part));
2818                                         pair->filename = g_build_filename (chooser_uri, escaped, NULL);
2819                                         g_free (escaped);
2820                                 } else {
2821                                         pair->filename = g_strdup (chooser_uri);
2822                                 }
2823                                 pair->part = mime_part;
2824                                 files_to_save = g_list_prepend (files_to_save, pair);
2825                         }
2826                         tny_iterator_next (iter);
2827                 }
2828                 g_object_unref (iter);
2829         }
2830         g_free (chooser_uri);
2831
2832         if (files_to_save != NULL) {
2833                 SaveMimePartInfo *info = g_slice_new0 (SaveMimePartInfo);
2834                 info->pairs = files_to_save;
2835                 info->result = TRUE;
2836                 save_mime_parts_to_file_with_checks (info);
2837         }
2838
2839  end:
2840         /* Free and close the dialog */
2841         g_object_unref (mime_parts);
2842         gtk_widget_destroy (GTK_WIDGET (dialog));
2843 }
2844
2845 void
2846 modest_msg_view_window_save_attachments (ModestMsgViewWindow *window, TnyList *mime_parts)
2847 {
2848         ModestMsgViewWindowPrivate *priv;
2849         GtkWidget *save_dialog = NULL;
2850         gchar *folder = NULL;
2851         gchar *filename = NULL;
2852         gchar *save_multiple_str = NULL;
2853
2854         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
2855         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2856
2857         if (mime_parts == NULL) {
2858                 mime_parts = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2859                 if (mime_parts == NULL || tny_list_get_length (mime_parts) == 0)
2860                         return;
2861         } else {
2862                 g_object_ref (mime_parts);
2863         }
2864
2865         /* prepare dialog */
2866         if (tny_list_get_length (mime_parts) == 1) {
2867                 TnyIterator *iter;
2868                 /* only one attachment selected */
2869                 iter = tny_list_create_iterator (mime_parts);
2870                 TnyMimePart *mime_part = (TnyMimePart *) tny_iterator_get_current (iter);
2871                 g_object_unref (iter);
2872                 if (!modest_tny_mime_part_is_msg (mime_part) && 
2873                     modest_tny_mime_part_is_attachment_for_modest (mime_part) &&
2874                     !tny_mime_part_is_purged (mime_part)) {
2875                         filename = g_strdup (tny_mime_part_get_filename (mime_part));
2876                 } else {
2877                         /* TODO: show any error? */
2878                         g_warning ("Tried to save a non-file attachment");
2879                         g_object_unref (mime_parts);
2880                         return;
2881                 }
2882                 g_object_unref (mime_part);
2883         } else {
2884                 save_multiple_str = g_strdup_printf (_FM("sfil_va_number_of_objects_attachments"), 
2885                                                      tny_list_get_length (mime_parts));
2886         }
2887         
2888         save_dialog = hildon_file_chooser_dialog_new (GTK_WINDOW (window), 
2889                                                       GTK_FILE_CHOOSER_ACTION_SAVE);
2890
2891         /* set folder */
2892         folder = g_build_filename (g_get_home_dir (), DEFAULT_FOLDER, NULL);
2893         gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (save_dialog), folder);
2894         g_free (folder);
2895
2896         /* set filename */
2897         if (filename) {
2898                 gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (save_dialog), 
2899                                                    filename);
2900                 g_free (filename);
2901         }
2902
2903         /* if multiple, set multiple string */
2904         if (save_multiple_str) {
2905                 g_object_set (G_OBJECT (save_dialog), "save-multiple", save_multiple_str, NULL);
2906                 gtk_window_set_title (GTK_WINDOW (save_dialog), _FM("sfil_ti_save_objects_files"));
2907         }
2908
2909         /* We must run this asynchronously, because the hildon dialog
2910            performs a gtk_dialog_run by itself which leads to gdk
2911            deadlocks */
2912         g_signal_connect (save_dialog, "response", 
2913                           G_CALLBACK (save_attachments_response), mime_parts);
2914
2915         gtk_widget_show_all (save_dialog);
2916 }
2917
2918 static gboolean
2919 show_remove_attachment_information (gpointer userdata)
2920 {
2921         ModestMsgViewWindow *window = (ModestMsgViewWindow *) userdata;
2922         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2923
2924         /* We're outside the main lock */
2925         gdk_threads_enter ();
2926
2927         if (priv->remove_attachment_banner != NULL) {
2928                 gtk_widget_destroy (priv->remove_attachment_banner);
2929                 g_object_unref (priv->remove_attachment_banner);
2930         }
2931
2932         priv->remove_attachment_banner = g_object_ref (
2933                 hildon_banner_show_animation (NULL, NULL, _("mcen_ib_removing_attachment")));
2934
2935         gdk_threads_leave ();
2936
2937         return FALSE;
2938 }
2939
2940 void
2941 modest_msg_view_window_remove_attachments (ModestMsgViewWindow *window, gboolean get_all)
2942 {
2943         ModestMsgViewWindowPrivate *priv;
2944         TnyList *mime_parts = NULL;
2945         gchar *confirmation_message;
2946         gint response;
2947         gint n_attachments;
2948         TnyMsg *msg;
2949         TnyIterator *iter;
2950
2951         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
2952         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2953
2954         if (get_all)
2955                 mime_parts = modest_msg_view_get_attachments (MODEST_MSG_VIEW (priv->msg_view));
2956         else
2957                 mime_parts = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2958                 
2959         /* Remove already purged messages from mime parts list */
2960         iter = tny_list_create_iterator (mime_parts);
2961         while (!tny_iterator_is_done (iter)) {
2962                 TnyMimePart *part = TNY_MIME_PART (tny_iterator_get_current (iter));
2963                 tny_iterator_next (iter);
2964                 if (tny_mime_part_is_purged (part)) {
2965                         tny_list_remove (mime_parts, (GObject *) part);
2966                 }
2967                 g_object_unref (part);
2968         }
2969         g_object_unref (iter);
2970
2971         if (tny_list_get_length (mime_parts) == 0) {
2972                 g_object_unref (mime_parts);
2973                 return;
2974         }
2975
2976         n_attachments = tny_list_get_length (mime_parts);
2977         if (n_attachments == 1) {
2978                 gchar *filename;
2979                 TnyMimePart *part;
2980
2981                 iter = tny_list_create_iterator (mime_parts);
2982                 part = (TnyMimePart *) tny_iterator_get_current (iter);
2983                 g_object_unref (iter);
2984                 if (modest_tny_mime_part_is_msg (part)) {
2985                         TnyHeader *header;
2986                         header = tny_msg_get_header (TNY_MSG (part));
2987                         filename = tny_header_dup_subject (header);
2988                         g_object_unref (header);
2989                         if (filename == NULL)
2990                                 filename = g_strdup (_("mail_va_no_subject"));
2991                 } else {
2992                         filename = g_strdup (tny_mime_part_get_filename (TNY_MIME_PART (part)));
2993                 }
2994                 confirmation_message = g_strdup_printf (_("mcen_nc_purge_file_text"), filename);
2995                 g_free (filename);
2996                 g_object_unref (part);
2997         } else {
2998                 confirmation_message = g_strdup_printf (ngettext("mcen_nc_purge_file_text", 
2999                                                                  "mcen_nc_purge_files_text", 
3000                                                                  n_attachments), n_attachments);
3001         }
3002         response = modest_platform_run_confirmation_dialog (GTK_WINDOW (window),
3003                                                             confirmation_message);
3004         g_free (confirmation_message);
3005
3006         if (response != GTK_RESPONSE_OK) {
3007                 g_object_unref (mime_parts);
3008                 return;
3009         }
3010
3011         priv->purge_timeout = g_timeout_add (2000, show_remove_attachment_information, window);
3012         
3013         iter = tny_list_create_iterator (mime_parts);
3014         while (!tny_iterator_is_done (iter)) {
3015                 TnyMimePart *part;
3016
3017                 part = (TnyMimePart *) tny_iterator_get_current (iter);
3018                 tny_mime_part_set_purged (TNY_MIME_PART (part));
3019                 g_object_unref (part);
3020                 tny_iterator_next (iter);
3021         }
3022         g_object_unref (iter);
3023
3024         msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
3025         tny_msg_view_clear (TNY_MSG_VIEW (priv->msg_view));
3026         tny_msg_rewrite_cache (msg);
3027         tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
3028         g_object_unref (msg);
3029
3030         g_object_unref (mime_parts);
3031
3032         if (priv->purge_timeout > 0) {
3033                 g_source_remove (priv->purge_timeout);
3034                 priv->purge_timeout = 0;
3035         }
3036
3037         if (priv->remove_attachment_banner) {
3038                 gtk_widget_destroy (priv->remove_attachment_banner);
3039                 g_object_unref (priv->remove_attachment_banner);
3040                 priv->remove_attachment_banner = NULL;
3041         }
3042
3043
3044 }
3045
3046
3047 static void
3048 update_window_title (ModestMsgViewWindow *window)
3049 {
3050         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
3051         TnyMsg *msg = NULL;
3052         TnyHeader *header = NULL;
3053         gchar *subject = NULL;
3054         
3055         msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
3056
3057         if (msg != NULL) {
3058                 header = tny_msg_get_header (msg);
3059                 subject = tny_header_dup_subject (header);
3060                 g_object_unref (header);
3061                 g_object_unref (msg);
3062         }
3063
3064         if ((subject == NULL)||(subject[0] == '\0')) {
3065                 g_free (subject);
3066                 subject = g_strdup (_("mail_va_no_subject"));
3067         }
3068
3069         gtk_window_set_title (GTK_WINDOW (window), subject);
3070 }
3071
3072
3073 static void on_move_focus (GtkWidget *widget,
3074                            GtkDirectionType direction,
3075                            gpointer userdata)
3076 {
3077         g_signal_stop_emission_by_name (G_OBJECT (widget), "move-focus");
3078 }
3079
3080 static TnyStream *
3081 fetch_image_open_stream (TnyStreamCache *self, gint64 *expected_size, gchar *uri)
3082 {
3083         GnomeVFSResult result;
3084         GnomeVFSHandle *handle = NULL;
3085         GnomeVFSFileInfo *info = NULL;
3086         TnyStream *stream;
3087
3088         result = gnome_vfs_open (&handle, uri, GNOME_VFS_OPEN_READ);
3089         if (result != GNOME_VFS_OK) {
3090                 *expected_size = 0;
3091                 return NULL;
3092         }
3093         
3094         info = gnome_vfs_file_info_new ();
3095         result = gnome_vfs_get_file_info_from_handle (handle, info, GNOME_VFS_FILE_INFO_DEFAULT);
3096         if (result != GNOME_VFS_OK || ! (info->valid_fields & GNOME_VFS_FILE_INFO_FIELDS_SIZE)) {
3097                 /* We put a "safe" default size for going to cache */
3098                 *expected_size = (300*1024);
3099         } else {
3100                 *expected_size = info->size;
3101         }
3102         gnome_vfs_file_info_unref (info);
3103
3104         stream = tny_vfs_stream_new (handle);
3105
3106         return stream;
3107
3108 }
3109
3110 typedef struct {
3111         gchar *uri;
3112         gchar *cache_id;
3113         TnyStream *output_stream;
3114         GtkWidget *msg_view;
3115 } FetchImageData;
3116
3117 gboolean
3118 on_fetch_image_idle_refresh_view (gpointer userdata)
3119 {
3120
3121         FetchImageData *fidata = (FetchImageData *) userdata;
3122         g_message ("REFRESH VIEW");
3123         if (GTK_WIDGET_DRAWABLE (fidata->msg_view)) {
3124                 g_message ("QUEUING DRAW");
3125                 gtk_widget_queue_draw (fidata->msg_view);
3126         }
3127         g_object_unref (fidata->msg_view);
3128         g_slice_free (FetchImageData, fidata);
3129         return FALSE;
3130 }
3131
3132 static gpointer
3133 on_fetch_image_thread (gpointer userdata)
3134 {
3135         FetchImageData *fidata = (FetchImageData *) userdata;
3136         TnyStreamCache *cache;
3137         TnyStream *cache_stream;
3138
3139         cache = modest_runtime_get_images_cache ();
3140         cache_stream = tny_stream_cache_get_stream (cache, fidata->cache_id, (TnyStreamCacheOpenStreamFetcher) fetch_image_open_stream, (gpointer) fidata->uri);
3141         g_free (fidata->cache_id);
3142         g_free (fidata->uri);
3143
3144         if (cache_stream != NULL) {
3145                 tny_stream_write_to_stream (cache_stream, fidata->output_stream);
3146                 tny_stream_close (cache_stream);
3147                 g_object_unref (cache_stream);
3148         }
3149
3150         tny_stream_close (fidata->output_stream);
3151         g_object_unref (fidata->output_stream);
3152
3153
3154         gdk_threads_enter ();
3155         g_idle_add (on_fetch_image_idle_refresh_view, fidata);
3156         gdk_threads_leave ();
3157
3158         return NULL;
3159 }
3160
3161 static gboolean
3162 on_fetch_image (ModestMsgView *msgview,
3163                 const gchar *uri,
3164                 TnyStream *stream,
3165                 ModestMsgViewWindow *window)
3166 {
3167         const gchar *current_account;
3168         ModestMsgViewWindowPrivate *priv;
3169         FetchImageData *fidata;
3170
3171         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
3172
3173         current_account = modest_window_get_active_account (MODEST_WINDOW (window));
3174
3175         fidata = g_slice_new0 (FetchImageData);
3176         fidata->msg_view = g_object_ref (msgview);
3177         fidata->uri = g_strdup (uri);
3178         fidata->cache_id = modest_images_cache_get_id (current_account, uri);
3179         fidata->output_stream = g_object_ref (stream);
3180
3181         if (g_thread_create (on_fetch_image_thread, fidata, FALSE, NULL) == NULL) {
3182                 g_object_unref (fidata->output_stream);
3183                 g_free (fidata->cache_id);
3184                 g_free (fidata->uri);
3185                 g_object_unref (fidata->msg_view);
3186                 g_slice_free (FetchImageData, fidata);
3187                 tny_stream_close (stream);
3188                 return FALSE;
3189         }
3190
3191         return TRUE;;
3192 }