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