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