Reorganize the message view menu, following the new ui specs:
[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 ModestWindowParentClass *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         self = (ModestMsgViewWindow *) modest_mail_operation_get_source (mail_op);
2232         if (canceled || !self || MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self)->msg_view == NULL ) {
2233                 if (row_reference)
2234                         gtk_tree_row_reference_free (row_reference);
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                 if (self) {
2249                         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2250                         /* First we check if the parent is a folder window */
2251                         if (priv->msg_uid && !modest_window_mgr_get_folder_window (MODEST_WINDOW_MGR (modest_runtime_get_window_mgr ()))) {
2252                                 gboolean is_merge;
2253                                 TnyAccount *account = NULL;
2254                                 GtkWidget *header_window = NULL;
2255
2256                                 is_merge = g_str_has_prefix (priv->msg_uid, "merge:");
2257
2258                                 /* Get the account */
2259                                 if (!is_merge)
2260                                         account = tny_account_store_find_account (TNY_ACCOUNT_STORE (modest_runtime_get_account_store ()),
2261                                                                                   priv->msg_uid);
2262
2263                                 if (is_merge || account) {
2264                                         TnyFolder *folder = NULL;
2265
2266                                         /* Try to get the message, if it's already downloaded
2267                                            we don't need to connect */
2268                                         if (account) {
2269                                                 folder = modest_tny_folder_store_find_folder_from_uri (TNY_FOLDER_STORE (account), 
2270                                                                                                        priv->msg_uid);
2271                                         } else {
2272                                                 ModestTnyAccountStore *account_store;
2273                                                 ModestTnyLocalFoldersAccount *local_folders_account;
2274
2275                                                 account_store = modest_runtime_get_account_store ();
2276                                                 local_folders_account = MODEST_TNY_LOCAL_FOLDERS_ACCOUNT (
2277                                                         modest_tny_account_store_get_local_folders_account (account_store));
2278                                                 folder = modest_tny_local_folders_account_get_merged_outbox (local_folders_account);
2279                                                 g_object_unref (local_folders_account);
2280                                         }
2281                                         if (account) g_object_unref (account);
2282
2283                                         if (folder) {
2284                                                 header_window = (GtkWidget *)
2285                                                         modest_header_window_new (
2286                                                                 folder, 
2287                                                                 modest_window_get_active_account (MODEST_WINDOW (self)), 
2288                                                                 modest_window_get_active_mailbox (MODEST_WINDOW (self)));
2289                                                 if (!modest_window_mgr_register_window (modest_runtime_get_window_mgr (),
2290                                                                                         MODEST_WINDOW (header_window),
2291                                                                                         NULL)) {
2292                                                         gtk_widget_destroy (GTK_WIDGET (header_window));
2293                                                 } else {
2294                                                         gtk_widget_show_all (GTK_WIDGET (header_window));
2295                                                 }
2296                                                 g_object_unref (folder);
2297                                         }
2298                                 }
2299                         }
2300
2301
2302                         /* Restore window title */
2303                         update_window_title (self);
2304                         modest_ui_actions_on_close_window (NULL, MODEST_WINDOW (self));
2305                         g_object_unref (self);
2306                 }
2307                 return;
2308         }
2309
2310         /* Get the window */ 
2311         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self));
2312         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2313
2314         /* Update the row reference */
2315         if (priv->row_reference != NULL) {
2316                 gtk_tree_row_reference_free (priv->row_reference);
2317                 priv->row_reference = (row_reference && gtk_tree_row_reference_valid (row_reference))?gtk_tree_row_reference_copy (row_reference):NULL;
2318                 if (priv->next_row_reference != NULL) {
2319                         gtk_tree_row_reference_free (priv->next_row_reference);
2320                 }
2321                 if (priv->row_reference) {
2322                         priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
2323                         select_next_valid_row (priv->header_model, &(priv->next_row_reference), TRUE, priv->is_outbox);
2324                 } else {
2325                         priv->next_row_reference = NULL;
2326                 }
2327         }
2328
2329         /* Mark header as read */
2330         if (!(tny_header_get_flags (header) & TNY_HEADER_FLAG_SEEN)) {
2331                 gchar *uid;
2332
2333                 tny_header_set_flag (header, TNY_HEADER_FLAG_SEEN);
2334                 uid = modest_tny_folder_get_header_unique_id (header);
2335                 modest_platform_emit_msg_read_changed_signal (uid, TRUE);
2336                 g_free (uid);
2337         }
2338
2339         /* Set new message */
2340         if (priv->msg_view != NULL && TNY_IS_MSG_VIEW (priv->msg_view)) {
2341                 tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
2342                 modest_msg_view_window_update_priority (self);
2343                 update_window_title (MODEST_MSG_VIEW_WINDOW (self));
2344                 update_branding (MODEST_MSG_VIEW_WINDOW (self));
2345                 modest_msg_view_grab_focus (MODEST_MSG_VIEW (priv->msg_view));
2346         }
2347
2348         /* Set the new message uid of the window  */
2349         if (priv->msg_uid) {
2350                 g_free (priv->msg_uid);
2351                 priv->msg_uid = modest_tny_folder_get_header_unique_id (header);
2352         }
2353
2354         /* Notify the observers */
2355         g_signal_emit (G_OBJECT (self), signals[MSG_CHANGED_SIGNAL],
2356                        0, priv->header_model, priv->row_reference);
2357
2358         /* Sync the flags if the message is not opened from a header
2359            model, i.e, if it's opened from a notification */
2360         if (!priv->header_model)
2361                 sync_flags (self);
2362
2363         /* Frees */
2364         g_object_unref (self);
2365         if (row_reference)
2366                 gtk_tree_row_reference_free (row_reference);
2367 }
2368
2369 TnyFolderType
2370 modest_msg_view_window_get_folder_type (ModestMsgViewWindow *window)
2371 {
2372         ModestMsgViewWindowPrivate *priv;
2373         TnyMsg *msg;
2374         TnyFolderType folder_type;
2375
2376         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2377
2378         folder_type = TNY_FOLDER_TYPE_UNKNOWN;
2379
2380         msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
2381         if (msg) {
2382                 TnyFolder *folder;
2383
2384                 folder = tny_msg_get_folder (msg);
2385                 if (folder) {
2386                         folder_type = modest_tny_folder_guess_folder_type (folder);
2387                         g_object_unref (folder);
2388                 }
2389                 g_object_unref (msg);
2390         }
2391
2392         return folder_type;
2393 }
2394
2395
2396 static void
2397 modest_msg_view_window_update_priority (ModestMsgViewWindow *window)
2398 {
2399         ModestMsgViewWindowPrivate *priv;
2400         TnyHeader *header = NULL;
2401         TnyHeaderFlags flags = 0;
2402
2403         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2404
2405         if (priv->header_model && priv->row_reference && gtk_tree_row_reference_valid (priv->row_reference)) {
2406                 GtkTreeIter iter;
2407                 GtkTreePath *path = NULL;
2408
2409                 path = gtk_tree_row_reference_get_path (priv->row_reference);
2410                 g_return_if_fail (path != NULL);
2411                 gtk_tree_model_get_iter (priv->header_model, 
2412                                          &iter, 
2413                                          gtk_tree_row_reference_get_path (priv->row_reference));
2414
2415                 gtk_tree_model_get (priv->header_model, &iter, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
2416                                     &header, -1);
2417                 gtk_tree_path_free (path);
2418         } else {
2419                 TnyMsg *msg;
2420                 msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
2421                 if (msg) {
2422                         header = tny_msg_get_header (msg);
2423                         g_object_unref (msg);
2424                 }
2425         }
2426
2427         if (header) {
2428                 flags = tny_header_get_flags (header);
2429                 g_object_unref(G_OBJECT(header));
2430         }
2431
2432         modest_msg_view_set_priority (MODEST_MSG_VIEW(priv->msg_view), flags);
2433
2434 }
2435
2436 static void
2437 toolbar_resize (ModestMsgViewWindow *self)
2438 {
2439         ModestMsgViewWindowPrivate *priv = NULL;
2440         ModestWindowPrivate *parent_priv = NULL;
2441         GtkWidget *widget;
2442         gint static_button_size;
2443         ModestWindowMgr *mgr;
2444
2445         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self));
2446         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2447         parent_priv = MODEST_WINDOW_GET_PRIVATE(self);
2448
2449         mgr = modest_runtime_get_window_mgr ();
2450         static_button_size = modest_window_mgr_get_fullscreen_mode (mgr)?120:120;
2451
2452         if (parent_priv->toolbar) {
2453                 /* Set expandable and homogeneous tool buttons */
2454                 widget = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageReply");
2455                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (widget), TRUE);
2456                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (widget), TRUE);
2457                 widget = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageReplyAll");
2458                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (widget), TRUE);
2459                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (widget), TRUE);
2460                 widget = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageForward");
2461                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (widget), TRUE);
2462                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (widget), TRUE);
2463                 widget = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarDeleteMessage");
2464                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (widget), TRUE);
2465                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (widget), TRUE);
2466                 widget = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarDownloadExternalImages");
2467                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (widget), TRUE);
2468                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (widget), TRUE);
2469                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (priv->next_toolitem), TRUE);
2470                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->next_toolitem), TRUE);
2471                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (priv->prev_toolitem), TRUE);
2472                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->prev_toolitem), TRUE);
2473         }
2474 }
2475
2476 static void
2477 modest_msg_view_window_show_toolbar (ModestWindow *self,
2478                                      gboolean show_toolbar)
2479 {
2480         ModestMsgViewWindowPrivate *priv = NULL;
2481         ModestWindowPrivate *parent_priv;
2482
2483         parent_priv = MODEST_WINDOW_GET_PRIVATE(self);
2484         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2485
2486         /* Set optimized view status */
2487         priv->optimized_view = !show_toolbar;
2488
2489         if (!parent_priv->toolbar) {
2490                 parent_priv->toolbar = gtk_ui_manager_get_widget (parent_priv->ui_manager, 
2491                                                                   "/ToolBar");
2492
2493 #ifdef MODEST_TOOLKIT_HILDON2
2494                 gtk_toolbar_set_icon_size (GTK_TOOLBAR (parent_priv->toolbar), HILDON_ICON_SIZE_FINGER);
2495 #else
2496                 gtk_toolbar_set_icon_size (GTK_TOOLBAR (parent_priv->toolbar), GTK_ICON_SIZE_LARGE_TOOLBAR);
2497 #endif
2498                 gtk_widget_set_no_show_all (parent_priv->toolbar, TRUE);
2499
2500                 priv->next_toolitem = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageNext");
2501                 priv->prev_toolitem = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageBack");
2502                 toolbar_resize (MODEST_MSG_VIEW_WINDOW (self));
2503
2504                 modest_window_add_toolbar (MODEST_WINDOW (self), 
2505                                            GTK_TOOLBAR (parent_priv->toolbar));
2506
2507         }
2508
2509         if (show_toolbar) {
2510                 /* Quick hack: this prevents toolbar icons "dance" when progress bar show status is changed */ 
2511                 /* TODO: resize mode migth be GTK_RESIZE_QUEUE, in order to avoid unneccesary shows */
2512                 gtk_container_set_resize_mode (GTK_CONTAINER(parent_priv->toolbar), GTK_RESIZE_IMMEDIATE);
2513
2514                 gtk_widget_show (GTK_WIDGET (parent_priv->toolbar));
2515                 if (modest_msg_view_window_transfer_mode_enabled (MODEST_MSG_VIEW_WINDOW (self))) 
2516                         set_progress_hint (MODEST_MSG_VIEW_WINDOW (self), TRUE);
2517                 else
2518                         set_progress_hint (MODEST_MSG_VIEW_WINDOW (self), FALSE);
2519
2520         } else {
2521                 gtk_widget_set_no_show_all (parent_priv->toolbar, TRUE);
2522                 gtk_widget_hide (GTK_WIDGET (parent_priv->toolbar));
2523         }
2524 }
2525
2526 static void 
2527 modest_msg_view_window_clipboard_owner_change (GtkClipboard *clipboard,
2528                                                GdkEvent *event,
2529                                                ModestMsgViewWindow *window)
2530 {
2531         if (!GTK_WIDGET_VISIBLE (window))
2532                 return;
2533
2534         modest_window_check_dimming_rules_group (MODEST_WINDOW (window), MODEST_DIMMING_RULES_CLIPBOARD);
2535 }
2536
2537 gboolean 
2538 modest_msg_view_window_transfer_mode_enabled (ModestMsgViewWindow *self)
2539 {
2540         ModestMsgViewWindowPrivate *priv;
2541         
2542         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), FALSE); 
2543         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2544
2545         return priv->progress_hint;
2546 }
2547
2548 static gboolean
2549 observers_empty (ModestMsgViewWindow *self)
2550 {
2551         GSList *tmp = NULL;
2552         ModestMsgViewWindowPrivate *priv;
2553         gboolean is_empty = TRUE;
2554         guint pending_ops = 0;
2555  
2556         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2557         tmp = priv->progress_widgets;
2558
2559         /* Check all observers */
2560         while (tmp && is_empty)  {
2561                 pending_ops = modest_progress_object_num_pending_operations (MODEST_PROGRESS_OBJECT(tmp->data));
2562                 is_empty = pending_ops == 0;
2563                 
2564                 tmp = g_slist_next(tmp);
2565         }
2566         
2567         return is_empty;
2568 }
2569
2570 static void
2571 on_account_removed (TnyAccountStore *account_store, 
2572                     TnyAccount *account,
2573                     gpointer user_data)
2574 {
2575         /* Do nothing if it's a transport account, because we only
2576            show the messages of a store account */
2577         if (tny_account_get_account_type(account) == TNY_ACCOUNT_TYPE_STORE) {
2578                 const gchar *parent_acc = NULL;
2579                 const gchar *our_acc = NULL;
2580
2581                 our_acc = modest_window_get_active_account (MODEST_WINDOW (user_data));
2582                 parent_acc = modest_tny_account_get_parent_modest_account_name_for_server_account (account);
2583
2584                 /* Close this window if I'm showing a message of the removed account */
2585                 if (our_acc && parent_acc && strcmp (parent_acc, our_acc) == 0)
2586                         modest_ui_actions_on_close_window (NULL, MODEST_WINDOW (user_data));
2587         }
2588 }
2589
2590 static void 
2591 on_mail_operation_started (ModestMailOperation *mail_op,
2592                            gpointer user_data)
2593 {
2594         ModestMsgViewWindow *self;
2595         ModestMailOperationTypeOperation op_type;
2596         GSList *tmp;
2597         ModestMsgViewWindowPrivate *priv;
2598         GObject *source = NULL;
2599
2600         self = MODEST_MSG_VIEW_WINDOW (user_data);
2601         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2602         op_type = modest_mail_operation_get_type_operation (mail_op);
2603         tmp = priv->progress_widgets;
2604         source = modest_mail_operation_get_source(mail_op);
2605         if (G_OBJECT (self) == source) {
2606                 if (op_type == MODEST_MAIL_OPERATION_TYPE_RECEIVE ||
2607                     op_type == MODEST_MAIL_OPERATION_TYPE_OPEN ||
2608                     op_type == MODEST_MAIL_OPERATION_TYPE_DELETE) {
2609                         set_progress_hint (self, TRUE);
2610                         while (tmp) {
2611                                 modest_progress_object_add_operation (
2612                                                 MODEST_PROGRESS_OBJECT (tmp->data),
2613                                                 mail_op);
2614                                 tmp = g_slist_next (tmp);
2615                         }
2616                 }
2617         }
2618         g_object_unref (source);
2619
2620         /* Update dimming rules */
2621         check_dimming_rules_after_change (self);
2622 }
2623
2624 static void
2625 on_mail_operation_finished (ModestMailOperation *mail_op,
2626                             gpointer user_data)
2627 {
2628         ModestMsgViewWindow *self;
2629         ModestMailOperationTypeOperation op_type;
2630         GSList *tmp;
2631         ModestMsgViewWindowPrivate *priv;
2632
2633         self = MODEST_MSG_VIEW_WINDOW (user_data);
2634         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2635         op_type = modest_mail_operation_get_type_operation (mail_op);
2636         tmp = priv->progress_widgets;
2637
2638         if (op_type == MODEST_MAIL_OPERATION_TYPE_RECEIVE ||
2639             op_type == MODEST_MAIL_OPERATION_TYPE_OPEN ||
2640             op_type == MODEST_MAIL_OPERATION_TYPE_DELETE) {
2641                 while (tmp) {
2642                         modest_progress_object_remove_operation (MODEST_PROGRESS_OBJECT (tmp->data),
2643                                                                  mail_op);
2644                         tmp = g_slist_next (tmp);
2645                 }
2646
2647                 /* If no more operations are being observed, NORMAL mode is enabled again */
2648                 if (observers_empty (self)) {
2649                         set_progress_hint (self, FALSE);
2650                 }
2651         }
2652
2653         /* Update dimming rules. We have to do this right here
2654            and not in view_msg_cb because at that point the
2655            transfer mode is still enabled so the dimming rule
2656            won't let the user delete the message that has been
2657            readed for example */
2658         check_dimming_rules_after_change (self);
2659 }
2660
2661 static void
2662 on_queue_changed (ModestMailOperationQueue *queue,
2663                   ModestMailOperation *mail_op,
2664                   ModestMailOperationQueueNotification type,
2665                   ModestMsgViewWindow *self)
2666 {       
2667         ModestMsgViewWindowPrivate *priv;
2668
2669         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2670
2671         /* If this operations was created by another window, do nothing */
2672         if (!modest_mail_operation_is_mine (mail_op, G_OBJECT(self))) 
2673             return;
2674
2675         if (type == MODEST_MAIL_OPERATION_QUEUE_OPERATION_ADDED) {
2676                 priv->sighandlers = modest_signal_mgr_connect (priv->sighandlers,
2677                                                                G_OBJECT (mail_op),
2678                                                                "operation-started",
2679                                                                G_CALLBACK (on_mail_operation_started),
2680                                                                self);
2681                 priv->sighandlers = modest_signal_mgr_connect (priv->sighandlers,
2682                                                                G_OBJECT (mail_op),
2683                                                                "operation-finished",
2684                                                                G_CALLBACK (on_mail_operation_finished),
2685                                                                self);
2686         } else if (type == MODEST_MAIL_OPERATION_QUEUE_OPERATION_REMOVED) {
2687                 priv->sighandlers = modest_signal_mgr_disconnect (priv->sighandlers,
2688                                                                   G_OBJECT (mail_op),
2689                                                                   "operation-started");
2690                 priv->sighandlers = modest_signal_mgr_disconnect (priv->sighandlers,
2691                                                                   G_OBJECT (mail_op),
2692                                                                   "operation-finished");
2693         }
2694 }
2695
2696 TnyList *
2697 modest_msg_view_window_get_attachments (ModestMsgViewWindow *win) 
2698 {
2699         ModestMsgViewWindowPrivate *priv;
2700         TnyList *selected_attachments = NULL;
2701         
2702         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (win), NULL);
2703         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (win);
2704
2705         /* In Hildon 2.2 as there's no selection we assume we have all attachments selected */
2706         selected_attachments = modest_msg_view_get_attachments (MODEST_MSG_VIEW (priv->msg_view));
2707         
2708         return selected_attachments;
2709 }
2710
2711 typedef struct {
2712         ModestMsgViewWindow *self;
2713         gchar *file_path;
2714         gchar *attachment_uid;
2715 } DecodeAsyncHelper;
2716
2717 static void
2718 on_decode_to_stream_async_handler (TnyMimePart *mime_part, 
2719                                    gboolean cancelled, 
2720                                    TnyStream *stream, 
2721                                    GError *err, 
2722                                    gpointer user_data)
2723 {
2724         DecodeAsyncHelper *helper = (DecodeAsyncHelper *) user_data;
2725         const gchar *content_type;
2726         ModestMsgViewWindowPrivate *priv;
2727
2728         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (helper->self);
2729
2730         if (cancelled || err) {
2731                 if (err) {
2732                         gchar *msg;
2733                         if ((err->domain == TNY_ERROR_DOMAIN) && 
2734                             (err->code == TNY_IO_ERROR_WRITE) &&
2735                             (errno == ENOSPC)) {
2736                                 msg = g_strdup_printf (_KR("cerm_device_memory_full"), "");
2737                         } else {
2738                                 msg = g_strdup (_("mail_ib_file_operation_failed"));
2739                         }
2740                         modest_platform_information_banner (NULL, NULL, msg);
2741                         g_free (msg);
2742                 }
2743                 goto free;
2744         }
2745
2746         /* It could happen that the window was closed. So we
2747            assume it is a cancelation */
2748         if (!GTK_WIDGET_VISIBLE (helper->self))
2749                 goto free;
2750
2751         /* Remove the progress hint */
2752         set_progress_hint (helper->self, FALSE);
2753
2754         content_type = tny_mime_part_get_content_type (mime_part);
2755         if (content_type && g_str_has_prefix (content_type, "message/rfc822")) {
2756                 ModestWindowMgr *mgr;
2757                 ModestWindow *msg_win = NULL;
2758                 TnyMsg * msg;
2759                 gchar *account;
2760                 const gchar *mailbox;
2761                 TnyStream *file_stream;
2762                 gint fd;
2763
2764                 fd = g_open (helper->file_path, O_RDONLY, 0644);
2765                 if (fd != -1) {
2766                         TnyMsg *top_msg;
2767                         file_stream = tny_fs_stream_new (fd);
2768
2769                         mgr = modest_runtime_get_window_mgr ();
2770
2771                         account = g_strdup (modest_window_get_active_account (MODEST_WINDOW (helper->self)));
2772                         mailbox = modest_window_get_active_mailbox (MODEST_WINDOW (helper->self));
2773
2774                         if (!account)
2775                                 account = modest_account_mgr_get_default_account (modest_runtime_get_account_mgr ());
2776
2777                         msg = tny_camel_msg_new ();
2778                         tny_camel_msg_parse (msg, file_stream);
2779
2780                         if (priv->top_msg)
2781                                 top_msg = g_object_ref (priv->top_msg);
2782                         else
2783                                 top_msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
2784
2785                         msg_win = modest_msg_view_window_new_for_attachment (TNY_MSG (msg), top_msg, 
2786                                                                              account, mailbox, helper->attachment_uid);
2787                         if (top_msg) g_object_unref (top_msg);
2788                         modest_window_set_zoom (MODEST_WINDOW (msg_win),
2789                                                 modest_window_get_zoom (MODEST_WINDOW (helper->self)));
2790                         if (modest_window_mgr_register_window (mgr, msg_win, MODEST_WINDOW (helper->self)))
2791                                 gtk_widget_show_all (GTK_WIDGET (msg_win));
2792                         else
2793                                 gtk_widget_destroy (GTK_WIDGET (msg_win));
2794                         g_object_unref (msg);
2795                         g_object_unref (file_stream);
2796                 } else {
2797                         modest_platform_information_banner (NULL, NULL, _("mail_ib_file_operation_failed"));
2798                 }
2799
2800         } else {
2801
2802                 /* make the file read-only */
2803                 g_chmod(helper->file_path, 0444);
2804
2805                 /* Activate the file */
2806                 modest_platform_activate_file (helper->file_path, content_type);
2807         }
2808
2809  free:
2810         /* Frees */
2811         g_object_unref (helper->self);
2812         g_free (helper->file_path);
2813         g_free (helper->attachment_uid);
2814         g_slice_free (DecodeAsyncHelper, helper);
2815 }
2816
2817 static void
2818 view_attachment_connect_handler (gboolean canceled,
2819                                  GError *err,
2820                                  GtkWindow *parent_window,
2821                                  TnyAccount *account,
2822                                  TnyMimePart *part)
2823 {
2824
2825         if (canceled || err) {
2826                 g_object_unref (part);
2827                 return;
2828         }
2829
2830         modest_msg_view_window_view_attachment (MODEST_MSG_VIEW_WINDOW (parent_window),
2831                                                 part);
2832         g_object_unref (part);
2833 }
2834
2835 void
2836 modest_msg_view_window_view_attachment (ModestMsgViewWindow *window, 
2837                                         TnyMimePart *mime_part)
2838 {
2839         ModestMsgViewWindowPrivate *priv;
2840         const gchar *msg_uid;
2841         gchar *attachment_uid = NULL;
2842         gint attachment_index = 0;
2843         TnyList *attachments;
2844
2845         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
2846         g_return_if_fail (TNY_IS_MIME_PART (mime_part) || (mime_part == NULL));
2847         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2848
2849         msg_uid = modest_msg_view_window_get_message_uid (MODEST_MSG_VIEW_WINDOW (window));
2850         attachments = modest_msg_view_get_attachments (MODEST_MSG_VIEW (priv->msg_view));
2851         attachment_index = modest_list_index (attachments, (GObject *) mime_part);
2852         g_object_unref (attachments);
2853
2854         if (msg_uid && attachment_index >= 0) {
2855                 attachment_uid = g_strdup_printf ("%s/%d", msg_uid, attachment_index);
2856         }
2857
2858         if (mime_part == NULL) {
2859                 gboolean error = FALSE;
2860                 TnyList *selected_attachments = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2861                 if (selected_attachments == NULL || tny_list_get_length (selected_attachments) == 0) {
2862                         error = TRUE;
2863                 } else if (tny_list_get_length (selected_attachments) > 1) {
2864                         modest_platform_system_banner (NULL, NULL, _("mcen_ib_unable_to_display_more"));
2865                         error = TRUE;
2866                 } else {
2867                         TnyIterator *iter;
2868                         iter = tny_list_create_iterator (selected_attachments);
2869                         mime_part = (TnyMimePart *) tny_iterator_get_current (iter);
2870                         g_object_unref (iter);
2871                 }
2872                 if (selected_attachments)
2873                         g_object_unref (selected_attachments);
2874
2875                 if (error)
2876                         goto frees;
2877         } else {
2878                 g_object_ref (mime_part);
2879         }
2880
2881         if (tny_mime_part_is_purged (mime_part))
2882                 goto frees;
2883
2884         if (TNY_IS_CAMEL_BS_MIME_PART (mime_part) &&
2885             !tny_camel_bs_mime_part_is_fetched (TNY_CAMEL_BS_MIME_PART (mime_part))) {
2886                 gboolean is_merge;
2887                 TnyAccount *account;
2888
2889                 is_merge = g_str_has_prefix (priv->msg_uid, "merge:");
2890                 account = NULL;
2891                 /* Get the account */
2892                 if (!is_merge)
2893                         account = tny_account_store_find_account (TNY_ACCOUNT_STORE (modest_runtime_get_account_store ()),
2894                                                                   priv->msg_uid);
2895
2896                 if (!tny_device_is_online (modest_runtime_get_device())) {
2897                         modest_platform_connect_and_perform ((ModestWindow *) window,
2898                                                              TRUE,
2899                                                              TNY_ACCOUNT (account),
2900                                                              (ModestConnectedPerformer) view_attachment_connect_handler,
2901                                                              g_object_ref (mime_part));
2902                         goto frees;
2903                 }
2904         }
2905
2906         if (!modest_tny_mime_part_is_msg (mime_part) && tny_mime_part_get_filename (mime_part)) {
2907                 gchar *filepath = NULL;
2908                 const gchar *att_filename = tny_mime_part_get_filename (mime_part);
2909                 gboolean show_error_banner = FALSE;
2910                 TnyFsStream *temp_stream = NULL;
2911                 temp_stream = modest_utils_create_temp_stream (att_filename, attachment_uid,
2912                                                                &filepath);
2913
2914                 if (temp_stream != NULL) {
2915                         ModestAccountMgr *mgr;
2916                         DecodeAsyncHelper *helper;
2917                         gboolean decode_in_provider;
2918                         ModestProtocol *protocol;
2919                         const gchar *account; 
2920
2921                         /* Activate progress hint */
2922                         set_progress_hint (window, TRUE);
2923
2924                         helper = g_slice_new0 (DecodeAsyncHelper);
2925                         helper->self = g_object_ref (window);
2926                         helper->file_path = g_strdup (filepath);
2927                         helper->attachment_uid = g_strdup (attachment_uid);
2928
2929                         decode_in_provider = FALSE;
2930                         mgr = modest_runtime_get_account_mgr ();
2931                         account = modest_window_get_active_account (MODEST_WINDOW (window));
2932                         if (modest_account_mgr_account_is_multimailbox (mgr, account, &protocol)) {
2933                                 if (MODEST_IS_ACCOUNT_PROTOCOL (protocol)) {
2934                                         gchar *uri;
2935                                         uri = g_strconcat ("file://", filepath, NULL);
2936                                         decode_in_provider = 
2937                                                 modest_account_protocol_decode_part_to_stream_async (
2938                                                         MODEST_ACCOUNT_PROTOCOL (protocol),
2939                                                         mime_part,
2940                                                         filepath,
2941                                                         TNY_STREAM (temp_stream),
2942                                                         on_decode_to_stream_async_handler,
2943                                                         NULL,
2944                                                         helper);
2945                                         g_free (uri);
2946                                 }
2947                         }
2948
2949                         if (!decode_in_provider)
2950                                 tny_mime_part_decode_to_stream_async (mime_part, TNY_STREAM (temp_stream),
2951                                                                       on_decode_to_stream_async_handler,
2952                                                                       NULL,
2953                                                                       helper);
2954                         g_object_unref (temp_stream);
2955                         /* NOTE: files in the temporary area will be automatically
2956                          * cleaned after some time if they are no longer in use */
2957                 } else {
2958                         if (filepath) {
2959                                 const gchar *content_type;
2960                                 /* the file may already exist but it isn't writable,
2961                                  * let's try to open it anyway */
2962                                 content_type = tny_mime_part_get_content_type (mime_part);
2963                                 modest_platform_activate_file (filepath, content_type);
2964                         } else {
2965                                 g_warning ("%s: modest_utils_create_temp_stream failed", __FUNCTION__);
2966                                 show_error_banner = TRUE;
2967                         }
2968                 }
2969                 if (filepath)
2970                         g_free (filepath);
2971                 if (show_error_banner)
2972                         modest_platform_information_banner (NULL, NULL, _("mail_ib_file_operation_failed"));
2973         } else if (!modest_tny_mime_part_is_msg (mime_part)) {
2974                 ModestWindowMgr *mgr;
2975                 ModestWindow *msg_win = NULL;
2976                 TnyMsg *current_msg;
2977                 gboolean found;
2978                 TnyHeader *header;
2979
2980                 current_msg = modest_msg_view_window_get_message (MODEST_MSG_VIEW_WINDOW (window));
2981                 mgr = modest_runtime_get_window_mgr ();
2982                 header = tny_msg_get_header (TNY_MSG (current_msg));
2983                 found = modest_window_mgr_find_registered_message_uid (mgr,
2984                                                                        attachment_uid,
2985                                                                        &msg_win);
2986                 
2987                 if (found) {
2988                         g_debug ("window for this body is already being created");
2989                 } else {
2990                         TnyMsg *top_msg;
2991
2992                         /* it's not found, so create a new window for it */
2993                         modest_window_mgr_register_header (mgr, header, attachment_uid); /* register the uid before building the window */
2994                         gchar *account = g_strdup (modest_window_get_active_account (MODEST_WINDOW (window)));
2995                         const gchar *mailbox = modest_window_get_active_mailbox (MODEST_WINDOW (window));
2996                         if (!account)
2997                                 account = modest_account_mgr_get_default_account (modest_runtime_get_account_mgr ());
2998
2999                         if (priv->top_msg)
3000                                 top_msg = g_object_ref (priv->top_msg);
3001                         else
3002                                 top_msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
3003                         
3004                         msg_win = modest_msg_view_window_new_with_other_body (TNY_MSG (current_msg), TNY_MIME_PART (mime_part), top_msg,
3005                                                                               account, mailbox, attachment_uid);
3006
3007                         if (top_msg) g_object_unref (top_msg);
3008                         
3009                         modest_window_set_zoom (MODEST_WINDOW (msg_win),
3010                                                 modest_window_get_zoom (MODEST_WINDOW (window)));
3011                         if (modest_window_mgr_register_window (mgr, msg_win, MODEST_WINDOW (window)))
3012                                 gtk_widget_show_all (GTK_WIDGET (msg_win));
3013                         else
3014                                 gtk_widget_destroy (GTK_WIDGET (msg_win));
3015                 }
3016                 g_object_unref (current_msg);           
3017         } else {
3018                 /* message attachment */
3019                 TnyHeader *header = NULL;
3020                 ModestWindowMgr *mgr;
3021                 ModestWindow *msg_win = NULL;
3022                 gboolean found;
3023
3024                 header = tny_msg_get_header (TNY_MSG (mime_part));
3025                 mgr = modest_runtime_get_window_mgr ();
3026                 found = modest_window_mgr_find_registered_header (mgr, header, &msg_win);
3027
3028                 if (found) {
3029                         /* if it's found, but there is no msg_win, it's probably in the process of being created;
3030                          * thus, we don't do anything */
3031                         g_debug ("window for is already being created");
3032                 } else {
3033                         TnyMsg *top_msg;
3034                         /* it's not found, so create a new window for it */
3035                         modest_window_mgr_register_header (mgr, header, attachment_uid); /* register the uid before building the window */
3036                         gchar *account = g_strdup (modest_window_get_active_account (MODEST_WINDOW (window)));
3037                         const gchar *mailbox = modest_window_get_active_mailbox (MODEST_WINDOW (window));
3038                         if (!account)
3039                                 account = modest_account_mgr_get_default_account (modest_runtime_get_account_mgr ());
3040                         if (priv->top_msg)
3041                                 top_msg = g_object_ref (priv->top_msg);
3042                         else
3043                                 top_msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
3044                         msg_win = modest_msg_view_window_new_for_attachment (
3045                                 TNY_MSG (mime_part), top_msg, account, 
3046                                 mailbox, attachment_uid);
3047                         modest_window_set_zoom (MODEST_WINDOW (msg_win),
3048                                                 modest_window_get_zoom (MODEST_WINDOW (window)));
3049                         if (modest_window_mgr_register_window (mgr, msg_win, MODEST_WINDOW (window)))
3050                                 gtk_widget_show_all (GTK_WIDGET (msg_win));
3051                         else
3052                                 gtk_widget_destroy (GTK_WIDGET (msg_win));
3053                 }
3054         }
3055
3056  frees:
3057         if (attachment_uid)
3058                 g_free (attachment_uid);
3059         if (mime_part)
3060                 g_object_unref (mime_part);
3061 }
3062
3063 typedef struct
3064 {
3065         gchar *filename;
3066         TnyMimePart *part;
3067 } SaveMimePartPair;
3068
3069 typedef struct
3070 {
3071         GList *pairs;
3072         GnomeVFSResult result;
3073         gchar *uri;
3074         ModestMsgViewWindow *window;
3075 } SaveMimePartInfo;
3076
3077 static void save_mime_part_info_free (SaveMimePartInfo *info, gboolean with_struct);
3078 static gboolean idle_save_mime_part_show_result (SaveMimePartInfo *info);
3079 static gpointer save_mime_part_to_file (SaveMimePartInfo *info);
3080 static void save_mime_parts_to_file_with_checks (GtkWindow *parent, SaveMimePartInfo *info);
3081
3082 static void
3083 save_mime_part_info_free (SaveMimePartInfo *info, gboolean with_struct)
3084 {
3085         GList *node;
3086
3087         for (node = info->pairs; node != NULL; node = g_list_next (node)) {
3088                 SaveMimePartPair *pair = (SaveMimePartPair *) node->data;
3089                 g_free (pair->filename);
3090                 g_object_unref (pair->part);
3091                 g_slice_free (SaveMimePartPair, pair);
3092         }
3093         g_list_free (info->pairs);
3094         info->pairs = NULL;
3095         g_free (info->uri);
3096         g_object_unref (info->window);
3097         info->window = NULL;
3098         if (with_struct) {
3099                 g_slice_free (SaveMimePartInfo, info);
3100         }
3101 }
3102
3103 static gboolean
3104 idle_save_mime_part_show_result (SaveMimePartInfo *info)
3105 {
3106         /* This is a GDK lock because we are an idle callback and
3107          * modest_platform_system_banner is or does Gtk+ code */
3108
3109         gdk_threads_enter (); /* CHECKED */
3110         if (info->result == GNOME_VFS_ERROR_CANCELLED) {
3111                 /* nothing */
3112         } else if (info->result == GNOME_VFS_OK) {
3113                 modest_platform_system_banner (NULL, NULL, _CS_SAVED);
3114         } else if (info->result == GNOME_VFS_ERROR_NO_SPACE) {
3115                 gchar *msg = NULL;
3116
3117                 /* Check if the uri belongs to the external mmc */
3118                 if (g_str_has_prefix (info->uri, g_getenv (MODEST_MMC1_VOLUMEPATH_ENV)))
3119                         msg = g_strdup_printf (_KR("cerm_device_memory_full"), "");
3120                 else
3121                         msg = g_strdup (_KR("cerm_memory_card_full"));
3122                 modest_platform_information_banner (NULL, NULL, msg);
3123                 g_free (msg);
3124         } else {
3125                 modest_platform_system_banner (NULL, NULL, _("mail_ib_file_operation_failed"));
3126         }
3127         set_progress_hint (info->window, FALSE);
3128         save_mime_part_info_free (info, FALSE);
3129         gdk_threads_leave (); /* CHECKED */
3130
3131         return FALSE;
3132 }
3133
3134 static void
3135 save_mime_part_to_file_connect_handler (gboolean canceled,
3136                                         GError *err,
3137                                         GtkWindow *parent_window,
3138                                         TnyAccount *account,
3139                                         SaveMimePartInfo *info)
3140 {
3141         if (canceled || err) {
3142                 if (canceled && !err) {
3143                         info->result = GNOME_VFS_ERROR_CANCELLED;
3144                 }
3145                 g_idle_add ((GSourceFunc) idle_save_mime_part_show_result, info);
3146         } else {
3147                 g_thread_create ((GThreadFunc)save_mime_part_to_file, info, FALSE, NULL);
3148         }
3149 }
3150
3151 static gboolean
3152 save_mime_part_to_file_connect_idle (SaveMimePartInfo *info)
3153 {
3154         gboolean is_merge;
3155         TnyAccount *account;
3156         ModestMsgViewWindowPrivate *priv;
3157
3158         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (info->window);
3159
3160         is_merge = g_str_has_prefix (priv->msg_uid, "merge:");
3161         account = NULL;
3162
3163         /* Get the account */
3164         if (!is_merge)
3165                 account = tny_account_store_find_account (TNY_ACCOUNT_STORE (modest_runtime_get_account_store ()),
3166                                                           priv->msg_uid);
3167
3168         modest_platform_connect_and_perform ((ModestWindow *) info->window,
3169                                              TRUE,
3170                                              TNY_ACCOUNT (account),
3171                                              (ModestConnectedPerformer) save_mime_part_to_file_connect_handler,
3172                                              info);
3173
3174         if (account)
3175                 g_object_unref (account);
3176
3177         return FALSE;
3178 }
3179
3180 static gpointer
3181 save_mime_part_to_file (SaveMimePartInfo *info)
3182 {
3183         GnomeVFSHandle *handle;
3184         TnyStream *stream;
3185         SaveMimePartPair *pair = (SaveMimePartPair *) info->pairs->data;
3186
3187         if (TNY_IS_CAMEL_BS_MIME_PART (pair->part) &&
3188             !tny_camel_bs_mime_part_is_fetched (TNY_CAMEL_BS_MIME_PART (pair->part))) {
3189                 gboolean check_online = TRUE;
3190                 ModestMsgViewWindowPrivate *priv = NULL;
3191
3192                 /* Check if we really need to connect to save the mime part */
3193                 priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (info->window);
3194                 if (g_str_has_prefix (priv->msg_uid, "merge:")) {
3195                         check_online = FALSE;
3196                 } else {
3197                         TnyAccountStore *acc_store;
3198                         TnyAccount *account = NULL;
3199
3200                         acc_store = (TnyAccountStore*) modest_runtime_get_account_store ();
3201                         account = tny_account_store_find_account (acc_store, priv->msg_uid);
3202
3203                         if (account) {
3204                                 if (tny_account_get_connection_status (account) ==
3205                                     TNY_CONNECTION_STATUS_CONNECTED)
3206                                         check_online = FALSE;
3207                                 g_object_unref (account);
3208                         } else {
3209                                 check_online = !tny_device_is_online (tny_account_store_get_device (acc_store));
3210                         }
3211                 }
3212
3213                 if (check_online) {
3214                         g_idle_add ((GSourceFunc) save_mime_part_to_file_connect_idle, info);
3215                         return NULL;
3216                 }
3217         }
3218
3219         info->result = gnome_vfs_create (&handle, pair->filename, GNOME_VFS_OPEN_WRITE, FALSE, 0644);
3220         if (info->result == GNOME_VFS_OK) {
3221                 GError *error = NULL;
3222                 gboolean decode_in_provider;
3223                 gssize written;
3224                 ModestAccountMgr *mgr;
3225                 const gchar *account;
3226                 ModestProtocol *protocol = NULL;
3227
3228                 stream = tny_vfs_stream_new (handle);
3229
3230                 decode_in_provider = FALSE;
3231                 mgr = modest_runtime_get_account_mgr ();
3232                 account = modest_window_get_active_account (MODEST_WINDOW (info->window));
3233                 if (modest_account_mgr_account_is_multimailbox (mgr, account, &protocol)) {
3234                         if (MODEST_IS_ACCOUNT_PROTOCOL (protocol)) {
3235                                 decode_in_provider = 
3236                                         modest_account_protocol_decode_part_to_stream (
3237                                                 MODEST_ACCOUNT_PROTOCOL (protocol),
3238                                                 pair->part,
3239                                                 pair->filename,
3240                                                 stream,
3241                                                 &written,
3242                                                 &error);
3243                         }
3244                 }
3245                 if (!decode_in_provider)
3246                         written = tny_mime_part_decode_to_stream (pair->part, stream, &error);
3247
3248                 if (written < 0) {
3249                         g_warning ("modest: could not save attachment %s: %d (%s)\n", pair->filename, error?error->code:-1, error?error->message:"Unknown error");
3250
3251                         if (error && (error->domain == TNY_ERROR_DOMAIN) &&
3252                             (error->code == TNY_IO_ERROR_WRITE) &&
3253                             (errno == ENOSPC)) {
3254                                 info->result = GNOME_VFS_ERROR_NO_SPACE;
3255                         } else {
3256                                 info->result = GNOME_VFS_ERROR_IO;
3257                         }
3258                 }
3259                 g_object_unref (G_OBJECT (stream));
3260         } else {
3261                 g_warning ("Could not create save attachment %s: %s\n", 
3262                            pair->filename, gnome_vfs_result_to_string (info->result));
3263         }
3264
3265         /* Go on saving remaining files */
3266         info->pairs = g_list_remove_link (info->pairs, info->pairs);
3267         if (info->pairs != NULL) {
3268                 save_mime_part_to_file (info);
3269         } else {
3270                 g_idle_add ((GSourceFunc) idle_save_mime_part_show_result, info);
3271         }
3272
3273         return NULL;
3274 }
3275
3276 static void
3277 save_mime_parts_to_file_with_checks (GtkWindow *parent,
3278                                      SaveMimePartInfo *info)
3279 {
3280         gboolean is_ok = TRUE;
3281         gint replaced_files = 0;
3282         const GList *files = info->pairs;
3283         const GList *iter, *to_replace = NULL;
3284
3285         for (iter = files; (iter != NULL) && (replaced_files < 2); iter = g_list_next(iter)) {
3286                 SaveMimePartPair *pair = iter->data;
3287                 gchar *unescaped = g_uri_unescape_string (pair->filename, NULL);
3288
3289                 if (modest_utils_file_exists (unescaped)) {
3290                         replaced_files++;
3291                         if (replaced_files == 1)
3292                                 to_replace = iter;
3293                 }
3294                 g_free (unescaped);
3295         }
3296         if (replaced_files) {
3297                 gint response;
3298
3299                 if (replaced_files == 1) {
3300                         SaveMimePartPair *pair = to_replace->data;
3301                         const gchar *basename = strrchr (pair->filename, G_DIR_SEPARATOR) + 1;
3302                         gchar *escaped_basename, *message;
3303
3304                         escaped_basename = g_uri_unescape_string (basename, NULL);
3305                         message = g_strdup_printf ("%s\n%s",
3306                                                    _FM_REPLACE_FILE,
3307                                                    (escaped_basename) ? escaped_basename : "");
3308                         response = modest_platform_run_confirmation_dialog (parent, message);
3309                         g_free (message);
3310                         g_free (escaped_basename);
3311                 } else {
3312                         response = modest_platform_run_confirmation_dialog (parent,
3313                                                                             _FM_REPLACE_MULTIPLE);
3314                 }
3315                 if (response != GTK_RESPONSE_OK)
3316                         is_ok = FALSE;
3317         }
3318
3319         if (!is_ok) {
3320                 save_mime_part_info_free (info, TRUE);
3321         } else {
3322                 /* Start progress and launch thread */
3323                 set_progress_hint (info->window, TRUE);
3324                 g_thread_create ((GThreadFunc)save_mime_part_to_file, info, FALSE, NULL);
3325         }
3326
3327 }
3328
3329 typedef struct _SaveAttachmentsInfo {
3330         TnyList *attachments_list;
3331         ModestMsgViewWindow *window;
3332 } SaveAttachmentsInfo;
3333
3334 static void
3335 save_attachments_response (GtkDialog *dialog,
3336                            gint       arg1,
3337                            gpointer   user_data)  
3338 {
3339         TnyList *mime_parts;
3340         gchar *chooser_uri;
3341         GList *files_to_save = NULL;
3342         gchar *current_folder;
3343         SaveAttachmentsInfo *sa_info = (SaveAttachmentsInfo *) user_data;
3344
3345         mime_parts = TNY_LIST (sa_info->attachments_list);
3346
3347         if (arg1 != GTK_RESPONSE_OK)
3348                 goto end;
3349
3350         chooser_uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dialog));
3351         current_folder = gtk_file_chooser_get_current_folder_uri (GTK_FILE_CHOOSER (dialog));
3352         if (current_folder && *current_folder != '\0') {
3353                 GError *err = NULL;
3354                 modest_conf_set_string (modest_runtime_get_conf (), MODEST_CONF_LATEST_SAVE_ATTACHMENT_PATH,
3355                                         current_folder,&err);
3356                 if (err != NULL) {
3357                         g_debug ("Error storing latest used folder: %s", err->message);
3358                         g_error_free (err);
3359                 }
3360         }
3361         g_free (current_folder);
3362
3363         if (!modest_utils_folder_writable (chooser_uri)) {
3364                 const gchar *err_msg;
3365
3366 #ifdef MODEST_PLATFORM_MAEMO
3367                 if (modest_maemo_utils_in_usb_mode ()) {
3368                         err_msg = dgettext ("hildon-status-bar-usb", "usbh_ib_mmc_usb_connected");
3369                 } else {
3370                         err_msg = _FM_READ_ONLY_LOCATION;
3371                 }
3372 #else
3373                 err_msg = _FM_READ_ONLY_LOCATION;
3374 #endif
3375                 modest_platform_system_banner (NULL, NULL, err_msg);
3376         } else {
3377                 TnyIterator *iter;
3378
3379                 iter = tny_list_create_iterator (mime_parts);
3380                 while (!tny_iterator_is_done (iter)) {
3381                         TnyMimePart *mime_part = (TnyMimePart *) tny_iterator_get_current (iter);
3382
3383                         if ((modest_tny_mime_part_is_attachment_for_modest (mime_part)) &&
3384                             !tny_mime_part_is_purged (mime_part) &&
3385                             (tny_mime_part_get_filename (mime_part) != NULL)) {
3386                                 SaveMimePartPair *pair;
3387
3388                                 pair = g_slice_new0 (SaveMimePartPair);
3389
3390                                 if (tny_list_get_length (mime_parts) > 1) {
3391                                         gchar *escaped = 
3392                                                 gnome_vfs_escape_slashes (tny_mime_part_get_filename (mime_part));
3393                                         pair->filename = g_build_filename (chooser_uri, escaped, NULL);
3394                                         g_free (escaped);
3395                                 } else {
3396                                         pair->filename = g_strdup (chooser_uri);
3397                                 }
3398                                 pair->part = mime_part;
3399                                 files_to_save = g_list_prepend (files_to_save, pair);
3400                         }
3401                         tny_iterator_next (iter);
3402                 }
3403                 g_object_unref (iter);
3404         }
3405
3406         if (files_to_save != NULL) {
3407                 SaveMimePartInfo *info = g_slice_new0 (SaveMimePartInfo);
3408                 info->pairs = files_to_save;
3409                 info->result = TRUE;
3410                 info->uri = g_strdup (chooser_uri);
3411                 info->window = g_object_ref (sa_info->window);
3412                 save_mime_parts_to_file_with_checks ((GtkWindow *) dialog, info);
3413         }
3414         g_free (chooser_uri);
3415
3416  end:
3417         /* Free and close the dialog */
3418         g_object_unref (mime_parts);
3419         g_object_unref (sa_info->window);
3420         g_slice_free (SaveAttachmentsInfo, sa_info);
3421         gtk_widget_destroy (GTK_WIDGET (dialog));
3422 }
3423
3424 static gboolean
3425 msg_is_attachment (TnyList *mime_parts)
3426 {
3427         TnyIterator *iter;
3428         gboolean retval = FALSE;
3429
3430         if (tny_list_get_length (mime_parts) > 1)
3431                 return FALSE;
3432
3433         iter = tny_list_create_iterator (mime_parts);
3434         if (iter) {
3435                 TnyMimePart *part = TNY_MIME_PART (tny_iterator_get_current (iter));
3436                 if (part) {
3437                         if (TNY_IS_MSG (part))
3438                                 retval = TRUE;
3439                         g_object_unref (part);
3440                 }
3441                 g_object_unref (iter);
3442         }
3443         return retval;
3444 }
3445
3446 void
3447 modest_msg_view_window_save_attachments (ModestMsgViewWindow *window, 
3448                                          TnyList *mime_parts)
3449 {
3450         ModestMsgViewWindowPrivate *priv;
3451         GtkWidget *save_dialog = NULL;
3452         gchar *conf_folder = NULL;
3453         gchar *filename = NULL;
3454         gchar *save_multiple_str = NULL;
3455         const gchar *root_folder = "file:///";
3456
3457         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
3458         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
3459
3460         if (mime_parts == NULL) {
3461                 gboolean allow_msgs = FALSE;
3462
3463                 /* In Hildon 2.2 save and delete operate over all the attachments as there's no
3464                  * selection available */
3465                 mime_parts = modest_msg_view_get_attachments (MODEST_MSG_VIEW (priv->msg_view));
3466
3467                 /* Check if the message is composed by an unique MIME
3468                    part whose content disposition is attachment. There
3469                    could be messages like this:
3470
3471                    Date: Tue, 12 Jan 2010 20:40:59 +0000
3472                    From: <sender@example.org>
3473                    To: <recipient@example.org>
3474                    Subject: no body
3475                    Content-Type: image/jpeg
3476                    Content-Disposition: attachment; filename="bug7718.jpeg"
3477
3478                    whose unique MIME part is the message itself whose
3479                    content disposition is attachment
3480                 */
3481                 if (mime_parts && msg_is_attachment (mime_parts))
3482                         allow_msgs = TRUE;
3483
3484                 if (mime_parts && !modest_toolkit_utils_select_attachments (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (window))), mime_parts, allow_msgs)) {
3485                         g_object_unref (mime_parts);
3486                         return;
3487                 }
3488
3489                 if (mime_parts == NULL || tny_list_get_length (mime_parts) == 0) {
3490                         if (mime_parts) {
3491                                 g_object_unref (mime_parts);
3492                                 mime_parts = NULL;
3493                         }
3494                         return;
3495                 }
3496         } else {
3497                 g_object_ref (mime_parts);
3498         }
3499
3500         /* prepare dialog */
3501         if (tny_list_get_length (mime_parts) == 1) {
3502                 TnyIterator *iter;
3503                 /* only one attachment selected */
3504                 iter = tny_list_create_iterator (mime_parts);
3505                 TnyMimePart *mime_part = (TnyMimePart *) tny_iterator_get_current (iter);
3506                 g_object_unref (iter);
3507                 if (!modest_tny_mime_part_is_msg (mime_part) && 
3508                     modest_tny_mime_part_is_attachment_for_modest (mime_part) &&
3509                     !tny_mime_part_is_purged (mime_part)) {
3510                         filename = g_strdup (tny_mime_part_get_filename (mime_part));
3511                 } else {
3512                         /* TODO: show any error? */
3513                         g_warning ("%s: Tried to save a non-file attachment", __FUNCTION__);
3514                         g_object_unref (mime_parts);
3515                         return;
3516                 }
3517                 g_object_unref (mime_part);
3518         } else {
3519                 gint num = tny_list_get_length (mime_parts);
3520                 save_multiple_str = g_strdup_printf (dngettext("hildon-fm",
3521                                                                "sfil_va_number_of_objects_attachment",
3522                                                               "sfil_va_number_of_objects_attachments",
3523                                                               num), num);
3524         }
3525
3526         /* Creation of hildon file chooser dialog for saving */
3527         save_dialog = modest_toolkit_factory_create_file_chooser_dialog (modest_runtime_get_toolkit_factory (),
3528                                                                          "",
3529                                                                          (GtkWindow *) window,
3530                                                                          GTK_FILE_CHOOSER_ACTION_SAVE);
3531
3532         /* Get last used folder */
3533         conf_folder = modest_conf_get_string (modest_runtime_get_conf (),
3534                                               MODEST_CONF_LATEST_SAVE_ATTACHMENT_PATH, NULL);
3535
3536         /* File chooser stops working if we select "file:///" as current folder */
3537         if (conf_folder && g_ascii_strcasecmp (root_folder, conf_folder) != 0) {
3538                 g_free (conf_folder);
3539                 conf_folder = NULL;
3540         }
3541
3542         if (conf_folder && conf_folder[0] != '\0') {
3543                 gtk_file_chooser_set_current_folder_uri (GTK_FILE_CHOOSER (save_dialog), conf_folder);
3544         } else {
3545                 gchar *docs_folder;
3546                 /* Set the default folder to documents folder */
3547                 docs_folder = (gchar *) g_strdup(g_get_user_special_dir (G_USER_DIRECTORY_DOCUMENTS));
3548                 if (!docs_folder) {
3549                         /* fallback */
3550                         docs_folder = g_build_filename (g_getenv (MYDOCS_ENV), DOCS_FOLDER, NULL);
3551                 }
3552                 gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (save_dialog), docs_folder);
3553                 g_free (docs_folder);
3554         }
3555         g_free (conf_folder);
3556
3557         /* set filename */
3558         if (filename) {
3559                 gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (save_dialog), 
3560                                                    filename);
3561                 g_free (filename);
3562         }
3563
3564         /* if multiple, set multiple string */
3565         if (save_multiple_str) {
3566                 g_object_set (G_OBJECT (save_dialog), "save-multiple", save_multiple_str, NULL);
3567                 gtk_window_set_title (GTK_WINDOW (save_dialog), _FM_SAVE_OBJECT_FILES);
3568                 g_free (save_multiple_str);
3569         }
3570
3571         /* We must run this asynchronously, because the hildon dialog
3572            performs a gtk_dialog_run by itself which leads to gdk
3573            deadlocks */
3574         SaveAttachmentsInfo *sa_info;
3575         sa_info = g_slice_new (SaveAttachmentsInfo);
3576         sa_info->attachments_list = mime_parts;
3577         sa_info->window = g_object_ref (window);
3578         g_signal_connect (save_dialog, "response", 
3579                           G_CALLBACK (save_attachments_response), sa_info);
3580
3581         gtk_widget_show_all (save_dialog);
3582 }
3583
3584 static gboolean
3585 show_remove_attachment_information (gpointer userdata)
3586 {
3587         ModestMsgViewWindow *window = (ModestMsgViewWindow *) userdata;
3588         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
3589
3590         /* We're outside the main lock */
3591         gdk_threads_enter ();
3592
3593         if (priv->remove_attachment_banner != NULL) {
3594                 gtk_widget_destroy (priv->remove_attachment_banner);
3595                 g_object_unref (priv->remove_attachment_banner);
3596         }
3597
3598         priv->remove_attachment_banner = g_object_ref (
3599                 modest_platform_animation_banner (NULL, NULL, _("mcen_me_inbox_remove_attachments")));
3600
3601         gdk_threads_leave ();
3602
3603         return FALSE;
3604 }
3605
3606 void
3607 modest_msg_view_window_remove_attachments (ModestMsgViewWindow *window, gboolean get_all)
3608 {
3609         ModestMsgViewWindowPrivate *priv;
3610         TnyList *mime_parts = NULL, *tmp;
3611         gchar *confirmation_message;
3612         gint response;
3613         gint n_attachments;
3614         TnyMsg *msg;
3615         TnyIterator *iter;
3616
3617         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
3618         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
3619
3620 #ifdef MODEST_TOOLKIT_HILDON2
3621         /* In hildon 2.2 we ignore the get_all flag as we always get all attachments. This is
3622          * because we don't have selection
3623          */
3624         mime_parts = modest_msg_view_get_attachments (MODEST_MSG_VIEW (priv->msg_view));
3625
3626         /* Remove already purged messages from mime parts list. We use
3627            a copy of the list to remove items in the original one */
3628         tmp = tny_list_copy (mime_parts);
3629         iter = tny_list_create_iterator (tmp);
3630         while (!tny_iterator_is_done (iter)) {
3631                 TnyMimePart *part = TNY_MIME_PART (tny_iterator_get_current (iter));
3632                 if (tny_mime_part_is_purged (part))
3633                         tny_list_remove (mime_parts, (GObject *) part);
3634
3635                 g_object_unref (part);
3636                 tny_iterator_next (iter);
3637         }
3638         g_object_unref (tmp);
3639         g_object_unref (iter);
3640
3641         if (!modest_toolkit_utils_select_attachments (GTK_WINDOW (window), mime_parts, TRUE) ||
3642             tny_list_get_length (mime_parts) == 0) {
3643                 g_object_unref (mime_parts);
3644                 return;
3645         }
3646 #else
3647         /* In gtk we get only selected attachments for the operation.
3648          */
3649         mime_parts = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
3650
3651         /* Remove already purged messages from mime parts list. We use
3652            a copy of the list to remove items in the original one */
3653         tmp = tny_list_copy (mime_parts);
3654         iter = tny_list_create_iterator (tmp);
3655         while (!tny_iterator_is_done (iter)) {
3656                 TnyMimePart *part = TNY_MIME_PART (tny_iterator_get_current (iter));
3657                 if (tny_mime_part_is_purged (part))
3658                         tny_list_remove (mime_parts, (GObject *) part);
3659
3660                 g_object_unref (part);
3661                 tny_iterator_next (iter);
3662         }
3663         g_object_unref (tmp);
3664         g_object_unref (iter);
3665
3666         if (tny_list_get_length (mime_parts) == 0) {
3667                 g_object_unref (mime_parts);
3668                 return;
3669         }
3670 #endif
3671
3672         n_attachments = tny_list_get_length (mime_parts);
3673         if (n_attachments == 1) {
3674                 gchar *filename;
3675                 TnyMimePart *part;
3676
3677                 iter = tny_list_create_iterator (mime_parts);
3678                 part = (TnyMimePart *) tny_iterator_get_current (iter);
3679                 g_object_unref (iter);
3680                 if (modest_tny_mime_part_is_msg (part)) {
3681                         TnyHeader *header;
3682                         header = tny_msg_get_header (TNY_MSG (part));
3683                         filename = tny_header_dup_subject (header);
3684                         g_object_unref (header);
3685                         if (filename == NULL)
3686                                 filename = g_strdup (_("mail_va_no_subject"));
3687                 } else {
3688                         filename = g_strdup (tny_mime_part_get_filename (TNY_MIME_PART (part)));
3689                 }
3690                 confirmation_message = g_strdup_printf (_("mcen_nc_purge_file_text"), filename);
3691                 g_free (filename);
3692                 g_object_unref (part);
3693         } else {
3694                 confirmation_message = g_strdup_printf (ngettext("mcen_nc_purge_file_text", 
3695                                                                  "mcen_nc_purge_files_text", 
3696                                                                  n_attachments), n_attachments);
3697         }
3698         response = modest_platform_run_confirmation_dialog (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (window))),
3699                                                             confirmation_message);
3700         g_free (confirmation_message);
3701
3702         if (response != GTK_RESPONSE_OK) {
3703                 g_object_unref (mime_parts);
3704                 return;
3705         }
3706
3707         priv->purge_timeout = g_timeout_add (2000, show_remove_attachment_information, window);
3708
3709         iter = tny_list_create_iterator (mime_parts);
3710         while (!tny_iterator_is_done (iter)) {
3711                 TnyMimePart *part;
3712
3713                 part = (TnyMimePart *) tny_iterator_get_current (iter);
3714                 tny_mime_part_set_purged (TNY_MIME_PART (part));
3715                 g_object_unref (part);
3716                 tny_iterator_next (iter);
3717         }
3718         g_object_unref (iter);
3719
3720         msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
3721         tny_msg_view_clear (TNY_MSG_VIEW (priv->msg_view));
3722         tny_msg_rewrite_cache (msg);
3723         tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
3724         g_object_unref (msg);
3725         update_branding (MODEST_MSG_VIEW_WINDOW (window));
3726
3727         g_object_unref (mime_parts);
3728
3729         if (priv->purge_timeout > 0) {
3730                 g_source_remove (priv->purge_timeout);
3731                 priv->purge_timeout = 0;
3732         }
3733
3734         if (priv->remove_attachment_banner) {
3735                 gtk_widget_destroy (priv->remove_attachment_banner);
3736                 g_object_unref (priv->remove_attachment_banner);
3737                 priv->remove_attachment_banner = NULL;
3738         }
3739 }
3740
3741
3742 static void
3743 update_window_title (ModestMsgViewWindow *window)
3744 {
3745         ModestMsgViewWindowPrivate *priv;
3746         TnyMsg *msg = NULL;
3747         TnyHeader *header = NULL;
3748         gchar *subject = NULL;
3749
3750         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
3751
3752         /* Note that if the window is closed while we're retrieving
3753            the message, this widget could de deleted */
3754         if (!priv->msg_view)
3755                 return;
3756
3757         msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
3758
3759         if (priv->other_body) {
3760                 gchar *description;
3761
3762                 description = modest_tny_mime_part_get_header_value (priv->other_body, "Content-Description");
3763                 if (description) {
3764                         g_strstrip (description);
3765                         subject = description;
3766                 }
3767         } else if (msg != NULL) {
3768                 header = tny_msg_get_header (msg);
3769                 subject = tny_header_dup_subject (header);
3770                 g_object_unref (header);
3771                 g_object_unref (msg);
3772         }
3773
3774         if ((subject == NULL)||(subject[0] == '\0')) {
3775                 g_free (subject);
3776                 subject = g_strdup (_("mail_va_no_subject"));
3777         }
3778
3779         modest_window_set_title (MODEST_WINDOW (window), subject);
3780 }
3781
3782
3783 static void
3784 on_move_focus (GtkWidget *widget,
3785                GtkDirectionType direction,
3786                gpointer userdata)
3787 {
3788         g_signal_stop_emission_by_name (G_OBJECT (widget), "move-focus");
3789 }
3790
3791 static TnyStream *
3792 fetch_image_open_stream (TnyStreamCache *self, gint64 *expected_size, gchar *uri)
3793 {
3794         GnomeVFSResult result;
3795         GnomeVFSHandle *handle = NULL;
3796         GnomeVFSFileInfo *info = NULL;
3797         TnyStream *stream;
3798
3799         result = gnome_vfs_open (&handle, uri, GNOME_VFS_OPEN_READ);
3800         if (result != GNOME_VFS_OK) {
3801                 *expected_size = 0;
3802                 return NULL;
3803         }
3804         
3805         info = gnome_vfs_file_info_new ();
3806         result = gnome_vfs_get_file_info_from_handle (handle, info, GNOME_VFS_FILE_INFO_DEFAULT);
3807         if (result != GNOME_VFS_OK || ! (info->valid_fields & GNOME_VFS_FILE_INFO_FIELDS_SIZE)) {
3808                 /* We put a "safe" default size for going to cache */
3809                 *expected_size = (300*1024);
3810         } else {
3811                 *expected_size = info->size;
3812         }
3813         gnome_vfs_file_info_unref (info);
3814
3815         stream = tny_vfs_stream_new (handle);
3816
3817         return stream;
3818
3819 }
3820
3821 typedef struct {
3822         gchar *uri;
3823         gchar *cache_id;
3824         TnyStream *output_stream;
3825         GtkWidget *msg_view;
3826         GtkWidget *window;
3827 } FetchImageData;
3828
3829 gboolean
3830 on_fetch_image_timeout_refresh_view (gpointer userdata)
3831 {
3832         ModestMsgViewWindowPrivate *priv;
3833
3834         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (userdata);
3835         update_progress_hint (MODEST_MSG_VIEW_WINDOW (userdata));
3836         /* Note that priv->msg_view is set to NULL when this window is
3837            distroyed */
3838         if (priv->msg_view && GTK_WIDGET_DRAWABLE (priv->msg_view)) {
3839                 gtk_widget_queue_draw (GTK_WIDGET (priv->msg_view));
3840         }
3841         priv->fetch_image_redraw_handler = 0;
3842         g_object_unref (userdata);
3843         return FALSE;
3844 }
3845
3846 gboolean
3847 on_fetch_image_idle_refresh_view (gpointer userdata)
3848 {
3849
3850         FetchImageData *fidata = (FetchImageData *) userdata;
3851
3852         gdk_threads_enter ();
3853         if (GTK_WIDGET_DRAWABLE (fidata->msg_view)) {
3854                 ModestMsgViewWindowPrivate *priv;
3855
3856                 priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (fidata->window);
3857                 priv->fetching_images--;
3858                 if (priv->fetch_image_redraw_handler == 0) {
3859                         priv->fetch_image_redraw_handler = g_timeout_add (500, on_fetch_image_timeout_refresh_view, g_object_ref (fidata->window));
3860                 }
3861
3862         }
3863         gdk_threads_leave ();
3864
3865         g_object_unref (fidata->msg_view);
3866         g_object_unref (fidata->window);
3867         g_slice_free (FetchImageData, fidata);
3868         return FALSE;
3869 }
3870
3871 static gpointer
3872 on_fetch_image_thread (gpointer userdata)
3873 {
3874         FetchImageData *fidata = (FetchImageData *) userdata;
3875         TnyStreamCache *cache;
3876         TnyStream *cache_stream;
3877
3878         cache = modest_runtime_get_images_cache ();
3879         cache_stream = 
3880                 tny_stream_cache_get_stream (cache, 
3881                                              fidata->cache_id, 
3882                                              (TnyStreamCacheOpenStreamFetcher) fetch_image_open_stream, 
3883                                              (gpointer) fidata->uri);
3884         g_free (fidata->cache_id);
3885         g_free (fidata->uri);
3886
3887         if (cache_stream != NULL) {
3888                 char buffer[4096];
3889
3890                 while (G_LIKELY (!tny_stream_is_eos (cache_stream))) {
3891                         gssize nb_read;
3892
3893                         nb_read = tny_stream_read (cache_stream, buffer, sizeof (buffer));
3894                         if (G_UNLIKELY (nb_read < 0)) {
3895                                 break;
3896                         } else if (G_LIKELY (nb_read > 0)) {
3897                                 gssize nb_written = 0;
3898
3899                                 while (G_UNLIKELY (nb_written < nb_read)) {
3900                                         gssize len;
3901
3902                                         len = tny_stream_write (fidata->output_stream, buffer + nb_written,
3903                                                                 nb_read - nb_written);
3904                                         if (G_UNLIKELY (len < 0))
3905                                                 break;
3906                                         nb_written += len;
3907                                 }
3908                         }
3909                 }
3910                 tny_stream_close (cache_stream);
3911                 g_object_unref (cache_stream);
3912         }
3913
3914         tny_stream_close (fidata->output_stream);
3915         g_object_unref (fidata->output_stream);
3916
3917         g_idle_add (on_fetch_image_idle_refresh_view, fidata);
3918
3919         return NULL;
3920 }
3921
3922 static gboolean
3923 on_fetch_image (ModestMsgView *msgview,
3924                 const gchar *uri,
3925                 TnyStream *stream,
3926                 ModestMsgViewWindow *window)
3927 {
3928         const gchar *current_account;
3929         ModestMsgViewWindowPrivate *priv;
3930         FetchImageData *fidata;
3931
3932         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
3933
3934         current_account = modest_window_get_active_account (MODEST_WINDOW (window));
3935
3936         fidata = g_slice_new0 (FetchImageData);
3937         fidata->msg_view = g_object_ref (msgview);
3938         fidata->window = g_object_ref (window);
3939         fidata->uri = g_strdup (uri);
3940         fidata->cache_id = modest_images_cache_get_id (current_account, uri);
3941         fidata->output_stream = g_object_ref (stream);
3942
3943         priv->fetching_images++;
3944         if (g_thread_create (on_fetch_image_thread, fidata, FALSE, NULL) == NULL) {
3945                 g_object_unref (fidata->output_stream);
3946                 g_free (fidata->cache_id);
3947                 g_free (fidata->uri);
3948                 g_object_unref (fidata->msg_view);
3949                 g_slice_free (FetchImageData, fidata);
3950                 tny_stream_close (stream);
3951                 priv->fetching_images--;
3952                 update_progress_hint (window);
3953                 return FALSE;
3954         }
3955         update_progress_hint (window);
3956
3957         return TRUE;
3958 }
3959
3960 static void 
3961 setup_menu (ModestMsgViewWindow *self)
3962 {
3963         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW(self));
3964
3965         /* Settings menu buttons */
3966         modest_window_add_to_menu (MODEST_WINDOW (self), _("mcen_me_new_message"), "<Control>n",
3967                                    MODEST_WINDOW_MENU_CALLBACK (modest_ui_actions_on_new_msg),
3968                                    MODEST_DIMMING_CALLBACK (modest_ui_dimming_rules_on_new_msg));
3969
3970         modest_window_add_to_menu (MODEST_WINDOW (self),
3971                                    dngettext(GETTEXT_PACKAGE,
3972                                              "mcen_me_move_message",
3973                                              "mcen_me_move_messages",
3974                                              1),
3975                                    NULL,
3976                                    MODEST_WINDOW_MENU_CALLBACK (modest_ui_actions_on_move_to),
3977                                    MODEST_DIMMING_CALLBACK (modest_ui_dimming_rules_on_move_to));
3978
3979         modest_window_add_to_menu (MODEST_WINDOW (self), _("mcen_me_inbox_mark_as_read"), NULL,
3980                                    MODEST_WINDOW_MENU_CALLBACK (modest_ui_actions_on_mark_as_read),
3981                                    MODEST_DIMMING_CALLBACK (modest_ui_dimming_rules_on_mark_as_read_msg_in_view));
3982         modest_window_add_to_menu (MODEST_WINDOW (self), _("mcen_me_inbox_mark_as_unread"), NULL,
3983                                    MODEST_WINDOW_MENU_CALLBACK (modest_ui_actions_on_mark_as_unread),
3984                                    MODEST_DIMMING_CALLBACK (modest_ui_dimming_rules_on_mark_as_unread_msg_in_view));
3985
3986         modest_window_add_to_menu (MODEST_WINDOW (self), _("mcen_me_viewer_save_attachments"), NULL,
3987                                    MODEST_WINDOW_MENU_CALLBACK (modest_ui_actions_save_attachments),
3988                                    MODEST_DIMMING_CALLBACK (modest_ui_dimming_rules_on_save_attachments));
3989         modest_window_add_to_menu (MODEST_WINDOW (self), _("mcen_me_inbox_remove_attachments"), NULL,
3990                                    MODEST_WINDOW_MENU_CALLBACK (modest_ui_actions_remove_attachments),
3991                                    MODEST_DIMMING_CALLBACK (modest_ui_dimming_rules_on_remove_attachments));
3992
3993         modest_window_add_to_menu (MODEST_WINDOW (self), _("mcen_me_viewer_addtocontacts"), NULL,
3994                                    MODEST_WINDOW_MENU_CALLBACK (modest_ui_actions_add_to_contacts),
3995                                    MODEST_DIMMING_CALLBACK (modest_ui_dimming_rules_on_add_to_contacts));
3996
3997         modest_window_add_to_menu (MODEST_WINDOW (self), _("mcen_ti_message_properties"), NULL,
3998                                    MODEST_WINDOW_MENU_CALLBACK (modest_ui_actions_on_details),
3999                                    MODEST_DIMMING_CALLBACK (modest_ui_dimming_rules_on_details));
4000
4001         modest_window_add_to_menu (MODEST_WINDOW (self), _("mcen_me_viewer_find"), NULL,
4002                                    MODEST_WINDOW_MENU_CALLBACK (modest_msg_view_window_show_isearch_toolbar),
4003                                    MODEST_DIMMING_CALLBACK (modest_ui_dimming_rules_on_find_in_msg));
4004 }
4005
4006 void
4007 modest_msg_view_window_add_to_contacts (ModestMsgViewWindow *self)
4008 {
4009         ModestMsgViewWindowPrivate *priv;
4010         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
4011         GSList *recipients = NULL;
4012         TnyMsg *msg = NULL;
4013
4014         msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
4015         if (msg == NULL) {
4016                 TnyHeader *header;
4017
4018                 header = modest_msg_view_window_get_header (self);
4019                 if (header == NULL)
4020                         return;
4021                 recipients = modest_tny_msg_header_get_all_recipients_list (header);
4022                 g_object_unref (header);
4023         } else {
4024                 recipients = modest_tny_msg_get_all_recipients_list (msg);
4025                 g_object_unref (msg);
4026         }
4027
4028         if (recipients) {
4029                 /* Offer the user to add recipients to the address book */
4030                 modest_address_book_add_address_list_with_selector (recipients, (GtkWindow *) self);
4031                 g_slist_foreach (recipients, (GFunc) g_free, NULL); g_slist_free (recipients);
4032         }
4033 }
4034
4035 static gboolean 
4036 _modest_msg_view_window_map_event (GtkWidget *widget,
4037                                    GdkEvent *event,
4038                                    gpointer userdata)
4039 {
4040         ModestMsgViewWindow *self = (ModestMsgViewWindow *) userdata;
4041
4042         update_progress_hint (self);
4043
4044         return FALSE;
4045 }
4046
4047 void
4048 modest_msg_view_window_fetch_images (ModestMsgViewWindow *self)
4049 {
4050         ModestMsgViewWindowPrivate *priv;
4051         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
4052
4053         modest_msg_view_request_fetch_images (MODEST_MSG_VIEW (priv->msg_view));
4054 }
4055
4056 gboolean 
4057 modest_msg_view_window_has_blocked_external_images (ModestMsgViewWindow *self)
4058 {
4059         ModestMsgViewWindowPrivate *priv;
4060         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
4061
4062         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), FALSE);
4063
4064         return modest_msg_view_has_blocked_external_images (MODEST_MSG_VIEW (priv->msg_view));
4065 }
4066
4067 void 
4068 modest_msg_view_window_reload (ModestMsgViewWindow *self)
4069 {
4070         ModestMsgViewWindowPrivate *priv;
4071         const gchar *msg_uid;
4072         TnyHeader *header = NULL;
4073         TnyFolder *folder = NULL;
4074
4075         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self));
4076
4077         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
4078
4079         header = modest_msg_view_window_get_header (MODEST_MSG_VIEW_WINDOW (self));
4080         if (!header)
4081                 return;
4082
4083         folder = tny_header_get_folder (header);
4084         g_object_unref (header);
4085
4086         if (!folder)
4087                 return;
4088
4089         msg_uid = modest_msg_view_window_get_message_uid (self);
4090         if (msg_uid) {
4091                 GtkTreeRowReference *row_reference;
4092
4093                 if (priv->row_reference && gtk_tree_row_reference_valid (priv->row_reference)) {
4094                         row_reference = priv->row_reference;
4095                 } else {
4096                         row_reference = NULL;
4097                 }
4098                 if (!message_reader (self, priv, NULL, msg_uid, folder, row_reference))
4099                         g_warning ("Shouldn't happen, trying to reload a message failed");
4100         }
4101
4102         g_object_unref (folder);
4103 }
4104
4105 static void
4106 update_branding (ModestMsgViewWindow *self)
4107 {
4108         const gchar *account; 
4109         const gchar *mailbox;
4110         ModestAccountMgr *mgr;
4111         ModestProtocol *protocol = NULL;
4112         gchar *service_name = NULL;
4113         const GdkPixbuf *service_icon = NULL;
4114         ModestMsgViewWindowPrivate *priv;
4115
4116         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
4117
4118         account = modest_window_get_active_account (MODEST_WINDOW (self));
4119         mailbox = modest_window_get_active_mailbox (MODEST_WINDOW (self));
4120
4121         mgr = modest_runtime_get_account_mgr ();
4122
4123         if (modest_account_mgr_account_is_multimailbox (mgr, account, &protocol)) {
4124                 if (MODEST_IS_ACCOUNT_PROTOCOL (protocol)) {
4125                         service_name = modest_account_protocol_get_service_name (MODEST_ACCOUNT_PROTOCOL (protocol),
4126                                                                                  account, mailbox);
4127                         service_icon = modest_account_protocol_get_service_icon (MODEST_ACCOUNT_PROTOCOL (protocol),
4128                                                                                  account, mailbox, MODEST_ICON_SIZE_SMALL);
4129                 }
4130         }
4131
4132         modest_msg_view_set_branding (MODEST_MSG_VIEW (priv->msg_view), service_name, service_icon);
4133         g_free (service_name);
4134 }
4135
4136 static void
4137 sync_flags (ModestMsgViewWindow *self)
4138 {
4139         TnyHeader *header = NULL;
4140
4141         header = modest_msg_view_window_get_header (self);
4142         if (!header) {
4143                 TnyMsg *msg = modest_msg_view_window_get_message (self);
4144                 if (msg) {
4145                         header = tny_msg_get_header (msg);
4146                         g_object_unref (msg);
4147                 }
4148         }
4149
4150         if (header) {
4151                 TnyFolder *folder = tny_header_get_folder (header);
4152
4153                 if (folder) {
4154                         ModestMailOperation *mail_op;
4155
4156                         /* Sync folder, we need this to save the seen flag */
4157                         mail_op = modest_mail_operation_new (NULL);
4158                         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
4159                                                          mail_op);
4160                         modest_mail_operation_sync_folder (mail_op, folder, FALSE, NULL, NULL);
4161                         g_object_unref (mail_op);
4162                         g_object_unref (folder);
4163                 }
4164                 g_object_unref (header);
4165         }
4166 }
4167
4168 #ifdef MODEST_TOOLKIT_HILDON2
4169 static gboolean
4170 on_realize (GtkWidget *widget,
4171             gpointer userdata)
4172 {
4173         GdkDisplay *display;
4174         Atom atom;
4175         unsigned long val = 1;
4176
4177         display = gdk_drawable_get_display (widget->window);
4178         atom = gdk_x11_get_xatom_by_name_for_display (display, "_HILDON_ZOOM_KEY_ATOM");
4179         XChangeProperty (GDK_DISPLAY_XDISPLAY (display),
4180                          GDK_WINDOW_XID (widget->window), atom,
4181                          XA_INTEGER, 32, PropModeReplace,
4182                          (unsigned char *) &val, 1);
4183
4184         return FALSE;
4185 }
4186 #endif
4187
4188 static gboolean
4189 on_handle_calendar (ModestMsgView *msgview, TnyMimePart *calendar_part, GtkContainer *container, ModestMsgViewWindow *self)
4190 {
4191         const gchar *account_name;
4192         ModestProtocolType proto_type;
4193         ModestProtocol *protocol;
4194         gboolean retval = FALSE;
4195
4196         account_name = modest_window_get_active_account (MODEST_WINDOW (self));
4197         
4198         /* Get proto */
4199         proto_type = modest_account_mgr_get_store_protocol (modest_runtime_get_account_mgr (), 
4200                                                             account_name);
4201         protocol = 
4202                 modest_protocol_registry_get_protocol_by_type (modest_runtime_get_protocol_registry (), 
4203                                                                proto_type);
4204
4205         if (MODEST_IS_ACCOUNT_PROTOCOL (protocol)) {
4206                 retval = modest_account_protocol_handle_calendar (MODEST_ACCOUNT_PROTOCOL (protocol), MODEST_WINDOW (self),
4207                                                                   calendar_part, container);
4208         }
4209         return retval;
4210 }