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