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