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