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