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