1 /* Copyright (c) 2006, Nokia Corporation
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
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.
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.
30 #include <glib/gi18n.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>
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);
63 static void tny_account_store_view_init (gpointer g,
66 static void modest_folder_view_set_account_store (TnyAccountStoreView *self,
67 TnyAccountStore *account_store);
69 static void on_selection_changed (GtkTreeSelection *sel, gpointer data);
71 static void on_account_update (TnyAccountStore *account_store,
75 static void on_account_removed (TnyAccountStore *self,
79 static void on_accounts_reloaded (TnyAccountStore *store,
82 static gint cmp_rows (GtkTreeModel *tree_model,
87 static gboolean filter_row (GtkTreeModel *model,
91 static gboolean on_key_pressed (GtkWidget *self,
95 static void on_configuration_key_changed (ModestConf* conf,
97 ModestConfEvent event,
98 ModestFolderView *self);
101 static void on_drag_data_get (GtkWidget *widget,
102 GdkDragContext *context,
103 GtkSelectionData *selection_data,
108 static void on_drag_data_received (GtkWidget *widget,
109 GdkDragContext *context,
112 GtkSelectionData *selection_data,
117 static gboolean on_drag_motion (GtkWidget *widget,
118 GdkDragContext *context,
124 static gint expand_row_timeout (gpointer data);
126 static void setup_drag_and_drop (GtkTreeView *self);
128 static void _clipboard_set_selected_data (ModestFolderView *folder_view, gboolean delete);
130 static void _clear_hidding_filter (ModestFolderView *folder_view);
135 FOLDER_SELECTION_CHANGED_SIGNAL,
136 FOLDER_DISPLAY_NAME_CHANGED_SIGNAL,
140 typedef struct _ModestFolderViewPrivate ModestFolderViewPrivate;
141 struct _ModestFolderViewPrivate {
142 TnyAccountStore *account_store;
143 TnyFolderStore *cur_folder_store;
145 gulong account_update_signal;
146 gulong changed_signal;
147 gulong accounts_reloaded_signal;
148 gulong account_removed_signal;
149 gulong conf_key_signal;
151 /* not unref this object, its a singlenton */
152 ModestEmailClipboard *clipboard;
154 /* Filter tree model */
158 TnyFolderStoreQuery *query;
159 guint timer_expander;
161 gchar *local_account_name;
162 gchar *visible_account_id;
163 ModestFolderViewStyle style;
165 gboolean reselect; /* we use this to force a reselection of the INBOX */
167 #define MODEST_FOLDER_VIEW_GET_PRIVATE(o) \
168 (G_TYPE_INSTANCE_GET_PRIVATE((o), \
169 MODEST_TYPE_FOLDER_VIEW, \
170 ModestFolderViewPrivate))
172 static GObjectClass *parent_class = NULL;
174 static guint signals[LAST_SIGNAL] = {0};
177 modest_folder_view_get_type (void)
179 static GType my_type = 0;
181 static const GTypeInfo my_info = {
182 sizeof(ModestFolderViewClass),
183 NULL, /* base init */
184 NULL, /* base finalize */
185 (GClassInitFunc) modest_folder_view_class_init,
186 NULL, /* class finalize */
187 NULL, /* class data */
188 sizeof(ModestFolderView),
190 (GInstanceInitFunc) modest_folder_view_init,
194 static const GInterfaceInfo tny_account_store_view_info = {
195 (GInterfaceInitFunc) tny_account_store_view_init, /* interface_init */
196 NULL, /* interface_finalize */
197 NULL /* interface_data */
201 my_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
205 g_type_add_interface_static (my_type,
206 TNY_TYPE_ACCOUNT_STORE_VIEW,
207 &tny_account_store_view_info);
213 modest_folder_view_class_init (ModestFolderViewClass *klass)
215 GObjectClass *gobject_class;
216 gobject_class = (GObjectClass*) klass;
218 parent_class = g_type_class_peek_parent (klass);
219 gobject_class->finalize = modest_folder_view_finalize;
221 g_type_class_add_private (gobject_class,
222 sizeof(ModestFolderViewPrivate));
224 signals[FOLDER_SELECTION_CHANGED_SIGNAL] =
225 g_signal_new ("folder_selection_changed",
226 G_TYPE_FROM_CLASS (gobject_class),
228 G_STRUCT_OFFSET (ModestFolderViewClass,
229 folder_selection_changed),
231 modest_marshal_VOID__POINTER_BOOLEAN,
232 G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_BOOLEAN);
235 * This signal is emitted whenever the currently selected
236 * folder display name is computed. Note that the name could
237 * be different to the folder name, because we could append
238 * the unread messages count to the folder name to build the
239 * folder display name
241 signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL] =
242 g_signal_new ("folder-display-name-changed",
243 G_TYPE_FROM_CLASS (gobject_class),
245 G_STRUCT_OFFSET (ModestFolderViewClass,
246 folder_display_name_changed),
248 g_cclosure_marshal_VOID__STRING,
249 G_TYPE_NONE, 1, G_TYPE_STRING);
253 text_cell_data (GtkTreeViewColumn *column, GtkCellRenderer *renderer,
254 GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
256 ModestFolderViewPrivate *priv;
261 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
262 GObject *instance = NULL;
264 g_return_if_fail (column);
265 g_return_if_fail (tree_model);
267 gtk_tree_model_get (tree_model, iter,
268 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &fname,
269 TNY_GTK_FOLDER_STORE_TREE_MODEL_ALL_COLUMN, &all,
270 TNY_GTK_FOLDER_STORE_TREE_MODEL_UNREAD_COLUMN, &unread,
271 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
272 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
274 rendobj = G_OBJECT(renderer);
285 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (data);
287 gchar *item_name = NULL;
288 gint item_weight = 400;
290 if (type != TNY_FOLDER_TYPE_ROOT) {
293 if (modest_tny_folder_is_local_folder (TNY_FOLDER (instance))) {
294 type = modest_tny_folder_get_local_folder_type (TNY_FOLDER (instance));
295 if (type != TNY_FOLDER_TYPE_UNKNOWN) {
297 fname = g_strdup(modest_local_folder_info_get_type_display_name (type));
301 /* Select the number to show: the unread or unsent messages */
302 if ((type == TNY_FOLDER_TYPE_DRAFTS) || (type == TNY_FOLDER_TYPE_OUTBOX))
307 /* Use bold font style if there are unread or unset messages */
309 item_name = g_strdup_printf ("%s (%d)", fname, number);
312 item_name = g_strdup (fname);
316 } else if (TNY_IS_ACCOUNT (instance)) {
317 /* If it's a server account */
318 if (modest_tny_account_is_virtual_local_folders (
319 TNY_ACCOUNT (instance))) {
320 item_name = g_strdup (priv->local_account_name);
323 item_name = g_strdup (fname);
329 item_name = g_strdup ("unknown");
331 if (item_name && item_weight) {
332 /* Set the name in the treeview cell: */
333 g_object_set (rendobj,"text", item_name, "weight", item_weight, NULL);
335 /* Notify display name observers */
336 if (G_OBJECT (priv->cur_folder_store) == instance) {
337 g_signal_emit (G_OBJECT(data),
338 signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
346 g_object_unref (G_OBJECT (instance));
353 icon_cell_data (GtkTreeViewColumn *column, GtkCellRenderer *renderer,
354 GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
356 GObject *rendobj = NULL, *instance = NULL;
357 GdkPixbuf *pixbuf = NULL;
358 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
360 const gchar *account_id = NULL;
363 rendobj = G_OBJECT(renderer);
364 gtk_tree_model_get (tree_model, iter,
365 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
366 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &fname,
367 TNY_GTK_FOLDER_STORE_TREE_MODEL_UNREAD_COLUMN, &unread,
368 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
379 /* We include the MERGE type here because it's used to create
380 the local OUTBOX folder */
381 if (type == TNY_FOLDER_TYPE_NORMAL ||
382 type == TNY_FOLDER_TYPE_UNKNOWN ||
383 type == TNY_FOLDER_TYPE_MERGE) {
384 type = modest_tny_folder_guess_folder_type (TNY_FOLDER (instance));
388 case TNY_FOLDER_TYPE_ROOT:
389 if (TNY_IS_ACCOUNT (instance)) {
391 if (modest_tny_account_is_virtual_local_folders (
392 TNY_ACCOUNT (instance))) {
393 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_LOCAL_FOLDERS);
396 account_id = tny_account_get_id (TNY_ACCOUNT (instance));
398 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
399 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_MMC);
401 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_ACCOUNT);
405 case TNY_FOLDER_TYPE_INBOX:
406 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_INBOX);
408 case TNY_FOLDER_TYPE_OUTBOX:
409 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_OUTBOX);
411 case TNY_FOLDER_TYPE_JUNK:
412 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_JUNK);
414 case TNY_FOLDER_TYPE_SENT:
415 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_SENT);
417 case TNY_FOLDER_TYPE_TRASH:
418 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_TRASH);
420 case TNY_FOLDER_TYPE_DRAFTS:
421 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_DRAFTS);
423 case TNY_FOLDER_TYPE_NORMAL:
425 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_NORMAL);
429 g_object_unref (G_OBJECT (instance));
433 g_object_set (rendobj, "pixbuf", pixbuf, NULL);
436 g_object_unref (pixbuf);
440 add_columns (GtkWidget *treeview)
442 GtkTreeViewColumn *column;
443 GtkCellRenderer *renderer;
444 GtkTreeSelection *sel;
447 column = gtk_tree_view_column_new ();
449 /* Set icon and text render function */
450 renderer = gtk_cell_renderer_pixbuf_new();
451 gtk_tree_view_column_pack_start (column, renderer, FALSE);
452 gtk_tree_view_column_set_cell_data_func(column, renderer,
453 icon_cell_data, treeview, NULL);
455 renderer = gtk_cell_renderer_text_new();
456 gtk_tree_view_column_pack_start (column, renderer, FALSE);
457 gtk_tree_view_column_set_cell_data_func(column, renderer,
458 text_cell_data, treeview, NULL);
460 /* Set selection mode */
461 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(treeview));
462 gtk_tree_selection_set_mode (sel, GTK_SELECTION_SINGLE);
464 /* Set treeview appearance */
465 gtk_tree_view_column_set_spacing (column, 2);
466 gtk_tree_view_column_set_resizable (column, TRUE);
467 gtk_tree_view_column_set_fixed_width (column, TRUE);
468 gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(treeview), FALSE);
469 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(treeview), FALSE);
472 gtk_tree_view_append_column (GTK_TREE_VIEW(treeview),column);
476 modest_folder_view_init (ModestFolderView *obj)
478 ModestFolderViewPrivate *priv;
481 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
483 priv->timer_expander = 0;
484 priv->account_store = NULL;
486 priv->style = MODEST_FOLDER_VIEW_STYLE_SHOW_ALL;
487 priv->cur_folder_store = NULL;
488 priv->visible_account_id = NULL;
490 /* Initialize the local account name */
491 conf = modest_runtime_get_conf();
492 priv->local_account_name = modest_conf_get_string (conf, MODEST_CONF_DEVICE_NAME, NULL);
494 /* Init email clipboard */
495 priv->clipboard = modest_runtime_get_email_clipboard ();
496 priv->hidding_ids = NULL;
497 priv->n_selected = 0;
498 priv->reselect = FALSE;
501 add_columns (GTK_WIDGET (obj));
503 /* Setup drag and drop */
504 setup_drag_and_drop (GTK_TREE_VIEW(obj));
506 /* Connect signals */
507 g_signal_connect (G_OBJECT (obj),
509 G_CALLBACK (on_key_pressed), NULL);
512 * Track changes in the local account name (in the device it
513 * will be the device name)
515 priv->conf_key_signal =
516 g_signal_connect (G_OBJECT(conf),
518 G_CALLBACK(on_configuration_key_changed), obj);
522 tny_account_store_view_init (gpointer g, gpointer iface_data)
524 TnyAccountStoreViewIface *klass = (TnyAccountStoreViewIface *)g;
526 klass->set_account_store_func = modest_folder_view_set_account_store;
532 modest_folder_view_finalize (GObject *obj)
534 ModestFolderViewPrivate *priv;
535 GtkTreeSelection *sel;
537 g_return_if_fail (obj);
539 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
541 if (priv->timer_expander != 0) {
542 g_source_remove (priv->timer_expander);
543 priv->timer_expander = 0;
546 if (priv->account_store) {
547 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
548 priv->account_update_signal);
549 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
550 priv->accounts_reloaded_signal);
551 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
552 priv->account_removed_signal);
553 g_object_unref (G_OBJECT(priv->account_store));
554 priv->account_store = NULL;
558 g_object_unref (G_OBJECT (priv->query));
562 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(obj));
564 g_signal_handler_disconnect (G_OBJECT(sel), priv->changed_signal);
566 g_free (priv->local_account_name);
567 g_free (priv->visible_account_id);
569 if (priv->conf_key_signal) {
570 g_signal_handler_disconnect (modest_runtime_get_conf (),
571 priv->conf_key_signal);
572 priv->conf_key_signal = 0;
575 if (priv->cur_folder_store) {
576 if (TNY_IS_FOLDER(priv->cur_folder_store))
577 tny_folder_sync (TNY_FOLDER(priv->cur_folder_store), TRUE, NULL);
578 /* expunge the message */
580 g_object_unref (priv->cur_folder_store);
581 priv->cur_folder_store = NULL;
584 /* Clear hidding array created by cut operation */
585 _clear_hidding_filter (MODEST_FOLDER_VIEW (obj));
587 G_OBJECT_CLASS(parent_class)->finalize (obj);
592 modest_folder_view_set_account_store (TnyAccountStoreView *self, TnyAccountStore *account_store)
594 ModestFolderViewPrivate *priv;
597 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
598 g_return_if_fail (TNY_IS_ACCOUNT_STORE (account_store));
600 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
601 device = tny_account_store_get_device (account_store);
603 if (G_UNLIKELY (priv->account_store)) {
605 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
606 priv->account_update_signal))
607 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
608 priv->account_update_signal);
609 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
610 priv->accounts_reloaded_signal))
611 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
612 priv->accounts_reloaded_signal);
613 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
614 priv->account_removed_signal))
615 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
616 priv->account_removed_signal);
618 g_object_unref (G_OBJECT (priv->account_store));
621 priv->account_store = g_object_ref (G_OBJECT (account_store));
623 priv->account_update_signal =
624 g_signal_connect (G_OBJECT(account_store), "account_update",
625 G_CALLBACK (on_account_update), self);
627 priv->account_removed_signal =
628 g_signal_connect (G_OBJECT(account_store), "account_removed",
629 G_CALLBACK (on_account_removed), self);
631 priv->accounts_reloaded_signal =
632 g_signal_connect (G_OBJECT(account_store), "accounts_reloaded",
633 G_CALLBACK (on_accounts_reloaded), self);
635 g_signal_connect (G_OBJECT(account_store), "connecting_finished",
636 G_CALLBACK (on_accounts_reloaded), self);
638 on_accounts_reloaded (account_store, (gpointer ) self);
640 g_object_unref (G_OBJECT (device));
644 on_account_removed (TnyAccountStore *account_store,
648 ModestFolderView *self = MODEST_FOLDER_VIEW (user_data);
649 ModestFolderViewPrivate *priv;
651 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
653 /* If the removed account is the currently viewed one then
654 clear the configuration value. The new visible account will be the default account */
655 if (!strcmp (priv->visible_account_id, tny_account_get_id (account))) {
656 modest_folder_view_set_account_id_of_visible_server_account (self, NULL);
661 on_account_update (TnyAccountStore *account_store,
662 const gchar *account,
665 ModestFolderView *self = MODEST_FOLDER_VIEW (user_data);
666 ModestFolderViewPrivate *priv;
668 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
669 if (!priv->visible_account_id)
670 modest_widget_memory_restore (modest_runtime_get_conf(), G_OBJECT(self),
671 MODEST_CONF_FOLDER_VIEW_KEY);
673 if (!modest_folder_view_update_model (self, account_store))
674 g_printerr ("modest: failed to update model for changes in '%s'",
679 on_accounts_reloaded (TnyAccountStore *account_store,
682 modest_folder_view_update_model (MODEST_FOLDER_VIEW (user_data), account_store);
686 modest_folder_view_set_title (ModestFolderView *self, const gchar *title)
688 GtkTreeViewColumn *col;
690 g_return_if_fail (self);
692 col = gtk_tree_view_get_column (GTK_TREE_VIEW(self), 0);
694 g_printerr ("modest: failed get column for title\n");
698 gtk_tree_view_column_set_title (col, title);
699 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self),
704 modest_folder_view_on_map (ModestFolderView *self,
705 GdkEventExpose *event,
708 ModestFolderViewPrivate *priv;
710 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
712 /* This won't happen often */
713 if (G_UNLIKELY (priv->reselect)) {
714 /* Select the first inbox or the local account if not found */
715 modest_folder_view_select_first_inbox_or_local (self);
716 priv->reselect = FALSE;
722 modest_folder_view_new (TnyFolderStoreQuery *query)
725 ModestFolderViewPrivate *priv;
726 GtkTreeSelection *sel;
728 self = G_OBJECT (g_object_new (MODEST_TYPE_FOLDER_VIEW, NULL));
729 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
732 priv->query = g_object_ref (query);
734 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
735 priv->changed_signal = g_signal_connect (sel, "changed",
736 G_CALLBACK (on_selection_changed), self);
738 g_signal_connect (self, "expose-event", G_CALLBACK (modest_folder_view_on_map), NULL);
740 return GTK_WIDGET(self);
743 /* this feels dirty; any other way to expand all the root items? */
745 expand_root_items (ModestFolderView *self)
748 path = gtk_tree_path_new_first ();
750 /* all folders should have child items, so.. */
751 while (gtk_tree_view_expand_row (GTK_TREE_VIEW(self), path, FALSE))
752 gtk_tree_path_next (path);
754 gtk_tree_path_free (path);
758 * We use this function to implement the
759 * MODEST_FOLDER_VIEW_STYLE_SHOW_ONE style. We only show the default
760 * account in this case, and the local folders.
763 filter_row (GtkTreeModel *model,
767 ModestFolderViewPrivate *priv;
768 gboolean retval = TRUE;
769 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
770 GObject *instance = NULL;
771 const gchar *id = NULL;
773 gboolean found = FALSE;
774 gboolean cleared = FALSE;
776 g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (data), FALSE);
777 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (data);
779 gtk_tree_model_get (model, iter,
780 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
781 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
784 /* Do not show if there is no instance, this could indeed
785 happen when the model is being modified while it's being
786 drawn. This could occur for example when moving folders
791 if (type == TNY_FOLDER_TYPE_ROOT) {
792 /* TNY_FOLDER_TYPE_ROOT means that the instance is an
793 account instead of a folder. */
794 if (TNY_IS_ACCOUNT (instance)) {
795 TnyAccount *acc = TNY_ACCOUNT (instance);
796 const gchar *account_id = tny_account_get_id (acc);
798 /* If it isn't a special folder,
799 * don't show it unless it is the visible account: */
800 if (!modest_tny_account_is_virtual_local_folders (acc) &&
801 strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
802 /* Show only the visible account id */
803 if (priv->visible_account_id && strcmp (account_id, priv->visible_account_id))
807 /* Never show these to the user. They are merged into one folder
808 * in the local-folders account instead: */
809 if (retval && MODEST_IS_TNY_OUTBOX_ACCOUNT (acc))
814 /* The virtual local-folders folder store is also shown by default. */
816 /* Check hiding (if necessary) */
817 cleared = modest_email_clipboard_cleared (priv->clipboard);
818 if ((retval) && (!cleared) && (TNY_IS_FOLDER (instance))) {
819 id = tny_folder_get_id (TNY_FOLDER(instance));
820 if (priv->hidding_ids != NULL)
821 for (i=0; i < priv->n_selected && !found; i++)
822 if (priv->hidding_ids[i] != NULL && id != NULL)
823 found = (!strcmp (priv->hidding_ids[i], id));
829 g_object_unref (instance);
835 modest_folder_view_update_model (ModestFolderView *self,
836 TnyAccountStore *account_store)
838 ModestFolderViewPrivate *priv;
839 GtkTreeModel *model /* , *old_model */;
840 /* TnyAccount *local_account; */
841 TnyList *model_as_list;
843 g_return_val_if_fail (account_store, FALSE);
845 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
847 /* Notify that there is no folder selected */
848 g_signal_emit (G_OBJECT(self),
849 signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
852 /* FIXME: the local accounts are not shown when the query
853 selects only the subscribed folders. */
854 /* model = tny_gtk_folder_store_tree_model_new (TRUE, priv->query); */
855 model = tny_gtk_folder_store_tree_model_new (TRUE, NULL);
857 /* Deal with the model via its TnyList Interface,
858 * filling the TnyList via a get_accounts() call: */
859 model_as_list = TNY_LIST(model);
861 /* Get the accounts: */
862 tny_account_store_get_accounts (TNY_ACCOUNT_STORE(account_store),
864 TNY_ACCOUNT_STORE_STORE_ACCOUNTS);
865 g_object_unref (model_as_list);
866 model_as_list = NULL;
868 GtkTreeModel *filter_model = NULL, *sortable = NULL;
870 sortable = gtk_tree_model_sort_new_with_model (model);
871 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
872 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
874 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
875 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
876 cmp_rows, NULL, NULL);
878 /* Create filter model */
879 filter_model = gtk_tree_model_filter_new (sortable, NULL);
880 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
884 /* if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE) { */
885 /* filter_model = gtk_tree_model_filter_new (sortable, NULL); */
886 /* gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model), */
893 gtk_tree_view_set_model (GTK_TREE_VIEW(self), filter_model);
894 /* gtk_tree_view_set_model (GTK_TREE_VIEW(self), */
895 /* (filter_model) ? filter_model : sortable); */
897 g_object_unref (model);
898 g_object_unref (filter_model);
899 /* if (filter_model) */
900 /* g_object_unref (filter_model); */
902 g_object_unref (sortable);
904 /* Force a reselection of the INBOX next time the widget is shown */
905 priv->reselect = TRUE;
912 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
915 TnyFolderStore *folder = NULL;
917 ModestFolderView *tree_view;
918 ModestFolderViewPrivate *priv;
920 g_return_if_fail (sel);
921 g_return_if_fail (user_data);
923 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
925 if(!gtk_tree_selection_get_selected (sel, &model, &iter))
928 /* Notify the display name observers */
929 g_signal_emit (G_OBJECT(user_data),
930 signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
933 tree_view = MODEST_FOLDER_VIEW (user_data);
934 gtk_tree_model_get (model, &iter,
935 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
938 /* If the folder is the same do not notify */
939 if (priv->cur_folder_store == folder && folder) {
940 g_object_unref (folder);
944 /* Current folder was unselected */
945 if (priv->cur_folder_store) {
946 if (TNY_IS_FOLDER(priv->cur_folder_store))
947 tny_folder_sync (TNY_FOLDER(priv->cur_folder_store), TRUE, NULL);
948 /* expunge the message */
950 g_signal_emit (G_OBJECT(tree_view), signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
951 priv->cur_folder_store, FALSE);
952 g_object_unref (priv->cur_folder_store);
953 priv->cur_folder_store = NULL;
956 /* New current references */
957 priv->cur_folder_store = folder;
959 /* New folder has been selected */
960 g_signal_emit (G_OBJECT(tree_view),
961 signals[FOLDER_SELECTION_CHANGED_SIGNAL],
962 0, priv->cur_folder_store, TRUE);
966 modest_folder_view_get_selected (ModestFolderView *self)
968 ModestFolderViewPrivate *priv;
970 g_return_val_if_fail (self, NULL);
972 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
973 if (priv->cur_folder_store)
974 g_object_ref (priv->cur_folder_store);
976 return priv->cur_folder_store;
980 get_cmp_rows_type_pos (GObject *folder)
982 /* Remote accounts -> Local account -> MMC account .*/
985 if (TNY_IS_ACCOUNT (folder) &&
986 modest_tny_account_is_virtual_local_folders (
987 TNY_ACCOUNT (folder))) {
989 } else if (TNY_IS_ACCOUNT (folder)) {
990 TnyAccount *account = TNY_ACCOUNT (folder);
991 const gchar *account_id = tny_account_get_id (account);
992 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
998 printf ("DEBUG: %s: unexpected type.\n", __FUNCTION__);
999 return -1; /* Should never happen */
1004 get_cmp_subfolder_type_pos (TnyFolderType t)
1006 /* Outbox, Drafts, Sent, User */
1010 case TNY_FOLDER_TYPE_OUTBOX:
1013 case TNY_FOLDER_TYPE_DRAFTS:
1016 case TNY_FOLDER_TYPE_SENT:
1025 * This function orders the mail accounts according to these rules:
1026 * 1st - remote accounts
1027 * 2nd - local account
1031 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1035 gchar *name1 = NULL;
1036 gchar *name2 = NULL;
1037 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1038 TnyFolderType type2 = TNY_FOLDER_TYPE_UNKNOWN;
1039 GObject *folder1 = NULL;
1040 GObject *folder2 = NULL;
1042 gtk_tree_model_get (tree_model, iter1,
1043 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name1,
1044 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
1045 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder1,
1047 gtk_tree_model_get (tree_model, iter2,
1048 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name2,
1049 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type2,
1050 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder2,
1053 if (type == TNY_FOLDER_TYPE_ROOT) {
1054 /* Compare the types, so that
1055 * Remote accounts -> Local account -> MMC account .*/
1056 const gint pos1 = get_cmp_rows_type_pos (folder1);
1057 const gint pos2 = get_cmp_rows_type_pos (folder2);
1058 /* printf ("DEBUG: %s:\n type1=%s, pos1=%d\n type2=%s, pos2=%d\n",
1059 __FUNCTION__, G_OBJECT_TYPE_NAME(folder1), pos1, G_OBJECT_TYPE_NAME(folder2), pos2); */
1062 else if (pos1 > pos2)
1065 /* Compare items of the same type: */
1067 TnyAccount *account1 = NULL;
1068 if (TNY_IS_ACCOUNT (folder1))
1069 account1 = TNY_ACCOUNT (folder1);
1071 TnyAccount *account2 = NULL;
1072 if (TNY_IS_ACCOUNT (folder2))
1073 account2 = TNY_ACCOUNT (folder2);
1075 const gchar *account_id = account1 ? tny_account_get_id (account1) : NULL;
1076 const gchar *account_id2 = account2 ? tny_account_get_id (account2) : NULL;
1078 if (!account_id && !account_id2)
1080 else if (!account_id)
1082 else if (!account_id2)
1084 else if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
1087 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
1091 gboolean has_parent;
1092 gint cmp1 = 0, cmp2 = 0;
1093 /* get the parent to know if it's a local folder */
1094 has_parent = gtk_tree_model_iter_parent (tree_model, &parent, iter1);
1096 GObject *parent_folder;
1097 TnyFolderType parent_type = TNY_FOLDER_TYPE_UNKNOWN;
1098 gtk_tree_model_get (tree_model, &parent,
1099 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &parent_type,
1100 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &parent_folder,
1102 if ((parent_type == TNY_FOLDER_TYPE_ROOT) &&
1103 TNY_IS_ACCOUNT (parent_folder) &&
1104 modest_tny_account_is_virtual_local_folders (TNY_ACCOUNT (parent_folder))) {
1105 cmp1 = get_cmp_subfolder_type_pos (modest_tny_folder_get_local_folder_type (TNY_FOLDER (folder1)));
1106 cmp2 = get_cmp_subfolder_type_pos (modest_tny_folder_get_local_folder_type (TNY_FOLDER (folder2)));
1108 g_object_unref (parent_folder);
1111 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
1113 cmp = (cmp1 - cmp2);
1117 g_object_unref(G_OBJECT(folder1));
1119 g_object_unref(G_OBJECT(folder2));
1127 /*****************************************************************************/
1128 /* DRAG and DROP stuff */
1129 /*****************************************************************************/
1132 * This function fills the #GtkSelectionData with the row and the
1133 * model that has been dragged. It's called when this widget is a
1134 * source for dnd after the event drop happened
1137 on_drag_data_get (GtkWidget *widget,
1138 GdkDragContext *context,
1139 GtkSelectionData *selection_data,
1144 GtkTreeSelection *selection;
1145 GtkTreeModel *model;
1147 GtkTreePath *source_row;
1149 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1150 gtk_tree_selection_get_selected (selection, &model, &iter);
1151 source_row = gtk_tree_model_get_path (model, &iter);
1153 gtk_tree_set_row_drag_data (selection_data,
1157 gtk_tree_path_free (source_row);
1160 typedef struct _DndHelper {
1161 gboolean delete_source;
1162 GtkTreePath *source_row;
1163 GdkDragContext *context;
1169 * This function is the callback of the
1170 * modest_mail_operation_xfer_msgs () and
1171 * modest_mail_operation_xfer_folder() calls. We check here if the
1172 * message/folder was correctly asynchronously transferred. The reason
1173 * to use the same callback is that the code is the same, it only has
1174 * to check that the operation went fine and then finalize the drag
1178 on_progress_changed (ModestMailOperation *mail_op,
1179 ModestMailOperationState *state,
1185 helper = (DndHelper *) user_data;
1187 if (!state->finished)
1190 if (state->status == MODEST_MAIL_OPERATION_STATUS_SUCCESS) {
1196 /* Notify the drag source. Never call delete, the monitor will
1197 do the job if needed */
1198 gtk_drag_finish (helper->context, success, FALSE, helper->time);
1200 /* Free the helper */
1201 gtk_tree_path_free (helper->source_row);
1202 g_slice_free (DndHelper, helper);
1206 * This function is used by drag_data_received_cb to manage drag and
1207 * drop of a header, i.e, and drag from the header view to the folder
1211 drag_and_drop_from_header_view (GtkTreeModel *source_model,
1212 GtkTreeModel *dest_model,
1213 GtkTreePath *dest_row,
1216 TnyList *headers = NULL;
1217 TnyHeader *header = NULL;
1218 TnyFolder *folder = NULL;
1219 ModestMailOperation *mail_op = NULL;
1220 GtkTreeIter source_iter, dest_iter;
1222 g_return_if_fail (GTK_IS_TREE_MODEL(source_model));
1223 g_return_if_fail (GTK_IS_TREE_MODEL(dest_model));
1224 g_return_if_fail (dest_row);
1225 g_return_if_fail (helper);
1228 gtk_tree_model_get_iter (source_model, &source_iter, helper->source_row);
1229 gtk_tree_model_get (source_model, &source_iter,
1230 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1232 if (!TNY_IS_HEADER(header)) {
1233 g_warning ("BUG: %s could not get a valid header", __FUNCTION__);
1238 gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
1239 gtk_tree_model_get (dest_model, &dest_iter,
1240 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
1243 if (!TNY_IS_FOLDER(folder)) {
1244 g_warning ("BUG: %s could not get a valid folder", __FUNCTION__);
1248 /* Transfer message */
1249 mail_op = modest_mail_operation_new (MODEST_MAIL_OPERATION_TYPE_RECEIVE, NULL);
1250 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1252 g_signal_connect (G_OBJECT (mail_op), "progress-changed",
1253 G_CALLBACK (on_progress_changed), helper);
1255 headers = tny_simple_list_new ();
1256 tny_list_append (headers, G_OBJECT (header));
1257 modest_mail_operation_xfer_msgs (mail_op,
1260 helper->delete_source,
1265 if (G_IS_OBJECT(mail_op))
1266 g_object_unref (G_OBJECT (mail_op));
1267 if (G_IS_OBJECT(header))
1268 g_object_unref (G_OBJECT (header));
1269 if (G_IS_OBJECT(folder))
1270 g_object_unref (G_OBJECT (folder));
1271 if (G_IS_OBJECT(headers))
1272 g_object_unref (headers);
1276 * This function is used by drag_data_received_cb to manage drag and
1277 * drop of a folder, i.e, and drag from the folder view to the same
1281 drag_and_drop_from_folder_view (GtkTreeModel *source_model,
1282 GtkTreeModel *dest_model,
1283 GtkTreePath *dest_row,
1284 GtkSelectionData *selection_data,
1287 ModestMailOperation *mail_op;
1288 GtkTreeIter parent_iter, iter;
1289 TnyFolderStore *parent_folder;
1292 /* Check if the drag is possible */
1293 /* if (!gtk_tree_path_compare (helper->source_row, dest_row) || */
1294 /* !gtk_tree_drag_dest_row_drop_possible (GTK_TREE_DRAG_DEST (dest_model), */
1296 /* selection_data)) { */
1297 if (!gtk_tree_path_compare (helper->source_row, dest_row)) {
1299 gtk_drag_finish (helper->context, FALSE, FALSE, helper->time);
1300 gtk_tree_path_free (helper->source_row);
1301 g_slice_free (DndHelper, helper);
1306 gtk_tree_model_get_iter (source_model, &parent_iter, dest_row);
1307 gtk_tree_model_get_iter (source_model, &iter, helper->source_row);
1308 gtk_tree_model_get (source_model, &parent_iter,
1309 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
1310 &parent_folder, -1);
1311 gtk_tree_model_get (source_model, &iter,
1312 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
1315 /* Do the mail operation */
1316 mail_op = modest_mail_operation_new_with_error_handling (MODEST_MAIL_OPERATION_TYPE_RECEIVE,
1318 modest_ui_actions_move_folder_error_handler,
1320 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1322 g_signal_connect (G_OBJECT (mail_op), "progress-changed",
1323 G_CALLBACK (on_progress_changed), helper);
1325 modest_mail_operation_xfer_folder (mail_op,
1328 helper->delete_source);
1331 g_object_unref (G_OBJECT (parent_folder));
1332 g_object_unref (G_OBJECT (folder));
1333 g_object_unref (G_OBJECT (mail_op));
1337 * This function receives the data set by the "drag-data-get" signal
1338 * handler. This information comes within the #GtkSelectionData. This
1339 * function will manage both the drags of folders of the treeview and
1340 * drags of headers of the header view widget.
1343 on_drag_data_received (GtkWidget *widget,
1344 GdkDragContext *context,
1347 GtkSelectionData *selection_data,
1352 GtkWidget *source_widget;
1353 GtkTreeModel *dest_model, *source_model;
1354 GtkTreePath *source_row, *dest_row;
1355 GtkTreeViewDropPosition pos;
1356 gboolean success = FALSE, delete_source = FALSE;
1357 DndHelper *helper = NULL;
1359 /* Do not allow further process */
1360 g_signal_stop_emission_by_name (widget, "drag-data-received");
1361 source_widget = gtk_drag_get_source_widget (context);
1363 /* Get the action */
1364 if (context->action == GDK_ACTION_MOVE) {
1365 delete_source = TRUE;
1367 /* Notify that there is no folder selected. We need to
1368 do this in order to update the headers view (and
1369 its monitors, because when moving, the old folder
1370 won't longer exist. We can not wait for the end of
1371 the operation, because the operation won't start if
1372 the folder is in use */
1373 if (source_widget == widget)
1374 g_signal_emit (G_OBJECT (widget),
1375 signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0, NULL, TRUE);
1378 /* Check if the get_data failed */
1379 if (selection_data == NULL || selection_data->length < 0)
1380 gtk_drag_finish (context, success, FALSE, time);
1382 /* Get the models */
1383 gtk_tree_get_row_drag_data (selection_data,
1387 /* Select the destination model */
1388 if (source_widget == widget) {
1389 dest_model = source_model;
1391 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
1394 /* Get the path to the destination row. Can not call
1395 gtk_tree_view_get_drag_dest_row() because the source row
1396 is not selected anymore */
1397 gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget), x, y,
1400 /* Only allow drops IN other rows */
1401 if (!dest_row || pos == GTK_TREE_VIEW_DROP_BEFORE || pos == GTK_TREE_VIEW_DROP_AFTER)
1402 gtk_drag_finish (context, success, FALSE, time);
1404 /* Create the helper */
1405 helper = g_slice_new0 (DndHelper);
1406 helper->delete_source = delete_source;
1407 helper->source_row = gtk_tree_path_copy (source_row);
1408 helper->context = context;
1409 helper->time = time;
1411 /* Drags from the header view */
1412 if (source_widget != widget) {
1414 drag_and_drop_from_header_view (source_model,
1421 drag_and_drop_from_folder_view (source_model,
1429 gtk_tree_path_free (source_row);
1430 gtk_tree_path_free (dest_row);
1434 * We define a "drag-drop" signal handler because we do not want to
1435 * use the default one, because the default one always calls
1436 * gtk_drag_finish and we prefer to do it in the "drag-data-received"
1437 * signal handler, because there we have all the information available
1438 * to know if the dnd was a success or not.
1441 drag_drop_cb (GtkWidget *widget,
1442 GdkDragContext *context,
1450 if (!context->targets)
1453 /* Check if we're dragging a folder row */
1454 target = gtk_drag_dest_find_target (widget, context, NULL);
1456 /* Request the data from the source. */
1457 gtk_drag_get_data(widget, context, target, time);
1463 * This function expands a node of a tree view if it's not expanded
1464 * yet. Not sure why it needs the threads stuff, but gtk+`example code
1465 * does that, so that's why they're here.
1468 expand_row_timeout (gpointer data)
1470 GtkTreeView *tree_view = data;
1471 GtkTreePath *dest_path = NULL;
1472 GtkTreeViewDropPosition pos;
1473 gboolean result = FALSE;
1475 GDK_THREADS_ENTER ();
1477 gtk_tree_view_get_drag_dest_row (tree_view,
1482 (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER ||
1483 pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)) {
1484 gtk_tree_view_expand_row (tree_view, dest_path, FALSE);
1485 gtk_tree_path_free (dest_path);
1489 gtk_tree_path_free (dest_path);
1494 GDK_THREADS_LEAVE ();
1500 * This function is called whenever the pointer is moved over a widget
1501 * while dragging some data. It installs a timeout that will expand a
1502 * node of the treeview if not expanded yet. This function also calls
1503 * gdk_drag_status in order to set the suggested action that will be
1504 * used by the "drag-data-received" signal handler to know if we
1505 * should do a move or just a copy of the data.
1508 on_drag_motion (GtkWidget *widget,
1509 GdkDragContext *context,
1515 GtkTreeViewDropPosition pos;
1516 GtkTreePath *dest_row;
1517 ModestFolderViewPrivate *priv;
1518 GdkDragAction suggested_action;
1519 gboolean valid_location = FALSE;
1521 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
1523 if (priv->timer_expander != 0) {
1524 g_source_remove (priv->timer_expander);
1525 priv->timer_expander = 0;
1528 gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
1533 /* Do not allow drops between folders */
1535 pos == GTK_TREE_VIEW_DROP_BEFORE ||
1536 pos == GTK_TREE_VIEW_DROP_AFTER) {
1537 gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW (widget), NULL, 0);
1538 gdk_drag_status(context, 0, time);
1539 valid_location = FALSE;
1542 valid_location = TRUE;
1545 /* Expand the selected row after 1/2 second */
1546 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), dest_row)) {
1547 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), dest_row, pos);
1548 priv->timer_expander = g_timeout_add (500, expand_row_timeout, widget);
1551 /* Select the desired action. By default we pick MOVE */
1552 suggested_action = GDK_ACTION_MOVE;
1554 if (context->actions == GDK_ACTION_COPY)
1555 gdk_drag_status(context, GDK_ACTION_COPY, time);
1556 else if (context->actions == GDK_ACTION_MOVE)
1557 gdk_drag_status(context, GDK_ACTION_MOVE, time);
1558 else if (context->actions & suggested_action)
1559 gdk_drag_status(context, suggested_action, time);
1561 gdk_drag_status(context, GDK_ACTION_DEFAULT, time);
1565 gtk_tree_path_free (dest_row);
1566 g_signal_stop_emission_by_name (widget, "drag-motion");
1567 return valid_location;
1571 /* Folder view drag types */
1572 const GtkTargetEntry folder_view_drag_types[] =
1574 { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, MODEST_FOLDER_ROW },
1575 { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_APP, MODEST_HEADER_ROW }
1579 * This function sets the treeview as a source and a target for dnd
1580 * events. It also connects all the requirede signals.
1583 setup_drag_and_drop (GtkTreeView *self)
1585 /* Set up the folder view as a dnd destination. Set only the
1586 highlight flag, otherwise gtk will have a different
1588 gtk_drag_dest_set (GTK_WIDGET (self),
1589 GTK_DEST_DEFAULT_HIGHLIGHT,
1590 folder_view_drag_types,
1591 G_N_ELEMENTS (folder_view_drag_types),
1592 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1594 g_signal_connect (G_OBJECT (self),
1595 "drag_data_received",
1596 G_CALLBACK (on_drag_data_received),
1600 /* Set up the treeview as a dnd source */
1601 gtk_drag_source_set (GTK_WIDGET (self),
1603 folder_view_drag_types,
1604 G_N_ELEMENTS (folder_view_drag_types),
1605 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1607 g_signal_connect (G_OBJECT (self),
1609 G_CALLBACK (on_drag_motion),
1612 g_signal_connect (G_OBJECT (self),
1614 G_CALLBACK (on_drag_data_get),
1617 g_signal_connect (G_OBJECT (self),
1619 G_CALLBACK (drag_drop_cb),
1624 * This function manages the navigation through the folders using the
1625 * keyboard or the hardware keys in the device
1628 on_key_pressed (GtkWidget *self,
1632 GtkTreeSelection *selection;
1634 GtkTreeModel *model;
1635 gboolean retval = FALSE;
1637 /* Up and Down are automatically managed by the treeview */
1638 if (event->keyval == GDK_Return) {
1639 /* Expand/Collapse the selected row */
1640 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1641 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
1644 path = gtk_tree_model_get_path (model, &iter);
1646 if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (self), path))
1647 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1649 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1650 gtk_tree_path_free (path);
1652 /* No further processing */
1660 * We listen to the changes in the local folder account name key,
1661 * because we want to show the right name in the view. The local
1662 * folder account name corresponds to the device name in the Maemo
1663 * version. We do this because we do not want to query gconf on each
1664 * tree view refresh. It's better to cache it and change whenever
1668 on_configuration_key_changed (ModestConf* conf,
1670 ModestConfEvent event,
1671 ModestFolderView *self)
1673 ModestFolderViewPrivate *priv;
1678 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
1679 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1681 if (!strcmp (key, MODEST_CONF_DEVICE_NAME)) {
1682 g_free (priv->local_account_name);
1684 if (event == MODEST_CONF_EVENT_KEY_UNSET)
1685 priv->local_account_name = g_strdup (MODEST_LOCAL_FOLDERS_DEFAULT_DISPLAY_NAME);
1687 priv->local_account_name = modest_conf_get_string (modest_runtime_get_conf(),
1688 MODEST_CONF_DEVICE_NAME, NULL);
1690 /* Force a redraw */
1691 #if GTK_CHECK_VERSION(2, 8, 0) /* gtk_tree_view_column_queue_resize is only available in GTK+ 2.8 */
1692 GtkTreeViewColumn * tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self),
1693 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN);
1694 gtk_tree_view_column_queue_resize (tree_column);
1700 modest_folder_view_set_style (ModestFolderView *self,
1701 ModestFolderViewStyle style)
1703 ModestFolderViewPrivate *priv;
1705 g_return_if_fail (self);
1707 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1709 priv->style = style;
1713 modest_folder_view_set_account_id_of_visible_server_account (ModestFolderView *self,
1714 const gchar *account_id)
1716 ModestFolderViewPrivate *priv;
1717 GtkTreeModel *model;
1719 g_return_if_fail (self);
1721 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1723 /* This will be used by the filter_row callback,
1724 * to decided which rows to show: */
1725 if (priv->visible_account_id) {
1726 g_free (priv->visible_account_id);
1727 priv->visible_account_id = NULL;
1730 priv->visible_account_id = g_strdup (account_id);
1733 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1734 if (GTK_IS_TREE_MODEL_FILTER (model))
1735 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
1737 /* Save settings to gconf */
1738 modest_widget_memory_save (modest_runtime_get_conf (), G_OBJECT(self),
1739 MODEST_CONF_FOLDER_VIEW_KEY);
1743 modest_folder_view_get_account_id_of_visible_server_account (ModestFolderView *self)
1745 ModestFolderViewPrivate *priv;
1747 g_return_val_if_fail (self, NULL);
1749 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1751 return (const gchar *) priv->visible_account_id;
1755 find_inbox_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *inbox_iter)
1759 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1762 gtk_tree_model_get (model, iter,
1763 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name,
1764 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN,
1768 printf ("DEBUG: %s: name=%s, type=%d, TNY_FOLDER_TYPE_INBOX=%d\n",
1769 __FUNCTION__, name, type, TNY_FOLDER_TYPE_INBOX);
1772 gboolean result = FALSE;
1773 if (type == TNY_FOLDER_TYPE_INBOX) {
1775 } else if (type == TNY_FOLDER_TYPE_NORMAL) {
1776 /* tinymail's camel implementation only provides TNY_FOLDER_TYPE_NORMAL
1777 * when getting folders from the cache, before connectin, so we do
1778 * an extra check. We could fix this in tinymail, but it's easier
1781 if (strcmp (name, "Inbox") == 0)
1788 *inbox_iter = *iter;
1792 if (gtk_tree_model_iter_children (model, &child, iter)) {
1793 if (find_inbox_iter (model, &child, inbox_iter))
1797 } while (gtk_tree_model_iter_next (model, iter));
1803 modest_folder_view_select_first_inbox_or_local (ModestFolderView *self)
1805 GtkTreeModel *model;
1806 GtkTreeIter iter, inbox_iter;
1807 GtkTreeSelection *sel;
1809 /* /\* Do not set it if the folder view was not painted *\/ */
1810 /* if (!GTK_WIDGET_MAPPED (self)) */
1813 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1817 expand_root_items (self);
1818 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1820 gtk_tree_model_get_iter_first (model, &iter);
1821 if (find_inbox_iter (model, &iter, &inbox_iter)) {
1822 gtk_tree_selection_select_iter (sel, &inbox_iter);
1825 gtk_tree_model_get_iter_first (model, &iter);
1826 gtk_tree_selection_select_iter (sel, &iter);
1831 modest_folder_view_copy_selection (ModestFolderView *folder_view)
1833 /* Copy selection */
1834 _clipboard_set_selected_data (folder_view, FALSE);
1838 modest_folder_view_cut_selection (ModestFolderView *folder_view)
1840 ModestFolderViewPrivate *priv = NULL;
1841 GtkTreeModel *model = NULL;
1842 const gchar **hidding = NULL;
1843 guint i, n_selected;
1845 g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view));
1846 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
1848 /* Copy selection */
1849 _clipboard_set_selected_data (folder_view, TRUE);
1851 /* Get hidding ids */
1852 hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected);
1854 /* Clear hidding array created by previous cut operation */
1855 _clear_hidding_filter (MODEST_FOLDER_VIEW (folder_view));
1857 /* Copy hidding array */
1858 priv->n_selected = n_selected;
1859 priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
1860 for (i=0; i < n_selected; i++)
1861 priv->hidding_ids[i] = g_strdup(hidding[i]);
1863 /* Hide cut folders */
1864 model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
1865 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
1869 _clipboard_set_selected_data (ModestFolderView *folder_view,
1872 ModestFolderViewPrivate *priv = NULL;
1873 TnyFolderStore *folder = NULL;
1875 g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view));
1876 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
1878 /* Set selected data on clipboard */
1879 g_return_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard));
1880 folder = modest_folder_view_get_selected (folder_view);
1881 modest_email_clipboard_set_data (priv->clipboard, TNY_FOLDER(folder), NULL, delete);
1884 g_object_unref (folder);
1888 _clear_hidding_filter (ModestFolderView *folder_view)
1890 ModestFolderViewPrivate *priv;
1893 g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view));
1894 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
1896 if (priv->hidding_ids != NULL) {
1897 for (i=0; i < priv->n_selected; i++)
1898 g_free (priv->hidding_ids[i]);
1899 g_free(priv->hidding_ids);