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