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