2d544037513b31fa57fcfaed7b3fd012f6f6f678
[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 = NULL;
1048         TnyHeader *header = NULL;
1049         TnyFolder *folder = NULL;
1050         ModestMailOperation *mail_op = NULL;
1051         GtkTreeIter source_iter, dest_iter;
1052
1053         g_return_if_fail (GTK_IS_TREE_MODEL(source_model));
1054         g_return_if_fail (GTK_IS_TREE_MODEL(dest_model));
1055         g_return_if_fail (dest_row);
1056         g_return_if_fail (helper);
1057         
1058         /* Get header */
1059         gtk_tree_model_get_iter (source_model, &source_iter, helper->source_row);
1060         gtk_tree_model_get (source_model, &source_iter, 
1061                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, 
1062                             &header, -1);
1063         if (!TNY_IS_HEADER(header)) {
1064                 g_warning ("BUG: %s could not get a valid header", __FUNCTION__);
1065                 goto cleanup;
1066         }
1067         
1068         /* Get Folder */
1069         gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
1070         gtk_tree_model_get (dest_model, &dest_iter, 
1071                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, 
1072                             &folder, -1);
1073
1074         if (!TNY_IS_FOLDER(folder)) {
1075                 g_warning ("BUG: %s could not get a valid folder", __FUNCTION__);
1076                 goto cleanup;
1077         }
1078
1079         /* Transfer message */
1080         mail_op = modest_mail_operation_new (MODEST_MAIL_OPERATION_TYPE_RECEIVE, NULL);
1081         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1082                                          mail_op);
1083         g_signal_connect (G_OBJECT (mail_op), "progress-changed",
1084                           G_CALLBACK (on_progress_changed), helper);
1085
1086         headers = tny_simple_list_new ();
1087         tny_list_append (headers, G_OBJECT (header));
1088         modest_mail_operation_xfer_msgs (mail_op, 
1089                                          headers, 
1090                                          folder, 
1091                                          helper->delete_source, 
1092                                          NULL, NULL);
1093         
1094         /* Frees */
1095 cleanup:
1096         if (G_IS_OBJECT(mail_op))
1097                 g_object_unref (G_OBJECT (mail_op));
1098         if (G_IS_OBJECT(header))
1099                 g_object_unref (G_OBJECT (header));
1100         if (G_IS_OBJECT(folder))
1101                 g_object_unref (G_OBJECT (folder));
1102         if (G_IS_OBJECT(headers))
1103                 g_object_unref (headers);
1104 }
1105
1106 /*
1107  * This function is used by drag_data_received_cb to manage drag and
1108  * drop of a folder, i.e, and drag from the folder view to the same
1109  * folder view.
1110  */
1111 static void
1112 drag_and_drop_from_folder_view (GtkTreeModel     *source_model,
1113                                 GtkTreeModel     *dest_model,
1114                                 GtkTreePath      *dest_row,
1115                                 GtkSelectionData *selection_data,
1116                                 DndHelper        *helper)
1117 {
1118         ModestMailOperation *mail_op;
1119         GtkTreeIter parent_iter, iter;
1120         TnyFolderStore *parent_folder;
1121         TnyFolder *folder;
1122
1123         /* Check if the drag is possible */
1124 /*      if (!gtk_tree_path_compare (helper->source_row, dest_row) || */
1125 /*          !gtk_tree_drag_dest_row_drop_possible (GTK_TREE_DRAG_DEST (dest_model), */
1126 /*                                                 dest_row, */
1127 /*                                                 selection_data)) { */
1128         if (!gtk_tree_path_compare (helper->source_row, dest_row)) {
1129
1130                 gtk_drag_finish (helper->context, FALSE, FALSE, helper->time);
1131                 gtk_tree_path_free (helper->source_row);        
1132                 g_slice_free (DndHelper, helper);
1133                 return;
1134         }
1135
1136         /* Get data */
1137         gtk_tree_model_get_iter (source_model, &parent_iter, dest_row);
1138         gtk_tree_model_get_iter (source_model, &iter, helper->source_row);
1139         gtk_tree_model_get (source_model, &parent_iter, 
1140                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, 
1141                             &parent_folder, -1);
1142         gtk_tree_model_get (source_model, &iter,
1143                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
1144                             &folder, -1);
1145
1146         /* Do the mail operation */
1147         mail_op = modest_mail_operation_new_with_error_handling (MODEST_MAIL_OPERATION_TYPE_RECEIVE, 
1148                                                                  NULL,
1149                                                                  modest_ui_actions_move_folder_error_handler,
1150                                                                  NULL);
1151         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (), 
1152                                          mail_op);
1153         g_signal_connect (G_OBJECT (mail_op), "progress-changed",
1154                           G_CALLBACK (on_progress_changed), helper);
1155
1156         modest_mail_operation_xfer_folder (mail_op, 
1157                                            folder, 
1158                                            parent_folder,
1159                                            helper->delete_source);
1160         
1161         /* Frees */
1162         g_object_unref (G_OBJECT (parent_folder));
1163         g_object_unref (G_OBJECT (folder));
1164         g_object_unref (G_OBJECT (mail_op));
1165 }
1166
1167 /*
1168  * This function receives the data set by the "drag-data-get" signal
1169  * handler. This information comes within the #GtkSelectionData. This
1170  * function will manage both the drags of folders of the treeview and
1171  * drags of headers of the header view widget.
1172  */
1173 static void 
1174 on_drag_data_received (GtkWidget *widget, 
1175                        GdkDragContext *context, 
1176                        gint x, 
1177                        gint y, 
1178                        GtkSelectionData *selection_data, 
1179                        guint target_type, 
1180                        guint time, 
1181                        gpointer data)
1182 {
1183         GtkWidget *source_widget;
1184         GtkTreeModel *dest_model, *source_model;
1185         GtkTreePath *source_row, *dest_row;
1186         GtkTreeViewDropPosition pos;
1187         gboolean success = FALSE, delete_source = FALSE;
1188         DndHelper *helper = NULL; 
1189
1190         /* Do not allow further process */
1191         g_signal_stop_emission_by_name (widget, "drag-data-received");
1192         source_widget = gtk_drag_get_source_widget (context);
1193
1194         /* Get the action */
1195         if (context->action == GDK_ACTION_MOVE) {
1196                 delete_source = TRUE;
1197
1198                 /* Notify that there is no folder selected. We need to
1199                    do this in order to update the headers view (and
1200                    its monitors, because when moving, the old folder
1201                    won't longer exist. We can not wait for the end of
1202                    the operation, because the operation won't start if
1203                    the folder is in use */
1204                 if (source_widget == widget)
1205                         g_signal_emit (G_OBJECT (widget), 
1206                                        signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0, NULL, TRUE);
1207         }
1208
1209         /* Check if the get_data failed */
1210         if (selection_data == NULL || selection_data->length < 0)
1211                 gtk_drag_finish (context, success, FALSE, time);
1212
1213         /* Get the models */
1214         gtk_tree_get_row_drag_data (selection_data,
1215                                     &source_model,
1216                                     &source_row);
1217
1218         /* Select the destination model */
1219         if (source_widget == widget) {
1220                 dest_model = source_model;
1221         } else {
1222                 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
1223         }
1224
1225         /* Get the path to the destination row. Can not call
1226            gtk_tree_view_get_drag_dest_row() because the source row
1227            is not selected anymore */
1228         gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget), x, y,
1229                                            &dest_row, &pos);
1230
1231         /* Only allow drops IN other rows */
1232         if (!dest_row || pos == GTK_TREE_VIEW_DROP_BEFORE || pos == GTK_TREE_VIEW_DROP_AFTER)
1233                 gtk_drag_finish (context, success, FALSE, time);
1234
1235         /* Create the helper */
1236         helper = g_slice_new0 (DndHelper);
1237         helper->delete_source = delete_source;
1238         helper->source_row = gtk_tree_path_copy (source_row);
1239         helper->context = context;
1240         helper->time = time;
1241
1242         /* Drags from the header view */
1243         if (source_widget != widget) {
1244
1245                 drag_and_drop_from_header_view (source_model,
1246                                                 dest_model,
1247                                                 dest_row,
1248                                                 helper);
1249         } else {
1250
1251
1252                 drag_and_drop_from_folder_view (source_model,
1253                                                 dest_model,
1254                                                 dest_row,
1255                                                 selection_data, 
1256                                                 helper);
1257         }
1258
1259         /* Frees */
1260         gtk_tree_path_free (source_row);
1261         gtk_tree_path_free (dest_row);
1262 }
1263
1264 /*
1265  * We define a "drag-drop" signal handler because we do not want to
1266  * use the default one, because the default one always calls
1267  * gtk_drag_finish and we prefer to do it in the "drag-data-received"
1268  * signal handler, because there we have all the information available
1269  * to know if the dnd was a success or not.
1270  */
1271 static gboolean
1272 drag_drop_cb (GtkWidget      *widget,
1273               GdkDragContext *context,
1274               gint            x,
1275               gint            y,
1276               guint           time,
1277               gpointer        user_data) 
1278 {
1279         gpointer target;
1280
1281         if (!context->targets)
1282                 return FALSE;
1283
1284         /* Check if we're dragging a folder row */
1285         target = gtk_drag_dest_find_target (widget, context, NULL);
1286
1287         /* Request the data from the source. */
1288         gtk_drag_get_data(widget, context, target, time);
1289
1290     return TRUE;
1291 }
1292
1293 /*
1294  * This function expands a node of a tree view if it's not expanded
1295  * yet. Not sure why it needs the threads stuff, but gtk+`example code
1296  * does that, so that's why they're here.
1297  */
1298 static gint
1299 expand_row_timeout (gpointer data)
1300 {
1301         GtkTreeView *tree_view = data;
1302         GtkTreePath *dest_path = NULL;
1303         GtkTreeViewDropPosition pos;
1304         gboolean result = FALSE;
1305         
1306         GDK_THREADS_ENTER ();
1307         
1308         gtk_tree_view_get_drag_dest_row (tree_view,
1309                                          &dest_path,
1310                                          &pos);
1311         
1312         if (dest_path &&
1313             (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER ||
1314              pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)) {
1315                 gtk_tree_view_expand_row (tree_view, dest_path, FALSE);
1316                 gtk_tree_path_free (dest_path);
1317         }
1318         else {
1319                 if (dest_path)
1320                         gtk_tree_path_free (dest_path);
1321                 
1322                 result = TRUE;
1323         }
1324         
1325         GDK_THREADS_LEAVE ();
1326
1327         return result;
1328 }
1329
1330 /*
1331  * This function is called whenever the pointer is moved over a widget
1332  * while dragging some data. It installs a timeout that will expand a
1333  * node of the treeview if not expanded yet. This function also calls
1334  * gdk_drag_status in order to set the suggested action that will be
1335  * used by the "drag-data-received" signal handler to know if we
1336  * should do a move or just a copy of the data.
1337  */
1338 static gboolean
1339 on_drag_motion (GtkWidget      *widget,
1340                 GdkDragContext *context,
1341                 gint            x,
1342                 gint            y,
1343                 guint           time,
1344                 gpointer        user_data)  
1345 {
1346         GtkTreeViewDropPosition pos;
1347         GtkTreePath *dest_row;
1348         ModestFolderViewPrivate *priv;
1349         GdkDragAction suggested_action;
1350         gboolean valid_location = FALSE;
1351
1352         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
1353
1354         if (priv->timer_expander != 0) {
1355                 g_source_remove (priv->timer_expander);
1356                 priv->timer_expander = 0;
1357         }
1358
1359         gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
1360                                            x, y,
1361                                            &dest_row,
1362                                            &pos);
1363
1364         /* Do not allow drops between folders */
1365         if (!dest_row ||
1366             pos == GTK_TREE_VIEW_DROP_BEFORE ||
1367             pos == GTK_TREE_VIEW_DROP_AFTER) {
1368                 gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW (widget), NULL, 0);
1369                 gdk_drag_status(context, 0, time);
1370                 valid_location = FALSE;
1371                 goto out;
1372         } else {
1373                 valid_location = TRUE;
1374         }
1375
1376         /* Expand the selected row after 1/2 second */
1377         if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), dest_row)) {
1378                 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), dest_row, pos);
1379                 priv->timer_expander = g_timeout_add (500, expand_row_timeout, widget);
1380         }
1381
1382         /* Select the desired action. By default we pick MOVE */
1383         suggested_action = GDK_ACTION_MOVE;
1384
1385         if (context->actions == GDK_ACTION_COPY)
1386             gdk_drag_status(context, GDK_ACTION_COPY, time);
1387         else if (context->actions == GDK_ACTION_MOVE)
1388             gdk_drag_status(context, GDK_ACTION_MOVE, time);
1389         else if (context->actions & suggested_action)
1390             gdk_drag_status(context, suggested_action, time);
1391         else
1392             gdk_drag_status(context, GDK_ACTION_DEFAULT, time);
1393
1394  out:
1395         if (dest_row)
1396                 gtk_tree_path_free (dest_row);
1397         g_signal_stop_emission_by_name (widget, "drag-motion");
1398         return valid_location;
1399 }
1400
1401
1402 /* Folder view drag types */
1403 const GtkTargetEntry folder_view_drag_types[] =
1404 {
1405         { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, MODEST_FOLDER_ROW },
1406         { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_APP,    MODEST_HEADER_ROW }
1407 };
1408
1409 /*
1410  * This function sets the treeview as a source and a target for dnd
1411  * events. It also connects all the requirede signals.
1412  */
1413 static void
1414 setup_drag_and_drop (GtkTreeView *self)
1415 {
1416         /* Set up the folder view as a dnd destination. Set only the
1417            highlight flag, otherwise gtk will have a different
1418            behaviour */
1419         gtk_drag_dest_set (GTK_WIDGET (self),
1420                            GTK_DEST_DEFAULT_HIGHLIGHT,
1421                            folder_view_drag_types,
1422                            G_N_ELEMENTS (folder_view_drag_types),
1423                            GDK_ACTION_MOVE | GDK_ACTION_COPY);
1424
1425         g_signal_connect (G_OBJECT (self),
1426                           "drag_data_received",
1427                           G_CALLBACK (on_drag_data_received),
1428                           NULL);
1429
1430
1431         /* Set up the treeview as a dnd source */
1432         gtk_drag_source_set (GTK_WIDGET (self),
1433                              GDK_BUTTON1_MASK,
1434                              folder_view_drag_types,
1435                              G_N_ELEMENTS (folder_view_drag_types),
1436                              GDK_ACTION_MOVE | GDK_ACTION_COPY);
1437
1438         g_signal_connect (G_OBJECT (self),
1439                           "drag_motion",
1440                           G_CALLBACK (on_drag_motion),
1441                           NULL);
1442         
1443         g_signal_connect (G_OBJECT (self),
1444                           "drag_data_get",
1445                           G_CALLBACK (on_drag_data_get),
1446                           NULL);
1447
1448         g_signal_connect (G_OBJECT (self),
1449                           "drag_drop",
1450                           G_CALLBACK (drag_drop_cb),
1451                           NULL);
1452 }
1453
1454 /*
1455  * This function manages the navigation through the folders using the
1456  * keyboard or the hardware keys in the device
1457  */
1458 static gboolean
1459 on_key_pressed (GtkWidget *self,
1460                 GdkEventKey *event,
1461                 gpointer user_data)
1462 {
1463         GtkTreeSelection *selection;
1464         GtkTreeIter iter;
1465         GtkTreeModel *model;
1466         gboolean retval = FALSE;
1467
1468         /* Up and Down are automatically managed by the treeview */
1469         if (event->keyval == GDK_Return) {
1470                 /* Expand/Collapse the selected row */
1471                 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1472                 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
1473                         GtkTreePath *path;
1474
1475                         path = gtk_tree_model_get_path (model, &iter);
1476
1477                         if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (self), path))
1478                                 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1479                         else
1480                                 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1481                         gtk_tree_path_free (path);
1482                 }
1483                 /* No further processing */
1484                 retval = TRUE;
1485         }
1486
1487         return retval;
1488 }
1489
1490 /*
1491  * We listen to the changes in the local folder account name key,
1492  * because we want to show the right name in the view. The local
1493  * folder account name corresponds to the device name in the Maemo
1494  * version. We do this because we do not want to query gconf on each
1495  * tree view refresh. It's better to cache it and change whenever
1496  * necessary.
1497  */
1498 static void 
1499 on_configuration_key_changed (ModestConf* conf, 
1500                               const gchar *key, 
1501                               ModestConfEvent event, 
1502                               ModestFolderView *self)
1503 {
1504         ModestFolderViewPrivate *priv;
1505
1506         if (!key)
1507                 return;
1508
1509         g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
1510         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1511
1512         if (!strcmp (key, MODEST_CONF_DEVICE_NAME)) {
1513                 g_free (priv->local_account_name);
1514
1515                 if (event == MODEST_CONF_EVENT_KEY_UNSET)
1516                         priv->local_account_name = g_strdup (MODEST_LOCAL_FOLDERS_DEFAULT_DISPLAY_NAME);
1517                 else
1518                         priv->local_account_name = modest_conf_get_string (modest_runtime_get_conf(),
1519                                                                            MODEST_CONF_DEVICE_NAME, NULL);
1520
1521                 /* Force a redraw */
1522 #if GTK_CHECK_VERSION(2, 8, 0) /* gtk_tree_view_column_queue_resize is only available in GTK+ 2.8 */
1523                 GtkTreeViewColumn * tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self), 
1524                                                                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN);
1525                 gtk_tree_view_column_queue_resize (tree_column);
1526 #endif
1527         }
1528 }
1529
1530 void 
1531 modest_folder_view_set_style (ModestFolderView *self,
1532                               ModestFolderViewStyle style)
1533 {
1534         ModestFolderViewPrivate *priv;
1535
1536         g_return_if_fail (self);
1537         
1538         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1539
1540         priv->style = style;
1541 }
1542
1543 void
1544 modest_folder_view_set_account_id_of_visible_server_account (ModestFolderView *self,
1545                                                              const gchar *account_id)
1546 {
1547         ModestFolderViewPrivate *priv;
1548         GtkTreeModel *model;
1549
1550         g_return_if_fail (self);
1551         
1552         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1553
1554         /* This will be used by the filter_row callback,
1555          * to decided which rows to show: */
1556         if (priv->visible_account_id)
1557                 g_free (priv->visible_account_id);
1558         priv->visible_account_id = g_strdup (account_id);
1559
1560         /* Refilter */
1561         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1562         if (GTK_IS_TREE_MODEL_FILTER (model))
1563                 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
1564 }
1565
1566 const gchar *
1567 modest_folder_view_get_account_id_of_visible_server_account (ModestFolderView *self)
1568 {
1569         ModestFolderViewPrivate *priv;
1570
1571         g_return_val_if_fail (self, NULL);
1572         
1573         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1574
1575         return (const gchar *) priv->visible_account_id;
1576 }
1577
1578 static gboolean
1579 find_inbox_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *inbox_iter)
1580 {
1581         do {
1582                 GtkTreeIter child;
1583                 gint type;
1584
1585                 gtk_tree_model_get (model, iter, 
1586                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, 
1587                                     &type, -1);
1588
1589                 if (type == TNY_FOLDER_TYPE_INBOX) {
1590                         *inbox_iter = *iter;
1591                         return TRUE;
1592                 }
1593
1594                 if (gtk_tree_model_iter_children (model, &child, iter)) {
1595                         if (find_inbox_iter (model, &child, inbox_iter))
1596                                 return TRUE;
1597                 }
1598
1599         } while (gtk_tree_model_iter_next (model, iter));
1600
1601         return FALSE;
1602 }
1603
1604
1605
1606 void 
1607 modest_folder_view_select_first_inbox_or_local (ModestFolderView *self)
1608 {
1609         GtkTreeModel *model;
1610         GtkTreeIter iter, inbox_iter;
1611         GtkTreeSelection *sel;
1612
1613         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1614         if (!model)
1615                 return;
1616
1617         expand_root_items (self);
1618         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1619
1620         gtk_tree_model_get_iter_first (model, &iter);
1621         if (find_inbox_iter (model, &iter, &inbox_iter))
1622                 gtk_tree_selection_select_iter (sel, &inbox_iter);
1623         else {
1624                 gtk_tree_model_get_iter_first (model, &iter);
1625                 gtk_tree_selection_select_iter (sel, &iter);
1626         }
1627 }