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