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