* Fixes NB@63545
[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                 TnyFolder *folder;
1528                 GtkResponseType response;
1529
1530                 op_type = MODEST_MAIL_OPERATION_TYPE_RECEIVE;
1531
1532                 /* Ask the user if he wants to download the message */
1533                 response = modest_platform_run_confirmation_dialog (GTK_WINDOW (window),
1534                                                                     _("mcen_nc_get_msg"));
1535                 if (response == GTK_RESPONSE_CANCEL)
1536                         return FALSE;
1537                 
1538                 /* Offer the connection dialog if necessary */
1539                 /* FIXME: should this stuff go directly to the mail
1540                    operation instead of spread it all over the
1541                    code? */
1542                 folder = tny_header_get_folder (header);
1543                 if (folder) {
1544                         if (!modest_platform_connect_and_wait_if_network_folderstore (NULL, TNY_FOLDER_STORE (folder))) {
1545                                 g_object_unref (folder);
1546                                 return FALSE;
1547                         }
1548                         g_object_unref (folder);
1549                 }
1550         }
1551
1552         /* New mail operation */
1553         mail_op = modest_mail_operation_new_with_error_handling (op_type, 
1554                                                                  G_OBJECT(window),
1555                                                                  modest_ui_actions_get_msgs_full_error_handler, 
1556                                                                  NULL);
1557                                 
1558         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (), mail_op);
1559         modest_mail_operation_get_msg (mail_op, header, view_msg_cb, path);
1560         g_object_unref (mail_op);
1561
1562         /* Update toolbar dimming rules */
1563         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (window));
1564
1565         return TRUE;
1566 }
1567
1568 gboolean        
1569 modest_msg_view_window_select_next_message (ModestMsgViewWindow *window)
1570 {
1571         ModestMsgViewWindowPrivate *priv;
1572         GtkTreePath *path= NULL;
1573         GtkTreeIter tmp_iter;
1574         TnyHeader *header;
1575         gboolean retval = TRUE;
1576
1577         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), FALSE);
1578         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1579
1580         /* Update the next row reference if it's not valid. This could
1581            happen if for example the header which it was pointing to,
1582            was deleted. The best place to do it is in the row-deleted
1583            handler but the tinymail model do not work like the glib
1584            tree models and reports the deletion when the row is still
1585            there */
1586         if (!gtk_tree_row_reference_valid (priv->next_row_reference)) {
1587                 if (gtk_tree_row_reference_valid (priv->row_reference)) {
1588                         priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
1589                         select_next_valid_row (priv->header_model, &(priv->next_row_reference), FALSE);
1590                 }
1591         }
1592         if (priv->next_row_reference)
1593                 path = gtk_tree_row_reference_get_path (priv->next_row_reference);
1594         if (path == NULL)
1595                 return FALSE;
1596
1597         gtk_tree_model_get_iter (priv->header_model,
1598                                  &tmp_iter,
1599                                  path);
1600
1601         gtk_tree_model_get (priv->header_model, &tmp_iter, 
1602                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1603                             &header, -1);
1604         
1605         /* Read the message & show it */
1606         if (!message_reader (window, priv, header, path)) {
1607                 retval = FALSE;
1608                 gtk_tree_path_free (path);
1609         }
1610
1611         /* Free */
1612         g_object_unref (header);
1613
1614         return retval;          
1615 }
1616
1617 gboolean 
1618 modest_msg_view_window_select_first_message (ModestMsgViewWindow *self)
1619 {
1620         ModestMsgViewWindowPrivate *priv = NULL;
1621         TnyHeader *header = NULL;
1622         GtkTreeIter iter;
1623         GtkTreePath *path;
1624
1625         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), FALSE);
1626         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
1627
1628         /* Check that the model is not empty */
1629         if (!gtk_tree_model_get_iter_first (priv->header_model, &iter))
1630                 return FALSE;
1631
1632         /* Get the header */
1633         gtk_tree_model_get (priv->header_model, 
1634                             &iter, 
1635                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1636                             &header, -1);
1637         g_return_val_if_fail (TNY_IS_HEADER (header), FALSE);
1638         if (tny_header_get_flags (header) & TNY_HEADER_FLAG_DELETED) {
1639                 g_object_unref (header);
1640                 return modest_msg_view_window_select_next_message (self);
1641         }
1642         
1643         path = gtk_tree_model_get_path (priv->header_model, &iter);
1644         
1645         /* Read the message & show it */
1646         message_reader (self, priv, header, path);
1647         
1648         /* Free */
1649         g_object_unref (header);
1650
1651         return TRUE;
1652 }
1653  
1654 gboolean        
1655 modest_msg_view_window_select_previous_message (ModestMsgViewWindow *window)
1656 {
1657         ModestMsgViewWindowPrivate *priv = NULL;
1658         GtkTreePath *path;
1659
1660         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), FALSE);
1661         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1662
1663         /* Return inmediatly if there is no header model */
1664         if (!priv->header_model)
1665                 return FALSE;
1666
1667         path = gtk_tree_row_reference_get_path (priv->row_reference);
1668         while (gtk_tree_path_prev (path)) {
1669                 TnyHeader *header;
1670                 GtkTreeIter iter;
1671
1672                 gtk_tree_model_get_iter (priv->header_model, &iter, path);
1673                 gtk_tree_model_get (priv->header_model, &iter, 
1674                                     TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1675                                     &header, -1);
1676                 if (!header)
1677                         break;
1678                 if (tny_header_get_flags (header) & TNY_HEADER_FLAG_DELETED) {
1679                         g_object_unref (header);
1680                         continue;
1681                 }
1682
1683                 /* Read the message & show it */
1684                 if (!message_reader (window, priv, header, path)) {
1685                         g_object_unref (header);
1686                         break;
1687                 }
1688
1689                 g_object_unref (header);
1690
1691                 return TRUE;
1692         }
1693
1694         gtk_tree_path_free (path);
1695         return FALSE;
1696 }
1697
1698 static void
1699 view_msg_cb (ModestMailOperation *mail_op, 
1700              TnyHeader *header, 
1701              TnyMsg *msg, 
1702              gpointer user_data)
1703 {
1704         ModestMsgViewWindow *self = NULL;
1705         ModestMsgViewWindowPrivate *priv = NULL;
1706         GtkTreePath *path;
1707
1708         /* If there was any error */
1709         path = (GtkTreePath *) user_data;
1710         if (!modest_ui_actions_msg_retrieval_check (mail_op, header, msg)) {
1711                 gtk_tree_path_free (path);                      
1712                 return;
1713         }
1714
1715         /* Get the window */ 
1716         self = (ModestMsgViewWindow *) modest_mail_operation_get_source (mail_op);
1717         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self));
1718         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
1719
1720         /* Update the row reference */
1721         gtk_tree_row_reference_free (priv->row_reference);
1722         priv->row_reference = gtk_tree_row_reference_new (priv->header_model, path);
1723         priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
1724         select_next_valid_row (priv->header_model, &(priv->next_row_reference), TRUE);
1725         gtk_tree_path_free (path);
1726
1727         /* Mark header as read */
1728         if (!(tny_header_get_flags (header) & TNY_HEADER_FLAG_SEEN))
1729                 tny_header_set_flags (header, TNY_HEADER_FLAG_SEEN);
1730
1731         /* Set new message */
1732         modest_msg_view_set_message (MODEST_MSG_VIEW (priv->msg_view), msg);
1733         modest_msg_view_window_update_priority (self);
1734         update_window_title (MODEST_MSG_VIEW_WINDOW (self));
1735         modest_msg_view_grab_focus (MODEST_MSG_VIEW (priv->msg_view));
1736
1737         /* Set the new message uid of the window  */
1738         if (priv->msg_uid) {
1739                 g_free (priv->msg_uid);
1740                 priv->msg_uid = modest_tny_folder_get_header_unique_id (header);
1741         }
1742
1743         /* Notify the observers */
1744         g_signal_emit (G_OBJECT (self), signals[MSG_CHANGED_SIGNAL], 
1745                        0, priv->header_model, priv->row_reference);
1746
1747         /* Free new references */
1748         g_object_unref (self);
1749 }
1750
1751 TnyFolderType
1752 modest_msg_view_window_get_folder_type (ModestMsgViewWindow *window)
1753 {
1754         ModestMsgViewWindowPrivate *priv;
1755         TnyMsg *msg;
1756         TnyFolderType folder_type;
1757
1758         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1759
1760         folder_type = TNY_FOLDER_TYPE_UNKNOWN;
1761
1762         msg = modest_msg_view_get_message (MODEST_MSG_VIEW (priv->msg_view));
1763         if (msg) {
1764                 TnyFolder *folder;
1765
1766                 folder = tny_msg_get_folder (msg);
1767                 
1768                 if (folder) {
1769                         folder_type = tny_folder_get_folder_type (folder);
1770                         
1771                         if (folder_type == TNY_FOLDER_TYPE_NORMAL || folder_type == TNY_FOLDER_TYPE_UNKNOWN) {
1772                                 const gchar *fname = tny_folder_get_name (folder);
1773                                 folder_type = modest_tny_folder_guess_folder_type_from_name (fname);
1774                         }
1775
1776                         g_object_unref (folder);
1777                 }
1778                 g_object_unref (msg);
1779         }
1780
1781         return folder_type;
1782 }
1783
1784
1785 static void
1786 modest_msg_view_window_update_priority (ModestMsgViewWindow *window)
1787 {
1788         ModestMsgViewWindowPrivate *priv;
1789         TnyHeaderFlags flags = 0;
1790
1791         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1792
1793         if (priv->header_model) {
1794                 TnyHeader *header;
1795                 GtkTreeIter iter;
1796                 GtkTreePath *path = NULL;
1797
1798                 path = gtk_tree_row_reference_get_path (priv->row_reference);
1799                 g_return_if_fail (path != NULL);
1800                 gtk_tree_model_get_iter (priv->header_model, 
1801                                          &iter, 
1802                                          gtk_tree_row_reference_get_path (priv->row_reference));
1803
1804                 gtk_tree_model_get (priv->header_model, &iter, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1805                                     &header, -1);
1806                 flags = tny_header_get_flags (header);
1807                 gtk_tree_path_free (path);
1808         }
1809
1810         modest_msg_view_set_priority (MODEST_MSG_VIEW(priv->msg_view), flags);
1811
1812 }
1813
1814 static gboolean
1815 modest_msg_view_window_window_state_event (GtkWidget *widget, GdkEventWindowState *event, gpointer userdata)
1816 {
1817         if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) {
1818                 ModestWindowPrivate *parent_priv;
1819                 ModestWindowMgr *mgr;
1820                 gboolean is_fullscreen;
1821                 GtkAction *fs_toggle_action;
1822                 gboolean active;
1823
1824                 mgr = modest_runtime_get_window_mgr ();
1825                 is_fullscreen = (modest_window_mgr_get_fullscreen_mode (mgr))?1:0;
1826
1827                 parent_priv = MODEST_WINDOW_GET_PRIVATE (widget);
1828                 
1829                 fs_toggle_action = gtk_ui_manager_get_action (parent_priv->ui_manager, "/MenuBar/ViewMenu/ViewToggleFullscreenMenu");
1830                 active = (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (fs_toggle_action)))?1:0;
1831                 if (is_fullscreen != active) {
1832                         gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (fs_toggle_action), is_fullscreen);
1833                 }
1834         }
1835
1836         return FALSE;
1837
1838 }
1839
1840 void
1841 modest_msg_view_window_toggle_fullscreen (ModestMsgViewWindow *window)
1842 {
1843                 ModestWindowPrivate *parent_priv;
1844                 GtkAction *fs_toggle_action;
1845                 parent_priv = MODEST_WINDOW_GET_PRIVATE (window);
1846                 
1847                 fs_toggle_action = gtk_ui_manager_get_action (parent_priv->ui_manager, "/MenuBar/ViewMenu/ViewToggleFullscreenMenu");
1848                 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (fs_toggle_action),
1849                                               !gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (fs_toggle_action)));
1850 }
1851
1852 static void
1853 set_homogeneous (GtkWidget *widget,
1854                  gpointer data)
1855 {
1856         if (GTK_IS_TOOL_ITEM (widget)) {
1857                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (widget), TRUE);
1858                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (widget), TRUE);
1859         }
1860 }
1861
1862 static void
1863 modest_msg_view_window_show_toolbar (ModestWindow *self,
1864                                      gboolean show_toolbar)
1865 {
1866         ModestMsgViewWindowPrivate *priv = NULL;
1867         ModestWindowPrivate *parent_priv;
1868         GtkWidget *reply_button = NULL, *menu = NULL;
1869         GtkWidget *placeholder = NULL;
1870         gint insert_index;
1871         
1872         parent_priv = MODEST_WINDOW_GET_PRIVATE(self);
1873         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
1874
1875         /* Set optimized view status */
1876         priv->optimized_view = !show_toolbar;
1877
1878         if (!parent_priv->toolbar) {
1879                 parent_priv->toolbar = gtk_ui_manager_get_widget (parent_priv->ui_manager, 
1880                                                                   "/ToolBar");
1881
1882                 /* Set homogeneous toolbar */
1883                 gtk_container_foreach (GTK_CONTAINER (parent_priv->toolbar), 
1884                                        set_homogeneous, NULL);
1885
1886                 priv->progress_toolitem = GTK_WIDGET (gtk_tool_item_new ());
1887                 priv->cancel_toolitem = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarCancel");
1888                 priv->next_toolitem = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageNext");
1889                 priv->prev_toolitem = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageBack");
1890                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->progress_toolitem), TRUE);
1891                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (priv->progress_toolitem), TRUE);
1892                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (priv->cancel_toolitem), FALSE);
1893                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->cancel_toolitem), FALSE);
1894
1895                 /* Add ProgressBar (Transfer toolbar) */ 
1896                 priv->progress_bar = modest_progress_bar_widget_new ();
1897                 gtk_widget_set_no_show_all (priv->progress_bar, TRUE);
1898                 placeholder = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ProgressbarView");
1899                 insert_index = gtk_toolbar_get_item_index(GTK_TOOLBAR (parent_priv->toolbar), GTK_TOOL_ITEM(placeholder));
1900                 gtk_container_add (GTK_CONTAINER (priv->progress_toolitem), priv->progress_bar);
1901                 gtk_toolbar_insert(GTK_TOOLBAR(parent_priv->toolbar), GTK_TOOL_ITEM (priv->progress_toolitem), insert_index);
1902                 
1903                 /* Connect cancel 'clicked' signal to abort progress mode */
1904                 g_signal_connect(priv->cancel_toolitem, "clicked",
1905                                  G_CALLBACK(cancel_progressbar),
1906                                  self);
1907                 
1908                 /* Add it to the observers list */
1909                 priv->progress_widgets = g_slist_prepend(priv->progress_widgets, priv->progress_bar);
1910
1911                 /* Add to window */
1912                 hildon_window_add_toolbar (HILDON_WINDOW (self), 
1913                                            GTK_TOOLBAR (parent_priv->toolbar));
1914
1915
1916                 /* Set reply button tap and hold menu */        
1917                 reply_button = gtk_ui_manager_get_widget (parent_priv->ui_manager, 
1918                                                           "/ToolBar/ToolbarMessageReply");
1919                 menu = gtk_ui_manager_get_widget (parent_priv->ui_manager, 
1920                                                   "/ToolbarReplyCSM");
1921                 gtk_widget_tap_and_hold_setup (GTK_WIDGET (reply_button), menu, NULL, 0);
1922         }
1923
1924         if (show_toolbar) {
1925                 /* Quick hack: this prevents toolbar icons "dance" when progress bar show status is changed */ 
1926                 /* TODO: resize mode migth be GTK_RESIZE_QUEUE, in order to avoid unneccesary shows */
1927                 gtk_container_set_resize_mode (GTK_CONTAINER(parent_priv->toolbar), GTK_RESIZE_IMMEDIATE);
1928                 
1929                 gtk_widget_show (GTK_WIDGET (parent_priv->toolbar));
1930                 set_toolbar_mode (MODEST_MSG_VIEW_WINDOW(self), TOOLBAR_MODE_NORMAL);                   
1931                 
1932         } else {
1933                 gtk_widget_set_no_show_all (parent_priv->toolbar, TRUE);
1934                 gtk_widget_hide (GTK_WIDGET (parent_priv->toolbar));
1935         }
1936 }
1937
1938 static void 
1939 modest_msg_view_window_clipboard_owner_change (GtkClipboard *clipboard,
1940                                                GdkEvent *event,
1941                                                ModestMsgViewWindow *window)
1942 {
1943         ModestWindowPrivate *parent_priv;
1944 /*      GtkAction *action; */
1945         gboolean is_address;
1946         gchar *selection;
1947         GtkWidget *focused;
1948
1949         if (!GTK_WIDGET_VISIBLE (window))
1950                 return;
1951
1952         parent_priv = MODEST_WINDOW_GET_PRIVATE (window);
1953         selection = gtk_clipboard_wait_for_text (clipboard);
1954
1955         is_address = ((selection != NULL) && (modest_text_utils_validate_recipient (selection, NULL)));
1956         
1957 /*      action = gtk_ui_manager_get_action (parent_priv->ui_manager, "/MenuBar/ToolsMenu/ToolsAddToContactsMenu"); */
1958 /*      gtk_action_set_sensitive (action, is_address); */
1959
1960         focused = gtk_window_get_focus (GTK_WINDOW (window));
1961
1962 /*      action = gtk_ui_manager_get_action (parent_priv->ui_manager, "/MenuBar/EditMenu/EditCopyMenu"); */
1963 /*      gtk_action_set_sensitive (action, (selection != NULL) && (!MODEST_IS_ATTACHMENTS_VIEW (focused))); */
1964
1965 /*      action = gtk_ui_manager_get_action (parent_priv->ui_manager, "/MenuBar/EditMenu/EditCutMenu"); */
1966 /*      gtk_action_set_sensitive (action, (selection != NULL) && (!MODEST_IS_ATTACHMENTS_VIEW (focused))); */
1967
1968         g_free (selection);
1969 /*      modest_msg_view_window_update_dimmed (window); */
1970         
1971 }
1972
1973 gboolean 
1974 modest_msg_view_window_transfer_mode_enabled (ModestMsgViewWindow *self)
1975 {
1976         ModestMsgViewWindowPrivate *priv;
1977         
1978         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), FALSE); 
1979         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
1980
1981         return priv->current_toolbar_mode == TOOLBAR_MODE_TRANSFER;
1982 }
1983
1984 static void
1985 cancel_progressbar (GtkToolButton *toolbutton,
1986                     ModestMsgViewWindow *self)
1987 {
1988         GSList *tmp;
1989         ModestMsgViewWindowPrivate *priv;
1990         
1991         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
1992
1993         /* Get operation observers and cancel its current operation */
1994         tmp = priv->progress_widgets;
1995         while (tmp) {
1996                 modest_progress_object_cancel_current_operation (MODEST_PROGRESS_OBJECT(tmp->data));
1997                 tmp=g_slist_next(tmp);
1998         }
1999 }
2000 static gboolean
2001 observers_empty (ModestMsgViewWindow *self)
2002 {
2003         GSList *tmp = NULL;
2004         ModestMsgViewWindowPrivate *priv;
2005         gboolean is_empty = TRUE;
2006         guint pending_ops = 0;
2007  
2008         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2009         tmp = priv->progress_widgets;
2010
2011         /* Check all observers */
2012         while (tmp && is_empty)  {
2013                 pending_ops = modest_progress_object_num_pending_operations (MODEST_PROGRESS_OBJECT(tmp->data));
2014                 is_empty = pending_ops == 0;
2015                 
2016                 tmp = g_slist_next(tmp);
2017         }
2018         
2019         return is_empty;
2020 }
2021
2022 static void
2023 on_account_removed (TnyAccountStore *account_store, 
2024                     TnyAccount *account,
2025                     gpointer user_data)
2026 {
2027         /* Do nothing if it's a transport account, because we only
2028            show the messages of a store account */
2029         if (tny_account_get_account_type(account) == TNY_ACCOUNT_TYPE_STORE) {
2030                 const gchar *parent_acc = NULL;
2031                 const gchar *our_acc = NULL;
2032
2033                 our_acc = modest_window_get_active_account (MODEST_WINDOW (user_data));
2034                 parent_acc = modest_tny_account_get_parent_modest_account_name_for_server_account (account);
2035
2036                 /* Close this window if I'm showing a message of the removed account */
2037                 if (strcmp (parent_acc, our_acc) == 0)
2038                         modest_ui_actions_on_close_window (NULL, MODEST_WINDOW (user_data));
2039         }
2040 }
2041
2042 static void
2043 on_queue_changed (ModestMailOperationQueue *queue,
2044                   ModestMailOperation *mail_op,
2045                   ModestMailOperationQueueNotification type,
2046                   ModestMsgViewWindow *self)
2047 {
2048         GSList *tmp;
2049         ModestMsgViewWindowPrivate *priv;
2050         ModestMailOperationTypeOperation op_type;
2051         ModestToolBarModes mode;
2052         
2053         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self));
2054         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2055
2056         /* If this operations was created by another window, do nothing */
2057         if (!modest_mail_operation_is_mine (mail_op, G_OBJECT(self))) 
2058             return;
2059
2060         /* Get toolbar mode from operation id*/
2061         op_type = modest_mail_operation_get_type_operation (mail_op);
2062         switch (op_type) {
2063 /*      case MODEST_MAIL_OPERATION_TYPE_SEND: */
2064         case MODEST_MAIL_OPERATION_TYPE_RECEIVE:
2065         case MODEST_MAIL_OPERATION_TYPE_OPEN:
2066                 mode = TOOLBAR_MODE_TRANSFER;
2067                 break;
2068         default:
2069                 mode = TOOLBAR_MODE_NORMAL;
2070                 
2071         }
2072                 
2073         /* Add operation observers and change toolbar if neccessary*/
2074         tmp = priv->progress_widgets;
2075         switch (type) {
2076         case MODEST_MAIL_OPERATION_QUEUE_OPERATION_ADDED:
2077                 if (mode == TOOLBAR_MODE_TRANSFER) {
2078                         /* Enable transfer toolbar mode */
2079                         set_toolbar_transfer_mode(self);
2080                         while (tmp) {
2081                                 modest_progress_object_add_operation (MODEST_PROGRESS_OBJECT (tmp->data),
2082                                                                       mail_op);
2083                                 tmp = g_slist_next (tmp);
2084                         }
2085                         
2086                 }
2087                 break;
2088         case MODEST_MAIL_OPERATION_QUEUE_OPERATION_REMOVED:
2089                 if (mode == TOOLBAR_MODE_TRANSFER) {
2090                         while (tmp) {
2091                                 modest_progress_object_remove_operation (MODEST_PROGRESS_OBJECT (tmp->data),
2092                                                                  mail_op);
2093                                 tmp = g_slist_next (tmp);
2094                                 
2095                         }
2096
2097                         /* If no more operations are being observed, NORMAL mode is enabled again */
2098                         if (observers_empty (self)) {
2099                                 set_toolbar_mode (self, TOOLBAR_MODE_NORMAL);
2100                         }
2101                 }
2102                 break;
2103         }
2104 }
2105
2106 GList *
2107 modest_msg_view_window_get_attachments (ModestMsgViewWindow *win) 
2108 {
2109         ModestMsgViewWindowPrivate *priv;
2110         GList *selected_attachments = NULL;
2111         
2112         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (win), NULL);
2113         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (win);
2114
2115         selected_attachments = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2116         
2117         return selected_attachments;
2118 }
2119
2120 void
2121 modest_msg_view_window_view_attachment (ModestMsgViewWindow *window, TnyMimePart *mime_part)
2122 {
2123         ModestMsgViewWindowPrivate *priv;
2124         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
2125         g_return_if_fail (TNY_IS_MIME_PART (mime_part) || (mime_part == NULL));
2126
2127         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2128
2129         if (mime_part == NULL) {
2130                 gboolean error = FALSE;
2131                 GList *selected_attachments = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2132                 if (selected_attachments == NULL) {
2133                         error = TRUE;
2134                 } else if (g_list_length (selected_attachments) > 1) {
2135                         hildon_banner_show_information (NULL, NULL, _("mcen_ib_unable_to_display_more"));
2136                         error = TRUE;
2137                 } else {
2138                         mime_part = (TnyMimePart *) selected_attachments->data;
2139                         g_object_ref (mime_part);
2140                 }
2141                 g_list_foreach (selected_attachments, (GFunc) g_object_unref, NULL);
2142                 g_list_free (selected_attachments);
2143
2144                 if (error)
2145                         return;
2146         } else {
2147                 g_object_ref (mime_part);
2148         }
2149
2150         if (tny_mime_part_is_purged (mime_part)) {
2151                 g_object_unref (mime_part);
2152                 return;
2153         }
2154
2155         if (!TNY_IS_MSG (mime_part)) {
2156                 gchar *filepath = NULL;
2157                 const gchar *att_filename = tny_mime_part_get_filename (mime_part);
2158                 gchar *extension = NULL;
2159                 TnyFsStream *temp_stream = NULL;
2160
2161                 if (att_filename) {
2162                         extension = g_strrstr (att_filename, ".");
2163                         if (extension != NULL)
2164                                 extension++;
2165                 }
2166
2167                 temp_stream = modest_maemo_utils_create_temp_stream (extension, &filepath);
2168
2169                 if (temp_stream) {
2170                         const gchar *content_type;
2171                         content_type = tny_mime_part_get_content_type (mime_part);
2172                         tny_mime_part_decode_to_stream (mime_part, TNY_STREAM (temp_stream));
2173                         
2174                         modest_platform_activate_file (filepath, content_type);
2175                         g_object_unref (temp_stream);
2176                         g_free (filepath);
2177                         /* TODO: delete temporary file */
2178                 }
2179         } else {
2180                 /* message attachment */
2181                 TnyHeader *header = NULL;
2182                 ModestWindowMgr *mgr;
2183                 ModestWindow *msg_win = NULL;
2184                 gboolean found;
2185                 
2186                 header = tny_msg_get_header (TNY_MSG (mime_part));
2187                 mgr = modest_runtime_get_window_mgr ();         
2188                 found = modest_window_mgr_find_registered_header (mgr, header, &msg_win);
2189
2190                 if (found) {
2191                         if (msg_win)                            /* there is already a window for this uid; top it */
2192                                 gtk_window_present (GTK_WINDOW(msg_win));
2193                         else 
2194                                 /* if it's found, but there is no msg_win, it's probably in the process of being created;
2195                                  * thus, we don't do anything */
2196                                 g_warning ("window for is already being created");
2197                 } else { 
2198                         /* it's not found, so create a new window for it */
2199                         modest_window_mgr_register_header (mgr, header); /* register the uid before building the window */
2200                         gchar *account = g_strdup (modest_window_get_active_account (MODEST_WINDOW (window)));
2201                         if (!account)
2202                                 account = modest_account_mgr_get_default_account (modest_runtime_get_account_mgr ());
2203                         msg_win = modest_msg_view_window_new_for_attachment (TNY_MSG (mime_part), account, NULL);
2204                         modest_window_set_zoom (MODEST_WINDOW (msg_win), 
2205                                                 modest_window_get_zoom (MODEST_WINDOW (window)));
2206                         modest_window_mgr_register_window (mgr, msg_win);
2207                         gtk_window_set_transient_for (GTK_WINDOW (msg_win), GTK_WINDOW (window));
2208                         gtk_widget_show_all (GTK_WIDGET (msg_win));
2209                 }
2210         }
2211         g_object_unref (mime_part);
2212 }
2213
2214 typedef struct
2215 {
2216         gchar *filename;
2217         TnyMimePart *part;
2218 } SaveMimePartPair;
2219
2220 typedef struct
2221 {
2222         GList *pairs;
2223         GtkWidget *banner;
2224         gboolean result;
2225 } SaveMimePartInfo;
2226
2227 static void save_mime_part_info_free (SaveMimePartInfo *info, gboolean with_struct);
2228 static gboolean idle_save_mime_part_show_result (SaveMimePartInfo *info);
2229 static gpointer save_mime_part_to_file (SaveMimePartInfo *info);
2230 static void save_mime_parts_to_file_with_checks (SaveMimePartInfo *info);
2231
2232 static void 
2233 save_mime_part_info_free (SaveMimePartInfo *info, gboolean with_struct)
2234 {
2235         
2236         GList *node;
2237         for (node = info->pairs; node != NULL; node = g_list_next (node)) {
2238                 SaveMimePartPair *pair = (SaveMimePartPair *) node->data;
2239                 g_free (pair->filename);
2240                 g_object_unref (pair->part);
2241                 g_slice_free (SaveMimePartPair, pair);
2242         }
2243         g_list_free (info->pairs);
2244         info->pairs = NULL;
2245         if (with_struct) {
2246                 gtk_widget_destroy (info->banner);
2247                 g_object_unref (info->banner);
2248                 g_slice_free (SaveMimePartInfo, info);
2249         }
2250 }
2251
2252 static gboolean
2253 idle_save_mime_part_show_result (SaveMimePartInfo *info)
2254 {
2255         if (info->pairs != NULL) {
2256                 /* This is a GDK lock because we are an idle callback and
2257                  * save_mime_parts_to_file_with_checks can contain Gtk+ code */
2258
2259                 gdk_threads_enter (); /* CHECKED */
2260                 save_mime_parts_to_file_with_checks (info);
2261                 gdk_threads_leave (); /* CHECKED */
2262         } else {
2263                 gboolean result;
2264                 result = info->result;
2265
2266                 /* This is a GDK lock because we are an idle callback and
2267                  * hildon_banner_show_information is or does Gtk+ code */
2268
2269                 gdk_threads_enter (); /* CHECKED */
2270                 save_mime_part_info_free (info, TRUE);
2271                 if (result) {
2272                         hildon_banner_show_information (NULL, NULL, _CS("sfil_ib_saved"));
2273                 } else {
2274                         hildon_banner_show_information (NULL, NULL, _("mail_ib_file_operation_failed"));
2275                 }
2276                 gdk_threads_leave (); /* CHECKED */
2277         }
2278
2279         return FALSE;
2280 }
2281
2282 static gpointer
2283 save_mime_part_to_file (SaveMimePartInfo *info)
2284 {
2285         GnomeVFSResult result;
2286         GnomeVFSHandle *handle;
2287         TnyStream *stream;
2288         SaveMimePartPair *pair = (SaveMimePartPair *) info->pairs->data;
2289
2290         result = gnome_vfs_create (&handle, pair->filename, GNOME_VFS_OPEN_WRITE, FALSE, 0777);
2291         if (result == GNOME_VFS_OK) {
2292                 stream = tny_vfs_stream_new (handle);
2293                 tny_mime_part_decode_to_stream (pair->part, stream);
2294                 g_object_unref (G_OBJECT (stream));
2295                 g_object_unref (pair->part);
2296                 g_slice_free (SaveMimePartPair, pair);
2297                 info->pairs = g_list_delete_link (info->pairs, info->pairs);
2298                 info->result = TRUE;
2299         } else {
2300                 save_mime_part_info_free (info, FALSE);
2301                 info->result = FALSE;
2302         }
2303
2304         g_idle_add ((GSourceFunc) idle_save_mime_part_show_result, info);
2305         return NULL;
2306 }
2307
2308 static void
2309 save_mime_parts_to_file_with_checks (SaveMimePartInfo *info)
2310 {
2311         SaveMimePartPair *pair;
2312         gboolean is_ok = TRUE;
2313
2314         pair = info->pairs->data;
2315         if (modest_maemo_utils_file_exists (pair->filename)) {
2316                 GtkWidget *confirm_overwrite_dialog;
2317                 confirm_overwrite_dialog = hildon_note_new_confirmation (NULL,
2318                                                                          _FM("docm_nc_replace_file"));
2319                 if (gtk_dialog_run (GTK_DIALOG (confirm_overwrite_dialog)) != GTK_RESPONSE_OK) {
2320                         is_ok = FALSE;
2321                 }
2322                 gtk_widget_destroy (confirm_overwrite_dialog);
2323         }
2324
2325         if (!is_ok) {
2326                 save_mime_part_info_free (info, TRUE);
2327         } else {
2328                 g_thread_create ((GThreadFunc)save_mime_part_to_file, info, FALSE, NULL);
2329         }
2330
2331 }
2332
2333
2334 void
2335 modest_msg_view_window_save_attachments (ModestMsgViewWindow *window, GList *mime_parts)
2336 {
2337         gboolean clean_list = FALSE;
2338         ModestMsgViewWindowPrivate *priv;
2339         GList *files_to_save = NULL;
2340         GtkWidget *save_dialog = NULL;
2341         gchar *folder = NULL;
2342         gboolean canceled = FALSE;
2343         const gchar *filename = NULL;
2344         gchar *save_multiple_str = NULL;
2345
2346         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
2347         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2348
2349         if (mime_parts == NULL) {
2350                 mime_parts = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2351                 if (mime_parts == NULL)
2352                         return;
2353                 clean_list = TRUE;
2354         }
2355
2356         /* prepare dialog */
2357         if (mime_parts->next == NULL) {
2358                 /* only one attachment selected */
2359                 TnyMimePart *mime_part = (TnyMimePart *) mime_parts->data;
2360                 if (!TNY_IS_MSG (mime_part) && tny_mime_part_is_attachment (mime_part)) {
2361                         filename = tny_mime_part_get_filename (mime_part);
2362                 } else {
2363                         g_warning ("Tried to save a non-file attachment");
2364                         canceled = TRUE;
2365                 }
2366         } else {
2367                 save_multiple_str = g_strdup_printf (_FM("sfil_va_number_of_objects_attachments"), 
2368                                                      g_list_length (mime_parts));
2369         }
2370         
2371         save_dialog = hildon_file_chooser_dialog_new (GTK_WINDOW (window), 
2372                                                       GTK_FILE_CHOOSER_ACTION_SAVE);
2373
2374         /* set folder */
2375         folder = g_build_filename (g_get_home_dir (), DEFAULT_FOLDER, NULL);
2376         gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (save_dialog), folder);
2377         g_free (folder);
2378
2379         /* set filename */
2380         if (filename != NULL)
2381                 gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (save_dialog), 
2382                                                    filename);
2383
2384         /* if multiple, set multiple string */
2385         if (save_multiple_str) {
2386                 g_object_set (G_OBJECT (save_dialog), "save-multiple", save_multiple_str, NULL);
2387         }
2388                 
2389         /* show dialog */
2390         if (gtk_dialog_run (GTK_DIALOG (save_dialog)) == GTK_RESPONSE_OK) {
2391                 gchar *chooser_uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (save_dialog));
2392
2393                 if (!modest_maemo_utils_folder_writable (chooser_uri)) {
2394                         hildon_banner_show_information 
2395                                 (NULL, NULL, dgettext("hildon-fm", "sfil_ib_readonly_location"));
2396                 } else {
2397                         GList *node = NULL;
2398
2399                         for (node = mime_parts; node != NULL; node = g_list_next (node)) {
2400                                 TnyMimePart *mime_part = (TnyMimePart *) node->data;
2401                                 
2402                                 if (tny_mime_part_is_attachment (mime_part)) {
2403                                         SaveMimePartPair *pair;
2404
2405                                         if ((mime_parts->next != NULL) &&
2406                                             (tny_mime_part_get_filename (mime_part) == NULL))
2407                                                 continue;
2408                                         
2409                                         pair = g_slice_new0 (SaveMimePartPair);
2410                                         if (mime_parts->next == NULL) {
2411                                                 pair->filename = g_strdup (chooser_uri);
2412                                         } else {
2413                                                 pair->filename = 
2414                                                         g_build_filename (chooser_uri,
2415                                                                           tny_mime_part_get_filename (mime_part), NULL);
2416                                         }
2417                                         pair->part = g_object_ref (mime_part);
2418                                         files_to_save = g_list_prepend (files_to_save, pair);
2419                                 }
2420                         }
2421                 }
2422                 g_free (chooser_uri);
2423         }
2424
2425         gtk_widget_destroy (save_dialog);
2426
2427         if (clean_list) {
2428                 g_list_foreach (mime_parts, (GFunc) g_object_unref, NULL);
2429                 g_list_free (mime_parts);
2430         }
2431
2432         if (files_to_save != NULL) {
2433                 SaveMimePartInfo *info = g_slice_new0 (SaveMimePartInfo);
2434                 GtkWidget *banner = hildon_banner_show_animation (NULL, NULL, 
2435                                                                   _CS("sfil_ib_saving"));
2436                 info->pairs = files_to_save;
2437                 info->banner = banner;
2438                 info->result = TRUE;
2439                 g_object_ref (banner);
2440                 save_mime_parts_to_file_with_checks (info);
2441         }
2442 }
2443
2444 static gboolean
2445 show_remove_attachment_information (gpointer userdata)
2446 {
2447         ModestMsgViewWindow *window = (ModestMsgViewWindow *) userdata;
2448         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2449
2450         if (priv->remove_attachment_banner != NULL) {
2451                 gtk_widget_destroy (priv->remove_attachment_banner);
2452                 g_object_unref (priv->remove_attachment_banner);
2453         }
2454
2455         priv->remove_attachment_banner = g_object_ref (
2456                 hildon_banner_show_animation (NULL, NULL, _("mcen_ib_removing_attachment")));
2457
2458         return FALSE;
2459 }
2460
2461 void
2462 modest_msg_view_window_remove_attachments (ModestMsgViewWindow *window, gboolean get_all)
2463 {
2464         ModestMsgViewWindowPrivate *priv;
2465         GList *mime_parts = NULL, *node;
2466         gchar *confirmation_message;
2467         gint response;
2468         gint n_attachments;
2469         TnyMsg *msg;
2470 /*      TnyFolder *folder; */
2471
2472         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
2473         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2474
2475         if (get_all)
2476                 mime_parts = modest_msg_view_get_attachments (MODEST_MSG_VIEW (priv->msg_view));
2477         else
2478                 mime_parts = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2479                 
2480         /* Remove already purged messages from mime parts list */
2481         node = mime_parts;
2482         while (node != NULL) {
2483                 TnyMimePart *part = TNY_MIME_PART (node->data);
2484                 if (tny_mime_part_is_purged (part)) {
2485                         GList *deleted_node = node;
2486                         node = g_list_next (node);
2487                         g_object_unref (part);
2488                         mime_parts = g_list_delete_link (mime_parts, deleted_node);
2489                 } else {
2490                         node = g_list_next (node);
2491                 }
2492         }
2493
2494         if (mime_parts == NULL)
2495                 return;
2496
2497         n_attachments = g_list_length (mime_parts);
2498         if (n_attachments == 1) {
2499                 const gchar *filename;
2500
2501                 if (TNY_IS_MSG (mime_parts->data)) {
2502                         TnyHeader *header;
2503                         header = tny_msg_get_header (TNY_MSG (mime_parts->data));
2504                         filename = tny_header_get_subject (header);
2505                         g_object_unref (header);
2506                         if (filename == NULL)
2507                                 filename = _("mail_va_no_subject");
2508                 } else {
2509                         filename = tny_mime_part_get_filename (TNY_MIME_PART (mime_parts->data));
2510                 }
2511                 confirmation_message = g_strdup_printf (_("mcen_nc_purge_file_text"), filename);
2512         } else {
2513                 confirmation_message = g_strdup_printf (ngettext("mcen_nc_purge_file_text", 
2514                                                                  "mcen_nc_purge_files_text", 
2515                                                                  n_attachments), n_attachments);
2516         }
2517         response = modest_platform_run_confirmation_dialog (GTK_WINDOW (window),
2518                                                             confirmation_message);
2519         g_free (confirmation_message);
2520
2521         if (response != GTK_RESPONSE_OK)
2522                 return;
2523
2524         priv->purge_timeout = g_timeout_add (2000, show_remove_attachment_information, window);
2525 /*      folder = tny_msg_get_folder (msg); */
2526 /*      tny_msg_uncache_attachments (msg); */
2527 /*      tny_folder_refresh (folder, NULL); */
2528 /*      g_object_unref (folder); */
2529         
2530         for (node = mime_parts; node != NULL; node = g_list_next (node)) {
2531                 tny_mime_part_set_purged (TNY_MIME_PART (node->data));
2532 /*              modest_msg_view_remove_attachment (MODEST_MSG_VIEW (priv->msg_view), node->data); */
2533         }
2534
2535         msg = modest_msg_view_get_message (MODEST_MSG_VIEW (priv->msg_view));
2536         modest_msg_view_set_message (MODEST_MSG_VIEW (priv->msg_view), NULL);
2537         tny_msg_rewrite_cache (msg);
2538         modest_msg_view_set_message (MODEST_MSG_VIEW (priv->msg_view), msg);
2539
2540         g_list_foreach (mime_parts, (GFunc) g_object_unref, NULL);
2541         g_list_free (mime_parts);
2542
2543         if (priv->purge_timeout > 0) {
2544                 g_source_remove (priv->purge_timeout);
2545                 priv->purge_timeout = 0;
2546         }
2547
2548         if (priv->remove_attachment_banner) {
2549                 gtk_widget_destroy (priv->remove_attachment_banner);
2550                 g_object_unref (priv->remove_attachment_banner);
2551                 priv->remove_attachment_banner = NULL;
2552         }
2553
2554
2555 }
2556
2557
2558 static void
2559 update_window_title (ModestMsgViewWindow *window)
2560 {
2561         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2562         TnyMsg *msg = NULL;
2563         TnyHeader *header = NULL;
2564         const gchar *subject = NULL;
2565
2566         msg = modest_msg_view_get_message (MODEST_MSG_VIEW (priv->msg_view));
2567         if (msg != NULL) {
2568                 header = tny_msg_get_header (msg);
2569                 subject = tny_header_get_subject (header);
2570                 g_object_unref (msg);
2571         }
2572
2573         if ((subject == NULL)||(subject[0] == '\0'))
2574                 subject = _("mail_va_no_subject");
2575
2576         gtk_window_set_title (GTK_WINDOW (window), subject);
2577 }