* Fixes NB#55338
[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         local_account = 
737                 modest_tny_account_store_get_tny_account_by_account (modest_runtime_get_account_store (),
738                                                                      MODEST_ACTUAL_LOCAL_FOLDERS_ACCOUNT_ID,
739                                                                      TNY_ACCOUNT_TYPE_STORE);
740         old_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
741         if (old_model) {
742                 GtkTreeModel *sorted, *model;
743
744                 if (GTK_IS_TREE_MODEL_FILTER (old_model))
745                         sorted = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (old_model));
746                 else
747                         sorted = old_model;
748
749                 model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sorted));
750                 
751                 tny_folder_store_remove_observer (TNY_FOLDER_STORE (local_account),
752                                                   TNY_FOLDER_STORE_OBSERVER (model));
753         }
754
755         /* FIXME: the local accounts are not shown when the query
756            selects only the subscribed folders. */
757 /*      model        = tny_gtk_folder_store_tree_model_new (TRUE, priv->query); */
758         model        = tny_gtk_folder_store_tree_model_new (TRUE, NULL);
759         
760         /* Deal with the model via its TnyList Interface,
761          * filling the TnyList via a get_accounts() call: */
762         model_as_list = TNY_LIST(model);
763
764         /* Get the accounts: */
765         tny_account_store_get_accounts (TNY_ACCOUNT_STORE(account_store),
766                                         model_as_list,
767                                         TNY_ACCOUNT_STORE_STORE_ACCOUNTS);
768         
769         /* Set the folder view as an account observer in order to let
770            us see the UI changes automatically when creating and
771            deleting folders just under the local account */
772         tny_folder_store_add_observer (TNY_FOLDER_STORE (local_account),
773                                        TNY_FOLDER_STORE_OBSERVER (model));
774         
775         g_object_unref (model_as_list);
776         model_as_list = NULL;   
777         g_object_unref (local_account);
778         
779         /*      
780         if (account_list)
781                 tny_list_foreach (account_list, on_tnylist_accounts_debug_print, "update_model: ");
782     */
783                                                      
784         GtkTreeModel *filter_model = NULL, *sortable = NULL;
785
786         sortable = gtk_tree_model_sort_new_with_model (model);
787         gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
788                                               TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, 
789                                               GTK_SORT_ASCENDING);
790         gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
791                                          TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
792                                          cmp_rows, NULL, NULL);
793
794         /* Create filter model */
795         if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE) {
796                 filter_model = gtk_tree_model_filter_new (sortable, NULL);
797                 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
798                                                         filter_row,
799                                                         self,
800                                                         NULL);
801         }
802
803         /* Set new model */
804         gtk_tree_view_set_model (GTK_TREE_VIEW(self), 
805                                  (filter_model) ? filter_model : sortable);
806         expand_root_items (self); /* expand all account folders */
807         
808         
809         g_object_unref (model);
810         
811         if (filter_model)
812                         g_object_unref (filter_model);
813                         
814         g_object_unref (sortable);
815                         
816         return TRUE;
817 }
818
819
820 static void
821 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
822 {
823         GtkTreeModel            *model;
824         TnyFolderStore          *folder = NULL;
825         GtkTreeIter             iter;
826         ModestFolderView        *tree_view;
827         ModestFolderViewPrivate *priv;
828         gint                    type;
829
830         g_return_if_fail (sel);
831         g_return_if_fail (user_data);
832         
833         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
834
835         /* folder was _un_selected if true */
836         if (!gtk_tree_selection_get_selected (sel, &model, &iter)) {
837                 if (priv->cur_folder_store)
838                         g_object_unref (priv->cur_folder_store);
839                 priv->cur_folder_store = NULL;
840
841                 /* Notify the display name observers */
842                 g_signal_emit (G_OBJECT(user_data),
843                                signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
844                                NULL);
845                 return;
846         }
847
848         tree_view = MODEST_FOLDER_VIEW (user_data);
849
850         gtk_tree_model_get (model, &iter,
851                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
852                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
853                             -1);
854
855         /* If the folder is the same do not notify */
856         if (priv->cur_folder_store == folder) {
857                 g_object_unref (folder);
858                 return;
859         }
860         
861         /* Current folder was unselected */
862         if (priv->cur_folder_store) {
863                 g_signal_emit (G_OBJECT(tree_view), signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
864                                priv->cur_folder_store, FALSE);
865                 g_object_unref (priv->cur_folder_store);
866         }
867
868         /* New current references */
869         priv->cur_folder_store = folder;
870
871         /* New folder has been selected */
872         g_signal_emit (G_OBJECT(tree_view),
873                        signals[FOLDER_SELECTION_CHANGED_SIGNAL],
874                        0, folder, TRUE);
875 }
876
877 TnyFolderStore *
878 modest_folder_view_get_selected (ModestFolderView *self)
879 {
880         ModestFolderViewPrivate *priv;
881
882         g_return_val_if_fail (self, NULL);
883         
884         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
885         if (priv->cur_folder_store)
886                 g_object_ref (priv->cur_folder_store);
887
888         return priv->cur_folder_store;
889 }
890
891 static gint
892 get_cmp_rows_type_pos (GObject *folder)
893 {
894         /* Remote accounts -> Local account -> MMC account .*/
895         /* 0, 1, 2 */
896         
897         if (TNY_IS_ACCOUNT (folder) && 
898                 modest_tny_account_is_virtual_local_folders (
899                         TNY_ACCOUNT (folder))) {
900                 return 1;
901         } else if (TNY_IS_ACCOUNT (folder)) {
902                 TnyAccount *account = TNY_ACCOUNT (folder);
903                 const gchar *account_id = tny_account_get_id (account);
904                 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
905                         return 2;
906                 else
907                         return 0;
908         }
909         else {
910                 printf ("DEBUG: %s: unexpected type.\n", __FUNCTION__);
911                 return -1; /* Should never happen */
912         }
913 }
914
915 /*
916  * This function orders the mail accounts according to these rules:
917  * 1st - remote accounts
918  * 2nd - local account
919  * 3rd - MMC account
920  */
921 static gint
922 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
923           gpointer user_data)
924 {
925         gint cmp;
926         gchar         *name1, *name2;
927         TnyFolderType type;
928         GObject *folder1 = NULL;
929         GObject *folder2 = NULL;
930
931         gtk_tree_model_get (tree_model, iter1,
932                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name1,
933                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
934                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder1,
935                             -1);
936         gtk_tree_model_get (tree_model, iter2,
937                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name2,
938                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder2,
939                             -1);
940
941         if (type == TNY_FOLDER_TYPE_ROOT) {
942                 /* Compare the types, so that 
943                  * Remote accounts -> Local account -> MMC account .*/
944                 const gint pos1 = get_cmp_rows_type_pos (folder1);
945                 const gint pos2 = get_cmp_rows_type_pos (folder2);
946                 /* printf ("DEBUG: %s:\n  type1=%s, pos1=%d\n  type2=%s, pos2=%d\n", 
947                         __FUNCTION__, G_OBJECT_TYPE_NAME(folder1), pos1, G_OBJECT_TYPE_NAME(folder2), pos2); */
948                 if (pos1 <  pos2)
949                         cmp = -1;
950                 else if (pos1 > pos2)
951                         cmp = 1;
952                 else {
953                         /* Compare items of the same type: */
954                         
955                         TnyAccount *account1 = NULL;
956                         if (TNY_IS_ACCOUNT (folder1))
957                                 account1 = TNY_ACCOUNT (folder1);
958                                 
959                         TnyAccount *account2 = NULL;
960                         if (TNY_IS_ACCOUNT (folder2))
961                                 account2 = TNY_ACCOUNT (folder2);
962                                 
963                         const gchar *account_id = account1 ? tny_account_get_id (account1) : NULL;
964                         const gchar *account_id2 = account2 ? tny_account_get_id (account2) : NULL;
965         
966                         if (!account_id && !account_id2)
967                                 return 0;
968                         else if (!account_id)
969                                 return -1;
970                         else if (!account_id2)
971                                 return +1;
972                         else if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
973                                 cmp = +1;
974                         else
975                                 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
976                 }
977         } else {
978                 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
979         }
980         
981         if (folder1)
982                 g_object_unref(G_OBJECT(folder1));
983         if (folder2)
984                 g_object_unref(G_OBJECT(folder2));
985
986         g_free (name1);
987         g_free (name2);
988
989         return cmp;     
990 }
991
992 /*****************************************************************************/
993 /*                        DRAG and DROP stuff                                */
994 /*****************************************************************************/
995
996 /*
997  * This function fills the #GtkSelectionData with the row and the
998  * model that has been dragged. It's called when this widget is a
999  * source for dnd after the event drop happened
1000  */
1001 static void
1002 on_drag_data_get (GtkWidget *widget, 
1003                   GdkDragContext *context, 
1004                   GtkSelectionData *selection_data, 
1005                   guint info, 
1006                   guint time, 
1007                   gpointer data)
1008 {
1009         GtkTreeSelection *selection;
1010         GtkTreeModel *model;
1011         GtkTreeIter iter;
1012         GtkTreePath *source_row;
1013
1014         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1015         gtk_tree_selection_get_selected (selection, &model, &iter);
1016         source_row = gtk_tree_model_get_path (model, &iter);
1017
1018         gtk_tree_set_row_drag_data (selection_data,
1019                                     model,
1020                                     source_row);
1021
1022         gtk_tree_path_free (source_row);
1023 }
1024
1025 typedef struct _DndHelper {
1026         gboolean delete_source;
1027         GtkTreePath *source_row;
1028         GdkDragContext *context;
1029         guint time;
1030 } DndHelper;
1031
1032
1033 /*
1034  * This function is the callback of the
1035  * modest_mail_operation_xfer_msgs () and
1036  * modest_mail_operation_xfer_folder() calls. We check here if the
1037  * message/folder was correctly asynchronously transferred. The reason
1038  * to use the same callback is that the code is the same, it only has
1039  * to check that the operation went fine and then finalize the drag
1040  * and drop action
1041  */
1042 static void
1043 on_progress_changed (ModestMailOperation *mail_op, 
1044                      ModestMailOperationState *state,
1045                      gpointer user_data)
1046 {
1047         gboolean success;
1048         DndHelper *helper;
1049
1050         helper = (DndHelper *) user_data;
1051
1052         if (!state->finished)
1053                 return;
1054
1055         if (state->status == MODEST_MAIL_OPERATION_STATUS_SUCCESS) {
1056                 success = TRUE;
1057         } else {
1058                 success = FALSE;
1059         }
1060
1061         /* Notify the drag source. Never call delete, the monitor will
1062            do the job if needed */
1063         gtk_drag_finish (helper->context, success, FALSE, helper->time);
1064
1065         /* Free the helper */
1066         gtk_tree_path_free (helper->source_row);        
1067         g_slice_free (DndHelper, helper);
1068 }
1069
1070 /*
1071  * This function is used by drag_data_received_cb to manage drag and
1072  * drop of a header, i.e, and drag from the header view to the folder
1073  * view.
1074  */
1075 static void
1076 drag_and_drop_from_header_view (GtkTreeModel *source_model,
1077                                 GtkTreeModel *dest_model,
1078                                 GtkTreePath  *dest_row,
1079                                 DndHelper    *helper)
1080 {
1081         TnyList *headers;
1082         TnyHeader *header;
1083         TnyFolder *folder;
1084         ModestMailOperation *mail_op;
1085         GtkTreeIter source_iter, dest_iter;
1086
1087         /* Get header */
1088         gtk_tree_model_get_iter (source_model, &source_iter, helper->source_row);
1089         gtk_tree_model_get (source_model, &source_iter, 
1090                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, 
1091                             &header, -1);
1092
1093         /* Get Folder */
1094         gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
1095         gtk_tree_model_get (dest_model, &dest_iter, 
1096                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, 
1097                             &folder, -1);
1098
1099         /* Transfer message */
1100         mail_op = modest_mail_operation_new (MODEST_MAIL_OPERATION_TYPE_RECEIVE, NULL);
1101
1102         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1103                                          mail_op);
1104         g_signal_connect (G_OBJECT (mail_op), "progress-changed",
1105                           G_CALLBACK (on_progress_changed), helper);
1106
1107         /* FIXME: I replaced this because the API changed, but D&D
1108            should be reviewed in order to allow multiple drags*/
1109         headers = tny_simple_list_new ();
1110         tny_list_append (headers, G_OBJECT (header));
1111         modest_mail_operation_xfer_msgs (mail_op, headers, folder, helper->delete_source, NULL, NULL);
1112         
1113         /* Frees */
1114         g_object_unref (G_OBJECT (mail_op));
1115         g_object_unref (G_OBJECT (header));
1116         g_object_unref (G_OBJECT (folder));
1117         g_object_unref (headers);
1118 }
1119
1120 /*
1121  * This function is used by drag_data_received_cb to manage drag and
1122  * drop of a folder, i.e, and drag from the folder view to the same
1123  * folder view.
1124  */
1125 static void
1126 drag_and_drop_from_folder_view (GtkTreeModel     *source_model,
1127                                 GtkTreeModel     *dest_model,
1128                                 GtkTreePath      *dest_row,
1129                                 GtkSelectionData *selection_data,
1130                                 DndHelper        *helper)
1131 {
1132         ModestMailOperation *mail_op;
1133         GtkTreeIter parent_iter, iter;
1134         TnyFolderStore *parent_folder;
1135         TnyFolder *folder;
1136
1137         /* Check if the drag is possible */
1138 /*      if (!gtk_tree_path_compare (helper->source_row, dest_row) || */
1139 /*          !gtk_tree_drag_dest_row_drop_possible (GTK_TREE_DRAG_DEST (dest_model), */
1140 /*                                                 dest_row, */
1141 /*                                                 selection_data)) { */
1142         if (!gtk_tree_path_compare (helper->source_row, dest_row)) {
1143
1144                 gtk_drag_finish (helper->context, FALSE, FALSE, helper->time);
1145                 gtk_tree_path_free (helper->source_row);        
1146                 g_slice_free (DndHelper, helper);
1147                 return;
1148         }
1149
1150         /* Get data */
1151         gtk_tree_model_get_iter (source_model, &parent_iter, dest_row);
1152         gtk_tree_model_get_iter (source_model, &iter, helper->source_row);
1153         gtk_tree_model_get (source_model, &parent_iter, 
1154                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, 
1155                             &parent_folder, -1);
1156         gtk_tree_model_get (source_model, &iter,
1157                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
1158                             &folder, -1);
1159
1160         /* Do the mail operation */
1161         mail_op = modest_mail_operation_new_with_error_handling (MODEST_MAIL_OPERATION_TYPE_RECEIVE, 
1162                                                                  NULL,
1163                                                                  modest_ui_actions_move_folder_error_handler,
1164                                                                  NULL);
1165         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (), 
1166                                          mail_op);
1167         g_signal_connect (G_OBJECT (mail_op), "progress-changed",
1168                           G_CALLBACK (on_progress_changed), helper);
1169
1170         modest_mail_operation_xfer_folder (mail_op, 
1171                                            folder, 
1172                                            parent_folder,
1173                                            helper->delete_source);
1174         
1175         /* Frees */
1176         g_object_unref (G_OBJECT (parent_folder));
1177         g_object_unref (G_OBJECT (folder));
1178         g_object_unref (G_OBJECT (mail_op));
1179 }
1180
1181 /*
1182  * This function receives the data set by the "drag-data-get" signal
1183  * handler. This information comes within the #GtkSelectionData. This
1184  * function will manage both the drags of folders of the treeview and
1185  * drags of headers of the header view widget.
1186  */
1187 static void 
1188 on_drag_data_received (GtkWidget *widget, 
1189                        GdkDragContext *context, 
1190                        gint x, 
1191                        gint y, 
1192                        GtkSelectionData *selection_data, 
1193                        guint target_type, 
1194                        guint time, 
1195                        gpointer data)
1196 {
1197         GtkWidget *source_widget;
1198         GtkTreeModel *dest_model, *source_model;
1199         GtkTreePath *source_row, *dest_row;
1200         GtkTreeViewDropPosition pos;
1201         gboolean success = FALSE, delete_source = FALSE;
1202         DndHelper *helper = NULL; 
1203
1204         /* Do not allow further process */
1205         g_signal_stop_emission_by_name (widget, "drag-data-received");
1206         source_widget = gtk_drag_get_source_widget (context);
1207
1208         /* Get the action */
1209         if (context->action == GDK_ACTION_MOVE) {
1210                 delete_source = TRUE;
1211
1212                 /* Notify that there is no folder selected. We need to
1213                    do this in order to update the headers view (and
1214                    its monitors, because when moving, the old folder
1215                    won't longer exist. We can not wait for the end of
1216                    the operation, because the operation won't start if
1217                    the folder is in use */
1218                 if (source_widget == widget)
1219                         g_signal_emit (G_OBJECT (widget), 
1220                                        signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0, NULL, TRUE);
1221         }
1222
1223         /* Check if the get_data failed */
1224         if (selection_data == NULL || selection_data->length < 0)
1225                 gtk_drag_finish (context, success, FALSE, time);
1226
1227         /* Get the models */
1228         gtk_tree_get_row_drag_data (selection_data,
1229                                     &source_model,
1230                                     &source_row);
1231
1232         /* Select the destination model */
1233         if (source_widget == widget) {
1234                 dest_model = source_model;
1235         } else {
1236                 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
1237         }
1238
1239         /* Get the path to the destination row. Can not call
1240            gtk_tree_view_get_drag_dest_row() because the source row
1241            is not selected anymore */
1242         gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget), x, y,
1243                                            &dest_row, &pos);
1244
1245         /* Only allow drops IN other rows */
1246         if (!dest_row || pos == GTK_TREE_VIEW_DROP_BEFORE || pos == GTK_TREE_VIEW_DROP_AFTER)
1247                 gtk_drag_finish (context, success, FALSE, time);
1248
1249         /* Create the helper */
1250         helper = g_slice_new0 (DndHelper);
1251         helper->delete_source = delete_source;
1252         helper->source_row = gtk_tree_path_copy (source_row);
1253         helper->context = context;
1254         helper->time = time;
1255
1256         /* Drags from the header view */
1257         if (source_widget != widget) {
1258
1259                 drag_and_drop_from_header_view (source_model,
1260                                                 dest_model,
1261                                                 dest_row,
1262                                                 helper);
1263         } else {
1264
1265
1266                 drag_and_drop_from_folder_view (source_model,
1267                                                 dest_model,
1268                                                 dest_row,
1269                                                 selection_data, 
1270                                                 helper);
1271         }
1272
1273         /* Frees */
1274         gtk_tree_path_free (source_row);
1275         gtk_tree_path_free (dest_row);
1276 }
1277
1278 /*
1279  * We define a "drag-drop" signal handler because we do not want to
1280  * use the default one, because the default one always calls
1281  * gtk_drag_finish and we prefer to do it in the "drag-data-received"
1282  * signal handler, because there we have all the information available
1283  * to know if the dnd was a success or not.
1284  */
1285 static gboolean
1286 drag_drop_cb (GtkWidget      *widget,
1287               GdkDragContext *context,
1288               gint            x,
1289               gint            y,
1290               guint           time,
1291               gpointer        user_data) 
1292 {
1293         gpointer target;
1294
1295         if (!context->targets)
1296                 return FALSE;
1297
1298         /* Check if we're dragging a folder row */
1299         target = gtk_drag_dest_find_target (widget, context, NULL);
1300
1301         /* Request the data from the source. */
1302         gtk_drag_get_data(widget, context, target, time);
1303
1304     return TRUE;
1305 }
1306
1307 /*
1308  * This function expands a node of a tree view if it's not expanded
1309  * yet. Not sure why it needs the threads stuff, but gtk+`example code
1310  * does that, so that's why they're here.
1311  */
1312 static gint
1313 expand_row_timeout (gpointer data)
1314 {
1315         GtkTreeView *tree_view = data;
1316         GtkTreePath *dest_path = NULL;
1317         GtkTreeViewDropPosition pos;
1318         gboolean result = FALSE;
1319         
1320         GDK_THREADS_ENTER ();
1321         
1322         gtk_tree_view_get_drag_dest_row (tree_view,
1323                                          &dest_path,
1324                                          &pos);
1325         
1326         if (dest_path &&
1327             (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER ||
1328              pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)) {
1329                 gtk_tree_view_expand_row (tree_view, dest_path, FALSE);
1330                 gtk_tree_path_free (dest_path);
1331         }
1332         else {
1333                 if (dest_path)
1334                         gtk_tree_path_free (dest_path);
1335                 
1336                 result = TRUE;
1337         }
1338         
1339         GDK_THREADS_LEAVE ();
1340
1341         return result;
1342 }
1343
1344 /*
1345  * This function is called whenever the pointer is moved over a widget
1346  * while dragging some data. It installs a timeout that will expand a
1347  * node of the treeview if not expanded yet. This function also calls
1348  * gdk_drag_status in order to set the suggested action that will be
1349  * used by the "drag-data-received" signal handler to know if we
1350  * should do a move or just a copy of the data.
1351  */
1352 static gboolean
1353 on_drag_motion (GtkWidget      *widget,
1354                 GdkDragContext *context,
1355                 gint            x,
1356                 gint            y,
1357                 guint           time,
1358                 gpointer        user_data)  
1359 {
1360         GtkTreeViewDropPosition pos;
1361         GtkTreePath *dest_row;
1362         ModestFolderViewPrivate *priv;
1363         GdkDragAction suggested_action;
1364         gboolean valid_location = FALSE;
1365
1366         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
1367
1368         if (priv->timer_expander != 0) {
1369                 g_source_remove (priv->timer_expander);
1370                 priv->timer_expander = 0;
1371         }
1372
1373         gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
1374                                            x, y,
1375                                            &dest_row,
1376                                            &pos);
1377
1378         /* Do not allow drops between folders */
1379         if (!dest_row ||
1380             pos == GTK_TREE_VIEW_DROP_BEFORE ||
1381             pos == GTK_TREE_VIEW_DROP_AFTER) {
1382                 gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW (widget), NULL, 0);
1383                 gdk_drag_status(context, 0, time);
1384                 valid_location = FALSE;
1385                 goto out;
1386         } else {
1387                 valid_location = TRUE;
1388         }
1389
1390         /* Expand the selected row after 1/2 second */
1391         if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), dest_row)) {
1392                 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), dest_row, pos);
1393                 priv->timer_expander = g_timeout_add (500, expand_row_timeout, widget);
1394         }
1395
1396         /* Select the desired action. By default we pick MOVE */
1397         suggested_action = GDK_ACTION_MOVE;
1398
1399         if (context->actions == GDK_ACTION_COPY)
1400             gdk_drag_status(context, GDK_ACTION_COPY, time);
1401         else if (context->actions == GDK_ACTION_MOVE)
1402             gdk_drag_status(context, GDK_ACTION_MOVE, time);
1403         else if (context->actions & suggested_action)
1404             gdk_drag_status(context, suggested_action, time);
1405         else
1406             gdk_drag_status(context, GDK_ACTION_DEFAULT, time);
1407
1408  out:
1409         if (dest_row)
1410                 gtk_tree_path_free (dest_row);
1411         g_signal_stop_emission_by_name (widget, "drag-motion");
1412         return valid_location;
1413 }
1414
1415
1416 /* Folder view drag types */
1417 const GtkTargetEntry folder_view_drag_types[] =
1418 {
1419         { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, MODEST_FOLDER_ROW },
1420         { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_APP,    MODEST_HEADER_ROW }
1421 };
1422
1423 /*
1424  * This function sets the treeview as a source and a target for dnd
1425  * events. It also connects all the requirede signals.
1426  */
1427 static void
1428 setup_drag_and_drop (GtkTreeView *self)
1429 {
1430         /* Set up the folder view as a dnd destination. Set only the
1431            highlight flag, otherwise gtk will have a different
1432            behaviour */
1433         gtk_drag_dest_set (GTK_WIDGET (self),
1434                            GTK_DEST_DEFAULT_HIGHLIGHT,
1435                            folder_view_drag_types,
1436                            G_N_ELEMENTS (folder_view_drag_types),
1437                            GDK_ACTION_MOVE | GDK_ACTION_COPY);
1438
1439         g_signal_connect (G_OBJECT (self),
1440                           "drag_data_received",
1441                           G_CALLBACK (on_drag_data_received),
1442                           NULL);
1443
1444
1445         /* Set up the treeview as a dnd source */
1446         gtk_drag_source_set (GTK_WIDGET (self),
1447                              GDK_BUTTON1_MASK,
1448                              folder_view_drag_types,
1449                              G_N_ELEMENTS (folder_view_drag_types),
1450                              GDK_ACTION_MOVE | GDK_ACTION_COPY);
1451
1452         g_signal_connect (G_OBJECT (self),
1453                           "drag_motion",
1454                           G_CALLBACK (on_drag_motion),
1455                           NULL);
1456         
1457         g_signal_connect (G_OBJECT (self),
1458                           "drag_data_get",
1459                           G_CALLBACK (on_drag_data_get),
1460                           NULL);
1461
1462         g_signal_connect (G_OBJECT (self),
1463                           "drag_drop",
1464                           G_CALLBACK (drag_drop_cb),
1465                           NULL);
1466 }
1467
1468 /*
1469  * This function manages the navigation through the folders using the
1470  * keyboard or the hardware keys in the device
1471  */
1472 static gboolean
1473 on_key_pressed (GtkWidget *self,
1474                 GdkEventKey *event,
1475                 gpointer user_data)
1476 {
1477         GtkTreeSelection *selection;
1478         GtkTreeIter iter;
1479         GtkTreeModel *model;
1480         gboolean retval = FALSE;
1481
1482         /* Up and Down are automatically managed by the treeview */
1483         if (event->keyval == GDK_Return) {
1484                 /* Expand/Collapse the selected row */
1485                 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1486                 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
1487                         GtkTreePath *path;
1488
1489                         path = gtk_tree_model_get_path (model, &iter);
1490
1491                         if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (self), path))
1492                                 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1493                         else
1494                                 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1495                         gtk_tree_path_free (path);
1496                 }
1497                 /* No further processing */
1498                 retval = TRUE;
1499         }
1500
1501         return retval;
1502 }
1503
1504 /*
1505  * We listen to the changes in the local folder account name key,
1506  * because we want to show the right name in the view. The local
1507  * folder account name corresponds to the device name in the Maemo
1508  * version. We do this because we do not want to query gconf on each
1509  * tree view refresh. It's better to cache it and change whenever
1510  * necessary.
1511  */
1512 static void 
1513 on_configuration_key_changed (ModestConf* conf, 
1514                               const gchar *key, 
1515                               ModestConfEvent event, 
1516                               ModestFolderView *self)
1517 {
1518         ModestFolderViewPrivate *priv;
1519
1520         if (!key)
1521                 return;
1522
1523         g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
1524         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1525
1526         if (!strcmp (key, MODEST_CONF_DEVICE_NAME)) {
1527                 g_free (priv->local_account_name);
1528
1529                 if (event == MODEST_CONF_EVENT_KEY_UNSET)
1530                         priv->local_account_name = g_strdup (MODEST_LOCAL_FOLDERS_DEFAULT_DISPLAY_NAME);
1531                 else
1532                         priv->local_account_name = modest_conf_get_string (modest_runtime_get_conf(),
1533                                                                            MODEST_CONF_DEVICE_NAME, NULL);
1534
1535                 /* Force a redraw */
1536 #if GTK_CHECK_VERSION(2, 8, 0) /* gtk_tree_view_column_queue_resize is only available in GTK+ 2.8 */
1537                 GtkTreeViewColumn * tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self), 
1538                                                                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN);
1539                 gtk_tree_view_column_queue_resize (tree_column);
1540 #endif
1541         }
1542 }
1543
1544 void 
1545 modest_folder_view_set_style (ModestFolderView *self,
1546                               ModestFolderViewStyle style)
1547 {
1548         ModestFolderViewPrivate *priv;
1549
1550         g_return_if_fail (self);
1551         
1552         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1553
1554         priv->style = style;
1555 }
1556
1557 void
1558 modest_folder_view_set_account_id_of_visible_server_account (ModestFolderView *self,
1559                                                              const gchar *account_id)
1560 {
1561         ModestFolderViewPrivate *priv;
1562         GtkTreeModel *model;
1563
1564         g_return_if_fail (self);
1565         
1566         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1567
1568         /* This will be used by the filter_row callback,
1569          * to decided which rows to show: */
1570         if (priv->visible_account_id)
1571                 g_free (priv->visible_account_id);
1572         priv->visible_account_id = g_strdup (account_id);
1573
1574         /* Refilter */
1575         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1576         if (GTK_IS_TREE_MODEL_FILTER (model))
1577                 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
1578 }
1579
1580 const gchar *
1581 modest_folder_view_get_account_id_of_visible_server_account (ModestFolderView *self)
1582 {
1583         ModestFolderViewPrivate *priv;
1584
1585         g_return_val_if_fail (self, NULL);
1586         
1587         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1588
1589         return (const gchar *) priv->visible_account_id;
1590 }