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