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