f81ce01634fa438b0b989ec2a1b08bd7fb8aad14
[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                         DecodeAsyncHelper *helper;
2671
2672                         /* Activate progress hint */
2673                         set_progress_hint (window, TRUE);
2674
2675                         helper = g_slice_new0 (DecodeAsyncHelper);
2676                         helper->self = g_object_ref (window);
2677                         helper->file_path = g_strdup (filepath);
2678
2679                         tny_mime_part_decode_to_stream_async (mime_part, TNY_STREAM (temp_stream),
2680                                                               on_decode_to_stream_async_handler,
2681                                                               NULL,
2682                                                               helper);
2683                         g_object_unref (temp_stream);
2684                         /* NOTE: files in the temporary area will be automatically
2685                          * cleaned after some time if they are no longer in use */
2686                 } else {
2687                         if (filepath) {
2688                                 const gchar *content_type;
2689                                 /* the file may already exist but it isn't writable,
2690                                  * let's try to open it anyway */
2691                                 content_type = tny_mime_part_get_content_type (mime_part);
2692                                 modest_platform_activate_file (filepath, content_type);
2693                         } else {
2694                                 g_warning ("%s: modest_utils_create_temp_stream failed", __FUNCTION__);
2695                                 show_error_banner = TRUE;
2696                         }
2697                 }
2698                 if (filepath)
2699                         g_free (filepath);
2700                 if (show_error_banner)
2701                         modest_platform_information_banner (NULL, NULL, _("mail_ib_file_operation_failed"));
2702         } else if (!modest_tny_mime_part_is_msg (mime_part)) {
2703                 ModestWindowMgr *mgr;
2704                 ModestWindow *msg_win = NULL;
2705                 TnyMsg *current_msg;
2706                 gboolean found;
2707                 TnyHeader *header;
2708
2709                 current_msg = modest_msg_view_window_get_message (MODEST_MSG_VIEW_WINDOW (window));
2710                 mgr = modest_runtime_get_window_mgr ();
2711                 header = tny_msg_get_header (TNY_MSG (current_msg));
2712                 found = modest_window_mgr_find_registered_message_uid (mgr,
2713                                                                        attachment_uid,
2714                                                                        &msg_win);
2715                 
2716                 if (found) {
2717                         g_debug ("window for this body is already being created");
2718                 } else {
2719
2720                         /* it's not found, so create a new window for it */
2721                         modest_window_mgr_register_header (mgr, header, attachment_uid); /* register the uid before building the window */
2722                         gchar *account = g_strdup (modest_window_get_active_account (MODEST_WINDOW (window)));
2723                         const gchar *mailbox = modest_window_get_active_mailbox (MODEST_WINDOW (window));
2724                         if (!account)
2725                                 account = modest_account_mgr_get_default_account (modest_runtime_get_account_mgr ());
2726                         
2727                         msg_win = modest_msg_view_window_new_with_other_body (TNY_MSG (current_msg), TNY_MIME_PART (mime_part),
2728                                                                               account, mailbox, attachment_uid);
2729                         
2730                         modest_window_set_zoom (MODEST_WINDOW (msg_win),
2731                                                 modest_window_get_zoom (MODEST_WINDOW (window)));
2732                         if (modest_window_mgr_register_window (mgr, msg_win, MODEST_WINDOW (window)))
2733                                 gtk_widget_show_all (GTK_WIDGET (msg_win));
2734                         else
2735                                 gtk_widget_destroy (GTK_WIDGET (msg_win));
2736                 }
2737                 g_object_unref (current_msg);           
2738         } else {
2739                 /* message attachment */
2740                 TnyHeader *header = NULL;
2741                 ModestWindowMgr *mgr;
2742                 ModestWindow *msg_win = NULL;
2743                 gboolean found;
2744
2745                 header = tny_msg_get_header (TNY_MSG (mime_part));
2746                 mgr = modest_runtime_get_window_mgr ();
2747                 found = modest_window_mgr_find_registered_header (mgr, header, &msg_win);
2748
2749                 if (found) {
2750                         /* if it's found, but there is no msg_win, it's probably in the process of being created;
2751                          * thus, we don't do anything */
2752                         g_debug ("window for is already being created");
2753                 } else {
2754                         /* it's not found, so create a new window for it */
2755                         modest_window_mgr_register_header (mgr, header, attachment_uid); /* register the uid before building the window */
2756                         gchar *account = g_strdup (modest_window_get_active_account (MODEST_WINDOW (window)));
2757                         const gchar *mailbox = modest_window_get_active_mailbox (MODEST_WINDOW (window));
2758                         if (!account)
2759                                 account = modest_account_mgr_get_default_account (modest_runtime_get_account_mgr ());
2760                         msg_win = modest_msg_view_window_new_for_attachment (TNY_MSG (mime_part), account, 
2761                                                                              mailbox, attachment_uid);
2762                         modest_window_set_zoom (MODEST_WINDOW (msg_win),
2763                                                 modest_window_get_zoom (MODEST_WINDOW (window)));
2764                         if (modest_window_mgr_register_window (mgr, msg_win, MODEST_WINDOW (window)))
2765                                 gtk_widget_show_all (GTK_WIDGET (msg_win));
2766                         else
2767                                 gtk_widget_destroy (GTK_WIDGET (msg_win));
2768                 }
2769         }
2770
2771  frees:
2772         if (attachment_uid)
2773                 g_free (attachment_uid);
2774         if (mime_part)
2775                 g_object_unref (mime_part);
2776 }
2777
2778 typedef struct
2779 {
2780         gchar *filename;
2781         TnyMimePart *part;
2782 } SaveMimePartPair;
2783
2784 typedef struct
2785 {
2786         GList *pairs;
2787         GnomeVFSResult result;
2788         gchar *uri;
2789 } SaveMimePartInfo;
2790
2791 static void save_mime_part_info_free (SaveMimePartInfo *info, gboolean with_struct);
2792 static gboolean idle_save_mime_part_show_result (SaveMimePartInfo *info);
2793 static gpointer save_mime_part_to_file (SaveMimePartInfo *info);
2794 static void save_mime_parts_to_file_with_checks (GtkWindow *parent, SaveMimePartInfo *info);
2795
2796 static void
2797 save_mime_part_info_free (SaveMimePartInfo *info, gboolean with_struct)
2798 {
2799         GList *node;
2800
2801         for (node = info->pairs; node != NULL; node = g_list_next (node)) {
2802                 SaveMimePartPair *pair = (SaveMimePartPair *) node->data;
2803                 g_free (pair->filename);
2804                 g_object_unref (pair->part);
2805                 g_slice_free (SaveMimePartPair, pair);
2806         }
2807         g_list_free (info->pairs);
2808         info->pairs = NULL;
2809         g_free (info->uri);
2810         if (with_struct) {
2811                 g_slice_free (SaveMimePartInfo, info);
2812         }
2813 }
2814
2815 static gboolean
2816 idle_save_mime_part_show_result (SaveMimePartInfo *info)
2817 {
2818         /* This is a GDK lock because we are an idle callback and
2819          * hildon_banner_show_information is or does Gtk+ code */
2820
2821         gdk_threads_enter (); /* CHECKED */
2822         if (info->result == GNOME_VFS_OK) {
2823                 hildon_banner_show_information (NULL, NULL, _CS("sfil_ib_saved"));
2824         } else if (info->result == GNOME_VFS_ERROR_NO_SPACE) {
2825                 gchar *msg = NULL;
2826
2827                 /* Check if the uri belongs to the external mmc */
2828                 if (g_str_has_prefix (info->uri, g_getenv (MODEST_MMC1_VOLUMEPATH_ENV)))
2829                         msg = g_strdup_printf (_KR("cerm_device_memory_full"), "");
2830                 else
2831                         msg = g_strdup (_KR("cerm_memory_card_full"));
2832                 modest_platform_information_banner (NULL, NULL, msg);
2833                 g_free (msg);
2834         } else {
2835                 hildon_banner_show_information (NULL, NULL, _("mail_ib_file_operation_failed"));
2836         }
2837         save_mime_part_info_free (info, FALSE);
2838         gdk_threads_leave (); /* CHECKED */
2839
2840         return FALSE;
2841 }
2842
2843 static gpointer
2844 save_mime_part_to_file (SaveMimePartInfo *info)
2845 {
2846         GnomeVFSHandle *handle;
2847         TnyStream *stream;
2848         SaveMimePartPair *pair = (SaveMimePartPair *) info->pairs->data;
2849
2850         info->result = gnome_vfs_create (&handle, pair->filename, GNOME_VFS_OPEN_WRITE, FALSE, 0644);
2851         if (info->result == GNOME_VFS_OK) {
2852                 GError *error = NULL;
2853                 stream = tny_vfs_stream_new (handle);
2854                 if (tny_mime_part_decode_to_stream (pair->part, stream, &error) < 0) {
2855                         g_warning ("modest: could not save attachment %s: %d (%s)\n", pair->filename, error?error->code:-1, error?error->message:"Unknown error");
2856
2857                         if ((error->domain == TNY_ERROR_DOMAIN) && 
2858                             (error->code == TNY_IO_ERROR_WRITE) &&
2859                             (errno == ENOSPC)) {
2860                                 info->result = GNOME_VFS_ERROR_NO_SPACE;
2861                         } else {
2862                                 info->result = GNOME_VFS_ERROR_IO;
2863                         }
2864                 }
2865                 g_object_unref (G_OBJECT (stream));
2866         } else {
2867                 g_warning ("Could not create save attachment %s: %s\n", 
2868                            pair->filename, gnome_vfs_result_to_string (info->result));
2869         }
2870
2871         /* Go on saving remaining files */
2872         info->pairs = g_list_remove_link (info->pairs, info->pairs);
2873         if (info->pairs != NULL) {
2874                 save_mime_part_to_file (info);
2875         } else {
2876                 g_idle_add ((GSourceFunc) idle_save_mime_part_show_result, info);
2877         }
2878
2879         return NULL;
2880 }
2881
2882 static void
2883 save_mime_parts_to_file_with_checks (GtkWindow *parent,
2884                                      SaveMimePartInfo *info)
2885 {
2886         gboolean is_ok = TRUE;
2887         gint replaced_files = 0;
2888         const GList *files = info->pairs;
2889         const GList *iter, *to_replace = NULL;
2890
2891         for (iter = files; (iter != NULL) && (replaced_files < 2); iter = g_list_next(iter)) {
2892                 SaveMimePartPair *pair = iter->data;
2893                 gchar *unescaped = g_uri_unescape_string (pair->filename, NULL);
2894
2895                 if (modest_utils_file_exists (unescaped)) {
2896                         replaced_files++;
2897                         if (replaced_files == 1)
2898                                 to_replace = iter;
2899                 }
2900                 g_free (unescaped);
2901         }
2902         if (replaced_files) {
2903                 gint response;
2904
2905                 if (replaced_files == 1) {
2906                         SaveMimePartPair *pair = to_replace->data;
2907                         const gchar *basename = strrchr (pair->filename, G_DIR_SEPARATOR) + 1;
2908                         gchar *escaped_basename, *message;
2909
2910                         escaped_basename = g_uri_unescape_string (basename, NULL);
2911                         message = g_strdup_printf ("%s\n%s",
2912                                                    _FM("docm_nc_replace_file"),
2913                                                    (escaped_basename) ? escaped_basename : "");
2914                         response = modest_platform_run_confirmation_dialog (parent, message);
2915                         g_free (message);
2916                         g_free (escaped_basename);
2917                 } else {
2918                         response = modest_platform_run_confirmation_dialog (parent,
2919                                                                             _FM("docm_nc_replace_multiple"));
2920                 }
2921                 if (response != GTK_RESPONSE_OK)
2922                         is_ok = FALSE;
2923         }
2924
2925         if (!is_ok) {
2926                 save_mime_part_info_free (info, TRUE);
2927         } else {
2928                 g_thread_create ((GThreadFunc)save_mime_part_to_file, info, FALSE, NULL);
2929         }
2930
2931 }
2932
2933 static void
2934 save_attachments_response (GtkDialog *dialog,
2935                            gint       arg1,
2936                            gpointer   user_data)  
2937 {
2938         TnyList *mime_parts;
2939         gchar *chooser_uri;
2940         GList *files_to_save = NULL;
2941         gchar *current_folder;
2942
2943         mime_parts = TNY_LIST (user_data);
2944
2945         if (arg1 != GTK_RESPONSE_OK)
2946                 goto end;
2947
2948         chooser_uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dialog));
2949         current_folder = gtk_file_chooser_get_current_folder_uri (GTK_FILE_CHOOSER (dialog));
2950         if (current_folder && *current_folder != '\0') {
2951                 GError *err = NULL;
2952                 modest_conf_set_string (modest_runtime_get_conf (), MODEST_CONF_LATEST_SAVE_ATTACHMENT_PATH,
2953                                         current_folder,&err);
2954                 if (err != NULL) {
2955                         g_debug ("Error storing latest used folder: %s", err->message);
2956                         g_error_free (err);
2957                 }
2958         }
2959         g_free (current_folder);
2960
2961         if (!modest_utils_folder_writable (chooser_uri)) {
2962                 const gchar *err_msg;
2963
2964 #ifdef MODEST_PLATFORM_MAEMO
2965                 if (modest_maemo_utils_in_usb_mode ()) {
2966                         err_msg = dgettext ("hildon-status-bar-usb", "usbh_ib_mmc_usb_connected");
2967                 } else {
2968                         err_msg = _FM("sfil_ib_readonly_location");
2969                 }
2970 #else
2971                 err_msg = _FM("sfil_ib_readonly_location");
2972 #endif
2973                 hildon_banner_show_information (NULL, NULL, err_msg);
2974         } else {
2975                 TnyIterator *iter;
2976
2977                 iter = tny_list_create_iterator (mime_parts);
2978                 while (!tny_iterator_is_done (iter)) {
2979                         TnyMimePart *mime_part = (TnyMimePart *) tny_iterator_get_current (iter);
2980
2981                         if ((modest_tny_mime_part_is_attachment_for_modest (mime_part)) &&
2982                             !tny_mime_part_is_purged (mime_part) &&
2983                             (tny_mime_part_get_filename (mime_part) != NULL)) {
2984                                 SaveMimePartPair *pair;
2985
2986                                 pair = g_slice_new0 (SaveMimePartPair);
2987
2988                                 if (tny_list_get_length (mime_parts) > 1) {
2989                                         gchar *escaped = 
2990                                                 gnome_vfs_escape_slashes (tny_mime_part_get_filename (mime_part));
2991                                         pair->filename = g_build_filename (chooser_uri, escaped, NULL);
2992                                         g_free (escaped);
2993                                 } else {
2994                                         pair->filename = g_strdup (chooser_uri);
2995                                 }
2996                                 pair->part = mime_part;
2997                                 files_to_save = g_list_prepend (files_to_save, pair);
2998                         }
2999                         tny_iterator_next (iter);
3000                 }
3001                 g_object_unref (iter);
3002         }
3003
3004         if (files_to_save != NULL) {
3005                 SaveMimePartInfo *info = g_slice_new0 (SaveMimePartInfo);
3006                 info->pairs = files_to_save;
3007                 info->result = TRUE;
3008                 info->uri = g_strdup (chooser_uri);
3009                 save_mime_parts_to_file_with_checks ((GtkWindow *) dialog, info);
3010         }
3011         g_free (chooser_uri);
3012
3013  end:
3014         /* Free and close the dialog */
3015         g_object_unref (mime_parts);
3016         gtk_widget_destroy (GTK_WIDGET (dialog));
3017 }
3018
3019 void
3020 modest_msg_view_window_save_attachments (ModestMsgViewWindow *window, 
3021                                          TnyList *mime_parts)
3022 {
3023         ModestMsgViewWindowPrivate *priv;
3024         GtkWidget *save_dialog = NULL;
3025         gchar *conf_folder = NULL;
3026         gchar *filename = NULL;
3027         gchar *save_multiple_str = NULL;
3028         const gchar *root_folder = "file:///";
3029
3030         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
3031         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
3032
3033         if (mime_parts == NULL) {
3034                 /* In Hildon 2.2 save and delete operate over all the attachments as there's no
3035                  * selection available */
3036                 mime_parts = modest_msg_view_get_attachments (MODEST_MSG_VIEW (priv->msg_view));
3037                 if (mime_parts && !modest_maemo_utils_select_attachments (GTK_WINDOW (window), mime_parts, FALSE)) {
3038                         g_object_unref (mime_parts);
3039                         return;
3040                 }
3041                 if (mime_parts == NULL || tny_list_get_length (mime_parts) == 0) {
3042                         if (mime_parts) {
3043                                 g_object_unref (mime_parts);
3044                                 mime_parts = NULL;
3045                         }
3046                         return;
3047                 }
3048         } else {
3049                 g_object_ref (mime_parts);
3050         }
3051
3052         /* prepare dialog */
3053         if (tny_list_get_length (mime_parts) == 1) {
3054                 TnyIterator *iter;
3055                 /* only one attachment selected */
3056                 iter = tny_list_create_iterator (mime_parts);
3057                 TnyMimePart *mime_part = (TnyMimePart *) tny_iterator_get_current (iter);
3058                 g_object_unref (iter);
3059                 if (!modest_tny_mime_part_is_msg (mime_part) && 
3060                     modest_tny_mime_part_is_attachment_for_modest (mime_part) &&
3061                     !tny_mime_part_is_purged (mime_part)) {
3062                         filename = g_strdup (tny_mime_part_get_filename (mime_part));
3063                 } else {
3064                         /* TODO: show any error? */
3065                         g_warning ("%s: Tried to save a non-file attachment", __FUNCTION__);
3066                         g_object_unref (mime_parts);
3067                         return;
3068                 }
3069                 g_object_unref (mime_part);
3070         } else {
3071                 gint num = tny_list_get_length (mime_parts);
3072                 save_multiple_str = g_strdup_printf (dngettext("hildon-fm",
3073                                                                "sfil_va_number_of_objects_attachment",
3074                                                               "sfil_va_number_of_objects_attachments",
3075                                                               num), num);
3076         }
3077
3078         save_dialog = hildon_file_chooser_dialog_new (GTK_WINDOW (window), 
3079                                                       GTK_FILE_CHOOSER_ACTION_SAVE);
3080
3081         /* Get last used folder */
3082         conf_folder = modest_conf_get_string (modest_runtime_get_conf (),
3083                                               MODEST_CONF_LATEST_SAVE_ATTACHMENT_PATH, NULL);
3084
3085         /* File chooser stops working if we select "file:///" as current folder */
3086         if (conf_folder && g_ascii_strcasecmp (root_folder, conf_folder) != 0) {
3087                 g_free (conf_folder);
3088                 conf_folder = NULL;
3089         }
3090
3091         if (conf_folder && conf_folder[0] != '\0') {
3092                 gtk_file_chooser_set_current_folder_uri (GTK_FILE_CHOOSER (save_dialog), conf_folder);
3093         } else {
3094                 gchar *docs_folder;
3095                 /* Set the default folder to documents folder */
3096                 docs_folder = (gchar *) g_strdup(g_get_user_special_dir (G_USER_DIRECTORY_DOCUMENTS));
3097                 if (!docs_folder) {
3098                         /* fallback */
3099                         docs_folder = g_build_filename (g_getenv (MYDOCS_ENV), DOCS_FOLDER, NULL);
3100                 }
3101                 gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (save_dialog), docs_folder);
3102                 g_free (docs_folder);
3103         }
3104         g_free (conf_folder);
3105
3106         /* set filename */
3107         if (filename) {
3108                 gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (save_dialog), 
3109                                                    filename);
3110                 g_free (filename);
3111         }
3112
3113         /* if multiple, set multiple string */
3114         if (save_multiple_str) {
3115                 g_object_set (G_OBJECT (save_dialog), "save-multiple", save_multiple_str, NULL);
3116                 gtk_window_set_title (GTK_WINDOW (save_dialog), _FM("sfil_ti_save_objects_files"));
3117                 g_free (save_multiple_str);
3118         }
3119
3120         /* We must run this asynchronously, because the hildon dialog
3121            performs a gtk_dialog_run by itself which leads to gdk
3122            deadlocks */
3123         g_signal_connect (save_dialog, "response", 
3124                           G_CALLBACK (save_attachments_response), mime_parts);
3125
3126         gtk_widget_show_all (save_dialog);
3127 }
3128
3129 static gboolean
3130 show_remove_attachment_information (gpointer userdata)
3131 {
3132         ModestMsgViewWindow *window = (ModestMsgViewWindow *) userdata;
3133         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
3134
3135         /* We're outside the main lock */
3136         gdk_threads_enter ();
3137
3138         if (priv->remove_attachment_banner != NULL) {
3139                 gtk_widget_destroy (priv->remove_attachment_banner);
3140                 g_object_unref (priv->remove_attachment_banner);
3141         }
3142
3143         priv->remove_attachment_banner = g_object_ref (
3144                 hildon_banner_show_animation (NULL, NULL, _("mcen_me_inbox_remove_attachments")));
3145
3146         gdk_threads_leave ();
3147
3148         return FALSE;
3149 }
3150
3151 void
3152 modest_msg_view_window_remove_attachments (ModestMsgViewWindow *window, gboolean get_all)
3153 {
3154         ModestMsgViewWindowPrivate *priv;
3155         TnyList *mime_parts = NULL, *tmp;
3156         gchar *confirmation_message;
3157         gint response;
3158         gint n_attachments;
3159         TnyMsg *msg;
3160         TnyIterator *iter;
3161
3162         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
3163         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
3164
3165         /* In hildon 2.2 we ignore the get_all flag as we always get all attachments. This is
3166          * because we don't have selection
3167          */
3168         mime_parts = modest_msg_view_get_attachments (MODEST_MSG_VIEW (priv->msg_view));
3169
3170         /* Remove already purged messages from mime parts list. We use
3171            a copy of the list to remove items in the original one */
3172         tmp = tny_list_copy (mime_parts);
3173         iter = tny_list_create_iterator (tmp);
3174         while (!tny_iterator_is_done (iter)) {
3175                 TnyMimePart *part = TNY_MIME_PART (tny_iterator_get_current (iter));
3176                 if (tny_mime_part_is_purged (part))
3177                         tny_list_remove (mime_parts, (GObject *) part);
3178
3179                 g_object_unref (part);
3180                 tny_iterator_next (iter);
3181         }
3182         g_object_unref (tmp);
3183         g_object_unref (iter);
3184
3185         if (!modest_maemo_utils_select_attachments (GTK_WINDOW (window), mime_parts, TRUE) ||
3186             tny_list_get_length (mime_parts) == 0) {
3187                 g_object_unref (mime_parts);
3188                 return;
3189         }
3190
3191         n_attachments = tny_list_get_length (mime_parts);
3192         if (n_attachments == 1) {
3193                 gchar *filename;
3194                 TnyMimePart *part;
3195
3196                 iter = tny_list_create_iterator (mime_parts);
3197                 part = (TnyMimePart *) tny_iterator_get_current (iter);
3198                 g_object_unref (iter);
3199                 if (modest_tny_mime_part_is_msg (part)) {
3200                         TnyHeader *header;
3201                         header = tny_msg_get_header (TNY_MSG (part));
3202                         filename = tny_header_dup_subject (header);
3203                         g_object_unref (header);
3204                         if (filename == NULL)
3205                                 filename = g_strdup (_("mail_va_no_subject"));
3206                 } else {
3207                         filename = g_strdup (tny_mime_part_get_filename (TNY_MIME_PART (part)));
3208                 }
3209                 confirmation_message = g_strdup_printf (_("mcen_nc_purge_file_text"), filename);
3210                 g_free (filename);
3211                 g_object_unref (part);
3212         } else {
3213                 confirmation_message = g_strdup_printf (ngettext("mcen_nc_purge_file_text", 
3214                                                                  "mcen_nc_purge_files_text", 
3215                                                                  n_attachments), n_attachments);
3216         }
3217         response = modest_platform_run_confirmation_dialog (GTK_WINDOW (window),
3218                                                             confirmation_message);
3219         g_free (confirmation_message);
3220
3221         if (response != GTK_RESPONSE_OK) {
3222                 g_object_unref (mime_parts);
3223                 return;
3224         }
3225
3226         priv->purge_timeout = g_timeout_add (2000, show_remove_attachment_information, window);
3227
3228         iter = tny_list_create_iterator (mime_parts);
3229         while (!tny_iterator_is_done (iter)) {
3230                 TnyMimePart *part;
3231
3232                 part = (TnyMimePart *) tny_iterator_get_current (iter);
3233                 tny_mime_part_set_purged (TNY_MIME_PART (part));
3234                 g_object_unref (part);
3235                 tny_iterator_next (iter);
3236         }
3237         g_object_unref (iter);
3238
3239         msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
3240         tny_msg_view_clear (TNY_MSG_VIEW (priv->msg_view));
3241         tny_msg_rewrite_cache (msg);
3242         tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
3243         g_object_unref (msg);
3244         update_branding (MODEST_MSG_VIEW_WINDOW (window));
3245
3246         g_object_unref (mime_parts);
3247
3248         if (priv->purge_timeout > 0) {
3249                 g_source_remove (priv->purge_timeout);
3250                 priv->purge_timeout = 0;
3251         }
3252
3253         if (priv->remove_attachment_banner) {
3254                 gtk_widget_destroy (priv->remove_attachment_banner);
3255                 g_object_unref (priv->remove_attachment_banner);
3256                 priv->remove_attachment_banner = NULL;
3257         }
3258 }
3259
3260
3261 static void
3262 update_window_title (ModestMsgViewWindow *window)
3263 {
3264         ModestMsgViewWindowPrivate *priv;
3265         TnyMsg *msg = NULL;
3266         TnyHeader *header = NULL;
3267         gchar *subject = NULL;
3268
3269         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
3270
3271         /* Note that if the window is closed while we're retrieving
3272            the message, this widget could de deleted */
3273         if (!priv->msg_view)
3274                 return;
3275
3276         msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
3277
3278         if (priv->other_body) {
3279                 gchar *description;
3280
3281                 description = modest_tny_mime_part_get_header_value (priv->other_body, "Content-Description");
3282                 if (description) {
3283                         g_strstrip (description);
3284                         subject = description;
3285                 }
3286         } else if (msg != NULL) {
3287                 header = tny_msg_get_header (msg);
3288                 subject = tny_header_dup_subject (header);
3289                 g_object_unref (header);
3290                 g_object_unref (msg);
3291         }
3292
3293         if ((subject == NULL)||(subject[0] == '\0')) {
3294                 g_free (subject);
3295                 subject = g_strdup (_("mail_va_no_subject"));
3296         }
3297
3298         gtk_window_set_title (GTK_WINDOW (window), subject);
3299 }
3300
3301
3302 static void
3303 on_move_focus (GtkWidget *widget,
3304                GtkDirectionType direction,
3305                gpointer userdata)
3306 {
3307         g_signal_stop_emission_by_name (G_OBJECT (widget), "move-focus");
3308 }
3309
3310 static TnyStream *
3311 fetch_image_open_stream (TnyStreamCache *self, gint64 *expected_size, gchar *uri)
3312 {
3313         GnomeVFSResult result;
3314         GnomeVFSHandle *handle = NULL;
3315         GnomeVFSFileInfo *info = NULL;
3316         TnyStream *stream;
3317
3318         result = gnome_vfs_open (&handle, uri, GNOME_VFS_OPEN_READ);
3319         if (result != GNOME_VFS_OK) {
3320                 *expected_size = 0;
3321                 return NULL;
3322         }
3323         
3324         info = gnome_vfs_file_info_new ();
3325         result = gnome_vfs_get_file_info_from_handle (handle, info, GNOME_VFS_FILE_INFO_DEFAULT);
3326         if (result != GNOME_VFS_OK || ! (info->valid_fields & GNOME_VFS_FILE_INFO_FIELDS_SIZE)) {
3327                 /* We put a "safe" default size for going to cache */
3328                 *expected_size = (300*1024);
3329         } else {
3330                 *expected_size = info->size;
3331         }
3332         gnome_vfs_file_info_unref (info);
3333
3334         stream = tny_vfs_stream_new (handle);
3335
3336         return stream;
3337
3338 }
3339
3340 typedef struct {
3341         gchar *uri;
3342         gchar *cache_id;
3343         TnyStream *output_stream;
3344         GtkWidget *msg_view;
3345         GtkWidget *window;
3346 } FetchImageData;
3347
3348 gboolean
3349 on_fetch_image_idle_refresh_view (gpointer userdata)
3350 {
3351
3352         FetchImageData *fidata = (FetchImageData *) userdata;
3353
3354         gdk_threads_enter ();
3355         if (GTK_WIDGET_DRAWABLE (fidata->msg_view)) {
3356                 ModestMsgViewWindowPrivate *priv;
3357
3358                 priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (fidata->window);
3359                 priv->fetching_images--;
3360                 gtk_widget_queue_draw (fidata->msg_view);
3361                 update_progress_hint (MODEST_MSG_VIEW_WINDOW (fidata->window));
3362         }
3363         gdk_threads_leave ();
3364
3365         g_object_unref (fidata->msg_view);
3366         g_object_unref (fidata->window);
3367         g_slice_free (FetchImageData, fidata);
3368         return FALSE;
3369 }
3370
3371 static gpointer
3372 on_fetch_image_thread (gpointer userdata)
3373 {
3374         FetchImageData *fidata = (FetchImageData *) userdata;
3375         TnyStreamCache *cache;
3376         TnyStream *cache_stream;
3377
3378         cache = modest_runtime_get_images_cache ();
3379         cache_stream = 
3380                 tny_stream_cache_get_stream (cache, 
3381                                              fidata->cache_id, 
3382                                              (TnyStreamCacheOpenStreamFetcher) fetch_image_open_stream, 
3383                                              (gpointer) fidata->uri);
3384         g_free (fidata->cache_id);
3385         g_free (fidata->uri);
3386
3387         if (cache_stream != NULL) {
3388                 char buffer[4096];
3389
3390                 while (G_LIKELY (!tny_stream_is_eos (cache_stream))) {
3391                         gssize nb_read;
3392
3393                         nb_read = tny_stream_read (cache_stream, buffer, sizeof (buffer));
3394                         if (G_UNLIKELY (nb_read < 0)) {
3395                                 break;
3396                         } else if (G_LIKELY (nb_read > 0)) {
3397                                 gssize nb_written = 0;
3398
3399                                 while (G_UNLIKELY (nb_written < nb_read)) {
3400                                         gssize len;
3401
3402                                         len = tny_stream_write (fidata->output_stream, buffer + nb_written,
3403                                                                 nb_read - nb_written);
3404                                         if (G_UNLIKELY (len < 0))
3405                                                 break;
3406                                         nb_written += len;
3407                                 }
3408                         }
3409                 }
3410                 tny_stream_close (cache_stream);
3411                 g_object_unref (cache_stream);
3412         }
3413
3414         tny_stream_close (fidata->output_stream);
3415         g_object_unref (fidata->output_stream);
3416
3417         g_idle_add (on_fetch_image_idle_refresh_view, fidata);
3418
3419         return NULL;
3420 }
3421
3422 static gboolean
3423 on_fetch_image (ModestMsgView *msgview,
3424                 const gchar *uri,
3425                 TnyStream *stream,
3426                 ModestMsgViewWindow *window)
3427 {
3428         const gchar *current_account;
3429         ModestMsgViewWindowPrivate *priv;
3430         FetchImageData *fidata;
3431
3432         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
3433
3434         current_account = modest_window_get_active_account (MODEST_WINDOW (window));
3435
3436         fidata = g_slice_new0 (FetchImageData);
3437         fidata->msg_view = g_object_ref (msgview);
3438         fidata->window = g_object_ref (window);
3439         fidata->uri = g_strdup (uri);
3440         fidata->cache_id = modest_images_cache_get_id (current_account, uri);
3441         fidata->output_stream = g_object_ref (stream);
3442
3443         priv->fetching_images++;
3444         if (g_thread_create (on_fetch_image_thread, fidata, FALSE, NULL) == NULL) {
3445                 g_object_unref (fidata->output_stream);
3446                 g_free (fidata->cache_id);
3447                 g_free (fidata->uri);
3448                 g_object_unref (fidata->msg_view);
3449                 g_slice_free (FetchImageData, fidata);
3450                 tny_stream_close (stream);
3451                 priv->fetching_images--;
3452                 update_progress_hint (window);
3453                 return FALSE;
3454         }
3455         update_progress_hint (window);
3456
3457         return TRUE;
3458 }
3459
3460 static void 
3461 setup_menu (ModestMsgViewWindow *self)
3462 {
3463         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW(self));
3464
3465         /* Settings menu buttons */
3466         modest_hildon2_window_add_to_menu (MODEST_HILDON2_WINDOW (self), _("mcen_me_viewer_find"), NULL,
3467                                            APP_MENU_CALLBACK (modest_msg_view_window_show_find_toolbar),
3468                                            MODEST_DIMMING_CALLBACK (modest_ui_dimming_rules_on_find_in_msg));
3469
3470         modest_hildon2_window_add_to_menu (MODEST_HILDON2_WINDOW (self),
3471                                            dngettext(GETTEXT_PACKAGE,
3472                                                      "mcen_me_move_message",
3473                                                      "mcen_me_move_messages",
3474                                                      1),
3475                                            NULL,
3476                                            APP_MENU_CALLBACK (modest_ui_actions_on_move_to),
3477                                            MODEST_DIMMING_CALLBACK (modest_ui_dimming_rules_on_move_to));
3478
3479         modest_hildon2_window_add_to_menu (MODEST_HILDON2_WINDOW (self), _("mcen_me_inbox_forward"), "<Control>d",
3480                                            APP_MENU_CALLBACK (modest_ui_actions_on_forward),
3481                                            MODEST_DIMMING_CALLBACK (modest_ui_dimming_rules_on_reply_msg));
3482
3483         modest_hildon2_window_add_to_menu (MODEST_HILDON2_WINDOW (self), _("mcen_me_inbox_mark_as_read"), NULL,
3484                                            APP_MENU_CALLBACK (modest_ui_actions_on_mark_as_read),
3485                                            MODEST_DIMMING_CALLBACK (modest_ui_dimming_rules_on_mark_as_read_msg_in_view));
3486
3487         modest_hildon2_window_add_to_menu (MODEST_HILDON2_WINDOW (self), _("mcen_me_inbox_mark_as_unread"), NULL,
3488                                            APP_MENU_CALLBACK (modest_ui_actions_on_mark_as_unread),
3489                                            MODEST_DIMMING_CALLBACK (modest_ui_dimming_rules_on_mark_as_unread_msg_in_view));
3490
3491         modest_hildon2_window_add_to_menu (MODEST_HILDON2_WINDOW (self), _("mcen_me_viewer_save_attachments"), NULL,
3492                                            APP_MENU_CALLBACK (modest_ui_actions_save_attachments),
3493                                            MODEST_DIMMING_CALLBACK (modest_ui_dimming_rules_on_save_attachments));
3494         modest_hildon2_window_add_to_menu (MODEST_HILDON2_WINDOW (self), _("mcen_me_inbox_remove_attachments"), NULL,
3495                                            APP_MENU_CALLBACK (modest_ui_actions_remove_attachments),
3496                                            MODEST_DIMMING_CALLBACK (modest_ui_dimming_rules_on_remove_attachments));
3497
3498         modest_hildon2_window_add_to_menu (MODEST_HILDON2_WINDOW (self), _("mcen_me_new_message"), "<Control>n",
3499                                            APP_MENU_CALLBACK (modest_ui_actions_on_new_msg),
3500                                            MODEST_DIMMING_CALLBACK (modest_ui_dimming_rules_on_new_msg));
3501         modest_hildon2_window_add_to_menu (MODEST_HILDON2_WINDOW (self), _("mcen_me_viewer_addtocontacts"), NULL,
3502                                            APP_MENU_CALLBACK (modest_ui_actions_add_to_contacts),
3503                                            MODEST_DIMMING_CALLBACK (modest_ui_dimming_rules_on_add_to_contacts));
3504
3505         modest_hildon2_window_add_to_menu (MODEST_HILDON2_WINDOW (self), _("mcen_ti_message_properties"), NULL,
3506                                            APP_MENU_CALLBACK (modest_ui_actions_on_details),
3507                                            MODEST_DIMMING_CALLBACK (modest_ui_dimming_rules_on_details));
3508 }
3509
3510 void
3511 modest_msg_view_window_add_to_contacts (ModestMsgViewWindow *self)
3512 {
3513         ModestMsgViewWindowPrivate *priv;
3514         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
3515         GSList *recipients = NULL;
3516         TnyMsg *msg = NULL;
3517         gboolean contacts_to_add = FALSE;
3518
3519         msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
3520         if (msg == NULL) {
3521                 TnyHeader *header;
3522
3523                 header = modest_msg_view_window_get_header (self);
3524                 if (header == NULL)
3525                         return;
3526                 recipients = modest_tny_msg_header_get_all_recipients_list (header);
3527                 g_object_unref (header);
3528         } else {
3529                 recipients = modest_tny_msg_get_all_recipients_list (msg);
3530                 g_object_unref (msg);
3531         }
3532
3533         if (recipients != NULL) {
3534                 GtkWidget *picker_dialog;
3535                 GtkWidget *selector;
3536                 GSList *node;
3537                 gchar *selected = NULL;
3538
3539                 selector = hildon_touch_selector_new_text ();
3540                 g_object_ref (selector);
3541
3542                 for (node = recipients; node != NULL; node = g_slist_next (node)) {
3543                         if (!modest_address_book_has_address ((const gchar *) node->data)) {
3544                                 hildon_touch_selector_append_text (HILDON_TOUCH_SELECTOR (selector), 
3545                                                                    (const gchar *) node->data);
3546                                 contacts_to_add = TRUE;
3547                         }
3548                 }
3549
3550                 if (contacts_to_add) {
3551                         gint picker_result;
3552
3553                         picker_dialog = hildon_picker_dialog_new (GTK_WINDOW (self));
3554                         gtk_window_set_title (GTK_WINDOW (picker_dialog), _("mcen_me_viewer_addtocontacts"));
3555
3556                         hildon_picker_dialog_set_selector (HILDON_PICKER_DIALOG (picker_dialog), 
3557                                                            HILDON_TOUCH_SELECTOR (selector));
3558                         
3559                         picker_result = gtk_dialog_run (GTK_DIALOG (picker_dialog));
3560
3561                         if (picker_result == GTK_RESPONSE_OK) {
3562                                 selected = hildon_touch_selector_get_current_text (HILDON_TOUCH_SELECTOR (selector));
3563                         }
3564                         gtk_widget_destroy (picker_dialog);
3565
3566                         if (selected)
3567                                 modest_address_book_add_address (selected, (GtkWindow *) self);
3568                         g_free (selected);
3569
3570                 } else {
3571
3572                         g_object_unref (selector);
3573
3574                 }
3575         }
3576         
3577         if (recipients) {g_slist_foreach (recipients, (GFunc) g_free, NULL); g_slist_free (recipients);}
3578 }
3579
3580 static gboolean 
3581 _modest_msg_view_window_map_event (GtkWidget *widget,
3582                                    GdkEvent *event,
3583                                    gpointer userdata)
3584 {
3585         ModestMsgViewWindow *self = (ModestMsgViewWindow *) userdata;
3586
3587         update_progress_hint (self);
3588
3589         return FALSE;
3590 }
3591
3592 void
3593 modest_msg_view_window_fetch_images (ModestMsgViewWindow *self)
3594 {
3595         ModestMsgViewWindowPrivate *priv;
3596         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
3597
3598         modest_msg_view_request_fetch_images (MODEST_MSG_VIEW (priv->msg_view));
3599 }
3600
3601 gboolean 
3602 modest_msg_view_window_has_blocked_external_images (ModestMsgViewWindow *self)
3603 {
3604         ModestMsgViewWindowPrivate *priv;
3605         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
3606
3607         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), FALSE);
3608
3609         return modest_msg_view_has_blocked_external_images (MODEST_MSG_VIEW (priv->msg_view));
3610 }
3611
3612 void 
3613 modest_msg_view_window_reload (ModestMsgViewWindow *self)
3614 {
3615         ModestMsgViewWindowPrivate *priv;
3616         const gchar *msg_uid;
3617         TnyHeader *header = NULL;
3618         TnyFolder *folder = NULL;
3619
3620         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self));
3621
3622         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
3623
3624         header = modest_msg_view_window_get_header (MODEST_MSG_VIEW_WINDOW (self));
3625         if (!header)
3626                 return;
3627
3628         folder = tny_header_get_folder (header);
3629         g_object_unref (header);
3630
3631         if (!folder)
3632                 return;
3633
3634         msg_uid = modest_msg_view_window_get_message_uid (self);
3635         if (msg_uid)
3636                 if (!message_reader (self, priv, NULL, msg_uid, folder, priv->row_reference))
3637                         g_warning ("Shouldn't happen, trying to reload a message failed");
3638
3639         g_object_unref (folder);
3640 }
3641
3642 static void
3643 update_branding (ModestMsgViewWindow *self)
3644 {
3645         const gchar *account; 
3646         const gchar *mailbox;
3647         ModestAccountMgr *mgr;
3648         ModestProtocol *protocol = NULL;
3649         gchar *service_name = NULL;
3650         const GdkPixbuf *service_icon = NULL;
3651         ModestMsgViewWindowPrivate *priv;
3652
3653         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
3654
3655         account = modest_window_get_active_account (MODEST_WINDOW (self));
3656         mailbox = modest_window_get_active_mailbox (MODEST_WINDOW (self));
3657
3658         mgr = modest_runtime_get_account_mgr ();
3659
3660         if (modest_account_mgr_account_is_multimailbox (mgr, account, &protocol)) {
3661                 if (MODEST_IS_ACCOUNT_PROTOCOL (protocol)) {
3662                         service_name = modest_account_protocol_get_service_name (MODEST_ACCOUNT_PROTOCOL (protocol),
3663                                                                                  account, mailbox);
3664                         service_icon = modest_account_protocol_get_service_icon (MODEST_ACCOUNT_PROTOCOL (protocol),
3665                                                                                  account, mailbox, MODEST_ICON_SIZE_SMALL);
3666                 }
3667         }
3668
3669         modest_msg_view_set_branding (MODEST_MSG_VIEW (priv->msg_view), service_name, service_icon);
3670         g_free (service_name);
3671 }