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