Create new method in ModestMsgView and ModestMsgViewWindow for setting a msg
[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                                         gtk_tree_path_free (next);
779                                         retval = TRUE;
780                                         finished = TRUE;
781                                 }
782                                 g_object_unref (header);
783                                 header = NULL;
784                         }
785                 } else if (cycle && gtk_tree_model_get_iter_first (model, &tmp_iter)) {
786                         next = gtk_tree_model_get_path (model, &tmp_iter);
787                         
788                         /* Ensure that we are not selecting the same */
789                         if (gtk_tree_path_compare (path, next) != 0) {
790                                 gtk_tree_model_get (model, &tmp_iter, 
791                                                     TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, 
792                                                     &header, -1);                               
793                                 if (header) {
794                                         if (msg_is_visible (header, is_outbox)) {
795                                                 *row_reference = gtk_tree_row_reference_new (model, next);
796                                                 retval = TRUE;
797                                                 finished = TRUE;
798                                         }
799                                         g_object_unref (header);
800                                         header = NULL;
801                                 }
802                         } else {
803                                 /* If we ended up in the same message
804                                    then there is no valid next
805                                    message */
806                                 finished = TRUE;
807                         }
808                         gtk_tree_path_free (next);
809                 } else {
810                         /* If there are no more messages and we don't
811                            want to start again in the first one then
812                            there is no valid next message */
813                         finished = TRUE;
814                 }
815         } while (!finished);
816
817         /* Free */
818         gtk_tree_path_free (path);
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 *mailbox, /*ignored */
938                                               const gchar *msg_uid,
939                                               GtkTreeModel *model, 
940                                               GtkTreeRowReference *row_reference)
941 {
942         ModestMsgViewWindow *window = NULL;
943         ModestMsgViewWindowPrivate *priv = NULL;
944         TnyFolder *header_folder = NULL;
945         ModestHeaderView *header_view = NULL;
946         ModestWindow *main_window = NULL;
947         ModestWindowMgr *mgr = NULL;
948
949         MODEST_DEBUG_BLOCK (
950                modest_tny_mime_part_to_string (TNY_MIME_PART (msg), 0);
951         );
952
953         mgr = modest_runtime_get_window_mgr ();
954         window = MODEST_MSG_VIEW_WINDOW (modest_window_mgr_get_msg_view_window (mgr));
955         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), NULL);
956
957         modest_msg_view_window_construct (window, modest_account_name, msg_uid);
958
959         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
960
961         /* Remember the message list's TreeModel so we can detect changes 
962          * and change the list selection when necessary: */
963
964         main_window = modest_window_mgr_get_main_window(mgr, FALSE); /* don't create */
965         if (main_window) {
966                 header_view = MODEST_HEADER_VIEW(modest_main_window_get_child_widget(
967                                                          MODEST_MAIN_WINDOW(main_window),
968                                                          MODEST_MAIN_WINDOW_WIDGET_TYPE_HEADER_VIEW));
969         }
970         
971         if (header_view != NULL){
972                 header_folder = modest_header_view_get_folder(header_view);
973                 /* This could happen if the header folder was
974                    unseleted before opening this msg window (for
975                    example if the user selects an account in the
976                    folder view of the main window */
977                 if (header_folder) {
978                         priv->is_outbox = (modest_tny_folder_guess_folder_type (header_folder) == TNY_FOLDER_TYPE_OUTBOX);
979                         priv->header_folder_id = tny_folder_get_id(header_folder);
980                         g_assert(priv->header_folder_id != NULL);
981                         g_object_unref(header_folder);
982                 }
983         }
984
985         /* Setup row references and connect signals */
986         priv->header_model = g_object_ref (model);
987
988         if (row_reference) {
989                 priv->row_reference = gtk_tree_row_reference_copy (row_reference);
990                 priv->next_row_reference = gtk_tree_row_reference_copy (row_reference);
991                 select_next_valid_row (model, &(priv->next_row_reference), TRUE, priv->is_outbox);
992         } else {
993                 priv->row_reference = NULL;
994                 priv->next_row_reference = NULL;
995         }
996
997         /* Connect signals */
998         priv->row_changed_handler = 
999                 g_signal_connect (GTK_TREE_MODEL(model), "row-changed",
1000                                   G_CALLBACK(modest_msg_view_window_on_row_changed),
1001                                   window);
1002         priv->row_deleted_handler = 
1003                 g_signal_connect (GTK_TREE_MODEL(model), "row-deleted",
1004                                   G_CALLBACK(modest_msg_view_window_on_row_deleted),
1005                                   window);
1006         priv->row_inserted_handler = 
1007                 g_signal_connect (GTK_TREE_MODEL(model), "row-inserted",
1008                                   G_CALLBACK(modest_msg_view_window_on_row_inserted),
1009                                   window);
1010         priv->rows_reordered_handler = 
1011                 g_signal_connect(GTK_TREE_MODEL(model), "rows-reordered",
1012                                  G_CALLBACK(modest_msg_view_window_on_row_reordered),
1013                                  window);
1014
1015         if (header_view != NULL){
1016                 modest_header_view_add_observer(header_view,
1017                                 MODEST_HEADER_VIEW_OBSERVER(window));
1018         }
1019
1020         tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
1021         update_window_title (MODEST_MSG_VIEW_WINDOW (window));
1022         gtk_widget_show_all (GTK_WIDGET (window));
1023         modest_msg_view_window_update_priority (window);
1024
1025         /* Check dimming rules */
1026         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (window));
1027         modest_ui_actions_check_menu_dimming_rules (MODEST_WINDOW (window));
1028         modest_window_check_dimming_rules_group (MODEST_WINDOW (window), MODEST_DIMMING_RULES_CLIPBOARD);
1029
1030         return MODEST_WINDOW(window);
1031 }
1032
1033 ModestWindow *
1034 modest_msg_view_window_new_for_search_result (TnyMsg *msg, 
1035                                               const gchar *modest_account_name,
1036                                               const gchar *mailbox, /*ignored*/
1037                                               const gchar *msg_uid)
1038 {
1039         ModestMsgViewWindow *window = NULL;
1040         ModestMsgViewWindowPrivate *priv = NULL;
1041         ModestWindowMgr *mgr = NULL;
1042
1043         mgr = modest_runtime_get_window_mgr ();
1044         window = MODEST_MSG_VIEW_WINDOW (modest_window_mgr_get_msg_view_window (mgr));
1045         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), NULL);
1046         modest_msg_view_window_construct (window, modest_account_name, msg_uid);
1047
1048         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1049
1050         /* Remember that this is a search result, 
1051          * so we can disable some UI appropriately: */
1052         priv->is_search_result = TRUE;
1053
1054         tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
1055         
1056         update_window_title (window);
1057         gtk_widget_show_all (GTK_WIDGET (window));
1058         modest_msg_view_window_update_priority (window);
1059
1060         /* Check dimming rules */
1061         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (window));
1062         modest_ui_actions_check_menu_dimming_rules (MODEST_WINDOW (window));
1063         modest_window_check_dimming_rules_group (MODEST_WINDOW (window), MODEST_DIMMING_RULES_CLIPBOARD);
1064
1065         return MODEST_WINDOW(window);
1066 }
1067
1068 ModestWindow *
1069 modest_msg_view_window_new_with_other_body (TnyMsg *msg, 
1070                                             TnyMimePart *part,
1071                                             const gchar *modest_account_name,
1072                                             const gchar *mailbox, /* ignored */
1073                                             const gchar *msg_uid)
1074 {
1075         GObject *obj = NULL;
1076         ModestMsgViewWindowPrivate *priv;       
1077         ModestWindowMgr *mgr = NULL;
1078
1079         g_return_val_if_fail (msg, NULL);
1080         mgr = modest_runtime_get_window_mgr ();
1081         obj = G_OBJECT (modest_window_mgr_get_msg_view_window (mgr));
1082         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (obj);
1083         modest_msg_view_window_construct (MODEST_MSG_VIEW_WINDOW (obj), 
1084                 modest_account_name, msg_uid);
1085
1086         if (other_body) {
1087                 modest_msg_view_set_msg_with_other_body (TNY_MSG_VIEW (priv->msg_view), msg, other_body);
1088         } else {
1089                 tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
1090         }
1091         update_window_title (MODEST_MSG_VIEW_WINDOW (obj));
1092
1093         gtk_widget_show_all (GTK_WIDGET (obj));
1094
1095         /* Check dimming rules */
1096         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (obj));
1097         modest_ui_actions_check_menu_dimming_rules (MODEST_WINDOW (obj));
1098         modest_window_check_dimming_rules_group (MODEST_WINDOW (obj), MODEST_DIMMING_RULES_CLIPBOARD);
1099
1100         return MODEST_WINDOW(obj);
1101 }
1102
1103 ModestWindow *
1104 modest_msg_view_window_new_for_attachment (TnyMsg *msg, 
1105                                            const gchar *modest_account_name,
1106                                            const gchar *mailbox, /* ignored */
1107                                            const gchar *msg_uid)
1108 {
1109         return modest_msg_view_window_new_with_other_body (msg, NULL, modest_account_name, mailbox, msg_uid);
1110
1111 }
1112
1113 static void
1114 modest_msg_view_window_on_row_changed (GtkTreeModel *header_model,
1115                                        GtkTreePath *arg1,
1116                                        GtkTreeIter *arg2,
1117                                        ModestMsgViewWindow *window)
1118 {
1119         check_dimming_rules_after_change (window);
1120 }
1121
1122 static void 
1123 modest_msg_view_window_on_row_deleted(GtkTreeModel *header_model,
1124                                       GtkTreePath *arg1,
1125                                       ModestMsgViewWindow *window)
1126 {
1127         check_dimming_rules_after_change (window);
1128 }
1129         /* The window could have dissapeared */
1130
1131 static void
1132 check_dimming_rules_after_change (ModestMsgViewWindow *window)
1133 {
1134         modest_ui_actions_check_menu_dimming_rules (MODEST_WINDOW (window));
1135         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (window));
1136 }
1137
1138
1139 /* On insertions we check if the folder still has the message we are
1140  * showing or do not. If do not, we do nothing. Which means we are still
1141  * not attached to any header folder and thus next/prev buttons are
1142  * still dimmed. Once the message that is shown by msg-view is found, the
1143  * new model of header-view will be attached and the references will be set.
1144  * On each further insertions dimming rules will be checked. However
1145  * this requires extra CPU time at least works.
1146  * (An message might be deleted from TnyFolder and thus will not be
1147  * inserted into the model again for example if it is removed by the
1148  * imap server and the header view is refreshed.)
1149  */
1150 static void 
1151 modest_msg_view_window_on_row_inserted (GtkTreeModel *model,
1152                                         GtkTreePath *tree_path,
1153                                         GtkTreeIter *tree_iter,
1154                                         ModestMsgViewWindow *window)
1155 {
1156         ModestMsgViewWindowPrivate *priv = NULL; 
1157         TnyHeader *header = NULL;
1158
1159         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
1160         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1161
1162         g_assert (model == priv->header_model);
1163         
1164         /* Check if the newly inserted message is the same we are actually
1165          * showing. IF not, we should remain detached from the header model
1166          * and thus prev and next toolbar buttons should remain dimmed. */
1167         gtk_tree_model_get (model, tree_iter, 
1168                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, 
1169                             &header, -1);
1170
1171         if (TNY_IS_HEADER (header)) {
1172                 gchar *uid = NULL;
1173
1174                 uid = modest_tny_folder_get_header_unique_id (header);
1175                 if (!g_str_equal(priv->msg_uid, uid)) {
1176                         check_dimming_rules_after_change (window);
1177                         g_free(uid);
1178                         g_object_unref (G_OBJECT(header));
1179                         return;
1180                 }
1181                 g_free(uid);
1182                 g_object_unref(G_OBJECT(header));
1183         }
1184
1185         if (priv->row_reference) {
1186                 gtk_tree_row_reference_free (priv->row_reference); 
1187         }
1188
1189         /* Setup row_reference for the actual msg. */
1190         priv->row_reference = gtk_tree_row_reference_new (priv->header_model, tree_path);
1191         if (priv->row_reference == NULL) {
1192                 g_warning("No reference for msg header item.");
1193                 return;
1194         }
1195
1196         /* Now set up next_row_reference. */
1197         if (priv->next_row_reference) {
1198                 gtk_tree_row_reference_free (priv->next_row_reference); 
1199         }
1200
1201         priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
1202         select_next_valid_row (priv->header_model,
1203                                &(priv->next_row_reference), FALSE, priv->is_outbox);
1204
1205         /* Connect the remaining callbacks to become able to detect
1206          * changes in header-view. */
1207         priv->row_changed_handler = 
1208                 g_signal_connect (priv->header_model, "row-changed",
1209                                   G_CALLBACK (modest_msg_view_window_on_row_changed),
1210                                   window);
1211         priv->row_deleted_handler = 
1212                 g_signal_connect (priv->header_model, "row-deleted",
1213                                   G_CALLBACK (modest_msg_view_window_on_row_deleted),
1214                                   window);
1215         priv->rows_reordered_handler = 
1216                 g_signal_connect (priv->header_model, "rows-reordered",
1217                                   G_CALLBACK (modest_msg_view_window_on_row_reordered),
1218                                   window);
1219
1220         check_dimming_rules_after_change (window);      
1221 }
1222
1223 static void 
1224 modest_msg_view_window_on_row_reordered (GtkTreeModel *header_model,
1225                                          GtkTreePath *arg1,
1226                                          GtkTreeIter *arg2,
1227                                          gpointer arg3,
1228                                          ModestMsgViewWindow *window)
1229 {
1230         ModestMsgViewWindowPrivate *priv = NULL; 
1231         gboolean already_changed = FALSE;
1232
1233         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(window);
1234
1235         /* If the current row was reordered select the proper next
1236            valid row. The same if the next row reference changes */
1237         if (priv->row_reference && 
1238             gtk_tree_row_reference_valid (priv->row_reference)) {
1239                 GtkTreePath *path;
1240                 path = gtk_tree_row_reference_get_path (priv->row_reference);
1241                 if (gtk_tree_path_compare (path, arg1) == 0) {
1242                         if (priv->next_row_reference) {
1243                                 gtk_tree_row_reference_free (priv->next_row_reference);
1244                         }
1245                         priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
1246                         select_next_valid_row (header_model, &(priv->next_row_reference), FALSE, priv->is_outbox);
1247                         already_changed = TRUE;
1248                 }
1249                 gtk_tree_path_free (path);
1250         }
1251         if (!already_changed &&
1252             priv->next_row_reference &&
1253             gtk_tree_row_reference_valid (priv->next_row_reference)) {
1254                 GtkTreePath *path;
1255                 path = gtk_tree_row_reference_get_path (priv->next_row_reference);
1256                 if (gtk_tree_path_compare (path, arg1) == 0) {
1257                         if (priv->next_row_reference) {
1258                                 gtk_tree_row_reference_free (priv->next_row_reference);
1259                         }
1260                         priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
1261                         select_next_valid_row (header_model, &(priv->next_row_reference), FALSE, priv->is_outbox);
1262                 }
1263                 gtk_tree_path_free (path);
1264         }
1265         check_dimming_rules_after_change (window);
1266 }
1267
1268 /* The modest_msg_view_window_update_model_replaced implements update
1269  * function for ModestHeaderViewObserver. Checks whether the TnyFolder
1270  * actually belongs to the header-view is the same as the TnyFolder of
1271  * the message of msg-view or not. If they are different, there is
1272  * nothing to do. If they are the same, then the model has replaced and
1273  * the reference in msg-view shall be replaced from the old model to
1274  * the new model. In this case the view will be detached from it's
1275  * header folder. From this point the next/prev buttons are dimmed.
1276  */
1277 static void 
1278 modest_msg_view_window_update_model_replaced (ModestHeaderViewObserver *observer,
1279                                               GtkTreeModel *model,
1280                                               const gchar *tny_folder_id)
1281 {
1282         ModestMsgViewWindowPrivate *priv = NULL; 
1283         ModestMsgViewWindow *window = NULL;
1284
1285         g_assert(MODEST_IS_HEADER_VIEW_OBSERVER(observer));
1286         g_assert(MODEST_IS_MSG_VIEW_WINDOW(observer));
1287
1288         window = MODEST_MSG_VIEW_WINDOW(observer);
1289         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(window);
1290
1291         /* If there is an other folder in the header-view then we do
1292          * not care about it's model (msg list). Else if the
1293          * header-view shows the folder the msg shown by us is in, we
1294          * shall replace our model reference and make some check. */
1295         if(model == NULL || tny_folder_id == NULL || 
1296            (priv->header_folder_id && !g_str_equal(tny_folder_id, priv->header_folder_id)))
1297                 return;
1298
1299         /* Model is changed(replaced), so we should forget the old
1300          * one. Because there might be other references and there
1301          * might be some change on the model even if we unreferenced
1302          * it, we need to disconnect our signals here. */
1303         if (priv->header_model) {
1304                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
1305                                                   priv->row_changed_handler))
1306                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
1307                                                     priv->row_changed_handler);
1308                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
1309                                                   priv->row_deleted_handler))
1310                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
1311                                                     priv->row_deleted_handler);
1312                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
1313                                                   priv->row_inserted_handler))
1314                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
1315                                                     priv->row_inserted_handler);
1316                 if (g_signal_handler_is_connected(G_OBJECT (priv->header_model), 
1317                                                   priv->rows_reordered_handler))
1318                         g_signal_handler_disconnect(G_OBJECT (priv->header_model), 
1319                                                     priv->rows_reordered_handler);
1320
1321                 /* Frees */
1322                 if (priv->row_reference)
1323                         gtk_tree_row_reference_free (priv->row_reference);
1324                 if (priv->next_row_reference)
1325                         gtk_tree_row_reference_free (priv->next_row_reference);
1326                 g_object_unref(priv->header_model);
1327
1328                 /* Initialize */
1329                 priv->row_changed_handler = 0;
1330                 priv->row_deleted_handler = 0;
1331                 priv->row_inserted_handler = 0;
1332                 priv->rows_reordered_handler = 0;
1333                 priv->next_row_reference = NULL;
1334                 priv->row_reference = NULL;
1335                 priv->header_model = NULL;
1336         }
1337
1338         priv->header_model = g_object_ref (model);
1339
1340         /* Also we must connect to the new model for row insertions.
1341          * Only for insertions now. We will need other ones only after
1342          * the msg is show by msg-view is added to the new model. */
1343         priv->row_inserted_handler =
1344                 g_signal_connect (priv->header_model, "row-inserted",
1345                                   G_CALLBACK(modest_msg_view_window_on_row_inserted),
1346                                   window);
1347
1348         modest_ui_actions_check_menu_dimming_rules(MODEST_WINDOW(window));
1349         modest_ui_actions_check_toolbar_dimming_rules(MODEST_WINDOW(window));
1350 }
1351
1352 gboolean 
1353 modest_msg_view_window_toolbar_on_transfer_mode     (ModestMsgViewWindow *self)
1354 {
1355         ModestMsgViewWindowPrivate *priv= NULL; 
1356
1357         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), FALSE);
1358         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
1359
1360         return priv->current_toolbar_mode == TOOLBAR_MODE_TRANSFER;
1361 }
1362
1363 TnyHeader*
1364 modest_msg_view_window_get_header (ModestMsgViewWindow *self)
1365 {
1366         ModestMsgViewWindowPrivate *priv= NULL; 
1367         TnyMsg *msg = NULL;
1368         TnyHeader *header = NULL;
1369         GtkTreePath *path = NULL;
1370         GtkTreeIter iter;
1371
1372         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), NULL);
1373         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
1374
1375         /* If the message was not obtained from a treemodel,
1376          * for instance if it was opened directly by the search UI:
1377          */
1378         if (priv->header_model == NULL || 
1379             priv->row_reference == NULL ||
1380             !gtk_tree_row_reference_valid (priv->row_reference)) {
1381                 msg = modest_msg_view_window_get_message (self);
1382                 if (msg) {
1383                         header = tny_msg_get_header (msg);
1384                         g_object_unref (msg);
1385                 }
1386                 return header;
1387         }
1388
1389         /* Get iter of the currently selected message in the header view: */
1390         path = gtk_tree_row_reference_get_path (priv->row_reference);
1391         g_return_val_if_fail (path != NULL, NULL);
1392         gtk_tree_model_get_iter (priv->header_model, 
1393                                  &iter, 
1394                                  path);
1395
1396         /* Get current message header */
1397         gtk_tree_model_get (priv->header_model, &iter, 
1398                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, 
1399                             &header, -1);
1400
1401         gtk_tree_path_free (path);
1402         return header;
1403 }
1404
1405 TnyMsg*
1406 modest_msg_view_window_get_message (ModestMsgViewWindow *self)
1407 {
1408         ModestMsgViewWindowPrivate *priv;
1409         
1410         g_return_val_if_fail (self, NULL);
1411         
1412         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
1413         
1414         return tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
1415 }
1416
1417 const gchar*
1418 modest_msg_view_window_get_message_uid (ModestMsgViewWindow *self)
1419 {
1420         ModestMsgViewWindowPrivate *priv;
1421
1422         g_return_val_if_fail (self, NULL);
1423         
1424         priv  = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
1425
1426         return (const gchar*) priv->msg_uid;
1427 }
1428
1429 static void 
1430 modest_msg_view_window_toggle_find_toolbar (GtkToggleAction *toggle,
1431                                             gpointer data)
1432 {
1433         ModestMsgViewWindow *window = MODEST_MSG_VIEW_WINDOW (data);
1434         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1435         ModestWindowPrivate *parent_priv = MODEST_WINDOW_GET_PRIVATE (window);
1436         gboolean is_active;
1437         GtkAction *action;
1438
1439         is_active = gtk_toggle_action_get_active (toggle);
1440
1441         if (is_active) {
1442                 gtk_widget_show (priv->find_toolbar);
1443                 hildon_find_toolbar_highlight_entry (HILDON_FIND_TOOLBAR (priv->find_toolbar), TRUE);
1444         } else {
1445                 gtk_widget_hide (priv->find_toolbar);
1446                 modest_msg_view_grab_focus (MODEST_MSG_VIEW (priv->msg_view));
1447         }
1448
1449         /* update the toggle buttons status */
1450         action = gtk_ui_manager_get_action (parent_priv->ui_manager, "/ToolBar/FindInMessage");
1451         modest_utils_toggle_action_set_active_block_notify (GTK_TOGGLE_ACTION (action), is_active);
1452         action = gtk_ui_manager_get_action (parent_priv->ui_manager, "/MenuBar/ToolsMenu/ToolsFindInMessageMenu");
1453         modest_utils_toggle_action_set_active_block_notify (GTK_TOGGLE_ACTION (action), is_active);
1454         
1455 }
1456
1457 static void
1458 modest_msg_view_window_find_toolbar_close (GtkWidget *widget,
1459                                            ModestMsgViewWindow *obj)
1460 {
1461         GtkToggleAction *toggle;
1462         ModestWindowPrivate *parent_priv;
1463         ModestMsgViewWindowPrivate *priv;
1464
1465         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (obj);
1466         parent_priv = MODEST_WINDOW_GET_PRIVATE (obj);
1467         
1468         toggle = GTK_TOGGLE_ACTION (gtk_ui_manager_get_action (parent_priv->ui_manager, "/ToolBar/FindInMessage"));
1469         gtk_toggle_action_set_active (toggle, FALSE);
1470         modest_msg_view_grab_focus (MODEST_MSG_VIEW (priv->msg_view));
1471 }
1472
1473 static void
1474 modest_msg_view_window_find_toolbar_search (GtkWidget *widget,
1475                                            ModestMsgViewWindow *obj)
1476 {
1477         gchar *current_search;
1478         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (obj);
1479
1480         if (modest_mime_part_view_is_empty (MODEST_MIME_PART_VIEW (priv->msg_view))) {
1481                 hildon_banner_show_information (NULL, NULL, _("mail_ib_nothing_to_find"));
1482                 return;
1483         }
1484
1485         g_object_get (G_OBJECT (widget), "prefix", &current_search, NULL);
1486
1487         if ((current_search == NULL) || (strcmp (current_search, "") == 0)) {
1488                 g_free (current_search);
1489                 hildon_banner_show_information (NULL, NULL, _CS("ecdg_ib_find_rep_enter_text"));
1490                 return;
1491         }
1492
1493         if ((priv->last_search == NULL) || (strcmp (priv->last_search, current_search) != 0)) {
1494                 gboolean result;
1495                 g_free (priv->last_search);
1496                 priv->last_search = g_strdup (current_search);
1497                 result = modest_isearch_view_search (MODEST_ISEARCH_VIEW (priv->msg_view),
1498                                                      priv->last_search);
1499                 if (!result) {
1500                         hildon_banner_show_information (NULL, NULL, _HL("ckct_ib_find_no_matches"));
1501                         g_free (priv->last_search);
1502                         priv->last_search = NULL;
1503                 } else {
1504                         modest_msg_view_grab_focus (MODEST_MSG_VIEW (priv->msg_view));
1505                         hildon_find_toolbar_highlight_entry (HILDON_FIND_TOOLBAR (priv->find_toolbar), TRUE);
1506                 }
1507         } else {
1508                 if (!modest_isearch_view_search_next (MODEST_ISEARCH_VIEW (priv->msg_view))) {
1509                         hildon_banner_show_information (NULL, NULL, _HL("ckct_ib_find_search_complete"));
1510                         g_free (priv->last_search);
1511                         priv->last_search = NULL;
1512                 } else {
1513                         modest_msg_view_grab_focus (MODEST_MSG_VIEW (priv->msg_view));
1514                         hildon_find_toolbar_highlight_entry (HILDON_FIND_TOOLBAR (priv->find_toolbar), TRUE);
1515                 }
1516         }
1517         
1518         g_free (current_search);
1519                 
1520 }
1521
1522 static void
1523 modest_msg_view_window_set_zoom (ModestWindow *window,
1524                                  gdouble zoom)
1525 {
1526         ModestMsgViewWindowPrivate *priv;
1527         ModestWindowPrivate *parent_priv;
1528         GtkAction *action = NULL;
1529         gint int_zoom = (gint) rint (zoom*100.0+0.1);
1530      
1531         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
1532
1533         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1534         parent_priv = MODEST_WINDOW_GET_PRIVATE (window);
1535         modest_zoomable_set_zoom (MODEST_ZOOMABLE (priv->msg_view), zoom);
1536
1537         action = gtk_ui_manager_get_action (parent_priv->ui_manager, 
1538                                             "/MenuBar/ViewMenu/ZoomMenu/Zoom50Menu");
1539
1540         gtk_radio_action_set_current_value (GTK_RADIO_ACTION (action), int_zoom);
1541 }
1542
1543 static gdouble
1544 modest_msg_view_window_get_zoom (ModestWindow *window)
1545 {
1546         ModestMsgViewWindowPrivate *priv;
1547      
1548         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), 1.0);
1549
1550         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1551         return modest_zoomable_get_zoom (MODEST_ZOOMABLE (priv->msg_view));
1552 }
1553
1554 static gboolean
1555 modest_msg_view_window_zoom_plus (ModestWindow *window)
1556 {
1557         ModestWindowPrivate *parent_priv;
1558         GtkRadioAction *zoom_radio_action;
1559         GSList *group, *node;
1560
1561         parent_priv = MODEST_WINDOW_GET_PRIVATE (window);
1562         zoom_radio_action = GTK_RADIO_ACTION (gtk_ui_manager_get_action (parent_priv->ui_manager, 
1563                                                                          "/MenuBar/ViewMenu/ZoomMenu/Zoom50Menu"));
1564
1565         group = gtk_radio_action_get_group (zoom_radio_action);
1566
1567         if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (group->data))) {
1568                 hildon_banner_show_information (NULL, NULL, _CS("ckct_ib_max_zoom_level_reached"));
1569                 return FALSE;
1570         }
1571
1572         for (node = group; node != NULL; node = g_slist_next (node)) {
1573                 if ((node->next != NULL) && gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (node->next->data))) {
1574                         gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (node->data), TRUE);
1575                         return TRUE;
1576                 }
1577         }
1578         return FALSE;
1579 }
1580
1581 static gboolean
1582 modest_msg_view_window_zoom_minus (ModestWindow *window)
1583 {
1584         ModestWindowPrivate *parent_priv;
1585         GtkRadioAction *zoom_radio_action;
1586         GSList *group, *node;
1587
1588         parent_priv = MODEST_WINDOW_GET_PRIVATE (window);
1589         zoom_radio_action = GTK_RADIO_ACTION (gtk_ui_manager_get_action (parent_priv->ui_manager, 
1590                                                                          "/MenuBar/ViewMenu/ZoomMenu/Zoom50Menu"));
1591
1592         group = gtk_radio_action_get_group (zoom_radio_action);
1593
1594         for (node = group; node != NULL; node = g_slist_next (node)) {
1595                 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (node->data))) {
1596                         if (node->next != NULL) {
1597                                 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (node->next->data), TRUE);
1598                                 return TRUE;
1599                         } else {
1600                           hildon_banner_show_information (NULL, NULL, 
1601                                                           _CS("ckct_ib_min_zoom_level_reached"));
1602                                 return FALSE;
1603                         }
1604                         break;
1605                 }
1606         }
1607         return FALSE;
1608 }
1609
1610 static gboolean
1611 modest_msg_view_window_key_event (GtkWidget *window,
1612                                   GdkEventKey *event,
1613                                   gpointer userdata)
1614 {
1615         GtkWidget *focus;
1616
1617         focus = gtk_window_get_focus (GTK_WINDOW (window));
1618
1619         /* for the find toolbar case */
1620         if (focus && GTK_IS_ENTRY (focus)) {
1621                 if (event->keyval == GDK_BackSpace) {
1622                         GdkEvent *copy;
1623                         copy = gdk_event_copy ((GdkEvent *) event);
1624                         gtk_widget_event (focus, copy);
1625                         gdk_event_free (copy);
1626                         return TRUE;
1627                 } else 
1628                         return FALSE;
1629         }
1630         if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up ||
1631             event->keyval == GDK_Down || event->keyval == GDK_KP_Down ||
1632             event->keyval == GDK_Page_Up || event->keyval == GDK_KP_Page_Up ||
1633             event->keyval == GDK_Page_Down || event->keyval == GDK_KP_Page_Down ||
1634             event->keyval == GDK_Home || event->keyval == GDK_KP_Home ||
1635             event->keyval == GDK_End || event->keyval == GDK_KP_End) {
1636                 /* ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window); */
1637                 /* gboolean return_value; */
1638
1639                 if (event->type == GDK_KEY_PRESS) {
1640                         GtkScrollType scroll_type;
1641                         
1642                         switch (event->keyval) {
1643                         case GDK_Up: 
1644                         case GDK_KP_Up:
1645                                 scroll_type = GTK_SCROLL_STEP_UP; break;
1646                         case GDK_Down: 
1647                         case GDK_KP_Down:
1648                                 scroll_type = GTK_SCROLL_STEP_DOWN; break;
1649                         case GDK_Page_Up:
1650                         case GDK_KP_Page_Up:
1651                                 scroll_type = GTK_SCROLL_PAGE_UP; break;
1652                         case GDK_Page_Down:
1653                         case GDK_KP_Page_Down:
1654                                 scroll_type = GTK_SCROLL_PAGE_DOWN; break;
1655                         case GDK_Home:
1656                         case GDK_KP_Home:
1657                                 scroll_type = GTK_SCROLL_START; break;
1658                         case GDK_End:
1659                         case GDK_KP_End:
1660                                 scroll_type = GTK_SCROLL_END; break;
1661                         default: scroll_type = GTK_SCROLL_NONE;
1662                         }
1663                         
1664                         /* g_signal_emit_by_name (G_OBJECT (priv->main_scroll), "scroll-child",  */
1665                         /*                     scroll_type, FALSE, &return_value); */
1666                         return FALSE;
1667                 } else {
1668                         return FALSE;
1669                 }
1670         } else {
1671                 return FALSE;
1672         }
1673 }
1674
1675 gboolean
1676 modest_msg_view_window_last_message_selected (ModestMsgViewWindow *window)
1677 {
1678         GtkTreePath *path;
1679         ModestMsgViewWindowPrivate *priv;
1680         GtkTreeIter tmp_iter;
1681         gboolean is_last_selected;
1682
1683         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), TRUE);
1684         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1685
1686         /*if no model (so no rows at all), then virtually we are the last*/
1687         if (!priv->header_model || !priv->row_reference)
1688                 return TRUE;
1689
1690         if (!gtk_tree_row_reference_valid (priv->row_reference))
1691                 return TRUE;
1692
1693         path = gtk_tree_row_reference_get_path (priv->row_reference);
1694         if (path == NULL)
1695                 return TRUE;
1696
1697         is_last_selected = TRUE;
1698         while (is_last_selected) {
1699                 TnyHeader *header;
1700                 gtk_tree_path_next (path);
1701                 if (!gtk_tree_model_get_iter (priv->header_model, &tmp_iter, path))
1702                         break;
1703                 gtk_tree_model_get (priv->header_model, &tmp_iter,
1704                                 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1705                                 &header, -1);
1706                 if (header) {
1707                         if (msg_is_visible (header, priv->is_outbox))
1708                                 is_last_selected = FALSE;
1709                         g_object_unref(G_OBJECT(header));
1710                 }
1711         }
1712         gtk_tree_path_free (path);
1713         return is_last_selected;
1714 }
1715
1716 gboolean
1717 modest_msg_view_window_has_headers_model (ModestMsgViewWindow *window)
1718 {
1719         ModestMsgViewWindowPrivate *priv;
1720
1721         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), TRUE);
1722         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1723
1724         return priv->header_model != NULL;
1725 }
1726
1727 gboolean
1728 modest_msg_view_window_is_search_result (ModestMsgViewWindow *window)
1729 {
1730         ModestMsgViewWindowPrivate *priv;
1731
1732         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), TRUE);
1733         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1734
1735         return priv->is_search_result;
1736 }
1737
1738 static gboolean
1739 msg_is_visible (TnyHeader *header, gboolean check_outbox)
1740 {
1741         if ((tny_header_get_flags(header) & TNY_HEADER_FLAG_DELETED))
1742                 return FALSE;
1743         if (!check_outbox) {
1744                 return TRUE;
1745         } else {
1746                 ModestTnySendQueueStatus status;
1747                 status = modest_tny_all_send_queues_get_msg_status (header);
1748                 return ((status != MODEST_TNY_SEND_QUEUE_FAILED) &&
1749                         (status != MODEST_TNY_SEND_QUEUE_SENDING));
1750         }
1751 }
1752
1753 gboolean
1754 modest_msg_view_window_first_message_selected (ModestMsgViewWindow *window)
1755 {
1756         GtkTreePath *path;
1757         ModestMsgViewWindowPrivate *priv;
1758         gboolean is_first_selected;
1759         GtkTreeIter tmp_iter;
1760
1761         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), TRUE);
1762         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1763
1764         /*if no model (so no rows at all), then virtually we are the first*/
1765         if (!priv->header_model || !priv->row_reference)
1766                 return TRUE;
1767
1768         if (!gtk_tree_row_reference_valid (priv->row_reference))
1769                 return TRUE;
1770
1771         path = gtk_tree_row_reference_get_path (priv->row_reference);
1772         if (!path)
1773                 return TRUE;
1774
1775         is_first_selected = TRUE;
1776         while (is_first_selected) {
1777                 TnyHeader *header;
1778                 if(!gtk_tree_path_prev (path))
1779                         break;
1780                 /* Here the 'if' is needless for logic, but let make sure
1781                  * iter is valid for gtk_tree_model_get. */
1782                 if (!gtk_tree_model_get_iter (priv->header_model, &tmp_iter, path))
1783                         break;
1784                 gtk_tree_model_get (priv->header_model, &tmp_iter,
1785                                 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1786                                 &header, -1);
1787                 if (header) {
1788                         if (msg_is_visible (header, priv->is_outbox))
1789                                 is_first_selected = FALSE;
1790                         g_object_unref(G_OBJECT(header));
1791                 }
1792         }
1793         gtk_tree_path_free (path);
1794         return is_first_selected;
1795 }
1796
1797 typedef struct {
1798         TnyHeader *header;
1799         GtkTreeRowReference *row_reference;
1800 } MsgReaderInfo;
1801
1802 static void
1803 message_reader_performer (gboolean canceled, 
1804                           GError *err,
1805                           GtkWindow *parent_window, 
1806                           TnyAccount *account, 
1807                           gpointer user_data)
1808 {
1809         ModestMailOperation *mail_op = NULL;
1810         MsgReaderInfo *info;
1811
1812         info = (MsgReaderInfo *) user_data;
1813         if (canceled || err) {
1814                 goto frees;
1815         }
1816
1817         /* Register the header - it'll be unregistered in the callback */
1818         modest_window_mgr_register_header (modest_runtime_get_window_mgr (), info->header, NULL);
1819
1820         /* New mail operation */
1821         mail_op = modest_mail_operation_new_with_error_handling (G_OBJECT(parent_window),
1822                                                                  modest_ui_actions_disk_operations_error_handler, 
1823                                                                  NULL, NULL);
1824                                 
1825         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (), mail_op);
1826         modest_mail_operation_get_msg (mail_op, info->header, TRUE, view_msg_cb, info->row_reference);
1827         g_object_unref (mail_op);
1828
1829         /* Update dimming rules */
1830         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (parent_window));
1831         modest_ui_actions_check_menu_dimming_rules (MODEST_WINDOW (parent_window));
1832
1833  frees:
1834         /* Frees. The row_reference will be freed by the view_msg_cb callback */
1835         g_object_unref (info->header);
1836         g_slice_free (MsgReaderInfo, info);
1837 }
1838
1839
1840 /**
1841  * Reads the message whose summary item is @header. It takes care of
1842  * several things, among others:
1843  *
1844  * If the message was not previously downloaded then ask the user
1845  * before downloading. If there is no connection launch the connection
1846  * dialog. Update toolbar dimming rules.
1847  *
1848  * Returns: TRUE if the mail operation was started, otherwise if the
1849  * user do not want to download the message, or if the user do not
1850  * want to connect, then the operation is not issued
1851  **/
1852 static gboolean
1853 message_reader (ModestMsgViewWindow *window,
1854                 ModestMsgViewWindowPrivate *priv,
1855                 TnyHeader *header,
1856                 GtkTreeRowReference *row_reference)
1857 {
1858         gboolean already_showing = FALSE;
1859         ModestWindow *msg_window = NULL;
1860         ModestWindowMgr *mgr;
1861         TnyAccount *account;
1862         TnyFolder *folder;
1863         MsgReaderInfo *info;
1864
1865         g_return_val_if_fail (row_reference != NULL, FALSE);
1866
1867         mgr = modest_runtime_get_window_mgr ();
1868         already_showing = modest_window_mgr_find_registered_header (mgr, header, &msg_window);
1869         if (already_showing && (msg_window != MODEST_WINDOW (window))) {
1870                 gboolean retval;
1871                 if (msg_window)
1872                         gtk_window_present (GTK_WINDOW (msg_window));
1873                 g_signal_emit_by_name (G_OBJECT (window), "delete-event", NULL, &retval);
1874                 return TRUE;
1875         }
1876
1877         /* Msg download completed */
1878         if (!(tny_header_get_flags (header) & TNY_HEADER_FLAG_CACHED)) {
1879                 /* Ask the user if he wants to download the message if
1880                    we're not online */
1881                 if (!tny_device_is_online (modest_runtime_get_device())) {
1882                         GtkResponseType response;
1883
1884                         response = modest_platform_run_confirmation_dialog (GTK_WINDOW (window),
1885                                                                             _("mcen_nc_get_msg"));
1886                         if (response == GTK_RESPONSE_CANCEL)
1887                                 return FALSE;
1888                 
1889                         folder = tny_header_get_folder (header);
1890                         info = g_slice_new (MsgReaderInfo);
1891                         info->header = g_object_ref (header);
1892                         info->row_reference = gtk_tree_row_reference_copy (row_reference);
1893
1894                         /* Offer the connection dialog if necessary */
1895                         modest_platform_connect_if_remote_and_perform ((GtkWindow *) window, 
1896                                                                        TRUE,
1897                                                                        TNY_FOLDER_STORE (folder),
1898                                                                        message_reader_performer, 
1899                                                                        info);
1900                         g_object_unref (folder);
1901                         return TRUE;
1902                 }
1903         }
1904         
1905         folder = tny_header_get_folder (header);
1906         account = tny_folder_get_account (folder);
1907         info = g_slice_new (MsgReaderInfo);
1908         info->header = g_object_ref (header);
1909         info->row_reference = gtk_tree_row_reference_copy (row_reference);
1910         
1911         message_reader_performer (FALSE, NULL, (GtkWindow *) window, account, info);
1912         g_object_unref (account);
1913         g_object_unref (folder);
1914
1915         return TRUE;
1916 }
1917
1918 gboolean        
1919 modest_msg_view_window_select_next_message (ModestMsgViewWindow *window)
1920 {
1921         ModestMsgViewWindowPrivate *priv;
1922         GtkTreePath *path= NULL;
1923         GtkTreeIter tmp_iter;
1924         TnyHeader *header;
1925         gboolean retval = TRUE;
1926         GtkTreeRowReference *row_reference = NULL;
1927
1928         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), FALSE);
1929         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1930
1931         if (!priv->row_reference)
1932                 return FALSE;
1933
1934         /* Update the next row reference if it's not valid. This could
1935            happen if for example the header which it was pointing to,
1936            was deleted. The best place to do it is in the row-deleted
1937            handler but the tinymail model do not work like the glib
1938            tree models and reports the deletion when the row is still
1939            there */
1940         if (!gtk_tree_row_reference_valid (priv->next_row_reference)) {
1941                 if (gtk_tree_row_reference_valid (priv->row_reference)) {
1942                         priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
1943                         select_next_valid_row (priv->header_model, &(priv->next_row_reference), FALSE, priv->is_outbox);
1944                 }
1945         }
1946         if (priv->next_row_reference)
1947                 path = gtk_tree_row_reference_get_path (priv->next_row_reference);
1948         if (path == NULL)
1949                 return FALSE;
1950
1951         row_reference = gtk_tree_row_reference_copy (priv->next_row_reference);
1952
1953         gtk_tree_model_get_iter (priv->header_model,
1954                                  &tmp_iter,
1955                                  path);
1956         gtk_tree_path_free (path);
1957
1958         gtk_tree_model_get (priv->header_model, &tmp_iter, 
1959                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1960                             &header, -1);
1961         
1962         /* Read the message & show it */
1963         if (!message_reader (window, priv, header, row_reference)) {
1964                 retval = FALSE;
1965         }
1966         gtk_tree_row_reference_free (row_reference);
1967
1968         /* Free */
1969         g_object_unref (header);
1970
1971         return retval;
1972 }
1973
1974 gboolean        
1975 modest_msg_view_window_select_previous_message (ModestMsgViewWindow *window)
1976 {
1977         ModestMsgViewWindowPrivate *priv = NULL;
1978         GtkTreePath *path;
1979         gboolean finished = FALSE;
1980         gboolean retval = FALSE;
1981
1982         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), FALSE);
1983         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1984
1985         /* Return inmediatly if there is no header model */
1986         if (!priv->header_model || !priv->row_reference)
1987                 return FALSE;
1988
1989         path = gtk_tree_row_reference_get_path (priv->row_reference);
1990         while (!finished && gtk_tree_path_prev (path)) {
1991                 TnyHeader *header;
1992                 GtkTreeIter iter;
1993
1994                 gtk_tree_model_get_iter (priv->header_model, &iter, path);
1995                 gtk_tree_model_get (priv->header_model, &iter, 
1996                                     TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1997                                     &header, -1);
1998                 finished = TRUE;
1999                 if (header) {
2000                         if (msg_is_visible (header, priv->is_outbox)) {
2001                                 GtkTreeRowReference *row_reference;
2002                                 row_reference = gtk_tree_row_reference_new (priv->header_model, path);
2003                                 /* Read the message & show it */
2004                                 retval = message_reader (window, priv, header, row_reference);
2005                                 gtk_tree_row_reference_free (row_reference);
2006                         } else {
2007                                 finished = FALSE;
2008                         }
2009                         g_object_unref (header);
2010                 }
2011         }
2012
2013         gtk_tree_path_free (path);
2014         return retval;
2015 }
2016
2017 static void
2018 view_msg_cb (ModestMailOperation *mail_op, 
2019              TnyHeader *header, 
2020              gboolean canceled,
2021              TnyMsg *msg, 
2022              GError *error,
2023              gpointer user_data)
2024 {
2025         ModestMsgViewWindow *self = NULL;
2026         ModestMsgViewWindowPrivate *priv = NULL;
2027         GtkTreeRowReference *row_reference = NULL;
2028
2029         /* Unregister the header (it was registered before creating the mail operation) */
2030         modest_window_mgr_unregister_header (modest_runtime_get_window_mgr (), header);
2031
2032         row_reference = (GtkTreeRowReference *) user_data;
2033         if (canceled) {
2034                 gtk_tree_row_reference_free (row_reference);
2035                 return;
2036         }
2037         
2038         /* If there was any error */
2039         if (!modest_ui_actions_msg_retrieval_check (mail_op, header, msg)) {
2040                 gtk_tree_row_reference_free (row_reference);                    
2041                 return;
2042         }
2043
2044         /* Get the window */ 
2045         self = (ModestMsgViewWindow *) modest_mail_operation_get_source (mail_op);
2046         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self));
2047         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2048
2049         /* Update the row reference */
2050         if (priv->row_reference != NULL) {
2051                 gtk_tree_row_reference_free (priv->row_reference);
2052                 priv->row_reference = gtk_tree_row_reference_copy (row_reference);
2053                 if (priv->next_row_reference != NULL) {
2054                         gtk_tree_row_reference_free (priv->next_row_reference);
2055                 }
2056                 priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
2057                 select_next_valid_row (priv->header_model, &(priv->next_row_reference), TRUE, priv->is_outbox);
2058         }
2059
2060         /* Mark header as read */
2061         if (!(tny_header_get_flags (header) & TNY_HEADER_FLAG_SEEN))
2062                 tny_header_set_flag (header, TNY_HEADER_FLAG_SEEN);
2063
2064         /* Set new message */
2065         if (priv->msg_view != NULL && TNY_IS_MSG_VIEW (priv->msg_view)) {
2066                 tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
2067                 modest_msg_view_window_update_priority (self);
2068                 update_window_title (MODEST_MSG_VIEW_WINDOW (self));
2069                 modest_msg_view_grab_focus (MODEST_MSG_VIEW (priv->msg_view));
2070         }
2071
2072         /* Set the new message uid of the window  */
2073         if (priv->msg_uid) {
2074                 g_free (priv->msg_uid);
2075                 priv->msg_uid = modest_tny_folder_get_header_unique_id (header);
2076         }
2077
2078         /* Notify the observers */
2079         g_signal_emit (G_OBJECT (self), signals[MSG_CHANGED_SIGNAL], 
2080                        0, priv->header_model, priv->row_reference);
2081
2082         /* Frees */
2083         g_object_unref (self);
2084         gtk_tree_row_reference_free (row_reference);            
2085 }
2086
2087 TnyFolderType
2088 modest_msg_view_window_get_folder_type (ModestMsgViewWindow *window)
2089 {
2090         ModestMsgViewWindowPrivate *priv;
2091         TnyMsg *msg;
2092         TnyFolderType folder_type;
2093
2094         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2095
2096         folder_type = TNY_FOLDER_TYPE_UNKNOWN;
2097
2098         msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
2099         if (msg) {
2100                 TnyFolder *folder;
2101
2102                 folder = tny_msg_get_folder (msg);
2103                 if (folder) {
2104                         folder_type = modest_tny_folder_guess_folder_type (folder);
2105                         g_object_unref (folder);
2106                 }
2107                 g_object_unref (msg);
2108         }
2109
2110         return folder_type;
2111 }
2112
2113
2114 static void
2115 modest_msg_view_window_update_priority (ModestMsgViewWindow *window)
2116 {
2117         ModestMsgViewWindowPrivate *priv;
2118         TnyHeader *header = NULL;
2119         TnyHeaderFlags flags = 0;
2120
2121         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2122
2123         if (priv->header_model && priv->row_reference) {
2124                 GtkTreeIter iter;
2125                 GtkTreePath *path = NULL;
2126
2127                 path = gtk_tree_row_reference_get_path (priv->row_reference);
2128                 g_return_if_fail (path != NULL);
2129                 gtk_tree_model_get_iter (priv->header_model, 
2130                                          &iter, 
2131                                          gtk_tree_row_reference_get_path (priv->row_reference));
2132
2133                 gtk_tree_model_get (priv->header_model, &iter, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
2134                                     &header, -1);
2135                 gtk_tree_path_free (path);
2136         } else {
2137                 TnyMsg *msg;
2138                 msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
2139                 if (msg) {
2140                         header = tny_msg_get_header (msg);
2141                         g_object_unref (msg);
2142                 }
2143         }
2144
2145         if (header) {
2146                 flags = tny_header_get_flags (header);
2147                 g_object_unref(G_OBJECT(header));
2148         }
2149
2150         modest_msg_view_set_priority (MODEST_MSG_VIEW(priv->msg_view), flags);
2151
2152 }
2153
2154 static void
2155 toolbar_resize (ModestMsgViewWindow *self)
2156 {
2157         ModestMsgViewWindowPrivate *priv = NULL;
2158         ModestWindowPrivate *parent_priv = NULL;
2159         GtkWidget *widget;
2160         gint static_button_size;
2161         ModestWindowMgr *mgr;
2162
2163         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self));
2164         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2165         parent_priv = MODEST_WINDOW_GET_PRIVATE(self);
2166
2167         mgr = modest_runtime_get_window_mgr ();
2168         static_button_size = modest_window_mgr_get_fullscreen_mode (mgr)?118:108;
2169
2170         if (parent_priv->toolbar) {
2171                 /* left size buttons */
2172                 widget = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageReply");
2173                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (widget), FALSE);
2174                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (widget), FALSE);
2175                 gtk_widget_set_size_request (GTK_WIDGET (widget), static_button_size, -1);
2176                 widget = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageMoveTo");
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/ToolbarDeleteMessage");
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/FindInMessage");
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                 
2189                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (priv->progress_toolitem), FALSE);
2190                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->progress_toolitem), TRUE);
2191                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (priv->cancel_toolitem), FALSE);
2192                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->cancel_toolitem), FALSE);
2193                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (priv->next_toolitem), TRUE);
2194                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->next_toolitem), TRUE);
2195                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (priv->prev_toolitem), TRUE);
2196                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->prev_toolitem), TRUE);
2197         }
2198                 
2199 }
2200
2201 static gboolean
2202 modest_msg_view_window_window_state_event (GtkWidget *widget, GdkEventWindowState *event, gpointer userdata)
2203 {
2204         if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) {
2205                 ModestWindowPrivate *parent_priv;
2206                 ModestWindowMgr *mgr;
2207                 gboolean is_fullscreen;
2208                 GtkAction *fs_toggle_action;
2209                 gboolean active;
2210
2211                 mgr = modest_runtime_get_window_mgr ();
2212                 is_fullscreen = (modest_window_mgr_get_fullscreen_mode (mgr))?1:0;
2213
2214                 parent_priv = MODEST_WINDOW_GET_PRIVATE (widget);
2215                 
2216                 fs_toggle_action = gtk_ui_manager_get_action (parent_priv->ui_manager, "/MenuBar/ViewMenu/ViewToggleFullscreenMenu");
2217                 active = (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (fs_toggle_action)))?1:0;
2218                 if (is_fullscreen != active) {
2219                         gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (fs_toggle_action), is_fullscreen);
2220                 }
2221                 toolbar_resize (MODEST_MSG_VIEW_WINDOW (widget));
2222         }
2223
2224         return FALSE;
2225
2226 }
2227
2228 static void
2229 modest_msg_view_window_show_toolbar (ModestWindow *self,
2230                                      gboolean show_toolbar)
2231 {
2232         ModestMsgViewWindowPrivate *priv = NULL;
2233         ModestWindowPrivate *parent_priv;
2234         GtkWidget *reply_button = NULL, *menu = NULL;
2235         GtkWidget *placeholder = NULL;
2236         gint insert_index;
2237         const gchar *action_name;
2238         GtkAction *action;
2239         
2240         parent_priv = MODEST_WINDOW_GET_PRIVATE(self);
2241         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2242
2243         /* Set optimized view status */
2244         priv->optimized_view = !show_toolbar;
2245
2246         if (!parent_priv->toolbar) {
2247                 parent_priv->toolbar = gtk_ui_manager_get_widget (parent_priv->ui_manager, 
2248                                                                   "/ToolBar");
2249                 gtk_widget_set_no_show_all (parent_priv->toolbar, TRUE);
2250
2251                 priv->progress_toolitem = GTK_WIDGET (gtk_tool_item_new ());
2252                 priv->cancel_toolitem = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarCancel");
2253                 priv->next_toolitem = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageNext");
2254                 priv->prev_toolitem = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageBack");
2255                 toolbar_resize (MODEST_MSG_VIEW_WINDOW (self));
2256
2257                 /* Add ProgressBar (Transfer toolbar) */ 
2258                 priv->progress_bar = modest_progress_bar_new ();
2259                 gtk_widget_set_no_show_all (priv->progress_bar, TRUE);
2260                 placeholder = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ProgressbarView");
2261                 insert_index = gtk_toolbar_get_item_index(GTK_TOOLBAR (parent_priv->toolbar), GTK_TOOL_ITEM(placeholder));
2262                 gtk_container_add (GTK_CONTAINER (priv->progress_toolitem), priv->progress_bar);
2263                 gtk_toolbar_insert(GTK_TOOLBAR(parent_priv->toolbar), GTK_TOOL_ITEM (priv->progress_toolitem), insert_index);
2264                 
2265                 /* Connect cancel 'clicked' signal to abort progress mode */
2266                 g_signal_connect(priv->cancel_toolitem, "clicked",
2267                                  G_CALLBACK(cancel_progressbar),
2268                                  self);
2269                 
2270                 /* Add it to the observers list */
2271                 priv->progress_widgets = g_slist_prepend(priv->progress_widgets, priv->progress_bar);
2272
2273                 /* Add to window */
2274                 hildon_window_add_toolbar (HILDON_WINDOW (self), 
2275                                            GTK_TOOLBAR (parent_priv->toolbar));
2276
2277                 /* Set reply button tap and hold menu */        
2278                 reply_button = gtk_ui_manager_get_widget (parent_priv->ui_manager, 
2279                                                           "/ToolBar/ToolbarMessageReply");
2280                 menu = gtk_ui_manager_get_widget (parent_priv->ui_manager, 
2281                                                   "/ToolbarReplyCSM");
2282                 gtk_widget_tap_and_hold_setup (GTK_WIDGET (reply_button), menu, NULL, 0);
2283         }
2284
2285         if (show_toolbar) {
2286                 /* Quick hack: this prevents toolbar icons "dance" when progress bar show status is changed */ 
2287                 /* TODO: resize mode migth be GTK_RESIZE_QUEUE, in order to avoid unneccesary shows */
2288                 gtk_container_set_resize_mode (GTK_CONTAINER(parent_priv->toolbar), GTK_RESIZE_IMMEDIATE);
2289
2290                 gtk_widget_show (GTK_WIDGET (parent_priv->toolbar));
2291                 if (modest_msg_view_window_transfer_mode_enabled (MODEST_MSG_VIEW_WINDOW (self))) 
2292                         set_toolbar_mode (MODEST_MSG_VIEW_WINDOW (self), TOOLBAR_MODE_TRANSFER);
2293                 else
2294                         set_toolbar_mode (MODEST_MSG_VIEW_WINDOW (self), TOOLBAR_MODE_NORMAL);
2295
2296         } else {
2297                 gtk_widget_set_no_show_all (parent_priv->toolbar, TRUE);
2298                 gtk_widget_hide (GTK_WIDGET (parent_priv->toolbar));
2299         }
2300
2301         /* Update also the actions (to update the toggles in the
2302            menus), we have to do it manually because some other window
2303            of the same time could have changed it (remember that the
2304            toolbar fullscreen mode is shared by all the windows of the
2305            same type */
2306         if (modest_window_mgr_get_fullscreen_mode (modest_runtime_get_window_mgr ()))
2307                 action_name = "/MenuBar/ViewMenu/ViewShowToolbarMenu/ViewShowToolbarFullScreenMenu";
2308         else
2309                 action_name = "/MenuBar/ViewMenu/ViewShowToolbarMenu/ViewShowToolbarNormalScreenMenu";
2310
2311         action = gtk_ui_manager_get_action (parent_priv->ui_manager, action_name);
2312         modest_utils_toggle_action_set_active_block_notify (GTK_TOGGLE_ACTION (action),
2313                                                             show_toolbar);
2314 }
2315
2316 static void 
2317 modest_msg_view_window_clipboard_owner_change (GtkClipboard *clipboard,
2318                                                GdkEvent *event,
2319                                                ModestMsgViewWindow *window)
2320 {
2321         if (!GTK_WIDGET_VISIBLE (window))
2322                 return;
2323
2324         modest_window_check_dimming_rules_group (MODEST_WINDOW (window), MODEST_DIMMING_RULES_CLIPBOARD);
2325 }
2326
2327 gboolean 
2328 modest_msg_view_window_transfer_mode_enabled (ModestMsgViewWindow *self)
2329 {
2330         ModestMsgViewWindowPrivate *priv;
2331         
2332         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), FALSE); 
2333         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2334
2335         return priv->current_toolbar_mode == TOOLBAR_MODE_TRANSFER;
2336 }
2337
2338 static void
2339 cancel_progressbar (GtkToolButton *toolbutton,
2340                     ModestMsgViewWindow *self)
2341 {
2342         GSList *tmp;
2343         ModestMsgViewWindowPrivate *priv;
2344         
2345         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2346
2347         /* Get operation observers and cancel its current operation */
2348         tmp = priv->progress_widgets;
2349         while (tmp) {
2350                 modest_progress_object_cancel_current_operation (MODEST_PROGRESS_OBJECT(tmp->data));
2351                 tmp=g_slist_next(tmp);
2352         }
2353 }
2354 static gboolean
2355 observers_empty (ModestMsgViewWindow *self)
2356 {
2357         GSList *tmp = NULL;
2358         ModestMsgViewWindowPrivate *priv;
2359         gboolean is_empty = TRUE;
2360         guint pending_ops = 0;
2361  
2362         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2363         tmp = priv->progress_widgets;
2364
2365         /* Check all observers */
2366         while (tmp && is_empty)  {
2367                 pending_ops = modest_progress_object_num_pending_operations (MODEST_PROGRESS_OBJECT(tmp->data));
2368                 is_empty = pending_ops == 0;
2369                 
2370                 tmp = g_slist_next(tmp);
2371         }
2372         
2373         return is_empty;
2374 }
2375
2376 static void
2377 on_account_removed (TnyAccountStore *account_store, 
2378                     TnyAccount *account,
2379                     gpointer user_data)
2380 {
2381         /* Do nothing if it's a transport account, because we only
2382            show the messages of a store account */
2383         if (tny_account_get_account_type(account) == TNY_ACCOUNT_TYPE_STORE) {
2384                 const gchar *parent_acc = NULL;
2385                 const gchar *our_acc = NULL;
2386
2387                 our_acc = modest_window_get_active_account (MODEST_WINDOW (user_data));
2388                 parent_acc = modest_tny_account_get_parent_modest_account_name_for_server_account (account);
2389
2390                 /* Close this window if I'm showing a message of the removed account */
2391                 if (strcmp (parent_acc, our_acc) == 0)
2392                         modest_ui_actions_on_close_window (NULL, MODEST_WINDOW (user_data));
2393         }
2394 }
2395
2396 static void 
2397 on_mail_operation_started (ModestMailOperation *mail_op,
2398                            gpointer user_data)
2399 {
2400         ModestMsgViewWindow *self;
2401         ModestMailOperationTypeOperation op_type;
2402         GSList *tmp;
2403         ModestMsgViewWindowPrivate *priv;
2404         GObject *source = NULL;
2405
2406         self = MODEST_MSG_VIEW_WINDOW (user_data);
2407         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2408         op_type = modest_mail_operation_get_type_operation (mail_op);
2409         tmp = priv->progress_widgets;
2410         source = modest_mail_operation_get_source(mail_op);
2411         if (G_OBJECT (self) == source) {
2412                 if (op_type == MODEST_MAIL_OPERATION_TYPE_RECEIVE ) {
2413                         set_toolbar_transfer_mode(self);
2414                         while (tmp) {
2415                                 modest_progress_object_add_operation (
2416                                                 MODEST_PROGRESS_OBJECT (tmp->data),
2417                                                 mail_op);
2418                                 tmp = g_slist_next (tmp);
2419                         }
2420                 }
2421         }
2422         g_object_unref (source);
2423 }
2424
2425 static void 
2426 on_mail_operation_finished (ModestMailOperation *mail_op,
2427                             gpointer user_data)
2428 {
2429         ModestMsgViewWindow *self;
2430         ModestMailOperationTypeOperation op_type;
2431         GSList *tmp;
2432         ModestMsgViewWindowPrivate *priv;
2433         
2434         self = MODEST_MSG_VIEW_WINDOW (user_data);
2435         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2436         op_type = modest_mail_operation_get_type_operation (mail_op);
2437         tmp = priv->progress_widgets;
2438         
2439         if (op_type == MODEST_MAIL_OPERATION_TYPE_RECEIVE ) {
2440                 while (tmp) {
2441                         modest_progress_object_remove_operation (MODEST_PROGRESS_OBJECT (tmp->data),
2442                                                                  mail_op);
2443                         tmp = g_slist_next (tmp);
2444                 }
2445
2446                 /* If no more operations are being observed, NORMAL mode is enabled again */
2447                 if (observers_empty (self)) {
2448                         set_toolbar_mode (self, TOOLBAR_MODE_NORMAL);
2449                 }
2450
2451                 /* Update dimming rules. We have to do this right here
2452                    and not in view_msg_cb because at that point the
2453                    transfer mode is still enabled so the dimming rule
2454                    won't let the user delete the message that has been
2455                    readed for example */
2456                 modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (self));
2457                 modest_ui_actions_check_menu_dimming_rules (MODEST_WINDOW (self));
2458         }
2459 }
2460
2461 static void
2462 on_queue_changed (ModestMailOperationQueue *queue,
2463                   ModestMailOperation *mail_op,
2464                   ModestMailOperationQueueNotification type,
2465                   ModestMsgViewWindow *self)
2466 {       
2467         ModestMsgViewWindowPrivate *priv;
2468
2469         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
2470
2471         /* If this operations was created by another window, do nothing */
2472         if (!modest_mail_operation_is_mine (mail_op, G_OBJECT(self))) 
2473             return;
2474
2475         if (type == MODEST_MAIL_OPERATION_QUEUE_OPERATION_ADDED) {
2476                 priv->sighandlers = modest_signal_mgr_connect (priv->sighandlers,
2477                                                                G_OBJECT (mail_op),
2478                                                                "operation-started",
2479                                                                G_CALLBACK (on_mail_operation_started),
2480                                                                self);
2481                 priv->sighandlers = modest_signal_mgr_connect (priv->sighandlers,
2482                                                                G_OBJECT (mail_op),
2483                                                                "operation-finished",
2484                                                                G_CALLBACK (on_mail_operation_finished),
2485                                                                self);
2486         } else if (type == MODEST_MAIL_OPERATION_QUEUE_OPERATION_REMOVED) {
2487                 priv->sighandlers = modest_signal_mgr_disconnect (priv->sighandlers,
2488                                                                   G_OBJECT (mail_op),
2489                                                                   "operation-started");
2490                 priv->sighandlers = modest_signal_mgr_disconnect (priv->sighandlers,
2491                                                                   G_OBJECT (mail_op),
2492                                                                   "operation-finished");
2493         }
2494 }
2495
2496 TnyList *
2497 modest_msg_view_window_get_attachments (ModestMsgViewWindow *win) 
2498 {
2499         ModestMsgViewWindowPrivate *priv;
2500         TnyList *selected_attachments = NULL;
2501         
2502         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (win), NULL);
2503         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (win);
2504
2505         selected_attachments = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2506         
2507         return selected_attachments;
2508 }
2509
2510 typedef struct {
2511         gchar *filepath;
2512         GtkWidget *banner;
2513         guint banner_idle_id;
2514 } DecodeAsyncHelper;
2515
2516 static gboolean
2517 decode_async_banner_idle (gpointer user_data)
2518 {
2519         DecodeAsyncHelper *helper = (DecodeAsyncHelper *) user_data;
2520
2521         helper->banner_idle_id = 0;
2522         helper->banner = hildon_banner_show_animation (NULL, NULL, _("mail_me_opening"));
2523         g_object_ref (helper->banner);
2524
2525         return FALSE;
2526 }
2527
2528 static void
2529 on_decode_to_stream_async_handler (TnyMimePart *mime_part, 
2530                                    gboolean cancelled, 
2531                                    TnyStream *stream, 
2532                                    GError *err, 
2533                                    gpointer user_data)
2534 {
2535         DecodeAsyncHelper *helper = (DecodeAsyncHelper *) user_data;
2536
2537         if (helper->banner_idle_id > 0) {
2538                 g_source_remove (helper->banner_idle_id);
2539                 helper->banner_idle_id = 0;
2540         }
2541         if (helper->banner) {
2542                 gtk_widget_destroy (helper->banner);
2543         }
2544         if (cancelled || err) {
2545                 modest_platform_information_banner (NULL, NULL, 
2546                                                     _("mail_ib_file_operation_failed"));
2547                 goto free;
2548         }
2549
2550         /* make the file read-only */
2551         g_chmod(helper->filepath, 0444);
2552         
2553         /* Activate the file */
2554         modest_platform_activate_file (helper->filepath, modest_tny_mime_part_get_content_type (mime_part));
2555
2556  free:
2557         /* Frees */
2558         g_free (helper->filepath);
2559         g_object_unref (helper->banner);
2560         g_slice_free (DecodeAsyncHelper, helper);
2561 }
2562
2563 void
2564 modest_msg_view_window_view_attachment (ModestMsgViewWindow *window, 
2565                                         TnyMimePart *mime_part)
2566 {
2567         ModestMsgViewWindowPrivate *priv;
2568         const gchar *msg_uid;
2569         gchar *attachment_uid = NULL;
2570         gint attachment_index = 0;
2571         TnyList *attachments;
2572         TnyMimePart *window_msg;
2573
2574         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
2575         g_return_if_fail (TNY_IS_MIME_PART (mime_part) || (mime_part == NULL));
2576         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2577
2578         msg_uid = modest_msg_view_window_get_message_uid (MODEST_MSG_VIEW_WINDOW (window));
2579         attachments = modest_msg_view_get_attachments (MODEST_MSG_VIEW (priv->msg_view));
2580         attachment_index = modest_list_index (attachments, (GObject *) mime_part);
2581         g_object_unref (attachments);
2582         
2583         if (msg_uid && attachment_index >= 0) {
2584                 attachment_uid = g_strdup_printf ("%s/%d", msg_uid, attachment_index);
2585         }
2586
2587         if (mime_part == NULL) {
2588                 gboolean error = FALSE;
2589                 TnyList *selected_attachments = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2590                 if (selected_attachments == NULL || tny_list_get_length (selected_attachments) == 0) {
2591                         error = TRUE;
2592                 } else if (tny_list_get_length (selected_attachments) > 1) {
2593                         hildon_banner_show_information (NULL, NULL, _("mcen_ib_unable_to_display_more"));
2594                         error = TRUE;
2595                 } else {
2596                         TnyIterator *iter;
2597                         iter = tny_list_create_iterator (selected_attachments);
2598                         mime_part = (TnyMimePart *) tny_iterator_get_current (iter);
2599                         g_object_unref (iter);
2600                 }
2601                 g_object_unref (selected_attachments);
2602
2603                 if (error)
2604                         return;
2605         } else {
2606                 g_object_ref (mime_part);
2607         }
2608
2609         if (tny_mime_part_is_purged (mime_part)) {
2610                 g_object_unref (mime_part);
2611                 return;
2612         }
2613
2614         /* we also check for mime_part == priv->msg, as this means it's a direct attachment
2615          * shown as attachment, so it should behave as a file */
2616         window_msg = TNY_MIME_PART (tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view)));
2617         if (!modest_tny_mime_part_is_msg (mime_part)||
2618             mime_part == window_msg) {
2619                 gchar *filepath = NULL;
2620                 const gchar *att_filename = tny_mime_part_get_filename (mime_part);
2621                 gboolean show_error_banner = FALSE;
2622                 TnyFsStream *temp_stream = NULL;
2623                 temp_stream = modest_utils_create_temp_stream (att_filename, attachment_uid,
2624                                                                &filepath);
2625                 
2626                 if (temp_stream != NULL) {
2627                         DecodeAsyncHelper *helper = g_slice_new (DecodeAsyncHelper);
2628                         helper->filepath = g_strdup (filepath);
2629                         helper->banner = NULL;
2630                         helper->banner_idle_id = g_timeout_add (1000, decode_async_banner_idle, helper);
2631                         tny_mime_part_decode_to_stream_async (mime_part, TNY_STREAM (temp_stream), 
2632                                                               on_decode_to_stream_async_handler, 
2633                                                               NULL, 
2634                                                               helper);
2635                         g_object_unref (temp_stream);
2636                         /* NOTE: files in the temporary area will be automatically
2637                          * cleaned after some time if they are no longer in use */
2638                 } else {
2639                         if (filepath) {
2640                                 const gchar *content_type;
2641                                 /* the file may already exist but it isn't writable,
2642                                  * let's try to open it anyway */
2643                                 content_type = modest_tny_mime_part_get_content_type (mime_part);
2644                                 modest_platform_activate_file (filepath, content_type);
2645                         } else {
2646                                 g_warning ("%s: modest_utils_create_temp_stream failed", __FUNCTION__);
2647                                 show_error_banner = TRUE;
2648                         }
2649                 }
2650                 if (filepath)
2651                         g_free (filepath);
2652                 if (show_error_banner)
2653                         modest_platform_information_banner (NULL, NULL, _("mail_ib_file_operation_failed"));
2654         } else {
2655                 /* message attachment */
2656                 TnyHeader *header = NULL;
2657                 ModestWindowMgr *mgr;
2658                 ModestWindow *msg_win = NULL;
2659                 gboolean found;
2660                 
2661                 header = tny_msg_get_header (TNY_MSG (mime_part));
2662                 mgr = modest_runtime_get_window_mgr ();         
2663                 found = modest_window_mgr_find_registered_header (mgr, header, &msg_win);
2664
2665                 if (found) {
2666                         if (msg_win)                            /* there is already a window for this uid; top it */
2667                                 gtk_window_present (GTK_WINDOW(msg_win));
2668                         else 
2669                                 /* if it's found, but there is no msg_win, it's probably in the process of being created;
2670                                  * thus, we don't do anything */
2671                                 g_warning ("window for is already being created");
2672                 } else { 
2673                         /* it's not found, so create a new window for it */
2674                         modest_window_mgr_register_header (mgr, header, attachment_uid); /* register the uid before building the window */
2675                         gchar *account = g_strdup (modest_window_get_active_account (MODEST_WINDOW (window)));
2676                         if (!account)
2677                                 account = modest_account_mgr_get_default_account (modest_runtime_get_account_mgr ());
2678                         msg_win = modest_msg_view_window_new_for_attachment (TNY_MSG (mime_part), account, 
2679                                                                              NULL, attachment_uid);
2680                         modest_window_set_zoom (MODEST_WINDOW (msg_win), 
2681                                                 modest_window_get_zoom (MODEST_WINDOW (window)));
2682                         modest_window_mgr_register_window (mgr, msg_win, MODEST_WINDOW (window));
2683                         gtk_widget_show_all (GTK_WIDGET (msg_win));
2684                 }
2685         }
2686         g_object_unref (window_msg);
2687         g_object_unref (mime_part);
2688 }
2689
2690 typedef struct
2691 {
2692         gchar *filename;
2693         TnyMimePart *part;
2694 } SaveMimePartPair;
2695
2696 typedef struct
2697 {
2698         GList *pairs;
2699         GtkWidget *banner;
2700         GnomeVFSResult result;
2701 } SaveMimePartInfo;
2702
2703 static void save_mime_part_info_free (SaveMimePartInfo *info, gboolean with_struct);
2704 static gboolean idle_save_mime_part_show_result (SaveMimePartInfo *info);
2705 static gpointer save_mime_part_to_file (SaveMimePartInfo *info);
2706 static void save_mime_parts_to_file_with_checks (SaveMimePartInfo *info);
2707
2708 static void 
2709 save_mime_part_info_free (SaveMimePartInfo *info, gboolean with_struct)
2710 {
2711         
2712         GList *node;
2713         for (node = info->pairs; node != NULL; node = g_list_next (node)) {
2714                 SaveMimePartPair *pair = (SaveMimePartPair *) node->data;
2715                 g_free (pair->filename);
2716                 g_object_unref (pair->part);
2717                 g_slice_free (SaveMimePartPair, pair);
2718         }
2719         g_list_free (info->pairs);
2720         info->pairs = NULL;
2721         if (with_struct) {
2722                 gtk_widget_destroy (info->banner);
2723                 g_slice_free (SaveMimePartInfo, info);
2724         }
2725 }
2726
2727 static gboolean
2728 idle_save_mime_part_show_result (SaveMimePartInfo *info)
2729 {
2730         if (info->pairs != NULL) {
2731                 save_mime_part_to_file (info);
2732         } else {
2733                 /* This is a GDK lock because we are an idle callback and
2734                  * hildon_banner_show_information is or does Gtk+ code */
2735
2736                 gdk_threads_enter (); /* CHECKED */
2737                 save_mime_part_info_free (info, TRUE);
2738                 if (info->result == GNOME_VFS_OK) {
2739                         hildon_banner_show_information (NULL, NULL, _CS("sfil_ib_saved"));
2740                 } else if (info->result == GNOME_VFS_ERROR_NO_SPACE) {
2741                         hildon_banner_show_information (NULL, NULL, 
2742                                                         _KR("cerm_device_memory_full"));
2743                 } else {
2744                         hildon_banner_show_information (NULL, NULL, 
2745                                                         _("mail_ib_file_operation_failed"));
2746                 }
2747                 gdk_threads_leave (); /* CHECKED */
2748         }
2749
2750         return FALSE;
2751 }
2752
2753 static gpointer
2754 save_mime_part_to_file (SaveMimePartInfo *info)
2755 {
2756         GnomeVFSHandle *handle;
2757         TnyStream *stream;
2758         SaveMimePartPair *pair = (SaveMimePartPair *) info->pairs->data;
2759
2760         info->result = gnome_vfs_create (&handle, pair->filename, GNOME_VFS_OPEN_WRITE, FALSE, 0644);
2761         if (info->result == GNOME_VFS_OK) {
2762                 GError *error = NULL;
2763                 stream = tny_vfs_stream_new (handle);
2764                 if (tny_mime_part_decode_to_stream (pair->part, stream, &error) < 0) {
2765                         g_warning ("modest: could not save attachment %s: %d (%s)\n", pair->filename, error?error->code:-1, error?error->message:"Unknown error");
2766                         
2767                         info->result = GNOME_VFS_ERROR_IO;
2768                 }
2769                 g_object_unref (G_OBJECT (stream));
2770                 g_object_unref (pair->part);
2771                 g_slice_free (SaveMimePartPair, pair);
2772                 info->pairs = g_list_delete_link (info->pairs, info->pairs);
2773         } else {
2774                 g_warning ("modest: could not create save attachment %s: %s\n", pair->filename, gnome_vfs_result_to_string (info->result));
2775                 save_mime_part_info_free (info, FALSE);
2776         }
2777
2778         g_idle_add ((GSourceFunc) idle_save_mime_part_show_result, info);
2779         return NULL;
2780 }
2781
2782 static void
2783 save_mime_parts_to_file_with_checks (SaveMimePartInfo *info)
2784 {
2785         gboolean is_ok = TRUE;
2786         gint replaced_files = 0;
2787         const GList *files = info->pairs;
2788         const GList *iter;
2789
2790         for (iter = files; (iter != NULL) && (replaced_files < 2); iter = g_list_next(iter)) {
2791                 SaveMimePartPair *pair = iter->data;
2792                 if (modest_utils_file_exists (pair->filename)) {
2793                         replaced_files++;
2794                 }
2795         }
2796         if (replaced_files) {
2797                 GtkWidget *confirm_overwrite_dialog;
2798                 const gchar *message = (replaced_files == 1) ?
2799                         _FM("docm_nc_replace_file") : _FM("docm_nc_replace_multiple");
2800                 confirm_overwrite_dialog = hildon_note_new_confirmation (NULL, message);
2801                 if (gtk_dialog_run (GTK_DIALOG (confirm_overwrite_dialog)) != GTK_RESPONSE_OK) {
2802                         is_ok = FALSE;
2803                 }
2804                 gtk_widget_destroy (confirm_overwrite_dialog);
2805         }
2806
2807         if (!is_ok) {
2808                 save_mime_part_info_free (info, TRUE);
2809         } else {
2810                 GtkWidget *banner = hildon_banner_show_animation (NULL, NULL, 
2811                                                                   _CS("sfil_ib_saving"));
2812                 info->banner = banner;
2813                 g_thread_create ((GThreadFunc)save_mime_part_to_file, info, FALSE, NULL);
2814         }
2815
2816 }
2817
2818 static void
2819 save_attachments_response (GtkDialog *dialog,
2820                            gint       arg1,
2821                            gpointer   user_data)  
2822 {
2823         TnyList *mime_parts;
2824         gchar *chooser_uri;
2825         GList *files_to_save = NULL;
2826
2827         mime_parts = TNY_LIST (user_data);
2828
2829         if (arg1 != GTK_RESPONSE_OK)
2830                 goto end;
2831
2832         chooser_uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dialog));
2833
2834         if (!modest_utils_folder_writable (chooser_uri)) {
2835                 hildon_banner_show_information 
2836                         (NULL, NULL, _FM("sfil_ib_readonly_location"));
2837         } else {
2838                 TnyIterator *iter;
2839
2840                 iter = tny_list_create_iterator (mime_parts);
2841                 while (!tny_iterator_is_done (iter)) {
2842                         TnyMimePart *mime_part = (TnyMimePart *) tny_iterator_get_current (iter);
2843
2844                         if ((modest_tny_mime_part_is_attachment_for_modest (mime_part)) &&
2845                             !tny_mime_part_is_purged (mime_part) &&
2846                             (tny_mime_part_get_filename (mime_part) != NULL)) {
2847                                 SaveMimePartPair *pair;
2848
2849                                 pair = g_slice_new0 (SaveMimePartPair);
2850
2851                                 if (tny_list_get_length (mime_parts) > 1) {
2852                                         gchar *escaped = 
2853                                                 gnome_vfs_escape_slashes (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         if (files_to_save != NULL) {
2869                 SaveMimePartInfo *info = g_slice_new0 (SaveMimePartInfo);
2870                 info->pairs = files_to_save;
2871                 info->result = TRUE;
2872                 save_mime_parts_to_file_with_checks (info);
2873         }
2874
2875  end:
2876         /* Free and close the dialog */
2877         g_object_unref (mime_parts);
2878         gtk_widget_destroy (GTK_WIDGET (dialog));
2879 }
2880
2881 void
2882 modest_msg_view_window_save_attachments (ModestMsgViewWindow *window, TnyList *mime_parts)
2883 {
2884         ModestMsgViewWindowPrivate *priv;
2885         GtkWidget *save_dialog = NULL;
2886         gchar *folder = NULL;
2887         gchar *filename = NULL;
2888         gchar *save_multiple_str = NULL;
2889         TnyMsg *window_msg;
2890
2891         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
2892         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2893
2894         if (mime_parts == NULL) {
2895                 mime_parts = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2896                 if (mime_parts == NULL || tny_list_get_length (mime_parts) == 0)
2897                         return;
2898         } else {
2899                 g_object_ref (mime_parts);
2900         }
2901
2902         window_msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
2903         /* prepare dialog */
2904         if (tny_list_get_length (mime_parts) == 1) {
2905                 TnyIterator *iter;
2906                 /* only one attachment selected */
2907                 iter = tny_list_create_iterator (mime_parts);
2908                 TnyMimePart *mime_part = (TnyMimePart *) tny_iterator_get_current (iter);
2909                 g_object_unref (iter);
2910                 if (!modest_tny_mime_part_is_msg (mime_part) && 
2911                     modest_tny_mime_part_is_attachment_for_modest (mime_part) &&
2912                     !tny_mime_part_is_purged (mime_part)) {
2913                         filename = g_strdup (tny_mime_part_get_filename (mime_part));
2914                 } else {
2915                         /* TODO: show any error? */
2916                         g_warning ("Tried to save a non-file attachment");
2917                         g_object_unref (mime_parts);
2918                         return;
2919                 }
2920                 g_object_unref (mime_part);
2921         } else {
2922                 save_multiple_str = g_strdup_printf (_FM("sfil_va_number_of_objects_attachments"), 
2923                                                      tny_list_get_length (mime_parts));
2924         }
2925         g_object_unref (window_msg);
2926         
2927         save_dialog = hildon_file_chooser_dialog_new (GTK_WINDOW (window), 
2928                                                       GTK_FILE_CHOOSER_ACTION_SAVE);
2929
2930         /* set folder */
2931         folder = g_build_filename (g_get_home_dir (), DEFAULT_FOLDER, NULL);
2932         gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (save_dialog), folder);
2933         g_free (folder);
2934
2935         /* set filename */
2936         if (filename) {
2937                 gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (save_dialog), 
2938                                                    filename);
2939                 g_free (filename);
2940         }
2941
2942         /* if multiple, set multiple string */
2943         if (save_multiple_str) {
2944                 g_object_set (G_OBJECT (save_dialog), "save-multiple", save_multiple_str, NULL);
2945                 gtk_window_set_title (GTK_WINDOW (save_dialog), _FM("sfil_ti_save_objects_files"));
2946         }
2947
2948         /* We must run this asynchronously, because the hildon dialog
2949            performs a gtk_dialog_run by itself which leads to gdk
2950            deadlocks */
2951         g_signal_connect (save_dialog, "response", 
2952                           G_CALLBACK (save_attachments_response), mime_parts);
2953
2954         gtk_widget_show_all (save_dialog);
2955 }
2956
2957 static gboolean
2958 show_remove_attachment_information (gpointer userdata)
2959 {
2960         ModestMsgViewWindow *window = (ModestMsgViewWindow *) userdata;
2961         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2962
2963         /* We're outside the main lock */
2964         gdk_threads_enter ();
2965
2966         if (priv->remove_attachment_banner != NULL) {
2967                 gtk_widget_destroy (priv->remove_attachment_banner);
2968                 g_object_unref (priv->remove_attachment_banner);
2969         }
2970
2971         priv->remove_attachment_banner = g_object_ref (
2972                 hildon_banner_show_animation (NULL, NULL, _("mcen_ib_removing_attachment")));
2973
2974         gdk_threads_leave ();
2975
2976         return FALSE;
2977 }
2978
2979 void
2980 modest_msg_view_window_remove_attachments (ModestMsgViewWindow *window, gboolean get_all)
2981 {
2982         ModestMsgViewWindowPrivate *priv;
2983         TnyList *mime_parts = NULL;
2984         gchar *confirmation_message;
2985         gint response;
2986         gint n_attachments;
2987         TnyMsg *msg;
2988         TnyIterator *iter;
2989
2990         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
2991         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2992
2993         if (get_all)
2994                 mime_parts = modest_msg_view_get_attachments (MODEST_MSG_VIEW (priv->msg_view));
2995         else
2996                 mime_parts = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2997                 
2998         /* Remove already purged messages from mime parts list */
2999         iter = tny_list_create_iterator (mime_parts);
3000         while (!tny_iterator_is_done (iter)) {
3001                 TnyMimePart *part = TNY_MIME_PART (tny_iterator_get_current (iter));
3002                 tny_iterator_next (iter);
3003                 if (tny_mime_part_is_purged (part)) {
3004                         tny_list_remove (mime_parts, (GObject *) part);
3005                 }
3006                 g_object_unref (part);
3007         }
3008         g_object_unref (iter);
3009
3010         if (tny_list_get_length (mime_parts) == 0) {
3011                 g_object_unref (mime_parts);
3012                 return;
3013         }
3014
3015         n_attachments = tny_list_get_length (mime_parts);
3016         if (n_attachments == 1) {
3017                 gchar *filename;
3018                 TnyMimePart *part;
3019
3020                 iter = tny_list_create_iterator (mime_parts);
3021                 part = (TnyMimePart *) tny_iterator_get_current (iter);
3022                 g_object_unref (iter);
3023                 if (modest_tny_mime_part_is_msg (part)) {
3024                         TnyHeader *header;
3025                         header = tny_msg_get_header (TNY_MSG (part));
3026                         filename = tny_header_dup_subject (header);
3027                         g_object_unref (header);
3028                         if (filename == NULL)
3029                                 filename = g_strdup (_("mail_va_no_subject"));
3030                 } else {
3031                         filename = g_strdup (tny_mime_part_get_filename (TNY_MIME_PART (part)));
3032                 }
3033                 confirmation_message = g_strdup_printf (_("mcen_nc_purge_file_text"), filename);
3034                 g_free (filename);
3035                 g_object_unref (part);
3036         } else {
3037                 confirmation_message = g_strdup_printf (ngettext("mcen_nc_purge_file_text", 
3038                                                                  "mcen_nc_purge_files_text", 
3039                                                                  n_attachments), n_attachments);
3040         }
3041         response = modest_platform_run_confirmation_dialog (GTK_WINDOW (window),
3042                                                             confirmation_message);
3043         g_free (confirmation_message);
3044
3045         if (response != GTK_RESPONSE_OK) {
3046                 g_object_unref (mime_parts);
3047                 return;
3048         }
3049
3050         priv->purge_timeout = g_timeout_add (2000, show_remove_attachment_information, window);
3051         
3052         iter = tny_list_create_iterator (mime_parts);
3053         while (!tny_iterator_is_done (iter)) {
3054                 TnyMimePart *part;
3055
3056                 part = (TnyMimePart *) tny_iterator_get_current (iter);
3057                 tny_mime_part_set_purged (TNY_MIME_PART (part));
3058                 g_object_unref (part);
3059                 tny_iterator_next (iter);
3060         }
3061         g_object_unref (iter);
3062
3063         msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
3064         tny_msg_view_clear (TNY_MSG_VIEW (priv->msg_view));
3065         tny_msg_rewrite_cache (msg);
3066         tny_msg_view_set_msg (TNY_MSG_VIEW (priv->msg_view), msg);
3067         g_object_unref (msg);
3068
3069         g_object_unref (mime_parts);
3070
3071         if (priv->purge_timeout > 0) {
3072                 g_source_remove (priv->purge_timeout);
3073                 priv->purge_timeout = 0;
3074         }
3075
3076         if (priv->remove_attachment_banner) {
3077                 gtk_widget_destroy (priv->remove_attachment_banner);
3078                 g_object_unref (priv->remove_attachment_banner);
3079                 priv->remove_attachment_banner = NULL;
3080         }
3081
3082
3083 }
3084
3085
3086 static void
3087 update_window_title (ModestMsgViewWindow *window)
3088 {
3089         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
3090         TnyMsg *msg = NULL;
3091         TnyHeader *header = NULL;
3092         gchar *subject = NULL;
3093         
3094         msg = tny_msg_view_get_msg (TNY_MSG_VIEW (priv->msg_view));
3095
3096         if (msg != NULL) {
3097                 header = tny_msg_get_header (msg);
3098                 subject = tny_header_dup_subject (header);
3099                 g_object_unref (header);
3100                 g_object_unref (msg);
3101         }
3102
3103         if ((subject == NULL)||(subject[0] == '\0')) {
3104                 g_free (subject);
3105                 subject = g_strdup (_("mail_va_no_subject"));
3106         }
3107
3108         gtk_window_set_title (GTK_WINDOW (window), subject);
3109 }
3110
3111
3112 static void on_move_focus (GtkWidget *widget,
3113                            GtkDirectionType direction,
3114                            gpointer userdata)
3115 {
3116         g_signal_stop_emission_by_name (G_OBJECT (widget), "move-focus");
3117 }
3118
3119 static TnyStream *
3120 fetch_image_open_stream (TnyStreamCache *self, gint64 *expected_size, gchar *uri)
3121 {
3122         GnomeVFSResult result;
3123         GnomeVFSHandle *handle = NULL;
3124         GnomeVFSFileInfo *info = NULL;
3125         TnyStream *stream;
3126
3127         result = gnome_vfs_open (&handle, uri, GNOME_VFS_OPEN_READ);
3128         if (result != GNOME_VFS_OK) {
3129                 *expected_size = 0;
3130                 return NULL;
3131         }
3132         
3133         info = gnome_vfs_file_info_new ();
3134         result = gnome_vfs_get_file_info_from_handle (handle, info, GNOME_VFS_FILE_INFO_DEFAULT);
3135         if (result != GNOME_VFS_OK || ! (info->valid_fields & GNOME_VFS_FILE_INFO_FIELDS_SIZE)) {
3136                 /* We put a "safe" default size for going to cache */
3137                 *expected_size = (300*1024);
3138         } else {
3139                 *expected_size = info->size;
3140         }
3141         gnome_vfs_file_info_unref (info);
3142
3143         stream = tny_vfs_stream_new (handle);
3144
3145         return stream;
3146
3147 }
3148
3149 typedef struct {
3150         gchar *uri;
3151         gchar *cache_id;
3152         TnyStream *output_stream;
3153         GtkWidget *msg_view;
3154 } FetchImageData;
3155
3156 gboolean
3157 on_fetch_image_idle_refresh_view (gpointer userdata)
3158 {
3159
3160         FetchImageData *fidata = (FetchImageData *) userdata;
3161         g_message ("REFRESH VIEW");
3162         if (GTK_WIDGET_DRAWABLE (fidata->msg_view)) {
3163                 g_message ("QUEUING DRAW");
3164                 gtk_widget_queue_draw (fidata->msg_view);
3165         }
3166         g_object_unref (fidata->msg_view);
3167         g_slice_free (FetchImageData, fidata);
3168         return FALSE;
3169 }
3170
3171 static gpointer
3172 on_fetch_image_thread (gpointer userdata)
3173 {
3174         FetchImageData *fidata = (FetchImageData *) userdata;
3175         TnyStreamCache *cache;
3176         TnyStream *cache_stream;
3177
3178         cache = modest_runtime_get_images_cache ();
3179         cache_stream = tny_stream_cache_get_stream (cache, fidata->cache_id, (TnyStreamCacheOpenStreamFetcher) fetch_image_open_stream, (gpointer) fidata->uri);
3180         g_free (fidata->cache_id);
3181         g_free (fidata->uri);
3182
3183         if (cache_stream != NULL) {
3184                 tny_stream_write_to_stream (cache_stream, fidata->output_stream);
3185                 tny_stream_close (cache_stream);
3186                 g_object_unref (cache_stream);
3187         }
3188
3189         tny_stream_close (fidata->output_stream);
3190         g_object_unref (fidata->output_stream);
3191
3192
3193         gdk_threads_enter ();
3194         g_idle_add (on_fetch_image_idle_refresh_view, fidata);
3195         gdk_threads_leave ();
3196
3197         return NULL;
3198 }
3199
3200 static gboolean
3201 on_fetch_image (ModestMsgView *msgview,
3202                 const gchar *uri,
3203                 TnyStream *stream,
3204                 ModestMsgViewWindow *window)
3205 {
3206         const gchar *current_account;
3207         ModestMsgViewWindowPrivate *priv;
3208         FetchImageData *fidata;
3209
3210         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
3211
3212         current_account = modest_window_get_active_account (MODEST_WINDOW (window));
3213
3214         fidata = g_slice_new0 (FetchImageData);
3215         fidata->msg_view = g_object_ref (msgview);
3216         fidata->uri = g_strdup (uri);
3217         fidata->cache_id = modest_images_cache_get_id (current_account, uri);
3218         fidata->output_stream = g_object_ref (stream);
3219
3220         if (g_thread_create (on_fetch_image_thread, fidata, FALSE, NULL) == NULL) {
3221                 g_object_unref (fidata->output_stream);
3222                 g_free (fidata->cache_id);
3223                 g_free (fidata->uri);
3224                 g_object_unref (fidata->msg_view);
3225                 g_slice_free (FetchImageData, fidata);
3226                 tny_stream_close (stream);
3227                 return FALSE;
3228         }
3229
3230         return TRUE;;
3231 }
3232
3233 void
3234 modest_msg_view_window_add_to_contacts (ModestMsgViewWindow *self)
3235 {
3236         modest_ui_actions_on_add_to_contacts (NULL, MODEST_WINDOW (self));
3237 }
3238
3239
3240 void
3241 modest_msg_view_window_fetch_images (ModestMsgViewWindow *self)
3242 {
3243         ModestMsgViewWindowPrivate *priv;
3244         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
3245
3246         modest_msg_view_request_fetch_images (MODEST_MSG_VIEW (priv->msg_view));
3247 }
3248
3249 gboolean 
3250 modest_msg_view_window_has_blocked_external_images (ModestMsgViewWindow *self)
3251 {
3252         ModestMsgViewWindowPrivate *priv;
3253         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
3254
3255         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), FALSE);
3256
3257         return modest_msg_view_has_blocked_external_images (MODEST_MSG_VIEW (priv->msg_view));
3258 }
3259
3260 void 
3261 modest_msg_view_window_reload (ModestMsgViewWindow *self)
3262 {
3263         ModestMsgViewWindowPrivate *priv;
3264         TnyHeader *header;
3265
3266         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self));
3267
3268         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
3269         header = modest_msg_view_window_get_header (MODEST_MSG_VIEW_WINDOW (self));     
3270
3271         if (!message_reader (self, priv, header, priv->row_reference)) {
3272                 g_warning ("Shouldn't happen, trying to reload a message failed");
3273         }
3274
3275         g_object_unref (header);
3276 }