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