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