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