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