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