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