8e21f2f1cfd51284f7f45bc3b43705c59a0e5638
[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         /* This could happen ig we perform a d&d very quickly over the
1821            same row that row could dissapear because message is
1822            transferred */
1823         if (!TNY_IS_FOLDER (src_folder))
1824                 goto cleanup;
1825
1826         /* Get the target folder */
1827         gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
1828         gtk_tree_model_get (dest_model, &dest_iter,
1829                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
1830                             &folder, -1);
1831
1832         if (!folder || !TNY_IS_FOLDER(folder)) {
1833 /*              g_warning ("%s: not a valid target folder (%p)", __FUNCTION__, folder); */
1834                 goto cleanup;
1835         }
1836
1837         folder_type = modest_tny_folder_guess_folder_type (folder);
1838         if (folder_type == TNY_FOLDER_TYPE_INVALID) {
1839 /*              g_warning ("%s: invalid target folder", __FUNCTION__); */
1840                 goto cleanup;  /* cannot move messages there */
1841         }
1842
1843         if (modest_tny_folder_get_rules((TNY_FOLDER(folder))) & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE) {
1844 /*              g_warning ("folder not writable"); */
1845                 goto cleanup; /* verboten! */
1846         }
1847
1848         /* Ask for confirmation to move */
1849         main_win = modest_window_mgr_get_main_window (mgr, FALSE); /* don't create */
1850         if (!main_win) {
1851                 g_warning ("%s: BUG: no main window found", __FUNCTION__);
1852                 goto cleanup;
1853         }
1854
1855         /* Transfer messages */
1856         modest_ui_actions_transfer_messages_helper (GTK_WINDOW (main_win), src_folder,
1857                                                     headers, folder);
1858
1859         /* Frees */
1860 cleanup:
1861         if (G_IS_OBJECT (src_folder))
1862                 g_object_unref (src_folder);
1863         if (G_IS_OBJECT(folder))
1864                 g_object_unref (G_OBJECT (folder));
1865         if (G_IS_OBJECT(headers))
1866                 g_object_unref (headers);
1867 }
1868
1869 typedef struct {
1870         TnyFolderStore *src_folder;
1871         TnyFolderStore *dst_folder;
1872         ModestFolderView *folder_view;
1873         DndHelper *helper;
1874 } DndFolderInfo;
1875
1876 static void
1877 dnd_folder_info_destroyer (DndFolderInfo *info)
1878 {
1879         if (info->src_folder)
1880                 g_object_unref (info->src_folder);
1881         if (info->dst_folder)
1882                 g_object_unref (info->dst_folder);
1883         g_slice_free (DndFolderInfo, info);
1884 }
1885
1886 static void
1887 dnd_on_connection_failed_destroyer (DndFolderInfo *info,
1888                                     GtkWindow *parent_window,
1889                                     TnyAccount *account)
1890 {
1891         /* Show error */
1892         modest_ui_actions_on_account_connection_error (parent_window, account);
1893
1894         /* Free the helper & info */
1895         dnd_helper_destroyer (info->helper);
1896         dnd_folder_info_destroyer (info);
1897 }
1898
1899 static void
1900 drag_and_drop_from_folder_view_src_folder_performer (gboolean canceled,
1901                                                      GError *err,
1902                                                      GtkWindow *parent_window,
1903                                                      TnyAccount *account,
1904                                                      gpointer user_data)
1905 {
1906         DndFolderInfo *info = NULL;
1907         ModestMailOperation *mail_op;
1908
1909         info = (DndFolderInfo *) user_data;
1910
1911         if (err || canceled) {
1912                 dnd_on_connection_failed_destroyer (info, parent_window, account);
1913                 return;
1914         }
1915
1916         /* Do the mail operation */
1917         mail_op = modest_mail_operation_new_with_error_handling ((GObject *) parent_window,
1918                                                                  modest_ui_actions_move_folder_error_handler,
1919                                                                  info->src_folder, NULL);
1920
1921         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1922                                          mail_op);
1923
1924         /* Transfer the folder */
1925         modest_mail_operation_xfer_folder (mail_op,
1926                                            TNY_FOLDER (info->src_folder),
1927                                            info->dst_folder,
1928                                            info->helper->delete_source,
1929                                            xfer_folder_cb,
1930                                            info->helper->folder_view);
1931
1932         /* Frees */
1933         g_object_unref (G_OBJECT (mail_op));
1934         dnd_helper_destroyer (info->helper);
1935         dnd_folder_info_destroyer (info);
1936 }
1937
1938
1939 static void
1940 drag_and_drop_from_folder_view_dst_folder_performer (gboolean canceled,
1941                                                      GError *err,
1942                                                      GtkWindow *parent_window,
1943                                                      TnyAccount *account,
1944                                                      gpointer user_data)
1945 {
1946         DndFolderInfo *info = NULL;
1947
1948         info = (DndFolderInfo *) user_data;
1949
1950         if (err || canceled) {
1951                 dnd_on_connection_failed_destroyer (info, parent_window, account);
1952                 return;
1953         }
1954
1955         /* Connect to source folder and perform the copy/move */
1956         modest_platform_connect_if_remote_and_perform (NULL, TRUE,
1957                                                        info->src_folder,
1958                                                        drag_and_drop_from_folder_view_src_folder_performer,
1959                                                        info);
1960 }
1961
1962 /*
1963  * This function is used by drag_data_received_cb to manage drag and
1964  * drop of a folder, i.e, and drag from the folder view to the same
1965  * folder view.
1966  */
1967 static void
1968 drag_and_drop_from_folder_view (GtkTreeModel     *source_model,
1969                                 GtkTreeModel     *dest_model,
1970                                 GtkTreePath      *dest_row,
1971                                 GtkSelectionData *selection_data,
1972                                 DndHelper        *helper)
1973 {
1974         GtkTreeIter dest_iter, iter;
1975         TnyFolderStore *dest_folder = NULL;
1976         TnyFolderStore *folder = NULL;
1977         gboolean forbidden = FALSE;
1978         ModestWindow *win;
1979         DndFolderInfo *info = NULL;
1980
1981         win = modest_window_mgr_get_main_window (modest_runtime_get_window_mgr(), FALSE); /* don't create */
1982         if (!win) {
1983                 g_warning ("%s: BUG: no main window", __FUNCTION__);
1984                 dnd_helper_destroyer (helper);
1985                 return;
1986         }
1987
1988         if (!forbidden) {
1989                 /* check the folder rules for the destination */
1990                 folder = tree_path_to_folder (dest_model, dest_row);
1991                 if (TNY_IS_FOLDER(folder)) {
1992                         ModestTnyFolderRules rules =
1993                                 modest_tny_folder_get_rules (TNY_FOLDER (folder));
1994                         forbidden = rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE;
1995                 } else if (TNY_IS_FOLDER_STORE(folder)) {
1996                         /* enable local root as destination for folders */
1997                         if (!MODEST_IS_TNY_LOCAL_FOLDERS_ACCOUNT (folder) &&
1998                             !modest_tny_account_is_memory_card_account (TNY_ACCOUNT (folder)))
1999                                 forbidden = TRUE;
2000                 }
2001                 g_object_unref (folder);
2002         }
2003         if (!forbidden) {
2004                 /* check the folder rules for the source */
2005                 folder = tree_path_to_folder (source_model, helper->source_row);
2006                 if (TNY_IS_FOLDER(folder)) {
2007                         ModestTnyFolderRules rules =
2008                                 modest_tny_folder_get_rules (TNY_FOLDER (folder));
2009                         forbidden = rules & MODEST_FOLDER_RULES_FOLDER_NON_MOVEABLE;
2010                 } else
2011                         forbidden = TRUE;
2012                 g_object_unref (folder);
2013         }
2014
2015
2016         /* Check if the drag is possible */
2017         if (forbidden || !gtk_tree_path_compare (helper->source_row, dest_row)) {
2018                 /* Show error */
2019                 modest_platform_run_information_dialog ((GtkWindow *) win, 
2020                                                         _("mail_in_ui_folder_move_target_error"), 
2021                                                         FALSE);
2022                 /* Restore the previous selection */
2023                 folder = tree_path_to_folder (source_model, helper->source_row);
2024                 if (folder) {
2025                         if (TNY_IS_FOLDER (folder))
2026                                 modest_folder_view_select_folder (helper->folder_view, 
2027                                                                   TNY_FOLDER (folder), FALSE);
2028                         g_object_unref (folder);
2029                 }
2030                 dnd_helper_destroyer (helper);
2031                 return;
2032         }
2033
2034         /* Get data */
2035         gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
2036         gtk_tree_model_get (dest_model, &dest_iter,
2037                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
2038                             &dest_folder, -1);
2039         gtk_tree_model_get_iter (source_model, &iter, helper->source_row);
2040         gtk_tree_model_get (source_model, &iter,
2041                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
2042                             &folder, -1);
2043
2044         /* Create the info for the performer */
2045         info = g_slice_new0 (DndFolderInfo);
2046         info->src_folder = g_object_ref (folder);
2047         info->dst_folder = g_object_ref (dest_folder);
2048         info->helper = helper;
2049
2050         /* Connect to the destination folder and perform the copy/move */
2051         modest_platform_connect_if_remote_and_perform (GTK_WINDOW (win), TRUE,
2052                                                        dest_folder,
2053                                                        drag_and_drop_from_folder_view_dst_folder_performer,
2054                                                        info);
2055
2056         /* Frees */
2057         g_object_unref (dest_folder);
2058         g_object_unref (folder);
2059 }
2060
2061 /*
2062  * This function receives the data set by the "drag-data-get" signal
2063  * handler. This information comes within the #GtkSelectionData. This
2064  * function will manage both the drags of folders of the treeview and
2065  * drags of headers of the header view widget.
2066  */
2067 static void
2068 on_drag_data_received (GtkWidget *widget,
2069                        GdkDragContext *context,
2070                        gint x,
2071                        gint y,
2072                        GtkSelectionData *selection_data,
2073                        guint target_type,
2074                        guint time,
2075                        gpointer data)
2076 {
2077         GtkWidget *source_widget;
2078         GtkTreeModel *dest_model, *source_model;
2079         GtkTreePath *source_row, *dest_row;
2080         GtkTreeViewDropPosition pos;
2081         gboolean delete_source = FALSE;
2082         gboolean success = FALSE;
2083
2084         /* Do not allow further process */
2085         g_signal_stop_emission_by_name (widget, "drag-data-received");
2086         source_widget = gtk_drag_get_source_widget (context);
2087
2088         /* Get the action */
2089         if (context->action == GDK_ACTION_MOVE) {
2090                 delete_source = TRUE;
2091
2092                 /* Notify that there is no folder selected. We need to
2093                    do this in order to update the headers view (and
2094                    its monitors, because when moving, the old folder
2095                    won't longer exist. We can not wait for the end of
2096                    the operation, because the operation won't start if
2097                    the folder is in use */
2098                 if (source_widget == widget) {
2099                         GtkTreeSelection *sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
2100                         gtk_tree_selection_unselect_all (sel);
2101                 }
2102         }
2103
2104         /* Check if the get_data failed */
2105         if (selection_data == NULL || selection_data->length < 0)
2106                 goto end;
2107
2108         /* Select the destination model */
2109         dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
2110
2111         /* Get the path to the destination row. Can not call
2112            gtk_tree_view_get_drag_dest_row() because the source row
2113            is not selected anymore */
2114         gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget), x, y,
2115                                            &dest_row, &pos);
2116
2117         /* Only allow drops IN other rows */
2118         if (!dest_row ||
2119             pos == GTK_TREE_VIEW_DROP_BEFORE ||
2120             pos == GTK_TREE_VIEW_DROP_AFTER)
2121                 goto end;
2122
2123         success = TRUE;
2124         /* Drags from the header view */
2125         if (source_widget != widget) {
2126                 source_model = gtk_tree_view_get_model (GTK_TREE_VIEW (source_widget));
2127
2128                 drag_and_drop_from_header_view (source_model,
2129                                                 dest_model,
2130                                                 dest_row,
2131                                                 selection_data);
2132         } else {
2133                 DndHelper *helper = NULL;
2134
2135                 /* Get the source model and row */
2136                 gtk_tree_get_row_drag_data (selection_data,
2137                                             &source_model,
2138                                             &source_row);
2139
2140                 /* Create the helper */
2141                 helper = g_slice_new0 (DndHelper);
2142                 helper->delete_source = delete_source;
2143                 helper->source_row = gtk_tree_path_copy (source_row);
2144                 helper->folder_view = MODEST_FOLDER_VIEW (widget);
2145
2146                 drag_and_drop_from_folder_view (source_model,
2147                                                 dest_model,
2148                                                 dest_row,
2149                                                 selection_data,
2150                                                 helper);
2151
2152                 gtk_tree_path_free (source_row);
2153         }
2154
2155         /* Frees */
2156         gtk_tree_path_free (dest_row);
2157
2158  end:
2159         /* Finish the drag and drop */
2160         gtk_drag_finish (context, success, FALSE, time);
2161 }
2162
2163 /*
2164  * We define a "drag-drop" signal handler because we do not want to
2165  * use the default one, because the default one always calls
2166  * gtk_drag_finish and we prefer to do it in the "drag-data-received"
2167  * signal handler, because there we have all the information available
2168  * to know if the dnd was a success or not.
2169  */
2170 static gboolean
2171 drag_drop_cb (GtkWidget      *widget,
2172               GdkDragContext *context,
2173               gint            x,
2174               gint            y,
2175               guint           time,
2176               gpointer        user_data)
2177 {
2178         gpointer target;
2179
2180         if (!context->targets)
2181                 return FALSE;
2182
2183         /* Check if we're dragging a folder row */
2184         target = gtk_drag_dest_find_target (widget, context, NULL);
2185
2186         /* Request the data from the source. */
2187         gtk_drag_get_data(widget, context, target, time);
2188
2189     return TRUE;
2190 }
2191
2192 /*
2193  * This function expands a node of a tree view if it's not expanded
2194  * yet. Not sure why it needs the threads stuff, but gtk+`example code
2195  * does that, so that's why they're here.
2196  */
2197 static gint
2198 expand_row_timeout (gpointer data)
2199 {
2200         GtkTreeView *tree_view = data;
2201         GtkTreePath *dest_path = NULL;
2202         GtkTreeViewDropPosition pos;
2203         gboolean result = FALSE;
2204
2205         gdk_threads_enter ();
2206
2207         gtk_tree_view_get_drag_dest_row (tree_view,
2208                                          &dest_path,
2209                                          &pos);
2210
2211         if (dest_path &&
2212             (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER ||
2213              pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)) {
2214                 gtk_tree_view_expand_row (tree_view, dest_path, FALSE);
2215                 gtk_tree_path_free (dest_path);
2216         }
2217         else {
2218                 if (dest_path)
2219                         gtk_tree_path_free (dest_path);
2220
2221                 result = TRUE;
2222         }
2223
2224         gdk_threads_leave ();
2225
2226         return result;
2227 }
2228
2229 /*
2230  * This function is called whenever the pointer is moved over a widget
2231  * while dragging some data. It installs a timeout that will expand a
2232  * node of the treeview if not expanded yet. This function also calls
2233  * gdk_drag_status in order to set the suggested action that will be
2234  * used by the "drag-data-received" signal handler to know if we
2235  * should do a move or just a copy of the data.
2236  */
2237 static gboolean
2238 on_drag_motion (GtkWidget      *widget,
2239                 GdkDragContext *context,
2240                 gint            x,
2241                 gint            y,
2242                 guint           time,
2243                 gpointer        user_data)
2244 {
2245         GtkTreeViewDropPosition pos;
2246         GtkTreePath *dest_row;
2247         GtkTreeModel *dest_model;
2248         ModestFolderViewPrivate *priv;
2249         GdkDragAction suggested_action;
2250         gboolean valid_location = FALSE;
2251         TnyFolderStore *folder = NULL;
2252
2253         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
2254
2255         if (priv->timer_expander != 0) {
2256                 g_source_remove (priv->timer_expander);
2257                 priv->timer_expander = 0;
2258         }
2259
2260         gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
2261                                            x, y,
2262                                            &dest_row,
2263                                            &pos);
2264
2265         /* Do not allow drops between folders */
2266         if (!dest_row ||
2267             pos == GTK_TREE_VIEW_DROP_BEFORE ||
2268             pos == GTK_TREE_VIEW_DROP_AFTER) {
2269                 gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW (widget), NULL, 0);
2270                 gdk_drag_status(context, 0, time);
2271                 valid_location = FALSE;
2272                 goto out;
2273         } else {
2274                 valid_location = TRUE;
2275         }
2276
2277         /* Check that the destination folder is writable */
2278         dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
2279         folder = tree_path_to_folder (dest_model, dest_row);
2280         if (folder && TNY_IS_FOLDER (folder)) {
2281                 ModestTnyFolderRules rules = modest_tny_folder_get_rules(TNY_FOLDER (folder));
2282
2283                 if (rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE) {
2284                         valid_location = FALSE;
2285                         goto out;
2286                 }
2287         }
2288
2289         /* Expand the selected row after 1/2 second */
2290         if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), dest_row)) {
2291                 priv->timer_expander = g_timeout_add (500, expand_row_timeout, widget);
2292         }
2293         gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), dest_row, pos);
2294
2295         /* Select the desired action. By default we pick MOVE */
2296         suggested_action = GDK_ACTION_MOVE;
2297
2298         if (context->actions == GDK_ACTION_COPY)
2299             gdk_drag_status(context, GDK_ACTION_COPY, time);
2300         else if (context->actions == GDK_ACTION_MOVE)
2301             gdk_drag_status(context, GDK_ACTION_MOVE, time);
2302         else if (context->actions & suggested_action)
2303             gdk_drag_status(context, suggested_action, time);
2304         else
2305             gdk_drag_status(context, GDK_ACTION_DEFAULT, time);
2306
2307  out:
2308         if (folder)
2309                 g_object_unref (folder);
2310         if (dest_row) {
2311                 gtk_tree_path_free (dest_row);
2312         }
2313         g_signal_stop_emission_by_name (widget, "drag-motion");
2314
2315         return valid_location;
2316 }
2317
2318 /*
2319  * This function sets the treeview as a source and a target for dnd
2320  * events. It also connects all the requirede signals.
2321  */
2322 static void
2323 setup_drag_and_drop (GtkTreeView *self)
2324 {
2325         /* Set up the folder view as a dnd destination. Set only the
2326            highlight flag, otherwise gtk will have a different
2327            behaviour */
2328         gtk_drag_dest_set (GTK_WIDGET (self),
2329                            GTK_DEST_DEFAULT_HIGHLIGHT,
2330                            folder_view_drag_types,
2331                            G_N_ELEMENTS (folder_view_drag_types),
2332                            GDK_ACTION_MOVE | GDK_ACTION_COPY);
2333
2334         g_signal_connect (G_OBJECT (self),
2335                           "drag_data_received",
2336                           G_CALLBACK (on_drag_data_received),
2337                           NULL);
2338
2339
2340         /* Set up the treeview as a dnd source */
2341         gtk_drag_source_set (GTK_WIDGET (self),
2342                              GDK_BUTTON1_MASK,
2343                              folder_view_drag_types,
2344                              G_N_ELEMENTS (folder_view_drag_types),
2345                              GDK_ACTION_MOVE | GDK_ACTION_COPY);
2346
2347         g_signal_connect (G_OBJECT (self),
2348                           "drag_motion",
2349                           G_CALLBACK (on_drag_motion),
2350                           NULL);
2351
2352         g_signal_connect (G_OBJECT (self),
2353                           "drag_data_get",
2354                           G_CALLBACK (on_drag_data_get),
2355                           NULL);
2356
2357         g_signal_connect (G_OBJECT (self),
2358                           "drag_drop",
2359                           G_CALLBACK (drag_drop_cb),
2360                           NULL);
2361 }
2362
2363 /*
2364  * This function manages the navigation through the folders using the
2365  * keyboard or the hardware keys in the device
2366  */
2367 static gboolean
2368 on_key_pressed (GtkWidget *self,
2369                 GdkEventKey *event,
2370                 gpointer user_data)
2371 {
2372         GtkTreeSelection *selection;
2373         GtkTreeIter iter;
2374         GtkTreeModel *model;
2375         gboolean retval = FALSE;
2376
2377         /* Up and Down are automatically managed by the treeview */
2378         if (event->keyval == GDK_Return) {
2379                 /* Expand/Collapse the selected row */
2380                 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2381                 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
2382                         GtkTreePath *path;
2383
2384                         path = gtk_tree_model_get_path (model, &iter);
2385
2386                         if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (self), path))
2387                                 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
2388                         else
2389                                 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
2390                         gtk_tree_path_free (path);
2391                 }
2392                 /* No further processing */
2393                 retval = TRUE;
2394         }
2395
2396         return retval;
2397 }
2398
2399 /*
2400  * We listen to the changes in the local folder account name key,
2401  * because we want to show the right name in the view. The local
2402  * folder account name corresponds to the device name in the Maemo
2403  * version. We do this because we do not want to query gconf on each
2404  * tree view refresh. It's better to cache it and change whenever
2405  * necessary.
2406  */
2407 static void
2408 on_configuration_key_changed (ModestConf* conf,
2409                               const gchar *key,
2410                               ModestConfEvent event,
2411                               ModestConfNotificationId id,
2412                               ModestFolderView *self)
2413 {
2414         ModestFolderViewPrivate *priv;
2415
2416
2417         g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
2418         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2419
2420         if (!strcmp (key, MODEST_CONF_DEVICE_NAME)) {
2421                 g_free (priv->local_account_name);
2422
2423                 if (event == MODEST_CONF_EVENT_KEY_UNSET)
2424                         priv->local_account_name = g_strdup (MODEST_LOCAL_FOLDERS_DEFAULT_DISPLAY_NAME);
2425                 else
2426                         priv->local_account_name = modest_conf_get_string (modest_runtime_get_conf(),
2427                                                                            MODEST_CONF_DEVICE_NAME, NULL);
2428
2429                 /* Force a redraw */
2430 #if GTK_CHECK_VERSION(2, 8, 0)
2431                 GtkTreeViewColumn * tree_column;
2432
2433                 tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self),
2434                                                         TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN);
2435                 gtk_tree_view_column_queue_resize (tree_column);
2436 #else
2437                 gtk_widget_queue_draw (GTK_WIDGET (self));
2438 #endif
2439         }
2440 }
2441
2442 void
2443 modest_folder_view_set_style (ModestFolderView *self,
2444                               ModestFolderViewStyle style)
2445 {
2446         ModestFolderViewPrivate *priv;
2447
2448         g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2449         g_return_if_fail (style == MODEST_FOLDER_VIEW_STYLE_SHOW_ALL ||
2450                           style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE);
2451
2452         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2453
2454
2455         priv->style = style;
2456 }
2457
2458 void
2459 modest_folder_view_set_account_id_of_visible_server_account (ModestFolderView *self,
2460                                                              const gchar *account_id)
2461 {
2462         ModestFolderViewPrivate *priv;
2463         GtkTreeModel *model;
2464
2465         g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2466
2467         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2468
2469         /* This will be used by the filter_row callback,
2470          * to decided which rows to show: */
2471         if (priv->visible_account_id) {
2472                 g_free (priv->visible_account_id);
2473                 priv->visible_account_id = NULL;
2474         }
2475         if (account_id)
2476                 priv->visible_account_id = g_strdup (account_id);
2477
2478         /* Refilter */
2479         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2480         if (GTK_IS_TREE_MODEL_FILTER (model))
2481                 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2482
2483         /* Save settings to gconf */
2484         modest_widget_memory_save (modest_runtime_get_conf (), G_OBJECT(self),
2485                                    MODEST_CONF_FOLDER_VIEW_KEY);
2486 }
2487
2488 const gchar *
2489 modest_folder_view_get_account_id_of_visible_server_account (ModestFolderView *self)
2490 {
2491         ModestFolderViewPrivate *priv;
2492
2493         g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW(self), NULL);
2494
2495         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2496
2497         return (const gchar *) priv->visible_account_id;
2498 }
2499
2500 static gboolean
2501 find_inbox_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *inbox_iter)
2502 {
2503         do {
2504                 GtkTreeIter child;
2505                 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2506
2507                 gtk_tree_model_get (model, iter,
2508                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN,
2509                                     &type, -1);
2510
2511                 gboolean result = FALSE;
2512                 if (type == TNY_FOLDER_TYPE_INBOX) {
2513                         result = TRUE;
2514                 }
2515                 if (result) {
2516                         *inbox_iter = *iter;
2517                         return TRUE;
2518                 }
2519
2520                 if (gtk_tree_model_iter_children (model, &child, iter)) {
2521                         if (find_inbox_iter (model, &child, inbox_iter))
2522                                 return TRUE;
2523                 }
2524
2525         } while (gtk_tree_model_iter_next (model, iter));
2526
2527         return FALSE;
2528 }
2529
2530
2531
2532
2533 void
2534 modest_folder_view_select_first_inbox_or_local (ModestFolderView *self)
2535 {
2536         GtkTreeModel *model;
2537         GtkTreeIter iter, inbox_iter;
2538         GtkTreeSelection *sel;
2539         GtkTreePath *path = NULL;
2540
2541         g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2542
2543         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2544         if (!model)
2545                 return;
2546
2547         expand_root_items (self);
2548         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2549
2550         if (!gtk_tree_model_get_iter_first (model, &iter)) {
2551                 g_warning ("%s: model is empty", __FUNCTION__);
2552                 return;
2553         }
2554
2555         if (find_inbox_iter (model, &iter, &inbox_iter))
2556                 path = gtk_tree_model_get_path (model, &inbox_iter);
2557         else
2558                 path = gtk_tree_path_new_first ();
2559
2560         /* Select the row and free */
2561         gtk_tree_view_set_cursor (GTK_TREE_VIEW (self), path, NULL, FALSE);
2562         gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (self), path, NULL, FALSE, 0.0, 0.0);
2563         gtk_tree_path_free (path);
2564
2565         /* set focus */
2566         gtk_widget_grab_focus (GTK_WIDGET(self));
2567 }
2568
2569
2570 /* recursive */
2571 static gboolean
2572 find_folder_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *folder_iter,
2573                   TnyFolder* folder)
2574 {
2575         do {
2576                 GtkTreeIter child;
2577                 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2578                 TnyFolder* a_folder;
2579                 gchar *name = NULL;
2580
2581                 gtk_tree_model_get (model, iter,
2582                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &a_folder,
2583                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name,
2584                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
2585                                     -1);
2586                 g_free (name);
2587
2588                 if (folder == a_folder) {
2589                         g_object_unref (a_folder);
2590                         *folder_iter = *iter;
2591                         return TRUE;
2592                 }
2593                 g_object_unref (a_folder);
2594
2595                 if (gtk_tree_model_iter_children (model, &child, iter)) {
2596                         if (find_folder_iter (model, &child, folder_iter, folder))
2597                                 return TRUE;
2598                 }
2599
2600         } while (gtk_tree_model_iter_next (model, iter));
2601
2602         return FALSE;
2603 }
2604
2605
2606 static void
2607 on_row_inserted_maybe_select_folder (GtkTreeModel *tree_model,
2608                                      GtkTreePath *path,
2609                                      GtkTreeIter *iter,
2610                                      ModestFolderView *self)
2611 {
2612         ModestFolderViewPrivate *priv = NULL;
2613         GtkTreeSelection *sel;
2614         TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2615         GObject *instance = NULL;
2616
2617         if (!MODEST_IS_FOLDER_VIEW(self))
2618                 return;
2619
2620         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2621
2622         priv->reexpand = TRUE;
2623
2624         gtk_tree_model_get (tree_model, iter,
2625                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
2626                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
2627                             -1);
2628         if (type == TNY_FOLDER_TYPE_INBOX && priv->folder_to_select == NULL) {
2629                 priv->folder_to_select = g_object_ref (instance);
2630         }
2631         g_object_unref (instance);
2632
2633         if (priv->folder_to_select) {
2634
2635                 if (!modest_folder_view_select_folder (self, priv->folder_to_select,
2636                                                        FALSE)) {
2637                         GtkTreePath *path;
2638                         path = gtk_tree_model_get_path (tree_model, iter);
2639                         gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
2640
2641                         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2642
2643                         gtk_tree_selection_select_iter (sel, iter);
2644                         gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
2645
2646                         gtk_tree_path_free (path);
2647                 }
2648
2649                 /* Disable next */
2650                 modest_folder_view_disable_next_folder_selection (self);
2651
2652                 /* Refilter the model */
2653                 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (tree_model));
2654         }
2655 }
2656
2657
2658 void
2659 modest_folder_view_disable_next_folder_selection (ModestFolderView *self)
2660 {
2661         ModestFolderViewPrivate *priv;
2662
2663         g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2664
2665         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2666
2667         if (priv->folder_to_select)
2668                 g_object_unref(priv->folder_to_select);
2669
2670         priv->folder_to_select = NULL;
2671 }
2672
2673 gboolean
2674 modest_folder_view_select_folder (ModestFolderView *self, TnyFolder *folder,
2675                                   gboolean after_change)
2676 {
2677         GtkTreeModel *model;
2678         GtkTreeIter iter, folder_iter;
2679         GtkTreeSelection *sel;
2680         ModestFolderViewPrivate *priv = NULL;
2681
2682         g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW (self), FALSE);
2683         g_return_val_if_fail (folder && TNY_IS_FOLDER (folder), FALSE);
2684
2685         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2686
2687         if (after_change) {
2688                 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2689                 gtk_tree_selection_unselect_all (sel);
2690
2691                 if (priv->folder_to_select)
2692                         g_object_unref(priv->folder_to_select);
2693                 priv->folder_to_select = TNY_FOLDER(g_object_ref(folder));
2694                 return TRUE;
2695         }
2696
2697         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2698         if (!model)
2699                 return FALSE;
2700
2701
2702         /* Refilter the model, before selecting the folder */
2703         gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2704
2705         if (!gtk_tree_model_get_iter_first (model, &iter)) {
2706                 g_warning ("%s: model is empty", __FUNCTION__);
2707                 return FALSE;
2708         }
2709
2710         if (find_folder_iter (model, &iter, &folder_iter, folder)) {
2711                 GtkTreePath *path;
2712
2713                 path = gtk_tree_model_get_path (model, &folder_iter);
2714                 gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
2715
2716                 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2717                 gtk_tree_selection_select_iter (sel, &folder_iter);
2718                 gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
2719
2720                 gtk_tree_path_free (path);
2721                 return TRUE;
2722         }
2723         return FALSE;
2724 }
2725
2726
2727 void
2728 modest_folder_view_copy_selection (ModestFolderView *self)
2729 {
2730         g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2731
2732         /* Copy selection */
2733         _clipboard_set_selected_data (self, FALSE);
2734 }
2735
2736 void
2737 modest_folder_view_cut_selection (ModestFolderView *folder_view)
2738 {
2739         ModestFolderViewPrivate *priv = NULL;
2740         GtkTreeModel *model = NULL;
2741         const gchar **hidding = NULL;
2742         guint i, n_selected;
2743
2744         g_return_if_fail (folder_view && MODEST_IS_FOLDER_VIEW (folder_view));
2745         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
2746
2747         /* Copy selection */
2748         if (!_clipboard_set_selected_data (folder_view, TRUE))
2749                 return;
2750
2751         /* Get hidding ids */
2752         hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected);
2753
2754         /* Clear hidding array created by previous cut operation */
2755         _clear_hidding_filter (MODEST_FOLDER_VIEW (folder_view));
2756
2757         /* Copy hidding array */
2758         priv->n_selected = n_selected;
2759         priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
2760         for (i=0; i < n_selected; i++)
2761                 priv->hidding_ids[i] = g_strdup(hidding[i]);
2762
2763         /* Hide cut folders */
2764         model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
2765         gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2766 }
2767
2768 void
2769 modest_folder_view_copy_model (ModestFolderView *folder_view_src,
2770                                ModestFolderView *folder_view_dst)
2771 {
2772         GtkTreeModel *filter_model = NULL;
2773         GtkTreeModel *model = NULL;
2774         GtkTreeModel *new_filter_model = NULL;
2775
2776         g_return_if_fail (folder_view_src && MODEST_IS_FOLDER_VIEW (folder_view_src));
2777         g_return_if_fail (folder_view_dst && MODEST_IS_FOLDER_VIEW (folder_view_dst));
2778
2779         /* Get src model*/
2780         filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view_src));
2781         model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER(filter_model));
2782
2783         /* Build new filter model */
2784         new_filter_model = gtk_tree_model_filter_new (model, NULL);
2785         gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (new_filter_model),
2786                                                 filter_row,
2787                                                 folder_view_dst,
2788                                                 NULL);
2789         /* Set copied model */
2790         gtk_tree_view_set_model (GTK_TREE_VIEW (folder_view_dst), new_filter_model);
2791         g_signal_connect (G_OBJECT(new_filter_model), "row-inserted",
2792                           (GCallback) on_row_inserted_maybe_select_folder, folder_view_dst);
2793
2794         /* Free */
2795         g_object_unref (new_filter_model);
2796 }
2797
2798 void
2799 modest_folder_view_show_non_move_folders (ModestFolderView *folder_view,
2800                                           gboolean show)
2801 {
2802         GtkTreeModel *model = NULL;
2803         ModestFolderViewPrivate* priv;
2804
2805         g_return_if_fail (folder_view && MODEST_IS_FOLDER_VIEW (folder_view));
2806
2807         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
2808         priv->show_non_move = show;
2809 /*      modest_folder_view_update_model(folder_view, */
2810 /*                                      TNY_ACCOUNT_STORE(modest_runtime_get_account_store())); */
2811
2812         /* Hide special folders */
2813         model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
2814         if (GTK_IS_TREE_MODEL_FILTER (model)) {
2815                 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2816         }
2817 }
2818
2819 /* Returns FALSE if it did not selected anything */
2820 static gboolean
2821 _clipboard_set_selected_data (ModestFolderView *folder_view,
2822                               gboolean delete)
2823 {
2824         ModestFolderViewPrivate *priv = NULL;
2825         TnyFolderStore *folder = NULL;
2826         gboolean retval = FALSE;
2827
2828         g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (folder_view), FALSE);
2829         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
2830
2831         /* Set selected data on clipboard   */
2832         g_return_val_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard), FALSE);
2833         folder = modest_folder_view_get_selected (folder_view);
2834
2835         /* Do not allow to select an account */
2836         if (TNY_IS_FOLDER (folder)) {
2837                 modest_email_clipboard_set_data (priv->clipboard, TNY_FOLDER(folder), NULL, delete);
2838                 retval = TRUE;
2839         }
2840
2841         /* Free */
2842         g_object_unref (folder);
2843
2844         return retval;
2845 }
2846
2847 static void
2848 _clear_hidding_filter (ModestFolderView *folder_view)
2849 {
2850         ModestFolderViewPrivate *priv;
2851         guint i;
2852
2853         g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view));
2854         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
2855
2856         if (priv->hidding_ids != NULL) {
2857                 for (i=0; i < priv->n_selected; i++)
2858                         g_free (priv->hidding_ids[i]);
2859                 g_free(priv->hidding_ids);
2860         }
2861 }
2862
2863
2864 static void
2865 on_display_name_changed (ModestAccountMgr *mgr,
2866                          const gchar *account,
2867                          gpointer user_data)
2868 {
2869         ModestFolderView *self;
2870
2871         self = MODEST_FOLDER_VIEW (user_data);
2872
2873         /* Force a redraw */
2874 #if GTK_CHECK_VERSION(2, 8, 0)
2875         GtkTreeViewColumn * tree_column;
2876
2877         tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self),
2878                                                 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN);
2879         gtk_tree_view_column_queue_resize (tree_column);
2880 #else
2881         gtk_widget_queue_draw (GTK_WIDGET (self));
2882 #endif
2883 }