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