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\"");
303 /* gtk_rc_parse_string ("style \"fremantle-modest-fv\" {\n" */
304 /* " GtkWidget::hildon-mode = 1\n" */
305 /* "} class \"ModestFolderView\" style \"fremantle-modest-fv\""); */
311 /* Simplify checks for NULLs: */
313 strings_are_equal (const gchar *a, const gchar *b)
319 return (strcmp (a, b) == 0);
326 on_model_foreach_set_name(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
328 GObject *instance = NULL;
330 gtk_tree_model_get (model, iter,
331 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
335 return FALSE; /* keep walking */
337 if (!TNY_IS_ACCOUNT (instance)) {
338 g_object_unref (instance);
339 return FALSE; /* keep walking */
342 /* Check if this is the looked-for account: */
343 TnyAccount *this_account = TNY_ACCOUNT (instance);
344 TnyAccount *account = TNY_ACCOUNT (data);
346 const gchar *this_account_id = tny_account_get_id(this_account);
347 const gchar *account_id = tny_account_get_id(account);
348 g_object_unref (instance);
351 /* printf ("DEBUG: %s: this_account_id=%s, account_id=%s\n", __FUNCTION__, this_account_id, account_id); */
352 if (strings_are_equal(this_account_id, account_id)) {
353 /* Tell the model that the data has changed, so that
354 * it calls the cell_data_func callbacks again: */
355 /* TODO: This does not seem to actually cause the new string to be shown: */
356 gtk_tree_model_row_changed (model, path, iter);
358 return TRUE; /* stop walking */
361 return FALSE; /* keep walking */
366 ModestFolderView *self;
367 gchar *previous_name;
368 } GetMmcAccountNameData;
371 on_get_mmc_account_name (TnyStoreAccount* account, gpointer user_data)
373 /* printf ("DEBU1G: %s: account name=%s\n", __FUNCTION__, tny_account_get_name (TNY_ACCOUNT(account))); */
375 GetMmcAccountNameData *data = (GetMmcAccountNameData*)user_data;
377 if (!strings_are_equal (
378 tny_account_get_name(TNY_ACCOUNT(account)),
379 data->previous_name)) {
381 /* Tell the model that the data has changed, so that
382 * it calls the cell_data_func callbacks again: */
383 ModestFolderView *self = data->self;
384 GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
386 gtk_tree_model_foreach(model, on_model_foreach_set_name, account);
389 g_free (data->previous_name);
390 g_slice_free (GetMmcAccountNameData, data);
394 text_cell_data (GtkTreeViewColumn *column,
395 GtkCellRenderer *renderer,
396 GtkTreeModel *tree_model,
400 ModestFolderViewPrivate *priv;
401 GObject *rendobj = (GObject *) renderer;
403 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
404 GObject *instance = NULL;
406 gtk_tree_model_get (tree_model, iter,
407 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &fname,
408 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
409 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
411 if (!fname || !instance)
414 ModestFolderView *self = MODEST_FOLDER_VIEW (data);
415 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
417 gchar *item_name = NULL;
418 gint item_weight = 400;
420 if (type != TNY_FOLDER_TYPE_ROOT) {
424 if (modest_tny_folder_is_local_folder (TNY_FOLDER (instance)) ||
425 modest_tny_folder_is_memory_card_folder (TNY_FOLDER (instance))) {
426 type = modest_tny_folder_get_local_or_mmc_folder_type (TNY_FOLDER (instance));
427 if (type != TNY_FOLDER_TYPE_UNKNOWN) {
429 fname = g_strdup (modest_local_folder_info_get_type_display_name (type));
432 /* Sometimes an special folder is reported by the server as
433 NORMAL, like some versions of Dovecot */
434 if (type == TNY_FOLDER_TYPE_NORMAL ||
435 type == TNY_FOLDER_TYPE_UNKNOWN) {
436 type = modest_tny_folder_guess_folder_type (TNY_FOLDER (instance));
440 if (type == TNY_FOLDER_TYPE_INBOX) {
442 fname = g_strdup (_("mcen_me_folder_inbox"));
445 /* note: we cannot reliably get the counts from the tree model, we need
446 * to use explicit calls on tny_folder for some reason.
448 /* Select the number to show: the unread or unsent messages. in case of outbox/drafts, show all */
449 if ((type == TNY_FOLDER_TYPE_DRAFTS) ||
450 (type == TNY_FOLDER_TYPE_OUTBOX) ||
451 (type == TNY_FOLDER_TYPE_MERGE)) { /* _OUTBOX actually returns _MERGE... */
452 number = tny_folder_get_all_count (TNY_FOLDER(instance));
455 number = tny_folder_get_unread_count (TNY_FOLDER(instance));
459 if (priv->cell_style == MODEST_FOLDER_VIEW_CELL_STYLE_COMPACT) {
462 substring = g_strdup_printf (drafts?"TODO:%d messages":"TODO:%d new messages", number);
464 substring = g_strdup ("");
466 item_name = g_strdup_printf ("%s\n<span size='x-small' color='grey'>%s</span>",
470 /* Use bold font style if there are unread or unset messages */
472 item_name = g_strdup_printf ("%s (%d)", fname, number);
475 item_name = g_strdup (fname);
480 } else if (TNY_IS_ACCOUNT (instance)) {
481 /* If it's a server account */
482 if (modest_tny_account_is_virtual_local_folders (TNY_ACCOUNT (instance))) {
483 item_name = g_strdup (priv->local_account_name);
485 } else if (modest_tny_account_is_memory_card_account (TNY_ACCOUNT (instance))) {
486 /* fname is only correct when the items are first
487 * added to the model, not when the account is
488 * changed later, so get the name from the account
490 item_name = g_strdup (tny_account_get_name (TNY_ACCOUNT (instance)));
493 item_name = g_strdup (fname);
499 item_name = g_strdup ("unknown");
501 if (item_name && item_weight) {
502 /* Set the name in the treeview cell: */
503 g_object_set (rendobj,"markup", item_name, "weight", item_weight, NULL);
505 /* Notify display name observers */
506 /* TODO: What listens for this signal, and how can it use only the new name? */
507 if (((GObject *) priv->cur_folder_store) == instance) {
508 g_signal_emit (G_OBJECT(self),
509 signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
516 /* If it is a Memory card account, make sure that we have the correct name.
517 * This function will be trigerred again when the name has been retrieved: */
518 if (TNY_IS_STORE_ACCOUNT (instance) &&
519 modest_tny_account_is_memory_card_account (TNY_ACCOUNT (instance))) {
521 /* Get the account name asynchronously: */
522 GetMmcAccountNameData *callback_data =
523 g_slice_new0(GetMmcAccountNameData);
524 callback_data->self = self;
526 const gchar *name = tny_account_get_name (TNY_ACCOUNT(instance));
528 callback_data->previous_name = g_strdup (name);
530 modest_tny_account_get_mmc_account_name (TNY_STORE_ACCOUNT (instance),
531 on_get_mmc_account_name, callback_data);
535 g_object_unref (G_OBJECT (instance));
543 GdkPixbuf *pixbuf_open;
544 GdkPixbuf *pixbuf_close;
548 static inline GdkPixbuf *
549 get_composite_pixbuf (const gchar *icon_name,
551 GdkPixbuf *base_pixbuf)
553 GdkPixbuf *emblem, *retval = NULL;
555 emblem = modest_platform_get_icon (icon_name, size);
557 retval = gdk_pixbuf_copy (base_pixbuf);
558 gdk_pixbuf_composite (emblem, retval, 0, 0,
559 MIN (gdk_pixbuf_get_width (emblem),
560 gdk_pixbuf_get_width (retval)),
561 MIN (gdk_pixbuf_get_height (emblem),
562 gdk_pixbuf_get_height (retval)),
563 0, 0, 1, 1, GDK_INTERP_NEAREST, 255);
564 g_object_unref (emblem);
569 static inline ThreePixbufs *
570 get_composite_icons (const gchar *icon_code,
572 GdkPixbuf **pixbuf_open,
573 GdkPixbuf **pixbuf_close)
575 ThreePixbufs *retval;
578 *pixbuf = gdk_pixbuf_copy (modest_platform_get_icon (icon_code, MODEST_ICON_SIZE_SMALL));
581 *pixbuf_open = get_composite_pixbuf ("qgn_list_gene_fldr_exp",
582 MODEST_ICON_SIZE_SMALL,
586 *pixbuf_close = get_composite_pixbuf ("qgn_list_gene_fldr_clp",
587 MODEST_ICON_SIZE_SMALL,
590 retval = g_slice_new0 (ThreePixbufs);
592 retval->pixbuf = g_object_ref (*pixbuf);
594 retval->pixbuf_open = g_object_ref (*pixbuf_open);
596 retval->pixbuf_close = g_object_ref (*pixbuf_close);
602 get_folder_icons (TnyFolderType type, GObject *instance)
604 static GdkPixbuf *inbox_pixbuf = NULL, *outbox_pixbuf = NULL,
605 *junk_pixbuf = NULL, *sent_pixbuf = NULL,
606 *trash_pixbuf = NULL, *draft_pixbuf = NULL,
607 *normal_pixbuf = NULL, *anorm_pixbuf = NULL,
608 *ammc_pixbuf = NULL, *avirt_pixbuf = NULL;
610 static GdkPixbuf *inbox_pixbuf_open = NULL, *outbox_pixbuf_open = NULL,
611 *junk_pixbuf_open = NULL, *sent_pixbuf_open = NULL,
612 *trash_pixbuf_open = NULL, *draft_pixbuf_open = NULL,
613 *normal_pixbuf_open = NULL, *anorm_pixbuf_open = NULL,
614 *ammc_pixbuf_open = NULL, *avirt_pixbuf_open = NULL;
616 static GdkPixbuf *inbox_pixbuf_close = NULL, *outbox_pixbuf_close = NULL,
617 *junk_pixbuf_close = NULL, *sent_pixbuf_close = NULL,
618 *trash_pixbuf_close = NULL, *draft_pixbuf_close = NULL,
619 *normal_pixbuf_close = NULL, *anorm_pixbuf_close = NULL,
620 *ammc_pixbuf_close = NULL, *avirt_pixbuf_close = NULL;
622 ThreePixbufs *retval = NULL;
624 /* Sometimes an special folder is reported by the server as
625 NORMAL, like some versions of Dovecot */
626 if (type == TNY_FOLDER_TYPE_NORMAL ||
627 type == TNY_FOLDER_TYPE_UNKNOWN) {
628 type = modest_tny_folder_guess_folder_type (TNY_FOLDER (instance));
632 case TNY_FOLDER_TYPE_INVALID:
633 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
636 case TNY_FOLDER_TYPE_ROOT:
637 if (TNY_IS_ACCOUNT (instance)) {
639 if (modest_tny_account_is_virtual_local_folders (TNY_ACCOUNT (instance))) {
640 retval = get_composite_icons (MODEST_FOLDER_ICON_LOCAL_FOLDERS,
643 &avirt_pixbuf_close);
645 const gchar *account_id = tny_account_get_id (TNY_ACCOUNT (instance));
647 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
648 retval = get_composite_icons (MODEST_FOLDER_ICON_MMC,
653 retval = get_composite_icons (MODEST_FOLDER_ICON_ACCOUNT,
656 &anorm_pixbuf_close);
661 case TNY_FOLDER_TYPE_INBOX:
662 retval = get_composite_icons (MODEST_FOLDER_ICON_INBOX,
665 &inbox_pixbuf_close);
667 case TNY_FOLDER_TYPE_OUTBOX:
668 retval = get_composite_icons (MODEST_FOLDER_ICON_OUTBOX,
671 &outbox_pixbuf_close);
673 case TNY_FOLDER_TYPE_JUNK:
674 retval = get_composite_icons (MODEST_FOLDER_ICON_JUNK,
679 case TNY_FOLDER_TYPE_SENT:
680 retval = get_composite_icons (MODEST_FOLDER_ICON_SENT,
685 case TNY_FOLDER_TYPE_TRASH:
686 retval = get_composite_icons (MODEST_FOLDER_ICON_TRASH,
689 &trash_pixbuf_close);
691 case TNY_FOLDER_TYPE_DRAFTS:
692 retval = get_composite_icons (MODEST_FOLDER_ICON_DRAFTS,
695 &draft_pixbuf_close);
697 case TNY_FOLDER_TYPE_NORMAL:
699 retval = get_composite_icons (MODEST_FOLDER_ICON_NORMAL,
702 &normal_pixbuf_close);
710 free_pixbufs (ThreePixbufs *pixbufs)
713 g_object_unref (pixbufs->pixbuf);
714 if (pixbufs->pixbuf_open)
715 g_object_unref (pixbufs->pixbuf_open);
716 if (pixbufs->pixbuf_close)
717 g_object_unref (pixbufs->pixbuf_close);
718 g_slice_free (ThreePixbufs, pixbufs);
722 icon_cell_data (GtkTreeViewColumn *column,
723 GtkCellRenderer *renderer,
724 GtkTreeModel *tree_model,
728 GObject *rendobj = NULL, *instance = NULL;
729 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
730 gboolean has_children;
731 ThreePixbufs *pixbufs;
733 rendobj = (GObject *) renderer;
735 gtk_tree_model_get (tree_model, iter,
736 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
737 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
743 has_children = gtk_tree_model_iter_has_child (tree_model, iter);
744 pixbufs = get_folder_icons (type, instance);
745 g_object_unref (instance);
748 g_object_set (rendobj, "pixbuf", pixbufs->pixbuf, NULL);
751 g_object_set (rendobj, "pixbuf-expander-open", pixbufs->pixbuf_open, NULL);
752 g_object_set (rendobj, "pixbuf-expander-closed", pixbufs->pixbuf_close, NULL);
755 free_pixbufs (pixbufs);
759 add_columns (GtkWidget *treeview)
761 GtkTreeViewColumn *column;
762 GtkCellRenderer *renderer;
763 GtkTreeSelection *sel;
766 column = gtk_tree_view_column_new ();
768 /* Set icon and text render function */
769 renderer = gtk_cell_renderer_pixbuf_new();
770 gtk_tree_view_column_pack_start (column, renderer, FALSE);
771 gtk_tree_view_column_set_cell_data_func(column, renderer,
772 icon_cell_data, treeview, NULL);
774 renderer = gtk_cell_renderer_text_new();
775 g_object_set (renderer, "ellipsize", PANGO_ELLIPSIZE_END,
776 "ellipsize-set", TRUE, NULL);
777 gtk_tree_view_column_pack_start (column, renderer, TRUE);
778 gtk_tree_view_column_set_cell_data_func(column, renderer,
779 text_cell_data, treeview, NULL);
781 /* Set selection mode */
782 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(treeview));
783 gtk_tree_selection_set_mode (sel, GTK_SELECTION_SINGLE);
785 /* Set treeview appearance */
786 gtk_tree_view_column_set_spacing (column, 2);
787 gtk_tree_view_column_set_resizable (column, TRUE);
788 gtk_tree_view_column_set_fixed_width (column, TRUE);
789 gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(treeview), FALSE);
790 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(treeview), FALSE);
793 gtk_tree_view_append_column (GTK_TREE_VIEW(treeview),column);
797 modest_folder_view_init (ModestFolderView *obj)
799 ModestFolderViewPrivate *priv;
802 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
804 priv->timer_expander = 0;
805 priv->account_store = NULL;
807 priv->style = MODEST_FOLDER_VIEW_STYLE_SHOW_ALL;
808 priv->cur_folder_store = NULL;
809 priv->visible_account_id = NULL;
810 priv->folder_to_select = NULL;
812 priv->reexpand = TRUE;
814 /* Initialize the local account name */
815 conf = modest_runtime_get_conf();
816 priv->local_account_name = modest_conf_get_string (conf, MODEST_CONF_DEVICE_NAME, NULL);
818 /* Init email clipboard */
819 priv->clipboard = modest_runtime_get_email_clipboard ();
820 priv->hidding_ids = NULL;
821 priv->n_selected = 0;
822 priv->reselect = FALSE;
823 priv->show_non_move = TRUE;
826 add_columns (GTK_WIDGET (obj));
828 /* Setup drag and drop */
829 setup_drag_and_drop (GTK_TREE_VIEW(obj));
831 /* Connect signals */
832 g_signal_connect (G_OBJECT (obj),
834 G_CALLBACK (on_key_pressed), NULL);
836 priv->display_name_changed_signal =
837 g_signal_connect (modest_runtime_get_account_mgr (),
838 "display_name_changed",
839 G_CALLBACK (on_display_name_changed),
843 * Track changes in the local account name (in the device it
844 * will be the device name)
846 priv->conf_key_signal = g_signal_connect (G_OBJECT(conf),
848 G_CALLBACK(on_configuration_key_changed),
853 tny_account_store_view_init (gpointer g, gpointer iface_data)
855 TnyAccountStoreViewIface *klass = (TnyAccountStoreViewIface *)g;
857 klass->set_account_store = modest_folder_view_set_account_store;
861 modest_folder_view_finalize (GObject *obj)
863 ModestFolderViewPrivate *priv;
864 GtkTreeSelection *sel;
866 g_return_if_fail (obj);
868 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
870 if (priv->timer_expander != 0) {
871 g_source_remove (priv->timer_expander);
872 priv->timer_expander = 0;
875 if (priv->account_store) {
876 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
877 priv->account_inserted_signal);
878 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
879 priv->account_removed_signal);
880 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
881 priv->account_changed_signal);
882 g_object_unref (G_OBJECT(priv->account_store));
883 priv->account_store = NULL;
886 if (g_signal_handler_is_connected (modest_runtime_get_account_mgr (),
887 priv->display_name_changed_signal)) {
888 g_signal_handler_disconnect (modest_runtime_get_account_mgr (),
889 priv->display_name_changed_signal);
890 priv->display_name_changed_signal = 0;
894 g_object_unref (G_OBJECT (priv->query));
898 if (priv->folder_to_select) {
899 g_object_unref (G_OBJECT(priv->folder_to_select));
900 priv->folder_to_select = NULL;
903 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(obj));
905 g_signal_handler_disconnect (G_OBJECT(sel), priv->changed_signal);
907 g_free (priv->local_account_name);
908 g_free (priv->visible_account_id);
910 if (priv->conf_key_signal) {
911 g_signal_handler_disconnect (modest_runtime_get_conf (),
912 priv->conf_key_signal);
913 priv->conf_key_signal = 0;
916 if (priv->cur_folder_store) {
917 g_object_unref (priv->cur_folder_store);
918 priv->cur_folder_store = NULL;
921 /* Clear hidding array created by cut operation */
922 _clear_hidding_filter (MODEST_FOLDER_VIEW (obj));
924 G_OBJECT_CLASS(parent_class)->finalize (obj);
929 modest_folder_view_set_account_store (TnyAccountStoreView *self, TnyAccountStore *account_store)
931 ModestFolderViewPrivate *priv;
934 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
935 g_return_if_fail (TNY_IS_ACCOUNT_STORE (account_store));
937 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
938 device = tny_account_store_get_device (account_store);
940 if (G_UNLIKELY (priv->account_store)) {
942 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
943 priv->account_inserted_signal))
944 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
945 priv->account_inserted_signal);
946 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
947 priv->account_removed_signal))
948 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
949 priv->account_removed_signal);
950 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
951 priv->account_changed_signal))
952 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
953 priv->account_changed_signal);
954 g_object_unref (G_OBJECT (priv->account_store));
957 priv->account_store = g_object_ref (G_OBJECT (account_store));
959 priv->account_removed_signal =
960 g_signal_connect (G_OBJECT(account_store), "account_removed",
961 G_CALLBACK (on_account_removed), self);
963 priv->account_inserted_signal =
964 g_signal_connect (G_OBJECT(account_store), "account_inserted",
965 G_CALLBACK (on_account_inserted), self);
967 priv->account_changed_signal =
968 g_signal_connect (G_OBJECT(account_store), "account_changed",
969 G_CALLBACK (on_account_changed), self);
971 modest_folder_view_update_model (MODEST_FOLDER_VIEW (self), account_store);
972 priv->reselect = FALSE;
973 modest_folder_view_select_first_inbox_or_local (MODEST_FOLDER_VIEW (self));
975 g_object_unref (G_OBJECT (device));
979 on_account_inserted (TnyAccountStore *account_store,
983 ModestFolderViewPrivate *priv;
984 GtkTreeModel *sort_model, *filter_model;
986 /* Ignore transport account insertions, we're not showing them
987 in the folder view */
988 if (TNY_IS_TRANSPORT_ACCOUNT (account))
991 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (user_data);
994 /* If we're adding a new account, and there is no previous
995 one, we need to select the visible server account */
996 if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE &&
997 !priv->visible_account_id)
998 modest_widget_memory_restore (modest_runtime_get_conf(),
999 G_OBJECT (user_data),
1000 MODEST_CONF_FOLDER_VIEW_KEY);
1002 if (!GTK_IS_TREE_VIEW(user_data)) {
1003 g_warning ("BUG: %s: not a valid tree view", __FUNCTION__);
1007 /* Get the inner model */
1008 /* check, is some rare cases, we did not get the right thing here,
1010 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (user_data));
1011 if (!GTK_IS_TREE_MODEL_FILTER(filter_model)) {
1012 g_warning ("BUG: %s: not a valid filter model", __FUNCTION__);
1016 /* check, is some rare cases, we did not get the right thing here,
1018 sort_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
1019 if (!GTK_IS_TREE_MODEL_SORT(sort_model)) {
1020 g_warning ("BUG: %s: not a valid sort model", __FUNCTION__);
1024 /* Insert the account in the model */
1025 tny_list_append (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
1026 G_OBJECT (account));
1028 /* Refilter the model */
1029 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1034 same_account_selected (ModestFolderView *self,
1035 TnyAccount *account)
1037 ModestFolderViewPrivate *priv;
1038 gboolean same_account = FALSE;
1040 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1042 if (priv->cur_folder_store) {
1043 TnyAccount *selected_folder_account = NULL;
1045 if (TNY_IS_FOLDER (priv->cur_folder_store)) {
1046 selected_folder_account =
1047 modest_tny_folder_get_account (TNY_FOLDER (priv->cur_folder_store));
1049 selected_folder_account =
1050 TNY_ACCOUNT (g_object_ref (priv->cur_folder_store));
1053 if (selected_folder_account == account)
1054 same_account = TRUE;
1056 g_object_unref (selected_folder_account);
1058 return same_account;
1063 * Selects the first inbox or the local account in an idle
1066 on_idle_select_first_inbox_or_local (gpointer user_data)
1068 ModestFolderView *self = MODEST_FOLDER_VIEW (user_data);
1070 gdk_threads_enter ();
1071 modest_folder_view_select_first_inbox_or_local (self);
1072 gdk_threads_leave ();
1078 on_account_changed (TnyAccountStore *account_store,
1079 TnyAccount *tny_account,
1082 ModestFolderView *self;
1083 ModestFolderViewPrivate *priv;
1084 GtkTreeModel *sort_model, *filter_model;
1085 GtkTreeSelection *sel;
1086 gboolean same_account;
1088 /* Ignore transport account insertions, we're not showing them
1089 in the folder view */
1090 if (TNY_IS_TRANSPORT_ACCOUNT (tny_account))
1093 if (!MODEST_IS_FOLDER_VIEW(user_data)) {
1094 g_warning ("BUG: %s: not a valid folder view", __FUNCTION__);
1098 self = MODEST_FOLDER_VIEW (user_data);
1099 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (user_data);
1101 /* Get the inner model */
1102 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (user_data));
1103 if (!GTK_IS_TREE_MODEL_FILTER(filter_model)) {
1104 g_warning ("BUG: %s: not a valid filter model", __FUNCTION__);
1108 sort_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
1109 if (!GTK_IS_TREE_MODEL_SORT(sort_model)) {
1110 g_warning ("BUG: %s: not a valid sort model", __FUNCTION__);
1114 /* Invalidate the cur_folder_store only if the selected folder
1115 belongs to the account that is being removed */
1116 same_account = same_account_selected (self, tny_account);
1118 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1119 gtk_tree_selection_unselect_all (sel);
1122 /* Remove the account from the model */
1123 tny_list_remove (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
1124 G_OBJECT (tny_account));
1126 /* Insert the account in the model */
1127 tny_list_append (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
1128 G_OBJECT (tny_account));
1130 /* Refilter the model */
1131 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1133 /* Select the first INBOX if the currently selected folder
1134 belongs to the account that is being deleted */
1135 if (same_account && !MODEST_IS_TNY_LOCAL_FOLDERS_ACCOUNT (tny_account))
1136 g_idle_add (on_idle_select_first_inbox_or_local, self);
1140 on_account_removed (TnyAccountStore *account_store,
1141 TnyAccount *account,
1144 ModestFolderView *self = NULL;
1145 ModestFolderViewPrivate *priv;
1146 GtkTreeModel *sort_model, *filter_model;
1147 GtkTreeSelection *sel = NULL;
1148 gboolean same_account = FALSE;
1150 /* Ignore transport account removals, we're not showing them
1151 in the folder view */
1152 if (TNY_IS_TRANSPORT_ACCOUNT (account))
1155 if (!MODEST_IS_FOLDER_VIEW(user_data)) {
1156 g_warning ("BUG: %s: not a valid folder view", __FUNCTION__);
1160 self = MODEST_FOLDER_VIEW (user_data);
1161 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1163 /* Invalidate the cur_folder_store only if the selected folder
1164 belongs to the account that is being removed */
1165 same_account = same_account_selected (self, account);
1167 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1168 gtk_tree_selection_unselect_all (sel);
1171 /* Invalidate row to select only if the folder to select
1172 belongs to the account that is being removed*/
1173 if (priv->folder_to_select) {
1174 TnyAccount *folder_to_select_account = NULL;
1176 folder_to_select_account = tny_folder_get_account (priv->folder_to_select);
1177 if (folder_to_select_account == account) {
1178 modest_folder_view_disable_next_folder_selection (self);
1179 g_object_unref (priv->folder_to_select);
1180 priv->folder_to_select = NULL;
1182 g_object_unref (folder_to_select_account);
1185 /* Remove the account from the model */
1186 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1187 if (!GTK_IS_TREE_MODEL_FILTER(filter_model)) {
1188 g_warning ("BUG: %s: not a valid filter model", __FUNCTION__);
1192 sort_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
1193 if (!GTK_IS_TREE_MODEL_SORT(sort_model)) {
1194 g_warning ("BUG: %s: not a valid sort model", __FUNCTION__);
1198 tny_list_remove (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
1199 G_OBJECT (account));
1201 /* If the removed account is the currently viewed one then
1202 clear the configuration value. The new visible account will be the default account */
1203 if (priv->visible_account_id &&
1204 !strcmp (priv->visible_account_id, tny_account_get_id (account))) {
1206 /* Clear the current visible account_id */
1207 modest_folder_view_set_account_id_of_visible_server_account (self, NULL);
1209 /* Call the restore method, this will set the new visible account */
1210 modest_widget_memory_restore (modest_runtime_get_conf(), G_OBJECT(self),
1211 MODEST_CONF_FOLDER_VIEW_KEY);
1214 /* Refilter the model */
1215 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1217 /* Select the first INBOX if the currently selected folder
1218 belongs to the account that is being deleted */
1220 g_idle_add (on_idle_select_first_inbox_or_local, self);
1224 modest_folder_view_set_title (ModestFolderView *self, const gchar *title)
1226 GtkTreeViewColumn *col;
1228 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
1230 col = gtk_tree_view_get_column (GTK_TREE_VIEW(self), 0);
1232 g_printerr ("modest: failed get column for title\n");
1236 gtk_tree_view_column_set_title (col, title);
1237 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self),
1242 modest_folder_view_on_map (ModestFolderView *self,
1243 GdkEventExpose *event,
1246 ModestFolderViewPrivate *priv;
1248 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1250 /* This won't happen often */
1251 if (G_UNLIKELY (priv->reselect)) {
1252 /* Select the first inbox or the local account if not found */
1254 /* TODO: this could cause a lock at startup, so we
1255 comment it for the moment. We know that this will
1256 be a bug, because the INBOX is not selected, but we
1257 need to rewrite some parts of Modest to avoid the
1258 deathlock situation */
1259 /* TODO: check if this is still the case */
1260 priv->reselect = FALSE;
1261 modest_folder_view_select_first_inbox_or_local (self);
1262 /* Notify the display name observers */
1263 g_signal_emit (G_OBJECT(self),
1264 signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
1268 if (priv->reexpand) {
1269 expand_root_items (self);
1270 priv->reexpand = FALSE;
1277 modest_folder_view_new (TnyFolderStoreQuery *query)
1280 ModestFolderViewPrivate *priv;
1281 GtkTreeSelection *sel;
1283 self = G_OBJECT (g_object_new (MODEST_TYPE_FOLDER_VIEW,
1284 #ifdef MODEST_TOOLKIT_HILDON2
1285 "hildon-ui-mode", HILDON_UI_MODE_NORMAL,
1288 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1291 priv->query = g_object_ref (query);
1293 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1294 priv->changed_signal = g_signal_connect (sel, "changed",
1295 G_CALLBACK (on_selection_changed), self);
1297 g_signal_connect (self, "row-activated", G_CALLBACK (on_row_activated), self);
1299 g_signal_connect (self, "expose-event", G_CALLBACK (modest_folder_view_on_map), NULL);
1301 return GTK_WIDGET(self);
1304 /* this feels dirty; any other way to expand all the root items? */
1306 expand_root_items (ModestFolderView *self)
1309 GtkTreeModel *model;
1312 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1313 path = gtk_tree_path_new_first ();
1315 /* all folders should have child items, so.. */
1317 gtk_tree_view_expand_row (GTK_TREE_VIEW(self), path, FALSE);
1318 gtk_tree_path_next (path);
1319 } while (gtk_tree_model_get_iter (model, &iter, path));
1321 gtk_tree_path_free (path);
1325 * We use this function to implement the
1326 * MODEST_FOLDER_VIEW_STYLE_SHOW_ONE style. We only show the default
1327 * account in this case, and the local folders.
1330 filter_row (GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
1332 ModestFolderViewPrivate *priv;
1333 gboolean retval = TRUE;
1334 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1335 GObject *instance = NULL;
1336 const gchar *id = NULL;
1338 gboolean found = FALSE;
1339 gboolean cleared = FALSE;
1341 g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (data), FALSE);
1342 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (data);
1344 gtk_tree_model_get (model, iter,
1345 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
1346 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
1349 /* Do not show if there is no instance, this could indeed
1350 happen when the model is being modified while it's being
1351 drawn. This could occur for example when moving folders
1356 if (type == TNY_FOLDER_TYPE_ROOT) {
1357 /* TNY_FOLDER_TYPE_ROOT means that the instance is an
1358 account instead of a folder. */
1359 if (TNY_IS_ACCOUNT (instance)) {
1360 TnyAccount *acc = TNY_ACCOUNT (instance);
1361 const gchar *account_id = tny_account_get_id (acc);
1363 /* If it isn't a special folder,
1364 * don't show it unless it is the visible account: */
1365 if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE &&
1366 !modest_tny_account_is_virtual_local_folders (acc) &&
1367 strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
1369 /* Show only the visible account id */
1370 if (priv->visible_account_id) {
1371 if (strcmp (account_id, priv->visible_account_id))
1378 /* Never show these to the user. They are merged into one folder
1379 * in the local-folders account instead: */
1380 if (retval && MODEST_IS_TNY_OUTBOX_ACCOUNT (acc))
1385 /* Check hiding (if necessary) */
1386 cleared = modest_email_clipboard_cleared (priv->clipboard);
1387 if ((retval) && (!cleared) && (TNY_IS_FOLDER (instance))) {
1388 id = tny_folder_get_id (TNY_FOLDER(instance));
1389 if (priv->hidding_ids != NULL)
1390 for (i=0; i < priv->n_selected && !found; i++)
1391 if (priv->hidding_ids[i] != NULL && id != NULL)
1392 found = (!strcmp (priv->hidding_ids[i], id));
1398 /* If this is a move to dialog, hide Sent, Outbox and Drafts
1399 folder as no message can be move there according to UI specs */
1400 if (!priv->show_non_move) {
1402 case TNY_FOLDER_TYPE_OUTBOX:
1403 case TNY_FOLDER_TYPE_SENT:
1404 case TNY_FOLDER_TYPE_DRAFTS:
1407 case TNY_FOLDER_TYPE_UNKNOWN:
1408 case TNY_FOLDER_TYPE_NORMAL:
1409 type = modest_tny_folder_guess_folder_type(TNY_FOLDER(instance));
1410 if (type == TNY_FOLDER_TYPE_INVALID)
1411 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1413 if (type == TNY_FOLDER_TYPE_OUTBOX ||
1414 type == TNY_FOLDER_TYPE_SENT
1415 || type == TNY_FOLDER_TYPE_DRAFTS)
1424 g_object_unref (instance);
1431 modest_folder_view_update_model (ModestFolderView *self,
1432 TnyAccountStore *account_store)
1434 ModestFolderViewPrivate *priv;
1435 GtkTreeModel *model /* , *old_model */;
1436 GtkTreeModel *filter_model = NULL, *sortable = NULL;
1438 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW (self), FALSE);
1439 g_return_val_if_fail (account_store && MODEST_IS_TNY_ACCOUNT_STORE(account_store),
1442 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1444 /* Notify that there is no folder selected */
1445 g_signal_emit (G_OBJECT(self),
1446 signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
1448 if (priv->cur_folder_store) {
1449 g_object_unref (priv->cur_folder_store);
1450 priv->cur_folder_store = NULL;
1453 /* FIXME: the local accounts are not shown when the query
1454 selects only the subscribed folders */
1455 model = tny_gtk_folder_store_tree_model_new (NULL);
1457 /* Get the accounts: */
1458 tny_account_store_get_accounts (TNY_ACCOUNT_STORE(account_store),
1460 TNY_ACCOUNT_STORE_STORE_ACCOUNTS);
1462 sortable = gtk_tree_model_sort_new_with_model (model);
1463 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
1464 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
1465 GTK_SORT_ASCENDING);
1466 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
1467 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
1468 cmp_rows, NULL, NULL);
1470 /* Create filter model */
1471 filter_model = gtk_tree_model_filter_new (sortable, NULL);
1472 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
1478 gtk_tree_view_set_model (GTK_TREE_VIEW(self), filter_model);
1479 g_signal_connect (G_OBJECT(filter_model), "row-inserted",
1480 (GCallback) on_row_inserted_maybe_select_folder, self);
1482 g_object_unref (model);
1483 g_object_unref (filter_model);
1484 g_object_unref (sortable);
1486 /* Force a reselection of the INBOX next time the widget is shown */
1487 priv->reselect = TRUE;
1494 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
1496 GtkTreeModel *model = NULL;
1497 TnyFolderStore *folder = NULL;
1499 ModestFolderView *tree_view = NULL;
1500 ModestFolderViewPrivate *priv = NULL;
1501 gboolean selected = FALSE;
1503 g_return_if_fail (sel);
1504 g_return_if_fail (user_data);
1506 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
1508 selected = gtk_tree_selection_get_selected (sel, &model, &iter);
1510 tree_view = MODEST_FOLDER_VIEW (user_data);
1513 gtk_tree_model_get (model, &iter,
1514 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
1517 /* If the folder is the same do not notify */
1518 if (folder && priv->cur_folder_store == folder) {
1519 g_object_unref (folder);
1524 /* Current folder was unselected */
1525 if (priv->cur_folder_store) {
1526 /* We must do this firstly because a libtinymail-camel
1527 implementation detail. If we issue the signal
1528 before doing the sync_async, then that signal could
1529 cause (and it actually does it) a free of the
1530 summary of the folder (because the main window will
1531 clear the headers view */
1532 if (TNY_IS_FOLDER(priv->cur_folder_store))
1533 tny_folder_sync_async (TNY_FOLDER(priv->cur_folder_store),
1534 FALSE, NULL, NULL, NULL);
1536 g_signal_emit (G_OBJECT(tree_view), signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
1537 priv->cur_folder_store, FALSE);
1539 g_object_unref (priv->cur_folder_store);
1540 priv->cur_folder_store = NULL;
1543 /* New current references */
1544 priv->cur_folder_store = folder;
1546 /* New folder has been selected. Do not notify if there is
1547 nothing new selected */
1549 g_signal_emit (G_OBJECT(tree_view),
1550 signals[FOLDER_SELECTION_CHANGED_SIGNAL],
1551 0, priv->cur_folder_store, TRUE);
1556 on_row_activated (GtkTreeView *treeview,
1557 GtkTreePath *treepath,
1558 GtkTreeViewColumn *column,
1561 GtkTreeModel *model = NULL;
1562 TnyFolderStore *folder = NULL;
1564 ModestFolderView *self = NULL;
1565 ModestFolderViewPrivate *priv = NULL;
1567 g_return_if_fail (treeview);
1568 g_return_if_fail (user_data);
1570 self = MODEST_FOLDER_VIEW (user_data);
1571 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
1573 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1575 if (!gtk_tree_model_get_iter (model, &iter, treepath))
1578 gtk_tree_model_get (model, &iter,
1579 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
1582 g_signal_emit (G_OBJECT(self),
1583 signals[FOLDER_ACTIVATED_SIGNAL],
1586 g_object_unref (folder);
1590 modest_folder_view_get_selected (ModestFolderView *self)
1592 ModestFolderViewPrivate *priv;
1594 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW(self), NULL);
1596 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1597 if (priv->cur_folder_store)
1598 g_object_ref (priv->cur_folder_store);
1600 return priv->cur_folder_store;
1604 get_cmp_rows_type_pos (GObject *folder)
1606 /* Remote accounts -> Local account -> MMC account .*/
1609 if (TNY_IS_ACCOUNT (folder) &&
1610 modest_tny_account_is_virtual_local_folders (
1611 TNY_ACCOUNT (folder))) {
1613 } else if (TNY_IS_ACCOUNT (folder)) {
1614 TnyAccount *account = TNY_ACCOUNT (folder);
1615 const gchar *account_id = tny_account_get_id (account);
1616 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
1622 printf ("DEBUG: %s: unexpected type.\n", __FUNCTION__);
1623 return -1; /* Should never happen */
1628 get_cmp_subfolder_type_pos (TnyFolderType t)
1630 /* Inbox, Outbox, Drafts, Sent, User */
1634 case TNY_FOLDER_TYPE_INBOX:
1637 case TNY_FOLDER_TYPE_OUTBOX:
1640 case TNY_FOLDER_TYPE_DRAFTS:
1643 case TNY_FOLDER_TYPE_SENT:
1652 * This function orders the mail accounts according to these rules:
1653 * 1st - remote accounts
1654 * 2nd - local account
1658 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1662 gchar *name1 = NULL;
1663 gchar *name2 = NULL;
1664 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1665 TnyFolderType type2 = TNY_FOLDER_TYPE_UNKNOWN;
1666 GObject *folder1 = NULL;
1667 GObject *folder2 = NULL;
1669 gtk_tree_model_get (tree_model, iter1,
1670 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name1,
1671 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
1672 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder1,
1674 gtk_tree_model_get (tree_model, iter2,
1675 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name2,
1676 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type2,
1677 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder2,
1680 /* Return if we get no folder. This could happen when folder
1681 operations are happening. The model is updated after the
1682 folder copy/move actually occurs, so there could be
1683 situations where the model to be drawn is not correct */
1684 if (!folder1 || !folder2)
1687 if (type == TNY_FOLDER_TYPE_ROOT) {
1688 /* Compare the types, so that
1689 * Remote accounts -> Local account -> MMC account .*/
1690 const gint pos1 = get_cmp_rows_type_pos (folder1);
1691 const gint pos2 = get_cmp_rows_type_pos (folder2);
1692 /* printf ("DEBUG: %s:\n type1=%s, pos1=%d\n type2=%s, pos2=%d\n",
1693 __FUNCTION__, G_OBJECT_TYPE_NAME(folder1), pos1, G_OBJECT_TYPE_NAME(folder2), pos2); */
1696 else if (pos1 > pos2)
1699 /* Compare items of the same type: */
1701 TnyAccount *account1 = NULL;
1702 if (TNY_IS_ACCOUNT (folder1))
1703 account1 = TNY_ACCOUNT (folder1);
1705 TnyAccount *account2 = NULL;
1706 if (TNY_IS_ACCOUNT (folder2))
1707 account2 = TNY_ACCOUNT (folder2);
1709 const gchar *account_id = account1 ? tny_account_get_id (account1) : NULL;
1710 const gchar *account_id2 = account2 ? tny_account_get_id (account2) : NULL;
1712 if (!account_id && !account_id2) {
1714 } else if (!account_id) {
1716 } else if (!account_id2) {
1718 } else if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
1721 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
1725 gint cmp1 = 0, cmp2 = 0;
1726 /* get the parent to know if it's a local folder */
1729 gboolean has_parent;
1730 has_parent = gtk_tree_model_iter_parent (tree_model, &parent, iter1);
1732 GObject *parent_folder;
1733 TnyFolderType parent_type = TNY_FOLDER_TYPE_UNKNOWN;
1734 gtk_tree_model_get (tree_model, &parent,
1735 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &parent_type,
1736 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &parent_folder,
1738 if ((parent_type == TNY_FOLDER_TYPE_ROOT) &&
1739 TNY_IS_ACCOUNT (parent_folder)) {
1740 if (modest_tny_account_is_virtual_local_folders (TNY_ACCOUNT (parent_folder))) {
1741 cmp1 = get_cmp_subfolder_type_pos (modest_tny_folder_get_local_or_mmc_folder_type
1742 (TNY_FOLDER (folder1)));
1743 cmp2 = get_cmp_subfolder_type_pos (modest_tny_folder_get_local_or_mmc_folder_type
1744 (TNY_FOLDER (folder2)));
1745 } else if (modest_tny_account_is_memory_card_account (TNY_ACCOUNT (parent_folder))) {
1746 if (modest_local_folder_info_get_type (tny_folder_get_name (TNY_FOLDER (folder1))) == TNY_FOLDER_TYPE_ARCHIVE) {
1749 } else if (modest_local_folder_info_get_type (tny_folder_get_name (TNY_FOLDER (folder2))) == TNY_FOLDER_TYPE_ARCHIVE) {
1755 g_object_unref (parent_folder);
1758 /* if they are not local folders */
1760 cmp1 = get_cmp_subfolder_type_pos (tny_folder_get_folder_type (TNY_FOLDER (folder1)));
1761 cmp2 = get_cmp_subfolder_type_pos (tny_folder_get_folder_type (TNY_FOLDER (folder2)));
1765 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
1767 cmp = (cmp1 - cmp2);
1772 g_object_unref(G_OBJECT(folder1));
1774 g_object_unref(G_OBJECT(folder2));
1782 /*****************************************************************************/
1783 /* DRAG and DROP stuff */
1784 /*****************************************************************************/
1786 * This function fills the #GtkSelectionData with the row and the
1787 * model that has been dragged. It's called when this widget is a
1788 * source for dnd after the event drop happened
1791 on_drag_data_get (GtkWidget *widget, GdkDragContext *context, GtkSelectionData *selection_data,
1792 guint info, guint time, gpointer data)
1794 GtkTreeSelection *selection;
1795 GtkTreeModel *model;
1797 GtkTreePath *source_row;
1799 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1800 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
1802 source_row = gtk_tree_model_get_path (model, &iter);
1803 gtk_tree_set_row_drag_data (selection_data,
1807 gtk_tree_path_free (source_row);
1811 typedef struct _DndHelper {
1812 ModestFolderView *folder_view;
1813 gboolean delete_source;
1814 GtkTreePath *source_row;
1818 dnd_helper_destroyer (DndHelper *helper)
1820 /* Free the helper */
1821 gtk_tree_path_free (helper->source_row);
1822 g_slice_free (DndHelper, helper);
1826 xfer_folder_cb (ModestMailOperation *mail_op,
1827 TnyFolder *new_folder,
1831 /* Select the folder */
1832 modest_folder_view_select_folder (MODEST_FOLDER_VIEW (user_data),
1838 /* get the folder for the row the treepath refers to. */
1839 /* folder must be unref'd */
1840 static TnyFolderStore *
1841 tree_path_to_folder (GtkTreeModel *model, GtkTreePath *path)
1844 TnyFolderStore *folder = NULL;
1846 if (gtk_tree_model_get_iter (model,&iter, path))
1847 gtk_tree_model_get (model, &iter,
1848 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
1855 * This function is used by drag_data_received_cb to manage drag and
1856 * drop of a header, i.e, and drag from the header view to the folder
1860 drag_and_drop_from_header_view (GtkTreeModel *source_model,
1861 GtkTreeModel *dest_model,
1862 GtkTreePath *dest_row,
1863 GtkSelectionData *selection_data)
1865 TnyList *headers = NULL;
1866 TnyFolder *folder = NULL, *src_folder = NULL;
1867 TnyFolderType folder_type;
1868 GtkTreeIter source_iter, dest_iter;
1869 ModestWindowMgr *mgr = NULL;
1870 ModestWindow *main_win = NULL;
1871 gchar **uris, **tmp;
1873 /* Build the list of headers */
1874 mgr = modest_runtime_get_window_mgr ();
1875 headers = tny_simple_list_new ();
1876 uris = modest_dnd_selection_data_get_paths (selection_data);
1879 while (*tmp != NULL) {
1882 gboolean first = TRUE;
1885 path = gtk_tree_path_new_from_string (*tmp);
1886 gtk_tree_model_get_iter (source_model, &source_iter, path);
1887 gtk_tree_model_get (source_model, &source_iter,
1888 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1891 /* Do not enable d&d of headers already opened */
1892 if (!modest_window_mgr_find_registered_header(mgr, header, NULL))
1893 tny_list_append (headers, G_OBJECT (header));
1895 if (G_UNLIKELY (first)) {
1896 src_folder = tny_header_get_folder (header);
1900 /* Free and go on */
1901 gtk_tree_path_free (path);
1902 g_object_unref (header);
1907 /* This could happen ig we perform a d&d very quickly over the
1908 same row that row could dissapear because message is
1910 if (!TNY_IS_FOLDER (src_folder))
1913 /* Get the target folder */
1914 gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
1915 gtk_tree_model_get (dest_model, &dest_iter,
1916 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
1919 if (!folder || !TNY_IS_FOLDER(folder)) {
1920 /* g_warning ("%s: not a valid target folder (%p)", __FUNCTION__, folder); */
1924 folder_type = modest_tny_folder_guess_folder_type (folder);
1925 if (folder_type == TNY_FOLDER_TYPE_INVALID) {
1926 /* g_warning ("%s: invalid target folder", __FUNCTION__); */
1927 goto cleanup; /* cannot move messages there */
1930 if (modest_tny_folder_get_rules((TNY_FOLDER(folder))) & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE) {
1931 /* g_warning ("folder not writable"); */
1932 goto cleanup; /* verboten! */
1935 /* Ask for confirmation to move */
1936 main_win = modest_window_mgr_get_main_window (mgr, FALSE); /* don't create */
1938 g_warning ("%s: BUG: no main window found", __FUNCTION__);
1942 /* Transfer messages */
1943 modest_ui_actions_transfer_messages_helper (GTK_WINDOW (main_win), src_folder,
1948 if (G_IS_OBJECT (src_folder))
1949 g_object_unref (src_folder);
1950 if (G_IS_OBJECT(folder))
1951 g_object_unref (G_OBJECT (folder));
1952 if (G_IS_OBJECT(headers))
1953 g_object_unref (headers);
1957 TnyFolderStore *src_folder;
1958 TnyFolderStore *dst_folder;
1959 ModestFolderView *folder_view;
1964 dnd_folder_info_destroyer (DndFolderInfo *info)
1966 if (info->src_folder)
1967 g_object_unref (info->src_folder);
1968 if (info->dst_folder)
1969 g_object_unref (info->dst_folder);
1970 g_slice_free (DndFolderInfo, info);
1974 dnd_on_connection_failed_destroyer (DndFolderInfo *info,
1975 GtkWindow *parent_window,
1976 TnyAccount *account)
1979 modest_ui_actions_on_account_connection_error (parent_window, account);
1981 /* Free the helper & info */
1982 dnd_helper_destroyer (info->helper);
1983 dnd_folder_info_destroyer (info);
1987 drag_and_drop_from_folder_view_src_folder_performer (gboolean canceled,
1989 GtkWindow *parent_window,
1990 TnyAccount *account,
1993 DndFolderInfo *info = NULL;
1994 ModestMailOperation *mail_op;
1996 info = (DndFolderInfo *) user_data;
1998 if (err || canceled) {
1999 dnd_on_connection_failed_destroyer (info, parent_window, account);
2003 /* Do the mail operation */
2004 mail_op = modest_mail_operation_new_with_error_handling ((GObject *) parent_window,
2005 modest_ui_actions_move_folder_error_handler,
2006 info->src_folder, NULL);
2008 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
2011 /* Transfer the folder */
2012 modest_mail_operation_xfer_folder (mail_op,
2013 TNY_FOLDER (info->src_folder),
2015 info->helper->delete_source,
2017 info->helper->folder_view);
2020 g_object_unref (G_OBJECT (mail_op));
2021 dnd_helper_destroyer (info->helper);
2022 dnd_folder_info_destroyer (info);
2027 drag_and_drop_from_folder_view_dst_folder_performer (gboolean canceled,
2029 GtkWindow *parent_window,
2030 TnyAccount *account,
2033 DndFolderInfo *info = NULL;
2035 info = (DndFolderInfo *) user_data;
2037 if (err || canceled) {
2038 dnd_on_connection_failed_destroyer (info, parent_window, account);
2042 /* Connect to source folder and perform the copy/move */
2043 modest_platform_connect_if_remote_and_perform (NULL, TRUE,
2045 drag_and_drop_from_folder_view_src_folder_performer,
2050 * This function is used by drag_data_received_cb to manage drag and
2051 * drop of a folder, i.e, and drag from the folder view to the same
2055 drag_and_drop_from_folder_view (GtkTreeModel *source_model,
2056 GtkTreeModel *dest_model,
2057 GtkTreePath *dest_row,
2058 GtkSelectionData *selection_data,
2061 GtkTreeIter dest_iter, iter;
2062 TnyFolderStore *dest_folder = NULL;
2063 TnyFolderStore *folder = NULL;
2064 gboolean forbidden = FALSE;
2066 DndFolderInfo *info = NULL;
2068 win = modest_window_mgr_get_main_window (modest_runtime_get_window_mgr(), FALSE); /* don't create */
2070 g_warning ("%s: BUG: no main window", __FUNCTION__);
2071 dnd_helper_destroyer (helper);
2076 /* check the folder rules for the destination */
2077 folder = tree_path_to_folder (dest_model, dest_row);
2078 if (TNY_IS_FOLDER(folder)) {
2079 ModestTnyFolderRules rules =
2080 modest_tny_folder_get_rules (TNY_FOLDER (folder));
2081 forbidden = rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE;
2082 } else if (TNY_IS_FOLDER_STORE(folder)) {
2083 /* enable local root as destination for folders */
2084 if (!MODEST_IS_TNY_LOCAL_FOLDERS_ACCOUNT (folder) &&
2085 !modest_tny_account_is_memory_card_account (TNY_ACCOUNT (folder)))
2088 g_object_unref (folder);
2091 /* check the folder rules for the source */
2092 folder = tree_path_to_folder (source_model, helper->source_row);
2093 if (TNY_IS_FOLDER(folder)) {
2094 ModestTnyFolderRules rules =
2095 modest_tny_folder_get_rules (TNY_FOLDER (folder));
2096 forbidden = rules & MODEST_FOLDER_RULES_FOLDER_NON_MOVEABLE;
2099 g_object_unref (folder);
2103 /* Check if the drag is possible */
2104 if (forbidden || !gtk_tree_path_compare (helper->source_row, dest_row)) {
2106 modest_platform_run_information_dialog ((GtkWindow *) win,
2107 _("mail_in_ui_folder_move_target_error"),
2109 /* Restore the previous selection */
2110 folder = tree_path_to_folder (source_model, helper->source_row);
2112 if (TNY_IS_FOLDER (folder))
2113 modest_folder_view_select_folder (helper->folder_view,
2114 TNY_FOLDER (folder), FALSE);
2115 g_object_unref (folder);
2117 dnd_helper_destroyer (helper);
2122 gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
2123 gtk_tree_model_get (dest_model, &dest_iter,
2124 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
2126 gtk_tree_model_get_iter (source_model, &iter, helper->source_row);
2127 gtk_tree_model_get (source_model, &iter,
2128 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
2131 /* Create the info for the performer */
2132 info = g_slice_new0 (DndFolderInfo);
2133 info->src_folder = g_object_ref (folder);
2134 info->dst_folder = g_object_ref (dest_folder);
2135 info->helper = helper;
2137 /* Connect to the destination folder and perform the copy/move */
2138 modest_platform_connect_if_remote_and_perform (GTK_WINDOW (win), TRUE,
2140 drag_and_drop_from_folder_view_dst_folder_performer,
2144 g_object_unref (dest_folder);
2145 g_object_unref (folder);
2149 * This function receives the data set by the "drag-data-get" signal
2150 * handler. This information comes within the #GtkSelectionData. This
2151 * function will manage both the drags of folders of the treeview and
2152 * drags of headers of the header view widget.
2155 on_drag_data_received (GtkWidget *widget,
2156 GdkDragContext *context,
2159 GtkSelectionData *selection_data,
2164 GtkWidget *source_widget;
2165 GtkTreeModel *dest_model, *source_model;
2166 GtkTreePath *source_row, *dest_row;
2167 GtkTreeViewDropPosition pos;
2168 gboolean delete_source = FALSE;
2169 gboolean success = FALSE;
2171 /* Do not allow further process */
2172 g_signal_stop_emission_by_name (widget, "drag-data-received");
2173 source_widget = gtk_drag_get_source_widget (context);
2175 /* Get the action */
2176 if (context->action == GDK_ACTION_MOVE) {
2177 delete_source = TRUE;
2179 /* Notify that there is no folder selected. We need to
2180 do this in order to update the headers view (and
2181 its monitors, because when moving, the old folder
2182 won't longer exist. We can not wait for the end of
2183 the operation, because the operation won't start if
2184 the folder is in use */
2185 if (source_widget == widget) {
2186 GtkTreeSelection *sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
2187 gtk_tree_selection_unselect_all (sel);
2191 /* Check if the get_data failed */
2192 if (selection_data == NULL || selection_data->length < 0)
2195 /* Select the destination model */
2196 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
2198 /* Get the path to the destination row. Can not call
2199 gtk_tree_view_get_drag_dest_row() because the source row
2200 is not selected anymore */
2201 gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget), x, y,
2204 /* Only allow drops IN other rows */
2206 pos == GTK_TREE_VIEW_DROP_BEFORE ||
2207 pos == GTK_TREE_VIEW_DROP_AFTER)
2211 /* Drags from the header view */
2212 if (source_widget != widget) {
2213 source_model = gtk_tree_view_get_model (GTK_TREE_VIEW (source_widget));
2215 drag_and_drop_from_header_view (source_model,
2220 DndHelper *helper = NULL;
2222 /* Get the source model and row */
2223 gtk_tree_get_row_drag_data (selection_data,
2227 /* Create the helper */
2228 helper = g_slice_new0 (DndHelper);
2229 helper->delete_source = delete_source;
2230 helper->source_row = gtk_tree_path_copy (source_row);
2231 helper->folder_view = MODEST_FOLDER_VIEW (widget);
2233 drag_and_drop_from_folder_view (source_model,
2239 gtk_tree_path_free (source_row);
2243 gtk_tree_path_free (dest_row);
2246 /* Finish the drag and drop */
2247 gtk_drag_finish (context, success, FALSE, time);
2251 * We define a "drag-drop" signal handler because we do not want to
2252 * use the default one, because the default one always calls
2253 * gtk_drag_finish and we prefer to do it in the "drag-data-received"
2254 * signal handler, because there we have all the information available
2255 * to know if the dnd was a success or not.
2258 drag_drop_cb (GtkWidget *widget,
2259 GdkDragContext *context,
2267 if (!context->targets)
2270 /* Check if we're dragging a folder row */
2271 target = gtk_drag_dest_find_target (widget, context, NULL);
2273 /* Request the data from the source. */
2274 gtk_drag_get_data(widget, context, target, time);
2280 * This function expands a node of a tree view if it's not expanded
2281 * yet. Not sure why it needs the threads stuff, but gtk+`example code
2282 * does that, so that's why they're here.
2285 expand_row_timeout (gpointer data)
2287 GtkTreeView *tree_view = data;
2288 GtkTreePath *dest_path = NULL;
2289 GtkTreeViewDropPosition pos;
2290 gboolean result = FALSE;
2292 gdk_threads_enter ();
2294 gtk_tree_view_get_drag_dest_row (tree_view,
2299 (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER ||
2300 pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)) {
2301 gtk_tree_view_expand_row (tree_view, dest_path, FALSE);
2302 gtk_tree_path_free (dest_path);
2306 gtk_tree_path_free (dest_path);
2311 gdk_threads_leave ();
2317 * This function is called whenever the pointer is moved over a widget
2318 * while dragging some data. It installs a timeout that will expand a
2319 * node of the treeview if not expanded yet. This function also calls
2320 * gdk_drag_status in order to set the suggested action that will be
2321 * used by the "drag-data-received" signal handler to know if we
2322 * should do a move or just a copy of the data.
2325 on_drag_motion (GtkWidget *widget,
2326 GdkDragContext *context,
2332 GtkTreeViewDropPosition pos;
2333 GtkTreePath *dest_row;
2334 GtkTreeModel *dest_model;
2335 ModestFolderViewPrivate *priv;
2336 GdkDragAction suggested_action;
2337 gboolean valid_location = FALSE;
2338 TnyFolderStore *folder = NULL;
2340 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
2342 if (priv->timer_expander != 0) {
2343 g_source_remove (priv->timer_expander);
2344 priv->timer_expander = 0;
2347 gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
2352 /* Do not allow drops between folders */
2354 pos == GTK_TREE_VIEW_DROP_BEFORE ||
2355 pos == GTK_TREE_VIEW_DROP_AFTER) {
2356 gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW (widget), NULL, 0);
2357 gdk_drag_status(context, 0, time);
2358 valid_location = FALSE;
2361 valid_location = TRUE;
2364 /* Check that the destination folder is writable */
2365 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
2366 folder = tree_path_to_folder (dest_model, dest_row);
2367 if (folder && TNY_IS_FOLDER (folder)) {
2368 ModestTnyFolderRules rules = modest_tny_folder_get_rules(TNY_FOLDER (folder));
2370 if (rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE) {
2371 valid_location = FALSE;
2376 /* Expand the selected row after 1/2 second */
2377 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), dest_row)) {
2378 priv->timer_expander = g_timeout_add (500, expand_row_timeout, widget);
2380 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), dest_row, pos);
2382 /* Select the desired action. By default we pick MOVE */
2383 suggested_action = GDK_ACTION_MOVE;
2385 if (context->actions == GDK_ACTION_COPY)
2386 gdk_drag_status(context, GDK_ACTION_COPY, time);
2387 else if (context->actions == GDK_ACTION_MOVE)
2388 gdk_drag_status(context, GDK_ACTION_MOVE, time);
2389 else if (context->actions & suggested_action)
2390 gdk_drag_status(context, suggested_action, time);
2392 gdk_drag_status(context, GDK_ACTION_DEFAULT, time);
2396 g_object_unref (folder);
2398 gtk_tree_path_free (dest_row);
2400 g_signal_stop_emission_by_name (widget, "drag-motion");
2402 return valid_location;
2406 * This function sets the treeview as a source and a target for dnd
2407 * events. It also connects all the requirede signals.
2410 setup_drag_and_drop (GtkTreeView *self)
2412 /* Set up the folder view as a dnd destination. Set only the
2413 highlight flag, otherwise gtk will have a different
2415 #ifdef MODEST_TOOLKIT_HILDON2
2418 gtk_drag_dest_set (GTK_WIDGET (self),
2419 GTK_DEST_DEFAULT_HIGHLIGHT,
2420 folder_view_drag_types,
2421 G_N_ELEMENTS (folder_view_drag_types),
2422 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2424 g_signal_connect (G_OBJECT (self),
2425 "drag_data_received",
2426 G_CALLBACK (on_drag_data_received),
2430 /* Set up the treeview as a dnd source */
2431 gtk_drag_source_set (GTK_WIDGET (self),
2433 folder_view_drag_types,
2434 G_N_ELEMENTS (folder_view_drag_types),
2435 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2437 g_signal_connect (G_OBJECT (self),
2439 G_CALLBACK (on_drag_motion),
2442 g_signal_connect (G_OBJECT (self),
2444 G_CALLBACK (on_drag_data_get),
2447 g_signal_connect (G_OBJECT (self),
2449 G_CALLBACK (drag_drop_cb),
2454 * This function manages the navigation through the folders using the
2455 * keyboard or the hardware keys in the device
2458 on_key_pressed (GtkWidget *self,
2462 GtkTreeSelection *selection;
2464 GtkTreeModel *model;
2465 gboolean retval = FALSE;
2467 /* Up and Down are automatically managed by the treeview */
2468 if (event->keyval == GDK_Return) {
2469 /* Expand/Collapse the selected row */
2470 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2471 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
2474 path = gtk_tree_model_get_path (model, &iter);
2476 if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (self), path))
2477 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
2479 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
2480 gtk_tree_path_free (path);
2482 /* No further processing */
2490 * We listen to the changes in the local folder account name key,
2491 * because we want to show the right name in the view. The local
2492 * folder account name corresponds to the device name in the Maemo
2493 * version. We do this because we do not want to query gconf on each
2494 * tree view refresh. It's better to cache it and change whenever
2498 on_configuration_key_changed (ModestConf* conf,
2500 ModestConfEvent event,
2501 ModestConfNotificationId id,
2502 ModestFolderView *self)
2504 ModestFolderViewPrivate *priv;
2507 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
2508 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2510 if (!strcmp (key, MODEST_CONF_DEVICE_NAME)) {
2511 g_free (priv->local_account_name);
2513 if (event == MODEST_CONF_EVENT_KEY_UNSET)
2514 priv->local_account_name = g_strdup (MODEST_LOCAL_FOLDERS_DEFAULT_DISPLAY_NAME);
2516 priv->local_account_name = modest_conf_get_string (modest_runtime_get_conf(),
2517 MODEST_CONF_DEVICE_NAME, NULL);
2519 /* Force a redraw */
2520 #if GTK_CHECK_VERSION(2, 8, 0)
2521 GtkTreeViewColumn * tree_column;
2523 tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self),
2524 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN);
2525 gtk_tree_view_column_queue_resize (tree_column);
2527 gtk_widget_queue_draw (GTK_WIDGET (self));
2533 modest_folder_view_set_style (ModestFolderView *self,
2534 ModestFolderViewStyle style)
2536 ModestFolderViewPrivate *priv;
2538 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2539 g_return_if_fail (style == MODEST_FOLDER_VIEW_STYLE_SHOW_ALL ||
2540 style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE);
2542 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2545 priv->style = style;
2549 modest_folder_view_set_account_id_of_visible_server_account (ModestFolderView *self,
2550 const gchar *account_id)
2552 ModestFolderViewPrivate *priv;
2553 GtkTreeModel *model;
2555 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2557 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2559 /* This will be used by the filter_row callback,
2560 * to decided which rows to show: */
2561 if (priv->visible_account_id) {
2562 g_free (priv->visible_account_id);
2563 priv->visible_account_id = NULL;
2566 priv->visible_account_id = g_strdup (account_id);
2569 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2570 if (GTK_IS_TREE_MODEL_FILTER (model))
2571 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2573 /* Save settings to gconf */
2574 modest_widget_memory_save (modest_runtime_get_conf (), G_OBJECT(self),
2575 MODEST_CONF_FOLDER_VIEW_KEY);
2579 modest_folder_view_get_account_id_of_visible_server_account (ModestFolderView *self)
2581 ModestFolderViewPrivate *priv;
2583 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW(self), NULL);
2585 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2587 return (const gchar *) priv->visible_account_id;
2591 find_inbox_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *inbox_iter)
2595 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2597 gtk_tree_model_get (model, iter,
2598 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN,
2601 gboolean result = FALSE;
2602 if (type == TNY_FOLDER_TYPE_INBOX) {
2606 *inbox_iter = *iter;
2610 if (gtk_tree_model_iter_children (model, &child, iter)) {
2611 if (find_inbox_iter (model, &child, inbox_iter))
2615 } while (gtk_tree_model_iter_next (model, iter));
2624 modest_folder_view_select_first_inbox_or_local (ModestFolderView *self)
2626 GtkTreeModel *model;
2627 GtkTreeIter iter, inbox_iter;
2628 GtkTreeSelection *sel;
2629 GtkTreePath *path = NULL;
2631 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2633 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2637 expand_root_items (self);
2638 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2640 if (!gtk_tree_model_get_iter_first (model, &iter)) {
2641 g_warning ("%s: model is empty", __FUNCTION__);
2645 if (find_inbox_iter (model, &iter, &inbox_iter))
2646 path = gtk_tree_model_get_path (model, &inbox_iter);
2648 path = gtk_tree_path_new_first ();
2650 /* Select the row and free */
2651 gtk_tree_view_set_cursor (GTK_TREE_VIEW (self), path, NULL, FALSE);
2652 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (self), path, NULL, FALSE, 0.0, 0.0);
2653 gtk_tree_path_free (path);
2656 gtk_widget_grab_focus (GTK_WIDGET(self));
2662 find_folder_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *folder_iter,
2667 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2668 TnyFolder* a_folder;
2671 gtk_tree_model_get (model, iter,
2672 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &a_folder,
2673 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name,
2674 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
2678 if (folder == a_folder) {
2679 g_object_unref (a_folder);
2680 *folder_iter = *iter;
2683 g_object_unref (a_folder);
2685 if (gtk_tree_model_iter_children (model, &child, iter)) {
2686 if (find_folder_iter (model, &child, folder_iter, folder))
2690 } while (gtk_tree_model_iter_next (model, iter));
2697 on_row_inserted_maybe_select_folder (GtkTreeModel *tree_model,
2700 ModestFolderView *self)
2702 ModestFolderViewPrivate *priv = NULL;
2703 GtkTreeSelection *sel;
2704 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2705 GObject *instance = NULL;
2707 if (!MODEST_IS_FOLDER_VIEW(self))
2710 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2712 priv->reexpand = TRUE;
2714 gtk_tree_model_get (tree_model, iter,
2715 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
2716 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
2718 if (type == TNY_FOLDER_TYPE_INBOX && priv->folder_to_select == NULL) {
2719 priv->folder_to_select = g_object_ref (instance);
2721 g_object_unref (instance);
2723 if (priv->folder_to_select) {
2725 if (!modest_folder_view_select_folder (self, priv->folder_to_select,
2728 path = gtk_tree_model_get_path (tree_model, iter);
2729 gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
2731 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2733 gtk_tree_selection_select_iter (sel, iter);
2734 gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
2736 gtk_tree_path_free (path);
2740 modest_folder_view_disable_next_folder_selection (self);
2742 /* Refilter the model */
2743 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (tree_model));
2749 modest_folder_view_disable_next_folder_selection (ModestFolderView *self)
2751 ModestFolderViewPrivate *priv;
2753 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2755 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2757 if (priv->folder_to_select)
2758 g_object_unref(priv->folder_to_select);
2760 priv->folder_to_select = NULL;
2764 modest_folder_view_select_folder (ModestFolderView *self, TnyFolder *folder,
2765 gboolean after_change)
2767 GtkTreeModel *model;
2768 GtkTreeIter iter, folder_iter;
2769 GtkTreeSelection *sel;
2770 ModestFolderViewPrivate *priv = NULL;
2772 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW (self), FALSE);
2773 g_return_val_if_fail (folder && TNY_IS_FOLDER (folder), FALSE);
2775 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2778 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2779 gtk_tree_selection_unselect_all (sel);
2781 if (priv->folder_to_select)
2782 g_object_unref(priv->folder_to_select);
2783 priv->folder_to_select = TNY_FOLDER(g_object_ref(folder));
2787 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2792 /* Refilter the model, before selecting the folder */
2793 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2795 if (!gtk_tree_model_get_iter_first (model, &iter)) {
2796 g_warning ("%s: model is empty", __FUNCTION__);
2800 if (find_folder_iter (model, &iter, &folder_iter, folder)) {
2803 path = gtk_tree_model_get_path (model, &folder_iter);
2804 gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
2806 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2807 gtk_tree_selection_select_iter (sel, &folder_iter);
2808 gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
2810 gtk_tree_path_free (path);
2818 modest_folder_view_copy_selection (ModestFolderView *self)
2820 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2822 /* Copy selection */
2823 _clipboard_set_selected_data (self, FALSE);
2827 modest_folder_view_cut_selection (ModestFolderView *folder_view)
2829 ModestFolderViewPrivate *priv = NULL;
2830 GtkTreeModel *model = NULL;
2831 const gchar **hidding = NULL;
2832 guint i, n_selected;
2834 g_return_if_fail (folder_view && MODEST_IS_FOLDER_VIEW (folder_view));
2835 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
2837 /* Copy selection */
2838 if (!_clipboard_set_selected_data (folder_view, TRUE))
2841 /* Get hidding ids */
2842 hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected);
2844 /* Clear hidding array created by previous cut operation */
2845 _clear_hidding_filter (MODEST_FOLDER_VIEW (folder_view));
2847 /* Copy hidding array */
2848 priv->n_selected = n_selected;
2849 priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
2850 for (i=0; i < n_selected; i++)
2851 priv->hidding_ids[i] = g_strdup(hidding[i]);
2853 /* Hide cut folders */
2854 model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
2855 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2859 modest_folder_view_copy_model (ModestFolderView *folder_view_src,
2860 ModestFolderView *folder_view_dst)
2862 GtkTreeModel *filter_model = NULL;
2863 GtkTreeModel *model = NULL;
2864 GtkTreeModel *new_filter_model = NULL;
2866 g_return_if_fail (folder_view_src && MODEST_IS_FOLDER_VIEW (folder_view_src));
2867 g_return_if_fail (folder_view_dst && MODEST_IS_FOLDER_VIEW (folder_view_dst));
2870 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view_src));
2871 model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER(filter_model));
2873 /* Build new filter model */
2874 new_filter_model = gtk_tree_model_filter_new (model, NULL);
2875 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (new_filter_model),
2879 /* Set copied model */
2880 gtk_tree_view_set_model (GTK_TREE_VIEW (folder_view_dst), new_filter_model);
2881 g_signal_connect (G_OBJECT(new_filter_model), "row-inserted",
2882 (GCallback) on_row_inserted_maybe_select_folder, folder_view_dst);
2885 g_object_unref (new_filter_model);
2889 modest_folder_view_show_non_move_folders (ModestFolderView *folder_view,
2892 GtkTreeModel *model = NULL;
2893 ModestFolderViewPrivate* priv;
2895 g_return_if_fail (folder_view && MODEST_IS_FOLDER_VIEW (folder_view));
2897 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
2898 priv->show_non_move = show;
2899 /* modest_folder_view_update_model(folder_view, */
2900 /* TNY_ACCOUNT_STORE(modest_runtime_get_account_store())); */
2902 /* Hide special folders */
2903 model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
2904 if (GTK_IS_TREE_MODEL_FILTER (model)) {
2905 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2909 /* Returns FALSE if it did not selected anything */
2911 _clipboard_set_selected_data (ModestFolderView *folder_view,
2914 ModestFolderViewPrivate *priv = NULL;
2915 TnyFolderStore *folder = NULL;
2916 gboolean retval = FALSE;
2918 g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (folder_view), FALSE);
2919 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
2921 /* Set selected data on clipboard */
2922 g_return_val_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard), FALSE);
2923 folder = modest_folder_view_get_selected (folder_view);
2925 /* Do not allow to select an account */
2926 if (TNY_IS_FOLDER (folder)) {
2927 modest_email_clipboard_set_data (priv->clipboard, TNY_FOLDER(folder), NULL, delete);
2932 g_object_unref (folder);
2938 _clear_hidding_filter (ModestFolderView *folder_view)
2940 ModestFolderViewPrivate *priv;
2943 g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view));
2944 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
2946 if (priv->hidding_ids != NULL) {
2947 for (i=0; i < priv->n_selected; i++)
2948 g_free (priv->hidding_ids[i]);
2949 g_free(priv->hidding_ids);
2955 on_display_name_changed (ModestAccountMgr *mgr,
2956 const gchar *account,
2959 ModestFolderView *self;
2961 self = MODEST_FOLDER_VIEW (user_data);
2963 /* Force a redraw */
2964 #if GTK_CHECK_VERSION(2, 8, 0)
2965 GtkTreeViewColumn * tree_column;
2967 tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self),
2968 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN);
2969 gtk_tree_view_column_queue_resize (tree_column);
2971 gtk_widget_queue_draw (GTK_WIDGET (self));
2976 modest_folder_view_set_cell_style (ModestFolderView *self,
2977 ModestFolderViewCellStyle cell_style)
2979 ModestFolderViewPrivate *priv = NULL;
2981 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
2982 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2984 priv->cell_style = cell_style;
2986 gtk_widget_queue_draw (GTK_WIDGET (self));