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