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