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