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