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