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