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);
719 add_columns (GtkWidget *treeview)
721 GtkTreeViewColumn *column;
722 GtkCellRenderer *renderer;
723 GtkTreeSelection *sel;
726 column = gtk_tree_view_column_new ();
728 /* Set icon and text render function */
729 renderer = gtk_cell_renderer_pixbuf_new();
730 gtk_tree_view_column_pack_start (column, renderer, FALSE);
731 gtk_tree_view_column_set_cell_data_func(column, renderer,
732 icon_cell_data, treeview, NULL);
734 renderer = gtk_cell_renderer_text_new();
735 g_object_set (renderer, "ellipsize", PANGO_ELLIPSIZE_END,
736 "ellipsize-set", TRUE, NULL);
737 gtk_tree_view_column_pack_start (column, renderer, TRUE);
738 gtk_tree_view_column_set_cell_data_func(column, renderer,
739 text_cell_data, treeview, NULL);
741 /* Set selection mode */
742 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(treeview));
743 gtk_tree_selection_set_mode (sel, GTK_SELECTION_SINGLE);
745 /* Set treeview appearance */
746 gtk_tree_view_column_set_spacing (column, 2);
747 gtk_tree_view_column_set_resizable (column, TRUE);
748 gtk_tree_view_column_set_fixed_width (column, TRUE);
749 gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(treeview), FALSE);
750 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(treeview), FALSE);
753 gtk_tree_view_append_column (GTK_TREE_VIEW(treeview),column);
757 modest_folder_view_init (ModestFolderView *obj)
759 ModestFolderViewPrivate *priv;
762 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
764 priv->timer_expander = 0;
765 priv->account_store = NULL;
767 priv->style = MODEST_FOLDER_VIEW_STYLE_SHOW_ALL;
768 priv->cur_folder_store = NULL;
769 priv->visible_account_id = NULL;
770 priv->folder_to_select = NULL;
772 priv->reexpand = TRUE;
774 /* Initialize the local account name */
775 conf = modest_runtime_get_conf();
776 priv->local_account_name = modest_conf_get_string (conf, MODEST_CONF_DEVICE_NAME, NULL);
778 /* Init email clipboard */
779 priv->clipboard = modest_runtime_get_email_clipboard ();
780 priv->hidding_ids = NULL;
781 priv->n_selected = 0;
782 priv->reselect = FALSE;
783 priv->show_non_move = TRUE;
786 add_columns (GTK_WIDGET (obj));
788 /* Setup drag and drop */
789 setup_drag_and_drop (GTK_TREE_VIEW(obj));
791 /* Connect signals */
792 g_signal_connect (G_OBJECT (obj),
794 G_CALLBACK (on_key_pressed), NULL);
796 priv->display_name_changed_signal =
797 g_signal_connect (modest_runtime_get_account_mgr (),
798 "display_name_changed",
799 G_CALLBACK (on_display_name_changed),
803 * Track changes in the local account name (in the device it
804 * will be the device name)
806 priv->conf_key_signal = g_signal_connect (G_OBJECT(conf),
808 G_CALLBACK(on_configuration_key_changed),
813 tny_account_store_view_init (gpointer g, gpointer iface_data)
815 TnyAccountStoreViewIface *klass = (TnyAccountStoreViewIface *)g;
817 klass->set_account_store = modest_folder_view_set_account_store;
821 modest_folder_view_finalize (GObject *obj)
823 ModestFolderViewPrivate *priv;
824 GtkTreeSelection *sel;
826 g_return_if_fail (obj);
828 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
830 if (priv->timer_expander != 0) {
831 g_source_remove (priv->timer_expander);
832 priv->timer_expander = 0;
835 if (priv->account_store) {
836 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
837 priv->account_inserted_signal);
838 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
839 priv->account_removed_signal);
840 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
841 priv->account_changed_signal);
842 g_object_unref (G_OBJECT(priv->account_store));
843 priv->account_store = NULL;
847 g_object_unref (G_OBJECT (priv->query));
851 if (priv->folder_to_select) {
852 g_object_unref (G_OBJECT(priv->folder_to_select));
853 priv->folder_to_select = NULL;
856 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(obj));
858 g_signal_handler_disconnect (G_OBJECT(sel), priv->changed_signal);
860 g_free (priv->local_account_name);
861 g_free (priv->visible_account_id);
863 if (priv->conf_key_signal) {
864 g_signal_handler_disconnect (modest_runtime_get_conf (),
865 priv->conf_key_signal);
866 priv->conf_key_signal = 0;
869 if (priv->cur_folder_store) {
870 g_object_unref (priv->cur_folder_store);
871 priv->cur_folder_store = NULL;
874 /* Clear hidding array created by cut operation */
875 _clear_hidding_filter (MODEST_FOLDER_VIEW (obj));
877 G_OBJECT_CLASS(parent_class)->finalize (obj);
882 modest_folder_view_set_account_store (TnyAccountStoreView *self, TnyAccountStore *account_store)
884 ModestFolderViewPrivate *priv;
887 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
888 g_return_if_fail (TNY_IS_ACCOUNT_STORE (account_store));
890 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
891 device = tny_account_store_get_device (account_store);
893 if (G_UNLIKELY (priv->account_store)) {
895 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
896 priv->account_inserted_signal))
897 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
898 priv->account_inserted_signal);
899 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
900 priv->account_removed_signal))
901 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
902 priv->account_removed_signal);
903 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
904 priv->account_changed_signal))
905 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
906 priv->account_changed_signal);
907 g_object_unref (G_OBJECT (priv->account_store));
910 priv->account_store = g_object_ref (G_OBJECT (account_store));
912 priv->account_removed_signal =
913 g_signal_connect (G_OBJECT(account_store), "account_removed",
914 G_CALLBACK (on_account_removed), self);
916 priv->account_inserted_signal =
917 g_signal_connect (G_OBJECT(account_store), "account_inserted",
918 G_CALLBACK (on_account_inserted), self);
920 priv->account_changed_signal =
921 g_signal_connect (G_OBJECT(account_store), "account_changed",
922 G_CALLBACK (on_account_changed), self);
924 modest_folder_view_update_model (MODEST_FOLDER_VIEW (self), account_store);
925 priv->reselect = FALSE;
926 modest_folder_view_select_first_inbox_or_local (MODEST_FOLDER_VIEW (self));
928 g_object_unref (G_OBJECT (device));
932 on_account_inserted (TnyAccountStore *account_store,
936 ModestFolderViewPrivate *priv;
937 GtkTreeModel *sort_model, *filter_model;
939 /* Ignore transport account insertions, we're not showing them
940 in the folder view */
941 if (TNY_IS_TRANSPORT_ACCOUNT (account))
944 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (user_data);
947 /* If we're adding a new account, and there is no previous
948 one, we need to select the visible server account */
949 if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE &&
950 !priv->visible_account_id)
951 modest_widget_memory_restore (modest_runtime_get_conf(),
952 G_OBJECT (user_data),
953 MODEST_CONF_FOLDER_VIEW_KEY);
955 if (!GTK_IS_TREE_VIEW(user_data)) {
956 g_warning ("BUG: %s: not a valid tree view", __FUNCTION__);
960 /* Get the inner model */
961 /* check, is some rare cases, we did not get the right thing here,
963 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (user_data));
964 if (!GTK_IS_TREE_MODEL_FILTER(filter_model)) {
965 g_warning ("BUG: %s: not a valid filter model", __FUNCTION__);
969 /* check, is some rare cases, we did not get the right thing here,
971 sort_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
972 if (!GTK_IS_TREE_MODEL_SORT(sort_model)) {
973 g_warning ("BUG: %s: not a valid sort model", __FUNCTION__);
977 /* Insert the account in the model */
978 tny_list_append (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
981 /* Refilter the model */
982 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
987 same_account_selected (ModestFolderView *self,
990 ModestFolderViewPrivate *priv;
991 gboolean same_account = FALSE;
993 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
995 if (priv->cur_folder_store) {
996 TnyAccount *selected_folder_account = NULL;
998 if (TNY_IS_FOLDER (priv->cur_folder_store)) {
999 selected_folder_account =
1000 modest_tny_folder_get_account (TNY_FOLDER (priv->cur_folder_store));
1002 selected_folder_account =
1003 TNY_ACCOUNT (g_object_ref (priv->cur_folder_store));
1006 if (selected_folder_account == account)
1007 same_account = TRUE;
1009 g_object_unref (selected_folder_account);
1011 return same_account;
1016 * Selects the first inbox or the local account in an idle
1019 on_idle_select_first_inbox_or_local (gpointer user_data)
1021 ModestFolderView *self = MODEST_FOLDER_VIEW (user_data);
1023 gdk_threads_enter ();
1024 modest_folder_view_select_first_inbox_or_local (self);
1025 gdk_threads_leave ();
1031 on_account_changed (TnyAccountStore *account_store,
1032 TnyAccount *tny_account,
1035 ModestFolderView *self;
1036 ModestFolderViewPrivate *priv;
1037 GtkTreeModel *sort_model, *filter_model;
1038 GtkTreeSelection *sel;
1039 gboolean same_account;
1041 /* Ignore transport account insertions, we're not showing them
1042 in the folder view */
1043 if (TNY_IS_TRANSPORT_ACCOUNT (tny_account))
1046 if (!MODEST_IS_FOLDER_VIEW(user_data)) {
1047 g_warning ("BUG: %s: not a valid folder view", __FUNCTION__);
1051 self = MODEST_FOLDER_VIEW (user_data);
1052 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (user_data);
1054 /* Get the inner model */
1055 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (user_data));
1056 if (!GTK_IS_TREE_MODEL_FILTER(filter_model)) {
1057 g_warning ("BUG: %s: not a valid filter model", __FUNCTION__);
1061 sort_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
1062 if (!GTK_IS_TREE_MODEL_SORT(sort_model)) {
1063 g_warning ("BUG: %s: not a valid sort model", __FUNCTION__);
1067 /* Invalidate the cur_folder_store only if the selected folder
1068 belongs to the account that is being removed */
1069 same_account = same_account_selected (self, tny_account);
1071 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1072 gtk_tree_selection_unselect_all (sel);
1075 /* Remove the account from the model */
1076 tny_list_remove (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
1077 G_OBJECT (tny_account));
1079 /* Insert the account in the model */
1080 tny_list_append (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
1081 G_OBJECT (tny_account));
1083 /* Refilter the model */
1084 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1086 /* Select the first INBOX if the currently selected folder
1087 belongs to the account that is being deleted */
1088 if (same_account && !MODEST_IS_TNY_LOCAL_FOLDERS_ACCOUNT (tny_account))
1089 g_idle_add (on_idle_select_first_inbox_or_local, self);
1093 on_account_removed (TnyAccountStore *account_store,
1094 TnyAccount *account,
1097 ModestFolderView *self = NULL;
1098 ModestFolderViewPrivate *priv;
1099 GtkTreeModel *sort_model, *filter_model;
1100 GtkTreeSelection *sel = NULL;
1101 gboolean same_account = FALSE;
1103 /* Ignore transport account removals, we're not showing them
1104 in the folder view */
1105 if (TNY_IS_TRANSPORT_ACCOUNT (account))
1108 if (!MODEST_IS_FOLDER_VIEW(user_data)) {
1109 g_warning ("BUG: %s: not a valid folder view", __FUNCTION__);
1113 self = MODEST_FOLDER_VIEW (user_data);
1114 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1116 /* Invalidate the cur_folder_store only if the selected folder
1117 belongs to the account that is being removed */
1118 same_account = same_account_selected (self, account);
1120 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1121 gtk_tree_selection_unselect_all (sel);
1124 /* Invalidate row to select only if the folder to select
1125 belongs to the account that is being removed*/
1126 if (priv->folder_to_select) {
1127 TnyAccount *folder_to_select_account = NULL;
1129 folder_to_select_account = tny_folder_get_account (priv->folder_to_select);
1130 if (folder_to_select_account == account) {
1131 modest_folder_view_disable_next_folder_selection (self);
1132 g_object_unref (priv->folder_to_select);
1133 priv->folder_to_select = NULL;
1135 g_object_unref (folder_to_select_account);
1138 /* Remove the account from the model */
1139 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1140 if (!GTK_IS_TREE_MODEL_FILTER(filter_model)) {
1141 g_warning ("BUG: %s: not a valid filter model", __FUNCTION__);
1145 sort_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
1146 if (!GTK_IS_TREE_MODEL_SORT(sort_model)) {
1147 g_warning ("BUG: %s: not a valid sort model", __FUNCTION__);
1151 tny_list_remove (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
1152 G_OBJECT (account));
1154 /* If the removed account is the currently viewed one then
1155 clear the configuration value. The new visible account will be the default account */
1156 if (priv->visible_account_id &&
1157 !strcmp (priv->visible_account_id, tny_account_get_id (account))) {
1159 /* Clear the current visible account_id */
1160 modest_folder_view_set_account_id_of_visible_server_account (self, NULL);
1162 /* Call the restore method, this will set the new visible account */
1163 modest_widget_memory_restore (modest_runtime_get_conf(), G_OBJECT(self),
1164 MODEST_CONF_FOLDER_VIEW_KEY);
1167 /* Refilter the model */
1168 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1170 /* Select the first INBOX if the currently selected folder
1171 belongs to the account that is being deleted */
1173 g_idle_add (on_idle_select_first_inbox_or_local, self);
1177 modest_folder_view_set_title (ModestFolderView *self, const gchar *title)
1179 GtkTreeViewColumn *col;
1181 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
1183 col = gtk_tree_view_get_column (GTK_TREE_VIEW(self), 0);
1185 g_printerr ("modest: failed get column for title\n");
1189 gtk_tree_view_column_set_title (col, title);
1190 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self),
1195 modest_folder_view_on_map (ModestFolderView *self,
1196 GdkEventExpose *event,
1199 ModestFolderViewPrivate *priv;
1201 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1203 /* This won't happen often */
1204 if (G_UNLIKELY (priv->reselect)) {
1205 /* Select the first inbox or the local account if not found */
1207 /* TODO: this could cause a lock at startup, so we
1208 comment it for the moment. We know that this will
1209 be a bug, because the INBOX is not selected, but we
1210 need to rewrite some parts of Modest to avoid the
1211 deathlock situation */
1212 /* TODO: check if this is still the case */
1213 priv->reselect = FALSE;
1214 modest_folder_view_select_first_inbox_or_local (self);
1215 /* Notify the display name observers */
1216 g_signal_emit (G_OBJECT(self),
1217 signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
1221 if (priv->reexpand) {
1222 expand_root_items (self);
1223 priv->reexpand = FALSE;
1230 modest_folder_view_new (TnyFolderStoreQuery *query)
1233 ModestFolderViewPrivate *priv;
1234 GtkTreeSelection *sel;
1236 self = G_OBJECT (g_object_new (MODEST_TYPE_FOLDER_VIEW, NULL));
1237 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1240 priv->query = g_object_ref (query);
1242 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1243 priv->changed_signal = g_signal_connect (sel, "changed",
1244 G_CALLBACK (on_selection_changed), self);
1246 g_signal_connect (self, "expose-event", G_CALLBACK (modest_folder_view_on_map), NULL);
1248 return GTK_WIDGET(self);
1251 /* this feels dirty; any other way to expand all the root items? */
1253 expand_root_items (ModestFolderView *self)
1256 GtkTreeModel *model;
1259 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1260 path = gtk_tree_path_new_first ();
1262 /* all folders should have child items, so.. */
1264 gtk_tree_view_expand_row (GTK_TREE_VIEW(self), path, FALSE);
1265 gtk_tree_path_next (path);
1266 } while (gtk_tree_model_get_iter (model, &iter, path));
1268 gtk_tree_path_free (path);
1272 * We use this function to implement the
1273 * MODEST_FOLDER_VIEW_STYLE_SHOW_ONE style. We only show the default
1274 * account in this case, and the local folders.
1277 filter_row (GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
1279 ModestFolderViewPrivate *priv;
1280 gboolean retval = TRUE;
1281 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1282 GObject *instance = NULL;
1283 const gchar *id = NULL;
1285 gboolean found = FALSE;
1286 gboolean cleared = FALSE;
1288 g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (data), FALSE);
1289 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (data);
1291 gtk_tree_model_get (model, iter,
1292 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
1293 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
1296 /* Do not show if there is no instance, this could indeed
1297 happen when the model is being modified while it's being
1298 drawn. This could occur for example when moving folders
1303 if (type == TNY_FOLDER_TYPE_ROOT) {
1304 /* TNY_FOLDER_TYPE_ROOT means that the instance is an
1305 account instead of a folder. */
1306 if (TNY_IS_ACCOUNT (instance)) {
1307 TnyAccount *acc = TNY_ACCOUNT (instance);
1308 const gchar *account_id = tny_account_get_id (acc);
1310 /* If it isn't a special folder,
1311 * don't show it unless it is the visible account: */
1312 if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE &&
1313 !modest_tny_account_is_virtual_local_folders (acc) &&
1314 strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
1316 /* Show only the visible account id */
1317 if (priv->visible_account_id) {
1318 if (strcmp (account_id, priv->visible_account_id))
1325 /* Never show these to the user. They are merged into one folder
1326 * in the local-folders account instead: */
1327 if (retval && MODEST_IS_TNY_OUTBOX_ACCOUNT (acc))
1332 /* Check hiding (if necessary) */
1333 cleared = modest_email_clipboard_cleared (priv->clipboard);
1334 if ((retval) && (!cleared) && (TNY_IS_FOLDER (instance))) {
1335 id = tny_folder_get_id (TNY_FOLDER(instance));
1336 if (priv->hidding_ids != NULL)
1337 for (i=0; i < priv->n_selected && !found; i++)
1338 if (priv->hidding_ids[i] != NULL && id != NULL)
1339 found = (!strcmp (priv->hidding_ids[i], id));
1345 /* If this is a move to dialog, hide Sent, Outbox and Drafts
1346 folder as no message can be move there according to UI specs */
1347 if (!priv->show_non_move) {
1349 case TNY_FOLDER_TYPE_OUTBOX:
1350 case TNY_FOLDER_TYPE_SENT:
1351 case TNY_FOLDER_TYPE_DRAFTS:
1354 case TNY_FOLDER_TYPE_UNKNOWN:
1355 case TNY_FOLDER_TYPE_NORMAL:
1356 type = modest_tny_folder_guess_folder_type(TNY_FOLDER(instance));
1357 if (type == TNY_FOLDER_TYPE_INVALID)
1358 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1360 if (type == TNY_FOLDER_TYPE_OUTBOX ||
1361 type == TNY_FOLDER_TYPE_SENT
1362 || type == TNY_FOLDER_TYPE_DRAFTS)
1371 g_object_unref (instance);
1378 modest_folder_view_update_model (ModestFolderView *self,
1379 TnyAccountStore *account_store)
1381 ModestFolderViewPrivate *priv;
1382 GtkTreeModel *model /* , *old_model */;
1383 GtkTreeModel *filter_model = NULL, *sortable = NULL;
1385 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW (self), FALSE);
1386 g_return_val_if_fail (account_store && MODEST_IS_TNY_ACCOUNT_STORE(account_store),
1389 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1391 /* Notify that there is no folder selected */
1392 g_signal_emit (G_OBJECT(self),
1393 signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
1395 if (priv->cur_folder_store) {
1396 g_object_unref (priv->cur_folder_store);
1397 priv->cur_folder_store = NULL;
1400 /* FIXME: the local accounts are not shown when the query
1401 selects only the subscribed folders */
1402 model = tny_gtk_folder_store_tree_model_new (NULL);
1404 /* Get the accounts: */
1405 tny_account_store_get_accounts (TNY_ACCOUNT_STORE(account_store),
1407 TNY_ACCOUNT_STORE_STORE_ACCOUNTS);
1409 sortable = gtk_tree_model_sort_new_with_model (model);
1410 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
1411 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
1412 GTK_SORT_ASCENDING);
1413 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
1414 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
1415 cmp_rows, NULL, NULL);
1417 /* Create filter model */
1418 filter_model = gtk_tree_model_filter_new (sortable, NULL);
1419 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
1425 gtk_tree_view_set_model (GTK_TREE_VIEW(self), filter_model);
1426 g_signal_connect (G_OBJECT(filter_model), "row-inserted",
1427 (GCallback) on_row_inserted_maybe_select_folder, self);
1429 g_object_unref (model);
1430 g_object_unref (filter_model);
1431 g_object_unref (sortable);
1433 /* Force a reselection of the INBOX next time the widget is shown */
1434 priv->reselect = TRUE;
1441 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
1443 GtkTreeModel *model = NULL;
1444 TnyFolderStore *folder = NULL;
1446 ModestFolderView *tree_view = NULL;
1447 ModestFolderViewPrivate *priv = NULL;
1448 gboolean selected = FALSE;
1450 g_return_if_fail (sel);
1451 g_return_if_fail (user_data);
1453 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
1455 selected = gtk_tree_selection_get_selected (sel, &model, &iter);
1457 tree_view = MODEST_FOLDER_VIEW (user_data);
1460 gtk_tree_model_get (model, &iter,
1461 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
1464 /* If the folder is the same do not notify */
1465 if (folder && priv->cur_folder_store == folder) {
1466 g_object_unref (folder);
1471 /* Current folder was unselected */
1472 if (priv->cur_folder_store) {
1473 /* We must do this firstly because a libtinymail-camel
1474 implementation detail. If we issue the signal
1475 before doing the sync_async, then that signal could
1476 cause (and it actually does it) a free of the
1477 summary of the folder (because the main window will
1478 clear the headers view */
1479 if (TNY_IS_FOLDER(priv->cur_folder_store))
1480 tny_folder_sync_async (TNY_FOLDER(priv->cur_folder_store),
1481 FALSE, NULL, NULL, NULL);
1483 g_signal_emit (G_OBJECT(tree_view), signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
1484 priv->cur_folder_store, FALSE);
1486 g_object_unref (priv->cur_folder_store);
1487 priv->cur_folder_store = NULL;
1490 /* New current references */
1491 priv->cur_folder_store = folder;
1493 /* New folder has been selected. Do not notify if there is
1494 nothing new selected */
1496 g_signal_emit (G_OBJECT(tree_view),
1497 signals[FOLDER_SELECTION_CHANGED_SIGNAL],
1498 0, priv->cur_folder_store, TRUE);
1503 modest_folder_view_get_selected (ModestFolderView *self)
1505 ModestFolderViewPrivate *priv;
1507 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW(self), NULL);
1509 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1510 if (priv->cur_folder_store)
1511 g_object_ref (priv->cur_folder_store);
1513 return priv->cur_folder_store;
1517 get_cmp_rows_type_pos (GObject *folder)
1519 /* Remote accounts -> Local account -> MMC account .*/
1522 if (TNY_IS_ACCOUNT (folder) &&
1523 modest_tny_account_is_virtual_local_folders (
1524 TNY_ACCOUNT (folder))) {
1526 } else if (TNY_IS_ACCOUNT (folder)) {
1527 TnyAccount *account = TNY_ACCOUNT (folder);
1528 const gchar *account_id = tny_account_get_id (account);
1529 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
1535 printf ("DEBUG: %s: unexpected type.\n", __FUNCTION__);
1536 return -1; /* Should never happen */
1541 get_cmp_subfolder_type_pos (TnyFolderType t)
1543 /* Inbox, Outbox, Drafts, Sent, User */
1547 case TNY_FOLDER_TYPE_INBOX:
1550 case TNY_FOLDER_TYPE_OUTBOX:
1553 case TNY_FOLDER_TYPE_DRAFTS:
1556 case TNY_FOLDER_TYPE_SENT:
1565 * This function orders the mail accounts according to these rules:
1566 * 1st - remote accounts
1567 * 2nd - local account
1571 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1575 gchar *name1 = NULL;
1576 gchar *name2 = NULL;
1577 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1578 TnyFolderType type2 = TNY_FOLDER_TYPE_UNKNOWN;
1579 GObject *folder1 = NULL;
1580 GObject *folder2 = NULL;
1582 gtk_tree_model_get (tree_model, iter1,
1583 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name1,
1584 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
1585 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder1,
1587 gtk_tree_model_get (tree_model, iter2,
1588 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name2,
1589 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type2,
1590 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder2,
1593 /* Return if we get no folder. This could happen when folder
1594 operations are happening. The model is updated after the
1595 folder copy/move actually occurs, so there could be
1596 situations where the model to be drawn is not correct */
1597 if (!folder1 || !folder2)
1600 if (type == TNY_FOLDER_TYPE_ROOT) {
1601 /* Compare the types, so that
1602 * Remote accounts -> Local account -> MMC account .*/
1603 const gint pos1 = get_cmp_rows_type_pos (folder1);
1604 const gint pos2 = get_cmp_rows_type_pos (folder2);
1605 /* printf ("DEBUG: %s:\n type1=%s, pos1=%d\n type2=%s, pos2=%d\n",
1606 __FUNCTION__, G_OBJECT_TYPE_NAME(folder1), pos1, G_OBJECT_TYPE_NAME(folder2), pos2); */
1609 else if (pos1 > pos2)
1612 /* Compare items of the same type: */
1614 TnyAccount *account1 = NULL;
1615 if (TNY_IS_ACCOUNT (folder1))
1616 account1 = TNY_ACCOUNT (folder1);
1618 TnyAccount *account2 = NULL;
1619 if (TNY_IS_ACCOUNT (folder2))
1620 account2 = TNY_ACCOUNT (folder2);
1622 const gchar *account_id = account1 ? tny_account_get_id (account1) : NULL;
1623 const gchar *account_id2 = account2 ? tny_account_get_id (account2) : NULL;
1625 if (!account_id && !account_id2) {
1627 } else if (!account_id) {
1629 } else if (!account_id2) {
1631 } else if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
1634 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
1638 gint cmp1 = 0, cmp2 = 0;
1639 /* get the parent to know if it's a local folder */
1642 gboolean has_parent;
1643 has_parent = gtk_tree_model_iter_parent (tree_model, &parent, iter1);
1645 GObject *parent_folder;
1646 TnyFolderType parent_type = TNY_FOLDER_TYPE_UNKNOWN;
1647 gtk_tree_model_get (tree_model, &parent,
1648 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &parent_type,
1649 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &parent_folder,
1651 if ((parent_type == TNY_FOLDER_TYPE_ROOT) &&
1652 TNY_IS_ACCOUNT (parent_folder)) {
1653 if (modest_tny_account_is_virtual_local_folders (TNY_ACCOUNT (parent_folder))) {
1654 cmp1 = get_cmp_subfolder_type_pos (modest_tny_folder_get_local_or_mmc_folder_type
1655 (TNY_FOLDER (folder1)));
1656 cmp2 = get_cmp_subfolder_type_pos (modest_tny_folder_get_local_or_mmc_folder_type
1657 (TNY_FOLDER (folder2)));
1658 } else if (modest_tny_account_is_memory_card_account (TNY_ACCOUNT (parent_folder))) {
1659 if (modest_local_folder_info_get_type (tny_folder_get_name (TNY_FOLDER (folder1))) == TNY_FOLDER_TYPE_ARCHIVE) {
1662 } else if (modest_local_folder_info_get_type (tny_folder_get_name (TNY_FOLDER (folder2))) == TNY_FOLDER_TYPE_ARCHIVE) {
1668 g_object_unref (parent_folder);
1671 /* if they are not local folders */
1673 cmp1 = get_cmp_subfolder_type_pos (tny_folder_get_folder_type (TNY_FOLDER (folder1)));
1674 cmp2 = get_cmp_subfolder_type_pos (tny_folder_get_folder_type (TNY_FOLDER (folder2)));
1678 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
1680 cmp = (cmp1 - cmp2);
1685 g_object_unref(G_OBJECT(folder1));
1687 g_object_unref(G_OBJECT(folder2));
1695 /*****************************************************************************/
1696 /* DRAG and DROP stuff */
1697 /*****************************************************************************/
1699 * This function fills the #GtkSelectionData with the row and the
1700 * model that has been dragged. It's called when this widget is a
1701 * source for dnd after the event drop happened
1704 on_drag_data_get (GtkWidget *widget, GdkDragContext *context, GtkSelectionData *selection_data,
1705 guint info, guint time, gpointer data)
1707 GtkTreeSelection *selection;
1708 GtkTreeModel *model;
1710 GtkTreePath *source_row;
1712 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1713 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
1715 source_row = gtk_tree_model_get_path (model, &iter);
1716 gtk_tree_set_row_drag_data (selection_data,
1720 gtk_tree_path_free (source_row);
1724 typedef struct _DndHelper {
1725 ModestFolderView *folder_view;
1726 gboolean delete_source;
1727 GtkTreePath *source_row;
1731 dnd_helper_destroyer (DndHelper *helper)
1733 /* Free the helper */
1734 gtk_tree_path_free (helper->source_row);
1735 g_slice_free (DndHelper, helper);
1739 xfer_folder_cb (ModestMailOperation *mail_op,
1740 TnyFolder *new_folder,
1744 /* Select the folder */
1745 modest_folder_view_select_folder (MODEST_FOLDER_VIEW (user_data),
1751 /* get the folder for the row the treepath refers to. */
1752 /* folder must be unref'd */
1753 static TnyFolderStore *
1754 tree_path_to_folder (GtkTreeModel *model, GtkTreePath *path)
1757 TnyFolderStore *folder = NULL;
1759 if (gtk_tree_model_get_iter (model,&iter, path))
1760 gtk_tree_model_get (model, &iter,
1761 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
1768 * This function is used by drag_data_received_cb to manage drag and
1769 * drop of a header, i.e, and drag from the header view to the folder
1773 drag_and_drop_from_header_view (GtkTreeModel *source_model,
1774 GtkTreeModel *dest_model,
1775 GtkTreePath *dest_row,
1776 GtkSelectionData *selection_data)
1778 TnyList *headers = NULL;
1779 TnyFolder *folder = NULL, *src_folder = NULL;
1780 TnyFolderType folder_type;
1781 GtkTreeIter source_iter, dest_iter;
1782 ModestWindowMgr *mgr = NULL;
1783 ModestWindow *main_win = NULL;
1784 gchar **uris, **tmp;
1786 /* Build the list of headers */
1787 mgr = modest_runtime_get_window_mgr ();
1788 headers = tny_simple_list_new ();
1789 uris = modest_dnd_selection_data_get_paths (selection_data);
1792 while (*tmp != NULL) {
1795 gboolean first = TRUE;
1798 path = gtk_tree_path_new_from_string (*tmp);
1799 gtk_tree_model_get_iter (source_model, &source_iter, path);
1800 gtk_tree_model_get (source_model, &source_iter,
1801 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1804 /* Do not enable d&d of headers already opened */
1805 if (!modest_window_mgr_find_registered_header(mgr, header, NULL))
1806 tny_list_append (headers, G_OBJECT (header));
1808 if (G_UNLIKELY (first)) {
1809 src_folder = tny_header_get_folder (header);
1813 /* Free and go on */
1814 gtk_tree_path_free (path);
1815 g_object_unref (header);
1820 /* Get the target folder */
1821 gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
1822 gtk_tree_model_get (dest_model, &dest_iter,
1823 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
1826 if (!folder || !TNY_IS_FOLDER(folder)) {
1827 /* g_warning ("%s: not a valid target folder (%p)", __FUNCTION__, folder); */
1831 folder_type = modest_tny_folder_guess_folder_type (folder);
1832 if (folder_type == TNY_FOLDER_TYPE_INVALID) {
1833 /* g_warning ("%s: invalid target folder", __FUNCTION__); */
1834 goto cleanup; /* cannot move messages there */
1837 if (modest_tny_folder_get_rules((TNY_FOLDER(folder))) & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE) {
1838 /* g_warning ("folder not writable"); */
1839 goto cleanup; /* verboten! */
1842 /* Ask for confirmation to move */
1843 main_win = modest_window_mgr_get_main_window (mgr, FALSE); /* don't create */
1845 g_warning ("%s: BUG: no main window found", __FUNCTION__);
1849 /* Transfer messages */
1850 modest_ui_actions_transfer_messages_helper (GTK_WINDOW (main_win), src_folder,
1855 if (G_IS_OBJECT (src_folder))
1856 g_object_unref (src_folder);
1857 if (G_IS_OBJECT(folder))
1858 g_object_unref (G_OBJECT (folder));
1859 if (G_IS_OBJECT(headers))
1860 g_object_unref (headers);
1864 TnyFolderStore *src_folder;
1865 TnyFolderStore *dst_folder;
1866 ModestFolderView *folder_view;
1871 dnd_folder_info_destroyer (DndFolderInfo *info)
1873 if (info->src_folder)
1874 g_object_unref (info->src_folder);
1875 if (info->dst_folder)
1876 g_object_unref (info->dst_folder);
1877 g_slice_free (DndFolderInfo, info);
1881 dnd_on_connection_failed_destroyer (DndFolderInfo *info,
1882 GtkWindow *parent_window,
1883 TnyAccount *account)
1886 modest_ui_actions_on_account_connection_error (parent_window, account);
1888 /* Free the helper & info */
1889 dnd_helper_destroyer (info->helper);
1890 dnd_folder_info_destroyer (info);
1894 drag_and_drop_from_folder_view_src_folder_performer (gboolean canceled,
1896 GtkWindow *parent_window,
1897 TnyAccount *account,
1900 DndFolderInfo *info = NULL;
1901 ModestMailOperation *mail_op;
1903 info = (DndFolderInfo *) user_data;
1905 if (err || canceled) {
1906 dnd_on_connection_failed_destroyer (info, parent_window, account);
1910 /* Do the mail operation */
1911 mail_op = modest_mail_operation_new_with_error_handling ((GObject *) parent_window,
1912 modest_ui_actions_move_folder_error_handler,
1913 info->src_folder, NULL);
1915 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1918 /* Transfer the folder */
1919 modest_mail_operation_xfer_folder (mail_op,
1920 TNY_FOLDER (info->src_folder),
1922 info->helper->delete_source,
1924 info->helper->folder_view);
1927 g_object_unref (G_OBJECT (mail_op));
1928 dnd_helper_destroyer (info->helper);
1929 dnd_folder_info_destroyer (info);
1934 drag_and_drop_from_folder_view_dst_folder_performer (gboolean canceled,
1936 GtkWindow *parent_window,
1937 TnyAccount *account,
1940 DndFolderInfo *info = NULL;
1942 info = (DndFolderInfo *) user_data;
1944 if (err || canceled) {
1945 dnd_on_connection_failed_destroyer (info, parent_window, account);
1949 /* Connect to source folder and perform the copy/move */
1950 modest_platform_connect_if_remote_and_perform (NULL, TRUE,
1952 drag_and_drop_from_folder_view_src_folder_performer,
1957 * This function is used by drag_data_received_cb to manage drag and
1958 * drop of a folder, i.e, and drag from the folder view to the same
1962 drag_and_drop_from_folder_view (GtkTreeModel *source_model,
1963 GtkTreeModel *dest_model,
1964 GtkTreePath *dest_row,
1965 GtkSelectionData *selection_data,
1968 GtkTreeIter dest_iter, iter;
1969 TnyFolderStore *dest_folder = NULL;
1970 TnyFolderStore *folder = NULL;
1971 gboolean forbidden = FALSE;
1973 DndFolderInfo *info = NULL;
1975 win = modest_window_mgr_get_main_window (modest_runtime_get_window_mgr(), FALSE); /* don't create */
1977 g_warning ("%s: BUG: no main window", __FUNCTION__);
1978 dnd_helper_destroyer (helper);
1983 /* check the folder rules for the destination */
1984 folder = tree_path_to_folder (dest_model, dest_row);
1985 if (TNY_IS_FOLDER(folder)) {
1986 ModestTnyFolderRules rules =
1987 modest_tny_folder_get_rules (TNY_FOLDER (folder));
1988 forbidden = rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE;
1989 } else if (TNY_IS_FOLDER_STORE(folder)) {
1990 /* enable local root as destination for folders */
1991 if (!MODEST_IS_TNY_LOCAL_FOLDERS_ACCOUNT (folder) &&
1992 !modest_tny_account_is_memory_card_account (TNY_ACCOUNT (folder)))
1995 g_object_unref (folder);
1998 /* check the folder rules for the source */
1999 folder = tree_path_to_folder (source_model, helper->source_row);
2000 if (TNY_IS_FOLDER(folder)) {
2001 ModestTnyFolderRules rules =
2002 modest_tny_folder_get_rules (TNY_FOLDER (folder));
2003 forbidden = rules & MODEST_FOLDER_RULES_FOLDER_NON_MOVEABLE;
2006 g_object_unref (folder);
2010 /* Check if the drag is possible */
2011 if (forbidden || !gtk_tree_path_compare (helper->source_row, dest_row)) {
2013 modest_platform_run_information_dialog ((GtkWindow *) win,
2014 _("mail_in_ui_folder_move_target_error"),
2016 /* Restore the previous selection */
2017 folder = tree_path_to_folder (source_model, helper->source_row);
2019 if (TNY_IS_FOLDER (folder))
2020 modest_folder_view_select_folder (helper->folder_view,
2021 TNY_FOLDER (folder), FALSE);
2022 g_object_unref (folder);
2024 dnd_helper_destroyer (helper);
2029 gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
2030 gtk_tree_model_get (dest_model, &dest_iter,
2031 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
2033 gtk_tree_model_get_iter (source_model, &iter, helper->source_row);
2034 gtk_tree_model_get (source_model, &iter,
2035 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
2038 /* Create the info for the performer */
2039 info = g_slice_new0 (DndFolderInfo);
2040 info->src_folder = g_object_ref (folder);
2041 info->dst_folder = g_object_ref (dest_folder);
2042 info->helper = helper;
2044 /* Connect to the destination folder and perform the copy/move */
2045 modest_platform_connect_if_remote_and_perform (GTK_WINDOW (win), TRUE,
2047 drag_and_drop_from_folder_view_dst_folder_performer,
2051 g_object_unref (dest_folder);
2052 g_object_unref (folder);
2056 * This function receives the data set by the "drag-data-get" signal
2057 * handler. This information comes within the #GtkSelectionData. This
2058 * function will manage both the drags of folders of the treeview and
2059 * drags of headers of the header view widget.
2062 on_drag_data_received (GtkWidget *widget,
2063 GdkDragContext *context,
2066 GtkSelectionData *selection_data,
2071 GtkWidget *source_widget;
2072 GtkTreeModel *dest_model, *source_model;
2073 GtkTreePath *source_row, *dest_row;
2074 GtkTreeViewDropPosition pos;
2075 gboolean delete_source = FALSE;
2076 gboolean success = FALSE;
2078 /* Do not allow further process */
2079 g_signal_stop_emission_by_name (widget, "drag-data-received");
2080 source_widget = gtk_drag_get_source_widget (context);
2082 /* Get the action */
2083 if (context->action == GDK_ACTION_MOVE) {
2084 delete_source = TRUE;
2086 /* Notify that there is no folder selected. We need to
2087 do this in order to update the headers view (and
2088 its monitors, because when moving, the old folder
2089 won't longer exist. We can not wait for the end of
2090 the operation, because the operation won't start if
2091 the folder is in use */
2092 if (source_widget == widget) {
2093 GtkTreeSelection *sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
2094 gtk_tree_selection_unselect_all (sel);
2098 /* Check if the get_data failed */
2099 if (selection_data == NULL || selection_data->length < 0)
2102 /* Select the destination model */
2103 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
2105 /* Get the path to the destination row. Can not call
2106 gtk_tree_view_get_drag_dest_row() because the source row
2107 is not selected anymore */
2108 gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget), x, y,
2111 /* Only allow drops IN other rows */
2113 pos == GTK_TREE_VIEW_DROP_BEFORE ||
2114 pos == GTK_TREE_VIEW_DROP_AFTER)
2118 /* Drags from the header view */
2119 if (source_widget != widget) {
2120 source_model = gtk_tree_view_get_model (GTK_TREE_VIEW (source_widget));
2122 drag_and_drop_from_header_view (source_model,
2127 DndHelper *helper = NULL;
2129 /* Get the source model and row */
2130 gtk_tree_get_row_drag_data (selection_data,
2134 /* Create the helper */
2135 helper = g_slice_new0 (DndHelper);
2136 helper->delete_source = delete_source;
2137 helper->source_row = gtk_tree_path_copy (source_row);
2138 helper->folder_view = MODEST_FOLDER_VIEW (widget);
2140 drag_and_drop_from_folder_view (source_model,
2146 gtk_tree_path_free (source_row);
2150 gtk_tree_path_free (dest_row);
2153 /* Finish the drag and drop */
2154 gtk_drag_finish (context, success, FALSE, time);
2158 * We define a "drag-drop" signal handler because we do not want to
2159 * use the default one, because the default one always calls
2160 * gtk_drag_finish and we prefer to do it in the "drag-data-received"
2161 * signal handler, because there we have all the information available
2162 * to know if the dnd was a success or not.
2165 drag_drop_cb (GtkWidget *widget,
2166 GdkDragContext *context,
2174 if (!context->targets)
2177 /* Check if we're dragging a folder row */
2178 target = gtk_drag_dest_find_target (widget, context, NULL);
2180 /* Request the data from the source. */
2181 gtk_drag_get_data(widget, context, target, time);
2187 * This function expands a node of a tree view if it's not expanded
2188 * yet. Not sure why it needs the threads stuff, but gtk+`example code
2189 * does that, so that's why they're here.
2192 expand_row_timeout (gpointer data)
2194 GtkTreeView *tree_view = data;
2195 GtkTreePath *dest_path = NULL;
2196 GtkTreeViewDropPosition pos;
2197 gboolean result = FALSE;
2199 gdk_threads_enter ();
2201 gtk_tree_view_get_drag_dest_row (tree_view,
2206 (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER ||
2207 pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)) {
2208 gtk_tree_view_expand_row (tree_view, dest_path, FALSE);
2209 gtk_tree_path_free (dest_path);
2213 gtk_tree_path_free (dest_path);
2218 gdk_threads_leave ();
2224 * This function is called whenever the pointer is moved over a widget
2225 * while dragging some data. It installs a timeout that will expand a
2226 * node of the treeview if not expanded yet. This function also calls
2227 * gdk_drag_status in order to set the suggested action that will be
2228 * used by the "drag-data-received" signal handler to know if we
2229 * should do a move or just a copy of the data.
2232 on_drag_motion (GtkWidget *widget,
2233 GdkDragContext *context,
2239 GtkTreeViewDropPosition pos;
2240 GtkTreePath *dest_row;
2241 GtkTreeModel *dest_model;
2242 ModestFolderViewPrivate *priv;
2243 GdkDragAction suggested_action;
2244 gboolean valid_location = FALSE;
2245 TnyFolderStore *folder = NULL;
2247 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
2249 if (priv->timer_expander != 0) {
2250 g_source_remove (priv->timer_expander);
2251 priv->timer_expander = 0;
2254 gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
2259 /* Do not allow drops between folders */
2261 pos == GTK_TREE_VIEW_DROP_BEFORE ||
2262 pos == GTK_TREE_VIEW_DROP_AFTER) {
2263 gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW (widget), NULL, 0);
2264 gdk_drag_status(context, 0, time);
2265 valid_location = FALSE;
2268 valid_location = TRUE;
2271 /* Check that the destination folder is writable */
2272 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
2273 folder = tree_path_to_folder (dest_model, dest_row);
2274 if (folder && TNY_IS_FOLDER (folder)) {
2275 ModestTnyFolderRules rules = modest_tny_folder_get_rules(TNY_FOLDER (folder));
2277 if (rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE) {
2278 valid_location = FALSE;
2283 /* Expand the selected row after 1/2 second */
2284 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), dest_row)) {
2285 priv->timer_expander = g_timeout_add (500, expand_row_timeout, widget);
2287 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), dest_row, pos);
2289 /* Select the desired action. By default we pick MOVE */
2290 suggested_action = GDK_ACTION_MOVE;
2292 if (context->actions == GDK_ACTION_COPY)
2293 gdk_drag_status(context, GDK_ACTION_COPY, time);
2294 else if (context->actions == GDK_ACTION_MOVE)
2295 gdk_drag_status(context, GDK_ACTION_MOVE, time);
2296 else if (context->actions & suggested_action)
2297 gdk_drag_status(context, suggested_action, time);
2299 gdk_drag_status(context, GDK_ACTION_DEFAULT, time);
2303 g_object_unref (folder);
2305 gtk_tree_path_free (dest_row);
2307 g_signal_stop_emission_by_name (widget, "drag-motion");
2309 return valid_location;
2313 * This function sets the treeview as a source and a target for dnd
2314 * events. It also connects all the requirede signals.
2317 setup_drag_and_drop (GtkTreeView *self)
2319 /* Set up the folder view as a dnd destination. Set only the
2320 highlight flag, otherwise gtk will have a different
2322 gtk_drag_dest_set (GTK_WIDGET (self),
2323 GTK_DEST_DEFAULT_HIGHLIGHT,
2324 folder_view_drag_types,
2325 G_N_ELEMENTS (folder_view_drag_types),
2326 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2328 g_signal_connect (G_OBJECT (self),
2329 "drag_data_received",
2330 G_CALLBACK (on_drag_data_received),
2334 /* Set up the treeview as a dnd source */
2335 gtk_drag_source_set (GTK_WIDGET (self),
2337 folder_view_drag_types,
2338 G_N_ELEMENTS (folder_view_drag_types),
2339 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2341 g_signal_connect (G_OBJECT (self),
2343 G_CALLBACK (on_drag_motion),
2346 g_signal_connect (G_OBJECT (self),
2348 G_CALLBACK (on_drag_data_get),
2351 g_signal_connect (G_OBJECT (self),
2353 G_CALLBACK (drag_drop_cb),
2358 * This function manages the navigation through the folders using the
2359 * keyboard or the hardware keys in the device
2362 on_key_pressed (GtkWidget *self,
2366 GtkTreeSelection *selection;
2368 GtkTreeModel *model;
2369 gboolean retval = FALSE;
2371 /* Up and Down are automatically managed by the treeview */
2372 if (event->keyval == GDK_Return) {
2373 /* Expand/Collapse the selected row */
2374 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2375 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
2378 path = gtk_tree_model_get_path (model, &iter);
2380 if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (self), path))
2381 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
2383 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
2384 gtk_tree_path_free (path);
2386 /* No further processing */
2394 * We listen to the changes in the local folder account name key,
2395 * because we want to show the right name in the view. The local
2396 * folder account name corresponds to the device name in the Maemo
2397 * version. We do this because we do not want to query gconf on each
2398 * tree view refresh. It's better to cache it and change whenever
2402 on_configuration_key_changed (ModestConf* conf,
2404 ModestConfEvent event,
2405 ModestConfNotificationId id,
2406 ModestFolderView *self)
2408 ModestFolderViewPrivate *priv;
2411 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
2412 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2414 if (!strcmp (key, MODEST_CONF_DEVICE_NAME)) {
2415 g_free (priv->local_account_name);
2417 if (event == MODEST_CONF_EVENT_KEY_UNSET)
2418 priv->local_account_name = g_strdup (MODEST_LOCAL_FOLDERS_DEFAULT_DISPLAY_NAME);
2420 priv->local_account_name = modest_conf_get_string (modest_runtime_get_conf(),
2421 MODEST_CONF_DEVICE_NAME, NULL);
2423 /* Force a redraw */
2424 #if GTK_CHECK_VERSION(2, 8, 0)
2425 GtkTreeViewColumn * tree_column;
2427 tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self),
2428 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN);
2429 gtk_tree_view_column_queue_resize (tree_column);
2431 gtk_widget_queue_draw (GTK_WIDGET (self));
2437 modest_folder_view_set_style (ModestFolderView *self,
2438 ModestFolderViewStyle style)
2440 ModestFolderViewPrivate *priv;
2442 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2443 g_return_if_fail (style == MODEST_FOLDER_VIEW_STYLE_SHOW_ALL ||
2444 style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE);
2446 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2449 priv->style = style;
2453 modest_folder_view_set_account_id_of_visible_server_account (ModestFolderView *self,
2454 const gchar *account_id)
2456 ModestFolderViewPrivate *priv;
2457 GtkTreeModel *model;
2459 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2461 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2463 /* This will be used by the filter_row callback,
2464 * to decided which rows to show: */
2465 if (priv->visible_account_id) {
2466 g_free (priv->visible_account_id);
2467 priv->visible_account_id = NULL;
2470 priv->visible_account_id = g_strdup (account_id);
2473 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2474 if (GTK_IS_TREE_MODEL_FILTER (model))
2475 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2477 /* Save settings to gconf */
2478 modest_widget_memory_save (modest_runtime_get_conf (), G_OBJECT(self),
2479 MODEST_CONF_FOLDER_VIEW_KEY);
2483 modest_folder_view_get_account_id_of_visible_server_account (ModestFolderView *self)
2485 ModestFolderViewPrivate *priv;
2487 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW(self), NULL);
2489 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2491 return (const gchar *) priv->visible_account_id;
2495 find_inbox_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *inbox_iter)
2499 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2501 gtk_tree_model_get (model, iter,
2502 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN,
2505 gboolean result = FALSE;
2506 if (type == TNY_FOLDER_TYPE_INBOX) {
2510 *inbox_iter = *iter;
2514 if (gtk_tree_model_iter_children (model, &child, iter)) {
2515 if (find_inbox_iter (model, &child, inbox_iter))
2519 } while (gtk_tree_model_iter_next (model, iter));
2528 modest_folder_view_select_first_inbox_or_local (ModestFolderView *self)
2530 GtkTreeModel *model;
2531 GtkTreeIter iter, inbox_iter;
2532 GtkTreeSelection *sel;
2533 GtkTreePath *path = NULL;
2535 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2537 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2541 expand_root_items (self);
2542 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2544 if (!gtk_tree_model_get_iter_first (model, &iter)) {
2545 g_warning ("%s: model is empty", __FUNCTION__);
2549 if (find_inbox_iter (model, &iter, &inbox_iter))
2550 path = gtk_tree_model_get_path (model, &inbox_iter);
2552 path = gtk_tree_path_new_first ();
2554 /* Select the row and free */
2555 gtk_tree_view_set_cursor (GTK_TREE_VIEW (self), path, NULL, FALSE);
2556 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (self), path, NULL, FALSE, 0.0, 0.0);
2557 gtk_tree_path_free (path);
2560 gtk_widget_grab_focus (GTK_WIDGET(self));
2566 find_folder_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *folder_iter,
2571 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2572 TnyFolder* a_folder;
2575 gtk_tree_model_get (model, iter,
2576 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &a_folder,
2577 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name,
2578 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
2582 if (folder == a_folder) {
2583 g_object_unref (a_folder);
2584 *folder_iter = *iter;
2587 g_object_unref (a_folder);
2589 if (gtk_tree_model_iter_children (model, &child, iter)) {
2590 if (find_folder_iter (model, &child, folder_iter, folder))
2594 } while (gtk_tree_model_iter_next (model, iter));
2601 on_row_inserted_maybe_select_folder (GtkTreeModel *tree_model,
2604 ModestFolderView *self)
2606 ModestFolderViewPrivate *priv = NULL;
2607 GtkTreeSelection *sel;
2608 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2609 GObject *instance = NULL;
2611 if (!MODEST_IS_FOLDER_VIEW(self))
2614 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2616 priv->reexpand = TRUE;
2618 gtk_tree_model_get (tree_model, iter,
2619 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
2620 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
2622 if (type == TNY_FOLDER_TYPE_INBOX && priv->folder_to_select == NULL) {
2623 priv->folder_to_select = g_object_ref (instance);
2625 g_object_unref (instance);
2627 if (priv->folder_to_select) {
2629 if (!modest_folder_view_select_folder (self, priv->folder_to_select,
2632 path = gtk_tree_model_get_path (tree_model, iter);
2633 gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
2635 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2637 gtk_tree_selection_select_iter (sel, iter);
2638 gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
2640 gtk_tree_path_free (path);
2644 modest_folder_view_disable_next_folder_selection (self);
2646 /* Refilter the model */
2647 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (tree_model));
2653 modest_folder_view_disable_next_folder_selection (ModestFolderView *self)
2655 ModestFolderViewPrivate *priv;
2657 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2659 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2661 if (priv->folder_to_select)
2662 g_object_unref(priv->folder_to_select);
2664 priv->folder_to_select = NULL;
2668 modest_folder_view_select_folder (ModestFolderView *self, TnyFolder *folder,
2669 gboolean after_change)
2671 GtkTreeModel *model;
2672 GtkTreeIter iter, folder_iter;
2673 GtkTreeSelection *sel;
2674 ModestFolderViewPrivate *priv = NULL;
2676 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW (self), FALSE);
2677 g_return_val_if_fail (folder && TNY_IS_FOLDER (folder), FALSE);
2679 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2682 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2683 gtk_tree_selection_unselect_all (sel);
2685 if (priv->folder_to_select)
2686 g_object_unref(priv->folder_to_select);
2687 priv->folder_to_select = TNY_FOLDER(g_object_ref(folder));
2691 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2696 /* Refilter the model, before selecting the folder */
2697 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2699 if (!gtk_tree_model_get_iter_first (model, &iter)) {
2700 g_warning ("%s: model is empty", __FUNCTION__);
2704 if (find_folder_iter (model, &iter, &folder_iter, folder)) {
2707 path = gtk_tree_model_get_path (model, &folder_iter);
2708 gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
2710 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2711 gtk_tree_selection_select_iter (sel, &folder_iter);
2712 gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
2714 gtk_tree_path_free (path);
2722 modest_folder_view_copy_selection (ModestFolderView *self)
2724 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2726 /* Copy selection */
2727 _clipboard_set_selected_data (self, FALSE);
2731 modest_folder_view_cut_selection (ModestFolderView *folder_view)
2733 ModestFolderViewPrivate *priv = NULL;
2734 GtkTreeModel *model = NULL;
2735 const gchar **hidding = NULL;
2736 guint i, n_selected;
2738 g_return_if_fail (folder_view && MODEST_IS_FOLDER_VIEW (folder_view));
2739 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
2741 /* Copy selection */
2742 if (!_clipboard_set_selected_data (folder_view, TRUE))
2745 /* Get hidding ids */
2746 hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected);
2748 /* Clear hidding array created by previous cut operation */
2749 _clear_hidding_filter (MODEST_FOLDER_VIEW (folder_view));
2751 /* Copy hidding array */
2752 priv->n_selected = n_selected;
2753 priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
2754 for (i=0; i < n_selected; i++)
2755 priv->hidding_ids[i] = g_strdup(hidding[i]);
2757 /* Hide cut folders */
2758 model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
2759 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2763 modest_folder_view_copy_model (ModestFolderView *folder_view_src,
2764 ModestFolderView *folder_view_dst)
2766 GtkTreeModel *filter_model = NULL;
2767 GtkTreeModel *model = NULL;
2768 GtkTreeModel *new_filter_model = NULL;
2770 g_return_if_fail (folder_view_src && MODEST_IS_FOLDER_VIEW (folder_view_src));
2771 g_return_if_fail (folder_view_dst && MODEST_IS_FOLDER_VIEW (folder_view_dst));
2774 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view_src));
2775 model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER(filter_model));
2777 /* Build new filter model */
2778 new_filter_model = gtk_tree_model_filter_new (model, NULL);
2779 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (new_filter_model),
2783 /* Set copied model */
2784 gtk_tree_view_set_model (GTK_TREE_VIEW (folder_view_dst), new_filter_model);
2785 g_signal_connect (G_OBJECT(new_filter_model), "row-inserted",
2786 (GCallback) on_row_inserted_maybe_select_folder, folder_view_dst);
2789 g_object_unref (new_filter_model);
2793 modest_folder_view_show_non_move_folders (ModestFolderView *folder_view,
2796 GtkTreeModel *model = NULL;
2797 ModestFolderViewPrivate* priv;
2799 g_return_if_fail (folder_view && MODEST_IS_FOLDER_VIEW (folder_view));
2801 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
2802 priv->show_non_move = show;
2803 /* modest_folder_view_update_model(folder_view, */
2804 /* TNY_ACCOUNT_STORE(modest_runtime_get_account_store())); */
2806 /* Hide special folders */
2807 model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
2808 if (GTK_IS_TREE_MODEL_FILTER (model)) {
2809 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2813 /* Returns FALSE if it did not selected anything */
2815 _clipboard_set_selected_data (ModestFolderView *folder_view,
2818 ModestFolderViewPrivate *priv = NULL;
2819 TnyFolderStore *folder = NULL;
2820 gboolean retval = FALSE;
2822 g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (folder_view), FALSE);
2823 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
2825 /* Set selected data on clipboard */
2826 g_return_val_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard), FALSE);
2827 folder = modest_folder_view_get_selected (folder_view);
2829 /* Do not allow to select an account */
2830 if (TNY_IS_FOLDER (folder)) {
2831 modest_email_clipboard_set_data (priv->clipboard, TNY_FOLDER(folder), NULL, delete);
2836 g_object_unref (folder);
2842 _clear_hidding_filter (ModestFolderView *folder_view)
2844 ModestFolderViewPrivate *priv;
2847 g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view));
2848 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
2850 if (priv->hidding_ids != NULL) {
2851 for (i=0; i < priv->n_selected; i++)
2852 g_free (priv->hidding_ids[i]);
2853 g_free(priv->hidding_ids);
2859 on_display_name_changed (ModestAccountMgr *mgr,
2860 const gchar *account,
2863 ModestFolderView *self;
2865 self = MODEST_FOLDER_VIEW (user_data);
2867 /* Force a redraw */
2868 #if GTK_CHECK_VERSION(2, 8, 0)
2869 GtkTreeViewColumn * tree_column;
2871 tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self),
2872 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN);
2873 gtk_tree_view_column_queue_resize (tree_column);
2875 gtk_widget_queue_draw (GTK_WIDGET (self));