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 <tny-camel-account.h>
45 #include <modest-tny-account.h>
46 #include <modest-tny-folder.h>
47 #include <modest-tny-local-folders-account.h>
48 #include <modest-tny-outbox-account.h>
49 #include <modest-marshal.h>
50 #include <modest-icon-names.h>
51 #include <modest-tny-account-store.h>
52 #include <modest-tny-local-folders-account.h>
53 #include <modest-text-utils.h>
54 #include <modest-runtime.h>
55 #include "modest-folder-view.h"
56 #include <modest-platform.h>
57 #include <modest-widget-memory.h>
58 #include <modest-ui-actions.h>
59 #include "modest-dnd.h"
60 #include "widgets/modest-window.h"
62 /* Folder view drag types */
63 const GtkTargetEntry folder_view_drag_types[] =
65 { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, MODEST_FOLDER_ROW },
66 { GTK_TREE_PATH_AS_STRING_LIST, GTK_TARGET_SAME_APP, MODEST_HEADER_ROW }
69 /* 'private'/'protected' functions */
70 static void modest_folder_view_class_init (ModestFolderViewClass *klass);
71 static void modest_folder_view_init (ModestFolderView *obj);
72 static void modest_folder_view_finalize (GObject *obj);
74 static void tny_account_store_view_init (gpointer g,
77 static void modest_folder_view_set_account_store (TnyAccountStoreView *self,
78 TnyAccountStore *account_store);
80 static void on_selection_changed (GtkTreeSelection *sel,
83 static void on_account_removed (TnyAccountStore *self,
87 static void on_account_inserted (TnyAccountStore *self,
91 static void on_account_changed (TnyAccountStore *self,
95 static gint cmp_rows (GtkTreeModel *tree_model,
100 static gboolean filter_row (GtkTreeModel *model,
104 static gboolean on_key_pressed (GtkWidget *self,
108 static void on_configuration_key_changed (ModestConf* conf,
110 ModestConfEvent event,
111 ModestConfNotificationId notification_id,
112 ModestFolderView *self);
115 static void on_drag_data_get (GtkWidget *widget,
116 GdkDragContext *context,
117 GtkSelectionData *selection_data,
122 static void on_drag_data_received (GtkWidget *widget,
123 GdkDragContext *context,
126 GtkSelectionData *selection_data,
131 static gboolean on_drag_motion (GtkWidget *widget,
132 GdkDragContext *context,
138 static void expand_root_items (ModestFolderView *self);
140 static gint expand_row_timeout (gpointer data);
142 static void setup_drag_and_drop (GtkTreeView *self);
144 static gboolean _clipboard_set_selected_data (ModestFolderView *folder_view,
147 static void _clear_hidding_filter (ModestFolderView *folder_view);
149 static void on_row_inserted_maybe_select_folder (GtkTreeModel *tree_model,
152 ModestFolderView *self);
154 static void on_display_name_changed (ModestAccountMgr *self,
155 const gchar *account,
159 FOLDER_SELECTION_CHANGED_SIGNAL,
160 FOLDER_DISPLAY_NAME_CHANGED_SIGNAL,
164 typedef struct _ModestFolderViewPrivate ModestFolderViewPrivate;
165 struct _ModestFolderViewPrivate {
166 TnyAccountStore *account_store;
167 TnyFolderStore *cur_folder_store;
169 TnyFolder *folder_to_select; /* folder to select after the next update */
171 gulong changed_signal;
172 gulong account_inserted_signal;
173 gulong account_removed_signal;
174 gulong account_changed_signal;
175 gulong conf_key_signal;
176 gulong display_name_changed_signal;
178 /* not unref this object, its a singlenton */
179 ModestEmailClipboard *clipboard;
181 /* Filter tree model */
185 TnyFolderStoreQuery *query;
186 guint timer_expander;
188 gchar *local_account_name;
189 gchar *visible_account_id;
190 ModestFolderViewStyle style;
192 gboolean reselect; /* we use this to force a reselection of the INBOX */
193 gboolean show_non_move;
194 gboolean reexpand; /* next time we expose, we'll expand all root folders */
196 #define MODEST_FOLDER_VIEW_GET_PRIVATE(o) \
197 (G_TYPE_INSTANCE_GET_PRIVATE((o), \
198 MODEST_TYPE_FOLDER_VIEW, \
199 ModestFolderViewPrivate))
201 static GObjectClass *parent_class = NULL;
203 static guint signals[LAST_SIGNAL] = {0};
206 modest_folder_view_get_type (void)
208 static GType my_type = 0;
210 static const GTypeInfo my_info = {
211 sizeof(ModestFolderViewClass),
212 NULL, /* base init */
213 NULL, /* base finalize */
214 (GClassInitFunc) modest_folder_view_class_init,
215 NULL, /* class finalize */
216 NULL, /* class data */
217 sizeof(ModestFolderView),
219 (GInstanceInitFunc) modest_folder_view_init,
223 static const GInterfaceInfo tny_account_store_view_info = {
224 (GInterfaceInitFunc) tny_account_store_view_init, /* interface_init */
225 NULL, /* interface_finalize */
226 NULL /* interface_data */
230 my_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
234 g_type_add_interface_static (my_type,
235 TNY_TYPE_ACCOUNT_STORE_VIEW,
236 &tny_account_store_view_info);
242 modest_folder_view_class_init (ModestFolderViewClass *klass)
244 GObjectClass *gobject_class;
245 GtkTreeViewClass *treeview_class;
246 gobject_class = (GObjectClass*) klass;
247 treeview_class = (GtkTreeViewClass*) klass;
249 parent_class = g_type_class_peek_parent (klass);
250 gobject_class->finalize = modest_folder_view_finalize;
252 g_type_class_add_private (gobject_class,
253 sizeof(ModestFolderViewPrivate));
255 signals[FOLDER_SELECTION_CHANGED_SIGNAL] =
256 g_signal_new ("folder_selection_changed",
257 G_TYPE_FROM_CLASS (gobject_class),
259 G_STRUCT_OFFSET (ModestFolderViewClass,
260 folder_selection_changed),
262 modest_marshal_VOID__POINTER_BOOLEAN,
263 G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_BOOLEAN);
266 * This signal is emitted whenever the currently selected
267 * folder display name is computed. Note that the name could
268 * be different to the folder name, because we could append
269 * the unread messages count to the folder name to build the
270 * folder display name
272 signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL] =
273 g_signal_new ("folder-display-name-changed",
274 G_TYPE_FROM_CLASS (gobject_class),
276 G_STRUCT_OFFSET (ModestFolderViewClass,
277 folder_display_name_changed),
279 g_cclosure_marshal_VOID__STRING,
280 G_TYPE_NONE, 1, G_TYPE_STRING);
282 treeview_class->select_cursor_parent = NULL;
286 /* Simplify checks for NULLs: */
288 strings_are_equal (const gchar *a, const gchar *b)
294 return (strcmp (a, b) == 0);
301 on_model_foreach_set_name(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
303 GObject *instance = NULL;
305 gtk_tree_model_get (model, iter,
306 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
310 return FALSE; /* keep walking */
312 if (!TNY_IS_ACCOUNT (instance)) {
313 g_object_unref (instance);
314 return FALSE; /* keep walking */
317 /* Check if this is the looked-for account: */
318 TnyAccount *this_account = TNY_ACCOUNT (instance);
319 TnyAccount *account = TNY_ACCOUNT (data);
321 const gchar *this_account_id = tny_account_get_id(this_account);
322 const gchar *account_id = tny_account_get_id(account);
323 g_object_unref (instance);
326 /* printf ("DEBUG: %s: this_account_id=%s, account_id=%s\n", __FUNCTION__, this_account_id, account_id); */
327 if (strings_are_equal(this_account_id, account_id)) {
328 /* Tell the model that the data has changed, so that
329 * it calls the cell_data_func callbacks again: */
330 /* TODO: This does not seem to actually cause the new string to be shown: */
331 gtk_tree_model_row_changed (model, path, iter);
333 return TRUE; /* stop walking */
336 return FALSE; /* keep walking */
341 ModestFolderView *self;
342 gchar *previous_name;
343 } GetMmcAccountNameData;
346 on_get_mmc_account_name (TnyStoreAccount* account, gpointer user_data)
348 /* printf ("DEBU1G: %s: account name=%s\n", __FUNCTION__, tny_account_get_name (TNY_ACCOUNT(account))); */
350 GetMmcAccountNameData *data = (GetMmcAccountNameData*)user_data;
352 if (!strings_are_equal (
353 tny_account_get_name(TNY_ACCOUNT(account)),
354 data->previous_name)) {
356 /* Tell the model that the data has changed, so that
357 * it calls the cell_data_func callbacks again: */
358 ModestFolderView *self = data->self;
359 GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
361 gtk_tree_model_foreach(model, on_model_foreach_set_name, account);
364 g_free (data->previous_name);
365 g_slice_free (GetMmcAccountNameData, data);
369 text_cell_data (GtkTreeViewColumn *column,
370 GtkCellRenderer *renderer,
371 GtkTreeModel *tree_model,
375 ModestFolderViewPrivate *priv;
376 GObject *rendobj = (GObject *) renderer;
378 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
379 GObject *instance = NULL;
381 gtk_tree_model_get (tree_model, iter,
382 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &fname,
383 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
384 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
386 if (!fname || !instance)
389 ModestFolderView *self = MODEST_FOLDER_VIEW (data);
390 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
392 gchar *item_name = NULL;
393 gint item_weight = 400;
395 if (type != TNY_FOLDER_TYPE_ROOT) {
398 if (modest_tny_folder_is_local_folder (TNY_FOLDER (instance)) ||
399 modest_tny_folder_is_memory_card_folder (TNY_FOLDER (instance))) {
400 type = modest_tny_folder_get_local_or_mmc_folder_type (TNY_FOLDER (instance));
401 if (type != TNY_FOLDER_TYPE_UNKNOWN) {
403 fname = g_strdup (modest_local_folder_info_get_type_display_name (type));
406 /* Sometimes an special folder is reported by the server as
407 NORMAL, like some versions of Dovecot */
408 if (type == TNY_FOLDER_TYPE_NORMAL ||
409 type == TNY_FOLDER_TYPE_UNKNOWN) {
410 type = modest_tny_folder_guess_folder_type (TNY_FOLDER (instance));
414 /* note: we cannot reliably get the counts from the tree model, we need
415 * to use explicit calls on tny_folder for some reason.
417 /* Select the number to show: the unread or unsent messages. in case of outbox/drafts, show all */
418 if ((type == TNY_FOLDER_TYPE_DRAFTS) ||
419 (type == TNY_FOLDER_TYPE_OUTBOX) ||
420 (type == TNY_FOLDER_TYPE_MERGE)) /* _OUTBOX actually returns _MERGE... */
421 number = tny_folder_get_all_count (TNY_FOLDER(instance));
423 number = tny_folder_get_unread_count (TNY_FOLDER(instance));
425 /* Use bold font style if there are unread or unset messages */
427 if (type == TNY_FOLDER_TYPE_INBOX)
428 item_name = g_strdup_printf ("%s (%d)", _("mcen_me_folder_inbox"), number);
430 item_name = g_strdup_printf ("%s (%d)", fname, number);
433 if (type == TNY_FOLDER_TYPE_INBOX)
434 item_name = g_strdup (_("mcen_me_folder_inbox"));
436 item_name = g_strdup (fname);
440 } else if (TNY_IS_ACCOUNT (instance)) {
441 /* If it's a server account */
442 if (modest_tny_account_is_virtual_local_folders (TNY_ACCOUNT (instance))) {
443 item_name = g_strdup (priv->local_account_name);
445 } else if (modest_tny_account_is_memory_card_account (TNY_ACCOUNT (instance))) {
446 /* fname is only correct when the items are first
447 * added to the model, not when the account is
448 * changed later, so get the name from the account
450 item_name = g_strdup (tny_account_get_name (TNY_ACCOUNT (instance)));
453 item_name = g_strdup (fname);
459 item_name = g_strdup ("unknown");
461 if (item_name && item_weight) {
462 /* Set the name in the treeview cell: */
463 g_object_set (rendobj,"text", item_name, "weight", item_weight, NULL);
465 /* Notify display name observers */
466 /* TODO: What listens for this signal, and how can it use only the new name? */
467 if (((GObject *) priv->cur_folder_store) == instance) {
468 g_signal_emit (G_OBJECT(self),
469 signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
476 /* If it is a Memory card account, make sure that we have the correct name.
477 * This function will be trigerred again when the name has been retrieved: */
478 if (TNY_IS_STORE_ACCOUNT (instance) &&
479 modest_tny_account_is_memory_card_account (TNY_ACCOUNT (instance))) {
481 /* Get the account name asynchronously: */
482 GetMmcAccountNameData *callback_data =
483 g_slice_new0(GetMmcAccountNameData);
484 callback_data->self = self;
486 const gchar *name = tny_account_get_name (TNY_ACCOUNT(instance));
488 callback_data->previous_name = g_strdup (name);
490 modest_tny_account_get_mmc_account_name (TNY_STORE_ACCOUNT (instance),
491 on_get_mmc_account_name, callback_data);
495 g_object_unref (G_OBJECT (instance));
503 GdkPixbuf *pixbuf_open;
504 GdkPixbuf *pixbuf_close;
508 static inline GdkPixbuf *
509 get_composite_pixbuf (const gchar *icon_name,
511 GdkPixbuf *base_pixbuf)
513 GdkPixbuf *emblem, *retval = NULL;
515 emblem = modest_platform_get_icon (icon_name, size);
517 retval = gdk_pixbuf_copy (base_pixbuf);
518 gdk_pixbuf_composite (emblem, retval, 0, 0,
519 MIN (gdk_pixbuf_get_width (emblem),
520 gdk_pixbuf_get_width (retval)),
521 MIN (gdk_pixbuf_get_height (emblem),
522 gdk_pixbuf_get_height (retval)),
523 0, 0, 1, 1, GDK_INTERP_NEAREST, 255);
524 g_object_unref (emblem);
529 static inline ThreePixbufs *
530 get_composite_icons (const gchar *icon_code,
532 GdkPixbuf **pixbuf_open,
533 GdkPixbuf **pixbuf_close)
535 ThreePixbufs *retval;
538 *pixbuf = gdk_pixbuf_copy (modest_platform_get_icon (icon_code, MODEST_ICON_SIZE_SMALL));
541 *pixbuf_open = get_composite_pixbuf ("qgn_list_gene_fldr_exp",
542 MODEST_ICON_SIZE_SMALL,
546 *pixbuf_close = get_composite_pixbuf ("qgn_list_gene_fldr_clp",
547 MODEST_ICON_SIZE_SMALL,
550 retval = g_slice_new0 (ThreePixbufs);
552 retval->pixbuf = g_object_ref (*pixbuf);
554 retval->pixbuf_open = g_object_ref (*pixbuf_open);
556 retval->pixbuf_close = g_object_ref (*pixbuf_close);
562 get_folder_icons (TnyFolderType type, GObject *instance)
564 static GdkPixbuf *inbox_pixbuf = NULL, *outbox_pixbuf = NULL,
565 *junk_pixbuf = NULL, *sent_pixbuf = NULL,
566 *trash_pixbuf = NULL, *draft_pixbuf = NULL,
567 *normal_pixbuf = NULL, *anorm_pixbuf = NULL,
568 *ammc_pixbuf = NULL, *avirt_pixbuf = NULL;
570 static GdkPixbuf *inbox_pixbuf_open = NULL, *outbox_pixbuf_open = NULL,
571 *junk_pixbuf_open = NULL, *sent_pixbuf_open = NULL,
572 *trash_pixbuf_open = NULL, *draft_pixbuf_open = NULL,
573 *normal_pixbuf_open = NULL, *anorm_pixbuf_open = NULL,
574 *ammc_pixbuf_open = NULL, *avirt_pixbuf_open = NULL;
576 static GdkPixbuf *inbox_pixbuf_close = NULL, *outbox_pixbuf_close = NULL,
577 *junk_pixbuf_close = NULL, *sent_pixbuf_close = NULL,
578 *trash_pixbuf_close = NULL, *draft_pixbuf_close = NULL,
579 *normal_pixbuf_close = NULL, *anorm_pixbuf_close = NULL,
580 *ammc_pixbuf_close = NULL, *avirt_pixbuf_close = NULL;
582 ThreePixbufs *retval = NULL;
584 /* Sometimes an special folder is reported by the server as
585 NORMAL, like some versions of Dovecot */
586 if (type == TNY_FOLDER_TYPE_NORMAL ||
587 type == TNY_FOLDER_TYPE_UNKNOWN) {
588 type = modest_tny_folder_guess_folder_type (TNY_FOLDER (instance));
592 case TNY_FOLDER_TYPE_INVALID:
593 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
596 case TNY_FOLDER_TYPE_ROOT:
597 if (TNY_IS_ACCOUNT (instance)) {
599 if (modest_tny_account_is_virtual_local_folders (TNY_ACCOUNT (instance))) {
600 retval = get_composite_icons (MODEST_FOLDER_ICON_LOCAL_FOLDERS,
603 &avirt_pixbuf_close);
605 const gchar *account_id = tny_account_get_id (TNY_ACCOUNT (instance));
607 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
608 retval = get_composite_icons (MODEST_FOLDER_ICON_MMC,
613 retval = get_composite_icons (MODEST_FOLDER_ICON_ACCOUNT,
616 &anorm_pixbuf_close);
621 case TNY_FOLDER_TYPE_INBOX:
622 retval = get_composite_icons (MODEST_FOLDER_ICON_INBOX,
625 &inbox_pixbuf_close);
627 case TNY_FOLDER_TYPE_OUTBOX:
628 retval = get_composite_icons (MODEST_FOLDER_ICON_OUTBOX,
631 &outbox_pixbuf_close);
633 case TNY_FOLDER_TYPE_JUNK:
634 retval = get_composite_icons (MODEST_FOLDER_ICON_JUNK,
639 case TNY_FOLDER_TYPE_SENT:
640 retval = get_composite_icons (MODEST_FOLDER_ICON_SENT,
645 case TNY_FOLDER_TYPE_TRASH:
646 retval = get_composite_icons (MODEST_FOLDER_ICON_TRASH,
649 &trash_pixbuf_close);
651 case TNY_FOLDER_TYPE_DRAFTS:
652 retval = get_composite_icons (MODEST_FOLDER_ICON_DRAFTS,
655 &draft_pixbuf_close);
657 case TNY_FOLDER_TYPE_NORMAL:
659 retval = get_composite_icons (MODEST_FOLDER_ICON_NORMAL,
662 &normal_pixbuf_close);
670 free_pixbufs (ThreePixbufs *pixbufs)
673 g_object_unref (pixbufs->pixbuf);
674 if (pixbufs->pixbuf_open)
675 g_object_unref (pixbufs->pixbuf_open);
676 if (pixbufs->pixbuf_close)
677 g_object_unref (pixbufs->pixbuf_close);
678 g_slice_free (ThreePixbufs, pixbufs);
682 icon_cell_data (GtkTreeViewColumn *column,
683 GtkCellRenderer *renderer,
684 GtkTreeModel *tree_model,
688 GObject *rendobj = NULL, *instance = NULL;
689 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
690 gboolean has_children;
691 ThreePixbufs *pixbufs;
693 rendobj = (GObject *) renderer;
695 gtk_tree_model_get (tree_model, iter,
696 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
697 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
703 has_children = gtk_tree_model_iter_has_child (tree_model, iter);
704 pixbufs = get_folder_icons (type, instance);
705 g_object_unref (instance);
708 g_object_set (rendobj, "pixbuf", pixbufs->pixbuf, NULL);
711 g_object_set (rendobj, "pixbuf-expander-open", pixbufs->pixbuf_open, NULL);
712 g_object_set (rendobj, "pixbuf-expander-closed", pixbufs->pixbuf_close, NULL);
715 free_pixbufs (pixbufs);
721 add_columns (GtkWidget *treeview)
723 GtkTreeViewColumn *column;
724 GtkCellRenderer *renderer;
725 GtkTreeSelection *sel;
728 column = gtk_tree_view_column_new ();
730 /* Set icon and text render function */
731 renderer = gtk_cell_renderer_pixbuf_new();
732 gtk_tree_view_column_pack_start (column, renderer, FALSE);
733 gtk_tree_view_column_set_cell_data_func(column, renderer,
734 icon_cell_data, treeview, NULL);
736 renderer = gtk_cell_renderer_text_new();
737 g_object_set (renderer, "ellipsize", PANGO_ELLIPSIZE_END,
738 "ellipsize-set", TRUE, NULL);
739 gtk_tree_view_column_pack_start (column, renderer, TRUE);
740 gtk_tree_view_column_set_cell_data_func(column, renderer,
741 text_cell_data, treeview, NULL);
743 /* Set selection mode */
744 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(treeview));
745 gtk_tree_selection_set_mode (sel, GTK_SELECTION_SINGLE);
747 /* Set treeview appearance */
748 gtk_tree_view_column_set_spacing (column, 2);
749 gtk_tree_view_column_set_resizable (column, TRUE);
750 gtk_tree_view_column_set_fixed_width (column, TRUE);
751 gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(treeview), FALSE);
752 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(treeview), FALSE);
755 gtk_tree_view_append_column (GTK_TREE_VIEW(treeview),column);
759 modest_folder_view_init (ModestFolderView *obj)
761 ModestFolderViewPrivate *priv;
764 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
766 priv->timer_expander = 0;
767 priv->account_store = NULL;
769 priv->style = MODEST_FOLDER_VIEW_STYLE_SHOW_ALL;
770 priv->cur_folder_store = NULL;
771 priv->visible_account_id = NULL;
772 priv->folder_to_select = NULL;
774 priv->reexpand = TRUE;
776 /* Initialize the local account name */
777 conf = modest_runtime_get_conf();
778 priv->local_account_name = modest_conf_get_string (conf, MODEST_CONF_DEVICE_NAME, NULL);
780 /* Init email clipboard */
781 priv->clipboard = modest_runtime_get_email_clipboard ();
782 priv->hidding_ids = NULL;
783 priv->n_selected = 0;
784 priv->reselect = FALSE;
785 priv->show_non_move = TRUE;
788 add_columns (GTK_WIDGET (obj));
790 /* Setup drag and drop */
791 setup_drag_and_drop (GTK_TREE_VIEW(obj));
793 /* Connect signals */
794 g_signal_connect (G_OBJECT (obj),
796 G_CALLBACK (on_key_pressed), NULL);
798 priv->display_name_changed_signal =
799 g_signal_connect (modest_runtime_get_account_mgr (),
800 "display_name_changed",
801 G_CALLBACK (on_display_name_changed),
805 * Track changes in the local account name (in the device it
806 * will be the device name)
808 priv->conf_key_signal = g_signal_connect (G_OBJECT(conf),
810 G_CALLBACK(on_configuration_key_changed),
815 tny_account_store_view_init (gpointer g, gpointer iface_data)
817 TnyAccountStoreViewIface *klass = (TnyAccountStoreViewIface *)g;
819 klass->set_account_store = modest_folder_view_set_account_store;
823 modest_folder_view_finalize (GObject *obj)
825 ModestFolderViewPrivate *priv;
826 GtkTreeSelection *sel;
828 g_return_if_fail (obj);
830 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
832 if (priv->timer_expander != 0) {
833 g_source_remove (priv->timer_expander);
834 priv->timer_expander = 0;
837 if (priv->account_store) {
838 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
839 priv->account_inserted_signal);
840 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
841 priv->account_removed_signal);
842 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
843 priv->account_changed_signal);
844 g_object_unref (G_OBJECT(priv->account_store));
845 priv->account_store = NULL;
849 g_object_unref (G_OBJECT (priv->query));
853 if (priv->folder_to_select) {
854 g_object_unref (G_OBJECT(priv->folder_to_select));
855 priv->folder_to_select = NULL;
858 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(obj));
860 g_signal_handler_disconnect (G_OBJECT(sel), priv->changed_signal);
862 g_free (priv->local_account_name);
863 g_free (priv->visible_account_id);
865 if (priv->conf_key_signal) {
866 g_signal_handler_disconnect (modest_runtime_get_conf (),
867 priv->conf_key_signal);
868 priv->conf_key_signal = 0;
871 if (priv->cur_folder_store) {
872 g_object_unref (priv->cur_folder_store);
873 priv->cur_folder_store = NULL;
876 /* Clear hidding array created by cut operation */
877 _clear_hidding_filter (MODEST_FOLDER_VIEW (obj));
879 G_OBJECT_CLASS(parent_class)->finalize (obj);
884 modest_folder_view_set_account_store (TnyAccountStoreView *self, TnyAccountStore *account_store)
886 ModestFolderViewPrivate *priv;
889 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
890 g_return_if_fail (TNY_IS_ACCOUNT_STORE (account_store));
892 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
893 device = tny_account_store_get_device (account_store);
895 if (G_UNLIKELY (priv->account_store)) {
897 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
898 priv->account_inserted_signal))
899 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
900 priv->account_inserted_signal);
901 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
902 priv->account_removed_signal))
903 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
904 priv->account_removed_signal);
905 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
906 priv->account_changed_signal))
907 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
908 priv->account_changed_signal);
909 g_object_unref (G_OBJECT (priv->account_store));
912 priv->account_store = g_object_ref (G_OBJECT (account_store));
914 priv->account_removed_signal =
915 g_signal_connect (G_OBJECT(account_store), "account_removed",
916 G_CALLBACK (on_account_removed), self);
918 priv->account_inserted_signal =
919 g_signal_connect (G_OBJECT(account_store), "account_inserted",
920 G_CALLBACK (on_account_inserted), self);
922 priv->account_changed_signal =
923 g_signal_connect (G_OBJECT(account_store), "account_changed",
924 G_CALLBACK (on_account_changed), self);
926 modest_folder_view_update_model (MODEST_FOLDER_VIEW (self), account_store);
927 priv->reselect = FALSE;
928 modest_folder_view_select_first_inbox_or_local (MODEST_FOLDER_VIEW (self));
930 g_object_unref (G_OBJECT (device));
934 on_account_inserted (TnyAccountStore *account_store,
938 ModestFolderViewPrivate *priv;
939 GtkTreeModel *sort_model, *filter_model;
941 /* Ignore transport account insertions, we're not showing them
942 in the folder view */
943 if (TNY_IS_TRANSPORT_ACCOUNT (account))
946 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (user_data);
949 /* If we're adding a new account, and there is no previous
950 one, we need to select the visible server account */
951 if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE &&
952 !priv->visible_account_id)
953 modest_widget_memory_restore (modest_runtime_get_conf(),
954 G_OBJECT (user_data),
955 MODEST_CONF_FOLDER_VIEW_KEY);
957 if (!GTK_IS_TREE_VIEW(user_data)) {
958 g_warning ("BUG: %s: not a valid tree view", __FUNCTION__);
962 /* Get the inner model */
963 /* check, is some rare cases, we did not get the right thing here,
965 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (user_data));
966 if (!GTK_IS_TREE_MODEL_FILTER(filter_model)) {
967 g_warning ("BUG: %s: not a valid filter model", __FUNCTION__);
971 /* check, is some rare cases, we did not get the right thing here,
973 sort_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
974 if (!GTK_IS_TREE_MODEL_SORT(sort_model)) {
975 g_warning ("BUG: %s: not a valid sort model", __FUNCTION__);
979 /* Insert the account in the model */
980 tny_list_append (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
983 /* Refilter the model */
984 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
989 same_account_selected (ModestFolderView *self,
992 ModestFolderViewPrivate *priv;
993 gboolean same_account = FALSE;
995 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
997 if (priv->cur_folder_store) {
998 TnyAccount *selected_folder_account = NULL;
1000 if (TNY_IS_FOLDER (priv->cur_folder_store)) {
1001 selected_folder_account =
1002 modest_tny_folder_get_account (TNY_FOLDER (priv->cur_folder_store));
1004 selected_folder_account =
1005 TNY_ACCOUNT (g_object_ref (priv->cur_folder_store));
1008 if (selected_folder_account == account)
1009 same_account = TRUE;
1011 g_object_unref (selected_folder_account);
1013 return same_account;
1018 * Selects the first inbox or the local account in an idle
1021 on_idle_select_first_inbox_or_local (gpointer user_data)
1023 ModestFolderView *self = MODEST_FOLDER_VIEW (user_data);
1025 gdk_threads_enter ();
1026 modest_folder_view_select_first_inbox_or_local (self);
1027 gdk_threads_leave ();
1033 on_account_changed (TnyAccountStore *account_store,
1034 TnyAccount *tny_account,
1037 ModestFolderView *self;
1038 ModestFolderViewPrivate *priv;
1039 GtkTreeModel *sort_model, *filter_model;
1040 GtkTreeSelection *sel;
1041 gboolean same_account;
1043 /* Ignore transport account insertions, we're not showing them
1044 in the folder view */
1045 if (TNY_IS_TRANSPORT_ACCOUNT (tny_account))
1048 if (!MODEST_IS_FOLDER_VIEW(user_data)) {
1049 g_warning ("BUG: %s: not a valid folder view", __FUNCTION__);
1053 self = MODEST_FOLDER_VIEW (user_data);
1054 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (user_data);
1056 /* Get the inner model */
1057 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (user_data));
1058 if (!GTK_IS_TREE_MODEL_FILTER(filter_model)) {
1059 g_warning ("BUG: %s: not a valid filter model", __FUNCTION__);
1063 sort_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
1064 if (!GTK_IS_TREE_MODEL_SORT(sort_model)) {
1065 g_warning ("BUG: %s: not a valid sort model", __FUNCTION__);
1069 /* Invalidate the cur_folder_store only if the selected folder
1070 belongs to the account that is being removed */
1071 same_account = same_account_selected (self, tny_account);
1073 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1074 gtk_tree_selection_unselect_all (sel);
1077 /* Remove the account from the model */
1078 tny_list_remove (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
1079 G_OBJECT (tny_account));
1081 /* Insert the account in the model */
1082 tny_list_append (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
1083 G_OBJECT (tny_account));
1085 /* Refilter the model */
1086 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1088 /* Select the first INBOX if the currently selected folder
1089 belongs to the account that is being deleted */
1090 if (same_account && !MODEST_IS_TNY_LOCAL_FOLDERS_ACCOUNT (tny_account))
1091 g_idle_add (on_idle_select_first_inbox_or_local, self);
1095 on_account_removed (TnyAccountStore *account_store,
1096 TnyAccount *account,
1099 ModestFolderView *self = NULL;
1100 ModestFolderViewPrivate *priv;
1101 GtkTreeModel *sort_model, *filter_model;
1102 GtkTreeSelection *sel = NULL;
1103 gboolean same_account = FALSE;
1105 /* Ignore transport account removals, we're not showing them
1106 in the folder view */
1107 if (TNY_IS_TRANSPORT_ACCOUNT (account))
1110 if (!MODEST_IS_FOLDER_VIEW(user_data)) {
1111 g_warning ("BUG: %s: not a valid folder view", __FUNCTION__);
1115 self = MODEST_FOLDER_VIEW (user_data);
1116 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1118 /* Invalidate the cur_folder_store only if the selected folder
1119 belongs to the account that is being removed */
1120 same_account = same_account_selected (self, account);
1122 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1123 gtk_tree_selection_unselect_all (sel);
1126 /* Invalidate row to select only if the folder to select
1127 belongs to the account that is being removed*/
1128 if (priv->folder_to_select) {
1129 TnyAccount *folder_to_select_account = NULL;
1131 folder_to_select_account = tny_folder_get_account (priv->folder_to_select);
1132 if (folder_to_select_account == account) {
1133 modest_folder_view_disable_next_folder_selection (self);
1134 g_object_unref (priv->folder_to_select);
1135 priv->folder_to_select = NULL;
1137 g_object_unref (folder_to_select_account);
1140 /* Remove the account from the model */
1141 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1142 if (!GTK_IS_TREE_MODEL_FILTER(filter_model)) {
1143 g_warning ("BUG: %s: not a valid filter model", __FUNCTION__);
1147 sort_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
1148 if (!GTK_IS_TREE_MODEL_SORT(sort_model)) {
1149 g_warning ("BUG: %s: not a valid sort model", __FUNCTION__);
1153 tny_list_remove (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
1154 G_OBJECT (account));
1156 /* If the removed account is the currently viewed one then
1157 clear the configuration value. The new visible account will be the default account */
1158 if (priv->visible_account_id &&
1159 !strcmp (priv->visible_account_id, tny_account_get_id (account))) {
1161 /* Clear the current visible account_id */
1162 modest_folder_view_set_account_id_of_visible_server_account (self, NULL);
1164 /* Call the restore method, this will set the new visible account */
1165 modest_widget_memory_restore (modest_runtime_get_conf(), G_OBJECT(self),
1166 MODEST_CONF_FOLDER_VIEW_KEY);
1169 /* Refilter the model */
1170 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1172 /* Select the first INBOX if the currently selected folder
1173 belongs to the account that is being deleted */
1175 g_idle_add (on_idle_select_first_inbox_or_local, self);
1179 modest_folder_view_set_title (ModestFolderView *self, const gchar *title)
1181 GtkTreeViewColumn *col;
1183 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
1185 col = gtk_tree_view_get_column (GTK_TREE_VIEW(self), 0);
1187 g_printerr ("modest: failed get column for title\n");
1191 gtk_tree_view_column_set_title (col, title);
1192 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self),
1197 modest_folder_view_on_map (ModestFolderView *self,
1198 GdkEventExpose *event,
1201 ModestFolderViewPrivate *priv;
1203 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1205 /* This won't happen often */
1206 if (G_UNLIKELY (priv->reselect)) {
1207 /* Select the first inbox or the local account if not found */
1209 /* TODO: this could cause a lock at startup, so we
1210 comment it for the moment. We know that this will
1211 be a bug, because the INBOX is not selected, but we
1212 need to rewrite some parts of Modest to avoid the
1213 deathlock situation */
1214 /* TODO: check if this is still the case */
1215 priv->reselect = FALSE;
1216 modest_folder_view_select_first_inbox_or_local (self);
1217 /* Notify the display name observers */
1218 g_signal_emit (G_OBJECT(self),
1219 signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
1223 if (priv->reexpand) {
1224 expand_root_items (self);
1225 priv->reexpand = FALSE;
1232 modest_folder_view_new (TnyFolderStoreQuery *query)
1235 ModestFolderViewPrivate *priv;
1236 GtkTreeSelection *sel;
1238 self = G_OBJECT (g_object_new (MODEST_TYPE_FOLDER_VIEW, NULL));
1239 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1242 priv->query = g_object_ref (query);
1244 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1245 priv->changed_signal = g_signal_connect (sel, "changed",
1246 G_CALLBACK (on_selection_changed), self);
1248 g_signal_connect (self, "expose-event", G_CALLBACK (modest_folder_view_on_map), NULL);
1250 return GTK_WIDGET(self);
1253 /* this feels dirty; any other way to expand all the root items? */
1255 expand_root_items (ModestFolderView *self)
1258 GtkTreeModel *model;
1261 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1262 path = gtk_tree_path_new_first ();
1264 /* all folders should have child items, so.. */
1266 gtk_tree_view_expand_row (GTK_TREE_VIEW(self), path, FALSE);
1267 gtk_tree_path_next (path);
1268 } while (gtk_tree_model_get_iter (model, &iter, path));
1270 gtk_tree_path_free (path);
1274 * We use this function to implement the
1275 * MODEST_FOLDER_VIEW_STYLE_SHOW_ONE style. We only show the default
1276 * account in this case, and the local folders.
1279 filter_row (GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
1281 ModestFolderViewPrivate *priv;
1282 gboolean retval = TRUE;
1283 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1284 GObject *instance = NULL;
1285 const gchar *id = NULL;
1287 gboolean found = FALSE;
1288 gboolean cleared = FALSE;
1290 g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (data), FALSE);
1291 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (data);
1293 gtk_tree_model_get (model, iter,
1294 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
1295 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
1298 /* Do not show if there is no instance, this could indeed
1299 happen when the model is being modified while it's being
1300 drawn. This could occur for example when moving folders
1305 if (type == TNY_FOLDER_TYPE_ROOT) {
1306 /* TNY_FOLDER_TYPE_ROOT means that the instance is an
1307 account instead of a folder. */
1308 if (TNY_IS_ACCOUNT (instance)) {
1309 TnyAccount *acc = TNY_ACCOUNT (instance);
1310 const gchar *account_id = tny_account_get_id (acc);
1312 /* If it isn't a special folder,
1313 * don't show it unless it is the visible account: */
1314 if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE &&
1315 !modest_tny_account_is_virtual_local_folders (acc) &&
1316 strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
1318 /* Show only the visible account id */
1319 if (priv->visible_account_id) {
1320 if (strcmp (account_id, priv->visible_account_id))
1327 /* Never show these to the user. They are merged into one folder
1328 * in the local-folders account instead: */
1329 if (retval && MODEST_IS_TNY_OUTBOX_ACCOUNT (acc))
1334 /* Check hiding (if necessary) */
1335 cleared = modest_email_clipboard_cleared (priv->clipboard);
1336 if ((retval) && (!cleared) && (TNY_IS_FOLDER (instance))) {
1337 id = tny_folder_get_id (TNY_FOLDER(instance));
1338 if (priv->hidding_ids != NULL)
1339 for (i=0; i < priv->n_selected && !found; i++)
1340 if (priv->hidding_ids[i] != NULL && id != NULL)
1341 found = (!strcmp (priv->hidding_ids[i], id));
1347 /* If this is a move to dialog, hide Sent, Outbox and Drafts
1348 folder as no message can be move there according to UI specs */
1349 if (!priv->show_non_move) {
1351 case TNY_FOLDER_TYPE_OUTBOX:
1352 case TNY_FOLDER_TYPE_SENT:
1353 case TNY_FOLDER_TYPE_DRAFTS:
1356 case TNY_FOLDER_TYPE_UNKNOWN:
1357 case TNY_FOLDER_TYPE_NORMAL:
1358 type = modest_tny_folder_guess_folder_type(TNY_FOLDER(instance));
1359 if (type == TNY_FOLDER_TYPE_INVALID)
1360 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1362 if (type == TNY_FOLDER_TYPE_OUTBOX ||
1363 type == TNY_FOLDER_TYPE_SENT
1364 || type == TNY_FOLDER_TYPE_DRAFTS)
1373 g_object_unref (instance);
1380 modest_folder_view_update_model (ModestFolderView *self,
1381 TnyAccountStore *account_store)
1383 ModestFolderViewPrivate *priv;
1384 GtkTreeModel *model /* , *old_model */;
1385 GtkTreeModel *filter_model = NULL, *sortable = NULL;
1387 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW (self), FALSE);
1388 g_return_val_if_fail (account_store && MODEST_IS_TNY_ACCOUNT_STORE(account_store),
1391 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1393 /* Notify that there is no folder selected */
1394 g_signal_emit (G_OBJECT(self),
1395 signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
1397 if (priv->cur_folder_store) {
1398 g_object_unref (priv->cur_folder_store);
1399 priv->cur_folder_store = NULL;
1402 /* FIXME: the local accounts are not shown when the query
1403 selects only the subscribed folders */
1404 model = tny_gtk_folder_store_tree_model_new (NULL);
1406 /* Get the accounts: */
1407 tny_account_store_get_accounts (TNY_ACCOUNT_STORE(account_store),
1409 TNY_ACCOUNT_STORE_STORE_ACCOUNTS);
1411 sortable = gtk_tree_model_sort_new_with_model (model);
1412 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
1413 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
1414 GTK_SORT_ASCENDING);
1415 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
1416 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
1417 cmp_rows, NULL, NULL);
1419 /* Create filter model */
1420 filter_model = gtk_tree_model_filter_new (sortable, NULL);
1421 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
1427 gtk_tree_view_set_model (GTK_TREE_VIEW(self), filter_model);
1428 g_signal_connect (G_OBJECT(filter_model), "row-inserted",
1429 (GCallback) on_row_inserted_maybe_select_folder, self);
1431 g_object_unref (model);
1432 g_object_unref (filter_model);
1433 g_object_unref (sortable);
1435 /* Force a reselection of the INBOX next time the widget is shown */
1436 priv->reselect = TRUE;
1443 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
1445 GtkTreeModel *model = NULL;
1446 TnyFolderStore *folder = NULL;
1448 ModestFolderView *tree_view = NULL;
1449 ModestFolderViewPrivate *priv = NULL;
1450 gboolean selected = FALSE;
1452 g_return_if_fail (sel);
1453 g_return_if_fail (user_data);
1455 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
1457 selected = gtk_tree_selection_get_selected (sel, &model, &iter);
1459 tree_view = MODEST_FOLDER_VIEW (user_data);
1462 gtk_tree_model_get (model, &iter,
1463 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
1466 /* If the folder is the same do not notify */
1467 if (folder && priv->cur_folder_store == folder) {
1468 g_object_unref (folder);
1473 /* Current folder was unselected */
1474 if (priv->cur_folder_store) {
1475 /* We must do this firstly because a libtinymail-camel
1476 implementation detail. If we issue the signal
1477 before doing the sync_async, then that signal could
1478 cause (and it actually does it) a free of the
1479 summary of the folder (because the main window will
1480 clear the headers view */
1481 if (TNY_IS_FOLDER(priv->cur_folder_store))
1482 tny_folder_sync_async (TNY_FOLDER(priv->cur_folder_store),
1483 FALSE, NULL, NULL, NULL);
1485 g_signal_emit (G_OBJECT(tree_view), signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
1486 priv->cur_folder_store, FALSE);
1488 g_object_unref (priv->cur_folder_store);
1489 priv->cur_folder_store = NULL;
1492 /* New current references */
1493 priv->cur_folder_store = folder;
1495 /* New folder has been selected. Do not notify if there is
1496 nothing new selected */
1498 g_signal_emit (G_OBJECT(tree_view),
1499 signals[FOLDER_SELECTION_CHANGED_SIGNAL],
1500 0, priv->cur_folder_store, TRUE);
1505 modest_folder_view_get_selected (ModestFolderView *self)
1507 ModestFolderViewPrivate *priv;
1509 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW(self), NULL);
1511 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1512 if (priv->cur_folder_store)
1513 g_object_ref (priv->cur_folder_store);
1515 return priv->cur_folder_store;
1519 get_cmp_rows_type_pos (GObject *folder)
1521 /* Remote accounts -> Local account -> MMC account .*/
1524 if (TNY_IS_ACCOUNT (folder) &&
1525 modest_tny_account_is_virtual_local_folders (
1526 TNY_ACCOUNT (folder))) {
1528 } else if (TNY_IS_ACCOUNT (folder)) {
1529 TnyAccount *account = TNY_ACCOUNT (folder);
1530 const gchar *account_id = tny_account_get_id (account);
1531 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
1537 printf ("DEBUG: %s: unexpected type.\n", __FUNCTION__);
1538 return -1; /* Should never happen */
1543 get_cmp_subfolder_type_pos (TnyFolderType t)
1545 /* Inbox, Outbox, Drafts, Sent, User */
1549 case TNY_FOLDER_TYPE_INBOX:
1552 case TNY_FOLDER_TYPE_OUTBOX:
1555 case TNY_FOLDER_TYPE_DRAFTS:
1558 case TNY_FOLDER_TYPE_SENT:
1567 * This function orders the mail accounts according to these rules:
1568 * 1st - remote accounts
1569 * 2nd - local account
1573 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1577 gchar *name1 = NULL;
1578 gchar *name2 = NULL;
1579 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1580 TnyFolderType type2 = TNY_FOLDER_TYPE_UNKNOWN;
1581 GObject *folder1 = NULL;
1582 GObject *folder2 = NULL;
1584 gtk_tree_model_get (tree_model, iter1,
1585 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name1,
1586 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
1587 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder1,
1589 gtk_tree_model_get (tree_model, iter2,
1590 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name2,
1591 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type2,
1592 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder2,
1595 /* Return if we get no folder. This could happen when folder
1596 operations are happening. The model is updated after the
1597 folder copy/move actually occurs, so there could be
1598 situations where the model to be drawn is not correct */
1599 if (!folder1 || !folder2)
1602 if (type == TNY_FOLDER_TYPE_ROOT) {
1603 /* Compare the types, so that
1604 * Remote accounts -> Local account -> MMC account .*/
1605 const gint pos1 = get_cmp_rows_type_pos (folder1);
1606 const gint pos2 = get_cmp_rows_type_pos (folder2);
1607 /* printf ("DEBUG: %s:\n type1=%s, pos1=%d\n type2=%s, pos2=%d\n",
1608 __FUNCTION__, G_OBJECT_TYPE_NAME(folder1), pos1, G_OBJECT_TYPE_NAME(folder2), pos2); */
1611 else if (pos1 > pos2)
1614 /* Compare items of the same type: */
1616 TnyAccount *account1 = NULL;
1617 if (TNY_IS_ACCOUNT (folder1))
1618 account1 = TNY_ACCOUNT (folder1);
1620 TnyAccount *account2 = NULL;
1621 if (TNY_IS_ACCOUNT (folder2))
1622 account2 = TNY_ACCOUNT (folder2);
1624 const gchar *account_id = account1 ? tny_account_get_id (account1) : NULL;
1625 const gchar *account_id2 = account2 ? tny_account_get_id (account2) : NULL;
1627 if (!account_id && !account_id2) {
1629 } else if (!account_id) {
1631 } else if (!account_id2) {
1633 } else if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
1636 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
1640 gint cmp1 = 0, cmp2 = 0;
1641 /* get the parent to know if it's a local folder */
1644 gboolean has_parent;
1645 has_parent = gtk_tree_model_iter_parent (tree_model, &parent, iter1);
1647 GObject *parent_folder;
1648 TnyFolderType parent_type = TNY_FOLDER_TYPE_UNKNOWN;
1649 gtk_tree_model_get (tree_model, &parent,
1650 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &parent_type,
1651 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &parent_folder,
1653 if ((parent_type == TNY_FOLDER_TYPE_ROOT) &&
1654 TNY_IS_ACCOUNT (parent_folder)) {
1655 if (modest_tny_account_is_virtual_local_folders (TNY_ACCOUNT (parent_folder))) {
1656 cmp1 = get_cmp_subfolder_type_pos (modest_tny_folder_get_local_or_mmc_folder_type
1657 (TNY_FOLDER (folder1)));
1658 cmp2 = get_cmp_subfolder_type_pos (modest_tny_folder_get_local_or_mmc_folder_type
1659 (TNY_FOLDER (folder2)));
1660 } else if (modest_tny_account_is_memory_card_account (TNY_ACCOUNT (parent_folder))) {
1661 if (modest_local_folder_info_get_type (tny_folder_get_name (TNY_FOLDER (folder1))) == TNY_FOLDER_TYPE_ARCHIVE) {
1664 } else if (modest_local_folder_info_get_type (tny_folder_get_name (TNY_FOLDER (folder2))) == TNY_FOLDER_TYPE_ARCHIVE) {
1670 g_object_unref (parent_folder);
1673 /* if they are not local folders */
1675 cmp1 = get_cmp_subfolder_type_pos (tny_folder_get_folder_type (TNY_FOLDER (folder1)));
1676 cmp2 = get_cmp_subfolder_type_pos (tny_folder_get_folder_type (TNY_FOLDER (folder2)));
1680 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
1682 cmp = (cmp1 - cmp2);
1687 g_object_unref(G_OBJECT(folder1));
1689 g_object_unref(G_OBJECT(folder2));
1697 /*****************************************************************************/
1698 /* DRAG and DROP stuff */
1699 /*****************************************************************************/
1701 * This function fills the #GtkSelectionData with the row and the
1702 * model that has been dragged. It's called when this widget is a
1703 * source for dnd after the event drop happened
1706 on_drag_data_get (GtkWidget *widget, GdkDragContext *context, GtkSelectionData *selection_data,
1707 guint info, guint time, gpointer data)
1709 GtkTreeSelection *selection;
1710 GtkTreeModel *model;
1712 GtkTreePath *source_row;
1714 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1715 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
1717 source_row = gtk_tree_model_get_path (model, &iter);
1718 gtk_tree_set_row_drag_data (selection_data,
1722 gtk_tree_path_free (source_row);
1726 typedef struct _DndHelper {
1727 ModestFolderView *folder_view;
1728 gboolean delete_source;
1729 GtkTreePath *source_row;
1733 dnd_helper_destroyer (DndHelper *helper)
1735 /* Free the helper */
1736 gtk_tree_path_free (helper->source_row);
1737 g_slice_free (DndHelper, helper);
1741 xfer_folder_cb (ModestMailOperation *mail_op,
1742 TnyFolder *new_folder,
1746 /* Select the folder */
1747 modest_folder_view_select_folder (MODEST_FOLDER_VIEW (user_data),
1753 /* get the folder for the row the treepath refers to. */
1754 /* folder must be unref'd */
1755 static TnyFolderStore *
1756 tree_path_to_folder (GtkTreeModel *model, GtkTreePath *path)
1759 TnyFolderStore *folder = NULL;
1761 if (gtk_tree_model_get_iter (model,&iter, path))
1762 gtk_tree_model_get (model, &iter,
1763 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
1770 * This function is used by drag_data_received_cb to manage drag and
1771 * drop of a header, i.e, and drag from the header view to the folder
1775 drag_and_drop_from_header_view (GtkTreeModel *source_model,
1776 GtkTreeModel *dest_model,
1777 GtkTreePath *dest_row,
1778 GtkSelectionData *selection_data)
1780 TnyList *headers = NULL;
1781 TnyFolder *folder = NULL, *src_folder = NULL;
1782 TnyFolderType folder_type;
1783 GtkTreeIter source_iter, dest_iter;
1784 ModestWindowMgr *mgr = NULL;
1785 ModestWindow *main_win = NULL;
1786 gchar **uris, **tmp;
1788 /* Build the list of headers */
1789 mgr = modest_runtime_get_window_mgr ();
1790 headers = tny_simple_list_new ();
1791 uris = modest_dnd_selection_data_get_paths (selection_data);
1794 while (*tmp != NULL) {
1797 gboolean first = TRUE;
1800 path = gtk_tree_path_new_from_string (*tmp);
1801 gtk_tree_model_get_iter (source_model, &source_iter, path);
1802 gtk_tree_model_get (source_model, &source_iter,
1803 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1806 /* Do not enable d&d of headers already opened */
1807 if (!modest_window_mgr_find_registered_header(mgr, header, NULL))
1808 tny_list_append (headers, G_OBJECT (header));
1810 if (G_UNLIKELY (first)) {
1811 src_folder = tny_header_get_folder (header);
1815 /* Free and go on */
1816 gtk_tree_path_free (path);
1817 g_object_unref (header);
1822 /* Get the target folder */
1823 gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
1824 gtk_tree_model_get (dest_model, &dest_iter,
1825 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
1828 if (!folder || !TNY_IS_FOLDER(folder)) {
1829 /* g_warning ("%s: not a valid target folder (%p)", __FUNCTION__, folder); */
1833 folder_type = modest_tny_folder_guess_folder_type (folder);
1834 if (folder_type == TNY_FOLDER_TYPE_INVALID) {
1835 /* g_warning ("%s: invalid target folder", __FUNCTION__); */
1836 goto cleanup; /* cannot move messages there */
1839 if (modest_tny_folder_get_rules((TNY_FOLDER(folder))) & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE) {
1840 /* g_warning ("folder not writable"); */
1841 goto cleanup; /* verboten! */
1844 /* Ask for confirmation to move */
1845 main_win = modest_window_mgr_get_main_window (mgr, FALSE); /* don't create */
1847 g_warning ("%s: BUG: no main window found", __FUNCTION__);
1851 /* Transfer messages */
1852 modest_ui_actions_transfer_messages_helper (GTK_WINDOW (main_win), src_folder,
1857 if (G_IS_OBJECT (src_folder))
1858 g_object_unref (src_folder);
1859 if (G_IS_OBJECT(folder))
1860 g_object_unref (G_OBJECT (folder));
1861 if (G_IS_OBJECT(headers))
1862 g_object_unref (headers);
1866 TnyFolderStore *src_folder;
1867 TnyFolderStore *dst_folder;
1868 ModestFolderView *folder_view;
1873 dnd_folder_info_destroyer (DndFolderInfo *info)
1875 if (info->src_folder)
1876 g_object_unref (info->src_folder);
1877 if (info->dst_folder)
1878 g_object_unref (info->dst_folder);
1879 g_slice_free (DndFolderInfo, info);
1883 dnd_on_connection_failed_destroyer (DndFolderInfo *info,
1884 GtkWindow *parent_window,
1885 TnyAccount *account)
1888 modest_ui_actions_on_account_connection_error (parent_window, account);
1890 /* Free the helper & info */
1891 dnd_helper_destroyer (info->helper);
1892 dnd_folder_info_destroyer (info);
1896 drag_and_drop_from_folder_view_src_folder_performer (gboolean canceled,
1898 GtkWindow *parent_window,
1899 TnyAccount *account,
1902 DndFolderInfo *info = NULL;
1903 ModestMailOperation *mail_op;
1905 info = (DndFolderInfo *) user_data;
1907 if (err || canceled) {
1908 dnd_on_connection_failed_destroyer (info, parent_window, account);
1912 /* Do the mail operation */
1913 mail_op = modest_mail_operation_new_with_error_handling ((GObject *) parent_window,
1914 modest_ui_actions_move_folder_error_handler,
1915 info->src_folder, NULL);
1917 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1920 /* Transfer the folder */
1921 modest_mail_operation_xfer_folder (mail_op,
1922 TNY_FOLDER (info->src_folder),
1924 info->helper->delete_source,
1926 info->helper->folder_view);
1929 g_object_unref (G_OBJECT (mail_op));
1930 dnd_helper_destroyer (info->helper);
1931 dnd_folder_info_destroyer (info);
1936 drag_and_drop_from_folder_view_dst_folder_performer (gboolean canceled,
1938 GtkWindow *parent_window,
1939 TnyAccount *account,
1942 DndFolderInfo *info = NULL;
1944 info = (DndFolderInfo *) user_data;
1946 if (err || canceled) {
1947 dnd_on_connection_failed_destroyer (info, parent_window, account);
1951 /* Connect to source folder and perform the copy/move */
1952 modest_platform_connect_if_remote_and_perform (NULL, TRUE,
1954 drag_and_drop_from_folder_view_src_folder_performer,
1959 * This function is used by drag_data_received_cb to manage drag and
1960 * drop of a folder, i.e, and drag from the folder view to the same
1964 drag_and_drop_from_folder_view (GtkTreeModel *source_model,
1965 GtkTreeModel *dest_model,
1966 GtkTreePath *dest_row,
1967 GtkSelectionData *selection_data,
1970 GtkTreeIter dest_iter, iter;
1971 TnyFolderStore *dest_folder = NULL;
1972 TnyFolderStore *folder = NULL;
1973 gboolean forbidden = FALSE;
1975 DndFolderInfo *info = NULL;
1977 win = modest_window_mgr_get_main_window (modest_runtime_get_window_mgr(), FALSE); /* don't create */
1979 g_warning ("%s: BUG: no main window", __FUNCTION__);
1980 dnd_helper_destroyer (helper);
1985 /* check the folder rules for the destination */
1986 folder = tree_path_to_folder (dest_model, dest_row);
1987 if (TNY_IS_FOLDER(folder)) {
1988 ModestTnyFolderRules rules =
1989 modest_tny_folder_get_rules (TNY_FOLDER (folder));
1990 forbidden = rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE;
1991 } else if (TNY_IS_FOLDER_STORE(folder)) {
1992 /* enable local root as destination for folders */
1993 if (!MODEST_IS_TNY_LOCAL_FOLDERS_ACCOUNT (folder) &&
1994 !modest_tny_account_is_memory_card_account (TNY_ACCOUNT (folder)))
1997 g_object_unref (folder);
2000 /* check the folder rules for the source */
2001 folder = tree_path_to_folder (source_model, helper->source_row);
2002 if (TNY_IS_FOLDER(folder)) {
2003 ModestTnyFolderRules rules =
2004 modest_tny_folder_get_rules (TNY_FOLDER (folder));
2005 forbidden = rules & MODEST_FOLDER_RULES_FOLDER_NON_MOVEABLE;
2008 g_object_unref (folder);
2012 /* Check if the drag is possible */
2013 if (forbidden || !gtk_tree_path_compare (helper->source_row, dest_row)) {
2014 dnd_helper_destroyer (helper);
2019 gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
2020 gtk_tree_model_get (dest_model, &dest_iter,
2021 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
2023 gtk_tree_model_get_iter (source_model, &iter, helper->source_row);
2024 gtk_tree_model_get (source_model, &iter,
2025 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
2028 /* Create the info for the performer */
2029 info = g_slice_new0 (DndFolderInfo);
2030 info->src_folder = g_object_ref (folder);
2031 info->dst_folder = g_object_ref (dest_folder);
2032 info->helper = helper;
2034 /* Connect to the destination folder and perform the copy/move */
2035 modest_platform_connect_if_remote_and_perform (GTK_WINDOW (win), TRUE,
2037 drag_and_drop_from_folder_view_dst_folder_performer,
2041 g_object_unref (dest_folder);
2042 g_object_unref (folder);
2046 * This function receives the data set by the "drag-data-get" signal
2047 * handler. This information comes within the #GtkSelectionData. This
2048 * function will manage both the drags of folders of the treeview and
2049 * drags of headers of the header view widget.
2052 on_drag_data_received (GtkWidget *widget,
2053 GdkDragContext *context,
2056 GtkSelectionData *selection_data,
2061 GtkWidget *source_widget;
2062 GtkTreeModel *dest_model, *source_model;
2063 GtkTreePath *source_row, *dest_row;
2064 GtkTreeViewDropPosition pos;
2065 gboolean delete_source = FALSE;
2066 gboolean success = FALSE;
2068 /* Do not allow further process */
2069 g_signal_stop_emission_by_name (widget, "drag-data-received");
2070 source_widget = gtk_drag_get_source_widget (context);
2072 /* Get the action */
2073 if (context->action == GDK_ACTION_MOVE) {
2074 delete_source = TRUE;
2076 /* Notify that there is no folder selected. We need to
2077 do this in order to update the headers view (and
2078 its monitors, because when moving, the old folder
2079 won't longer exist. We can not wait for the end of
2080 the operation, because the operation won't start if
2081 the folder is in use */
2082 if (source_widget == widget) {
2083 GtkTreeSelection *sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
2084 gtk_tree_selection_unselect_all (sel);
2088 /* Check if the get_data failed */
2089 if (selection_data == NULL || selection_data->length < 0)
2092 /* Select the destination model */
2093 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
2095 /* Get the path to the destination row. Can not call
2096 gtk_tree_view_get_drag_dest_row() because the source row
2097 is not selected anymore */
2098 gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget), x, y,
2101 /* Only allow drops IN other rows */
2103 pos == GTK_TREE_VIEW_DROP_BEFORE ||
2104 pos == GTK_TREE_VIEW_DROP_AFTER)
2108 /* Drags from the header view */
2109 if (source_widget != widget) {
2110 source_model = gtk_tree_view_get_model (GTK_TREE_VIEW (source_widget));
2112 drag_and_drop_from_header_view (source_model,
2117 DndHelper *helper = NULL;
2119 /* Get the source model and row */
2120 gtk_tree_get_row_drag_data (selection_data,
2124 /* Create the helper */
2125 helper = g_slice_new0 (DndHelper);
2126 helper->delete_source = delete_source;
2127 helper->source_row = gtk_tree_path_copy (source_row);
2128 helper->folder_view = MODEST_FOLDER_VIEW (widget);
2130 drag_and_drop_from_folder_view (source_model,
2136 gtk_tree_path_free (source_row);
2140 gtk_tree_path_free (dest_row);
2143 /* Finish the drag and drop */
2144 gtk_drag_finish (context, success, FALSE, time);
2148 * We define a "drag-drop" signal handler because we do not want to
2149 * use the default one, because the default one always calls
2150 * gtk_drag_finish and we prefer to do it in the "drag-data-received"
2151 * signal handler, because there we have all the information available
2152 * to know if the dnd was a success or not.
2155 drag_drop_cb (GtkWidget *widget,
2156 GdkDragContext *context,
2164 if (!context->targets)
2167 /* Check if we're dragging a folder row */
2168 target = gtk_drag_dest_find_target (widget, context, NULL);
2170 /* Request the data from the source. */
2171 gtk_drag_get_data(widget, context, target, time);
2177 * This function expands a node of a tree view if it's not expanded
2178 * yet. Not sure why it needs the threads stuff, but gtk+`example code
2179 * does that, so that's why they're here.
2182 expand_row_timeout (gpointer data)
2184 GtkTreeView *tree_view = data;
2185 GtkTreePath *dest_path = NULL;
2186 GtkTreeViewDropPosition pos;
2187 gboolean result = FALSE;
2189 gdk_threads_enter ();
2191 gtk_tree_view_get_drag_dest_row (tree_view,
2196 (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER ||
2197 pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)) {
2198 gtk_tree_view_expand_row (tree_view, dest_path, FALSE);
2199 gtk_tree_path_free (dest_path);
2203 gtk_tree_path_free (dest_path);
2208 gdk_threads_leave ();
2214 * This function is called whenever the pointer is moved over a widget
2215 * while dragging some data. It installs a timeout that will expand a
2216 * node of the treeview if not expanded yet. This function also calls
2217 * gdk_drag_status in order to set the suggested action that will be
2218 * used by the "drag-data-received" signal handler to know if we
2219 * should do a move or just a copy of the data.
2222 on_drag_motion (GtkWidget *widget,
2223 GdkDragContext *context,
2229 GtkTreeViewDropPosition pos;
2230 GtkTreePath *dest_row;
2231 GtkTreeModel *dest_model;
2232 ModestFolderViewPrivate *priv;
2233 GdkDragAction suggested_action;
2234 gboolean valid_location = FALSE;
2235 TnyFolderStore *folder = NULL;
2237 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
2239 if (priv->timer_expander != 0) {
2240 g_source_remove (priv->timer_expander);
2241 priv->timer_expander = 0;
2244 gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
2249 /* Do not allow drops between folders */
2251 pos == GTK_TREE_VIEW_DROP_BEFORE ||
2252 pos == GTK_TREE_VIEW_DROP_AFTER) {
2253 gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW (widget), NULL, 0);
2254 gdk_drag_status(context, 0, time);
2255 valid_location = FALSE;
2258 valid_location = TRUE;
2261 /* Check that the destination folder is writable */
2262 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
2263 folder = tree_path_to_folder (dest_model, dest_row);
2264 if (folder && TNY_IS_FOLDER (folder)) {
2265 ModestTnyFolderRules rules = modest_tny_folder_get_rules(TNY_FOLDER (folder));
2267 if (rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE) {
2268 valid_location = FALSE;
2273 /* Expand the selected row after 1/2 second */
2274 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), dest_row)) {
2275 priv->timer_expander = g_timeout_add (500, expand_row_timeout, widget);
2277 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), dest_row, pos);
2279 /* Select the desired action. By default we pick MOVE */
2280 suggested_action = GDK_ACTION_MOVE;
2282 if (context->actions == GDK_ACTION_COPY)
2283 gdk_drag_status(context, GDK_ACTION_COPY, time);
2284 else if (context->actions == GDK_ACTION_MOVE)
2285 gdk_drag_status(context, GDK_ACTION_MOVE, time);
2286 else if (context->actions & suggested_action)
2287 gdk_drag_status(context, suggested_action, time);
2289 gdk_drag_status(context, GDK_ACTION_DEFAULT, time);
2293 g_object_unref (folder);
2295 gtk_tree_path_free (dest_row);
2297 g_signal_stop_emission_by_name (widget, "drag-motion");
2299 return valid_location;
2303 * This function sets the treeview as a source and a target for dnd
2304 * events. It also connects all the requirede signals.
2307 setup_drag_and_drop (GtkTreeView *self)
2309 /* Set up the folder view as a dnd destination. Set only the
2310 highlight flag, otherwise gtk will have a different
2312 gtk_drag_dest_set (GTK_WIDGET (self),
2313 GTK_DEST_DEFAULT_HIGHLIGHT,
2314 folder_view_drag_types,
2315 G_N_ELEMENTS (folder_view_drag_types),
2316 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2318 g_signal_connect (G_OBJECT (self),
2319 "drag_data_received",
2320 G_CALLBACK (on_drag_data_received),
2324 /* Set up the treeview as a dnd source */
2325 gtk_drag_source_set (GTK_WIDGET (self),
2327 folder_view_drag_types,
2328 G_N_ELEMENTS (folder_view_drag_types),
2329 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2331 g_signal_connect (G_OBJECT (self),
2333 G_CALLBACK (on_drag_motion),
2336 g_signal_connect (G_OBJECT (self),
2338 G_CALLBACK (on_drag_data_get),
2341 g_signal_connect (G_OBJECT (self),
2343 G_CALLBACK (drag_drop_cb),
2348 * This function manages the navigation through the folders using the
2349 * keyboard or the hardware keys in the device
2352 on_key_pressed (GtkWidget *self,
2356 GtkTreeSelection *selection;
2358 GtkTreeModel *model;
2359 gboolean retval = FALSE;
2361 /* Up and Down are automatically managed by the treeview */
2362 if (event->keyval == GDK_Return) {
2363 /* Expand/Collapse the selected row */
2364 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2365 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
2368 path = gtk_tree_model_get_path (model, &iter);
2370 if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (self), path))
2371 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
2373 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
2374 gtk_tree_path_free (path);
2376 /* No further processing */
2384 * We listen to the changes in the local folder account name key,
2385 * because we want to show the right name in the view. The local
2386 * folder account name corresponds to the device name in the Maemo
2387 * version. We do this because we do not want to query gconf on each
2388 * tree view refresh. It's better to cache it and change whenever
2392 on_configuration_key_changed (ModestConf* conf,
2394 ModestConfEvent event,
2395 ModestConfNotificationId id,
2396 ModestFolderView *self)
2398 ModestFolderViewPrivate *priv;
2401 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
2402 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2404 if (!strcmp (key, MODEST_CONF_DEVICE_NAME)) {
2405 g_free (priv->local_account_name);
2407 if (event == MODEST_CONF_EVENT_KEY_UNSET)
2408 priv->local_account_name = g_strdup (MODEST_LOCAL_FOLDERS_DEFAULT_DISPLAY_NAME);
2410 priv->local_account_name = modest_conf_get_string (modest_runtime_get_conf(),
2411 MODEST_CONF_DEVICE_NAME, NULL);
2413 /* Force a redraw */
2414 #if GTK_CHECK_VERSION(2, 8, 0)
2415 GtkTreeViewColumn * tree_column;
2417 tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self),
2418 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN);
2419 gtk_tree_view_column_queue_resize (tree_column);
2421 gtk_widget_queue_draw (GTK_WIDGET (self));
2427 modest_folder_view_set_style (ModestFolderView *self,
2428 ModestFolderViewStyle style)
2430 ModestFolderViewPrivate *priv;
2432 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2433 g_return_if_fail (style == MODEST_FOLDER_VIEW_STYLE_SHOW_ALL ||
2434 style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE);
2436 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2439 priv->style = style;
2443 modest_folder_view_set_account_id_of_visible_server_account (ModestFolderView *self,
2444 const gchar *account_id)
2446 ModestFolderViewPrivate *priv;
2447 GtkTreeModel *model;
2449 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2451 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2453 /* This will be used by the filter_row callback,
2454 * to decided which rows to show: */
2455 if (priv->visible_account_id) {
2456 g_free (priv->visible_account_id);
2457 priv->visible_account_id = NULL;
2460 priv->visible_account_id = g_strdup (account_id);
2463 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2464 if (GTK_IS_TREE_MODEL_FILTER (model))
2465 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2467 /* Save settings to gconf */
2468 modest_widget_memory_save (modest_runtime_get_conf (), G_OBJECT(self),
2469 MODEST_CONF_FOLDER_VIEW_KEY);
2473 modest_folder_view_get_account_id_of_visible_server_account (ModestFolderView *self)
2475 ModestFolderViewPrivate *priv;
2477 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW(self), NULL);
2479 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2481 return (const gchar *) priv->visible_account_id;
2485 find_inbox_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *inbox_iter)
2489 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2491 gtk_tree_model_get (model, iter,
2492 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN,
2495 gboolean result = FALSE;
2496 if (type == TNY_FOLDER_TYPE_INBOX) {
2500 *inbox_iter = *iter;
2504 if (gtk_tree_model_iter_children (model, &child, iter)) {
2505 if (find_inbox_iter (model, &child, inbox_iter))
2509 } while (gtk_tree_model_iter_next (model, iter));
2518 modest_folder_view_select_first_inbox_or_local (ModestFolderView *self)
2520 GtkTreeModel *model;
2521 GtkTreeIter iter, inbox_iter;
2522 GtkTreeSelection *sel;
2523 GtkTreePath *path = NULL;
2525 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2527 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2531 expand_root_items (self);
2532 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2534 if (!gtk_tree_model_get_iter_first (model, &iter)) {
2535 g_warning ("%s: model is empty", __FUNCTION__);
2539 if (find_inbox_iter (model, &iter, &inbox_iter))
2540 path = gtk_tree_model_get_path (model, &inbox_iter);
2542 path = gtk_tree_path_new_first ();
2544 /* Select the row and free */
2545 gtk_tree_view_set_cursor (GTK_TREE_VIEW (self), path, NULL, FALSE);
2546 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (self), path, NULL, FALSE, 0.0, 0.0);
2547 gtk_tree_path_free (path);
2550 gtk_widget_grab_focus (GTK_WIDGET(self));
2556 find_folder_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *folder_iter,
2561 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2562 TnyFolder* a_folder;
2565 gtk_tree_model_get (model, iter,
2566 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &a_folder,
2567 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name,
2568 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
2572 if (folder == a_folder) {
2573 g_object_unref (a_folder);
2574 *folder_iter = *iter;
2577 g_object_unref (a_folder);
2579 if (gtk_tree_model_iter_children (model, &child, iter)) {
2580 if (find_folder_iter (model, &child, folder_iter, folder))
2584 } while (gtk_tree_model_iter_next (model, iter));
2591 on_row_inserted_maybe_select_folder (GtkTreeModel *tree_model,
2594 ModestFolderView *self)
2596 ModestFolderViewPrivate *priv = NULL;
2597 GtkTreeSelection *sel;
2598 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2599 GObject *instance = NULL;
2601 if (!MODEST_IS_FOLDER_VIEW(self))
2604 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2606 priv->reexpand = TRUE;
2608 gtk_tree_model_get (tree_model, iter,
2609 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
2610 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
2612 if (type == TNY_FOLDER_TYPE_INBOX && priv->folder_to_select == NULL) {
2613 priv->folder_to_select = g_object_ref (instance);
2615 g_object_unref (instance);
2617 if (priv->folder_to_select) {
2619 if (!modest_folder_view_select_folder (self, priv->folder_to_select,
2622 path = gtk_tree_model_get_path (tree_model, iter);
2623 gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
2625 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2627 gtk_tree_selection_select_iter (sel, iter);
2628 gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
2630 gtk_tree_path_free (path);
2634 modest_folder_view_disable_next_folder_selection (self);
2636 /* Refilter the model */
2637 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (tree_model));
2643 modest_folder_view_disable_next_folder_selection (ModestFolderView *self)
2645 ModestFolderViewPrivate *priv;
2647 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2649 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2651 if (priv->folder_to_select)
2652 g_object_unref(priv->folder_to_select);
2654 priv->folder_to_select = NULL;
2658 modest_folder_view_select_folder (ModestFolderView *self, TnyFolder *folder,
2659 gboolean after_change)
2661 GtkTreeModel *model;
2662 GtkTreeIter iter, folder_iter;
2663 GtkTreeSelection *sel;
2664 ModestFolderViewPrivate *priv = NULL;
2666 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW (self), FALSE);
2667 g_return_val_if_fail (folder && TNY_IS_FOLDER (folder), FALSE);
2669 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2672 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2673 gtk_tree_selection_unselect_all (sel);
2675 if (priv->folder_to_select)
2676 g_object_unref(priv->folder_to_select);
2677 priv->folder_to_select = TNY_FOLDER(g_object_ref(folder));
2681 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2686 /* Refilter the model, before selecting the folder */
2687 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2689 if (!gtk_tree_model_get_iter_first (model, &iter)) {
2690 g_warning ("%s: model is empty", __FUNCTION__);
2694 if (find_folder_iter (model, &iter, &folder_iter, folder)) {
2697 path = gtk_tree_model_get_path (model, &folder_iter);
2698 gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
2700 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2701 gtk_tree_selection_select_iter (sel, &folder_iter);
2702 gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
2704 gtk_tree_path_free (path);
2712 modest_folder_view_copy_selection (ModestFolderView *self)
2714 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2716 /* Copy selection */
2717 _clipboard_set_selected_data (self, FALSE);
2721 modest_folder_view_cut_selection (ModestFolderView *folder_view)
2723 ModestFolderViewPrivate *priv = NULL;
2724 GtkTreeModel *model = NULL;
2725 const gchar **hidding = NULL;
2726 guint i, n_selected;
2728 g_return_if_fail (folder_view && MODEST_IS_FOLDER_VIEW (folder_view));
2729 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
2731 /* Copy selection */
2732 if (!_clipboard_set_selected_data (folder_view, TRUE))
2735 /* Get hidding ids */
2736 hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected);
2738 /* Clear hidding array created by previous cut operation */
2739 _clear_hidding_filter (MODEST_FOLDER_VIEW (folder_view));
2741 /* Copy hidding array */
2742 priv->n_selected = n_selected;
2743 priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
2744 for (i=0; i < n_selected; i++)
2745 priv->hidding_ids[i] = g_strdup(hidding[i]);
2747 /* Hide cut folders */
2748 model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
2749 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2753 modest_folder_view_copy_model (ModestFolderView *folder_view_src,
2754 ModestFolderView *folder_view_dst)
2756 GtkTreeModel *filter_model = NULL;
2757 GtkTreeModel *model = NULL;
2758 GtkTreeModel *new_filter_model = NULL;
2760 g_return_if_fail (folder_view_src && MODEST_IS_FOLDER_VIEW (folder_view_src));
2761 g_return_if_fail (folder_view_dst && MODEST_IS_FOLDER_VIEW (folder_view_dst));
2764 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view_src));
2765 model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER(filter_model));
2767 /* Build new filter model */
2768 new_filter_model = gtk_tree_model_filter_new (model, NULL);
2769 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (new_filter_model),
2773 /* Set copied model */
2774 gtk_tree_view_set_model (GTK_TREE_VIEW (folder_view_dst), new_filter_model);
2775 g_signal_connect (G_OBJECT(new_filter_model), "row-inserted",
2776 (GCallback) on_row_inserted_maybe_select_folder, folder_view_dst);
2779 g_object_unref (new_filter_model);
2783 modest_folder_view_show_non_move_folders (ModestFolderView *folder_view,
2786 GtkTreeModel *model = NULL;
2787 ModestFolderViewPrivate* priv;
2789 g_return_if_fail (folder_view && MODEST_IS_FOLDER_VIEW (folder_view));
2791 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
2792 priv->show_non_move = show;
2793 /* modest_folder_view_update_model(folder_view, */
2794 /* TNY_ACCOUNT_STORE(modest_runtime_get_account_store())); */
2796 /* Hide special folders */
2797 model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
2798 if (GTK_IS_TREE_MODEL_FILTER (model)) {
2799 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2803 /* Returns FALSE if it did not selected anything */
2805 _clipboard_set_selected_data (ModestFolderView *folder_view,
2808 ModestFolderViewPrivate *priv = NULL;
2809 TnyFolderStore *folder = NULL;
2810 gboolean retval = FALSE;
2812 g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (folder_view), FALSE);
2813 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
2815 /* Set selected data on clipboard */
2816 g_return_val_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard), FALSE);
2817 folder = modest_folder_view_get_selected (folder_view);
2819 /* Do not allow to select an account */
2820 if (TNY_IS_FOLDER (folder)) {
2821 modest_email_clipboard_set_data (priv->clipboard, TNY_FOLDER(folder), NULL, delete);
2826 g_object_unref (folder);
2832 _clear_hidding_filter (ModestFolderView *folder_view)
2834 ModestFolderViewPrivate *priv;
2837 g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view));
2838 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
2840 if (priv->hidding_ids != NULL) {
2841 for (i=0; i < priv->n_selected; i++)
2842 g_free (priv->hidding_ids[i]);
2843 g_free(priv->hidding_ids);
2849 on_display_name_changed (ModestAccountMgr *mgr,
2850 const gchar *account,
2853 ModestFolderView *self;
2855 self = MODEST_FOLDER_VIEW (user_data);
2857 /* Force a redraw */
2858 #if GTK_CHECK_VERSION(2, 8, 0)
2859 GtkTreeViewColumn * tree_column;
2861 tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self),
2862 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN);
2863 gtk_tree_view_column_queue_resize (tree_column);
2865 gtk_widget_queue_draw (GTK_WIDGET (self));