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