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