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