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