* Avoid to get MMC root folder name in text_cell_data.
[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-account.h>
45 #include <modest-tny-folder.h>
46 #include <modest-tny-local-folders-account.h>
47 #include <modest-tny-outbox-account.h>
48 #include <modest-marshal.h>
49 #include <modest-icon-names.h>
50 #include <modest-tny-account-store.h>
51 #include <modest-text-utils.h>
52 #include <modest-runtime.h>
53 #include "modest-folder-view.h"
54 #include <modest-dnd.h>
55 #include <modest-platform.h>
56 #include <modest-widget-memory.h>
57 #include <modest-ui-actions.h>
58
59 /* 'private'/'protected' functions */
60 static void modest_folder_view_class_init  (ModestFolderViewClass *klass);
61 static void modest_folder_view_init        (ModestFolderView *obj);
62 static void modest_folder_view_finalize    (GObject *obj);
63
64 static void         tny_account_store_view_init (gpointer g, 
65                                                  gpointer iface_data);
66
67 static void         modest_folder_view_set_account_store (TnyAccountStoreView *self, 
68                                                           TnyAccountStore     *account_store);
69
70 static void         on_selection_changed   (GtkTreeSelection *sel, gpointer data);
71
72 static void         on_account_removed     (TnyAccountStore *self, 
73                                             TnyAccount *account,
74                                             gpointer user_data);
75
76 static void         on_account_inserted    (TnyAccountStore *self, 
77                                             TnyAccount *account,
78                                             gpointer user_data);
79
80 static void         on_account_changed    (TnyAccountStore *self, 
81                                             TnyAccount *account,
82                                             gpointer user_data);
83
84 static gint         cmp_rows               (GtkTreeModel *tree_model, 
85                                             GtkTreeIter *iter1, 
86                                             GtkTreeIter *iter2,
87                                             gpointer user_data);
88
89 static gboolean     filter_row             (GtkTreeModel *model,
90                                             GtkTreeIter *iter,
91                                             gpointer data);
92
93 static gboolean     on_key_pressed         (GtkWidget *self,
94                                             GdkEventKey *event,
95                                             gpointer user_data);
96
97 static void         on_configuration_key_changed  (ModestConf* conf, 
98                                                    const gchar *key, 
99                                                    ModestConfEvent event,
100                                                    ModestConfNotificationId notification_id, 
101                                                    ModestFolderView *self);
102
103 /* DnD functions */
104 static void         on_drag_data_get       (GtkWidget *widget, 
105                                             GdkDragContext *context, 
106                                             GtkSelectionData *selection_data, 
107                                             guint info, 
108                                             guint time, 
109                                             gpointer data);
110
111 static void         on_drag_data_received  (GtkWidget *widget, 
112                                             GdkDragContext *context, 
113                                             gint x, 
114                                             gint y, 
115                                             GtkSelectionData *selection_data, 
116                                             guint info, 
117                                             guint time, 
118                                             gpointer data);
119
120 static gboolean     on_drag_motion         (GtkWidget      *widget,
121                                             GdkDragContext *context,
122                                             gint            x,
123                                             gint            y,
124                                             guint           time,
125                                             gpointer        user_data);
126
127 static void expand_root_items (ModestFolderView *self);
128
129 static gint         expand_row_timeout     (gpointer data);
130
131 static void         setup_drag_and_drop    (GtkTreeView *self);
132
133 static gboolean     _clipboard_set_selected_data (ModestFolderView *folder_view, 
134                                                   gboolean delete);
135
136 static void         _clear_hidding_filter (ModestFolderView *folder_view);
137
138 static void         on_row_inserted_maybe_select_folder (GtkTreeModel     *tree_model, 
139                                                          GtkTreePath      *path, 
140                                                          GtkTreeIter      *iter,
141                                                          ModestFolderView *self);
142
143 enum {
144         FOLDER_SELECTION_CHANGED_SIGNAL,
145         FOLDER_DISPLAY_NAME_CHANGED_SIGNAL,
146         LAST_SIGNAL
147 };
148
149 typedef struct _ModestFolderViewPrivate ModestFolderViewPrivate;
150 struct _ModestFolderViewPrivate {
151         TnyAccountStore      *account_store;
152         TnyFolderStore       *cur_folder_store;
153
154         TnyFolder            *folder_to_select; /* folder to select after the next update */
155
156         ModestConfNotificationId notification_id;
157
158         gulong                changed_signal;
159         gulong                account_inserted_signal;
160         gulong                account_removed_signal;
161         gulong                account_changed_signal;
162         gulong                conf_key_signal;
163         
164         /* not unref this object, its a singlenton */
165         ModestEmailClipboard *clipboard;
166
167         /* Filter tree model */
168         gchar **hidding_ids;
169         guint n_selected;
170
171         TnyFolderStoreQuery  *query;
172         guint                 timer_expander;
173
174         gchar                *local_account_name;
175         gchar                *visible_account_id;
176         ModestFolderViewStyle style;
177
178         gboolean  reselect; /* we use this to force a reselection of the INBOX */
179         gboolean  show_non_move;
180 };
181 #define MODEST_FOLDER_VIEW_GET_PRIVATE(o)                       \
182         (G_TYPE_INSTANCE_GET_PRIVATE((o),                       \
183                                      MODEST_TYPE_FOLDER_VIEW,   \
184                                      ModestFolderViewPrivate))
185 /* globals */
186 static GObjectClass *parent_class = NULL;
187
188 static guint signals[LAST_SIGNAL] = {0}; 
189
190 GType
191 modest_folder_view_get_type (void)
192 {
193         static GType my_type = 0;
194         if (!my_type) {
195                 static const GTypeInfo my_info = {
196                         sizeof(ModestFolderViewClass),
197                         NULL,           /* base init */
198                         NULL,           /* base finalize */
199                         (GClassInitFunc) modest_folder_view_class_init,
200                         NULL,           /* class finalize */
201                         NULL,           /* class data */
202                         sizeof(ModestFolderView),
203                         1,              /* n_preallocs */
204                         (GInstanceInitFunc) modest_folder_view_init,
205                         NULL
206                 };
207
208                 static const GInterfaceInfo tny_account_store_view_info = {
209                         (GInterfaceInitFunc) tny_account_store_view_init, /* interface_init */
210                         NULL,         /* interface_finalize */
211                         NULL          /* interface_data */
212                 };
213
214                                 
215                 my_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
216                                                   "ModestFolderView",
217                                                   &my_info, 0);
218
219                 g_type_add_interface_static (my_type, 
220                                              TNY_TYPE_ACCOUNT_STORE_VIEW, 
221                                              &tny_account_store_view_info);
222         }
223         return my_type;
224 }
225
226 static void
227 modest_folder_view_class_init (ModestFolderViewClass *klass)
228 {
229         GObjectClass *gobject_class;
230         gobject_class = (GObjectClass*) klass;
231
232         parent_class            = g_type_class_peek_parent (klass);
233         gobject_class->finalize = modest_folder_view_finalize;
234
235         g_type_class_add_private (gobject_class,
236                                   sizeof(ModestFolderViewPrivate));
237         
238         signals[FOLDER_SELECTION_CHANGED_SIGNAL] = 
239                 g_signal_new ("folder_selection_changed",
240                               G_TYPE_FROM_CLASS (gobject_class),
241                               G_SIGNAL_RUN_FIRST,
242                               G_STRUCT_OFFSET (ModestFolderViewClass,
243                                                folder_selection_changed),
244                               NULL, NULL,
245                               modest_marshal_VOID__POINTER_BOOLEAN,
246                               G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_BOOLEAN);
247
248         /*
249          * This signal is emitted whenever the currently selected
250          * folder display name is computed. Note that the name could
251          * be different to the folder name, because we could append
252          * the unread messages count to the folder name to build the
253          * folder display name
254          */
255         signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL] = 
256                 g_signal_new ("folder-display-name-changed",
257                               G_TYPE_FROM_CLASS (gobject_class),
258                               G_SIGNAL_RUN_FIRST,
259                               G_STRUCT_OFFSET (ModestFolderViewClass,
260                                                folder_display_name_changed),
261                               NULL, NULL,
262                               g_cclosure_marshal_VOID__STRING,
263                               G_TYPE_NONE, 1, G_TYPE_STRING);
264 }
265
266
267
268 typedef struct 
269 {
270         ModestFolderView *self;
271         gchar *previous_name;
272 } GetMmcAccountNameData;
273
274
275 static void
276 text_cell_data  (GtkTreeViewColumn *column,  GtkCellRenderer *renderer,
277                  GtkTreeModel *tree_model,  GtkTreeIter *iter,  gpointer data)
278 {
279         ModestFolderViewPrivate *priv;
280         GObject *rendobj;
281         gchar *fname = NULL;
282         gint unread = 0;
283         gint all = 0;
284         TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
285         GObject *instance = NULL;
286
287         g_return_if_fail (column);
288         g_return_if_fail (tree_model);
289         g_return_if_fail (iter != NULL);
290
291         gtk_tree_model_get (tree_model, iter,
292                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &fname,
293                             TNY_GTK_FOLDER_STORE_TREE_MODEL_ALL_COLUMN, &all,
294                             TNY_GTK_FOLDER_STORE_TREE_MODEL_UNREAD_COLUMN, &unread,
295                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
296                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
297                             -1);
298         rendobj = G_OBJECT(renderer);
299
300         if (!fname)
301                 return;
302
303         if (!instance) {
304                 g_free (fname);
305                 return;
306         }
307
308         ModestFolderView *self = MODEST_FOLDER_VIEW (data);
309         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE (self);
310         
311         gchar *item_name = NULL;
312         gint item_weight = 400;
313         
314         if (type != TNY_FOLDER_TYPE_ROOT) {
315                 gint number = 0;
316                 
317                 if (modest_tny_folder_is_local_folder (TNY_FOLDER (instance)) ||
318                     modest_tny_folder_is_memory_card_folder (TNY_FOLDER (instance))) {
319                         type = modest_tny_folder_get_local_or_mmc_folder_type (TNY_FOLDER (instance));
320                         if (type != TNY_FOLDER_TYPE_UNKNOWN) {
321                                 g_free (fname);
322                                 fname = g_strdup(modest_local_folder_info_get_type_display_name (type));
323                         }
324                 }
325
326                 /* Select the number to show: the unread or unsent messages */
327                 if ((type == TNY_FOLDER_TYPE_DRAFTS) || (type == TNY_FOLDER_TYPE_OUTBOX))
328                         number = all;
329                 else
330                         number = unread;
331                 
332                 /* Use bold font style if there are unread or unset messages */
333                 if (number > 0) {
334                         item_name = g_strdup_printf ("%s (%d)", fname, number);
335                         item_weight = 800;
336                 } else {
337                         item_name = g_strdup (fname);
338                         item_weight = 400;
339                 }
340                 
341         } else if (TNY_IS_ACCOUNT (instance)) {
342                 /* If it's a server account */
343                 if (modest_tny_account_is_virtual_local_folders (
344                                 TNY_ACCOUNT (instance))) {
345                         item_name = g_strdup (priv->local_account_name);
346                         item_weight = 800;
347                 } else if (modest_tny_account_is_memory_card_account (
348                                 TNY_ACCOUNT (instance))) {
349                         /* fname is only correct when the items are first 
350                          * added to the model, not when the account is 
351                          * changed later, so get the name from the account
352                          * instance: */
353                         item_name = g_strdup (tny_account_get_name (TNY_ACCOUNT (instance)));
354                         item_weight = 800;
355                 } else {
356                         item_name = g_strdup (fname);
357                         item_weight = 800;
358                 }
359         }
360         
361         if (!item_name)
362                 item_name = g_strdup ("unknown");
363                         
364         if (item_name && item_weight) {
365                 /* Set the name in the treeview cell: */
366                 g_object_set (rendobj,"text", item_name, "weight", item_weight, NULL);
367                 
368                 /* Notify display name observers */
369                 /* TODO: What listens for this signal, and how can it use only the new name? */
370                 if (G_OBJECT (priv->cur_folder_store) == instance) {
371                         g_signal_emit (G_OBJECT(self),
372                                                signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
373                                                item_name);
374                 }
375                 g_free (item_name);
376                 
377         }
378                                 
379         g_object_unref (G_OBJECT (instance));
380         g_free (fname);
381 }
382
383 static void
384 icon_cell_data  (GtkTreeViewColumn *column,  GtkCellRenderer *renderer,
385                  GtkTreeModel *tree_model,  GtkTreeIter *iter, gpointer data)
386 {
387         GObject *rendobj = NULL, *instance = NULL;
388         GdkPixbuf *pixbuf = NULL;
389         TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
390         const gchar *account_id = NULL;
391         gboolean has_children;
392         
393         rendobj = G_OBJECT(renderer);
394         gtk_tree_model_get (tree_model, iter,
395                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
396                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
397                             -1);
398         has_children = gtk_tree_model_iter_has_child (tree_model, iter);
399
400         if (!instance) 
401                 return;
402
403         /* MERGE is not needed anymore as the folder now has the correct type jschmid */
404         /* We include the MERGE type here because it's used to create
405            the local OUTBOX folder */
406         if (type == TNY_FOLDER_TYPE_NORMAL || 
407             type == TNY_FOLDER_TYPE_UNKNOWN) {
408                 type = modest_tny_folder_guess_folder_type (TNY_FOLDER (instance));
409         }
410
411         switch (type) {
412         case TNY_FOLDER_TYPE_ROOT:
413                 if (TNY_IS_ACCOUNT (instance)) {
414                         
415                         if (modest_tny_account_is_virtual_local_folders (
416                                 TNY_ACCOUNT (instance))) {
417                                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_LOCAL_FOLDERS);
418                         }
419                         else {
420                                 account_id = tny_account_get_id (TNY_ACCOUNT (instance));
421                                 
422                                 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
423                                         pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_MMC);
424                                 else
425                                         pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_ACCOUNT);
426                         }
427                 }
428                 break;
429         case TNY_FOLDER_TYPE_INBOX:
430             pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_INBOX);
431             break;
432         case TNY_FOLDER_TYPE_OUTBOX:
433                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_OUTBOX);
434                 break;
435         case TNY_FOLDER_TYPE_JUNK:
436                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_JUNK);
437                 break;
438         case TNY_FOLDER_TYPE_SENT:
439                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_SENT);
440                 break;
441         case TNY_FOLDER_TYPE_TRASH:
442                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_TRASH);
443                 break;
444         case TNY_FOLDER_TYPE_DRAFTS:
445                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_DRAFTS);
446                 break;
447         case TNY_FOLDER_TYPE_NORMAL:
448         default:
449                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_NORMAL);
450                 break;
451         }
452         
453         g_object_unref (G_OBJECT (instance));
454
455         /* Set pixbuf */
456         g_object_set (rendobj, "pixbuf", pixbuf, NULL);
457         if (has_children && (pixbuf != NULL)) {
458                 GdkPixbuf *open_pixbuf, *closed_pixbuf;
459                 GdkPixbuf *open_emblem, *closed_emblem;
460                 open_pixbuf = gdk_pixbuf_copy (pixbuf);
461                 closed_pixbuf = gdk_pixbuf_copy (pixbuf);
462                 open_emblem = modest_platform_get_icon ("qgn_list_gene_fldr_exp");
463                 closed_emblem = modest_platform_get_icon ("qgn_list_gene_fldr_clp");
464
465                 if (open_emblem) {
466                         gdk_pixbuf_composite (open_emblem, open_pixbuf, 0, 0, 
467                                               MIN (gdk_pixbuf_get_width (open_emblem), 
468                                                    gdk_pixbuf_get_width (open_pixbuf)),
469                                               MIN (gdk_pixbuf_get_height (open_emblem), 
470                                                    gdk_pixbuf_get_height (open_pixbuf)),
471                                               0, 0, 1, 1, GDK_INTERP_NEAREST, 255);
472                         g_object_set (rendobj, "pixbuf-expander-open", open_pixbuf, NULL);
473                         g_object_unref (open_emblem);
474                 }
475                 if (closed_emblem) {
476                         gdk_pixbuf_composite (closed_emblem, closed_pixbuf, 0, 0, 
477                                               MIN (gdk_pixbuf_get_width (closed_emblem), 
478                                                    gdk_pixbuf_get_width (closed_pixbuf)),
479                                               MIN (gdk_pixbuf_get_height (closed_emblem), 
480                                                    gdk_pixbuf_get_height (closed_pixbuf)),
481                                               0, 0, 1, 1, GDK_INTERP_NEAREST, 255);
482                         g_object_set (rendobj, "pixbuf-expander-closed", closed_pixbuf, NULL);
483                         g_object_unref (closed_emblem);
484                 }
485                 if (closed_pixbuf)
486                         g_object_unref (closed_pixbuf);
487                 if (open_pixbuf)
488                         g_object_unref (open_pixbuf);
489         }
490
491         if (pixbuf != NULL)
492                 g_object_unref (pixbuf);
493 }
494
495 static void
496 add_columns (GtkWidget *treeview)
497 {
498         GtkTreeViewColumn *column;
499         GtkCellRenderer *renderer;
500         GtkTreeSelection *sel;
501
502         /* Create column */
503         column = gtk_tree_view_column_new ();   
504         
505         /* Set icon and text render function */
506         renderer = gtk_cell_renderer_pixbuf_new();
507         gtk_tree_view_column_pack_start (column, renderer, FALSE);
508         gtk_tree_view_column_set_cell_data_func(column, renderer,
509                                                 icon_cell_data, treeview, NULL);
510         
511         renderer = gtk_cell_renderer_text_new();
512         gtk_tree_view_column_pack_start (column, renderer, FALSE);
513         gtk_tree_view_column_set_cell_data_func(column, renderer,
514                                                 text_cell_data, treeview, NULL);
515         
516         /* Set selection mode */
517         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(treeview));
518         gtk_tree_selection_set_mode (sel, GTK_SELECTION_SINGLE);
519
520         /* Set treeview appearance */
521         gtk_tree_view_column_set_spacing (column, 2);
522         gtk_tree_view_column_set_resizable (column, TRUE);
523         gtk_tree_view_column_set_fixed_width (column, TRUE);            
524         gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(treeview), FALSE);
525         gtk_tree_view_set_enable_search (GTK_TREE_VIEW(treeview), FALSE);
526
527         /* Add column */
528         gtk_tree_view_append_column (GTK_TREE_VIEW(treeview),column);
529 }
530
531 static void
532 modest_folder_view_init (ModestFolderView *obj)
533 {
534         ModestFolderViewPrivate *priv;
535         ModestConf *conf;
536         
537         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
538         
539         priv->timer_expander = 0;
540         priv->account_store  = NULL;
541         priv->query          = NULL;
542         priv->style          = MODEST_FOLDER_VIEW_STYLE_SHOW_ALL;
543         priv->cur_folder_store   = NULL;
544         priv->visible_account_id = NULL;
545         priv->folder_to_select = NULL;
546
547         /* Initialize the local account name */
548         conf = modest_runtime_get_conf();
549         priv->local_account_name = modest_conf_get_string (conf, MODEST_CONF_DEVICE_NAME, NULL);
550
551         /* Init email clipboard */
552         priv->clipboard = modest_runtime_get_email_clipboard ();
553         priv->hidding_ids = NULL;
554         priv->n_selected = 0;
555         priv->reselect = FALSE;
556         priv->show_non_move = TRUE;
557
558         /* Build treeview */
559         add_columns (GTK_WIDGET (obj));
560
561         /* Setup drag and drop */
562         setup_drag_and_drop (GTK_TREE_VIEW(obj));
563
564         /* Connect signals */
565         g_signal_connect (G_OBJECT (obj), 
566                           "key-press-event", 
567                           G_CALLBACK (on_key_pressed), NULL);
568
569         /*
570          * Track changes in the local account name (in the device it
571          * will be the device name)
572          */
573         priv->notification_id = modest_conf_listen_to_namespace (conf, 
574                                                                  MODEST_CONF_NAMESPACE);
575         priv->conf_key_signal = g_signal_connect (G_OBJECT(conf), 
576                                                   "key_changed",
577                                                   G_CALLBACK(on_configuration_key_changed), 
578                                                   obj);
579 }
580
581 static void
582 tny_account_store_view_init (gpointer g, gpointer iface_data)
583 {
584         TnyAccountStoreViewIface *klass = (TnyAccountStoreViewIface *)g;
585
586         klass->set_account_store_func = modest_folder_view_set_account_store;
587
588         return;
589 }
590
591 static void
592 modest_folder_view_finalize (GObject *obj)
593 {
594         ModestFolderViewPrivate *priv;
595         GtkTreeSelection    *sel;
596         
597         g_return_if_fail (obj);
598         
599         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
600
601         if (priv->notification_id) {
602                 modest_conf_forget_namespace (modest_runtime_get_conf (),
603                                               MODEST_CONF_NAMESPACE,
604                                               priv->notification_id);
605         }
606
607         if (priv->timer_expander != 0) {
608                 g_source_remove (priv->timer_expander);
609                 priv->timer_expander = 0;
610         }
611
612         if (priv->account_store) {
613                 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
614                                              priv->account_inserted_signal);
615                 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
616                                              priv->account_removed_signal);
617                 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
618                                              priv->account_changed_signal);
619                 g_object_unref (G_OBJECT(priv->account_store));
620                 priv->account_store = NULL;
621         }
622
623         if (priv->query) {
624                 g_object_unref (G_OBJECT (priv->query));
625                 priv->query = NULL;
626         }
627
628 /*      modest_folder_view_disable_next_folder_selection (MODEST_FOLDER_VIEW(obj)); */
629         if (priv->folder_to_select) {
630                 g_object_unref (G_OBJECT(priv->folder_to_select));
631                 priv->folder_to_select = NULL;
632         }
633    
634         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(obj));
635         if (sel)
636                 g_signal_handler_disconnect (G_OBJECT(sel), priv->changed_signal);
637
638         g_free (priv->local_account_name);
639         g_free (priv->visible_account_id);
640         
641         if (priv->conf_key_signal) {
642                 g_signal_handler_disconnect (modest_runtime_get_conf (),
643                                              priv->conf_key_signal);
644                 priv->conf_key_signal = 0;
645         }
646
647         if (priv->cur_folder_store) {
648                 if (TNY_IS_FOLDER(priv->cur_folder_store))
649                         tny_folder_sync (TNY_FOLDER(priv->cur_folder_store), FALSE, NULL);
650                         /* FALSE --> expunge the message */
651
652                 g_object_unref (priv->cur_folder_store);
653                 priv->cur_folder_store = NULL;
654         }
655
656         /* Clear hidding array created by cut operation */
657         _clear_hidding_filter (MODEST_FOLDER_VIEW (obj));
658
659         G_OBJECT_CLASS(parent_class)->finalize (obj);
660 }
661
662
663 static void
664 modest_folder_view_set_account_store (TnyAccountStoreView *self, TnyAccountStore *account_store)
665 {
666         ModestFolderViewPrivate *priv;
667         TnyDevice *device;
668
669         g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
670         g_return_if_fail (TNY_IS_ACCOUNT_STORE (account_store));
671
672         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
673         device = tny_account_store_get_device (account_store);
674
675         if (G_UNLIKELY (priv->account_store)) {
676
677                 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
678                                                    priv->account_inserted_signal))
679                         g_signal_handler_disconnect (G_OBJECT (priv->account_store),
680                                                      priv->account_inserted_signal);
681                 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store), 
682                                                    priv->account_removed_signal))
683                         g_signal_handler_disconnect (G_OBJECT (priv->account_store), 
684                                                      priv->account_removed_signal);
685                 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store), 
686                                                    priv->account_changed_signal))
687                         g_signal_handler_disconnect (G_OBJECT (priv->account_store), 
688                                                      priv->account_changed_signal);
689                 g_object_unref (G_OBJECT (priv->account_store));
690         }
691
692         priv->account_store = g_object_ref (G_OBJECT (account_store));
693
694         priv->account_removed_signal = 
695                 g_signal_connect (G_OBJECT(account_store), "account_removed",
696                                   G_CALLBACK (on_account_removed), self);
697
698         priv->account_inserted_signal =
699                 g_signal_connect (G_OBJECT(account_store), "account_inserted",
700                                   G_CALLBACK (on_account_inserted), self);
701
702         priv->account_changed_signal =
703                 g_signal_connect (G_OBJECT(account_store), "account_changed",
704                                   G_CALLBACK (on_account_changed), self);
705
706         modest_folder_view_update_model (MODEST_FOLDER_VIEW (self), account_store);
707         
708         g_object_unref (G_OBJECT (device));
709 }
710
711 static void
712 on_account_inserted (TnyAccountStore *account_store, 
713                      TnyAccount *account,
714                      gpointer user_data)
715 {
716         ModestFolderViewPrivate *priv;
717         GtkTreeModel *sort_model, *filter_model;
718
719         /* Ignore transport account insertions, we're not showing them
720            in the folder view */
721         if (TNY_IS_TRANSPORT_ACCOUNT (account))
722                 return;
723
724         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (user_data);
725
726         /* If we're adding a new account, and there is no previous
727            one, we need to select the visible server account */
728         if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE &&
729             !priv->visible_account_id)
730                 modest_widget_memory_restore (modest_runtime_get_conf(), 
731                                               G_OBJECT (user_data),
732                                               MODEST_CONF_FOLDER_VIEW_KEY);
733
734         /* Get the inner model */
735         filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (user_data));
736         sort_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
737
738         /* Insert the account in the model */
739         tny_list_append (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
740                          G_OBJECT (account));
741 }
742
743
744 static void
745 on_account_changed (TnyAccountStore *account_store, TnyAccount *tny_account,
746                     gpointer user_data)
747 {
748         /* do nothing */
749 }
750
751
752
753 static void
754 on_account_removed (TnyAccountStore *account_store, 
755                     TnyAccount *account,
756                     gpointer user_data)
757 {
758         ModestFolderView *self = NULL;
759         ModestFolderViewPrivate *priv;
760         GtkTreeModel *sort_model, *filter_model;
761         GtkTreeSelection *sel = NULL;
762
763         /* Ignore transport account removals, we're not showing them
764            in the folder view */
765         if (TNY_IS_TRANSPORT_ACCOUNT (account))
766                 return;
767
768         self = MODEST_FOLDER_VIEW (user_data);
769         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
770
771         /* Invalidate the cur_folder_store only if the selected folder
772            belongs to the account that is being removed */
773         if (priv->cur_folder_store) {
774                 TnyAccount *selected_folder_account = NULL;
775
776                 if (TNY_IS_FOLDER (priv->cur_folder_store)) {
777                         selected_folder_account = 
778                                 tny_folder_get_account (TNY_FOLDER (priv->cur_folder_store));
779                 } else {
780                         selected_folder_account = 
781                                 TNY_ACCOUNT (g_object_ref (priv->cur_folder_store));
782                 }
783
784                 if (selected_folder_account == account) {
785                         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
786                         gtk_tree_selection_unselect_all (sel);
787                 }
788                 g_object_unref (selected_folder_account);
789         }
790
791         /* Invalidate row to select only if the folder to select
792            belongs to the account that is being removed*/
793         if (priv->folder_to_select) {
794                 TnyAccount *folder_to_select_account = NULL;
795
796                 folder_to_select_account = tny_folder_get_account (priv->folder_to_select);
797                 if (folder_to_select_account == account) {
798 /*                      modest_folder_view_disable_next_folder_selection (self); */
799                         g_object_unref (priv->folder_to_select);
800                         priv->folder_to_select = NULL;
801                 }
802                 g_object_unref (folder_to_select_account);
803         }
804
805         /* Remove the account from the model */
806         filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
807         sort_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
808         tny_list_remove (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
809                          G_OBJECT (account));
810
811         /* If the removed account is the currently viewed one then
812            clear the configuration value. The new visible account will be the default account */
813         if (priv->visible_account_id &&
814             !strcmp (priv->visible_account_id, tny_account_get_id (account))) {
815
816                 /* Clear the current visible account_id */
817                 modest_folder_view_set_account_id_of_visible_server_account (self, NULL);
818
819                 /* Call the restore method, this will set the new visible account */
820                 modest_widget_memory_restore (modest_runtime_get_conf(), G_OBJECT(self),
821                                               MODEST_CONF_FOLDER_VIEW_KEY);
822         }
823
824         /* Select the INBOX */
825         modest_folder_view_select_first_inbox_or_local (self);
826 }
827
828 void
829 modest_folder_view_set_title (ModestFolderView *self, const gchar *title)
830 {
831         GtkTreeViewColumn *col;
832         
833         g_return_if_fail (self);
834
835         col = gtk_tree_view_get_column (GTK_TREE_VIEW(self), 0);
836         if (!col) {
837                 g_printerr ("modest: failed get column for title\n");
838                 return;
839         }
840
841         gtk_tree_view_column_set_title (col, title);
842         gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self),
843                                            title != NULL);
844 }
845
846 static gboolean
847 modest_folder_view_on_map (ModestFolderView *self, 
848                            GdkEventExpose *event,
849                            gpointer data)
850 {
851         ModestFolderViewPrivate *priv;
852
853         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
854
855         /* This won't happen often */
856         if (G_UNLIKELY (priv->reselect)) {
857                 /* Select the first inbox or the local account if not found */
858
859                 /* TODO: this could cause a lock at startup, so we
860                    comment it for the moment. We know that this will
861                    be a bug, because the INBOX is not selected, but we
862                    need to rewrite some parts of Modest to avoid the
863                    deathlock situation */
864                 /* TODO: check if this is still the case */
865                 priv->reselect = FALSE;
866                 modest_folder_view_select_first_inbox_or_local (self);
867                 /* Notify the display name observers */
868                 g_signal_emit (G_OBJECT(self),
869                                signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
870                                NULL);
871         }
872
873         expand_root_items (self); 
874
875         return FALSE;
876 }
877
878 GtkWidget*
879 modest_folder_view_new (TnyFolderStoreQuery *query)
880 {
881         GObject *self;
882         ModestFolderViewPrivate *priv;
883         GtkTreeSelection *sel;
884         
885         self = G_OBJECT (g_object_new (MODEST_TYPE_FOLDER_VIEW, NULL));
886         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
887
888         if (query)
889                 priv->query = g_object_ref (query);
890         
891         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
892         priv->changed_signal = g_signal_connect (sel, "changed",
893                                                  G_CALLBACK (on_selection_changed), self);
894
895         g_signal_connect (self, "expose-event", G_CALLBACK (modest_folder_view_on_map), NULL);
896
897         return GTK_WIDGET(self);
898 }
899
900 /* this feels dirty; any other way to expand all the root items? */
901 static void
902 expand_root_items (ModestFolderView *self)
903 {
904         GtkTreePath *path;
905         path = gtk_tree_path_new_first ();
906
907         /* all folders should have child items, so.. */
908         while (gtk_tree_view_expand_row (GTK_TREE_VIEW(self), path, FALSE))
909                 gtk_tree_path_next (path);
910         
911         gtk_tree_path_free (path);
912 }
913
914 /*
915  * We use this function to implement the
916  * MODEST_FOLDER_VIEW_STYLE_SHOW_ONE style. We only show the default
917  * account in this case, and the local folders.
918  */
919 static gboolean 
920 filter_row (GtkTreeModel *model,
921             GtkTreeIter *iter,
922             gpointer data)
923 {
924         ModestFolderViewPrivate *priv;
925         gboolean retval = TRUE;
926         TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
927         GObject *instance = NULL;
928         const gchar *id = NULL;
929         guint i;
930         gboolean found = FALSE;
931         gboolean cleared = FALSE;
932
933         g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (data), FALSE);
934         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (data);
935
936         gtk_tree_model_get (model, iter,
937                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
938                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
939                             -1);
940
941         /* Do not show if there is no instance, this could indeed
942            happen when the model is being modified while it's being
943            drawn. This could occur for example when moving folders
944            using drag&drop */
945         if (!instance)
946                 return FALSE;
947
948         if (type == TNY_FOLDER_TYPE_ROOT) {
949                 /* TNY_FOLDER_TYPE_ROOT means that the instance is an
950                    account instead of a folder. */
951                 if (TNY_IS_ACCOUNT (instance)) {
952                         TnyAccount *acc = TNY_ACCOUNT (instance);
953                         const gchar *account_id = tny_account_get_id (acc);
954         
955                         /* If it isn't a special folder, 
956                          * don't show it unless it is the visible account: */
957                         if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE &&
958                             !modest_tny_account_is_virtual_local_folders (acc) &&
959                             strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
960                                 
961                                 /* Show only the visible account id */
962                                 if (priv->visible_account_id) {
963                                         if (strcmp (account_id, priv->visible_account_id))
964                                                 retval = FALSE;
965                                 } else {
966                                         retval = FALSE;
967                                 }                               
968                         }
969                         
970                         /* Never show these to the user. They are merged into one folder 
971                          * in the local-folders account instead: */
972                         if (retval && MODEST_IS_TNY_OUTBOX_ACCOUNT (acc))
973                                 retval = FALSE;
974                 }
975         }
976
977         /* Check hiding (if necessary) */
978         cleared = modest_email_clipboard_cleared (priv->clipboard);            
979         if ((retval) && (!cleared) && (TNY_IS_FOLDER (instance))) {
980                 id = tny_folder_get_id (TNY_FOLDER(instance));
981                 if (priv->hidding_ids != NULL)
982                         for (i=0; i < priv->n_selected && !found; i++)
983                                 if (priv->hidding_ids[i] != NULL && id != NULL)
984                                         found = (!strcmp (priv->hidding_ids[i], id));
985                 
986                 retval = !found;
987         }
988         
989         
990         /* If this is a move to dialog, hide Sent, Outbox and Drafts
991         folder as no message can be move there according to UI specs */
992         if (!priv->show_non_move)
993         {
994                 switch (type)
995                 {
996                         case TNY_FOLDER_TYPE_OUTBOX:
997                         case TNY_FOLDER_TYPE_SENT:
998                         case TNY_FOLDER_TYPE_DRAFTS:
999                                 retval = FALSE;
1000                                 break;
1001                         case TNY_FOLDER_TYPE_UNKNOWN:
1002                         case TNY_FOLDER_TYPE_NORMAL:
1003                                 type = modest_tny_folder_guess_folder_type(TNY_FOLDER(instance));
1004                                 if (type == TNY_FOLDER_TYPE_OUTBOX || type == TNY_FOLDER_TYPE_SENT
1005                                                 || type == TNY_FOLDER_TYPE_DRAFTS)
1006                                 {
1007                                         retval = FALSE;
1008                                 }
1009                                 break;
1010                         default:
1011                                 break;  
1012                 }       
1013         }
1014         
1015         /* Free */
1016         g_object_unref (instance);
1017
1018         return retval;
1019 }
1020
1021
1022 gboolean
1023 modest_folder_view_update_model (ModestFolderView *self,
1024                                  TnyAccountStore *account_store)
1025 {
1026         ModestFolderViewPrivate *priv;
1027         GtkTreeModel *model /* , *old_model */;
1028         /* TnyAccount *local_account; */
1029         TnyList *model_as_list;
1030
1031         g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (self), FALSE);
1032         g_return_val_if_fail (account_store, FALSE);
1033
1034         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1035         
1036         /* Notify that there is no folder selected */
1037         g_signal_emit (G_OBJECT(self), 
1038                        signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
1039                        NULL, FALSE);
1040         if (priv->cur_folder_store) {
1041                 g_object_unref (priv->cur_folder_store);
1042                 priv->cur_folder_store = NULL;
1043         }
1044
1045         /* FIXME: the local accounts are not shown when the query
1046            selects only the subscribed folders. */
1047 /*      model        = tny_gtk_folder_store_tree_model_new (TRUE, priv->query); */
1048         model        = tny_gtk_folder_store_tree_model_new (NULL);
1049         
1050         /* Deal with the model via its TnyList Interface,
1051          * filling the TnyList via a get_accounts() call: */
1052         model_as_list = TNY_LIST(model);
1053
1054         /* Get the accounts: */
1055         tny_account_store_get_accounts (TNY_ACCOUNT_STORE(account_store),
1056                                         model_as_list,
1057                                         TNY_ACCOUNT_STORE_STORE_ACCOUNTS);
1058         g_object_unref (model_as_list);
1059         model_as_list = NULL;   
1060                                                      
1061         GtkTreeModel *filter_model = NULL, *sortable = NULL;
1062
1063         sortable = gtk_tree_model_sort_new_with_model (model);
1064         gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
1065                                               TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, 
1066                                               GTK_SORT_ASCENDING);
1067         gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
1068                                          TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
1069                                          cmp_rows, NULL, NULL);
1070
1071         /* Create filter model */
1072         filter_model = gtk_tree_model_filter_new (sortable, NULL);
1073         gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
1074                                                 filter_row,
1075                                                 self,
1076                                                 NULL);
1077
1078         /* Set new model */
1079         gtk_tree_view_set_model (GTK_TREE_VIEW(self), filter_model);
1080         g_signal_connect (G_OBJECT(filter_model), "row-inserted",
1081                           (GCallback) on_row_inserted_maybe_select_folder, self);
1082
1083
1084         g_object_unref (model);
1085         g_object_unref (filter_model);          
1086         g_object_unref (sortable);
1087         
1088         /* Force a reselection of the INBOX next time the widget is shown */
1089         priv->reselect = TRUE;
1090                         
1091         return TRUE;
1092 }
1093
1094
1095 static void
1096 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
1097 {
1098         GtkTreeModel *model = NULL;
1099         TnyFolderStore *folder = NULL;
1100         GtkTreeIter iter;
1101         ModestFolderView *tree_view = NULL;
1102         ModestFolderViewPrivate *priv = NULL;
1103         gboolean selected = FALSE;
1104
1105         g_return_if_fail (sel);
1106         g_return_if_fail (user_data);
1107         
1108         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
1109
1110         selected = gtk_tree_selection_get_selected (sel, &model, &iter);
1111 /*      if(!gtk_tree_selection_get_selected (sel, &model, &iter)) */
1112 /*              return; */
1113
1114         /* Notify the display name observers */
1115         g_signal_emit (G_OBJECT(user_data),
1116                        signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
1117                        NULL);
1118
1119         tree_view = MODEST_FOLDER_VIEW (user_data);
1120
1121         if (selected) {
1122                 gtk_tree_model_get (model, &iter,
1123                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
1124                                     -1);
1125
1126                 /* If the folder is the same do not notify */
1127                 if (priv->cur_folder_store == folder && folder) {
1128                         g_object_unref (folder);
1129                         return;
1130                 }
1131         }
1132         
1133         /* Current folder was unselected */
1134         if (priv->cur_folder_store) {
1135                 g_signal_emit (G_OBJECT(tree_view), signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
1136                        priv->cur_folder_store, FALSE);
1137
1138                 if (TNY_IS_FOLDER(priv->cur_folder_store))
1139                         tny_folder_sync_async (TNY_FOLDER(priv->cur_folder_store),
1140                                                FALSE, NULL, NULL, NULL);
1141
1142                 /* FALSE --> don't expunge the messages */
1143
1144                 g_object_unref (priv->cur_folder_store);
1145                 priv->cur_folder_store = NULL;
1146         }
1147
1148         /* New current references */
1149         priv->cur_folder_store = folder;
1150
1151         /* New folder has been selected */
1152         g_signal_emit (G_OBJECT(tree_view),
1153                        signals[FOLDER_SELECTION_CHANGED_SIGNAL],
1154                        0, priv->cur_folder_store, TRUE);
1155 }
1156
1157 TnyFolderStore *
1158 modest_folder_view_get_selected (ModestFolderView *self)
1159 {
1160         ModestFolderViewPrivate *priv;
1161
1162         g_return_val_if_fail (self, NULL);
1163         
1164         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1165         if (priv->cur_folder_store)
1166                 g_object_ref (priv->cur_folder_store);
1167
1168         return priv->cur_folder_store;
1169 }
1170
1171 static gint
1172 get_cmp_rows_type_pos (GObject *folder)
1173 {
1174         /* Remote accounts -> Local account -> MMC account .*/
1175         /* 0, 1, 2 */
1176         
1177         if (TNY_IS_ACCOUNT (folder) && 
1178                 modest_tny_account_is_virtual_local_folders (
1179                         TNY_ACCOUNT (folder))) {
1180                 return 1;
1181         } else if (TNY_IS_ACCOUNT (folder)) {
1182                 TnyAccount *account = TNY_ACCOUNT (folder);
1183                 const gchar *account_id = tny_account_get_id (account);
1184                 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
1185                         return 2;
1186                 else
1187                         return 0;
1188         }
1189         else {
1190                 printf ("DEBUG: %s: unexpected type.\n", __FUNCTION__);
1191                 return -1; /* Should never happen */
1192         }
1193 }
1194
1195 static gint
1196 get_cmp_subfolder_type_pos (TnyFolderType t)
1197 {
1198         /* Inbox, Outbox, Drafts, Sent, User */
1199         /* 0, 1, 2, 3, 4 */
1200
1201         switch (t) {
1202         case TNY_FOLDER_TYPE_INBOX:
1203                 return 0;
1204                 break;
1205         case TNY_FOLDER_TYPE_OUTBOX:
1206                 return 1;
1207                 break;
1208         case TNY_FOLDER_TYPE_DRAFTS:
1209                 return 2;
1210                 break;
1211         case TNY_FOLDER_TYPE_SENT:
1212                 return 3;
1213                 break;
1214         default:
1215                 return 4;
1216         }
1217 }
1218
1219 /*
1220  * This function orders the mail accounts according to these rules:
1221  * 1st - remote accounts
1222  * 2nd - local account
1223  * 3rd - MMC account
1224  */
1225 static gint
1226 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1227           gpointer user_data)
1228 {
1229         gint cmp = 0;
1230         gchar *name1 = NULL;
1231         gchar *name2 = NULL;
1232         TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1233         TnyFolderType type2 = TNY_FOLDER_TYPE_UNKNOWN;
1234         GObject *folder1 = NULL;
1235         GObject *folder2 = NULL;
1236
1237         gtk_tree_model_get (tree_model, iter1,
1238                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name1,
1239                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
1240                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder1,
1241                             -1);
1242         gtk_tree_model_get (tree_model, iter2,
1243                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name2,
1244                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type2,
1245                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder2,
1246                             -1);
1247
1248         /* Return if we get no folder. This could happen when folder
1249            operations are happening. The model is updated after the
1250            folder copy/move actually occurs, so there could be
1251            situations where the model to be drawn is not correct */
1252         if (!folder1 || !folder2)
1253                 goto finish;
1254
1255         if (type == TNY_FOLDER_TYPE_ROOT) {
1256                 /* Compare the types, so that 
1257                  * Remote accounts -> Local account -> MMC account .*/
1258                 const gint pos1 = get_cmp_rows_type_pos (folder1);
1259                 const gint pos2 = get_cmp_rows_type_pos (folder2);
1260                 /* printf ("DEBUG: %s:\n  type1=%s, pos1=%d\n  type2=%s, pos2=%d\n", 
1261                         __FUNCTION__, G_OBJECT_TYPE_NAME(folder1), pos1, G_OBJECT_TYPE_NAME(folder2), pos2); */
1262                 if (pos1 <  pos2)
1263                         cmp = -1;
1264                 else if (pos1 > pos2)
1265                         cmp = 1;
1266                 else {
1267                         /* Compare items of the same type: */
1268                         
1269                         TnyAccount *account1 = NULL;
1270                         if (TNY_IS_ACCOUNT (folder1))
1271                                 account1 = TNY_ACCOUNT (folder1);
1272                                 
1273                         TnyAccount *account2 = NULL;
1274                         if (TNY_IS_ACCOUNT (folder2))
1275                                 account2 = TNY_ACCOUNT (folder2);
1276                                 
1277                         const gchar *account_id = account1 ? tny_account_get_id (account1) : NULL;
1278                         const gchar *account_id2 = account2 ? tny_account_get_id (account2) : NULL;
1279         
1280                         if (!account_id && !account_id2) {
1281                                 cmp = 0;
1282                         } else if (!account_id) {
1283                                 cmp = -1;
1284                         } else if (!account_id2) {
1285                                 cmp = +1;
1286                         } else if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
1287                                 cmp = +1;
1288                         } else {
1289                                 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
1290                         }
1291                 }
1292         } else {
1293                 gint cmp1 = 0, cmp2 = 0;
1294                 /* get the parent to know if it's a local folder */
1295
1296                 GtkTreeIter parent;
1297                 gboolean has_parent;
1298                 has_parent = gtk_tree_model_iter_parent (tree_model, &parent, iter1);
1299                 if (has_parent) {
1300                         GObject *parent_folder;
1301                         TnyFolderType parent_type = TNY_FOLDER_TYPE_UNKNOWN;
1302                         gtk_tree_model_get (tree_model, &parent, 
1303                                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &parent_type,
1304                                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &parent_folder,
1305                                             -1);
1306                         if ((parent_type == TNY_FOLDER_TYPE_ROOT) &&
1307                             TNY_IS_ACCOUNT (parent_folder) &&
1308                             modest_tny_account_is_virtual_local_folders (TNY_ACCOUNT (parent_folder))) {
1309                                 cmp1 = get_cmp_subfolder_type_pos (modest_tny_folder_get_local_or_mmc_folder_type (TNY_FOLDER (folder1)));
1310                                 cmp2 = get_cmp_subfolder_type_pos (modest_tny_folder_get_local_or_mmc_folder_type (TNY_FOLDER (folder2)));
1311                         }
1312                         g_object_unref (parent_folder);
1313                 }
1314
1315                 /* if they are not local folders */
1316                 if (cmp1 == cmp2) {
1317                         cmp1 = get_cmp_subfolder_type_pos (tny_folder_get_folder_type (TNY_FOLDER (folder1)));
1318                         cmp2 = get_cmp_subfolder_type_pos (tny_folder_get_folder_type (TNY_FOLDER (folder2)));
1319                 }
1320
1321                 if (cmp1 == cmp2)
1322                         cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
1323                 else 
1324                         cmp = (cmp1 - cmp2);
1325         }
1326
1327 finish: 
1328         if (folder1)
1329                 g_object_unref(G_OBJECT(folder1));
1330         if (folder2)
1331                 g_object_unref(G_OBJECT(folder2));
1332
1333         g_free (name1);
1334         g_free (name2);
1335
1336         return cmp;     
1337 }
1338
1339 /*****************************************************************************/
1340 /*                        DRAG and DROP stuff                                */
1341 /*****************************************************************************/
1342
1343 /*
1344  * This function fills the #GtkSelectionData with the row and the
1345  * model that has been dragged. It's called when this widget is a
1346  * source for dnd after the event drop happened
1347  */
1348 static void
1349 on_drag_data_get (GtkWidget *widget, 
1350                   GdkDragContext *context, 
1351                   GtkSelectionData *selection_data, 
1352                   guint info, 
1353                   guint time, 
1354                   gpointer data)
1355 {
1356         GtkTreeSelection *selection;
1357         GtkTreeModel *model;
1358         GtkTreeIter iter;
1359         GtkTreePath *source_row;
1360
1361         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1362         gtk_tree_selection_get_selected (selection, &model, &iter);
1363         source_row = gtk_tree_model_get_path (model, &iter);
1364
1365         gtk_tree_set_row_drag_data (selection_data,
1366                                     model,
1367                                     source_row);
1368
1369         gtk_tree_path_free (source_row);
1370 }
1371
1372 typedef struct _DndHelper {
1373         gboolean delete_source;
1374         GtkTreePath *source_row;
1375         GdkDragContext *context;
1376         guint time;
1377 } DndHelper;
1378
1379
1380 /*
1381  * This function is the callback of the
1382  * modest_mail_operation_xfer_msgs () and
1383  * modest_mail_operation_xfer_folder() calls. We check here if the
1384  * message/folder was correctly asynchronously transferred. The reason
1385  * to use the same callback is that the code is the same, it only has
1386  * to check that the operation went fine and then finalize the drag
1387  * and drop action
1388  */
1389 static void
1390 on_progress_changed (ModestMailOperation *mail_op, 
1391                      ModestMailOperationState *state,
1392                      gpointer user_data)
1393 {
1394         gboolean success;
1395         DndHelper *helper;
1396
1397         helper = (DndHelper *) user_data;
1398
1399         if (!state->finished)
1400                 return;
1401
1402         if (state->status == MODEST_MAIL_OPERATION_STATUS_SUCCESS) {
1403                 success = TRUE;
1404         } else {
1405                 success = FALSE;
1406         }
1407
1408         /* Notify the drag source. Never call delete, the monitor will
1409            do the job if needed */
1410         gtk_drag_finish (helper->context, success, FALSE, helper->time);
1411
1412         /* Free the helper */
1413         gtk_tree_path_free (helper->source_row);
1414         g_slice_free (DndHelper, helper);
1415 }
1416
1417
1418 /* get the folder for the row the treepath refers to. */
1419 /* folder must be unref'd */
1420 static TnyFolder*
1421 tree_path_to_folder (GtkTreeModel *model, GtkTreePath *path)
1422 {
1423         GtkTreeIter iter;
1424         TnyFolder *folder = NULL;
1425         
1426         if (gtk_tree_model_get_iter (model,&iter, path))
1427                 gtk_tree_model_get (model, &iter,
1428                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
1429                                     -1);
1430         return folder;
1431 }
1432
1433 static void 
1434 show_banner_move_target_error ()
1435 {
1436         ModestWindow *main_window;
1437
1438         main_window = modest_window_mgr_get_main_window(
1439                         modest_runtime_get_window_mgr());
1440                                 
1441         modest_platform_information_banner(GTK_WIDGET(main_window),
1442                         NULL, _("mail_in_ui_folder_move_target_error"));
1443 }
1444
1445 /*
1446  * This function is used by drag_data_received_cb to manage drag and
1447  * drop of a header, i.e, and drag from the header view to the folder
1448  * view.
1449  */
1450 static void
1451 drag_and_drop_from_header_view (GtkTreeModel *source_model,
1452                                 GtkTreeModel *dest_model,
1453                                 GtkTreePath  *dest_row,
1454                                 DndHelper    *helper)
1455 {
1456         TnyList *headers = NULL;
1457         TnyHeader *header = NULL;
1458         TnyFolder *folder = NULL;
1459         ModestMailOperation *mail_op = NULL;
1460         GtkTreeIter source_iter;
1461         ModestWindowMgr *mgr = NULL; /*no need for unref*/
1462         ModestWindow *main_win = NULL; /*no need for unref*/
1463
1464         g_return_if_fail (GTK_IS_TREE_MODEL(source_model));
1465         g_return_if_fail (GTK_IS_TREE_MODEL(dest_model));
1466         g_return_if_fail (dest_row);
1467         g_return_if_fail (helper);
1468
1469         /* Get header */
1470         gtk_tree_model_get_iter (source_model, &source_iter, helper->source_row);
1471         gtk_tree_model_get (source_model, &source_iter, 
1472                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, 
1473                             &header, -1);
1474         if (!TNY_IS_HEADER(header)) {
1475                 g_warning ("BUG: %s could not get a valid header", __FUNCTION__);
1476                 goto cleanup;
1477         }
1478         
1479         /* Check if the selected message is in msg-view. If it is than
1480          * do not enable drag&drop on that. */
1481         mgr = modest_runtime_get_window_mgr ();
1482         if (modest_window_mgr_find_registered_header(mgr, header, NULL))
1483                 goto cleanup;
1484
1485         /* Get Folder */
1486         folder = tree_path_to_folder (dest_model, dest_row);
1487         if (!TNY_IS_FOLDER(folder)) {
1488                 g_warning ("BUG: %s could not get a valid folder", __FUNCTION__);
1489                 show_banner_move_target_error();
1490                 goto cleanup;
1491         }
1492         if (modest_tny_folder_get_rules(folder) & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE) {
1493                 g_debug ("folder rules: cannot write to that folder");
1494                 goto cleanup;
1495         }
1496         
1497         headers = tny_simple_list_new ();
1498         tny_list_append (headers, G_OBJECT (header));
1499
1500         main_win = modest_window_mgr_get_main_window(mgr);
1501         if(msgs_move_to_confirmation(GTK_WINDOW(main_win), folder, TRUE, headers)
1502                         == GTK_RESPONSE_CANCEL)
1503                 goto cleanup;
1504
1505         /* Transfer message */
1506         mail_op = modest_mail_operation_new_with_error_handling (MODEST_MAIL_OPERATION_TYPE_RECEIVE, 
1507                                                                  NULL,
1508                                                                  modest_ui_actions_move_folder_error_handler,
1509                                                                  NULL);
1510         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1511                                          mail_op);
1512         g_signal_connect (G_OBJECT (mail_op), "progress-changed",
1513                           G_CALLBACK (on_progress_changed), helper);
1514
1515         modest_mail_operation_xfer_msgs (mail_op, 
1516                                          headers, 
1517                                          folder, 
1518                                          helper->delete_source, 
1519                                          NULL, NULL);
1520         
1521         /* Frees */
1522 cleanup:
1523         if (G_IS_OBJECT(mail_op))
1524                 g_object_unref (G_OBJECT (mail_op));
1525         if (G_IS_OBJECT(header))
1526                 g_object_unref (G_OBJECT (header));
1527         if (G_IS_OBJECT(folder))
1528                 g_object_unref (G_OBJECT (folder));
1529         if (G_IS_OBJECT(headers))
1530                 g_object_unref (headers);
1531 }
1532
1533 /*
1534  * This function is used by drag_data_received_cb to manage drag and
1535  * drop of a folder, i.e, and drag from the folder view to the same
1536  * folder view.
1537  */
1538 static void
1539 drag_and_drop_from_folder_view (GtkTreeModel     *source_model,
1540                                 GtkTreeModel     *dest_model,
1541                                 GtkTreePath      *dest_row,
1542                                 GtkSelectionData *selection_data,
1543                                 DndHelper        *helper)
1544 {
1545         ModestMailOperation *mail_op = NULL;
1546         GtkTreeIter dest_iter, iter;
1547         TnyFolderStore *dest_folder = NULL;
1548         TnyFolder *folder = NULL;
1549         gboolean forbidden = FALSE;
1550
1551         if (!forbidden) {
1552                 /* check the folder rules for the destination */
1553                 folder = tree_path_to_folder (dest_model, dest_row);
1554                 if (TNY_IS_FOLDER(folder)) {
1555                         ModestTnyFolderRules rules =
1556                                         modest_tny_folder_get_rules (folder);
1557                         forbidden = rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE;
1558
1559                         if (forbidden)
1560                                 g_debug ("folder rules: cannot write to that folder");
1561                 } else if (TNY_IS_FOLDER_STORE(folder)){
1562                         /* enable local root as destination for folders */
1563                         if (!MODEST_IS_TNY_LOCAL_FOLDERS_ACCOUNT (folder)
1564                                         && TNY_IS_ACCOUNT (folder))
1565                                 forbidden = TRUE;
1566                 }
1567                 g_object_unref (folder);
1568         }
1569         if (!forbidden) {
1570                 /* check the folder rules for the source */
1571                 folder = tree_path_to_folder (source_model, helper->source_row);
1572                 if (TNY_IS_FOLDER(folder)) {
1573                         ModestTnyFolderRules rules =
1574                                         modest_tny_folder_get_rules (folder);
1575                         forbidden = rules & MODEST_FOLDER_RULES_FOLDER_NON_MOVEABLE;
1576                         if (forbidden)
1577                                 g_debug ("folder rules: cannot move that folder");
1578                 } else
1579                         forbidden = TRUE;
1580                 g_object_unref (folder);
1581         }
1582
1583         
1584         /* Check if the drag is possible */
1585         if (forbidden || !gtk_tree_path_compare (helper->source_row, dest_row)) {
1586                 gtk_drag_finish (helper->context, FALSE, FALSE, helper->time);
1587                 gtk_tree_path_free (helper->source_row);        
1588                 g_slice_free (DndHelper, helper);
1589                 return;
1590         }
1591
1592         /* Get data */
1593         gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
1594         gtk_tree_model_get (dest_model, &dest_iter, 
1595                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, 
1596                             &dest_folder, -1);
1597         gtk_tree_model_get_iter (source_model, &iter, helper->source_row);
1598         gtk_tree_model_get (source_model, &iter,
1599                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
1600                             &folder, -1);
1601
1602         /* Offer the connection dialog if necessary, for the destination parent folder and source folder: */
1603         if (modest_platform_connect_and_wait_if_network_folderstore (
1604                                 NULL, dest_folder) && 
1605                         modest_platform_connect_and_wait_if_network_folderstore (
1606                                 NULL, TNY_FOLDER_STORE (folder))) {
1607                 /* Do the mail operation */
1608                 mail_op = modest_mail_operation_new_with_error_handling (
1609                                 MODEST_MAIL_OPERATION_TYPE_RECEIVE, 
1610                                 NULL,
1611                                 modest_ui_actions_move_folder_error_handler,
1612                                 NULL);
1613                 modest_mail_operation_queue_add (
1614                                 modest_runtime_get_mail_operation_queue (), 
1615                                 mail_op);
1616                 g_signal_connect (
1617                                 G_OBJECT (mail_op),
1618                                 "progress-changed",
1619                                 G_CALLBACK (on_progress_changed),
1620                                 helper);
1621
1622                 modest_mail_operation_xfer_folder (mail_op, 
1623                                 folder, 
1624                                 dest_folder,
1625                                 helper->delete_source,
1626                                 NULL,
1627                                 NULL);
1628
1629                 g_object_unref (G_OBJECT (mail_op));    
1630         }
1631         
1632         /* Frees */
1633         g_object_unref (G_OBJECT (dest_folder));
1634         g_object_unref (G_OBJECT (folder));
1635 }
1636
1637 /*
1638  * This function receives the data set by the "drag-data-get" signal
1639  * handler. This information comes within the #GtkSelectionData. This
1640  * function will manage both the drags of folders of the treeview and
1641  * drags of headers of the header view widget.
1642  */
1643 static void 
1644 on_drag_data_received (GtkWidget *widget, 
1645                        GdkDragContext *context, 
1646                        gint x, 
1647                        gint y, 
1648                        GtkSelectionData *selection_data, 
1649                        guint target_type, 
1650                        guint time, 
1651                        gpointer data)
1652 {
1653         GtkWidget *source_widget;
1654         GtkTreeModel *dest_model, *source_model;
1655         GtkTreePath *source_row, *dest_row;
1656         GtkTreeViewDropPosition pos;
1657         gboolean success = FALSE, delete_source = FALSE;
1658         DndHelper *helper = NULL; 
1659
1660         /* Do not allow further process */
1661         g_signal_stop_emission_by_name (widget, "drag-data-received");
1662         source_widget = gtk_drag_get_source_widget (context);
1663
1664         /* Get the action */
1665         if (context->action == GDK_ACTION_MOVE) {
1666                 delete_source = TRUE;
1667
1668                 /* Notify that there is no folder selected. We need to
1669                    do this in order to update the headers view (and
1670                    its monitors, because when moving, the old folder
1671                    won't longer exist. We can not wait for the end of
1672                    the operation, because the operation won't start if
1673                    the folder is in use */
1674                 if (source_widget == widget) {
1675                         ModestFolderViewPrivate *priv;
1676
1677                         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
1678                         if (priv->cur_folder_store) {
1679                                 g_object_unref (priv->cur_folder_store);
1680                                 priv->cur_folder_store = NULL;
1681                         }
1682
1683                         g_signal_emit (G_OBJECT (widget), 
1684                                        signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0, NULL, FALSE);
1685                 }
1686         }
1687
1688         /* Check if the get_data failed */
1689         if (selection_data == NULL || selection_data->length < 0)
1690                 gtk_drag_finish (context, success, FALSE, time);
1691
1692         /* Get the models */
1693         gtk_tree_get_row_drag_data (selection_data,
1694                                     &source_model,
1695                                     &source_row);
1696
1697         /* Select the destination model */
1698         if (source_widget == widget) {
1699                 dest_model = source_model;
1700         } else {
1701                 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
1702         }
1703
1704         /* Get the path to the destination row. Can not call
1705            gtk_tree_view_get_drag_dest_row() because the source row
1706            is not selected anymore */
1707         gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget), x, y,
1708                                            &dest_row, &pos);
1709
1710         /* Only allow drops IN other rows */
1711         if (!dest_row || pos == GTK_TREE_VIEW_DROP_BEFORE || pos == GTK_TREE_VIEW_DROP_AFTER)
1712                 gtk_drag_finish (context, success, FALSE, time);
1713
1714         /* Create the helper */
1715         helper = g_slice_new0 (DndHelper);
1716         helper->delete_source = delete_source;
1717         helper->source_row = gtk_tree_path_copy (source_row);
1718         helper->context = context;
1719         helper->time = time;
1720
1721         /* Drags from the header view */
1722         if (source_widget != widget) {
1723
1724                 drag_and_drop_from_header_view (source_model,
1725                                                 dest_model,
1726                                                 dest_row,
1727                                                 helper);
1728         } else {
1729
1730
1731                 drag_and_drop_from_folder_view (source_model,
1732                                                 dest_model,
1733                                                 dest_row,
1734                                                 selection_data, 
1735                                                 helper);
1736         }
1737
1738         /* Frees */
1739         gtk_tree_path_free (source_row);
1740         gtk_tree_path_free (dest_row);
1741 }
1742
1743 /*
1744  * We define a "drag-drop" signal handler because we do not want to
1745  * use the default one, because the default one always calls
1746  * gtk_drag_finish and we prefer to do it in the "drag-data-received"
1747  * signal handler, because there we have all the information available
1748  * to know if the dnd was a success or not.
1749  */
1750 static gboolean
1751 drag_drop_cb (GtkWidget      *widget,
1752               GdkDragContext *context,
1753               gint            x,
1754               gint            y,
1755               guint           time,
1756               gpointer        user_data) 
1757 {
1758         gpointer target;
1759
1760         if (!context->targets)
1761                 return FALSE;
1762
1763         /* Check if we're dragging a folder row */
1764         target = gtk_drag_dest_find_target (widget, context, NULL);
1765
1766         /* Request the data from the source. */
1767         gtk_drag_get_data(widget, context, target, time);
1768
1769     return TRUE;
1770 }
1771
1772 /*
1773  * This function expands a node of a tree view if it's not expanded
1774  * yet. Not sure why it needs the threads stuff, but gtk+`example code
1775  * does that, so that's why they're here.
1776  */
1777 static gint
1778 expand_row_timeout (gpointer data)
1779 {
1780         GtkTreeView *tree_view = data;
1781         GtkTreePath *dest_path = NULL;
1782         GtkTreeViewDropPosition pos;
1783         gboolean result = FALSE;
1784         
1785         GDK_THREADS_ENTER ();
1786         
1787         gtk_tree_view_get_drag_dest_row (tree_view,
1788                                          &dest_path,
1789                                          &pos);
1790         
1791         if (dest_path &&
1792             (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER ||
1793              pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)) {
1794                 gtk_tree_view_expand_row (tree_view, dest_path, FALSE);
1795                 gtk_tree_path_free (dest_path);
1796         }
1797         else {
1798                 if (dest_path)
1799                         gtk_tree_path_free (dest_path);
1800                 
1801                 result = TRUE;
1802         }
1803         
1804         GDK_THREADS_LEAVE ();
1805
1806         return result;
1807 }
1808
1809 /*
1810  * This function is called whenever the pointer is moved over a widget
1811  * while dragging some data. It installs a timeout that will expand a
1812  * node of the treeview if not expanded yet. This function also calls
1813  * gdk_drag_status in order to set the suggested action that will be
1814  * used by the "drag-data-received" signal handler to know if we
1815  * should do a move or just a copy of the data.
1816  */
1817 static gboolean
1818 on_drag_motion (GtkWidget      *widget,
1819                 GdkDragContext *context,
1820                 gint            x,
1821                 gint            y,
1822                 guint           time,
1823                 gpointer        user_data)  
1824 {
1825         GtkTreeViewDropPosition pos;
1826         GtkTreePath *dest_row;
1827         ModestFolderViewPrivate *priv;
1828         GdkDragAction suggested_action;
1829         gboolean valid_location = FALSE;
1830
1831         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
1832
1833         if (priv->timer_expander != 0) {
1834                 g_source_remove (priv->timer_expander);
1835                 priv->timer_expander = 0;
1836         }
1837
1838         gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
1839                                            x, y,
1840                                            &dest_row,
1841                                            &pos);
1842
1843         /* Do not allow drops between folders */
1844         if (!dest_row ||
1845             pos == GTK_TREE_VIEW_DROP_BEFORE ||
1846             pos == GTK_TREE_VIEW_DROP_AFTER) {
1847                 gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW (widget), NULL, 0);
1848                 gdk_drag_status(context, 0, time);
1849                 valid_location = FALSE;
1850                 goto out;
1851         } else {
1852                 valid_location = TRUE;
1853         }
1854
1855         /* Expand the selected row after 1/2 second */
1856         if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), dest_row)) {
1857                 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), dest_row, pos);
1858                 priv->timer_expander = g_timeout_add (500, expand_row_timeout, widget);
1859         }
1860
1861         /* Select the desired action. By default we pick MOVE */
1862         suggested_action = GDK_ACTION_MOVE;
1863
1864         if (context->actions == GDK_ACTION_COPY)
1865             gdk_drag_status(context, GDK_ACTION_COPY, time);
1866         else if (context->actions == GDK_ACTION_MOVE)
1867             gdk_drag_status(context, GDK_ACTION_MOVE, time);
1868         else if (context->actions & suggested_action)
1869             gdk_drag_status(context, suggested_action, time);
1870         else
1871             gdk_drag_status(context, GDK_ACTION_DEFAULT, time);
1872
1873  out:
1874         if (dest_row)
1875                 gtk_tree_path_free (dest_row);
1876         g_signal_stop_emission_by_name (widget, "drag-motion");
1877         return valid_location;
1878 }
1879
1880
1881 /* Folder view drag types */
1882 const GtkTargetEntry folder_view_drag_types[] =
1883 {
1884         { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, MODEST_FOLDER_ROW },
1885         { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_APP,    MODEST_HEADER_ROW }
1886 };
1887
1888 /*
1889  * This function sets the treeview as a source and a target for dnd
1890  * events. It also connects all the requirede signals.
1891  */
1892 static void
1893 setup_drag_and_drop (GtkTreeView *self)
1894 {
1895         /* Set up the folder view as a dnd destination. Set only the
1896            highlight flag, otherwise gtk will have a different
1897            behaviour */
1898         gtk_drag_dest_set (GTK_WIDGET (self),
1899                            GTK_DEST_DEFAULT_HIGHLIGHT,
1900                            folder_view_drag_types,
1901                            G_N_ELEMENTS (folder_view_drag_types),
1902                            GDK_ACTION_MOVE | GDK_ACTION_COPY);
1903
1904         g_signal_connect (G_OBJECT (self),
1905                           "drag_data_received",
1906                           G_CALLBACK (on_drag_data_received),
1907                           NULL);
1908
1909
1910         /* Set up the treeview as a dnd source */
1911         gtk_drag_source_set (GTK_WIDGET (self),
1912                              GDK_BUTTON1_MASK,
1913                              folder_view_drag_types,
1914                              G_N_ELEMENTS (folder_view_drag_types),
1915                              GDK_ACTION_MOVE | GDK_ACTION_COPY);
1916
1917         g_signal_connect (G_OBJECT (self),
1918                           "drag_motion",
1919                           G_CALLBACK (on_drag_motion),
1920                           NULL);
1921         
1922         g_signal_connect (G_OBJECT (self),
1923                           "drag_data_get",
1924                           G_CALLBACK (on_drag_data_get),
1925                           NULL);
1926
1927         g_signal_connect (G_OBJECT (self),
1928                           "drag_drop",
1929                           G_CALLBACK (drag_drop_cb),
1930                           NULL);
1931 }
1932
1933 /*
1934  * This function manages the navigation through the folders using the
1935  * keyboard or the hardware keys in the device
1936  */
1937 static gboolean
1938 on_key_pressed (GtkWidget *self,
1939                 GdkEventKey *event,
1940                 gpointer user_data)
1941 {
1942         GtkTreeSelection *selection;
1943         GtkTreeIter iter;
1944         GtkTreeModel *model;
1945         gboolean retval = FALSE;
1946
1947         /* Up and Down are automatically managed by the treeview */
1948         if (event->keyval == GDK_Return) {
1949                 /* Expand/Collapse the selected row */
1950                 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1951                 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
1952                         GtkTreePath *path;
1953
1954                         path = gtk_tree_model_get_path (model, &iter);
1955
1956                         if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (self), path))
1957                                 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1958                         else
1959                                 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1960                         gtk_tree_path_free (path);
1961                 }
1962                 /* No further processing */
1963                 retval = TRUE;
1964         }
1965
1966         return retval;
1967 }
1968
1969 /*
1970  * We listen to the changes in the local folder account name key,
1971  * because we want to show the right name in the view. The local
1972  * folder account name corresponds to the device name in the Maemo
1973  * version. We do this because we do not want to query gconf on each
1974  * tree view refresh. It's better to cache it and change whenever
1975  * necessary.
1976  */
1977 static void 
1978 on_configuration_key_changed (ModestConf* conf, 
1979                               const gchar *key, 
1980                               ModestConfEvent event,
1981                               ModestConfNotificationId id, 
1982                               ModestFolderView *self)
1983 {
1984         ModestFolderViewPrivate *priv;
1985
1986
1987         g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
1988         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1989
1990         /* Do not listen for changes in other namespaces */
1991         if (priv->notification_id != id)
1992                  return;
1993          
1994         if (!strcmp (key, MODEST_CONF_DEVICE_NAME)) {
1995                 g_free (priv->local_account_name);
1996
1997                 if (event == MODEST_CONF_EVENT_KEY_UNSET)
1998                         priv->local_account_name = g_strdup (MODEST_LOCAL_FOLDERS_DEFAULT_DISPLAY_NAME);
1999                 else
2000                         priv->local_account_name = modest_conf_get_string (modest_runtime_get_conf(),
2001                                                                            MODEST_CONF_DEVICE_NAME, NULL);
2002
2003                 /* Force a redraw */
2004 #if GTK_CHECK_VERSION(2, 8, 0) /* gtk_tree_view_column_queue_resize is only available in GTK+ 2.8 */
2005                 GtkTreeViewColumn * tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self), 
2006                                                                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN);
2007                 gtk_tree_view_column_queue_resize (tree_column);
2008 #endif
2009         }
2010 }
2011
2012 void
2013 modest_folder_view_set_style (ModestFolderView *self,
2014                               ModestFolderViewStyle style)
2015 {
2016         ModestFolderViewPrivate *priv;
2017
2018         g_return_if_fail (self);
2019         
2020         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2021
2022         priv->style = style;
2023 }
2024
2025 void
2026 modest_folder_view_set_account_id_of_visible_server_account (ModestFolderView *self,
2027                                                              const gchar *account_id)
2028 {
2029         ModestFolderViewPrivate *priv;
2030         GtkTreeModel *model;
2031
2032         g_return_if_fail (self);
2033         
2034         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2035
2036         /* This will be used by the filter_row callback,
2037          * to decided which rows to show: */
2038         if (priv->visible_account_id) {
2039                 g_free (priv->visible_account_id);
2040                 priv->visible_account_id = NULL;
2041         }
2042         if (account_id)
2043                 priv->visible_account_id = g_strdup (account_id);
2044
2045         /* Refilter */
2046         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2047         if (GTK_IS_TREE_MODEL_FILTER (model))
2048                 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2049
2050         /* Save settings to gconf */
2051         modest_widget_memory_save (modest_runtime_get_conf (), G_OBJECT(self),
2052                                    MODEST_CONF_FOLDER_VIEW_KEY);
2053 }
2054
2055 const gchar *
2056 modest_folder_view_get_account_id_of_visible_server_account (ModestFolderView *self)
2057 {
2058         ModestFolderViewPrivate *priv;
2059
2060         g_return_val_if_fail (self, NULL);
2061         
2062         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2063
2064         return (const gchar *) priv->visible_account_id;
2065 }
2066
2067 static gboolean
2068 find_inbox_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *inbox_iter)
2069 {
2070         do {
2071                 GtkTreeIter child;
2072                 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2073
2074                 gtk_tree_model_get (model, iter, 
2075                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, 
2076                                     &type, -1);
2077                         
2078                 gboolean result = FALSE;
2079                 if (type == TNY_FOLDER_TYPE_INBOX) {
2080                         result = TRUE;
2081                 }               
2082                 if (result) {
2083                         *inbox_iter = *iter;
2084                         return TRUE;
2085                 }
2086
2087                 if (gtk_tree_model_iter_children (model, &child, iter)) {
2088                         if (find_inbox_iter (model, &child, inbox_iter))
2089                                 return TRUE;
2090                 }
2091
2092         } while (gtk_tree_model_iter_next (model, iter));
2093
2094         return FALSE;
2095 }
2096
2097
2098
2099
2100 void 
2101 modest_folder_view_select_first_inbox_or_local (ModestFolderView *self)
2102 {
2103         GtkTreeModel *model;
2104         GtkTreeIter iter, inbox_iter;
2105         GtkTreeSelection *sel;
2106         GtkTreePath *path = NULL;
2107
2108         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2109         if (!model)
2110                 return;
2111
2112         expand_root_items (self);
2113         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2114
2115         gtk_tree_model_get_iter_first (model, &iter);
2116
2117         if (find_inbox_iter (model, &iter, &inbox_iter))
2118                 path = gtk_tree_model_get_path (model, &inbox_iter);
2119         else
2120                 path = gtk_tree_path_new_first ();
2121
2122         /* Select the row and free */
2123         gtk_tree_view_set_cursor (GTK_TREE_VIEW (self), path, NULL, FALSE);
2124         gtk_tree_path_free (path);
2125
2126         /* set focus */
2127         gtk_widget_grab_focus (GTK_WIDGET(self));
2128 }
2129
2130
2131 /* recursive */
2132 static gboolean
2133 find_folder_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *folder_iter, 
2134                   TnyFolder* folder)
2135 {
2136         do {
2137                 GtkTreeIter child;
2138                 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2139                 TnyFolder* a_folder;
2140                 gchar *name = NULL;
2141                 
2142                 gtk_tree_model_get (model, iter, 
2143                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &a_folder,
2144                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name,
2145                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type, 
2146                                     -1);                
2147                 g_free (name);
2148
2149                 if (folder == a_folder) {
2150                         g_object_unref (a_folder);
2151                         *folder_iter = *iter;
2152                         return TRUE;
2153                 }
2154                 g_object_unref (a_folder);
2155                 
2156                 if (gtk_tree_model_iter_children (model, &child, iter)) {
2157                         if (find_folder_iter (model, &child, folder_iter, folder)) 
2158                                 return TRUE;
2159                 }
2160
2161         } while (gtk_tree_model_iter_next (model, iter));
2162
2163         return FALSE;
2164 }
2165
2166
2167 static void
2168 on_row_inserted_maybe_select_folder (GtkTreeModel *tree_model, GtkTreePath  *path, GtkTreeIter *iter,
2169                                      ModestFolderView *self)
2170 {
2171         ModestFolderViewPrivate *priv = NULL;
2172         GtkTreeSelection *sel;
2173
2174         if (!MODEST_IS_FOLDER_VIEW(self))
2175                 return;
2176         
2177         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2178         
2179         if (priv->folder_to_select) {
2180                 
2181                 if (!modest_folder_view_select_folder (self, priv->folder_to_select,
2182                                                        FALSE)) {
2183                         GtkTreePath *path;
2184                         path = gtk_tree_model_get_path (tree_model, iter);
2185                         gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
2186                         
2187                         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2188
2189                         gtk_tree_selection_select_iter (sel, iter);
2190                         gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
2191
2192                         gtk_tree_path_free (path);
2193                 
2194                 }
2195
2196                 /* Disable next */
2197                 modest_folder_view_disable_next_folder_selection (self);
2198 /*              g_object_unref (priv->folder_to_select); */
2199 /*              priv->folder_to_select = NULL; */
2200         }
2201 }
2202
2203
2204 void
2205 modest_folder_view_disable_next_folder_selection (ModestFolderView *self) 
2206 {
2207         ModestFolderViewPrivate *priv = NULL;
2208
2209         g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));        
2210         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2211
2212         if (priv->folder_to_select)
2213                 g_object_unref(priv->folder_to_select);
2214         
2215         priv->folder_to_select = NULL;
2216 }
2217
2218 gboolean
2219 modest_folder_view_select_folder (ModestFolderView *self, TnyFolder *folder, 
2220                                   gboolean after_change)
2221 {
2222         GtkTreeModel *model;
2223         GtkTreeIter iter, folder_iter;
2224         GtkTreeSelection *sel;
2225         ModestFolderViewPrivate *priv = NULL;
2226         
2227         g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (self), FALSE);     
2228         g_return_val_if_fail (TNY_IS_FOLDER (folder), FALSE);   
2229                 
2230         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2231
2232         if (after_change) {
2233
2234                 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2235                 gtk_tree_selection_unselect_all (sel);
2236
2237                 if (priv->folder_to_select)
2238                         g_object_unref(priv->folder_to_select);
2239                 priv->folder_to_select = TNY_FOLDER(g_object_ref(folder));
2240                 return TRUE;
2241         }
2242                 
2243         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2244         if (!model)
2245                 return FALSE;
2246
2247                 
2248         gtk_tree_model_get_iter_first (model, &iter);
2249         if (find_folder_iter (model, &iter, &folder_iter, folder)) {
2250                 GtkTreePath *path;
2251
2252                 path = gtk_tree_model_get_path (model, &folder_iter);
2253                 gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
2254
2255                 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2256                 gtk_tree_selection_select_iter (sel, &folder_iter);
2257                 gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
2258
2259                 gtk_tree_path_free (path);
2260                 return TRUE;
2261         }
2262         return FALSE;
2263 }
2264
2265
2266 void 
2267 modest_folder_view_copy_selection (ModestFolderView *folder_view)
2268 {
2269         /* Copy selection */
2270         _clipboard_set_selected_data (folder_view, FALSE);
2271 }
2272
2273 void 
2274 modest_folder_view_cut_selection (ModestFolderView *folder_view)
2275 {
2276         ModestFolderViewPrivate *priv = NULL;
2277         GtkTreeModel *model = NULL;
2278         const gchar **hidding = NULL;
2279         guint i, n_selected;
2280
2281         g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view));
2282         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
2283
2284         /* Copy selection */
2285         if (!_clipboard_set_selected_data (folder_view, TRUE))
2286                 return;
2287
2288         /* Get hidding ids */
2289         hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected); 
2290         
2291         /* Clear hidding array created by previous cut operation */
2292         _clear_hidding_filter (MODEST_FOLDER_VIEW (folder_view));
2293
2294         /* Copy hidding array */
2295         priv->n_selected = n_selected;
2296         priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
2297         for (i=0; i < n_selected; i++) 
2298                 priv->hidding_ids[i] = g_strdup(hidding[i]);            
2299
2300         /* Hide cut folders */
2301         model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
2302         gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2303 }
2304
2305 void
2306 modest_folder_view_copy_model (ModestFolderView *folder_view_src,
2307                                ModestFolderView *folder_view_dst)
2308 {
2309         GtkTreeModel *filter_model = NULL;
2310         GtkTreeModel *model = NULL;
2311         GtkTreeModel *new_filter_model = NULL;
2312         
2313         g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view_src));
2314         g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view_dst));
2315
2316         /* Get src model*/
2317         filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view_src));
2318         model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER(filter_model));
2319
2320         /* Build new filter model */
2321         new_filter_model = gtk_tree_model_filter_new (model, NULL);     
2322         gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (new_filter_model),
2323                                                 filter_row,
2324                                                 folder_view_dst,
2325                                                 NULL);
2326         /* Set copied model */
2327         gtk_tree_view_set_model (GTK_TREE_VIEW (folder_view_dst), new_filter_model);
2328
2329         /* Free */
2330         g_object_unref (new_filter_model);
2331 }
2332
2333 void
2334 modest_folder_view_show_non_move_folders (ModestFolderView *folder_view,
2335                                           gboolean show)
2336 {
2337         GtkTreeModel *model = NULL;
2338         ModestFolderViewPrivate* priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
2339         priv->show_non_move = show;
2340 /*      modest_folder_view_update_model(folder_view, */
2341 /*                                      TNY_ACCOUNT_STORE(modest_runtime_get_account_store())); */
2342
2343         /* Hide special folders */
2344         model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
2345         if (GTK_IS_TREE_MODEL_FILTER (model)) {
2346                 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2347         }
2348 }
2349
2350 /* Returns FALSE if it did not selected anything */
2351 static gboolean
2352 _clipboard_set_selected_data (ModestFolderView *folder_view,
2353                               gboolean delete)
2354 {
2355         ModestFolderViewPrivate *priv = NULL;
2356         TnyFolderStore *folder = NULL;
2357         gboolean retval = FALSE;
2358
2359         g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (folder_view), FALSE);
2360         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
2361                 
2362         /* Set selected data on clipboard   */
2363         g_return_val_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard), FALSE);
2364         folder = modest_folder_view_get_selected (folder_view);
2365
2366         /* Do not allow to select an account */
2367         if (TNY_IS_FOLDER (folder)) {
2368                 modest_email_clipboard_set_data (priv->clipboard, TNY_FOLDER(folder), NULL, delete);
2369                 retval = TRUE;
2370         }
2371
2372         /* Free */
2373         g_object_unref (folder);
2374
2375         return retval;
2376 }
2377
2378 static void
2379 _clear_hidding_filter (ModestFolderView *folder_view) 
2380 {
2381         ModestFolderViewPrivate *priv;
2382         guint i;
2383         
2384         g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view)); 
2385         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
2386
2387         if (priv->hidding_ids != NULL) {
2388                 for (i=0; i < priv->n_selected; i++) 
2389                         g_free (priv->hidding_ids[i]);
2390                 g_free(priv->hidding_ids);
2391         }       
2392 }
2393
2394