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