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