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