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