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