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