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