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 !TNY_IS_ACCOUNT (instance) &&
635 type != TNY_FOLDER_TYPE_INBOX &&
636 modest_tny_folder_store_is_remote (TNY_FOLDER_STORE (instance))) {
637 return get_composite_icons (MODEST_FOLDER_ICON_NORMAL,
640 &normal_pixbuf_close);
644 case TNY_FOLDER_TYPE_INVALID:
645 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
648 case TNY_FOLDER_TYPE_ROOT:
649 if (TNY_IS_ACCOUNT (instance)) {
651 if (modest_tny_account_is_virtual_local_folders (TNY_ACCOUNT (instance))) {
652 retval = get_composite_icons (MODEST_FOLDER_ICON_LOCAL_FOLDERS,
655 &avirt_pixbuf_close);
657 const gchar *account_id = tny_account_get_id (TNY_ACCOUNT (instance));
659 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
660 retval = get_composite_icons (MODEST_FOLDER_ICON_MMC,
665 retval = get_composite_icons (MODEST_FOLDER_ICON_ACCOUNT,
668 &anorm_pixbuf_close);
673 case TNY_FOLDER_TYPE_INBOX:
674 retval = get_composite_icons (MODEST_FOLDER_ICON_INBOX,
677 &inbox_pixbuf_close);
679 case TNY_FOLDER_TYPE_OUTBOX:
680 retval = get_composite_icons (MODEST_FOLDER_ICON_OUTBOX,
683 &outbox_pixbuf_close);
685 case TNY_FOLDER_TYPE_JUNK:
686 retval = get_composite_icons (MODEST_FOLDER_ICON_JUNK,
691 case TNY_FOLDER_TYPE_SENT:
692 retval = get_composite_icons (MODEST_FOLDER_ICON_SENT,
697 case TNY_FOLDER_TYPE_TRASH:
698 retval = get_composite_icons (MODEST_FOLDER_ICON_TRASH,
701 &trash_pixbuf_close);
703 case TNY_FOLDER_TYPE_DRAFTS:
704 retval = get_composite_icons (MODEST_FOLDER_ICON_DRAFTS,
707 &draft_pixbuf_close);
709 case TNY_FOLDER_TYPE_NORMAL:
711 retval = get_composite_icons (MODEST_FOLDER_ICON_NORMAL,
714 &normal_pixbuf_close);
722 free_pixbufs (ThreePixbufs *pixbufs)
725 g_object_unref (pixbufs->pixbuf);
726 if (pixbufs->pixbuf_open)
727 g_object_unref (pixbufs->pixbuf_open);
728 if (pixbufs->pixbuf_close)
729 g_object_unref (pixbufs->pixbuf_close);
730 g_slice_free (ThreePixbufs, pixbufs);
734 icon_cell_data (GtkTreeViewColumn *column,
735 GtkCellRenderer *renderer,
736 GtkTreeModel *tree_model,
740 GObject *rendobj = NULL, *instance = NULL;
741 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
742 gboolean has_children;
743 ThreePixbufs *pixbufs;
745 rendobj = (GObject *) renderer;
747 gtk_tree_model_get (tree_model, iter,
748 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
749 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
755 has_children = gtk_tree_model_iter_has_child (tree_model, iter);
756 pixbufs = get_folder_icons (type, instance);
757 g_object_unref (instance);
760 g_object_set (rendobj, "pixbuf", pixbufs->pixbuf, NULL);
763 g_object_set (rendobj, "pixbuf-expander-open", pixbufs->pixbuf_open, NULL);
764 g_object_set (rendobj, "pixbuf-expander-closed", pixbufs->pixbuf_close, NULL);
767 free_pixbufs (pixbufs);
771 add_columns (GtkWidget *treeview)
773 GtkTreeViewColumn *column;
774 GtkCellRenderer *renderer;
775 GtkTreeSelection *sel;
778 column = gtk_tree_view_column_new ();
780 /* Set icon and text render function */
781 renderer = gtk_cell_renderer_pixbuf_new();
782 gtk_tree_view_column_pack_start (column, renderer, FALSE);
783 gtk_tree_view_column_set_cell_data_func(column, renderer,
784 icon_cell_data, treeview, NULL);
786 renderer = gtk_cell_renderer_text_new();
787 g_object_set (renderer, "ellipsize", PANGO_ELLIPSIZE_END,
788 "ellipsize-set", TRUE, NULL);
789 gtk_tree_view_column_pack_start (column, renderer, TRUE);
790 gtk_tree_view_column_set_cell_data_func(column, renderer,
791 text_cell_data, treeview, NULL);
793 /* Set selection mode */
794 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(treeview));
795 gtk_tree_selection_set_mode (sel, GTK_SELECTION_SINGLE);
797 /* Set treeview appearance */
798 gtk_tree_view_column_set_spacing (column, 2);
799 gtk_tree_view_column_set_resizable (column, TRUE);
800 gtk_tree_view_column_set_fixed_width (column, TRUE);
801 gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(treeview), FALSE);
802 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(treeview), FALSE);
805 gtk_tree_view_append_column (GTK_TREE_VIEW(treeview),column);
809 modest_folder_view_init (ModestFolderView *obj)
811 ModestFolderViewPrivate *priv;
814 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
816 priv->timer_expander = 0;
817 priv->account_store = NULL;
819 priv->style = MODEST_FOLDER_VIEW_STYLE_SHOW_ALL;
820 priv->cur_folder_store = NULL;
821 priv->visible_account_id = NULL;
822 priv->folder_to_select = NULL;
824 priv->reexpand = TRUE;
826 /* Initialize the local account name */
827 conf = modest_runtime_get_conf();
828 priv->local_account_name = modest_conf_get_string (conf, MODEST_CONF_DEVICE_NAME, NULL);
830 /* Init email clipboard */
831 priv->clipboard = modest_runtime_get_email_clipboard ();
832 priv->hidding_ids = NULL;
833 priv->n_selected = 0;
834 priv->reselect = FALSE;
835 priv->show_non_move = TRUE;
838 add_columns (GTK_WIDGET (obj));
840 /* Setup drag and drop */
841 setup_drag_and_drop (GTK_TREE_VIEW(obj));
843 /* Connect signals */
844 g_signal_connect (G_OBJECT (obj),
846 G_CALLBACK (on_key_pressed), NULL);
848 priv->display_name_changed_signal =
849 g_signal_connect (modest_runtime_get_account_mgr (),
850 "display_name_changed",
851 G_CALLBACK (on_display_name_changed),
855 * Track changes in the local account name (in the device it
856 * will be the device name)
858 priv->conf_key_signal = g_signal_connect (G_OBJECT(conf),
860 G_CALLBACK(on_configuration_key_changed),
865 tny_account_store_view_init (gpointer g, gpointer iface_data)
867 TnyAccountStoreViewIface *klass = (TnyAccountStoreViewIface *)g;
869 klass->set_account_store = modest_folder_view_set_account_store;
873 modest_folder_view_finalize (GObject *obj)
875 ModestFolderViewPrivate *priv;
876 GtkTreeSelection *sel;
878 g_return_if_fail (obj);
880 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
882 if (priv->timer_expander != 0) {
883 g_source_remove (priv->timer_expander);
884 priv->timer_expander = 0;
887 if (priv->account_store) {
888 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
889 priv->account_inserted_signal);
890 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
891 priv->account_removed_signal);
892 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
893 priv->account_changed_signal);
894 g_object_unref (G_OBJECT(priv->account_store));
895 priv->account_store = NULL;
898 if (g_signal_handler_is_connected (modest_runtime_get_account_mgr (),
899 priv->display_name_changed_signal)) {
900 g_signal_handler_disconnect (modest_runtime_get_account_mgr (),
901 priv->display_name_changed_signal);
902 priv->display_name_changed_signal = 0;
906 g_object_unref (G_OBJECT (priv->query));
910 if (priv->folder_to_select) {
911 g_object_unref (G_OBJECT(priv->folder_to_select));
912 priv->folder_to_select = NULL;
915 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(obj));
917 g_signal_handler_disconnect (G_OBJECT(sel), priv->changed_signal);
919 g_free (priv->local_account_name);
920 g_free (priv->visible_account_id);
922 if (priv->conf_key_signal) {
923 g_signal_handler_disconnect (modest_runtime_get_conf (),
924 priv->conf_key_signal);
925 priv->conf_key_signal = 0;
928 if (priv->cur_folder_store) {
929 g_object_unref (priv->cur_folder_store);
930 priv->cur_folder_store = NULL;
933 /* Clear hidding array created by cut operation */
934 _clear_hidding_filter (MODEST_FOLDER_VIEW (obj));
936 G_OBJECT_CLASS(parent_class)->finalize (obj);
941 modest_folder_view_set_account_store (TnyAccountStoreView *self, TnyAccountStore *account_store)
943 ModestFolderViewPrivate *priv;
946 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
947 g_return_if_fail (TNY_IS_ACCOUNT_STORE (account_store));
949 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
950 device = tny_account_store_get_device (account_store);
952 if (G_UNLIKELY (priv->account_store)) {
954 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
955 priv->account_inserted_signal))
956 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
957 priv->account_inserted_signal);
958 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
959 priv->account_removed_signal))
960 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
961 priv->account_removed_signal);
962 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
963 priv->account_changed_signal))
964 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
965 priv->account_changed_signal);
966 g_object_unref (G_OBJECT (priv->account_store));
969 priv->account_store = g_object_ref (G_OBJECT (account_store));
971 priv->account_removed_signal =
972 g_signal_connect (G_OBJECT(account_store), "account_removed",
973 G_CALLBACK (on_account_removed), self);
975 priv->account_inserted_signal =
976 g_signal_connect (G_OBJECT(account_store), "account_inserted",
977 G_CALLBACK (on_account_inserted), self);
979 priv->account_changed_signal =
980 g_signal_connect (G_OBJECT(account_store), "account_changed",
981 G_CALLBACK (on_account_changed), self);
983 modest_folder_view_update_model (MODEST_FOLDER_VIEW (self), account_store);
984 priv->reselect = FALSE;
985 modest_folder_view_select_first_inbox_or_local (MODEST_FOLDER_VIEW (self));
987 g_object_unref (G_OBJECT (device));
991 on_account_inserted (TnyAccountStore *account_store,
995 ModestFolderViewPrivate *priv;
996 GtkTreeModel *sort_model, *filter_model;
998 /* Ignore transport account insertions, we're not showing them
999 in the folder view */
1000 if (TNY_IS_TRANSPORT_ACCOUNT (account))
1003 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (user_data);
1006 /* If we're adding a new account, and there is no previous
1007 one, we need to select the visible server account */
1008 if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE &&
1009 !priv->visible_account_id)
1010 modest_widget_memory_restore (modest_runtime_get_conf(),
1011 G_OBJECT (user_data),
1012 MODEST_CONF_FOLDER_VIEW_KEY);
1014 if (!GTK_IS_TREE_VIEW(user_data)) {
1015 g_warning ("BUG: %s: not a valid tree view", __FUNCTION__);
1019 /* Get the inner model */
1020 /* check, is some rare cases, we did not get the right thing here,
1022 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (user_data));
1023 if (!GTK_IS_TREE_MODEL_FILTER(filter_model)) {
1024 g_warning ("BUG: %s: not a valid filter model", __FUNCTION__);
1028 /* check, is some rare cases, we did not get the right thing here,
1030 sort_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
1031 if (!GTK_IS_TREE_MODEL_SORT(sort_model)) {
1032 g_warning ("BUG: %s: not a valid sort model", __FUNCTION__);
1036 /* Insert the account in the model */
1037 tny_list_append (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
1038 G_OBJECT (account));
1040 /* Refilter the model */
1041 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1046 same_account_selected (ModestFolderView *self,
1047 TnyAccount *account)
1049 ModestFolderViewPrivate *priv;
1050 gboolean same_account = FALSE;
1052 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1054 if (priv->cur_folder_store) {
1055 TnyAccount *selected_folder_account = NULL;
1057 if (TNY_IS_FOLDER (priv->cur_folder_store)) {
1058 selected_folder_account =
1059 modest_tny_folder_get_account (TNY_FOLDER (priv->cur_folder_store));
1061 selected_folder_account =
1062 TNY_ACCOUNT (g_object_ref (priv->cur_folder_store));
1065 if (selected_folder_account == account)
1066 same_account = TRUE;
1068 g_object_unref (selected_folder_account);
1070 return same_account;
1075 * Selects the first inbox or the local account in an idle
1078 on_idle_select_first_inbox_or_local (gpointer user_data)
1080 ModestFolderView *self = MODEST_FOLDER_VIEW (user_data);
1082 gdk_threads_enter ();
1083 modest_folder_view_select_first_inbox_or_local (self);
1084 gdk_threads_leave ();
1090 on_account_changed (TnyAccountStore *account_store,
1091 TnyAccount *tny_account,
1094 ModestFolderView *self;
1095 ModestFolderViewPrivate *priv;
1096 GtkTreeModel *sort_model, *filter_model;
1097 GtkTreeSelection *sel;
1098 gboolean same_account;
1100 /* Ignore transport account insertions, we're not showing them
1101 in the folder view */
1102 if (TNY_IS_TRANSPORT_ACCOUNT (tny_account))
1105 if (!MODEST_IS_FOLDER_VIEW(user_data)) {
1106 g_warning ("BUG: %s: not a valid folder view", __FUNCTION__);
1110 self = MODEST_FOLDER_VIEW (user_data);
1111 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (user_data);
1113 /* Get the inner model */
1114 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (user_data));
1115 if (!GTK_IS_TREE_MODEL_FILTER(filter_model)) {
1116 g_warning ("BUG: %s: not a valid filter model", __FUNCTION__);
1120 sort_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
1121 if (!GTK_IS_TREE_MODEL_SORT(sort_model)) {
1122 g_warning ("BUG: %s: not a valid sort model", __FUNCTION__);
1126 /* Invalidate the cur_folder_store only if the selected folder
1127 belongs to the account that is being removed */
1128 same_account = same_account_selected (self, tny_account);
1130 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1131 gtk_tree_selection_unselect_all (sel);
1134 /* Remove the account from the model */
1135 tny_list_remove (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
1136 G_OBJECT (tny_account));
1138 /* Insert the account in the model */
1139 tny_list_append (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
1140 G_OBJECT (tny_account));
1142 /* Refilter the model */
1143 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1145 /* Select the first INBOX if the currently selected folder
1146 belongs to the account that is being deleted */
1147 if (same_account && !MODEST_IS_TNY_LOCAL_FOLDERS_ACCOUNT (tny_account))
1148 g_idle_add (on_idle_select_first_inbox_or_local, self);
1152 on_account_removed (TnyAccountStore *account_store,
1153 TnyAccount *account,
1156 ModestFolderView *self = NULL;
1157 ModestFolderViewPrivate *priv;
1158 GtkTreeModel *sort_model, *filter_model;
1159 GtkTreeSelection *sel = NULL;
1160 gboolean same_account = FALSE;
1162 /* Ignore transport account removals, we're not showing them
1163 in the folder view */
1164 if (TNY_IS_TRANSPORT_ACCOUNT (account))
1167 if (!MODEST_IS_FOLDER_VIEW(user_data)) {
1168 g_warning ("BUG: %s: not a valid folder view", __FUNCTION__);
1172 self = MODEST_FOLDER_VIEW (user_data);
1173 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1175 /* Invalidate the cur_folder_store only if the selected folder
1176 belongs to the account that is being removed */
1177 same_account = same_account_selected (self, account);
1179 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1180 gtk_tree_selection_unselect_all (sel);
1183 /* Invalidate row to select only if the folder to select
1184 belongs to the account that is being removed*/
1185 if (priv->folder_to_select) {
1186 TnyAccount *folder_to_select_account = NULL;
1188 folder_to_select_account = tny_folder_get_account (priv->folder_to_select);
1189 if (folder_to_select_account == account) {
1190 modest_folder_view_disable_next_folder_selection (self);
1191 g_object_unref (priv->folder_to_select);
1192 priv->folder_to_select = NULL;
1194 g_object_unref (folder_to_select_account);
1197 /* Remove the account from the model */
1198 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1199 if (!GTK_IS_TREE_MODEL_FILTER(filter_model)) {
1200 g_warning ("BUG: %s: not a valid filter model", __FUNCTION__);
1204 sort_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
1205 if (!GTK_IS_TREE_MODEL_SORT(sort_model)) {
1206 g_warning ("BUG: %s: not a valid sort model", __FUNCTION__);
1210 tny_list_remove (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
1211 G_OBJECT (account));
1213 /* If the removed account is the currently viewed one then
1214 clear the configuration value. The new visible account will be the default account */
1215 if (priv->visible_account_id &&
1216 !strcmp (priv->visible_account_id, tny_account_get_id (account))) {
1218 /* Clear the current visible account_id */
1219 modest_folder_view_set_account_id_of_visible_server_account (self, NULL);
1221 /* Call the restore method, this will set the new visible account */
1222 modest_widget_memory_restore (modest_runtime_get_conf(), G_OBJECT(self),
1223 MODEST_CONF_FOLDER_VIEW_KEY);
1226 /* Refilter the model */
1227 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1229 /* Select the first INBOX if the currently selected folder
1230 belongs to the account that is being deleted */
1232 g_idle_add (on_idle_select_first_inbox_or_local, self);
1236 modest_folder_view_set_title (ModestFolderView *self, const gchar *title)
1238 GtkTreeViewColumn *col;
1240 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
1242 col = gtk_tree_view_get_column (GTK_TREE_VIEW(self), 0);
1244 g_printerr ("modest: failed get column for title\n");
1248 gtk_tree_view_column_set_title (col, title);
1249 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self),
1254 modest_folder_view_on_map (ModestFolderView *self,
1255 GdkEventExpose *event,
1258 ModestFolderViewPrivate *priv;
1260 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1262 /* This won't happen often */
1263 if (G_UNLIKELY (priv->reselect)) {
1264 /* Select the first inbox or the local account if not found */
1266 /* TODO: this could cause a lock at startup, so we
1267 comment it for the moment. We know that this will
1268 be a bug, because the INBOX is not selected, but we
1269 need to rewrite some parts of Modest to avoid the
1270 deathlock situation */
1271 /* TODO: check if this is still the case */
1272 priv->reselect = FALSE;
1273 modest_folder_view_select_first_inbox_or_local (self);
1274 /* Notify the display name observers */
1275 g_signal_emit (G_OBJECT(self),
1276 signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
1280 if (priv->reexpand) {
1281 expand_root_items (self);
1282 priv->reexpand = FALSE;
1289 modest_folder_view_new (TnyFolderStoreQuery *query)
1292 ModestFolderViewPrivate *priv;
1293 GtkTreeSelection *sel;
1295 self = G_OBJECT (g_object_new (MODEST_TYPE_FOLDER_VIEW,
1296 #ifdef MODEST_TOOLKIT_HILDON2
1297 "hildon-ui-mode", HILDON_UI_MODE_NORMAL,
1300 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1303 priv->query = g_object_ref (query);
1305 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1306 priv->changed_signal = g_signal_connect (sel, "changed",
1307 G_CALLBACK (on_selection_changed), self);
1309 g_signal_connect (self, "row-activated", G_CALLBACK (on_row_activated), self);
1311 g_signal_connect (self, "expose-event", G_CALLBACK (modest_folder_view_on_map), NULL);
1313 return GTK_WIDGET(self);
1316 /* this feels dirty; any other way to expand all the root items? */
1318 expand_root_items (ModestFolderView *self)
1321 GtkTreeModel *model;
1324 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1325 path = gtk_tree_path_new_first ();
1327 /* all folders should have child items, so.. */
1329 gtk_tree_view_expand_row (GTK_TREE_VIEW(self), path, FALSE);
1330 gtk_tree_path_next (path);
1331 } while (gtk_tree_model_get_iter (model, &iter, path));
1333 gtk_tree_path_free (path);
1337 * We use this function to implement the
1338 * MODEST_FOLDER_VIEW_STYLE_SHOW_ONE style. We only show the default
1339 * account in this case, and the local folders.
1342 filter_row (GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
1344 ModestFolderViewPrivate *priv;
1345 gboolean retval = TRUE;
1346 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1347 GObject *instance = NULL;
1348 const gchar *id = NULL;
1350 gboolean found = FALSE;
1351 gboolean cleared = FALSE;
1353 g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (data), FALSE);
1354 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (data);
1356 gtk_tree_model_get (model, iter,
1357 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
1358 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
1361 /* Do not show if there is no instance, this could indeed
1362 happen when the model is being modified while it's being
1363 drawn. This could occur for example when moving folders
1368 if (type == TNY_FOLDER_TYPE_ROOT) {
1369 /* TNY_FOLDER_TYPE_ROOT means that the instance is an
1370 account instead of a folder. */
1371 if (TNY_IS_ACCOUNT (instance)) {
1372 TnyAccount *acc = TNY_ACCOUNT (instance);
1373 const gchar *account_id = tny_account_get_id (acc);
1375 /* If it isn't a special folder,
1376 * don't show it unless it is the visible account: */
1377 if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE &&
1378 !modest_tny_account_is_virtual_local_folders (acc) &&
1379 strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
1381 /* Show only the visible account id */
1382 if (priv->visible_account_id) {
1383 if (strcmp (account_id, priv->visible_account_id))
1390 /* Never show these to the user. They are merged into one folder
1391 * in the local-folders account instead: */
1392 if (retval && MODEST_IS_TNY_OUTBOX_ACCOUNT (acc))
1397 /* Check hiding (if necessary) */
1398 cleared = modest_email_clipboard_cleared (priv->clipboard);
1399 if ((retval) && (!cleared) && (TNY_IS_FOLDER (instance))) {
1400 id = tny_folder_get_id (TNY_FOLDER(instance));
1401 if (priv->hidding_ids != NULL)
1402 for (i=0; i < priv->n_selected && !found; i++)
1403 if (priv->hidding_ids[i] != NULL && id != NULL)
1404 found = (!strcmp (priv->hidding_ids[i], id));
1410 /* If this is a move to dialog, hide Sent, Outbox and Drafts
1411 folder as no message can be move there according to UI specs */
1412 if (!priv->show_non_move) {
1414 case TNY_FOLDER_TYPE_OUTBOX:
1415 case TNY_FOLDER_TYPE_SENT:
1416 case TNY_FOLDER_TYPE_DRAFTS:
1419 case TNY_FOLDER_TYPE_UNKNOWN:
1420 case TNY_FOLDER_TYPE_NORMAL:
1421 type = modest_tny_folder_guess_folder_type(TNY_FOLDER(instance));
1422 if (type == TNY_FOLDER_TYPE_INVALID)
1423 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1425 if (type == TNY_FOLDER_TYPE_OUTBOX ||
1426 type == TNY_FOLDER_TYPE_SENT
1427 || type == TNY_FOLDER_TYPE_DRAFTS)
1436 g_object_unref (instance);
1443 modest_folder_view_update_model (ModestFolderView *self,
1444 TnyAccountStore *account_store)
1446 ModestFolderViewPrivate *priv;
1447 GtkTreeModel *model /* , *old_model */;
1448 GtkTreeModel *filter_model = NULL, *sortable = NULL;
1450 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW (self), FALSE);
1451 g_return_val_if_fail (account_store && MODEST_IS_TNY_ACCOUNT_STORE(account_store),
1454 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1456 /* Notify that there is no folder selected */
1457 g_signal_emit (G_OBJECT(self),
1458 signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
1460 if (priv->cur_folder_store) {
1461 g_object_unref (priv->cur_folder_store);
1462 priv->cur_folder_store = NULL;
1465 /* FIXME: the local accounts are not shown when the query
1466 selects only the subscribed folders */
1467 model = tny_gtk_folder_store_tree_model_new (NULL);
1469 /* Get the accounts: */
1470 tny_account_store_get_accounts (TNY_ACCOUNT_STORE(account_store),
1472 TNY_ACCOUNT_STORE_STORE_ACCOUNTS);
1474 sortable = gtk_tree_model_sort_new_with_model (model);
1475 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
1476 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
1477 GTK_SORT_ASCENDING);
1478 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
1479 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
1480 cmp_rows, NULL, NULL);
1482 /* Create filter model */
1483 filter_model = gtk_tree_model_filter_new (sortable, NULL);
1484 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
1490 gtk_tree_view_set_model (GTK_TREE_VIEW(self), filter_model);
1491 g_signal_connect (G_OBJECT(filter_model), "row-inserted",
1492 (GCallback) on_row_inserted_maybe_select_folder, self);
1494 g_object_unref (model);
1495 g_object_unref (filter_model);
1496 g_object_unref (sortable);
1498 /* Force a reselection of the INBOX next time the widget is shown */
1499 priv->reselect = TRUE;
1506 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
1508 GtkTreeModel *model = NULL;
1509 TnyFolderStore *folder = NULL;
1511 ModestFolderView *tree_view = NULL;
1512 ModestFolderViewPrivate *priv = NULL;
1513 gboolean selected = FALSE;
1515 g_return_if_fail (sel);
1516 g_return_if_fail (user_data);
1518 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
1520 selected = gtk_tree_selection_get_selected (sel, &model, &iter);
1522 tree_view = MODEST_FOLDER_VIEW (user_data);
1525 gtk_tree_model_get (model, &iter,
1526 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
1529 /* If the folder is the same do not notify */
1530 if (folder && priv->cur_folder_store == folder) {
1531 g_object_unref (folder);
1536 /* Current folder was unselected */
1537 if (priv->cur_folder_store) {
1538 /* We must do this firstly because a libtinymail-camel
1539 implementation detail. If we issue the signal
1540 before doing the sync_async, then that signal could
1541 cause (and it actually does it) a free of the
1542 summary of the folder (because the main window will
1543 clear the headers view */
1544 if (TNY_IS_FOLDER(priv->cur_folder_store))
1545 tny_folder_sync_async (TNY_FOLDER(priv->cur_folder_store),
1546 FALSE, NULL, NULL, NULL);
1548 g_signal_emit (G_OBJECT(tree_view), signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
1549 priv->cur_folder_store, FALSE);
1551 g_object_unref (priv->cur_folder_store);
1552 priv->cur_folder_store = NULL;
1555 /* New current references */
1556 priv->cur_folder_store = folder;
1558 /* New folder has been selected. Do not notify if there is
1559 nothing new selected */
1561 g_signal_emit (G_OBJECT(tree_view),
1562 signals[FOLDER_SELECTION_CHANGED_SIGNAL],
1563 0, priv->cur_folder_store, TRUE);
1568 on_row_activated (GtkTreeView *treeview,
1569 GtkTreePath *treepath,
1570 GtkTreeViewColumn *column,
1573 GtkTreeModel *model = NULL;
1574 TnyFolderStore *folder = NULL;
1576 ModestFolderView *self = NULL;
1577 ModestFolderViewPrivate *priv = NULL;
1579 g_return_if_fail (treeview);
1580 g_return_if_fail (user_data);
1582 self = MODEST_FOLDER_VIEW (user_data);
1583 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
1585 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1587 if (!gtk_tree_model_get_iter (model, &iter, treepath))
1590 gtk_tree_model_get (model, &iter,
1591 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
1594 g_signal_emit (G_OBJECT(self),
1595 signals[FOLDER_ACTIVATED_SIGNAL],
1598 g_object_unref (folder);
1602 modest_folder_view_get_selected (ModestFolderView *self)
1604 ModestFolderViewPrivate *priv;
1606 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW(self), NULL);
1608 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1609 if (priv->cur_folder_store)
1610 g_object_ref (priv->cur_folder_store);
1612 return priv->cur_folder_store;
1616 get_cmp_rows_type_pos (GObject *folder)
1618 /* Remote accounts -> Local account -> MMC account .*/
1621 if (TNY_IS_ACCOUNT (folder) &&
1622 modest_tny_account_is_virtual_local_folders (
1623 TNY_ACCOUNT (folder))) {
1625 } else if (TNY_IS_ACCOUNT (folder)) {
1626 TnyAccount *account = TNY_ACCOUNT (folder);
1627 const gchar *account_id = tny_account_get_id (account);
1628 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
1634 printf ("DEBUG: %s: unexpected type.\n", __FUNCTION__);
1635 return -1; /* Should never happen */
1640 get_cmp_subfolder_type_pos (TnyFolderType t)
1642 /* Inbox, Outbox, Drafts, Sent, User */
1646 case TNY_FOLDER_TYPE_INBOX:
1649 case TNY_FOLDER_TYPE_OUTBOX:
1652 case TNY_FOLDER_TYPE_DRAFTS:
1655 case TNY_FOLDER_TYPE_SENT:
1664 * This function orders the mail accounts according to these rules:
1665 * 1st - remote accounts
1666 * 2nd - local account
1670 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1674 gchar *name1 = NULL;
1675 gchar *name2 = NULL;
1676 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1677 TnyFolderType type2 = TNY_FOLDER_TYPE_UNKNOWN;
1678 GObject *folder1 = NULL;
1679 GObject *folder2 = NULL;
1681 gtk_tree_model_get (tree_model, iter1,
1682 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name1,
1683 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
1684 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder1,
1686 gtk_tree_model_get (tree_model, iter2,
1687 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name2,
1688 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type2,
1689 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder2,
1692 /* Return if we get no folder. This could happen when folder
1693 operations are happening. The model is updated after the
1694 folder copy/move actually occurs, so there could be
1695 situations where the model to be drawn is not correct */
1696 if (!folder1 || !folder2)
1699 if (type == TNY_FOLDER_TYPE_ROOT) {
1700 /* Compare the types, so that
1701 * Remote accounts -> Local account -> MMC account .*/
1702 const gint pos1 = get_cmp_rows_type_pos (folder1);
1703 const gint pos2 = get_cmp_rows_type_pos (folder2);
1704 /* printf ("DEBUG: %s:\n type1=%s, pos1=%d\n type2=%s, pos2=%d\n",
1705 __FUNCTION__, G_OBJECT_TYPE_NAME(folder1), pos1, G_OBJECT_TYPE_NAME(folder2), pos2); */
1708 else if (pos1 > pos2)
1711 /* Compare items of the same type: */
1713 TnyAccount *account1 = NULL;
1714 if (TNY_IS_ACCOUNT (folder1))
1715 account1 = TNY_ACCOUNT (folder1);
1717 TnyAccount *account2 = NULL;
1718 if (TNY_IS_ACCOUNT (folder2))
1719 account2 = TNY_ACCOUNT (folder2);
1721 const gchar *account_id = account1 ? tny_account_get_id (account1) : NULL;
1722 const gchar *account_id2 = account2 ? tny_account_get_id (account2) : NULL;
1724 if (!account_id && !account_id2) {
1726 } else if (!account_id) {
1728 } else if (!account_id2) {
1730 } else if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
1733 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
1737 gint cmp1 = 0, cmp2 = 0;
1738 /* get the parent to know if it's a local folder */
1741 gboolean has_parent;
1742 has_parent = gtk_tree_model_iter_parent (tree_model, &parent, iter1);
1744 GObject *parent_folder;
1745 TnyFolderType parent_type = TNY_FOLDER_TYPE_UNKNOWN;
1746 gtk_tree_model_get (tree_model, &parent,
1747 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &parent_type,
1748 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &parent_folder,
1750 if ((parent_type == TNY_FOLDER_TYPE_ROOT) &&
1751 TNY_IS_ACCOUNT (parent_folder)) {
1752 if (modest_tny_account_is_virtual_local_folders (TNY_ACCOUNT (parent_folder))) {
1753 cmp1 = get_cmp_subfolder_type_pos (modest_tny_folder_get_local_or_mmc_folder_type
1754 (TNY_FOLDER (folder1)));
1755 cmp2 = get_cmp_subfolder_type_pos (modest_tny_folder_get_local_or_mmc_folder_type
1756 (TNY_FOLDER (folder2)));
1757 } else if (modest_tny_account_is_memory_card_account (TNY_ACCOUNT (parent_folder))) {
1758 if (modest_local_folder_info_get_type (tny_folder_get_name (TNY_FOLDER (folder1))) == TNY_FOLDER_TYPE_ARCHIVE) {
1761 } else if (modest_local_folder_info_get_type (tny_folder_get_name (TNY_FOLDER (folder2))) == TNY_FOLDER_TYPE_ARCHIVE) {
1767 g_object_unref (parent_folder);
1770 /* if they are not local folders */
1772 cmp1 = get_cmp_subfolder_type_pos (tny_folder_get_folder_type (TNY_FOLDER (folder1)));
1773 cmp2 = get_cmp_subfolder_type_pos (tny_folder_get_folder_type (TNY_FOLDER (folder2)));
1777 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
1779 cmp = (cmp1 - cmp2);
1784 g_object_unref(G_OBJECT(folder1));
1786 g_object_unref(G_OBJECT(folder2));
1794 /*****************************************************************************/
1795 /* DRAG and DROP stuff */
1796 /*****************************************************************************/
1798 * This function fills the #GtkSelectionData with the row and the
1799 * model that has been dragged. It's called when this widget is a
1800 * source for dnd after the event drop happened
1803 on_drag_data_get (GtkWidget *widget, GdkDragContext *context, GtkSelectionData *selection_data,
1804 guint info, guint time, gpointer data)
1806 GtkTreeSelection *selection;
1807 GtkTreeModel *model;
1809 GtkTreePath *source_row;
1811 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1812 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
1814 source_row = gtk_tree_model_get_path (model, &iter);
1815 gtk_tree_set_row_drag_data (selection_data,
1819 gtk_tree_path_free (source_row);
1823 typedef struct _DndHelper {
1824 ModestFolderView *folder_view;
1825 gboolean delete_source;
1826 GtkTreePath *source_row;
1830 dnd_helper_destroyer (DndHelper *helper)
1832 /* Free the helper */
1833 gtk_tree_path_free (helper->source_row);
1834 g_slice_free (DndHelper, helper);
1838 xfer_folder_cb (ModestMailOperation *mail_op,
1839 TnyFolder *new_folder,
1843 /* Select the folder */
1844 modest_folder_view_select_folder (MODEST_FOLDER_VIEW (user_data),
1850 /* get the folder for the row the treepath refers to. */
1851 /* folder must be unref'd */
1852 static TnyFolderStore *
1853 tree_path_to_folder (GtkTreeModel *model, GtkTreePath *path)
1856 TnyFolderStore *folder = NULL;
1858 if (gtk_tree_model_get_iter (model,&iter, path))
1859 gtk_tree_model_get (model, &iter,
1860 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
1867 * This function is used by drag_data_received_cb to manage drag and
1868 * drop of a header, i.e, and drag from the header view to the folder
1872 drag_and_drop_from_header_view (GtkTreeModel *source_model,
1873 GtkTreeModel *dest_model,
1874 GtkTreePath *dest_row,
1875 GtkSelectionData *selection_data)
1877 TnyList *headers = NULL;
1878 TnyFolder *folder = NULL, *src_folder = NULL;
1879 TnyFolderType folder_type;
1880 GtkTreeIter source_iter, dest_iter;
1881 ModestWindowMgr *mgr = NULL;
1882 ModestWindow *main_win = NULL;
1883 gchar **uris, **tmp;
1885 /* Build the list of headers */
1886 mgr = modest_runtime_get_window_mgr ();
1887 headers = tny_simple_list_new ();
1888 uris = modest_dnd_selection_data_get_paths (selection_data);
1891 while (*tmp != NULL) {
1894 gboolean first = TRUE;
1897 path = gtk_tree_path_new_from_string (*tmp);
1898 gtk_tree_model_get_iter (source_model, &source_iter, path);
1899 gtk_tree_model_get (source_model, &source_iter,
1900 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1903 /* Do not enable d&d of headers already opened */
1904 if (!modest_window_mgr_find_registered_header(mgr, header, NULL))
1905 tny_list_append (headers, G_OBJECT (header));
1907 if (G_UNLIKELY (first)) {
1908 src_folder = tny_header_get_folder (header);
1912 /* Free and go on */
1913 gtk_tree_path_free (path);
1914 g_object_unref (header);
1919 /* This could happen ig we perform a d&d very quickly over the
1920 same row that row could dissapear because message is
1922 if (!TNY_IS_FOLDER (src_folder))
1925 /* Get the target folder */
1926 gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
1927 gtk_tree_model_get (dest_model, &dest_iter,
1928 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
1931 if (!folder || !TNY_IS_FOLDER(folder)) {
1932 /* g_warning ("%s: not a valid target folder (%p)", __FUNCTION__, folder); */
1936 folder_type = modest_tny_folder_guess_folder_type (folder);
1937 if (folder_type == TNY_FOLDER_TYPE_INVALID) {
1938 /* g_warning ("%s: invalid target folder", __FUNCTION__); */
1939 goto cleanup; /* cannot move messages there */
1942 if (modest_tny_folder_get_rules((TNY_FOLDER(folder))) & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE) {
1943 /* g_warning ("folder not writable"); */
1944 goto cleanup; /* verboten! */
1947 /* Ask for confirmation to move */
1948 main_win = modest_window_mgr_get_main_window (mgr, FALSE); /* don't create */
1950 g_warning ("%s: BUG: no main window found", __FUNCTION__);
1954 /* Transfer messages */
1955 modest_ui_actions_transfer_messages_helper (GTK_WINDOW (main_win), src_folder,
1960 if (G_IS_OBJECT (src_folder))
1961 g_object_unref (src_folder);
1962 if (G_IS_OBJECT(folder))
1963 g_object_unref (G_OBJECT (folder));
1964 if (G_IS_OBJECT(headers))
1965 g_object_unref (headers);
1969 TnyFolderStore *src_folder;
1970 TnyFolderStore *dst_folder;
1971 ModestFolderView *folder_view;
1976 dnd_folder_info_destroyer (DndFolderInfo *info)
1978 if (info->src_folder)
1979 g_object_unref (info->src_folder);
1980 if (info->dst_folder)
1981 g_object_unref (info->dst_folder);
1982 g_slice_free (DndFolderInfo, info);
1986 dnd_on_connection_failed_destroyer (DndFolderInfo *info,
1987 GtkWindow *parent_window,
1988 TnyAccount *account)
1991 modest_ui_actions_on_account_connection_error (parent_window, account);
1993 /* Free the helper & info */
1994 dnd_helper_destroyer (info->helper);
1995 dnd_folder_info_destroyer (info);
1999 drag_and_drop_from_folder_view_src_folder_performer (gboolean canceled,
2001 GtkWindow *parent_window,
2002 TnyAccount *account,
2005 DndFolderInfo *info = NULL;
2006 ModestMailOperation *mail_op;
2008 info = (DndFolderInfo *) user_data;
2010 if (err || canceled) {
2011 dnd_on_connection_failed_destroyer (info, parent_window, account);
2015 /* Do the mail operation */
2016 mail_op = modest_mail_operation_new_with_error_handling ((GObject *) parent_window,
2017 modest_ui_actions_move_folder_error_handler,
2018 info->src_folder, NULL);
2020 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
2023 /* Transfer the folder */
2024 modest_mail_operation_xfer_folder (mail_op,
2025 TNY_FOLDER (info->src_folder),
2027 info->helper->delete_source,
2029 info->helper->folder_view);
2032 g_object_unref (G_OBJECT (mail_op));
2033 dnd_helper_destroyer (info->helper);
2034 dnd_folder_info_destroyer (info);
2039 drag_and_drop_from_folder_view_dst_folder_performer (gboolean canceled,
2041 GtkWindow *parent_window,
2042 TnyAccount *account,
2045 DndFolderInfo *info = NULL;
2047 info = (DndFolderInfo *) user_data;
2049 if (err || canceled) {
2050 dnd_on_connection_failed_destroyer (info, parent_window, account);
2054 /* Connect to source folder and perform the copy/move */
2055 modest_platform_connect_if_remote_and_perform (NULL, TRUE,
2057 drag_and_drop_from_folder_view_src_folder_performer,
2062 * This function is used by drag_data_received_cb to manage drag and
2063 * drop of a folder, i.e, and drag from the folder view to the same
2067 drag_and_drop_from_folder_view (GtkTreeModel *source_model,
2068 GtkTreeModel *dest_model,
2069 GtkTreePath *dest_row,
2070 GtkSelectionData *selection_data,
2073 GtkTreeIter dest_iter, iter;
2074 TnyFolderStore *dest_folder = NULL;
2075 TnyFolderStore *folder = NULL;
2076 gboolean forbidden = FALSE;
2078 DndFolderInfo *info = NULL;
2080 win = modest_window_mgr_get_main_window (modest_runtime_get_window_mgr(), FALSE); /* don't create */
2082 g_warning ("%s: BUG: no main window", __FUNCTION__);
2083 dnd_helper_destroyer (helper);
2088 /* check the folder rules for the destination */
2089 folder = tree_path_to_folder (dest_model, dest_row);
2090 if (TNY_IS_FOLDER(folder)) {
2091 ModestTnyFolderRules rules =
2092 modest_tny_folder_get_rules (TNY_FOLDER (folder));
2093 forbidden = rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE;
2094 } else if (TNY_IS_FOLDER_STORE(folder)) {
2095 /* enable local root as destination for folders */
2096 if (!MODEST_IS_TNY_LOCAL_FOLDERS_ACCOUNT (folder) &&
2097 !modest_tny_account_is_memory_card_account (TNY_ACCOUNT (folder)))
2100 g_object_unref (folder);
2103 /* check the folder rules for the source */
2104 folder = tree_path_to_folder (source_model, helper->source_row);
2105 if (TNY_IS_FOLDER(folder)) {
2106 ModestTnyFolderRules rules =
2107 modest_tny_folder_get_rules (TNY_FOLDER (folder));
2108 forbidden = rules & MODEST_FOLDER_RULES_FOLDER_NON_MOVEABLE;
2111 g_object_unref (folder);
2115 /* Check if the drag is possible */
2116 if (forbidden || !gtk_tree_path_compare (helper->source_row, dest_row)) {
2118 modest_platform_run_information_dialog ((GtkWindow *) win,
2119 _("mail_in_ui_folder_move_target_error"),
2121 /* Restore the previous selection */
2122 folder = tree_path_to_folder (source_model, helper->source_row);
2124 if (TNY_IS_FOLDER (folder))
2125 modest_folder_view_select_folder (helper->folder_view,
2126 TNY_FOLDER (folder), FALSE);
2127 g_object_unref (folder);
2129 dnd_helper_destroyer (helper);
2134 gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
2135 gtk_tree_model_get (dest_model, &dest_iter,
2136 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
2138 gtk_tree_model_get_iter (source_model, &iter, helper->source_row);
2139 gtk_tree_model_get (source_model, &iter,
2140 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
2143 /* Create the info for the performer */
2144 info = g_slice_new0 (DndFolderInfo);
2145 info->src_folder = g_object_ref (folder);
2146 info->dst_folder = g_object_ref (dest_folder);
2147 info->helper = helper;
2149 /* Connect to the destination folder and perform the copy/move */
2150 modest_platform_connect_if_remote_and_perform (GTK_WINDOW (win), TRUE,
2152 drag_and_drop_from_folder_view_dst_folder_performer,
2156 g_object_unref (dest_folder);
2157 g_object_unref (folder);
2161 * This function receives the data set by the "drag-data-get" signal
2162 * handler. This information comes within the #GtkSelectionData. This
2163 * function will manage both the drags of folders of the treeview and
2164 * drags of headers of the header view widget.
2167 on_drag_data_received (GtkWidget *widget,
2168 GdkDragContext *context,
2171 GtkSelectionData *selection_data,
2176 GtkWidget *source_widget;
2177 GtkTreeModel *dest_model, *source_model;
2178 GtkTreePath *source_row, *dest_row;
2179 GtkTreeViewDropPosition pos;
2180 gboolean delete_source = FALSE;
2181 gboolean success = FALSE;
2183 /* Do not allow further process */
2184 g_signal_stop_emission_by_name (widget, "drag-data-received");
2185 source_widget = gtk_drag_get_source_widget (context);
2187 /* Get the action */
2188 if (context->action == GDK_ACTION_MOVE) {
2189 delete_source = TRUE;
2191 /* Notify that there is no folder selected. We need to
2192 do this in order to update the headers view (and
2193 its monitors, because when moving, the old folder
2194 won't longer exist. We can not wait for the end of
2195 the operation, because the operation won't start if
2196 the folder is in use */
2197 if (source_widget == widget) {
2198 GtkTreeSelection *sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
2199 gtk_tree_selection_unselect_all (sel);
2203 /* Check if the get_data failed */
2204 if (selection_data == NULL || selection_data->length < 0)
2207 /* Select the destination model */
2208 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
2210 /* Get the path to the destination row. Can not call
2211 gtk_tree_view_get_drag_dest_row() because the source row
2212 is not selected anymore */
2213 gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget), x, y,
2216 /* Only allow drops IN other rows */
2218 pos == GTK_TREE_VIEW_DROP_BEFORE ||
2219 pos == GTK_TREE_VIEW_DROP_AFTER)
2223 /* Drags from the header view */
2224 if (source_widget != widget) {
2225 source_model = gtk_tree_view_get_model (GTK_TREE_VIEW (source_widget));
2227 drag_and_drop_from_header_view (source_model,
2232 DndHelper *helper = NULL;
2234 /* Get the source model and row */
2235 gtk_tree_get_row_drag_data (selection_data,
2239 /* Create the helper */
2240 helper = g_slice_new0 (DndHelper);
2241 helper->delete_source = delete_source;
2242 helper->source_row = gtk_tree_path_copy (source_row);
2243 helper->folder_view = MODEST_FOLDER_VIEW (widget);
2245 drag_and_drop_from_folder_view (source_model,
2251 gtk_tree_path_free (source_row);
2255 gtk_tree_path_free (dest_row);
2258 /* Finish the drag and drop */
2259 gtk_drag_finish (context, success, FALSE, time);
2263 * We define a "drag-drop" signal handler because we do not want to
2264 * use the default one, because the default one always calls
2265 * gtk_drag_finish and we prefer to do it in the "drag-data-received"
2266 * signal handler, because there we have all the information available
2267 * to know if the dnd was a success or not.
2270 drag_drop_cb (GtkWidget *widget,
2271 GdkDragContext *context,
2279 if (!context->targets)
2282 /* Check if we're dragging a folder row */
2283 target = gtk_drag_dest_find_target (widget, context, NULL);
2285 /* Request the data from the source. */
2286 gtk_drag_get_data(widget, context, target, time);
2292 * This function expands a node of a tree view if it's not expanded
2293 * yet. Not sure why it needs the threads stuff, but gtk+`example code
2294 * does that, so that's why they're here.
2297 expand_row_timeout (gpointer data)
2299 GtkTreeView *tree_view = data;
2300 GtkTreePath *dest_path = NULL;
2301 GtkTreeViewDropPosition pos;
2302 gboolean result = FALSE;
2304 gdk_threads_enter ();
2306 gtk_tree_view_get_drag_dest_row (tree_view,
2311 (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER ||
2312 pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)) {
2313 gtk_tree_view_expand_row (tree_view, dest_path, FALSE);
2314 gtk_tree_path_free (dest_path);
2318 gtk_tree_path_free (dest_path);
2323 gdk_threads_leave ();
2329 * This function is called whenever the pointer is moved over a widget
2330 * while dragging some data. It installs a timeout that will expand a
2331 * node of the treeview if not expanded yet. This function also calls
2332 * gdk_drag_status in order to set the suggested action that will be
2333 * used by the "drag-data-received" signal handler to know if we
2334 * should do a move or just a copy of the data.
2337 on_drag_motion (GtkWidget *widget,
2338 GdkDragContext *context,
2344 GtkTreeViewDropPosition pos;
2345 GtkTreePath *dest_row;
2346 GtkTreeModel *dest_model;
2347 ModestFolderViewPrivate *priv;
2348 GdkDragAction suggested_action;
2349 gboolean valid_location = FALSE;
2350 TnyFolderStore *folder = NULL;
2352 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
2354 if (priv->timer_expander != 0) {
2355 g_source_remove (priv->timer_expander);
2356 priv->timer_expander = 0;
2359 gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
2364 /* Do not allow drops between folders */
2366 pos == GTK_TREE_VIEW_DROP_BEFORE ||
2367 pos == GTK_TREE_VIEW_DROP_AFTER) {
2368 gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW (widget), NULL, 0);
2369 gdk_drag_status(context, 0, time);
2370 valid_location = FALSE;
2373 valid_location = TRUE;
2376 /* Check that the destination folder is writable */
2377 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
2378 folder = tree_path_to_folder (dest_model, dest_row);
2379 if (folder && TNY_IS_FOLDER (folder)) {
2380 ModestTnyFolderRules rules = modest_tny_folder_get_rules(TNY_FOLDER (folder));
2382 if (rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE) {
2383 valid_location = FALSE;
2388 /* Expand the selected row after 1/2 second */
2389 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), dest_row)) {
2390 priv->timer_expander = g_timeout_add (500, expand_row_timeout, widget);
2392 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), dest_row, pos);
2394 /* Select the desired action. By default we pick MOVE */
2395 suggested_action = GDK_ACTION_MOVE;
2397 if (context->actions == GDK_ACTION_COPY)
2398 gdk_drag_status(context, GDK_ACTION_COPY, time);
2399 else if (context->actions == GDK_ACTION_MOVE)
2400 gdk_drag_status(context, GDK_ACTION_MOVE, time);
2401 else if (context->actions & suggested_action)
2402 gdk_drag_status(context, suggested_action, time);
2404 gdk_drag_status(context, GDK_ACTION_DEFAULT, time);
2408 g_object_unref (folder);
2410 gtk_tree_path_free (dest_row);
2412 g_signal_stop_emission_by_name (widget, "drag-motion");
2414 return valid_location;
2418 * This function sets the treeview as a source and a target for dnd
2419 * events. It also connects all the requirede signals.
2422 setup_drag_and_drop (GtkTreeView *self)
2424 /* Set up the folder view as a dnd destination. Set only the
2425 highlight flag, otherwise gtk will have a different
2427 #ifdef MODEST_TOOLKIT_HILDON2
2430 gtk_drag_dest_set (GTK_WIDGET (self),
2431 GTK_DEST_DEFAULT_HIGHLIGHT,
2432 folder_view_drag_types,
2433 G_N_ELEMENTS (folder_view_drag_types),
2434 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2436 g_signal_connect (G_OBJECT (self),
2437 "drag_data_received",
2438 G_CALLBACK (on_drag_data_received),
2442 /* Set up the treeview as a dnd source */
2443 gtk_drag_source_set (GTK_WIDGET (self),
2445 folder_view_drag_types,
2446 G_N_ELEMENTS (folder_view_drag_types),
2447 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2449 g_signal_connect (G_OBJECT (self),
2451 G_CALLBACK (on_drag_motion),
2454 g_signal_connect (G_OBJECT (self),
2456 G_CALLBACK (on_drag_data_get),
2459 g_signal_connect (G_OBJECT (self),
2461 G_CALLBACK (drag_drop_cb),
2466 * This function manages the navigation through the folders using the
2467 * keyboard or the hardware keys in the device
2470 on_key_pressed (GtkWidget *self,
2474 GtkTreeSelection *selection;
2476 GtkTreeModel *model;
2477 gboolean retval = FALSE;
2479 /* Up and Down are automatically managed by the treeview */
2480 if (event->keyval == GDK_Return) {
2481 /* Expand/Collapse the selected row */
2482 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2483 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
2486 path = gtk_tree_model_get_path (model, &iter);
2488 if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (self), path))
2489 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
2491 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
2492 gtk_tree_path_free (path);
2494 /* No further processing */
2502 * We listen to the changes in the local folder account name key,
2503 * because we want to show the right name in the view. The local
2504 * folder account name corresponds to the device name in the Maemo
2505 * version. We do this because we do not want to query gconf on each
2506 * tree view refresh. It's better to cache it and change whenever
2510 on_configuration_key_changed (ModestConf* conf,
2512 ModestConfEvent event,
2513 ModestConfNotificationId id,
2514 ModestFolderView *self)
2516 ModestFolderViewPrivate *priv;
2519 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
2520 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2522 if (!strcmp (key, MODEST_CONF_DEVICE_NAME)) {
2523 g_free (priv->local_account_name);
2525 if (event == MODEST_CONF_EVENT_KEY_UNSET)
2526 priv->local_account_name = g_strdup (MODEST_LOCAL_FOLDERS_DEFAULT_DISPLAY_NAME);
2528 priv->local_account_name = modest_conf_get_string (modest_runtime_get_conf(),
2529 MODEST_CONF_DEVICE_NAME, NULL);
2531 /* Force a redraw */
2532 #if GTK_CHECK_VERSION(2, 8, 0)
2533 GtkTreeViewColumn * tree_column;
2535 tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self),
2536 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN);
2537 gtk_tree_view_column_queue_resize (tree_column);
2539 gtk_widget_queue_draw (GTK_WIDGET (self));
2545 modest_folder_view_set_style (ModestFolderView *self,
2546 ModestFolderViewStyle style)
2548 ModestFolderViewPrivate *priv;
2550 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2551 g_return_if_fail (style == MODEST_FOLDER_VIEW_STYLE_SHOW_ALL ||
2552 style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE);
2554 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2557 priv->style = style;
2561 modest_folder_view_set_account_id_of_visible_server_account (ModestFolderView *self,
2562 const gchar *account_id)
2564 ModestFolderViewPrivate *priv;
2565 GtkTreeModel *model;
2567 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2569 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2571 /* This will be used by the filter_row callback,
2572 * to decided which rows to show: */
2573 if (priv->visible_account_id) {
2574 g_free (priv->visible_account_id);
2575 priv->visible_account_id = NULL;
2578 priv->visible_account_id = g_strdup (account_id);
2581 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2582 if (GTK_IS_TREE_MODEL_FILTER (model))
2583 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2585 /* Save settings to gconf */
2586 modest_widget_memory_save (modest_runtime_get_conf (), G_OBJECT(self),
2587 MODEST_CONF_FOLDER_VIEW_KEY);
2591 modest_folder_view_get_account_id_of_visible_server_account (ModestFolderView *self)
2593 ModestFolderViewPrivate *priv;
2595 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW(self), NULL);
2597 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2599 return (const gchar *) priv->visible_account_id;
2603 find_inbox_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *inbox_iter)
2607 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2609 gtk_tree_model_get (model, iter,
2610 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN,
2613 gboolean result = FALSE;
2614 if (type == TNY_FOLDER_TYPE_INBOX) {
2618 *inbox_iter = *iter;
2622 if (gtk_tree_model_iter_children (model, &child, iter)) {
2623 if (find_inbox_iter (model, &child, inbox_iter))
2627 } while (gtk_tree_model_iter_next (model, iter));
2636 modest_folder_view_select_first_inbox_or_local (ModestFolderView *self)
2638 GtkTreeModel *model;
2639 GtkTreeIter iter, inbox_iter;
2640 GtkTreeSelection *sel;
2641 GtkTreePath *path = NULL;
2643 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2645 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2649 expand_root_items (self);
2650 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2652 if (!gtk_tree_model_get_iter_first (model, &iter)) {
2653 g_warning ("%s: model is empty", __FUNCTION__);
2657 if (find_inbox_iter (model, &iter, &inbox_iter))
2658 path = gtk_tree_model_get_path (model, &inbox_iter);
2660 path = gtk_tree_path_new_first ();
2662 /* Select the row and free */
2663 gtk_tree_view_set_cursor (GTK_TREE_VIEW (self), path, NULL, FALSE);
2664 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (self), path, NULL, FALSE, 0.0, 0.0);
2665 gtk_tree_path_free (path);
2668 gtk_widget_grab_focus (GTK_WIDGET(self));
2674 find_folder_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *folder_iter,
2679 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2680 TnyFolder* a_folder;
2683 gtk_tree_model_get (model, iter,
2684 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &a_folder,
2685 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name,
2686 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
2690 if (folder == a_folder) {
2691 g_object_unref (a_folder);
2692 *folder_iter = *iter;
2695 g_object_unref (a_folder);
2697 if (gtk_tree_model_iter_children (model, &child, iter)) {
2698 if (find_folder_iter (model, &child, folder_iter, folder))
2702 } while (gtk_tree_model_iter_next (model, iter));
2709 on_row_inserted_maybe_select_folder (GtkTreeModel *tree_model,
2712 ModestFolderView *self)
2714 ModestFolderViewPrivate *priv = NULL;
2715 GtkTreeSelection *sel;
2716 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2717 GObject *instance = NULL;
2719 if (!MODEST_IS_FOLDER_VIEW(self))
2722 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2724 priv->reexpand = TRUE;
2726 gtk_tree_model_get (tree_model, iter,
2727 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
2728 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
2730 if (type == TNY_FOLDER_TYPE_INBOX && priv->folder_to_select == NULL) {
2731 priv->folder_to_select = g_object_ref (instance);
2733 g_object_unref (instance);
2735 if (priv->folder_to_select) {
2737 if (!modest_folder_view_select_folder (self, priv->folder_to_select,
2740 path = gtk_tree_model_get_path (tree_model, iter);
2741 gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
2743 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2745 gtk_tree_selection_select_iter (sel, iter);
2746 gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
2748 gtk_tree_path_free (path);
2752 modest_folder_view_disable_next_folder_selection (self);
2754 /* Refilter the model */
2755 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (tree_model));
2761 modest_folder_view_disable_next_folder_selection (ModestFolderView *self)
2763 ModestFolderViewPrivate *priv;
2765 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2767 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2769 if (priv->folder_to_select)
2770 g_object_unref(priv->folder_to_select);
2772 priv->folder_to_select = NULL;
2776 modest_folder_view_select_folder (ModestFolderView *self, TnyFolder *folder,
2777 gboolean after_change)
2779 GtkTreeModel *model;
2780 GtkTreeIter iter, folder_iter;
2781 GtkTreeSelection *sel;
2782 ModestFolderViewPrivate *priv = NULL;
2784 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW (self), FALSE);
2785 g_return_val_if_fail (folder && TNY_IS_FOLDER (folder), FALSE);
2787 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2790 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2791 gtk_tree_selection_unselect_all (sel);
2793 if (priv->folder_to_select)
2794 g_object_unref(priv->folder_to_select);
2795 priv->folder_to_select = TNY_FOLDER(g_object_ref(folder));
2799 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2804 /* Refilter the model, before selecting the folder */
2805 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2807 if (!gtk_tree_model_get_iter_first (model, &iter)) {
2808 g_warning ("%s: model is empty", __FUNCTION__);
2812 if (find_folder_iter (model, &iter, &folder_iter, folder)) {
2815 path = gtk_tree_model_get_path (model, &folder_iter);
2816 gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
2818 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2819 gtk_tree_selection_select_iter (sel, &folder_iter);
2820 gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
2822 gtk_tree_path_free (path);
2830 modest_folder_view_copy_selection (ModestFolderView *self)
2832 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2834 /* Copy selection */
2835 _clipboard_set_selected_data (self, FALSE);
2839 modest_folder_view_cut_selection (ModestFolderView *folder_view)
2841 ModestFolderViewPrivate *priv = NULL;
2842 GtkTreeModel *model = NULL;
2843 const gchar **hidding = NULL;
2844 guint i, n_selected;
2846 g_return_if_fail (folder_view && MODEST_IS_FOLDER_VIEW (folder_view));
2847 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
2849 /* Copy selection */
2850 if (!_clipboard_set_selected_data (folder_view, TRUE))
2853 /* Get hidding ids */
2854 hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected);
2856 /* Clear hidding array created by previous cut operation */
2857 _clear_hidding_filter (MODEST_FOLDER_VIEW (folder_view));
2859 /* Copy hidding array */
2860 priv->n_selected = n_selected;
2861 priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
2862 for (i=0; i < n_selected; i++)
2863 priv->hidding_ids[i] = g_strdup(hidding[i]);
2865 /* Hide cut folders */
2866 model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
2867 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2871 modest_folder_view_copy_model (ModestFolderView *folder_view_src,
2872 ModestFolderView *folder_view_dst)
2874 GtkTreeModel *filter_model = NULL;
2875 GtkTreeModel *model = NULL;
2876 GtkTreeModel *new_filter_model = NULL;
2878 g_return_if_fail (folder_view_src && MODEST_IS_FOLDER_VIEW (folder_view_src));
2879 g_return_if_fail (folder_view_dst && MODEST_IS_FOLDER_VIEW (folder_view_dst));
2882 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view_src));
2883 model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER(filter_model));
2885 /* Build new filter model */
2886 new_filter_model = gtk_tree_model_filter_new (model, NULL);
2887 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (new_filter_model),
2891 /* Set copied model */
2892 gtk_tree_view_set_model (GTK_TREE_VIEW (folder_view_dst), new_filter_model);
2893 g_signal_connect (G_OBJECT(new_filter_model), "row-inserted",
2894 (GCallback) on_row_inserted_maybe_select_folder, folder_view_dst);
2897 g_object_unref (new_filter_model);
2901 modest_folder_view_show_non_move_folders (ModestFolderView *folder_view,
2904 GtkTreeModel *model = NULL;
2905 ModestFolderViewPrivate* priv;
2907 g_return_if_fail (folder_view && MODEST_IS_FOLDER_VIEW (folder_view));
2909 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
2910 priv->show_non_move = show;
2911 /* modest_folder_view_update_model(folder_view, */
2912 /* TNY_ACCOUNT_STORE(modest_runtime_get_account_store())); */
2914 /* Hide special folders */
2915 model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
2916 if (GTK_IS_TREE_MODEL_FILTER (model)) {
2917 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2921 /* Returns FALSE if it did not selected anything */
2923 _clipboard_set_selected_data (ModestFolderView *folder_view,
2926 ModestFolderViewPrivate *priv = NULL;
2927 TnyFolderStore *folder = NULL;
2928 gboolean retval = FALSE;
2930 g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (folder_view), FALSE);
2931 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
2933 /* Set selected data on clipboard */
2934 g_return_val_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard), FALSE);
2935 folder = modest_folder_view_get_selected (folder_view);
2937 /* Do not allow to select an account */
2938 if (TNY_IS_FOLDER (folder)) {
2939 modest_email_clipboard_set_data (priv->clipboard, TNY_FOLDER(folder), NULL, delete);
2944 g_object_unref (folder);
2950 _clear_hidding_filter (ModestFolderView *folder_view)
2952 ModestFolderViewPrivate *priv;
2955 g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view));
2956 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
2958 if (priv->hidding_ids != NULL) {
2959 for (i=0; i < priv->n_selected; i++)
2960 g_free (priv->hidding_ids[i]);
2961 g_free(priv->hidding_ids);
2967 on_display_name_changed (ModestAccountMgr *mgr,
2968 const gchar *account,
2971 ModestFolderView *self;
2973 self = MODEST_FOLDER_VIEW (user_data);
2975 /* Force a redraw */
2976 #if GTK_CHECK_VERSION(2, 8, 0)
2977 GtkTreeViewColumn * tree_column;
2979 tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self),
2980 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN);
2981 gtk_tree_view_column_queue_resize (tree_column);
2983 gtk_widget_queue_draw (GTK_WIDGET (self));
2988 modest_folder_view_set_cell_style (ModestFolderView *self,
2989 ModestFolderViewCellStyle cell_style)
2991 ModestFolderViewPrivate *priv = NULL;
2993 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
2994 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2996 priv->cell_style = cell_style;
2998 gtk_widget_queue_draw (GTK_WIDGET (self));