Added listening on an extra signal
[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         g_signal_connect (G_OBJECT(account_store), "connecting_finished",
632                                 G_CALLBACK (on_accounts_reloaded), self);
633
634         on_accounts_reloaded (account_store, (gpointer ) self);
635         
636         g_object_unref (G_OBJECT (device));
637 }
638
639 static void
640 on_account_removed (TnyAccountStore *account_store, 
641                     TnyAccount *account,
642                     gpointer user_data)
643 {
644         ModestFolderView *self = MODEST_FOLDER_VIEW (user_data);
645         ModestFolderViewPrivate *priv;
646
647         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
648
649         /* If the removed account is the currently viewed one then
650            clear the configuration value. The new visible account will be the default account */
651         if (!strcmp (priv->visible_account_id, tny_account_get_id (account))) {
652                 modest_folder_view_set_account_id_of_visible_server_account (self, NULL);
653         }
654 }
655
656 static void
657 on_account_update (TnyAccountStore *account_store, 
658                    const gchar *account,
659                    gpointer user_data)
660 {
661         ModestFolderView *self = MODEST_FOLDER_VIEW (user_data);
662         ModestFolderViewPrivate *priv;
663
664         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
665         if (!priv->visible_account_id)
666                 modest_widget_memory_restore (modest_runtime_get_conf(), G_OBJECT(self),
667                                               MODEST_CONF_FOLDER_VIEW_KEY);
668
669         if (!modest_folder_view_update_model (self, account_store))
670                 g_printerr ("modest: failed to update model for changes in '%s'",
671                             account);
672 }
673
674 static void 
675 on_accounts_reloaded   (TnyAccountStore *account_store, 
676                         gpointer user_data)
677 {
678         modest_folder_view_update_model (MODEST_FOLDER_VIEW (user_data), account_store);
679 }
680
681 void
682 modest_folder_view_set_title (ModestFolderView *self, const gchar *title)
683 {
684         GtkTreeViewColumn *col;
685         
686         g_return_if_fail (self);
687
688         col = gtk_tree_view_get_column (GTK_TREE_VIEW(self), 0);
689         if (!col) {
690                 g_printerr ("modest: failed get column for title\n");
691                 return;
692         }
693
694         gtk_tree_view_column_set_title (col, title);
695         gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self),
696                                            title != NULL);
697 }
698
699 static gboolean
700 modest_folder_view_on_map (ModestFolderView *self, 
701                            GdkEventExpose *event,
702                            gpointer data)
703 {
704         ModestFolderViewPrivate *priv;
705
706         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
707
708         /* This won't happen often */
709         if (G_UNLIKELY (priv->reselect)) {
710                 /* Select the first inbox or the local account if not found */
711                 modest_folder_view_select_first_inbox_or_local (self);
712                 priv->reselect = FALSE;
713         }
714         return FALSE;
715 }
716
717 GtkWidget*
718 modest_folder_view_new (TnyFolderStoreQuery *query)
719 {
720         GObject *self;
721         ModestFolderViewPrivate *priv;
722         GtkTreeSelection *sel;
723         
724         self = G_OBJECT (g_object_new (MODEST_TYPE_FOLDER_VIEW, NULL));
725         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
726
727         if (query)
728                 priv->query = g_object_ref (query);
729         
730         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
731         priv->changed_signal = g_signal_connect (sel, "changed",
732                                                  G_CALLBACK (on_selection_changed), self);
733
734         g_signal_connect (self, "expose-event", G_CALLBACK (modest_folder_view_on_map), NULL);
735
736         return GTK_WIDGET(self);
737 }
738
739 /* this feels dirty; any other way to expand all the root items? */
740 static void
741 expand_root_items (ModestFolderView *self)
742 {
743         GtkTreePath *path;
744         path = gtk_tree_path_new_first ();
745
746         /* all folders should have child items, so.. */
747         while (gtk_tree_view_expand_row (GTK_TREE_VIEW(self), path, FALSE))
748                 gtk_tree_path_next (path);
749         
750         gtk_tree_path_free (path);
751 }
752
753 /*
754  * We use this function to implement the
755  * MODEST_FOLDER_VIEW_STYLE_SHOW_ONE style. We only show the default
756  * account in this case, and the local folders.
757  */
758 static gboolean 
759 filter_row (GtkTreeModel *model,
760             GtkTreeIter *iter,
761             gpointer data)
762 {
763         ModestFolderViewPrivate *priv;
764         gboolean retval = TRUE;
765         TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
766         GObject *instance = NULL;
767         const gchar *id = NULL;
768         guint i;
769         gboolean found = FALSE;
770         gboolean cleared = FALSE;
771         
772         g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (data), FALSE);
773         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (data);
774
775         gtk_tree_model_get (model, iter,
776                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
777                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
778                             -1);
779
780         /* Do not show if there is no instance, this could indeed
781            happen when the model is being modified while it's being
782            drawn. This could occur for example when moving folders
783            using drag&drop */
784         if (!instance)
785                 return FALSE;
786
787         if (type == TNY_FOLDER_TYPE_ROOT) {
788                 /* TNY_FOLDER_TYPE_ROOT means that the instance is an account instead of a folder. */
789                 if (TNY_IS_ACCOUNT (instance)) {
790                         TnyAccount *acc = TNY_ACCOUNT (instance);
791                         const gchar *account_id = tny_account_get_id (acc);
792         
793                         /* If it isn't a special folder, 
794                          * don't show it unless it is the visible account: */
795                         if (!modest_tny_account_is_virtual_local_folders (acc) &&
796                                 strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) { 
797                                 /* Show only the visible account id */
798                                 if (priv->visible_account_id && strcmp (account_id, priv->visible_account_id))
799                                         retval = FALSE;
800                         }
801                         
802                         /* Never show these to the user. They are merged into one folder 
803                          * in the local-folders account instead: */
804                         if (retval && MODEST_IS_TNY_OUTBOX_ACCOUNT (acc))
805                                 retval = FALSE;
806                 }
807         }
808         
809         /* The virtual local-folders folder store is also shown by default. */
810
811         /* Check hiding (if necessary) */
812         cleared = modest_email_clipboard_cleared (priv->clipboard);            
813         if ((retval) && (!cleared) && (TNY_IS_FOLDER (instance))) {
814                 id = tny_folder_get_id (TNY_FOLDER(instance));
815                 if (priv->hidding_ids != NULL)
816                         for (i=0; i < priv->n_selected && !found; i++)
817                                 if (priv->hidding_ids[i] != NULL && id != NULL)
818                                         found = (!strcmp (priv->hidding_ids[i], id));
819                 
820                 retval = !found;
821         }
822
823         /* Free */
824         g_object_unref (instance);
825
826         return retval;
827 }
828
829 gboolean
830 modest_folder_view_update_model (ModestFolderView *self,
831                                  TnyAccountStore *account_store)
832 {
833         ModestFolderViewPrivate *priv;
834         GtkTreeModel *model /* , *old_model */;
835         /* TnyAccount *local_account; */
836         TnyList *model_as_list;
837
838         g_return_val_if_fail (account_store, FALSE);
839
840         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE(self);
841         
842         /* Notify that there is no folder selected */
843         g_signal_emit (G_OBJECT(self), 
844                        signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
845                        NULL, TRUE);
846
847         /* FIXME: the local accounts are not shown when the query
848            selects only the subscribed folders. */
849 /*      model        = tny_gtk_folder_store_tree_model_new (TRUE, priv->query); */
850         model        = tny_gtk_folder_store_tree_model_new (TRUE, NULL);
851         
852         /* Deal with the model via its TnyList Interface,
853          * filling the TnyList via a get_accounts() call: */
854         model_as_list = TNY_LIST(model);
855
856         /* Get the accounts: */
857         tny_account_store_get_accounts (TNY_ACCOUNT_STORE(account_store),
858                                         model_as_list,
859                                         TNY_ACCOUNT_STORE_STORE_ACCOUNTS);
860         g_object_unref (model_as_list);
861         model_as_list = NULL;   
862                                                      
863         GtkTreeModel *filter_model = NULL, *sortable = NULL;
864
865         sortable = gtk_tree_model_sort_new_with_model (model);
866         gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
867                                               TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, 
868                                               GTK_SORT_ASCENDING);
869         gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
870                                          TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
871                                          cmp_rows, NULL, NULL);
872
873         /* Create filter model */
874         filter_model = gtk_tree_model_filter_new (sortable, NULL);
875         gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
876                                                 filter_row,
877                                                 self,
878                                                 NULL);
879 /*      if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE) { */
880 /*              filter_model = gtk_tree_model_filter_new (sortable, NULL); */
881 /*              gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model), */
882 /*                                                      filter_row, */
883 /*                                                      self, */
884 /*                                                      NULL); */
885 /*      } */
886
887         /* Set new model */
888         gtk_tree_view_set_model (GTK_TREE_VIEW(self), filter_model);
889 /*      gtk_tree_view_set_model (GTK_TREE_VIEW(self),  */
890 /*                               (filter_model) ? filter_model : sortable); */
891
892         g_object_unref (model);
893         g_object_unref (filter_model);
894 /*      if (filter_model) */
895 /*              g_object_unref (filter_model); */
896                         
897         g_object_unref (sortable);
898
899         /* Force a reselection of the INBOX next time the widget is shown */
900         priv->reselect = TRUE;
901                         
902         return TRUE;
903 }
904
905
906 static void
907 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
908 {
909         GtkTreeModel            *model;
910         TnyFolderStore          *folder = NULL;
911         GtkTreeIter             iter;
912         ModestFolderView        *tree_view;
913         ModestFolderViewPrivate *priv;
914
915         g_return_if_fail (sel);
916         g_return_if_fail (user_data);
917         
918         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
919
920         if(!gtk_tree_selection_get_selected (sel, &model, &iter))
921                 return;
922
923         /* Notify the display name observers */
924         g_signal_emit (G_OBJECT(user_data),
925                        signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
926                        NULL);
927
928         tree_view = MODEST_FOLDER_VIEW (user_data);
929         gtk_tree_model_get (model, &iter,
930                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
931                             -1);
932
933         /* If the folder is the same do not notify */
934         if (priv->cur_folder_store == folder && folder) {
935                 g_object_unref (folder);
936                 return;
937         }
938         
939         /* Current folder was unselected */
940         if (priv->cur_folder_store) {
941                 g_signal_emit (G_OBJECT(tree_view), signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
942                                priv->cur_folder_store, FALSE);
943                 g_object_unref (priv->cur_folder_store);
944                 priv->cur_folder_store = NULL;
945         }
946
947         /* New current references */
948         priv->cur_folder_store = folder;
949
950         /* New folder has been selected */
951         g_signal_emit (G_OBJECT(tree_view),
952                        signals[FOLDER_SELECTION_CHANGED_SIGNAL],
953                        0, priv->cur_folder_store, TRUE);
954 }
955
956 TnyFolderStore *
957 modest_folder_view_get_selected (ModestFolderView *self)
958 {
959         ModestFolderViewPrivate *priv;
960
961         g_return_val_if_fail (self, NULL);
962         
963         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
964         if (priv->cur_folder_store)
965                 g_object_ref (priv->cur_folder_store);
966
967         return priv->cur_folder_store;
968 }
969
970 static gint
971 get_cmp_rows_type_pos (GObject *folder)
972 {
973         /* Remote accounts -> Local account -> MMC account .*/
974         /* 0, 1, 2 */
975         
976         if (TNY_IS_ACCOUNT (folder) && 
977                 modest_tny_account_is_virtual_local_folders (
978                         TNY_ACCOUNT (folder))) {
979                 return 1;
980         } else if (TNY_IS_ACCOUNT (folder)) {
981                 TnyAccount *account = TNY_ACCOUNT (folder);
982                 const gchar *account_id = tny_account_get_id (account);
983                 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
984                         return 2;
985                 else
986                         return 0;
987         }
988         else {
989                 printf ("DEBUG: %s: unexpected type.\n", __FUNCTION__);
990                 return -1; /* Should never happen */
991         }
992 }
993
994 /*
995  * This function orders the mail accounts according to these rules:
996  * 1st - remote accounts
997  * 2nd - local account
998  * 3rd - MMC account
999  */
1000 static gint
1001 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1002           gpointer user_data)
1003 {
1004         gint cmp;
1005         gchar *name1 = NULL;
1006         gchar *name2 = NULL;
1007         TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1008         GObject *folder1 = NULL;
1009         GObject *folder2 = NULL;
1010
1011         gtk_tree_model_get (tree_model, iter1,
1012                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name1,
1013                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
1014                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder1,
1015                             -1);
1016         gtk_tree_model_get (tree_model, iter2,
1017                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name2,
1018                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder2,
1019                             -1);
1020
1021         if (type == TNY_FOLDER_TYPE_ROOT) {
1022                 /* Compare the types, so that 
1023                  * Remote accounts -> Local account -> MMC account .*/
1024                 const gint pos1 = get_cmp_rows_type_pos (folder1);
1025                 const gint pos2 = get_cmp_rows_type_pos (folder2);
1026                 /* printf ("DEBUG: %s:\n  type1=%s, pos1=%d\n  type2=%s, pos2=%d\n", 
1027                         __FUNCTION__, G_OBJECT_TYPE_NAME(folder1), pos1, G_OBJECT_TYPE_NAME(folder2), pos2); */
1028                 if (pos1 <  pos2)
1029                         cmp = -1;
1030                 else if (pos1 > pos2)
1031                         cmp = 1;
1032                 else {
1033                         /* Compare items of the same type: */
1034                         
1035                         TnyAccount *account1 = NULL;
1036                         if (TNY_IS_ACCOUNT (folder1))
1037                                 account1 = TNY_ACCOUNT (folder1);
1038                                 
1039                         TnyAccount *account2 = NULL;
1040                         if (TNY_IS_ACCOUNT (folder2))
1041                                 account2 = TNY_ACCOUNT (folder2);
1042                                 
1043                         const gchar *account_id = account1 ? tny_account_get_id (account1) : NULL;
1044                         const gchar *account_id2 = account2 ? tny_account_get_id (account2) : NULL;
1045         
1046                         if (!account_id && !account_id2)
1047                                 return 0;
1048                         else if (!account_id)
1049                                 return -1;
1050                         else if (!account_id2)
1051                                 return +1;
1052                         else if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
1053                                 cmp = +1;
1054                         else
1055                                 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
1056                 }
1057         } else {
1058                 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
1059         }
1060         
1061         if (folder1)
1062                 g_object_unref(G_OBJECT(folder1));
1063         if (folder2)
1064                 g_object_unref(G_OBJECT(folder2));
1065
1066         g_free (name1);
1067         g_free (name2);
1068
1069         return cmp;     
1070 }
1071
1072 /*****************************************************************************/
1073 /*                        DRAG and DROP stuff                                */
1074 /*****************************************************************************/
1075
1076 /*
1077  * This function fills the #GtkSelectionData with the row and the
1078  * model that has been dragged. It's called when this widget is a
1079  * source for dnd after the event drop happened
1080  */
1081 static void
1082 on_drag_data_get (GtkWidget *widget, 
1083                   GdkDragContext *context, 
1084                   GtkSelectionData *selection_data, 
1085                   guint info, 
1086                   guint time, 
1087                   gpointer data)
1088 {
1089         GtkTreeSelection *selection;
1090         GtkTreeModel *model;
1091         GtkTreeIter iter;
1092         GtkTreePath *source_row;
1093
1094         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1095         gtk_tree_selection_get_selected (selection, &model, &iter);
1096         source_row = gtk_tree_model_get_path (model, &iter);
1097
1098         gtk_tree_set_row_drag_data (selection_data,
1099                                     model,
1100                                     source_row);
1101
1102         gtk_tree_path_free (source_row);
1103 }
1104
1105 typedef struct _DndHelper {
1106         gboolean delete_source;
1107         GtkTreePath *source_row;
1108         GdkDragContext *context;
1109         guint time;
1110 } DndHelper;
1111
1112
1113 /*
1114  * This function is the callback of the
1115  * modest_mail_operation_xfer_msgs () and
1116  * modest_mail_operation_xfer_folder() calls. We check here if the
1117  * message/folder was correctly asynchronously transferred. The reason
1118  * to use the same callback is that the code is the same, it only has
1119  * to check that the operation went fine and then finalize the drag
1120  * and drop action
1121  */
1122 static void
1123 on_progress_changed (ModestMailOperation *mail_op, 
1124                      ModestMailOperationState *state,
1125                      gpointer user_data)
1126 {
1127         gboolean success;
1128         DndHelper *helper;
1129
1130         helper = (DndHelper *) user_data;
1131
1132         if (!state->finished)
1133                 return;
1134
1135         if (state->status == MODEST_MAIL_OPERATION_STATUS_SUCCESS) {
1136                 success = TRUE;
1137         } else {
1138                 success = FALSE;
1139         }
1140
1141         /* Notify the drag source. Never call delete, the monitor will
1142            do the job if needed */
1143         gtk_drag_finish (helper->context, success, FALSE, helper->time);
1144
1145         /* Free the helper */
1146         gtk_tree_path_free (helper->source_row);        
1147         g_slice_free (DndHelper, helper);
1148 }
1149
1150 /*
1151  * This function is used by drag_data_received_cb to manage drag and
1152  * drop of a header, i.e, and drag from the header view to the folder
1153  * view.
1154  */
1155 static void
1156 drag_and_drop_from_header_view (GtkTreeModel *source_model,
1157                                 GtkTreeModel *dest_model,
1158                                 GtkTreePath  *dest_row,
1159                                 DndHelper    *helper)
1160 {
1161         TnyList *headers = NULL;
1162         TnyHeader *header = NULL;
1163         TnyFolder *folder = NULL;
1164         ModestMailOperation *mail_op = NULL;
1165         GtkTreeIter source_iter, dest_iter;
1166
1167         g_return_if_fail (GTK_IS_TREE_MODEL(source_model));
1168         g_return_if_fail (GTK_IS_TREE_MODEL(dest_model));
1169         g_return_if_fail (dest_row);
1170         g_return_if_fail (helper);
1171         
1172         /* Get header */
1173         gtk_tree_model_get_iter (source_model, &source_iter, helper->source_row);
1174         gtk_tree_model_get (source_model, &source_iter, 
1175                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, 
1176                             &header, -1);
1177         if (!TNY_IS_HEADER(header)) {
1178                 g_warning ("BUG: %s could not get a valid header", __FUNCTION__);
1179                 goto cleanup;
1180         }
1181         
1182         /* Get Folder */
1183         gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
1184         gtk_tree_model_get (dest_model, &dest_iter, 
1185                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, 
1186                             &folder, -1);
1187
1188         if (!TNY_IS_FOLDER(folder)) {
1189                 g_warning ("BUG: %s could not get a valid folder", __FUNCTION__);
1190                 goto cleanup;
1191         }
1192
1193         /* Transfer message */
1194         mail_op = modest_mail_operation_new (MODEST_MAIL_OPERATION_TYPE_RECEIVE, NULL);
1195         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1196                                          mail_op);
1197         g_signal_connect (G_OBJECT (mail_op), "progress-changed",
1198                           G_CALLBACK (on_progress_changed), helper);
1199
1200         headers = tny_simple_list_new ();
1201         tny_list_append (headers, G_OBJECT (header));
1202         modest_mail_operation_xfer_msgs (mail_op, 
1203                                          headers, 
1204                                          folder, 
1205                                          helper->delete_source, 
1206                                          NULL, NULL);
1207         
1208         /* Frees */
1209 cleanup:
1210         if (G_IS_OBJECT(mail_op))
1211                 g_object_unref (G_OBJECT (mail_op));
1212         if (G_IS_OBJECT(header))
1213                 g_object_unref (G_OBJECT (header));
1214         if (G_IS_OBJECT(folder))
1215                 g_object_unref (G_OBJECT (folder));
1216         if (G_IS_OBJECT(headers))
1217                 g_object_unref (headers);
1218 }
1219
1220 /*
1221  * This function is used by drag_data_received_cb to manage drag and
1222  * drop of a folder, i.e, and drag from the folder view to the same
1223  * folder view.
1224  */
1225 static void
1226 drag_and_drop_from_folder_view (GtkTreeModel     *source_model,
1227                                 GtkTreeModel     *dest_model,
1228                                 GtkTreePath      *dest_row,
1229                                 GtkSelectionData *selection_data,
1230                                 DndHelper        *helper)
1231 {
1232         ModestMailOperation *mail_op;
1233         GtkTreeIter parent_iter, iter;
1234         TnyFolderStore *parent_folder;
1235         TnyFolder *folder;
1236
1237         /* Check if the drag is possible */
1238 /*      if (!gtk_tree_path_compare (helper->source_row, dest_row) || */
1239 /*          !gtk_tree_drag_dest_row_drop_possible (GTK_TREE_DRAG_DEST (dest_model), */
1240 /*                                                 dest_row, */
1241 /*                                                 selection_data)) { */
1242         if (!gtk_tree_path_compare (helper->source_row, dest_row)) {
1243
1244                 gtk_drag_finish (helper->context, FALSE, FALSE, helper->time);
1245                 gtk_tree_path_free (helper->source_row);        
1246                 g_slice_free (DndHelper, helper);
1247                 return;
1248         }
1249
1250         /* Get data */
1251         gtk_tree_model_get_iter (source_model, &parent_iter, dest_row);
1252         gtk_tree_model_get_iter (source_model, &iter, helper->source_row);
1253         gtk_tree_model_get (source_model, &parent_iter, 
1254                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, 
1255                             &parent_folder, -1);
1256         gtk_tree_model_get (source_model, &iter,
1257                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
1258                             &folder, -1);
1259
1260         /* Do the mail operation */
1261         mail_op = modest_mail_operation_new_with_error_handling (MODEST_MAIL_OPERATION_TYPE_RECEIVE, 
1262                                                                  NULL,
1263                                                                  modest_ui_actions_move_folder_error_handler,
1264                                                                  NULL);
1265         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (), 
1266                                          mail_op);
1267         g_signal_connect (G_OBJECT (mail_op), "progress-changed",
1268                           G_CALLBACK (on_progress_changed), helper);
1269
1270         modest_mail_operation_xfer_folder (mail_op, 
1271                                            folder, 
1272                                            parent_folder,
1273                                            helper->delete_source);
1274         
1275         /* Frees */
1276         g_object_unref (G_OBJECT (parent_folder));
1277         g_object_unref (G_OBJECT (folder));
1278         g_object_unref (G_OBJECT (mail_op));
1279 }
1280
1281 /*
1282  * This function receives the data set by the "drag-data-get" signal
1283  * handler. This information comes within the #GtkSelectionData. This
1284  * function will manage both the drags of folders of the treeview and
1285  * drags of headers of the header view widget.
1286  */
1287 static void 
1288 on_drag_data_received (GtkWidget *widget, 
1289                        GdkDragContext *context, 
1290                        gint x, 
1291                        gint y, 
1292                        GtkSelectionData *selection_data, 
1293                        guint target_type, 
1294                        guint time, 
1295                        gpointer data)
1296 {
1297         GtkWidget *source_widget;
1298         GtkTreeModel *dest_model, *source_model;
1299         GtkTreePath *source_row, *dest_row;
1300         GtkTreeViewDropPosition pos;
1301         gboolean success = FALSE, delete_source = FALSE;
1302         DndHelper *helper = NULL; 
1303
1304         /* Do not allow further process */
1305         g_signal_stop_emission_by_name (widget, "drag-data-received");
1306         source_widget = gtk_drag_get_source_widget (context);
1307
1308         /* Get the action */
1309         if (context->action == GDK_ACTION_MOVE) {
1310                 delete_source = TRUE;
1311
1312                 /* Notify that there is no folder selected. We need to
1313                    do this in order to update the headers view (and
1314                    its monitors, because when moving, the old folder
1315                    won't longer exist. We can not wait for the end of
1316                    the operation, because the operation won't start if
1317                    the folder is in use */
1318                 if (source_widget == widget)
1319                         g_signal_emit (G_OBJECT (widget), 
1320                                        signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0, NULL, TRUE);
1321         }
1322
1323         /* Check if the get_data failed */
1324         if (selection_data == NULL || selection_data->length < 0)
1325                 gtk_drag_finish (context, success, FALSE, time);
1326
1327         /* Get the models */
1328         gtk_tree_get_row_drag_data (selection_data,
1329                                     &source_model,
1330                                     &source_row);
1331
1332         /* Select the destination model */
1333         if (source_widget == widget) {
1334                 dest_model = source_model;
1335         } else {
1336                 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
1337         }
1338
1339         /* Get the path to the destination row. Can not call
1340            gtk_tree_view_get_drag_dest_row() because the source row
1341            is not selected anymore */
1342         gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget), x, y,
1343                                            &dest_row, &pos);
1344
1345         /* Only allow drops IN other rows */
1346         if (!dest_row || pos == GTK_TREE_VIEW_DROP_BEFORE || pos == GTK_TREE_VIEW_DROP_AFTER)
1347                 gtk_drag_finish (context, success, FALSE, time);
1348
1349         /* Create the helper */
1350         helper = g_slice_new0 (DndHelper);
1351         helper->delete_source = delete_source;
1352         helper->source_row = gtk_tree_path_copy (source_row);
1353         helper->context = context;
1354         helper->time = time;
1355
1356         /* Drags from the header view */
1357         if (source_widget != widget) {
1358
1359                 drag_and_drop_from_header_view (source_model,
1360                                                 dest_model,
1361                                                 dest_row,
1362                                                 helper);
1363         } else {
1364
1365
1366                 drag_and_drop_from_folder_view (source_model,
1367                                                 dest_model,
1368                                                 dest_row,
1369                                                 selection_data, 
1370                                                 helper);
1371         }
1372
1373         /* Frees */
1374         gtk_tree_path_free (source_row);
1375         gtk_tree_path_free (dest_row);
1376 }
1377
1378 /*
1379  * We define a "drag-drop" signal handler because we do not want to
1380  * use the default one, because the default one always calls
1381  * gtk_drag_finish and we prefer to do it in the "drag-data-received"
1382  * signal handler, because there we have all the information available
1383  * to know if the dnd was a success or not.
1384  */
1385 static gboolean
1386 drag_drop_cb (GtkWidget      *widget,
1387               GdkDragContext *context,
1388               gint            x,
1389               gint            y,
1390               guint           time,
1391               gpointer        user_data) 
1392 {
1393         gpointer target;
1394
1395         if (!context->targets)
1396                 return FALSE;
1397
1398         /* Check if we're dragging a folder row */
1399         target = gtk_drag_dest_find_target (widget, context, NULL);
1400
1401         /* Request the data from the source. */
1402         gtk_drag_get_data(widget, context, target, time);
1403
1404     return TRUE;
1405 }
1406
1407 /*
1408  * This function expands a node of a tree view if it's not expanded
1409  * yet. Not sure why it needs the threads stuff, but gtk+`example code
1410  * does that, so that's why they're here.
1411  */
1412 static gint
1413 expand_row_timeout (gpointer data)
1414 {
1415         GtkTreeView *tree_view = data;
1416         GtkTreePath *dest_path = NULL;
1417         GtkTreeViewDropPosition pos;
1418         gboolean result = FALSE;
1419         
1420         GDK_THREADS_ENTER ();
1421         
1422         gtk_tree_view_get_drag_dest_row (tree_view,
1423                                          &dest_path,
1424                                          &pos);
1425         
1426         if (dest_path &&
1427             (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER ||
1428              pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)) {
1429                 gtk_tree_view_expand_row (tree_view, dest_path, FALSE);
1430                 gtk_tree_path_free (dest_path);
1431         }
1432         else {
1433                 if (dest_path)
1434                         gtk_tree_path_free (dest_path);
1435                 
1436                 result = TRUE;
1437         }
1438         
1439         GDK_THREADS_LEAVE ();
1440
1441         return result;
1442 }
1443
1444 /*
1445  * This function is called whenever the pointer is moved over a widget
1446  * while dragging some data. It installs a timeout that will expand a
1447  * node of the treeview if not expanded yet. This function also calls
1448  * gdk_drag_status in order to set the suggested action that will be
1449  * used by the "drag-data-received" signal handler to know if we
1450  * should do a move or just a copy of the data.
1451  */
1452 static gboolean
1453 on_drag_motion (GtkWidget      *widget,
1454                 GdkDragContext *context,
1455                 gint            x,
1456                 gint            y,
1457                 guint           time,
1458                 gpointer        user_data)  
1459 {
1460         GtkTreeViewDropPosition pos;
1461         GtkTreePath *dest_row;
1462         ModestFolderViewPrivate *priv;
1463         GdkDragAction suggested_action;
1464         gboolean valid_location = FALSE;
1465
1466         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
1467
1468         if (priv->timer_expander != 0) {
1469                 g_source_remove (priv->timer_expander);
1470                 priv->timer_expander = 0;
1471         }
1472
1473         gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
1474                                            x, y,
1475                                            &dest_row,
1476                                            &pos);
1477
1478         /* Do not allow drops between folders */
1479         if (!dest_row ||
1480             pos == GTK_TREE_VIEW_DROP_BEFORE ||
1481             pos == GTK_TREE_VIEW_DROP_AFTER) {
1482                 gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW (widget), NULL, 0);
1483                 gdk_drag_status(context, 0, time);
1484                 valid_location = FALSE;
1485                 goto out;
1486         } else {
1487                 valid_location = TRUE;
1488         }
1489
1490         /* Expand the selected row after 1/2 second */
1491         if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), dest_row)) {
1492                 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), dest_row, pos);
1493                 priv->timer_expander = g_timeout_add (500, expand_row_timeout, widget);
1494         }
1495
1496         /* Select the desired action. By default we pick MOVE */
1497         suggested_action = GDK_ACTION_MOVE;
1498
1499         if (context->actions == GDK_ACTION_COPY)
1500             gdk_drag_status(context, GDK_ACTION_COPY, time);
1501         else if (context->actions == GDK_ACTION_MOVE)
1502             gdk_drag_status(context, GDK_ACTION_MOVE, time);
1503         else if (context->actions & suggested_action)
1504             gdk_drag_status(context, suggested_action, time);
1505         else
1506             gdk_drag_status(context, GDK_ACTION_DEFAULT, time);
1507
1508  out:
1509         if (dest_row)
1510                 gtk_tree_path_free (dest_row);
1511         g_signal_stop_emission_by_name (widget, "drag-motion");
1512         return valid_location;
1513 }
1514
1515
1516 /* Folder view drag types */
1517 const GtkTargetEntry folder_view_drag_types[] =
1518 {
1519         { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, MODEST_FOLDER_ROW },
1520         { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_APP,    MODEST_HEADER_ROW }
1521 };
1522
1523 /*
1524  * This function sets the treeview as a source and a target for dnd
1525  * events. It also connects all the requirede signals.
1526  */
1527 static void
1528 setup_drag_and_drop (GtkTreeView *self)
1529 {
1530         /* Set up the folder view as a dnd destination. Set only the
1531            highlight flag, otherwise gtk will have a different
1532            behaviour */
1533         gtk_drag_dest_set (GTK_WIDGET (self),
1534                            GTK_DEST_DEFAULT_HIGHLIGHT,
1535                            folder_view_drag_types,
1536                            G_N_ELEMENTS (folder_view_drag_types),
1537                            GDK_ACTION_MOVE | GDK_ACTION_COPY);
1538
1539         g_signal_connect (G_OBJECT (self),
1540                           "drag_data_received",
1541                           G_CALLBACK (on_drag_data_received),
1542                           NULL);
1543
1544
1545         /* Set up the treeview as a dnd source */
1546         gtk_drag_source_set (GTK_WIDGET (self),
1547                              GDK_BUTTON1_MASK,
1548                              folder_view_drag_types,
1549                              G_N_ELEMENTS (folder_view_drag_types),
1550                              GDK_ACTION_MOVE | GDK_ACTION_COPY);
1551
1552         g_signal_connect (G_OBJECT (self),
1553                           "drag_motion",
1554                           G_CALLBACK (on_drag_motion),
1555                           NULL);
1556         
1557         g_signal_connect (G_OBJECT (self),
1558                           "drag_data_get",
1559                           G_CALLBACK (on_drag_data_get),
1560                           NULL);
1561
1562         g_signal_connect (G_OBJECT (self),
1563                           "drag_drop",
1564                           G_CALLBACK (drag_drop_cb),
1565                           NULL);
1566 }
1567
1568 /*
1569  * This function manages the navigation through the folders using the
1570  * keyboard or the hardware keys in the device
1571  */
1572 static gboolean
1573 on_key_pressed (GtkWidget *self,
1574                 GdkEventKey *event,
1575                 gpointer user_data)
1576 {
1577         GtkTreeSelection *selection;
1578         GtkTreeIter iter;
1579         GtkTreeModel *model;
1580         gboolean retval = FALSE;
1581
1582         /* Up and Down are automatically managed by the treeview */
1583         if (event->keyval == GDK_Return) {
1584                 /* Expand/Collapse the selected row */
1585                 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1586                 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
1587                         GtkTreePath *path;
1588
1589                         path = gtk_tree_model_get_path (model, &iter);
1590
1591                         if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (self), path))
1592                                 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1593                         else
1594                                 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1595                         gtk_tree_path_free (path);
1596                 }
1597                 /* No further processing */
1598                 retval = TRUE;
1599         }
1600
1601         return retval;
1602 }
1603
1604 /*
1605  * We listen to the changes in the local folder account name key,
1606  * because we want to show the right name in the view. The local
1607  * folder account name corresponds to the device name in the Maemo
1608  * version. We do this because we do not want to query gconf on each
1609  * tree view refresh. It's better to cache it and change whenever
1610  * necessary.
1611  */
1612 static void 
1613 on_configuration_key_changed (ModestConf* conf, 
1614                               const gchar *key, 
1615                               ModestConfEvent event, 
1616                               ModestFolderView *self)
1617 {
1618         ModestFolderViewPrivate *priv;
1619
1620         if (!key)
1621                 return;
1622
1623         g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
1624         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1625
1626         if (!strcmp (key, MODEST_CONF_DEVICE_NAME)) {
1627                 g_free (priv->local_account_name);
1628
1629                 if (event == MODEST_CONF_EVENT_KEY_UNSET)
1630                         priv->local_account_name = g_strdup (MODEST_LOCAL_FOLDERS_DEFAULT_DISPLAY_NAME);
1631                 else
1632                         priv->local_account_name = modest_conf_get_string (modest_runtime_get_conf(),
1633                                                                            MODEST_CONF_DEVICE_NAME, NULL);
1634
1635                 /* Force a redraw */
1636 #if GTK_CHECK_VERSION(2, 8, 0) /* gtk_tree_view_column_queue_resize is only available in GTK+ 2.8 */
1637                 GtkTreeViewColumn * tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self), 
1638                                                                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN);
1639                 gtk_tree_view_column_queue_resize (tree_column);
1640 #endif
1641         }
1642 }
1643
1644 void 
1645 modest_folder_view_set_style (ModestFolderView *self,
1646                               ModestFolderViewStyle style)
1647 {
1648         ModestFolderViewPrivate *priv;
1649
1650         g_return_if_fail (self);
1651         
1652         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1653
1654         priv->style = style;
1655 }
1656
1657 void
1658 modest_folder_view_set_account_id_of_visible_server_account (ModestFolderView *self,
1659                                                              const gchar *account_id)
1660 {
1661         ModestFolderViewPrivate *priv;
1662         GtkTreeModel *model;
1663
1664         g_return_if_fail (self);
1665         
1666         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1667
1668         /* This will be used by the filter_row callback,
1669          * to decided which rows to show: */
1670         if (priv->visible_account_id) {
1671                 g_free (priv->visible_account_id);
1672                 priv->visible_account_id = NULL;
1673         }
1674         if (account_id)
1675                 priv->visible_account_id = g_strdup (account_id);
1676
1677         /* Refilter */
1678         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1679         if (GTK_IS_TREE_MODEL_FILTER (model))
1680                 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
1681
1682         /* Save settings to gconf */
1683         modest_widget_memory_save (modest_runtime_get_conf (), G_OBJECT(self),
1684                                    MODEST_CONF_FOLDER_VIEW_KEY);
1685 }
1686
1687 const gchar *
1688 modest_folder_view_get_account_id_of_visible_server_account (ModestFolderView *self)
1689 {
1690         ModestFolderViewPrivate *priv;
1691
1692         g_return_val_if_fail (self, NULL);
1693         
1694         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1695
1696         return (const gchar *) priv->visible_account_id;
1697 }
1698
1699 static gboolean
1700 find_inbox_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *inbox_iter)
1701 {
1702         do {
1703                 GtkTreeIter child;
1704                 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1705                 gchar *name = NULL;
1706
1707                 gtk_tree_model_get (model, iter, 
1708                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name,
1709                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, 
1710                                     &type, -1);
1711
1712                 /*
1713                 printf ("DEBUG: %s: name=%s, type=%d, TNY_FOLDER_TYPE_INBOX=%d\n", 
1714                         __FUNCTION__, name, type, TNY_FOLDER_TYPE_INBOX);
1715                 */
1716                         
1717                 gboolean result = FALSE;
1718                 if (type == TNY_FOLDER_TYPE_INBOX) {
1719                         result = TRUE;
1720                 } else if (type == TNY_FOLDER_TYPE_NORMAL) {
1721                         /* tinymail's camel implementation only provides TNY_FOLDER_TYPE_NORMAL
1722                          * when getting folders from the cache, before connectin, so we do 
1723                          * an extra check. We could fix this in tinymail, but it's easier 
1724                          * to do here.
1725                          */
1726                          if (strcmp (name, "Inbox") == 0)
1727                                 result = TRUE;
1728                 }
1729                 
1730                 g_free (name);
1731                 
1732                 if (result) {
1733                         *inbox_iter = *iter;
1734                         return TRUE;
1735                 }
1736
1737                 if (gtk_tree_model_iter_children (model, &child, iter)) {
1738                         if (find_inbox_iter (model, &child, inbox_iter))
1739                                 return TRUE;
1740                 }
1741
1742         } while (gtk_tree_model_iter_next (model, iter));
1743
1744         return FALSE;
1745 }
1746
1747 void 
1748 modest_folder_view_select_first_inbox_or_local (ModestFolderView *self)
1749 {
1750         GtkTreeModel *model;
1751         GtkTreeIter iter, inbox_iter;
1752         GtkTreeSelection *sel;
1753
1754 /*      /\* Do not set it if the folder view was not painted *\/ */
1755 /*      if (!GTK_WIDGET_MAPPED (self)) */
1756 /*              return; */
1757
1758         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1759         if (!model)
1760                 return;
1761
1762         expand_root_items (self);
1763         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1764
1765         gtk_tree_model_get_iter_first (model, &iter);
1766         if (find_inbox_iter (model, &iter, &inbox_iter)) {
1767                 gtk_tree_selection_select_iter (sel, &inbox_iter);
1768         }
1769         else {
1770                 gtk_tree_model_get_iter_first (model, &iter);
1771                 gtk_tree_selection_select_iter (sel, &iter);
1772         }
1773 }
1774
1775 void 
1776 modest_folder_view_copy_selection (ModestFolderView *folder_view)
1777 {
1778         /* Copy selection */
1779         _clipboard_set_selected_data (folder_view, FALSE);
1780 }
1781
1782 void 
1783 modest_folder_view_cut_selection (ModestFolderView *folder_view)
1784 {
1785         ModestFolderViewPrivate *priv = NULL;
1786         GtkTreeModel *model = NULL;
1787         const gchar **hidding = NULL;
1788         guint i, n_selected;
1789
1790         g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view));
1791         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
1792
1793         /* Copy selection */
1794         _clipboard_set_selected_data (folder_view, TRUE);
1795
1796         /* Get hidding ids */
1797         hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected); 
1798         
1799         /* Clear hidding array created by previous cut operation */
1800         _clear_hidding_filter (MODEST_FOLDER_VIEW (folder_view));
1801
1802         /* Copy hidding array */
1803         priv->n_selected = n_selected;
1804         priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
1805         for (i=0; i < n_selected; i++) 
1806                 priv->hidding_ids[i] = g_strdup(hidding[i]);            
1807
1808         /* Hide cut folders */
1809         model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
1810         gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
1811 }
1812
1813 static void
1814 _clipboard_set_selected_data (ModestFolderView *folder_view,
1815                               gboolean delete)
1816 {
1817         ModestFolderViewPrivate *priv = NULL;
1818         TnyFolderStore *folder = NULL;
1819
1820         g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view));
1821         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
1822                 
1823         /* Set selected data on clipboard   */
1824         g_return_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard));
1825         folder = modest_folder_view_get_selected (folder_view);
1826         modest_email_clipboard_set_data (priv->clipboard, TNY_FOLDER(folder), NULL, delete);
1827
1828         /* Free */
1829         g_object_unref (folder);
1830 }
1831
1832 static void
1833 _clear_hidding_filter (ModestFolderView *folder_view) 
1834 {
1835         ModestFolderViewPrivate *priv;
1836         guint i;
1837         
1838         g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view)); 
1839         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
1840
1841         if (priv->hidding_ids != NULL) {
1842                 for (i=0; i < priv->n_selected; i++) 
1843                         g_free (priv->hidding_ids[i]);
1844                 g_free(priv->hidding_ids);
1845         }       
1846 }