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);
460 item_name = g_strdup_printf ("<span weight='800'>%s</span>\n<span weight='800' size='x-small' color='grey'>%s</span>",
464 substring = g_strdup ("");
465 item_name = g_strdup_printf ("%s\n<span size='x-small' color='grey'>%s</span>",
471 /* Use bold font style if there are unread or unset messages */
473 item_name = g_strdup_printf ("%s (%d)", fname, number);
476 item_name = g_strdup (fname);
481 } else if (TNY_IS_ACCOUNT (instance)) {
482 /* If it's a server account */
483 if (modest_tny_account_is_virtual_local_folders (TNY_ACCOUNT (instance))) {
484 item_name = g_strdup (priv->local_account_name);
486 } else if (modest_tny_account_is_memory_card_account (TNY_ACCOUNT (instance))) {
487 /* fname is only correct when the items are first
488 * added to the model, not when the account is
489 * changed later, so get the name from the account
491 item_name = g_strdup (tny_account_get_name (TNY_ACCOUNT (instance)));
494 item_name = g_strdup (fname);
500 item_name = g_strdup ("unknown");
502 if (item_name && item_weight) {
503 /* Set the name in the treeview cell: */
504 g_object_set (rendobj,"markup", item_name, "weight", item_weight, NULL);
506 /* Notify display name observers */
507 /* TODO: What listens for this signal, and how can it use only the new name? */
508 if (((GObject *) priv->cur_folder_store) == instance) {
509 g_signal_emit (G_OBJECT(self),
510 signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
517 /* If it is a Memory card account, make sure that we have the correct name.
518 * This function will be trigerred again when the name has been retrieved: */
519 if (TNY_IS_STORE_ACCOUNT (instance) &&
520 modest_tny_account_is_memory_card_account (TNY_ACCOUNT (instance))) {
522 /* Get the account name asynchronously: */
523 GetMmcAccountNameData *callback_data =
524 g_slice_new0(GetMmcAccountNameData);
525 callback_data->self = self;
527 const gchar *name = tny_account_get_name (TNY_ACCOUNT(instance));
529 callback_data->previous_name = g_strdup (name);
531 modest_tny_account_get_mmc_account_name (TNY_STORE_ACCOUNT (instance),
532 on_get_mmc_account_name, callback_data);
536 g_object_unref (G_OBJECT (instance));
544 GdkPixbuf *pixbuf_open;
545 GdkPixbuf *pixbuf_close;
549 static inline GdkPixbuf *
550 get_composite_pixbuf (const gchar *icon_name,
552 GdkPixbuf *base_pixbuf)
554 GdkPixbuf *emblem, *retval = NULL;
556 emblem = modest_platform_get_icon (icon_name, size);
558 retval = gdk_pixbuf_copy (base_pixbuf);
559 gdk_pixbuf_composite (emblem, retval, 0, 0,
560 MIN (gdk_pixbuf_get_width (emblem),
561 gdk_pixbuf_get_width (retval)),
562 MIN (gdk_pixbuf_get_height (emblem),
563 gdk_pixbuf_get_height (retval)),
564 0, 0, 1, 1, GDK_INTERP_NEAREST, 255);
565 g_object_unref (emblem);
570 static inline ThreePixbufs *
571 get_composite_icons (const gchar *icon_code,
573 GdkPixbuf **pixbuf_open,
574 GdkPixbuf **pixbuf_close)
576 ThreePixbufs *retval;
579 *pixbuf = gdk_pixbuf_copy (modest_platform_get_icon (icon_code, MODEST_ICON_SIZE_SMALL));
582 *pixbuf_open = get_composite_pixbuf ("qgn_list_gene_fldr_exp",
583 MODEST_ICON_SIZE_SMALL,
587 *pixbuf_close = get_composite_pixbuf ("qgn_list_gene_fldr_clp",
588 MODEST_ICON_SIZE_SMALL,
591 retval = g_slice_new0 (ThreePixbufs);
593 retval->pixbuf = g_object_ref (*pixbuf);
595 retval->pixbuf_open = g_object_ref (*pixbuf_open);
597 retval->pixbuf_close = g_object_ref (*pixbuf_close);
603 get_folder_icons (TnyFolderType type, GObject *instance)
605 static GdkPixbuf *inbox_pixbuf = NULL, *outbox_pixbuf = NULL,
606 *junk_pixbuf = NULL, *sent_pixbuf = NULL,
607 *trash_pixbuf = NULL, *draft_pixbuf = NULL,
608 *normal_pixbuf = NULL, *anorm_pixbuf = NULL,
609 *ammc_pixbuf = NULL, *avirt_pixbuf = NULL;
611 static GdkPixbuf *inbox_pixbuf_open = NULL, *outbox_pixbuf_open = NULL,
612 *junk_pixbuf_open = NULL, *sent_pixbuf_open = NULL,
613 *trash_pixbuf_open = NULL, *draft_pixbuf_open = NULL,
614 *normal_pixbuf_open = NULL, *anorm_pixbuf_open = NULL,
615 *ammc_pixbuf_open = NULL, *avirt_pixbuf_open = NULL;
617 static GdkPixbuf *inbox_pixbuf_close = NULL, *outbox_pixbuf_close = NULL,
618 *junk_pixbuf_close = NULL, *sent_pixbuf_close = NULL,
619 *trash_pixbuf_close = NULL, *draft_pixbuf_close = NULL,
620 *normal_pixbuf_close = NULL, *anorm_pixbuf_close = NULL,
621 *ammc_pixbuf_close = NULL, *avirt_pixbuf_close = NULL;
623 ThreePixbufs *retval = NULL;
625 /* Sometimes an special folder is reported by the server as
626 NORMAL, like some versions of Dovecot */
627 if (type == TNY_FOLDER_TYPE_NORMAL ||
628 type == TNY_FOLDER_TYPE_UNKNOWN) {
629 type = modest_tny_folder_guess_folder_type (TNY_FOLDER (instance));
632 /* Remote folders should not be treated as special folders */
633 if (TNY_IS_FOLDER_STORE (instance) &&
634 type != TNY_FOLDER_TYPE_INBOX &&
635 modest_tny_folder_store_is_remote (TNY_FOLDER_STORE (instance))) {
636 return get_composite_icons (MODEST_FOLDER_ICON_NORMAL,
639 &normal_pixbuf_close);
643 case TNY_FOLDER_TYPE_INVALID:
644 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
647 case TNY_FOLDER_TYPE_ROOT:
648 if (TNY_IS_ACCOUNT (instance)) {
650 if (modest_tny_account_is_virtual_local_folders (TNY_ACCOUNT (instance))) {
651 retval = get_composite_icons (MODEST_FOLDER_ICON_LOCAL_FOLDERS,
654 &avirt_pixbuf_close);
656 const gchar *account_id = tny_account_get_id (TNY_ACCOUNT (instance));
658 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
659 retval = get_composite_icons (MODEST_FOLDER_ICON_MMC,
664 retval = get_composite_icons (MODEST_FOLDER_ICON_ACCOUNT,
667 &anorm_pixbuf_close);
672 case TNY_FOLDER_TYPE_INBOX:
673 retval = get_composite_icons (MODEST_FOLDER_ICON_INBOX,
676 &inbox_pixbuf_close);
678 case TNY_FOLDER_TYPE_OUTBOX:
679 retval = get_composite_icons (MODEST_FOLDER_ICON_OUTBOX,
682 &outbox_pixbuf_close);
684 case TNY_FOLDER_TYPE_JUNK:
685 retval = get_composite_icons (MODEST_FOLDER_ICON_JUNK,
690 case TNY_FOLDER_TYPE_SENT:
691 retval = get_composite_icons (MODEST_FOLDER_ICON_SENT,
696 case TNY_FOLDER_TYPE_TRASH:
697 retval = get_composite_icons (MODEST_FOLDER_ICON_TRASH,
700 &trash_pixbuf_close);
702 case TNY_FOLDER_TYPE_DRAFTS:
703 retval = get_composite_icons (MODEST_FOLDER_ICON_DRAFTS,
706 &draft_pixbuf_close);
708 case TNY_FOLDER_TYPE_NORMAL:
710 retval = get_composite_icons (MODEST_FOLDER_ICON_NORMAL,
713 &normal_pixbuf_close);
721 free_pixbufs (ThreePixbufs *pixbufs)
724 g_object_unref (pixbufs->pixbuf);
725 if (pixbufs->pixbuf_open)
726 g_object_unref (pixbufs->pixbuf_open);
727 if (pixbufs->pixbuf_close)
728 g_object_unref (pixbufs->pixbuf_close);
729 g_slice_free (ThreePixbufs, pixbufs);
733 icon_cell_data (GtkTreeViewColumn *column,
734 GtkCellRenderer *renderer,
735 GtkTreeModel *tree_model,
739 GObject *rendobj = NULL, *instance = NULL;
740 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
741 gboolean has_children;
742 ThreePixbufs *pixbufs;
744 rendobj = (GObject *) renderer;
746 gtk_tree_model_get (tree_model, iter,
747 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
748 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
754 has_children = gtk_tree_model_iter_has_child (tree_model, iter);
755 pixbufs = get_folder_icons (type, instance);
756 g_object_unref (instance);
759 g_object_set (rendobj, "pixbuf", pixbufs->pixbuf, NULL);
762 g_object_set (rendobj, "pixbuf-expander-open", pixbufs->pixbuf_open, NULL);
763 g_object_set (rendobj, "pixbuf-expander-closed", pixbufs->pixbuf_close, NULL);
766 free_pixbufs (pixbufs);
770 add_columns (GtkWidget *treeview)
772 GtkTreeViewColumn *column;
773 GtkCellRenderer *renderer;
774 GtkTreeSelection *sel;
777 column = gtk_tree_view_column_new ();
779 /* Set icon and text render function */
780 renderer = gtk_cell_renderer_pixbuf_new();
781 gtk_tree_view_column_pack_start (column, renderer, FALSE);
782 gtk_tree_view_column_set_cell_data_func(column, renderer,
783 icon_cell_data, treeview, NULL);
785 renderer = gtk_cell_renderer_text_new();
786 g_object_set (renderer, "ellipsize", PANGO_ELLIPSIZE_END,
787 "ellipsize-set", TRUE, NULL);
788 gtk_tree_view_column_pack_start (column, renderer, TRUE);
789 gtk_tree_view_column_set_cell_data_func(column, renderer,
790 text_cell_data, treeview, NULL);
792 /* Set selection mode */
793 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(treeview));
794 gtk_tree_selection_set_mode (sel, GTK_SELECTION_SINGLE);
796 /* Set treeview appearance */
797 gtk_tree_view_column_set_spacing (column, 2);
798 gtk_tree_view_column_set_resizable (column, TRUE);
799 gtk_tree_view_column_set_fixed_width (column, TRUE);
800 gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(treeview), FALSE);
801 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(treeview), FALSE);
804 gtk_tree_view_append_column (GTK_TREE_VIEW(treeview),column);
808 modest_folder_view_init (ModestFolderView *obj)
810 ModestFolderViewPrivate *priv;
813 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
815 priv->timer_expander = 0;
816 priv->account_store = NULL;
818 priv->style = MODEST_FOLDER_VIEW_STYLE_SHOW_ALL;
819 priv->cur_folder_store = NULL;
820 priv->visible_account_id = NULL;
821 priv->folder_to_select = NULL;
823 priv->reexpand = TRUE;
825 /* Initialize the local account name */
826 conf = modest_runtime_get_conf();
827 priv->local_account_name = modest_conf_get_string (conf, MODEST_CONF_DEVICE_NAME, NULL);
829 /* Init email clipboard */
830 priv->clipboard = modest_runtime_get_email_clipboard ();
831 priv->hidding_ids = NULL;
832 priv->n_selected = 0;
833 priv->reselect = FALSE;
834 priv->show_non_move = TRUE;
837 add_columns (GTK_WIDGET (obj));
839 /* Setup drag and drop */
840 setup_drag_and_drop (GTK_TREE_VIEW(obj));
842 /* Connect signals */
843 g_signal_connect (G_OBJECT (obj),
845 G_CALLBACK (on_key_pressed), NULL);
847 priv->display_name_changed_signal =
848 g_signal_connect (modest_runtime_get_account_mgr (),
849 "display_name_changed",
850 G_CALLBACK (on_display_name_changed),
854 * Track changes in the local account name (in the device it
855 * will be the device name)
857 priv->conf_key_signal = g_signal_connect (G_OBJECT(conf),
859 G_CALLBACK(on_configuration_key_changed),
864 tny_account_store_view_init (gpointer g, gpointer iface_data)
866 TnyAccountStoreViewIface *klass = (TnyAccountStoreViewIface *)g;
868 klass->set_account_store = modest_folder_view_set_account_store;
872 modest_folder_view_finalize (GObject *obj)
874 ModestFolderViewPrivate *priv;
875 GtkTreeSelection *sel;
877 g_return_if_fail (obj);
879 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
881 if (priv->timer_expander != 0) {
882 g_source_remove (priv->timer_expander);
883 priv->timer_expander = 0;
886 if (priv->account_store) {
887 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
888 priv->account_inserted_signal);
889 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
890 priv->account_removed_signal);
891 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
892 priv->account_changed_signal);
893 g_object_unref (G_OBJECT(priv->account_store));
894 priv->account_store = NULL;
897 if (g_signal_handler_is_connected (modest_runtime_get_account_mgr (),
898 priv->display_name_changed_signal)) {
899 g_signal_handler_disconnect (modest_runtime_get_account_mgr (),
900 priv->display_name_changed_signal);
901 priv->display_name_changed_signal = 0;
905 g_object_unref (G_OBJECT (priv->query));
909 if (priv->folder_to_select) {
910 g_object_unref (G_OBJECT(priv->folder_to_select));
911 priv->folder_to_select = NULL;
914 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(obj));
916 g_signal_handler_disconnect (G_OBJECT(sel), priv->changed_signal);
918 g_free (priv->local_account_name);
919 g_free (priv->visible_account_id);
921 if (priv->conf_key_signal) {
922 g_signal_handler_disconnect (modest_runtime_get_conf (),
923 priv->conf_key_signal);
924 priv->conf_key_signal = 0;
927 if (priv->cur_folder_store) {
928 g_object_unref (priv->cur_folder_store);
929 priv->cur_folder_store = NULL;
932 /* Clear hidding array created by cut operation */
933 _clear_hidding_filter (MODEST_FOLDER_VIEW (obj));
935 G_OBJECT_CLASS(parent_class)->finalize (obj);
940 modest_folder_view_set_account_store (TnyAccountStoreView *self, TnyAccountStore *account_store)
942 ModestFolderViewPrivate *priv;
945 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
946 g_return_if_fail (TNY_IS_ACCOUNT_STORE (account_store));
948 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
949 device = tny_account_store_get_device (account_store);
951 if (G_UNLIKELY (priv->account_store)) {
953 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
954 priv->account_inserted_signal))
955 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
956 priv->account_inserted_signal);
957 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
958 priv->account_removed_signal))
959 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
960 priv->account_removed_signal);
961 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
962 priv->account_changed_signal))
963 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
964 priv->account_changed_signal);
965 g_object_unref (G_OBJECT (priv->account_store));
968 priv->account_store = g_object_ref (G_OBJECT (account_store));
970 priv->account_removed_signal =
971 g_signal_connect (G_OBJECT(account_store), "account_removed",
972 G_CALLBACK (on_account_removed), self);
974 priv->account_inserted_signal =
975 g_signal_connect (G_OBJECT(account_store), "account_inserted",
976 G_CALLBACK (on_account_inserted), self);
978 priv->account_changed_signal =
979 g_signal_connect (G_OBJECT(account_store), "account_changed",
980 G_CALLBACK (on_account_changed), self);
982 modest_folder_view_update_model (MODEST_FOLDER_VIEW (self), account_store);
983 priv->reselect = FALSE;
984 modest_folder_view_select_first_inbox_or_local (MODEST_FOLDER_VIEW (self));
986 g_object_unref (G_OBJECT (device));
990 on_account_inserted (TnyAccountStore *account_store,
994 ModestFolderViewPrivate *priv;
995 GtkTreeModel *sort_model, *filter_model;
997 /* Ignore transport account insertions, we're not showing them
998 in the folder view */
999 if (TNY_IS_TRANSPORT_ACCOUNT (account))
1002 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (user_data);
1005 /* If we're adding a new account, and there is no previous
1006 one, we need to select the visible server account */
1007 if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE &&
1008 !priv->visible_account_id)
1009 modest_widget_memory_restore (modest_runtime_get_conf(),
1010 G_OBJECT (user_data),
1011 MODEST_CONF_FOLDER_VIEW_KEY);
1013 if (!GTK_IS_TREE_VIEW(user_data)) {
1014 g_warning ("BUG: %s: not a valid tree view", __FUNCTION__);
1018 /* Get the inner model */
1019 /* check, is some rare cases, we did not get the right thing here,
1021 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (user_data));
1022 if (!GTK_IS_TREE_MODEL_FILTER(filter_model)) {
1023 g_warning ("BUG: %s: not a valid filter model", __FUNCTION__);
1027 /* check, is some rare cases, we did not get the right thing here,
1029 sort_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
1030 if (!GTK_IS_TREE_MODEL_SORT(sort_model)) {
1031 g_warning ("BUG: %s: not a valid sort model", __FUNCTION__);
1035 /* Insert the account in the model */
1036 tny_list_append (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
1037 G_OBJECT (account));
1039 /* Refilter the model */
1040 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1045 same_account_selected (ModestFolderView *self,
1046 TnyAccount *account)
1048 ModestFolderViewPrivate *priv;
1049 gboolean same_account = FALSE;
1051 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1053 if (priv->cur_folder_store) {
1054 TnyAccount *selected_folder_account = NULL;
1056 if (TNY_IS_FOLDER (priv->cur_folder_store)) {
1057 selected_folder_account =
1058 modest_tny_folder_get_account (TNY_FOLDER (priv->cur_folder_store));
1060 selected_folder_account =
1061 TNY_ACCOUNT (g_object_ref (priv->cur_folder_store));
1064 if (selected_folder_account == account)
1065 same_account = TRUE;
1067 g_object_unref (selected_folder_account);
1069 return same_account;
1074 * Selects the first inbox or the local account in an idle
1077 on_idle_select_first_inbox_or_local (gpointer user_data)
1079 ModestFolderView *self = MODEST_FOLDER_VIEW (user_data);
1081 gdk_threads_enter ();
1082 modest_folder_view_select_first_inbox_or_local (self);
1083 gdk_threads_leave ();
1089 on_account_changed (TnyAccountStore *account_store,
1090 TnyAccount *tny_account,
1093 ModestFolderView *self;
1094 ModestFolderViewPrivate *priv;
1095 GtkTreeModel *sort_model, *filter_model;
1096 GtkTreeSelection *sel;
1097 gboolean same_account;
1099 /* Ignore transport account insertions, we're not showing them
1100 in the folder view */
1101 if (TNY_IS_TRANSPORT_ACCOUNT (tny_account))
1104 if (!MODEST_IS_FOLDER_VIEW(user_data)) {
1105 g_warning ("BUG: %s: not a valid folder view", __FUNCTION__);
1109 self = MODEST_FOLDER_VIEW (user_data);
1110 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (user_data);
1112 /* Get the inner model */
1113 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (user_data));
1114 if (!GTK_IS_TREE_MODEL_FILTER(filter_model)) {
1115 g_warning ("BUG: %s: not a valid filter model", __FUNCTION__);
1119 sort_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
1120 if (!GTK_IS_TREE_MODEL_SORT(sort_model)) {
1121 g_warning ("BUG: %s: not a valid sort model", __FUNCTION__);
1125 /* Invalidate the cur_folder_store only if the selected folder
1126 belongs to the account that is being removed */
1127 same_account = same_account_selected (self, tny_account);
1129 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1130 gtk_tree_selection_unselect_all (sel);
1133 /* Remove the account from the model */
1134 tny_list_remove (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
1135 G_OBJECT (tny_account));
1137 /* Insert the account in the model */
1138 tny_list_append (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
1139 G_OBJECT (tny_account));
1141 /* Refilter the model */
1142 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1144 /* Select the first INBOX if the currently selected folder
1145 belongs to the account that is being deleted */
1146 if (same_account && !MODEST_IS_TNY_LOCAL_FOLDERS_ACCOUNT (tny_account))
1147 g_idle_add (on_idle_select_first_inbox_or_local, self);
1151 on_account_removed (TnyAccountStore *account_store,
1152 TnyAccount *account,
1155 ModestFolderView *self = NULL;
1156 ModestFolderViewPrivate *priv;
1157 GtkTreeModel *sort_model, *filter_model;
1158 GtkTreeSelection *sel = NULL;
1159 gboolean same_account = FALSE;
1161 /* Ignore transport account removals, we're not showing them
1162 in the folder view */
1163 if (TNY_IS_TRANSPORT_ACCOUNT (account))
1166 if (!MODEST_IS_FOLDER_VIEW(user_data)) {
1167 g_warning ("BUG: %s: not a valid folder view", __FUNCTION__);
1171 self = MODEST_FOLDER_VIEW (user_data);
1172 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1174 /* Invalidate the cur_folder_store only if the selected folder
1175 belongs to the account that is being removed */
1176 same_account = same_account_selected (self, account);
1178 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1179 gtk_tree_selection_unselect_all (sel);
1182 /* Invalidate row to select only if the folder to select
1183 belongs to the account that is being removed*/
1184 if (priv->folder_to_select) {
1185 TnyAccount *folder_to_select_account = NULL;
1187 folder_to_select_account = tny_folder_get_account (priv->folder_to_select);
1188 if (folder_to_select_account == account) {
1189 modest_folder_view_disable_next_folder_selection (self);
1190 g_object_unref (priv->folder_to_select);
1191 priv->folder_to_select = NULL;
1193 g_object_unref (folder_to_select_account);
1196 /* Remove the account from the model */
1197 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1198 if (!GTK_IS_TREE_MODEL_FILTER(filter_model)) {
1199 g_warning ("BUG: %s: not a valid filter model", __FUNCTION__);
1203 sort_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
1204 if (!GTK_IS_TREE_MODEL_SORT(sort_model)) {
1205 g_warning ("BUG: %s: not a valid sort model", __FUNCTION__);
1209 tny_list_remove (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
1210 G_OBJECT (account));
1212 /* If the removed account is the currently viewed one then
1213 clear the configuration value. The new visible account will be the default account */
1214 if (priv->visible_account_id &&
1215 !strcmp (priv->visible_account_id, tny_account_get_id (account))) {
1217 /* Clear the current visible account_id */
1218 modest_folder_view_set_account_id_of_visible_server_account (self, NULL);
1220 /* Call the restore method, this will set the new visible account */
1221 modest_widget_memory_restore (modest_runtime_get_conf(), G_OBJECT(self),
1222 MODEST_CONF_FOLDER_VIEW_KEY);
1225 /* Refilter the model */
1226 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1228 /* Select the first INBOX if the currently selected folder
1229 belongs to the account that is being deleted */
1231 g_idle_add (on_idle_select_first_inbox_or_local, self);
1235 modest_folder_view_set_title (ModestFolderView *self, const gchar *title)
1237 GtkTreeViewColumn *col;
1239 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
1241 col = gtk_tree_view_get_column (GTK_TREE_VIEW(self), 0);
1243 g_printerr ("modest: failed get column for title\n");
1247 gtk_tree_view_column_set_title (col, title);
1248 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self),
1253 modest_folder_view_on_map (ModestFolderView *self,
1254 GdkEventExpose *event,
1257 ModestFolderViewPrivate *priv;
1259 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1261 /* This won't happen often */
1262 if (G_UNLIKELY (priv->reselect)) {
1263 /* Select the first inbox or the local account if not found */
1265 /* TODO: this could cause a lock at startup, so we
1266 comment it for the moment. We know that this will
1267 be a bug, because the INBOX is not selected, but we
1268 need to rewrite some parts of Modest to avoid the
1269 deathlock situation */
1270 /* TODO: check if this is still the case */
1271 priv->reselect = FALSE;
1272 modest_folder_view_select_first_inbox_or_local (self);
1273 /* Notify the display name observers */
1274 g_signal_emit (G_OBJECT(self),
1275 signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
1279 if (priv->reexpand) {
1280 expand_root_items (self);
1281 priv->reexpand = FALSE;
1288 modest_folder_view_new (TnyFolderStoreQuery *query)
1291 ModestFolderViewPrivate *priv;
1292 GtkTreeSelection *sel;
1294 self = G_OBJECT (g_object_new (MODEST_TYPE_FOLDER_VIEW,
1295 #ifdef MODEST_TOOLKIT_HILDON2
1296 "hildon-ui-mode", HILDON_UI_MODE_NORMAL,
1299 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1302 priv->query = g_object_ref (query);
1304 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1305 priv->changed_signal = g_signal_connect (sel, "changed",
1306 G_CALLBACK (on_selection_changed), self);
1308 g_signal_connect (self, "row-activated", G_CALLBACK (on_row_activated), self);
1310 g_signal_connect (self, "expose-event", G_CALLBACK (modest_folder_view_on_map), NULL);
1312 return GTK_WIDGET(self);
1315 /* this feels dirty; any other way to expand all the root items? */
1317 expand_root_items (ModestFolderView *self)
1320 GtkTreeModel *model;
1323 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1324 path = gtk_tree_path_new_first ();
1326 /* all folders should have child items, so.. */
1328 gtk_tree_view_expand_row (GTK_TREE_VIEW(self), path, FALSE);
1329 gtk_tree_path_next (path);
1330 } while (gtk_tree_model_get_iter (model, &iter, path));
1332 gtk_tree_path_free (path);
1336 * We use this function to implement the
1337 * MODEST_FOLDER_VIEW_STYLE_SHOW_ONE style. We only show the default
1338 * account in this case, and the local folders.
1341 filter_row (GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
1343 ModestFolderViewPrivate *priv;
1344 gboolean retval = TRUE;
1345 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1346 GObject *instance = NULL;
1347 const gchar *id = NULL;
1349 gboolean found = FALSE;
1350 gboolean cleared = FALSE;
1352 g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (data), FALSE);
1353 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (data);
1355 gtk_tree_model_get (model, iter,
1356 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
1357 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
1360 /* Do not show if there is no instance, this could indeed
1361 happen when the model is being modified while it's being
1362 drawn. This could occur for example when moving folders
1367 if (type == TNY_FOLDER_TYPE_ROOT) {
1368 /* TNY_FOLDER_TYPE_ROOT means that the instance is an
1369 account instead of a folder. */
1370 if (TNY_IS_ACCOUNT (instance)) {
1371 TnyAccount *acc = TNY_ACCOUNT (instance);
1372 const gchar *account_id = tny_account_get_id (acc);
1374 /* If it isn't a special folder,
1375 * don't show it unless it is the visible account: */
1376 if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE &&
1377 !modest_tny_account_is_virtual_local_folders (acc) &&
1378 strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
1380 /* Show only the visible account id */
1381 if (priv->visible_account_id) {
1382 if (strcmp (account_id, priv->visible_account_id))
1389 /* Never show these to the user. They are merged into one folder
1390 * in the local-folders account instead: */
1391 if (retval && MODEST_IS_TNY_OUTBOX_ACCOUNT (acc))
1396 /* Check hiding (if necessary) */
1397 cleared = modest_email_clipboard_cleared (priv->clipboard);
1398 if ((retval) && (!cleared) && (TNY_IS_FOLDER (instance))) {
1399 id = tny_folder_get_id (TNY_FOLDER(instance));
1400 if (priv->hidding_ids != NULL)
1401 for (i=0; i < priv->n_selected && !found; i++)
1402 if (priv->hidding_ids[i] != NULL && id != NULL)
1403 found = (!strcmp (priv->hidding_ids[i], id));
1409 /* If this is a move to dialog, hide Sent, Outbox and Drafts
1410 folder as no message can be move there according to UI specs */
1411 if (!priv->show_non_move) {
1413 case TNY_FOLDER_TYPE_OUTBOX:
1414 case TNY_FOLDER_TYPE_SENT:
1415 case TNY_FOLDER_TYPE_DRAFTS:
1418 case TNY_FOLDER_TYPE_UNKNOWN:
1419 case TNY_FOLDER_TYPE_NORMAL:
1420 type = modest_tny_folder_guess_folder_type(TNY_FOLDER(instance));
1421 if (type == TNY_FOLDER_TYPE_INVALID)
1422 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1424 if (type == TNY_FOLDER_TYPE_OUTBOX ||
1425 type == TNY_FOLDER_TYPE_SENT
1426 || type == TNY_FOLDER_TYPE_DRAFTS)
1435 g_object_unref (instance);
1442 modest_folder_view_update_model (ModestFolderView *self,
1443 TnyAccountStore *account_store)
1445 ModestFolderViewPrivate *priv;
1446 GtkTreeModel *model /* , *old_model */;
1447 GtkTreeModel *filter_model = NULL, *sortable = NULL;
1449 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW (self), FALSE);
1450 g_return_val_if_fail (account_store && MODEST_IS_TNY_ACCOUNT_STORE(account_store),
1453 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1455 /* Notify that there is no folder selected */
1456 g_signal_emit (G_OBJECT(self),
1457 signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
1459 if (priv->cur_folder_store) {
1460 g_object_unref (priv->cur_folder_store);
1461 priv->cur_folder_store = NULL;
1464 /* FIXME: the local accounts are not shown when the query
1465 selects only the subscribed folders */
1466 model = tny_gtk_folder_store_tree_model_new (NULL);
1468 /* Get the accounts: */
1469 tny_account_store_get_accounts (TNY_ACCOUNT_STORE(account_store),
1471 TNY_ACCOUNT_STORE_STORE_ACCOUNTS);
1473 sortable = gtk_tree_model_sort_new_with_model (model);
1474 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
1475 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
1476 GTK_SORT_ASCENDING);
1477 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
1478 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
1479 cmp_rows, NULL, NULL);
1481 /* Create filter model */
1482 filter_model = gtk_tree_model_filter_new (sortable, NULL);
1483 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
1489 gtk_tree_view_set_model (GTK_TREE_VIEW(self), filter_model);
1490 g_signal_connect (G_OBJECT(filter_model), "row-inserted",
1491 (GCallback) on_row_inserted_maybe_select_folder, self);
1493 g_object_unref (model);
1494 g_object_unref (filter_model);
1495 g_object_unref (sortable);
1497 /* Force a reselection of the INBOX next time the widget is shown */
1498 priv->reselect = TRUE;
1505 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
1507 GtkTreeModel *model = NULL;
1508 TnyFolderStore *folder = NULL;
1510 ModestFolderView *tree_view = NULL;
1511 ModestFolderViewPrivate *priv = NULL;
1512 gboolean selected = FALSE;
1514 g_return_if_fail (sel);
1515 g_return_if_fail (user_data);
1517 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
1519 selected = gtk_tree_selection_get_selected (sel, &model, &iter);
1521 tree_view = MODEST_FOLDER_VIEW (user_data);
1524 gtk_tree_model_get (model, &iter,
1525 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
1528 /* If the folder is the same do not notify */
1529 if (folder && priv->cur_folder_store == folder) {
1530 g_object_unref (folder);
1535 /* Current folder was unselected */
1536 if (priv->cur_folder_store) {
1537 /* We must do this firstly because a libtinymail-camel
1538 implementation detail. If we issue the signal
1539 before doing the sync_async, then that signal could
1540 cause (and it actually does it) a free of the
1541 summary of the folder (because the main window will
1542 clear the headers view */
1543 if (TNY_IS_FOLDER(priv->cur_folder_store))
1544 tny_folder_sync_async (TNY_FOLDER(priv->cur_folder_store),
1545 FALSE, NULL, NULL, NULL);
1547 g_signal_emit (G_OBJECT(tree_view), signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
1548 priv->cur_folder_store, FALSE);
1550 g_object_unref (priv->cur_folder_store);
1551 priv->cur_folder_store = NULL;
1554 /* New current references */
1555 priv->cur_folder_store = folder;
1557 /* New folder has been selected. Do not notify if there is
1558 nothing new selected */
1560 g_signal_emit (G_OBJECT(tree_view),
1561 signals[FOLDER_SELECTION_CHANGED_SIGNAL],
1562 0, priv->cur_folder_store, TRUE);
1567 on_row_activated (GtkTreeView *treeview,
1568 GtkTreePath *treepath,
1569 GtkTreeViewColumn *column,
1572 GtkTreeModel *model = NULL;
1573 TnyFolderStore *folder = NULL;
1575 ModestFolderView *self = NULL;
1576 ModestFolderViewPrivate *priv = NULL;
1578 g_return_if_fail (treeview);
1579 g_return_if_fail (user_data);
1581 self = MODEST_FOLDER_VIEW (user_data);
1582 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
1584 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1586 if (!gtk_tree_model_get_iter (model, &iter, treepath))
1589 gtk_tree_model_get (model, &iter,
1590 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
1593 g_signal_emit (G_OBJECT(self),
1594 signals[FOLDER_ACTIVATED_SIGNAL],
1597 g_object_unref (folder);
1601 modest_folder_view_get_selected (ModestFolderView *self)
1603 ModestFolderViewPrivate *priv;
1605 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW(self), NULL);
1607 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1608 if (priv->cur_folder_store)
1609 g_object_ref (priv->cur_folder_store);
1611 return priv->cur_folder_store;
1615 get_cmp_rows_type_pos (GObject *folder)
1617 /* Remote accounts -> Local account -> MMC account .*/
1620 if (TNY_IS_ACCOUNT (folder) &&
1621 modest_tny_account_is_virtual_local_folders (
1622 TNY_ACCOUNT (folder))) {
1624 } else if (TNY_IS_ACCOUNT (folder)) {
1625 TnyAccount *account = TNY_ACCOUNT (folder);
1626 const gchar *account_id = tny_account_get_id (account);
1627 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
1633 printf ("DEBUG: %s: unexpected type.\n", __FUNCTION__);
1634 return -1; /* Should never happen */
1639 get_cmp_subfolder_type_pos (TnyFolderType t)
1641 /* Inbox, Outbox, Drafts, Sent, User */
1645 case TNY_FOLDER_TYPE_INBOX:
1648 case TNY_FOLDER_TYPE_OUTBOX:
1651 case TNY_FOLDER_TYPE_DRAFTS:
1654 case TNY_FOLDER_TYPE_SENT:
1663 * This function orders the mail accounts according to these rules:
1664 * 1st - remote accounts
1665 * 2nd - local account
1669 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1673 gchar *name1 = NULL;
1674 gchar *name2 = NULL;
1675 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1676 TnyFolderType type2 = TNY_FOLDER_TYPE_UNKNOWN;
1677 GObject *folder1 = NULL;
1678 GObject *folder2 = NULL;
1680 gtk_tree_model_get (tree_model, iter1,
1681 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name1,
1682 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
1683 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder1,
1685 gtk_tree_model_get (tree_model, iter2,
1686 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name2,
1687 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type2,
1688 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder2,
1691 /* Return if we get no folder. This could happen when folder
1692 operations are happening. The model is updated after the
1693 folder copy/move actually occurs, so there could be
1694 situations where the model to be drawn is not correct */
1695 if (!folder1 || !folder2)
1698 if (type == TNY_FOLDER_TYPE_ROOT) {
1699 /* Compare the types, so that
1700 * Remote accounts -> Local account -> MMC account .*/
1701 const gint pos1 = get_cmp_rows_type_pos (folder1);
1702 const gint pos2 = get_cmp_rows_type_pos (folder2);
1703 /* printf ("DEBUG: %s:\n type1=%s, pos1=%d\n type2=%s, pos2=%d\n",
1704 __FUNCTION__, G_OBJECT_TYPE_NAME(folder1), pos1, G_OBJECT_TYPE_NAME(folder2), pos2); */
1707 else if (pos1 > pos2)
1710 /* Compare items of the same type: */
1712 TnyAccount *account1 = NULL;
1713 if (TNY_IS_ACCOUNT (folder1))
1714 account1 = TNY_ACCOUNT (folder1);
1716 TnyAccount *account2 = NULL;
1717 if (TNY_IS_ACCOUNT (folder2))
1718 account2 = TNY_ACCOUNT (folder2);
1720 const gchar *account_id = account1 ? tny_account_get_id (account1) : NULL;
1721 const gchar *account_id2 = account2 ? tny_account_get_id (account2) : NULL;
1723 if (!account_id && !account_id2) {
1725 } else if (!account_id) {
1727 } else if (!account_id2) {
1729 } else if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
1732 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
1736 gint cmp1 = 0, cmp2 = 0;
1737 /* get the parent to know if it's a local folder */
1740 gboolean has_parent;
1741 has_parent = gtk_tree_model_iter_parent (tree_model, &parent, iter1);
1743 GObject *parent_folder;
1744 TnyFolderType parent_type = TNY_FOLDER_TYPE_UNKNOWN;
1745 gtk_tree_model_get (tree_model, &parent,
1746 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &parent_type,
1747 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &parent_folder,
1749 if ((parent_type == TNY_FOLDER_TYPE_ROOT) &&
1750 TNY_IS_ACCOUNT (parent_folder)) {
1751 if (modest_tny_account_is_virtual_local_folders (TNY_ACCOUNT (parent_folder))) {
1752 cmp1 = get_cmp_subfolder_type_pos (modest_tny_folder_get_local_or_mmc_folder_type
1753 (TNY_FOLDER (folder1)));
1754 cmp2 = get_cmp_subfolder_type_pos (modest_tny_folder_get_local_or_mmc_folder_type
1755 (TNY_FOLDER (folder2)));
1756 } else if (modest_tny_account_is_memory_card_account (TNY_ACCOUNT (parent_folder))) {
1757 if (modest_local_folder_info_get_type (tny_folder_get_name (TNY_FOLDER (folder1))) == TNY_FOLDER_TYPE_ARCHIVE) {
1760 } else if (modest_local_folder_info_get_type (tny_folder_get_name (TNY_FOLDER (folder2))) == TNY_FOLDER_TYPE_ARCHIVE) {
1766 g_object_unref (parent_folder);
1769 /* if they are not local folders */
1771 cmp1 = get_cmp_subfolder_type_pos (tny_folder_get_folder_type (TNY_FOLDER (folder1)));
1772 cmp2 = get_cmp_subfolder_type_pos (tny_folder_get_folder_type (TNY_FOLDER (folder2)));
1776 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
1778 cmp = (cmp1 - cmp2);
1783 g_object_unref(G_OBJECT(folder1));
1785 g_object_unref(G_OBJECT(folder2));
1793 /*****************************************************************************/
1794 /* DRAG and DROP stuff */
1795 /*****************************************************************************/
1797 * This function fills the #GtkSelectionData with the row and the
1798 * model that has been dragged. It's called when this widget is a
1799 * source for dnd after the event drop happened
1802 on_drag_data_get (GtkWidget *widget, GdkDragContext *context, GtkSelectionData *selection_data,
1803 guint info, guint time, gpointer data)
1805 GtkTreeSelection *selection;
1806 GtkTreeModel *model;
1808 GtkTreePath *source_row;
1810 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1811 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
1813 source_row = gtk_tree_model_get_path (model, &iter);
1814 gtk_tree_set_row_drag_data (selection_data,
1818 gtk_tree_path_free (source_row);
1822 typedef struct _DndHelper {
1823 ModestFolderView *folder_view;
1824 gboolean delete_source;
1825 GtkTreePath *source_row;
1829 dnd_helper_destroyer (DndHelper *helper)
1831 /* Free the helper */
1832 gtk_tree_path_free (helper->source_row);
1833 g_slice_free (DndHelper, helper);
1837 xfer_folder_cb (ModestMailOperation *mail_op,
1838 TnyFolder *new_folder,
1842 /* Select the folder */
1843 modest_folder_view_select_folder (MODEST_FOLDER_VIEW (user_data),
1849 /* get the folder for the row the treepath refers to. */
1850 /* folder must be unref'd */
1851 static TnyFolderStore *
1852 tree_path_to_folder (GtkTreeModel *model, GtkTreePath *path)
1855 TnyFolderStore *folder = NULL;
1857 if (gtk_tree_model_get_iter (model,&iter, path))
1858 gtk_tree_model_get (model, &iter,
1859 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
1866 * This function is used by drag_data_received_cb to manage drag and
1867 * drop of a header, i.e, and drag from the header view to the folder
1871 drag_and_drop_from_header_view (GtkTreeModel *source_model,
1872 GtkTreeModel *dest_model,
1873 GtkTreePath *dest_row,
1874 GtkSelectionData *selection_data)
1876 TnyList *headers = NULL;
1877 TnyFolder *folder = NULL, *src_folder = NULL;
1878 TnyFolderType folder_type;
1879 GtkTreeIter source_iter, dest_iter;
1880 ModestWindowMgr *mgr = NULL;
1881 ModestWindow *main_win = NULL;
1882 gchar **uris, **tmp;
1884 /* Build the list of headers */
1885 mgr = modest_runtime_get_window_mgr ();
1886 headers = tny_simple_list_new ();
1887 uris = modest_dnd_selection_data_get_paths (selection_data);
1890 while (*tmp != NULL) {
1893 gboolean first = TRUE;
1896 path = gtk_tree_path_new_from_string (*tmp);
1897 gtk_tree_model_get_iter (source_model, &source_iter, path);
1898 gtk_tree_model_get (source_model, &source_iter,
1899 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1902 /* Do not enable d&d of headers already opened */
1903 if (!modest_window_mgr_find_registered_header(mgr, header, NULL))
1904 tny_list_append (headers, G_OBJECT (header));
1906 if (G_UNLIKELY (first)) {
1907 src_folder = tny_header_get_folder (header);
1911 /* Free and go on */
1912 gtk_tree_path_free (path);
1913 g_object_unref (header);
1918 /* This could happen ig we perform a d&d very quickly over the
1919 same row that row could dissapear because message is
1921 if (!TNY_IS_FOLDER (src_folder))
1924 /* Get the target folder */
1925 gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
1926 gtk_tree_model_get (dest_model, &dest_iter,
1927 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
1930 if (!folder || !TNY_IS_FOLDER(folder)) {
1931 /* g_warning ("%s: not a valid target folder (%p)", __FUNCTION__, folder); */
1935 folder_type = modest_tny_folder_guess_folder_type (folder);
1936 if (folder_type == TNY_FOLDER_TYPE_INVALID) {
1937 /* g_warning ("%s: invalid target folder", __FUNCTION__); */
1938 goto cleanup; /* cannot move messages there */
1941 if (modest_tny_folder_get_rules((TNY_FOLDER(folder))) & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE) {
1942 /* g_warning ("folder not writable"); */
1943 goto cleanup; /* verboten! */
1946 /* Ask for confirmation to move */
1947 main_win = modest_window_mgr_get_main_window (mgr, FALSE); /* don't create */
1949 g_warning ("%s: BUG: no main window found", __FUNCTION__);
1953 /* Transfer messages */
1954 modest_ui_actions_transfer_messages_helper (GTK_WINDOW (main_win), src_folder,
1959 if (G_IS_OBJECT (src_folder))
1960 g_object_unref (src_folder);
1961 if (G_IS_OBJECT(folder))
1962 g_object_unref (G_OBJECT (folder));
1963 if (G_IS_OBJECT(headers))
1964 g_object_unref (headers);
1968 TnyFolderStore *src_folder;
1969 TnyFolderStore *dst_folder;
1970 ModestFolderView *folder_view;
1975 dnd_folder_info_destroyer (DndFolderInfo *info)
1977 if (info->src_folder)
1978 g_object_unref (info->src_folder);
1979 if (info->dst_folder)
1980 g_object_unref (info->dst_folder);
1981 g_slice_free (DndFolderInfo, info);
1985 dnd_on_connection_failed_destroyer (DndFolderInfo *info,
1986 GtkWindow *parent_window,
1987 TnyAccount *account)
1990 modest_ui_actions_on_account_connection_error (parent_window, account);
1992 /* Free the helper & info */
1993 dnd_helper_destroyer (info->helper);
1994 dnd_folder_info_destroyer (info);
1998 drag_and_drop_from_folder_view_src_folder_performer (gboolean canceled,
2000 GtkWindow *parent_window,
2001 TnyAccount *account,
2004 DndFolderInfo *info = NULL;
2005 ModestMailOperation *mail_op;
2007 info = (DndFolderInfo *) user_data;
2009 if (err || canceled) {
2010 dnd_on_connection_failed_destroyer (info, parent_window, account);
2014 /* Do the mail operation */
2015 mail_op = modest_mail_operation_new_with_error_handling ((GObject *) parent_window,
2016 modest_ui_actions_move_folder_error_handler,
2017 info->src_folder, NULL);
2019 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
2022 /* Transfer the folder */
2023 modest_mail_operation_xfer_folder (mail_op,
2024 TNY_FOLDER (info->src_folder),
2026 info->helper->delete_source,
2028 info->helper->folder_view);
2031 g_object_unref (G_OBJECT (mail_op));
2032 dnd_helper_destroyer (info->helper);
2033 dnd_folder_info_destroyer (info);
2038 drag_and_drop_from_folder_view_dst_folder_performer (gboolean canceled,
2040 GtkWindow *parent_window,
2041 TnyAccount *account,
2044 DndFolderInfo *info = NULL;
2046 info = (DndFolderInfo *) user_data;
2048 if (err || canceled) {
2049 dnd_on_connection_failed_destroyer (info, parent_window, account);
2053 /* Connect to source folder and perform the copy/move */
2054 modest_platform_connect_if_remote_and_perform (NULL, TRUE,
2056 drag_and_drop_from_folder_view_src_folder_performer,
2061 * This function is used by drag_data_received_cb to manage drag and
2062 * drop of a folder, i.e, and drag from the folder view to the same
2066 drag_and_drop_from_folder_view (GtkTreeModel *source_model,
2067 GtkTreeModel *dest_model,
2068 GtkTreePath *dest_row,
2069 GtkSelectionData *selection_data,
2072 GtkTreeIter dest_iter, iter;
2073 TnyFolderStore *dest_folder = NULL;
2074 TnyFolderStore *folder = NULL;
2075 gboolean forbidden = FALSE;
2077 DndFolderInfo *info = NULL;
2079 win = modest_window_mgr_get_main_window (modest_runtime_get_window_mgr(), FALSE); /* don't create */
2081 g_warning ("%s: BUG: no main window", __FUNCTION__);
2082 dnd_helper_destroyer (helper);
2087 /* check the folder rules for the destination */
2088 folder = tree_path_to_folder (dest_model, dest_row);
2089 if (TNY_IS_FOLDER(folder)) {
2090 ModestTnyFolderRules rules =
2091 modest_tny_folder_get_rules (TNY_FOLDER (folder));
2092 forbidden = rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE;
2093 } else if (TNY_IS_FOLDER_STORE(folder)) {
2094 /* enable local root as destination for folders */
2095 if (!MODEST_IS_TNY_LOCAL_FOLDERS_ACCOUNT (folder) &&
2096 !modest_tny_account_is_memory_card_account (TNY_ACCOUNT (folder)))
2099 g_object_unref (folder);
2102 /* check the folder rules for the source */
2103 folder = tree_path_to_folder (source_model, helper->source_row);
2104 if (TNY_IS_FOLDER(folder)) {
2105 ModestTnyFolderRules rules =
2106 modest_tny_folder_get_rules (TNY_FOLDER (folder));
2107 forbidden = rules & MODEST_FOLDER_RULES_FOLDER_NON_MOVEABLE;
2110 g_object_unref (folder);
2114 /* Check if the drag is possible */
2115 if (forbidden || !gtk_tree_path_compare (helper->source_row, dest_row)) {
2117 modest_platform_run_information_dialog ((GtkWindow *) win,
2118 _("mail_in_ui_folder_move_target_error"),
2120 /* Restore the previous selection */
2121 folder = tree_path_to_folder (source_model, helper->source_row);
2123 if (TNY_IS_FOLDER (folder))
2124 modest_folder_view_select_folder (helper->folder_view,
2125 TNY_FOLDER (folder), FALSE);
2126 g_object_unref (folder);
2128 dnd_helper_destroyer (helper);
2133 gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
2134 gtk_tree_model_get (dest_model, &dest_iter,
2135 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
2137 gtk_tree_model_get_iter (source_model, &iter, helper->source_row);
2138 gtk_tree_model_get (source_model, &iter,
2139 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
2142 /* Create the info for the performer */
2143 info = g_slice_new0 (DndFolderInfo);
2144 info->src_folder = g_object_ref (folder);
2145 info->dst_folder = g_object_ref (dest_folder);
2146 info->helper = helper;
2148 /* Connect to the destination folder and perform the copy/move */
2149 modest_platform_connect_if_remote_and_perform (GTK_WINDOW (win), TRUE,
2151 drag_and_drop_from_folder_view_dst_folder_performer,
2155 g_object_unref (dest_folder);
2156 g_object_unref (folder);
2160 * This function receives the data set by the "drag-data-get" signal
2161 * handler. This information comes within the #GtkSelectionData. This
2162 * function will manage both the drags of folders of the treeview and
2163 * drags of headers of the header view widget.
2166 on_drag_data_received (GtkWidget *widget,
2167 GdkDragContext *context,
2170 GtkSelectionData *selection_data,
2175 GtkWidget *source_widget;
2176 GtkTreeModel *dest_model, *source_model;
2177 GtkTreePath *source_row, *dest_row;
2178 GtkTreeViewDropPosition pos;
2179 gboolean delete_source = FALSE;
2180 gboolean success = FALSE;
2182 /* Do not allow further process */
2183 g_signal_stop_emission_by_name (widget, "drag-data-received");
2184 source_widget = gtk_drag_get_source_widget (context);
2186 /* Get the action */
2187 if (context->action == GDK_ACTION_MOVE) {
2188 delete_source = TRUE;
2190 /* Notify that there is no folder selected. We need to
2191 do this in order to update the headers view (and
2192 its monitors, because when moving, the old folder
2193 won't longer exist. We can not wait for the end of
2194 the operation, because the operation won't start if
2195 the folder is in use */
2196 if (source_widget == widget) {
2197 GtkTreeSelection *sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
2198 gtk_tree_selection_unselect_all (sel);
2202 /* Check if the get_data failed */
2203 if (selection_data == NULL || selection_data->length < 0)
2206 /* Select the destination model */
2207 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
2209 /* Get the path to the destination row. Can not call
2210 gtk_tree_view_get_drag_dest_row() because the source row
2211 is not selected anymore */
2212 gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget), x, y,
2215 /* Only allow drops IN other rows */
2217 pos == GTK_TREE_VIEW_DROP_BEFORE ||
2218 pos == GTK_TREE_VIEW_DROP_AFTER)
2222 /* Drags from the header view */
2223 if (source_widget != widget) {
2224 source_model = gtk_tree_view_get_model (GTK_TREE_VIEW (source_widget));
2226 drag_and_drop_from_header_view (source_model,
2231 DndHelper *helper = NULL;
2233 /* Get the source model and row */
2234 gtk_tree_get_row_drag_data (selection_data,
2238 /* Create the helper */
2239 helper = g_slice_new0 (DndHelper);
2240 helper->delete_source = delete_source;
2241 helper->source_row = gtk_tree_path_copy (source_row);
2242 helper->folder_view = MODEST_FOLDER_VIEW (widget);
2244 drag_and_drop_from_folder_view (source_model,
2250 gtk_tree_path_free (source_row);
2254 gtk_tree_path_free (dest_row);
2257 /* Finish the drag and drop */
2258 gtk_drag_finish (context, success, FALSE, time);
2262 * We define a "drag-drop" signal handler because we do not want to
2263 * use the default one, because the default one always calls
2264 * gtk_drag_finish and we prefer to do it in the "drag-data-received"
2265 * signal handler, because there we have all the information available
2266 * to know if the dnd was a success or not.
2269 drag_drop_cb (GtkWidget *widget,
2270 GdkDragContext *context,
2278 if (!context->targets)
2281 /* Check if we're dragging a folder row */
2282 target = gtk_drag_dest_find_target (widget, context, NULL);
2284 /* Request the data from the source. */
2285 gtk_drag_get_data(widget, context, target, time);
2291 * This function expands a node of a tree view if it's not expanded
2292 * yet. Not sure why it needs the threads stuff, but gtk+`example code
2293 * does that, so that's why they're here.
2296 expand_row_timeout (gpointer data)
2298 GtkTreeView *tree_view = data;
2299 GtkTreePath *dest_path = NULL;
2300 GtkTreeViewDropPosition pos;
2301 gboolean result = FALSE;
2303 gdk_threads_enter ();
2305 gtk_tree_view_get_drag_dest_row (tree_view,
2310 (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER ||
2311 pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)) {
2312 gtk_tree_view_expand_row (tree_view, dest_path, FALSE);
2313 gtk_tree_path_free (dest_path);
2317 gtk_tree_path_free (dest_path);
2322 gdk_threads_leave ();
2328 * This function is called whenever the pointer is moved over a widget
2329 * while dragging some data. It installs a timeout that will expand a
2330 * node of the treeview if not expanded yet. This function also calls
2331 * gdk_drag_status in order to set the suggested action that will be
2332 * used by the "drag-data-received" signal handler to know if we
2333 * should do a move or just a copy of the data.
2336 on_drag_motion (GtkWidget *widget,
2337 GdkDragContext *context,
2343 GtkTreeViewDropPosition pos;
2344 GtkTreePath *dest_row;
2345 GtkTreeModel *dest_model;
2346 ModestFolderViewPrivate *priv;
2347 GdkDragAction suggested_action;
2348 gboolean valid_location = FALSE;
2349 TnyFolderStore *folder = NULL;
2351 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
2353 if (priv->timer_expander != 0) {
2354 g_source_remove (priv->timer_expander);
2355 priv->timer_expander = 0;
2358 gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
2363 /* Do not allow drops between folders */
2365 pos == GTK_TREE_VIEW_DROP_BEFORE ||
2366 pos == GTK_TREE_VIEW_DROP_AFTER) {
2367 gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW (widget), NULL, 0);
2368 gdk_drag_status(context, 0, time);
2369 valid_location = FALSE;
2372 valid_location = TRUE;
2375 /* Check that the destination folder is writable */
2376 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
2377 folder = tree_path_to_folder (dest_model, dest_row);
2378 if (folder && TNY_IS_FOLDER (folder)) {
2379 ModestTnyFolderRules rules = modest_tny_folder_get_rules(TNY_FOLDER (folder));
2381 if (rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE) {
2382 valid_location = FALSE;
2387 /* Expand the selected row after 1/2 second */
2388 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), dest_row)) {
2389 priv->timer_expander = g_timeout_add (500, expand_row_timeout, widget);
2391 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), dest_row, pos);
2393 /* Select the desired action. By default we pick MOVE */
2394 suggested_action = GDK_ACTION_MOVE;
2396 if (context->actions == GDK_ACTION_COPY)
2397 gdk_drag_status(context, GDK_ACTION_COPY, time);
2398 else if (context->actions == GDK_ACTION_MOVE)
2399 gdk_drag_status(context, GDK_ACTION_MOVE, time);
2400 else if (context->actions & suggested_action)
2401 gdk_drag_status(context, suggested_action, time);
2403 gdk_drag_status(context, GDK_ACTION_DEFAULT, time);
2407 g_object_unref (folder);
2409 gtk_tree_path_free (dest_row);
2411 g_signal_stop_emission_by_name (widget, "drag-motion");
2413 return valid_location;
2417 * This function sets the treeview as a source and a target for dnd
2418 * events. It also connects all the requirede signals.
2421 setup_drag_and_drop (GtkTreeView *self)
2423 /* Set up the folder view as a dnd destination. Set only the
2424 highlight flag, otherwise gtk will have a different
2426 #ifdef MODEST_TOOLKIT_HILDON2
2429 gtk_drag_dest_set (GTK_WIDGET (self),
2430 GTK_DEST_DEFAULT_HIGHLIGHT,
2431 folder_view_drag_types,
2432 G_N_ELEMENTS (folder_view_drag_types),
2433 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2435 g_signal_connect (G_OBJECT (self),
2436 "drag_data_received",
2437 G_CALLBACK (on_drag_data_received),
2441 /* Set up the treeview as a dnd source */
2442 gtk_drag_source_set (GTK_WIDGET (self),
2444 folder_view_drag_types,
2445 G_N_ELEMENTS (folder_view_drag_types),
2446 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2448 g_signal_connect (G_OBJECT (self),
2450 G_CALLBACK (on_drag_motion),
2453 g_signal_connect (G_OBJECT (self),
2455 G_CALLBACK (on_drag_data_get),
2458 g_signal_connect (G_OBJECT (self),
2460 G_CALLBACK (drag_drop_cb),
2465 * This function manages the navigation through the folders using the
2466 * keyboard or the hardware keys in the device
2469 on_key_pressed (GtkWidget *self,
2473 GtkTreeSelection *selection;
2475 GtkTreeModel *model;
2476 gboolean retval = FALSE;
2478 /* Up and Down are automatically managed by the treeview */
2479 if (event->keyval == GDK_Return) {
2480 /* Expand/Collapse the selected row */
2481 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2482 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
2485 path = gtk_tree_model_get_path (model, &iter);
2487 if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (self), path))
2488 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
2490 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
2491 gtk_tree_path_free (path);
2493 /* No further processing */
2501 * We listen to the changes in the local folder account name key,
2502 * because we want to show the right name in the view. The local
2503 * folder account name corresponds to the device name in the Maemo
2504 * version. We do this because we do not want to query gconf on each
2505 * tree view refresh. It's better to cache it and change whenever
2509 on_configuration_key_changed (ModestConf* conf,
2511 ModestConfEvent event,
2512 ModestConfNotificationId id,
2513 ModestFolderView *self)
2515 ModestFolderViewPrivate *priv;
2518 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
2519 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2521 if (!strcmp (key, MODEST_CONF_DEVICE_NAME)) {
2522 g_free (priv->local_account_name);
2524 if (event == MODEST_CONF_EVENT_KEY_UNSET)
2525 priv->local_account_name = g_strdup (MODEST_LOCAL_FOLDERS_DEFAULT_DISPLAY_NAME);
2527 priv->local_account_name = modest_conf_get_string (modest_runtime_get_conf(),
2528 MODEST_CONF_DEVICE_NAME, NULL);
2530 /* Force a redraw */
2531 #if GTK_CHECK_VERSION(2, 8, 0)
2532 GtkTreeViewColumn * tree_column;
2534 tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self),
2535 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN);
2536 gtk_tree_view_column_queue_resize (tree_column);
2538 gtk_widget_queue_draw (GTK_WIDGET (self));
2544 modest_folder_view_set_style (ModestFolderView *self,
2545 ModestFolderViewStyle style)
2547 ModestFolderViewPrivate *priv;
2549 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2550 g_return_if_fail (style == MODEST_FOLDER_VIEW_STYLE_SHOW_ALL ||
2551 style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE);
2553 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2556 priv->style = style;
2560 modest_folder_view_set_account_id_of_visible_server_account (ModestFolderView *self,
2561 const gchar *account_id)
2563 ModestFolderViewPrivate *priv;
2564 GtkTreeModel *model;
2566 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2568 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2570 /* This will be used by the filter_row callback,
2571 * to decided which rows to show: */
2572 if (priv->visible_account_id) {
2573 g_free (priv->visible_account_id);
2574 priv->visible_account_id = NULL;
2577 priv->visible_account_id = g_strdup (account_id);
2580 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2581 if (GTK_IS_TREE_MODEL_FILTER (model))
2582 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2584 /* Save settings to gconf */
2585 modest_widget_memory_save (modest_runtime_get_conf (), G_OBJECT(self),
2586 MODEST_CONF_FOLDER_VIEW_KEY);
2590 modest_folder_view_get_account_id_of_visible_server_account (ModestFolderView *self)
2592 ModestFolderViewPrivate *priv;
2594 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW(self), NULL);
2596 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2598 return (const gchar *) priv->visible_account_id;
2602 find_inbox_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *inbox_iter)
2606 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2608 gtk_tree_model_get (model, iter,
2609 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN,
2612 gboolean result = FALSE;
2613 if (type == TNY_FOLDER_TYPE_INBOX) {
2617 *inbox_iter = *iter;
2621 if (gtk_tree_model_iter_children (model, &child, iter)) {
2622 if (find_inbox_iter (model, &child, inbox_iter))
2626 } while (gtk_tree_model_iter_next (model, iter));
2635 modest_folder_view_select_first_inbox_or_local (ModestFolderView *self)
2637 GtkTreeModel *model;
2638 GtkTreeIter iter, inbox_iter;
2639 GtkTreeSelection *sel;
2640 GtkTreePath *path = NULL;
2642 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2644 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2648 expand_root_items (self);
2649 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2651 if (!gtk_tree_model_get_iter_first (model, &iter)) {
2652 g_warning ("%s: model is empty", __FUNCTION__);
2656 if (find_inbox_iter (model, &iter, &inbox_iter))
2657 path = gtk_tree_model_get_path (model, &inbox_iter);
2659 path = gtk_tree_path_new_first ();
2661 /* Select the row and free */
2662 gtk_tree_view_set_cursor (GTK_TREE_VIEW (self), path, NULL, FALSE);
2663 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (self), path, NULL, FALSE, 0.0, 0.0);
2664 gtk_tree_path_free (path);
2667 gtk_widget_grab_focus (GTK_WIDGET(self));
2673 find_folder_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *folder_iter,
2678 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2679 TnyFolder* a_folder;
2682 gtk_tree_model_get (model, iter,
2683 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &a_folder,
2684 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name,
2685 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
2689 if (folder == a_folder) {
2690 g_object_unref (a_folder);
2691 *folder_iter = *iter;
2694 g_object_unref (a_folder);
2696 if (gtk_tree_model_iter_children (model, &child, iter)) {
2697 if (find_folder_iter (model, &child, folder_iter, folder))
2701 } while (gtk_tree_model_iter_next (model, iter));
2708 on_row_inserted_maybe_select_folder (GtkTreeModel *tree_model,
2711 ModestFolderView *self)
2713 ModestFolderViewPrivate *priv = NULL;
2714 GtkTreeSelection *sel;
2715 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2716 GObject *instance = NULL;
2718 if (!MODEST_IS_FOLDER_VIEW(self))
2721 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2723 priv->reexpand = TRUE;
2725 gtk_tree_model_get (tree_model, iter,
2726 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
2727 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
2729 if (type == TNY_FOLDER_TYPE_INBOX && priv->folder_to_select == NULL) {
2730 priv->folder_to_select = g_object_ref (instance);
2732 g_object_unref (instance);
2734 if (priv->folder_to_select) {
2736 if (!modest_folder_view_select_folder (self, priv->folder_to_select,
2739 path = gtk_tree_model_get_path (tree_model, iter);
2740 gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
2742 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2744 gtk_tree_selection_select_iter (sel, iter);
2745 gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
2747 gtk_tree_path_free (path);
2751 modest_folder_view_disable_next_folder_selection (self);
2753 /* Refilter the model */
2754 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (tree_model));
2760 modest_folder_view_disable_next_folder_selection (ModestFolderView *self)
2762 ModestFolderViewPrivate *priv;
2764 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2766 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2768 if (priv->folder_to_select)
2769 g_object_unref(priv->folder_to_select);
2771 priv->folder_to_select = NULL;
2775 modest_folder_view_select_folder (ModestFolderView *self, TnyFolder *folder,
2776 gboolean after_change)
2778 GtkTreeModel *model;
2779 GtkTreeIter iter, folder_iter;
2780 GtkTreeSelection *sel;
2781 ModestFolderViewPrivate *priv = NULL;
2783 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW (self), FALSE);
2784 g_return_val_if_fail (folder && TNY_IS_FOLDER (folder), FALSE);
2786 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2789 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2790 gtk_tree_selection_unselect_all (sel);
2792 if (priv->folder_to_select)
2793 g_object_unref(priv->folder_to_select);
2794 priv->folder_to_select = TNY_FOLDER(g_object_ref(folder));
2798 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2803 /* Refilter the model, before selecting the folder */
2804 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2806 if (!gtk_tree_model_get_iter_first (model, &iter)) {
2807 g_warning ("%s: model is empty", __FUNCTION__);
2811 if (find_folder_iter (model, &iter, &folder_iter, folder)) {
2814 path = gtk_tree_model_get_path (model, &folder_iter);
2815 gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
2817 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2818 gtk_tree_selection_select_iter (sel, &folder_iter);
2819 gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
2821 gtk_tree_path_free (path);
2829 modest_folder_view_copy_selection (ModestFolderView *self)
2831 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2833 /* Copy selection */
2834 _clipboard_set_selected_data (self, FALSE);
2838 modest_folder_view_cut_selection (ModestFolderView *folder_view)
2840 ModestFolderViewPrivate *priv = NULL;
2841 GtkTreeModel *model = NULL;
2842 const gchar **hidding = NULL;
2843 guint i, n_selected;
2845 g_return_if_fail (folder_view && MODEST_IS_FOLDER_VIEW (folder_view));
2846 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
2848 /* Copy selection */
2849 if (!_clipboard_set_selected_data (folder_view, TRUE))
2852 /* Get hidding ids */
2853 hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected);
2855 /* Clear hidding array created by previous cut operation */
2856 _clear_hidding_filter (MODEST_FOLDER_VIEW (folder_view));
2858 /* Copy hidding array */
2859 priv->n_selected = n_selected;
2860 priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
2861 for (i=0; i < n_selected; i++)
2862 priv->hidding_ids[i] = g_strdup(hidding[i]);
2864 /* Hide cut folders */
2865 model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
2866 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2870 modest_folder_view_copy_model (ModestFolderView *folder_view_src,
2871 ModestFolderView *folder_view_dst)
2873 GtkTreeModel *filter_model = NULL;
2874 GtkTreeModel *model = NULL;
2875 GtkTreeModel *new_filter_model = NULL;
2877 g_return_if_fail (folder_view_src && MODEST_IS_FOLDER_VIEW (folder_view_src));
2878 g_return_if_fail (folder_view_dst && MODEST_IS_FOLDER_VIEW (folder_view_dst));
2881 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view_src));
2882 model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER(filter_model));
2884 /* Build new filter model */
2885 new_filter_model = gtk_tree_model_filter_new (model, NULL);
2886 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (new_filter_model),
2890 /* Set copied model */
2891 gtk_tree_view_set_model (GTK_TREE_VIEW (folder_view_dst), new_filter_model);
2892 g_signal_connect (G_OBJECT(new_filter_model), "row-inserted",
2893 (GCallback) on_row_inserted_maybe_select_folder, folder_view_dst);
2896 g_object_unref (new_filter_model);
2900 modest_folder_view_show_non_move_folders (ModestFolderView *folder_view,
2903 GtkTreeModel *model = NULL;
2904 ModestFolderViewPrivate* priv;
2906 g_return_if_fail (folder_view && MODEST_IS_FOLDER_VIEW (folder_view));
2908 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
2909 priv->show_non_move = show;
2910 /* modest_folder_view_update_model(folder_view, */
2911 /* TNY_ACCOUNT_STORE(modest_runtime_get_account_store())); */
2913 /* Hide special folders */
2914 model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
2915 if (GTK_IS_TREE_MODEL_FILTER (model)) {
2916 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2920 /* Returns FALSE if it did not selected anything */
2922 _clipboard_set_selected_data (ModestFolderView *folder_view,
2925 ModestFolderViewPrivate *priv = NULL;
2926 TnyFolderStore *folder = NULL;
2927 gboolean retval = FALSE;
2929 g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (folder_view), FALSE);
2930 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
2932 /* Set selected data on clipboard */
2933 g_return_val_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard), FALSE);
2934 folder = modest_folder_view_get_selected (folder_view);
2936 /* Do not allow to select an account */
2937 if (TNY_IS_FOLDER (folder)) {
2938 modest_email_clipboard_set_data (priv->clipboard, TNY_FOLDER(folder), NULL, delete);
2943 g_object_unref (folder);
2949 _clear_hidding_filter (ModestFolderView *folder_view)
2951 ModestFolderViewPrivate *priv;
2954 g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view));
2955 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
2957 if (priv->hidding_ids != NULL) {
2958 for (i=0; i < priv->n_selected; i++)
2959 g_free (priv->hidding_ids[i]);
2960 g_free(priv->hidding_ids);
2966 on_display_name_changed (ModestAccountMgr *mgr,
2967 const gchar *account,
2970 ModestFolderView *self;
2972 self = MODEST_FOLDER_VIEW (user_data);
2974 /* Force a redraw */
2975 #if GTK_CHECK_VERSION(2, 8, 0)
2976 GtkTreeViewColumn * tree_column;
2978 tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self),
2979 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN);
2980 gtk_tree_view_column_queue_resize (tree_column);
2982 gtk_widget_queue_draw (GTK_WIDGET (self));
2987 modest_folder_view_set_cell_style (ModestFolderView *self,
2988 ModestFolderViewCellStyle cell_style)
2990 ModestFolderViewPrivate *priv = NULL;
2992 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
2993 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2995 priv->cell_style = cell_style;
2997 gtk_widget_queue_draw (GTK_WIDGET (self));