* The toolbar buttons are expanded correctly in the viewer after clicking on Next...
[modest] / src / maemo / 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-maemo-utils.h>
39 #include <modest-tny-msg.h>
40 #include <modest-msg-view-window.h>
41 #include <modest-attachments-view.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-runtime.h>
46 #include <modest-window-priv.h>
47 #include <modest-tny-folder.h>
48 #include <modest-text-utils.h>
49 #include <modest-account-mgr-helpers.h>
50 #include "modest-progress-bar-widget.h"
51 #include "modest-defs.h"
52 #include "modest-hildon-includes.h"
53 #include "modest-ui-dimming-manager.h"
54 #include <gdk/gdkkeysyms.h>
55 #include <modest-tny-account.h>
56 #include <modest-mime-part-view.h>
57 #include <modest-isearch-view.h>
58 #include <math.h>
59 #include <errno.h>
60 #include <glib/gstdio.h>
61
62 #define DEFAULT_FOLDER "MyDocs/.documents"
63
64 static void  modest_msg_view_window_class_init   (ModestMsgViewWindowClass *klass);
65 static void  modest_msg_view_window_init         (ModestMsgViewWindow *obj);
66 static void  modest_header_view_observer_init(
67                 ModestHeaderViewObserverIface *iface_class);
68 static void  modest_msg_view_window_finalize     (GObject *obj);
69 static void  modest_msg_view_window_toggle_find_toolbar (GtkToggleAction *obj,
70                                                          gpointer data);
71 static void  modest_msg_view_window_find_toolbar_close (GtkWidget *widget,
72                                                         ModestMsgViewWindow *obj);
73 static void  modest_msg_view_window_find_toolbar_search (GtkWidget *widget,
74                                                         ModestMsgViewWindow *obj);
75
76 static void modest_msg_view_window_disconnect_signals (ModestWindow *self);
77 static void modest_msg_view_window_set_zoom (ModestWindow *window,
78                                              gdouble zoom);
79 static gdouble modest_msg_view_window_get_zoom (ModestWindow *window);
80 static gboolean modest_msg_view_window_zoom_minus (ModestWindow *window);
81 static gboolean modest_msg_view_window_zoom_plus (ModestWindow *window);
82 static gboolean modest_msg_view_window_key_event (GtkWidget *window,
83                                                   GdkEventKey *event,
84                                                   gpointer userdata);
85 static gboolean modest_msg_view_window_window_state_event (GtkWidget *widget, 
86                                                            GdkEventWindowState *event, 
87                                                            gpointer userdata);
88 static void modest_msg_view_window_update_priority (ModestMsgViewWindow *window);
89
90 static void modest_msg_view_window_show_toolbar   (ModestWindow *window,
91                                                    gboolean show_toolbar);
92
93 static void modest_msg_view_window_clipboard_owner_change (GtkClipboard *clipboard,
94                                                            GdkEvent *event,
95                                                            ModestMsgViewWindow *window);
96 void modest_msg_view_window_on_row_changed(
97                 GtkTreeModel *header_model,
98                 GtkTreePath *arg1,
99                 GtkTreeIter *arg2,
100                 ModestMsgViewWindow *window);
101
102 void modest_msg_view_window_on_row_deleted(
103                 GtkTreeModel *header_model,
104                 GtkTreePath *arg1,
105                 ModestMsgViewWindow *window);
106
107 void modest_msg_view_window_on_row_inserted(
108                 GtkTreeModel *header_model,
109                 GtkTreePath *tree_path,
110                 GtkTreeIter *tree_iter,
111                 ModestMsgViewWindow *window);
112
113 void modest_msg_view_window_on_row_reordered(
114                 GtkTreeModel *header_model,
115                 GtkTreePath *arg1,
116                 GtkTreeIter *arg2,
117                 gpointer arg3,
118                 ModestMsgViewWindow *window);
119
120 void modest_msg_view_window_update_model_replaced(
121                 ModestHeaderViewObserver *window,
122                 GtkTreeModel *model,
123                 const gchar *tny_folder_id);
124
125 static void cancel_progressbar  (GtkToolButton *toolbutton,
126                                  ModestMsgViewWindow *self);
127
128 static void on_queue_changed    (ModestMailOperationQueue *queue,
129                                  ModestMailOperation *mail_op,
130                                  ModestMailOperationQueueNotification type,
131                                  ModestMsgViewWindow *self);
132
133 static void on_account_removed  (TnyAccountStore *account_store, 
134                                  TnyAccount *account,
135                                  gpointer user_data);
136
137 static void on_move_focus (GtkWidget *widget,
138                            GtkDirectionType direction,
139                            gpointer userdata);
140
141 static void view_msg_cb         (ModestMailOperation *mail_op, 
142                                  TnyHeader *header, 
143                                  gboolean canceled,
144                                  TnyMsg *msg, 
145                                  GError *error,
146                                  gpointer user_data);
147
148 static void set_toolbar_mode    (ModestMsgViewWindow *self, 
149                                  ModestToolBarModes mode);
150
151 static void update_window_title (ModestMsgViewWindow *window);
152
153 static gboolean set_toolbar_transfer_mode     (ModestMsgViewWindow *self); 
154 static void init_window (ModestMsgViewWindow *obj);
155
156
157 /* list my signals */
158 enum {
159         MSG_CHANGED_SIGNAL,
160         LAST_SIGNAL
161 };
162
163 static const GtkToggleActionEntry msg_view_toggle_action_entries [] = {
164         { "FindInMessage",    MODEST_TOOLBAR_ICON_FIND,    N_("qgn_toolb_gene_find"), NULL, NULL, G_CALLBACK (modest_msg_view_window_toggle_find_toolbar), FALSE },
165         { "ToolsFindInMessage", NULL, N_("mcen_me_viewer_find"), "<CTRL>F", NULL, G_CALLBACK (modest_msg_view_window_toggle_find_toolbar), FALSE },
166 };
167
168 static const GtkRadioActionEntry msg_view_zoom_action_entries [] = {
169         { "Zoom50", NULL, N_("mcen_me_viewer_50"), NULL, NULL, 50 },
170         { "Zoom80", NULL, N_("mcen_me_viewer_80"), NULL, NULL, 80 },
171         { "Zoom100", NULL, N_("mcen_me_viewer_100"), NULL, NULL, 100 },
172         { "Zoom120", NULL, N_("mcen_me_viewer_120"), NULL, NULL, 120 },
173         { "Zoom150", NULL, N_("mcen_me_viewer_150"), NULL, NULL, 150 },
174         { "Zoom200", NULL, N_("mcen_me_viewer_200"), NULL, NULL, 200 }
175 };
176
177 typedef struct _ModestMsgViewWindowPrivate ModestMsgViewWindowPrivate;
178 struct _ModestMsgViewWindowPrivate {
179
180         GtkWidget   *msg_view;
181         GtkWidget   *main_scroll;
182         GtkWidget   *find_toolbar;
183         gchar       *last_search;
184
185         /* Progress observers */
186         GtkWidget        *progress_bar;
187         GSList           *progress_widgets;
188
189         /* Tollbar items */
190         GtkWidget   *progress_toolitem;
191         GtkWidget   *cancel_toolitem;
192         GtkWidget   *prev_toolitem;
193         GtkWidget   *next_toolitem;
194         ModestToolBarModes current_toolbar_mode;
195
196         /* Optimized view enabled */
197         gboolean optimized_view;
198
199         /* Whether this was created via the *_new_for_search_result() function. */
200         gboolean is_search_result;
201         
202         /* A reference to the @model of the header view 
203          * to allow selecting previous/next messages,
204          * if the message is currently selected in the header view.
205          */
206         const gchar *header_folder_id;
207         GtkTreeModel *header_model;
208         GtkTreeRowReference *row_reference;
209         GtkTreeRowReference *next_row_reference;
210
211         gulong clipboard_change_handler;
212         gulong queue_change_handler;
213         gulong account_removed_handler;
214         gulong row_changed_handler;
215         gulong row_deleted_handler;
216         gulong row_inserted_handler;
217         gulong rows_reordered_handler;
218
219         guint purge_timeout;
220         GtkWidget *remove_attachment_banner;
221
222         guint progress_bar_timeout;
223
224         gchar *msg_uid;
225         
226         GSList *sighandlers;
227 };
228
229 #define MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(o)      (G_TYPE_INSTANCE_GET_PRIVATE((o), \
230                                                     MODEST_TYPE_MSG_VIEW_WINDOW, \
231                                                     ModestMsgViewWindowPrivate))
232 /* globals */
233 static GtkWindowClass *parent_class = NULL;
234
235 /* uncomment the following if you have defined any signals */
236 static guint signals[LAST_SIGNAL] = {0};
237
238 GType
239 modest_msg_view_window_get_type (void)
240 {
241         static GType my_type = 0;
242         if (!my_type) {
243                 static const GTypeInfo my_info = {
244                         sizeof(ModestMsgViewWindowClass),
245                         NULL,           /* base init */
246                         NULL,           /* base finalize */
247                         (GClassInitFunc) modest_msg_view_window_class_init,
248                         NULL,           /* class finalize */
249                         NULL,           /* class data */
250                         sizeof(ModestMsgViewWindow),
251                         1,              /* n_preallocs */
252                         (GInstanceInitFunc) modest_msg_view_window_init,
253                         NULL
254                 };
255                 my_type = g_type_register_static (MODEST_TYPE_WINDOW,
256                                                   "ModestMsgViewWindow",
257                                                   &my_info, 0);
258
259                 static const GInterfaceInfo modest_header_view_observer_info = 
260                 {
261                         (GInterfaceInitFunc) modest_header_view_observer_init,
262                         NULL,         /* interface_finalize */
263                         NULL          /* interface_data */
264                 };
265
266                 g_type_add_interface_static (my_type,
267                                 MODEST_TYPE_HEADER_VIEW_OBSERVER,
268                                 &modest_header_view_observer_info);
269         }
270         return my_type;
271 }
272
273 static void
274 save_state (ModestWindow *self)
275 {
276         modest_widget_memory_save (modest_runtime_get_conf (),
277                                    G_OBJECT(self), 
278                                    MODEST_CONF_MSG_VIEW_WINDOW_KEY);
279 }
280
281
282 static void
283 restore_settings (ModestMsgViewWindow *self)
284 {
285         ModestConf *conf;
286         ModestWindowPrivate *parent_priv = MODEST_WINDOW_GET_PRIVATE (self);
287         GtkAction *action;
288
289         conf = modest_runtime_get_conf ();
290         action = gtk_ui_manager_get_action (parent_priv->ui_manager, 
291                                             "/MenuBar/ViewMenu/ViewShowToolbarMenu/ViewShowToolbarNormalScreenMenu");
292         gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action),
293                                       modest_conf_get_bool (conf, MODEST_CONF_MSG_VIEW_WINDOW_SHOW_TOOLBAR, NULL));
294         action = gtk_ui_manager_get_action (parent_priv->ui_manager, 
295                                             "/MenuBar/ViewMenu/ViewShowToolbarMenu/ViewShowToolbarFullScreenMenu");
296         gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action),
297                                       modest_conf_get_bool (conf, MODEST_CONF_MSG_VIEW_WINDOW_SHOW_TOOLBAR_FULLSCREEN, NULL));
298         modest_widget_memory_restore (conf,
299                                       G_OBJECT(self), 
300                                       MODEST_CONF_MSG_VIEW_WINDOW_KEY);
301 }
302
303 static void
304 modest_msg_view_window_class_init (ModestMsgViewWindowClass *klass)
305 {
306         GObjectClass *gobject_class;
307         ModestWindowClass *modest_window_class;
308         gobject_class = (GObjectClass*) klass;
309         modest_window_class = (ModestWindowClass *) klass;
310
311         parent_class            = g_type_class_peek_parent (klass);
312         gobject_class->finalize = modest_msg_view_window_finalize;
313
314         modest_window_class->set_zoom_func = modest_msg_view_window_set_zoom;
315         modest_window_class->get_zoom_func = modest_msg_view_window_get_zoom;
316         modest_window_class->zoom_minus_func = modest_msg_view_window_zoom_minus;
317         modest_window_class->zoom_plus_func = modest_msg_view_window_zoom_plus;
318         modest_window_class->show_toolbar_func = modest_msg_view_window_show_toolbar;
319         modest_window_class->disconnect_signals_func = modest_msg_view_window_disconnect_signals;
320
321         g_type_class_add_private (gobject_class, sizeof(ModestMsgViewWindowPrivate));
322
323         modest_window_class->save_state_func = save_state;
324
325         signals[MSG_CHANGED_SIGNAL] =
326                 g_signal_new ("msg-changed",
327                               G_TYPE_FROM_CLASS (gobject_class),
328                               G_SIGNAL_RUN_FIRST,
329                               G_STRUCT_OFFSET (ModestMsgViewWindowClass, msg_changed),
330                               NULL, NULL,
331                               modest_marshal_VOID__POINTER_POINTER,
332                               G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER);
333 }
334
335 static void modest_header_view_observer_init(
336                 ModestHeaderViewObserverIface *iface_class)
337 {
338         iface_class->update_func = modest_msg_view_window_update_model_replaced;
339 }
340
341 static void
342 modest_msg_view_window_init (ModestMsgViewWindow *obj)
343 {
344         ModestMsgViewWindowPrivate *priv;
345         ModestWindowPrivate *parent_priv = NULL;
346         GtkActionGroup *action_group = NULL;
347         GError *error = NULL;
348         GdkPixbuf *window_icon;
349
350         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(obj);
351         parent_priv = MODEST_WINDOW_GET_PRIVATE(obj);
352         parent_priv->ui_manager = gtk_ui_manager_new();
353
354         action_group = gtk_action_group_new ("ModestMsgViewWindowActions");
355         gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE);
356
357         /* Add common actions */
358         gtk_action_group_add_actions (action_group,
359                                       modest_action_entries,
360                                       G_N_ELEMENTS (modest_action_entries),
361                                       obj);
362         gtk_action_group_add_toggle_actions (action_group,
363                                              modest_toggle_action_entries,
364                                              G_N_ELEMENTS (modest_toggle_action_entries),
365                                              obj);
366         gtk_action_group_add_toggle_actions (action_group,
367                                              msg_view_toggle_action_entries,
368                                              G_N_ELEMENTS (msg_view_toggle_action_entries),
369                                              obj);
370         gtk_action_group_add_radio_actions (action_group,
371                                             msg_view_zoom_action_entries,
372                                             G_N_ELEMENTS (msg_view_zoom_action_entries),
373                                             100,
374                                             G_CALLBACK (modest_ui_actions_on_change_zoom),
375                                             obj);
376
377         gtk_ui_manager_insert_action_group (parent_priv->ui_manager, action_group, 0);
378         g_object_unref (action_group);
379
380         /* Load the UI definition */
381         gtk_ui_manager_add_ui_from_file (parent_priv->ui_manager, MODEST_UIDIR "modest-msg-view-window-ui.xml",
382                                          &error);
383         if (error) {
384                 g_printerr ("modest: could not merge modest-msg-view-window-ui.xml: %s\n", error->message);
385                 g_error_free (error);
386                 error = NULL;
387         }
388         /* ****** */
389
390         /* Add accelerators */
391         gtk_window_add_accel_group (GTK_WINDOW (obj), 
392                                     gtk_ui_manager_get_accel_group (parent_priv->ui_manager));
393         
394         priv->is_search_result = FALSE;
395
396         priv->msg_view      = NULL;
397         priv->header_model  = NULL;
398         priv->header_folder_id  = NULL;
399         priv->clipboard_change_handler = 0;
400         priv->queue_change_handler = 0;
401         priv->account_removed_handler = 0;
402         priv->row_changed_handler = 0;
403         priv->row_deleted_handler = 0;
404         priv->row_inserted_handler = 0;
405         priv->rows_reordered_handler = 0;
406         priv->current_toolbar_mode = TOOLBAR_MODE_NORMAL;
407
408         priv->optimized_view  = FALSE;
409         priv->progress_bar_timeout = 0;
410         priv->purge_timeout = 0;
411         priv->remove_attachment_banner = NULL;
412         priv->msg_uid = NULL;
413         
414         priv->sighandlers = NULL;
415         
416         /* Init window */
417         init_window (MODEST_MSG_VIEW_WINDOW(obj));
418         /* Set window icon */
419         window_icon = modest_platform_get_icon (MODEST_APP_MSG_VIEW_ICON); 
420         if (window_icon) {
421                 gtk_window_set_icon (GTK_WINDOW (obj), window_icon);
422                 g_object_unref (window_icon);
423         }
424
425
426         modest_window_mgr_register_help_id (modest_runtime_get_window_mgr(),
427                                             GTK_WINDOW(obj),"applications_email_viewer");
428 }
429
430
431 static gboolean
432 set_toolbar_transfer_mode (ModestMsgViewWindow *self)
433 {
434         ModestMsgViewWindowPrivate *priv = NULL;
435         
436         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), FALSE);
437
438         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
439
440         set_toolbar_mode (self, TOOLBAR_MODE_TRANSFER);
441         
442         if (priv->progress_bar_timeout > 0) {
443                 g_source_remove (priv->progress_bar_timeout);
444                 priv->progress_bar_timeout = 0;
445         }
446         
447         return FALSE;
448 }
449
450 static void 
451 set_toolbar_mode (ModestMsgViewWindow *self, 
452                   ModestToolBarModes mode)
453 {
454         ModestWindowPrivate *parent_priv;
455         ModestMsgViewWindowPrivate *priv;
456 /*      GtkWidget *widget = NULL; */
457
458         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self));
459
460         parent_priv = MODEST_WINDOW_GET_PRIVATE(self);
461         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
462                         
463         /* Sets current toolbar mode */
464         priv->current_toolbar_mode = mode;
465
466         /* Update toolbar dimming state */
467         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (self));
468
469         switch (mode) {
470         case TOOLBAR_MODE_NORMAL:               
471                 if (priv->progress_toolitem) {
472                         gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->progress_toolitem), FALSE);
473                         gtk_widget_hide (priv->progress_toolitem);
474                 }
475
476                 if (priv->progress_bar)
477                         gtk_widget_hide (priv->progress_bar);
478                         
479                 if (priv->cancel_toolitem)
480                         gtk_widget_hide (priv->cancel_toolitem);
481
482                 if (priv->prev_toolitem)
483                         gtk_widget_show (priv->prev_toolitem);
484                 
485                 if (priv->next_toolitem)
486                         gtk_widget_show (priv->next_toolitem);
487                         
488                 /* Hide toolbar if optimized view is enabled */
489                 if (priv->optimized_view) {
490                         gtk_widget_set_no_show_all (parent_priv->toolbar, TRUE);
491                         gtk_widget_hide (GTK_WIDGET(parent_priv->toolbar));
492                 }
493
494                 break;
495         case TOOLBAR_MODE_TRANSFER:
496                 if (priv->prev_toolitem)
497                         gtk_widget_hide (priv->prev_toolitem);
498                 
499                 if (priv->next_toolitem)
500                         gtk_widget_hide (priv->next_toolitem);
501                 
502                 if (priv->progress_bar)
503                         gtk_widget_show (priv->progress_bar);
504
505                 if (priv->progress_toolitem) {
506                         gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->progress_toolitem), TRUE);
507                         gtk_widget_show (priv->progress_toolitem);
508                 }
509                         
510                 if (priv->cancel_toolitem)
511                         gtk_widget_show (priv->cancel_toolitem);
512
513                 /* Show toolbar if it's hiden (optimized view ) */
514                 if (priv->optimized_view) {
515                         gtk_widget_set_no_show_all (parent_priv->toolbar, FALSE);
516                         gtk_widget_show (GTK_WIDGET (parent_priv->toolbar));
517                 }
518
519                 break;
520         default:
521                 g_return_if_reached ();
522         }
523
524 }
525
526
527 static GtkWidget *
528 menubar_to_menu (GtkUIManager *ui_manager)
529 {
530         GtkWidget *main_menu;
531
532         /* Get the menubar from the UI manager */
533         main_menu = gtk_ui_manager_get_widget (ui_manager, "/MenuBar");
534
535         return main_menu;
536 }
537
538 static void
539 init_window (ModestMsgViewWindow *obj)
540 {
541         GtkWidget *main_vbox;
542         ModestMsgViewWindowPrivate *priv;
543         ModestWindowPrivate *parent_priv;
544
545         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(obj);
546         parent_priv = MODEST_WINDOW_GET_PRIVATE(obj);
547
548         priv->msg_view = GTK_WIDGET (tny_platform_factory_new_msg_view (modest_tny_platform_factory_get_instance ()));
549         modest_msg_view_set_shadow_type (MODEST_MSG_VIEW (priv->msg_view), GTK_SHADOW_NONE);
550         main_vbox = gtk_vbox_new  (FALSE, 6);
551
552 #ifdef MODEST_USE_MOZEMBED
553         priv->main_scroll = priv->msg_view;
554         gtk_widget_set_size_request (priv->msg_view, -1, 1600);
555 #else
556         priv->main_scroll = gtk_scrolled_window_new (NULL, NULL);
557         gtk_container_add (GTK_CONTAINER (priv->main_scroll), priv->msg_view);
558 #endif
559         gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->main_scroll), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
560         gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (priv->main_scroll), GTK_SHADOW_NONE);
561         modest_maemo_set_thumbable_scrollbar (GTK_SCROLLED_WINDOW(priv->main_scroll), TRUE);
562
563         gtk_box_pack_start (GTK_BOX(main_vbox), priv->main_scroll, TRUE, TRUE, 0);
564         gtk_container_add   (GTK_CONTAINER(obj), main_vbox);
565
566         priv->find_toolbar = hildon_find_toolbar_new (NULL);
567         hildon_window_add_toolbar (HILDON_WINDOW (obj), GTK_TOOLBAR (priv->find_toolbar));
568         gtk_widget_set_no_show_all (priv->find_toolbar, TRUE);
569         
570         gtk_widget_show_all (GTK_WIDGET(main_vbox));
571 }
572
573 static void
574 modest_msg_view_window_disconnect_signals (ModestWindow *self)
575 {
576         ModestMsgViewWindowPrivate *priv;
577         ModestHeaderView *header_view = NULL;
578         ModestWindow *main_window = NULL;
579         
580         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
581
582         if (gtk_clipboard_get (GDK_SELECTION_PRIMARY) &&
583             g_signal_handler_is_connected (gtk_clipboard_get (GDK_SELECTION_PRIMARY),
584                                            priv->clipboard_change_handler)) 
585                 g_signal_handler_disconnect (gtk_clipboard_get (GDK_SELECTION_PRIMARY), 
586                                              priv->clipboard_change_handler);
587
588         if (g_signal_handler_is_connected (G_OBJECT (modest_runtime_get_mail_operation_queue ()), 
589                                            priv->queue_change_handler))
590                 g_signal_handler_disconnect (G_OBJECT (modest_runtime_get_mail_operation_queue ()), 
591                                              priv->queue_change_handler);
592
593         if (g_signal_handler_is_connected (G_OBJECT (modest_runtime_get_account_store ()), 
594                                            priv->account_removed_handler))
595                 g_signal_handler_disconnect (G_OBJECT (modest_runtime_get_account_store ()), 
596                                              priv->account_removed_handler);
597
598         if (priv->header_model) {
599                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
600                                                   priv->row_changed_handler))
601                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
602                                                     priv->row_changed_handler);
603                 
604                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
605                                                   priv->row_deleted_handler))
606                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
607                                              priv->row_deleted_handler);
608                 
609                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
610                                                   priv->row_inserted_handler))
611                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
612                                                     priv->row_inserted_handler);
613                 
614                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
615                                                   priv->rows_reordered_handler))
616                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
617                                                     priv->rows_reordered_handler);
618         }
619
620         modest_signal_mgr_disconnect_all_and_destroy (priv->sighandlers);
621         priv->sighandlers = NULL;
622         
623         main_window = modest_window_mgr_get_main_window (modest_runtime_get_window_mgr(),
624                                                          FALSE); /* don't create */
625         if (!main_window)
626                 return;
627         
628         header_view = MODEST_HEADER_VIEW(
629                         modest_main_window_get_child_widget(
630                                 MODEST_MAIN_WINDOW(main_window),
631                                 MODEST_MAIN_WINDOW_WIDGET_TYPE_HEADER_VIEW));
632         if (header_view == NULL)
633                 return;
634         
635         modest_header_view_remove_observer(header_view,
636                         MODEST_HEADER_VIEW_OBSERVER(self));
637 }       
638
639 static void
640 modest_msg_view_window_finalize (GObject *obj)
641 {
642         ModestMsgViewWindowPrivate *priv;
643
644         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (obj);
645
646         /* Sanity check: shouldn't be needed, the window mgr should
647            call this function before */
648         modest_msg_view_window_disconnect_signals (MODEST_WINDOW (obj));
649
650         if (priv->header_model != NULL) {
651                 g_object_unref (priv->header_model);
652                 priv->header_model = NULL;
653         }
654
655         if (priv->progress_bar_timeout > 0) {
656                 g_source_remove (priv->progress_bar_timeout);
657                 priv->progress_bar_timeout = 0;
658         }
659
660         if (priv->remove_attachment_banner) {
661                 gtk_widget_destroy (priv->remove_attachment_banner);
662                 g_object_unref (priv->remove_attachment_banner);
663                 priv->remove_attachment_banner = NULL;
664         }
665
666         if (priv->purge_timeout > 0) {
667                 g_source_remove (priv->purge_timeout);
668                 priv->purge_timeout = 0;
669         }
670
671         if (priv->row_reference) {
672                 gtk_tree_row_reference_free (priv->row_reference);
673                 priv->row_reference = NULL;
674         }
675
676         if (priv->next_row_reference) {
677                 gtk_tree_row_reference_free (priv->next_row_reference);
678                 priv->next_row_reference = NULL;
679         }
680
681         if (priv->msg_uid) {
682                 g_free (priv->msg_uid);
683                 priv->msg_uid = NULL;
684         }
685
686         G_OBJECT_CLASS(parent_class)->finalize (obj);
687 }
688
689 static gboolean
690 select_next_valid_row (GtkTreeModel *model,
691                        GtkTreeRowReference **row_reference,
692                        gboolean cycle)
693 {
694         GtkTreeIter tmp_iter;
695         GtkTreePath *path, *next;
696         gboolean retval = FALSE;
697
698         g_return_val_if_fail (gtk_tree_row_reference_valid (*row_reference), FALSE);
699
700         path = gtk_tree_row_reference_get_path (*row_reference);
701         gtk_tree_model_get_iter (model, &tmp_iter, path);
702         gtk_tree_row_reference_free (*row_reference);
703         *row_reference = NULL;
704
705         if (gtk_tree_model_iter_next (model, &tmp_iter)) {
706                 next = gtk_tree_model_get_path (model, &tmp_iter);
707                 *row_reference = gtk_tree_row_reference_new (model, next);
708                 retval = TRUE;
709         } else if (cycle && gtk_tree_model_get_iter_first (model, &tmp_iter)) {
710                 next = gtk_tree_model_get_path (model, &tmp_iter);
711
712                 /* Ensure that we are not selecting the same */
713                 if (gtk_tree_path_compare (path, next) != 0) {
714                         *row_reference = gtk_tree_row_reference_new (model, next);
715                         retval = TRUE;
716                 }
717         }
718
719         /* Free */
720         gtk_tree_path_free (path);
721
722         return retval;
723 }
724
725 /* TODO: This should be in _init(), with the parameters as properties. */
726 static void
727 modest_msg_view_window_construct (ModestMsgViewWindow *self, 
728                             const gchar *modest_account_name,
729                             const gchar *msg_uid)
730 {
731         GObject *obj = NULL;
732         ModestMsgViewWindowPrivate *priv = NULL;
733         ModestWindowPrivate *parent_priv = NULL;
734         ModestDimmingRulesGroup *menu_rules_group = NULL;
735         ModestDimmingRulesGroup *toolbar_rules_group = NULL;
736         ModestDimmingRulesGroup *clipboard_rules_group = NULL;
737
738         obj = G_OBJECT (self);
739         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(obj);
740         parent_priv = MODEST_WINDOW_GET_PRIVATE(obj);
741
742         priv->msg_uid = g_strdup (msg_uid);
743
744         /* Menubar */
745         parent_priv->menubar = menubar_to_menu (parent_priv->ui_manager);
746         hildon_window_set_menu    (HILDON_WINDOW(obj), GTK_MENU(parent_priv->menubar));
747         gtk_widget_show (parent_priv->menubar);
748         parent_priv->ui_dimming_manager = modest_ui_dimming_manager_new();
749
750         menu_rules_group = modest_dimming_rules_group_new ("ModestMenuDimmingRules", FALSE);
751         toolbar_rules_group = modest_dimming_rules_group_new ("ModestToolbarDimmingRules", TRUE);
752         clipboard_rules_group = modest_dimming_rules_group_new ("ModestClipboardDimmingRules", FALSE);
753
754         /* Add common dimming rules */
755         modest_dimming_rules_group_add_rules (menu_rules_group, 
756                                               modest_msg_view_menu_dimming_entries,
757                                               G_N_ELEMENTS (modest_msg_view_menu_dimming_entries),
758                                               MODEST_WINDOW (self));
759         modest_dimming_rules_group_add_rules (toolbar_rules_group, 
760                                               modest_msg_view_toolbar_dimming_entries,
761                                               G_N_ELEMENTS (modest_msg_view_toolbar_dimming_entries),
762                                               MODEST_WINDOW (self));
763         modest_dimming_rules_group_add_rules (clipboard_rules_group, 
764                                               modest_msg_view_clipboard_dimming_entries,
765                                               G_N_ELEMENTS (modest_msg_view_clipboard_dimming_entries),
766                                               MODEST_WINDOW (self));
767
768         /* Insert dimming rules group for this window */
769         modest_ui_dimming_manager_insert_rules_group (parent_priv->ui_dimming_manager, menu_rules_group);
770         modest_ui_dimming_manager_insert_rules_group (parent_priv->ui_dimming_manager, toolbar_rules_group);
771         modest_ui_dimming_manager_insert_rules_group (parent_priv->ui_dimming_manager, clipboard_rules_group);
772         g_object_unref (menu_rules_group);
773         g_object_unref (toolbar_rules_group);
774         g_object_unref (clipboard_rules_group);
775
776         restore_settings (MODEST_MSG_VIEW_WINDOW(obj));
777         
778         /* g_signal_connect (G_OBJECT(obj), "delete-event", G_CALLBACK(on_delete_event), obj); */
779
780         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);
781         g_signal_connect (G_OBJECT(priv->msg_view), "activate_link",
782                           G_CALLBACK (modest_ui_actions_on_msg_link_clicked), obj);
783         g_signal_connect (G_OBJECT(priv->msg_view), "link_hover",
784                           G_CALLBACK (modest_ui_actions_on_msg_link_hover), obj);
785         g_signal_connect (G_OBJECT(priv->msg_view), "attachment_clicked",
786                           G_CALLBACK (modest_ui_actions_on_msg_attachment_clicked), obj);
787         g_signal_connect (G_OBJECT(priv->msg_view), "recpt_activated",
788                           G_CALLBACK (modest_ui_actions_on_msg_recpt_activated), obj);
789         g_signal_connect (G_OBJECT(priv->msg_view), "link_contextual",
790                           G_CALLBACK (modest_ui_actions_on_msg_link_contextual), obj);
791
792         g_signal_connect (G_OBJECT (obj), "key-release-event",
793                           G_CALLBACK (modest_msg_view_window_key_event),
794                           NULL);
795
796         g_signal_connect (G_OBJECT (obj), "key-press-event",
797                           G_CALLBACK (modest_msg_view_window_key_event),
798                           NULL);
799
800         g_signal_connect (G_OBJECT (obj), "window-state-event",
801                           G_CALLBACK (modest_msg_view_window_window_state_event),
802                           NULL);
803
804         g_signal_connect (G_OBJECT (obj), "move-focus",
805                           G_CALLBACK (on_move_focus), obj);
806
807         /* Mail Operation Queue */
808         priv->queue_change_handler = g_signal_connect (G_OBJECT (modest_runtime_get_mail_operation_queue ()),
809                                                        "queue-changed",
810                                                        G_CALLBACK (on_queue_changed),
811                                                        obj);
812
813         /* Account manager */
814         priv->account_removed_handler = g_signal_connect (G_OBJECT (modest_runtime_get_account_store ()),
815                                                           "account_removed",
816                                                           G_CALLBACK(on_account_removed),
817                                                           obj);
818
819         modest_window_set_active_account (MODEST_WINDOW(obj), modest_account_name);
820
821         g_signal_connect (G_OBJECT (priv->find_toolbar), "close", G_CALLBACK (modest_msg_view_window_find_toolbar_close), obj);
822         g_signal_connect (G_OBJECT (priv->find_toolbar), "search", G_CALLBACK (modest_msg_view_window_find_toolbar_search), obj);
823         priv->last_search = NULL;
824
825         /* Init the clipboard actions dim status */
826         modest_msg_view_grab_focus(MODEST_MSG_VIEW (priv->msg_view));
827
828         update_window_title (MODEST_MSG_VIEW_WINDOW (obj));
829
830         /* Check toolbar dimming rules */
831         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (obj));
832         modest_window_check_dimming_rules_group (MODEST_WINDOW (obj), "ModestClipboardDimmingRules");
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         mgr = modest_runtime_get_window_mgr ();
852         window = MODEST_MSG_VIEW_WINDOW (modest_window_mgr_get_msg_view_window (mgr));
853         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), NULL);
854
855         modest_msg_view_window_construct (window, modest_account_name, msg_uid);
856
857         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
858
859         /* Remember the message list's TreeModel so we can detect changes 
860          * and change the list selection when necessary: */
861
862         main_window = modest_window_mgr_get_main_window(mgr, FALSE); /* don't create */
863         if (!main_window) {
864                 g_warning ("%s: BUG: no main window", __FUNCTION__);
865                 return NULL;
866         }
867         
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         if (header_view != NULL){
872                 header_folder = modest_header_view_get_folder(header_view);
873                 g_assert(header_folder != NULL);
874                 priv->header_folder_id = tny_folder_get_id(header_folder);
875                 g_assert(priv->header_folder_id != NULL);
876                 g_object_unref(header_folder);
877         }
878
879         priv->header_model = g_object_ref(model);
880         priv->row_reference = gtk_tree_row_reference_copy (row_reference);
881         priv->next_row_reference = gtk_tree_row_reference_copy (row_reference);
882         select_next_valid_row (model, &(priv->next_row_reference), TRUE);
883
884         priv->row_changed_handler = g_signal_connect(
885                         GTK_TREE_MODEL(model), "row-changed",
886                         G_CALLBACK(modest_msg_view_window_on_row_changed),
887                         window);
888         priv->row_deleted_handler = g_signal_connect(
889                         GTK_TREE_MODEL(model), "row-deleted",
890                         G_CALLBACK(modest_msg_view_window_on_row_deleted),
891                         window);
892         priv->row_inserted_handler = g_signal_connect (
893                         GTK_TREE_MODEL(model), "row-inserted",
894                         G_CALLBACK(modest_msg_view_window_on_row_inserted),
895                         window);
896         priv->rows_reordered_handler = g_signal_connect(
897                         GTK_TREE_MODEL(model), "rows-reordered",
898                         G_CALLBACK(modest_msg_view_window_on_row_reordered),
899                         window);
900
901         if (header_view != NULL){
902                 modest_header_view_add_observer(header_view,
903                                 MODEST_HEADER_VIEW_OBSERVER(window));
904         }
905
906         tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
907         update_window_title (MODEST_MSG_VIEW_WINDOW (window));
908         gtk_widget_show_all (GTK_WIDGET (window));
909
910         modest_msg_view_window_update_priority (window);
911
912         /* Check toolbar dimming rules */
913         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (window));
914
915         return MODEST_WINDOW(window);
916 }
917
918 ModestWindow *
919 modest_msg_view_window_new_for_search_result (TnyMsg *msg, 
920                                               const gchar *modest_account_name,
921                                               const gchar *msg_uid)
922 {
923         ModestMsgViewWindow *window = NULL;
924         ModestMsgViewWindowPrivate *priv = NULL;
925         ModestWindowMgr *mgr = NULL;
926
927         mgr = modest_runtime_get_window_mgr ();
928         window = MODEST_MSG_VIEW_WINDOW (modest_window_mgr_get_msg_view_window (mgr));
929         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), NULL);
930         modest_msg_view_window_construct (window, modest_account_name, msg_uid);
931
932         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
933
934         /* Remember that this is a search result, 
935          * so we can disable some UI appropriately: */
936         priv->is_search_result = TRUE;
937
938         tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
939         update_window_title (window);
940
941         return MODEST_WINDOW(window);
942 }
943
944 ModestWindow *
945 modest_msg_view_window_new_for_attachment (TnyMsg *msg, 
946                             const gchar *modest_account_name,
947                             const gchar *msg_uid)
948 {
949         GObject *obj = NULL;
950         ModestMsgViewWindowPrivate *priv;       
951         ModestWindowMgr *mgr = NULL;
952
953         g_return_val_if_fail (msg, NULL);
954         mgr = modest_runtime_get_window_mgr ();
955         obj = G_OBJECT (modest_window_mgr_get_msg_view_window (mgr));
956         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (obj);
957         modest_msg_view_window_construct (MODEST_MSG_VIEW_WINDOW (obj), 
958                 modest_account_name, msg_uid);
959
960         tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
961         update_window_title (MODEST_MSG_VIEW_WINDOW (obj));
962
963         return MODEST_WINDOW(obj);
964 }
965
966 void modest_msg_view_window_on_row_changed(
967                 GtkTreeModel *header_model,
968                 GtkTreePath *arg1,
969                 GtkTreeIter *arg2,
970                 ModestMsgViewWindow *window){
971         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (window));
972 }
973
974 void modest_msg_view_window_on_row_deleted(
975                 GtkTreeModel *header_model,
976                 GtkTreePath *arg1,
977                 ModestMsgViewWindow *window){
978         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (window));
979 }
980
981 /* On insertions we check if the folder still has the message we are
982  * showing or do not. If do not, we do nothing. Which means we are still
983  * not attached to any header folder and thus next/prev buttons are
984  * still dimmed. Once the message that is shown by msg-view is found, the
985  * new model of header-view will be attached and the references will be set.
986  * On each further insertions dimming rules will be checked. However
987  * this requires extra CPU time at least works.
988  * (An message might be deleted from TnyFolder and thus will not be
989  * inserted into the model again for example if it is removed by the
990  * imap server and the header view is refreshed.)
991  */
992 void modest_msg_view_window_on_row_inserted(
993                 GtkTreeModel *new_model,
994                 GtkTreePath *tree_path,
995                 GtkTreeIter *tree_iter,
996                 ModestMsgViewWindow *window){
997         ModestMsgViewWindowPrivate *priv = NULL; 
998         TnyHeader *header = NULL;
999         gchar *uid = NULL;
1000
1001         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
1002
1003         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1004         
1005         /* If we already has a model attached then the message shown by
1006          * msg-view is in it, and thus we do not need any actions but
1007          * to check the dimming rules.*/
1008         if(priv->header_model != NULL){
1009                 gtk_tree_row_reference_free(priv->next_row_reference);
1010                 priv->next_row_reference = gtk_tree_row_reference_copy(
1011                                 priv->row_reference);
1012                 select_next_valid_row (priv->header_model,
1013                                 &(priv->next_row_reference), FALSE);
1014                 modest_ui_actions_check_toolbar_dimming_rules (
1015                                 MODEST_WINDOW (window));
1016                 return;
1017         }
1018
1019         /* Check if the newly inserted message is the same we are actually
1020          * showing. IF not, we should remain detached from the header model
1021          * and thus prev and next toolbarbuttons should remain dimmed. */
1022         gtk_tree_model_get (new_model, tree_iter, 
1023                         TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header, -1);
1024         uid = modest_tny_folder_get_header_unique_id(header);
1025         g_object_unref(G_OBJECT(header));
1026         header = NULL;
1027         if(!g_str_equal(priv->msg_uid, uid)){
1028                 g_free(uid);
1029                 return;
1030         }
1031         g_free(uid);
1032
1033         /* Setup row_reference for the actual msg. */
1034         priv->row_reference = gtk_tree_row_reference_new(
1035                         new_model, tree_path);
1036         if(priv->row_reference == NULL){
1037                 g_warning("No reference for msg header item.");
1038                 return;
1039         }
1040
1041         /* Attach new_model and connect some callback to it to become able
1042          * to detect changes in header-view. */
1043         priv->header_model = g_object_ref(new_model);
1044         g_signal_connect (new_model, "row-changed",
1045                         G_CALLBACK (modest_msg_view_window_on_row_changed),
1046                         window);
1047         g_signal_connect (new_model, "row-deleted",
1048                         G_CALLBACK (modest_msg_view_window_on_row_deleted),
1049                         window);
1050         g_signal_connect (new_model, "rows-reordered",
1051                         G_CALLBACK (modest_msg_view_window_on_row_reordered),
1052                         window);
1053
1054         /* Now set up next_row_reference. */
1055         priv->next_row_reference = gtk_tree_row_reference_copy(
1056                         priv->row_reference);
1057         select_next_valid_row (priv->header_model,
1058                         &(priv->next_row_reference), FALSE);
1059
1060         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (window));
1061 }
1062
1063 void modest_msg_view_window_on_row_reordered(
1064                 GtkTreeModel *header_model,
1065                 GtkTreePath *arg1,
1066                 GtkTreeIter *arg2,
1067                 gpointer arg3,
1068                 ModestMsgViewWindow *window){
1069         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (window));
1070 }
1071
1072 /* The modest_msg_view_window_update_model_replaced implements update
1073  * function for ModestHeaderViewObserver. Checks whether the TnyFolder
1074  * actually belongs to the header-view is the same as the TnyFolder of
1075  * the message of msg-view or not. If they are different, there is
1076  * nothing to do. If they are the same, then the model has replaced and
1077  * the reference in msg-view shall be replaced from the old model to
1078  * the new model. In this case the view will be detached from it's
1079  * header folder. From this point the next/prev buttons are dimmed.
1080  */
1081 void modest_msg_view_window_update_model_replaced(
1082                 ModestHeaderViewObserver *observer,
1083                 GtkTreeModel *model,
1084                 const gchar *tny_folder_id){
1085         ModestMsgViewWindowPrivate *priv = NULL; 
1086         ModestMsgViewWindow *window = NULL;
1087
1088         g_assert(MODEST_IS_HEADER_VIEW_OBSERVER(observer));
1089         g_assert(MODEST_IS_MSG_VIEW_WINDOW(observer));
1090
1091         window = MODEST_MSG_VIEW_WINDOW(observer);
1092         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(window);
1093
1094         /* If there is an other folder in the header-view then we do
1095          * not care about it's model (msg list). Else if the
1096          * header-view shows the folder the msg shown by us is in, we
1097          * shall replace our model reference and make some check. */
1098         if(tny_folder_id == NULL || !g_str_equal(tny_folder_id, priv->header_folder_id))
1099                 return;
1100         
1101         /* Model is changed(replaced), so we should forget the old
1102          * one. Because there might be other references and there
1103          * might be some change on the model even if we unreferenced
1104          * it, we need to disconnect our signals here. */
1105         if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
1106                                            priv->row_changed_handler))
1107                 g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
1108                                              priv->row_changed_handler);
1109         priv->row_changed_handler = 0;
1110         if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
1111                                            priv->row_deleted_handler))
1112                 g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
1113                                              priv->row_deleted_handler);
1114         priv->row_deleted_handler = 0;
1115         if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
1116                                            priv->row_inserted_handler))
1117                 g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
1118                                              priv->row_inserted_handler);
1119         priv->row_inserted_handler = 0;
1120         if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
1121                                            priv->rows_reordered_handler))
1122                 g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
1123                                              priv->rows_reordered_handler);
1124         priv->rows_reordered_handler = 0;
1125         g_object_unref(priv->header_model);
1126         priv->header_model = NULL;
1127         g_object_unref(priv->row_reference);
1128         priv->row_reference = NULL;
1129         g_object_unref(priv->next_row_reference);
1130         priv->next_row_reference = NULL;
1131
1132         modest_ui_actions_check_toolbar_dimming_rules(MODEST_WINDOW(window));
1133
1134         g_assert(model != NULL);
1135
1136         /* Also we must connect to the new model for row insertions.
1137          * Only for insertions now. We will need other ones only after
1138          * the msg is show by msg-view is added to the new model. */
1139         priv->row_inserted_handler = g_signal_connect (
1140                         model, "row-inserted",
1141                         G_CALLBACK(modest_msg_view_window_on_row_inserted),
1142                         window);
1143 }
1144
1145 gboolean 
1146 modest_msg_view_window_toolbar_on_transfer_mode     (ModestMsgViewWindow *self)
1147 {
1148         ModestMsgViewWindowPrivate *priv= NULL; 
1149
1150         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), FALSE);
1151         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
1152
1153         return priv->current_toolbar_mode == TOOLBAR_MODE_TRANSFER;
1154 }
1155
1156 TnyHeader*
1157 modest_msg_view_window_get_header (ModestMsgViewWindow *self)
1158 {
1159         ModestMsgViewWindowPrivate *priv= NULL; 
1160         TnyMsg *msg = NULL;
1161         TnyHeader *header = NULL;
1162         GtkTreePath *path = NULL;
1163         GtkTreeIter iter;
1164
1165         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), NULL);
1166         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
1167
1168         /* If the message was not obtained from a treemodel,
1169          * for instance if it was opened directly by the search UI:
1170          */
1171         if (priv->header_model == NULL) {
1172                 msg = modest_msg_view_window_get_message (self);
1173                 if (msg) {
1174                         header = tny_msg_get_header (msg);
1175                         g_object_unref (msg);
1176                 }
1177                 return header;
1178         }
1179
1180         /* Get iter of the currently selected message in the header view: */
1181         /* TODO: Why not just give this window a ref of the TnyHeader or TnyMessage,
1182          * instead of sometimes retrieving it from the header view?
1183          * Then we wouldn't be dependent on the message actually still being selected 
1184          * in the header view. murrayc. */
1185         if (!gtk_tree_row_reference_valid (priv->row_reference))
1186                 return NULL;
1187         path = gtk_tree_row_reference_get_path (priv->row_reference);
1188         g_return_val_if_fail (path != NULL, NULL);
1189         gtk_tree_model_get_iter (priv->header_model, 
1190                                  &iter, 
1191                                  path);
1192
1193         /* Get current message header */
1194         gtk_tree_model_get (priv->header_model, &iter, 
1195                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, 
1196                             &header, -1);
1197
1198         gtk_tree_path_free (path);
1199         return header;
1200 }
1201
1202 TnyMsg*
1203 modest_msg_view_window_get_message (ModestMsgViewWindow *self)
1204 {
1205         ModestMsgViewWindowPrivate *priv;
1206         
1207         g_return_val_if_fail (self, NULL);
1208         
1209         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
1210         
1211         return tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
1212 }
1213
1214 const gchar*
1215 modest_msg_view_window_get_message_uid (ModestMsgViewWindow *self)
1216 {
1217         ModestMsgViewWindowPrivate *priv;
1218
1219         g_return_val_if_fail (self, NULL);
1220         
1221         priv  = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
1222
1223         return (const gchar*) priv->msg_uid;
1224 }
1225
1226 static void 
1227 modest_msg_view_window_toggle_find_toolbar (GtkToggleAction *toggle,
1228                                             gpointer data)
1229 {
1230         ModestMsgViewWindow *window = MODEST_MSG_VIEW_WINDOW (data);
1231         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1232         ModestWindowPrivate *parent_priv = MODEST_WINDOW_GET_PRIVATE (window);
1233         gboolean is_active;
1234         GtkAction *action;
1235
1236         is_active = gtk_toggle_action_get_active (toggle);
1237
1238         if (is_active) {
1239                 gtk_widget_show (priv->find_toolbar);
1240                 hildon_find_toolbar_highlight_entry (HILDON_FIND_TOOLBAR (priv->find_toolbar), TRUE);
1241         } else {
1242                 gtk_widget_hide (priv->find_toolbar);
1243         }
1244
1245         /* update the toggle buttons status */
1246         action = gtk_ui_manager_get_action (parent_priv->ui_manager, "/ToolBar/FindInMessage");
1247         modest_maemo_toggle_action_set_active_block_notify (GTK_TOGGLE_ACTION (action), is_active);
1248         action = gtk_ui_manager_get_action (parent_priv->ui_manager, "/MenuBar/ToolsMenu/ToolsFindInMessageMenu");
1249         modest_maemo_toggle_action_set_active_block_notify (GTK_TOGGLE_ACTION (action), is_active);
1250         
1251 }
1252
1253 static void
1254 modest_msg_view_window_find_toolbar_close (GtkWidget *widget,
1255                                            ModestMsgViewWindow *obj)
1256 {
1257         GtkToggleAction *toggle;
1258         ModestWindowPrivate *parent_priv;
1259         parent_priv = MODEST_WINDOW_GET_PRIVATE (obj);
1260         
1261         toggle = GTK_TOGGLE_ACTION (gtk_ui_manager_get_action (parent_priv->ui_manager, "/ToolBar/FindInMessage"));
1262         gtk_toggle_action_set_active (toggle, FALSE);
1263 }
1264
1265 static void
1266 modest_msg_view_window_find_toolbar_search (GtkWidget *widget,
1267                                            ModestMsgViewWindow *obj)
1268 {
1269         gchar *current_search;
1270         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (obj);
1271
1272         if (modest_mime_part_view_is_empty (MODEST_MIME_PART_VIEW (priv->msg_view))) {
1273                 hildon_banner_show_information (NULL, NULL, _("mail_ib_nothing_to_find"));
1274                 return;
1275         }
1276
1277         g_object_get (G_OBJECT (widget), "prefix", &current_search, NULL);
1278
1279         if ((current_search == NULL) || (strcmp (current_search, "") == 0)) {
1280                 g_free (current_search);
1281                 hildon_banner_show_information (NULL, NULL, dgettext("hildon-common-strings", "ecdg_ib_find_rep_enter_text"));
1282                 return;
1283         }
1284
1285         if ((priv->last_search == NULL) || (strcmp (priv->last_search, current_search) != 0)) {
1286                 gboolean result;
1287                 g_free (priv->last_search);
1288                 priv->last_search = g_strdup (current_search);
1289                 result = modest_isearch_view_search (MODEST_ISEARCH_VIEW (priv->msg_view),
1290                                                      priv->last_search);
1291                 if (!result) {
1292                         hildon_banner_show_information (NULL, NULL, dgettext("hildon-libs", "ckct_ib_find_no_matches"));
1293                         g_free (priv->last_search);
1294                         priv->last_search = NULL;
1295                 } else {
1296                         modest_msg_view_grab_focus (MODEST_MSG_VIEW (priv->msg_view));
1297                 }
1298         } else {
1299                 if (!modest_isearch_view_search_next (MODEST_ISEARCH_VIEW (priv->msg_view))) {
1300                         hildon_banner_show_information (NULL, NULL, dgettext("hildon-libs", "ckct_ib_find_search_complete"));
1301                         g_free (priv->last_search);
1302                         priv->last_search = NULL;
1303                 } else {
1304                         modest_msg_view_grab_focus (MODEST_MSG_VIEW (priv->msg_view));
1305                 }
1306         }
1307         
1308         g_free (current_search);
1309                 
1310 }
1311
1312 static void
1313 modest_msg_view_window_set_zoom (ModestWindow *window,
1314                                  gdouble zoom)
1315 {
1316         ModestMsgViewWindowPrivate *priv;
1317         ModestWindowPrivate *parent_priv;
1318         GtkAction *action = NULL;
1319         gint int_zoom = (gint) rint (zoom*100.0+0.1);
1320      
1321         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
1322
1323         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1324         parent_priv = MODEST_WINDOW_GET_PRIVATE (window);
1325         modest_zoomable_set_zoom (MODEST_ZOOMABLE (priv->msg_view), zoom);
1326
1327         action = gtk_ui_manager_get_action (parent_priv->ui_manager, 
1328                                             "/MenuBar/ViewMenu/ZoomMenu/Zoom50Menu");
1329
1330         gtk_radio_action_set_current_value (GTK_RADIO_ACTION (action), int_zoom);
1331 }
1332
1333 static gdouble
1334 modest_msg_view_window_get_zoom (ModestWindow *window)
1335 {
1336         ModestMsgViewWindowPrivate *priv;
1337      
1338         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), 1.0);
1339
1340         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1341         return modest_zoomable_get_zoom (MODEST_ZOOMABLE (priv->msg_view));
1342 }
1343
1344 static gboolean
1345 modest_msg_view_window_zoom_plus (ModestWindow *window)
1346 {
1347         ModestWindowPrivate *parent_priv;
1348         GtkRadioAction *zoom_radio_action;
1349         GSList *group, *node;
1350
1351         parent_priv = MODEST_WINDOW_GET_PRIVATE (window);
1352         zoom_radio_action = GTK_RADIO_ACTION (gtk_ui_manager_get_action (parent_priv->ui_manager, 
1353                                                                          "/MenuBar/ViewMenu/ZoomMenu/Zoom50Menu"));
1354
1355         group = gtk_radio_action_get_group (zoom_radio_action);
1356
1357         if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (group->data))) {
1358                 hildon_banner_show_information (NULL, NULL, dgettext("hildon-common-strings", "ckct_ib_max_zoom_level_reached"));
1359                 return FALSE;
1360         }
1361
1362         for (node = group; node != NULL; node = g_slist_next (node)) {
1363                 if ((node->next != NULL) && gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (node->next->data))) {
1364                         gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (node->data), TRUE);
1365                         return TRUE;
1366                 }
1367         }
1368         return FALSE;
1369 }
1370
1371 static gboolean
1372 modest_msg_view_window_zoom_minus (ModestWindow *window)
1373 {
1374         ModestWindowPrivate *parent_priv;
1375         GtkRadioAction *zoom_radio_action;
1376         GSList *group, *node;
1377
1378         parent_priv = MODEST_WINDOW_GET_PRIVATE (window);
1379         zoom_radio_action = GTK_RADIO_ACTION (gtk_ui_manager_get_action (parent_priv->ui_manager, 
1380                                                                          "/MenuBar/ViewMenu/ZoomMenu/Zoom50Menu"));
1381
1382         group = gtk_radio_action_get_group (zoom_radio_action);
1383
1384         for (node = group; node != NULL; node = g_slist_next (node)) {
1385                 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (node->data))) {
1386                         if (node->next != NULL) {
1387                                 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (node->next->data), TRUE);
1388                                 return TRUE;
1389                         } else {
1390                           hildon_banner_show_information (NULL, NULL, dgettext("hildon-common-strings", "ckct_ib_min_zoom_level_reached"));
1391                                 return FALSE;
1392                         }
1393                         break;
1394                 }
1395         }
1396         return FALSE;
1397 }
1398
1399 static gboolean
1400 modest_msg_view_window_key_event (GtkWidget *window,
1401                                   GdkEventKey *event,
1402                                   gpointer userdata)
1403 {
1404         
1405         if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up ||
1406             event->keyval == GDK_Down || event->keyval == GDK_KP_Down ||
1407             event->keyval == GDK_Page_Up || event->keyval == GDK_KP_Page_Up ||
1408             event->keyval == GDK_Page_Down || event->keyval == GDK_KP_Page_Down ||
1409             event->keyval == GDK_Home || event->keyval == GDK_KP_Home ||
1410             event->keyval == GDK_End || event->keyval == GDK_KP_End) {
1411                 ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1412                 gboolean return_value;
1413
1414                 if (event->type == GDK_KEY_RELEASE) {
1415                         GtkScrollType scroll_type;
1416                         
1417                         switch (event->keyval) {
1418                         case GDK_Up: 
1419                         case GDK_KP_Up:
1420                                 scroll_type = GTK_SCROLL_STEP_UP; break;
1421                         case GDK_Down: 
1422                         case GDK_KP_Down:
1423                                 scroll_type = GTK_SCROLL_STEP_DOWN; break;
1424                         case GDK_Page_Up:
1425                         case GDK_KP_Page_Up:
1426                                 scroll_type = GTK_SCROLL_PAGE_UP; break;
1427                         case GDK_Page_Down:
1428                         case GDK_KP_Page_Down:
1429                                 scroll_type = GTK_SCROLL_PAGE_DOWN; break;
1430                         case GDK_Home:
1431                         case GDK_KP_Home:
1432                                 scroll_type = GTK_SCROLL_START; break;
1433                         case GDK_End:
1434                         case GDK_KP_End:
1435                                 scroll_type = GTK_SCROLL_END; break;
1436                         default: scroll_type = GTK_SCROLL_NONE;
1437                         }
1438                         
1439                         g_signal_emit_by_name (G_OBJECT (priv->main_scroll), "scroll-child", 
1440                                                scroll_type, FALSE, &return_value);
1441                         return TRUE;
1442                 } else {
1443                         return FALSE;
1444                 }
1445         } else {
1446                 return FALSE;
1447         }
1448 }
1449
1450 gboolean
1451 modest_msg_view_window_last_message_selected (ModestMsgViewWindow *window)
1452 {
1453         GtkTreePath *path;
1454         ModestMsgViewWindowPrivate *priv;
1455         GtkTreeIter tmp_iter;
1456         gboolean is_last_selected;
1457
1458         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), TRUE);
1459         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1460
1461         /*if no model (so no rows at all), then virtually we are the last*/
1462         if (!priv->header_model)
1463                 return TRUE;
1464
1465         path = gtk_tree_row_reference_get_path (priv->row_reference);
1466         if (path == NULL)
1467                 return TRUE;
1468
1469         is_last_selected = TRUE;
1470         while (is_last_selected) {
1471                 TnyHeader *header;
1472                 gtk_tree_path_next (path);
1473                 if (!gtk_tree_model_get_iter (priv->header_model, &tmp_iter, path))
1474                         break;
1475                 gtk_tree_model_get (priv->header_model, &tmp_iter,
1476                                 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1477                                 &header, -1);
1478                 if (header) {
1479                         if (!(tny_header_get_flags(header) & TNY_HEADER_FLAG_DELETED))
1480                                 is_last_selected = FALSE;
1481                         g_object_unref(G_OBJECT(header));
1482                 }
1483         }
1484         gtk_tree_path_free (path);
1485         return is_last_selected;
1486 }
1487
1488 gboolean
1489 modest_msg_view_window_has_headers_model (ModestMsgViewWindow *window)
1490 {
1491         ModestMsgViewWindowPrivate *priv;
1492
1493         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), TRUE);
1494         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1495
1496         return priv->header_model != NULL;
1497 }
1498
1499 gboolean
1500 modest_msg_view_window_is_search_result (ModestMsgViewWindow *window)
1501 {
1502         ModestMsgViewWindowPrivate *priv;
1503
1504         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), TRUE);
1505         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1506
1507         return priv->is_search_result;
1508 }
1509
1510 gboolean
1511 modest_msg_view_window_first_message_selected (ModestMsgViewWindow *window)
1512 {
1513         GtkTreePath *path;
1514         ModestMsgViewWindowPrivate *priv;
1515         gboolean is_first_selected;
1516         GtkTreeIter tmp_iter;
1517 /*      gchar * path_string;*/
1518
1519         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), TRUE);
1520         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1521
1522         /*if no model (so no rows at all), then virtually we are the first*/
1523         if (!priv->header_model)
1524                 return TRUE;
1525
1526         path = gtk_tree_row_reference_get_path (priv->row_reference);
1527         if (!path)
1528                 return TRUE;
1529
1530 /*      path_string = gtk_tree_path_to_string (path);
1531         is_first_selected = strcmp (path_string, "0");
1532
1533         g_free (path_string);
1534         gtk_tree_path_free (path);
1535
1536         return is_first_selected;*/
1537
1538         is_first_selected = TRUE;
1539         while (is_first_selected) {
1540                 TnyHeader *header;
1541                 if(!gtk_tree_path_prev (path))
1542                         break;
1543                 /* Here the 'if' is needless for logic, but let make sure
1544                  * iter is valid for gtk_tree_model_get. */
1545                 if (!gtk_tree_model_get_iter (priv->header_model, &tmp_iter, path))
1546                         break;
1547                 gtk_tree_model_get (priv->header_model, &tmp_iter,
1548                                 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1549                                 &header, -1);
1550                 if (header) {
1551                         if (!(tny_header_get_flags(header) & TNY_HEADER_FLAG_DELETED))
1552                                 is_first_selected = FALSE;
1553                         g_object_unref(G_OBJECT(header));
1554                 }
1555         }
1556         gtk_tree_path_free (path);
1557         return is_first_selected;
1558 }
1559
1560 /**
1561  * Reads the message whose summary item is @header. It takes care of
1562  * several things, among others:
1563  *
1564  * If the message was not previously downloaded then ask the user
1565  * before downloading. If there is no connection launch the connection
1566  * dialog. Update toolbar dimming rules.
1567  *
1568  * Returns: TRUE if the mail operation was started, otherwise if the
1569  * user do not want to download the message, or if the user do not
1570  * want to connect, then the operation is not issued
1571  **/
1572 static gboolean
1573 message_reader (ModestMsgViewWindow *window,
1574                 ModestMsgViewWindowPrivate *priv,
1575                 TnyHeader *header,
1576                 GtkTreeRowReference *row_reference)
1577 {
1578         ModestMailOperation *mail_op = NULL;
1579         gboolean already_showing = FALSE;
1580         ModestWindow *msg_window = NULL;
1581         ModestWindowMgr *mgr;
1582
1583         g_return_val_if_fail (row_reference != NULL, FALSE);
1584
1585         mgr = modest_runtime_get_window_mgr ();
1586         already_showing = modest_window_mgr_find_registered_header (mgr, header, &msg_window);
1587         if (already_showing && (msg_window != MODEST_WINDOW (window))) {
1588                 gboolean retval;
1589                 if (msg_window)
1590                         gtk_window_present (GTK_WINDOW (msg_window));
1591                 g_signal_emit_by_name (G_OBJECT (window), "delete-event", NULL, &retval);
1592                 return TRUE;
1593         }
1594
1595         /* Msg download completed */
1596         if (!(tny_header_get_flags (header) & TNY_HEADER_FLAG_CACHED)) {
1597                 /* Ask the user if he wants to download the message if
1598                    we're not online */
1599                 if (!tny_device_is_online (modest_runtime_get_device())) {
1600                         TnyFolder *folder = NULL;
1601                         GtkResponseType response;
1602
1603                         response = modest_platform_run_confirmation_dialog (GTK_WINDOW (window),
1604                                                                             _("mcen_nc_get_msg"));
1605                         if (response == GTK_RESPONSE_CANCEL)
1606                                 return FALSE;
1607                 
1608                         /* Offer the connection dialog if necessary */
1609                         folder = tny_header_get_folder (header);
1610                         if (folder) {
1611                                 if (!modest_platform_connect_and_wait_if_network_folderstore (NULL, 
1612                                                                                               TNY_FOLDER_STORE (folder))) {
1613                                         g_object_unref (folder);
1614                                         return FALSE;
1615                                 }
1616                                 g_object_unref (folder);
1617                         }
1618                 }
1619         }
1620
1621         /* New mail operation */
1622         mail_op = modest_mail_operation_new_with_error_handling (G_OBJECT(window),
1623                                                                  modest_ui_actions_get_msgs_full_error_handler, 
1624                                                                  NULL, NULL);
1625                                 
1626         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (), mail_op);
1627         modest_mail_operation_get_msg (mail_op, header, view_msg_cb, row_reference);
1628         g_object_unref (mail_op);
1629
1630         /* Update toolbar dimming rules */
1631         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (window));
1632
1633         return TRUE;
1634 }
1635
1636 gboolean        
1637 modest_msg_view_window_select_next_message (ModestMsgViewWindow *window)
1638 {
1639         ModestMsgViewWindowPrivate *priv;
1640         GtkTreePath *path= NULL;
1641         GtkTreeIter tmp_iter;
1642         TnyHeader *header;
1643         gboolean retval = TRUE;
1644         GtkTreeRowReference *row_reference = NULL;
1645
1646         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), FALSE);
1647         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1648
1649         /* Update the next row reference if it's not valid. This could
1650            happen if for example the header which it was pointing to,
1651            was deleted. The best place to do it is in the row-deleted
1652            handler but the tinymail model do not work like the glib
1653            tree models and reports the deletion when the row is still
1654            there */
1655         if (!gtk_tree_row_reference_valid (priv->next_row_reference)) {
1656                 if (gtk_tree_row_reference_valid (priv->row_reference)) {
1657                         priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
1658                         select_next_valid_row (priv->header_model, &(priv->next_row_reference), FALSE);
1659                 }
1660         }
1661         if (priv->next_row_reference)
1662                 path = gtk_tree_row_reference_get_path (priv->next_row_reference);
1663         if (path == NULL)
1664                 return FALSE;
1665
1666         row_reference = gtk_tree_row_reference_copy (priv->next_row_reference);
1667
1668         gtk_tree_model_get_iter (priv->header_model,
1669                                  &tmp_iter,
1670                                  path);
1671         gtk_tree_path_free (path);
1672
1673         gtk_tree_model_get (priv->header_model, &tmp_iter, 
1674                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1675                             &header, -1);
1676         
1677         /* Read the message & show it */
1678         if (!message_reader (window, priv, header, row_reference)) {
1679                 retval = FALSE;
1680                 gtk_tree_row_reference_free (row_reference);
1681         }
1682
1683         /* Free */
1684         g_object_unref (header);
1685
1686         return retval;          
1687 }
1688
1689 gboolean 
1690 modest_msg_view_window_select_first_message (ModestMsgViewWindow *self)
1691 {
1692         ModestMsgViewWindowPrivate *priv = NULL;
1693         TnyHeader *header = NULL;
1694         GtkTreeIter iter;
1695         GtkTreePath *path = NULL;
1696         GtkTreeRowReference *row_reference = NULL;
1697
1698         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), FALSE);
1699         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
1700
1701         /* Check that the model is not empty */
1702         if (!gtk_tree_model_get_iter_first (priv->header_model, &iter))
1703                 return FALSE;
1704
1705         /* Get the header */
1706         gtk_tree_model_get (priv->header_model, 
1707                             &iter, 
1708                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1709                             &header, -1);
1710         g_return_val_if_fail (TNY_IS_HEADER (header), FALSE);
1711         if (tny_header_get_flags (header) & TNY_HEADER_FLAG_DELETED) {
1712                 g_object_unref (header);
1713                 return modest_msg_view_window_select_next_message (self);
1714         }
1715         
1716         path = gtk_tree_model_get_path (priv->header_model, &iter);
1717         row_reference = gtk_tree_row_reference_new (priv->header_model, path);
1718         gtk_tree_path_free (path);
1719
1720         /* Read the message & show it */
1721         message_reader (self, priv, header, row_reference);
1722         
1723         /* Free */
1724         g_object_unref (header);
1725
1726         return TRUE;
1727 }
1728  
1729 gboolean        
1730 modest_msg_view_window_select_previous_message (ModestMsgViewWindow *window)
1731 {
1732         ModestMsgViewWindowPrivate *priv = NULL;
1733         GtkTreePath *path;
1734         GtkTreeRowReference *row_reference = NULL;
1735
1736         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), FALSE);
1737         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1738
1739         /* Return inmediatly if there is no header model */
1740         if (!priv->header_model)
1741                 return FALSE;
1742
1743         path = gtk_tree_row_reference_get_path (priv->row_reference);
1744         while (gtk_tree_path_prev (path)) {
1745                 TnyHeader *header;
1746                 GtkTreeIter iter;
1747
1748                 gtk_tree_model_get_iter (priv->header_model, &iter, path);
1749                 gtk_tree_model_get (priv->header_model, &iter, 
1750                                     TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1751                                     &header, -1);
1752                 if (!header)
1753                         break;
1754                 if (tny_header_get_flags (header) & TNY_HEADER_FLAG_DELETED) {
1755                         g_object_unref (header);
1756                         continue;
1757                 }
1758
1759                 row_reference = gtk_tree_row_reference_new (priv->header_model, path);
1760                 /* Read the message & show it */
1761                 if (!message_reader (window, priv, header, row_reference)) {
1762                         gtk_tree_row_reference_free (row_reference);
1763                         g_object_unref (header);
1764                         break;
1765                 }
1766
1767                 gtk_tree_path_free (path);
1768                 g_object_unref (header);
1769
1770                 return TRUE;
1771         }
1772
1773         gtk_tree_path_free (path);
1774         return FALSE;
1775 }
1776
1777 static void
1778 view_msg_cb (ModestMailOperation *mail_op, 
1779              TnyHeader *header, 
1780              gboolean canceled,
1781              TnyMsg *msg, 
1782              GError *error,
1783              gpointer user_data)
1784 {
1785         ModestMsgViewWindow *self = NULL;
1786         ModestMsgViewWindowPrivate *priv = NULL;
1787         GtkTreeRowReference *row_reference = NULL;
1788
1789         /* If there was any error */
1790         row_reference = (GtkTreeRowReference *) user_data;
1791         if (!modest_ui_actions_msg_retrieval_check (mail_op, header, msg)) {
1792                 gtk_tree_row_reference_free (row_reference);                    
1793                 return;
1794         }
1795
1796         /* Get the window */ 
1797         self = (ModestMsgViewWindow *) modest_mail_operation_get_source (mail_op);
1798         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self));
1799         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
1800
1801         /* Update the row reference */
1802         gtk_tree_row_reference_free (priv->row_reference);
1803         priv->row_reference = gtk_tree_row_reference_copy (row_reference);
1804         priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
1805         select_next_valid_row (priv->header_model, &(priv->next_row_reference), TRUE);
1806         gtk_tree_row_reference_free (row_reference);
1807
1808         /* Mark header as read */
1809         if (!(tny_header_get_flags (header) & TNY_HEADER_FLAG_SEEN))
1810                 tny_header_set_flag (header, TNY_HEADER_FLAG_SEEN);
1811
1812         /* Set new message */
1813         tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
1814         modest_msg_view_window_update_priority (self);
1815         update_window_title (MODEST_MSG_VIEW_WINDOW (self));
1816         modest_msg_view_grab_focus (MODEST_MSG_VIEW (priv->msg_view));
1817
1818         /* Set the new message uid of the window  */
1819         if (priv->msg_uid) {
1820                 g_free (priv->msg_uid);
1821                 priv->msg_uid = modest_tny_folder_get_header_unique_id (header);
1822         }
1823
1824         /* Notify the observers */
1825         g_signal_emit (G_OBJECT (self), signals[MSG_CHANGED_SIGNAL], 
1826                        0, priv->header_model, priv->row_reference);
1827
1828         /* Free new references */
1829         g_object_unref (self);
1830 }
1831
1832 TnyFolderType
1833 modest_msg_view_window_get_folder_type (ModestMsgViewWindow *window)
1834 {
1835         ModestMsgViewWindowPrivate *priv;
1836         TnyMsg *msg;
1837         TnyFolderType folder_type;
1838
1839         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1840
1841         folder_type = TNY_FOLDER_TYPE_UNKNOWN;
1842
1843         msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
1844         if (msg) {
1845                 TnyFolder *folder;
1846
1847                 folder = tny_msg_get_folder (msg);
1848                 if (folder) {
1849                         folder_type = tny_folder_get_folder_type (folder);
1850                         g_object_unref (folder);
1851                 }
1852                 g_object_unref (msg);
1853         }
1854
1855         return folder_type;
1856 }
1857
1858
1859 static void
1860 modest_msg_view_window_update_priority (ModestMsgViewWindow *window)
1861 {
1862         ModestMsgViewWindowPrivate *priv;
1863         TnyHeaderFlags flags = 0;
1864
1865         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1866
1867         if (priv->header_model) {
1868                 TnyHeader *header;
1869                 GtkTreeIter iter;
1870                 GtkTreePath *path = NULL;
1871
1872                 path = gtk_tree_row_reference_get_path (priv->row_reference);
1873                 g_return_if_fail (path != NULL);
1874                 gtk_tree_model_get_iter (priv->header_model, 
1875                                          &iter, 
1876                                          gtk_tree_row_reference_get_path (priv->row_reference));
1877
1878                 gtk_tree_model_get (priv->header_model, &iter, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1879                                     &header, -1);
1880                 if (header) {
1881                         flags = tny_header_get_flags (header);
1882                         g_object_unref(G_OBJECT(header));
1883                 }
1884                 gtk_tree_path_free (path);
1885         }
1886
1887         modest_msg_view_set_priority (MODEST_MSG_VIEW(priv->msg_view), flags);
1888
1889 }
1890
1891 static gboolean
1892 modest_msg_view_window_window_state_event (GtkWidget *widget, GdkEventWindowState *event, gpointer userdata)
1893 {
1894         if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) {
1895                 ModestWindowPrivate *parent_priv;
1896                 ModestWindowMgr *mgr;
1897                 gboolean is_fullscreen;
1898                 GtkAction *fs_toggle_action;
1899                 gboolean active;
1900
1901                 mgr = modest_runtime_get_window_mgr ();
1902                 is_fullscreen = (modest_window_mgr_get_fullscreen_mode (mgr))?1:0;
1903
1904                 parent_priv = MODEST_WINDOW_GET_PRIVATE (widget);
1905                 
1906                 fs_toggle_action = gtk_ui_manager_get_action (parent_priv->ui_manager, "/MenuBar/ViewMenu/ViewToggleFullscreenMenu");
1907                 active = (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (fs_toggle_action)))?1:0;
1908                 if (is_fullscreen != active) {
1909                         gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (fs_toggle_action), is_fullscreen);
1910                 }
1911         }
1912
1913         return FALSE;
1914
1915 }
1916
1917 static void
1918 set_homogeneous (GtkWidget *widget,
1919                  gpointer data)
1920 {
1921         if (GTK_IS_TOOL_ITEM (widget)) {
1922                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (widget), TRUE);
1923                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (widget), TRUE);
1924         }
1925 }
1926
1927 static void
1928 modest_msg_view_window_show_toolbar (ModestWindow *self,
1929                                      gboolean show_toolbar)
1930 {
1931         ModestMsgViewWindowPrivate *priv = NULL;
1932         ModestWindowPrivate *parent_priv;
1933         GtkWidget *reply_button = NULL, *menu = NULL;
1934         GtkWidget *placeholder = NULL;
1935         gint insert_index;
1936         const gchar *action_name;
1937         GtkAction *action;
1938         
1939         parent_priv = MODEST_WINDOW_GET_PRIVATE(self);
1940         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
1941
1942         /* Set optimized view status */
1943         priv->optimized_view = !show_toolbar;
1944
1945         if (!parent_priv->toolbar) {
1946                 parent_priv->toolbar = gtk_ui_manager_get_widget (parent_priv->ui_manager, 
1947                                                                   "/ToolBar");
1948                 gtk_widget_set_no_show_all (parent_priv->toolbar, TRUE);
1949
1950                 /* Set homogeneous toolbar */
1951                 gtk_container_foreach (GTK_CONTAINER (parent_priv->toolbar), 
1952                                        set_homogeneous, NULL);
1953
1954                 priv->progress_toolitem = GTK_WIDGET (gtk_tool_item_new ());
1955                 priv->cancel_toolitem = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarCancel");
1956                 priv->next_toolitem = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageNext");
1957                 priv->prev_toolitem = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageBack");
1958                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->progress_toolitem), TRUE);
1959                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (priv->progress_toolitem), TRUE);
1960                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (priv->cancel_toolitem), FALSE);
1961                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->cancel_toolitem), FALSE);
1962
1963                 /* Add ProgressBar (Transfer toolbar) */ 
1964                 priv->progress_bar = modest_progress_bar_widget_new ();
1965                 gtk_widget_set_no_show_all (priv->progress_bar, TRUE);
1966                 placeholder = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ProgressbarView");
1967                 insert_index = gtk_toolbar_get_item_index(GTK_TOOLBAR (parent_priv->toolbar), GTK_TOOL_ITEM(placeholder));
1968                 gtk_container_add (GTK_CONTAINER (priv->progress_toolitem), priv->progress_bar);
1969                 gtk_toolbar_insert(GTK_TOOLBAR(parent_priv->toolbar), GTK_TOOL_ITEM (priv->progress_toolitem), insert_index);
1970                 
1971                 /* Connect cancel 'clicked' signal to abort progress mode */
1972                 g_signal_connect(priv->cancel_toolitem, "clicked",
1973                                  G_CALLBACK(cancel_progressbar),
1974                                  self);
1975                 
1976                 /* Add it to the observers list */
1977                 priv->progress_widgets = g_slist_prepend(priv->progress_widgets, priv->progress_bar);
1978
1979                 /* Add to window */
1980                 hildon_window_add_toolbar (HILDON_WINDOW (self), 
1981                                            GTK_TOOLBAR (parent_priv->toolbar));
1982
1983                 /* Set reply button tap and hold menu */        
1984                 reply_button = gtk_ui_manager_get_widget (parent_priv->ui_manager, 
1985                                                           "/ToolBar/ToolbarMessageReply");
1986                 menu = gtk_ui_manager_get_widget (parent_priv->ui_manager, 
1987                                                   "/ToolbarReplyCSM");
1988                 gtk_widget_tap_and_hold_setup (GTK_WIDGET (reply_button), menu, NULL, 0);
1989         }
1990
1991         if (show_toolbar) {
1992                 /* Quick hack: this prevents toolbar icons "dance" when progress bar show status is changed */ 
1993                 /* TODO: resize mode migth be GTK_RESIZE_QUEUE, in order to avoid unneccesary shows */
1994                 gtk_container_set_resize_mode (GTK_CONTAINER(parent_priv->toolbar), GTK_RESIZE_IMMEDIATE);
1995
1996                 gtk_widget_show (GTK_WIDGET (parent_priv->toolbar));
1997                 if (modest_msg_view_window_transfer_mode_enabled (MODEST_MSG_VIEW_WINDOW (self))) 
1998                         set_toolbar_mode (MODEST_MSG_VIEW_WINDOW (self), TOOLBAR_MODE_TRANSFER);
1999                 else
2000                         set_toolbar_mode (MODEST_MSG_VIEW_WINDOW (self), TOOLBAR_MODE_NORMAL);
2001
2002         } else {
2003                 gtk_widget_set_no_show_all (parent_priv->toolbar, TRUE);
2004                 gtk_widget_hide (GTK_WIDGET (parent_priv->toolbar));
2005         }
2006
2007         /* Update also the actions (to update the toggles in the
2008            menus), we have to do it manually because some other window
2009            of the same time could have changed it (remember that the
2010            toolbar fullscreen mode is shared by all the windows of the
2011            same type */
2012         if (modest_window_mgr_get_fullscreen_mode (modest_runtime_get_window_mgr ()))
2013                 action_name = "/MenuBar/ViewMenu/ViewShowToolbarMenu/ViewShowToolbarFullScreenMenu";
2014         else
2015                 action_name = "/MenuBar/ViewMenu/ViewShowToolbarMenu/ViewShowToolbarNormalScreenMenu";
2016
2017         action = gtk_ui_manager_get_action (parent_priv->ui_manager, action_name);
2018         modest_maemo_toggle_action_set_active_block_notify (GTK_TOGGLE_ACTION (action),
2019                                                             show_toolbar);
2020 }
2021
2022 static void 
2023 modest_msg_view_window_clipboard_owner_change (GtkClipboard *clipboard,
2024                                                GdkEvent *event,
2025                                                ModestMsgViewWindow *window)
2026 {
2027         if (!GTK_WIDGET_VISIBLE (window))
2028                 return;
2029
2030         modest_window_check_dimming_rules_group (MODEST_WINDOW (window), "ModestClipboardDimmingRules");
2031 }
2032
2033 gboolean 
2034 modest_msg_view_window_transfer_mode_enabled (ModestMsgViewWindow *self)
2035 {
2036         ModestMsgViewWindowPrivate *priv;
2037         
2038         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), FALSE); 
2039         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2040
2041         return priv->current_toolbar_mode == TOOLBAR_MODE_TRANSFER;
2042 }
2043
2044 static void
2045 cancel_progressbar (GtkToolButton *toolbutton,
2046                     ModestMsgViewWindow *self)
2047 {
2048         GSList *tmp;
2049         ModestMsgViewWindowPrivate *priv;
2050         
2051         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2052
2053         /* Get operation observers and cancel its current operation */
2054         tmp = priv->progress_widgets;
2055         while (tmp) {
2056                 modest_progress_object_cancel_current_operation (MODEST_PROGRESS_OBJECT(tmp->data));
2057                 tmp=g_slist_next(tmp);
2058         }
2059 }
2060 static gboolean
2061 observers_empty (ModestMsgViewWindow *self)
2062 {
2063         GSList *tmp = NULL;
2064         ModestMsgViewWindowPrivate *priv;
2065         gboolean is_empty = TRUE;
2066         guint pending_ops = 0;
2067  
2068         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2069         tmp = priv->progress_widgets;
2070
2071         /* Check all observers */
2072         while (tmp && is_empty)  {
2073                 pending_ops = modest_progress_object_num_pending_operations (MODEST_PROGRESS_OBJECT(tmp->data));
2074                 is_empty = pending_ops == 0;
2075                 
2076                 tmp = g_slist_next(tmp);
2077         }
2078         
2079         return is_empty;
2080 }
2081
2082 static void
2083 on_account_removed (TnyAccountStore *account_store, 
2084                     TnyAccount *account,
2085                     gpointer user_data)
2086 {
2087         /* Do nothing if it's a transport account, because we only
2088            show the messages of a store account */
2089         if (tny_account_get_account_type(account) == TNY_ACCOUNT_TYPE_STORE) {
2090                 const gchar *parent_acc = NULL;
2091                 const gchar *our_acc = NULL;
2092
2093                 our_acc = modest_window_get_active_account (MODEST_WINDOW (user_data));
2094                 parent_acc = modest_tny_account_get_parent_modest_account_name_for_server_account (account);
2095
2096                 /* Close this window if I'm showing a message of the removed account */
2097                 if (strcmp (parent_acc, our_acc) == 0)
2098                         modest_ui_actions_on_close_window (NULL, MODEST_WINDOW (user_data));
2099         }
2100 }
2101
2102 static void 
2103 on_mail_operation_started (ModestMailOperation *mail_op,
2104                            gpointer user_data)
2105 {
2106         ModestMsgViewWindow *self;
2107         ModestMailOperationTypeOperation op_type;
2108         GSList *tmp;
2109         ModestMsgViewWindowPrivate *priv;
2110
2111         self = MODEST_MSG_VIEW_WINDOW (user_data);
2112         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2113         op_type = modest_mail_operation_get_type_operation (mail_op);
2114         tmp = priv->progress_widgets;
2115         
2116         if (op_type == MODEST_MAIL_OPERATION_TYPE_RECEIVE || op_type == MODEST_MAIL_OPERATION_TYPE_OPEN ) {
2117                 set_toolbar_transfer_mode(self);
2118                 while (tmp) {
2119                         modest_progress_object_add_operation (
2120                                         MODEST_PROGRESS_OBJECT (tmp->data),
2121                                         mail_op);
2122                         tmp = g_slist_next (tmp);
2123                 }
2124         }
2125 }
2126
2127 static void 
2128 on_mail_operation_finished (ModestMailOperation *mail_op,
2129                             gpointer user_data)
2130 {
2131         ModestMsgViewWindow *self;
2132         ModestMailOperationTypeOperation op_type;
2133         GSList *tmp;
2134         ModestMsgViewWindowPrivate *priv;
2135         
2136         self = MODEST_MSG_VIEW_WINDOW (user_data);
2137         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2138         op_type = modest_mail_operation_get_type_operation (mail_op);
2139         tmp = priv->progress_widgets;
2140         
2141         if (op_type == MODEST_MAIL_OPERATION_TYPE_RECEIVE || op_type == MODEST_MAIL_OPERATION_TYPE_OPEN ) {
2142                 while (tmp) {
2143                         modest_progress_object_remove_operation (
2144                                         MODEST_PROGRESS_OBJECT (tmp->data),
2145                                         mail_op);
2146                         tmp = g_slist_next (tmp);
2147                 }
2148
2149                 /* If no more operations are being observed, NORMAL mode is enabled again */
2150                 if (observers_empty (self)) {
2151                         set_toolbar_mode (self, TOOLBAR_MODE_NORMAL);
2152                 }
2153         }
2154 }
2155
2156 static void
2157 on_queue_changed (ModestMailOperationQueue *queue,
2158                   ModestMailOperation *mail_op,
2159                   ModestMailOperationQueueNotification type,
2160                   ModestMsgViewWindow *self)
2161 {       
2162         ModestMsgViewWindowPrivate *priv;
2163
2164         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2165
2166         /* If this operations was created by another window, do nothing */
2167         if (!modest_mail_operation_is_mine (mail_op, G_OBJECT(self))) 
2168             return;
2169
2170         if (type == MODEST_MAIL_OPERATION_QUEUE_OPERATION_ADDED) {
2171                 priv->sighandlers = modest_signal_mgr_connect (priv->sighandlers,
2172                                                                G_OBJECT (mail_op),
2173                                                                "operation-started",
2174                                                                G_CALLBACK (on_mail_operation_started),
2175                                                                self);
2176                 priv->sighandlers = modest_signal_mgr_connect (priv->sighandlers,
2177                                                                G_OBJECT (mail_op),
2178                                                                "operation-finished",
2179                                                                G_CALLBACK (on_mail_operation_finished),
2180                                                                self);
2181         } else if (type == MODEST_MAIL_OPERATION_QUEUE_OPERATION_REMOVED) {
2182                 priv->sighandlers = modest_signal_mgr_disconnect (priv->sighandlers,
2183                                                                   G_OBJECT (mail_op),
2184                                                                   "operation-started");
2185                 priv->sighandlers = modest_signal_mgr_disconnect (priv->sighandlers,
2186                                                                   G_OBJECT (mail_op),
2187                                                                   "operation-finished");
2188         }
2189 }
2190
2191 GList *
2192 modest_msg_view_window_get_attachments (ModestMsgViewWindow *win) 
2193 {
2194         ModestMsgViewWindowPrivate *priv;
2195         GList *selected_attachments = NULL;
2196         
2197         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (win), NULL);
2198         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (win);
2199
2200         selected_attachments = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2201         
2202         return selected_attachments;
2203 }
2204
2205 void
2206 modest_msg_view_window_view_attachment (ModestMsgViewWindow *window, TnyMimePart *mime_part)
2207 {
2208         ModestMsgViewWindowPrivate *priv;
2209         const gchar *msg_uid;
2210         gchar *attachment_uid = NULL;
2211         gint attachment_index = 0;
2212         GList *attachments;
2213
2214         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
2215         g_return_if_fail (TNY_IS_MIME_PART (mime_part) || (mime_part == NULL));
2216         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2217
2218         msg_uid = modest_msg_view_window_get_message_uid (MODEST_MSG_VIEW_WINDOW (window));
2219         attachments = modest_msg_view_get_attachments (MODEST_MSG_VIEW (priv->msg_view));
2220         attachment_index = g_list_index (attachments, mime_part);
2221         g_list_free (attachments);
2222         
2223         if (msg_uid && attachment_index >= 0) {
2224                 attachment_uid = g_strdup_printf ("%s/%d", msg_uid, attachment_index);
2225         }
2226
2227         if (mime_part == NULL) {
2228                 gboolean error = FALSE;
2229                 GList *selected_attachments = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2230                 if (selected_attachments == NULL) {
2231                         error = TRUE;
2232                 } else if (g_list_length (selected_attachments) > 1) {
2233                         hildon_banner_show_information (NULL, NULL, _("mcen_ib_unable_to_display_more"));
2234                         error = TRUE;
2235                 } else {
2236                         mime_part = (TnyMimePart *) selected_attachments->data;
2237                         g_object_ref (mime_part);
2238                 }
2239                 g_list_foreach (selected_attachments, (GFunc) g_object_unref, NULL);
2240                 g_list_free (selected_attachments);
2241
2242                 if (error)
2243                         return;
2244         } else {
2245                 g_object_ref (mime_part);
2246         }
2247
2248         if (tny_mime_part_is_purged (mime_part)) {
2249                 g_object_unref (mime_part);
2250                 return;
2251         }
2252
2253         if (!TNY_IS_MSG (mime_part)) {
2254                 gchar *filepath = NULL;
2255                 const gchar *att_filename = tny_mime_part_get_filename (mime_part);
2256                 const gchar *content_type;
2257                 TnyFsStream *temp_stream = NULL;
2258                 temp_stream = modest_maemo_utils_create_temp_stream (att_filename, attachment_uid,
2259                                                                      &filepath);
2260                 
2261                 if (temp_stream != NULL) {
2262                         content_type = tny_mime_part_get_content_type (mime_part);
2263                         tny_mime_part_decode_to_stream (mime_part, TNY_STREAM (temp_stream));
2264
2265                         /* make the file read-only */
2266                         if (g_chmod(filepath, 0444) != 0)
2267                                 g_warning ("%s: failed to set file '%s' to read-only: %s",
2268                                            __FUNCTION__, filepath, strerror(errno));
2269                         
2270                         modest_platform_activate_file (filepath, content_type);
2271                         g_object_unref (temp_stream);
2272                         g_free (filepath);
2273                         /* NOTE: files in the temporary area will be automatically
2274                          * cleaned after some time if they are no longer in use */
2275                 } else if (filepath != NULL) {
2276                         /* the file may already exist but it isn't writable,
2277                          * let's try to open it anyway */
2278                         content_type = tny_mime_part_get_content_type (mime_part);
2279                         modest_platform_activate_file (filepath, content_type);
2280                         g_free (filepath);
2281                 }
2282         } else {
2283                 /* message attachment */
2284                 TnyHeader *header = NULL;
2285                 ModestWindowMgr *mgr;
2286                 ModestWindow *msg_win = NULL;
2287                 gboolean found;
2288                 
2289                 header = tny_msg_get_header (TNY_MSG (mime_part));
2290                 mgr = modest_runtime_get_window_mgr ();         
2291                 found = modest_window_mgr_find_registered_header (mgr, header, &msg_win);
2292
2293                 if (found) {
2294                         if (msg_win)                            /* there is already a window for this uid; top it */
2295                                 gtk_window_present (GTK_WINDOW(msg_win));
2296                         else 
2297                                 /* if it's found, but there is no msg_win, it's probably in the process of being created;
2298                                  * thus, we don't do anything */
2299                                 g_warning ("window for is already being created");
2300                 } else { 
2301                         /* it's not found, so create a new window for it */
2302                         modest_window_mgr_register_header (mgr, header, attachment_uid); /* register the uid before building the window */
2303                         gchar *account = g_strdup (modest_window_get_active_account (MODEST_WINDOW (window)));
2304                         if (!account)
2305                                 account = modest_account_mgr_get_default_account (modest_runtime_get_account_mgr ());
2306                         msg_win = modest_msg_view_window_new_for_attachment (TNY_MSG (mime_part), account, attachment_uid);
2307                         modest_window_set_zoom (MODEST_WINDOW (msg_win), 
2308                                                 modest_window_get_zoom (MODEST_WINDOW (window)));
2309                         modest_window_mgr_register_window (mgr, msg_win);
2310                         gtk_window_set_transient_for (GTK_WINDOW (msg_win), GTK_WINDOW (window));
2311                         gtk_widget_show_all (GTK_WIDGET (msg_win));
2312                 }
2313         }
2314         g_object_unref (mime_part);
2315 }
2316
2317 typedef struct
2318 {
2319         gchar *filename;
2320         TnyMimePart *part;
2321 } SaveMimePartPair;
2322
2323 typedef struct
2324 {
2325         GList *pairs;
2326         GtkWidget *banner;
2327         gboolean result;
2328 } SaveMimePartInfo;
2329
2330 static void save_mime_part_info_free (SaveMimePartInfo *info, gboolean with_struct);
2331 static gboolean idle_save_mime_part_show_result (SaveMimePartInfo *info);
2332 static gpointer save_mime_part_to_file (SaveMimePartInfo *info);
2333 static void save_mime_parts_to_file_with_checks (SaveMimePartInfo *info);
2334
2335 static void 
2336 save_mime_part_info_free (SaveMimePartInfo *info, gboolean with_struct)
2337 {
2338         
2339         GList *node;
2340         for (node = info->pairs; node != NULL; node = g_list_next (node)) {
2341                 SaveMimePartPair *pair = (SaveMimePartPair *) node->data;
2342                 g_free (pair->filename);
2343                 g_object_unref (pair->part);
2344                 g_slice_free (SaveMimePartPair, pair);
2345         }
2346         g_list_free (info->pairs);
2347         info->pairs = NULL;
2348         if (with_struct) {
2349                 gtk_widget_destroy (info->banner);
2350                 g_object_unref (info->banner);
2351                 g_slice_free (SaveMimePartInfo, info);
2352         }
2353 }
2354
2355 static gboolean
2356 idle_save_mime_part_show_result (SaveMimePartInfo *info)
2357 {
2358         if (info->pairs != NULL) {
2359                 save_mime_part_to_file (info);
2360         } else {
2361                 gboolean result;
2362                 result = info->result;
2363
2364                 /* This is a GDK lock because we are an idle callback and
2365                  * hildon_banner_show_information is or does Gtk+ code */
2366
2367                 gdk_threads_enter (); /* CHECKED */
2368                 save_mime_part_info_free (info, TRUE);
2369                 if (result) {
2370                         hildon_banner_show_information (NULL, NULL, _CS("sfil_ib_saved"));
2371                 } else {
2372                         hildon_banner_show_information (NULL, NULL, _("mail_ib_file_operation_failed"));
2373                 }
2374                 gdk_threads_leave (); /* CHECKED */
2375         }
2376
2377         return FALSE;
2378 }
2379
2380 static gpointer
2381 save_mime_part_to_file (SaveMimePartInfo *info)
2382 {
2383         GnomeVFSResult result;
2384         GnomeVFSHandle *handle;
2385         TnyStream *stream;
2386         SaveMimePartPair *pair = (SaveMimePartPair *) info->pairs->data;
2387
2388         result = gnome_vfs_create (&handle, pair->filename, GNOME_VFS_OPEN_WRITE, FALSE, 0644);
2389         if (result == GNOME_VFS_OK) {
2390                 stream = tny_vfs_stream_new (handle);
2391                 tny_mime_part_decode_to_stream (pair->part, stream);
2392                 g_object_unref (G_OBJECT (stream));
2393                 g_object_unref (pair->part);
2394                 g_slice_free (SaveMimePartPair, pair);
2395                 info->pairs = g_list_delete_link (info->pairs, info->pairs);
2396                 info->result = TRUE;
2397         } else {
2398                 save_mime_part_info_free (info, FALSE);
2399                 info->result = FALSE;
2400         }
2401
2402         g_idle_add ((GSourceFunc) idle_save_mime_part_show_result, info);
2403         return NULL;
2404 }
2405
2406 static void
2407 save_mime_parts_to_file_with_checks (SaveMimePartInfo *info)
2408 {
2409         gboolean is_ok = TRUE;
2410         gint replaced_files = 0;
2411         const GList *files = info->pairs;
2412         const GList *iter;
2413
2414         for (iter = files; (iter != NULL) && (replaced_files < 2); iter = g_list_next(iter)) {
2415                 SaveMimePartPair *pair = iter->data;
2416                 if (modest_maemo_utils_file_exists (pair->filename)) {
2417                         replaced_files++;
2418                 }
2419         }
2420         if (replaced_files) {
2421                 GtkWidget *confirm_overwrite_dialog;
2422                 const gchar *message = (replaced_files == 1) ?
2423                         _FM("docm_nc_replace_file") : _FM("docm_nc_replace_multiple");
2424                 confirm_overwrite_dialog = hildon_note_new_confirmation (NULL, message);
2425                 if (gtk_dialog_run (GTK_DIALOG (confirm_overwrite_dialog)) != GTK_RESPONSE_OK) {
2426                         is_ok = FALSE;
2427                 }
2428                 gtk_widget_destroy (confirm_overwrite_dialog);
2429         }
2430
2431         if (!is_ok) {
2432                 save_mime_part_info_free (info, TRUE);
2433         } else {
2434                 GtkWidget *banner = hildon_banner_show_animation (NULL, NULL, 
2435                                                                   _CS("sfil_ib_saving"));
2436                 info->banner = g_object_ref (banner);
2437                 g_thread_create ((GThreadFunc)save_mime_part_to_file, info, FALSE, NULL);
2438                 g_object_unref (banner);
2439         }
2440
2441 }
2442
2443
2444 void
2445 modest_msg_view_window_save_attachments (ModestMsgViewWindow *window, GList *mime_parts)
2446 {
2447         gboolean clean_list = FALSE;
2448         ModestMsgViewWindowPrivate *priv;
2449         GList *files_to_save = NULL;
2450         GtkWidget *save_dialog = NULL;
2451         gchar *folder = NULL;
2452         gboolean canceled = FALSE;
2453         const gchar *filename = NULL;
2454         gchar *save_multiple_str = NULL;
2455
2456         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
2457         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2458
2459         if (mime_parts == NULL) {
2460                 mime_parts = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2461                 if (mime_parts == NULL)
2462                         return;
2463                 clean_list = TRUE;
2464         }
2465
2466         /* prepare dialog */
2467         if (mime_parts->next == NULL) {
2468                 /* only one attachment selected */
2469                 TnyMimePart *mime_part = (TnyMimePart *) mime_parts->data;
2470                 if (!TNY_IS_MSG (mime_part) && tny_mime_part_is_attachment (mime_part)) {
2471                         filename = tny_mime_part_get_filename (mime_part);
2472                 } else {
2473                         g_warning ("Tried to save a non-file attachment");
2474                         canceled = TRUE;
2475                 }
2476         } else {
2477                 save_multiple_str = g_strdup_printf (_FM("sfil_va_number_of_objects_attachments"), 
2478                                                      g_list_length (mime_parts));
2479         }
2480         
2481         save_dialog = hildon_file_chooser_dialog_new (GTK_WINDOW (window), 
2482                                                       GTK_FILE_CHOOSER_ACTION_SAVE);
2483
2484         /* set folder */
2485         folder = g_build_filename (g_get_home_dir (), DEFAULT_FOLDER, NULL);
2486         gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (save_dialog), folder);
2487         g_free (folder);
2488
2489         /* set filename */
2490         if (filename != NULL)
2491                 gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (save_dialog), 
2492                                                    filename);
2493
2494         /* if multiple, set multiple string */
2495         if (save_multiple_str) {
2496                 g_object_set (G_OBJECT (save_dialog), "save-multiple", save_multiple_str, NULL);
2497         }
2498                 
2499         /* show dialog */
2500         if (gtk_dialog_run (GTK_DIALOG (save_dialog)) == GTK_RESPONSE_OK) {
2501                 gchar *chooser_uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (save_dialog));
2502
2503                 if (!modest_maemo_utils_folder_writable (chooser_uri)) {
2504                         hildon_banner_show_information 
2505                                 (NULL, NULL, dgettext("hildon-fm", "sfil_ib_readonly_location"));
2506                 } else {
2507                         GList *node = NULL;
2508
2509                         for (node = mime_parts; node != NULL; node = g_list_next (node)) {
2510                                 TnyMimePart *mime_part = (TnyMimePart *) node->data;
2511                                 
2512                                 if (tny_mime_part_is_attachment (mime_part)) {
2513                                         SaveMimePartPair *pair;
2514
2515                                         if ((mime_parts->next != NULL) &&
2516                                             (tny_mime_part_get_filename (mime_part) == NULL))
2517                                                 continue;
2518                                         
2519                                         pair = g_slice_new0 (SaveMimePartPair);
2520                                         if (mime_parts->next == NULL) {
2521                                                 pair->filename = g_strdup (chooser_uri);
2522                                         } else {
2523                                                 pair->filename = 
2524                                                         g_build_filename (chooser_uri,
2525                                                                           tny_mime_part_get_filename (mime_part), NULL);
2526                                         }
2527                                         pair->part = g_object_ref (mime_part);
2528                                         files_to_save = g_list_prepend (files_to_save, pair);
2529                                 }
2530                         }
2531                 }
2532                 g_free (chooser_uri);
2533         }
2534
2535         gtk_widget_destroy (save_dialog);
2536
2537         if (clean_list) {
2538                 g_list_foreach (mime_parts, (GFunc) g_object_unref, NULL);
2539                 g_list_free (mime_parts);
2540         }
2541
2542         if (files_to_save != NULL) {
2543                 SaveMimePartInfo *info = g_slice_new0 (SaveMimePartInfo);
2544                 info->pairs = files_to_save;
2545                 info->result = TRUE;
2546                 save_mime_parts_to_file_with_checks (info);
2547         }
2548 }
2549
2550 static gboolean
2551 show_remove_attachment_information (gpointer userdata)
2552 {
2553         ModestMsgViewWindow *window = (ModestMsgViewWindow *) userdata;
2554         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2555
2556         /* We're outside the main lock */
2557         gdk_threads_enter ();
2558
2559         if (priv->remove_attachment_banner != NULL) {
2560                 gtk_widget_destroy (priv->remove_attachment_banner);
2561                 g_object_unref (priv->remove_attachment_banner);
2562         }
2563
2564         priv->remove_attachment_banner = g_object_ref (
2565                 hildon_banner_show_animation (NULL, NULL, _("mcen_ib_removing_attachment")));
2566
2567         gdk_threads_leave ();
2568
2569         return FALSE;
2570 }
2571
2572 void
2573 modest_msg_view_window_remove_attachments (ModestMsgViewWindow *window, gboolean get_all)
2574 {
2575         ModestMsgViewWindowPrivate *priv;
2576         GList *mime_parts = NULL, *node;
2577         gchar *confirmation_message;
2578         gint response;
2579         gint n_attachments;
2580         TnyMsg *msg;
2581 /*      TnyFolder *folder; */
2582
2583         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
2584         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2585
2586         if (get_all)
2587                 mime_parts = modest_msg_view_get_attachments (MODEST_MSG_VIEW (priv->msg_view));
2588         else
2589                 mime_parts = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2590                 
2591         /* Remove already purged messages from mime parts list */
2592         node = mime_parts;
2593         while (node != NULL) {
2594                 TnyMimePart *part = TNY_MIME_PART (node->data);
2595                 if (tny_mime_part_is_purged (part)) {
2596                         GList *deleted_node = node;
2597                         node = g_list_next (node);
2598                         g_object_unref (part);
2599                         mime_parts = g_list_delete_link (mime_parts, deleted_node);
2600                 } else {
2601                         node = g_list_next (node);
2602                 }
2603         }
2604
2605         if (mime_parts == NULL)
2606                 return;
2607
2608         n_attachments = g_list_length (mime_parts);
2609         if (n_attachments == 1) {
2610                 const gchar *filename;
2611
2612                 if (TNY_IS_MSG (mime_parts->data)) {
2613                         TnyHeader *header;
2614                         header = tny_msg_get_header (TNY_MSG (mime_parts->data));
2615                         filename = tny_header_get_subject (header);
2616                         g_object_unref (header);
2617                         if (filename == NULL)
2618                                 filename = _("mail_va_no_subject");
2619                 } else {
2620                         filename = tny_mime_part_get_filename (TNY_MIME_PART (mime_parts->data));
2621                 }
2622                 confirmation_message = g_strdup_printf (_("mcen_nc_purge_file_text"), filename);
2623         } else {
2624                 confirmation_message = g_strdup_printf (ngettext("mcen_nc_purge_file_text", 
2625                                                                  "mcen_nc_purge_files_text", 
2626                                                                  n_attachments), n_attachments);
2627         }
2628         response = modest_platform_run_confirmation_dialog (GTK_WINDOW (window),
2629                                                             confirmation_message);
2630         g_free (confirmation_message);
2631
2632         if (response != GTK_RESPONSE_OK)
2633                 return;
2634
2635         priv->purge_timeout = g_timeout_add (2000, show_remove_attachment_information, window);
2636 /*      folder = tny_msg_get_folder (msg); */
2637 /*      tny_msg_uncache_attachments (msg); */
2638 /*      tny_folder_refresh (folder, NULL); */
2639 /*      g_object_unref (folder); */
2640         
2641         for (node = mime_parts; node != NULL; node = g_list_next (node)) {
2642                 tny_mime_part_set_purged (TNY_MIME_PART (node->data));
2643 /*              modest_msg_view_remove_attachment (MODEST_MSG_VIEW (priv->msg_view), node->data); */
2644         }
2645
2646         msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
2647         tny_msg_view_clear (TNY_MSG_VIEW (priv->msg_view));
2648         tny_msg_rewrite_cache (msg);
2649         tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
2650
2651         g_list_foreach (mime_parts, (GFunc) g_object_unref, NULL);
2652         g_list_free (mime_parts);
2653
2654         if (priv->purge_timeout > 0) {
2655                 g_source_remove (priv->purge_timeout);
2656                 priv->purge_timeout = 0;
2657         }
2658
2659         if (priv->remove_attachment_banner) {
2660                 gtk_widget_destroy (priv->remove_attachment_banner);
2661                 g_object_unref (priv->remove_attachment_banner);
2662                 priv->remove_attachment_banner = NULL;
2663         }
2664
2665
2666 }
2667
2668
2669 static void
2670 update_window_title (ModestMsgViewWindow *window)
2671 {
2672         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2673         TnyMsg *msg = NULL;
2674         TnyHeader *header = NULL;
2675         const gchar *subject = NULL;
2676         
2677         msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
2678
2679         if (msg != NULL) {
2680                 header = tny_msg_get_header (msg);
2681                 subject = tny_header_get_subject (header);
2682                 g_object_unref (msg);
2683         }
2684
2685         if ((subject == NULL)||(subject[0] == '\0'))
2686                 subject = _("mail_va_no_subject");
2687
2688         gtk_window_set_title (GTK_WINDOW (window), subject);
2689 }
2690
2691
2692 static void on_move_focus (GtkWidget *widget,
2693                            GtkDirectionType direction,
2694                            gpointer userdata)
2695 {
2696         g_signal_stop_emission_by_name (G_OBJECT (widget), "move-focus");
2697 }
2698