77691da3f76f7fd208e2d2bd0f02073e5714535a
[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-account.h>
45 #include <modest-tny-folder.h>
46 #include <modest-tny-local-folders-account.h>
47 #include <modest-tny-outbox-account.h>
48 #include <modest-marshal.h>
49 #include <modest-icon-names.h>
50 #include <modest-tny-account-store.h>
51 #include <modest-text-utils.h>
52 #include <modest-runtime.h>
53 #include "modest-folder-view.h"
54 #include <modest-dnd.h>
55 #include <modest-platform.h>
56 #include <modest-widget-memory.h>
57 #include <modest-ui-actions.h>
58
59 /* 'private'/'protected' functions */
60 static void modest_folder_view_class_init  (ModestFolderViewClass *klass);
61 static void modest_folder_view_init        (ModestFolderView *obj);
62 static void modest_folder_view_finalize    (GObject *obj);
63
64 static void         tny_account_store_view_init (gpointer g, 
65                                                  gpointer iface_data);
66
67 static void         modest_folder_view_set_account_store (TnyAccountStoreView *self, 
68                                                           TnyAccountStore     *account_store);
69
70 static void         on_selection_changed   (GtkTreeSelection *sel, gpointer data);
71
72 static void         on_account_removed     (TnyAccountStore *self, 
73                                             TnyAccount *account,
74                                             gpointer user_data);
75
76 static void         on_account_inserted    (TnyAccountStore *self, 
77                                             TnyAccount *account,
78                                             gpointer user_data);
79
80 static gint         cmp_rows               (GtkTreeModel *tree_model, 
81                                             GtkTreeIter *iter1, 
82                                             GtkTreeIter *iter2,
83                                             gpointer user_data);
84
85 static gboolean     filter_row             (GtkTreeModel *model,
86                                             GtkTreeIter *iter,
87                                             gpointer data);
88
89 static gboolean     on_key_pressed         (GtkWidget *self,
90                                             GdkEventKey *event,
91                                             gpointer user_data);
92
93 static void         on_configuration_key_changed         (ModestConf* conf, 
94                                                           const gchar *key, 
95                                                           ModestConfEvent event, 
96                                                           ModestFolderView *self);
97
98 /* DnD functions */
99 static void         on_drag_data_get       (GtkWidget *widget, 
100                                             GdkDragContext *context, 
101                                             GtkSelectionData *selection_data, 
102                                             guint info, 
103                                             guint time, 
104                                             gpointer data);
105
106 static void         on_drag_data_received  (GtkWidget *widget, 
107                                             GdkDragContext *context, 
108                                             gint x, 
109                                             gint y, 
110                                             GtkSelectionData *selection_data, 
111                                             guint info, 
112                                             guint time, 
113                                             gpointer data);
114
115 static gboolean     on_drag_motion         (GtkWidget      *widget,
116                                             GdkDragContext *context,
117                                             gint            x,
118                                             gint            y,
119                                             guint           time,
120                                             gpointer        user_data);
121
122 static gint         expand_row_timeout     (gpointer data);
123
124 static void         setup_drag_and_drop    (GtkTreeView *self);
125
126 static gboolean     _clipboard_set_selected_data (ModestFolderView *folder_view, 
127                                                   gboolean delete);
128
129 static void         _clear_hidding_filter (ModestFolderView *folder_view);
130
131 static void          on_row_changed_maybe_select_folder (GtkTreeModel     *tree_model, 
132                                                          GtkTreePath      *path, 
133                                                          GtkTreeIter      *iter,
134                                                          ModestFolderView *self);
135
136 enum {
137         FOLDER_SELECTION_CHANGED_SIGNAL,
138         FOLDER_DISPLAY_NAME_CHANGED_SIGNAL,
139         LAST_SIGNAL
140 };
141
142 typedef struct _ModestFolderViewPrivate ModestFolderViewPrivate;
143 struct _ModestFolderViewPrivate {
144         TnyAccountStore      *account_store;
145         TnyFolderStore       *cur_folder_store;
146
147         TnyFolder            *folder_to_select; /* folder to select after the next update */
148
149         gulong                changed_signal;
150         gulong                account_inserted_signal;
151         gulong                account_removed_signal;
152         gulong                conf_key_signal;
153         
154         /* not unref this object, its a singlenton */
155         ModestEmailClipboard *clipboard;
156
157         /* Filter tree model */
158         gchar **hidding_ids;
159         guint n_selected;
160
161         TnyFolderStoreQuery  *query;
162         guint                 timer_expander;
163
164         gchar                *local_account_name;
165         gchar                *visible_account_id;
166         ModestFolderViewStyle style;
167
168         gboolean              reselect; /* we use this to force a reselection of the INBOX */
169         gboolean                                                        show_non_move;
170 };
171 #define MODEST_FOLDER_VIEW_GET_PRIVATE(o)                       \
172         (G_TYPE_INSTANCE_GET_PRIVATE((o),                       \
173                                      MODEST_TYPE_FOLDER_VIEW,   \
174                                      ModestFolderViewPrivate))
175 /* globals */
176 static GObjectClass *parent_class = NULL;
177
178 static guint signals[LAST_SIGNAL] = {0}; 
179
180 GType
181 modest_folder_view_get_type (void)
182 {
183         static GType my_type = 0;
184         if (!my_type) {
185                 static const GTypeInfo my_info = {
186                         sizeof(ModestFolderViewClass),
187                         NULL,           /* base init */
188                         NULL,           /* base finalize */
189                         (GClassInitFunc) modest_folder_view_class_init,
190                         NULL,           /* class finalize */
191                         NULL,           /* class data */
192                         sizeof(ModestFolderView),
193                         1,              /* n_preallocs */
194                         (GInstanceInitFunc) modest_folder_view_init,
195                         NULL
196                 };
197
198                 static const GInterfaceInfo tny_account_store_view_info = {
199                         (GInterfaceInitFunc) tny_account_store_view_init, /* interface_init */
200                         NULL,         /* interface_finalize */
201                         NULL          /* interface_data */
202                 };
203
204                                 
205                 my_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
206                                                   "ModestFolderView",
207                                                   &my_info, 0);
208
209                 g_type_add_interface_static (my_type, 
210                                              TNY_TYPE_ACCOUNT_STORE_VIEW, 
211                                              &tny_account_store_view_info);
212         }
213         return my_type;
214 }
215
216 static void
217 modest_folder_view_class_init (ModestFolderViewClass *klass)
218 {
219         GObjectClass *gobject_class;
220         gobject_class = (GObjectClass*) klass;
221
222         parent_class            = g_type_class_peek_parent (klass);
223         gobject_class->finalize = modest_folder_view_finalize;
224
225         g_type_class_add_private (gobject_class,
226                                   sizeof(ModestFolderViewPrivate));
227         
228         signals[FOLDER_SELECTION_CHANGED_SIGNAL] = 
229                 g_signal_new ("folder_selection_changed",
230                               G_TYPE_FROM_CLASS (gobject_class),
231                               G_SIGNAL_RUN_FIRST,
232                               G_STRUCT_OFFSET (ModestFolderViewClass,
233                                                folder_selection_changed),
234                               NULL, NULL,
235                               modest_marshal_VOID__POINTER_BOOLEAN,
236                               G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_BOOLEAN);
237
238         /*
239          * This signal is emitted whenever the currently selected
240          * folder display name is computed. Note that the name could
241          * be different to the folder name, because we could append
242          * the unread messages count to the folder name to build the
243          * folder display name
244          */
245         signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL] = 
246                 g_signal_new ("folder-display-name-changed",
247                               G_TYPE_FROM_CLASS (gobject_class),
248                               G_SIGNAL_RUN_FIRST,
249                               G_STRUCT_OFFSET (ModestFolderViewClass,
250                                                folder_display_name_changed),
251                               NULL, NULL,
252                               g_cclosure_marshal_VOID__STRING,
253                               G_TYPE_NONE, 1, G_TYPE_STRING);
254 }
255
256 /* Simplify checks for NULLs: */
257 static gboolean strings_are_equal (const gchar *a, const gchar *b)
258 {
259         if (!a && !b)
260                 return TRUE;
261         if (a && b)
262         {
263                 return (strcmp (a, b) == 0);
264         }
265         else
266                 return FALSE;
267 }
268
269 static gboolean on_model_foreach_set_name(GtkTreeModel *model, GtkTreePath *path,  GtkTreeIter *iter, gpointer data)
270 {
271         GObject *instance = NULL;
272         
273         gtk_tree_model_get (model, iter,
274                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
275                             -1);
276                             
277         if (!instance)
278                 return FALSE; /* keep walking */
279                         
280         if (!TNY_IS_ACCOUNT (instance)) {
281                 g_object_unref (instance);
282                 return FALSE; /* keep walking */        
283         }    
284         
285         /* Check if this is the looked-for account: */
286         TnyAccount *this_account = TNY_ACCOUNT (instance);
287         TnyAccount *account = TNY_ACCOUNT (data);
288         
289         const gchar *this_account_id = tny_account_get_id(this_account);
290         const gchar *account_id = tny_account_get_id(account);
291         g_object_unref (instance);
292         instance = NULL;
293
294         /* printf ("DEBUG: %s: this_account_id=%s, account_id=%s\n", __FUNCTION__, this_account_id, account_id); */
295         if (strings_are_equal(this_account_id, account_id)) {
296                 /* Tell the model that the data has changed, so that
297                  * it calls the cell_data_func callbacks again: */
298                 /* TODO: This does not seem to actually cause the new string to be shown: */
299                 gtk_tree_model_row_changed (model, path, iter);
300                 
301                 return TRUE; /* stop walking */
302         }
303         
304         return FALSE; /* keep walking */
305 }
306
307 typedef struct 
308 {
309         ModestFolderView *self;
310         gchar *previous_name;
311 } GetMmcAccountNameData;
312
313 static void on_get_mmc_account_name (TnyStoreAccount* account, gpointer user_data)
314 {
315         /* printf ("DEBU1G: %s: account name=%s\n", __FUNCTION__, tny_account_get_name (TNY_ACCOUNT(account))); */
316
317         GetMmcAccountNameData *data = (GetMmcAccountNameData*)user_data;
318         
319         if (!strings_are_equal (
320                 tny_account_get_name(TNY_ACCOUNT(account)), 
321                 data->previous_name)) {
322         
323                 /* Tell the model that the data has changed, so that 
324                  * it calls the cell_data_func callbacks again: */
325                 ModestFolderView *self = data->self;
326                 GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
327                 if (model)
328                         gtk_tree_model_foreach(model, on_model_foreach_set_name, account);
329         }
330
331         g_free (data->previous_name);
332         g_slice_free (GetMmcAccountNameData, data);
333 }
334
335 static void
336 text_cell_data  (GtkTreeViewColumn *column,  GtkCellRenderer *renderer,
337                  GtkTreeModel *tree_model,  GtkTreeIter *iter,  gpointer data)
338 {
339         ModestFolderViewPrivate *priv;
340         GObject *rendobj;
341         gchar *fname = NULL;
342         gint unread = 0;
343         gint all = 0;
344         TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
345         GObject *instance = NULL;
346         
347         g_return_if_fail (column);
348         g_return_if_fail (tree_model);
349
350         gtk_tree_model_get (tree_model, iter,
351                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &fname,
352                             TNY_GTK_FOLDER_STORE_TREE_MODEL_ALL_COLUMN, &all,
353                             TNY_GTK_FOLDER_STORE_TREE_MODEL_UNREAD_COLUMN, &unread,
354                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
355                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
356                             -1);
357         rendobj = G_OBJECT(renderer);
358
359         if (!fname)
360                 return;
361
362         if (!instance) {
363                 g_free (fname);
364                 return;
365         }
366
367         ModestFolderView *self = MODEST_FOLDER_VIEW (data);
368         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE (self);
369         
370         gchar *item_name = NULL;
371         gint item_weight = 400;
372         
373         if (type != TNY_FOLDER_TYPE_ROOT) {
374                 gint number = 0;
375                 
376                 if (modest_tny_folder_is_local_folder (TNY_FOLDER (instance)) ||
377                     modest_tny_folder_is_memory_card_folder (TNY_FOLDER (instance))) {
378                         type = modest_tny_folder_get_local_or_mmc_folder_type (TNY_FOLDER (instance));
379                         if (type != TNY_FOLDER_TYPE_UNKNOWN) {
380                                 g_free (fname);
381                                 fname = g_strdup(modest_local_folder_info_get_type_display_name (type));
382                         }
383                 }
384
385                 /* Select the number to show: the unread or unsent messages */
386                 if ((type == TNY_FOLDER_TYPE_DRAFTS) || (type == TNY_FOLDER_TYPE_OUTBOX))
387                         number = all;
388                 else
389                         number = unread;
390                 
391                 /* Use bold font style if there are unread or unset messages */
392                 if (number > 0) {
393                         item_name = g_strdup_printf ("%s (%d)", fname, number);
394                         item_weight = 800;
395                 } else {
396                         item_name = g_strdup (fname);
397                         item_weight = 400;
398                 }
399                 
400         } else if (TNY_IS_ACCOUNT (instance)) {
401                 /* If it's a server account */
402                 if (modest_tny_account_is_virtual_local_folders (
403                                 TNY_ACCOUNT (instance))) {
404                         item_name = g_strdup (priv->local_account_name);
405                         item_weight = 800;
406                 } else if (modest_tny_account_is_memory_card_account (
407                                 TNY_ACCOUNT (instance))) {
408                         /* fname is only correct when the items are first 
409                          * added to the model, not when the account is 
410                          * changed later, so get the name from the account
411                          * instance: */
412                         item_name = g_strdup (tny_account_get_name (
413                                 TNY_ACCOUNT (instance)));
414                         item_weight = 800;
415                 } else {
416                         item_name = g_strdup (fname);
417                         item_weight = 800;
418                 }
419         }
420         
421         if (!item_name)
422                 item_name = g_strdup ("unknown");
423                         
424         if (item_name && item_weight) {
425                 /* Set the name in the treeview cell: */
426                 g_object_set (rendobj,"text", item_name, "weight", item_weight, NULL);
427                 
428                 /* Notify display name observers */
429                 /* TODO: What listens for this signal, and how can it use only the new name? */
430                 if (G_OBJECT (priv->cur_folder_store) == instance) {
431                         g_signal_emit (G_OBJECT(self),
432                                                signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
433                                                item_name);
434                 }
435                 g_free (item_name);
436                 
437         }
438         
439         /* If it is a Memory card account, make sure that we have the correct name.
440          * This function will be trigerred again when the name has been retrieved: */
441         if (TNY_IS_STORE_ACCOUNT (instance) && 
442                 modest_tny_account_is_memory_card_account (TNY_ACCOUNT (instance))) {
443
444                 /* Get the account name asynchronously: */
445                 GetMmcAccountNameData *callback_data = 
446                         g_slice_new0(GetMmcAccountNameData);
447                 callback_data->self = self;
448
449                 const gchar *name = tny_account_get_name (TNY_ACCOUNT(instance));
450                 if (name)
451                         callback_data->previous_name = g_strdup (name); 
452
453                 modest_tny_account_get_mmc_account_name (TNY_STORE_ACCOUNT (instance), 
454                         on_get_mmc_account_name, callback_data);
455         }
456                         
457         g_object_unref (G_OBJECT (instance));
458         g_free (fname);
459 }
460
461 static void
462 icon_cell_data  (GtkTreeViewColumn *column,  GtkCellRenderer *renderer,
463                  GtkTreeModel *tree_model,  GtkTreeIter *iter, gpointer data)
464 {
465         GObject *rendobj = NULL, *instance = NULL;
466         GdkPixbuf *pixbuf = NULL;
467         TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
468         gchar *fname = NULL;
469         const gchar *account_id = NULL;
470         gint unread = 0;
471         gboolean has_children;
472         
473         rendobj = G_OBJECT(renderer);
474         gtk_tree_model_get (tree_model, iter,
475                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
476                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &fname,
477                             TNY_GTK_FOLDER_STORE_TREE_MODEL_UNREAD_COLUMN, &unread,
478                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
479                             -1);
480         has_children = gtk_tree_model_iter_has_child (tree_model, iter);
481
482         if (!fname)
483                 return;
484
485         if (!instance) {
486                 g_free (fname);
487                 return;
488         }
489
490         /* MERGE is not needed anymore as the folder now has the correct type jschmid */
491         /* We include the MERGE type here because it's used to create
492            the local OUTBOX folder */
493         if (type == TNY_FOLDER_TYPE_NORMAL || 
494             type == TNY_FOLDER_TYPE_UNKNOWN) {
495                 type = modest_tny_folder_guess_folder_type (TNY_FOLDER (instance));
496         }
497
498         switch (type) {
499         case TNY_FOLDER_TYPE_ROOT:
500                 if (TNY_IS_ACCOUNT (instance)) {
501                         
502                         if (modest_tny_account_is_virtual_local_folders (
503                                 TNY_ACCOUNT (instance))) {
504                                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_LOCAL_FOLDERS);
505                         }
506                         else {
507                                 account_id = tny_account_get_id (TNY_ACCOUNT (instance));
508                                 
509                                 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
510                                         pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_MMC);
511                                 else
512                                         pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_ACCOUNT);
513                         }
514                 }
515                 break;
516         case TNY_FOLDER_TYPE_INBOX:
517             pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_INBOX);
518             break;
519         case TNY_FOLDER_TYPE_OUTBOX:
520                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_OUTBOX);
521                 break;
522         case TNY_FOLDER_TYPE_JUNK:
523                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_JUNK);
524                 break;
525         case TNY_FOLDER_TYPE_SENT:
526                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_SENT);
527                 break;
528         case TNY_FOLDER_TYPE_TRASH:
529                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_TRASH);
530                 break;
531         case TNY_FOLDER_TYPE_DRAFTS:
532                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_DRAFTS);
533                 break;
534         case TNY_FOLDER_TYPE_NORMAL:
535         default:
536                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_NORMAL);
537                 break;
538         }
539         
540         g_object_unref (G_OBJECT (instance));
541         g_free (fname);
542
543         /* Set pixbuf */
544         g_object_set (rendobj, "pixbuf", pixbuf, NULL);
545         if (has_children && (pixbuf != NULL)) {
546                 GdkPixbuf *open_pixbuf, *closed_pixbuf;
547                 GdkPixbuf *open_emblem, *closed_emblem;
548                 open_pixbuf = gdk_pixbuf_copy (pixbuf);
549                 closed_pixbuf = gdk_pixbuf_copy (pixbuf);
550                 open_emblem = modest_platform_get_icon ("qgn_list_gene_fldr_exp");
551                 closed_emblem = modest_platform_get_icon ("qgn_list_gene_fldr_clp");
552
553                 if (open_emblem) {
554                         gdk_pixbuf_composite (open_emblem, open_pixbuf, 0, 0, 
555                                               MIN (gdk_pixbuf_get_width (open_emblem), 
556                                                    gdk_pixbuf_get_width (open_pixbuf)),
557                                               MIN (gdk_pixbuf_get_height (open_emblem), 
558                                                    gdk_pixbuf_get_height (open_pixbuf)),
559                                               0, 0, 1, 1, GDK_INTERP_NEAREST, 255);
560                         g_object_set (rendobj, "pixbuf-expander-open", open_pixbuf, NULL);
561                         g_object_unref (open_emblem);
562                 }
563                 if (closed_emblem) {
564                         gdk_pixbuf_composite (closed_emblem, closed_pixbuf, 0, 0, 
565                                               MIN (gdk_pixbuf_get_width (closed_emblem), 
566                                                    gdk_pixbuf_get_width (closed_pixbuf)),
567                                               MIN (gdk_pixbuf_get_height (closed_emblem), 
568                                                    gdk_pixbuf_get_height (closed_pixbuf)),
569                                               0, 0, 1, 1, GDK_INTERP_NEAREST, 255);
570                         g_object_set (rendobj, "pixbuf-expander-closed", closed_pixbuf, NULL);
571                         g_object_unref (closed_emblem);
572                 }
573                 if (closed_pixbuf)
574                         g_object_unref (closed_pixbuf);
575                 if (open_pixbuf)
576                         g_object_unref (open_pixbuf);
577         }
578
579         if (pixbuf != NULL)
580                 g_object_unref (pixbuf);
581 }
582
583 static void
584 add_columns (GtkWidget *treeview)
585 {
586         GtkTreeViewColumn *column;
587         GtkCellRenderer *renderer;
588         GtkTreeSelection *sel;
589
590         /* Create column */
591         column = gtk_tree_view_column_new ();   
592         
593         /* Set icon and text render function */
594         renderer = gtk_cell_renderer_pixbuf_new();
595         gtk_tree_view_column_pack_start (column, renderer, FALSE);
596         gtk_tree_view_column_set_cell_data_func(column, renderer,
597                                                 icon_cell_data, treeview, NULL);
598         
599         renderer = gtk_cell_renderer_text_new();
600         gtk_tree_view_column_pack_start (column, renderer, FALSE);
601         gtk_tree_view_column_set_cell_data_func(column, renderer,
602                                                 text_cell_data, treeview, NULL);
603         
604         /* Set selection mode */
605         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(treeview));
606         gtk_tree_selection_set_mode (sel, GTK_SELECTION_SINGLE);
607
608         /* Set treeview appearance */
609         gtk_tree_view_column_set_spacing (column, 2);
610         gtk_tree_view_column_set_resizable (column, TRUE);
611         gtk_tree_view_column_set_fixed_width (column, TRUE);            
612         gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(treeview), FALSE);
613         gtk_tree_view_set_enable_search (GTK_TREE_VIEW(treeview), FALSE);
614
615         /* Add column */
616         gtk_tree_view_append_column (GTK_TREE_VIEW(treeview),column);
617 }
618
619 static void
620 modest_folder_view_init (ModestFolderView *obj)
621 {
622         ModestFolderViewPrivate *priv;
623         ModestConf *conf;
624         
625         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
626         
627         priv->timer_expander = 0;
628         priv->account_store  = NULL;
629         priv->query          = NULL;
630         priv->style          = MODEST_FOLDER_VIEW_STYLE_SHOW_ALL;
631         priv->cur_folder_store   = NULL;
632         priv->visible_account_id = NULL;
633         priv->folder_to_select = NULL;
634
635         /* Initialize the local account name */
636         conf = modest_runtime_get_conf();
637         priv->local_account_name = modest_conf_get_string (conf, MODEST_CONF_DEVICE_NAME, NULL);
638
639         /* Init email clipboard */
640         priv->clipboard = modest_runtime_get_email_clipboard ();
641         priv->hidding_ids = NULL;
642         priv->n_selected = 0;
643         priv->reselect = FALSE;
644         priv->show_non_move = TRUE;
645
646         /* Build treeview */
647         add_columns (GTK_WIDGET (obj));
648
649         /* Setup drag and drop */
650         setup_drag_and_drop (GTK_TREE_VIEW(obj));
651
652         /* Connect signals */
653         g_signal_connect (G_OBJECT (obj), 
654                           "key-press-event", 
655                           G_CALLBACK (on_key_pressed), NULL);
656
657         /*
658          * Track changes in the local account name (in the device it
659          * will be the device name)
660          */
661         priv->conf_key_signal = 
662                 g_signal_connect (G_OBJECT(conf), 
663                                   "key_changed",
664                                   G_CALLBACK(on_configuration_key_changed), obj);
665 }
666
667 static void
668 tny_account_store_view_init (gpointer g, gpointer iface_data)
669 {
670         TnyAccountStoreViewIface *klass = (TnyAccountStoreViewIface *)g;
671
672         klass->set_account_store_func = modest_folder_view_set_account_store;
673
674         return;
675 }
676
677 static void
678 modest_folder_view_finalize (GObject *obj)
679 {
680         ModestFolderViewPrivate *priv;
681         GtkTreeSelection    *sel;
682         
683         g_return_if_fail (obj);
684         
685         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
686
687         if (priv->timer_expander != 0) {
688                 g_source_remove (priv->timer_expander);
689                 priv->timer_expander = 0;
690         }
691
692         if (priv->account_store) {
693                 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
694                                              priv->account_inserted_signal);
695                 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
696                                              priv->account_removed_signal);
697                 g_object_unref (G_OBJECT(priv->account_store));
698                 priv->account_store = NULL;
699         }
700
701         if (priv->query) {
702                 g_object_unref (G_OBJECT (priv->query));
703                 priv->query = NULL;
704         }
705
706         if (priv->folder_to_select) {
707                 g_object_unref (G_OBJECT(priv->folder_to_select));
708                 priv->folder_to_select = NULL;
709         }
710    
711         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(obj));
712         if (sel)
713                 g_signal_handler_disconnect (G_OBJECT(sel), priv->changed_signal);
714
715         g_free (priv->local_account_name);
716         g_free (priv->visible_account_id);
717         
718         if (priv->conf_key_signal) {
719                 g_signal_handler_disconnect (modest_runtime_get_conf (),
720                                              priv->conf_key_signal);
721                 priv->conf_key_signal = 0;
722         }
723
724         if (priv->cur_folder_store) {
725                 if (TNY_IS_FOLDER(priv->cur_folder_store))
726                         tny_folder_sync (TNY_FOLDER(priv->cur_folder_store), FALSE, NULL);
727                         /* FALSE --> expunge the message */
728
729                 g_object_unref (priv->cur_folder_store);
730                 priv->cur_folder_store = NULL;
731         }
732
733         /* Clear hidding array created by cut operation */
734         _clear_hidding_filter (MODEST_FOLDER_VIEW (obj));
735
736         G_OBJECT_CLASS(parent_class)->finalize (obj);
737 }
738
739
740 static void
741 modest_folder_view_set_account_store (TnyAccountStoreView *self, TnyAccountStore *account_store)
742 {
743         ModestFolderViewPrivate *priv;
744         TnyDevice *device;
745
746         g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
747         g_return_if_fail (TNY_IS_ACCOUNT_STORE (account_store));
748
749         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
750         device = tny_account_store_get_device (account_store);
751
752         if (G_UNLIKELY (priv->account_store)) {
753
754                 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
755                                                    priv->account_inserted_signal))
756                         g_signal_handler_disconnect (G_OBJECT (priv->account_store),
757                                                      priv->account_inserted_signal);
758                 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store), 
759                                                    priv->account_removed_signal))
760                         g_signal_handler_disconnect (G_OBJECT (priv->account_store), 
761                                                      priv->account_removed_signal);
762
763                 g_object_unref (G_OBJECT (priv->account_store));
764         }
765
766         priv->account_store = g_object_ref (G_OBJECT (account_store));
767
768         priv->account_removed_signal = 
769                 g_signal_connect (G_OBJECT(account_store), "account_removed",
770                                   G_CALLBACK (on_account_removed), self);
771
772         priv->account_inserted_signal =
773                 g_signal_connect (G_OBJECT(account_store), "account_inserted",
774                                   G_CALLBACK (on_account_inserted), self);
775
776
777 /*      g_signal_connect (G_OBJECT(account_store), "connecting_finished", */
778 /*                              G_CALLBACK (on_accounts_reloaded), self); */
779
780 /*      on_accounts_reloaded (account_store, (gpointer ) self); */
781
782         modest_folder_view_update_model (MODEST_FOLDER_VIEW (self), account_store);
783         
784         g_object_unref (G_OBJECT (device));
785 }
786
787 static void
788 on_account_inserted (TnyAccountStore *account_store, 
789                      TnyAccount *account,
790                      gpointer user_data)
791 {
792         ModestFolderViewPrivate *priv;
793         GtkTreeModel *sort_model, *filter_model;
794
795         /* Ignore transport account insertions, we're not showing them
796            in the folder view */
797         if (TNY_IS_TRANSPORT_ACCOUNT (account))
798                 return;
799
800         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (user_data);
801
802         /* If we're adding a new account, and there is no previous
803            one, we need to select the visible server account */
804         if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE &&
805             !priv->visible_account_id)
806                 modest_widget_memory_restore (modest_runtime_get_conf(), 
807                                               G_OBJECT (user_data),
808                                               MODEST_CONF_FOLDER_VIEW_KEY);
809
810         /* Get the inner model */
811         filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (user_data));
812         sort_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
813
814         /* Insert the account in the model */
815         tny_list_append (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
816                          G_OBJECT (account));
817 }
818
819 static void
820 on_account_removed (TnyAccountStore *account_store, 
821                     TnyAccount *account,
822                     gpointer user_data)
823 {
824         ModestFolderView *self = NULL;
825         ModestFolderViewPrivate *priv;
826         GtkTreeModel *sort_model, *filter_model;
827
828         self = MODEST_FOLDER_VIEW (user_data);
829         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
830
831         /* TODO: invalidate the cur_folder_* and folder_to_select things */
832
833         /* Remove the account from the model */
834         filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
835         sort_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
836         tny_list_remove (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
837                          G_OBJECT (account));
838
839         /* If the removed account is the currently viewed one then
840            clear the configuration value. The new visible account will be the default account */
841         if (priv->visible_account_id &&
842             !strcmp (priv->visible_account_id, tny_account_get_id (account))) {
843
844                 /* Clear the current visible account_id */
845                 modest_folder_view_set_account_id_of_visible_server_account (self, NULL);
846
847                 /* Call the restore method, this will set the new visible account */
848                 modest_widget_memory_restore (modest_runtime_get_conf(), G_OBJECT(self),
849                                               MODEST_CONF_FOLDER_VIEW_KEY);
850
851                 /* Select the INBOX */
852                 modest_folder_view_select_first_inbox_or_local (self);
853         }
854 }
855
856 void
857 modest_folder_view_set_title (ModestFolderView *self, const gchar *title)
858 {
859         GtkTreeViewColumn *col;
860         
861         g_return_if_fail (self);
862
863         col = gtk_tree_view_get_column (GTK_TREE_VIEW(self), 0);
864         if (!col) {
865                 g_printerr ("modest: failed get column for title\n");
866                 return;
867         }
868
869         gtk_tree_view_column_set_title (col, title);
870         gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self),
871                                            title != NULL);
872 }
873
874 static gboolean
875 modest_folder_view_on_map (ModestFolderView *self, 
876                            GdkEventExpose *event,
877                            gpointer data)
878 {
879         ModestFolderViewPrivate *priv;
880
881         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
882
883         /* This won't happen often */
884         if (G_UNLIKELY (priv->reselect)) {
885                 /* Select the first inbox or the local account if not found */
886
887                 /* TODO: this could cause a lock at startup, so we
888                    comment it for the moment. We know that this will
889                    be a bug, because the INBOX is not selected, but we
890                    need to rewrite some parts of Modest to avoid the
891                    deathlock situation */
892                 /* TODO: check if this is still the case */
893                 priv->reselect = FALSE;
894                 modest_folder_view_select_first_inbox_or_local (self);
895                 /* Notify the display name observers */
896                 g_signal_emit (G_OBJECT(self),
897                                signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
898                                NULL);
899         }
900         return FALSE;
901 }
902
903 GtkWidget*
904 modest_folder_view_new (TnyFolderStoreQuery *query)
905 {
906         GObject *self;
907         ModestFolderViewPrivate *priv;
908         GtkTreeSelection *sel;
909         
910         self = G_OBJECT (g_object_new (MODEST_TYPE_FOLDER_VIEW, NULL));
911         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
912
913         if (query)
914                 priv->query = g_object_ref (query);
915         
916         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
917         priv->changed_signal = g_signal_connect (sel, "changed",
918                                                  G_CALLBACK (on_selection_changed), self);
919
920         g_signal_connect (self, "expose-event", G_CALLBACK (modest_folder_view_on_map), NULL);
921
922         return GTK_WIDGET(self);
923 }
924
925 /* this feels dirty; any other way to expand all the root items? */
926 static void
927 expand_root_items (ModestFolderView *self)
928 {
929         GtkTreePath *path;
930         path = gtk_tree_path_new_first ();
931
932         /* all folders should have child items, so.. */
933         while (gtk_tree_view_expand_row (GTK_TREE_VIEW(self), path, FALSE))
934                 gtk_tree_path_next (path);
935         
936         gtk_tree_path_free (path);
937 }
938
939 /*
940  * We use this function to implement the
941  * MODEST_FOLDER_VIEW_STYLE_SHOW_ONE style. We only show the default
942  * account in this case, and the local folders.
943  */
944 static gboolean 
945 filter_row (GtkTreeModel *model,
946             GtkTreeIter *iter,
947             gpointer data)
948 {
949         ModestFolderViewPrivate *priv;
950         gboolean retval = TRUE;
951         TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
952         GObject *instance = NULL;
953         const gchar *id = NULL;
954         guint i;
955         gboolean found = FALSE;
956         gboolean cleared = FALSE;
957
958         g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (data), FALSE);
959         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (data);
960
961         gtk_tree_model_get (model, iter,
962                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
963                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
964                             -1);
965
966         /* Do not show if there is no instance, this could indeed
967            happen when the model is being modified while it's being
968            drawn. This could occur for example when moving folders
969            using drag&drop */
970         if (!instance)
971                 return FALSE;
972
973         if (type == TNY_FOLDER_TYPE_ROOT) {
974                 /* TNY_FOLDER_TYPE_ROOT means that the instance is an
975                    account instead of a folder. */
976                 if (TNY_IS_ACCOUNT (instance)) {
977                         TnyAccount *acc = TNY_ACCOUNT (instance);
978                         const gchar *account_id = tny_account_get_id (acc);
979         
980                         /* If it isn't a special folder, 
981                          * don't show it unless it is the visible account: */
982                         if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE &&
983                             !modest_tny_account_is_virtual_local_folders (acc) &&
984                             strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
985                                 
986                                 /* Show only the visible account id */
987                                 if (priv->visible_account_id) {
988                                         if (strcmp (account_id, priv->visible_account_id))
989                                                 retval = FALSE;
990                                 } else {
991                                         retval = FALSE;
992                                 }                               
993                         }
994                         
995                         /* Never show these to the user. They are merged into one folder 
996                          * in the local-folders account instead: */
997                         if (retval && MODEST_IS_TNY_OUTBOX_ACCOUNT (acc))
998                                 retval = FALSE;
999                 }
1000         }
1001
1002         /* Check hiding (if necessary) */
1003         cleared = modest_email_clipboard_cleared (priv->clipboard);            
1004         if ((retval) && (!cleared) && (TNY_IS_FOLDER (instance))) {
1005                 id = tny_folder_get_id (TNY_FOLDER(instance));
1006                 if (priv->hidding_ids != NULL)
1007                         for (i=0; i < priv->n_selected && !found; i++)
1008                                 if (priv->hidding_ids[i] != NULL && id != NULL)
1009                                         found = (!strcmp (priv->hidding_ids[i], id));
1010                 
1011                 retval = !found;
1012         }
1013         
1014         
1015         /* If this is a move to dialog, hide Sent, Outbox and Drafts
1016         folder as no message can be move there according to UI specs */
1017         if (!priv->show_non_move)
1018         {
1019                 switch (type)
1020                 {
1021                         case TNY_FOLDER_TYPE_OUTBOX:
1022                         case TNY_FOLDER_TYPE_SENT:
1023                         case TNY_FOLDER_TYPE_DRAFTS:
1024                                 retval = FALSE;
1025                                 break;
1026                         case TNY_FOLDER_TYPE_UNKNOWN:
1027                         case TNY_FOLDER_TYPE_NORMAL:
1028                                 type = modest_tny_folder_guess_folder_type(TNY_FOLDER(instance));
1029                                 if (type == TNY_FOLDER_TYPE_OUTBOX || type == TNY_FOLDER_TYPE_SENT
1030                                                 || type == TNY_FOLDER_TYPE_DRAFTS)
1031                                 {
1032                                         retval = FALSE;
1033                                 }
1034                                 break;
1035                         default:
1036                                 break;  
1037                 }       
1038         }
1039         
1040         /* Free */
1041         g_object_unref (instance);
1042
1043         return retval;
1044 }
1045
1046
1047 gboolean
1048 modest_folder_view_update_model (ModestFolderView *self,
1049                                  TnyAccountStore *account_store)
1050 {
1051         ModestFolderViewPrivate *priv;
1052         GtkTreeModel *model /* , *old_model */;
1053         /* TnyAccount *local_account; */
1054         TnyList *model_as_list;
1055
1056         g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (self), FALSE);
1057         g_return_val_if_fail (account_store, FALSE);
1058
1059         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1060         
1061         /* Notify that there is no folder selected */
1062         g_signal_emit (G_OBJECT(self), 
1063                        signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
1064                        NULL, FALSE);
1065         if (priv->cur_folder_store) {
1066                 g_object_unref (priv->cur_folder_store);
1067                 priv->cur_folder_store = NULL;
1068         }
1069
1070         /* FIXME: the local accounts are not shown when the query
1071            selects only the subscribed folders. */
1072 /*      model        = tny_gtk_folder_store_tree_model_new (TRUE, priv->query); */
1073         model        = tny_gtk_folder_store_tree_model_new (NULL);
1074         
1075         /* Deal with the model via its TnyList Interface,
1076          * filling the TnyList via a get_accounts() call: */
1077         model_as_list = TNY_LIST(model);
1078
1079         /* Get the accounts: */
1080         tny_account_store_get_accounts (TNY_ACCOUNT_STORE(account_store),
1081                                         model_as_list,
1082                                         TNY_ACCOUNT_STORE_STORE_ACCOUNTS);
1083         g_object_unref (model_as_list);
1084         model_as_list = NULL;   
1085                                                      
1086         GtkTreeModel *filter_model = NULL, *sortable = NULL;
1087
1088         sortable = gtk_tree_model_sort_new_with_model (model);
1089         gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
1090                                               TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, 
1091                                               GTK_SORT_ASCENDING);
1092         gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
1093                                          TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
1094                                          cmp_rows, NULL, NULL);
1095
1096         /* Create filter model */
1097         filter_model = gtk_tree_model_filter_new (sortable, NULL);
1098         gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
1099                                                 filter_row,
1100                                                 self,
1101                                                 NULL);
1102
1103         /* Set new model */
1104         gtk_tree_view_set_model (GTK_TREE_VIEW(self), filter_model);
1105         g_signal_connect (G_OBJECT(filter_model), "row-changed",
1106                           (GCallback)on_row_changed_maybe_select_folder, self);
1107         g_signal_connect (G_OBJECT(filter_model), "row-inserted",
1108                           (GCallback)on_row_changed_maybe_select_folder, self);
1109
1110
1111         g_object_unref (model);
1112         g_object_unref (filter_model);          
1113         g_object_unref (sortable);
1114
1115         /* Force a reselection of the INBOX next time the widget is shown */
1116         priv->reselect = TRUE;
1117                         
1118         return TRUE;
1119 }
1120
1121
1122 static void
1123 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
1124 {
1125         GtkTreeModel            *model;
1126         TnyFolderStore          *folder = NULL;
1127         GtkTreeIter             iter;
1128         ModestFolderView        *tree_view;
1129         ModestFolderViewPrivate *priv;
1130
1131         g_return_if_fail (sel);
1132         g_return_if_fail (user_data);
1133         
1134         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
1135
1136         if(!gtk_tree_selection_get_selected (sel, &model, &iter))
1137                 return;
1138
1139         /* Notify the display name observers */
1140         g_signal_emit (G_OBJECT(user_data),
1141                        signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
1142                        NULL);
1143
1144         tree_view = MODEST_FOLDER_VIEW (user_data);
1145         gtk_tree_model_get (model, &iter,
1146                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
1147                             -1);
1148
1149         /* If the folder is the same do not notify */
1150         if (priv->cur_folder_store == folder && folder) {
1151                 g_object_unref (folder);
1152                 return;
1153         }
1154         
1155         /* Current folder was unselected */
1156         if (priv->cur_folder_store) {
1157                 g_signal_emit (G_OBJECT(tree_view), signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
1158                        priv->cur_folder_store, FALSE);
1159
1160                 if (TNY_IS_FOLDER(priv->cur_folder_store))
1161                         tny_folder_sync_async (TNY_FOLDER(priv->cur_folder_store),
1162                                                FALSE, NULL, NULL, NULL);
1163                 /* FALSE --> don't expunge the messages */
1164
1165                 g_object_unref (priv->cur_folder_store);
1166                 priv->cur_folder_store = NULL;
1167         }
1168
1169         /* New current references */
1170         priv->cur_folder_store = folder;
1171
1172         /* New folder has been selected */
1173         g_signal_emit (G_OBJECT(tree_view),
1174                        signals[FOLDER_SELECTION_CHANGED_SIGNAL],
1175                        0, priv->cur_folder_store, TRUE);
1176 }
1177
1178 TnyFolderStore *
1179 modest_folder_view_get_selected (ModestFolderView *self)
1180 {
1181         ModestFolderViewPrivate *priv;
1182
1183         g_return_val_if_fail (self, NULL);
1184         
1185         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1186         if (priv->cur_folder_store)
1187                 g_object_ref (priv->cur_folder_store);
1188
1189         return priv->cur_folder_store;
1190 }
1191
1192 static gint
1193 get_cmp_rows_type_pos (GObject *folder)
1194 {
1195         /* Remote accounts -> Local account -> MMC account .*/
1196         /* 0, 1, 2 */
1197         
1198         if (TNY_IS_ACCOUNT (folder) && 
1199                 modest_tny_account_is_virtual_local_folders (
1200                         TNY_ACCOUNT (folder))) {
1201                 return 1;
1202         } else if (TNY_IS_ACCOUNT (folder)) {
1203                 TnyAccount *account = TNY_ACCOUNT (folder);
1204                 const gchar *account_id = tny_account_get_id (account);
1205                 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
1206                         return 2;
1207                 else
1208                         return 0;
1209         }
1210         else {
1211                 printf ("DEBUG: %s: unexpected type.\n", __FUNCTION__);
1212                 return -1; /* Should never happen */
1213         }
1214 }
1215
1216 static gint
1217 get_cmp_subfolder_type_pos (TnyFolderType t)
1218 {
1219         /* Inbox, Outbox, Drafts, Sent, User */
1220         /* 0, 1, 2, 3, 4 */
1221
1222         switch (t) {
1223         case TNY_FOLDER_TYPE_INBOX:
1224                 return 0;
1225                 break;
1226         case TNY_FOLDER_TYPE_OUTBOX:
1227                 return 1;
1228                 break;
1229         case TNY_FOLDER_TYPE_DRAFTS:
1230                 return 2;
1231                 break;
1232         case TNY_FOLDER_TYPE_SENT:
1233                 return 3;
1234                 break;
1235         default:
1236                 return 4;
1237         }
1238 }
1239
1240 /*
1241  * This function orders the mail accounts according to these rules:
1242  * 1st - remote accounts
1243  * 2nd - local account
1244  * 3rd - MMC account
1245  */
1246 static gint
1247 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1248           gpointer user_data)
1249 {
1250         gint cmp = 0;
1251         gchar *name1 = NULL;
1252         gchar *name2 = NULL;
1253         TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1254         TnyFolderType type2 = TNY_FOLDER_TYPE_UNKNOWN;
1255         GObject *folder1 = NULL;
1256         GObject *folder2 = NULL;
1257
1258         gtk_tree_model_get (tree_model, iter1,
1259                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name1,
1260                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
1261                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder1,
1262                             -1);
1263         gtk_tree_model_get (tree_model, iter2,
1264                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name2,
1265                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type2,
1266                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder2,
1267                             -1);
1268
1269         /* Return if we get no folder. This could happen when folder
1270            operations are happening. The model is updated after the
1271            folder copy/move actually occurs, so there could be
1272            situations where the model to be drawn is not correct */
1273         if (!folder1 || !folder2)
1274                 goto finish;
1275
1276         if (type == TNY_FOLDER_TYPE_ROOT) {
1277                 /* Compare the types, so that 
1278                  * Remote accounts -> Local account -> MMC account .*/
1279                 const gint pos1 = get_cmp_rows_type_pos (folder1);
1280                 const gint pos2 = get_cmp_rows_type_pos (folder2);
1281                 /* printf ("DEBUG: %s:\n  type1=%s, pos1=%d\n  type2=%s, pos2=%d\n", 
1282                         __FUNCTION__, G_OBJECT_TYPE_NAME(folder1), pos1, G_OBJECT_TYPE_NAME(folder2), pos2); */
1283                 if (pos1 <  pos2)
1284                         cmp = -1;
1285                 else if (pos1 > pos2)
1286                         cmp = 1;
1287                 else {
1288                         /* Compare items of the same type: */
1289                         
1290                         TnyAccount *account1 = NULL;
1291                         if (TNY_IS_ACCOUNT (folder1))
1292                                 account1 = TNY_ACCOUNT (folder1);
1293                                 
1294                         TnyAccount *account2 = NULL;
1295                         if (TNY_IS_ACCOUNT (folder2))
1296                                 account2 = TNY_ACCOUNT (folder2);
1297                                 
1298                         const gchar *account_id = account1 ? tny_account_get_id (account1) : NULL;
1299                         const gchar *account_id2 = account2 ? tny_account_get_id (account2) : NULL;
1300         
1301                         if (!account_id && !account_id2) {
1302                                 cmp = 0;
1303                         } else if (!account_id) {
1304                                 cmp = -1;
1305                         } else if (!account_id2) {
1306                                 cmp = +1;
1307                         } else if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
1308                                 cmp = +1;
1309                         } else {
1310                                 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
1311                         }
1312                 }
1313         } else {
1314                 gint cmp1 = 0, cmp2 = 0;
1315                 /* get the parent to know if it's a local folder */
1316
1317                 GtkTreeIter parent;
1318                 gboolean has_parent;
1319                 has_parent = gtk_tree_model_iter_parent (tree_model, &parent, iter1);
1320                 if (has_parent) {
1321                         GObject *parent_folder;
1322                         TnyFolderType parent_type = TNY_FOLDER_TYPE_UNKNOWN;
1323                         gtk_tree_model_get (tree_model, &parent, 
1324                                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &parent_type,
1325                                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &parent_folder,
1326                                             -1);
1327                         if ((parent_type == TNY_FOLDER_TYPE_ROOT) &&
1328                             TNY_IS_ACCOUNT (parent_folder) &&
1329                             modest_tny_account_is_virtual_local_folders (TNY_ACCOUNT (parent_folder))) {
1330                                 cmp1 = get_cmp_subfolder_type_pos (modest_tny_folder_get_local_or_mmc_folder_type (TNY_FOLDER (folder1)));
1331                                 cmp2 = get_cmp_subfolder_type_pos (modest_tny_folder_get_local_or_mmc_folder_type (TNY_FOLDER (folder2)));
1332                         }
1333                         g_object_unref (parent_folder);
1334                 }
1335
1336                 /* if they are not local folders */
1337                 if (cmp1 == cmp2) {
1338                         cmp1 = get_cmp_subfolder_type_pos (tny_folder_get_folder_type (TNY_FOLDER (folder1)));
1339                         cmp2 = get_cmp_subfolder_type_pos (tny_folder_get_folder_type (TNY_FOLDER (folder2)));
1340                 }
1341
1342                 if (cmp1 == cmp2)
1343                         cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
1344                 else 
1345                         cmp = (cmp1 - cmp2);
1346         }
1347
1348 finish: 
1349         if (folder1)
1350                 g_object_unref(G_OBJECT(folder1));
1351         if (folder2)
1352                 g_object_unref(G_OBJECT(folder2));
1353
1354         g_free (name1);
1355         g_free (name2);
1356
1357         return cmp;     
1358 }
1359
1360 /*****************************************************************************/
1361 /*                        DRAG and DROP stuff                                */
1362 /*****************************************************************************/
1363
1364 /*
1365  * This function fills the #GtkSelectionData with the row and the
1366  * model that has been dragged. It's called when this widget is a
1367  * source for dnd after the event drop happened
1368  */
1369 static void
1370 on_drag_data_get (GtkWidget *widget, 
1371                   GdkDragContext *context, 
1372                   GtkSelectionData *selection_data, 
1373                   guint info, 
1374                   guint time, 
1375                   gpointer data)
1376 {
1377         GtkTreeSelection *selection;
1378         GtkTreeModel *model;
1379         GtkTreeIter iter;
1380         GtkTreePath *source_row;
1381
1382         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1383         gtk_tree_selection_get_selected (selection, &model, &iter);
1384         source_row = gtk_tree_model_get_path (model, &iter);
1385
1386         gtk_tree_set_row_drag_data (selection_data,
1387                                     model,
1388                                     source_row);
1389
1390         gtk_tree_path_free (source_row);
1391 }
1392
1393 typedef struct _DndHelper {
1394         gboolean delete_source;
1395         GtkTreePath *source_row;
1396         GdkDragContext *context;
1397         guint time;
1398 } DndHelper;
1399
1400
1401 /*
1402  * This function is the callback of the
1403  * modest_mail_operation_xfer_msgs () and
1404  * modest_mail_operation_xfer_folder() calls. We check here if the
1405  * message/folder was correctly asynchronously transferred. The reason
1406  * to use the same callback is that the code is the same, it only has
1407  * to check that the operation went fine and then finalize the drag
1408  * and drop action
1409  */
1410 static void
1411 on_progress_changed (ModestMailOperation *mail_op, 
1412                      ModestMailOperationState *state,
1413                      gpointer user_data)
1414 {
1415         gboolean success;
1416         DndHelper *helper;
1417
1418         helper = (DndHelper *) user_data;
1419
1420         if (!state->finished)
1421                 return;
1422
1423         if (state->status == MODEST_MAIL_OPERATION_STATUS_SUCCESS) {
1424                 success = TRUE;
1425         } else {
1426                 success = FALSE;
1427         }
1428
1429         /* Notify the drag source. Never call delete, the monitor will
1430            do the job if needed */
1431         gtk_drag_finish (helper->context, success, FALSE, helper->time);
1432
1433         /* Free the helper */
1434         gtk_tree_path_free (helper->source_row);        
1435         g_slice_free (DndHelper, helper);
1436 }
1437
1438 /*
1439  * This function is used by drag_data_received_cb to manage drag and
1440  * drop of a header, i.e, and drag from the header view to the folder
1441  * view.
1442  */
1443 static void
1444 drag_and_drop_from_header_view (GtkTreeModel *source_model,
1445                                 GtkTreeModel *dest_model,
1446                                 GtkTreePath  *dest_row,
1447                                 DndHelper    *helper)
1448 {
1449         TnyList *headers = NULL;
1450         TnyHeader *header = NULL;
1451         TnyFolder *folder = NULL;
1452         ModestMailOperation *mail_op = NULL;
1453         GtkTreeIter source_iter, dest_iter;
1454
1455         g_return_if_fail (GTK_IS_TREE_MODEL(source_model));
1456         g_return_if_fail (GTK_IS_TREE_MODEL(dest_model));
1457         g_return_if_fail (dest_row);
1458         g_return_if_fail (helper);
1459         
1460         /* Get header */
1461         gtk_tree_model_get_iter (source_model, &source_iter, helper->source_row);
1462         gtk_tree_model_get (source_model, &source_iter, 
1463                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, 
1464                             &header, -1);
1465         if (!TNY_IS_HEADER(header)) {
1466                 g_warning ("BUG: %s could not get a valid header", __FUNCTION__);
1467                 goto cleanup;
1468         }
1469         
1470         /* Get Folder */
1471         gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
1472         gtk_tree_model_get (dest_model, &dest_iter, 
1473                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, 
1474                             &folder, -1);
1475
1476         if (!TNY_IS_FOLDER(folder)) {
1477                 g_warning ("BUG: %s could not get a valid folder", __FUNCTION__);
1478                 goto cleanup;
1479         }
1480
1481         /* Transfer message */
1482         mail_op = modest_mail_operation_new_with_error_handling (MODEST_MAIL_OPERATION_TYPE_RECEIVE, 
1483                                                                  NULL,
1484                                                                  modest_ui_actions_move_folder_error_handler,
1485                                                                  NULL);
1486         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1487                                          mail_op);
1488         g_signal_connect (G_OBJECT (mail_op), "progress-changed",
1489                           G_CALLBACK (on_progress_changed), helper);
1490
1491         headers = tny_simple_list_new ();
1492         tny_list_append (headers, G_OBJECT (header));
1493         modest_mail_operation_xfer_msgs (mail_op, 
1494                                          headers, 
1495                                          folder, 
1496                                          helper->delete_source, 
1497                                          NULL, NULL);
1498         
1499         /* Frees */
1500 cleanup:
1501         if (G_IS_OBJECT(mail_op))
1502                 g_object_unref (G_OBJECT (mail_op));
1503         if (G_IS_OBJECT(header))
1504                 g_object_unref (G_OBJECT (header));
1505         if (G_IS_OBJECT(folder))
1506                 g_object_unref (G_OBJECT (folder));
1507         if (G_IS_OBJECT(headers))
1508                 g_object_unref (headers);
1509 }
1510
1511 /*
1512  * This function is used by drag_data_received_cb to manage drag and
1513  * drop of a folder, i.e, and drag from the folder view to the same
1514  * folder view.
1515  */
1516 static void
1517 drag_and_drop_from_folder_view (GtkTreeModel     *source_model,
1518                                 GtkTreeModel     *dest_model,
1519                                 GtkTreePath      *dest_row,
1520                                 GtkSelectionData *selection_data,
1521                                 DndHelper        *helper)
1522 {
1523         ModestMailOperation *mail_op = NULL;
1524         GtkTreeIter parent_iter, iter;
1525         TnyFolderStore *parent_folder = NULL;
1526         TnyFolder *folder = NULL;
1527
1528         /* Check if the drag is possible */
1529 /*      if (!gtk_tree_path_compare (helper->source_row, dest_row) || */
1530 /*          !gtk_tree_drag_dest_row_drop_possible (GTK_TREE_DRAG_DEST (dest_model), */
1531 /*                                                 dest_row, */
1532 /*                                                 selection_data)) { */
1533         if (!gtk_tree_path_compare (helper->source_row, dest_row)) {
1534
1535                 gtk_drag_finish (helper->context, FALSE, FALSE, helper->time);
1536                 gtk_tree_path_free (helper->source_row);        
1537                 g_slice_free (DndHelper, helper);
1538                 return;
1539         }
1540
1541         /* Get data */
1542         gtk_tree_model_get_iter (source_model, &parent_iter, dest_row);
1543         gtk_tree_model_get_iter (source_model, &iter, helper->source_row);
1544         gtk_tree_model_get (source_model, &parent_iter, 
1545                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, 
1546                             &parent_folder, -1);
1547         gtk_tree_model_get (source_model, &iter,
1548                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
1549                             &folder, -1);
1550
1551         /* Offer the connection dialog if necessary, for the destination parent folder and source folder: */
1552         if (modest_platform_connect_and_wait_if_network_folderstore (NULL, parent_folder) && 
1553                 modest_platform_connect_and_wait_if_network_folderstore (NULL, TNY_FOLDER_STORE (folder))) {
1554                 /* Do the mail operation */
1555                 mail_op = modest_mail_operation_new_with_error_handling (MODEST_MAIL_OPERATION_TYPE_RECEIVE, 
1556                                                                  NULL,
1557                                                                  modest_ui_actions_move_folder_error_handler,
1558                                                                  NULL);
1559                 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (), 
1560                                          mail_op);
1561                 g_signal_connect (G_OBJECT (mail_op), "progress-changed",
1562                                   G_CALLBACK (on_progress_changed), helper);
1563
1564                 modest_mail_operation_xfer_folder (mail_op, 
1565                                            folder, 
1566                                            parent_folder,
1567                                            helper->delete_source,
1568                                            NULL,
1569                                            NULL);
1570
1571                 g_object_unref (G_OBJECT (mail_op));    
1572         }
1573         
1574         /* Frees */
1575         g_object_unref (G_OBJECT (parent_folder));
1576         g_object_unref (G_OBJECT (folder));
1577 }
1578
1579 /*
1580  * This function receives the data set by the "drag-data-get" signal
1581  * handler. This information comes within the #GtkSelectionData. This
1582  * function will manage both the drags of folders of the treeview and
1583  * drags of headers of the header view widget.
1584  */
1585 static void 
1586 on_drag_data_received (GtkWidget *widget, 
1587                        GdkDragContext *context, 
1588                        gint x, 
1589                        gint y, 
1590                        GtkSelectionData *selection_data, 
1591                        guint target_type, 
1592                        guint time, 
1593                        gpointer data)
1594 {
1595         GtkWidget *source_widget;
1596         GtkTreeModel *dest_model, *source_model;
1597         GtkTreePath *source_row, *dest_row;
1598         GtkTreeViewDropPosition pos;
1599         gboolean success = FALSE, delete_source = FALSE;
1600         DndHelper *helper = NULL; 
1601
1602         /* Do not allow further process */
1603         g_signal_stop_emission_by_name (widget, "drag-data-received");
1604         source_widget = gtk_drag_get_source_widget (context);
1605
1606         /* Get the action */
1607         if (context->action == GDK_ACTION_MOVE) {
1608                 delete_source = TRUE;
1609
1610                 /* Notify that there is no folder selected. We need to
1611                    do this in order to update the headers view (and
1612                    its monitors, because when moving, the old folder
1613                    won't longer exist. We can not wait for the end of
1614                    the operation, because the operation won't start if
1615                    the folder is in use */
1616                 if (source_widget == widget) {
1617                         ModestFolderViewPrivate *priv;
1618
1619                         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
1620                         if (priv->cur_folder_store) {
1621                                 g_object_unref (priv->cur_folder_store);
1622                                 priv->cur_folder_store = NULL;
1623                         }
1624
1625                         g_signal_emit (G_OBJECT (widget), 
1626                                        signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0, NULL, FALSE);
1627                 }
1628         }
1629
1630         /* Check if the get_data failed */
1631         if (selection_data == NULL || selection_data->length < 0)
1632                 gtk_drag_finish (context, success, FALSE, time);
1633
1634         /* Get the models */
1635         gtk_tree_get_row_drag_data (selection_data,
1636                                     &source_model,
1637                                     &source_row);
1638
1639         /* Select the destination model */
1640         if (source_widget == widget) {
1641                 dest_model = source_model;
1642         } else {
1643                 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
1644         }
1645
1646         /* Get the path to the destination row. Can not call
1647            gtk_tree_view_get_drag_dest_row() because the source row
1648            is not selected anymore */
1649         gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget), x, y,
1650                                            &dest_row, &pos);
1651
1652         /* Only allow drops IN other rows */
1653         if (!dest_row || pos == GTK_TREE_VIEW_DROP_BEFORE || pos == GTK_TREE_VIEW_DROP_AFTER)
1654                 gtk_drag_finish (context, success, FALSE, time);
1655
1656         /* Create the helper */
1657         helper = g_slice_new0 (DndHelper);
1658         helper->delete_source = delete_source;
1659         helper->source_row = gtk_tree_path_copy (source_row);
1660         helper->context = context;
1661         helper->time = time;
1662
1663         /* Drags from the header view */
1664         if (source_widget != widget) {
1665
1666                 drag_and_drop_from_header_view (source_model,
1667                                                 dest_model,
1668                                                 dest_row,
1669                                                 helper);
1670         } else {
1671
1672
1673                 drag_and_drop_from_folder_view (source_model,
1674                                                 dest_model,
1675                                                 dest_row,
1676                                                 selection_data, 
1677                                                 helper);
1678         }
1679
1680         /* Frees */
1681         gtk_tree_path_free (source_row);
1682         gtk_tree_path_free (dest_row);
1683 }
1684
1685 /*
1686  * We define a "drag-drop" signal handler because we do not want to
1687  * use the default one, because the default one always calls
1688  * gtk_drag_finish and we prefer to do it in the "drag-data-received"
1689  * signal handler, because there we have all the information available
1690  * to know if the dnd was a success or not.
1691  */
1692 static gboolean
1693 drag_drop_cb (GtkWidget      *widget,
1694               GdkDragContext *context,
1695               gint            x,
1696               gint            y,
1697               guint           time,
1698               gpointer        user_data) 
1699 {
1700         gpointer target;
1701
1702         if (!context->targets)
1703                 return FALSE;
1704
1705         /* Check if we're dragging a folder row */
1706         target = gtk_drag_dest_find_target (widget, context, NULL);
1707
1708         /* Request the data from the source. */
1709         gtk_drag_get_data(widget, context, target, time);
1710
1711     return TRUE;
1712 }
1713
1714 /*
1715  * This function expands a node of a tree view if it's not expanded
1716  * yet. Not sure why it needs the threads stuff, but gtk+`example code
1717  * does that, so that's why they're here.
1718  */
1719 static gint
1720 expand_row_timeout (gpointer data)
1721 {
1722         GtkTreeView *tree_view = data;
1723         GtkTreePath *dest_path = NULL;
1724         GtkTreeViewDropPosition pos;
1725         gboolean result = FALSE;
1726         
1727         GDK_THREADS_ENTER ();
1728         
1729         gtk_tree_view_get_drag_dest_row (tree_view,
1730                                          &dest_path,
1731                                          &pos);
1732         
1733         if (dest_path &&
1734             (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER ||
1735              pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)) {
1736                 gtk_tree_view_expand_row (tree_view, dest_path, FALSE);
1737                 gtk_tree_path_free (dest_path);
1738         }
1739         else {
1740                 if (dest_path)
1741                         gtk_tree_path_free (dest_path);
1742                 
1743                 result = TRUE;
1744         }
1745         
1746         GDK_THREADS_LEAVE ();
1747
1748         return result;
1749 }
1750
1751 /*
1752  * This function is called whenever the pointer is moved over a widget
1753  * while dragging some data. It installs a timeout that will expand a
1754  * node of the treeview if not expanded yet. This function also calls
1755  * gdk_drag_status in order to set the suggested action that will be
1756  * used by the "drag-data-received" signal handler to know if we
1757  * should do a move or just a copy of the data.
1758  */
1759 static gboolean
1760 on_drag_motion (GtkWidget      *widget,
1761                 GdkDragContext *context,
1762                 gint            x,
1763                 gint            y,
1764                 guint           time,
1765                 gpointer        user_data)  
1766 {
1767         GtkTreeViewDropPosition pos;
1768         GtkTreePath *dest_row;
1769         ModestFolderViewPrivate *priv;
1770         GdkDragAction suggested_action;
1771         gboolean valid_location = FALSE;
1772
1773         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
1774
1775         if (priv->timer_expander != 0) {
1776                 g_source_remove (priv->timer_expander);
1777                 priv->timer_expander = 0;
1778         }
1779
1780         gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
1781                                            x, y,
1782                                            &dest_row,
1783                                            &pos);
1784
1785         /* Do not allow drops between folders */
1786         if (!dest_row ||
1787             pos == GTK_TREE_VIEW_DROP_BEFORE ||
1788             pos == GTK_TREE_VIEW_DROP_AFTER) {
1789                 gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW (widget), NULL, 0);
1790                 gdk_drag_status(context, 0, time);
1791                 valid_location = FALSE;
1792                 goto out;
1793         } else {
1794                 valid_location = TRUE;
1795         }
1796
1797         /* Expand the selected row after 1/2 second */
1798         if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), dest_row)) {
1799                 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), dest_row, pos);
1800                 priv->timer_expander = g_timeout_add (500, expand_row_timeout, widget);
1801         }
1802
1803         /* Select the desired action. By default we pick MOVE */
1804         suggested_action = GDK_ACTION_MOVE;
1805
1806         if (context->actions == GDK_ACTION_COPY)
1807             gdk_drag_status(context, GDK_ACTION_COPY, time);
1808         else if (context->actions == GDK_ACTION_MOVE)
1809             gdk_drag_status(context, GDK_ACTION_MOVE, time);
1810         else if (context->actions & suggested_action)
1811             gdk_drag_status(context, suggested_action, time);
1812         else
1813             gdk_drag_status(context, GDK_ACTION_DEFAULT, time);
1814
1815  out:
1816         if (dest_row)
1817                 gtk_tree_path_free (dest_row);
1818         g_signal_stop_emission_by_name (widget, "drag-motion");
1819         return valid_location;
1820 }
1821
1822
1823 /* Folder view drag types */
1824 const GtkTargetEntry folder_view_drag_types[] =
1825 {
1826         { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, MODEST_FOLDER_ROW },
1827         { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_APP,    MODEST_HEADER_ROW }
1828 };
1829
1830 /*
1831  * This function sets the treeview as a source and a target for dnd
1832  * events. It also connects all the requirede signals.
1833  */
1834 static void
1835 setup_drag_and_drop (GtkTreeView *self)
1836 {
1837         /* Set up the folder view as a dnd destination. Set only the
1838            highlight flag, otherwise gtk will have a different
1839            behaviour */
1840         gtk_drag_dest_set (GTK_WIDGET (self),
1841                            GTK_DEST_DEFAULT_HIGHLIGHT,
1842                            folder_view_drag_types,
1843                            G_N_ELEMENTS (folder_view_drag_types),
1844                            GDK_ACTION_MOVE | GDK_ACTION_COPY);
1845
1846         g_signal_connect (G_OBJECT (self),
1847                           "drag_data_received",
1848                           G_CALLBACK (on_drag_data_received),
1849                           NULL);
1850
1851
1852         /* Set up the treeview as a dnd source */
1853         gtk_drag_source_set (GTK_WIDGET (self),
1854                              GDK_BUTTON1_MASK,
1855                              folder_view_drag_types,
1856                              G_N_ELEMENTS (folder_view_drag_types),
1857                              GDK_ACTION_MOVE | GDK_ACTION_COPY);
1858
1859         g_signal_connect (G_OBJECT (self),
1860                           "drag_motion",
1861                           G_CALLBACK (on_drag_motion),
1862                           NULL);
1863         
1864         g_signal_connect (G_OBJECT (self),
1865                           "drag_data_get",
1866                           G_CALLBACK (on_drag_data_get),
1867                           NULL);
1868
1869         g_signal_connect (G_OBJECT (self),
1870                           "drag_drop",
1871                           G_CALLBACK (drag_drop_cb),
1872                           NULL);
1873 }
1874
1875 /*
1876  * This function manages the navigation through the folders using the
1877  * keyboard or the hardware keys in the device
1878  */
1879 static gboolean
1880 on_key_pressed (GtkWidget *self,
1881                 GdkEventKey *event,
1882                 gpointer user_data)
1883 {
1884         GtkTreeSelection *selection;
1885         GtkTreeIter iter;
1886         GtkTreeModel *model;
1887         gboolean retval = FALSE;
1888
1889         /* Up and Down are automatically managed by the treeview */
1890         if (event->keyval == GDK_Return) {
1891                 /* Expand/Collapse the selected row */
1892                 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1893                 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
1894                         GtkTreePath *path;
1895
1896                         path = gtk_tree_model_get_path (model, &iter);
1897
1898                         if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (self), path))
1899                                 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1900                         else
1901                                 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1902                         gtk_tree_path_free (path);
1903                 }
1904                 /* No further processing */
1905                 retval = TRUE;
1906         }
1907
1908         return retval;
1909 }
1910
1911 /*
1912  * We listen to the changes in the local folder account name key,
1913  * because we want to show the right name in the view. The local
1914  * folder account name corresponds to the device name in the Maemo
1915  * version. We do this because we do not want to query gconf on each
1916  * tree view refresh. It's better to cache it and change whenever
1917  * necessary.
1918  */
1919 static void 
1920 on_configuration_key_changed (ModestConf* conf, 
1921                               const gchar *key, 
1922                               ModestConfEvent event, 
1923                               ModestFolderView *self)
1924 {
1925         ModestFolderViewPrivate *priv;
1926
1927         if (!key)
1928                 return;
1929
1930         g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
1931         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1932
1933         if (!strcmp (key, MODEST_CONF_DEVICE_NAME)) {
1934                 g_free (priv->local_account_name);
1935
1936                 if (event == MODEST_CONF_EVENT_KEY_UNSET)
1937                         priv->local_account_name = g_strdup (MODEST_LOCAL_FOLDERS_DEFAULT_DISPLAY_NAME);
1938                 else
1939                         priv->local_account_name = modest_conf_get_string (modest_runtime_get_conf(),
1940                                                                            MODEST_CONF_DEVICE_NAME, NULL);
1941
1942                 /* Force a redraw */
1943 #if GTK_CHECK_VERSION(2, 8, 0) /* gtk_tree_view_column_queue_resize is only available in GTK+ 2.8 */
1944                 GtkTreeViewColumn * tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self), 
1945                                                                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN);
1946                 gtk_tree_view_column_queue_resize (tree_column);
1947 #endif
1948         }
1949 }
1950
1951 void
1952 modest_folder_view_set_style (ModestFolderView *self,
1953                               ModestFolderViewStyle style)
1954 {
1955         ModestFolderViewPrivate *priv;
1956
1957         g_return_if_fail (self);
1958         
1959         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1960
1961         priv->style = style;
1962 }
1963
1964 void
1965 modest_folder_view_set_account_id_of_visible_server_account (ModestFolderView *self,
1966                                                              const gchar *account_id)
1967 {
1968         ModestFolderViewPrivate *priv;
1969         GtkTreeModel *model;
1970
1971         g_return_if_fail (self);
1972         
1973         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1974
1975         /* This will be used by the filter_row callback,
1976          * to decided which rows to show: */
1977         if (priv->visible_account_id) {
1978                 g_free (priv->visible_account_id);
1979                 priv->visible_account_id = NULL;
1980         }
1981         if (account_id)
1982                 priv->visible_account_id = g_strdup (account_id);
1983
1984         /* Refilter */
1985         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1986         if (GTK_IS_TREE_MODEL_FILTER (model))
1987                 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
1988
1989         /* Save settings to gconf */
1990         modest_widget_memory_save (modest_runtime_get_conf (), G_OBJECT(self),
1991                                    MODEST_CONF_FOLDER_VIEW_KEY);
1992 }
1993
1994 const gchar *
1995 modest_folder_view_get_account_id_of_visible_server_account (ModestFolderView *self)
1996 {
1997         ModestFolderViewPrivate *priv;
1998
1999         g_return_val_if_fail (self, NULL);
2000         
2001         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2002
2003         return (const gchar *) priv->visible_account_id;
2004 }
2005
2006 static gboolean
2007 find_inbox_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *inbox_iter)
2008 {
2009         do {
2010                 GtkTreeIter child;
2011                 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2012
2013                 gtk_tree_model_get (model, iter, 
2014                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, 
2015                                     &type, -1);
2016                         
2017                 gboolean result = FALSE;
2018                 if (type == TNY_FOLDER_TYPE_INBOX) {
2019                         result = TRUE;
2020                 }               
2021                 if (result) {
2022                         *inbox_iter = *iter;
2023                         return TRUE;
2024                 }
2025
2026                 if (gtk_tree_model_iter_children (model, &child, iter)) {
2027                         if (find_inbox_iter (model, &child, inbox_iter))
2028                                 return TRUE;
2029                 }
2030
2031         } while (gtk_tree_model_iter_next (model, iter));
2032
2033         return FALSE;
2034 }
2035
2036
2037
2038
2039 void 
2040 modest_folder_view_select_first_inbox_or_local (ModestFolderView *self)
2041 {
2042         GtkTreeModel *model;
2043         GtkTreeIter iter, inbox_iter;
2044         GtkTreeSelection *sel;
2045         GtkTreePath *path = NULL;
2046
2047         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2048         if (!model)
2049                 return;
2050
2051         expand_root_items (self);
2052         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2053
2054         gtk_tree_model_get_iter_first (model, &iter);
2055
2056         if (find_inbox_iter (model, &iter, &inbox_iter))
2057                 path = gtk_tree_model_get_path (model, &inbox_iter);
2058         else
2059                 path = gtk_tree_path_new_first ();
2060
2061         /* Select the row and free */
2062         gtk_tree_view_set_cursor (GTK_TREE_VIEW (self), path, NULL, FALSE);
2063         gtk_tree_path_free (path);
2064 }
2065
2066
2067 /* recursive */
2068 static gboolean
2069 find_folder_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *folder_iter, 
2070                   TnyFolder* folder)
2071 {
2072         do {
2073                 GtkTreeIter child;
2074                 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2075                 TnyFolder* a_folder;
2076                 gchar *name = NULL;
2077                 
2078                 gtk_tree_model_get (model, iter, 
2079                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &a_folder,
2080                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name,
2081                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type, 
2082                                     -1);                
2083         
2084                 g_debug ("===> %s (%p ---- %p)", name, a_folder, folder);
2085                 g_free (name);
2086
2087                 if (folder == a_folder) {
2088                         g_object_unref (a_folder);
2089                         *folder_iter = *iter;
2090                         return TRUE;
2091                 }
2092                 g_object_unref (a_folder);
2093                 
2094                 if (gtk_tree_model_iter_children (model, &child, iter)) {
2095                         if (find_folder_iter (model, &child, folder_iter, folder)) 
2096                                 return TRUE;
2097                 }
2098
2099         } while (gtk_tree_model_iter_next (model, iter));
2100
2101         return FALSE;
2102 }
2103
2104
2105 static void
2106 on_row_changed_maybe_select_folder (GtkTreeModel *tree_model, GtkTreePath  *path, GtkTreeIter *iter,
2107                                     ModestFolderView *self)
2108 {
2109         ModestFolderViewPrivate *priv = NULL;
2110         GtkTreeSelection *sel;
2111
2112         if (!MODEST_IS_FOLDER_VIEW(self))
2113                 return;
2114         
2115         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2116         
2117         if (priv->folder_to_select) {
2118                 
2119                 if (!modest_folder_view_select_folder (self, priv->folder_to_select,
2120                                                        FALSE)) {
2121                         GtkTreePath *path;
2122                         path = gtk_tree_model_get_path (tree_model, iter);
2123                         gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
2124                         
2125                         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2126
2127                         gtk_tree_selection_select_iter (sel, iter);
2128                         gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
2129
2130                         gtk_tree_path_free (path);
2131                 
2132                 }
2133                 g_object_unref (priv->folder_to_select);
2134                 priv->folder_to_select = NULL;
2135         }
2136 }
2137
2138
2139 gboolean
2140 modest_folder_view_select_folder (ModestFolderView *self, TnyFolder *folder, 
2141                                   gboolean after_change)
2142 {
2143         GtkTreeModel *model;
2144         GtkTreeIter iter, folder_iter;
2145         GtkTreeSelection *sel;
2146         ModestFolderViewPrivate *priv = NULL;
2147         
2148         g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (self), FALSE);     
2149         g_return_val_if_fail (TNY_IS_FOLDER (folder), FALSE);   
2150                 
2151         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2152
2153         if (after_change) {
2154
2155                 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2156                 gtk_tree_selection_unselect_all (sel);
2157
2158                 if (priv->folder_to_select)
2159                         g_object_unref(priv->folder_to_select);
2160                 priv->folder_to_select = TNY_FOLDER(g_object_ref(folder));
2161                 return TRUE;
2162         }
2163                 
2164         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2165         if (!model)
2166                 return FALSE;
2167
2168                 
2169         gtk_tree_model_get_iter_first (model, &iter);
2170         if (find_folder_iter (model, &iter, &folder_iter, folder)) {
2171                 GtkTreePath *path;
2172
2173                 path = gtk_tree_model_get_path (model, &folder_iter);
2174                 gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
2175
2176                 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2177                 gtk_tree_selection_select_iter (sel, &folder_iter);
2178                 gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
2179
2180                 gtk_tree_path_free (path);
2181                 return TRUE;
2182         }
2183         return FALSE;
2184 }
2185
2186
2187 void 
2188 modest_folder_view_copy_selection (ModestFolderView *folder_view)
2189 {
2190         /* Copy selection */
2191         _clipboard_set_selected_data (folder_view, FALSE);
2192 }
2193
2194 void 
2195 modest_folder_view_cut_selection (ModestFolderView *folder_view)
2196 {
2197         ModestFolderViewPrivate *priv = NULL;
2198         GtkTreeModel *model = NULL;
2199         const gchar **hidding = NULL;
2200         guint i, n_selected;
2201
2202         g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view));
2203         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
2204
2205         /* Copy selection */
2206         if (!_clipboard_set_selected_data (folder_view, TRUE))
2207                 return;
2208
2209         /* Get hidding ids */
2210         hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected); 
2211         
2212         /* Clear hidding array created by previous cut operation */
2213         _clear_hidding_filter (MODEST_FOLDER_VIEW (folder_view));
2214
2215         /* Copy hidding array */
2216         priv->n_selected = n_selected;
2217         priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
2218         for (i=0; i < n_selected; i++) 
2219                 priv->hidding_ids[i] = g_strdup(hidding[i]);            
2220
2221         /* Hide cut folders */
2222         model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
2223         gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2224 }
2225
2226 void
2227 modest_folder_view_show_non_move_folders (ModestFolderView *folder_view,
2228                                     gboolean show)
2229 {
2230         ModestFolderViewPrivate* priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
2231         priv->show_non_move = show;
2232         modest_folder_view_update_model(folder_view,
2233                                                                                                                                         TNY_ACCOUNT_STORE(modest_runtime_get_account_store()));
2234 }
2235
2236 /* Returns FALSE if it did not selected anything */
2237 static gboolean
2238 _clipboard_set_selected_data (ModestFolderView *folder_view,
2239                               gboolean delete)
2240 {
2241         ModestFolderViewPrivate *priv = NULL;
2242         TnyFolderStore *folder = NULL;
2243         gboolean retval = FALSE;
2244
2245         g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (folder_view), FALSE);
2246         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
2247                 
2248         /* Set selected data on clipboard   */
2249         g_return_val_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard), FALSE);
2250         folder = modest_folder_view_get_selected (folder_view);
2251
2252         /* Do not allow to select an account */
2253         if (TNY_IS_FOLDER (folder)) {
2254                 modest_email_clipboard_set_data (priv->clipboard, TNY_FOLDER(folder), NULL, delete);
2255                 retval = TRUE;
2256         }
2257
2258         /* Free */
2259         g_object_unref (folder);
2260
2261         return retval;
2262 }
2263
2264 static void
2265 _clear_hidding_filter (ModestFolderView *folder_view) 
2266 {
2267         ModestFolderViewPrivate *priv;
2268         guint i;
2269         
2270         g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view)); 
2271         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
2272
2273         if (priv->hidding_ids != NULL) {
2274                 for (i=0; i < priv->n_selected; i++) 
2275                         g_free (priv->hidding_ids[i]);
2276                 g_free(priv->hidding_ids);
2277         }       
2278 }
2279
2280