* Added some checks that prevent glib criticals in some cases
[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         path = gtk_tree_row_reference_get_path (priv->row_reference);
1171         g_return_val_if_fail (path != NULL, NULL);
1172         gtk_tree_model_get_iter (priv->header_model, 
1173                                  &iter, 
1174                                  path);
1175
1176         /* Get current message header */
1177         gtk_tree_model_get (priv->header_model, &iter, 
1178                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, 
1179                             &header, -1);
1180
1181         gtk_tree_path_free (path);
1182         return header;
1183 }
1184
1185 TnyMsg*
1186 modest_msg_view_window_get_message (ModestMsgViewWindow *self)
1187 {
1188         ModestMsgView *msg_view;
1189         ModestMsgViewWindowPrivate *priv;
1190
1191         g_return_val_if_fail (self, NULL);
1192
1193         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
1194
1195         msg_view = MODEST_MSG_VIEW (priv->msg_view);
1196
1197         return modest_msg_view_get_message (msg_view);
1198 }
1199
1200 const gchar*
1201 modest_msg_view_window_get_message_uid (ModestMsgViewWindow *self)
1202 {
1203         ModestMsgViewWindowPrivate *priv;
1204
1205         g_return_val_if_fail (self, NULL);
1206         
1207         priv  = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
1208
1209         return (const gchar*) priv->msg_uid;
1210 }
1211
1212 static void 
1213 modest_msg_view_window_toggle_find_toolbar (GtkToggleAction *toggle,
1214                                             gpointer data)
1215 {
1216         ModestMsgViewWindow *window = MODEST_MSG_VIEW_WINDOW (data);
1217         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1218         ModestWindowPrivate *parent_priv = MODEST_WINDOW_GET_PRIVATE (window);
1219         gboolean is_active;
1220         GtkAction *action;
1221
1222         is_active = gtk_toggle_action_get_active (toggle);
1223
1224         if (is_active) {
1225                 gtk_widget_show (priv->find_toolbar);
1226                 hildon_find_toolbar_highlight_entry (HILDON_FIND_TOOLBAR (priv->find_toolbar), TRUE);
1227         } else {
1228                 gtk_widget_hide (priv->find_toolbar);
1229         }
1230
1231         /* update the toggle buttons status */
1232         action = gtk_ui_manager_get_action (parent_priv->ui_manager, "/ToolBar/FindInMessage");
1233         modest_maemo_toggle_action_set_active_block_notify (GTK_TOGGLE_ACTION (action), is_active);
1234         action = gtk_ui_manager_get_action (parent_priv->ui_manager, "/MenuBar/ToolsMenu/ToolsFindInMessageMenu");
1235         modest_maemo_toggle_action_set_active_block_notify (GTK_TOGGLE_ACTION (action), is_active);
1236         
1237 }
1238
1239 static void
1240 modest_msg_view_window_find_toolbar_close (GtkWidget *widget,
1241                                            ModestMsgViewWindow *obj)
1242 {
1243         GtkToggleAction *toggle;
1244         ModestWindowPrivate *parent_priv;
1245         parent_priv = MODEST_WINDOW_GET_PRIVATE (obj);
1246         
1247         toggle = GTK_TOGGLE_ACTION (gtk_ui_manager_get_action (parent_priv->ui_manager, "/ToolBar/FindInMessage"));
1248         gtk_toggle_action_set_active (toggle, FALSE);
1249 }
1250
1251 static void
1252 modest_msg_view_window_find_toolbar_search (GtkWidget *widget,
1253                                            ModestMsgViewWindow *obj)
1254 {
1255         gchar *current_search;
1256         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (obj);
1257
1258         if (modest_msg_view_get_message_is_empty (MODEST_MSG_VIEW (priv->msg_view))) {
1259                 hildon_banner_show_information (NULL, NULL, _("mail_ib_nothing_to_find"));
1260                 return;
1261         }
1262
1263         g_object_get (G_OBJECT (widget), "prefix", &current_search, NULL);
1264
1265         if ((current_search == NULL) || (strcmp (current_search, "") == 0)) {
1266                 g_free (current_search);
1267                 hildon_banner_show_information (NULL, NULL, dgettext("hildon-common-strings", "ecdg_ib_find_rep_enter_text"));
1268                 return;
1269         }
1270
1271         if ((priv->last_search == NULL) || (strcmp (priv->last_search, current_search) != 0)) {
1272                 gboolean result;
1273                 g_free (priv->last_search);
1274                 priv->last_search = g_strdup (current_search);
1275                 result = modest_msg_view_search (MODEST_MSG_VIEW (priv->msg_view),
1276                                                  priv->last_search);
1277                 if (!result) {
1278                         hildon_banner_show_information (NULL, NULL, dgettext("hildon-libs", "ckct_ib_find_no_matches"));
1279                         g_free (priv->last_search);
1280                         priv->last_search = NULL;
1281                 } 
1282         } else {
1283                 if (!modest_msg_view_search_next (MODEST_MSG_VIEW (priv->msg_view))) {
1284                         hildon_banner_show_information (NULL, NULL, dgettext("hildon-libs", "ckct_ib_find_search_complete"));
1285                         g_free (priv->last_search);
1286                         priv->last_search = NULL;
1287                 }
1288         }
1289         
1290         g_free (current_search);
1291                 
1292 }
1293
1294 static void
1295 modest_msg_view_window_set_zoom (ModestWindow *window,
1296                                  gdouble zoom)
1297 {
1298         ModestMsgViewWindowPrivate *priv;
1299         ModestWindowPrivate *parent_priv;
1300         GtkAction *action = NULL;
1301         gint int_zoom = (gint) rint (zoom*100.0+0.1);
1302      
1303         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
1304
1305         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1306         parent_priv = MODEST_WINDOW_GET_PRIVATE (window);
1307         modest_msg_view_set_zoom (MODEST_MSG_VIEW (priv->msg_view), zoom);
1308
1309         action = gtk_ui_manager_get_action (parent_priv->ui_manager, 
1310                                             "/MenuBar/ViewMenu/ZoomMenu/Zoom50Menu");
1311
1312         gtk_radio_action_set_current_value (GTK_RADIO_ACTION (action), int_zoom);
1313 }
1314
1315 static gdouble
1316 modest_msg_view_window_get_zoom (ModestWindow *window)
1317 {
1318         ModestMsgViewWindowPrivate *priv;
1319      
1320         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), 1.0);
1321
1322         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1323         return modest_msg_view_get_zoom (MODEST_MSG_VIEW (priv->msg_view));
1324 }
1325
1326 static gboolean
1327 modest_msg_view_window_zoom_plus (ModestWindow *window)
1328 {
1329         ModestWindowPrivate *parent_priv;
1330         GtkRadioAction *zoom_radio_action;
1331         GSList *group, *node;
1332
1333         parent_priv = MODEST_WINDOW_GET_PRIVATE (window);
1334         zoom_radio_action = GTK_RADIO_ACTION (gtk_ui_manager_get_action (parent_priv->ui_manager, 
1335                                                                          "/MenuBar/ViewMenu/ZoomMenu/Zoom50Menu"));
1336
1337         group = gtk_radio_action_get_group (zoom_radio_action);
1338
1339         if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (group->data))) {
1340                 hildon_banner_show_information (NULL, NULL, dgettext("hildon-common-strings", "ckct_ib_max_zoom_level_reached"));
1341                 return FALSE;
1342         }
1343
1344         for (node = group; node != NULL; node = g_slist_next (node)) {
1345                 if ((node->next != NULL) && gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (node->next->data))) {
1346                         gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (node->data), TRUE);
1347                         return TRUE;
1348                 }
1349         }
1350         return FALSE;
1351 }
1352
1353 static gboolean
1354 modest_msg_view_window_zoom_minus (ModestWindow *window)
1355 {
1356         ModestWindowPrivate *parent_priv;
1357         GtkRadioAction *zoom_radio_action;
1358         GSList *group, *node;
1359
1360         parent_priv = MODEST_WINDOW_GET_PRIVATE (window);
1361         zoom_radio_action = GTK_RADIO_ACTION (gtk_ui_manager_get_action (parent_priv->ui_manager, 
1362                                                                          "/MenuBar/ViewMenu/ZoomMenu/Zoom50Menu"));
1363
1364         group = gtk_radio_action_get_group (zoom_radio_action);
1365
1366         for (node = group; node != NULL; node = g_slist_next (node)) {
1367                 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (node->data))) {
1368                         if (node->next != NULL) {
1369                                 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (node->next->data), TRUE);
1370                                 return TRUE;
1371                         } else {
1372                           hildon_banner_show_information (NULL, NULL, dgettext("hildon-common-strings", "ckct_ib_min_zoom_level_reached"));
1373                                 return FALSE;
1374                         }
1375                         break;
1376                 }
1377         }
1378         return FALSE;
1379 }
1380
1381 static gboolean
1382 modest_msg_view_window_key_release_event (GtkWidget *window,
1383                                           GdkEventKey *event,
1384                                           gpointer userdata)
1385 {
1386         if (event->type == GDK_KEY_RELEASE) {
1387                 switch (event->keyval) {
1388                 case GDK_Up:
1389                         modest_msg_view_window_scroll_up (MODEST_WINDOW (window));
1390                         return TRUE;
1391                         break;
1392                 case GDK_Down:
1393                         modest_msg_view_window_scroll_down (MODEST_WINDOW (window));
1394                         return TRUE;
1395                         break;
1396                 default:
1397                         return FALSE;
1398                         break;
1399                 };
1400         } else {
1401                 return FALSE;
1402         }
1403 }
1404
1405 static void
1406 modest_msg_view_window_scroll_up (ModestWindow *window)
1407 {
1408         ModestMsgViewWindowPrivate *priv;
1409         gboolean return_value;
1410
1411         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1412         g_signal_emit_by_name (G_OBJECT (priv->main_scroll), "scroll-child", GTK_SCROLL_STEP_UP, FALSE, &return_value);
1413 }
1414
1415 static void
1416 modest_msg_view_window_scroll_down (ModestWindow *window)
1417 {
1418         ModestMsgViewWindowPrivate *priv;
1419         gboolean return_value;
1420
1421         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1422         g_signal_emit_by_name (G_OBJECT (priv->main_scroll), "scroll-child", GTK_SCROLL_STEP_DOWN, FALSE, &return_value);
1423 }
1424
1425 gboolean
1426 modest_msg_view_window_last_message_selected (ModestMsgViewWindow *window)
1427 {
1428         GtkTreePath *path;
1429         ModestMsgViewWindowPrivate *priv;
1430         GtkTreeIter tmp_iter;
1431         gboolean is_last_selected;
1432
1433         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), TRUE);
1434         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1435
1436         /*if no model (so no rows at all), then virtually we are the last*/
1437         if (!priv->header_model)
1438                 return TRUE;
1439
1440         path = gtk_tree_row_reference_get_path (priv->row_reference);
1441         if (path == NULL)
1442                 return TRUE;
1443
1444         is_last_selected = TRUE;
1445         while (is_last_selected) {
1446                 TnyHeader *header;
1447                 gtk_tree_path_next (path);
1448                 if (!gtk_tree_model_get_iter (priv->header_model, &tmp_iter, path))
1449                         break;
1450                 gtk_tree_model_get (priv->header_model, &tmp_iter,
1451                                 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1452                                 &header, -1);
1453                 if (!(tny_header_get_flags(header) & TNY_HEADER_FLAG_DELETED))
1454                         is_last_selected = FALSE;
1455         }
1456         gtk_tree_path_free (path);
1457         return is_last_selected;
1458 }
1459
1460 gboolean
1461 modest_msg_view_window_has_headers_model (ModestMsgViewWindow *window)
1462 {
1463         ModestMsgViewWindowPrivate *priv;
1464
1465         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), TRUE);
1466         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1467
1468         return priv->header_model != NULL;
1469 }
1470
1471 gboolean
1472 modest_msg_view_window_is_search_result (ModestMsgViewWindow *window)
1473 {
1474         ModestMsgViewWindowPrivate *priv;
1475
1476         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), TRUE);
1477         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1478
1479         return priv->is_search_result;
1480 }
1481
1482 gboolean
1483 modest_msg_view_window_first_message_selected (ModestMsgViewWindow *window)
1484 {
1485         GtkTreePath *path;
1486         ModestMsgViewWindowPrivate *priv;
1487         gboolean is_first_selected;
1488         GtkTreeIter tmp_iter;
1489 /*      gchar * path_string;*/
1490
1491         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), TRUE);
1492         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1493
1494         /*if no model (so no rows at all), then virtually we are the first*/
1495         if (!priv->header_model)
1496                 return TRUE;
1497
1498         path = gtk_tree_row_reference_get_path (priv->row_reference);
1499         if (!path)
1500                 return TRUE;
1501
1502 /*      path_string = gtk_tree_path_to_string (path);
1503         is_first_selected = strcmp (path_string, "0");
1504
1505         g_free (path_string);
1506         gtk_tree_path_free (path);
1507
1508         return is_first_selected;*/
1509
1510         is_first_selected = TRUE;
1511         while (is_first_selected) {
1512                 TnyHeader *header;
1513                 if(!gtk_tree_path_prev (path))
1514                         break;
1515                 /* Here the 'if' is needless for logic, but let make sure
1516                  * iter is valid for gtk_tree_model_get. */
1517                 if (!gtk_tree_model_get_iter (priv->header_model, &tmp_iter, path))
1518                         break;
1519                 gtk_tree_model_get (priv->header_model, &tmp_iter,
1520                                 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1521                                 &header, -1);
1522                 if (!(tny_header_get_flags(header) & TNY_HEADER_FLAG_DELETED))
1523                         is_first_selected = FALSE;
1524         }
1525         gtk_tree_path_free (path);
1526         return is_first_selected;
1527 }
1528
1529 /**
1530  * Reads the message whose summary item is @header. It takes care of
1531  * several things, among others:
1532  *
1533  * If the message was not previously downloaded then ask the user
1534  * before downloading. If there is no connection launch the connection
1535  * dialog. Update toolbar dimming rules.
1536  *
1537  * Returns: TRUE if the mail operation was started, otherwise if the
1538  * user do not want to download the message, or if the user do not
1539  * want to connect, then the operation is not issued
1540  **/
1541 static gboolean
1542 message_reader (ModestMsgViewWindow *window,
1543                 ModestMsgViewWindowPrivate *priv,
1544                 TnyHeader *header,
1545                 GtkTreePath *path)
1546 {
1547         ModestMailOperation *mail_op = NULL;
1548         ModestMailOperationTypeOperation op_type;
1549         gboolean already_showing = FALSE;
1550         ModestWindow *msg_window = NULL;
1551         ModestWindowMgr *mgr;
1552
1553         g_return_val_if_fail (path != NULL, FALSE);
1554
1555         mgr = modest_runtime_get_window_mgr ();
1556         already_showing = modest_window_mgr_find_registered_header (mgr, header, &msg_window);
1557         if (already_showing && (msg_window != MODEST_WINDOW (window))) {
1558                 gboolean retval;
1559                 if (msg_window)
1560                         gtk_window_present (GTK_WINDOW (msg_window));
1561                 g_signal_emit_by_name (G_OBJECT (window), "delete-event", NULL, &retval);
1562                 return TRUE;
1563         }
1564
1565         /* Msg download completed */
1566         if (tny_header_get_flags (header) & TNY_HEADER_FLAG_CACHED) {
1567                 op_type = MODEST_MAIL_OPERATION_TYPE_OPEN;
1568         } else {
1569                 op_type = MODEST_MAIL_OPERATION_TYPE_RECEIVE;
1570
1571                 /* Ask the user if he wants to download the message if
1572                    we're not online */
1573                 if (!tny_device_is_online (modest_runtime_get_device())) {
1574                         TnyFolder *folder = NULL;
1575                         GtkResponseType response;
1576
1577                         response = modest_platform_run_confirmation_dialog (GTK_WINDOW (window),
1578                                                                             _("mcen_nc_get_msg"));
1579                         if (response == GTK_RESPONSE_CANCEL)
1580                                 return FALSE;
1581                 
1582                         /* Offer the connection dialog if necessary */
1583                         folder = tny_header_get_folder (header);
1584                         if (folder) {
1585                                 if (!modest_platform_connect_and_wait_if_network_folderstore (NULL, 
1586                                                                                               TNY_FOLDER_STORE (folder))) {
1587                                         g_object_unref (folder);
1588                                         return FALSE;
1589                                 }
1590                                 g_object_unref (folder);
1591                         }
1592                 }
1593         }
1594
1595         /* New mail operation */
1596         mail_op = modest_mail_operation_new_with_error_handling (op_type, 
1597                                                                  G_OBJECT(window),
1598                                                                  modest_ui_actions_get_msgs_full_error_handler, 
1599                                                                  NULL);
1600                                 
1601         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (), mail_op);
1602         modest_mail_operation_get_msg (mail_op, header, view_msg_cb, path);
1603         g_object_unref (mail_op);
1604
1605         /* Update toolbar dimming rules */
1606         modest_ui_actions_check_toolbar_dimming_rules (MODEST_WINDOW (window));
1607
1608         return TRUE;
1609 }
1610
1611 gboolean        
1612 modest_msg_view_window_select_next_message (ModestMsgViewWindow *window)
1613 {
1614         ModestMsgViewWindowPrivate *priv;
1615         GtkTreePath *path= NULL;
1616         GtkTreeIter tmp_iter;
1617         TnyHeader *header;
1618         gboolean retval = TRUE;
1619
1620         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), FALSE);
1621         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1622
1623         /* Update the next row reference if it's not valid. This could
1624            happen if for example the header which it was pointing to,
1625            was deleted. The best place to do it is in the row-deleted
1626            handler but the tinymail model do not work like the glib
1627            tree models and reports the deletion when the row is still
1628            there */
1629         if (!gtk_tree_row_reference_valid (priv->next_row_reference)) {
1630                 if (gtk_tree_row_reference_valid (priv->row_reference)) {
1631                         priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
1632                         select_next_valid_row (priv->header_model, &(priv->next_row_reference), FALSE);
1633                 }
1634         }
1635         if (priv->next_row_reference)
1636                 path = gtk_tree_row_reference_get_path (priv->next_row_reference);
1637         if (path == NULL)
1638                 return FALSE;
1639
1640         gtk_tree_model_get_iter (priv->header_model,
1641                                  &tmp_iter,
1642                                  path);
1643
1644         gtk_tree_model_get (priv->header_model, &tmp_iter, 
1645                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1646                             &header, -1);
1647         
1648         /* Read the message & show it */
1649         if (!message_reader (window, priv, header, path)) {
1650                 retval = FALSE;
1651                 gtk_tree_path_free (path);
1652         }
1653
1654         /* Free */
1655         g_object_unref (header);
1656
1657         return retval;          
1658 }
1659
1660 gboolean 
1661 modest_msg_view_window_select_first_message (ModestMsgViewWindow *self)
1662 {
1663         ModestMsgViewWindowPrivate *priv = NULL;
1664         TnyHeader *header = NULL;
1665         GtkTreeIter iter;
1666         GtkTreePath *path;
1667
1668         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), FALSE);
1669         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
1670
1671         /* Check that the model is not empty */
1672         if (!gtk_tree_model_get_iter_first (priv->header_model, &iter))
1673                 return FALSE;
1674
1675         /* Get the header */
1676         gtk_tree_model_get (priv->header_model, 
1677                             &iter, 
1678                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1679                             &header, -1);
1680         g_return_val_if_fail (TNY_IS_HEADER (header), FALSE);
1681         if (tny_header_get_flags (header) & TNY_HEADER_FLAG_DELETED) {
1682                 g_object_unref (header);
1683                 return modest_msg_view_window_select_next_message (self);
1684         }
1685         
1686         path = gtk_tree_model_get_path (priv->header_model, &iter);
1687         
1688         /* Read the message & show it */
1689         message_reader (self, priv, header, path);
1690         
1691         /* Free */
1692         g_object_unref (header);
1693
1694         return TRUE;
1695 }
1696  
1697 gboolean        
1698 modest_msg_view_window_select_previous_message (ModestMsgViewWindow *window)
1699 {
1700         ModestMsgViewWindowPrivate *priv = NULL;
1701         GtkTreePath *path;
1702
1703         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window), FALSE);
1704         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1705
1706         /* Return inmediatly if there is no header model */
1707         if (!priv->header_model)
1708                 return FALSE;
1709
1710         path = gtk_tree_row_reference_get_path (priv->row_reference);
1711         while (gtk_tree_path_prev (path)) {
1712                 TnyHeader *header;
1713                 GtkTreeIter iter;
1714
1715                 gtk_tree_model_get_iter (priv->header_model, &iter, path);
1716                 gtk_tree_model_get (priv->header_model, &iter, 
1717                                     TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1718                                     &header, -1);
1719                 if (!header)
1720                         break;
1721                 if (tny_header_get_flags (header) & TNY_HEADER_FLAG_DELETED) {
1722                         g_object_unref (header);
1723                         continue;
1724                 }
1725
1726                 /* Read the message & show it */
1727                 if (!message_reader (window, priv, header, path)) {
1728                         g_object_unref (header);
1729                         break;
1730                 }
1731
1732                 g_object_unref (header);
1733
1734                 return TRUE;
1735         }
1736
1737         gtk_tree_path_free (path);
1738         return FALSE;
1739 }
1740
1741 static void
1742 view_msg_cb (ModestMailOperation *mail_op, 
1743              TnyHeader *header, 
1744              TnyMsg *msg, 
1745              gpointer user_data)
1746 {
1747         ModestMsgViewWindow *self = NULL;
1748         ModestMsgViewWindowPrivate *priv = NULL;
1749         GtkTreePath *path;
1750
1751         /* If there was any error */
1752         path = (GtkTreePath *) user_data;
1753         if (!modest_ui_actions_msg_retrieval_check (mail_op, header, msg)) {
1754                 gtk_tree_path_free (path);                      
1755                 return;
1756         }
1757
1758         /* Get the window */ 
1759         self = (ModestMsgViewWindow *) modest_mail_operation_get_source (mail_op);
1760         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self));
1761         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (self);
1762
1763         /* Update the row reference */
1764         gtk_tree_row_reference_free (priv->row_reference);
1765         priv->row_reference = gtk_tree_row_reference_new (priv->header_model, path);
1766         priv->next_row_reference = gtk_tree_row_reference_copy (priv->row_reference);
1767         select_next_valid_row (priv->header_model, &(priv->next_row_reference), TRUE);
1768         gtk_tree_path_free (path);
1769
1770         /* Mark header as read */
1771         if (!(tny_header_get_flags (header) & TNY_HEADER_FLAG_SEEN))
1772                 tny_header_set_flags (header, TNY_HEADER_FLAG_SEEN);
1773
1774         /* Set new message */
1775         modest_msg_view_set_message (MODEST_MSG_VIEW (priv->msg_view), msg);
1776         modest_msg_view_window_update_priority (self);
1777         update_window_title (MODEST_MSG_VIEW_WINDOW (self));
1778         modest_msg_view_grab_focus (MODEST_MSG_VIEW (priv->msg_view));
1779
1780         /* Set the new message uid of the window  */
1781         if (priv->msg_uid) {
1782                 g_free (priv->msg_uid);
1783                 priv->msg_uid = modest_tny_folder_get_header_unique_id (header);
1784         }
1785
1786         /* Notify the observers */
1787         g_signal_emit (G_OBJECT (self), signals[MSG_CHANGED_SIGNAL], 
1788                        0, priv->header_model, priv->row_reference);
1789
1790         /* Free new references */
1791         g_object_unref (self);
1792 }
1793
1794 TnyFolderType
1795 modest_msg_view_window_get_folder_type (ModestMsgViewWindow *window)
1796 {
1797         ModestMsgViewWindowPrivate *priv;
1798         TnyMsg *msg;
1799         TnyFolderType folder_type;
1800
1801         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1802
1803         folder_type = TNY_FOLDER_TYPE_UNKNOWN;
1804
1805         msg = modest_msg_view_get_message (MODEST_MSG_VIEW (priv->msg_view));
1806         if (msg) {
1807                 TnyFolder *folder;
1808
1809                 folder = tny_msg_get_folder (msg);
1810                 
1811                 if (folder) {
1812                         folder_type = tny_folder_get_folder_type (folder);
1813                         
1814                         if (folder_type == TNY_FOLDER_TYPE_NORMAL || folder_type == TNY_FOLDER_TYPE_UNKNOWN) {
1815                                 const gchar *fname = tny_folder_get_name (folder);
1816                                 folder_type = modest_tny_folder_guess_folder_type_from_name (fname);
1817                         }
1818
1819                         g_object_unref (folder);
1820                 }
1821                 g_object_unref (msg);
1822         }
1823
1824         return folder_type;
1825 }
1826
1827
1828 static void
1829 modest_msg_view_window_update_priority (ModestMsgViewWindow *window)
1830 {
1831         ModestMsgViewWindowPrivate *priv;
1832         TnyHeaderFlags flags = 0;
1833
1834         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
1835
1836         if (priv->header_model) {
1837                 TnyHeader *header;
1838                 GtkTreeIter iter;
1839                 GtkTreePath *path = NULL;
1840
1841                 path = gtk_tree_row_reference_get_path (priv->row_reference);
1842                 g_return_if_fail (path != NULL);
1843                 gtk_tree_model_get_iter (priv->header_model, 
1844                                          &iter, 
1845                                          gtk_tree_row_reference_get_path (priv->row_reference));
1846
1847                 gtk_tree_model_get (priv->header_model, &iter, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1848                                     &header, -1);
1849                 flags = tny_header_get_flags (header);
1850                 gtk_tree_path_free (path);
1851         }
1852
1853         modest_msg_view_set_priority (MODEST_MSG_VIEW(priv->msg_view), flags);
1854
1855 }
1856
1857 static gboolean
1858 modest_msg_view_window_window_state_event (GtkWidget *widget, GdkEventWindowState *event, gpointer userdata)
1859 {
1860         if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) {
1861                 ModestWindowPrivate *parent_priv;
1862                 ModestWindowMgr *mgr;
1863                 gboolean is_fullscreen;
1864                 GtkAction *fs_toggle_action;
1865                 gboolean active;
1866
1867                 mgr = modest_runtime_get_window_mgr ();
1868                 is_fullscreen = (modest_window_mgr_get_fullscreen_mode (mgr))?1:0;
1869
1870                 parent_priv = MODEST_WINDOW_GET_PRIVATE (widget);
1871                 
1872                 fs_toggle_action = gtk_ui_manager_get_action (parent_priv->ui_manager, "/MenuBar/ViewMenu/ViewToggleFullscreenMenu");
1873                 active = (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (fs_toggle_action)))?1:0;
1874                 if (is_fullscreen != active) {
1875                         gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (fs_toggle_action), is_fullscreen);
1876                 }
1877         }
1878
1879         return FALSE;
1880
1881 }
1882
1883 static void
1884 set_homogeneous (GtkWidget *widget,
1885                  gpointer data)
1886 {
1887         if (GTK_IS_TOOL_ITEM (widget)) {
1888                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (widget), TRUE);
1889                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (widget), TRUE);
1890         }
1891 }
1892
1893 static void
1894 modest_msg_view_window_show_toolbar (ModestWindow *self,
1895                                      gboolean show_toolbar)
1896 {
1897         ModestMsgViewWindowPrivate *priv = NULL;
1898         ModestWindowPrivate *parent_priv;
1899         GtkWidget *reply_button = NULL, *menu = NULL;
1900         GtkWidget *placeholder = NULL;
1901         gint insert_index;
1902         const gchar *action_name;
1903         GtkAction *action;
1904         
1905         parent_priv = MODEST_WINDOW_GET_PRIVATE(self);
1906         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
1907
1908         /* Set optimized view status */
1909         priv->optimized_view = !show_toolbar;
1910
1911         if (!parent_priv->toolbar) {
1912                 parent_priv->toolbar = gtk_ui_manager_get_widget (parent_priv->ui_manager, 
1913                                                                   "/ToolBar");
1914                 gtk_widget_set_no_show_all (parent_priv->toolbar, TRUE);
1915
1916                 /* Set homogeneous toolbar */
1917                 gtk_container_foreach (GTK_CONTAINER (parent_priv->toolbar), 
1918                                        set_homogeneous, NULL);
1919
1920                 priv->progress_toolitem = GTK_WIDGET (gtk_tool_item_new ());
1921                 priv->cancel_toolitem = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarCancel");
1922                 priv->next_toolitem = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageNext");
1923                 priv->prev_toolitem = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ToolbarMessageBack");
1924                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->progress_toolitem), TRUE);
1925                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (priv->progress_toolitem), TRUE);
1926                 gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (priv->cancel_toolitem), FALSE);
1927                 gtk_tool_item_set_expand (GTK_TOOL_ITEM (priv->cancel_toolitem), FALSE);
1928
1929                 /* Add ProgressBar (Transfer toolbar) */ 
1930                 priv->progress_bar = modest_progress_bar_widget_new ();
1931                 gtk_widget_set_no_show_all (priv->progress_bar, TRUE);
1932                 placeholder = gtk_ui_manager_get_widget (parent_priv->ui_manager, "/ToolBar/ProgressbarView");
1933                 insert_index = gtk_toolbar_get_item_index(GTK_TOOLBAR (parent_priv->toolbar), GTK_TOOL_ITEM(placeholder));
1934                 gtk_container_add (GTK_CONTAINER (priv->progress_toolitem), priv->progress_bar);
1935                 gtk_toolbar_insert(GTK_TOOLBAR(parent_priv->toolbar), GTK_TOOL_ITEM (priv->progress_toolitem), insert_index);
1936                 
1937                 /* Connect cancel 'clicked' signal to abort progress mode */
1938                 g_signal_connect(priv->cancel_toolitem, "clicked",
1939                                  G_CALLBACK(cancel_progressbar),
1940                                  self);
1941                 
1942                 /* Add it to the observers list */
1943                 priv->progress_widgets = g_slist_prepend(priv->progress_widgets, priv->progress_bar);
1944
1945                 /* Add to window */
1946                 hildon_window_add_toolbar (HILDON_WINDOW (self), 
1947                                            GTK_TOOLBAR (parent_priv->toolbar));
1948
1949
1950                 /* Set reply button tap and hold menu */        
1951                 reply_button = gtk_ui_manager_get_widget (parent_priv->ui_manager, 
1952                                                           "/ToolBar/ToolbarMessageReply");
1953                 menu = gtk_ui_manager_get_widget (parent_priv->ui_manager, 
1954                                                   "/ToolbarReplyCSM");
1955                 gtk_widget_tap_and_hold_setup (GTK_WIDGET (reply_button), menu, NULL, 0);
1956         }
1957
1958         if (show_toolbar) {
1959                 /* Quick hack: this prevents toolbar icons "dance" when progress bar show status is changed */ 
1960                 /* TODO: resize mode migth be GTK_RESIZE_QUEUE, in order to avoid unneccesary shows */
1961                 gtk_container_set_resize_mode (GTK_CONTAINER(parent_priv->toolbar), GTK_RESIZE_IMMEDIATE);
1962                 
1963                 gtk_widget_show (GTK_WIDGET (parent_priv->toolbar));
1964                 set_toolbar_mode (MODEST_MSG_VIEW_WINDOW(self), TOOLBAR_MODE_NORMAL);                   
1965                 
1966         } else {
1967                 gtk_widget_set_no_show_all (parent_priv->toolbar, TRUE);
1968                 gtk_widget_hide (GTK_WIDGET (parent_priv->toolbar));
1969         }
1970
1971         /* Update also the actions (to update the toggles in the
1972            menus), we have to do it manually because some other window
1973            of the same time could have changed it (remember that the
1974            toolbar fullscreen mode is shared by all the windows of the
1975            same type */
1976         if (modest_window_mgr_get_fullscreen_mode (modest_runtime_get_window_mgr ()))
1977                 action_name = "/MenuBar/ViewMenu/ViewShowToolbarMenu/ViewShowToolbarFullScreenMenu";
1978         else
1979                 action_name = "/MenuBar/ViewMenu/ViewShowToolbarMenu/ViewShowToolbarNormalScreenMenu";
1980
1981         action = gtk_ui_manager_get_action (parent_priv->ui_manager, action_name);
1982         modest_maemo_toggle_action_set_active_block_notify (GTK_TOGGLE_ACTION (action),
1983                                                             show_toolbar);
1984 }
1985
1986 static void 
1987 modest_msg_view_window_clipboard_owner_change (GtkClipboard *clipboard,
1988                                                GdkEvent *event,
1989                                                ModestMsgViewWindow *window)
1990 {
1991         if (!GTK_WIDGET_VISIBLE (window))
1992                 return;
1993
1994         modest_window_check_dimming_rules_group (MODEST_WINDOW (window), "ModestClipboardDimmingRules");
1995 }
1996
1997 gboolean 
1998 modest_msg_view_window_transfer_mode_enabled (ModestMsgViewWindow *self)
1999 {
2000         ModestMsgViewWindowPrivate *priv;
2001         
2002         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self), FALSE); 
2003         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2004
2005         return priv->current_toolbar_mode == TOOLBAR_MODE_TRANSFER;
2006 }
2007
2008 static void
2009 cancel_progressbar (GtkToolButton *toolbutton,
2010                     ModestMsgViewWindow *self)
2011 {
2012         GSList *tmp;
2013         ModestMsgViewWindowPrivate *priv;
2014         
2015         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2016
2017         /* Get operation observers and cancel its current operation */
2018         tmp = priv->progress_widgets;
2019         while (tmp) {
2020                 modest_progress_object_cancel_current_operation (MODEST_PROGRESS_OBJECT(tmp->data));
2021                 tmp=g_slist_next(tmp);
2022         }
2023 }
2024 static gboolean
2025 observers_empty (ModestMsgViewWindow *self)
2026 {
2027         GSList *tmp = NULL;
2028         ModestMsgViewWindowPrivate *priv;
2029         gboolean is_empty = TRUE;
2030         guint pending_ops = 0;
2031  
2032         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2033         tmp = priv->progress_widgets;
2034
2035         /* Check all observers */
2036         while (tmp && is_empty)  {
2037                 pending_ops = modest_progress_object_num_pending_operations (MODEST_PROGRESS_OBJECT(tmp->data));
2038                 is_empty = pending_ops == 0;
2039                 
2040                 tmp = g_slist_next(tmp);
2041         }
2042         
2043         return is_empty;
2044 }
2045
2046 static void
2047 on_account_removed (TnyAccountStore *account_store, 
2048                     TnyAccount *account,
2049                     gpointer user_data)
2050 {
2051         /* Do nothing if it's a transport account, because we only
2052            show the messages of a store account */
2053         if (tny_account_get_account_type(account) == TNY_ACCOUNT_TYPE_STORE) {
2054                 const gchar *parent_acc = NULL;
2055                 const gchar *our_acc = NULL;
2056
2057                 our_acc = modest_window_get_active_account (MODEST_WINDOW (user_data));
2058                 parent_acc = modest_tny_account_get_parent_modest_account_name_for_server_account (account);
2059
2060                 /* Close this window if I'm showing a message of the removed account */
2061                 if (strcmp (parent_acc, our_acc) == 0)
2062                         modest_ui_actions_on_close_window (NULL, MODEST_WINDOW (user_data));
2063         }
2064 }
2065
2066 static void
2067 on_queue_changed (ModestMailOperationQueue *queue,
2068                   ModestMailOperation *mail_op,
2069                   ModestMailOperationQueueNotification type,
2070                   ModestMsgViewWindow *self)
2071 {
2072         GSList *tmp;
2073         ModestMsgViewWindowPrivate *priv;
2074         ModestMailOperationTypeOperation op_type;
2075         ModestToolBarModes mode;
2076         
2077         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (self));
2078         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE(self);
2079
2080         /* If this operations was created by another window, do nothing */
2081         if (!modest_mail_operation_is_mine (mail_op, G_OBJECT(self))) 
2082             return;
2083
2084         /* Get toolbar mode from operation id*/
2085         op_type = modest_mail_operation_get_type_operation (mail_op);
2086         switch (op_type) {
2087 /*      case MODEST_MAIL_OPERATION_TYPE_SEND: */
2088         case MODEST_MAIL_OPERATION_TYPE_RECEIVE:
2089         case MODEST_MAIL_OPERATION_TYPE_OPEN:
2090                 mode = TOOLBAR_MODE_TRANSFER;
2091                 break;
2092         default:
2093                 mode = TOOLBAR_MODE_NORMAL;
2094                 
2095         }
2096                 
2097         /* Add operation observers and change toolbar if neccessary*/
2098         tmp = priv->progress_widgets;
2099         switch (type) {
2100         case MODEST_MAIL_OPERATION_QUEUE_OPERATION_ADDED:
2101                 if (mode == TOOLBAR_MODE_TRANSFER) {
2102                         /* Enable transfer toolbar mode */
2103                         set_toolbar_transfer_mode(self);
2104                         while (tmp) {
2105                                 modest_progress_object_add_operation (MODEST_PROGRESS_OBJECT (tmp->data),
2106                                                                       mail_op);
2107                                 tmp = g_slist_next (tmp);
2108                         }
2109                         
2110                 }
2111                 break;
2112         case MODEST_MAIL_OPERATION_QUEUE_OPERATION_REMOVED:
2113                 if (mode == TOOLBAR_MODE_TRANSFER) {
2114                         while (tmp) {
2115                                 modest_progress_object_remove_operation (MODEST_PROGRESS_OBJECT (tmp->data),
2116                                                                  mail_op);
2117                                 tmp = g_slist_next (tmp);
2118                                 
2119                         }
2120
2121                         /* If no more operations are being observed, NORMAL mode is enabled again */
2122                         if (observers_empty (self)) {
2123                                 set_toolbar_mode (self, TOOLBAR_MODE_NORMAL);
2124                         }
2125                 }
2126                 break;
2127         }
2128 }
2129
2130 GList *
2131 modest_msg_view_window_get_attachments (ModestMsgViewWindow *win) 
2132 {
2133         ModestMsgViewWindowPrivate *priv;
2134         GList *selected_attachments = NULL;
2135         
2136         g_return_val_if_fail (MODEST_IS_MSG_VIEW_WINDOW (win), NULL);
2137         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (win);
2138
2139         selected_attachments = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2140         
2141         return selected_attachments;
2142 }
2143
2144 void
2145 modest_msg_view_window_view_attachment (ModestMsgViewWindow *window, TnyMimePart *mime_part)
2146 {
2147         ModestMsgViewWindowPrivate *priv;
2148         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
2149         g_return_if_fail (TNY_IS_MIME_PART (mime_part) || (mime_part == NULL));
2150
2151         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2152
2153         if (mime_part == NULL) {
2154                 gboolean error = FALSE;
2155                 GList *selected_attachments = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2156                 if (selected_attachments == NULL) {
2157                         error = TRUE;
2158                 } else if (g_list_length (selected_attachments) > 1) {
2159                         hildon_banner_show_information (NULL, NULL, _("mcen_ib_unable_to_display_more"));
2160                         error = TRUE;
2161                 } else {
2162                         mime_part = (TnyMimePart *) selected_attachments->data;
2163                         g_object_ref (mime_part);
2164                 }
2165                 g_list_foreach (selected_attachments, (GFunc) g_object_unref, NULL);
2166                 g_list_free (selected_attachments);
2167
2168                 if (error)
2169                         return;
2170         } else {
2171                 g_object_ref (mime_part);
2172         }
2173
2174         if (tny_mime_part_is_purged (mime_part)) {
2175                 g_object_unref (mime_part);
2176                 return;
2177         }
2178
2179         if (!TNY_IS_MSG (mime_part)) {
2180                 gchar *filepath = NULL;
2181                 const gchar *att_filename = tny_mime_part_get_filename (mime_part);
2182                 TnyFsStream *temp_stream = NULL;
2183
2184                 temp_stream = modest_maemo_utils_create_temp_stream (att_filename, &filepath);
2185                 
2186                 if (temp_stream) {
2187                         const gchar *content_type;
2188                         content_type = tny_mime_part_get_content_type (mime_part);
2189                         tny_mime_part_decode_to_stream (mime_part, TNY_STREAM (temp_stream));
2190                         
2191                         modest_platform_activate_file (filepath, content_type);
2192                         g_object_unref (temp_stream);
2193                         g_free (filepath);
2194                         /* NOTE: files in the temporary area will be automatically
2195                          * cleaned after some time if they are no longer in use */
2196                 }
2197         } else {
2198                 /* message attachment */
2199                 TnyHeader *header = NULL;
2200                 ModestWindowMgr *mgr;
2201                 ModestWindow *msg_win = NULL;
2202                 gboolean found;
2203                 
2204                 header = tny_msg_get_header (TNY_MSG (mime_part));
2205                 mgr = modest_runtime_get_window_mgr ();         
2206                 found = modest_window_mgr_find_registered_header (mgr, header, &msg_win);
2207
2208                 if (found) {
2209                         if (msg_win)                            /* there is already a window for this uid; top it */
2210                                 gtk_window_present (GTK_WINDOW(msg_win));
2211                         else 
2212                                 /* if it's found, but there is no msg_win, it's probably in the process of being created;
2213                                  * thus, we don't do anything */
2214                                 g_warning ("window for is already being created");
2215                 } else { 
2216                         /* it's not found, so create a new window for it */
2217                         modest_window_mgr_register_header (mgr, header); /* register the uid before building the window */
2218                         gchar *account = g_strdup (modest_window_get_active_account (MODEST_WINDOW (window)));
2219                         if (!account)
2220                                 account = modest_account_mgr_get_default_account (modest_runtime_get_account_mgr ());
2221                         msg_win = modest_msg_view_window_new_for_attachment (TNY_MSG (mime_part), account, NULL);
2222                         modest_window_set_zoom (MODEST_WINDOW (msg_win), 
2223                                                 modest_window_get_zoom (MODEST_WINDOW (window)));
2224                         modest_window_mgr_register_window (mgr, msg_win);
2225                         gtk_window_set_transient_for (GTK_WINDOW (msg_win), GTK_WINDOW (window));
2226                         gtk_widget_show_all (GTK_WIDGET (msg_win));
2227                 }
2228         }
2229         g_object_unref (mime_part);
2230 }
2231
2232 typedef struct
2233 {
2234         gchar *filename;
2235         TnyMimePart *part;
2236 } SaveMimePartPair;
2237
2238 typedef struct
2239 {
2240         GList *pairs;
2241         GtkWidget *banner;
2242         gboolean result;
2243 } SaveMimePartInfo;
2244
2245 static void save_mime_part_info_free (SaveMimePartInfo *info, gboolean with_struct);
2246 static gboolean idle_save_mime_part_show_result (SaveMimePartInfo *info);
2247 static gpointer save_mime_part_to_file (SaveMimePartInfo *info);
2248 static void save_mime_parts_to_file_with_checks (SaveMimePartInfo *info);
2249
2250 static void 
2251 save_mime_part_info_free (SaveMimePartInfo *info, gboolean with_struct)
2252 {
2253         
2254         GList *node;
2255         for (node = info->pairs; node != NULL; node = g_list_next (node)) {
2256                 SaveMimePartPair *pair = (SaveMimePartPair *) node->data;
2257                 g_free (pair->filename);
2258                 g_object_unref (pair->part);
2259                 g_slice_free (SaveMimePartPair, pair);
2260         }
2261         g_list_free (info->pairs);
2262         info->pairs = NULL;
2263         if (with_struct) {
2264                 gtk_widget_destroy (info->banner);
2265                 g_object_unref (info->banner);
2266                 g_slice_free (SaveMimePartInfo, info);
2267         }
2268 }
2269
2270 static gboolean
2271 idle_save_mime_part_show_result (SaveMimePartInfo *info)
2272 {
2273         if (info->pairs != NULL) {
2274                 save_mime_part_to_file (info);
2275         } else {
2276                 gboolean result;
2277                 result = info->result;
2278
2279                 /* This is a GDK lock because we are an idle callback and
2280                  * hildon_banner_show_information is or does Gtk+ code */
2281
2282                 gdk_threads_enter (); /* CHECKED */
2283                 save_mime_part_info_free (info, TRUE);
2284                 if (result) {
2285                         hildon_banner_show_information (NULL, NULL, _CS("sfil_ib_saved"));
2286                 } else {
2287                         hildon_banner_show_information (NULL, NULL, _("mail_ib_file_operation_failed"));
2288                 }
2289                 gdk_threads_leave (); /* CHECKED */
2290         }
2291
2292         return FALSE;
2293 }
2294
2295 static gpointer
2296 save_mime_part_to_file (SaveMimePartInfo *info)
2297 {
2298         GnomeVFSResult result;
2299         GnomeVFSHandle *handle;
2300         TnyStream *stream;
2301         SaveMimePartPair *pair = (SaveMimePartPair *) info->pairs->data;
2302
2303         result = gnome_vfs_create (&handle, pair->filename, GNOME_VFS_OPEN_WRITE, FALSE, 0777);
2304         if (result == GNOME_VFS_OK) {
2305                 stream = tny_vfs_stream_new (handle);
2306                 tny_mime_part_decode_to_stream (pair->part, stream);
2307                 g_object_unref (G_OBJECT (stream));
2308                 g_object_unref (pair->part);
2309                 g_slice_free (SaveMimePartPair, pair);
2310                 info->pairs = g_list_delete_link (info->pairs, info->pairs);
2311                 info->result = TRUE;
2312         } else {
2313                 save_mime_part_info_free (info, FALSE);
2314                 info->result = FALSE;
2315         }
2316
2317         g_idle_add ((GSourceFunc) idle_save_mime_part_show_result, info);
2318         return NULL;
2319 }
2320
2321 static void
2322 save_mime_parts_to_file_with_checks (SaveMimePartInfo *info)
2323 {
2324         gboolean is_ok = TRUE;
2325         gint replaced_files = 0;
2326         const GList *files = info->pairs;
2327         const GList *iter;
2328
2329         for (iter = files; (iter != NULL) && (replaced_files < 2); iter = g_list_next(iter)) {
2330                 SaveMimePartPair *pair = iter->data;
2331                 if (modest_maemo_utils_file_exists (pair->filename)) {
2332                         replaced_files++;
2333                 }
2334         }
2335         if (replaced_files) {
2336                 GtkWidget *confirm_overwrite_dialog;
2337                 const gchar *message = (replaced_files == 1) ?
2338                         _FM("docm_nc_replace_file") : _FM("docm_nc_replace_multiple");
2339                 confirm_overwrite_dialog = hildon_note_new_confirmation (NULL, message);
2340                 if (gtk_dialog_run (GTK_DIALOG (confirm_overwrite_dialog)) != GTK_RESPONSE_OK) {
2341                         is_ok = FALSE;
2342                 }
2343                 gtk_widget_destroy (confirm_overwrite_dialog);
2344         }
2345
2346         if (!is_ok) {
2347                 save_mime_part_info_free (info, TRUE);
2348         } else {
2349                 g_thread_create ((GThreadFunc)save_mime_part_to_file, info, FALSE, NULL);
2350         }
2351
2352 }
2353
2354
2355 void
2356 modest_msg_view_window_save_attachments (ModestMsgViewWindow *window, GList *mime_parts)
2357 {
2358         gboolean clean_list = FALSE;
2359         ModestMsgViewWindowPrivate *priv;
2360         GList *files_to_save = NULL;
2361         GtkWidget *save_dialog = NULL;
2362         gchar *folder = NULL;
2363         gboolean canceled = FALSE;
2364         const gchar *filename = NULL;
2365         gchar *save_multiple_str = NULL;
2366
2367         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
2368         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2369
2370         if (mime_parts == NULL) {
2371                 mime_parts = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2372                 if (mime_parts == NULL)
2373                         return;
2374                 clean_list = TRUE;
2375         }
2376
2377         /* prepare dialog */
2378         if (mime_parts->next == NULL) {
2379                 /* only one attachment selected */
2380                 TnyMimePart *mime_part = (TnyMimePart *) mime_parts->data;
2381                 if (!TNY_IS_MSG (mime_part) && tny_mime_part_is_attachment (mime_part)) {
2382                         filename = tny_mime_part_get_filename (mime_part);
2383                 } else {
2384                         g_warning ("Tried to save a non-file attachment");
2385                         canceled = TRUE;
2386                 }
2387         } else {
2388                 save_multiple_str = g_strdup_printf (_FM("sfil_va_number_of_objects_attachments"), 
2389                                                      g_list_length (mime_parts));
2390         }
2391         
2392         save_dialog = hildon_file_chooser_dialog_new (GTK_WINDOW (window), 
2393                                                       GTK_FILE_CHOOSER_ACTION_SAVE);
2394
2395         /* set folder */
2396         folder = g_build_filename (g_get_home_dir (), DEFAULT_FOLDER, NULL);
2397         gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (save_dialog), folder);
2398         g_free (folder);
2399
2400         /* set filename */
2401         if (filename != NULL)
2402                 gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (save_dialog), 
2403                                                    filename);
2404
2405         /* if multiple, set multiple string */
2406         if (save_multiple_str) {
2407                 g_object_set (G_OBJECT (save_dialog), "save-multiple", save_multiple_str, NULL);
2408         }
2409                 
2410         /* show dialog */
2411         if (gtk_dialog_run (GTK_DIALOG (save_dialog)) == GTK_RESPONSE_OK) {
2412                 gchar *chooser_uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (save_dialog));
2413
2414                 if (!modest_maemo_utils_folder_writable (chooser_uri)) {
2415                         hildon_banner_show_information 
2416                                 (NULL, NULL, dgettext("hildon-fm", "sfil_ib_readonly_location"));
2417                 } else {
2418                         GList *node = NULL;
2419
2420                         for (node = mime_parts; node != NULL; node = g_list_next (node)) {
2421                                 TnyMimePart *mime_part = (TnyMimePart *) node->data;
2422                                 
2423                                 if (tny_mime_part_is_attachment (mime_part)) {
2424                                         SaveMimePartPair *pair;
2425
2426                                         if ((mime_parts->next != NULL) &&
2427                                             (tny_mime_part_get_filename (mime_part) == NULL))
2428                                                 continue;
2429                                         
2430                                         pair = g_slice_new0 (SaveMimePartPair);
2431                                         if (mime_parts->next == NULL) {
2432                                                 pair->filename = g_strdup (chooser_uri);
2433                                         } else {
2434                                                 pair->filename = 
2435                                                         g_build_filename (chooser_uri,
2436                                                                           tny_mime_part_get_filename (mime_part), NULL);
2437                                         }
2438                                         pair->part = g_object_ref (mime_part);
2439                                         files_to_save = g_list_prepend (files_to_save, pair);
2440                                 }
2441                         }
2442                 }
2443                 g_free (chooser_uri);
2444         }
2445
2446         gtk_widget_destroy (save_dialog);
2447
2448         if (clean_list) {
2449                 g_list_foreach (mime_parts, (GFunc) g_object_unref, NULL);
2450                 g_list_free (mime_parts);
2451         }
2452
2453         if (files_to_save != NULL) {
2454                 SaveMimePartInfo *info = g_slice_new0 (SaveMimePartInfo);
2455                 GtkWidget *banner = hildon_banner_show_animation (NULL, NULL, 
2456                                                                   _CS("sfil_ib_saving"));
2457                 info->pairs = files_to_save;
2458                 info->banner = banner;
2459                 info->result = TRUE;
2460                 g_object_ref (banner);
2461                 save_mime_parts_to_file_with_checks (info);
2462         }
2463 }
2464
2465 static gboolean
2466 show_remove_attachment_information (gpointer userdata)
2467 {
2468         ModestMsgViewWindow *window = (ModestMsgViewWindow *) userdata;
2469         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2470
2471         if (priv->remove_attachment_banner != NULL) {
2472                 gtk_widget_destroy (priv->remove_attachment_banner);
2473                 g_object_unref (priv->remove_attachment_banner);
2474         }
2475
2476         priv->remove_attachment_banner = g_object_ref (
2477                 hildon_banner_show_animation (NULL, NULL, _("mcen_ib_removing_attachment")));
2478
2479         return FALSE;
2480 }
2481
2482 void
2483 modest_msg_view_window_remove_attachments (ModestMsgViewWindow *window, gboolean get_all)
2484 {
2485         ModestMsgViewWindowPrivate *priv;
2486         GList *mime_parts = NULL, *node;
2487         gchar *confirmation_message;
2488         gint response;
2489         gint n_attachments;
2490         TnyMsg *msg;
2491 /*      TnyFolder *folder; */
2492
2493         g_return_if_fail (MODEST_IS_MSG_VIEW_WINDOW (window));
2494         priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2495
2496         if (get_all)
2497                 mime_parts = modest_msg_view_get_attachments (MODEST_MSG_VIEW (priv->msg_view));
2498         else
2499                 mime_parts = modest_msg_view_get_selected_attachments (MODEST_MSG_VIEW (priv->msg_view));
2500                 
2501         /* Remove already purged messages from mime parts list */
2502         node = mime_parts;
2503         while (node != NULL) {
2504                 TnyMimePart *part = TNY_MIME_PART (node->data);
2505                 if (tny_mime_part_is_purged (part)) {
2506                         GList *deleted_node = node;
2507                         node = g_list_next (node);
2508                         g_object_unref (part);
2509                         mime_parts = g_list_delete_link (mime_parts, deleted_node);
2510                 } else {
2511                         node = g_list_next (node);
2512                 }
2513         }
2514
2515         if (mime_parts == NULL)
2516                 return;
2517
2518         n_attachments = g_list_length (mime_parts);
2519         if (n_attachments == 1) {
2520                 const gchar *filename;
2521
2522                 if (TNY_IS_MSG (mime_parts->data)) {
2523                         TnyHeader *header;
2524                         header = tny_msg_get_header (TNY_MSG (mime_parts->data));
2525                         filename = tny_header_get_subject (header);
2526                         g_object_unref (header);
2527                         if (filename == NULL)
2528                                 filename = _("mail_va_no_subject");
2529                 } else {
2530                         filename = tny_mime_part_get_filename (TNY_MIME_PART (mime_parts->data));
2531                 }
2532                 confirmation_message = g_strdup_printf (_("mcen_nc_purge_file_text"), filename);
2533         } else {
2534                 confirmation_message = g_strdup_printf (ngettext("mcen_nc_purge_file_text", 
2535                                                                  "mcen_nc_purge_files_text", 
2536                                                                  n_attachments), n_attachments);
2537         }
2538         response = modest_platform_run_confirmation_dialog (GTK_WINDOW (window),
2539                                                             confirmation_message);
2540         g_free (confirmation_message);
2541
2542         if (response != GTK_RESPONSE_OK)
2543                 return;
2544
2545         priv->purge_timeout = g_timeout_add (2000, show_remove_attachment_information, window);
2546 /*      folder = tny_msg_get_folder (msg); */
2547 /*      tny_msg_uncache_attachments (msg); */
2548 /*      tny_folder_refresh (folder, NULL); */
2549 /*      g_object_unref (folder); */
2550         
2551         for (node = mime_parts; node != NULL; node = g_list_next (node)) {
2552                 tny_mime_part_set_purged (TNY_MIME_PART (node->data));
2553 /*              modest_msg_view_remove_attachment (MODEST_MSG_VIEW (priv->msg_view), node->data); */
2554         }
2555
2556         msg = modest_msg_view_get_message (MODEST_MSG_VIEW (priv->msg_view));
2557         modest_msg_view_set_message (MODEST_MSG_VIEW (priv->msg_view), NULL);
2558         tny_msg_rewrite_cache (msg);
2559         modest_msg_view_set_message (MODEST_MSG_VIEW (priv->msg_view), msg);
2560
2561         g_list_foreach (mime_parts, (GFunc) g_object_unref, NULL);
2562         g_list_free (mime_parts);
2563
2564         if (priv->purge_timeout > 0) {
2565                 g_source_remove (priv->purge_timeout);
2566                 priv->purge_timeout = 0;
2567         }
2568
2569         if (priv->remove_attachment_banner) {
2570                 gtk_widget_destroy (priv->remove_attachment_banner);
2571                 g_object_unref (priv->remove_attachment_banner);
2572                 priv->remove_attachment_banner = NULL;
2573         }
2574
2575
2576 }
2577
2578
2579 static void
2580 update_window_title (ModestMsgViewWindow *window)
2581 {
2582         ModestMsgViewWindowPrivate *priv = MODEST_MSG_VIEW_WINDOW_GET_PRIVATE (window);
2583         TnyMsg *msg = NULL;
2584         TnyHeader *header = NULL;
2585         const gchar *subject = NULL;
2586
2587         msg = modest_msg_view_get_message (MODEST_MSG_VIEW (priv->msg_view));
2588         if (msg != NULL) {
2589                 header = tny_msg_get_header (msg);
2590                 subject = tny_header_get_subject (header);
2591                 g_object_unref (msg);
2592         }
2593
2594         if ((subject == NULL)||(subject[0] == '\0'))
2595                 subject = _("mail_va_no_subject");
2596
2597         gtk_window_set_title (GTK_WINDOW (window), subject);
2598 }
2599
2600 static void on_move_focus (ModestMsgViewWindow *window,
2601                            GtkDirectionType direction,
2602                            gpointer userdata)
2603 {
2604         GtkWidget *current_focus = NULL;
2605
2606         current_focus = gtk_window_get_focus (GTK_WINDOW (window));
2607         if ((current_focus != NULL) &&
2608             MODEST_IS_ATTACHMENTS_VIEW (current_focus)) {
2609                 g_signal_stop_emission_by_name (G_OBJECT (window), "move-focus");
2610         }
2611 }
2612