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