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