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