fixes NB#63563
[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                         else
1626                                 /* Dragging and dropping of remote folders to local
1627                                  * should not possible. Fixes NB#63563 */
1628                                 forbidden = modest_tny_folder_is_remote_folder(
1629                                                 folder);
1630                         g_object_unref (folder);
1631                 }
1632
1633         }
1634
1635         
1636         /* Check if the drag is possible */
1637         if (forbidden || !gtk_tree_path_compare (helper->source_row, dest_row)) {
1638
1639                 gtk_drag_finish (helper->context, FALSE, FALSE, helper->time);
1640                 gtk_tree_path_free (helper->source_row);        
1641                 g_slice_free (DndHelper, helper);
1642                 return;
1643         }
1644
1645         /* Get data */
1646         gtk_tree_model_get_iter (source_model, &parent_iter, dest_row);
1647         gtk_tree_model_get_iter (source_model, &iter, helper->source_row);
1648         gtk_tree_model_get (source_model, &parent_iter, 
1649                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, 
1650                             &parent_folder, -1);
1651         gtk_tree_model_get (source_model, &iter,
1652                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
1653                             &folder, -1);
1654
1655         /* Offer the connection dialog if necessary, for the destination parent folder and source folder: */
1656         if (modest_platform_connect_and_wait_if_network_folderstore (NULL, parent_folder) && 
1657                 modest_platform_connect_and_wait_if_network_folderstore (NULL, TNY_FOLDER_STORE (folder))) {
1658                 /* Do the mail operation */
1659                 mail_op = modest_mail_operation_new_with_error_handling (MODEST_MAIL_OPERATION_TYPE_RECEIVE, 
1660                                                                  NULL,
1661                                                                  modest_ui_actions_move_folder_error_handler,
1662                                                                  NULL);
1663                 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (), 
1664                                          mail_op);
1665                 g_signal_connect (G_OBJECT (mail_op), "progress-changed",
1666                                   G_CALLBACK (on_progress_changed), helper);
1667
1668                 modest_mail_operation_xfer_folder (mail_op, 
1669                                            folder, 
1670                                            parent_folder,
1671                                            helper->delete_source,
1672                                            NULL,
1673                                            NULL);
1674
1675                 g_object_unref (G_OBJECT (mail_op));    
1676         }
1677         
1678         /* Frees */
1679         g_object_unref (G_OBJECT (parent_folder));
1680         g_object_unref (G_OBJECT (folder));
1681 }
1682
1683 /*
1684  * This function receives the data set by the "drag-data-get" signal
1685  * handler. This information comes within the #GtkSelectionData. This
1686  * function will manage both the drags of folders of the treeview and
1687  * drags of headers of the header view widget.
1688  */
1689 static void 
1690 on_drag_data_received (GtkWidget *widget, 
1691                        GdkDragContext *context, 
1692                        gint x, 
1693                        gint y, 
1694                        GtkSelectionData *selection_data, 
1695                        guint target_type, 
1696                        guint time, 
1697                        gpointer data)
1698 {
1699         GtkWidget *source_widget;
1700         GtkTreeModel *dest_model, *source_model;
1701         GtkTreePath *source_row, *dest_row;
1702         GtkTreeViewDropPosition pos;
1703         gboolean success = FALSE, delete_source = FALSE;
1704         DndHelper *helper = NULL; 
1705
1706         /* Do not allow further process */
1707         g_signal_stop_emission_by_name (widget, "drag-data-received");
1708         source_widget = gtk_drag_get_source_widget (context);
1709
1710         /* Get the action */
1711         if (context->action == GDK_ACTION_MOVE) {
1712                 delete_source = TRUE;
1713
1714                 /* Notify that there is no folder selected. We need to
1715                    do this in order to update the headers view (and
1716                    its monitors, because when moving, the old folder
1717                    won't longer exist. We can not wait for the end of
1718                    the operation, because the operation won't start if
1719                    the folder is in use */
1720                 if (source_widget == widget) {
1721                         ModestFolderViewPrivate *priv;
1722
1723                         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
1724                         if (priv->cur_folder_store) {
1725                                 g_object_unref (priv->cur_folder_store);
1726                                 priv->cur_folder_store = NULL;
1727                         }
1728
1729                         g_signal_emit (G_OBJECT (widget), 
1730                                        signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0, NULL, FALSE);
1731                 }
1732         }
1733
1734         /* Check if the get_data failed */
1735         if (selection_data == NULL || selection_data->length < 0)
1736                 gtk_drag_finish (context, success, FALSE, time);
1737
1738         /* Get the models */
1739         gtk_tree_get_row_drag_data (selection_data,
1740                                     &source_model,
1741                                     &source_row);
1742
1743         /* Select the destination model */
1744         if (source_widget == widget) {
1745                 dest_model = source_model;
1746         } else {
1747                 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
1748         }
1749
1750         /* Get the path to the destination row. Can not call
1751            gtk_tree_view_get_drag_dest_row() because the source row
1752            is not selected anymore */
1753         gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget), x, y,
1754                                            &dest_row, &pos);
1755
1756         /* Only allow drops IN other rows */
1757         if (!dest_row || pos == GTK_TREE_VIEW_DROP_BEFORE || pos == GTK_TREE_VIEW_DROP_AFTER)
1758                 gtk_drag_finish (context, success, FALSE, time);
1759
1760         /* Create the helper */
1761         helper = g_slice_new0 (DndHelper);
1762         helper->delete_source = delete_source;
1763         helper->source_row = gtk_tree_path_copy (source_row);
1764         helper->context = context;
1765         helper->time = time;
1766
1767         /* Drags from the header view */
1768         if (source_widget != widget) {
1769
1770                 drag_and_drop_from_header_view (source_model,
1771                                                 dest_model,
1772                                                 dest_row,
1773                                                 helper);
1774         } else {
1775
1776
1777                 drag_and_drop_from_folder_view (source_model,
1778                                                 dest_model,
1779                                                 dest_row,
1780                                                 selection_data, 
1781                                                 helper);
1782         }
1783
1784         /* Frees */
1785         gtk_tree_path_free (source_row);
1786         gtk_tree_path_free (dest_row);
1787 }
1788
1789 /*
1790  * We define a "drag-drop" signal handler because we do not want to
1791  * use the default one, because the default one always calls
1792  * gtk_drag_finish and we prefer to do it in the "drag-data-received"
1793  * signal handler, because there we have all the information available
1794  * to know if the dnd was a success or not.
1795  */
1796 static gboolean
1797 drag_drop_cb (GtkWidget      *widget,
1798               GdkDragContext *context,
1799               gint            x,
1800               gint            y,
1801               guint           time,
1802               gpointer        user_data) 
1803 {
1804         gpointer target;
1805
1806         if (!context->targets)
1807                 return FALSE;
1808
1809         /* Check if we're dragging a folder row */
1810         target = gtk_drag_dest_find_target (widget, context, NULL);
1811
1812         /* Request the data from the source. */
1813         gtk_drag_get_data(widget, context, target, time);
1814
1815     return TRUE;
1816 }
1817
1818 /*
1819  * This function expands a node of a tree view if it's not expanded
1820  * yet. Not sure why it needs the threads stuff, but gtk+`example code
1821  * does that, so that's why they're here.
1822  */
1823 static gint
1824 expand_row_timeout (gpointer data)
1825 {
1826         GtkTreeView *tree_view = data;
1827         GtkTreePath *dest_path = NULL;
1828         GtkTreeViewDropPosition pos;
1829         gboolean result = FALSE;
1830         
1831         GDK_THREADS_ENTER ();
1832         
1833         gtk_tree_view_get_drag_dest_row (tree_view,
1834                                          &dest_path,
1835                                          &pos);
1836         
1837         if (dest_path &&
1838             (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER ||
1839              pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)) {
1840                 gtk_tree_view_expand_row (tree_view, dest_path, FALSE);
1841                 gtk_tree_path_free (dest_path);
1842         }
1843         else {
1844                 if (dest_path)
1845                         gtk_tree_path_free (dest_path);
1846                 
1847                 result = TRUE;
1848         }
1849         
1850         GDK_THREADS_LEAVE ();
1851
1852         return result;
1853 }
1854
1855 /*
1856  * This function is called whenever the pointer is moved over a widget
1857  * while dragging some data. It installs a timeout that will expand a
1858  * node of the treeview if not expanded yet. This function also calls
1859  * gdk_drag_status in order to set the suggested action that will be
1860  * used by the "drag-data-received" signal handler to know if we
1861  * should do a move or just a copy of the data.
1862  */
1863 static gboolean
1864 on_drag_motion (GtkWidget      *widget,
1865                 GdkDragContext *context,
1866                 gint            x,
1867                 gint            y,
1868                 guint           time,
1869                 gpointer        user_data)  
1870 {
1871         GtkTreeViewDropPosition pos;
1872         GtkTreePath *dest_row;
1873         ModestFolderViewPrivate *priv;
1874         GdkDragAction suggested_action;
1875         gboolean valid_location = FALSE;
1876
1877         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
1878
1879         if (priv->timer_expander != 0) {
1880                 g_source_remove (priv->timer_expander);
1881                 priv->timer_expander = 0;
1882         }
1883
1884         gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
1885                                            x, y,
1886                                            &dest_row,
1887                                            &pos);
1888
1889         /* Do not allow drops between folders */
1890         if (!dest_row ||
1891             pos == GTK_TREE_VIEW_DROP_BEFORE ||
1892             pos == GTK_TREE_VIEW_DROP_AFTER) {
1893                 gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW (widget), NULL, 0);
1894                 gdk_drag_status(context, 0, time);
1895                 valid_location = FALSE;
1896                 goto out;
1897         } else {
1898                 valid_location = TRUE;
1899         }
1900
1901         /* Expand the selected row after 1/2 second */
1902         if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), dest_row)) {
1903                 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), dest_row, pos);
1904                 priv->timer_expander = g_timeout_add (500, expand_row_timeout, widget);
1905         }
1906
1907         /* Select the desired action. By default we pick MOVE */
1908         suggested_action = GDK_ACTION_MOVE;
1909
1910         if (context->actions == GDK_ACTION_COPY)
1911             gdk_drag_status(context, GDK_ACTION_COPY, time);
1912         else if (context->actions == GDK_ACTION_MOVE)
1913             gdk_drag_status(context, GDK_ACTION_MOVE, time);
1914         else if (context->actions & suggested_action)
1915             gdk_drag_status(context, suggested_action, time);
1916         else
1917             gdk_drag_status(context, GDK_ACTION_DEFAULT, time);
1918
1919  out:
1920         if (dest_row)
1921                 gtk_tree_path_free (dest_row);
1922         g_signal_stop_emission_by_name (widget, "drag-motion");
1923         return valid_location;
1924 }
1925
1926
1927 /* Folder view drag types */
1928 const GtkTargetEntry folder_view_drag_types[] =
1929 {
1930         { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, MODEST_FOLDER_ROW },
1931         { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_APP,    MODEST_HEADER_ROW }
1932 };
1933
1934 /*
1935  * This function sets the treeview as a source and a target for dnd
1936  * events. It also connects all the requirede signals.
1937  */
1938 static void
1939 setup_drag_and_drop (GtkTreeView *self)
1940 {
1941         /* Set up the folder view as a dnd destination. Set only the
1942            highlight flag, otherwise gtk will have a different
1943            behaviour */
1944         gtk_drag_dest_set (GTK_WIDGET (self),
1945                            GTK_DEST_DEFAULT_HIGHLIGHT,
1946                            folder_view_drag_types,
1947                            G_N_ELEMENTS (folder_view_drag_types),
1948                            GDK_ACTION_MOVE | GDK_ACTION_COPY);
1949
1950         g_signal_connect (G_OBJECT (self),
1951                           "drag_data_received",
1952                           G_CALLBACK (on_drag_data_received),
1953                           NULL);
1954
1955
1956         /* Set up the treeview as a dnd source */
1957         gtk_drag_source_set (GTK_WIDGET (self),
1958                              GDK_BUTTON1_MASK,
1959                              folder_view_drag_types,
1960                              G_N_ELEMENTS (folder_view_drag_types),
1961                              GDK_ACTION_MOVE | GDK_ACTION_COPY);
1962
1963         g_signal_connect (G_OBJECT (self),
1964                           "drag_motion",
1965                           G_CALLBACK (on_drag_motion),
1966                           NULL);
1967         
1968         g_signal_connect (G_OBJECT (self),
1969                           "drag_data_get",
1970                           G_CALLBACK (on_drag_data_get),
1971                           NULL);
1972
1973         g_signal_connect (G_OBJECT (self),
1974                           "drag_drop",
1975                           G_CALLBACK (drag_drop_cb),
1976                           NULL);
1977 }
1978
1979 /*
1980  * This function manages the navigation through the folders using the
1981  * keyboard or the hardware keys in the device
1982  */
1983 static gboolean
1984 on_key_pressed (GtkWidget *self,
1985                 GdkEventKey *event,
1986                 gpointer user_data)
1987 {
1988         GtkTreeSelection *selection;
1989         GtkTreeIter iter;
1990         GtkTreeModel *model;
1991         gboolean retval = FALSE;
1992
1993         /* Up and Down are automatically managed by the treeview */
1994         if (event->keyval == GDK_Return) {
1995                 /* Expand/Collapse the selected row */
1996                 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1997                 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
1998                         GtkTreePath *path;
1999
2000                         path = gtk_tree_model_get_path (model, &iter);
2001
2002                         if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (self), path))
2003                                 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
2004                         else
2005                                 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
2006                         gtk_tree_path_free (path);
2007                 }
2008                 /* No further processing */
2009                 retval = TRUE;
2010         }
2011
2012         return retval;
2013 }
2014
2015 /*
2016  * We listen to the changes in the local folder account name key,
2017  * because we want to show the right name in the view. The local
2018  * folder account name corresponds to the device name in the Maemo
2019  * version. We do this because we do not want to query gconf on each
2020  * tree view refresh. It's better to cache it and change whenever
2021  * necessary.
2022  */
2023 static void 
2024 on_configuration_key_changed (ModestConf* conf, 
2025                               const gchar *key, 
2026                               ModestConfEvent event,
2027                               ModestConfNotificationId id, 
2028                               ModestFolderView *self)
2029 {
2030         ModestFolderViewPrivate *priv;
2031
2032
2033         g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
2034         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2035
2036         /* Do not listen for changes in other namespaces */
2037         if (priv->notification_id != id)
2038                  return;
2039          
2040         if (!strcmp (key, MODEST_CONF_DEVICE_NAME)) {
2041                 g_free (priv->local_account_name);
2042
2043                 if (event == MODEST_CONF_EVENT_KEY_UNSET)
2044                         priv->local_account_name = g_strdup (MODEST_LOCAL_FOLDERS_DEFAULT_DISPLAY_NAME);
2045                 else
2046                         priv->local_account_name = modest_conf_get_string (modest_runtime_get_conf(),
2047                                                                            MODEST_CONF_DEVICE_NAME, NULL);
2048
2049                 /* Force a redraw */
2050 #if GTK_CHECK_VERSION(2, 8, 0) /* gtk_tree_view_column_queue_resize is only available in GTK+ 2.8 */
2051                 GtkTreeViewColumn * tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self), 
2052                                                                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN);
2053                 gtk_tree_view_column_queue_resize (tree_column);
2054 #endif
2055         }
2056 }
2057
2058 void
2059 modest_folder_view_set_style (ModestFolderView *self,
2060                               ModestFolderViewStyle style)
2061 {
2062         ModestFolderViewPrivate *priv;
2063
2064         g_return_if_fail (self);
2065         
2066         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2067
2068         priv->style = style;
2069 }
2070
2071 void
2072 modest_folder_view_set_account_id_of_visible_server_account (ModestFolderView *self,
2073                                                              const gchar *account_id)
2074 {
2075         ModestFolderViewPrivate *priv;
2076         GtkTreeModel *model;
2077
2078         g_return_if_fail (self);
2079         
2080         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2081
2082         /* This will be used by the filter_row callback,
2083          * to decided which rows to show: */
2084         if (priv->visible_account_id) {
2085                 g_free (priv->visible_account_id);
2086                 priv->visible_account_id = NULL;
2087         }
2088         if (account_id)
2089                 priv->visible_account_id = g_strdup (account_id);
2090
2091         /* Refilter */
2092         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2093         if (GTK_IS_TREE_MODEL_FILTER (model))
2094                 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2095
2096         /* Save settings to gconf */
2097         modest_widget_memory_save (modest_runtime_get_conf (), G_OBJECT(self),
2098                                    MODEST_CONF_FOLDER_VIEW_KEY);
2099 }
2100
2101 const gchar *
2102 modest_folder_view_get_account_id_of_visible_server_account (ModestFolderView *self)
2103 {
2104         ModestFolderViewPrivate *priv;
2105
2106         g_return_val_if_fail (self, NULL);
2107         
2108         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2109
2110         return (const gchar *) priv->visible_account_id;
2111 }
2112
2113 static gboolean
2114 find_inbox_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *inbox_iter)
2115 {
2116         do {
2117                 GtkTreeIter child;
2118                 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2119
2120                 gtk_tree_model_get (model, iter, 
2121                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, 
2122                                     &type, -1);
2123                         
2124                 gboolean result = FALSE;
2125                 if (type == TNY_FOLDER_TYPE_INBOX) {
2126                         result = TRUE;
2127                 }               
2128                 if (result) {
2129                         *inbox_iter = *iter;
2130                         return TRUE;
2131                 }
2132
2133                 if (gtk_tree_model_iter_children (model, &child, iter)) {
2134                         if (find_inbox_iter (model, &child, inbox_iter))
2135                                 return TRUE;
2136                 }
2137
2138         } while (gtk_tree_model_iter_next (model, iter));
2139
2140         return FALSE;
2141 }
2142
2143
2144
2145
2146 void 
2147 modest_folder_view_select_first_inbox_or_local (ModestFolderView *self)
2148 {
2149         GtkTreeModel *model;
2150         GtkTreeIter iter, inbox_iter;
2151         GtkTreeSelection *sel;
2152         GtkTreePath *path = NULL;
2153
2154         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2155         if (!model)
2156                 return;
2157
2158         expand_root_items (self);
2159         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2160
2161         gtk_tree_model_get_iter_first (model, &iter);
2162
2163         if (find_inbox_iter (model, &iter, &inbox_iter))
2164                 path = gtk_tree_model_get_path (model, &inbox_iter);
2165         else
2166                 path = gtk_tree_path_new_first ();
2167
2168         /* Select the row and free */
2169         gtk_tree_view_set_cursor (GTK_TREE_VIEW (self), path, NULL, FALSE);
2170         gtk_tree_path_free (path);
2171
2172         /* set focus */
2173         gtk_widget_grab_focus (GTK_WIDGET(self));
2174 }
2175
2176
2177 /* recursive */
2178 static gboolean
2179 find_folder_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *folder_iter, 
2180                   TnyFolder* folder)
2181 {
2182         do {
2183                 GtkTreeIter child;
2184                 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2185                 TnyFolder* a_folder;
2186                 gchar *name = NULL;
2187                 
2188                 gtk_tree_model_get (model, iter, 
2189                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &a_folder,
2190                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name,
2191                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type, 
2192                                     -1);                
2193         
2194                 g_debug ("===> %s (%p ---- %p)", name, a_folder, folder);
2195                 g_free (name);
2196
2197                 if (folder == a_folder) {
2198                         g_object_unref (a_folder);
2199                         *folder_iter = *iter;
2200                         return TRUE;
2201                 }
2202                 g_object_unref (a_folder);
2203                 
2204                 if (gtk_tree_model_iter_children (model, &child, iter)) {
2205                         if (find_folder_iter (model, &child, folder_iter, folder)) 
2206                                 return TRUE;
2207                 }
2208
2209         } while (gtk_tree_model_iter_next (model, iter));
2210
2211         return FALSE;
2212 }
2213
2214
2215 static void
2216 on_row_changed_maybe_select_folder (GtkTreeModel *tree_model, GtkTreePath  *path, GtkTreeIter *iter,
2217                                     ModestFolderView *self)
2218 {
2219         ModestFolderViewPrivate *priv = NULL;
2220         GtkTreeSelection *sel;
2221
2222         if (!MODEST_IS_FOLDER_VIEW(self))
2223                 return;
2224         
2225         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2226         
2227         if (priv->folder_to_select) {
2228                 
2229                 if (!modest_folder_view_select_folder (self, priv->folder_to_select,
2230                                                        FALSE)) {
2231                         GtkTreePath *path;
2232                         path = gtk_tree_model_get_path (tree_model, iter);
2233                         gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
2234                         
2235                         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2236
2237                         gtk_tree_selection_select_iter (sel, iter);
2238                         gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
2239
2240                         gtk_tree_path_free (path);
2241                 
2242                 }
2243                 g_object_unref (priv->folder_to_select);
2244                 priv->folder_to_select = NULL;
2245         }
2246 }
2247
2248
2249 gboolean
2250 modest_folder_view_select_folder (ModestFolderView *self, TnyFolder *folder, 
2251                                   gboolean after_change)
2252 {
2253         GtkTreeModel *model;
2254         GtkTreeIter iter, folder_iter;
2255         GtkTreeSelection *sel;
2256         ModestFolderViewPrivate *priv = NULL;
2257         
2258         g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (self), FALSE);     
2259         g_return_val_if_fail (TNY_IS_FOLDER (folder), FALSE);   
2260                 
2261         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2262
2263         if (after_change) {
2264
2265                 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2266                 gtk_tree_selection_unselect_all (sel);
2267
2268                 if (priv->folder_to_select)
2269                         g_object_unref(priv->folder_to_select);
2270                 priv->folder_to_select = TNY_FOLDER(g_object_ref(folder));
2271                 return TRUE;
2272         }
2273                 
2274         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2275         if (!model)
2276                 return FALSE;
2277
2278                 
2279         gtk_tree_model_get_iter_first (model, &iter);
2280         if (find_folder_iter (model, &iter, &folder_iter, folder)) {
2281                 GtkTreePath *path;
2282
2283                 path = gtk_tree_model_get_path (model, &folder_iter);
2284                 gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
2285
2286                 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2287                 gtk_tree_selection_select_iter (sel, &folder_iter);
2288                 gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
2289
2290                 gtk_tree_path_free (path);
2291                 return TRUE;
2292         }
2293         return FALSE;
2294 }
2295
2296
2297 void 
2298 modest_folder_view_copy_selection (ModestFolderView *folder_view)
2299 {
2300         /* Copy selection */
2301         _clipboard_set_selected_data (folder_view, FALSE);
2302 }
2303
2304 void 
2305 modest_folder_view_cut_selection (ModestFolderView *folder_view)
2306 {
2307         ModestFolderViewPrivate *priv = NULL;
2308         GtkTreeModel *model = NULL;
2309         const gchar **hidding = NULL;
2310         guint i, n_selected;
2311
2312         g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view));
2313         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
2314
2315         /* Copy selection */
2316         if (!_clipboard_set_selected_data (folder_view, TRUE))
2317                 return;
2318
2319         /* Get hidding ids */
2320         hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected); 
2321         
2322         /* Clear hidding array created by previous cut operation */
2323         _clear_hidding_filter (MODEST_FOLDER_VIEW (folder_view));
2324
2325         /* Copy hidding array */
2326         priv->n_selected = n_selected;
2327         priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
2328         for (i=0; i < n_selected; i++) 
2329                 priv->hidding_ids[i] = g_strdup(hidding[i]);            
2330
2331         /* Hide cut folders */
2332         model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
2333         gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2334 }
2335
2336 void
2337 modest_folder_view_show_non_move_folders (ModestFolderView *folder_view,
2338                                     gboolean show)
2339 {
2340         ModestFolderViewPrivate* priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
2341         priv->show_non_move = show;
2342         modest_folder_view_update_model(folder_view,
2343                                                                                                                                         TNY_ACCOUNT_STORE(modest_runtime_get_account_store()));
2344 }
2345
2346 /* Returns FALSE if it did not selected anything */
2347 static gboolean
2348 _clipboard_set_selected_data (ModestFolderView *folder_view,
2349                               gboolean delete)
2350 {
2351         ModestFolderViewPrivate *priv = NULL;
2352         TnyFolderStore *folder = NULL;
2353         gboolean retval = FALSE;
2354
2355         g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (folder_view), FALSE);
2356         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
2357                 
2358         /* Set selected data on clipboard   */
2359         g_return_val_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard), FALSE);
2360         folder = modest_folder_view_get_selected (folder_view);
2361
2362         /* Do not allow to select an account */
2363         if (TNY_IS_FOLDER (folder)) {
2364                 modest_email_clipboard_set_data (priv->clipboard, TNY_FOLDER(folder), NULL, delete);
2365                 retval = TRUE;
2366         }
2367
2368         /* Free */
2369         g_object_unref (folder);
2370
2371         return retval;
2372 }
2373
2374 static void
2375 _clear_hidding_filter (ModestFolderView *folder_view) 
2376 {
2377         ModestFolderViewPrivate *priv;
2378         guint i;
2379         
2380         g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view)); 
2381         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
2382
2383         if (priv->hidding_ids != NULL) {
2384                 for (i=0; i < priv->n_selected; i++) 
2385                         g_free (priv->hidding_ids[i]);
2386                 g_free(priv->hidding_ids);
2387         }       
2388 }
2389
2390