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