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