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_row_activated (GtkTreeView *treeview,
85 GtkTreeViewColumn *column,
88 static void on_account_removed (TnyAccountStore *self,
92 static void on_account_inserted (TnyAccountStore *self,
96 static void on_account_changed (TnyAccountStore *self,
100 static gint cmp_rows (GtkTreeModel *tree_model,
105 static gboolean filter_row (GtkTreeModel *model,
109 static gboolean on_key_pressed (GtkWidget *self,
113 static void on_configuration_key_changed (ModestConf* conf,
115 ModestConfEvent event,
116 ModestConfNotificationId notification_id,
117 ModestFolderView *self);
120 static void on_drag_data_get (GtkWidget *widget,
121 GdkDragContext *context,
122 GtkSelectionData *selection_data,
127 static void on_drag_data_received (GtkWidget *widget,
128 GdkDragContext *context,
131 GtkSelectionData *selection_data,
136 static gboolean on_drag_motion (GtkWidget *widget,
137 GdkDragContext *context,
143 static void expand_root_items (ModestFolderView *self);
145 static gint expand_row_timeout (gpointer data);
147 static void setup_drag_and_drop (GtkTreeView *self);
149 static gboolean _clipboard_set_selected_data (ModestFolderView *folder_view,
152 static void _clear_hidding_filter (ModestFolderView *folder_view);
154 static void on_row_inserted_maybe_select_folder (GtkTreeModel *tree_model,
157 ModestFolderView *self);
159 static void on_display_name_changed (ModestAccountMgr *self,
160 const gchar *account,
164 FOLDER_SELECTION_CHANGED_SIGNAL,
165 FOLDER_DISPLAY_NAME_CHANGED_SIGNAL,
166 FOLDER_ACTIVATED_SIGNAL,
170 typedef struct _ModestFolderViewPrivate ModestFolderViewPrivate;
171 struct _ModestFolderViewPrivate {
172 TnyAccountStore *account_store;
173 TnyFolderStore *cur_folder_store;
175 TnyFolder *folder_to_select; /* folder to select after the next update */
177 gulong changed_signal;
178 gulong account_inserted_signal;
179 gulong account_removed_signal;
180 gulong account_changed_signal;
181 gulong conf_key_signal;
182 gulong display_name_changed_signal;
184 /* not unref this object, its a singlenton */
185 ModestEmailClipboard *clipboard;
187 /* Filter tree model */
191 TnyFolderStoreQuery *query;
192 guint timer_expander;
194 gchar *local_account_name;
195 gchar *visible_account_id;
196 ModestFolderViewStyle style;
197 ModestFolderViewCellStyle cell_style;
199 gboolean reselect; /* we use this to force a reselection of the INBOX */
200 gboolean show_non_move;
201 gboolean reexpand; /* next time we expose, we'll expand all root folders */
203 #define MODEST_FOLDER_VIEW_GET_PRIVATE(o) \
204 (G_TYPE_INSTANCE_GET_PRIVATE((o), \
205 MODEST_TYPE_FOLDER_VIEW, \
206 ModestFolderViewPrivate))
208 static GObjectClass *parent_class = NULL;
210 static guint signals[LAST_SIGNAL] = {0};
213 modest_folder_view_get_type (void)
215 static GType my_type = 0;
217 static const GTypeInfo my_info = {
218 sizeof(ModestFolderViewClass),
219 NULL, /* base init */
220 NULL, /* base finalize */
221 (GClassInitFunc) modest_folder_view_class_init,
222 NULL, /* class finalize */
223 NULL, /* class data */
224 sizeof(ModestFolderView),
226 (GInstanceInitFunc) modest_folder_view_init,
230 static const GInterfaceInfo tny_account_store_view_info = {
231 (GInterfaceInitFunc) tny_account_store_view_init, /* interface_init */
232 NULL, /* interface_finalize */
233 NULL /* interface_data */
237 my_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
241 g_type_add_interface_static (my_type,
242 TNY_TYPE_ACCOUNT_STORE_VIEW,
243 &tny_account_store_view_info);
249 modest_folder_view_class_init (ModestFolderViewClass *klass)
251 GObjectClass *gobject_class;
252 GtkTreeViewClass *treeview_class;
253 gobject_class = (GObjectClass*) klass;
254 treeview_class = (GtkTreeViewClass*) klass;
256 parent_class = g_type_class_peek_parent (klass);
257 gobject_class->finalize = modest_folder_view_finalize;
259 g_type_class_add_private (gobject_class,
260 sizeof(ModestFolderViewPrivate));
262 signals[FOLDER_SELECTION_CHANGED_SIGNAL] =
263 g_signal_new ("folder_selection_changed",
264 G_TYPE_FROM_CLASS (gobject_class),
266 G_STRUCT_OFFSET (ModestFolderViewClass,
267 folder_selection_changed),
269 modest_marshal_VOID__POINTER_BOOLEAN,
270 G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_BOOLEAN);
273 * This signal is emitted whenever the currently selected
274 * folder display name is computed. Note that the name could
275 * be different to the folder name, because we could append
276 * the unread messages count to the folder name to build the
277 * folder display name
279 signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL] =
280 g_signal_new ("folder-display-name-changed",
281 G_TYPE_FROM_CLASS (gobject_class),
283 G_STRUCT_OFFSET (ModestFolderViewClass,
284 folder_display_name_changed),
286 g_cclosure_marshal_VOID__STRING,
287 G_TYPE_NONE, 1, G_TYPE_STRING);
289 signals[FOLDER_ACTIVATED_SIGNAL] =
290 g_signal_new ("folder_activated",
291 G_TYPE_FROM_CLASS (gobject_class),
293 G_STRUCT_OFFSET (ModestFolderViewClass,
296 g_cclosure_marshal_VOID__POINTER,
297 G_TYPE_NONE, 1, G_TYPE_POINTER);
299 treeview_class->select_cursor_parent = NULL;
301 #ifdef MODEST_TOOLKIT_HILDON2
302 gtk_rc_parse_string ("class \"ModestFolderView\" style \"fremantle-touchlist\"");
308 /* Simplify checks for NULLs: */
310 strings_are_equal (const gchar *a, const gchar *b)
316 return (strcmp (a, b) == 0);
323 on_model_foreach_set_name(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
325 GObject *instance = NULL;
327 gtk_tree_model_get (model, iter,
328 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
332 return FALSE; /* keep walking */
334 if (!TNY_IS_ACCOUNT (instance)) {
335 g_object_unref (instance);
336 return FALSE; /* keep walking */
339 /* Check if this is the looked-for account: */
340 TnyAccount *this_account = TNY_ACCOUNT (instance);
341 TnyAccount *account = TNY_ACCOUNT (data);
343 const gchar *this_account_id = tny_account_get_id(this_account);
344 const gchar *account_id = tny_account_get_id(account);
345 g_object_unref (instance);
348 /* printf ("DEBUG: %s: this_account_id=%s, account_id=%s\n", __FUNCTION__, this_account_id, account_id); */
349 if (strings_are_equal(this_account_id, account_id)) {
350 /* Tell the model that the data has changed, so that
351 * it calls the cell_data_func callbacks again: */
352 /* TODO: This does not seem to actually cause the new string to be shown: */
353 gtk_tree_model_row_changed (model, path, iter);
355 return TRUE; /* stop walking */
358 return FALSE; /* keep walking */
363 ModestFolderView *self;
364 gchar *previous_name;
365 } GetMmcAccountNameData;
368 on_get_mmc_account_name (TnyStoreAccount* account, gpointer user_data)
370 /* printf ("DEBU1G: %s: account name=%s\n", __FUNCTION__, tny_account_get_name (TNY_ACCOUNT(account))); */
372 GetMmcAccountNameData *data = (GetMmcAccountNameData*)user_data;
374 if (!strings_are_equal (
375 tny_account_get_name(TNY_ACCOUNT(account)),
376 data->previous_name)) {
378 /* Tell the model that the data has changed, so that
379 * it calls the cell_data_func callbacks again: */
380 ModestFolderView *self = data->self;
381 GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
383 gtk_tree_model_foreach(model, on_model_foreach_set_name, account);
386 g_free (data->previous_name);
387 g_slice_free (GetMmcAccountNameData, data);
391 text_cell_data (GtkTreeViewColumn *column,
392 GtkCellRenderer *renderer,
393 GtkTreeModel *tree_model,
397 ModestFolderViewPrivate *priv;
398 GObject *rendobj = (GObject *) renderer;
400 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
401 GObject *instance = NULL;
403 gtk_tree_model_get (tree_model, iter,
404 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &fname,
405 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
406 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
408 if (!fname || !instance)
411 ModestFolderView *self = MODEST_FOLDER_VIEW (data);
412 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
414 gchar *item_name = NULL;
415 gint item_weight = 400;
417 if (type != TNY_FOLDER_TYPE_ROOT) {
421 if (modest_tny_folder_is_local_folder (TNY_FOLDER (instance)) ||
422 modest_tny_folder_is_memory_card_folder (TNY_FOLDER (instance))) {
423 type = modest_tny_folder_get_local_or_mmc_folder_type (TNY_FOLDER (instance));
424 if (type != TNY_FOLDER_TYPE_UNKNOWN) {
426 fname = g_strdup (modest_local_folder_info_get_type_display_name (type));
429 /* Sometimes an special folder is reported by the server as
430 NORMAL, like some versions of Dovecot */
431 if (type == TNY_FOLDER_TYPE_NORMAL ||
432 type == TNY_FOLDER_TYPE_UNKNOWN) {
433 type = modest_tny_folder_guess_folder_type (TNY_FOLDER (instance));
437 if (type == TNY_FOLDER_TYPE_INBOX) {
439 fname = g_strdup (_("mcen_me_folder_inbox"));
442 /* note: we cannot reliably get the counts from the tree model, we need
443 * to use explicit calls on tny_folder for some reason.
445 /* Select the number to show: the unread or unsent messages. in case of outbox/drafts, show all */
446 if ((type == TNY_FOLDER_TYPE_DRAFTS) ||
447 (type == TNY_FOLDER_TYPE_OUTBOX) ||
448 (type == TNY_FOLDER_TYPE_MERGE)) { /* _OUTBOX actually returns _MERGE... */
449 number = tny_folder_get_all_count (TNY_FOLDER(instance));
452 number = tny_folder_get_unread_count (TNY_FOLDER(instance));
456 if (priv->cell_style == MODEST_FOLDER_VIEW_CELL_STYLE_COMPACT) {
459 substring = g_strdup_printf (drafts?"TODO:%d messages":"TODO:%d new messages", number);
461 substring = g_strdup ("");
463 item_name = g_strdup_printf ("%s\n<span size='x-small' color='grey'>%s</span>",
467 /* Use bold font style if there are unread or unset messages */
469 item_name = g_strdup_printf ("%s (%d)", fname, number);
472 item_name = g_strdup (fname);
477 } else if (TNY_IS_ACCOUNT (instance)) {
478 /* If it's a server account */
479 if (modest_tny_account_is_virtual_local_folders (TNY_ACCOUNT (instance))) {
480 item_name = g_strdup (priv->local_account_name);
482 } else if (modest_tny_account_is_memory_card_account (TNY_ACCOUNT (instance))) {
483 /* fname is only correct when the items are first
484 * added to the model, not when the account is
485 * changed later, so get the name from the account
487 item_name = g_strdup (tny_account_get_name (TNY_ACCOUNT (instance)));
490 item_name = g_strdup (fname);
496 item_name = g_strdup ("unknown");
498 if (item_name && item_weight) {
499 /* Set the name in the treeview cell: */
500 g_object_set (rendobj,"markup", item_name, "weight", item_weight, NULL);
502 /* Notify display name observers */
503 /* TODO: What listens for this signal, and how can it use only the new name? */
504 if (((GObject *) priv->cur_folder_store) == instance) {
505 g_signal_emit (G_OBJECT(self),
506 signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
513 /* If it is a Memory card account, make sure that we have the correct name.
514 * This function will be trigerred again when the name has been retrieved: */
515 if (TNY_IS_STORE_ACCOUNT (instance) &&
516 modest_tny_account_is_memory_card_account (TNY_ACCOUNT (instance))) {
518 /* Get the account name asynchronously: */
519 GetMmcAccountNameData *callback_data =
520 g_slice_new0(GetMmcAccountNameData);
521 callback_data->self = self;
523 const gchar *name = tny_account_get_name (TNY_ACCOUNT(instance));
525 callback_data->previous_name = g_strdup (name);
527 modest_tny_account_get_mmc_account_name (TNY_STORE_ACCOUNT (instance),
528 on_get_mmc_account_name, callback_data);
532 g_object_unref (G_OBJECT (instance));
540 GdkPixbuf *pixbuf_open;
541 GdkPixbuf *pixbuf_close;
545 static inline GdkPixbuf *
546 get_composite_pixbuf (const gchar *icon_name,
548 GdkPixbuf *base_pixbuf)
550 GdkPixbuf *emblem, *retval = NULL;
552 emblem = modest_platform_get_icon (icon_name, size);
554 retval = gdk_pixbuf_copy (base_pixbuf);
555 gdk_pixbuf_composite (emblem, retval, 0, 0,
556 MIN (gdk_pixbuf_get_width (emblem),
557 gdk_pixbuf_get_width (retval)),
558 MIN (gdk_pixbuf_get_height (emblem),
559 gdk_pixbuf_get_height (retval)),
560 0, 0, 1, 1, GDK_INTERP_NEAREST, 255);
561 g_object_unref (emblem);
566 static inline ThreePixbufs *
567 get_composite_icons (const gchar *icon_code,
569 GdkPixbuf **pixbuf_open,
570 GdkPixbuf **pixbuf_close)
572 ThreePixbufs *retval;
575 *pixbuf = gdk_pixbuf_copy (modest_platform_get_icon (icon_code, MODEST_ICON_SIZE_SMALL));
578 *pixbuf_open = get_composite_pixbuf ("qgn_list_gene_fldr_exp",
579 MODEST_ICON_SIZE_SMALL,
583 *pixbuf_close = get_composite_pixbuf ("qgn_list_gene_fldr_clp",
584 MODEST_ICON_SIZE_SMALL,
587 retval = g_slice_new0 (ThreePixbufs);
589 retval->pixbuf = g_object_ref (*pixbuf);
591 retval->pixbuf_open = g_object_ref (*pixbuf_open);
593 retval->pixbuf_close = g_object_ref (*pixbuf_close);
599 get_folder_icons (TnyFolderType type, GObject *instance)
601 static GdkPixbuf *inbox_pixbuf = NULL, *outbox_pixbuf = NULL,
602 *junk_pixbuf = NULL, *sent_pixbuf = NULL,
603 *trash_pixbuf = NULL, *draft_pixbuf = NULL,
604 *normal_pixbuf = NULL, *anorm_pixbuf = NULL,
605 *ammc_pixbuf = NULL, *avirt_pixbuf = NULL;
607 static GdkPixbuf *inbox_pixbuf_open = NULL, *outbox_pixbuf_open = NULL,
608 *junk_pixbuf_open = NULL, *sent_pixbuf_open = NULL,
609 *trash_pixbuf_open = NULL, *draft_pixbuf_open = NULL,
610 *normal_pixbuf_open = NULL, *anorm_pixbuf_open = NULL,
611 *ammc_pixbuf_open = NULL, *avirt_pixbuf_open = NULL;
613 static GdkPixbuf *inbox_pixbuf_close = NULL, *outbox_pixbuf_close = NULL,
614 *junk_pixbuf_close = NULL, *sent_pixbuf_close = NULL,
615 *trash_pixbuf_close = NULL, *draft_pixbuf_close = NULL,
616 *normal_pixbuf_close = NULL, *anorm_pixbuf_close = NULL,
617 *ammc_pixbuf_close = NULL, *avirt_pixbuf_close = NULL;
619 ThreePixbufs *retval = NULL;
621 /* Sometimes an special folder is reported by the server as
622 NORMAL, like some versions of Dovecot */
623 if (type == TNY_FOLDER_TYPE_NORMAL ||
624 type == TNY_FOLDER_TYPE_UNKNOWN) {
625 type = modest_tny_folder_guess_folder_type (TNY_FOLDER (instance));
629 case TNY_FOLDER_TYPE_INVALID:
630 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
633 case TNY_FOLDER_TYPE_ROOT:
634 if (TNY_IS_ACCOUNT (instance)) {
636 if (modest_tny_account_is_virtual_local_folders (TNY_ACCOUNT (instance))) {
637 retval = get_composite_icons (MODEST_FOLDER_ICON_LOCAL_FOLDERS,
640 &avirt_pixbuf_close);
642 const gchar *account_id = tny_account_get_id (TNY_ACCOUNT (instance));
644 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
645 retval = get_composite_icons (MODEST_FOLDER_ICON_MMC,
650 retval = get_composite_icons (MODEST_FOLDER_ICON_ACCOUNT,
653 &anorm_pixbuf_close);
658 case TNY_FOLDER_TYPE_INBOX:
659 retval = get_composite_icons (MODEST_FOLDER_ICON_INBOX,
662 &inbox_pixbuf_close);
664 case TNY_FOLDER_TYPE_OUTBOX:
665 retval = get_composite_icons (MODEST_FOLDER_ICON_OUTBOX,
668 &outbox_pixbuf_close);
670 case TNY_FOLDER_TYPE_JUNK:
671 retval = get_composite_icons (MODEST_FOLDER_ICON_JUNK,
676 case TNY_FOLDER_TYPE_SENT:
677 retval = get_composite_icons (MODEST_FOLDER_ICON_SENT,
682 case TNY_FOLDER_TYPE_TRASH:
683 retval = get_composite_icons (MODEST_FOLDER_ICON_TRASH,
686 &trash_pixbuf_close);
688 case TNY_FOLDER_TYPE_DRAFTS:
689 retval = get_composite_icons (MODEST_FOLDER_ICON_DRAFTS,
692 &draft_pixbuf_close);
694 case TNY_FOLDER_TYPE_NORMAL:
696 retval = get_composite_icons (MODEST_FOLDER_ICON_NORMAL,
699 &normal_pixbuf_close);
707 free_pixbufs (ThreePixbufs *pixbufs)
710 g_object_unref (pixbufs->pixbuf);
711 if (pixbufs->pixbuf_open)
712 g_object_unref (pixbufs->pixbuf_open);
713 if (pixbufs->pixbuf_close)
714 g_object_unref (pixbufs->pixbuf_close);
715 g_slice_free (ThreePixbufs, pixbufs);
719 icon_cell_data (GtkTreeViewColumn *column,
720 GtkCellRenderer *renderer,
721 GtkTreeModel *tree_model,
725 GObject *rendobj = NULL, *instance = NULL;
726 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
727 gboolean has_children;
728 ThreePixbufs *pixbufs;
730 rendobj = (GObject *) renderer;
732 gtk_tree_model_get (tree_model, iter,
733 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
734 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
740 has_children = gtk_tree_model_iter_has_child (tree_model, iter);
741 pixbufs = get_folder_icons (type, instance);
742 g_object_unref (instance);
745 g_object_set (rendobj, "pixbuf", pixbufs->pixbuf, NULL);
748 g_object_set (rendobj, "pixbuf-expander-open", pixbufs->pixbuf_open, NULL);
749 g_object_set (rendobj, "pixbuf-expander-closed", pixbufs->pixbuf_close, NULL);
752 free_pixbufs (pixbufs);
756 add_columns (GtkWidget *treeview)
758 GtkTreeViewColumn *column;
759 GtkCellRenderer *renderer;
760 GtkTreeSelection *sel;
763 column = gtk_tree_view_column_new ();
765 /* Set icon and text render function */
766 renderer = gtk_cell_renderer_pixbuf_new();
767 gtk_tree_view_column_pack_start (column, renderer, FALSE);
768 gtk_tree_view_column_set_cell_data_func(column, renderer,
769 icon_cell_data, treeview, NULL);
771 renderer = gtk_cell_renderer_text_new();
772 g_object_set (renderer, "ellipsize", PANGO_ELLIPSIZE_END,
773 "ellipsize-set", TRUE, NULL);
774 gtk_tree_view_column_pack_start (column, renderer, TRUE);
775 gtk_tree_view_column_set_cell_data_func(column, renderer,
776 text_cell_data, treeview, NULL);
778 /* Set selection mode */
779 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(treeview));
780 gtk_tree_selection_set_mode (sel, GTK_SELECTION_SINGLE);
782 /* Set treeview appearance */
783 gtk_tree_view_column_set_spacing (column, 2);
784 gtk_tree_view_column_set_resizable (column, TRUE);
785 gtk_tree_view_column_set_fixed_width (column, TRUE);
786 gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(treeview), FALSE);
787 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(treeview), FALSE);
790 gtk_tree_view_append_column (GTK_TREE_VIEW(treeview),column);
794 modest_folder_view_init (ModestFolderView *obj)
796 ModestFolderViewPrivate *priv;
799 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
801 priv->timer_expander = 0;
802 priv->account_store = NULL;
804 priv->style = MODEST_FOLDER_VIEW_STYLE_SHOW_ALL;
805 priv->cur_folder_store = NULL;
806 priv->visible_account_id = NULL;
807 priv->folder_to_select = NULL;
809 priv->reexpand = TRUE;
811 /* Initialize the local account name */
812 conf = modest_runtime_get_conf();
813 priv->local_account_name = modest_conf_get_string (conf, MODEST_CONF_DEVICE_NAME, NULL);
815 /* Init email clipboard */
816 priv->clipboard = modest_runtime_get_email_clipboard ();
817 priv->hidding_ids = NULL;
818 priv->n_selected = 0;
819 priv->reselect = FALSE;
820 priv->show_non_move = TRUE;
823 add_columns (GTK_WIDGET (obj));
825 /* Setup drag and drop */
826 setup_drag_and_drop (GTK_TREE_VIEW(obj));
828 /* Connect signals */
829 g_signal_connect (G_OBJECT (obj),
831 G_CALLBACK (on_key_pressed), NULL);
833 priv->display_name_changed_signal =
834 g_signal_connect (modest_runtime_get_account_mgr (),
835 "display_name_changed",
836 G_CALLBACK (on_display_name_changed),
840 * Track changes in the local account name (in the device it
841 * will be the device name)
843 priv->conf_key_signal = g_signal_connect (G_OBJECT(conf),
845 G_CALLBACK(on_configuration_key_changed),
850 tny_account_store_view_init (gpointer g, gpointer iface_data)
852 TnyAccountStoreViewIface *klass = (TnyAccountStoreViewIface *)g;
854 klass->set_account_store = modest_folder_view_set_account_store;
858 modest_folder_view_finalize (GObject *obj)
860 ModestFolderViewPrivate *priv;
861 GtkTreeSelection *sel;
863 g_return_if_fail (obj);
865 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
867 if (priv->timer_expander != 0) {
868 g_source_remove (priv->timer_expander);
869 priv->timer_expander = 0;
872 if (priv->account_store) {
873 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
874 priv->account_inserted_signal);
875 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
876 priv->account_removed_signal);
877 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
878 priv->account_changed_signal);
879 g_object_unref (G_OBJECT(priv->account_store));
880 priv->account_store = NULL;
883 if (g_signal_handler_is_connected (modest_runtime_get_account_mgr (),
884 priv->display_name_changed_signal)) {
885 g_signal_handler_disconnect (modest_runtime_get_account_mgr (),
886 priv->display_name_changed_signal);
887 priv->display_name_changed_signal = 0;
891 g_object_unref (G_OBJECT (priv->query));
895 if (priv->folder_to_select) {
896 g_object_unref (G_OBJECT(priv->folder_to_select));
897 priv->folder_to_select = NULL;
900 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(obj));
902 g_signal_handler_disconnect (G_OBJECT(sel), priv->changed_signal);
904 g_free (priv->local_account_name);
905 g_free (priv->visible_account_id);
907 if (priv->conf_key_signal) {
908 g_signal_handler_disconnect (modest_runtime_get_conf (),
909 priv->conf_key_signal);
910 priv->conf_key_signal = 0;
913 if (priv->cur_folder_store) {
914 g_object_unref (priv->cur_folder_store);
915 priv->cur_folder_store = NULL;
918 /* Clear hidding array created by cut operation */
919 _clear_hidding_filter (MODEST_FOLDER_VIEW (obj));
921 G_OBJECT_CLASS(parent_class)->finalize (obj);
926 modest_folder_view_set_account_store (TnyAccountStoreView *self, TnyAccountStore *account_store)
928 ModestFolderViewPrivate *priv;
931 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
932 g_return_if_fail (TNY_IS_ACCOUNT_STORE (account_store));
934 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
935 device = tny_account_store_get_device (account_store);
937 if (G_UNLIKELY (priv->account_store)) {
939 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
940 priv->account_inserted_signal))
941 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
942 priv->account_inserted_signal);
943 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
944 priv->account_removed_signal))
945 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
946 priv->account_removed_signal);
947 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
948 priv->account_changed_signal))
949 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
950 priv->account_changed_signal);
951 g_object_unref (G_OBJECT (priv->account_store));
954 priv->account_store = g_object_ref (G_OBJECT (account_store));
956 priv->account_removed_signal =
957 g_signal_connect (G_OBJECT(account_store), "account_removed",
958 G_CALLBACK (on_account_removed), self);
960 priv->account_inserted_signal =
961 g_signal_connect (G_OBJECT(account_store), "account_inserted",
962 G_CALLBACK (on_account_inserted), self);
964 priv->account_changed_signal =
965 g_signal_connect (G_OBJECT(account_store), "account_changed",
966 G_CALLBACK (on_account_changed), self);
968 modest_folder_view_update_model (MODEST_FOLDER_VIEW (self), account_store);
969 priv->reselect = FALSE;
970 modest_folder_view_select_first_inbox_or_local (MODEST_FOLDER_VIEW (self));
972 g_object_unref (G_OBJECT (device));
976 on_account_inserted (TnyAccountStore *account_store,
980 ModestFolderViewPrivate *priv;
981 GtkTreeModel *sort_model, *filter_model;
983 /* Ignore transport account insertions, we're not showing them
984 in the folder view */
985 if (TNY_IS_TRANSPORT_ACCOUNT (account))
988 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (user_data);
991 /* If we're adding a new account, and there is no previous
992 one, we need to select the visible server account */
993 if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE &&
994 !priv->visible_account_id)
995 modest_widget_memory_restore (modest_runtime_get_conf(),
996 G_OBJECT (user_data),
997 MODEST_CONF_FOLDER_VIEW_KEY);
999 if (!GTK_IS_TREE_VIEW(user_data)) {
1000 g_warning ("BUG: %s: not a valid tree view", __FUNCTION__);
1004 /* Get the inner model */
1005 /* check, is some rare cases, we did not get the right thing here,
1007 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (user_data));
1008 if (!GTK_IS_TREE_MODEL_FILTER(filter_model)) {
1009 g_warning ("BUG: %s: not a valid filter model", __FUNCTION__);
1013 /* check, is some rare cases, we did not get the right thing here,
1015 sort_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
1016 if (!GTK_IS_TREE_MODEL_SORT(sort_model)) {
1017 g_warning ("BUG: %s: not a valid sort model", __FUNCTION__);
1021 /* Insert the account in the model */
1022 tny_list_append (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
1023 G_OBJECT (account));
1025 /* Refilter the model */
1026 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1031 same_account_selected (ModestFolderView *self,
1032 TnyAccount *account)
1034 ModestFolderViewPrivate *priv;
1035 gboolean same_account = FALSE;
1037 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1039 if (priv->cur_folder_store) {
1040 TnyAccount *selected_folder_account = NULL;
1042 if (TNY_IS_FOLDER (priv->cur_folder_store)) {
1043 selected_folder_account =
1044 modest_tny_folder_get_account (TNY_FOLDER (priv->cur_folder_store));
1046 selected_folder_account =
1047 TNY_ACCOUNT (g_object_ref (priv->cur_folder_store));
1050 if (selected_folder_account == account)
1051 same_account = TRUE;
1053 g_object_unref (selected_folder_account);
1055 return same_account;
1060 * Selects the first inbox or the local account in an idle
1063 on_idle_select_first_inbox_or_local (gpointer user_data)
1065 ModestFolderView *self = MODEST_FOLDER_VIEW (user_data);
1067 gdk_threads_enter ();
1068 modest_folder_view_select_first_inbox_or_local (self);
1069 gdk_threads_leave ();
1075 on_account_changed (TnyAccountStore *account_store,
1076 TnyAccount *tny_account,
1079 ModestFolderView *self;
1080 ModestFolderViewPrivate *priv;
1081 GtkTreeModel *sort_model, *filter_model;
1082 GtkTreeSelection *sel;
1083 gboolean same_account;
1085 /* Ignore transport account insertions, we're not showing them
1086 in the folder view */
1087 if (TNY_IS_TRANSPORT_ACCOUNT (tny_account))
1090 if (!MODEST_IS_FOLDER_VIEW(user_data)) {
1091 g_warning ("BUG: %s: not a valid folder view", __FUNCTION__);
1095 self = MODEST_FOLDER_VIEW (user_data);
1096 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (user_data);
1098 /* Get the inner model */
1099 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (user_data));
1100 if (!GTK_IS_TREE_MODEL_FILTER(filter_model)) {
1101 g_warning ("BUG: %s: not a valid filter model", __FUNCTION__);
1105 sort_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
1106 if (!GTK_IS_TREE_MODEL_SORT(sort_model)) {
1107 g_warning ("BUG: %s: not a valid sort model", __FUNCTION__);
1111 /* Invalidate the cur_folder_store only if the selected folder
1112 belongs to the account that is being removed */
1113 same_account = same_account_selected (self, tny_account);
1115 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1116 gtk_tree_selection_unselect_all (sel);
1119 /* Remove the account from the model */
1120 tny_list_remove (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
1121 G_OBJECT (tny_account));
1123 /* Insert the account in the model */
1124 tny_list_append (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
1125 G_OBJECT (tny_account));
1127 /* Refilter the model */
1128 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1130 /* Select the first INBOX if the currently selected folder
1131 belongs to the account that is being deleted */
1132 if (same_account && !MODEST_IS_TNY_LOCAL_FOLDERS_ACCOUNT (tny_account))
1133 g_idle_add (on_idle_select_first_inbox_or_local, self);
1137 on_account_removed (TnyAccountStore *account_store,
1138 TnyAccount *account,
1141 ModestFolderView *self = NULL;
1142 ModestFolderViewPrivate *priv;
1143 GtkTreeModel *sort_model, *filter_model;
1144 GtkTreeSelection *sel = NULL;
1145 gboolean same_account = FALSE;
1147 /* Ignore transport account removals, we're not showing them
1148 in the folder view */
1149 if (TNY_IS_TRANSPORT_ACCOUNT (account))
1152 if (!MODEST_IS_FOLDER_VIEW(user_data)) {
1153 g_warning ("BUG: %s: not a valid folder view", __FUNCTION__);
1157 self = MODEST_FOLDER_VIEW (user_data);
1158 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1160 /* Invalidate the cur_folder_store only if the selected folder
1161 belongs to the account that is being removed */
1162 same_account = same_account_selected (self, account);
1164 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1165 gtk_tree_selection_unselect_all (sel);
1168 /* Invalidate row to select only if the folder to select
1169 belongs to the account that is being removed*/
1170 if (priv->folder_to_select) {
1171 TnyAccount *folder_to_select_account = NULL;
1173 folder_to_select_account = tny_folder_get_account (priv->folder_to_select);
1174 if (folder_to_select_account == account) {
1175 modest_folder_view_disable_next_folder_selection (self);
1176 g_object_unref (priv->folder_to_select);
1177 priv->folder_to_select = NULL;
1179 g_object_unref (folder_to_select_account);
1182 /* Remove the account from the model */
1183 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1184 if (!GTK_IS_TREE_MODEL_FILTER(filter_model)) {
1185 g_warning ("BUG: %s: not a valid filter model", __FUNCTION__);
1189 sort_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
1190 if (!GTK_IS_TREE_MODEL_SORT(sort_model)) {
1191 g_warning ("BUG: %s: not a valid sort model", __FUNCTION__);
1195 tny_list_remove (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
1196 G_OBJECT (account));
1198 /* If the removed account is the currently viewed one then
1199 clear the configuration value. The new visible account will be the default account */
1200 if (priv->visible_account_id &&
1201 !strcmp (priv->visible_account_id, tny_account_get_id (account))) {
1203 /* Clear the current visible account_id */
1204 modest_folder_view_set_account_id_of_visible_server_account (self, NULL);
1206 /* Call the restore method, this will set the new visible account */
1207 modest_widget_memory_restore (modest_runtime_get_conf(), G_OBJECT(self),
1208 MODEST_CONF_FOLDER_VIEW_KEY);
1211 /* Refilter the model */
1212 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1214 /* Select the first INBOX if the currently selected folder
1215 belongs to the account that is being deleted */
1217 g_idle_add (on_idle_select_first_inbox_or_local, self);
1221 modest_folder_view_set_title (ModestFolderView *self, const gchar *title)
1223 GtkTreeViewColumn *col;
1225 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
1227 col = gtk_tree_view_get_column (GTK_TREE_VIEW(self), 0);
1229 g_printerr ("modest: failed get column for title\n");
1233 gtk_tree_view_column_set_title (col, title);
1234 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self),
1239 modest_folder_view_on_map (ModestFolderView *self,
1240 GdkEventExpose *event,
1243 ModestFolderViewPrivate *priv;
1245 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1247 /* This won't happen often */
1248 if (G_UNLIKELY (priv->reselect)) {
1249 /* Select the first inbox or the local account if not found */
1251 /* TODO: this could cause a lock at startup, so we
1252 comment it for the moment. We know that this will
1253 be a bug, because the INBOX is not selected, but we
1254 need to rewrite some parts of Modest to avoid the
1255 deathlock situation */
1256 /* TODO: check if this is still the case */
1257 priv->reselect = FALSE;
1258 modest_folder_view_select_first_inbox_or_local (self);
1259 /* Notify the display name observers */
1260 g_signal_emit (G_OBJECT(self),
1261 signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
1265 if (priv->reexpand) {
1266 expand_root_items (self);
1267 priv->reexpand = FALSE;
1274 modest_folder_view_new (TnyFolderStoreQuery *query)
1277 ModestFolderViewPrivate *priv;
1278 GtkTreeSelection *sel;
1280 self = G_OBJECT (g_object_new (MODEST_TYPE_FOLDER_VIEW,
1281 #ifdef MODEST_TOOLKIT_HILDON2
1282 "hildon-ui-mode", HILDON_UI_MODE_NORMAL,
1285 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1288 priv->query = g_object_ref (query);
1290 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1291 priv->changed_signal = g_signal_connect (sel, "changed",
1292 G_CALLBACK (on_selection_changed), self);
1294 g_signal_connect (self, "row-activated", G_CALLBACK (on_row_activated), self);
1296 g_signal_connect (self, "expose-event", G_CALLBACK (modest_folder_view_on_map), NULL);
1298 return GTK_WIDGET(self);
1301 /* this feels dirty; any other way to expand all the root items? */
1303 expand_root_items (ModestFolderView *self)
1306 GtkTreeModel *model;
1309 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1310 path = gtk_tree_path_new_first ();
1312 /* all folders should have child items, so.. */
1314 gtk_tree_view_expand_row (GTK_TREE_VIEW(self), path, FALSE);
1315 gtk_tree_path_next (path);
1316 } while (gtk_tree_model_get_iter (model, &iter, path));
1318 gtk_tree_path_free (path);
1322 * We use this function to implement the
1323 * MODEST_FOLDER_VIEW_STYLE_SHOW_ONE style. We only show the default
1324 * account in this case, and the local folders.
1327 filter_row (GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
1329 ModestFolderViewPrivate *priv;
1330 gboolean retval = TRUE;
1331 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1332 GObject *instance = NULL;
1333 const gchar *id = NULL;
1335 gboolean found = FALSE;
1336 gboolean cleared = FALSE;
1338 g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (data), FALSE);
1339 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (data);
1341 gtk_tree_model_get (model, iter,
1342 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
1343 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
1346 /* Do not show if there is no instance, this could indeed
1347 happen when the model is being modified while it's being
1348 drawn. This could occur for example when moving folders
1353 if (type == TNY_FOLDER_TYPE_ROOT) {
1354 /* TNY_FOLDER_TYPE_ROOT means that the instance is an
1355 account instead of a folder. */
1356 if (TNY_IS_ACCOUNT (instance)) {
1357 TnyAccount *acc = TNY_ACCOUNT (instance);
1358 const gchar *account_id = tny_account_get_id (acc);
1360 /* If it isn't a special folder,
1361 * don't show it unless it is the visible account: */
1362 if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE &&
1363 !modest_tny_account_is_virtual_local_folders (acc) &&
1364 strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
1366 /* Show only the visible account id */
1367 if (priv->visible_account_id) {
1368 if (strcmp (account_id, priv->visible_account_id))
1375 /* Never show these to the user. They are merged into one folder
1376 * in the local-folders account instead: */
1377 if (retval && MODEST_IS_TNY_OUTBOX_ACCOUNT (acc))
1382 /* Check hiding (if necessary) */
1383 cleared = modest_email_clipboard_cleared (priv->clipboard);
1384 if ((retval) && (!cleared) && (TNY_IS_FOLDER (instance))) {
1385 id = tny_folder_get_id (TNY_FOLDER(instance));
1386 if (priv->hidding_ids != NULL)
1387 for (i=0; i < priv->n_selected && !found; i++)
1388 if (priv->hidding_ids[i] != NULL && id != NULL)
1389 found = (!strcmp (priv->hidding_ids[i], id));
1395 /* If this is a move to dialog, hide Sent, Outbox and Drafts
1396 folder as no message can be move there according to UI specs */
1397 if (!priv->show_non_move) {
1399 case TNY_FOLDER_TYPE_OUTBOX:
1400 case TNY_FOLDER_TYPE_SENT:
1401 case TNY_FOLDER_TYPE_DRAFTS:
1404 case TNY_FOLDER_TYPE_UNKNOWN:
1405 case TNY_FOLDER_TYPE_NORMAL:
1406 type = modest_tny_folder_guess_folder_type(TNY_FOLDER(instance));
1407 if (type == TNY_FOLDER_TYPE_INVALID)
1408 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1410 if (type == TNY_FOLDER_TYPE_OUTBOX ||
1411 type == TNY_FOLDER_TYPE_SENT
1412 || type == TNY_FOLDER_TYPE_DRAFTS)
1421 g_object_unref (instance);
1428 modest_folder_view_update_model (ModestFolderView *self,
1429 TnyAccountStore *account_store)
1431 ModestFolderViewPrivate *priv;
1432 GtkTreeModel *model /* , *old_model */;
1433 GtkTreeModel *filter_model = NULL, *sortable = NULL;
1435 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW (self), FALSE);
1436 g_return_val_if_fail (account_store && MODEST_IS_TNY_ACCOUNT_STORE(account_store),
1439 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1441 /* Notify that there is no folder selected */
1442 g_signal_emit (G_OBJECT(self),
1443 signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
1445 if (priv->cur_folder_store) {
1446 g_object_unref (priv->cur_folder_store);
1447 priv->cur_folder_store = NULL;
1450 /* FIXME: the local accounts are not shown when the query
1451 selects only the subscribed folders */
1452 model = tny_gtk_folder_store_tree_model_new (NULL);
1454 /* Get the accounts: */
1455 tny_account_store_get_accounts (TNY_ACCOUNT_STORE(account_store),
1457 TNY_ACCOUNT_STORE_STORE_ACCOUNTS);
1459 sortable = gtk_tree_model_sort_new_with_model (model);
1460 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
1461 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
1462 GTK_SORT_ASCENDING);
1463 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
1464 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
1465 cmp_rows, NULL, NULL);
1467 /* Create filter model */
1468 filter_model = gtk_tree_model_filter_new (sortable, NULL);
1469 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
1475 gtk_tree_view_set_model (GTK_TREE_VIEW(self), filter_model);
1476 g_signal_connect (G_OBJECT(filter_model), "row-inserted",
1477 (GCallback) on_row_inserted_maybe_select_folder, self);
1479 g_object_unref (model);
1480 g_object_unref (filter_model);
1481 g_object_unref (sortable);
1483 /* Force a reselection of the INBOX next time the widget is shown */
1484 priv->reselect = TRUE;
1491 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
1493 GtkTreeModel *model = NULL;
1494 TnyFolderStore *folder = NULL;
1496 ModestFolderView *tree_view = NULL;
1497 ModestFolderViewPrivate *priv = NULL;
1498 gboolean selected = FALSE;
1500 g_return_if_fail (sel);
1501 g_return_if_fail (user_data);
1503 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
1505 selected = gtk_tree_selection_get_selected (sel, &model, &iter);
1507 tree_view = MODEST_FOLDER_VIEW (user_data);
1510 gtk_tree_model_get (model, &iter,
1511 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
1514 /* If the folder is the same do not notify */
1515 if (folder && priv->cur_folder_store == folder) {
1516 g_object_unref (folder);
1521 /* Current folder was unselected */
1522 if (priv->cur_folder_store) {
1523 /* We must do this firstly because a libtinymail-camel
1524 implementation detail. If we issue the signal
1525 before doing the sync_async, then that signal could
1526 cause (and it actually does it) a free of the
1527 summary of the folder (because the main window will
1528 clear the headers view */
1529 if (TNY_IS_FOLDER(priv->cur_folder_store))
1530 tny_folder_sync_async (TNY_FOLDER(priv->cur_folder_store),
1531 FALSE, NULL, NULL, NULL);
1533 g_signal_emit (G_OBJECT(tree_view), signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
1534 priv->cur_folder_store, FALSE);
1536 g_object_unref (priv->cur_folder_store);
1537 priv->cur_folder_store = NULL;
1540 /* New current references */
1541 priv->cur_folder_store = folder;
1543 /* New folder has been selected. Do not notify if there is
1544 nothing new selected */
1546 g_signal_emit (G_OBJECT(tree_view),
1547 signals[FOLDER_SELECTION_CHANGED_SIGNAL],
1548 0, priv->cur_folder_store, TRUE);
1553 on_row_activated (GtkTreeView *treeview,
1554 GtkTreePath *treepath,
1555 GtkTreeViewColumn *column,
1558 GtkTreeModel *model = NULL;
1559 TnyFolderStore *folder = NULL;
1561 ModestFolderView *self = NULL;
1562 ModestFolderViewPrivate *priv = NULL;
1564 g_return_if_fail (treeview);
1565 g_return_if_fail (user_data);
1567 self = MODEST_FOLDER_VIEW (user_data);
1568 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
1570 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1572 if (!gtk_tree_model_get_iter (model, &iter, treepath))
1575 gtk_tree_model_get (model, &iter,
1576 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
1579 g_signal_emit (G_OBJECT(self),
1580 signals[FOLDER_ACTIVATED_SIGNAL],
1583 g_object_unref (folder);
1587 modest_folder_view_get_selected (ModestFolderView *self)
1589 ModestFolderViewPrivate *priv;
1591 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW(self), NULL);
1593 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1594 if (priv->cur_folder_store)
1595 g_object_ref (priv->cur_folder_store);
1597 return priv->cur_folder_store;
1601 get_cmp_rows_type_pos (GObject *folder)
1603 /* Remote accounts -> Local account -> MMC account .*/
1606 if (TNY_IS_ACCOUNT (folder) &&
1607 modest_tny_account_is_virtual_local_folders (
1608 TNY_ACCOUNT (folder))) {
1610 } else if (TNY_IS_ACCOUNT (folder)) {
1611 TnyAccount *account = TNY_ACCOUNT (folder);
1612 const gchar *account_id = tny_account_get_id (account);
1613 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
1619 printf ("DEBUG: %s: unexpected type.\n", __FUNCTION__);
1620 return -1; /* Should never happen */
1625 get_cmp_subfolder_type_pos (TnyFolderType t)
1627 /* Inbox, Outbox, Drafts, Sent, User */
1631 case TNY_FOLDER_TYPE_INBOX:
1634 case TNY_FOLDER_TYPE_OUTBOX:
1637 case TNY_FOLDER_TYPE_DRAFTS:
1640 case TNY_FOLDER_TYPE_SENT:
1649 * This function orders the mail accounts according to these rules:
1650 * 1st - remote accounts
1651 * 2nd - local account
1655 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1659 gchar *name1 = NULL;
1660 gchar *name2 = NULL;
1661 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1662 TnyFolderType type2 = TNY_FOLDER_TYPE_UNKNOWN;
1663 GObject *folder1 = NULL;
1664 GObject *folder2 = NULL;
1666 gtk_tree_model_get (tree_model, iter1,
1667 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name1,
1668 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
1669 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder1,
1671 gtk_tree_model_get (tree_model, iter2,
1672 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name2,
1673 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type2,
1674 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder2,
1677 /* Return if we get no folder. This could happen when folder
1678 operations are happening. The model is updated after the
1679 folder copy/move actually occurs, so there could be
1680 situations where the model to be drawn is not correct */
1681 if (!folder1 || !folder2)
1684 if (type == TNY_FOLDER_TYPE_ROOT) {
1685 /* Compare the types, so that
1686 * Remote accounts -> Local account -> MMC account .*/
1687 const gint pos1 = get_cmp_rows_type_pos (folder1);
1688 const gint pos2 = get_cmp_rows_type_pos (folder2);
1689 /* printf ("DEBUG: %s:\n type1=%s, pos1=%d\n type2=%s, pos2=%d\n",
1690 __FUNCTION__, G_OBJECT_TYPE_NAME(folder1), pos1, G_OBJECT_TYPE_NAME(folder2), pos2); */
1693 else if (pos1 > pos2)
1696 /* Compare items of the same type: */
1698 TnyAccount *account1 = NULL;
1699 if (TNY_IS_ACCOUNT (folder1))
1700 account1 = TNY_ACCOUNT (folder1);
1702 TnyAccount *account2 = NULL;
1703 if (TNY_IS_ACCOUNT (folder2))
1704 account2 = TNY_ACCOUNT (folder2);
1706 const gchar *account_id = account1 ? tny_account_get_id (account1) : NULL;
1707 const gchar *account_id2 = account2 ? tny_account_get_id (account2) : NULL;
1709 if (!account_id && !account_id2) {
1711 } else if (!account_id) {
1713 } else if (!account_id2) {
1715 } else if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
1718 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
1722 gint cmp1 = 0, cmp2 = 0;
1723 /* get the parent to know if it's a local folder */
1726 gboolean has_parent;
1727 has_parent = gtk_tree_model_iter_parent (tree_model, &parent, iter1);
1729 GObject *parent_folder;
1730 TnyFolderType parent_type = TNY_FOLDER_TYPE_UNKNOWN;
1731 gtk_tree_model_get (tree_model, &parent,
1732 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &parent_type,
1733 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &parent_folder,
1735 if ((parent_type == TNY_FOLDER_TYPE_ROOT) &&
1736 TNY_IS_ACCOUNT (parent_folder)) {
1737 if (modest_tny_account_is_virtual_local_folders (TNY_ACCOUNT (parent_folder))) {
1738 cmp1 = get_cmp_subfolder_type_pos (modest_tny_folder_get_local_or_mmc_folder_type
1739 (TNY_FOLDER (folder1)));
1740 cmp2 = get_cmp_subfolder_type_pos (modest_tny_folder_get_local_or_mmc_folder_type
1741 (TNY_FOLDER (folder2)));
1742 } else if (modest_tny_account_is_memory_card_account (TNY_ACCOUNT (parent_folder))) {
1743 if (modest_local_folder_info_get_type (tny_folder_get_name (TNY_FOLDER (folder1))) == TNY_FOLDER_TYPE_ARCHIVE) {
1746 } else if (modest_local_folder_info_get_type (tny_folder_get_name (TNY_FOLDER (folder2))) == TNY_FOLDER_TYPE_ARCHIVE) {
1752 g_object_unref (parent_folder);
1755 /* if they are not local folders */
1757 cmp1 = get_cmp_subfolder_type_pos (tny_folder_get_folder_type (TNY_FOLDER (folder1)));
1758 cmp2 = get_cmp_subfolder_type_pos (tny_folder_get_folder_type (TNY_FOLDER (folder2)));
1762 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
1764 cmp = (cmp1 - cmp2);
1769 g_object_unref(G_OBJECT(folder1));
1771 g_object_unref(G_OBJECT(folder2));
1779 /*****************************************************************************/
1780 /* DRAG and DROP stuff */
1781 /*****************************************************************************/
1783 * This function fills the #GtkSelectionData with the row and the
1784 * model that has been dragged. It's called when this widget is a
1785 * source for dnd after the event drop happened
1788 on_drag_data_get (GtkWidget *widget, GdkDragContext *context, GtkSelectionData *selection_data,
1789 guint info, guint time, gpointer data)
1791 GtkTreeSelection *selection;
1792 GtkTreeModel *model;
1794 GtkTreePath *source_row;
1796 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1797 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
1799 source_row = gtk_tree_model_get_path (model, &iter);
1800 gtk_tree_set_row_drag_data (selection_data,
1804 gtk_tree_path_free (source_row);
1808 typedef struct _DndHelper {
1809 ModestFolderView *folder_view;
1810 gboolean delete_source;
1811 GtkTreePath *source_row;
1815 dnd_helper_destroyer (DndHelper *helper)
1817 /* Free the helper */
1818 gtk_tree_path_free (helper->source_row);
1819 g_slice_free (DndHelper, helper);
1823 xfer_folder_cb (ModestMailOperation *mail_op,
1824 TnyFolder *new_folder,
1828 /* Select the folder */
1829 modest_folder_view_select_folder (MODEST_FOLDER_VIEW (user_data),
1835 /* get the folder for the row the treepath refers to. */
1836 /* folder must be unref'd */
1837 static TnyFolderStore *
1838 tree_path_to_folder (GtkTreeModel *model, GtkTreePath *path)
1841 TnyFolderStore *folder = NULL;
1843 if (gtk_tree_model_get_iter (model,&iter, path))
1844 gtk_tree_model_get (model, &iter,
1845 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
1852 * This function is used by drag_data_received_cb to manage drag and
1853 * drop of a header, i.e, and drag from the header view to the folder
1857 drag_and_drop_from_header_view (GtkTreeModel *source_model,
1858 GtkTreeModel *dest_model,
1859 GtkTreePath *dest_row,
1860 GtkSelectionData *selection_data)
1862 TnyList *headers = NULL;
1863 TnyFolder *folder = NULL, *src_folder = NULL;
1864 TnyFolderType folder_type;
1865 GtkTreeIter source_iter, dest_iter;
1866 ModestWindowMgr *mgr = NULL;
1867 ModestWindow *main_win = NULL;
1868 gchar **uris, **tmp;
1870 /* Build the list of headers */
1871 mgr = modest_runtime_get_window_mgr ();
1872 headers = tny_simple_list_new ();
1873 uris = modest_dnd_selection_data_get_paths (selection_data);
1876 while (*tmp != NULL) {
1879 gboolean first = TRUE;
1882 path = gtk_tree_path_new_from_string (*tmp);
1883 gtk_tree_model_get_iter (source_model, &source_iter, path);
1884 gtk_tree_model_get (source_model, &source_iter,
1885 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1888 /* Do not enable d&d of headers already opened */
1889 if (!modest_window_mgr_find_registered_header(mgr, header, NULL))
1890 tny_list_append (headers, G_OBJECT (header));
1892 if (G_UNLIKELY (first)) {
1893 src_folder = tny_header_get_folder (header);
1897 /* Free and go on */
1898 gtk_tree_path_free (path);
1899 g_object_unref (header);
1904 /* This could happen ig we perform a d&d very quickly over the
1905 same row that row could dissapear because message is
1907 if (!TNY_IS_FOLDER (src_folder))
1910 /* Get the target folder */
1911 gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
1912 gtk_tree_model_get (dest_model, &dest_iter,
1913 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
1916 if (!folder || !TNY_IS_FOLDER(folder)) {
1917 /* g_warning ("%s: not a valid target folder (%p)", __FUNCTION__, folder); */
1921 folder_type = modest_tny_folder_guess_folder_type (folder);
1922 if (folder_type == TNY_FOLDER_TYPE_INVALID) {
1923 /* g_warning ("%s: invalid target folder", __FUNCTION__); */
1924 goto cleanup; /* cannot move messages there */
1927 if (modest_tny_folder_get_rules((TNY_FOLDER(folder))) & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE) {
1928 /* g_warning ("folder not writable"); */
1929 goto cleanup; /* verboten! */
1932 /* Ask for confirmation to move */
1933 main_win = modest_window_mgr_get_main_window (mgr, FALSE); /* don't create */
1935 g_warning ("%s: BUG: no main window found", __FUNCTION__);
1939 /* Transfer messages */
1940 modest_ui_actions_transfer_messages_helper (GTK_WINDOW (main_win), src_folder,
1945 if (G_IS_OBJECT (src_folder))
1946 g_object_unref (src_folder);
1947 if (G_IS_OBJECT(folder))
1948 g_object_unref (G_OBJECT (folder));
1949 if (G_IS_OBJECT(headers))
1950 g_object_unref (headers);
1954 TnyFolderStore *src_folder;
1955 TnyFolderStore *dst_folder;
1956 ModestFolderView *folder_view;
1961 dnd_folder_info_destroyer (DndFolderInfo *info)
1963 if (info->src_folder)
1964 g_object_unref (info->src_folder);
1965 if (info->dst_folder)
1966 g_object_unref (info->dst_folder);
1967 g_slice_free (DndFolderInfo, info);
1971 dnd_on_connection_failed_destroyer (DndFolderInfo *info,
1972 GtkWindow *parent_window,
1973 TnyAccount *account)
1976 modest_ui_actions_on_account_connection_error (parent_window, account);
1978 /* Free the helper & info */
1979 dnd_helper_destroyer (info->helper);
1980 dnd_folder_info_destroyer (info);
1984 drag_and_drop_from_folder_view_src_folder_performer (gboolean canceled,
1986 GtkWindow *parent_window,
1987 TnyAccount *account,
1990 DndFolderInfo *info = NULL;
1991 ModestMailOperation *mail_op;
1993 info = (DndFolderInfo *) user_data;
1995 if (err || canceled) {
1996 dnd_on_connection_failed_destroyer (info, parent_window, account);
2000 /* Do the mail operation */
2001 mail_op = modest_mail_operation_new_with_error_handling ((GObject *) parent_window,
2002 modest_ui_actions_move_folder_error_handler,
2003 info->src_folder, NULL);
2005 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
2008 /* Transfer the folder */
2009 modest_mail_operation_xfer_folder (mail_op,
2010 TNY_FOLDER (info->src_folder),
2012 info->helper->delete_source,
2014 info->helper->folder_view);
2017 g_object_unref (G_OBJECT (mail_op));
2018 dnd_helper_destroyer (info->helper);
2019 dnd_folder_info_destroyer (info);
2024 drag_and_drop_from_folder_view_dst_folder_performer (gboolean canceled,
2026 GtkWindow *parent_window,
2027 TnyAccount *account,
2030 DndFolderInfo *info = NULL;
2032 info = (DndFolderInfo *) user_data;
2034 if (err || canceled) {
2035 dnd_on_connection_failed_destroyer (info, parent_window, account);
2039 /* Connect to source folder and perform the copy/move */
2040 modest_platform_connect_if_remote_and_perform (NULL, TRUE,
2042 drag_and_drop_from_folder_view_src_folder_performer,
2047 * This function is used by drag_data_received_cb to manage drag and
2048 * drop of a folder, i.e, and drag from the folder view to the same
2052 drag_and_drop_from_folder_view (GtkTreeModel *source_model,
2053 GtkTreeModel *dest_model,
2054 GtkTreePath *dest_row,
2055 GtkSelectionData *selection_data,
2058 GtkTreeIter dest_iter, iter;
2059 TnyFolderStore *dest_folder = NULL;
2060 TnyFolderStore *folder = NULL;
2061 gboolean forbidden = FALSE;
2063 DndFolderInfo *info = NULL;
2065 win = modest_window_mgr_get_main_window (modest_runtime_get_window_mgr(), FALSE); /* don't create */
2067 g_warning ("%s: BUG: no main window", __FUNCTION__);
2068 dnd_helper_destroyer (helper);
2073 /* check the folder rules for the destination */
2074 folder = tree_path_to_folder (dest_model, dest_row);
2075 if (TNY_IS_FOLDER(folder)) {
2076 ModestTnyFolderRules rules =
2077 modest_tny_folder_get_rules (TNY_FOLDER (folder));
2078 forbidden = rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE;
2079 } else if (TNY_IS_FOLDER_STORE(folder)) {
2080 /* enable local root as destination for folders */
2081 if (!MODEST_IS_TNY_LOCAL_FOLDERS_ACCOUNT (folder) &&
2082 !modest_tny_account_is_memory_card_account (TNY_ACCOUNT (folder)))
2085 g_object_unref (folder);
2088 /* check the folder rules for the source */
2089 folder = tree_path_to_folder (source_model, helper->source_row);
2090 if (TNY_IS_FOLDER(folder)) {
2091 ModestTnyFolderRules rules =
2092 modest_tny_folder_get_rules (TNY_FOLDER (folder));
2093 forbidden = rules & MODEST_FOLDER_RULES_FOLDER_NON_MOVEABLE;
2096 g_object_unref (folder);
2100 /* Check if the drag is possible */
2101 if (forbidden || !gtk_tree_path_compare (helper->source_row, dest_row)) {
2103 modest_platform_run_information_dialog ((GtkWindow *) win,
2104 _("mail_in_ui_folder_move_target_error"),
2106 /* Restore the previous selection */
2107 folder = tree_path_to_folder (source_model, helper->source_row);
2109 if (TNY_IS_FOLDER (folder))
2110 modest_folder_view_select_folder (helper->folder_view,
2111 TNY_FOLDER (folder), FALSE);
2112 g_object_unref (folder);
2114 dnd_helper_destroyer (helper);
2119 gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
2120 gtk_tree_model_get (dest_model, &dest_iter,
2121 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
2123 gtk_tree_model_get_iter (source_model, &iter, helper->source_row);
2124 gtk_tree_model_get (source_model, &iter,
2125 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
2128 /* Create the info for the performer */
2129 info = g_slice_new0 (DndFolderInfo);
2130 info->src_folder = g_object_ref (folder);
2131 info->dst_folder = g_object_ref (dest_folder);
2132 info->helper = helper;
2134 /* Connect to the destination folder and perform the copy/move */
2135 modest_platform_connect_if_remote_and_perform (GTK_WINDOW (win), TRUE,
2137 drag_and_drop_from_folder_view_dst_folder_performer,
2141 g_object_unref (dest_folder);
2142 g_object_unref (folder);
2146 * This function receives the data set by the "drag-data-get" signal
2147 * handler. This information comes within the #GtkSelectionData. This
2148 * function will manage both the drags of folders of the treeview and
2149 * drags of headers of the header view widget.
2152 on_drag_data_received (GtkWidget *widget,
2153 GdkDragContext *context,
2156 GtkSelectionData *selection_data,
2161 GtkWidget *source_widget;
2162 GtkTreeModel *dest_model, *source_model;
2163 GtkTreePath *source_row, *dest_row;
2164 GtkTreeViewDropPosition pos;
2165 gboolean delete_source = FALSE;
2166 gboolean success = FALSE;
2168 /* Do not allow further process */
2169 g_signal_stop_emission_by_name (widget, "drag-data-received");
2170 source_widget = gtk_drag_get_source_widget (context);
2172 /* Get the action */
2173 if (context->action == GDK_ACTION_MOVE) {
2174 delete_source = TRUE;
2176 /* Notify that there is no folder selected. We need to
2177 do this in order to update the headers view (and
2178 its monitors, because when moving, the old folder
2179 won't longer exist. We can not wait for the end of
2180 the operation, because the operation won't start if
2181 the folder is in use */
2182 if (source_widget == widget) {
2183 GtkTreeSelection *sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
2184 gtk_tree_selection_unselect_all (sel);
2188 /* Check if the get_data failed */
2189 if (selection_data == NULL || selection_data->length < 0)
2192 /* Select the destination model */
2193 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
2195 /* Get the path to the destination row. Can not call
2196 gtk_tree_view_get_drag_dest_row() because the source row
2197 is not selected anymore */
2198 gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget), x, y,
2201 /* Only allow drops IN other rows */
2203 pos == GTK_TREE_VIEW_DROP_BEFORE ||
2204 pos == GTK_TREE_VIEW_DROP_AFTER)
2208 /* Drags from the header view */
2209 if (source_widget != widget) {
2210 source_model = gtk_tree_view_get_model (GTK_TREE_VIEW (source_widget));
2212 drag_and_drop_from_header_view (source_model,
2217 DndHelper *helper = NULL;
2219 /* Get the source model and row */
2220 gtk_tree_get_row_drag_data (selection_data,
2224 /* Create the helper */
2225 helper = g_slice_new0 (DndHelper);
2226 helper->delete_source = delete_source;
2227 helper->source_row = gtk_tree_path_copy (source_row);
2228 helper->folder_view = MODEST_FOLDER_VIEW (widget);
2230 drag_and_drop_from_folder_view (source_model,
2236 gtk_tree_path_free (source_row);
2240 gtk_tree_path_free (dest_row);
2243 /* Finish the drag and drop */
2244 gtk_drag_finish (context, success, FALSE, time);
2248 * We define a "drag-drop" signal handler because we do not want to
2249 * use the default one, because the default one always calls
2250 * gtk_drag_finish and we prefer to do it in the "drag-data-received"
2251 * signal handler, because there we have all the information available
2252 * to know if the dnd was a success or not.
2255 drag_drop_cb (GtkWidget *widget,
2256 GdkDragContext *context,
2264 if (!context->targets)
2267 /* Check if we're dragging a folder row */
2268 target = gtk_drag_dest_find_target (widget, context, NULL);
2270 /* Request the data from the source. */
2271 gtk_drag_get_data(widget, context, target, time);
2277 * This function expands a node of a tree view if it's not expanded
2278 * yet. Not sure why it needs the threads stuff, but gtk+`example code
2279 * does that, so that's why they're here.
2282 expand_row_timeout (gpointer data)
2284 GtkTreeView *tree_view = data;
2285 GtkTreePath *dest_path = NULL;
2286 GtkTreeViewDropPosition pos;
2287 gboolean result = FALSE;
2289 gdk_threads_enter ();
2291 gtk_tree_view_get_drag_dest_row (tree_view,
2296 (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER ||
2297 pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)) {
2298 gtk_tree_view_expand_row (tree_view, dest_path, FALSE);
2299 gtk_tree_path_free (dest_path);
2303 gtk_tree_path_free (dest_path);
2308 gdk_threads_leave ();
2314 * This function is called whenever the pointer is moved over a widget
2315 * while dragging some data. It installs a timeout that will expand a
2316 * node of the treeview if not expanded yet. This function also calls
2317 * gdk_drag_status in order to set the suggested action that will be
2318 * used by the "drag-data-received" signal handler to know if we
2319 * should do a move or just a copy of the data.
2322 on_drag_motion (GtkWidget *widget,
2323 GdkDragContext *context,
2329 GtkTreeViewDropPosition pos;
2330 GtkTreePath *dest_row;
2331 GtkTreeModel *dest_model;
2332 ModestFolderViewPrivate *priv;
2333 GdkDragAction suggested_action;
2334 gboolean valid_location = FALSE;
2335 TnyFolderStore *folder = NULL;
2337 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
2339 if (priv->timer_expander != 0) {
2340 g_source_remove (priv->timer_expander);
2341 priv->timer_expander = 0;
2344 gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
2349 /* Do not allow drops between folders */
2351 pos == GTK_TREE_VIEW_DROP_BEFORE ||
2352 pos == GTK_TREE_VIEW_DROP_AFTER) {
2353 gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW (widget), NULL, 0);
2354 gdk_drag_status(context, 0, time);
2355 valid_location = FALSE;
2358 valid_location = TRUE;
2361 /* Check that the destination folder is writable */
2362 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
2363 folder = tree_path_to_folder (dest_model, dest_row);
2364 if (folder && TNY_IS_FOLDER (folder)) {
2365 ModestTnyFolderRules rules = modest_tny_folder_get_rules(TNY_FOLDER (folder));
2367 if (rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE) {
2368 valid_location = FALSE;
2373 /* Expand the selected row after 1/2 second */
2374 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), dest_row)) {
2375 priv->timer_expander = g_timeout_add (500, expand_row_timeout, widget);
2377 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), dest_row, pos);
2379 /* Select the desired action. By default we pick MOVE */
2380 suggested_action = GDK_ACTION_MOVE;
2382 if (context->actions == GDK_ACTION_COPY)
2383 gdk_drag_status(context, GDK_ACTION_COPY, time);
2384 else if (context->actions == GDK_ACTION_MOVE)
2385 gdk_drag_status(context, GDK_ACTION_MOVE, time);
2386 else if (context->actions & suggested_action)
2387 gdk_drag_status(context, suggested_action, time);
2389 gdk_drag_status(context, GDK_ACTION_DEFAULT, time);
2393 g_object_unref (folder);
2395 gtk_tree_path_free (dest_row);
2397 g_signal_stop_emission_by_name (widget, "drag-motion");
2399 return valid_location;
2403 * This function sets the treeview as a source and a target for dnd
2404 * events. It also connects all the requirede signals.
2407 setup_drag_and_drop (GtkTreeView *self)
2409 /* Set up the folder view as a dnd destination. Set only the
2410 highlight flag, otherwise gtk will have a different
2412 #ifdef MODEST_TOOLKIT_HILDON2
2415 gtk_drag_dest_set (GTK_WIDGET (self),
2416 GTK_DEST_DEFAULT_HIGHLIGHT,
2417 folder_view_drag_types,
2418 G_N_ELEMENTS (folder_view_drag_types),
2419 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2421 g_signal_connect (G_OBJECT (self),
2422 "drag_data_received",
2423 G_CALLBACK (on_drag_data_received),
2427 /* Set up the treeview as a dnd source */
2428 gtk_drag_source_set (GTK_WIDGET (self),
2430 folder_view_drag_types,
2431 G_N_ELEMENTS (folder_view_drag_types),
2432 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2434 g_signal_connect (G_OBJECT (self),
2436 G_CALLBACK (on_drag_motion),
2439 g_signal_connect (G_OBJECT (self),
2441 G_CALLBACK (on_drag_data_get),
2444 g_signal_connect (G_OBJECT (self),
2446 G_CALLBACK (drag_drop_cb),
2451 * This function manages the navigation through the folders using the
2452 * keyboard or the hardware keys in the device
2455 on_key_pressed (GtkWidget *self,
2459 GtkTreeSelection *selection;
2461 GtkTreeModel *model;
2462 gboolean retval = FALSE;
2464 /* Up and Down are automatically managed by the treeview */
2465 if (event->keyval == GDK_Return) {
2466 /* Expand/Collapse the selected row */
2467 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2468 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
2471 path = gtk_tree_model_get_path (model, &iter);
2473 if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (self), path))
2474 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
2476 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
2477 gtk_tree_path_free (path);
2479 /* No further processing */
2487 * We listen to the changes in the local folder account name key,
2488 * because we want to show the right name in the view. The local
2489 * folder account name corresponds to the device name in the Maemo
2490 * version. We do this because we do not want to query gconf on each
2491 * tree view refresh. It's better to cache it and change whenever
2495 on_configuration_key_changed (ModestConf* conf,
2497 ModestConfEvent event,
2498 ModestConfNotificationId id,
2499 ModestFolderView *self)
2501 ModestFolderViewPrivate *priv;
2504 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
2505 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2507 if (!strcmp (key, MODEST_CONF_DEVICE_NAME)) {
2508 g_free (priv->local_account_name);
2510 if (event == MODEST_CONF_EVENT_KEY_UNSET)
2511 priv->local_account_name = g_strdup (MODEST_LOCAL_FOLDERS_DEFAULT_DISPLAY_NAME);
2513 priv->local_account_name = modest_conf_get_string (modest_runtime_get_conf(),
2514 MODEST_CONF_DEVICE_NAME, NULL);
2516 /* Force a redraw */
2517 #if GTK_CHECK_VERSION(2, 8, 0)
2518 GtkTreeViewColumn * tree_column;
2520 tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self),
2521 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN);
2522 gtk_tree_view_column_queue_resize (tree_column);
2524 gtk_widget_queue_draw (GTK_WIDGET (self));
2530 modest_folder_view_set_style (ModestFolderView *self,
2531 ModestFolderViewStyle style)
2533 ModestFolderViewPrivate *priv;
2535 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2536 g_return_if_fail (style == MODEST_FOLDER_VIEW_STYLE_SHOW_ALL ||
2537 style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE);
2539 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2542 priv->style = style;
2546 modest_folder_view_set_account_id_of_visible_server_account (ModestFolderView *self,
2547 const gchar *account_id)
2549 ModestFolderViewPrivate *priv;
2550 GtkTreeModel *model;
2552 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2554 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2556 /* This will be used by the filter_row callback,
2557 * to decided which rows to show: */
2558 if (priv->visible_account_id) {
2559 g_free (priv->visible_account_id);
2560 priv->visible_account_id = NULL;
2563 priv->visible_account_id = g_strdup (account_id);
2566 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2567 if (GTK_IS_TREE_MODEL_FILTER (model))
2568 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2570 /* Save settings to gconf */
2571 modest_widget_memory_save (modest_runtime_get_conf (), G_OBJECT(self),
2572 MODEST_CONF_FOLDER_VIEW_KEY);
2576 modest_folder_view_get_account_id_of_visible_server_account (ModestFolderView *self)
2578 ModestFolderViewPrivate *priv;
2580 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW(self), NULL);
2582 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2584 return (const gchar *) priv->visible_account_id;
2588 find_inbox_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *inbox_iter)
2592 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2594 gtk_tree_model_get (model, iter,
2595 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN,
2598 gboolean result = FALSE;
2599 if (type == TNY_FOLDER_TYPE_INBOX) {
2603 *inbox_iter = *iter;
2607 if (gtk_tree_model_iter_children (model, &child, iter)) {
2608 if (find_inbox_iter (model, &child, inbox_iter))
2612 } while (gtk_tree_model_iter_next (model, iter));
2621 modest_folder_view_select_first_inbox_or_local (ModestFolderView *self)
2623 GtkTreeModel *model;
2624 GtkTreeIter iter, inbox_iter;
2625 GtkTreeSelection *sel;
2626 GtkTreePath *path = NULL;
2628 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2630 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2634 expand_root_items (self);
2635 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2637 if (!gtk_tree_model_get_iter_first (model, &iter)) {
2638 g_warning ("%s: model is empty", __FUNCTION__);
2642 if (find_inbox_iter (model, &iter, &inbox_iter))
2643 path = gtk_tree_model_get_path (model, &inbox_iter);
2645 path = gtk_tree_path_new_first ();
2647 /* Select the row and free */
2648 gtk_tree_view_set_cursor (GTK_TREE_VIEW (self), path, NULL, FALSE);
2649 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (self), path, NULL, FALSE, 0.0, 0.0);
2650 gtk_tree_path_free (path);
2653 gtk_widget_grab_focus (GTK_WIDGET(self));
2659 find_folder_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *folder_iter,
2664 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2665 TnyFolder* a_folder;
2668 gtk_tree_model_get (model, iter,
2669 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &a_folder,
2670 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name,
2671 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
2675 if (folder == a_folder) {
2676 g_object_unref (a_folder);
2677 *folder_iter = *iter;
2680 g_object_unref (a_folder);
2682 if (gtk_tree_model_iter_children (model, &child, iter)) {
2683 if (find_folder_iter (model, &child, folder_iter, folder))
2687 } while (gtk_tree_model_iter_next (model, iter));
2694 on_row_inserted_maybe_select_folder (GtkTreeModel *tree_model,
2697 ModestFolderView *self)
2699 ModestFolderViewPrivate *priv = NULL;
2700 GtkTreeSelection *sel;
2701 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2702 GObject *instance = NULL;
2704 if (!MODEST_IS_FOLDER_VIEW(self))
2707 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2709 priv->reexpand = TRUE;
2711 gtk_tree_model_get (tree_model, iter,
2712 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
2713 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
2715 if (type == TNY_FOLDER_TYPE_INBOX && priv->folder_to_select == NULL) {
2716 priv->folder_to_select = g_object_ref (instance);
2718 g_object_unref (instance);
2720 if (priv->folder_to_select) {
2722 if (!modest_folder_view_select_folder (self, priv->folder_to_select,
2725 path = gtk_tree_model_get_path (tree_model, iter);
2726 gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
2728 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2730 gtk_tree_selection_select_iter (sel, iter);
2731 gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
2733 gtk_tree_path_free (path);
2737 modest_folder_view_disable_next_folder_selection (self);
2739 /* Refilter the model */
2740 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (tree_model));
2746 modest_folder_view_disable_next_folder_selection (ModestFolderView *self)
2748 ModestFolderViewPrivate *priv;
2750 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2752 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2754 if (priv->folder_to_select)
2755 g_object_unref(priv->folder_to_select);
2757 priv->folder_to_select = NULL;
2761 modest_folder_view_select_folder (ModestFolderView *self, TnyFolder *folder,
2762 gboolean after_change)
2764 GtkTreeModel *model;
2765 GtkTreeIter iter, folder_iter;
2766 GtkTreeSelection *sel;
2767 ModestFolderViewPrivate *priv = NULL;
2769 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW (self), FALSE);
2770 g_return_val_if_fail (folder && TNY_IS_FOLDER (folder), FALSE);
2772 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2775 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2776 gtk_tree_selection_unselect_all (sel);
2778 if (priv->folder_to_select)
2779 g_object_unref(priv->folder_to_select);
2780 priv->folder_to_select = TNY_FOLDER(g_object_ref(folder));
2784 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2789 /* Refilter the model, before selecting the folder */
2790 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2792 if (!gtk_tree_model_get_iter_first (model, &iter)) {
2793 g_warning ("%s: model is empty", __FUNCTION__);
2797 if (find_folder_iter (model, &iter, &folder_iter, folder)) {
2800 path = gtk_tree_model_get_path (model, &folder_iter);
2801 gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
2803 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2804 gtk_tree_selection_select_iter (sel, &folder_iter);
2805 gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
2807 gtk_tree_path_free (path);
2815 modest_folder_view_copy_selection (ModestFolderView *self)
2817 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2819 /* Copy selection */
2820 _clipboard_set_selected_data (self, FALSE);
2824 modest_folder_view_cut_selection (ModestFolderView *folder_view)
2826 ModestFolderViewPrivate *priv = NULL;
2827 GtkTreeModel *model = NULL;
2828 const gchar **hidding = NULL;
2829 guint i, n_selected;
2831 g_return_if_fail (folder_view && MODEST_IS_FOLDER_VIEW (folder_view));
2832 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
2834 /* Copy selection */
2835 if (!_clipboard_set_selected_data (folder_view, TRUE))
2838 /* Get hidding ids */
2839 hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected);
2841 /* Clear hidding array created by previous cut operation */
2842 _clear_hidding_filter (MODEST_FOLDER_VIEW (folder_view));
2844 /* Copy hidding array */
2845 priv->n_selected = n_selected;
2846 priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
2847 for (i=0; i < n_selected; i++)
2848 priv->hidding_ids[i] = g_strdup(hidding[i]);
2850 /* Hide cut folders */
2851 model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
2852 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2856 modest_folder_view_copy_model (ModestFolderView *folder_view_src,
2857 ModestFolderView *folder_view_dst)
2859 GtkTreeModel *filter_model = NULL;
2860 GtkTreeModel *model = NULL;
2861 GtkTreeModel *new_filter_model = NULL;
2863 g_return_if_fail (folder_view_src && MODEST_IS_FOLDER_VIEW (folder_view_src));
2864 g_return_if_fail (folder_view_dst && MODEST_IS_FOLDER_VIEW (folder_view_dst));
2867 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view_src));
2868 model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER(filter_model));
2870 /* Build new filter model */
2871 new_filter_model = gtk_tree_model_filter_new (model, NULL);
2872 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (new_filter_model),
2876 /* Set copied model */
2877 gtk_tree_view_set_model (GTK_TREE_VIEW (folder_view_dst), new_filter_model);
2878 g_signal_connect (G_OBJECT(new_filter_model), "row-inserted",
2879 (GCallback) on_row_inserted_maybe_select_folder, folder_view_dst);
2882 g_object_unref (new_filter_model);
2886 modest_folder_view_show_non_move_folders (ModestFolderView *folder_view,
2889 GtkTreeModel *model = NULL;
2890 ModestFolderViewPrivate* priv;
2892 g_return_if_fail (folder_view && MODEST_IS_FOLDER_VIEW (folder_view));
2894 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
2895 priv->show_non_move = show;
2896 /* modest_folder_view_update_model(folder_view, */
2897 /* TNY_ACCOUNT_STORE(modest_runtime_get_account_store())); */
2899 /* Hide special folders */
2900 model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
2901 if (GTK_IS_TREE_MODEL_FILTER (model)) {
2902 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2906 /* Returns FALSE if it did not selected anything */
2908 _clipboard_set_selected_data (ModestFolderView *folder_view,
2911 ModestFolderViewPrivate *priv = NULL;
2912 TnyFolderStore *folder = NULL;
2913 gboolean retval = FALSE;
2915 g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (folder_view), FALSE);
2916 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
2918 /* Set selected data on clipboard */
2919 g_return_val_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard), FALSE);
2920 folder = modest_folder_view_get_selected (folder_view);
2922 /* Do not allow to select an account */
2923 if (TNY_IS_FOLDER (folder)) {
2924 modest_email_clipboard_set_data (priv->clipboard, TNY_FOLDER(folder), NULL, delete);
2929 g_object_unref (folder);
2935 _clear_hidding_filter (ModestFolderView *folder_view)
2937 ModestFolderViewPrivate *priv;
2940 g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view));
2941 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
2943 if (priv->hidding_ids != NULL) {
2944 for (i=0; i < priv->n_selected; i++)
2945 g_free (priv->hidding_ids[i]);
2946 g_free(priv->hidding_ids);
2952 on_display_name_changed (ModestAccountMgr *mgr,
2953 const gchar *account,
2956 ModestFolderView *self;
2958 self = MODEST_FOLDER_VIEW (user_data);
2960 /* Force a redraw */
2961 #if GTK_CHECK_VERSION(2, 8, 0)
2962 GtkTreeViewColumn * tree_column;
2964 tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self),
2965 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN);
2966 gtk_tree_view_column_queue_resize (tree_column);
2968 gtk_widget_queue_draw (GTK_WIDGET (self));
2973 modest_folder_view_set_cell_style (ModestFolderView *self,
2974 ModestFolderViewCellStyle cell_style)
2976 ModestFolderViewPrivate *priv = NULL;
2978 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
2979 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2981 priv->cell_style = cell_style;
2983 gtk_widget_queue_draw (GTK_WIDGET (self));