fixes NB#61800
[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 <modest-tny-account.h>
45 #include <modest-tny-folder.h>
46 #include <modest-tny-local-folders-account.h>
47 #include <modest-tny-outbox-account.h>
48 #include <modest-marshal.h>
49 #include <modest-icon-names.h>
50 #include <modest-tny-account-store.h>
51 #include <modest-text-utils.h>
52 #include <modest-runtime.h>
53 #include "modest-folder-view.h"
54 #include <modest-dnd.h>
55 #include <modest-platform.h>
56 #include <modest-widget-memory.h>
57 #include <modest-ui-actions.h>
58
59 #ifdef MODEST_PLATFORM_MAEMO
60 #include <tny-maemo-conic-device.h>
61 #ifdef MODEST_HAVE_HILDON0_WIDGETS
62 #include <hildon-widgets/hildon-note.h>
63 #include <hildon-widgets/hildon-banner.h>
64 #else
65 #include <hildon/hildon-note.h>
66 #include <hildon/hildon-banner.h>
67 #endif
68 #endif
69
70 /* 'private'/'protected' functions */
71 static void modest_folder_view_class_init  (ModestFolderViewClass *klass);
72 static void modest_folder_view_init        (ModestFolderView *obj);
73 static void modest_folder_view_finalize    (GObject *obj);
74
75 static void         tny_account_store_view_init (gpointer g, 
76                                                  gpointer iface_data);
77
78 static void         modest_folder_view_set_account_store (TnyAccountStoreView *self, 
79                                                           TnyAccountStore     *account_store);
80
81 static void         on_selection_changed   (GtkTreeSelection *sel, gpointer data);
82
83 static void         on_account_removed     (TnyAccountStore *self, 
84                                             TnyAccount *account,
85                                             gpointer user_data);
86
87 static void         on_account_inserted    (TnyAccountStore *self, 
88                                             TnyAccount *account,
89                                             gpointer user_data);
90
91 static void         on_account_changed    (TnyAccountStore *self, 
92                                             TnyAccount *account,
93                                             gpointer user_data);
94
95 static gint         cmp_rows               (GtkTreeModel *tree_model, 
96                                             GtkTreeIter *iter1, 
97                                             GtkTreeIter *iter2,
98                                             gpointer user_data);
99
100 static gboolean     filter_row             (GtkTreeModel *model,
101                                             GtkTreeIter *iter,
102                                             gpointer data);
103
104 static gboolean     on_key_pressed         (GtkWidget *self,
105                                             GdkEventKey *event,
106                                             gpointer user_data);
107
108 static void         on_configuration_key_changed  (ModestConf* conf, 
109                                                    const gchar *key, 
110                                                    ModestConfEvent event,
111                                                    ModestConfNotificationId notification_id, 
112                                                    ModestFolderView *self);
113
114 /* DnD functions */
115 static void         on_drag_data_get       (GtkWidget *widget, 
116                                             GdkDragContext *context, 
117                                             GtkSelectionData *selection_data, 
118                                             guint info, 
119                                             guint time, 
120                                             gpointer data);
121
122 static void         on_drag_data_received  (GtkWidget *widget, 
123                                             GdkDragContext *context, 
124                                             gint x, 
125                                             gint y, 
126                                             GtkSelectionData *selection_data, 
127                                             guint info, 
128                                             guint time, 
129                                             gpointer data);
130
131 static gboolean     on_drag_motion         (GtkWidget      *widget,
132                                             GdkDragContext *context,
133                                             gint            x,
134                                             gint            y,
135                                             guint           time,
136                                             gpointer        user_data);
137
138 static void expand_root_items (ModestFolderView *self);
139
140 static gint         expand_row_timeout     (gpointer data);
141
142 static void         setup_drag_and_drop    (GtkTreeView *self);
143
144 static gboolean     _clipboard_set_selected_data (ModestFolderView *folder_view, 
145                                                   gboolean delete);
146
147 static void         _clear_hidding_filter (ModestFolderView *folder_view);
148
149 static void          on_row_changed_maybe_select_folder (GtkTreeModel     *tree_model, 
150                                                          GtkTreePath      *path, 
151                                                          GtkTreeIter      *iter,
152                                                          ModestFolderView *self);
153
154 enum {
155         FOLDER_SELECTION_CHANGED_SIGNAL,
156         FOLDER_DISPLAY_NAME_CHANGED_SIGNAL,
157         LAST_SIGNAL
158 };
159
160 typedef struct _ModestFolderViewPrivate ModestFolderViewPrivate;
161 struct _ModestFolderViewPrivate {
162         TnyAccountStore      *account_store;
163         TnyFolderStore       *cur_folder_store;
164
165         TnyFolder            *folder_to_select; /* folder to select after the next update */
166
167         ModestConfNotificationId notification_id;
168
169         gulong                changed_signal;
170         gulong                account_inserted_signal;
171         gulong                account_removed_signal;
172         gulong                account_changed_signal;
173         gulong                conf_key_signal;
174         
175         /* not unref this object, its a singlenton */
176         ModestEmailClipboard *clipboard;
177
178         /* Filter tree model */
179         gchar **hidding_ids;
180         guint n_selected;
181
182         TnyFolderStoreQuery  *query;
183         guint                 timer_expander;
184
185         gchar                *local_account_name;
186         gchar                *visible_account_id;
187         ModestFolderViewStyle style;
188
189         gboolean  reselect; /* we use this to force a reselection of the INBOX */
190         gboolean  show_non_move;
191 };
192 #define MODEST_FOLDER_VIEW_GET_PRIVATE(o)                       \
193         (G_TYPE_INSTANCE_GET_PRIVATE((o),                       \
194                                      MODEST_TYPE_FOLDER_VIEW,   \
195                                      ModestFolderViewPrivate))
196 /* globals */
197 static GObjectClass *parent_class = NULL;
198
199 static guint signals[LAST_SIGNAL] = {0}; 
200
201 GType
202 modest_folder_view_get_type (void)
203 {
204         static GType my_type = 0;
205         if (!my_type) {
206                 static const GTypeInfo my_info = {
207                         sizeof(ModestFolderViewClass),
208                         NULL,           /* base init */
209                         NULL,           /* base finalize */
210                         (GClassInitFunc) modest_folder_view_class_init,
211                         NULL,           /* class finalize */
212                         NULL,           /* class data */
213                         sizeof(ModestFolderView),
214                         1,              /* n_preallocs */
215                         (GInstanceInitFunc) modest_folder_view_init,
216                         NULL
217                 };
218
219                 static const GInterfaceInfo tny_account_store_view_info = {
220                         (GInterfaceInitFunc) tny_account_store_view_init, /* interface_init */
221                         NULL,         /* interface_finalize */
222                         NULL          /* interface_data */
223                 };
224
225                                 
226                 my_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
227                                                   "ModestFolderView",
228                                                   &my_info, 0);
229
230                 g_type_add_interface_static (my_type, 
231                                              TNY_TYPE_ACCOUNT_STORE_VIEW, 
232                                              &tny_account_store_view_info);
233         }
234         return my_type;
235 }
236
237 static void
238 modest_folder_view_class_init (ModestFolderViewClass *klass)
239 {
240         GObjectClass *gobject_class;
241         gobject_class = (GObjectClass*) klass;
242
243         parent_class            = g_type_class_peek_parent (klass);
244         gobject_class->finalize = modest_folder_view_finalize;
245
246         g_type_class_add_private (gobject_class,
247                                   sizeof(ModestFolderViewPrivate));
248         
249         signals[FOLDER_SELECTION_CHANGED_SIGNAL] = 
250                 g_signal_new ("folder_selection_changed",
251                               G_TYPE_FROM_CLASS (gobject_class),
252                               G_SIGNAL_RUN_FIRST,
253                               G_STRUCT_OFFSET (ModestFolderViewClass,
254                                                folder_selection_changed),
255                               NULL, NULL,
256                               modest_marshal_VOID__POINTER_BOOLEAN,
257                               G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_BOOLEAN);
258
259         /*
260          * This signal is emitted whenever the currently selected
261          * folder display name is computed. Note that the name could
262          * be different to the folder name, because we could append
263          * the unread messages count to the folder name to build the
264          * folder display name
265          */
266         signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL] = 
267                 g_signal_new ("folder-display-name-changed",
268                               G_TYPE_FROM_CLASS (gobject_class),
269                               G_SIGNAL_RUN_FIRST,
270                               G_STRUCT_OFFSET (ModestFolderViewClass,
271                                                folder_display_name_changed),
272                               NULL, NULL,
273                               g_cclosure_marshal_VOID__STRING,
274                               G_TYPE_NONE, 1, G_TYPE_STRING);
275 }
276
277 /* Simplify checks for NULLs: */
278 static gboolean
279 strings_are_equal (const gchar *a, const gchar *b)
280 {
281         if (!a && !b)
282                 return TRUE;
283         if (a && b)
284         {
285                 return (strcmp (a, b) == 0);
286         }
287         else
288                 return FALSE;
289 }
290
291 static gboolean
292 on_model_foreach_set_name(GtkTreeModel *model, GtkTreePath *path,  GtkTreeIter *iter, gpointer data)
293 {
294         GObject *instance = NULL;
295         
296         gtk_tree_model_get (model, iter,
297                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
298                             -1);
299                             
300         if (!instance)
301                 return FALSE; /* keep walking */
302                         
303         if (!TNY_IS_ACCOUNT (instance)) {
304                 g_object_unref (instance);
305                 return FALSE; /* keep walking */        
306         }    
307         
308         /* Check if this is the looked-for account: */
309         TnyAccount *this_account = TNY_ACCOUNT (instance);
310         TnyAccount *account = TNY_ACCOUNT (data);
311         
312         const gchar *this_account_id = tny_account_get_id(this_account);
313         const gchar *account_id = tny_account_get_id(account);
314         g_object_unref (instance);
315         instance = NULL;
316
317         /* printf ("DEBUG: %s: this_account_id=%s, account_id=%s\n", __FUNCTION__, this_account_id, account_id); */
318         if (strings_are_equal(this_account_id, account_id)) {
319                 /* Tell the model that the data has changed, so that
320                  * it calls the cell_data_func callbacks again: */
321                 /* TODO: This does not seem to actually cause the new string to be shown: */
322                 gtk_tree_model_row_changed (model, path, iter);
323                 
324                 return TRUE; /* stop walking */
325         }
326         
327         return FALSE; /* keep walking */
328 }
329
330 typedef struct 
331 {
332         ModestFolderView *self;
333         gchar *previous_name;
334 } GetMmcAccountNameData;
335
336 static void
337 on_get_mmc_account_name (TnyStoreAccount* account, gpointer user_data)
338 {
339         /* printf ("DEBU1G: %s: account name=%s\n", __FUNCTION__, tny_account_get_name (TNY_ACCOUNT(account))); */
340
341         GetMmcAccountNameData *data = (GetMmcAccountNameData*)user_data;
342         
343         if (!strings_are_equal (
344                 tny_account_get_name(TNY_ACCOUNT(account)), 
345                 data->previous_name)) {
346         
347                 /* Tell the model that the data has changed, so that 
348                  * it calls the cell_data_func callbacks again: */
349                 ModestFolderView *self = data->self;
350                 GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
351                 if (model)
352                         gtk_tree_model_foreach(model, on_model_foreach_set_name, account);
353         }
354
355         g_free (data->previous_name);
356         g_slice_free (GetMmcAccountNameData, data);
357 }
358
359 static void
360 text_cell_data  (GtkTreeViewColumn *column,  GtkCellRenderer *renderer,
361                  GtkTreeModel *tree_model,  GtkTreeIter *iter,  gpointer data)
362 {
363         ModestFolderViewPrivate *priv;
364         GObject *rendobj;
365         gchar *fname = NULL;
366         gint unread = 0;
367         gint all = 0;
368         TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
369         GObject *instance = NULL;
370         
371         g_return_if_fail (column);
372         g_return_if_fail (tree_model);
373
374         gtk_tree_model_get (tree_model, iter,
375                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &fname,
376                             TNY_GTK_FOLDER_STORE_TREE_MODEL_ALL_COLUMN, &all,
377                             TNY_GTK_FOLDER_STORE_TREE_MODEL_UNREAD_COLUMN, &unread,
378                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
379                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
380                             -1);
381         rendobj = G_OBJECT(renderer);
382
383         if (!fname)
384                 return;
385
386         if (!instance) {
387                 g_free (fname);
388                 return;
389         }
390
391         ModestFolderView *self = MODEST_FOLDER_VIEW (data);
392         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE (self);
393         
394         gchar *item_name = NULL;
395         gint item_weight = 400;
396         
397         if (type != TNY_FOLDER_TYPE_ROOT) {
398                 gint number = 0;
399                 
400                 if (modest_tny_folder_is_local_folder (TNY_FOLDER (instance)) ||
401                     modest_tny_folder_is_memory_card_folder (TNY_FOLDER (instance))) {
402                         type = modest_tny_folder_get_local_or_mmc_folder_type (TNY_FOLDER (instance));
403                         if (type != TNY_FOLDER_TYPE_UNKNOWN) {
404                                 g_free (fname);
405                                 fname = g_strdup(modest_local_folder_info_get_type_display_name (type));
406                         }
407                 }
408
409                 /* Select the number to show: the unread or unsent messages */
410                 if ((type == TNY_FOLDER_TYPE_DRAFTS) || (type == TNY_FOLDER_TYPE_OUTBOX))
411                         number = all;
412                 else
413                         number = unread;
414                 
415                 /* Use bold font style if there are unread or unset messages */
416                 if (number > 0) {
417                         item_name = g_strdup_printf ("%s (%d)", fname, number);
418                         item_weight = 800;
419                 } else {
420                         item_name = g_strdup (fname);
421                         item_weight = 400;
422                 }
423                 
424         } else if (TNY_IS_ACCOUNT (instance)) {
425                 /* If it's a server account */
426                 if (modest_tny_account_is_virtual_local_folders (
427                                 TNY_ACCOUNT (instance))) {
428                         item_name = g_strdup (priv->local_account_name);
429                         item_weight = 800;
430                 } else if (modest_tny_account_is_memory_card_account (
431                                 TNY_ACCOUNT (instance))) {
432                         /* fname is only correct when the items are first 
433                          * added to the model, not when the account is 
434                          * changed later, so get the name from the account
435                          * instance: */
436                         item_name = g_strdup (tny_account_get_name (TNY_ACCOUNT (instance)));
437                         item_weight = 800;
438                 } else {
439                         item_name = g_strdup (fname);
440                         item_weight = 800;
441                 }
442         }
443         
444         if (!item_name)
445                 item_name = g_strdup ("unknown");
446                         
447         if (item_name && item_weight) {
448                 /* Set the name in the treeview cell: */
449                 g_object_set (rendobj,"text", item_name, "weight", item_weight, NULL);
450                 
451                 /* Notify display name observers */
452                 /* TODO: What listens for this signal, and how can it use only the new name? */
453                 if (G_OBJECT (priv->cur_folder_store) == instance) {
454                         g_signal_emit (G_OBJECT(self),
455                                                signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
456                                                item_name);
457                 }
458                 g_free (item_name);
459                 
460         }
461         
462         /* If it is a Memory card account, make sure that we have the correct name.
463          * This function will be trigerred again when the name has been retrieved: */
464         if (TNY_IS_STORE_ACCOUNT (instance) && 
465                 modest_tny_account_is_memory_card_account (TNY_ACCOUNT (instance))) {
466
467                 /* Get the account name asynchronously: */
468                 GetMmcAccountNameData *callback_data = 
469                         g_slice_new0(GetMmcAccountNameData);
470                 callback_data->self = self;
471
472                 const gchar *name = tny_account_get_name (TNY_ACCOUNT(instance));
473                 if (name)
474                         callback_data->previous_name = g_strdup (name); 
475
476                 modest_tny_account_get_mmc_account_name (TNY_STORE_ACCOUNT (instance), 
477                         on_get_mmc_account_name, callback_data);
478         }
479                         
480         g_object_unref (G_OBJECT (instance));
481         g_free (fname);
482 }
483
484 static void
485 icon_cell_data  (GtkTreeViewColumn *column,  GtkCellRenderer *renderer,
486                  GtkTreeModel *tree_model,  GtkTreeIter *iter, gpointer data)
487 {
488         GObject *rendobj = NULL, *instance = NULL;
489         GdkPixbuf *pixbuf = NULL;
490         TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
491         const gchar *account_id = NULL;
492         gboolean has_children;
493         
494         rendobj = G_OBJECT(renderer);
495         gtk_tree_model_get (tree_model, iter,
496                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
497                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
498                             -1);
499         has_children = gtk_tree_model_iter_has_child (tree_model, iter);
500
501         if (!instance) 
502                 return;
503
504         /* MERGE is not needed anymore as the folder now has the correct type jschmid */
505         /* We include the MERGE type here because it's used to create
506            the local OUTBOX folder */
507         if (type == TNY_FOLDER_TYPE_NORMAL || 
508             type == TNY_FOLDER_TYPE_UNKNOWN) {
509                 type = modest_tny_folder_guess_folder_type (TNY_FOLDER (instance));
510         }
511
512         switch (type) {
513         case TNY_FOLDER_TYPE_ROOT:
514                 if (TNY_IS_ACCOUNT (instance)) {
515                         
516                         if (modest_tny_account_is_virtual_local_folders (
517                                 TNY_ACCOUNT (instance))) {
518                                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_LOCAL_FOLDERS);
519                         }
520                         else {
521                                 account_id = tny_account_get_id (TNY_ACCOUNT (instance));
522                                 
523                                 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
524                                         pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_MMC);
525                                 else
526                                         pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_ACCOUNT);
527                         }
528                 }
529                 break;
530         case TNY_FOLDER_TYPE_INBOX:
531             pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_INBOX);
532             break;
533         case TNY_FOLDER_TYPE_OUTBOX:
534                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_OUTBOX);
535                 break;
536         case TNY_FOLDER_TYPE_JUNK:
537                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_JUNK);
538                 break;
539         case TNY_FOLDER_TYPE_SENT:
540                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_SENT);
541                 break;
542         case TNY_FOLDER_TYPE_TRASH:
543                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_TRASH);
544                 break;
545         case TNY_FOLDER_TYPE_DRAFTS:
546                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_DRAFTS);
547                 break;
548         case TNY_FOLDER_TYPE_NORMAL:
549         default:
550                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_NORMAL);
551                 break;
552         }
553         
554         g_object_unref (G_OBJECT (instance));
555
556         /* Set pixbuf */
557         g_object_set (rendobj, "pixbuf", pixbuf, NULL);
558         if (has_children && (pixbuf != NULL)) {
559                 GdkPixbuf *open_pixbuf, *closed_pixbuf;
560                 GdkPixbuf *open_emblem, *closed_emblem;
561                 open_pixbuf = gdk_pixbuf_copy (pixbuf);
562                 closed_pixbuf = gdk_pixbuf_copy (pixbuf);
563                 open_emblem = modest_platform_get_icon ("qgn_list_gene_fldr_exp");
564                 closed_emblem = modest_platform_get_icon ("qgn_list_gene_fldr_clp");
565
566                 if (open_emblem) {
567                         gdk_pixbuf_composite (open_emblem, open_pixbuf, 0, 0, 
568                                               MIN (gdk_pixbuf_get_width (open_emblem), 
569                                                    gdk_pixbuf_get_width (open_pixbuf)),
570                                               MIN (gdk_pixbuf_get_height (open_emblem), 
571                                                    gdk_pixbuf_get_height (open_pixbuf)),
572                                               0, 0, 1, 1, GDK_INTERP_NEAREST, 255);
573                         g_object_set (rendobj, "pixbuf-expander-open", open_pixbuf, NULL);
574                         g_object_unref (open_emblem);
575                 }
576                 if (closed_emblem) {
577                         gdk_pixbuf_composite (closed_emblem, closed_pixbuf, 0, 0, 
578                                               MIN (gdk_pixbuf_get_width (closed_emblem), 
579                                                    gdk_pixbuf_get_width (closed_pixbuf)),
580                                               MIN (gdk_pixbuf_get_height (closed_emblem), 
581                                                    gdk_pixbuf_get_height (closed_pixbuf)),
582                                               0, 0, 1, 1, GDK_INTERP_NEAREST, 255);
583                         g_object_set (rendobj, "pixbuf-expander-closed", closed_pixbuf, NULL);
584                         g_object_unref (closed_emblem);
585                 }
586                 if (closed_pixbuf)
587                         g_object_unref (closed_pixbuf);
588                 if (open_pixbuf)
589                         g_object_unref (open_pixbuf);
590         }
591
592         if (pixbuf != NULL)
593                 g_object_unref (pixbuf);
594 }
595
596 static void
597 add_columns (GtkWidget *treeview)
598 {
599         GtkTreeViewColumn *column;
600         GtkCellRenderer *renderer;
601         GtkTreeSelection *sel;
602
603         /* Create column */
604         column = gtk_tree_view_column_new ();   
605         
606         /* Set icon and text render function */
607         renderer = gtk_cell_renderer_pixbuf_new();
608         gtk_tree_view_column_pack_start (column, renderer, FALSE);
609         gtk_tree_view_column_set_cell_data_func(column, renderer,
610                                                 icon_cell_data, treeview, NULL);
611         
612         renderer = gtk_cell_renderer_text_new();
613         gtk_tree_view_column_pack_start (column, renderer, FALSE);
614         gtk_tree_view_column_set_cell_data_func(column, renderer,
615                                                 text_cell_data, treeview, NULL);
616         
617         /* Set selection mode */
618         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(treeview));
619         gtk_tree_selection_set_mode (sel, GTK_SELECTION_SINGLE);
620
621         /* Set treeview appearance */
622         gtk_tree_view_column_set_spacing (column, 2);
623         gtk_tree_view_column_set_resizable (column, TRUE);
624         gtk_tree_view_column_set_fixed_width (column, TRUE);            
625         gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(treeview), FALSE);
626         gtk_tree_view_set_enable_search (GTK_TREE_VIEW(treeview), FALSE);
627
628         /* Add column */
629         gtk_tree_view_append_column (GTK_TREE_VIEW(treeview),column);
630 }
631
632 static void
633 modest_folder_view_init (ModestFolderView *obj)
634 {
635         ModestFolderViewPrivate *priv;
636         ModestConf *conf;
637         
638         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
639         
640         priv->timer_expander = 0;
641         priv->account_store  = NULL;
642         priv->query          = NULL;
643         priv->style          = MODEST_FOLDER_VIEW_STYLE_SHOW_ALL;
644         priv->cur_folder_store   = NULL;
645         priv->visible_account_id = NULL;
646         priv->folder_to_select = NULL;
647
648         /* Initialize the local account name */
649         conf = modest_runtime_get_conf();
650         priv->local_account_name = modest_conf_get_string (conf, MODEST_CONF_DEVICE_NAME, NULL);
651
652         /* Init email clipboard */
653         priv->clipboard = modest_runtime_get_email_clipboard ();
654         priv->hidding_ids = NULL;
655         priv->n_selected = 0;
656         priv->reselect = FALSE;
657         priv->show_non_move = TRUE;
658
659         /* Build treeview */
660         add_columns (GTK_WIDGET (obj));
661
662         /* Setup drag and drop */
663         setup_drag_and_drop (GTK_TREE_VIEW(obj));
664
665         /* Connect signals */
666         g_signal_connect (G_OBJECT (obj), 
667                           "key-press-event", 
668                           G_CALLBACK (on_key_pressed), NULL);
669
670         /*
671          * Track changes in the local account name (in the device it
672          * will be the device name)
673          */
674         priv->notification_id = modest_conf_listen_to_namespace (conf, 
675                                                                  MODEST_CONF_NAMESPACE);
676         priv->conf_key_signal = g_signal_connect (G_OBJECT(conf), 
677                                                   "key_changed",
678                                                   G_CALLBACK(on_configuration_key_changed), 
679                                                   obj);
680 }
681
682 static void
683 tny_account_store_view_init (gpointer g, gpointer iface_data)
684 {
685         TnyAccountStoreViewIface *klass = (TnyAccountStoreViewIface *)g;
686
687         klass->set_account_store_func = modest_folder_view_set_account_store;
688
689         return;
690 }
691
692 static void
693 modest_folder_view_finalize (GObject *obj)
694 {
695         ModestFolderViewPrivate *priv;
696         GtkTreeSelection    *sel;
697         
698         g_return_if_fail (obj);
699         
700         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
701
702         if (priv->notification_id) {
703                 modest_conf_forget_namespace (modest_runtime_get_conf (),
704                                               MODEST_CONF_NAMESPACE,
705                                               priv->notification_id);
706         }
707
708         if (priv->timer_expander != 0) {
709                 g_source_remove (priv->timer_expander);
710                 priv->timer_expander = 0;
711         }
712
713         if (priv->account_store) {
714                 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
715                                              priv->account_inserted_signal);
716                 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
717                                              priv->account_removed_signal);
718                 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
719                                              priv->account_changed_signal);
720                 g_object_unref (G_OBJECT(priv->account_store));
721                 priv->account_store = NULL;
722         }
723
724         if (priv->query) {
725                 g_object_unref (G_OBJECT (priv->query));
726                 priv->query = NULL;
727         }
728
729         if (priv->folder_to_select) {
730                 g_object_unref (G_OBJECT(priv->folder_to_select));
731                 priv->folder_to_select = NULL;
732         }
733    
734         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(obj));
735         if (sel)
736                 g_signal_handler_disconnect (G_OBJECT(sel), priv->changed_signal);
737
738         g_free (priv->local_account_name);
739         g_free (priv->visible_account_id);
740         
741         if (priv->conf_key_signal) {
742                 g_signal_handler_disconnect (modest_runtime_get_conf (),
743                                              priv->conf_key_signal);
744                 priv->conf_key_signal = 0;
745         }
746
747         if (priv->cur_folder_store) {
748                 if (TNY_IS_FOLDER(priv->cur_folder_store))
749                         tny_folder_sync (TNY_FOLDER(priv->cur_folder_store), FALSE, NULL);
750                         /* FALSE --> expunge the message */
751
752                 g_object_unref (priv->cur_folder_store);
753                 priv->cur_folder_store = NULL;
754         }
755
756         /* Clear hidding array created by cut operation */
757         _clear_hidding_filter (MODEST_FOLDER_VIEW (obj));
758
759         G_OBJECT_CLASS(parent_class)->finalize (obj);
760 }
761
762
763 static void
764 modest_folder_view_set_account_store (TnyAccountStoreView *self, TnyAccountStore *account_store)
765 {
766         ModestFolderViewPrivate *priv;
767         TnyDevice *device;
768
769         g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
770         g_return_if_fail (TNY_IS_ACCOUNT_STORE (account_store));
771
772         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
773         device = tny_account_store_get_device (account_store);
774
775         if (G_UNLIKELY (priv->account_store)) {
776
777                 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
778                                                    priv->account_inserted_signal))
779                         g_signal_handler_disconnect (G_OBJECT (priv->account_store),
780                                                      priv->account_inserted_signal);
781                 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store), 
782                                                    priv->account_removed_signal))
783                         g_signal_handler_disconnect (G_OBJECT (priv->account_store), 
784                                                      priv->account_removed_signal);
785                 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store), 
786                                                    priv->account_changed_signal))
787                         g_signal_handler_disconnect (G_OBJECT (priv->account_store), 
788                                                      priv->account_changed_signal);
789                 g_object_unref (G_OBJECT (priv->account_store));
790         }
791
792         priv->account_store = g_object_ref (G_OBJECT (account_store));
793
794         priv->account_removed_signal = 
795                 g_signal_connect (G_OBJECT(account_store), "account_removed",
796                                   G_CALLBACK (on_account_removed), self);
797
798         priv->account_inserted_signal =
799                 g_signal_connect (G_OBJECT(account_store), "account_inserted",
800                                   G_CALLBACK (on_account_inserted), self);
801
802         priv->account_changed_signal =
803                 g_signal_connect (G_OBJECT(account_store), "account_changed",
804                                   G_CALLBACK (on_account_changed), self);
805
806         modest_folder_view_update_model (MODEST_FOLDER_VIEW (self), account_store);
807         
808         g_object_unref (G_OBJECT (device));
809 }
810
811 static void
812 on_account_inserted (TnyAccountStore *account_store, 
813                      TnyAccount *account,
814                      gpointer user_data)
815 {
816         ModestFolderViewPrivate *priv;
817         GtkTreeModel *sort_model, *filter_model;
818
819         /* Ignore transport account insertions, we're not showing them
820            in the folder view */
821         if (TNY_IS_TRANSPORT_ACCOUNT (account))
822                 return;
823
824         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (user_data);
825
826         /* If we're adding a new account, and there is no previous
827            one, we need to select the visible server account */
828         if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE &&
829             !priv->visible_account_id)
830                 modest_widget_memory_restore (modest_runtime_get_conf(), 
831                                               G_OBJECT (user_data),
832                                               MODEST_CONF_FOLDER_VIEW_KEY);
833
834         /* Get the inner model */
835         filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (user_data));
836         sort_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
837
838         /* Insert the account in the model */
839         tny_list_append (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
840                          G_OBJECT (account));
841 }
842
843
844 static void
845 on_account_changed (TnyAccountStore *account_store, TnyAccount *tny_account,
846                     gpointer user_data)
847 {
848         /* do nothing */
849 }
850
851
852
853 static void
854 on_account_removed (TnyAccountStore *account_store, 
855                     TnyAccount *account,
856                     gpointer user_data)
857 {
858         ModestFolderView *self = NULL;
859         ModestFolderViewPrivate *priv;
860         GtkTreeModel *sort_model, *filter_model;
861
862         /* Ignore transport account removals, we're not showing them
863            in the folder view */
864         if (TNY_IS_TRANSPORT_ACCOUNT (account))
865                 return;
866
867         g_print ("--------------------- FOLDER ---------------\n");
868
869         self = MODEST_FOLDER_VIEW (user_data);
870         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
871
872         /* TODO: invalidate the cur_folder_* and folder_to_select things */
873
874         /* Remove the account from the model */
875         filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
876         sort_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
877         tny_list_remove (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
878                          G_OBJECT (account));
879
880         /* If the removed account is the currently viewed one then
881            clear the configuration value. The new visible account will be the default account */
882         if (priv->visible_account_id &&
883             !strcmp (priv->visible_account_id, tny_account_get_id (account))) {
884
885                 /* Clear the current visible account_id */
886                 modest_folder_view_set_account_id_of_visible_server_account (self, NULL);
887
888                 /* Call the restore method, this will set the new visible account */
889                 modest_widget_memory_restore (modest_runtime_get_conf(), G_OBJECT(self),
890                                               MODEST_CONF_FOLDER_VIEW_KEY);
891         }
892
893         /* Select the INBOX */
894         modest_folder_view_select_first_inbox_or_local (self);
895 }
896
897 void
898 modest_folder_view_set_title (ModestFolderView *self, const gchar *title)
899 {
900         GtkTreeViewColumn *col;
901         
902         g_return_if_fail (self);
903
904         col = gtk_tree_view_get_column (GTK_TREE_VIEW(self), 0);
905         if (!col) {
906                 g_printerr ("modest: failed get column for title\n");
907                 return;
908         }
909
910         gtk_tree_view_column_set_title (col, title);
911         gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self),
912                                            title != NULL);
913 }
914
915 static gboolean
916 modest_folder_view_on_map (ModestFolderView *self, 
917                            GdkEventExpose *event,
918                            gpointer data)
919 {
920         ModestFolderViewPrivate *priv;
921
922         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
923
924         /* This won't happen often */
925         if (G_UNLIKELY (priv->reselect)) {
926                 /* Select the first inbox or the local account if not found */
927
928                 /* TODO: this could cause a lock at startup, so we
929                    comment it for the moment. We know that this will
930                    be a bug, because the INBOX is not selected, but we
931                    need to rewrite some parts of Modest to avoid the
932                    deathlock situation */
933                 /* TODO: check if this is still the case */
934                 priv->reselect = FALSE;
935                 modest_folder_view_select_first_inbox_or_local (self);
936                 /* Notify the display name observers */
937                 g_signal_emit (G_OBJECT(self),
938                                signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
939                                NULL);
940         }
941
942         expand_root_items (self); 
943
944         return FALSE;
945 }
946
947 GtkWidget*
948 modest_folder_view_new (TnyFolderStoreQuery *query)
949 {
950         GObject *self;
951         ModestFolderViewPrivate *priv;
952         GtkTreeSelection *sel;
953         
954         self = G_OBJECT (g_object_new (MODEST_TYPE_FOLDER_VIEW, NULL));
955         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
956
957         if (query)
958                 priv->query = g_object_ref (query);
959         
960         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
961         priv->changed_signal = g_signal_connect (sel, "changed",
962                                                  G_CALLBACK (on_selection_changed), self);
963
964         g_signal_connect (self, "expose-event", G_CALLBACK (modest_folder_view_on_map), NULL);
965
966         return GTK_WIDGET(self);
967 }
968
969 /* this feels dirty; any other way to expand all the root items? */
970 static void
971 expand_root_items (ModestFolderView *self)
972 {
973         GtkTreePath *path;
974         path = gtk_tree_path_new_first ();
975
976         /* all folders should have child items, so.. */
977         while (gtk_tree_view_expand_row (GTK_TREE_VIEW(self), path, FALSE))
978                 gtk_tree_path_next (path);
979         
980         gtk_tree_path_free (path);
981 }
982
983 /*
984  * We use this function to implement the
985  * MODEST_FOLDER_VIEW_STYLE_SHOW_ONE style. We only show the default
986  * account in this case, and the local folders.
987  */
988 static gboolean 
989 filter_row (GtkTreeModel *model,
990             GtkTreeIter *iter,
991             gpointer data)
992 {
993         ModestFolderViewPrivate *priv;
994         gboolean retval = TRUE;
995         TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
996         GObject *instance = NULL;
997         const gchar *id = NULL;
998         guint i;
999         gboolean found = FALSE;
1000         gboolean cleared = FALSE;
1001
1002         g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (data), FALSE);
1003         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (data);
1004
1005         gtk_tree_model_get (model, iter,
1006                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
1007                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
1008                             -1);
1009
1010         /* Do not show if there is no instance, this could indeed
1011            happen when the model is being modified while it's being
1012            drawn. This could occur for example when moving folders
1013            using drag&drop */
1014         if (!instance)
1015                 return FALSE;
1016
1017         if (type == TNY_FOLDER_TYPE_ROOT) {
1018                 /* TNY_FOLDER_TYPE_ROOT means that the instance is an
1019                    account instead of a folder. */
1020                 if (TNY_IS_ACCOUNT (instance)) {
1021                         TnyAccount *acc = TNY_ACCOUNT (instance);
1022                         const gchar *account_id = tny_account_get_id (acc);
1023         
1024                         /* If it isn't a special folder, 
1025                          * don't show it unless it is the visible account: */
1026                         if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE &&
1027                             !modest_tny_account_is_virtual_local_folders (acc) &&
1028                             strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
1029                                 
1030                                 /* Show only the visible account id */
1031                                 if (priv->visible_account_id) {
1032                                         if (strcmp (account_id, priv->visible_account_id))
1033                                                 retval = FALSE;
1034                                 } else {
1035                                         retval = FALSE;
1036                                 }                               
1037                         }
1038                         
1039                         /* Never show these to the user. They are merged into one folder 
1040                          * in the local-folders account instead: */
1041                         if (retval && MODEST_IS_TNY_OUTBOX_ACCOUNT (acc))
1042                                 retval = FALSE;
1043                 }
1044         }
1045
1046         /* Check hiding (if necessary) */
1047         cleared = modest_email_clipboard_cleared (priv->clipboard);            
1048         if ((retval) && (!cleared) && (TNY_IS_FOLDER (instance))) {
1049                 id = tny_folder_get_id (TNY_FOLDER(instance));
1050                 if (priv->hidding_ids != NULL)
1051                         for (i=0; i < priv->n_selected && !found; i++)
1052                                 if (priv->hidding_ids[i] != NULL && id != NULL)
1053                                         found = (!strcmp (priv->hidding_ids[i], id));
1054                 
1055                 retval = !found;
1056         }
1057         
1058         
1059         /* If this is a move to dialog, hide Sent, Outbox and Drafts
1060         folder as no message can be move there according to UI specs */
1061         if (!priv->show_non_move)
1062         {
1063                 switch (type)
1064                 {
1065                         case TNY_FOLDER_TYPE_OUTBOX:
1066                         case TNY_FOLDER_TYPE_SENT:
1067                         case TNY_FOLDER_TYPE_DRAFTS:
1068                                 retval = FALSE;
1069                                 break;
1070                         case TNY_FOLDER_TYPE_UNKNOWN:
1071                         case TNY_FOLDER_TYPE_NORMAL:
1072                                 type = modest_tny_folder_guess_folder_type(TNY_FOLDER(instance));
1073                                 if (type == TNY_FOLDER_TYPE_OUTBOX || type == TNY_FOLDER_TYPE_SENT
1074                                                 || type == TNY_FOLDER_TYPE_DRAFTS)
1075                                 {
1076                                         retval = FALSE;
1077                                 }
1078                                 break;
1079                         default:
1080                                 break;  
1081                 }       
1082         }
1083         
1084         /* Free */
1085         g_object_unref (instance);
1086
1087         return retval;
1088 }
1089
1090
1091 gboolean
1092 modest_folder_view_update_model (ModestFolderView *self,
1093                                  TnyAccountStore *account_store)
1094 {
1095         ModestFolderViewPrivate *priv;
1096         GtkTreeModel *model /* , *old_model */;
1097         /* TnyAccount *local_account; */
1098         TnyList *model_as_list;
1099
1100         g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (self), FALSE);
1101         g_return_val_if_fail (account_store, FALSE);
1102
1103         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1104         
1105         /* Notify that there is no folder selected */
1106         g_signal_emit (G_OBJECT(self), 
1107                        signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
1108                        NULL, FALSE);
1109         if (priv->cur_folder_store) {
1110                 g_object_unref (priv->cur_folder_store);
1111                 priv->cur_folder_store = NULL;
1112         }
1113
1114         /* FIXME: the local accounts are not shown when the query
1115            selects only the subscribed folders. */
1116 /*      model        = tny_gtk_folder_store_tree_model_new (TRUE, priv->query); */
1117         model        = tny_gtk_folder_store_tree_model_new (NULL);
1118         
1119         /* Deal with the model via its TnyList Interface,
1120          * filling the TnyList via a get_accounts() call: */
1121         model_as_list = TNY_LIST(model);
1122
1123         /* Get the accounts: */
1124         tny_account_store_get_accounts (TNY_ACCOUNT_STORE(account_store),
1125                                         model_as_list,
1126                                         TNY_ACCOUNT_STORE_STORE_ACCOUNTS);
1127         g_object_unref (model_as_list);
1128         model_as_list = NULL;   
1129                                                      
1130         GtkTreeModel *filter_model = NULL, *sortable = NULL;
1131
1132         sortable = gtk_tree_model_sort_new_with_model (model);
1133         gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
1134                                               TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, 
1135                                               GTK_SORT_ASCENDING);
1136         gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
1137                                          TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
1138                                          cmp_rows, NULL, NULL);
1139
1140         /* Create filter model */
1141         filter_model = gtk_tree_model_filter_new (sortable, NULL);
1142         gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
1143                                                 filter_row,
1144                                                 self,
1145                                                 NULL);
1146
1147         /* Set new model */
1148         gtk_tree_view_set_model (GTK_TREE_VIEW(self), filter_model);
1149         g_signal_connect (G_OBJECT(filter_model), "row-changed",
1150                           (GCallback)on_row_changed_maybe_select_folder, self);
1151         g_signal_connect (G_OBJECT(filter_model), "row-inserted",
1152                           (GCallback)on_row_changed_maybe_select_folder, self);
1153
1154
1155         g_object_unref (model);
1156         g_object_unref (filter_model);          
1157         g_object_unref (sortable);
1158         
1159         /* Force a reselection of the INBOX next time the widget is shown */
1160         priv->reselect = TRUE;
1161                         
1162         return TRUE;
1163 }
1164
1165
1166 static void
1167 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
1168 {
1169         GtkTreeModel            *model;
1170         TnyFolderStore          *folder = NULL;
1171         GtkTreeIter             iter;
1172         ModestFolderView        *tree_view;
1173         ModestFolderViewPrivate *priv;
1174
1175         g_return_if_fail (sel);
1176         g_return_if_fail (user_data);
1177         
1178         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
1179
1180         if(!gtk_tree_selection_get_selected (sel, &model, &iter))
1181                 return;
1182
1183         /* Notify the display name observers */
1184         g_signal_emit (G_OBJECT(user_data),
1185                        signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
1186                        NULL);
1187
1188         tree_view = MODEST_FOLDER_VIEW (user_data);
1189         gtk_tree_model_get (model, &iter,
1190                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
1191                             -1);
1192
1193         /* If the folder is the same do not notify */
1194         if (priv->cur_folder_store == folder && folder) {
1195                 g_object_unref (folder);
1196                 return;
1197         }
1198         
1199         /* Current folder was unselected */
1200         if (priv->cur_folder_store) {
1201                 g_signal_emit (G_OBJECT(tree_view), signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
1202                        priv->cur_folder_store, FALSE);
1203
1204                 if (TNY_IS_FOLDER(priv->cur_folder_store))
1205                         tny_folder_sync_async (TNY_FOLDER(priv->cur_folder_store),
1206                                                FALSE, NULL, NULL, NULL);
1207                 /* FALSE --> don't expunge the messages */
1208
1209                 g_object_unref (priv->cur_folder_store);
1210                 priv->cur_folder_store = NULL;
1211         }
1212
1213         /* New current references */
1214         priv->cur_folder_store = folder;
1215
1216         /* New folder has been selected */
1217         g_signal_emit (G_OBJECT(tree_view),
1218                        signals[FOLDER_SELECTION_CHANGED_SIGNAL],
1219                        0, priv->cur_folder_store, TRUE);
1220 }
1221
1222 TnyFolderStore *
1223 modest_folder_view_get_selected (ModestFolderView *self)
1224 {
1225         ModestFolderViewPrivate *priv;
1226
1227         g_return_val_if_fail (self, NULL);
1228         
1229         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1230         if (priv->cur_folder_store)
1231                 g_object_ref (priv->cur_folder_store);
1232
1233         return priv->cur_folder_store;
1234 }
1235
1236 static gint
1237 get_cmp_rows_type_pos (GObject *folder)
1238 {
1239         /* Remote accounts -> Local account -> MMC account .*/
1240         /* 0, 1, 2 */
1241         
1242         if (TNY_IS_ACCOUNT (folder) && 
1243                 modest_tny_account_is_virtual_local_folders (
1244                         TNY_ACCOUNT (folder))) {
1245                 return 1;
1246         } else if (TNY_IS_ACCOUNT (folder)) {
1247                 TnyAccount *account = TNY_ACCOUNT (folder);
1248                 const gchar *account_id = tny_account_get_id (account);
1249                 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
1250                         return 2;
1251                 else
1252                         return 0;
1253         }
1254         else {
1255                 printf ("DEBUG: %s: unexpected type.\n", __FUNCTION__);
1256                 return -1; /* Should never happen */
1257         }
1258 }
1259
1260 static gint
1261 get_cmp_subfolder_type_pos (TnyFolderType t)
1262 {
1263         /* Inbox, Outbox, Drafts, Sent, User */
1264         /* 0, 1, 2, 3, 4 */
1265
1266         switch (t) {
1267         case TNY_FOLDER_TYPE_INBOX:
1268                 return 0;
1269                 break;
1270         case TNY_FOLDER_TYPE_OUTBOX:
1271                 return 1;
1272                 break;
1273         case TNY_FOLDER_TYPE_DRAFTS:
1274                 return 2;
1275                 break;
1276         case TNY_FOLDER_TYPE_SENT:
1277                 return 3;
1278                 break;
1279         default:
1280                 return 4;
1281         }
1282 }
1283
1284 /*
1285  * This function orders the mail accounts according to these rules:
1286  * 1st - remote accounts
1287  * 2nd - local account
1288  * 3rd - MMC account
1289  */
1290 static gint
1291 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1292           gpointer user_data)
1293 {
1294         gint cmp = 0;
1295         gchar *name1 = NULL;
1296         gchar *name2 = NULL;
1297         TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1298         TnyFolderType type2 = TNY_FOLDER_TYPE_UNKNOWN;
1299         GObject *folder1 = NULL;
1300         GObject *folder2 = NULL;
1301
1302         gtk_tree_model_get (tree_model, iter1,
1303                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name1,
1304                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
1305                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder1,
1306                             -1);
1307         gtk_tree_model_get (tree_model, iter2,
1308                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name2,
1309                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type2,
1310                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder2,
1311                             -1);
1312
1313         /* Return if we get no folder. This could happen when folder
1314            operations are happening. The model is updated after the
1315            folder copy/move actually occurs, so there could be
1316            situations where the model to be drawn is not correct */
1317         if (!folder1 || !folder2)
1318                 goto finish;
1319
1320         if (type == TNY_FOLDER_TYPE_ROOT) {
1321                 /* Compare the types, so that 
1322                  * Remote accounts -> Local account -> MMC account .*/
1323                 const gint pos1 = get_cmp_rows_type_pos (folder1);
1324                 const gint pos2 = get_cmp_rows_type_pos (folder2);
1325                 /* printf ("DEBUG: %s:\n  type1=%s, pos1=%d\n  type2=%s, pos2=%d\n", 
1326                         __FUNCTION__, G_OBJECT_TYPE_NAME(folder1), pos1, G_OBJECT_TYPE_NAME(folder2), pos2); */
1327                 if (pos1 <  pos2)
1328                         cmp = -1;
1329                 else if (pos1 > pos2)
1330                         cmp = 1;
1331                 else {
1332                         /* Compare items of the same type: */
1333                         
1334                         TnyAccount *account1 = NULL;
1335                         if (TNY_IS_ACCOUNT (folder1))
1336                                 account1 = TNY_ACCOUNT (folder1);
1337                                 
1338                         TnyAccount *account2 = NULL;
1339                         if (TNY_IS_ACCOUNT (folder2))
1340                                 account2 = TNY_ACCOUNT (folder2);
1341                                 
1342                         const gchar *account_id = account1 ? tny_account_get_id (account1) : NULL;
1343                         const gchar *account_id2 = account2 ? tny_account_get_id (account2) : NULL;
1344         
1345                         if (!account_id && !account_id2) {
1346                                 cmp = 0;
1347                         } else if (!account_id) {
1348                                 cmp = -1;
1349                         } else if (!account_id2) {
1350                                 cmp = +1;
1351                         } else if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
1352                                 cmp = +1;
1353                         } else {
1354                                 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
1355                         }
1356                 }
1357         } else {
1358                 gint cmp1 = 0, cmp2 = 0;
1359                 /* get the parent to know if it's a local folder */
1360
1361                 GtkTreeIter parent;
1362                 gboolean has_parent;
1363                 has_parent = gtk_tree_model_iter_parent (tree_model, &parent, iter1);
1364                 if (has_parent) {
1365                         GObject *parent_folder;
1366                         TnyFolderType parent_type = TNY_FOLDER_TYPE_UNKNOWN;
1367                         gtk_tree_model_get (tree_model, &parent, 
1368                                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &parent_type,
1369                                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &parent_folder,
1370                                             -1);
1371                         if ((parent_type == TNY_FOLDER_TYPE_ROOT) &&
1372                             TNY_IS_ACCOUNT (parent_folder) &&
1373                             modest_tny_account_is_virtual_local_folders (TNY_ACCOUNT (parent_folder))) {
1374                                 cmp1 = get_cmp_subfolder_type_pos (modest_tny_folder_get_local_or_mmc_folder_type (TNY_FOLDER (folder1)));
1375                                 cmp2 = get_cmp_subfolder_type_pos (modest_tny_folder_get_local_or_mmc_folder_type (TNY_FOLDER (folder2)));
1376                         }
1377                         g_object_unref (parent_folder);
1378                 }
1379
1380                 /* if they are not local folders */
1381                 if (cmp1 == cmp2) {
1382                         cmp1 = get_cmp_subfolder_type_pos (tny_folder_get_folder_type (TNY_FOLDER (folder1)));
1383                         cmp2 = get_cmp_subfolder_type_pos (tny_folder_get_folder_type (TNY_FOLDER (folder2)));
1384                 }
1385
1386                 if (cmp1 == cmp2)
1387                         cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
1388                 else 
1389                         cmp = (cmp1 - cmp2);
1390         }
1391
1392 finish: 
1393         if (folder1)
1394                 g_object_unref(G_OBJECT(folder1));
1395         if (folder2)
1396                 g_object_unref(G_OBJECT(folder2));
1397
1398         g_free (name1);
1399         g_free (name2);
1400
1401         return cmp;     
1402 }
1403
1404 /*****************************************************************************/
1405 /*                        DRAG and DROP stuff                                */
1406 /*****************************************************************************/
1407
1408 /*
1409  * This function fills the #GtkSelectionData with the row and the
1410  * model that has been dragged. It's called when this widget is a
1411  * source for dnd after the event drop happened
1412  */
1413 static void
1414 on_drag_data_get (GtkWidget *widget, 
1415                   GdkDragContext *context, 
1416                   GtkSelectionData *selection_data, 
1417                   guint info, 
1418                   guint time, 
1419                   gpointer data)
1420 {
1421         GtkTreeSelection *selection;
1422         GtkTreeModel *model;
1423         GtkTreeIter iter;
1424         GtkTreePath *source_row;
1425
1426         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1427         gtk_tree_selection_get_selected (selection, &model, &iter);
1428         source_row = gtk_tree_model_get_path (model, &iter);
1429
1430         gtk_tree_set_row_drag_data (selection_data,
1431                                     model,
1432                                     source_row);
1433
1434         gtk_tree_path_free (source_row);
1435 }
1436
1437 typedef struct _DndHelper {
1438         gboolean delete_source;
1439         GtkTreePath *source_row;
1440         GdkDragContext *context;
1441         guint time;
1442 } DndHelper;
1443
1444
1445 /*
1446  * This function is the callback of the
1447  * modest_mail_operation_xfer_msgs () and
1448  * modest_mail_operation_xfer_folder() calls. We check here if the
1449  * message/folder was correctly asynchronously transferred. The reason
1450  * to use the same callback is that the code is the same, it only has
1451  * to check that the operation went fine and then finalize the drag
1452  * and drop action
1453  */
1454 static void
1455 on_progress_changed (ModestMailOperation *mail_op, 
1456                      ModestMailOperationState *state,
1457                      gpointer user_data)
1458 {
1459         gboolean success;
1460         DndHelper *helper;
1461
1462         helper = (DndHelper *) user_data;
1463
1464         if (!state->finished)
1465                 return;
1466
1467         if (state->status == MODEST_MAIL_OPERATION_STATUS_SUCCESS) {
1468                 success = TRUE;
1469         } else {
1470                 success = FALSE;
1471         }
1472
1473         /* Notify the drag source. Never call delete, the monitor will
1474            do the job if needed */
1475         gtk_drag_finish (helper->context, success, FALSE, helper->time);
1476
1477         /* Free the helper */
1478         gtk_tree_path_free (helper->source_row);        
1479         g_slice_free (DndHelper, helper);
1480 }
1481
1482
1483 /* get the folder for the row the treepath refers to. */
1484 /* folder must be unref'd */
1485 static TnyFolder*
1486 tree_path_to_folder (GtkTreeModel *model, GtkTreePath *path)
1487 {
1488         GtkTreeIter iter;
1489         TnyFolder *folder = NULL;
1490         
1491         if (gtk_tree_model_get_iter (model,&iter, path))
1492                 gtk_tree_model_get (model, &iter,
1493                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
1494                                     -1);
1495         return folder;
1496 }
1497
1498 static void 
1499 show_banner_move_target_error ()
1500 {
1501         ModestWindow *main_window;
1502
1503         main_window = modest_window_mgr_get_main_window(
1504                         modest_runtime_get_window_mgr());
1505                                 
1506         hildon_banner_show_information(GTK_WIDGET(main_window),
1507                         NULL, _("mail_in_ui_folder_move_target_error"));
1508 }
1509
1510 /*
1511  * This function is used by drag_data_received_cb to manage drag and
1512  * drop of a header, i.e, and drag from the header view to the folder
1513  * view.
1514  */
1515 static void
1516 drag_and_drop_from_header_view (GtkTreeModel *source_model,
1517                                 GtkTreeModel *dest_model,
1518                                 GtkTreePath  *dest_row,
1519                                 DndHelper    *helper)
1520 {
1521         TnyList *headers = NULL;
1522         TnyHeader *header = NULL;
1523         TnyFolder *folder = NULL;
1524         ModestMailOperation *mail_op = NULL;
1525         GtkTreeIter source_iter;
1526         ModestWindowMgr *mgr = NULL;
1527
1528         g_return_if_fail (GTK_IS_TREE_MODEL(source_model));
1529         g_return_if_fail (GTK_IS_TREE_MODEL(dest_model));
1530         g_return_if_fail (dest_row);
1531         g_return_if_fail (helper);
1532
1533         /* Get header */
1534         gtk_tree_model_get_iter (source_model, &source_iter, helper->source_row);
1535         gtk_tree_model_get (source_model, &source_iter, 
1536                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, 
1537                             &header, -1);
1538         if (!TNY_IS_HEADER(header)) {
1539                 g_warning ("BUG: %s could not get a valid header", __FUNCTION__);
1540                 goto cleanup;
1541         }
1542         
1543         /* Check if the selected message is in msg-view. If it is than
1544          * do not enable drag&drop on that. */
1545         mgr = modest_runtime_get_window_mgr ();
1546         if (modest_window_mgr_find_registered_header(mgr, header, NULL))
1547                 goto cleanup;
1548         
1549         /* Get Folder */
1550         folder = tree_path_to_folder (dest_model, dest_row);
1551         if (!TNY_IS_FOLDER(folder)) {
1552                 g_warning ("BUG: %s could not get a valid folder", __FUNCTION__);
1553                 show_banner_move_target_error();
1554                 goto cleanup;
1555         }
1556         if (modest_tny_folder_get_rules(folder) & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE) {
1557                 g_debug ("folder rules: cannot write to that folder");
1558                 goto cleanup;
1559         }
1560         
1561
1562         /* Transfer message */
1563         mail_op = modest_mail_operation_new_with_error_handling (MODEST_MAIL_OPERATION_TYPE_RECEIVE, 
1564                                                                  NULL,
1565                                                                  modest_ui_actions_move_folder_error_handler,
1566                                                                  NULL);
1567         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1568                                          mail_op);
1569         g_signal_connect (G_OBJECT (mail_op), "progress-changed",
1570                           G_CALLBACK (on_progress_changed), helper);
1571
1572         headers = tny_simple_list_new ();
1573         tny_list_append (headers, G_OBJECT (header));
1574         modest_mail_operation_xfer_msgs (mail_op, 
1575                                          headers, 
1576                                          folder, 
1577                                          helper->delete_source, 
1578                                          NULL, NULL);
1579         
1580         /* Frees */
1581 cleanup:
1582         if (G_IS_OBJECT(mail_op))
1583                 g_object_unref (G_OBJECT (mail_op));
1584         if (G_IS_OBJECT(header))
1585                 g_object_unref (G_OBJECT (header));
1586         if (G_IS_OBJECT(folder))
1587                 g_object_unref (G_OBJECT (folder));
1588         if (G_IS_OBJECT(headers))
1589                 g_object_unref (headers);
1590 }
1591
1592 /*
1593  * This function is used by drag_data_received_cb to manage drag and
1594  * drop of a folder, i.e, and drag from the folder view to the same
1595  * folder view.
1596  */
1597 static void
1598 drag_and_drop_from_folder_view (GtkTreeModel     *source_model,
1599                                 GtkTreeModel     *dest_model,
1600                                 GtkTreePath      *dest_row,
1601                                 GtkSelectionData *selection_data,
1602                                 DndHelper        *helper)
1603 {
1604         ModestMailOperation *mail_op = NULL;
1605         GtkTreeIter parent_iter, iter;
1606         TnyFolderStore *parent_folder = NULL;
1607         TnyFolder *folder = NULL;
1608         gboolean forbidden = TRUE;
1609
1610         /* check the folder rules for the destination */
1611         folder = tree_path_to_folder (dest_model, dest_row);
1612         if (folder) {
1613                 ModestTnyFolderRules rules =
1614                         modest_tny_folder_get_rules (folder);
1615                 forbidden = rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE;
1616                 if (forbidden)
1617                         g_debug ("folder rules: cannot write to that folder");
1618                 g_object_unref (folder);
1619         }
1620         
1621         if (!forbidden) {
1622                 /* check the folder rules for the source */
1623                 folder = tree_path_to_folder (source_model, helper->source_row);
1624                 if (folder) {
1625                         ModestTnyFolderRules rules =
1626                                 modest_tny_folder_get_rules (folder);
1627                         forbidden = rules & MODEST_FOLDER_RULES_FOLDER_NON_MOVEABLE;
1628                         if (forbidden)
1629                                 g_debug ("folder rules: cannot move that folder");
1630                         g_object_unref (folder);
1631                 }
1632         }
1633
1634         
1635         /* Check if the drag is possible */
1636         if (forbidden || !gtk_tree_path_compare (helper->source_row, dest_row)) {
1637
1638                 gtk_drag_finish (helper->context, FALSE, FALSE, helper->time);
1639                 gtk_tree_path_free (helper->source_row);        
1640                 g_slice_free (DndHelper, helper);
1641                 return;
1642         }
1643
1644         /* Get data */
1645         gtk_tree_model_get_iter (source_model, &parent_iter, dest_row);
1646         gtk_tree_model_get_iter (source_model, &iter, helper->source_row);
1647         gtk_tree_model_get (source_model, &parent_iter, 
1648                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, 
1649                             &parent_folder, -1);
1650         gtk_tree_model_get (source_model, &iter,
1651                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
1652                             &folder, -1);
1653
1654         /* Offer the connection dialog if necessary, for the destination parent folder and source folder: */
1655         if (modest_platform_connect_and_wait_if_network_folderstore (NULL, parent_folder) && 
1656                 modest_platform_connect_and_wait_if_network_folderstore (NULL, TNY_FOLDER_STORE (folder))) {
1657                 /* Do the mail operation */
1658                 mail_op = modest_mail_operation_new_with_error_handling (MODEST_MAIL_OPERATION_TYPE_RECEIVE, 
1659                                                                  NULL,
1660                                                                  modest_ui_actions_move_folder_error_handler,
1661                                                                  NULL);
1662                 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (), 
1663                                          mail_op);
1664                 g_signal_connect (G_OBJECT (mail_op), "progress-changed",
1665                                   G_CALLBACK (on_progress_changed), helper);
1666
1667                 modest_mail_operation_xfer_folder (mail_op, 
1668                                            folder, 
1669                                            parent_folder,
1670                                            helper->delete_source,
1671                                            NULL,
1672                                            NULL);
1673
1674                 g_object_unref (G_OBJECT (mail_op));    
1675         }
1676         
1677         /* Frees */
1678         g_object_unref (G_OBJECT (parent_folder));
1679         g_object_unref (G_OBJECT (folder));
1680 }
1681
1682 /*
1683  * This function receives the data set by the "drag-data-get" signal
1684  * handler. This information comes within the #GtkSelectionData. This
1685  * function will manage both the drags of folders of the treeview and
1686  * drags of headers of the header view widget.
1687  */
1688 static void 
1689 on_drag_data_received (GtkWidget *widget, 
1690                        GdkDragContext *context, 
1691                        gint x, 
1692                        gint y, 
1693                        GtkSelectionData *selection_data, 
1694                        guint target_type, 
1695                        guint time, 
1696                        gpointer data)
1697 {
1698         GtkWidget *source_widget;
1699         GtkTreeModel *dest_model, *source_model;
1700         GtkTreePath *source_row, *dest_row;
1701         GtkTreeViewDropPosition pos;
1702         gboolean success = FALSE, delete_source = FALSE;
1703         DndHelper *helper = NULL; 
1704
1705         /* Do not allow further process */
1706         g_signal_stop_emission_by_name (widget, "drag-data-received");
1707         source_widget = gtk_drag_get_source_widget (context);
1708
1709         /* Get the action */
1710         if (context->action == GDK_ACTION_MOVE) {
1711                 delete_source = TRUE;
1712
1713                 /* Notify that there is no folder selected. We need to
1714                    do this in order to update the headers view (and
1715                    its monitors, because when moving, the old folder
1716                    won't longer exist. We can not wait for the end of
1717                    the operation, because the operation won't start if
1718                    the folder is in use */
1719                 if (source_widget == widget) {
1720                         ModestFolderViewPrivate *priv;
1721
1722                         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
1723                         if (priv->cur_folder_store) {
1724                                 g_object_unref (priv->cur_folder_store);
1725                                 priv->cur_folder_store = NULL;
1726                         }
1727
1728                         g_signal_emit (G_OBJECT (widget), 
1729                                        signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0, NULL, FALSE);
1730                 }
1731         }
1732
1733         /* Check if the get_data failed */
1734         if (selection_data == NULL || selection_data->length < 0)
1735                 gtk_drag_finish (context, success, FALSE, time);
1736
1737         /* Get the models */
1738         gtk_tree_get_row_drag_data (selection_data,
1739                                     &source_model,
1740                                     &source_row);
1741
1742         /* Select the destination model */
1743         if (source_widget == widget) {
1744                 dest_model = source_model;
1745         } else {
1746                 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
1747         }
1748
1749         /* Get the path to the destination row. Can not call
1750            gtk_tree_view_get_drag_dest_row() because the source row
1751            is not selected anymore */
1752         gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget), x, y,
1753                                            &dest_row, &pos);
1754
1755         /* Only allow drops IN other rows */
1756         if (!dest_row || pos == GTK_TREE_VIEW_DROP_BEFORE || pos == GTK_TREE_VIEW_DROP_AFTER)
1757                 gtk_drag_finish (context, success, FALSE, time);
1758
1759         /* Create the helper */
1760         helper = g_slice_new0 (DndHelper);
1761         helper->delete_source = delete_source;
1762         helper->source_row = gtk_tree_path_copy (source_row);
1763         helper->context = context;
1764         helper->time = time;
1765
1766         /* Drags from the header view */
1767         if (source_widget != widget) {
1768
1769                 drag_and_drop_from_header_view (source_model,
1770                                                 dest_model,
1771                                                 dest_row,
1772                                                 helper);
1773         } else {
1774
1775
1776                 drag_and_drop_from_folder_view (source_model,
1777                                                 dest_model,
1778                                                 dest_row,
1779                                                 selection_data, 
1780                                                 helper);
1781         }
1782
1783         /* Frees */
1784         gtk_tree_path_free (source_row);
1785         gtk_tree_path_free (dest_row);
1786 }
1787
1788 /*
1789  * We define a "drag-drop" signal handler because we do not want to
1790  * use the default one, because the default one always calls
1791  * gtk_drag_finish and we prefer to do it in the "drag-data-received"
1792  * signal handler, because there we have all the information available
1793  * to know if the dnd was a success or not.
1794  */
1795 static gboolean
1796 drag_drop_cb (GtkWidget      *widget,
1797               GdkDragContext *context,
1798               gint            x,
1799               gint            y,
1800               guint           time,
1801               gpointer        user_data) 
1802 {
1803         gpointer target;
1804
1805         if (!context->targets)
1806                 return FALSE;
1807
1808         /* Check if we're dragging a folder row */
1809         target = gtk_drag_dest_find_target (widget, context, NULL);
1810
1811         /* Request the data from the source. */
1812         gtk_drag_get_data(widget, context, target, time);
1813
1814     return TRUE;
1815 }
1816
1817 /*
1818  * This function expands a node of a tree view if it's not expanded
1819  * yet. Not sure why it needs the threads stuff, but gtk+`example code
1820  * does that, so that's why they're here.
1821  */
1822 static gint
1823 expand_row_timeout (gpointer data)
1824 {
1825         GtkTreeView *tree_view = data;
1826         GtkTreePath *dest_path = NULL;
1827         GtkTreeViewDropPosition pos;
1828         gboolean result = FALSE;
1829         
1830         GDK_THREADS_ENTER ();
1831         
1832         gtk_tree_view_get_drag_dest_row (tree_view,
1833                                          &dest_path,
1834                                          &pos);
1835         
1836         if (dest_path &&
1837             (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER ||
1838              pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)) {
1839                 gtk_tree_view_expand_row (tree_view, dest_path, FALSE);
1840                 gtk_tree_path_free (dest_path);
1841         }
1842         else {
1843                 if (dest_path)
1844                         gtk_tree_path_free (dest_path);
1845                 
1846                 result = TRUE;
1847         }
1848         
1849         GDK_THREADS_LEAVE ();
1850
1851         return result;
1852 }
1853
1854 /*
1855  * This function is called whenever the pointer is moved over a widget
1856  * while dragging some data. It installs a timeout that will expand a
1857  * node of the treeview if not expanded yet. This function also calls
1858  * gdk_drag_status in order to set the suggested action that will be
1859  * used by the "drag-data-received" signal handler to know if we
1860  * should do a move or just a copy of the data.
1861  */
1862 static gboolean
1863 on_drag_motion (GtkWidget      *widget,
1864                 GdkDragContext *context,
1865                 gint            x,
1866                 gint            y,
1867                 guint           time,
1868                 gpointer        user_data)  
1869 {
1870         GtkTreeViewDropPosition pos;
1871         GtkTreePath *dest_row;
1872         ModestFolderViewPrivate *priv;
1873         GdkDragAction suggested_action;
1874         gboolean valid_location = FALSE;
1875
1876         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
1877
1878         if (priv->timer_expander != 0) {
1879                 g_source_remove (priv->timer_expander);
1880                 priv->timer_expander = 0;
1881         }
1882
1883         gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
1884                                            x, y,
1885                                            &dest_row,
1886                                            &pos);
1887
1888         /* Do not allow drops between folders */
1889         if (!dest_row ||
1890             pos == GTK_TREE_VIEW_DROP_BEFORE ||
1891             pos == GTK_TREE_VIEW_DROP_AFTER) {
1892                 gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW (widget), NULL, 0);
1893                 gdk_drag_status(context, 0, time);
1894                 valid_location = FALSE;
1895                 goto out;
1896         } else {
1897                 valid_location = TRUE;
1898         }
1899
1900         /* Expand the selected row after 1/2 second */
1901         if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), dest_row)) {
1902                 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), dest_row, pos);
1903                 priv->timer_expander = g_timeout_add (500, expand_row_timeout, widget);
1904         }
1905
1906         /* Select the desired action. By default we pick MOVE */
1907         suggested_action = GDK_ACTION_MOVE;
1908
1909         if (context->actions == GDK_ACTION_COPY)
1910             gdk_drag_status(context, GDK_ACTION_COPY, time);
1911         else if (context->actions == GDK_ACTION_MOVE)
1912             gdk_drag_status(context, GDK_ACTION_MOVE, time);
1913         else if (context->actions & suggested_action)
1914             gdk_drag_status(context, suggested_action, time);
1915         else
1916             gdk_drag_status(context, GDK_ACTION_DEFAULT, time);
1917
1918  out:
1919         if (dest_row)
1920                 gtk_tree_path_free (dest_row);
1921         g_signal_stop_emission_by_name (widget, "drag-motion");
1922         return valid_location;
1923 }
1924
1925
1926 /* Folder view drag types */
1927 const GtkTargetEntry folder_view_drag_types[] =
1928 {
1929         { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, MODEST_FOLDER_ROW },
1930         { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_APP,    MODEST_HEADER_ROW }
1931 };
1932
1933 /*
1934  * This function sets the treeview as a source and a target for dnd
1935  * events. It also connects all the requirede signals.
1936  */
1937 static void
1938 setup_drag_and_drop (GtkTreeView *self)
1939 {
1940         /* Set up the folder view as a dnd destination. Set only the
1941            highlight flag, otherwise gtk will have a different
1942            behaviour */
1943         gtk_drag_dest_set (GTK_WIDGET (self),
1944                            GTK_DEST_DEFAULT_HIGHLIGHT,
1945                            folder_view_drag_types,
1946                            G_N_ELEMENTS (folder_view_drag_types),
1947                            GDK_ACTION_MOVE | GDK_ACTION_COPY);
1948
1949         g_signal_connect (G_OBJECT (self),
1950                           "drag_data_received",
1951                           G_CALLBACK (on_drag_data_received),
1952                           NULL);
1953
1954
1955         /* Set up the treeview as a dnd source */
1956         gtk_drag_source_set (GTK_WIDGET (self),
1957                              GDK_BUTTON1_MASK,
1958                              folder_view_drag_types,
1959                              G_N_ELEMENTS (folder_view_drag_types),
1960                              GDK_ACTION_MOVE | GDK_ACTION_COPY);
1961
1962         g_signal_connect (G_OBJECT (self),
1963                           "drag_motion",
1964                           G_CALLBACK (on_drag_motion),
1965                           NULL);
1966         
1967         g_signal_connect (G_OBJECT (self),
1968                           "drag_data_get",
1969                           G_CALLBACK (on_drag_data_get),
1970                           NULL);
1971
1972         g_signal_connect (G_OBJECT (self),
1973                           "drag_drop",
1974                           G_CALLBACK (drag_drop_cb),
1975                           NULL);
1976 }
1977
1978 /*
1979  * This function manages the navigation through the folders using the
1980  * keyboard or the hardware keys in the device
1981  */
1982 static gboolean
1983 on_key_pressed (GtkWidget *self,
1984                 GdkEventKey *event,
1985                 gpointer user_data)
1986 {
1987         GtkTreeSelection *selection;
1988         GtkTreeIter iter;
1989         GtkTreeModel *model;
1990         gboolean retval = FALSE;
1991
1992         /* Up and Down are automatically managed by the treeview */
1993         if (event->keyval == GDK_Return) {
1994                 /* Expand/Collapse the selected row */
1995                 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1996                 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
1997                         GtkTreePath *path;
1998
1999                         path = gtk_tree_model_get_path (model, &iter);
2000
2001                         if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (self), path))
2002                                 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
2003                         else
2004                                 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
2005                         gtk_tree_path_free (path);
2006                 }
2007                 /* No further processing */
2008                 retval = TRUE;
2009         }
2010
2011         return retval;
2012 }
2013
2014 /*
2015  * We listen to the changes in the local folder account name key,
2016  * because we want to show the right name in the view. The local
2017  * folder account name corresponds to the device name in the Maemo
2018  * version. We do this because we do not want to query gconf on each
2019  * tree view refresh. It's better to cache it and change whenever
2020  * necessary.
2021  */
2022 static void 
2023 on_configuration_key_changed (ModestConf* conf, 
2024                               const gchar *key, 
2025                               ModestConfEvent event,
2026                               ModestConfNotificationId id, 
2027                               ModestFolderView *self)
2028 {
2029         ModestFolderViewPrivate *priv;
2030
2031
2032         g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
2033         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2034
2035         /* Do not listen for changes in other namespaces */
2036         if (priv->notification_id != id)
2037                  return;
2038          
2039         if (!strcmp (key, MODEST_CONF_DEVICE_NAME)) {
2040                 g_free (priv->local_account_name);
2041
2042                 if (event == MODEST_CONF_EVENT_KEY_UNSET)
2043                         priv->local_account_name = g_strdup (MODEST_LOCAL_FOLDERS_DEFAULT_DISPLAY_NAME);
2044                 else
2045                         priv->local_account_name = modest_conf_get_string (modest_runtime_get_conf(),
2046                                                                            MODEST_CONF_DEVICE_NAME, NULL);
2047
2048                 /* Force a redraw */
2049 #if GTK_CHECK_VERSION(2, 8, 0) /* gtk_tree_view_column_queue_resize is only available in GTK+ 2.8 */
2050                 GtkTreeViewColumn * tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self), 
2051                                                                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN);
2052                 gtk_tree_view_column_queue_resize (tree_column);
2053 #endif
2054         }
2055 }
2056
2057 void
2058 modest_folder_view_set_style (ModestFolderView *self,
2059                               ModestFolderViewStyle style)
2060 {
2061         ModestFolderViewPrivate *priv;
2062
2063         g_return_if_fail (self);
2064         
2065         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2066
2067         priv->style = style;
2068 }
2069
2070 void
2071 modest_folder_view_set_account_id_of_visible_server_account (ModestFolderView *self,
2072                                                              const gchar *account_id)
2073 {
2074         ModestFolderViewPrivate *priv;
2075         GtkTreeModel *model;
2076
2077         g_return_if_fail (self);
2078         
2079         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2080
2081         /* This will be used by the filter_row callback,
2082          * to decided which rows to show: */
2083         if (priv->visible_account_id) {
2084                 g_free (priv->visible_account_id);
2085                 priv->visible_account_id = NULL;
2086         }
2087         if (account_id)
2088                 priv->visible_account_id = g_strdup (account_id);
2089
2090         /* Refilter */
2091         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2092         if (GTK_IS_TREE_MODEL_FILTER (model))
2093                 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2094
2095         /* Save settings to gconf */
2096         modest_widget_memory_save (modest_runtime_get_conf (), G_OBJECT(self),
2097                                    MODEST_CONF_FOLDER_VIEW_KEY);
2098 }
2099
2100 const gchar *
2101 modest_folder_view_get_account_id_of_visible_server_account (ModestFolderView *self)
2102 {
2103         ModestFolderViewPrivate *priv;
2104
2105         g_return_val_if_fail (self, NULL);
2106         
2107         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2108
2109         return (const gchar *) priv->visible_account_id;
2110 }
2111
2112 static gboolean
2113 find_inbox_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *inbox_iter)
2114 {
2115         do {
2116                 GtkTreeIter child;
2117                 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2118
2119                 gtk_tree_model_get (model, iter, 
2120                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, 
2121                                     &type, -1);
2122                         
2123                 gboolean result = FALSE;
2124                 if (type == TNY_FOLDER_TYPE_INBOX) {
2125                         result = TRUE;
2126                 }               
2127                 if (result) {
2128                         *inbox_iter = *iter;
2129                         return TRUE;
2130                 }
2131
2132                 if (gtk_tree_model_iter_children (model, &child, iter)) {
2133                         if (find_inbox_iter (model, &child, inbox_iter))
2134                                 return TRUE;
2135                 }
2136
2137         } while (gtk_tree_model_iter_next (model, iter));
2138
2139         return FALSE;
2140 }
2141
2142
2143
2144
2145 void 
2146 modest_folder_view_select_first_inbox_or_local (ModestFolderView *self)
2147 {
2148         GtkTreeModel *model;
2149         GtkTreeIter iter, inbox_iter;
2150         GtkTreeSelection *sel;
2151         GtkTreePath *path = NULL;
2152
2153         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2154         if (!model)
2155                 return;
2156
2157         expand_root_items (self);
2158         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2159
2160         gtk_tree_model_get_iter_first (model, &iter);
2161
2162         if (find_inbox_iter (model, &iter, &inbox_iter))
2163                 path = gtk_tree_model_get_path (model, &inbox_iter);
2164         else
2165                 path = gtk_tree_path_new_first ();
2166
2167         /* Select the row and free */
2168         gtk_tree_view_set_cursor (GTK_TREE_VIEW (self), path, NULL, FALSE);
2169         gtk_tree_path_free (path);
2170
2171         /* set focus */
2172         gtk_widget_grab_focus (GTK_WIDGET(self));
2173 }
2174
2175
2176 /* recursive */
2177 static gboolean
2178 find_folder_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *folder_iter, 
2179                   TnyFolder* folder)
2180 {
2181         do {
2182                 GtkTreeIter child;
2183                 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2184                 TnyFolder* a_folder;
2185                 gchar *name = NULL;
2186                 
2187                 gtk_tree_model_get (model, iter, 
2188                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &a_folder,
2189                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name,
2190                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type, 
2191                                     -1);                
2192         
2193                 g_debug ("===> %s (%p ---- %p)", name, a_folder, folder);
2194                 g_free (name);
2195
2196                 if (folder == a_folder) {
2197                         g_object_unref (a_folder);
2198                         *folder_iter = *iter;
2199                         return TRUE;
2200                 }
2201                 g_object_unref (a_folder);
2202                 
2203                 if (gtk_tree_model_iter_children (model, &child, iter)) {
2204                         if (find_folder_iter (model, &child, folder_iter, folder)) 
2205                                 return TRUE;
2206                 }
2207
2208         } while (gtk_tree_model_iter_next (model, iter));
2209
2210         return FALSE;
2211 }
2212
2213
2214 static void
2215 on_row_changed_maybe_select_folder (GtkTreeModel *tree_model, GtkTreePath  *path, GtkTreeIter *iter,
2216                                     ModestFolderView *self)
2217 {
2218         ModestFolderViewPrivate *priv = NULL;
2219         GtkTreeSelection *sel;
2220
2221         if (!MODEST_IS_FOLDER_VIEW(self))
2222                 return;
2223         
2224         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2225         
2226         if (priv->folder_to_select) {
2227                 
2228                 if (!modest_folder_view_select_folder (self, priv->folder_to_select,
2229                                                        FALSE)) {
2230                         GtkTreePath *path;
2231                         path = gtk_tree_model_get_path (tree_model, iter);
2232                         gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
2233                         
2234                         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2235
2236                         gtk_tree_selection_select_iter (sel, iter);
2237                         gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
2238
2239                         gtk_tree_path_free (path);
2240                 
2241                 }
2242                 g_object_unref (priv->folder_to_select);
2243                 priv->folder_to_select = NULL;
2244         }
2245 }
2246
2247
2248 gboolean
2249 modest_folder_view_select_folder (ModestFolderView *self, TnyFolder *folder, 
2250                                   gboolean after_change)
2251 {
2252         GtkTreeModel *model;
2253         GtkTreeIter iter, folder_iter;
2254         GtkTreeSelection *sel;
2255         ModestFolderViewPrivate *priv = NULL;
2256         
2257         g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (self), FALSE);     
2258         g_return_val_if_fail (TNY_IS_FOLDER (folder), FALSE);   
2259                 
2260         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2261
2262         if (after_change) {
2263
2264                 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2265                 gtk_tree_selection_unselect_all (sel);
2266
2267                 if (priv->folder_to_select)
2268                         g_object_unref(priv->folder_to_select);
2269                 priv->folder_to_select = TNY_FOLDER(g_object_ref(folder));
2270                 return TRUE;
2271         }
2272                 
2273         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2274         if (!model)
2275                 return FALSE;
2276
2277                 
2278         gtk_tree_model_get_iter_first (model, &iter);
2279         if (find_folder_iter (model, &iter, &folder_iter, folder)) {
2280                 GtkTreePath *path;
2281
2282                 path = gtk_tree_model_get_path (model, &folder_iter);
2283                 gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
2284
2285                 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2286                 gtk_tree_selection_select_iter (sel, &folder_iter);
2287                 gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
2288
2289                 gtk_tree_path_free (path);
2290                 return TRUE;
2291         }
2292         return FALSE;
2293 }
2294
2295
2296 void 
2297 modest_folder_view_copy_selection (ModestFolderView *folder_view)
2298 {
2299         /* Copy selection */
2300         _clipboard_set_selected_data (folder_view, FALSE);
2301 }
2302
2303 void 
2304 modest_folder_view_cut_selection (ModestFolderView *folder_view)
2305 {
2306         ModestFolderViewPrivate *priv = NULL;
2307         GtkTreeModel *model = NULL;
2308         const gchar **hidding = NULL;
2309         guint i, n_selected;
2310
2311         g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view));
2312         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
2313
2314         /* Copy selection */
2315         if (!_clipboard_set_selected_data (folder_view, TRUE))
2316                 return;
2317
2318         /* Get hidding ids */
2319         hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected); 
2320         
2321         /* Clear hidding array created by previous cut operation */
2322         _clear_hidding_filter (MODEST_FOLDER_VIEW (folder_view));
2323
2324         /* Copy hidding array */
2325         priv->n_selected = n_selected;
2326         priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
2327         for (i=0; i < n_selected; i++) 
2328                 priv->hidding_ids[i] = g_strdup(hidding[i]);            
2329
2330         /* Hide cut folders */
2331         model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
2332         gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2333 }
2334
2335 void
2336 modest_folder_view_show_non_move_folders (ModestFolderView *folder_view,
2337                                     gboolean show)
2338 {
2339         ModestFolderViewPrivate* priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
2340         priv->show_non_move = show;
2341         modest_folder_view_update_model(folder_view,
2342                                                                                                                                         TNY_ACCOUNT_STORE(modest_runtime_get_account_store()));
2343 }
2344
2345 /* Returns FALSE if it did not selected anything */
2346 static gboolean
2347 _clipboard_set_selected_data (ModestFolderView *folder_view,
2348                               gboolean delete)
2349 {
2350         ModestFolderViewPrivate *priv = NULL;
2351         TnyFolderStore *folder = NULL;
2352         gboolean retval = FALSE;
2353
2354         g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (folder_view), FALSE);
2355         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
2356                 
2357         /* Set selected data on clipboard   */
2358         g_return_val_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard), FALSE);
2359         folder = modest_folder_view_get_selected (folder_view);
2360
2361         /* Do not allow to select an account */
2362         if (TNY_IS_FOLDER (folder)) {
2363                 modest_email_clipboard_set_data (priv->clipboard, TNY_FOLDER(folder), NULL, delete);
2364                 retval = TRUE;
2365         }
2366
2367         /* Free */
2368         g_object_unref (folder);
2369
2370         return retval;
2371 }
2372
2373 static void
2374 _clear_hidding_filter (ModestFolderView *folder_view) 
2375 {
2376         ModestFolderViewPrivate *priv;
2377         guint i;
2378         
2379         g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view)); 
2380         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
2381
2382         if (priv->hidding_ids != NULL) {
2383                 for (i=0; i < priv->n_selected; i++) 
2384                         g_free (priv->hidding_ids[i]);
2385                 g_free(priv->hidding_ids);
2386         }       
2387 }
2388
2389