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_account_removed (TnyAccountStore *self,
87 static void on_account_inserted (TnyAccountStore *self,
91 static void on_account_changed (TnyAccountStore *self,
95 static gint cmp_rows (GtkTreeModel *tree_model,
100 static gboolean filter_row (GtkTreeModel *model,
104 static gboolean on_key_pressed (GtkWidget *self,
108 static void on_configuration_key_changed (ModestConf* conf,
110 ModestConfEvent event,
111 ModestConfNotificationId notification_id,
112 ModestFolderView *self);
115 static void on_drag_data_get (GtkWidget *widget,
116 GdkDragContext *context,
117 GtkSelectionData *selection_data,
122 static void on_drag_data_received (GtkWidget *widget,
123 GdkDragContext *context,
126 GtkSelectionData *selection_data,
131 static gboolean on_drag_motion (GtkWidget *widget,
132 GdkDragContext *context,
138 static void expand_root_items (ModestFolderView *self);
140 static gint expand_row_timeout (gpointer data);
142 static void setup_drag_and_drop (GtkTreeView *self);
144 static gboolean _clipboard_set_selected_data (ModestFolderView *folder_view,
147 static void _clear_hidding_filter (ModestFolderView *folder_view);
149 static void on_row_inserted_maybe_select_folder (GtkTreeModel *tree_model,
152 ModestFolderView *self);
154 static void on_display_name_changed (ModestAccountMgr *self,
155 const gchar *account,
159 FOLDER_SELECTION_CHANGED_SIGNAL,
160 FOLDER_DISPLAY_NAME_CHANGED_SIGNAL,
164 typedef struct _ModestFolderViewPrivate ModestFolderViewPrivate;
165 struct _ModestFolderViewPrivate {
166 TnyAccountStore *account_store;
167 TnyFolderStore *cur_folder_store;
169 TnyFolder *folder_to_select; /* folder to select after the next update */
171 gulong changed_signal;
172 gulong account_inserted_signal;
173 gulong account_removed_signal;
174 gulong account_changed_signal;
175 gulong conf_key_signal;
176 gulong display_name_changed_signal;
178 /* not unref this object, its a singlenton */
179 ModestEmailClipboard *clipboard;
181 /* Filter tree model */
185 TnyFolderStoreQuery *query;
186 guint timer_expander;
188 gchar *local_account_name;
189 gchar *visible_account_id;
190 ModestFolderViewStyle style;
192 gboolean reselect; /* we use this to force a reselection of the INBOX */
193 gboolean show_non_move;
194 gboolean reexpand; /* next time we expose, we'll expand all root folders */
196 #define MODEST_FOLDER_VIEW_GET_PRIVATE(o) \
197 (G_TYPE_INSTANCE_GET_PRIVATE((o), \
198 MODEST_TYPE_FOLDER_VIEW, \
199 ModestFolderViewPrivate))
201 static GObjectClass *parent_class = NULL;
203 static guint signals[LAST_SIGNAL] = {0};
206 modest_folder_view_get_type (void)
208 static GType my_type = 0;
210 static const GTypeInfo my_info = {
211 sizeof(ModestFolderViewClass),
212 NULL, /* base init */
213 NULL, /* base finalize */
214 (GClassInitFunc) modest_folder_view_class_init,
215 NULL, /* class finalize */
216 NULL, /* class data */
217 sizeof(ModestFolderView),
219 (GInstanceInitFunc) modest_folder_view_init,
223 static const GInterfaceInfo tny_account_store_view_info = {
224 (GInterfaceInitFunc) tny_account_store_view_init, /* interface_init */
225 NULL, /* interface_finalize */
226 NULL /* interface_data */
230 my_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
234 g_type_add_interface_static (my_type,
235 TNY_TYPE_ACCOUNT_STORE_VIEW,
236 &tny_account_store_view_info);
242 modest_folder_view_class_init (ModestFolderViewClass *klass)
244 GObjectClass *gobject_class;
245 GtkTreeViewClass *treeview_class;
246 gobject_class = (GObjectClass*) klass;
247 treeview_class = (GtkTreeViewClass*) klass;
249 parent_class = g_type_class_peek_parent (klass);
250 gobject_class->finalize = modest_folder_view_finalize;
252 g_type_class_add_private (gobject_class,
253 sizeof(ModestFolderViewPrivate));
255 signals[FOLDER_SELECTION_CHANGED_SIGNAL] =
256 g_signal_new ("folder_selection_changed",
257 G_TYPE_FROM_CLASS (gobject_class),
259 G_STRUCT_OFFSET (ModestFolderViewClass,
260 folder_selection_changed),
262 modest_marshal_VOID__POINTER_BOOLEAN,
263 G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_BOOLEAN);
266 * This signal is emitted whenever the currently selected
267 * folder display name is computed. Note that the name could
268 * be different to the folder name, because we could append
269 * the unread messages count to the folder name to build the
270 * folder display name
272 signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL] =
273 g_signal_new ("folder-display-name-changed",
274 G_TYPE_FROM_CLASS (gobject_class),
276 G_STRUCT_OFFSET (ModestFolderViewClass,
277 folder_display_name_changed),
279 g_cclosure_marshal_VOID__STRING,
280 G_TYPE_NONE, 1, G_TYPE_STRING);
282 treeview_class->select_cursor_parent = NULL;
286 /* Simplify checks for NULLs: */
288 strings_are_equal (const gchar *a, const gchar *b)
294 return (strcmp (a, b) == 0);
301 on_model_foreach_set_name(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
303 GObject *instance = NULL;
305 gtk_tree_model_get (model, iter,
306 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
310 return FALSE; /* keep walking */
312 if (!TNY_IS_ACCOUNT (instance)) {
313 g_object_unref (instance);
314 return FALSE; /* keep walking */
317 /* Check if this is the looked-for account: */
318 TnyAccount *this_account = TNY_ACCOUNT (instance);
319 TnyAccount *account = TNY_ACCOUNT (data);
321 const gchar *this_account_id = tny_account_get_id(this_account);
322 const gchar *account_id = tny_account_get_id(account);
323 g_object_unref (instance);
326 /* printf ("DEBUG: %s: this_account_id=%s, account_id=%s\n", __FUNCTION__, this_account_id, account_id); */
327 if (strings_are_equal(this_account_id, account_id)) {
328 /* Tell the model that the data has changed, so that
329 * it calls the cell_data_func callbacks again: */
330 /* TODO: This does not seem to actually cause the new string to be shown: */
331 gtk_tree_model_row_changed (model, path, iter);
333 return TRUE; /* stop walking */
336 return FALSE; /* keep walking */
341 ModestFolderView *self;
342 gchar *previous_name;
343 } GetMmcAccountNameData;
346 on_get_mmc_account_name (TnyStoreAccount* account, gpointer user_data)
348 /* printf ("DEBU1G: %s: account name=%s\n", __FUNCTION__, tny_account_get_name (TNY_ACCOUNT(account))); */
350 GetMmcAccountNameData *data = (GetMmcAccountNameData*)user_data;
352 if (!strings_are_equal (
353 tny_account_get_name(TNY_ACCOUNT(account)),
354 data->previous_name)) {
356 /* Tell the model that the data has changed, so that
357 * it calls the cell_data_func callbacks again: */
358 ModestFolderView *self = data->self;
359 GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
361 gtk_tree_model_foreach(model, on_model_foreach_set_name, account);
364 g_free (data->previous_name);
365 g_slice_free (GetMmcAccountNameData, data);
369 text_cell_data (GtkTreeViewColumn *column,
370 GtkCellRenderer *renderer,
371 GtkTreeModel *tree_model,
375 ModestFolderViewPrivate *priv;
376 GObject *rendobj = (GObject *) renderer;
378 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
379 GObject *instance = NULL;
381 gtk_tree_model_get (tree_model, iter,
382 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &fname,
383 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
384 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
386 if (!fname || !instance)
389 ModestFolderView *self = MODEST_FOLDER_VIEW (data);
390 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
392 gchar *item_name = NULL;
393 gint item_weight = 400;
395 if (type != TNY_FOLDER_TYPE_ROOT) {
398 if (modest_tny_folder_is_local_folder (TNY_FOLDER (instance)) ||
399 modest_tny_folder_is_memory_card_folder (TNY_FOLDER (instance))) {
400 type = modest_tny_folder_get_local_or_mmc_folder_type (TNY_FOLDER (instance));
401 if (type != TNY_FOLDER_TYPE_UNKNOWN) {
403 fname = g_strdup (modest_local_folder_info_get_type_display_name (type));
407 /* note: we cannot reliably get the counts from the tree model, we need
408 * to use explicit calls on tny_folder for some reason.
410 /* Select the number to show: the unread or unsent messages. in case of outbox/drafts, show all */
411 if ((type == TNY_FOLDER_TYPE_DRAFTS) ||
412 (type == TNY_FOLDER_TYPE_OUTBOX) ||
413 (type == TNY_FOLDER_TYPE_MERGE)) /* _OUTBOX actually returns _MERGE... */
414 number = tny_folder_get_all_count (TNY_FOLDER(instance));
416 number = tny_folder_get_unread_count (TNY_FOLDER(instance));
418 /* Use bold font style if there are unread or unset messages */
420 if (type == TNY_FOLDER_TYPE_INBOX)
421 item_name = g_strdup_printf ("%s (%d)", _("mcen_me_folder_inbox"), number);
423 item_name = g_strdup_printf ("%s (%d)", fname, number);
426 if (type == TNY_FOLDER_TYPE_INBOX)
427 item_name = g_strdup (_("mcen_me_folder_inbox"));
429 item_name = g_strdup (fname);
433 } else if (TNY_IS_ACCOUNT (instance)) {
434 /* If it's a server account */
435 if (modest_tny_account_is_virtual_local_folders (TNY_ACCOUNT (instance))) {
436 item_name = g_strdup (priv->local_account_name);
438 } else if (modest_tny_account_is_memory_card_account (TNY_ACCOUNT (instance))) {
439 /* fname is only correct when the items are first
440 * added to the model, not when the account is
441 * changed later, so get the name from the account
443 item_name = g_strdup (tny_account_get_name (TNY_ACCOUNT (instance)));
446 item_name = g_strdup (fname);
452 item_name = g_strdup ("unknown");
454 if (item_name && item_weight) {
455 /* Set the name in the treeview cell: */
456 g_object_set (rendobj,"text", item_name, "weight", item_weight, NULL);
458 /* Notify display name observers */
459 /* TODO: What listens for this signal, and how can it use only the new name? */
460 if (((GObject *) priv->cur_folder_store) == instance) {
461 g_signal_emit (G_OBJECT(self),
462 signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
469 /* If it is a Memory card account, make sure that we have the correct name.
470 * This function will be trigerred again when the name has been retrieved: */
471 if (TNY_IS_STORE_ACCOUNT (instance) &&
472 modest_tny_account_is_memory_card_account (TNY_ACCOUNT (instance))) {
474 /* Get the account name asynchronously: */
475 GetMmcAccountNameData *callback_data =
476 g_slice_new0(GetMmcAccountNameData);
477 callback_data->self = self;
479 const gchar *name = tny_account_get_name (TNY_ACCOUNT(instance));
481 callback_data->previous_name = g_strdup (name);
483 modest_tny_account_get_mmc_account_name (TNY_STORE_ACCOUNT (instance),
484 on_get_mmc_account_name, callback_data);
488 g_object_unref (G_OBJECT (instance));
496 GdkPixbuf *pixbuf_open;
497 GdkPixbuf *pixbuf_close;
501 static inline GdkPixbuf *
502 get_composite_pixbuf (const gchar *icon_name,
504 GdkPixbuf *base_pixbuf)
506 GdkPixbuf *emblem, *retval = NULL;
508 emblem = modest_platform_get_icon (icon_name, size);
510 retval = gdk_pixbuf_copy (base_pixbuf);
511 gdk_pixbuf_composite (emblem, retval, 0, 0,
512 MIN (gdk_pixbuf_get_width (emblem),
513 gdk_pixbuf_get_width (retval)),
514 MIN (gdk_pixbuf_get_height (emblem),
515 gdk_pixbuf_get_height (retval)),
516 0, 0, 1, 1, GDK_INTERP_NEAREST, 255);
517 g_object_unref (emblem);
522 static inline ThreePixbufs *
523 get_composite_icons (const gchar *icon_code,
525 GdkPixbuf **pixbuf_open,
526 GdkPixbuf **pixbuf_close)
528 ThreePixbufs *retval;
531 *pixbuf = gdk_pixbuf_copy (modest_platform_get_icon (icon_code, MODEST_ICON_SIZE_SMALL));
534 *pixbuf_open = get_composite_pixbuf ("qgn_list_gene_fldr_exp",
535 MODEST_ICON_SIZE_SMALL,
539 *pixbuf_close = get_composite_pixbuf ("qgn_list_gene_fldr_clp",
540 MODEST_ICON_SIZE_SMALL,
543 retval = g_slice_new0 (ThreePixbufs);
545 retval->pixbuf = g_object_ref (*pixbuf);
547 retval->pixbuf_open = g_object_ref (*pixbuf_open);
549 retval->pixbuf_close = g_object_ref (*pixbuf_close);
555 get_folder_icons (TnyFolderType type, GObject *instance)
557 static GdkPixbuf *inbox_pixbuf = NULL, *outbox_pixbuf = NULL,
558 *junk_pixbuf = NULL, *sent_pixbuf = NULL,
559 *trash_pixbuf = NULL, *draft_pixbuf = NULL,
560 *normal_pixbuf = NULL, *anorm_pixbuf = NULL,
561 *ammc_pixbuf = NULL, *avirt_pixbuf = NULL;
563 static GdkPixbuf *inbox_pixbuf_open = NULL, *outbox_pixbuf_open = NULL,
564 *junk_pixbuf_open = NULL, *sent_pixbuf_open = NULL,
565 *trash_pixbuf_open = NULL, *draft_pixbuf_open = NULL,
566 *normal_pixbuf_open = NULL, *anorm_pixbuf_open = NULL,
567 *ammc_pixbuf_open = NULL, *avirt_pixbuf_open = NULL;
569 static GdkPixbuf *inbox_pixbuf_close = NULL, *outbox_pixbuf_close = NULL,
570 *junk_pixbuf_close = NULL, *sent_pixbuf_close = NULL,
571 *trash_pixbuf_close = NULL, *draft_pixbuf_close = NULL,
572 *normal_pixbuf_close = NULL, *anorm_pixbuf_close = NULL,
573 *ammc_pixbuf_close = NULL, *avirt_pixbuf_close = NULL;
575 ThreePixbufs *retval = NULL;
577 /* MERGE is not needed anymore as the folder now has the correct type jschmid */
578 /* We include the MERGE type here because it's used to create
579 the local OUTBOX folder */
580 if (type == TNY_FOLDER_TYPE_NORMAL ||
581 type == TNY_FOLDER_TYPE_UNKNOWN) {
582 type = modest_tny_folder_guess_folder_type (TNY_FOLDER (instance));
586 case TNY_FOLDER_TYPE_INVALID:
587 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
590 case TNY_FOLDER_TYPE_ROOT:
591 if (TNY_IS_ACCOUNT (instance)) {
593 if (modest_tny_account_is_virtual_local_folders (TNY_ACCOUNT (instance))) {
594 retval = get_composite_icons (MODEST_FOLDER_ICON_LOCAL_FOLDERS,
597 &avirt_pixbuf_close);
599 const gchar *account_id = tny_account_get_id (TNY_ACCOUNT (instance));
601 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
602 retval = get_composite_icons (MODEST_FOLDER_ICON_MMC,
607 retval = get_composite_icons (MODEST_FOLDER_ICON_ACCOUNT,
610 &anorm_pixbuf_close);
615 case TNY_FOLDER_TYPE_INBOX:
616 retval = get_composite_icons (MODEST_FOLDER_ICON_INBOX,
619 &inbox_pixbuf_close);
621 case TNY_FOLDER_TYPE_OUTBOX:
622 retval = get_composite_icons (MODEST_FOLDER_ICON_OUTBOX,
625 &outbox_pixbuf_close);
627 case TNY_FOLDER_TYPE_JUNK:
628 retval = get_composite_icons (MODEST_FOLDER_ICON_JUNK,
633 case TNY_FOLDER_TYPE_SENT:
634 retval = get_composite_icons (MODEST_FOLDER_ICON_SENT,
639 case TNY_FOLDER_TYPE_TRASH:
640 retval = get_composite_icons (MODEST_FOLDER_ICON_TRASH,
643 &trash_pixbuf_close);
645 case TNY_FOLDER_TYPE_DRAFTS:
646 retval = get_composite_icons (MODEST_FOLDER_ICON_DRAFTS,
649 &draft_pixbuf_close);
651 case TNY_FOLDER_TYPE_NORMAL:
653 retval = get_composite_icons (MODEST_FOLDER_ICON_NORMAL,
656 &normal_pixbuf_close);
664 free_pixbufs (ThreePixbufs *pixbufs)
667 g_object_unref (pixbufs->pixbuf);
668 if (pixbufs->pixbuf_open)
669 g_object_unref (pixbufs->pixbuf_open);
670 if (pixbufs->pixbuf_close)
671 g_object_unref (pixbufs->pixbuf_close);
672 g_slice_free (ThreePixbufs, pixbufs);
676 icon_cell_data (GtkTreeViewColumn *column,
677 GtkCellRenderer *renderer,
678 GtkTreeModel *tree_model,
682 GObject *rendobj = NULL, *instance = NULL;
683 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
684 gboolean has_children;
685 ThreePixbufs *pixbufs;
687 rendobj = (GObject *) renderer;
689 gtk_tree_model_get (tree_model, iter,
690 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
691 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
697 has_children = gtk_tree_model_iter_has_child (tree_model, iter);
698 pixbufs = get_folder_icons (type, instance);
699 g_object_unref (instance);
702 g_object_set (rendobj, "pixbuf", pixbufs->pixbuf, NULL);
705 g_object_set (rendobj, "pixbuf-expander-open", pixbufs->pixbuf_open, NULL);
706 g_object_set (rendobj, "pixbuf-expander-closed", pixbufs->pixbuf_close, NULL);
709 free_pixbufs (pixbufs);
715 add_columns (GtkWidget *treeview)
717 GtkTreeViewColumn *column;
718 GtkCellRenderer *renderer;
719 GtkTreeSelection *sel;
722 column = gtk_tree_view_column_new ();
724 /* Set icon and text render function */
725 renderer = gtk_cell_renderer_pixbuf_new();
726 gtk_tree_view_column_pack_start (column, renderer, FALSE);
727 gtk_tree_view_column_set_cell_data_func(column, renderer,
728 icon_cell_data, treeview, NULL);
730 renderer = gtk_cell_renderer_text_new();
731 g_object_set (renderer, "ellipsize", PANGO_ELLIPSIZE_END,
732 "ellipsize-set", TRUE, NULL);
733 gtk_tree_view_column_pack_start (column, renderer, TRUE);
734 gtk_tree_view_column_set_cell_data_func(column, renderer,
735 text_cell_data, treeview, NULL);
737 /* Set selection mode */
738 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(treeview));
739 gtk_tree_selection_set_mode (sel, GTK_SELECTION_SINGLE);
741 /* Set treeview appearance */
742 gtk_tree_view_column_set_spacing (column, 2);
743 gtk_tree_view_column_set_resizable (column, TRUE);
744 gtk_tree_view_column_set_fixed_width (column, TRUE);
745 gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(treeview), FALSE);
746 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(treeview), FALSE);
749 gtk_tree_view_append_column (GTK_TREE_VIEW(treeview),column);
753 modest_folder_view_init (ModestFolderView *obj)
755 ModestFolderViewPrivate *priv;
758 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
760 priv->timer_expander = 0;
761 priv->account_store = NULL;
763 priv->style = MODEST_FOLDER_VIEW_STYLE_SHOW_ALL;
764 priv->cur_folder_store = NULL;
765 priv->visible_account_id = NULL;
766 priv->folder_to_select = NULL;
768 priv->reexpand = TRUE;
770 /* Initialize the local account name */
771 conf = modest_runtime_get_conf();
772 priv->local_account_name = modest_conf_get_string (conf, MODEST_CONF_DEVICE_NAME, NULL);
774 /* Init email clipboard */
775 priv->clipboard = modest_runtime_get_email_clipboard ();
776 priv->hidding_ids = NULL;
777 priv->n_selected = 0;
778 priv->reselect = FALSE;
779 priv->show_non_move = TRUE;
782 add_columns (GTK_WIDGET (obj));
784 /* Setup drag and drop */
785 setup_drag_and_drop (GTK_TREE_VIEW(obj));
787 /* Connect signals */
788 g_signal_connect (G_OBJECT (obj),
790 G_CALLBACK (on_key_pressed), NULL);
792 priv->display_name_changed_signal =
793 g_signal_connect (modest_runtime_get_account_mgr (),
794 "display_name_changed",
795 G_CALLBACK (on_display_name_changed),
799 * Track changes in the local account name (in the device it
800 * will be the device name)
802 priv->conf_key_signal = g_signal_connect (G_OBJECT(conf),
804 G_CALLBACK(on_configuration_key_changed),
809 tny_account_store_view_init (gpointer g, gpointer iface_data)
811 TnyAccountStoreViewIface *klass = (TnyAccountStoreViewIface *)g;
813 klass->set_account_store = modest_folder_view_set_account_store;
817 modest_folder_view_finalize (GObject *obj)
819 ModestFolderViewPrivate *priv;
820 GtkTreeSelection *sel;
822 g_return_if_fail (obj);
824 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
826 if (priv->timer_expander != 0) {
827 g_source_remove (priv->timer_expander);
828 priv->timer_expander = 0;
831 if (priv->account_store) {
832 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
833 priv->account_inserted_signal);
834 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
835 priv->account_removed_signal);
836 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
837 priv->account_changed_signal);
838 g_object_unref (G_OBJECT(priv->account_store));
839 priv->account_store = NULL;
843 g_object_unref (G_OBJECT (priv->query));
847 if (priv->folder_to_select) {
848 g_object_unref (G_OBJECT(priv->folder_to_select));
849 priv->folder_to_select = NULL;
852 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(obj));
854 g_signal_handler_disconnect (G_OBJECT(sel), priv->changed_signal);
856 g_free (priv->local_account_name);
857 g_free (priv->visible_account_id);
859 if (priv->conf_key_signal) {
860 g_signal_handler_disconnect (modest_runtime_get_conf (),
861 priv->conf_key_signal);
862 priv->conf_key_signal = 0;
865 if (priv->cur_folder_store) {
866 g_object_unref (priv->cur_folder_store);
867 priv->cur_folder_store = NULL;
870 /* Clear hidding array created by cut operation */
871 _clear_hidding_filter (MODEST_FOLDER_VIEW (obj));
873 G_OBJECT_CLASS(parent_class)->finalize (obj);
878 modest_folder_view_set_account_store (TnyAccountStoreView *self, TnyAccountStore *account_store)
880 ModestFolderViewPrivate *priv;
883 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
884 g_return_if_fail (TNY_IS_ACCOUNT_STORE (account_store));
886 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
887 device = tny_account_store_get_device (account_store);
889 if (G_UNLIKELY (priv->account_store)) {
891 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
892 priv->account_inserted_signal))
893 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
894 priv->account_inserted_signal);
895 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
896 priv->account_removed_signal))
897 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
898 priv->account_removed_signal);
899 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
900 priv->account_changed_signal))
901 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
902 priv->account_changed_signal);
903 g_object_unref (G_OBJECT (priv->account_store));
906 priv->account_store = g_object_ref (G_OBJECT (account_store));
908 priv->account_removed_signal =
909 g_signal_connect (G_OBJECT(account_store), "account_removed",
910 G_CALLBACK (on_account_removed), self);
912 priv->account_inserted_signal =
913 g_signal_connect (G_OBJECT(account_store), "account_inserted",
914 G_CALLBACK (on_account_inserted), self);
916 priv->account_changed_signal =
917 g_signal_connect (G_OBJECT(account_store), "account_changed",
918 G_CALLBACK (on_account_changed), self);
920 modest_folder_view_update_model (MODEST_FOLDER_VIEW (self), account_store);
921 priv->reselect = FALSE;
922 modest_folder_view_select_first_inbox_or_local (MODEST_FOLDER_VIEW (self));
924 g_object_unref (G_OBJECT (device));
928 on_account_inserted (TnyAccountStore *account_store,
932 ModestFolderViewPrivate *priv;
933 GtkTreeModel *sort_model, *filter_model;
935 /* Ignore transport account insertions, we're not showing them
936 in the folder view */
937 if (TNY_IS_TRANSPORT_ACCOUNT (account))
940 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (user_data);
943 /* If we're adding a new account, and there is no previous
944 one, we need to select the visible server account */
945 if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE &&
946 !priv->visible_account_id)
947 modest_widget_memory_restore (modest_runtime_get_conf(),
948 G_OBJECT (user_data),
949 MODEST_CONF_FOLDER_VIEW_KEY);
951 if (!GTK_IS_TREE_VIEW(user_data)) {
952 g_warning ("BUG: %s: not a valid tree view", __FUNCTION__);
956 /* Get the inner model */
957 /* check, is some rare cases, we did not get the right thing here,
959 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (user_data));
960 if (!GTK_IS_TREE_MODEL_FILTER(filter_model)) {
961 g_warning ("BUG: %s: not a valid filter model", __FUNCTION__);
965 /* check, is some rare cases, we did not get the right thing here,
967 sort_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
968 if (!GTK_IS_TREE_MODEL_SORT(sort_model)) {
969 g_warning ("BUG: %s: not a valid sort model", __FUNCTION__);
973 /* Insert the account in the model */
974 tny_list_append (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
977 /* Refilter the model */
978 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
983 same_account_selected (ModestFolderView *self,
986 ModestFolderViewPrivate *priv;
987 gboolean same_account = FALSE;
989 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
991 if (priv->cur_folder_store) {
992 TnyAccount *selected_folder_account = NULL;
994 if (TNY_IS_FOLDER (priv->cur_folder_store)) {
995 selected_folder_account =
996 modest_tny_folder_get_account (TNY_FOLDER (priv->cur_folder_store));
998 selected_folder_account =
999 TNY_ACCOUNT (g_object_ref (priv->cur_folder_store));
1002 if (selected_folder_account == account)
1003 same_account = TRUE;
1005 g_object_unref (selected_folder_account);
1007 return same_account;
1012 * Selects the first inbox or the local account in an idle
1015 on_idle_select_first_inbox_or_local (gpointer user_data)
1017 ModestFolderView *self = MODEST_FOLDER_VIEW (user_data);
1019 gdk_threads_enter ();
1020 modest_folder_view_select_first_inbox_or_local (self);
1021 gdk_threads_leave ();
1027 on_account_changed (TnyAccountStore *account_store,
1028 TnyAccount *tny_account,
1031 ModestFolderView *self;
1032 ModestFolderViewPrivate *priv;
1033 GtkTreeModel *sort_model, *filter_model;
1034 GtkTreeSelection *sel;
1035 gboolean same_account;
1037 /* Ignore transport account insertions, we're not showing them
1038 in the folder view */
1039 if (TNY_IS_TRANSPORT_ACCOUNT (tny_account))
1042 if (!MODEST_IS_FOLDER_VIEW(user_data)) {
1043 g_warning ("BUG: %s: not a valid folder view", __FUNCTION__);
1047 self = MODEST_FOLDER_VIEW (user_data);
1048 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (user_data);
1050 /* Get the inner model */
1051 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (user_data));
1052 if (!GTK_IS_TREE_MODEL_FILTER(filter_model)) {
1053 g_warning ("BUG: %s: not a valid filter model", __FUNCTION__);
1057 sort_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
1058 if (!GTK_IS_TREE_MODEL_SORT(sort_model)) {
1059 g_warning ("BUG: %s: not a valid sort model", __FUNCTION__);
1063 /* Invalidate the cur_folder_store only if the selected folder
1064 belongs to the account that is being removed */
1065 same_account = same_account_selected (self, tny_account);
1067 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1068 gtk_tree_selection_unselect_all (sel);
1071 /* Remove the account from the model */
1072 tny_list_remove (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
1073 G_OBJECT (tny_account));
1075 /* Insert the account in the model */
1076 tny_list_append (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
1077 G_OBJECT (tny_account));
1079 /* Refilter the model */
1080 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1082 /* Select the first INBOX if the currently selected folder
1083 belongs to the account that is being deleted */
1084 if (same_account && !MODEST_IS_TNY_LOCAL_FOLDERS_ACCOUNT (tny_account))
1085 g_idle_add (on_idle_select_first_inbox_or_local, self);
1089 on_account_removed (TnyAccountStore *account_store,
1090 TnyAccount *account,
1093 ModestFolderView *self = NULL;
1094 ModestFolderViewPrivate *priv;
1095 GtkTreeModel *sort_model, *filter_model;
1096 GtkTreeSelection *sel = NULL;
1097 gboolean same_account = FALSE;
1099 /* Ignore transport account removals, we're not showing them
1100 in the folder view */
1101 if (TNY_IS_TRANSPORT_ACCOUNT (account))
1104 if (!MODEST_IS_FOLDER_VIEW(user_data)) {
1105 g_warning ("BUG: %s: not a valid folder view", __FUNCTION__);
1109 self = MODEST_FOLDER_VIEW (user_data);
1110 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1112 /* Invalidate the cur_folder_store only if the selected folder
1113 belongs to the account that is being removed */
1114 same_account = same_account_selected (self, account);
1116 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1117 gtk_tree_selection_unselect_all (sel);
1120 /* Invalidate row to select only if the folder to select
1121 belongs to the account that is being removed*/
1122 if (priv->folder_to_select) {
1123 TnyAccount *folder_to_select_account = NULL;
1125 folder_to_select_account = tny_folder_get_account (priv->folder_to_select);
1126 if (folder_to_select_account == account) {
1127 modest_folder_view_disable_next_folder_selection (self);
1128 g_object_unref (priv->folder_to_select);
1129 priv->folder_to_select = NULL;
1131 g_object_unref (folder_to_select_account);
1134 /* Remove the account from the model */
1135 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1136 if (!GTK_IS_TREE_MODEL_FILTER(filter_model)) {
1137 g_warning ("BUG: %s: not a valid filter model", __FUNCTION__);
1141 sort_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
1142 if (!GTK_IS_TREE_MODEL_SORT(sort_model)) {
1143 g_warning ("BUG: %s: not a valid sort model", __FUNCTION__);
1147 tny_list_remove (TNY_LIST (gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model))),
1148 G_OBJECT (account));
1150 /* If the removed account is the currently viewed one then
1151 clear the configuration value. The new visible account will be the default account */
1152 if (priv->visible_account_id &&
1153 !strcmp (priv->visible_account_id, tny_account_get_id (account))) {
1155 /* Clear the current visible account_id */
1156 modest_folder_view_set_account_id_of_visible_server_account (self, NULL);
1158 /* Call the restore method, this will set the new visible account */
1159 modest_widget_memory_restore (modest_runtime_get_conf(), G_OBJECT(self),
1160 MODEST_CONF_FOLDER_VIEW_KEY);
1163 /* Refilter the model */
1164 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1166 /* Select the first INBOX if the currently selected folder
1167 belongs to the account that is being deleted */
1169 g_idle_add (on_idle_select_first_inbox_or_local, self);
1173 modest_folder_view_set_title (ModestFolderView *self, const gchar *title)
1175 GtkTreeViewColumn *col;
1177 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
1179 col = gtk_tree_view_get_column (GTK_TREE_VIEW(self), 0);
1181 g_printerr ("modest: failed get column for title\n");
1185 gtk_tree_view_column_set_title (col, title);
1186 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self),
1191 modest_folder_view_on_map (ModestFolderView *self,
1192 GdkEventExpose *event,
1195 ModestFolderViewPrivate *priv;
1197 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1199 /* This won't happen often */
1200 if (G_UNLIKELY (priv->reselect)) {
1201 /* Select the first inbox or the local account if not found */
1203 /* TODO: this could cause a lock at startup, so we
1204 comment it for the moment. We know that this will
1205 be a bug, because the INBOX is not selected, but we
1206 need to rewrite some parts of Modest to avoid the
1207 deathlock situation */
1208 /* TODO: check if this is still the case */
1209 priv->reselect = FALSE;
1210 modest_folder_view_select_first_inbox_or_local (self);
1211 /* Notify the display name observers */
1212 g_signal_emit (G_OBJECT(self),
1213 signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
1217 if (priv->reexpand) {
1218 expand_root_items (self);
1219 priv->reexpand = FALSE;
1226 modest_folder_view_new (TnyFolderStoreQuery *query)
1229 ModestFolderViewPrivate *priv;
1230 GtkTreeSelection *sel;
1232 self = G_OBJECT (g_object_new (MODEST_TYPE_FOLDER_VIEW, NULL));
1233 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1236 priv->query = g_object_ref (query);
1238 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1239 priv->changed_signal = g_signal_connect (sel, "changed",
1240 G_CALLBACK (on_selection_changed), self);
1242 g_signal_connect (self, "expose-event", G_CALLBACK (modest_folder_view_on_map), NULL);
1244 return GTK_WIDGET(self);
1247 /* this feels dirty; any other way to expand all the root items? */
1249 expand_root_items (ModestFolderView *self)
1252 GtkTreeModel *model;
1255 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1256 path = gtk_tree_path_new_first ();
1258 /* all folders should have child items, so.. */
1260 gtk_tree_view_expand_row (GTK_TREE_VIEW(self), path, FALSE);
1261 gtk_tree_path_next (path);
1262 } while (gtk_tree_model_get_iter (model, &iter, path));
1264 gtk_tree_path_free (path);
1268 * We use this function to implement the
1269 * MODEST_FOLDER_VIEW_STYLE_SHOW_ONE style. We only show the default
1270 * account in this case, and the local folders.
1273 filter_row (GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
1275 ModestFolderViewPrivate *priv;
1276 gboolean retval = TRUE;
1277 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1278 GObject *instance = NULL;
1279 const gchar *id = NULL;
1281 gboolean found = FALSE;
1282 gboolean cleared = FALSE;
1284 g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (data), FALSE);
1285 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (data);
1287 gtk_tree_model_get (model, iter,
1288 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
1289 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
1292 /* Do not show if there is no instance, this could indeed
1293 happen when the model is being modified while it's being
1294 drawn. This could occur for example when moving folders
1299 if (type == TNY_FOLDER_TYPE_ROOT) {
1300 /* TNY_FOLDER_TYPE_ROOT means that the instance is an
1301 account instead of a folder. */
1302 if (TNY_IS_ACCOUNT (instance)) {
1303 TnyAccount *acc = TNY_ACCOUNT (instance);
1304 const gchar *account_id = tny_account_get_id (acc);
1306 /* If it isn't a special folder,
1307 * don't show it unless it is the visible account: */
1308 if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE &&
1309 !modest_tny_account_is_virtual_local_folders (acc) &&
1310 strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
1312 /* Show only the visible account id */
1313 if (priv->visible_account_id) {
1314 if (strcmp (account_id, priv->visible_account_id))
1321 /* Never show these to the user. They are merged into one folder
1322 * in the local-folders account instead: */
1323 if (retval && MODEST_IS_TNY_OUTBOX_ACCOUNT (acc))
1328 /* Check hiding (if necessary) */
1329 cleared = modest_email_clipboard_cleared (priv->clipboard);
1330 if ((retval) && (!cleared) && (TNY_IS_FOLDER (instance))) {
1331 id = tny_folder_get_id (TNY_FOLDER(instance));
1332 if (priv->hidding_ids != NULL)
1333 for (i=0; i < priv->n_selected && !found; i++)
1334 if (priv->hidding_ids[i] != NULL && id != NULL)
1335 found = (!strcmp (priv->hidding_ids[i], id));
1341 /* If this is a move to dialog, hide Sent, Outbox and Drafts
1342 folder as no message can be move there according to UI specs */
1343 if (!priv->show_non_move) {
1345 case TNY_FOLDER_TYPE_OUTBOX:
1346 case TNY_FOLDER_TYPE_SENT:
1347 case TNY_FOLDER_TYPE_DRAFTS:
1350 case TNY_FOLDER_TYPE_UNKNOWN:
1351 case TNY_FOLDER_TYPE_NORMAL:
1352 type = modest_tny_folder_guess_folder_type(TNY_FOLDER(instance));
1353 if (type == TNY_FOLDER_TYPE_INVALID)
1354 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1356 if (type == TNY_FOLDER_TYPE_OUTBOX ||
1357 type == TNY_FOLDER_TYPE_SENT
1358 || type == TNY_FOLDER_TYPE_DRAFTS)
1367 g_object_unref (instance);
1374 modest_folder_view_update_model (ModestFolderView *self,
1375 TnyAccountStore *account_store)
1377 ModestFolderViewPrivate *priv;
1378 GtkTreeModel *model /* , *old_model */;
1379 GtkTreeModel *filter_model = NULL, *sortable = NULL;
1381 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW (self), FALSE);
1382 g_return_val_if_fail (account_store && MODEST_IS_TNY_ACCOUNT_STORE(account_store),
1385 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1387 /* Notify that there is no folder selected */
1388 g_signal_emit (G_OBJECT(self),
1389 signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
1391 if (priv->cur_folder_store) {
1392 g_object_unref (priv->cur_folder_store);
1393 priv->cur_folder_store = NULL;
1396 /* FIXME: the local accounts are not shown when the query
1397 selects only the subscribed folders */
1398 model = tny_gtk_folder_store_tree_model_new (NULL);
1400 /* Get the accounts: */
1401 tny_account_store_get_accounts (TNY_ACCOUNT_STORE(account_store),
1403 TNY_ACCOUNT_STORE_STORE_ACCOUNTS);
1405 sortable = gtk_tree_model_sort_new_with_model (model);
1406 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
1407 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
1408 GTK_SORT_ASCENDING);
1409 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
1410 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
1411 cmp_rows, NULL, NULL);
1413 /* Create filter model */
1414 filter_model = gtk_tree_model_filter_new (sortable, NULL);
1415 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
1421 gtk_tree_view_set_model (GTK_TREE_VIEW(self), filter_model);
1422 g_signal_connect (G_OBJECT(filter_model), "row-inserted",
1423 (GCallback) on_row_inserted_maybe_select_folder, self);
1425 g_object_unref (model);
1426 g_object_unref (filter_model);
1427 g_object_unref (sortable);
1429 /* Force a reselection of the INBOX next time the widget is shown */
1430 priv->reselect = TRUE;
1437 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
1439 GtkTreeModel *model = NULL;
1440 TnyFolderStore *folder = NULL;
1442 ModestFolderView *tree_view = NULL;
1443 ModestFolderViewPrivate *priv = NULL;
1444 gboolean selected = FALSE;
1446 g_return_if_fail (sel);
1447 g_return_if_fail (user_data);
1449 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
1451 selected = gtk_tree_selection_get_selected (sel, &model, &iter);
1453 tree_view = MODEST_FOLDER_VIEW (user_data);
1456 gtk_tree_model_get (model, &iter,
1457 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
1460 /* If the folder is the same do not notify */
1461 if (folder && priv->cur_folder_store == folder) {
1462 g_object_unref (folder);
1467 /* Current folder was unselected */
1468 if (priv->cur_folder_store) {
1469 /* We must do this firstly because a libtinymail-camel
1470 implementation detail. If we issue the signal
1471 before doing the sync_async, then that signal could
1472 cause (and it actually does it) a free of the
1473 summary of the folder (because the main window will
1474 clear the headers view */
1475 if (TNY_IS_FOLDER(priv->cur_folder_store))
1476 tny_folder_sync_async (TNY_FOLDER(priv->cur_folder_store),
1477 FALSE, NULL, NULL, NULL);
1479 g_signal_emit (G_OBJECT(tree_view), signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
1480 priv->cur_folder_store, FALSE);
1482 g_object_unref (priv->cur_folder_store);
1483 priv->cur_folder_store = NULL;
1486 /* New current references */
1487 priv->cur_folder_store = folder;
1489 /* New folder has been selected. Do not notify if there is
1490 nothing new selected */
1492 g_signal_emit (G_OBJECT(tree_view),
1493 signals[FOLDER_SELECTION_CHANGED_SIGNAL],
1494 0, priv->cur_folder_store, TRUE);
1499 modest_folder_view_get_selected (ModestFolderView *self)
1501 ModestFolderViewPrivate *priv;
1503 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW(self), NULL);
1505 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1506 if (priv->cur_folder_store)
1507 g_object_ref (priv->cur_folder_store);
1509 return priv->cur_folder_store;
1513 get_cmp_rows_type_pos (GObject *folder)
1515 /* Remote accounts -> Local account -> MMC account .*/
1518 if (TNY_IS_ACCOUNT (folder) &&
1519 modest_tny_account_is_virtual_local_folders (
1520 TNY_ACCOUNT (folder))) {
1522 } else if (TNY_IS_ACCOUNT (folder)) {
1523 TnyAccount *account = TNY_ACCOUNT (folder);
1524 const gchar *account_id = tny_account_get_id (account);
1525 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
1531 printf ("DEBUG: %s: unexpected type.\n", __FUNCTION__);
1532 return -1; /* Should never happen */
1537 get_cmp_subfolder_type_pos (TnyFolderType t)
1539 /* Inbox, Outbox, Drafts, Sent, User */
1543 case TNY_FOLDER_TYPE_INBOX:
1546 case TNY_FOLDER_TYPE_OUTBOX:
1549 case TNY_FOLDER_TYPE_DRAFTS:
1552 case TNY_FOLDER_TYPE_SENT:
1561 * This function orders the mail accounts according to these rules:
1562 * 1st - remote accounts
1563 * 2nd - local account
1567 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1571 gchar *name1 = NULL;
1572 gchar *name2 = NULL;
1573 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1574 TnyFolderType type2 = TNY_FOLDER_TYPE_UNKNOWN;
1575 GObject *folder1 = NULL;
1576 GObject *folder2 = NULL;
1578 gtk_tree_model_get (tree_model, iter1,
1579 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name1,
1580 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
1581 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder1,
1583 gtk_tree_model_get (tree_model, iter2,
1584 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name2,
1585 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type2,
1586 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder2,
1589 /* Return if we get no folder. This could happen when folder
1590 operations are happening. The model is updated after the
1591 folder copy/move actually occurs, so there could be
1592 situations where the model to be drawn is not correct */
1593 if (!folder1 || !folder2)
1596 if (type == TNY_FOLDER_TYPE_ROOT) {
1597 /* Compare the types, so that
1598 * Remote accounts -> Local account -> MMC account .*/
1599 const gint pos1 = get_cmp_rows_type_pos (folder1);
1600 const gint pos2 = get_cmp_rows_type_pos (folder2);
1601 /* printf ("DEBUG: %s:\n type1=%s, pos1=%d\n type2=%s, pos2=%d\n",
1602 __FUNCTION__, G_OBJECT_TYPE_NAME(folder1), pos1, G_OBJECT_TYPE_NAME(folder2), pos2); */
1605 else if (pos1 > pos2)
1608 /* Compare items of the same type: */
1610 TnyAccount *account1 = NULL;
1611 if (TNY_IS_ACCOUNT (folder1))
1612 account1 = TNY_ACCOUNT (folder1);
1614 TnyAccount *account2 = NULL;
1615 if (TNY_IS_ACCOUNT (folder2))
1616 account2 = TNY_ACCOUNT (folder2);
1618 const gchar *account_id = account1 ? tny_account_get_id (account1) : NULL;
1619 const gchar *account_id2 = account2 ? tny_account_get_id (account2) : NULL;
1621 if (!account_id && !account_id2) {
1623 } else if (!account_id) {
1625 } else if (!account_id2) {
1627 } else if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
1630 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
1634 gint cmp1 = 0, cmp2 = 0;
1635 /* get the parent to know if it's a local folder */
1638 gboolean has_parent;
1639 has_parent = gtk_tree_model_iter_parent (tree_model, &parent, iter1);
1641 GObject *parent_folder;
1642 TnyFolderType parent_type = TNY_FOLDER_TYPE_UNKNOWN;
1643 gtk_tree_model_get (tree_model, &parent,
1644 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &parent_type,
1645 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &parent_folder,
1647 if ((parent_type == TNY_FOLDER_TYPE_ROOT) &&
1648 TNY_IS_ACCOUNT (parent_folder)) {
1649 if (modest_tny_account_is_virtual_local_folders (TNY_ACCOUNT (parent_folder))) {
1650 cmp1 = get_cmp_subfolder_type_pos (modest_tny_folder_get_local_or_mmc_folder_type
1651 (TNY_FOLDER (folder1)));
1652 cmp2 = get_cmp_subfolder_type_pos (modest_tny_folder_get_local_or_mmc_folder_type
1653 (TNY_FOLDER (folder2)));
1654 } else if (modest_tny_account_is_memory_card_account (TNY_ACCOUNT (parent_folder))) {
1655 if (modest_local_folder_info_get_type (tny_folder_get_name (TNY_FOLDER (folder1))) == TNY_FOLDER_TYPE_ARCHIVE) {
1658 } else if (modest_local_folder_info_get_type (tny_folder_get_name (TNY_FOLDER (folder2))) == TNY_FOLDER_TYPE_ARCHIVE) {
1664 g_object_unref (parent_folder);
1667 /* if they are not local folders */
1669 cmp1 = get_cmp_subfolder_type_pos (tny_folder_get_folder_type (TNY_FOLDER (folder1)));
1670 cmp2 = get_cmp_subfolder_type_pos (tny_folder_get_folder_type (TNY_FOLDER (folder2)));
1674 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
1676 cmp = (cmp1 - cmp2);
1681 g_object_unref(G_OBJECT(folder1));
1683 g_object_unref(G_OBJECT(folder2));
1691 /*****************************************************************************/
1692 /* DRAG and DROP stuff */
1693 /*****************************************************************************/
1695 * This function fills the #GtkSelectionData with the row and the
1696 * model that has been dragged. It's called when this widget is a
1697 * source for dnd after the event drop happened
1700 on_drag_data_get (GtkWidget *widget, GdkDragContext *context, GtkSelectionData *selection_data,
1701 guint info, guint time, gpointer data)
1703 GtkTreeSelection *selection;
1704 GtkTreeModel *model;
1706 GtkTreePath *source_row;
1708 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1709 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
1711 source_row = gtk_tree_model_get_path (model, &iter);
1712 gtk_tree_set_row_drag_data (selection_data,
1716 gtk_tree_path_free (source_row);
1720 typedef struct _DndHelper {
1721 ModestFolderView *folder_view;
1722 gboolean delete_source;
1723 GtkTreePath *source_row;
1727 dnd_helper_destroyer (DndHelper *helper)
1729 /* Free the helper */
1730 gtk_tree_path_free (helper->source_row);
1731 g_slice_free (DndHelper, helper);
1735 xfer_folder_cb (ModestMailOperation *mail_op,
1736 TnyFolder *new_folder,
1740 /* Select the folder */
1741 modest_folder_view_select_folder (MODEST_FOLDER_VIEW (user_data),
1747 /* get the folder for the row the treepath refers to. */
1748 /* folder must be unref'd */
1749 static TnyFolderStore *
1750 tree_path_to_folder (GtkTreeModel *model, GtkTreePath *path)
1753 TnyFolderStore *folder = NULL;
1755 if (gtk_tree_model_get_iter (model,&iter, path))
1756 gtk_tree_model_get (model, &iter,
1757 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
1764 * This function is used by drag_data_received_cb to manage drag and
1765 * drop of a header, i.e, and drag from the header view to the folder
1769 drag_and_drop_from_header_view (GtkTreeModel *source_model,
1770 GtkTreeModel *dest_model,
1771 GtkTreePath *dest_row,
1772 GtkSelectionData *selection_data)
1774 TnyList *headers = NULL;
1775 TnyFolder *folder = NULL, *src_folder = NULL;
1776 TnyFolderType folder_type;
1777 GtkTreeIter source_iter, dest_iter;
1778 ModestWindowMgr *mgr = NULL;
1779 ModestWindow *main_win = NULL;
1780 gchar **uris, **tmp;
1782 /* Build the list of headers */
1783 mgr = modest_runtime_get_window_mgr ();
1784 headers = tny_simple_list_new ();
1785 uris = modest_dnd_selection_data_get_paths (selection_data);
1788 while (*tmp != NULL) {
1791 gboolean first = TRUE;
1794 path = gtk_tree_path_new_from_string (*tmp);
1795 gtk_tree_model_get_iter (source_model, &source_iter, path);
1796 gtk_tree_model_get (source_model, &source_iter,
1797 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1800 /* Do not enable d&d of headers already opened */
1801 if (!modest_window_mgr_find_registered_header(mgr, header, NULL))
1802 tny_list_append (headers, G_OBJECT (header));
1804 if (G_UNLIKELY (first)) {
1805 src_folder = tny_header_get_folder (header);
1809 /* Free and go on */
1810 gtk_tree_path_free (path);
1811 g_object_unref (header);
1816 /* Get the target folder */
1817 gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
1818 gtk_tree_model_get (dest_model, &dest_iter,
1819 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
1822 if (!folder || !TNY_IS_FOLDER(folder)) {
1823 /* g_warning ("%s: not a valid target folder (%p)", __FUNCTION__, folder); */
1827 folder_type = modest_tny_folder_guess_folder_type (folder);
1828 if (folder_type == TNY_FOLDER_TYPE_INVALID) {
1829 /* g_warning ("%s: invalid target folder", __FUNCTION__); */
1830 goto cleanup; /* cannot move messages there */
1833 if (modest_tny_folder_get_rules((TNY_FOLDER(folder))) & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE) {
1834 /* g_warning ("folder not writable"); */
1835 goto cleanup; /* verboten! */
1838 /* Ask for confirmation to move */
1839 main_win = modest_window_mgr_get_main_window (mgr, FALSE); /* don't create */
1841 g_warning ("%s: BUG: no main window found", __FUNCTION__);
1845 /* Transfer messages */
1846 modest_ui_actions_transfer_messages_helper (GTK_WINDOW (main_win), src_folder,
1851 if (G_IS_OBJECT (src_folder))
1852 g_object_unref (src_folder);
1853 if (G_IS_OBJECT(folder))
1854 g_object_unref (G_OBJECT (folder));
1855 if (G_IS_OBJECT(headers))
1856 g_object_unref (headers);
1860 TnyFolderStore *src_folder;
1861 TnyFolderStore *dst_folder;
1862 ModestFolderView *folder_view;
1867 dnd_folder_info_destroyer (DndFolderInfo *info)
1869 if (info->src_folder)
1870 g_object_unref (info->src_folder);
1871 if (info->dst_folder)
1872 g_object_unref (info->dst_folder);
1873 g_slice_free (DndFolderInfo, info);
1877 dnd_on_connection_failed_destroyer (DndFolderInfo *info,
1878 GtkWindow *parent_window,
1879 TnyAccount *account)
1882 modest_ui_actions_on_account_connection_error (parent_window, account);
1884 /* Free the helper & info */
1885 dnd_helper_destroyer (info->helper);
1886 dnd_folder_info_destroyer (info);
1890 drag_and_drop_from_folder_view_src_folder_performer (gboolean canceled,
1892 GtkWindow *parent_window,
1893 TnyAccount *account,
1896 DndFolderInfo *info = NULL;
1897 ModestMailOperation *mail_op;
1899 info = (DndFolderInfo *) user_data;
1901 if (err || canceled) {
1902 dnd_on_connection_failed_destroyer (info, parent_window, account);
1906 /* Do the mail operation */
1907 mail_op = modest_mail_operation_new_with_error_handling ((GObject *) parent_window,
1908 modest_ui_actions_move_folder_error_handler,
1909 info->src_folder, NULL);
1911 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1914 /* Transfer the folder */
1915 modest_mail_operation_xfer_folder (mail_op,
1916 TNY_FOLDER (info->src_folder),
1918 info->helper->delete_source,
1920 info->helper->folder_view);
1923 g_object_unref (G_OBJECT (mail_op));
1924 dnd_helper_destroyer (info->helper);
1925 dnd_folder_info_destroyer (info);
1930 drag_and_drop_from_folder_view_dst_folder_performer (gboolean canceled,
1932 GtkWindow *parent_window,
1933 TnyAccount *account,
1936 DndFolderInfo *info = NULL;
1938 info = (DndFolderInfo *) user_data;
1940 if (err || canceled) {
1941 dnd_on_connection_failed_destroyer (info, parent_window, account);
1945 /* Connect to source folder and perform the copy/move */
1946 modest_platform_connect_if_remote_and_perform (NULL, TRUE,
1948 drag_and_drop_from_folder_view_src_folder_performer,
1953 * This function is used by drag_data_received_cb to manage drag and
1954 * drop of a folder, i.e, and drag from the folder view to the same
1958 drag_and_drop_from_folder_view (GtkTreeModel *source_model,
1959 GtkTreeModel *dest_model,
1960 GtkTreePath *dest_row,
1961 GtkSelectionData *selection_data,
1964 GtkTreeIter dest_iter, iter;
1965 TnyFolderStore *dest_folder = NULL;
1966 TnyFolderStore *folder = NULL;
1967 gboolean forbidden = FALSE;
1969 DndFolderInfo *info = NULL;
1971 win = modest_window_mgr_get_main_window (modest_runtime_get_window_mgr(), FALSE); /* don't create */
1973 g_warning ("%s: BUG: no main window", __FUNCTION__);
1974 dnd_helper_destroyer (helper);
1979 /* check the folder rules for the destination */
1980 folder = tree_path_to_folder (dest_model, dest_row);
1981 if (TNY_IS_FOLDER(folder)) {
1982 ModestTnyFolderRules rules =
1983 modest_tny_folder_get_rules (TNY_FOLDER (folder));
1984 forbidden = rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE;
1985 } else if (TNY_IS_FOLDER_STORE(folder)) {
1986 /* enable local root as destination for folders */
1987 if (!MODEST_IS_TNY_LOCAL_FOLDERS_ACCOUNT (folder) &&
1988 !modest_tny_account_is_memory_card_account (TNY_ACCOUNT (folder)))
1991 g_object_unref (folder);
1994 /* check the folder rules for the source */
1995 folder = tree_path_to_folder (source_model, helper->source_row);
1996 if (TNY_IS_FOLDER(folder)) {
1997 ModestTnyFolderRules rules =
1998 modest_tny_folder_get_rules (TNY_FOLDER (folder));
1999 forbidden = rules & MODEST_FOLDER_RULES_FOLDER_NON_MOVEABLE;
2002 g_object_unref (folder);
2006 /* Check if the drag is possible */
2007 if (forbidden || !gtk_tree_path_compare (helper->source_row, dest_row)) {
2008 dnd_helper_destroyer (helper);
2013 gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
2014 gtk_tree_model_get (dest_model, &dest_iter,
2015 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
2017 gtk_tree_model_get_iter (source_model, &iter, helper->source_row);
2018 gtk_tree_model_get (source_model, &iter,
2019 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
2022 /* Create the info for the performer */
2023 info = g_slice_new0 (DndFolderInfo);
2024 info->src_folder = g_object_ref (folder);
2025 info->dst_folder = g_object_ref (dest_folder);
2026 info->helper = helper;
2028 /* Connect to the destination folder and perform the copy/move */
2029 modest_platform_connect_if_remote_and_perform (GTK_WINDOW (win), TRUE,
2031 drag_and_drop_from_folder_view_dst_folder_performer,
2035 g_object_unref (dest_folder);
2036 g_object_unref (folder);
2040 * This function receives the data set by the "drag-data-get" signal
2041 * handler. This information comes within the #GtkSelectionData. This
2042 * function will manage both the drags of folders of the treeview and
2043 * drags of headers of the header view widget.
2046 on_drag_data_received (GtkWidget *widget,
2047 GdkDragContext *context,
2050 GtkSelectionData *selection_data,
2055 GtkWidget *source_widget;
2056 GtkTreeModel *dest_model, *source_model;
2057 GtkTreePath *source_row, *dest_row;
2058 GtkTreeViewDropPosition pos;
2059 gboolean delete_source = FALSE;
2060 gboolean success = FALSE;
2062 /* Do not allow further process */
2063 g_signal_stop_emission_by_name (widget, "drag-data-received");
2064 source_widget = gtk_drag_get_source_widget (context);
2066 /* Get the action */
2067 if (context->action == GDK_ACTION_MOVE) {
2068 delete_source = TRUE;
2070 /* Notify that there is no folder selected. We need to
2071 do this in order to update the headers view (and
2072 its monitors, because when moving, the old folder
2073 won't longer exist. We can not wait for the end of
2074 the operation, because the operation won't start if
2075 the folder is in use */
2076 if (source_widget == widget) {
2077 GtkTreeSelection *sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
2078 gtk_tree_selection_unselect_all (sel);
2082 /* Check if the get_data failed */
2083 if (selection_data == NULL || selection_data->length < 0)
2086 /* Select the destination model */
2087 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
2089 /* Get the path to the destination row. Can not call
2090 gtk_tree_view_get_drag_dest_row() because the source row
2091 is not selected anymore */
2092 gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget), x, y,
2095 /* Only allow drops IN other rows */
2097 pos == GTK_TREE_VIEW_DROP_BEFORE ||
2098 pos == GTK_TREE_VIEW_DROP_AFTER)
2102 /* Drags from the header view */
2103 if (source_widget != widget) {
2104 source_model = gtk_tree_view_get_model (GTK_TREE_VIEW (source_widget));
2106 drag_and_drop_from_header_view (source_model,
2111 DndHelper *helper = NULL;
2113 /* Get the source model and row */
2114 gtk_tree_get_row_drag_data (selection_data,
2118 /* Create the helper */
2119 helper = g_slice_new0 (DndHelper);
2120 helper->delete_source = delete_source;
2121 helper->source_row = gtk_tree_path_copy (source_row);
2122 helper->folder_view = MODEST_FOLDER_VIEW (widget);
2124 drag_and_drop_from_folder_view (source_model,
2130 gtk_tree_path_free (source_row);
2134 gtk_tree_path_free (dest_row);
2137 /* Finish the drag and drop */
2138 gtk_drag_finish (context, success, FALSE, time);
2142 * We define a "drag-drop" signal handler because we do not want to
2143 * use the default one, because the default one always calls
2144 * gtk_drag_finish and we prefer to do it in the "drag-data-received"
2145 * signal handler, because there we have all the information available
2146 * to know if the dnd was a success or not.
2149 drag_drop_cb (GtkWidget *widget,
2150 GdkDragContext *context,
2158 if (!context->targets)
2161 /* Check if we're dragging a folder row */
2162 target = gtk_drag_dest_find_target (widget, context, NULL);
2164 /* Request the data from the source. */
2165 gtk_drag_get_data(widget, context, target, time);
2171 * This function expands a node of a tree view if it's not expanded
2172 * yet. Not sure why it needs the threads stuff, but gtk+`example code
2173 * does that, so that's why they're here.
2176 expand_row_timeout (gpointer data)
2178 GtkTreeView *tree_view = data;
2179 GtkTreePath *dest_path = NULL;
2180 GtkTreeViewDropPosition pos;
2181 gboolean result = FALSE;
2183 gdk_threads_enter ();
2185 gtk_tree_view_get_drag_dest_row (tree_view,
2190 (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER ||
2191 pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)) {
2192 gtk_tree_view_expand_row (tree_view, dest_path, FALSE);
2193 gtk_tree_path_free (dest_path);
2197 gtk_tree_path_free (dest_path);
2202 gdk_threads_leave ();
2208 * This function is called whenever the pointer is moved over a widget
2209 * while dragging some data. It installs a timeout that will expand a
2210 * node of the treeview if not expanded yet. This function also calls
2211 * gdk_drag_status in order to set the suggested action that will be
2212 * used by the "drag-data-received" signal handler to know if we
2213 * should do a move or just a copy of the data.
2216 on_drag_motion (GtkWidget *widget,
2217 GdkDragContext *context,
2223 GtkTreeViewDropPosition pos;
2224 GtkTreePath *dest_row;
2225 GtkTreeModel *dest_model;
2226 ModestFolderViewPrivate *priv;
2227 GdkDragAction suggested_action;
2228 gboolean valid_location = FALSE;
2229 TnyFolderStore *folder = NULL;
2231 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
2233 if (priv->timer_expander != 0) {
2234 g_source_remove (priv->timer_expander);
2235 priv->timer_expander = 0;
2238 gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
2243 /* Do not allow drops between folders */
2245 pos == GTK_TREE_VIEW_DROP_BEFORE ||
2246 pos == GTK_TREE_VIEW_DROP_AFTER) {
2247 gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW (widget), NULL, 0);
2248 gdk_drag_status(context, 0, time);
2249 valid_location = FALSE;
2252 valid_location = TRUE;
2255 /* Check that the destination folder is writable */
2256 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
2257 folder = tree_path_to_folder (dest_model, dest_row);
2258 if (folder && TNY_IS_FOLDER (folder)) {
2259 ModestTnyFolderRules rules = modest_tny_folder_get_rules(TNY_FOLDER (folder));
2261 if (rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE) {
2262 valid_location = FALSE;
2267 /* Expand the selected row after 1/2 second */
2268 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), dest_row)) {
2269 priv->timer_expander = g_timeout_add (500, expand_row_timeout, widget);
2271 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), dest_row, pos);
2273 /* Select the desired action. By default we pick MOVE */
2274 suggested_action = GDK_ACTION_MOVE;
2276 if (context->actions == GDK_ACTION_COPY)
2277 gdk_drag_status(context, GDK_ACTION_COPY, time);
2278 else if (context->actions == GDK_ACTION_MOVE)
2279 gdk_drag_status(context, GDK_ACTION_MOVE, time);
2280 else if (context->actions & suggested_action)
2281 gdk_drag_status(context, suggested_action, time);
2283 gdk_drag_status(context, GDK_ACTION_DEFAULT, time);
2287 g_object_unref (folder);
2289 gtk_tree_path_free (dest_row);
2291 g_signal_stop_emission_by_name (widget, "drag-motion");
2293 return valid_location;
2297 * This function sets the treeview as a source and a target for dnd
2298 * events. It also connects all the requirede signals.
2301 setup_drag_and_drop (GtkTreeView *self)
2303 /* Set up the folder view as a dnd destination. Set only the
2304 highlight flag, otherwise gtk will have a different
2306 gtk_drag_dest_set (GTK_WIDGET (self),
2307 GTK_DEST_DEFAULT_HIGHLIGHT,
2308 folder_view_drag_types,
2309 G_N_ELEMENTS (folder_view_drag_types),
2310 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2312 g_signal_connect (G_OBJECT (self),
2313 "drag_data_received",
2314 G_CALLBACK (on_drag_data_received),
2318 /* Set up the treeview as a dnd source */
2319 gtk_drag_source_set (GTK_WIDGET (self),
2321 folder_view_drag_types,
2322 G_N_ELEMENTS (folder_view_drag_types),
2323 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2325 g_signal_connect (G_OBJECT (self),
2327 G_CALLBACK (on_drag_motion),
2330 g_signal_connect (G_OBJECT (self),
2332 G_CALLBACK (on_drag_data_get),
2335 g_signal_connect (G_OBJECT (self),
2337 G_CALLBACK (drag_drop_cb),
2342 * This function manages the navigation through the folders using the
2343 * keyboard or the hardware keys in the device
2346 on_key_pressed (GtkWidget *self,
2350 GtkTreeSelection *selection;
2352 GtkTreeModel *model;
2353 gboolean retval = FALSE;
2355 /* Up and Down are automatically managed by the treeview */
2356 if (event->keyval == GDK_Return) {
2357 /* Expand/Collapse the selected row */
2358 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2359 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
2362 path = gtk_tree_model_get_path (model, &iter);
2364 if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (self), path))
2365 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
2367 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
2368 gtk_tree_path_free (path);
2370 /* No further processing */
2378 * We listen to the changes in the local folder account name key,
2379 * because we want to show the right name in the view. The local
2380 * folder account name corresponds to the device name in the Maemo
2381 * version. We do this because we do not want to query gconf on each
2382 * tree view refresh. It's better to cache it and change whenever
2386 on_configuration_key_changed (ModestConf* conf,
2388 ModestConfEvent event,
2389 ModestConfNotificationId id,
2390 ModestFolderView *self)
2392 ModestFolderViewPrivate *priv;
2395 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
2396 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2398 if (!strcmp (key, MODEST_CONF_DEVICE_NAME)) {
2399 g_free (priv->local_account_name);
2401 if (event == MODEST_CONF_EVENT_KEY_UNSET)
2402 priv->local_account_name = g_strdup (MODEST_LOCAL_FOLDERS_DEFAULT_DISPLAY_NAME);
2404 priv->local_account_name = modest_conf_get_string (modest_runtime_get_conf(),
2405 MODEST_CONF_DEVICE_NAME, NULL);
2407 /* Force a redraw */
2408 #if GTK_CHECK_VERSION(2, 8, 0)
2409 GtkTreeViewColumn * tree_column;
2411 tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self),
2412 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN);
2413 gtk_tree_view_column_queue_resize (tree_column);
2415 gtk_widget_queue_draw (GTK_WIDGET (self));
2421 modest_folder_view_set_style (ModestFolderView *self,
2422 ModestFolderViewStyle style)
2424 ModestFolderViewPrivate *priv;
2426 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2427 g_return_if_fail (style == MODEST_FOLDER_VIEW_STYLE_SHOW_ALL ||
2428 style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE);
2430 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2433 priv->style = style;
2437 modest_folder_view_set_account_id_of_visible_server_account (ModestFolderView *self,
2438 const gchar *account_id)
2440 ModestFolderViewPrivate *priv;
2441 GtkTreeModel *model;
2443 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2445 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2447 /* This will be used by the filter_row callback,
2448 * to decided which rows to show: */
2449 if (priv->visible_account_id) {
2450 g_free (priv->visible_account_id);
2451 priv->visible_account_id = NULL;
2454 priv->visible_account_id = g_strdup (account_id);
2457 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2458 if (GTK_IS_TREE_MODEL_FILTER (model))
2459 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2461 /* Save settings to gconf */
2462 modest_widget_memory_save (modest_runtime_get_conf (), G_OBJECT(self),
2463 MODEST_CONF_FOLDER_VIEW_KEY);
2467 modest_folder_view_get_account_id_of_visible_server_account (ModestFolderView *self)
2469 ModestFolderViewPrivate *priv;
2471 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW(self), NULL);
2473 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2475 return (const gchar *) priv->visible_account_id;
2479 find_inbox_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *inbox_iter)
2483 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2485 gtk_tree_model_get (model, iter,
2486 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN,
2489 gboolean result = FALSE;
2490 if (type == TNY_FOLDER_TYPE_INBOX) {
2494 *inbox_iter = *iter;
2498 if (gtk_tree_model_iter_children (model, &child, iter)) {
2499 if (find_inbox_iter (model, &child, inbox_iter))
2503 } while (gtk_tree_model_iter_next (model, iter));
2512 modest_folder_view_select_first_inbox_or_local (ModestFolderView *self)
2514 GtkTreeModel *model;
2515 GtkTreeIter iter, inbox_iter;
2516 GtkTreeSelection *sel;
2517 GtkTreePath *path = NULL;
2519 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2521 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2525 expand_root_items (self);
2526 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2528 if (!gtk_tree_model_get_iter_first (model, &iter)) {
2529 g_warning ("%s: model is empty", __FUNCTION__);
2533 if (find_inbox_iter (model, &iter, &inbox_iter))
2534 path = gtk_tree_model_get_path (model, &inbox_iter);
2536 path = gtk_tree_path_new_first ();
2538 /* Select the row and free */
2539 gtk_tree_view_set_cursor (GTK_TREE_VIEW (self), path, NULL, FALSE);
2540 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (self), path, NULL, FALSE, 0.0, 0.0);
2541 gtk_tree_path_free (path);
2544 gtk_widget_grab_focus (GTK_WIDGET(self));
2550 find_folder_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *folder_iter,
2555 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2556 TnyFolder* a_folder;
2559 gtk_tree_model_get (model, iter,
2560 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &a_folder,
2561 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name,
2562 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
2566 if (folder == a_folder) {
2567 g_object_unref (a_folder);
2568 *folder_iter = *iter;
2571 g_object_unref (a_folder);
2573 if (gtk_tree_model_iter_children (model, &child, iter)) {
2574 if (find_folder_iter (model, &child, folder_iter, folder))
2578 } while (gtk_tree_model_iter_next (model, iter));
2585 on_row_inserted_maybe_select_folder (GtkTreeModel *tree_model,
2588 ModestFolderView *self)
2590 ModestFolderViewPrivate *priv = NULL;
2591 GtkTreeSelection *sel;
2592 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2593 GObject *instance = NULL;
2595 if (!MODEST_IS_FOLDER_VIEW(self))
2598 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2600 priv->reexpand = TRUE;
2602 gtk_tree_model_get (tree_model, iter,
2603 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
2604 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
2606 if (type == TNY_FOLDER_TYPE_INBOX && priv->folder_to_select == NULL) {
2607 priv->folder_to_select = g_object_ref (instance);
2609 g_object_unref (instance);
2611 if (priv->folder_to_select) {
2613 if (!modest_folder_view_select_folder (self, priv->folder_to_select,
2616 path = gtk_tree_model_get_path (tree_model, iter);
2617 gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
2619 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2621 gtk_tree_selection_select_iter (sel, iter);
2622 gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
2624 gtk_tree_path_free (path);
2628 modest_folder_view_disable_next_folder_selection (self);
2630 /* Refilter the model */
2631 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (tree_model));
2637 modest_folder_view_disable_next_folder_selection (ModestFolderView *self)
2639 ModestFolderViewPrivate *priv;
2641 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2643 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2645 if (priv->folder_to_select)
2646 g_object_unref(priv->folder_to_select);
2648 priv->folder_to_select = NULL;
2652 modest_folder_view_select_folder (ModestFolderView *self, TnyFolder *folder,
2653 gboolean after_change)
2655 GtkTreeModel *model;
2656 GtkTreeIter iter, folder_iter;
2657 GtkTreeSelection *sel;
2658 ModestFolderViewPrivate *priv = NULL;
2660 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW (self), FALSE);
2661 g_return_val_if_fail (folder && TNY_IS_FOLDER (folder), FALSE);
2663 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
2666 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2667 gtk_tree_selection_unselect_all (sel);
2669 if (priv->folder_to_select)
2670 g_object_unref(priv->folder_to_select);
2671 priv->folder_to_select = TNY_FOLDER(g_object_ref(folder));
2675 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2680 /* Refilter the model, before selecting the folder */
2681 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2683 if (!gtk_tree_model_get_iter_first (model, &iter)) {
2684 g_warning ("%s: model is empty", __FUNCTION__);
2688 if (find_folder_iter (model, &iter, &folder_iter, folder)) {
2691 path = gtk_tree_model_get_path (model, &folder_iter);
2692 gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
2694 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2695 gtk_tree_selection_select_iter (sel, &folder_iter);
2696 gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
2698 gtk_tree_path_free (path);
2706 modest_folder_view_copy_selection (ModestFolderView *self)
2708 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
2710 /* Copy selection */
2711 _clipboard_set_selected_data (self, FALSE);
2715 modest_folder_view_cut_selection (ModestFolderView *folder_view)
2717 ModestFolderViewPrivate *priv = NULL;
2718 GtkTreeModel *model = NULL;
2719 const gchar **hidding = NULL;
2720 guint i, n_selected;
2722 g_return_if_fail (folder_view && MODEST_IS_FOLDER_VIEW (folder_view));
2723 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
2725 /* Copy selection */
2726 if (!_clipboard_set_selected_data (folder_view, TRUE))
2729 /* Get hidding ids */
2730 hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected);
2732 /* Clear hidding array created by previous cut operation */
2733 _clear_hidding_filter (MODEST_FOLDER_VIEW (folder_view));
2735 /* Copy hidding array */
2736 priv->n_selected = n_selected;
2737 priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
2738 for (i=0; i < n_selected; i++)
2739 priv->hidding_ids[i] = g_strdup(hidding[i]);
2741 /* Hide cut folders */
2742 model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
2743 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2747 modest_folder_view_copy_model (ModestFolderView *folder_view_src,
2748 ModestFolderView *folder_view_dst)
2750 GtkTreeModel *filter_model = NULL;
2751 GtkTreeModel *model = NULL;
2752 GtkTreeModel *new_filter_model = NULL;
2754 g_return_if_fail (folder_view_src && MODEST_IS_FOLDER_VIEW (folder_view_src));
2755 g_return_if_fail (folder_view_dst && MODEST_IS_FOLDER_VIEW (folder_view_dst));
2758 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view_src));
2759 model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER(filter_model));
2761 /* Build new filter model */
2762 new_filter_model = gtk_tree_model_filter_new (model, NULL);
2763 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (new_filter_model),
2767 /* Set copied model */
2768 gtk_tree_view_set_model (GTK_TREE_VIEW (folder_view_dst), new_filter_model);
2769 g_signal_connect (G_OBJECT(new_filter_model), "row-inserted",
2770 (GCallback) on_row_inserted_maybe_select_folder, folder_view_dst);
2773 g_object_unref (new_filter_model);
2777 modest_folder_view_show_non_move_folders (ModestFolderView *folder_view,
2780 GtkTreeModel *model = NULL;
2781 ModestFolderViewPrivate* priv;
2783 g_return_if_fail (folder_view && MODEST_IS_FOLDER_VIEW (folder_view));
2785 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
2786 priv->show_non_move = show;
2787 /* modest_folder_view_update_model(folder_view, */
2788 /* TNY_ACCOUNT_STORE(modest_runtime_get_account_store())); */
2790 /* Hide special folders */
2791 model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
2792 if (GTK_IS_TREE_MODEL_FILTER (model)) {
2793 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2797 /* Returns FALSE if it did not selected anything */
2799 _clipboard_set_selected_data (ModestFolderView *folder_view,
2802 ModestFolderViewPrivate *priv = NULL;
2803 TnyFolderStore *folder = NULL;
2804 gboolean retval = FALSE;
2806 g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (folder_view), FALSE);
2807 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
2809 /* Set selected data on clipboard */
2810 g_return_val_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard), FALSE);
2811 folder = modest_folder_view_get_selected (folder_view);
2813 /* Do not allow to select an account */
2814 if (TNY_IS_FOLDER (folder)) {
2815 modest_email_clipboard_set_data (priv->clipboard, TNY_FOLDER(folder), NULL, delete);
2820 g_object_unref (folder);
2826 _clear_hidding_filter (ModestFolderView *folder_view)
2828 ModestFolderViewPrivate *priv;
2831 g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view));
2832 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
2834 if (priv->hidding_ids != NULL) {
2835 for (i=0; i < priv->n_selected; i++)
2836 g_free (priv->hidding_ids[i]);
2837 g_free(priv->hidding_ids);
2843 on_display_name_changed (ModestAccountMgr *mgr,
2844 const gchar *account,
2847 ModestFolderView *self;
2849 self = MODEST_FOLDER_VIEW (user_data);
2851 /* Force a redraw */
2852 #if GTK_CHECK_VERSION(2, 8, 0)
2853 GtkTreeViewColumn * tree_column;
2855 tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self),
2856 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN);
2857 gtk_tree_view_column_queue_resize (tree_column);
2859 gtk_widget_queue_draw (GTK_WIDGET (self));