* Set focus on folder-view at startup to show correctly
[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 /*
1488  * This function is used by drag_data_received_cb to manage drag and
1489  * drop of a header, i.e, and drag from the header view to the folder
1490  * view.
1491  */
1492 static void
1493 drag_and_drop_from_header_view (GtkTreeModel *source_model,
1494                                 GtkTreeModel *dest_model,
1495                                 GtkTreePath  *dest_row,
1496                                 DndHelper    *helper)
1497 {
1498         TnyList *headers = NULL;
1499         TnyHeader *header = NULL;
1500         TnyFolder *folder = NULL;
1501         ModestMailOperation *mail_op = NULL;
1502         GtkTreeIter source_iter;
1503
1504         g_return_if_fail (GTK_IS_TREE_MODEL(source_model));
1505         g_return_if_fail (GTK_IS_TREE_MODEL(dest_model));
1506         g_return_if_fail (dest_row);
1507         g_return_if_fail (helper);
1508
1509         /* Get header */
1510         gtk_tree_model_get_iter (source_model, &source_iter, helper->source_row);
1511         gtk_tree_model_get (source_model, &source_iter, 
1512                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, 
1513                             &header, -1);
1514         if (!TNY_IS_HEADER(header)) {
1515                 g_warning ("BUG: %s could not get a valid header", __FUNCTION__);
1516                 goto cleanup;
1517         }
1518         
1519         /* Get Folder */
1520         folder = tree_path_to_folder (dest_model, dest_row);
1521         if (!TNY_IS_FOLDER(folder)) {
1522                 g_warning ("BUG: %s could not get a valid folder", __FUNCTION__);
1523                 goto cleanup;
1524         }
1525         if (modest_tny_folder_get_rules(folder) & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE) {
1526                 g_debug ("folder rules: cannot write to that folder");
1527                 goto cleanup;
1528         }
1529         
1530
1531         /* Transfer message */
1532         mail_op = modest_mail_operation_new_with_error_handling (MODEST_MAIL_OPERATION_TYPE_RECEIVE, 
1533                                                                  NULL,
1534                                                                  modest_ui_actions_move_folder_error_handler,
1535                                                                  NULL);
1536         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1537                                          mail_op);
1538         g_signal_connect (G_OBJECT (mail_op), "progress-changed",
1539                           G_CALLBACK (on_progress_changed), helper);
1540
1541         headers = tny_simple_list_new ();
1542         tny_list_append (headers, G_OBJECT (header));
1543         modest_mail_operation_xfer_msgs (mail_op, 
1544                                          headers, 
1545                                          folder, 
1546                                          helper->delete_source, 
1547                                          NULL, NULL);
1548         
1549         /* Frees */
1550 cleanup:
1551         if (G_IS_OBJECT(mail_op))
1552                 g_object_unref (G_OBJECT (mail_op));
1553         if (G_IS_OBJECT(header))
1554                 g_object_unref (G_OBJECT (header));
1555         if (G_IS_OBJECT(folder))
1556                 g_object_unref (G_OBJECT (folder));
1557         if (G_IS_OBJECT(headers))
1558                 g_object_unref (headers);
1559 }
1560
1561 /*
1562  * This function is used by drag_data_received_cb to manage drag and
1563  * drop of a folder, i.e, and drag from the folder view to the same
1564  * folder view.
1565  */
1566 static void
1567 drag_and_drop_from_folder_view (GtkTreeModel     *source_model,
1568                                 GtkTreeModel     *dest_model,
1569                                 GtkTreePath      *dest_row,
1570                                 GtkSelectionData *selection_data,
1571                                 DndHelper        *helper)
1572 {
1573         ModestMailOperation *mail_op = NULL;
1574         GtkTreeIter parent_iter, iter;
1575         TnyFolderStore *parent_folder = NULL;
1576         TnyFolder *folder = NULL;
1577         gboolean forbidden = TRUE;
1578
1579         /* check the folder rules for the destination */
1580         folder = tree_path_to_folder (dest_model, dest_row);
1581         if (folder) {
1582                 ModestTnyFolderRules rules =
1583                         modest_tny_folder_get_rules (folder);
1584                 forbidden = rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE;
1585                 if (forbidden)
1586                         g_debug ("folder rules: cannot write to that folder");
1587                 g_object_unref (folder);
1588         }
1589         
1590         if (!forbidden) {
1591                 /* check the folder rules for the source */
1592                 folder = tree_path_to_folder (source_model, helper->source_row);
1593                 if (folder) {
1594                         ModestTnyFolderRules rules =
1595                                 modest_tny_folder_get_rules (folder);
1596                         forbidden = rules & MODEST_FOLDER_RULES_FOLDER_NON_MOVEABLE;
1597                         if (forbidden)
1598                                 g_debug ("folder rules: cannot move that folder");
1599                         g_object_unref (folder);
1600                 }
1601         }
1602
1603         
1604         /* Check if the drag is possible */
1605         if (forbidden || !gtk_tree_path_compare (helper->source_row, dest_row)) {
1606
1607                 gtk_drag_finish (helper->context, FALSE, FALSE, helper->time);
1608                 gtk_tree_path_free (helper->source_row);        
1609                 g_slice_free (DndHelper, helper);
1610                 return;
1611         }
1612
1613         /* Get data */
1614         gtk_tree_model_get_iter (source_model, &parent_iter, dest_row);
1615         gtk_tree_model_get_iter (source_model, &iter, helper->source_row);
1616         gtk_tree_model_get (source_model, &parent_iter, 
1617                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, 
1618                             &parent_folder, -1);
1619         gtk_tree_model_get (source_model, &iter,
1620                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
1621                             &folder, -1);
1622
1623         /* Offer the connection dialog if necessary, for the destination parent folder and source folder: */
1624         if (modest_platform_connect_and_wait_if_network_folderstore (NULL, parent_folder) && 
1625                 modest_platform_connect_and_wait_if_network_folderstore (NULL, TNY_FOLDER_STORE (folder))) {
1626                 /* Do the mail operation */
1627                 mail_op = modest_mail_operation_new_with_error_handling (MODEST_MAIL_OPERATION_TYPE_RECEIVE, 
1628                                                                  NULL,
1629                                                                  modest_ui_actions_move_folder_error_handler,
1630                                                                  NULL);
1631                 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (), 
1632                                          mail_op);
1633                 g_signal_connect (G_OBJECT (mail_op), "progress-changed",
1634                                   G_CALLBACK (on_progress_changed), helper);
1635
1636                 modest_mail_operation_xfer_folder (mail_op, 
1637                                            folder, 
1638                                            parent_folder,
1639                                            helper->delete_source,
1640                                            NULL,
1641                                            NULL);
1642
1643                 g_object_unref (G_OBJECT (mail_op));    
1644         }
1645         
1646         /* Frees */
1647         g_object_unref (G_OBJECT (parent_folder));
1648         g_object_unref (G_OBJECT (folder));
1649 }
1650
1651 /*
1652  * This function receives the data set by the "drag-data-get" signal
1653  * handler. This information comes within the #GtkSelectionData. This
1654  * function will manage both the drags of folders of the treeview and
1655  * drags of headers of the header view widget.
1656  */
1657 static void 
1658 on_drag_data_received (GtkWidget *widget, 
1659                        GdkDragContext *context, 
1660                        gint x, 
1661                        gint y, 
1662                        GtkSelectionData *selection_data, 
1663                        guint target_type, 
1664                        guint time, 
1665                        gpointer data)
1666 {
1667         GtkWidget *source_widget;
1668         GtkTreeModel *dest_model, *source_model;
1669         GtkTreePath *source_row, *dest_row;
1670         GtkTreeViewDropPosition pos;
1671         gboolean success = FALSE, delete_source = FALSE;
1672         DndHelper *helper = NULL; 
1673
1674         /* Do not allow further process */
1675         g_signal_stop_emission_by_name (widget, "drag-data-received");
1676         source_widget = gtk_drag_get_source_widget (context);
1677
1678         /* Get the action */
1679         if (context->action == GDK_ACTION_MOVE) {
1680                 delete_source = TRUE;
1681
1682                 /* Notify that there is no folder selected. We need to
1683                    do this in order to update the headers view (and
1684                    its monitors, because when moving, the old folder
1685                    won't longer exist. We can not wait for the end of
1686                    the operation, because the operation won't start if
1687                    the folder is in use */
1688                 if (source_widget == widget) {
1689                         ModestFolderViewPrivate *priv;
1690
1691                         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
1692                         if (priv->cur_folder_store) {
1693                                 g_object_unref (priv->cur_folder_store);
1694                                 priv->cur_folder_store = NULL;
1695                         }
1696
1697                         g_signal_emit (G_OBJECT (widget), 
1698                                        signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0, NULL, FALSE);
1699                 }
1700         }
1701
1702         /* Check if the get_data failed */
1703         if (selection_data == NULL || selection_data->length < 0)
1704                 gtk_drag_finish (context, success, FALSE, time);
1705
1706         /* Get the models */
1707         gtk_tree_get_row_drag_data (selection_data,
1708                                     &source_model,
1709                                     &source_row);
1710
1711         /* Select the destination model */
1712         if (source_widget == widget) {
1713                 dest_model = source_model;
1714         } else {
1715                 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
1716         }
1717
1718         /* Get the path to the destination row. Can not call
1719            gtk_tree_view_get_drag_dest_row() because the source row
1720            is not selected anymore */
1721         gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget), x, y,
1722                                            &dest_row, &pos);
1723
1724         /* Only allow drops IN other rows */
1725         if (!dest_row || pos == GTK_TREE_VIEW_DROP_BEFORE || pos == GTK_TREE_VIEW_DROP_AFTER)
1726                 gtk_drag_finish (context, success, FALSE, time);
1727
1728         /* Create the helper */
1729         helper = g_slice_new0 (DndHelper);
1730         helper->delete_source = delete_source;
1731         helper->source_row = gtk_tree_path_copy (source_row);
1732         helper->context = context;
1733         helper->time = time;
1734
1735         /* Drags from the header view */
1736         if (source_widget != widget) {
1737
1738                 drag_and_drop_from_header_view (source_model,
1739                                                 dest_model,
1740                                                 dest_row,
1741                                                 helper);
1742         } else {
1743
1744
1745                 drag_and_drop_from_folder_view (source_model,
1746                                                 dest_model,
1747                                                 dest_row,
1748                                                 selection_data, 
1749                                                 helper);
1750         }
1751
1752         /* Frees */
1753         gtk_tree_path_free (source_row);
1754         gtk_tree_path_free (dest_row);
1755 }
1756
1757 /*
1758  * We define a "drag-drop" signal handler because we do not want to
1759  * use the default one, because the default one always calls
1760  * gtk_drag_finish and we prefer to do it in the "drag-data-received"
1761  * signal handler, because there we have all the information available
1762  * to know if the dnd was a success or not.
1763  */
1764 static gboolean
1765 drag_drop_cb (GtkWidget      *widget,
1766               GdkDragContext *context,
1767               gint            x,
1768               gint            y,
1769               guint           time,
1770               gpointer        user_data) 
1771 {
1772         gpointer target;
1773
1774         if (!context->targets)
1775                 return FALSE;
1776
1777         /* Check if we're dragging a folder row */
1778         target = gtk_drag_dest_find_target (widget, context, NULL);
1779
1780         /* Request the data from the source. */
1781         gtk_drag_get_data(widget, context, target, time);
1782
1783     return TRUE;
1784 }
1785
1786 /*
1787  * This function expands a node of a tree view if it's not expanded
1788  * yet. Not sure why it needs the threads stuff, but gtk+`example code
1789  * does that, so that's why they're here.
1790  */
1791 static gint
1792 expand_row_timeout (gpointer data)
1793 {
1794         GtkTreeView *tree_view = data;
1795         GtkTreePath *dest_path = NULL;
1796         GtkTreeViewDropPosition pos;
1797         gboolean result = FALSE;
1798         
1799         GDK_THREADS_ENTER ();
1800         
1801         gtk_tree_view_get_drag_dest_row (tree_view,
1802                                          &dest_path,
1803                                          &pos);
1804         
1805         if (dest_path &&
1806             (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER ||
1807              pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)) {
1808                 gtk_tree_view_expand_row (tree_view, dest_path, FALSE);
1809                 gtk_tree_path_free (dest_path);
1810         }
1811         else {
1812                 if (dest_path)
1813                         gtk_tree_path_free (dest_path);
1814                 
1815                 result = TRUE;
1816         }
1817         
1818         GDK_THREADS_LEAVE ();
1819
1820         return result;
1821 }
1822
1823 /*
1824  * This function is called whenever the pointer is moved over a widget
1825  * while dragging some data. It installs a timeout that will expand a
1826  * node of the treeview if not expanded yet. This function also calls
1827  * gdk_drag_status in order to set the suggested action that will be
1828  * used by the "drag-data-received" signal handler to know if we
1829  * should do a move or just a copy of the data.
1830  */
1831 static gboolean
1832 on_drag_motion (GtkWidget      *widget,
1833                 GdkDragContext *context,
1834                 gint            x,
1835                 gint            y,
1836                 guint           time,
1837                 gpointer        user_data)  
1838 {
1839         GtkTreeViewDropPosition pos;
1840         GtkTreePath *dest_row;
1841         ModestFolderViewPrivate *priv;
1842         GdkDragAction suggested_action;
1843         gboolean valid_location = FALSE;
1844
1845         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
1846
1847         if (priv->timer_expander != 0) {
1848                 g_source_remove (priv->timer_expander);
1849                 priv->timer_expander = 0;
1850         }
1851
1852         gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
1853                                            x, y,
1854                                            &dest_row,
1855                                            &pos);
1856
1857         /* Do not allow drops between folders */
1858         if (!dest_row ||
1859             pos == GTK_TREE_VIEW_DROP_BEFORE ||
1860             pos == GTK_TREE_VIEW_DROP_AFTER) {
1861                 gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW (widget), NULL, 0);
1862                 gdk_drag_status(context, 0, time);
1863                 valid_location = FALSE;
1864                 goto out;
1865         } else {
1866                 valid_location = TRUE;
1867         }
1868
1869         /* Expand the selected row after 1/2 second */
1870         if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), dest_row)) {
1871                 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), dest_row, pos);
1872                 priv->timer_expander = g_timeout_add (500, expand_row_timeout, widget);
1873         }
1874
1875         /* Select the desired action. By default we pick MOVE */
1876         suggested_action = GDK_ACTION_MOVE;
1877
1878         if (context->actions == GDK_ACTION_COPY)
1879             gdk_drag_status(context, GDK_ACTION_COPY, time);
1880         else if (context->actions == GDK_ACTION_MOVE)
1881             gdk_drag_status(context, GDK_ACTION_MOVE, time);
1882         else if (context->actions & suggested_action)
1883             gdk_drag_status(context, suggested_action, time);
1884         else
1885             gdk_drag_status(context, GDK_ACTION_DEFAULT, time);
1886
1887  out:
1888         if (dest_row)
1889                 gtk_tree_path_free (dest_row);
1890         g_signal_stop_emission_by_name (widget, "drag-motion");
1891         return valid_location;
1892 }
1893
1894
1895 /* Folder view drag types */
1896 const GtkTargetEntry folder_view_drag_types[] =
1897 {
1898         { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, MODEST_FOLDER_ROW },
1899         { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_APP,    MODEST_HEADER_ROW }
1900 };
1901
1902 /*
1903  * This function sets the treeview as a source and a target for dnd
1904  * events. It also connects all the requirede signals.
1905  */
1906 static void
1907 setup_drag_and_drop (GtkTreeView *self)
1908 {
1909         /* Set up the folder view as a dnd destination. Set only the
1910            highlight flag, otherwise gtk will have a different
1911            behaviour */
1912         gtk_drag_dest_set (GTK_WIDGET (self),
1913                            GTK_DEST_DEFAULT_HIGHLIGHT,
1914                            folder_view_drag_types,
1915                            G_N_ELEMENTS (folder_view_drag_types),
1916                            GDK_ACTION_MOVE | GDK_ACTION_COPY);
1917
1918         g_signal_connect (G_OBJECT (self),
1919                           "drag_data_received",
1920                           G_CALLBACK (on_drag_data_received),
1921                           NULL);
1922
1923
1924         /* Set up the treeview as a dnd source */
1925         gtk_drag_source_set (GTK_WIDGET (self),
1926                              GDK_BUTTON1_MASK,
1927                              folder_view_drag_types,
1928                              G_N_ELEMENTS (folder_view_drag_types),
1929                              GDK_ACTION_MOVE | GDK_ACTION_COPY);
1930
1931         g_signal_connect (G_OBJECT (self),
1932                           "drag_motion",
1933                           G_CALLBACK (on_drag_motion),
1934                           NULL);
1935         
1936         g_signal_connect (G_OBJECT (self),
1937                           "drag_data_get",
1938                           G_CALLBACK (on_drag_data_get),
1939                           NULL);
1940
1941         g_signal_connect (G_OBJECT (self),
1942                           "drag_drop",
1943                           G_CALLBACK (drag_drop_cb),
1944                           NULL);
1945 }
1946
1947 /*
1948  * This function manages the navigation through the folders using the
1949  * keyboard or the hardware keys in the device
1950  */
1951 static gboolean
1952 on_key_pressed (GtkWidget *self,
1953                 GdkEventKey *event,
1954                 gpointer user_data)
1955 {
1956         GtkTreeSelection *selection;
1957         GtkTreeIter iter;
1958         GtkTreeModel *model;
1959         gboolean retval = FALSE;
1960
1961         /* Up and Down are automatically managed by the treeview */
1962         if (event->keyval == GDK_Return) {
1963                 /* Expand/Collapse the selected row */
1964                 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1965                 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
1966                         GtkTreePath *path;
1967
1968                         path = gtk_tree_model_get_path (model, &iter);
1969
1970                         if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (self), path))
1971                                 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1972                         else
1973                                 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1974                         gtk_tree_path_free (path);
1975                 }
1976                 /* No further processing */
1977                 retval = TRUE;
1978         }
1979
1980         return retval;
1981 }
1982
1983 /*
1984  * We listen to the changes in the local folder account name key,
1985  * because we want to show the right name in the view. The local
1986  * folder account name corresponds to the device name in the Maemo
1987  * version. We do this because we do not want to query gconf on each
1988  * tree view refresh. It's better to cache it and change whenever
1989  * necessary.
1990  */
1991 static void 
1992 on_configuration_key_changed (ModestConf* conf, 
1993                               const gchar *key, 
1994                               ModestConfEvent event,
1995                               ModestConfNotificationId id, 
1996                               ModestFolderView *self)
1997 {
1998         ModestFolderViewPrivate *priv;
1999
2000
2001         g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
2002         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2003
2004         /* Do not listen for changes in other namespaces */
2005         if (priv->notification_id != id)
2006                  return;
2007          
2008         if (!strcmp (key, MODEST_CONF_DEVICE_NAME)) {
2009                 g_free (priv->local_account_name);
2010
2011                 if (event == MODEST_CONF_EVENT_KEY_UNSET)
2012                         priv->local_account_name = g_strdup (MODEST_LOCAL_FOLDERS_DEFAULT_DISPLAY_NAME);
2013                 else
2014                         priv->local_account_name = modest_conf_get_string (modest_runtime_get_conf(),
2015                                                                            MODEST_CONF_DEVICE_NAME, NULL);
2016
2017                 /* Force a redraw */
2018 #if GTK_CHECK_VERSION(2, 8, 0) /* gtk_tree_view_column_queue_resize is only available in GTK+ 2.8 */
2019                 GtkTreeViewColumn * tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self), 
2020                                                                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN);
2021                 gtk_tree_view_column_queue_resize (tree_column);
2022 #endif
2023         }
2024 }
2025
2026 void
2027 modest_folder_view_set_style (ModestFolderView *self,
2028                               ModestFolderViewStyle style)
2029 {
2030         ModestFolderViewPrivate *priv;
2031
2032         g_return_if_fail (self);
2033         
2034         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2035
2036         priv->style = style;
2037 }
2038
2039 void
2040 modest_folder_view_set_account_id_of_visible_server_account (ModestFolderView *self,
2041                                                              const gchar *account_id)
2042 {
2043         ModestFolderViewPrivate *priv;
2044         GtkTreeModel *model;
2045
2046         g_return_if_fail (self);
2047         
2048         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2049
2050         /* This will be used by the filter_row callback,
2051          * to decided which rows to show: */
2052         if (priv->visible_account_id) {
2053                 g_free (priv->visible_account_id);
2054                 priv->visible_account_id = NULL;
2055         }
2056         if (account_id)
2057                 priv->visible_account_id = g_strdup (account_id);
2058
2059         /* Refilter */
2060         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2061         if (GTK_IS_TREE_MODEL_FILTER (model))
2062                 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2063
2064         /* Save settings to gconf */
2065         modest_widget_memory_save (modest_runtime_get_conf (), G_OBJECT(self),
2066                                    MODEST_CONF_FOLDER_VIEW_KEY);
2067 }
2068
2069 const gchar *
2070 modest_folder_view_get_account_id_of_visible_server_account (ModestFolderView *self)
2071 {
2072         ModestFolderViewPrivate *priv;
2073
2074         g_return_val_if_fail (self, NULL);
2075         
2076         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2077
2078         return (const gchar *) priv->visible_account_id;
2079 }
2080
2081 static gboolean
2082 find_inbox_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *inbox_iter)
2083 {
2084         do {
2085                 GtkTreeIter child;
2086                 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2087
2088                 gtk_tree_model_get (model, iter, 
2089                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, 
2090                                     &type, -1);
2091                         
2092                 gboolean result = FALSE;
2093                 if (type == TNY_FOLDER_TYPE_INBOX) {
2094                         result = TRUE;
2095                 }               
2096                 if (result) {
2097                         *inbox_iter = *iter;
2098                         return TRUE;
2099                 }
2100
2101                 if (gtk_tree_model_iter_children (model, &child, iter)) {
2102                         if (find_inbox_iter (model, &child, inbox_iter))
2103                                 return TRUE;
2104                 }
2105
2106         } while (gtk_tree_model_iter_next (model, iter));
2107
2108         return FALSE;
2109 }
2110
2111
2112
2113
2114 void 
2115 modest_folder_view_select_first_inbox_or_local (ModestFolderView *self)
2116 {
2117         GtkTreeModel *model;
2118         GtkTreeIter iter, inbox_iter;
2119         GtkTreeSelection *sel;
2120         GtkTreePath *path = NULL;
2121
2122         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2123         if (!model)
2124                 return;
2125
2126         expand_root_items (self);
2127         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2128
2129         gtk_tree_model_get_iter_first (model, &iter);
2130
2131         if (find_inbox_iter (model, &iter, &inbox_iter))
2132                 path = gtk_tree_model_get_path (model, &inbox_iter);
2133         else
2134                 path = gtk_tree_path_new_first ();
2135
2136         /* Select the row and free */
2137         gtk_tree_view_set_cursor (GTK_TREE_VIEW (self), path, NULL, FALSE);
2138         gtk_tree_path_free (path);
2139
2140         /* set focus */
2141         gtk_widget_grab_focus (GTK_WIDGET(self));
2142 }
2143
2144
2145 /* recursive */
2146 static gboolean
2147 find_folder_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *folder_iter, 
2148                   TnyFolder* folder)
2149 {
2150         do {
2151                 GtkTreeIter child;
2152                 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2153                 TnyFolder* a_folder;
2154                 gchar *name = NULL;
2155                 
2156                 gtk_tree_model_get (model, iter, 
2157                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &a_folder,
2158                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name,
2159                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type, 
2160                                     -1);                
2161         
2162                 g_debug ("===> %s (%p ---- %p)", name, a_folder, folder);
2163                 g_free (name);
2164
2165                 if (folder == a_folder) {
2166                         g_object_unref (a_folder);
2167                         *folder_iter = *iter;
2168                         return TRUE;
2169                 }
2170                 g_object_unref (a_folder);
2171                 
2172                 if (gtk_tree_model_iter_children (model, &child, iter)) {
2173                         if (find_folder_iter (model, &child, folder_iter, folder)) 
2174                                 return TRUE;
2175                 }
2176
2177         } while (gtk_tree_model_iter_next (model, iter));
2178
2179         return FALSE;
2180 }
2181
2182
2183 static void
2184 on_row_changed_maybe_select_folder (GtkTreeModel *tree_model, GtkTreePath  *path, GtkTreeIter *iter,
2185                                     ModestFolderView *self)
2186 {
2187         ModestFolderViewPrivate *priv = NULL;
2188         GtkTreeSelection *sel;
2189
2190         if (!MODEST_IS_FOLDER_VIEW(self))
2191                 return;
2192         
2193         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2194         
2195         if (priv->folder_to_select) {
2196                 
2197                 if (!modest_folder_view_select_folder (self, priv->folder_to_select,
2198                                                        FALSE)) {
2199                         GtkTreePath *path;
2200                         path = gtk_tree_model_get_path (tree_model, iter);
2201                         gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
2202                         
2203                         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2204
2205                         gtk_tree_selection_select_iter (sel, iter);
2206                         gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
2207
2208                         gtk_tree_path_free (path);
2209                 
2210                 }
2211                 g_object_unref (priv->folder_to_select);
2212                 priv->folder_to_select = NULL;
2213         }
2214 }
2215
2216
2217 gboolean
2218 modest_folder_view_select_folder (ModestFolderView *self, TnyFolder *folder, 
2219                                   gboolean after_change)
2220 {
2221         GtkTreeModel *model;
2222         GtkTreeIter iter, folder_iter;
2223         GtkTreeSelection *sel;
2224         ModestFolderViewPrivate *priv = NULL;
2225         
2226         g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (self), FALSE);     
2227         g_return_val_if_fail (TNY_IS_FOLDER (folder), FALSE);   
2228                 
2229         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2230
2231         if (after_change) {
2232
2233                 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2234                 gtk_tree_selection_unselect_all (sel);
2235
2236                 if (priv->folder_to_select)
2237                         g_object_unref(priv->folder_to_select);
2238                 priv->folder_to_select = TNY_FOLDER(g_object_ref(folder));
2239                 return TRUE;
2240         }
2241                 
2242         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2243         if (!model)
2244                 return FALSE;
2245
2246                 
2247         gtk_tree_model_get_iter_first (model, &iter);
2248         if (find_folder_iter (model, &iter, &folder_iter, folder)) {
2249                 GtkTreePath *path;
2250
2251                 path = gtk_tree_model_get_path (model, &folder_iter);
2252                 gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
2253
2254                 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2255                 gtk_tree_selection_select_iter (sel, &folder_iter);
2256                 gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
2257
2258                 gtk_tree_path_free (path);
2259                 return TRUE;
2260         }
2261         return FALSE;
2262 }
2263
2264
2265 void 
2266 modest_folder_view_copy_selection (ModestFolderView *folder_view)
2267 {
2268         /* Copy selection */
2269         _clipboard_set_selected_data (folder_view, FALSE);
2270 }
2271
2272 void 
2273 modest_folder_view_cut_selection (ModestFolderView *folder_view)
2274 {
2275         ModestFolderViewPrivate *priv = NULL;
2276         GtkTreeModel *model = NULL;
2277         const gchar **hidding = NULL;
2278         guint i, n_selected;
2279
2280         g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view));
2281         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
2282
2283         /* Copy selection */
2284         if (!_clipboard_set_selected_data (folder_view, TRUE))
2285                 return;
2286
2287         /* Get hidding ids */
2288         hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected); 
2289         
2290         /* Clear hidding array created by previous cut operation */
2291         _clear_hidding_filter (MODEST_FOLDER_VIEW (folder_view));
2292
2293         /* Copy hidding array */
2294         priv->n_selected = n_selected;
2295         priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
2296         for (i=0; i < n_selected; i++) 
2297                 priv->hidding_ids[i] = g_strdup(hidding[i]);            
2298
2299         /* Hide cut folders */
2300         model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
2301         gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2302 }
2303
2304 void
2305 modest_folder_view_show_non_move_folders (ModestFolderView *folder_view,
2306                                     gboolean show)
2307 {
2308         ModestFolderViewPrivate* priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
2309         priv->show_non_move = show;
2310         modest_folder_view_update_model(folder_view,
2311                                                                                                                                         TNY_ACCOUNT_STORE(modest_runtime_get_account_store()));
2312 }
2313
2314 /* Returns FALSE if it did not selected anything */
2315 static gboolean
2316 _clipboard_set_selected_data (ModestFolderView *folder_view,
2317                               gboolean delete)
2318 {
2319         ModestFolderViewPrivate *priv = NULL;
2320         TnyFolderStore *folder = NULL;
2321         gboolean retval = FALSE;
2322
2323         g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (folder_view), FALSE);
2324         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
2325                 
2326         /* Set selected data on clipboard   */
2327         g_return_val_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard), FALSE);
2328         folder = modest_folder_view_get_selected (folder_view);
2329
2330         /* Do not allow to select an account */
2331         if (TNY_IS_FOLDER (folder)) {
2332                 modest_email_clipboard_set_data (priv->clipboard, TNY_FOLDER(folder), NULL, delete);
2333                 retval = TRUE;
2334         }
2335
2336         /* Free */
2337         g_object_unref (folder);
2338
2339         return retval;
2340 }
2341
2342 static void
2343 _clear_hidding_filter (ModestFolderView *folder_view) 
2344 {
2345         ModestFolderViewPrivate *priv;
2346         guint i;
2347         
2348         g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view)); 
2349         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
2350
2351         if (priv->hidding_ids != NULL) {
2352                 for (i=0; i < priv->n_selected; i++) 
2353                         g_free (priv->hidding_ids[i]);
2354                 g_free(priv->hidding_ids);
2355         }       
2356 }
2357
2358