* Fixes NB#86822, added some extra heuristic to properly detect the inbox folder...
[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         return;
718 }
719
720 static void
721 add_columns (GtkWidget *treeview)
722 {
723         GtkTreeViewColumn *column;
724         GtkCellRenderer *renderer;
725         GtkTreeSelection *sel;
726
727         /* Create column */
728         column = gtk_tree_view_column_new ();
729
730         /* Set icon and text render function */
731         renderer = gtk_cell_renderer_pixbuf_new();
732         gtk_tree_view_column_pack_start (column, renderer, FALSE);
733         gtk_tree_view_column_set_cell_data_func(column, renderer,
734                                                 icon_cell_data, treeview, NULL);
735
736         renderer = gtk_cell_renderer_text_new();
737         g_object_set (renderer, "ellipsize", PANGO_ELLIPSIZE_END,
738                         "ellipsize-set", TRUE, NULL);
739         gtk_tree_view_column_pack_start (column, renderer, TRUE);
740         gtk_tree_view_column_set_cell_data_func(column, renderer,
741                                                 text_cell_data, treeview, NULL);
742
743         /* Set selection mode */
744         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(treeview));
745         gtk_tree_selection_set_mode (sel, GTK_SELECTION_SINGLE);
746
747         /* Set treeview appearance */
748         gtk_tree_view_column_set_spacing (column, 2);
749         gtk_tree_view_column_set_resizable (column, TRUE);
750         gtk_tree_view_column_set_fixed_width (column, TRUE);
751         gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(treeview), FALSE);
752         gtk_tree_view_set_enable_search (GTK_TREE_VIEW(treeview), FALSE);
753
754         /* Add column */
755         gtk_tree_view_append_column (GTK_TREE_VIEW(treeview),column);
756 }
757
758 static void
759 modest_folder_view_init (ModestFolderView *obj)
760 {
761         ModestFolderViewPrivate *priv;
762         ModestConf *conf;
763
764         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
765
766         priv->timer_expander = 0;
767         priv->account_store  = NULL;
768         priv->query          = NULL;
769         priv->style          = MODEST_FOLDER_VIEW_STYLE_SHOW_ALL;
770         priv->cur_folder_store   = NULL;
771         priv->visible_account_id = NULL;
772         priv->folder_to_select = NULL;
773
774         priv->reexpand = TRUE;
775
776         /* Initialize the local account name */
777         conf = modest_runtime_get_conf();
778         priv->local_account_name = modest_conf_get_string (conf, MODEST_CONF_DEVICE_NAME, NULL);
779
780         /* Init email clipboard */
781         priv->clipboard = modest_runtime_get_email_clipboard ();
782         priv->hidding_ids = NULL;
783         priv->n_selected = 0;
784         priv->reselect = FALSE;
785         priv->show_non_move = TRUE;
786
787         /* Build treeview */
788         add_columns (GTK_WIDGET (obj));
789
790         /* Setup drag and drop */
791         setup_drag_and_drop (GTK_TREE_VIEW(obj));
792
793         /* Connect signals */
794         g_signal_connect (G_OBJECT (obj),
795                           "key-press-event",
796                           G_CALLBACK (on_key_pressed), NULL);
797
798         priv->display_name_changed_signal =
799                 g_signal_connect (modest_runtime_get_account_mgr (),
800                                   "display_name_changed",
801                                   G_CALLBACK (on_display_name_changed),
802                                   obj);
803
804         /*
805          * Track changes in the local account name (in the device it
806          * will be the device name)
807          */
808         priv->conf_key_signal = g_signal_connect (G_OBJECT(conf),
809                                                   "key_changed",
810                                                   G_CALLBACK(on_configuration_key_changed),
811                                                   obj);
812 }
813
814 static void
815 tny_account_store_view_init (gpointer g, gpointer iface_data)
816 {
817         TnyAccountStoreViewIface *klass = (TnyAccountStoreViewIface *)g;
818
819         klass->set_account_store = modest_folder_view_set_account_store;
820 }
821
822 static void
823 modest_folder_view_finalize (GObject *obj)
824 {
825         ModestFolderViewPrivate *priv;
826         GtkTreeSelection    *sel;
827
828         g_return_if_fail (obj);
829
830         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
831
832         if (priv->timer_expander != 0) {
833                 g_source_remove (priv->timer_expander);
834                 priv->timer_expander = 0;
835         }
836
837         if (priv->account_store) {
838                 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
839                                              priv->account_inserted_signal);
840                 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
841                                              priv->account_removed_signal);
842                 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
843                                              priv->account_changed_signal);
844                 g_object_unref (G_OBJECT(priv->account_store));
845                 priv->account_store = NULL;
846         }
847
848         if (priv->query) {
849                 g_object_unref (G_OBJECT (priv->query));
850                 priv->query = NULL;
851         }
852
853         if (priv->folder_to_select) {
854                 g_object_unref (G_OBJECT(priv->folder_to_select));
855                 priv->folder_to_select = NULL;
856         }
857
858         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(obj));
859         if (sel)
860                 g_signal_handler_disconnect (G_OBJECT(sel), priv->changed_signal);
861
862         g_free (priv->local_account_name);
863         g_free (priv->visible_account_id);
864
865         if (priv->conf_key_signal) {
866                 g_signal_handler_disconnect (modest_runtime_get_conf (),
867                                              priv->conf_key_signal);
868                 priv->conf_key_signal = 0;
869         }
870
871         if (priv->cur_folder_store) {
872                 g_object_unref (priv->cur_folder_store);
873                 priv->cur_folder_store = NULL;
874         }
875
876         /* Clear hidding array created by cut operation */
877         _clear_hidding_filter (MODEST_FOLDER_VIEW (obj));
878
879         G_OBJECT_CLASS(parent_class)->finalize (obj);
880 }
881
882
883 static void
884 modest_folder_view_set_account_store (TnyAccountStoreView *self, TnyAccountStore *account_store)
885 {
886         ModestFolderViewPrivate *priv;
887         TnyDevice *device;
888
889         g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
890         g_return_if_fail (TNY_IS_ACCOUNT_STORE (account_store));
891
892         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
893         device = tny_account_store_get_device (account_store);
894
895         if (G_UNLIKELY (priv->account_store)) {
896
897                 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
898                                                    priv->account_inserted_signal))
899                         g_signal_handler_disconnect (G_OBJECT (priv->account_store),
900                                                      priv->account_inserted_signal);
901                 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
902                                                    priv->account_removed_signal))
903                         g_signal_handler_disconnect (G_OBJECT (priv->account_store),
904                                                      priv->account_removed_signal);
905                 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
906                                                    priv->account_changed_signal))
907                         g_signal_handler_disconnect (G_OBJECT (priv->account_store),
908                                                      priv->account_changed_signal);
909                 g_object_unref (G_OBJECT (priv->account_store));
910         }
911
912         priv->account_store = g_object_ref (G_OBJECT (account_store));
913
914         priv->account_removed_signal =
915                 g_signal_connect (G_OBJECT(account_store), "account_removed",
916                                   G_CALLBACK (on_account_removed), self);
917
918         priv->account_inserted_signal =
919                 g_signal_connect (G_OBJECT(account_store), "account_inserted",
920                                   G_CALLBACK (on_account_inserted), self);
921
922         priv->account_changed_signal =
923                 g_signal_connect (G_OBJECT(account_store), "account_changed",
924                                   G_CALLBACK (on_account_changed), self);
925
926         modest_folder_view_update_model (MODEST_FOLDER_VIEW (self), account_store);
927         priv->reselect = FALSE;
928         modest_folder_view_select_first_inbox_or_local (MODEST_FOLDER_VIEW (self));
929
930         g_object_unref (G_OBJECT (device));
931 }
932
933 static void
934 on_account_inserted (TnyAccountStore *account_store,
935                      TnyAccount *account,
936                      gpointer user_data)
937 {
938         ModestFolderViewPrivate *priv;
939         GtkTreeModel *sort_model, *filter_model;
940
941         /* Ignore transport account insertions, we're not showing them
942            in the folder view */
943         if (TNY_IS_TRANSPORT_ACCOUNT (account))
944                 return;
945
946         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (user_data);
947
948
949         /* If we're adding a new account, and there is no previous
950            one, we need to select the visible server account */
951         if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE &&
952             !priv->visible_account_id)
953                 modest_widget_memory_restore (modest_runtime_get_conf(),
954                                               G_OBJECT (user_data),
955                                               MODEST_CONF_FOLDER_VIEW_KEY);
956
957         if (!GTK_IS_TREE_VIEW(user_data)) {
958                 g_warning ("BUG: %s: not a valid tree view", __FUNCTION__);
959                 return;
960         }
961
962         /* Get the inner model */
963         /* check, is some rare cases, we did not get the right thing here,
964          * NB#84097 */
965         filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (user_data));
966         if (!GTK_IS_TREE_MODEL_FILTER(filter_model)) {
967                 g_warning ("BUG: %s: not a valid filter model", __FUNCTION__);
968                 return;
969         }
970
971         /* check, is some rare cases, we did not get the right thing here,
972          * NB#84097 */
973         sort_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
974         if (!GTK_IS_TREE_MODEL_SORT(sort_model)) {
975                 g_warning ("BUG: %s: not a valid sort model", __FUNCTION__);
976                 return;
977         }
978
979         /* Insert the account in the model */
980         tny_list_append (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
981                          G_OBJECT (account));
982
983         /* Refilter the model */
984         gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
985 }
986
987
988 static gboolean
989 same_account_selected (ModestFolderView *self,
990                        TnyAccount *account)
991 {
992         ModestFolderViewPrivate *priv;
993         gboolean same_account = FALSE;
994
995         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
996
997         if (priv->cur_folder_store) {
998                 TnyAccount *selected_folder_account = NULL;
999
1000                 if (TNY_IS_FOLDER (priv->cur_folder_store)) {
1001                         selected_folder_account =
1002                                 modest_tny_folder_get_account (TNY_FOLDER (priv->cur_folder_store));
1003                 } else {
1004                         selected_folder_account =
1005                                 TNY_ACCOUNT (g_object_ref (priv->cur_folder_store));
1006                 }
1007
1008                 if (selected_folder_account == account)
1009                         same_account = TRUE;
1010
1011                 g_object_unref (selected_folder_account);
1012         }
1013         return same_account;
1014 }
1015
1016 /**
1017  *
1018  * Selects the first inbox or the local account in an idle
1019  */
1020 static gboolean
1021 on_idle_select_first_inbox_or_local (gpointer user_data)
1022 {
1023         ModestFolderView *self = MODEST_FOLDER_VIEW (user_data);
1024
1025         gdk_threads_enter ();
1026         modest_folder_view_select_first_inbox_or_local (self);
1027         gdk_threads_leave ();
1028
1029         return FALSE;
1030 }
1031
1032 static void
1033 on_account_changed (TnyAccountStore *account_store,
1034                     TnyAccount *tny_account,
1035                     gpointer user_data)
1036 {
1037         ModestFolderView *self;
1038         ModestFolderViewPrivate *priv;
1039         GtkTreeModel *sort_model, *filter_model;
1040         GtkTreeSelection *sel;
1041         gboolean same_account;
1042
1043         /* Ignore transport account insertions, we're not showing them
1044            in the folder view */
1045         if (TNY_IS_TRANSPORT_ACCOUNT (tny_account))
1046                 return;
1047
1048         if (!MODEST_IS_FOLDER_VIEW(user_data)) {
1049                 g_warning ("BUG: %s: not a valid folder view", __FUNCTION__);
1050                 return;
1051         }
1052
1053         self = MODEST_FOLDER_VIEW (user_data);
1054         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (user_data);
1055
1056         /* Get the inner model */
1057         filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (user_data));
1058         if (!GTK_IS_TREE_MODEL_FILTER(filter_model)) {
1059                 g_warning ("BUG: %s: not a valid filter model", __FUNCTION__);
1060                 return;
1061         }
1062
1063         sort_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
1064         if (!GTK_IS_TREE_MODEL_SORT(sort_model)) {
1065                 g_warning ("BUG: %s: not a valid sort model", __FUNCTION__);
1066                 return;
1067         }
1068
1069         /* Invalidate the cur_folder_store only if the selected folder
1070            belongs to the account that is being removed */
1071         same_account = same_account_selected (self, tny_account);
1072         if (same_account) {
1073                 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1074                 gtk_tree_selection_unselect_all (sel);
1075         }
1076
1077         /* Remove the account from the model */
1078         tny_list_remove (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
1079                          G_OBJECT (tny_account));
1080
1081         /* Insert the account in the model */
1082         tny_list_append (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
1083                          G_OBJECT (tny_account));
1084
1085         /* Refilter the model */
1086         gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1087
1088         /* Select the first INBOX if the currently selected folder
1089            belongs to the account that is being deleted */
1090         if (same_account && !MODEST_IS_TNY_LOCAL_FOLDERS_ACCOUNT (tny_account))
1091                 g_idle_add (on_idle_select_first_inbox_or_local, self);
1092 }
1093
1094 static void
1095 on_account_removed (TnyAccountStore *account_store,
1096                     TnyAccount *account,
1097                     gpointer user_data)
1098 {
1099         ModestFolderView *self = NULL;
1100         ModestFolderViewPrivate *priv;
1101         GtkTreeModel *sort_model, *filter_model;
1102         GtkTreeSelection *sel = NULL;
1103         gboolean same_account = FALSE;
1104
1105         /* Ignore transport account removals, we're not showing them
1106            in the folder view */
1107         if (TNY_IS_TRANSPORT_ACCOUNT (account))
1108                 return;
1109
1110         if (!MODEST_IS_FOLDER_VIEW(user_data)) {
1111                 g_warning ("BUG: %s: not a valid folder view", __FUNCTION__);
1112                 return;
1113         }
1114
1115         self = MODEST_FOLDER_VIEW (user_data);
1116         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1117
1118         /* Invalidate the cur_folder_store only if the selected folder
1119            belongs to the account that is being removed */
1120         same_account = same_account_selected (self, account);
1121         if (same_account) {
1122                 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1123                 gtk_tree_selection_unselect_all (sel);
1124         }
1125
1126         /* Invalidate row to select only if the folder to select
1127            belongs to the account that is being removed*/
1128         if (priv->folder_to_select) {
1129                 TnyAccount *folder_to_select_account = NULL;
1130
1131                 folder_to_select_account = tny_folder_get_account (priv->folder_to_select);
1132                 if (folder_to_select_account == account) {
1133                         modest_folder_view_disable_next_folder_selection (self);
1134                         g_object_unref (priv->folder_to_select);
1135                         priv->folder_to_select = NULL;
1136                 }
1137                 g_object_unref (folder_to_select_account);
1138         }
1139
1140         /* Remove the account from the model */
1141         filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1142         if (!GTK_IS_TREE_MODEL_FILTER(filter_model)) {
1143                 g_warning ("BUG: %s: not a valid filter model", __FUNCTION__);
1144                 return;
1145         }
1146
1147         sort_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
1148         if (!GTK_IS_TREE_MODEL_SORT(sort_model)) {
1149                 g_warning ("BUG: %s: not a valid sort model", __FUNCTION__);
1150                 return;
1151         }
1152
1153         tny_list_remove (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
1154                          G_OBJECT (account));
1155
1156         /* If the removed account is the currently viewed one then
1157            clear the configuration value. The new visible account will be the default account */
1158         if (priv->visible_account_id &&
1159             !strcmp (priv->visible_account_id, tny_account_get_id (account))) {
1160
1161                 /* Clear the current visible account_id */
1162                 modest_folder_view_set_account_id_of_visible_server_account (self, NULL);
1163
1164                 /* Call the restore method, this will set the new visible account */
1165                 modest_widget_memory_restore (modest_runtime_get_conf(), G_OBJECT(self),
1166                                               MODEST_CONF_FOLDER_VIEW_KEY);
1167         }
1168
1169         /* Refilter the model */
1170         gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1171
1172         /* Select the first INBOX if the currently selected folder
1173            belongs to the account that is being deleted */
1174         if (same_account)
1175                 g_idle_add (on_idle_select_first_inbox_or_local, self);
1176 }
1177
1178 void
1179 modest_folder_view_set_title (ModestFolderView *self, const gchar *title)
1180 {
1181         GtkTreeViewColumn *col;
1182
1183         g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
1184
1185         col = gtk_tree_view_get_column (GTK_TREE_VIEW(self), 0);
1186         if (!col) {
1187                 g_printerr ("modest: failed get column for title\n");
1188                 return;
1189         }
1190
1191         gtk_tree_view_column_set_title (col, title);
1192         gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self),
1193                                            title != NULL);
1194 }
1195
1196 static gboolean
1197 modest_folder_view_on_map (ModestFolderView *self,
1198                            GdkEventExpose *event,
1199                            gpointer data)
1200 {
1201         ModestFolderViewPrivate *priv;
1202
1203         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1204
1205         /* This won't happen often */
1206         if (G_UNLIKELY (priv->reselect)) {
1207                 /* Select the first inbox or the local account if not found */
1208
1209                 /* TODO: this could cause a lock at startup, so we
1210                    comment it for the moment. We know that this will
1211                    be a bug, because the INBOX is not selected, but we
1212                    need to rewrite some parts of Modest to avoid the
1213                    deathlock situation */
1214                 /* TODO: check if this is still the case */
1215                 priv->reselect = FALSE;
1216                 modest_folder_view_select_first_inbox_or_local (self);
1217                 /* Notify the display name observers */
1218                 g_signal_emit (G_OBJECT(self),
1219                                signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
1220                                NULL);
1221         }
1222
1223         if (priv->reexpand) {
1224                 expand_root_items (self);
1225                 priv->reexpand = FALSE;
1226         }
1227
1228         return FALSE;
1229 }
1230
1231 GtkWidget*
1232 modest_folder_view_new (TnyFolderStoreQuery *query)
1233 {
1234         GObject *self;
1235         ModestFolderViewPrivate *priv;
1236         GtkTreeSelection *sel;
1237
1238         self = G_OBJECT (g_object_new (MODEST_TYPE_FOLDER_VIEW, NULL));
1239         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1240
1241         if (query)
1242                 priv->query = g_object_ref (query);
1243
1244         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1245         priv->changed_signal = g_signal_connect (sel, "changed",
1246                                                  G_CALLBACK (on_selection_changed), self);
1247
1248         g_signal_connect (self, "expose-event", G_CALLBACK (modest_folder_view_on_map), NULL);
1249
1250         return GTK_WIDGET(self);
1251 }
1252
1253 /* this feels dirty; any other way to expand all the root items? */
1254 static void
1255 expand_root_items (ModestFolderView *self)
1256 {
1257         GtkTreePath *path;
1258         GtkTreeModel *model;
1259         GtkTreeIter iter;
1260
1261         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1262         path = gtk_tree_path_new_first ();
1263
1264         /* all folders should have child items, so.. */
1265         do {
1266                 gtk_tree_view_expand_row (GTK_TREE_VIEW(self), path, FALSE);
1267                 gtk_tree_path_next (path);
1268         } while (gtk_tree_model_get_iter (model, &iter, path));
1269
1270         gtk_tree_path_free (path);
1271 }
1272
1273 /*
1274  * We use this function to implement the
1275  * MODEST_FOLDER_VIEW_STYLE_SHOW_ONE style. We only show the default
1276  * account in this case, and the local folders.
1277  */
1278 static gboolean
1279 filter_row (GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
1280 {
1281         ModestFolderViewPrivate *priv;
1282         gboolean retval = TRUE;
1283         TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1284         GObject *instance = NULL;
1285         const gchar *id = NULL;
1286         guint i;
1287         gboolean found = FALSE;
1288         gboolean cleared = FALSE;
1289
1290         g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (data), FALSE);
1291         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (data);
1292
1293         gtk_tree_model_get (model, iter,
1294                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
1295                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
1296                             -1);
1297
1298         /* Do not show if there is no instance, this could indeed
1299            happen when the model is being modified while it's being
1300            drawn. This could occur for example when moving folders
1301            using drag&drop */
1302         if (!instance)
1303                 return FALSE;
1304
1305         if (type == TNY_FOLDER_TYPE_ROOT) {
1306                 /* TNY_FOLDER_TYPE_ROOT means that the instance is an
1307                    account instead of a folder. */
1308                 if (TNY_IS_ACCOUNT (instance)) {
1309                         TnyAccount *acc = TNY_ACCOUNT (instance);
1310                         const gchar *account_id = tny_account_get_id (acc);
1311
1312                         /* If it isn't a special folder,
1313                          * don't show it unless it is the visible account: */
1314                         if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE &&
1315                             !modest_tny_account_is_virtual_local_folders (acc) &&
1316                             strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
1317
1318                                 /* Show only the visible account id */
1319                                 if (priv->visible_account_id) {
1320                                         if (strcmp (account_id, priv->visible_account_id))
1321                                                 retval = FALSE;
1322                                 } else {
1323                                         retval = FALSE;
1324                                 }
1325                         }
1326
1327                         /* Never show these to the user. They are merged into one folder
1328                          * in the local-folders account instead: */
1329                         if (retval && MODEST_IS_TNY_OUTBOX_ACCOUNT (acc))
1330                                 retval = FALSE;
1331                 }
1332         }
1333
1334         /* Check hiding (if necessary) */
1335         cleared = modest_email_clipboard_cleared (priv->clipboard);
1336         if ((retval) && (!cleared) && (TNY_IS_FOLDER (instance))) {
1337                 id = tny_folder_get_id (TNY_FOLDER(instance));
1338                 if (priv->hidding_ids != NULL)
1339                         for (i=0; i < priv->n_selected && !found; i++)
1340                                 if (priv->hidding_ids[i] != NULL && id != NULL)
1341                                         found = (!strcmp (priv->hidding_ids[i], id));
1342
1343                 retval = !found;
1344         }
1345
1346
1347         /* If this is a move to dialog, hide Sent, Outbox and Drafts
1348         folder as no message can be move there according to UI specs */
1349         if (!priv->show_non_move) {
1350                 switch (type) {
1351                         case TNY_FOLDER_TYPE_OUTBOX:
1352                         case TNY_FOLDER_TYPE_SENT:
1353                         case TNY_FOLDER_TYPE_DRAFTS:
1354                                 retval = FALSE;
1355                                 break;
1356                         case TNY_FOLDER_TYPE_UNKNOWN:
1357                         case TNY_FOLDER_TYPE_NORMAL:
1358                                 type = modest_tny_folder_guess_folder_type(TNY_FOLDER(instance));
1359                                 if (type == TNY_FOLDER_TYPE_INVALID)
1360                                         g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1361
1362                                 if (type == TNY_FOLDER_TYPE_OUTBOX ||
1363                                     type == TNY_FOLDER_TYPE_SENT
1364                                     || type == TNY_FOLDER_TYPE_DRAFTS)
1365                                         retval = FALSE;
1366                                 break;
1367                         default:
1368                                 break;
1369                 }
1370         }
1371
1372         /* Free */
1373         g_object_unref (instance);
1374
1375         return retval;
1376 }
1377
1378
1379 gboolean
1380 modest_folder_view_update_model (ModestFolderView *self,
1381                                  TnyAccountStore *account_store)
1382 {
1383         ModestFolderViewPrivate *priv;
1384         GtkTreeModel *model /* , *old_model */;
1385         GtkTreeModel *filter_model = NULL, *sortable = NULL;
1386
1387         g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW (self), FALSE);
1388         g_return_val_if_fail (account_store && MODEST_IS_TNY_ACCOUNT_STORE(account_store),
1389                               FALSE);
1390
1391         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1392
1393         /* Notify that there is no folder selected */
1394         g_signal_emit (G_OBJECT(self),
1395                        signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
1396                        NULL, FALSE);
1397         if (priv->cur_folder_store) {
1398                 g_object_unref (priv->cur_folder_store);
1399                 priv->cur_folder_store = NULL;
1400         }
1401
1402         /* FIXME: the local accounts are not shown when the query
1403            selects only the subscribed folders */
1404         model = tny_gtk_folder_store_tree_model_new (NULL);
1405
1406         /* Get the accounts: */
1407         tny_account_store_get_accounts (TNY_ACCOUNT_STORE(account_store),
1408                                         TNY_LIST (model),
1409                                         TNY_ACCOUNT_STORE_STORE_ACCOUNTS);
1410
1411         sortable = gtk_tree_model_sort_new_with_model (model);
1412         gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
1413                                               TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
1414                                               GTK_SORT_ASCENDING);
1415         gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
1416                                          TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
1417                                          cmp_rows, NULL, NULL);
1418
1419         /* Create filter model */
1420         filter_model = gtk_tree_model_filter_new (sortable, NULL);
1421         gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
1422                                                 filter_row,
1423                                                 self,
1424                                                 NULL);
1425
1426         /* Set new model */
1427         gtk_tree_view_set_model (GTK_TREE_VIEW(self), filter_model);
1428         g_signal_connect (G_OBJECT(filter_model), "row-inserted",
1429                           (GCallback) on_row_inserted_maybe_select_folder, self);
1430
1431         g_object_unref (model);
1432         g_object_unref (filter_model);
1433         g_object_unref (sortable);
1434
1435         /* Force a reselection of the INBOX next time the widget is shown */
1436         priv->reselect = TRUE;
1437
1438         return TRUE;
1439 }
1440
1441
1442 static void
1443 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
1444 {
1445         GtkTreeModel *model = NULL;
1446         TnyFolderStore *folder = NULL;
1447         GtkTreeIter iter;
1448         ModestFolderView *tree_view = NULL;
1449         ModestFolderViewPrivate *priv = NULL;
1450         gboolean selected = FALSE;
1451
1452         g_return_if_fail (sel);
1453         g_return_if_fail (user_data);
1454
1455         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
1456
1457         selected = gtk_tree_selection_get_selected (sel, &model, &iter);
1458
1459         tree_view = MODEST_FOLDER_VIEW (user_data);
1460
1461         if (selected) {
1462                 gtk_tree_model_get (model, &iter,
1463                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
1464                                     -1);
1465
1466                 /* If the folder is the same do not notify */
1467                 if (folder && priv->cur_folder_store == folder) {
1468                         g_object_unref (folder);
1469                         return;
1470                 }
1471         }
1472
1473         /* Current folder was unselected */
1474         if (priv->cur_folder_store) {
1475                 /* We must do this firstly because a libtinymail-camel
1476                    implementation detail. If we issue the signal
1477                    before doing the sync_async, then that signal could
1478                    cause (and it actually does it) a free of the
1479                    summary of the folder (because the main window will
1480                    clear the headers view */
1481                 if (TNY_IS_FOLDER(priv->cur_folder_store))
1482                         tny_folder_sync_async (TNY_FOLDER(priv->cur_folder_store),
1483                                                FALSE, NULL, NULL, NULL);
1484
1485                 g_signal_emit (G_OBJECT(tree_view), signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
1486                        priv->cur_folder_store, FALSE);
1487
1488                 g_object_unref (priv->cur_folder_store);
1489                 priv->cur_folder_store = NULL;
1490         }
1491
1492         /* New current references */
1493         priv->cur_folder_store = folder;
1494
1495         /* New folder has been selected. Do not notify if there is
1496            nothing new selected */
1497         if (selected) {
1498                 g_signal_emit (G_OBJECT(tree_view),
1499                                signals[FOLDER_SELECTION_CHANGED_SIGNAL],
1500                                0, priv->cur_folder_store, TRUE);
1501         }
1502 }
1503
1504 TnyFolderStore *
1505 modest_folder_view_get_selected (ModestFolderView *self)
1506 {
1507         ModestFolderViewPrivate *priv;
1508
1509         g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW(self), NULL);
1510
1511         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1512         if (priv->cur_folder_store)
1513                 g_object_ref (priv->cur_folder_store);
1514
1515         return priv->cur_folder_store;
1516 }
1517
1518 static gint
1519 get_cmp_rows_type_pos (GObject *folder)
1520 {
1521         /* Remote accounts -> Local account -> MMC account .*/
1522         /* 0, 1, 2 */
1523
1524         if (TNY_IS_ACCOUNT (folder) &&
1525                 modest_tny_account_is_virtual_local_folders (
1526                         TNY_ACCOUNT (folder))) {
1527                 return 1;
1528         } else if (TNY_IS_ACCOUNT (folder)) {
1529                 TnyAccount *account = TNY_ACCOUNT (folder);
1530                 const gchar *account_id = tny_account_get_id (account);
1531                 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
1532                         return 2;
1533                 else
1534                         return 0;
1535         }
1536         else {
1537                 printf ("DEBUG: %s: unexpected type.\n", __FUNCTION__);
1538                 return -1; /* Should never happen */
1539         }
1540 }
1541
1542 static gint
1543 get_cmp_subfolder_type_pos (TnyFolderType t)
1544 {
1545         /* Inbox, Outbox, Drafts, Sent, User */
1546         /* 0, 1, 2, 3, 4 */
1547
1548         switch (t) {
1549         case TNY_FOLDER_TYPE_INBOX:
1550                 return 0;
1551                 break;
1552         case TNY_FOLDER_TYPE_OUTBOX:
1553                 return 1;
1554                 break;
1555         case TNY_FOLDER_TYPE_DRAFTS:
1556                 return 2;
1557                 break;
1558         case TNY_FOLDER_TYPE_SENT:
1559                 return 3;
1560                 break;
1561         default:
1562                 return 4;
1563         }
1564 }
1565
1566 /*
1567  * This function orders the mail accounts according to these rules:
1568  * 1st - remote accounts
1569  * 2nd - local account
1570  * 3rd - MMC account
1571  */
1572 static gint
1573 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1574           gpointer user_data)
1575 {
1576         gint cmp = 0;
1577         gchar *name1 = NULL;
1578         gchar *name2 = NULL;
1579         TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1580         TnyFolderType type2 = TNY_FOLDER_TYPE_UNKNOWN;
1581         GObject *folder1 = NULL;
1582         GObject *folder2 = NULL;
1583
1584         gtk_tree_model_get (tree_model, iter1,
1585                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name1,
1586                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
1587                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder1,
1588                             -1);
1589         gtk_tree_model_get (tree_model, iter2,
1590                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name2,
1591                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type2,
1592                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder2,
1593                             -1);
1594
1595         /* Return if we get no folder. This could happen when folder
1596            operations are happening. The model is updated after the
1597            folder copy/move actually occurs, so there could be
1598            situations where the model to be drawn is not correct */
1599         if (!folder1 || !folder2)
1600                 goto finish;
1601
1602         if (type == TNY_FOLDER_TYPE_ROOT) {
1603                 /* Compare the types, so that
1604                  * Remote accounts -> Local account -> MMC account .*/
1605                 const gint pos1 = get_cmp_rows_type_pos (folder1);
1606                 const gint pos2 = get_cmp_rows_type_pos (folder2);
1607                 /* printf ("DEBUG: %s:\n  type1=%s, pos1=%d\n  type2=%s, pos2=%d\n",
1608                         __FUNCTION__, G_OBJECT_TYPE_NAME(folder1), pos1, G_OBJECT_TYPE_NAME(folder2), pos2); */
1609                 if (pos1 <  pos2)
1610                         cmp = -1;
1611                 else if (pos1 > pos2)
1612                         cmp = 1;
1613                 else {
1614                         /* Compare items of the same type: */
1615
1616                         TnyAccount *account1 = NULL;
1617                         if (TNY_IS_ACCOUNT (folder1))
1618                                 account1 = TNY_ACCOUNT (folder1);
1619
1620                         TnyAccount *account2 = NULL;
1621                         if (TNY_IS_ACCOUNT (folder2))
1622                                 account2 = TNY_ACCOUNT (folder2);
1623
1624                         const gchar *account_id = account1 ? tny_account_get_id (account1) : NULL;
1625                         const gchar *account_id2 = account2 ? tny_account_get_id (account2) : NULL;
1626
1627                         if (!account_id && !account_id2) {
1628                                 cmp = 0;
1629                         } else if (!account_id) {
1630                                 cmp = -1;
1631                         } else if (!account_id2) {
1632                                 cmp = +1;
1633                         } else if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
1634                                 cmp = +1;
1635                         } else {
1636                                 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
1637                         }
1638                 }
1639         } else {
1640                 gint cmp1 = 0, cmp2 = 0;
1641                 /* get the parent to know if it's a local folder */
1642
1643                 GtkTreeIter parent;
1644                 gboolean has_parent;
1645                 has_parent = gtk_tree_model_iter_parent (tree_model, &parent, iter1);
1646                 if (has_parent) {
1647                         GObject *parent_folder;
1648                         TnyFolderType parent_type = TNY_FOLDER_TYPE_UNKNOWN;
1649                         gtk_tree_model_get (tree_model, &parent,
1650                                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &parent_type,
1651                                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &parent_folder,
1652                                             -1);
1653                         if ((parent_type == TNY_FOLDER_TYPE_ROOT) &&
1654                             TNY_IS_ACCOUNT (parent_folder)) {
1655                                 if (modest_tny_account_is_virtual_local_folders (TNY_ACCOUNT (parent_folder))) {
1656                                         cmp1 = get_cmp_subfolder_type_pos (modest_tny_folder_get_local_or_mmc_folder_type
1657                                                                            (TNY_FOLDER (folder1)));
1658                                         cmp2 = get_cmp_subfolder_type_pos (modest_tny_folder_get_local_or_mmc_folder_type
1659                                                                            (TNY_FOLDER (folder2)));
1660                                 } else if (modest_tny_account_is_memory_card_account (TNY_ACCOUNT (parent_folder))) {
1661                                         if (modest_local_folder_info_get_type (tny_folder_get_name (TNY_FOLDER (folder1))) == TNY_FOLDER_TYPE_ARCHIVE) {
1662                                                 cmp1 = 0;
1663                                                 cmp2 = 1;
1664                                                 } else if (modest_local_folder_info_get_type (tny_folder_get_name (TNY_FOLDER (folder2))) == TNY_FOLDER_TYPE_ARCHIVE) {
1665                                                 cmp1 = 1;
1666                                                 cmp2 = 0;
1667                                         }
1668                                 }
1669                         }
1670                         g_object_unref (parent_folder);
1671                 }
1672
1673                 /* if they are not local folders */
1674                 if (cmp1 == cmp2) {
1675                         cmp1 = get_cmp_subfolder_type_pos (tny_folder_get_folder_type (TNY_FOLDER (folder1)));
1676                         cmp2 = get_cmp_subfolder_type_pos (tny_folder_get_folder_type (TNY_FOLDER (folder2)));
1677                 }
1678
1679                 if (cmp1 == cmp2)
1680                         cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
1681                 else
1682                         cmp = (cmp1 - cmp2);
1683         }
1684
1685 finish:
1686         if (folder1)
1687                 g_object_unref(G_OBJECT(folder1));
1688         if (folder2)
1689                 g_object_unref(G_OBJECT(folder2));
1690
1691         g_free (name1);
1692         g_free (name2);
1693
1694         return cmp;
1695 }
1696
1697 /*****************************************************************************/
1698 /*                        DRAG and DROP stuff                                */
1699 /*****************************************************************************/
1700 /*
1701  * This function fills the #GtkSelectionData with the row and the
1702  * model that has been dragged. It's called when this widget is a
1703  * source for dnd after the event drop happened
1704  */
1705 static void
1706 on_drag_data_get (GtkWidget *widget, GdkDragContext *context, GtkSelectionData *selection_data,
1707                   guint info, guint time, gpointer data)
1708 {
1709         GtkTreeSelection *selection;
1710         GtkTreeModel *model;
1711         GtkTreeIter iter;
1712         GtkTreePath *source_row;
1713
1714         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1715         if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
1716
1717                 source_row = gtk_tree_model_get_path (model, &iter);
1718                 gtk_tree_set_row_drag_data (selection_data,
1719                                             model,
1720                                             source_row);
1721
1722                 gtk_tree_path_free (source_row);
1723         }
1724 }
1725
1726 typedef struct _DndHelper {
1727         ModestFolderView *folder_view;
1728         gboolean delete_source;
1729         GtkTreePath *source_row;
1730 } DndHelper;
1731
1732 static void
1733 dnd_helper_destroyer (DndHelper *helper)
1734 {
1735         /* Free the helper */
1736         gtk_tree_path_free (helper->source_row);
1737         g_slice_free (DndHelper, helper);
1738 }
1739
1740 static void
1741 xfer_folder_cb (ModestMailOperation *mail_op,
1742                 TnyFolder *new_folder,
1743                 gpointer user_data)
1744 {
1745         if (new_folder) {
1746                 /* Select the folder */
1747                 modest_folder_view_select_folder (MODEST_FOLDER_VIEW (user_data),
1748                                                   new_folder, FALSE);
1749         }
1750 }
1751
1752
1753 /* get the folder for the row the treepath refers to. */
1754 /* folder must be unref'd */
1755 static TnyFolderStore *
1756 tree_path_to_folder (GtkTreeModel *model, GtkTreePath *path)
1757 {
1758         GtkTreeIter iter;
1759         TnyFolderStore *folder = NULL;
1760
1761         if (gtk_tree_model_get_iter (model,&iter, path))
1762                 gtk_tree_model_get (model, &iter,
1763                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
1764                                     -1);
1765         return folder;
1766 }
1767
1768
1769 /*
1770  * This function is used by drag_data_received_cb to manage drag and
1771  * drop of a header, i.e, and drag from the header view to the folder
1772  * view.
1773  */
1774 static void
1775 drag_and_drop_from_header_view (GtkTreeModel *source_model,
1776                                 GtkTreeModel *dest_model,
1777                                 GtkTreePath  *dest_row,
1778                                 GtkSelectionData *selection_data)
1779 {
1780         TnyList *headers = NULL;
1781         TnyFolder *folder = NULL, *src_folder = NULL;
1782         TnyFolderType folder_type;
1783         GtkTreeIter source_iter, dest_iter;
1784         ModestWindowMgr *mgr = NULL;
1785         ModestWindow *main_win = NULL;
1786         gchar **uris, **tmp;
1787
1788         /* Build the list of headers */
1789         mgr = modest_runtime_get_window_mgr ();
1790         headers = tny_simple_list_new ();
1791         uris = modest_dnd_selection_data_get_paths (selection_data);
1792         tmp = uris;
1793
1794         while (*tmp != NULL) {
1795                 TnyHeader *header;
1796                 GtkTreePath *path;
1797                 gboolean first = TRUE;
1798
1799                 /* Get header */
1800                 path = gtk_tree_path_new_from_string (*tmp);
1801                 gtk_tree_model_get_iter (source_model, &source_iter, path);
1802                 gtk_tree_model_get (source_model, &source_iter,
1803                                     TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1804                                     &header, -1);
1805
1806                 /* Do not enable d&d of headers already opened */
1807                 if (!modest_window_mgr_find_registered_header(mgr, header, NULL))
1808                         tny_list_append (headers, G_OBJECT (header));
1809
1810                 if (G_UNLIKELY (first)) {
1811                         src_folder = tny_header_get_folder (header);
1812                         first = FALSE;
1813                 }
1814
1815                 /* Free and go on */
1816                 gtk_tree_path_free (path);
1817                 g_object_unref (header);
1818                 tmp++;
1819         }
1820         g_strfreev (uris);
1821
1822         /* Get the target folder */
1823         gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
1824         gtk_tree_model_get (dest_model, &dest_iter,
1825                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
1826                             &folder, -1);
1827
1828         if (!folder || !TNY_IS_FOLDER(folder)) {
1829 /*              g_warning ("%s: not a valid target folder (%p)", __FUNCTION__, folder); */
1830                 goto cleanup;
1831         }
1832
1833         folder_type = modest_tny_folder_guess_folder_type (folder);
1834         if (folder_type == TNY_FOLDER_TYPE_INVALID) {
1835 /*              g_warning ("%s: invalid target folder", __FUNCTION__); */
1836                 goto cleanup;  /* cannot move messages there */
1837         }
1838
1839         if (modest_tny_folder_get_rules((TNY_FOLDER(folder))) & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE) {
1840 /*              g_warning ("folder not writable"); */
1841                 goto cleanup; /* verboten! */
1842         }
1843
1844         /* Ask for confirmation to move */
1845         main_win = modest_window_mgr_get_main_window (mgr, FALSE); /* don't create */
1846         if (!main_win) {
1847                 g_warning ("%s: BUG: no main window found", __FUNCTION__);
1848                 goto cleanup;
1849         }
1850
1851         /* Transfer messages */
1852         modest_ui_actions_transfer_messages_helper (GTK_WINDOW (main_win), src_folder,
1853                                                     headers, folder);
1854
1855         /* Frees */
1856 cleanup:
1857         if (G_IS_OBJECT (src_folder))
1858                 g_object_unref (src_folder);
1859         if (G_IS_OBJECT(folder))
1860                 g_object_unref (G_OBJECT (folder));
1861         if (G_IS_OBJECT(headers))
1862                 g_object_unref (headers);
1863 }
1864
1865 typedef struct {
1866         TnyFolderStore *src_folder;
1867         TnyFolderStore *dst_folder;
1868         ModestFolderView *folder_view;
1869         DndHelper *helper;
1870 } DndFolderInfo;
1871
1872 static void
1873 dnd_folder_info_destroyer (DndFolderInfo *info)
1874 {
1875         if (info->src_folder)
1876                 g_object_unref (info->src_folder);
1877         if (info->dst_folder)
1878                 g_object_unref (info->dst_folder);
1879         g_slice_free (DndFolderInfo, info);
1880 }
1881
1882 static void
1883 dnd_on_connection_failed_destroyer (DndFolderInfo *info,
1884                                     GtkWindow *parent_window,
1885                                     TnyAccount *account)
1886 {
1887         /* Show error */
1888         modest_ui_actions_on_account_connection_error (parent_window, account);
1889
1890         /* Free the helper & info */
1891         dnd_helper_destroyer (info->helper);
1892         dnd_folder_info_destroyer (info);
1893 }
1894
1895 static void
1896 drag_and_drop_from_folder_view_src_folder_performer (gboolean canceled,
1897                                                      GError *err,
1898                                                      GtkWindow *parent_window,
1899                                                      TnyAccount *account,
1900                                                      gpointer user_data)
1901 {
1902         DndFolderInfo *info = NULL;
1903         ModestMailOperation *mail_op;
1904
1905         info = (DndFolderInfo *) user_data;
1906
1907         if (err || canceled) {
1908                 dnd_on_connection_failed_destroyer (info, parent_window, account);
1909                 return;
1910         }
1911
1912         /* Do the mail operation */
1913         mail_op = modest_mail_operation_new_with_error_handling ((GObject *) parent_window,
1914                                                                  modest_ui_actions_move_folder_error_handler,
1915                                                                  info->src_folder, NULL);
1916
1917         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1918                                          mail_op);
1919
1920         /* Transfer the folder */
1921         modest_mail_operation_xfer_folder (mail_op,
1922                                            TNY_FOLDER (info->src_folder),
1923                                            info->dst_folder,
1924                                            info->helper->delete_source,
1925                                            xfer_folder_cb,
1926                                            info->helper->folder_view);
1927
1928         /* Frees */
1929         g_object_unref (G_OBJECT (mail_op));
1930         dnd_helper_destroyer (info->helper);
1931         dnd_folder_info_destroyer (info);
1932 }
1933
1934
1935 static void
1936 drag_and_drop_from_folder_view_dst_folder_performer (gboolean canceled,
1937                                                      GError *err,
1938                                                      GtkWindow *parent_window,
1939                                                      TnyAccount *account,
1940                                                      gpointer user_data)
1941 {
1942         DndFolderInfo *info = NULL;
1943
1944         info = (DndFolderInfo *) user_data;
1945
1946         if (err || canceled) {
1947                 dnd_on_connection_failed_destroyer (info, parent_window, account);
1948                 return;
1949         }
1950
1951         /* Connect to source folder and perform the copy/move */
1952         modest_platform_connect_if_remote_and_perform (NULL, TRUE,
1953                                                        info->src_folder,
1954                                                        drag_and_drop_from_folder_view_src_folder_performer,
1955                                                        info);
1956 }
1957
1958 /*
1959  * This function is used by drag_data_received_cb to manage drag and
1960  * drop of a folder, i.e, and drag from the folder view to the same
1961  * folder view.
1962  */
1963 static void
1964 drag_and_drop_from_folder_view (GtkTreeModel     *source_model,
1965                                 GtkTreeModel     *dest_model,
1966                                 GtkTreePath      *dest_row,
1967                                 GtkSelectionData *selection_data,
1968                                 DndHelper        *helper)
1969 {
1970         GtkTreeIter dest_iter, iter;
1971         TnyFolderStore *dest_folder = NULL;
1972         TnyFolderStore *folder = NULL;
1973         gboolean forbidden = FALSE;
1974         ModestWindow *win;
1975         DndFolderInfo *info = NULL;
1976
1977         win = modest_window_mgr_get_main_window (modest_runtime_get_window_mgr(), FALSE); /* don't create */
1978         if (!win) {
1979                 g_warning ("%s: BUG: no main window", __FUNCTION__);
1980                 dnd_helper_destroyer (helper);
1981                 return;
1982         }
1983
1984         if (!forbidden) {
1985                 /* check the folder rules for the destination */
1986                 folder = tree_path_to_folder (dest_model, dest_row);
1987                 if (TNY_IS_FOLDER(folder)) {
1988                         ModestTnyFolderRules rules =
1989                                 modest_tny_folder_get_rules (TNY_FOLDER (folder));
1990                         forbidden = rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE;
1991                 } else if (TNY_IS_FOLDER_STORE(folder)) {
1992                         /* enable local root as destination for folders */
1993                         if (!MODEST_IS_TNY_LOCAL_FOLDERS_ACCOUNT (folder) &&
1994                             !modest_tny_account_is_memory_card_account (TNY_ACCOUNT (folder)))
1995                                 forbidden = TRUE;
1996                 }
1997                 g_object_unref (folder);
1998         }
1999         if (!forbidden) {
2000                 /* check the folder rules for the source */
2001                 folder = tree_path_to_folder (source_model, helper->source_row);
2002                 if (TNY_IS_FOLDER(folder)) {
2003                         ModestTnyFolderRules rules =
2004                                 modest_tny_folder_get_rules (TNY_FOLDER (folder));
2005                         forbidden = rules & MODEST_FOLDER_RULES_FOLDER_NON_MOVEABLE;
2006                 } else
2007                         forbidden = TRUE;
2008                 g_object_unref (folder);
2009         }
2010
2011
2012         /* Check if the drag is possible */
2013         if (forbidden || !gtk_tree_path_compare (helper->source_row, dest_row)) {
2014                 dnd_helper_destroyer (helper);
2015                 return;
2016         }
2017
2018         /* Get data */
2019         gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
2020         gtk_tree_model_get (dest_model, &dest_iter,
2021                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
2022                             &dest_folder, -1);
2023         gtk_tree_model_get_iter (source_model, &iter, helper->source_row);
2024         gtk_tree_model_get (source_model, &iter,
2025                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
2026                             &folder, -1);
2027
2028         /* Create the info for the performer */
2029         info = g_slice_new0 (DndFolderInfo);
2030         info->src_folder = g_object_ref (folder);
2031         info->dst_folder = g_object_ref (dest_folder);
2032         info->helper = helper;
2033
2034         /* Connect to the destination folder and perform the copy/move */
2035         modest_platform_connect_if_remote_and_perform (GTK_WINDOW (win), TRUE,
2036                                                        dest_folder,
2037                                                        drag_and_drop_from_folder_view_dst_folder_performer,
2038                                                        info);
2039
2040         /* Frees */
2041         g_object_unref (dest_folder);
2042         g_object_unref (folder);
2043 }
2044
2045 /*
2046  * This function receives the data set by the "drag-data-get" signal
2047  * handler. This information comes within the #GtkSelectionData. This
2048  * function will manage both the drags of folders of the treeview and
2049  * drags of headers of the header view widget.
2050  */
2051 static void
2052 on_drag_data_received (GtkWidget *widget,
2053                        GdkDragContext *context,
2054                        gint x,
2055                        gint y,
2056                        GtkSelectionData *selection_data,
2057                        guint target_type,
2058                        guint time,
2059                        gpointer data)
2060 {
2061         GtkWidget *source_widget;
2062         GtkTreeModel *dest_model, *source_model;
2063         GtkTreePath *source_row, *dest_row;
2064         GtkTreeViewDropPosition pos;
2065         gboolean delete_source = FALSE;
2066         gboolean success = FALSE;
2067
2068         /* Do not allow further process */
2069         g_signal_stop_emission_by_name (widget, "drag-data-received");
2070         source_widget = gtk_drag_get_source_widget (context);
2071
2072         /* Get the action */
2073         if (context->action == GDK_ACTION_MOVE) {
2074                 delete_source = TRUE;
2075
2076                 /* Notify that there is no folder selected. We need to
2077                    do this in order to update the headers view (and
2078                    its monitors, because when moving, the old folder
2079                    won't longer exist. We can not wait for the end of
2080                    the operation, because the operation won't start if
2081                    the folder is in use */
2082                 if (source_widget == widget) {
2083                         GtkTreeSelection *sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
2084                         gtk_tree_selection_unselect_all (sel);
2085                 }
2086         }
2087
2088         /* Check if the get_data failed */
2089         if (selection_data == NULL || selection_data->length < 0)
2090                 goto end;
2091
2092         /* Select the destination model */
2093         dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
2094
2095         /* Get the path to the destination row. Can not call
2096            gtk_tree_view_get_drag_dest_row() because the source row
2097            is not selected anymore */
2098         gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget), x, y,
2099                                            &dest_row, &pos);
2100
2101         /* Only allow drops IN other rows */
2102         if (!dest_row ||
2103             pos == GTK_TREE_VIEW_DROP_BEFORE ||
2104             pos == GTK_TREE_VIEW_DROP_AFTER)
2105                 goto end;
2106
2107         success = TRUE;
2108         /* Drags from the header view */
2109         if (source_widget != widget) {
2110                 source_model = gtk_tree_view_get_model (GTK_TREE_VIEW (source_widget));
2111
2112                 drag_and_drop_from_header_view (source_model,
2113                                                 dest_model,
2114                                                 dest_row,
2115                                                 selection_data);
2116         } else {
2117                 DndHelper *helper = NULL;
2118
2119                 /* Get the source model and row */
2120                 gtk_tree_get_row_drag_data (selection_data,
2121                                             &source_model,
2122                                             &source_row);
2123
2124                 /* Create the helper */
2125                 helper = g_slice_new0 (DndHelper);
2126                 helper->delete_source = delete_source;
2127                 helper->source_row = gtk_tree_path_copy (source_row);
2128                 helper->folder_view = MODEST_FOLDER_VIEW (widget);
2129
2130                 drag_and_drop_from_folder_view (source_model,
2131                                                 dest_model,
2132                                                 dest_row,
2133                                                 selection_data,
2134                                                 helper);
2135
2136                 gtk_tree_path_free (source_row);
2137         }
2138
2139         /* Frees */
2140         gtk_tree_path_free (dest_row);
2141
2142  end:
2143         /* Finish the drag and drop */
2144         gtk_drag_finish (context, success, FALSE, time);
2145 }
2146
2147 /*
2148  * We define a "drag-drop" signal handler because we do not want to
2149  * use the default one, because the default one always calls
2150  * gtk_drag_finish and we prefer to do it in the "drag-data-received"
2151  * signal handler, because there we have all the information available
2152  * to know if the dnd was a success or not.
2153  */
2154 static gboolean
2155 drag_drop_cb (GtkWidget      *widget,
2156               GdkDragContext *context,
2157               gint            x,
2158               gint            y,
2159               guint           time,
2160               gpointer        user_data)
2161 {
2162         gpointer target;
2163
2164         if (!context->targets)
2165                 return FALSE;
2166
2167         /* Check if we're dragging a folder row */
2168         target = gtk_drag_dest_find_target (widget, context, NULL);
2169
2170         /* Request the data from the source. */
2171         gtk_drag_get_data(widget, context, target, time);
2172
2173     return TRUE;
2174 }
2175
2176 /*
2177  * This function expands a node of a tree view if it's not expanded
2178  * yet. Not sure why it needs the threads stuff, but gtk+`example code
2179  * does that, so that's why they're here.
2180  */
2181 static gint
2182 expand_row_timeout (gpointer data)
2183 {
2184         GtkTreeView *tree_view = data;
2185         GtkTreePath *dest_path = NULL;
2186         GtkTreeViewDropPosition pos;
2187         gboolean result = FALSE;
2188
2189         gdk_threads_enter ();
2190
2191         gtk_tree_view_get_drag_dest_row (tree_view,
2192                                          &dest_path,
2193                                          &pos);
2194
2195         if (dest_path &&
2196             (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER ||
2197              pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)) {
2198                 gtk_tree_view_expand_row (tree_view, dest_path, FALSE);
2199                 gtk_tree_path_free (dest_path);
2200         }
2201         else {
2202                 if (dest_path)
2203                         gtk_tree_path_free (dest_path);
2204
2205                 result = TRUE;
2206         }
2207
2208         gdk_threads_leave ();
2209
2210         return result;
2211 }
2212
2213 /*
2214  * This function is called whenever the pointer is moved over a widget
2215  * while dragging some data. It installs a timeout that will expand a
2216  * node of the treeview if not expanded yet. This function also calls
2217  * gdk_drag_status in order to set the suggested action that will be
2218  * used by the "drag-data-received" signal handler to know if we
2219  * should do a move or just a copy of the data.
2220  */
2221 static gboolean
2222 on_drag_motion (GtkWidget      *widget,
2223                 GdkDragContext *context,
2224                 gint            x,
2225                 gint            y,
2226                 guint           time,
2227                 gpointer        user_data)
2228 {
2229         GtkTreeViewDropPosition pos;
2230         GtkTreePath *dest_row;
2231         GtkTreeModel *dest_model;
2232         ModestFolderViewPrivate *priv;
2233         GdkDragAction suggested_action;
2234         gboolean valid_location = FALSE;
2235         TnyFolderStore *folder = NULL;
2236
2237         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
2238
2239         if (priv->timer_expander != 0) {
2240                 g_source_remove (priv->timer_expander);
2241                 priv->timer_expander = 0;
2242         }
2243
2244         gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
2245                                            x, y,
2246                                            &dest_row,
2247                                            &pos);
2248
2249         /* Do not allow drops between folders */
2250         if (!dest_row ||
2251             pos == GTK_TREE_VIEW_DROP_BEFORE ||
2252             pos == GTK_TREE_VIEW_DROP_AFTER) {
2253                 gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW (widget), NULL, 0);
2254                 gdk_drag_status(context, 0, time);
2255                 valid_location = FALSE;
2256                 goto out;
2257         } else {
2258                 valid_location = TRUE;
2259         }
2260
2261         /* Check that the destination folder is writable */
2262         dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
2263         folder = tree_path_to_folder (dest_model, dest_row);
2264         if (folder && TNY_IS_FOLDER (folder)) {
2265                 ModestTnyFolderRules rules = modest_tny_folder_get_rules(TNY_FOLDER (folder));
2266
2267                 if (rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE) {
2268                         valid_location = FALSE;
2269                         goto out;
2270                 }
2271         }
2272
2273         /* Expand the selected row after 1/2 second */
2274         if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), dest_row)) {
2275                 priv->timer_expander = g_timeout_add (500, expand_row_timeout, widget);
2276         }
2277         gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), dest_row, pos);
2278
2279         /* Select the desired action. By default we pick MOVE */
2280         suggested_action = GDK_ACTION_MOVE;
2281
2282         if (context->actions == GDK_ACTION_COPY)
2283             gdk_drag_status(context, GDK_ACTION_COPY, time);
2284         else if (context->actions == GDK_ACTION_MOVE)
2285             gdk_drag_status(context, GDK_ACTION_MOVE, time);
2286         else if (context->actions & suggested_action)
2287             gdk_drag_status(context, suggested_action, time);
2288         else
2289             gdk_drag_status(context, GDK_ACTION_DEFAULT, time);
2290
2291  out:
2292         if (folder)
2293                 g_object_unref (folder);
2294         if (dest_row) {
2295                 gtk_tree_path_free (dest_row);
2296         }
2297         g_signal_stop_emission_by_name (widget, "drag-motion");
2298
2299         return valid_location;
2300 }
2301
2302 /*
2303  * This function sets the treeview as a source and a target for dnd
2304  * events. It also connects all the requirede signals.
2305  */
2306 static void
2307 setup_drag_and_drop (GtkTreeView *self)
2308 {
2309         /* Set up the folder view as a dnd destination. Set only the
2310            highlight flag, otherwise gtk will have a different
2311            behaviour */
2312         gtk_drag_dest_set (GTK_WIDGET (self),
2313                            GTK_DEST_DEFAULT_HIGHLIGHT,
2314                            folder_view_drag_types,
2315                            G_N_ELEMENTS (folder_view_drag_types),
2316                            GDK_ACTION_MOVE | GDK_ACTION_COPY);
2317
2318         g_signal_connect (G_OBJECT (self),
2319                           "drag_data_received",
2320                           G_CALLBACK (on_drag_data_received),
2321                           NULL);
2322
2323
2324         /* Set up the treeview as a dnd source */
2325         gtk_drag_source_set (GTK_WIDGET (self),
2326                              GDK_BUTTON1_MASK,
2327                              folder_view_drag_types,
2328                              G_N_ELEMENTS (folder_view_drag_types),
2329                              GDK_ACTION_MOVE | GDK_ACTION_COPY);
2330
2331         g_signal_connect (G_OBJECT (self),
2332                           "drag_motion",
2333                           G_CALLBACK (on_drag_motion),
2334                           NULL);
2335
2336         g_signal_connect (G_OBJECT (self),
2337                           "drag_data_get",
2338                           G_CALLBACK (on_drag_data_get),
2339                           NULL);
2340
2341         g_signal_connect (G_OBJECT (self),
2342                           "drag_drop",
2343                           G_CALLBACK (drag_drop_cb),
2344                           NULL);
2345 }
2346
2347 /*
2348  * This function manages the navigation through the folders using the
2349  * keyboard or the hardware keys in the device
2350  */
2351 static gboolean
2352 on_key_pressed (GtkWidget *self,
2353                 GdkEventKey *event,
2354                 gpointer user_data)
2355 {
2356         GtkTreeSelection *selection;
2357         GtkTreeIter iter;
2358         GtkTreeModel *model;
2359         gboolean retval = FALSE;
2360
2361         /* Up and Down are automatically managed by the treeview */
2362         if (event->keyval == GDK_Return) {
2363                 /* Expand/Collapse the selected row */
2364                 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2365                 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
2366                         GtkTreePath *path;
2367
2368                         path = gtk_tree_model_get_path (model, &iter);
2369
2370                         if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (self), path))
2371                                 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
2372                         else
2373                                 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
2374                         gtk_tree_path_free (path);
2375                 }
2376                 /* No further processing */
2377                 retval = TRUE;
2378         }
2379
2380         return retval;
2381 }
2382
2383 /*
2384  * We listen to the changes in the local folder account name key,
2385  * because we want to show the right name in the view. The local
2386  * folder account name corresponds to the device name in the Maemo
2387  * version. We do this because we do not want to query gconf on each
2388  * tree view refresh. It's better to cache it and change whenever
2389  * necessary.
2390  */
2391 static void
2392 on_configuration_key_changed (ModestConf* conf,
2393                               const gchar *key,
2394                               ModestConfEvent event,
2395                               ModestConfNotificationId id,
2396                               ModestFolderView *self)
2397 {
2398         ModestFolderViewPrivate *priv;
2399
2400
2401         g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
2402         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2403
2404         if (!strcmp (key, MODEST_CONF_DEVICE_NAME)) {
2405                 g_free (priv->local_account_name);
2406
2407                 if (event == MODEST_CONF_EVENT_KEY_UNSET)
2408                         priv->local_account_name = g_strdup (MODEST_LOCAL_FOLDERS_DEFAULT_DISPLAY_NAME);
2409                 else
2410                         priv->local_account_name = modest_conf_get_string (modest_runtime_get_conf(),
2411                                                                            MODEST_CONF_DEVICE_NAME, NULL);
2412
2413                 /* Force a redraw */
2414 #if GTK_CHECK_VERSION(2, 8, 0)
2415                 GtkTreeViewColumn * tree_column;
2416
2417                 tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self),
2418                                                         TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN);
2419                 gtk_tree_view_column_queue_resize (tree_column);
2420 #else
2421                 gtk_widget_queue_draw (GTK_WIDGET (self));
2422 #endif
2423         }
2424 }
2425
2426 void
2427 modest_folder_view_set_style (ModestFolderView *self,
2428                               ModestFolderViewStyle style)
2429 {
2430         ModestFolderViewPrivate *priv;
2431
2432         g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2433         g_return_if_fail (style == MODEST_FOLDER_VIEW_STYLE_SHOW_ALL ||
2434                           style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE);
2435
2436         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2437
2438
2439         priv->style = style;
2440 }
2441
2442 void
2443 modest_folder_view_set_account_id_of_visible_server_account (ModestFolderView *self,
2444                                                              const gchar *account_id)
2445 {
2446         ModestFolderViewPrivate *priv;
2447         GtkTreeModel *model;
2448
2449         g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2450
2451         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2452
2453         /* This will be used by the filter_row callback,
2454          * to decided which rows to show: */
2455         if (priv->visible_account_id) {
2456                 g_free (priv->visible_account_id);
2457                 priv->visible_account_id = NULL;
2458         }
2459         if (account_id)
2460                 priv->visible_account_id = g_strdup (account_id);
2461
2462         /* Refilter */
2463         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2464         if (GTK_IS_TREE_MODEL_FILTER (model))
2465                 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2466
2467         /* Save settings to gconf */
2468         modest_widget_memory_save (modest_runtime_get_conf (), G_OBJECT(self),
2469                                    MODEST_CONF_FOLDER_VIEW_KEY);
2470 }
2471
2472 const gchar *
2473 modest_folder_view_get_account_id_of_visible_server_account (ModestFolderView *self)
2474 {
2475         ModestFolderViewPrivate *priv;
2476
2477         g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW(self), NULL);
2478
2479         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2480
2481         return (const gchar *) priv->visible_account_id;
2482 }
2483
2484 static gboolean
2485 find_inbox_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *inbox_iter)
2486 {
2487         do {
2488                 GtkTreeIter child;
2489                 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2490
2491                 gtk_tree_model_get (model, iter,
2492                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN,
2493                                     &type, -1);
2494
2495                 gboolean result = FALSE;
2496                 if (type == TNY_FOLDER_TYPE_INBOX) {
2497                         result = TRUE;
2498                 }
2499                 if (result) {
2500                         *inbox_iter = *iter;
2501                         return TRUE;
2502                 }
2503
2504                 if (gtk_tree_model_iter_children (model, &child, iter)) {
2505                         if (find_inbox_iter (model, &child, inbox_iter))
2506                                 return TRUE;
2507                 }
2508
2509         } while (gtk_tree_model_iter_next (model, iter));
2510
2511         return FALSE;
2512 }
2513
2514
2515
2516
2517 void
2518 modest_folder_view_select_first_inbox_or_local (ModestFolderView *self)
2519 {
2520         GtkTreeModel *model;
2521         GtkTreeIter iter, inbox_iter;
2522         GtkTreeSelection *sel;
2523         GtkTreePath *path = NULL;
2524
2525         g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2526
2527         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2528         if (!model)
2529                 return;
2530
2531         expand_root_items (self);
2532         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2533
2534         if (!gtk_tree_model_get_iter_first (model, &iter)) {
2535                 g_warning ("%s: model is empty", __FUNCTION__);
2536                 return;
2537         }
2538
2539         if (find_inbox_iter (model, &iter, &inbox_iter))
2540                 path = gtk_tree_model_get_path (model, &inbox_iter);
2541         else
2542                 path = gtk_tree_path_new_first ();
2543
2544         /* Select the row and free */
2545         gtk_tree_view_set_cursor (GTK_TREE_VIEW (self), path, NULL, FALSE);
2546         gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (self), path, NULL, FALSE, 0.0, 0.0);
2547         gtk_tree_path_free (path);
2548
2549         /* set focus */
2550         gtk_widget_grab_focus (GTK_WIDGET(self));
2551 }
2552
2553
2554 /* recursive */
2555 static gboolean
2556 find_folder_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *folder_iter,
2557                   TnyFolder* folder)
2558 {
2559         do {
2560                 GtkTreeIter child;
2561                 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2562                 TnyFolder* a_folder;
2563                 gchar *name = NULL;
2564
2565                 gtk_tree_model_get (model, iter,
2566                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &a_folder,
2567                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name,
2568                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
2569                                     -1);
2570                 g_free (name);
2571
2572                 if (folder == a_folder) {
2573                         g_object_unref (a_folder);
2574                         *folder_iter = *iter;
2575                         return TRUE;
2576                 }
2577                 g_object_unref (a_folder);
2578
2579                 if (gtk_tree_model_iter_children (model, &child, iter)) {
2580                         if (find_folder_iter (model, &child, folder_iter, folder))
2581                                 return TRUE;
2582                 }
2583
2584         } while (gtk_tree_model_iter_next (model, iter));
2585
2586         return FALSE;
2587 }
2588
2589
2590 static void
2591 on_row_inserted_maybe_select_folder (GtkTreeModel *tree_model,
2592                                      GtkTreePath *path,
2593                                      GtkTreeIter *iter,
2594                                      ModestFolderView *self)
2595 {
2596         ModestFolderViewPrivate *priv = NULL;
2597         GtkTreeSelection *sel;
2598         TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2599         GObject *instance = NULL;
2600
2601         if (!MODEST_IS_FOLDER_VIEW(self))
2602                 return;
2603
2604         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2605
2606         priv->reexpand = TRUE;
2607
2608         gtk_tree_model_get (tree_model, iter,
2609                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
2610                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
2611                             -1);
2612         if (type == TNY_FOLDER_TYPE_INBOX && priv->folder_to_select == NULL) {
2613                 priv->folder_to_select = g_object_ref (instance);
2614         }
2615         g_object_unref (instance);
2616
2617         if (priv->folder_to_select) {
2618
2619                 if (!modest_folder_view_select_folder (self, priv->folder_to_select,
2620                                                        FALSE)) {
2621                         GtkTreePath *path;
2622                         path = gtk_tree_model_get_path (tree_model, iter);
2623                         gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
2624
2625                         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2626
2627                         gtk_tree_selection_select_iter (sel, iter);
2628                         gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
2629
2630                         gtk_tree_path_free (path);
2631                 }
2632
2633                 /* Disable next */
2634                 modest_folder_view_disable_next_folder_selection (self);
2635
2636                 /* Refilter the model */
2637                 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (tree_model));
2638         }
2639 }
2640
2641
2642 void
2643 modest_folder_view_disable_next_folder_selection (ModestFolderView *self)
2644 {
2645         ModestFolderViewPrivate *priv;
2646
2647         g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2648
2649         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2650
2651         if (priv->folder_to_select)
2652                 g_object_unref(priv->folder_to_select);
2653
2654         priv->folder_to_select = NULL;
2655 }
2656
2657 gboolean
2658 modest_folder_view_select_folder (ModestFolderView *self, TnyFolder *folder,
2659                                   gboolean after_change)
2660 {
2661         GtkTreeModel *model;
2662         GtkTreeIter iter, folder_iter;
2663         GtkTreeSelection *sel;
2664         ModestFolderViewPrivate *priv = NULL;
2665
2666         g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW (self), FALSE);
2667         g_return_val_if_fail (folder && TNY_IS_FOLDER (folder), FALSE);
2668
2669         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2670
2671         if (after_change) {
2672                 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2673                 gtk_tree_selection_unselect_all (sel);
2674
2675                 if (priv->folder_to_select)
2676                         g_object_unref(priv->folder_to_select);
2677                 priv->folder_to_select = TNY_FOLDER(g_object_ref(folder));
2678                 return TRUE;
2679         }
2680
2681         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2682         if (!model)
2683                 return FALSE;
2684
2685
2686         /* Refilter the model, before selecting the folder */
2687         gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2688
2689         if (!gtk_tree_model_get_iter_first (model, &iter)) {
2690                 g_warning ("%s: model is empty", __FUNCTION__);
2691                 return FALSE;
2692         }
2693
2694         if (find_folder_iter (model, &iter, &folder_iter, folder)) {
2695                 GtkTreePath *path;
2696
2697                 path = gtk_tree_model_get_path (model, &folder_iter);
2698                 gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
2699
2700                 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2701                 gtk_tree_selection_select_iter (sel, &folder_iter);
2702                 gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
2703
2704                 gtk_tree_path_free (path);
2705                 return TRUE;
2706         }
2707         return FALSE;
2708 }
2709
2710
2711 void
2712 modest_folder_view_copy_selection (ModestFolderView *self)
2713 {
2714         g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2715
2716         /* Copy selection */
2717         _clipboard_set_selected_data (self, FALSE);
2718 }
2719
2720 void
2721 modest_folder_view_cut_selection (ModestFolderView *folder_view)
2722 {
2723         ModestFolderViewPrivate *priv = NULL;
2724         GtkTreeModel *model = NULL;
2725         const gchar **hidding = NULL;
2726         guint i, n_selected;
2727
2728         g_return_if_fail (folder_view && MODEST_IS_FOLDER_VIEW (folder_view));
2729         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
2730
2731         /* Copy selection */
2732         if (!_clipboard_set_selected_data (folder_view, TRUE))
2733                 return;
2734
2735         /* Get hidding ids */
2736         hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected);
2737
2738         /* Clear hidding array created by previous cut operation */
2739         _clear_hidding_filter (MODEST_FOLDER_VIEW (folder_view));
2740
2741         /* Copy hidding array */
2742         priv->n_selected = n_selected;
2743         priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
2744         for (i=0; i < n_selected; i++)
2745                 priv->hidding_ids[i] = g_strdup(hidding[i]);
2746
2747         /* Hide cut folders */
2748         model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
2749         gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2750 }
2751
2752 void
2753 modest_folder_view_copy_model (ModestFolderView *folder_view_src,
2754                                ModestFolderView *folder_view_dst)
2755 {
2756         GtkTreeModel *filter_model = NULL;
2757         GtkTreeModel *model = NULL;
2758         GtkTreeModel *new_filter_model = NULL;
2759
2760         g_return_if_fail (folder_view_src && MODEST_IS_FOLDER_VIEW (folder_view_src));
2761         g_return_if_fail (folder_view_dst && MODEST_IS_FOLDER_VIEW (folder_view_dst));
2762
2763         /* Get src model*/
2764         filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view_src));
2765         model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER(filter_model));
2766
2767         /* Build new filter model */
2768         new_filter_model = gtk_tree_model_filter_new (model, NULL);
2769         gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (new_filter_model),
2770                                                 filter_row,
2771                                                 folder_view_dst,
2772                                                 NULL);
2773         /* Set copied model */
2774         gtk_tree_view_set_model (GTK_TREE_VIEW (folder_view_dst), new_filter_model);
2775         g_signal_connect (G_OBJECT(new_filter_model), "row-inserted",
2776                           (GCallback) on_row_inserted_maybe_select_folder, folder_view_dst);
2777
2778         /* Free */
2779         g_object_unref (new_filter_model);
2780 }
2781
2782 void
2783 modest_folder_view_show_non_move_folders (ModestFolderView *folder_view,
2784                                           gboolean show)
2785 {
2786         GtkTreeModel *model = NULL;
2787         ModestFolderViewPrivate* priv;
2788
2789         g_return_if_fail (folder_view && MODEST_IS_FOLDER_VIEW (folder_view));
2790
2791         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
2792         priv->show_non_move = show;
2793 /*      modest_folder_view_update_model(folder_view, */
2794 /*                                      TNY_ACCOUNT_STORE(modest_runtime_get_account_store())); */
2795
2796         /* Hide special folders */
2797         model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
2798         if (GTK_IS_TREE_MODEL_FILTER (model)) {
2799                 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2800         }
2801 }
2802
2803 /* Returns FALSE if it did not selected anything */
2804 static gboolean
2805 _clipboard_set_selected_data (ModestFolderView *folder_view,
2806                               gboolean delete)
2807 {
2808         ModestFolderViewPrivate *priv = NULL;
2809         TnyFolderStore *folder = NULL;
2810         gboolean retval = FALSE;
2811
2812         g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (folder_view), FALSE);
2813         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
2814
2815         /* Set selected data on clipboard   */
2816         g_return_val_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard), FALSE);
2817         folder = modest_folder_view_get_selected (folder_view);
2818
2819         /* Do not allow to select an account */
2820         if (TNY_IS_FOLDER (folder)) {
2821                 modest_email_clipboard_set_data (priv->clipboard, TNY_FOLDER(folder), NULL, delete);
2822                 retval = TRUE;
2823         }
2824
2825         /* Free */
2826         g_object_unref (folder);
2827
2828         return retval;
2829 }
2830
2831 static void
2832 _clear_hidding_filter (ModestFolderView *folder_view)
2833 {
2834         ModestFolderViewPrivate *priv;
2835         guint i;
2836
2837         g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view));
2838         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
2839
2840         if (priv->hidding_ids != NULL) {
2841                 for (i=0; i < priv->n_selected; i++)
2842                         g_free (priv->hidding_ids[i]);
2843                 g_free(priv->hidding_ids);
2844         }
2845 }
2846
2847
2848 static void
2849 on_display_name_changed (ModestAccountMgr *mgr,
2850                          const gchar *account,
2851                          gpointer user_data)
2852 {
2853         ModestFolderView *self;
2854
2855         self = MODEST_FOLDER_VIEW (user_data);
2856
2857         /* Force a redraw */
2858 #if GTK_CHECK_VERSION(2, 8, 0)
2859         GtkTreeViewColumn * tree_column;
2860
2861         tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self),
2862                                                 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN);
2863         gtk_tree_view_column_queue_resize (tree_column);
2864 #else
2865         gtk_widget_queue_draw (GTK_WIDGET (self));
2866 #endif
2867 }