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