Refactoring of mail header view.
[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 <tny-camel-account.h>
45 #include <modest-tny-account.h>
46 #include <modest-tny-folder.h>
47 #include <modest-tny-local-folders-account.h>
48 #include <modest-tny-outbox-account.h>
49 #include <modest-marshal.h>
50 #include <modest-icon-names.h>
51 #include <modest-tny-account-store.h>
52 #include <modest-tny-local-folders-account.h>
53 #include <modest-text-utils.h>
54 #include <modest-runtime.h>
55 #include "modest-folder-view.h"
56 #include <modest-platform.h>
57 #include <modest-widget-memory.h>
58 #include <modest-ui-actions.h>
59 #include "modest-dnd.h"
60 #include "widgets/modest-window.h"
61
62 /* Folder view drag types */
63 const GtkTargetEntry folder_view_drag_types[] =
64 {
65         { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, MODEST_FOLDER_ROW },
66         { GTK_TREE_PATH_AS_STRING_LIST, GTK_TARGET_SAME_APP, MODEST_HEADER_ROW }
67 };
68
69 /* 'private'/'protected' functions */
70 static void modest_folder_view_class_init  (ModestFolderViewClass *klass);
71 static void modest_folder_view_init        (ModestFolderView *obj);
72 static void modest_folder_view_finalize    (GObject *obj);
73
74 static void         tny_account_store_view_init (gpointer g,
75                                                  gpointer iface_data);
76
77 static void         modest_folder_view_set_account_store (TnyAccountStoreView *self,
78                                                           TnyAccountStore     *account_store);
79
80 static void         on_selection_changed   (GtkTreeSelection *sel,
81                                             gpointer data);
82
83 static void         on_row_activated       (GtkTreeView *treeview,
84                                             GtkTreePath *path,
85                                             GtkTreeViewColumn *column,
86                                             gpointer userdata);
87
88 static void         on_account_removed     (TnyAccountStore *self,
89                                             TnyAccount *account,
90                                             gpointer user_data);
91
92 static void         on_account_inserted    (TnyAccountStore *self,
93                                             TnyAccount *account,
94                                             gpointer user_data);
95
96 static void         on_account_changed    (TnyAccountStore *self,
97                                             TnyAccount *account,
98                                             gpointer user_data);
99
100 static gint         cmp_rows               (GtkTreeModel *tree_model,
101                                             GtkTreeIter *iter1,
102                                             GtkTreeIter *iter2,
103                                             gpointer user_data);
104
105 static gboolean     filter_row             (GtkTreeModel *model,
106                                             GtkTreeIter *iter,
107                                             gpointer data);
108
109 static gboolean     on_key_pressed         (GtkWidget *self,
110                                             GdkEventKey *event,
111                                             gpointer user_data);
112
113 static void         on_configuration_key_changed  (ModestConf* conf,
114                                                    const gchar *key,
115                                                    ModestConfEvent event,
116                                                    ModestConfNotificationId notification_id,
117                                                    ModestFolderView *self);
118
119 /* DnD functions */
120 static void         on_drag_data_get       (GtkWidget *widget,
121                                             GdkDragContext *context,
122                                             GtkSelectionData *selection_data,
123                                             guint info,
124                                             guint time,
125                                             gpointer data);
126
127 static void         on_drag_data_received  (GtkWidget *widget,
128                                             GdkDragContext *context,
129                                             gint x,
130                                             gint y,
131                                             GtkSelectionData *selection_data,
132                                             guint info,
133                                             guint time,
134                                             gpointer data);
135
136 static gboolean     on_drag_motion         (GtkWidget      *widget,
137                                             GdkDragContext *context,
138                                             gint            x,
139                                             gint            y,
140                                             guint           time,
141                                             gpointer        user_data);
142
143 static void         expand_root_items (ModestFolderView *self);
144
145 static gint         expand_row_timeout     (gpointer data);
146
147 static void         setup_drag_and_drop    (GtkTreeView *self);
148
149 static gboolean     _clipboard_set_selected_data (ModestFolderView *folder_view,
150                                                   gboolean delete);
151
152 static void         _clear_hidding_filter (ModestFolderView *folder_view);
153
154 static void         on_row_inserted_maybe_select_folder (GtkTreeModel     *tree_model,
155                                                          GtkTreePath      *path,
156                                                          GtkTreeIter      *iter,
157                                                          ModestFolderView *self);
158
159 static void         on_display_name_changed (ModestAccountMgr *self,
160                                              const gchar *account,
161                                              gpointer user_data);
162
163 enum {
164         FOLDER_SELECTION_CHANGED_SIGNAL,
165         FOLDER_DISPLAY_NAME_CHANGED_SIGNAL,
166         FOLDER_ACTIVATED_SIGNAL,
167         LAST_SIGNAL
168 };
169
170 typedef struct _ModestFolderViewPrivate ModestFolderViewPrivate;
171 struct _ModestFolderViewPrivate {
172         TnyAccountStore      *account_store;
173         TnyFolderStore       *cur_folder_store;
174
175         TnyFolder            *folder_to_select; /* folder to select after the next update */
176
177         gulong                changed_signal;
178         gulong                account_inserted_signal;
179         gulong                account_removed_signal;
180         gulong                account_changed_signal;
181         gulong                conf_key_signal;
182         gulong                display_name_changed_signal;
183
184         /* not unref this object, its a singlenton */
185         ModestEmailClipboard *clipboard;
186
187         /* Filter tree model */
188         gchar **hidding_ids;
189         guint n_selected;
190
191         TnyFolderStoreQuery  *query;
192         guint                 timer_expander;
193
194         gchar                *local_account_name;
195         gchar                *visible_account_id;
196         ModestFolderViewStyle style;
197         ModestFolderViewCellStyle cell_style;
198
199         gboolean  reselect; /* we use this to force a reselection of the INBOX */
200         gboolean  show_non_move;
201         gboolean  reexpand; /* next time we expose, we'll expand all root folders */
202 };
203 #define MODEST_FOLDER_VIEW_GET_PRIVATE(o)                       \
204         (G_TYPE_INSTANCE_GET_PRIVATE((o),                       \
205                                      MODEST_TYPE_FOLDER_VIEW,   \
206                                      ModestFolderViewPrivate))
207 /* globals */
208 static GObjectClass *parent_class = NULL;
209
210 static guint signals[LAST_SIGNAL] = {0};
211
212 GType
213 modest_folder_view_get_type (void)
214 {
215         static GType my_type = 0;
216         if (!my_type) {
217                 static const GTypeInfo my_info = {
218                         sizeof(ModestFolderViewClass),
219                         NULL,           /* base init */
220                         NULL,           /* base finalize */
221                         (GClassInitFunc) modest_folder_view_class_init,
222                         NULL,           /* class finalize */
223                         NULL,           /* class data */
224                         sizeof(ModestFolderView),
225                         1,              /* n_preallocs */
226                         (GInstanceInitFunc) modest_folder_view_init,
227                         NULL
228                 };
229
230                 static const GInterfaceInfo tny_account_store_view_info = {
231                         (GInterfaceInitFunc) tny_account_store_view_init, /* interface_init */
232                         NULL,         /* interface_finalize */
233                         NULL          /* interface_data */
234                 };
235
236
237                 my_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
238                                                   "ModestFolderView",
239                                                   &my_info, 0);
240
241                 g_type_add_interface_static (my_type,
242                                              TNY_TYPE_ACCOUNT_STORE_VIEW,
243                                              &tny_account_store_view_info);
244         }
245         return my_type;
246 }
247
248 static void
249 modest_folder_view_class_init (ModestFolderViewClass *klass)
250 {
251         GObjectClass *gobject_class;
252         GtkTreeViewClass *treeview_class;
253         gobject_class = (GObjectClass*) klass;
254         treeview_class = (GtkTreeViewClass*) klass;
255
256         parent_class            = g_type_class_peek_parent (klass);
257         gobject_class->finalize = modest_folder_view_finalize;
258
259         g_type_class_add_private (gobject_class,
260                                   sizeof(ModestFolderViewPrivate));
261
262         signals[FOLDER_SELECTION_CHANGED_SIGNAL] =
263                 g_signal_new ("folder_selection_changed",
264                               G_TYPE_FROM_CLASS (gobject_class),
265                               G_SIGNAL_RUN_FIRST,
266                               G_STRUCT_OFFSET (ModestFolderViewClass,
267                                                folder_selection_changed),
268                               NULL, NULL,
269                               modest_marshal_VOID__POINTER_BOOLEAN,
270                               G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_BOOLEAN);
271
272         /*
273          * This signal is emitted whenever the currently selected
274          * folder display name is computed. Note that the name could
275          * be different to the folder name, because we could append
276          * the unread messages count to the folder name to build the
277          * folder display name
278          */
279         signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL] =
280                 g_signal_new ("folder-display-name-changed",
281                               G_TYPE_FROM_CLASS (gobject_class),
282                               G_SIGNAL_RUN_FIRST,
283                               G_STRUCT_OFFSET (ModestFolderViewClass,
284                                                folder_display_name_changed),
285                               NULL, NULL,
286                               g_cclosure_marshal_VOID__STRING,
287                               G_TYPE_NONE, 1, G_TYPE_STRING);
288
289         signals[FOLDER_ACTIVATED_SIGNAL] =
290                 g_signal_new ("folder_activated",
291                               G_TYPE_FROM_CLASS (gobject_class),
292                               G_SIGNAL_RUN_FIRST,
293                               G_STRUCT_OFFSET (ModestFolderViewClass,
294                                                folder_activated),
295                               NULL, NULL,
296                               g_cclosure_marshal_VOID__POINTER,
297                               G_TYPE_NONE, 1, G_TYPE_POINTER);
298
299         treeview_class->select_cursor_parent = NULL;
300
301 #ifdef MODEST_TOOLKIT_HILDON2
302         gtk_rc_parse_string ("class \"ModestFolderView\" style \"fremantle-touchlist\"");
303         
304 #endif
305
306 }
307
308 /* Simplify checks for NULLs: */
309 static gboolean
310 strings_are_equal (const gchar *a, const gchar *b)
311 {
312         if (!a && !b)
313                 return TRUE;
314         if (a && b)
315         {
316                 return (strcmp (a, b) == 0);
317         }
318         else
319                 return FALSE;
320 }
321
322 static gboolean
323 on_model_foreach_set_name(GtkTreeModel *model, GtkTreePath *path,  GtkTreeIter *iter, gpointer data)
324 {
325         GObject *instance = NULL;
326
327         gtk_tree_model_get (model, iter,
328                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
329                             -1);
330
331         if (!instance)
332                 return FALSE; /* keep walking */
333
334         if (!TNY_IS_ACCOUNT (instance)) {
335                 g_object_unref (instance);
336                 return FALSE; /* keep walking */
337         }
338
339         /* Check if this is the looked-for account: */
340         TnyAccount *this_account = TNY_ACCOUNT (instance);
341         TnyAccount *account = TNY_ACCOUNT (data);
342
343         const gchar *this_account_id = tny_account_get_id(this_account);
344         const gchar *account_id = tny_account_get_id(account);
345         g_object_unref (instance);
346         instance = NULL;
347
348         /* printf ("DEBUG: %s: this_account_id=%s, account_id=%s\n", __FUNCTION__, this_account_id, account_id); */
349         if (strings_are_equal(this_account_id, account_id)) {
350                 /* Tell the model that the data has changed, so that
351                  * it calls the cell_data_func callbacks again: */
352                 /* TODO: This does not seem to actually cause the new string to be shown: */
353                 gtk_tree_model_row_changed (model, path, iter);
354
355                 return TRUE; /* stop walking */
356         }
357
358         return FALSE; /* keep walking */
359 }
360
361 typedef struct
362 {
363         ModestFolderView *self;
364         gchar *previous_name;
365 } GetMmcAccountNameData;
366
367 static void
368 on_get_mmc_account_name (TnyStoreAccount* account, gpointer user_data)
369 {
370         /* printf ("DEBU1G: %s: account name=%s\n", __FUNCTION__, tny_account_get_name (TNY_ACCOUNT(account))); */
371
372         GetMmcAccountNameData *data = (GetMmcAccountNameData*)user_data;
373
374         if (!strings_are_equal (
375                 tny_account_get_name(TNY_ACCOUNT(account)),
376                 data->previous_name)) {
377
378                 /* Tell the model that the data has changed, so that
379                  * it calls the cell_data_func callbacks again: */
380                 ModestFolderView *self = data->self;
381                 GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
382                 if (model)
383                         gtk_tree_model_foreach(model, on_model_foreach_set_name, account);
384         }
385
386         g_free (data->previous_name);
387         g_slice_free (GetMmcAccountNameData, data);
388 }
389
390 static void
391 text_cell_data  (GtkTreeViewColumn *column,
392                  GtkCellRenderer *renderer,
393                  GtkTreeModel *tree_model,
394                  GtkTreeIter *iter,
395                  gpointer data)
396 {
397         ModestFolderViewPrivate *priv;
398         GObject *rendobj = (GObject *) renderer;
399         gchar *fname = NULL;
400         TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
401         GObject *instance = NULL;
402
403         gtk_tree_model_get (tree_model, iter,
404                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &fname,
405                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
406                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
407                             -1);
408         if (!fname || !instance)
409                 goto end;
410
411         ModestFolderView *self = MODEST_FOLDER_VIEW (data);
412         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE (self);
413
414         gchar *item_name = NULL;
415         gint item_weight = 400;
416
417         if (type != TNY_FOLDER_TYPE_ROOT) {
418                 gint number = 0;
419                 gboolean drafts;
420
421                 if (modest_tny_folder_is_local_folder (TNY_FOLDER (instance)) ||
422                     modest_tny_folder_is_memory_card_folder (TNY_FOLDER (instance))) {
423                         type = modest_tny_folder_get_local_or_mmc_folder_type (TNY_FOLDER (instance));
424                         if (type != TNY_FOLDER_TYPE_UNKNOWN) {
425                                 g_free (fname);
426                                 fname = g_strdup (modest_local_folder_info_get_type_display_name (type));
427                         }
428                 } else {
429                         /* Sometimes an special folder is reported by the server as
430                            NORMAL, like some versions of Dovecot */
431                         if (type == TNY_FOLDER_TYPE_NORMAL ||
432                             type == TNY_FOLDER_TYPE_UNKNOWN) {
433                                 type = modest_tny_folder_guess_folder_type (TNY_FOLDER (instance));
434                         }
435                 }
436
437                 if (type == TNY_FOLDER_TYPE_INBOX) {
438                         g_free (fname);
439                         fname = g_strdup (_("mcen_me_folder_inbox"));
440                 }
441
442                 /* note: we cannot reliably get the counts from the tree model, we need
443                  * to use explicit calls on tny_folder for some reason.
444                  */
445                 /* Select the number to show: the unread or unsent messages. in case of outbox/drafts, show all */
446                 if ((type == TNY_FOLDER_TYPE_DRAFTS) ||
447                     (type == TNY_FOLDER_TYPE_OUTBOX) ||
448                     (type == TNY_FOLDER_TYPE_MERGE)) { /* _OUTBOX actually returns _MERGE... */
449                         number = tny_folder_get_all_count (TNY_FOLDER(instance));
450                         drafts = TRUE;
451                 } else {
452                         number = tny_folder_get_unread_count (TNY_FOLDER(instance));
453                         drafts = FALSE;
454                 }
455
456                 if (priv->cell_style == MODEST_FOLDER_VIEW_CELL_STYLE_COMPACT) {
457                         gchar * substring;
458                         if (number > 0) {
459                                 substring = g_strdup_printf (drafts?"TODO:%d messages":"TODO:%d new messages", number);
460                                 item_name = g_strdup_printf ("<span weight='800'>%s</span>\n<span weight='800' size='x-small' color='grey'>%s</span>", 
461                                                              fname, substring);
462                                 item_weight = 800;
463                         } else {
464                                 substring = g_strdup ("");
465                                 item_name = g_strdup_printf ("%s\n<span size='x-small' color='grey'>%s</span>", 
466                                                              fname, substring);
467                                 item_weight = 400;
468                         }
469                         g_free (substring);
470                 } else {
471                         /* Use bold font style if there are unread or unset messages */
472                         if (number > 0) {
473                                 item_name = g_strdup_printf ("%s (%d)", fname, number);
474                                 item_weight = 800;
475                         } else {
476                                 item_name = g_strdup (fname);
477                                 item_weight = 400;
478                         }
479                 }
480
481         } else if (TNY_IS_ACCOUNT (instance)) {
482                 /* If it's a server account */
483                 if (modest_tny_account_is_virtual_local_folders (TNY_ACCOUNT (instance))) {
484                         item_name = g_strdup (priv->local_account_name);
485                         item_weight = 800;
486                 } else if (modest_tny_account_is_memory_card_account (TNY_ACCOUNT (instance))) {
487                         /* fname is only correct when the items are first
488                          * added to the model, not when the account is
489                          * changed later, so get the name from the account
490                          * instance: */
491                         item_name = g_strdup (tny_account_get_name (TNY_ACCOUNT (instance)));
492                         item_weight = 800;
493                 } else {
494                         item_name = g_strdup (fname);
495                         item_weight = 800;
496                 }
497         }
498
499         if (!item_name)
500                 item_name = g_strdup ("unknown");
501
502         if (item_name && item_weight) {
503                 /* Set the name in the treeview cell: */
504                 g_object_set (rendobj,"markup", item_name, "weight", item_weight, NULL);
505
506                 /* Notify display name observers */
507                 /* TODO: What listens for this signal, and how can it use only the new name? */
508                 if (((GObject *) priv->cur_folder_store) == instance) {
509                         g_signal_emit (G_OBJECT(self),
510                                                signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
511                                                item_name);
512                 }
513                 g_free (item_name);
514
515         }
516
517         /* If it is a Memory card account, make sure that we have the correct name.
518          * This function will be trigerred again when the name has been retrieved: */
519         if (TNY_IS_STORE_ACCOUNT (instance) &&
520                 modest_tny_account_is_memory_card_account (TNY_ACCOUNT (instance))) {
521
522                 /* Get the account name asynchronously: */
523                 GetMmcAccountNameData *callback_data =
524                         g_slice_new0(GetMmcAccountNameData);
525                 callback_data->self = self;
526
527                 const gchar *name = tny_account_get_name (TNY_ACCOUNT(instance));
528                 if (name)
529                         callback_data->previous_name = g_strdup (name);
530
531                 modest_tny_account_get_mmc_account_name (TNY_STORE_ACCOUNT (instance),
532                                                          on_get_mmc_account_name, callback_data);
533         }
534  end:
535         if (instance)
536                 g_object_unref (G_OBJECT (instance));
537         if (fname)
538                 g_free (fname);
539 }
540
541
542 typedef struct {
543         GdkPixbuf *pixbuf;
544         GdkPixbuf *pixbuf_open;
545         GdkPixbuf *pixbuf_close;
546 } ThreePixbufs;
547
548
549 static inline GdkPixbuf *
550 get_composite_pixbuf (const gchar *icon_name,
551                       const gint size,
552                       GdkPixbuf *base_pixbuf)
553 {
554         GdkPixbuf *emblem, *retval = NULL;
555
556         emblem = modest_platform_get_icon (icon_name, size);
557         if (emblem) {
558                 retval = gdk_pixbuf_copy (base_pixbuf);
559                 gdk_pixbuf_composite (emblem, retval, 0, 0,
560                                       MIN (gdk_pixbuf_get_width (emblem),
561                                            gdk_pixbuf_get_width (retval)),
562                                       MIN (gdk_pixbuf_get_height (emblem),
563                                            gdk_pixbuf_get_height (retval)),
564                                                   0, 0, 1, 1, GDK_INTERP_NEAREST, 255);
565                 g_object_unref (emblem);
566         }
567         return retval;
568 }
569
570 static inline ThreePixbufs *
571 get_composite_icons (const gchar *icon_code,
572                      GdkPixbuf **pixbuf,
573                      GdkPixbuf **pixbuf_open,
574                      GdkPixbuf **pixbuf_close)
575 {
576         ThreePixbufs *retval;
577
578         if (!*pixbuf)
579                 *pixbuf = gdk_pixbuf_copy (modest_platform_get_icon (icon_code, MODEST_ICON_SIZE_SMALL));
580
581         if (!*pixbuf_open)
582                 *pixbuf_open = get_composite_pixbuf ("qgn_list_gene_fldr_exp",
583                                                      MODEST_ICON_SIZE_SMALL,
584                                                      *pixbuf);
585
586         if (!*pixbuf_close)
587                 *pixbuf_close = get_composite_pixbuf ("qgn_list_gene_fldr_clp",
588                                                       MODEST_ICON_SIZE_SMALL,
589                                                       *pixbuf);
590
591         retval = g_slice_new0 (ThreePixbufs);
592         if (*pixbuf)
593                 retval->pixbuf = g_object_ref (*pixbuf);
594         if (*pixbuf_open)
595                 retval->pixbuf_open = g_object_ref (*pixbuf_open);
596         if (*pixbuf_close)
597                 retval->pixbuf_close = g_object_ref (*pixbuf_close);
598
599         return retval;
600 }
601
602 static ThreePixbufs*
603 get_folder_icons (TnyFolderType type, GObject *instance)
604 {
605         static GdkPixbuf *inbox_pixbuf = NULL, *outbox_pixbuf = NULL,
606                 *junk_pixbuf = NULL, *sent_pixbuf = NULL,
607                 *trash_pixbuf = NULL, *draft_pixbuf = NULL,
608                 *normal_pixbuf = NULL, *anorm_pixbuf = NULL,
609                 *ammc_pixbuf = NULL, *avirt_pixbuf = NULL;
610
611         static GdkPixbuf *inbox_pixbuf_open = NULL, *outbox_pixbuf_open = NULL,
612                 *junk_pixbuf_open = NULL, *sent_pixbuf_open = NULL,
613                 *trash_pixbuf_open = NULL, *draft_pixbuf_open = NULL,
614                 *normal_pixbuf_open = NULL, *anorm_pixbuf_open = NULL,
615                 *ammc_pixbuf_open = NULL, *avirt_pixbuf_open = NULL;
616
617         static GdkPixbuf *inbox_pixbuf_close = NULL, *outbox_pixbuf_close = NULL,
618                 *junk_pixbuf_close = NULL, *sent_pixbuf_close = NULL,
619                 *trash_pixbuf_close = NULL, *draft_pixbuf_close = NULL,
620                 *normal_pixbuf_close = NULL, *anorm_pixbuf_close = NULL,
621                 *ammc_pixbuf_close = NULL, *avirt_pixbuf_close = NULL;
622
623         ThreePixbufs *retval = NULL;
624
625         /* Sometimes an special folder is reported by the server as
626            NORMAL, like some versions of Dovecot */
627         if (type == TNY_FOLDER_TYPE_NORMAL ||
628             type == TNY_FOLDER_TYPE_UNKNOWN) {
629                 type = modest_tny_folder_guess_folder_type (TNY_FOLDER (instance));
630         }
631
632         switch (type) {
633         case TNY_FOLDER_TYPE_INVALID:
634                 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
635                 break;
636
637         case TNY_FOLDER_TYPE_ROOT:
638                 if (TNY_IS_ACCOUNT (instance)) {
639
640                         if (modest_tny_account_is_virtual_local_folders (TNY_ACCOUNT (instance))) {
641                                 retval = get_composite_icons (MODEST_FOLDER_ICON_LOCAL_FOLDERS,
642                                                               &avirt_pixbuf,
643                                                               &avirt_pixbuf_open,
644                                                               &avirt_pixbuf_close);
645                         } else {
646                                 const gchar *account_id = tny_account_get_id (TNY_ACCOUNT (instance));
647
648                                 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
649                                         retval = get_composite_icons (MODEST_FOLDER_ICON_MMC,
650                                                                       &ammc_pixbuf,
651                                                                       &ammc_pixbuf_open,
652                                                                       &ammc_pixbuf_close);
653                                 } else {
654                                         retval = get_composite_icons (MODEST_FOLDER_ICON_ACCOUNT,
655                                                                       &anorm_pixbuf,
656                                                                       &anorm_pixbuf_open,
657                                                                       &anorm_pixbuf_close);
658                                 }
659                         }
660                 }
661                 break;
662         case TNY_FOLDER_TYPE_INBOX:
663                 retval = get_composite_icons (MODEST_FOLDER_ICON_INBOX,
664                                               &inbox_pixbuf,
665                                               &inbox_pixbuf_open,
666                                               &inbox_pixbuf_close);
667                 break;
668         case TNY_FOLDER_TYPE_OUTBOX:
669                 retval = get_composite_icons (MODEST_FOLDER_ICON_OUTBOX,
670                                               &outbox_pixbuf,
671                                               &outbox_pixbuf_open,
672                                               &outbox_pixbuf_close);
673                 break;
674         case TNY_FOLDER_TYPE_JUNK:
675                 retval = get_composite_icons (MODEST_FOLDER_ICON_JUNK,
676                                               &junk_pixbuf,
677                                               &junk_pixbuf_open,
678                                               &junk_pixbuf_close);
679                 break;
680         case TNY_FOLDER_TYPE_SENT:
681                 retval = get_composite_icons (MODEST_FOLDER_ICON_SENT,
682                                               &sent_pixbuf,
683                                               &sent_pixbuf_open,
684                                               &sent_pixbuf_close);
685                 break;
686         case TNY_FOLDER_TYPE_TRASH:
687                 retval = get_composite_icons (MODEST_FOLDER_ICON_TRASH,
688                                               &trash_pixbuf,
689                                               &trash_pixbuf_open,
690                                               &trash_pixbuf_close);
691                 break;
692         case TNY_FOLDER_TYPE_DRAFTS:
693                 retval = get_composite_icons (MODEST_FOLDER_ICON_DRAFTS,
694                                               &draft_pixbuf,
695                                               &draft_pixbuf_open,
696                                               &draft_pixbuf_close);
697                 break;
698         case TNY_FOLDER_TYPE_NORMAL:
699         default:
700                 retval = get_composite_icons (MODEST_FOLDER_ICON_NORMAL,
701                                               &normal_pixbuf,
702                                               &normal_pixbuf_open,
703                                               &normal_pixbuf_close);
704                 break;
705         }
706
707         return retval;
708 }
709
710 static void
711 free_pixbufs (ThreePixbufs *pixbufs)
712 {
713         if (pixbufs->pixbuf)
714                 g_object_unref (pixbufs->pixbuf);
715         if (pixbufs->pixbuf_open)
716                 g_object_unref (pixbufs->pixbuf_open);
717         if (pixbufs->pixbuf_close)
718                 g_object_unref (pixbufs->pixbuf_close);
719         g_slice_free (ThreePixbufs, pixbufs);
720 }
721
722 static void
723 icon_cell_data  (GtkTreeViewColumn *column,
724                  GtkCellRenderer *renderer,
725                  GtkTreeModel *tree_model,
726                  GtkTreeIter *iter,
727                  gpointer data)
728 {
729         GObject *rendobj = NULL, *instance = NULL;
730         TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
731         gboolean has_children;
732         ThreePixbufs *pixbufs;
733
734         rendobj = (GObject *) renderer;
735
736         gtk_tree_model_get (tree_model, iter,
737                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
738                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
739                             -1);
740
741         if (!instance)
742                 return;
743
744         has_children = gtk_tree_model_iter_has_child (tree_model, iter);
745         pixbufs = get_folder_icons (type, instance);
746         g_object_unref (instance);
747
748         /* Set pixbuf */
749         g_object_set (rendobj, "pixbuf", pixbufs->pixbuf, NULL);
750
751         if (has_children) {
752                 g_object_set (rendobj, "pixbuf-expander-open", pixbufs->pixbuf_open, NULL);
753                 g_object_set (rendobj, "pixbuf-expander-closed", pixbufs->pixbuf_close, NULL);
754         }
755
756         free_pixbufs (pixbufs);
757 }
758
759 static void
760 add_columns (GtkWidget *treeview)
761 {
762         GtkTreeViewColumn *column;
763         GtkCellRenderer *renderer;
764         GtkTreeSelection *sel;
765
766         /* Create column */
767         column = gtk_tree_view_column_new ();
768
769         /* Set icon and text render function */
770         renderer = gtk_cell_renderer_pixbuf_new();
771         gtk_tree_view_column_pack_start (column, renderer, FALSE);
772         gtk_tree_view_column_set_cell_data_func(column, renderer,
773                                                 icon_cell_data, treeview, NULL);
774
775         renderer = gtk_cell_renderer_text_new();
776         g_object_set (renderer, "ellipsize", PANGO_ELLIPSIZE_END,
777                         "ellipsize-set", TRUE, NULL);
778         gtk_tree_view_column_pack_start (column, renderer, TRUE);
779         gtk_tree_view_column_set_cell_data_func(column, renderer,
780                                                 text_cell_data, treeview, NULL);
781
782         /* Set selection mode */
783         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(treeview));
784         gtk_tree_selection_set_mode (sel, GTK_SELECTION_SINGLE);
785
786         /* Set treeview appearance */
787         gtk_tree_view_column_set_spacing (column, 2);
788         gtk_tree_view_column_set_resizable (column, TRUE);
789         gtk_tree_view_column_set_fixed_width (column, TRUE);
790         gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(treeview), FALSE);
791         gtk_tree_view_set_enable_search (GTK_TREE_VIEW(treeview), FALSE);
792
793         /* Add column */
794         gtk_tree_view_append_column (GTK_TREE_VIEW(treeview),column);
795 }
796
797 static void
798 modest_folder_view_init (ModestFolderView *obj)
799 {
800         ModestFolderViewPrivate *priv;
801         ModestConf *conf;
802
803         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
804
805         priv->timer_expander = 0;
806         priv->account_store  = NULL;
807         priv->query          = NULL;
808         priv->style          = MODEST_FOLDER_VIEW_STYLE_SHOW_ALL;
809         priv->cur_folder_store   = NULL;
810         priv->visible_account_id = NULL;
811         priv->folder_to_select = NULL;
812
813         priv->reexpand = TRUE;
814
815         /* Initialize the local account name */
816         conf = modest_runtime_get_conf();
817         priv->local_account_name = modest_conf_get_string (conf, MODEST_CONF_DEVICE_NAME, NULL);
818
819         /* Init email clipboard */
820         priv->clipboard = modest_runtime_get_email_clipboard ();
821         priv->hidding_ids = NULL;
822         priv->n_selected = 0;
823         priv->reselect = FALSE;
824         priv->show_non_move = TRUE;
825
826         /* Build treeview */
827         add_columns (GTK_WIDGET (obj));
828
829         /* Setup drag and drop */
830         setup_drag_and_drop (GTK_TREE_VIEW(obj));
831
832         /* Connect signals */
833         g_signal_connect (G_OBJECT (obj),
834                           "key-press-event",
835                           G_CALLBACK (on_key_pressed), NULL);
836
837         priv->display_name_changed_signal =
838                 g_signal_connect (modest_runtime_get_account_mgr (),
839                                   "display_name_changed",
840                                   G_CALLBACK (on_display_name_changed),
841                                   obj);
842
843         /*
844          * Track changes in the local account name (in the device it
845          * will be the device name)
846          */
847         priv->conf_key_signal = g_signal_connect (G_OBJECT(conf),
848                                                   "key_changed",
849                                                   G_CALLBACK(on_configuration_key_changed),
850                                                   obj);
851 }
852
853 static void
854 tny_account_store_view_init (gpointer g, gpointer iface_data)
855 {
856         TnyAccountStoreViewIface *klass = (TnyAccountStoreViewIface *)g;
857
858         klass->set_account_store = modest_folder_view_set_account_store;
859 }
860
861 static void
862 modest_folder_view_finalize (GObject *obj)
863 {
864         ModestFolderViewPrivate *priv;
865         GtkTreeSelection    *sel;
866
867         g_return_if_fail (obj);
868
869         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
870
871         if (priv->timer_expander != 0) {
872                 g_source_remove (priv->timer_expander);
873                 priv->timer_expander = 0;
874         }
875
876         if (priv->account_store) {
877                 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
878                                              priv->account_inserted_signal);
879                 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
880                                              priv->account_removed_signal);
881                 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
882                                              priv->account_changed_signal);
883                 g_object_unref (G_OBJECT(priv->account_store));
884                 priv->account_store = NULL;
885         }
886
887         if (g_signal_handler_is_connected (modest_runtime_get_account_mgr (), 
888                                            priv->display_name_changed_signal)) {
889                 g_signal_handler_disconnect (modest_runtime_get_account_mgr (),
890                                              priv->display_name_changed_signal);
891                 priv->display_name_changed_signal = 0;
892         }
893
894         if (priv->query) {
895                 g_object_unref (G_OBJECT (priv->query));
896                 priv->query = NULL;
897         }
898
899         if (priv->folder_to_select) {
900                 g_object_unref (G_OBJECT(priv->folder_to_select));
901                 priv->folder_to_select = NULL;
902         }
903
904         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(obj));
905         if (sel)
906                 g_signal_handler_disconnect (G_OBJECT(sel), priv->changed_signal);
907
908         g_free (priv->local_account_name);
909         g_free (priv->visible_account_id);
910
911         if (priv->conf_key_signal) {
912                 g_signal_handler_disconnect (modest_runtime_get_conf (),
913                                              priv->conf_key_signal);
914                 priv->conf_key_signal = 0;
915         }
916
917         if (priv->cur_folder_store) {
918                 g_object_unref (priv->cur_folder_store);
919                 priv->cur_folder_store = NULL;
920         }
921
922         /* Clear hidding array created by cut operation */
923         _clear_hidding_filter (MODEST_FOLDER_VIEW (obj));
924
925         G_OBJECT_CLASS(parent_class)->finalize (obj);
926 }
927
928
929 static void
930 modest_folder_view_set_account_store (TnyAccountStoreView *self, TnyAccountStore *account_store)
931 {
932         ModestFolderViewPrivate *priv;
933         TnyDevice *device;
934
935         g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
936         g_return_if_fail (TNY_IS_ACCOUNT_STORE (account_store));
937
938         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
939         device = tny_account_store_get_device (account_store);
940
941         if (G_UNLIKELY (priv->account_store)) {
942
943                 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
944                                                    priv->account_inserted_signal))
945                         g_signal_handler_disconnect (G_OBJECT (priv->account_store),
946                                                      priv->account_inserted_signal);
947                 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
948                                                    priv->account_removed_signal))
949                         g_signal_handler_disconnect (G_OBJECT (priv->account_store),
950                                                      priv->account_removed_signal);
951                 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
952                                                    priv->account_changed_signal))
953                         g_signal_handler_disconnect (G_OBJECT (priv->account_store),
954                                                      priv->account_changed_signal);
955                 g_object_unref (G_OBJECT (priv->account_store));
956         }
957
958         priv->account_store = g_object_ref (G_OBJECT (account_store));
959
960         priv->account_removed_signal =
961                 g_signal_connect (G_OBJECT(account_store), "account_removed",
962                                   G_CALLBACK (on_account_removed), self);
963
964         priv->account_inserted_signal =
965                 g_signal_connect (G_OBJECT(account_store), "account_inserted",
966                                   G_CALLBACK (on_account_inserted), self);
967
968         priv->account_changed_signal =
969                 g_signal_connect (G_OBJECT(account_store), "account_changed",
970                                   G_CALLBACK (on_account_changed), self);
971
972         modest_folder_view_update_model (MODEST_FOLDER_VIEW (self), account_store);
973         priv->reselect = FALSE;
974         modest_folder_view_select_first_inbox_or_local (MODEST_FOLDER_VIEW (self));
975
976         g_object_unref (G_OBJECT (device));
977 }
978
979 static void
980 on_account_inserted (TnyAccountStore *account_store,
981                      TnyAccount *account,
982                      gpointer user_data)
983 {
984         ModestFolderViewPrivate *priv;
985         GtkTreeModel *sort_model, *filter_model;
986
987         /* Ignore transport account insertions, we're not showing them
988            in the folder view */
989         if (TNY_IS_TRANSPORT_ACCOUNT (account))
990                 return;
991
992         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (user_data);
993
994
995         /* If we're adding a new account, and there is no previous
996            one, we need to select the visible server account */
997         if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE &&
998             !priv->visible_account_id)
999                 modest_widget_memory_restore (modest_runtime_get_conf(),
1000                                               G_OBJECT (user_data),
1001                                               MODEST_CONF_FOLDER_VIEW_KEY);
1002
1003         if (!GTK_IS_TREE_VIEW(user_data)) {
1004                 g_warning ("BUG: %s: not a valid tree view", __FUNCTION__);
1005                 return;
1006         }
1007
1008         /* Get the inner model */
1009         /* check, is some rare cases, we did not get the right thing here,
1010          * NB#84097 */
1011         filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (user_data));
1012         if (!GTK_IS_TREE_MODEL_FILTER(filter_model)) {
1013                 g_warning ("BUG: %s: not a valid filter model", __FUNCTION__);
1014                 return;
1015         }
1016
1017         /* check, is some rare cases, we did not get the right thing here,
1018          * NB#84097 */
1019         sort_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
1020         if (!GTK_IS_TREE_MODEL_SORT(sort_model)) {
1021                 g_warning ("BUG: %s: not a valid sort model", __FUNCTION__);
1022                 return;
1023         }
1024
1025         /* Insert the account in the model */
1026         tny_list_append (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
1027                          G_OBJECT (account));
1028
1029         /* Refilter the model */
1030         gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1031 }
1032
1033
1034 static gboolean
1035 same_account_selected (ModestFolderView *self,
1036                        TnyAccount *account)
1037 {
1038         ModestFolderViewPrivate *priv;
1039         gboolean same_account = FALSE;
1040
1041         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1042
1043         if (priv->cur_folder_store) {
1044                 TnyAccount *selected_folder_account = NULL;
1045
1046                 if (TNY_IS_FOLDER (priv->cur_folder_store)) {
1047                         selected_folder_account =
1048                                 modest_tny_folder_get_account (TNY_FOLDER (priv->cur_folder_store));
1049                 } else {
1050                         selected_folder_account =
1051                                 TNY_ACCOUNT (g_object_ref (priv->cur_folder_store));
1052                 }
1053
1054                 if (selected_folder_account == account)
1055                         same_account = TRUE;
1056
1057                 g_object_unref (selected_folder_account);
1058         }
1059         return same_account;
1060 }
1061
1062 /**
1063  *
1064  * Selects the first inbox or the local account in an idle
1065  */
1066 static gboolean
1067 on_idle_select_first_inbox_or_local (gpointer user_data)
1068 {
1069         ModestFolderView *self = MODEST_FOLDER_VIEW (user_data);
1070
1071         gdk_threads_enter ();
1072         modest_folder_view_select_first_inbox_or_local (self);
1073         gdk_threads_leave ();
1074
1075         return FALSE;
1076 }
1077
1078 static void
1079 on_account_changed (TnyAccountStore *account_store,
1080                     TnyAccount *tny_account,
1081                     gpointer user_data)
1082 {
1083         ModestFolderView *self;
1084         ModestFolderViewPrivate *priv;
1085         GtkTreeModel *sort_model, *filter_model;
1086         GtkTreeSelection *sel;
1087         gboolean same_account;
1088
1089         /* Ignore transport account insertions, we're not showing them
1090            in the folder view */
1091         if (TNY_IS_TRANSPORT_ACCOUNT (tny_account))
1092                 return;
1093
1094         if (!MODEST_IS_FOLDER_VIEW(user_data)) {
1095                 g_warning ("BUG: %s: not a valid folder view", __FUNCTION__);
1096                 return;
1097         }
1098
1099         self = MODEST_FOLDER_VIEW (user_data);
1100         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (user_data);
1101
1102         /* Get the inner model */
1103         filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (user_data));
1104         if (!GTK_IS_TREE_MODEL_FILTER(filter_model)) {
1105                 g_warning ("BUG: %s: not a valid filter model", __FUNCTION__);
1106                 return;
1107         }
1108
1109         sort_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
1110         if (!GTK_IS_TREE_MODEL_SORT(sort_model)) {
1111                 g_warning ("BUG: %s: not a valid sort model", __FUNCTION__);
1112                 return;
1113         }
1114
1115         /* Invalidate the cur_folder_store only if the selected folder
1116            belongs to the account that is being removed */
1117         same_account = same_account_selected (self, tny_account);
1118         if (same_account) {
1119                 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1120                 gtk_tree_selection_unselect_all (sel);
1121         }
1122
1123         /* Remove the account from the model */
1124         tny_list_remove (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
1125                          G_OBJECT (tny_account));
1126
1127         /* Insert the account in the model */
1128         tny_list_append (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
1129                          G_OBJECT (tny_account));
1130
1131         /* Refilter the model */
1132         gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1133
1134         /* Select the first INBOX if the currently selected folder
1135            belongs to the account that is being deleted */
1136         if (same_account && !MODEST_IS_TNY_LOCAL_FOLDERS_ACCOUNT (tny_account))
1137                 g_idle_add (on_idle_select_first_inbox_or_local, self);
1138 }
1139
1140 static void
1141 on_account_removed (TnyAccountStore *account_store,
1142                     TnyAccount *account,
1143                     gpointer user_data)
1144 {
1145         ModestFolderView *self = NULL;
1146         ModestFolderViewPrivate *priv;
1147         GtkTreeModel *sort_model, *filter_model;
1148         GtkTreeSelection *sel = NULL;
1149         gboolean same_account = FALSE;
1150
1151         /* Ignore transport account removals, we're not showing them
1152            in the folder view */
1153         if (TNY_IS_TRANSPORT_ACCOUNT (account))
1154                 return;
1155
1156         if (!MODEST_IS_FOLDER_VIEW(user_data)) {
1157                 g_warning ("BUG: %s: not a valid folder view", __FUNCTION__);
1158                 return;
1159         }
1160
1161         self = MODEST_FOLDER_VIEW (user_data);
1162         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1163
1164         /* Invalidate the cur_folder_store only if the selected folder
1165            belongs to the account that is being removed */
1166         same_account = same_account_selected (self, account);
1167         if (same_account) {
1168                 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1169                 gtk_tree_selection_unselect_all (sel);
1170         }
1171
1172         /* Invalidate row to select only if the folder to select
1173            belongs to the account that is being removed*/
1174         if (priv->folder_to_select) {
1175                 TnyAccount *folder_to_select_account = NULL;
1176
1177                 folder_to_select_account = tny_folder_get_account (priv->folder_to_select);
1178                 if (folder_to_select_account == account) {
1179                         modest_folder_view_disable_next_folder_selection (self);
1180                         g_object_unref (priv->folder_to_select);
1181                         priv->folder_to_select = NULL;
1182                 }
1183                 g_object_unref (folder_to_select_account);
1184         }
1185
1186         /* Remove the account from the model */
1187         filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1188         if (!GTK_IS_TREE_MODEL_FILTER(filter_model)) {
1189                 g_warning ("BUG: %s: not a valid filter model", __FUNCTION__);
1190                 return;
1191         }
1192
1193         sort_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
1194         if (!GTK_IS_TREE_MODEL_SORT(sort_model)) {
1195                 g_warning ("BUG: %s: not a valid sort model", __FUNCTION__);
1196                 return;
1197         }
1198
1199         tny_list_remove (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
1200                          G_OBJECT (account));
1201
1202         /* If the removed account is the currently viewed one then
1203            clear the configuration value. The new visible account will be the default account */
1204         if (priv->visible_account_id &&
1205             !strcmp (priv->visible_account_id, tny_account_get_id (account))) {
1206
1207                 /* Clear the current visible account_id */
1208                 modest_folder_view_set_account_id_of_visible_server_account (self, NULL);
1209
1210                 /* Call the restore method, this will set the new visible account */
1211                 modest_widget_memory_restore (modest_runtime_get_conf(), G_OBJECT(self),
1212                                               MODEST_CONF_FOLDER_VIEW_KEY);
1213         }
1214
1215         /* Refilter the model */
1216         gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1217
1218         /* Select the first INBOX if the currently selected folder
1219            belongs to the account that is being deleted */
1220         if (same_account)
1221                 g_idle_add (on_idle_select_first_inbox_or_local, self);
1222 }
1223
1224 void
1225 modest_folder_view_set_title (ModestFolderView *self, const gchar *title)
1226 {
1227         GtkTreeViewColumn *col;
1228
1229         g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
1230
1231         col = gtk_tree_view_get_column (GTK_TREE_VIEW(self), 0);
1232         if (!col) {
1233                 g_printerr ("modest: failed get column for title\n");
1234                 return;
1235         }
1236
1237         gtk_tree_view_column_set_title (col, title);
1238         gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self),
1239                                            title != NULL);
1240 }
1241
1242 static gboolean
1243 modest_folder_view_on_map (ModestFolderView *self,
1244                            GdkEventExpose *event,
1245                            gpointer data)
1246 {
1247         ModestFolderViewPrivate *priv;
1248
1249         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1250
1251         /* This won't happen often */
1252         if (G_UNLIKELY (priv->reselect)) {
1253                 /* Select the first inbox or the local account if not found */
1254
1255                 /* TODO: this could cause a lock at startup, so we
1256                    comment it for the moment. We know that this will
1257                    be a bug, because the INBOX is not selected, but we
1258                    need to rewrite some parts of Modest to avoid the
1259                    deathlock situation */
1260                 /* TODO: check if this is still the case */
1261                 priv->reselect = FALSE;
1262                 modest_folder_view_select_first_inbox_or_local (self);
1263                 /* Notify the display name observers */
1264                 g_signal_emit (G_OBJECT(self),
1265                                signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
1266                                NULL);
1267         }
1268
1269         if (priv->reexpand) {
1270                 expand_root_items (self);
1271                 priv->reexpand = FALSE;
1272         }
1273
1274         return FALSE;
1275 }
1276
1277 GtkWidget*
1278 modest_folder_view_new (TnyFolderStoreQuery *query)
1279 {
1280         GObject *self;
1281         ModestFolderViewPrivate *priv;
1282         GtkTreeSelection *sel;
1283
1284         self = G_OBJECT (g_object_new (MODEST_TYPE_FOLDER_VIEW, 
1285 #ifdef MODEST_TOOLKIT_HILDON2
1286                                        "hildon-ui-mode", HILDON_UI_MODE_NORMAL,
1287 #endif
1288                                        NULL));
1289         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1290
1291         if (query)
1292                 priv->query = g_object_ref (query);
1293
1294         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1295         priv->changed_signal = g_signal_connect (sel, "changed",
1296                                                  G_CALLBACK (on_selection_changed), self);
1297
1298         g_signal_connect (self, "row-activated", G_CALLBACK (on_row_activated), self);
1299
1300         g_signal_connect (self, "expose-event", G_CALLBACK (modest_folder_view_on_map), NULL);
1301
1302         return GTK_WIDGET(self);
1303 }
1304
1305 /* this feels dirty; any other way to expand all the root items? */
1306 static void
1307 expand_root_items (ModestFolderView *self)
1308 {
1309         GtkTreePath *path;
1310         GtkTreeModel *model;
1311         GtkTreeIter iter;
1312
1313         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1314         path = gtk_tree_path_new_first ();
1315
1316         /* all folders should have child items, so.. */
1317         do {
1318                 gtk_tree_view_expand_row (GTK_TREE_VIEW(self), path, FALSE);
1319                 gtk_tree_path_next (path);
1320         } while (gtk_tree_model_get_iter (model, &iter, path));
1321
1322         gtk_tree_path_free (path);
1323 }
1324
1325 /*
1326  * We use this function to implement the
1327  * MODEST_FOLDER_VIEW_STYLE_SHOW_ONE style. We only show the default
1328  * account in this case, and the local folders.
1329  */
1330 static gboolean
1331 filter_row (GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
1332 {
1333         ModestFolderViewPrivate *priv;
1334         gboolean retval = TRUE;
1335         TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1336         GObject *instance = NULL;
1337         const gchar *id = NULL;
1338         guint i;
1339         gboolean found = FALSE;
1340         gboolean cleared = FALSE;
1341
1342         g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (data), FALSE);
1343         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (data);
1344
1345         gtk_tree_model_get (model, iter,
1346                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
1347                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
1348                             -1);
1349
1350         /* Do not show if there is no instance, this could indeed
1351            happen when the model is being modified while it's being
1352            drawn. This could occur for example when moving folders
1353            using drag&drop */
1354         if (!instance)
1355                 return FALSE;
1356
1357         if (type == TNY_FOLDER_TYPE_ROOT) {
1358                 /* TNY_FOLDER_TYPE_ROOT means that the instance is an
1359                    account instead of a folder. */
1360                 if (TNY_IS_ACCOUNT (instance)) {
1361                         TnyAccount *acc = TNY_ACCOUNT (instance);
1362                         const gchar *account_id = tny_account_get_id (acc);
1363
1364                         /* If it isn't a special folder,
1365                          * don't show it unless it is the visible account: */
1366                         if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE &&
1367                             !modest_tny_account_is_virtual_local_folders (acc) &&
1368                             strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
1369
1370                                 /* Show only the visible account id */
1371                                 if (priv->visible_account_id) {
1372                                         if (strcmp (account_id, priv->visible_account_id))
1373                                                 retval = FALSE;
1374                                 } else {
1375                                         retval = FALSE;
1376                                 }
1377                         }
1378
1379                         /* Never show these to the user. They are merged into one folder
1380                          * in the local-folders account instead: */
1381                         if (retval && MODEST_IS_TNY_OUTBOX_ACCOUNT (acc))
1382                                 retval = FALSE;
1383                 }
1384         }
1385
1386         /* Check hiding (if necessary) */
1387         cleared = modest_email_clipboard_cleared (priv->clipboard);
1388         if ((retval) && (!cleared) && (TNY_IS_FOLDER (instance))) {
1389                 id = tny_folder_get_id (TNY_FOLDER(instance));
1390                 if (priv->hidding_ids != NULL)
1391                         for (i=0; i < priv->n_selected && !found; i++)
1392                                 if (priv->hidding_ids[i] != NULL && id != NULL)
1393                                         found = (!strcmp (priv->hidding_ids[i], id));
1394
1395                 retval = !found;
1396         }
1397
1398
1399         /* If this is a move to dialog, hide Sent, Outbox and Drafts
1400         folder as no message can be move there according to UI specs */
1401         if (!priv->show_non_move) {
1402                 switch (type) {
1403                         case TNY_FOLDER_TYPE_OUTBOX:
1404                         case TNY_FOLDER_TYPE_SENT:
1405                         case TNY_FOLDER_TYPE_DRAFTS:
1406                                 retval = FALSE;
1407                                 break;
1408                         case TNY_FOLDER_TYPE_UNKNOWN:
1409                         case TNY_FOLDER_TYPE_NORMAL:
1410                                 type = modest_tny_folder_guess_folder_type(TNY_FOLDER(instance));
1411                                 if (type == TNY_FOLDER_TYPE_INVALID)
1412                                         g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1413
1414                                 if (type == TNY_FOLDER_TYPE_OUTBOX ||
1415                                     type == TNY_FOLDER_TYPE_SENT
1416                                     || type == TNY_FOLDER_TYPE_DRAFTS)
1417                                         retval = FALSE;
1418                                 break;
1419                         default:
1420                                 break;
1421                 }
1422         }
1423
1424         /* Free */
1425         g_object_unref (instance);
1426
1427         return retval;
1428 }
1429
1430
1431 gboolean
1432 modest_folder_view_update_model (ModestFolderView *self,
1433                                  TnyAccountStore *account_store)
1434 {
1435         ModestFolderViewPrivate *priv;
1436         GtkTreeModel *model /* , *old_model */;
1437         GtkTreeModel *filter_model = NULL, *sortable = NULL;
1438
1439         g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW (self), FALSE);
1440         g_return_val_if_fail (account_store && MODEST_IS_TNY_ACCOUNT_STORE(account_store),
1441                               FALSE);
1442
1443         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1444
1445         /* Notify that there is no folder selected */
1446         g_signal_emit (G_OBJECT(self),
1447                        signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
1448                        NULL, FALSE);
1449         if (priv->cur_folder_store) {
1450                 g_object_unref (priv->cur_folder_store);
1451                 priv->cur_folder_store = NULL;
1452         }
1453
1454         /* FIXME: the local accounts are not shown when the query
1455            selects only the subscribed folders */
1456         model = tny_gtk_folder_store_tree_model_new (NULL);
1457
1458         /* Get the accounts: */
1459         tny_account_store_get_accounts (TNY_ACCOUNT_STORE(account_store),
1460                                         TNY_LIST (model),
1461                                         TNY_ACCOUNT_STORE_STORE_ACCOUNTS);
1462
1463         sortable = gtk_tree_model_sort_new_with_model (model);
1464         gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
1465                                               TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
1466                                               GTK_SORT_ASCENDING);
1467         gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
1468                                          TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
1469                                          cmp_rows, NULL, NULL);
1470
1471         /* Create filter model */
1472         filter_model = gtk_tree_model_filter_new (sortable, NULL);
1473         gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
1474                                                 filter_row,
1475                                                 self,
1476                                                 NULL);
1477
1478         /* Set new model */
1479         gtk_tree_view_set_model (GTK_TREE_VIEW(self), filter_model);
1480         g_signal_connect (G_OBJECT(filter_model), "row-inserted",
1481                           (GCallback) on_row_inserted_maybe_select_folder, self);
1482
1483         g_object_unref (model);
1484         g_object_unref (filter_model);
1485         g_object_unref (sortable);
1486
1487         /* Force a reselection of the INBOX next time the widget is shown */
1488         priv->reselect = TRUE;
1489
1490         return TRUE;
1491 }
1492
1493
1494 static void
1495 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
1496 {
1497         GtkTreeModel *model = NULL;
1498         TnyFolderStore *folder = NULL;
1499         GtkTreeIter iter;
1500         ModestFolderView *tree_view = NULL;
1501         ModestFolderViewPrivate *priv = NULL;
1502         gboolean selected = FALSE;
1503
1504         g_return_if_fail (sel);
1505         g_return_if_fail (user_data);
1506
1507         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
1508
1509         selected = gtk_tree_selection_get_selected (sel, &model, &iter);
1510
1511         tree_view = MODEST_FOLDER_VIEW (user_data);
1512
1513         if (selected) {
1514                 gtk_tree_model_get (model, &iter,
1515                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
1516                                     -1);
1517
1518                 /* If the folder is the same do not notify */
1519                 if (folder && priv->cur_folder_store == folder) {
1520                         g_object_unref (folder);
1521                         return;
1522                 }
1523         }
1524
1525         /* Current folder was unselected */
1526         if (priv->cur_folder_store) {
1527                 /* We must do this firstly because a libtinymail-camel
1528                    implementation detail. If we issue the signal
1529                    before doing the sync_async, then that signal could
1530                    cause (and it actually does it) a free of the
1531                    summary of the folder (because the main window will
1532                    clear the headers view */
1533                 if (TNY_IS_FOLDER(priv->cur_folder_store))
1534                         tny_folder_sync_async (TNY_FOLDER(priv->cur_folder_store),
1535                                                FALSE, NULL, NULL, NULL);
1536
1537                 g_signal_emit (G_OBJECT(tree_view), signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
1538                        priv->cur_folder_store, FALSE);
1539
1540                 g_object_unref (priv->cur_folder_store);
1541                 priv->cur_folder_store = NULL;
1542         }
1543
1544         /* New current references */
1545         priv->cur_folder_store = folder;
1546
1547         /* New folder has been selected. Do not notify if there is
1548            nothing new selected */
1549         if (selected) {
1550                 g_signal_emit (G_OBJECT(tree_view),
1551                                signals[FOLDER_SELECTION_CHANGED_SIGNAL],
1552                                0, priv->cur_folder_store, TRUE);
1553         }
1554 }
1555
1556 static void
1557 on_row_activated (GtkTreeView *treeview,
1558                   GtkTreePath *treepath,
1559                   GtkTreeViewColumn *column,
1560                   gpointer user_data)
1561 {
1562         GtkTreeModel *model = NULL;
1563         TnyFolderStore *folder = NULL;
1564         GtkTreeIter iter;
1565         ModestFolderView *self = NULL;
1566         ModestFolderViewPrivate *priv = NULL;
1567
1568         g_return_if_fail (treeview);
1569         g_return_if_fail (user_data);
1570
1571         self = MODEST_FOLDER_VIEW (user_data);
1572         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
1573
1574         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1575
1576         if (!gtk_tree_model_get_iter (model, &iter, treepath))
1577                 return;
1578
1579         gtk_tree_model_get (model, &iter,
1580                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
1581                             -1);
1582
1583         g_signal_emit (G_OBJECT(self),
1584                        signals[FOLDER_ACTIVATED_SIGNAL],
1585                        0, folder);
1586
1587         g_object_unref (folder);
1588 }
1589
1590 TnyFolderStore *
1591 modest_folder_view_get_selected (ModestFolderView *self)
1592 {
1593         ModestFolderViewPrivate *priv;
1594
1595         g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW(self), NULL);
1596
1597         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1598         if (priv->cur_folder_store)
1599                 g_object_ref (priv->cur_folder_store);
1600
1601         return priv->cur_folder_store;
1602 }
1603
1604 static gint
1605 get_cmp_rows_type_pos (GObject *folder)
1606 {
1607         /* Remote accounts -> Local account -> MMC account .*/
1608         /* 0, 1, 2 */
1609
1610         if (TNY_IS_ACCOUNT (folder) &&
1611                 modest_tny_account_is_virtual_local_folders (
1612                         TNY_ACCOUNT (folder))) {
1613                 return 1;
1614         } else if (TNY_IS_ACCOUNT (folder)) {
1615                 TnyAccount *account = TNY_ACCOUNT (folder);
1616                 const gchar *account_id = tny_account_get_id (account);
1617                 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
1618                         return 2;
1619                 else
1620                         return 0;
1621         }
1622         else {
1623                 printf ("DEBUG: %s: unexpected type.\n", __FUNCTION__);
1624                 return -1; /* Should never happen */
1625         }
1626 }
1627
1628 static gint
1629 get_cmp_subfolder_type_pos (TnyFolderType t)
1630 {
1631         /* Inbox, Outbox, Drafts, Sent, User */
1632         /* 0, 1, 2, 3, 4 */
1633
1634         switch (t) {
1635         case TNY_FOLDER_TYPE_INBOX:
1636                 return 0;
1637                 break;
1638         case TNY_FOLDER_TYPE_OUTBOX:
1639                 return 1;
1640                 break;
1641         case TNY_FOLDER_TYPE_DRAFTS:
1642                 return 2;
1643                 break;
1644         case TNY_FOLDER_TYPE_SENT:
1645                 return 3;
1646                 break;
1647         default:
1648                 return 4;
1649         }
1650 }
1651
1652 /*
1653  * This function orders the mail accounts according to these rules:
1654  * 1st - remote accounts
1655  * 2nd - local account
1656  * 3rd - MMC account
1657  */
1658 static gint
1659 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1660           gpointer user_data)
1661 {
1662         gint cmp = 0;
1663         gchar *name1 = NULL;
1664         gchar *name2 = NULL;
1665         TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1666         TnyFolderType type2 = TNY_FOLDER_TYPE_UNKNOWN;
1667         GObject *folder1 = NULL;
1668         GObject *folder2 = NULL;
1669
1670         gtk_tree_model_get (tree_model, iter1,
1671                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name1,
1672                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
1673                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder1,
1674                             -1);
1675         gtk_tree_model_get (tree_model, iter2,
1676                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name2,
1677                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type2,
1678                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder2,
1679                             -1);
1680
1681         /* Return if we get no folder. This could happen when folder
1682            operations are happening. The model is updated after the
1683            folder copy/move actually occurs, so there could be
1684            situations where the model to be drawn is not correct */
1685         if (!folder1 || !folder2)
1686                 goto finish;
1687
1688         if (type == TNY_FOLDER_TYPE_ROOT) {
1689                 /* Compare the types, so that
1690                  * Remote accounts -> Local account -> MMC account .*/
1691                 const gint pos1 = get_cmp_rows_type_pos (folder1);
1692                 const gint pos2 = get_cmp_rows_type_pos (folder2);
1693                 /* printf ("DEBUG: %s:\n  type1=%s, pos1=%d\n  type2=%s, pos2=%d\n",
1694                         __FUNCTION__, G_OBJECT_TYPE_NAME(folder1), pos1, G_OBJECT_TYPE_NAME(folder2), pos2); */
1695                 if (pos1 <  pos2)
1696                         cmp = -1;
1697                 else if (pos1 > pos2)
1698                         cmp = 1;
1699                 else {
1700                         /* Compare items of the same type: */
1701
1702                         TnyAccount *account1 = NULL;
1703                         if (TNY_IS_ACCOUNT (folder1))
1704                                 account1 = TNY_ACCOUNT (folder1);
1705
1706                         TnyAccount *account2 = NULL;
1707                         if (TNY_IS_ACCOUNT (folder2))
1708                                 account2 = TNY_ACCOUNT (folder2);
1709
1710                         const gchar *account_id = account1 ? tny_account_get_id (account1) : NULL;
1711                         const gchar *account_id2 = account2 ? tny_account_get_id (account2) : NULL;
1712
1713                         if (!account_id && !account_id2) {
1714                                 cmp = 0;
1715                         } else if (!account_id) {
1716                                 cmp = -1;
1717                         } else if (!account_id2) {
1718                                 cmp = +1;
1719                         } else if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
1720                                 cmp = +1;
1721                         } else {
1722                                 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
1723                         }
1724                 }
1725         } else {
1726                 gint cmp1 = 0, cmp2 = 0;
1727                 /* get the parent to know if it's a local folder */
1728
1729                 GtkTreeIter parent;
1730                 gboolean has_parent;
1731                 has_parent = gtk_tree_model_iter_parent (tree_model, &parent, iter1);
1732                 if (has_parent) {
1733                         GObject *parent_folder;
1734                         TnyFolderType parent_type = TNY_FOLDER_TYPE_UNKNOWN;
1735                         gtk_tree_model_get (tree_model, &parent,
1736                                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &parent_type,
1737                                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &parent_folder,
1738                                             -1);
1739                         if ((parent_type == TNY_FOLDER_TYPE_ROOT) &&
1740                             TNY_IS_ACCOUNT (parent_folder)) {
1741                                 if (modest_tny_account_is_virtual_local_folders (TNY_ACCOUNT (parent_folder))) {
1742                                         cmp1 = get_cmp_subfolder_type_pos (modest_tny_folder_get_local_or_mmc_folder_type
1743                                                                            (TNY_FOLDER (folder1)));
1744                                         cmp2 = get_cmp_subfolder_type_pos (modest_tny_folder_get_local_or_mmc_folder_type
1745                                                                            (TNY_FOLDER (folder2)));
1746                                 } else if (modest_tny_account_is_memory_card_account (TNY_ACCOUNT (parent_folder))) {
1747                                         if (modest_local_folder_info_get_type (tny_folder_get_name (TNY_FOLDER (folder1))) == TNY_FOLDER_TYPE_ARCHIVE) {
1748                                                 cmp1 = 0;
1749                                                 cmp2 = 1;
1750                                                 } else if (modest_local_folder_info_get_type (tny_folder_get_name (TNY_FOLDER (folder2))) == TNY_FOLDER_TYPE_ARCHIVE) {
1751                                                 cmp1 = 1;
1752                                                 cmp2 = 0;
1753                                         }
1754                                 }
1755                         }
1756                         g_object_unref (parent_folder);
1757                 }
1758
1759                 /* if they are not local folders */
1760                 if (cmp1 == cmp2) {
1761                         cmp1 = get_cmp_subfolder_type_pos (tny_folder_get_folder_type (TNY_FOLDER (folder1)));
1762                         cmp2 = get_cmp_subfolder_type_pos (tny_folder_get_folder_type (TNY_FOLDER (folder2)));
1763                 }
1764
1765                 if (cmp1 == cmp2)
1766                         cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
1767                 else
1768                         cmp = (cmp1 - cmp2);
1769         }
1770
1771 finish:
1772         if (folder1)
1773                 g_object_unref(G_OBJECT(folder1));
1774         if (folder2)
1775                 g_object_unref(G_OBJECT(folder2));
1776
1777         g_free (name1);
1778         g_free (name2);
1779
1780         return cmp;
1781 }
1782
1783 /*****************************************************************************/
1784 /*                        DRAG and DROP stuff                                */
1785 /*****************************************************************************/
1786 /*
1787  * This function fills the #GtkSelectionData with the row and the
1788  * model that has been dragged. It's called when this widget is a
1789  * source for dnd after the event drop happened
1790  */
1791 static void
1792 on_drag_data_get (GtkWidget *widget, GdkDragContext *context, GtkSelectionData *selection_data,
1793                   guint info, guint time, gpointer data)
1794 {
1795         GtkTreeSelection *selection;
1796         GtkTreeModel *model;
1797         GtkTreeIter iter;
1798         GtkTreePath *source_row;
1799
1800         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1801         if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
1802
1803                 source_row = gtk_tree_model_get_path (model, &iter);
1804                 gtk_tree_set_row_drag_data (selection_data,
1805                                             model,
1806                                             source_row);
1807
1808                 gtk_tree_path_free (source_row);
1809         }
1810 }
1811
1812 typedef struct _DndHelper {
1813         ModestFolderView *folder_view;
1814         gboolean delete_source;
1815         GtkTreePath *source_row;
1816 } DndHelper;
1817
1818 static void
1819 dnd_helper_destroyer (DndHelper *helper)
1820 {
1821         /* Free the helper */
1822         gtk_tree_path_free (helper->source_row);
1823         g_slice_free (DndHelper, helper);
1824 }
1825
1826 static void
1827 xfer_folder_cb (ModestMailOperation *mail_op,
1828                 TnyFolder *new_folder,
1829                 gpointer user_data)
1830 {
1831         if (new_folder) {
1832                 /* Select the folder */
1833                 modest_folder_view_select_folder (MODEST_FOLDER_VIEW (user_data),
1834                                                   new_folder, FALSE);
1835         }
1836 }
1837
1838
1839 /* get the folder for the row the treepath refers to. */
1840 /* folder must be unref'd */
1841 static TnyFolderStore *
1842 tree_path_to_folder (GtkTreeModel *model, GtkTreePath *path)
1843 {
1844         GtkTreeIter iter;
1845         TnyFolderStore *folder = NULL;
1846
1847         if (gtk_tree_model_get_iter (model,&iter, path))
1848                 gtk_tree_model_get (model, &iter,
1849                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
1850                                     -1);
1851         return folder;
1852 }
1853
1854
1855 /*
1856  * This function is used by drag_data_received_cb to manage drag and
1857  * drop of a header, i.e, and drag from the header view to the folder
1858  * view.
1859  */
1860 static void
1861 drag_and_drop_from_header_view (GtkTreeModel *source_model,
1862                                 GtkTreeModel *dest_model,
1863                                 GtkTreePath  *dest_row,
1864                                 GtkSelectionData *selection_data)
1865 {
1866         TnyList *headers = NULL;
1867         TnyFolder *folder = NULL, *src_folder = NULL;
1868         TnyFolderType folder_type;
1869         GtkTreeIter source_iter, dest_iter;
1870         ModestWindowMgr *mgr = NULL;
1871         ModestWindow *main_win = NULL;
1872         gchar **uris, **tmp;
1873
1874         /* Build the list of headers */
1875         mgr = modest_runtime_get_window_mgr ();
1876         headers = tny_simple_list_new ();
1877         uris = modest_dnd_selection_data_get_paths (selection_data);
1878         tmp = uris;
1879
1880         while (*tmp != NULL) {
1881                 TnyHeader *header;
1882                 GtkTreePath *path;
1883                 gboolean first = TRUE;
1884
1885                 /* Get header */
1886                 path = gtk_tree_path_new_from_string (*tmp);
1887                 gtk_tree_model_get_iter (source_model, &source_iter, path);
1888                 gtk_tree_model_get (source_model, &source_iter,
1889                                     TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1890                                     &header, -1);
1891
1892                 /* Do not enable d&d of headers already opened */
1893                 if (!modest_window_mgr_find_registered_header(mgr, header, NULL))
1894                         tny_list_append (headers, G_OBJECT (header));
1895
1896                 if (G_UNLIKELY (first)) {
1897                         src_folder = tny_header_get_folder (header);
1898                         first = FALSE;
1899                 }
1900
1901                 /* Free and go on */
1902                 gtk_tree_path_free (path);
1903                 g_object_unref (header);
1904                 tmp++;
1905         }
1906         g_strfreev (uris);
1907
1908         /* This could happen ig we perform a d&d very quickly over the
1909            same row that row could dissapear because message is
1910            transferred */
1911         if (!TNY_IS_FOLDER (src_folder))
1912                 goto cleanup;
1913
1914         /* Get the target folder */
1915         gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
1916         gtk_tree_model_get (dest_model, &dest_iter,
1917                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
1918                             &folder, -1);
1919
1920         if (!folder || !TNY_IS_FOLDER(folder)) {
1921 /*              g_warning ("%s: not a valid target folder (%p)", __FUNCTION__, folder); */
1922                 goto cleanup;
1923         }
1924
1925         folder_type = modest_tny_folder_guess_folder_type (folder);
1926         if (folder_type == TNY_FOLDER_TYPE_INVALID) {
1927 /*              g_warning ("%s: invalid target folder", __FUNCTION__); */
1928                 goto cleanup;  /* cannot move messages there */
1929         }
1930
1931         if (modest_tny_folder_get_rules((TNY_FOLDER(folder))) & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE) {
1932 /*              g_warning ("folder not writable"); */
1933                 goto cleanup; /* verboten! */
1934         }
1935
1936         /* Ask for confirmation to move */
1937         main_win = modest_window_mgr_get_main_window (mgr, FALSE); /* don't create */
1938         if (!main_win) {
1939                 g_warning ("%s: BUG: no main window found", __FUNCTION__);
1940                 goto cleanup;
1941         }
1942
1943         /* Transfer messages */
1944         modest_ui_actions_transfer_messages_helper (GTK_WINDOW (main_win), src_folder,
1945                                                     headers, folder);
1946
1947         /* Frees */
1948 cleanup:
1949         if (G_IS_OBJECT (src_folder))
1950                 g_object_unref (src_folder);
1951         if (G_IS_OBJECT(folder))
1952                 g_object_unref (G_OBJECT (folder));
1953         if (G_IS_OBJECT(headers))
1954                 g_object_unref (headers);
1955 }
1956
1957 typedef struct {
1958         TnyFolderStore *src_folder;
1959         TnyFolderStore *dst_folder;
1960         ModestFolderView *folder_view;
1961         DndHelper *helper;
1962 } DndFolderInfo;
1963
1964 static void
1965 dnd_folder_info_destroyer (DndFolderInfo *info)
1966 {
1967         if (info->src_folder)
1968                 g_object_unref (info->src_folder);
1969         if (info->dst_folder)
1970                 g_object_unref (info->dst_folder);
1971         g_slice_free (DndFolderInfo, info);
1972 }
1973
1974 static void
1975 dnd_on_connection_failed_destroyer (DndFolderInfo *info,
1976                                     GtkWindow *parent_window,
1977                                     TnyAccount *account)
1978 {
1979         /* Show error */
1980         modest_ui_actions_on_account_connection_error (parent_window, account);
1981
1982         /* Free the helper & info */
1983         dnd_helper_destroyer (info->helper);
1984         dnd_folder_info_destroyer (info);
1985 }
1986
1987 static void
1988 drag_and_drop_from_folder_view_src_folder_performer (gboolean canceled,
1989                                                      GError *err,
1990                                                      GtkWindow *parent_window,
1991                                                      TnyAccount *account,
1992                                                      gpointer user_data)
1993 {
1994         DndFolderInfo *info = NULL;
1995         ModestMailOperation *mail_op;
1996
1997         info = (DndFolderInfo *) user_data;
1998
1999         if (err || canceled) {
2000                 dnd_on_connection_failed_destroyer (info, parent_window, account);
2001                 return;
2002         }
2003
2004         /* Do the mail operation */
2005         mail_op = modest_mail_operation_new_with_error_handling ((GObject *) parent_window,
2006                                                                  modest_ui_actions_move_folder_error_handler,
2007                                                                  info->src_folder, NULL);
2008
2009         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
2010                                          mail_op);
2011
2012         /* Transfer the folder */
2013         modest_mail_operation_xfer_folder (mail_op,
2014                                            TNY_FOLDER (info->src_folder),
2015                                            info->dst_folder,
2016                                            info->helper->delete_source,
2017                                            xfer_folder_cb,
2018                                            info->helper->folder_view);
2019
2020         /* Frees */
2021         g_object_unref (G_OBJECT (mail_op));
2022         dnd_helper_destroyer (info->helper);
2023         dnd_folder_info_destroyer (info);
2024 }
2025
2026
2027 static void
2028 drag_and_drop_from_folder_view_dst_folder_performer (gboolean canceled,
2029                                                      GError *err,
2030                                                      GtkWindow *parent_window,
2031                                                      TnyAccount *account,
2032                                                      gpointer user_data)
2033 {
2034         DndFolderInfo *info = NULL;
2035
2036         info = (DndFolderInfo *) user_data;
2037
2038         if (err || canceled) {
2039                 dnd_on_connection_failed_destroyer (info, parent_window, account);
2040                 return;
2041         }
2042
2043         /* Connect to source folder and perform the copy/move */
2044         modest_platform_connect_if_remote_and_perform (NULL, TRUE,
2045                                                        info->src_folder,
2046                                                        drag_and_drop_from_folder_view_src_folder_performer,
2047                                                        info);
2048 }
2049
2050 /*
2051  * This function is used by drag_data_received_cb to manage drag and
2052  * drop of a folder, i.e, and drag from the folder view to the same
2053  * folder view.
2054  */
2055 static void
2056 drag_and_drop_from_folder_view (GtkTreeModel     *source_model,
2057                                 GtkTreeModel     *dest_model,
2058                                 GtkTreePath      *dest_row,
2059                                 GtkSelectionData *selection_data,
2060                                 DndHelper        *helper)
2061 {
2062         GtkTreeIter dest_iter, iter;
2063         TnyFolderStore *dest_folder = NULL;
2064         TnyFolderStore *folder = NULL;
2065         gboolean forbidden = FALSE;
2066         ModestWindow *win;
2067         DndFolderInfo *info = NULL;
2068
2069         win = modest_window_mgr_get_main_window (modest_runtime_get_window_mgr(), FALSE); /* don't create */
2070         if (!win) {
2071                 g_warning ("%s: BUG: no main window", __FUNCTION__);
2072                 dnd_helper_destroyer (helper);
2073                 return;
2074         }
2075
2076         if (!forbidden) {
2077                 /* check the folder rules for the destination */
2078                 folder = tree_path_to_folder (dest_model, dest_row);
2079                 if (TNY_IS_FOLDER(folder)) {
2080                         ModestTnyFolderRules rules =
2081                                 modest_tny_folder_get_rules (TNY_FOLDER (folder));
2082                         forbidden = rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE;
2083                 } else if (TNY_IS_FOLDER_STORE(folder)) {
2084                         /* enable local root as destination for folders */
2085                         if (!MODEST_IS_TNY_LOCAL_FOLDERS_ACCOUNT (folder) &&
2086                             !modest_tny_account_is_memory_card_account (TNY_ACCOUNT (folder)))
2087                                 forbidden = TRUE;
2088                 }
2089                 g_object_unref (folder);
2090         }
2091         if (!forbidden) {
2092                 /* check the folder rules for the source */
2093                 folder = tree_path_to_folder (source_model, helper->source_row);
2094                 if (TNY_IS_FOLDER(folder)) {
2095                         ModestTnyFolderRules rules =
2096                                 modest_tny_folder_get_rules (TNY_FOLDER (folder));
2097                         forbidden = rules & MODEST_FOLDER_RULES_FOLDER_NON_MOVEABLE;
2098                 } else
2099                         forbidden = TRUE;
2100                 g_object_unref (folder);
2101         }
2102
2103
2104         /* Check if the drag is possible */
2105         if (forbidden || !gtk_tree_path_compare (helper->source_row, dest_row)) {
2106                 /* Show error */
2107                 modest_platform_run_information_dialog ((GtkWindow *) win, 
2108                                                         _("mail_in_ui_folder_move_target_error"), 
2109                                                         FALSE);
2110                 /* Restore the previous selection */
2111                 folder = tree_path_to_folder (source_model, helper->source_row);
2112                 if (folder) {
2113                         if (TNY_IS_FOLDER (folder))
2114                                 modest_folder_view_select_folder (helper->folder_view, 
2115                                                                   TNY_FOLDER (folder), FALSE);
2116                         g_object_unref (folder);
2117                 }
2118                 dnd_helper_destroyer (helper);
2119                 return;
2120         }
2121
2122         /* Get data */
2123         gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
2124         gtk_tree_model_get (dest_model, &dest_iter,
2125                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
2126                             &dest_folder, -1);
2127         gtk_tree_model_get_iter (source_model, &iter, helper->source_row);
2128         gtk_tree_model_get (source_model, &iter,
2129                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
2130                             &folder, -1);
2131
2132         /* Create the info for the performer */
2133         info = g_slice_new0 (DndFolderInfo);
2134         info->src_folder = g_object_ref (folder);
2135         info->dst_folder = g_object_ref (dest_folder);
2136         info->helper = helper;
2137
2138         /* Connect to the destination folder and perform the copy/move */
2139         modest_platform_connect_if_remote_and_perform (GTK_WINDOW (win), TRUE,
2140                                                        dest_folder,
2141                                                        drag_and_drop_from_folder_view_dst_folder_performer,
2142                                                        info);
2143
2144         /* Frees */
2145         g_object_unref (dest_folder);
2146         g_object_unref (folder);
2147 }
2148
2149 /*
2150  * This function receives the data set by the "drag-data-get" signal
2151  * handler. This information comes within the #GtkSelectionData. This
2152  * function will manage both the drags of folders of the treeview and
2153  * drags of headers of the header view widget.
2154  */
2155 static void
2156 on_drag_data_received (GtkWidget *widget,
2157                        GdkDragContext *context,
2158                        gint x,
2159                        gint y,
2160                        GtkSelectionData *selection_data,
2161                        guint target_type,
2162                        guint time,
2163                        gpointer data)
2164 {
2165         GtkWidget *source_widget;
2166         GtkTreeModel *dest_model, *source_model;
2167         GtkTreePath *source_row, *dest_row;
2168         GtkTreeViewDropPosition pos;
2169         gboolean delete_source = FALSE;
2170         gboolean success = FALSE;
2171
2172         /* Do not allow further process */
2173         g_signal_stop_emission_by_name (widget, "drag-data-received");
2174         source_widget = gtk_drag_get_source_widget (context);
2175
2176         /* Get the action */
2177         if (context->action == GDK_ACTION_MOVE) {
2178                 delete_source = TRUE;
2179
2180                 /* Notify that there is no folder selected. We need to
2181                    do this in order to update the headers view (and
2182                    its monitors, because when moving, the old folder
2183                    won't longer exist. We can not wait for the end of
2184                    the operation, because the operation won't start if
2185                    the folder is in use */
2186                 if (source_widget == widget) {
2187                         GtkTreeSelection *sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
2188                         gtk_tree_selection_unselect_all (sel);
2189                 }
2190         }
2191
2192         /* Check if the get_data failed */
2193         if (selection_data == NULL || selection_data->length < 0)
2194                 goto end;
2195
2196         /* Select the destination model */
2197         dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
2198
2199         /* Get the path to the destination row. Can not call
2200            gtk_tree_view_get_drag_dest_row() because the source row
2201            is not selected anymore */
2202         gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget), x, y,
2203                                            &dest_row, &pos);
2204
2205         /* Only allow drops IN other rows */
2206         if (!dest_row ||
2207             pos == GTK_TREE_VIEW_DROP_BEFORE ||
2208             pos == GTK_TREE_VIEW_DROP_AFTER)
2209                 goto end;
2210
2211         success = TRUE;
2212         /* Drags from the header view */
2213         if (source_widget != widget) {
2214                 source_model = gtk_tree_view_get_model (GTK_TREE_VIEW (source_widget));
2215
2216                 drag_and_drop_from_header_view (source_model,
2217                                                 dest_model,
2218                                                 dest_row,
2219                                                 selection_data);
2220         } else {
2221                 DndHelper *helper = NULL;
2222
2223                 /* Get the source model and row */
2224                 gtk_tree_get_row_drag_data (selection_data,
2225                                             &source_model,
2226                                             &source_row);
2227
2228                 /* Create the helper */
2229                 helper = g_slice_new0 (DndHelper);
2230                 helper->delete_source = delete_source;
2231                 helper->source_row = gtk_tree_path_copy (source_row);
2232                 helper->folder_view = MODEST_FOLDER_VIEW (widget);
2233
2234                 drag_and_drop_from_folder_view (source_model,
2235                                                 dest_model,
2236                                                 dest_row,
2237                                                 selection_data,
2238                                                 helper);
2239
2240                 gtk_tree_path_free (source_row);
2241         }
2242
2243         /* Frees */
2244         gtk_tree_path_free (dest_row);
2245
2246  end:
2247         /* Finish the drag and drop */
2248         gtk_drag_finish (context, success, FALSE, time);
2249 }
2250
2251 /*
2252  * We define a "drag-drop" signal handler because we do not want to
2253  * use the default one, because the default one always calls
2254  * gtk_drag_finish and we prefer to do it in the "drag-data-received"
2255  * signal handler, because there we have all the information available
2256  * to know if the dnd was a success or not.
2257  */
2258 static gboolean
2259 drag_drop_cb (GtkWidget      *widget,
2260               GdkDragContext *context,
2261               gint            x,
2262               gint            y,
2263               guint           time,
2264               gpointer        user_data)
2265 {
2266         gpointer target;
2267
2268         if (!context->targets)
2269                 return FALSE;
2270
2271         /* Check if we're dragging a folder row */
2272         target = gtk_drag_dest_find_target (widget, context, NULL);
2273
2274         /* Request the data from the source. */
2275         gtk_drag_get_data(widget, context, target, time);
2276
2277     return TRUE;
2278 }
2279
2280 /*
2281  * This function expands a node of a tree view if it's not expanded
2282  * yet. Not sure why it needs the threads stuff, but gtk+`example code
2283  * does that, so that's why they're here.
2284  */
2285 static gint
2286 expand_row_timeout (gpointer data)
2287 {
2288         GtkTreeView *tree_view = data;
2289         GtkTreePath *dest_path = NULL;
2290         GtkTreeViewDropPosition pos;
2291         gboolean result = FALSE;
2292
2293         gdk_threads_enter ();
2294
2295         gtk_tree_view_get_drag_dest_row (tree_view,
2296                                          &dest_path,
2297                                          &pos);
2298
2299         if (dest_path &&
2300             (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER ||
2301              pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)) {
2302                 gtk_tree_view_expand_row (tree_view, dest_path, FALSE);
2303                 gtk_tree_path_free (dest_path);
2304         }
2305         else {
2306                 if (dest_path)
2307                         gtk_tree_path_free (dest_path);
2308
2309                 result = TRUE;
2310         }
2311
2312         gdk_threads_leave ();
2313
2314         return result;
2315 }
2316
2317 /*
2318  * This function is called whenever the pointer is moved over a widget
2319  * while dragging some data. It installs a timeout that will expand a
2320  * node of the treeview if not expanded yet. This function also calls
2321  * gdk_drag_status in order to set the suggested action that will be
2322  * used by the "drag-data-received" signal handler to know if we
2323  * should do a move or just a copy of the data.
2324  */
2325 static gboolean
2326 on_drag_motion (GtkWidget      *widget,
2327                 GdkDragContext *context,
2328                 gint            x,
2329                 gint            y,
2330                 guint           time,
2331                 gpointer        user_data)
2332 {
2333         GtkTreeViewDropPosition pos;
2334         GtkTreePath *dest_row;
2335         GtkTreeModel *dest_model;
2336         ModestFolderViewPrivate *priv;
2337         GdkDragAction suggested_action;
2338         gboolean valid_location = FALSE;
2339         TnyFolderStore *folder = NULL;
2340
2341         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
2342
2343         if (priv->timer_expander != 0) {
2344                 g_source_remove (priv->timer_expander);
2345                 priv->timer_expander = 0;
2346         }
2347
2348         gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
2349                                            x, y,
2350                                            &dest_row,
2351                                            &pos);
2352
2353         /* Do not allow drops between folders */
2354         if (!dest_row ||
2355             pos == GTK_TREE_VIEW_DROP_BEFORE ||
2356             pos == GTK_TREE_VIEW_DROP_AFTER) {
2357                 gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW (widget), NULL, 0);
2358                 gdk_drag_status(context, 0, time);
2359                 valid_location = FALSE;
2360                 goto out;
2361         } else {
2362                 valid_location = TRUE;
2363         }
2364
2365         /* Check that the destination folder is writable */
2366         dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
2367         folder = tree_path_to_folder (dest_model, dest_row);
2368         if (folder && TNY_IS_FOLDER (folder)) {
2369                 ModestTnyFolderRules rules = modest_tny_folder_get_rules(TNY_FOLDER (folder));
2370
2371                 if (rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE) {
2372                         valid_location = FALSE;
2373                         goto out;
2374                 }
2375         }
2376
2377         /* Expand the selected row after 1/2 second */
2378         if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), dest_row)) {
2379                 priv->timer_expander = g_timeout_add (500, expand_row_timeout, widget);
2380         }
2381         gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), dest_row, pos);
2382
2383         /* Select the desired action. By default we pick MOVE */
2384         suggested_action = GDK_ACTION_MOVE;
2385
2386         if (context->actions == GDK_ACTION_COPY)
2387             gdk_drag_status(context, GDK_ACTION_COPY, time);
2388         else if (context->actions == GDK_ACTION_MOVE)
2389             gdk_drag_status(context, GDK_ACTION_MOVE, time);
2390         else if (context->actions & suggested_action)
2391             gdk_drag_status(context, suggested_action, time);
2392         else
2393             gdk_drag_status(context, GDK_ACTION_DEFAULT, time);
2394
2395  out:
2396         if (folder)
2397                 g_object_unref (folder);
2398         if (dest_row) {
2399                 gtk_tree_path_free (dest_row);
2400         }
2401         g_signal_stop_emission_by_name (widget, "drag-motion");
2402
2403         return valid_location;
2404 }
2405
2406 /*
2407  * This function sets the treeview as a source and a target for dnd
2408  * events. It also connects all the requirede signals.
2409  */
2410 static void
2411 setup_drag_and_drop (GtkTreeView *self)
2412 {
2413         /* Set up the folder view as a dnd destination. Set only the
2414            highlight flag, otherwise gtk will have a different
2415            behaviour */
2416 #ifdef MODEST_TOOLKIT_HILDON2
2417         return;
2418 #endif
2419         gtk_drag_dest_set (GTK_WIDGET (self),
2420                            GTK_DEST_DEFAULT_HIGHLIGHT,
2421                            folder_view_drag_types,
2422                            G_N_ELEMENTS (folder_view_drag_types),
2423                            GDK_ACTION_MOVE | GDK_ACTION_COPY);
2424
2425         g_signal_connect (G_OBJECT (self),
2426                           "drag_data_received",
2427                           G_CALLBACK (on_drag_data_received),
2428                           NULL);
2429
2430
2431         /* Set up the treeview as a dnd source */
2432         gtk_drag_source_set (GTK_WIDGET (self),
2433                              GDK_BUTTON1_MASK,
2434                              folder_view_drag_types,
2435                              G_N_ELEMENTS (folder_view_drag_types),
2436                              GDK_ACTION_MOVE | GDK_ACTION_COPY);
2437
2438         g_signal_connect (G_OBJECT (self),
2439                           "drag_motion",
2440                           G_CALLBACK (on_drag_motion),
2441                           NULL);
2442
2443         g_signal_connect (G_OBJECT (self),
2444                           "drag_data_get",
2445                           G_CALLBACK (on_drag_data_get),
2446                           NULL);
2447
2448         g_signal_connect (G_OBJECT (self),
2449                           "drag_drop",
2450                           G_CALLBACK (drag_drop_cb),
2451                           NULL);
2452 }
2453
2454 /*
2455  * This function manages the navigation through the folders using the
2456  * keyboard or the hardware keys in the device
2457  */
2458 static gboolean
2459 on_key_pressed (GtkWidget *self,
2460                 GdkEventKey *event,
2461                 gpointer user_data)
2462 {
2463         GtkTreeSelection *selection;
2464         GtkTreeIter iter;
2465         GtkTreeModel *model;
2466         gboolean retval = FALSE;
2467
2468         /* Up and Down are automatically managed by the treeview */
2469         if (event->keyval == GDK_Return) {
2470                 /* Expand/Collapse the selected row */
2471                 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2472                 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
2473                         GtkTreePath *path;
2474
2475                         path = gtk_tree_model_get_path (model, &iter);
2476
2477                         if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (self), path))
2478                                 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
2479                         else
2480                                 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
2481                         gtk_tree_path_free (path);
2482                 }
2483                 /* No further processing */
2484                 retval = TRUE;
2485         }
2486
2487         return retval;
2488 }
2489
2490 /*
2491  * We listen to the changes in the local folder account name key,
2492  * because we want to show the right name in the view. The local
2493  * folder account name corresponds to the device name in the Maemo
2494  * version. We do this because we do not want to query gconf on each
2495  * tree view refresh. It's better to cache it and change whenever
2496  * necessary.
2497  */
2498 static void
2499 on_configuration_key_changed (ModestConf* conf,
2500                               const gchar *key,
2501                               ModestConfEvent event,
2502                               ModestConfNotificationId id,
2503                               ModestFolderView *self)
2504 {
2505         ModestFolderViewPrivate *priv;
2506
2507
2508         g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
2509         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2510
2511         if (!strcmp (key, MODEST_CONF_DEVICE_NAME)) {
2512                 g_free (priv->local_account_name);
2513
2514                 if (event == MODEST_CONF_EVENT_KEY_UNSET)
2515                         priv->local_account_name = g_strdup (MODEST_LOCAL_FOLDERS_DEFAULT_DISPLAY_NAME);
2516                 else
2517                         priv->local_account_name = modest_conf_get_string (modest_runtime_get_conf(),
2518                                                                            MODEST_CONF_DEVICE_NAME, NULL);
2519
2520                 /* Force a redraw */
2521 #if GTK_CHECK_VERSION(2, 8, 0)
2522                 GtkTreeViewColumn * tree_column;
2523
2524                 tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self),
2525                                                         TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN);
2526                 gtk_tree_view_column_queue_resize (tree_column);
2527 #else
2528                 gtk_widget_queue_draw (GTK_WIDGET (self));
2529 #endif
2530         }
2531 }
2532
2533 void
2534 modest_folder_view_set_style (ModestFolderView *self,
2535                               ModestFolderViewStyle style)
2536 {
2537         ModestFolderViewPrivate *priv;
2538
2539         g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2540         g_return_if_fail (style == MODEST_FOLDER_VIEW_STYLE_SHOW_ALL ||
2541                           style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE);
2542
2543         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2544
2545
2546         priv->style = style;
2547 }
2548
2549 void
2550 modest_folder_view_set_account_id_of_visible_server_account (ModestFolderView *self,
2551                                                              const gchar *account_id)
2552 {
2553         ModestFolderViewPrivate *priv;
2554         GtkTreeModel *model;
2555
2556         g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2557
2558         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2559
2560         /* This will be used by the filter_row callback,
2561          * to decided which rows to show: */
2562         if (priv->visible_account_id) {
2563                 g_free (priv->visible_account_id);
2564                 priv->visible_account_id = NULL;
2565         }
2566         if (account_id)
2567                 priv->visible_account_id = g_strdup (account_id);
2568
2569         /* Refilter */
2570         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2571         if (GTK_IS_TREE_MODEL_FILTER (model))
2572                 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2573
2574         /* Save settings to gconf */
2575         modest_widget_memory_save (modest_runtime_get_conf (), G_OBJECT(self),
2576                                    MODEST_CONF_FOLDER_VIEW_KEY);
2577 }
2578
2579 const gchar *
2580 modest_folder_view_get_account_id_of_visible_server_account (ModestFolderView *self)
2581 {
2582         ModestFolderViewPrivate *priv;
2583
2584         g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW(self), NULL);
2585
2586         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2587
2588         return (const gchar *) priv->visible_account_id;
2589 }
2590
2591 static gboolean
2592 find_inbox_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *inbox_iter)
2593 {
2594         do {
2595                 GtkTreeIter child;
2596                 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2597
2598                 gtk_tree_model_get (model, iter,
2599                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN,
2600                                     &type, -1);
2601
2602                 gboolean result = FALSE;
2603                 if (type == TNY_FOLDER_TYPE_INBOX) {
2604                         result = TRUE;
2605                 }
2606                 if (result) {
2607                         *inbox_iter = *iter;
2608                         return TRUE;
2609                 }
2610
2611                 if (gtk_tree_model_iter_children (model, &child, iter)) {
2612                         if (find_inbox_iter (model, &child, inbox_iter))
2613                                 return TRUE;
2614                 }
2615
2616         } while (gtk_tree_model_iter_next (model, iter));
2617
2618         return FALSE;
2619 }
2620
2621
2622
2623
2624 void
2625 modest_folder_view_select_first_inbox_or_local (ModestFolderView *self)
2626 {
2627         GtkTreeModel *model;
2628         GtkTreeIter iter, inbox_iter;
2629         GtkTreeSelection *sel;
2630         GtkTreePath *path = NULL;
2631
2632         g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2633
2634         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2635         if (!model)
2636                 return;
2637
2638         expand_root_items (self);
2639         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2640
2641         if (!gtk_tree_model_get_iter_first (model, &iter)) {
2642                 g_warning ("%s: model is empty", __FUNCTION__);
2643                 return;
2644         }
2645
2646         if (find_inbox_iter (model, &iter, &inbox_iter))
2647                 path = gtk_tree_model_get_path (model, &inbox_iter);
2648         else
2649                 path = gtk_tree_path_new_first ();
2650
2651         /* Select the row and free */
2652         gtk_tree_view_set_cursor (GTK_TREE_VIEW (self), path, NULL, FALSE);
2653         gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (self), path, NULL, FALSE, 0.0, 0.0);
2654         gtk_tree_path_free (path);
2655
2656         /* set focus */
2657         gtk_widget_grab_focus (GTK_WIDGET(self));
2658 }
2659
2660
2661 /* recursive */
2662 static gboolean
2663 find_folder_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *folder_iter,
2664                   TnyFolder* folder)
2665 {
2666         do {
2667                 GtkTreeIter child;
2668                 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2669                 TnyFolder* a_folder;
2670                 gchar *name = NULL;
2671
2672                 gtk_tree_model_get (model, iter,
2673                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &a_folder,
2674                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name,
2675                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
2676                                     -1);
2677                 g_free (name);
2678
2679                 if (folder == a_folder) {
2680                         g_object_unref (a_folder);
2681                         *folder_iter = *iter;
2682                         return TRUE;
2683                 }
2684                 g_object_unref (a_folder);
2685
2686                 if (gtk_tree_model_iter_children (model, &child, iter)) {
2687                         if (find_folder_iter (model, &child, folder_iter, folder))
2688                                 return TRUE;
2689                 }
2690
2691         } while (gtk_tree_model_iter_next (model, iter));
2692
2693         return FALSE;
2694 }
2695
2696
2697 static void
2698 on_row_inserted_maybe_select_folder (GtkTreeModel *tree_model,
2699                                      GtkTreePath *path,
2700                                      GtkTreeIter *iter,
2701                                      ModestFolderView *self)
2702 {
2703         ModestFolderViewPrivate *priv = NULL;
2704         GtkTreeSelection *sel;
2705         TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2706         GObject *instance = NULL;
2707
2708         if (!MODEST_IS_FOLDER_VIEW(self))
2709                 return;
2710
2711         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2712
2713         priv->reexpand = TRUE;
2714
2715         gtk_tree_model_get (tree_model, iter,
2716                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
2717                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
2718                             -1);
2719         if (type == TNY_FOLDER_TYPE_INBOX && priv->folder_to_select == NULL) {
2720                 priv->folder_to_select = g_object_ref (instance);
2721         }
2722         g_object_unref (instance);
2723
2724         if (priv->folder_to_select) {
2725
2726                 if (!modest_folder_view_select_folder (self, priv->folder_to_select,
2727                                                        FALSE)) {
2728                         GtkTreePath *path;
2729                         path = gtk_tree_model_get_path (tree_model, iter);
2730                         gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
2731
2732                         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2733
2734                         gtk_tree_selection_select_iter (sel, iter);
2735                         gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
2736
2737                         gtk_tree_path_free (path);
2738                 }
2739
2740                 /* Disable next */
2741                 modest_folder_view_disable_next_folder_selection (self);
2742
2743                 /* Refilter the model */
2744                 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (tree_model));
2745         }
2746 }
2747
2748
2749 void
2750 modest_folder_view_disable_next_folder_selection (ModestFolderView *self)
2751 {
2752         ModestFolderViewPrivate *priv;
2753
2754         g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2755
2756         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2757
2758         if (priv->folder_to_select)
2759                 g_object_unref(priv->folder_to_select);
2760
2761         priv->folder_to_select = NULL;
2762 }
2763
2764 gboolean
2765 modest_folder_view_select_folder (ModestFolderView *self, TnyFolder *folder,
2766                                   gboolean after_change)
2767 {
2768         GtkTreeModel *model;
2769         GtkTreeIter iter, folder_iter;
2770         GtkTreeSelection *sel;
2771         ModestFolderViewPrivate *priv = NULL;
2772
2773         g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW (self), FALSE);
2774         g_return_val_if_fail (folder && TNY_IS_FOLDER (folder), FALSE);
2775
2776         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2777
2778         if (after_change) {
2779                 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2780                 gtk_tree_selection_unselect_all (sel);
2781
2782                 if (priv->folder_to_select)
2783                         g_object_unref(priv->folder_to_select);
2784                 priv->folder_to_select = TNY_FOLDER(g_object_ref(folder));
2785                 return TRUE;
2786         }
2787
2788         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2789         if (!model)
2790                 return FALSE;
2791
2792
2793         /* Refilter the model, before selecting the folder */
2794         gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2795
2796         if (!gtk_tree_model_get_iter_first (model, &iter)) {
2797                 g_warning ("%s: model is empty", __FUNCTION__);
2798                 return FALSE;
2799         }
2800
2801         if (find_folder_iter (model, &iter, &folder_iter, folder)) {
2802                 GtkTreePath *path;
2803
2804                 path = gtk_tree_model_get_path (model, &folder_iter);
2805                 gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
2806
2807                 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2808                 gtk_tree_selection_select_iter (sel, &folder_iter);
2809                 gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
2810
2811                 gtk_tree_path_free (path);
2812                 return TRUE;
2813         }
2814         return FALSE;
2815 }
2816
2817
2818 void
2819 modest_folder_view_copy_selection (ModestFolderView *self)
2820 {
2821         g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2822
2823         /* Copy selection */
2824         _clipboard_set_selected_data (self, FALSE);
2825 }
2826
2827 void
2828 modest_folder_view_cut_selection (ModestFolderView *folder_view)
2829 {
2830         ModestFolderViewPrivate *priv = NULL;
2831         GtkTreeModel *model = NULL;
2832         const gchar **hidding = NULL;
2833         guint i, n_selected;
2834
2835         g_return_if_fail (folder_view && MODEST_IS_FOLDER_VIEW (folder_view));
2836         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
2837
2838         /* Copy selection */
2839         if (!_clipboard_set_selected_data (folder_view, TRUE))
2840                 return;
2841
2842         /* Get hidding ids */
2843         hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected);
2844
2845         /* Clear hidding array created by previous cut operation */
2846         _clear_hidding_filter (MODEST_FOLDER_VIEW (folder_view));
2847
2848         /* Copy hidding array */
2849         priv->n_selected = n_selected;
2850         priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
2851         for (i=0; i < n_selected; i++)
2852                 priv->hidding_ids[i] = g_strdup(hidding[i]);
2853
2854         /* Hide cut folders */
2855         model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
2856         gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2857 }
2858
2859 void
2860 modest_folder_view_copy_model (ModestFolderView *folder_view_src,
2861                                ModestFolderView *folder_view_dst)
2862 {
2863         GtkTreeModel *filter_model = NULL;
2864         GtkTreeModel *model = NULL;
2865         GtkTreeModel *new_filter_model = NULL;
2866
2867         g_return_if_fail (folder_view_src && MODEST_IS_FOLDER_VIEW (folder_view_src));
2868         g_return_if_fail (folder_view_dst && MODEST_IS_FOLDER_VIEW (folder_view_dst));
2869
2870         /* Get src model*/
2871         filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view_src));
2872         model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER(filter_model));
2873
2874         /* Build new filter model */
2875         new_filter_model = gtk_tree_model_filter_new (model, NULL);
2876         gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (new_filter_model),
2877                                                 filter_row,
2878                                                 folder_view_dst,
2879                                                 NULL);
2880         /* Set copied model */
2881         gtk_tree_view_set_model (GTK_TREE_VIEW (folder_view_dst), new_filter_model);
2882         g_signal_connect (G_OBJECT(new_filter_model), "row-inserted",
2883                           (GCallback) on_row_inserted_maybe_select_folder, folder_view_dst);
2884
2885         /* Free */
2886         g_object_unref (new_filter_model);
2887 }
2888
2889 void
2890 modest_folder_view_show_non_move_folders (ModestFolderView *folder_view,
2891                                           gboolean show)
2892 {
2893         GtkTreeModel *model = NULL;
2894         ModestFolderViewPrivate* priv;
2895
2896         g_return_if_fail (folder_view && MODEST_IS_FOLDER_VIEW (folder_view));
2897
2898         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
2899         priv->show_non_move = show;
2900 /*      modest_folder_view_update_model(folder_view, */
2901 /*                                      TNY_ACCOUNT_STORE(modest_runtime_get_account_store())); */
2902
2903         /* Hide special folders */
2904         model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
2905         if (GTK_IS_TREE_MODEL_FILTER (model)) {
2906                 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2907         }
2908 }
2909
2910 /* Returns FALSE if it did not selected anything */
2911 static gboolean
2912 _clipboard_set_selected_data (ModestFolderView *folder_view,
2913                               gboolean delete)
2914 {
2915         ModestFolderViewPrivate *priv = NULL;
2916         TnyFolderStore *folder = NULL;
2917         gboolean retval = FALSE;
2918
2919         g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (folder_view), FALSE);
2920         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
2921
2922         /* Set selected data on clipboard   */
2923         g_return_val_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard), FALSE);
2924         folder = modest_folder_view_get_selected (folder_view);
2925
2926         /* Do not allow to select an account */
2927         if (TNY_IS_FOLDER (folder)) {
2928                 modest_email_clipboard_set_data (priv->clipboard, TNY_FOLDER(folder), NULL, delete);
2929                 retval = TRUE;
2930         }
2931
2932         /* Free */
2933         g_object_unref (folder);
2934
2935         return retval;
2936 }
2937
2938 static void
2939 _clear_hidding_filter (ModestFolderView *folder_view)
2940 {
2941         ModestFolderViewPrivate *priv;
2942         guint i;
2943
2944         g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view));
2945         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
2946
2947         if (priv->hidding_ids != NULL) {
2948                 for (i=0; i < priv->n_selected; i++)
2949                         g_free (priv->hidding_ids[i]);
2950                 g_free(priv->hidding_ids);
2951         }
2952 }
2953
2954
2955 static void
2956 on_display_name_changed (ModestAccountMgr *mgr,
2957                          const gchar *account,
2958                          gpointer user_data)
2959 {
2960         ModestFolderView *self;
2961
2962         self = MODEST_FOLDER_VIEW (user_data);
2963
2964         /* Force a redraw */
2965 #if GTK_CHECK_VERSION(2, 8, 0)
2966         GtkTreeViewColumn * tree_column;
2967
2968         tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self),
2969                                                 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN);
2970         gtk_tree_view_column_queue_resize (tree_column);
2971 #else
2972         gtk_widget_queue_draw (GTK_WIDGET (self));
2973 #endif
2974 }
2975
2976 void 
2977 modest_folder_view_set_cell_style (ModestFolderView *self,
2978                                    ModestFolderViewCellStyle cell_style)
2979 {
2980         ModestFolderViewPrivate *priv = NULL;
2981
2982         g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
2983         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2984
2985         priv->cell_style = cell_style;
2986         
2987         gtk_widget_queue_draw (GTK_WIDGET (self));
2988 }