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