a5e497829b747131073d4779d957672182d5742c
[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-folder.h>
45 #include <modest-tny-local-folders-account.h>
46 #include <modest-marshal.h>
47 #include <modest-icon-names.h>
48 #include <modest-tny-account-store.h>
49 #include <modest-text-utils.h>
50 #include <modest-runtime.h>
51 #include "modest-folder-view.h"
52 #include <modest-dnd.h>
53 #include <modest-platform.h>
54 #include <modest-account-mgr-helpers.h>
55 #include <modest-widget-memory.h>
56 #include <modest-ui-actions.h>
57
58 /* 'private'/'protected' functions */
59 static void modest_folder_view_class_init  (ModestFolderViewClass *klass);
60 static void modest_folder_view_init        (ModestFolderView *obj);
61 static void modest_folder_view_finalize    (GObject *obj);
62
63 static void         tny_account_store_view_init (gpointer g, 
64                                                  gpointer iface_data);
65
66 static void         modest_folder_view_set_account_store (TnyAccountStoreView *self, 
67                                                           TnyAccountStore     *account_store);
68
69 static void         on_selection_changed   (GtkTreeSelection *sel, gpointer data);
70
71 static void         on_account_update      (TnyAccountStore *account_store, 
72                                             const gchar *account,
73                                             gpointer user_data);
74
75 static void         on_accounts_reloaded   (TnyAccountStore *store, 
76                                             gpointer user_data);
77
78 static gint         cmp_rows               (GtkTreeModel *tree_model, 
79                                             GtkTreeIter *iter1, 
80                                             GtkTreeIter *iter2,
81                                             gpointer user_data);
82
83 static gboolean     filter_row             (GtkTreeModel *model,
84                                             GtkTreeIter *iter,
85                                             gpointer data);
86
87 static gboolean     on_key_pressed         (GtkWidget *self,
88                                             GdkEventKey *event,
89                                             gpointer user_data);
90
91 static void         on_configuration_key_changed         (ModestConf* conf, 
92                                                           const gchar *key, 
93                                                           ModestConfEvent event, 
94                                                           ModestFolderView *self);
95
96 /* DnD functions */
97 static void         on_drag_data_get       (GtkWidget *widget, 
98                                             GdkDragContext *context, 
99                                             GtkSelectionData *selection_data, 
100                                             guint info, 
101                                             guint time, 
102                                             gpointer data);
103
104 static void         on_drag_data_received  (GtkWidget *widget, 
105                                             GdkDragContext *context, 
106                                             gint x, 
107                                             gint y, 
108                                             GtkSelectionData *selection_data, 
109                                             guint info, 
110                                             guint time, 
111                                             gpointer data);
112
113 static gboolean     on_drag_motion         (GtkWidget      *widget,
114                                             GdkDragContext *context,
115                                             gint            x,
116                                             gint            y,
117                                             guint           time,
118                                             gpointer        user_data);
119
120 static gint         expand_row_timeout     (gpointer data);
121
122 static void         setup_drag_and_drop    (GtkTreeView *self);
123
124 enum {
125         FOLDER_SELECTION_CHANGED_SIGNAL,
126         FOLDER_DISPLAY_NAME_CHANGED_SIGNAL,
127         LAST_SIGNAL
128 };
129
130 typedef struct _ModestFolderViewPrivate ModestFolderViewPrivate;
131 struct _ModestFolderViewPrivate {
132         TnyAccountStore      *account_store;
133         TnyFolderStore       *cur_folder_store;
134
135         gulong                account_update_signal;
136         gulong                changed_signal;
137         gulong                accounts_reloaded_signal;
138         
139         TnyFolderStoreQuery  *query;
140         guint                 timer_expander;
141
142         gchar                *local_account_name;
143         gchar                *visible_account_id;
144         ModestFolderViewStyle style;
145 };
146 #define MODEST_FOLDER_VIEW_GET_PRIVATE(o)                       \
147         (G_TYPE_INSTANCE_GET_PRIVATE((o),                       \
148                                      MODEST_TYPE_FOLDER_VIEW,   \
149                                      ModestFolderViewPrivate))
150 /* globals */
151 static GObjectClass *parent_class = NULL;
152
153 static guint signals[LAST_SIGNAL] = {0}; 
154
155 GType
156 modest_folder_view_get_type (void)
157 {
158         static GType my_type = 0;
159         if (!my_type) {
160                 static const GTypeInfo my_info = {
161                         sizeof(ModestFolderViewClass),
162                         NULL,           /* base init */
163                         NULL,           /* base finalize */
164                         (GClassInitFunc) modest_folder_view_class_init,
165                         NULL,           /* class finalize */
166                         NULL,           /* class data */
167                         sizeof(ModestFolderView),
168                         1,              /* n_preallocs */
169                         (GInstanceInitFunc) modest_folder_view_init,
170                         NULL
171                 };
172
173                 static const GInterfaceInfo tny_account_store_view_info = {
174                         (GInterfaceInitFunc) tny_account_store_view_init, /* interface_init */
175                         NULL,         /* interface_finalize */
176                         NULL          /* interface_data */
177                 };
178
179                                 
180                 my_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
181                                                   "ModestFolderView",
182                                                   &my_info, 0);
183
184                 g_type_add_interface_static (my_type, 
185                                              TNY_TYPE_ACCOUNT_STORE_VIEW, 
186                                              &tny_account_store_view_info);
187         }
188         return my_type;
189 }
190
191 static void
192 modest_folder_view_class_init (ModestFolderViewClass *klass)
193 {
194         GObjectClass *gobject_class;
195         gobject_class = (GObjectClass*) klass;
196
197         parent_class            = g_type_class_peek_parent (klass);
198         gobject_class->finalize = modest_folder_view_finalize;
199
200         g_type_class_add_private (gobject_class,
201                                   sizeof(ModestFolderViewPrivate));
202         
203         signals[FOLDER_SELECTION_CHANGED_SIGNAL] = 
204                 g_signal_new ("folder_selection_changed",
205                               G_TYPE_FROM_CLASS (gobject_class),
206                               G_SIGNAL_RUN_FIRST,
207                               G_STRUCT_OFFSET (ModestFolderViewClass,
208                                                folder_selection_changed),
209                               NULL, NULL,
210                               modest_marshal_VOID__POINTER_BOOLEAN,
211                               G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_BOOLEAN);
212
213         /*
214          * This signal is emitted whenever the currently selected
215          * folder display name is computed. Note that the name could
216          * be different to the folder name, because we could append
217          * the unread messages count to the folder name to build the
218          * folder display name
219          */
220         signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL] = 
221                 g_signal_new ("folder-display-name-changed",
222                               G_TYPE_FROM_CLASS (gobject_class),
223                               G_SIGNAL_RUN_FIRST,
224                               G_STRUCT_OFFSET (ModestFolderViewClass,
225                                                folder_display_name_changed),
226                               NULL, NULL,
227                               g_cclosure_marshal_VOID__STRING,
228                               G_TYPE_NONE, 1, G_TYPE_STRING);
229 }
230
231 static void
232 text_cell_data  (GtkTreeViewColumn *column,  GtkCellRenderer *renderer,
233                  GtkTreeModel *tree_model,  GtkTreeIter *iter,  gpointer data)
234 {
235         ModestFolderViewPrivate *priv;
236         GObject *rendobj;
237         gchar *fname = NULL;
238         gint unread = 0;
239         gint all = 0;
240         TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
241         GObject *instance = NULL;
242         
243         g_return_if_fail (column);
244         g_return_if_fail (tree_model);
245
246         gtk_tree_model_get (tree_model, iter,
247                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &fname,
248                             TNY_GTK_FOLDER_STORE_TREE_MODEL_ALL_COLUMN, &all,
249                             TNY_GTK_FOLDER_STORE_TREE_MODEL_UNREAD_COLUMN, &unread,
250                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
251                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
252                             -1);
253         rendobj = G_OBJECT(renderer);
254  
255         if (!fname)
256                 return;
257
258         if (!instance) {
259                 g_free (fname);
260                 return;
261         }
262
263         
264         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE (data);
265         
266         gchar *item_name = NULL;
267         gint item_weight = 400;
268         
269         if (type != TNY_FOLDER_TYPE_ROOT) {
270                 gint number = 0;
271                 
272                 if (modest_tny_folder_is_local_folder (TNY_FOLDER (instance))) {
273                         TnyFolderType folder_type
274                                 = modest_tny_folder_get_local_folder_type (TNY_FOLDER (instance));
275                         if (folder_type != TNY_FOLDER_TYPE_UNKNOWN) {
276                                 g_free (fname);
277                                 fname = g_strdup(modest_local_folder_info_get_type_display_name (folder_type));
278                         }
279                 }
280
281                 /* Select the number to show */
282                 if ((type == TNY_FOLDER_TYPE_DRAFTS) || (type == TNY_FOLDER_TYPE_OUTBOX))
283                         number = all;
284                 else
285                         number = unread;
286
287                 /* Use bold font style if there are unread messages */
288                 if (unread > 0) {
289                         item_name = g_strdup_printf ("%s (%d)", fname, unread);
290                         item_weight = 800;
291                 } else {
292                         item_name = g_strdup (fname);
293                         item_weight = 400;
294                 }
295
296         } else if (TNY_IS_ACCOUNT (instance)) {
297                 /* If it's a server account */
298                 if (modest_tny_account_is_virtual_local_folders (
299                                 TNY_ACCOUNT (instance))) {
300                         item_name = g_strdup (priv->local_account_name);
301                         item_weight = 400;
302                 } else {
303                         item_name = g_strdup (fname);
304                         item_weight = 800;
305                 }
306         }
307         
308         if (!item_name)
309                 item_name = g_strdup ("unknown");
310                         
311         if (item_name && item_weight) {
312                 /* Set the name in the treeview cell: */
313                 g_object_set (rendobj,"text", item_name, "weight", item_weight, NULL);
314                 
315                 /* Notify display name observers */
316                 if (G_OBJECT (priv->cur_folder_store) == instance) {
317                         g_signal_emit (G_OBJECT(data),
318                                                signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
319                                                item_name);
320                 }
321                 
322                 g_free (item_name);
323                 
324         }
325         
326         g_object_unref (G_OBJECT (instance));
327         g_free (fname);
328 }
329
330
331
332 static void
333 icon_cell_data  (GtkTreeViewColumn *column,  GtkCellRenderer *renderer,
334                  GtkTreeModel *tree_model,  GtkTreeIter *iter, gpointer data)
335 {
336         GObject *rendobj = NULL, *instance = NULL;
337         GdkPixbuf *pixbuf = NULL;
338         TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
339         gchar *fname = NULL;
340         const gchar *account_id = NULL;
341         gint unread = 0;
342         
343         rendobj = G_OBJECT(renderer);
344         gtk_tree_model_get (tree_model, iter,
345                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
346                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &fname,
347                             TNY_GTK_FOLDER_STORE_TREE_MODEL_UNREAD_COLUMN, &unread,
348                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
349                             -1);
350
351         if (!fname)
352                 return;
353
354         if (!instance) {
355                 g_free (fname);
356                 return;
357         }
358         
359         if (type == TNY_FOLDER_TYPE_NORMAL || type == TNY_FOLDER_TYPE_UNKNOWN) {
360                 type = modest_tny_folder_guess_folder_type_from_name (fname);
361         }
362
363         switch (type) {
364         case TNY_FOLDER_TYPE_ROOT:
365                 if (TNY_IS_ACCOUNT (instance)) {
366                         
367                         if (modest_tny_account_is_virtual_local_folders (
368                                 TNY_ACCOUNT (instance))) {
369                                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_LOCAL_FOLDERS);
370                         }
371                         else {
372                                 account_id = tny_account_get_id (TNY_ACCOUNT (instance));
373                                 
374                                 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
375                                         pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_MMC);
376                                 else
377                                         pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_ACCOUNT);
378                         }
379                 }
380                 break;
381         case TNY_FOLDER_TYPE_INBOX:
382             pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_INBOX);
383             break;
384         case TNY_FOLDER_TYPE_OUTBOX:
385                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_OUTBOX);
386                 break;
387         case TNY_FOLDER_TYPE_JUNK:
388                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_JUNK);
389                 break;
390         case TNY_FOLDER_TYPE_SENT:
391                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_SENT);
392                 break;
393         case TNY_FOLDER_TYPE_TRASH:
394                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_TRASH);
395                 break;
396         case TNY_FOLDER_TYPE_DRAFTS:
397                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_DRAFTS);
398                 break;
399         case TNY_FOLDER_TYPE_NORMAL:
400         default:
401                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_NORMAL);
402                 break;
403         }
404         
405         g_object_unref (G_OBJECT (instance));
406         g_free (fname);
407
408         /* Set pixbuf */
409         g_object_set (rendobj, "pixbuf", pixbuf, NULL);
410
411         if (pixbuf != NULL)
412                 g_object_unref (pixbuf);
413 }
414
415 static void
416 add_columns (GtkWidget *treeview)
417 {
418         GtkTreeViewColumn *column;
419         GtkCellRenderer *renderer;
420         GtkTreeSelection *sel;
421
422         /* Create column */
423         column = gtk_tree_view_column_new ();   
424         
425         /* Set icon and text render function */
426         renderer = gtk_cell_renderer_pixbuf_new();
427         gtk_tree_view_column_pack_start (column, renderer, FALSE);
428         gtk_tree_view_column_set_cell_data_func(column, renderer,
429                                                 icon_cell_data, treeview, NULL);
430         
431         renderer = gtk_cell_renderer_text_new();
432         gtk_tree_view_column_pack_start (column, renderer, FALSE);
433         gtk_tree_view_column_set_cell_data_func(column, renderer,
434                                                 text_cell_data, treeview, NULL);
435         
436         /* Set selection mode */
437         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(treeview));
438         gtk_tree_selection_set_mode (sel, GTK_SELECTION_SINGLE);
439
440         /* Set treeview appearance */
441         gtk_tree_view_column_set_spacing (column, 2);
442         gtk_tree_view_column_set_resizable (column, TRUE);
443         gtk_tree_view_column_set_fixed_width (column, TRUE);            
444         gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(treeview), FALSE);
445         gtk_tree_view_set_enable_search (GTK_TREE_VIEW(treeview), FALSE);
446
447         /* Add column */
448         gtk_tree_view_append_column (GTK_TREE_VIEW(treeview),column);
449 }
450
451 static void
452 modest_folder_view_init (ModestFolderView *obj)
453 {
454         ModestFolderViewPrivate *priv;
455         ModestConf *conf;
456         
457         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
458         
459         priv->timer_expander = 0;
460         priv->account_store  = NULL;
461         priv->query          = NULL;
462         priv->style          = MODEST_FOLDER_VIEW_STYLE_SHOW_ALL;
463         priv->cur_folder_store   = NULL;
464         priv->visible_account_id = NULL;
465
466         /* Initialize the local account name */
467         conf = modest_runtime_get_conf();
468         priv->local_account_name = modest_conf_get_string (conf, MODEST_CONF_DEVICE_NAME, NULL);
469
470         /* Build treeview */
471         add_columns (GTK_WIDGET (obj));
472
473         /* Setup drag and drop */
474         setup_drag_and_drop (GTK_TREE_VIEW(obj));
475
476         /* Connect signals */
477         g_signal_connect (G_OBJECT (obj), 
478                           "key-press-event", 
479                           G_CALLBACK (on_key_pressed), NULL);
480
481         /*
482          * Track changes in the local account name (in the device it
483          * will be the device name)
484          */
485         g_signal_connect (G_OBJECT(conf), 
486                           "key_changed",
487                           G_CALLBACK(on_configuration_key_changed), obj);
488
489 }
490
491 static void
492 tny_account_store_view_init (gpointer g, gpointer iface_data)
493 {
494         TnyAccountStoreViewIface *klass = (TnyAccountStoreViewIface *)g;
495
496         klass->set_account_store_func = modest_folder_view_set_account_store;
497
498         return;
499 }
500
501 static void
502 modest_folder_view_finalize (GObject *obj)
503 {
504         ModestFolderViewPrivate *priv;
505         GtkTreeSelection    *sel;
506         
507         g_return_if_fail (obj);
508         
509         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
510
511         if (priv->timer_expander != 0) {
512                 g_source_remove (priv->timer_expander);
513                 priv->timer_expander = 0;
514         }
515
516         if (priv->account_store) {
517                 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
518                                              priv->account_update_signal);
519                 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
520                                              priv->accounts_reloaded_signal);
521                 g_object_unref (G_OBJECT(priv->account_store));
522                 priv->account_store = NULL;
523         }
524
525         if (priv->query) {
526                 g_object_unref (G_OBJECT (priv->query));
527                 priv->query = NULL;
528         }
529
530         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(obj));
531         if (sel)
532                 g_signal_handler_disconnect (G_OBJECT(sel), priv->changed_signal);
533
534         g_free (priv->local_account_name);
535         g_free (priv->visible_account_id);
536         
537         G_OBJECT_CLASS(parent_class)->finalize (obj);
538 }
539
540
541 static void
542 modest_folder_view_set_account_store (TnyAccountStoreView *self, TnyAccountStore *account_store)
543 {
544         ModestFolderViewPrivate *priv;
545         TnyDevice *device;
546
547         g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
548         g_return_if_fail (TNY_IS_ACCOUNT_STORE (account_store));
549
550         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
551         device = tny_account_store_get_device (account_store);
552
553         if (G_UNLIKELY (priv->account_store)) {
554
555                 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store), 
556                                                    priv->account_update_signal))
557                         g_signal_handler_disconnect (G_OBJECT (priv->account_store), 
558                                                      priv->account_update_signal);
559                 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store), 
560                                                    priv->accounts_reloaded_signal))
561                         g_signal_handler_disconnect (G_OBJECT (priv->account_store), 
562                                                      priv->accounts_reloaded_signal);
563
564                 g_object_unref (G_OBJECT (priv->account_store));
565         }
566
567         priv->account_store = g_object_ref (G_OBJECT (account_store));
568
569         priv->account_update_signal = 
570                 g_signal_connect (G_OBJECT(account_store), "account_update",
571                                   G_CALLBACK (on_account_update), self);
572
573         priv->accounts_reloaded_signal = 
574                 g_signal_connect (G_OBJECT(account_store), "accounts_reloaded",
575                                   G_CALLBACK (on_accounts_reloaded), self);
576
577         on_accounts_reloaded (account_store, (gpointer ) self);
578         
579         g_object_unref (G_OBJECT (device));
580 }
581
582 static void
583 on_account_update (TnyAccountStore *account_store, const gchar *account,
584                    gpointer user_data)
585 {
586         if (!modest_folder_view_update_model (MODEST_FOLDER_VIEW(user_data), 
587                                               account_store))
588                 g_printerr ("modest: failed to update model for changes in '%s'",
589                             account);
590 }
591
592 static void 
593 on_accounts_reloaded   (TnyAccountStore *account_store, 
594                         gpointer user_data)
595 {
596         ModestConf *conf = modest_runtime_get_conf ();
597
598         modest_widget_memory_save (conf, G_OBJECT (user_data), MODEST_CONF_FOLDER_VIEW_KEY);
599         modest_folder_view_update_model (MODEST_FOLDER_VIEW (user_data), account_store);
600         modest_widget_memory_restore (conf, G_OBJECT (user_data), MODEST_CONF_FOLDER_VIEW_KEY);
601 }
602
603 void
604 modest_folder_view_set_title (ModestFolderView *self, const gchar *title)
605 {
606         GtkTreeViewColumn *col;
607         
608         g_return_if_fail (self);
609
610         col = gtk_tree_view_get_column (GTK_TREE_VIEW(self), 0);
611         if (!col) {
612                 g_printerr ("modest: failed get column for title\n");
613                 return;
614         }
615
616         gtk_tree_view_column_set_title (col, title);
617         gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self),
618                                            title != NULL);
619 }
620
621 GtkWidget*
622 modest_folder_view_new (TnyFolderStoreQuery *query)
623 {
624         GObject *self;
625         ModestFolderViewPrivate *priv;
626         GtkTreeSelection *sel;
627         
628         self = G_OBJECT (g_object_new (MODEST_TYPE_FOLDER_VIEW, NULL));
629         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
630
631         if (query)
632                 priv->query = g_object_ref (query);
633         
634         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
635         priv->changed_signal = g_signal_connect (sel, "changed",
636                                                  G_CALLBACK (on_selection_changed), self);
637
638         return GTK_WIDGET(self);
639 }
640
641 /* this feels dirty; any other way to expand all the root items? */
642 static void
643 expand_root_items (ModestFolderView *self)
644 {
645         GtkTreePath *path;
646         path = gtk_tree_path_new_first ();
647
648         /* all folders should have child items, so.. */
649         while (gtk_tree_view_expand_row (GTK_TREE_VIEW(self), path, FALSE))
650                 gtk_tree_path_next (path);
651         
652         gtk_tree_path_free (path);
653 }
654
655 /*
656  * We use this function to implement the
657  * MODEST_FOLDER_VIEW_STYLE_SHOW_ONE style. We only show the default
658  * account in this case, and the local folders.
659  */
660 static gboolean 
661 filter_row (GtkTreeModel *model,
662             GtkTreeIter *iter,
663             gpointer data)
664 {
665         gboolean retval = TRUE;
666         TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
667         GObject *instance = NULL;
668
669         gtk_tree_model_get (model, iter,
670                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
671                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
672                             -1);
673
674         /* Do not show if there is no instance, this could indeed
675            happen when the model is being modified while it's being
676            drawn. This could occur for example when moving folders
677            using drag&drop */
678         if (!instance)
679                 return FALSE;
680
681         if (type == TNY_FOLDER_TYPE_ROOT) {
682                 /* TNY_FOLDER_TYPE_ROOT means that the instance is an account instead of a folder. */
683                 if (TNY_IS_ACCOUNT (instance)) {
684                         TnyAccount *acc = TNY_ACCOUNT (instance);
685                         const gchar *account_id = tny_account_get_id (acc);
686                         
687                         /* If it isn't a special folder, 
688                          * don't show it unless it is the visible account: */
689                         if (!modest_tny_account_is_virtual_local_folders (acc) &&
690                                 strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) { 
691                                 /* Show only the visible account id */
692                                 ModestFolderViewPrivate *priv = 
693                                         MODEST_FOLDER_VIEW_GET_PRIVATE (data);
694                                 if (priv->visible_account_id && strcmp (account_id, priv->visible_account_id))
695                                         retval = FALSE;
696                         }
697                 }
698         }
699         
700         /* The virtual local-folders folder store is also shown by default. */
701
702         g_object_unref (instance);
703
704         return retval;
705 }
706
707 gboolean
708 modest_folder_view_update_model (ModestFolderView *self,
709                                  TnyAccountStore *account_store)
710 {
711         ModestFolderViewPrivate *priv;
712         GtkTreeModel *model /* , *old_model */;
713         /* TnyAccount *local_account; */
714         TnyList *model_as_list;
715
716         g_return_val_if_fail (account_store, FALSE);
717
718         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE(self);
719         
720         /* Notify that there is no folder selected */
721         g_signal_emit (G_OBJECT(self), 
722                        signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
723                        NULL, TRUE);
724
725         /* FIXME: the local accounts are not shown when the query
726            selects only the subscribed folders. */
727 /*      model        = tny_gtk_folder_store_tree_model_new (TRUE, priv->query); */
728         model        = tny_gtk_folder_store_tree_model_new (TRUE, NULL);
729         
730         /* Deal with the model via its TnyList Interface,
731          * filling the TnyList via a get_accounts() call: */
732         model_as_list = TNY_LIST(model);
733
734         /* Get the accounts: */
735         tny_account_store_get_accounts (TNY_ACCOUNT_STORE(account_store),
736                                         model_as_list,
737                                         TNY_ACCOUNT_STORE_STORE_ACCOUNTS);
738         g_object_unref (model_as_list);
739         model_as_list = NULL;   
740                                                      
741         GtkTreeModel *filter_model = NULL, *sortable = NULL;
742
743         sortable = gtk_tree_model_sort_new_with_model (model);
744         gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
745                                               TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, 
746                                               GTK_SORT_ASCENDING);
747         gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
748                                          TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
749                                          cmp_rows, NULL, NULL);
750
751         /* Create filter model */
752         if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE) {
753                 filter_model = gtk_tree_model_filter_new (sortable, NULL);
754                 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
755                                                         filter_row,
756                                                         self,
757                                                         NULL);
758         }
759
760         /* Set new model */
761         gtk_tree_view_set_model (GTK_TREE_VIEW(self), 
762                                  (filter_model) ? filter_model : sortable);
763         expand_root_items (self); /* expand all account folders */
764         
765         g_object_unref (model);
766         
767         if (filter_model)
768                 g_object_unref (filter_model);
769                         
770         g_object_unref (sortable);
771                         
772         return TRUE;
773 }
774
775
776 static void
777 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
778 {
779         GtkTreeModel            *model;
780         TnyFolderStore          *folder = NULL;
781         GtkTreeIter             iter;
782         ModestFolderView        *tree_view;
783         ModestFolderViewPrivate *priv;
784
785         g_return_if_fail (sel);
786         g_return_if_fail (user_data);
787         
788         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
789
790         /* folder was _un_selected if true */
791         if (!gtk_tree_selection_get_selected (sel, &model, &iter)) {
792                 if (priv->cur_folder_store)
793                         g_object_unref (priv->cur_folder_store);
794                 priv->cur_folder_store = NULL;
795
796                 /* Notify the display name observers */
797                 g_signal_emit (G_OBJECT(user_data),
798                                signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
799                                NULL);
800                 return;
801         }
802
803         tree_view = MODEST_FOLDER_VIEW (user_data);
804
805         gtk_tree_model_get (model, &iter,
806                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
807                             -1);
808
809         /* If the folder is the same do not notify */
810         if (priv->cur_folder_store == folder) {
811                 g_object_unref (folder);
812                 return;
813         }
814         
815         /* Current folder was unselected */
816         if (priv->cur_folder_store) {
817                 g_signal_emit (G_OBJECT(tree_view), signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
818                                priv->cur_folder_store, FALSE);
819                 g_object_unref (priv->cur_folder_store);
820         }
821
822         /* New current references */
823         priv->cur_folder_store = folder;
824
825         /* New folder has been selected */
826         g_signal_emit (G_OBJECT(tree_view),
827                        signals[FOLDER_SELECTION_CHANGED_SIGNAL],
828                        0, folder, TRUE);
829 }
830
831 TnyFolderStore *
832 modest_folder_view_get_selected (ModestFolderView *self)
833 {
834         ModestFolderViewPrivate *priv;
835
836         g_return_val_if_fail (self, NULL);
837         
838         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
839         if (priv->cur_folder_store)
840                 g_object_ref (priv->cur_folder_store);
841
842         return priv->cur_folder_store;
843 }
844
845 static gint
846 get_cmp_rows_type_pos (GObject *folder)
847 {
848         /* Remote accounts -> Local account -> MMC account .*/
849         /* 0, 1, 2 */
850         
851         if (TNY_IS_ACCOUNT (folder) && 
852                 modest_tny_account_is_virtual_local_folders (
853                         TNY_ACCOUNT (folder))) {
854                 return 1;
855         } else if (TNY_IS_ACCOUNT (folder)) {
856                 TnyAccount *account = TNY_ACCOUNT (folder);
857                 const gchar *account_id = tny_account_get_id (account);
858                 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
859                         return 2;
860                 else
861                         return 0;
862         }
863         else {
864                 printf ("DEBUG: %s: unexpected type.\n", __FUNCTION__);
865                 return -1; /* Should never happen */
866         }
867 }
868
869 /*
870  * This function orders the mail accounts according to these rules:
871  * 1st - remote accounts
872  * 2nd - local account
873  * 3rd - MMC account
874  */
875 static gint
876 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
877           gpointer user_data)
878 {
879         gint cmp;
880         gchar *name1 = NULL;
881         gchar *name2 = NULL;
882         TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
883         GObject *folder1 = NULL;
884         GObject *folder2 = NULL;
885
886         gtk_tree_model_get (tree_model, iter1,
887                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name1,
888                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
889                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder1,
890                             -1);
891         gtk_tree_model_get (tree_model, iter2,
892                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name2,
893                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder2,
894                             -1);
895
896         if (type == TNY_FOLDER_TYPE_ROOT) {
897                 /* Compare the types, so that 
898                  * Remote accounts -> Local account -> MMC account .*/
899                 const gint pos1 = get_cmp_rows_type_pos (folder1);
900                 const gint pos2 = get_cmp_rows_type_pos (folder2);
901                 /* printf ("DEBUG: %s:\n  type1=%s, pos1=%d\n  type2=%s, pos2=%d\n", 
902                         __FUNCTION__, G_OBJECT_TYPE_NAME(folder1), pos1, G_OBJECT_TYPE_NAME(folder2), pos2); */
903                 if (pos1 <  pos2)
904                         cmp = -1;
905                 else if (pos1 > pos2)
906                         cmp = 1;
907                 else {
908                         /* Compare items of the same type: */
909                         
910                         TnyAccount *account1 = NULL;
911                         if (TNY_IS_ACCOUNT (folder1))
912                                 account1 = TNY_ACCOUNT (folder1);
913                                 
914                         TnyAccount *account2 = NULL;
915                         if (TNY_IS_ACCOUNT (folder2))
916                                 account2 = TNY_ACCOUNT (folder2);
917                                 
918                         const gchar *account_id = account1 ? tny_account_get_id (account1) : NULL;
919                         const gchar *account_id2 = account2 ? tny_account_get_id (account2) : NULL;
920         
921                         if (!account_id && !account_id2)
922                                 return 0;
923                         else if (!account_id)
924                                 return -1;
925                         else if (!account_id2)
926                                 return +1;
927                         else if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
928                                 cmp = +1;
929                         else
930                                 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
931                 }
932         } else {
933                 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
934         }
935         
936         if (folder1)
937                 g_object_unref(G_OBJECT(folder1));
938         if (folder2)
939                 g_object_unref(G_OBJECT(folder2));
940
941         g_free (name1);
942         g_free (name2);
943
944         return cmp;     
945 }
946
947 /*****************************************************************************/
948 /*                        DRAG and DROP stuff                                */
949 /*****************************************************************************/
950
951 /*
952  * This function fills the #GtkSelectionData with the row and the
953  * model that has been dragged. It's called when this widget is a
954  * source for dnd after the event drop happened
955  */
956 static void
957 on_drag_data_get (GtkWidget *widget, 
958                   GdkDragContext *context, 
959                   GtkSelectionData *selection_data, 
960                   guint info, 
961                   guint time, 
962                   gpointer data)
963 {
964         GtkTreeSelection *selection;
965         GtkTreeModel *model;
966         GtkTreeIter iter;
967         GtkTreePath *source_row;
968
969         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
970         gtk_tree_selection_get_selected (selection, &model, &iter);
971         source_row = gtk_tree_model_get_path (model, &iter);
972
973         gtk_tree_set_row_drag_data (selection_data,
974                                     model,
975                                     source_row);
976
977         gtk_tree_path_free (source_row);
978 }
979
980 typedef struct _DndHelper {
981         gboolean delete_source;
982         GtkTreePath *source_row;
983         GdkDragContext *context;
984         guint time;
985 } DndHelper;
986
987
988 /*
989  * This function is the callback of the
990  * modest_mail_operation_xfer_msgs () and
991  * modest_mail_operation_xfer_folder() calls. We check here if the
992  * message/folder was correctly asynchronously transferred. The reason
993  * to use the same callback is that the code is the same, it only has
994  * to check that the operation went fine and then finalize the drag
995  * and drop action
996  */
997 static void
998 on_progress_changed (ModestMailOperation *mail_op, 
999                      ModestMailOperationState *state,
1000                      gpointer user_data)
1001 {
1002         gboolean success;
1003         DndHelper *helper;
1004
1005         helper = (DndHelper *) user_data;
1006
1007         if (!state->finished)
1008                 return;
1009
1010         if (state->status == MODEST_MAIL_OPERATION_STATUS_SUCCESS) {
1011                 success = TRUE;
1012         } else {
1013                 success = FALSE;
1014         }
1015
1016         /* Notify the drag source. Never call delete, the monitor will
1017            do the job if needed */
1018         gtk_drag_finish (helper->context, success, FALSE, helper->time);
1019
1020         /* Free the helper */
1021         gtk_tree_path_free (helper->source_row);        
1022         g_slice_free (DndHelper, helper);
1023 }
1024
1025 /*
1026  * This function is used by drag_data_received_cb to manage drag and
1027  * drop of a header, i.e, and drag from the header view to the folder
1028  * view.
1029  */
1030 static void
1031 drag_and_drop_from_header_view (GtkTreeModel *source_model,
1032                                 GtkTreeModel *dest_model,
1033                                 GtkTreePath  *dest_row,
1034                                 DndHelper    *helper)
1035 {
1036         TnyList *headers = NULL;
1037         TnyHeader *header = NULL;
1038         TnyFolder *folder = NULL;
1039         ModestMailOperation *mail_op = NULL;
1040         GtkTreeIter source_iter, dest_iter;
1041
1042         g_return_if_fail (GTK_IS_TREE_MODEL(source_model));
1043         g_return_if_fail (GTK_IS_TREE_MODEL(dest_model));
1044         g_return_if_fail (dest_row);
1045         g_return_if_fail (helper);
1046         
1047         /* Get header */
1048         gtk_tree_model_get_iter (source_model, &source_iter, helper->source_row);
1049         gtk_tree_model_get (source_model, &source_iter, 
1050                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, 
1051                             &header, -1);
1052         if (!TNY_IS_HEADER(header)) {
1053                 g_warning ("BUG: %s could not get a valid header", __FUNCTION__);
1054                 goto cleanup;
1055         }
1056         
1057         /* Get Folder */
1058         gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
1059         gtk_tree_model_get (dest_model, &dest_iter, 
1060                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, 
1061                             &folder, -1);
1062
1063         if (!TNY_IS_FOLDER(folder)) {
1064                 g_warning ("BUG: %s could not get a valid folder", __FUNCTION__);
1065                 goto cleanup;
1066         }
1067
1068         /* Transfer message */
1069         mail_op = modest_mail_operation_new (MODEST_MAIL_OPERATION_TYPE_RECEIVE, NULL);
1070         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1071                                          mail_op);
1072         g_signal_connect (G_OBJECT (mail_op), "progress-changed",
1073                           G_CALLBACK (on_progress_changed), helper);
1074
1075         headers = tny_simple_list_new ();
1076         tny_list_append (headers, G_OBJECT (header));
1077         modest_mail_operation_xfer_msgs (mail_op, 
1078                                          headers, 
1079                                          folder, 
1080                                          helper->delete_source, 
1081                                          NULL, NULL);
1082         
1083         /* Frees */
1084 cleanup:
1085         if (G_IS_OBJECT(mail_op))
1086                 g_object_unref (G_OBJECT (mail_op));
1087         if (G_IS_OBJECT(header))
1088                 g_object_unref (G_OBJECT (header));
1089         if (G_IS_OBJECT(folder))
1090                 g_object_unref (G_OBJECT (folder));
1091         if (G_IS_OBJECT(headers))
1092                 g_object_unref (headers);
1093 }
1094
1095 /*
1096  * This function is used by drag_data_received_cb to manage drag and
1097  * drop of a folder, i.e, and drag from the folder view to the same
1098  * folder view.
1099  */
1100 static void
1101 drag_and_drop_from_folder_view (GtkTreeModel     *source_model,
1102                                 GtkTreeModel     *dest_model,
1103                                 GtkTreePath      *dest_row,
1104                                 GtkSelectionData *selection_data,
1105                                 DndHelper        *helper)
1106 {
1107         ModestMailOperation *mail_op;
1108         GtkTreeIter parent_iter, iter;
1109         TnyFolderStore *parent_folder;
1110         TnyFolder *folder;
1111
1112         /* Check if the drag is possible */
1113 /*      if (!gtk_tree_path_compare (helper->source_row, dest_row) || */
1114 /*          !gtk_tree_drag_dest_row_drop_possible (GTK_TREE_DRAG_DEST (dest_model), */
1115 /*                                                 dest_row, */
1116 /*                                                 selection_data)) { */
1117         if (!gtk_tree_path_compare (helper->source_row, dest_row)) {
1118
1119                 gtk_drag_finish (helper->context, FALSE, FALSE, helper->time);
1120                 gtk_tree_path_free (helper->source_row);        
1121                 g_slice_free (DndHelper, helper);
1122                 return;
1123         }
1124
1125         /* Get data */
1126         gtk_tree_model_get_iter (source_model, &parent_iter, dest_row);
1127         gtk_tree_model_get_iter (source_model, &iter, helper->source_row);
1128         gtk_tree_model_get (source_model, &parent_iter, 
1129                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, 
1130                             &parent_folder, -1);
1131         gtk_tree_model_get (source_model, &iter,
1132                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
1133                             &folder, -1);
1134
1135         /* Do the mail operation */
1136         mail_op = modest_mail_operation_new_with_error_handling (MODEST_MAIL_OPERATION_TYPE_RECEIVE, 
1137                                                                  NULL,
1138                                                                  modest_ui_actions_move_folder_error_handler,
1139                                                                  NULL);
1140         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (), 
1141                                          mail_op);
1142         g_signal_connect (G_OBJECT (mail_op), "progress-changed",
1143                           G_CALLBACK (on_progress_changed), helper);
1144
1145         modest_mail_operation_xfer_folder (mail_op, 
1146                                            folder, 
1147                                            parent_folder,
1148                                            helper->delete_source);
1149         
1150         /* Frees */
1151         g_object_unref (G_OBJECT (parent_folder));
1152         g_object_unref (G_OBJECT (folder));
1153         g_object_unref (G_OBJECT (mail_op));
1154 }
1155
1156 /*
1157  * This function receives the data set by the "drag-data-get" signal
1158  * handler. This information comes within the #GtkSelectionData. This
1159  * function will manage both the drags of folders of the treeview and
1160  * drags of headers of the header view widget.
1161  */
1162 static void 
1163 on_drag_data_received (GtkWidget *widget, 
1164                        GdkDragContext *context, 
1165                        gint x, 
1166                        gint y, 
1167                        GtkSelectionData *selection_data, 
1168                        guint target_type, 
1169                        guint time, 
1170                        gpointer data)
1171 {
1172         GtkWidget *source_widget;
1173         GtkTreeModel *dest_model, *source_model;
1174         GtkTreePath *source_row, *dest_row;
1175         GtkTreeViewDropPosition pos;
1176         gboolean success = FALSE, delete_source = FALSE;
1177         DndHelper *helper = NULL; 
1178
1179         /* Do not allow further process */
1180         g_signal_stop_emission_by_name (widget, "drag-data-received");
1181         source_widget = gtk_drag_get_source_widget (context);
1182
1183         /* Get the action */
1184         if (context->action == GDK_ACTION_MOVE) {
1185                 delete_source = TRUE;
1186
1187                 /* Notify that there is no folder selected. We need to
1188                    do this in order to update the headers view (and
1189                    its monitors, because when moving, the old folder
1190                    won't longer exist. We can not wait for the end of
1191                    the operation, because the operation won't start if
1192                    the folder is in use */
1193                 if (source_widget == widget)
1194                         g_signal_emit (G_OBJECT (widget), 
1195                                        signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0, NULL, TRUE);
1196         }
1197
1198         /* Check if the get_data failed */
1199         if (selection_data == NULL || selection_data->length < 0)
1200                 gtk_drag_finish (context, success, FALSE, time);
1201
1202         /* Get the models */
1203         gtk_tree_get_row_drag_data (selection_data,
1204                                     &source_model,
1205                                     &source_row);
1206
1207         /* Select the destination model */
1208         if (source_widget == widget) {
1209                 dest_model = source_model;
1210         } else {
1211                 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
1212         }
1213
1214         /* Get the path to the destination row. Can not call
1215            gtk_tree_view_get_drag_dest_row() because the source row
1216            is not selected anymore */
1217         gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget), x, y,
1218                                            &dest_row, &pos);
1219
1220         /* Only allow drops IN other rows */
1221         if (!dest_row || pos == GTK_TREE_VIEW_DROP_BEFORE || pos == GTK_TREE_VIEW_DROP_AFTER)
1222                 gtk_drag_finish (context, success, FALSE, time);
1223
1224         /* Create the helper */
1225         helper = g_slice_new0 (DndHelper);
1226         helper->delete_source = delete_source;
1227         helper->source_row = gtk_tree_path_copy (source_row);
1228         helper->context = context;
1229         helper->time = time;
1230
1231         /* Drags from the header view */
1232         if (source_widget != widget) {
1233
1234                 drag_and_drop_from_header_view (source_model,
1235                                                 dest_model,
1236                                                 dest_row,
1237                                                 helper);
1238         } else {
1239
1240
1241                 drag_and_drop_from_folder_view (source_model,
1242                                                 dest_model,
1243                                                 dest_row,
1244                                                 selection_data, 
1245                                                 helper);
1246         }
1247
1248         /* Frees */
1249         gtk_tree_path_free (source_row);
1250         gtk_tree_path_free (dest_row);
1251 }
1252
1253 /*
1254  * We define a "drag-drop" signal handler because we do not want to
1255  * use the default one, because the default one always calls
1256  * gtk_drag_finish and we prefer to do it in the "drag-data-received"
1257  * signal handler, because there we have all the information available
1258  * to know if the dnd was a success or not.
1259  */
1260 static gboolean
1261 drag_drop_cb (GtkWidget      *widget,
1262               GdkDragContext *context,
1263               gint            x,
1264               gint            y,
1265               guint           time,
1266               gpointer        user_data) 
1267 {
1268         gpointer target;
1269
1270         if (!context->targets)
1271                 return FALSE;
1272
1273         /* Check if we're dragging a folder row */
1274         target = gtk_drag_dest_find_target (widget, context, NULL);
1275
1276         /* Request the data from the source. */
1277         gtk_drag_get_data(widget, context, target, time);
1278
1279     return TRUE;
1280 }
1281
1282 /*
1283  * This function expands a node of a tree view if it's not expanded
1284  * yet. Not sure why it needs the threads stuff, but gtk+`example code
1285  * does that, so that's why they're here.
1286  */
1287 static gint
1288 expand_row_timeout (gpointer data)
1289 {
1290         GtkTreeView *tree_view = data;
1291         GtkTreePath *dest_path = NULL;
1292         GtkTreeViewDropPosition pos;
1293         gboolean result = FALSE;
1294         
1295         GDK_THREADS_ENTER ();
1296         
1297         gtk_tree_view_get_drag_dest_row (tree_view,
1298                                          &dest_path,
1299                                          &pos);
1300         
1301         if (dest_path &&
1302             (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER ||
1303              pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)) {
1304                 gtk_tree_view_expand_row (tree_view, dest_path, FALSE);
1305                 gtk_tree_path_free (dest_path);
1306         }
1307         else {
1308                 if (dest_path)
1309                         gtk_tree_path_free (dest_path);
1310                 
1311                 result = TRUE;
1312         }
1313         
1314         GDK_THREADS_LEAVE ();
1315
1316         return result;
1317 }
1318
1319 /*
1320  * This function is called whenever the pointer is moved over a widget
1321  * while dragging some data. It installs a timeout that will expand a
1322  * node of the treeview if not expanded yet. This function also calls
1323  * gdk_drag_status in order to set the suggested action that will be
1324  * used by the "drag-data-received" signal handler to know if we
1325  * should do a move or just a copy of the data.
1326  */
1327 static gboolean
1328 on_drag_motion (GtkWidget      *widget,
1329                 GdkDragContext *context,
1330                 gint            x,
1331                 gint            y,
1332                 guint           time,
1333                 gpointer        user_data)  
1334 {
1335         GtkTreeViewDropPosition pos;
1336         GtkTreePath *dest_row;
1337         ModestFolderViewPrivate *priv;
1338         GdkDragAction suggested_action;
1339         gboolean valid_location = FALSE;
1340
1341         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
1342
1343         if (priv->timer_expander != 0) {
1344                 g_source_remove (priv->timer_expander);
1345                 priv->timer_expander = 0;
1346         }
1347
1348         gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
1349                                            x, y,
1350                                            &dest_row,
1351                                            &pos);
1352
1353         /* Do not allow drops between folders */
1354         if (!dest_row ||
1355             pos == GTK_TREE_VIEW_DROP_BEFORE ||
1356             pos == GTK_TREE_VIEW_DROP_AFTER) {
1357                 gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW (widget), NULL, 0);
1358                 gdk_drag_status(context, 0, time);
1359                 valid_location = FALSE;
1360                 goto out;
1361         } else {
1362                 valid_location = TRUE;
1363         }
1364
1365         /* Expand the selected row after 1/2 second */
1366         if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), dest_row)) {
1367                 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), dest_row, pos);
1368                 priv->timer_expander = g_timeout_add (500, expand_row_timeout, widget);
1369         }
1370
1371         /* Select the desired action. By default we pick MOVE */
1372         suggested_action = GDK_ACTION_MOVE;
1373
1374         if (context->actions == GDK_ACTION_COPY)
1375             gdk_drag_status(context, GDK_ACTION_COPY, time);
1376         else if (context->actions == GDK_ACTION_MOVE)
1377             gdk_drag_status(context, GDK_ACTION_MOVE, time);
1378         else if (context->actions & suggested_action)
1379             gdk_drag_status(context, suggested_action, time);
1380         else
1381             gdk_drag_status(context, GDK_ACTION_DEFAULT, time);
1382
1383  out:
1384         if (dest_row)
1385                 gtk_tree_path_free (dest_row);
1386         g_signal_stop_emission_by_name (widget, "drag-motion");
1387         return valid_location;
1388 }
1389
1390
1391 /* Folder view drag types */
1392 const GtkTargetEntry folder_view_drag_types[] =
1393 {
1394         { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, MODEST_FOLDER_ROW },
1395         { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_APP,    MODEST_HEADER_ROW }
1396 };
1397
1398 /*
1399  * This function sets the treeview as a source and a target for dnd
1400  * events. It also connects all the requirede signals.
1401  */
1402 static void
1403 setup_drag_and_drop (GtkTreeView *self)
1404 {
1405         /* Set up the folder view as a dnd destination. Set only the
1406            highlight flag, otherwise gtk will have a different
1407            behaviour */
1408         gtk_drag_dest_set (GTK_WIDGET (self),
1409                            GTK_DEST_DEFAULT_HIGHLIGHT,
1410                            folder_view_drag_types,
1411                            G_N_ELEMENTS (folder_view_drag_types),
1412                            GDK_ACTION_MOVE | GDK_ACTION_COPY);
1413
1414         g_signal_connect (G_OBJECT (self),
1415                           "drag_data_received",
1416                           G_CALLBACK (on_drag_data_received),
1417                           NULL);
1418
1419
1420         /* Set up the treeview as a dnd source */
1421         gtk_drag_source_set (GTK_WIDGET (self),
1422                              GDK_BUTTON1_MASK,
1423                              folder_view_drag_types,
1424                              G_N_ELEMENTS (folder_view_drag_types),
1425                              GDK_ACTION_MOVE | GDK_ACTION_COPY);
1426
1427         g_signal_connect (G_OBJECT (self),
1428                           "drag_motion",
1429                           G_CALLBACK (on_drag_motion),
1430                           NULL);
1431         
1432         g_signal_connect (G_OBJECT (self),
1433                           "drag_data_get",
1434                           G_CALLBACK (on_drag_data_get),
1435                           NULL);
1436
1437         g_signal_connect (G_OBJECT (self),
1438                           "drag_drop",
1439                           G_CALLBACK (drag_drop_cb),
1440                           NULL);
1441 }
1442
1443 /*
1444  * This function manages the navigation through the folders using the
1445  * keyboard or the hardware keys in the device
1446  */
1447 static gboolean
1448 on_key_pressed (GtkWidget *self,
1449                 GdkEventKey *event,
1450                 gpointer user_data)
1451 {
1452         GtkTreeSelection *selection;
1453         GtkTreeIter iter;
1454         GtkTreeModel *model;
1455         gboolean retval = FALSE;
1456
1457         /* Up and Down are automatically managed by the treeview */
1458         if (event->keyval == GDK_Return) {
1459                 /* Expand/Collapse the selected row */
1460                 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1461                 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
1462                         GtkTreePath *path;
1463
1464                         path = gtk_tree_model_get_path (model, &iter);
1465
1466                         if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (self), path))
1467                                 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1468                         else
1469                                 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1470                         gtk_tree_path_free (path);
1471                 }
1472                 /* No further processing */
1473                 retval = TRUE;
1474         }
1475
1476         return retval;
1477 }
1478
1479 /*
1480  * We listen to the changes in the local folder account name key,
1481  * because we want to show the right name in the view. The local
1482  * folder account name corresponds to the device name in the Maemo
1483  * version. We do this because we do not want to query gconf on each
1484  * tree view refresh. It's better to cache it and change whenever
1485  * necessary.
1486  */
1487 static void 
1488 on_configuration_key_changed (ModestConf* conf, 
1489                               const gchar *key, 
1490                               ModestConfEvent event, 
1491                               ModestFolderView *self)
1492 {
1493         ModestFolderViewPrivate *priv;
1494
1495         if (!key)
1496                 return;
1497
1498         g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
1499         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1500
1501         if (!strcmp (key, MODEST_CONF_DEVICE_NAME)) {
1502                 g_free (priv->local_account_name);
1503
1504                 if (event == MODEST_CONF_EVENT_KEY_UNSET)
1505                         priv->local_account_name = g_strdup (MODEST_LOCAL_FOLDERS_DEFAULT_DISPLAY_NAME);
1506                 else
1507                         priv->local_account_name = modest_conf_get_string (modest_runtime_get_conf(),
1508                                                                            MODEST_CONF_DEVICE_NAME, NULL);
1509
1510                 /* Force a redraw */
1511 #if GTK_CHECK_VERSION(2, 8, 0) /* gtk_tree_view_column_queue_resize is only available in GTK+ 2.8 */
1512                 GtkTreeViewColumn * tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self), 
1513                                                                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN);
1514                 gtk_tree_view_column_queue_resize (tree_column);
1515 #endif
1516         }
1517 }
1518
1519 void 
1520 modest_folder_view_set_style (ModestFolderView *self,
1521                               ModestFolderViewStyle style)
1522 {
1523         ModestFolderViewPrivate *priv;
1524
1525         g_return_if_fail (self);
1526         
1527         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1528
1529         priv->style = style;
1530 }
1531
1532 void
1533 modest_folder_view_set_account_id_of_visible_server_account (ModestFolderView *self,
1534                                                              const gchar *account_id)
1535 {
1536         ModestFolderViewPrivate *priv;
1537         GtkTreeModel *model;
1538
1539         g_return_if_fail (self);
1540         
1541         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1542
1543         /* This will be used by the filter_row callback,
1544          * to decided which rows to show: */
1545         if (priv->visible_account_id)
1546                 g_free (priv->visible_account_id);
1547         priv->visible_account_id = g_strdup (account_id);
1548
1549         /* Refilter */
1550         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1551         if (GTK_IS_TREE_MODEL_FILTER (model))
1552                 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
1553 }
1554
1555 const gchar *
1556 modest_folder_view_get_account_id_of_visible_server_account (ModestFolderView *self)
1557 {
1558         ModestFolderViewPrivate *priv;
1559
1560         g_return_val_if_fail (self, NULL);
1561         
1562         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1563
1564         return (const gchar *) priv->visible_account_id;
1565 }
1566
1567 static gboolean
1568 find_inbox_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *inbox_iter)
1569 {
1570         do {
1571                 GtkTreeIter child;
1572                 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1573                 gchar *name = NULL;
1574
1575                 gtk_tree_model_get (model, iter, 
1576                                         TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name,
1577                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, 
1578                                     &type, -1);
1579
1580                 /*
1581                 printf ("DEBUG: %s: name=%s, type=%d, TNY_FOLDER_TYPE_INBOX=%d\n", 
1582                         __FUNCTION__, name, type, TNY_FOLDER_TYPE_INBOX);
1583                 */
1584                         
1585                 gboolean result = FALSE;
1586                 if (type == TNY_FOLDER_TYPE_INBOX) {
1587                         result = TRUE;
1588                 } else if (type == TNY_FOLDER_TYPE_NORMAL) {
1589                         /* tinymail's camel implementation only provides TNY_FOLDER_TYPE_NORMAL
1590                          * when getting folders from the cache, before connectin, so we do 
1591                          * an extra check. We could fix this in tinymail, but it's easier 
1592                          * to do here.
1593                          */
1594                          if (strcmp (name, "Inbox") == 0)
1595                                 result = TRUE;
1596                 }
1597                 
1598                 g_free (name);
1599                 
1600                 if (result) {
1601                         *inbox_iter = *iter;
1602                         return TRUE;
1603                 }
1604
1605                 if (gtk_tree_model_iter_children (model, &child, iter)) {
1606                         if (find_inbox_iter (model, &child, inbox_iter))
1607                                 return TRUE;
1608                 }
1609
1610         } while (gtk_tree_model_iter_next (model, iter));
1611
1612         return FALSE;
1613 }
1614
1615
1616
1617 void 
1618 modest_folder_view_select_first_inbox_or_local (ModestFolderView *self)
1619 {
1620         GtkTreeModel *model;
1621         GtkTreeIter iter, inbox_iter;
1622         GtkTreeSelection *sel;
1623
1624         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1625         if (!model)
1626                 return;
1627
1628         expand_root_items (self);
1629         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1630
1631         gtk_tree_model_get_iter_first (model, &iter);
1632         if (find_inbox_iter (model, &iter, &inbox_iter)) {
1633                 gtk_tree_selection_select_iter (sel, &inbox_iter);
1634         }
1635         else {
1636                 gtk_tree_model_get_iter_first (model, &iter);
1637                 gtk_tree_selection_select_iter (sel, &iter);
1638         }
1639 }