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