* Fixes NB#90498, disconnect a signal when finalizing the folder view
[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 <tny-camel-account.h>
45 #include <modest-tny-account.h>
46 #include <modest-tny-folder.h>
47 #include <modest-tny-local-folders-account.h>
48 #include <modest-tny-outbox-account.h>
49 #include <modest-marshal.h>
50 #include <modest-icon-names.h>
51 #include <modest-tny-account-store.h>
52 #include <modest-tny-local-folders-account.h>
53 #include <modest-text-utils.h>
54 #include <modest-runtime.h>
55 #include "modest-folder-view.h"
56 #include <modest-platform.h>
57 #include <modest-widget-memory.h>
58 #include <modest-ui-actions.h>
59 #include "modest-dnd.h"
60 #include "widgets/modest-window.h"
61
62 /* Folder view drag types */
63 const GtkTargetEntry folder_view_drag_types[] =
64 {
65         { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, MODEST_FOLDER_ROW },
66         { GTK_TREE_PATH_AS_STRING_LIST, GTK_TARGET_SAME_APP, MODEST_HEADER_ROW }
67 };
68
69 /* 'private'/'protected' functions */
70 static void modest_folder_view_class_init  (ModestFolderViewClass *klass);
71 static void modest_folder_view_init        (ModestFolderView *obj);
72 static void modest_folder_view_finalize    (GObject *obj);
73
74 static void         tny_account_store_view_init (gpointer g,
75                                                  gpointer iface_data);
76
77 static void         modest_folder_view_set_account_store (TnyAccountStoreView *self,
78                                                           TnyAccountStore     *account_store);
79
80 static void         on_selection_changed   (GtkTreeSelection *sel,
81                                             gpointer data);
82
83 static void         on_account_removed     (TnyAccountStore *self,
84                                             TnyAccount *account,
85                                             gpointer user_data);
86
87 static void         on_account_inserted    (TnyAccountStore *self,
88                                             TnyAccount *account,
89                                             gpointer user_data);
90
91 static void         on_account_changed    (TnyAccountStore *self,
92                                             TnyAccount *account,
93                                             gpointer user_data);
94
95 static gint         cmp_rows               (GtkTreeModel *tree_model,
96                                             GtkTreeIter *iter1,
97                                             GtkTreeIter *iter2,
98                                             gpointer user_data);
99
100 static gboolean     filter_row             (GtkTreeModel *model,
101                                             GtkTreeIter *iter,
102                                             gpointer data);
103
104 static gboolean     on_key_pressed         (GtkWidget *self,
105                                             GdkEventKey *event,
106                                             gpointer user_data);
107
108 static void         on_configuration_key_changed  (ModestConf* conf,
109                                                    const gchar *key,
110                                                    ModestConfEvent event,
111                                                    ModestConfNotificationId notification_id,
112                                                    ModestFolderView *self);
113
114 /* DnD functions */
115 static void         on_drag_data_get       (GtkWidget *widget,
116                                             GdkDragContext *context,
117                                             GtkSelectionData *selection_data,
118                                             guint info,
119                                             guint time,
120                                             gpointer data);
121
122 static void         on_drag_data_received  (GtkWidget *widget,
123                                             GdkDragContext *context,
124                                             gint x,
125                                             gint y,
126                                             GtkSelectionData *selection_data,
127                                             guint info,
128                                             guint time,
129                                             gpointer data);
130
131 static gboolean     on_drag_motion         (GtkWidget      *widget,
132                                             GdkDragContext *context,
133                                             gint            x,
134                                             gint            y,
135                                             guint           time,
136                                             gpointer        user_data);
137
138 static void         expand_root_items (ModestFolderView *self);
139
140 static gint         expand_row_timeout     (gpointer data);
141
142 static void         setup_drag_and_drop    (GtkTreeView *self);
143
144 static gboolean     _clipboard_set_selected_data (ModestFolderView *folder_view,
145                                                   gboolean delete);
146
147 static void         _clear_hidding_filter (ModestFolderView *folder_view);
148
149 static void         on_row_inserted_maybe_select_folder (GtkTreeModel     *tree_model,
150                                                          GtkTreePath      *path,
151                                                          GtkTreeIter      *iter,
152                                                          ModestFolderView *self);
153
154 static void         on_display_name_changed (ModestAccountMgr *self,
155                                              const gchar *account,
156                                              gpointer user_data);
157
158 enum {
159         FOLDER_SELECTION_CHANGED_SIGNAL,
160         FOLDER_DISPLAY_NAME_CHANGED_SIGNAL,
161         LAST_SIGNAL
162 };
163
164 typedef struct _ModestFolderViewPrivate ModestFolderViewPrivate;
165 struct _ModestFolderViewPrivate {
166         TnyAccountStore      *account_store;
167         TnyFolderStore       *cur_folder_store;
168
169         TnyFolder            *folder_to_select; /* folder to select after the next update */
170
171         gulong                changed_signal;
172         gulong                account_inserted_signal;
173         gulong                account_removed_signal;
174         gulong                account_changed_signal;
175         gulong                conf_key_signal;
176         gulong                display_name_changed_signal;
177
178         /* not unref this object, its a singlenton */
179         ModestEmailClipboard *clipboard;
180
181         /* Filter tree model */
182         gchar **hidding_ids;
183         guint n_selected;
184
185         TnyFolderStoreQuery  *query;
186         guint                 timer_expander;
187
188         gchar                *local_account_name;
189         gchar                *visible_account_id;
190         ModestFolderViewStyle style;
191
192         gboolean  reselect; /* we use this to force a reselection of the INBOX */
193         gboolean  show_non_move;
194         gboolean  reexpand; /* next time we expose, we'll expand all root folders */
195 };
196 #define MODEST_FOLDER_VIEW_GET_PRIVATE(o)                       \
197         (G_TYPE_INSTANCE_GET_PRIVATE((o),                       \
198                                      MODEST_TYPE_FOLDER_VIEW,   \
199                                      ModestFolderViewPrivate))
200 /* globals */
201 static GObjectClass *parent_class = NULL;
202
203 static guint signals[LAST_SIGNAL] = {0};
204
205 GType
206 modest_folder_view_get_type (void)
207 {
208         static GType my_type = 0;
209         if (!my_type) {
210                 static const GTypeInfo my_info = {
211                         sizeof(ModestFolderViewClass),
212                         NULL,           /* base init */
213                         NULL,           /* base finalize */
214                         (GClassInitFunc) modest_folder_view_class_init,
215                         NULL,           /* class finalize */
216                         NULL,           /* class data */
217                         sizeof(ModestFolderView),
218                         1,              /* n_preallocs */
219                         (GInstanceInitFunc) modest_folder_view_init,
220                         NULL
221                 };
222
223                 static const GInterfaceInfo tny_account_store_view_info = {
224                         (GInterfaceInitFunc) tny_account_store_view_init, /* interface_init */
225                         NULL,         /* interface_finalize */
226                         NULL          /* interface_data */
227                 };
228
229
230                 my_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
231                                                   "ModestFolderView",
232                                                   &my_info, 0);
233
234                 g_type_add_interface_static (my_type,
235                                              TNY_TYPE_ACCOUNT_STORE_VIEW,
236                                              &tny_account_store_view_info);
237         }
238         return my_type;
239 }
240
241 static void
242 modest_folder_view_class_init (ModestFolderViewClass *klass)
243 {
244         GObjectClass *gobject_class;
245         GtkTreeViewClass *treeview_class;
246         gobject_class = (GObjectClass*) klass;
247         treeview_class = (GtkTreeViewClass*) klass;
248
249         parent_class            = g_type_class_peek_parent (klass);
250         gobject_class->finalize = modest_folder_view_finalize;
251
252         g_type_class_add_private (gobject_class,
253                                   sizeof(ModestFolderViewPrivate));
254
255         signals[FOLDER_SELECTION_CHANGED_SIGNAL] =
256                 g_signal_new ("folder_selection_changed",
257                               G_TYPE_FROM_CLASS (gobject_class),
258                               G_SIGNAL_RUN_FIRST,
259                               G_STRUCT_OFFSET (ModestFolderViewClass,
260                                                folder_selection_changed),
261                               NULL, NULL,
262                               modest_marshal_VOID__POINTER_BOOLEAN,
263                               G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_BOOLEAN);
264
265         /*
266          * This signal is emitted whenever the currently selected
267          * folder display name is computed. Note that the name could
268          * be different to the folder name, because we could append
269          * the unread messages count to the folder name to build the
270          * folder display name
271          */
272         signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL] =
273                 g_signal_new ("folder-display-name-changed",
274                               G_TYPE_FROM_CLASS (gobject_class),
275                               G_SIGNAL_RUN_FIRST,
276                               G_STRUCT_OFFSET (ModestFolderViewClass,
277                                                folder_display_name_changed),
278                               NULL, NULL,
279                               g_cclosure_marshal_VOID__STRING,
280                               G_TYPE_NONE, 1, G_TYPE_STRING);
281
282         treeview_class->select_cursor_parent = NULL;
283
284 }
285
286 /* Simplify checks for NULLs: */
287 static gboolean
288 strings_are_equal (const gchar *a, const gchar *b)
289 {
290         if (!a && !b)
291                 return TRUE;
292         if (a && b)
293         {
294                 return (strcmp (a, b) == 0);
295         }
296         else
297                 return FALSE;
298 }
299
300 static gboolean
301 on_model_foreach_set_name(GtkTreeModel *model, GtkTreePath *path,  GtkTreeIter *iter, gpointer data)
302 {
303         GObject *instance = NULL;
304
305         gtk_tree_model_get (model, iter,
306                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
307                             -1);
308
309         if (!instance)
310                 return FALSE; /* keep walking */
311
312         if (!TNY_IS_ACCOUNT (instance)) {
313                 g_object_unref (instance);
314                 return FALSE; /* keep walking */
315         }
316
317         /* Check if this is the looked-for account: */
318         TnyAccount *this_account = TNY_ACCOUNT (instance);
319         TnyAccount *account = TNY_ACCOUNT (data);
320
321         const gchar *this_account_id = tny_account_get_id(this_account);
322         const gchar *account_id = tny_account_get_id(account);
323         g_object_unref (instance);
324         instance = NULL;
325
326         /* printf ("DEBUG: %s: this_account_id=%s, account_id=%s\n", __FUNCTION__, this_account_id, account_id); */
327         if (strings_are_equal(this_account_id, account_id)) {
328                 /* Tell the model that the data has changed, so that
329                  * it calls the cell_data_func callbacks again: */
330                 /* TODO: This does not seem to actually cause the new string to be shown: */
331                 gtk_tree_model_row_changed (model, path, iter);
332
333                 return TRUE; /* stop walking */
334         }
335
336         return FALSE; /* keep walking */
337 }
338
339 typedef struct
340 {
341         ModestFolderView *self;
342         gchar *previous_name;
343 } GetMmcAccountNameData;
344
345 static void
346 on_get_mmc_account_name (TnyStoreAccount* account, gpointer user_data)
347 {
348         /* printf ("DEBU1G: %s: account name=%s\n", __FUNCTION__, tny_account_get_name (TNY_ACCOUNT(account))); */
349
350         GetMmcAccountNameData *data = (GetMmcAccountNameData*)user_data;
351
352         if (!strings_are_equal (
353                 tny_account_get_name(TNY_ACCOUNT(account)),
354                 data->previous_name)) {
355
356                 /* Tell the model that the data has changed, so that
357                  * it calls the cell_data_func callbacks again: */
358                 ModestFolderView *self = data->self;
359                 GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
360                 if (model)
361                         gtk_tree_model_foreach(model, on_model_foreach_set_name, account);
362         }
363
364         g_free (data->previous_name);
365         g_slice_free (GetMmcAccountNameData, data);
366 }
367
368 static void
369 text_cell_data  (GtkTreeViewColumn *column,
370                  GtkCellRenderer *renderer,
371                  GtkTreeModel *tree_model,
372                  GtkTreeIter *iter,
373                  gpointer data)
374 {
375         ModestFolderViewPrivate *priv;
376         GObject *rendobj = (GObject *) renderer;
377         gchar *fname = NULL;
378         TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
379         GObject *instance = NULL;
380
381         gtk_tree_model_get (tree_model, iter,
382                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &fname,
383                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
384                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
385                             -1);
386         if (!fname || !instance)
387                 goto end;
388
389         ModestFolderView *self = MODEST_FOLDER_VIEW (data);
390         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE (self);
391
392         gchar *item_name = NULL;
393         gint item_weight = 400;
394
395         if (type != TNY_FOLDER_TYPE_ROOT) {
396                 gint number = 0;
397
398                 if (modest_tny_folder_is_local_folder (TNY_FOLDER (instance)) ||
399                     modest_tny_folder_is_memory_card_folder (TNY_FOLDER (instance))) {
400                         type = modest_tny_folder_get_local_or_mmc_folder_type (TNY_FOLDER (instance));
401                         if (type != TNY_FOLDER_TYPE_UNKNOWN) {
402                                 g_free (fname);
403                                 fname = g_strdup (modest_local_folder_info_get_type_display_name (type));
404                         }
405                 } else {
406                         /* Sometimes an special folder is reported by the server as
407                            NORMAL, like some versions of Dovecot */
408                         if (type == TNY_FOLDER_TYPE_NORMAL ||
409                             type == TNY_FOLDER_TYPE_UNKNOWN) {
410                                 type = modest_tny_folder_guess_folder_type (TNY_FOLDER (instance));
411                         }
412                 }
413
414                 /* note: we cannot reliably get the counts from the tree model, we need
415                  * to use explicit calls on tny_folder for some reason.
416                  */
417                 /* Select the number to show: the unread or unsent messages. in case of outbox/drafts, show all */
418                 if ((type == TNY_FOLDER_TYPE_DRAFTS) ||
419                     (type == TNY_FOLDER_TYPE_OUTBOX) ||
420                     (type == TNY_FOLDER_TYPE_MERGE)) /* _OUTBOX actually returns _MERGE... */
421                         number = tny_folder_get_all_count (TNY_FOLDER(instance));
422                 else
423                         number = tny_folder_get_unread_count (TNY_FOLDER(instance));
424
425                 /* Use bold font style if there are unread or unset messages */
426                 if (number > 0) {
427                         if (type == TNY_FOLDER_TYPE_INBOX)
428                                 item_name = g_strdup_printf ("%s (%d)", _("mcen_me_folder_inbox"), number);
429                         else
430                                 item_name = g_strdup_printf ("%s (%d)", fname, number);
431                         item_weight = 800;
432                 } else {
433                         if (type == TNY_FOLDER_TYPE_INBOX)
434                                 item_name = g_strdup (_("mcen_me_folder_inbox"));
435                         else
436                                 item_name = g_strdup (fname);
437                         item_weight = 400;
438                 }
439
440         } else if (TNY_IS_ACCOUNT (instance)) {
441                 /* If it's a server account */
442                 if (modest_tny_account_is_virtual_local_folders (TNY_ACCOUNT (instance))) {
443                         item_name = g_strdup (priv->local_account_name);
444                         item_weight = 800;
445                 } else if (modest_tny_account_is_memory_card_account (TNY_ACCOUNT (instance))) {
446                         /* fname is only correct when the items are first
447                          * added to the model, not when the account is
448                          * changed later, so get the name from the account
449                          * instance: */
450                         item_name = g_strdup (tny_account_get_name (TNY_ACCOUNT (instance)));
451                         item_weight = 800;
452                 } else {
453                         item_name = g_strdup (fname);
454                         item_weight = 800;
455                 }
456         }
457
458         if (!item_name)
459                 item_name = g_strdup ("unknown");
460
461         if (item_name && item_weight) {
462                 /* Set the name in the treeview cell: */
463                 g_object_set (rendobj,"text", item_name, "weight", item_weight, NULL);
464
465                 /* Notify display name observers */
466                 /* TODO: What listens for this signal, and how can it use only the new name? */
467                 if (((GObject *) priv->cur_folder_store) == instance) {
468                         g_signal_emit (G_OBJECT(self),
469                                                signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
470                                                item_name);
471                 }
472                 g_free (item_name);
473
474         }
475
476         /* If it is a Memory card account, make sure that we have the correct name.
477          * This function will be trigerred again when the name has been retrieved: */
478         if (TNY_IS_STORE_ACCOUNT (instance) &&
479                 modest_tny_account_is_memory_card_account (TNY_ACCOUNT (instance))) {
480
481                 /* Get the account name asynchronously: */
482                 GetMmcAccountNameData *callback_data =
483                         g_slice_new0(GetMmcAccountNameData);
484                 callback_data->self = self;
485
486                 const gchar *name = tny_account_get_name (TNY_ACCOUNT(instance));
487                 if (name)
488                         callback_data->previous_name = g_strdup (name);
489
490                 modest_tny_account_get_mmc_account_name (TNY_STORE_ACCOUNT (instance),
491                                                          on_get_mmc_account_name, callback_data);
492         }
493  end:
494         if (instance)
495                 g_object_unref (G_OBJECT (instance));
496         if (fname)
497                 g_free (fname);
498 }
499
500
501 typedef struct {
502         GdkPixbuf *pixbuf;
503         GdkPixbuf *pixbuf_open;
504         GdkPixbuf *pixbuf_close;
505 } ThreePixbufs;
506
507
508 static inline GdkPixbuf *
509 get_composite_pixbuf (const gchar *icon_name,
510                       const gint size,
511                       GdkPixbuf *base_pixbuf)
512 {
513         GdkPixbuf *emblem, *retval = NULL;
514
515         emblem = modest_platform_get_icon (icon_name, size);
516         if (emblem) {
517                 retval = gdk_pixbuf_copy (base_pixbuf);
518                 gdk_pixbuf_composite (emblem, retval, 0, 0,
519                                       MIN (gdk_pixbuf_get_width (emblem),
520                                            gdk_pixbuf_get_width (retval)),
521                                       MIN (gdk_pixbuf_get_height (emblem),
522                                            gdk_pixbuf_get_height (retval)),
523                                                   0, 0, 1, 1, GDK_INTERP_NEAREST, 255);
524                 g_object_unref (emblem);
525         }
526         return retval;
527 }
528
529 static inline ThreePixbufs *
530 get_composite_icons (const gchar *icon_code,
531                      GdkPixbuf **pixbuf,
532                      GdkPixbuf **pixbuf_open,
533                      GdkPixbuf **pixbuf_close)
534 {
535         ThreePixbufs *retval;
536
537         if (!*pixbuf)
538                 *pixbuf = gdk_pixbuf_copy (modest_platform_get_icon (icon_code, MODEST_ICON_SIZE_SMALL));
539
540         if (!*pixbuf_open)
541                 *pixbuf_open = get_composite_pixbuf ("qgn_list_gene_fldr_exp",
542                                                      MODEST_ICON_SIZE_SMALL,
543                                                      *pixbuf);
544
545         if (!*pixbuf_close)
546                 *pixbuf_close = get_composite_pixbuf ("qgn_list_gene_fldr_clp",
547                                                       MODEST_ICON_SIZE_SMALL,
548                                                       *pixbuf);
549
550         retval = g_slice_new0 (ThreePixbufs);
551         if (*pixbuf)
552                 retval->pixbuf = g_object_ref (*pixbuf);
553         if (*pixbuf_open)
554                 retval->pixbuf_open = g_object_ref (*pixbuf_open);
555         if (*pixbuf_close)
556                 retval->pixbuf_close = g_object_ref (*pixbuf_close);
557
558         return retval;
559 }
560
561 static ThreePixbufs*
562 get_folder_icons (TnyFolderType type, GObject *instance)
563 {
564         static GdkPixbuf *inbox_pixbuf = NULL, *outbox_pixbuf = NULL,
565                 *junk_pixbuf = NULL, *sent_pixbuf = NULL,
566                 *trash_pixbuf = NULL, *draft_pixbuf = NULL,
567                 *normal_pixbuf = NULL, *anorm_pixbuf = NULL,
568                 *ammc_pixbuf = NULL, *avirt_pixbuf = NULL;
569
570         static GdkPixbuf *inbox_pixbuf_open = NULL, *outbox_pixbuf_open = NULL,
571                 *junk_pixbuf_open = NULL, *sent_pixbuf_open = NULL,
572                 *trash_pixbuf_open = NULL, *draft_pixbuf_open = NULL,
573                 *normal_pixbuf_open = NULL, *anorm_pixbuf_open = NULL,
574                 *ammc_pixbuf_open = NULL, *avirt_pixbuf_open = NULL;
575
576         static GdkPixbuf *inbox_pixbuf_close = NULL, *outbox_pixbuf_close = NULL,
577                 *junk_pixbuf_close = NULL, *sent_pixbuf_close = NULL,
578                 *trash_pixbuf_close = NULL, *draft_pixbuf_close = NULL,
579                 *normal_pixbuf_close = NULL, *anorm_pixbuf_close = NULL,
580                 *ammc_pixbuf_close = NULL, *avirt_pixbuf_close = NULL;
581
582         ThreePixbufs *retval = NULL;
583
584         /* Sometimes an special folder is reported by the server as
585            NORMAL, like some versions of Dovecot */
586         if (type == TNY_FOLDER_TYPE_NORMAL ||
587             type == TNY_FOLDER_TYPE_UNKNOWN) {
588                 type = modest_tny_folder_guess_folder_type (TNY_FOLDER (instance));
589         }
590
591         switch (type) {
592         case TNY_FOLDER_TYPE_INVALID:
593                 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
594                 break;
595
596         case TNY_FOLDER_TYPE_ROOT:
597                 if (TNY_IS_ACCOUNT (instance)) {
598
599                         if (modest_tny_account_is_virtual_local_folders (TNY_ACCOUNT (instance))) {
600                                 retval = get_composite_icons (MODEST_FOLDER_ICON_LOCAL_FOLDERS,
601                                                               &avirt_pixbuf,
602                                                               &avirt_pixbuf_open,
603                                                               &avirt_pixbuf_close);
604                         } else {
605                                 const gchar *account_id = tny_account_get_id (TNY_ACCOUNT (instance));
606
607                                 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
608                                         retval = get_composite_icons (MODEST_FOLDER_ICON_MMC,
609                                                                       &ammc_pixbuf,
610                                                                       &ammc_pixbuf_open,
611                                                                       &ammc_pixbuf_close);
612                                 } else {
613                                         retval = get_composite_icons (MODEST_FOLDER_ICON_ACCOUNT,
614                                                                       &anorm_pixbuf,
615                                                                       &anorm_pixbuf_open,
616                                                                       &anorm_pixbuf_close);
617                                 }
618                         }
619                 }
620                 break;
621         case TNY_FOLDER_TYPE_INBOX:
622                 retval = get_composite_icons (MODEST_FOLDER_ICON_INBOX,
623                                               &inbox_pixbuf,
624                                               &inbox_pixbuf_open,
625                                               &inbox_pixbuf_close);
626                 break;
627         case TNY_FOLDER_TYPE_OUTBOX:
628                 retval = get_composite_icons (MODEST_FOLDER_ICON_OUTBOX,
629                                               &outbox_pixbuf,
630                                               &outbox_pixbuf_open,
631                                               &outbox_pixbuf_close);
632                 break;
633         case TNY_FOLDER_TYPE_JUNK:
634                 retval = get_composite_icons (MODEST_FOLDER_ICON_JUNK,
635                                               &junk_pixbuf,
636                                               &junk_pixbuf_open,
637                                               &junk_pixbuf_close);
638                 break;
639         case TNY_FOLDER_TYPE_SENT:
640                 retval = get_composite_icons (MODEST_FOLDER_ICON_SENT,
641                                               &sent_pixbuf,
642                                               &sent_pixbuf_open,
643                                               &sent_pixbuf_close);
644                 break;
645         case TNY_FOLDER_TYPE_TRASH:
646                 retval = get_composite_icons (MODEST_FOLDER_ICON_TRASH,
647                                               &trash_pixbuf,
648                                               &trash_pixbuf_open,
649                                               &trash_pixbuf_close);
650                 break;
651         case TNY_FOLDER_TYPE_DRAFTS:
652                 retval = get_composite_icons (MODEST_FOLDER_ICON_DRAFTS,
653                                               &draft_pixbuf,
654                                               &draft_pixbuf_open,
655                                               &draft_pixbuf_close);
656                 break;
657         case TNY_FOLDER_TYPE_NORMAL:
658         default:
659                 retval = get_composite_icons (MODEST_FOLDER_ICON_NORMAL,
660                                               &normal_pixbuf,
661                                               &normal_pixbuf_open,
662                                               &normal_pixbuf_close);
663                 break;
664         }
665
666         return retval;
667 }
668
669 static void
670 free_pixbufs (ThreePixbufs *pixbufs)
671 {
672         if (pixbufs->pixbuf)
673                 g_object_unref (pixbufs->pixbuf);
674         if (pixbufs->pixbuf_open)
675                 g_object_unref (pixbufs->pixbuf_open);
676         if (pixbufs->pixbuf_close)
677                 g_object_unref (pixbufs->pixbuf_close);
678         g_slice_free (ThreePixbufs, pixbufs);
679 }
680
681 static void
682 icon_cell_data  (GtkTreeViewColumn *column,
683                  GtkCellRenderer *renderer,
684                  GtkTreeModel *tree_model,
685                  GtkTreeIter *iter,
686                  gpointer data)
687 {
688         GObject *rendobj = NULL, *instance = NULL;
689         TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
690         gboolean has_children;
691         ThreePixbufs *pixbufs;
692
693         rendobj = (GObject *) renderer;
694
695         gtk_tree_model_get (tree_model, iter,
696                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
697                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
698                             -1);
699
700         if (!instance)
701                 return;
702
703         has_children = gtk_tree_model_iter_has_child (tree_model, iter);
704         pixbufs = get_folder_icons (type, instance);
705         g_object_unref (instance);
706
707         /* Set pixbuf */
708         g_object_set (rendobj, "pixbuf", pixbufs->pixbuf, NULL);
709
710         if (has_children) {
711                 g_object_set (rendobj, "pixbuf-expander-open", pixbufs->pixbuf_open, NULL);
712                 g_object_set (rendobj, "pixbuf-expander-closed", pixbufs->pixbuf_close, NULL);
713         }
714
715         free_pixbufs (pixbufs);
716 }
717
718 static void
719 add_columns (GtkWidget *treeview)
720 {
721         GtkTreeViewColumn *column;
722         GtkCellRenderer *renderer;
723         GtkTreeSelection *sel;
724
725         /* Create column */
726         column = gtk_tree_view_column_new ();
727
728         /* Set icon and text render function */
729         renderer = gtk_cell_renderer_pixbuf_new();
730         gtk_tree_view_column_pack_start (column, renderer, FALSE);
731         gtk_tree_view_column_set_cell_data_func(column, renderer,
732                                                 icon_cell_data, treeview, NULL);
733
734         renderer = gtk_cell_renderer_text_new();
735         g_object_set (renderer, "ellipsize", PANGO_ELLIPSIZE_END,
736                         "ellipsize-set", TRUE, NULL);
737         gtk_tree_view_column_pack_start (column, renderer, TRUE);
738         gtk_tree_view_column_set_cell_data_func(column, renderer,
739                                                 text_cell_data, treeview, NULL);
740
741         /* Set selection mode */
742         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(treeview));
743         gtk_tree_selection_set_mode (sel, GTK_SELECTION_SINGLE);
744
745         /* Set treeview appearance */
746         gtk_tree_view_column_set_spacing (column, 2);
747         gtk_tree_view_column_set_resizable (column, TRUE);
748         gtk_tree_view_column_set_fixed_width (column, TRUE);
749         gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(treeview), FALSE);
750         gtk_tree_view_set_enable_search (GTK_TREE_VIEW(treeview), FALSE);
751
752         /* Add column */
753         gtk_tree_view_append_column (GTK_TREE_VIEW(treeview),column);
754 }
755
756 static void
757 modest_folder_view_init (ModestFolderView *obj)
758 {
759         ModestFolderViewPrivate *priv;
760         ModestConf *conf;
761
762         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
763
764         priv->timer_expander = 0;
765         priv->account_store  = NULL;
766         priv->query          = NULL;
767         priv->style          = MODEST_FOLDER_VIEW_STYLE_SHOW_ALL;
768         priv->cur_folder_store   = NULL;
769         priv->visible_account_id = NULL;
770         priv->folder_to_select = NULL;
771
772         priv->reexpand = TRUE;
773
774         /* Initialize the local account name */
775         conf = modest_runtime_get_conf();
776         priv->local_account_name = modest_conf_get_string (conf, MODEST_CONF_DEVICE_NAME, NULL);
777
778         /* Init email clipboard */
779         priv->clipboard = modest_runtime_get_email_clipboard ();
780         priv->hidding_ids = NULL;
781         priv->n_selected = 0;
782         priv->reselect = FALSE;
783         priv->show_non_move = TRUE;
784
785         /* Build treeview */
786         add_columns (GTK_WIDGET (obj));
787
788         /* Setup drag and drop */
789         setup_drag_and_drop (GTK_TREE_VIEW(obj));
790
791         /* Connect signals */
792         g_signal_connect (G_OBJECT (obj),
793                           "key-press-event",
794                           G_CALLBACK (on_key_pressed), NULL);
795
796         priv->display_name_changed_signal =
797                 g_signal_connect (modest_runtime_get_account_mgr (),
798                                   "display_name_changed",
799                                   G_CALLBACK (on_display_name_changed),
800                                   obj);
801
802         /*
803          * Track changes in the local account name (in the device it
804          * will be the device name)
805          */
806         priv->conf_key_signal = g_signal_connect (G_OBJECT(conf),
807                                                   "key_changed",
808                                                   G_CALLBACK(on_configuration_key_changed),
809                                                   obj);
810 }
811
812 static void
813 tny_account_store_view_init (gpointer g, gpointer iface_data)
814 {
815         TnyAccountStoreViewIface *klass = (TnyAccountStoreViewIface *)g;
816
817         klass->set_account_store = modest_folder_view_set_account_store;
818 }
819
820 static void
821 modest_folder_view_finalize (GObject *obj)
822 {
823         ModestFolderViewPrivate *priv;
824         GtkTreeSelection    *sel;
825
826         g_return_if_fail (obj);
827
828         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
829
830         if (priv->timer_expander != 0) {
831                 g_source_remove (priv->timer_expander);
832                 priv->timer_expander = 0;
833         }
834
835         if (priv->account_store) {
836                 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
837                                              priv->account_inserted_signal);
838                 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
839                                              priv->account_removed_signal);
840                 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
841                                              priv->account_changed_signal);
842                 g_object_unref (G_OBJECT(priv->account_store));
843                 priv->account_store = NULL;
844         }
845
846         if (g_signal_handler_is_connected (modest_runtime_get_account_mgr (), 
847                                            priv->display_name_changed_signal)) {
848                 g_signal_handler_disconnect (modest_runtime_get_account_mgr (),
849                                              priv->display_name_changed_signal);
850                 priv->display_name_changed_signal = 0;
851         }
852
853         if (priv->query) {
854                 g_object_unref (G_OBJECT (priv->query));
855                 priv->query = NULL;
856         }
857
858         if (priv->folder_to_select) {
859                 g_object_unref (G_OBJECT(priv->folder_to_select));
860                 priv->folder_to_select = NULL;
861         }
862
863         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(obj));
864         if (sel)
865                 g_signal_handler_disconnect (G_OBJECT(sel), priv->changed_signal);
866
867         g_free (priv->local_account_name);
868         g_free (priv->visible_account_id);
869
870         if (priv->conf_key_signal) {
871                 g_signal_handler_disconnect (modest_runtime_get_conf (),
872                                              priv->conf_key_signal);
873                 priv->conf_key_signal = 0;
874         }
875
876         if (priv->cur_folder_store) {
877                 g_object_unref (priv->cur_folder_store);
878                 priv->cur_folder_store = NULL;
879         }
880
881         /* Clear hidding array created by cut operation */
882         _clear_hidding_filter (MODEST_FOLDER_VIEW (obj));
883
884         G_OBJECT_CLASS(parent_class)->finalize (obj);
885 }
886
887
888 static void
889 modest_folder_view_set_account_store (TnyAccountStoreView *self, TnyAccountStore *account_store)
890 {
891         ModestFolderViewPrivate *priv;
892         TnyDevice *device;
893
894         g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
895         g_return_if_fail (TNY_IS_ACCOUNT_STORE (account_store));
896
897         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
898         device = tny_account_store_get_device (account_store);
899
900         if (G_UNLIKELY (priv->account_store)) {
901
902                 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
903                                                    priv->account_inserted_signal))
904                         g_signal_handler_disconnect (G_OBJECT (priv->account_store),
905                                                      priv->account_inserted_signal);
906                 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
907                                                    priv->account_removed_signal))
908                         g_signal_handler_disconnect (G_OBJECT (priv->account_store),
909                                                      priv->account_removed_signal);
910                 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
911                                                    priv->account_changed_signal))
912                         g_signal_handler_disconnect (G_OBJECT (priv->account_store),
913                                                      priv->account_changed_signal);
914                 g_object_unref (G_OBJECT (priv->account_store));
915         }
916
917         priv->account_store = g_object_ref (G_OBJECT (account_store));
918
919         priv->account_removed_signal =
920                 g_signal_connect (G_OBJECT(account_store), "account_removed",
921                                   G_CALLBACK (on_account_removed), self);
922
923         priv->account_inserted_signal =
924                 g_signal_connect (G_OBJECT(account_store), "account_inserted",
925                                   G_CALLBACK (on_account_inserted), self);
926
927         priv->account_changed_signal =
928                 g_signal_connect (G_OBJECT(account_store), "account_changed",
929                                   G_CALLBACK (on_account_changed), self);
930
931         modest_folder_view_update_model (MODEST_FOLDER_VIEW (self), account_store);
932         priv->reselect = FALSE;
933         modest_folder_view_select_first_inbox_or_local (MODEST_FOLDER_VIEW (self));
934
935         g_object_unref (G_OBJECT (device));
936 }
937
938 static void
939 on_account_inserted (TnyAccountStore *account_store,
940                      TnyAccount *account,
941                      gpointer user_data)
942 {
943         ModestFolderViewPrivate *priv;
944         GtkTreeModel *sort_model, *filter_model;
945
946         /* Ignore transport account insertions, we're not showing them
947            in the folder view */
948         if (TNY_IS_TRANSPORT_ACCOUNT (account))
949                 return;
950
951         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (user_data);
952
953
954         /* If we're adding a new account, and there is no previous
955            one, we need to select the visible server account */
956         if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE &&
957             !priv->visible_account_id)
958                 modest_widget_memory_restore (modest_runtime_get_conf(),
959                                               G_OBJECT (user_data),
960                                               MODEST_CONF_FOLDER_VIEW_KEY);
961
962         if (!GTK_IS_TREE_VIEW(user_data)) {
963                 g_warning ("BUG: %s: not a valid tree view", __FUNCTION__);
964                 return;
965         }
966
967         /* Get the inner model */
968         /* check, is some rare cases, we did not get the right thing here,
969          * NB#84097 */
970         filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (user_data));
971         if (!GTK_IS_TREE_MODEL_FILTER(filter_model)) {
972                 g_warning ("BUG: %s: not a valid filter model", __FUNCTION__);
973                 return;
974         }
975
976         /* check, is some rare cases, we did not get the right thing here,
977          * NB#84097 */
978         sort_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
979         if (!GTK_IS_TREE_MODEL_SORT(sort_model)) {
980                 g_warning ("BUG: %s: not a valid sort model", __FUNCTION__);
981                 return;
982         }
983
984         /* Insert the account in the model */
985         tny_list_append (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
986                          G_OBJECT (account));
987
988         /* Refilter the model */
989         gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
990 }
991
992
993 static gboolean
994 same_account_selected (ModestFolderView *self,
995                        TnyAccount *account)
996 {
997         ModestFolderViewPrivate *priv;
998         gboolean same_account = FALSE;
999
1000         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1001
1002         if (priv->cur_folder_store) {
1003                 TnyAccount *selected_folder_account = NULL;
1004
1005                 if (TNY_IS_FOLDER (priv->cur_folder_store)) {
1006                         selected_folder_account =
1007                                 modest_tny_folder_get_account (TNY_FOLDER (priv->cur_folder_store));
1008                 } else {
1009                         selected_folder_account =
1010                                 TNY_ACCOUNT (g_object_ref (priv->cur_folder_store));
1011                 }
1012
1013                 if (selected_folder_account == account)
1014                         same_account = TRUE;
1015
1016                 g_object_unref (selected_folder_account);
1017         }
1018         return same_account;
1019 }
1020
1021 /**
1022  *
1023  * Selects the first inbox or the local account in an idle
1024  */
1025 static gboolean
1026 on_idle_select_first_inbox_or_local (gpointer user_data)
1027 {
1028         ModestFolderView *self = MODEST_FOLDER_VIEW (user_data);
1029
1030         gdk_threads_enter ();
1031         modest_folder_view_select_first_inbox_or_local (self);
1032         gdk_threads_leave ();
1033
1034         return FALSE;
1035 }
1036
1037 static void
1038 on_account_changed (TnyAccountStore *account_store,
1039                     TnyAccount *tny_account,
1040                     gpointer user_data)
1041 {
1042         ModestFolderView *self;
1043         ModestFolderViewPrivate *priv;
1044         GtkTreeModel *sort_model, *filter_model;
1045         GtkTreeSelection *sel;
1046         gboolean same_account;
1047
1048         /* Ignore transport account insertions, we're not showing them
1049            in the folder view */
1050         if (TNY_IS_TRANSPORT_ACCOUNT (tny_account))
1051                 return;
1052
1053         if (!MODEST_IS_FOLDER_VIEW(user_data)) {
1054                 g_warning ("BUG: %s: not a valid folder view", __FUNCTION__);
1055                 return;
1056         }
1057
1058         self = MODEST_FOLDER_VIEW (user_data);
1059         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (user_data);
1060
1061         /* Get the inner model */
1062         filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (user_data));
1063         if (!GTK_IS_TREE_MODEL_FILTER(filter_model)) {
1064                 g_warning ("BUG: %s: not a valid filter model", __FUNCTION__);
1065                 return;
1066         }
1067
1068         sort_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
1069         if (!GTK_IS_TREE_MODEL_SORT(sort_model)) {
1070                 g_warning ("BUG: %s: not a valid sort model", __FUNCTION__);
1071                 return;
1072         }
1073
1074         /* Invalidate the cur_folder_store only if the selected folder
1075            belongs to the account that is being removed */
1076         same_account = same_account_selected (self, tny_account);
1077         if (same_account) {
1078                 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1079                 gtk_tree_selection_unselect_all (sel);
1080         }
1081
1082         /* Remove the account from the model */
1083         tny_list_remove (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
1084                          G_OBJECT (tny_account));
1085
1086         /* Insert the account in the model */
1087         tny_list_append (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
1088                          G_OBJECT (tny_account));
1089
1090         /* Refilter the model */
1091         gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1092
1093         /* Select the first INBOX if the currently selected folder
1094            belongs to the account that is being deleted */
1095         if (same_account && !MODEST_IS_TNY_LOCAL_FOLDERS_ACCOUNT (tny_account))
1096                 g_idle_add (on_idle_select_first_inbox_or_local, self);
1097 }
1098
1099 static void
1100 on_account_removed (TnyAccountStore *account_store,
1101                     TnyAccount *account,
1102                     gpointer user_data)
1103 {
1104         ModestFolderView *self = NULL;
1105         ModestFolderViewPrivate *priv;
1106         GtkTreeModel *sort_model, *filter_model;
1107         GtkTreeSelection *sel = NULL;
1108         gboolean same_account = FALSE;
1109
1110         /* Ignore transport account removals, we're not showing them
1111            in the folder view */
1112         if (TNY_IS_TRANSPORT_ACCOUNT (account))
1113                 return;
1114
1115         if (!MODEST_IS_FOLDER_VIEW(user_data)) {
1116                 g_warning ("BUG: %s: not a valid folder view", __FUNCTION__);
1117                 return;
1118         }
1119
1120         self = MODEST_FOLDER_VIEW (user_data);
1121         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1122
1123         /* Invalidate the cur_folder_store only if the selected folder
1124            belongs to the account that is being removed */
1125         same_account = same_account_selected (self, account);
1126         if (same_account) {
1127                 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1128                 gtk_tree_selection_unselect_all (sel);
1129         }
1130
1131         /* Invalidate row to select only if the folder to select
1132            belongs to the account that is being removed*/
1133         if (priv->folder_to_select) {
1134                 TnyAccount *folder_to_select_account = NULL;
1135
1136                 folder_to_select_account = tny_folder_get_account (priv->folder_to_select);
1137                 if (folder_to_select_account == account) {
1138                         modest_folder_view_disable_next_folder_selection (self);
1139                         g_object_unref (priv->folder_to_select);
1140                         priv->folder_to_select = NULL;
1141                 }
1142                 g_object_unref (folder_to_select_account);
1143         }
1144
1145         /* Remove the account from the model */
1146         filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1147         if (!GTK_IS_TREE_MODEL_FILTER(filter_model)) {
1148                 g_warning ("BUG: %s: not a valid filter model", __FUNCTION__);
1149                 return;
1150         }
1151
1152         sort_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
1153         if (!GTK_IS_TREE_MODEL_SORT(sort_model)) {
1154                 g_warning ("BUG: %s: not a valid sort model", __FUNCTION__);
1155                 return;
1156         }
1157
1158         tny_list_remove (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
1159                          G_OBJECT (account));
1160
1161         /* If the removed account is the currently viewed one then
1162            clear the configuration value. The new visible account will be the default account */
1163         if (priv->visible_account_id &&
1164             !strcmp (priv->visible_account_id, tny_account_get_id (account))) {
1165
1166                 /* Clear the current visible account_id */
1167                 modest_folder_view_set_account_id_of_visible_server_account (self, NULL);
1168
1169                 /* Call the restore method, this will set the new visible account */
1170                 modest_widget_memory_restore (modest_runtime_get_conf(), G_OBJECT(self),
1171                                               MODEST_CONF_FOLDER_VIEW_KEY);
1172         }
1173
1174         /* Refilter the model */
1175         gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1176
1177         /* Select the first INBOX if the currently selected folder
1178            belongs to the account that is being deleted */
1179         if (same_account)
1180                 g_idle_add (on_idle_select_first_inbox_or_local, self);
1181 }
1182
1183 void
1184 modest_folder_view_set_title (ModestFolderView *self, const gchar *title)
1185 {
1186         GtkTreeViewColumn *col;
1187
1188         g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
1189
1190         col = gtk_tree_view_get_column (GTK_TREE_VIEW(self), 0);
1191         if (!col) {
1192                 g_printerr ("modest: failed get column for title\n");
1193                 return;
1194         }
1195
1196         gtk_tree_view_column_set_title (col, title);
1197         gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self),
1198                                            title != NULL);
1199 }
1200
1201 static gboolean
1202 modest_folder_view_on_map (ModestFolderView *self,
1203                            GdkEventExpose *event,
1204                            gpointer data)
1205 {
1206         ModestFolderViewPrivate *priv;
1207
1208         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1209
1210         /* This won't happen often */
1211         if (G_UNLIKELY (priv->reselect)) {
1212                 /* Select the first inbox or the local account if not found */
1213
1214                 /* TODO: this could cause a lock at startup, so we
1215                    comment it for the moment. We know that this will
1216                    be a bug, because the INBOX is not selected, but we
1217                    need to rewrite some parts of Modest to avoid the
1218                    deathlock situation */
1219                 /* TODO: check if this is still the case */
1220                 priv->reselect = FALSE;
1221                 modest_folder_view_select_first_inbox_or_local (self);
1222                 /* Notify the display name observers */
1223                 g_signal_emit (G_OBJECT(self),
1224                                signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
1225                                NULL);
1226         }
1227
1228         if (priv->reexpand) {
1229                 expand_root_items (self);
1230                 priv->reexpand = FALSE;
1231         }
1232
1233         return FALSE;
1234 }
1235
1236 GtkWidget*
1237 modest_folder_view_new (TnyFolderStoreQuery *query)
1238 {
1239         GObject *self;
1240         ModestFolderViewPrivate *priv;
1241         GtkTreeSelection *sel;
1242
1243         self = G_OBJECT (g_object_new (MODEST_TYPE_FOLDER_VIEW, NULL));
1244         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1245
1246         if (query)
1247                 priv->query = g_object_ref (query);
1248
1249         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1250         priv->changed_signal = g_signal_connect (sel, "changed",
1251                                                  G_CALLBACK (on_selection_changed), self);
1252
1253         g_signal_connect (self, "expose-event", G_CALLBACK (modest_folder_view_on_map), NULL);
1254
1255         return GTK_WIDGET(self);
1256 }
1257
1258 /* this feels dirty; any other way to expand all the root items? */
1259 static void
1260 expand_root_items (ModestFolderView *self)
1261 {
1262         GtkTreePath *path;
1263         GtkTreeModel *model;
1264         GtkTreeIter iter;
1265
1266         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1267         path = gtk_tree_path_new_first ();
1268
1269         /* all folders should have child items, so.. */
1270         do {
1271                 gtk_tree_view_expand_row (GTK_TREE_VIEW(self), path, FALSE);
1272                 gtk_tree_path_next (path);
1273         } while (gtk_tree_model_get_iter (model, &iter, path));
1274
1275         gtk_tree_path_free (path);
1276 }
1277
1278 /*
1279  * We use this function to implement the
1280  * MODEST_FOLDER_VIEW_STYLE_SHOW_ONE style. We only show the default
1281  * account in this case, and the local folders.
1282  */
1283 static gboolean
1284 filter_row (GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
1285 {
1286         ModestFolderViewPrivate *priv;
1287         gboolean retval = TRUE;
1288         TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1289         GObject *instance = NULL;
1290         const gchar *id = NULL;
1291         guint i;
1292         gboolean found = FALSE;
1293         gboolean cleared = FALSE;
1294
1295         g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (data), FALSE);
1296         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (data);
1297
1298         gtk_tree_model_get (model, iter,
1299                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
1300                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
1301                             -1);
1302
1303         /* Do not show if there is no instance, this could indeed
1304            happen when the model is being modified while it's being
1305            drawn. This could occur for example when moving folders
1306            using drag&drop */
1307         if (!instance)
1308                 return FALSE;
1309
1310         if (type == TNY_FOLDER_TYPE_ROOT) {
1311                 /* TNY_FOLDER_TYPE_ROOT means that the instance is an
1312                    account instead of a folder. */
1313                 if (TNY_IS_ACCOUNT (instance)) {
1314                         TnyAccount *acc = TNY_ACCOUNT (instance);
1315                         const gchar *account_id = tny_account_get_id (acc);
1316
1317                         /* If it isn't a special folder,
1318                          * don't show it unless it is the visible account: */
1319                         if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE &&
1320                             !modest_tny_account_is_virtual_local_folders (acc) &&
1321                             strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
1322
1323                                 /* Show only the visible account id */
1324                                 if (priv->visible_account_id) {
1325                                         if (strcmp (account_id, priv->visible_account_id))
1326                                                 retval = FALSE;
1327                                 } else {
1328                                         retval = FALSE;
1329                                 }
1330                         }
1331
1332                         /* Never show these to the user. They are merged into one folder
1333                          * in the local-folders account instead: */
1334                         if (retval && MODEST_IS_TNY_OUTBOX_ACCOUNT (acc))
1335                                 retval = FALSE;
1336                 }
1337         }
1338
1339         /* Check hiding (if necessary) */
1340         cleared = modest_email_clipboard_cleared (priv->clipboard);
1341         if ((retval) && (!cleared) && (TNY_IS_FOLDER (instance))) {
1342                 id = tny_folder_get_id (TNY_FOLDER(instance));
1343                 if (priv->hidding_ids != NULL)
1344                         for (i=0; i < priv->n_selected && !found; i++)
1345                                 if (priv->hidding_ids[i] != NULL && id != NULL)
1346                                         found = (!strcmp (priv->hidding_ids[i], id));
1347
1348                 retval = !found;
1349         }
1350
1351
1352         /* If this is a move to dialog, hide Sent, Outbox and Drafts
1353         folder as no message can be move there according to UI specs */
1354         if (!priv->show_non_move) {
1355                 switch (type) {
1356                         case TNY_FOLDER_TYPE_OUTBOX:
1357                         case TNY_FOLDER_TYPE_SENT:
1358                         case TNY_FOLDER_TYPE_DRAFTS:
1359                                 retval = FALSE;
1360                                 break;
1361                         case TNY_FOLDER_TYPE_UNKNOWN:
1362                         case TNY_FOLDER_TYPE_NORMAL:
1363                                 type = modest_tny_folder_guess_folder_type(TNY_FOLDER(instance));
1364                                 if (type == TNY_FOLDER_TYPE_INVALID)
1365                                         g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1366
1367                                 if (type == TNY_FOLDER_TYPE_OUTBOX ||
1368                                     type == TNY_FOLDER_TYPE_SENT
1369                                     || type == TNY_FOLDER_TYPE_DRAFTS)
1370                                         retval = FALSE;
1371                                 break;
1372                         default:
1373                                 break;
1374                 }
1375         }
1376
1377         /* Free */
1378         g_object_unref (instance);
1379
1380         return retval;
1381 }
1382
1383
1384 gboolean
1385 modest_folder_view_update_model (ModestFolderView *self,
1386                                  TnyAccountStore *account_store)
1387 {
1388         ModestFolderViewPrivate *priv;
1389         GtkTreeModel *model /* , *old_model */;
1390         GtkTreeModel *filter_model = NULL, *sortable = NULL;
1391
1392         g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW (self), FALSE);
1393         g_return_val_if_fail (account_store && MODEST_IS_TNY_ACCOUNT_STORE(account_store),
1394                               FALSE);
1395
1396         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1397
1398         /* Notify that there is no folder selected */
1399         g_signal_emit (G_OBJECT(self),
1400                        signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
1401                        NULL, FALSE);
1402         if (priv->cur_folder_store) {
1403                 g_object_unref (priv->cur_folder_store);
1404                 priv->cur_folder_store = NULL;
1405         }
1406
1407         /* FIXME: the local accounts are not shown when the query
1408            selects only the subscribed folders */
1409         model = tny_gtk_folder_store_tree_model_new (NULL);
1410
1411         /* Get the accounts: */
1412         tny_account_store_get_accounts (TNY_ACCOUNT_STORE(account_store),
1413                                         TNY_LIST (model),
1414                                         TNY_ACCOUNT_STORE_STORE_ACCOUNTS);
1415
1416         sortable = gtk_tree_model_sort_new_with_model (model);
1417         gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
1418                                               TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
1419                                               GTK_SORT_ASCENDING);
1420         gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
1421                                          TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
1422                                          cmp_rows, NULL, NULL);
1423
1424         /* Create filter model */
1425         filter_model = gtk_tree_model_filter_new (sortable, NULL);
1426         gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
1427                                                 filter_row,
1428                                                 self,
1429                                                 NULL);
1430
1431         /* Set new model */
1432         gtk_tree_view_set_model (GTK_TREE_VIEW(self), filter_model);
1433         g_signal_connect (G_OBJECT(filter_model), "row-inserted",
1434                           (GCallback) on_row_inserted_maybe_select_folder, self);
1435
1436         g_object_unref (model);
1437         g_object_unref (filter_model);
1438         g_object_unref (sortable);
1439
1440         /* Force a reselection of the INBOX next time the widget is shown */
1441         priv->reselect = TRUE;
1442
1443         return TRUE;
1444 }
1445
1446
1447 static void
1448 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
1449 {
1450         GtkTreeModel *model = NULL;
1451         TnyFolderStore *folder = NULL;
1452         GtkTreeIter iter;
1453         ModestFolderView *tree_view = NULL;
1454         ModestFolderViewPrivate *priv = NULL;
1455         gboolean selected = FALSE;
1456
1457         g_return_if_fail (sel);
1458         g_return_if_fail (user_data);
1459
1460         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
1461
1462         selected = gtk_tree_selection_get_selected (sel, &model, &iter);
1463
1464         tree_view = MODEST_FOLDER_VIEW (user_data);
1465
1466         if (selected) {
1467                 gtk_tree_model_get (model, &iter,
1468                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
1469                                     -1);
1470
1471                 /* If the folder is the same do not notify */
1472                 if (folder && priv->cur_folder_store == folder) {
1473                         g_object_unref (folder);
1474                         return;
1475                 }
1476         }
1477
1478         /* Current folder was unselected */
1479         if (priv->cur_folder_store) {
1480                 /* We must do this firstly because a libtinymail-camel
1481                    implementation detail. If we issue the signal
1482                    before doing the sync_async, then that signal could
1483                    cause (and it actually does it) a free of the
1484                    summary of the folder (because the main window will
1485                    clear the headers view */
1486                 if (TNY_IS_FOLDER(priv->cur_folder_store))
1487                         tny_folder_sync_async (TNY_FOLDER(priv->cur_folder_store),
1488                                                FALSE, NULL, NULL, NULL);
1489
1490                 g_signal_emit (G_OBJECT(tree_view), signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
1491                        priv->cur_folder_store, FALSE);
1492
1493                 g_object_unref (priv->cur_folder_store);
1494                 priv->cur_folder_store = NULL;
1495         }
1496
1497         /* New current references */
1498         priv->cur_folder_store = folder;
1499
1500         /* New folder has been selected. Do not notify if there is
1501            nothing new selected */
1502         if (selected) {
1503                 g_signal_emit (G_OBJECT(tree_view),
1504                                signals[FOLDER_SELECTION_CHANGED_SIGNAL],
1505                                0, priv->cur_folder_store, TRUE);
1506         }
1507 }
1508
1509 TnyFolderStore *
1510 modest_folder_view_get_selected (ModestFolderView *self)
1511 {
1512         ModestFolderViewPrivate *priv;
1513
1514         g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW(self), NULL);
1515
1516         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1517         if (priv->cur_folder_store)
1518                 g_object_ref (priv->cur_folder_store);
1519
1520         return priv->cur_folder_store;
1521 }
1522
1523 static gint
1524 get_cmp_rows_type_pos (GObject *folder)
1525 {
1526         /* Remote accounts -> Local account -> MMC account .*/
1527         /* 0, 1, 2 */
1528
1529         if (TNY_IS_ACCOUNT (folder) &&
1530                 modest_tny_account_is_virtual_local_folders (
1531                         TNY_ACCOUNT (folder))) {
1532                 return 1;
1533         } else if (TNY_IS_ACCOUNT (folder)) {
1534                 TnyAccount *account = TNY_ACCOUNT (folder);
1535                 const gchar *account_id = tny_account_get_id (account);
1536                 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
1537                         return 2;
1538                 else
1539                         return 0;
1540         }
1541         else {
1542                 printf ("DEBUG: %s: unexpected type.\n", __FUNCTION__);
1543                 return -1; /* Should never happen */
1544         }
1545 }
1546
1547 static gint
1548 get_cmp_subfolder_type_pos (TnyFolderType t)
1549 {
1550         /* Inbox, Outbox, Drafts, Sent, User */
1551         /* 0, 1, 2, 3, 4 */
1552
1553         switch (t) {
1554         case TNY_FOLDER_TYPE_INBOX:
1555                 return 0;
1556                 break;
1557         case TNY_FOLDER_TYPE_OUTBOX:
1558                 return 1;
1559                 break;
1560         case TNY_FOLDER_TYPE_DRAFTS:
1561                 return 2;
1562                 break;
1563         case TNY_FOLDER_TYPE_SENT:
1564                 return 3;
1565                 break;
1566         default:
1567                 return 4;
1568         }
1569 }
1570
1571 /*
1572  * This function orders the mail accounts according to these rules:
1573  * 1st - remote accounts
1574  * 2nd - local account
1575  * 3rd - MMC account
1576  */
1577 static gint
1578 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1579           gpointer user_data)
1580 {
1581         gint cmp = 0;
1582         gchar *name1 = NULL;
1583         gchar *name2 = NULL;
1584         TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1585         TnyFolderType type2 = TNY_FOLDER_TYPE_UNKNOWN;
1586         GObject *folder1 = NULL;
1587         GObject *folder2 = NULL;
1588
1589         gtk_tree_model_get (tree_model, iter1,
1590                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name1,
1591                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
1592                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder1,
1593                             -1);
1594         gtk_tree_model_get (tree_model, iter2,
1595                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name2,
1596                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type2,
1597                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder2,
1598                             -1);
1599
1600         /* Return if we get no folder. This could happen when folder
1601            operations are happening. The model is updated after the
1602            folder copy/move actually occurs, so there could be
1603            situations where the model to be drawn is not correct */
1604         if (!folder1 || !folder2)
1605                 goto finish;
1606
1607         if (type == TNY_FOLDER_TYPE_ROOT) {
1608                 /* Compare the types, so that
1609                  * Remote accounts -> Local account -> MMC account .*/
1610                 const gint pos1 = get_cmp_rows_type_pos (folder1);
1611                 const gint pos2 = get_cmp_rows_type_pos (folder2);
1612                 /* printf ("DEBUG: %s:\n  type1=%s, pos1=%d\n  type2=%s, pos2=%d\n",
1613                         __FUNCTION__, G_OBJECT_TYPE_NAME(folder1), pos1, G_OBJECT_TYPE_NAME(folder2), pos2); */
1614                 if (pos1 <  pos2)
1615                         cmp = -1;
1616                 else if (pos1 > pos2)
1617                         cmp = 1;
1618                 else {
1619                         /* Compare items of the same type: */
1620
1621                         TnyAccount *account1 = NULL;
1622                         if (TNY_IS_ACCOUNT (folder1))
1623                                 account1 = TNY_ACCOUNT (folder1);
1624
1625                         TnyAccount *account2 = NULL;
1626                         if (TNY_IS_ACCOUNT (folder2))
1627                                 account2 = TNY_ACCOUNT (folder2);
1628
1629                         const gchar *account_id = account1 ? tny_account_get_id (account1) : NULL;
1630                         const gchar *account_id2 = account2 ? tny_account_get_id (account2) : NULL;
1631
1632                         if (!account_id && !account_id2) {
1633                                 cmp = 0;
1634                         } else if (!account_id) {
1635                                 cmp = -1;
1636                         } else if (!account_id2) {
1637                                 cmp = +1;
1638                         } else if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
1639                                 cmp = +1;
1640                         } else {
1641                                 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
1642                         }
1643                 }
1644         } else {
1645                 gint cmp1 = 0, cmp2 = 0;
1646                 /* get the parent to know if it's a local folder */
1647
1648                 GtkTreeIter parent;
1649                 gboolean has_parent;
1650                 has_parent = gtk_tree_model_iter_parent (tree_model, &parent, iter1);
1651                 if (has_parent) {
1652                         GObject *parent_folder;
1653                         TnyFolderType parent_type = TNY_FOLDER_TYPE_UNKNOWN;
1654                         gtk_tree_model_get (tree_model, &parent,
1655                                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &parent_type,
1656                                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &parent_folder,
1657                                             -1);
1658                         if ((parent_type == TNY_FOLDER_TYPE_ROOT) &&
1659                             TNY_IS_ACCOUNT (parent_folder)) {
1660                                 if (modest_tny_account_is_virtual_local_folders (TNY_ACCOUNT (parent_folder))) {
1661                                         cmp1 = get_cmp_subfolder_type_pos (modest_tny_folder_get_local_or_mmc_folder_type
1662                                                                            (TNY_FOLDER (folder1)));
1663                                         cmp2 = get_cmp_subfolder_type_pos (modest_tny_folder_get_local_or_mmc_folder_type
1664                                                                            (TNY_FOLDER (folder2)));
1665                                 } else if (modest_tny_account_is_memory_card_account (TNY_ACCOUNT (parent_folder))) {
1666                                         if (modest_local_folder_info_get_type (tny_folder_get_name (TNY_FOLDER (folder1))) == TNY_FOLDER_TYPE_ARCHIVE) {
1667                                                 cmp1 = 0;
1668                                                 cmp2 = 1;
1669                                                 } else if (modest_local_folder_info_get_type (tny_folder_get_name (TNY_FOLDER (folder2))) == TNY_FOLDER_TYPE_ARCHIVE) {
1670                                                 cmp1 = 1;
1671                                                 cmp2 = 0;
1672                                         }
1673                                 }
1674                         }
1675                         g_object_unref (parent_folder);
1676                 }
1677
1678                 /* if they are not local folders */
1679                 if (cmp1 == cmp2) {
1680                         cmp1 = get_cmp_subfolder_type_pos (tny_folder_get_folder_type (TNY_FOLDER (folder1)));
1681                         cmp2 = get_cmp_subfolder_type_pos (tny_folder_get_folder_type (TNY_FOLDER (folder2)));
1682                 }
1683
1684                 if (cmp1 == cmp2)
1685                         cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
1686                 else
1687                         cmp = (cmp1 - cmp2);
1688         }
1689
1690 finish:
1691         if (folder1)
1692                 g_object_unref(G_OBJECT(folder1));
1693         if (folder2)
1694                 g_object_unref(G_OBJECT(folder2));
1695
1696         g_free (name1);
1697         g_free (name2);
1698
1699         return cmp;
1700 }
1701
1702 /*****************************************************************************/
1703 /*                        DRAG and DROP stuff                                */
1704 /*****************************************************************************/
1705 /*
1706  * This function fills the #GtkSelectionData with the row and the
1707  * model that has been dragged. It's called when this widget is a
1708  * source for dnd after the event drop happened
1709  */
1710 static void
1711 on_drag_data_get (GtkWidget *widget, GdkDragContext *context, GtkSelectionData *selection_data,
1712                   guint info, guint time, gpointer data)
1713 {
1714         GtkTreeSelection *selection;
1715         GtkTreeModel *model;
1716         GtkTreeIter iter;
1717         GtkTreePath *source_row;
1718
1719         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1720         if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
1721
1722                 source_row = gtk_tree_model_get_path (model, &iter);
1723                 gtk_tree_set_row_drag_data (selection_data,
1724                                             model,
1725                                             source_row);
1726
1727                 gtk_tree_path_free (source_row);
1728         }
1729 }
1730
1731 typedef struct _DndHelper {
1732         ModestFolderView *folder_view;
1733         gboolean delete_source;
1734         GtkTreePath *source_row;
1735 } DndHelper;
1736
1737 static void
1738 dnd_helper_destroyer (DndHelper *helper)
1739 {
1740         /* Free the helper */
1741         gtk_tree_path_free (helper->source_row);
1742         g_slice_free (DndHelper, helper);
1743 }
1744
1745 static void
1746 xfer_folder_cb (ModestMailOperation *mail_op,
1747                 TnyFolder *new_folder,
1748                 gpointer user_data)
1749 {
1750         if (new_folder) {
1751                 /* Select the folder */
1752                 modest_folder_view_select_folder (MODEST_FOLDER_VIEW (user_data),
1753                                                   new_folder, FALSE);
1754         }
1755 }
1756
1757
1758 /* get the folder for the row the treepath refers to. */
1759 /* folder must be unref'd */
1760 static TnyFolderStore *
1761 tree_path_to_folder (GtkTreeModel *model, GtkTreePath *path)
1762 {
1763         GtkTreeIter iter;
1764         TnyFolderStore *folder = NULL;
1765
1766         if (gtk_tree_model_get_iter (model,&iter, path))
1767                 gtk_tree_model_get (model, &iter,
1768                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
1769                                     -1);
1770         return folder;
1771 }
1772
1773
1774 /*
1775  * This function is used by drag_data_received_cb to manage drag and
1776  * drop of a header, i.e, and drag from the header view to the folder
1777  * view.
1778  */
1779 static void
1780 drag_and_drop_from_header_view (GtkTreeModel *source_model,
1781                                 GtkTreeModel *dest_model,
1782                                 GtkTreePath  *dest_row,
1783                                 GtkSelectionData *selection_data)
1784 {
1785         TnyList *headers = NULL;
1786         TnyFolder *folder = NULL, *src_folder = NULL;
1787         TnyFolderType folder_type;
1788         GtkTreeIter source_iter, dest_iter;
1789         ModestWindowMgr *mgr = NULL;
1790         ModestWindow *main_win = NULL;
1791         gchar **uris, **tmp;
1792
1793         /* Build the list of headers */
1794         mgr = modest_runtime_get_window_mgr ();
1795         headers = tny_simple_list_new ();
1796         uris = modest_dnd_selection_data_get_paths (selection_data);
1797         tmp = uris;
1798
1799         while (*tmp != NULL) {
1800                 TnyHeader *header;
1801                 GtkTreePath *path;
1802                 gboolean first = TRUE;
1803
1804                 /* Get header */
1805                 path = gtk_tree_path_new_from_string (*tmp);
1806                 gtk_tree_model_get_iter (source_model, &source_iter, path);
1807                 gtk_tree_model_get (source_model, &source_iter,
1808                                     TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1809                                     &header, -1);
1810
1811                 /* Do not enable d&d of headers already opened */
1812                 if (!modest_window_mgr_find_registered_header(mgr, header, NULL))
1813                         tny_list_append (headers, G_OBJECT (header));
1814
1815                 if (G_UNLIKELY (first)) {
1816                         src_folder = tny_header_get_folder (header);
1817                         first = FALSE;
1818                 }
1819
1820                 /* Free and go on */
1821                 gtk_tree_path_free (path);
1822                 g_object_unref (header);
1823                 tmp++;
1824         }
1825         g_strfreev (uris);
1826
1827         /* This could happen ig we perform a d&d very quickly over the
1828            same row that row could dissapear because message is
1829            transferred */
1830         if (!TNY_IS_FOLDER (src_folder))
1831                 goto cleanup;
1832
1833         /* Get the target folder */
1834         gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
1835         gtk_tree_model_get (dest_model, &dest_iter,
1836                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
1837                             &folder, -1);
1838
1839         if (!folder || !TNY_IS_FOLDER(folder)) {
1840 /*              g_warning ("%s: not a valid target folder (%p)", __FUNCTION__, folder); */
1841                 goto cleanup;
1842         }
1843
1844         folder_type = modest_tny_folder_guess_folder_type (folder);
1845         if (folder_type == TNY_FOLDER_TYPE_INVALID) {
1846 /*              g_warning ("%s: invalid target folder", __FUNCTION__); */
1847                 goto cleanup;  /* cannot move messages there */
1848         }
1849
1850         if (modest_tny_folder_get_rules((TNY_FOLDER(folder))) & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE) {
1851 /*              g_warning ("folder not writable"); */
1852                 goto cleanup; /* verboten! */
1853         }
1854
1855         /* Ask for confirmation to move */
1856         main_win = modest_window_mgr_get_main_window (mgr, FALSE); /* don't create */
1857         if (!main_win) {
1858                 g_warning ("%s: BUG: no main window found", __FUNCTION__);
1859                 goto cleanup;
1860         }
1861
1862         /* Transfer messages */
1863         modest_ui_actions_transfer_messages_helper (GTK_WINDOW (main_win), src_folder,
1864                                                     headers, folder);
1865
1866         /* Frees */
1867 cleanup:
1868         if (G_IS_OBJECT (src_folder))
1869                 g_object_unref (src_folder);
1870         if (G_IS_OBJECT(folder))
1871                 g_object_unref (G_OBJECT (folder));
1872         if (G_IS_OBJECT(headers))
1873                 g_object_unref (headers);
1874 }
1875
1876 typedef struct {
1877         TnyFolderStore *src_folder;
1878         TnyFolderStore *dst_folder;
1879         ModestFolderView *folder_view;
1880         DndHelper *helper;
1881 } DndFolderInfo;
1882
1883 static void
1884 dnd_folder_info_destroyer (DndFolderInfo *info)
1885 {
1886         if (info->src_folder)
1887                 g_object_unref (info->src_folder);
1888         if (info->dst_folder)
1889                 g_object_unref (info->dst_folder);
1890         g_slice_free (DndFolderInfo, info);
1891 }
1892
1893 static void
1894 dnd_on_connection_failed_destroyer (DndFolderInfo *info,
1895                                     GtkWindow *parent_window,
1896                                     TnyAccount *account)
1897 {
1898         /* Show error */
1899         modest_ui_actions_on_account_connection_error (parent_window, account);
1900
1901         /* Free the helper & info */
1902         dnd_helper_destroyer (info->helper);
1903         dnd_folder_info_destroyer (info);
1904 }
1905
1906 static void
1907 drag_and_drop_from_folder_view_src_folder_performer (gboolean canceled,
1908                                                      GError *err,
1909                                                      GtkWindow *parent_window,
1910                                                      TnyAccount *account,
1911                                                      gpointer user_data)
1912 {
1913         DndFolderInfo *info = NULL;
1914         ModestMailOperation *mail_op;
1915
1916         info = (DndFolderInfo *) user_data;
1917
1918         if (err || canceled) {
1919                 dnd_on_connection_failed_destroyer (info, parent_window, account);
1920                 return;
1921         }
1922
1923         /* Do the mail operation */
1924         mail_op = modest_mail_operation_new_with_error_handling ((GObject *) parent_window,
1925                                                                  modest_ui_actions_move_folder_error_handler,
1926                                                                  info->src_folder, NULL);
1927
1928         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1929                                          mail_op);
1930
1931         /* Transfer the folder */
1932         modest_mail_operation_xfer_folder (mail_op,
1933                                            TNY_FOLDER (info->src_folder),
1934                                            info->dst_folder,
1935                                            info->helper->delete_source,
1936                                            xfer_folder_cb,
1937                                            info->helper->folder_view);
1938
1939         /* Frees */
1940         g_object_unref (G_OBJECT (mail_op));
1941         dnd_helper_destroyer (info->helper);
1942         dnd_folder_info_destroyer (info);
1943 }
1944
1945
1946 static void
1947 drag_and_drop_from_folder_view_dst_folder_performer (gboolean canceled,
1948                                                      GError *err,
1949                                                      GtkWindow *parent_window,
1950                                                      TnyAccount *account,
1951                                                      gpointer user_data)
1952 {
1953         DndFolderInfo *info = NULL;
1954
1955         info = (DndFolderInfo *) user_data;
1956
1957         if (err || canceled) {
1958                 dnd_on_connection_failed_destroyer (info, parent_window, account);
1959                 return;
1960         }
1961
1962         /* Connect to source folder and perform the copy/move */
1963         modest_platform_connect_if_remote_and_perform (NULL, TRUE,
1964                                                        info->src_folder,
1965                                                        drag_and_drop_from_folder_view_src_folder_performer,
1966                                                        info);
1967 }
1968
1969 /*
1970  * This function is used by drag_data_received_cb to manage drag and
1971  * drop of a folder, i.e, and drag from the folder view to the same
1972  * folder view.
1973  */
1974 static void
1975 drag_and_drop_from_folder_view (GtkTreeModel     *source_model,
1976                                 GtkTreeModel     *dest_model,
1977                                 GtkTreePath      *dest_row,
1978                                 GtkSelectionData *selection_data,
1979                                 DndHelper        *helper)
1980 {
1981         GtkTreeIter dest_iter, iter;
1982         TnyFolderStore *dest_folder = NULL;
1983         TnyFolderStore *folder = NULL;
1984         gboolean forbidden = FALSE;
1985         ModestWindow *win;
1986         DndFolderInfo *info = NULL;
1987
1988         win = modest_window_mgr_get_main_window (modest_runtime_get_window_mgr(), FALSE); /* don't create */
1989         if (!win) {
1990                 g_warning ("%s: BUG: no main window", __FUNCTION__);
1991                 dnd_helper_destroyer (helper);
1992                 return;
1993         }
1994
1995         if (!forbidden) {
1996                 /* check the folder rules for the destination */
1997                 folder = tree_path_to_folder (dest_model, dest_row);
1998                 if (TNY_IS_FOLDER(folder)) {
1999                         ModestTnyFolderRules rules =
2000                                 modest_tny_folder_get_rules (TNY_FOLDER (folder));
2001                         forbidden = rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE;
2002                 } else if (TNY_IS_FOLDER_STORE(folder)) {
2003                         /* enable local root as destination for folders */
2004                         if (!MODEST_IS_TNY_LOCAL_FOLDERS_ACCOUNT (folder) &&
2005                             !modest_tny_account_is_memory_card_account (TNY_ACCOUNT (folder)))
2006                                 forbidden = TRUE;
2007                 }
2008                 g_object_unref (folder);
2009         }
2010         if (!forbidden) {
2011                 /* check the folder rules for the source */
2012                 folder = tree_path_to_folder (source_model, helper->source_row);
2013                 if (TNY_IS_FOLDER(folder)) {
2014                         ModestTnyFolderRules rules =
2015                                 modest_tny_folder_get_rules (TNY_FOLDER (folder));
2016                         forbidden = rules & MODEST_FOLDER_RULES_FOLDER_NON_MOVEABLE;
2017                 } else
2018                         forbidden = TRUE;
2019                 g_object_unref (folder);
2020         }
2021
2022
2023         /* Check if the drag is possible */
2024         if (forbidden || !gtk_tree_path_compare (helper->source_row, dest_row)) {
2025                 /* Show error */
2026                 modest_platform_run_information_dialog ((GtkWindow *) win, 
2027                                                         _("mail_in_ui_folder_move_target_error"), 
2028                                                         FALSE);
2029                 /* Restore the previous selection */
2030                 folder = tree_path_to_folder (source_model, helper->source_row);
2031                 if (folder) {
2032                         if (TNY_IS_FOLDER (folder))
2033                                 modest_folder_view_select_folder (helper->folder_view, 
2034                                                                   TNY_FOLDER (folder), FALSE);
2035                         g_object_unref (folder);
2036                 }
2037                 dnd_helper_destroyer (helper);
2038                 return;
2039         }
2040
2041         /* Get data */
2042         gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
2043         gtk_tree_model_get (dest_model, &dest_iter,
2044                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
2045                             &dest_folder, -1);
2046         gtk_tree_model_get_iter (source_model, &iter, helper->source_row);
2047         gtk_tree_model_get (source_model, &iter,
2048                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
2049                             &folder, -1);
2050
2051         /* Create the info for the performer */
2052         info = g_slice_new0 (DndFolderInfo);
2053         info->src_folder = g_object_ref (folder);
2054         info->dst_folder = g_object_ref (dest_folder);
2055         info->helper = helper;
2056
2057         /* Connect to the destination folder and perform the copy/move */
2058         modest_platform_connect_if_remote_and_perform (GTK_WINDOW (win), TRUE,
2059                                                        dest_folder,
2060                                                        drag_and_drop_from_folder_view_dst_folder_performer,
2061                                                        info);
2062
2063         /* Frees */
2064         g_object_unref (dest_folder);
2065         g_object_unref (folder);
2066 }
2067
2068 /*
2069  * This function receives the data set by the "drag-data-get" signal
2070  * handler. This information comes within the #GtkSelectionData. This
2071  * function will manage both the drags of folders of the treeview and
2072  * drags of headers of the header view widget.
2073  */
2074 static void
2075 on_drag_data_received (GtkWidget *widget,
2076                        GdkDragContext *context,
2077                        gint x,
2078                        gint y,
2079                        GtkSelectionData *selection_data,
2080                        guint target_type,
2081                        guint time,
2082                        gpointer data)
2083 {
2084         GtkWidget *source_widget;
2085         GtkTreeModel *dest_model, *source_model;
2086         GtkTreePath *source_row, *dest_row;
2087         GtkTreeViewDropPosition pos;
2088         gboolean delete_source = FALSE;
2089         gboolean success = FALSE;
2090
2091         /* Do not allow further process */
2092         g_signal_stop_emission_by_name (widget, "drag-data-received");
2093         source_widget = gtk_drag_get_source_widget (context);
2094
2095         /* Get the action */
2096         if (context->action == GDK_ACTION_MOVE) {
2097                 delete_source = TRUE;
2098
2099                 /* Notify that there is no folder selected. We need to
2100                    do this in order to update the headers view (and
2101                    its monitors, because when moving, the old folder
2102                    won't longer exist. We can not wait for the end of
2103                    the operation, because the operation won't start if
2104                    the folder is in use */
2105                 if (source_widget == widget) {
2106                         GtkTreeSelection *sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
2107                         gtk_tree_selection_unselect_all (sel);
2108                 }
2109         }
2110
2111         /* Check if the get_data failed */
2112         if (selection_data == NULL || selection_data->length < 0)
2113                 goto end;
2114
2115         /* Select the destination model */
2116         dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
2117
2118         /* Get the path to the destination row. Can not call
2119            gtk_tree_view_get_drag_dest_row() because the source row
2120            is not selected anymore */
2121         gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget), x, y,
2122                                            &dest_row, &pos);
2123
2124         /* Only allow drops IN other rows */
2125         if (!dest_row ||
2126             pos == GTK_TREE_VIEW_DROP_BEFORE ||
2127             pos == GTK_TREE_VIEW_DROP_AFTER)
2128                 goto end;
2129
2130         success = TRUE;
2131         /* Drags from the header view */
2132         if (source_widget != widget) {
2133                 source_model = gtk_tree_view_get_model (GTK_TREE_VIEW (source_widget));
2134
2135                 drag_and_drop_from_header_view (source_model,
2136                                                 dest_model,
2137                                                 dest_row,
2138                                                 selection_data);
2139         } else {
2140                 DndHelper *helper = NULL;
2141
2142                 /* Get the source model and row */
2143                 gtk_tree_get_row_drag_data (selection_data,
2144                                             &source_model,
2145                                             &source_row);
2146
2147                 /* Create the helper */
2148                 helper = g_slice_new0 (DndHelper);
2149                 helper->delete_source = delete_source;
2150                 helper->source_row = gtk_tree_path_copy (source_row);
2151                 helper->folder_view = MODEST_FOLDER_VIEW (widget);
2152
2153                 drag_and_drop_from_folder_view (source_model,
2154                                                 dest_model,
2155                                                 dest_row,
2156                                                 selection_data,
2157                                                 helper);
2158
2159                 gtk_tree_path_free (source_row);
2160         }
2161
2162         /* Frees */
2163         gtk_tree_path_free (dest_row);
2164
2165  end:
2166         /* Finish the drag and drop */
2167         gtk_drag_finish (context, success, FALSE, time);
2168 }
2169
2170 /*
2171  * We define a "drag-drop" signal handler because we do not want to
2172  * use the default one, because the default one always calls
2173  * gtk_drag_finish and we prefer to do it in the "drag-data-received"
2174  * signal handler, because there we have all the information available
2175  * to know if the dnd was a success or not.
2176  */
2177 static gboolean
2178 drag_drop_cb (GtkWidget      *widget,
2179               GdkDragContext *context,
2180               gint            x,
2181               gint            y,
2182               guint           time,
2183               gpointer        user_data)
2184 {
2185         gpointer target;
2186
2187         if (!context->targets)
2188                 return FALSE;
2189
2190         /* Check if we're dragging a folder row */
2191         target = gtk_drag_dest_find_target (widget, context, NULL);
2192
2193         /* Request the data from the source. */
2194         gtk_drag_get_data(widget, context, target, time);
2195
2196     return TRUE;
2197 }
2198
2199 /*
2200  * This function expands a node of a tree view if it's not expanded
2201  * yet. Not sure why it needs the threads stuff, but gtk+`example code
2202  * does that, so that's why they're here.
2203  */
2204 static gint
2205 expand_row_timeout (gpointer data)
2206 {
2207         GtkTreeView *tree_view = data;
2208         GtkTreePath *dest_path = NULL;
2209         GtkTreeViewDropPosition pos;
2210         gboolean result = FALSE;
2211
2212         gdk_threads_enter ();
2213
2214         gtk_tree_view_get_drag_dest_row (tree_view,
2215                                          &dest_path,
2216                                          &pos);
2217
2218         if (dest_path &&
2219             (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER ||
2220              pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)) {
2221                 gtk_tree_view_expand_row (tree_view, dest_path, FALSE);
2222                 gtk_tree_path_free (dest_path);
2223         }
2224         else {
2225                 if (dest_path)
2226                         gtk_tree_path_free (dest_path);
2227
2228                 result = TRUE;
2229         }
2230
2231         gdk_threads_leave ();
2232
2233         return result;
2234 }
2235
2236 /*
2237  * This function is called whenever the pointer is moved over a widget
2238  * while dragging some data. It installs a timeout that will expand a
2239  * node of the treeview if not expanded yet. This function also calls
2240  * gdk_drag_status in order to set the suggested action that will be
2241  * used by the "drag-data-received" signal handler to know if we
2242  * should do a move or just a copy of the data.
2243  */
2244 static gboolean
2245 on_drag_motion (GtkWidget      *widget,
2246                 GdkDragContext *context,
2247                 gint            x,
2248                 gint            y,
2249                 guint           time,
2250                 gpointer        user_data)
2251 {
2252         GtkTreeViewDropPosition pos;
2253         GtkTreePath *dest_row;
2254         GtkTreeModel *dest_model;
2255         ModestFolderViewPrivate *priv;
2256         GdkDragAction suggested_action;
2257         gboolean valid_location = FALSE;
2258         TnyFolderStore *folder = NULL;
2259
2260         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
2261
2262         if (priv->timer_expander != 0) {
2263                 g_source_remove (priv->timer_expander);
2264                 priv->timer_expander = 0;
2265         }
2266
2267         gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
2268                                            x, y,
2269                                            &dest_row,
2270                                            &pos);
2271
2272         /* Do not allow drops between folders */
2273         if (!dest_row ||
2274             pos == GTK_TREE_VIEW_DROP_BEFORE ||
2275             pos == GTK_TREE_VIEW_DROP_AFTER) {
2276                 gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW (widget), NULL, 0);
2277                 gdk_drag_status(context, 0, time);
2278                 valid_location = FALSE;
2279                 goto out;
2280         } else {
2281                 valid_location = TRUE;
2282         }
2283
2284         /* Check that the destination folder is writable */
2285         dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
2286         folder = tree_path_to_folder (dest_model, dest_row);
2287         if (folder && TNY_IS_FOLDER (folder)) {
2288                 ModestTnyFolderRules rules = modest_tny_folder_get_rules(TNY_FOLDER (folder));
2289
2290                 if (rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE) {
2291                         valid_location = FALSE;
2292                         goto out;
2293                 }
2294         }
2295
2296         /* Expand the selected row after 1/2 second */
2297         if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), dest_row)) {
2298                 priv->timer_expander = g_timeout_add (500, expand_row_timeout, widget);
2299         }
2300         gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), dest_row, pos);
2301
2302         /* Select the desired action. By default we pick MOVE */
2303         suggested_action = GDK_ACTION_MOVE;
2304
2305         if (context->actions == GDK_ACTION_COPY)
2306             gdk_drag_status(context, GDK_ACTION_COPY, time);
2307         else if (context->actions == GDK_ACTION_MOVE)
2308             gdk_drag_status(context, GDK_ACTION_MOVE, time);
2309         else if (context->actions & suggested_action)
2310             gdk_drag_status(context, suggested_action, time);
2311         else
2312             gdk_drag_status(context, GDK_ACTION_DEFAULT, time);
2313
2314  out:
2315         if (folder)
2316                 g_object_unref (folder);
2317         if (dest_row) {
2318                 gtk_tree_path_free (dest_row);
2319         }
2320         g_signal_stop_emission_by_name (widget, "drag-motion");
2321
2322         return valid_location;
2323 }
2324
2325 /*
2326  * This function sets the treeview as a source and a target for dnd
2327  * events. It also connects all the requirede signals.
2328  */
2329 static void
2330 setup_drag_and_drop (GtkTreeView *self)
2331 {
2332         /* Set up the folder view as a dnd destination. Set only the
2333            highlight flag, otherwise gtk will have a different
2334            behaviour */
2335         gtk_drag_dest_set (GTK_WIDGET (self),
2336                            GTK_DEST_DEFAULT_HIGHLIGHT,
2337                            folder_view_drag_types,
2338                            G_N_ELEMENTS (folder_view_drag_types),
2339                            GDK_ACTION_MOVE | GDK_ACTION_COPY);
2340
2341         g_signal_connect (G_OBJECT (self),
2342                           "drag_data_received",
2343                           G_CALLBACK (on_drag_data_received),
2344                           NULL);
2345
2346
2347         /* Set up the treeview as a dnd source */
2348         gtk_drag_source_set (GTK_WIDGET (self),
2349                              GDK_BUTTON1_MASK,
2350                              folder_view_drag_types,
2351                              G_N_ELEMENTS (folder_view_drag_types),
2352                              GDK_ACTION_MOVE | GDK_ACTION_COPY);
2353
2354         g_signal_connect (G_OBJECT (self),
2355                           "drag_motion",
2356                           G_CALLBACK (on_drag_motion),
2357                           NULL);
2358
2359         g_signal_connect (G_OBJECT (self),
2360                           "drag_data_get",
2361                           G_CALLBACK (on_drag_data_get),
2362                           NULL);
2363
2364         g_signal_connect (G_OBJECT (self),
2365                           "drag_drop",
2366                           G_CALLBACK (drag_drop_cb),
2367                           NULL);
2368 }
2369
2370 /*
2371  * This function manages the navigation through the folders using the
2372  * keyboard or the hardware keys in the device
2373  */
2374 static gboolean
2375 on_key_pressed (GtkWidget *self,
2376                 GdkEventKey *event,
2377                 gpointer user_data)
2378 {
2379         GtkTreeSelection *selection;
2380         GtkTreeIter iter;
2381         GtkTreeModel *model;
2382         gboolean retval = FALSE;
2383
2384         /* Up and Down are automatically managed by the treeview */
2385         if (event->keyval == GDK_Return) {
2386                 /* Expand/Collapse the selected row */
2387                 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2388                 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
2389                         GtkTreePath *path;
2390
2391                         path = gtk_tree_model_get_path (model, &iter);
2392
2393                         if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (self), path))
2394                                 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
2395                         else
2396                                 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
2397                         gtk_tree_path_free (path);
2398                 }
2399                 /* No further processing */
2400                 retval = TRUE;
2401         }
2402
2403         return retval;
2404 }
2405
2406 /*
2407  * We listen to the changes in the local folder account name key,
2408  * because we want to show the right name in the view. The local
2409  * folder account name corresponds to the device name in the Maemo
2410  * version. We do this because we do not want to query gconf on each
2411  * tree view refresh. It's better to cache it and change whenever
2412  * necessary.
2413  */
2414 static void
2415 on_configuration_key_changed (ModestConf* conf,
2416                               const gchar *key,
2417                               ModestConfEvent event,
2418                               ModestConfNotificationId id,
2419                               ModestFolderView *self)
2420 {
2421         ModestFolderViewPrivate *priv;
2422
2423
2424         g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
2425         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2426
2427         if (!strcmp (key, MODEST_CONF_DEVICE_NAME)) {
2428                 g_free (priv->local_account_name);
2429
2430                 if (event == MODEST_CONF_EVENT_KEY_UNSET)
2431                         priv->local_account_name = g_strdup (MODEST_LOCAL_FOLDERS_DEFAULT_DISPLAY_NAME);
2432                 else
2433                         priv->local_account_name = modest_conf_get_string (modest_runtime_get_conf(),
2434                                                                            MODEST_CONF_DEVICE_NAME, NULL);
2435
2436                 /* Force a redraw */
2437 #if GTK_CHECK_VERSION(2, 8, 0)
2438                 GtkTreeViewColumn * tree_column;
2439
2440                 tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self),
2441                                                         TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN);
2442                 gtk_tree_view_column_queue_resize (tree_column);
2443 #else
2444                 gtk_widget_queue_draw (GTK_WIDGET (self));
2445 #endif
2446         }
2447 }
2448
2449 void
2450 modest_folder_view_set_style (ModestFolderView *self,
2451                               ModestFolderViewStyle style)
2452 {
2453         ModestFolderViewPrivate *priv;
2454
2455         g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2456         g_return_if_fail (style == MODEST_FOLDER_VIEW_STYLE_SHOW_ALL ||
2457                           style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE);
2458
2459         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2460
2461
2462         priv->style = style;
2463 }
2464
2465 void
2466 modest_folder_view_set_account_id_of_visible_server_account (ModestFolderView *self,
2467                                                              const gchar *account_id)
2468 {
2469         ModestFolderViewPrivate *priv;
2470         GtkTreeModel *model;
2471
2472         g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2473
2474         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2475
2476         /* This will be used by the filter_row callback,
2477          * to decided which rows to show: */
2478         if (priv->visible_account_id) {
2479                 g_free (priv->visible_account_id);
2480                 priv->visible_account_id = NULL;
2481         }
2482         if (account_id)
2483                 priv->visible_account_id = g_strdup (account_id);
2484
2485         /* Refilter */
2486         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2487         if (GTK_IS_TREE_MODEL_FILTER (model))
2488                 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2489
2490         /* Save settings to gconf */
2491         modest_widget_memory_save (modest_runtime_get_conf (), G_OBJECT(self),
2492                                    MODEST_CONF_FOLDER_VIEW_KEY);
2493 }
2494
2495 const gchar *
2496 modest_folder_view_get_account_id_of_visible_server_account (ModestFolderView *self)
2497 {
2498         ModestFolderViewPrivate *priv;
2499
2500         g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW(self), NULL);
2501
2502         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2503
2504         return (const gchar *) priv->visible_account_id;
2505 }
2506
2507 static gboolean
2508 find_inbox_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *inbox_iter)
2509 {
2510         do {
2511                 GtkTreeIter child;
2512                 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2513
2514                 gtk_tree_model_get (model, iter,
2515                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN,
2516                                     &type, -1);
2517
2518                 gboolean result = FALSE;
2519                 if (type == TNY_FOLDER_TYPE_INBOX) {
2520                         result = TRUE;
2521                 }
2522                 if (result) {
2523                         *inbox_iter = *iter;
2524                         return TRUE;
2525                 }
2526
2527                 if (gtk_tree_model_iter_children (model, &child, iter)) {
2528                         if (find_inbox_iter (model, &child, inbox_iter))
2529                                 return TRUE;
2530                 }
2531
2532         } while (gtk_tree_model_iter_next (model, iter));
2533
2534         return FALSE;
2535 }
2536
2537
2538
2539
2540 void
2541 modest_folder_view_select_first_inbox_or_local (ModestFolderView *self)
2542 {
2543         GtkTreeModel *model;
2544         GtkTreeIter iter, inbox_iter;
2545         GtkTreeSelection *sel;
2546         GtkTreePath *path = NULL;
2547
2548         g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2549
2550         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2551         if (!model)
2552                 return;
2553
2554         expand_root_items (self);
2555         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2556
2557         if (!gtk_tree_model_get_iter_first (model, &iter)) {
2558                 g_warning ("%s: model is empty", __FUNCTION__);
2559                 return;
2560         }
2561
2562         if (find_inbox_iter (model, &iter, &inbox_iter))
2563                 path = gtk_tree_model_get_path (model, &inbox_iter);
2564         else
2565                 path = gtk_tree_path_new_first ();
2566
2567         /* Select the row and free */
2568         gtk_tree_view_set_cursor (GTK_TREE_VIEW (self), path, NULL, FALSE);
2569         gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (self), path, NULL, FALSE, 0.0, 0.0);
2570         gtk_tree_path_free (path);
2571
2572         /* set focus */
2573         gtk_widget_grab_focus (GTK_WIDGET(self));
2574 }
2575
2576
2577 /* recursive */
2578 static gboolean
2579 find_folder_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *folder_iter,
2580                   TnyFolder* folder)
2581 {
2582         do {
2583                 GtkTreeIter child;
2584                 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2585                 TnyFolder* a_folder;
2586                 gchar *name = NULL;
2587
2588                 gtk_tree_model_get (model, iter,
2589                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &a_folder,
2590                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name,
2591                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
2592                                     -1);
2593                 g_free (name);
2594
2595                 if (folder == a_folder) {
2596                         g_object_unref (a_folder);
2597                         *folder_iter = *iter;
2598                         return TRUE;
2599                 }
2600                 g_object_unref (a_folder);
2601
2602                 if (gtk_tree_model_iter_children (model, &child, iter)) {
2603                         if (find_folder_iter (model, &child, folder_iter, folder))
2604                                 return TRUE;
2605                 }
2606
2607         } while (gtk_tree_model_iter_next (model, iter));
2608
2609         return FALSE;
2610 }
2611
2612
2613 static void
2614 on_row_inserted_maybe_select_folder (GtkTreeModel *tree_model,
2615                                      GtkTreePath *path,
2616                                      GtkTreeIter *iter,
2617                                      ModestFolderView *self)
2618 {
2619         ModestFolderViewPrivate *priv = NULL;
2620         GtkTreeSelection *sel;
2621         TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2622         GObject *instance = NULL;
2623
2624         if (!MODEST_IS_FOLDER_VIEW(self))
2625                 return;
2626
2627         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2628
2629         priv->reexpand = TRUE;
2630
2631         gtk_tree_model_get (tree_model, iter,
2632                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
2633                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
2634                             -1);
2635         if (type == TNY_FOLDER_TYPE_INBOX && priv->folder_to_select == NULL) {
2636                 priv->folder_to_select = g_object_ref (instance);
2637         }
2638         g_object_unref (instance);
2639
2640         if (priv->folder_to_select) {
2641
2642                 if (!modest_folder_view_select_folder (self, priv->folder_to_select,
2643                                                        FALSE)) {
2644                         GtkTreePath *path;
2645                         path = gtk_tree_model_get_path (tree_model, iter);
2646                         gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
2647
2648                         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2649
2650                         gtk_tree_selection_select_iter (sel, iter);
2651                         gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
2652
2653                         gtk_tree_path_free (path);
2654                 }
2655
2656                 /* Disable next */
2657                 modest_folder_view_disable_next_folder_selection (self);
2658
2659                 /* Refilter the model */
2660                 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (tree_model));
2661         }
2662 }
2663
2664
2665 void
2666 modest_folder_view_disable_next_folder_selection (ModestFolderView *self)
2667 {
2668         ModestFolderViewPrivate *priv;
2669
2670         g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2671
2672         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2673
2674         if (priv->folder_to_select)
2675                 g_object_unref(priv->folder_to_select);
2676
2677         priv->folder_to_select = NULL;
2678 }
2679
2680 gboolean
2681 modest_folder_view_select_folder (ModestFolderView *self, TnyFolder *folder,
2682                                   gboolean after_change)
2683 {
2684         GtkTreeModel *model;
2685         GtkTreeIter iter, folder_iter;
2686         GtkTreeSelection *sel;
2687         ModestFolderViewPrivate *priv = NULL;
2688
2689         g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW (self), FALSE);
2690         g_return_val_if_fail (folder && TNY_IS_FOLDER (folder), FALSE);
2691
2692         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2693
2694         if (after_change) {
2695                 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2696                 gtk_tree_selection_unselect_all (sel);
2697
2698                 if (priv->folder_to_select)
2699                         g_object_unref(priv->folder_to_select);
2700                 priv->folder_to_select = TNY_FOLDER(g_object_ref(folder));
2701                 return TRUE;
2702         }
2703
2704         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2705         if (!model)
2706                 return FALSE;
2707
2708
2709         /* Refilter the model, before selecting the folder */
2710         gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2711
2712         if (!gtk_tree_model_get_iter_first (model, &iter)) {
2713                 g_warning ("%s: model is empty", __FUNCTION__);
2714                 return FALSE;
2715         }
2716
2717         if (find_folder_iter (model, &iter, &folder_iter, folder)) {
2718                 GtkTreePath *path;
2719
2720                 path = gtk_tree_model_get_path (model, &folder_iter);
2721                 gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
2722
2723                 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2724                 gtk_tree_selection_select_iter (sel, &folder_iter);
2725                 gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
2726
2727                 gtk_tree_path_free (path);
2728                 return TRUE;
2729         }
2730         return FALSE;
2731 }
2732
2733
2734 void
2735 modest_folder_view_copy_selection (ModestFolderView *self)
2736 {
2737         g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2738
2739         /* Copy selection */
2740         _clipboard_set_selected_data (self, FALSE);
2741 }
2742
2743 void
2744 modest_folder_view_cut_selection (ModestFolderView *folder_view)
2745 {
2746         ModestFolderViewPrivate *priv = NULL;
2747         GtkTreeModel *model = NULL;
2748         const gchar **hidding = NULL;
2749         guint i, n_selected;
2750
2751         g_return_if_fail (folder_view && MODEST_IS_FOLDER_VIEW (folder_view));
2752         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
2753
2754         /* Copy selection */
2755         if (!_clipboard_set_selected_data (folder_view, TRUE))
2756                 return;
2757
2758         /* Get hidding ids */
2759         hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected);
2760
2761         /* Clear hidding array created by previous cut operation */
2762         _clear_hidding_filter (MODEST_FOLDER_VIEW (folder_view));
2763
2764         /* Copy hidding array */
2765         priv->n_selected = n_selected;
2766         priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
2767         for (i=0; i < n_selected; i++)
2768                 priv->hidding_ids[i] = g_strdup(hidding[i]);
2769
2770         /* Hide cut folders */
2771         model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
2772         gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2773 }
2774
2775 void
2776 modest_folder_view_copy_model (ModestFolderView *folder_view_src,
2777                                ModestFolderView *folder_view_dst)
2778 {
2779         GtkTreeModel *filter_model = NULL;
2780         GtkTreeModel *model = NULL;
2781         GtkTreeModel *new_filter_model = NULL;
2782
2783         g_return_if_fail (folder_view_src && MODEST_IS_FOLDER_VIEW (folder_view_src));
2784         g_return_if_fail (folder_view_dst && MODEST_IS_FOLDER_VIEW (folder_view_dst));
2785
2786         /* Get src model*/
2787         filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view_src));
2788         model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER(filter_model));
2789
2790         /* Build new filter model */
2791         new_filter_model = gtk_tree_model_filter_new (model, NULL);
2792         gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (new_filter_model),
2793                                                 filter_row,
2794                                                 folder_view_dst,
2795                                                 NULL);
2796         /* Set copied model */
2797         gtk_tree_view_set_model (GTK_TREE_VIEW (folder_view_dst), new_filter_model);
2798         g_signal_connect (G_OBJECT(new_filter_model), "row-inserted",
2799                           (GCallback) on_row_inserted_maybe_select_folder, folder_view_dst);
2800
2801         /* Free */
2802         g_object_unref (new_filter_model);
2803 }
2804
2805 void
2806 modest_folder_view_show_non_move_folders (ModestFolderView *folder_view,
2807                                           gboolean show)
2808 {
2809         GtkTreeModel *model = NULL;
2810         ModestFolderViewPrivate* priv;
2811
2812         g_return_if_fail (folder_view && MODEST_IS_FOLDER_VIEW (folder_view));
2813
2814         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
2815         priv->show_non_move = show;
2816 /*      modest_folder_view_update_model(folder_view, */
2817 /*                                      TNY_ACCOUNT_STORE(modest_runtime_get_account_store())); */
2818
2819         /* Hide special folders */
2820         model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
2821         if (GTK_IS_TREE_MODEL_FILTER (model)) {
2822                 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2823         }
2824 }
2825
2826 /* Returns FALSE if it did not selected anything */
2827 static gboolean
2828 _clipboard_set_selected_data (ModestFolderView *folder_view,
2829                               gboolean delete)
2830 {
2831         ModestFolderViewPrivate *priv = NULL;
2832         TnyFolderStore *folder = NULL;
2833         gboolean retval = FALSE;
2834
2835         g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (folder_view), FALSE);
2836         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
2837
2838         /* Set selected data on clipboard   */
2839         g_return_val_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard), FALSE);
2840         folder = modest_folder_view_get_selected (folder_view);
2841
2842         /* Do not allow to select an account */
2843         if (TNY_IS_FOLDER (folder)) {
2844                 modest_email_clipboard_set_data (priv->clipboard, TNY_FOLDER(folder), NULL, delete);
2845                 retval = TRUE;
2846         }
2847
2848         /* Free */
2849         g_object_unref (folder);
2850
2851         return retval;
2852 }
2853
2854 static void
2855 _clear_hidding_filter (ModestFolderView *folder_view)
2856 {
2857         ModestFolderViewPrivate *priv;
2858         guint i;
2859
2860         g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view));
2861         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
2862
2863         if (priv->hidding_ids != NULL) {
2864                 for (i=0; i < priv->n_selected; i++)
2865                         g_free (priv->hidding_ids[i]);
2866                 g_free(priv->hidding_ids);
2867         }
2868 }
2869
2870
2871 static void
2872 on_display_name_changed (ModestAccountMgr *mgr,
2873                          const gchar *account,
2874                          gpointer user_data)
2875 {
2876         ModestFolderView *self;
2877
2878         self = MODEST_FOLDER_VIEW (user_data);
2879
2880         /* Force a redraw */
2881 #if GTK_CHECK_VERSION(2, 8, 0)
2882         GtkTreeViewColumn * tree_column;
2883
2884         tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self),
2885                                                 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN);
2886         gtk_tree_view_column_queue_resize (tree_column);
2887 #else
2888         gtk_widget_queue_draw (GTK_WIDGET (self));
2889 #endif
2890 }