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