904a2186a5d7bd14c503f67d69281dbd58fcd0f2
[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 (G_OBJECT(window),
1603                                                                  modest_ui_actions_get_msgs_full_error_handler, 
1604                                                                  NULL, NULL);
1605                                 
1606         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (), mail_op);
1607         modest_mail_operation_get_msg (mail_op, header, view_msg_cb, row_reference);
1608         g_object_unref (mail_op);
1609
1610         /* Update toolbar dimming rules */
1611         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (window));
1612
1613         return TRUE;
1614 }
1615
1616 gboolean        
1617 modest_msg_view_window_select_next_message (ModestMsgViewWindow *window)
1618 {
1619         ModestMsgViewWindowPrivate *priv;
1620         GtkTreePath *path= NULL;
1621         GtkTreeIter tmp_iter;
1622         TnyHeader *header;
1623         gboolean retval = TRUE;
1624         GtkTreeRowReference *row_reference = NULL;
1625
1626         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), FALSE);
1627         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1628
1629         /* Update the next row reference if it's not valid. This could
1630            happen if for example the header which it was pointing to,
1631            was deleted. The best place to do it is in the row-deleted
1632            handler but the tinymail model do not work like the glib
1633            tree models and reports the deletion when the row is still
1634            there */
1635         if (!gtk_tree_row_reference_valid (priv->next_row_reference)) {
1636                 if (gtk_tree_row_reference_valid (priv->row_reference)) {
1637                         priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
1638                         select_next_valid_row (priv->header_model, &(priv->next_row_reference), FALSE);
1639                 }
1640         }
1641         if (priv->next_row_reference)
1642                 path = gtk_tree_row_reference_get_path (priv->next_row_reference);
1643         if (path == NULL)
1644                 return FALSE;
1645
1646         row_reference = gtk_tree_row_reference_copy (priv->next_row_reference);
1647
1648         gtk_tree_model_get_iter (priv->header_model,
1649                                  &tmp_iter,
1650                                  path);
1651         gtk_tree_path_free (path);
1652
1653         gtk_tree_model_get (priv->header_model, &tmp_iter, 
1654                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1655                             &header, -1);
1656         
1657         /* Read the message & show it */
1658         if (!message_reader (window, priv, header, row_reference)) {
1659                 retval = FALSE;
1660                 gtk_tree_row_reference_free (row_reference);
1661         }
1662
1663         /* Free */
1664         g_object_unref (header);
1665
1666         return retval;          
1667 }
1668
1669 gboolean 
1670 modest_msg_view_window_select_first_message (ModestMsgViewWindow *self)
1671 {
1672         ModestMsgViewWindowPrivate *priv = NULL;
1673         TnyHeader *header = NULL;
1674         GtkTreeIter iter;
1675         GtkTreePath *path = NULL;
1676         GtkTreeRowReference *row_reference = NULL;
1677
1678         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), FALSE);
1679         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
1680
1681         /* Check that the model is not empty */
1682         if (!gtk_tree_model_get_iter_first (priv->header_model, &iter))
1683                 return FALSE;
1684
1685         /* Get the header */
1686         gtk_tree_model_get (priv->header_model, 
1687                             &iter, 
1688                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1689                             &header, -1);
1690         g_return_val_if_fail (TNY_IS_HEADER (header), FALSE);
1691         if (tny_header_get_flags (header) & TNY_HEADER_FLAG_DELETED) {
1692                 g_object_unref (header);
1693                 return modest_msg_view_window_select_next_message (self);
1694         }
1695         
1696         path = gtk_tree_model_get_path (priv->header_model, &iter);
1697         row_reference = gtk_tree_row_reference_new (priv->header_model, path);
1698         gtk_tree_path_free (path);
1699
1700         /* Read the message & show it */
1701         message_reader (self, priv, header, row_reference);
1702         
1703         /* Free */
1704         g_object_unref (header);
1705
1706         return TRUE;
1707 }
1708  
1709 gboolean        
1710 modest_msg_view_window_select_previous_message (ModestMsgViewWindow *window)
1711 {
1712         ModestMsgViewWindowPrivate *priv = NULL;
1713         GtkTreePath *path;
1714         GtkTreeRowReference *row_reference = NULL;
1715
1716         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), FALSE);
1717         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1718
1719         /* Return inmediatly if there is no header model */
1720         if (!priv->header_model)
1721                 return FALSE;
1722
1723         path = gtk_tree_row_reference_get_path (priv->row_reference);
1724         while (gtk_tree_path_prev (path)) {
1725                 TnyHeader *header;
1726                 GtkTreeIter iter;
1727
1728                 gtk_tree_model_get_iter (priv->header_model, &iter, path);
1729                 gtk_tree_model_get (priv->header_model, &iter, 
1730                                     TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1731                                     &header, -1);
1732                 if (!header)
1733                         break;
1734                 if (tny_header_get_flags (header) & TNY_HEADER_FLAG_DELETED) {
1735                         g_object_unref (header);
1736                         continue;
1737                 }
1738
1739                 row_reference = gtk_tree_row_reference_new (priv->header_model, path);
1740                 /* Read the message & show it */
1741                 if (!message_reader (window, priv, header, row_reference)) {
1742                         gtk_tree_row_reference_free (row_reference);
1743                         g_object_unref (header);
1744                         break;
1745                 }
1746
1747                 gtk_tree_path_free (path);
1748                 g_object_unref (header);
1749
1750                 return TRUE;
1751         }
1752
1753         gtk_tree_path_free (path);
1754         return FALSE;
1755 }
1756
1757 static void
1758 view_msg_cb (ModestMailOperation *mail_op, 
1759              TnyHeader *header, 
1760              TnyMsg *msg, 
1761              gpointer user_data)
1762 {
1763         ModestMsgViewWindow *self = NULL;
1764         ModestMsgViewWindowPrivate *priv = NULL;
1765         GtkTreeRowReference *row_reference = NULL;
1766
1767         /* If there was any error */
1768         row_reference = (GtkTreeRowReference *) user_data;
1769         if (!modest_ui_actions_msg_retrieval_check (mail_op, header, msg)) {
1770                 gtk_tree_row_reference_free (row_reference);                    
1771                 return;
1772         }
1773
1774         /* Get the window */ 
1775         self = (ModestMsgViewWindow *) modest_mail_operation_get_source (mail_op);
1776         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self));
1777         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
1778
1779         /* Update the row reference */
1780         gtk_tree_row_reference_free (priv->row_reference);
1781         priv->row_reference = gtk_tree_row_reference_copy (row_reference);
1782         priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
1783         select_next_valid_row (priv->header_model, &(priv->next_row_reference), TRUE);
1784         gtk_tree_row_reference_free (row_reference);
1785
1786         /* Mark header as read */
1787         if (!(tny_header_get_flags (header) & TNY_HEADER_FLAG_SEEN))
1788                 tny_header_set_flags (header, TNY_HEADER_FLAG_SEEN);
1789
1790         /* Set new message */
1791         modest_msg_view_set_message (MODEST_MSG_VIEW (priv->msg_view), msg);
1792         modest_msg_view_window_update_priority (self);
1793         update_window_title (MODEST_MSG_VIEW_WINDOW (self));
1794         modest_msg_view_grab_focus (MODEST_MSG_VIEW (priv->msg_view));
1795
1796         /* Set the new message uid of the window  */
1797         if (priv->msg_uid) {
1798                 g_free (priv->msg_uid);
1799                 priv->msg_uid = modest_tny_folder_get_header_unique_id (header);
1800         }
1801
1802         /* Notify the observers */
1803         g_signal_emit (G_OBJECT (self), signals[MSG_CHANGED_SIGNAL], 
1804                        0, priv->header_model, priv->row_reference);
1805
1806         /* Free new references */
1807         g_object_unref (self);
1808 }
1809
1810 TnyFolderType
1811 modest_msg_view_window_get_folder_type (ModestMsgViewWindow *window)
1812 {
1813         ModestMsgViewWindowPrivate *priv;
1814         TnyMsg *msg;
1815         TnyFolderType folder_type;
1816
1817         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1818
1819         folder_type = TNY_FOLDER_TYPE_UNKNOWN;
1820
1821         msg = modest_msg_view_get_message (MODEST_MSG_VIEW (priv->msg_view));
1822         if (msg) {
1823                 TnyFolder *folder;
1824
1825                 folder = tny_msg_get_folder (msg);
1826                 
1827                 if (folder) {
1828                         folder_type = tny_folder_get_folder_type (folder);
1829                         
1830                         if (folder_type == TNY_FOLDER_TYPE_NORMAL || folder_type == TNY_FOLDER_TYPE_UNKNOWN) {
1831                                 const gchar *fname = tny_folder_get_name (folder);
1832                                 folder_type = modest_tny_folder_guess_folder_type_from_name (fname);
1833                         }
1834
1835                         g_object_unref (folder);
1836                 }
1837                 g_object_unref (msg);
1838         }
1839
1840         return folder_type;
1841 }
1842
1843
1844 static void
1845 modest_msg_view_window_update_priority (ModestMsgViewWindow *window)
1846 {
1847         ModestMsgViewWindowPrivate *priv;
1848         TnyHeaderFlags flags = 0;
1849
1850         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1851
1852         if (priv->header_model) {
1853                 TnyHeader *header;
1854                 GtkTreeIter iter;
1855                 GtkTreePath *path = NULL;
1856
1857                 path = gtk_tree_row_reference_get_path (priv->row_reference);
1858                 g_return_if_fail (path != NULL);
1859                 gtk_tree_model_get_iter (priv->header_model, 
1860                                          &iter, 
1861                                          gtk_tree_row_reference_get_path (priv->row_reference));
1862
1863                 gtk_tree_model_get (priv->header_model, &iter, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1864                                     &header, -1);
1865                 flags = tny_header_get_flags (header);
1866                 gtk_tree_path_free (path);
1867         }
1868
1869         modest_msg_view_set_priority (MODEST_MSG_VIEW(priv->msg_view), flags);
1870
1871 }
1872
1873 static gboolean
1874 modest_msg_view_window_window_state_event (GtkWidget *widget, GdkEventWindowState *event, gpointer userdata)
1875 {
1876         if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) {
1877                 ModestWindowPrivate *parent_priv;
1878                 ModestWindowMgr *mgr;
1879                 gboolean is_fullscreen;
1880                 GtkAction *fs_toggle_action;
1881                 gboolean active;
1882
1883                 mgr = modest_runtime_get_window_mgr ();
1884                 is_fullscreen = (modest_window_mgr_get_fullscreen_mode (mgr))?1:0;
1885
1886                 parent_priv = MODEST_WINDOW_GET_PRIVATE (widget);
1887                 
1888                 fs_toggle_action = gtk_ui_manager_get_action (parent_priv->ui_manager, "/MenuBar/ViewMenu/ViewToggleFullscreenMenu");
1889                 active = (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (fs_toggle_action)))?1:0;
1890                 if (is_fullscreen != active) {
1891                         gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (fs_toggle_action), is_fullscreen);
1892                 }
1893         }
1894
1895         return FALSE;
1896
1897 }
1898
1899 static void
1900 set_homogeneous (GtkWidget *widget,
1901                  gpointer data)
1902 {
1903         if (GTK_IS_TOOL_ITEM (widget)) {
1904                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (widget), TRUE);
1905                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (widget), TRUE);
1906         }
1907 }
1908
1909 static void
1910 modest_msg_view_window_show_toolbar (ModestWindow *self,
1911                                      gboolean show_toolbar)
1912 {
1913         ModestMsgViewWindowPrivate *priv = NULL;
1914         ModestWindowPrivate *parent_priv;
1915         GtkWidget *reply_button = NULL, *menu = NULL;
1916         GtkWidget *placeholder = NULL;
1917         gint insert_index;
1918         const gchar *action_name;
1919         GtkAction *action;
1920         
1921         parent_priv = MODEST_WINDOW_GET_PRIVATE(self);
1922         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
1923
1924         /* Set optimized view status */
1925         priv->optimized_view = !show_toolbar;
1926
1927         if (!parent_priv->toolbar) {
1928                 parent_priv->toolbar = gtk_ui_manager_get_widget (parent_priv->ui_manager, 
1929                                                                   "/ToolBar");
1930                 gtk_widget_set_no_show_all (parent_priv->toolbar, TRUE);
1931
1932                 /* Set homogeneous toolbar */
1933                 gtk_container_foreach (GTK_CONTAINER (parent_priv->toolbar), 
1934                                        set_homogeneous, NULL);
1935
1936                 priv->progress_toolitem = GTK_WIDGET (gtk_tool_item_new ());
1937                 priv->cancel_toolitem = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarCancel");
1938                 priv->next_toolitem = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageNext");
1939                 priv->prev_toolitem = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageBack");
1940                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->progress_toolitem), TRUE);
1941                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (priv->progress_toolitem), TRUE);
1942                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (priv->cancel_toolitem), FALSE);
1943                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->cancel_toolitem), FALSE);
1944
1945                 /* Add ProgressBar (Transfer toolbar) */ 
1946                 priv->progress_bar = modest_progress_bar_widget_new ();
1947                 gtk_widget_set_no_show_all (priv->progress_bar, TRUE);
1948                 placeholder = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ProgressbarView");
1949                 insert_index = gtk_toolbar_get_item_index(GTK_TOOLBAR (parent_priv->toolbar), GTK_TOOL_ITEM(placeholder));
1950                 gtk_container_add (GTK_CONTAINER (priv->progress_toolitem), priv->progress_bar);
1951                 gtk_toolbar_insert(GTK_TOOLBAR(parent_priv->toolbar), GTK_TOOL_ITEM (priv->progress_toolitem), insert_index);
1952                 
1953                 /* Connect cancel 'clicked' signal to abort progress mode */
1954                 g_signal_connect(priv->cancel_toolitem, "clicked",
1955                                  G_CALLBACK(cancel_progressbar),
1956                                  self);
1957                 
1958                 /* Add it to the observers list */
1959                 priv->progress_widgets = g_slist_prepend(priv->progress_widgets, priv->progress_bar);
1960
1961                 /* Add to window */
1962                 hildon_window_add_toolbar (HILDON_WINDOW (self), 
1963                                            GTK_TOOLBAR (parent_priv->toolbar));
1964
1965
1966                 /* Set reply button tap and hold menu */        
1967                 reply_button = gtk_ui_manager_get_widget (parent_priv->ui_manager, 
1968                                                           "/ToolBar/ToolbarMessageReply");
1969                 menu = gtk_ui_manager_get_widget (parent_priv->ui_manager, 
1970                                                   "/ToolbarReplyCSM");
1971                 gtk_widget_tap_and_hold_setup (GTK_WIDGET (reply_button), menu, NULL, 0);
1972         }
1973
1974         if (show_toolbar) {
1975                 /* Quick hack: this prevents toolbar icons "dance" when progress bar show status is changed */ 
1976                 /* TODO: resize mode migth be GTK_RESIZE_QUEUE, in order to avoid unneccesary shows */
1977                 gtk_container_set_resize_mode (GTK_CONTAINER(parent_priv->toolbar), GTK_RESIZE_IMMEDIATE);
1978                 
1979                 gtk_widget_show (GTK_WIDGET (parent_priv->toolbar));
1980                 set_toolbar_mode (MODEST_MSG_VIEW_WINDOW(self), TOOLBAR_MODE_NORMAL);                   
1981                 
1982         } else {
1983                 gtk_widget_set_no_show_all (parent_priv->toolbar, TRUE);
1984                 gtk_widget_hide (GTK_WIDGET (parent_priv->toolbar));
1985         }
1986
1987         /* Update also the actions (to update the toggles in the
1988            menus), we have to do it manually because some other window
1989            of the same time could have changed it (remember that the
1990            toolbar fullscreen mode is shared by all the windows of the
1991            same type */
1992         if (modest_window_mgr_get_fullscreen_mode (modest_runtime_get_window_mgr ()))
1993                 action_name = "/MenuBar/ViewMenu/ViewShowToolbarMenu/ViewShowToolbarFullScreenMenu";
1994         else
1995                 action_name = "/MenuBar/ViewMenu/ViewShowToolbarMenu/ViewShowToolbarNormalScreenMenu";
1996
1997         action = gtk_ui_manager_get_action (parent_priv->ui_manager, action_name);
1998         modest_maemo_toggle_action_set_active_block_notify (GTK_TOGGLE_ACTION (action),
1999                                                             show_toolbar);
2000 }
2001
2002 static void 
2003 modest_msg_view_window_clipboard_owner_change (GtkClipboard *clipboard,
2004                                                GdkEvent *event,
2005                                                ModestMsgViewWindow *window)
2006 {
2007         if (!GTK_WIDGET_VISIBLE (window))
2008                 return;
2009
2010         modest_window_check_dimming_rules_group (MODEST_WINDOW (window), "ModestClipboardDimmingRules");
2011 }
2012
2013 gboolean 
2014 modest_msg_view_window_transfer_mode_enabled (ModestMsgViewWindow *self)
2015 {
2016         ModestMsgViewWindowPrivate *priv;
2017         
2018         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), FALSE); 
2019         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2020
2021         return priv->current_toolbar_mode == TOOLBAR_MODE_TRANSFER;
2022 }
2023
2024 static void
2025 cancel_progressbar (GtkToolButton *toolbutton,
2026                     ModestMsgViewWindow *self)
2027 {
2028         GSList *tmp;
2029         ModestMsgViewWindowPrivate *priv;
2030         
2031         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2032
2033         /* Get operation observers and cancel its current operation */
2034         tmp = priv->progress_widgets;
2035         while (tmp) {
2036                 modest_progress_object_cancel_current_operation (MODEST_PROGRESS_OBJECT(tmp->data));
2037                 tmp=g_slist_next(tmp);
2038         }
2039 }
2040 static gboolean
2041 observers_empty (ModestMsgViewWindow *self)
2042 {
2043         GSList *tmp = NULL;
2044         ModestMsgViewWindowPrivate *priv;
2045         gboolean is_empty = TRUE;
2046         guint pending_ops = 0;
2047  
2048         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2049         tmp = priv->progress_widgets;
2050
2051         /* Check all observers */
2052         while (tmp && is_empty)  {
2053                 pending_ops = modest_progress_object_num_pending_operations (MODEST_PROGRESS_OBJECT(tmp->data));
2054                 is_empty = pending_ops == 0;
2055                 
2056                 tmp = g_slist_next(tmp);
2057         }
2058         
2059         return is_empty;
2060 }
2061
2062 static void
2063 on_account_removed (TnyAccountStore *account_store, 
2064                     TnyAccount *account,
2065                     gpointer user_data)
2066 {
2067         /* Do nothing if it's a transport account, because we only
2068            show the messages of a store account */
2069         if (tny_account_get_account_type(account) == TNY_ACCOUNT_TYPE_STORE) {
2070                 const gchar *parent_acc = NULL;
2071                 const gchar *our_acc = NULL;
2072
2073                 our_acc = modest_window_get_active_account (MODEST_WINDOW (user_data));
2074                 parent_acc = modest_tny_account_get_parent_modest_account_name_for_server_account (account);
2075
2076                 /* Close this window if I'm showing a message of the removed account */
2077                 if (strcmp (parent_acc, our_acc) == 0)
2078                         modest_ui_actions_on_close_window (NULL, MODEST_WINDOW (user_data));
2079         }
2080 }
2081
2082 static void
2083 on_queue_changed (ModestMailOperationQueue *queue,
2084                   ModestMailOperation *mail_op,
2085                   ModestMailOperationQueueNotification type,
2086                   ModestMsgViewWindow *self)
2087 {
2088         GSList *tmp;
2089         ModestMsgViewWindowPrivate *priv;
2090         ModestMailOperationTypeOperation op_type;
2091         ModestToolBarModes mode;
2092         
2093         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self));
2094         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2095
2096         /* If this operations was created by another window, do nothing */
2097         if (!modest_mail_operation_is_mine (mail_op, G_OBJECT(self))) 
2098             return;
2099
2100         /* Get toolbar mode from operation id*/
2101         op_type = modest_mail_operation_get_type_operation (mail_op);
2102         switch (op_type) {
2103 /*      case MODEST_MAIL_OPERATION_TYPE_SEND: */
2104         case MODEST_MAIL_OPERATION_TYPE_RECEIVE:
2105         case MODEST_MAIL_OPERATION_TYPE_OPEN:
2106                 mode = TOOLBAR_MODE_TRANSFER;
2107                 break;
2108         default:
2109                 mode = TOOLBAR_MODE_NORMAL;
2110                 
2111         }
2112                 
2113         /* Add operation observers and change toolbar if neccessary*/
2114         tmp = priv->progress_widgets;
2115         switch (type) {
2116         case MODEST_MAIL_OPERATION_QUEUE_OPERATION_ADDED:
2117                 if (mode == TOOLBAR_MODE_TRANSFER) {
2118                         /* Enable transfer toolbar mode */
2119                         set_toolbar_transfer_mode(self);
2120                         while (tmp) {
2121                                 modest_progress_object_add_operation (MODEST_PROGRESS_OBJECT (tmp->data),
2122                                                                       mail_op);
2123                                 tmp = g_slist_next (tmp);
2124                         }
2125                         
2126                 }
2127                 break;
2128         case MODEST_MAIL_OPERATION_QUEUE_OPERATION_REMOVED:
2129                 if (mode == TOOLBAR_MODE_TRANSFER) {
2130                         while (tmp) {
2131                                 modest_progress_object_remove_operation (MODEST_PROGRESS_OBJECT (tmp->data),
2132                                                                  mail_op);
2133                                 tmp = g_slist_next (tmp);
2134                                 
2135                         }
2136
2137                         /* If no more operations are being observed, NORMAL mode is enabled again */
2138                         if (observers_empty (self)) {
2139                                 set_toolbar_mode (self, TOOLBAR_MODE_NORMAL);
2140                         }
2141                 }
2142                 break;
2143         }
2144 }
2145
2146 GList *
2147 modest_msg_view_window_get_attachments (ModestMsgViewWindow *win) 
2148 {
2149         ModestMsgViewWindowPrivate *priv;
2150         GList *selected_attachments = NULL;
2151         
2152         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (win), NULL);
2153         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (win);
2154
2155         selected_attachments = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2156         
2157         return selected_attachments;
2158 }
2159
2160 void
2161 modest_msg_view_window_view_attachment (ModestMsgViewWindow *window, TnyMimePart *mime_part)
2162 {
2163         ModestMsgViewWindowPrivate *priv;
2164         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
2165         g_return_if_fail (TNY_IS_MIME_PART (mime_part) || (mime_part == NULL));
2166
2167         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2168
2169         if (mime_part == NULL) {
2170                 gboolean error = FALSE;
2171                 GList *selected_attachments = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2172                 if (selected_attachments == NULL) {
2173                         error = TRUE;
2174                 } else if (g_list_length (selected_attachments) > 1) {
2175                         hildon_banner_show_information (NULL, NULL, _("mcen_ib_unable_to_display_more"));
2176                         error = TRUE;
2177                 } else {
2178                         mime_part = (TnyMimePart *) selected_attachments->data;
2179                         g_object_ref (mime_part);
2180                 }
2181                 g_list_foreach (selected_attachments, (GFunc) g_object_unref, NULL);
2182                 g_list_free (selected_attachments);
2183
2184                 if (error)
2185                         return;
2186         } else {
2187                 g_object_ref (mime_part);
2188         }
2189
2190         if (tny_mime_part_is_purged (mime_part)) {
2191                 g_object_unref (mime_part);
2192                 return;
2193         }
2194
2195         if (!TNY_IS_MSG (mime_part)) {
2196                 gchar *filepath = NULL;
2197                 const gchar *att_filename = tny_mime_part_get_filename (mime_part);
2198                 TnyFsStream *temp_stream = NULL;
2199
2200                 temp_stream = modest_maemo_utils_create_temp_stream (att_filename, &filepath);
2201                 
2202                 if (temp_stream) {
2203                         const gchar *content_type;
2204                         content_type = tny_mime_part_get_content_type (mime_part);
2205                         tny_mime_part_decode_to_stream (mime_part, TNY_STREAM (temp_stream));
2206                         
2207                         modest_platform_activate_file (filepath, content_type);
2208                         g_object_unref (temp_stream);
2209                         g_free (filepath);
2210                         /* NOTE: files in the temporary area will be automatically
2211                          * cleaned after some time if they are no longer in use */
2212                 }
2213         } else {
2214                 /* message attachment */
2215                 TnyHeader *header = NULL;
2216                 ModestWindowMgr *mgr;
2217                 ModestWindow *msg_win = NULL;
2218                 gboolean found;
2219                 
2220                 header = tny_msg_get_header (TNY_MSG (mime_part));
2221                 mgr = modest_runtime_get_window_mgr ();         
2222                 found = modest_window_mgr_find_registered_header (mgr, header, &msg_win);
2223
2224                 if (found) {
2225                         if (msg_win)                            /* there is already a window for this uid; top it */
2226                                 gtk_window_present (GTK_WINDOW(msg_win));
2227                         else 
2228                                 /* if it's found, but there is no msg_win, it's probably in the process of being created;
2229                                  * thus, we don't do anything */
2230                                 g_warning ("window for is already being created");
2231                 } else { 
2232                         /* it's not found, so create a new window for it */
2233                         modest_window_mgr_register_header (mgr, header); /* register the uid before building the window */
2234                         gchar *account = g_strdup (modest_window_get_active_account (MODEST_WINDOW (window)));
2235                         if (!account)
2236                                 account = modest_account_mgr_get_default_account (modest_runtime_get_account_mgr ());
2237                         msg_win = modest_msg_view_window_new_for_attachment (TNY_MSG (mime_part), account, NULL);
2238                         modest_window_set_zoom (MODEST_WINDOW (msg_win), 
2239                                                 modest_window_get_zoom (MODEST_WINDOW (window)));
2240                         modest_window_mgr_register_window (mgr, msg_win);
2241                         gtk_window_set_transient_for (GTK_WINDOW (msg_win), GTK_WINDOW (window));
2242                         gtk_widget_show_all (GTK_WIDGET (msg_win));
2243                 }
2244         }
2245         g_object_unref (mime_part);
2246 }
2247
2248 typedef struct
2249 {
2250         gchar *filename;
2251         TnyMimePart *part;
2252 } SaveMimePartPair;
2253
2254 typedef struct
2255 {
2256         GList *pairs;
2257         GtkWidget *banner;
2258         gboolean result;
2259 } SaveMimePartInfo;
2260
2261 static void save_mime_part_info_free (SaveMimePartInfo *info, gboolean with_struct);
2262 static gboolean idle_save_mime_part_show_result (SaveMimePartInfo *info);
2263 static gpointer save_mime_part_to_file (SaveMimePartInfo *info);
2264 static void save_mime_parts_to_file_with_checks (SaveMimePartInfo *info);
2265
2266 static void 
2267 save_mime_part_info_free (SaveMimePartInfo *info, gboolean with_struct)
2268 {
2269         
2270         GList *node;
2271         for (node = info->pairs; node != NULL; node = g_list_next (node)) {
2272                 SaveMimePartPair *pair = (SaveMimePartPair *) node->data;
2273                 g_free (pair->filename);
2274                 g_object_unref (pair->part);
2275                 g_slice_free (SaveMimePartPair, pair);
2276         }
2277         g_list_free (info->pairs);
2278         info->pairs = NULL;
2279         if (with_struct) {
2280                 gtk_widget_destroy (info->banner);
2281                 g_object_unref (info->banner);
2282                 g_slice_free (SaveMimePartInfo, info);
2283         }
2284 }
2285
2286 static gboolean
2287 idle_save_mime_part_show_result (SaveMimePartInfo *info)
2288 {
2289         if (info->pairs != NULL) {
2290                 save_mime_part_to_file (info);
2291         } else {
2292                 gboolean result;
2293                 result = info->result;
2294
2295                 /* This is a GDK lock because we are an idle callback and
2296                  * hildon_banner_show_information is or does Gtk+ code */
2297
2298                 gdk_threads_enter (); /* CHECKED */
2299                 save_mime_part_info_free (info, TRUE);
2300                 if (result) {
2301                         hildon_banner_show_information (NULL, NULL, _CS("sfil_ib_saved"));
2302                 } else {
2303                         hildon_banner_show_information (NULL, NULL, _("mail_ib_file_operation_failed"));
2304                 }
2305                 gdk_threads_leave (); /* CHECKED */
2306         }
2307
2308         return FALSE;
2309 }
2310
2311 static gpointer
2312 save_mime_part_to_file (SaveMimePartInfo *info)
2313 {
2314         GnomeVFSResult result;
2315         GnomeVFSHandle *handle;
2316         TnyStream *stream;
2317         SaveMimePartPair *pair = (SaveMimePartPair *) info->pairs->data;
2318
2319         result = gnome_vfs_create (&handle, pair->filename, GNOME_VFS_OPEN_WRITE, FALSE, 0777);
2320         if (result == GNOME_VFS_OK) {
2321                 stream = tny_vfs_stream_new (handle);
2322                 tny_mime_part_decode_to_stream (pair->part, stream);
2323                 g_object_unref (G_OBJECT (stream));
2324                 g_object_unref (pair->part);
2325                 g_slice_free (SaveMimePartPair, pair);
2326                 info->pairs = g_list_delete_link (info->pairs, info->pairs);
2327                 info->result = TRUE;
2328         } else {
2329                 save_mime_part_info_free (info, FALSE);
2330                 info->result = FALSE;
2331         }
2332
2333         g_idle_add ((GSourceFunc) idle_save_mime_part_show_result, info);
2334         return NULL;
2335 }
2336
2337 static void
2338 save_mime_parts_to_file_with_checks (SaveMimePartInfo *info)
2339 {
2340         gboolean is_ok = TRUE;
2341         gint replaced_files = 0;
2342         const GList *files = info->pairs;
2343         const GList *iter;
2344
2345         for (iter = files; (iter != NULL) && (replaced_files < 2); iter = g_list_next(iter)) {
2346                 SaveMimePartPair *pair = iter->data;
2347                 if (modest_maemo_utils_file_exists (pair->filename)) {
2348                         replaced_files++;
2349                 }
2350         }
2351         if (replaced_files) {
2352                 GtkWidget *confirm_overwrite_dialog;
2353                 const gchar *message = (replaced_files == 1) ?
2354                         _FM("docm_nc_replace_file") : _FM("docm_nc_replace_multiple");
2355                 confirm_overwrite_dialog = hildon_note_new_confirmation (NULL, message);
2356                 if (gtk_dialog_run (GTK_DIALOG (confirm_overwrite_dialog)) != GTK_RESPONSE_OK) {
2357                         is_ok = FALSE;
2358                 }
2359                 gtk_widget_destroy (confirm_overwrite_dialog);
2360         }
2361
2362         if (!is_ok) {
2363                 save_mime_part_info_free (info, TRUE);
2364         } else {
2365                 GtkWidget *banner = hildon_banner_show_animation (NULL, NULL, 
2366                                                                   _CS("sfil_ib_saving"));
2367                 info->banner = g_object_ref (banner);
2368                 g_thread_create ((GThreadFunc)save_mime_part_to_file, info, FALSE, NULL);
2369                 g_object_unref (banner);
2370         }
2371
2372 }
2373
2374
2375 void
2376 modest_msg_view_window_save_attachments (ModestMsgViewWindow *window, GList *mime_parts)
2377 {
2378         gboolean clean_list = FALSE;
2379         ModestMsgViewWindowPrivate *priv;
2380         GList *files_to_save = NULL;
2381         GtkWidget *save_dialog = NULL;
2382         gchar *folder = NULL;
2383         gboolean canceled = FALSE;
2384         const gchar *filename = NULL;
2385         gchar *save_multiple_str = NULL;
2386
2387         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
2388         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2389
2390         if (mime_parts == NULL) {
2391                 mime_parts = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2392                 if (mime_parts == NULL)
2393                         return;
2394                 clean_list = TRUE;
2395         }
2396
2397         /* prepare dialog */
2398         if (mime_parts->next == NULL) {
2399                 /* only one attachment selected */
2400                 TnyMimePart *mime_part = (TnyMimePart *) mime_parts->data;
2401                 if (!TNY_IS_MSG (mime_part) && tny_mime_part_is_attachment (mime_part)) {
2402                         filename = tny_mime_part_get_filename (mime_part);
2403                 } else {
2404                         g_warning ("Tried to save a non-file attachment");
2405                         canceled = TRUE;
2406                 }
2407         } else {
2408                 save_multiple_str = g_strdup_printf (_FM("sfil_va_number_of_objects_attachments"), 
2409                                                      g_list_length (mime_parts));
2410         }
2411         
2412         save_dialog = hildon_file_chooser_dialog_new (GTK_WINDOW (window), 
2413                                                       GTK_FILE_CHOOSER_ACTION_SAVE);
2414
2415         /* set folder */
2416         folder = g_build_filename (g_get_home_dir (), DEFAULT_FOLDER, NULL);
2417         gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (save_dialog), folder);
2418         g_free (folder);
2419
2420         /* set filename */
2421         if (filename != NULL)
2422                 gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (save_dialog), 
2423                                                    filename);
2424
2425         /* if multiple, set multiple string */
2426         if (save_multiple_str) {
2427                 g_object_set (G_OBJECT (save_dialog), "save-multiple", save_multiple_str, NULL);
2428         }
2429                 
2430         /* show dialog */
2431         if (gtk_dialog_run (GTK_DIALOG (save_dialog)) == GTK_RESPONSE_OK) {
2432                 gchar *chooser_uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (save_dialog));
2433
2434                 if (!modest_maemo_utils_folder_writable (chooser_uri)) {
2435                         hildon_banner_show_information 
2436                                 (NULL, NULL, dgettext("hildon-fm", "sfil_ib_readonly_location"));
2437                 } else {
2438                         GList *node = NULL;
2439
2440                         for (node = mime_parts; node != NULL; node = g_list_next (node)) {
2441                                 TnyMimePart *mime_part = (TnyMimePart *) node->data;
2442                                 
2443                                 if (tny_mime_part_is_attachment (mime_part)) {
2444                                         SaveMimePartPair *pair;
2445
2446                                         if ((mime_parts->next != NULL) &&
2447                                             (tny_mime_part_get_filename (mime_part) == NULL))
2448                                                 continue;
2449                                         
2450                                         pair = g_slice_new0 (SaveMimePartPair);
2451                                         if (mime_parts->next == NULL) {
2452                                                 pair->filename = g_strdup (chooser_uri);
2453                                         } else {
2454                                                 pair->filename = 
2455                                                         g_build_filename (chooser_uri,
2456                                                                           tny_mime_part_get_filename (mime_part), NULL);
2457                                         }
2458                                         pair->part = g_object_ref (mime_part);
2459                                         files_to_save = g_list_prepend (files_to_save, pair);
2460                                 }
2461                         }
2462                 }
2463                 g_free (chooser_uri);
2464         }
2465
2466         gtk_widget_destroy (save_dialog);
2467
2468         if (clean_list) {
2469                 g_list_foreach (mime_parts, (GFunc) g_object_unref, NULL);
2470                 g_list_free (mime_parts);
2471         }
2472
2473         if (files_to_save != NULL) {
2474                 SaveMimePartInfo *info = g_slice_new0 (SaveMimePartInfo);
2475                 info->pairs = files_to_save;
2476                 info->result = TRUE;
2477                 save_mime_parts_to_file_with_checks (info);
2478         }
2479 }
2480
2481 static gboolean
2482 show_remove_attachment_information (gpointer userdata)
2483 {
2484         ModestMsgViewWindow *window = (ModestMsgViewWindow *) userdata;
2485         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2486
2487         if (priv->remove_attachment_banner != NULL) {
2488                 gtk_widget_destroy (priv->remove_attachment_banner);
2489                 g_object_unref (priv->remove_attachment_banner);
2490         }
2491
2492         priv->remove_attachment_banner = g_object_ref (
2493                 hildon_banner_show_animation (NULL, NULL, _("mcen_ib_removing_attachment")));
2494
2495         return FALSE;
2496 }
2497
2498 void
2499 modest_msg_view_window_remove_attachments (ModestMsgViewWindow *window, gboolean get_all)
2500 {
2501         ModestMsgViewWindowPrivate *priv;
2502         GList *mime_parts = NULL, *node;
2503         gchar *confirmation_message;
2504         gint response;
2505         gint n_attachments;
2506         TnyMsg *msg;
2507 /*      TnyFolder *folder; */
2508
2509         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
2510         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2511
2512         if (get_all)
2513                 mime_parts = modest_msg_view_get_attachments (MODEST_MSG_VIEW (priv->msg_view));
2514         else
2515                 mime_parts = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2516                 
2517         /* Remove already purged messages from mime parts list */
2518         node = mime_parts;
2519         while (node != NULL) {
2520                 TnyMimePart *part = TNY_MIME_PART (node->data);
2521                 if (tny_mime_part_is_purged (part)) {
2522                         GList *deleted_node = node;
2523                         node = g_list_next (node);
2524                         g_object_unref (part);
2525                         mime_parts = g_list_delete_link (mime_parts, deleted_node);
2526                 } else {
2527                         node = g_list_next (node);
2528                 }
2529         }
2530
2531         if (mime_parts == NULL)
2532                 return;
2533
2534         n_attachments = g_list_length (mime_parts);
2535         if (n_attachments == 1) {
2536                 const gchar *filename;
2537
2538                 if (TNY_IS_MSG (mime_parts->data)) {
2539                         TnyHeader *header;
2540                         header = tny_msg_get_header (TNY_MSG (mime_parts->data));
2541                         filename = tny_header_get_subject (header);
2542                         g_object_unref (header);
2543                         if (filename == NULL)
2544                                 filename = _("mail_va_no_subject");
2545                 } else {
2546                         filename = tny_mime_part_get_filename (TNY_MIME_PART (mime_parts->data));
2547                 }
2548                 confirmation_message = g_strdup_printf (_("mcen_nc_purge_file_text"), filename);
2549         } else {
2550                 confirmation_message = g_strdup_printf (ngettext("mcen_nc_purge_file_text", 
2551                                                                  "mcen_nc_purge_files_text", 
2552                                                                  n_attachments), n_attachments);
2553         }
2554         response = modest_platform_run_confirmation_dialog (GTK_WINDOW (window),
2555                                                             confirmation_message);
2556         g_free (confirmation_message);
2557
2558         if (response != GTK_RESPONSE_OK)
2559                 return;
2560
2561         priv->purge_timeout = g_timeout_add (2000, show_remove_attachment_information, window);
2562 /*      folder = tny_msg_get_folder (msg); */
2563 /*      tny_msg_uncache_attachments (msg); */
2564 /*      tny_folder_refresh (folder, NULL); */
2565 /*      g_object_unref (folder); */
2566         
2567         for (node = mime_parts; node != NULL; node = g_list_next (node)) {
2568                 tny_mime_part_set_purged (TNY_MIME_PART (node->data));
2569 /*              modest_msg_view_remove_attachment (MODEST_MSG_VIEW (priv->msg_view), node->data); */
2570         }
2571
2572         msg = modest_msg_view_get_message (MODEST_MSG_VIEW (priv->msg_view));
2573         modest_msg_view_set_message (MODEST_MSG_VIEW (priv->msg_view), NULL);
2574         tny_msg_rewrite_cache (msg);
2575         modest_msg_view_set_message (MODEST_MSG_VIEW (priv->msg_view), msg);
2576
2577         g_list_foreach (mime_parts, (GFunc) g_object_unref, NULL);
2578         g_list_free (mime_parts);
2579
2580         if (priv->purge_timeout > 0) {
2581                 g_source_remove (priv->purge_timeout);
2582                 priv->purge_timeout = 0;
2583         }
2584
2585         if (priv->remove_attachment_banner) {
2586                 gtk_widget_destroy (priv->remove_attachment_banner);
2587                 g_object_unref (priv->remove_attachment_banner);
2588                 priv->remove_attachment_banner = NULL;
2589         }
2590
2591
2592 }
2593
2594
2595 static void
2596 update_window_title (ModestMsgViewWindow *window)
2597 {
2598         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2599         TnyMsg *msg = NULL;
2600         TnyHeader *header = NULL;
2601         const gchar *subject = NULL;
2602
2603         msg = modest_msg_view_get_message (MODEST_MSG_VIEW (priv->msg_view));
2604         if (msg != NULL) {
2605                 header = tny_msg_get_header (msg);
2606                 subject = tny_header_get_subject (header);
2607                 g_object_unref (msg);
2608         }
2609
2610         if ((subject == NULL)||(subject[0] == '\0'))
2611                 subject = _("mail_va_no_subject");
2612
2613         gtk_window_set_title (GTK_WINDOW (window), subject);
2614 }
2615
2616 static void on_move_focus (ModestMsgViewWindow *window,
2617                            GtkDirectionType direction,
2618                            gpointer userdata)
2619 {
2620         GtkWidget *current_focus = NULL;
2621
2622         current_focus = gtk_window_get_focus (GTK_WINDOW (window));
2623         if ((current_focus != NULL) &&
2624             MODEST_IS_ATTACHMENTS_VIEW (current_focus)) {
2625                 g_signal_stop_emission_by_name (G_OBJECT (window), "move-focus");
2626         }
2627 }
2628