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