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