Fixes NB#126941 fixes some potential invalid dereference issues
[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 = NULL;
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                         if (folder)
2005                                 g_object_unref (folder);
2006                         return TRUE;
2007                 }
2008         }
2009
2010         if (header) {
2011                 folder = tny_header_get_folder (header);
2012         }
2013         if (folder)
2014                 account = tny_folder_get_account (folder);
2015
2016         info = g_slice_new (MsgReaderInfo);
2017         info->msg_uid = g_strdup (msg_uid);
2018         if (folder)
2019                 info->folder = g_object_ref (folder);
2020         else
2021                 info->folder = NULL;
2022         if (header)
2023                 info->header = g_object_ref (header);
2024         else
2025                 info->header = NULL;
2026         if (row_reference)
2027                 info->row_reference = gtk_tree_row_reference_copy (row_reference);
2028         else
2029                 info->row_reference = NULL;
2030
2031         message_reader_performer (FALSE, NULL, (GtkWindow *) window, account, info);
2032         if (account)
2033                 g_object_unref (account);
2034         if (folder)
2035                 g_object_unref (folder);
2036
2037         return TRUE;
2038 }
2039
2040 gboolean
2041 modest_msg_view_window_select_next_message (ModestMsgViewWindow *window)
2042 {
2043         ModestMsgViewWindowPrivate *priv;
2044         GtkTreePath *path= NULL;
2045         GtkTreeIter tmp_iter;
2046         TnyHeader *header;
2047         gboolean retval = TRUE;
2048         GtkTreeRowReference *row_reference = NULL;
2049
2050         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), FALSE);
2051         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2052
2053         if (!priv->row_reference)
2054                 return FALSE;
2055
2056         /* Update the next row reference if it's not valid. This could
2057            happen if for example the header which it was pointing to,
2058            was deleted. The best place to do it is in the row-deleted
2059            handler but the tinymail model do not work like the glib
2060            tree models and reports the deletion when the row is still
2061            there */
2062         if (!gtk_tree_row_reference_valid (priv->next_row_reference)) {
2063                 if (gtk_tree_row_reference_valid (priv->row_reference)) {
2064                         priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
2065                         select_next_valid_row (priv->header_model, &(priv->next_row_reference), FALSE, priv->is_outbox);
2066                 }
2067         }
2068         if (priv->next_row_reference)
2069                 path = gtk_tree_row_reference_get_path (priv->next_row_reference);
2070         if (path == NULL)
2071                 return FALSE;
2072
2073         row_reference = gtk_tree_row_reference_copy (priv->next_row_reference);
2074
2075         gtk_tree_model_get_iter (priv->header_model,
2076                                  &tmp_iter,
2077                                  path);
2078         gtk_tree_path_free (path);
2079
2080         gtk_tree_model_get (priv->header_model, &tmp_iter, 
2081                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
2082                             &header, -1);
2083         
2084         /* Read the message & show it */
2085         if (!message_reader (window, priv, header, NULL, NULL, row_reference)) {
2086                 retval = FALSE;
2087         }
2088         gtk_tree_row_reference_free (row_reference);
2089
2090         /* Free */
2091         g_object_unref (header);
2092
2093         return retval;
2094 }
2095
2096 gboolean        
2097 modest_msg_view_window_select_previous_message (ModestMsgViewWindow *window)
2098 {
2099         ModestMsgViewWindowPrivate *priv = NULL;
2100         GtkTreePath *path;
2101         gboolean finished = FALSE;
2102         gboolean retval = FALSE;
2103
2104         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), FALSE);
2105         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2106
2107         /* Return inmediatly if there is no header model */
2108         if (!priv->header_model || !priv->row_reference)
2109                 return FALSE;
2110
2111         path = gtk_tree_row_reference_get_path (priv->row_reference);
2112         while (!finished && gtk_tree_path_prev (path)) {
2113                 TnyHeader *header;
2114                 GtkTreeIter iter;
2115
2116                 gtk_tree_model_get_iter (priv->header_model, &iter, path);
2117                 gtk_tree_model_get (priv->header_model, &iter, 
2118                                     TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
2119                                     &header, -1);
2120                 finished = TRUE;
2121                 if (header) {
2122                         if (msg_is_visible (header, priv->is_outbox)) {
2123                                 GtkTreeRowReference *row_reference;
2124                                 row_reference = gtk_tree_row_reference_new (priv->header_model, path);
2125                                 /* Read the message & show it */
2126                                 retval = message_reader (window, priv, header, NULL, NULL, row_reference);
2127                                 gtk_tree_row_reference_free (row_reference);
2128                         } else {
2129                                 finished = FALSE;
2130                         }
2131                         g_object_unref (header);
2132                 }
2133         }
2134
2135         gtk_tree_path_free (path);
2136         return retval;
2137 }
2138
2139 static void
2140 view_msg_cb (ModestMailOperation *mail_op, 
2141              TnyHeader *header, 
2142              gboolean canceled,
2143              TnyMsg *msg, 
2144              GError *error,
2145              gpointer user_data)
2146 {
2147         ModestMsgViewWindow *self = NULL;
2148         ModestMsgViewWindowPrivate *priv = NULL;
2149         GtkTreeRowReference *row_reference = NULL;
2150
2151         /* Unregister the header (it was registered before creating the mail operation) */
2152         modest_window_mgr_unregister_header (modest_runtime_get_window_mgr (), header);
2153
2154         row_reference = (GtkTreeRowReference *) user_data;
2155         if (canceled) {
2156                 if (row_reference)
2157                         gtk_tree_row_reference_free (row_reference);
2158                 self = (ModestMsgViewWindow *) modest_mail_operation_get_source (mail_op);
2159                 if (self) {
2160                         /* Restore window title */
2161                         update_window_title (self);
2162                         g_object_unref (self);
2163                 }
2164                 return;
2165         }
2166
2167         /* If there was any error */
2168         if (!modest_ui_actions_msg_retrieval_check (mail_op, header, msg)) {
2169                 if (row_reference)
2170                         gtk_tree_row_reference_free (row_reference);
2171                 self = (ModestMsgViewWindow *) modest_mail_operation_get_source (mail_op);
2172                 if (self) {
2173                         /* Restore window title */
2174                         update_window_title (self);
2175                         g_object_unref (self);
2176                 }
2177                 return;
2178         }
2179
2180         /* Get the window */ 
2181         self = (ModestMsgViewWindow *) modest_mail_operation_get_source (mail_op);
2182         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self));
2183         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2184
2185         /* Update the row reference */
2186         if (priv->row_reference != NULL) {
2187                 gtk_tree_row_reference_free (priv->row_reference);
2188                 priv->row_reference = row_reference?gtk_tree_row_reference_copy (row_reference):NULL;
2189                 if (priv->next_row_reference != NULL) {
2190                         gtk_tree_row_reference_free (priv->next_row_reference);
2191                 }
2192                 if (row_reference) {
2193                         priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
2194                         select_next_valid_row (priv->header_model, &(priv->next_row_reference), TRUE, priv->is_outbox);
2195                 } else {
2196                         priv->next_row_reference = NULL;
2197                 }
2198         }
2199
2200         /* Mark header as read */
2201         if (!(tny_header_get_flags (header) & TNY_HEADER_FLAG_SEEN))
2202                 tny_header_set_flag (header, TNY_HEADER_FLAG_SEEN);
2203
2204         /* Set new message */
2205         if (priv->msg_view != NULL && TNY_IS_MSG_VIEW (priv->msg_view)) {
2206                 tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
2207                 modest_msg_view_window_update_priority (self);
2208                 update_window_title (MODEST_MSG_VIEW_WINDOW (self));
2209                 update_branding (MODEST_MSG_VIEW_WINDOW (self));
2210                 modest_msg_view_grab_focus (MODEST_MSG_VIEW (priv->msg_view));
2211         }
2212
2213         /* Set the new message uid of the window  */
2214         if (priv->msg_uid) {
2215                 g_free (priv->msg_uid);
2216                 priv->msg_uid = modest_tny_folder_get_header_unique_id (header);
2217         }
2218
2219         /* Notify the observers */
2220         g_signal_emit (G_OBJECT (self), signals[MSG_CHANGED_SIGNAL], 
2221                        0, priv->header_model, priv->row_reference);
2222
2223         /* Frees */
2224         g_object_unref (self);
2225         if (row_reference)
2226                 gtk_tree_row_reference_free (row_reference);            
2227 }
2228
2229 TnyFolderType
2230 modest_msg_view_window_get_folder_type (ModestMsgViewWindow *window)
2231 {
2232         ModestMsgViewWindowPrivate *priv;
2233         TnyMsg *msg;
2234         TnyFolderType folder_type;
2235
2236         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2237
2238         folder_type = TNY_FOLDER_TYPE_UNKNOWN;
2239
2240         msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
2241         if (msg) {
2242                 TnyFolder *folder;
2243
2244                 folder = tny_msg_get_folder (msg);
2245                 if (folder) {
2246                         folder_type = modest_tny_folder_guess_folder_type (folder);
2247                         g_object_unref (folder);
2248                 }
2249                 g_object_unref (msg);
2250         }
2251
2252         return folder_type;
2253 }
2254
2255
2256 static void
2257 modest_msg_view_window_update_priority (ModestMsgViewWindow *window)
2258 {
2259         ModestMsgViewWindowPrivate *priv;
2260         TnyHeader *header = NULL;
2261         TnyHeaderFlags flags = 0;
2262
2263         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2264
2265         if (priv->header_model && priv->row_reference) {
2266                 GtkTreeIter iter;
2267                 GtkTreePath *path = NULL;
2268
2269                 path = gtk_tree_row_reference_get_path (priv->row_reference);
2270                 g_return_if_fail (path != NULL);
2271                 gtk_tree_model_get_iter (priv->header_model, 
2272                                          &iter, 
2273                                          gtk_tree_row_reference_get_path (priv->row_reference));
2274
2275                 gtk_tree_model_get (priv->header_model, &iter, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
2276                                     &header, -1);
2277                 gtk_tree_path_free (path);
2278         } else {
2279                 TnyMsg *msg;
2280                 msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
2281                 if (msg) {
2282                         header = tny_msg_get_header (msg);
2283                         g_object_unref (msg);
2284                 }
2285         }
2286
2287         if (header) {
2288                 flags = tny_header_get_flags (header);
2289                 g_object_unref(G_OBJECT(header));
2290         }
2291
2292         modest_msg_view_set_priority (MODEST_MSG_VIEW(priv->msg_view), flags);
2293
2294 }
2295
2296 static void
2297 toolbar_resize (ModestMsgViewWindow *self)
2298 {
2299         ModestMsgViewWindowPrivate *priv = NULL;
2300         ModestWindowPrivate *parent_priv = NULL;
2301         GtkWidget *widget;
2302         gint static_button_size;
2303         ModestWindowMgr *mgr;
2304
2305         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self));
2306         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2307         parent_priv = MODEST_WINDOW_GET_PRIVATE(self);
2308
2309         mgr = modest_runtime_get_window_mgr ();
2310         static_button_size = modest_window_mgr_get_fullscreen_mode (mgr)?120:120;
2311
2312         if (parent_priv->toolbar) {
2313                 /* left size buttons */
2314                 widget = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageReply");
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/ToolbarMessageReplyAll");
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/ToolbarDeleteMessage");
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                 widget = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarDownloadExternalImages");
2327                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (widget), FALSE);
2328                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (widget), FALSE);
2329                 gtk_widget_set_size_request (GTK_WIDGET (widget), static_button_size, -1);
2330
2331                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (priv->next_toolitem), TRUE);
2332                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->next_toolitem), TRUE);
2333                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (priv->prev_toolitem), TRUE);
2334                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->prev_toolitem), TRUE);
2335         }
2336 }
2337
2338 static void
2339 modest_msg_view_window_show_toolbar (ModestWindow *self,
2340                                      gboolean show_toolbar)
2341 {
2342         ModestMsgViewWindowPrivate *priv = NULL;
2343         ModestWindowPrivate *parent_priv;
2344
2345         parent_priv = MODEST_WINDOW_GET_PRIVATE(self);
2346         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2347
2348         /* Set optimized view status */
2349         priv->optimized_view = !show_toolbar;
2350
2351         if (!parent_priv->toolbar) {
2352                 parent_priv->toolbar = gtk_ui_manager_get_widget (parent_priv->ui_manager, 
2353                                                                   "/ToolBar");
2354                 gtk_toolbar_set_icon_size (GTK_TOOLBAR (parent_priv->toolbar), HILDON_ICON_SIZE_FINGER);
2355                 gtk_widget_set_no_show_all (parent_priv->toolbar, TRUE);
2356
2357                 priv->next_toolitem = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageNext");
2358                 priv->prev_toolitem = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageBack");
2359                 toolbar_resize (MODEST_MSG_VIEW_WINDOW (self));
2360
2361                 /* Add to window */
2362                 hildon_window_add_toolbar (HILDON_WINDOW (self), 
2363                                            GTK_TOOLBAR (parent_priv->toolbar));
2364
2365         }
2366
2367         if (show_toolbar) {
2368                 /* Quick hack: this prevents toolbar icons "dance" when progress bar show status is changed */ 
2369                 /* TODO: resize mode migth be GTK_RESIZE_QUEUE, in order to avoid unneccesary shows */
2370                 gtk_container_set_resize_mode (GTK_CONTAINER(parent_priv->toolbar), GTK_RESIZE_IMMEDIATE);
2371
2372                 gtk_widget_show (GTK_WIDGET (parent_priv->toolbar));
2373                 if (modest_msg_view_window_transfer_mode_enabled (MODEST_MSG_VIEW_WINDOW (self))) 
2374                         set_progress_hint (MODEST_MSG_VIEW_WINDOW (self), TRUE);
2375                 else
2376                         set_progress_hint (MODEST_MSG_VIEW_WINDOW (self), FALSE);
2377
2378         } else {
2379                 gtk_widget_set_no_show_all (parent_priv->toolbar, TRUE);
2380                 gtk_widget_hide (GTK_WIDGET (parent_priv->toolbar));
2381         }
2382 }
2383
2384 static void 
2385 modest_msg_view_window_clipboard_owner_change (GtkClipboard *clipboard,
2386                                                GdkEvent *event,
2387                                                ModestMsgViewWindow *window)
2388 {
2389         if (!GTK_WIDGET_VISIBLE (window))
2390                 return;
2391
2392         modest_window_check_dimming_rules_group (MODEST_WINDOW (window), MODEST_DIMMING_RULES_CLIPBOARD);
2393 }
2394
2395 gboolean 
2396 modest_msg_view_window_transfer_mode_enabled (ModestMsgViewWindow *self)
2397 {
2398         ModestMsgViewWindowPrivate *priv;
2399         
2400         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), FALSE); 
2401         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2402
2403         return priv->progress_hint;
2404 }
2405
2406 static gboolean
2407 observers_empty (ModestMsgViewWindow *self)
2408 {
2409         GSList *tmp = NULL;
2410         ModestMsgViewWindowPrivate *priv;
2411         gboolean is_empty = TRUE;
2412         guint pending_ops = 0;
2413  
2414         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2415         tmp = priv->progress_widgets;
2416
2417         /* Check all observers */
2418         while (tmp && is_empty)  {
2419                 pending_ops = modest_progress_object_num_pending_operations (MODEST_PROGRESS_OBJECT(tmp->data));
2420                 is_empty = pending_ops == 0;
2421                 
2422                 tmp = g_slist_next(tmp);
2423         }
2424         
2425         return is_empty;
2426 }
2427
2428 static void
2429 on_account_removed (TnyAccountStore *account_store, 
2430                     TnyAccount *account,
2431                     gpointer user_data)
2432 {
2433         /* Do nothing if it's a transport account, because we only
2434            show the messages of a store account */
2435         if (tny_account_get_account_type(account) == TNY_ACCOUNT_TYPE_STORE) {
2436                 const gchar *parent_acc = NULL;
2437                 const gchar *our_acc = NULL;
2438
2439                 our_acc = modest_window_get_active_account (MODEST_WINDOW (user_data));
2440                 parent_acc = modest_tny_account_get_parent_modest_account_name_for_server_account (account);
2441
2442                 /* Close this window if I'm showing a message of the removed account */
2443                 if (our_acc && parent_acc && strcmp (parent_acc, our_acc) == 0)
2444                         modest_ui_actions_on_close_window (NULL, MODEST_WINDOW (user_data));
2445         }
2446 }
2447
2448 static void 
2449 on_mail_operation_started (ModestMailOperation *mail_op,
2450                            gpointer user_data)
2451 {
2452         ModestMsgViewWindow *self;
2453         ModestMailOperationTypeOperation op_type;
2454         GSList *tmp;
2455         ModestMsgViewWindowPrivate *priv;
2456         GObject *source = NULL;
2457
2458         self = MODEST_MSG_VIEW_WINDOW (user_data);
2459         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2460         op_type = modest_mail_operation_get_type_operation (mail_op);
2461         tmp = priv->progress_widgets;
2462         source = modest_mail_operation_get_source(mail_op);
2463         if (G_OBJECT (self) == source) {
2464                 if (op_type == MODEST_MAIL_OPERATION_TYPE_RECEIVE ||
2465                     op_type == MODEST_MAIL_OPERATION_TYPE_OPEN ||
2466                     op_type == MODEST_MAIL_OPERATION_TYPE_DELETE) {
2467                         set_progress_hint (self, TRUE);
2468                         while (tmp) {
2469                                 modest_progress_object_add_operation (
2470                                                 MODEST_PROGRESS_OBJECT (tmp->data),
2471                                                 mail_op);
2472                                 tmp = g_slist_next (tmp);
2473                         }
2474                 }
2475         }
2476         g_object_unref (source);
2477
2478         /* Update dimming rules */
2479         check_dimming_rules_after_change (self);
2480 }
2481
2482 static void
2483 on_mail_operation_finished (ModestMailOperation *mail_op,
2484                             gpointer user_data)
2485 {
2486         ModestMsgViewWindow *self;
2487         ModestMailOperationTypeOperation op_type;
2488         GSList *tmp;
2489         ModestMsgViewWindowPrivate *priv;
2490
2491         self = MODEST_MSG_VIEW_WINDOW (user_data);
2492         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2493         op_type = modest_mail_operation_get_type_operation (mail_op);
2494         tmp = priv->progress_widgets;
2495
2496         if (op_type == MODEST_MAIL_OPERATION_TYPE_RECEIVE ||
2497             op_type == MODEST_MAIL_OPERATION_TYPE_OPEN ||
2498             op_type == MODEST_MAIL_OPERATION_TYPE_DELETE) {
2499                 while (tmp) {
2500                         modest_progress_object_remove_operation (MODEST_PROGRESS_OBJECT (tmp->data),
2501                                                                  mail_op);
2502                         tmp = g_slist_next (tmp);
2503                 }
2504
2505                 /* If no more operations are being observed, NORMAL mode is enabled again */
2506                 if (observers_empty (self)) {
2507                         set_progress_hint (self, FALSE);
2508                 }
2509         }
2510
2511         /* Update dimming rules. We have to do this right here
2512            and not in view_msg_cb because at that point the
2513            transfer mode is still enabled so the dimming rule
2514            won't let the user delete the message that has been
2515            readed for example */
2516         check_dimming_rules_after_change (self);
2517 }
2518
2519 static void
2520 on_queue_changed (ModestMailOperationQueue *queue,
2521                   ModestMailOperation *mail_op,
2522                   ModestMailOperationQueueNotification type,
2523                   ModestMsgViewWindow *self)
2524 {       
2525         ModestMsgViewWindowPrivate *priv;
2526
2527         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2528
2529         /* If this operations was created by another window, do nothing */
2530         if (!modest_mail_operation_is_mine (mail_op, G_OBJECT(self))) 
2531             return;
2532
2533         if (type == MODEST_MAIL_OPERATION_QUEUE_OPERATION_ADDED) {
2534                 priv->sighandlers = modest_signal_mgr_connect (priv->sighandlers,
2535                                                                G_OBJECT (mail_op),
2536                                                                "operation-started",
2537                                                                G_CALLBACK (on_mail_operation_started),
2538                                                                self);
2539                 priv->sighandlers = modest_signal_mgr_connect (priv->sighandlers,
2540                                                                G_OBJECT (mail_op),
2541                                                                "operation-finished",
2542                                                                G_CALLBACK (on_mail_operation_finished),
2543                                                                self);
2544         } else if (type == MODEST_MAIL_OPERATION_QUEUE_OPERATION_REMOVED) {
2545                 priv->sighandlers = modest_signal_mgr_disconnect (priv->sighandlers,
2546                                                                   G_OBJECT (mail_op),
2547                                                                   "operation-started");
2548                 priv->sighandlers = modest_signal_mgr_disconnect (priv->sighandlers,
2549                                                                   G_OBJECT (mail_op),
2550                                                                   "operation-finished");
2551         }
2552 }
2553
2554 TnyList *
2555 modest_msg_view_window_get_attachments (ModestMsgViewWindow *win) 
2556 {
2557         ModestMsgViewWindowPrivate *priv;
2558         TnyList *selected_attachments = NULL;
2559         
2560         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (win), NULL);
2561         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (win);
2562
2563         /* In Hildon 2.2 as there's no selection we assume we have all attachments selected */
2564         selected_attachments = modest_msg_view_get_attachments (MODEST_MSG_VIEW (priv->msg_view));
2565         
2566         return selected_attachments;
2567 }
2568
2569 typedef struct {
2570         ModestMsgViewWindow *self;
2571         gchar *file_path;
2572 } DecodeAsyncHelper;
2573
2574 static void
2575 on_decode_to_stream_async_handler (TnyMimePart *mime_part, 
2576                                    gboolean cancelled, 
2577                                    TnyStream *stream, 
2578                                    GError *err, 
2579                                    gpointer user_data)
2580 {
2581         DecodeAsyncHelper *helper = (DecodeAsyncHelper *) user_data;
2582
2583         /* It could happen that the window was closed */
2584         if (GTK_WIDGET_VISIBLE (helper->self))
2585                 set_progress_hint (helper->self, FALSE);
2586
2587         if (cancelled || err) {
2588                 if (err) {
2589                         gchar *msg = g_strdup_printf (_KR("cerm_device_memory_full"), "");
2590                         modest_platform_information_banner (NULL, NULL, msg);
2591                         g_free (msg);
2592                 }
2593                 goto free;
2594         }
2595
2596         /* make the file read-only */
2597         g_chmod(helper->file_path, 0444);
2598
2599         /* Activate the file */
2600         modest_platform_activate_file (helper->file_path, tny_mime_part_get_content_type (mime_part));
2601
2602  free:
2603         /* Frees */
2604         g_object_unref (helper->self);
2605         g_free (helper->file_path);
2606         g_slice_free (DecodeAsyncHelper, helper);
2607 }
2608
2609 void
2610 modest_msg_view_window_view_attachment (ModestMsgViewWindow *window, 
2611                                         TnyMimePart *mime_part)
2612 {
2613         ModestMsgViewWindowPrivate *priv;
2614         const gchar *msg_uid;
2615         gchar *attachment_uid = NULL;
2616         gint attachment_index = 0;
2617         TnyList *attachments;
2618
2619         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
2620         g_return_if_fail (TNY_IS_MIME_PART (mime_part) || (mime_part == NULL));
2621         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2622
2623         msg_uid = modest_msg_view_window_get_message_uid (MODEST_MSG_VIEW_WINDOW (window));
2624         attachments = modest_msg_view_get_attachments (MODEST_MSG_VIEW (priv->msg_view));
2625         attachment_index = modest_list_index (attachments, (GObject *) mime_part);
2626         g_object_unref (attachments);
2627
2628         if (msg_uid && attachment_index >= 0) {
2629                 attachment_uid = g_strdup_printf ("%s/%d", msg_uid, attachment_index);
2630         }
2631
2632         if (mime_part == NULL) {
2633                 gboolean error = FALSE;
2634                 TnyList *selected_attachments = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2635                 if (selected_attachments == NULL || tny_list_get_length (selected_attachments) == 0) {
2636                         error = TRUE;
2637                 } else if (tny_list_get_length (selected_attachments) > 1) {
2638                         hildon_banner_show_information (NULL, NULL, _("mcen_ib_unable_to_display_more"));
2639                         error = TRUE;
2640                 } else {
2641                         TnyIterator *iter;
2642                         iter = tny_list_create_iterator (selected_attachments);
2643                         mime_part = (TnyMimePart *) tny_iterator_get_current (iter);
2644                         g_object_unref (iter);
2645                 }
2646                 if (selected_attachments)
2647                         g_object_unref (selected_attachments);
2648
2649                 if (error)
2650                         goto frees;
2651         } else {
2652                 g_object_ref (mime_part);
2653         }
2654
2655         if (tny_mime_part_is_purged (mime_part))
2656                 goto frees;
2657
2658         if (!modest_tny_mime_part_is_msg (mime_part) && tny_mime_part_get_filename (mime_part)) {
2659                 gchar *filepath = NULL;
2660                 const gchar *att_filename = tny_mime_part_get_filename (mime_part);
2661                 gboolean show_error_banner = FALSE;
2662                 TnyFsStream *temp_stream = NULL;
2663                 temp_stream = modest_utils_create_temp_stream (att_filename, attachment_uid,
2664                                                                &filepath);
2665
2666                 if (temp_stream != NULL) {
2667                         DecodeAsyncHelper *helper;
2668
2669                         /* Activate progress hint */
2670                         set_progress_hint (window, TRUE);
2671
2672                         helper = g_slice_new0 (DecodeAsyncHelper);
2673                         helper->self = g_object_ref (window);
2674                         helper->file_path = g_strdup (filepath);
2675
2676                         tny_mime_part_decode_to_stream_async (mime_part, TNY_STREAM (temp_stream),
2677                                                               on_decode_to_stream_async_handler,
2678                                                               NULL,
2679                                                               helper);
2680                         g_object_unref (temp_stream);
2681                         /* NOTE: files in the temporary area will be automatically
2682                          * cleaned after some time if they are no longer in use */
2683                 } else {
2684                         if (filepath) {
2685                                 const gchar *content_type;
2686                                 /* the file may already exist but it isn't writable,
2687                                  * let's try to open it anyway */
2688                                 content_type = tny_mime_part_get_content_type (mime_part);
2689                                 modest_platform_activate_file (filepath, content_type);
2690                         } else {
2691                                 g_warning ("%s: modest_utils_create_temp_stream failed", __FUNCTION__);
2692                                 show_error_banner = TRUE;
2693                         }
2694                 }
2695                 if (filepath)
2696                         g_free (filepath);
2697                 if (show_error_banner)
2698                         modest_platform_information_banner (NULL, NULL, _("mail_ib_file_operation_failed"));
2699         } else if (!modest_tny_mime_part_is_msg (mime_part)) {
2700                 ModestWindowMgr *mgr;
2701                 ModestWindow *msg_win = NULL;
2702                 TnyMsg *current_msg;
2703                 gboolean found;
2704                 TnyHeader *header;
2705
2706                 current_msg = modest_msg_view_window_get_message (MODEST_MSG_VIEW_WINDOW (window));
2707                 mgr = modest_runtime_get_window_mgr ();
2708                 header = tny_msg_get_header (TNY_MSG (current_msg));
2709                 found = modest_window_mgr_find_registered_message_uid (mgr,
2710                                                                        attachment_uid,
2711                                                                        &msg_win);
2712                 
2713                 if (found) {
2714                         g_debug ("window for this body is already being created");
2715                 } else {
2716
2717                         /* it's not found, so create a new window for it */
2718                         modest_window_mgr_register_header (mgr, header, attachment_uid); /* register the uid before building the window */
2719                         gchar *account = g_strdup (modest_window_get_active_account (MODEST_WINDOW (window)));
2720                         const gchar *mailbox = modest_window_get_active_mailbox (MODEST_WINDOW (window));
2721                         if (!account)
2722                                 account = modest_account_mgr_get_default_account (modest_runtime_get_account_mgr ());
2723                         
2724                         msg_win = modest_msg_view_window_new_with_other_body (TNY_MSG (current_msg), TNY_MIME_PART (mime_part),
2725                                                                               account, mailbox, attachment_uid);
2726                         
2727                         modest_window_set_zoom (MODEST_WINDOW (msg_win),
2728                                                 modest_window_get_zoom (MODEST_WINDOW (window)));
2729                         if (modest_window_mgr_register_window (mgr, msg_win, MODEST_WINDOW (window)))
2730                                 gtk_widget_show_all (GTK_WIDGET (msg_win));
2731                         else
2732                                 gtk_widget_destroy (GTK_WIDGET (msg_win));
2733                 }
2734                 g_object_unref (current_msg);           
2735         } else {
2736                 /* message attachment */
2737                 TnyHeader *header = NULL;
2738                 ModestWindowMgr *mgr;
2739                 ModestWindow *msg_win = NULL;
2740                 gboolean found;
2741
2742                 header = tny_msg_get_header (TNY_MSG (mime_part));
2743                 mgr = modest_runtime_get_window_mgr ();
2744                 found = modest_window_mgr_find_registered_header (mgr, header, &msg_win);
2745
2746                 if (found) {
2747                         /* if it's found, but there is no msg_win, it's probably in the process of being created;
2748                          * thus, we don't do anything */
2749                         g_debug ("window for is already being created");
2750                 } else {
2751                         /* it's not found, so create a new window for it */
2752                         modest_window_mgr_register_header (mgr, header, attachment_uid); /* register the uid before building the window */
2753                         gchar *account = g_strdup (modest_window_get_active_account (MODEST_WINDOW (window)));
2754                         const gchar *mailbox = modest_window_get_active_mailbox (MODEST_WINDOW (window));
2755                         if (!account)
2756                                 account = modest_account_mgr_get_default_account (modest_runtime_get_account_mgr ());
2757                         msg_win = modest_msg_view_window_new_for_attachment (TNY_MSG (mime_part), account, 
2758                                                                              mailbox, attachment_uid);
2759                         modest_window_set_zoom (MODEST_WINDOW (msg_win),
2760                                                 modest_window_get_zoom (MODEST_WINDOW (window)));
2761                         if (modest_window_mgr_register_window (mgr, msg_win, MODEST_WINDOW (window)))
2762                                 gtk_widget_show_all (GTK_WIDGET (msg_win));
2763                         else
2764                                 gtk_widget_destroy (GTK_WIDGET (msg_win));
2765                 }
2766         }
2767
2768  frees:
2769         if (attachment_uid)
2770                 g_free (attachment_uid);
2771         if (mime_part)
2772                 g_object_unref (mime_part);
2773 }
2774
2775 typedef struct
2776 {
2777         gchar *filename;
2778         TnyMimePart *part;
2779 } SaveMimePartPair;
2780
2781 typedef struct
2782 {
2783         GList *pairs;
2784         GnomeVFSResult result;
2785         gchar *uri;
2786 } SaveMimePartInfo;
2787
2788 static void save_mime_part_info_free (SaveMimePartInfo *info, gboolean with_struct);
2789 static gboolean idle_save_mime_part_show_result (SaveMimePartInfo *info);
2790 static gpointer save_mime_part_to_file (SaveMimePartInfo *info);
2791 static void save_mime_parts_to_file_with_checks (GtkWindow *parent, SaveMimePartInfo *info);
2792
2793 static void
2794 save_mime_part_info_free (SaveMimePartInfo *info, gboolean with_struct)
2795 {
2796         GList *node;
2797
2798         for (node = info->pairs; node != NULL; node = g_list_next (node)) {
2799                 SaveMimePartPair *pair = (SaveMimePartPair *) node->data;
2800                 g_free (pair->filename);
2801                 g_object_unref (pair->part);
2802                 g_slice_free (SaveMimePartPair, pair);
2803         }
2804         g_list_free (info->pairs);
2805         info->pairs = NULL;
2806         g_free (info->uri);
2807         if (with_struct) {
2808                 g_slice_free (SaveMimePartInfo, info);
2809         }
2810 }
2811
2812 static gboolean
2813 idle_save_mime_part_show_result (SaveMimePartInfo *info)
2814 {
2815         /* This is a GDK lock because we are an idle callback and
2816          * hildon_banner_show_information is or does Gtk+ code */
2817
2818         gdk_threads_enter (); /* CHECKED */
2819         if (info->result == GNOME_VFS_OK) {
2820                 hildon_banner_show_information (NULL, NULL, _CS("sfil_ib_saved"));
2821         } else if (info->result == GNOME_VFS_ERROR_NO_SPACE) {
2822                 gchar *msg = NULL;
2823
2824                 /* Check if the uri belongs to the external mmc */
2825                 if (g_str_has_prefix (info->uri, g_getenv (MODEST_MMC1_VOLUMEPATH_ENV)))
2826                         msg = g_strdup_printf (_KR("cerm_device_memory_full"), "");
2827                 else
2828                         msg = g_strdup (_KR("cerm_memory_card_full"));
2829                 modest_platform_information_banner (NULL, NULL, msg);
2830                 g_free (msg);
2831         } else {
2832                 hildon_banner_show_information (NULL, NULL, _("mail_ib_file_operation_failed"));
2833         }
2834         save_mime_part_info_free (info, FALSE);
2835         gdk_threads_leave (); /* CHECKED */
2836
2837         return FALSE;
2838 }
2839
2840 static gpointer
2841 save_mime_part_to_file (SaveMimePartInfo *info)
2842 {
2843         GnomeVFSHandle *handle;
2844         TnyStream *stream;
2845         SaveMimePartPair *pair = (SaveMimePartPair *) info->pairs->data;
2846
2847         info->result = gnome_vfs_create (&handle, pair->filename, GNOME_VFS_OPEN_WRITE, FALSE, 0644);
2848         if (info->result == GNOME_VFS_OK) {
2849                 GError *error = NULL;
2850                 stream = tny_vfs_stream_new (handle);
2851                 if (tny_mime_part_decode_to_stream (pair->part, stream, &error) < 0) {
2852                         g_warning ("modest: could not save attachment %s: %d (%s)\n", pair->filename, error?error->code:-1, error?error->message:"Unknown error");
2853
2854                         if ((error->domain == TNY_ERROR_DOMAIN) && 
2855                             (error->code == TNY_IO_ERROR_WRITE) &&
2856                             (errno == ENOSPC)) {
2857                                 info->result = GNOME_VFS_ERROR_NO_SPACE;
2858                         } else {
2859                                 info->result = GNOME_VFS_ERROR_IO;
2860                         }
2861                 }
2862                 g_object_unref (G_OBJECT (stream));
2863         } else {
2864                 g_warning ("Could not create save attachment %s: %s\n", 
2865                            pair->filename, gnome_vfs_result_to_string (info->result));
2866         }
2867
2868         /* Go on saving remaining files */
2869         info->pairs = g_list_remove_link (info->pairs, info->pairs);
2870         if (info->pairs != NULL) {
2871                 save_mime_part_to_file (info);
2872         } else {
2873                 g_idle_add ((GSourceFunc) idle_save_mime_part_show_result, info);
2874         }
2875
2876         return NULL;
2877 }
2878
2879 static void
2880 save_mime_parts_to_file_with_checks (GtkWindow *parent,
2881                                      SaveMimePartInfo *info)
2882 {
2883         gboolean is_ok = TRUE;
2884         gint replaced_files = 0;
2885         const GList *files = info->pairs;
2886         const GList *iter, *to_replace = NULL;
2887
2888         for (iter = files; (iter != NULL) && (replaced_files < 2); iter = g_list_next(iter)) {
2889                 SaveMimePartPair *pair = iter->data;
2890                 gchar *unescaped = g_uri_unescape_string (pair->filename, NULL);
2891
2892                 if (modest_utils_file_exists (unescaped)) {
2893                         replaced_files++;
2894                         if (replaced_files == 1)
2895                                 to_replace = iter;
2896                 }
2897                 g_free (unescaped);
2898         }
2899         if (replaced_files) {
2900                 gint response;
2901
2902                 if (replaced_files == 1) {
2903                         SaveMimePartPair *pair = to_replace->data;
2904                         const gchar *basename = strrchr (pair->filename, G_DIR_SEPARATOR) + 1;
2905                         gchar *escaped_basename, *message;
2906
2907                         escaped_basename = g_uri_unescape_string (basename, NULL);
2908                         message = g_strdup_printf ("%s\n%s",
2909                                                    _FM("docm_nc_replace_file"),
2910                                                    (escaped_basename) ? escaped_basename : "");
2911                         response = modest_platform_run_confirmation_dialog (parent, message);
2912                         g_free (message);
2913                         g_free (escaped_basename);
2914                 } else {
2915                         response = modest_platform_run_confirmation_dialog (parent,
2916                                                                             _FM("docm_nc_replace_multiple"));
2917                 }
2918                 if (response != GTK_RESPONSE_OK)
2919                         is_ok = FALSE;
2920         }
2921
2922         if (!is_ok) {
2923                 save_mime_part_info_free (info, TRUE);
2924         } else {
2925                 g_thread_create ((GThreadFunc)save_mime_part_to_file, info, FALSE, NULL);
2926         }
2927
2928 }
2929
2930 static void
2931 save_attachments_response (GtkDialog *dialog,
2932                            gint       arg1,
2933                            gpointer   user_data)  
2934 {
2935         TnyList *mime_parts;
2936         gchar *chooser_uri;
2937         GList *files_to_save = NULL;
2938         gchar *current_folder;
2939
2940         mime_parts = TNY_LIST (user_data);
2941
2942         if (arg1 != GTK_RESPONSE_OK)
2943                 goto end;
2944
2945         chooser_uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dialog));
2946         current_folder = gtk_file_chooser_get_current_folder_uri (GTK_FILE_CHOOSER (dialog));
2947         if (current_folder && *current_folder != '\0') {
2948                 GError *err = NULL;
2949                 modest_conf_set_string (modest_runtime_get_conf (), MODEST_CONF_LATEST_SAVE_ATTACHMENT_PATH,
2950                                         current_folder,&err);
2951                 if (err != NULL) {
2952                         g_debug ("Error storing latest used folder: %s", err->message);
2953                         g_error_free (err);
2954                 }
2955         }
2956         g_free (current_folder);
2957
2958         if (!modest_utils_folder_writable (chooser_uri)) {
2959                 const gchar *err_msg;
2960
2961 #ifdef MODEST_PLATFORM_MAEMO
2962                 if (modest_maemo_utils_in_usb_mode ()) {
2963                         err_msg = dgettext ("hildon-status-bar-usb", "usbh_ib_mmc_usb_connected");
2964                 } else {
2965                         err_msg = _FM("sfil_ib_readonly_location");
2966                 }
2967 #else
2968                 err_msg = _FM("sfil_ib_readonly_location");
2969 #endif
2970                 hildon_banner_show_information (NULL, NULL, err_msg);
2971         } else {
2972                 TnyIterator *iter;
2973
2974                 iter = tny_list_create_iterator (mime_parts);
2975                 while (!tny_iterator_is_done (iter)) {
2976                         TnyMimePart *mime_part = (TnyMimePart *) tny_iterator_get_current (iter);
2977
2978                         if ((modest_tny_mime_part_is_attachment_for_modest (mime_part)) &&
2979                             !tny_mime_part_is_purged (mime_part) &&
2980                             (tny_mime_part_get_filename (mime_part) != NULL)) {
2981                                 SaveMimePartPair *pair;
2982
2983                                 pair = g_slice_new0 (SaveMimePartPair);
2984
2985                                 if (tny_list_get_length (mime_parts) > 1) {
2986                                         gchar *escaped = 
2987                                                 gnome_vfs_escape_slashes (tny_mime_part_get_filename (mime_part));
2988                                         pair->filename = g_build_filename (chooser_uri, escaped, NULL);
2989                                         g_free (escaped);
2990                                 } else {
2991                                         pair->filename = g_strdup (chooser_uri);
2992                                 }
2993                                 pair->part = mime_part;
2994                                 files_to_save = g_list_prepend (files_to_save, pair);
2995                         }
2996                         tny_iterator_next (iter);
2997                 }
2998                 g_object_unref (iter);
2999         }
3000
3001         if (files_to_save != NULL) {
3002                 SaveMimePartInfo *info = g_slice_new0 (SaveMimePartInfo);
3003                 info->pairs = files_to_save;
3004                 info->result = TRUE;
3005                 info->uri = g_strdup (chooser_uri);
3006                 save_mime_parts_to_file_with_checks ((GtkWindow *) dialog, info);
3007         }
3008         g_free (chooser_uri);
3009
3010  end:
3011         /* Free and close the dialog */
3012         g_object_unref (mime_parts);
3013         gtk_widget_destroy (GTK_WIDGET (dialog));
3014 }
3015
3016 void
3017 modest_msg_view_window_save_attachments (ModestMsgViewWindow *window, 
3018                                          TnyList *mime_parts)
3019 {
3020         ModestMsgViewWindowPrivate *priv;
3021         GtkWidget *save_dialog = NULL;
3022         gchar *conf_folder = NULL;
3023         gchar *filename = NULL;
3024         gchar *save_multiple_str = NULL;
3025         const gchar *root_folder = "file:///";
3026
3027         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
3028         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
3029
3030         if (mime_parts == NULL) {
3031                 /* In Hildon 2.2 save and delete operate over all the attachments as there's no
3032                  * selection available */
3033                 mime_parts = modest_msg_view_get_attachments (MODEST_MSG_VIEW (priv->msg_view));
3034                 if (mime_parts && !modest_maemo_utils_select_attachments (GTK_WINDOW (window), mime_parts, FALSE)) {
3035                         g_object_unref (mime_parts);
3036                         return;
3037                 }
3038                 if (mime_parts == NULL || tny_list_get_length (mime_parts) == 0) {
3039                         if (mime_parts) {
3040                                 g_object_unref (mime_parts);
3041                                 mime_parts = NULL;
3042                         }
3043                         return;
3044                 }
3045         } else {
3046                 g_object_ref (mime_parts);
3047         }
3048
3049         /* prepare dialog */
3050         if (tny_list_get_length (mime_parts) == 1) {
3051                 TnyIterator *iter;
3052                 /* only one attachment selected */
3053                 iter = tny_list_create_iterator (mime_parts);
3054                 TnyMimePart *mime_part = (TnyMimePart *) tny_iterator_get_current (iter);
3055                 g_object_unref (iter);
3056                 if (!modest_tny_mime_part_is_msg (mime_part) && 
3057                     modest_tny_mime_part_is_attachment_for_modest (mime_part) &&
3058                     !tny_mime_part_is_purged (mime_part)) {
3059                         filename = g_strdup (tny_mime_part_get_filename (mime_part));
3060                 } else {
3061                         /* TODO: show any error? */
3062                         g_warning ("%s: Tried to save a non-file attachment", __FUNCTION__);
3063                         g_object_unref (mime_parts);
3064                         return;
3065                 }
3066                 g_object_unref (mime_part);
3067         } else {
3068                 gint num = tny_list_get_length (mime_parts);
3069                 save_multiple_str = g_strdup_printf (dngettext("hildon-fm",
3070                                                                "sfil_va_number_of_objects_attachment",
3071                                                               "sfil_va_number_of_objects_attachments",
3072                                                               num), num);
3073         }
3074
3075         save_dialog = hildon_file_chooser_dialog_new (GTK_WINDOW (window), 
3076                                                       GTK_FILE_CHOOSER_ACTION_SAVE);
3077
3078         /* Get last used folder */
3079         conf_folder = modest_conf_get_string (modest_runtime_get_conf (),
3080                                               MODEST_CONF_LATEST_SAVE_ATTACHMENT_PATH, NULL);
3081
3082         /* File chooser stops working if we select "file:///" as current folder */
3083         if (conf_folder && g_ascii_strcasecmp (root_folder, conf_folder) != 0) {
3084                 g_free (conf_folder);
3085                 conf_folder = NULL;
3086         }
3087
3088         if (conf_folder && conf_folder[0] != '\0') {
3089                 gtk_file_chooser_set_current_folder_uri (GTK_FILE_CHOOSER (save_dialog), conf_folder);
3090         } else {
3091                 gchar *docs_folder;
3092                 /* Set the default folder to documents folder */
3093                 docs_folder = (gchar *) g_strdup(g_get_user_special_dir (G_USER_DIRECTORY_DOCUMENTS));
3094                 if (!docs_folder) {
3095                         /* fallback */
3096                         docs_folder = g_build_filename (g_getenv (MYDOCS_ENV), DOCS_FOLDER, NULL);
3097                 }
3098                 gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (save_dialog), docs_folder);
3099                 g_free (docs_folder);
3100         }
3101         g_free (conf_folder);
3102
3103         /* set filename */
3104         if (filename) {
3105                 gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (save_dialog), 
3106                                                    filename);
3107                 g_free (filename);
3108         }
3109
3110         /* if multiple, set multiple string */
3111         if (save_multiple_str) {
3112                 g_object_set (G_OBJECT (save_dialog), "save-multiple", save_multiple_str, NULL);
3113                 gtk_window_set_title (GTK_WINDOW (save_dialog), _FM("sfil_ti_save_objects_files"));
3114                 g_free (save_multiple_str);
3115         }
3116
3117         /* We must run this asynchronously, because the hildon dialog
3118            performs a gtk_dialog_run by itself which leads to gdk
3119            deadlocks */
3120         g_signal_connect (save_dialog, "response", 
3121                           G_CALLBACK (save_attachments_response), mime_parts);
3122
3123         gtk_widget_show_all (save_dialog);
3124 }
3125
3126 static gboolean
3127 show_remove_attachment_information (gpointer userdata)
3128 {
3129         ModestMsgViewWindow *window = (ModestMsgViewWindow *) userdata;
3130         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
3131
3132         /* We're outside the main lock */
3133         gdk_threads_enter ();
3134
3135         if (priv->remove_attachment_banner != NULL) {
3136                 gtk_widget_destroy (priv->remove_attachment_banner);
3137                 g_object_unref (priv->remove_attachment_banner);
3138         }
3139
3140         priv->remove_attachment_banner = g_object_ref (
3141                 hildon_banner_show_animation (NULL, NULL, _("mcen_me_inbox_remove_attachments")));
3142
3143         gdk_threads_leave ();
3144
3145         return FALSE;
3146 }
3147
3148 void
3149 modest_msg_view_window_remove_attachments (ModestMsgViewWindow *window, gboolean get_all)
3150 {
3151         ModestMsgViewWindowPrivate *priv;
3152         TnyList *mime_parts = NULL, *tmp;
3153         gchar *confirmation_message;
3154         gint response;
3155         gint n_attachments;
3156         TnyMsg *msg;
3157         TnyIterator *iter;
3158
3159         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
3160         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
3161
3162         /* In hildon 2.2 we ignore the get_all flag as we always get all attachments. This is
3163          * because we don't have selection
3164          */
3165         mime_parts = modest_msg_view_get_attachments (MODEST_MSG_VIEW (priv->msg_view));
3166
3167         /* Remove already purged messages from mime parts list. We use
3168            a copy of the list to remove items in the original one */
3169         tmp = tny_list_copy (mime_parts);
3170         iter = tny_list_create_iterator (tmp);
3171         while (!tny_iterator_is_done (iter)) {
3172                 TnyMimePart *part = TNY_MIME_PART (tny_iterator_get_current (iter));
3173                 if (tny_mime_part_is_purged (part))
3174                         tny_list_remove (mime_parts, (GObject *) part);
3175
3176                 g_object_unref (part);
3177                 tny_iterator_next (iter);
3178         }
3179         g_object_unref (tmp);
3180         g_object_unref (iter);
3181
3182         if (!modest_maemo_utils_select_attachments (GTK_WINDOW (window), mime_parts, TRUE) ||
3183             tny_list_get_length (mime_parts) == 0) {
3184                 g_object_unref (mime_parts);
3185                 return;
3186         }
3187
3188         n_attachments = tny_list_get_length (mime_parts);
3189         if (n_attachments == 1) {
3190                 gchar *filename;
3191                 TnyMimePart *part;
3192
3193                 iter = tny_list_create_iterator (mime_parts);
3194                 part = (TnyMimePart *) tny_iterator_get_current (iter);
3195                 g_object_unref (iter);
3196                 if (modest_tny_mime_part_is_msg (part)) {
3197                         TnyHeader *header;
3198                         header = tny_msg_get_header (TNY_MSG (part));
3199                         filename = tny_header_dup_subject (header);
3200                         g_object_unref (header);
3201                         if (filename == NULL)
3202                                 filename = g_strdup (_("mail_va_no_subject"));
3203                 } else {
3204                         filename = g_strdup (tny_mime_part_get_filename (TNY_MIME_PART (part)));
3205                 }
3206                 confirmation_message = g_strdup_printf (_("mcen_nc_purge_file_text"), filename);
3207                 g_free (filename);
3208                 g_object_unref (part);
3209         } else {
3210                 confirmation_message = g_strdup_printf (ngettext("mcen_nc_purge_file_text", 
3211                                                                  "mcen_nc_purge_files_text", 
3212                                                                  n_attachments), n_attachments);
3213         }
3214         response = modest_platform_run_confirmation_dialog (GTK_WINDOW (window),
3215                                                             confirmation_message);
3216         g_free (confirmation_message);
3217
3218         if (response != GTK_RESPONSE_OK) {
3219                 g_object_unref (mime_parts);
3220                 return;
3221         }
3222
3223         priv->purge_timeout = g_timeout_add (2000, show_remove_attachment_information, window);
3224
3225         iter = tny_list_create_iterator (mime_parts);
3226         while (!tny_iterator_is_done (iter)) {
3227                 TnyMimePart *part;
3228
3229                 part = (TnyMimePart *) tny_iterator_get_current (iter);
3230                 tny_mime_part_set_purged (TNY_MIME_PART (part));
3231                 g_object_unref (part);
3232                 tny_iterator_next (iter);
3233         }
3234         g_object_unref (iter);
3235
3236         msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
3237         tny_msg_view_clear (TNY_MSG_VIEW (priv->msg_view));
3238         tny_msg_rewrite_cache (msg);
3239         tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
3240         g_object_unref (msg);
3241         update_branding (MODEST_MSG_VIEW_WINDOW (window));
3242
3243         g_object_unref (mime_parts);
3244
3245         if (priv->purge_timeout > 0) {
3246                 g_source_remove (priv->purge_timeout);
3247                 priv->purge_timeout = 0;
3248         }
3249
3250         if (priv->remove_attachment_banner) {
3251                 gtk_widget_destroy (priv->remove_attachment_banner);
3252                 g_object_unref (priv->remove_attachment_banner);
3253                 priv->remove_attachment_banner = NULL;
3254         }
3255 }
3256
3257
3258 static void
3259 update_window_title (ModestMsgViewWindow *window)
3260 {
3261         ModestMsgViewWindowPrivate *priv;
3262         TnyMsg *msg = NULL;
3263         TnyHeader *header = NULL;
3264         gchar *subject = NULL;
3265
3266         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
3267
3268         /* Note that if the window is closed while we're retrieving
3269            the message, this widget could de deleted */
3270         if (!priv->msg_view)
3271                 return;
3272
3273         msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
3274
3275         if (priv->other_body) {
3276                 gchar *description;
3277
3278                 description = modest_tny_mime_part_get_header_value (priv->other_body, "Content-Description");
3279                 if (description) {
3280                         g_strstrip (description);
3281                         subject = description;
3282                 }
3283         } else if (msg != NULL) {
3284                 header = tny_msg_get_header (msg);
3285                 subject = tny_header_dup_subject (header);
3286                 g_object_unref (header);
3287                 g_object_unref (msg);
3288         }
3289
3290         if ((subject == NULL)||(subject[0] == '\0')) {
3291                 g_free (subject);
3292                 subject = g_strdup (_("mail_va_no_subject"));
3293         }
3294
3295         gtk_window_set_title (GTK_WINDOW (window), subject);
3296 }
3297
3298
3299 static void
3300 on_move_focus (GtkWidget *widget,
3301                GtkDirectionType direction,
3302                gpointer userdata)
3303 {
3304         g_signal_stop_emission_by_name (G_OBJECT (widget), "move-focus");
3305 }
3306
3307 static TnyStream *
3308 fetch_image_open_stream (TnyStreamCache *self, gint64 *expected_size, gchar *uri)
3309 {
3310         GnomeVFSResult result;
3311         GnomeVFSHandle *handle = NULL;
3312         GnomeVFSFileInfo *info = NULL;
3313         TnyStream *stream;
3314
3315         result = gnome_vfs_open (&handle, uri, GNOME_VFS_OPEN_READ);
3316         if (result != GNOME_VFS_OK) {
3317                 *expected_size = 0;
3318                 return NULL;
3319         }
3320         
3321         info = gnome_vfs_file_info_new ();
3322         result = gnome_vfs_get_file_info_from_handle (handle, info, GNOME_VFS_FILE_INFO_DEFAULT);
3323         if (result != GNOME_VFS_OK || ! (info->valid_fields & GNOME_VFS_FILE_INFO_FIELDS_SIZE)) {
3324                 /* We put a "safe" default size for going to cache */
3325                 *expected_size = (300*1024);
3326         } else {
3327                 *expected_size = info->size;
3328         }
3329         gnome_vfs_file_info_unref (info);
3330
3331         stream = tny_vfs_stream_new (handle);
3332
3333         return stream;
3334
3335 }
3336
3337 typedef struct {
3338         gchar *uri;
3339         gchar *cache_id;
3340         TnyStream *output_stream;
3341         GtkWidget *msg_view;
3342         GtkWidget *window;
3343 } FetchImageData;
3344
3345 gboolean
3346 on_fetch_image_idle_refresh_view (gpointer userdata)
3347 {
3348
3349         FetchImageData *fidata = (FetchImageData *) userdata;
3350
3351         gdk_threads_enter ();
3352         if (GTK_WIDGET_DRAWABLE (fidata->msg_view)) {
3353                 ModestMsgViewWindowPrivate *priv;
3354
3355                 priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (fidata->window);
3356                 priv->fetching_images--;
3357                 gtk_widget_queue_draw (fidata->msg_view);
3358                 update_progress_hint (MODEST_MSG_VIEW_WINDOW (fidata->window));
3359         }
3360         gdk_threads_leave ();
3361
3362         g_object_unref (fidata->msg_view);
3363         g_object_unref (fidata->window);
3364         g_slice_free (FetchImageData, fidata);
3365         return FALSE;
3366 }
3367
3368 static gpointer
3369 on_fetch_image_thread (gpointer userdata)
3370 {
3371         FetchImageData *fidata = (FetchImageData *) userdata;
3372         TnyStreamCache *cache;
3373         TnyStream *cache_stream;
3374
3375         cache = modest_runtime_get_images_cache ();
3376         cache_stream = 
3377                 tny_stream_cache_get_stream (cache, 
3378                                              fidata->cache_id, 
3379                                              (TnyStreamCacheOpenStreamFetcher) fetch_image_open_stream, 
3380                                              (gpointer) fidata->uri);
3381         g_free (fidata->cache_id);
3382         g_free (fidata->uri);
3383
3384         if (cache_stream != NULL) {
3385                 char buffer[4096];
3386
3387                 while (G_LIKELY (!tny_stream_is_eos (cache_stream))) {
3388                         gssize nb_read;
3389
3390                         nb_read = tny_stream_read (cache_stream, buffer, sizeof (buffer));
3391                         if (G_UNLIKELY (nb_read < 0)) {
3392                                 break;
3393                         } else if (G_LIKELY (nb_read > 0)) {
3394                                 gssize nb_written = 0;
3395
3396                                 while (G_UNLIKELY (nb_written < nb_read)) {
3397                                         gssize len;
3398
3399                                         len = tny_stream_write (fidata->output_stream, buffer + nb_written,
3400                                                                 nb_read - nb_written);
3401                                         if (G_UNLIKELY (len < 0))
3402                                                 break;
3403                                         nb_written += len;
3404                                 }
3405                         }
3406                 }
3407                 tny_stream_close (cache_stream);
3408                 g_object_unref (cache_stream);
3409         }
3410
3411         tny_stream_close (fidata->output_stream);
3412         g_object_unref (fidata->output_stream);
3413
3414         g_idle_add (on_fetch_image_idle_refresh_view, fidata);
3415
3416         return NULL;
3417 }
3418
3419 static gboolean
3420 on_fetch_image (ModestMsgView *msgview,
3421                 const gchar *uri,
3422                 TnyStream *stream,
3423                 ModestMsgViewWindow *window)
3424 {
3425         const gchar *current_account;
3426         ModestMsgViewWindowPrivate *priv;
3427         FetchImageData *fidata;
3428
3429         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
3430
3431         current_account = modest_window_get_active_account (MODEST_WINDOW (window));
3432
3433         fidata = g_slice_new0 (FetchImageData);
3434         fidata->msg_view = g_object_ref (msgview);
3435         fidata->window = g_object_ref (window);
3436         fidata->uri = g_strdup (uri);
3437         fidata->cache_id = modest_images_cache_get_id (current_account, uri);
3438         fidata->output_stream = g_object_ref (stream);
3439
3440         priv->fetching_images++;
3441         if (g_thread_create (on_fetch_image_thread, fidata, FALSE, NULL) == NULL) {
3442                 g_object_unref (fidata->output_stream);
3443                 g_free (fidata->cache_id);
3444                 g_free (fidata->uri);
3445                 g_object_unref (fidata->msg_view);
3446                 g_slice_free (FetchImageData, fidata);
3447                 tny_stream_close (stream);
3448                 priv->fetching_images--;
3449                 update_progress_hint (window);
3450                 return FALSE;
3451         }
3452         update_progress_hint (window);
3453
3454         return TRUE;
3455 }
3456
3457 static void 
3458 setup_menu (ModestMsgViewWindow *self)
3459 {
3460         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW(self));
3461
3462         /* Settings menu buttons */
3463         modest_hildon2_window_add_to_menu (MODEST_HILDON2_WINDOW (self), _("mcen_me_viewer_find"), NULL,
3464                                            APP_MENU_CALLBACK (modest_msg_view_window_show_find_toolbar),
3465                                            MODEST_DIMMING_CALLBACK (modest_ui_dimming_rules_on_find_in_msg));
3466
3467         modest_hildon2_window_add_to_menu (MODEST_HILDON2_WINDOW (self),
3468                                            dngettext(GETTEXT_PACKAGE,
3469                                                      "mcen_me_move_message",
3470                                                      "mcen_me_move_messages",
3471                                                      1),
3472                                            NULL,
3473                                            APP_MENU_CALLBACK (modest_ui_actions_on_move_to),
3474                                            MODEST_DIMMING_CALLBACK (modest_ui_dimming_rules_on_move_to));
3475
3476         modest_hildon2_window_add_to_menu (MODEST_HILDON2_WINDOW (self), _("mcen_me_inbox_forward"), "<Control>d",
3477                                            APP_MENU_CALLBACK (modest_ui_actions_on_forward),
3478                                            MODEST_DIMMING_CALLBACK (modest_ui_dimming_rules_on_reply_msg));
3479
3480         modest_hildon2_window_add_to_menu (MODEST_HILDON2_WINDOW (self), _("mcen_me_inbox_mark_as_read"), NULL,
3481                                            APP_MENU_CALLBACK (modest_ui_actions_on_mark_as_read),
3482                                            MODEST_DIMMING_CALLBACK (modest_ui_dimming_rules_on_mark_as_read_msg_in_view));
3483
3484         modest_hildon2_window_add_to_menu (MODEST_HILDON2_WINDOW (self), _("mcen_me_inbox_mark_as_unread"), NULL,
3485                                            APP_MENU_CALLBACK (modest_ui_actions_on_mark_as_unread),
3486                                            MODEST_DIMMING_CALLBACK (modest_ui_dimming_rules_on_mark_as_unread_msg_in_view));
3487
3488         modest_hildon2_window_add_to_menu (MODEST_HILDON2_WINDOW (self), _("mcen_me_viewer_save_attachments"), NULL,
3489                                            APP_MENU_CALLBACK (modest_ui_actions_save_attachments),
3490                                            MODEST_DIMMING_CALLBACK (modest_ui_dimming_rules_on_save_attachments));
3491         modest_hildon2_window_add_to_menu (MODEST_HILDON2_WINDOW (self), _("mcen_me_inbox_remove_attachments"), NULL,
3492                                            APP_MENU_CALLBACK (modest_ui_actions_remove_attachments),
3493                                            MODEST_DIMMING_CALLBACK (modest_ui_dimming_rules_on_remove_attachments));
3494
3495         modest_hildon2_window_add_to_menu (MODEST_HILDON2_WINDOW (self), _("mcen_me_new_message"), "<Control>n",
3496                                            APP_MENU_CALLBACK (modest_ui_actions_on_new_msg),
3497                                            MODEST_DIMMING_CALLBACK (modest_ui_dimming_rules_on_new_msg));
3498         modest_hildon2_window_add_to_menu (MODEST_HILDON2_WINDOW (self), _("mcen_me_viewer_addtocontacts"), NULL,
3499                                            APP_MENU_CALLBACK (modest_ui_actions_add_to_contacts),
3500                                            MODEST_DIMMING_CALLBACK (modest_ui_dimming_rules_on_add_to_contacts));
3501
3502         modest_hildon2_window_add_to_menu (MODEST_HILDON2_WINDOW (self), _("mcen_ti_message_properties"), NULL,
3503                                            APP_MENU_CALLBACK (modest_ui_actions_on_details),
3504                                            MODEST_DIMMING_CALLBACK (modest_ui_dimming_rules_on_details));
3505 }
3506
3507 void
3508 modest_msg_view_window_add_to_contacts (ModestMsgViewWindow *self)
3509 {
3510         ModestMsgViewWindowPrivate *priv;
3511         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
3512         GSList *recipients = NULL;
3513         TnyMsg *msg = NULL;
3514         gboolean contacts_to_add = FALSE;
3515
3516         msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
3517         if (msg == NULL) {
3518                 TnyHeader *header;
3519
3520                 header = modest_msg_view_window_get_header (self);
3521                 if (header == NULL)
3522                         return;
3523                 recipients = modest_tny_msg_header_get_all_recipients_list (header);
3524                 g_object_unref (header);
3525         } else {
3526                 recipients = modest_tny_msg_get_all_recipients_list (msg);
3527                 g_object_unref (msg);
3528         }
3529
3530         if (recipients != NULL) {
3531                 GtkWidget *picker_dialog;
3532                 GtkWidget *selector;
3533                 GSList *node;
3534                 gchar *selected = NULL;
3535
3536                 selector = hildon_touch_selector_new_text ();
3537                 g_object_ref (selector);
3538
3539                 for (node = recipients; node != NULL; node = g_slist_next (node)) {
3540                         if (!modest_address_book_has_address ((const gchar *) node->data)) {
3541                                 hildon_touch_selector_append_text (HILDON_TOUCH_SELECTOR (selector), 
3542                                                                    (const gchar *) node->data);
3543                                 contacts_to_add = TRUE;
3544                         }
3545                 }
3546
3547                 if (contacts_to_add) {
3548                         gint picker_result;
3549
3550                         picker_dialog = hildon_picker_dialog_new (GTK_WINDOW (self));
3551                         gtk_window_set_title (GTK_WINDOW (picker_dialog), _("mcen_me_viewer_addtocontacts"));
3552
3553                         hildon_picker_dialog_set_selector (HILDON_PICKER_DIALOG (picker_dialog), 
3554                                                            HILDON_TOUCH_SELECTOR (selector));
3555                         
3556                         picker_result = gtk_dialog_run (GTK_DIALOG (picker_dialog));
3557
3558                         if (picker_result == GTK_RESPONSE_OK) {
3559                                 selected = hildon_touch_selector_get_current_text (HILDON_TOUCH_SELECTOR (selector));
3560                         }
3561                         gtk_widget_destroy (picker_dialog);
3562
3563                         if (selected)
3564                                 modest_address_book_add_address (selected, (GtkWindow *) self);
3565                         g_free (selected);
3566
3567                 } else {
3568
3569                         g_object_unref (selector);
3570
3571                 }
3572         }
3573         
3574         if (recipients) {g_slist_foreach (recipients, (GFunc) g_free, NULL); g_slist_free (recipients);}
3575 }
3576
3577 static gboolean 
3578 _modest_msg_view_window_map_event (GtkWidget *widget,
3579                                    GdkEvent *event,
3580                                    gpointer userdata)
3581 {
3582         ModestMsgViewWindow *self = (ModestMsgViewWindow *) userdata;
3583
3584         update_progress_hint (self);
3585
3586         return FALSE;
3587 }
3588
3589 void
3590 modest_msg_view_window_fetch_images (ModestMsgViewWindow *self)
3591 {
3592         ModestMsgViewWindowPrivate *priv;
3593         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
3594
3595         modest_msg_view_request_fetch_images (MODEST_MSG_VIEW (priv->msg_view));
3596 }
3597
3598 gboolean 
3599 modest_msg_view_window_has_blocked_external_images (ModestMsgViewWindow *self)
3600 {
3601         ModestMsgViewWindowPrivate *priv;
3602         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
3603
3604         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), FALSE);
3605
3606         return modest_msg_view_has_blocked_external_images (MODEST_MSG_VIEW (priv->msg_view));
3607 }
3608
3609 void 
3610 modest_msg_view_window_reload (ModestMsgViewWindow *self)
3611 {
3612         ModestMsgViewWindowPrivate *priv;
3613         TnyHeader *header;
3614
3615         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self));
3616
3617         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
3618         header = modest_msg_view_window_get_header (MODEST_MSG_VIEW_WINDOW (self));     
3619
3620         if (!message_reader (self, priv, header, NULL, NULL, priv->row_reference)) {
3621                 g_warning ("Shouldn't happen, trying to reload a message failed");
3622         }
3623
3624         g_object_unref (header);
3625 }
3626
3627 static void
3628 update_branding (ModestMsgViewWindow *self)
3629 {
3630         const gchar *account; 
3631         const gchar *mailbox;
3632         ModestAccountMgr *mgr;
3633         ModestProtocol *protocol = NULL;
3634         gchar *service_name = NULL;
3635         const GdkPixbuf *service_icon = NULL;
3636         ModestMsgViewWindowPrivate *priv;
3637
3638         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
3639
3640         account = modest_window_get_active_account (MODEST_WINDOW (self));
3641         mailbox = modest_window_get_active_mailbox (MODEST_WINDOW (self));
3642
3643         mgr = modest_runtime_get_account_mgr ();
3644
3645         if (modest_account_mgr_account_is_multimailbox (mgr, account, &protocol)) {
3646                 if (MODEST_IS_ACCOUNT_PROTOCOL (protocol)) {
3647                         service_name = modest_account_protocol_get_service_name (MODEST_ACCOUNT_PROTOCOL (protocol),
3648                                                                                  account, mailbox);
3649                         service_icon = modest_account_protocol_get_service_icon (MODEST_ACCOUNT_PROTOCOL (protocol),
3650                                                                                  account, mailbox, MODEST_ICON_SIZE_SMALL);
3651                 }
3652         }
3653
3654         modest_msg_view_set_branding (MODEST_MSG_VIEW (priv->msg_view), service_name, service_icon);
3655         g_free (service_name);
3656 }