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