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