ab6e035c988b0f6920d615b3d60a00ee8228e01f
[modest] / src / modest-ui.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
30 #include <gtk/gtk.h>
31 #include <glade/glade.h>
32 #include <glib/gi18n.h>
33 #include <string.h>
34
35 #ifdef HAVE_CONFIG_H
36 #include <config.h>
37 #endif /*HAVE_CONFIG_H*/
38
39 #include <glib/gi18n.h>
40 #include <gtk/gtkuimanager.h>
41 #include <tny-error.h>
42 #include <tny-simple-list.h>
43 #include "modest-ui.h"
44 #include "modest-tny-account-store.h"
45 #include "modest-account-mgr.h"
46 #include "modest-widget-factory.h"
47 #include "modest-tny-platform-factory.h"
48
49 #include "modest-edit-msg-window.h"
50 #include "modest-account-view-window.h"
51 #include "modest-icon-names.h"
52 #include "modest-main-window.h"
53
54 typedef struct _ModestUIPrivate ModestUIPrivate;
55 struct _ModestUIPrivate {
56         ModestWidgetFactory  *widget_factory;   
57         TnyAccountStore      *account_store;
58         GtkWidget            *main_window;
59 };
60
61 #define MODEST_UI_GET_PRIVATE(o)      (G_TYPE_INSTANCE_GET_PRIVATE((o), \
62                                        MODEST_TYPE_UI, \
63                                        ModestUIPrivate))
64
65 typedef struct _GetMsgAsyncHelper {
66         ModestUIPrivate *priv;
67         TnyIterator *iter;
68         GFunc func;
69         gpointer user_data;
70 } GetMsgAsyncHelper;
71
72 typedef enum _ReplyForwardAction {
73         ACTION_REPLY,
74         ACTION_REPLY_TO_ALL,
75         ACTION_FORWARD
76 } ReplyForwardAction;
77
78 typedef struct _ReplyForwardHelper {
79         guint reply_forward_type;
80         ReplyForwardAction action;
81         gchar *from;
82 } ReplyForwardHelper;
83
84 /* globals */
85 static GObjectClass *parent_class = NULL;
86
87 /* 'private'/'protected' functions */
88 static void   modest_ui_class_init     (ModestUIClass *klass);
89 static void   modest_ui_init           (ModestUI *obj);
90 static void   modest_ui_finalize       (GObject *obj);
91
92 static void   register_stock_icons        ();
93 static void   connect_signals             (ModestUI *self);
94 static void   connect_main_window_signals (ModestUI *self);
95 static GtkUIManager *create_ui_manager ();
96
97 static void   reply_forward_func (gpointer data, gpointer user_data);
98 static void   read_msg_func      (gpointer data, gpointer user_data);
99 static void   get_msg_cb         (TnyFolder *folder, 
100                                   TnyMsg *msg, 
101                                   GError **err, 
102                                   gpointer user_data);
103
104 static void   reply_forward      (GtkWidget *widget,
105                                   ReplyForwardAction action,
106                                   ModestUIPrivate *priv);
107
108 /* Menu & toolbar actions */
109 static void     modest_ui_actions_on_about         (GtkWidget *widget, ModestUIPrivate *priv);
110
111 static void     modest_ui_actions_on_delete        (GtkWidget *widget, ModestUIPrivate *priv);
112
113 static void     modest_ui_actions_on_quit          (GtkWidget *widget, ModestUIPrivate *priv);
114
115 static void     modest_ui_actions_on_accounts      (GtkWidget *widget, ModestUIPrivate *priv);
116
117 static void     modest_ui_actions_on_new_msg       (GtkWidget *widget, ModestUIPrivate *priv);
118
119 static void     modest_ui_actions_on_reply         (GtkWidget *widget, ModestUIPrivate *priv);
120
121 static void     modest_ui_actions_on_forward       (GtkWidget *widget, ModestUIPrivate *priv);
122
123 static void     modest_ui_actions_on_reply_all     (GtkWidget *widget, ModestUIPrivate *priv);
124
125 static void     modest_ui_actions_on_next          (GtkWidget *widget, ModestUIPrivate *priv);
126
127 /* Widget actions */
128 static void     modest_ui_actions_on_header_selected          (ModestHeaderView *folder_view, 
129                                                                TnyHeader *header,
130                                                                ModestUIPrivate *priv);
131
132 static void     modest_ui_actions_on_folder_selection_changed (ModestFolderView *folder_view,
133                                                                TnyFolder *folder, 
134                                                                gboolean selected,
135                                                                ModestUIPrivate *priv);
136
137 static void     modest_ui_actions_on_password_requested       (ModestTnyAccountStore *account_store, 
138                                                                const gchar* account_name,
139                                                                gchar **password, 
140                                                                gboolean *cancel, 
141                                                                gboolean *remember, 
142                                                                ModestUIPrivate *priv);
143
144 static void     modest_ui_actions_on_connection_changed       (TnyDevice *device, 
145                                                                gboolean online,
146                                                                ModestUIPrivate *priv);
147
148 static void     modest_ui_actions_on_online_toggle_toggled    (GtkToggleButton *toggle,
149                                                                ModestUIPrivate *priv);
150
151 static void     modest_ui_actions_on_item_not_found           (ModestHeaderView *header_view,
152                                                                ModestItemType type,
153                                                                ModestUIPrivate *priv);
154
155 static void     modest_ui_actions_on_header_status_update     (ModestHeaderView *header_view, 
156                                                                const gchar *msg,
157                                                                gint num, 
158                                                                gint total, 
159                                                                ModestUIPrivate *priv);
160
161 static void     modest_ui_actions_on_msg_link_hover           (ModestMsgView *msgview, 
162                                                                const gchar* link,
163                                                                ModestUIPrivate *priv);
164
165
166 static void     modest_ui_actions_on_msg_link_clicked         (ModestMsgView *msgview, 
167                                                                const gchar* link,
168                                                                ModestUIPrivate *priv);
169
170 static void     modest_ui_actions_on_msg_attachment_clicked   (ModestMsgView *msgview, 
171                                                                int index,
172                                                                ModestUIPrivate *priv);
173
174
175 /* Action entries */
176 static const GtkActionEntry modest_action_entries [] = {
177
178         /* Toplevel */
179         { "File", NULL, N_("_File") },
180         { "Edit", NULL, N_("_Edit") },
181         { "Actions", NULL, N_("_Actions") },
182         { "Options", NULL, N_("_Options") },
183         { "Help", NULL, N_("_Help") },
184
185         /* FILE menu */
186         { "FileNew",    GTK_STOCK_NEW,     N_("_New"),     "<CTRL>N", N_("Compose new message"),  G_CALLBACK (modest_ui_actions_on_new_msg) },
187         { "FileOpen",   GTK_STOCK_OPEN,    N_("_Open"),    "<CTRL>O", N_("Open a message"),       NULL },
188         { "FileSave",   GTK_STOCK_SAVE,    N_("_Save"),    "<CTRL>S", N_("Save a message"),       NULL },
189         { "FileSaveAs", GTK_STOCK_SAVE_AS, N_("Save _As"), NULL,      N_("Save a message as"),    NULL },
190         { "FileQuit",   GTK_STOCK_QUIT,    N_("_Quit"),    "<CTRL>Q", N_("Exit the application"), G_CALLBACK (modest_ui_actions_on_quit) },
191
192         /* EDIT menu */
193         { "EditUndo",        GTK_STOCK_UNDO,   N_("_Undo"), "<CTRL>Z",        N_("Undo last action"),  NULL },
194         { "EditRedo",        GTK_STOCK_REDO,   N_("_Redo"), "<shift><CTRL>Z", N_("Redo previous action"),  NULL },
195         { "EditCut",         GTK_STOCK_CUT,    N_("Cut"),   "<CTRL>X",        N_("_Cut"), NULL   },
196         { "EditCopy",        GTK_STOCK_COPY,   N_("Copy"),  "<CTRL>C",        N_("Copy"), NULL },
197         { "EditPaste",       GTK_STOCK_PASTE,  N_("Paste"), "<CTRL>V",        N_("Paste"), NULL },
198         { "EditDelete",      GTK_STOCK_DELETE, N_("_Delete"),      "<CTRL>Q",         N_("Delete"), NULL },
199         { "EditSelectAll",   NULL,             N_("Select all"),   "<CTRL>A",         N_("Select all"), NULL },
200         { "EditDeselectAll", NULL,             N_("Deselect all"), "<Shift><CTRL>A",  N_("Deselect all"), NULL },
201
202         /* ACTIONS menu */
203         { "ActionsNew",         MODEST_STOCK_MAIL_SEND, N_("_New Message"),   NULL, N_("Compose a new message"), G_CALLBACK (modest_ui_actions_on_new_msg) },
204         { "ActionsReply",       MODEST_STOCK_REPLY, N_("_Reply"),         NULL, N_("Reply to a message"), G_CALLBACK (modest_ui_actions_on_reply) },
205         { "ActionsReplyAll",    MODEST_STOCK_REPLY_ALL, N_("Reply to all"),   NULL, N_("Reply to all"), G_CALLBACK (modest_ui_actions_on_reply_all) },
206         { "ActionsForward",     MODEST_STOCK_FORWARD, N_("_Forward"),       NULL, N_("Forward a message"), G_CALLBACK (modest_ui_actions_on_forward) },
207         { "ActionsBounce",      NULL, N_("_Bounce"),        NULL, N_("Bounce a message"),          NULL },
208         { "ActionsSendReceive", MODEST_STOCK_SEND_RECEIVE, N_("Send/Receive"),   NULL, N_("Send and receive messages"), NULL },
209         { "ActionsDelete",      MODEST_STOCK_DELETE, N_("Delete message"), NULL, N_("Delete messages"), G_CALLBACK (modest_ui_actions_on_delete) },
210
211         /* GOTO menu */
212         { "GotoPrevious", MODEST_STOCK_PREV, N_("Previous"), NULL, N_("Go to previous message"), NULL },
213         { "GotoNext",     MODEST_STOCK_NEXT, N_("Next"),     NULL, N_("Go to next message"), G_CALLBACK (modest_ui_actions_on_next) },
214
215         /* OPTIONS menu */
216         { "OptionsAccounts",  NULL, N_("_Accounts"), NULL, N_("Manage accounts"), G_CALLBACK (modest_ui_actions_on_accounts) },
217         { "OptionsContacts",  NULL, N_("_Contacts"), NULL, N_("Manage contacts"), NULL },
218         
219         /* HELP menu */
220         { "HelpAbout", GTK_STOCK_ABOUT, N_("About"), NULL, N_("About Modest"), G_CALLBACK (modest_ui_actions_on_about) },
221 };
222
223 GType
224 modest_ui_get_type (void)
225 {
226         static GType my_type = 0;
227         if (!my_type) {
228                 static const GTypeInfo my_info = {
229                         sizeof(ModestUIClass),
230                         NULL,           /* base init */
231                         NULL,           /* base finalize */
232                         (GClassInitFunc) modest_ui_class_init,
233                         NULL,           /* class finalize */
234                         NULL,           /* class data */
235                         sizeof(ModestUI),
236                         1,              /* n_preallocs */
237                         (GInstanceInitFunc) modest_ui_init,
238                         NULL
239                 };
240                 my_type = g_type_register_static (G_TYPE_OBJECT,
241                                                   "ModestUI",
242                                                   &my_info, 0);
243         }
244         return my_type;
245 }
246
247
248 static void
249 modest_ui_class_init (ModestUIClass *klass)
250 {
251         GObjectClass *gobject_class;
252         gobject_class = (GObjectClass*) klass;
253
254         parent_class            = g_type_class_peek_parent (klass);
255         gobject_class->finalize = modest_ui_finalize;
256
257         g_type_class_add_private (gobject_class, sizeof(ModestUIPrivate));
258
259 }
260
261
262 static void
263 modest_ui_init (ModestUI *obj)
264 {
265         ModestUIPrivate *priv;
266
267         priv = MODEST_UI_GET_PRIVATE(obj);
268
269         priv->widget_factory = NULL;
270         priv->main_window    = NULL;
271         priv->account_store  = NULL;
272 }
273
274
275 static void
276 modest_ui_finalize (GObject *obj)
277 {
278         
279         ModestUIPrivate *priv = MODEST_UI_GET_PRIVATE(obj);
280         
281         if (priv->widget_factory) {
282                 g_object_unref (G_OBJECT(priv->widget_factory));
283                 priv->widget_factory = NULL;
284         }
285         
286         G_OBJECT_CLASS(parent_class)->finalize (obj);
287 }
288
289
290 ModestUI*
291 modest_ui_new (TnyAccountStore *account_store)
292 {
293         GObject *obj;
294         ModestUIPrivate *priv;
295         TnyPlatformFactory *fact;
296         ModestAccountMgr *account_mgr;
297
298         obj  = g_object_new(MODEST_TYPE_UI, NULL);
299         priv = MODEST_UI_GET_PRIVATE(obj);
300
301         /* Get the platform-dependent instances */
302         fact = modest_tny_platform_factory_get_instance ();
303         
304         priv->account_store = account_store;
305
306         account_mgr = modest_tny_platform_factory_get_modest_account_mgr_instance (fact);
307         if (!account_mgr) {
308                 g_printerr ("modest: could not create ModestAccountMgr instance\n");
309                 g_object_unref (obj);
310                 return NULL;
311         }
312
313         priv->widget_factory = modest_widget_factory_new ();
314         if (!priv->widget_factory) {
315                 g_printerr ("modest: could not initialize widget factory\n");
316                 return NULL;
317         }
318
319         /* Connect signals */
320         connect_signals (MODEST_UI (obj));
321                 
322         return MODEST_UI(obj);
323 }
324
325 static gboolean
326 on_main_window_destroy (GtkObject *widget, ModestUI *self)
327 {
328         /* FIXME: check if there any viewer/editing windows opened */
329         gtk_main_quit ();
330         return FALSE;
331 }
332
333
334 GtkWidget*
335 modest_ui_main_window (ModestUI *self)
336 {
337         ModestUIPrivate *priv;
338         GtkUIManager *ui_manager;
339
340         g_return_val_if_fail (self, NULL);
341         priv = MODEST_UI_GET_PRIVATE(self);
342
343         if (!priv->main_window) {
344                 TnyDevice *device;
345
346                 /* Register our own icons as stock icons in order to
347                    use them with the UI manager */
348                 register_stock_icons ();
349
350                 /* Create UI manager */ 
351                 ui_manager = create_ui_manager (self);
352
353                 /* Create main window */
354                 priv->main_window = modest_main_window_new (priv->widget_factory, ui_manager);
355                 g_signal_connect (G_OBJECT(priv->main_window), "destroy",
356                                   G_CALLBACK(on_main_window_destroy), self);
357
358                 /* Connect Main Window signals */
359                 connect_main_window_signals (self);
360
361                 g_object_unref (G_OBJECT (ui_manager));
362         }
363                 
364         if (!priv->main_window)
365                 g_printerr ("modest: could not create main window\n");
366         
367         return priv->main_window;
368 }
369
370 static GtkUIManager *
371 create_ui_manager (ModestUI *self)
372 {
373         GtkUIManager *ui_manager;
374         GtkActionGroup *action_group;
375         GError *error = NULL;
376         ModestUIPrivate *priv;
377
378         priv = MODEST_UI_GET_PRIVATE(self);
379
380         /* Create UI manager */
381         ui_manager = gtk_ui_manager_new();
382
383         /* Create action group */
384         action_group = gtk_action_group_new ("ModestMainWindowActions");
385         gtk_action_group_add_actions (action_group,
386                                       modest_action_entries,
387                                       G_N_ELEMENTS (modest_action_entries),
388                                       priv);
389         gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
390         g_object_unref (action_group);
391
392         /* Load the UI definition */
393         gtk_ui_manager_add_ui_from_file (ui_manager, MODEST_UIDIR "modest-ui.xml", &error);
394         if (error != NULL) {
395                 g_warning ("Could not merge modest-ui.xml: %s", error->message);
396                 g_error_free (error);
397         }
398         return ui_manager;
399 }
400
401 /* 
402  *  This function registers our custom toolbar icons, so they can be
403  *  themed. The idea of this function was taken from the gtk-demo
404  */
405 static void
406 register_stock_icons ()
407 {
408         static gboolean registered = FALSE;
409   
410         if (!registered) {
411                 GdkPixbuf *pixbuf;
412                 GtkIconFactory *factory;
413                 gchar *filename;
414                 gint i;
415
416                 static GtkStockItem items[] = {
417                         { MODEST_STOCK_MAIL_SEND, "send mail", 0, 0, NULL },
418                         { MODEST_STOCK_NEW_MAIL, "new mail", 0, 0, NULL },
419                         { MODEST_STOCK_SEND_RECEIVE, "send receive", 0, 0, NULL },
420                         { MODEST_STOCK_REPLY, "reply", 0, 0, NULL },
421                         { MODEST_STOCK_REPLY_ALL, "reply all", 0, 0, NULL },
422                         { MODEST_STOCK_FORWARD, "forward", 0, 0, NULL },
423                         { MODEST_STOCK_DELETE, "delete", 0, 0, NULL },
424                         { MODEST_STOCK_NEXT, "next", 0, 0, NULL },
425                         { MODEST_STOCK_PREV, "prev", 0, 0, NULL },
426                         { MODEST_STOCK_STOP, "stop", 0, 0, NULL }
427                 };
428       
429                 static gchar *items_names [] = {
430                         MODEST_TOOLBAR_ICON_MAIL_SEND,
431                         MODEST_TOOLBAR_ICON_NEW_MAIL,           
432                         MODEST_TOOLBAR_ICON_SEND_RECEIVE,
433                         MODEST_TOOLBAR_ICON_REPLY,      
434                         MODEST_TOOLBAR_ICON_REPLY_ALL,
435                         MODEST_TOOLBAR_ICON_FORWARD,
436                         MODEST_TOOLBAR_ICON_DELETE,
437                         MODEST_TOOLBAR_ICON_NEXT,
438                         MODEST_TOOLBAR_ICON_PREV,
439                         MODEST_TOOLBAR_ICON_STOP
440                 };
441
442                 registered = TRUE;
443
444                 /* Register our stock items */
445                 gtk_stock_add (items, G_N_ELEMENTS (items));
446       
447                 /* Add our custom icon factory to the list of defaults */
448                 factory = gtk_icon_factory_new ();
449                 gtk_icon_factory_add_default (factory);
450
451                 /* Register icons to accompany stock items */
452                 for (i = 0; i < G_N_ELEMENTS (items); i++) {
453                         pixbuf = NULL;
454                         pixbuf = gdk_pixbuf_new_from_file (items_names[i], NULL);
455
456                         if (pixbuf != NULL) {
457                                 GtkIconSet *icon_set;
458                                 GdkPixbuf *transparent;
459
460                                 transparent = gdk_pixbuf_add_alpha (pixbuf, TRUE, 0xff, 0xff, 0xff);
461
462                                 icon_set = gtk_icon_set_new_from_pixbuf (transparent);
463                                 gtk_icon_factory_add (factory, items[i].stock_id, icon_set);
464                                 gtk_icon_set_unref (icon_set);
465                                 g_object_unref (pixbuf);
466                                 g_object_unref (transparent);
467                         }
468                         else
469                                 g_warning ("failed to load %s icon", items_names[i]);
470                 }
471                 /* Drop our reference to the factory, GTK will hold a reference. */
472                 g_object_unref (factory);
473         }
474 }
475
476 /* FIXME: uninit these as well */
477 static void
478 connect_signals (ModestUI *self)
479 {
480         TnyDevice *device;
481         ModestUIPrivate *priv;
482         ModestFolderView *folder_view;
483         ModestHeaderView *header_view;
484         ModestMsgView *msg_view;
485         GtkWidget *toggle;
486         
487         priv = MODEST_UI_GET_PRIVATE(self);
488
489         folder_view = modest_widget_factory_get_folder_view (priv->widget_factory);
490         header_view = modest_widget_factory_get_header_view (priv->widget_factory);
491         msg_view    = modest_widget_factory_get_msg_preview (priv->widget_factory);
492         toggle      = modest_widget_factory_get_online_toggle (priv->widget_factory);
493         device      = tny_account_store_get_device (priv->account_store);
494
495         /* folder view */
496         g_signal_connect (G_OBJECT(folder_view), "folder_selection_changed",
497                           G_CALLBACK(modest_ui_actions_on_folder_selection_changed),
498                           priv);
499 /*      g_signal_connect (G_OBJECT(folder_view), "key-press-event", */
500 /*                        G_CALLBACK(on_folder_key_press_event), priv->widget_factory); */
501
502         /* header view */
503         g_signal_connect (G_OBJECT(header_view), "status_update",
504                           G_CALLBACK(modest_ui_actions_on_header_status_update), 
505                           priv);
506         g_signal_connect (G_OBJECT(header_view), "header_selected",
507                           G_CALLBACK(modest_ui_actions_on_header_selected), 
508                           priv);
509         g_signal_connect (G_OBJECT(header_view), "item_not_found",
510                           G_CALLBACK(modest_ui_actions_on_item_not_found), 
511                           priv);
512
513         
514         /* msg preview */
515         g_signal_connect (G_OBJECT(msg_view), "link_clicked",
516                           G_CALLBACK(modest_ui_actions_on_msg_link_clicked), 
517                           priv);
518         g_signal_connect (G_OBJECT(msg_view), "link_hover",
519                           G_CALLBACK(modest_ui_actions_on_msg_link_hover), 
520                           priv);
521         g_signal_connect (G_OBJECT(msg_view), "attachment_clicked",
522                           G_CALLBACK(modest_ui_actions_on_msg_attachment_clicked), 
523                           priv);
524
525         /* Device */
526         g_signal_connect (G_OBJECT(device), "connection_changed",
527                           G_CALLBACK(modest_ui_actions_on_connection_changed), 
528                           priv);
529         g_signal_connect (G_OBJECT(toggle), "toggled",
530                           G_CALLBACK(modest_ui_actions_on_online_toggle_toggled),
531                           priv);
532                 
533         /* Init toggle in correct state */
534         modest_ui_actions_on_connection_changed (device,
535                                                  tny_device_is_online (device),
536                                                  priv);
537 }
538
539
540 static void
541 connect_main_window_signals (ModestUI *self)
542 {
543         ModestUIPrivate *priv;
544         
545         priv = MODEST_UI_GET_PRIVATE(self);
546
547         /* account store */
548         g_signal_connect (G_OBJECT (priv->account_store), 
549                           "password_requested",
550                           G_CALLBACK(modest_ui_actions_on_password_requested),
551                           priv);
552 }
553
554 /* ***************************************************************** */
555 /*                M O D E S T    U I    A C T I O N S                */
556 /* ***************************************************************** */
557 static void     
558 modest_ui_actions_on_about (GtkWidget *widget, 
559                             ModestUIPrivate *priv)
560 {
561         GtkWidget *about;
562         const gchar *authors[] = {
563                 "Dirk-Jan C. Binnema <dirk-jan.binnema@nokia.com>",
564                 NULL
565         };      
566         about = gtk_about_dialog_new ();
567         gtk_about_dialog_set_name (GTK_ABOUT_DIALOG(about), PACKAGE_NAME);
568         gtk_about_dialog_set_version (GTK_ABOUT_DIALOG(about),PACKAGE_VERSION);
569         gtk_about_dialog_set_copyright (GTK_ABOUT_DIALOG(about),
570                                         _("Copyright (c) 2006, Nokia Corporation\n"
571                                           "All rights reserved."));
572         gtk_about_dialog_set_comments (GTK_ABOUT_DIALOG(about),
573                                        _("a modest e-mail client\n\n"
574                                          "design and implementation: Dirk-Jan C. Binnema\n"
575                                          "contributions from the fine people at KernelConcepts and Igalia\n"
576                                          "uses the tinymail email framework written by Philip van Hoof"));
577         gtk_about_dialog_set_authors (GTK_ABOUT_DIALOG(about), authors);
578         gtk_about_dialog_set_website (GTK_ABOUT_DIALOG(about), "http://modest.garage.maemo.org");
579
580         gtk_dialog_run (GTK_DIALOG (about));
581         gtk_widget_destroy(about);
582 }
583
584 static void
585 modest_ui_actions_on_delete (GtkWidget *widget, 
586                              ModestUIPrivate *priv)
587 {
588         ModestWidgetFactory *widget_factory;
589         ModestHeaderView *header_view;
590         TnyList *header_list;
591         TnyIterator *iter;
592         GtkTreeModel *model;
593
594         header_view = modest_widget_factory_get_header_view (priv->widget_factory);
595         header_list = modest_header_view_get_selected_headers (header_view);
596         
597         if (header_list) {
598                 iter = tny_list_create_iterator (header_list);
599                 model = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
600                 if (GTK_IS_TREE_MODEL_SORT (model))
601                         model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (model));
602                 do {
603                         TnyHeader *header;
604                         ModestMailOperation *mail_op;
605
606                         header = TNY_HEADER (tny_iterator_get_current (iter));
607                         /* TODO: thick grain mail operation involving
608                            a list of objects. Composite pattern ??? */
609                         mail_op = modest_mail_operation_new ();
610
611                         /* TODO: add confirmation dialog */
612
613                         /* Move to trash */
614                         modest_mail_operation_remove_msg (mail_op, header, TRUE);
615
616                         /* Remove from tree model */
617                         if (modest_mail_operation_get_status (mail_op) == 
618                             MODEST_MAIL_OPERATION_STATUS_SUCCESS)
619                                 tny_list_remove (TNY_LIST (model), G_OBJECT (header));
620                         else {
621                                 /* TODO: error handling management */
622                                 const GError *error;
623                                 error = modest_mail_operation_get_error (mail_op);
624                                 g_warning (error->message);
625                         }
626
627                         g_object_unref (G_OBJECT (mail_op));
628                         g_object_unref (header);
629                         tny_iterator_next (iter);
630
631                 } while (!tny_iterator_is_done (iter));
632         }
633 }
634
635 static void
636 modest_ui_actions_on_quit (GtkWidget *widget, 
637                            ModestUIPrivate *priv)
638 {
639         /* FIXME: save size of main window */
640 /*      save_sizes (main_window); */
641         gtk_widget_destroy (GTK_WIDGET (priv->main_window));
642 }
643
644 static void
645 modest_ui_actions_on_accounts (GtkWidget *widget, 
646                                ModestUIPrivate *priv)
647 {
648         GtkWidget *account_win;
649
650         account_win = modest_account_view_window_new (priv->widget_factory);
651
652         gtk_window_set_transient_for (GTK_WINDOW (account_win),
653                                       GTK_WINDOW (priv->main_window));
654                                       
655         gtk_widget_show (account_win);
656 }
657
658 static void
659 modest_ui_actions_on_new_msg (GtkWidget *widget, 
660                               ModestUIPrivate *priv)
661 {
662         GtkWidget *msg_win;
663
664         msg_win = modest_edit_msg_window_new (priv->widget_factory,
665                                               MODEST_EDIT_TYPE_NEW);
666         gtk_widget_show (msg_win);
667 }
668
669 static void
670 reply_forward_func (gpointer data, gpointer user_data)
671 {
672         TnyHeader *new_header;
673         TnyMsg *msg, *new_msg;
674         GetMsgAsyncHelper *helper;
675         ReplyForwardHelper *rf_helper;
676         GtkWidget *msg_win;
677         ModestEditType edit_type;
678
679         msg = TNY_MSG (data);
680         helper = (GetMsgAsyncHelper *) user_data;
681         rf_helper = (ReplyForwardHelper *) helper->user_data;
682
683         /* Create reply mail */
684         switch (rf_helper->action) {
685         case ACTION_REPLY:
686                 new_msg = 
687                         modest_mail_operation_create_reply_mail (msg, 
688                                                                  rf_helper->from, 
689                                                                  rf_helper->reply_forward_type,
690                                                                  MODEST_MAIL_OPERATION_REPLY_MODE_SENDER);
691         case ACTION_REPLY_TO_ALL:
692                 new_msg = 
693                         modest_mail_operation_create_reply_mail (msg, rf_helper->from, rf_helper->reply_forward_type,
694                                                                  MODEST_MAIL_OPERATION_REPLY_MODE_ALL);
695                 edit_type = MODEST_EDIT_TYPE_REPLY;
696                 break;
697         case ACTION_FORWARD:
698                 new_msg = 
699                         modest_mail_operation_create_forward_mail (msg, rf_helper->from, rf_helper->reply_forward_type);
700                 edit_type = MODEST_EDIT_TYPE_FORWARD;
701                 break;
702         }
703
704         /* Set from */
705         new_header = tny_msg_get_header (new_msg);
706         tny_header_set_from (new_header, rf_helper->from);
707         g_object_unref (G_OBJECT (new_header));
708                 
709         /* Show edit window */
710         msg_win = modest_edit_msg_window_new (helper->priv->widget_factory,
711                                               edit_type);
712         modest_edit_msg_window_set_msg (MODEST_EDIT_MSG_WINDOW (msg_win),
713                                         new_msg);
714         gtk_widget_show (msg_win);
715         
716         /* Clean */
717         g_object_unref (new_msg);
718         g_free (rf_helper->from);
719         g_slice_free (ReplyForwardHelper, rf_helper);
720 }
721
722 /*
723  * Common code for the reply and forward actions
724  */
725 static void
726 reply_forward (GtkWidget *widget,
727                ReplyForwardAction action,
728                ModestUIPrivate *priv)
729 {
730         ModestHeaderView *header_view;
731         TnyList *header_list;
732         guint reply_forward_type;
733         ModestConf *conf;
734         TnyPlatformFactory *plat_factory;
735         TnyHeader *header;
736         TnyFolder *folder;
737         gchar *from, *key;
738         ModestFolderView *folder_view;
739         GetMsgAsyncHelper *helper;
740         ReplyForwardHelper *rf_helper;
741
742         /* Get ModestConf */
743         plat_factory = modest_tny_platform_factory_get_instance ();
744         conf = modest_tny_platform_factory_get_modest_conf_instance (plat_factory);
745
746         /* Get reply or forward type */
747         key = g_strdup_printf ("%s/%s", MODEST_CONF_NAMESPACE, 
748                                (action == ACTION_FORWARD) ? MODEST_CONF_FORWARD_TYPE : MODEST_CONF_REPLY_TYPE);
749         reply_forward_type = modest_conf_get_int (conf, key, NULL);
750         g_free (key);
751
752         /* Get the list of headers */
753         header_view = modest_widget_factory_get_header_view (priv->widget_factory);
754         header_list = modest_header_view_get_selected_headers (header_view);
755
756         if (!header_list)
757                 return;
758
759         /* We assume that we can only select messages of the
760            same folder and that we reply all of them from the
761            same account. In fact the interface currently only
762            allows single selection */
763
764         /* TODO: get the from string from account */
765         from = g_strdup ("Invalid");
766         
767         /* Fill helpers */
768         rf_helper = g_slice_new0 (ReplyForwardHelper);
769         rf_helper->reply_forward_type = reply_forward_type;
770         rf_helper->action = action;
771         rf_helper->from = from;
772         
773         helper = g_slice_new0 (GetMsgAsyncHelper);
774         helper->priv = priv;
775         helper->func = reply_forward_func;
776         helper->iter = tny_list_create_iterator (header_list);
777         helper->user_data = rf_helper;
778         
779         header = TNY_HEADER (tny_iterator_get_current (helper->iter));
780         folder = tny_header_get_folder (header);
781         
782         /* The callback will call it per each header */
783         tny_folder_get_msg_async (folder, header, get_msg_cb, helper);
784         
785         /* Clean */
786         g_object_unref (G_OBJECT (folder));
787 }
788
789 static void
790 modest_ui_actions_on_reply (GtkWidget *widget,
791                             ModestUIPrivate *priv)
792 {
793         reply_forward (widget, ACTION_REPLY, priv);
794 }
795
796 static void
797 modest_ui_actions_on_forward (GtkWidget *widget,
798                               ModestUIPrivate *priv)
799 {
800         reply_forward (widget, ACTION_FORWARD, priv);
801 }
802
803 static void
804 modest_ui_actions_on_reply_all (GtkWidget *widget,
805                                 ModestUIPrivate *priv)
806 {
807         reply_forward (widget, ACTION_REPLY_TO_ALL, priv);
808 }
809
810 static void 
811 modest_ui_actions_on_next (GtkWidget *widget, 
812                            ModestUIPrivate *priv)
813 {
814         ModestHeaderView *header_view;
815
816         header_view = modest_widget_factory_get_header_view (priv->widget_factory);
817         modest_header_view_select_next (header_view);
818 }
819
820 /*
821  * Marks a message as read and passes it to the msg preview widget
822  */
823 static void
824 read_msg_func (gpointer data, gpointer user_data)
825 {
826         ModestMsgView *msg_view;
827         TnyMsg *msg;
828         TnyHeader *header;
829         GetMsgAsyncHelper *helper;
830         TnyHeaderFlags header_flags;
831
832         msg = TNY_MSG (data);
833         helper = (GetMsgAsyncHelper *) user_data;
834
835         /* mark message as seen; _set_flags crashes, bug in tinymail? */
836         header = TNY_HEADER (tny_iterator_get_current (helper->iter));
837         header_flags = tny_header_get_flags (header);
838         tny_header_set_flags (header, header_flags | TNY_HEADER_FLAG_SEEN);
839         g_object_unref (G_OBJECT (header));
840
841         /* Set message on msg view */
842         msg_view = modest_widget_factory_get_msg_preview (helper->priv->widget_factory);
843         modest_msg_view_set_message (msg_view, msg);
844 }
845
846 /*
847  * This function is a generic handler for the tny_folder_get_msg_async
848  * call. It expects as user_data a #GetMsgAsyncHelper. This helper
849  * contains a user provided function that is called inside this
850  * method. This will allow us to use this callback in many different
851  * places. This callback performs the common actions for the
852  * get_msg_async call, more specific actions will be done by the user
853  * function
854  */
855 static void
856 get_msg_cb (TnyFolder *folder, TnyMsg *msg, GError **err, gpointer user_data)
857 {
858         GetMsgAsyncHelper *helper;
859
860         helper = (GetMsgAsyncHelper *) user_data;
861
862         if (*err && ((*err)->code == TNY_FOLDER_ERROR_GET_MSG)) {
863                 ModestHeaderView *header_view;
864
865                 header_view = modest_widget_factory_get_header_view (helper->priv->widget_factory);
866                 modest_ui_actions_on_item_not_found (header_view, MODEST_ITEM_TYPE_MESSAGE, helper->priv);
867                 return;
868         }
869
870         if (!msg)
871                 return;
872
873         /* Call user function */
874         helper->func (msg, user_data);
875
876         /* Process next element (if exists) */
877         tny_iterator_next (helper->iter);
878         if (tny_iterator_is_done (helper->iter)) {
879                 TnyList *headers;
880                 headers = tny_iterator_get_list (helper->iter);
881                 /* Free resources */
882                 g_object_unref (G_OBJECT (headers));
883                 g_object_unref (G_OBJECT (helper->iter));
884                 g_slice_free (GetMsgAsyncHelper, helper);
885         } else
886                 tny_folder_get_msg_async (folder, 
887                                           TNY_HEADER (tny_iterator_get_current (helper->iter)), 
888                                           get_msg_cb, helper);
889 }
890
891 static void 
892 modest_ui_actions_on_header_selected (ModestHeaderView *folder_view, 
893                                       TnyHeader *header,
894                                       ModestUIPrivate *priv)
895 {
896         TnyFolder *folder;
897         GetMsgAsyncHelper *helper;
898         TnyList *list;
899         TnyIterator *iter;
900
901         if (!header)
902                 return;
903
904         folder = tny_header_get_folder (TNY_HEADER(header));
905
906         /* Create list */
907         list = tny_simple_list_new ();
908         tny_list_prepend (list, G_OBJECT (header));
909
910         /* Fill helper data */
911         helper = g_slice_new0 (GetMsgAsyncHelper);
912         helper->priv = priv;
913         helper->iter = tny_list_create_iterator (list);
914         helper->func = read_msg_func;
915
916         tny_folder_get_msg_async (TNY_FOLDER(folder),
917                                   header,
918                                   get_msg_cb,
919                                   helper);
920
921         /* Frees */
922         g_object_unref (G_OBJECT (folder));
923 }
924
925 static void 
926 modest_ui_actions_on_folder_selection_changed (ModestFolderView *folder_view,
927                                                TnyFolder *folder, 
928                                                gboolean selected,
929                                                ModestUIPrivate *priv)
930 {
931         GtkLabel *folder_info_label;
932         TnyPlatformFactory *factory;
933         gchar *txt;     
934         ModestConf *conf;
935         ModestHeaderView *header_view;
936
937         folder_info_label = 
938                 GTK_LABEL (modest_widget_factory_get_folder_info_label (priv->widget_factory));
939
940         if (!folder) {
941                 gtk_label_set_label (GTK_LABEL(folder_info_label), "");
942                 return;
943         }
944
945         factory = modest_tny_platform_factory_get_instance ();
946         header_view = modest_widget_factory_get_header_view (priv->widget_factory);
947         conf = modest_tny_platform_factory_get_modest_conf_instance (factory);
948
949         if (!selected) { /* the folder was unselected; save it's settings  */
950                 modest_widget_memory_save (conf, G_OBJECT (header_view),
951                                            "header-view");
952         } else {  /* the folder was selected */
953                 guint num, unread;
954                 num    = tny_folder_get_all_count    (folder);
955                 unread = tny_folder_get_unread_count (folder);
956                         
957                 txt = g_strdup_printf (_("%d %s, %d unread"),
958                                        num, num==1 ? _("item") : _("items"), unread);           
959                 gtk_label_set_label (GTK_LABEL(folder_info_label), txt);
960                 g_free (txt);
961                         
962                 modest_header_view_set_folder (header_view, folder);
963                 modest_widget_memory_restore (conf, G_OBJECT(header_view),
964                                               "header-view");
965         }
966 }
967
968 static void
969 modest_ui_actions_on_password_requested (ModestTnyAccountStore *account_store, 
970                                          const gchar* account_name,
971                                          gchar **password, 
972                                          gboolean *cancel, 
973                                          gboolean *remember, 
974                                          ModestUIPrivate *priv)
975 {
976         gchar *txt;
977         GtkWidget *dialog, *entry, *remember_pass_check;
978
979         dialog = gtk_dialog_new_with_buttons (_("Password requested"),
980                                               GTK_WINDOW (priv->main_window),
981                                               GTK_DIALOG_MODAL,
982                                               GTK_STOCK_CANCEL,
983                                               GTK_RESPONSE_REJECT,
984                                               GTK_STOCK_OK,
985                                               GTK_RESPONSE_ACCEPT,
986                                               NULL);
987
988         txt = g_strdup_printf (_("Please enter your password for %s"), account_name);
989         gtk_box_pack_start (GTK_BOX(GTK_DIALOG(dialog)->vbox), gtk_label_new(txt),
990                             FALSE, FALSE, 0);
991         g_free (txt);
992
993         entry = gtk_entry_new_with_max_length (40);
994         gtk_entry_set_visibility (GTK_ENTRY(entry), FALSE);
995         gtk_entry_set_invisible_char (GTK_ENTRY(entry), 0x2022); /* bullet unichar */
996         
997         gtk_box_pack_start (GTK_BOX(GTK_DIALOG(dialog)->vbox), entry,
998                             TRUE, FALSE, 0);    
999
1000         remember_pass_check = gtk_check_button_new_with_label (_("Remember password"));
1001         gtk_box_pack_start (GTK_BOX(GTK_DIALOG(dialog)->vbox), remember_pass_check,
1002                             TRUE, FALSE, 0);
1003
1004         gtk_widget_show_all (GTK_WIDGET(GTK_DIALOG(dialog)->vbox));
1005         
1006         if (gtk_dialog_run (GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
1007                 *password = g_strdup (gtk_entry_get_text (GTK_ENTRY(entry)));
1008                 *cancel   = FALSE;
1009         } else {
1010                 *password = NULL;
1011                 *cancel   = TRUE;
1012         }
1013
1014         if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (remember_pass_check)))
1015                 *remember = TRUE;
1016         else
1017                 *remember = FALSE;
1018
1019         gtk_widget_destroy (dialog);
1020
1021         while (gtk_events_pending ())
1022                 gtk_main_iteration ();
1023 }
1024
1025 /****************************************************/
1026 /*
1027  * below some stuff to clearup statusbar messages after 1,5 seconds....
1028  */
1029 typedef struct {
1030         GtkWidget *status_bar;
1031         GtkWidget *progress_bar;
1032         guint     msg_id;
1033 } StatusRemoveData;
1034
1035 static gboolean
1036 on_statusbar_remove_msg (StatusRemoveData *data)
1037 {
1038         /* we need to test types, as this callback maybe called after the
1039          * widgets have been destroyed
1040          */
1041         if (GTK_IS_STATUSBAR(data->status_bar)) 
1042                 gtk_statusbar_remove (GTK_STATUSBAR(data->status_bar),
1043                                       0, data->msg_id);
1044         if (GTK_IS_PROGRESS_BAR(data->progress_bar))
1045                 gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR(data->progress_bar),
1046                                                1.0);
1047         g_free (data);
1048         return FALSE;
1049 }
1050
1051 static void
1052 statusbar_push (ModestWidgetFactory *factory, guint context_id, const gchar *msg)
1053 {
1054         guint id;
1055         StatusRemoveData *data;
1056         GtkWidget *status_bar, *progress_bar;
1057         
1058         if (!msg)
1059                 return;
1060
1061         progress_bar = modest_widget_factory_get_progress_bar (factory);
1062         status_bar   = modest_widget_factory_get_status_bar (factory);
1063         
1064         id = gtk_statusbar_push (GTK_STATUSBAR(status_bar), 0, msg);
1065
1066         data = g_new (StatusRemoveData, 1);
1067         data->status_bar   = status_bar;
1068         data->progress_bar = progress_bar;
1069         data->msg_id     = id;
1070
1071         g_timeout_add (1500, (GSourceFunc)on_statusbar_remove_msg, data);
1072 }
1073 /****************************************************************************/
1074
1075 static void
1076 modest_ui_actions_on_connection_changed (TnyDevice *device, 
1077                                          gboolean online,
1078                                          ModestUIPrivate *priv)
1079 {
1080         GtkWidget *online_toggle;
1081         ModestHeaderView *header_view;
1082
1083         header_view   = modest_widget_factory_get_header_view (priv->widget_factory);
1084         online_toggle = modest_widget_factory_get_online_toggle (priv->widget_factory);
1085
1086         gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(online_toggle),
1087                                       online);
1088         gtk_button_set_label (GTK_BUTTON(online_toggle),
1089                               online ? _("Online") : _("Offline"));
1090
1091         statusbar_push (priv->widget_factory, 0, 
1092                         online ? _("Modest went online") : _("Modest went offline"));
1093
1094         /* If Modest has became online and the header view has a
1095            header selected then show it */
1096         if (online) {
1097                 GtkTreeSelection *selected;
1098
1099                 selected = gtk_tree_view_get_selection (GTK_TREE_VIEW (header_view));
1100                 _modest_header_view_change_selection (selected, header_view);
1101         }
1102 }
1103
1104 static void
1105 modest_ui_actions_on_online_toggle_toggled (GtkToggleButton *toggle,
1106                                             ModestUIPrivate *priv)
1107 {
1108         gboolean online;
1109         TnyDevice *device;
1110
1111         online  = gtk_toggle_button_get_active (toggle);
1112         device = tny_account_store_get_device (priv->account_store);
1113
1114         if (online)
1115                 tny_device_force_online (device);
1116         else
1117                 tny_device_force_offline (device);
1118 }
1119
1120 static void 
1121 modest_ui_actions_on_item_not_found (ModestHeaderView *header_view,
1122                                      ModestItemType type,
1123                                      ModestUIPrivate *priv)
1124 {
1125         GtkWidget *dialog;
1126         gchar *txt, *item;
1127         gboolean online;
1128         TnyDevice *device;
1129
1130         item = (type == MODEST_ITEM_TYPE_FOLDER) ? "folder" : "message";
1131         device = tny_account_store_get_device (priv->account_store);
1132         
1133         gdk_threads_enter ();
1134         online = tny_device_is_online (device);
1135
1136         if (online) {
1137                 /* already online -- the item is simply not there... */
1138                 dialog = gtk_message_dialog_new (GTK_WINDOW (priv->main_window),
1139                                                  GTK_DIALOG_MODAL,
1140                                                  GTK_MESSAGE_WARNING,
1141                                                  GTK_BUTTONS_OK,
1142                                                  _("The %s you selected cannot be found"),
1143                                                  item);
1144                 gtk_dialog_run (GTK_DIALOG(dialog));
1145         } else {
1146
1147                 dialog = gtk_dialog_new_with_buttons (_("Connection requested"),
1148                                                       GTK_WINDOW (priv->main_window),
1149                                                       GTK_DIALOG_MODAL,
1150                                                       GTK_STOCK_CANCEL,
1151                                                       GTK_RESPONSE_REJECT,
1152                                                       GTK_STOCK_OK,
1153                                                       GTK_RESPONSE_ACCEPT,
1154                                                       NULL);
1155
1156                 txt = g_strdup_printf (_("This %s is not available in offline mode.\n"
1157                                          "Do you want to get online?"), item);
1158                 gtk_box_pack_start (GTK_BOX(GTK_DIALOG(dialog)->vbox), 
1159                                     gtk_label_new (txt), FALSE, FALSE, 0);
1160                 gtk_widget_show_all (GTK_WIDGET(GTK_DIALOG(dialog)->vbox));
1161                 g_free (txt);
1162
1163                 gtk_window_set_default_size (GTK_WINDOW(dialog), 300, 300);
1164                 if (gtk_dialog_run (GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
1165                         tny_device_force_online (device);
1166                 }
1167         }
1168         gtk_widget_destroy (dialog);
1169         gdk_threads_leave ();
1170 }
1171
1172 static void
1173 modest_ui_actions_on_header_status_update (ModestHeaderView *header_view, const gchar *msg,
1174                                            gint num, gint total, ModestUIPrivate *priv)
1175 {
1176         GtkWidget *progress_bar;
1177         
1178         progress_bar = modest_widget_factory_get_progress_bar (priv->widget_factory);
1179
1180         if (total != 0)
1181                 gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR(progress_bar),
1182                                                (gdouble)num/(gdouble)total);
1183         else
1184                 gtk_progress_bar_pulse (GTK_PROGRESS_BAR(progress_bar));
1185
1186         statusbar_push (priv->widget_factory, 0, msg);
1187 }
1188
1189
1190 static void
1191 modest_ui_actions_on_msg_link_hover (ModestMsgView *msgview, const gchar* link,
1192                                      ModestUIPrivate *priv)
1193 {
1194         
1195         statusbar_push (priv->widget_factory, 0, link);
1196
1197         /* TODO: do something */
1198 }       
1199
1200
1201 static void
1202 modest_ui_actions_on_msg_link_clicked (ModestMsgView *msgview, const gchar* link,
1203                                        ModestUIPrivate *priv)
1204 {
1205         gchar *msg;
1206         msg = g_strdup_printf (_("Opening %s..."), link);
1207
1208         statusbar_push (priv->widget_factory, 0, msg);
1209         g_free (msg);
1210
1211         /* TODO: do something */
1212 }
1213
1214 static void
1215 modest_ui_actions_on_msg_attachment_clicked (ModestMsgView *msgview, int index,
1216                                              ModestUIPrivate *priv)
1217 {
1218         gchar *msg;
1219         
1220         msg = g_strdup_printf (_("Opening attachment %d..."), index);
1221         statusbar_push (priv->widget_factory, 0, msg);
1222         
1223         g_free (msg);
1224
1225         /* TODO: do something */
1226 }