* Fixes NB#59396, the OUTBOX folder icon is now shown properly
[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-folder.h>
45 #include <modest-tny-local-folders-account.h>
46 #include <modest-tny-outbox-account.h>
47 #include <modest-marshal.h>
48 #include <modest-icon-names.h>
49 #include <modest-tny-account-store.h>
50 #include <modest-text-utils.h>
51 #include <modest-runtime.h>
52 #include "modest-folder-view.h"
53 #include <modest-dnd.h>
54 #include <modest-platform.h>
55 #include <modest-widget-memory.h>
56 #include <modest-ui-actions.h>
57
58 /* 'private'/'protected' functions */
59 static void modest_folder_view_class_init  (ModestFolderViewClass *klass);
60 static void modest_folder_view_init        (ModestFolderView *obj);
61 static void modest_folder_view_finalize    (GObject *obj);
62
63 static void         tny_account_store_view_init (gpointer g, 
64                                                  gpointer iface_data);
65
66 static void         modest_folder_view_set_account_store (TnyAccountStoreView *self, 
67                                                           TnyAccountStore     *account_store);
68
69 static void         on_selection_changed   (GtkTreeSelection *sel, gpointer data);
70
71 static void         on_account_update      (TnyAccountStore *account_store, 
72                                             const gchar *account,
73                                             gpointer user_data);
74
75 static void         on_account_removed     (TnyAccountStore *self, 
76                                             TnyAccount *account,
77                                             gpointer user_data);
78
79 static void         on_accounts_reloaded   (TnyAccountStore *store, 
80                                             gpointer user_data);
81
82 static gint         cmp_rows               (GtkTreeModel *tree_model, 
83                                             GtkTreeIter *iter1, 
84                                             GtkTreeIter *iter2,
85                                             gpointer user_data);
86
87 static gboolean     filter_row             (GtkTreeModel *model,
88                                             GtkTreeIter *iter,
89                                             gpointer data);
90
91 static gboolean     on_key_pressed         (GtkWidget *self,
92                                             GdkEventKey *event,
93                                             gpointer user_data);
94
95 static void         on_configuration_key_changed         (ModestConf* conf, 
96                                                           const gchar *key, 
97                                                           ModestConfEvent event, 
98                                                           ModestFolderView *self);
99
100 /* DnD functions */
101 static void         on_drag_data_get       (GtkWidget *widget, 
102                                             GdkDragContext *context, 
103                                             GtkSelectionData *selection_data, 
104                                             guint info, 
105                                             guint time, 
106                                             gpointer data);
107
108 static void         on_drag_data_received  (GtkWidget *widget, 
109                                             GdkDragContext *context, 
110                                             gint x, 
111                                             gint y, 
112                                             GtkSelectionData *selection_data, 
113                                             guint info, 
114                                             guint time, 
115                                             gpointer data);
116
117 static gboolean     on_drag_motion         (GtkWidget      *widget,
118                                             GdkDragContext *context,
119                                             gint            x,
120                                             gint            y,
121                                             guint           time,
122                                             gpointer        user_data);
123
124 static gint         expand_row_timeout     (gpointer data);
125
126 static void         setup_drag_and_drop    (GtkTreeView *self);
127
128 enum {
129         FOLDER_SELECTION_CHANGED_SIGNAL,
130         FOLDER_DISPLAY_NAME_CHANGED_SIGNAL,
131         LAST_SIGNAL
132 };
133
134 typedef struct _ModestFolderViewPrivate ModestFolderViewPrivate;
135 struct _ModestFolderViewPrivate {
136         TnyAccountStore      *account_store;
137         TnyFolderStore       *cur_folder_store;
138
139         gulong                account_update_signal;
140         gulong                changed_signal;
141         gulong                accounts_reloaded_signal;
142         gulong                account_removed_signal;
143         gulong                conf_key_signal;
144         
145         TnyFolderStoreQuery  *query;
146         guint                 timer_expander;
147
148         gchar                *local_account_name;
149         gchar                *visible_account_id;
150         ModestFolderViewStyle style;
151 };
152 #define MODEST_FOLDER_VIEW_GET_PRIVATE(o)                       \
153         (G_TYPE_INSTANCE_GET_PRIVATE((o),                       \
154                                      MODEST_TYPE_FOLDER_VIEW,   \
155                                      ModestFolderViewPrivate))
156 /* globals */
157 static GObjectClass *parent_class = NULL;
158
159 static guint signals[LAST_SIGNAL] = {0}; 
160
161 GType
162 modest_folder_view_get_type (void)
163 {
164         static GType my_type = 0;
165         if (!my_type) {
166                 static const GTypeInfo my_info = {
167                         sizeof(ModestFolderViewClass),
168                         NULL,           /* base init */
169                         NULL,           /* base finalize */
170                         (GClassInitFunc) modest_folder_view_class_init,
171                         NULL,           /* class finalize */
172                         NULL,           /* class data */
173                         sizeof(ModestFolderView),
174                         1,              /* n_preallocs */
175                         (GInstanceInitFunc) modest_folder_view_init,
176                         NULL
177                 };
178
179                 static const GInterfaceInfo tny_account_store_view_info = {
180                         (GInterfaceInitFunc) tny_account_store_view_init, /* interface_init */
181                         NULL,         /* interface_finalize */
182                         NULL          /* interface_data */
183                 };
184
185                                 
186                 my_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
187                                                   "ModestFolderView",
188                                                   &my_info, 0);
189
190                 g_type_add_interface_static (my_type, 
191                                              TNY_TYPE_ACCOUNT_STORE_VIEW, 
192                                              &tny_account_store_view_info);
193         }
194         return my_type;
195 }
196
197 static void
198 modest_folder_view_class_init (ModestFolderViewClass *klass)
199 {
200         GObjectClass *gobject_class;
201         gobject_class = (GObjectClass*) klass;
202
203         parent_class            = g_type_class_peek_parent (klass);
204         gobject_class->finalize = modest_folder_view_finalize;
205
206         g_type_class_add_private (gobject_class,
207                                   sizeof(ModestFolderViewPrivate));
208         
209         signals[FOLDER_SELECTION_CHANGED_SIGNAL] = 
210                 g_signal_new ("folder_selection_changed",
211                               G_TYPE_FROM_CLASS (gobject_class),
212                               G_SIGNAL_RUN_FIRST,
213                               G_STRUCT_OFFSET (ModestFolderViewClass,
214                                                folder_selection_changed),
215                               NULL, NULL,
216                               modest_marshal_VOID__POINTER_BOOLEAN,
217                               G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_BOOLEAN);
218
219         /*
220          * This signal is emitted whenever the currently selected
221          * folder display name is computed. Note that the name could
222          * be different to the folder name, because we could append
223          * the unread messages count to the folder name to build the
224          * folder display name
225          */
226         signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL] = 
227                 g_signal_new ("folder-display-name-changed",
228                               G_TYPE_FROM_CLASS (gobject_class),
229                               G_SIGNAL_RUN_FIRST,
230                               G_STRUCT_OFFSET (ModestFolderViewClass,
231                                                folder_display_name_changed),
232                               NULL, NULL,
233                               g_cclosure_marshal_VOID__STRING,
234                               G_TYPE_NONE, 1, G_TYPE_STRING);
235 }
236
237 static void
238 text_cell_data  (GtkTreeViewColumn *column,  GtkCellRenderer *renderer,
239                  GtkTreeModel *tree_model,  GtkTreeIter *iter,  gpointer data)
240 {
241         ModestFolderViewPrivate *priv;
242         GObject *rendobj;
243         gchar *fname = NULL;
244         gint unread = 0;
245         gint all = 0;
246         TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
247         GObject *instance = NULL;
248         
249         g_return_if_fail (column);
250         g_return_if_fail (tree_model);
251
252         gtk_tree_model_get (tree_model, iter,
253                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &fname,
254                             TNY_GTK_FOLDER_STORE_TREE_MODEL_ALL_COLUMN, &all,
255                             TNY_GTK_FOLDER_STORE_TREE_MODEL_UNREAD_COLUMN, &unread,
256                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
257                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
258                             -1);
259         rendobj = G_OBJECT(renderer);
260  
261         if (!fname)
262                 return;
263
264         if (!instance) {
265                 g_free (fname);
266                 return;
267         }
268
269         
270         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE (data);
271         
272         gchar *item_name = NULL;
273         gint item_weight = 400;
274         
275         if (type != TNY_FOLDER_TYPE_ROOT) {
276                 gint number = 0;
277                 
278                 if (modest_tny_folder_is_local_folder (TNY_FOLDER (instance))) {
279                         type = modest_tny_folder_get_local_folder_type (TNY_FOLDER (instance));
280                         if (type != TNY_FOLDER_TYPE_UNKNOWN) {
281                                 g_free (fname);
282                                 fname = g_strdup(modest_local_folder_info_get_type_display_name (type));
283                         }
284                 }
285
286                 /* Select the number to show: the unread or unsent messages */
287                 if ((type == TNY_FOLDER_TYPE_DRAFTS) || (type == TNY_FOLDER_TYPE_OUTBOX))
288                         number = all;
289                 else
290                         number = unread;
291                 
292                 /* Use bold font style if there are unread or unset messages */
293                 if (number > 0) {
294                         item_name = g_strdup_printf ("%s (%d)", fname, number);
295                         item_weight = 800;
296                 } else {
297                         item_name = g_strdup (fname);
298                         item_weight = 400;
299                 }
300                 
301         } else if (TNY_IS_ACCOUNT (instance)) {
302                 /* If it's a server account */
303                 if (modest_tny_account_is_virtual_local_folders (
304                                 TNY_ACCOUNT (instance))) {
305                         item_name = g_strdup (priv->local_account_name);
306                         item_weight = 400;
307                 } else {
308                         item_name = g_strdup (fname);
309                         item_weight = 800;
310                 }
311         }
312         
313         if (!item_name)
314                 item_name = g_strdup ("unknown");
315                         
316         if (item_name && item_weight) {
317                 /* Set the name in the treeview cell: */
318                 g_object_set (rendobj,"text", item_name, "weight", item_weight, NULL);
319                 
320                 /* Notify display name observers */
321                 if (G_OBJECT (priv->cur_folder_store) == instance) {
322                         g_signal_emit (G_OBJECT(data),
323                                                signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
324                                                item_name);
325                 }
326                 
327                 g_free (item_name);
328                 
329         }
330         
331         g_object_unref (G_OBJECT (instance));
332         g_free (fname);
333 }
334
335
336
337 static void
338 icon_cell_data  (GtkTreeViewColumn *column,  GtkCellRenderer *renderer,
339                  GtkTreeModel *tree_model,  GtkTreeIter *iter, gpointer data)
340 {
341         GObject *rendobj = NULL, *instance = NULL;
342         GdkPixbuf *pixbuf = NULL;
343         TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
344         gchar *fname = NULL;
345         const gchar *account_id = NULL;
346         gint unread = 0;
347         
348         rendobj = G_OBJECT(renderer);
349         gtk_tree_model_get (tree_model, iter,
350                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
351                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &fname,
352                             TNY_GTK_FOLDER_STORE_TREE_MODEL_UNREAD_COLUMN, &unread,
353                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
354                             -1);
355
356         if (!fname)
357                 return;
358
359         if (!instance) {
360                 g_free (fname);
361                 return;
362         }
363
364         /* We include the MERGE type here because it's used to create
365            the local OUTBOX folder */
366         if (type == TNY_FOLDER_TYPE_NORMAL || 
367             type == TNY_FOLDER_TYPE_UNKNOWN ||
368             type == TNY_FOLDER_TYPE_MERGE) {
369                 type = modest_tny_folder_guess_folder_type (TNY_FOLDER (instance));
370         }
371
372         switch (type) {
373         case TNY_FOLDER_TYPE_ROOT:
374                 if (TNY_IS_ACCOUNT (instance)) {
375                         
376                         if (modest_tny_account_is_virtual_local_folders (
377                                 TNY_ACCOUNT (instance))) {
378                                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_LOCAL_FOLDERS);
379                         }
380                         else {
381                                 account_id = tny_account_get_id (TNY_ACCOUNT (instance));
382                                 
383                                 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
384                                         pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_MMC);
385                                 else
386                                         pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_ACCOUNT);
387                         }
388                 }
389                 break;
390         case TNY_FOLDER_TYPE_INBOX:
391             pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_INBOX);
392             break;
393         case TNY_FOLDER_TYPE_OUTBOX:
394                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_OUTBOX);
395                 break;
396         case TNY_FOLDER_TYPE_JUNK:
397                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_JUNK);
398                 break;
399         case TNY_FOLDER_TYPE_SENT:
400                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_SENT);
401                 break;
402         case TNY_FOLDER_TYPE_TRASH:
403                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_TRASH);
404                 break;
405         case TNY_FOLDER_TYPE_DRAFTS:
406                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_DRAFTS);
407                 break;
408         case TNY_FOLDER_TYPE_NORMAL:
409         default:
410                 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_NORMAL);
411                 break;
412         }
413         
414         g_object_unref (G_OBJECT (instance));
415         g_free (fname);
416
417         /* Set pixbuf */
418         g_object_set (rendobj, "pixbuf", pixbuf, NULL);
419
420         if (pixbuf != NULL)
421                 g_object_unref (pixbuf);
422 }
423
424 static void
425 add_columns (GtkWidget *treeview)
426 {
427         GtkTreeViewColumn *column;
428         GtkCellRenderer *renderer;
429         GtkTreeSelection *sel;
430
431         /* Create column */
432         column = gtk_tree_view_column_new ();   
433         
434         /* Set icon and text render function */
435         renderer = gtk_cell_renderer_pixbuf_new();
436         gtk_tree_view_column_pack_start (column, renderer, FALSE);
437         gtk_tree_view_column_set_cell_data_func(column, renderer,
438                                                 icon_cell_data, treeview, NULL);
439         
440         renderer = gtk_cell_renderer_text_new();
441         gtk_tree_view_column_pack_start (column, renderer, FALSE);
442         gtk_tree_view_column_set_cell_data_func(column, renderer,
443                                                 text_cell_data, treeview, NULL);
444         
445         /* Set selection mode */
446         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(treeview));
447         gtk_tree_selection_set_mode (sel, GTK_SELECTION_SINGLE);
448
449         /* Set treeview appearance */
450         gtk_tree_view_column_set_spacing (column, 2);
451         gtk_tree_view_column_set_resizable (column, TRUE);
452         gtk_tree_view_column_set_fixed_width (column, TRUE);            
453         gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(treeview), FALSE);
454         gtk_tree_view_set_enable_search (GTK_TREE_VIEW(treeview), FALSE);
455
456         /* Add column */
457         gtk_tree_view_append_column (GTK_TREE_VIEW(treeview),column);
458 }
459
460 static void
461 modest_folder_view_init (ModestFolderView *obj)
462 {
463         ModestFolderViewPrivate *priv;
464         ModestConf *conf;
465         
466         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
467         
468         priv->timer_expander = 0;
469         priv->account_store  = NULL;
470         priv->query          = NULL;
471         priv->style          = MODEST_FOLDER_VIEW_STYLE_SHOW_ALL;
472         priv->cur_folder_store   = NULL;
473         priv->visible_account_id = NULL;
474
475         /* Initialize the local account name */
476         conf = modest_runtime_get_conf();
477         priv->local_account_name = modest_conf_get_string (conf, MODEST_CONF_DEVICE_NAME, NULL);
478
479         /* Build treeview */
480         add_columns (GTK_WIDGET (obj));
481
482         /* Setup drag and drop */
483         setup_drag_and_drop (GTK_TREE_VIEW(obj));
484
485         /* Connect signals */
486         g_signal_connect (G_OBJECT (obj), 
487                           "key-press-event", 
488                           G_CALLBACK (on_key_pressed), NULL);
489
490         /*
491          * Track changes in the local account name (in the device it
492          * will be the device name)
493          */
494         priv->conf_key_signal = 
495                 g_signal_connect (G_OBJECT(conf), 
496                                   "key_changed",
497                                   G_CALLBACK(on_configuration_key_changed), obj);
498 }
499
500 static void
501 tny_account_store_view_init (gpointer g, gpointer iface_data)
502 {
503         TnyAccountStoreViewIface *klass = (TnyAccountStoreViewIface *)g;
504
505         klass->set_account_store_func = modest_folder_view_set_account_store;
506
507         return;
508 }
509
510 static void
511 modest_folder_view_finalize (GObject *obj)
512 {
513         ModestFolderViewPrivate *priv;
514         GtkTreeSelection    *sel;
515         
516         g_return_if_fail (obj);
517         
518         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
519
520         if (priv->timer_expander != 0) {
521                 g_source_remove (priv->timer_expander);
522                 priv->timer_expander = 0;
523         }
524
525         if (priv->account_store) {
526                 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
527                                              priv->account_update_signal);
528                 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
529                                              priv->accounts_reloaded_signal);
530                 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
531                                              priv->account_removed_signal);
532                 g_object_unref (G_OBJECT(priv->account_store));
533                 priv->account_store = NULL;
534         }
535
536         if (priv->query) {
537                 g_object_unref (G_OBJECT (priv->query));
538                 priv->query = NULL;
539         }
540
541         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(obj));
542         if (sel)
543                 g_signal_handler_disconnect (G_OBJECT(sel), priv->changed_signal);
544
545         g_free (priv->local_account_name);
546         g_free (priv->visible_account_id);
547         
548         if (priv->conf_key_signal) {
549                 g_signal_handler_disconnect (modest_runtime_get_conf (),
550                                              priv->conf_key_signal);
551                 priv->conf_key_signal = 0;
552         }
553
554         G_OBJECT_CLASS(parent_class)->finalize (obj);
555 }
556
557
558 static void
559 modest_folder_view_set_account_store (TnyAccountStoreView *self, TnyAccountStore *account_store)
560 {
561         ModestFolderViewPrivate *priv;
562         TnyDevice *device;
563
564         g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
565         g_return_if_fail (TNY_IS_ACCOUNT_STORE (account_store));
566
567         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
568         device = tny_account_store_get_device (account_store);
569
570         if (G_UNLIKELY (priv->account_store)) {
571
572                 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store), 
573                                                    priv->account_update_signal))
574                         g_signal_handler_disconnect (G_OBJECT (priv->account_store), 
575                                                      priv->account_update_signal);
576                 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store), 
577                                                    priv->accounts_reloaded_signal))
578                         g_signal_handler_disconnect (G_OBJECT (priv->account_store), 
579                                                      priv->accounts_reloaded_signal);
580                 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store), 
581                                                    priv->account_removed_signal))
582                         g_signal_handler_disconnect (G_OBJECT (priv->account_store), 
583                                                      priv->account_removed_signal);
584
585                 g_object_unref (G_OBJECT (priv->account_store));
586         }
587
588         priv->account_store = g_object_ref (G_OBJECT (account_store));
589
590         priv->account_update_signal = 
591                 g_signal_connect (G_OBJECT(account_store), "account_update",
592                                   G_CALLBACK (on_account_update), self);
593
594         priv->account_removed_signal = 
595                 g_signal_connect (G_OBJECT(account_store), "account_removed",
596                                   G_CALLBACK (on_account_removed), self);
597
598         priv->accounts_reloaded_signal = 
599                 g_signal_connect (G_OBJECT(account_store), "accounts_reloaded",
600                                   G_CALLBACK (on_accounts_reloaded), self);
601
602         on_accounts_reloaded (account_store, (gpointer ) self);
603         
604         g_object_unref (G_OBJECT (device));
605 }
606
607 static void
608 on_account_removed (TnyAccountStore *account_store, 
609                     TnyAccount *account,
610                     gpointer user_data)
611 {
612         ModestFolderView *self = MODEST_FOLDER_VIEW (user_data);
613         ModestFolderViewPrivate *priv;
614
615         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
616
617         /* If the removed account is the currently viewed one then
618            clear the configuration value. The new visible account will be the default account */
619         if (!strcmp (priv->visible_account_id, tny_account_get_id (account))) {
620                 modest_folder_view_set_account_id_of_visible_server_account (self, NULL);
621         }
622 }
623
624 static void
625 on_account_update (TnyAccountStore *account_store, 
626                    const gchar *account,
627                    gpointer user_data)
628 {
629         ModestFolderView *self = MODEST_FOLDER_VIEW (user_data);
630         ModestFolderViewPrivate *priv;
631
632         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
633         if (!priv->visible_account_id)
634                 modest_widget_memory_restore (modest_runtime_get_conf(), G_OBJECT(self),
635                                               MODEST_CONF_FOLDER_VIEW_KEY);
636
637         if (!modest_folder_view_update_model (self, account_store))
638                 g_printerr ("modest: failed to update model for changes in '%s'",
639                             account);
640 }
641
642 static void 
643 on_accounts_reloaded   (TnyAccountStore *account_store, 
644                         gpointer user_data)
645 {
646         modest_folder_view_update_model (MODEST_FOLDER_VIEW (user_data), account_store);
647 }
648
649 void
650 modest_folder_view_set_title (ModestFolderView *self, const gchar *title)
651 {
652         GtkTreeViewColumn *col;
653         
654         g_return_if_fail (self);
655
656         col = gtk_tree_view_get_column (GTK_TREE_VIEW(self), 0);
657         if (!col) {
658                 g_printerr ("modest: failed get column for title\n");
659                 return;
660         }
661
662         gtk_tree_view_column_set_title (col, title);
663         gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self),
664                                            title != NULL);
665 }
666
667 GtkWidget*
668 modest_folder_view_new (TnyFolderStoreQuery *query)
669 {
670         GObject *self;
671         ModestFolderViewPrivate *priv;
672         GtkTreeSelection *sel;
673         
674         self = G_OBJECT (g_object_new (MODEST_TYPE_FOLDER_VIEW, NULL));
675         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
676
677         if (query)
678                 priv->query = g_object_ref (query);
679         
680         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
681         priv->changed_signal = g_signal_connect (sel, "changed",
682                                                  G_CALLBACK (on_selection_changed), self);
683
684         return GTK_WIDGET(self);
685 }
686
687 /* this feels dirty; any other way to expand all the root items? */
688 static void
689 expand_root_items (ModestFolderView *self)
690 {
691         GtkTreePath *path;
692         path = gtk_tree_path_new_first ();
693
694         /* all folders should have child items, so.. */
695         while (gtk_tree_view_expand_row (GTK_TREE_VIEW(self), path, FALSE))
696                 gtk_tree_path_next (path);
697         
698         gtk_tree_path_free (path);
699 }
700
701 /*
702  * We use this function to implement the
703  * MODEST_FOLDER_VIEW_STYLE_SHOW_ONE style. We only show the default
704  * account in this case, and the local folders.
705  */
706 static gboolean 
707 filter_row (GtkTreeModel *model,
708             GtkTreeIter *iter,
709             gpointer data)
710 {
711         gboolean retval = TRUE;
712         TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
713         GObject *instance = NULL;
714
715         gtk_tree_model_get (model, iter,
716                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
717                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
718                             -1);
719
720         /* Do not show if there is no instance, this could indeed
721            happen when the model is being modified while it's being
722            drawn. This could occur for example when moving folders
723            using drag&drop */
724         if (!instance)
725                 return FALSE;
726
727         if (type == TNY_FOLDER_TYPE_ROOT) {
728                 /* TNY_FOLDER_TYPE_ROOT means that the instance is an account instead of a folder. */
729                 if (TNY_IS_ACCOUNT (instance)) {
730                         TnyAccount *acc = TNY_ACCOUNT (instance);
731                         const gchar *account_id = tny_account_get_id (acc);
732         
733                         /* If it isn't a special folder, 
734                          * don't show it unless it is the visible account: */
735                         if (!modest_tny_account_is_virtual_local_folders (acc) &&
736                                 strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) { 
737                                 /* Show only the visible account id */
738                                 ModestFolderViewPrivate *priv = 
739                                         MODEST_FOLDER_VIEW_GET_PRIVATE (data);
740                                 if (priv->visible_account_id && strcmp (account_id, priv->visible_account_id))
741                                         retval = FALSE;
742                         }
743                         
744                         /* Never show these to the user. They are merged into one folder 
745                          * in the local-folders account instead: */
746                         if (retval && MODEST_IS_TNY_OUTBOX_ACCOUNT (acc))
747                                 retval = FALSE;
748                 }
749         }
750         
751         /* The virtual local-folders folder store is also shown by default. */
752
753         g_object_unref (instance);
754
755         return retval;
756 }
757
758 gboolean
759 modest_folder_view_update_model (ModestFolderView *self,
760                                  TnyAccountStore *account_store)
761 {
762         ModestFolderViewPrivate *priv;
763         GtkTreeModel *model /* , *old_model */;
764         /* TnyAccount *local_account; */
765         TnyList *model_as_list;
766
767         g_return_val_if_fail (account_store, FALSE);
768
769         priv =  MODEST_FOLDER_VIEW_GET_PRIVATE(self);
770         
771         /* Notify that there is no folder selected */
772         g_signal_emit (G_OBJECT(self), 
773                        signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
774                        NULL, TRUE);
775
776         /* FIXME: the local accounts are not shown when the query
777            selects only the subscribed folders. */
778 /*      model        = tny_gtk_folder_store_tree_model_new (TRUE, priv->query); */
779         model        = tny_gtk_folder_store_tree_model_new (TRUE, NULL);
780         
781         /* Deal with the model via its TnyList Interface,
782          * filling the TnyList via a get_accounts() call: */
783         model_as_list = TNY_LIST(model);
784
785         /* Get the accounts: */
786         tny_account_store_get_accounts (TNY_ACCOUNT_STORE(account_store),
787                                         model_as_list,
788                                         TNY_ACCOUNT_STORE_STORE_ACCOUNTS);
789         g_object_unref (model_as_list);
790         model_as_list = NULL;   
791                                                      
792         GtkTreeModel *filter_model = NULL, *sortable = NULL;
793
794         sortable = gtk_tree_model_sort_new_with_model (model);
795         gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
796                                               TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, 
797                                               GTK_SORT_ASCENDING);
798         gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
799                                          TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
800                                          cmp_rows, NULL, NULL);
801
802         /* Create filter model */
803         if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE) {
804                 filter_model = gtk_tree_model_filter_new (sortable, NULL);
805                 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
806                                                         filter_row,
807                                                         self,
808                                                         NULL);
809         }
810
811         /* Set new model */
812         gtk_tree_view_set_model (GTK_TREE_VIEW(self), 
813                                  (filter_model) ? filter_model : sortable);
814         expand_root_items (self); /* expand all account folders */
815         
816         g_object_unref (model);
817         
818         if (filter_model)
819                 g_object_unref (filter_model);
820                         
821         g_object_unref (sortable);
822
823         /* Select the first inbox or the local account if not found */
824         modest_folder_view_select_first_inbox_or_local (self);
825                         
826         return TRUE;
827 }
828
829
830 static void
831 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
832 {
833         GtkTreeModel            *model;
834         TnyFolderStore          *folder = NULL;
835         GtkTreeIter             iter;
836         ModestFolderView        *tree_view;
837         ModestFolderViewPrivate *priv;
838
839         g_return_if_fail (sel);
840         g_return_if_fail (user_data);
841         
842         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
843
844         /* folder was _un_selected if true */
845         if (!gtk_tree_selection_get_selected (sel, &model, &iter)) {
846                 if (priv->cur_folder_store)
847                         g_object_unref (priv->cur_folder_store);
848                 priv->cur_folder_store = NULL;
849
850                 /* Notify the display name observers */
851                 g_signal_emit (G_OBJECT(user_data),
852                                signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
853                                NULL);
854                 return;
855         }
856
857         tree_view = MODEST_FOLDER_VIEW (user_data);
858
859         gtk_tree_model_get (model, &iter,
860                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
861                             -1);
862
863         /* If the folder is the same do not notify */
864         if (priv->cur_folder_store == folder) {
865                 g_object_unref (folder);
866                 return;
867         }
868         
869         /* Current folder was unselected */
870         if (priv->cur_folder_store) {
871                 g_signal_emit (G_OBJECT(tree_view), signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
872                                priv->cur_folder_store, FALSE);
873                 g_object_unref (priv->cur_folder_store);
874         }
875
876         /* New current references */
877         priv->cur_folder_store = folder;
878
879         /* New folder has been selected */
880         g_signal_emit (G_OBJECT(tree_view),
881                        signals[FOLDER_SELECTION_CHANGED_SIGNAL],
882                        0, folder, TRUE);
883 }
884
885 TnyFolderStore *
886 modest_folder_view_get_selected (ModestFolderView *self)
887 {
888         ModestFolderViewPrivate *priv;
889
890         g_return_val_if_fail (self, NULL);
891         
892         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
893         if (priv->cur_folder_store)
894                 g_object_ref (priv->cur_folder_store);
895
896         return priv->cur_folder_store;
897 }
898
899 static gint
900 get_cmp_rows_type_pos (GObject *folder)
901 {
902         /* Remote accounts -> Local account -> MMC account .*/
903         /* 0, 1, 2 */
904         
905         if (TNY_IS_ACCOUNT (folder) && 
906                 modest_tny_account_is_virtual_local_folders (
907                         TNY_ACCOUNT (folder))) {
908                 return 1;
909         } else if (TNY_IS_ACCOUNT (folder)) {
910                 TnyAccount *account = TNY_ACCOUNT (folder);
911                 const gchar *account_id = tny_account_get_id (account);
912                 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
913                         return 2;
914                 else
915                         return 0;
916         }
917         else {
918                 printf ("DEBUG: %s: unexpected type.\n", __FUNCTION__);
919                 return -1; /* Should never happen */
920         }
921 }
922
923 /*
924  * This function orders the mail accounts according to these rules:
925  * 1st - remote accounts
926  * 2nd - local account
927  * 3rd - MMC account
928  */
929 static gint
930 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
931           gpointer user_data)
932 {
933         gint cmp;
934         gchar *name1 = NULL;
935         gchar *name2 = NULL;
936         TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
937         GObject *folder1 = NULL;
938         GObject *folder2 = NULL;
939
940         gtk_tree_model_get (tree_model, iter1,
941                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name1,
942                             TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
943                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder1,
944                             -1);
945         gtk_tree_model_get (tree_model, iter2,
946                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name2,
947                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder2,
948                             -1);
949
950         if (type == TNY_FOLDER_TYPE_ROOT) {
951                 /* Compare the types, so that 
952                  * Remote accounts -> Local account -> MMC account .*/
953                 const gint pos1 = get_cmp_rows_type_pos (folder1);
954                 const gint pos2 = get_cmp_rows_type_pos (folder2);
955                 /* printf ("DEBUG: %s:\n  type1=%s, pos1=%d\n  type2=%s, pos2=%d\n", 
956                         __FUNCTION__, G_OBJECT_TYPE_NAME(folder1), pos1, G_OBJECT_TYPE_NAME(folder2), pos2); */
957                 if (pos1 <  pos2)
958                         cmp = -1;
959                 else if (pos1 > pos2)
960                         cmp = 1;
961                 else {
962                         /* Compare items of the same type: */
963                         
964                         TnyAccount *account1 = NULL;
965                         if (TNY_IS_ACCOUNT (folder1))
966                                 account1 = TNY_ACCOUNT (folder1);
967                                 
968                         TnyAccount *account2 = NULL;
969                         if (TNY_IS_ACCOUNT (folder2))
970                                 account2 = TNY_ACCOUNT (folder2);
971                                 
972                         const gchar *account_id = account1 ? tny_account_get_id (account1) : NULL;
973                         const gchar *account_id2 = account2 ? tny_account_get_id (account2) : NULL;
974         
975                         if (!account_id && !account_id2)
976                                 return 0;
977                         else if (!account_id)
978                                 return -1;
979                         else if (!account_id2)
980                                 return +1;
981                         else if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
982                                 cmp = +1;
983                         else
984                                 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
985                 }
986         } else {
987                 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
988         }
989         
990         if (folder1)
991                 g_object_unref(G_OBJECT(folder1));
992         if (folder2)
993                 g_object_unref(G_OBJECT(folder2));
994
995         g_free (name1);
996         g_free (name2);
997
998         return cmp;     
999 }
1000
1001 /*****************************************************************************/
1002 /*                        DRAG and DROP stuff                                */
1003 /*****************************************************************************/
1004
1005 /*
1006  * This function fills the #GtkSelectionData with the row and the
1007  * model that has been dragged. It's called when this widget is a
1008  * source for dnd after the event drop happened
1009  */
1010 static void
1011 on_drag_data_get (GtkWidget *widget, 
1012                   GdkDragContext *context, 
1013                   GtkSelectionData *selection_data, 
1014                   guint info, 
1015                   guint time, 
1016                   gpointer data)
1017 {
1018         GtkTreeSelection *selection;
1019         GtkTreeModel *model;
1020         GtkTreeIter iter;
1021         GtkTreePath *source_row;
1022
1023         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1024         gtk_tree_selection_get_selected (selection, &model, &iter);
1025         source_row = gtk_tree_model_get_path (model, &iter);
1026
1027         gtk_tree_set_row_drag_data (selection_data,
1028                                     model,
1029                                     source_row);
1030
1031         gtk_tree_path_free (source_row);
1032 }
1033
1034 typedef struct _DndHelper {
1035         gboolean delete_source;
1036         GtkTreePath *source_row;
1037         GdkDragContext *context;
1038         guint time;
1039 } DndHelper;
1040
1041
1042 /*
1043  * This function is the callback of the
1044  * modest_mail_operation_xfer_msgs () and
1045  * modest_mail_operation_xfer_folder() calls. We check here if the
1046  * message/folder was correctly asynchronously transferred. The reason
1047  * to use the same callback is that the code is the same, it only has
1048  * to check that the operation went fine and then finalize the drag
1049  * and drop action
1050  */
1051 static void
1052 on_progress_changed (ModestMailOperation *mail_op, 
1053                      ModestMailOperationState *state,
1054                      gpointer user_data)
1055 {
1056         gboolean success;
1057         DndHelper *helper;
1058
1059         helper = (DndHelper *) user_data;
1060
1061         if (!state->finished)
1062                 return;
1063
1064         if (state->status == MODEST_MAIL_OPERATION_STATUS_SUCCESS) {
1065                 success = TRUE;
1066         } else {
1067                 success = FALSE;
1068         }
1069
1070         /* Notify the drag source. Never call delete, the monitor will
1071            do the job if needed */
1072         gtk_drag_finish (helper->context, success, FALSE, helper->time);
1073
1074         /* Free the helper */
1075         gtk_tree_path_free (helper->source_row);        
1076         g_slice_free (DndHelper, helper);
1077 }
1078
1079 /*
1080  * This function is used by drag_data_received_cb to manage drag and
1081  * drop of a header, i.e, and drag from the header view to the folder
1082  * view.
1083  */
1084 static void
1085 drag_and_drop_from_header_view (GtkTreeModel *source_model,
1086                                 GtkTreeModel *dest_model,
1087                                 GtkTreePath  *dest_row,
1088                                 DndHelper    *helper)
1089 {
1090         TnyList *headers = NULL;
1091         TnyHeader *header = NULL;
1092         TnyFolder *folder = NULL;
1093         ModestMailOperation *mail_op = NULL;
1094         GtkTreeIter source_iter, dest_iter;
1095
1096         g_return_if_fail (GTK_IS_TREE_MODEL(source_model));
1097         g_return_if_fail (GTK_IS_TREE_MODEL(dest_model));
1098         g_return_if_fail (dest_row);
1099         g_return_if_fail (helper);
1100         
1101         /* Get header */
1102         gtk_tree_model_get_iter (source_model, &source_iter, helper->source_row);
1103         gtk_tree_model_get (source_model, &source_iter, 
1104                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, 
1105                             &header, -1);
1106         if (!TNY_IS_HEADER(header)) {
1107                 g_warning ("BUG: %s could not get a valid header", __FUNCTION__);
1108                 goto cleanup;
1109         }
1110         
1111         /* Get Folder */
1112         gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
1113         gtk_tree_model_get (dest_model, &dest_iter, 
1114                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, 
1115                             &folder, -1);
1116
1117         if (!TNY_IS_FOLDER(folder)) {
1118                 g_warning ("BUG: %s could not get a valid folder", __FUNCTION__);
1119                 goto cleanup;
1120         }
1121
1122         /* Transfer message */
1123         mail_op = modest_mail_operation_new (MODEST_MAIL_OPERATION_TYPE_RECEIVE, NULL);
1124         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1125                                          mail_op);
1126         g_signal_connect (G_OBJECT (mail_op), "progress-changed",
1127                           G_CALLBACK (on_progress_changed), helper);
1128
1129         headers = tny_simple_list_new ();
1130         tny_list_append (headers, G_OBJECT (header));
1131         modest_mail_operation_xfer_msgs (mail_op, 
1132                                          headers, 
1133                                          folder, 
1134                                          helper->delete_source, 
1135                                          NULL, NULL);
1136         
1137         /* Frees */
1138 cleanup:
1139         if (G_IS_OBJECT(mail_op))
1140                 g_object_unref (G_OBJECT (mail_op));
1141         if (G_IS_OBJECT(header))
1142                 g_object_unref (G_OBJECT (header));
1143         if (G_IS_OBJECT(folder))
1144                 g_object_unref (G_OBJECT (folder));
1145         if (G_IS_OBJECT(headers))
1146                 g_object_unref (headers);
1147 }
1148
1149 /*
1150  * This function is used by drag_data_received_cb to manage drag and
1151  * drop of a folder, i.e, and drag from the folder view to the same
1152  * folder view.
1153  */
1154 static void
1155 drag_and_drop_from_folder_view (GtkTreeModel     *source_model,
1156                                 GtkTreeModel     *dest_model,
1157                                 GtkTreePath      *dest_row,
1158                                 GtkSelectionData *selection_data,
1159                                 DndHelper        *helper)
1160 {
1161         ModestMailOperation *mail_op;
1162         GtkTreeIter parent_iter, iter;
1163         TnyFolderStore *parent_folder;
1164         TnyFolder *folder;
1165
1166         /* Check if the drag is possible */
1167 /*      if (!gtk_tree_path_compare (helper->source_row, dest_row) || */
1168 /*          !gtk_tree_drag_dest_row_drop_possible (GTK_TREE_DRAG_DEST (dest_model), */
1169 /*                                                 dest_row, */
1170 /*                                                 selection_data)) { */
1171         if (!gtk_tree_path_compare (helper->source_row, dest_row)) {
1172
1173                 gtk_drag_finish (helper->context, FALSE, FALSE, helper->time);
1174                 gtk_tree_path_free (helper->source_row);        
1175                 g_slice_free (DndHelper, helper);
1176                 return;
1177         }
1178
1179         /* Get data */
1180         gtk_tree_model_get_iter (source_model, &parent_iter, dest_row);
1181         gtk_tree_model_get_iter (source_model, &iter, helper->source_row);
1182         gtk_tree_model_get (source_model, &parent_iter, 
1183                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, 
1184                             &parent_folder, -1);
1185         gtk_tree_model_get (source_model, &iter,
1186                             TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
1187                             &folder, -1);
1188
1189         /* Do the mail operation */
1190         mail_op = modest_mail_operation_new_with_error_handling (MODEST_MAIL_OPERATION_TYPE_RECEIVE, 
1191                                                                  NULL,
1192                                                                  modest_ui_actions_move_folder_error_handler,
1193                                                                  NULL);
1194         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (), 
1195                                          mail_op);
1196         g_signal_connect (G_OBJECT (mail_op), "progress-changed",
1197                           G_CALLBACK (on_progress_changed), helper);
1198
1199         modest_mail_operation_xfer_folder (mail_op, 
1200                                            folder, 
1201                                            parent_folder,
1202                                            helper->delete_source);
1203         
1204         /* Frees */
1205         g_object_unref (G_OBJECT (parent_folder));
1206         g_object_unref (G_OBJECT (folder));
1207         g_object_unref (G_OBJECT (mail_op));
1208 }
1209
1210 /*
1211  * This function receives the data set by the "drag-data-get" signal
1212  * handler. This information comes within the #GtkSelectionData. This
1213  * function will manage both the drags of folders of the treeview and
1214  * drags of headers of the header view widget.
1215  */
1216 static void 
1217 on_drag_data_received (GtkWidget *widget, 
1218                        GdkDragContext *context, 
1219                        gint x, 
1220                        gint y, 
1221                        GtkSelectionData *selection_data, 
1222                        guint target_type, 
1223                        guint time, 
1224                        gpointer data)
1225 {
1226         GtkWidget *source_widget;
1227         GtkTreeModel *dest_model, *source_model;
1228         GtkTreePath *source_row, *dest_row;
1229         GtkTreeViewDropPosition pos;
1230         gboolean success = FALSE, delete_source = FALSE;
1231         DndHelper *helper = NULL; 
1232
1233         /* Do not allow further process */
1234         g_signal_stop_emission_by_name (widget, "drag-data-received");
1235         source_widget = gtk_drag_get_source_widget (context);
1236
1237         /* Get the action */
1238         if (context->action == GDK_ACTION_MOVE) {
1239                 delete_source = TRUE;
1240
1241                 /* Notify that there is no folder selected. We need to
1242                    do this in order to update the headers view (and
1243                    its monitors, because when moving, the old folder
1244                    won't longer exist. We can not wait for the end of
1245                    the operation, because the operation won't start if
1246                    the folder is in use */
1247                 if (source_widget == widget)
1248                         g_signal_emit (G_OBJECT (widget), 
1249                                        signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0, NULL, TRUE);
1250         }
1251
1252         /* Check if the get_data failed */
1253         if (selection_data == NULL || selection_data->length < 0)
1254                 gtk_drag_finish (context, success, FALSE, time);
1255
1256         /* Get the models */
1257         gtk_tree_get_row_drag_data (selection_data,
1258                                     &source_model,
1259                                     &source_row);
1260
1261         /* Select the destination model */
1262         if (source_widget == widget) {
1263                 dest_model = source_model;
1264         } else {
1265                 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
1266         }
1267
1268         /* Get the path to the destination row. Can not call
1269            gtk_tree_view_get_drag_dest_row() because the source row
1270            is not selected anymore */
1271         gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget), x, y,
1272                                            &dest_row, &pos);
1273
1274         /* Only allow drops IN other rows */
1275         if (!dest_row || pos == GTK_TREE_VIEW_DROP_BEFORE || pos == GTK_TREE_VIEW_DROP_AFTER)
1276                 gtk_drag_finish (context, success, FALSE, time);
1277
1278         /* Create the helper */
1279         helper = g_slice_new0 (DndHelper);
1280         helper->delete_source = delete_source;
1281         helper->source_row = gtk_tree_path_copy (source_row);
1282         helper->context = context;
1283         helper->time = time;
1284
1285         /* Drags from the header view */
1286         if (source_widget != widget) {
1287
1288                 drag_and_drop_from_header_view (source_model,
1289                                                 dest_model,
1290                                                 dest_row,
1291                                                 helper);
1292         } else {
1293
1294
1295                 drag_and_drop_from_folder_view (source_model,
1296                                                 dest_model,
1297                                                 dest_row,
1298                                                 selection_data, 
1299                                                 helper);
1300         }
1301
1302         /* Frees */
1303         gtk_tree_path_free (source_row);
1304         gtk_tree_path_free (dest_row);
1305 }
1306
1307 /*
1308  * We define a "drag-drop" signal handler because we do not want to
1309  * use the default one, because the default one always calls
1310  * gtk_drag_finish and we prefer to do it in the "drag-data-received"
1311  * signal handler, because there we have all the information available
1312  * to know if the dnd was a success or not.
1313  */
1314 static gboolean
1315 drag_drop_cb (GtkWidget      *widget,
1316               GdkDragContext *context,
1317               gint            x,
1318               gint            y,
1319               guint           time,
1320               gpointer        user_data) 
1321 {
1322         gpointer target;
1323
1324         if (!context->targets)
1325                 return FALSE;
1326
1327         /* Check if we're dragging a folder row */
1328         target = gtk_drag_dest_find_target (widget, context, NULL);
1329
1330         /* Request the data from the source. */
1331         gtk_drag_get_data(widget, context, target, time);
1332
1333     return TRUE;
1334 }
1335
1336 /*
1337  * This function expands a node of a tree view if it's not expanded
1338  * yet. Not sure why it needs the threads stuff, but gtk+`example code
1339  * does that, so that's why they're here.
1340  */
1341 static gint
1342 expand_row_timeout (gpointer data)
1343 {
1344         GtkTreeView *tree_view = data;
1345         GtkTreePath *dest_path = NULL;
1346         GtkTreeViewDropPosition pos;
1347         gboolean result = FALSE;
1348         
1349         GDK_THREADS_ENTER ();
1350         
1351         gtk_tree_view_get_drag_dest_row (tree_view,
1352                                          &dest_path,
1353                                          &pos);
1354         
1355         if (dest_path &&
1356             (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER ||
1357              pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)) {
1358                 gtk_tree_view_expand_row (tree_view, dest_path, FALSE);
1359                 gtk_tree_path_free (dest_path);
1360         }
1361         else {
1362                 if (dest_path)
1363                         gtk_tree_path_free (dest_path);
1364                 
1365                 result = TRUE;
1366         }
1367         
1368         GDK_THREADS_LEAVE ();
1369
1370         return result;
1371 }
1372
1373 /*
1374  * This function is called whenever the pointer is moved over a widget
1375  * while dragging some data. It installs a timeout that will expand a
1376  * node of the treeview if not expanded yet. This function also calls
1377  * gdk_drag_status in order to set the suggested action that will be
1378  * used by the "drag-data-received" signal handler to know if we
1379  * should do a move or just a copy of the data.
1380  */
1381 static gboolean
1382 on_drag_motion (GtkWidget      *widget,
1383                 GdkDragContext *context,
1384                 gint            x,
1385                 gint            y,
1386                 guint           time,
1387                 gpointer        user_data)  
1388 {
1389         GtkTreeViewDropPosition pos;
1390         GtkTreePath *dest_row;
1391         ModestFolderViewPrivate *priv;
1392         GdkDragAction suggested_action;
1393         gboolean valid_location = FALSE;
1394
1395         priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
1396
1397         if (priv->timer_expander != 0) {
1398                 g_source_remove (priv->timer_expander);
1399                 priv->timer_expander = 0;
1400         }
1401
1402         gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
1403                                            x, y,
1404                                            &dest_row,
1405                                            &pos);
1406
1407         /* Do not allow drops between folders */
1408         if (!dest_row ||
1409             pos == GTK_TREE_VIEW_DROP_BEFORE ||
1410             pos == GTK_TREE_VIEW_DROP_AFTER) {
1411                 gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW (widget), NULL, 0);
1412                 gdk_drag_status(context, 0, time);
1413                 valid_location = FALSE;
1414                 goto out;
1415         } else {
1416                 valid_location = TRUE;
1417         }
1418
1419         /* Expand the selected row after 1/2 second */
1420         if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), dest_row)) {
1421                 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), dest_row, pos);
1422                 priv->timer_expander = g_timeout_add (500, expand_row_timeout, widget);
1423         }
1424
1425         /* Select the desired action. By default we pick MOVE */
1426         suggested_action = GDK_ACTION_MOVE;
1427
1428         if (context->actions == GDK_ACTION_COPY)
1429             gdk_drag_status(context, GDK_ACTION_COPY, time);
1430         else if (context->actions == GDK_ACTION_MOVE)
1431             gdk_drag_status(context, GDK_ACTION_MOVE, time);
1432         else if (context->actions & suggested_action)
1433             gdk_drag_status(context, suggested_action, time);
1434         else
1435             gdk_drag_status(context, GDK_ACTION_DEFAULT, time);
1436
1437  out:
1438         if (dest_row)
1439                 gtk_tree_path_free (dest_row);
1440         g_signal_stop_emission_by_name (widget, "drag-motion");
1441         return valid_location;
1442 }
1443
1444
1445 /* Folder view drag types */
1446 const GtkTargetEntry folder_view_drag_types[] =
1447 {
1448         { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, MODEST_FOLDER_ROW },
1449         { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_APP,    MODEST_HEADER_ROW }
1450 };
1451
1452 /*
1453  * This function sets the treeview as a source and a target for dnd
1454  * events. It also connects all the requirede signals.
1455  */
1456 static void
1457 setup_drag_and_drop (GtkTreeView *self)
1458 {
1459         /* Set up the folder view as a dnd destination. Set only the
1460            highlight flag, otherwise gtk will have a different
1461            behaviour */
1462         gtk_drag_dest_set (GTK_WIDGET (self),
1463                            GTK_DEST_DEFAULT_HIGHLIGHT,
1464                            folder_view_drag_types,
1465                            G_N_ELEMENTS (folder_view_drag_types),
1466                            GDK_ACTION_MOVE | GDK_ACTION_COPY);
1467
1468         g_signal_connect (G_OBJECT (self),
1469                           "drag_data_received",
1470                           G_CALLBACK (on_drag_data_received),
1471                           NULL);
1472
1473
1474         /* Set up the treeview as a dnd source */
1475         gtk_drag_source_set (GTK_WIDGET (self),
1476                              GDK_BUTTON1_MASK,
1477                              folder_view_drag_types,
1478                              G_N_ELEMENTS (folder_view_drag_types),
1479                              GDK_ACTION_MOVE | GDK_ACTION_COPY);
1480
1481         g_signal_connect (G_OBJECT (self),
1482                           "drag_motion",
1483                           G_CALLBACK (on_drag_motion),
1484                           NULL);
1485         
1486         g_signal_connect (G_OBJECT (self),
1487                           "drag_data_get",
1488                           G_CALLBACK (on_drag_data_get),
1489                           NULL);
1490
1491         g_signal_connect (G_OBJECT (self),
1492                           "drag_drop",
1493                           G_CALLBACK (drag_drop_cb),
1494                           NULL);
1495 }
1496
1497 /*
1498  * This function manages the navigation through the folders using the
1499  * keyboard or the hardware keys in the device
1500  */
1501 static gboolean
1502 on_key_pressed (GtkWidget *self,
1503                 GdkEventKey *event,
1504                 gpointer user_data)
1505 {
1506         GtkTreeSelection *selection;
1507         GtkTreeIter iter;
1508         GtkTreeModel *model;
1509         gboolean retval = FALSE;
1510
1511         /* Up and Down are automatically managed by the treeview */
1512         if (event->keyval == GDK_Return) {
1513                 /* Expand/Collapse the selected row */
1514                 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1515                 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
1516                         GtkTreePath *path;
1517
1518                         path = gtk_tree_model_get_path (model, &iter);
1519
1520                         if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (self), path))
1521                                 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1522                         else
1523                                 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1524                         gtk_tree_path_free (path);
1525                 }
1526                 /* No further processing */
1527                 retval = TRUE;
1528         }
1529
1530         return retval;
1531 }
1532
1533 /*
1534  * We listen to the changes in the local folder account name key,
1535  * because we want to show the right name in the view. The local
1536  * folder account name corresponds to the device name in the Maemo
1537  * version. We do this because we do not want to query gconf on each
1538  * tree view refresh. It's better to cache it and change whenever
1539  * necessary.
1540  */
1541 static void 
1542 on_configuration_key_changed (ModestConf* conf, 
1543                               const gchar *key, 
1544                               ModestConfEvent event, 
1545                               ModestFolderView *self)
1546 {
1547         ModestFolderViewPrivate *priv;
1548
1549         if (!key)
1550                 return;
1551
1552         g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
1553         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1554
1555         if (!strcmp (key, MODEST_CONF_DEVICE_NAME)) {
1556                 g_free (priv->local_account_name);
1557
1558                 if (event == MODEST_CONF_EVENT_KEY_UNSET)
1559                         priv->local_account_name = g_strdup (MODEST_LOCAL_FOLDERS_DEFAULT_DISPLAY_NAME);
1560                 else
1561                         priv->local_account_name = modest_conf_get_string (modest_runtime_get_conf(),
1562                                                                            MODEST_CONF_DEVICE_NAME, NULL);
1563
1564                 /* Force a redraw */
1565 #if GTK_CHECK_VERSION(2, 8, 0) /* gtk_tree_view_column_queue_resize is only available in GTK+ 2.8 */
1566                 GtkTreeViewColumn * tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self), 
1567                                                                             TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN);
1568                 gtk_tree_view_column_queue_resize (tree_column);
1569 #endif
1570         }
1571 }
1572
1573 void 
1574 modest_folder_view_set_style (ModestFolderView *self,
1575                               ModestFolderViewStyle style)
1576 {
1577         ModestFolderViewPrivate *priv;
1578
1579         g_return_if_fail (self);
1580         
1581         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1582
1583         priv->style = style;
1584 }
1585
1586 void
1587 modest_folder_view_set_account_id_of_visible_server_account (ModestFolderView *self,
1588                                                              const gchar *account_id)
1589 {
1590         ModestFolderViewPrivate *priv;
1591         GtkTreeModel *model;
1592
1593         g_return_if_fail (self);
1594         
1595         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1596
1597         /* This will be used by the filter_row callback,
1598          * to decided which rows to show: */
1599         if (priv->visible_account_id) {
1600                 g_free (priv->visible_account_id);
1601                 priv->visible_account_id = NULL;
1602         }
1603         if (account_id)
1604                 priv->visible_account_id = g_strdup (account_id);
1605
1606         /* Refilter */
1607         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1608         if (GTK_IS_TREE_MODEL_FILTER (model))
1609                 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
1610
1611         /* Save settings to gconf */
1612         modest_widget_memory_save (modest_runtime_get_conf (), G_OBJECT(self),
1613                                    MODEST_CONF_FOLDER_VIEW_KEY);
1614 }
1615
1616 const gchar *
1617 modest_folder_view_get_account_id_of_visible_server_account (ModestFolderView *self)
1618 {
1619         ModestFolderViewPrivate *priv;
1620
1621         g_return_val_if_fail (self, NULL);
1622         
1623         priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1624
1625         return (const gchar *) priv->visible_account_id;
1626 }
1627
1628 static gboolean
1629 find_inbox_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *inbox_iter)
1630 {
1631         do {
1632                 GtkTreeIter child;
1633                 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1634                 gchar *name = NULL;
1635
1636                 gtk_tree_model_get (model, iter, 
1637                                         TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name,
1638                                     TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, 
1639                                     &type, -1);
1640
1641                 /*
1642                 printf ("DEBUG: %s: name=%s, type=%d, TNY_FOLDER_TYPE_INBOX=%d\n", 
1643                         __FUNCTION__, name, type, TNY_FOLDER_TYPE_INBOX);
1644                 */
1645                         
1646                 gboolean result = FALSE;
1647                 if (type == TNY_FOLDER_TYPE_INBOX) {
1648                         result = TRUE;
1649                 } else if (type == TNY_FOLDER_TYPE_NORMAL) {
1650                         /* tinymail's camel implementation only provides TNY_FOLDER_TYPE_NORMAL
1651                          * when getting folders from the cache, before connectin, so we do 
1652                          * an extra check. We could fix this in tinymail, but it's easier 
1653                          * to do here.
1654                          */
1655                          if (strcmp (name, "Inbox") == 0)
1656                                 result = TRUE;
1657                 }
1658                 
1659                 g_free (name);
1660                 
1661                 if (result) {
1662                         *inbox_iter = *iter;
1663                         return TRUE;
1664                 }
1665
1666                 if (gtk_tree_model_iter_children (model, &child, iter)) {
1667                         if (find_inbox_iter (model, &child, inbox_iter))
1668                                 return TRUE;
1669                 }
1670
1671         } while (gtk_tree_model_iter_next (model, iter));
1672
1673         return FALSE;
1674 }
1675
1676 void 
1677 modest_folder_view_select_first_inbox_or_local (ModestFolderView *self)
1678 {
1679         GtkTreeModel *model;
1680         GtkTreeIter iter, inbox_iter;
1681         GtkTreeSelection *sel;
1682
1683         /* Do not set it if the folder view was not painted */
1684         if (!GTK_WIDGET_MAPPED (self))
1685                 return; 
1686
1687         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1688         if (!model)
1689                 return;
1690
1691         expand_root_items (self);
1692         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1693
1694         gtk_tree_model_get_iter_first (model, &iter);
1695         if (find_inbox_iter (model, &iter, &inbox_iter)) {
1696                 gtk_tree_selection_select_iter (sel, &inbox_iter);
1697         }
1698         else {
1699                 gtk_tree_model_get_iter_first (model, &iter);
1700                 gtk_tree_selection_select_iter (sel, &iter);
1701         }
1702 }