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