* Fixed drag&drop for folders
[modest] / src / widgets / modest-folder-view.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 <glib/gi18n.h>
31 #include <string.h>
32 #include <gdk/gdkkeysyms.h>
33 #include <tny-account-store-view.h>
34 #include <tny-gtk-account-list-model.h>
35 #include <tny-gtk-folder-store-tree-model.h>
36 #include <tny-gtk-header-list-model.h>
37 #include <tny-folder.h>
38 #include <tny-account-store.h>
39 #include <tny-account.h>
40 #include <tny-folder.h>
41 #include <tny-camel-folder.h>
42 #include <tny-simple-list.h>
43 #include <modest-tny-folder.h>
44 #include <modest-marshal.h>
45 #include <modest-icon-names.h>
46 #include <modest-tny-account-store.h>
47 #include <modest-text-utils.h>
48 #include <modest-runtime.h>
49 #include "modest-folder-view.h"
50 #include <modest-dnd.h>
51 #include <modest-platform.h>
52 #include <modest-account-mgr-helpers.h>
53 #include <modest-widget-memory.h>
54
55 /* 'private'/'protected' functions */
56 static void modest_folder_view_class_init  (ModestFolderViewClass *klass);
57 static void modest_folder_view_init        (ModestFolderView *obj);
58 static void modest_folder_view_finalize    (GObject *obj);
59
60 static void         tny_account_store_view_init (gpointer g, 
61                                                  gpointer iface_data);
62
63 static void         modest_folder_view_set_account_store (TnyAccountStoreView *self, 
64                                                           TnyAccountStore     *account_store);
65
66 static gboolean     update_model           (ModestFolderView *self,
67                                             ModestTnyAccountStore *account_store);
68
69 static void         on_selection_changed   (GtkTreeSelection *sel, gpointer data);
70
71 static void         on_account_update      (TnyAccountStore *account_store, 
72                                             const gchar *account,
73                                             gpointer user_data);
74
75 static void         on_accounts_reloaded   (TnyAccountStore *store, 
76                                             gpointer user_data);
77
78 static gint         cmp_rows               (GtkTreeModel *tree_model, 
79                                             GtkTreeIter *iter1, 
80                                             GtkTreeIter *iter2,
81                                             gpointer user_data);
82
83 static gboolean     filter_row             (GtkTreeModel *model,
84                                             GtkTreeIter *iter,
85                                             gpointer data);
86
87 static gboolean     on_key_pressed         (GtkWidget *self,
88                                             GdkEventKey *event,
89                                             gpointer user_data);
90
91 static void         on_configuration_key_changed         (ModestConf* conf, 
92                                                           const gchar *key, 
93                                                           ModestConfEvent event, 
94                                                           ModestFolderView *self);
95
96 /* DnD functions */
97 static void         on_drag_data_get       (GtkWidget *widget, 
98                                             GdkDragContext *context, 
99                                             GtkSelectionData *selection_data, 
100                                             guint info, 
101                                             guint time, 
102                                             gpointer data);
103
104 static void         on_drag_data_received  (GtkWidget *widget, 
105                                             GdkDragContext *context, 
106                                             gint x, 
107                                             gint y, 
108                                             GtkSelectionData *selection_data, 
109                                             guint info, 
110                                             guint time, 
111                                             gpointer data);
112
113 static gboolean     on_drag_motion         (GtkWidget      *widget,
114                                             GdkDragContext *context,
115                                             gint            x,
116                                             gint            y,
117                                             guint           time,
118                                             gpointer        user_data);
119
120 static gint         expand_row_timeout     (gpointer data);
121
122 static void         setup_drag_and_drop    (GtkTreeView *self);
123
124 enum {
125         FOLDER_SELECTION_CHANGED_SIGNAL,
126         FOLDER_DISPLAY_NAME_CHANGED_SIGNAL,
127         LAST_SIGNAL
128 };
129
130 typedef struct _ModestFolderViewPrivate ModestFolderViewPrivate;
131 struct _ModestFolderViewPrivate {
132         TnyAccountStore      *account_store;
133         TnyFolderStore       *cur_folder_store;
134
135         gulong                account_update_signal;
136         gulong                changed_signal;
137         gulong                accounts_reloaded_signal;
138         
139         GtkTreeSelection     *cur_selection;
140         TnyFolderStoreQuery  *query;
141         guint                 timer_expander;
142
143         gchar                *local_account_name;
144         gchar                *visible_account_id;
145         ModestFolderViewStyle style;
146 };
147 #define MODEST_FOLDER_VIEW_GET_PRIVATE(o)                       \
148         (G_TYPE_INSTANCE_GET_PRIVATE((o),                       \
149                                      MODEST_TYPE_FOLDER_VIEW,   \
150                                      ModestFolderViewPrivate))
151 /* globals */
152 static GObjectClass *parent_class = NULL;
153
154 static guint signals[LAST_SIGNAL] = {0}; 
155
156 GType
157 modest_folder_view_get_type (void)
158 {
159         static GType my_type = 0;
160         if (!my_type) {
161                 static const GTypeInfo my_info = {
162                         sizeof(ModestFolderViewClass),
163                         NULL,           /* base init */
164                         NULL,           /* base finalize */
165                         (GClassInitFunc) modest_folder_view_class_init,
166                         NULL,           /* class finalize */
167                         NULL,           /* class data */
168                         sizeof(ModestFolderView),
169                         1,              /* n_preallocs */
170                         (GInstanceInitFunc) modest_folder_view_init,
171                         NULL
172                 };
173
174                 static const GInterfaceInfo tny_account_store_view_info = {
175                         (GInterfaceInitFunc) tny_account_store_view_init, /* interface_init */
176                         NULL,         /* interface_finalize */
177                         NULL          /* interface_data */
178                 };
179
180                                 
181                 my_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
182                                                   "ModestFolderView",
183                                                   &my_info, 0);
184
185                 g_type_add_interface_static (my_type, 
186                                              TNY_TYPE_ACCOUNT_STORE_VIEW, 
187                                              &tny_account_store_view_info);
188         }
189         return my_type;
190 }
191
192 static void
193 modest_folder_view_class_init (ModestFolderViewClass *klass)
194 {
195         GObjectClass *gobject_class;
196         gobject_class = (GObjectClass*) klass;
197
198         parent_class            = g_type_class_peek_parent (klass);
199         gobject_class->finalize = modest_folder_view_finalize;
200
201         g_type_class_add_private (gobject_class,
202                                   sizeof(ModestFolderViewPrivate));
203         
204         signals[FOLDER_SELECTION_CHANGED_SIGNAL] = 
205                 g_signal_new ("folder_selection_changed",
206                               G_TYPE_FROM_CLASS (gobject_class),
207                               G_SIGNAL_RUN_FIRST,
208                               G_STRUCT_OFFSET (ModestFolderViewClass,
209                                                folder_selection_changed),
210                               NULL, NULL,
211                               modest_marshal_VOID__POINTER_BOOLEAN,
212                               G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_BOOLEAN);
213
214         /*
215          * This signal is emitted whenever the currently selected
216          * folder display name is computed. Note that the name could
217          * be different to the folder name, because we could append
218          * the unread messages count to the folder name to build the
219          * folder display name
220          */
221         signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL] = 
222                 g_signal_new ("folder-display-name-changed",
223                               G_TYPE_FROM_CLASS (gobject_class),
224                               G_SIGNAL_RUN_FIRST,
225                               G_STRUCT_OFFSET (ModestFolderViewClass,
226                                                folder_display_name_changed),
227                               NULL, NULL,
228                               g_cclosure_marshal_VOID__STRING,
229                               G_TYPE_NONE, 1, G_TYPE_STRING);
230 }
231
232
233
234 static void
235 text_cell_data  (GtkTreeViewColumn *column,  GtkCellRenderer *renderer,
236                  GtkTreeModel *tree_model,  GtkTreeIter *iter,  gpointer data)
237 {
238         ModestFolderViewPrivate *priv;
239         GObject *rendobj;
240         gchar *fname = NULL;
241         gint unread, all;
242         TnyFolderType type;
243         GObject *instance = NULL;
244         
245         g_return_if_fail (column);
246         g_return_if_fail (tree_model);
247
248         gtk_tree_model_get (tree_model, iter,
249                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &fname,
250                             TNY_GTK_FOLDER_STORE_TREE_MODEL_ALL_COLUMN, &all,
251                             TNY_GTK_FOLDER_STORE_TREE_MODEL_UNREAD_COLUMN, &unread,
252                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
253                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
254                             -1);
255         rendobj = G_OBJECT(renderer);
256  
257         if (!fname)
258                 return;
259
260         if (!instance) {
261                 g_free (fname);
262                 return;
263         }
264
265         
266         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE (data);
267         
268         if (type != TNY_FOLDER_TYPE_ROOT) {
269                 gint number;
270
271                 if (modest_tny_folder_is_local_folder (TNY_FOLDER (instance))) {
272                         TnyFolderType type;
273                         type = modest_tny_folder_get_local_folder_type (TNY_FOLDER (instance));
274                         if (type != TNY_FOLDER_TYPE_UNKNOWN) {
275                                 g_free (fname);
276                                 fname = g_strdup(modest_local_folder_info_get_type_display_name (type));
277                         }
278                 }
279
280                 /* Select the number to show */
281                 if ((type == TNY_FOLDER_TYPE_DRAFTS) || (type == TNY_FOLDER_TYPE_OUTBOX))
282                         number = all;
283                 else
284                         number = unread;
285
286                 /* Use bold font style if there are unread messages */
287                 if (unread > 0) {
288                         gchar *folder_title = g_strdup_printf ("%s (%d)", fname, unread);
289                         g_object_set (rendobj,"text", folder_title,  "weight", 800, NULL);
290                         if (G_OBJECT (priv->cur_folder_store) == instance)
291                                 g_signal_emit (G_OBJECT(data),
292                                                signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
293                                                folder_title);
294                         g_free (folder_title);
295                 } else {
296                         g_object_set (rendobj,"text", fname, "weight", 400, NULL);
297                         if (G_OBJECT (priv->cur_folder_store) == instance)
298                                 g_signal_emit (G_OBJECT(data),
299                                                signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
300                                                fname);
301                 }
302
303         } else {
304                 const gchar *account_name = NULL;
305                 const gchar *account_id = NULL;
306
307                 /* If it's a server account */
308                 account_id = tny_account_get_id (TNY_ACCOUNT (instance));
309                 if (!strcmp (account_id, MODEST_LOCAL_FOLDERS_ACCOUNT_ID)) {
310                         account_name = priv->local_account_name;
311                 } else {
312                         if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
313                                 /* TODO: get MMC card name */
314                         } else {
315                                 account_name = fname;
316                         }
317                 }
318
319                 /* Notify display name observers */
320                 if (G_OBJECT (priv->cur_folder_store) == instance)
321                         g_signal_emit (G_OBJECT(data),
322                                        signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
323                                        account_name);
324
325                 /* Use bold font style */
326                 g_object_set (rendobj,"text", account_name, "weight", 800, NULL);
327         }
328         
329         g_object_unref (G_OBJECT (instance));
330         g_free (fname);
331 }
332
333
334
335 static void
336 icon_cell_data  (GtkTreeViewColumn *column,  GtkCellRenderer *renderer,
337                  GtkTreeModel *tree_model,  GtkTreeIter *iter, gpointer data)
338 {
339         GObject *rendobj = NULL, *instance = NULL;
340         GdkPixbuf *pixbuf = NULL;
341         TnyFolderType type;
342         gchar *fname = NULL;
343         const gchar *account_id = NULL;
344         gint unread;
345         
346         rendobj = G_OBJECT(renderer);
347         gtk_tree_model_get (tree_model, iter,
348                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
349                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &fname,
350                             TNY_GTK_FOLDER_STORE_TREE_MODEL_UNREAD_COLUMN, &unread,
351                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
352                             -1);
353
354         if (!fname)
355                 return;
356
357         if (!instance) {
358                 g_free (fname);
359                 return;
360         }
361         
362         if (type == TNY_FOLDER_TYPE_NORMAL || type == TNY_FOLDER_TYPE_UNKNOWN) {
363                 type = modest_tny_folder_guess_folder_type_from_name (fname);
364         }
365
366         switch (type) {
367         case TNY_FOLDER_TYPE_ROOT:
368                 account_id = tny_account_get_id (TNY_ACCOUNT (instance));
369                 if (!strcmp (account_id, MODEST_LOCAL_FOLDERS_ACCOUNT_ID)) {
370                         pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_LOCAL_FOLDERS);
371                 } else {
372                         if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
373                                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_MMC);
374                         else
375                                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_ACCOUNT);
376                 }
377                 break;
378         case TNY_FOLDER_TYPE_INBOX:
379                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_INBOX);
380                 break;
381         case TNY_FOLDER_TYPE_OUTBOX:
382                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_OUTBOX);
383                 break;
384         case TNY_FOLDER_TYPE_JUNK:
385                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_JUNK);
386                 break;
387         case TNY_FOLDER_TYPE_SENT:
388                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_SENT);
389                 break;
390         case TNY_FOLDER_TYPE_TRASH:
391                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_TRASH);
392                 break;
393         case TNY_FOLDER_TYPE_DRAFTS:
394                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_DRAFTS);
395                 break;
396         case TNY_FOLDER_TYPE_NORMAL:
397         default:
398                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_NORMAL);
399                 break;
400         }
401         g_object_unref (G_OBJECT (instance));
402         g_free (fname);
403
404         /* Set pixbuf */
405         g_object_set (rendobj, "pixbuf", pixbuf, NULL);
406
407         if (pixbuf != NULL)
408                 g_object_unref (pixbuf);
409 }
410
411 static void
412 add_columns (GtkWidget *treeview)
413 {
414         GtkTreeViewColumn *column;
415         GtkCellRenderer *renderer;
416         GtkTreeSelection *sel;
417
418         /* Create column */
419         column = gtk_tree_view_column_new ();   
420         
421         /* Set icon and text render function */
422         renderer = gtk_cell_renderer_pixbuf_new();
423         gtk_tree_view_column_pack_start (column, renderer, FALSE);
424         gtk_tree_view_column_set_cell_data_func(column, renderer,
425                                                 icon_cell_data, treeview, NULL);
426         
427         renderer = gtk_cell_renderer_text_new();
428         gtk_tree_view_column_pack_start (column, renderer, FALSE);
429         gtk_tree_view_column_set_cell_data_func(column, renderer,
430                                                 text_cell_data, treeview, NULL);
431         
432         /* Set selection mode */
433         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(treeview));
434         gtk_tree_selection_set_mode (sel, GTK_SELECTION_SINGLE);
435
436         /* Set treeview appearance */
437         gtk_tree_view_column_set_spacing (column, 2);
438         gtk_tree_view_column_set_resizable (column, TRUE);
439         gtk_tree_view_column_set_fixed_width (column, TRUE);            
440         gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(treeview), FALSE);
441         gtk_tree_view_set_enable_search (GTK_TREE_VIEW(treeview), FALSE);
442
443         /* Add column */
444         gtk_tree_view_append_column (GTK_TREE_VIEW(treeview),column);
445 }
446
447 static void
448 modest_folder_view_init (ModestFolderView *obj)
449 {
450         ModestFolderViewPrivate *priv;
451         ModestConf *conf;
452         
453         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
454         
455         priv->timer_expander = 0;
456         priv->account_store  = NULL;
457         priv->query          = NULL;
458         priv->style          = MODEST_FOLDER_VIEW_STYLE_SHOW_ALL;
459         priv->cur_folder_store   = NULL;
460         priv->visible_account_id = NULL;
461
462         /* Initialize the local account name */
463         conf = modest_runtime_get_conf();
464         priv->local_account_name = modest_conf_get_string (conf, MODEST_CONF_DEVICE_NAME, NULL);
465
466         /* Build treeview */
467         add_columns (GTK_WIDGET (obj));
468
469         /* Setup drag and drop */
470         setup_drag_and_drop (GTK_TREE_VIEW(obj));
471
472         /* Restore conf */
473         modest_widget_memory_restore (conf, G_OBJECT (obj), MODEST_CONF_FOLDER_VIEW_KEY);
474
475         /* Connect signals */
476         g_signal_connect (G_OBJECT (obj), 
477                           "key-press-event", 
478                           G_CALLBACK (on_key_pressed), NULL);
479
480         /*
481          * Track changes in the local account name (in the device it
482          * will be the device name)
483          */
484         g_signal_connect (G_OBJECT(conf), 
485                           "key_changed",
486                           G_CALLBACK(on_configuration_key_changed), obj);
487
488 }
489
490 static void
491 tny_account_store_view_init (gpointer g, gpointer iface_data)
492 {
493         TnyAccountStoreViewIface *klass = (TnyAccountStoreViewIface *)g;
494
495         klass->set_account_store_func = modest_folder_view_set_account_store;
496
497         return;
498 }
499
500 static void
501 modest_folder_view_finalize (GObject *obj)
502 {
503         ModestFolderViewPrivate *priv;
504         GtkTreeSelection    *sel;
505         
506         g_return_if_fail (obj);
507         
508         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
509
510         if (priv->timer_expander != 0) {
511                 g_source_remove (priv->timer_expander);
512                 priv->timer_expander = 0;
513         }
514
515         if (priv->account_store) {
516                 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
517                                              priv->account_update_signal);
518                 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
519                                              priv->accounts_reloaded_signal);
520                 g_object_unref (G_OBJECT(priv->account_store));
521                 priv->account_store = NULL;
522         }
523
524         if (priv->query) {
525                 g_object_unref (G_OBJECT (priv->query));
526                 priv->query = NULL;
527         }
528
529         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(obj));
530         if (sel)
531                 g_signal_handler_disconnect (G_OBJECT(sel), priv->changed_signal);
532
533         g_free (priv->local_account_name);
534         g_free (priv->visible_account_id);
535         
536         G_OBJECT_CLASS(parent_class)->finalize (obj);
537 }
538
539
540 static void
541 modest_folder_view_set_account_store (TnyAccountStoreView *self, TnyAccountStore *account_store)
542 {
543         ModestFolderViewPrivate *priv;
544         TnyDevice *device;
545
546         g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
547         g_return_if_fail (TNY_IS_ACCOUNT_STORE (account_store));
548
549         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
550         device = tny_account_store_get_device (account_store);
551
552         if (G_UNLIKELY (priv->account_store)) {
553
554                 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store), 
555                                                    priv->account_update_signal))
556                         g_signal_handler_disconnect (G_OBJECT (priv->account_store), 
557                                                      priv->account_update_signal);
558                 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store), 
559                                                    priv->accounts_reloaded_signal))
560                         g_signal_handler_disconnect (G_OBJECT (priv->account_store), 
561                                                      priv->accounts_reloaded_signal);
562
563                 g_object_unref (G_OBJECT (priv->account_store));
564         }
565
566         priv->account_store = g_object_ref (G_OBJECT (account_store));
567
568         priv->account_update_signal = 
569                 g_signal_connect (G_OBJECT(account_store), "account_update",
570                                   G_CALLBACK (on_account_update), self);
571
572         priv->accounts_reloaded_signal = 
573                 g_signal_connect (G_OBJECT(account_store), "accounts_reloaded",
574                                   G_CALLBACK (on_accounts_reloaded), self);
575         
576         if (!update_model (MODEST_FOLDER_VIEW (self),
577                            MODEST_TNY_ACCOUNT_STORE (priv->account_store)))
578                 g_printerr ("modest: failed to update model\n");
579
580         g_object_unref (G_OBJECT (device));
581 }
582
583 static void
584 on_account_update (TnyAccountStore *account_store, const gchar *account,
585                    gpointer user_data)
586 {
587         if (!update_model (MODEST_FOLDER_VIEW(user_data), 
588                            MODEST_TNY_ACCOUNT_STORE(account_store)))
589                 g_printerr ("modest: failed to update model for changes in '%s'",
590                             account);
591 }
592
593 static void 
594 on_accounts_reloaded   (TnyAccountStore *account_store, 
595                         gpointer user_data)
596 {
597         update_model (MODEST_FOLDER_VIEW (user_data), 
598                       MODEST_TNY_ACCOUNT_STORE(account_store));
599 }
600
601 void
602 modest_folder_view_set_title (ModestFolderView *self, const gchar *title)
603 {
604         GtkTreeViewColumn *col;
605         
606         g_return_if_fail (self);
607
608         col = gtk_tree_view_get_column (GTK_TREE_VIEW(self), 0);
609         if (!col) {
610                 g_printerr ("modest: failed get column for title\n");
611                 return;
612         }
613
614         gtk_tree_view_column_set_title (col, title);
615         gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self),
616                                            title != NULL);
617 }
618
619 GtkWidget*
620 modest_folder_view_new (TnyFolderStoreQuery *query)
621 {
622         GObject *self;
623         ModestFolderViewPrivate *priv;
624         GtkTreeSelection *sel;
625         
626         self = G_OBJECT (g_object_new (MODEST_TYPE_FOLDER_VIEW, NULL));
627         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
628
629         if (query)
630                 priv->query = g_object_ref (query);
631         
632         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
633         priv->changed_signal = g_signal_connect (sel, "changed",
634                                                  G_CALLBACK (on_selection_changed), self);
635
636         return GTK_WIDGET(self);
637 }
638
639 /* this feels dirty; any other way to expand all the root items? */
640 static void
641 expand_root_items (ModestFolderView *self)
642 {
643         GtkTreePath *path;
644         path = gtk_tree_path_new_first ();
645
646         /* all folders should have child items, so.. */
647         while (gtk_tree_view_expand_row (GTK_TREE_VIEW(self), path, FALSE))
648                 gtk_tree_path_next (path);
649         
650         gtk_tree_path_free (path);
651 }
652
653 /*
654  * We use this function to implement the
655  * MODEST_FOLDER_VIEW_STYLE_SHOW_ONE style. We only show the default
656  * account in this case
657  */
658 static gboolean 
659 filter_row (GtkTreeModel *model,
660             GtkTreeIter *iter,
661             gpointer data)
662 {
663         gboolean retval = TRUE;
664         gint type;
665         GObject *instance = NULL;
666
667         gtk_tree_model_get (model, iter,
668                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
669                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
670                             -1);
671
672         if (type == TNY_FOLDER_TYPE_ROOT) {
673                 TnyAccount *acc;
674                 const gchar *account_id;
675
676                 acc = TNY_ACCOUNT (instance);
677                 account_id = tny_account_get_id (acc);
678
679                 if (strcmp (account_id, MODEST_LOCAL_FOLDERS_ACCOUNT_ID) &&
680                     strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) { 
681                         ModestFolderViewPrivate *priv;
682                         
683                         /* Show only the visible account id */
684                         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (data);
685                         if (priv->visible_account_id && strcmp (account_id, priv->visible_account_id))
686                                 retval = FALSE;
687                 }
688         }
689
690         g_object_unref (instance);
691
692         return retval;
693 }
694
695 static gboolean
696 update_model (ModestFolderView *self, ModestTnyAccountStore *account_store)
697 {
698         ModestFolderViewPrivate *priv;
699         TnyList          *account_list;
700         GtkTreeModel     *model;
701
702         g_return_val_if_fail (account_store, FALSE);
703
704         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE(self);
705         
706         /* Notify that there is no folder selected */
707         g_signal_emit (G_OBJECT(self), 
708                        signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
709                        NULL, TRUE);
710         
711         /* FIXME: the local accounts are not shown when the query
712            selects only the subscribed folders. */
713 /*      model        = tny_gtk_folder_store_tree_model_new (TRUE, priv->query); */
714         model        = tny_gtk_folder_store_tree_model_new (TRUE, NULL);
715         account_list = TNY_LIST(model);
716
717         tny_account_store_get_accounts (TNY_ACCOUNT_STORE(account_store),
718                                         account_list,
719                                         TNY_ACCOUNT_STORE_STORE_ACCOUNTS);      
720         if (account_list) {
721                 GtkTreeModel *filter_model = NULL, *sortable = NULL;
722
723                 sortable = gtk_tree_model_sort_new_with_model (model);
724                 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
725                                                       TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, 
726                                                       GTK_SORT_ASCENDING);
727                 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
728                                                  TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
729                                                  cmp_rows, NULL, NULL);
730
731                 /* Create filter model */
732                 if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE) {
733                         filter_model = gtk_tree_model_filter_new (sortable, NULL);
734                         gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
735                                                                 filter_row,
736                                                                 self,
737                                                                 NULL);
738                 }
739
740                 /* Set new model */
741                 gtk_tree_view_set_model (GTK_TREE_VIEW(self), 
742                                          (filter_model) ? filter_model : sortable);
743                 expand_root_items (self); /* expand all account folders */
744                 g_object_unref (account_list);
745         }
746         
747         return TRUE;
748 }
749
750
751 static void
752 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
753 {
754         GtkTreeModel            *model;
755         TnyFolderStore          *folder = NULL;
756         GtkTreeIter             iter;
757         ModestFolderView        *tree_view;
758         ModestFolderViewPrivate *priv;
759         gint                    type;
760
761         g_return_if_fail (sel);
762         g_return_if_fail (user_data);
763         
764         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
765         priv->cur_selection = sel;
766         
767         /* folder was _un_selected if true */
768         if (!gtk_tree_selection_get_selected (sel, &model, &iter)) {
769                 if (priv->cur_folder_store)
770                         g_object_unref (priv->cur_folder_store);
771                 priv->cur_folder_store = NULL;
772
773                 /* Notify the display name observers */
774                 g_signal_emit (G_OBJECT(user_data),
775                                signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
776                                NULL);
777                 return;
778         }
779
780         tree_view = MODEST_FOLDER_VIEW (user_data);
781
782         gtk_tree_model_get (model, &iter,
783                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
784                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
785                             -1);
786
787         /* If the folder is the same do not notify */
788         if (priv->cur_folder_store == folder) {
789                 g_object_unref (folder);
790                 return;
791         }
792         
793         /* Current folder was unselected */
794         if (priv->cur_folder_store) {
795                 g_signal_emit (G_OBJECT(tree_view), signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
796                                priv->cur_folder_store, FALSE);
797                 g_object_unref (priv->cur_folder_store);
798         }
799
800         /* New current references */
801         priv->cur_folder_store = folder;
802
803         /* New folder has been selected */
804         g_signal_emit (G_OBJECT(tree_view),
805                        signals[FOLDER_SELECTION_CHANGED_SIGNAL],
806                        0, folder, TRUE);
807 }
808
809 TnyFolderStore *
810 modest_folder_view_get_selected (ModestFolderView *self)
811 {
812         ModestFolderViewPrivate *priv;
813
814         g_return_val_if_fail (self, NULL);
815         
816         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
817         if (priv->cur_folder_store)
818                 g_object_ref (priv->cur_folder_store);
819
820         return priv->cur_folder_store;
821 }
822
823 /*
824  * This function orders the mail accounts following the next rules
825  * 1st - remote accounts
826  * 2nd - local account
827  * 3rd - MMC account
828  */
829 static gint
830 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
831           gpointer user_data)
832 {
833         gint cmp;
834         gchar         *name1, *name2;
835         TnyFolderType type;
836         TnyFolder     *folder1, *folder2;
837
838         gtk_tree_model_get (tree_model, iter1,
839                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name1,
840                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
841                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder1,
842                             -1);
843         gtk_tree_model_get (tree_model, iter2,
844                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name2,
845                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder2,
846                             -1);
847
848         /* Order must be: Remote accounts -> Local account -> MMC account */
849         if (type == TNY_FOLDER_TYPE_ROOT) {
850                 const gchar *account_id = tny_account_get_id (TNY_ACCOUNT (folder1));
851                 const gchar *account_id2 = tny_account_get_id (TNY_ACCOUNT (folder2));
852
853                 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
854                         cmp = +1;
855                 else {
856                         if (!strcmp (account_id, MODEST_LOCAL_FOLDERS_ACCOUNT_ID)) {
857                                 if (!strcmp (account_id2, MODEST_MMC_ACCOUNT_ID))
858                                         cmp = -1;
859                                 else
860                                         cmp = +1;
861                         } else {
862                                 if (!strcmp (account_id2, MODEST_LOCAL_FOLDERS_ACCOUNT_ID) ||
863                                     !strcmp (account_id2, MODEST_MMC_ACCOUNT_ID))
864                                         cmp = -1;
865                                 else
866                                         cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
867                         }
868                 }
869         } else 
870                 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
871         
872         if (folder1)
873                 g_object_unref(G_OBJECT(folder1));
874         if (folder2)
875                 g_object_unref(G_OBJECT(folder2));
876
877         g_free (name1);
878         g_free (name2);
879
880         return cmp;     
881 }
882
883 /*****************************************************************************/
884 /*                        DRAG and DROP stuff                                */
885 /*****************************************************************************/
886
887 /*
888  * This function fills the #GtkSelectionData with the row and the
889  * model that has been dragged. It's called when this widget is a
890  * source for dnd after the event drop happened
891  */
892 static void
893 on_drag_data_get (GtkWidget *widget, 
894                   GdkDragContext *context, 
895                   GtkSelectionData *selection_data, 
896                   guint info, 
897                   guint time, 
898                   gpointer data)
899 {
900         GtkTreeSelection *selection;
901         GtkTreeModel *model;
902         GtkTreeIter iter;
903         GtkTreePath *source_row;
904
905         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
906         gtk_tree_selection_get_selected (selection, &model, &iter);
907         source_row = gtk_tree_model_get_path (model, &iter);
908
909         gtk_tree_set_row_drag_data (selection_data,
910                                     model,
911                                     source_row);
912
913         gtk_tree_path_free (source_row);
914 }
915
916 typedef struct _DndHelper {
917         gboolean delete_source;
918         GtkTreePath *source_row;
919         GdkDragContext *context;
920         guint time;
921 } DndHelper;
922
923
924 /*
925  * This function is the callback of the
926  * modest_mail_operation_xfer_msgs () and
927  * modest_mail_operation_xfer_folder() calls. We check here if the
928  * message/folder was correctly asynchronously transferred. The reason
929  * to use the same callback is that the code is the same, it only has
930  * to check that the operation went fine and then finalize the drag
931  * and drop action
932  */
933 static void
934 on_progress_changed (ModestMailOperation *mail_op, gpointer user_data)
935 {
936         gboolean success;
937         DndHelper *helper;
938
939         helper = (DndHelper *) user_data;
940
941         if (!modest_mail_operation_is_finished (mail_op))
942                 return;
943
944         if (modest_mail_operation_get_status (mail_op) == 
945             MODEST_MAIL_OPERATION_STATUS_SUCCESS) {
946                 success = TRUE;
947         } else {
948                 success = FALSE;
949         }
950
951         /* Notify the drag source. Never call delete, the monitor will
952            do the job if needed */
953         gtk_drag_finish (helper->context, success, FALSE, helper->time);
954
955         /* Free the helper */
956         gtk_tree_path_free (helper->source_row);        
957         g_slice_free (DndHelper, helper);
958 }
959
960 /*
961  * This function is used by drag_data_received_cb to manage drag and
962  * drop of a header, i.e, and drag from the header view to the folder
963  * view.
964  */
965 static void
966 drag_and_drop_from_header_view (GtkTreeModel *source_model,
967                                 GtkTreeModel *dest_model,
968                                 GtkTreePath  *dest_row,
969                                 DndHelper    *helper)
970 {
971         TnyList *headers;
972         TnyHeader *header;
973         TnyFolder *folder;
974         ModestMailOperation *mail_op;
975         GtkTreeIter source_iter, dest_iter;
976
977         /* Get header */
978         gtk_tree_model_get_iter (source_model, &source_iter, helper->source_row);
979         gtk_tree_model_get (source_model, &source_iter, 
980                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, 
981                             &header, -1);
982
983         /* Get Folder */
984         gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
985         gtk_tree_model_get (dest_model, &dest_iter, 
986                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, 
987                             &folder, -1);
988
989         /* Transfer message */
990         mail_op = modest_mail_operation_new (MODEST_MAIL_OPERATION_ID_RECEIVE, NULL);
991
992         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
993                                          mail_op);
994         g_signal_connect (G_OBJECT (mail_op), "progress-changed",
995                           G_CALLBACK (on_progress_changed), helper);
996
997         /* FIXME: I replaced this because the API changed, but D&D
998            should be reviewed in order to allow multiple drags*/
999         headers = tny_simple_list_new ();
1000         tny_list_append (headers, G_OBJECT (header));
1001         modest_mail_operation_xfer_msgs (mail_op, headers, folder, helper->delete_source, NULL, NULL);
1002
1003         /* Frees */
1004         g_object_unref (G_OBJECT (mail_op));
1005         g_object_unref (G_OBJECT (header));
1006         g_object_unref (G_OBJECT (folder));
1007         g_object_unref (headers);
1008 }
1009
1010 /*
1011  * This function is used by drag_data_received_cb to manage drag and
1012  * drop of a folder, i.e, and drag from the folder view to the same
1013  * folder view.
1014  */
1015 static void
1016 drag_and_drop_from_folder_view (GtkTreeModel     *source_model,
1017                                 GtkTreeModel     *dest_model,
1018                                 GtkTreePath      *dest_row,
1019                                 GtkSelectionData *selection_data,
1020                                 DndHelper        *helper)
1021 {
1022         ModestMailOperation *mail_op;
1023         GtkTreeIter parent_iter, iter;
1024         TnyFolderStore *parent_folder;
1025         TnyFolder *folder;
1026
1027         /* Check if the drag is possible */
1028 /*      if (!gtk_tree_path_compare (helper->source_row, dest_row) || */
1029 /*          !gtk_tree_drag_dest_row_drop_possible (GTK_TREE_DRAG_DEST (dest_model), */
1030 /*                                                 dest_row, */
1031 /*                                                 selection_data)) { */
1032         if (!gtk_tree_path_compare (helper->source_row, dest_row)) {
1033
1034                 gtk_drag_finish (helper->context, FALSE, FALSE, helper->time);
1035                 gtk_tree_path_free (helper->source_row);        
1036                 g_slice_free (DndHelper, helper);
1037                 return;
1038         }
1039
1040         /* Get data */
1041         gtk_tree_model_get_iter (source_model, &parent_iter, dest_row);
1042         gtk_tree_model_get_iter (source_model, &iter, helper->source_row);
1043         gtk_tree_model_get (source_model, &parent_iter, 
1044                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, 
1045                             &parent_folder, -1);
1046         gtk_tree_model_get (source_model, &iter,
1047                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
1048                             &folder, -1);
1049
1050         /* Do the mail operation */
1051         mail_op = modest_mail_operation_new (MODEST_MAIL_OPERATION_ID_RECEIVE, NULL);
1052         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (), 
1053                                          mail_op);
1054         g_signal_connect (G_OBJECT (mail_op), "progress-changed",
1055                           G_CALLBACK (on_progress_changed), helper);
1056
1057         modest_mail_operation_xfer_folder_async (mail_op, 
1058                                                  folder, 
1059                                                  parent_folder,
1060                                                  helper->delete_source);
1061         
1062         /* Frees */
1063         g_object_unref (G_OBJECT (parent_folder));
1064         g_object_unref (G_OBJECT (folder));
1065         g_object_unref (G_OBJECT (mail_op));
1066 }
1067
1068 /*
1069  * This function receives the data set by the "drag-data-get" signal
1070  * handler. This information comes within the #GtkSelectionData. This
1071  * function will manage both the drags of folders of the treeview and
1072  * drags of headers of the header view widget.
1073  */
1074 static void 
1075 on_drag_data_received (GtkWidget *widget, 
1076                        GdkDragContext *context, 
1077                        gint x, 
1078                        gint y, 
1079                        GtkSelectionData *selection_data, 
1080                        guint target_type, 
1081                        guint time, 
1082                        gpointer data)
1083 {
1084         GtkWidget *source_widget;
1085         GtkTreeModel *dest_model, *source_model;
1086         GtkTreePath *source_row, *dest_row;
1087         GtkTreeViewDropPosition pos;
1088         gboolean success = FALSE, delete_source = FALSE;
1089         DndHelper *helper;
1090
1091         /* Do not allow further process */
1092         g_signal_stop_emission_by_name (widget, "drag-data-received");
1093         source_widget = gtk_drag_get_source_widget (context);
1094
1095         /* Get the action */
1096         if (context->action == GDK_ACTION_MOVE) {
1097                 delete_source = TRUE;
1098
1099                 /* Notify that there is no folder selected. We need to
1100                    do this in order to update the headers view (and
1101                    its monitors, because when moving, the old folder
1102                    won't longer exist. We can not wait for the end of
1103                    the operation, because the operation won't start if
1104                    the folder is in use */
1105                 if (helper->delete_source && source_widget == widget)
1106                         g_signal_emit (G_OBJECT (widget), 
1107                                        signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0, NULL, TRUE);
1108         }
1109
1110         /* Check if the get_data failed */
1111         if (selection_data == NULL || selection_data->length < 0)
1112                 gtk_drag_finish (context, success, FALSE, time);
1113
1114         /* Get the models */
1115         gtk_tree_get_row_drag_data (selection_data,
1116                                     &source_model,
1117                                     &source_row);
1118
1119         /* Select the destination model */
1120         if (source_widget == widget) {
1121                 dest_model = source_model;
1122         } else {
1123                 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
1124         }
1125
1126         /* Get the path to the destination row. Can not call
1127            gtk_tree_view_get_drag_dest_row() because the source row
1128            is not selected anymore */
1129         gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget), x, y,
1130                                            &dest_row, &pos);
1131
1132         /* Only allow drops IN other rows */
1133         if (!dest_row || pos == GTK_TREE_VIEW_DROP_BEFORE || pos == GTK_TREE_VIEW_DROP_AFTER)
1134                 gtk_drag_finish (context, success, FALSE, time);
1135
1136         /* Create the helper */
1137         helper = g_slice_new0 (DndHelper);
1138         helper->delete_source = delete_source;
1139         helper->source_row = gtk_tree_path_copy (source_row);
1140         helper->context = context;
1141         helper->time = time;
1142
1143         /* Drags from the header view */
1144         if (source_widget != widget) {
1145
1146                 drag_and_drop_from_header_view (source_model,
1147                                                 dest_model,
1148                                                 dest_row,
1149                                                 helper);
1150         } else {
1151
1152
1153                 drag_and_drop_from_folder_view (source_model,
1154                                                 dest_model,
1155                                                 dest_row,
1156                                                 selection_data, 
1157                                                 helper);
1158         }
1159
1160         /* Frees */
1161         gtk_tree_path_free (source_row);
1162         gtk_tree_path_free (dest_row);
1163 }
1164
1165 /*
1166  * We define a "drag-drop" signal handler because we do not want to
1167  * use the default one, because the default one always calls
1168  * gtk_drag_finish and we prefer to do it in the "drag-data-received"
1169  * signal handler, because there we have all the information available
1170  * to know if the dnd was a success or not.
1171  */
1172 static gboolean
1173 drag_drop_cb (GtkWidget      *widget,
1174               GdkDragContext *context,
1175               gint            x,
1176               gint            y,
1177               guint           time,
1178               gpointer        user_data) 
1179 {
1180         gpointer target;
1181
1182         if (!context->targets)
1183                 return FALSE;
1184
1185         /* Check if we're dragging a folder row */
1186         target = gtk_drag_dest_find_target (widget, context, NULL);
1187
1188         /* Request the data from the source. */
1189         gtk_drag_get_data(widget, context, target, time);
1190
1191     return TRUE;
1192 }
1193
1194 /*
1195  * This function expands a node of a tree view if it's not expanded
1196  * yet. Not sure why it needs the threads stuff, but gtk+`example code
1197  * does that, so that's why they're here.
1198  */
1199 static gint
1200 expand_row_timeout (gpointer data)
1201 {
1202         GtkTreeView *tree_view = data;
1203         GtkTreePath *dest_path = NULL;
1204         GtkTreeViewDropPosition pos;
1205         gboolean result = FALSE;
1206         
1207         GDK_THREADS_ENTER ();
1208         
1209         gtk_tree_view_get_drag_dest_row (tree_view,
1210                                          &dest_path,
1211                                          &pos);
1212         
1213         if (dest_path &&
1214             (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER ||
1215              pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)) {
1216                 gtk_tree_view_expand_row (tree_view, dest_path, FALSE);
1217                 gtk_tree_path_free (dest_path);
1218         }
1219         else {
1220                 if (dest_path)
1221                         gtk_tree_path_free (dest_path);
1222                 
1223                 result = TRUE;
1224         }
1225         
1226         GDK_THREADS_LEAVE ();
1227
1228         return result;
1229 }
1230
1231 /*
1232  * This function is called whenever the pointer is moved over a widget
1233  * while dragging some data. It installs a timeout that will expand a
1234  * node of the treeview if not expanded yet. This function also calls
1235  * gdk_drag_status in order to set the suggested action that will be
1236  * used by the "drag-data-received" signal handler to know if we
1237  * should do a move or just a copy of the data.
1238  */
1239 static gboolean
1240 on_drag_motion (GtkWidget      *widget,
1241                 GdkDragContext *context,
1242                 gint            x,
1243                 gint            y,
1244                 guint           time,
1245                 gpointer        user_data)  
1246 {
1247         GtkTreeViewDropPosition pos;
1248         GtkTreePath *dest_row;
1249         ModestFolderViewPrivate *priv;
1250         GdkDragAction suggested_action;
1251         gboolean valid_location = FALSE;
1252
1253         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
1254
1255         if (priv->timer_expander != 0) {
1256                 g_source_remove (priv->timer_expander);
1257                 priv->timer_expander = 0;
1258         }
1259
1260         gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
1261                                            x, y,
1262                                            &dest_row,
1263                                            &pos);
1264
1265         /* Do not allow drops between folders */
1266         if (!dest_row ||
1267             pos == GTK_TREE_VIEW_DROP_BEFORE ||
1268             pos == GTK_TREE_VIEW_DROP_AFTER) {
1269                 gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW (widget), NULL, 0);
1270                 gdk_drag_status(context, 0, time);
1271                 valid_location = FALSE;
1272                 goto out;
1273         } else {
1274                 valid_location = TRUE;
1275         }
1276
1277         /* Expand the selected row after 1/2 second */
1278         if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), dest_row)) {
1279                 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), dest_row, pos);
1280                 priv->timer_expander = g_timeout_add (500, expand_row_timeout, widget);
1281         }
1282
1283         /* Select the desired action. By default we pick MOVE */
1284         suggested_action = GDK_ACTION_MOVE;
1285
1286         if (context->actions == GDK_ACTION_COPY)
1287             gdk_drag_status(context, GDK_ACTION_COPY, time);
1288         else if (context->actions == GDK_ACTION_MOVE)
1289             gdk_drag_status(context, GDK_ACTION_MOVE, time);
1290         else if (context->actions & suggested_action)
1291             gdk_drag_status(context, suggested_action, time);
1292         else
1293             gdk_drag_status(context, GDK_ACTION_DEFAULT, time);
1294
1295  out:
1296         if (dest_row)
1297                 gtk_tree_path_free (dest_row);
1298         g_signal_stop_emission_by_name (widget, "drag-motion");
1299         return valid_location;
1300 }
1301
1302
1303 /* Folder view drag types */
1304 const GtkTargetEntry folder_view_drag_types[] =
1305 {
1306         { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, MODEST_FOLDER_ROW },
1307         { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_APP,    MODEST_HEADER_ROW }
1308 };
1309
1310 /*
1311  * This function sets the treeview as a source and a target for dnd
1312  * events. It also connects all the requirede signals.
1313  */
1314 static void
1315 setup_drag_and_drop (GtkTreeView *self)
1316 {
1317         /* Set up the folder view as a dnd destination. Set only the
1318            highlight flag, otherwise gtk will have a different
1319            behaviour */
1320         gtk_drag_dest_set (GTK_WIDGET (self),
1321                            GTK_DEST_DEFAULT_HIGHLIGHT,
1322                            folder_view_drag_types,
1323                            G_N_ELEMENTS (folder_view_drag_types),
1324                            GDK_ACTION_MOVE | GDK_ACTION_COPY);
1325
1326         g_signal_connect (G_OBJECT (self),
1327                           "drag_data_received",
1328                           G_CALLBACK (on_drag_data_received),
1329                           NULL);
1330
1331
1332         /* Set up the treeview as a dnd source */
1333         gtk_drag_source_set (GTK_WIDGET (self),
1334                              GDK_BUTTON1_MASK,
1335                              folder_view_drag_types,
1336                              G_N_ELEMENTS (folder_view_drag_types),
1337                              GDK_ACTION_MOVE | GDK_ACTION_COPY);
1338
1339         g_signal_connect (G_OBJECT (self),
1340                           "drag_motion",
1341                           G_CALLBACK (on_drag_motion),
1342                           NULL);
1343         
1344         g_signal_connect (G_OBJECT (self),
1345                           "drag_data_get",
1346                           G_CALLBACK (on_drag_data_get),
1347                           NULL);
1348
1349         g_signal_connect (G_OBJECT (self),
1350                           "drag_drop",
1351                           G_CALLBACK (drag_drop_cb),
1352                           NULL);
1353 }
1354
1355 /*
1356  * This function manages the navigation through the folders using the
1357  * keyboard or the hardware keys in the device
1358  */
1359 static gboolean
1360 on_key_pressed (GtkWidget *self,
1361                 GdkEventKey *event,
1362                 gpointer user_data)
1363 {
1364         GtkTreeSelection *selection;
1365         GtkTreeIter iter;
1366         GtkTreeModel *model;
1367         gboolean retval = FALSE;
1368
1369         /* Up and Down are automatically managed by the treeview */
1370         if (event->keyval == GDK_Return) {
1371                 /* Expand/Collapse the selected row */
1372                 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1373                 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
1374                         GtkTreePath *path;
1375
1376                         path = gtk_tree_model_get_path (model, &iter);
1377
1378                         if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (self), path))
1379                                 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1380                         else
1381                                 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1382                         gtk_tree_path_free (path);
1383                 }
1384                 /* No further processing */
1385                 retval = TRUE;
1386         }
1387
1388         return retval;
1389 }
1390
1391 /*
1392  * We listen to the changes in the local folder account name key,
1393  * because we want to show the right name in the view. The local
1394  * folder account name corresponds to the device name in the Maemo
1395  * version. We do this because we do not want to query gconf on each
1396  * tree view refresh. It's better to cache it and change whenever
1397  * necessary.
1398  */
1399 static void 
1400 on_configuration_key_changed (ModestConf* conf, 
1401                               const gchar *key, 
1402                               ModestConfEvent event, 
1403                               ModestFolderView *self)
1404 {
1405         ModestFolderViewPrivate *priv;
1406
1407         if (!key)
1408                 return;
1409
1410         g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
1411         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1412
1413         if (!strcmp (key, MODEST_CONF_DEVICE_NAME)) {
1414                 g_free (priv->local_account_name);
1415
1416                 if (event == MODEST_CONF_EVENT_KEY_UNSET)
1417                         priv->local_account_name = g_strdup (MODEST_LOCAL_FOLDERS_DEFAULT_DISPLAY_NAME);
1418                 else
1419                         priv->local_account_name = modest_conf_get_string (modest_runtime_get_conf(),
1420                                                                            MODEST_CONF_DEVICE_NAME, NULL);
1421
1422                 /* Force a redraw */
1423 #if GTK_CHECK_VERSION(2, 8, 0) /* gtk_tree_view_column_queue_resize is only available in GTK+ 2.8 */
1424                 GtkTreeViewColumn * tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self), 
1425                                                                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN);
1426                 gtk_tree_view_column_queue_resize (tree_column);
1427 #endif
1428         }
1429 }
1430
1431 void 
1432 modest_folder_view_set_style (ModestFolderView *self,
1433                               ModestFolderViewStyle style)
1434 {
1435         ModestFolderViewPrivate *priv;
1436
1437         g_return_if_fail (self);
1438         
1439         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1440
1441         priv->style = style;
1442 }
1443
1444 void
1445 modest_folder_view_set_account_id_of_visible_server_account (ModestFolderView *self,
1446                                                              const gchar *account_id)
1447 {
1448         ModestFolderViewPrivate *priv;
1449         ModestConf *conf;
1450         GtkTreeModel *model;
1451
1452         g_return_if_fail (self);
1453         
1454         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1455
1456         if (priv->visible_account_id)
1457                 g_free (priv->visible_account_id);
1458         priv->visible_account_id = g_strdup (account_id);
1459
1460         /* Save preferences */
1461         conf = modest_runtime_get_conf ();
1462         modest_widget_memory_save (conf, G_OBJECT (self), MODEST_CONF_FOLDER_VIEW_KEY);
1463
1464         /* Refilter */
1465         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1466         if (GTK_IS_TREE_MODEL_FILTER (model))
1467                 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
1468 }
1469
1470 const gchar *
1471 modest_folder_view_get_account_id_of_visible_server_account (ModestFolderView *self)
1472 {
1473         ModestFolderViewPrivate *priv;
1474
1475         g_return_val_if_fail (self, NULL);
1476         
1477         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1478
1479         return (const gchar *) priv->visible_account_id;
1480 }