* only use striketrough to mark deleted messages
[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
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-monitor.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-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
53 /* 'private'/'protected' functions */
54 static void modest_folder_view_class_init  (ModestFolderViewClass *klass);
55 static void modest_folder_view_init        (ModestFolderView *obj);
56 static void modest_folder_view_finalize    (GObject *obj);
57
58 static void         tny_account_store_view_init (gpointer g, 
59                                                  gpointer iface_data);
60
61 static void         modest_folder_view_set_account_store (TnyAccountStoreView *self, 
62                                                           TnyAccountStore     *account_store);
63
64 static gboolean     update_model           (ModestFolderView *self,
65                                             ModestTnyAccountStore *account_store);
66
67 static gboolean     update_model_empty     (ModestFolderView *self);
68
69 static void         on_selection_changed   (GtkTreeSelection *sel, gpointer data);
70
71 static void         on_account_update      (TnyAccountStore *account_store, 
72                                             const gchar *account,
73                                             gpointer user_data);
74
75 static void         on_accounts_reloaded   (TnyAccountStore *store, 
76                                             gpointer user_data);
77
78 static gint         cmp_rows               (GtkTreeModel *tree_model, 
79                                             GtkTreeIter *iter1, 
80                                             GtkTreeIter *iter2,
81                                             gpointer user_data);
82
83 /* DnD functions */
84 static void         on_drag_data_get       (GtkWidget *widget, 
85                                             GdkDragContext *context, 
86                                             GtkSelectionData *selection_data, 
87                                             guint info, 
88                                             guint time, 
89                                             gpointer data);
90
91 static void         on_drag_data_received  (GtkWidget *widget, 
92                                             GdkDragContext *context, 
93                                             gint x, 
94                                             gint y, 
95                                             GtkSelectionData *selection_data, 
96                                             guint info, 
97                                             guint time, 
98                                             gpointer data);
99
100 static gboolean     on_drag_motion         (GtkWidget      *widget,
101                                             GdkDragContext *context,
102                                             gint            x,
103                                             gint            y,
104                                             guint           time,
105                                             gpointer        user_data);
106
107 static gint         expand_row_timeout     (gpointer data);
108
109 static void         setup_drag_and_drop    (GtkTreeView *self);
110
111 enum {
112         FOLDER_SELECTION_CHANGED_SIGNAL,
113         LAST_SIGNAL
114 };
115
116 typedef struct _ModestFolderViewPrivate ModestFolderViewPrivate;
117 struct _ModestFolderViewPrivate {
118         TnyAccountStore     *account_store;
119         TnyFolder           *cur_folder;
120         GtkTreeRowReference *cur_row;
121
122         gulong               account_update_signal;
123         gulong               changed_signal;
124         gulong               accounts_reloaded_signal;
125         GMutex              *lock;
126         
127         GtkTreeSelection    *cur_selection;
128         TnyFolderStoreQuery *query;
129         guint                timer_expander;
130
131         TnyFolderMonitor    *monitor;
132 };
133 #define MODEST_FOLDER_VIEW_GET_PRIVATE(o)                       \
134         (G_TYPE_INSTANCE_GET_PRIVATE((o),                       \
135                                      MODEST_TYPE_FOLDER_VIEW,   \
136                                      ModestFolderViewPrivate))
137 /* globals */
138 static GObjectClass *parent_class = NULL;
139
140 static guint signals[LAST_SIGNAL] = {0}; 
141
142 GType
143 modest_folder_view_get_type (void)
144 {
145         static GType my_type = 0;
146         if (!my_type) {
147                 static const GTypeInfo my_info = {
148                         sizeof(ModestFolderViewClass),
149                         NULL,           /* base init */
150                         NULL,           /* base finalize */
151                         (GClassInitFunc) modest_folder_view_class_init,
152                         NULL,           /* class finalize */
153                         NULL,           /* class data */
154                         sizeof(ModestFolderView),
155                         1,              /* n_preallocs */
156                         (GInstanceInitFunc) modest_folder_view_init,
157                         NULL
158                 };
159
160                 static const GInterfaceInfo tny_account_store_view_info = {
161                         (GInterfaceInitFunc) tny_account_store_view_init, /* interface_init */
162                         NULL,         /* interface_finalize */
163                         NULL          /* interface_data */
164                 };
165
166                                 
167                 my_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
168                                                   "ModestFolderView",
169                                                   &my_info, 0);
170
171                 g_type_add_interface_static (my_type, 
172                                              TNY_TYPE_ACCOUNT_STORE_VIEW, 
173                                              &tny_account_store_view_info);
174         }
175         return my_type;
176 }
177
178 static void
179 modest_folder_view_class_init (ModestFolderViewClass *klass)
180 {
181         GObjectClass *gobject_class;
182         gobject_class = (GObjectClass*) klass;
183
184         parent_class            = g_type_class_peek_parent (klass);
185         gobject_class->finalize = modest_folder_view_finalize;
186
187         g_type_class_add_private (gobject_class,
188                                   sizeof(ModestFolderViewPrivate));
189         
190         signals[FOLDER_SELECTION_CHANGED_SIGNAL] = 
191                 g_signal_new ("folder_selection_changed",
192                               G_TYPE_FROM_CLASS (gobject_class),
193                               G_SIGNAL_RUN_FIRST,
194                               G_STRUCT_OFFSET (ModestFolderViewClass,
195                                                folder_selection_changed),
196                               NULL, NULL,
197                               modest_marshal_VOID__POINTER_BOOLEAN,
198                               G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_BOOLEAN);
199 }
200
201
202
203 static void
204 text_cell_data  (GtkTreeViewColumn *column,  GtkCellRenderer *renderer,
205                  GtkTreeModel *tree_model,  GtkTreeIter *iter,  gpointer data)
206 {
207         GObject *rendobj;
208         gchar *fname = NULL;
209         gint unread;
210         TnyFolderType type;
211         TnyFolder *folder = NULL;
212         
213         g_return_if_fail (column);
214         g_return_if_fail (tree_model);
215
216         gtk_tree_model_get (tree_model, iter,
217                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &fname,
218                             TNY_GTK_FOLDER_STORE_TREE_MODEL_UNREAD_COLUMN, &unread,
219                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
220                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
221                             -1);
222         rendobj = G_OBJECT(renderer);
223  
224         if (!fname)
225                 return;
226         
227         if (folder && type != TNY_FOLDER_TYPE_ROOT) { /* FIXME: tnymail bug? crashes with root folders */
228                 if (modest_tny_folder_is_local_folder (folder)) {
229                         TnyFolderType type;
230                         type = modest_tny_folder_get_local_folder_type (folder);
231                         if (type != TNY_FOLDER_TYPE_UNKNOWN) {
232                                 g_free (fname);
233                                 fname = g_strdup(modest_local_folder_info_get_type_display_name (type));
234                         }
235                 }
236         } else if (folder && type == TNY_FOLDER_TYPE_ROOT) {
237                 g_warning ("fname: %s", fname);
238                 /* FIXME: todo */
239         }
240                         
241         if (unread > 0) {
242                 gchar *folder_title = g_strdup_printf ("%s (%d)", fname, unread);
243                 g_object_set (rendobj,"text", folder_title,  "weight", 800, NULL);
244                 g_free (folder_title);
245         } else 
246                 g_object_set (rendobj,"text", fname, "weight", 400, NULL);
247                 
248         g_free (fname);
249         if (folder) g_object_unref (G_OBJECT (folder));
250 }
251
252
253 static GdkPixbuf*
254 get_cached_icon (const gchar *name)
255 {
256         GError *err = NULL;
257         gpointer pixbuf;
258         gpointer orig_key;
259         static GHashTable *icon_cache = NULL;
260         
261         g_return_val_if_fail (name, NULL);
262
263         if (G_UNLIKELY(!icon_cache))
264                 icon_cache = modest_cache_mgr_get_cache (modest_runtime_get_cache_mgr(),
265                                                          MODEST_CACHE_MGR_CACHE_TYPE_PIXBUF);
266         
267         if (!icon_cache || !g_hash_table_lookup_extended (icon_cache, name, &orig_key, &pixbuf)) {
268                 pixbuf = (gpointer)gdk_pixbuf_new_from_file (name, &err);
269                 if (!pixbuf) {
270                         g_printerr ("modest: error in icon factory while loading '%s': %s\n",
271                                     name, err->message);
272                         g_error_free (err);
273                 }
274                 /* if we cannot find it, we still insert (if we have a cache), so we get the error
275                  * only once */
276                 if (icon_cache)
277                         g_hash_table_insert (icon_cache, g_strdup(name),(gpointer)pixbuf);
278         }
279         return GDK_PIXBUF(pixbuf);
280 }
281
282
283 static void
284 icon_cell_data  (GtkTreeViewColumn *column,  GtkCellRenderer *renderer,
285                  GtkTreeModel *tree_model,  GtkTreeIter *iter, gpointer data)
286 {
287         GObject *rendobj;
288         GdkPixbuf *pixbuf;
289         TnyFolderType type;
290         gchar *fname = NULL;
291         gint unread;
292         
293         rendobj = G_OBJECT(renderer);
294         gtk_tree_model_get (tree_model, iter,
295                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
296                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &fname,
297                             TNY_GTK_FOLDER_STORE_TREE_MODEL_UNREAD_COLUMN, &unread, 
298                             -1);
299         rendobj = G_OBJECT(renderer);
300         
301         if (type == TNY_FOLDER_TYPE_NORMAL || type == TNY_FOLDER_TYPE_UNKNOWN) {
302                 type = modest_tny_folder_guess_folder_type_from_name (fname);
303         }
304         g_free (fname);
305
306         switch (type) {
307         case TNY_FOLDER_TYPE_ROOT:
308                 pixbuf = get_cached_icon (MODEST_FOLDER_ICON_ACCOUNT);
309                 break;
310         case TNY_FOLDER_TYPE_INBOX:
311                 pixbuf = get_cached_icon (MODEST_FOLDER_ICON_INBOX);
312                 break;
313         case TNY_FOLDER_TYPE_OUTBOX:
314                 pixbuf = get_cached_icon (MODEST_FOLDER_ICON_OUTBOX);
315                 break;
316         case TNY_FOLDER_TYPE_JUNK:
317                 pixbuf = get_cached_icon (MODEST_FOLDER_ICON_JUNK);
318                 break;
319         case TNY_FOLDER_TYPE_SENT:
320                 pixbuf = get_cached_icon (MODEST_FOLDER_ICON_SENT);
321                 break;
322         case TNY_FOLDER_TYPE_TRASH:
323                 pixbuf = get_cached_icon (MODEST_FOLDER_ICON_TRASH);
324                 break;
325         case TNY_FOLDER_TYPE_DRAFTS:
326                 pixbuf = get_cached_icon (MODEST_FOLDER_ICON_DRAFTS);
327                 break;
328         case TNY_FOLDER_TYPE_NOTES:
329                 pixbuf = get_cached_icon (MODEST_FOLDER_ICON_NOTES);
330                 break;
331         case TNY_FOLDER_TYPE_CALENDAR:
332                 pixbuf = get_cached_icon (MODEST_FOLDER_ICON_CALENDAR);
333                 break;
334         case TNY_FOLDER_TYPE_CONTACTS:
335                 pixbuf = get_cached_icon (MODEST_FOLDER_ICON_CONTACTS);
336                 break;
337         case TNY_FOLDER_TYPE_NORMAL:
338         default:
339                 pixbuf = get_cached_icon (MODEST_FOLDER_ICON_NORMAL);
340                 break;
341         }
342         g_object_set (rendobj, "pixbuf", pixbuf, NULL);
343 }
344
345 static void
346 modest_folder_view_init (ModestFolderView *obj)
347 {
348         ModestFolderViewPrivate *priv;
349         GtkTreeViewColumn *column;
350         GtkCellRenderer *renderer;
351         GtkTreeSelection *sel;
352         
353         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
354         
355         priv->timer_expander = 0;
356         priv->account_store  = NULL;
357         priv->cur_folder     = NULL;
358         priv->cur_row        = NULL;
359         priv->query          = NULL;
360         priv->monitor        = NULL;
361
362         priv->lock           = g_mutex_new ();
363
364         column = gtk_tree_view_column_new ();   
365         gtk_tree_view_append_column (GTK_TREE_VIEW(obj),column);
366         
367         renderer = gtk_cell_renderer_pixbuf_new();
368         gtk_tree_view_column_pack_start (column, renderer, FALSE);
369         gtk_tree_view_column_set_cell_data_func(column, renderer,
370                                                 icon_cell_data, NULL, NULL);
371         
372         renderer = gtk_cell_renderer_text_new();
373         gtk_tree_view_column_pack_start (column, renderer, FALSE);
374         gtk_tree_view_column_set_cell_data_func(column, renderer,
375                                                 text_cell_data, NULL, NULL);
376         
377         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(obj));
378         gtk_tree_selection_set_mode (sel, GTK_SELECTION_SINGLE);
379
380         gtk_tree_view_column_set_spacing (column, 2);
381         gtk_tree_view_column_set_resizable (column, TRUE);
382         gtk_tree_view_column_set_fixed_width (column, TRUE);            
383         gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(obj), FALSE);
384         gtk_tree_view_set_enable_search     (GTK_TREE_VIEW(obj), FALSE);
385
386         setup_drag_and_drop (GTK_TREE_VIEW(obj));
387 }
388
389 static void
390 tny_account_store_view_init (gpointer g, gpointer iface_data)
391 {
392         TnyAccountStoreViewIface *klass = (TnyAccountStoreViewIface *)g;
393
394         klass->set_account_store_func = modest_folder_view_set_account_store;
395
396         return;
397 }
398
399 static void
400 modest_folder_view_finalize (GObject *obj)
401 {
402         ModestFolderViewPrivate *priv;
403         GtkTreeSelection    *sel;
404         
405         g_return_if_fail (obj);
406         
407         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
408
409         if (priv->timer_expander != 0) {
410                 g_source_remove (priv->timer_expander);
411                 priv->timer_expander = 0;
412         }
413
414         if (priv->account_store) {
415                 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
416                                              priv->account_update_signal);
417                 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
418                                              priv->accounts_reloaded_signal);
419                 g_object_unref (G_OBJECT(priv->account_store));
420                 priv->account_store = NULL;
421         }
422
423         if (priv->lock) {
424                 g_mutex_free (priv->lock);
425                 priv->lock = NULL;
426         }
427
428         if (priv->query) {
429                 g_object_unref (G_OBJECT (priv->query));
430                 priv->query = NULL;
431         }
432
433         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(obj));
434         if (sel)
435                 g_signal_handler_disconnect (G_OBJECT(sel), priv->changed_signal);
436         
437         G_OBJECT_CLASS(parent_class)->finalize (obj);
438 }
439
440
441 static void
442 modest_folder_view_set_account_store (TnyAccountStoreView *self, TnyAccountStore *account_store)
443 {
444         ModestFolderViewPrivate *priv;
445         TnyDevice *device;
446
447         g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
448         g_return_if_fail (TNY_IS_ACCOUNT_STORE (account_store));
449
450         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
451         device = tny_account_store_get_device (account_store);
452
453         if (G_UNLIKELY (priv->account_store)) {
454
455                 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store), 
456                                                    priv->account_update_signal))
457                         g_signal_handler_disconnect (G_OBJECT (priv->account_store), 
458                                                      priv->account_update_signal);
459                 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store), 
460                                                    priv->accounts_reloaded_signal))
461                         g_signal_handler_disconnect (G_OBJECT (priv->account_store), 
462                                                      priv->accounts_reloaded_signal);
463
464                 g_object_unref (G_OBJECT (priv->account_store));
465         }
466
467         priv->account_store = g_object_ref (G_OBJECT (account_store));
468
469         priv->account_update_signal = 
470                 g_signal_connect (G_OBJECT(account_store), "account_update",
471                                   G_CALLBACK (on_account_update), self);
472
473         priv->accounts_reloaded_signal = 
474                 g_signal_connect (G_OBJECT(account_store), "accounts_reloaded",
475                                   G_CALLBACK (on_accounts_reloaded), self);
476         
477         if (!update_model (MODEST_FOLDER_VIEW (self),
478                            MODEST_TNY_ACCOUNT_STORE (priv->account_store)))
479                 g_printerr ("modest: failed to update model\n");
480
481         g_object_unref (G_OBJECT (device));
482 }
483
484 static void
485 on_account_update (TnyAccountStore *account_store, const gchar *account,
486                    gpointer user_data)
487 {
488         if (!update_model (MODEST_FOLDER_VIEW(user_data), 
489                            MODEST_TNY_ACCOUNT_STORE(account_store)))
490                 g_printerr ("modest: failed to update model for changes in '%s'",
491                             account);
492 }
493
494 static void 
495 on_accounts_reloaded   (TnyAccountStore *account_store, 
496                         gpointer user_data)
497 {
498         update_model (MODEST_FOLDER_VIEW (user_data), 
499                       MODEST_TNY_ACCOUNT_STORE(account_store));
500 }
501
502 void
503 modest_folder_view_set_title (ModestFolderView *self, const gchar *title)
504 {
505         GtkTreeViewColumn *col;
506         
507         g_return_if_fail (self);
508
509         col = gtk_tree_view_get_column (GTK_TREE_VIEW(self), 0);
510         if (!col) {
511                 g_printerr ("modest: failed get column for title\n");
512                 return;
513         }
514
515         gtk_tree_view_column_set_title (col, title);
516         gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self),
517                                            title != NULL);
518 }
519
520 GtkWidget*
521 modest_folder_view_new (TnyFolderStoreQuery *query)
522 {
523         GObject *self;
524         ModestFolderViewPrivate *priv;
525         GtkTreeSelection *sel;
526         
527         self = G_OBJECT (g_object_new (MODEST_TYPE_FOLDER_VIEW, NULL));
528         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
529
530         priv->query = g_object_ref (query);
531         
532         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
533         priv->changed_signal = g_signal_connect (sel, "changed",
534                                                  G_CALLBACK (on_selection_changed), self);
535         return GTK_WIDGET(self);
536 }
537
538
539 static gboolean
540 update_model_empty (ModestFolderView *self)
541 {
542         ModestFolderViewPrivate *priv;
543         
544         g_return_val_if_fail (self, FALSE);
545         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
546
547         g_mutex_lock (priv->lock);
548         {
549                 if (priv->monitor) {
550                         tny_folder_monitor_stop (priv->monitor);
551                         g_object_unref(G_OBJECT(priv->monitor));
552                         priv->monitor = NULL;
553                 }
554         }
555         g_mutex_unlock (priv->lock);
556         
557         g_signal_emit (G_OBJECT(self), signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
558                        NULL, TRUE);
559         return TRUE;
560 }
561
562
563 /* this feels dirty; any other way to expand all the root items? */
564 static void
565 expand_root_items (ModestFolderView *self)
566 {
567         GtkTreePath *path;
568         path = gtk_tree_path_new_first ();
569
570         /* all folders should have child items, so.. */
571         while (gtk_tree_view_expand_row (GTK_TREE_VIEW(self), path, FALSE))
572                 gtk_tree_path_next (path);
573         
574         gtk_tree_path_free (path);
575 }
576
577 static gboolean
578 update_model (ModestFolderView *self, ModestTnyAccountStore *account_store)
579 {
580         ModestFolderViewPrivate *priv;
581
582         TnyList          *account_list;
583         GtkTreeModel     *model, *sortable;
584
585         g_return_val_if_fail (account_store, FALSE);
586
587         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE(self);
588         
589         /* Notify that there is no folder selected */
590         update_model_empty (self);
591         
592         /* FIXME: the local accounts are not shown when the query
593            selects only the subscribed folders. */
594 /*      model        = tny_gtk_folder_store_tree_model_new (TRUE, priv->query); */
595         model        = tny_gtk_folder_store_tree_model_new (TRUE, NULL);
596         account_list = TNY_LIST(model);
597
598         tny_account_store_get_accounts (TNY_ACCOUNT_STORE(account_store),
599                                         account_list,
600                                         TNY_ACCOUNT_STORE_STORE_ACCOUNTS);      
601         if (account_list) {
602                 sortable = gtk_tree_model_sort_new_with_model (model);
603                 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
604                                                       TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, 
605                                                       GTK_SORT_ASCENDING);
606                 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
607                                                  TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
608                                                  cmp_rows, NULL, NULL);
609
610                 /* Set new model */
611                 gtk_tree_view_set_model (GTK_TREE_VIEW(self), sortable);
612                 expand_root_items (self); /* expand all account folders */
613                 g_object_unref (account_list);
614         }
615         
616         return TRUE;
617 }
618
619
620 static void
621 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
622 {
623         GtkTreeModel            *model_sort, *model;
624         TnyFolder               *folder = NULL;
625         GtkTreeIter             iter, iter_sort;
626         GtkTreePath            *path;
627         ModestFolderView        *tree_view;
628         ModestFolderViewPrivate *priv;
629         gint                    type;
630
631         g_return_if_fail (sel);
632         g_return_if_fail (user_data);
633         
634         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
635         priv->cur_selection = sel;
636         
637         /* folder was _un_selected if true */
638         if (!gtk_tree_selection_get_selected (sel, &model_sort, &iter_sort)) {
639                 priv->cur_folder = NULL; /* FIXME: need this? */
640                 gtk_tree_row_reference_free (priv->cur_row);
641                 priv->cur_row = NULL;
642                 return; 
643         }
644
645         model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (model_sort));
646         gtk_tree_model_sort_convert_iter_to_child_iter (GTK_TREE_MODEL_SORT (model_sort),
647                                                         &iter,
648                                                         &iter_sort);
649
650         tree_view = MODEST_FOLDER_VIEW (user_data);
651
652         gtk_tree_model_get (model, &iter,
653                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
654                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
655                             -1);
656
657         if (type == TNY_FOLDER_TYPE_ROOT) {
658                 g_object_unref (folder);
659                 return;
660         }
661         
662         /* Current folder was unselected */
663         g_signal_emit (G_OBJECT(tree_view), signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
664                        priv->cur_folder, FALSE);
665
666         if (priv->cur_row) {
667 /*              tny_folder_sync (priv->cur_folder, TRUE, NULL); /\* FIXME *\/ */
668                 gtk_tree_row_reference_free (priv->cur_row);
669         }
670
671         /* New current references */
672         path = gtk_tree_model_get_path (model_sort, &iter_sort);
673         priv->cur_folder = folder;
674         priv->cur_row = gtk_tree_row_reference_new (model_sort, path);
675
676         /* Frees */
677         gtk_tree_path_free (path);
678         g_object_unref (G_OBJECT (folder));
679
680         /* New folder has been selected */
681         g_signal_emit (G_OBJECT(tree_view), 
682                        signals[FOLDER_SELECTION_CHANGED_SIGNAL], 
683                        0, folder, TRUE); 
684 }
685
686 TnyFolder *
687 modest_folder_view_get_selected (ModestFolderView *self)
688 {
689         ModestFolderViewPrivate *priv;
690
691         g_return_val_if_fail (self, NULL);
692         
693         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
694         if (priv->cur_folder)
695                 g_object_ref (priv->cur_folder);
696
697         return priv->cur_folder;
698 }
699
700 /* static gboolean */
701 /* get_model_iter (ModestFolderView *self,  */
702 /*              GtkTreeModel **model,  */
703 /*              GtkTreeIter *iter) */
704 /* { */
705 /*      GtkTreeModel *model_sort; */
706 /*      GtkTreeIter iter_sort; */
707 /*      GtkTreePath *path; */
708 /*      ModestFolderViewPrivate *priv; */
709
710 /*      priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self); */
711
712 /*      if (!priv->cur_folder) */
713 /*              return FALSE; */
714
715 /*      if (!gtk_tree_row_reference_valid (priv->cur_row)) */
716 /*              return FALSE; */
717
718 /*      model_sort = gtk_tree_view_get_model (GTK_TREE_VIEW (self)); */
719 /*      *model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (model_sort)); */
720
721 /*      /\* Get path to retrieve iter *\/ */
722 /*      path = gtk_tree_row_reference_get_path (priv->cur_row); */
723 /*      if (!gtk_tree_model_get_iter (model_sort, &iter_sort, path)) */
724 /*              return FALSE; */
725
726 /*      gtk_tree_model_sort_convert_iter_to_child_iter (GTK_TREE_MODEL_SORT (model_sort), */
727 /*                                                      iter, */
728 /*                                                      &iter_sort); */
729 /*      return TRUE; */
730 /* } */
731
732 static gint
733 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
734           gpointer user_data)
735 {
736         gint cmp;
737         gchar         *name1, *name2;
738         TnyFolderType type;
739         TnyFolder     *folder1, *folder2;
740         
741         gtk_tree_model_get (tree_model, iter1,
742                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name1,
743                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
744                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder1,
745                             -1);
746         gtk_tree_model_get (tree_model, iter2,
747                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name2,
748                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder2,
749                             -1);
750
751         /* local_folders should be the last one */
752         if (type == TNY_FOLDER_TYPE_ROOT) {
753                 /* the account name is also the name of the root folder
754                  * in case of local folders */
755                 if (name1 && strcmp (name1, MODEST_LOCAL_FOLDERS_ACCOUNT_NAME) == 0)
756                         cmp = +1;
757                 else if (name2 && strcmp (name2, MODEST_LOCAL_FOLDERS_ACCOUNT_NAME) == 0)
758                         cmp = -1;
759                 else 
760                         cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
761         } else 
762                 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
763
764         
765         if (folder1)
766                 g_object_unref(G_OBJECT(folder1));
767         if (folder2)
768                 g_object_unref(G_OBJECT(folder2));
769         
770         g_free (name1);
771         g_free (name2);
772
773         return cmp;     
774 }
775
776 /*****************************************************************************/
777 /*                        DRAG and DROP stuff                                */
778 /*****************************************************************************/
779
780 /*
781  * This function fills the #GtkSelectionData with the row and the
782  * model that has been dragged. It's called when this widget is a
783  * source for dnd after the event drop happened
784  */
785 static void
786 on_drag_data_get (GtkWidget *widget, 
787                   GdkDragContext *context, 
788                   GtkSelectionData *selection_data, 
789                   guint info, 
790                   guint time, 
791                   gpointer data)
792 {
793         GtkTreeSelection *selection;
794         GtkTreeModel *model_sort, *model;
795         GtkTreeIter iter;
796         GtkTreePath *source_row_sort;
797         GtkTreePath *source_row;
798
799         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
800         gtk_tree_selection_get_selected (selection, &model_sort, &iter);
801         source_row_sort = gtk_tree_model_get_path (model_sort, &iter);
802
803         /* Get the unsorted path and model */
804         model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (model_sort));
805         source_row = gtk_tree_model_sort_convert_path_to_child_path (GTK_TREE_MODEL_SORT (model_sort),
806                                                                      source_row_sort);
807
808         gtk_tree_set_row_drag_data (selection_data,
809                                     model,
810                                     source_row);
811
812         gtk_tree_path_free (source_row_sort);
813         gtk_tree_path_free (source_row);
814 }
815
816 typedef struct _DndHelper {
817         gboolean delete_source;
818         GtkWidget *source_widget;
819         GtkWidget *dest_widget;
820         GtkTreePath *source_row;
821         GdkDragContext *context;
822         guint time;
823 } DndHelper;
824
825
826 /*
827  * This function saves the source row in the source widget, will be
828  * used by the drag-data-delete handler to remove the source row
829  */
830 static void
831 save_and_clean (DndHelper *helper, 
832                 gboolean success)
833 {
834         /* Save row data */
835         if (success && helper->delete_source)
836                 g_object_set_data (G_OBJECT (helper->source_widget),
837                                    ROW_REF_DATA_NAME,
838                                    gtk_tree_path_copy (helper->source_row));
839
840         /* Clean dest row */
841         gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (helper->dest_widget),
842                                          NULL,
843                                          GTK_TREE_VIEW_DROP_BEFORE);
844
845 }
846
847 /*
848  * This function is the callback of the
849  * modest_mail_operation_xfer_msg() call. We check here if the message
850  * was correctly asynchronously transfered
851  */
852 static void
853 on_progress_changed (ModestMailOperation *mail_op, gpointer user_data)
854 {
855         ModestMailOperationQueue *queue;
856         gboolean success = FALSE;
857         DndHelper *helper;
858
859         helper = (DndHelper *) user_data;
860
861         if (modest_mail_operation_get_status (mail_op) == 
862             MODEST_MAIL_OPERATION_STATUS_SUCCESS) {
863                 success = TRUE;
864         } else {
865                 const GError *error;
866                 error = modest_mail_operation_get_error (mail_op);
867                 g_warning ("Error transferring messages: %s\n", error->message);
868         }
869
870         /* Remove the mail operation */ 
871         queue = modest_runtime_get_mail_operation_queue ();
872         modest_mail_operation_queue_remove (queue, mail_op);
873         g_object_unref (G_OBJECT (mail_op));
874
875         /* Save and clean */
876         save_and_clean (helper, success);       
877         
878         /* Notify the drag source */
879         gtk_drag_finish (helper->context, success, (success && helper->delete_source), helper->time);
880
881         /* Free the helper */
882         g_slice_free (DndHelper, helper);
883 }
884
885 /*
886  * This function is used by drag_data_received_cb to manage drag and
887  * drop of a header, i.e, and drag from the header view to the folder
888  * view.
889  */
890 static void
891 drag_and_drop_from_header_view (GtkTreeModel *source_model,
892                                 GtkTreeModel *dest_model,
893                                 GtkTreePath  *dest_row,
894                                 DndHelper    *helper)
895 {
896         TnyHeader *header;
897         TnyFolder *folder;
898         ModestMailOperationQueue *queue;
899         ModestMailOperation *mail_op;
900         gboolean started;
901         GtkTreeIter source_iter, dest_iter;
902
903         /* Get header */
904         gtk_tree_model_get_iter (source_model, &source_iter, helper->source_row);
905         gtk_tree_model_get (source_model, &source_iter, 
906                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, 
907                             &header, -1);
908
909         /* Get Folder */
910         gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
911         gtk_tree_model_get (dest_model, &dest_iter, 
912                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, 
913                             &folder, -1);
914
915         /* Transfer message */
916         queue = modest_runtime_get_mail_operation_queue ();
917         mail_op = modest_mail_operation_new ();
918         started = modest_mail_operation_xfer_msg (mail_op, header,
919                                                   folder, helper->delete_source);
920         if (started) {
921                 g_signal_connect (G_OBJECT (mail_op), "progress_changed",
922                                   G_CALLBACK (on_progress_changed), helper);
923                 modest_mail_operation_queue_add (queue, mail_op);
924         } else {
925                 const GError *error;
926                 error = modest_mail_operation_get_error (mail_op);
927                 if (error)
928                         g_warning ("Error trying to transfer messages: %s\n",
929                                    error->message);
930
931                 g_slice_free (DndHelper, helper);
932         }
933
934         /* Frees */
935         g_object_unref (G_OBJECT (mail_op));
936         g_object_unref (G_OBJECT (header));
937         g_object_unref (G_OBJECT (folder));
938 }
939
940 /*
941  * This function is used by drag_data_received_cb to manage drag and
942  * drop of a folder, i.e, and drag from the folder view to the same
943  * folder view.
944  */
945 static void
946 drag_and_drop_from_folder_view (GtkTreeModel     *source_model,
947                                 GtkTreeModel     *dest_model,
948                                 GtkTreePath      *dest_row,
949                                 GtkSelectionData *selection_data,
950                                 DndHelper        *helper)
951 {
952         ModestMailOperation *mail_op;
953         const GError *error;
954         GtkTreeRowReference *source_row_reference;
955         GtkTreeIter parent_iter, iter;
956         TnyFolder *folder, *new_folder;
957         TnyFolderStore *parent_folder;
958         gboolean success = FALSE;
959
960         /* Check if the drag is possible */
961         if (!gtk_tree_path_compare (helper->source_row, dest_row))
962                 goto out;
963
964         if (!gtk_tree_drag_dest_row_drop_possible (GTK_TREE_DRAG_DEST (dest_model),
965                                                    dest_row,
966                                                    selection_data))
967                 goto out;
968
969         /* Get data */
970         gtk_tree_model_get_iter (source_model, &parent_iter, dest_row);
971         gtk_tree_model_get_iter (source_model, &iter, helper->source_row);
972         gtk_tree_model_get (source_model, &parent_iter, 
973                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, 
974                             &parent_folder, -1);
975         gtk_tree_model_get (source_model, &iter,
976                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
977                             &folder, -1);
978
979         /* Do the mail operation */
980         mail_op = modest_mail_operation_new ();
981         new_folder = modest_mail_operation_xfer_folder (mail_op, folder, parent_folder, 
982                                                         helper->delete_source);
983
984         g_object_unref (G_OBJECT (parent_folder));
985         g_object_unref (G_OBJECT (folder));
986
987         error = modest_mail_operation_get_error (mail_op);
988         if (error) {
989                 g_warning ("Error transferring folder: %s\n", error->message);
990                 g_object_unref (G_OBJECT (mail_op));
991                 goto out;
992         }
993         g_object_unref (G_OBJECT (mail_op));
994
995         /* Get a row reference to the source path because the path
996            could change after the insertion. The gtk_drag_finish() is
997            not able to delete the source because that, so we have to
998            do it manually */
999         source_row_reference = gtk_tree_row_reference_new (source_model, helper->source_row);
1000         gtk_tree_path_free (helper->source_row);
1001
1002         /* Insert the dragged row as a child of the dest row */
1003         gtk_tree_path_down (dest_row);
1004         if (gtk_tree_drag_dest_drag_data_received (GTK_TREE_DRAG_DEST (dest_model),
1005                                                    dest_row,
1006                                                    selection_data)) {
1007
1008                 GtkTreeIter iter;
1009
1010                 /* Set the newly created folder as the instance in the row */
1011                 gtk_tree_model_get_iter (dest_model, &iter, dest_row);
1012                 gtk_tree_store_set (GTK_TREE_STORE (dest_model), &iter,
1013                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, 
1014                                     new_folder, -1);
1015                 g_object_unref (G_OBJECT (new_folder));         
1016
1017                 helper->source_row = gtk_tree_row_reference_get_path (source_row_reference);
1018
1019                 success = TRUE;
1020         }
1021         gtk_tree_row_reference_free (source_row_reference);
1022
1023         /* Save and clean */
1024         save_and_clean (helper, success);       
1025
1026  out:
1027         gtk_drag_finish (helper->context, success, (success && helper->delete_source), helper->time);
1028 }
1029
1030 /*
1031  * This function receives the data set by the "drag-data-get" signal
1032  * handler. This information comes within the #GtkSelectionData. This
1033  * function will manage both the drags of folders of the treeview and
1034  * drags of headers of the header view widget.
1035  */
1036 static void 
1037 on_drag_data_received (GtkWidget *widget, 
1038                        GdkDragContext *context, 
1039                        gint x, 
1040                        gint y, 
1041                        GtkSelectionData *selection_data, 
1042                        guint target_type, 
1043                        guint time, 
1044                        gpointer data)
1045 {
1046         GtkWidget *source_widget;
1047         GtkTreeModel *model_sort, *dest_model, *source_model;
1048         GtkTreePath *source_row, *dest_row, *child_dest_row;
1049         GtkTreeViewDropPosition pos;
1050         gboolean success = FALSE, delete_source = FALSE;
1051         DndHelper *helper;
1052
1053         /* Do not allow further process */
1054         g_signal_stop_emission_by_name (widget, "drag-data-received");
1055
1056         /* Get the action */
1057         if (context->action == GDK_ACTION_MOVE)
1058                 delete_source = TRUE;
1059
1060         /* Check if the get_data failed */
1061         if (selection_data == NULL || selection_data->length < 0)
1062                 gtk_drag_finish (context, success, (success && delete_source), time);
1063
1064         /* Get the models */
1065         source_widget = gtk_drag_get_source_widget (context);
1066         gtk_tree_get_row_drag_data (selection_data,
1067                                     &source_model,
1068                                     &source_row);
1069
1070         model_sort = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
1071         /* Select the destination model */
1072         if (source_widget == widget) {
1073                 dest_model = source_model;
1074         } else {
1075                 dest_model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (model_sort));
1076         }
1077
1078         /* Get the path to the destination row. Can not call
1079            gtk_tree_view_get_drag_dest_row() because the source row
1080            is not selected anymore */
1081         gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget), x, y,
1082                                            &dest_row, &pos);
1083
1084         /* Only allow drops IN other rows */
1085         if (!dest_row || pos == GTK_TREE_VIEW_DROP_BEFORE || pos == GTK_TREE_VIEW_DROP_AFTER)
1086                 gtk_drag_finish (context, success, (success && delete_source), time);
1087
1088         /* Create the helper */
1089         helper = g_slice_new0 (DndHelper);
1090         helper->delete_source = delete_source;
1091         helper->source_widget = source_widget;
1092         helper->dest_widget = widget;
1093         helper->source_row = source_row;
1094         helper->context = context;
1095         helper->time = time;
1096
1097         /* Get path from the unsorted model */
1098         child_dest_row = 
1099                 gtk_tree_model_sort_convert_path_to_child_path (GTK_TREE_MODEL_SORT (model_sort),
1100                                                                 dest_row);
1101         gtk_tree_path_free (dest_row);
1102
1103         /* Drags from the header view */
1104         if (source_widget != widget) {
1105
1106                 drag_and_drop_from_header_view (source_model,
1107                                                 dest_model,
1108                                                 child_dest_row,
1109                                                 helper);
1110         } else {
1111
1112
1113                 drag_and_drop_from_folder_view (source_model,
1114                                                 dest_model,
1115                                                 child_dest_row,
1116                                                 selection_data, 
1117                                                 helper);
1118         }
1119         gtk_tree_path_free (child_dest_row);
1120 }
1121
1122 /*
1123  * We define a "drag-drop" signal handler because we do not want to
1124  * use the default one, because the default one always calls
1125  * gtk_drag_finish and we prefer to do it in the "drag-data-received"
1126  * signal handler, because there we have all the information available
1127  * to know if the dnd was a success or not.
1128  */
1129 static gboolean
1130 drag_drop_cb (GtkWidget      *widget,
1131               GdkDragContext *context,
1132               gint            x,
1133               gint            y,
1134               guint           time,
1135               gpointer        user_data) 
1136 {
1137         gpointer target;
1138
1139         if (!context->targets)
1140                 return FALSE;
1141
1142         /* Check if we're dragging a folder row */
1143         target = gtk_drag_dest_find_target (widget, context, NULL);
1144
1145         /* Request the data from the source. */
1146         gtk_drag_get_data(widget, context, target, time);
1147
1148     return TRUE;
1149 }
1150
1151 /*
1152  * This function deletes the data that has been dragged from its
1153  * source widget. Since is a function received by the source of the
1154  * drag, this function only deletes rows of the folder view
1155  * widget. The header view widget will need to define its own one.
1156  */
1157 static void 
1158 drag_data_delete_cb (GtkWidget      *widget,
1159                      GdkDragContext *context,
1160                      gpointer        user_data)
1161 {
1162         GtkTreePath *source_row;
1163         GtkTreeModel *model_sort, *model;
1164
1165         model_sort = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
1166         model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (model_sort));
1167         source_row = g_object_steal_data (G_OBJECT (widget), ROW_REF_DATA_NAME);
1168
1169         /* Delete the source row */
1170         gtk_tree_drag_source_drag_data_delete (GTK_TREE_DRAG_SOURCE (model),
1171                                                source_row);
1172
1173         gtk_tree_path_free (source_row);
1174 }
1175
1176 /*
1177  * This function expands a node of a tree view if it's not expanded
1178  * yet. Not sure why it needs the threads stuff, but gtk+`example code
1179  * does that, so that's why they're here.
1180  */
1181 static gint
1182 expand_row_timeout (gpointer data)
1183 {
1184         GtkTreeView *tree_view = data;
1185         GtkTreePath *dest_path = NULL;
1186         GtkTreeViewDropPosition pos;
1187         gboolean result = FALSE;
1188         
1189         GDK_THREADS_ENTER ();
1190         
1191         gtk_tree_view_get_drag_dest_row (tree_view,
1192                                          &dest_path,
1193                                          &pos);
1194         
1195         if (dest_path &&
1196             (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER ||
1197              pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)) {
1198                 gtk_tree_view_expand_row (tree_view, dest_path, FALSE);
1199                 gtk_tree_path_free (dest_path);
1200         }
1201         else {
1202                 if (dest_path)
1203                         gtk_tree_path_free (dest_path);
1204                 
1205                 result = TRUE;
1206         }
1207         
1208         GDK_THREADS_LEAVE ();
1209
1210         return result;
1211 }
1212
1213 /*
1214  * This function is called whenever the pointer is moved over a widget
1215  * while dragging some data. It installs a timeout that will expand a
1216  * node of the treeview if not expanded yet. This function also calls
1217  * gdk_drag_status in order to set the suggested action that will be
1218  * used by the "drag-data-received" signal handler to know if we
1219  * should do a move or just a copy of the data.
1220  */
1221 static gboolean
1222 on_drag_motion (GtkWidget      *widget,
1223                 GdkDragContext *context,
1224                 gint            x,
1225                 gint            y,
1226                 guint           time,
1227                 gpointer        user_data)  
1228 {
1229         GtkTreeViewDropPosition pos;
1230         GtkTreePath *dest_row;
1231         ModestFolderViewPrivate *priv;
1232         GdkDragAction suggested_action;
1233
1234         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
1235
1236         if (priv->timer_expander != 0) {
1237                 g_source_remove (priv->timer_expander);
1238                 priv->timer_expander = 0;
1239         }
1240
1241         gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
1242                                            x, y,
1243                                            &dest_row,
1244                                            &pos);
1245
1246         if (!dest_row)
1247                 return FALSE;
1248
1249         /* Expand the selected row after 1/2 second */
1250         if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), dest_row)) {
1251                 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), dest_row, pos);
1252                 priv->timer_expander = g_timeout_add (500, expand_row_timeout, widget);
1253         }
1254         gtk_tree_path_free (dest_row);
1255
1256         /* Select the desired action. By default we pick MOVE */
1257         suggested_action = GDK_ACTION_MOVE;
1258
1259         if (context->actions == GDK_ACTION_COPY)
1260             gdk_drag_status(context, GDK_ACTION_COPY, time);
1261         else if (context->actions == GDK_ACTION_MOVE)
1262             gdk_drag_status(context, GDK_ACTION_MOVE, time);
1263         else if (context->actions & suggested_action)
1264             gdk_drag_status(context, suggested_action, time);
1265         else
1266             gdk_drag_status(context, GDK_ACTION_DEFAULT, time);
1267
1268         return TRUE;
1269 }
1270
1271
1272 /* Folder view drag types */
1273 const GtkTargetEntry folder_view_drag_types[] =
1274 {
1275         { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, FOLDER_ROW },
1276         { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_APP, HEADER_ROW }
1277 };
1278
1279 /*
1280  * This function sets the treeview as a source and a target for dnd
1281  * events. It also connects all the requirede signals.
1282  */
1283 static void
1284 setup_drag_and_drop (GtkTreeView *self)
1285 {
1286         /* Set up the folder view as a dnd destination. Set only the
1287            highlight flag, otherwise gtk will have a different
1288            behaviour */
1289         gtk_drag_dest_set (GTK_WIDGET (self),
1290                            GTK_DEST_DEFAULT_HIGHLIGHT,
1291                            folder_view_drag_types,
1292                            G_N_ELEMENTS (folder_view_drag_types),
1293                            GDK_ACTION_MOVE | GDK_ACTION_COPY);
1294
1295         gtk_signal_connect(GTK_OBJECT (self),
1296                            "drag_data_received",
1297                            GTK_SIGNAL_FUNC(on_drag_data_received),
1298                            NULL);
1299
1300
1301         /* Set up the treeview as a dnd source */
1302         gtk_drag_source_set (GTK_WIDGET (self),
1303                              GDK_BUTTON1_MASK,
1304                              folder_view_drag_types,
1305                              G_N_ELEMENTS (folder_view_drag_types),
1306                              GDK_ACTION_MOVE | GDK_ACTION_COPY);
1307
1308         gtk_signal_connect(GTK_OBJECT (self),
1309                            "drag_data_delete",
1310                            GTK_SIGNAL_FUNC(drag_data_delete_cb),
1311                            NULL);
1312
1313         gtk_signal_connect(GTK_OBJECT (self),
1314                            "drag_motion",
1315                            GTK_SIGNAL_FUNC(on_drag_motion),
1316                            NULL);
1317
1318
1319         gtk_signal_connect(GTK_OBJECT (self),
1320                            "drag_data_get",
1321                            GTK_SIGNAL_FUNC(on_drag_data_get),
1322                            NULL);
1323
1324         gtk_signal_connect(GTK_OBJECT (self),
1325                            "drag_drop",
1326                            GTK_SIGNAL_FUNC(drag_drop_cb),
1327                            NULL);
1328 }