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