2007-05-23 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         if (type == TNY_FOLDER_TYPE_ROOT) {
697                 /* TNY_FOLDER_TYPE_ROOT means that the instance is an account instead of a folder. */
698                 if (TNY_IS_ACCOUNT (instance)) {
699                         TnyAccount *acc = TNY_ACCOUNT (instance);
700                         const gchar *account_id = tny_account_get_id (acc);
701                         
702                         /* If it isn't a special folder, 
703                          * don't show it unless it is the visible account: */
704                         if (strcmp (account_id, MODEST_ACTUAL_LOCAL_FOLDERS_ACCOUNT_ID) &&
705                             strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) { 
706                                 
707                                 /* TODO: Merge the folders before we get to this point? */
708                                 
709                                 /* Show only the visible account id */
710                                 /*
711                                 ModestFolderViewPrivate *priv = 
712                                         MODEST_FOLDER_VIEW_GET_PRIVATE (data);
713                                 if (priv->visible_account_id && strcmp (account_id, priv->visible_account_id))
714                                         retval = FALSE;
715                                 */
716                         }
717                 }
718         }
719
720         g_object_unref (instance);
721
722         return retval;
723 }
724
725 /*
726 static void on_tnylist_accounts_debug_print(gpointer data,  gpointer user_data)
727 {
728         TnyAccount* account = TNY_ACCOUNT(data);
729         const gchar *prefix = (const gchar*)(user_data);
730         
731         printf("%s account id=%s\n", prefix, tny_account_get_id (account));
732 }
733 */
734
735 static void
736 add_account_folders_to_merged_folder (TnyAccount *account, TnyMergeFolder* merge_folder)
737 {
738         const gchar* account_id = tny_account_get_id (account);
739         const gboolean is_actual_local_folders_account = account_id && 
740                 (strcmp (account_id, MODEST_ACTUAL_LOCAL_FOLDERS_ACCOUNT_ID) == 0);
741                 
742         TnyList *list_outbox_folders = tny_simple_list_new ();
743         tny_folder_store_get_folders (TNY_FOLDER_STORE (account), 
744                 list_outbox_folders, NULL, NULL);
745                 
746         TnyIterator*  iter =  tny_list_create_iterator (list_outbox_folders);
747         while (!tny_iterator_is_done (iter))
748         {
749                 TnyFolder *folder = TNY_FOLDER (tny_iterator_get_current (iter));
750                 
751                 if (folder) {
752                         gboolean add = TRUE;
753                         /* TODO: Do not add outboxes that are inside local-folders/, 
754                          * because these are just left-over from earlier Modest versions 
755                          * that put the outbox there: */
756                         if (is_actual_local_folders_account) {
757                                 const TnyFolderType type = modest_tny_folder_get_local_folder_type (folder);
758                                 if (type == TNY_FOLDER_TYPE_OUTBOX) {
759                                         add = FALSE;
760                                 }
761                         }
762                         
763                         if (add)
764                                 tny_merge_folder_add_folder (merge_folder, folder);
765                                 
766                         g_object_unref (folder);        
767                 }
768                 
769                 tny_iterator_next (iter);
770         }
771         
772         g_object_unref (list_outbox_folders);
773 }
774
775
776 static void
777 add_account_folders_to_simple_folder_store (TnyAccount *account, ModestTnySimpleFolderStore* store)
778 {
779         g_return_if_fail (account);
780         g_return_if_fail (store);
781                 
782         TnyList *list_outbox_folders = tny_simple_list_new ();
783         tny_folder_store_get_folders (TNY_FOLDER_STORE (account), 
784                 list_outbox_folders, NULL, NULL);
785         
786         /* Special handling for the .modest/local-folders account,
787          * to avoid adding unwanted folders.
788          * We cannot prevent them from being in the TnyAccount without 
789          * changing the libtinymail-camel. */
790         const gchar* account_id = tny_account_get_id (account);
791         const gboolean is_actual_local_folders_account = account_id && 
792                 (strcmp (account_id, MODEST_ACTUAL_LOCAL_FOLDERS_ACCOUNT_ID) == 0);
793         
794         TnyIterator*  iter =  tny_list_create_iterator (list_outbox_folders);
795         while (!tny_iterator_is_done (iter))
796         {
797                 TnyFolder *folder = TNY_FOLDER (tny_iterator_get_current (iter));
798                 
799                 if (folder) {
800                         gboolean add = TRUE;
801                         /* TODO: Do not add outboxes that are inside local-folders/, 
802                          * because these are just left-over from earlier Modest versions 
803                          * that put the outbox there: */
804                         if (is_actual_local_folders_account) {
805                                 const TnyFolderType type = modest_tny_folder_get_local_folder_type (folder);
806                                 if (type == TNY_FOLDER_TYPE_OUTBOX) {
807                                         add = FALSE;
808                                 }
809                         }
810                         
811                         if (add)
812                                 modest_tny_simple_folder_store_add_folder (store, folder);
813                                 
814                         g_object_unref (folder);        
815                 }
816                 
817                 tny_iterator_next (iter);
818         }
819         
820         g_object_unref (list_outbox_folders);
821 }
822
823 static gboolean
824 update_model (ModestFolderView *self, ModestTnyAccountStore *account_store)
825 {
826         ModestFolderViewPrivate *priv;
827
828         GtkTreeModel     *model;
829
830         g_return_val_if_fail (account_store, FALSE);
831
832         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE(self);
833         
834         /* Notify that there is no folder selected */
835         g_signal_emit (G_OBJECT(self), 
836                        signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
837                        NULL, TRUE);
838         
839         /* FIXME: the local accounts are not shown when the query
840            selects only the subscribed folders. */
841 /*      model        = tny_gtk_folder_store_tree_model_new (TRUE, priv->query); */
842         model        = tny_gtk_folder_store_tree_model_new (TRUE, NULL);
843         
844         /* Deal with the model via its TnyList Interface,
845          * filling the TnyList via a get_accounts() call: */
846         TnyList *model_as_list = TNY_LIST(model);
847
848         /* Create a virtual local-folders folder store, 
849          * containing the real local folders and the (merged) various per-account 
850          * outbox folders:
851          */
852         ModestTnySimpleFolderStore *store = modest_tny_simple_folder_store_new ();
853
854         /* Get the accounts: */
855         TnyList *account_list = tny_simple_list_new ();
856         tny_account_store_get_accounts (TNY_ACCOUNT_STORE(account_store),
857                                         account_list,
858                                         TNY_ACCOUNT_STORE_STORE_ACCOUNTS);
859         TnyIterator* iter =  tny_list_create_iterator (account_list);
860         
861         /* All per-account outbox folders are merged into one folders
862          * so that they appear as one outbox to the user: */
863         TnyMergeFolder *merged_outbox = TNY_MERGE_FOLDER (tny_merge_folder_new());
864         
865         while (!tny_iterator_is_done (iter))
866         {
867                 GObject *cur = tny_iterator_get_current (iter);
868                 TnyAccount *account = TNY_ACCOUNT (cur);
869                 if (account) {
870                         /* Add both outbox account and local-folders account folders
871                          * to our one combined account:
872                          */
873                         if (MODEST_IS_TNY_OUTBOX_ACCOUNT (account)) {
874                                 /* Add the folder to the merged folder.
875                                  * We will add it later to the virtual local-folders store: */
876                                 add_account_folders_to_merged_folder (account, merged_outbox);
877                         } else {
878                                 const gchar *account_id = tny_account_get_id (account);
879                                 if (account_id && !strcmp (account_id, MODEST_ACTUAL_LOCAL_FOLDERS_ACCOUNT_ID)) {
880                                         /* Add the folders to the virtual local-folders store: */
881                                         add_account_folders_to_simple_folder_store (account, store);
882                                 }
883                                 else {
884                                         /* Just add the account: */
885                                         tny_list_append (model_as_list, G_OBJECT (account));
886                                 }
887                         }
888                 }
889            
890                 g_object_unref (cur);
891                 tny_iterator_next (iter);
892         }
893         
894         /* Add the merged outbox folder to the virtual local-folders store: */
895         modest_tny_simple_folder_store_add_folder (store, TNY_FOLDER(merged_outbox));
896         g_object_unref (merged_outbox);
897         merged_outbox = NULL;
898         
899         /* Add the virtual local-folders store to the model: */
900         tny_list_append (model_as_list, G_OBJECT (store));
901         
902         
903         g_object_unref (account_list);
904         account_list = NULL;
905         
906         g_object_unref (model_as_list);
907         model_as_list = NULL;   
908                 
909         /* tny_list_foreach (account_list, on_tnylist_accounts_debug_print, "update_model: "); */
910                                                      
911         GtkTreeModel *filter_model = NULL, *sortable = NULL;
912
913         sortable = gtk_tree_model_sort_new_with_model (model);
914         gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
915                                               TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, 
916                                               GTK_SORT_ASCENDING);
917         gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
918                                          TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
919                                          cmp_rows, NULL, NULL);
920
921         /* Create filter model */
922         if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE) {
923                 filter_model = gtk_tree_model_filter_new (sortable, NULL);
924                 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
925                                                         filter_row,
926                                                         self,
927                                                         NULL);
928         }
929
930         /* Set new model */
931         gtk_tree_view_set_model (GTK_TREE_VIEW(self), 
932                                  (filter_model) ? filter_model : sortable);
933         expand_root_items (self); /* expand all account folders */
934         
935         return TRUE;
936 }
937
938
939 static void
940 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
941 {
942         GtkTreeModel            *model;
943         TnyFolderStore          *folder = NULL;
944         GtkTreeIter             iter;
945         ModestFolderView        *tree_view;
946         ModestFolderViewPrivate *priv;
947         gint                    type;
948
949         g_return_if_fail (sel);
950         g_return_if_fail (user_data);
951         
952         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
953         priv->cur_selection = sel;
954         
955         /* folder was _un_selected if true */
956         if (!gtk_tree_selection_get_selected (sel, &model, &iter)) {
957                 if (priv->cur_folder_store)
958                         g_object_unref (priv->cur_folder_store);
959                 priv->cur_folder_store = NULL;
960
961                 /* Notify the display name observers */
962                 g_signal_emit (G_OBJECT(user_data),
963                                signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
964                                NULL);
965                 return;
966         }
967
968         tree_view = MODEST_FOLDER_VIEW (user_data);
969
970         gtk_tree_model_get (model, &iter,
971                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
972                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
973                             -1);
974
975         /* If the folder is the same do not notify */
976         if (priv->cur_folder_store == folder) {
977                 g_object_unref (folder);
978                 return;
979         }
980         
981         /* Current folder was unselected */
982         if (priv->cur_folder_store) {
983                 g_signal_emit (G_OBJECT(tree_view), signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
984                                priv->cur_folder_store, FALSE);
985                 g_object_unref (priv->cur_folder_store);
986         }
987
988         /* New current references */
989         priv->cur_folder_store = folder;
990
991         /* New folder has been selected */
992         g_signal_emit (G_OBJECT(tree_view),
993                        signals[FOLDER_SELECTION_CHANGED_SIGNAL],
994                        0, folder, TRUE);
995 }
996
997 TnyFolderStore *
998 modest_folder_view_get_selected (ModestFolderView *self)
999 {
1000         ModestFolderViewPrivate *priv;
1001
1002         g_return_val_if_fail (self, NULL);
1003         
1004         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1005         if (priv->cur_folder_store)
1006                 g_object_ref (priv->cur_folder_store);
1007
1008         return priv->cur_folder_store;
1009 }
1010
1011 static gint
1012 get_cmp_rows_type_pos (GObject *folder)
1013 {
1014         /* Remote accounts -> Local account -> MMC account .*/
1015         /* 0, 1, 2 */
1016         
1017         if (TNY_IS_FOLDER_STORE (folder) && 
1018                 modest_tny_folder_store_is_virtual_local_folders (
1019                         TNY_FOLDER_STORE (folder))) {
1020                 return 1;
1021         } else if (TNY_IS_ACCOUNT (folder)) {
1022                 TnyAccount *account = TNY_ACCOUNT (folder);
1023                 const gchar *account_id = tny_account_get_id (account);
1024                 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
1025                         return 2;
1026                 else
1027                         return 0;
1028         }
1029         else {
1030                 printf ("DEBUG: %s: unexpected type.\n", __FUNCTION__);
1031                 return -1; /* Should never happen */
1032         }
1033 }
1034
1035 /*
1036  * This function orders the mail accounts according to these rules:
1037  * 1st - remote accounts
1038  * 2nd - local account
1039  * 3rd - MMC account
1040  */
1041 static gint
1042 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1043           gpointer user_data)
1044 {
1045         gint cmp;
1046         gchar         *name1, *name2;
1047         TnyFolderType type;
1048         GObject *folder1 = NULL;
1049         GObject *folder2 = NULL;
1050
1051         gtk_tree_model_get (tree_model, iter1,
1052                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name1,
1053                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
1054                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder1,
1055                             -1);
1056         gtk_tree_model_get (tree_model, iter2,
1057                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name2,
1058                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder2,
1059                             -1);
1060
1061         if (type == TNY_FOLDER_TYPE_ROOT) {
1062                 /* Compare the types, so that 
1063                  * Remote accounts -> Local account -> MMC account .*/
1064                 const gint pos1 = get_cmp_rows_type_pos (folder1);
1065                 const gint pos2 = get_cmp_rows_type_pos (folder2);
1066                 /* printf ("DEBUG: %s:\n  type1=%s, pos1=%d\n  type2=%s, pos2=%d\n", 
1067                         __FUNCTION__, G_OBJECT_TYPE_NAME(folder1), pos1, G_OBJECT_TYPE_NAME(folder2), pos2); */
1068                 if (pos1 <  pos2)
1069                         cmp = -1;
1070                 else if (pos1 > pos2)
1071                         cmp = 1;
1072                 else {
1073                         /* Compare items of the same type: */
1074                         
1075                         TnyAccount *account1 = NULL;
1076                         if (TNY_IS_ACCOUNT (folder1))
1077                                 account1 = TNY_ACCOUNT (folder1);
1078                                 
1079                         TnyAccount *account2 = NULL;
1080                         if (TNY_IS_ACCOUNT (folder2))
1081                                 account2 = TNY_ACCOUNT (folder2);
1082                                 
1083                         const gchar *account_id = account1 ? tny_account_get_id (account1) : NULL;
1084                         const gchar *account_id2 = account2 ? tny_account_get_id (account2) : NULL;
1085         
1086                         if (!account_id && !account_id2)
1087                                 return 0;
1088                         else if (!account_id)
1089                                 return -1;
1090                         else if (!account_id2)
1091                                 return +1;
1092                         else if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
1093                                 cmp = +1;
1094                         else
1095                                 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
1096                 }
1097         } else {
1098                 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
1099         }
1100         
1101         if (folder1)
1102                 g_object_unref(G_OBJECT(folder1));
1103         if (folder2)
1104                 g_object_unref(G_OBJECT(folder2));
1105
1106         g_free (name1);
1107         g_free (name2);
1108
1109         return cmp;     
1110 }
1111
1112 /*****************************************************************************/
1113 /*                        DRAG and DROP stuff                                */
1114 /*****************************************************************************/
1115
1116 /*
1117  * This function fills the #GtkSelectionData with the row and the
1118  * model that has been dragged. It's called when this widget is a
1119  * source for dnd after the event drop happened
1120  */
1121 static void
1122 on_drag_data_get (GtkWidget *widget, 
1123                   GdkDragContext *context, 
1124                   GtkSelectionData *selection_data, 
1125                   guint info, 
1126                   guint time, 
1127                   gpointer data)
1128 {
1129         GtkTreeSelection *selection;
1130         GtkTreeModel *model;
1131         GtkTreeIter iter;
1132         GtkTreePath *source_row;
1133
1134         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1135         gtk_tree_selection_get_selected (selection, &model, &iter);
1136         source_row = gtk_tree_model_get_path (model, &iter);
1137
1138         gtk_tree_set_row_drag_data (selection_data,
1139                                     model,
1140                                     source_row);
1141
1142         gtk_tree_path_free (source_row);
1143 }
1144
1145 typedef struct _DndHelper {
1146         gboolean delete_source;
1147         GtkTreePath *source_row;
1148         GdkDragContext *context;
1149         guint time;
1150 } DndHelper;
1151
1152
1153 /*
1154  * This function is the callback of the
1155  * modest_mail_operation_xfer_msgs () and
1156  * modest_mail_operation_xfer_folder() calls. We check here if the
1157  * message/folder was correctly asynchronously transferred. The reason
1158  * to use the same callback is that the code is the same, it only has
1159  * to check that the operation went fine and then finalize the drag
1160  * and drop action
1161  */
1162 static void
1163 on_progress_changed (ModestMailOperation *mail_op, gpointer user_data)
1164 {
1165         gboolean success;
1166         DndHelper *helper;
1167
1168         helper = (DndHelper *) user_data;
1169
1170         if (!modest_mail_operation_is_finished (mail_op))
1171                 return;
1172
1173         if (modest_mail_operation_get_status (mail_op) == 
1174             MODEST_MAIL_OPERATION_STATUS_SUCCESS) {
1175                 success = TRUE;
1176         } else {
1177                 success = FALSE;
1178         }
1179
1180         /* Notify the drag source. Never call delete, the monitor will
1181            do the job if needed */
1182         gtk_drag_finish (helper->context, success, FALSE, helper->time);
1183
1184         /* Free the helper */
1185         gtk_tree_path_free (helper->source_row);        
1186         g_slice_free (DndHelper, helper);
1187 }
1188
1189 /*
1190  * This function is used by drag_data_received_cb to manage drag and
1191  * drop of a header, i.e, and drag from the header view to the folder
1192  * view.
1193  */
1194 static void
1195 drag_and_drop_from_header_view (GtkTreeModel *source_model,
1196                                 GtkTreeModel *dest_model,
1197                                 GtkTreePath  *dest_row,
1198                                 DndHelper    *helper)
1199 {
1200         TnyList *headers;
1201         TnyHeader *header;
1202         TnyFolder *folder;
1203         ModestMailOperation *mail_op;
1204         GtkTreeIter source_iter, dest_iter;
1205
1206         /* Get header */
1207         gtk_tree_model_get_iter (source_model, &source_iter, helper->source_row);
1208         gtk_tree_model_get (source_model, &source_iter, 
1209                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, 
1210                             &header, -1);
1211
1212         /* Get Folder */
1213         gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
1214         gtk_tree_model_get (dest_model, &dest_iter, 
1215                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, 
1216                             &folder, -1);
1217
1218         /* Transfer message */
1219         mail_op = modest_mail_operation_new (MODEST_MAIL_OPERATION_ID_RECEIVE, NULL);
1220
1221         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1222                                          mail_op);
1223         g_signal_connect (G_OBJECT (mail_op), "progress-changed",
1224                           G_CALLBACK (on_progress_changed), helper);
1225
1226         /* FIXME: I replaced this because the API changed, but D&D
1227            should be reviewed in order to allow multiple drags*/
1228         headers = tny_simple_list_new ();
1229         tny_list_append (headers, G_OBJECT (header));
1230         modest_mail_operation_xfer_msgs (mail_op, headers, folder, helper->delete_source, NULL, NULL);
1231
1232         /* Frees */
1233         g_object_unref (G_OBJECT (mail_op));
1234         g_object_unref (G_OBJECT (header));
1235         g_object_unref (G_OBJECT (folder));
1236         g_object_unref (headers);
1237 }
1238
1239 /*
1240  * This function is used by drag_data_received_cb to manage drag and
1241  * drop of a folder, i.e, and drag from the folder view to the same
1242  * folder view.
1243  */
1244 static void
1245 drag_and_drop_from_folder_view (GtkTreeModel     *source_model,
1246                                 GtkTreeModel     *dest_model,
1247                                 GtkTreePath      *dest_row,
1248                                 GtkSelectionData *selection_data,
1249                                 DndHelper        *helper)
1250 {
1251         ModestMailOperation *mail_op;
1252         GtkTreeIter parent_iter, iter;
1253         TnyFolderStore *parent_folder;
1254         TnyFolder *folder;
1255
1256         /* Check if the drag is possible */
1257 /*      if (!gtk_tree_path_compare (helper->source_row, dest_row) || */
1258 /*          !gtk_tree_drag_dest_row_drop_possible (GTK_TREE_DRAG_DEST (dest_model), */
1259 /*                                                 dest_row, */
1260 /*                                                 selection_data)) { */
1261         if (!gtk_tree_path_compare (helper->source_row, dest_row)) {
1262
1263                 gtk_drag_finish (helper->context, FALSE, FALSE, helper->time);
1264                 gtk_tree_path_free (helper->source_row);        
1265                 g_slice_free (DndHelper, helper);
1266                 return;
1267         }
1268
1269         /* Get data */
1270         gtk_tree_model_get_iter (source_model, &parent_iter, dest_row);
1271         gtk_tree_model_get_iter (source_model, &iter, helper->source_row);
1272         gtk_tree_model_get (source_model, &parent_iter, 
1273                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, 
1274                             &parent_folder, -1);
1275         gtk_tree_model_get (source_model, &iter,
1276                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
1277                             &folder, -1);
1278
1279         /* Do the mail operation */
1280         mail_op = modest_mail_operation_new (MODEST_MAIL_OPERATION_ID_RECEIVE, NULL);
1281         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (), 
1282                                          mail_op);
1283         g_signal_connect (G_OBJECT (mail_op), "progress-changed",
1284                           G_CALLBACK (on_progress_changed), helper);
1285
1286         modest_mail_operation_xfer_folder (mail_op, 
1287                                            folder, 
1288                                            parent_folder,
1289                                            helper->delete_source);
1290         
1291         /* Frees */
1292         g_object_unref (G_OBJECT (parent_folder));
1293         g_object_unref (G_OBJECT (folder));
1294         g_object_unref (G_OBJECT (mail_op));
1295 }
1296
1297 /*
1298  * This function receives the data set by the "drag-data-get" signal
1299  * handler. This information comes within the #GtkSelectionData. This
1300  * function will manage both the drags of folders of the treeview and
1301  * drags of headers of the header view widget.
1302  */
1303 static void 
1304 on_drag_data_received (GtkWidget *widget, 
1305                        GdkDragContext *context, 
1306                        gint x, 
1307                        gint y, 
1308                        GtkSelectionData *selection_data, 
1309                        guint target_type, 
1310                        guint time, 
1311                        gpointer data)
1312 {
1313         GtkWidget *source_widget;
1314         GtkTreeModel *dest_model, *source_model;
1315         GtkTreePath *source_row, *dest_row;
1316         GtkTreeViewDropPosition pos;
1317         gboolean success = FALSE, delete_source = FALSE;
1318         DndHelper *helper = NULL; 
1319
1320         /* Do not allow further process */
1321         g_signal_stop_emission_by_name (widget, "drag-data-received");
1322         source_widget = gtk_drag_get_source_widget (context);
1323
1324         /* Get the action */
1325         if (context->action == GDK_ACTION_MOVE) {
1326                 delete_source = TRUE;
1327
1328                 /* Notify that there is no folder selected. We need to
1329                    do this in order to update the headers view (and
1330                    its monitors, because when moving, the old folder
1331                    won't longer exist. We can not wait for the end of
1332                    the operation, because the operation won't start if
1333                    the folder is in use */
1334                 if (source_widget == widget)
1335                         g_signal_emit (G_OBJECT (widget), 
1336                                        signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0, NULL, TRUE);
1337         }
1338
1339         /* Check if the get_data failed */
1340         if (selection_data == NULL || selection_data->length < 0)
1341                 gtk_drag_finish (context, success, FALSE, time);
1342
1343         /* Get the models */
1344         gtk_tree_get_row_drag_data (selection_data,
1345                                     &source_model,
1346                                     &source_row);
1347
1348         /* Select the destination model */
1349         if (source_widget == widget) {
1350                 dest_model = source_model;
1351         } else {
1352                 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
1353         }
1354
1355         /* Get the path to the destination row. Can not call
1356            gtk_tree_view_get_drag_dest_row() because the source row
1357            is not selected anymore */
1358         gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget), x, y,
1359                                            &dest_row, &pos);
1360
1361         /* Only allow drops IN other rows */
1362         if (!dest_row || pos == GTK_TREE_VIEW_DROP_BEFORE || pos == GTK_TREE_VIEW_DROP_AFTER)
1363                 gtk_drag_finish (context, success, FALSE, time);
1364
1365         /* Create the helper */
1366         helper = g_slice_new0 (DndHelper);
1367         helper->delete_source = delete_source;
1368         helper->source_row = gtk_tree_path_copy (source_row);
1369         helper->context = context;
1370         helper->time = time;
1371
1372         /* Drags from the header view */
1373         if (source_widget != widget) {
1374
1375                 drag_and_drop_from_header_view (source_model,
1376                                                 dest_model,
1377                                                 dest_row,
1378                                                 helper);
1379         } else {
1380
1381
1382                 drag_and_drop_from_folder_view (source_model,
1383                                                 dest_model,
1384                                                 dest_row,
1385                                                 selection_data, 
1386                                                 helper);
1387         }
1388
1389         /* Frees */
1390         gtk_tree_path_free (source_row);
1391         gtk_tree_path_free (dest_row);
1392 }
1393
1394 /*
1395  * We define a "drag-drop" signal handler because we do not want to
1396  * use the default one, because the default one always calls
1397  * gtk_drag_finish and we prefer to do it in the "drag-data-received"
1398  * signal handler, because there we have all the information available
1399  * to know if the dnd was a success or not.
1400  */
1401 static gboolean
1402 drag_drop_cb (GtkWidget      *widget,
1403               GdkDragContext *context,
1404               gint            x,
1405               gint            y,
1406               guint           time,
1407               gpointer        user_data) 
1408 {
1409         gpointer target;
1410
1411         if (!context->targets)
1412                 return FALSE;
1413
1414         /* Check if we're dragging a folder row */
1415         target = gtk_drag_dest_find_target (widget, context, NULL);
1416
1417         /* Request the data from the source. */
1418         gtk_drag_get_data(widget, context, target, time);
1419
1420     return TRUE;
1421 }
1422
1423 /*
1424  * This function expands a node of a tree view if it's not expanded
1425  * yet. Not sure why it needs the threads stuff, but gtk+`example code
1426  * does that, so that's why they're here.
1427  */
1428 static gint
1429 expand_row_timeout (gpointer data)
1430 {
1431         GtkTreeView *tree_view = data;
1432         GtkTreePath *dest_path = NULL;
1433         GtkTreeViewDropPosition pos;
1434         gboolean result = FALSE;
1435         
1436         GDK_THREADS_ENTER ();
1437         
1438         gtk_tree_view_get_drag_dest_row (tree_view,
1439                                          &dest_path,
1440                                          &pos);
1441         
1442         if (dest_path &&
1443             (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER ||
1444              pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)) {
1445                 gtk_tree_view_expand_row (tree_view, dest_path, FALSE);
1446                 gtk_tree_path_free (dest_path);
1447         }
1448         else {
1449                 if (dest_path)
1450                         gtk_tree_path_free (dest_path);
1451                 
1452                 result = TRUE;
1453         }
1454         
1455         GDK_THREADS_LEAVE ();
1456
1457         return result;
1458 }
1459
1460 /*
1461  * This function is called whenever the pointer is moved over a widget
1462  * while dragging some data. It installs a timeout that will expand a
1463  * node of the treeview if not expanded yet. This function also calls
1464  * gdk_drag_status in order to set the suggested action that will be
1465  * used by the "drag-data-received" signal handler to know if we
1466  * should do a move or just a copy of the data.
1467  */
1468 static gboolean
1469 on_drag_motion (GtkWidget      *widget,
1470                 GdkDragContext *context,
1471                 gint            x,
1472                 gint            y,
1473                 guint           time,
1474                 gpointer        user_data)  
1475 {
1476         GtkTreeViewDropPosition pos;
1477         GtkTreePath *dest_row;
1478         ModestFolderViewPrivate *priv;
1479         GdkDragAction suggested_action;
1480         gboolean valid_location = FALSE;
1481
1482         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
1483
1484         if (priv->timer_expander != 0) {
1485                 g_source_remove (priv->timer_expander);
1486                 priv->timer_expander = 0;
1487         }
1488
1489         gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
1490                                            x, y,
1491                                            &dest_row,
1492                                            &pos);
1493
1494         /* Do not allow drops between folders */
1495         if (!dest_row ||
1496             pos == GTK_TREE_VIEW_DROP_BEFORE ||
1497             pos == GTK_TREE_VIEW_DROP_AFTER) {
1498                 gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW (widget), NULL, 0);
1499                 gdk_drag_status(context, 0, time);
1500                 valid_location = FALSE;
1501                 goto out;
1502         } else {
1503                 valid_location = TRUE;
1504         }
1505
1506         /* Expand the selected row after 1/2 second */
1507         if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), dest_row)) {
1508                 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), dest_row, pos);
1509                 priv->timer_expander = g_timeout_add (500, expand_row_timeout, widget);
1510         }
1511
1512         /* Select the desired action. By default we pick MOVE */
1513         suggested_action = GDK_ACTION_MOVE;
1514
1515         if (context->actions == GDK_ACTION_COPY)
1516             gdk_drag_status(context, GDK_ACTION_COPY, time);
1517         else if (context->actions == GDK_ACTION_MOVE)
1518             gdk_drag_status(context, GDK_ACTION_MOVE, time);
1519         else if (context->actions & suggested_action)
1520             gdk_drag_status(context, suggested_action, time);
1521         else
1522             gdk_drag_status(context, GDK_ACTION_DEFAULT, time);
1523
1524  out:
1525         if (dest_row)
1526                 gtk_tree_path_free (dest_row);
1527         g_signal_stop_emission_by_name (widget, "drag-motion");
1528         return valid_location;
1529 }
1530
1531
1532 /* Folder view drag types */
1533 const GtkTargetEntry folder_view_drag_types[] =
1534 {
1535         { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, MODEST_FOLDER_ROW },
1536         { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_APP,    MODEST_HEADER_ROW }
1537 };
1538
1539 /*
1540  * This function sets the treeview as a source and a target for dnd
1541  * events. It also connects all the requirede signals.
1542  */
1543 static void
1544 setup_drag_and_drop (GtkTreeView *self)
1545 {
1546         /* Set up the folder view as a dnd destination. Set only the
1547            highlight flag, otherwise gtk will have a different
1548            behaviour */
1549         gtk_drag_dest_set (GTK_WIDGET (self),
1550                            GTK_DEST_DEFAULT_HIGHLIGHT,
1551                            folder_view_drag_types,
1552                            G_N_ELEMENTS (folder_view_drag_types),
1553                            GDK_ACTION_MOVE | GDK_ACTION_COPY);
1554
1555         g_signal_connect (G_OBJECT (self),
1556                           "drag_data_received",
1557                           G_CALLBACK (on_drag_data_received),
1558                           NULL);
1559
1560
1561         /* Set up the treeview as a dnd source */
1562         gtk_drag_source_set (GTK_WIDGET (self),
1563                              GDK_BUTTON1_MASK,
1564                              folder_view_drag_types,
1565                              G_N_ELEMENTS (folder_view_drag_types),
1566                              GDK_ACTION_MOVE | GDK_ACTION_COPY);
1567
1568         g_signal_connect (G_OBJECT (self),
1569                           "drag_motion",
1570                           G_CALLBACK (on_drag_motion),
1571                           NULL);
1572         
1573         g_signal_connect (G_OBJECT (self),
1574                           "drag_data_get",
1575                           G_CALLBACK (on_drag_data_get),
1576                           NULL);
1577
1578         g_signal_connect (G_OBJECT (self),
1579                           "drag_drop",
1580                           G_CALLBACK (drag_drop_cb),
1581                           NULL);
1582 }
1583
1584 /*
1585  * This function manages the navigation through the folders using the
1586  * keyboard or the hardware keys in the device
1587  */
1588 static gboolean
1589 on_key_pressed (GtkWidget *self,
1590                 GdkEventKey *event,
1591                 gpointer user_data)
1592 {
1593         GtkTreeSelection *selection;
1594         GtkTreeIter iter;
1595         GtkTreeModel *model;
1596         gboolean retval = FALSE;
1597
1598         /* Up and Down are automatically managed by the treeview */
1599         if (event->keyval == GDK_Return) {
1600                 /* Expand/Collapse the selected row */
1601                 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1602                 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
1603                         GtkTreePath *path;
1604
1605                         path = gtk_tree_model_get_path (model, &iter);
1606
1607                         if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (self), path))
1608                                 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1609                         else
1610                                 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1611                         gtk_tree_path_free (path);
1612                 }
1613                 /* No further processing */
1614                 retval = TRUE;
1615         }
1616
1617         return retval;
1618 }
1619
1620 /*
1621  * We listen to the changes in the local folder account name key,
1622  * because we want to show the right name in the view. The local
1623  * folder account name corresponds to the device name in the Maemo
1624  * version. We do this because we do not want to query gconf on each
1625  * tree view refresh. It's better to cache it and change whenever
1626  * necessary.
1627  */
1628 static void 
1629 on_configuration_key_changed (ModestConf* conf, 
1630                               const gchar *key, 
1631                               ModestConfEvent event, 
1632                               ModestFolderView *self)
1633 {
1634         ModestFolderViewPrivate *priv;
1635
1636         if (!key)
1637                 return;
1638
1639         g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
1640         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1641
1642         if (!strcmp (key, MODEST_CONF_DEVICE_NAME)) {
1643                 g_free (priv->local_account_name);
1644
1645                 if (event == MODEST_CONF_EVENT_KEY_UNSET)
1646                         priv->local_account_name = g_strdup (MODEST_LOCAL_FOLDERS_DEFAULT_DISPLAY_NAME);
1647                 else
1648                         priv->local_account_name = modest_conf_get_string (modest_runtime_get_conf(),
1649                                                                            MODEST_CONF_DEVICE_NAME, NULL);
1650
1651                 /* Force a redraw */
1652 #if GTK_CHECK_VERSION(2, 8, 0) /* gtk_tree_view_column_queue_resize is only available in GTK+ 2.8 */
1653                 GtkTreeViewColumn * tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self), 
1654                                                                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN);
1655                 gtk_tree_view_column_queue_resize (tree_column);
1656 #endif
1657         }
1658 }
1659
1660 void 
1661 modest_folder_view_set_style (ModestFolderView *self,
1662                               ModestFolderViewStyle style)
1663 {
1664         ModestFolderViewPrivate *priv;
1665
1666         g_return_if_fail (self);
1667         
1668         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1669
1670         priv->style = style;
1671 }
1672
1673 void
1674 modest_folder_view_set_account_id_of_visible_server_account (ModestFolderView *self,
1675                                                              const gchar *account_id)
1676 {
1677         ModestFolderViewPrivate *priv;
1678         ModestConf *conf;
1679         GtkTreeModel *model;
1680
1681         g_return_if_fail (self);
1682         
1683         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1684
1685         /* This will be used by the filter_row callback,
1686          * to decided which rows to show: */
1687         if (priv->visible_account_id)
1688                 g_free (priv->visible_account_id);
1689         priv->visible_account_id = g_strdup (account_id);
1690
1691         /* Save preferences */
1692         conf = modest_runtime_get_conf ();
1693         modest_widget_memory_save (conf, G_OBJECT (self), MODEST_CONF_FOLDER_VIEW_KEY);
1694
1695         /* Refilter */
1696         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1697         if (GTK_IS_TREE_MODEL_FILTER (model))
1698                 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
1699 }
1700
1701 const gchar *
1702 modest_folder_view_get_account_id_of_visible_server_account (ModestFolderView *self)
1703 {
1704         ModestFolderViewPrivate *priv;
1705
1706         g_return_val_if_fail (self, NULL);
1707         
1708         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1709
1710         return (const gchar *) priv->visible_account_id;
1711 }