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