7e300e586456115617f14dea161fccac23714b59
[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
1105 static gboolean
1106 check_dimming_rules_after_change_in_idle (gpointer data)
1107 {
1108         /* The window could have dissapeared */
1109         if (MODEST_IS_WINDOW (data)) {
1110                 ModestWindow *win = MODEST_WINDOW (data);
1111                 gdk_threads_enter ();
1112                 modest_ui_actions_check_menu_dimming_rules (win);
1113                 modest_ui_actions_check_toolbar_dimming_rules (win);
1114                 gdk_threads_leave ();
1115         }
1116
1117         return FALSE;
1118 }
1119
1120 static void
1121 check_dimming_rules_after_change (ModestMsgViewWindow *window)
1122 {
1123         static guint dimming_delayer = 0;
1124
1125         if (dimming_delayer > 0)
1126                 g_source_remove (dimming_delayer);
1127
1128         /* We're expecting a lot of changes at the same time so don't
1129            need to check dimming rules for every change that
1130            happens */
1131         dimming_delayer = g_timeout_add (100, check_dimming_rules_after_change_in_idle, window);
1132 }
1133
1134
1135 /* On insertions we check if the folder still has the message we are
1136  * showing or do not. If do not, we do nothing. Which means we are still
1137  * not attached to any header folder and thus next/prev buttons are
1138  * still dimmed. Once the message that is shown by msg-view is found, the
1139  * new model of header-view will be attached and the references will be set.
1140  * On each further insertions dimming rules will be checked. However
1141  * this requires extra CPU time at least works.
1142  * (An message might be deleted from TnyFolder and thus will not be
1143  * inserted into the model again for example if it is removed by the
1144  * imap server and the header view is refreshed.)
1145  */
1146 static void 
1147 modest_msg_view_window_on_row_inserted (GtkTreeModel *model,
1148                                         GtkTreePath *tree_path,
1149                                         GtkTreeIter *tree_iter,
1150                                         ModestMsgViewWindow *window)
1151 {
1152         ModestMsgViewWindowPrivate *priv = NULL; 
1153         TnyHeader *header = NULL;
1154
1155         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
1156         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1157
1158         g_assert (model == priv->header_model);
1159         
1160         /* Check if the newly inserted message is the same we are actually
1161          * showing. IF not, we should remain detached from the header model
1162          * and thus prev and next toolbar buttons should remain dimmed. */
1163         gtk_tree_model_get (model, tree_iter, 
1164                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, 
1165                             &header, -1);
1166
1167         if (TNY_IS_HEADER (header)) {
1168                 gchar *uid = NULL;
1169
1170                 uid = modest_tny_folder_get_header_unique_id (header);
1171                 if (!g_str_equal(priv->msg_uid, uid)) {
1172                         check_dimming_rules_after_change (window);
1173                         g_free(uid);
1174                         g_object_unref (G_OBJECT(header));
1175                         return;
1176                 }
1177                 g_free(uid);
1178                 g_object_unref(G_OBJECT(header));
1179         }
1180
1181         if (priv->row_reference) {
1182                 gtk_tree_row_reference_free (priv->row_reference); 
1183         }
1184
1185         /* Setup row_reference for the actual msg. */
1186         priv->row_reference = gtk_tree_row_reference_new (priv->header_model, tree_path);
1187         if (priv->row_reference == NULL) {
1188                 g_warning("No reference for msg header item.");
1189                 return;
1190         }
1191
1192         /* Now set up next_row_reference. */
1193         if (priv->next_row_reference) {
1194                 gtk_tree_row_reference_free (priv->next_row_reference); 
1195         }
1196
1197         priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
1198         select_next_valid_row (priv->header_model,
1199                                &(priv->next_row_reference), FALSE, priv->is_outbox);
1200
1201         /* Connect the remaining callbacks to become able to detect
1202          * changes in header-view. */
1203         priv->row_changed_handler = 
1204                 g_signal_connect (priv->header_model, "row-changed",
1205                                   G_CALLBACK (modest_msg_view_window_on_row_changed),
1206                                   window);
1207         priv->row_deleted_handler = 
1208                 g_signal_connect (priv->header_model, "row-deleted",
1209                                   G_CALLBACK (modest_msg_view_window_on_row_deleted),
1210                                   window);
1211         priv->rows_reordered_handler = 
1212                 g_signal_connect (priv->header_model, "rows-reordered",
1213                                   G_CALLBACK (modest_msg_view_window_on_row_reordered),
1214                                   window);
1215
1216         check_dimming_rules_after_change (window);      
1217 }
1218
1219 static void 
1220 modest_msg_view_window_on_row_reordered (GtkTreeModel *header_model,
1221                                          GtkTreePath *arg1,
1222                                          GtkTreeIter *arg2,
1223                                          gpointer arg3,
1224                                          ModestMsgViewWindow *window)
1225 {
1226         ModestMsgViewWindowPrivate *priv = NULL; 
1227         gboolean already_changed = FALSE;
1228
1229         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(window);
1230
1231         /* If the current row was reordered select the proper next
1232            valid row. The same if the next row reference changes */
1233         if (priv->row_reference && 
1234             gtk_tree_row_reference_valid (priv->row_reference)) {
1235                 GtkTreePath *path;
1236                 path = gtk_tree_row_reference_get_path (priv->row_reference);
1237                 if (gtk_tree_path_compare (path, arg1) == 0) {
1238                         if (priv->next_row_reference) {
1239                                 gtk_tree_row_reference_free (priv->next_row_reference);
1240                         }
1241                         priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
1242                         select_next_valid_row (header_model, &(priv->next_row_reference), FALSE, priv->is_outbox);
1243                         already_changed = TRUE;
1244                 }
1245                 gtk_tree_path_free (path);
1246         }
1247         if (!already_changed &&
1248             priv->next_row_reference &&
1249             gtk_tree_row_reference_valid (priv->next_row_reference)) {
1250                 GtkTreePath *path;
1251                 path = gtk_tree_row_reference_get_path (priv->next_row_reference);
1252                 if (gtk_tree_path_compare (path, arg1) == 0) {
1253                         if (priv->next_row_reference) {
1254                                 gtk_tree_row_reference_free (priv->next_row_reference);
1255                         }
1256                         priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
1257                         select_next_valid_row (header_model, &(priv->next_row_reference), FALSE, priv->is_outbox);
1258                 }
1259                 gtk_tree_path_free (path);
1260         }
1261         check_dimming_rules_after_change (window);
1262 }
1263
1264 /* The modest_msg_view_window_update_model_replaced implements update
1265  * function for ModestHeaderViewObserver. Checks whether the TnyFolder
1266  * actually belongs to the header-view is the same as the TnyFolder of
1267  * the message of msg-view or not. If they are different, there is
1268  * nothing to do. If they are the same, then the model has replaced and
1269  * the reference in msg-view shall be replaced from the old model to
1270  * the new model. In this case the view will be detached from it's
1271  * header folder. From this point the next/prev buttons are dimmed.
1272  */
1273 static void 
1274 modest_msg_view_window_update_model_replaced (ModestHeaderViewObserver *observer,
1275                                               GtkTreeModel *model,
1276                                               const gchar *tny_folder_id)
1277 {
1278         ModestMsgViewWindowPrivate *priv = NULL; 
1279         ModestMsgViewWindow *window = NULL;
1280
1281         g_assert(MODEST_IS_HEADER_VIEW_OBSERVER(observer));
1282         g_assert(MODEST_IS_MSG_VIEW_WINDOW(observer));
1283
1284         window = MODEST_MSG_VIEW_WINDOW(observer);
1285         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(window);
1286
1287         /* If there is an other folder in the header-view then we do
1288          * not care about it's model (msg list). Else if the
1289          * header-view shows the folder the msg shown by us is in, we
1290          * shall replace our model reference and make some check. */
1291         if(model == NULL || tny_folder_id == NULL || 
1292            (priv->header_folder_id && !g_str_equal(tny_folder_id, priv->header_folder_id)))
1293                 return;
1294
1295         /* Model is changed(replaced), so we should forget the old
1296          * one. Because there might be other references and there
1297          * might be some change on the model even if we unreferenced
1298          * it, we need to disconnect our signals here. */
1299         if (priv->header_model) {
1300                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
1301                                                   priv->row_changed_handler))
1302                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
1303                                                     priv->row_changed_handler);
1304                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
1305                                                   priv->row_deleted_handler))
1306                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
1307                                                     priv->row_deleted_handler);
1308                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
1309                                                   priv->row_inserted_handler))
1310                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
1311                                                     priv->row_inserted_handler);
1312                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
1313                                                   priv->rows_reordered_handler))
1314                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
1315                                                     priv->rows_reordered_handler);
1316
1317                 /* Frees */
1318                 if (priv->row_reference)
1319                         gtk_tree_row_reference_free (priv->row_reference);
1320                 if (priv->next_row_reference)
1321                         gtk_tree_row_reference_free (priv->next_row_reference);
1322                 g_object_unref(priv->header_model);
1323
1324                 /* Initialize */
1325                 priv->row_changed_handler = 0;
1326                 priv->row_deleted_handler = 0;
1327                 priv->row_inserted_handler = 0;
1328                 priv->rows_reordered_handler = 0;
1329                 priv->next_row_reference = NULL;
1330                 priv->row_reference = NULL;
1331                 priv->header_model = NULL;
1332         }
1333
1334         priv->header_model = g_object_ref (model);
1335
1336         /* Also we must connect to the new model for row insertions.
1337          * Only for insertions now. We will need other ones only after
1338          * the msg is show by msg-view is added to the new model. */
1339         priv->row_inserted_handler =
1340                 g_signal_connect (priv->header_model, "row-inserted",
1341                                   G_CALLBACK(modest_msg_view_window_on_row_inserted),
1342                                   window);
1343
1344         modest_ui_actions_check_menu_dimming_rules(MODEST_WINDOW(window));
1345         modest_ui_actions_check_toolbar_dimming_rules(MODEST_WINDOW(window));
1346 }
1347
1348 gboolean 
1349 modest_msg_view_window_toolbar_on_transfer_mode     (ModestMsgViewWindow *self)
1350 {
1351         ModestMsgViewWindowPrivate *priv= NULL; 
1352
1353         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), FALSE);
1354         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
1355
1356         return priv->current_toolbar_mode == TOOLBAR_MODE_TRANSFER;
1357 }
1358
1359 TnyHeader*
1360 modest_msg_view_window_get_header (ModestMsgViewWindow *self)
1361 {
1362         ModestMsgViewWindowPrivate *priv= NULL; 
1363         TnyMsg *msg = NULL;
1364         TnyHeader *header = NULL;
1365         GtkTreePath *path = NULL;
1366         GtkTreeIter iter;
1367
1368         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), NULL);
1369         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
1370
1371         /* If the message was not obtained from a treemodel,
1372          * for instance if it was opened directly by the search UI:
1373          */
1374         if (priv->header_model == NULL || 
1375             priv->row_reference == NULL ||
1376             !gtk_tree_row_reference_valid (priv->row_reference)) {
1377                 msg = modest_msg_view_window_get_message (self);
1378                 if (msg) {
1379                         header = tny_msg_get_header (msg);
1380                         g_object_unref (msg);
1381                 }
1382                 return header;
1383         }
1384
1385         /* Get iter of the currently selected message in the header view: */
1386         path = gtk_tree_row_reference_get_path (priv->row_reference);
1387         g_return_val_if_fail (path != NULL, NULL);
1388         gtk_tree_model_get_iter (priv->header_model, 
1389                                  &iter, 
1390                                  path);
1391
1392         /* Get current message header */
1393         gtk_tree_model_get (priv->header_model, &iter, 
1394                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, 
1395                             &header, -1);
1396
1397         gtk_tree_path_free (path);
1398         return header;
1399 }
1400
1401 TnyMsg*
1402 modest_msg_view_window_get_message (ModestMsgViewWindow *self)
1403 {
1404         ModestMsgViewWindowPrivate *priv;
1405         
1406         g_return_val_if_fail (self, NULL);
1407         
1408         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
1409         
1410         return tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
1411 }
1412
1413 const gchar*
1414 modest_msg_view_window_get_message_uid (ModestMsgViewWindow *self)
1415 {
1416         ModestMsgViewWindowPrivate *priv;
1417
1418         g_return_val_if_fail (self, NULL);
1419         
1420         priv  = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
1421
1422         return (const gchar*) priv->msg_uid;
1423 }
1424
1425 static void 
1426 modest_msg_view_window_toggle_find_toolbar (GtkToggleAction *toggle,
1427                                             gpointer data)
1428 {
1429         ModestMsgViewWindow *window = MODEST_MSG_VIEW_WINDOW (data);
1430         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1431         ModestWindowPrivate *parent_priv = MODEST_WINDOW_GET_PRIVATE (window);
1432         gboolean is_active;
1433         GtkAction *action;
1434
1435         is_active = gtk_toggle_action_get_active (toggle);
1436
1437         if (is_active) {
1438                 gtk_widget_show (priv->find_toolbar);
1439                 hildon_find_toolbar_highlight_entry (HILDON_FIND_TOOLBAR (priv->find_toolbar), TRUE);
1440         } else {
1441                 gtk_widget_hide (priv->find_toolbar);
1442         }
1443
1444         /* update the toggle buttons status */
1445         action = gtk_ui_manager_get_action (parent_priv->ui_manager, "/ToolBar/FindInMessage");
1446         modest_utils_toggle_action_set_active_block_notify (GTK_TOGGLE_ACTION (action), is_active);
1447         action = gtk_ui_manager_get_action (parent_priv->ui_manager, "/MenuBar/ToolsMenu/ToolsFindInMessageMenu");
1448         modest_utils_toggle_action_set_active_block_notify (GTK_TOGGLE_ACTION (action), is_active);
1449         
1450 }
1451
1452 static void
1453 modest_msg_view_window_find_toolbar_close (GtkWidget *widget,
1454                                            ModestMsgViewWindow *obj)
1455 {
1456         GtkToggleAction *toggle;
1457         ModestWindowPrivate *parent_priv;
1458         ModestMsgViewWindowPrivate *priv;
1459
1460         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (obj);
1461         parent_priv = MODEST_WINDOW_GET_PRIVATE (obj);
1462         
1463         toggle = GTK_TOGGLE_ACTION (gtk_ui_manager_get_action (parent_priv->ui_manager, "/ToolBar/FindInMessage"));
1464         gtk_toggle_action_set_active (toggle, FALSE);
1465         modest_msg_view_grab_focus (MODEST_MSG_VIEW (priv->msg_view));
1466 }
1467
1468 static void
1469 modest_msg_view_window_find_toolbar_search (GtkWidget *widget,
1470                                            ModestMsgViewWindow *obj)
1471 {
1472         gchar *current_search;
1473         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (obj);
1474
1475         if (modest_mime_part_view_is_empty (MODEST_MIME_PART_VIEW (priv->msg_view))) {
1476                 hildon_banner_show_information (NULL, NULL, _("mail_ib_nothing_to_find"));
1477                 return;
1478         }
1479
1480         g_object_get (G_OBJECT (widget), "prefix", &current_search, NULL);
1481
1482         if ((current_search == NULL) || (strcmp (current_search, "") == 0)) {
1483                 g_free (current_search);
1484                 hildon_banner_show_information (NULL, NULL, dgettext("hildon-common-strings", "ecdg_ib_find_rep_enter_text"));
1485                 return;
1486         }
1487
1488         if ((priv->last_search == NULL) || (strcmp (priv->last_search, current_search) != 0)) {
1489                 gboolean result;
1490                 g_free (priv->last_search);
1491                 priv->last_search = g_strdup (current_search);
1492                 result = modest_isearch_view_search (MODEST_ISEARCH_VIEW (priv->msg_view),
1493                                                      priv->last_search);
1494                 if (!result) {
1495                         hildon_banner_show_information (NULL, NULL, dgettext("hildon-libs", "ckct_ib_find_no_matches"));
1496                         g_free (priv->last_search);
1497                         priv->last_search = NULL;
1498                 } else {
1499                         modest_msg_view_grab_focus (MODEST_MSG_VIEW (priv->msg_view));
1500                         hildon_find_toolbar_highlight_entry (HILDON_FIND_TOOLBAR (priv->find_toolbar), TRUE);
1501                 }
1502         } else {
1503                 if (!modest_isearch_view_search_next (MODEST_ISEARCH_VIEW (priv->msg_view))) {
1504                         hildon_banner_show_information (NULL, NULL, dgettext("hildon-libs", "ckct_ib_find_search_complete"));
1505                         g_free (priv->last_search);
1506                         priv->last_search = NULL;
1507                 } else {
1508                         modest_msg_view_grab_focus (MODEST_MSG_VIEW (priv->msg_view));
1509                         hildon_find_toolbar_highlight_entry (HILDON_FIND_TOOLBAR (priv->find_toolbar), TRUE);
1510                 }
1511         }
1512         
1513         g_free (current_search);
1514                 
1515 }
1516
1517 static void
1518 modest_msg_view_window_set_zoom (ModestWindow *window,
1519                                  gdouble zoom)
1520 {
1521         ModestMsgViewWindowPrivate *priv;
1522         ModestWindowPrivate *parent_priv;
1523         GtkAction *action = NULL;
1524         gint int_zoom = (gint) rint (zoom*100.0+0.1);
1525      
1526         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
1527
1528         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1529         parent_priv = MODEST_WINDOW_GET_PRIVATE (window);
1530         modest_zoomable_set_zoom (MODEST_ZOOMABLE (priv->msg_view), zoom);
1531
1532         action = gtk_ui_manager_get_action (parent_priv->ui_manager, 
1533                                             "/MenuBar/ViewMenu/ZoomMenu/Zoom50Menu");
1534
1535         gtk_radio_action_set_current_value (GTK_RADIO_ACTION (action), int_zoom);
1536 }
1537
1538 static gdouble
1539 modest_msg_view_window_get_zoom (ModestWindow *window)
1540 {
1541         ModestMsgViewWindowPrivate *priv;
1542      
1543         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), 1.0);
1544
1545         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1546         return modest_zoomable_get_zoom (MODEST_ZOOMABLE (priv->msg_view));
1547 }
1548
1549 static gboolean
1550 modest_msg_view_window_zoom_plus (ModestWindow *window)
1551 {
1552         ModestWindowPrivate *parent_priv;
1553         GtkRadioAction *zoom_radio_action;
1554         GSList *group, *node;
1555
1556         parent_priv = MODEST_WINDOW_GET_PRIVATE (window);
1557         zoom_radio_action = GTK_RADIO_ACTION (gtk_ui_manager_get_action (parent_priv->ui_manager, 
1558                                                                          "/MenuBar/ViewMenu/ZoomMenu/Zoom50Menu"));
1559
1560         group = gtk_radio_action_get_group (zoom_radio_action);
1561
1562         if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (group->data))) {
1563                 hildon_banner_show_information (NULL, NULL, dgettext("hildon-common-strings", "ckct_ib_max_zoom_level_reached"));
1564                 return FALSE;
1565         }
1566
1567         for (node = group; node != NULL; node = g_slist_next (node)) {
1568                 if ((node->next != NULL) && gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (node->next->data))) {
1569                         gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (node->data), TRUE);
1570                         return TRUE;
1571                 }
1572         }
1573         return FALSE;
1574 }
1575
1576 static gboolean
1577 modest_msg_view_window_zoom_minus (ModestWindow *window)
1578 {
1579         ModestWindowPrivate *parent_priv;
1580         GtkRadioAction *zoom_radio_action;
1581         GSList *group, *node;
1582
1583         parent_priv = MODEST_WINDOW_GET_PRIVATE (window);
1584         zoom_radio_action = GTK_RADIO_ACTION (gtk_ui_manager_get_action (parent_priv->ui_manager, 
1585                                                                          "/MenuBar/ViewMenu/ZoomMenu/Zoom50Menu"));
1586
1587         group = gtk_radio_action_get_group (zoom_radio_action);
1588
1589         for (node = group; node != NULL; node = g_slist_next (node)) {
1590                 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (node->data))) {
1591                         if (node->next != NULL) {
1592                                 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (node->next->data), TRUE);
1593                                 return TRUE;
1594                         } else {
1595                           hildon_banner_show_information (NULL, NULL, dgettext("hildon-common-strings", "ckct_ib_min_zoom_level_reached"));
1596                                 return FALSE;
1597                         }
1598                         break;
1599                 }
1600         }
1601         return FALSE;
1602 }
1603
1604 static gboolean
1605 modest_msg_view_window_key_event (GtkWidget *window,
1606                                   GdkEventKey *event,
1607                                   gpointer userdata)
1608 {
1609         GtkWidget *focus;
1610
1611         focus = gtk_window_get_focus (GTK_WINDOW (window));
1612
1613         /* for the find toolbar case */
1614         if (focus && GTK_IS_ENTRY (focus)) {
1615                 if (event->keyval == GDK_BackSpace) {
1616                         GdkEvent *copy;
1617                         copy = gdk_event_copy ((GdkEvent *) event);
1618                         gtk_widget_event (focus, copy);
1619                         gdk_event_free (copy);
1620                         return TRUE;
1621                 } else 
1622                         return FALSE;
1623         }
1624         if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up ||
1625             event->keyval == GDK_Down || event->keyval == GDK_KP_Down ||
1626             event->keyval == GDK_Page_Up || event->keyval == GDK_KP_Page_Up ||
1627             event->keyval == GDK_Page_Down || event->keyval == GDK_KP_Page_Down ||
1628             event->keyval == GDK_Home || event->keyval == GDK_KP_Home ||
1629             event->keyval == GDK_End || event->keyval == GDK_KP_End) {
1630                 /* ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window); */
1631                 /* gboolean return_value; */
1632
1633                 if (event->type == GDK_KEY_PRESS) {
1634                         GtkScrollType scroll_type;
1635                         
1636                         switch (event->keyval) {
1637                         case GDK_Up: 
1638                         case GDK_KP_Up:
1639                                 scroll_type = GTK_SCROLL_STEP_UP; break;
1640                         case GDK_Down: 
1641                         case GDK_KP_Down:
1642                                 scroll_type = GTK_SCROLL_STEP_DOWN; break;
1643                         case GDK_Page_Up:
1644                         case GDK_KP_Page_Up:
1645                                 scroll_type = GTK_SCROLL_PAGE_UP; break;
1646                         case GDK_Page_Down:
1647                         case GDK_KP_Page_Down:
1648                                 scroll_type = GTK_SCROLL_PAGE_DOWN; break;
1649                         case GDK_Home:
1650                         case GDK_KP_Home:
1651                                 scroll_type = GTK_SCROLL_START; break;
1652                         case GDK_End:
1653                         case GDK_KP_End:
1654                                 scroll_type = GTK_SCROLL_END; break;
1655                         default: scroll_type = GTK_SCROLL_NONE;
1656                         }
1657                         
1658                         /* g_signal_emit_by_name (G_OBJECT (priv->main_scroll), "scroll-child",  */
1659                         /*                     scroll_type, FALSE, &return_value); */
1660                         return FALSE;
1661                 } else {
1662                         return FALSE;
1663                 }
1664         } else {
1665                 return FALSE;
1666         }
1667 }
1668
1669 gboolean
1670 modest_msg_view_window_last_message_selected (ModestMsgViewWindow *window)
1671 {
1672         GtkTreePath *path;
1673         ModestMsgViewWindowPrivate *priv;
1674         GtkTreeIter tmp_iter;
1675         gboolean is_last_selected;
1676
1677         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), TRUE);
1678         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1679
1680         /*if no model (so no rows at all), then virtually we are the last*/
1681         if (!priv->header_model || !priv->row_reference)
1682                 return TRUE;
1683
1684         path = gtk_tree_row_reference_get_path (priv->row_reference);
1685         if (path == NULL)
1686                 return TRUE;
1687
1688         is_last_selected = TRUE;
1689         while (is_last_selected) {
1690                 TnyHeader *header;
1691                 gtk_tree_path_next (path);
1692                 if (!gtk_tree_model_get_iter (priv->header_model, &tmp_iter, path))
1693                         break;
1694                 gtk_tree_model_get (priv->header_model, &tmp_iter,
1695                                 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1696                                 &header, -1);
1697                 if (header) {
1698                         if (msg_is_visible (header, priv->is_outbox))
1699                                 is_last_selected = FALSE;
1700                         g_object_unref(G_OBJECT(header));
1701                 }
1702         }
1703         gtk_tree_path_free (path);
1704         return is_last_selected;
1705 }
1706
1707 gboolean
1708 modest_msg_view_window_has_headers_model (ModestMsgViewWindow *window)
1709 {
1710         ModestMsgViewWindowPrivate *priv;
1711
1712         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), TRUE);
1713         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1714
1715         return priv->header_model != NULL;
1716 }
1717
1718 gboolean
1719 modest_msg_view_window_is_search_result (ModestMsgViewWindow *window)
1720 {
1721         ModestMsgViewWindowPrivate *priv;
1722
1723         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), TRUE);
1724         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1725
1726         return priv->is_search_result;
1727 }
1728
1729 static gboolean
1730 msg_is_visible (TnyHeader *header, gboolean check_outbox)
1731 {
1732         return (!(tny_header_get_flags(header) & TNY_HEADER_FLAG_DELETED)) &&
1733                 ( (!check_outbox) || (modest_tny_all_send_queues_get_msg_status (header) != MODEST_TNY_SEND_QUEUE_FAILED)) ;
1734         
1735 }
1736
1737 gboolean
1738 modest_msg_view_window_first_message_selected (ModestMsgViewWindow *window)
1739 {
1740         GtkTreePath *path;
1741         ModestMsgViewWindowPrivate *priv;
1742         gboolean is_first_selected;
1743         GtkTreeIter tmp_iter;
1744 /*      gchar * path_string;*/
1745
1746         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), TRUE);
1747         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1748
1749         /*if no model (so no rows at all), then virtually we are the first*/
1750         if (!priv->header_model || !priv->row_reference)
1751                 return TRUE;
1752
1753         path = gtk_tree_row_reference_get_path (priv->row_reference);
1754         if (!path)
1755                 return TRUE;
1756
1757 /*      path_string = gtk_tree_path_to_string (path);
1758         is_first_selected = strcmp (path_string, "0");
1759
1760         g_free (path_string);
1761         gtk_tree_path_free (path);
1762
1763         return is_first_selected;*/
1764
1765         is_first_selected = TRUE;
1766         while (is_first_selected) {
1767                 TnyHeader *header;
1768                 if(!gtk_tree_path_prev (path))
1769                         break;
1770                 /* Here the 'if' is needless for logic, but let make sure
1771                  * iter is valid for gtk_tree_model_get. */
1772                 if (!gtk_tree_model_get_iter (priv->header_model, &tmp_iter, path))
1773                         break;
1774                 gtk_tree_model_get (priv->header_model, &tmp_iter,
1775                                 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1776                                 &header, -1);
1777                 if (header) {
1778                         if (msg_is_visible (header, priv->is_outbox))
1779                                 is_first_selected = FALSE;
1780                         g_object_unref(G_OBJECT(header));
1781                 }
1782         }
1783         gtk_tree_path_free (path);
1784         return is_first_selected;
1785 }
1786
1787 typedef struct {
1788         TnyHeader *header;
1789         GtkTreeRowReference *row_reference;
1790 } MsgReaderInfo;
1791
1792 static void
1793 message_reader_performer (gboolean canceled, 
1794                           GError *err,
1795                           GtkWindow *parent_window, 
1796                           TnyAccount *account, 
1797                           gpointer user_data)
1798 {
1799         ModestMailOperation *mail_op = NULL;
1800         MsgReaderInfo *info;
1801
1802         info = (MsgReaderInfo *) user_data;
1803         if (canceled || err) {
1804                 goto frees;
1805         }
1806
1807         /* Register the header - it'll be unregistered in the callback */
1808         modest_window_mgr_register_header (modest_runtime_get_window_mgr (), info->header, NULL);
1809
1810         /* New mail operation */
1811         mail_op = modest_mail_operation_new_with_error_handling (G_OBJECT(parent_window),
1812                                                                  modest_ui_actions_disk_operations_error_handler, 
1813                                                                  NULL, NULL);
1814                                 
1815         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (), mail_op);
1816         modest_mail_operation_get_msg (mail_op, info->header, TRUE, view_msg_cb, info->row_reference);
1817         g_object_unref (mail_op);
1818
1819         /* Update dimming rules */
1820         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (parent_window));
1821         modest_ui_actions_check_menu_dimming_rules (MODEST_WINDOW (parent_window));
1822
1823  frees:
1824         /* Frees. The row_reference will be freed by the view_msg_cb callback */
1825         g_object_unref (info->header);
1826         g_slice_free (MsgReaderInfo, info);
1827 }
1828
1829
1830 /**
1831  * Reads the message whose summary item is @header. It takes care of
1832  * several things, among others:
1833  *
1834  * If the message was not previously downloaded then ask the user
1835  * before downloading. If there is no connection launch the connection
1836  * dialog. Update toolbar dimming rules.
1837  *
1838  * Returns: TRUE if the mail operation was started, otherwise if the
1839  * user do not want to download the message, or if the user do not
1840  * want to connect, then the operation is not issued
1841  **/
1842 static gboolean
1843 message_reader (ModestMsgViewWindow *window,
1844                 ModestMsgViewWindowPrivate *priv,
1845                 TnyHeader *header,
1846                 GtkTreeRowReference *row_reference)
1847 {
1848         gboolean already_showing = FALSE;
1849         ModestWindow *msg_window = NULL;
1850         ModestWindowMgr *mgr;
1851         TnyAccount *account;
1852         TnyFolder *folder;
1853         MsgReaderInfo *info;
1854
1855         g_return_val_if_fail (row_reference != NULL, FALSE);
1856
1857         mgr = modest_runtime_get_window_mgr ();
1858         already_showing = modest_window_mgr_find_registered_header (mgr, header, &msg_window);
1859         if (already_showing && (msg_window != MODEST_WINDOW (window))) {
1860                 gboolean retval;
1861                 if (msg_window)
1862                         gtk_window_present (GTK_WINDOW (msg_window));
1863                 g_signal_emit_by_name (G_OBJECT (window), "delete-event", NULL, &retval);
1864                 return TRUE;
1865         }
1866
1867         /* Msg download completed */
1868         if (!(tny_header_get_flags (header) & TNY_HEADER_FLAG_CACHED)) {
1869                 /* Ask the user if he wants to download the message if
1870                    we're not online */
1871                 if (!tny_device_is_online (modest_runtime_get_device())) {
1872                         GtkResponseType response;
1873
1874                         response = modest_platform_run_confirmation_dialog (GTK_WINDOW (window),
1875                                                                             _("mcen_nc_get_msg"));
1876                         if (response == GTK_RESPONSE_CANCEL)
1877                                 return FALSE;
1878                 
1879                         folder = tny_header_get_folder (header);
1880                         info = g_slice_new (MsgReaderInfo);
1881                         info->header = g_object_ref (header);
1882                         info->row_reference = gtk_tree_row_reference_copy (row_reference);
1883
1884                         /* Offer the connection dialog if necessary */
1885                         modest_platform_connect_if_remote_and_perform ((GtkWindow *) window, 
1886                                                                        TRUE,
1887                                                                        TNY_FOLDER_STORE (folder),
1888                                                                        message_reader_performer, 
1889                                                                        info);
1890                         g_object_unref (folder);
1891                         return TRUE;
1892                 }
1893         }
1894         
1895         folder = tny_header_get_folder (header);
1896         account = tny_folder_get_account (folder);
1897         info = g_slice_new (MsgReaderInfo);
1898         info->header = g_object_ref (header);
1899         info->row_reference = gtk_tree_row_reference_copy (row_reference);
1900         
1901         message_reader_performer (FALSE, NULL, (GtkWindow *) window, account, info);
1902         g_object_unref (account);
1903         g_object_unref (folder);
1904
1905         return TRUE;
1906 }
1907
1908 gboolean        
1909 modest_msg_view_window_select_next_message (ModestMsgViewWindow *window)
1910 {
1911         ModestMsgViewWindowPrivate *priv;
1912         GtkTreePath *path= NULL;
1913         GtkTreeIter tmp_iter;
1914         TnyHeader *header;
1915         gboolean retval = TRUE;
1916         GtkTreeRowReference *row_reference = NULL;
1917
1918         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), FALSE);
1919         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1920
1921         if (!priv->row_reference)
1922                 return FALSE;
1923
1924         /* Update the next row reference if it's not valid. This could
1925            happen if for example the header which it was pointing to,
1926            was deleted. The best place to do it is in the row-deleted
1927            handler but the tinymail model do not work like the glib
1928            tree models and reports the deletion when the row is still
1929            there */
1930         if (!gtk_tree_row_reference_valid (priv->next_row_reference)) {
1931                 if (gtk_tree_row_reference_valid (priv->row_reference)) {
1932                         priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
1933                         select_next_valid_row (priv->header_model, &(priv->next_row_reference), FALSE, priv->is_outbox);
1934                 }
1935         }
1936         if (priv->next_row_reference)
1937                 path = gtk_tree_row_reference_get_path (priv->next_row_reference);
1938         if (path == NULL)
1939                 return FALSE;
1940
1941         row_reference = gtk_tree_row_reference_copy (priv->next_row_reference);
1942
1943         gtk_tree_model_get_iter (priv->header_model,
1944                                  &tmp_iter,
1945                                  path);
1946         gtk_tree_path_free (path);
1947
1948         gtk_tree_model_get (priv->header_model, &tmp_iter, 
1949                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1950                             &header, -1);
1951         
1952         /* Read the message & show it */
1953         if (!message_reader (window, priv, header, row_reference)) {
1954                 retval = FALSE;
1955         }
1956         gtk_tree_row_reference_free (row_reference);
1957
1958         /* Free */
1959         g_object_unref (header);
1960
1961         return retval;
1962 }
1963
1964 gboolean        
1965 modest_msg_view_window_select_previous_message (ModestMsgViewWindow *window)
1966 {
1967         ModestMsgViewWindowPrivate *priv = NULL;
1968         GtkTreePath *path;
1969         gboolean finished = FALSE;
1970         gboolean retval = FALSE;
1971
1972         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), FALSE);
1973         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1974
1975         /* Return inmediatly if there is no header model */
1976         if (!priv->header_model || !priv->row_reference)
1977                 return FALSE;
1978
1979         path = gtk_tree_row_reference_get_path (priv->row_reference);
1980         while (!finished && gtk_tree_path_prev (path)) {
1981                 TnyHeader *header;
1982                 GtkTreeIter iter;
1983
1984                 gtk_tree_model_get_iter (priv->header_model, &iter, path);
1985                 gtk_tree_model_get (priv->header_model, &iter, 
1986                                     TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1987                                     &header, -1);
1988                 finished = TRUE;
1989                 if (header) {
1990                         if (msg_is_visible (header, priv->is_outbox)) {
1991                                 GtkTreeRowReference *row_reference;
1992                                 row_reference = gtk_tree_row_reference_new (priv->header_model, path);
1993                                 /* Read the message & show it */
1994                                 retval = message_reader (window, priv, header, row_reference);
1995                                 gtk_tree_row_reference_free (row_reference);
1996                         } else {
1997                                 finished = FALSE;
1998                         }
1999                         g_object_unref (header);
2000                 }
2001         }
2002
2003         gtk_tree_path_free (path);
2004         return retval;
2005 }
2006
2007 static void
2008 view_msg_cb (ModestMailOperation *mail_op, 
2009              TnyHeader *header, 
2010              gboolean canceled,
2011              TnyMsg *msg, 
2012              GError *error,
2013              gpointer user_data)
2014 {
2015         ModestMsgViewWindow *self = NULL;
2016         ModestMsgViewWindowPrivate *priv = NULL;
2017         GtkTreeRowReference *row_reference = NULL;
2018
2019         /* Unregister the header (it was registered before creating the mail operation) */
2020         modest_window_mgr_unregister_header (modest_runtime_get_window_mgr (), header);
2021
2022         row_reference = (GtkTreeRowReference *) user_data;
2023         if (canceled) {
2024                 gtk_tree_row_reference_free (row_reference);
2025                 return;
2026         }
2027         
2028         /* If there was any error */
2029         if (!modest_ui_actions_msg_retrieval_check (mail_op, header, msg)) {
2030                 gtk_tree_row_reference_free (row_reference);                    
2031                 return;
2032         }
2033
2034         /* Get the window */ 
2035         self = (ModestMsgViewWindow *) modest_mail_operation_get_source (mail_op);
2036         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self));
2037         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2038
2039         /* Update the row reference */
2040         if (priv->row_reference != NULL) {
2041                 gtk_tree_row_reference_free (priv->row_reference);
2042                 priv->row_reference = gtk_tree_row_reference_copy (row_reference);
2043                 if (priv->next_row_reference != NULL) {
2044                         gtk_tree_row_reference_free (priv->next_row_reference);
2045                 }
2046                 priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
2047                 select_next_valid_row (priv->header_model, &(priv->next_row_reference), TRUE, priv->is_outbox);
2048         }
2049
2050         /* Mark header as read */
2051         if (!(tny_header_get_flags (header) & TNY_HEADER_FLAG_SEEN))
2052                 tny_header_set_flag (header, TNY_HEADER_FLAG_SEEN);
2053
2054         /* Set new message */
2055         if (priv->msg_view != NULL && TNY_IS_MSG_VIEW (priv->msg_view)) {
2056                 tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
2057                 modest_msg_view_window_update_priority (self);
2058                 update_window_title (MODEST_MSG_VIEW_WINDOW (self));
2059                 modest_msg_view_grab_focus (MODEST_MSG_VIEW (priv->msg_view));
2060         }
2061
2062         /* Set the new message uid of the window  */
2063         if (priv->msg_uid) {
2064                 g_free (priv->msg_uid);
2065                 priv->msg_uid = modest_tny_folder_get_header_unique_id (header);
2066         }
2067
2068         /* Notify the observers */
2069         g_signal_emit (G_OBJECT (self), signals[MSG_CHANGED_SIGNAL], 
2070                        0, priv->header_model, priv->row_reference);
2071
2072         /* Frees */
2073         g_object_unref (self);
2074         gtk_tree_row_reference_free (row_reference);            
2075 }
2076
2077 TnyFolderType
2078 modest_msg_view_window_get_folder_type (ModestMsgViewWindow *window)
2079 {
2080         ModestMsgViewWindowPrivate *priv;
2081         TnyMsg *msg;
2082         TnyFolderType folder_type;
2083
2084         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2085
2086         folder_type = TNY_FOLDER_TYPE_UNKNOWN;
2087
2088         msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
2089         if (msg) {
2090                 TnyFolder *folder;
2091
2092                 folder = tny_msg_get_folder (msg);
2093                 if (folder) {
2094                         folder_type = modest_tny_folder_guess_folder_type (folder);
2095                         g_object_unref (folder);
2096                 }
2097                 g_object_unref (msg);
2098         }
2099
2100         return folder_type;
2101 }
2102
2103
2104 static void
2105 modest_msg_view_window_update_priority (ModestMsgViewWindow *window)
2106 {
2107         ModestMsgViewWindowPrivate *priv;
2108         TnyHeader *header = NULL;
2109         TnyHeaderFlags flags = 0;
2110
2111         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2112
2113         if (priv->header_model && priv->row_reference) {
2114                 GtkTreeIter iter;
2115                 GtkTreePath *path = NULL;
2116
2117                 path = gtk_tree_row_reference_get_path (priv->row_reference);
2118                 g_return_if_fail (path != NULL);
2119                 gtk_tree_model_get_iter (priv->header_model, 
2120                                          &iter, 
2121                                          gtk_tree_row_reference_get_path (priv->row_reference));
2122
2123                 gtk_tree_model_get (priv->header_model, &iter, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
2124                                     &header, -1);
2125                 gtk_tree_path_free (path);
2126         } else {
2127                 TnyMsg *msg;
2128                 msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
2129                 if (msg) {
2130                         header = tny_msg_get_header (msg);
2131                         g_object_unref (msg);
2132                 }
2133         }
2134
2135         if (header) {
2136                 flags = tny_header_get_flags (header);
2137                 g_object_unref(G_OBJECT(header));
2138         }
2139
2140         modest_msg_view_set_priority (MODEST_MSG_VIEW(priv->msg_view), flags);
2141
2142 }
2143
2144 static void
2145 toolbar_resize (ModestMsgViewWindow *self)
2146 {
2147         ModestMsgViewWindowPrivate *priv = NULL;
2148         ModestWindowPrivate *parent_priv = NULL;
2149         GtkWidget *widget;
2150         gint static_button_size;
2151         ModestWindowMgr *mgr;
2152
2153         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self));
2154         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2155         parent_priv = MODEST_WINDOW_GET_PRIVATE(self);
2156
2157         mgr = modest_runtime_get_window_mgr ();
2158         static_button_size = modest_window_mgr_get_fullscreen_mode (mgr)?118:108;
2159
2160         if (parent_priv->toolbar) {
2161                 /* left size buttons */
2162                 widget = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageReply");
2163                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (widget), FALSE);
2164                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (widget), FALSE);
2165                 gtk_widget_set_size_request (GTK_WIDGET (widget), static_button_size, -1);
2166                 widget = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageMoveTo");
2167                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (widget), FALSE);
2168                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (widget), FALSE);
2169                 gtk_widget_set_size_request (GTK_WIDGET (widget), static_button_size, -1);
2170                 widget = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarDeleteMessage");
2171                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (widget), FALSE);
2172                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (widget), FALSE);
2173                 gtk_widget_set_size_request (GTK_WIDGET (widget), static_button_size, -1);
2174                 widget = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/FindInMessage");
2175                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (widget), FALSE);
2176                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (widget), FALSE);
2177                 gtk_widget_set_size_request (GTK_WIDGET (widget), static_button_size, -1);
2178                 
2179                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (priv->progress_toolitem), FALSE);
2180                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->progress_toolitem), TRUE);
2181                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (priv->cancel_toolitem), FALSE);
2182                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->cancel_toolitem), FALSE);
2183                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (priv->next_toolitem), TRUE);
2184                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->next_toolitem), TRUE);
2185                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (priv->prev_toolitem), TRUE);
2186                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->prev_toolitem), TRUE);
2187         }
2188                 
2189 }
2190
2191 static gboolean
2192 modest_msg_view_window_window_state_event (GtkWidget *widget, GdkEventWindowState *event, gpointer userdata)
2193 {
2194         if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) {
2195                 ModestWindowPrivate *parent_priv;
2196                 ModestWindowMgr *mgr;
2197                 gboolean is_fullscreen;
2198                 GtkAction *fs_toggle_action;
2199                 gboolean active;
2200
2201                 mgr = modest_runtime_get_window_mgr ();
2202                 is_fullscreen = (modest_window_mgr_get_fullscreen_mode (mgr))?1:0;
2203
2204                 parent_priv = MODEST_WINDOW_GET_PRIVATE (widget);
2205                 
2206                 fs_toggle_action = gtk_ui_manager_get_action (parent_priv->ui_manager, "/MenuBar/ViewMenu/ViewToggleFullscreenMenu");
2207                 active = (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (fs_toggle_action)))?1:0;
2208                 if (is_fullscreen != active) {
2209                         gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (fs_toggle_action), is_fullscreen);
2210                 }
2211                 toolbar_resize (MODEST_MSG_VIEW_WINDOW (widget));
2212         }
2213
2214         return FALSE;
2215
2216 }
2217
2218 static void
2219 modest_msg_view_window_show_toolbar (ModestWindow *self,
2220                                      gboolean show_toolbar)
2221 {
2222         ModestMsgViewWindowPrivate *priv = NULL;
2223         ModestWindowPrivate *parent_priv;
2224         GtkWidget *reply_button = NULL, *menu = NULL;
2225         GtkWidget *placeholder = NULL;
2226         gint insert_index;
2227         const gchar *action_name;
2228         GtkAction *action;
2229         
2230         parent_priv = MODEST_WINDOW_GET_PRIVATE(self);
2231         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2232
2233         /* Set optimized view status */
2234         priv->optimized_view = !show_toolbar;
2235
2236         if (!parent_priv->toolbar) {
2237                 parent_priv->toolbar = gtk_ui_manager_get_widget (parent_priv->ui_manager, 
2238                                                                   "/ToolBar");
2239                 gtk_widget_set_no_show_all (parent_priv->toolbar, TRUE);
2240
2241                 priv->progress_toolitem = GTK_WIDGET (gtk_tool_item_new ());
2242                 priv->cancel_toolitem = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarCancel");
2243                 priv->next_toolitem = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageNext");
2244                 priv->prev_toolitem = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageBack");
2245                 toolbar_resize (MODEST_MSG_VIEW_WINDOW (self));
2246
2247                 /* Add ProgressBar (Transfer toolbar) */ 
2248                 priv->progress_bar = modest_progress_bar_new ();
2249                 gtk_widget_set_no_show_all (priv->progress_bar, TRUE);
2250                 placeholder = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ProgressbarView");
2251                 insert_index = gtk_toolbar_get_item_index(GTK_TOOLBAR (parent_priv->toolbar), GTK_TOOL_ITEM(placeholder));
2252                 gtk_container_add (GTK_CONTAINER (priv->progress_toolitem), priv->progress_bar);
2253                 gtk_toolbar_insert(GTK_TOOLBAR(parent_priv->toolbar), GTK_TOOL_ITEM (priv->progress_toolitem), insert_index);
2254                 
2255                 /* Connect cancel 'clicked' signal to abort progress mode */
2256                 g_signal_connect(priv->cancel_toolitem, "clicked",
2257                                  G_CALLBACK(cancel_progressbar),
2258                                  self);
2259                 
2260                 /* Add it to the observers list */
2261                 priv->progress_widgets = g_slist_prepend(priv->progress_widgets, priv->progress_bar);
2262
2263                 /* Add to window */
2264                 hildon_window_add_toolbar (HILDON_WINDOW (self), 
2265                                            GTK_TOOLBAR (parent_priv->toolbar));
2266
2267                 /* Set reply button tap and hold menu */        
2268                 reply_button = gtk_ui_manager_get_widget (parent_priv->ui_manager, 
2269                                                           "/ToolBar/ToolbarMessageReply");
2270                 menu = gtk_ui_manager_get_widget (parent_priv->ui_manager, 
2271                                                   "/ToolbarReplyCSM");
2272                 gtk_widget_tap_and_hold_setup (GTK_WIDGET (reply_button), menu, NULL, 0);
2273         }
2274
2275         if (show_toolbar) {
2276                 /* Quick hack: this prevents toolbar icons "dance" when progress bar show status is changed */ 
2277                 /* TODO: resize mode migth be GTK_RESIZE_QUEUE, in order to avoid unneccesary shows */
2278                 gtk_container_set_resize_mode (GTK_CONTAINER(parent_priv->toolbar), GTK_RESIZE_IMMEDIATE);
2279
2280                 gtk_widget_show (GTK_WIDGET (parent_priv->toolbar));
2281                 if (modest_msg_view_window_transfer_mode_enabled (MODEST_MSG_VIEW_WINDOW (self))) 
2282                         set_toolbar_mode (MODEST_MSG_VIEW_WINDOW (self), TOOLBAR_MODE_TRANSFER);
2283                 else
2284                         set_toolbar_mode (MODEST_MSG_VIEW_WINDOW (self), TOOLBAR_MODE_NORMAL);
2285
2286         } else {
2287                 gtk_widget_set_no_show_all (parent_priv->toolbar, TRUE);
2288                 gtk_widget_hide (GTK_WIDGET (parent_priv->toolbar));
2289         }
2290
2291         /* Update also the actions (to update the toggles in the
2292            menus), we have to do it manually because some other window
2293            of the same time could have changed it (remember that the
2294            toolbar fullscreen mode is shared by all the windows of the
2295            same type */
2296         if (modest_window_mgr_get_fullscreen_mode (modest_runtime_get_window_mgr ()))
2297                 action_name = "/MenuBar/ViewMenu/ViewShowToolbarMenu/ViewShowToolbarFullScreenMenu";
2298         else
2299                 action_name = "/MenuBar/ViewMenu/ViewShowToolbarMenu/ViewShowToolbarNormalScreenMenu";
2300
2301         action = gtk_ui_manager_get_action (parent_priv->ui_manager, action_name);
2302         modest_utils_toggle_action_set_active_block_notify (GTK_TOGGLE_ACTION (action),
2303                                                             show_toolbar);
2304 }
2305
2306 static void 
2307 modest_msg_view_window_clipboard_owner_change (GtkClipboard *clipboard,
2308                                                GdkEvent *event,
2309                                                ModestMsgViewWindow *window)
2310 {
2311         if (!GTK_WIDGET_VISIBLE (window))
2312                 return;
2313
2314         modest_window_check_dimming_rules_group (MODEST_WINDOW (window), MODEST_DIMMING_RULES_CLIPBOARD);
2315 }
2316
2317 gboolean 
2318 modest_msg_view_window_transfer_mode_enabled (ModestMsgViewWindow *self)
2319 {
2320         ModestMsgViewWindowPrivate *priv;
2321         
2322         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), FALSE); 
2323         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2324
2325         return priv->current_toolbar_mode == TOOLBAR_MODE_TRANSFER;
2326 }
2327
2328 static void
2329 cancel_progressbar (GtkToolButton *toolbutton,
2330                     ModestMsgViewWindow *self)
2331 {
2332         GSList *tmp;
2333         ModestMsgViewWindowPrivate *priv;
2334         
2335         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2336
2337         /* Get operation observers and cancel its current operation */
2338         tmp = priv->progress_widgets;
2339         while (tmp) {
2340                 modest_progress_object_cancel_current_operation (MODEST_PROGRESS_OBJECT(tmp->data));
2341                 tmp=g_slist_next(tmp);
2342         }
2343 }
2344 static gboolean
2345 observers_empty (ModestMsgViewWindow *self)
2346 {
2347         GSList *tmp = NULL;
2348         ModestMsgViewWindowPrivate *priv;
2349         gboolean is_empty = TRUE;
2350         guint pending_ops = 0;
2351  
2352         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2353         tmp = priv->progress_widgets;
2354
2355         /* Check all observers */
2356         while (tmp && is_empty)  {
2357                 pending_ops = modest_progress_object_num_pending_operations (MODEST_PROGRESS_OBJECT(tmp->data));
2358                 is_empty = pending_ops == 0;
2359                 
2360                 tmp = g_slist_next(tmp);
2361         }
2362         
2363         return is_empty;
2364 }
2365
2366 static void
2367 on_account_removed (TnyAccountStore *account_store, 
2368                     TnyAccount *account,
2369                     gpointer user_data)
2370 {
2371         /* Do nothing if it's a transport account, because we only
2372            show the messages of a store account */
2373         if (tny_account_get_account_type(account) == TNY_ACCOUNT_TYPE_STORE) {
2374                 const gchar *parent_acc = NULL;
2375                 const gchar *our_acc = NULL;
2376
2377                 our_acc = modest_window_get_active_account (MODEST_WINDOW (user_data));
2378                 parent_acc = modest_tny_account_get_parent_modest_account_name_for_server_account (account);
2379
2380                 /* Close this window if I'm showing a message of the removed account */
2381                 if (strcmp (parent_acc, our_acc) == 0)
2382                         modest_ui_actions_on_close_window (NULL, MODEST_WINDOW (user_data));
2383         }
2384 }
2385
2386 static void 
2387 on_mail_operation_started (ModestMailOperation *mail_op,
2388                            gpointer user_data)
2389 {
2390         ModestMsgViewWindow *self;
2391         ModestMailOperationTypeOperation op_type;
2392         GSList *tmp;
2393         ModestMsgViewWindowPrivate *priv;
2394         GObject *source = NULL;
2395
2396         self = MODEST_MSG_VIEW_WINDOW (user_data);
2397         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2398         op_type = modest_mail_operation_get_type_operation (mail_op);
2399         tmp = priv->progress_widgets;
2400         source = modest_mail_operation_get_source(mail_op);
2401         if (G_OBJECT (self) == source) {
2402                 if (op_type == MODEST_MAIL_OPERATION_TYPE_RECEIVE || op_type == MODEST_MAIL_OPERATION_TYPE_OPEN ) {
2403                         set_toolbar_transfer_mode(self);
2404                         while (tmp) {
2405                                 modest_progress_object_add_operation (
2406                                                 MODEST_PROGRESS_OBJECT (tmp->data),
2407                                                 mail_op);
2408                                 tmp = g_slist_next (tmp);
2409                         }
2410                 }
2411         }
2412         g_object_unref (source);
2413 }
2414
2415 static void 
2416 on_mail_operation_finished (ModestMailOperation *mail_op,
2417                             gpointer user_data)
2418 {
2419         ModestMsgViewWindow *self;
2420         ModestMailOperationTypeOperation op_type;
2421         GSList *tmp;
2422         ModestMsgViewWindowPrivate *priv;
2423         
2424         self = MODEST_MSG_VIEW_WINDOW (user_data);
2425         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2426         op_type = modest_mail_operation_get_type_operation (mail_op);
2427         tmp = priv->progress_widgets;
2428         
2429         if (op_type == MODEST_MAIL_OPERATION_TYPE_RECEIVE || op_type == MODEST_MAIL_OPERATION_TYPE_OPEN ) {
2430                 while (tmp) {
2431                         modest_progress_object_remove_operation (MODEST_PROGRESS_OBJECT (tmp->data),
2432                                                                  mail_op);
2433                         tmp = g_slist_next (tmp);
2434                 }
2435
2436                 /* If no more operations are being observed, NORMAL mode is enabled again */
2437                 if (observers_empty (self)) {
2438                         set_toolbar_mode (self, TOOLBAR_MODE_NORMAL);
2439                 }
2440
2441                 /* Update dimming rules. We have to do this right here
2442                    and not in view_msg_cb because at that point the
2443                    transfer mode is still enabled so the dimming rule
2444                    won't let the user delete the message that has been
2445                    readed for example */
2446                 modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (self));
2447                 modest_ui_actions_check_menu_dimming_rules (MODEST_WINDOW (self));
2448         }
2449 }
2450
2451 static void
2452 on_queue_changed (ModestMailOperationQueue *queue,
2453                   ModestMailOperation *mail_op,
2454                   ModestMailOperationQueueNotification type,
2455                   ModestMsgViewWindow *self)
2456 {       
2457         ModestMsgViewWindowPrivate *priv;
2458
2459         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2460
2461         /* If this operations was created by another window, do nothing */
2462         if (!modest_mail_operation_is_mine (mail_op, G_OBJECT(self))) 
2463             return;
2464
2465         if (type == MODEST_MAIL_OPERATION_QUEUE_OPERATION_ADDED) {
2466                 priv->sighandlers = modest_signal_mgr_connect (priv->sighandlers,
2467                                                                G_OBJECT (mail_op),
2468                                                                "operation-started",
2469                                                                G_CALLBACK (on_mail_operation_started),
2470                                                                self);
2471                 priv->sighandlers = modest_signal_mgr_connect (priv->sighandlers,
2472                                                                G_OBJECT (mail_op),
2473                                                                "operation-finished",
2474                                                                G_CALLBACK (on_mail_operation_finished),
2475                                                                self);
2476         } else if (type == MODEST_MAIL_OPERATION_QUEUE_OPERATION_REMOVED) {
2477                 priv->sighandlers = modest_signal_mgr_disconnect (priv->sighandlers,
2478                                                                   G_OBJECT (mail_op),
2479                                                                   "operation-started");
2480                 priv->sighandlers = modest_signal_mgr_disconnect (priv->sighandlers,
2481                                                                   G_OBJECT (mail_op),
2482                                                                   "operation-finished");
2483         }
2484 }
2485
2486 TnyList *
2487 modest_msg_view_window_get_attachments (ModestMsgViewWindow *win) 
2488 {
2489         ModestMsgViewWindowPrivate *priv;
2490         TnyList *selected_attachments = NULL;
2491         
2492         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (win), NULL);
2493         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (win);
2494
2495         selected_attachments = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2496         
2497         return selected_attachments;
2498 }
2499
2500 typedef struct {
2501         gchar *filepath;
2502         GtkWidget *banner;
2503         guint banner_idle_id;
2504 } DecodeAsyncHelper;
2505
2506 static gboolean
2507 decode_async_banner_idle (gpointer user_data)
2508 {
2509         DecodeAsyncHelper *helper = (DecodeAsyncHelper *) user_data;
2510
2511         helper->banner_idle_id = 0;
2512         helper->banner = hildon_banner_show_animation (NULL, NULL, _("mail_me_opening"));
2513         g_object_ref (helper->banner);
2514
2515         return FALSE;
2516 }
2517
2518 static void
2519 on_decode_to_stream_async_handler (TnyMimePart *mime_part, 
2520                                    gboolean cancelled, 
2521                                    TnyStream *stream, 
2522                                    GError *err, 
2523                                    gpointer user_data)
2524 {
2525         DecodeAsyncHelper *helper = (DecodeAsyncHelper *) user_data;
2526
2527         if (helper->banner_idle_id > 0) {
2528                 g_source_remove (helper->banner_idle_id);
2529                 helper->banner_idle_id = 0;
2530         }
2531         if (helper->banner) {
2532                 gtk_widget_destroy (helper->banner);
2533         }
2534         if (cancelled || err) {
2535                 modest_platform_information_banner (NULL, NULL, 
2536                                                     _("mail_ib_file_operation_failed"));
2537                 goto free;
2538         }
2539
2540         /* make the file read-only */
2541         g_chmod(helper->filepath, 0444);
2542         
2543         /* Activate the file */
2544         modest_platform_activate_file (helper->filepath, tny_mime_part_get_content_type (mime_part));
2545
2546  free:
2547         /* Frees */
2548         g_free (helper->filepath);
2549         g_object_unref (helper->banner);
2550         g_slice_free (DecodeAsyncHelper, helper);
2551 }
2552
2553 void
2554 modest_msg_view_window_view_attachment (ModestMsgViewWindow *window, TnyMimePart *mime_part)
2555 {
2556         ModestMsgViewWindowPrivate *priv;
2557         const gchar *msg_uid;
2558         gchar *attachment_uid = NULL;
2559         gint attachment_index = 0;
2560         TnyList *attachments;
2561
2562         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
2563         g_return_if_fail (TNY_IS_MIME_PART (mime_part) || (mime_part == NULL));
2564         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2565
2566         msg_uid = modest_msg_view_window_get_message_uid (MODEST_MSG_VIEW_WINDOW (window));
2567         attachments = modest_msg_view_get_attachments (MODEST_MSG_VIEW (priv->msg_view));
2568         attachment_index = modest_list_index (attachments, (GObject *) mime_part);
2569         g_object_unref (attachments);
2570         
2571         if (msg_uid && attachment_index >= 0) {
2572                 attachment_uid = g_strdup_printf ("%s/%d", msg_uid, attachment_index);
2573         }
2574
2575         if (mime_part == NULL) {
2576                 gboolean error = FALSE;
2577                 TnyList *selected_attachments = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2578                 if (selected_attachments == NULL || tny_list_get_length (selected_attachments) == 0) {
2579                         error = TRUE;
2580                 } else if (tny_list_get_length (selected_attachments) > 1) {
2581                         hildon_banner_show_information (NULL, NULL, _("mcen_ib_unable_to_display_more"));
2582                         error = TRUE;
2583                 } else {
2584                         TnyIterator *iter;
2585                         iter = tny_list_create_iterator (selected_attachments);
2586                         mime_part = (TnyMimePart *) tny_iterator_get_current (iter);
2587                         g_object_unref (iter);
2588                 }
2589                 g_object_unref (selected_attachments);
2590
2591                 if (error)
2592                         return;
2593         } else {
2594                 g_object_ref (mime_part);
2595         }
2596
2597         if (tny_mime_part_is_purged (mime_part)) {
2598                 g_object_unref (mime_part);
2599                 return;
2600         }
2601
2602         if (!modest_tny_mime_part_is_msg (mime_part)) {
2603                 gchar *filepath = NULL;
2604                 const gchar *att_filename = tny_mime_part_get_filename (mime_part);
2605                 gboolean show_error_banner = FALSE;
2606                 TnyFsStream *temp_stream = NULL;
2607                 temp_stream = modest_utils_create_temp_stream (att_filename, attachment_uid,
2608                                                                &filepath);
2609                 
2610                 if (temp_stream != NULL) {
2611                         DecodeAsyncHelper *helper = g_slice_new (DecodeAsyncHelper);
2612                         helper->filepath = g_strdup (filepath);
2613                         helper->banner = NULL;
2614                         helper->banner_idle_id = g_timeout_add (1000, decode_async_banner_idle, helper);
2615                         tny_mime_part_decode_to_stream_async (mime_part, TNY_STREAM (temp_stream), 
2616                                                               on_decode_to_stream_async_handler, 
2617                                                               NULL, 
2618                                                               helper);
2619                         g_object_unref (temp_stream);
2620                         /* NOTE: files in the temporary area will be automatically
2621                          * cleaned after some time if they are no longer in use */
2622                 } else {
2623                         if (filepath != NULL) {
2624                                 const gchar *content_type;
2625                                 /* the file may already exist but it isn't writable,
2626                                  * let's try to open it anyway */
2627                                 content_type = tny_mime_part_get_content_type (mime_part);
2628                                 modest_platform_activate_file (filepath, content_type);
2629                                 g_free (filepath);
2630                         } else {
2631                                 g_warning ("%s: modest_utils_create_temp_stream failed", __FUNCTION__);
2632                                 show_error_banner = TRUE;
2633                         }
2634                 }
2635                 if (show_error_banner)
2636                         modest_platform_information_banner (NULL, NULL, _("mail_ib_file_operation_failed"));
2637         } else {
2638                 /* message attachment */
2639                 TnyHeader *header = NULL;
2640                 ModestWindowMgr *mgr;
2641                 ModestWindow *msg_win = NULL;
2642                 gboolean found;
2643                 
2644                 header = tny_msg_get_header (TNY_MSG (mime_part));
2645                 mgr = modest_runtime_get_window_mgr ();         
2646                 found = modest_window_mgr_find_registered_header (mgr, header, &msg_win);
2647
2648                 if (found) {
2649                         if (msg_win)                            /* there is already a window for this uid; top it */
2650                                 gtk_window_present (GTK_WINDOW(msg_win));
2651                         else 
2652                                 /* if it's found, but there is no msg_win, it's probably in the process of being created;
2653                                  * thus, we don't do anything */
2654                                 g_warning ("window for is already being created");
2655                 } else { 
2656                         /* it's not found, so create a new window for it */
2657                         modest_window_mgr_register_header (mgr, header, attachment_uid); /* register the uid before building the window */
2658                         gchar *account = g_strdup (modest_window_get_active_account (MODEST_WINDOW (window)));
2659                         if (!account)
2660                                 account = modest_account_mgr_get_default_account (modest_runtime_get_account_mgr ());
2661                         msg_win = modest_msg_view_window_new_for_attachment (TNY_MSG (mime_part), account, attachment_uid);
2662                         modest_window_set_zoom (MODEST_WINDOW (msg_win), 
2663                                                 modest_window_get_zoom (MODEST_WINDOW (window)));
2664                         modest_window_mgr_register_window (mgr, msg_win);
2665                         gtk_widget_show_all (GTK_WIDGET (msg_win));
2666                 }
2667         }
2668         g_object_unref (mime_part);
2669 }
2670
2671 typedef struct
2672 {
2673         gchar *filename;
2674         TnyMimePart *part;
2675 } SaveMimePartPair;
2676
2677 typedef struct
2678 {
2679         GList *pairs;
2680         GtkWidget *banner;
2681         GnomeVFSResult result;
2682 } SaveMimePartInfo;
2683
2684 static void save_mime_part_info_free (SaveMimePartInfo *info, gboolean with_struct);
2685 static gboolean idle_save_mime_part_show_result (SaveMimePartInfo *info);
2686 static gpointer save_mime_part_to_file (SaveMimePartInfo *info);
2687 static void save_mime_parts_to_file_with_checks (SaveMimePartInfo *info);
2688
2689 static void 
2690 save_mime_part_info_free (SaveMimePartInfo *info, gboolean with_struct)
2691 {
2692         
2693         GList *node;
2694         for (node = info->pairs; node != NULL; node = g_list_next (node)) {
2695                 SaveMimePartPair *pair = (SaveMimePartPair *) node->data;
2696                 g_free (pair->filename);
2697                 g_object_unref (pair->part);
2698                 g_slice_free (SaveMimePartPair, pair);
2699         }
2700         g_list_free (info->pairs);
2701         info->pairs = NULL;
2702         if (with_struct) {
2703                 gtk_widget_destroy (info->banner);
2704                 g_slice_free (SaveMimePartInfo, info);
2705         }
2706 }
2707
2708 static gboolean
2709 idle_save_mime_part_show_result (SaveMimePartInfo *info)
2710 {
2711         if (info->pairs != NULL) {
2712                 save_mime_part_to_file (info);
2713         } else {
2714                 /* This is a GDK lock because we are an idle callback and
2715                  * hildon_banner_show_information is or does Gtk+ code */
2716
2717                 gdk_threads_enter (); /* CHECKED */
2718                 save_mime_part_info_free (info, TRUE);
2719                 if (info->result == GNOME_VFS_OK) {
2720                         hildon_banner_show_information (NULL, NULL, _CS("sfil_ib_saved"));
2721                 } else if (info->result == GNOME_VFS_ERROR_NO_SPACE) {
2722                         hildon_banner_show_information (NULL, NULL, dgettext("ke-recv", 
2723                                                                              "cerm_device_memory_full"));
2724                 } else {
2725                         hildon_banner_show_information (NULL, NULL, _("mail_ib_file_operation_failed"));
2726                 }
2727                 gdk_threads_leave (); /* CHECKED */
2728         }
2729
2730         return FALSE;
2731 }
2732
2733 static gpointer
2734 save_mime_part_to_file (SaveMimePartInfo *info)
2735 {
2736         GnomeVFSHandle *handle;
2737         TnyStream *stream;
2738         SaveMimePartPair *pair = (SaveMimePartPair *) info->pairs->data;
2739
2740         info->result = gnome_vfs_create (&handle, pair->filename, GNOME_VFS_OPEN_WRITE, FALSE, 0644);
2741         if (info->result == GNOME_VFS_OK) {
2742                 GError *error = NULL;
2743                 stream = tny_vfs_stream_new (handle);
2744                 if (tny_mime_part_decode_to_stream (pair->part, stream, &error) < 0) {
2745                         g_warning ("modest: could not save attachment %s: %d (%s)\n", pair->filename, error?error->code:-1, error?error->message:"Unknown error");
2746                         
2747                         info->result = GNOME_VFS_ERROR_IO;
2748                 }
2749                 g_object_unref (G_OBJECT (stream));
2750                 g_object_unref (pair->part);
2751                 g_slice_free (SaveMimePartPair, pair);
2752                 info->pairs = g_list_delete_link (info->pairs, info->pairs);
2753         } else {
2754                 g_warning ("modest: could not create save attachment %s: %s\n", pair->filename, gnome_vfs_result_to_string (info->result));
2755                 save_mime_part_info_free (info, FALSE);
2756         }
2757
2758         g_idle_add ((GSourceFunc) idle_save_mime_part_show_result, info);
2759         return NULL;
2760 }
2761
2762 static void
2763 save_mime_parts_to_file_with_checks (SaveMimePartInfo *info)
2764 {
2765         gboolean is_ok = TRUE;
2766         gint replaced_files = 0;
2767         const GList *files = info->pairs;
2768         const GList *iter;
2769
2770         for (iter = files; (iter != NULL) && (replaced_files < 2); iter = g_list_next(iter)) {
2771                 SaveMimePartPair *pair = iter->data;
2772                 if (modest_utils_file_exists (pair->filename)) {
2773                         replaced_files++;
2774                 }
2775         }
2776         if (replaced_files) {
2777                 GtkWidget *confirm_overwrite_dialog;
2778                 const gchar *message = (replaced_files == 1) ?
2779                         _FM("docm_nc_replace_file") : _FM("docm_nc_replace_multiple");
2780                 confirm_overwrite_dialog = hildon_note_new_confirmation (NULL, message);
2781                 if (gtk_dialog_run (GTK_DIALOG (confirm_overwrite_dialog)) != GTK_RESPONSE_OK) {
2782                         is_ok = FALSE;
2783                 }
2784                 gtk_widget_destroy (confirm_overwrite_dialog);
2785         }
2786
2787         if (!is_ok) {
2788                 save_mime_part_info_free (info, TRUE);
2789         } else {
2790                 GtkWidget *banner = hildon_banner_show_animation (NULL, NULL, 
2791                                                                   _CS("sfil_ib_saving"));
2792                 info->banner = banner;
2793                 g_thread_create ((GThreadFunc)save_mime_part_to_file, info, FALSE, NULL);
2794         }
2795
2796 }
2797
2798 void
2799 modest_msg_view_window_save_attachments (ModestMsgViewWindow *window, TnyList *mime_parts)
2800 {
2801         ModestMsgViewWindowPrivate *priv;
2802         GList *files_to_save = NULL;
2803         GtkWidget *save_dialog = NULL;
2804         gchar *folder = NULL;
2805         const gchar *filename = NULL;
2806         gchar *save_multiple_str = NULL;
2807
2808         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
2809         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2810
2811         if (mime_parts == NULL) {
2812                 mime_parts = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2813                 if (mime_parts == NULL || tny_list_get_length (mime_parts) == 0)
2814                         return;
2815         } else {
2816                 g_object_ref (mime_parts);
2817         }
2818
2819         /* prepare dialog */
2820         if (tny_list_get_length (mime_parts) == 1) {
2821                 TnyIterator *iter;
2822                 /* only one attachment selected */
2823                 iter = tny_list_create_iterator (mime_parts);
2824                 TnyMimePart *mime_part = (TnyMimePart *) tny_iterator_get_current (iter);
2825                 g_object_unref (iter);
2826                 if (!modest_tny_mime_part_is_msg (mime_part) && 
2827                     modest_tny_mime_part_is_attachment_for_modest (mime_part) &&
2828                     !tny_mime_part_is_purged (mime_part)) {
2829                         filename = tny_mime_part_get_filename (mime_part);
2830                 } else {
2831                         /* TODO: show any error? */
2832                         g_warning ("Tried to save a non-file attachment");
2833                         g_object_unref (mime_parts);
2834                         return;
2835                 }
2836                 g_object_unref (mime_part);
2837         } else {
2838                 save_multiple_str = g_strdup_printf (_FM("sfil_va_number_of_objects_attachments"), 
2839                                                      tny_list_get_length (mime_parts));
2840         }
2841         
2842         save_dialog = hildon_file_chooser_dialog_new (GTK_WINDOW (window), 
2843                                                       GTK_FILE_CHOOSER_ACTION_SAVE);
2844
2845         /* set folder */
2846         folder = g_build_filename (g_get_home_dir (), DEFAULT_FOLDER, NULL);
2847         gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (save_dialog), folder);
2848         g_free (folder);
2849
2850         /* set filename */
2851         if (filename != NULL)
2852                 gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (save_dialog), 
2853                                                    filename);
2854
2855         /* if multiple, set multiple string */
2856         if (save_multiple_str) {
2857                 g_object_set (G_OBJECT (save_dialog), "save-multiple", save_multiple_str, NULL);
2858                 gtk_window_set_title (GTK_WINDOW (save_dialog), _FM("sfil_ti_save_objects_files"));
2859         }
2860                 
2861         /* show dialog */
2862         if (gtk_dialog_run (GTK_DIALOG (save_dialog)) == GTK_RESPONSE_OK) {
2863                 gchar *chooser_uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (save_dialog));
2864
2865                 if (!modest_utils_folder_writable (chooser_uri)) {
2866                         hildon_banner_show_information 
2867                                 (NULL, NULL, dgettext("hildon-fm", "sfil_ib_readonly_location"));
2868                 } else {
2869                         TnyIterator *iter;
2870
2871                         iter = tny_list_create_iterator (mime_parts);
2872                         while (!tny_iterator_is_done (iter)) {
2873                                 TnyMimePart *mime_part = (TnyMimePart *) tny_iterator_get_current (iter);
2874
2875                                 if ((modest_tny_mime_part_is_attachment_for_modest (mime_part)) &&
2876                                     !tny_mime_part_is_purged (mime_part) &&
2877                                     (tny_mime_part_get_filename (mime_part) != NULL)) {
2878                                         SaveMimePartPair *pair;
2879                                         
2880                                         pair = g_slice_new0 (SaveMimePartPair);
2881                                         if (save_multiple_str) {
2882                                                 gchar *escaped = gnome_vfs_escape_slashes (
2883                                                         tny_mime_part_get_filename (mime_part));
2884                                                 pair->filename = g_build_filename (chooser_uri, escaped, NULL);
2885                                                 g_free (escaped);
2886                                         } else {
2887                                                 pair->filename = g_strdup (chooser_uri);
2888                                         }
2889                                         pair->part = mime_part;
2890                                         files_to_save = g_list_prepend (files_to_save, pair);
2891                                 }
2892                                 tny_iterator_next (iter);
2893                         }
2894                         g_object_unref (iter);
2895                 }
2896                 g_free (chooser_uri);
2897         }
2898
2899         gtk_widget_destroy (save_dialog);
2900
2901         g_object_unref (mime_parts);
2902
2903         if (files_to_save != NULL) {
2904                 SaveMimePartInfo *info = g_slice_new0 (SaveMimePartInfo);
2905                 info->pairs = files_to_save;
2906                 info->result = TRUE;
2907                 save_mime_parts_to_file_with_checks (info);
2908         }
2909 }
2910
2911 static gboolean
2912 show_remove_attachment_information (gpointer userdata)
2913 {
2914         ModestMsgViewWindow *window = (ModestMsgViewWindow *) userdata;
2915         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2916
2917         /* We're outside the main lock */
2918         gdk_threads_enter ();
2919
2920         if (priv->remove_attachment_banner != NULL) {
2921                 gtk_widget_destroy (priv->remove_attachment_banner);
2922                 g_object_unref (priv->remove_attachment_banner);
2923         }
2924
2925         priv->remove_attachment_banner = g_object_ref (
2926                 hildon_banner_show_animation (NULL, NULL, _("mcen_ib_removing_attachment")));
2927
2928         gdk_threads_leave ();
2929
2930         return FALSE;
2931 }
2932
2933 void
2934 modest_msg_view_window_remove_attachments (ModestMsgViewWindow *window, gboolean get_all)
2935 {
2936         ModestMsgViewWindowPrivate *priv;
2937         TnyList *mime_parts = NULL;
2938         gchar *confirmation_message;
2939         gint response;
2940         gint n_attachments;
2941         TnyMsg *msg;
2942         TnyIterator *iter;
2943
2944         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
2945         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2946
2947         if (get_all)
2948                 mime_parts = modest_msg_view_get_attachments (MODEST_MSG_VIEW (priv->msg_view));
2949         else
2950                 mime_parts = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2951                 
2952         /* Remove already purged messages from mime parts list */
2953         iter = tny_list_create_iterator (mime_parts);
2954         while (!tny_iterator_is_done (iter)) {
2955                 TnyMimePart *part = TNY_MIME_PART (tny_iterator_get_current (iter));
2956                 tny_iterator_next (iter);
2957                 if (tny_mime_part_is_purged (part)) {
2958                         tny_list_remove (mime_parts, (GObject *) part);
2959                 }
2960                 g_object_unref (part);
2961         }
2962         g_object_unref (iter);
2963
2964         if (tny_list_get_length (mime_parts) == 0) {
2965                 g_object_unref (mime_parts);
2966                 return;
2967         }
2968
2969         n_attachments = tny_list_get_length (mime_parts);
2970         if (n_attachments == 1) {
2971                 gchar *filename;
2972                 TnyMimePart *part;
2973
2974                 iter = tny_list_create_iterator (mime_parts);
2975                 part = (TnyMimePart *) tny_iterator_get_current (iter);
2976                 g_object_unref (iter);
2977                 if (modest_tny_mime_part_is_msg (part)) {
2978                         TnyHeader *header;
2979                         header = tny_msg_get_header (TNY_MSG (part));
2980                         filename = tny_header_dup_subject (header);
2981                         g_object_unref (header);
2982                         if (filename == NULL)
2983                                 filename = g_strdup (_("mail_va_no_subject"));
2984                 } else {
2985                         filename = g_strdup (tny_mime_part_get_filename (TNY_MIME_PART (part)));
2986                 }
2987                 confirmation_message = g_strdup_printf (_("mcen_nc_purge_file_text"), filename);
2988                 g_free (filename);
2989                 g_object_unref (part);
2990         } else {
2991                 confirmation_message = g_strdup_printf (ngettext("mcen_nc_purge_file_text", 
2992                                                                  "mcen_nc_purge_files_text", 
2993                                                                  n_attachments), n_attachments);
2994         }
2995         response = modest_platform_run_confirmation_dialog (GTK_WINDOW (window),
2996                                                             confirmation_message);
2997         g_free (confirmation_message);
2998
2999         if (response != GTK_RESPONSE_OK) {
3000                 g_object_unref (mime_parts);
3001                 return;
3002         }
3003
3004         priv->purge_timeout = g_timeout_add (2000, show_remove_attachment_information, window);
3005         
3006         iter = tny_list_create_iterator (mime_parts);
3007         while (!tny_iterator_is_done (iter)) {
3008                 TnyMimePart *part;
3009
3010                 part = (TnyMimePart *) tny_iterator_get_current (iter);
3011                 tny_mime_part_set_purged (TNY_MIME_PART (part));
3012                 g_object_unref (part);
3013                 tny_iterator_next (iter);
3014         }
3015         g_object_unref (iter);
3016
3017         msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
3018         tny_msg_view_clear (TNY_MSG_VIEW (priv->msg_view));
3019         tny_msg_rewrite_cache (msg);
3020         tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
3021         g_object_unref (msg);
3022
3023         g_object_unref (mime_parts);
3024
3025         if (priv->purge_timeout > 0) {
3026                 g_source_remove (priv->purge_timeout);
3027                 priv->purge_timeout = 0;
3028         }
3029
3030         if (priv->remove_attachment_banner) {
3031                 gtk_widget_destroy (priv->remove_attachment_banner);
3032                 g_object_unref (priv->remove_attachment_banner);
3033                 priv->remove_attachment_banner = NULL;
3034         }
3035
3036
3037 }
3038
3039
3040 static void
3041 update_window_title (ModestMsgViewWindow *window)
3042 {
3043         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
3044         TnyMsg *msg = NULL;
3045         TnyHeader *header = NULL;
3046         gchar *subject = NULL;
3047         
3048         msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
3049
3050         if (msg != NULL) {
3051                 header = tny_msg_get_header (msg);
3052                 subject = tny_header_dup_subject (header);
3053                 g_object_unref (header);
3054                 g_object_unref (msg);
3055         }
3056
3057         if ((subject == NULL)||(subject[0] == '\0')) {
3058                 g_free (subject);
3059                 subject = g_strdup (_("mail_va_no_subject"));
3060         }
3061
3062         gtk_window_set_title (GTK_WINDOW (window), subject);
3063 }
3064
3065
3066 static void on_move_focus (GtkWidget *widget,
3067                            GtkDirectionType direction,
3068                            gpointer userdata)
3069 {
3070         g_signal_stop_emission_by_name (G_OBJECT (widget), "move-focus");
3071 }
3072