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