* Fixed a lot of reference leaks
[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-folder.h>
45 #include <modest-tny-local-folders-account.h>
46 #include <modest-tny-outbox-account.h>
47 #include <modest-marshal.h>
48 #include <modest-icon-names.h>
49 #include <modest-tny-account-store.h>
50 #include <modest-text-utils.h>
51 #include <modest-runtime.h>
52 #include "modest-folder-view.h"
53 #include <modest-dnd.h>
54 #include <modest-platform.h>
55 #include <modest-widget-memory.h>
56 #include <modest-ui-actions.h>
57
58 /* 'private'/'protected' functions */
59 static void modest_folder_view_class_init  (ModestFolderViewClass *klass);
60 static void modest_folder_view_init        (ModestFolderView *obj);
61 static void modest_folder_view_finalize    (GObject *obj);
62
63 static void         tny_account_store_view_init (gpointer g, 
64                                                  gpointer iface_data);
65
66 static void         modest_folder_view_set_account_store (TnyAccountStoreView *self, 
67                                                           TnyAccountStore     *account_store);
68
69 static void         on_selection_changed   (GtkTreeSelection *sel, gpointer data);
70
71 static void         on_account_update      (TnyAccountStore *account_store, 
72                                             const gchar *account,
73                                             gpointer user_data);
74
75 static void         on_account_removed     (TnyAccountStore *self, 
76                                             TnyAccount *account,
77                                             gpointer user_data);
78
79 static void         on_accounts_reloaded   (TnyAccountStore *store, 
80                                             gpointer user_data);
81
82 static gint         cmp_rows               (GtkTreeModel *tree_model, 
83                                             GtkTreeIter *iter1, 
84                                             GtkTreeIter *iter2,
85                                             gpointer user_data);
86
87 static gboolean     filter_row             (GtkTreeModel *model,
88                                             GtkTreeIter *iter,
89                                             gpointer data);
90
91 static gboolean     on_key_pressed         (GtkWidget *self,
92                                             GdkEventKey *event,
93                                             gpointer user_data);
94
95 static void         on_configuration_key_changed         (ModestConf* conf, 
96                                                           const gchar *key, 
97                                                           ModestConfEvent event, 
98                                                           ModestFolderView *self);
99
100 /* DnD functions */
101 static void         on_drag_data_get       (GtkWidget *widget, 
102                                             GdkDragContext *context, 
103                                             GtkSelectionData *selection_data, 
104                                             guint info, 
105                                             guint time, 
106                                             gpointer data);
107
108 static void         on_drag_data_received  (GtkWidget *widget, 
109                                             GdkDragContext *context, 
110                                             gint x, 
111                                             gint y, 
112                                             GtkSelectionData *selection_data, 
113                                             guint info, 
114                                             guint time, 
115                                             gpointer data);
116
117 static gboolean     on_drag_motion         (GtkWidget      *widget,
118                                             GdkDragContext *context,
119                                             gint            x,
120                                             gint            y,
121                                             guint           time,
122                                             gpointer        user_data);
123
124 static gint         expand_row_timeout     (gpointer data);
125
126 static void         setup_drag_and_drop    (GtkTreeView *self);
127
128 static void          _clipboard_set_selected_data (ModestFolderView *folder_view, gboolean delete);
129
130 static void          _clear_hidding_filter (ModestFolderView *folder_view);
131
132
133
134 enum {
135         FOLDER_SELECTION_CHANGED_SIGNAL,
136         FOLDER_DISPLAY_NAME_CHANGED_SIGNAL,
137         LAST_SIGNAL
138 };
139
140 typedef struct _ModestFolderViewPrivate ModestFolderViewPrivate;
141 struct _ModestFolderViewPrivate {
142         TnyAccountStore      *account_store;
143         TnyFolderStore       *cur_folder_store;
144
145         gulong                account_update_signal;
146         gulong                changed_signal;
147         gulong                accounts_reloaded_signal;
148         gulong                account_removed_signal;
149         gulong                conf_key_signal;
150         
151         /* not unref this object, its a singlenton */
152         ModestEmailClipboard *clipboard;
153
154         /* Filter tree model */
155         gchar **hidding_ids;
156         guint n_selected;
157
158         TnyFolderStoreQuery  *query;
159         guint                 timer_expander;
160
161         gchar                *local_account_name;
162         gchar                *visible_account_id;
163         ModestFolderViewStyle style;
164
165         gboolean              reselect; /* we use this to force a reselection of the INBOX */
166 };
167 #define MODEST_FOLDER_VIEW_GET_PRIVATE(o)                       \
168         (G_TYPE_INSTANCE_GET_PRIVATE((o),                       \
169                                      MODEST_TYPE_FOLDER_VIEW,   \
170                                      ModestFolderViewPrivate))
171 /* globals */
172 static GObjectClass *parent_class = NULL;
173
174 static guint signals[LAST_SIGNAL] = {0}; 
175
176 GType
177 modest_folder_view_get_type (void)
178 {
179         static GType my_type = 0;
180         if (!my_type) {
181                 static const GTypeInfo my_info = {
182                         sizeof(ModestFolderViewClass),
183                         NULL,           /* base init */
184                         NULL,           /* base finalize */
185                         (GClassInitFunc) modest_folder_view_class_init,
186                         NULL,           /* class finalize */
187                         NULL,           /* class data */
188                         sizeof(ModestFolderView),
189                         1,              /* n_preallocs */
190                         (GInstanceInitFunc) modest_folder_view_init,
191                         NULL
192                 };
193
194                 static const GInterfaceInfo tny_account_store_view_info = {
195                         (GInterfaceInitFunc) tny_account_store_view_init, /* interface_init */
196                         NULL,         /* interface_finalize */
197                         NULL          /* interface_data */
198                 };
199
200                                 
201                 my_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
202                                                   "ModestFolderView",
203                                                   &my_info, 0);
204
205                 g_type_add_interface_static (my_type, 
206                                              TNY_TYPE_ACCOUNT_STORE_VIEW, 
207                                              &tny_account_store_view_info);
208         }
209         return my_type;
210 }
211
212 static void
213 modest_folder_view_class_init (ModestFolderViewClass *klass)
214 {
215         GObjectClass *gobject_class;
216         gobject_class = (GObjectClass*) klass;
217
218         parent_class            = g_type_class_peek_parent (klass);
219         gobject_class->finalize = modest_folder_view_finalize;
220
221         g_type_class_add_private (gobject_class,
222                                   sizeof(ModestFolderViewPrivate));
223         
224         signals[FOLDER_SELECTION_CHANGED_SIGNAL] = 
225                 g_signal_new ("folder_selection_changed",
226                               G_TYPE_FROM_CLASS (gobject_class),
227                               G_SIGNAL_RUN_FIRST,
228                               G_STRUCT_OFFSET (ModestFolderViewClass,
229                                                folder_selection_changed),
230                               NULL, NULL,
231                               modest_marshal_VOID__POINTER_BOOLEAN,
232                               G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_BOOLEAN);
233
234         /*
235          * This signal is emitted whenever the currently selected
236          * folder display name is computed. Note that the name could
237          * be different to the folder name, because we could append
238          * the unread messages count to the folder name to build the
239          * folder display name
240          */
241         signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL] = 
242                 g_signal_new ("folder-display-name-changed",
243                               G_TYPE_FROM_CLASS (gobject_class),
244                               G_SIGNAL_RUN_FIRST,
245                               G_STRUCT_OFFSET (ModestFolderViewClass,
246                                                folder_display_name_changed),
247                               NULL, NULL,
248                               g_cclosure_marshal_VOID__STRING,
249                               G_TYPE_NONE, 1, G_TYPE_STRING);
250 }
251
252 static void
253 text_cell_data  (GtkTreeViewColumn *column,  GtkCellRenderer *renderer,
254                  GtkTreeModel *tree_model,  GtkTreeIter *iter,  gpointer data)
255 {
256         ModestFolderViewPrivate *priv;
257         GObject *rendobj;
258         gchar *fname = NULL;
259         gint unread = 0;
260         gint all = 0;
261         TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
262         GObject *instance = NULL;
263         
264         g_return_if_fail (column);
265         g_return_if_fail (tree_model);
266
267         gtk_tree_model_get (tree_model, iter,
268                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &fname,
269                             TNY_GTK_FOLDER_STORE_TREE_MODEL_ALL_COLUMN, &all,
270                             TNY_GTK_FOLDER_STORE_TREE_MODEL_UNREAD_COLUMN, &unread,
271                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
272                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
273                             -1);
274         rendobj = G_OBJECT(renderer);
275  
276         if (!fname)
277                 return;
278
279         if (!instance) {
280                 g_free (fname);
281                 return;
282         }
283
284         
285         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE (data);
286         
287         gchar *item_name = NULL;
288         gint item_weight = 400;
289         
290         if (type != TNY_FOLDER_TYPE_ROOT) {
291                 gint number = 0;
292                 
293                 if (modest_tny_folder_is_local_folder (TNY_FOLDER (instance))) {
294                         type = modest_tny_folder_get_local_folder_type (TNY_FOLDER (instance));
295                         if (type != TNY_FOLDER_TYPE_UNKNOWN) {
296                                 g_free (fname);
297                                 fname = g_strdup(modest_local_folder_info_get_type_display_name (type));
298                         }
299                 }
300
301                 /* Select the number to show: the unread or unsent messages */
302                 if ((type == TNY_FOLDER_TYPE_DRAFTS) || (type == TNY_FOLDER_TYPE_OUTBOX))
303                         number = all;
304                 else
305                         number = unread;
306                 
307                 /* Use bold font style if there are unread or unset messages */
308                 if (number > 0) {
309                         item_name = g_strdup_printf ("%s (%d)", fname, number);
310                         item_weight = 800;
311                 } else {
312                         item_name = g_strdup (fname);
313                         item_weight = 400;
314                 }
315                 
316         } else if (TNY_IS_ACCOUNT (instance)) {
317                 /* If it's a server account */
318                 if (modest_tny_account_is_virtual_local_folders (
319                                 TNY_ACCOUNT (instance))) {
320                         item_name = g_strdup (priv->local_account_name);
321                         item_weight = 400;
322                 } else {
323                         item_name = g_strdup (fname);
324                         item_weight = 800;
325                 }
326         }
327         
328         if (!item_name)
329                 item_name = g_strdup ("unknown");
330                         
331         if (item_name && item_weight) {
332                 /* Set the name in the treeview cell: */
333                 g_object_set (rendobj,"text", item_name, "weight", item_weight, NULL);
334                 
335                 /* Notify display name observers */
336                 if (G_OBJECT (priv->cur_folder_store) == instance) {
337                         g_signal_emit (G_OBJECT(data),
338                                                signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
339                                                item_name);
340                 }
341                 
342                 g_free (item_name);
343                 
344         }
345         
346         g_object_unref (G_OBJECT (instance));
347         g_free (fname);
348 }
349
350
351
352 static void
353 icon_cell_data  (GtkTreeViewColumn *column,  GtkCellRenderer *renderer,
354                  GtkTreeModel *tree_model,  GtkTreeIter *iter, gpointer data)
355 {
356         GObject *rendobj = NULL, *instance = NULL;
357         GdkPixbuf *pixbuf = NULL;
358         TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
359         gchar *fname = NULL;
360         const gchar *account_id = NULL;
361         gint unread = 0;
362         
363         rendobj = G_OBJECT(renderer);
364         gtk_tree_model_get (tree_model, iter,
365                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
366                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &fname,
367                             TNY_GTK_FOLDER_STORE_TREE_MODEL_UNREAD_COLUMN, &unread,
368                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
369                             -1);
370
371         if (!fname)
372                 return;
373
374         if (!instance) {
375                 g_free (fname);
376                 return;
377         }
378
379         /* We include the MERGE type here because it's used to create
380            the local OUTBOX folder */
381         if (type == TNY_FOLDER_TYPE_NORMAL || 
382             type == TNY_FOLDER_TYPE_UNKNOWN ||
383             type == TNY_FOLDER_TYPE_MERGE) {
384                 type = modest_tny_folder_guess_folder_type (TNY_FOLDER (instance));
385         }
386
387         switch (type) {
388         case TNY_FOLDER_TYPE_ROOT:
389                 if (TNY_IS_ACCOUNT (instance)) {
390                         
391                         if (modest_tny_account_is_virtual_local_folders (
392                                 TNY_ACCOUNT (instance))) {
393                                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_LOCAL_FOLDERS);
394                         }
395                         else {
396                                 account_id = tny_account_get_id (TNY_ACCOUNT (instance));
397                                 
398                                 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
399                                         pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_MMC);
400                                 else
401                                         pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_ACCOUNT);
402                         }
403                 }
404                 break;
405         case TNY_FOLDER_TYPE_INBOX:
406             pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_INBOX);
407             break;
408         case TNY_FOLDER_TYPE_OUTBOX:
409                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_OUTBOX);
410                 break;
411         case TNY_FOLDER_TYPE_JUNK:
412                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_JUNK);
413                 break;
414         case TNY_FOLDER_TYPE_SENT:
415                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_SENT);
416                 break;
417         case TNY_FOLDER_TYPE_TRASH:
418                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_TRASH);
419                 break;
420         case TNY_FOLDER_TYPE_DRAFTS:
421                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_DRAFTS);
422                 break;
423         case TNY_FOLDER_TYPE_NORMAL:
424         default:
425                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_NORMAL);
426                 break;
427         }
428         
429         g_object_unref (G_OBJECT (instance));
430         g_free (fname);
431
432         /* Set pixbuf */
433         g_object_set (rendobj, "pixbuf", pixbuf, NULL);
434
435         if (pixbuf != NULL)
436                 g_object_unref (pixbuf);
437 }
438
439 static void
440 add_columns (GtkWidget *treeview)
441 {
442         GtkTreeViewColumn *column;
443         GtkCellRenderer *renderer;
444         GtkTreeSelection *sel;
445
446         /* Create column */
447         column = gtk_tree_view_column_new ();   
448         
449         /* Set icon and text render function */
450         renderer = gtk_cell_renderer_pixbuf_new();
451         gtk_tree_view_column_pack_start (column, renderer, FALSE);
452         gtk_tree_view_column_set_cell_data_func(column, renderer,
453                                                 icon_cell_data, treeview, NULL);
454         
455         renderer = gtk_cell_renderer_text_new();
456         gtk_tree_view_column_pack_start (column, renderer, FALSE);
457         gtk_tree_view_column_set_cell_data_func(column, renderer,
458                                                 text_cell_data, treeview, NULL);
459         
460         /* Set selection mode */
461         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(treeview));
462         gtk_tree_selection_set_mode (sel, GTK_SELECTION_SINGLE);
463
464         /* Set treeview appearance */
465         gtk_tree_view_column_set_spacing (column, 2);
466         gtk_tree_view_column_set_resizable (column, TRUE);
467         gtk_tree_view_column_set_fixed_width (column, TRUE);            
468         gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(treeview), FALSE);
469         gtk_tree_view_set_enable_search (GTK_TREE_VIEW(treeview), FALSE);
470
471         /* Add column */
472         gtk_tree_view_append_column (GTK_TREE_VIEW(treeview),column);
473 }
474
475 static void
476 modest_folder_view_init (ModestFolderView *obj)
477 {
478         ModestFolderViewPrivate *priv;
479         ModestConf *conf;
480         
481         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
482         
483         priv->timer_expander = 0;
484         priv->account_store  = NULL;
485         priv->query          = NULL;
486         priv->style          = MODEST_FOLDER_VIEW_STYLE_SHOW_ALL;
487         priv->cur_folder_store   = NULL;
488         priv->visible_account_id = NULL;
489
490         /* Initialize the local account name */
491         conf = modest_runtime_get_conf();
492         priv->local_account_name = modest_conf_get_string (conf, MODEST_CONF_DEVICE_NAME, NULL);
493
494         /* Init email clipboard */
495         priv->clipboard = modest_runtime_get_email_clipboard ();
496         priv->hidding_ids = NULL;
497         priv->n_selected = 0;
498         priv->reselect = FALSE;
499
500         /* Build treeview */
501         add_columns (GTK_WIDGET (obj));
502
503         /* Setup drag and drop */
504         setup_drag_and_drop (GTK_TREE_VIEW(obj));
505
506         /* Connect signals */
507         g_signal_connect (G_OBJECT (obj), 
508                           "key-press-event", 
509                           G_CALLBACK (on_key_pressed), NULL);
510
511         /*
512          * Track changes in the local account name (in the device it
513          * will be the device name)
514          */
515         priv->conf_key_signal = 
516                 g_signal_connect (G_OBJECT(conf), 
517                                   "key_changed",
518                                   G_CALLBACK(on_configuration_key_changed), obj);
519 }
520
521 static void
522 tny_account_store_view_init (gpointer g, gpointer iface_data)
523 {
524         TnyAccountStoreViewIface *klass = (TnyAccountStoreViewIface *)g;
525
526         klass->set_account_store_func = modest_folder_view_set_account_store;
527
528         return;
529 }
530
531 static void
532 modest_folder_view_finalize (GObject *obj)
533 {
534         ModestFolderViewPrivate *priv;
535         GtkTreeSelection    *sel;
536         
537         g_return_if_fail (obj);
538         
539         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
540
541         if (priv->timer_expander != 0) {
542                 g_source_remove (priv->timer_expander);
543                 priv->timer_expander = 0;
544         }
545
546         if (priv->account_store) {
547                 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
548                                              priv->account_update_signal);
549                 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
550                                              priv->accounts_reloaded_signal);
551                 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
552                                              priv->account_removed_signal);
553                 g_object_unref (G_OBJECT(priv->account_store));
554                 priv->account_store = NULL;
555         }
556
557         if (priv->query) {
558                 g_object_unref (G_OBJECT (priv->query));
559                 priv->query = NULL;
560         }
561
562         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(obj));
563         if (sel)
564                 g_signal_handler_disconnect (G_OBJECT(sel), priv->changed_signal);
565
566         g_free (priv->local_account_name);
567         g_free (priv->visible_account_id);
568         
569         if (priv->conf_key_signal) {
570                 g_signal_handler_disconnect (modest_runtime_get_conf (),
571                                              priv->conf_key_signal);
572                 priv->conf_key_signal = 0;
573         }
574
575         if (priv->cur_folder_store) {
576                 g_object_unref (priv->cur_folder_store);
577                 priv->cur_folder_store = NULL;
578         }
579
580         /* Clear hidding array created by cut operation */
581         _clear_hidding_filter (MODEST_FOLDER_VIEW (obj));
582
583         G_OBJECT_CLASS(parent_class)->finalize (obj);
584 }
585
586
587 static void
588 modest_folder_view_set_account_store (TnyAccountStoreView *self, TnyAccountStore *account_store)
589 {
590         ModestFolderViewPrivate *priv;
591         TnyDevice *device;
592
593         g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
594         g_return_if_fail (TNY_IS_ACCOUNT_STORE (account_store));
595
596         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
597         device = tny_account_store_get_device (account_store);
598
599         if (G_UNLIKELY (priv->account_store)) {
600
601                 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store), 
602                                                    priv->account_update_signal))
603                         g_signal_handler_disconnect (G_OBJECT (priv->account_store), 
604                                                      priv->account_update_signal);
605                 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store), 
606                                                    priv->accounts_reloaded_signal))
607                         g_signal_handler_disconnect (G_OBJECT (priv->account_store), 
608                                                      priv->accounts_reloaded_signal);
609                 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store), 
610                                                    priv->account_removed_signal))
611                         g_signal_handler_disconnect (G_OBJECT (priv->account_store), 
612                                                      priv->account_removed_signal);
613
614                 g_object_unref (G_OBJECT (priv->account_store));
615         }
616
617         priv->account_store = g_object_ref (G_OBJECT (account_store));
618
619         priv->account_update_signal = 
620                 g_signal_connect (G_OBJECT(account_store), "account_update",
621                                   G_CALLBACK (on_account_update), self);
622
623         priv->account_removed_signal = 
624                 g_signal_connect (G_OBJECT(account_store), "account_removed",
625                                   G_CALLBACK (on_account_removed), self);
626
627         priv->accounts_reloaded_signal = 
628                 g_signal_connect (G_OBJECT(account_store), "accounts_reloaded",
629                                   G_CALLBACK (on_accounts_reloaded), self);
630
631         on_accounts_reloaded (account_store, (gpointer ) self);
632         
633         g_object_unref (G_OBJECT (device));
634 }
635
636 static void
637 on_account_removed (TnyAccountStore *account_store, 
638                     TnyAccount *account,
639                     gpointer user_data)
640 {
641         ModestFolderView *self = MODEST_FOLDER_VIEW (user_data);
642         ModestFolderViewPrivate *priv;
643
644         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
645
646         /* If the removed account is the currently viewed one then
647            clear the configuration value. The new visible account will be the default account */
648         if (!strcmp (priv->visible_account_id, tny_account_get_id (account))) {
649                 modest_folder_view_set_account_id_of_visible_server_account (self, NULL);
650         }
651 }
652
653 static void
654 on_account_update (TnyAccountStore *account_store, 
655                    const gchar *account,
656                    gpointer user_data)
657 {
658         ModestFolderView *self = MODEST_FOLDER_VIEW (user_data);
659         ModestFolderViewPrivate *priv;
660
661         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
662         if (!priv->visible_account_id)
663                 modest_widget_memory_restore (modest_runtime_get_conf(), G_OBJECT(self),
664                                               MODEST_CONF_FOLDER_VIEW_KEY);
665
666         if (!modest_folder_view_update_model (self, account_store))
667                 g_printerr ("modest: failed to update model for changes in '%s'",
668                             account);
669 }
670
671 static void 
672 on_accounts_reloaded   (TnyAccountStore *account_store, 
673                         gpointer user_data)
674 {
675         modest_folder_view_update_model (MODEST_FOLDER_VIEW (user_data), account_store);
676 }
677
678 void
679 modest_folder_view_set_title (ModestFolderView *self, const gchar *title)
680 {
681         GtkTreeViewColumn *col;
682         
683         g_return_if_fail (self);
684
685         col = gtk_tree_view_get_column (GTK_TREE_VIEW(self), 0);
686         if (!col) {
687                 g_printerr ("modest: failed get column for title\n");
688                 return;
689         }
690
691         gtk_tree_view_column_set_title (col, title);
692         gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self),
693                                            title != NULL);
694 }
695
696 static gboolean
697 modest_folder_view_on_map (ModestFolderView *self, 
698                            GdkEventExpose *event,
699                            gpointer data)
700 {
701         ModestFolderViewPrivate *priv;
702
703         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
704
705         /* This won't happen often */
706         if (G_UNLIKELY (priv->reselect)) {
707                 /* Select the first inbox or the local account if not found */
708                 modest_folder_view_select_first_inbox_or_local (self);
709                 priv->reselect = FALSE;
710         }
711         return FALSE;
712 }
713
714 GtkWidget*
715 modest_folder_view_new (TnyFolderStoreQuery *query)
716 {
717         GObject *self;
718         ModestFolderViewPrivate *priv;
719         GtkTreeSelection *sel;
720         
721         self = G_OBJECT (g_object_new (MODEST_TYPE_FOLDER_VIEW, NULL));
722         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
723
724         if (query)
725                 priv->query = g_object_ref (query);
726         
727         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
728         priv->changed_signal = g_signal_connect (sel, "changed",
729                                                  G_CALLBACK (on_selection_changed), self);
730
731         g_signal_connect (self, "expose-event", G_CALLBACK (modest_folder_view_on_map), NULL);
732
733         return GTK_WIDGET(self);
734 }
735
736 /* this feels dirty; any other way to expand all the root items? */
737 static void
738 expand_root_items (ModestFolderView *self)
739 {
740         GtkTreePath *path;
741         path = gtk_tree_path_new_first ();
742
743         /* all folders should have child items, so.. */
744         while (gtk_tree_view_expand_row (GTK_TREE_VIEW(self), path, FALSE))
745                 gtk_tree_path_next (path);
746         
747         gtk_tree_path_free (path);
748 }
749
750 /*
751  * We use this function to implement the
752  * MODEST_FOLDER_VIEW_STYLE_SHOW_ONE style. We only show the default
753  * account in this case, and the local folders.
754  */
755 static gboolean 
756 filter_row (GtkTreeModel *model,
757             GtkTreeIter *iter,
758             gpointer data)
759 {
760         ModestFolderViewPrivate *priv;
761         gboolean retval = TRUE;
762         TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
763         GObject *instance = NULL;
764         const gchar *id = NULL;
765         guint i;
766         gboolean found = FALSE;
767         gboolean cleared = FALSE;
768         
769         g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (data), FALSE);
770         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (data);
771
772         gtk_tree_model_get (model, iter,
773                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
774                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
775                             -1);
776
777         /* Do not show if there is no instance, this could indeed
778            happen when the model is being modified while it's being
779            drawn. This could occur for example when moving folders
780            using drag&drop */
781         if (!instance)
782                 return FALSE;
783
784         if (type == TNY_FOLDER_TYPE_ROOT) {
785                 /* TNY_FOLDER_TYPE_ROOT means that the instance is an account instead of a folder. */
786                 if (TNY_IS_ACCOUNT (instance)) {
787                         TnyAccount *acc = TNY_ACCOUNT (instance);
788                         const gchar *account_id = tny_account_get_id (acc);
789         
790                         /* If it isn't a special folder, 
791                          * don't show it unless it is the visible account: */
792                         if (!modest_tny_account_is_virtual_local_folders (acc) &&
793                                 strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) { 
794                                 /* Show only the visible account id */
795                                 if (priv->visible_account_id && strcmp (account_id, priv->visible_account_id))
796                                         retval = FALSE;
797                         }
798                         
799                         /* Never show these to the user. They are merged into one folder 
800                          * in the local-folders account instead: */
801                         if (retval && MODEST_IS_TNY_OUTBOX_ACCOUNT (acc))
802                                 retval = FALSE;
803                 }
804         }
805         
806         /* The virtual local-folders folder store is also shown by default. */
807
808         /* Check hiding (if necessary) */
809         cleared = modest_email_clipboard_cleared (priv->clipboard);            
810         if ((retval) && (!cleared) && (TNY_IS_FOLDER (instance))) {
811                 id = tny_folder_get_id (TNY_FOLDER(instance));
812                 if (priv->hidding_ids != NULL)
813                         for (i=0; i < priv->n_selected && !found; i++)
814                                 if (priv->hidding_ids[i] != NULL && id != NULL)
815                                         found = (!strcmp (priv->hidding_ids[i], id));
816                 
817                 retval = !found;
818         }
819
820         /* Free */
821         g_object_unref (instance);
822
823         return retval;
824 }
825
826 gboolean
827 modest_folder_view_update_model (ModestFolderView *self,
828                                  TnyAccountStore *account_store)
829 {
830         ModestFolderViewPrivate *priv;
831         GtkTreeModel *model /* , *old_model */;
832         /* TnyAccount *local_account; */
833         TnyList *model_as_list;
834
835         g_return_val_if_fail (account_store, FALSE);
836
837         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE(self);
838         
839         /* Notify that there is no folder selected */
840         g_signal_emit (G_OBJECT(self), 
841                        signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
842                        NULL, TRUE);
843
844         /* FIXME: the local accounts are not shown when the query
845            selects only the subscribed folders. */
846 /*      model        = tny_gtk_folder_store_tree_model_new (TRUE, priv->query); */
847         model        = tny_gtk_folder_store_tree_model_new (TRUE, NULL);
848         
849         /* Deal with the model via its TnyList Interface,
850          * filling the TnyList via a get_accounts() call: */
851         model_as_list = TNY_LIST(model);
852
853         /* Get the accounts: */
854         tny_account_store_get_accounts (TNY_ACCOUNT_STORE(account_store),
855                                         model_as_list,
856                                         TNY_ACCOUNT_STORE_STORE_ACCOUNTS);
857         g_object_unref (model_as_list);
858         model_as_list = NULL;   
859                                                      
860         GtkTreeModel *filter_model = NULL, *sortable = NULL;
861
862         sortable = gtk_tree_model_sort_new_with_model (model);
863         gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
864                                               TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, 
865                                               GTK_SORT_ASCENDING);
866         gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
867                                          TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
868                                          cmp_rows, NULL, NULL);
869
870         /* Create filter model */
871         filter_model = gtk_tree_model_filter_new (sortable, NULL);
872         gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
873                                                 filter_row,
874                                                 self,
875                                                 NULL);
876 /*      if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE) { */
877 /*              filter_model = gtk_tree_model_filter_new (sortable, NULL); */
878 /*              gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model), */
879 /*                                                      filter_row, */
880 /*                                                      self, */
881 /*                                                      NULL); */
882 /*      } */
883
884         /* Set new model */
885         gtk_tree_view_set_model (GTK_TREE_VIEW(self), filter_model);
886 /*      gtk_tree_view_set_model (GTK_TREE_VIEW(self),  */
887 /*                               (filter_model) ? filter_model : sortable); */
888
889         g_object_unref (model);
890         g_object_unref (filter_model);
891 /*      if (filter_model) */
892 /*              g_object_unref (filter_model); */
893                         
894         g_object_unref (sortable);
895
896         /* Force a reselection of the INBOX next time the widget is shown */
897         priv->reselect = TRUE;
898                         
899         return TRUE;
900 }
901
902
903 static void
904 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
905 {
906         GtkTreeModel            *model;
907         TnyFolderStore          *folder = NULL;
908         GtkTreeIter             iter;
909         ModestFolderView        *tree_view;
910         ModestFolderViewPrivate *priv;
911
912         g_return_if_fail (sel);
913         g_return_if_fail (user_data);
914         
915         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
916
917         if(!gtk_tree_selection_get_selected (sel, &model, &iter))
918                 return;
919
920         /* Notify the display name observers */
921         g_signal_emit (G_OBJECT(user_data),
922                        signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
923                        NULL);
924
925         tree_view = MODEST_FOLDER_VIEW (user_data);
926         gtk_tree_model_get (model, &iter,
927                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
928                             -1);
929
930         /* If the folder is the same do not notify */
931         if (priv->cur_folder_store == folder && folder) {
932                 g_object_unref (folder);
933                 return;
934         }
935         
936         /* Current folder was unselected */
937         if (priv->cur_folder_store) {
938                 g_signal_emit (G_OBJECT(tree_view), signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
939                                priv->cur_folder_store, FALSE);
940                 g_object_unref (priv->cur_folder_store);
941                 priv->cur_folder_store = NULL;
942         }
943
944         /* New current references */
945         priv->cur_folder_store = folder;
946
947         /* New folder has been selected */
948         g_signal_emit (G_OBJECT(tree_view),
949                        signals[FOLDER_SELECTION_CHANGED_SIGNAL],
950                        0, priv->cur_folder_store, TRUE);
951 }
952
953 TnyFolderStore *
954 modest_folder_view_get_selected (ModestFolderView *self)
955 {
956         ModestFolderViewPrivate *priv;
957
958         g_return_val_if_fail (self, NULL);
959         
960         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
961         if (priv->cur_folder_store)
962                 g_object_ref (priv->cur_folder_store);
963
964         return priv->cur_folder_store;
965 }
966
967 static gint
968 get_cmp_rows_type_pos (GObject *folder)
969 {
970         /* Remote accounts -> Local account -> MMC account .*/
971         /* 0, 1, 2 */
972         
973         if (TNY_IS_ACCOUNT (folder) && 
974                 modest_tny_account_is_virtual_local_folders (
975                         TNY_ACCOUNT (folder))) {
976                 return 1;
977         } else if (TNY_IS_ACCOUNT (folder)) {
978                 TnyAccount *account = TNY_ACCOUNT (folder);
979                 const gchar *account_id = tny_account_get_id (account);
980                 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
981                         return 2;
982                 else
983                         return 0;
984         }
985         else {
986                 printf ("DEBUG: %s: unexpected type.\n", __FUNCTION__);
987                 return -1; /* Should never happen */
988         }
989 }
990
991 /*
992  * This function orders the mail accounts according to these rules:
993  * 1st - remote accounts
994  * 2nd - local account
995  * 3rd - MMC account
996  */
997 static gint
998 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
999           gpointer user_data)
1000 {
1001         gint cmp;
1002         gchar *name1 = NULL;
1003         gchar *name2 = NULL;
1004         TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1005         GObject *folder1 = NULL;
1006         GObject *folder2 = NULL;
1007
1008         gtk_tree_model_get (tree_model, iter1,
1009                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name1,
1010                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
1011                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder1,
1012                             -1);
1013         gtk_tree_model_get (tree_model, iter2,
1014                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name2,
1015                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder2,
1016                             -1);
1017
1018         if (type == TNY_FOLDER_TYPE_ROOT) {
1019                 /* Compare the types, so that 
1020                  * Remote accounts -> Local account -> MMC account .*/
1021                 const gint pos1 = get_cmp_rows_type_pos (folder1);
1022                 const gint pos2 = get_cmp_rows_type_pos (folder2);
1023                 /* printf ("DEBUG: %s:\n  type1=%s, pos1=%d\n  type2=%s, pos2=%d\n", 
1024                         __FUNCTION__, G_OBJECT_TYPE_NAME(folder1), pos1, G_OBJECT_TYPE_NAME(folder2), pos2); */
1025                 if (pos1 <  pos2)
1026                         cmp = -1;
1027                 else if (pos1 > pos2)
1028                         cmp = 1;
1029                 else {
1030                         /* Compare items of the same type: */
1031                         
1032                         TnyAccount *account1 = NULL;
1033                         if (TNY_IS_ACCOUNT (folder1))
1034                                 account1 = TNY_ACCOUNT (folder1);
1035                                 
1036                         TnyAccount *account2 = NULL;
1037                         if (TNY_IS_ACCOUNT (folder2))
1038                                 account2 = TNY_ACCOUNT (folder2);
1039                                 
1040                         const gchar *account_id = account1 ? tny_account_get_id (account1) : NULL;
1041                         const gchar *account_id2 = account2 ? tny_account_get_id (account2) : NULL;
1042         
1043                         if (!account_id && !account_id2)
1044                                 return 0;
1045                         else if (!account_id)
1046                                 return -1;
1047                         else if (!account_id2)
1048                                 return +1;
1049                         else if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
1050                                 cmp = +1;
1051                         else
1052                                 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
1053                 }
1054         } else {
1055                 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
1056         }
1057         
1058         if (folder1)
1059                 g_object_unref(G_OBJECT(folder1));
1060         if (folder2)
1061                 g_object_unref(G_OBJECT(folder2));
1062
1063         g_free (name1);
1064         g_free (name2);
1065
1066         return cmp;     
1067 }
1068
1069 /*****************************************************************************/
1070 /*                        DRAG and DROP stuff                                */
1071 /*****************************************************************************/
1072
1073 /*
1074  * This function fills the #GtkSelectionData with the row and the
1075  * model that has been dragged. It's called when this widget is a
1076  * source for dnd after the event drop happened
1077  */
1078 static void
1079 on_drag_data_get (GtkWidget *widget, 
1080                   GdkDragContext *context, 
1081                   GtkSelectionData *selection_data, 
1082                   guint info, 
1083                   guint time, 
1084                   gpointer data)
1085 {
1086         GtkTreeSelection *selection;
1087         GtkTreeModel *model;
1088         GtkTreeIter iter;
1089         GtkTreePath *source_row;
1090
1091         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1092         gtk_tree_selection_get_selected (selection, &model, &iter);
1093         source_row = gtk_tree_model_get_path (model, &iter);
1094
1095         gtk_tree_set_row_drag_data (selection_data,
1096                                     model,
1097                                     source_row);
1098
1099         gtk_tree_path_free (source_row);
1100 }
1101
1102 typedef struct _DndHelper {
1103         gboolean delete_source;
1104         GtkTreePath *source_row;
1105         GdkDragContext *context;
1106         guint time;
1107 } DndHelper;
1108
1109
1110 /*
1111  * This function is the callback of the
1112  * modest_mail_operation_xfer_msgs () and
1113  * modest_mail_operation_xfer_folder() calls. We check here if the
1114  * message/folder was correctly asynchronously transferred. The reason
1115  * to use the same callback is that the code is the same, it only has
1116  * to check that the operation went fine and then finalize the drag
1117  * and drop action
1118  */
1119 static void
1120 on_progress_changed (ModestMailOperation *mail_op, 
1121                      ModestMailOperationState *state,
1122                      gpointer user_data)
1123 {
1124         gboolean success;
1125         DndHelper *helper;
1126
1127         helper = (DndHelper *) user_data;
1128
1129         if (!state->finished)
1130                 return;
1131
1132         if (state->status == MODEST_MAIL_OPERATION_STATUS_SUCCESS) {
1133                 success = TRUE;
1134         } else {
1135                 success = FALSE;
1136         }
1137
1138         /* Notify the drag source. Never call delete, the monitor will
1139            do the job if needed */
1140         gtk_drag_finish (helper->context, success, FALSE, helper->time);
1141
1142         /* Free the helper */
1143         gtk_tree_path_free (helper->source_row);        
1144         g_slice_free (DndHelper, helper);
1145 }
1146
1147 /*
1148  * This function is used by drag_data_received_cb to manage drag and
1149  * drop of a header, i.e, and drag from the header view to the folder
1150  * view.
1151  */
1152 static void
1153 drag_and_drop_from_header_view (GtkTreeModel *source_model,
1154                                 GtkTreeModel *dest_model,
1155                                 GtkTreePath  *dest_row,
1156                                 DndHelper    *helper)
1157 {
1158         TnyList *headers = NULL;
1159         TnyHeader *header = NULL;
1160         TnyFolder *folder = NULL;
1161         ModestMailOperation *mail_op = NULL;
1162         GtkTreeIter source_iter, dest_iter;
1163
1164         g_return_if_fail (GTK_IS_TREE_MODEL(source_model));
1165         g_return_if_fail (GTK_IS_TREE_MODEL(dest_model));
1166         g_return_if_fail (dest_row);
1167         g_return_if_fail (helper);
1168         
1169         /* Get header */
1170         gtk_tree_model_get_iter (source_model, &source_iter, helper->source_row);
1171         gtk_tree_model_get (source_model, &source_iter, 
1172                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, 
1173                             &header, -1);
1174         if (!TNY_IS_HEADER(header)) {
1175                 g_warning ("BUG: %s could not get a valid header", __FUNCTION__);
1176                 goto cleanup;
1177         }
1178         
1179         /* Get Folder */
1180         gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
1181         gtk_tree_model_get (dest_model, &dest_iter, 
1182                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, 
1183                             &folder, -1);
1184
1185         if (!TNY_IS_FOLDER(folder)) {
1186                 g_warning ("BUG: %s could not get a valid folder", __FUNCTION__);
1187                 goto cleanup;
1188         }
1189
1190         /* Transfer message */
1191         mail_op = modest_mail_operation_new (MODEST_MAIL_OPERATION_TYPE_RECEIVE, NULL);
1192         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1193                                          mail_op);
1194         g_signal_connect (G_OBJECT (mail_op), "progress-changed",
1195                           G_CALLBACK (on_progress_changed), helper);
1196
1197         headers = tny_simple_list_new ();
1198         tny_list_append (headers, G_OBJECT (header));
1199         modest_mail_operation_xfer_msgs (mail_op, 
1200                                          headers, 
1201                                          folder, 
1202                                          helper->delete_source, 
1203                                          NULL, NULL);
1204         
1205         /* Frees */
1206 cleanup:
1207         if (G_IS_OBJECT(mail_op))
1208                 g_object_unref (G_OBJECT (mail_op));
1209         if (G_IS_OBJECT(header))
1210                 g_object_unref (G_OBJECT (header));
1211         if (G_IS_OBJECT(folder))
1212                 g_object_unref (G_OBJECT (folder));
1213         if (G_IS_OBJECT(headers))
1214                 g_object_unref (headers);
1215 }
1216
1217 /*
1218  * This function is used by drag_data_received_cb to manage drag and
1219  * drop of a folder, i.e, and drag from the folder view to the same
1220  * folder view.
1221  */
1222 static void
1223 drag_and_drop_from_folder_view (GtkTreeModel     *source_model,
1224                                 GtkTreeModel     *dest_model,
1225                                 GtkTreePath      *dest_row,
1226                                 GtkSelectionData *selection_data,
1227                                 DndHelper        *helper)
1228 {
1229         ModestMailOperation *mail_op;
1230         GtkTreeIter parent_iter, iter;
1231         TnyFolderStore *parent_folder;
1232         TnyFolder *folder;
1233
1234         /* Check if the drag is possible */
1235 /*      if (!gtk_tree_path_compare (helper->source_row, dest_row) || */
1236 /*          !gtk_tree_drag_dest_row_drop_possible (GTK_TREE_DRAG_DEST (dest_model), */
1237 /*                                                 dest_row, */
1238 /*                                                 selection_data)) { */
1239         if (!gtk_tree_path_compare (helper->source_row, dest_row)) {
1240
1241                 gtk_drag_finish (helper->context, FALSE, FALSE, helper->time);
1242                 gtk_tree_path_free (helper->source_row);        
1243                 g_slice_free (DndHelper, helper);
1244                 return;
1245         }
1246
1247         /* Get data */
1248         gtk_tree_model_get_iter (source_model, &parent_iter, dest_row);
1249         gtk_tree_model_get_iter (source_model, &iter, helper->source_row);
1250         gtk_tree_model_get (source_model, &parent_iter, 
1251                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, 
1252                             &parent_folder, -1);
1253         gtk_tree_model_get (source_model, &iter,
1254                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
1255                             &folder, -1);
1256
1257         /* Do the mail operation */
1258         mail_op = modest_mail_operation_new_with_error_handling (MODEST_MAIL_OPERATION_TYPE_RECEIVE, 
1259                                                                  NULL,
1260                                                                  modest_ui_actions_move_folder_error_handler,
1261                                                                  NULL);
1262         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (), 
1263                                          mail_op);
1264         g_signal_connect (G_OBJECT (mail_op), "progress-changed",
1265                           G_CALLBACK (on_progress_changed), helper);
1266
1267         modest_mail_operation_xfer_folder (mail_op, 
1268                                            folder, 
1269                                            parent_folder,
1270                                            helper->delete_source);
1271         
1272         /* Frees */
1273         g_object_unref (G_OBJECT (parent_folder));
1274         g_object_unref (G_OBJECT (folder));
1275         g_object_unref (G_OBJECT (mail_op));
1276 }
1277
1278 /*
1279  * This function receives the data set by the "drag-data-get" signal
1280  * handler. This information comes within the #GtkSelectionData. This
1281  * function will manage both the drags of folders of the treeview and
1282  * drags of headers of the header view widget.
1283  */
1284 static void 
1285 on_drag_data_received (GtkWidget *widget, 
1286                        GdkDragContext *context, 
1287                        gint x, 
1288                        gint y, 
1289                        GtkSelectionData *selection_data, 
1290                        guint target_type, 
1291                        guint time, 
1292                        gpointer data)
1293 {
1294         GtkWidget *source_widget;
1295         GtkTreeModel *dest_model, *source_model;
1296         GtkTreePath *source_row, *dest_row;
1297         GtkTreeViewDropPosition pos;
1298         gboolean success = FALSE, delete_source = FALSE;
1299         DndHelper *helper = NULL; 
1300
1301         /* Do not allow further process */
1302         g_signal_stop_emission_by_name (widget, "drag-data-received");
1303         source_widget = gtk_drag_get_source_widget (context);
1304
1305         /* Get the action */
1306         if (context->action == GDK_ACTION_MOVE) {
1307                 delete_source = TRUE;
1308
1309                 /* Notify that there is no folder selected. We need to
1310                    do this in order to update the headers view (and
1311                    its monitors, because when moving, the old folder
1312                    won't longer exist. We can not wait for the end of
1313                    the operation, because the operation won't start if
1314                    the folder is in use */
1315                 if (source_widget == widget)
1316                         g_signal_emit (G_OBJECT (widget), 
1317                                        signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0, NULL, TRUE);
1318         }
1319
1320         /* Check if the get_data failed */
1321         if (selection_data == NULL || selection_data->length < 0)
1322                 gtk_drag_finish (context, success, FALSE, time);
1323
1324         /* Get the models */
1325         gtk_tree_get_row_drag_data (selection_data,
1326                                     &source_model,
1327                                     &source_row);
1328
1329         /* Select the destination model */
1330         if (source_widget == widget) {
1331                 dest_model = source_model;
1332         } else {
1333                 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
1334         }
1335
1336         /* Get the path to the destination row. Can not call
1337            gtk_tree_view_get_drag_dest_row() because the source row
1338            is not selected anymore */
1339         gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget), x, y,
1340                                            &dest_row, &pos);
1341
1342         /* Only allow drops IN other rows */
1343         if (!dest_row || pos == GTK_TREE_VIEW_DROP_BEFORE || pos == GTK_TREE_VIEW_DROP_AFTER)
1344                 gtk_drag_finish (context, success, FALSE, time);
1345
1346         /* Create the helper */
1347         helper = g_slice_new0 (DndHelper);
1348         helper->delete_source = delete_source;
1349         helper->source_row = gtk_tree_path_copy (source_row);
1350         helper->context = context;
1351         helper->time = time;
1352
1353         /* Drags from the header view */
1354         if (source_widget != widget) {
1355
1356                 drag_and_drop_from_header_view (source_model,
1357                                                 dest_model,
1358                                                 dest_row,
1359                                                 helper);
1360         } else {
1361
1362
1363                 drag_and_drop_from_folder_view (source_model,
1364                                                 dest_model,
1365                                                 dest_row,
1366                                                 selection_data, 
1367                                                 helper);
1368         }
1369
1370         /* Frees */
1371         gtk_tree_path_free (source_row);
1372         gtk_tree_path_free (dest_row);
1373 }
1374
1375 /*
1376  * We define a "drag-drop" signal handler because we do not want to
1377  * use the default one, because the default one always calls
1378  * gtk_drag_finish and we prefer to do it in the "drag-data-received"
1379  * signal handler, because there we have all the information available
1380  * to know if the dnd was a success or not.
1381  */
1382 static gboolean
1383 drag_drop_cb (GtkWidget      *widget,
1384               GdkDragContext *context,
1385               gint            x,
1386               gint            y,
1387               guint           time,
1388               gpointer        user_data) 
1389 {
1390         gpointer target;
1391
1392         if (!context->targets)
1393                 return FALSE;
1394
1395         /* Check if we're dragging a folder row */
1396         target = gtk_drag_dest_find_target (widget, context, NULL);
1397
1398         /* Request the data from the source. */
1399         gtk_drag_get_data(widget, context, target, time);
1400
1401     return TRUE;
1402 }
1403
1404 /*
1405  * This function expands a node of a tree view if it's not expanded
1406  * yet. Not sure why it needs the threads stuff, but gtk+`example code
1407  * does that, so that's why they're here.
1408  */
1409 static gint
1410 expand_row_timeout (gpointer data)
1411 {
1412         GtkTreeView *tree_view = data;
1413         GtkTreePath *dest_path = NULL;
1414         GtkTreeViewDropPosition pos;
1415         gboolean result = FALSE;
1416         
1417         GDK_THREADS_ENTER ();
1418         
1419         gtk_tree_view_get_drag_dest_row (tree_view,
1420                                          &dest_path,
1421                                          &pos);
1422         
1423         if (dest_path &&
1424             (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER ||
1425              pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)) {
1426                 gtk_tree_view_expand_row (tree_view, dest_path, FALSE);
1427                 gtk_tree_path_free (dest_path);
1428         }
1429         else {
1430                 if (dest_path)
1431                         gtk_tree_path_free (dest_path);
1432                 
1433                 result = TRUE;
1434         }
1435         
1436         GDK_THREADS_LEAVE ();
1437
1438         return result;
1439 }
1440
1441 /*
1442  * This function is called whenever the pointer is moved over a widget
1443  * while dragging some data. It installs a timeout that will expand a
1444  * node of the treeview if not expanded yet. This function also calls
1445  * gdk_drag_status in order to set the suggested action that will be
1446  * used by the "drag-data-received" signal handler to know if we
1447  * should do a move or just a copy of the data.
1448  */
1449 static gboolean
1450 on_drag_motion (GtkWidget      *widget,
1451                 GdkDragContext *context,
1452                 gint            x,
1453                 gint            y,
1454                 guint           time,
1455                 gpointer        user_data)  
1456 {
1457         GtkTreeViewDropPosition pos;
1458         GtkTreePath *dest_row;
1459         ModestFolderViewPrivate *priv;
1460         GdkDragAction suggested_action;
1461         gboolean valid_location = FALSE;
1462
1463         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
1464
1465         if (priv->timer_expander != 0) {
1466                 g_source_remove (priv->timer_expander);
1467                 priv->timer_expander = 0;
1468         }
1469
1470         gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
1471                                            x, y,
1472                                            &dest_row,
1473                                            &pos);
1474
1475         /* Do not allow drops between folders */
1476         if (!dest_row ||
1477             pos == GTK_TREE_VIEW_DROP_BEFORE ||
1478             pos == GTK_TREE_VIEW_DROP_AFTER) {
1479                 gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW (widget), NULL, 0);
1480                 gdk_drag_status(context, 0, time);
1481                 valid_location = FALSE;
1482                 goto out;
1483         } else {
1484                 valid_location = TRUE;
1485         }
1486
1487         /* Expand the selected row after 1/2 second */
1488         if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), dest_row)) {
1489                 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), dest_row, pos);
1490                 priv->timer_expander = g_timeout_add (500, expand_row_timeout, widget);
1491         }
1492
1493         /* Select the desired action. By default we pick MOVE */
1494         suggested_action = GDK_ACTION_MOVE;
1495
1496         if (context->actions == GDK_ACTION_COPY)
1497             gdk_drag_status(context, GDK_ACTION_COPY, time);
1498         else if (context->actions == GDK_ACTION_MOVE)
1499             gdk_drag_status(context, GDK_ACTION_MOVE, time);
1500         else if (context->actions & suggested_action)
1501             gdk_drag_status(context, suggested_action, time);
1502         else
1503             gdk_drag_status(context, GDK_ACTION_DEFAULT, time);
1504
1505  out:
1506         if (dest_row)
1507                 gtk_tree_path_free (dest_row);
1508         g_signal_stop_emission_by_name (widget, "drag-motion");
1509         return valid_location;
1510 }
1511
1512
1513 /* Folder view drag types */
1514 const GtkTargetEntry folder_view_drag_types[] =
1515 {
1516         { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, MODEST_FOLDER_ROW },
1517         { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_APP,    MODEST_HEADER_ROW }
1518 };
1519
1520 /*
1521  * This function sets the treeview as a source and a target for dnd
1522  * events. It also connects all the requirede signals.
1523  */
1524 static void
1525 setup_drag_and_drop (GtkTreeView *self)
1526 {
1527         /* Set up the folder view as a dnd destination. Set only the
1528            highlight flag, otherwise gtk will have a different
1529            behaviour */
1530         gtk_drag_dest_set (GTK_WIDGET (self),
1531                            GTK_DEST_DEFAULT_HIGHLIGHT,
1532                            folder_view_drag_types,
1533                            G_N_ELEMENTS (folder_view_drag_types),
1534                            GDK_ACTION_MOVE | GDK_ACTION_COPY);
1535
1536         g_signal_connect (G_OBJECT (self),
1537                           "drag_data_received",
1538                           G_CALLBACK (on_drag_data_received),
1539                           NULL);
1540
1541
1542         /* Set up the treeview as a dnd source */
1543         gtk_drag_source_set (GTK_WIDGET (self),
1544                              GDK_BUTTON1_MASK,
1545                              folder_view_drag_types,
1546                              G_N_ELEMENTS (folder_view_drag_types),
1547                              GDK_ACTION_MOVE | GDK_ACTION_COPY);
1548
1549         g_signal_connect (G_OBJECT (self),
1550                           "drag_motion",
1551                           G_CALLBACK (on_drag_motion),
1552                           NULL);
1553         
1554         g_signal_connect (G_OBJECT (self),
1555                           "drag_data_get",
1556                           G_CALLBACK (on_drag_data_get),
1557                           NULL);
1558
1559         g_signal_connect (G_OBJECT (self),
1560                           "drag_drop",
1561                           G_CALLBACK (drag_drop_cb),
1562                           NULL);
1563 }
1564
1565 /*
1566  * This function manages the navigation through the folders using the
1567  * keyboard or the hardware keys in the device
1568  */
1569 static gboolean
1570 on_key_pressed (GtkWidget *self,
1571                 GdkEventKey *event,
1572                 gpointer user_data)
1573 {
1574         GtkTreeSelection *selection;
1575         GtkTreeIter iter;
1576         GtkTreeModel *model;
1577         gboolean retval = FALSE;
1578
1579         /* Up and Down are automatically managed by the treeview */
1580         if (event->keyval == GDK_Return) {
1581                 /* Expand/Collapse the selected row */
1582                 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1583                 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
1584                         GtkTreePath *path;
1585
1586                         path = gtk_tree_model_get_path (model, &iter);
1587
1588                         if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (self), path))
1589                                 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1590                         else
1591                                 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1592                         gtk_tree_path_free (path);
1593                 }
1594                 /* No further processing */
1595                 retval = TRUE;
1596         }
1597
1598         return retval;
1599 }
1600
1601 /*
1602  * We listen to the changes in the local folder account name key,
1603  * because we want to show the right name in the view. The local
1604  * folder account name corresponds to the device name in the Maemo
1605  * version. We do this because we do not want to query gconf on each
1606  * tree view refresh. It's better to cache it and change whenever
1607  * necessary.
1608  */
1609 static void 
1610 on_configuration_key_changed (ModestConf* conf, 
1611                               const gchar *key, 
1612                               ModestConfEvent event, 
1613                               ModestFolderView *self)
1614 {
1615         ModestFolderViewPrivate *priv;
1616
1617         if (!key)
1618                 return;
1619
1620         g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
1621         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1622
1623         if (!strcmp (key, MODEST_CONF_DEVICE_NAME)) {
1624                 g_free (priv->local_account_name);
1625
1626                 if (event == MODEST_CONF_EVENT_KEY_UNSET)
1627                         priv->local_account_name = g_strdup (MODEST_LOCAL_FOLDERS_DEFAULT_DISPLAY_NAME);
1628                 else
1629                         priv->local_account_name = modest_conf_get_string (modest_runtime_get_conf(),
1630                                                                            MODEST_CONF_DEVICE_NAME, NULL);
1631
1632                 /* Force a redraw */
1633 #if GTK_CHECK_VERSION(2, 8, 0) /* gtk_tree_view_column_queue_resize is only available in GTK+ 2.8 */
1634                 GtkTreeViewColumn * tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self), 
1635                                                                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN);
1636                 gtk_tree_view_column_queue_resize (tree_column);
1637 #endif
1638         }
1639 }
1640
1641 void 
1642 modest_folder_view_set_style (ModestFolderView *self,
1643                               ModestFolderViewStyle style)
1644 {
1645         ModestFolderViewPrivate *priv;
1646
1647         g_return_if_fail (self);
1648         
1649         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1650
1651         priv->style = style;
1652 }
1653
1654 void
1655 modest_folder_view_set_account_id_of_visible_server_account (ModestFolderView *self,
1656                                                              const gchar *account_id)
1657 {
1658         ModestFolderViewPrivate *priv;
1659         GtkTreeModel *model;
1660
1661         g_return_if_fail (self);
1662         
1663         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1664
1665         /* This will be used by the filter_row callback,
1666          * to decided which rows to show: */
1667         if (priv->visible_account_id) {
1668                 g_free (priv->visible_account_id);
1669                 priv->visible_account_id = NULL;
1670         }
1671         if (account_id)
1672                 priv->visible_account_id = g_strdup (account_id);
1673
1674         /* Refilter */
1675         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1676         if (GTK_IS_TREE_MODEL_FILTER (model))
1677                 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
1678
1679         /* Save settings to gconf */
1680         modest_widget_memory_save (modest_runtime_get_conf (), G_OBJECT(self),
1681                                    MODEST_CONF_FOLDER_VIEW_KEY);
1682 }
1683
1684 const gchar *
1685 modest_folder_view_get_account_id_of_visible_server_account (ModestFolderView *self)
1686 {
1687         ModestFolderViewPrivate *priv;
1688
1689         g_return_val_if_fail (self, NULL);
1690         
1691         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1692
1693         return (const gchar *) priv->visible_account_id;
1694 }
1695
1696 static gboolean
1697 find_inbox_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *inbox_iter)
1698 {
1699         do {
1700                 GtkTreeIter child;
1701                 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1702                 gchar *name = NULL;
1703
1704                 gtk_tree_model_get (model, iter, 
1705                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name,
1706                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, 
1707                                     &type, -1);
1708
1709                 /*
1710                 printf ("DEBUG: %s: name=%s, type=%d, TNY_FOLDER_TYPE_INBOX=%d\n", 
1711                         __FUNCTION__, name, type, TNY_FOLDER_TYPE_INBOX);
1712                 */
1713                         
1714                 gboolean result = FALSE;
1715                 if (type == TNY_FOLDER_TYPE_INBOX) {
1716                         result = TRUE;
1717                 } else if (type == TNY_FOLDER_TYPE_NORMAL) {
1718                         /* tinymail's camel implementation only provides TNY_FOLDER_TYPE_NORMAL
1719                          * when getting folders from the cache, before connectin, so we do 
1720                          * an extra check. We could fix this in tinymail, but it's easier 
1721                          * to do here.
1722                          */
1723                          if (strcmp (name, "Inbox") == 0)
1724                                 result = TRUE;
1725                 }
1726                 
1727                 g_free (name);
1728                 
1729                 if (result) {
1730                         *inbox_iter = *iter;
1731                         return TRUE;
1732                 }
1733
1734                 if (gtk_tree_model_iter_children (model, &child, iter)) {
1735                         if (find_inbox_iter (model, &child, inbox_iter))
1736                                 return TRUE;
1737                 }
1738
1739         } while (gtk_tree_model_iter_next (model, iter));
1740
1741         return FALSE;
1742 }
1743
1744 void 
1745 modest_folder_view_select_first_inbox_or_local (ModestFolderView *self)
1746 {
1747         GtkTreeModel *model;
1748         GtkTreeIter iter, inbox_iter;
1749         GtkTreeSelection *sel;
1750
1751 /*      /\* Do not set it if the folder view was not painted *\/ */
1752 /*      if (!GTK_WIDGET_MAPPED (self)) */
1753 /*              return; */
1754
1755         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1756         if (!model)
1757                 return;
1758
1759         expand_root_items (self);
1760         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1761
1762         gtk_tree_model_get_iter_first (model, &iter);
1763         if (find_inbox_iter (model, &iter, &inbox_iter)) {
1764                 gtk_tree_selection_select_iter (sel, &inbox_iter);
1765         }
1766         else {
1767                 gtk_tree_model_get_iter_first (model, &iter);
1768                 gtk_tree_selection_select_iter (sel, &iter);
1769         }
1770 }
1771
1772 void 
1773 modest_folder_view_copy_selection (ModestFolderView *folder_view)
1774 {
1775         /* Copy selection */
1776         _clipboard_set_selected_data (folder_view, FALSE);
1777 }
1778
1779 void 
1780 modest_folder_view_cut_selection (ModestFolderView *folder_view)
1781 {
1782         ModestFolderViewPrivate *priv = NULL;
1783         GtkTreeModel *model = NULL;
1784         const gchar **hidding = NULL;
1785         guint i, n_selected;
1786
1787         g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view));
1788         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
1789
1790         /* Copy selection */
1791         _clipboard_set_selected_data (folder_view, TRUE);
1792
1793         /* Get hidding ids */
1794         hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected); 
1795         
1796         /* Clear hidding array created by previous cut operation */
1797         _clear_hidding_filter (MODEST_FOLDER_VIEW (folder_view));
1798
1799         /* Copy hidding array */
1800         priv->n_selected = n_selected;
1801         priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
1802         for (i=0; i < n_selected; i++) 
1803                 priv->hidding_ids[i] = g_strdup(hidding[i]);            
1804
1805         /* Hide cut folders */
1806         model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
1807         gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
1808 }
1809
1810 static void
1811 _clipboard_set_selected_data (ModestFolderView *folder_view,
1812                               gboolean delete)
1813 {
1814         ModestFolderViewPrivate *priv = NULL;
1815         TnyFolderStore *folder = NULL;
1816
1817         g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view));
1818         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
1819                 
1820         /* Set selected data on clipboard   */
1821         g_return_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard));
1822         folder = modest_folder_view_get_selected (folder_view);
1823         modest_email_clipboard_set_data (priv->clipboard, TNY_FOLDER(folder), NULL, delete);
1824
1825         /* Free */
1826         g_object_unref (folder);
1827 }
1828
1829 static void
1830 _clear_hidding_filter (ModestFolderView *folder_view) 
1831 {
1832         ModestFolderViewPrivate *priv;
1833         guint i;
1834         
1835         g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view)); 
1836         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
1837
1838         if (priv->hidding_ids != NULL) {
1839                 for (i=0; i < priv->n_selected; i++) 
1840                         g_free (priv->hidding_ids[i]);
1841                 g_free(priv->hidding_ids);
1842         }       
1843 }