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