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