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