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