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