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