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