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));
633 case TNY_FOLDER_TYPE_INVALID:
634 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
637 case TNY_FOLDER_TYPE_ROOT:
638 if (TNY_IS_ACCOUNT (instance)) {
640 if (modest_tny_account_is_virtual_local_folders (TNY_ACCOUNT (instance))) {
641 retval = get_composite_icons (MODEST_FOLDER_ICON_LOCAL_FOLDERS,
644 &avirt_pixbuf_close);
646 const gchar *account_id = tny_account_get_id (TNY_ACCOUNT (instance));
648 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
649 retval = get_composite_icons (MODEST_FOLDER_ICON_MMC,
654 retval = get_composite_icons (MODEST_FOLDER_ICON_ACCOUNT,
657 &anorm_pixbuf_close);
662 case TNY_FOLDER_TYPE_INBOX:
663 retval = get_composite_icons (MODEST_FOLDER_ICON_INBOX,
666 &inbox_pixbuf_close);
668 case TNY_FOLDER_TYPE_OUTBOX:
669 retval = get_composite_icons (MODEST_FOLDER_ICON_OUTBOX,
672 &outbox_pixbuf_close);
674 case TNY_FOLDER_TYPE_JUNK:
675 retval = get_composite_icons (MODEST_FOLDER_ICON_JUNK,
680 case TNY_FOLDER_TYPE_SENT:
681 retval = get_composite_icons (MODEST_FOLDER_ICON_SENT,
686 case TNY_FOLDER_TYPE_TRASH:
687 retval = get_composite_icons (MODEST_FOLDER_ICON_TRASH,
690 &trash_pixbuf_close);
692 case TNY_FOLDER_TYPE_DRAFTS:
693 retval = get_composite_icons (MODEST_FOLDER_ICON_DRAFTS,
696 &draft_pixbuf_close);
698 case TNY_FOLDER_TYPE_NORMAL:
700 retval = get_composite_icons (MODEST_FOLDER_ICON_NORMAL,
703 &normal_pixbuf_close);
711 free_pixbufs (ThreePixbufs *pixbufs)
714 g_object_unref (pixbufs->pixbuf);
715 if (pixbufs->pixbuf_open)
716 g_object_unref (pixbufs->pixbuf_open);
717 if (pixbufs->pixbuf_close)
718 g_object_unref (pixbufs->pixbuf_close);
719 g_slice_free (ThreePixbufs, pixbufs);
723 icon_cell_data (GtkTreeViewColumn *column,
724 GtkCellRenderer *renderer,
725 GtkTreeModel *tree_model,
729 GObject *rendobj = NULL, *instance = NULL;
730 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
731 gboolean has_children;
732 ThreePixbufs *pixbufs;
734 rendobj = (GObject *) renderer;
736 gtk_tree_model_get (tree_model, iter,
737 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
738 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
744 has_children = gtk_tree_model_iter_has_child (tree_model, iter);
745 pixbufs = get_folder_icons (type, instance);
746 g_object_unref (instance);
749 g_object_set (rendobj, "pixbuf", pixbufs->pixbuf, NULL);
752 g_object_set (rendobj, "pixbuf-expander-open", pixbufs->pixbuf_open, NULL);
753 g_object_set (rendobj, "pixbuf-expander-closed", pixbufs->pixbuf_close, NULL);
756 free_pixbufs (pixbufs);
760 add_columns (GtkWidget *treeview)
762 GtkTreeViewColumn *column;
763 GtkCellRenderer *renderer;
764 GtkTreeSelection *sel;
767 column = gtk_tree_view_column_new ();
769 /* Set icon and text render function */
770 renderer = gtk_cell_renderer_pixbuf_new();
771 gtk_tree_view_column_pack_start (column, renderer, FALSE);
772 gtk_tree_view_column_set_cell_data_func(column, renderer,
773 icon_cell_data, treeview, NULL);
775 renderer = gtk_cell_renderer_text_new();
776 g_object_set (renderer, "ellipsize", PANGO_ELLIPSIZE_END,
777 "ellipsize-set", TRUE, NULL);
778 gtk_tree_view_column_pack_start (column, renderer, TRUE);
779 gtk_tree_view_column_set_cell_data_func(column, renderer,
780 text_cell_data, treeview, NULL);
782 /* Set selection mode */
783 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(treeview));
784 gtk_tree_selection_set_mode (sel, GTK_SELECTION_SINGLE);
786 /* Set treeview appearance */
787 gtk_tree_view_column_set_spacing (column, 2);
788 gtk_tree_view_column_set_resizable (column, TRUE);
789 gtk_tree_view_column_set_fixed_width (column, TRUE);
790 gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(treeview), FALSE);
791 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(treeview), FALSE);
794 gtk_tree_view_append_column (GTK_TREE_VIEW(treeview),column);
798 modest_folder_view_init (ModestFolderView *obj)
800 ModestFolderViewPrivate *priv;
803 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
805 priv->timer_expander = 0;
806 priv->account_store = NULL;
808 priv->style = MODEST_FOLDER_VIEW_STYLE_SHOW_ALL;
809 priv->cur_folder_store = NULL;
810 priv->visible_account_id = NULL;
811 priv->folder_to_select = NULL;
813 priv->reexpand = TRUE;
815 /* Initialize the local account name */
816 conf = modest_runtime_get_conf();
817 priv->local_account_name = modest_conf_get_string (conf, MODEST_CONF_DEVICE_NAME, NULL);
819 /* Init email clipboard */
820 priv->clipboard = modest_runtime_get_email_clipboard ();
821 priv->hidding_ids = NULL;
822 priv->n_selected = 0;
823 priv->reselect = FALSE;
824 priv->show_non_move = TRUE;
827 add_columns (GTK_WIDGET (obj));
829 /* Setup drag and drop */
830 setup_drag_and_drop (GTK_TREE_VIEW(obj));
832 /* Connect signals */
833 g_signal_connect (G_OBJECT (obj),
835 G_CALLBACK (on_key_pressed), NULL);
837 priv->display_name_changed_signal =
838 g_signal_connect (modest_runtime_get_account_mgr (),
839 "display_name_changed",
840 G_CALLBACK (on_display_name_changed),
844 * Track changes in the local account name (in the device it
845 * will be the device name)
847 priv->conf_key_signal = g_signal_connect (G_OBJECT(conf),
849 G_CALLBACK(on_configuration_key_changed),
854 tny_account_store_view_init (gpointer g, gpointer iface_data)
856 TnyAccountStoreViewIface *klass = (TnyAccountStoreViewIface *)g;
858 klass->set_account_store = modest_folder_view_set_account_store;
862 modest_folder_view_finalize (GObject *obj)
864 ModestFolderViewPrivate *priv;
865 GtkTreeSelection *sel;
867 g_return_if_fail (obj);
869 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
871 if (priv->timer_expander != 0) {
872 g_source_remove (priv->timer_expander);
873 priv->timer_expander = 0;
876 if (priv->account_store) {
877 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
878 priv->account_inserted_signal);
879 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
880 priv->account_removed_signal);
881 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
882 priv->account_changed_signal);
883 g_object_unref (G_OBJECT(priv->account_store));
884 priv->account_store = NULL;
887 if (g_signal_handler_is_connected (modest_runtime_get_account_mgr (),
888 priv->display_name_changed_signal)) {
889 g_signal_handler_disconnect (modest_runtime_get_account_mgr (),
890 priv->display_name_changed_signal);
891 priv->display_name_changed_signal = 0;
895 g_object_unref (G_OBJECT (priv->query));
899 if (priv->folder_to_select) {
900 g_object_unref (G_OBJECT(priv->folder_to_select));
901 priv->folder_to_select = NULL;
904 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(obj));
906 g_signal_handler_disconnect (G_OBJECT(sel), priv->changed_signal);
908 g_free (priv->local_account_name);
909 g_free (priv->visible_account_id);
911 if (priv->conf_key_signal) {
912 g_signal_handler_disconnect (modest_runtime_get_conf (),
913 priv->conf_key_signal);
914 priv->conf_key_signal = 0;
917 if (priv->cur_folder_store) {
918 g_object_unref (priv->cur_folder_store);
919 priv->cur_folder_store = NULL;
922 /* Clear hidding array created by cut operation */
923 _clear_hidding_filter (MODEST_FOLDER_VIEW (obj));
925 G_OBJECT_CLASS(parent_class)->finalize (obj);
930 modest_folder_view_set_account_store (TnyAccountStoreView *self, TnyAccountStore *account_store)
932 ModestFolderViewPrivate *priv;
935 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
936 g_return_if_fail (TNY_IS_ACCOUNT_STORE (account_store));
938 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
939 device = tny_account_store_get_device (account_store);
941 if (G_UNLIKELY (priv->account_store)) {
943 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
944 priv->account_inserted_signal))
945 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
946 priv->account_inserted_signal);
947 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
948 priv->account_removed_signal))
949 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
950 priv->account_removed_signal);
951 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
952 priv->account_changed_signal))
953 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
954 priv->account_changed_signal);
955 g_object_unref (G_OBJECT (priv->account_store));
958 priv->account_store = g_object_ref (G_OBJECT (account_store));
960 priv->account_removed_signal =
961 g_signal_connect (G_OBJECT(account_store), "account_removed",
962 G_CALLBACK (on_account_removed), self);
964 priv->account_inserted_signal =
965 g_signal_connect (G_OBJECT(account_store), "account_inserted",
966 G_CALLBACK (on_account_inserted), self);
968 priv->account_changed_signal =
969 g_signal_connect (G_OBJECT(account_store), "account_changed",
970 G_CALLBACK (on_account_changed), self);
972 modest_folder_view_update_model (MODEST_FOLDER_VIEW (self), account_store);
973 priv->reselect = FALSE;
974 modest_folder_view_select_first_inbox_or_local (MODEST_FOLDER_VIEW (self));
976 g_object_unref (G_OBJECT (device));
980 on_account_inserted (TnyAccountStore *account_store,
984 ModestFolderViewPrivate *priv;
985 GtkTreeModel *sort_model, *filter_model;
987 /* Ignore transport account insertions, we're not showing them
988 in the folder view */
989 if (TNY_IS_TRANSPORT_ACCOUNT (account))
992 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (user_data);
995 /* If we're adding a new account, and there is no previous
996 one, we need to select the visible server account */
997 if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE &&
998 !priv->visible_account_id)
999 modest_widget_memory_restore (modest_runtime_get_conf(),
1000 G_OBJECT (user_data),
1001 MODEST_CONF_FOLDER_VIEW_KEY);
1003 if (!GTK_IS_TREE_VIEW(user_data)) {
1004 g_warning ("BUG: %s: not a valid tree view", __FUNCTION__);
1008 /* Get the inner model */
1009 /* check, is some rare cases, we did not get the right thing here,
1011 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (user_data));
1012 if (!GTK_IS_TREE_MODEL_FILTER(filter_model)) {
1013 g_warning ("BUG: %s: not a valid filter model", __FUNCTION__);
1017 /* check, is some rare cases, we did not get the right thing here,
1019 sort_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
1020 if (!GTK_IS_TREE_MODEL_SORT(sort_model)) {
1021 g_warning ("BUG: %s: not a valid sort model", __FUNCTION__);
1025 /* Insert the account in the model */
1026 tny_list_append (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
1027 G_OBJECT (account));
1029 /* Refilter the model */
1030 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1035 same_account_selected (ModestFolderView *self,
1036 TnyAccount *account)
1038 ModestFolderViewPrivate *priv;
1039 gboolean same_account = FALSE;
1041 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1043 if (priv->cur_folder_store) {
1044 TnyAccount *selected_folder_account = NULL;
1046 if (TNY_IS_FOLDER (priv->cur_folder_store)) {
1047 selected_folder_account =
1048 modest_tny_folder_get_account (TNY_FOLDER (priv->cur_folder_store));
1050 selected_folder_account =
1051 TNY_ACCOUNT (g_object_ref (priv->cur_folder_store));
1054 if (selected_folder_account == account)
1055 same_account = TRUE;
1057 g_object_unref (selected_folder_account);
1059 return same_account;
1064 * Selects the first inbox or the local account in an idle
1067 on_idle_select_first_inbox_or_local (gpointer user_data)
1069 ModestFolderView *self = MODEST_FOLDER_VIEW (user_data);
1071 gdk_threads_enter ();
1072 modest_folder_view_select_first_inbox_or_local (self);
1073 gdk_threads_leave ();
1079 on_account_changed (TnyAccountStore *account_store,
1080 TnyAccount *tny_account,
1083 ModestFolderView *self;
1084 ModestFolderViewPrivate *priv;
1085 GtkTreeModel *sort_model, *filter_model;
1086 GtkTreeSelection *sel;
1087 gboolean same_account;
1089 /* Ignore transport account insertions, we're not showing them
1090 in the folder view */
1091 if (TNY_IS_TRANSPORT_ACCOUNT (tny_account))
1094 if (!MODEST_IS_FOLDER_VIEW(user_data)) {
1095 g_warning ("BUG: %s: not a valid folder view", __FUNCTION__);
1099 self = MODEST_FOLDER_VIEW (user_data);
1100 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (user_data);
1102 /* Get the inner model */
1103 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (user_data));
1104 if (!GTK_IS_TREE_MODEL_FILTER(filter_model)) {
1105 g_warning ("BUG: %s: not a valid filter model", __FUNCTION__);
1109 sort_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
1110 if (!GTK_IS_TREE_MODEL_SORT(sort_model)) {
1111 g_warning ("BUG: %s: not a valid sort model", __FUNCTION__);
1115 /* Invalidate the cur_folder_store only if the selected folder
1116 belongs to the account that is being removed */
1117 same_account = same_account_selected (self, tny_account);
1119 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1120 gtk_tree_selection_unselect_all (sel);
1123 /* Remove the account from the model */
1124 tny_list_remove (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
1125 G_OBJECT (tny_account));
1127 /* Insert the account in the model */
1128 tny_list_append (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
1129 G_OBJECT (tny_account));
1131 /* Refilter the model */
1132 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1134 /* Select the first INBOX if the currently selected folder
1135 belongs to the account that is being deleted */
1136 if (same_account && !MODEST_IS_TNY_LOCAL_FOLDERS_ACCOUNT (tny_account))
1137 g_idle_add (on_idle_select_first_inbox_or_local, self);
1141 on_account_removed (TnyAccountStore *account_store,
1142 TnyAccount *account,
1145 ModestFolderView *self = NULL;
1146 ModestFolderViewPrivate *priv;
1147 GtkTreeModel *sort_model, *filter_model;
1148 GtkTreeSelection *sel = NULL;
1149 gboolean same_account = FALSE;
1151 /* Ignore transport account removals, we're not showing them
1152 in the folder view */
1153 if (TNY_IS_TRANSPORT_ACCOUNT (account))
1156 if (!MODEST_IS_FOLDER_VIEW(user_data)) {
1157 g_warning ("BUG: %s: not a valid folder view", __FUNCTION__);
1161 self = MODEST_FOLDER_VIEW (user_data);
1162 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1164 /* Invalidate the cur_folder_store only if the selected folder
1165 belongs to the account that is being removed */
1166 same_account = same_account_selected (self, account);
1168 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1169 gtk_tree_selection_unselect_all (sel);
1172 /* Invalidate row to select only if the folder to select
1173 belongs to the account that is being removed*/
1174 if (priv->folder_to_select) {
1175 TnyAccount *folder_to_select_account = NULL;
1177 folder_to_select_account = tny_folder_get_account (priv->folder_to_select);
1178 if (folder_to_select_account == account) {
1179 modest_folder_view_disable_next_folder_selection (self);
1180 g_object_unref (priv->folder_to_select);
1181 priv->folder_to_select = NULL;
1183 g_object_unref (folder_to_select_account);
1186 /* Remove the account from the model */
1187 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1188 if (!GTK_IS_TREE_MODEL_FILTER(filter_model)) {
1189 g_warning ("BUG: %s: not a valid filter model", __FUNCTION__);
1193 sort_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
1194 if (!GTK_IS_TREE_MODEL_SORT(sort_model)) {
1195 g_warning ("BUG: %s: not a valid sort model", __FUNCTION__);
1199 tny_list_remove (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
1200 G_OBJECT (account));
1202 /* If the removed account is the currently viewed one then
1203 clear the configuration value. The new visible account will be the default account */
1204 if (priv->visible_account_id &&
1205 !strcmp (priv->visible_account_id, tny_account_get_id (account))) {
1207 /* Clear the current visible account_id */
1208 modest_folder_view_set_account_id_of_visible_server_account (self, NULL);
1210 /* Call the restore method, this will set the new visible account */
1211 modest_widget_memory_restore (modest_runtime_get_conf(), G_OBJECT(self),
1212 MODEST_CONF_FOLDER_VIEW_KEY);
1215 /* Refilter the model */
1216 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1218 /* Select the first INBOX if the currently selected folder
1219 belongs to the account that is being deleted */
1221 g_idle_add (on_idle_select_first_inbox_or_local, self);
1225 modest_folder_view_set_title (ModestFolderView *self, const gchar *title)
1227 GtkTreeViewColumn *col;
1229 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
1231 col = gtk_tree_view_get_column (GTK_TREE_VIEW(self), 0);
1233 g_printerr ("modest: failed get column for title\n");
1237 gtk_tree_view_column_set_title (col, title);
1238 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self),
1243 modest_folder_view_on_map (ModestFolderView *self,
1244 GdkEventExpose *event,
1247 ModestFolderViewPrivate *priv;
1249 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1251 /* This won't happen often */
1252 if (G_UNLIKELY (priv->reselect)) {
1253 /* Select the first inbox or the local account if not found */
1255 /* TODO: this could cause a lock at startup, so we
1256 comment it for the moment. We know that this will
1257 be a bug, because the INBOX is not selected, but we
1258 need to rewrite some parts of Modest to avoid the
1259 deathlock situation */
1260 /* TODO: check if this is still the case */
1261 priv->reselect = FALSE;
1262 modest_folder_view_select_first_inbox_or_local (self);
1263 /* Notify the display name observers */
1264 g_signal_emit (G_OBJECT(self),
1265 signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
1269 if (priv->reexpand) {
1270 expand_root_items (self);
1271 priv->reexpand = FALSE;
1278 modest_folder_view_new (TnyFolderStoreQuery *query)
1281 ModestFolderViewPrivate *priv;
1282 GtkTreeSelection *sel;
1284 self = G_OBJECT (g_object_new (MODEST_TYPE_FOLDER_VIEW,
1285 #ifdef MODEST_TOOLKIT_HILDON2
1286 "hildon-ui-mode", HILDON_UI_MODE_NORMAL,
1289 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1292 priv->query = g_object_ref (query);
1294 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1295 priv->changed_signal = g_signal_connect (sel, "changed",
1296 G_CALLBACK (on_selection_changed), self);
1298 g_signal_connect (self, "row-activated", G_CALLBACK (on_row_activated), self);
1300 g_signal_connect (self, "expose-event", G_CALLBACK (modest_folder_view_on_map), NULL);
1302 return GTK_WIDGET(self);
1305 /* this feels dirty; any other way to expand all the root items? */
1307 expand_root_items (ModestFolderView *self)
1310 GtkTreeModel *model;
1313 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1314 path = gtk_tree_path_new_first ();
1316 /* all folders should have child items, so.. */
1318 gtk_tree_view_expand_row (GTK_TREE_VIEW(self), path, FALSE);
1319 gtk_tree_path_next (path);
1320 } while (gtk_tree_model_get_iter (model, &iter, path));
1322 gtk_tree_path_free (path);
1326 * We use this function to implement the
1327 * MODEST_FOLDER_VIEW_STYLE_SHOW_ONE style. We only show the default
1328 * account in this case, and the local folders.
1331 filter_row (GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
1333 ModestFolderViewPrivate *priv;
1334 gboolean retval = TRUE;
1335 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1336 GObject *instance = NULL;
1337 const gchar *id = NULL;
1339 gboolean found = FALSE;
1340 gboolean cleared = FALSE;
1342 g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (data), FALSE);
1343 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (data);
1345 gtk_tree_model_get (model, iter,
1346 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
1347 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
1350 /* Do not show if there is no instance, this could indeed
1351 happen when the model is being modified while it's being
1352 drawn. This could occur for example when moving folders
1357 if (type == TNY_FOLDER_TYPE_ROOT) {
1358 /* TNY_FOLDER_TYPE_ROOT means that the instance is an
1359 account instead of a folder. */
1360 if (TNY_IS_ACCOUNT (instance)) {
1361 TnyAccount *acc = TNY_ACCOUNT (instance);
1362 const gchar *account_id = tny_account_get_id (acc);
1364 /* If it isn't a special folder,
1365 * don't show it unless it is the visible account: */
1366 if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE &&
1367 !modest_tny_account_is_virtual_local_folders (acc) &&
1368 strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
1370 /* Show only the visible account id */
1371 if (priv->visible_account_id) {
1372 if (strcmp (account_id, priv->visible_account_id))
1379 /* Never show these to the user. They are merged into one folder
1380 * in the local-folders account instead: */
1381 if (retval && MODEST_IS_TNY_OUTBOX_ACCOUNT (acc))
1386 /* Check hiding (if necessary) */
1387 cleared = modest_email_clipboard_cleared (priv->clipboard);
1388 if ((retval) && (!cleared) && (TNY_IS_FOLDER (instance))) {
1389 id = tny_folder_get_id (TNY_FOLDER(instance));
1390 if (priv->hidding_ids != NULL)
1391 for (i=0; i < priv->n_selected && !found; i++)
1392 if (priv->hidding_ids[i] != NULL && id != NULL)
1393 found = (!strcmp (priv->hidding_ids[i], id));
1399 /* If this is a move to dialog, hide Sent, Outbox and Drafts
1400 folder as no message can be move there according to UI specs */
1401 if (!priv->show_non_move) {
1403 case TNY_FOLDER_TYPE_OUTBOX:
1404 case TNY_FOLDER_TYPE_SENT:
1405 case TNY_FOLDER_TYPE_DRAFTS:
1408 case TNY_FOLDER_TYPE_UNKNOWN:
1409 case TNY_FOLDER_TYPE_NORMAL:
1410 type = modest_tny_folder_guess_folder_type(TNY_FOLDER(instance));
1411 if (type == TNY_FOLDER_TYPE_INVALID)
1412 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1414 if (type == TNY_FOLDER_TYPE_OUTBOX ||
1415 type == TNY_FOLDER_TYPE_SENT
1416 || type == TNY_FOLDER_TYPE_DRAFTS)
1425 g_object_unref (instance);
1432 modest_folder_view_update_model (ModestFolderView *self,
1433 TnyAccountStore *account_store)
1435 ModestFolderViewPrivate *priv;
1436 GtkTreeModel *model /* , *old_model */;
1437 GtkTreeModel *filter_model = NULL, *sortable = NULL;
1439 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW (self), FALSE);
1440 g_return_val_if_fail (account_store && MODEST_IS_TNY_ACCOUNT_STORE(account_store),
1443 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1445 /* Notify that there is no folder selected */
1446 g_signal_emit (G_OBJECT(self),
1447 signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
1449 if (priv->cur_folder_store) {
1450 g_object_unref (priv->cur_folder_store);
1451 priv->cur_folder_store = NULL;
1454 /* FIXME: the local accounts are not shown when the query
1455 selects only the subscribed folders */
1456 model = tny_gtk_folder_store_tree_model_new (NULL);
1458 /* Get the accounts: */
1459 tny_account_store_get_accounts (TNY_ACCOUNT_STORE(account_store),
1461 TNY_ACCOUNT_STORE_STORE_ACCOUNTS);
1463 sortable = gtk_tree_model_sort_new_with_model (model);
1464 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
1465 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
1466 GTK_SORT_ASCENDING);
1467 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
1468 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
1469 cmp_rows, NULL, NULL);
1471 /* Create filter model */
1472 filter_model = gtk_tree_model_filter_new (sortable, NULL);
1473 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
1479 gtk_tree_view_set_model (GTK_TREE_VIEW(self), filter_model);
1480 g_signal_connect (G_OBJECT(filter_model), "row-inserted",
1481 (GCallback) on_row_inserted_maybe_select_folder, self);
1483 g_object_unref (model);
1484 g_object_unref (filter_model);
1485 g_object_unref (sortable);
1487 /* Force a reselection of the INBOX next time the widget is shown */
1488 priv->reselect = TRUE;
1495 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
1497 GtkTreeModel *model = NULL;
1498 TnyFolderStore *folder = NULL;
1500 ModestFolderView *tree_view = NULL;
1501 ModestFolderViewPrivate *priv = NULL;
1502 gboolean selected = FALSE;
1504 g_return_if_fail (sel);
1505 g_return_if_fail (user_data);
1507 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
1509 selected = gtk_tree_selection_get_selected (sel, &model, &iter);
1511 tree_view = MODEST_FOLDER_VIEW (user_data);
1514 gtk_tree_model_get (model, &iter,
1515 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
1518 /* If the folder is the same do not notify */
1519 if (folder && priv->cur_folder_store == folder) {
1520 g_object_unref (folder);
1525 /* Current folder was unselected */
1526 if (priv->cur_folder_store) {
1527 /* We must do this firstly because a libtinymail-camel
1528 implementation detail. If we issue the signal
1529 before doing the sync_async, then that signal could
1530 cause (and it actually does it) a free of the
1531 summary of the folder (because the main window will
1532 clear the headers view */
1533 if (TNY_IS_FOLDER(priv->cur_folder_store))
1534 tny_folder_sync_async (TNY_FOLDER(priv->cur_folder_store),
1535 FALSE, NULL, NULL, NULL);
1537 g_signal_emit (G_OBJECT(tree_view), signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
1538 priv->cur_folder_store, FALSE);
1540 g_object_unref (priv->cur_folder_store);
1541 priv->cur_folder_store = NULL;
1544 /* New current references */
1545 priv->cur_folder_store = folder;
1547 /* New folder has been selected. Do not notify if there is
1548 nothing new selected */
1550 g_signal_emit (G_OBJECT(tree_view),
1551 signals[FOLDER_SELECTION_CHANGED_SIGNAL],
1552 0, priv->cur_folder_store, TRUE);
1557 on_row_activated (GtkTreeView *treeview,
1558 GtkTreePath *treepath,
1559 GtkTreeViewColumn *column,
1562 GtkTreeModel *model = NULL;
1563 TnyFolderStore *folder = NULL;
1565 ModestFolderView *self = NULL;
1566 ModestFolderViewPrivate *priv = NULL;
1568 g_return_if_fail (treeview);
1569 g_return_if_fail (user_data);
1571 self = MODEST_FOLDER_VIEW (user_data);
1572 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
1574 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1576 if (!gtk_tree_model_get_iter (model, &iter, treepath))
1579 gtk_tree_model_get (model, &iter,
1580 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
1583 g_signal_emit (G_OBJECT(self),
1584 signals[FOLDER_ACTIVATED_SIGNAL],
1587 g_object_unref (folder);
1591 modest_folder_view_get_selected (ModestFolderView *self)
1593 ModestFolderViewPrivate *priv;
1595 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW(self), NULL);
1597 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1598 if (priv->cur_folder_store)
1599 g_object_ref (priv->cur_folder_store);
1601 return priv->cur_folder_store;
1605 get_cmp_rows_type_pos (GObject *folder)
1607 /* Remote accounts -> Local account -> MMC account .*/
1610 if (TNY_IS_ACCOUNT (folder) &&
1611 modest_tny_account_is_virtual_local_folders (
1612 TNY_ACCOUNT (folder))) {
1614 } else if (TNY_IS_ACCOUNT (folder)) {
1615 TnyAccount *account = TNY_ACCOUNT (folder);
1616 const gchar *account_id = tny_account_get_id (account);
1617 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
1623 printf ("DEBUG: %s: unexpected type.\n", __FUNCTION__);
1624 return -1; /* Should never happen */
1629 get_cmp_subfolder_type_pos (TnyFolderType t)
1631 /* Inbox, Outbox, Drafts, Sent, User */
1635 case TNY_FOLDER_TYPE_INBOX:
1638 case TNY_FOLDER_TYPE_OUTBOX:
1641 case TNY_FOLDER_TYPE_DRAFTS:
1644 case TNY_FOLDER_TYPE_SENT:
1653 * This function orders the mail accounts according to these rules:
1654 * 1st - remote accounts
1655 * 2nd - local account
1659 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1663 gchar *name1 = NULL;
1664 gchar *name2 = NULL;
1665 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1666 TnyFolderType type2 = TNY_FOLDER_TYPE_UNKNOWN;
1667 GObject *folder1 = NULL;
1668 GObject *folder2 = NULL;
1670 gtk_tree_model_get (tree_model, iter1,
1671 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name1,
1672 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
1673 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder1,
1675 gtk_tree_model_get (tree_model, iter2,
1676 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name2,
1677 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type2,
1678 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder2,
1681 /* Return if we get no folder. This could happen when folder
1682 operations are happening. The model is updated after the
1683 folder copy/move actually occurs, so there could be
1684 situations where the model to be drawn is not correct */
1685 if (!folder1 || !folder2)
1688 if (type == TNY_FOLDER_TYPE_ROOT) {
1689 /* Compare the types, so that
1690 * Remote accounts -> Local account -> MMC account .*/
1691 const gint pos1 = get_cmp_rows_type_pos (folder1);
1692 const gint pos2 = get_cmp_rows_type_pos (folder2);
1693 /* printf ("DEBUG: %s:\n type1=%s, pos1=%d\n type2=%s, pos2=%d\n",
1694 __FUNCTION__, G_OBJECT_TYPE_NAME(folder1), pos1, G_OBJECT_TYPE_NAME(folder2), pos2); */
1697 else if (pos1 > pos2)
1700 /* Compare items of the same type: */
1702 TnyAccount *account1 = NULL;
1703 if (TNY_IS_ACCOUNT (folder1))
1704 account1 = TNY_ACCOUNT (folder1);
1706 TnyAccount *account2 = NULL;
1707 if (TNY_IS_ACCOUNT (folder2))
1708 account2 = TNY_ACCOUNT (folder2);
1710 const gchar *account_id = account1 ? tny_account_get_id (account1) : NULL;
1711 const gchar *account_id2 = account2 ? tny_account_get_id (account2) : NULL;
1713 if (!account_id && !account_id2) {
1715 } else if (!account_id) {
1717 } else if (!account_id2) {
1719 } else if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
1722 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
1726 gint cmp1 = 0, cmp2 = 0;
1727 /* get the parent to know if it's a local folder */
1730 gboolean has_parent;
1731 has_parent = gtk_tree_model_iter_parent (tree_model, &parent, iter1);
1733 GObject *parent_folder;
1734 TnyFolderType parent_type = TNY_FOLDER_TYPE_UNKNOWN;
1735 gtk_tree_model_get (tree_model, &parent,
1736 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &parent_type,
1737 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &parent_folder,
1739 if ((parent_type == TNY_FOLDER_TYPE_ROOT) &&
1740 TNY_IS_ACCOUNT (parent_folder)) {
1741 if (modest_tny_account_is_virtual_local_folders (TNY_ACCOUNT (parent_folder))) {
1742 cmp1 = get_cmp_subfolder_type_pos (modest_tny_folder_get_local_or_mmc_folder_type
1743 (TNY_FOLDER (folder1)));
1744 cmp2 = get_cmp_subfolder_type_pos (modest_tny_folder_get_local_or_mmc_folder_type
1745 (TNY_FOLDER (folder2)));
1746 } else if (modest_tny_account_is_memory_card_account (TNY_ACCOUNT (parent_folder))) {
1747 if (modest_local_folder_info_get_type (tny_folder_get_name (TNY_FOLDER (folder1))) == TNY_FOLDER_TYPE_ARCHIVE) {
1750 } else if (modest_local_folder_info_get_type (tny_folder_get_name (TNY_FOLDER (folder2))) == TNY_FOLDER_TYPE_ARCHIVE) {
1756 g_object_unref (parent_folder);
1759 /* if they are not local folders */
1761 cmp1 = get_cmp_subfolder_type_pos (tny_folder_get_folder_type (TNY_FOLDER (folder1)));
1762 cmp2 = get_cmp_subfolder_type_pos (tny_folder_get_folder_type (TNY_FOLDER (folder2)));
1766 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
1768 cmp = (cmp1 - cmp2);
1773 g_object_unref(G_OBJECT(folder1));
1775 g_object_unref(G_OBJECT(folder2));
1783 /*****************************************************************************/
1784 /* DRAG and DROP stuff */
1785 /*****************************************************************************/
1787 * This function fills the #GtkSelectionData with the row and the
1788 * model that has been dragged. It's called when this widget is a
1789 * source for dnd after the event drop happened
1792 on_drag_data_get (GtkWidget *widget, GdkDragContext *context, GtkSelectionData *selection_data,
1793 guint info, guint time, gpointer data)
1795 GtkTreeSelection *selection;
1796 GtkTreeModel *model;
1798 GtkTreePath *source_row;
1800 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1801 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
1803 source_row = gtk_tree_model_get_path (model, &iter);
1804 gtk_tree_set_row_drag_data (selection_data,
1808 gtk_tree_path_free (source_row);
1812 typedef struct _DndHelper {
1813 ModestFolderView *folder_view;
1814 gboolean delete_source;
1815 GtkTreePath *source_row;
1819 dnd_helper_destroyer (DndHelper *helper)
1821 /* Free the helper */
1822 gtk_tree_path_free (helper->source_row);
1823 g_slice_free (DndHelper, helper);
1827 xfer_folder_cb (ModestMailOperation *mail_op,
1828 TnyFolder *new_folder,
1832 /* Select the folder */
1833 modest_folder_view_select_folder (MODEST_FOLDER_VIEW (user_data),
1839 /* get the folder for the row the treepath refers to. */
1840 /* folder must be unref'd */
1841 static TnyFolderStore *
1842 tree_path_to_folder (GtkTreeModel *model, GtkTreePath *path)
1845 TnyFolderStore *folder = NULL;
1847 if (gtk_tree_model_get_iter (model,&iter, path))
1848 gtk_tree_model_get (model, &iter,
1849 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
1856 * This function is used by drag_data_received_cb to manage drag and
1857 * drop of a header, i.e, and drag from the header view to the folder
1861 drag_and_drop_from_header_view (GtkTreeModel *source_model,
1862 GtkTreeModel *dest_model,
1863 GtkTreePath *dest_row,
1864 GtkSelectionData *selection_data)
1866 TnyList *headers = NULL;
1867 TnyFolder *folder = NULL, *src_folder = NULL;
1868 TnyFolderType folder_type;
1869 GtkTreeIter source_iter, dest_iter;
1870 ModestWindowMgr *mgr = NULL;
1871 ModestWindow *main_win = NULL;
1872 gchar **uris, **tmp;
1874 /* Build the list of headers */
1875 mgr = modest_runtime_get_window_mgr ();
1876 headers = tny_simple_list_new ();
1877 uris = modest_dnd_selection_data_get_paths (selection_data);
1880 while (*tmp != NULL) {
1883 gboolean first = TRUE;
1886 path = gtk_tree_path_new_from_string (*tmp);
1887 gtk_tree_model_get_iter (source_model, &source_iter, path);
1888 gtk_tree_model_get (source_model, &source_iter,
1889 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1892 /* Do not enable d&d of headers already opened */
1893 if (!modest_window_mgr_find_registered_header(mgr, header, NULL))
1894 tny_list_append (headers, G_OBJECT (header));
1896 if (G_UNLIKELY (first)) {
1897 src_folder = tny_header_get_folder (header);
1901 /* Free and go on */
1902 gtk_tree_path_free (path);
1903 g_object_unref (header);
1908 /* This could happen ig we perform a d&d very quickly over the
1909 same row that row could dissapear because message is
1911 if (!TNY_IS_FOLDER (src_folder))
1914 /* Get the target folder */
1915 gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
1916 gtk_tree_model_get (dest_model, &dest_iter,
1917 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
1920 if (!folder || !TNY_IS_FOLDER(folder)) {
1921 /* g_warning ("%s: not a valid target folder (%p)", __FUNCTION__, folder); */
1925 folder_type = modest_tny_folder_guess_folder_type (folder);
1926 if (folder_type == TNY_FOLDER_TYPE_INVALID) {
1927 /* g_warning ("%s: invalid target folder", __FUNCTION__); */
1928 goto cleanup; /* cannot move messages there */
1931 if (modest_tny_folder_get_rules((TNY_FOLDER(folder))) & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE) {
1932 /* g_warning ("folder not writable"); */
1933 goto cleanup; /* verboten! */
1936 /* Ask for confirmation to move */
1937 main_win = modest_window_mgr_get_main_window (mgr, FALSE); /* don't create */
1939 g_warning ("%s: BUG: no main window found", __FUNCTION__);
1943 /* Transfer messages */
1944 modest_ui_actions_transfer_messages_helper (GTK_WINDOW (main_win), src_folder,
1949 if (G_IS_OBJECT (src_folder))
1950 g_object_unref (src_folder);
1951 if (G_IS_OBJECT(folder))
1952 g_object_unref (G_OBJECT (folder));
1953 if (G_IS_OBJECT(headers))
1954 g_object_unref (headers);
1958 TnyFolderStore *src_folder;
1959 TnyFolderStore *dst_folder;
1960 ModestFolderView *folder_view;
1965 dnd_folder_info_destroyer (DndFolderInfo *info)
1967 if (info->src_folder)
1968 g_object_unref (info->src_folder);
1969 if (info->dst_folder)
1970 g_object_unref (info->dst_folder);
1971 g_slice_free (DndFolderInfo, info);
1975 dnd_on_connection_failed_destroyer (DndFolderInfo *info,
1976 GtkWindow *parent_window,
1977 TnyAccount *account)
1980 modest_ui_actions_on_account_connection_error (parent_window, account);
1982 /* Free the helper & info */
1983 dnd_helper_destroyer (info->helper);
1984 dnd_folder_info_destroyer (info);
1988 drag_and_drop_from_folder_view_src_folder_performer (gboolean canceled,
1990 GtkWindow *parent_window,
1991 TnyAccount *account,
1994 DndFolderInfo *info = NULL;
1995 ModestMailOperation *mail_op;
1997 info = (DndFolderInfo *) user_data;
1999 if (err || canceled) {
2000 dnd_on_connection_failed_destroyer (info, parent_window, account);
2004 /* Do the mail operation */
2005 mail_op = modest_mail_operation_new_with_error_handling ((GObject *) parent_window,
2006 modest_ui_actions_move_folder_error_handler,
2007 info->src_folder, NULL);
2009 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
2012 /* Transfer the folder */
2013 modest_mail_operation_xfer_folder (mail_op,
2014 TNY_FOLDER (info->src_folder),
2016 info->helper->delete_source,
2018 info->helper->folder_view);
2021 g_object_unref (G_OBJECT (mail_op));
2022 dnd_helper_destroyer (info->helper);
2023 dnd_folder_info_destroyer (info);
2028 drag_and_drop_from_folder_view_dst_folder_performer (gboolean canceled,
2030 GtkWindow *parent_window,
2031 TnyAccount *account,
2034 DndFolderInfo *info = NULL;
2036 info = (DndFolderInfo *) user_data;
2038 if (err || canceled) {
2039 dnd_on_connection_failed_destroyer (info, parent_window, account);
2043 /* Connect to source folder and perform the copy/move */
2044 modest_platform_connect_if_remote_and_perform (NULL, TRUE,
2046 drag_and_drop_from_folder_view_src_folder_performer,
2051 * This function is used by drag_data_received_cb to manage drag and
2052 * drop of a folder, i.e, and drag from the folder view to the same
2056 drag_and_drop_from_folder_view (GtkTreeModel *source_model,
2057 GtkTreeModel *dest_model,
2058 GtkTreePath *dest_row,
2059 GtkSelectionData *selection_data,
2062 GtkTreeIter dest_iter, iter;
2063 TnyFolderStore *dest_folder = NULL;
2064 TnyFolderStore *folder = NULL;
2065 gboolean forbidden = FALSE;
2067 DndFolderInfo *info = NULL;
2069 win = modest_window_mgr_get_main_window (modest_runtime_get_window_mgr(), FALSE); /* don't create */
2071 g_warning ("%s: BUG: no main window", __FUNCTION__);
2072 dnd_helper_destroyer (helper);
2077 /* check the folder rules for the destination */
2078 folder = tree_path_to_folder (dest_model, dest_row);
2079 if (TNY_IS_FOLDER(folder)) {
2080 ModestTnyFolderRules rules =
2081 modest_tny_folder_get_rules (TNY_FOLDER (folder));
2082 forbidden = rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE;
2083 } else if (TNY_IS_FOLDER_STORE(folder)) {
2084 /* enable local root as destination for folders */
2085 if (!MODEST_IS_TNY_LOCAL_FOLDERS_ACCOUNT (folder) &&
2086 !modest_tny_account_is_memory_card_account (TNY_ACCOUNT (folder)))
2089 g_object_unref (folder);
2092 /* check the folder rules for the source */
2093 folder = tree_path_to_folder (source_model, helper->source_row);
2094 if (TNY_IS_FOLDER(folder)) {
2095 ModestTnyFolderRules rules =
2096 modest_tny_folder_get_rules (TNY_FOLDER (folder));
2097 forbidden = rules & MODEST_FOLDER_RULES_FOLDER_NON_MOVEABLE;
2100 g_object_unref (folder);
2104 /* Check if the drag is possible */
2105 if (forbidden || !gtk_tree_path_compare (helper->source_row, dest_row)) {
2107 modest_platform_run_information_dialog ((GtkWindow *) win,
2108 _("mail_in_ui_folder_move_target_error"),
2110 /* Restore the previous selection */
2111 folder = tree_path_to_folder (source_model, helper->source_row);
2113 if (TNY_IS_FOLDER (folder))
2114 modest_folder_view_select_folder (helper->folder_view,
2115 TNY_FOLDER (folder), FALSE);
2116 g_object_unref (folder);
2118 dnd_helper_destroyer (helper);
2123 gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
2124 gtk_tree_model_get (dest_model, &dest_iter,
2125 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
2127 gtk_tree_model_get_iter (source_model, &iter, helper->source_row);
2128 gtk_tree_model_get (source_model, &iter,
2129 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
2132 /* Create the info for the performer */
2133 info = g_slice_new0 (DndFolderInfo);
2134 info->src_folder = g_object_ref (folder);
2135 info->dst_folder = g_object_ref (dest_folder);
2136 info->helper = helper;
2138 /* Connect to the destination folder and perform the copy/move */
2139 modest_platform_connect_if_remote_and_perform (GTK_WINDOW (win), TRUE,
2141 drag_and_drop_from_folder_view_dst_folder_performer,
2145 g_object_unref (dest_folder);
2146 g_object_unref (folder);
2150 * This function receives the data set by the "drag-data-get" signal
2151 * handler. This information comes within the #GtkSelectionData. This
2152 * function will manage both the drags of folders of the treeview and
2153 * drags of headers of the header view widget.
2156 on_drag_data_received (GtkWidget *widget,
2157 GdkDragContext *context,
2160 GtkSelectionData *selection_data,
2165 GtkWidget *source_widget;
2166 GtkTreeModel *dest_model, *source_model;
2167 GtkTreePath *source_row, *dest_row;
2168 GtkTreeViewDropPosition pos;
2169 gboolean delete_source = FALSE;
2170 gboolean success = FALSE;
2172 /* Do not allow further process */
2173 g_signal_stop_emission_by_name (widget, "drag-data-received");
2174 source_widget = gtk_drag_get_source_widget (context);
2176 /* Get the action */
2177 if (context->action == GDK_ACTION_MOVE) {
2178 delete_source = TRUE;
2180 /* Notify that there is no folder selected. We need to
2181 do this in order to update the headers view (and
2182 its monitors, because when moving, the old folder
2183 won't longer exist. We can not wait for the end of
2184 the operation, because the operation won't start if
2185 the folder is in use */
2186 if (source_widget == widget) {
2187 GtkTreeSelection *sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
2188 gtk_tree_selection_unselect_all (sel);
2192 /* Check if the get_data failed */
2193 if (selection_data == NULL || selection_data->length < 0)
2196 /* Select the destination model */
2197 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
2199 /* Get the path to the destination row. Can not call
2200 gtk_tree_view_get_drag_dest_row() because the source row
2201 is not selected anymore */
2202 gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget), x, y,
2205 /* Only allow drops IN other rows */
2207 pos == GTK_TREE_VIEW_DROP_BEFORE ||
2208 pos == GTK_TREE_VIEW_DROP_AFTER)
2212 /* Drags from the header view */
2213 if (source_widget != widget) {
2214 source_model = gtk_tree_view_get_model (GTK_TREE_VIEW (source_widget));
2216 drag_and_drop_from_header_view (source_model,
2221 DndHelper *helper = NULL;
2223 /* Get the source model and row */
2224 gtk_tree_get_row_drag_data (selection_data,
2228 /* Create the helper */
2229 helper = g_slice_new0 (DndHelper);
2230 helper->delete_source = delete_source;
2231 helper->source_row = gtk_tree_path_copy (source_row);
2232 helper->folder_view = MODEST_FOLDER_VIEW (widget);
2234 drag_and_drop_from_folder_view (source_model,
2240 gtk_tree_path_free (source_row);
2244 gtk_tree_path_free (dest_row);
2247 /* Finish the drag and drop */
2248 gtk_drag_finish (context, success, FALSE, time);
2252 * We define a "drag-drop" signal handler because we do not want to
2253 * use the default one, because the default one always calls
2254 * gtk_drag_finish and we prefer to do it in the "drag-data-received"
2255 * signal handler, because there we have all the information available
2256 * to know if the dnd was a success or not.
2259 drag_drop_cb (GtkWidget *widget,
2260 GdkDragContext *context,
2268 if (!context->targets)
2271 /* Check if we're dragging a folder row */
2272 target = gtk_drag_dest_find_target (widget, context, NULL);
2274 /* Request the data from the source. */
2275 gtk_drag_get_data(widget, context, target, time);
2281 * This function expands a node of a tree view if it's not expanded
2282 * yet. Not sure why it needs the threads stuff, but gtk+`example code
2283 * does that, so that's why they're here.
2286 expand_row_timeout (gpointer data)
2288 GtkTreeView *tree_view = data;
2289 GtkTreePath *dest_path = NULL;
2290 GtkTreeViewDropPosition pos;
2291 gboolean result = FALSE;
2293 gdk_threads_enter ();
2295 gtk_tree_view_get_drag_dest_row (tree_view,
2300 (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER ||
2301 pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)) {
2302 gtk_tree_view_expand_row (tree_view, dest_path, FALSE);
2303 gtk_tree_path_free (dest_path);
2307 gtk_tree_path_free (dest_path);
2312 gdk_threads_leave ();
2318 * This function is called whenever the pointer is moved over a widget
2319 * while dragging some data. It installs a timeout that will expand a
2320 * node of the treeview if not expanded yet. This function also calls
2321 * gdk_drag_status in order to set the suggested action that will be
2322 * used by the "drag-data-received" signal handler to know if we
2323 * should do a move or just a copy of the data.
2326 on_drag_motion (GtkWidget *widget,
2327 GdkDragContext *context,
2333 GtkTreeViewDropPosition pos;
2334 GtkTreePath *dest_row;
2335 GtkTreeModel *dest_model;
2336 ModestFolderViewPrivate *priv;
2337 GdkDragAction suggested_action;
2338 gboolean valid_location = FALSE;
2339 TnyFolderStore *folder = NULL;
2341 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
2343 if (priv->timer_expander != 0) {
2344 g_source_remove (priv->timer_expander);
2345 priv->timer_expander = 0;
2348 gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
2353 /* Do not allow drops between folders */
2355 pos == GTK_TREE_VIEW_DROP_BEFORE ||
2356 pos == GTK_TREE_VIEW_DROP_AFTER) {
2357 gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW (widget), NULL, 0);
2358 gdk_drag_status(context, 0, time);
2359 valid_location = FALSE;
2362 valid_location = TRUE;
2365 /* Check that the destination folder is writable */
2366 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
2367 folder = tree_path_to_folder (dest_model, dest_row);
2368 if (folder && TNY_IS_FOLDER (folder)) {
2369 ModestTnyFolderRules rules = modest_tny_folder_get_rules(TNY_FOLDER (folder));
2371 if (rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE) {
2372 valid_location = FALSE;
2377 /* Expand the selected row after 1/2 second */
2378 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), dest_row)) {
2379 priv->timer_expander = g_timeout_add (500, expand_row_timeout, widget);
2381 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), dest_row, pos);
2383 /* Select the desired action. By default we pick MOVE */
2384 suggested_action = GDK_ACTION_MOVE;
2386 if (context->actions == GDK_ACTION_COPY)
2387 gdk_drag_status(context, GDK_ACTION_COPY, time);
2388 else if (context->actions == GDK_ACTION_MOVE)
2389 gdk_drag_status(context, GDK_ACTION_MOVE, time);
2390 else if (context->actions & suggested_action)
2391 gdk_drag_status(context, suggested_action, time);
2393 gdk_drag_status(context, GDK_ACTION_DEFAULT, time);
2397 g_object_unref (folder);
2399 gtk_tree_path_free (dest_row);
2401 g_signal_stop_emission_by_name (widget, "drag-motion");
2403 return valid_location;
2407 * This function sets the treeview as a source and a target for dnd
2408 * events. It also connects all the requirede signals.
2411 setup_drag_and_drop (GtkTreeView *self)
2413 /* Set up the folder view as a dnd destination. Set only the
2414 highlight flag, otherwise gtk will have a different
2416 #ifdef MODEST_TOOLKIT_HILDON2
2419 gtk_drag_dest_set (GTK_WIDGET (self),
2420 GTK_DEST_DEFAULT_HIGHLIGHT,
2421 folder_view_drag_types,
2422 G_N_ELEMENTS (folder_view_drag_types),
2423 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2425 g_signal_connect (G_OBJECT (self),
2426 "drag_data_received",
2427 G_CALLBACK (on_drag_data_received),
2431 /* Set up the treeview as a dnd source */
2432 gtk_drag_source_set (GTK_WIDGET (self),
2434 folder_view_drag_types,
2435 G_N_ELEMENTS (folder_view_drag_types),
2436 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2438 g_signal_connect (G_OBJECT (self),
2440 G_CALLBACK (on_drag_motion),
2443 g_signal_connect (G_OBJECT (self),
2445 G_CALLBACK (on_drag_data_get),
2448 g_signal_connect (G_OBJECT (self),
2450 G_CALLBACK (drag_drop_cb),
2455 * This function manages the navigation through the folders using the
2456 * keyboard or the hardware keys in the device
2459 on_key_pressed (GtkWidget *self,
2463 GtkTreeSelection *selection;
2465 GtkTreeModel *model;
2466 gboolean retval = FALSE;
2468 /* Up and Down are automatically managed by the treeview */
2469 if (event->keyval == GDK_Return) {
2470 /* Expand/Collapse the selected row */
2471 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2472 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
2475 path = gtk_tree_model_get_path (model, &iter);
2477 if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (self), path))
2478 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
2480 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
2481 gtk_tree_path_free (path);
2483 /* No further processing */
2491 * We listen to the changes in the local folder account name key,
2492 * because we want to show the right name in the view. The local
2493 * folder account name corresponds to the device name in the Maemo
2494 * version. We do this because we do not want to query gconf on each
2495 * tree view refresh. It's better to cache it and change whenever
2499 on_configuration_key_changed (ModestConf* conf,
2501 ModestConfEvent event,
2502 ModestConfNotificationId id,
2503 ModestFolderView *self)
2505 ModestFolderViewPrivate *priv;
2508 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
2509 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2511 if (!strcmp (key, MODEST_CONF_DEVICE_NAME)) {
2512 g_free (priv->local_account_name);
2514 if (event == MODEST_CONF_EVENT_KEY_UNSET)
2515 priv->local_account_name = g_strdup (MODEST_LOCAL_FOLDERS_DEFAULT_DISPLAY_NAME);
2517 priv->local_account_name = modest_conf_get_string (modest_runtime_get_conf(),
2518 MODEST_CONF_DEVICE_NAME, NULL);
2520 /* Force a redraw */
2521 #if GTK_CHECK_VERSION(2, 8, 0)
2522 GtkTreeViewColumn * tree_column;
2524 tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self),
2525 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN);
2526 gtk_tree_view_column_queue_resize (tree_column);
2528 gtk_widget_queue_draw (GTK_WIDGET (self));
2534 modest_folder_view_set_style (ModestFolderView *self,
2535 ModestFolderViewStyle style)
2537 ModestFolderViewPrivate *priv;
2539 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2540 g_return_if_fail (style == MODEST_FOLDER_VIEW_STYLE_SHOW_ALL ||
2541 style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE);
2543 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2546 priv->style = style;
2550 modest_folder_view_set_account_id_of_visible_server_account (ModestFolderView *self,
2551 const gchar *account_id)
2553 ModestFolderViewPrivate *priv;
2554 GtkTreeModel *model;
2556 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2558 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2560 /* This will be used by the filter_row callback,
2561 * to decided which rows to show: */
2562 if (priv->visible_account_id) {
2563 g_free (priv->visible_account_id);
2564 priv->visible_account_id = NULL;
2567 priv->visible_account_id = g_strdup (account_id);
2570 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2571 if (GTK_IS_TREE_MODEL_FILTER (model))
2572 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2574 /* Save settings to gconf */
2575 modest_widget_memory_save (modest_runtime_get_conf (), G_OBJECT(self),
2576 MODEST_CONF_FOLDER_VIEW_KEY);
2580 modest_folder_view_get_account_id_of_visible_server_account (ModestFolderView *self)
2582 ModestFolderViewPrivate *priv;
2584 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW(self), NULL);
2586 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2588 return (const gchar *) priv->visible_account_id;
2592 find_inbox_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *inbox_iter)
2596 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2598 gtk_tree_model_get (model, iter,
2599 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN,
2602 gboolean result = FALSE;
2603 if (type == TNY_FOLDER_TYPE_INBOX) {
2607 *inbox_iter = *iter;
2611 if (gtk_tree_model_iter_children (model, &child, iter)) {
2612 if (find_inbox_iter (model, &child, inbox_iter))
2616 } while (gtk_tree_model_iter_next (model, iter));
2625 modest_folder_view_select_first_inbox_or_local (ModestFolderView *self)
2627 GtkTreeModel *model;
2628 GtkTreeIter iter, inbox_iter;
2629 GtkTreeSelection *sel;
2630 GtkTreePath *path = NULL;
2632 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2634 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2638 expand_root_items (self);
2639 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2641 if (!gtk_tree_model_get_iter_first (model, &iter)) {
2642 g_warning ("%s: model is empty", __FUNCTION__);
2646 if (find_inbox_iter (model, &iter, &inbox_iter))
2647 path = gtk_tree_model_get_path (model, &inbox_iter);
2649 path = gtk_tree_path_new_first ();
2651 /* Select the row and free */
2652 gtk_tree_view_set_cursor (GTK_TREE_VIEW (self), path, NULL, FALSE);
2653 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (self), path, NULL, FALSE, 0.0, 0.0);
2654 gtk_tree_path_free (path);
2657 gtk_widget_grab_focus (GTK_WIDGET(self));
2663 find_folder_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *folder_iter,
2668 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2669 TnyFolder* a_folder;
2672 gtk_tree_model_get (model, iter,
2673 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &a_folder,
2674 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name,
2675 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
2679 if (folder == a_folder) {
2680 g_object_unref (a_folder);
2681 *folder_iter = *iter;
2684 g_object_unref (a_folder);
2686 if (gtk_tree_model_iter_children (model, &child, iter)) {
2687 if (find_folder_iter (model, &child, folder_iter, folder))
2691 } while (gtk_tree_model_iter_next (model, iter));
2698 on_row_inserted_maybe_select_folder (GtkTreeModel *tree_model,
2701 ModestFolderView *self)
2703 ModestFolderViewPrivate *priv = NULL;
2704 GtkTreeSelection *sel;
2705 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2706 GObject *instance = NULL;
2708 if (!MODEST_IS_FOLDER_VIEW(self))
2711 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2713 priv->reexpand = TRUE;
2715 gtk_tree_model_get (tree_model, iter,
2716 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
2717 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
2719 if (type == TNY_FOLDER_TYPE_INBOX && priv->folder_to_select == NULL) {
2720 priv->folder_to_select = g_object_ref (instance);
2722 g_object_unref (instance);
2724 if (priv->folder_to_select) {
2726 if (!modest_folder_view_select_folder (self, priv->folder_to_select,
2729 path = gtk_tree_model_get_path (tree_model, iter);
2730 gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
2732 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2734 gtk_tree_selection_select_iter (sel, iter);
2735 gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
2737 gtk_tree_path_free (path);
2741 modest_folder_view_disable_next_folder_selection (self);
2743 /* Refilter the model */
2744 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (tree_model));
2750 modest_folder_view_disable_next_folder_selection (ModestFolderView *self)
2752 ModestFolderViewPrivate *priv;
2754 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2756 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2758 if (priv->folder_to_select)
2759 g_object_unref(priv->folder_to_select);
2761 priv->folder_to_select = NULL;
2765 modest_folder_view_select_folder (ModestFolderView *self, TnyFolder *folder,
2766 gboolean after_change)
2768 GtkTreeModel *model;
2769 GtkTreeIter iter, folder_iter;
2770 GtkTreeSelection *sel;
2771 ModestFolderViewPrivate *priv = NULL;
2773 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW (self), FALSE);
2774 g_return_val_if_fail (folder && TNY_IS_FOLDER (folder), FALSE);
2776 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2779 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2780 gtk_tree_selection_unselect_all (sel);
2782 if (priv->folder_to_select)
2783 g_object_unref(priv->folder_to_select);
2784 priv->folder_to_select = TNY_FOLDER(g_object_ref(folder));
2788 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2793 /* Refilter the model, before selecting the folder */
2794 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2796 if (!gtk_tree_model_get_iter_first (model, &iter)) {
2797 g_warning ("%s: model is empty", __FUNCTION__);
2801 if (find_folder_iter (model, &iter, &folder_iter, folder)) {
2804 path = gtk_tree_model_get_path (model, &folder_iter);
2805 gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
2807 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2808 gtk_tree_selection_select_iter (sel, &folder_iter);
2809 gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
2811 gtk_tree_path_free (path);
2819 modest_folder_view_copy_selection (ModestFolderView *self)
2821 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2823 /* Copy selection */
2824 _clipboard_set_selected_data (self, FALSE);
2828 modest_folder_view_cut_selection (ModestFolderView *folder_view)
2830 ModestFolderViewPrivate *priv = NULL;
2831 GtkTreeModel *model = NULL;
2832 const gchar **hidding = NULL;
2833 guint i, n_selected;
2835 g_return_if_fail (folder_view && MODEST_IS_FOLDER_VIEW (folder_view));
2836 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
2838 /* Copy selection */
2839 if (!_clipboard_set_selected_data (folder_view, TRUE))
2842 /* Get hidding ids */
2843 hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected);
2845 /* Clear hidding array created by previous cut operation */
2846 _clear_hidding_filter (MODEST_FOLDER_VIEW (folder_view));
2848 /* Copy hidding array */
2849 priv->n_selected = n_selected;
2850 priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
2851 for (i=0; i < n_selected; i++)
2852 priv->hidding_ids[i] = g_strdup(hidding[i]);
2854 /* Hide cut folders */
2855 model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
2856 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2860 modest_folder_view_copy_model (ModestFolderView *folder_view_src,
2861 ModestFolderView *folder_view_dst)
2863 GtkTreeModel *filter_model = NULL;
2864 GtkTreeModel *model = NULL;
2865 GtkTreeModel *new_filter_model = NULL;
2867 g_return_if_fail (folder_view_src && MODEST_IS_FOLDER_VIEW (folder_view_src));
2868 g_return_if_fail (folder_view_dst && MODEST_IS_FOLDER_VIEW (folder_view_dst));
2871 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view_src));
2872 model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER(filter_model));
2874 /* Build new filter model */
2875 new_filter_model = gtk_tree_model_filter_new (model, NULL);
2876 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (new_filter_model),
2880 /* Set copied model */
2881 gtk_tree_view_set_model (GTK_TREE_VIEW (folder_view_dst), new_filter_model);
2882 g_signal_connect (G_OBJECT(new_filter_model), "row-inserted",
2883 (GCallback) on_row_inserted_maybe_select_folder, folder_view_dst);
2886 g_object_unref (new_filter_model);
2890 modest_folder_view_show_non_move_folders (ModestFolderView *folder_view,
2893 GtkTreeModel *model = NULL;
2894 ModestFolderViewPrivate* priv;
2896 g_return_if_fail (folder_view && MODEST_IS_FOLDER_VIEW (folder_view));
2898 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
2899 priv->show_non_move = show;
2900 /* modest_folder_view_update_model(folder_view, */
2901 /* TNY_ACCOUNT_STORE(modest_runtime_get_account_store())); */
2903 /* Hide special folders */
2904 model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
2905 if (GTK_IS_TREE_MODEL_FILTER (model)) {
2906 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2910 /* Returns FALSE if it did not selected anything */
2912 _clipboard_set_selected_data (ModestFolderView *folder_view,
2915 ModestFolderViewPrivate *priv = NULL;
2916 TnyFolderStore *folder = NULL;
2917 gboolean retval = FALSE;
2919 g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (folder_view), FALSE);
2920 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
2922 /* Set selected data on clipboard */
2923 g_return_val_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard), FALSE);
2924 folder = modest_folder_view_get_selected (folder_view);
2926 /* Do not allow to select an account */
2927 if (TNY_IS_FOLDER (folder)) {
2928 modest_email_clipboard_set_data (priv->clipboard, TNY_FOLDER(folder), NULL, delete);
2933 g_object_unref (folder);
2939 _clear_hidding_filter (ModestFolderView *folder_view)
2941 ModestFolderViewPrivate *priv;
2944 g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view));
2945 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
2947 if (priv->hidding_ids != NULL) {
2948 for (i=0; i < priv->n_selected; i++)
2949 g_free (priv->hidding_ids[i]);
2950 g_free(priv->hidding_ids);
2956 on_display_name_changed (ModestAccountMgr *mgr,
2957 const gchar *account,
2960 ModestFolderView *self;
2962 self = MODEST_FOLDER_VIEW (user_data);
2964 /* Force a redraw */
2965 #if GTK_CHECK_VERSION(2, 8, 0)
2966 GtkTreeViewColumn * tree_column;
2968 tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self),
2969 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN);
2970 gtk_tree_view_column_queue_resize (tree_column);
2972 gtk_widget_queue_draw (GTK_WIDGET (self));
2977 modest_folder_view_set_cell_style (ModestFolderView *self,
2978 ModestFolderViewCellStyle cell_style)
2980 ModestFolderViewPrivate *priv = NULL;
2982 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
2983 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2985 priv->cell_style = cell_style;
2987 gtk_widget_queue_draw (GTK_WIDGET (self));