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