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