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