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