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