* Fixed some reference leaks
[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         
993         /* If this is a move to dialog, hide Sent, Outbox and Drafts
994         folder as no message can be move there according to UI specs */
995         if (!priv->show_non_move)
996         {
997                 switch (type)
998                 {
999                         case TNY_FOLDER_TYPE_OUTBOX:
1000                         case TNY_FOLDER_TYPE_SENT:
1001                         case TNY_FOLDER_TYPE_DRAFTS:
1002                                 retval = FALSE;
1003                                 break;
1004                         case TNY_FOLDER_TYPE_UNKNOWN:
1005                         case TNY_FOLDER_TYPE_NORMAL:
1006                                 type = modest_tny_folder_guess_folder_type(TNY_FOLDER(instance));
1007                                 if (type == TNY_FOLDER_TYPE_OUTBOX || type == TNY_FOLDER_TYPE_SENT
1008                                                 || type == TNY_FOLDER_TYPE_DRAFTS)
1009                                 {
1010                                         retval = FALSE;
1011                                 }
1012                                 break;
1013                         default:
1014                                 break;  
1015                 }       
1016         }
1017         
1018         /* Free */
1019         g_object_unref (instance);
1020
1021         return retval;
1022 }
1023
1024
1025 gboolean
1026 modest_folder_view_update_model (ModestFolderView *self,
1027                                  TnyAccountStore *account_store)
1028 {
1029         ModestFolderViewPrivate *priv;
1030         GtkTreeModel *model /* , *old_model */;
1031         /* TnyAccount *local_account; */
1032         TnyList *model_as_list;
1033
1034         g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (self), FALSE);
1035         g_return_val_if_fail (account_store, FALSE);
1036
1037         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1038         
1039         /* Notify that there is no folder selected */
1040         g_signal_emit (G_OBJECT(self), 
1041                        signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
1042                        NULL, FALSE);
1043         if (priv->cur_folder_store) {
1044                 g_object_unref (priv->cur_folder_store);
1045                 priv->cur_folder_store = NULL;
1046         }
1047
1048         /* FIXME: the local accounts are not shown when the query
1049            selects only the subscribed folders. */
1050 /*      model        = tny_gtk_folder_store_tree_model_new (TRUE, priv->query); */
1051         model        = tny_gtk_folder_store_tree_model_new (TRUE, NULL);
1052         
1053         /* Deal with the model via its TnyList Interface,
1054          * filling the TnyList via a get_accounts() call: */
1055         model_as_list = TNY_LIST(model);
1056
1057         /* Get the accounts: */
1058         tny_account_store_get_accounts (TNY_ACCOUNT_STORE(account_store),
1059                                         model_as_list,
1060                                         TNY_ACCOUNT_STORE_STORE_ACCOUNTS);
1061         g_object_unref (model_as_list);
1062         model_as_list = NULL;   
1063                                                      
1064         GtkTreeModel *filter_model = NULL, *sortable = NULL;
1065
1066         sortable = gtk_tree_model_sort_new_with_model (model);
1067         gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
1068                                               TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, 
1069                                               GTK_SORT_ASCENDING);
1070         gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
1071                                          TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
1072                                  cmp_rows, NULL, NULL);
1073
1074         /* Create filter model */
1075         filter_model = gtk_tree_model_filter_new (sortable, NULL);
1076         gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
1077                                                 filter_row,
1078                                                 self,
1079                                                 NULL);
1080 /*      if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE) { */
1081 /*              filter_model = gtk_tree_model_filter_new (sortable, NULL); */
1082 /*              gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model), */
1083 /*                                                      filter_row, */
1084 /*                                                      self, */
1085 /*                                                      NULL); */
1086 /*      } */
1087
1088         /* Set new model */
1089         gtk_tree_view_set_model (GTK_TREE_VIEW(self), filter_model);
1090         g_signal_connect (G_OBJECT(filter_model), "row-changed",
1091                           (GCallback)on_row_changed_maybe_select_folder, self);
1092         g_signal_connect (G_OBJECT(filter_model), "row-inserted",
1093                           (GCallback)on_row_changed_maybe_select_folder, self);
1094
1095         
1096
1097 /*      gtk_tree_view_set_model (GTK_TREE_VIEW(self),  */
1098 /*                               (filter_model) ? filter_model : sortable); */
1099
1100         g_object_unref (model);
1101         g_object_unref (filter_model);
1102 /*      if (filter_model) */
1103 /*              g_object_unref (filter_model); */
1104                         
1105         g_object_unref (sortable);
1106
1107         /* Force a reselection of the INBOX next time the widget is shown */
1108         priv->reselect = TRUE;
1109                         
1110         return TRUE;
1111 }
1112
1113
1114 static void
1115 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
1116 {
1117         GtkTreeModel            *model;
1118         TnyFolderStore          *folder = NULL;
1119         GtkTreeIter             iter;
1120         ModestFolderView        *tree_view;
1121         ModestFolderViewPrivate *priv;
1122
1123         g_return_if_fail (sel);
1124         g_return_if_fail (user_data);
1125         
1126         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
1127
1128         if(!gtk_tree_selection_get_selected (sel, &model, &iter))
1129                 return;
1130
1131         /* Notify the display name observers */
1132         g_signal_emit (G_OBJECT(user_data),
1133                        signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
1134                        NULL);
1135
1136         tree_view = MODEST_FOLDER_VIEW (user_data);
1137         gtk_tree_model_get (model, &iter,
1138                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
1139                             -1);
1140
1141         /* If the folder is the same do not notify */
1142         if (priv->cur_folder_store == folder && folder) {
1143                 g_object_unref (folder);
1144                 return;
1145         }
1146         
1147         /* Current folder was unselected */
1148         if (priv->cur_folder_store) {
1149                 g_signal_emit (G_OBJECT(tree_view), signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
1150                        priv->cur_folder_store, FALSE);
1151
1152                 if (TNY_IS_FOLDER(priv->cur_folder_store))
1153                         tny_folder_sync_async (TNY_FOLDER(priv->cur_folder_store),
1154                                                FALSE, NULL, NULL, NULL);
1155                 /* FALSE --> don't expunge the messages */
1156
1157                 g_object_unref (priv->cur_folder_store);
1158                 priv->cur_folder_store = NULL;
1159         }
1160
1161         /* New current references */
1162         priv->cur_folder_store = folder;
1163
1164         /* New folder has been selected */
1165         g_signal_emit (G_OBJECT(tree_view),
1166                        signals[FOLDER_SELECTION_CHANGED_SIGNAL],
1167                        0, priv->cur_folder_store, TRUE);
1168 }
1169
1170 TnyFolderStore *
1171 modest_folder_view_get_selected (ModestFolderView *self)
1172 {
1173         ModestFolderViewPrivate *priv;
1174
1175         g_return_val_if_fail (self, NULL);
1176         
1177         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1178         if (priv->cur_folder_store)
1179                 g_object_ref (priv->cur_folder_store);
1180
1181         return priv->cur_folder_store;
1182 }
1183
1184 static gint
1185 get_cmp_rows_type_pos (GObject *folder)
1186 {
1187         /* Remote accounts -> Local account -> MMC account .*/
1188         /* 0, 1, 2 */
1189         
1190         if (TNY_IS_ACCOUNT (folder) && 
1191                 modest_tny_account_is_virtual_local_folders (
1192                         TNY_ACCOUNT (folder))) {
1193                 return 1;
1194         } else if (TNY_IS_ACCOUNT (folder)) {
1195                 TnyAccount *account = TNY_ACCOUNT (folder);
1196                 const gchar *account_id = tny_account_get_id (account);
1197                 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
1198                         return 2;
1199                 else
1200                         return 0;
1201         }
1202         else {
1203                 printf ("DEBUG: %s: unexpected type.\n", __FUNCTION__);
1204                 return -1; /* Should never happen */
1205         }
1206 }
1207
1208 static gint
1209 get_cmp_subfolder_type_pos (TnyFolderType t)
1210 {
1211         /* Inbox, Outbox, Drafts, Sent, User */
1212         /* 0, 1, 2, 3, 4 */
1213
1214         switch (t) {
1215         case TNY_FOLDER_TYPE_INBOX:
1216                 return 0;
1217                 break;
1218         case TNY_FOLDER_TYPE_OUTBOX:
1219                 return 1;
1220                 break;
1221         case TNY_FOLDER_TYPE_DRAFTS:
1222                 return 2;
1223                 break;
1224         case TNY_FOLDER_TYPE_SENT:
1225                 return 3;
1226                 break;
1227         default:
1228                 return 4;
1229         }
1230 }
1231
1232 /*
1233  * This function orders the mail accounts according to these rules:
1234  * 1st - remote accounts
1235  * 2nd - local account
1236  * 3rd - MMC account
1237  */
1238 static gint
1239 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1240           gpointer user_data)
1241 {
1242         gint cmp = 0;
1243         gchar *name1 = NULL;
1244         gchar *name2 = NULL;
1245         TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1246         TnyFolderType type2 = TNY_FOLDER_TYPE_UNKNOWN;
1247         GObject *folder1 = NULL;
1248         GObject *folder2 = NULL;
1249
1250         gtk_tree_model_get (tree_model, iter1,
1251                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name1,
1252                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
1253                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder1,
1254                             -1);
1255         gtk_tree_model_get (tree_model, iter2,
1256                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name2,
1257                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type2,
1258                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder2,
1259                             -1);
1260
1261         /* Return if we get no folder. This could happen when folder
1262            operations are happening. The model is updated after the
1263            folder copy/move actually occurs, so there could be
1264            situations where the model to be drawn is not correct */
1265         if (!folder1 || !folder2)
1266                 goto finish;
1267
1268         if (type == TNY_FOLDER_TYPE_ROOT) {
1269                 /* Compare the types, so that 
1270                  * Remote accounts -> Local account -> MMC account .*/
1271                 const gint pos1 = get_cmp_rows_type_pos (folder1);
1272                 const gint pos2 = get_cmp_rows_type_pos (folder2);
1273                 /* printf ("DEBUG: %s:\n  type1=%s, pos1=%d\n  type2=%s, pos2=%d\n", 
1274                         __FUNCTION__, G_OBJECT_TYPE_NAME(folder1), pos1, G_OBJECT_TYPE_NAME(folder2), pos2); */
1275                 if (pos1 <  pos2)
1276                         cmp = -1;
1277                 else if (pos1 > pos2)
1278                         cmp = 1;
1279                 else {
1280                         /* Compare items of the same type: */
1281                         
1282                         TnyAccount *account1 = NULL;
1283                         if (TNY_IS_ACCOUNT (folder1))
1284                                 account1 = TNY_ACCOUNT (folder1);
1285                                 
1286                         TnyAccount *account2 = NULL;
1287                         if (TNY_IS_ACCOUNT (folder2))
1288                                 account2 = TNY_ACCOUNT (folder2);
1289                                 
1290                         const gchar *account_id = account1 ? tny_account_get_id (account1) : NULL;
1291                         const gchar *account_id2 = account2 ? tny_account_get_id (account2) : NULL;
1292         
1293                         if (!account_id && !account_id2) {
1294                                 cmp = 0;
1295                         } else if (!account_id) {
1296                                 cmp = -1;
1297                         } else if (!account_id2) {
1298                                 cmp = +1;
1299                         } else if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
1300                                 cmp = +1;
1301                         } else {
1302                                 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
1303                         }
1304                 }
1305         } else {
1306                 gint cmp1 = 0, cmp2 = 0;
1307                 /* get the parent to know if it's a local folder */
1308
1309                 GtkTreeIter parent;
1310                 gboolean has_parent;
1311                 has_parent = gtk_tree_model_iter_parent (tree_model, &parent, iter1);
1312                 if (has_parent) {
1313                         GObject *parent_folder;
1314                         TnyFolderType parent_type = TNY_FOLDER_TYPE_UNKNOWN;
1315                         gtk_tree_model_get (tree_model, &parent, 
1316                                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &parent_type,
1317                                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &parent_folder,
1318                                             -1);
1319                         if ((parent_type == TNY_FOLDER_TYPE_ROOT) &&
1320                             TNY_IS_ACCOUNT (parent_folder) &&
1321                             modest_tny_account_is_virtual_local_folders (TNY_ACCOUNT (parent_folder))) {
1322                                 cmp1 = get_cmp_subfolder_type_pos (modest_tny_folder_get_local_folder_type (TNY_FOLDER (folder1)));
1323                                 cmp2 = get_cmp_subfolder_type_pos (modest_tny_folder_get_local_folder_type (TNY_FOLDER (folder2)));
1324                         }
1325                         g_object_unref (parent_folder);
1326                 }
1327
1328                 /* if they are not local folders */
1329                 if (cmp1 == cmp2) {
1330                         cmp1 = get_cmp_subfolder_type_pos (tny_folder_get_folder_type (TNY_FOLDER (folder1)));
1331                         cmp2 = get_cmp_subfolder_type_pos (tny_folder_get_folder_type (TNY_FOLDER (folder2)));
1332                 }
1333
1334                 if (cmp1 == cmp2)
1335                         cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
1336                 else 
1337                         cmp = (cmp1 - cmp2);
1338         }
1339
1340 finish: 
1341         if (folder1)
1342                 g_object_unref(G_OBJECT(folder1));
1343         if (folder2)
1344                 g_object_unref(G_OBJECT(folder2));
1345
1346         g_free (name1);
1347         g_free (name2);
1348
1349         return cmp;     
1350 }
1351
1352 /*****************************************************************************/
1353 /*                        DRAG and DROP stuff                                */
1354 /*****************************************************************************/
1355
1356 /*
1357  * This function fills the #GtkSelectionData with the row and the
1358  * model that has been dragged. It's called when this widget is a
1359  * source for dnd after the event drop happened
1360  */
1361 static void
1362 on_drag_data_get (GtkWidget *widget, 
1363                   GdkDragContext *context, 
1364                   GtkSelectionData *selection_data, 
1365                   guint info, 
1366                   guint time, 
1367                   gpointer data)
1368 {
1369         GtkTreeSelection *selection;
1370         GtkTreeModel *model;
1371         GtkTreeIter iter;
1372         GtkTreePath *source_row;
1373
1374         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1375         gtk_tree_selection_get_selected (selection, &model, &iter);
1376         source_row = gtk_tree_model_get_path (model, &iter);
1377
1378         gtk_tree_set_row_drag_data (selection_data,
1379                                     model,
1380                                     source_row);
1381
1382         gtk_tree_path_free (source_row);
1383 }
1384
1385 typedef struct _DndHelper {
1386         gboolean delete_source;
1387         GtkTreePath *source_row;
1388         GdkDragContext *context;
1389         guint time;
1390 } DndHelper;
1391
1392
1393 /*
1394  * This function is the callback of the
1395  * modest_mail_operation_xfer_msgs () and
1396  * modest_mail_operation_xfer_folder() calls. We check here if the
1397  * message/folder was correctly asynchronously transferred. The reason
1398  * to use the same callback is that the code is the same, it only has
1399  * to check that the operation went fine and then finalize the drag
1400  * and drop action
1401  */
1402 static void
1403 on_progress_changed (ModestMailOperation *mail_op, 
1404                      ModestMailOperationState *state,
1405                      gpointer user_data)
1406 {
1407         gboolean success;
1408         DndHelper *helper;
1409
1410         helper = (DndHelper *) user_data;
1411
1412         if (!state->finished)
1413                 return;
1414
1415         if (state->status == MODEST_MAIL_OPERATION_STATUS_SUCCESS) {
1416                 success = TRUE;
1417         } else {
1418                 success = FALSE;
1419         }
1420
1421         /* Notify the drag source. Never call delete, the monitor will
1422            do the job if needed */
1423         gtk_drag_finish (helper->context, success, FALSE, helper->time);
1424
1425         /* Free the helper */
1426         gtk_tree_path_free (helper->source_row);        
1427         g_slice_free (DndHelper, helper);
1428 }
1429
1430 /*
1431  * This function is used by drag_data_received_cb to manage drag and
1432  * drop of a header, i.e, and drag from the header view to the folder
1433  * view.
1434  */
1435 static void
1436 drag_and_drop_from_header_view (GtkTreeModel *source_model,
1437                                 GtkTreeModel *dest_model,
1438                                 GtkTreePath  *dest_row,
1439                                 DndHelper    *helper)
1440 {
1441         TnyList *headers = NULL;
1442         TnyHeader *header = NULL;
1443         TnyFolder *folder = NULL;
1444         ModestMailOperation *mail_op = NULL;
1445         GtkTreeIter source_iter, dest_iter;
1446
1447         g_return_if_fail (GTK_IS_TREE_MODEL(source_model));
1448         g_return_if_fail (GTK_IS_TREE_MODEL(dest_model));
1449         g_return_if_fail (dest_row);
1450         g_return_if_fail (helper);
1451         
1452         /* Get header */
1453         gtk_tree_model_get_iter (source_model, &source_iter, helper->source_row);
1454         gtk_tree_model_get (source_model, &source_iter, 
1455                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, 
1456                             &header, -1);
1457         if (!TNY_IS_HEADER(header)) {
1458                 g_warning ("BUG: %s could not get a valid header", __FUNCTION__);
1459                 goto cleanup;
1460         }
1461         
1462         /* Get Folder */
1463         gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
1464         gtk_tree_model_get (dest_model, &dest_iter, 
1465                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, 
1466                             &folder, -1);
1467
1468         if (!TNY_IS_FOLDER(folder)) {
1469                 g_warning ("BUG: %s could not get a valid folder", __FUNCTION__);
1470                 goto cleanup;
1471         }
1472
1473         /* Transfer message */
1474         mail_op = modest_mail_operation_new_with_error_handling (MODEST_MAIL_OPERATION_TYPE_RECEIVE, 
1475                                                                  NULL,
1476                                                                  modest_ui_actions_move_folder_error_handler,
1477                                                                  NULL);
1478         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1479                                          mail_op);
1480         g_signal_connect (G_OBJECT (mail_op), "progress-changed",
1481                           G_CALLBACK (on_progress_changed), helper);
1482
1483         headers = tny_simple_list_new ();
1484         tny_list_append (headers, G_OBJECT (header));
1485         modest_mail_operation_xfer_msgs (mail_op, 
1486                                          headers, 
1487                                          folder, 
1488                                          helper->delete_source, 
1489                                          NULL, NULL);
1490         
1491         /* Frees */
1492 cleanup:
1493         if (G_IS_OBJECT(mail_op))
1494                 g_object_unref (G_OBJECT (mail_op));
1495         if (G_IS_OBJECT(header))
1496                 g_object_unref (G_OBJECT (header));
1497         if (G_IS_OBJECT(folder))
1498                 g_object_unref (G_OBJECT (folder));
1499         if (G_IS_OBJECT(headers))
1500                 g_object_unref (headers);
1501 }
1502
1503 /*
1504  * This function is used by drag_data_received_cb to manage drag and
1505  * drop of a folder, i.e, and drag from the folder view to the same
1506  * folder view.
1507  */
1508 static void
1509 drag_and_drop_from_folder_view (GtkTreeModel     *source_model,
1510                                 GtkTreeModel     *dest_model,
1511                                 GtkTreePath      *dest_row,
1512                                 GtkSelectionData *selection_data,
1513                                 DndHelper        *helper)
1514 {
1515         ModestMailOperation *mail_op = NULL;
1516         GtkTreeIter parent_iter, iter;
1517         TnyFolderStore *parent_folder = NULL;
1518         TnyFolder *folder = NULL;
1519
1520         /* Check if the drag is possible */
1521 /*      if (!gtk_tree_path_compare (helper->source_row, dest_row) || */
1522 /*          !gtk_tree_drag_dest_row_drop_possible (GTK_TREE_DRAG_DEST (dest_model), */
1523 /*                                                 dest_row, */
1524 /*                                                 selection_data)) { */
1525         if (!gtk_tree_path_compare (helper->source_row, dest_row)) {
1526
1527                 gtk_drag_finish (helper->context, FALSE, FALSE, helper->time);
1528                 gtk_tree_path_free (helper->source_row);        
1529                 g_slice_free (DndHelper, helper);
1530                 return;
1531         }
1532
1533         /* Get data */
1534         gtk_tree_model_get_iter (source_model, &parent_iter, dest_row);
1535         gtk_tree_model_get_iter (source_model, &iter, helper->source_row);
1536         gtk_tree_model_get (source_model, &parent_iter, 
1537                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, 
1538                             &parent_folder, -1);
1539         gtk_tree_model_get (source_model, &iter,
1540                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
1541                             &folder, -1);
1542
1543         /* Offer the connection dialog if necessary, for the destination parent folder and source folder: */
1544         if (modest_platform_connect_and_wait_if_network_folderstore (NULL, parent_folder) && 
1545                 modest_platform_connect_and_wait_if_network_folderstore (NULL, TNY_FOLDER_STORE (folder))) {
1546                 /* Do the mail operation */
1547                 mail_op = modest_mail_operation_new_with_error_handling (MODEST_MAIL_OPERATION_TYPE_RECEIVE, 
1548                                                                  NULL,
1549                                                                  modest_ui_actions_move_folder_error_handler,
1550                                                                  NULL);
1551                 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (), 
1552                                          mail_op);
1553                 g_signal_connect (G_OBJECT (mail_op), "progress-changed",
1554                                   G_CALLBACK (on_progress_changed), helper);
1555
1556                 modest_mail_operation_xfer_folder (mail_op, 
1557                                            folder, 
1558                                            parent_folder,
1559                                            helper->delete_source,
1560                                            NULL,
1561                                            NULL);
1562         }
1563         
1564         /* Frees */
1565         g_object_unref (G_OBJECT (parent_folder));
1566         g_object_unref (G_OBJECT (folder));
1567         g_object_unref (G_OBJECT (mail_op));
1568 }
1569
1570 /*
1571  * This function receives the data set by the "drag-data-get" signal
1572  * handler. This information comes within the #GtkSelectionData. This
1573  * function will manage both the drags of folders of the treeview and
1574  * drags of headers of the header view widget.
1575  */
1576 static void 
1577 on_drag_data_received (GtkWidget *widget, 
1578                        GdkDragContext *context, 
1579                        gint x, 
1580                        gint y, 
1581                        GtkSelectionData *selection_data, 
1582                        guint target_type, 
1583                        guint time, 
1584                        gpointer data)
1585 {
1586         GtkWidget *source_widget;
1587         GtkTreeModel *dest_model, *source_model;
1588         GtkTreePath *source_row, *dest_row;
1589         GtkTreeViewDropPosition pos;
1590         gboolean success = FALSE, delete_source = FALSE;
1591         DndHelper *helper = NULL; 
1592
1593         /* Do not allow further process */
1594         g_signal_stop_emission_by_name (widget, "drag-data-received");
1595         source_widget = gtk_drag_get_source_widget (context);
1596
1597         /* Get the action */
1598         if (context->action == GDK_ACTION_MOVE) {
1599                 delete_source = TRUE;
1600
1601                 /* Notify that there is no folder selected. We need to
1602                    do this in order to update the headers view (and
1603                    its monitors, because when moving, the old folder
1604                    won't longer exist. We can not wait for the end of
1605                    the operation, because the operation won't start if
1606                    the folder is in use */
1607                 if (source_widget == widget) {
1608                         ModestFolderViewPrivate *priv;
1609
1610                         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
1611                         if (priv->cur_folder_store) {
1612                                 g_object_unref (priv->cur_folder_store);
1613                                 priv->cur_folder_store = NULL;
1614                         }
1615
1616                         g_signal_emit (G_OBJECT (widget), 
1617                                        signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0, NULL, FALSE);
1618                 }
1619         }
1620
1621         /* Check if the get_data failed */
1622         if (selection_data == NULL || selection_data->length < 0)
1623                 gtk_drag_finish (context, success, FALSE, time);
1624
1625         /* Get the models */
1626         gtk_tree_get_row_drag_data (selection_data,
1627                                     &source_model,
1628                                     &source_row);
1629
1630         /* Select the destination model */
1631         if (source_widget == widget) {
1632                 dest_model = source_model;
1633         } else {
1634                 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
1635         }
1636
1637         /* Get the path to the destination row. Can not call
1638            gtk_tree_view_get_drag_dest_row() because the source row
1639            is not selected anymore */
1640         gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget), x, y,
1641                                            &dest_row, &pos);
1642
1643         /* Only allow drops IN other rows */
1644         if (!dest_row || pos == GTK_TREE_VIEW_DROP_BEFORE || pos == GTK_TREE_VIEW_DROP_AFTER)
1645                 gtk_drag_finish (context, success, FALSE, time);
1646
1647         /* Create the helper */
1648         helper = g_slice_new0 (DndHelper);
1649         helper->delete_source = delete_source;
1650         helper->source_row = gtk_tree_path_copy (source_row);
1651         helper->context = context;
1652         helper->time = time;
1653
1654         /* Drags from the header view */
1655         if (source_widget != widget) {
1656
1657                 drag_and_drop_from_header_view (source_model,
1658                                                 dest_model,
1659                                                 dest_row,
1660                                                 helper);
1661         } else {
1662
1663
1664                 drag_and_drop_from_folder_view (source_model,
1665                                                 dest_model,
1666                                                 dest_row,
1667                                                 selection_data, 
1668                                                 helper);
1669         }
1670
1671         /* Frees */
1672         gtk_tree_path_free (source_row);
1673         gtk_tree_path_free (dest_row);
1674 }
1675
1676 /*
1677  * We define a "drag-drop" signal handler because we do not want to
1678  * use the default one, because the default one always calls
1679  * gtk_drag_finish and we prefer to do it in the "drag-data-received"
1680  * signal handler, because there we have all the information available
1681  * to know if the dnd was a success or not.
1682  */
1683 static gboolean
1684 drag_drop_cb (GtkWidget      *widget,
1685               GdkDragContext *context,
1686               gint            x,
1687               gint            y,
1688               guint           time,
1689               gpointer        user_data) 
1690 {
1691         gpointer target;
1692
1693         if (!context->targets)
1694                 return FALSE;
1695
1696         /* Check if we're dragging a folder row */
1697         target = gtk_drag_dest_find_target (widget, context, NULL);
1698
1699         /* Request the data from the source. */
1700         gtk_drag_get_data(widget, context, target, time);
1701
1702     return TRUE;
1703 }
1704
1705 /*
1706  * This function expands a node of a tree view if it's not expanded
1707  * yet. Not sure why it needs the threads stuff, but gtk+`example code
1708  * does that, so that's why they're here.
1709  */
1710 static gint
1711 expand_row_timeout (gpointer data)
1712 {
1713         GtkTreeView *tree_view = data;
1714         GtkTreePath *dest_path = NULL;
1715         GtkTreeViewDropPosition pos;
1716         gboolean result = FALSE;
1717         
1718         GDK_THREADS_ENTER ();
1719         
1720         gtk_tree_view_get_drag_dest_row (tree_view,
1721                                          &dest_path,
1722                                          &pos);
1723         
1724         if (dest_path &&
1725             (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER ||
1726              pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)) {
1727                 gtk_tree_view_expand_row (tree_view, dest_path, FALSE);
1728                 gtk_tree_path_free (dest_path);
1729         }
1730         else {
1731                 if (dest_path)
1732                         gtk_tree_path_free (dest_path);
1733                 
1734                 result = TRUE;
1735         }
1736         
1737         GDK_THREADS_LEAVE ();
1738
1739         return result;
1740 }
1741
1742 /*
1743  * This function is called whenever the pointer is moved over a widget
1744  * while dragging some data. It installs a timeout that will expand a
1745  * node of the treeview if not expanded yet. This function also calls
1746  * gdk_drag_status in order to set the suggested action that will be
1747  * used by the "drag-data-received" signal handler to know if we
1748  * should do a move or just a copy of the data.
1749  */
1750 static gboolean
1751 on_drag_motion (GtkWidget      *widget,
1752                 GdkDragContext *context,
1753                 gint            x,
1754                 gint            y,
1755                 guint           time,
1756                 gpointer        user_data)  
1757 {
1758         GtkTreeViewDropPosition pos;
1759         GtkTreePath *dest_row;
1760         ModestFolderViewPrivate *priv;
1761         GdkDragAction suggested_action;
1762         gboolean valid_location = FALSE;
1763
1764         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
1765
1766         if (priv->timer_expander != 0) {
1767                 g_source_remove (priv->timer_expander);
1768                 priv->timer_expander = 0;
1769         }
1770
1771         gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
1772                                            x, y,
1773                                            &dest_row,
1774                                            &pos);
1775
1776         /* Do not allow drops between folders */
1777         if (!dest_row ||
1778             pos == GTK_TREE_VIEW_DROP_BEFORE ||
1779             pos == GTK_TREE_VIEW_DROP_AFTER) {
1780                 gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW (widget), NULL, 0);
1781                 gdk_drag_status(context, 0, time);
1782                 valid_location = FALSE;
1783                 goto out;
1784         } else {
1785                 valid_location = TRUE;
1786         }
1787
1788         /* Expand the selected row after 1/2 second */
1789         if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), dest_row)) {
1790                 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), dest_row, pos);
1791                 priv->timer_expander = g_timeout_add (500, expand_row_timeout, widget);
1792         }
1793
1794         /* Select the desired action. By default we pick MOVE */
1795         suggested_action = GDK_ACTION_MOVE;
1796
1797         if (context->actions == GDK_ACTION_COPY)
1798             gdk_drag_status(context, GDK_ACTION_COPY, time);
1799         else if (context->actions == GDK_ACTION_MOVE)
1800             gdk_drag_status(context, GDK_ACTION_MOVE, time);
1801         else if (context->actions & suggested_action)
1802             gdk_drag_status(context, suggested_action, time);
1803         else
1804             gdk_drag_status(context, GDK_ACTION_DEFAULT, time);
1805
1806  out:
1807         if (dest_row)
1808                 gtk_tree_path_free (dest_row);
1809         g_signal_stop_emission_by_name (widget, "drag-motion");
1810         return valid_location;
1811 }
1812
1813
1814 /* Folder view drag types */
1815 const GtkTargetEntry folder_view_drag_types[] =
1816 {
1817         { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, MODEST_FOLDER_ROW },
1818         { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_APP,    MODEST_HEADER_ROW }
1819 };
1820
1821 /*
1822  * This function sets the treeview as a source and a target for dnd
1823  * events. It also connects all the requirede signals.
1824  */
1825 static void
1826 setup_drag_and_drop (GtkTreeView *self)
1827 {
1828         /* Set up the folder view as a dnd destination. Set only the
1829            highlight flag, otherwise gtk will have a different
1830            behaviour */
1831         gtk_drag_dest_set (GTK_WIDGET (self),
1832                            GTK_DEST_DEFAULT_HIGHLIGHT,
1833                            folder_view_drag_types,
1834                            G_N_ELEMENTS (folder_view_drag_types),
1835                            GDK_ACTION_MOVE | GDK_ACTION_COPY);
1836
1837         g_signal_connect (G_OBJECT (self),
1838                           "drag_data_received",
1839                           G_CALLBACK (on_drag_data_received),
1840                           NULL);
1841
1842
1843         /* Set up the treeview as a dnd source */
1844         gtk_drag_source_set (GTK_WIDGET (self),
1845                              GDK_BUTTON1_MASK,
1846                              folder_view_drag_types,
1847                              G_N_ELEMENTS (folder_view_drag_types),
1848                              GDK_ACTION_MOVE | GDK_ACTION_COPY);
1849
1850         g_signal_connect (G_OBJECT (self),
1851                           "drag_motion",
1852                           G_CALLBACK (on_drag_motion),
1853                           NULL);
1854         
1855         g_signal_connect (G_OBJECT (self),
1856                           "drag_data_get",
1857                           G_CALLBACK (on_drag_data_get),
1858                           NULL);
1859
1860         g_signal_connect (G_OBJECT (self),
1861                           "drag_drop",
1862                           G_CALLBACK (drag_drop_cb),
1863                           NULL);
1864 }
1865
1866 /*
1867  * This function manages the navigation through the folders using the
1868  * keyboard or the hardware keys in the device
1869  */
1870 static gboolean
1871 on_key_pressed (GtkWidget *self,
1872                 GdkEventKey *event,
1873                 gpointer user_data)
1874 {
1875         GtkTreeSelection *selection;
1876         GtkTreeIter iter;
1877         GtkTreeModel *model;
1878         gboolean retval = FALSE;
1879
1880         /* Up and Down are automatically managed by the treeview */
1881         if (event->keyval == GDK_Return) {
1882                 /* Expand/Collapse the selected row */
1883                 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1884                 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
1885                         GtkTreePath *path;
1886
1887                         path = gtk_tree_model_get_path (model, &iter);
1888
1889                         if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (self), path))
1890                                 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1891                         else
1892                                 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1893                         gtk_tree_path_free (path);
1894                 }
1895                 /* No further processing */
1896                 retval = TRUE;
1897         }
1898
1899         return retval;
1900 }
1901
1902 /*
1903  * We listen to the changes in the local folder account name key,
1904  * because we want to show the right name in the view. The local
1905  * folder account name corresponds to the device name in the Maemo
1906  * version. We do this because we do not want to query gconf on each
1907  * tree view refresh. It's better to cache it and change whenever
1908  * necessary.
1909  */
1910 static void 
1911 on_configuration_key_changed (ModestConf* conf, 
1912                               const gchar *key, 
1913                               ModestConfEvent event, 
1914                               ModestFolderView *self)
1915 {
1916         ModestFolderViewPrivate *priv;
1917
1918         if (!key)
1919                 return;
1920
1921         g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
1922         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1923
1924         if (!strcmp (key, MODEST_CONF_DEVICE_NAME)) {
1925                 g_free (priv->local_account_name);
1926
1927                 if (event == MODEST_CONF_EVENT_KEY_UNSET)
1928                         priv->local_account_name = g_strdup (MODEST_LOCAL_FOLDERS_DEFAULT_DISPLAY_NAME);
1929                 else
1930                         priv->local_account_name = modest_conf_get_string (modest_runtime_get_conf(),
1931                                                                            MODEST_CONF_DEVICE_NAME, NULL);
1932
1933                 /* Force a redraw */
1934 #if GTK_CHECK_VERSION(2, 8, 0) /* gtk_tree_view_column_queue_resize is only available in GTK+ 2.8 */
1935                 GtkTreeViewColumn * tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self), 
1936                                                                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN);
1937                 gtk_tree_view_column_queue_resize (tree_column);
1938 #endif
1939         }
1940 }
1941
1942 void 
1943 modest_folder_view_set_style (ModestFolderView *self,
1944                               ModestFolderViewStyle style)
1945 {
1946         ModestFolderViewPrivate *priv;
1947
1948         g_return_if_fail (self);
1949         
1950         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1951
1952         priv->style = style;
1953 }
1954
1955 void
1956 modest_folder_view_set_account_id_of_visible_server_account (ModestFolderView *self,
1957                                                              const gchar *account_id)
1958 {
1959         ModestFolderViewPrivate *priv;
1960         GtkTreeModel *model;
1961
1962         g_return_if_fail (self);
1963         
1964         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1965
1966         /* This will be used by the filter_row callback,
1967          * to decided which rows to show: */
1968         if (priv->visible_account_id) {
1969                 g_free (priv->visible_account_id);
1970                 priv->visible_account_id = NULL;
1971         }
1972         if (account_id)
1973                 priv->visible_account_id = g_strdup (account_id);
1974
1975         /* Refilter */
1976         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1977         if (GTK_IS_TREE_MODEL_FILTER (model))
1978                 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
1979
1980         /* Save settings to gconf */
1981         modest_widget_memory_save (modest_runtime_get_conf (), G_OBJECT(self),
1982                                    MODEST_CONF_FOLDER_VIEW_KEY);
1983 }
1984
1985 const gchar *
1986 modest_folder_view_get_account_id_of_visible_server_account (ModestFolderView *self)
1987 {
1988         ModestFolderViewPrivate *priv;
1989
1990         g_return_val_if_fail (self, NULL);
1991         
1992         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1993
1994         return (const gchar *) priv->visible_account_id;
1995 }
1996
1997 static gboolean
1998 find_inbox_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *inbox_iter)
1999 {
2000         do {
2001                 GtkTreeIter child;
2002                 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2003                 gchar *name = NULL;
2004
2005                 gtk_tree_model_get (model, iter, 
2006                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name,
2007                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, 
2008                                     &type, -1);
2009
2010                 /*
2011                 printf ("DEBUG: %s: name=%s, type=%d, TNY_FOLDER_TYPE_INBOX=%d\n", 
2012                         __FUNCTION__, name, type, TNY_FOLDER_TYPE_INBOX);
2013                 */
2014                         
2015                 gboolean result = FALSE;
2016                 if (type == TNY_FOLDER_TYPE_INBOX) {
2017                         result = TRUE;
2018                 } else if (type == TNY_FOLDER_TYPE_NORMAL) {
2019                         /* tinymail's camel implementation only provides TNY_FOLDER_TYPE_NORMAL
2020                          * when getting folders from the cache, before connectin, so we do 
2021                          * an extra check. We could fix this in tinymail, but it's easier 
2022                          * to do here.
2023                          */
2024                          if (strcmp (name, "Inbox") == 0)
2025                                 result = TRUE;
2026                 }
2027                 
2028                 g_free (name);
2029                 
2030                 if (result) {
2031                         *inbox_iter = *iter;
2032                         return TRUE;
2033                 }
2034
2035                 if (gtk_tree_model_iter_children (model, &child, iter)) {
2036                         if (find_inbox_iter (model, &child, inbox_iter))
2037                                 return TRUE;
2038                 }
2039
2040         } while (gtk_tree_model_iter_next (model, iter));
2041
2042         return FALSE;
2043 }
2044
2045
2046
2047
2048 void 
2049 modest_folder_view_select_first_inbox_or_local (ModestFolderView *self)
2050 {
2051         GtkTreeModel *model;
2052         GtkTreeIter iter, inbox_iter;
2053         GtkTreeSelection *sel;
2054
2055 /*      /\* Do not set it if the folder view was not painted *\/ */
2056 /*      if (!GTK_WIDGET_MAPPED (self)) */
2057 /*              return; */
2058
2059         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2060         if (!model)
2061                 return;
2062
2063         expand_root_items (self);
2064         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2065
2066         gtk_tree_model_get_iter_first (model, &iter);
2067         gtk_tree_selection_unselect_all (sel);
2068
2069         if (find_inbox_iter (model, &iter, &inbox_iter)) {
2070                 gtk_tree_selection_select_iter (sel, &inbox_iter);
2071         }
2072         else {
2073                 gtk_tree_model_get_iter_first (model, &iter);
2074                 gtk_tree_selection_select_iter (sel, &iter);
2075         }
2076 }
2077
2078
2079 /* recursive */
2080 static gboolean
2081 find_folder_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *folder_iter, 
2082                   TnyFolder* folder)
2083 {
2084         do {
2085                 GtkTreeIter child;
2086                 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2087                 TnyFolder* a_folder;
2088                 gchar *name = NULL;
2089                 
2090                 gtk_tree_model_get (model, iter, 
2091                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &a_folder,
2092                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name,
2093                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type, 
2094                                     -1);                
2095         
2096                 g_debug ("===> %s (%p ---- %p)", name, a_folder, folder);
2097                 g_free (name);
2098
2099                 if (folder == a_folder) {
2100                         g_object_unref (a_folder);
2101                         *folder_iter = *iter;
2102                         return TRUE;
2103                 }
2104                 g_object_unref (a_folder);
2105                 
2106                 if (gtk_tree_model_iter_children (model, &child, iter)) {
2107                         if (find_folder_iter (model, &child, folder_iter, folder)) 
2108                                 return TRUE;
2109                 }
2110
2111         } while (gtk_tree_model_iter_next (model, iter));
2112
2113         return FALSE;
2114 }
2115
2116
2117 static void
2118 on_row_changed_maybe_select_folder (GtkTreeModel *tree_model, GtkTreePath  *path, GtkTreeIter *iter,
2119                                     ModestFolderView *self)
2120 {
2121         ModestFolderViewPrivate *priv = NULL;
2122         GtkTreeSelection *sel;
2123
2124         if (!MODEST_IS_FOLDER_VIEW(self))
2125                 return;
2126         
2127         g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
2128         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2129         
2130         
2131         if (priv->folder_to_select) {
2132                 
2133                 if (!modest_folder_view_select_folder (self, priv->folder_to_select,
2134                                                        FALSE)) {
2135                         GtkTreePath *path;
2136                         path = gtk_tree_model_get_path (tree_model, iter);
2137                         gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
2138                         
2139                         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2140
2141                         gtk_tree_selection_select_iter (sel, iter);
2142                         gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
2143
2144                         gtk_tree_path_free (path);
2145                 
2146                 } 
2147                 g_object_unref (priv->folder_to_select);
2148                 priv->folder_to_select = NULL;
2149         } 
2150 }
2151
2152
2153 gboolean
2154 modest_folder_view_select_folder (ModestFolderView *self, TnyFolder *folder, 
2155                                   gboolean after_change)
2156 {
2157         GtkTreeModel *model;
2158         GtkTreeIter iter, folder_iter;
2159         GtkTreeSelection *sel;
2160         ModestFolderViewPrivate *priv = NULL;
2161         
2162         g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (self), FALSE);     
2163         g_return_val_if_fail (TNY_IS_FOLDER (folder), FALSE);   
2164                 
2165         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2166
2167         if (after_change) {
2168                 if (priv->folder_to_select)
2169                         g_object_unref(priv->folder_to_select);
2170                 priv->folder_to_select = TNY_FOLDER(g_object_ref(folder));
2171                 return TRUE;
2172         }
2173                 
2174         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2175         if (!model)
2176                 return FALSE;
2177
2178                 
2179         gtk_tree_model_get_iter_first (model, &iter);
2180         if (find_folder_iter (model, &iter, &folder_iter, folder)) {
2181                 GtkTreePath *path;
2182
2183                 path = gtk_tree_model_get_path (model, &folder_iter);
2184                 gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
2185
2186                 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2187                 gtk_tree_selection_select_iter (sel, &folder_iter);
2188                 gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
2189
2190                 gtk_tree_path_free (path);
2191                 return TRUE;
2192         }
2193         return FALSE;
2194 }
2195
2196
2197 void 
2198 modest_folder_view_copy_selection (ModestFolderView *folder_view)
2199 {
2200         /* Copy selection */
2201         _clipboard_set_selected_data (folder_view, FALSE);
2202 }
2203
2204 void 
2205 modest_folder_view_cut_selection (ModestFolderView *folder_view)
2206 {
2207         ModestFolderViewPrivate *priv = NULL;
2208         GtkTreeModel *model = NULL;
2209         const gchar **hidding = NULL;
2210         guint i, n_selected;
2211
2212         g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view));
2213         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
2214
2215         /* Copy selection */
2216         if (!_clipboard_set_selected_data (folder_view, TRUE))
2217                 return;
2218
2219         /* Get hidding ids */
2220         hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected); 
2221         
2222         /* Clear hidding array created by previous cut operation */
2223         _clear_hidding_filter (MODEST_FOLDER_VIEW (folder_view));
2224
2225         /* Copy hidding array */
2226         priv->n_selected = n_selected;
2227         priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
2228         for (i=0; i < n_selected; i++) 
2229                 priv->hidding_ids[i] = g_strdup(hidding[i]);            
2230
2231         /* Hide cut folders */
2232         model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
2233         gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2234 }
2235
2236 void
2237 modest_folder_view_show_non_move_folders (ModestFolderView *folder_view,
2238                                     gboolean show)
2239 {
2240         ModestFolderViewPrivate* priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
2241         priv->show_non_move = show;
2242         modest_folder_view_update_model(folder_view,
2243                                                                                                                                         TNY_ACCOUNT_STORE(modest_runtime_get_account_store()));
2244 }
2245
2246 /* Returns FALSE if it did not selected anything */
2247 static gboolean
2248 _clipboard_set_selected_data (ModestFolderView *folder_view,
2249                               gboolean delete)
2250 {
2251         ModestFolderViewPrivate *priv = NULL;
2252         TnyFolderStore *folder = NULL;
2253         gboolean retval = FALSE;
2254
2255         g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (folder_view), FALSE);
2256         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
2257                 
2258         /* Set selected data on clipboard   */
2259         g_return_val_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard), FALSE);
2260         folder = modest_folder_view_get_selected (folder_view);
2261
2262         /* Do not allow to select an account */
2263         if (TNY_IS_FOLDER (folder)) {
2264                 modest_email_clipboard_set_data (priv->clipboard, TNY_FOLDER(folder), NULL, delete);
2265                 retval = TRUE;
2266         }
2267
2268         /* Free */
2269         g_object_unref (folder);
2270
2271         return retval;
2272 }
2273
2274 static void
2275 _clear_hidding_filter (ModestFolderView *folder_view) 
2276 {
2277         ModestFolderViewPrivate *priv;
2278         guint i;
2279         
2280         g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view)); 
2281         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
2282
2283         if (priv->hidding_ids != NULL) {
2284                 for (i=0; i < priv->n_selected; i++) 
2285                         g_free (priv->hidding_ids[i]);
2286                 g_free(priv->hidding_ids);
2287         }       
2288 }
2289
2290