29ba8a37239fd2cd600b5edb2c6473ff10003a31
[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         g_object_ref (priv->header_model);
976
977         if (row_reference) {
978                 priv->row_reference = gtk_tree_row_reference_copy (row_reference);
979                 priv->next_row_reference = gtk_tree_row_reference_copy (row_reference);
980                 select_next_valid_row (priv->header_model, &(priv->next_row_reference), TRUE, priv->is_outbox);
981         } else {
982                 priv->row_reference = NULL;
983                 priv->next_row_reference = NULL;
984         }
985
986         /* Connect signals */
987         priv->row_changed_handler = 
988                 g_signal_connect (GTK_TREE_MODEL(priv->header_model), "row-changed",
989                                   G_CALLBACK(modest_msg_view_window_on_row_changed),
990                                   window);
991         priv->row_deleted_handler = 
992                 g_signal_connect (GTK_TREE_MODEL(priv->header_model), "row-deleted",
993                                   G_CALLBACK(modest_msg_view_window_on_row_deleted),
994                                   window);
995         priv->row_inserted_handler = 
996                 g_signal_connect (GTK_TREE_MODEL(priv->header_model), "row-inserted",
997                                   G_CALLBACK(modest_msg_view_window_on_row_inserted),
998                                   window);
999         priv->rows_reordered_handler = 
1000                 g_signal_connect(GTK_TREE_MODEL(priv->header_model), "rows-reordered",
1001                                  G_CALLBACK(modest_msg_view_window_on_row_reordered),
1002                                  window);
1003
1004         if (header_view != NULL){
1005                 modest_header_view_add_observer(header_view,
1006                                 MODEST_HEADER_VIEW_OBSERVER(window));
1007         }
1008
1009         tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), NULL);
1010
1011         path = gtk_tree_row_reference_get_path (row_reference);
1012         if (gtk_tree_model_get_iter (priv->header_model, &iter, path)) {
1013                 TnyHeader *header;
1014                 gtk_tree_model_get (priv->header_model, &iter, 
1015                                     TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1016                                     &header, -1);
1017                 message_reader (window, priv, header, row_reference);
1018         }
1019         gtk_tree_path_free (path);
1020
1021         /* Check dimming rules */
1022         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (window));
1023         modest_ui_actions_check_menu_dimming_rules (MODEST_WINDOW (window));
1024         modest_window_check_dimming_rules_group (MODEST_WINDOW (window), MODEST_DIMMING_RULES_CLIPBOARD);
1025
1026         return MODEST_WINDOW(window);
1027 }
1028
1029 ModestWindow *
1030 modest_msg_view_window_new_for_search_result (TnyMsg *msg, 
1031                                               const gchar *modest_account_name,
1032                                               const gchar *msg_uid)
1033 {
1034         ModestMsgViewWindow *window = NULL;
1035         ModestMsgViewWindowPrivate *priv = NULL;
1036         ModestWindowMgr *mgr = NULL;
1037
1038         mgr = modest_runtime_get_window_mgr ();
1039         window = MODEST_MSG_VIEW_WINDOW (modest_window_mgr_get_msg_view_window (mgr));
1040         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), NULL);
1041         modest_msg_view_window_construct (window, modest_account_name, msg_uid);
1042
1043         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1044
1045         /* Remember that this is a search result, 
1046          * so we can disable some UI appropriately: */
1047         priv->is_search_result = TRUE;
1048
1049         tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
1050         
1051         update_window_title (window);
1052         /* gtk_widget_show_all (GTK_WIDGET (window));*/
1053         modest_msg_view_window_update_priority (window);
1054
1055         /* Check dimming rules */
1056         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (window));
1057         modest_ui_actions_check_menu_dimming_rules (MODEST_WINDOW (window));
1058         modest_window_check_dimming_rules_group (MODEST_WINDOW (window), MODEST_DIMMING_RULES_CLIPBOARD);
1059
1060         return MODEST_WINDOW(window);
1061 }
1062
1063 ModestWindow *
1064 modest_msg_view_window_new_for_attachment (TnyMsg *msg, 
1065                             const gchar *modest_account_name,
1066                             const gchar *msg_uid)
1067 {
1068         GObject *obj = NULL;
1069         ModestMsgViewWindowPrivate *priv;       
1070         ModestWindowMgr *mgr = NULL;
1071
1072         g_return_val_if_fail (msg, NULL);
1073         mgr = modest_runtime_get_window_mgr ();
1074         obj = G_OBJECT (modest_window_mgr_get_msg_view_window (mgr));
1075         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (obj);
1076         modest_msg_view_window_construct (MODEST_MSG_VIEW_WINDOW (obj), 
1077                 modest_account_name, msg_uid);
1078
1079         tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
1080         update_window_title (MODEST_MSG_VIEW_WINDOW (obj));
1081
1082         /* gtk_widget_show_all (GTK_WIDGET (obj)); */
1083
1084         /* Check dimming rules */
1085         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (obj));
1086         modest_ui_actions_check_menu_dimming_rules (MODEST_WINDOW (obj));
1087         modest_window_check_dimming_rules_group (MODEST_WINDOW (obj), MODEST_DIMMING_RULES_CLIPBOARD);
1088
1089         return MODEST_WINDOW(obj);
1090 }
1091
1092 static void
1093 modest_msg_view_window_on_row_changed (GtkTreeModel *header_model,
1094                                        GtkTreePath *arg1,
1095                                        GtkTreeIter *arg2,
1096                                        ModestMsgViewWindow *window)
1097 {
1098         check_dimming_rules_after_change (window);
1099 }
1100
1101 static void 
1102 modest_msg_view_window_on_row_deleted(GtkTreeModel *header_model,
1103                                       GtkTreePath *arg1,
1104                                       ModestMsgViewWindow *window)
1105 {
1106         check_dimming_rules_after_change (window);
1107 }
1108         /* The window could have dissapeared */
1109
1110 static void
1111 check_dimming_rules_after_change (ModestMsgViewWindow *window)
1112 {
1113         modest_ui_actions_check_menu_dimming_rules (MODEST_WINDOW (window));
1114         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (window));
1115 }
1116
1117
1118 /* On insertions we check if the folder still has the message we are
1119  * showing or do not. If do not, we do nothing. Which means we are still
1120  * not attached to any header folder and thus next/prev buttons are
1121  * still dimmed. Once the message that is shown by msg-view is found, the
1122  * new model of header-view will be attached and the references will be set.
1123  * On each further insertions dimming rules will be checked. However
1124  * this requires extra CPU time at least works.
1125  * (An message might be deleted from TnyFolder and thus will not be
1126  * inserted into the model again for example if it is removed by the
1127  * imap server and the header view is refreshed.)
1128  */
1129 static void 
1130 modest_msg_view_window_on_row_inserted (GtkTreeModel *model,
1131                                         GtkTreePath *tree_path,
1132                                         GtkTreeIter *tree_iter,
1133                                         ModestMsgViewWindow *window)
1134 {
1135         ModestMsgViewWindowPrivate *priv = NULL; 
1136         TnyHeader *header = NULL;
1137
1138         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
1139         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1140
1141         g_assert (model == priv->header_model);
1142         
1143         /* Check if the newly inserted message is the same we are actually
1144          * showing. IF not, we should remain detached from the header model
1145          * and thus prev and next toolbar buttons should remain dimmed. */
1146         gtk_tree_model_get (model, tree_iter, 
1147                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, 
1148                             &header, -1);
1149
1150         if (TNY_IS_HEADER (header)) {
1151                 gchar *uid = NULL;
1152
1153                 uid = modest_tny_folder_get_header_unique_id (header);
1154                 if (!g_str_equal(priv->msg_uid, uid)) {
1155                         check_dimming_rules_after_change (window);
1156                         g_free(uid);
1157                         g_object_unref (G_OBJECT(header));
1158                         return;
1159                 }
1160                 g_free(uid);
1161                 g_object_unref(G_OBJECT(header));
1162         }
1163
1164         if (priv->row_reference) {
1165                 gtk_tree_row_reference_free (priv->row_reference); 
1166         }
1167
1168         /* Setup row_reference for the actual msg. */
1169         priv->row_reference = gtk_tree_row_reference_new (priv->header_model, tree_path);
1170         if (priv->row_reference == NULL) {
1171                 g_warning("No reference for msg header item.");
1172                 return;
1173         }
1174
1175         /* Now set up next_row_reference. */
1176         if (priv->next_row_reference) {
1177                 gtk_tree_row_reference_free (priv->next_row_reference); 
1178         }
1179
1180         priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
1181         select_next_valid_row (priv->header_model,
1182                                &(priv->next_row_reference), FALSE, priv->is_outbox);
1183
1184         /* Connect the remaining callbacks to become able to detect
1185          * changes in header-view. */
1186         priv->row_changed_handler = 
1187                 g_signal_connect (priv->header_model, "row-changed",
1188                                   G_CALLBACK (modest_msg_view_window_on_row_changed),
1189                                   window);
1190         priv->row_deleted_handler = 
1191                 g_signal_connect (priv->header_model, "row-deleted",
1192                                   G_CALLBACK (modest_msg_view_window_on_row_deleted),
1193                                   window);
1194         priv->rows_reordered_handler = 
1195                 g_signal_connect (priv->header_model, "rows-reordered",
1196                                   G_CALLBACK (modest_msg_view_window_on_row_reordered),
1197                                   window);
1198
1199         check_dimming_rules_after_change (window);      
1200 }
1201
1202 static void 
1203 modest_msg_view_window_on_row_reordered (GtkTreeModel *header_model,
1204                                          GtkTreePath *arg1,
1205                                          GtkTreeIter *arg2,
1206                                          gpointer arg3,
1207                                          ModestMsgViewWindow *window)
1208 {
1209         ModestMsgViewWindowPrivate *priv = NULL; 
1210         gboolean already_changed = FALSE;
1211
1212         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(window);
1213
1214         /* If the current row was reordered select the proper next
1215            valid row. The same if the next row reference changes */
1216         if (priv->row_reference && 
1217             gtk_tree_row_reference_valid (priv->row_reference)) {
1218                 GtkTreePath *path;
1219                 path = gtk_tree_row_reference_get_path (priv->row_reference);
1220                 if (gtk_tree_path_compare (path, arg1) == 0) {
1221                         if (priv->next_row_reference) {
1222                                 gtk_tree_row_reference_free (priv->next_row_reference);
1223                         }
1224                         priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
1225                         select_next_valid_row (header_model, &(priv->next_row_reference), FALSE, priv->is_outbox);
1226                         already_changed = TRUE;
1227                 }
1228                 gtk_tree_path_free (path);
1229         }
1230         if (!already_changed &&
1231             priv->next_row_reference &&
1232             gtk_tree_row_reference_valid (priv->next_row_reference)) {
1233                 GtkTreePath *path;
1234                 path = gtk_tree_row_reference_get_path (priv->next_row_reference);
1235                 if (gtk_tree_path_compare (path, arg1) == 0) {
1236                         if (priv->next_row_reference) {
1237                                 gtk_tree_row_reference_free (priv->next_row_reference);
1238                         }
1239                         priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
1240                         select_next_valid_row (header_model, &(priv->next_row_reference), FALSE, priv->is_outbox);
1241                 }
1242                 gtk_tree_path_free (path);
1243         }
1244         check_dimming_rules_after_change (window);
1245 }
1246
1247 /* The modest_msg_view_window_update_model_replaced implements update
1248  * function for ModestHeaderViewObserver. Checks whether the TnyFolder
1249  * actually belongs to the header-view is the same as the TnyFolder of
1250  * the message of msg-view or not. If they are different, there is
1251  * nothing to do. If they are the same, then the model has replaced and
1252  * the reference in msg-view shall be replaced from the old model to
1253  * the new model. In this case the view will be detached from it's
1254  * header folder. From this point the next/prev buttons are dimmed.
1255  */
1256 static void 
1257 modest_msg_view_window_update_model_replaced (ModestHeaderViewObserver *observer,
1258                                               GtkTreeModel *model,
1259                                               const gchar *tny_folder_id)
1260 {
1261         ModestMsgViewWindowPrivate *priv = NULL; 
1262         ModestMsgViewWindow *window = NULL;
1263
1264         g_assert(MODEST_IS_HEADER_VIEW_OBSERVER(observer));
1265         g_assert(MODEST_IS_MSG_VIEW_WINDOW(observer));
1266
1267         window = MODEST_MSG_VIEW_WINDOW(observer);
1268         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(window);
1269
1270         /* If there is an other folder in the header-view then we do
1271          * not care about it's model (msg list). Else if the
1272          * header-view shows the folder the msg shown by us is in, we
1273          * shall replace our model reference and make some check. */
1274         if(model == NULL || tny_folder_id == NULL || 
1275            (priv->header_folder_id && !g_str_equal(tny_folder_id, priv->header_folder_id)))
1276                 return;
1277
1278         /* Model is changed(replaced), so we should forget the old
1279          * one. Because there might be other references and there
1280          * might be some change on the model even if we unreferenced
1281          * it, we need to disconnect our signals here. */
1282         if (priv->header_model) {
1283                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
1284                                                   priv->row_changed_handler))
1285                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
1286                                                     priv->row_changed_handler);
1287                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
1288                                                   priv->row_deleted_handler))
1289                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
1290                                                     priv->row_deleted_handler);
1291                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
1292                                                   priv->row_inserted_handler))
1293                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
1294                                                     priv->row_inserted_handler);
1295                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
1296                                                   priv->rows_reordered_handler))
1297                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
1298                                                     priv->rows_reordered_handler);
1299
1300                 /* Frees */
1301                 if (priv->row_reference)
1302                         gtk_tree_row_reference_free (priv->row_reference);
1303                 if (priv->next_row_reference)
1304                         gtk_tree_row_reference_free (priv->next_row_reference);
1305                 g_object_unref(priv->header_model);
1306
1307                 /* Initialize */
1308                 priv->row_changed_handler = 0;
1309                 priv->row_deleted_handler = 0;
1310                 priv->row_inserted_handler = 0;
1311                 priv->rows_reordered_handler = 0;
1312                 priv->next_row_reference = NULL;
1313                 priv->row_reference = NULL;
1314                 priv->header_model = NULL;
1315         }
1316
1317         priv->header_model = g_object_ref (model);
1318
1319         /* Also we must connect to the new model for row insertions.
1320          * Only for insertions now. We will need other ones only after
1321          * the msg is show by msg-view is added to the new model. */
1322         priv->row_inserted_handler =
1323                 g_signal_connect (priv->header_model, "row-inserted",
1324                                   G_CALLBACK(modest_msg_view_window_on_row_inserted),
1325                                   window);
1326
1327         modest_ui_actions_check_menu_dimming_rules(MODEST_WINDOW(window));
1328         modest_ui_actions_check_toolbar_dimming_rules(MODEST_WINDOW(window));
1329 }
1330
1331 gboolean 
1332 modest_msg_view_window_toolbar_on_transfer_mode     (ModestMsgViewWindow *self)
1333 {
1334         ModestMsgViewWindowPrivate *priv= NULL; 
1335
1336         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), FALSE);
1337         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
1338
1339         return priv->progress_hint;
1340 }
1341
1342 TnyHeader*
1343 modest_msg_view_window_get_header (ModestMsgViewWindow *self)
1344 {
1345         ModestMsgViewWindowPrivate *priv= NULL; 
1346         TnyMsg *msg = NULL;
1347         TnyHeader *header = NULL;
1348         GtkTreePath *path = NULL;
1349         GtkTreeIter iter;
1350
1351         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), NULL);
1352         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
1353
1354         /* If the message was not obtained from a treemodel,
1355          * for instance if it was opened directly by the search UI:
1356          */
1357         if (priv->header_model == NULL || 
1358             priv->row_reference == NULL ||
1359             !gtk_tree_row_reference_valid (priv->row_reference)) {
1360                 msg = modest_msg_view_window_get_message (self);
1361                 if (msg) {
1362                         header = tny_msg_get_header (msg);
1363                         g_object_unref (msg);
1364                 }
1365                 return header;
1366         }
1367
1368         /* Get iter of the currently selected message in the header view: */
1369         path = gtk_tree_row_reference_get_path (priv->row_reference);
1370         g_return_val_if_fail (path != NULL, NULL);
1371         gtk_tree_model_get_iter (priv->header_model, 
1372                                  &iter, 
1373                                  path);
1374
1375         /* Get current message header */
1376         gtk_tree_model_get (priv->header_model, &iter, 
1377                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, 
1378                             &header, -1);
1379
1380         gtk_tree_path_free (path);
1381         return header;
1382 }
1383
1384 TnyMsg*
1385 modest_msg_view_window_get_message (ModestMsgViewWindow *self)
1386 {
1387         ModestMsgViewWindowPrivate *priv;
1388         
1389         g_return_val_if_fail (self, NULL);
1390         
1391         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
1392         
1393         return tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
1394 }
1395
1396 const gchar*
1397 modest_msg_view_window_get_message_uid (ModestMsgViewWindow *self)
1398 {
1399         ModestMsgViewWindowPrivate *priv;
1400
1401         g_return_val_if_fail (self, NULL);
1402         
1403         priv  = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
1404
1405         return (const gchar*) priv->msg_uid;
1406 }
1407
1408 static void 
1409 modest_msg_view_window_toggle_find_toolbar (GtkToggleAction *toggle,
1410                                             gpointer data)
1411 {
1412         ModestMsgViewWindow *window = MODEST_MSG_VIEW_WINDOW (data);
1413         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1414         ModestWindowPrivate *parent_priv = MODEST_WINDOW_GET_PRIVATE (window);
1415         gboolean is_active;
1416         GtkAction *action;
1417
1418         is_active = gtk_toggle_action_get_active (toggle);
1419
1420         if (is_active) {
1421                 gtk_widget_show (priv->find_toolbar);
1422                 hildon_find_toolbar_highlight_entry (HILDON_FIND_TOOLBAR (priv->find_toolbar), TRUE);
1423         } else {
1424                 gtk_widget_hide (priv->find_toolbar);
1425                 modest_msg_view_grab_focus (MODEST_MSG_VIEW (priv->msg_view));
1426         }
1427
1428         /* update the toggle buttons status */
1429         action = gtk_ui_manager_get_action (parent_priv->ui_manager, "/ToolBar/FindInMessage");
1430         if (action)
1431                 modest_utils_toggle_action_set_active_block_notify (GTK_TOGGLE_ACTION (action), is_active);
1432
1433 }
1434
1435 static void
1436 modest_msg_view_window_find_toolbar_close (GtkWidget *widget,
1437                                            ModestMsgViewWindow *obj)
1438 {
1439         GtkToggleAction *toggle;
1440         ModestWindowPrivate *parent_priv;
1441         ModestMsgViewWindowPrivate *priv;
1442
1443         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (obj);
1444         parent_priv = MODEST_WINDOW_GET_PRIVATE (obj);
1445
1446         toggle = GTK_TOGGLE_ACTION (gtk_ui_manager_get_action (parent_priv->ui_manager, "/ToolBar/FindInMessage"));
1447         gtk_toggle_action_set_active (toggle, FALSE);
1448         modest_msg_view_grab_focus (MODEST_MSG_VIEW (priv->msg_view));
1449 }
1450
1451 static void
1452 modest_msg_view_window_find_toolbar_search (GtkWidget *widget,
1453                                            ModestMsgViewWindow *obj)
1454 {
1455         gchar *current_search;
1456         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (obj);
1457
1458         if (modest_mime_part_view_is_empty (MODEST_MIME_PART_VIEW (priv->msg_view))) {
1459                 hildon_banner_show_information (NULL, NULL, _("mail_ib_nothing_to_find"));
1460                 return;
1461         }
1462
1463         g_object_get (G_OBJECT (widget), "prefix", &current_search, NULL);
1464
1465         if ((current_search == NULL) || (strcmp (current_search, "") == 0)) {
1466                 g_free (current_search);
1467                 hildon_banner_show_information (NULL, NULL, dgettext("hildon-common-strings", "ecdg_ib_find_rep_enter_text"));
1468                 return;
1469         }
1470
1471         if ((priv->last_search == NULL) || (strcmp (priv->last_search, current_search) != 0)) {
1472                 gboolean result;
1473                 g_free (priv->last_search);
1474                 priv->last_search = g_strdup (current_search);
1475                 result = modest_isearch_view_search (MODEST_ISEARCH_VIEW (priv->msg_view),
1476                                                      priv->last_search);
1477                 if (!result) {
1478                         hildon_banner_show_information (NULL, NULL, dgettext("hildon-libs", "ckct_ib_find_no_matches"));
1479                         g_free (priv->last_search);
1480                         priv->last_search = NULL;
1481                 } else {
1482                         modest_msg_view_grab_focus (MODEST_MSG_VIEW (priv->msg_view));
1483                         hildon_find_toolbar_highlight_entry (HILDON_FIND_TOOLBAR (priv->find_toolbar), TRUE);
1484                 }
1485         } else {
1486                 if (!modest_isearch_view_search_next (MODEST_ISEARCH_VIEW (priv->msg_view))) {
1487                         hildon_banner_show_information (NULL, NULL, dgettext("hildon-libs", "ckct_ib_find_search_complete"));
1488                         g_free (priv->last_search);
1489                         priv->last_search = NULL;
1490                 } else {
1491                         modest_msg_view_grab_focus (MODEST_MSG_VIEW (priv->msg_view));
1492                         hildon_find_toolbar_highlight_entry (HILDON_FIND_TOOLBAR (priv->find_toolbar), TRUE);
1493                 }
1494         }
1495         
1496         g_free (current_search);
1497                 
1498 }
1499
1500 static void
1501 modest_msg_view_window_set_zoom (ModestWindow *window,
1502                                  gdouble zoom)
1503 {
1504         ModestMsgViewWindowPrivate *priv;
1505         ModestWindowPrivate *parent_priv;
1506      
1507         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
1508
1509         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1510         parent_priv = MODEST_WINDOW_GET_PRIVATE (window);
1511         modest_zoomable_set_zoom (MODEST_ZOOMABLE (priv->msg_view), zoom);
1512
1513 }
1514
1515 static gdouble
1516 modest_msg_view_window_get_zoom (ModestWindow *window)
1517 {
1518         ModestMsgViewWindowPrivate *priv;
1519      
1520         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), 1.0);
1521
1522         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1523         return modest_zoomable_get_zoom (MODEST_ZOOMABLE (priv->msg_view));
1524 }
1525
1526 static gboolean
1527 modest_msg_view_window_zoom_plus (ModestWindow *window)
1528 {
1529         gdouble zoom_level;
1530         ModestMsgViewWindowPrivate *priv;
1531      
1532         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), 1.0);
1533         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1534   
1535         zoom_level =  modest_zoomable_get_zoom (MODEST_ZOOMABLE (priv->msg_view));
1536
1537         if (zoom_level >= 2.0) {
1538                 hildon_banner_show_information (NULL, NULL, dgettext("hildon-common-strings", "ckct_ib_max_zoom_level_reached"));
1539                 return FALSE;
1540         } else if (zoom_level >= 1.5) {
1541                 zoom_level = 2.0;
1542         } else if (zoom_level >= 1.2) {
1543                 zoom_level = 1.5;
1544         } else if (zoom_level >= 1.0) {
1545                 zoom_level = 1.2;
1546         } else if (zoom_level >= 0.8) {
1547                 zoom_level = 1.0;
1548         } else if (zoom_level >= 0.5) {
1549                 zoom_level = 0.8;
1550         } else {
1551                 zoom_level = 0.5;
1552         }
1553
1554         /* set zoom level */
1555         modest_zoomable_set_zoom (MODEST_ZOOMABLE (priv->msg_view), zoom_level);
1556
1557         return TRUE;
1558         
1559 }
1560
1561 static gboolean
1562 modest_msg_view_window_zoom_minus (ModestWindow *window)
1563 {
1564         gdouble zoom_level;
1565         ModestMsgViewWindowPrivate *priv;
1566      
1567         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), 1.0);
1568         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1569   
1570         zoom_level =  modest_zoomable_get_zoom (MODEST_ZOOMABLE (priv->msg_view));
1571
1572         if (zoom_level <= 0.5) {
1573                           hildon_banner_show_information (NULL, NULL, dgettext("hildon-common-strings", "ckct_ib_min_zoom_level_reached"));
1574                 return FALSE;
1575         } else if (zoom_level <= 0.8) {
1576                 zoom_level = 0.5;
1577         } else if (zoom_level <= 1.0) {
1578                 zoom_level = 0.8;
1579         } else if (zoom_level <= 1.2) {
1580                 zoom_level = 1.0;
1581         } else if (zoom_level <= 1.5) {
1582                 zoom_level = 1.2;
1583         } else if (zoom_level <= 2.0) {
1584                 zoom_level = 1.5;
1585         } else {
1586                 zoom_level = 2.0;
1587         }
1588
1589         /* set zoom level */
1590         modest_zoomable_set_zoom (MODEST_ZOOMABLE (priv->msg_view), zoom_level);
1591
1592         return TRUE;
1593         
1594 }
1595
1596 static gboolean
1597 modest_msg_view_window_key_event (GtkWidget *window,
1598                                   GdkEventKey *event,
1599                                   gpointer userdata)
1600 {
1601         GtkWidget *focus;
1602
1603         focus = gtk_window_get_focus (GTK_WINDOW (window));
1604
1605         /* for the find toolbar case */
1606         if (focus && GTK_IS_ENTRY (focus)) {
1607                 if (event->keyval == GDK_BackSpace) {
1608                         GdkEvent *copy;
1609                         copy = gdk_event_copy ((GdkEvent *) event);
1610                         gtk_widget_event (focus, copy);
1611                         gdk_event_free (copy);
1612                         return TRUE;
1613                 } else 
1614                         return FALSE;
1615         }
1616         if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up ||
1617             event->keyval == GDK_Down || event->keyval == GDK_KP_Down ||
1618             event->keyval == GDK_Page_Up || event->keyval == GDK_KP_Page_Up ||
1619             event->keyval == GDK_Page_Down || event->keyval == GDK_KP_Page_Down ||
1620             event->keyval == GDK_Home || event->keyval == GDK_KP_Home ||
1621             event->keyval == GDK_End || event->keyval == GDK_KP_End) {
1622                 /* ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window); */
1623                 /* gboolean return_value; */
1624
1625                 if (event->type == GDK_KEY_PRESS) {
1626                         GtkScrollType scroll_type;
1627                         
1628                         switch (event->keyval) {
1629                         case GDK_Up: 
1630                         case GDK_KP_Up:
1631                                 scroll_type = GTK_SCROLL_STEP_UP; break;
1632                         case GDK_Down: 
1633                         case GDK_KP_Down:
1634                                 scroll_type = GTK_SCROLL_STEP_DOWN; break;
1635                         case GDK_Page_Up:
1636                         case GDK_KP_Page_Up:
1637                                 scroll_type = GTK_SCROLL_PAGE_UP; break;
1638                         case GDK_Page_Down:
1639                         case GDK_KP_Page_Down:
1640                                 scroll_type = GTK_SCROLL_PAGE_DOWN; break;
1641                         case GDK_Home:
1642                         case GDK_KP_Home:
1643                                 scroll_type = GTK_SCROLL_START; break;
1644                         case GDK_End:
1645                         case GDK_KP_End:
1646                                 scroll_type = GTK_SCROLL_END; break;
1647                         default: scroll_type = GTK_SCROLL_NONE;
1648                         }
1649                         
1650                         /* g_signal_emit_by_name (G_OBJECT (priv->main_scroll), "scroll-child",  */
1651                         /*                     scroll_type, FALSE, &return_value); */
1652                         return FALSE;
1653                 } else {
1654                         return FALSE;
1655                 }
1656         } else {
1657                 return FALSE;
1658         }
1659 }
1660
1661 gboolean
1662 modest_msg_view_window_last_message_selected (ModestMsgViewWindow *window)
1663 {
1664         GtkTreePath *path;
1665         ModestMsgViewWindowPrivate *priv;
1666         GtkTreeIter tmp_iter;
1667         gboolean is_last_selected;
1668
1669         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), TRUE);
1670         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1671
1672         /*if no model (so no rows at all), then virtually we are the last*/
1673         if (!priv->header_model || !priv->row_reference)
1674                 return TRUE;
1675
1676         if (!gtk_tree_row_reference_valid (priv->row_reference))
1677                 return TRUE;
1678
1679         path = gtk_tree_row_reference_get_path (priv->row_reference);
1680         if (path == NULL)
1681                 return TRUE;
1682
1683         is_last_selected = TRUE;
1684         while (is_last_selected) {
1685                 TnyHeader *header;
1686                 gtk_tree_path_next (path);
1687                 if (!gtk_tree_model_get_iter (priv->header_model, &tmp_iter, path))
1688                         break;
1689                 gtk_tree_model_get (priv->header_model, &tmp_iter,
1690                                 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1691                                 &header, -1);
1692                 if (header) {
1693                         if (msg_is_visible (header, priv->is_outbox))
1694                                 is_last_selected = FALSE;
1695                         g_object_unref(G_OBJECT(header));
1696                 }
1697         }
1698         gtk_tree_path_free (path);
1699         return is_last_selected;
1700 }
1701
1702 gboolean
1703 modest_msg_view_window_has_headers_model (ModestMsgViewWindow *window)
1704 {
1705         ModestMsgViewWindowPrivate *priv;
1706
1707         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), TRUE);
1708         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1709
1710         return priv->header_model != NULL;
1711 }
1712
1713 gboolean
1714 modest_msg_view_window_is_search_result (ModestMsgViewWindow *window)
1715 {
1716         ModestMsgViewWindowPrivate *priv;
1717
1718         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), TRUE);
1719         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1720
1721         return priv->is_search_result;
1722 }
1723
1724 static gboolean
1725 msg_is_visible (TnyHeader *header, gboolean check_outbox)
1726 {
1727         if ((tny_header_get_flags(header) & TNY_HEADER_FLAG_DELETED))
1728                 return FALSE;
1729         if (!check_outbox) {
1730                 return TRUE;
1731         } else {
1732                 ModestTnySendQueueStatus status;
1733                 status = modest_tny_all_send_queues_get_msg_status (header);
1734                 return ((status != MODEST_TNY_SEND_QUEUE_FAILED) &&
1735                         (status != MODEST_TNY_SEND_QUEUE_SENDING));
1736         }
1737 }
1738
1739 gboolean
1740 modest_msg_view_window_first_message_selected (ModestMsgViewWindow *window)
1741 {
1742         GtkTreePath *path;
1743         ModestMsgViewWindowPrivate *priv;
1744         gboolean is_first_selected;
1745         GtkTreeIter tmp_iter;
1746
1747         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), TRUE);
1748         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1749
1750         /*if no model (so no rows at all), then virtually we are the first*/
1751         if (!priv->header_model || !priv->row_reference)
1752                 return TRUE;
1753
1754         if (!gtk_tree_row_reference_valid (priv->row_reference))
1755                 return TRUE;
1756
1757         path = gtk_tree_row_reference_get_path (priv->row_reference);
1758         if (!path)
1759                 return TRUE;
1760
1761         is_first_selected = TRUE;
1762         while (is_first_selected) {
1763                 TnyHeader *header;
1764                 if(!gtk_tree_path_prev (path))
1765                         break;
1766                 /* Here the 'if' is needless for logic, but let make sure
1767                  * iter is valid for gtk_tree_model_get. */
1768                 if (!gtk_tree_model_get_iter (priv->header_model, &tmp_iter, path))
1769                         break;
1770                 gtk_tree_model_get (priv->header_model, &tmp_iter,
1771                                 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1772                                 &header, -1);
1773                 if (header) {
1774                         if (msg_is_visible (header, priv->is_outbox))
1775                                 is_first_selected = FALSE;
1776                         g_object_unref(G_OBJECT(header));
1777                 }
1778         }
1779         gtk_tree_path_free (path);
1780         return is_first_selected;
1781 }
1782
1783 typedef struct {
1784         TnyHeader *header;
1785         GtkTreeRowReference *row_reference;
1786 } MsgReaderInfo;
1787
1788 static void
1789 message_reader_performer (gboolean canceled, 
1790                           GError *err,
1791                           GtkWindow *parent_window, 
1792                           TnyAccount *account, 
1793                           gpointer user_data)
1794 {
1795         ModestMailOperation *mail_op = NULL;
1796         MsgReaderInfo *info;
1797
1798         info = (MsgReaderInfo *) user_data;
1799         if (canceled || err) {
1800                 goto frees;
1801         }
1802
1803         /* Register the header - it'll be unregistered in the callback */
1804         modest_window_mgr_register_header (modest_runtime_get_window_mgr (), info->header, NULL);
1805
1806         /* New mail operation */
1807         mail_op = modest_mail_operation_new_with_error_handling (G_OBJECT(parent_window),
1808                                                                  modest_ui_actions_disk_operations_error_handler, 
1809                                                                  NULL, NULL);
1810                                 
1811         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (), mail_op);
1812         modest_mail_operation_get_msg (mail_op, info->header, TRUE, view_msg_cb, info->row_reference);
1813         g_object_unref (mail_op);
1814
1815         /* Update dimming rules */
1816         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (parent_window));
1817         modest_ui_actions_check_menu_dimming_rules (MODEST_WINDOW (parent_window));
1818
1819  frees:
1820         /* Frees. The row_reference will be freed by the view_msg_cb callback */
1821         g_object_unref (info->header);
1822         g_slice_free (MsgReaderInfo, info);
1823 }
1824
1825
1826 /**
1827  * Reads the message whose summary item is @header. It takes care of
1828  * several things, among others:
1829  *
1830  * If the message was not previously downloaded then ask the user
1831  * before downloading. If there is no connection launch the connection
1832  * dialog. Update toolbar dimming rules.
1833  *
1834  * Returns: TRUE if the mail operation was started, otherwise if the
1835  * user do not want to download the message, or if the user do not
1836  * want to connect, then the operation is not issued
1837  **/
1838 static gboolean
1839 message_reader (ModestMsgViewWindow *window,
1840                 ModestMsgViewWindowPrivate *priv,
1841                 TnyHeader *header,
1842                 GtkTreeRowReference *row_reference)
1843 {
1844         ModestWindowMgr *mgr;
1845         TnyAccount *account;
1846         TnyFolder *folder;
1847         MsgReaderInfo *info;
1848
1849         g_return_val_if_fail (row_reference != NULL, FALSE);
1850
1851         mgr = modest_runtime_get_window_mgr ();
1852         /* Msg download completed */
1853         if (!(tny_header_get_flags (header) & TNY_HEADER_FLAG_CACHED)) {
1854                 /* Ask the user if he wants to download the message if
1855                    we're not online */
1856                 if (!tny_device_is_online (modest_runtime_get_device())) {
1857                         GtkResponseType response;
1858
1859                         response = modest_platform_run_confirmation_dialog (GTK_WINDOW (window),
1860                                                                             _("mcen_nc_get_msg"));
1861                         if (response == GTK_RESPONSE_CANCEL)
1862                                 return FALSE;
1863                 
1864                         folder = tny_header_get_folder (header);
1865                         info = g_slice_new (MsgReaderInfo);
1866                         info->header = g_object_ref (header);
1867                         info->row_reference = gtk_tree_row_reference_copy (row_reference);
1868
1869                         /* Offer the connection dialog if necessary */
1870                         modest_platform_connect_if_remote_and_perform ((GtkWindow *) window, 
1871                                                                        TRUE,
1872                                                                        TNY_FOLDER_STORE (folder),
1873                                                                        message_reader_performer, 
1874                                                                        info);
1875                         g_object_unref (folder);
1876                         return TRUE;
1877                 }
1878         }
1879         
1880         folder = tny_header_get_folder (header);
1881         account = tny_folder_get_account (folder);
1882         info = g_slice_new (MsgReaderInfo);
1883         info->header = g_object_ref (header);
1884         info->row_reference = gtk_tree_row_reference_copy (row_reference);
1885         
1886         message_reader_performer (FALSE, NULL, (GtkWindow *) window, account, info);
1887         g_object_unref (account);
1888         g_object_unref (folder);
1889
1890         return TRUE;
1891 }
1892
1893 gboolean        
1894 modest_msg_view_window_select_next_message (ModestMsgViewWindow *window)
1895 {
1896         ModestMsgViewWindowPrivate *priv;
1897         GtkTreePath *path= NULL;
1898         GtkTreeIter tmp_iter;
1899         TnyHeader *header;
1900         gboolean retval = TRUE;
1901         GtkTreeRowReference *row_reference = NULL;
1902
1903         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), FALSE);
1904         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1905
1906         if (!priv->row_reference)
1907                 return FALSE;
1908
1909         /* Update the next row reference if it's not valid. This could
1910            happen if for example the header which it was pointing to,
1911            was deleted. The best place to do it is in the row-deleted
1912            handler but the tinymail model do not work like the glib
1913            tree models and reports the deletion when the row is still
1914            there */
1915         if (!gtk_tree_row_reference_valid (priv->next_row_reference)) {
1916                 if (gtk_tree_row_reference_valid (priv->row_reference)) {
1917                         priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
1918                         select_next_valid_row (priv->header_model, &(priv->next_row_reference), FALSE, priv->is_outbox);
1919                 }
1920         }
1921         if (priv->next_row_reference)
1922                 path = gtk_tree_row_reference_get_path (priv->next_row_reference);
1923         if (path == NULL)
1924                 return FALSE;
1925
1926         row_reference = gtk_tree_row_reference_copy (priv->next_row_reference);
1927
1928         gtk_tree_model_get_iter (priv->header_model,
1929                                  &tmp_iter,
1930                                  path);
1931         gtk_tree_path_free (path);
1932
1933         gtk_tree_model_get (priv->header_model, &tmp_iter, 
1934                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1935                             &header, -1);
1936         
1937         /* Read the message & show it */
1938         if (!message_reader (window, priv, header, row_reference)) {
1939                 retval = FALSE;
1940         }
1941         gtk_tree_row_reference_free (row_reference);
1942
1943         /* Free */
1944         g_object_unref (header);
1945
1946         return retval;
1947 }
1948
1949 gboolean        
1950 modest_msg_view_window_select_previous_message (ModestMsgViewWindow *window)
1951 {
1952         ModestMsgViewWindowPrivate *priv = NULL;
1953         GtkTreePath *path;
1954         gboolean finished = FALSE;
1955         gboolean retval = FALSE;
1956
1957         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), FALSE);
1958         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1959
1960         /* Return inmediatly if there is no header model */
1961         if (!priv->header_model || !priv->row_reference)
1962                 return FALSE;
1963
1964         path = gtk_tree_row_reference_get_path (priv->row_reference);
1965         while (!finished && gtk_tree_path_prev (path)) {
1966                 TnyHeader *header;
1967                 GtkTreeIter iter;
1968
1969                 gtk_tree_model_get_iter (priv->header_model, &iter, path);
1970                 gtk_tree_model_get (priv->header_model, &iter, 
1971                                     TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1972                                     &header, -1);
1973                 finished = TRUE;
1974                 if (header) {
1975                         if (msg_is_visible (header, priv->is_outbox)) {
1976                                 GtkTreeRowReference *row_reference;
1977                                 row_reference = gtk_tree_row_reference_new (priv->header_model, path);
1978                                 /* Read the message & show it */
1979                                 retval = message_reader (window, priv, header, row_reference);
1980                                 gtk_tree_row_reference_free (row_reference);
1981                         } else {
1982                                 finished = FALSE;
1983                         }
1984                         g_object_unref (header);
1985                 }
1986         }
1987
1988         gtk_tree_path_free (path);
1989         return retval;
1990 }
1991
1992 static void
1993 view_msg_cb (ModestMailOperation *mail_op, 
1994              TnyHeader *header, 
1995              gboolean canceled,
1996              TnyMsg *msg, 
1997              GError *error,
1998              gpointer user_data)
1999 {
2000         ModestMsgViewWindow *self = NULL;
2001         ModestMsgViewWindowPrivate *priv = NULL;
2002         GtkTreeRowReference *row_reference = NULL;
2003
2004         /* Unregister the header (it was registered before creating the mail operation) */
2005         modest_window_mgr_unregister_header (modest_runtime_get_window_mgr (), header);
2006
2007         row_reference = (GtkTreeRowReference *) user_data;
2008         if (canceled) {
2009                 gtk_tree_row_reference_free (row_reference);
2010                 return;
2011         }
2012         
2013         /* If there was any error */
2014         if (!modest_ui_actions_msg_retrieval_check (mail_op, header, msg)) {
2015                 gtk_tree_row_reference_free (row_reference);                    
2016                 return;
2017         }
2018
2019         /* Get the window */ 
2020         self = (ModestMsgViewWindow *) modest_mail_operation_get_source (mail_op);
2021         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self));
2022         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2023
2024         /* Update the row reference */
2025         if (priv->row_reference != NULL) {
2026                 gtk_tree_row_reference_free (priv->row_reference);
2027                 priv->row_reference = gtk_tree_row_reference_copy (row_reference);
2028                 if (priv->next_row_reference != NULL) {
2029                         gtk_tree_row_reference_free (priv->next_row_reference);
2030                 }
2031                 priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
2032                 select_next_valid_row (priv->header_model, &(priv->next_row_reference), TRUE, priv->is_outbox);
2033         }
2034
2035         /* Mark header as read */
2036         if (!(tny_header_get_flags (header) & TNY_HEADER_FLAG_SEEN))
2037                 tny_header_set_flag (header, TNY_HEADER_FLAG_SEEN);
2038
2039         /* Set new message */
2040         if (priv->msg_view != NULL && TNY_IS_MSG_VIEW (priv->msg_view)) {
2041                 tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
2042                 modest_msg_view_window_update_priority (self);
2043                 update_window_title (MODEST_MSG_VIEW_WINDOW (self));
2044                 modest_msg_view_grab_focus (MODEST_MSG_VIEW (priv->msg_view));
2045         }
2046
2047         /* Set the new message uid of the window  */
2048         if (priv->msg_uid) {
2049                 g_free (priv->msg_uid);
2050                 priv->msg_uid = modest_tny_folder_get_header_unique_id (header);
2051         }
2052
2053         /* Notify the observers */
2054         g_signal_emit (G_OBJECT (self), signals[MSG_CHANGED_SIGNAL], 
2055                        0, priv->header_model, priv->row_reference);
2056
2057         /* Frees */
2058         g_object_unref (self);
2059         gtk_tree_row_reference_free (row_reference);            
2060 }
2061
2062 TnyFolderType
2063 modest_msg_view_window_get_folder_type (ModestMsgViewWindow *window)
2064 {
2065         ModestMsgViewWindowPrivate *priv;
2066         TnyMsg *msg;
2067         TnyFolderType folder_type;
2068
2069         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2070
2071         folder_type = TNY_FOLDER_TYPE_UNKNOWN;
2072
2073         msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
2074         if (msg) {
2075                 TnyFolder *folder;
2076
2077                 folder = tny_msg_get_folder (msg);
2078                 if (folder) {
2079                         folder_type = modest_tny_folder_guess_folder_type (folder);
2080                         g_object_unref (folder);
2081                 }
2082                 g_object_unref (msg);
2083         }
2084
2085         return folder_type;
2086 }
2087
2088
2089 static void
2090 modest_msg_view_window_update_priority (ModestMsgViewWindow *window)
2091 {
2092         ModestMsgViewWindowPrivate *priv;
2093         TnyHeader *header = NULL;
2094         TnyHeaderFlags flags = 0;
2095
2096         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2097
2098         if (priv->header_model && priv->row_reference) {
2099                 GtkTreeIter iter;
2100                 GtkTreePath *path = NULL;
2101
2102                 path = gtk_tree_row_reference_get_path (priv->row_reference);
2103                 g_return_if_fail (path != NULL);
2104                 gtk_tree_model_get_iter (priv->header_model, 
2105                                          &iter, 
2106                                          gtk_tree_row_reference_get_path (priv->row_reference));
2107
2108                 gtk_tree_model_get (priv->header_model, &iter, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
2109                                     &header, -1);
2110                 gtk_tree_path_free (path);
2111         } else {
2112                 TnyMsg *msg;
2113                 msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
2114                 if (msg) {
2115                         header = tny_msg_get_header (msg);
2116                         g_object_unref (msg);
2117                 }
2118         }
2119
2120         if (header) {
2121                 flags = tny_header_get_flags (header);
2122                 g_object_unref(G_OBJECT(header));
2123         }
2124
2125         modest_msg_view_set_priority (MODEST_MSG_VIEW(priv->msg_view), flags);
2126
2127 }
2128
2129 static void
2130 toolbar_resize (ModestMsgViewWindow *self)
2131 {
2132         ModestMsgViewWindowPrivate *priv = NULL;
2133         ModestWindowPrivate *parent_priv = NULL;
2134         GtkWidget *widget;
2135         gint static_button_size;
2136         ModestWindowMgr *mgr;
2137
2138         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self));
2139         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2140         parent_priv = MODEST_WINDOW_GET_PRIVATE(self);
2141
2142         mgr = modest_runtime_get_window_mgr ();
2143         static_button_size = modest_window_mgr_get_fullscreen_mode (mgr)?118:108;
2144
2145         if (parent_priv->toolbar) {
2146                 /* left size buttons */
2147                 widget = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageReply");
2148                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (widget), FALSE);
2149                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (widget), FALSE);
2150                 gtk_widget_set_size_request (GTK_WIDGET (widget), static_button_size, -1);
2151                 widget = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageMoveTo");
2152                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (widget), FALSE);
2153                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (widget), FALSE);
2154                 gtk_widget_set_size_request (GTK_WIDGET (widget), static_button_size, -1);
2155                 widget = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarDeleteMessage");
2156                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (widget), FALSE);
2157                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (widget), FALSE);
2158                 gtk_widget_set_size_request (GTK_WIDGET (widget), static_button_size, -1);
2159                 widget = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/FindInMessage");
2160                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (widget), FALSE);
2161                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (widget), FALSE);
2162                 gtk_widget_set_size_request (GTK_WIDGET (widget), static_button_size, -1);
2163                 
2164                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (priv->next_toolitem), TRUE);
2165                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->next_toolitem), TRUE);
2166                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (priv->prev_toolitem), TRUE);
2167                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->prev_toolitem), TRUE);
2168         }
2169                 
2170 }
2171
2172 static void
2173 modest_msg_view_window_show_toolbar (ModestWindow *self,
2174                                      gboolean show_toolbar)
2175 {
2176         ModestMsgViewWindowPrivate *priv = NULL;
2177         ModestWindowPrivate *parent_priv;
2178         GtkWidget *reply_button = NULL, *menu = NULL;
2179
2180         parent_priv = MODEST_WINDOW_GET_PRIVATE(self);
2181         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2182
2183         /* Set optimized view status */
2184         priv->optimized_view = !show_toolbar;
2185
2186         if (!parent_priv->toolbar) {
2187                 parent_priv->toolbar = gtk_ui_manager_get_widget (parent_priv->ui_manager, 
2188                                                                   "/ToolBar");
2189                 gtk_widget_set_no_show_all (parent_priv->toolbar, TRUE);
2190
2191                 priv->next_toolitem = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageNext");
2192                 priv->prev_toolitem = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageBack");
2193                 toolbar_resize (MODEST_MSG_VIEW_WINDOW (self));
2194
2195                 /* Add to window */
2196                 hildon_window_add_toolbar (HILDON_WINDOW (self), 
2197                                            GTK_TOOLBAR (parent_priv->toolbar));
2198
2199                 /* Set reply button tap and hold menu */
2200                 reply_button = gtk_ui_manager_get_widget (parent_priv->ui_manager, 
2201                                                           "/ToolBar/ToolbarMessageReply");
2202                 menu = gtk_ui_manager_get_widget (parent_priv->ui_manager, 
2203                                                   "/ToolbarReplyCSM");
2204                 if (menu && reply_button)
2205                         gtk_widget_tap_and_hold_setup (GTK_WIDGET (reply_button), menu, NULL, 0);
2206         }
2207
2208         if (show_toolbar) {
2209                 /* Quick hack: this prevents toolbar icons "dance" when progress bar show status is changed */ 
2210                 /* TODO: resize mode migth be GTK_RESIZE_QUEUE, in order to avoid unneccesary shows */
2211                 gtk_container_set_resize_mode (GTK_CONTAINER(parent_priv->toolbar), GTK_RESIZE_IMMEDIATE);
2212
2213                 gtk_widget_show (GTK_WIDGET (parent_priv->toolbar));
2214                 if (modest_msg_view_window_transfer_mode_enabled (MODEST_MSG_VIEW_WINDOW (self))) 
2215                         set_progress_hint (MODEST_MSG_VIEW_WINDOW (self), TRUE);
2216                 else
2217                         set_progress_hint (MODEST_MSG_VIEW_WINDOW (self), FALSE);
2218
2219         } else {
2220                 gtk_widget_set_no_show_all (parent_priv->toolbar, TRUE);
2221                 gtk_widget_hide (GTK_WIDGET (parent_priv->toolbar));
2222         }
2223 }
2224
2225 static void 
2226 modest_msg_view_window_clipboard_owner_change (GtkClipboard *clipboard,
2227                                                GdkEvent *event,
2228                                                ModestMsgViewWindow *window)
2229 {
2230         if (!GTK_WIDGET_VISIBLE (window))
2231                 return;
2232
2233         modest_window_check_dimming_rules_group (MODEST_WINDOW (window), MODEST_DIMMING_RULES_CLIPBOARD);
2234 }
2235
2236 gboolean 
2237 modest_msg_view_window_transfer_mode_enabled (ModestMsgViewWindow *self)
2238 {
2239         ModestMsgViewWindowPrivate *priv;
2240         
2241         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), FALSE); 
2242         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2243
2244         return priv->progress_hint;
2245 }
2246
2247 static gboolean
2248 observers_empty (ModestMsgViewWindow *self)
2249 {
2250         GSList *tmp = NULL;
2251         ModestMsgViewWindowPrivate *priv;
2252         gboolean is_empty = TRUE;
2253         guint pending_ops = 0;
2254  
2255         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2256         tmp = priv->progress_widgets;
2257
2258         /* Check all observers */
2259         while (tmp && is_empty)  {
2260                 pending_ops = modest_progress_object_num_pending_operations (MODEST_PROGRESS_OBJECT(tmp->data));
2261                 is_empty = pending_ops == 0;
2262                 
2263                 tmp = g_slist_next(tmp);
2264         }
2265         
2266         return is_empty;
2267 }
2268
2269 static void
2270 on_account_removed (TnyAccountStore *account_store, 
2271                     TnyAccount *account,
2272                     gpointer user_data)
2273 {
2274         /* Do nothing if it's a transport account, because we only
2275            show the messages of a store account */
2276         if (tny_account_get_account_type(account) == TNY_ACCOUNT_TYPE_STORE) {
2277                 const gchar *parent_acc = NULL;
2278                 const gchar *our_acc = NULL;
2279
2280                 our_acc = modest_window_get_active_account (MODEST_WINDOW (user_data));
2281                 parent_acc = modest_tny_account_get_parent_modest_account_name_for_server_account (account);
2282
2283                 /* Close this window if I'm showing a message of the removed account */
2284                 if (strcmp (parent_acc, our_acc) == 0)
2285                         modest_ui_actions_on_close_window (NULL, MODEST_WINDOW (user_data));
2286         }
2287 }
2288
2289 static void 
2290 on_mail_operation_started (ModestMailOperation *mail_op,
2291                            gpointer user_data)
2292 {
2293         ModestMsgViewWindow *self;
2294         ModestMailOperationTypeOperation op_type;
2295         GSList *tmp;
2296         ModestMsgViewWindowPrivate *priv;
2297         GObject *source = NULL;
2298
2299         self = MODEST_MSG_VIEW_WINDOW (user_data);
2300         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2301         op_type = modest_mail_operation_get_type_operation (mail_op);
2302         tmp = priv->progress_widgets;
2303         source = modest_mail_operation_get_source(mail_op);
2304         if (G_OBJECT (self) == source) {
2305                 if (op_type == MODEST_MAIL_OPERATION_TYPE_RECEIVE ) {
2306                         set_toolbar_transfer_mode(self);
2307                         while (tmp) {
2308                                 modest_progress_object_add_operation (
2309                                                 MODEST_PROGRESS_OBJECT (tmp->data),
2310                                                 mail_op);
2311                                 tmp = g_slist_next (tmp);
2312                         }
2313                 }
2314         }
2315         g_object_unref (source);
2316 }
2317
2318 static void 
2319 on_mail_operation_finished (ModestMailOperation *mail_op,
2320                             gpointer user_data)
2321 {
2322         ModestMsgViewWindow *self;
2323         ModestMailOperationTypeOperation op_type;
2324         GSList *tmp;
2325         ModestMsgViewWindowPrivate *priv;
2326         
2327         self = MODEST_MSG_VIEW_WINDOW (user_data);
2328         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2329         op_type = modest_mail_operation_get_type_operation (mail_op);
2330         tmp = priv->progress_widgets;
2331         
2332         if (op_type == MODEST_MAIL_OPERATION_TYPE_RECEIVE ) {
2333                 while (tmp) {
2334                         modest_progress_object_remove_operation (MODEST_PROGRESS_OBJECT (tmp->data),
2335                                                                  mail_op);
2336                         tmp = g_slist_next (tmp);
2337                 }
2338
2339                 /* If no more operations are being observed, NORMAL mode is enabled again */
2340                 if (observers_empty (self)) {
2341                         set_progress_hint (self, FALSE);
2342                 }
2343
2344                 /* Update dimming rules. We have to do this right here
2345                    and not in view_msg_cb because at that point the
2346                    transfer mode is still enabled so the dimming rule
2347                    won't let the user delete the message that has been
2348                    readed for example */
2349                 modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (self));
2350                 modest_ui_actions_check_menu_dimming_rules (MODEST_WINDOW (self));
2351         }
2352 }
2353
2354 static void
2355 on_queue_changed (ModestMailOperationQueue *queue,
2356                   ModestMailOperation *mail_op,
2357                   ModestMailOperationQueueNotification type,
2358                   ModestMsgViewWindow *self)
2359 {       
2360         ModestMsgViewWindowPrivate *priv;
2361
2362         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2363
2364         /* If this operations was created by another window, do nothing */
2365         if (!modest_mail_operation_is_mine (mail_op, G_OBJECT(self))) 
2366             return;
2367
2368         if (type == MODEST_MAIL_OPERATION_QUEUE_OPERATION_ADDED) {
2369                 priv->sighandlers = modest_signal_mgr_connect (priv->sighandlers,
2370                                                                G_OBJECT (mail_op),
2371                                                                "operation-started",
2372                                                                G_CALLBACK (on_mail_operation_started),
2373                                                                self);
2374                 priv->sighandlers = modest_signal_mgr_connect (priv->sighandlers,
2375                                                                G_OBJECT (mail_op),
2376                                                                "operation-finished",
2377                                                                G_CALLBACK (on_mail_operation_finished),
2378                                                                self);
2379         } else if (type == MODEST_MAIL_OPERATION_QUEUE_OPERATION_REMOVED) {
2380                 priv->sighandlers = modest_signal_mgr_disconnect (priv->sighandlers,
2381                                                                   G_OBJECT (mail_op),
2382                                                                   "operation-started");
2383                 priv->sighandlers = modest_signal_mgr_disconnect (priv->sighandlers,
2384                                                                   G_OBJECT (mail_op),
2385                                                                   "operation-finished");
2386         }
2387 }
2388
2389 TnyList *
2390 modest_msg_view_window_get_attachments (ModestMsgViewWindow *win) 
2391 {
2392         ModestMsgViewWindowPrivate *priv;
2393         TnyList *selected_attachments = NULL;
2394         
2395         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (win), NULL);
2396         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (win);
2397
2398         selected_attachments = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2399         
2400         return selected_attachments;
2401 }
2402
2403 typedef struct {
2404         gchar *filepath;
2405         GtkWidget *banner;
2406         guint banner_idle_id;
2407 } DecodeAsyncHelper;
2408
2409 static gboolean
2410 decode_async_banner_idle (gpointer user_data)
2411 {
2412         DecodeAsyncHelper *helper = (DecodeAsyncHelper *) user_data;
2413
2414         helper->banner_idle_id = 0;
2415         helper->banner = hildon_banner_show_animation (NULL, NULL, _("mail_me_opening"));
2416
2417         return FALSE;
2418 }
2419
2420 static void
2421 on_decode_to_stream_async_handler (TnyMimePart *mime_part, 
2422                                    gboolean cancelled, 
2423                                    TnyStream *stream, 
2424                                    GError *err, 
2425                                    gpointer user_data)
2426 {
2427         DecodeAsyncHelper *helper = (DecodeAsyncHelper *) user_data;
2428
2429         if (helper->banner_idle_id > 0) {
2430                 g_source_remove (helper->banner_idle_id);
2431                 helper->banner_idle_id = 0;
2432         }
2433         if (helper->banner) {
2434                 gtk_widget_destroy (helper->banner);
2435                 helper->banner = NULL;
2436         }
2437         if (cancelled || err) {
2438                 modest_platform_information_banner (NULL, NULL, 
2439                                                     _("mail_ib_file_operation_failed"));
2440                 goto free;
2441         }
2442
2443         /* make the file read-only */
2444         g_chmod(helper->filepath, 0444);
2445
2446         /* Activate the file */
2447         modest_platform_activate_file (helper->filepath, tny_mime_part_get_content_type (mime_part));
2448
2449  free:
2450         /* Frees */
2451         g_free (helper->filepath);
2452         g_slice_free (DecodeAsyncHelper, helper);
2453 }
2454
2455 void
2456 modest_msg_view_window_view_attachment (ModestMsgViewWindow *window, 
2457                                         TnyMimePart *mime_part)
2458 {
2459         ModestMsgViewWindowPrivate *priv;
2460         const gchar *msg_uid;
2461         gchar *attachment_uid = NULL;
2462         gint attachment_index = 0;
2463         TnyList *attachments;
2464
2465         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
2466         g_return_if_fail (TNY_IS_MIME_PART (mime_part) || (mime_part == NULL));
2467         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2468
2469         msg_uid = modest_msg_view_window_get_message_uid (MODEST_MSG_VIEW_WINDOW (window));
2470         attachments = modest_msg_view_get_attachments (MODEST_MSG_VIEW (priv->msg_view));
2471         attachment_index = modest_list_index (attachments, (GObject *) mime_part);
2472         g_object_unref (attachments);
2473         
2474         if (msg_uid && attachment_index >= 0) {
2475                 attachment_uid = g_strdup_printf ("%s/%d", msg_uid, attachment_index);
2476         }
2477
2478         if (mime_part == NULL) {
2479                 gboolean error = FALSE;
2480                 TnyList *selected_attachments = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2481                 if (selected_attachments == NULL || tny_list_get_length (selected_attachments) == 0) {
2482                         error = TRUE;
2483                 } else if (tny_list_get_length (selected_attachments) > 1) {
2484                         hildon_banner_show_information (NULL, NULL, _("mcen_ib_unable_to_display_more"));
2485                         error = TRUE;
2486                 } else {
2487                         TnyIterator *iter;
2488                         iter = tny_list_create_iterator (selected_attachments);
2489                         mime_part = (TnyMimePart *) tny_iterator_get_current (iter);
2490                         g_object_unref (iter);
2491                 }
2492                 g_object_unref (selected_attachments);
2493
2494                 if (error)
2495                         return;
2496         } else {
2497                 g_object_ref (mime_part);
2498         }
2499
2500         if (tny_mime_part_is_purged (mime_part)) {
2501                 g_object_unref (mime_part);
2502                 return;
2503         }
2504
2505         if (!modest_tny_mime_part_is_msg (mime_part)) {
2506                 gchar *filepath = NULL;
2507                 const gchar *att_filename = tny_mime_part_get_filename (mime_part);
2508                 gboolean show_error_banner = FALSE;
2509                 TnyFsStream *temp_stream = NULL;
2510                 temp_stream = modest_utils_create_temp_stream (att_filename, attachment_uid,
2511                                                                &filepath);
2512                 
2513                 if (temp_stream != NULL) {
2514                         DecodeAsyncHelper *helper = g_slice_new (DecodeAsyncHelper);
2515                         helper->filepath = g_strdup (filepath);
2516                         helper->banner = NULL;
2517                         helper->banner_idle_id = g_timeout_add (1000, decode_async_banner_idle, helper);
2518                         tny_mime_part_decode_to_stream_async (mime_part, TNY_STREAM (temp_stream), 
2519                                                               on_decode_to_stream_async_handler, 
2520                                                               NULL, 
2521                                                               helper);
2522                         g_object_unref (temp_stream);
2523                         /* NOTE: files in the temporary area will be automatically
2524                          * cleaned after some time if they are no longer in use */
2525                 } else {
2526                         if (filepath) {
2527                                 const gchar *content_type;
2528                                 /* the file may already exist but it isn't writable,
2529                                  * let's try to open it anyway */
2530                                 content_type = tny_mime_part_get_content_type (mime_part);
2531                                 modest_platform_activate_file (filepath, content_type);
2532                         } else {
2533                                 g_warning ("%s: modest_utils_create_temp_stream failed", __FUNCTION__);
2534                                 show_error_banner = TRUE;
2535                         }
2536                 }
2537                 if (filepath)
2538                         g_free (filepath);
2539                 if (show_error_banner)
2540                         modest_platform_information_banner (NULL, NULL, _("mail_ib_file_operation_failed"));
2541         } else {
2542                 /* message attachment */
2543                 TnyHeader *header = NULL;
2544                 ModestWindowMgr *mgr;
2545                 ModestWindow *msg_win = NULL;
2546                 gboolean found;
2547
2548                 header = tny_msg_get_header (TNY_MSG (mime_part));
2549                 mgr = modest_runtime_get_window_mgr ();
2550                 found = modest_window_mgr_find_registered_header (mgr, header, &msg_win);
2551
2552                 if (found) {
2553                         /* if it's found, but there is no msg_win, it's probably in the process of being created;
2554                          * thus, we don't do anything */
2555                         g_warning ("window for is already being created");
2556                 } else {
2557                         /* it's not found, so create a new window for it */
2558                         modest_window_mgr_register_header (mgr, header, attachment_uid); /* register the uid before building the window */
2559                         gchar *account = g_strdup (modest_window_get_active_account (MODEST_WINDOW (window)));
2560                         if (!account)
2561                                 account = modest_account_mgr_get_default_account (modest_runtime_get_account_mgr ());
2562                         msg_win = modest_msg_view_window_new_for_attachment (TNY_MSG (mime_part), account, attachment_uid);
2563                         modest_window_set_zoom (MODEST_WINDOW (msg_win),
2564                                                 modest_window_get_zoom (MODEST_WINDOW (window)));
2565                         modest_window_mgr_register_window (mgr, msg_win, MODEST_WINDOW (window));
2566                         gtk_widget_show_all (GTK_WIDGET (msg_win));
2567                 }
2568         }
2569         g_object_unref (mime_part);
2570 }
2571
2572 typedef struct
2573 {
2574         gchar *filename;
2575         TnyMimePart *part;
2576 } SaveMimePartPair;
2577
2578 typedef struct
2579 {
2580         GList *pairs;
2581         GtkWidget *banner;
2582         GnomeVFSResult result;
2583 } SaveMimePartInfo;
2584
2585 static void save_mime_part_info_free (SaveMimePartInfo *info, gboolean with_struct);
2586 static gboolean idle_save_mime_part_show_result (SaveMimePartInfo *info);
2587 static gpointer save_mime_part_to_file (SaveMimePartInfo *info);
2588 static void save_mime_parts_to_file_with_checks (SaveMimePartInfo *info);
2589
2590 static void 
2591 save_mime_part_info_free (SaveMimePartInfo *info, gboolean with_struct)
2592 {
2593         
2594         GList *node;
2595         for (node = info->pairs; node != NULL; node = g_list_next (node)) {
2596                 SaveMimePartPair *pair = (SaveMimePartPair *) node->data;
2597                 g_free (pair->filename);
2598                 g_object_unref (pair->part);
2599                 g_slice_free (SaveMimePartPair, pair);
2600         }
2601         g_list_free (info->pairs);
2602         info->pairs = NULL;
2603         if (with_struct) {
2604                 gtk_widget_destroy (info->banner);
2605                 g_slice_free (SaveMimePartInfo, info);
2606         }
2607 }
2608
2609 static gboolean
2610 idle_save_mime_part_show_result (SaveMimePartInfo *info)
2611 {
2612         if (info->pairs != NULL) {
2613                 save_mime_part_to_file (info);
2614         } else {
2615                 /* This is a GDK lock because we are an idle callback and
2616                  * hildon_banner_show_information is or does Gtk+ code */
2617
2618                 gdk_threads_enter (); /* CHECKED */
2619                 save_mime_part_info_free (info, TRUE);
2620                 if (info->result == GNOME_VFS_OK) {
2621                         hildon_banner_show_information (NULL, NULL, _CS("sfil_ib_saved"));
2622                 } else if (info->result == GNOME_VFS_ERROR_NO_SPACE) {
2623                         hildon_banner_show_information (NULL, NULL, dgettext("ke-recv", 
2624                                                                              "cerm_device_memory_full"));
2625                 } else {
2626                         hildon_banner_show_information (NULL, NULL, _("mail_ib_file_operation_failed"));
2627                 }
2628                 gdk_threads_leave (); /* CHECKED */
2629         }
2630
2631         return FALSE;
2632 }
2633
2634 static gpointer
2635 save_mime_part_to_file (SaveMimePartInfo *info)
2636 {
2637         GnomeVFSHandle *handle;
2638         TnyStream *stream;
2639         SaveMimePartPair *pair = (SaveMimePartPair *) info->pairs->data;
2640
2641         info->result = gnome_vfs_create (&handle, pair->filename, GNOME_VFS_OPEN_WRITE, FALSE, 0644);
2642         if (info->result == GNOME_VFS_OK) {
2643                 GError *error = NULL;
2644                 stream = tny_vfs_stream_new (handle);
2645                 if (tny_mime_part_decode_to_stream (pair->part, stream, &error) < 0) {
2646                         g_warning ("modest: could not save attachment %s: %d (%s)\n", pair->filename, error?error->code:-1, error?error->message:"Unknown error");
2647                         
2648                         info->result = GNOME_VFS_ERROR_IO;
2649                 }
2650                 g_object_unref (G_OBJECT (stream));
2651                 g_object_unref (pair->part);
2652                 g_slice_free (SaveMimePartPair, pair);
2653                 info->pairs = g_list_delete_link (info->pairs, info->pairs);
2654         } else {
2655                 g_warning ("modest: could not create save attachment %s: %s\n", pair->filename, gnome_vfs_result_to_string (info->result));
2656                 save_mime_part_info_free (info, FALSE);
2657         }
2658
2659         g_idle_add ((GSourceFunc) idle_save_mime_part_show_result, info);
2660         return NULL;
2661 }
2662
2663 static void
2664 save_mime_parts_to_file_with_checks (SaveMimePartInfo *info)
2665 {
2666         gboolean is_ok = TRUE;
2667         gint replaced_files = 0;
2668         const GList *files = info->pairs;
2669         const GList *iter;
2670
2671         for (iter = files; (iter != NULL) && (replaced_files < 2); iter = g_list_next(iter)) {
2672                 SaveMimePartPair *pair = iter->data;
2673                 if (modest_utils_file_exists (pair->filename)) {
2674                         replaced_files++;
2675                 }
2676         }
2677         if (replaced_files) {
2678                 GtkWidget *confirm_overwrite_dialog;
2679                 const gchar *message = (replaced_files == 1) ?
2680                         _FM("docm_nc_replace_file") : _FM("docm_nc_replace_multiple");
2681                 confirm_overwrite_dialog = hildon_note_new_confirmation (NULL, message);
2682                 if (gtk_dialog_run (GTK_DIALOG (confirm_overwrite_dialog)) != GTK_RESPONSE_OK) {
2683                         is_ok = FALSE;
2684                 }
2685                 gtk_widget_destroy (confirm_overwrite_dialog);
2686         }
2687
2688         if (!is_ok) {
2689                 save_mime_part_info_free (info, TRUE);
2690         } else {
2691                 GtkWidget *banner = hildon_banner_show_animation (NULL, NULL, 
2692                                                                   _CS("sfil_ib_saving"));
2693                 info->banner = banner;
2694                 g_thread_create ((GThreadFunc)save_mime_part_to_file, info, FALSE, NULL);
2695         }
2696
2697 }
2698
2699 static void
2700 save_attachments_response (GtkDialog *dialog,
2701                            gint       arg1,
2702                            gpointer   user_data)  
2703 {
2704         TnyList *mime_parts;
2705         gchar *chooser_uri;
2706         GList *files_to_save = NULL;
2707
2708         mime_parts = TNY_LIST (user_data);
2709
2710         if (arg1 != GTK_RESPONSE_OK)
2711                 goto end;
2712
2713         chooser_uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dialog));
2714
2715         if (!modest_utils_folder_writable (chooser_uri)) {
2716                 hildon_banner_show_information 
2717                         (NULL, NULL, dgettext("hildon-fm", "sfil_ib_readonly_location"));
2718         } else {
2719                 TnyIterator *iter;
2720
2721                 iter = tny_list_create_iterator (mime_parts);
2722                 while (!tny_iterator_is_done (iter)) {
2723                         TnyMimePart *mime_part = (TnyMimePart *) tny_iterator_get_current (iter);
2724
2725                         if ((modest_tny_mime_part_is_attachment_for_modest (mime_part)) &&
2726                             !tny_mime_part_is_purged (mime_part) &&
2727                             (tny_mime_part_get_filename (mime_part) != NULL)) {
2728                                 SaveMimePartPair *pair;
2729                                         
2730                                 pair = g_slice_new0 (SaveMimePartPair);
2731
2732                                 if (tny_list_get_length (mime_parts) > 1) {
2733                                         gchar *escaped = 
2734                                                 gnome_vfs_escape_slashes (tny_mime_part_get_filename (mime_part));
2735                                         pair->filename = g_build_filename (chooser_uri, escaped, NULL);
2736                                         g_free (escaped);
2737                                 } else {
2738                                         pair->filename = g_strdup (chooser_uri);
2739                                 }
2740                                 pair->part = mime_part;
2741                                 files_to_save = g_list_prepend (files_to_save, pair);
2742                         }
2743                         tny_iterator_next (iter);
2744                 }
2745                 g_object_unref (iter);
2746         }
2747         g_free (chooser_uri);
2748
2749         if (files_to_save != NULL) {
2750                 SaveMimePartInfo *info = g_slice_new0 (SaveMimePartInfo);
2751                 info->pairs = files_to_save;
2752                 info->result = TRUE;
2753                 save_mime_parts_to_file_with_checks (info);
2754         }
2755
2756  end:
2757         /* Free and close the dialog */
2758         g_object_unref (mime_parts);
2759         gtk_widget_destroy (GTK_WIDGET (dialog));
2760 }
2761
2762 void
2763 modest_msg_view_window_save_attachments (ModestMsgViewWindow *window, TnyList *mime_parts)
2764 {
2765         ModestMsgViewWindowPrivate *priv;
2766         GtkWidget *save_dialog = NULL;
2767         gchar *folder = NULL;
2768         gchar *filename = NULL;
2769         gchar *save_multiple_str = NULL;
2770
2771         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
2772         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2773
2774         if (mime_parts == NULL) {
2775                 mime_parts = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2776                 if (mime_parts == NULL || tny_list_get_length (mime_parts) == 0)
2777                         return;
2778         } else {
2779                 g_object_ref (mime_parts);
2780         }
2781
2782         /* prepare dialog */
2783         if (tny_list_get_length (mime_parts) == 1) {
2784                 TnyIterator *iter;
2785                 /* only one attachment selected */
2786                 iter = tny_list_create_iterator (mime_parts);
2787                 TnyMimePart *mime_part = (TnyMimePart *) tny_iterator_get_current (iter);
2788                 g_object_unref (iter);
2789                 if (!modest_tny_mime_part_is_msg (mime_part) && 
2790                     modest_tny_mime_part_is_attachment_for_modest (mime_part) &&
2791                     !tny_mime_part_is_purged (mime_part)) {
2792                         filename = g_strdup (tny_mime_part_get_filename (mime_part));
2793                 } else {
2794                         /* TODO: show any error? */
2795                         g_warning ("Tried to save a non-file attachment");
2796                         g_object_unref (mime_parts);
2797                         return;
2798                 }
2799                 g_object_unref (mime_part);
2800         } else {
2801                 save_multiple_str = g_strdup_printf (_FM("sfil_va_number_of_objects_attachments"), 
2802                                                      tny_list_get_length (mime_parts));
2803         }
2804         
2805         save_dialog = hildon_file_chooser_dialog_new (GTK_WINDOW (window), 
2806                                                       GTK_FILE_CHOOSER_ACTION_SAVE);
2807
2808         /* set folder */
2809         folder = g_build_filename (g_get_home_dir (), g_getenv (MYDOCS_ENV), DOCS_FOLDER, NULL);
2810         gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (save_dialog), folder);
2811         g_free (folder);
2812
2813         /* set filename */
2814         if (filename) {
2815                 gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (save_dialog), 
2816                                                    filename);
2817                 g_free (filename);
2818         }
2819
2820         /* if multiple, set multiple string */
2821         if (save_multiple_str) {
2822                 g_object_set (G_OBJECT (save_dialog), "save-multiple", save_multiple_str, NULL);
2823                 gtk_window_set_title (GTK_WINDOW (save_dialog), _FM("sfil_ti_save_objects_files"));
2824         }
2825
2826         /* We must run this asynchronously, because the hildon dialog
2827            performs a gtk_dialog_run by itself which leads to gdk
2828            deadlocks */
2829         g_signal_connect (save_dialog, "response", 
2830                           G_CALLBACK (save_attachments_response), mime_parts);
2831
2832         gtk_widget_show_all (save_dialog);
2833 }
2834
2835 static gboolean
2836 show_remove_attachment_information (gpointer userdata)
2837 {
2838         ModestMsgViewWindow *window = (ModestMsgViewWindow *) userdata;
2839         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2840
2841         /* We're outside the main lock */
2842         gdk_threads_enter ();
2843
2844         if (priv->remove_attachment_banner != NULL) {
2845                 gtk_widget_destroy (priv->remove_attachment_banner);
2846                 g_object_unref (priv->remove_attachment_banner);
2847         }
2848
2849         priv->remove_attachment_banner = g_object_ref (
2850                 hildon_banner_show_animation (NULL, NULL, _("mcen_me_inbox_remove_attachments")));
2851
2852         gdk_threads_leave ();
2853
2854         return FALSE;
2855 }
2856
2857 void
2858 modest_msg_view_window_remove_attachments (ModestMsgViewWindow *window, gboolean get_all)
2859 {
2860         ModestMsgViewWindowPrivate *priv;
2861         TnyList *mime_parts = NULL;
2862         gchar *confirmation_message;
2863         gint response;
2864         gint n_attachments;
2865         TnyMsg *msg;
2866         TnyIterator *iter;
2867
2868         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
2869         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2870
2871         if (get_all)
2872                 mime_parts = modest_msg_view_get_attachments (MODEST_MSG_VIEW (priv->msg_view));
2873         else
2874                 mime_parts = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2875                 
2876         /* Remove already purged messages from mime parts list */
2877         iter = tny_list_create_iterator (mime_parts);
2878         while (!tny_iterator_is_done (iter)) {
2879                 TnyMimePart *part = TNY_MIME_PART (tny_iterator_get_current (iter));
2880                 tny_iterator_next (iter);
2881                 if (tny_mime_part_is_purged (part)) {
2882                         tny_list_remove (mime_parts, (GObject *) part);
2883                 }
2884                 g_object_unref (part);
2885         }
2886         g_object_unref (iter);
2887
2888         if (tny_list_get_length (mime_parts) == 0) {
2889                 g_object_unref (mime_parts);
2890                 return;
2891         }
2892
2893         n_attachments = tny_list_get_length (mime_parts);
2894         if (n_attachments == 1) {
2895                 gchar *filename;
2896                 TnyMimePart *part;
2897
2898                 iter = tny_list_create_iterator (mime_parts);
2899                 part = (TnyMimePart *) tny_iterator_get_current (iter);
2900                 g_object_unref (iter);
2901                 if (modest_tny_mime_part_is_msg (part)) {
2902                         TnyHeader *header;
2903                         header = tny_msg_get_header (TNY_MSG (part));
2904                         filename = tny_header_dup_subject (header);
2905                         g_object_unref (header);
2906                         if (filename == NULL)
2907                                 filename = g_strdup (_("mail_va_no_subject"));
2908                 } else {
2909                         filename = g_strdup (tny_mime_part_get_filename (TNY_MIME_PART (part)));
2910                 }
2911                 confirmation_message = g_strdup_printf (_("mcen_nc_purge_file_text"), filename);
2912                 g_free (filename);
2913                 g_object_unref (part);
2914         } else {
2915                 confirmation_message = g_strdup_printf (ngettext("mcen_nc_purge_file_text", 
2916                                                                  "mcen_nc_purge_files_text", 
2917                                                                  n_attachments), n_attachments);
2918         }
2919         response = modest_platform_run_confirmation_dialog (GTK_WINDOW (window),
2920                                                             confirmation_message);
2921         g_free (confirmation_message);
2922
2923         if (response != GTK_RESPONSE_OK) {
2924                 g_object_unref (mime_parts);
2925                 return;
2926         }
2927
2928         priv->purge_timeout = g_timeout_add (2000, show_remove_attachment_information, window);
2929         
2930         iter = tny_list_create_iterator (mime_parts);
2931         while (!tny_iterator_is_done (iter)) {
2932                 TnyMimePart *part;
2933
2934                 part = (TnyMimePart *) tny_iterator_get_current (iter);
2935                 tny_mime_part_set_purged (TNY_MIME_PART (part));
2936                 g_object_unref (part);
2937                 tny_iterator_next (iter);
2938         }
2939         g_object_unref (iter);
2940
2941         msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
2942         tny_msg_view_clear (TNY_MSG_VIEW (priv->msg_view));
2943         tny_msg_rewrite_cache (msg);
2944         tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
2945         g_object_unref (msg);
2946
2947         g_object_unref (mime_parts);
2948
2949         if (priv->purge_timeout > 0) {
2950                 g_source_remove (priv->purge_timeout);
2951                 priv->purge_timeout = 0;
2952         }
2953
2954         if (priv->remove_attachment_banner) {
2955                 gtk_widget_destroy (priv->remove_attachment_banner);
2956                 g_object_unref (priv->remove_attachment_banner);
2957                 priv->remove_attachment_banner = NULL;
2958         }
2959
2960
2961 }
2962
2963
2964 static void
2965 update_window_title (ModestMsgViewWindow *window)
2966 {
2967         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2968         TnyMsg *msg = NULL;
2969         TnyHeader *header = NULL;
2970         gchar *subject = NULL;
2971         
2972         msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
2973
2974         if (msg != NULL) {
2975                 header = tny_msg_get_header (msg);
2976                 subject = tny_header_dup_subject (header);
2977                 g_object_unref (header);
2978                 g_object_unref (msg);
2979         }
2980
2981         if ((subject == NULL)||(subject[0] == '\0')) {
2982                 g_free (subject);
2983                 subject = g_strdup (_("mail_va_no_subject"));
2984         }
2985
2986         gtk_window_set_title (GTK_WINDOW (window), subject);
2987 }
2988
2989
2990 static void on_move_focus (GtkWidget *widget,
2991                            GtkDirectionType direction,
2992                            gpointer userdata)
2993 {
2994         g_signal_stop_emission_by_name (G_OBJECT (widget), "move-focus");
2995 }
2996
2997 static TnyStream *
2998 fetch_image_open_stream (TnyStreamCache *self, gint64 *expected_size, gchar *uri)
2999 {
3000         GnomeVFSResult result;
3001         GnomeVFSHandle *handle = NULL;
3002         GnomeVFSFileInfo *info = NULL;
3003         TnyStream *stream;
3004
3005         result = gnome_vfs_open (&handle, uri, GNOME_VFS_OPEN_READ);
3006         if (result != GNOME_VFS_OK) {
3007                 *expected_size = 0;
3008                 return NULL;
3009         }
3010         
3011         info = gnome_vfs_file_info_new ();
3012         result = gnome_vfs_get_file_info_from_handle (handle, info, GNOME_VFS_FILE_INFO_DEFAULT);
3013         if (result != GNOME_VFS_OK || ! (info->valid_fields & GNOME_VFS_FILE_INFO_FIELDS_SIZE)) {
3014                 /* We put a "safe" default size for going to cache */
3015                 *expected_size = (300*1024);
3016         } else {
3017                 *expected_size = info->size;
3018         }
3019         gnome_vfs_file_info_unref (info);
3020
3021         stream = tny_vfs_stream_new (handle);
3022
3023         return stream;
3024
3025 }
3026
3027 typedef struct {
3028         gchar *uri;
3029         gchar *cache_id;
3030         TnyStream *output_stream;
3031         GtkWidget *msg_view;
3032 } FetchImageData;
3033
3034 gboolean
3035 on_fetch_image_idle_refresh_view (gpointer userdata)
3036 {
3037
3038         FetchImageData *fidata = (FetchImageData *) userdata;
3039         g_message ("REFRESH VIEW");
3040         if (GTK_WIDGET_DRAWABLE (fidata->msg_view)) {
3041                 g_message ("QUEUING DRAW");
3042                 gtk_widget_queue_draw (fidata->msg_view);
3043         }
3044         g_object_unref (fidata->msg_view);
3045         g_slice_free (FetchImageData, fidata);
3046         return FALSE;
3047 }
3048
3049 static gpointer
3050 on_fetch_image_thread (gpointer userdata)
3051 {
3052         FetchImageData *fidata = (FetchImageData *) userdata;
3053         TnyStreamCache *cache;
3054         TnyStream *cache_stream;
3055
3056         cache = modest_runtime_get_images_cache ();
3057         cache_stream = tny_stream_cache_get_stream (cache, fidata->cache_id, (TnyStreamCacheOpenStreamFetcher) fetch_image_open_stream, (gpointer) fidata->uri);
3058         g_free (fidata->cache_id);
3059         g_free (fidata->uri);
3060
3061         if (cache_stream != NULL) {
3062                 tny_stream_write_to_stream (cache_stream, fidata->output_stream);
3063                 tny_stream_close (cache_stream);
3064                 g_object_unref (cache_stream);
3065         }
3066
3067         tny_stream_close (fidata->output_stream);
3068         g_object_unref (fidata->output_stream);
3069
3070
3071         gdk_threads_enter ();
3072         g_idle_add (on_fetch_image_idle_refresh_view, fidata);
3073         gdk_threads_leave ();
3074
3075         return NULL;
3076 }
3077
3078 static gboolean
3079 on_fetch_image (ModestMsgView *msgview,
3080                 const gchar *uri,
3081                 TnyStream *stream,
3082                 ModestMsgViewWindow *window)
3083 {
3084         const gchar *current_account;
3085         ModestMsgViewWindowPrivate *priv;
3086         FetchImageData *fidata;
3087
3088         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
3089
3090         current_account = modest_window_get_active_account (MODEST_WINDOW (window));
3091
3092         fidata = g_slice_new0 (FetchImageData);
3093         fidata->msg_view = g_object_ref (msgview);
3094         fidata->uri = g_strdup (uri);
3095         fidata->cache_id = modest_images_cache_get_id (current_account, uri);
3096         fidata->output_stream = g_object_ref (stream);
3097
3098         if (g_thread_create (on_fetch_image_thread, fidata, FALSE, NULL) == NULL) {
3099                 g_object_unref (fidata->output_stream);
3100                 g_free (fidata->cache_id);
3101                 g_free (fidata->uri);
3102                 g_object_unref (fidata->msg_view);
3103                 g_slice_free (FetchImageData, fidata);
3104                 tny_stream_close (stream);
3105                 return FALSE;
3106         }
3107
3108         return TRUE;;
3109 }
3110
3111 static void 
3112 add_to_menu (ModestMsgViewWindow *self,
3113              HildonAppMenu *menu,
3114              gchar *label,
3115              GCallback callback,
3116              ModestDimmingRulesGroup *dimming_group,
3117              GCallback dimming_callback)
3118 {
3119         GtkWidget *button;
3120
3121         button = gtk_button_new_with_label (label);
3122         g_signal_connect_after (G_OBJECT (button), "clicked",
3123                                 callback, (gpointer) self);
3124         modest_dimming_rules_group_add_widget_rule (dimming_group,
3125                                                     button,
3126                                                     dimming_callback,
3127                                                     MODEST_WINDOW (self));
3128         hildon_app_menu_append (menu, GTK_BUTTON (button));
3129 }
3130
3131 static void 
3132 setup_menu (ModestMsgViewWindow *self, ModestDimmingRulesGroup *group)
3133 {
3134         ModestMsgViewWindowPrivate *priv = NULL;
3135         GtkWidget *app_menu;
3136
3137         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW(self));
3138
3139         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
3140
3141         app_menu = hildon_app_menu_new ();
3142
3143         /* Settings menu buttons */
3144         add_to_menu (self, HILDON_APP_MENU (app_menu), _("mcen_me_inbox_replytoall"),
3145                      G_CALLBACK (modest_ui_actions_on_reply_all),
3146                      group, G_CALLBACK (modest_ui_dimming_rules_on_reply_msg));
3147         add_to_menu (self, HILDON_APP_MENU (app_menu), _("mcen_me_inbox_forward"),
3148                      G_CALLBACK (modest_ui_actions_on_forward),
3149                      group, G_CALLBACK (modest_ui_dimming_rules_on_reply_msg));
3150
3151         add_to_menu (self, HILDON_APP_MENU (app_menu), _("mcen_me_inbox_mark_as_read"),
3152                      G_CALLBACK (modest_ui_actions_on_mark_as_read),
3153                      group, G_CALLBACK (modest_ui_dimming_rules_on_mark_as_read_msg_in_view));
3154         add_to_menu (self, HILDON_APP_MENU (app_menu), _("mcen_me_inbox_mark_as_unread"),
3155                      G_CALLBACK (modest_ui_actions_on_mark_as_unread),
3156                      group, G_CALLBACK (modest_ui_dimming_rules_on_mark_as_unread_msg_in_view));
3157
3158         add_to_menu (self, HILDON_APP_MENU (app_menu), _("mcen_me_viewer_save_attachments"),
3159                      G_CALLBACK (modest_ui_actions_save_attachments),
3160                      group, G_CALLBACK (modest_ui_dimming_rules_on_save_attachments));
3161         add_to_menu (self, HILDON_APP_MENU (app_menu), _("mcen_me_inbox_remove_attachments"),
3162                      G_CALLBACK (modest_ui_actions_remove_attachments),
3163                      group, G_CALLBACK (modest_ui_dimming_rules_on_remove_attachments));
3164
3165         add_to_menu (self, HILDON_APP_MENU (app_menu), _("mcen_me_viewer_newemail"),
3166                      G_CALLBACK (modest_ui_actions_on_new_msg),
3167                      group, G_CALLBACK (modest_ui_dimming_rules_on_new_msg));
3168         add_to_menu (self, HILDON_APP_MENU (app_menu), _("mcen_me_viewer_addtocontacts"),
3169                      G_CALLBACK (modest_ui_actions_add_to_contacts),
3170                      group, G_CALLBACK (modest_ui_dimming_rules_on_add_to_contacts));
3171
3172         hildon_stackable_window_set_main_menu (HILDON_STACKABLE_WINDOW (self), 
3173                                                HILDON_APP_MENU (app_menu));
3174 }
3175
3176 static gboolean 
3177 modest_msg_view_window_toggle_menu (HildonWindow *window,
3178                                     guint button,
3179                                     guint32 time)
3180 {
3181         modest_ui_actions_check_menu_dimming_rules (MODEST_WINDOW (window));
3182
3183         return HILDON_WINDOW_CLASS (parent_class)->toggle_menu (window, button, time);
3184 }
3185
3186
3187 void
3188 modest_msg_view_window_add_to_contacts (ModestMsgViewWindow *self)
3189 {
3190         ModestMsgViewWindowPrivate *priv;
3191         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
3192         GSList *recipients = NULL;
3193         TnyMsg *msg = NULL;
3194         gboolean contacts_to_add = FALSE;
3195
3196         msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
3197         if (msg == NULL) return;
3198         recipients = modest_tny_msg_get_all_recipients_list (msg);
3199
3200         if (recipients != NULL) {
3201                 GtkWidget *picker_dialog;
3202                 GtkWidget *selector;
3203                 GSList *node;
3204                 gchar *selected = NULL;
3205
3206                 selector = hildon_touch_selector_new_text ();
3207                 g_object_ref (selector);
3208
3209                 for (node = recipients; node != NULL; node = g_slist_next (node)) {
3210                         if (!modest_address_book_has_address ((const gchar *) node->data)) {
3211                                 hildon_touch_selector_append_text (HILDON_TOUCH_SELECTOR (selector), 
3212                                                                    (const gchar *) node->data);
3213                                 contacts_to_add = TRUE;
3214                         }
3215                 }
3216
3217                 if (contacts_to_add) {
3218                         gint picker_result;
3219
3220                         picker_dialog = hildon_picker_dialog_new (GTK_WINDOW (self));
3221                         gtk_window_set_title (GTK_WINDOW (picker_dialog), _("mcen_me_viewer_addtocontacts"));
3222
3223                         hildon_picker_dialog_set_selector (HILDON_PICKER_DIALOG (picker_dialog), 
3224                                                            HILDON_TOUCH_SELECTOR (selector));
3225                         
3226                         picker_result = gtk_dialog_run (GTK_DIALOG (picker_dialog));
3227
3228                         if (picker_result == GTK_RESPONSE_OK) {
3229                                 selected = hildon_touch_selector_get_current_text (HILDON_TOUCH_SELECTOR (selector));
3230                         }
3231                         gtk_widget_destroy (picker_dialog);
3232
3233                         if (selected)
3234                                 modest_address_book_add_address (selected);
3235                         g_free (selected);
3236
3237                 } else {
3238
3239                         g_object_unref (selector);
3240
3241                 }
3242         }
3243         
3244         if (recipients) {g_slist_foreach (recipients, (GFunc) g_free, NULL); g_slist_free (recipients);}
3245         g_object_unref (msg);
3246 }