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