bb553cd8c66813c8e86099966745396e4a1cb90d
[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         gulong                conf_key_signal;
144         
145         TnyFolderStoreQuery  *query;
146         guint                 timer_expander;
147
148         gchar                *local_account_name;
149         gchar                *visible_account_id;
150         ModestFolderViewStyle style;
151 };
152 #define MODEST_FOLDER_VIEW_GET_PRIVATE(o)                       \
153         (G_TYPE_INSTANCE_GET_PRIVATE((o),                       \
154                                      MODEST_TYPE_FOLDER_VIEW,   \
155                                      ModestFolderViewPrivate))
156 /* globals */
157 static GObjectClass *parent_class = NULL;
158
159 static guint signals[LAST_SIGNAL] = {0}; 
160
161 GType
162 modest_folder_view_get_type (void)
163 {
164         static GType my_type = 0;
165         if (!my_type) {
166                 static const GTypeInfo my_info = {
167                         sizeof(ModestFolderViewClass),
168                         NULL,           /* base init */
169                         NULL,           /* base finalize */
170                         (GClassInitFunc) modest_folder_view_class_init,
171                         NULL,           /* class finalize */
172                         NULL,           /* class data */
173                         sizeof(ModestFolderView),
174                         1,              /* n_preallocs */
175                         (GInstanceInitFunc) modest_folder_view_init,
176                         NULL
177                 };
178
179                 static const GInterfaceInfo tny_account_store_view_info = {
180                         (GInterfaceInitFunc) tny_account_store_view_init, /* interface_init */
181                         NULL,         /* interface_finalize */
182                         NULL          /* interface_data */
183                 };
184
185                                 
186                 my_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
187                                                   "ModestFolderView",
188                                                   &my_info, 0);
189
190                 g_type_add_interface_static (my_type, 
191                                              TNY_TYPE_ACCOUNT_STORE_VIEW, 
192                                              &tny_account_store_view_info);
193         }
194         return my_type;
195 }
196
197 static void
198 modest_folder_view_class_init (ModestFolderViewClass *klass)
199 {
200         GObjectClass *gobject_class;
201         gobject_class = (GObjectClass*) klass;
202
203         parent_class            = g_type_class_peek_parent (klass);
204         gobject_class->finalize = modest_folder_view_finalize;
205
206         g_type_class_add_private (gobject_class,
207                                   sizeof(ModestFolderViewPrivate));
208         
209         signals[FOLDER_SELECTION_CHANGED_SIGNAL] = 
210                 g_signal_new ("folder_selection_changed",
211                               G_TYPE_FROM_CLASS (gobject_class),
212                               G_SIGNAL_RUN_FIRST,
213                               G_STRUCT_OFFSET (ModestFolderViewClass,
214                                                folder_selection_changed),
215                               NULL, NULL,
216                               modest_marshal_VOID__POINTER_BOOLEAN,
217                               G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_BOOLEAN);
218
219         /*
220          * This signal is emitted whenever the currently selected
221          * folder display name is computed. Note that the name could
222          * be different to the folder name, because we could append
223          * the unread messages count to the folder name to build the
224          * folder display name
225          */
226         signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL] = 
227                 g_signal_new ("folder-display-name-changed",
228                               G_TYPE_FROM_CLASS (gobject_class),
229                               G_SIGNAL_RUN_FIRST,
230                               G_STRUCT_OFFSET (ModestFolderViewClass,
231                                                folder_display_name_changed),
232                               NULL, NULL,
233                               g_cclosure_marshal_VOID__STRING,
234                               G_TYPE_NONE, 1, G_TYPE_STRING);
235 }
236
237 static void
238 text_cell_data  (GtkTreeViewColumn *column,  GtkCellRenderer *renderer,
239                  GtkTreeModel *tree_model,  GtkTreeIter *iter,  gpointer data)
240 {
241         ModestFolderViewPrivate *priv;
242         GObject *rendobj;
243         gchar *fname = NULL;
244         gint unread = 0;
245         gint all = 0;
246         TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
247         GObject *instance = NULL;
248         
249         g_return_if_fail (column);
250         g_return_if_fail (tree_model);
251
252         gtk_tree_model_get (tree_model, iter,
253                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &fname,
254                             TNY_GTK_FOLDER_STORE_TREE_MODEL_ALL_COLUMN, &all,
255                             TNY_GTK_FOLDER_STORE_TREE_MODEL_UNREAD_COLUMN, &unread,
256                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
257                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
258                             -1);
259         rendobj = G_OBJECT(renderer);
260  
261         if (!fname)
262                 return;
263
264         if (!instance) {
265                 g_free (fname);
266                 return;
267         }
268
269         
270         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE (data);
271         
272         gchar *item_name = NULL;
273         gint item_weight = 400;
274         
275         if (type != TNY_FOLDER_TYPE_ROOT) {
276                 gint number = 0;
277                 
278                 if (modest_tny_folder_is_local_folder (TNY_FOLDER (instance))) {
279                         type = modest_tny_folder_get_local_folder_type (TNY_FOLDER (instance));
280                         if (type != TNY_FOLDER_TYPE_UNKNOWN) {
281                                 g_free (fname);
282                                 fname = g_strdup(modest_local_folder_info_get_type_display_name (type));
283                         }
284                 }
285
286                 /* Select the number to show: the unread or unsent messages */
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 or unset messages */
293                 if (number > 0) {
294                         item_name = g_strdup_printf ("%s (%d)", fname, number);
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         priv->conf_key_signal = 
491                 g_signal_connect (G_OBJECT(conf), 
492                                   "key_changed",
493                                   G_CALLBACK(on_configuration_key_changed), obj);
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         if (priv->conf_key_signal) {
545                 g_signal_handler_disconnect (modest_runtime_get_conf (),
546                                              priv->conf_key_signal);
547                 priv->conf_key_signal = 0;
548         }
549
550         G_OBJECT_CLASS(parent_class)->finalize (obj);
551 }
552
553
554 static void
555 modest_folder_view_set_account_store (TnyAccountStoreView *self, TnyAccountStore *account_store)
556 {
557         ModestFolderViewPrivate *priv;
558         TnyDevice *device;
559
560         g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
561         g_return_if_fail (TNY_IS_ACCOUNT_STORE (account_store));
562
563         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
564         device = tny_account_store_get_device (account_store);
565
566         if (G_UNLIKELY (priv->account_store)) {
567
568                 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store), 
569                                                    priv->account_update_signal))
570                         g_signal_handler_disconnect (G_OBJECT (priv->account_store), 
571                                                      priv->account_update_signal);
572                 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store), 
573                                                    priv->accounts_reloaded_signal))
574                         g_signal_handler_disconnect (G_OBJECT (priv->account_store), 
575                                                      priv->accounts_reloaded_signal);
576                 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store), 
577                                                    priv->account_removed_signal))
578                         g_signal_handler_disconnect (G_OBJECT (priv->account_store), 
579                                                      priv->account_removed_signal);
580
581                 g_object_unref (G_OBJECT (priv->account_store));
582         }
583
584         priv->account_store = g_object_ref (G_OBJECT (account_store));
585
586         priv->account_update_signal = 
587                 g_signal_connect (G_OBJECT(account_store), "account_update",
588                                   G_CALLBACK (on_account_update), self);
589
590         priv->account_removed_signal = 
591                 g_signal_connect (G_OBJECT(account_store), "account_removed",
592                                   G_CALLBACK (on_account_removed), self);
593
594         priv->accounts_reloaded_signal = 
595                 g_signal_connect (G_OBJECT(account_store), "accounts_reloaded",
596                                   G_CALLBACK (on_accounts_reloaded), self);
597
598         on_accounts_reloaded (account_store, (gpointer ) self);
599         
600         g_object_unref (G_OBJECT (device));
601 }
602
603 static void
604 on_account_removed (TnyAccountStore *account_store, 
605                     TnyAccount *account,
606                     gpointer user_data)
607 {
608         ModestFolderView *self = MODEST_FOLDER_VIEW (user_data);
609         ModestFolderViewPrivate *priv;
610
611         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
612
613         /* If the removed account is the currently viewed one then
614            clear the configuration value. The new visible account will be the default account */
615         if (!strcmp (priv->visible_account_id, tny_account_get_id (account))) {
616                 modest_folder_view_set_account_id_of_visible_server_account (self, NULL);
617         }
618 }
619
620 static void
621 on_account_update (TnyAccountStore *account_store, 
622                    const gchar *account,
623                    gpointer user_data)
624 {
625         ModestFolderView *self = MODEST_FOLDER_VIEW (user_data);
626         ModestFolderViewPrivate *priv;
627
628         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
629         if (!priv->visible_account_id)
630                 modest_widget_memory_restore (modest_runtime_get_conf(), G_OBJECT(self),
631                                               MODEST_CONF_FOLDER_VIEW_KEY);
632
633         if (!modest_folder_view_update_model (self, account_store))
634                 g_printerr ("modest: failed to update model for changes in '%s'",
635                             account);
636 }
637
638 static void 
639 on_accounts_reloaded   (TnyAccountStore *account_store, 
640                         gpointer user_data)
641 {
642         modest_folder_view_update_model (MODEST_FOLDER_VIEW (user_data), account_store);
643 }
644
645 void
646 modest_folder_view_set_title (ModestFolderView *self, const gchar *title)
647 {
648         GtkTreeViewColumn *col;
649         
650         g_return_if_fail (self);
651
652         col = gtk_tree_view_get_column (GTK_TREE_VIEW(self), 0);
653         if (!col) {
654                 g_printerr ("modest: failed get column for title\n");
655                 return;
656         }
657
658         gtk_tree_view_column_set_title (col, title);
659         gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self),
660                                            title != NULL);
661 }
662
663 GtkWidget*
664 modest_folder_view_new (TnyFolderStoreQuery *query)
665 {
666         GObject *self;
667         ModestFolderViewPrivate *priv;
668         GtkTreeSelection *sel;
669         
670         self = G_OBJECT (g_object_new (MODEST_TYPE_FOLDER_VIEW, NULL));
671         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
672
673         if (query)
674                 priv->query = g_object_ref (query);
675         
676         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
677         priv->changed_signal = g_signal_connect (sel, "changed",
678                                                  G_CALLBACK (on_selection_changed), self);
679
680         return GTK_WIDGET(self);
681 }
682
683 /* this feels dirty; any other way to expand all the root items? */
684 static void
685 expand_root_items (ModestFolderView *self)
686 {
687         GtkTreePath *path;
688         path = gtk_tree_path_new_first ();
689
690         /* all folders should have child items, so.. */
691         while (gtk_tree_view_expand_row (GTK_TREE_VIEW(self), path, FALSE))
692                 gtk_tree_path_next (path);
693         
694         gtk_tree_path_free (path);
695 }
696
697 /*
698  * We use this function to implement the
699  * MODEST_FOLDER_VIEW_STYLE_SHOW_ONE style. We only show the default
700  * account in this case, and the local folders.
701  */
702 static gboolean 
703 filter_row (GtkTreeModel *model,
704             GtkTreeIter *iter,
705             gpointer data)
706 {
707         gboolean retval = TRUE;
708         TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
709         GObject *instance = NULL;
710
711         gtk_tree_model_get (model, iter,
712                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
713                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
714                             -1);
715
716         /* Do not show if there is no instance, this could indeed
717            happen when the model is being modified while it's being
718            drawn. This could occur for example when moving folders
719            using drag&drop */
720         if (!instance)
721                 return FALSE;
722
723         if (type == TNY_FOLDER_TYPE_ROOT) {
724                 /* TNY_FOLDER_TYPE_ROOT means that the instance is an account instead of a folder. */
725                 if (TNY_IS_ACCOUNT (instance)) {
726                         TnyAccount *acc = TNY_ACCOUNT (instance);
727                         const gchar *account_id = tny_account_get_id (acc);
728         
729                         /* If it isn't a special folder, 
730                          * don't show it unless it is the visible account: */
731                         if (!modest_tny_account_is_virtual_local_folders (acc) &&
732                                 strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) { 
733                                 /* Show only the visible account id */
734                                 ModestFolderViewPrivate *priv = 
735                                         MODEST_FOLDER_VIEW_GET_PRIVATE (data);
736                                 if (priv->visible_account_id && strcmp (account_id, priv->visible_account_id))
737                                         retval = FALSE;
738                         }
739                         
740                         /* Never show these to the user. They are merged into one folder 
741                          * in the local-folders account instead: */
742                         if (retval && MODEST_IS_TNY_OUTBOX_ACCOUNT (acc))
743                                 retval = FALSE;
744                 }
745         }
746         
747         /* The virtual local-folders folder store is also shown by default. */
748
749         g_object_unref (instance);
750
751         return retval;
752 }
753
754 gboolean
755 modest_folder_view_update_model (ModestFolderView *self,
756                                  TnyAccountStore *account_store)
757 {
758         ModestFolderViewPrivate *priv;
759         GtkTreeModel *model /* , *old_model */;
760         /* TnyAccount *local_account; */
761         TnyList *model_as_list;
762
763         g_return_val_if_fail (account_store, FALSE);
764
765         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE(self);
766         
767         /* Notify that there is no folder selected */
768         g_signal_emit (G_OBJECT(self), 
769                        signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
770                        NULL, TRUE);
771
772         /* FIXME: the local accounts are not shown when the query
773            selects only the subscribed folders. */
774 /*      model        = tny_gtk_folder_store_tree_model_new (TRUE, priv->query); */
775         model        = tny_gtk_folder_store_tree_model_new (TRUE, NULL);
776         
777         /* Deal with the model via its TnyList Interface,
778          * filling the TnyList via a get_accounts() call: */
779         model_as_list = TNY_LIST(model);
780
781         /* Get the accounts: */
782         tny_account_store_get_accounts (TNY_ACCOUNT_STORE(account_store),
783                                         model_as_list,
784                                         TNY_ACCOUNT_STORE_STORE_ACCOUNTS);
785         g_object_unref (model_as_list);
786         model_as_list = NULL;   
787                                                      
788         GtkTreeModel *filter_model = NULL, *sortable = NULL;
789
790         sortable = gtk_tree_model_sort_new_with_model (model);
791         gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
792                                               TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, 
793                                               GTK_SORT_ASCENDING);
794         gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
795                                          TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
796                                          cmp_rows, NULL, NULL);
797
798         /* Create filter model */
799         if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE) {
800                 filter_model = gtk_tree_model_filter_new (sortable, NULL);
801                 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
802                                                         filter_row,
803                                                         self,
804                                                         NULL);
805         }
806
807         /* Set new model */
808         gtk_tree_view_set_model (GTK_TREE_VIEW(self), 
809                                  (filter_model) ? filter_model : sortable);
810         expand_root_items (self); /* expand all account folders */
811         
812         g_object_unref (model);
813         
814         if (filter_model)
815                 g_object_unref (filter_model);
816                         
817         g_object_unref (sortable);
818
819         /* Select the first inbox or the local account if not found */
820         modest_folder_view_select_first_inbox_or_local (self);
821                         
822         return TRUE;
823 }
824
825
826 static void
827 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
828 {
829         GtkTreeModel            *model;
830         TnyFolderStore          *folder = NULL;
831         GtkTreeIter             iter;
832         ModestFolderView        *tree_view;
833         ModestFolderViewPrivate *priv;
834
835         g_return_if_fail (sel);
836         g_return_if_fail (user_data);
837         
838         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
839
840         /* folder was _un_selected if true */
841         if (!gtk_tree_selection_get_selected (sel, &model, &iter)) {
842                 if (priv->cur_folder_store)
843                         g_object_unref (priv->cur_folder_store);
844                 priv->cur_folder_store = NULL;
845
846                 /* Notify the display name observers */
847                 g_signal_emit (G_OBJECT(user_data),
848                                signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
849                                NULL);
850                 return;
851         }
852
853         tree_view = MODEST_FOLDER_VIEW (user_data);
854
855         gtk_tree_model_get (model, &iter,
856                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
857                             -1);
858
859         /* If the folder is the same do not notify */
860         if (priv->cur_folder_store == folder) {
861                 g_object_unref (folder);
862                 return;
863         }
864         
865         /* Current folder was unselected */
866         if (priv->cur_folder_store) {
867                 g_signal_emit (G_OBJECT(tree_view), signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
868                                priv->cur_folder_store, FALSE);
869                 g_object_unref (priv->cur_folder_store);
870         }
871
872         /* New current references */
873         priv->cur_folder_store = folder;
874
875         /* New folder has been selected */
876         g_signal_emit (G_OBJECT(tree_view),
877                        signals[FOLDER_SELECTION_CHANGED_SIGNAL],
878                        0, folder, TRUE);
879 }
880
881 TnyFolderStore *
882 modest_folder_view_get_selected (ModestFolderView *self)
883 {
884         ModestFolderViewPrivate *priv;
885
886         g_return_val_if_fail (self, NULL);
887         
888         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
889         if (priv->cur_folder_store)
890                 g_object_ref (priv->cur_folder_store);
891
892         return priv->cur_folder_store;
893 }
894
895 static gint
896 get_cmp_rows_type_pos (GObject *folder)
897 {
898         /* Remote accounts -> Local account -> MMC account .*/
899         /* 0, 1, 2 */
900         
901         if (TNY_IS_ACCOUNT (folder) && 
902                 modest_tny_account_is_virtual_local_folders (
903                         TNY_ACCOUNT (folder))) {
904                 return 1;
905         } else if (TNY_IS_ACCOUNT (folder)) {
906                 TnyAccount *account = TNY_ACCOUNT (folder);
907                 const gchar *account_id = tny_account_get_id (account);
908                 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
909                         return 2;
910                 else
911                         return 0;
912         }
913         else {
914                 printf ("DEBUG: %s: unexpected type.\n", __FUNCTION__);
915                 return -1; /* Should never happen */
916         }
917 }
918
919 /*
920  * This function orders the mail accounts according to these rules:
921  * 1st - remote accounts
922  * 2nd - local account
923  * 3rd - MMC account
924  */
925 static gint
926 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
927           gpointer user_data)
928 {
929         gint cmp;
930         gchar *name1 = NULL;
931         gchar *name2 = NULL;
932         TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
933         GObject *folder1 = NULL;
934         GObject *folder2 = NULL;
935
936         gtk_tree_model_get (tree_model, iter1,
937                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name1,
938                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
939                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder1,
940                             -1);
941         gtk_tree_model_get (tree_model, iter2,
942                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name2,
943                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder2,
944                             -1);
945
946         if (type == TNY_FOLDER_TYPE_ROOT) {
947                 /* Compare the types, so that 
948                  * Remote accounts -> Local account -> MMC account .*/
949                 const gint pos1 = get_cmp_rows_type_pos (folder1);
950                 const gint pos2 = get_cmp_rows_type_pos (folder2);
951                 /* printf ("DEBUG: %s:\n  type1=%s, pos1=%d\n  type2=%s, pos2=%d\n", 
952                         __FUNCTION__, G_OBJECT_TYPE_NAME(folder1), pos1, G_OBJECT_TYPE_NAME(folder2), pos2); */
953                 if (pos1 <  pos2)
954                         cmp = -1;
955                 else if (pos1 > pos2)
956                         cmp = 1;
957                 else {
958                         /* Compare items of the same type: */
959                         
960                         TnyAccount *account1 = NULL;
961                         if (TNY_IS_ACCOUNT (folder1))
962                                 account1 = TNY_ACCOUNT (folder1);
963                                 
964                         TnyAccount *account2 = NULL;
965                         if (TNY_IS_ACCOUNT (folder2))
966                                 account2 = TNY_ACCOUNT (folder2);
967                                 
968                         const gchar *account_id = account1 ? tny_account_get_id (account1) : NULL;
969                         const gchar *account_id2 = account2 ? tny_account_get_id (account2) : NULL;
970         
971                         if (!account_id && !account_id2)
972                                 return 0;
973                         else if (!account_id)
974                                 return -1;
975                         else if (!account_id2)
976                                 return +1;
977                         else if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
978                                 cmp = +1;
979                         else
980                                 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
981                 }
982         } else {
983                 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
984         }
985         
986         if (folder1)
987                 g_object_unref(G_OBJECT(folder1));
988         if (folder2)
989                 g_object_unref(G_OBJECT(folder2));
990
991         g_free (name1);
992         g_free (name2);
993
994         return cmp;     
995 }
996
997 /*****************************************************************************/
998 /*                        DRAG and DROP stuff                                */
999 /*****************************************************************************/
1000
1001 /*
1002  * This function fills the #GtkSelectionData with the row and the
1003  * model that has been dragged. It's called when this widget is a
1004  * source for dnd after the event drop happened
1005  */
1006 static void
1007 on_drag_data_get (GtkWidget *widget, 
1008                   GdkDragContext *context, 
1009                   GtkSelectionData *selection_data, 
1010                   guint info, 
1011                   guint time, 
1012                   gpointer data)
1013 {
1014         GtkTreeSelection *selection;
1015         GtkTreeModel *model;
1016         GtkTreeIter iter;
1017         GtkTreePath *source_row;
1018
1019         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1020         gtk_tree_selection_get_selected (selection, &model, &iter);
1021         source_row = gtk_tree_model_get_path (model, &iter);
1022
1023         gtk_tree_set_row_drag_data (selection_data,
1024                                     model,
1025                                     source_row);
1026
1027         gtk_tree_path_free (source_row);
1028 }
1029
1030 typedef struct _DndHelper {
1031         gboolean delete_source;
1032         GtkTreePath *source_row;
1033         GdkDragContext *context;
1034         guint time;
1035 } DndHelper;
1036
1037
1038 /*
1039  * This function is the callback of the
1040  * modest_mail_operation_xfer_msgs () and
1041  * modest_mail_operation_xfer_folder() calls. We check here if the
1042  * message/folder was correctly asynchronously transferred. The reason
1043  * to use the same callback is that the code is the same, it only has
1044  * to check that the operation went fine and then finalize the drag
1045  * and drop action
1046  */
1047 static void
1048 on_progress_changed (ModestMailOperation *mail_op, 
1049                      ModestMailOperationState *state,
1050                      gpointer user_data)
1051 {
1052         gboolean success;
1053         DndHelper *helper;
1054
1055         helper = (DndHelper *) user_data;
1056
1057         if (!state->finished)
1058                 return;
1059
1060         if (state->status == MODEST_MAIL_OPERATION_STATUS_SUCCESS) {
1061                 success = TRUE;
1062         } else {
1063                 success = FALSE;
1064         }
1065
1066         /* Notify the drag source. Never call delete, the monitor will
1067            do the job if needed */
1068         gtk_drag_finish (helper->context, success, FALSE, helper->time);
1069
1070         /* Free the helper */
1071         gtk_tree_path_free (helper->source_row);        
1072         g_slice_free (DndHelper, helper);
1073 }
1074
1075 /*
1076  * This function is used by drag_data_received_cb to manage drag and
1077  * drop of a header, i.e, and drag from the header view to the folder
1078  * view.
1079  */
1080 static void
1081 drag_and_drop_from_header_view (GtkTreeModel *source_model,
1082                                 GtkTreeModel *dest_model,
1083                                 GtkTreePath  *dest_row,
1084                                 DndHelper    *helper)
1085 {
1086         TnyList *headers = NULL;
1087         TnyHeader *header = NULL;
1088         TnyFolder *folder = NULL;
1089         ModestMailOperation *mail_op = NULL;
1090         GtkTreeIter source_iter, dest_iter;
1091
1092         g_return_if_fail (GTK_IS_TREE_MODEL(source_model));
1093         g_return_if_fail (GTK_IS_TREE_MODEL(dest_model));
1094         g_return_if_fail (dest_row);
1095         g_return_if_fail (helper);
1096         
1097         /* Get header */
1098         gtk_tree_model_get_iter (source_model, &source_iter, helper->source_row);
1099         gtk_tree_model_get (source_model, &source_iter, 
1100                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, 
1101                             &header, -1);
1102         if (!TNY_IS_HEADER(header)) {
1103                 g_warning ("BUG: %s could not get a valid header", __FUNCTION__);
1104                 goto cleanup;
1105         }
1106         
1107         /* Get Folder */
1108         gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
1109         gtk_tree_model_get (dest_model, &dest_iter, 
1110                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, 
1111                             &folder, -1);
1112
1113         if (!TNY_IS_FOLDER(folder)) {
1114                 g_warning ("BUG: %s could not get a valid folder", __FUNCTION__);
1115                 goto cleanup;
1116         }
1117
1118         /* Transfer message */
1119         mail_op = modest_mail_operation_new (MODEST_MAIL_OPERATION_TYPE_RECEIVE, NULL);
1120         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1121                                          mail_op);
1122         g_signal_connect (G_OBJECT (mail_op), "progress-changed",
1123                           G_CALLBACK (on_progress_changed), helper);
1124
1125         headers = tny_simple_list_new ();
1126         tny_list_append (headers, G_OBJECT (header));
1127         modest_mail_operation_xfer_msgs (mail_op, 
1128                                          headers, 
1129                                          folder, 
1130                                          helper->delete_source, 
1131                                          NULL, NULL);
1132         
1133         /* Frees */
1134 cleanup:
1135         if (G_IS_OBJECT(mail_op))
1136                 g_object_unref (G_OBJECT (mail_op));
1137         if (G_IS_OBJECT(header))
1138                 g_object_unref (G_OBJECT (header));
1139         if (G_IS_OBJECT(folder))
1140                 g_object_unref (G_OBJECT (folder));
1141         if (G_IS_OBJECT(headers))
1142                 g_object_unref (headers);
1143 }
1144
1145 /*
1146  * This function is used by drag_data_received_cb to manage drag and
1147  * drop of a folder, i.e, and drag from the folder view to the same
1148  * folder view.
1149  */
1150 static void
1151 drag_and_drop_from_folder_view (GtkTreeModel     *source_model,
1152                                 GtkTreeModel     *dest_model,
1153                                 GtkTreePath      *dest_row,
1154                                 GtkSelectionData *selection_data,
1155                                 DndHelper        *helper)
1156 {
1157         ModestMailOperation *mail_op;
1158         GtkTreeIter parent_iter, iter;
1159         TnyFolderStore *parent_folder;
1160         TnyFolder *folder;
1161
1162         /* Check if the drag is possible */
1163 /*      if (!gtk_tree_path_compare (helper->source_row, dest_row) || */
1164 /*          !gtk_tree_drag_dest_row_drop_possible (GTK_TREE_DRAG_DEST (dest_model), */
1165 /*                                                 dest_row, */
1166 /*                                                 selection_data)) { */
1167         if (!gtk_tree_path_compare (helper->source_row, dest_row)) {
1168
1169                 gtk_drag_finish (helper->context, FALSE, FALSE, helper->time);
1170                 gtk_tree_path_free (helper->source_row);        
1171                 g_slice_free (DndHelper, helper);
1172                 return;
1173         }
1174
1175         /* Get data */
1176         gtk_tree_model_get_iter (source_model, &parent_iter, dest_row);
1177         gtk_tree_model_get_iter (source_model, &iter, helper->source_row);
1178         gtk_tree_model_get (source_model, &parent_iter, 
1179                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, 
1180                             &parent_folder, -1);
1181         gtk_tree_model_get (source_model, &iter,
1182                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
1183                             &folder, -1);
1184
1185         /* Do the mail operation */
1186         mail_op = modest_mail_operation_new_with_error_handling (MODEST_MAIL_OPERATION_TYPE_RECEIVE, 
1187                                                                  NULL,
1188                                                                  modest_ui_actions_move_folder_error_handler,
1189                                                                  NULL);
1190         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (), 
1191                                          mail_op);
1192         g_signal_connect (G_OBJECT (mail_op), "progress-changed",
1193                           G_CALLBACK (on_progress_changed), helper);
1194
1195         modest_mail_operation_xfer_folder (mail_op, 
1196                                            folder, 
1197                                            parent_folder,
1198                                            helper->delete_source);
1199         
1200         /* Frees */
1201         g_object_unref (G_OBJECT (parent_folder));
1202         g_object_unref (G_OBJECT (folder));
1203         g_object_unref (G_OBJECT (mail_op));
1204 }
1205
1206 /*
1207  * This function receives the data set by the "drag-data-get" signal
1208  * handler. This information comes within the #GtkSelectionData. This
1209  * function will manage both the drags of folders of the treeview and
1210  * drags of headers of the header view widget.
1211  */
1212 static void 
1213 on_drag_data_received (GtkWidget *widget, 
1214                        GdkDragContext *context, 
1215                        gint x, 
1216                        gint y, 
1217                        GtkSelectionData *selection_data, 
1218                        guint target_type, 
1219                        guint time, 
1220                        gpointer data)
1221 {
1222         GtkWidget *source_widget;
1223         GtkTreeModel *dest_model, *source_model;
1224         GtkTreePath *source_row, *dest_row;
1225         GtkTreeViewDropPosition pos;
1226         gboolean success = FALSE, delete_source = FALSE;
1227         DndHelper *helper = NULL; 
1228
1229         /* Do not allow further process */
1230         g_signal_stop_emission_by_name (widget, "drag-data-received");
1231         source_widget = gtk_drag_get_source_widget (context);
1232
1233         /* Get the action */
1234         if (context->action == GDK_ACTION_MOVE) {
1235                 delete_source = TRUE;
1236
1237                 /* Notify that there is no folder selected. We need to
1238                    do this in order to update the headers view (and
1239                    its monitors, because when moving, the old folder
1240                    won't longer exist. We can not wait for the end of
1241                    the operation, because the operation won't start if
1242                    the folder is in use */
1243                 if (source_widget == widget)
1244                         g_signal_emit (G_OBJECT (widget), 
1245                                        signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0, NULL, TRUE);
1246         }
1247
1248         /* Check if the get_data failed */
1249         if (selection_data == NULL || selection_data->length < 0)
1250                 gtk_drag_finish (context, success, FALSE, time);
1251
1252         /* Get the models */
1253         gtk_tree_get_row_drag_data (selection_data,
1254                                     &source_model,
1255                                     &source_row);
1256
1257         /* Select the destination model */
1258         if (source_widget == widget) {
1259                 dest_model = source_model;
1260         } else {
1261                 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
1262         }
1263
1264         /* Get the path to the destination row. Can not call
1265            gtk_tree_view_get_drag_dest_row() because the source row
1266            is not selected anymore */
1267         gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget), x, y,
1268                                            &dest_row, &pos);
1269
1270         /* Only allow drops IN other rows */
1271         if (!dest_row || pos == GTK_TREE_VIEW_DROP_BEFORE || pos == GTK_TREE_VIEW_DROP_AFTER)
1272                 gtk_drag_finish (context, success, FALSE, time);
1273
1274         /* Create the helper */
1275         helper = g_slice_new0 (DndHelper);
1276         helper->delete_source = delete_source;
1277         helper->source_row = gtk_tree_path_copy (source_row);
1278         helper->context = context;
1279         helper->time = time;
1280
1281         /* Drags from the header view */
1282         if (source_widget != widget) {
1283
1284                 drag_and_drop_from_header_view (source_model,
1285                                                 dest_model,
1286                                                 dest_row,
1287                                                 helper);
1288         } else {
1289
1290
1291                 drag_and_drop_from_folder_view (source_model,
1292                                                 dest_model,
1293                                                 dest_row,
1294                                                 selection_data, 
1295                                                 helper);
1296         }
1297
1298         /* Frees */
1299         gtk_tree_path_free (source_row);
1300         gtk_tree_path_free (dest_row);
1301 }
1302
1303 /*
1304  * We define a "drag-drop" signal handler because we do not want to
1305  * use the default one, because the default one always calls
1306  * gtk_drag_finish and we prefer to do it in the "drag-data-received"
1307  * signal handler, because there we have all the information available
1308  * to know if the dnd was a success or not.
1309  */
1310 static gboolean
1311 drag_drop_cb (GtkWidget      *widget,
1312               GdkDragContext *context,
1313               gint            x,
1314               gint            y,
1315               guint           time,
1316               gpointer        user_data) 
1317 {
1318         gpointer target;
1319
1320         if (!context->targets)
1321                 return FALSE;
1322
1323         /* Check if we're dragging a folder row */
1324         target = gtk_drag_dest_find_target (widget, context, NULL);
1325
1326         /* Request the data from the source. */
1327         gtk_drag_get_data(widget, context, target, time);
1328
1329     return TRUE;
1330 }
1331
1332 /*
1333  * This function expands a node of a tree view if it's not expanded
1334  * yet. Not sure why it needs the threads stuff, but gtk+`example code
1335  * does that, so that's why they're here.
1336  */
1337 static gint
1338 expand_row_timeout (gpointer data)
1339 {
1340         GtkTreeView *tree_view = data;
1341         GtkTreePath *dest_path = NULL;
1342         GtkTreeViewDropPosition pos;
1343         gboolean result = FALSE;
1344         
1345         GDK_THREADS_ENTER ();
1346         
1347         gtk_tree_view_get_drag_dest_row (tree_view,
1348                                          &dest_path,
1349                                          &pos);
1350         
1351         if (dest_path &&
1352             (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER ||
1353              pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)) {
1354                 gtk_tree_view_expand_row (tree_view, dest_path, FALSE);
1355                 gtk_tree_path_free (dest_path);
1356         }
1357         else {
1358                 if (dest_path)
1359                         gtk_tree_path_free (dest_path);
1360                 
1361                 result = TRUE;
1362         }
1363         
1364         GDK_THREADS_LEAVE ();
1365
1366         return result;
1367 }
1368
1369 /*
1370  * This function is called whenever the pointer is moved over a widget
1371  * while dragging some data. It installs a timeout that will expand a
1372  * node of the treeview if not expanded yet. This function also calls
1373  * gdk_drag_status in order to set the suggested action that will be
1374  * used by the "drag-data-received" signal handler to know if we
1375  * should do a move or just a copy of the data.
1376  */
1377 static gboolean
1378 on_drag_motion (GtkWidget      *widget,
1379                 GdkDragContext *context,
1380                 gint            x,
1381                 gint            y,
1382                 guint           time,
1383                 gpointer        user_data)  
1384 {
1385         GtkTreeViewDropPosition pos;
1386         GtkTreePath *dest_row;
1387         ModestFolderViewPrivate *priv;
1388         GdkDragAction suggested_action;
1389         gboolean valid_location = FALSE;
1390
1391         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
1392
1393         if (priv->timer_expander != 0) {
1394                 g_source_remove (priv->timer_expander);
1395                 priv->timer_expander = 0;
1396         }
1397
1398         gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
1399                                            x, y,
1400                                            &dest_row,
1401                                            &pos);
1402
1403         /* Do not allow drops between folders */
1404         if (!dest_row ||
1405             pos == GTK_TREE_VIEW_DROP_BEFORE ||
1406             pos == GTK_TREE_VIEW_DROP_AFTER) {
1407                 gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW (widget), NULL, 0);
1408                 gdk_drag_status(context, 0, time);
1409                 valid_location = FALSE;
1410                 goto out;
1411         } else {
1412                 valid_location = TRUE;
1413         }
1414
1415         /* Expand the selected row after 1/2 second */
1416         if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), dest_row)) {
1417                 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), dest_row, pos);
1418                 priv->timer_expander = g_timeout_add (500, expand_row_timeout, widget);
1419         }
1420
1421         /* Select the desired action. By default we pick MOVE */
1422         suggested_action = GDK_ACTION_MOVE;
1423
1424         if (context->actions == GDK_ACTION_COPY)
1425             gdk_drag_status(context, GDK_ACTION_COPY, time);
1426         else if (context->actions == GDK_ACTION_MOVE)
1427             gdk_drag_status(context, GDK_ACTION_MOVE, time);
1428         else if (context->actions & suggested_action)
1429             gdk_drag_status(context, suggested_action, time);
1430         else
1431             gdk_drag_status(context, GDK_ACTION_DEFAULT, time);
1432
1433  out:
1434         if (dest_row)
1435                 gtk_tree_path_free (dest_row);
1436         g_signal_stop_emission_by_name (widget, "drag-motion");
1437         return valid_location;
1438 }
1439
1440
1441 /* Folder view drag types */
1442 const GtkTargetEntry folder_view_drag_types[] =
1443 {
1444         { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, MODEST_FOLDER_ROW },
1445         { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_APP,    MODEST_HEADER_ROW }
1446 };
1447
1448 /*
1449  * This function sets the treeview as a source and a target for dnd
1450  * events. It also connects all the requirede signals.
1451  */
1452 static void
1453 setup_drag_and_drop (GtkTreeView *self)
1454 {
1455         /* Set up the folder view as a dnd destination. Set only the
1456            highlight flag, otherwise gtk will have a different
1457            behaviour */
1458         gtk_drag_dest_set (GTK_WIDGET (self),
1459                            GTK_DEST_DEFAULT_HIGHLIGHT,
1460                            folder_view_drag_types,
1461                            G_N_ELEMENTS (folder_view_drag_types),
1462                            GDK_ACTION_MOVE | GDK_ACTION_COPY);
1463
1464         g_signal_connect (G_OBJECT (self),
1465                           "drag_data_received",
1466                           G_CALLBACK (on_drag_data_received),
1467                           NULL);
1468
1469
1470         /* Set up the treeview as a dnd source */
1471         gtk_drag_source_set (GTK_WIDGET (self),
1472                              GDK_BUTTON1_MASK,
1473                              folder_view_drag_types,
1474                              G_N_ELEMENTS (folder_view_drag_types),
1475                              GDK_ACTION_MOVE | GDK_ACTION_COPY);
1476
1477         g_signal_connect (G_OBJECT (self),
1478                           "drag_motion",
1479                           G_CALLBACK (on_drag_motion),
1480                           NULL);
1481         
1482         g_signal_connect (G_OBJECT (self),
1483                           "drag_data_get",
1484                           G_CALLBACK (on_drag_data_get),
1485                           NULL);
1486
1487         g_signal_connect (G_OBJECT (self),
1488                           "drag_drop",
1489                           G_CALLBACK (drag_drop_cb),
1490                           NULL);
1491 }
1492
1493 /*
1494  * This function manages the navigation through the folders using the
1495  * keyboard or the hardware keys in the device
1496  */
1497 static gboolean
1498 on_key_pressed (GtkWidget *self,
1499                 GdkEventKey *event,
1500                 gpointer user_data)
1501 {
1502         GtkTreeSelection *selection;
1503         GtkTreeIter iter;
1504         GtkTreeModel *model;
1505         gboolean retval = FALSE;
1506
1507         /* Up and Down are automatically managed by the treeview */
1508         if (event->keyval == GDK_Return) {
1509                 /* Expand/Collapse the selected row */
1510                 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1511                 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
1512                         GtkTreePath *path;
1513
1514                         path = gtk_tree_model_get_path (model, &iter);
1515
1516                         if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (self), path))
1517                                 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1518                         else
1519                                 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1520                         gtk_tree_path_free (path);
1521                 }
1522                 /* No further processing */
1523                 retval = TRUE;
1524         }
1525
1526         return retval;
1527 }
1528
1529 /*
1530  * We listen to the changes in the local folder account name key,
1531  * because we want to show the right name in the view. The local
1532  * folder account name corresponds to the device name in the Maemo
1533  * version. We do this because we do not want to query gconf on each
1534  * tree view refresh. It's better to cache it and change whenever
1535  * necessary.
1536  */
1537 static void 
1538 on_configuration_key_changed (ModestConf* conf, 
1539                               const gchar *key, 
1540                               ModestConfEvent event, 
1541                               ModestFolderView *self)
1542 {
1543         ModestFolderViewPrivate *priv;
1544
1545         if (!key)
1546                 return;
1547
1548         g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
1549         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1550
1551         if (!strcmp (key, MODEST_CONF_DEVICE_NAME)) {
1552                 g_free (priv->local_account_name);
1553
1554                 if (event == MODEST_CONF_EVENT_KEY_UNSET)
1555                         priv->local_account_name = g_strdup (MODEST_LOCAL_FOLDERS_DEFAULT_DISPLAY_NAME);
1556                 else
1557                         priv->local_account_name = modest_conf_get_string (modest_runtime_get_conf(),
1558                                                                            MODEST_CONF_DEVICE_NAME, NULL);
1559
1560                 /* Force a redraw */
1561 #if GTK_CHECK_VERSION(2, 8, 0) /* gtk_tree_view_column_queue_resize is only available in GTK+ 2.8 */
1562                 GtkTreeViewColumn * tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self), 
1563                                                                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN);
1564                 gtk_tree_view_column_queue_resize (tree_column);
1565 #endif
1566         }
1567 }
1568
1569 void 
1570 modest_folder_view_set_style (ModestFolderView *self,
1571                               ModestFolderViewStyle style)
1572 {
1573         ModestFolderViewPrivate *priv;
1574
1575         g_return_if_fail (self);
1576         
1577         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1578
1579         priv->style = style;
1580 }
1581
1582 void
1583 modest_folder_view_set_account_id_of_visible_server_account (ModestFolderView *self,
1584                                                              const gchar *account_id)
1585 {
1586         ModestFolderViewPrivate *priv;
1587         GtkTreeModel *model;
1588
1589         g_return_if_fail (self);
1590         
1591         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1592
1593         /* This will be used by the filter_row callback,
1594          * to decided which rows to show: */
1595         if (priv->visible_account_id) {
1596                 g_free (priv->visible_account_id);
1597                 priv->visible_account_id = NULL;
1598         }
1599         if (account_id)
1600                 priv->visible_account_id = g_strdup (account_id);
1601
1602         /* Refilter */
1603         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1604         if (GTK_IS_TREE_MODEL_FILTER (model))
1605                 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
1606
1607         /* Save settings to gconf */
1608         modest_widget_memory_save (modest_runtime_get_conf (), G_OBJECT(self),
1609                                    MODEST_CONF_FOLDER_VIEW_KEY);
1610 }
1611
1612 const gchar *
1613 modest_folder_view_get_account_id_of_visible_server_account (ModestFolderView *self)
1614 {
1615         ModestFolderViewPrivate *priv;
1616
1617         g_return_val_if_fail (self, NULL);
1618         
1619         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1620
1621         return (const gchar *) priv->visible_account_id;
1622 }
1623
1624 static gboolean
1625 find_inbox_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *inbox_iter)
1626 {
1627         do {
1628                 GtkTreeIter child;
1629                 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1630                 gchar *name = NULL;
1631
1632                 gtk_tree_model_get (model, iter, 
1633                                         TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name,
1634                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, 
1635                                     &type, -1);
1636
1637                 /*
1638                 printf ("DEBUG: %s: name=%s, type=%d, TNY_FOLDER_TYPE_INBOX=%d\n", 
1639                         __FUNCTION__, name, type, TNY_FOLDER_TYPE_INBOX);
1640                 */
1641                         
1642                 gboolean result = FALSE;
1643                 if (type == TNY_FOLDER_TYPE_INBOX) {
1644                         result = TRUE;
1645                 } else if (type == TNY_FOLDER_TYPE_NORMAL) {
1646                         /* tinymail's camel implementation only provides TNY_FOLDER_TYPE_NORMAL
1647                          * when getting folders from the cache, before connectin, so we do 
1648                          * an extra check. We could fix this in tinymail, but it's easier 
1649                          * to do here.
1650                          */
1651                          if (strcmp (name, "Inbox") == 0)
1652                                 result = TRUE;
1653                 }
1654                 
1655                 g_free (name);
1656                 
1657                 if (result) {
1658                         *inbox_iter = *iter;
1659                         return TRUE;
1660                 }
1661
1662                 if (gtk_tree_model_iter_children (model, &child, iter)) {
1663                         if (find_inbox_iter (model, &child, inbox_iter))
1664                                 return TRUE;
1665                 }
1666
1667         } while (gtk_tree_model_iter_next (model, iter));
1668
1669         return FALSE;
1670 }
1671
1672 void 
1673 modest_folder_view_select_first_inbox_or_local (ModestFolderView *self)
1674 {
1675         GtkTreeModel *model;
1676         GtkTreeIter iter, inbox_iter;
1677         GtkTreeSelection *sel;
1678
1679         /* Do not set it if the folder view was not painted */
1680         if (!GTK_WIDGET_MAPPED (self))
1681                 return; 
1682
1683         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1684         if (!model)
1685                 return;
1686
1687         expand_root_items (self);
1688         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1689
1690         gtk_tree_model_get_iter_first (model, &iter);
1691         if (find_inbox_iter (model, &iter, &inbox_iter)) {
1692                 gtk_tree_selection_select_iter (sel, &inbox_iter);
1693         }
1694         else {
1695                 gtk_tree_model_get_iter_first (model, &iter);
1696                 gtk_tree_selection_select_iter (sel, &iter);
1697         }
1698 }