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