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