Add method to get the folder without accessing network.
[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         if (info->header)
1920                 modest_window_mgr_register_header (modest_runtime_get_window_mgr (), info->header, NULL);
1921
1922         /* New mail operation */
1923         mail_op = modest_mail_operation_new_with_error_handling (G_OBJECT(parent_window),
1924                                                                  modest_ui_actions_disk_operations_error_handler, 
1925                                                                  NULL, NULL);
1926                                 
1927         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (), mail_op);
1928         if (info->header)
1929                 modest_mail_operation_get_msg (mail_op, info->header, TRUE, view_msg_cb, info->row_reference);
1930         else
1931                 modest_mail_operation_find_msg (mail_op, info->folder, info->uid, TRUE, view_msg_cb);
1932         g_object_unref (mail_op);
1933
1934         /* Update dimming rules */
1935         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (parent_window));
1936         modest_ui_actions_check_menu_dimming_rules (MODEST_WINDOW (parent_window));
1937
1938  frees:
1939         /* Frees. The row_reference will be freed by the view_msg_cb callback */
1940         g_free (info->uid);
1941         if (info->header)
1942                 g_object_unref (info->header);
1943         if (info->folder)
1944                 g_object_unref (info->folder);
1945         g_slice_free (MsgReaderInfo, info);
1946 }
1947
1948
1949 /**
1950  * Reads the message whose summary item is @header. It takes care of
1951  * several things, among others:
1952  *
1953  * If the message was not previously downloaded then ask the user
1954  * before downloading. If there is no connection launch the connection
1955  * dialog. Update toolbar dimming rules.
1956  *
1957  * Returns: TRUE if the mail operation was started, otherwise if the
1958  * user do not want to download the message, or if the user do not
1959  * want to connect, then the operation is not issued
1960  **/
1961 static gboolean
1962 message_reader (ModestMsgViewWindow *window,
1963                 ModestMsgViewWindowPrivate *priv,
1964                 TnyHeader *header,
1965                 const gchar *msg_uid,
1966                 TnyFolder *folder,
1967                 GtkTreeRowReference *row_reference)
1968 {
1969         gboolean already_showing = FALSE;
1970         ModestWindow *msg_window = NULL;
1971         ModestWindowMgr *mgr;
1972         TnyAccount *account;
1973         MsgReaderInfo *info;
1974
1975         mgr = modest_runtime_get_window_mgr ();
1976         already_showing = header && modest_window_mgr_find_registered_header (mgr, header, &msg_window);
1977         if (already_showing && (msg_window != MODEST_WINDOW (window))) {
1978                 gboolean retval;
1979                 if (msg_window)
1980                         gtk_window_present (GTK_WINDOW (msg_window));
1981                 g_signal_emit_by_name (G_OBJECT (window), "delete-event", NULL, &retval);
1982                 return TRUE;
1983         }
1984
1985         if (folder)
1986                 g_object_ref (folder);
1987                 
1988
1989         /* Msg download completed */
1990         if (!header || !(tny_header_get_flags (header) & TNY_HEADER_FLAG_CACHED)) {
1991                 /* Ask the user if he wants to download the message if
1992                    we're not online */
1993                 if (!tny_device_is_online (modest_runtime_get_device())) {
1994                         GtkResponseType response;
1995
1996                         response = modest_platform_run_confirmation_dialog (GTK_WINDOW (window),
1997                                                                             _("mcen_nc_get_msg"));
1998                         if (response == GTK_RESPONSE_CANCEL)
1999                                 return FALSE;
2000
2001                         if (header) {
2002                                 if (folder) g_object_unref (folder);
2003                                 folder = tny_header_get_folder (header);
2004                         }
2005                         info = g_slice_new (MsgReaderInfo);
2006                         info->msg_uid = g_strdup (msg_uid);
2007                         if (header)
2008                                 info->header = g_object_ref (header);
2009                         else
2010                                 info->header = NULL;
2011                         if (row_reference)
2012                                 info->row_reference = gtk_tree_row_reference_copy (row_reference);
2013                         else
2014                                 info->row_reference = NULL;
2015
2016                         /* Offer the connection dialog if necessary */
2017                         modest_platform_connect_if_remote_and_perform ((GtkWindow *) window, 
2018                                                                        TRUE,
2019                                                                        TNY_FOLDER_STORE (folder),
2020                                                                        message_reader_performer, 
2021                                                                        info);
2022                         g_object_unref (folder);
2023                         return TRUE;
2024                 }
2025         }
2026
2027         if (header) {
2028                 if (folder) g_object_unref (folder);
2029                 folder = tny_header_get_folder (header);
2030         }
2031         account = tny_folder_get_account (folder);
2032         info = g_slice_new (MsgReaderInfo);
2033         info->msg_uid = g_strdup (msg_uid);
2034         if (header)
2035                 info->header = g_object_ref (header);
2036         else
2037                 info->header = NULL;
2038         if (row_reference)
2039                 info->row_reference = gtk_tree_row_reference_copy (row_reference);
2040         else
2041                 row_reference = NULL;
2042         
2043         message_reader_performer (FALSE, NULL, (GtkWindow *) window, account, info);
2044         g_object_unref (account);
2045         g_object_unref (folder);
2046
2047         return TRUE;
2048 }
2049
2050 gboolean        
2051 modest_msg_view_window_select_next_message (ModestMsgViewWindow *window)
2052 {
2053         ModestMsgViewWindowPrivate *priv;
2054         GtkTreePath *path= NULL;
2055         GtkTreeIter tmp_iter;
2056         TnyHeader *header;
2057         gboolean retval = TRUE;
2058         GtkTreeRowReference *row_reference = NULL;
2059
2060         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), FALSE);
2061         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2062
2063         if (!priv->row_reference)
2064                 return FALSE;
2065
2066         /* Update the next row reference if it's not valid. This could
2067            happen if for example the header which it was pointing to,
2068            was deleted. The best place to do it is in the row-deleted
2069            handler but the tinymail model do not work like the glib
2070            tree models and reports the deletion when the row is still
2071            there */
2072         if (!gtk_tree_row_reference_valid (priv->next_row_reference)) {
2073                 if (gtk_tree_row_reference_valid (priv->row_reference)) {
2074                         priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
2075                         select_next_valid_row (priv->header_model, &(priv->next_row_reference), FALSE, priv->is_outbox);
2076                 }
2077         }
2078         if (priv->next_row_reference)
2079                 path = gtk_tree_row_reference_get_path (priv->next_row_reference);
2080         if (path == NULL)
2081                 return FALSE;
2082
2083         row_reference = gtk_tree_row_reference_copy (priv->next_row_reference);
2084
2085         gtk_tree_model_get_iter (priv->header_model,
2086                                  &tmp_iter,
2087                                  path);
2088         gtk_tree_path_free (path);
2089
2090         gtk_tree_model_get (priv->header_model, &tmp_iter, 
2091                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
2092                             &header, -1);
2093         
2094         /* Read the message & show it */
2095         if (!message_reader (window, priv, header, NULL, NULL, row_reference)) {
2096                 retval = FALSE;
2097         }
2098         gtk_tree_row_reference_free (row_reference);
2099
2100         /* Free */
2101         g_object_unref (header);
2102
2103         return retval;
2104 }
2105
2106 gboolean        
2107 modest_msg_view_window_select_previous_message (ModestMsgViewWindow *window)
2108 {
2109         ModestMsgViewWindowPrivate *priv = NULL;
2110         GtkTreePath *path;
2111         gboolean finished = FALSE;
2112         gboolean retval = FALSE;
2113
2114         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), FALSE);
2115         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2116
2117         /* Return inmediatly if there is no header model */
2118         if (!priv->header_model || !priv->row_reference)
2119                 return FALSE;
2120
2121         path = gtk_tree_row_reference_get_path (priv->row_reference);
2122         while (!finished && gtk_tree_path_prev (path)) {
2123                 TnyHeader *header;
2124                 GtkTreeIter iter;
2125
2126                 gtk_tree_model_get_iter (priv->header_model, &iter, path);
2127                 gtk_tree_model_get (priv->header_model, &iter, 
2128                                     TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
2129                                     &header, -1);
2130                 finished = TRUE;
2131                 if (header) {
2132                         if (msg_is_visible (header, priv->is_outbox)) {
2133                                 GtkTreeRowReference *row_reference;
2134                                 row_reference = gtk_tree_row_reference_new (priv->header_model, path);
2135                                 /* Read the message & show it */
2136                                 retval = message_reader (window, priv, header, NULL, NULL, row_reference);
2137                                 gtk_tree_row_reference_free (row_reference);
2138                         } else {
2139                                 finished = FALSE;
2140                         }
2141                         g_object_unref (header);
2142                 }
2143         }
2144
2145         gtk_tree_path_free (path);
2146         return retval;
2147 }
2148
2149 static void
2150 view_msg_cb (ModestMailOperation *mail_op, 
2151              TnyHeader *header, 
2152              gboolean canceled,
2153              TnyMsg *msg, 
2154              GError *error,
2155              gpointer user_data)
2156 {
2157         ModestMsgViewWindow *self = NULL;
2158         ModestMsgViewWindowPrivate *priv = NULL;
2159         GtkTreeRowReference *row_reference = NULL;
2160
2161         /* Unregister the header (it was registered before creating the mail operation) */
2162         modest_window_mgr_unregister_header (modest_runtime_get_window_mgr (), header);
2163
2164         row_reference = (GtkTreeRowReference *) user_data;
2165         if (canceled) {
2166                 if (row_reference)
2167                         gtk_tree_row_reference_free (row_reference);
2168                 return;
2169         }
2170         
2171         /* If there was any error */
2172         if (!modest_ui_actions_msg_retrieval_check (mail_op, header, msg)) {
2173                 if (row_reference)
2174                         gtk_tree_row_reference_free (row_reference);                    
2175                 return;
2176         }
2177
2178         /* Get the window */ 
2179         self = (ModestMsgViewWindow *) modest_mail_operation_get_source (mail_op);
2180         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self));
2181         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2182
2183         /* Update the row reference */
2184         if (priv->row_reference != NULL) {
2185                 gtk_tree_row_reference_free (priv->row_reference);
2186                 priv->row_reference = gtk_tree_row_reference_copy (row_reference);
2187                 if (priv->next_row_reference != NULL) {
2188                         gtk_tree_row_reference_free (priv->next_row_reference);
2189                 }
2190                 priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
2191                 select_next_valid_row (priv->header_model, &(priv->next_row_reference), TRUE, priv->is_outbox);
2192         }
2193
2194         /* Mark header as read */
2195         if (!(tny_header_get_flags (header) & TNY_HEADER_FLAG_SEEN))
2196                 tny_header_set_flag (header, TNY_HEADER_FLAG_SEEN);
2197
2198         /* Set new message */
2199         if (priv->msg_view != NULL && TNY_IS_MSG_VIEW (priv->msg_view)) {
2200                 tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
2201                 modest_msg_view_window_update_priority (self);
2202                 update_window_title (MODEST_MSG_VIEW_WINDOW (self));
2203                 modest_msg_view_grab_focus (MODEST_MSG_VIEW (priv->msg_view));
2204         }
2205
2206         /* Set the new message uid of the window  */
2207         if (priv->msg_uid) {
2208                 g_free (priv->msg_uid);
2209                 priv->msg_uid = modest_tny_folder_get_header_unique_id (header);
2210         }
2211
2212         /* Notify the observers */
2213         g_signal_emit (G_OBJECT (self), signals[MSG_CHANGED_SIGNAL], 
2214                        0, priv->header_model, priv->row_reference);
2215
2216         /* Frees */
2217         g_object_unref (self);
2218         if (row_reference)
2219                 gtk_tree_row_reference_free (row_reference);
2220 }
2221
2222 TnyFolderType
2223 modest_msg_view_window_get_folder_type (ModestMsgViewWindow *window)
2224 {
2225         ModestMsgViewWindowPrivate *priv;
2226         TnyMsg *msg;
2227         TnyFolderType folder_type;
2228
2229         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2230
2231         folder_type = TNY_FOLDER_TYPE_UNKNOWN;
2232
2233         msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
2234         if (msg) {
2235                 TnyFolder *folder;
2236
2237                 folder = tny_msg_get_folder (msg);
2238                 if (folder) {
2239                         folder_type = modest_tny_folder_guess_folder_type (folder);
2240                         g_object_unref (folder);
2241                 }
2242                 g_object_unref (msg);
2243         }
2244
2245         return folder_type;
2246 }
2247
2248
2249 static void
2250 modest_msg_view_window_update_priority (ModestMsgViewWindow *window)
2251 {
2252         ModestMsgViewWindowPrivate *priv;
2253         TnyHeader *header = NULL;
2254         TnyHeaderFlags flags = 0;
2255
2256         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2257
2258         if (priv->header_model && priv->row_reference) {
2259                 GtkTreeIter iter;
2260                 GtkTreePath *path = NULL;
2261
2262                 path = gtk_tree_row_reference_get_path (priv->row_reference);
2263                 g_return_if_fail (path != NULL);
2264                 gtk_tree_model_get_iter (priv->header_model, 
2265                                          &iter, 
2266                                          gtk_tree_row_reference_get_path (priv->row_reference));
2267
2268                 gtk_tree_model_get (priv->header_model, &iter, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
2269                                     &header, -1);
2270                 gtk_tree_path_free (path);
2271         } else {
2272                 TnyMsg *msg;
2273                 msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
2274                 if (msg) {
2275                         header = tny_msg_get_header (msg);
2276                         g_object_unref (msg);
2277                 }
2278         }
2279
2280         if (header) {
2281                 flags = tny_header_get_flags (header);
2282                 g_object_unref(G_OBJECT(header));
2283         }
2284
2285         modest_msg_view_set_priority (MODEST_MSG_VIEW(priv->msg_view), flags);
2286
2287 }
2288
2289 static void
2290 toolbar_resize (ModestMsgViewWindow *self)
2291 {
2292         ModestMsgViewWindowPrivate *priv = NULL;
2293         ModestWindowPrivate *parent_priv = NULL;
2294         GtkWidget *widget;
2295         gint static_button_size;
2296         ModestWindowMgr *mgr;
2297
2298         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self));
2299         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2300         parent_priv = MODEST_WINDOW_GET_PRIVATE(self);
2301
2302         mgr = modest_runtime_get_window_mgr ();
2303         static_button_size = modest_window_mgr_get_fullscreen_mode (mgr)?118:108;
2304
2305         if (parent_priv->toolbar) {
2306                 /* left size buttons */
2307                 widget = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageReply");
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/ToolbarMessageMoveTo");
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                 widget = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarDeleteMessage");
2316                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (widget), FALSE);
2317                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (widget), FALSE);
2318                 gtk_widget_set_size_request (GTK_WIDGET (widget), static_button_size, -1);
2319                 widget = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/FindInMessage");
2320                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (widget), FALSE);
2321                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (widget), FALSE);
2322                 gtk_widget_set_size_request (GTK_WIDGET (widget), static_button_size, -1);
2323                 
2324                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (priv->progress_toolitem), FALSE);
2325                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->progress_toolitem), TRUE);
2326                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (priv->cancel_toolitem), FALSE);
2327                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->cancel_toolitem), FALSE);
2328                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (priv->next_toolitem), TRUE);
2329                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->next_toolitem), TRUE);
2330                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (priv->prev_toolitem), TRUE);
2331                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->prev_toolitem), TRUE);
2332         }
2333                 
2334 }
2335
2336 static gboolean
2337 modest_msg_view_window_window_state_event (GtkWidget *widget, GdkEventWindowState *event, gpointer userdata)
2338 {
2339         if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) {
2340                 ModestWindowPrivate *parent_priv;
2341                 ModestWindowMgr *mgr;
2342                 gboolean is_fullscreen;
2343                 GtkAction *fs_toggle_action;
2344                 gboolean active;
2345
2346                 mgr = modest_runtime_get_window_mgr ();
2347                 is_fullscreen = (modest_window_mgr_get_fullscreen_mode (mgr))?1:0;
2348
2349                 parent_priv = MODEST_WINDOW_GET_PRIVATE (widget);
2350                 
2351                 fs_toggle_action = gtk_ui_manager_get_action (parent_priv->ui_manager, "/MenuBar/ViewMenu/ViewToggleFullscreenMenu");
2352                 active = (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (fs_toggle_action)))?1:0;
2353                 if (is_fullscreen != active) {
2354                         gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (fs_toggle_action), is_fullscreen);
2355                 }
2356                 toolbar_resize (MODEST_MSG_VIEW_WINDOW (widget));
2357         }
2358
2359         return FALSE;
2360
2361 }
2362
2363 static void
2364 modest_msg_view_window_show_toolbar (ModestWindow *self,
2365                                      gboolean show_toolbar)
2366 {
2367         ModestMsgViewWindowPrivate *priv = NULL;
2368         ModestWindowPrivate *parent_priv;
2369         GtkWidget *reply_button = NULL, *menu = NULL;
2370         GtkWidget *placeholder = NULL;
2371         gint insert_index;
2372         const gchar *action_name;
2373         GtkAction *action;
2374         
2375         parent_priv = MODEST_WINDOW_GET_PRIVATE(self);
2376         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2377
2378         /* Set optimized view status */
2379         priv->optimized_view = !show_toolbar;
2380
2381         if (!parent_priv->toolbar) {
2382                 parent_priv->toolbar = gtk_ui_manager_get_widget (parent_priv->ui_manager, 
2383                                                                   "/ToolBar");
2384                 gtk_widget_set_no_show_all (parent_priv->toolbar, TRUE);
2385
2386                 priv->progress_toolitem = GTK_WIDGET (gtk_tool_item_new ());
2387                 priv->cancel_toolitem = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarCancel");
2388                 priv->next_toolitem = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageNext");
2389                 priv->prev_toolitem = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageBack");
2390                 toolbar_resize (MODEST_MSG_VIEW_WINDOW (self));
2391
2392                 /* Add ProgressBar (Transfer toolbar) */ 
2393                 priv->progress_bar = modest_progress_bar_new ();
2394                 gtk_widget_set_no_show_all (priv->progress_bar, TRUE);
2395                 placeholder = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ProgressbarView");
2396                 insert_index = gtk_toolbar_get_item_index(GTK_TOOLBAR (parent_priv->toolbar), GTK_TOOL_ITEM(placeholder));
2397                 gtk_container_add (GTK_CONTAINER (priv->progress_toolitem), priv->progress_bar);
2398                 gtk_toolbar_insert(GTK_TOOLBAR(parent_priv->toolbar), GTK_TOOL_ITEM (priv->progress_toolitem), insert_index);
2399                 
2400                 /* Connect cancel 'clicked' signal to abort progress mode */
2401                 g_signal_connect(priv->cancel_toolitem, "clicked",
2402                                  G_CALLBACK(cancel_progressbar),
2403                                  self);
2404                 
2405                 /* Add it to the observers list */
2406                 priv->progress_widgets = g_slist_prepend(priv->progress_widgets, priv->progress_bar);
2407
2408                 /* Add to window */
2409                 hildon_window_add_toolbar (HILDON_WINDOW (self), 
2410                                            GTK_TOOLBAR (parent_priv->toolbar));
2411
2412                 /* Set reply button tap and hold menu */        
2413                 reply_button = gtk_ui_manager_get_widget (parent_priv->ui_manager, 
2414                                                           "/ToolBar/ToolbarMessageReply");
2415                 menu = gtk_ui_manager_get_widget (parent_priv->ui_manager, 
2416                                                   "/ToolbarReplyCSM");
2417                 gtk_widget_tap_and_hold_setup (GTK_WIDGET (reply_button), menu, NULL, 0);
2418         }
2419
2420         if (show_toolbar) {
2421                 /* Quick hack: this prevents toolbar icons "dance" when progress bar show status is changed */ 
2422                 /* TODO: resize mode migth be GTK_RESIZE_QUEUE, in order to avoid unneccesary shows */
2423                 gtk_container_set_resize_mode (GTK_CONTAINER(parent_priv->toolbar), GTK_RESIZE_IMMEDIATE);
2424
2425                 gtk_widget_show (GTK_WIDGET (parent_priv->toolbar));
2426                 if (modest_msg_view_window_transfer_mode_enabled (MODEST_MSG_VIEW_WINDOW (self))) 
2427                         set_toolbar_mode (MODEST_MSG_VIEW_WINDOW (self), TOOLBAR_MODE_TRANSFER);
2428                 else
2429                         set_toolbar_mode (MODEST_MSG_VIEW_WINDOW (self), TOOLBAR_MODE_NORMAL);
2430
2431         } else {
2432                 gtk_widget_set_no_show_all (parent_priv->toolbar, TRUE);
2433                 gtk_widget_hide (GTK_WIDGET (parent_priv->toolbar));
2434         }
2435
2436         /* Update also the actions (to update the toggles in the
2437            menus), we have to do it manually because some other window
2438            of the same time could have changed it (remember that the
2439            toolbar fullscreen mode is shared by all the windows of the
2440            same type */
2441         if (modest_window_mgr_get_fullscreen_mode (modest_runtime_get_window_mgr ()))
2442                 action_name = "/MenuBar/ViewMenu/ViewShowToolbarMenu/ViewShowToolbarFullScreenMenu";
2443         else
2444                 action_name = "/MenuBar/ViewMenu/ViewShowToolbarMenu/ViewShowToolbarNormalScreenMenu";
2445
2446         action = gtk_ui_manager_get_action (parent_priv->ui_manager, action_name);
2447         modest_utils_toggle_action_set_active_block_notify (GTK_TOGGLE_ACTION (action),
2448                                                             show_toolbar);
2449 }
2450
2451 static void 
2452 modest_msg_view_window_clipboard_owner_change (GtkClipboard *clipboard,
2453                                                GdkEvent *event,
2454                                                ModestMsgViewWindow *window)
2455 {
2456         if (!GTK_WIDGET_VISIBLE (window))
2457                 return;
2458
2459         modest_window_check_dimming_rules_group (MODEST_WINDOW (window), MODEST_DIMMING_RULES_CLIPBOARD);
2460 }
2461
2462 gboolean 
2463 modest_msg_view_window_transfer_mode_enabled (ModestMsgViewWindow *self)
2464 {
2465         ModestMsgViewWindowPrivate *priv;
2466         
2467         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), FALSE); 
2468         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2469
2470         return priv->current_toolbar_mode == TOOLBAR_MODE_TRANSFER;
2471 }
2472
2473 static void
2474 cancel_progressbar (GtkToolButton *toolbutton,
2475                     ModestMsgViewWindow *self)
2476 {
2477         GSList *tmp;
2478         ModestMsgViewWindowPrivate *priv;
2479         
2480         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2481
2482         /* Get operation observers and cancel its current operation */
2483         tmp = priv->progress_widgets;
2484         while (tmp) {
2485                 modest_progress_object_cancel_current_operation (MODEST_PROGRESS_OBJECT(tmp->data));
2486                 tmp=g_slist_next(tmp);
2487         }
2488 }
2489 static gboolean
2490 observers_empty (ModestMsgViewWindow *self)
2491 {
2492         GSList *tmp = NULL;
2493         ModestMsgViewWindowPrivate *priv;
2494         gboolean is_empty = TRUE;
2495         guint pending_ops = 0;
2496  
2497         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2498         tmp = priv->progress_widgets;
2499
2500         /* Check all observers */
2501         while (tmp && is_empty)  {
2502                 pending_ops = modest_progress_object_num_pending_operations (MODEST_PROGRESS_OBJECT(tmp->data));
2503                 is_empty = pending_ops == 0;
2504                 
2505                 tmp = g_slist_next(tmp);
2506         }
2507         
2508         return is_empty;
2509 }
2510
2511 static void
2512 on_account_removed (TnyAccountStore *account_store, 
2513                     TnyAccount *account,
2514                     gpointer user_data)
2515 {
2516         /* Do nothing if it's a transport account, because we only
2517            show the messages of a store account */
2518         if (tny_account_get_account_type(account) == TNY_ACCOUNT_TYPE_STORE) {
2519                 const gchar *parent_acc = NULL;
2520                 const gchar *our_acc = NULL;
2521
2522                 our_acc = modest_window_get_active_account (MODEST_WINDOW (user_data));
2523                 parent_acc = modest_tny_account_get_parent_modest_account_name_for_server_account (account);
2524
2525                 /* Close this window if I'm showing a message of the removed account */
2526                 if (strcmp (parent_acc, our_acc) == 0)
2527                         modest_ui_actions_on_close_window (NULL, MODEST_WINDOW (user_data));
2528         }
2529 }
2530
2531 static void 
2532 on_mail_operation_started (ModestMailOperation *mail_op,
2533                            gpointer user_data)
2534 {
2535         ModestMsgViewWindow *self;
2536         ModestMailOperationTypeOperation op_type;
2537         GSList *tmp;
2538         ModestMsgViewWindowPrivate *priv;
2539         GObject *source = NULL;
2540
2541         self = MODEST_MSG_VIEW_WINDOW (user_data);
2542         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2543         op_type = modest_mail_operation_get_type_operation (mail_op);
2544         tmp = priv->progress_widgets;
2545         source = modest_mail_operation_get_source(mail_op);
2546         if (G_OBJECT (self) == source) {
2547                 if (op_type == MODEST_MAIL_OPERATION_TYPE_RECEIVE ) {
2548                         set_toolbar_transfer_mode(self);
2549                         while (tmp) {
2550                                 modest_progress_object_add_operation (
2551                                                 MODEST_PROGRESS_OBJECT (tmp->data),
2552                                                 mail_op);
2553                                 tmp = g_slist_next (tmp);
2554                         }
2555                 }
2556         }
2557         g_object_unref (source);
2558 }
2559
2560 static void 
2561 on_mail_operation_finished (ModestMailOperation *mail_op,
2562                             gpointer user_data)
2563 {
2564         ModestMsgViewWindow *self;
2565         ModestMailOperationTypeOperation op_type;
2566         GSList *tmp;
2567         ModestMsgViewWindowPrivate *priv;
2568         
2569         self = MODEST_MSG_VIEW_WINDOW (user_data);
2570         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2571         op_type = modest_mail_operation_get_type_operation (mail_op);
2572         tmp = priv->progress_widgets;
2573         
2574         if (op_type == MODEST_MAIL_OPERATION_TYPE_RECEIVE ) {
2575                 while (tmp) {
2576                         modest_progress_object_remove_operation (MODEST_PROGRESS_OBJECT (tmp->data),
2577                                                                  mail_op);
2578                         tmp = g_slist_next (tmp);
2579                 }
2580
2581                 /* If no more operations are being observed, NORMAL mode is enabled again */
2582                 if (observers_empty (self)) {
2583                         set_toolbar_mode (self, TOOLBAR_MODE_NORMAL);
2584                 }
2585
2586                 /* Update dimming rules. We have to do this right here
2587                    and not in view_msg_cb because at that point the
2588                    transfer mode is still enabled so the dimming rule
2589                    won't let the user delete the message that has been
2590                    readed for example */
2591                 modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (self));
2592                 modest_ui_actions_check_menu_dimming_rules (MODEST_WINDOW (self));
2593         }
2594 }
2595
2596 static void
2597 on_queue_changed (ModestMailOperationQueue *queue,
2598                   ModestMailOperation *mail_op,
2599                   ModestMailOperationQueueNotification type,
2600                   ModestMsgViewWindow *self)
2601 {       
2602         ModestMsgViewWindowPrivate *priv;
2603
2604         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2605
2606         /* If this operations was created by another window, do nothing */
2607         if (!modest_mail_operation_is_mine (mail_op, G_OBJECT(self))) 
2608             return;
2609
2610         if (type == MODEST_MAIL_OPERATION_QUEUE_OPERATION_ADDED) {
2611                 priv->sighandlers = modest_signal_mgr_connect (priv->sighandlers,
2612                                                                G_OBJECT (mail_op),
2613                                                                "operation-started",
2614                                                                G_CALLBACK (on_mail_operation_started),
2615                                                                self);
2616                 priv->sighandlers = modest_signal_mgr_connect (priv->sighandlers,
2617                                                                G_OBJECT (mail_op),
2618                                                                "operation-finished",
2619                                                                G_CALLBACK (on_mail_operation_finished),
2620                                                                self);
2621         } else if (type == MODEST_MAIL_OPERATION_QUEUE_OPERATION_REMOVED) {
2622                 priv->sighandlers = modest_signal_mgr_disconnect (priv->sighandlers,
2623                                                                   G_OBJECT (mail_op),
2624                                                                   "operation-started");
2625                 priv->sighandlers = modest_signal_mgr_disconnect (priv->sighandlers,
2626                                                                   G_OBJECT (mail_op),
2627                                                                   "operation-finished");
2628         }
2629 }
2630
2631 TnyList *
2632 modest_msg_view_window_get_attachments (ModestMsgViewWindow *win) 
2633 {
2634         ModestMsgViewWindowPrivate *priv;
2635         TnyList *selected_attachments = NULL;
2636         
2637         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (win), NULL);
2638         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (win);
2639
2640         selected_attachments = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2641         
2642         return selected_attachments;
2643 }
2644
2645 typedef struct {
2646         gchar *filepath;
2647         GtkWidget *banner;
2648         guint banner_idle_id;
2649 } DecodeAsyncHelper;
2650
2651 static gboolean
2652 decode_async_banner_idle (gpointer user_data)
2653 {
2654         DecodeAsyncHelper *helper = (DecodeAsyncHelper *) user_data;
2655
2656         helper->banner_idle_id = 0;
2657         helper->banner = hildon_banner_show_animation (NULL, NULL, _("mail_me_opening"));
2658         g_object_ref (helper->banner);
2659
2660         return FALSE;
2661 }
2662
2663 static void
2664 on_decode_to_stream_async_handler (TnyMimePart *mime_part, 
2665                                    gboolean cancelled, 
2666                                    TnyStream *stream, 
2667                                    GError *err, 
2668                                    gpointer user_data)
2669 {
2670         DecodeAsyncHelper *helper = (DecodeAsyncHelper *) user_data;
2671
2672         if (helper->banner_idle_id > 0) {
2673                 g_source_remove (helper->banner_idle_id);
2674                 helper->banner_idle_id = 0;
2675         }
2676         if (helper->banner) {
2677                 gtk_widget_destroy (helper->banner);
2678         }
2679         if (cancelled || err) {
2680                 modest_platform_information_banner (NULL, NULL, 
2681                                                     _("mail_ib_file_operation_failed"));
2682                 goto free;
2683         }
2684
2685         /* make the file read-only */
2686         g_chmod(helper->filepath, 0444);
2687         
2688         /* Activate the file */
2689         modest_platform_activate_file (helper->filepath, modest_tny_mime_part_get_content_type (mime_part));
2690
2691  free:
2692         /* Frees */
2693         g_free (helper->filepath);
2694         g_object_unref (helper->banner);
2695         g_slice_free (DecodeAsyncHelper, helper);
2696 }
2697
2698 void
2699 modest_msg_view_window_view_attachment (ModestMsgViewWindow *window, 
2700                                         TnyMimePart *mime_part)
2701 {
2702         ModestMsgViewWindowPrivate *priv;
2703         const gchar *msg_uid;
2704         gchar *attachment_uid = NULL;
2705         gint attachment_index = 0;
2706         TnyList *attachments;
2707         TnyMimePart *window_msg;
2708
2709         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
2710         g_return_if_fail (TNY_IS_MIME_PART (mime_part) || (mime_part == NULL));
2711         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2712
2713         msg_uid = modest_msg_view_window_get_message_uid (MODEST_MSG_VIEW_WINDOW (window));
2714         attachments = modest_msg_view_get_attachments (MODEST_MSG_VIEW (priv->msg_view));
2715         attachment_index = modest_list_index (attachments, (GObject *) mime_part);
2716         g_object_unref (attachments);
2717         
2718         if (msg_uid && attachment_index >= 0) {
2719                 attachment_uid = g_strdup_printf ("%s/%d", msg_uid, attachment_index);
2720         }
2721
2722         if (mime_part == NULL) {
2723                 gboolean error = FALSE;
2724                 TnyList *selected_attachments = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2725                 if (selected_attachments == NULL || tny_list_get_length (selected_attachments) == 0) {
2726                         error = TRUE;
2727                 } else if (tny_list_get_length (selected_attachments) > 1) {
2728                         hildon_banner_show_information (NULL, NULL, _("mcen_ib_unable_to_display_more"));
2729                         error = TRUE;
2730                 } else {
2731                         TnyIterator *iter;
2732                         iter = tny_list_create_iterator (selected_attachments);
2733                         mime_part = (TnyMimePart *) tny_iterator_get_current (iter);
2734                         g_object_unref (iter);
2735                 }
2736                 if (selected_attachments)
2737                   g_object_unref (selected_attachments);
2738
2739                 if (error)
2740                         return;
2741         } else {
2742                 g_object_ref (mime_part);
2743         }
2744
2745         if (tny_mime_part_is_purged (mime_part)) {
2746                 g_object_unref (mime_part);
2747                 return;
2748         }
2749
2750         /* we also check for mime_part == priv->msg, as this means it's a direct attachment
2751          * shown as attachment, so it should behave as a file */
2752         window_msg = TNY_MIME_PART (tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view)));
2753         if ((!modest_tny_mime_part_is_msg (mime_part) && tny_mime_part_get_filename (mime_part)) ||
2754             mime_part == window_msg) {
2755                 gchar *filepath = NULL;
2756                 const gchar *att_filename = tny_mime_part_get_filename (mime_part);
2757                 gboolean show_error_banner = FALSE;
2758                 TnyFsStream *temp_stream = NULL;
2759                 temp_stream = modest_utils_create_temp_stream (att_filename, attachment_uid,
2760                                                                &filepath);
2761                 
2762                 if (temp_stream != NULL) {
2763                         DecodeAsyncHelper *helper = g_slice_new (DecodeAsyncHelper);
2764                         helper->filepath = g_strdup (filepath);
2765                         helper->banner = NULL;
2766                         helper->banner_idle_id = g_timeout_add (1000, decode_async_banner_idle, helper);
2767                         tny_mime_part_decode_to_stream_async (mime_part, TNY_STREAM (temp_stream), 
2768                                                               on_decode_to_stream_async_handler, 
2769                                                               NULL, 
2770                                                               helper);
2771                         g_object_unref (temp_stream);
2772                         /* NOTE: files in the temporary area will be automatically
2773                          * cleaned after some time if they are no longer in use */
2774                 } else {
2775                         if (filepath) {
2776                                 const gchar *content_type;
2777                                 /* the file may already exist but it isn't writable,
2778                                  * let's try to open it anyway */
2779                                 content_type = modest_tny_mime_part_get_content_type (mime_part);
2780                                 modest_platform_activate_file (filepath, content_type);
2781                         } else {
2782                                 g_warning ("%s: modest_utils_create_temp_stream failed", __FUNCTION__);
2783                                 show_error_banner = TRUE;
2784                         }
2785                 }
2786                 if (filepath)
2787                         g_free (filepath);
2788                 if (show_error_banner)
2789                         modest_platform_information_banner (NULL, NULL, _("mail_ib_file_operation_failed"));
2790         } else if (!modest_tny_mime_part_is_msg (mime_part)) {
2791                 ModestWindowMgr *mgr;
2792                 ModestWindow *msg_win = NULL;
2793                 TnyMsg *current_msg;
2794                 gboolean found;
2795                 TnyHeader *header;
2796
2797                 current_msg = modest_msg_view_window_get_message (MODEST_MSG_VIEW_WINDOW (window));
2798                 mgr = modest_runtime_get_window_mgr ();
2799                 header = tny_msg_get_header (TNY_MSG (current_msg));
2800                 found = modest_window_mgr_find_registered_message_uid (mgr,
2801                                                                        attachment_uid,
2802                                                                        &msg_win);
2803                 
2804                 if (found) {
2805                         g_debug ("window for this body is already being created");
2806                 } else {
2807
2808                         /* it's not found, so create a new window for it */
2809                         modest_window_mgr_register_header (mgr, header, attachment_uid); /* register the uid before building the window */
2810                         gchar *account = g_strdup (modest_window_get_active_account (MODEST_WINDOW (window)));
2811                         const gchar *mailbox = modest_window_get_active_mailbox (MODEST_WINDOW (window));
2812                         if (!account)
2813                                 account = modest_account_mgr_get_default_account (modest_runtime_get_account_mgr ());
2814                         
2815                         msg_win = modest_msg_view_window_new_with_other_body (TNY_MSG (current_msg), TNY_MIME_PART (mime_part),
2816                                                                               account, mailbox, attachment_uid);
2817                         
2818                         modest_window_set_zoom (MODEST_WINDOW (msg_win),
2819                                                 modest_window_get_zoom (MODEST_WINDOW (window)));
2820                         if (modest_window_mgr_register_window (mgr, msg_win, MODEST_WINDOW (window)))
2821                                 gtk_widget_show_all (GTK_WIDGET (msg_win));
2822                         else
2823                                 gtk_widget_destroy (GTK_WIDGET (msg_win));
2824                 }
2825                 g_object_unref (current_msg);           
2826         } else {
2827                 /* message attachment */
2828                 TnyHeader *header = NULL;
2829                 ModestWindowMgr *mgr;
2830                 ModestWindow *msg_win = NULL;
2831                 gboolean found;
2832                 
2833                 header = tny_msg_get_header (TNY_MSG (mime_part));
2834                 mgr = modest_runtime_get_window_mgr ();         
2835                 found = modest_window_mgr_find_registered_header (mgr, header, &msg_win);
2836
2837                 if (found) {
2838                         if (msg_win)                            /* there is already a window for this uid; top it */
2839                                 gtk_window_present (GTK_WINDOW(msg_win));
2840                         else 
2841                                 /* if it's found, but there is no msg_win, it's probably in the process of being created;
2842                                  * thus, we don't do anything */
2843                                 g_debug ("window for is already being created");
2844                 } else { 
2845                         /* it's not found, so create a new window for it */
2846                         modest_window_mgr_register_header (mgr, header, attachment_uid); /* register the uid before building the window */
2847                         gchar *account = g_strdup (modest_window_get_active_account (MODEST_WINDOW (window)));
2848                         if (!account)
2849                                 account = modest_account_mgr_get_default_account (modest_runtime_get_account_mgr ());
2850                         msg_win = modest_msg_view_window_new_for_attachment (TNY_MSG (mime_part), account, 
2851                                                                              NULL, attachment_uid);
2852                         modest_window_set_zoom (MODEST_WINDOW (msg_win), 
2853                                                 modest_window_get_zoom (MODEST_WINDOW (window)));
2854                         modest_window_mgr_register_window (mgr, msg_win, MODEST_WINDOW (window));
2855                         gtk_widget_show_all (GTK_WIDGET (msg_win));
2856                 }
2857         }
2858         g_object_unref (window_msg);
2859         g_object_unref (mime_part);
2860 }
2861
2862 typedef struct
2863 {
2864         gchar *filename;
2865         TnyMimePart *part;
2866 } SaveMimePartPair;
2867
2868 typedef struct
2869 {
2870         GList *pairs;
2871         GtkWidget *banner;
2872         GnomeVFSResult result;
2873 } SaveMimePartInfo;
2874
2875 static void save_mime_part_info_free (SaveMimePartInfo *info, gboolean with_struct);
2876 static gboolean idle_save_mime_part_show_result (SaveMimePartInfo *info);
2877 static gpointer save_mime_part_to_file (SaveMimePartInfo *info);
2878 static void save_mime_parts_to_file_with_checks (GtkWindow *parent, SaveMimePartInfo *info);
2879
2880 static void 
2881 save_mime_part_info_free (SaveMimePartInfo *info, gboolean with_struct)
2882 {
2883         
2884         GList *node;
2885         for (node = info->pairs; node != NULL; node = g_list_next (node)) {
2886                 SaveMimePartPair *pair = (SaveMimePartPair *) node->data;
2887                 g_free (pair->filename);
2888                 g_object_unref (pair->part);
2889                 g_slice_free (SaveMimePartPair, pair);
2890         }
2891         g_list_free (info->pairs);
2892         info->pairs = NULL;
2893         if (with_struct) {
2894                 gtk_widget_destroy (info->banner);
2895                 g_slice_free (SaveMimePartInfo, info);
2896         }
2897 }
2898
2899 static gboolean
2900 idle_save_mime_part_show_result (SaveMimePartInfo *info)
2901 {
2902         if (info->pairs != NULL) {
2903                 save_mime_part_to_file (info);
2904         } else {
2905                 /* This is a GDK lock because we are an idle callback and
2906                  * hildon_banner_show_information is or does Gtk+ code */
2907
2908                 gdk_threads_enter (); /* CHECKED */
2909                 save_mime_part_info_free (info, TRUE);
2910                 if (info->result == GNOME_VFS_OK) {
2911                         hildon_banner_show_information (NULL, NULL, _CS("sfil_ib_saved"));
2912                 } else if (info->result == GNOME_VFS_ERROR_NO_SPACE) {
2913                         hildon_banner_show_information (NULL, NULL, 
2914                                                         _KR("cerm_device_memory_full"));
2915                 } else {
2916                         hildon_banner_show_information (NULL, NULL, 
2917                                                         _("mail_ib_file_operation_failed"));
2918                 }
2919                 gdk_threads_leave (); /* CHECKED */
2920         }
2921
2922         return FALSE;
2923 }
2924
2925 static gpointer
2926 save_mime_part_to_file (SaveMimePartInfo *info)
2927 {
2928         GnomeVFSHandle *handle;
2929         TnyStream *stream;
2930         SaveMimePartPair *pair = (SaveMimePartPair *) info->pairs->data;
2931
2932         info->result = gnome_vfs_create (&handle, pair->filename, GNOME_VFS_OPEN_WRITE, FALSE, 0644);
2933         if (info->result == GNOME_VFS_OK) {
2934                 GError *error = NULL;
2935                 stream = tny_vfs_stream_new (handle);
2936                 if (tny_mime_part_decode_to_stream (pair->part, stream, &error) < 0) {
2937                         g_warning ("modest: could not save attachment %s: %d (%s)\n", pair->filename, error?error->code:-1, error?error->message:"Unknown error");
2938                         
2939                         info->result = GNOME_VFS_ERROR_IO;
2940                 }
2941                 g_object_unref (G_OBJECT (stream));
2942                 g_object_unref (pair->part);
2943                 g_slice_free (SaveMimePartPair, pair);
2944                 info->pairs = g_list_delete_link (info->pairs, info->pairs);
2945         } else {
2946                 g_warning ("modest: could not create save attachment %s: %s\n", pair->filename, gnome_vfs_result_to_string (info->result));
2947                 save_mime_part_info_free (info, FALSE);
2948         }
2949
2950         g_idle_add ((GSourceFunc) idle_save_mime_part_show_result, info);
2951         return NULL;
2952 }
2953
2954 static void
2955 save_mime_parts_to_file_with_checks (GtkWindow *parent, SaveMimePartInfo *info)
2956 {
2957         gboolean is_ok = TRUE;
2958         gint replaced_files = 0;
2959         const GList *files = info->pairs;
2960         const GList *iter;
2961
2962         for (iter = files; (iter != NULL) && (replaced_files < 2); iter = g_list_next(iter)) {
2963                 SaveMimePartPair *pair = iter->data;
2964                 if (modest_utils_file_exists (pair->filename)) {
2965                         replaced_files++;
2966                 }
2967         }
2968         if (replaced_files) {
2969                 gint response;
2970                 const gchar *message = (replaced_files == 1) ?
2971                         _FM("docm_nc_replace_file") : _FM("docm_nc_replace_multiple");
2972                 response = modest_platform_run_confirmation_dialog (parent, message);
2973                 if (response != GTK_RESPONSE_OK)
2974                         is_ok = FALSE;
2975         }
2976
2977         if (!is_ok) {
2978                 save_mime_part_info_free (info, TRUE);
2979         } else {
2980                 GtkWidget *banner = hildon_banner_show_animation (NULL, NULL, 
2981                                                                   _CS("sfil_ib_saving"));
2982                 info->banner = banner;
2983                 g_thread_create ((GThreadFunc)save_mime_part_to_file, info, FALSE, NULL);
2984         }
2985
2986 }
2987
2988 static void
2989 save_attachments_response (GtkDialog *dialog,
2990                            gint       arg1,
2991                            gpointer   user_data)  
2992 {
2993         TnyList *mime_parts;
2994         gchar *chooser_uri;
2995         GList *files_to_save = NULL;
2996
2997         mime_parts = TNY_LIST (user_data);
2998
2999         if (arg1 != GTK_RESPONSE_OK)
3000                 goto end;
3001
3002         chooser_uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dialog));
3003
3004         if (!modest_utils_folder_writable (chooser_uri)) {
3005                 hildon_banner_show_information 
3006                         (NULL, NULL, _FM("sfil_ib_readonly_location"));
3007         } else {
3008                 TnyIterator *iter;
3009
3010                 iter = tny_list_create_iterator (mime_parts);
3011                 while (!tny_iterator_is_done (iter)) {
3012                         TnyMimePart *mime_part = (TnyMimePart *) tny_iterator_get_current (iter);
3013
3014                         if ((modest_tny_mime_part_is_attachment_for_modest (mime_part)) &&
3015                             !tny_mime_part_is_purged (mime_part) &&
3016                             (tny_mime_part_get_filename (mime_part) != NULL)) {
3017                                 SaveMimePartPair *pair;
3018
3019                                 pair = g_slice_new0 (SaveMimePartPair);
3020
3021                                 if (tny_list_get_length (mime_parts) > 1) {
3022                                         gchar *escaped = 
3023                                                 gnome_vfs_escape_slashes (tny_mime_part_get_filename (mime_part));
3024                                         pair->filename = g_build_filename (chooser_uri, escaped, NULL);
3025                                         g_free (escaped);
3026                                 } else {
3027                                         pair->filename = g_strdup (chooser_uri);
3028                                 }
3029                                 pair->part = mime_part;
3030                                 files_to_save = g_list_prepend (files_to_save, pair);
3031                         }
3032                         tny_iterator_next (iter);
3033                 }
3034                 g_object_unref (iter);
3035         }
3036         g_free (chooser_uri);
3037
3038         if (files_to_save != NULL) {
3039                 SaveMimePartInfo *info = g_slice_new0 (SaveMimePartInfo);
3040                 info->pairs = files_to_save;
3041                 info->result = TRUE;
3042                 save_mime_parts_to_file_with_checks ((GtkWindow*) dialog, info);
3043         }
3044
3045  end:
3046         /* Free and close the dialog */
3047         g_object_unref (mime_parts);
3048         gtk_widget_destroy (GTK_WIDGET (dialog));
3049 }
3050
3051 void
3052 modest_msg_view_window_save_attachments (ModestMsgViewWindow *window, TnyList *mime_parts)
3053 {
3054         ModestMsgViewWindowPrivate *priv;
3055         GtkWidget *save_dialog = NULL;
3056         gchar *folder = NULL;
3057         gchar *filename = NULL;
3058         gchar *save_multiple_str = NULL;
3059         TnyMsg *window_msg;
3060
3061         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
3062         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
3063
3064         if (mime_parts == NULL) {
3065                 mime_parts = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
3066                 if (mime_parts == NULL || tny_list_get_length (mime_parts) == 0)
3067                         return;
3068         } else {
3069                 g_object_ref (mime_parts);
3070         }
3071
3072         window_msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
3073         /* prepare dialog */
3074         if (tny_list_get_length (mime_parts) == 1) {
3075                 TnyIterator *iter;
3076                 /* only one attachment selected */
3077                 iter = tny_list_create_iterator (mime_parts);
3078                 TnyMimePart *mime_part = (TnyMimePart *) tny_iterator_get_current (iter);
3079                 g_object_unref (iter);
3080                 if (!modest_tny_mime_part_is_msg (mime_part) && 
3081                     modest_tny_mime_part_is_attachment_for_modest (mime_part) &&
3082                     !tny_mime_part_is_purged (mime_part)) {
3083                         filename = g_strdup (tny_mime_part_get_filename (mime_part));
3084                 } else {
3085                         /* TODO: show any error? */
3086                         g_warning ("Tried to save a non-file attachment");
3087                         g_object_unref (mime_parts);
3088                         return;
3089                 }
3090                 g_object_unref (mime_part);
3091         } else {
3092                 save_multiple_str = g_strdup_printf (_FM("sfil_va_number_of_objects_attachments"), 
3093                                                      tny_list_get_length (mime_parts));
3094         }