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