* src/maemo/modest-msg-view-window.c:
[modest] / src / maemo / modest-msg-view-window.c
1 /* Copyright (c) 2006, Nokia Corporation
2  * All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  * * Redistributions of source code must retain the above copyright
9  *   notice, this list of conditions and the following disclaimer.
10  * * Redistributions in binary form must reproduce the above copyright
11  *   notice, this list of conditions and the following disclaimer in the
12  *   documentation and/or other materials provided with the distribution.
13  * * Neither the name of the Nokia Corporation nor the names of its
14  *   contributors may be used to endorse or promote products derived from
15  *   this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
18  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
20  * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
21  * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29 #include <glib/gi18n.h>
30 #include <string.h>
31 #include <tny-account-store.h>
32 #include <tny-simple-list.h>
33 #include <tny-msg.h>
34 #include <tny-mime-part.h>
35 #include <tny-vfs-stream.h>
36 #include "modest-marshal.h"
37 #include "modest-platform.h"
38 #include <modest-maemo-utils.h>
39 #include <modest-tny-msg.h>
40 #include <modest-msg-view-window.h>
41 #include <modest-attachments-view.h>
42 #include <modest-main-window-ui.h>
43 #include "modest-msg-view-window-ui-dimming.h"
44 #include <modest-widget-memory.h>
45 #include <modest-runtime.h>
46 #include <modest-window-priv.h>
47 #include <modest-tny-folder.h>
48 #include <modest-text-utils.h>
49 #include <modest-account-mgr-helpers.h>
50 #include "modest-progress-bar-widget.h"
51 #include "modest-defs.h"
52 #include "modest-hildon-includes.h"
53 #include <gtkhtml/gtkhtml-search.h>
54 #include "modest-ui-dimming-manager.h"
55 #include <gdk/gdkkeysyms.h>
56 #include <modest-tny-account.h>
57
58 #define DEFAULT_FOLDER "MyDocs/.documents"
59
60 static void  modest_msg_view_window_class_init   (ModestMsgViewWindowClass *klass);
61 static void  modest_msg_view_window_init         (ModestMsgViewWindow *obj);
62 static void  modest_msg_view_window_finalize     (GObject *obj);
63 static void  modest_msg_view_window_toggle_find_toolbar (GtkToggleAction *obj,
64                                                          gpointer data);
65 static void  modest_msg_view_window_find_toolbar_close (GtkWidget *widget,
66                                                         ModestMsgViewWindow *obj);
67 static void  modest_msg_view_window_find_toolbar_search (GtkWidget *widget,
68                                                         ModestMsgViewWindow *obj);
69
70 static void modest_msg_view_window_disconnect_signals (ModestWindow *self);
71 static void modest_msg_view_window_set_zoom (ModestWindow *window,
72                                              gdouble zoom);
73 static gdouble modest_msg_view_window_get_zoom (ModestWindow *window);
74 static gboolean modest_msg_view_window_zoom_minus (ModestWindow *window);
75 static gboolean modest_msg_view_window_zoom_plus (ModestWindow *window);
76 static gboolean modest_msg_view_window_key_release_event (GtkWidget *window,
77                                                           GdkEventKey *event,
78                                                           gpointer userdata);
79 static gboolean modest_msg_view_window_window_state_event (GtkWidget *widget, 
80                                                            GdkEventWindowState *event, 
81                                                            gpointer userdata);
82 static void modest_msg_view_window_scroll_up (ModestWindow *window);
83 static void modest_msg_view_window_scroll_down (ModestWindow *window);
84 static void modest_msg_view_window_update_priority (ModestMsgViewWindow *window);
85
86 static void modest_msg_view_window_show_toolbar   (ModestWindow *window,
87                                                    gboolean show_toolbar);
88
89 static void modest_msg_view_window_clipboard_owner_change (GtkClipboard *clipboard,
90                                                            GdkEvent *event,
91                                                            ModestMsgViewWindow *window);
92
93 static void cancel_progressbar  (GtkToolButton *toolbutton,
94                                  ModestMsgViewWindow *self);
95
96 static void on_queue_changed    (ModestMailOperationQueue *queue,
97                                  ModestMailOperation *mail_op,
98                                  ModestMailOperationQueueNotification type,
99                                  ModestMsgViewWindow *self);
100
101 static void on_account_removed  (TnyAccountStore *account_store, 
102                                  TnyAccount *account,
103                                  gpointer user_data);
104
105 static void view_msg_cb         (ModestMailOperation *mail_op, 
106                                  TnyHeader *header, 
107                                  TnyMsg *msg, 
108                                  gpointer user_data);
109
110 static void set_toolbar_mode    (ModestMsgViewWindow *self, 
111                                  ModestToolBarModes mode);
112
113 static void update_window_title (ModestMsgViewWindow *window);
114
115 static gboolean set_toolbar_transfer_mode     (ModestMsgViewWindow *self); 
116
117
118 /* list my signals */
119 enum {
120         MSG_CHANGED_SIGNAL,
121         LAST_SIGNAL
122 };
123
124 static const GtkToggleActionEntry msg_view_toggle_action_entries [] = {
125         { "FindInMessage",    MODEST_TOOLBAR_ICON_FIND,    N_("qgn_toolb_gene_find"), NULL, NULL, G_CALLBACK (modest_msg_view_window_toggle_find_toolbar), FALSE },
126         { "ToolsFindInMessage", NULL, N_("mcen_me_viewer_find"), NULL, NULL, G_CALLBACK (modest_msg_view_window_toggle_find_toolbar), FALSE },
127 };
128
129 static const GtkRadioActionEntry msg_view_zoom_action_entries [] = {
130         { "Zoom50", NULL, N_("mcen_me_viewer_50"), NULL, NULL, 50 },
131         { "Zoom80", NULL, N_("mcen_me_viewer_80"), NULL, NULL, 80 },
132         { "Zoom100", NULL, N_("mcen_me_viewer_100"), NULL, NULL, 100 },
133         { "Zoom120", NULL, N_("mcen_me_viewer_120"), NULL, NULL, 120 },
134         { "Zoom150", NULL, N_("mcen_me_viewer_150"), NULL, NULL, 150 },
135         { "Zoom200", NULL, N_("mcen_me_viewer_200"), NULL, NULL, 200 }
136 };
137
138 typedef struct _ModestMsgViewWindowPrivate ModestMsgViewWindowPrivate;
139 struct _ModestMsgViewWindowPrivate {
140
141         GtkWidget   *msg_view;
142         GtkWidget   *main_scroll;
143         GtkWidget   *find_toolbar;
144         gchar       *last_search;
145
146         /* Progress observers */
147         GtkWidget        *progress_bar;
148         GSList           *progress_widgets;
149
150         /* Tollbar items */
151         GtkWidget   *progress_toolitem;
152         GtkWidget   *cancel_toolitem;
153         GtkWidget   *prev_toolitem;
154         GtkWidget   *next_toolitem;
155         ModestToolBarModes current_toolbar_mode;
156
157         /* Optimized view enabled */
158         gboolean optimized_view;
159
160         /* A reference to the @model of the header view 
161          * to allow selecting previous/next messages,
162          * if the message is currently selected in the header view.
163          */
164         GtkTreeModel *header_model;
165         GtkTreeRowReference *row_reference;
166         GtkTreeRowReference *next_row_reference;
167
168         guint clipboard_change_handler;
169         guint queue_change_handler;
170         guint account_removed_handler;
171
172         guint purge_timeout;
173         GtkWidget *remove_attachment_banner;
174
175         guint progress_bar_timeout;
176
177         gchar *msg_uid;
178 };
179
180 #define MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(o)      (G_TYPE_INSTANCE_GET_PRIVATE((o), \
181                                                     MODEST_TYPE_MSG_VIEW_WINDOW, \
182                                                     ModestMsgViewWindowPrivate))
183 /* globals */
184 static GtkWindowClass *parent_class = NULL;
185
186 /* uncomment the following if you have defined any signals */
187 static guint signals[LAST_SIGNAL] = {0};
188
189 GType
190 modest_msg_view_window_get_type (void)
191 {
192         static GType my_type = 0;
193         if (!my_type) {
194                 static const GTypeInfo my_info = {
195                         sizeof(ModestMsgViewWindowClass),
196                         NULL,           /* base init */
197                         NULL,           /* base finalize */
198                         (GClassInitFunc) modest_msg_view_window_class_init,
199                         NULL,           /* class finalize */
200                         NULL,           /* class data */
201                         sizeof(ModestMsgViewWindow),
202                         1,              /* n_preallocs */
203                         (GInstanceInitFunc) modest_msg_view_window_init,
204                         NULL
205                 };
206                 my_type = g_type_register_static (MODEST_TYPE_WINDOW,
207                                                   "ModestMsgViewWindow",
208                                                   &my_info, 0);
209         }
210         return my_type;
211 }
212
213 static void
214 save_state (ModestWindow *self)
215 {
216         modest_widget_memory_save (modest_runtime_get_conf (),
217                                    G_OBJECT(self), 
218                                    MODEST_CONF_MSG_VIEW_WINDOW_KEY);
219 }
220
221
222 static void
223 restore_settings (ModestMsgViewWindow *self)
224 {
225         modest_widget_memory_restore (modest_runtime_get_conf (),
226                                       G_OBJECT(self), 
227                                       MODEST_CONF_MSG_VIEW_WINDOW_KEY);
228 }
229
230 static void
231 modest_msg_view_window_class_init (ModestMsgViewWindowClass *klass)
232 {
233         GObjectClass *gobject_class;
234         ModestWindowClass *modest_window_class;
235         gobject_class = (GObjectClass*) klass;
236         modest_window_class = (ModestWindowClass *) klass;
237
238         parent_class            = g_type_class_peek_parent (klass);
239         gobject_class->finalize = modest_msg_view_window_finalize;
240
241         modest_window_class->set_zoom_func = modest_msg_view_window_set_zoom;
242         modest_window_class->get_zoom_func = modest_msg_view_window_get_zoom;
243         modest_window_class->zoom_minus_func = modest_msg_view_window_zoom_minus;
244         modest_window_class->zoom_plus_func = modest_msg_view_window_zoom_plus;
245         modest_window_class->show_toolbar_func = modest_msg_view_window_show_toolbar;
246         modest_window_class->disconnect_signals_func = modest_msg_view_window_disconnect_signals;
247
248         g_type_class_add_private (gobject_class, sizeof(ModestMsgViewWindowPrivate));
249
250         modest_window_class->save_state_func = save_state;
251
252         signals[MSG_CHANGED_SIGNAL] =
253                 g_signal_new ("msg-changed",
254                               G_TYPE_FROM_CLASS (gobject_class),
255                               G_SIGNAL_RUN_FIRST,
256                               G_STRUCT_OFFSET (ModestMsgViewWindowClass, msg_changed),
257                               NULL, NULL,
258                               modest_marshal_VOID__POINTER_POINTER,
259                               G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER);
260 }
261
262 static void
263 modest_msg_view_window_init (ModestMsgViewWindow *obj)
264 {
265         ModestMsgViewWindowPrivate *priv;
266         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(obj);
267
268         priv->msg_view      = NULL;
269         priv->header_model  = NULL;
270         priv->clipboard_change_handler = 0;
271         priv->queue_change_handler = 0;
272         priv->account_removed_handler = 0;
273         priv->current_toolbar_mode = TOOLBAR_MODE_NORMAL;
274
275         priv->optimized_view  = FALSE;
276         priv->progress_bar_timeout = 0;
277         priv->purge_timeout = 0;
278         priv->remove_attachment_banner = NULL;
279         priv->msg_uid = NULL;
280 }
281
282
283 static gboolean
284 set_toolbar_transfer_mode (ModestMsgViewWindow *self)
285 {
286         ModestMsgViewWindowPrivate *priv = NULL;
287         
288         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), FALSE);
289
290         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
291
292         set_toolbar_mode (self, TOOLBAR_MODE_TRANSFER);
293         
294         if (priv->progress_bar_timeout > 0) {
295                 g_source_remove (priv->progress_bar_timeout);
296                 priv->progress_bar_timeout = 0;
297         }
298         
299         return FALSE;
300 }
301
302 static void 
303 set_toolbar_mode (ModestMsgViewWindow *self, 
304                   ModestToolBarModes mode)
305 {
306         ModestWindowPrivate *parent_priv;
307         ModestMsgViewWindowPrivate *priv;
308 /*      GtkWidget *widget = NULL; */
309
310         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self));
311
312         parent_priv = MODEST_WINDOW_GET_PRIVATE(self);
313         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
314                         
315         /* Sets current toolbar mode */
316         priv->current_toolbar_mode = mode;
317
318         /* Update toolbar dimming state */
319         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (self));
320
321         switch (mode) {
322         case TOOLBAR_MODE_NORMAL:               
323 /*              widget = gtk_ui_manager_get_action (parent_priv->ui_manager, "/ToolBar/ToolbarMessageReply"); */
324 /*              gtk_action_set_sensitive (widget, TRUE); */
325 /*              widget = gtk_ui_manager_get_action (parent_priv->ui_manager, "/ToolBar/ToolbarDeleteMessage"); */
326 /*              gtk_action_set_sensitive (widget, TRUE); */
327 /*              widget = gtk_ui_manager_get_action (parent_priv->ui_manager, "/ToolBar/ToolbarMessageMoveTo"); */
328 /*              gtk_action_set_sensitive (widget, TRUE); */
329 /*              widget = gtk_ui_manager_get_action (parent_priv->ui_manager, "/ToolBar/FindInMessage"); */
330 /*              gtk_action_set_sensitive (widget, TRUE); */
331
332                 if (priv->prev_toolitem)
333                         gtk_widget_show (priv->prev_toolitem);
334                 
335                 if (priv->next_toolitem)
336                         gtk_widget_show (priv->next_toolitem);
337                         
338                 if (priv->progress_toolitem)
339                         gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->progress_toolitem), FALSE);
340                 if (priv->progress_bar)
341                         gtk_widget_hide (priv->progress_bar);
342                         
343                 if (priv->cancel_toolitem)
344                         gtk_widget_hide (priv->cancel_toolitem);
345
346                 /* Hide toolbar if optimized view is enabled */
347                 if (priv->optimized_view) {
348                         gtk_widget_set_no_show_all (parent_priv->toolbar, TRUE);
349                         gtk_widget_hide (GTK_WIDGET(parent_priv->toolbar));
350                 }
351
352                 break;
353         case TOOLBAR_MODE_TRANSFER:
354 /*              widget = gtk_ui_manager_get_action (parent_priv->ui_manager, "/ToolBar/ToolbarMessageReply"); */
355 /*              gtk_action_set_sensitive (widget, FALSE); */
356 /*              widget = gtk_ui_manager_get_action (parent_priv->ui_manager, "/ToolBar/ToolbarDeleteMessage"); */
357 /*              gtk_action_set_sensitive (widget, FALSE); */
358 /*              widget = gtk_ui_manager_get_action (parent_priv->ui_manager, "/ToolBar/ToolbarMessageMoveTo"); */
359 /*              gtk_action_set_sensitive (widget, FALSE); */
360 /*              widget = gtk_ui_manager_get_action (parent_priv->ui_manager, "/ToolBar/FindInMessage"); */
361 /*              gtk_action_set_sensitive (widget, FALSE); */
362
363                 if (priv->prev_toolitem)
364                         gtk_widget_hide (priv->prev_toolitem);
365                 
366                 if (priv->next_toolitem)
367                         gtk_widget_hide (priv->next_toolitem);
368                 
369                 if (priv->progress_toolitem)
370                         gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->progress_toolitem), TRUE);
371                 if (priv->progress_bar)
372                         gtk_widget_show (priv->progress_bar);
373                         
374                 if (priv->cancel_toolitem)
375                         gtk_widget_show (priv->cancel_toolitem);
376
377                 /* Show toolbar if it's hiden (optimized view ) */
378                 if (priv->optimized_view) {
379                         gtk_widget_set_no_show_all (parent_priv->toolbar, FALSE);
380                         gtk_widget_show (GTK_WIDGET (parent_priv->toolbar));
381                 }
382
383                 break;
384         default:
385                 g_return_if_reached ();
386         }
387
388 }
389
390
391 static GtkWidget *
392 menubar_to_menu (GtkUIManager *ui_manager)
393 {
394         GtkWidget *main_menu;
395         GtkWidget *menubar;
396         GList *iter;
397
398         /* Create new main menu */
399         main_menu = gtk_menu_new();
400
401         /* Get the menubar from the UI manager */
402         menubar = gtk_ui_manager_get_widget (ui_manager, "/MenuBar");
403
404         iter = gtk_container_get_children (GTK_CONTAINER (menubar));
405         while (iter) {
406                 GtkWidget *menu;
407
408                 menu = GTK_WIDGET (iter->data);
409                 gtk_widget_reparent(menu, main_menu);
410
411                 iter = g_list_next (iter);
412         }
413         return main_menu;
414 }
415
416 static void
417 init_window (ModestMsgViewWindow *obj, TnyMsg *msg)
418 {
419         GtkWidget *main_vbox;
420         ModestMsgViewWindowPrivate *priv;
421         ModestWindowPrivate *parent_priv;
422         
423         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(obj);
424         parent_priv = MODEST_WINDOW_GET_PRIVATE(obj);
425
426         priv->msg_view = modest_msg_view_new (msg);
427         modest_msg_view_set_shadow_type (MODEST_MSG_VIEW (priv->msg_view), GTK_SHADOW_NONE);
428         main_vbox = gtk_vbox_new  (FALSE, 6);
429
430         /* Menubar */
431         parent_priv->menubar = menubar_to_menu (parent_priv->ui_manager);
432         gtk_widget_show_all (GTK_WIDGET(parent_priv->menubar));
433         hildon_window_set_menu    (HILDON_WINDOW(obj), GTK_MENU(parent_priv->menubar));
434
435         priv->main_scroll = gtk_scrolled_window_new (NULL, NULL);
436         gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->main_scroll), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
437         gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (priv->main_scroll), GTK_SHADOW_NONE);
438         modest_maemo_set_thumbable_scrollbar (GTK_SCROLLED_WINDOW(priv->main_scroll), TRUE);
439
440         gtk_container_add (GTK_CONTAINER (priv->main_scroll), priv->msg_view);
441         gtk_box_pack_start (GTK_BOX(main_vbox), priv->main_scroll, TRUE, TRUE, 0);
442         gtk_container_add   (GTK_CONTAINER(obj), main_vbox);
443
444         priv->find_toolbar = hildon_find_toolbar_new (NULL);
445         hildon_window_add_toolbar (HILDON_WINDOW (obj), GTK_TOOLBAR (priv->find_toolbar));
446         gtk_widget_set_no_show_all (priv->find_toolbar, TRUE);
447         g_signal_connect (G_OBJECT (priv->find_toolbar), "close", G_CALLBACK (modest_msg_view_window_find_toolbar_close), obj);
448         g_signal_connect (G_OBJECT (priv->find_toolbar), "search", G_CALLBACK (modest_msg_view_window_find_toolbar_search), obj);
449         
450         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);
451         gtk_widget_show_all (GTK_WIDGET(main_vbox));
452 }
453
454 static void
455 modest_msg_view_window_disconnect_signals (ModestWindow *self)
456 {
457         ModestMsgViewWindowPrivate *priv;
458
459         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
460
461         if (g_signal_handler_is_connected (gtk_clipboard_get (GDK_SELECTION_PRIMARY),
462                                            priv->clipboard_change_handler)) 
463                 g_signal_handler_disconnect (gtk_clipboard_get (GDK_SELECTION_PRIMARY), 
464                                              priv->clipboard_change_handler);
465
466         if (g_signal_handler_is_connected (G_OBJECT (modest_runtime_get_mail_operation_queue ()), 
467                                            priv->queue_change_handler))
468                 g_signal_handler_disconnect (G_OBJECT (modest_runtime_get_mail_operation_queue ()), 
469                                              priv->queue_change_handler);
470
471         if (g_signal_handler_is_connected (G_OBJECT (modest_runtime_get_account_store ()), 
472                                            priv->account_removed_handler))
473                 g_signal_handler_disconnect (G_OBJECT (modest_runtime_get_account_store ()), 
474                                              priv->account_removed_handler);
475 }       
476
477 static void
478 modest_msg_view_window_finalize (GObject *obj)
479 {
480         ModestMsgViewWindowPrivate *priv;
481
482         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (obj);
483
484         /* Sanity check: shouldn't be needed, the window mgr should
485            call this function before */
486         modest_msg_view_window_disconnect_signals (MODEST_WINDOW (obj));
487
488         if (priv->header_model != NULL) {
489                 g_object_unref (priv->header_model);
490                 priv->header_model = NULL;
491         }
492
493         if (priv->progress_bar_timeout > 0) {
494                 g_source_remove (priv->progress_bar_timeout);
495                 priv->progress_bar_timeout = 0;
496         }
497
498         if (priv->remove_attachment_banner) {
499                 gtk_widget_destroy (priv->remove_attachment_banner);
500                 g_object_unref (priv->remove_attachment_banner);
501                 priv->remove_attachment_banner = NULL;
502         }
503
504         if (priv->purge_timeout > 0) {
505                 g_source_remove (priv->purge_timeout);
506                 priv->purge_timeout = 0;
507         }
508
509         if (priv->row_reference) {
510                 gtk_tree_row_reference_free (priv->row_reference);
511                 priv->row_reference = NULL;
512         }
513
514         if (priv->next_row_reference) {
515                 gtk_tree_row_reference_free (priv->next_row_reference);
516                 priv->next_row_reference = NULL;
517         }
518
519         if (priv->msg_uid) {
520                 g_free (priv->msg_uid);
521                 priv->msg_uid = NULL;
522         }
523
524         G_OBJECT_CLASS(parent_class)->finalize (obj);
525 }
526
527 static gboolean
528 select_next_valid_row (GtkTreeModel *model,
529                        GtkTreeRowReference **row_reference,
530                        gboolean cycle)
531 {
532         GtkTreeIter tmp_iter;
533         GtkTreePath *path, *next;
534         gboolean retval = FALSE;
535
536         g_return_val_if_fail (gtk_tree_row_reference_valid (*row_reference), FALSE);
537
538         path = gtk_tree_row_reference_get_path (*row_reference);
539         gtk_tree_model_get_iter (model, &tmp_iter, path);
540         gtk_tree_row_reference_free (*row_reference);
541         *row_reference = NULL;
542
543         if (gtk_tree_model_iter_next (model, &tmp_iter)) {
544                 next = gtk_tree_model_get_path (model, &tmp_iter);
545                 *row_reference = gtk_tree_row_reference_new (model, next);
546                 retval = TRUE;
547         } else if (cycle && gtk_tree_model_get_iter_first (model, &tmp_iter)) {
548                 next = gtk_tree_model_get_path (model, &tmp_iter);
549
550                 /* Ensure that we are not selecting the same */
551                 if (gtk_tree_path_compare (path, next) != 0) {
552                         *row_reference = gtk_tree_row_reference_new (model, next);
553                         retval = TRUE;
554                 }
555         }
556
557         /* Free */
558         gtk_tree_path_free (path);
559
560         return retval;
561 }
562
563 ModestWindow *
564 modest_msg_view_window_new_with_header_model (TnyMsg *msg, 
565                                               const gchar *modest_account_name,
566                                               const gchar *msg_uid,
567                                               GtkTreeModel *model, 
568                                               GtkTreeRowReference *row_reference)
569 {
570         ModestMsgViewWindow *window = NULL;
571         ModestMsgViewWindowPrivate *priv = NULL;
572
573         window = MODEST_MSG_VIEW_WINDOW(modest_msg_view_window_new (msg, modest_account_name, msg_uid));
574         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), NULL);
575
576         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
577
578         g_object_ref (model);
579         priv->header_model = model;
580         priv->row_reference = gtk_tree_row_reference_copy (row_reference);
581         priv->next_row_reference = gtk_tree_row_reference_copy (row_reference);
582         select_next_valid_row (model, &(priv->next_row_reference), TRUE);
583
584         modest_msg_view_window_update_priority (window);
585
586         /* Check toolbar dimming rules */
587         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (window));
588
589         return MODEST_WINDOW(window);
590 }
591
592
593 ModestWindow *
594 modest_msg_view_window_new (TnyMsg *msg, 
595                             const gchar *modest_account_name,
596                             const gchar *msg_uid)
597 {
598         ModestMsgViewWindow *self = NULL;
599         GObject *obj = NULL;
600         ModestMsgViewWindowPrivate *priv = NULL;
601         ModestWindowPrivate *parent_priv = NULL;
602         ModestDimmingRulesGroup *menu_rules_group = NULL;
603         ModestDimmingRulesGroup *toolbar_rules_group = NULL;
604         GtkActionGroup *action_group = NULL;
605         GError *error = NULL;
606         GdkPixbuf *window_icon;
607
608         g_return_val_if_fail (msg, NULL);
609         
610         obj = g_object_new(MODEST_TYPE_MSG_VIEW_WINDOW, NULL);
611         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(obj);
612         parent_priv = MODEST_WINDOW_GET_PRIVATE(obj);
613         self = MODEST_MSG_VIEW_WINDOW (obj);
614
615         priv->msg_uid = g_strdup (msg_uid);
616
617         parent_priv->ui_manager = gtk_ui_manager_new();
618         parent_priv->ui_dimming_manager = modest_ui_dimming_manager_new();
619
620         action_group = gtk_action_group_new ("ModestMsgViewWindowActions");
621         gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE);
622
623         menu_rules_group = modest_dimming_rules_group_new ("ModestMenuDimmingRules", FALSE);
624         toolbar_rules_group = modest_dimming_rules_group_new ("ModestToolbarDimmingRules", TRUE);
625
626         /* Add common actions */
627         gtk_action_group_add_actions (action_group,
628                                       modest_action_entries,
629                                       G_N_ELEMENTS (modest_action_entries),
630                                       obj);
631         gtk_action_group_add_toggle_actions (action_group,
632                                              modest_toggle_action_entries,
633                                              G_N_ELEMENTS (modest_toggle_action_entries),
634                                              obj);
635         gtk_action_group_add_toggle_actions (action_group,
636                                              msg_view_toggle_action_entries,
637                                              G_N_ELEMENTS (msg_view_toggle_action_entries),
638                                              obj);
639         gtk_action_group_add_radio_actions (action_group,
640                                             msg_view_zoom_action_entries,
641                                             G_N_ELEMENTS (msg_view_zoom_action_entries),
642                                             100,
643                                             G_CALLBACK (modest_ui_actions_on_change_zoom),
644                                             obj);
645
646         gtk_ui_manager_insert_action_group (parent_priv->ui_manager, action_group, 0);
647         g_object_unref (action_group);
648
649         /* Load the UI definition */
650         gtk_ui_manager_add_ui_from_file (parent_priv->ui_manager, MODEST_UIDIR "modest-msg-view-window-ui.xml",
651                                          &error);
652         if (error) {
653                 g_printerr ("modest: could not merge modest-msg-view-window-ui.xml: %s\n", error->message);
654                 g_error_free (error);
655                 error = NULL;
656         }
657         /* ****** */
658
659         /* Add common dimming rules */
660         modest_dimming_rules_group_add_rules (menu_rules_group, 
661                                               modest_msg_view_menu_dimming_entries,
662                                               G_N_ELEMENTS (modest_msg_view_menu_dimming_entries),
663                                               self);
664         modest_dimming_rules_group_add_rules (toolbar_rules_group, 
665                                               modest_msg_view_toolbar_dimming_entries,
666                                               G_N_ELEMENTS (modest_msg_view_toolbar_dimming_entries),
667                                               self);
668
669         /* Insert dimming rules group for this window */
670         modest_ui_dimming_manager_insert_rules_group (parent_priv->ui_dimming_manager, menu_rules_group);
671         modest_ui_dimming_manager_insert_rules_group (parent_priv->ui_dimming_manager, toolbar_rules_group);
672         g_object_unref (menu_rules_group);
673         g_object_unref (toolbar_rules_group);
674
675         /* Add accelerators */
676         gtk_window_add_accel_group (GTK_WINDOW (obj), 
677                                     gtk_ui_manager_get_accel_group (parent_priv->ui_manager));
678         
679         /* Init window */
680         init_window (MODEST_MSG_VIEW_WINDOW(obj), msg);
681         restore_settings (MODEST_MSG_VIEW_WINDOW(obj));
682         
683         /* Set window icon */
684         window_icon = modest_platform_get_icon (MODEST_APP_MSG_VIEW_ICON); 
685         if (window_icon) {
686                 gtk_window_set_icon (GTK_WINDOW (obj), window_icon);
687                 g_object_unref (window_icon);
688         }
689
690         /* g_signal_connect (G_OBJECT(obj), "delete-event", G_CALLBACK(on_delete_event), obj); */
691
692         g_signal_connect (G_OBJECT(priv->msg_view), "link_clicked",
693                           G_CALLBACK (modest_ui_actions_on_msg_link_clicked), obj);
694         g_signal_connect (G_OBJECT(priv->msg_view), "link_hover",
695                           G_CALLBACK (modest_ui_actions_on_msg_link_hover), obj);
696         g_signal_connect (G_OBJECT(priv->msg_view), "attachment_clicked",
697                           G_CALLBACK (modest_ui_actions_on_msg_attachment_clicked), obj);
698         g_signal_connect (G_OBJECT(priv->msg_view), "recpt_activated",
699                           G_CALLBACK (modest_ui_actions_on_msg_recpt_activated), obj);
700         g_signal_connect (G_OBJECT(priv->msg_view), "link_contextual",
701                           G_CALLBACK (modest_ui_actions_on_msg_link_contextual), obj);
702
703         g_signal_connect (G_OBJECT (obj), "key-release-event",
704                           G_CALLBACK (modest_msg_view_window_key_release_event),
705                           NULL);
706
707         g_signal_connect (G_OBJECT (obj), "window-state-event",
708                           G_CALLBACK (modest_msg_view_window_window_state_event),
709                           NULL);
710
711         /* Mail Operation Queue */
712         priv->queue_change_handler = g_signal_connect (G_OBJECT (modest_runtime_get_mail_operation_queue ()),
713                                                        "queue-changed",
714                                                        G_CALLBACK (on_queue_changed),
715                                                        obj);
716
717         /* Account manager */
718         priv->account_removed_handler = g_signal_connect (G_OBJECT (modest_runtime_get_account_store ()),
719                                                           "account_removed",
720                                                           G_CALLBACK(on_account_removed),
721                                                           obj);
722
723         modest_window_set_active_account (MODEST_WINDOW(obj), modest_account_name);
724
725         priv->last_search = NULL;
726
727         /* Init the clipboard actions dim status */
728         modest_msg_view_grab_focus(MODEST_MSG_VIEW (priv->msg_view));
729
730         update_window_title (MODEST_MSG_VIEW_WINDOW (obj));
731
732         /* Check toolbar dimming rules */
733         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (obj));
734
735         return MODEST_WINDOW(obj);
736 }
737
738
739 gboolean 
740 modest_msg_view_window_toolbar_on_transfer_mode     (ModestMsgViewWindow *self)
741 {
742         ModestMsgViewWindowPrivate *priv= NULL; 
743
744         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), FALSE);
745         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
746
747         return priv->current_toolbar_mode == TOOLBAR_MODE_TRANSFER;
748 }
749
750 TnyHeader*
751 modest_msg_view_window_get_header (ModestMsgViewWindow *self)
752 {
753         ModestMsgViewWindowPrivate *priv= NULL; 
754         TnyMsg *msg = NULL;
755         TnyHeader *header = NULL;
756         GtkTreePath *path = NULL;
757         GtkTreeIter iter;
758
759         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), NULL);
760         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
761
762         /* If the message ws not obtained from a treemodel,
763          * for instance if it was opened directly by the search UI:
764          */
765         if (priv->header_model == NULL) {
766                 msg = modest_msg_view_window_get_message (self);
767                 header = tny_msg_get_header (msg);
768                 g_object_unref (msg);
769                 return header;
770         }
771
772         /* Get iter of the currently selected message in the header view: */
773         /* TODO: Why not just give this window a ref of the TnyHeader or TnyMessage,
774          * instead of sometimes retrieving it from the header view?
775          * Then we wouldn't be dependent on the message actually still being selected 
776          * in the header view. murrayc. */
777         path = gtk_tree_row_reference_get_path (priv->row_reference);
778         g_return_val_if_fail (path != NULL, NULL);
779         gtk_tree_model_get_iter (priv->header_model, 
780                                  &iter, 
781                                  path);
782
783         /* Get current message header */
784         gtk_tree_model_get (priv->header_model, &iter, 
785                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, 
786                             &header, -1);
787
788         gtk_tree_path_free (path);
789         return header;
790 }
791
792 TnyMsg*
793 modest_msg_view_window_get_message (ModestMsgViewWindow *self)
794 {
795         ModestMsgView *msg_view;
796         ModestMsgViewWindowPrivate *priv;
797
798         g_return_val_if_fail (self, NULL);
799
800         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
801
802         msg_view = MODEST_MSG_VIEW (priv->msg_view);
803
804         return modest_msg_view_get_message (msg_view);
805 }
806
807 const gchar*
808 modest_msg_view_window_get_message_uid (ModestMsgViewWindow *self)
809 {
810         ModestMsgViewWindowPrivate *priv;
811
812         g_return_val_if_fail (self, NULL);
813         
814         priv  = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
815
816         return (const gchar*) priv->msg_uid;
817 }
818
819 static void 
820 modest_msg_view_window_toggle_find_toolbar (GtkToggleAction *toggle,
821                                             gpointer data)
822 {
823         ModestMsgViewWindow *window = MODEST_MSG_VIEW_WINDOW (data);
824         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
825         ModestWindowPrivate *parent_priv = MODEST_WINDOW_GET_PRIVATE (window);
826         gboolean is_active;
827         GtkAction *action;
828
829         is_active = gtk_toggle_action_get_active (toggle);
830
831         if (is_active) {
832                 gtk_widget_show (priv->find_toolbar);
833                 hildon_find_toolbar_highlight_entry (HILDON_FIND_TOOLBAR (priv->find_toolbar), TRUE);
834         } else {
835                 gtk_widget_hide (priv->find_toolbar);
836         }
837
838         /* update the toggle buttons status */
839         action = gtk_ui_manager_get_action (parent_priv->ui_manager, "/ToolBar/FindInMessage");
840         modest_maemo_toggle_action_set_active_block_notify (GTK_TOGGLE_ACTION (action), is_active);
841         action = gtk_ui_manager_get_action (parent_priv->ui_manager, "/MenuBar/ToolsMenu/ToolsFindInMessageMenu");
842         modest_maemo_toggle_action_set_active_block_notify (GTK_TOGGLE_ACTION (action), is_active);
843         
844 }
845
846 static void
847 modest_msg_view_window_find_toolbar_close (GtkWidget *widget,
848                                            ModestMsgViewWindow *obj)
849 {
850         GtkToggleAction *toggle;
851         ModestWindowPrivate *parent_priv;
852         parent_priv = MODEST_WINDOW_GET_PRIVATE (obj);
853         
854         toggle = GTK_TOGGLE_ACTION (gtk_ui_manager_get_action (parent_priv->ui_manager, "/ToolBar/FindInMessage"));
855         gtk_toggle_action_set_active (toggle, FALSE);
856 }
857
858 static void
859 modest_msg_view_window_find_toolbar_search (GtkWidget *widget,
860                                            ModestMsgViewWindow *obj)
861 {
862         gchar *current_search;
863         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (obj);
864
865         if (modest_msg_view_get_message_is_empty (MODEST_MSG_VIEW (priv->msg_view))) {
866                 hildon_banner_show_information (NULL, NULL, _("mail_ib_nothing_to_find"));
867                 return;
868         }
869
870         g_object_get (G_OBJECT (widget), "prefix", &current_search, NULL);
871
872         if ((current_search == NULL) || (strcmp (current_search, "") == 0)) {
873                 g_free (current_search);
874                 hildon_banner_show_information (NULL, NULL, dgettext("hildon-common-strings", "ecdg_ib_find_rep_enter_text"));
875                 return;
876         }
877
878         if ((priv->last_search == NULL) || (strcmp (priv->last_search, current_search) != 0)) {
879                 gboolean result;
880                 g_free (priv->last_search);
881                 priv->last_search = g_strdup (current_search);
882                 result = modest_msg_view_search (MODEST_MSG_VIEW (priv->msg_view),
883                                                  priv->last_search);
884                 if (!result) {
885                         hildon_banner_show_information (NULL, NULL, dgettext("hildon-libs", "ckct_ib_find_no_matches"));
886                         g_free (priv->last_search);
887                         priv->last_search = NULL;
888                 } 
889         } else {
890                 if (!modest_msg_view_search_next (MODEST_MSG_VIEW (priv->msg_view))) {
891                         hildon_banner_show_information (NULL, NULL, dgettext("hildon-libs", "ckct_ib_find_search_complete"));
892                         g_free (priv->last_search);
893                         priv->last_search = NULL;
894                 }
895         }
896         
897         g_free (current_search);
898                 
899 }
900
901 static void
902 modest_msg_view_window_set_zoom (ModestWindow *window,
903                                  gdouble zoom)
904 {
905         ModestMsgViewWindowPrivate *priv;
906      
907         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
908
909         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
910         modest_msg_view_set_zoom (MODEST_MSG_VIEW (priv->msg_view), zoom);
911 }
912
913 static gdouble
914 modest_msg_view_window_get_zoom (ModestWindow *window)
915 {
916         ModestMsgViewWindowPrivate *priv;
917      
918         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), 1.0);
919
920         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
921         return modest_msg_view_get_zoom (MODEST_MSG_VIEW (priv->msg_view));
922 }
923
924 static gboolean
925 modest_msg_view_window_zoom_plus (ModestWindow *window)
926 {
927         ModestWindowPrivate *parent_priv;
928         GtkRadioAction *zoom_radio_action;
929         GSList *group, *node;
930
931         parent_priv = MODEST_WINDOW_GET_PRIVATE (window);
932         zoom_radio_action = GTK_RADIO_ACTION (gtk_ui_manager_get_action (parent_priv->ui_manager, 
933                                                                          "/MenuBar/ViewMenu/ZoomMenu/Zoom50Menu"));
934
935         group = gtk_radio_action_get_group (zoom_radio_action);
936
937         if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (group->data))) {
938                 hildon_banner_show_information (NULL, NULL, dgettext("hildon-common-strings", "ckct_ib_max_zoom_level_reached"));
939                 return FALSE;
940         }
941
942         for (node = group; node != NULL; node = g_slist_next (node)) {
943                 if ((node->next != NULL) && gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (node->next->data))) {
944                         gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (node->data), TRUE);
945                         return TRUE;
946                 }
947         }
948         return FALSE;
949 }
950
951 static gboolean
952 modest_msg_view_window_zoom_minus (ModestWindow *window)
953 {
954         ModestWindowPrivate *parent_priv;
955         GtkRadioAction *zoom_radio_action;
956         GSList *group, *node;
957
958         parent_priv = MODEST_WINDOW_GET_PRIVATE (window);
959         zoom_radio_action = GTK_RADIO_ACTION (gtk_ui_manager_get_action (parent_priv->ui_manager, 
960                                                                          "/MenuBar/ViewMenu/ZoomMenu/Zoom50Menu"));
961
962         group = gtk_radio_action_get_group (zoom_radio_action);
963
964         for (node = group; node != NULL; node = g_slist_next (node)) {
965                 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (node->data))) {
966                         if (node->next != NULL) {
967                                 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (node->next->data), TRUE);
968                                 return TRUE;
969                         } else {
970                           hildon_banner_show_information (NULL, NULL, dgettext("hildon-common-strings", "ckct_ib_min_zoom_level_reached"));
971                                 return FALSE;
972                         }
973                         break;
974                 }
975         }
976         return FALSE;
977 }
978
979 static gboolean
980 modest_msg_view_window_key_release_event (GtkWidget *window,
981                                           GdkEventKey *event,
982                                           gpointer userdata)
983 {
984         if (event->type == GDK_KEY_RELEASE) {
985                 switch (event->keyval) {
986                 case GDK_Up:
987                         modest_msg_view_window_scroll_up (MODEST_WINDOW (window));
988                         return TRUE;
989                         break;
990                 case GDK_Down:
991                         modest_msg_view_window_scroll_down (MODEST_WINDOW (window));
992                         return TRUE;
993                         break;
994                 default:
995                         return FALSE;
996                         break;
997                 };
998         } else {
999                 return FALSE;
1000         }
1001 }
1002
1003 static void
1004 modest_msg_view_window_scroll_up (ModestWindow *window)
1005 {
1006         ModestMsgViewWindowPrivate *priv;
1007         gboolean return_value;
1008
1009         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1010         g_signal_emit_by_name (G_OBJECT (priv->main_scroll), "scroll-child", GTK_SCROLL_STEP_UP, FALSE, &return_value);
1011 }
1012
1013 static void
1014 modest_msg_view_window_scroll_down (ModestWindow *window)
1015 {
1016         ModestMsgViewWindowPrivate *priv;
1017         gboolean return_value;
1018
1019         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1020         g_signal_emit_by_name (G_OBJECT (priv->main_scroll), "scroll-child", GTK_SCROLL_STEP_DOWN, FALSE, &return_value);
1021 }
1022
1023 gboolean
1024 modest_msg_view_window_last_message_selected (ModestMsgViewWindow *window)
1025 {
1026         GtkTreePath *path;
1027         ModestMsgViewWindowPrivate *priv;
1028         GtkTreeIter tmp_iter;
1029         gboolean has_next = FALSE;
1030
1031         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), TRUE);
1032         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1033
1034         if (priv->header_model) {
1035                 path = gtk_tree_row_reference_get_path (priv->row_reference);
1036                 if (path == NULL) return FALSE;
1037                 while (!has_next) {
1038                         TnyHeader *header;
1039                         gtk_tree_path_next (path);
1040                         if (!gtk_tree_model_get_iter (priv->header_model, &tmp_iter, path))
1041                                 break;
1042                         gtk_tree_model_get (priv->header_model, &tmp_iter, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1043                                             &header, -1);
1044                         if (!(tny_header_get_flags(header) & TNY_HEADER_FLAG_DELETED)) {
1045                                 has_next = TRUE;
1046                                 break;
1047                         }       
1048                 }
1049                 gtk_tree_path_free (path);
1050                 return !has_next;
1051         } else {
1052                 return TRUE;
1053         }
1054         
1055 }
1056
1057 gboolean
1058 modest_msg_view_window_has_headers_model (ModestMsgViewWindow *window)
1059 {
1060         ModestMsgViewWindowPrivate *priv;
1061
1062         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), TRUE);
1063         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1064
1065         return priv->header_model != NULL;
1066 }
1067
1068 gboolean
1069 modest_msg_view_window_first_message_selected (ModestMsgViewWindow *window)
1070 {
1071         GtkTreePath *path;
1072         ModestMsgViewWindowPrivate *priv;
1073         gboolean result;
1074         GtkTreeIter tmp_iter;
1075
1076         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), TRUE);
1077         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1078
1079         if (priv->header_model) {
1080                 gchar * path_string;
1081                 path = gtk_tree_row_reference_get_path (priv->row_reference);
1082                 if (!path)
1083                         return TRUE;
1084
1085                 path_string = gtk_tree_path_to_string (path);
1086                 result = (strcmp (path_string, "0")==0);
1087                 if (result) {
1088                         g_free (path_string);
1089                         gtk_tree_path_free (path);
1090                         return result;
1091                 }
1092
1093                 while (result) {
1094                         TnyHeader *header;
1095
1096                         gtk_tree_path_prev (path);
1097                         
1098                         if (!gtk_tree_model_get_iter (priv->header_model, &tmp_iter, path))
1099                                 break;
1100                         gtk_tree_model_get (priv->header_model, &tmp_iter, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1101                                             &header, -1);
1102                         if (!(tny_header_get_flags(header)&TNY_HEADER_FLAG_DELETED)) {
1103                                 result = FALSE;
1104                                 break;
1105                         }
1106
1107                         path_string = gtk_tree_path_to_string (path);
1108                         if (strcmp(path_string, "0")==0) {
1109                                 g_free (path_string);
1110                                 break;
1111                         }
1112                         g_free (path_string);
1113                 }
1114                 gtk_tree_path_free (path);
1115                 return result;
1116         } else {
1117                 return TRUE;
1118         }
1119         
1120 }
1121
1122 /**
1123  * Reads the message whose summary item is @header. It takes care of
1124  * several things, among others:
1125  *
1126  * If the message was not previously downloaded then ask the user
1127  * before downloading. If there is no connection launch the connection
1128  * dialog. Update toolbar dimming rules.
1129  *
1130  * Returns: TRUE if the mail operation was started, otherwise if the
1131  * user do not want to download the message, or if the user do not
1132  * want to connect, then the operation is not issued
1133  **/
1134 static gboolean
1135 message_reader (ModestMsgViewWindow *window,
1136                 ModestMsgViewWindowPrivate *priv,
1137                 TnyHeader *header,
1138                 GtkTreePath *path)
1139 {
1140         ModestMailOperation *mail_op = NULL;
1141         ModestMailOperationTypeOperation op_type;
1142         gboolean already_showing = FALSE;
1143         ModestWindow *msg_window = NULL;
1144         ModestWindowMgr *mgr;
1145
1146         g_return_val_if_fail (path != NULL, FALSE);
1147
1148         mgr = modest_runtime_get_window_mgr ();
1149         already_showing = modest_window_mgr_find_registered_header (mgr, header, &msg_window);
1150         if (already_showing && (msg_window != MODEST_WINDOW (window))) {
1151                 gboolean retval;
1152                 if (msg_window)
1153                         gtk_window_present (GTK_WINDOW (msg_window));
1154                 g_signal_emit_by_name (G_OBJECT (window), "delete-event", NULL, &retval);
1155                 return TRUE;
1156         }
1157
1158         /* Msg download completed */
1159         if (tny_header_get_flags (header) & TNY_HEADER_FLAG_CACHED) {
1160                 op_type = MODEST_MAIL_OPERATION_TYPE_OPEN;
1161         } else {
1162                 TnyFolder *folder;
1163                 GtkResponseType response;
1164
1165                 op_type = MODEST_MAIL_OPERATION_TYPE_RECEIVE;
1166
1167                 /* Ask the user if he wants to download the message */
1168                 response = modest_platform_run_confirmation_dialog (GTK_WINDOW (window),
1169                                                                     _("mcen_nc_get_msg"));
1170                 if (response == GTK_RESPONSE_CANCEL)
1171                         return FALSE;
1172                 
1173                 /* Offer the connection dialog if necessary */
1174                 /* FIXME: should this stuff go directly to the mail
1175                    operation instead of spread it all over the
1176                    code? */
1177                 folder = tny_header_get_folder (header);
1178                 if (folder) {
1179                         if (!modest_platform_connect_and_wait_if_network_folderstore (NULL, TNY_FOLDER_STORE (folder))) {
1180                                 g_object_unref (folder);
1181                                 return FALSE;
1182                         }
1183                         g_object_unref (folder);
1184                 }
1185         }
1186
1187         /* New mail operation */
1188         mail_op = modest_mail_operation_new_with_error_handling (op_type, 
1189                                                                  G_OBJECT(window),
1190                                                                  modest_ui_actions_get_msgs_full_error_handler, 
1191                                                                  NULL);
1192                                 
1193         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (), mail_op);
1194         modest_mail_operation_get_msg (mail_op, header, view_msg_cb, path);
1195         g_object_unref (mail_op);
1196
1197         /* Update toolbar dimming rules */
1198         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (window));
1199
1200         return TRUE;
1201 }
1202
1203 gboolean        
1204 modest_msg_view_window_select_next_message (ModestMsgViewWindow *window)
1205 {
1206         ModestMsgViewWindowPrivate *priv;
1207         GtkTreePath *path= NULL;
1208         GtkTreeIter tmp_iter;
1209         TnyHeader *header;
1210         gboolean retval = TRUE;
1211
1212         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), FALSE);
1213         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1214
1215         /* Update the next row reference if it's not valid. This could
1216            happen if for example the header which it was pointing to,
1217            was deleted. The best place to do it is in the row-deleted
1218            handler but the tinymail model do not work like the glib
1219            tree models and reports the deletion when the row is still
1220            there */
1221         if (!gtk_tree_row_reference_valid (priv->next_row_reference)) {
1222                 if (gtk_tree_row_reference_valid (priv->row_reference)) {
1223                         priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
1224                         select_next_valid_row (priv->header_model, &(priv->next_row_reference), FALSE);
1225                 }
1226         }
1227         if (priv->next_row_reference)
1228                 path = gtk_tree_row_reference_get_path (priv->next_row_reference);
1229         if (path == NULL)
1230                 return FALSE;
1231
1232         gtk_tree_model_get_iter (priv->header_model,
1233                                  &tmp_iter,
1234                                  path);
1235
1236         gtk_tree_model_get (priv->header_model, &tmp_iter, 
1237                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1238                             &header, -1);
1239         
1240         /* Read the message & show it */
1241         if (!message_reader (window, priv, header, path)) {
1242                 retval = FALSE;
1243                 gtk_tree_path_free (path);
1244         }
1245
1246         /* Free */
1247         g_object_unref (header);
1248
1249         return retval;          
1250 }
1251
1252 gboolean 
1253 modest_msg_view_window_select_first_message (ModestMsgViewWindow *self)
1254 {
1255         ModestMsgViewWindowPrivate *priv = NULL;
1256         TnyHeader *header = NULL;
1257         GtkTreeIter iter;
1258         GtkTreePath *path;
1259
1260         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), FALSE);
1261         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
1262
1263         /* Check that the model is not empty */
1264         if (!gtk_tree_model_get_iter_first (priv->header_model, &iter))
1265                 return FALSE;
1266
1267         /* Get the header */
1268         gtk_tree_model_get (priv->header_model, 
1269                             &iter, 
1270                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1271                             &header, -1);
1272         g_return_val_if_fail (TNY_IS_HEADER (header), FALSE);
1273         if (tny_header_get_flags (header) & TNY_HEADER_FLAG_DELETED) {
1274                 g_object_unref (header);
1275                 return modest_msg_view_window_select_next_message (self);
1276         }
1277         
1278         path = gtk_tree_model_get_path (priv->header_model, &iter);
1279         
1280         /* Read the message & show it */
1281         message_reader (self, priv, header, path);
1282         
1283         /* Free */
1284         g_object_unref (header);
1285
1286         return TRUE;
1287 }
1288  
1289 gboolean        
1290 modest_msg_view_window_select_previous_message (ModestMsgViewWindow *window)
1291 {
1292         ModestMsgViewWindowPrivate *priv = NULL;
1293         GtkTreePath *path;
1294
1295         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), FALSE);
1296         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1297
1298         /* Return inmediatly if there is no header model */
1299         if (!priv->header_model)
1300                 return FALSE;
1301
1302         path = gtk_tree_row_reference_get_path (priv->row_reference);
1303         while (gtk_tree_path_prev (path)) {
1304                 TnyHeader *header;
1305                 GtkTreeIter iter;
1306
1307                 gtk_tree_model_get_iter (priv->header_model, &iter, path);
1308                 gtk_tree_model_get (priv->header_model, &iter, 
1309                                     TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1310                                     &header, -1);
1311                 if (!header)
1312                         break;
1313                 if (tny_header_get_flags (header) & TNY_HEADER_FLAG_DELETED) {
1314                         g_object_unref (header);
1315                         continue;
1316                 }
1317
1318                 /* Read the message & show it */
1319                 if (!message_reader (window, priv, header, path)) {
1320                         g_object_unref (header);
1321                         break;
1322                 }
1323
1324                 g_object_unref (header);
1325
1326                 return TRUE;
1327         }
1328
1329         gtk_tree_path_free (path);
1330         return FALSE;
1331 }
1332
1333 static void
1334 view_msg_cb (ModestMailOperation *mail_op, 
1335              TnyHeader *header, 
1336              TnyMsg *msg, 
1337              gpointer user_data)
1338 {
1339         ModestMsgViewWindow *self = NULL;
1340         ModestMsgViewWindowPrivate *priv = NULL;
1341         GtkTreePath *path;
1342
1343         /* If there was any error */
1344         path = (GtkTreePath *) user_data;
1345         if (!modest_ui_actions_msg_retrieval_check (mail_op, header, msg)) {
1346                 gtk_tree_path_free (path);                      
1347                 return;
1348         }
1349
1350         /* Get the window */ 
1351         self = (ModestMsgViewWindow *) modest_mail_operation_get_source (mail_op);
1352         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self));
1353         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
1354
1355         /* Update the row reference */
1356         gtk_tree_row_reference_free (priv->row_reference);
1357         priv->row_reference = gtk_tree_row_reference_new (priv->header_model, path);
1358         priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
1359         select_next_valid_row (priv->header_model, &(priv->next_row_reference), TRUE);
1360         gtk_tree_path_free (path);
1361
1362         /* Mark header as read */
1363         if (!(tny_header_get_flags (header) & TNY_HEADER_FLAG_SEEN))
1364                 tny_header_set_flags (header, TNY_HEADER_FLAG_SEEN);
1365
1366         /* Set new message */
1367         modest_msg_view_set_message (MODEST_MSG_VIEW (priv->msg_view), msg);
1368         modest_msg_view_window_update_priority (self);
1369         update_window_title (MODEST_MSG_VIEW_WINDOW (self));
1370         modest_msg_view_grab_focus (MODEST_MSG_VIEW (priv->msg_view));
1371
1372         /* Set the new message uid of the window  */
1373         if (priv->msg_uid) {
1374                 g_free (priv->msg_uid);
1375                 priv->msg_uid = modest_tny_folder_get_header_unique_id (header);
1376         }
1377
1378         /* Notify the observers */
1379         g_signal_emit (G_OBJECT (self), signals[MSG_CHANGED_SIGNAL], 
1380                        0, priv->header_model, priv->row_reference);
1381
1382         /* Free new references */
1383         g_object_unref (self);
1384 }
1385
1386 TnyFolderType
1387 modest_msg_view_window_get_folder_type (ModestMsgViewWindow *window)
1388 {
1389         ModestMsgViewWindowPrivate *priv;
1390         TnyMsg *msg;
1391         TnyFolderType folder_type;
1392
1393         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1394
1395         folder_type = TNY_FOLDER_TYPE_UNKNOWN;
1396
1397         msg = modest_msg_view_get_message (MODEST_MSG_VIEW (priv->msg_view));
1398         if (msg) {
1399                 TnyFolder *folder;
1400
1401                 folder = tny_msg_get_folder (msg);
1402                 
1403                 if (folder) {
1404                         folder_type = tny_folder_get_folder_type (folder);
1405                         
1406                         if (folder_type == TNY_FOLDER_TYPE_NORMAL || folder_type == TNY_FOLDER_TYPE_UNKNOWN) {
1407                                 const gchar *fname = tny_folder_get_name (folder);
1408                                 folder_type = modest_tny_folder_guess_folder_type_from_name (fname);
1409                         }
1410
1411                         g_object_unref (folder);
1412                 }
1413                 g_object_unref (msg);
1414         }
1415
1416         return folder_type;
1417 }
1418
1419
1420 static void
1421 modest_msg_view_window_update_priority (ModestMsgViewWindow *window)
1422 {
1423         ModestMsgViewWindowPrivate *priv;
1424         TnyHeaderFlags flags = 0;
1425
1426         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1427
1428         if (priv->header_model) {
1429                 TnyHeader *header;
1430                 GtkTreeIter iter;
1431                 GtkTreePath *path = NULL;
1432
1433                 path = gtk_tree_row_reference_get_path (priv->row_reference);
1434                 g_return_if_fail (path != NULL);
1435                 gtk_tree_model_get_iter (priv->header_model, 
1436                                          &iter, 
1437                                          gtk_tree_row_reference_get_path (priv->row_reference));
1438
1439                 gtk_tree_model_get (priv->header_model, &iter, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1440                                     &header, -1);
1441                 flags = tny_header_get_flags (header);
1442                 gtk_tree_path_free (path);
1443         }
1444
1445         modest_msg_view_set_priority (MODEST_MSG_VIEW(priv->msg_view), flags);
1446
1447 }
1448
1449 static gboolean
1450 modest_msg_view_window_window_state_event (GtkWidget *widget, GdkEventWindowState *event, gpointer userdata)
1451 {
1452         if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) {
1453                 ModestWindowPrivate *parent_priv;
1454                 ModestWindowMgr *mgr;
1455                 gboolean is_fullscreen;
1456                 GtkAction *fs_toggle_action;
1457                 gboolean active;
1458
1459                 mgr = modest_runtime_get_window_mgr ();
1460                 is_fullscreen = (modest_window_mgr_get_fullscreen_mode (mgr))?1:0;
1461
1462                 parent_priv = MODEST_WINDOW_GET_PRIVATE (widget);
1463                 
1464                 fs_toggle_action = gtk_ui_manager_get_action (parent_priv->ui_manager, "/MenuBar/ViewMenu/ViewToggleFullscreenMenu");
1465                 active = (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (fs_toggle_action)))?1:0;
1466                 if (is_fullscreen != active) {
1467                         gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (fs_toggle_action), is_fullscreen);
1468                 }
1469         }
1470
1471         return FALSE;
1472
1473 }
1474
1475 void
1476 modest_msg_view_window_toggle_fullscreen (ModestMsgViewWindow *window)
1477 {
1478                 ModestWindowPrivate *parent_priv;
1479                 GtkAction *fs_toggle_action;
1480                 parent_priv = MODEST_WINDOW_GET_PRIVATE (window);
1481                 
1482                 fs_toggle_action = gtk_ui_manager_get_action (parent_priv->ui_manager, "/MenuBar/ViewMenu/ViewToggleFullscreenMenu");
1483                 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (fs_toggle_action),
1484                                               !gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (fs_toggle_action)));
1485 }
1486
1487 static void
1488 set_homogeneous (GtkWidget *widget,
1489                  gpointer data)
1490 {
1491         if (GTK_IS_TOOL_ITEM (widget)) {
1492                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (widget), TRUE);
1493                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (widget), TRUE);
1494         }
1495 }
1496
1497 static void
1498 modest_msg_view_window_show_toolbar (ModestWindow *self,
1499                                      gboolean show_toolbar)
1500 {
1501         ModestMsgViewWindowPrivate *priv = NULL;
1502         ModestWindowPrivate *parent_priv;
1503         GtkWidget *reply_button = NULL, *menu = NULL;
1504         GtkWidget *placeholder = NULL;
1505         gint insert_index;
1506         
1507         parent_priv = MODEST_WINDOW_GET_PRIVATE(self);
1508         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
1509
1510         /* Set optimized view status */
1511         priv->optimized_view = !show_toolbar;
1512
1513         if (!parent_priv->toolbar) {
1514                 parent_priv->toolbar = gtk_ui_manager_get_widget (parent_priv->ui_manager, 
1515                                                                   "/ToolBar");
1516
1517                 /* Set homogeneous toolbar */
1518                 gtk_container_foreach (GTK_CONTAINER (parent_priv->toolbar), 
1519                                        set_homogeneous, NULL);
1520
1521                 priv->progress_toolitem = GTK_WIDGET (gtk_tool_item_new ());
1522                 priv->cancel_toolitem = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarCancel");
1523                 priv->next_toolitem = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageNext");
1524                 priv->prev_toolitem = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageBack");
1525                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->progress_toolitem), TRUE);
1526                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (priv->progress_toolitem), TRUE);
1527                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (priv->cancel_toolitem), FALSE);
1528                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->cancel_toolitem), FALSE);
1529
1530                 /* Add ProgressBar (Transfer toolbar) */ 
1531                 priv->progress_bar = modest_progress_bar_widget_new ();
1532                 gtk_widget_set_no_show_all (priv->progress_bar, TRUE);
1533                 placeholder = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ProgressbarView");
1534                 insert_index = gtk_toolbar_get_item_index(GTK_TOOLBAR (parent_priv->toolbar), GTK_TOOL_ITEM(placeholder));
1535                 gtk_container_add (GTK_CONTAINER (priv->progress_toolitem), priv->progress_bar);
1536                 gtk_toolbar_insert(GTK_TOOLBAR(parent_priv->toolbar), GTK_TOOL_ITEM (priv->progress_toolitem), insert_index);
1537                 
1538                 /* Connect cancel 'clicked' signal to abort progress mode */
1539                 g_signal_connect(priv->cancel_toolitem, "clicked",
1540                                  G_CALLBACK(cancel_progressbar),
1541                                  self);
1542                 
1543                 /* Add it to the observers list */
1544                 priv->progress_widgets = g_slist_prepend(priv->progress_widgets, priv->progress_bar);
1545
1546                 /* Add to window */
1547                 hildon_window_add_toolbar (HILDON_WINDOW (self), 
1548                                            GTK_TOOLBAR (parent_priv->toolbar));
1549
1550
1551                 /* Set reply button tap and hold menu */        
1552                 reply_button = gtk_ui_manager_get_widget (parent_priv->ui_manager, 
1553                                                           "/ToolBar/ToolbarMessageReply");
1554                 menu = gtk_ui_manager_get_widget (parent_priv->ui_manager, 
1555                                                   "/ToolbarReplyCSM");
1556                 gtk_widget_tap_and_hold_setup (GTK_WIDGET (reply_button), menu, NULL, 0);
1557         }
1558
1559         if (show_toolbar) {
1560                 /* Quick hack: this prevents toolbar icons "dance" when progress bar show status is changed */ 
1561                 /* TODO: resize mode migth be GTK_RESIZE_QUEUE, in order to avoid unneccesary shows */
1562                 gtk_container_set_resize_mode (GTK_CONTAINER(parent_priv->toolbar), GTK_RESIZE_IMMEDIATE);
1563                 
1564                 gtk_widget_show (GTK_WIDGET (parent_priv->toolbar));
1565                 set_toolbar_mode (MODEST_MSG_VIEW_WINDOW(self), TOOLBAR_MODE_NORMAL);                   
1566                 
1567         } else {
1568                 gtk_widget_set_no_show_all (parent_priv->toolbar, TRUE);
1569                 gtk_widget_hide (GTK_WIDGET (parent_priv->toolbar));
1570         }
1571 }
1572
1573 static void 
1574 modest_msg_view_window_clipboard_owner_change (GtkClipboard *clipboard,
1575                                                GdkEvent *event,
1576                                                ModestMsgViewWindow *window)
1577 {
1578         ModestWindowPrivate *parent_priv;
1579 /*      GtkAction *action; */
1580         gboolean is_address;
1581         gchar *selection;
1582         GtkWidget *focused;
1583
1584         if (!GTK_WIDGET_VISIBLE (window))
1585                 return;
1586
1587         parent_priv = MODEST_WINDOW_GET_PRIVATE (window);
1588         selection = gtk_clipboard_wait_for_text (clipboard);
1589
1590         is_address = ((selection != NULL) && (modest_text_utils_validate_recipient (selection, NULL)));
1591         
1592 /*      action = gtk_ui_manager_get_action (parent_priv->ui_manager, "/MenuBar/ToolsMenu/ToolsAddToContactsMenu"); */
1593 /*      gtk_action_set_sensitive (action, is_address); */
1594
1595         focused = gtk_window_get_focus (GTK_WINDOW (window));
1596
1597 /*      action = gtk_ui_manager_get_action (parent_priv->ui_manager, "/MenuBar/EditMenu/EditCopyMenu"); */
1598 /*      gtk_action_set_sensitive (action, (selection != NULL) && (!MODEST_IS_ATTACHMENTS_VIEW (focused))); */
1599
1600 /*      action = gtk_ui_manager_get_action (parent_priv->ui_manager, "/MenuBar/EditMenu/EditCutMenu"); */
1601 /*      gtk_action_set_sensitive (action, (selection != NULL) && (!MODEST_IS_ATTACHMENTS_VIEW (focused))); */
1602
1603         g_free (selection);
1604 /*      modest_msg_view_window_update_dimmed (window); */
1605         
1606 }
1607
1608 gboolean 
1609 modest_msg_view_window_transfer_mode_enabled (ModestMsgViewWindow *self)
1610 {
1611         ModestMsgViewWindowPrivate *priv;
1612         
1613         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), FALSE); 
1614         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
1615
1616         return priv->current_toolbar_mode == TOOLBAR_MODE_TRANSFER;
1617 }
1618
1619 static void
1620 cancel_progressbar (GtkToolButton *toolbutton,
1621                     ModestMsgViewWindow *self)
1622 {
1623         GSList *tmp;
1624         ModestMsgViewWindowPrivate *priv;
1625         
1626         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
1627
1628         /* Get operation observers and cancel its current operation */
1629         tmp = priv->progress_widgets;
1630         while (tmp) {
1631                 modest_progress_object_cancel_current_operation (MODEST_PROGRESS_OBJECT(tmp->data));
1632                 tmp=g_slist_next(tmp);
1633         }
1634 }
1635 static gboolean
1636 observers_empty (ModestMsgViewWindow *self)
1637 {
1638         GSList *tmp = NULL;
1639         ModestMsgViewWindowPrivate *priv;
1640         gboolean is_empty = TRUE;
1641         guint pending_ops = 0;
1642  
1643         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
1644         tmp = priv->progress_widgets;
1645
1646         /* Check all observers */
1647         while (tmp && is_empty)  {
1648                 pending_ops = modest_progress_object_num_pending_operations (MODEST_PROGRESS_OBJECT(tmp->data));
1649                 is_empty = pending_ops == 0;
1650                 
1651                 tmp = g_slist_next(tmp);
1652         }
1653         
1654         return is_empty;
1655 }
1656
1657 static void
1658 on_account_removed (TnyAccountStore *account_store, 
1659                     TnyAccount *account,
1660                     gpointer user_data)
1661 {
1662         /* Do nothing if it's a transport account, because we only
1663            show the messages of a store account */
1664         if (tny_account_get_account_type(account) == TNY_ACCOUNT_TYPE_STORE) {
1665                 const gchar *parent_acc = NULL;
1666                 const gchar *our_acc = NULL;
1667
1668                 our_acc = modest_window_get_active_account (MODEST_WINDOW (user_data));
1669                 parent_acc = modest_tny_account_get_parent_modest_account_name_for_server_account (account);
1670
1671                 /* Close this window if I'm showing a message of the removed account */
1672                 if (strcmp (parent_acc, our_acc) == 0)
1673                         modest_ui_actions_on_close_window (NULL, MODEST_WINDOW (user_data));
1674         }
1675 }
1676
1677 static void
1678 on_queue_changed (ModestMailOperationQueue *queue,
1679                   ModestMailOperation *mail_op,
1680                   ModestMailOperationQueueNotification type,
1681                   ModestMsgViewWindow *self)
1682 {
1683         GSList *tmp;
1684         ModestMsgViewWindowPrivate *priv;
1685         ModestMailOperationTypeOperation op_type;
1686         ModestToolBarModes mode;
1687         
1688         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self));
1689         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
1690
1691         /* If this operations was created by another window, do nothing */
1692         if (!modest_mail_operation_is_mine (mail_op, G_OBJECT(self))) 
1693             return;
1694
1695         /* Get toolbar mode from operation id*/
1696         op_type = modest_mail_operation_get_type_operation (mail_op);
1697         switch (op_type) {
1698 /*      case MODEST_MAIL_OPERATION_TYPE_SEND: */
1699         case MODEST_MAIL_OPERATION_TYPE_RECEIVE:
1700         case MODEST_MAIL_OPERATION_TYPE_OPEN:
1701                 mode = TOOLBAR_MODE_TRANSFER;
1702                 break;
1703         default:
1704                 mode = TOOLBAR_MODE_NORMAL;
1705                 
1706         }
1707                 
1708         /* Add operation observers and change toolbar if neccessary*/
1709         tmp = priv->progress_widgets;
1710         switch (type) {
1711         case MODEST_MAIL_OPERATION_QUEUE_OPERATION_ADDED:
1712                 if (mode == TOOLBAR_MODE_TRANSFER) {
1713                         /* Enable transfer toolbar mode */
1714                         set_toolbar_transfer_mode(self);
1715                         while (tmp) {
1716                                 modest_progress_object_add_operation (MODEST_PROGRESS_OBJECT (tmp->data),
1717                                                                       mail_op);
1718                                 tmp = g_slist_next (tmp);
1719                         }
1720                         
1721                 }
1722                 break;
1723         case MODEST_MAIL_OPERATION_QUEUE_OPERATION_REMOVED:
1724                 if (mode == TOOLBAR_MODE_TRANSFER) {
1725                         while (tmp) {
1726                                 modest_progress_object_remove_operation (MODEST_PROGRESS_OBJECT (tmp->data),
1727                                                                  mail_op);
1728                                 tmp = g_slist_next (tmp);
1729                                 
1730                         }
1731
1732                         /* If no more operations are being observed, NORMAL mode is enabled again */
1733                         if (observers_empty (self)) {
1734                                 set_toolbar_mode (self, TOOLBAR_MODE_NORMAL);
1735                         }
1736                 }
1737                 break;
1738         }
1739 }
1740
1741 GList *
1742 modest_msg_view_window_get_attachments (ModestMsgViewWindow *win) 
1743 {
1744         ModestMsgViewWindowPrivate *priv;
1745         GList *selected_attachments = NULL;
1746         
1747         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (win), NULL);
1748         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (win);
1749
1750         selected_attachments = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
1751         
1752         return selected_attachments;
1753 }
1754
1755 void
1756 modest_msg_view_window_view_attachment (ModestMsgViewWindow *window, TnyMimePart *mime_part)
1757 {
1758         ModestMsgViewWindowPrivate *priv;
1759         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
1760         g_return_if_fail (TNY_IS_MIME_PART (mime_part) || (mime_part == NULL));
1761
1762         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1763
1764         if (mime_part == NULL) {
1765                 gboolean error = FALSE;
1766                 GList *selected_attachments = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
1767                 if (selected_attachments == NULL) {
1768                         error = TRUE;
1769                 } else if (g_list_length (selected_attachments) > 1) {
1770                         hildon_banner_show_information (NULL, NULL, _("mcen_ib_unable_to_display_more"));
1771                         error = TRUE;
1772                 } else {
1773                         mime_part = (TnyMimePart *) selected_attachments->data;
1774                         g_object_ref (mime_part);
1775                 }
1776                 g_list_foreach (selected_attachments, (GFunc) g_object_unref, NULL);
1777                 g_list_free (selected_attachments);
1778
1779                 if (error)
1780                         return;
1781         } else {
1782                 g_object_ref (mime_part);
1783         }
1784
1785         if (tny_mime_part_is_purged (mime_part)) {
1786                 g_object_unref (mime_part);
1787                 return;
1788         }
1789
1790         if (!TNY_IS_MSG (mime_part)) {
1791                 gchar *filepath = NULL;
1792                 const gchar *att_filename = tny_mime_part_get_filename (mime_part);
1793                 gchar *extension = NULL;
1794                 TnyFsStream *temp_stream = NULL;
1795
1796                 if (att_filename) {
1797                         extension = g_strrstr (att_filename, ".");
1798                         if (extension != NULL)
1799                                 extension++;
1800                 }
1801
1802                 temp_stream = modest_maemo_utils_create_temp_stream (extension, &filepath);
1803
1804                 if (temp_stream) {
1805                         const gchar *content_type;
1806                         content_type = tny_mime_part_get_content_type (mime_part);
1807                         tny_mime_part_decode_to_stream (mime_part, TNY_STREAM (temp_stream));
1808                         
1809                         modest_platform_activate_file (filepath, content_type);
1810                         g_object_unref (temp_stream);
1811                         g_free (filepath);
1812                         /* TODO: delete temporary file */
1813                 }
1814         } else {
1815                 /* message attachment */
1816                 TnyHeader *header = NULL;
1817                 ModestWindowMgr *mgr;
1818                 ModestWindow *msg_win = NULL;
1819                 gboolean found;
1820                 
1821                 header = tny_msg_get_header (TNY_MSG (mime_part));
1822                 mgr = modest_runtime_get_window_mgr ();         
1823                 found = modest_window_mgr_find_registered_header (mgr, header, &msg_win);
1824
1825                 if (found) {
1826                         if (msg_win)                            /* there is already a window for this uid; top it */
1827                                 gtk_window_present (GTK_WINDOW(msg_win));
1828                         else 
1829                                 /* if it's found, but there is no msg_win, it's probably in the process of being created;
1830                                  * thus, we don't do anything */
1831                                 g_warning ("window for is already being created");
1832                 } else { 
1833                         /* it's not found, so create a new window for it */
1834                         modest_window_mgr_register_header (mgr, header); /* register the uid before building the window */
1835                         gchar *account = g_strdup (modest_window_get_active_account (MODEST_WINDOW (window)));
1836                         if (!account)
1837                                 account = modest_account_mgr_get_default_account (modest_runtime_get_account_mgr ());
1838                         msg_win = modest_msg_view_window_new (TNY_MSG (mime_part), account, NULL);
1839                         modest_window_set_zoom (MODEST_WINDOW (msg_win), 
1840                                                 modest_window_get_zoom (MODEST_WINDOW (window)));
1841                         modest_window_mgr_register_window (mgr, msg_win);
1842                         gtk_window_set_transient_for (GTK_WINDOW (msg_win), GTK_WINDOW (window));
1843                         gtk_widget_show_all (GTK_WIDGET (msg_win));
1844                 }
1845         }
1846         g_object_unref (mime_part);
1847 }
1848
1849 typedef struct
1850 {
1851         gchar *filename;
1852         TnyMimePart *part;
1853 } SaveMimePartPair;
1854
1855 typedef struct
1856 {
1857         GList *pairs;
1858         GtkWidget *banner;
1859         gboolean result;
1860 } SaveMimePartInfo;
1861
1862 static void save_mime_part_info_free (SaveMimePartInfo *info, gboolean with_struct);
1863 static gboolean idle_save_mime_part_show_result (SaveMimePartInfo *info);
1864 static gpointer save_mime_part_to_file (SaveMimePartInfo *info);
1865 static void save_mime_parts_to_file_with_checks (SaveMimePartInfo *info);
1866
1867 static void 
1868 save_mime_part_info_free (SaveMimePartInfo *info, gboolean with_struct)
1869 {
1870         
1871         GList *node;
1872         for (node = info->pairs; node != NULL; node = g_list_next (node)) {
1873                 SaveMimePartPair *pair = (SaveMimePartPair *) node->data;
1874                 g_free (pair->filename);
1875                 g_object_unref (pair->part);
1876                 g_slice_free (SaveMimePartPair, pair);
1877         }
1878         g_list_free (info->pairs);
1879         info->pairs = NULL;
1880         if (with_struct) {
1881                 gtk_widget_destroy (info->banner);
1882                 g_object_unref (info->banner);
1883                 g_slice_free (SaveMimePartInfo, info);
1884         }
1885 }
1886
1887 static gboolean
1888 idle_save_mime_part_show_result (SaveMimePartInfo *info)
1889 {
1890         if (info->pairs != NULL) {
1891                 gdk_threads_enter ();
1892                 save_mime_parts_to_file_with_checks (info);
1893                 gdk_threads_leave ();
1894         } else {
1895                 gboolean result;
1896                 result = info->result;
1897
1898                 gdk_threads_enter ();
1899                 save_mime_part_info_free (info, TRUE);
1900                 if (result) {
1901                         hildon_banner_show_information (NULL, NULL, _CS("sfil_ib_saved"));
1902                 } else {
1903                         hildon_banner_show_information (NULL, NULL, _("mail_ib_file_operation_failed"));
1904                 }
1905                 gdk_threads_leave ();
1906         }
1907
1908         return FALSE;
1909 }
1910
1911 static gpointer
1912 save_mime_part_to_file (SaveMimePartInfo *info)
1913 {
1914         GnomeVFSResult result;
1915         GnomeVFSHandle *handle;
1916         TnyStream *stream;
1917         SaveMimePartPair *pair = (SaveMimePartPair *) info->pairs->data;
1918
1919         result = gnome_vfs_create (&handle, pair->filename, GNOME_VFS_OPEN_WRITE, FALSE, 0777);
1920         if (result == GNOME_VFS_OK) {
1921                 stream = tny_vfs_stream_new (handle);
1922                 tny_mime_part_decode_to_stream (pair->part, stream);
1923                 g_object_unref (G_OBJECT (stream));
1924                 g_object_unref (pair->part);
1925                 g_slice_free (SaveMimePartPair, pair);
1926                 info->pairs = g_list_delete_link (info->pairs, info->pairs);
1927                 info->result = TRUE;
1928         } else {
1929                 save_mime_part_info_free (info, FALSE);
1930                 info->result = FALSE;
1931         }
1932
1933         g_idle_add ((GSourceFunc) idle_save_mime_part_show_result, info);
1934         return NULL;
1935 }
1936
1937 static void
1938 save_mime_parts_to_file_with_checks (SaveMimePartInfo *info)
1939 {
1940         SaveMimePartPair *pair;
1941         gboolean is_ok = TRUE;
1942
1943         pair = info->pairs->data;
1944         if (modest_maemo_utils_file_exists (pair->filename)) {
1945                 GtkWidget *confirm_overwrite_dialog;
1946                 confirm_overwrite_dialog = hildon_note_new_confirmation (NULL,
1947                                                                          _("emev_nc_replace_files"));
1948                 if (gtk_dialog_run (GTK_DIALOG (confirm_overwrite_dialog)) != GTK_RESPONSE_OK) {
1949                         is_ok = FALSE;
1950                 }
1951                 gtk_widget_destroy (confirm_overwrite_dialog);
1952         }
1953
1954         if (!is_ok) {
1955                 save_mime_part_info_free (info, TRUE);
1956         } else {
1957                 g_thread_create ((GThreadFunc)save_mime_part_to_file, info, FALSE, NULL);
1958         }
1959
1960 }
1961
1962
1963 void
1964 modest_msg_view_window_save_attachments (ModestMsgViewWindow *window, GList *mime_parts)
1965 {
1966         gboolean clean_list = FALSE;
1967         ModestMsgViewWindowPrivate *priv;
1968         GList *files_to_save = NULL;
1969         GtkWidget *save_dialog = NULL;
1970         gchar *folder = NULL;
1971         gboolean canceled = FALSE;
1972         const gchar *filename = NULL;
1973         gchar *save_multiple_str = NULL;
1974
1975         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
1976         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1977
1978         if (mime_parts == NULL) {
1979                 mime_parts = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
1980                 if (mime_parts == NULL)
1981                         return;
1982                 clean_list = TRUE;
1983         }
1984
1985         /* prepare dialog */
1986         if (mime_parts->next == NULL) {
1987                 /* only one attachment selected */
1988                 TnyMimePart *mime_part = (TnyMimePart *) mime_parts->data;
1989                 if (!TNY_IS_MSG (mime_part) && tny_mime_part_is_attachment (mime_part)) {
1990                         filename = tny_mime_part_get_filename (mime_part);
1991                 } else {
1992                         g_warning ("Tried to save a non-file attachment");
1993                         canceled = TRUE;
1994                 }
1995         } else {
1996                 save_multiple_str = g_strdup_printf (_FM("sfil_va_number_of_objects_attachments"), 
1997                                                      g_list_length (mime_parts));
1998         }
1999         
2000         save_dialog = hildon_file_chooser_dialog_new (GTK_WINDOW (window), 
2001                                                       GTK_FILE_CHOOSER_ACTION_SAVE);
2002
2003         /* set folder */
2004         folder = g_build_filename (g_get_home_dir (), DEFAULT_FOLDER, NULL);
2005         gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (save_dialog), folder);
2006         g_free (folder);
2007
2008         /* set filename */
2009         if (filename != NULL)
2010                 gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (save_dialog), 
2011                                                    filename);
2012
2013         /* if multiple, set multiple string */
2014         if (save_multiple_str) {
2015                 g_object_set (G_OBJECT (save_dialog), "save-multiple", save_multiple_str, NULL);
2016         }
2017                 
2018         /* show dialog */
2019         if (gtk_dialog_run (GTK_DIALOG (save_dialog)) == GTK_RESPONSE_OK) {
2020                 gchar *chooser_uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (save_dialog));
2021
2022                 if (!modest_maemo_utils_folder_writable (chooser_uri)) {
2023                         hildon_banner_show_information 
2024                                 (NULL, NULL, dgettext("hildon-fm", "sfil_ib_readonly_location"));
2025                 } else {
2026                         GList *node = NULL;
2027
2028                         for (node = mime_parts; node != NULL; node = g_list_next (node)) {
2029                                 TnyMimePart *mime_part = (TnyMimePart *) node->data;
2030                                 
2031                                 if (tny_mime_part_is_attachment (mime_part)) {
2032                                         SaveMimePartPair *pair;
2033
2034                                         if ((mime_parts->next != NULL) &&
2035                                             (tny_mime_part_get_filename (mime_part) == NULL))
2036                                                 continue;
2037                                         
2038                                         pair = g_slice_new0 (SaveMimePartPair);
2039                                         if (mime_parts->next == NULL) {
2040                                                 pair->filename = g_strdup (chooser_uri);
2041                                         } else {
2042                                                 pair->filename = 
2043                                                         g_build_filename (chooser_uri,
2044                                                                           tny_mime_part_get_filename (mime_part), NULL);
2045                                         }
2046                                         pair->part = g_object_ref (mime_part);
2047                                         files_to_save = g_list_prepend (files_to_save, pair);
2048                                 }
2049                         }
2050                 }
2051                 g_free (chooser_uri);
2052         }
2053
2054         gtk_widget_destroy (save_dialog);
2055
2056         if (clean_list) {
2057                 g_list_foreach (mime_parts, (GFunc) g_object_unref, NULL);
2058                 g_list_free (mime_parts);
2059         }
2060
2061         if (files_to_save != NULL) {
2062                 SaveMimePartInfo *info = g_slice_new0 (SaveMimePartInfo);
2063                 GtkWidget *banner = hildon_banner_show_animation (NULL, NULL, 
2064                                                                   _CS("sfil_ib_saving"));
2065                 info->pairs = files_to_save;
2066                 info->banner = banner;
2067                 info->result = TRUE;
2068                 g_object_ref (banner);
2069                 save_mime_parts_to_file_with_checks (info);
2070         }
2071 }
2072
2073 static gboolean
2074 show_remove_attachment_information (gpointer userdata)
2075 {
2076         ModestMsgViewWindow *window = (ModestMsgViewWindow *) userdata;
2077         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2078
2079         if (priv->remove_attachment_banner != NULL) {
2080                 gtk_widget_destroy (priv->remove_attachment_banner);
2081                 g_object_unref (priv->remove_attachment_banner);
2082         }
2083
2084         priv->remove_attachment_banner = g_object_ref (
2085                 hildon_banner_show_animation (NULL, NULL, _("mcen_ib_removing_attachment")));
2086
2087         return FALSE;
2088 }
2089
2090 void
2091 modest_msg_view_window_remove_attachments (ModestMsgViewWindow *window, gboolean get_all)
2092 {
2093         ModestMsgViewWindowPrivate *priv;
2094         GList *mime_parts = NULL, *node;
2095         gchar *confirmation_message;
2096         gint response;
2097         gint n_attachments;
2098         TnyMsg *msg;
2099 /*      TnyFolder *folder; */
2100
2101         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
2102         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2103
2104         if (get_all)
2105                 mime_parts = modest_msg_view_get_attachments (MODEST_MSG_VIEW (priv->msg_view));
2106         else
2107                 mime_parts = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2108                 
2109         /* Remove already purged messages from mime parts list */
2110         node = mime_parts;
2111         while (node != NULL) {
2112                 TnyMimePart *part = TNY_MIME_PART (node->data);
2113                 if (tny_mime_part_is_purged (part)) {
2114                         GList *deleted_node = node;
2115                         node = g_list_next (node);
2116                         g_object_unref (part);
2117                         mime_parts = g_list_delete_link (mime_parts, deleted_node);
2118                 } else {
2119                         node = g_list_next (node);
2120                 }
2121         }
2122
2123         if (mime_parts == NULL)
2124                 return;
2125
2126         n_attachments = g_list_length (mime_parts);
2127         if (n_attachments == 1) {
2128                 const gchar *filename;
2129
2130                 if (TNY_IS_MSG (mime_parts->data)) {
2131                         TnyHeader *header;
2132                         header = tny_msg_get_header (TNY_MSG (mime_parts->data));
2133                         filename = tny_header_get_subject (header);
2134                         g_object_unref (header);
2135                         if (filename == NULL)
2136                                 filename = _("mail_va_no_subject");
2137                 } else {
2138                         filename = tny_mime_part_get_filename (TNY_MIME_PART (mime_parts->data));
2139                 }
2140                 confirmation_message = g_strdup_printf (_("mcen_nc_purge_file_text"), filename);
2141         } else {
2142                 confirmation_message = g_strdup_printf (ngettext("mcen_nc_purge_file_text", 
2143                                                                  "mcen_nc_purge_files_text", 
2144                                                                  n_attachments), n_attachments);
2145         }
2146         response = modest_platform_run_confirmation_dialog (GTK_WINDOW (window),
2147                                                             confirmation_message);
2148         g_free (confirmation_message);
2149
2150         if (response != GTK_RESPONSE_OK)
2151                 return;
2152
2153         priv->purge_timeout = g_timeout_add (2000, show_remove_attachment_information, window);
2154 /*      folder = tny_msg_get_folder (msg); */
2155 /*      tny_msg_uncache_attachments (msg); */
2156 /*      tny_folder_refresh (folder, NULL); */
2157 /*      g_object_unref (folder); */
2158         
2159         for (node = mime_parts; node != NULL; node = g_list_next (node)) {
2160                 tny_mime_part_set_purged (TNY_MIME_PART (node->data));
2161 /*              modest_msg_view_remove_attachment (MODEST_MSG_VIEW (priv->msg_view), node->data); */
2162         }
2163
2164         msg = modest_msg_view_get_message (MODEST_MSG_VIEW (priv->msg_view));
2165         modest_msg_view_set_message (MODEST_MSG_VIEW (priv->msg_view), NULL);
2166         tny_msg_rewrite_cache (msg);
2167         modest_msg_view_set_message (MODEST_MSG_VIEW (priv->msg_view), msg);
2168
2169         g_list_foreach (mime_parts, (GFunc) g_object_unref, NULL);
2170         g_list_free (mime_parts);
2171
2172         if (priv->purge_timeout > 0) {
2173                 g_source_remove (priv->purge_timeout);
2174                 priv->purge_timeout = 0;
2175         }
2176
2177         if (priv->remove_attachment_banner) {
2178                 gtk_widget_destroy (priv->remove_attachment_banner);
2179                 g_object_unref (priv->remove_attachment_banner);
2180                 priv->remove_attachment_banner = NULL;
2181         }
2182
2183
2184 }
2185
2186
2187 static void
2188 update_window_title (ModestMsgViewWindow *window)
2189 {
2190         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2191         TnyMsg *msg = NULL;
2192         TnyHeader *header = NULL;
2193         const gchar *subject = NULL;
2194
2195         msg = modest_msg_view_get_message (MODEST_MSG_VIEW (priv->msg_view));
2196         if (msg != NULL) {
2197                 header = tny_msg_get_header (msg);
2198                 subject = tny_header_get_subject (header);
2199                 g_object_unref (msg);
2200         }
2201
2202         if ((subject == NULL)||(subject[0] == '\0'))
2203                 subject = _("mail_va_no_subject");
2204
2205         gtk_window_set_title (GTK_WINDOW (window), subject);
2206 }