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