7e0e61ba94b8fda6a07472585a661575c94e893e
[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                                  gboolean canceled,
144                                  TnyMsg *msg, 
145                                  GError *error,
146                                  gpointer user_data);
147
148 static void set_toolbar_mode    (ModestMsgViewWindow *self, 
149                                  ModestToolBarModes mode);
150
151 static void update_window_title (ModestMsgViewWindow *window);
152
153 static gboolean set_toolbar_transfer_mode     (ModestMsgViewWindow *self); 
154
155
156 /* list my signals */
157 enum {
158         MSG_CHANGED_SIGNAL,
159         LAST_SIGNAL
160 };
161
162 static const GtkToggleActionEntry msg_view_toggle_action_entries [] = {
163         { "FindInMessage",    MODEST_TOOLBAR_ICON_FIND,    N_("qgn_toolb_gene_find"), NULL, NULL, G_CALLBACK (modest_msg_view_window_toggle_find_toolbar), FALSE },
164         { "ToolsFindInMessage", NULL, N_("mcen_me_viewer_find"), "<CTRL>F", NULL, G_CALLBACK (modest_msg_view_window_toggle_find_toolbar), FALSE },
165 };
166
167 static const GtkRadioActionEntry msg_view_zoom_action_entries [] = {
168         { "Zoom50", NULL, N_("mcen_me_viewer_50"), NULL, NULL, 50 },
169         { "Zoom80", NULL, N_("mcen_me_viewer_80"), NULL, NULL, 80 },
170         { "Zoom100", NULL, N_("mcen_me_viewer_100"), NULL, NULL, 100 },
171         { "Zoom120", NULL, N_("mcen_me_viewer_120"), NULL, NULL, 120 },
172         { "Zoom150", NULL, N_("mcen_me_viewer_150"), NULL, NULL, 150 },
173         { "Zoom200", NULL, N_("mcen_me_viewer_200"), NULL, NULL, 200 }
174 };
175
176 typedef struct _ModestMsgViewWindowPrivate ModestMsgViewWindowPrivate;
177 struct _ModestMsgViewWindowPrivate {
178
179         GtkWidget   *msg_view;
180         GtkWidget   *main_scroll;
181         GtkWidget   *find_toolbar;
182         gchar       *last_search;
183
184         /* Progress observers */
185         GtkWidget        *progress_bar;
186         GSList           *progress_widgets;
187
188         /* Tollbar items */
189         GtkWidget   *progress_toolitem;
190         GtkWidget   *cancel_toolitem;
191         GtkWidget   *prev_toolitem;
192         GtkWidget   *next_toolitem;
193         ModestToolBarModes current_toolbar_mode;
194
195         /* Optimized view enabled */
196         gboolean optimized_view;
197
198         /* Whether this was created via the *_new_for_search_result() function. */
199         gboolean is_search_result;
200         
201         /* A reference to the @model of the header view 
202          * to allow selecting previous/next messages,
203          * if the message is currently selected in the header view.
204          */
205         const gchar *header_folder_id;
206         GtkTreeModel *header_model;
207         GtkTreeRowReference *row_reference;
208         GtkTreeRowReference *next_row_reference;
209
210         gulong clipboard_change_handler;
211         gulong queue_change_handler;
212         gulong account_removed_handler;
213         gulong row_changed_handler;
214         gulong row_deleted_handler;
215         gulong row_inserted_handler;
216         gulong rows_reordered_handler;
217
218         guint purge_timeout;
219         GtkWidget *remove_attachment_banner;
220
221         guint progress_bar_timeout;
222
223         gchar *msg_uid;
224 };
225
226 #define MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(o)      (G_TYPE_INSTANCE_GET_PRIVATE((o), \
227                                                     MODEST_TYPE_MSG_VIEW_WINDOW, \
228                                                     ModestMsgViewWindowPrivate))
229 /* globals */
230 static GtkWindowClass *parent_class = NULL;
231
232 /* uncomment the following if you have defined any signals */
233 static guint signals[LAST_SIGNAL] = {0};
234
235 GType
236 modest_msg_view_window_get_type (void)
237 {
238         static GType my_type = 0;
239         if (!my_type) {
240                 static const GTypeInfo my_info = {
241                         sizeof(ModestMsgViewWindowClass),
242                         NULL,           /* base init */
243                         NULL,           /* base finalize */
244                         (GClassInitFunc) modest_msg_view_window_class_init,
245                         NULL,           /* class finalize */
246                         NULL,           /* class data */
247                         sizeof(ModestMsgViewWindow),
248                         1,              /* n_preallocs */
249                         (GInstanceInitFunc) modest_msg_view_window_init,
250                         NULL
251                 };
252                 my_type = g_type_register_static (MODEST_TYPE_WINDOW,
253                                                   "ModestMsgViewWindow",
254                                                   &my_info, 0);
255
256                 static const GInterfaceInfo modest_header_view_observer_info = 
257                 {
258                         (GInterfaceInitFunc) modest_header_view_observer_init,
259                         NULL,         /* interface_finalize */
260                         NULL          /* interface_data */
261                 };
262
263                 g_type_add_interface_static (my_type,
264                                 MODEST_TYPE_HEADER_VIEW_OBSERVER,
265                                 &modest_header_view_observer_info);
266         }
267         return my_type;
268 }
269
270 static void
271 save_state (ModestWindow *self)
272 {
273         modest_widget_memory_save (modest_runtime_get_conf (),
274                                    G_OBJECT(self), 
275                                    MODEST_CONF_MSG_VIEW_WINDOW_KEY);
276 }
277
278
279 static void
280 restore_settings (ModestMsgViewWindow *self)
281 {
282         modest_widget_memory_restore (modest_runtime_get_conf (),
283                                       G_OBJECT(self), 
284                                       MODEST_CONF_MSG_VIEW_WINDOW_KEY);
285 }
286
287 static void
288 modest_msg_view_window_class_init (ModestMsgViewWindowClass *klass)
289 {
290         GObjectClass *gobject_class;
291         ModestWindowClass *modest_window_class;
292         gobject_class = (GObjectClass*) klass;
293         modest_window_class = (ModestWindowClass *) klass;
294
295         parent_class            = g_type_class_peek_parent (klass);
296         gobject_class->finalize = modest_msg_view_window_finalize;
297
298         modest_window_class->set_zoom_func = modest_msg_view_window_set_zoom;
299         modest_window_class->get_zoom_func = modest_msg_view_window_get_zoom;
300         modest_window_class->zoom_minus_func = modest_msg_view_window_zoom_minus;
301         modest_window_class->zoom_plus_func = modest_msg_view_window_zoom_plus;
302         modest_window_class->show_toolbar_func = modest_msg_view_window_show_toolbar;
303         modest_window_class->disconnect_signals_func = modest_msg_view_window_disconnect_signals;
304
305         g_type_class_add_private (gobject_class, sizeof(ModestMsgViewWindowPrivate));
306
307         modest_window_class->save_state_func = save_state;
308
309         signals[MSG_CHANGED_SIGNAL] =
310                 g_signal_new ("msg-changed",
311                               G_TYPE_FROM_CLASS (gobject_class),
312                               G_SIGNAL_RUN_FIRST,
313                               G_STRUCT_OFFSET (ModestMsgViewWindowClass, msg_changed),
314                               NULL, NULL,
315                               modest_marshal_VOID__POINTER_POINTER,
316                               G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER);
317 }
318
319 static void modest_header_view_observer_init(
320                 ModestHeaderViewObserverIface *iface_class)
321 {
322         iface_class->update_func = modest_msg_view_window_update_model_replaced;
323 }
324
325 static void
326 modest_msg_view_window_init (ModestMsgViewWindow *obj)
327 {
328         ModestMsgViewWindowPrivate *priv;
329         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(obj);
330
331         priv->is_search_result = FALSE;
332
333         priv->msg_view      = NULL;
334         priv->header_model  = NULL;
335         priv->header_folder_id  = NULL;
336         priv->clipboard_change_handler = 0;
337         priv->queue_change_handler = 0;
338         priv->account_removed_handler = 0;
339         priv->row_changed_handler = 0;
340         priv->row_deleted_handler = 0;
341         priv->row_inserted_handler = 0;
342         priv->rows_reordered_handler = 0;
343         priv->current_toolbar_mode = TOOLBAR_MODE_NORMAL;
344
345         priv->optimized_view  = FALSE;
346         priv->progress_bar_timeout = 0;
347         priv->purge_timeout = 0;
348         priv->remove_attachment_banner = NULL;
349         priv->msg_uid = NULL;
350         
351         modest_window_mgr_register_help_id (modest_runtime_get_window_mgr(),
352                                             GTK_WINDOW(obj),"applications_email_viewer");
353 }
354
355
356 static gboolean
357 set_toolbar_transfer_mode (ModestMsgViewWindow *self)
358 {
359         ModestMsgViewWindowPrivate *priv = NULL;
360         
361         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), FALSE);
362
363         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
364
365         set_toolbar_mode (self, TOOLBAR_MODE_TRANSFER);
366         
367         if (priv->progress_bar_timeout > 0) {
368                 g_source_remove (priv->progress_bar_timeout);
369                 priv->progress_bar_timeout = 0;
370         }
371         
372         return FALSE;
373 }
374
375 static void 
376 set_toolbar_mode (ModestMsgViewWindow *self, 
377                   ModestToolBarModes mode)
378 {
379         ModestWindowPrivate *parent_priv;
380         ModestMsgViewWindowPrivate *priv;
381 /*      GtkWidget *widget = NULL; */
382
383         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self));
384
385         parent_priv = MODEST_WINDOW_GET_PRIVATE(self);
386         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
387                         
388         /* Sets current toolbar mode */
389         priv->current_toolbar_mode = mode;
390
391         /* Update toolbar dimming state */
392         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (self));
393
394         switch (mode) {
395         case TOOLBAR_MODE_NORMAL:               
396 /*              widget = gtk_ui_manager_get_action (parent_priv->ui_manager, "/ToolBar/ToolbarMessageReply"); */
397 /*              gtk_action_set_sensitive (widget, TRUE); */
398 /*              widget = gtk_ui_manager_get_action (parent_priv->ui_manager, "/ToolBar/ToolbarDeleteMessage"); */
399 /*              gtk_action_set_sensitive (widget, TRUE); */
400 /*              widget = gtk_ui_manager_get_action (parent_priv->ui_manager, "/ToolBar/ToolbarMessageMoveTo"); */
401 /*              gtk_action_set_sensitive (widget, TRUE); */
402 /*              widget = gtk_ui_manager_get_action (parent_priv->ui_manager, "/ToolBar/FindInMessage"); */
403 /*              gtk_action_set_sensitive (widget, TRUE); */
404
405                 if (priv->prev_toolitem)
406                         gtk_widget_show (priv->prev_toolitem);
407                 
408                 if (priv->next_toolitem)
409                         gtk_widget_show (priv->next_toolitem);
410                         
411                 if (priv->progress_toolitem)
412                         gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->progress_toolitem), FALSE);
413                 if (priv->progress_bar)
414                         gtk_widget_hide (priv->progress_bar);
415                         
416                 if (priv->cancel_toolitem)
417                         gtk_widget_hide (priv->cancel_toolitem);
418
419                 /* Hide toolbar if optimized view is enabled */
420                 if (priv->optimized_view) {
421                         gtk_widget_set_no_show_all (parent_priv->toolbar, TRUE);
422                         gtk_widget_hide (GTK_WIDGET(parent_priv->toolbar));
423                 }
424
425                 break;
426         case TOOLBAR_MODE_TRANSFER:
427 /*              widget = gtk_ui_manager_get_action (parent_priv->ui_manager, "/ToolBar/ToolbarMessageReply"); */
428 /*              gtk_action_set_sensitive (widget, FALSE); */
429 /*              widget = gtk_ui_manager_get_action (parent_priv->ui_manager, "/ToolBar/ToolbarDeleteMessage"); */
430 /*              gtk_action_set_sensitive (widget, FALSE); */
431 /*              widget = gtk_ui_manager_get_action (parent_priv->ui_manager, "/ToolBar/ToolbarMessageMoveTo"); */
432 /*              gtk_action_set_sensitive (widget, FALSE); */
433 /*              widget = gtk_ui_manager_get_action (parent_priv->ui_manager, "/ToolBar/FindInMessage"); */
434 /*              gtk_action_set_sensitive (widget, FALSE); */
435
436                 if (priv->prev_toolitem)
437                         gtk_widget_hide (priv->prev_toolitem);
438                 
439                 if (priv->next_toolitem)
440                         gtk_widget_hide (priv->next_toolitem);
441                 
442                 if (priv->progress_toolitem)
443                         gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->progress_toolitem), TRUE);
444                 if (priv->progress_bar)
445                         gtk_widget_show (priv->progress_bar);
446                         
447                 if (priv->cancel_toolitem)
448                         gtk_widget_show (priv->cancel_toolitem);
449
450                 /* Show toolbar if it's hiden (optimized view ) */
451                 if (priv->optimized_view) {
452                         gtk_widget_set_no_show_all (parent_priv->toolbar, FALSE);
453                         gtk_widget_show (GTK_WIDGET (parent_priv->toolbar));
454                 }
455
456                 break;
457         default:
458                 g_return_if_reached ();
459         }
460
461 }
462
463
464 static GtkWidget *
465 menubar_to_menu (GtkUIManager *ui_manager)
466 {
467         GtkWidget *main_menu;
468         GtkWidget *menubar;
469         GList *iter, *children;
470
471         /* Create new main menu */
472         main_menu = gtk_menu_new();
473
474         /* Get the menubar from the UI manager */
475         menubar = gtk_ui_manager_get_widget (ui_manager, "/MenuBar");
476
477         iter = children = gtk_container_get_children (GTK_CONTAINER (menubar));
478         while (iter) {
479                 GtkWidget *menu;
480
481                 menu = GTK_WIDGET (iter->data);
482                 gtk_widget_reparent(menu, main_menu);
483                 
484                 iter = g_list_next (iter);
485         }
486         g_list_free (children);
487                      
488         return main_menu;
489 }
490
491 static void
492 init_window (ModestMsgViewWindow *obj)
493 {
494         GtkWidget *main_vbox;
495         ModestMsgViewWindowPrivate *priv;
496         ModestWindowPrivate *parent_priv;
497         ModestConf *conf;
498         GtkAction *action = NULL;
499
500         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(obj);
501         parent_priv = MODEST_WINDOW_GET_PRIVATE(obj);
502
503         priv->msg_view = GTK_WIDGET (tny_platform_factory_new_msg_view (modest_tny_platform_factory_get_instance ()));
504         modest_msg_view_set_shadow_type (MODEST_MSG_VIEW (priv->msg_view), GTK_SHADOW_NONE);
505         main_vbox = gtk_vbox_new  (FALSE, 6);
506
507         /* Menubar */
508         parent_priv->menubar = menubar_to_menu (parent_priv->ui_manager);
509         conf = modest_runtime_get_conf ();
510         action = gtk_ui_manager_get_action (parent_priv->ui_manager, 
511                                             "/MenuBar/ViewMenu/ViewShowToolbarMenu/ViewShowToolbarNormalScreenMenu");
512         gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action),
513                                       modest_conf_get_bool (conf, MODEST_CONF_MSG_VIEW_WINDOW_SHOW_TOOLBAR, NULL));
514         action = gtk_ui_manager_get_action (parent_priv->ui_manager, 
515                                             "/MenuBar/ViewMenu/ViewShowToolbarMenu/ViewShowToolbarFullScreenMenu");
516         gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action),
517                                       modest_conf_get_bool (conf, MODEST_CONF_MSG_VIEW_WINDOW_SHOW_TOOLBAR_FULLSCREEN, NULL));
518         hildon_window_set_menu    (HILDON_WINDOW(obj), GTK_MENU(parent_priv->menubar));
519         gtk_widget_show (GTK_WIDGET(parent_priv->menubar));
520
521 #ifdef MODEST_USE_MOZEMBED
522         priv->main_scroll = priv->msg_view;
523         gtk_widget_set_size_request (priv->msg_view, -1, 1600);
524 #else
525         priv->main_scroll = gtk_scrolled_window_new (NULL, NULL);
526         gtk_container_add (GTK_CONTAINER (priv->main_scroll), priv->msg_view);
527 #endif
528         gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->main_scroll), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
529         gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (priv->main_scroll), GTK_SHADOW_NONE);
530         modest_maemo_set_thumbable_scrollbar (GTK_SCROLLED_WINDOW(priv->main_scroll), TRUE);
531
532         gtk_box_pack_start (GTK_BOX(main_vbox), priv->main_scroll, TRUE, TRUE, 0);
533         gtk_container_add   (GTK_CONTAINER(obj), main_vbox);
534
535         priv->find_toolbar = hildon_find_toolbar_new (NULL);
536         hildon_window_add_toolbar (HILDON_WINDOW (obj), GTK_TOOLBAR (priv->find_toolbar));
537         gtk_widget_set_no_show_all (priv->find_toolbar, TRUE);
538         g_signal_connect (G_OBJECT (priv->find_toolbar), "close", G_CALLBACK (modest_msg_view_window_find_toolbar_close), obj);
539         g_signal_connect (G_OBJECT (priv->find_toolbar), "search", G_CALLBACK (modest_msg_view_window_find_toolbar_search), obj);
540         
541         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);
542         gtk_widget_show_all (GTK_WIDGET(main_vbox));
543 }
544
545 static void
546 modest_msg_view_window_disconnect_signals (ModestWindow *self)
547 {
548         ModestMsgViewWindowPrivate *priv;
549         ModestHeaderView *header_view = NULL;
550         ModestWindow *main_window = NULL;
551         
552         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
553
554         if (gtk_clipboard_get (GDK_SELECTION_PRIMARY) &&
555             g_signal_handler_is_connected (gtk_clipboard_get (GDK_SELECTION_PRIMARY),
556                                            priv->clipboard_change_handler)) 
557                 g_signal_handler_disconnect (gtk_clipboard_get (GDK_SELECTION_PRIMARY), 
558                                              priv->clipboard_change_handler);
559
560         if (g_signal_handler_is_connected (G_OBJECT (modest_runtime_get_mail_operation_queue ()), 
561                                            priv->queue_change_handler))
562                 g_signal_handler_disconnect (G_OBJECT (modest_runtime_get_mail_operation_queue ()), 
563                                              priv->queue_change_handler);
564
565         if (g_signal_handler_is_connected (G_OBJECT (modest_runtime_get_account_store ()), 
566                                            priv->account_removed_handler))
567                 g_signal_handler_disconnect (G_OBJECT (modest_runtime_get_account_store ()), 
568                                              priv->account_removed_handler);
569
570         if (priv->header_model) {
571                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
572                                                   priv->row_changed_handler))
573                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
574                                                     priv->row_changed_handler);
575                 
576                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
577                                                   priv->row_deleted_handler))
578                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
579                                              priv->row_deleted_handler);
580                 
581                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
582                                                   priv->row_inserted_handler))
583                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
584                                                     priv->row_inserted_handler);
585                 
586                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
587                                                   priv->rows_reordered_handler))
588                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
589                                                     priv->rows_reordered_handler);
590         }
591
592         main_window = modest_window_mgr_get_main_window (modest_runtime_get_window_mgr(),
593                                                          FALSE); /* don't create */
594         if (!main_window)
595                 return;
596         
597         header_view = MODEST_HEADER_VIEW(
598                         modest_main_window_get_child_widget(
599                                 MODEST_MAIN_WINDOW(main_window),
600                                 MODEST_MAIN_WINDOW_WIDGET_TYPE_HEADER_VIEW));
601         if (header_view == NULL)
602                 return;
603         
604         modest_header_view_remove_observer(header_view,
605                         MODEST_HEADER_VIEW_OBSERVER(self));
606 }       
607
608 static void
609 modest_msg_view_window_finalize (GObject *obj)
610 {
611         ModestMsgViewWindowPrivate *priv;
612
613         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (obj);
614
615         /* Sanity check: shouldn't be needed, the window mgr should
616            call this function before */
617         modest_msg_view_window_disconnect_signals (MODEST_WINDOW (obj));
618
619         if (priv->header_model != NULL) {
620                 g_object_unref (priv->header_model);
621                 priv->header_model = NULL;
622         }
623
624         if (priv->progress_bar_timeout > 0) {
625                 g_source_remove (priv->progress_bar_timeout);
626                 priv->progress_bar_timeout = 0;
627         }
628
629         if (priv->remove_attachment_banner) {
630                 gtk_widget_destroy (priv->remove_attachment_banner);
631                 g_object_unref (priv->remove_attachment_banner);
632                 priv->remove_attachment_banner = NULL;
633         }
634
635         if (priv->purge_timeout > 0) {
636                 g_source_remove (priv->purge_timeout);
637                 priv->purge_timeout = 0;
638         }
639
640         if (priv->row_reference) {
641                 gtk_tree_row_reference_free (priv->row_reference);
642                 priv->row_reference = NULL;
643         }
644
645         if (priv->next_row_reference) {
646                 gtk_tree_row_reference_free (priv->next_row_reference);
647                 priv->next_row_reference = NULL;
648         }
649
650         if (priv->msg_uid) {
651                 g_free (priv->msg_uid);
652                 priv->msg_uid = NULL;
653         }
654
655         G_OBJECT_CLASS(parent_class)->finalize (obj);
656 }
657
658 static gboolean
659 select_next_valid_row (GtkTreeModel *model,
660                        GtkTreeRowReference **row_reference,
661                        gboolean cycle)
662 {
663         GtkTreeIter tmp_iter;
664         GtkTreePath *path, *next;
665         gboolean retval = FALSE;
666
667         g_return_val_if_fail (gtk_tree_row_reference_valid (*row_reference), FALSE);
668
669         path = gtk_tree_row_reference_get_path (*row_reference);
670         gtk_tree_model_get_iter (model, &tmp_iter, path);
671         gtk_tree_row_reference_free (*row_reference);
672         *row_reference = NULL;
673
674         if (gtk_tree_model_iter_next (model, &tmp_iter)) {
675                 next = gtk_tree_model_get_path (model, &tmp_iter);
676                 *row_reference = gtk_tree_row_reference_new (model, next);
677                 retval = TRUE;
678         } else if (cycle && gtk_tree_model_get_iter_first (model, &tmp_iter)) {
679                 next = gtk_tree_model_get_path (model, &tmp_iter);
680
681                 /* Ensure that we are not selecting the same */
682                 if (gtk_tree_path_compare (path, next) != 0) {
683                         *row_reference = gtk_tree_row_reference_new (model, next);
684                         retval = TRUE;
685                 }
686         }
687
688         /* Free */
689         gtk_tree_path_free (path);
690
691         return retval;
692 }
693
694 /* TODO: This should be in _init(), with the parameters as properties. */
695 static void
696 modest_msg_view_window_construct (ModestMsgViewWindow *self, 
697                             const gchar *modest_account_name,
698                             const gchar *msg_uid)
699 {
700         GObject *obj = NULL;
701         ModestMsgViewWindowPrivate *priv = NULL;
702         ModestWindowPrivate *parent_priv = NULL;
703         ModestDimmingRulesGroup *menu_rules_group = NULL;
704         ModestDimmingRulesGroup *toolbar_rules_group = NULL;
705         ModestDimmingRulesGroup *clipboard_rules_group = NULL;
706         GtkActionGroup *action_group = NULL;
707         GError *error = NULL;
708         GdkPixbuf *window_icon;
709
710         obj = G_OBJECT (self);
711         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(obj);
712         parent_priv = MODEST_WINDOW_GET_PRIVATE(obj);
713
714         priv->msg_uid = g_strdup (msg_uid);
715
716         parent_priv->ui_manager = gtk_ui_manager_new();
717         parent_priv->ui_dimming_manager = modest_ui_dimming_manager_new();
718
719         action_group = gtk_action_group_new ("ModestMsgViewWindowActions");
720         gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE);
721
722         menu_rules_group = modest_dimming_rules_group_new ("ModestMenuDimmingRules", FALSE);
723         toolbar_rules_group = modest_dimming_rules_group_new ("ModestToolbarDimmingRules", TRUE);
724         clipboard_rules_group = modest_dimming_rules_group_new ("ModestClipboardDimmingRules", FALSE);
725
726         /* Add common actions */
727         gtk_action_group_add_actions (action_group,
728                                       modest_action_entries,
729                                       G_N_ELEMENTS (modest_action_entries),
730                                       obj);
731         gtk_action_group_add_toggle_actions (action_group,
732                                              modest_toggle_action_entries,
733                                              G_N_ELEMENTS (modest_toggle_action_entries),
734                                              obj);
735         gtk_action_group_add_toggle_actions (action_group,
736                                              msg_view_toggle_action_entries,
737                                              G_N_ELEMENTS (msg_view_toggle_action_entries),
738                                              obj);
739         gtk_action_group_add_radio_actions (action_group,
740                                             msg_view_zoom_action_entries,
741                                             G_N_ELEMENTS (msg_view_zoom_action_entries),
742                                             100,
743                                             G_CALLBACK (modest_ui_actions_on_change_zoom),
744                                             obj);
745
746         gtk_ui_manager_insert_action_group (parent_priv->ui_manager, action_group, 0);
747         g_object_unref (action_group);
748
749         /* Load the UI definition */
750         gtk_ui_manager_add_ui_from_file (parent_priv->ui_manager, MODEST_UIDIR "modest-msg-view-window-ui.xml",
751                                          &error);
752         if (error) {
753                 g_printerr ("modest: could not merge modest-msg-view-window-ui.xml: %s\n", error->message);
754                 g_error_free (error);
755                 error = NULL;
756         }
757         /* ****** */
758
759         /* Add common dimming rules */
760         modest_dimming_rules_group_add_rules (menu_rules_group, 
761                                               modest_msg_view_menu_dimming_entries,
762                                               G_N_ELEMENTS (modest_msg_view_menu_dimming_entries),
763                                               MODEST_WINDOW (self));
764         modest_dimming_rules_group_add_rules (toolbar_rules_group, 
765                                               modest_msg_view_toolbar_dimming_entries,
766                                               G_N_ELEMENTS (modest_msg_view_toolbar_dimming_entries),
767                                               MODEST_WINDOW (self));
768         modest_dimming_rules_group_add_rules (clipboard_rules_group, 
769                                               modest_msg_view_clipboard_dimming_entries,
770                                               G_N_ELEMENTS (modest_msg_view_clipboard_dimming_entries),
771                                               MODEST_WINDOW (self));
772
773         /* Insert dimming rules group for this window */
774         modest_ui_dimming_manager_insert_rules_group (parent_priv->ui_dimming_manager, menu_rules_group);
775         modest_ui_dimming_manager_insert_rules_group (parent_priv->ui_dimming_manager, toolbar_rules_group);
776         modest_ui_dimming_manager_insert_rules_group (parent_priv->ui_dimming_manager, clipboard_rules_group);
777         g_object_unref (menu_rules_group);
778         g_object_unref (toolbar_rules_group);
779         g_object_unref (clipboard_rules_group);
780
781         /* Add accelerators */
782         gtk_window_add_accel_group (GTK_WINDOW (obj), 
783                                     gtk_ui_manager_get_accel_group (parent_priv->ui_manager));
784         
785         /* Init window */
786         init_window (MODEST_MSG_VIEW_WINDOW(obj));
787         restore_settings (MODEST_MSG_VIEW_WINDOW(obj));
788         
789         /* Set window icon */
790         window_icon = modest_platform_get_icon (MODEST_APP_MSG_VIEW_ICON); 
791         if (window_icon) {
792                 gtk_window_set_icon (GTK_WINDOW (obj), window_icon);
793                 g_object_unref (window_icon);
794         }
795
796         /* g_signal_connect (G_OBJECT(obj), "delete-event", G_CALLBACK(on_delete_event), obj); */
797
798         g_signal_connect (G_OBJECT(priv->msg_view), "activate_link",
799                           G_CALLBACK (modest_ui_actions_on_msg_link_clicked), obj);
800         g_signal_connect (G_OBJECT(priv->msg_view), "link_hover",
801                           G_CALLBACK (modest_ui_actions_on_msg_link_hover), obj);
802         g_signal_connect (G_OBJECT(priv->msg_view), "attachment_clicked",
803                           G_CALLBACK (modest_ui_actions_on_msg_attachment_clicked), obj);
804         g_signal_connect (G_OBJECT(priv->msg_view), "recpt_activated",
805                           G_CALLBACK (modest_ui_actions_on_msg_recpt_activated), obj);
806         g_signal_connect (G_OBJECT(priv->msg_view), "link_contextual",
807                           G_CALLBACK (modest_ui_actions_on_msg_link_contextual), obj);
808
809         g_signal_connect (G_OBJECT (obj), "key-release-event",
810                           G_CALLBACK (modest_msg_view_window_key_release_event),
811                           NULL);
812
813         g_signal_connect (G_OBJECT (obj), "window-state-event",
814                           G_CALLBACK (modest_msg_view_window_window_state_event),
815                           NULL);
816
817         g_signal_connect (G_OBJECT (obj), "move-focus",
818                           G_CALLBACK (on_move_focus), obj);
819
820         /* Mail Operation Queue */
821         priv->queue_change_handler = g_signal_connect (G_OBJECT (modest_runtime_get_mail_operation_queue ()),
822                                                        "queue-changed",
823                                                        G_CALLBACK (on_queue_changed),
824                                                        obj);
825
826         /* Account manager */
827         priv->account_removed_handler = g_signal_connect (G_OBJECT (modest_runtime_get_account_store ()),
828                                                           "account_removed",
829                                                           G_CALLBACK(on_account_removed),
830                                                           obj);
831
832         modest_window_set_active_account (MODEST_WINDOW(obj), modest_account_name);
833
834         priv->last_search = NULL;
835
836         /* Init the clipboard actions dim status */
837         modest_msg_view_grab_focus(MODEST_MSG_VIEW (priv->msg_view));
838
839         update_window_title (MODEST_MSG_VIEW_WINDOW (obj));
840
841         /* Check toolbar dimming rules */
842         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (obj));
843         modest_window_check_dimming_rules_group (MODEST_WINDOW (obj), "ModestClipboardDimmingRules");
844
845 }
846
847 /* FIXME: parameter checks */
848 ModestWindow *
849 modest_msg_view_window_new_with_header_model (TnyMsg *msg, 
850                                               const gchar *modest_account_name,
851                                               const gchar *msg_uid,
852                                               GtkTreeModel *model, 
853                                               GtkTreeRowReference *row_reference)
854 {
855         ModestMsgViewWindow *window = NULL;
856         ModestMsgViewWindowPrivate *priv = NULL;
857         TnyFolder *header_folder = NULL;
858         ModestHeaderView *header_view = NULL;
859         ModestWindow *main_window = NULL;
860         
861         window = g_object_new(MODEST_TYPE_MSG_VIEW_WINDOW, NULL);
862         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), NULL);
863
864         modest_msg_view_window_construct (window, modest_account_name, msg_uid);
865
866         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
867
868         /* Remember the message list's TreeModel so we can detect changes 
869          * and change the list selection when necessary: */
870
871         main_window = modest_window_mgr_get_main_window(
872                 modest_runtime_get_window_mgr(), FALSE); /* don't create */
873         if (!main_window) {
874                 g_warning ("%s: BUG: no main window", __FUNCTION__);
875                 return NULL;
876         }
877         
878         header_view = MODEST_HEADER_VIEW(modest_main_window_get_child_widget(
879                                                  MODEST_MAIN_WINDOW(main_window),
880                                                  MODEST_MAIN_WINDOW_WIDGET_TYPE_HEADER_VIEW));
881         if (header_view != NULL){
882                 header_folder = modest_header_view_get_folder(header_view);
883                 g_assert(header_folder != NULL);
884                 priv->header_folder_id = tny_folder_get_id(header_folder);
885                 g_assert(priv->header_folder_id != NULL);
886                 g_object_unref(header_folder);
887         }
888
889         priv->header_model = g_object_ref(model);
890         priv->row_reference = gtk_tree_row_reference_copy (row_reference);
891         priv->next_row_reference = gtk_tree_row_reference_copy (row_reference);
892         select_next_valid_row (model, &(priv->next_row_reference), TRUE);
893
894         priv->row_changed_handler = g_signal_connect(
895                         GTK_TREE_MODEL(model), "row-changed",
896                         G_CALLBACK(modest_msg_view_window_on_row_changed),
897                         window);
898         priv->row_deleted_handler = g_signal_connect(
899                         GTK_TREE_MODEL(model), "row-deleted",
900                         G_CALLBACK(modest_msg_view_window_on_row_deleted),
901                         window);
902         priv->row_inserted_handler = g_signal_connect (
903                         GTK_TREE_MODEL(model), "row-inserted",
904                         G_CALLBACK(modest_msg_view_window_on_row_inserted),
905                         window);
906         priv->rows_reordered_handler = g_signal_connect(
907                         GTK_TREE_MODEL(model), "rows-reordered",
908                         G_CALLBACK(modest_msg_view_window_on_row_reordered),
909                         window);
910
911         if (header_view != NULL){
912                 modest_header_view_add_observer(header_view,
913                                 MODEST_HEADER_VIEW_OBSERVER(window));
914         }
915
916         gtk_widget_show_all (GTK_WIDGET (window));
917
918         tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
919         update_window_title (MODEST_MSG_VIEW_WINDOW (window));
920
921         modest_msg_view_window_update_priority (window);
922
923         /* Check toolbar dimming rules */
924         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (window));
925
926         return MODEST_WINDOW(window);
927 }
928
929 ModestWindow *
930 modest_msg_view_window_new_for_search_result (TnyMsg *msg, 
931                                               const gchar *modest_account_name,
932                                               const gchar *msg_uid)
933 {
934         ModestMsgViewWindow *window = NULL;
935         ModestMsgViewWindowPrivate *priv = NULL;
936
937         window = g_object_new(MODEST_TYPE_MSG_VIEW_WINDOW, NULL);
938         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), NULL);
939         modest_msg_view_window_construct (window, modest_account_name, msg_uid);
940
941         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
942
943         /* Remember that this is a search result, 
944          * so we can disable some UI appropriately: */
945         priv->is_search_result = TRUE;
946
947         tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
948
949         return MODEST_WINDOW(window);
950 }
951
952 ModestWindow *
953 modest_msg_view_window_new_for_attachment (TnyMsg *msg, 
954                             const gchar *modest_account_name,
955                             const gchar *msg_uid)
956 {
957         GObject *obj = NULL;
958         ModestMsgViewWindowPrivate *priv;
959         g_return_val_if_fail (msg, NULL);
960         
961         obj = g_object_new(MODEST_TYPE_MSG_VIEW_WINDOW, NULL);
962         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (obj);
963         modest_msg_view_window_construct (MODEST_MSG_VIEW_WINDOW (obj), 
964                 modest_account_name, msg_uid);
965
966         tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
967
968         return MODEST_WINDOW(obj);
969 }
970
971 void modest_msg_view_window_on_row_changed(
972                 GtkTreeModel *header_model,
973                 GtkTreePath *arg1,
974                 GtkTreeIter *arg2,
975                 ModestMsgViewWindow *window){
976         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (window));
977 }
978
979 void modest_msg_view_window_on_row_deleted(
980                 GtkTreeModel *header_model,
981                 GtkTreePath *arg1,
982                 ModestMsgViewWindow *window){
983         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (window));
984 }
985
986 /* On insertions we check if the folder still has the message we are
987  * showing or do not. If do not, we do nothing. Which means we are still
988  * not attached to any header folder and thus next/prev buttons are
989  * still dimmed. Once the message that is shown by msg-view is found, the
990  * new model of header-view will be attached and the references will be set.
991  * On each further insertions dimming rules will be checked. However
992  * this requires extra CPU time at least works.
993  * (An message might be deleted from TnyFolder and thus will not be
994  * inserted into the model again for example if it is removed by the
995  * imap server and the header view is refreshed.)
996  */
997 void modest_msg_view_window_on_row_inserted(
998                 GtkTreeModel *new_model,
999                 GtkTreePath *tree_path,
1000                 GtkTreeIter *tree_iter,
1001                 ModestMsgViewWindow *window){
1002         ModestMsgViewWindowPrivate *priv = NULL; 
1003         TnyHeader *header = NULL;
1004         gchar *uid = NULL;
1005
1006         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
1007
1008         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1009         
1010         /* If we already has a model attached then the message shown by
1011          * msg-view is in it, and thus we do not need any actions but
1012          * to check the dimming rules.*/
1013         if(priv->header_model != NULL){
1014                 gtk_tree_row_reference_free(priv->next_row_reference);
1015                 priv->next_row_reference = gtk_tree_row_reference_copy(
1016                                 priv->row_reference);
1017                 select_next_valid_row (priv->header_model,
1018                                 &(priv->next_row_reference), FALSE);
1019                 modest_ui_actions_check_toolbar_dimming_rules (
1020                                 MODEST_WINDOW (window));
1021                 return;
1022         }
1023
1024         /* Check if the newly inserted message is the same we are actually
1025          * showing. IF not, we should remain detached from the header model
1026          * and thus prev and next toolbarbuttons should remain dimmed. */
1027         gtk_tree_model_get (new_model, tree_iter, 
1028                         TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header, -1);
1029         uid = modest_tny_folder_get_header_unique_id(header);
1030         if(!g_str_equal(priv->msg_uid, uid)){
1031                 g_free(uid);
1032                 return;
1033         }
1034         g_free(uid);
1035
1036         /* Setup row_reference for the actual msg. */
1037         priv->row_reference = gtk_tree_row_reference_new(
1038                         new_model, tree_path);
1039         if(priv->row_reference == NULL){
1040                 g_warning("No reference for msg header item.");
1041                 return;
1042         }
1043
1044         /* Attach new_model and connect some callback to it to become able
1045          * to detect changes in header-view. */
1046         priv->header_model = g_object_ref(new_model);
1047         g_signal_connect (new_model, "row-changed",
1048                         G_CALLBACK (modest_msg_view_window_on_row_changed),
1049                         window);
1050         g_signal_connect (new_model, "row-deleted",
1051                         G_CALLBACK (modest_msg_view_window_on_row_deleted),
1052                         window);
1053         g_signal_connect (new_model, "rows-reordered",
1054                         G_CALLBACK (modest_msg_view_window_on_row_reordered),
1055                         window);
1056
1057         /* Now set up next_row_reference. */
1058         priv->next_row_reference = gtk_tree_row_reference_copy(
1059                         priv->row_reference);
1060         select_next_valid_row (priv->header_model,
1061                         &(priv->next_row_reference), FALSE);
1062
1063         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (window));
1064 }
1065
1066 void modest_msg_view_window_on_row_reordered(
1067                 GtkTreeModel *header_model,
1068                 GtkTreePath *arg1,
1069                 GtkTreeIter *arg2,
1070                 gpointer arg3,
1071                 ModestMsgViewWindow *window){
1072         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (window));
1073 }
1074
1075 /* The modest_msg_view_window_update_model_replaced implements update
1076  * function for ModestHeaderViewObserver. Checks whether the TnyFolder
1077  * actually belongs to the header-view is the same as the TnyFolder of
1078  * the message of msg-view or not. If they are different, there is
1079  * nothing to do. If they are the same, then the model has replaced and
1080  * the reference in msg-view shall be replaced from the old model to
1081  * the new model. In this case the view will be detached from it's
1082  * header folder. From this point the next/prev buttons are dimmed.
1083  */
1084 void modest_msg_view_window_update_model_replaced(
1085                 ModestHeaderViewObserver *observer,
1086                 GtkTreeModel *model,
1087                 const gchar *tny_folder_id){
1088         ModestMsgViewWindowPrivate *priv = NULL; 
1089         ModestMsgViewWindow *window = NULL;
1090
1091         g_assert(MODEST_IS_HEADER_VIEW_OBSERVER(observer));
1092         g_assert(MODEST_IS_MSG_VIEW_WINDOW(observer));
1093
1094         window = MODEST_MSG_VIEW_WINDOW(observer);
1095         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(window);
1096
1097         /* If there is an other folder in the header-view then we do
1098          * not care about it's model (msg list). Else if the
1099          * header-view shows the folder the msg shown by us is in, we
1100          * shall replace our model reference and make some check. */
1101         if(tny_folder_id == NULL || !g_str_equal(tny_folder_id, priv->header_folder_id))
1102                 return;
1103         
1104         /* Model is changed(replaced), so we should forget the old
1105          * one. Because there might be other references and there
1106          * might be some change on the model even if we unreferenced
1107          * it, we need to disconnect our signals here. */
1108         if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
1109                                            priv->row_changed_handler))
1110                 g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
1111                                              priv->row_changed_handler);
1112         priv->row_changed_handler = 0;
1113         if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
1114                                            priv->row_deleted_handler))
1115                 g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
1116                                              priv->row_deleted_handler);
1117         priv->row_deleted_handler = 0;
1118         if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
1119                                            priv->row_inserted_handler))
1120                 g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
1121                                              priv->row_inserted_handler);
1122         priv->row_inserted_handler = 0;
1123         if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
1124                                            priv->rows_reordered_handler))
1125                 g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
1126                                              priv->rows_reordered_handler);
1127         priv->rows_reordered_handler = 0;
1128         g_object_unref(priv->header_model);
1129         priv->header_model = NULL;
1130         g_object_unref(priv->row_reference);
1131         priv->row_reference = NULL;
1132         g_object_unref(priv->next_row_reference);
1133         priv->next_row_reference = NULL;
1134
1135         modest_ui_actions_check_toolbar_dimming_rules(MODEST_WINDOW(window));
1136
1137         g_assert(model != NULL);
1138
1139         /* Also we must connect to the new model for row insertions.
1140          * Only for insertions now. We will need other ones only after
1141          * the msg is show by msg-view is added to the new model. */
1142         priv->row_inserted_handler = g_signal_connect (
1143                         model, "row-inserted",
1144                         G_CALLBACK(modest_msg_view_window_on_row_inserted),
1145                         window);
1146 }
1147
1148 gboolean 
1149 modest_msg_view_window_toolbar_on_transfer_mode     (ModestMsgViewWindow *self)
1150 {
1151         ModestMsgViewWindowPrivate *priv= NULL; 
1152
1153         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), FALSE);
1154         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
1155
1156         return priv->current_toolbar_mode == TOOLBAR_MODE_TRANSFER;
1157 }
1158
1159 TnyHeader*
1160 modest_msg_view_window_get_header (ModestMsgViewWindow *self)
1161 {
1162         ModestMsgViewWindowPrivate *priv= NULL; 
1163         TnyMsg *msg = NULL;
1164         TnyHeader *header = NULL;
1165         GtkTreePath *path = NULL;
1166         GtkTreeIter iter;
1167
1168         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), NULL);
1169         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
1170
1171         /* If the message was not obtained from a treemodel,
1172          * for instance if it was opened directly by the search UI:
1173          */
1174         if (priv->header_model == NULL) {
1175                 msg = modest_msg_view_window_get_message (self);
1176                 if (msg) {
1177                         header = tny_msg_get_header (msg);
1178                         g_object_unref (msg);
1179                 }
1180                 return header;
1181         }
1182
1183         /* Get iter of the currently selected message in the header view: */
1184         /* TODO: Why not just give this window a ref of the TnyHeader or TnyMessage,
1185          * instead of sometimes retrieving it from the header view?
1186          * Then we wouldn't be dependent on the message actually still being selected 
1187          * in the header view. murrayc. */
1188         if (!gtk_tree_row_reference_valid (priv->row_reference))
1189                 return NULL;
1190         path = gtk_tree_row_reference_get_path (priv->row_reference);
1191         g_return_val_if_fail (path != NULL, NULL);
1192         gtk_tree_model_get_iter (priv->header_model, 
1193                                  &iter, 
1194                                  path);
1195
1196         /* Get current message header */
1197         gtk_tree_model_get (priv->header_model, &iter, 
1198                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, 
1199                             &header, -1);
1200
1201         gtk_tree_path_free (path);
1202         return header;
1203 }
1204
1205 TnyMsg*
1206 modest_msg_view_window_get_message (ModestMsgViewWindow *self)
1207 {
1208         ModestMsgViewWindowPrivate *priv;
1209         
1210         g_return_val_if_fail (self, NULL);
1211         
1212         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
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              gboolean canceled,
1776              TnyMsg *msg, 
1777              GError *error,
1778              gpointer user_data)
1779 {
1780         ModestMsgViewWindow *self = NULL;
1781         ModestMsgViewWindowPrivate *priv = NULL;
1782         GtkTreeRowReference *row_reference = NULL;
1783
1784         /* If there was any error */
1785         row_reference = (GtkTreeRowReference *) user_data;
1786         if (!modest_ui_actions_msg_retrieval_check (mail_op, header, msg)) {
1787                 gtk_tree_row_reference_free (row_reference);                    
1788                 return;
1789         }
1790
1791         /* Get the window */ 
1792         self = (ModestMsgViewWindow *) modest_mail_operation_get_source (mail_op);
1793         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self));
1794         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
1795
1796         /* Update the row reference */
1797         gtk_tree_row_reference_free (priv->row_reference);
1798         priv->row_reference = gtk_tree_row_reference_copy (row_reference);
1799         priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
1800         select_next_valid_row (priv->header_model, &(priv->next_row_reference), TRUE);
1801         gtk_tree_row_reference_free (row_reference);
1802
1803         /* Mark header as read */
1804         if (!(tny_header_get_flags (header) & TNY_HEADER_FLAG_SEEN))
1805                 tny_header_set_flag (header, TNY_HEADER_FLAG_SEEN);
1806
1807         /* Set new message */
1808         tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
1809         modest_msg_view_window_update_priority (self);
1810         update_window_title (MODEST_MSG_VIEW_WINDOW (self));
1811         modest_msg_view_grab_focus (MODEST_MSG_VIEW (priv->msg_view));
1812
1813         /* Set the new message uid of the window  */
1814         if (priv->msg_uid) {
1815                 g_free (priv->msg_uid);
1816                 priv->msg_uid = modest_tny_folder_get_header_unique_id (header);
1817         }
1818
1819         /* Notify the observers */
1820         g_signal_emit (G_OBJECT (self), signals[MSG_CHANGED_SIGNAL], 
1821                        0, priv->header_model, priv->row_reference);
1822
1823         /* Free new references */
1824         g_object_unref (self);
1825 }
1826
1827 TnyFolderType
1828 modest_msg_view_window_get_folder_type (ModestMsgViewWindow *window)
1829 {
1830         ModestMsgViewWindowPrivate *priv;
1831         TnyMsg *msg;
1832         TnyFolderType folder_type;
1833
1834         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1835
1836         folder_type = TNY_FOLDER_TYPE_UNKNOWN;
1837
1838         msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
1839         if (msg) {
1840                 TnyFolder *folder;
1841
1842                 folder = tny_msg_get_folder (msg);
1843                 if (folder) {
1844                         folder_type = tny_folder_get_folder_type (folder);
1845                         g_object_unref (folder);
1846                 }
1847                 g_object_unref (msg);
1848         }
1849
1850         return folder_type;
1851 }
1852
1853
1854 static void
1855 modest_msg_view_window_update_priority (ModestMsgViewWindow *window)
1856 {
1857         ModestMsgViewWindowPrivate *priv;
1858         TnyHeaderFlags flags = 0;
1859
1860         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1861
1862         if (priv->header_model) {
1863                 TnyHeader *header;
1864                 GtkTreeIter iter;
1865                 GtkTreePath *path = NULL;
1866
1867                 path = gtk_tree_row_reference_get_path (priv->row_reference);
1868                 g_return_if_fail (path != NULL);
1869                 gtk_tree_model_get_iter (priv->header_model, 
1870                                          &iter, 
1871                                          gtk_tree_row_reference_get_path (priv->row_reference));
1872
1873                 gtk_tree_model_get (priv->header_model, &iter, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1874                                     &header, -1);
1875                 flags = tny_header_get_flags (header);
1876                 gtk_tree_path_free (path);
1877         }
1878
1879         modest_msg_view_set_priority (MODEST_MSG_VIEW(priv->msg_view), flags);
1880
1881 }
1882
1883 static gboolean
1884 modest_msg_view_window_window_state_event (GtkWidget *widget, GdkEventWindowState *event, gpointer userdata)
1885 {
1886         if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) {
1887                 ModestWindowPrivate *parent_priv;
1888                 ModestWindowMgr *mgr;
1889                 gboolean is_fullscreen;
1890                 GtkAction *fs_toggle_action;
1891                 gboolean active;
1892
1893                 mgr = modest_runtime_get_window_mgr ();
1894                 is_fullscreen = (modest_window_mgr_get_fullscreen_mode (mgr))?1:0;
1895
1896                 parent_priv = MODEST_WINDOW_GET_PRIVATE (widget);
1897                 
1898                 fs_toggle_action = gtk_ui_manager_get_action (parent_priv->ui_manager, "/MenuBar/ViewMenu/ViewToggleFullscreenMenu");
1899                 active = (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (fs_toggle_action)))?1:0;
1900                 if (is_fullscreen != active) {
1901                         gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (fs_toggle_action), is_fullscreen);
1902                 }
1903         }
1904
1905         return FALSE;
1906
1907 }
1908
1909 static void
1910 set_homogeneous (GtkWidget *widget,
1911                  gpointer data)
1912 {
1913         if (GTK_IS_TOOL_ITEM (widget)) {
1914                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (widget), TRUE);
1915                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (widget), TRUE);
1916         }
1917 }
1918
1919 static void
1920 modest_msg_view_window_show_toolbar (ModestWindow *self,
1921                                      gboolean show_toolbar)
1922 {
1923         ModestMsgViewWindowPrivate *priv = NULL;
1924         ModestWindowPrivate *parent_priv;
1925         GtkWidget *reply_button = NULL, *menu = NULL;
1926         GtkWidget *placeholder = NULL;
1927         gint insert_index;
1928         const gchar *action_name;
1929         GtkAction *action;
1930         
1931         parent_priv = MODEST_WINDOW_GET_PRIVATE(self);
1932         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
1933
1934         /* Set optimized view status */
1935         priv->optimized_view = !show_toolbar;
1936
1937         if (!parent_priv->toolbar) {
1938                 parent_priv->toolbar = gtk_ui_manager_get_widget (parent_priv->ui_manager, 
1939                                                                   "/ToolBar");
1940                 gtk_widget_set_no_show_all (parent_priv->toolbar, TRUE);
1941
1942                 /* Set homogeneous toolbar */
1943                 gtk_container_foreach (GTK_CONTAINER (parent_priv->toolbar), 
1944                                        set_homogeneous, NULL);
1945
1946                 priv->progress_toolitem = GTK_WIDGET (gtk_tool_item_new ());
1947                 priv->cancel_toolitem = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarCancel");
1948                 priv->next_toolitem = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageNext");
1949                 priv->prev_toolitem = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageBack");
1950                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->progress_toolitem), TRUE);
1951                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (priv->progress_toolitem), TRUE);
1952                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (priv->cancel_toolitem), FALSE);
1953                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->cancel_toolitem), FALSE);
1954
1955                 /* Add ProgressBar (Transfer toolbar) */ 
1956                 priv->progress_bar = modest_progress_bar_widget_new ();
1957                 gtk_widget_set_no_show_all (priv->progress_bar, TRUE);
1958                 placeholder = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ProgressbarView");
1959                 insert_index = gtk_toolbar_get_item_index(GTK_TOOLBAR (parent_priv->toolbar), GTK_TOOL_ITEM(placeholder));
1960                 gtk_container_add (GTK_CONTAINER (priv->progress_toolitem), priv->progress_bar);
1961                 gtk_toolbar_insert(GTK_TOOLBAR(parent_priv->toolbar), GTK_TOOL_ITEM (priv->progress_toolitem), insert_index);
1962                 
1963                 /* Connect cancel 'clicked' signal to abort progress mode */
1964                 g_signal_connect(priv->cancel_toolitem, "clicked",
1965                                  G_CALLBACK(cancel_progressbar),
1966                                  self);
1967                 
1968                 /* Add it to the observers list */
1969                 priv->progress_widgets = g_slist_prepend(priv->progress_widgets, priv->progress_bar);
1970
1971                 /* Add to window */
1972                 hildon_window_add_toolbar (HILDON_WINDOW (self), 
1973                                            GTK_TOOLBAR (parent_priv->toolbar));
1974
1975
1976                 /* Set reply button tap and hold menu */        
1977                 reply_button = gtk_ui_manager_get_widget (parent_priv->ui_manager, 
1978                                                           "/ToolBar/ToolbarMessageReply");
1979                 menu = gtk_ui_manager_get_widget (parent_priv->ui_manager, 
1980                                                   "/ToolbarReplyCSM");
1981                 gtk_widget_tap_and_hold_setup (GTK_WIDGET (reply_button), menu, NULL, 0);
1982         }
1983
1984         if (show_toolbar) {
1985                 /* Quick hack: this prevents toolbar icons "dance" when progress bar show status is changed */ 
1986                 /* TODO: resize mode migth be GTK_RESIZE_QUEUE, in order to avoid unneccesary shows */
1987                 gtk_container_set_resize_mode (GTK_CONTAINER(parent_priv->toolbar), GTK_RESIZE_IMMEDIATE);
1988                 
1989                 gtk_widget_show (GTK_WIDGET (parent_priv->toolbar));
1990                 set_toolbar_mode (MODEST_MSG_VIEW_WINDOW(self), TOOLBAR_MODE_NORMAL);                   
1991                 
1992         } else {
1993                 gtk_widget_set_no_show_all (parent_priv->toolbar, TRUE);
1994                 gtk_widget_hide (GTK_WIDGET (parent_priv->toolbar));
1995         }
1996
1997         /* Update also the actions (to update the toggles in the
1998            menus), we have to do it manually because some other window
1999            of the same time could have changed it (remember that the
2000            toolbar fullscreen mode is shared by all the windows of the
2001            same type */
2002         if (modest_window_mgr_get_fullscreen_mode (modest_runtime_get_window_mgr ()))
2003                 action_name = "/MenuBar/ViewMenu/ViewShowToolbarMenu/ViewShowToolbarFullScreenMenu";
2004         else
2005                 action_name = "/MenuBar/ViewMenu/ViewShowToolbarMenu/ViewShowToolbarNormalScreenMenu";
2006
2007         action = gtk_ui_manager_get_action (parent_priv->ui_manager, action_name);
2008         modest_maemo_toggle_action_set_active_block_notify (GTK_TOGGLE_ACTION (action),
2009                                                             show_toolbar);
2010 }
2011
2012 static void 
2013 modest_msg_view_window_clipboard_owner_change (GtkClipboard *clipboard,
2014                                                GdkEvent *event,
2015                                                ModestMsgViewWindow *window)
2016 {
2017         if (!GTK_WIDGET_VISIBLE (window))
2018                 return;
2019
2020         modest_window_check_dimming_rules_group (MODEST_WINDOW (window), "ModestClipboardDimmingRules");
2021 }
2022
2023 gboolean 
2024 modest_msg_view_window_transfer_mode_enabled (ModestMsgViewWindow *self)
2025 {
2026         ModestMsgViewWindowPrivate *priv;
2027         
2028         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), FALSE); 
2029         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2030
2031         return priv->current_toolbar_mode == TOOLBAR_MODE_TRANSFER;
2032 }
2033
2034 static void
2035 cancel_progressbar (GtkToolButton *toolbutton,
2036                     ModestMsgViewWindow *self)
2037 {
2038         GSList *tmp;
2039         ModestMsgViewWindowPrivate *priv;
2040         
2041         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2042
2043         /* Get operation observers and cancel its current operation */
2044         tmp = priv->progress_widgets;
2045         while (tmp) {
2046                 modest_progress_object_cancel_current_operation (MODEST_PROGRESS_OBJECT(tmp->data));
2047                 tmp=g_slist_next(tmp);
2048         }
2049 }
2050 static gboolean
2051 observers_empty (ModestMsgViewWindow *self)
2052 {
2053         GSList *tmp = NULL;
2054         ModestMsgViewWindowPrivate *priv;
2055         gboolean is_empty = TRUE;
2056         guint pending_ops = 0;
2057  
2058         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2059         tmp = priv->progress_widgets;
2060
2061         /* Check all observers */
2062         while (tmp && is_empty)  {
2063                 pending_ops = modest_progress_object_num_pending_operations (MODEST_PROGRESS_OBJECT(tmp->data));
2064                 is_empty = pending_ops == 0;
2065                 
2066                 tmp = g_slist_next(tmp);
2067         }
2068         
2069         return is_empty;
2070 }
2071
2072 static void
2073 on_account_removed (TnyAccountStore *account_store, 
2074                     TnyAccount *account,
2075                     gpointer user_data)
2076 {
2077         /* Do nothing if it's a transport account, because we only
2078            show the messages of a store account */
2079         if (tny_account_get_account_type(account) == TNY_ACCOUNT_TYPE_STORE) {
2080                 const gchar *parent_acc = NULL;
2081                 const gchar *our_acc = NULL;
2082
2083                 our_acc = modest_window_get_active_account (MODEST_WINDOW (user_data));
2084                 parent_acc = modest_tny_account_get_parent_modest_account_name_for_server_account (account);
2085
2086                 /* Close this window if I'm showing a message of the removed account */
2087                 if (strcmp (parent_acc, our_acc) == 0)
2088                         modest_ui_actions_on_close_window (NULL, MODEST_WINDOW (user_data));
2089         }
2090 }
2091
2092 static void
2093 on_queue_changed (ModestMailOperationQueue *queue,
2094                   ModestMailOperation *mail_op,
2095                   ModestMailOperationQueueNotification type,
2096                   ModestMsgViewWindow *self)
2097 {
2098         GSList *tmp;
2099         ModestMsgViewWindowPrivate *priv;
2100         ModestMailOperationTypeOperation op_type;
2101         ModestToolBarModes mode;
2102         
2103         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self));
2104         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2105
2106         /* If this operations was created by another window, do nothing */
2107         if (!modest_mail_operation_is_mine (mail_op, G_OBJECT(self))) 
2108             return;
2109
2110         /* Get toolbar mode from operation id*/
2111         op_type = modest_mail_operation_get_type_operation (mail_op);
2112         switch (op_type) {
2113 /*      case MODEST_MAIL_OPERATION_TYPE_SEND: */
2114         case MODEST_MAIL_OPERATION_TYPE_RECEIVE:
2115         case MODEST_MAIL_OPERATION_TYPE_OPEN:
2116                 mode = TOOLBAR_MODE_TRANSFER;
2117                 break;
2118         default:
2119                 mode = TOOLBAR_MODE_NORMAL;
2120                 
2121         }
2122                 
2123         /* Add operation observers and change toolbar if neccessary*/
2124         tmp = priv->progress_widgets;
2125         switch (type) {
2126         case MODEST_MAIL_OPERATION_QUEUE_OPERATION_ADDED:
2127                 if (mode == TOOLBAR_MODE_TRANSFER) {
2128                         /* Enable transfer toolbar mode */
2129                         set_toolbar_transfer_mode(self);
2130                         while (tmp) {
2131                                 modest_progress_object_add_operation (MODEST_PROGRESS_OBJECT (tmp->data),
2132                                                                       mail_op);
2133                                 tmp = g_slist_next (tmp);
2134                         }
2135                         
2136                 }
2137                 break;
2138         case MODEST_MAIL_OPERATION_QUEUE_OPERATION_REMOVED:
2139                 if (mode == TOOLBAR_MODE_TRANSFER) {
2140                         while (tmp) {
2141                                 modest_progress_object_remove_operation (MODEST_PROGRESS_OBJECT (tmp->data),
2142                                                                  mail_op);
2143                                 tmp = g_slist_next (tmp);
2144                                 
2145                         }
2146
2147                         /* If no more operations are being observed, NORMAL mode is enabled again */
2148                         if (observers_empty (self)) {
2149                                 set_toolbar_mode (self, TOOLBAR_MODE_NORMAL);
2150                         }
2151                 }
2152                 break;
2153         }
2154 }
2155
2156 GList *
2157 modest_msg_view_window_get_attachments (ModestMsgViewWindow *win) 
2158 {
2159         ModestMsgViewWindowPrivate *priv;
2160         GList *selected_attachments = NULL;
2161         
2162         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (win), NULL);
2163         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (win);
2164
2165         selected_attachments = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2166         
2167         return selected_attachments;
2168 }
2169
2170 void
2171 modest_msg_view_window_view_attachment (ModestMsgViewWindow *window, TnyMimePart *mime_part)
2172 {
2173         ModestMsgViewWindowPrivate *priv;
2174         const gchar *msg_uid;
2175         gchar *attachment_uid = NULL;
2176         gint attachment_index = 0;
2177         GList *attachments;
2178
2179         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
2180         g_return_if_fail (TNY_IS_MIME_PART (mime_part) || (mime_part == NULL));
2181         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2182
2183         msg_uid = modest_msg_view_window_get_message_uid (MODEST_MSG_VIEW_WINDOW (window));
2184         attachments = modest_msg_view_get_attachments (MODEST_MSG_VIEW (priv->msg_view));
2185         attachment_index = g_list_index (attachments, mime_part);
2186         g_list_free (attachments);
2187         
2188         if (msg_uid && attachment_index >= 0) {
2189                 attachment_uid = g_strdup_printf ("%s/%d", msg_uid, attachment_index);
2190         }
2191
2192         if (mime_part == NULL) {
2193                 gboolean error = FALSE;
2194                 GList *selected_attachments = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2195                 if (selected_attachments == NULL) {
2196                         error = TRUE;
2197                 } else if (g_list_length (selected_attachments) > 1) {
2198                         hildon_banner_show_information (NULL, NULL, _("mcen_ib_unable_to_display_more"));
2199                         error = TRUE;
2200                 } else {
2201                         mime_part = (TnyMimePart *) selected_attachments->data;
2202                         g_object_ref (mime_part);
2203                 }
2204                 g_list_foreach (selected_attachments, (GFunc) g_object_unref, NULL);
2205                 g_list_free (selected_attachments);
2206
2207                 if (error)
2208                         return;
2209         } else {
2210                 g_object_ref (mime_part);
2211         }
2212
2213         if (tny_mime_part_is_purged (mime_part)) {
2214                 g_object_unref (mime_part);
2215                 return;
2216         }
2217
2218         if (!TNY_IS_MSG (mime_part)) {
2219                 gchar *filepath = NULL;
2220                 const gchar *att_filename = tny_mime_part_get_filename (mime_part);
2221                 TnyFsStream *temp_stream = NULL;
2222                 temp_stream = modest_maemo_utils_create_temp_stream (att_filename, attachment_uid, &filepath);
2223                 
2224                 if (temp_stream) {
2225                         const gchar *content_type;
2226                         content_type = tny_mime_part_get_content_type (mime_part);
2227                         tny_mime_part_decode_to_stream (mime_part, TNY_STREAM (temp_stream));
2228                         
2229                         modest_platform_activate_file (filepath, content_type);
2230                         g_object_unref (temp_stream);
2231                         g_free (filepath);
2232                         /* NOTE: files in the temporary area will be automatically
2233                          * cleaned after some time if they are no longer in use */
2234                 }
2235         } else {
2236                 /* message attachment */
2237                 TnyHeader *header = NULL;
2238                 ModestWindowMgr *mgr;
2239                 ModestWindow *msg_win = NULL;
2240                 gboolean found;
2241                 
2242                 header = tny_msg_get_header (TNY_MSG (mime_part));
2243                 mgr = modest_runtime_get_window_mgr ();         
2244                 found = modest_window_mgr_find_registered_header (mgr, header, &msg_win);
2245
2246                 if (found) {
2247                         if (msg_win)                            /* there is already a window for this uid; top it */
2248                                 gtk_window_present (GTK_WINDOW(msg_win));
2249                         else 
2250                                 /* if it's found, but there is no msg_win, it's probably in the process of being created;
2251                                  * thus, we don't do anything */
2252                                 g_warning ("window for is already being created");
2253                 } else { 
2254                         /* it's not found, so create a new window for it */
2255                         modest_window_mgr_register_header (mgr, header, attachment_uid); /* register the uid before building the window */
2256                         gchar *account = g_strdup (modest_window_get_active_account (MODEST_WINDOW (window)));
2257                         if (!account)
2258                                 account = modest_account_mgr_get_default_account (modest_runtime_get_account_mgr ());
2259                         msg_win = modest_msg_view_window_new_for_attachment (TNY_MSG (mime_part), account, attachment_uid);
2260                         modest_window_set_zoom (MODEST_WINDOW (msg_win), 
2261                                                 modest_window_get_zoom (MODEST_WINDOW (window)));
2262                         modest_window_mgr_register_window (mgr, msg_win);
2263                         gtk_window_set_transient_for (GTK_WINDOW (msg_win), GTK_WINDOW (window));
2264                         gtk_widget_show_all (GTK_WIDGET (msg_win));
2265                 }
2266         }
2267         g_object_unref (mime_part);
2268 }
2269
2270 typedef struct
2271 {
2272         gchar *filename;
2273         TnyMimePart *part;
2274 } SaveMimePartPair;
2275
2276 typedef struct
2277 {
2278         GList *pairs;
2279         GtkWidget *banner;
2280         gboolean result;
2281 } SaveMimePartInfo;
2282
2283 static void save_mime_part_info_free (SaveMimePartInfo *info, gboolean with_struct);
2284 static gboolean idle_save_mime_part_show_result (SaveMimePartInfo *info);
2285 static gpointer save_mime_part_to_file (SaveMimePartInfo *info);
2286 static void save_mime_parts_to_file_with_checks (SaveMimePartInfo *info);
2287
2288 static void 
2289 save_mime_part_info_free (SaveMimePartInfo *info, gboolean with_struct)
2290 {
2291         
2292         GList *node;
2293         for (node = info->pairs; node != NULL; node = g_list_next (node)) {
2294                 SaveMimePartPair *pair = (SaveMimePartPair *) node->data;
2295                 g_free (pair->filename);
2296                 g_object_unref (pair->part);
2297                 g_slice_free (SaveMimePartPair, pair);
2298         }
2299         g_list_free (info->pairs);
2300         info->pairs = NULL;
2301         if (with_struct) {
2302                 gtk_widget_destroy (info->banner);
2303                 g_object_unref (info->banner);
2304                 g_slice_free (SaveMimePartInfo, info);
2305         }
2306 }
2307
2308 static gboolean
2309 idle_save_mime_part_show_result (SaveMimePartInfo *info)
2310 {
2311         if (info->pairs != NULL) {
2312                 save_mime_part_to_file (info);
2313         } else {
2314                 gboolean result;
2315                 result = info->result;
2316
2317                 /* This is a GDK lock because we are an idle callback and
2318                  * hildon_banner_show_information is or does Gtk+ code */
2319
2320                 gdk_threads_enter (); /* CHECKED */
2321                 save_mime_part_info_free (info, TRUE);
2322                 if (result) {
2323                         hildon_banner_show_information (NULL, NULL, _CS("sfil_ib_saved"));
2324                 } else {
2325                         hildon_banner_show_information (NULL, NULL, _("mail_ib_file_operation_failed"));
2326                 }
2327                 gdk_threads_leave (); /* CHECKED */
2328         }
2329
2330         return FALSE;
2331 }
2332
2333 static gpointer
2334 save_mime_part_to_file (SaveMimePartInfo *info)
2335 {
2336         GnomeVFSResult result;
2337         GnomeVFSHandle *handle;
2338         TnyStream *stream;
2339         SaveMimePartPair *pair = (SaveMimePartPair *) info->pairs->data;
2340
2341         result = gnome_vfs_create (&handle, pair->filename, GNOME_VFS_OPEN_WRITE, FALSE, 0444);
2342         if (result == GNOME_VFS_OK) {
2343                 stream = tny_vfs_stream_new (handle);
2344                 tny_mime_part_decode_to_stream (pair->part, stream);
2345                 g_object_unref (G_OBJECT (stream));
2346                 g_object_unref (pair->part);
2347                 g_slice_free (SaveMimePartPair, pair);
2348                 info->pairs = g_list_delete_link (info->pairs, info->pairs);
2349                 info->result = TRUE;
2350         } else {
2351                 save_mime_part_info_free (info, FALSE);
2352                 info->result = FALSE;
2353         }
2354
2355         g_idle_add ((GSourceFunc) idle_save_mime_part_show_result, info);
2356         return NULL;
2357 }
2358
2359 static void
2360 save_mime_parts_to_file_with_checks (SaveMimePartInfo *info)
2361 {
2362         gboolean is_ok = TRUE;
2363         gint replaced_files = 0;
2364         const GList *files = info->pairs;
2365         const GList *iter;
2366
2367         for (iter = files; (iter != NULL) && (replaced_files < 2); iter = g_list_next(iter)) {
2368                 SaveMimePartPair *pair = iter->data;
2369                 if (modest_maemo_utils_file_exists (pair->filename)) {
2370                         replaced_files++;
2371                 }
2372         }
2373         if (replaced_files) {
2374                 GtkWidget *confirm_overwrite_dialog;
2375                 const gchar *message = (replaced_files == 1) ?
2376                         _FM("docm_nc_replace_file") : _FM("docm_nc_replace_multiple");
2377                 confirm_overwrite_dialog = hildon_note_new_confirmation (NULL, message);
2378                 if (gtk_dialog_run (GTK_DIALOG (confirm_overwrite_dialog)) != GTK_RESPONSE_OK) {
2379                         is_ok = FALSE;
2380                 }
2381                 gtk_widget_destroy (confirm_overwrite_dialog);
2382         }
2383
2384         if (!is_ok) {
2385                 save_mime_part_info_free (info, TRUE);
2386         } else {
2387                 GtkWidget *banner = hildon_banner_show_animation (NULL, NULL, 
2388                                                                   _CS("sfil_ib_saving"));
2389                 info->banner = g_object_ref (banner);
2390                 g_thread_create ((GThreadFunc)save_mime_part_to_file, info, FALSE, NULL);
2391                 g_object_unref (banner);
2392         }
2393
2394 }
2395
2396
2397 void
2398 modest_msg_view_window_save_attachments (ModestMsgViewWindow *window, GList *mime_parts)
2399 {
2400         gboolean clean_list = FALSE;
2401         ModestMsgViewWindowPrivate *priv;
2402         GList *files_to_save = NULL;
2403         GtkWidget *save_dialog = NULL;
2404         gchar *folder = NULL;
2405         gboolean canceled = FALSE;
2406         const gchar *filename = NULL;
2407         gchar *save_multiple_str = NULL;
2408
2409         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
2410         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2411
2412         if (mime_parts == NULL) {
2413                 mime_parts = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2414                 if (mime_parts == NULL)
2415                         return;
2416                 clean_list = TRUE;
2417         }
2418
2419         /* prepare dialog */
2420         if (mime_parts->next == NULL) {
2421                 /* only one attachment selected */
2422                 TnyMimePart *mime_part = (TnyMimePart *) mime_parts->data;
2423                 if (!TNY_IS_MSG (mime_part) && tny_mime_part_is_attachment (mime_part)) {
2424                         filename = tny_mime_part_get_filename (mime_part);
2425                 } else {
2426                         g_warning ("Tried to save a non-file attachment");
2427                         canceled = TRUE;
2428                 }
2429         } else {
2430                 save_multiple_str = g_strdup_printf (_FM("sfil_va_number_of_objects_attachments"), 
2431                                                      g_list_length (mime_parts));
2432         }
2433         
2434         save_dialog = hildon_file_chooser_dialog_new (GTK_WINDOW (window), 
2435                                                       GTK_FILE_CHOOSER_ACTION_SAVE);
2436
2437         /* set folder */
2438         folder = g_build_filename (g_get_home_dir (), DEFAULT_FOLDER, NULL);
2439         gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (save_dialog), folder);
2440         g_free (folder);
2441
2442         /* set filename */
2443         if (filename != NULL)
2444                 gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (save_dialog), 
2445                                                    filename);
2446
2447         /* if multiple, set multiple string */
2448         if (save_multiple_str) {
2449                 g_object_set (G_OBJECT (save_dialog), "save-multiple", save_multiple_str, NULL);
2450         }
2451                 
2452         /* show dialog */
2453         if (gtk_dialog_run (GTK_DIALOG (save_dialog)) == GTK_RESPONSE_OK) {
2454                 gchar *chooser_uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (save_dialog));
2455
2456                 if (!modest_maemo_utils_folder_writable (chooser_uri)) {
2457                         hildon_banner_show_information 
2458                                 (NULL, NULL, dgettext("hildon-fm", "sfil_ib_readonly_location"));
2459                 } else {
2460                         GList *node = NULL;
2461
2462                         for (node = mime_parts; node != NULL; node = g_list_next (node)) {
2463                                 TnyMimePart *mime_part = (TnyMimePart *) node->data;
2464                                 
2465                                 if (tny_mime_part_is_attachment (mime_part)) {
2466                                         SaveMimePartPair *pair;
2467
2468                                         if ((mime_parts->next != NULL) &&
2469                                             (tny_mime_part_get_filename (mime_part) == NULL))
2470                                                 continue;
2471                                         
2472                                         pair = g_slice_new0 (SaveMimePartPair);
2473                                         if (mime_parts->next == NULL) {
2474                                                 pair->filename = g_strdup (chooser_uri);
2475                                         } else {
2476                                                 pair->filename = 
2477                                                         g_build_filename (chooser_uri,
2478                                                                           tny_mime_part_get_filename (mime_part), NULL);
2479                                         }
2480                                         pair->part = g_object_ref (mime_part);
2481                                         files_to_save = g_list_prepend (files_to_save, pair);
2482                                 }
2483                         }
2484                 }
2485                 g_free (chooser_uri);
2486         }
2487
2488         gtk_widget_destroy (save_dialog);
2489
2490         if (clean_list) {
2491                 g_list_foreach (mime_parts, (GFunc) g_object_unref, NULL);
2492                 g_list_free (mime_parts);
2493         }
2494
2495         if (files_to_save != NULL) {
2496                 SaveMimePartInfo *info = g_slice_new0 (SaveMimePartInfo);
2497                 info->pairs = files_to_save;
2498                 info->result = TRUE;
2499                 save_mime_parts_to_file_with_checks (info);
2500         }
2501 }
2502
2503 static gboolean
2504 show_remove_attachment_information (gpointer userdata)
2505 {
2506         ModestMsgViewWindow *window = (ModestMsgViewWindow *) userdata;
2507         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2508
2509         /* We're outside the main lock */
2510         gdk_threads_enter ();
2511
2512         if (priv->remove_attachment_banner != NULL) {
2513                 gtk_widget_destroy (priv->remove_attachment_banner);
2514                 g_object_unref (priv->remove_attachment_banner);
2515         }
2516
2517         priv->remove_attachment_banner = g_object_ref (
2518                 hildon_banner_show_animation (NULL, NULL, _("mcen_ib_removing_attachment")));
2519
2520         gdk_threads_leave ();
2521
2522         return FALSE;
2523 }
2524
2525 void
2526 modest_msg_view_window_remove_attachments (ModestMsgViewWindow *window, gboolean get_all)
2527 {
2528         ModestMsgViewWindowPrivate *priv;
2529         GList *mime_parts = NULL, *node;
2530         gchar *confirmation_message;
2531         gint response;
2532         gint n_attachments;
2533         TnyMsg *msg;
2534 /*      TnyFolder *folder; */
2535
2536         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
2537         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2538
2539         if (get_all)
2540                 mime_parts = modest_msg_view_get_attachments (MODEST_MSG_VIEW (priv->msg_view));
2541         else
2542                 mime_parts = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2543                 
2544         /* Remove already purged messages from mime parts list */
2545         node = mime_parts;
2546         while (node != NULL) {
2547                 TnyMimePart *part = TNY_MIME_PART (node->data);
2548                 if (tny_mime_part_is_purged (part)) {
2549                         GList *deleted_node = node;
2550                         node = g_list_next (node);
2551                         g_object_unref (part);
2552                         mime_parts = g_list_delete_link (mime_parts, deleted_node);
2553                 } else {
2554                         node = g_list_next (node);
2555                 }
2556         }
2557
2558         if (mime_parts == NULL)
2559                 return;
2560
2561         n_attachments = g_list_length (mime_parts);
2562         if (n_attachments == 1) {
2563                 const gchar *filename;
2564
2565                 if (TNY_IS_MSG (mime_parts->data)) {
2566                         TnyHeader *header;
2567                         header = tny_msg_get_header (TNY_MSG (mime_parts->data));
2568                         filename = tny_header_get_subject (header);
2569                         g_object_unref (header);
2570                         if (filename == NULL)
2571                                 filename = _("mail_va_no_subject");
2572                 } else {
2573                         filename = tny_mime_part_get_filename (TNY_MIME_PART (mime_parts->data));
2574                 }
2575                 confirmation_message = g_strdup_printf (_("mcen_nc_purge_file_text"), filename);
2576         } else {
2577                 confirmation_message = g_strdup_printf (ngettext("mcen_nc_purge_file_text", 
2578                                                                  "mcen_nc_purge_files_text", 
2579                                                                  n_attachments), n_attachments);
2580         }
2581         response = modest_platform_run_confirmation_dialog (GTK_WINDOW (window),
2582                                                             confirmation_message);
2583         g_free (confirmation_message);
2584
2585         if (response != GTK_RESPONSE_OK)
2586                 return;
2587
2588         priv->purge_timeout = g_timeout_add (2000, show_remove_attachment_information, window);
2589 /*      folder = tny_msg_get_folder (msg); */
2590 /*      tny_msg_uncache_attachments (msg); */
2591 /*      tny_folder_refresh (folder, NULL); */
2592 /*      g_object_unref (folder); */
2593         
2594         for (node = mime_parts; node != NULL; node = g_list_next (node)) {
2595                 tny_mime_part_set_purged (TNY_MIME_PART (node->data));
2596 /*              modest_msg_view_remove_attachment (MODEST_MSG_VIEW (priv->msg_view), node->data); */
2597         }
2598
2599         msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
2600         tny_msg_view_clear (TNY_MSG_VIEW (priv->msg_view));
2601         tny_msg_rewrite_cache (msg);
2602         tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
2603
2604         g_list_foreach (mime_parts, (GFunc) g_object_unref, NULL);
2605         g_list_free (mime_parts);
2606
2607         if (priv->purge_timeout > 0) {
2608                 g_source_remove (priv->purge_timeout);
2609                 priv->purge_timeout = 0;
2610         }
2611
2612         if (priv->remove_attachment_banner) {
2613                 gtk_widget_destroy (priv->remove_attachment_banner);
2614                 g_object_unref (priv->remove_attachment_banner);
2615                 priv->remove_attachment_banner = NULL;
2616         }
2617
2618
2619 }
2620
2621
2622 static void
2623 update_window_title (ModestMsgViewWindow *window)
2624 {
2625         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2626         TnyMsg *msg = NULL;
2627         TnyHeader *header = NULL;
2628         const gchar *subject = NULL;
2629         
2630         msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
2631
2632         if (msg != NULL) {
2633                 header = tny_msg_get_header (msg);
2634                 subject = tny_header_get_subject (header);
2635                 g_object_unref (msg);
2636         }
2637
2638         if ((subject == NULL)||(subject[0] == '\0'))
2639                 subject = _("mail_va_no_subject");
2640
2641         gtk_window_set_title (GTK_WINDOW (window), subject);
2642 }
2643
2644
2645 static void on_move_focus (ModestMsgViewWindow *window,
2646                            GtkDirectionType direction,
2647                            gpointer userdata)
2648 {
2649         GtkWidget *current_focus = NULL;
2650
2651         current_focus = gtk_window_get_focus (GTK_WINDOW (window));
2652         if ((current_focus != NULL) &&
2653             MODEST_IS_ATTACHMENTS_VIEW (current_focus)) {
2654                 g_signal_stop_emission_by_name (G_OBJECT (window), "move-focus");
2655         }
2656 }
2657