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