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