Remove unnecessary check and add a 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_MMC_ACCOUNT_ID)) { 
705                                 /* Show only the visible account id */
706                                 ModestFolderViewPrivate *priv = 
707                                         MODEST_FOLDER_VIEW_GET_PRIVATE (data);
708                                 if (priv->visible_account_id && strcmp (account_id, priv->visible_account_id))
709                                         retval = FALSE;
710                         }
711                 }
712         }
713         
714         /* The virtual local-folders folder store is also shown by default. */
715
716         g_object_unref (instance);
717
718         return retval;
719 }
720
721 /*
722 static void on_tnylist_accounts_debug_print(gpointer data,  gpointer user_data)
723 {
724         TnyAccount* account = TNY_ACCOUNT(data);
725         const gchar *prefix = (const gchar*)(user_data);
726         
727         printf("%s account id=%s\n", prefix, tny_account_get_id (account));
728 }
729 */
730
731 static void
732 add_account_folders_to_merged_folder (TnyAccount *account, TnyMergeFolder* merge_folder)
733 {
734         const gchar* account_id = tny_account_get_id (account);
735         const gboolean is_actual_local_folders_account = account_id && 
736                 (strcmp (account_id, MODEST_ACTUAL_LOCAL_FOLDERS_ACCOUNT_ID) == 0);
737                 
738         TnyList *list_outbox_folders = tny_simple_list_new ();
739         tny_folder_store_get_folders (TNY_FOLDER_STORE (account), 
740                 list_outbox_folders, NULL, NULL);
741                 
742         TnyIterator*  iter =  tny_list_create_iterator (list_outbox_folders);
743         while (!tny_iterator_is_done (iter))
744         {
745                 TnyFolder *folder = TNY_FOLDER (tny_iterator_get_current (iter));
746                 
747                 if (folder) {
748                         gboolean add = TRUE;
749                         /* TODO: Do not add outboxes that are inside local-folders/, 
750                          * because these are just left-over from earlier Modest versions 
751                          * that put the outbox there: */
752                         if (is_actual_local_folders_account) {
753                                 const TnyFolderType type = modest_tny_folder_get_local_folder_type (folder);
754                                 if (type == TNY_FOLDER_TYPE_OUTBOX) {
755                                         add = FALSE;
756                                 }
757                         }
758                         
759                         if (add)
760                                 tny_merge_folder_add_folder (merge_folder, folder);
761                                 
762                         g_object_unref (folder);        
763                 }
764                 
765                 tny_iterator_next (iter);
766         }
767         
768         g_object_unref (list_outbox_folders);
769 }
770
771
772 static void
773 add_account_folders_to_simple_folder_store (TnyAccount *account, ModestTnySimpleFolderStore* store)
774 {
775         g_return_if_fail (account);
776         g_return_if_fail (store);
777                 
778         TnyList *list_outbox_folders = tny_simple_list_new ();
779         tny_folder_store_get_folders (TNY_FOLDER_STORE (account), 
780                 list_outbox_folders, NULL, NULL);
781         
782         /* Special handling for the .modest/local-folders account,
783          * to avoid adding unwanted folders.
784          * We cannot prevent them from being in the TnyAccount without 
785          * changing the libtinymail-camel. */
786         const gchar* account_id = tny_account_get_id (account);
787         const gboolean is_actual_local_folders_account = account_id && 
788                 (strcmp (account_id, MODEST_ACTUAL_LOCAL_FOLDERS_ACCOUNT_ID) == 0);
789         
790         TnyIterator*  iter =  tny_list_create_iterator (list_outbox_folders);
791         while (!tny_iterator_is_done (iter))
792         {
793                 TnyFolder *folder = TNY_FOLDER (tny_iterator_get_current (iter));
794                 
795                 if (folder) {
796                         gboolean add = TRUE;
797                         /* TODO: Do not add outboxes that are inside local-folders/, 
798                          * because these are just left-over from earlier Modest versions 
799                          * that put the outbox there: */
800                         if (is_actual_local_folders_account) {
801                                 const TnyFolderType type = modest_tny_folder_get_local_folder_type (folder);
802                                 if (type == TNY_FOLDER_TYPE_OUTBOX) {
803                                         add = FALSE;
804                                 }
805                         }
806                         
807                         if (add)
808                                 modest_tny_simple_folder_store_add_folder (store, folder);
809                                 
810                         g_object_unref (folder);        
811                 }
812                 
813                 tny_iterator_next (iter);
814         }
815         
816         g_object_unref (list_outbox_folders);
817 }
818
819 static gboolean
820 update_model (ModestFolderView *self, ModestTnyAccountStore *account_store)
821 {
822         ModestFolderViewPrivate *priv;
823
824         GtkTreeModel     *model;
825
826         g_return_val_if_fail (account_store, FALSE);
827
828         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE(self);
829         
830         /* Notify that there is no folder selected */
831         g_signal_emit (G_OBJECT(self), 
832                        signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
833                        NULL, TRUE);
834         
835         /* FIXME: the local accounts are not shown when the query
836            selects only the subscribed folders. */
837 /*      model        = tny_gtk_folder_store_tree_model_new (TRUE, priv->query); */
838         model        = tny_gtk_folder_store_tree_model_new (TRUE, NULL);
839         
840         /* Deal with the model via its TnyList Interface,
841          * filling the TnyList via a get_accounts() call: */
842         TnyList *model_as_list = TNY_LIST(model);
843
844         /* Create a virtual local-folders folder store, 
845          * containing the real local folders and the (merged) various per-account 
846          * outbox folders:
847          */
848         ModestTnySimpleFolderStore *store = modest_tny_simple_folder_store_new ();
849
850         /* Get the accounts: */
851         TnyList *account_list = tny_simple_list_new ();
852         tny_account_store_get_accounts (TNY_ACCOUNT_STORE(account_store),
853                                         account_list,
854                                         TNY_ACCOUNT_STORE_STORE_ACCOUNTS);
855         TnyIterator* iter =  tny_list_create_iterator (account_list);
856         
857         /* All per-account outbox folders are merged into one folders
858          * so that they appear as one outbox to the user: */
859         TnyMergeFolder *merged_outbox = TNY_MERGE_FOLDER (tny_merge_folder_new());
860         
861         while (!tny_iterator_is_done (iter))
862         {
863                 GObject *cur = tny_iterator_get_current (iter);
864                 TnyAccount *account = TNY_ACCOUNT (cur);
865                 if (account) {
866                         /* Add both outbox account and local-folders account folders
867                          * to our one combined account:
868                          */
869                         if (MODEST_IS_TNY_OUTBOX_ACCOUNT (account)) {
870                                 /* Add the folder to the merged folder.
871                                  * We will add it later to the virtual local-folders store: */
872                                 add_account_folders_to_merged_folder (account, merged_outbox);
873                         } else {
874                                 const gchar *account_id = tny_account_get_id (account);
875                                 if (account_id && !strcmp (account_id, MODEST_ACTUAL_LOCAL_FOLDERS_ACCOUNT_ID)) {
876                                         /* Add the folders to the virtual local-folders store: */
877                                         add_account_folders_to_simple_folder_store (account, store);
878                                 }
879                                 else {
880                                         /* Just add the account: */
881                                         tny_list_append (model_as_list, G_OBJECT (account));
882                                 }
883                         }
884                 }
885            
886                 g_object_unref (cur);
887                 tny_iterator_next (iter);
888         }
889         
890         /* Add the merged outbox folder to the virtual local-folders store: */
891         modest_tny_simple_folder_store_add_folder (store, TNY_FOLDER(merged_outbox));
892         g_object_unref (merged_outbox);
893         merged_outbox = NULL;
894         
895         /* Add the virtual local-folders store to the model: */
896         tny_list_append (model_as_list, G_OBJECT (store));
897         
898         
899         g_object_unref (account_list);
900         account_list = NULL;
901         
902         g_object_unref (model_as_list);
903         model_as_list = NULL;   
904                 
905         /* tny_list_foreach (account_list, on_tnylist_accounts_debug_print, "update_model: "); */
906                                                      
907         GtkTreeModel *filter_model = NULL, *sortable = NULL;
908
909         sortable = gtk_tree_model_sort_new_with_model (model);
910         gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
911                                               TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, 
912                                               GTK_SORT_ASCENDING);
913         gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
914                                          TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
915                                          cmp_rows, NULL, NULL);
916
917         /* Create filter model */
918         if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE) {
919                 filter_model = gtk_tree_model_filter_new (sortable, NULL);
920                 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
921                                                         filter_row,
922                                                         self,
923                                                         NULL);
924         }
925
926         /* Set new model */
927         gtk_tree_view_set_model (GTK_TREE_VIEW(self), 
928                                  (filter_model) ? filter_model : sortable);
929         expand_root_items (self); /* expand all account folders */
930         
931         return TRUE;
932 }
933
934
935 static void
936 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
937 {
938         GtkTreeModel            *model;
939         TnyFolderStore          *folder = NULL;
940         GtkTreeIter             iter;
941         ModestFolderView        *tree_view;
942         ModestFolderViewPrivate *priv;
943         gint                    type;
944
945         g_return_if_fail (sel);
946         g_return_if_fail (user_data);
947         
948         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
949         priv->cur_selection = sel;
950         
951         /* folder was _un_selected if true */
952         if (!gtk_tree_selection_get_selected (sel, &model, &iter)) {
953                 if (priv->cur_folder_store)
954                         g_object_unref (priv->cur_folder_store);
955                 priv->cur_folder_store = NULL;
956
957                 /* Notify the display name observers */
958                 g_signal_emit (G_OBJECT(user_data),
959                                signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
960                                NULL);
961                 return;
962         }
963
964         tree_view = MODEST_FOLDER_VIEW (user_data);
965
966         gtk_tree_model_get (model, &iter,
967                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
968                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
969                             -1);
970
971         /* If the folder is the same do not notify */
972         if (priv->cur_folder_store == folder) {
973                 g_object_unref (folder);
974                 return;
975         }
976         
977         /* Current folder was unselected */
978         if (priv->cur_folder_store) {
979                 g_signal_emit (G_OBJECT(tree_view), signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
980                                priv->cur_folder_store, FALSE);
981                 g_object_unref (priv->cur_folder_store);
982         }
983
984         /* New current references */
985         priv->cur_folder_store = folder;
986
987         /* New folder has been selected */
988         g_signal_emit (G_OBJECT(tree_view),
989                        signals[FOLDER_SELECTION_CHANGED_SIGNAL],
990                        0, folder, TRUE);
991 }
992
993 TnyFolderStore *
994 modest_folder_view_get_selected (ModestFolderView *self)
995 {
996         ModestFolderViewPrivate *priv;
997
998         g_return_val_if_fail (self, NULL);
999         
1000         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1001         if (priv->cur_folder_store)
1002                 g_object_ref (priv->cur_folder_store);
1003
1004         return priv->cur_folder_store;
1005 }
1006
1007 static gint
1008 get_cmp_rows_type_pos (GObject *folder)
1009 {
1010         /* Remote accounts -> Local account -> MMC account .*/
1011         /* 0, 1, 2 */
1012         
1013         if (TNY_IS_FOLDER_STORE (folder) && 
1014                 modest_tny_folder_store_is_virtual_local_folders (
1015                         TNY_FOLDER_STORE (folder))) {
1016                 return 1;
1017         } else if (TNY_IS_ACCOUNT (folder)) {
1018                 TnyAccount *account = TNY_ACCOUNT (folder);
1019                 const gchar *account_id = tny_account_get_id (account);
1020                 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
1021                         return 2;
1022                 else
1023                         return 0;
1024         }
1025         else {
1026                 printf ("DEBUG: %s: unexpected type.\n", __FUNCTION__);
1027                 return -1; /* Should never happen */
1028         }
1029 }
1030
1031 /*
1032  * This function orders the mail accounts according to these rules:
1033  * 1st - remote accounts
1034  * 2nd - local account
1035  * 3rd - MMC account
1036  */
1037 static gint
1038 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1039           gpointer user_data)
1040 {
1041         gint cmp;
1042         gchar         *name1, *name2;
1043         TnyFolderType type;
1044         GObject *folder1 = NULL;
1045         GObject *folder2 = NULL;
1046
1047         gtk_tree_model_get (tree_model, iter1,
1048                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name1,
1049                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
1050                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder1,
1051                             -1);
1052         gtk_tree_model_get (tree_model, iter2,
1053                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name2,
1054                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder2,
1055                             -1);
1056
1057         if (type == TNY_FOLDER_TYPE_ROOT) {
1058                 /* Compare the types, so that 
1059                  * Remote accounts -> Local account -> MMC account .*/
1060                 const gint pos1 = get_cmp_rows_type_pos (folder1);
1061                 const gint pos2 = get_cmp_rows_type_pos (folder2);
1062                 /* printf ("DEBUG: %s:\n  type1=%s, pos1=%d\n  type2=%s, pos2=%d\n", 
1063                         __FUNCTION__, G_OBJECT_TYPE_NAME(folder1), pos1, G_OBJECT_TYPE_NAME(folder2), pos2); */
1064                 if (pos1 <  pos2)
1065                         cmp = -1;
1066                 else if (pos1 > pos2)
1067                         cmp = 1;
1068                 else {
1069                         /* Compare items of the same type: */
1070                         
1071                         TnyAccount *account1 = NULL;
1072                         if (TNY_IS_ACCOUNT (folder1))
1073                                 account1 = TNY_ACCOUNT (folder1);
1074                                 
1075                         TnyAccount *account2 = NULL;
1076                         if (TNY_IS_ACCOUNT (folder2))
1077                                 account2 = TNY_ACCOUNT (folder2);
1078                                 
1079                         const gchar *account_id = account1 ? tny_account_get_id (account1) : NULL;
1080                         const gchar *account_id2 = account2 ? tny_account_get_id (account2) : NULL;
1081         
1082                         if (!account_id && !account_id2)
1083                                 return 0;
1084                         else if (!account_id)
1085                                 return -1;
1086                         else if (!account_id2)
1087                                 return +1;
1088                         else if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
1089                                 cmp = +1;
1090                         else
1091                                 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
1092                 }
1093         } else {
1094                 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
1095         }
1096         
1097         if (folder1)
1098                 g_object_unref(G_OBJECT(folder1));
1099         if (folder2)
1100                 g_object_unref(G_OBJECT(folder2));
1101
1102         g_free (name1);
1103         g_free (name2);
1104
1105         return cmp;     
1106 }
1107
1108 /*****************************************************************************/
1109 /*                        DRAG and DROP stuff                                */
1110 /*****************************************************************************/
1111
1112 /*
1113  * This function fills the #GtkSelectionData with the row and the
1114  * model that has been dragged. It's called when this widget is a
1115  * source for dnd after the event drop happened
1116  */
1117 static void
1118 on_drag_data_get (GtkWidget *widget, 
1119                   GdkDragContext *context, 
1120                   GtkSelectionData *selection_data, 
1121                   guint info, 
1122                   guint time, 
1123                   gpointer data)
1124 {
1125         GtkTreeSelection *selection;
1126         GtkTreeModel *model;
1127         GtkTreeIter iter;
1128         GtkTreePath *source_row;
1129
1130         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1131         gtk_tree_selection_get_selected (selection, &model, &iter);
1132         source_row = gtk_tree_model_get_path (model, &iter);
1133
1134         gtk_tree_set_row_drag_data (selection_data,
1135                                     model,
1136                                     source_row);
1137
1138         gtk_tree_path_free (source_row);
1139 }
1140
1141 typedef struct _DndHelper {
1142         gboolean delete_source;
1143         GtkTreePath *source_row;
1144         GdkDragContext *context;
1145         guint time;
1146 } DndHelper;
1147
1148
1149 /*
1150  * This function is the callback of the
1151  * modest_mail_operation_xfer_msgs () and
1152  * modest_mail_operation_xfer_folder() calls. We check here if the
1153  * message/folder was correctly asynchronously transferred. The reason
1154  * to use the same callback is that the code is the same, it only has
1155  * to check that the operation went fine and then finalize the drag
1156  * and drop action
1157  */
1158 static void
1159 on_progress_changed (ModestMailOperation *mail_op, gpointer user_data)
1160 {
1161         gboolean success;
1162         DndHelper *helper;
1163
1164         helper = (DndHelper *) user_data;
1165
1166         if (!modest_mail_operation_is_finished (mail_op))
1167                 return;
1168
1169         if (modest_mail_operation_get_status (mail_op) == 
1170             MODEST_MAIL_OPERATION_STATUS_SUCCESS) {
1171                 success = TRUE;
1172         } else {
1173                 success = FALSE;
1174         }
1175
1176         /* Notify the drag source. Never call delete, the monitor will
1177            do the job if needed */
1178         gtk_drag_finish (helper->context, success, FALSE, helper->time);
1179
1180         /* Free the helper */
1181         gtk_tree_path_free (helper->source_row);        
1182         g_slice_free (DndHelper, helper);
1183 }
1184
1185 /*
1186  * This function is used by drag_data_received_cb to manage drag and
1187  * drop of a header, i.e, and drag from the header view to the folder
1188  * view.
1189  */
1190 static void
1191 drag_and_drop_from_header_view (GtkTreeModel *source_model,
1192                                 GtkTreeModel *dest_model,
1193                                 GtkTreePath  *dest_row,
1194                                 DndHelper    *helper)
1195 {
1196         TnyList *headers;
1197         TnyHeader *header;
1198         TnyFolder *folder;
1199         ModestMailOperation *mail_op;
1200         GtkTreeIter source_iter, dest_iter;
1201
1202         /* Get header */
1203         gtk_tree_model_get_iter (source_model, &source_iter, helper->source_row);
1204         gtk_tree_model_get (source_model, &source_iter, 
1205                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, 
1206                             &header, -1);
1207
1208         /* Get Folder */
1209         gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
1210         gtk_tree_model_get (dest_model, &dest_iter, 
1211                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, 
1212                             &folder, -1);
1213
1214         /* Transfer message */
1215         mail_op = modest_mail_operation_new (MODEST_MAIL_OPERATION_ID_RECEIVE, NULL);
1216
1217         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1218                                          mail_op);
1219         g_signal_connect (G_OBJECT (mail_op), "progress-changed",
1220                           G_CALLBACK (on_progress_changed), helper);
1221
1222         /* FIXME: I replaced this because the API changed, but D&D
1223            should be reviewed in order to allow multiple drags*/
1224         headers = tny_simple_list_new ();
1225         tny_list_append (headers, G_OBJECT (header));
1226         modest_mail_operation_xfer_msgs (mail_op, headers, folder, helper->delete_source, NULL, NULL);
1227
1228         /* Frees */
1229         g_object_unref (G_OBJECT (mail_op));
1230         g_object_unref (G_OBJECT (header));
1231         g_object_unref (G_OBJECT (folder));
1232         g_object_unref (headers);
1233 }
1234
1235 /*
1236  * This function is used by drag_data_received_cb to manage drag and
1237  * drop of a folder, i.e, and drag from the folder view to the same
1238  * folder view.
1239  */
1240 static void
1241 drag_and_drop_from_folder_view (GtkTreeModel     *source_model,
1242                                 GtkTreeModel     *dest_model,
1243                                 GtkTreePath      *dest_row,
1244                                 GtkSelectionData *selection_data,
1245                                 DndHelper        *helper)
1246 {
1247         ModestMailOperation *mail_op;
1248         GtkTreeIter parent_iter, iter;
1249         TnyFolderStore *parent_folder;
1250         TnyFolder *folder;
1251
1252         /* Check if the drag is possible */
1253 /*      if (!gtk_tree_path_compare (helper->source_row, dest_row) || */
1254 /*          !gtk_tree_drag_dest_row_drop_possible (GTK_TREE_DRAG_DEST (dest_model), */
1255 /*                                                 dest_row, */
1256 /*                                                 selection_data)) { */
1257         if (!gtk_tree_path_compare (helper->source_row, dest_row)) {
1258
1259                 gtk_drag_finish (helper->context, FALSE, FALSE, helper->time);
1260                 gtk_tree_path_free (helper->source_row);        
1261                 g_slice_free (DndHelper, helper);
1262                 return;
1263         }
1264
1265         /* Get data */
1266         gtk_tree_model_get_iter (source_model, &parent_iter, dest_row);
1267         gtk_tree_model_get_iter (source_model, &iter, helper->source_row);
1268         gtk_tree_model_get (source_model, &parent_iter, 
1269                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, 
1270                             &parent_folder, -1);
1271         gtk_tree_model_get (source_model, &iter,
1272                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
1273                             &folder, -1);
1274
1275         /* Do the mail operation */
1276         mail_op = modest_mail_operation_new (MODEST_MAIL_OPERATION_ID_RECEIVE, NULL);
1277         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (), 
1278                                          mail_op);
1279         g_signal_connect (G_OBJECT (mail_op), "progress-changed",
1280                           G_CALLBACK (on_progress_changed), helper);
1281
1282         modest_mail_operation_xfer_folder (mail_op, 
1283                                            folder, 
1284                                            parent_folder,
1285                                            helper->delete_source);
1286         
1287         /* Frees */
1288         g_object_unref (G_OBJECT (parent_folder));
1289         g_object_unref (G_OBJECT (folder));
1290         g_object_unref (G_OBJECT (mail_op));
1291 }
1292
1293 /*
1294  * This function receives the data set by the "drag-data-get" signal
1295  * handler. This information comes within the #GtkSelectionData. This
1296  * function will manage both the drags of folders of the treeview and
1297  * drags of headers of the header view widget.
1298  */
1299 static void 
1300 on_drag_data_received (GtkWidget *widget, 
1301                        GdkDragContext *context, 
1302                        gint x, 
1303                        gint y, 
1304                        GtkSelectionData *selection_data, 
1305                        guint target_type, 
1306                        guint time, 
1307                        gpointer data)
1308 {
1309         GtkWidget *source_widget;
1310         GtkTreeModel *dest_model, *source_model;
1311         GtkTreePath *source_row, *dest_row;
1312         GtkTreeViewDropPosition pos;
1313         gboolean success = FALSE, delete_source = FALSE;
1314         DndHelper *helper = NULL; 
1315
1316         /* Do not allow further process */
1317         g_signal_stop_emission_by_name (widget, "drag-data-received");
1318         source_widget = gtk_drag_get_source_widget (context);
1319
1320         /* Get the action */
1321         if (context->action == GDK_ACTION_MOVE) {
1322                 delete_source = TRUE;
1323
1324                 /* Notify that there is no folder selected. We need to
1325                    do this in order to update the headers view (and
1326                    its monitors, because when moving, the old folder
1327                    won't longer exist. We can not wait for the end of
1328                    the operation, because the operation won't start if
1329                    the folder is in use */
1330                 if (source_widget == widget)
1331                         g_signal_emit (G_OBJECT (widget), 
1332                                        signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0, NULL, TRUE);
1333         }
1334
1335         /* Check if the get_data failed */
1336         if (selection_data == NULL || selection_data->length < 0)
1337                 gtk_drag_finish (context, success, FALSE, time);
1338
1339         /* Get the models */
1340         gtk_tree_get_row_drag_data (selection_data,
1341                                     &source_model,
1342                                     &source_row);
1343
1344         /* Select the destination model */
1345         if (source_widget == widget) {
1346                 dest_model = source_model;
1347         } else {
1348                 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
1349         }
1350
1351         /* Get the path to the destination row. Can not call
1352            gtk_tree_view_get_drag_dest_row() because the source row
1353            is not selected anymore */
1354         gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget), x, y,
1355                                            &dest_row, &pos);
1356
1357         /* Only allow drops IN other rows */
1358         if (!dest_row || pos == GTK_TREE_VIEW_DROP_BEFORE || pos == GTK_TREE_VIEW_DROP_AFTER)
1359                 gtk_drag_finish (context, success, FALSE, time);
1360
1361         /* Create the helper */
1362         helper = g_slice_new0 (DndHelper);
1363         helper->delete_source = delete_source;
1364         helper->source_row = gtk_tree_path_copy (source_row);
1365         helper->context = context;
1366         helper->time = time;
1367
1368         /* Drags from the header view */
1369         if (source_widget != widget) {
1370
1371                 drag_and_drop_from_header_view (source_model,
1372                                                 dest_model,
1373                                                 dest_row,
1374                                                 helper);
1375         } else {
1376
1377
1378                 drag_and_drop_from_folder_view (source_model,
1379                                                 dest_model,
1380                                                 dest_row,
1381                                                 selection_data, 
1382                                                 helper);
1383         }
1384
1385         /* Frees */
1386         gtk_tree_path_free (source_row);
1387         gtk_tree_path_free (dest_row);
1388 }
1389
1390 /*
1391  * We define a "drag-drop" signal handler because we do not want to
1392  * use the default one, because the default one always calls
1393  * gtk_drag_finish and we prefer to do it in the "drag-data-received"
1394  * signal handler, because there we have all the information available
1395  * to know if the dnd was a success or not.
1396  */
1397 static gboolean
1398 drag_drop_cb (GtkWidget      *widget,
1399               GdkDragContext *context,
1400               gint            x,
1401               gint            y,
1402               guint           time,
1403               gpointer        user_data) 
1404 {
1405         gpointer target;
1406
1407         if (!context->targets)
1408                 return FALSE;
1409
1410         /* Check if we're dragging a folder row */
1411         target = gtk_drag_dest_find_target (widget, context, NULL);
1412
1413         /* Request the data from the source. */
1414         gtk_drag_get_data(widget, context, target, time);
1415
1416     return TRUE;
1417 }
1418
1419 /*
1420  * This function expands a node of a tree view if it's not expanded
1421  * yet. Not sure why it needs the threads stuff, but gtk+`example code
1422  * does that, so that's why they're here.
1423  */
1424 static gint
1425 expand_row_timeout (gpointer data)
1426 {
1427         GtkTreeView *tree_view = data;
1428         GtkTreePath *dest_path = NULL;
1429         GtkTreeViewDropPosition pos;
1430         gboolean result = FALSE;
1431         
1432         GDK_THREADS_ENTER ();
1433         
1434         gtk_tree_view_get_drag_dest_row (tree_view,
1435                                          &dest_path,
1436                                          &pos);
1437         
1438         if (dest_path &&
1439             (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER ||
1440              pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)) {
1441                 gtk_tree_view_expand_row (tree_view, dest_path, FALSE);
1442                 gtk_tree_path_free (dest_path);
1443         }
1444         else {
1445                 if (dest_path)
1446                         gtk_tree_path_free (dest_path);
1447                 
1448                 result = TRUE;
1449         }
1450         
1451         GDK_THREADS_LEAVE ();
1452
1453         return result;
1454 }
1455
1456 /*
1457  * This function is called whenever the pointer is moved over a widget
1458  * while dragging some data. It installs a timeout that will expand a
1459  * node of the treeview if not expanded yet. This function also calls
1460  * gdk_drag_status in order to set the suggested action that will be
1461  * used by the "drag-data-received" signal handler to know if we
1462  * should do a move or just a copy of the data.
1463  */
1464 static gboolean
1465 on_drag_motion (GtkWidget      *widget,
1466                 GdkDragContext *context,
1467                 gint            x,
1468                 gint            y,
1469                 guint           time,
1470                 gpointer        user_data)  
1471 {
1472         GtkTreeViewDropPosition pos;
1473         GtkTreePath *dest_row;
1474         ModestFolderViewPrivate *priv;
1475         GdkDragAction suggested_action;
1476         gboolean valid_location = FALSE;
1477
1478         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
1479
1480         if (priv->timer_expander != 0) {
1481                 g_source_remove (priv->timer_expander);
1482                 priv->timer_expander = 0;
1483         }
1484
1485         gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
1486                                            x, y,
1487                                            &dest_row,
1488                                            &pos);
1489
1490         /* Do not allow drops between folders */
1491         if (!dest_row ||
1492             pos == GTK_TREE_VIEW_DROP_BEFORE ||
1493             pos == GTK_TREE_VIEW_DROP_AFTER) {
1494                 gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW (widget), NULL, 0);
1495                 gdk_drag_status(context, 0, time);
1496                 valid_location = FALSE;
1497                 goto out;
1498         } else {
1499                 valid_location = TRUE;
1500         }
1501
1502         /* Expand the selected row after 1/2 second */
1503         if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), dest_row)) {
1504                 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), dest_row, pos);
1505                 priv->timer_expander = g_timeout_add (500, expand_row_timeout, widget);
1506         }
1507
1508         /* Select the desired action. By default we pick MOVE */
1509         suggested_action = GDK_ACTION_MOVE;
1510
1511         if (context->actions == GDK_ACTION_COPY)
1512             gdk_drag_status(context, GDK_ACTION_COPY, time);
1513         else if (context->actions == GDK_ACTION_MOVE)
1514             gdk_drag_status(context, GDK_ACTION_MOVE, time);
1515         else if (context->actions & suggested_action)
1516             gdk_drag_status(context, suggested_action, time);
1517         else
1518             gdk_drag_status(context, GDK_ACTION_DEFAULT, time);
1519
1520  out:
1521         if (dest_row)
1522                 gtk_tree_path_free (dest_row);
1523         g_signal_stop_emission_by_name (widget, "drag-motion");
1524         return valid_location;
1525 }
1526
1527
1528 /* Folder view drag types */
1529 const GtkTargetEntry folder_view_drag_types[] =
1530 {
1531         { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, MODEST_FOLDER_ROW },
1532         { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_APP,    MODEST_HEADER_ROW }
1533 };
1534
1535 /*
1536  * This function sets the treeview as a source and a target for dnd
1537  * events. It also connects all the requirede signals.
1538  */
1539 static void
1540 setup_drag_and_drop (GtkTreeView *self)
1541 {
1542         /* Set up the folder view as a dnd destination. Set only the
1543            highlight flag, otherwise gtk will have a different
1544            behaviour */
1545         gtk_drag_dest_set (GTK_WIDGET (self),
1546                            GTK_DEST_DEFAULT_HIGHLIGHT,
1547                            folder_view_drag_types,
1548                            G_N_ELEMENTS (folder_view_drag_types),
1549                            GDK_ACTION_MOVE | GDK_ACTION_COPY);
1550
1551         g_signal_connect (G_OBJECT (self),
1552                           "drag_data_received",
1553                           G_CALLBACK (on_drag_data_received),
1554                           NULL);
1555
1556
1557         /* Set up the treeview as a dnd source */
1558         gtk_drag_source_set (GTK_WIDGET (self),
1559                              GDK_BUTTON1_MASK,
1560                              folder_view_drag_types,
1561                              G_N_ELEMENTS (folder_view_drag_types),
1562                              GDK_ACTION_MOVE | GDK_ACTION_COPY);
1563
1564         g_signal_connect (G_OBJECT (self),
1565                           "drag_motion",
1566                           G_CALLBACK (on_drag_motion),
1567                           NULL);
1568         
1569         g_signal_connect (G_OBJECT (self),
1570                           "drag_data_get",
1571                           G_CALLBACK (on_drag_data_get),
1572                           NULL);
1573
1574         g_signal_connect (G_OBJECT (self),
1575                           "drag_drop",
1576                           G_CALLBACK (drag_drop_cb),
1577                           NULL);
1578 }
1579
1580 /*
1581  * This function manages the navigation through the folders using the
1582  * keyboard or the hardware keys in the device
1583  */
1584 static gboolean
1585 on_key_pressed (GtkWidget *self,
1586                 GdkEventKey *event,
1587                 gpointer user_data)
1588 {
1589         GtkTreeSelection *selection;
1590         GtkTreeIter iter;
1591         GtkTreeModel *model;
1592         gboolean retval = FALSE;
1593
1594         /* Up and Down are automatically managed by the treeview */
1595         if (event->keyval == GDK_Return) {
1596                 /* Expand/Collapse the selected row */
1597                 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1598                 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
1599                         GtkTreePath *path;
1600
1601                         path = gtk_tree_model_get_path (model, &iter);
1602
1603                         if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (self), path))
1604                                 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1605                         else
1606                                 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1607                         gtk_tree_path_free (path);
1608                 }
1609                 /* No further processing */
1610                 retval = TRUE;
1611         }
1612
1613         return retval;
1614 }
1615
1616 /*
1617  * We listen to the changes in the local folder account name key,
1618  * because we want to show the right name in the view. The local
1619  * folder account name corresponds to the device name in the Maemo
1620  * version. We do this because we do not want to query gconf on each
1621  * tree view refresh. It's better to cache it and change whenever
1622  * necessary.
1623  */
1624 static void 
1625 on_configuration_key_changed (ModestConf* conf, 
1626                               const gchar *key, 
1627                               ModestConfEvent event, 
1628                               ModestFolderView *self)
1629 {
1630         ModestFolderViewPrivate *priv;
1631
1632         if (!key)
1633                 return;
1634
1635         g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
1636         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1637
1638         if (!strcmp (key, MODEST_CONF_DEVICE_NAME)) {
1639                 g_free (priv->local_account_name);
1640
1641                 if (event == MODEST_CONF_EVENT_KEY_UNSET)
1642                         priv->local_account_name = g_strdup (MODEST_LOCAL_FOLDERS_DEFAULT_DISPLAY_NAME);
1643                 else
1644                         priv->local_account_name = modest_conf_get_string (modest_runtime_get_conf(),
1645                                                                            MODEST_CONF_DEVICE_NAME, NULL);
1646
1647                 /* Force a redraw */
1648 #if GTK_CHECK_VERSION(2, 8, 0) /* gtk_tree_view_column_queue_resize is only available in GTK+ 2.8 */
1649                 GtkTreeViewColumn * tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self), 
1650                                                                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN);
1651                 gtk_tree_view_column_queue_resize (tree_column);
1652 #endif
1653         }
1654 }
1655
1656 void 
1657 modest_folder_view_set_style (ModestFolderView *self,
1658                               ModestFolderViewStyle style)
1659 {
1660         ModestFolderViewPrivate *priv;
1661
1662         g_return_if_fail (self);
1663         
1664         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1665
1666         priv->style = style;
1667 }
1668
1669 void
1670 modest_folder_view_set_account_id_of_visible_server_account (ModestFolderView *self,
1671                                                              const gchar *account_id)
1672 {
1673         ModestFolderViewPrivate *priv;
1674         ModestConf *conf;
1675         GtkTreeModel *model;
1676
1677         g_return_if_fail (self);
1678         
1679         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1680
1681         /* This will be used by the filter_row callback,
1682          * to decided which rows to show: */
1683         if (priv->visible_account_id)
1684                 g_free (priv->visible_account_id);
1685         priv->visible_account_id = g_strdup (account_id);
1686
1687         /* Save preferences */
1688         conf = modest_runtime_get_conf ();
1689         modest_widget_memory_save (conf, G_OBJECT (self), MODEST_CONF_FOLDER_VIEW_KEY);
1690
1691         /* Refilter */
1692         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1693         if (GTK_IS_TREE_MODEL_FILTER (model))
1694                 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
1695 }
1696
1697 const gchar *
1698 modest_folder_view_get_account_id_of_visible_server_account (ModestFolderView *self)
1699 {
1700         ModestFolderViewPrivate *priv;
1701
1702         g_return_val_if_fail (self, NULL);
1703         
1704         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1705
1706         return (const gchar *) priv->visible_account_id;
1707 }