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