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