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