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