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