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