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