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