478a89283ab81ef3c46b7052509af8b653378654
[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 /* 'private'/'protected' functions */
60 static void modest_folder_view_class_init  (ModestFolderViewClass *klass);
61 static void modest_folder_view_init        (ModestFolderView *obj);
62 static void modest_folder_view_finalize    (GObject *obj);
63
64 static void         tny_account_store_view_init (gpointer g, 
65                                                  gpointer iface_data);
66
67 static void         modest_folder_view_set_account_store (TnyAccountStoreView *self, 
68                                                           TnyAccountStore     *account_store);
69
70 static void         on_selection_changed   (GtkTreeSelection *sel, gpointer data);
71
72 static void         on_account_removed     (TnyAccountStore *self, 
73                                             TnyAccount *account,
74                                             gpointer user_data);
75
76 static void         on_account_inserted    (TnyAccountStore *self, 
77                                             TnyAccount *account,
78                                             gpointer user_data);
79
80 static void         on_account_changed    (TnyAccountStore *self, 
81                                             TnyAccount *account,
82                                             gpointer user_data);
83
84 static gint         cmp_rows               (GtkTreeModel *tree_model, 
85                                             GtkTreeIter *iter1, 
86                                             GtkTreeIter *iter2,
87                                             gpointer user_data);
88
89 static gboolean     filter_row             (GtkTreeModel *model,
90                                             GtkTreeIter *iter,
91                                             gpointer data);
92
93 static gboolean     on_key_pressed         (GtkWidget *self,
94                                             GdkEventKey *event,
95                                             gpointer user_data);
96
97 static void         on_configuration_key_changed  (ModestConf* conf, 
98                                                    const gchar *key, 
99                                                    ModestConfEvent event,
100                                                    ModestConfNotificationId notification_id, 
101                                                    ModestFolderView *self);
102
103 /* DnD functions */
104 static void         on_drag_data_get       (GtkWidget *widget, 
105                                             GdkDragContext *context, 
106                                             GtkSelectionData *selection_data, 
107                                             guint info, 
108                                             guint time, 
109                                             gpointer data);
110
111 static void         on_drag_data_received  (GtkWidget *widget, 
112                                             GdkDragContext *context, 
113                                             gint x, 
114                                             gint y, 
115                                             GtkSelectionData *selection_data, 
116                                             guint info, 
117                                             guint time, 
118                                             gpointer data);
119
120 static gboolean     on_drag_motion         (GtkWidget      *widget,
121                                             GdkDragContext *context,
122                                             gint            x,
123                                             gint            y,
124                                             guint           time,
125                                             gpointer        user_data);
126
127 static void expand_root_items (ModestFolderView *self);
128
129 static gint         expand_row_timeout     (gpointer data);
130
131 static void         setup_drag_and_drop    (GtkTreeView *self);
132
133 static gboolean     _clipboard_set_selected_data (ModestFolderView *folder_view, 
134                                                   gboolean delete);
135
136 static void         _clear_hidding_filter (ModestFolderView *folder_view);
137
138 static void          on_row_changed_maybe_select_folder (GtkTreeModel     *tree_model, 
139                                                          GtkTreePath      *path, 
140                                                          GtkTreeIter      *iter,
141                                                          ModestFolderView *self);
142
143 enum {
144         FOLDER_SELECTION_CHANGED_SIGNAL,
145         FOLDER_DISPLAY_NAME_CHANGED_SIGNAL,
146         LAST_SIGNAL
147 };
148
149 typedef struct _ModestFolderViewPrivate ModestFolderViewPrivate;
150 struct _ModestFolderViewPrivate {
151         TnyAccountStore      *account_store;
152         TnyFolderStore       *cur_folder_store;
153
154         TnyFolder            *folder_to_select; /* folder to select after the next update */
155
156         ModestConfNotificationId notification_id;
157
158         gulong                changed_signal;
159         gulong                account_inserted_signal;
160         gulong                account_removed_signal;
161         gulong                account_changed_signal;
162         gulong                conf_key_signal;
163         
164         /* not unref this object, its a singlenton */
165         ModestEmailClipboard *clipboard;
166
167         /* Filter tree model */
168         gchar **hidding_ids;
169         guint n_selected;
170
171         TnyFolderStoreQuery  *query;
172         guint                 timer_expander;
173
174         gchar                *local_account_name;
175         gchar                *visible_account_id;
176         ModestFolderViewStyle style;
177
178         gboolean  reselect; /* we use this to force a reselection of the INBOX */
179         gboolean  show_non_move;
180 };
181 #define MODEST_FOLDER_VIEW_GET_PRIVATE(o)                       \
182         (G_TYPE_INSTANCE_GET_PRIVATE((o),                       \
183                                      MODEST_TYPE_FOLDER_VIEW,   \
184                                      ModestFolderViewPrivate))
185 /* globals */
186 static GObjectClass *parent_class = NULL;
187
188 static guint signals[LAST_SIGNAL] = {0}; 
189
190 GType
191 modest_folder_view_get_type (void)
192 {
193         static GType my_type = 0;
194         if (!my_type) {
195                 static const GTypeInfo my_info = {
196                         sizeof(ModestFolderViewClass),
197                         NULL,           /* base init */
198                         NULL,           /* base finalize */
199                         (GClassInitFunc) modest_folder_view_class_init,
200                         NULL,           /* class finalize */
201                         NULL,           /* class data */
202                         sizeof(ModestFolderView),
203                         1,              /* n_preallocs */
204                         (GInstanceInitFunc) modest_folder_view_init,
205                         NULL
206                 };
207
208                 static const GInterfaceInfo tny_account_store_view_info = {
209                         (GInterfaceInitFunc) tny_account_store_view_init, /* interface_init */
210                         NULL,         /* interface_finalize */
211                         NULL          /* interface_data */
212                 };
213
214                                 
215                 my_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
216                                                   "ModestFolderView",
217                                                   &my_info, 0);
218
219                 g_type_add_interface_static (my_type, 
220                                              TNY_TYPE_ACCOUNT_STORE_VIEW, 
221                                              &tny_account_store_view_info);
222         }
223         return my_type;
224 }
225
226 static void
227 modest_folder_view_class_init (ModestFolderViewClass *klass)
228 {
229         GObjectClass *gobject_class;
230         gobject_class = (GObjectClass*) klass;
231
232         parent_class            = g_type_class_peek_parent (klass);
233         gobject_class->finalize = modest_folder_view_finalize;
234
235         g_type_class_add_private (gobject_class,
236                                   sizeof(ModestFolderViewPrivate));
237         
238         signals[FOLDER_SELECTION_CHANGED_SIGNAL] = 
239                 g_signal_new ("folder_selection_changed",
240                               G_TYPE_FROM_CLASS (gobject_class),
241                               G_SIGNAL_RUN_FIRST,
242                               G_STRUCT_OFFSET (ModestFolderViewClass,
243                                                folder_selection_changed),
244                               NULL, NULL,
245                               modest_marshal_VOID__POINTER_BOOLEAN,
246                               G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_BOOLEAN);
247
248         /*
249          * This signal is emitted whenever the currently selected
250          * folder display name is computed. Note that the name could
251          * be different to the folder name, because we could append
252          * the unread messages count to the folder name to build the
253          * folder display name
254          */
255         signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL] = 
256                 g_signal_new ("folder-display-name-changed",
257                               G_TYPE_FROM_CLASS (gobject_class),
258                               G_SIGNAL_RUN_FIRST,
259                               G_STRUCT_OFFSET (ModestFolderViewClass,
260                                                folder_display_name_changed),
261                               NULL, NULL,
262                               g_cclosure_marshal_VOID__STRING,
263                               G_TYPE_NONE, 1, G_TYPE_STRING);
264 }
265
266 /* Simplify checks for NULLs: */
267 static gboolean
268 strings_are_equal (const gchar *a, const gchar *b)
269 {
270         if (!a && !b)
271                 return TRUE;
272         if (a && b)
273         {
274                 return (strcmp (a, b) == 0);
275         }
276         else
277                 return FALSE;
278 }
279
280 static gboolean
281 on_model_foreach_set_name(GtkTreeModel *model, GtkTreePath *path,  GtkTreeIter *iter, gpointer data)
282 {
283         GObject *instance = NULL;
284         
285         gtk_tree_model_get (model, iter,
286                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
287                             -1);
288                             
289         if (!instance)
290                 return FALSE; /* keep walking */
291                         
292         if (!TNY_IS_ACCOUNT (instance)) {
293                 g_object_unref (instance);
294                 return FALSE; /* keep walking */        
295         }    
296         
297         /* Check if this is the looked-for account: */
298         TnyAccount *this_account = TNY_ACCOUNT (instance);
299         TnyAccount *account = TNY_ACCOUNT (data);
300         
301         const gchar *this_account_id = tny_account_get_id(this_account);
302         const gchar *account_id = tny_account_get_id(account);
303         g_object_unref (instance);
304         instance = NULL;
305
306         /* printf ("DEBUG: %s: this_account_id=%s, account_id=%s\n", __FUNCTION__, this_account_id, account_id); */
307         if (strings_are_equal(this_account_id, account_id)) {
308                 /* Tell the model that the data has changed, so that
309                  * it calls the cell_data_func callbacks again: */
310                 /* TODO: This does not seem to actually cause the new string to be shown: */
311                 gtk_tree_model_row_changed (model, path, iter);
312                 
313                 return TRUE; /* stop walking */
314         }
315         
316         return FALSE; /* keep walking */
317 }
318
319 typedef struct 
320 {
321         ModestFolderView *self;
322         gchar *previous_name;
323 } GetMmcAccountNameData;
324
325 static void
326 on_get_mmc_account_name (TnyStoreAccount* account, gpointer user_data)
327 {
328         /* printf ("DEBU1G: %s: account name=%s\n", __FUNCTION__, tny_account_get_name (TNY_ACCOUNT(account))); */
329
330         GetMmcAccountNameData *data = (GetMmcAccountNameData*)user_data;
331         
332         if (!strings_are_equal (
333                 tny_account_get_name(TNY_ACCOUNT(account)), 
334                 data->previous_name)) {
335         
336                 /* Tell the model that the data has changed, so that 
337                  * it calls the cell_data_func callbacks again: */
338                 ModestFolderView *self = data->self;
339                 GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
340                 if (model)
341                         gtk_tree_model_foreach(model, on_model_foreach_set_name, account);
342         }
343
344         g_free (data->previous_name);
345         g_slice_free (GetMmcAccountNameData, data);
346 }
347
348 static void
349 text_cell_data  (GtkTreeViewColumn *column,  GtkCellRenderer *renderer,
350                  GtkTreeModel *tree_model,  GtkTreeIter *iter,  gpointer data)
351 {
352         ModestFolderViewPrivate *priv;
353         GObject *rendobj;
354         gchar *fname = NULL;
355         gint unread = 0;
356         gint all = 0;
357         TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
358         GObject *instance = NULL;
359         
360         g_return_if_fail (column);
361         g_return_if_fail (tree_model);
362
363         gtk_tree_model_get (tree_model, iter,
364                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &fname,
365                             TNY_GTK_FOLDER_STORE_TREE_MODEL_ALL_COLUMN, &all,
366                             TNY_GTK_FOLDER_STORE_TREE_MODEL_UNREAD_COLUMN, &unread,
367                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
368                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
369                             -1);
370         rendobj = G_OBJECT(renderer);
371
372         if (!fname)
373                 return;
374
375         if (!instance) {
376                 g_free (fname);
377                 return;
378         }
379
380         ModestFolderView *self = MODEST_FOLDER_VIEW (data);
381         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE (self);
382         
383         gchar *item_name = NULL;
384         gint item_weight = 400;
385         
386         if (type != TNY_FOLDER_TYPE_ROOT) {
387                 gint number = 0;
388                 
389                 if (modest_tny_folder_is_local_folder (TNY_FOLDER (instance)) ||
390                     modest_tny_folder_is_memory_card_folder (TNY_FOLDER (instance))) {
391                         type = modest_tny_folder_get_local_or_mmc_folder_type (TNY_FOLDER (instance));
392                         if (type != TNY_FOLDER_TYPE_UNKNOWN) {
393                                 g_free (fname);
394                                 fname = g_strdup(modest_local_folder_info_get_type_display_name (type));
395                         }
396                 }
397
398                 /* Select the number to show: the unread or unsent messages */
399                 if ((type == TNY_FOLDER_TYPE_DRAFTS) || (type == TNY_FOLDER_TYPE_OUTBOX))
400                         number = all;
401                 else
402                         number = unread;
403                 
404                 /* Use bold font style if there are unread or unset messages */
405                 if (number > 0) {
406                         item_name = g_strdup_printf ("%s (%d)", fname, number);
407                         item_weight = 800;
408                 } else {
409                         item_name = g_strdup (fname);
410                         item_weight = 400;
411                 }
412                 
413         } else if (TNY_IS_ACCOUNT (instance)) {
414                 /* If it's a server account */
415                 if (modest_tny_account_is_virtual_local_folders (
416                                 TNY_ACCOUNT (instance))) {
417                         item_name = g_strdup (priv->local_account_name);
418                         item_weight = 800;
419                 } else if (modest_tny_account_is_memory_card_account (
420                                 TNY_ACCOUNT (instance))) {
421                         /* fname is only correct when the items are first 
422                          * added to the model, not when the account is 
423                          * changed later, so get the name from the account
424                          * instance: */
425                         item_name = g_strdup (tny_account_get_name (TNY_ACCOUNT (instance)));
426                         item_weight = 800;
427                 } else {
428                         item_name = g_strdup (fname);
429                         item_weight = 800;
430                 }
431         }
432         
433         if (!item_name)
434                 item_name = g_strdup ("unknown");
435                         
436         if (item_name && item_weight) {
437                 /* Set the name in the treeview cell: */
438                 g_object_set (rendobj,"text", item_name, "weight", item_weight, NULL);
439                 
440                 /* Notify display name observers */
441                 /* TODO: What listens for this signal, and how can it use only the new name? */
442                 if (G_OBJECT (priv->cur_folder_store) == instance) {
443                         g_signal_emit (G_OBJECT(self),
444                                                signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
445                                                item_name);
446                 }
447                 g_free (item_name);
448                 
449         }
450         
451         /* If it is a Memory card account, make sure that we have the correct name.
452          * This function will be trigerred again when the name has been retrieved: */
453         if (TNY_IS_STORE_ACCOUNT (instance) && 
454                 modest_tny_account_is_memory_card_account (TNY_ACCOUNT (instance))) {
455
456                 /* Get the account name asynchronously: */
457                 GetMmcAccountNameData *callback_data = 
458                         g_slice_new0(GetMmcAccountNameData);
459                 callback_data->self = self;
460
461                 const gchar *name = tny_account_get_name (TNY_ACCOUNT(instance));
462                 if (name)
463                         callback_data->previous_name = g_strdup (name); 
464
465                 modest_tny_account_get_mmc_account_name (TNY_STORE_ACCOUNT (instance), 
466                         on_get_mmc_account_name, callback_data);
467         }
468                         
469         g_object_unref (G_OBJECT (instance));
470         g_free (fname);
471 }
472
473 static void
474 icon_cell_data  (GtkTreeViewColumn *column,  GtkCellRenderer *renderer,
475                  GtkTreeModel *tree_model,  GtkTreeIter *iter, gpointer data)
476 {
477         GObject *rendobj = NULL, *instance = NULL;
478         GdkPixbuf *pixbuf = NULL;
479         TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
480         const gchar *account_id = NULL;
481         gboolean has_children;
482         
483         rendobj = G_OBJECT(renderer);
484         gtk_tree_model_get (tree_model, iter,
485                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
486                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
487                             -1);
488         has_children = gtk_tree_model_iter_has_child (tree_model, iter);
489
490         if (!instance) 
491                 return;
492
493         /* MERGE is not needed anymore as the folder now has the correct type jschmid */
494         /* We include the MERGE type here because it's used to create
495            the local OUTBOX folder */
496         if (type == TNY_FOLDER_TYPE_NORMAL || 
497             type == TNY_FOLDER_TYPE_UNKNOWN) {
498                 type = modest_tny_folder_guess_folder_type (TNY_FOLDER (instance));
499         }
500
501         switch (type) {
502         case TNY_FOLDER_TYPE_ROOT:
503                 if (TNY_IS_ACCOUNT (instance)) {
504                         
505                         if (modest_tny_account_is_virtual_local_folders (
506                                 TNY_ACCOUNT (instance))) {
507                                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_LOCAL_FOLDERS);
508                         }
509                         else {
510                                 account_id = tny_account_get_id (TNY_ACCOUNT (instance));
511                                 
512                                 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
513                                         pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_MMC);
514                                 else
515                                         pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_ACCOUNT);
516                         }
517                 }
518                 break;
519         case TNY_FOLDER_TYPE_INBOX:
520             pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_INBOX);
521             break;
522         case TNY_FOLDER_TYPE_OUTBOX:
523                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_OUTBOX);
524                 break;
525         case TNY_FOLDER_TYPE_JUNK:
526                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_JUNK);
527                 break;
528         case TNY_FOLDER_TYPE_SENT:
529                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_SENT);
530                 break;
531         case TNY_FOLDER_TYPE_TRASH:
532                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_TRASH);
533                 break;
534         case TNY_FOLDER_TYPE_DRAFTS:
535                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_DRAFTS);
536                 break;
537         case TNY_FOLDER_TYPE_NORMAL:
538         default:
539                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_NORMAL);
540                 break;
541         }
542         
543         g_object_unref (G_OBJECT (instance));
544
545         /* Set pixbuf */
546         g_object_set (rendobj, "pixbuf", pixbuf, NULL);
547         if (has_children && (pixbuf != NULL)) {
548                 GdkPixbuf *open_pixbuf, *closed_pixbuf;
549                 GdkPixbuf *open_emblem, *closed_emblem;
550                 open_pixbuf = gdk_pixbuf_copy (pixbuf);
551                 closed_pixbuf = gdk_pixbuf_copy (pixbuf);
552                 open_emblem = modest_platform_get_icon ("qgn_list_gene_fldr_exp");
553                 closed_emblem = modest_platform_get_icon ("qgn_list_gene_fldr_clp");
554
555                 if (open_emblem) {
556                         gdk_pixbuf_composite (open_emblem, open_pixbuf, 0, 0, 
557                                               MIN (gdk_pixbuf_get_width (open_emblem), 
558                                                    gdk_pixbuf_get_width (open_pixbuf)),
559                                               MIN (gdk_pixbuf_get_height (open_emblem), 
560                                                    gdk_pixbuf_get_height (open_pixbuf)),
561                                               0, 0, 1, 1, GDK_INTERP_NEAREST, 255);
562                         g_object_set (rendobj, "pixbuf-expander-open", open_pixbuf, NULL);
563                         g_object_unref (open_emblem);
564                 }
565                 if (closed_emblem) {
566                         gdk_pixbuf_composite (closed_emblem, closed_pixbuf, 0, 0, 
567                                               MIN (gdk_pixbuf_get_width (closed_emblem), 
568                                                    gdk_pixbuf_get_width (closed_pixbuf)),
569                                               MIN (gdk_pixbuf_get_height (closed_emblem), 
570                                                    gdk_pixbuf_get_height (closed_pixbuf)),
571                                               0, 0, 1, 1, GDK_INTERP_NEAREST, 255);
572                         g_object_set (rendobj, "pixbuf-expander-closed", closed_pixbuf, NULL);
573                         g_object_unref (closed_emblem);
574                 }
575                 if (closed_pixbuf)
576                         g_object_unref (closed_pixbuf);
577                 if (open_pixbuf)
578                         g_object_unref (open_pixbuf);
579         }
580
581         if (pixbuf != NULL)
582                 g_object_unref (pixbuf);
583 }
584
585 static void
586 add_columns (GtkWidget *treeview)
587 {
588         GtkTreeViewColumn *column;
589         GtkCellRenderer *renderer;
590         GtkTreeSelection *sel;
591
592         /* Create column */
593         column = gtk_tree_view_column_new ();   
594         
595         /* Set icon and text render function */
596         renderer = gtk_cell_renderer_pixbuf_new();
597         gtk_tree_view_column_pack_start (column, renderer, FALSE);
598         gtk_tree_view_column_set_cell_data_func(column, renderer,
599                                                 icon_cell_data, treeview, NULL);
600         
601         renderer = gtk_cell_renderer_text_new();
602         gtk_tree_view_column_pack_start (column, renderer, FALSE);
603         gtk_tree_view_column_set_cell_data_func(column, renderer,
604                                                 text_cell_data, treeview, NULL);
605         
606         /* Set selection mode */
607         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(treeview));
608         gtk_tree_selection_set_mode (sel, GTK_SELECTION_SINGLE);
609
610         /* Set treeview appearance */
611         gtk_tree_view_column_set_spacing (column, 2);
612         gtk_tree_view_column_set_resizable (column, TRUE);
613         gtk_tree_view_column_set_fixed_width (column, TRUE);            
614         gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(treeview), FALSE);
615         gtk_tree_view_set_enable_search (GTK_TREE_VIEW(treeview), FALSE);
616
617         /* Add column */
618         gtk_tree_view_append_column (GTK_TREE_VIEW(treeview),column);
619 }
620
621 static void
622 modest_folder_view_init (ModestFolderView *obj)
623 {
624         ModestFolderViewPrivate *priv;
625         ModestConf *conf;
626         
627         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
628         
629         priv->timer_expander = 0;
630         priv->account_store  = NULL;
631         priv->query          = NULL;
632         priv->style          = MODEST_FOLDER_VIEW_STYLE_SHOW_ALL;
633         priv->cur_folder_store   = NULL;
634         priv->visible_account_id = NULL;
635         priv->folder_to_select = NULL;
636
637         /* Initialize the local account name */
638         conf = modest_runtime_get_conf();
639         priv->local_account_name = modest_conf_get_string (conf, MODEST_CONF_DEVICE_NAME, NULL);
640
641         /* Init email clipboard */
642         priv->clipboard = modest_runtime_get_email_clipboard ();
643         priv->hidding_ids = NULL;
644         priv->n_selected = 0;
645         priv->reselect = FALSE;
646         priv->show_non_move = TRUE;
647
648         /* Build treeview */
649         add_columns (GTK_WIDGET (obj));
650
651         /* Setup drag and drop */
652         setup_drag_and_drop (GTK_TREE_VIEW(obj));
653
654         /* Connect signals */
655         g_signal_connect (G_OBJECT (obj), 
656                           "key-press-event", 
657                           G_CALLBACK (on_key_pressed), NULL);
658
659         /*
660          * Track changes in the local account name (in the device it
661          * will be the device name)
662          */
663         priv->notification_id = modest_conf_listen_to_namespace (conf, 
664                                                                  MODEST_CONF_NAMESPACE);
665         priv->conf_key_signal = g_signal_connect (G_OBJECT(conf), 
666                                                   "key_changed",
667                                                   G_CALLBACK(on_configuration_key_changed), 
668                                                   obj);
669 }
670
671 static void
672 tny_account_store_view_init (gpointer g, gpointer iface_data)
673 {
674         TnyAccountStoreViewIface *klass = (TnyAccountStoreViewIface *)g;
675
676         klass->set_account_store_func = modest_folder_view_set_account_store;
677
678         return;
679 }
680
681 static void
682 modest_folder_view_finalize (GObject *obj)
683 {
684         ModestFolderViewPrivate *priv;
685         GtkTreeSelection    *sel;
686         
687         g_return_if_fail (obj);
688         
689         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
690
691         if (priv->notification_id) {
692                 modest_conf_forget_namespace (modest_runtime_get_conf (),
693                                               MODEST_CONF_NAMESPACE,
694                                               priv->notification_id);
695         }
696
697         if (priv->timer_expander != 0) {
698                 g_source_remove (priv->timer_expander);
699                 priv->timer_expander = 0;
700         }
701
702         if (priv->account_store) {
703                 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
704                                              priv->account_inserted_signal);
705                 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
706                                              priv->account_removed_signal);
707                 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
708                                              priv->account_changed_signal);
709                 g_object_unref (G_OBJECT(priv->account_store));
710                 priv->account_store = NULL;
711         }
712
713         if (priv->query) {
714                 g_object_unref (G_OBJECT (priv->query));
715                 priv->query = NULL;
716         }
717
718         if (priv->folder_to_select) {
719                 g_object_unref (G_OBJECT(priv->folder_to_select));
720                 priv->folder_to_select = NULL;
721         }
722    
723         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(obj));
724         if (sel)
725                 g_signal_handler_disconnect (G_OBJECT(sel), priv->changed_signal);
726
727         g_free (priv->local_account_name);
728         g_free (priv->visible_account_id);
729         
730         if (priv->conf_key_signal) {
731                 g_signal_handler_disconnect (modest_runtime_get_conf (),
732                                              priv->conf_key_signal);
733                 priv->conf_key_signal = 0;
734         }
735
736         if (priv->cur_folder_store) {
737                 if (TNY_IS_FOLDER(priv->cur_folder_store))
738                         tny_folder_sync (TNY_FOLDER(priv->cur_folder_store), FALSE, NULL);
739                         /* FALSE --> expunge the message */
740
741                 g_object_unref (priv->cur_folder_store);
742                 priv->cur_folder_store = NULL;
743         }
744
745         /* Clear hidding array created by cut operation */
746         _clear_hidding_filter (MODEST_FOLDER_VIEW (obj));
747
748         G_OBJECT_CLASS(parent_class)->finalize (obj);
749 }
750
751
752 static void
753 modest_folder_view_set_account_store (TnyAccountStoreView *self, TnyAccountStore *account_store)
754 {
755         ModestFolderViewPrivate *priv;
756         TnyDevice *device;
757
758         g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
759         g_return_if_fail (TNY_IS_ACCOUNT_STORE (account_store));
760
761         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
762         device = tny_account_store_get_device (account_store);
763
764         if (G_UNLIKELY (priv->account_store)) {
765
766                 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
767                                                    priv->account_inserted_signal))
768                         g_signal_handler_disconnect (G_OBJECT (priv->account_store),
769                                                      priv->account_inserted_signal);
770                 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store), 
771                                                    priv->account_removed_signal))
772                         g_signal_handler_disconnect (G_OBJECT (priv->account_store), 
773                                                      priv->account_removed_signal);
774                 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store), 
775                                                    priv->account_changed_signal))
776                         g_signal_handler_disconnect (G_OBJECT (priv->account_store), 
777                                                      priv->account_changed_signal);
778                 g_object_unref (G_OBJECT (priv->account_store));
779         }
780
781         priv->account_store = g_object_ref (G_OBJECT (account_store));
782
783         priv->account_removed_signal = 
784                 g_signal_connect (G_OBJECT(account_store), "account_removed",
785                                   G_CALLBACK (on_account_removed), self);
786
787         priv->account_inserted_signal =
788                 g_signal_connect (G_OBJECT(account_store), "account_inserted",
789                                   G_CALLBACK (on_account_inserted), self);
790
791         priv->account_changed_signal =
792                 g_signal_connect (G_OBJECT(account_store), "account_changed",
793                                   G_CALLBACK (on_account_changed), self);
794
795         modest_folder_view_update_model (MODEST_FOLDER_VIEW (self), account_store);
796         
797         g_object_unref (G_OBJECT (device));
798 }
799
800 static void
801 on_account_inserted (TnyAccountStore *account_store, 
802                      TnyAccount *account,
803                      gpointer user_data)
804 {
805         ModestFolderViewPrivate *priv;
806         GtkTreeModel *sort_model, *filter_model;
807
808         /* Ignore transport account insertions, we're not showing them
809            in the folder view */
810         if (TNY_IS_TRANSPORT_ACCOUNT (account))
811                 return;
812
813         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (user_data);
814
815         /* If we're adding a new account, and there is no previous
816            one, we need to select the visible server account */
817         if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE &&
818             !priv->visible_account_id)
819                 modest_widget_memory_restore (modest_runtime_get_conf(), 
820                                               G_OBJECT (user_data),
821                                               MODEST_CONF_FOLDER_VIEW_KEY);
822
823         /* Get the inner model */
824         filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (user_data));
825         sort_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
826
827         /* Insert the account in the model */
828         tny_list_append (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
829                          G_OBJECT (account));
830 }
831
832
833 static void
834 on_account_changed (TnyAccountStore *account_store, TnyAccount *tny_account,
835                     gpointer user_data)
836 {
837         ModestFolderView*  self;
838
839         self = MODEST_FOLDER_VIEW (user_data);
840         
841         /* Ignore transport account insertions, we're not showing them
842            in the folder view */
843         if (TNY_IS_TRANSPORT_ACCOUNT (tny_account))
844                 return;
845         
846         /* ugly hack to force a redraw */
847         modest_folder_view_update_model (self,
848                                          account_store);
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 /*
1499  * This function is used by drag_data_received_cb to manage drag and
1500  * drop of a header, i.e, and drag from the header view to the folder
1501  * view.
1502  */
1503 static void
1504 drag_and_drop_from_header_view (GtkTreeModel *source_model,
1505                                 GtkTreeModel *dest_model,
1506                                 GtkTreePath  *dest_row,
1507                                 DndHelper    *helper)
1508 {
1509         TnyList *headers = NULL;
1510         TnyHeader *header = NULL;
1511         TnyFolder *folder = NULL;
1512         ModestMailOperation *mail_op = NULL;
1513         GtkTreeIter source_iter;
1514
1515         g_return_if_fail (GTK_IS_TREE_MODEL(source_model));
1516         g_return_if_fail (GTK_IS_TREE_MODEL(dest_model));
1517         g_return_if_fail (dest_row);
1518         g_return_if_fail (helper);
1519
1520         /* Get header */
1521         gtk_tree_model_get_iter (source_model, &source_iter, helper->source_row);
1522         gtk_tree_model_get (source_model, &source_iter, 
1523                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, 
1524                             &header, -1);
1525         if (!TNY_IS_HEADER(header)) {
1526                 g_warning ("BUG: %s could not get a valid header", __FUNCTION__);
1527                 goto cleanup;
1528         }
1529         
1530         /* Get Folder */
1531         folder = tree_path_to_folder (dest_model, dest_row);
1532         if (!TNY_IS_FOLDER(folder)) {
1533                 g_warning ("BUG: %s could not get a valid folder", __FUNCTION__);
1534                 goto cleanup;
1535         }
1536         if (modest_tny_folder_get_rules(folder) & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE) {
1537                 g_debug ("folder rules: cannot write to that folder");
1538                 goto cleanup;
1539         }
1540         
1541
1542         /* Transfer message */
1543         mail_op = modest_mail_operation_new_with_error_handling (MODEST_MAIL_OPERATION_TYPE_RECEIVE, 
1544                                                                  NULL,
1545                                                                  modest_ui_actions_move_folder_error_handler,
1546                                                                  NULL);
1547         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1548                                          mail_op);
1549         g_signal_connect (G_OBJECT (mail_op), "progress-changed",
1550                           G_CALLBACK (on_progress_changed), helper);
1551
1552         headers = tny_simple_list_new ();
1553         tny_list_append (headers, G_OBJECT (header));
1554         modest_mail_operation_xfer_msgs (mail_op, 
1555                                          headers, 
1556                                          folder, 
1557                                          helper->delete_source, 
1558                                          NULL, NULL);
1559         
1560         /* Frees */
1561 cleanup:
1562         if (G_IS_OBJECT(mail_op))
1563                 g_object_unref (G_OBJECT (mail_op));
1564         if (G_IS_OBJECT(header))
1565                 g_object_unref (G_OBJECT (header));
1566         if (G_IS_OBJECT(folder))
1567                 g_object_unref (G_OBJECT (folder));
1568         if (G_IS_OBJECT(headers))
1569                 g_object_unref (headers);
1570 }
1571
1572 /*
1573  * This function is used by drag_data_received_cb to manage drag and
1574  * drop of a folder, i.e, and drag from the folder view to the same
1575  * folder view.
1576  */
1577 static void
1578 drag_and_drop_from_folder_view (GtkTreeModel     *source_model,
1579                                 GtkTreeModel     *dest_model,
1580                                 GtkTreePath      *dest_row,
1581                                 GtkSelectionData *selection_data,
1582                                 DndHelper        *helper)
1583 {
1584         ModestMailOperation *mail_op = NULL;
1585         GtkTreeIter parent_iter, iter;
1586         TnyFolderStore *parent_folder = NULL;
1587         TnyFolder *folder = NULL;
1588         gboolean forbidden = TRUE;
1589
1590         /* check the folder rules for the destination */
1591         folder = tree_path_to_folder (dest_model, dest_row);
1592         if (folder) {
1593                 ModestTnyFolderRules rules =
1594                         modest_tny_folder_get_rules (folder);
1595                 forbidden = rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE;
1596                 if (forbidden)
1597                         g_debug ("folder rules: cannot write to that folder");
1598                 g_object_unref (folder);
1599         }
1600         
1601         if (!forbidden) {
1602                 /* check the folder rules for the source */
1603                 folder = tree_path_to_folder (source_model, helper->source_row);
1604                 if (folder) {
1605                         ModestTnyFolderRules rules =
1606                                 modest_tny_folder_get_rules (folder);
1607                         forbidden = rules & MODEST_FOLDER_RULES_FOLDER_NON_MOVEABLE;
1608                         if (forbidden)
1609                                 g_debug ("folder rules: cannot move that folder");
1610                         g_object_unref (folder);
1611                 }
1612         }
1613
1614         
1615         /* Check if the drag is possible */
1616         if (forbidden || !gtk_tree_path_compare (helper->source_row, dest_row)) {
1617
1618                 gtk_drag_finish (helper->context, FALSE, FALSE, helper->time);
1619                 gtk_tree_path_free (helper->source_row);        
1620                 g_slice_free (DndHelper, helper);
1621                 return;
1622         }
1623
1624         /* Get data */
1625         gtk_tree_model_get_iter (source_model, &parent_iter, dest_row);
1626         gtk_tree_model_get_iter (source_model, &iter, helper->source_row);
1627         gtk_tree_model_get (source_model, &parent_iter, 
1628                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, 
1629                             &parent_folder, -1);
1630         gtk_tree_model_get (source_model, &iter,
1631                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
1632                             &folder, -1);
1633
1634         /* Offer the connection dialog if necessary, for the destination parent folder and source folder: */
1635         if (modest_platform_connect_and_wait_if_network_folderstore (NULL, parent_folder) && 
1636                 modest_platform_connect_and_wait_if_network_folderstore (NULL, TNY_FOLDER_STORE (folder))) {
1637                 /* Do the mail operation */
1638                 mail_op = modest_mail_operation_new_with_error_handling (MODEST_MAIL_OPERATION_TYPE_RECEIVE, 
1639                                                                  NULL,
1640                                                                  modest_ui_actions_move_folder_error_handler,
1641                                                                  NULL);
1642                 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (), 
1643                                          mail_op);
1644                 g_signal_connect (G_OBJECT (mail_op), "progress-changed",
1645                                   G_CALLBACK (on_progress_changed), helper);
1646
1647                 modest_mail_operation_xfer_folder (mail_op, 
1648                                            folder, 
1649                                            parent_folder,
1650                                            helper->delete_source,
1651                                            NULL,
1652                                            NULL);
1653
1654                 g_object_unref (G_OBJECT (mail_op));    
1655         }
1656         
1657         /* Frees */
1658         g_object_unref (G_OBJECT (parent_folder));
1659         g_object_unref (G_OBJECT (folder));
1660 }
1661
1662 /*
1663  * This function receives the data set by the "drag-data-get" signal
1664  * handler. This information comes within the #GtkSelectionData. This
1665  * function will manage both the drags of folders of the treeview and
1666  * drags of headers of the header view widget.
1667  */
1668 static void 
1669 on_drag_data_received (GtkWidget *widget, 
1670                        GdkDragContext *context, 
1671                        gint x, 
1672                        gint y, 
1673                        GtkSelectionData *selection_data, 
1674                        guint target_type, 
1675                        guint time, 
1676                        gpointer data)
1677 {
1678         GtkWidget *source_widget;
1679         GtkTreeModel *dest_model, *source_model;
1680         GtkTreePath *source_row, *dest_row;
1681         GtkTreeViewDropPosition pos;
1682         gboolean success = FALSE, delete_source = FALSE;
1683         DndHelper *helper = NULL; 
1684
1685         /* Do not allow further process */
1686         g_signal_stop_emission_by_name (widget, "drag-data-received");
1687         source_widget = gtk_drag_get_source_widget (context);
1688
1689         /* Get the action */
1690         if (context->action == GDK_ACTION_MOVE) {
1691                 delete_source = TRUE;
1692
1693                 /* Notify that there is no folder selected. We need to
1694                    do this in order to update the headers view (and
1695                    its monitors, because when moving, the old folder
1696                    won't longer exist. We can not wait for the end of
1697                    the operation, because the operation won't start if
1698                    the folder is in use */
1699                 if (source_widget == widget) {
1700                         ModestFolderViewPrivate *priv;
1701
1702                         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
1703                         if (priv->cur_folder_store) {
1704                                 g_object_unref (priv->cur_folder_store);
1705                                 priv->cur_folder_store = NULL;
1706                         }
1707
1708                         g_signal_emit (G_OBJECT (widget), 
1709                                        signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0, NULL, FALSE);
1710                 }
1711         }
1712
1713         /* Check if the get_data failed */
1714         if (selection_data == NULL || selection_data->length < 0)
1715                 gtk_drag_finish (context, success, FALSE, time);
1716
1717         /* Get the models */
1718         gtk_tree_get_row_drag_data (selection_data,
1719                                     &source_model,
1720                                     &source_row);
1721
1722         /* Select the destination model */
1723         if (source_widget == widget) {
1724                 dest_model = source_model;
1725         } else {
1726                 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
1727         }
1728
1729         /* Get the path to the destination row. Can not call
1730            gtk_tree_view_get_drag_dest_row() because the source row
1731            is not selected anymore */
1732         gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget), x, y,
1733                                            &dest_row, &pos);
1734
1735         /* Only allow drops IN other rows */
1736         if (!dest_row || pos == GTK_TREE_VIEW_DROP_BEFORE || pos == GTK_TREE_VIEW_DROP_AFTER)
1737                 gtk_drag_finish (context, success, FALSE, time);
1738
1739         /* Create the helper */
1740         helper = g_slice_new0 (DndHelper);
1741         helper->delete_source = delete_source;
1742         helper->source_row = gtk_tree_path_copy (source_row);
1743         helper->context = context;
1744         helper->time = time;
1745
1746         /* Drags from the header view */
1747         if (source_widget != widget) {
1748
1749                 drag_and_drop_from_header_view (source_model,
1750                                                 dest_model,
1751                                                 dest_row,
1752                                                 helper);
1753         } else {
1754
1755
1756                 drag_and_drop_from_folder_view (source_model,
1757                                                 dest_model,
1758                                                 dest_row,
1759                                                 selection_data, 
1760                                                 helper);
1761         }
1762
1763         /* Frees */
1764         gtk_tree_path_free (source_row);
1765         gtk_tree_path_free (dest_row);
1766 }
1767
1768 /*
1769  * We define a "drag-drop" signal handler because we do not want to
1770  * use the default one, because the default one always calls
1771  * gtk_drag_finish and we prefer to do it in the "drag-data-received"
1772  * signal handler, because there we have all the information available
1773  * to know if the dnd was a success or not.
1774  */
1775 static gboolean
1776 drag_drop_cb (GtkWidget      *widget,
1777               GdkDragContext *context,
1778               gint            x,
1779               gint            y,
1780               guint           time,
1781               gpointer        user_data) 
1782 {
1783         gpointer target;
1784
1785         if (!context->targets)
1786                 return FALSE;
1787
1788         /* Check if we're dragging a folder row */
1789         target = gtk_drag_dest_find_target (widget, context, NULL);
1790
1791         /* Request the data from the source. */
1792         gtk_drag_get_data(widget, context, target, time);
1793
1794     return TRUE;
1795 }
1796
1797 /*
1798  * This function expands a node of a tree view if it's not expanded
1799  * yet. Not sure why it needs the threads stuff, but gtk+`example code
1800  * does that, so that's why they're here.
1801  */
1802 static gint
1803 expand_row_timeout (gpointer data)
1804 {
1805         GtkTreeView *tree_view = data;
1806         GtkTreePath *dest_path = NULL;
1807         GtkTreeViewDropPosition pos;
1808         gboolean result = FALSE;
1809         
1810         GDK_THREADS_ENTER ();
1811         
1812         gtk_tree_view_get_drag_dest_row (tree_view,
1813                                          &dest_path,
1814                                          &pos);
1815         
1816         if (dest_path &&
1817             (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER ||
1818              pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)) {
1819                 gtk_tree_view_expand_row (tree_view, dest_path, FALSE);
1820                 gtk_tree_path_free (dest_path);
1821         }
1822         else {
1823                 if (dest_path)
1824                         gtk_tree_path_free (dest_path);
1825                 
1826                 result = TRUE;
1827         }
1828         
1829         GDK_THREADS_LEAVE ();
1830
1831         return result;
1832 }
1833
1834 /*
1835  * This function is called whenever the pointer is moved over a widget
1836  * while dragging some data. It installs a timeout that will expand a
1837  * node of the treeview if not expanded yet. This function also calls
1838  * gdk_drag_status in order to set the suggested action that will be
1839  * used by the "drag-data-received" signal handler to know if we
1840  * should do a move or just a copy of the data.
1841  */
1842 static gboolean
1843 on_drag_motion (GtkWidget      *widget,
1844                 GdkDragContext *context,
1845                 gint            x,
1846                 gint            y,
1847                 guint           time,
1848                 gpointer        user_data)  
1849 {
1850         GtkTreeViewDropPosition pos;
1851         GtkTreePath *dest_row;
1852         ModestFolderViewPrivate *priv;
1853         GdkDragAction suggested_action;
1854         gboolean valid_location = FALSE;
1855
1856         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
1857
1858         if (priv->timer_expander != 0) {
1859                 g_source_remove (priv->timer_expander);
1860                 priv->timer_expander = 0;
1861         }
1862
1863         gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
1864                                            x, y,
1865                                            &dest_row,
1866                                            &pos);
1867
1868         /* Do not allow drops between folders */
1869         if (!dest_row ||
1870             pos == GTK_TREE_VIEW_DROP_BEFORE ||
1871             pos == GTK_TREE_VIEW_DROP_AFTER) {
1872                 gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW (widget), NULL, 0);
1873                 gdk_drag_status(context, 0, time);
1874                 valid_location = FALSE;
1875                 goto out;
1876         } else {
1877                 valid_location = TRUE;
1878         }
1879
1880         /* Expand the selected row after 1/2 second */
1881         if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), dest_row)) {
1882                 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), dest_row, pos);
1883                 priv->timer_expander = g_timeout_add (500, expand_row_timeout, widget);
1884         }
1885
1886         /* Select the desired action. By default we pick MOVE */
1887         suggested_action = GDK_ACTION_MOVE;
1888
1889         if (context->actions == GDK_ACTION_COPY)
1890             gdk_drag_status(context, GDK_ACTION_COPY, time);
1891         else if (context->actions == GDK_ACTION_MOVE)
1892             gdk_drag_status(context, GDK_ACTION_MOVE, time);
1893         else if (context->actions & suggested_action)
1894             gdk_drag_status(context, suggested_action, time);
1895         else
1896             gdk_drag_status(context, GDK_ACTION_DEFAULT, time);
1897
1898  out:
1899         if (dest_row)
1900                 gtk_tree_path_free (dest_row);
1901         g_signal_stop_emission_by_name (widget, "drag-motion");
1902         return valid_location;
1903 }
1904
1905
1906 /* Folder view drag types */
1907 const GtkTargetEntry folder_view_drag_types[] =
1908 {
1909         { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, MODEST_FOLDER_ROW },
1910         { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_APP,    MODEST_HEADER_ROW }
1911 };
1912
1913 /*
1914  * This function sets the treeview as a source and a target for dnd
1915  * events. It also connects all the requirede signals.
1916  */
1917 static void
1918 setup_drag_and_drop (GtkTreeView *self)
1919 {
1920         /* Set up the folder view as a dnd destination. Set only the
1921            highlight flag, otherwise gtk will have a different
1922            behaviour */
1923         gtk_drag_dest_set (GTK_WIDGET (self),
1924                            GTK_DEST_DEFAULT_HIGHLIGHT,
1925                            folder_view_drag_types,
1926                            G_N_ELEMENTS (folder_view_drag_types),
1927                            GDK_ACTION_MOVE | GDK_ACTION_COPY);
1928
1929         g_signal_connect (G_OBJECT (self),
1930                           "drag_data_received",
1931                           G_CALLBACK (on_drag_data_received),
1932                           NULL);
1933
1934
1935         /* Set up the treeview as a dnd source */
1936         gtk_drag_source_set (GTK_WIDGET (self),
1937                              GDK_BUTTON1_MASK,
1938                              folder_view_drag_types,
1939                              G_N_ELEMENTS (folder_view_drag_types),
1940                              GDK_ACTION_MOVE | GDK_ACTION_COPY);
1941
1942         g_signal_connect (G_OBJECT (self),
1943                           "drag_motion",
1944                           G_CALLBACK (on_drag_motion),
1945                           NULL);
1946         
1947         g_signal_connect (G_OBJECT (self),
1948                           "drag_data_get",
1949                           G_CALLBACK (on_drag_data_get),
1950                           NULL);
1951
1952         g_signal_connect (G_OBJECT (self),
1953                           "drag_drop",
1954                           G_CALLBACK (drag_drop_cb),
1955                           NULL);
1956 }
1957
1958 /*
1959  * This function manages the navigation through the folders using the
1960  * keyboard or the hardware keys in the device
1961  */
1962 static gboolean
1963 on_key_pressed (GtkWidget *self,
1964                 GdkEventKey *event,
1965                 gpointer user_data)
1966 {
1967         GtkTreeSelection *selection;
1968         GtkTreeIter iter;
1969         GtkTreeModel *model;
1970         gboolean retval = FALSE;
1971
1972         /* Up and Down are automatically managed by the treeview */
1973         if (event->keyval == GDK_Return) {
1974                 /* Expand/Collapse the selected row */
1975                 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1976                 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
1977                         GtkTreePath *path;
1978
1979                         path = gtk_tree_model_get_path (model, &iter);
1980
1981                         if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (self), path))
1982                                 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1983                         else
1984                                 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1985                         gtk_tree_path_free (path);
1986                 }
1987                 /* No further processing */
1988                 retval = TRUE;
1989         }
1990
1991         return retval;
1992 }
1993
1994 /*
1995  * We listen to the changes in the local folder account name key,
1996  * because we want to show the right name in the view. The local
1997  * folder account name corresponds to the device name in the Maemo
1998  * version. We do this because we do not want to query gconf on each
1999  * tree view refresh. It's better to cache it and change whenever
2000  * necessary.
2001  */
2002 static void 
2003 on_configuration_key_changed (ModestConf* conf, 
2004                               const gchar *key, 
2005                               ModestConfEvent event,
2006                               ModestConfNotificationId id, 
2007                               ModestFolderView *self)
2008 {
2009         ModestFolderViewPrivate *priv;
2010
2011
2012         g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
2013         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2014
2015         /* Do not listen for changes in other namespaces */
2016         if (priv->notification_id != id)
2017                  return;
2018          
2019         if (!strcmp (key, MODEST_CONF_DEVICE_NAME)) {
2020                 g_free (priv->local_account_name);
2021
2022                 if (event == MODEST_CONF_EVENT_KEY_UNSET)
2023                         priv->local_account_name = g_strdup (MODEST_LOCAL_FOLDERS_DEFAULT_DISPLAY_NAME);
2024                 else
2025                         priv->local_account_name = modest_conf_get_string (modest_runtime_get_conf(),
2026                                                                            MODEST_CONF_DEVICE_NAME, NULL);
2027
2028                 /* Force a redraw */
2029 #if GTK_CHECK_VERSION(2, 8, 0) /* gtk_tree_view_column_queue_resize is only available in GTK+ 2.8 */
2030                 GtkTreeViewColumn * tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self), 
2031                                                                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN);
2032                 gtk_tree_view_column_queue_resize (tree_column);
2033 #endif
2034         }
2035 }
2036
2037 void
2038 modest_folder_view_set_style (ModestFolderView *self,
2039                               ModestFolderViewStyle style)
2040 {
2041         ModestFolderViewPrivate *priv;
2042
2043         g_return_if_fail (self);
2044         
2045         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2046
2047         priv->style = style;
2048 }
2049
2050 void
2051 modest_folder_view_set_account_id_of_visible_server_account (ModestFolderView *self,
2052                                                              const gchar *account_id)
2053 {
2054         ModestFolderViewPrivate *priv;
2055         GtkTreeModel *model;
2056
2057         g_return_if_fail (self);
2058         
2059         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2060
2061         /* This will be used by the filter_row callback,
2062          * to decided which rows to show: */
2063         if (priv->visible_account_id) {
2064                 g_free (priv->visible_account_id);
2065                 priv->visible_account_id = NULL;
2066         }
2067         if (account_id)
2068                 priv->visible_account_id = g_strdup (account_id);
2069
2070         /* Refilter */
2071         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2072         if (GTK_IS_TREE_MODEL_FILTER (model))
2073                 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2074
2075         /* Save settings to gconf */
2076         modest_widget_memory_save (modest_runtime_get_conf (), G_OBJECT(self),
2077                                    MODEST_CONF_FOLDER_VIEW_KEY);
2078 }
2079
2080 const gchar *
2081 modest_folder_view_get_account_id_of_visible_server_account (ModestFolderView *self)
2082 {
2083         ModestFolderViewPrivate *priv;
2084
2085         g_return_val_if_fail (self, NULL);
2086         
2087         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2088
2089         return (const gchar *) priv->visible_account_id;
2090 }
2091
2092 static gboolean
2093 find_inbox_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *inbox_iter)
2094 {
2095         do {
2096                 GtkTreeIter child;
2097                 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2098
2099                 gtk_tree_model_get (model, iter, 
2100                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, 
2101                                     &type, -1);
2102                         
2103                 gboolean result = FALSE;
2104                 if (type == TNY_FOLDER_TYPE_INBOX) {
2105                         result = TRUE;
2106                 }               
2107                 if (result) {
2108                         *inbox_iter = *iter;
2109                         return TRUE;
2110                 }
2111
2112                 if (gtk_tree_model_iter_children (model, &child, iter)) {
2113                         if (find_inbox_iter (model, &child, inbox_iter))
2114                                 return TRUE;
2115                 }
2116
2117         } while (gtk_tree_model_iter_next (model, iter));
2118
2119         return FALSE;
2120 }
2121
2122
2123
2124
2125 void 
2126 modest_folder_view_select_first_inbox_or_local (ModestFolderView *self)
2127 {
2128         GtkTreeModel *model;
2129         GtkTreeIter iter, inbox_iter;
2130         GtkTreeSelection *sel;
2131         GtkTreePath *path = NULL;
2132
2133         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2134         if (!model)
2135                 return;
2136
2137         expand_root_items (self);
2138         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2139
2140         gtk_tree_model_get_iter_first (model, &iter);
2141
2142         if (find_inbox_iter (model, &iter, &inbox_iter))
2143                 path = gtk_tree_model_get_path (model, &inbox_iter);
2144         else
2145                 path = gtk_tree_path_new_first ();
2146
2147         /* Select the row and free */
2148         gtk_tree_view_set_cursor (GTK_TREE_VIEW (self), path, NULL, FALSE);
2149         gtk_tree_path_free (path);
2150 }
2151
2152
2153 /* recursive */
2154 static gboolean
2155 find_folder_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *folder_iter, 
2156                   TnyFolder* folder)
2157 {
2158         do {
2159                 GtkTreeIter child;
2160                 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2161                 TnyFolder* a_folder;
2162                 gchar *name = NULL;
2163                 
2164                 gtk_tree_model_get (model, iter, 
2165                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &a_folder,
2166                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name,
2167                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type, 
2168                                     -1);                
2169         
2170                 g_debug ("===> %s (%p ---- %p)", name, a_folder, folder);
2171                 g_free (name);
2172
2173                 if (folder == a_folder) {
2174                         g_object_unref (a_folder);
2175                         *folder_iter = *iter;
2176                         return TRUE;
2177                 }
2178                 g_object_unref (a_folder);
2179                 
2180                 if (gtk_tree_model_iter_children (model, &child, iter)) {
2181                         if (find_folder_iter (model, &child, folder_iter, folder)) 
2182                                 return TRUE;
2183                 }
2184
2185         } while (gtk_tree_model_iter_next (model, iter));
2186
2187         return FALSE;
2188 }
2189
2190
2191 static void
2192 on_row_changed_maybe_select_folder (GtkTreeModel *tree_model, GtkTreePath  *path, GtkTreeIter *iter,
2193                                     ModestFolderView *self)
2194 {
2195         ModestFolderViewPrivate *priv = NULL;
2196         GtkTreeSelection *sel;
2197
2198         if (!MODEST_IS_FOLDER_VIEW(self))
2199                 return;
2200         
2201         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2202         
2203         if (priv->folder_to_select) {
2204                 
2205                 if (!modest_folder_view_select_folder (self, priv->folder_to_select,
2206                                                        FALSE)) {
2207                         GtkTreePath *path;
2208                         path = gtk_tree_model_get_path (tree_model, iter);
2209                         gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
2210                         
2211                         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2212
2213                         gtk_tree_selection_select_iter (sel, iter);
2214                         gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
2215
2216                         gtk_tree_path_free (path);
2217                 
2218                 }
2219                 g_object_unref (priv->folder_to_select);
2220                 priv->folder_to_select = NULL;
2221         }
2222 }
2223
2224
2225 gboolean
2226 modest_folder_view_select_folder (ModestFolderView *self, TnyFolder *folder, 
2227                                   gboolean after_change)
2228 {
2229         GtkTreeModel *model;
2230         GtkTreeIter iter, folder_iter;
2231         GtkTreeSelection *sel;
2232         ModestFolderViewPrivate *priv = NULL;
2233         
2234         g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (self), FALSE);     
2235         g_return_val_if_fail (TNY_IS_FOLDER (folder), FALSE);   
2236                 
2237         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2238
2239         if (after_change) {
2240
2241                 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2242                 gtk_tree_selection_unselect_all (sel);
2243
2244                 if (priv->folder_to_select)
2245                         g_object_unref(priv->folder_to_select);
2246                 priv->folder_to_select = TNY_FOLDER(g_object_ref(folder));
2247                 return TRUE;
2248         }
2249                 
2250         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2251         if (!model)
2252                 return FALSE;
2253
2254                 
2255         gtk_tree_model_get_iter_first (model, &iter);
2256         if (find_folder_iter (model, &iter, &folder_iter, folder)) {
2257                 GtkTreePath *path;
2258
2259                 path = gtk_tree_model_get_path (model, &folder_iter);
2260                 gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
2261
2262                 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2263                 gtk_tree_selection_select_iter (sel, &folder_iter);
2264                 gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
2265
2266                 gtk_tree_path_free (path);
2267                 return TRUE;
2268         }
2269         return FALSE;
2270 }
2271
2272
2273 void 
2274 modest_folder_view_copy_selection (ModestFolderView *folder_view)
2275 {
2276         /* Copy selection */
2277         _clipboard_set_selected_data (folder_view, FALSE);
2278 }
2279
2280 void 
2281 modest_folder_view_cut_selection (ModestFolderView *folder_view)
2282 {
2283         ModestFolderViewPrivate *priv = NULL;
2284         GtkTreeModel *model = NULL;
2285         const gchar **hidding = NULL;
2286         guint i, n_selected;
2287
2288         g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view));
2289         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
2290
2291         /* Copy selection */
2292         if (!_clipboard_set_selected_data (folder_view, TRUE))
2293                 return;
2294
2295         /* Get hidding ids */
2296         hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected); 
2297         
2298         /* Clear hidding array created by previous cut operation */
2299         _clear_hidding_filter (MODEST_FOLDER_VIEW (folder_view));
2300
2301         /* Copy hidding array */
2302         priv->n_selected = n_selected;
2303         priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
2304         for (i=0; i < n_selected; i++) 
2305                 priv->hidding_ids[i] = g_strdup(hidding[i]);            
2306
2307         /* Hide cut folders */
2308         model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
2309         gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2310 }
2311
2312 void
2313 modest_folder_view_show_non_move_folders (ModestFolderView *folder_view,
2314                                     gboolean show)
2315 {
2316         ModestFolderViewPrivate* priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
2317         priv->show_non_move = show;
2318         modest_folder_view_update_model(folder_view,
2319                                                                                                                                         TNY_ACCOUNT_STORE(modest_runtime_get_account_store()));
2320 }
2321
2322 /* Returns FALSE if it did not selected anything */
2323 static gboolean
2324 _clipboard_set_selected_data (ModestFolderView *folder_view,
2325                               gboolean delete)
2326 {
2327         ModestFolderViewPrivate *priv = NULL;
2328         TnyFolderStore *folder = NULL;
2329         gboolean retval = FALSE;
2330
2331         g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (folder_view), FALSE);
2332         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
2333                 
2334         /* Set selected data on clipboard   */
2335         g_return_val_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard), FALSE);
2336         folder = modest_folder_view_get_selected (folder_view);
2337
2338         /* Do not allow to select an account */
2339         if (TNY_IS_FOLDER (folder)) {
2340                 modest_email_clipboard_set_data (priv->clipboard, TNY_FOLDER(folder), NULL, delete);
2341                 retval = TRUE;
2342         }
2343
2344         /* Free */
2345         g_object_unref (folder);
2346
2347         return retval;
2348 }
2349
2350 static void
2351 _clear_hidding_filter (ModestFolderView *folder_view) 
2352 {
2353         ModestFolderViewPrivate *priv;
2354         guint i;
2355         
2356         g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view)); 
2357         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
2358
2359         if (priv->hidding_ids != NULL) {
2360                 for (i=0; i < priv->n_selected; i++) 
2361                         g_free (priv->hidding_ids[i]);
2362                 g_free(priv->hidding_ids);
2363         }       
2364 }
2365
2366