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