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