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-list-store.h>
36 #include <tny-gtk-folder-store-tree-model.h>
37 #include <tny-gtk-header-list-model.h>
38 #include <tny-merge-folder.h>
39 #include <tny-folder.h>
40 #include <tny-folder-store-observer.h>
41 #include <tny-account-store.h>
42 #include <tny-account.h>
43 #include <tny-folder.h>
44 #include <tny-camel-folder.h>
45 #include <tny-simple-list.h>
46 #include <tny-camel-account.h>
47 #include <modest-defs.h>
48 #include <modest-tny-account.h>
49 #include <modest-tny-folder.h>
50 #include <modest-tny-local-folders-account.h>
51 #include <modest-tny-outbox-account.h>
52 #include <modest-marshal.h>
53 #include <modest-icon-names.h>
54 #include <modest-tny-account-store.h>
55 #include <modest-tny-local-folders-account.h>
56 #include <modest-text-utils.h>
57 #include <modest-runtime.h>
58 #include "modest-folder-view.h"
59 #include <modest-platform.h>
60 #include <modest-widget-memory.h>
61 #include <modest-ui-actions.h>
62 #include "modest-dnd.h"
63 #include "modest-ui-constants.h"
64 #include "widgets/modest-window.h"
66 /* Folder view drag types */
67 const GtkTargetEntry folder_view_drag_types[] =
69 { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, MODEST_FOLDER_ROW },
70 { GTK_TREE_PATH_AS_STRING_LIST, GTK_TARGET_SAME_APP, MODEST_HEADER_ROW }
73 /* Default icon sizes for Fremantle style are different */
74 #ifdef MODEST_TOOLKIT_HILDON2
75 #define FOLDER_ICON_SIZE MODEST_ICON_SIZE_BIG
77 #define FOLDER_ICON_SIZE MODEST_ICON_SIZE_SMALL
80 /* Column names depending on we use list store or tree store */
81 #ifdef MODEST_TOOLKIT_HILDON2
82 #define NAME_COLUMN TNY_GTK_FOLDER_LIST_STORE_NAME_COLUMN
83 #define UNREAD_COLUMN TNY_GTK_FOLDER_LIST_STORE_UNREAD_COLUMN
84 #define ALL_COLUMN TNY_GTK_FOLDER_LIST_STORE_ALL_COLUMN
85 #define TYPE_COLUMN TNY_GTK_FOLDER_LIST_STORE_TYPE_COLUMN
86 #define INSTANCE_COLUMN TNY_GTK_FOLDER_LIST_STORE_INSTANCE_COLUMN
88 #define NAME_COLUMN TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN
89 #define UNREAD_COLUMN TNY_GTK_FOLDER_STORE_TREE_MODEL_UNREAD_COLUMN
90 #define ALL_COLUMN TNY_GTK_FOLDER_STORE_TREE_MODEL_ALL_COLUMN
91 #define TYPE_COLUMN TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN
92 #define INSTANCE_COLUMN TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN
95 /* 'private'/'protected' functions */
96 static void modest_folder_view_class_init (ModestFolderViewClass *klass);
97 static void modest_folder_view_init (ModestFolderView *obj);
98 static void modest_folder_view_finalize (GObject *obj);
100 static void tny_account_store_view_init (gpointer g,
101 gpointer iface_data);
103 static void modest_folder_view_set_account_store (TnyAccountStoreView *self,
104 TnyAccountStore *account_store);
106 static void on_selection_changed (GtkTreeSelection *sel,
109 static void on_row_activated (GtkTreeView *treeview,
111 GtkTreeViewColumn *column,
114 static void on_account_removed (TnyAccountStore *self,
118 static void on_account_inserted (TnyAccountStore *self,
122 static void on_account_changed (TnyAccountStore *self,
126 static gint cmp_rows (GtkTreeModel *tree_model,
131 static gboolean filter_row (GtkTreeModel *model,
135 static gboolean on_key_pressed (GtkWidget *self,
139 static void on_configuration_key_changed (ModestConf* conf,
141 ModestConfEvent event,
142 ModestConfNotificationId notification_id,
143 ModestFolderView *self);
146 static void on_drag_data_get (GtkWidget *widget,
147 GdkDragContext *context,
148 GtkSelectionData *selection_data,
153 static void on_drag_data_received (GtkWidget *widget,
154 GdkDragContext *context,
157 GtkSelectionData *selection_data,
162 static gboolean on_drag_motion (GtkWidget *widget,
163 GdkDragContext *context,
169 static void expand_root_items (ModestFolderView *self);
171 static gint expand_row_timeout (gpointer data);
173 static void setup_drag_and_drop (GtkTreeView *self);
175 static gboolean _clipboard_set_selected_data (ModestFolderView *folder_view,
178 static void _clear_hidding_filter (ModestFolderView *folder_view);
180 #ifndef MODEST_TOOLKIT_HILDON2
181 static void on_row_inserted_maybe_select_folder (GtkTreeModel *tree_model,
184 ModestFolderView *self);
187 static void on_display_name_changed (ModestAccountMgr *self,
188 const gchar *account,
190 static void update_style (ModestFolderView *self);
191 static void on_notify_style (GObject *obj, GParamSpec *spec, gpointer userdata);
192 static gint get_cmp_pos (TnyFolderType t, TnyFolder *folder_store);
193 static gboolean inbox_is_special (TnyFolderStore *folder_store);
195 static gboolean get_inner_models (ModestFolderView *self,
196 GtkTreeModel **filter_model,
197 GtkTreeModel **sort_model,
198 GtkTreeModel **tny_model);
201 FOLDER_SELECTION_CHANGED_SIGNAL,
202 FOLDER_DISPLAY_NAME_CHANGED_SIGNAL,
203 FOLDER_ACTIVATED_SIGNAL,
207 typedef struct _ModestFolderViewPrivate ModestFolderViewPrivate;
208 struct _ModestFolderViewPrivate {
209 TnyAccountStore *account_store;
210 TnyFolderStore *cur_folder_store;
212 TnyFolder *folder_to_select; /* folder to select after the next update */
214 gulong changed_signal;
215 gulong account_inserted_signal;
216 gulong account_removed_signal;
217 gulong account_changed_signal;
218 gulong conf_key_signal;
219 gulong display_name_changed_signal;
221 /* not unref this object, its a singlenton */
222 ModestEmailClipboard *clipboard;
224 /* Filter tree model */
227 ModestFolderViewFilter filter;
229 TnyFolderStoreQuery *query;
230 guint timer_expander;
232 gchar *local_account_name;
233 gchar *visible_account_id;
234 ModestFolderViewStyle style;
235 ModestFolderViewCellStyle cell_style;
237 gboolean reselect; /* we use this to force a reselection of the INBOX */
238 gboolean show_non_move;
239 TnyList *list_to_move;
240 gboolean reexpand; /* next time we expose, we'll expand all root folders */
242 GtkCellRenderer *messages_renderer;
244 gulong outbox_deleted_handler;
246 #define MODEST_FOLDER_VIEW_GET_PRIVATE(o) \
247 (G_TYPE_INSTANCE_GET_PRIVATE((o), \
248 MODEST_TYPE_FOLDER_VIEW, \
249 ModestFolderViewPrivate))
251 static GObjectClass *parent_class = NULL;
253 static guint signals[LAST_SIGNAL] = {0};
256 modest_folder_view_get_type (void)
258 static GType my_type = 0;
260 static const GTypeInfo my_info = {
261 sizeof(ModestFolderViewClass),
262 NULL, /* base init */
263 NULL, /* base finalize */
264 (GClassInitFunc) modest_folder_view_class_init,
265 NULL, /* class finalize */
266 NULL, /* class data */
267 sizeof(ModestFolderView),
269 (GInstanceInitFunc) modest_folder_view_init,
273 static const GInterfaceInfo tny_account_store_view_info = {
274 (GInterfaceInitFunc) tny_account_store_view_init, /* interface_init */
275 NULL, /* interface_finalize */
276 NULL /* interface_data */
280 my_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
284 g_type_add_interface_static (my_type,
285 TNY_TYPE_ACCOUNT_STORE_VIEW,
286 &tny_account_store_view_info);
292 modest_folder_view_class_init (ModestFolderViewClass *klass)
294 GObjectClass *gobject_class;
295 GtkTreeViewClass *treeview_class;
296 gobject_class = (GObjectClass*) klass;
297 treeview_class = (GtkTreeViewClass*) klass;
299 parent_class = g_type_class_peek_parent (klass);
300 gobject_class->finalize = modest_folder_view_finalize;
302 g_type_class_add_private (gobject_class,
303 sizeof(ModestFolderViewPrivate));
305 signals[FOLDER_SELECTION_CHANGED_SIGNAL] =
306 g_signal_new ("folder_selection_changed",
307 G_TYPE_FROM_CLASS (gobject_class),
309 G_STRUCT_OFFSET (ModestFolderViewClass,
310 folder_selection_changed),
312 modest_marshal_VOID__POINTER_BOOLEAN,
313 G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_BOOLEAN);
316 * This signal is emitted whenever the currently selected
317 * folder display name is computed. Note that the name could
318 * be different to the folder name, because we could append
319 * the unread messages count to the folder name to build the
320 * folder display name
322 signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL] =
323 g_signal_new ("folder-display-name-changed",
324 G_TYPE_FROM_CLASS (gobject_class),
326 G_STRUCT_OFFSET (ModestFolderViewClass,
327 folder_display_name_changed),
329 g_cclosure_marshal_VOID__STRING,
330 G_TYPE_NONE, 1, G_TYPE_STRING);
332 signals[FOLDER_ACTIVATED_SIGNAL] =
333 g_signal_new ("folder_activated",
334 G_TYPE_FROM_CLASS (gobject_class),
336 G_STRUCT_OFFSET (ModestFolderViewClass,
339 g_cclosure_marshal_VOID__POINTER,
340 G_TYPE_NONE, 1, G_TYPE_POINTER);
342 treeview_class->select_cursor_parent = NULL;
344 #ifdef MODEST_TOOLKIT_HILDON2
345 gtk_rc_parse_string ("class \"ModestFolderView\" style \"fremantle-touchlist\"");
351 /* Retrieves the filter, sort and tny models of the folder view. If
352 any of these does not exist then it returns FALSE */
354 get_inner_models (ModestFolderView *self,
355 GtkTreeModel **filter_model,
356 GtkTreeModel **sort_model,
357 GtkTreeModel **tny_model)
359 GtkTreeModel *s_model, *f_model, *t_model;
361 f_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
362 if (!GTK_IS_TREE_MODEL_FILTER(f_model)) {
363 g_warning ("BUG: %s: not a valid filter model", __FUNCTION__);
367 s_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (f_model));
368 if (!GTK_IS_TREE_MODEL_SORT(s_model)) {
369 g_warning ("BUG: %s: not a valid sort model", __FUNCTION__);
373 t_model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (s_model));
377 *filter_model = f_model;
379 *sort_model = s_model;
381 *tny_model = t_model;
386 /* Simplify checks for NULLs: */
388 strings_are_equal (const gchar *a, const gchar *b)
394 return (strcmp (a, b) == 0);
401 on_model_foreach_set_name(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
403 GObject *instance = NULL;
405 gtk_tree_model_get (model, iter,
406 INSTANCE_COLUMN, &instance,
410 return FALSE; /* keep walking */
412 if (!TNY_IS_ACCOUNT (instance)) {
413 g_object_unref (instance);
414 return FALSE; /* keep walking */
417 /* Check if this is the looked-for account: */
418 TnyAccount *this_account = TNY_ACCOUNT (instance);
419 TnyAccount *account = TNY_ACCOUNT (data);
421 const gchar *this_account_id = tny_account_get_id(this_account);
422 const gchar *account_id = tny_account_get_id(account);
423 g_object_unref (instance);
426 /* printf ("DEBUG: %s: this_account_id=%s, account_id=%s\n", __FUNCTION__, this_account_id, account_id); */
427 if (strings_are_equal(this_account_id, account_id)) {
428 /* Tell the model that the data has changed, so that
429 * it calls the cell_data_func callbacks again: */
430 /* TODO: This does not seem to actually cause the new string to be shown: */
431 gtk_tree_model_row_changed (model, path, iter);
433 return TRUE; /* stop walking */
436 return FALSE; /* keep walking */
441 ModestFolderView *self;
442 gchar *previous_name;
443 } GetMmcAccountNameData;
446 on_get_mmc_account_name (TnyStoreAccount* account, gpointer user_data)
448 /* printf ("DEBU1G: %s: account name=%s\n", __FUNCTION__, tny_account_get_name (TNY_ACCOUNT(account))); */
450 GetMmcAccountNameData *data = (GetMmcAccountNameData*)user_data;
452 if (!strings_are_equal (
453 tny_account_get_name(TNY_ACCOUNT(account)),
454 data->previous_name)) {
456 /* Tell the model that the data has changed, so that
457 * it calls the cell_data_func callbacks again: */
458 ModestFolderView *self = data->self;
459 GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
461 gtk_tree_model_foreach(model, on_model_foreach_set_name, account);
464 g_free (data->previous_name);
465 g_slice_free (GetMmcAccountNameData, data);
469 convert_parent_folders_to_dots (gchar **item_name)
473 gchar *last_separator;
475 if (item_name == NULL)
478 for (c = *item_name; *c != '\0'; c++) {
479 if (g_str_has_prefix (c, MODEST_FOLDER_PATH_SEPARATOR)) {
484 last_separator = g_strrstr (*item_name, MODEST_FOLDER_PATH_SEPARATOR);
485 if (last_separator != NULL) {
486 last_separator = last_separator + strlen (MODEST_FOLDER_PATH_SEPARATOR);
493 buffer = g_string_new ("");
494 for (i = 0; i < n_parents; i++) {
495 buffer = g_string_append (buffer, MODEST_FOLDER_DOT);
497 buffer = g_string_append (buffer, last_separator);
499 *item_name = g_string_free (buffer, FALSE);
505 format_compact_style (gchar **item_name,
508 gboolean multiaccount,
509 gboolean *use_markup)
513 TnyFolderType folder_type;
515 if (!TNY_IS_FOLDER (instance))
518 folder = (TnyFolder *) instance;
520 folder_type = tny_folder_get_folder_type (folder);
521 is_special = (get_cmp_pos (folder_type, folder)!= 4);
523 if (!is_special || multiaccount) {
524 TnyAccount *account = tny_folder_get_account (folder);
525 const gchar *folder_name;
526 gboolean concat_folder_name = FALSE;
529 /* Should not happen */
533 /* convert parent folders to dots */
534 convert_parent_folders_to_dots (item_name);
536 folder_name = tny_folder_get_name (folder);
537 if (g_str_has_suffix (*item_name, folder_name)) {
538 gchar *offset = g_strrstr (*item_name, folder_name);
540 concat_folder_name = TRUE;
543 buffer = g_string_new ("");
545 buffer = g_string_append (buffer, *item_name);
546 if (concat_folder_name) {
547 if (bold) buffer = g_string_append (buffer, "<span weight='bold'>");
548 buffer = g_string_append (buffer, folder_name);
549 if (bold) buffer = g_string_append (buffer, "</span>");
552 g_object_unref (account);
554 *item_name = g_string_free (buffer, FALSE);
562 text_cell_data (GtkTreeViewColumn *column,
563 GtkCellRenderer *renderer,
564 GtkTreeModel *tree_model,
568 ModestFolderViewPrivate *priv;
569 GObject *rendobj = (GObject *) renderer;
571 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
572 GObject *instance = NULL;
573 gboolean use_markup = FALSE;
575 gtk_tree_model_get (tree_model, iter,
578 INSTANCE_COLUMN, &instance,
580 if (!fname || !instance)
583 ModestFolderView *self = MODEST_FOLDER_VIEW (data);
584 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
586 gchar *item_name = NULL;
587 gint item_weight = 400;
589 if (type != TNY_FOLDER_TYPE_ROOT) {
593 if (modest_tny_folder_is_local_folder (TNY_FOLDER (instance)) ||
594 modest_tny_folder_is_memory_card_folder (TNY_FOLDER (instance))) {
595 type = modest_tny_folder_get_local_or_mmc_folder_type (TNY_FOLDER (instance));
596 if (type != TNY_FOLDER_TYPE_UNKNOWN) {
598 fname = g_strdup (modest_local_folder_info_get_type_display_name (type));
601 /* Sometimes an special folder is reported by the server as
602 NORMAL, like some versions of Dovecot */
603 if (type == TNY_FOLDER_TYPE_NORMAL ||
604 type == TNY_FOLDER_TYPE_UNKNOWN) {
605 type = modest_tny_folder_guess_folder_type (TNY_FOLDER (instance));
609 /* note: we cannot reliably get the counts from the
610 * tree model, we need to use explicit calls on
611 * tny_folder for some reason. Select the number to
612 * show: the unread or unsent messages. in case of
613 * outbox/drafts, show all */
614 if ((type == TNY_FOLDER_TYPE_DRAFTS) ||
615 (type == TNY_FOLDER_TYPE_OUTBOX) ||
616 (type == TNY_FOLDER_TYPE_MERGE)) { /* _OUTBOX actually returns _MERGE... */
617 number = tny_folder_get_all_count (TNY_FOLDER(instance));
620 number = tny_folder_get_unread_count (TNY_FOLDER(instance));
624 if (priv->cell_style == MODEST_FOLDER_VIEW_CELL_STYLE_COMPACT) {
625 item_name = g_strdup (fname);
632 /* Use bold font style if there are unread or unset messages */
634 item_name = g_strdup_printf ("%s (%d)", fname, number);
637 item_name = g_strdup (fname);
642 } else if (TNY_IS_ACCOUNT (instance)) {
643 /* If it's a server account */
644 if (modest_tny_account_is_virtual_local_folders (TNY_ACCOUNT (instance))) {
645 item_name = g_strdup (priv->local_account_name);
647 } else if (modest_tny_account_is_memory_card_account (TNY_ACCOUNT (instance))) {
648 /* fname is only correct when the items are first
649 * added to the model, not when the account is
650 * changed later, so get the name from the account
652 item_name = g_strdup (tny_account_get_name (TNY_ACCOUNT (instance)));
655 item_name = g_strdup (fname);
661 item_name = g_strdup ("unknown");
663 if (priv->cell_style == MODEST_FOLDER_VIEW_CELL_STYLE_COMPACT) {
664 gboolean multiaccount;
666 multiaccount = (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ALL);
667 /* Convert item_name to markup */
668 format_compact_style (&item_name, instance,
670 multiaccount, &use_markup);
673 if (item_name && item_weight) {
674 /* Set the name in the treeview cell: */
676 g_object_set (rendobj, "markup", item_name, NULL);
678 g_object_set (rendobj, "text", item_name, "weight", item_weight, NULL);
680 /* Notify display name observers */
681 /* TODO: What listens for this signal, and how can it use only the new name? */
682 if (((GObject *) priv->cur_folder_store) == instance) {
683 g_signal_emit (G_OBJECT(self),
684 signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
691 /* If it is a Memory card account, make sure that we have the correct name.
692 * This function will be trigerred again when the name has been retrieved: */
693 if (TNY_IS_STORE_ACCOUNT (instance) &&
694 modest_tny_account_is_memory_card_account (TNY_ACCOUNT (instance))) {
696 /* Get the account name asynchronously: */
697 GetMmcAccountNameData *callback_data =
698 g_slice_new0(GetMmcAccountNameData);
699 callback_data->self = self;
701 const gchar *name = tny_account_get_name (TNY_ACCOUNT(instance));
703 callback_data->previous_name = g_strdup (name);
705 modest_tny_account_get_mmc_account_name (TNY_STORE_ACCOUNT (instance),
706 on_get_mmc_account_name, callback_data);
710 g_object_unref (G_OBJECT (instance));
716 messages_cell_data (GtkTreeViewColumn *column,
717 GtkCellRenderer *renderer,
718 GtkTreeModel *tree_model,
722 ModestFolderView *self;
723 ModestFolderViewPrivate *priv;
724 GObject *rendobj = (GObject *) renderer;
725 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
726 GObject *instance = NULL;
727 gchar *item_name = NULL;
729 gtk_tree_model_get (tree_model, iter,
731 INSTANCE_COLUMN, &instance,
736 self = MODEST_FOLDER_VIEW (data);
737 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
740 if (type != TNY_FOLDER_TYPE_ROOT) {
744 if (modest_tny_folder_is_local_folder (TNY_FOLDER (instance)) ||
745 modest_tny_folder_is_memory_card_folder (TNY_FOLDER (instance))) {
746 type = modest_tny_folder_get_local_or_mmc_folder_type (TNY_FOLDER (instance));
748 /* Sometimes an special folder is reported by the server as
749 NORMAL, like some versions of Dovecot */
750 if (type == TNY_FOLDER_TYPE_NORMAL ||
751 type == TNY_FOLDER_TYPE_UNKNOWN) {
752 type = modest_tny_folder_guess_folder_type (TNY_FOLDER (instance));
756 /* note: we cannot reliably get the counts from the tree model, we need
757 * to use explicit calls on tny_folder for some reason.
759 /* Select the number to show: the unread or unsent messages. in case of outbox/drafts, show all */
760 if ((type == TNY_FOLDER_TYPE_DRAFTS) ||
761 (type == TNY_FOLDER_TYPE_OUTBOX) ||
762 (type == TNY_FOLDER_TYPE_MERGE)) { /* _OUTBOX actually returns _MERGE... */
763 number = tny_folder_get_all_count (TNY_FOLDER(instance));
766 number = tny_folder_get_unread_count (TNY_FOLDER(instance));
770 if (priv->cell_style == MODEST_FOLDER_VIEW_CELL_STYLE_COMPACT) {
772 item_name = g_strdup_printf (drafts?_("mcen_ti_messages"):_("mcen_ti_new_messages"),
780 item_name = g_strdup ("");
783 /* Set the name in the treeview cell: */
784 g_object_set (rendobj,"text", item_name, NULL);
792 g_object_unref (G_OBJECT (instance));
798 GdkPixbuf *pixbuf_open;
799 GdkPixbuf *pixbuf_close;
803 static inline GdkPixbuf *
804 get_composite_pixbuf (const gchar *icon_name,
806 GdkPixbuf *base_pixbuf)
808 GdkPixbuf *emblem, *retval = NULL;
810 emblem = modest_platform_get_icon (icon_name, size);
812 retval = gdk_pixbuf_copy (base_pixbuf);
813 gdk_pixbuf_composite (emblem, retval, 0, 0,
814 MIN (gdk_pixbuf_get_width (emblem),
815 gdk_pixbuf_get_width (retval)),
816 MIN (gdk_pixbuf_get_height (emblem),
817 gdk_pixbuf_get_height (retval)),
818 0, 0, 1, 1, GDK_INTERP_NEAREST, 255);
819 g_object_unref (emblem);
824 static inline ThreePixbufs *
825 get_composite_icons (const gchar *icon_code,
827 GdkPixbuf **pixbuf_open,
828 GdkPixbuf **pixbuf_close)
830 ThreePixbufs *retval;
833 *pixbuf = gdk_pixbuf_copy (modest_platform_get_icon (icon_code, FOLDER_ICON_SIZE));
836 *pixbuf_open = get_composite_pixbuf ("qgn_list_gene_fldr_exp",
841 *pixbuf_close = get_composite_pixbuf ("qgn_list_gene_fldr_clp",
845 retval = g_slice_new0 (ThreePixbufs);
847 retval->pixbuf = g_object_ref (*pixbuf);
849 retval->pixbuf_open = g_object_ref (*pixbuf_open);
851 retval->pixbuf_close = g_object_ref (*pixbuf_close);
856 static inline ThreePixbufs*
857 get_folder_icons (TnyFolderType type, GObject *instance)
859 static GdkPixbuf *inbox_pixbuf = NULL, *outbox_pixbuf = NULL,
860 *junk_pixbuf = NULL, *sent_pixbuf = NULL,
861 *trash_pixbuf = NULL, *draft_pixbuf = NULL,
862 *normal_pixbuf = NULL, *anorm_pixbuf = NULL, *mmc_pixbuf = NULL,
863 *ammc_pixbuf = NULL, *avirt_pixbuf = NULL;
865 static GdkPixbuf *inbox_pixbuf_open = NULL, *outbox_pixbuf_open = NULL,
866 *junk_pixbuf_open = NULL, *sent_pixbuf_open = NULL,
867 *trash_pixbuf_open = NULL, *draft_pixbuf_open = NULL,
868 *normal_pixbuf_open = NULL, *anorm_pixbuf_open = NULL, *mmc_pixbuf_open = NULL,
869 *ammc_pixbuf_open = NULL, *avirt_pixbuf_open = NULL;
871 static GdkPixbuf *inbox_pixbuf_close = NULL, *outbox_pixbuf_close = NULL,
872 *junk_pixbuf_close = NULL, *sent_pixbuf_close = NULL,
873 *trash_pixbuf_close = NULL, *draft_pixbuf_close = NULL,
874 *normal_pixbuf_close = NULL, *anorm_pixbuf_close = NULL, *mmc_pixbuf_close = NULL,
875 *ammc_pixbuf_close = NULL, *avirt_pixbuf_close = NULL;
877 ThreePixbufs *retval = NULL;
879 /* Sometimes an special folder is reported by the server as
880 NORMAL, like some versions of Dovecot */
881 if (type == TNY_FOLDER_TYPE_NORMAL ||
882 type == TNY_FOLDER_TYPE_UNKNOWN) {
883 type = modest_tny_folder_guess_folder_type (TNY_FOLDER (instance));
886 /* It's not enough with check the folder type. We need to
887 ensure that we're not giving a special folder icon to a
888 normal folder with the same name than a special folder */
889 if (TNY_IS_FOLDER (instance) &&
890 get_cmp_pos (type, TNY_FOLDER (instance)) == 4)
891 type = TNY_FOLDER_TYPE_NORMAL;
893 /* Remote folders should not be treated as special folders */
894 if (TNY_IS_FOLDER_STORE (instance) &&
895 !TNY_IS_ACCOUNT (instance) &&
896 type != TNY_FOLDER_TYPE_INBOX &&
897 modest_tny_folder_store_is_remote (TNY_FOLDER_STORE (instance))) {
898 #ifdef MODEST_TOOLKIT_HILDON2
899 return get_composite_icons (MODEST_FOLDER_ICON_ACCOUNT,
902 &anorm_pixbuf_close);
904 return get_composite_icons (MODEST_FOLDER_ICON_NORMAL,
907 &normal_pixbuf_close);
913 case TNY_FOLDER_TYPE_INVALID:
914 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
917 case TNY_FOLDER_TYPE_ROOT:
918 if (TNY_IS_ACCOUNT (instance)) {
920 if (modest_tny_account_is_virtual_local_folders (TNY_ACCOUNT (instance))) {
921 retval = get_composite_icons (MODEST_FOLDER_ICON_LOCAL_FOLDERS,
924 &avirt_pixbuf_close);
926 const gchar *account_id = tny_account_get_id (TNY_ACCOUNT (instance));
928 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
929 retval = get_composite_icons (MODEST_FOLDER_ICON_MMC,
934 retval = get_composite_icons (MODEST_FOLDER_ICON_ACCOUNT,
937 &anorm_pixbuf_close);
942 case TNY_FOLDER_TYPE_INBOX:
943 retval = get_composite_icons (MODEST_FOLDER_ICON_INBOX,
946 &inbox_pixbuf_close);
948 case TNY_FOLDER_TYPE_OUTBOX:
949 retval = get_composite_icons (MODEST_FOLDER_ICON_OUTBOX,
952 &outbox_pixbuf_close);
954 case TNY_FOLDER_TYPE_JUNK:
955 retval = get_composite_icons (MODEST_FOLDER_ICON_JUNK,
960 case TNY_FOLDER_TYPE_SENT:
961 retval = get_composite_icons (MODEST_FOLDER_ICON_SENT,
966 case TNY_FOLDER_TYPE_TRASH:
967 retval = get_composite_icons (MODEST_FOLDER_ICON_TRASH,
970 &trash_pixbuf_close);
972 case TNY_FOLDER_TYPE_DRAFTS:
973 retval = get_composite_icons (MODEST_FOLDER_ICON_DRAFTS,
976 &draft_pixbuf_close);
978 case TNY_FOLDER_TYPE_ARCHIVE:
979 retval = get_composite_icons (MODEST_FOLDER_ICON_MMC_FOLDER,
984 case TNY_FOLDER_TYPE_NORMAL:
986 /* Memory card folders could have an special icon */
987 if (modest_tny_folder_is_memory_card_folder (TNY_FOLDER (instance))) {
988 retval = get_composite_icons (MODEST_FOLDER_ICON_MMC_FOLDER,
993 retval = get_composite_icons (MODEST_FOLDER_ICON_NORMAL,
996 &normal_pixbuf_close);
1005 free_pixbufs (ThreePixbufs *pixbufs)
1007 if (pixbufs->pixbuf)
1008 g_object_unref (pixbufs->pixbuf);
1009 if (pixbufs->pixbuf_open)
1010 g_object_unref (pixbufs->pixbuf_open);
1011 if (pixbufs->pixbuf_close)
1012 g_object_unref (pixbufs->pixbuf_close);
1013 g_slice_free (ThreePixbufs, pixbufs);
1017 icon_cell_data (GtkTreeViewColumn *column,
1018 GtkCellRenderer *renderer,
1019 GtkTreeModel *tree_model,
1023 GObject *rendobj = NULL, *instance = NULL;
1024 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1025 gboolean has_children;
1026 ThreePixbufs *pixbufs;
1028 rendobj = (GObject *) renderer;
1030 gtk_tree_model_get (tree_model, iter,
1032 INSTANCE_COLUMN, &instance,
1038 has_children = gtk_tree_model_iter_has_child (tree_model, iter);
1039 pixbufs = get_folder_icons (type, instance);
1040 g_object_unref (instance);
1043 g_object_set (rendobj, "pixbuf", pixbufs->pixbuf, NULL);
1046 g_object_set (rendobj, "pixbuf-expander-open", pixbufs->pixbuf_open, NULL);
1047 g_object_set (rendobj, "pixbuf-expander-closed", pixbufs->pixbuf_close, NULL);
1050 free_pixbufs (pixbufs);
1054 add_columns (GtkWidget *treeview)
1056 GtkTreeViewColumn *column;
1057 GtkCellRenderer *renderer;
1058 GtkTreeSelection *sel;
1059 ModestFolderViewPrivate *priv;
1061 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(treeview);
1064 column = gtk_tree_view_column_new ();
1066 /* Set icon and text render function */
1067 renderer = gtk_cell_renderer_pixbuf_new();
1068 #ifdef MODEST_TOOLKIT_HILDON2
1069 g_object_set (renderer,
1070 "xpad", MODEST_MARGIN_DEFAULT,
1071 "ypad", MODEST_MARGIN_DEFAULT,
1074 gtk_tree_view_column_pack_start (column, renderer, FALSE);
1075 gtk_tree_view_column_set_cell_data_func(column, renderer,
1076 icon_cell_data, treeview, NULL);
1078 renderer = gtk_cell_renderer_text_new();
1079 g_object_set (renderer,
1080 #ifdef MODEST_TOOLKIT_HILDON2
1081 "ellipsize", PANGO_ELLIPSIZE_MIDDLE,
1082 "ypad", MODEST_MARGIN_DEFAULT,
1084 "ellipsize", PANGO_ELLIPSIZE_END,
1086 "ellipsize-set", TRUE, NULL);
1087 gtk_tree_view_column_pack_start (column, renderer, TRUE);
1088 gtk_tree_view_column_set_cell_data_func(column, renderer,
1089 text_cell_data, treeview, NULL);
1091 priv->messages_renderer = gtk_cell_renderer_text_new ();
1092 g_object_set (priv->messages_renderer,
1093 #ifdef MODEST_TOOLKIT_HILDON2
1095 "ypad", MODEST_MARGIN_DEFAULT,
1096 "xpad", MODEST_MARGIN_DOUBLE,
1098 "scale", PANGO_SCALE_X_SMALL,
1101 "alignment", PANGO_ALIGN_RIGHT,
1105 gtk_tree_view_column_pack_start (column, priv->messages_renderer, FALSE);
1106 gtk_tree_view_column_set_cell_data_func(column, priv->messages_renderer,
1107 messages_cell_data, treeview, NULL);
1109 /* Set selection mode */
1110 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(treeview));
1111 gtk_tree_selection_set_mode (sel, GTK_SELECTION_SINGLE);
1113 /* Set treeview appearance */
1114 gtk_tree_view_column_set_spacing (column, 2);
1115 gtk_tree_view_column_set_resizable (column, TRUE);
1116 gtk_tree_view_column_set_fixed_width (column, TRUE);
1117 gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(treeview), FALSE);
1118 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(treeview), FALSE);
1121 gtk_tree_view_append_column (GTK_TREE_VIEW(treeview),column);
1125 modest_folder_view_init (ModestFolderView *obj)
1127 ModestFolderViewPrivate *priv;
1130 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
1132 priv->timer_expander = 0;
1133 priv->account_store = NULL;
1135 priv->style = MODEST_FOLDER_VIEW_STYLE_SHOW_ALL;
1136 priv->cur_folder_store = NULL;
1137 priv->visible_account_id = NULL;
1138 priv->folder_to_select = NULL;
1139 priv->outbox_deleted_handler = 0;
1140 priv->reexpand = TRUE;
1142 /* Initialize the local account name */
1143 conf = modest_runtime_get_conf();
1144 priv->local_account_name = modest_conf_get_string (conf, MODEST_CONF_DEVICE_NAME, NULL);
1146 /* Init email clipboard */
1147 priv->clipboard = modest_runtime_get_email_clipboard ();
1148 priv->hidding_ids = NULL;
1149 priv->n_selected = 0;
1150 priv->filter = MODEST_FOLDER_VIEW_FILTER_NONE;
1151 priv->reselect = FALSE;
1152 priv->show_non_move = TRUE;
1153 priv->list_to_move = NULL;
1155 /* Build treeview */
1156 add_columns (GTK_WIDGET (obj));
1158 /* Setup drag and drop */
1159 setup_drag_and_drop (GTK_TREE_VIEW(obj));
1161 /* Connect signals */
1162 g_signal_connect (G_OBJECT (obj),
1164 G_CALLBACK (on_key_pressed), NULL);
1166 priv->display_name_changed_signal =
1167 g_signal_connect (modest_runtime_get_account_mgr (),
1168 "display_name_changed",
1169 G_CALLBACK (on_display_name_changed),
1173 * Track changes in the local account name (in the device it
1174 * will be the device name)
1176 priv->conf_key_signal = g_signal_connect (G_OBJECT(conf),
1178 G_CALLBACK(on_configuration_key_changed),
1182 g_signal_connect (G_OBJECT (obj), "notify::style", G_CALLBACK (on_notify_style), (gpointer) obj);
1188 tny_account_store_view_init (gpointer g, gpointer iface_data)
1190 TnyAccountStoreViewIface *klass = (TnyAccountStoreViewIface *)g;
1192 klass->set_account_store = modest_folder_view_set_account_store;
1196 modest_folder_view_finalize (GObject *obj)
1198 ModestFolderViewPrivate *priv;
1199 GtkTreeSelection *sel;
1200 TnyAccount *local_account;
1202 g_return_if_fail (obj);
1204 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
1206 if (priv->timer_expander != 0) {
1207 g_source_remove (priv->timer_expander);
1208 priv->timer_expander = 0;
1211 local_account = (TnyAccount *)
1212 modest_tny_account_store_get_local_folders_account (modest_runtime_get_account_store ());
1213 if (local_account) {
1214 if (g_signal_handler_is_connected (local_account,
1215 priv->outbox_deleted_handler))
1216 g_signal_handler_disconnect (local_account,
1217 priv->outbox_deleted_handler);
1218 g_object_unref (local_account);
1221 if (priv->account_store) {
1222 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
1223 priv->account_inserted_signal);
1224 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
1225 priv->account_removed_signal);
1226 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
1227 priv->account_changed_signal);
1228 g_object_unref (G_OBJECT(priv->account_store));
1229 priv->account_store = NULL;
1232 if (g_signal_handler_is_connected (modest_runtime_get_account_mgr (),
1233 priv->display_name_changed_signal)) {
1234 g_signal_handler_disconnect (modest_runtime_get_account_mgr (),
1235 priv->display_name_changed_signal);
1236 priv->display_name_changed_signal = 0;
1240 g_object_unref (G_OBJECT (priv->query));
1244 if (priv->folder_to_select) {
1245 g_object_unref (G_OBJECT(priv->folder_to_select));
1246 priv->folder_to_select = NULL;
1249 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(obj));
1251 g_signal_handler_disconnect (G_OBJECT(sel), priv->changed_signal);
1253 g_free (priv->local_account_name);
1254 g_free (priv->visible_account_id);
1256 if (priv->conf_key_signal) {
1257 g_signal_handler_disconnect (modest_runtime_get_conf (),
1258 priv->conf_key_signal);
1259 priv->conf_key_signal = 0;
1262 if (priv->cur_folder_store) {
1263 g_object_unref (priv->cur_folder_store);
1264 priv->cur_folder_store = NULL;
1267 if (priv->list_to_move) {
1268 g_object_unref (priv->list_to_move);
1269 priv->list_to_move = NULL;
1272 /* Clear hidding array created by cut operation */
1273 _clear_hidding_filter (MODEST_FOLDER_VIEW (obj));
1275 G_OBJECT_CLASS(parent_class)->finalize (obj);
1280 modest_folder_view_set_account_store (TnyAccountStoreView *self, TnyAccountStore *account_store)
1282 ModestFolderViewPrivate *priv;
1285 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
1286 g_return_if_fail (TNY_IS_ACCOUNT_STORE (account_store));
1288 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1289 device = tny_account_store_get_device (account_store);
1291 if (G_UNLIKELY (priv->account_store)) {
1293 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
1294 priv->account_inserted_signal))
1295 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
1296 priv->account_inserted_signal);
1297 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
1298 priv->account_removed_signal))
1299 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
1300 priv->account_removed_signal);
1301 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
1302 priv->account_changed_signal))
1303 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
1304 priv->account_changed_signal);
1305 g_object_unref (G_OBJECT (priv->account_store));
1308 priv->account_store = g_object_ref (G_OBJECT (account_store));
1310 priv->account_removed_signal =
1311 g_signal_connect (G_OBJECT(account_store), "account_removed",
1312 G_CALLBACK (on_account_removed), self);
1314 priv->account_inserted_signal =
1315 g_signal_connect (G_OBJECT(account_store), "account_inserted",
1316 G_CALLBACK (on_account_inserted), self);
1318 priv->account_changed_signal =
1319 g_signal_connect (G_OBJECT(account_store), "account_changed",
1320 G_CALLBACK (on_account_changed), self);
1322 modest_folder_view_update_model (MODEST_FOLDER_VIEW (self), account_store);
1323 priv->reselect = FALSE;
1324 modest_folder_view_select_first_inbox_or_local (MODEST_FOLDER_VIEW (self));
1326 g_object_unref (G_OBJECT (device));
1330 on_outbox_deleted_cb (ModestTnyLocalFoldersAccount *local_account,
1333 ModestFolderView *self;
1334 GtkTreeModel *model, *filter_model;
1337 self = MODEST_FOLDER_VIEW (user_data);
1339 if (!get_inner_models (self, &filter_model, NULL, &model))
1342 /* Remove outbox from model */
1343 outbox = modest_tny_local_folders_account_get_merged_outbox (local_account);
1344 tny_list_remove (TNY_LIST (model), G_OBJECT (outbox));
1345 g_object_unref (outbox);
1348 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1352 on_account_inserted (TnyAccountStore *account_store,
1353 TnyAccount *account,
1356 ModestFolderViewPrivate *priv;
1357 GtkTreeModel *model, *filter_model;
1359 /* Ignore transport account insertions, we're not showing them
1360 in the folder view */
1361 if (TNY_IS_TRANSPORT_ACCOUNT (account))
1364 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (user_data);
1367 /* If we're adding a new account, and there is no previous
1368 one, we need to select the visible server account */
1369 if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE &&
1370 !priv->visible_account_id)
1371 modest_widget_memory_restore (modest_runtime_get_conf(),
1372 G_OBJECT (user_data),
1373 MODEST_CONF_FOLDER_VIEW_KEY);
1377 if (!get_inner_models (MODEST_FOLDER_VIEW (user_data),
1378 &filter_model, NULL, &model))
1381 /* Insert the account in the model */
1382 tny_list_append (TNY_LIST (model), G_OBJECT (account));
1384 /* When the model is a list store (plain representation) the
1385 outbox is not a child of any account so we have to manually
1386 delete it because removing the local folders account won't
1387 delete it (because tny_folder_get_account() is not defined
1388 for a merge folder */
1389 if (TNY_IS_GTK_FOLDER_LIST_STORE (model) &&
1390 MODEST_IS_TNY_LOCAL_FOLDERS_ACCOUNT (account)) {
1392 priv->outbox_deleted_handler =
1393 g_signal_connect (account,
1395 G_CALLBACK (on_outbox_deleted_cb),
1399 /* Refilter the model */
1400 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1405 same_account_selected (ModestFolderView *self,
1406 TnyAccount *account)
1408 ModestFolderViewPrivate *priv;
1409 gboolean same_account = FALSE;
1411 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1413 if (priv->cur_folder_store) {
1414 TnyAccount *selected_folder_account = NULL;
1416 if (TNY_IS_FOLDER (priv->cur_folder_store)) {
1417 selected_folder_account =
1418 modest_tny_folder_get_account (TNY_FOLDER (priv->cur_folder_store));
1420 selected_folder_account =
1421 TNY_ACCOUNT (g_object_ref (priv->cur_folder_store));
1424 if (selected_folder_account == account)
1425 same_account = TRUE;
1427 g_object_unref (selected_folder_account);
1429 return same_account;
1434 * Selects the first inbox or the local account in an idle
1437 on_idle_select_first_inbox_or_local (gpointer user_data)
1439 ModestFolderView *self = MODEST_FOLDER_VIEW (user_data);
1441 gdk_threads_enter ();
1442 modest_folder_view_select_first_inbox_or_local (self);
1443 gdk_threads_leave ();
1449 on_account_changed (TnyAccountStore *account_store,
1450 TnyAccount *tny_account,
1453 ModestFolderView *self;
1454 ModestFolderViewPrivate *priv;
1455 GtkTreeModel *model, *filter_model;
1456 GtkTreeSelection *sel;
1457 gboolean same_account;
1459 /* Ignore transport account insertions, we're not showing them
1460 in the folder view */
1461 if (TNY_IS_TRANSPORT_ACCOUNT (tny_account))
1464 self = MODEST_FOLDER_VIEW (user_data);
1465 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (user_data);
1467 /* Get the inner model */
1468 if (!get_inner_models (MODEST_FOLDER_VIEW (user_data),
1469 &filter_model, NULL, &model))
1472 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (user_data));
1474 /* Invalidate the cur_folder_store only if the selected folder
1475 belongs to the account that is being removed */
1476 same_account = same_account_selected (self, tny_account);
1478 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1479 gtk_tree_selection_unselect_all (sel);
1482 /* Remove the account from the model */
1483 tny_list_remove (TNY_LIST (model), G_OBJECT (tny_account));
1485 /* Insert the account in the model */
1486 tny_list_append (TNY_LIST (model), G_OBJECT (tny_account));
1488 /* Refilter the model */
1489 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1491 /* Select the first INBOX if the currently selected folder
1492 belongs to the account that is being deleted */
1493 if (same_account && !MODEST_IS_TNY_LOCAL_FOLDERS_ACCOUNT (tny_account))
1494 g_idle_add (on_idle_select_first_inbox_or_local, self);
1498 on_account_removed (TnyAccountStore *account_store,
1499 TnyAccount *account,
1502 ModestFolderView *self = NULL;
1503 ModestFolderViewPrivate *priv;
1504 GtkTreeModel *model, *filter_model;
1505 GtkTreeSelection *sel = NULL;
1506 gboolean same_account = FALSE;
1508 /* Ignore transport account removals, we're not showing them
1509 in the folder view */
1510 if (TNY_IS_TRANSPORT_ACCOUNT (account))
1513 if (!MODEST_IS_FOLDER_VIEW(user_data)) {
1514 g_warning ("BUG: %s: not a valid folder view", __FUNCTION__);
1518 self = MODEST_FOLDER_VIEW (user_data);
1519 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1521 /* Invalidate the cur_folder_store only if the selected folder
1522 belongs to the account that is being removed */
1523 same_account = same_account_selected (self, account);
1525 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1526 gtk_tree_selection_unselect_all (sel);
1529 /* Invalidate row to select only if the folder to select
1530 belongs to the account that is being removed*/
1531 if (priv->folder_to_select) {
1532 TnyAccount *folder_to_select_account = NULL;
1534 folder_to_select_account = tny_folder_get_account (priv->folder_to_select);
1535 if (folder_to_select_account == account) {
1536 modest_folder_view_disable_next_folder_selection (self);
1537 g_object_unref (priv->folder_to_select);
1538 priv->folder_to_select = NULL;
1540 g_object_unref (folder_to_select_account);
1543 if (!get_inner_models (MODEST_FOLDER_VIEW (user_data),
1544 &filter_model, NULL, &model))
1547 /* Disconnect the signal handler */
1548 if (TNY_IS_GTK_FOLDER_LIST_STORE (model) &&
1549 MODEST_IS_TNY_LOCAL_FOLDERS_ACCOUNT (account)) {
1550 if (g_signal_handler_is_connected (account,
1551 priv->outbox_deleted_handler))
1552 g_signal_handler_disconnect (account,
1553 priv->outbox_deleted_handler);
1556 /* Remove the account from the model */
1557 tny_list_remove (TNY_LIST (model), G_OBJECT (account));
1559 /* If the removed account is the currently viewed one then
1560 clear the configuration value. The new visible account will be the default account */
1561 if (priv->visible_account_id &&
1562 !strcmp (priv->visible_account_id, tny_account_get_id (account))) {
1564 /* Clear the current visible account_id */
1565 modest_folder_view_set_account_id_of_visible_server_account (self, NULL);
1567 /* Call the restore method, this will set the new visible account */
1568 modest_widget_memory_restore (modest_runtime_get_conf(), G_OBJECT(self),
1569 MODEST_CONF_FOLDER_VIEW_KEY);
1572 /* Refilter the model */
1573 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1575 /* Select the first INBOX if the currently selected folder
1576 belongs to the account that is being deleted */
1578 g_idle_add (on_idle_select_first_inbox_or_local, self);
1582 modest_folder_view_set_title (ModestFolderView *self, const gchar *title)
1584 GtkTreeViewColumn *col;
1586 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
1588 col = gtk_tree_view_get_column (GTK_TREE_VIEW(self), 0);
1590 g_printerr ("modest: failed get column for title\n");
1594 gtk_tree_view_column_set_title (col, title);
1595 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self),
1600 modest_folder_view_on_map (ModestFolderView *self,
1601 GdkEventExpose *event,
1604 ModestFolderViewPrivate *priv;
1606 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1608 /* This won't happen often */
1609 if (G_UNLIKELY (priv->reselect)) {
1610 /* Select the first inbox or the local account if not found */
1612 /* TODO: this could cause a lock at startup, so we
1613 comment it for the moment. We know that this will
1614 be a bug, because the INBOX is not selected, but we
1615 need to rewrite some parts of Modest to avoid the
1616 deathlock situation */
1617 /* TODO: check if this is still the case */
1618 priv->reselect = FALSE;
1619 modest_folder_view_select_first_inbox_or_local (self);
1620 /* Notify the display name observers */
1621 g_signal_emit (G_OBJECT(self),
1622 signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
1626 if (priv->reexpand) {
1627 expand_root_items (self);
1628 priv->reexpand = FALSE;
1635 modest_folder_view_new (TnyFolderStoreQuery *query)
1638 ModestFolderViewPrivate *priv;
1639 GtkTreeSelection *sel;
1641 self = G_OBJECT (g_object_new (MODEST_TYPE_FOLDER_VIEW,
1642 #ifdef MODEST_TOOLKIT_HILDON2
1643 "hildon-ui-mode", HILDON_UI_MODE_NORMAL,
1646 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1649 priv->query = g_object_ref (query);
1651 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1652 priv->changed_signal = g_signal_connect (sel, "changed",
1653 G_CALLBACK (on_selection_changed), self);
1655 g_signal_connect (self, "row-activated", G_CALLBACK (on_row_activated), self);
1657 g_signal_connect (self, "expose-event", G_CALLBACK (modest_folder_view_on_map), NULL);
1659 return GTK_WIDGET(self);
1662 /* this feels dirty; any other way to expand all the root items? */
1664 expand_root_items (ModestFolderView *self)
1667 GtkTreeModel *model;
1670 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1671 path = gtk_tree_path_new_first ();
1673 /* all folders should have child items, so.. */
1675 gtk_tree_view_expand_row (GTK_TREE_VIEW(self), path, FALSE);
1676 gtk_tree_path_next (path);
1677 } while (gtk_tree_model_get_iter (model, &iter, path));
1679 gtk_tree_path_free (path);
1683 is_parent_of (TnyFolder *a, TnyFolder *b)
1686 gboolean retval = FALSE;
1688 a_id = tny_folder_get_id (a);
1690 gchar *string_to_match;
1693 string_to_match = g_strconcat (a_id, "/", NULL);
1694 b_id = tny_folder_get_id (b);
1695 retval = g_str_has_prefix (b_id, string_to_match);
1696 g_free (string_to_match);
1702 typedef struct _ForeachFolderInfo {
1705 } ForeachFolderInfo;
1708 foreach_folder_with_id (GtkTreeModel *model,
1713 ForeachFolderInfo *info;
1716 info = (ForeachFolderInfo *) data;
1717 gtk_tree_model_get (model, iter,
1718 INSTANCE_COLUMN, &instance,
1721 if (TNY_IS_FOLDER (instance)) {
1724 id = tny_folder_get_id (TNY_FOLDER (instance));
1726 collate = g_utf8_collate_key (id, -1);
1727 info->found = !strcmp (info->needle, collate);
1738 has_folder_with_id (ModestFolderView *self, const gchar *id)
1740 GtkTreeModel *model;
1741 ForeachFolderInfo info = {NULL, FALSE};
1743 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1744 info.needle = g_utf8_collate_key (id, -1);
1746 gtk_tree_model_foreach (model, foreach_folder_with_id, &info);
1747 g_free (info.needle);
1753 has_child_with_name_of (ModestFolderView *self, TnyFolder *a, TnyFolder *b)
1756 gboolean retval = FALSE;
1758 a_id = tny_folder_get_id (a);
1761 b_id = tny_folder_get_id (b);
1764 const gchar *last_bar;
1765 gchar *string_to_match;
1766 last_bar = g_strrstr (b_id, "/");
1771 string_to_match = g_strconcat (a_id, "/", last_bar, NULL);
1772 retval = has_folder_with_id (self, string_to_match);
1773 g_free (string_to_match);
1781 check_move_to_this_folder_valid (ModestFolderView *self, TnyFolder *folder)
1783 ModestFolderViewPrivate *priv;
1784 TnyIterator *iterator;
1785 gboolean retval = TRUE;
1787 g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (self), FALSE);
1788 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1790 for (iterator = tny_list_create_iterator (priv->list_to_move);
1791 retval && !tny_iterator_is_done (iterator);
1792 tny_iterator_next (iterator)) {
1794 instance = tny_iterator_get_current (iterator);
1795 if (instance == (GObject *) folder) {
1797 } else if (TNY_IS_FOLDER (instance)) {
1798 retval = !is_parent_of (TNY_FOLDER (instance), folder);
1800 retval = !has_child_with_name_of (self, folder, TNY_FOLDER (instance));
1803 g_object_unref (instance);
1805 g_object_unref (iterator);
1812 * We use this function to implement the
1813 * MODEST_FOLDER_VIEW_STYLE_SHOW_ONE style. We only show the default
1814 * account in this case, and the local folders.
1817 filter_row (GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
1819 ModestFolderViewPrivate *priv;
1820 gboolean retval = TRUE;
1821 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1822 GObject *instance = NULL;
1823 const gchar *id = NULL;
1825 gboolean found = FALSE;
1826 gboolean cleared = FALSE;
1827 ModestTnyFolderRules rules = 0;
1829 g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (data), FALSE);
1830 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (data);
1832 gtk_tree_model_get (model, iter,
1834 INSTANCE_COLUMN, &instance,
1837 /* Do not show if there is no instance, this could indeed
1838 happen when the model is being modified while it's being
1839 drawn. This could occur for example when moving folders
1844 if (TNY_IS_ACCOUNT (instance)) {
1845 TnyAccount *acc = TNY_ACCOUNT (instance);
1846 const gchar *account_id = tny_account_get_id (acc);
1848 /* If it isn't a special folder,
1849 * don't show it unless it is the visible account: */
1850 if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE &&
1851 !modest_tny_account_is_virtual_local_folders (acc) &&
1852 strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
1854 /* Show only the visible account id */
1855 if (priv->visible_account_id) {
1856 if (strcmp (account_id, priv->visible_account_id))
1863 /* Never show these to the user. They are merged into one folder
1864 * in the local-folders account instead: */
1865 if (retval && MODEST_IS_TNY_OUTBOX_ACCOUNT (acc))
1868 if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE) {
1869 /* Only show special folders for current account if needed */
1870 if (TNY_IS_FOLDER (instance) && !TNY_IS_MERGE_FOLDER (instance)) {
1871 TnyAccount *account;
1873 account = tny_folder_get_account (TNY_FOLDER (instance));
1875 if (TNY_IS_ACCOUNT (account)) {
1876 const gchar *account_id = tny_account_get_id (account);
1878 if (!modest_tny_account_is_virtual_local_folders (account) &&
1879 strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
1880 /* Show only the visible account id */
1881 if (priv->visible_account_id) {
1882 if (strcmp (account_id, priv->visible_account_id))
1886 g_object_unref (account);
1893 /* Check hiding (if necessary) */
1894 cleared = modest_email_clipboard_cleared (priv->clipboard);
1895 if ((retval) && (!cleared) && (TNY_IS_FOLDER (instance))) {
1896 id = tny_folder_get_id (TNY_FOLDER(instance));
1897 if (priv->hidding_ids != NULL)
1898 for (i=0; i < priv->n_selected && !found; i++)
1899 if (priv->hidding_ids[i] != NULL && id != NULL)
1900 found = (!strcmp (priv->hidding_ids[i], id));
1905 /* If this is a move to dialog, hide Sent, Outbox and Drafts
1906 folder as no message can be move there according to UI specs */
1907 if (!priv->show_non_move) {
1908 if (priv->list_to_move &&
1909 tny_list_get_length (priv->list_to_move) > 0 &&
1910 TNY_IS_FOLDER (instance)) {
1911 retval = check_move_to_this_folder_valid (MODEST_FOLDER_VIEW (data), TNY_FOLDER (instance));
1913 if (retval && TNY_IS_FOLDER (instance) &&
1914 modest_tny_folder_is_local_folder (TNY_FOLDER (instance))) {
1916 case TNY_FOLDER_TYPE_OUTBOX:
1917 case TNY_FOLDER_TYPE_SENT:
1918 case TNY_FOLDER_TYPE_DRAFTS:
1921 case TNY_FOLDER_TYPE_UNKNOWN:
1922 case TNY_FOLDER_TYPE_NORMAL:
1923 type = modest_tny_folder_guess_folder_type(TNY_FOLDER(instance));
1924 if (type == TNY_FOLDER_TYPE_INVALID)
1925 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1927 if (type == TNY_FOLDER_TYPE_OUTBOX ||
1928 type == TNY_FOLDER_TYPE_SENT
1929 || type == TNY_FOLDER_TYPE_DRAFTS)
1938 /* apply special filters */
1939 if (retval && (priv->filter & MODEST_FOLDER_VIEW_FILTER_HIDE_ACCOUNTS)) {
1940 if (TNY_IS_ACCOUNT (instance))
1944 if (retval && (priv->filter & MODEST_FOLDER_VIEW_FILTER_CAN_HAVE_FOLDERS)) {
1945 if (TNY_IS_FOLDER (instance)) {
1946 /* Check folder rules */
1947 ModestTnyFolderRules rules;
1949 rules = modest_tny_folder_get_rules (TNY_FOLDER (instance));
1950 retval = !(rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE);
1951 } else if (TNY_IS_ACCOUNT (instance)) {
1952 if (modest_tny_folder_store_is_remote (TNY_FOLDER_STORE (instance))) {
1960 if (retval && (priv->filter & MODEST_FOLDER_VIEW_FILTER_HIDE_MANDATORY_FOLDERS)) {
1961 if (TNY_IS_FOLDER (instance)) {
1962 TnyFolderType guess_type;
1964 if (TNY_FOLDER_TYPE_NORMAL) {
1965 guess_type = modest_tny_folder_guess_folder_type (TNY_FOLDER (instance));
1971 case TNY_FOLDER_TYPE_OUTBOX:
1972 case TNY_FOLDER_TYPE_SENT:
1973 case TNY_FOLDER_TYPE_DRAFTS:
1974 case TNY_FOLDER_TYPE_ARCHIVE:
1975 case TNY_FOLDER_TYPE_INBOX:
1978 case TNY_FOLDER_TYPE_UNKNOWN:
1979 case TNY_FOLDER_TYPE_NORMAL:
1985 } else if (TNY_IS_ACCOUNT (instance)) {
1990 if (retval && TNY_IS_FOLDER (instance)) {
1991 rules = modest_tny_folder_get_rules (TNY_FOLDER (instance));
1994 if (retval && (priv->filter & MODEST_FOLDER_VIEW_FILTER_DELETABLE)) {
1995 if (TNY_IS_FOLDER (instance)) {
1996 retval = !(rules & MODEST_FOLDER_RULES_FOLDER_NON_DELETABLE);
1997 } else if (TNY_IS_ACCOUNT (instance)) {
2002 if (retval && (priv->filter & MODEST_FOLDER_VIEW_FILTER_RENAMEABLE)) {
2003 if (TNY_IS_FOLDER (instance)) {
2004 retval = !(rules & MODEST_FOLDER_RULES_FOLDER_NON_RENAMEABLE);
2005 } else if (TNY_IS_ACCOUNT (instance)) {
2010 if (retval && (priv->filter & MODEST_FOLDER_VIEW_FILTER_MOVEABLE)) {
2011 if (TNY_IS_FOLDER (instance)) {
2012 retval = !(rules & MODEST_FOLDER_RULES_FOLDER_NON_MOVEABLE);
2013 } else if (TNY_IS_ACCOUNT (instance)) {
2019 g_object_unref (instance);
2026 modest_folder_view_update_model (ModestFolderView *self,
2027 TnyAccountStore *account_store)
2029 ModestFolderViewPrivate *priv;
2030 GtkTreeModel *model /* , *old_model */;
2031 GtkTreeModel *filter_model = NULL, *sortable = NULL;
2033 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW (self), FALSE);
2034 g_return_val_if_fail (account_store && MODEST_IS_TNY_ACCOUNT_STORE(account_store),
2037 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2039 /* Notify that there is no folder selected */
2040 g_signal_emit (G_OBJECT(self),
2041 signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
2043 if (priv->cur_folder_store) {
2044 g_object_unref (priv->cur_folder_store);
2045 priv->cur_folder_store = NULL;
2048 /* FIXME: the local accounts are not shown when the query
2049 selects only the subscribed folders */
2050 #ifdef MODEST_TOOLKIT_HILDON2
2051 model = tny_gtk_folder_list_store_new_with_flags (NULL,
2052 TNY_GTK_FOLDER_LIST_STORE_FLAG_SHOW_PATH);
2053 tny_gtk_folder_list_store_set_path_separator (TNY_GTK_FOLDER_LIST_STORE (model),
2054 MODEST_FOLDER_PATH_SEPARATOR);
2056 model = tny_gtk_folder_store_tree_model_new (NULL);
2059 /* When the model is a list store (plain representation) the
2060 outbox is not a child of any account so we have to manually
2061 delete it because removing the local folders account won't
2062 delete it (because tny_folder_get_account() is not defined
2063 for a merge folder */
2064 if (TNY_IS_GTK_FOLDER_LIST_STORE (model)) {
2065 TnyAccount *account;
2066 ModestTnyAccountStore *acc_store;
2068 acc_store = modest_runtime_get_account_store ();
2069 account = modest_tny_account_store_get_local_folders_account (acc_store);
2071 if (g_signal_handler_is_connected (account,
2072 priv->outbox_deleted_handler))
2073 g_signal_handler_disconnect (account,
2074 priv->outbox_deleted_handler);
2076 priv->outbox_deleted_handler =
2077 g_signal_connect (account,
2079 G_CALLBACK (on_outbox_deleted_cb),
2081 g_object_unref (account);
2084 /* Get the accounts: */
2085 tny_account_store_get_accounts (TNY_ACCOUNT_STORE(account_store),
2087 TNY_ACCOUNT_STORE_STORE_ACCOUNTS);
2089 sortable = gtk_tree_model_sort_new_with_model (model);
2090 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
2092 GTK_SORT_ASCENDING);
2093 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
2095 cmp_rows, NULL, NULL);
2097 /* Create filter model */
2098 filter_model = gtk_tree_model_filter_new (sortable, NULL);
2099 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
2105 gtk_tree_view_set_model (GTK_TREE_VIEW(self), filter_model);
2106 #ifndef MODEST_TOOLKIT_HILDON2
2107 g_signal_connect (G_OBJECT(filter_model), "row-inserted",
2108 (GCallback) on_row_inserted_maybe_select_folder, self);
2111 g_object_unref (model);
2112 g_object_unref (filter_model);
2113 g_object_unref (sortable);
2115 /* Force a reselection of the INBOX next time the widget is shown */
2116 priv->reselect = TRUE;
2123 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
2125 GtkTreeModel *model = NULL;
2126 TnyFolderStore *folder = NULL;
2128 ModestFolderView *tree_view = NULL;
2129 ModestFolderViewPrivate *priv = NULL;
2130 gboolean selected = FALSE;
2132 g_return_if_fail (sel);
2133 g_return_if_fail (user_data);
2135 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
2137 selected = gtk_tree_selection_get_selected (sel, &model, &iter);
2139 tree_view = MODEST_FOLDER_VIEW (user_data);
2142 gtk_tree_model_get (model, &iter,
2143 INSTANCE_COLUMN, &folder,
2146 /* If the folder is the same do not notify */
2147 if (folder && priv->cur_folder_store == folder) {
2148 g_object_unref (folder);
2153 /* Current folder was unselected */
2154 if (priv->cur_folder_store) {
2155 /* We must do this firstly because a libtinymail-camel
2156 implementation detail. If we issue the signal
2157 before doing the sync_async, then that signal could
2158 cause (and it actually does it) a free of the
2159 summary of the folder (because the main window will
2160 clear the headers view */
2161 if (TNY_IS_FOLDER(priv->cur_folder_store))
2162 tny_folder_sync_async (TNY_FOLDER(priv->cur_folder_store),
2163 FALSE, NULL, NULL, NULL);
2165 g_signal_emit (G_OBJECT(tree_view), signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
2166 priv->cur_folder_store, FALSE);
2168 g_object_unref (priv->cur_folder_store);
2169 priv->cur_folder_store = NULL;
2172 /* New current references */
2173 priv->cur_folder_store = folder;
2175 /* New folder has been selected. Do not notify if there is
2176 nothing new selected */
2178 g_signal_emit (G_OBJECT(tree_view),
2179 signals[FOLDER_SELECTION_CHANGED_SIGNAL],
2180 0, priv->cur_folder_store, TRUE);
2185 on_row_activated (GtkTreeView *treeview,
2186 GtkTreePath *treepath,
2187 GtkTreeViewColumn *column,
2190 GtkTreeModel *model = NULL;
2191 TnyFolderStore *folder = NULL;
2193 ModestFolderView *self = NULL;
2194 ModestFolderViewPrivate *priv = NULL;
2196 g_return_if_fail (treeview);
2197 g_return_if_fail (user_data);
2199 self = MODEST_FOLDER_VIEW (user_data);
2200 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
2202 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2204 if (!gtk_tree_model_get_iter (model, &iter, treepath))
2207 gtk_tree_model_get (model, &iter,
2208 INSTANCE_COLUMN, &folder,
2211 g_signal_emit (G_OBJECT(self),
2212 signals[FOLDER_ACTIVATED_SIGNAL],
2215 #ifdef MODEST_TOOLKIT_HILDON2
2216 HildonUIMode ui_mode;
2217 g_object_get (G_OBJECT (self), "hildon-ui-mode", &ui_mode, NULL);
2218 if (ui_mode == HILDON_UI_MODE_NORMAL) {
2219 if (priv->cur_folder_store)
2220 g_object_unref (priv->cur_folder_store);
2221 priv->cur_folder_store = g_object_ref (folder);
2225 g_object_unref (folder);
2229 modest_folder_view_get_selected (ModestFolderView *self)
2231 ModestFolderViewPrivate *priv;
2233 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW(self), NULL);
2235 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2236 if (priv->cur_folder_store)
2237 g_object_ref (priv->cur_folder_store);
2239 return priv->cur_folder_store;
2243 get_cmp_rows_type_pos (GObject *folder)
2245 /* Remote accounts -> Local account -> MMC account .*/
2248 if (TNY_IS_ACCOUNT (folder) &&
2249 modest_tny_account_is_virtual_local_folders (
2250 TNY_ACCOUNT (folder))) {
2252 } else if (TNY_IS_ACCOUNT (folder)) {
2253 TnyAccount *account = TNY_ACCOUNT (folder);
2254 const gchar *account_id = tny_account_get_id (account);
2255 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
2261 printf ("DEBUG: %s: unexpected type.\n", __FUNCTION__);
2262 return -1; /* Should never happen */
2267 inbox_is_special (TnyFolderStore *folder_store)
2269 gboolean is_special = TRUE;
2271 if (TNY_IS_FOLDER (folder_store)) {
2275 gchar *last_inbox_bar;
2277 id = tny_folder_get_id (TNY_FOLDER (folder_store));
2278 downcase = g_utf8_strdown (id, -1);
2279 last_bar = g_strrstr (downcase, "/");
2281 last_inbox_bar = g_strrstr (downcase, "inbox/");
2282 if ((last_inbox_bar == NULL) || (last_inbox_bar + 5 != last_bar))
2293 get_cmp_pos (TnyFolderType t, TnyFolder *folder_store)
2295 TnyAccount *account;
2296 gboolean is_special;
2297 /* Inbox, Outbox, Drafts, Sent, User */
2300 if (!TNY_IS_FOLDER (folder_store))
2303 case TNY_FOLDER_TYPE_INBOX:
2305 account = tny_folder_get_account (folder_store);
2306 is_special = (get_cmp_rows_type_pos (G_OBJECT (account)) == 0);
2308 /* In inbox case we need to know if the inbox is really the top
2309 * inbox of the account, or if it's a submailbox inbox. To do
2310 * this we'll apply an heuristic rule: Find last "/" and check
2311 * if it's preceeded by another Inbox */
2312 is_special = is_special && !inbox_is_special (TNY_FOLDER_STORE (folder_store));
2313 g_object_unref (account);
2314 return is_special?0:4;
2317 case TNY_FOLDER_TYPE_OUTBOX:
2318 return (TNY_IS_MERGE_FOLDER (folder_store))?2:4;
2320 case TNY_FOLDER_TYPE_DRAFTS:
2322 account = tny_folder_get_account (folder_store);
2323 is_special = (get_cmp_rows_type_pos (G_OBJECT (account)) == 1);
2324 g_object_unref (account);
2325 return is_special?1:4;
2328 case TNY_FOLDER_TYPE_SENT:
2330 account = tny_folder_get_account (folder_store);
2331 is_special = (get_cmp_rows_type_pos (G_OBJECT (account)) == 1);
2332 g_object_unref (account);
2333 return is_special?3:4;
2342 compare_account_names (TnyAccount *a1, TnyAccount *a2)
2344 const gchar *a1_name, *a2_name;
2346 a1_name = tny_account_get_name (a1);
2347 a2_name = tny_account_get_name (a2);
2349 return modest_text_utils_utf8_strcmp (a1_name, a2_name, TRUE);
2353 compare_accounts (TnyFolderStore *s1, TnyFolderStore *s2)
2355 TnyAccount *a1 = NULL, *a2 = NULL;
2358 if (TNY_IS_ACCOUNT (s1)) {
2359 a1 = TNY_ACCOUNT (g_object_ref (s1));
2360 } else if (!TNY_IS_MERGE_FOLDER (s1)) {
2361 a1 = tny_folder_get_account (TNY_FOLDER (s1));
2364 if (TNY_IS_ACCOUNT (s2)) {
2365 a2 = TNY_ACCOUNT (g_object_ref (s2));
2366 } else if (!TNY_IS_MERGE_FOLDER (s2)) {
2367 a2 = tny_folder_get_account (TNY_FOLDER (s2));
2384 /* First we sort with the type of account */
2385 cmp = get_cmp_rows_type_pos (G_OBJECT (a1)) - get_cmp_rows_type_pos (G_OBJECT (a2));
2389 cmp = compare_account_names (a1, a2);
2393 g_object_unref (a1);
2395 g_object_unref (a2);
2401 compare_accounts_first (TnyFolderStore *s1, TnyFolderStore *s2)
2403 gint is_account1, is_account2;
2405 is_account1 = TNY_IS_ACCOUNT (s1)?1:0;
2406 is_account2 = TNY_IS_ACCOUNT (s2)?1:0;
2408 return is_account2 - is_account1;
2412 * This function orders the mail accounts according to these rules:
2413 * 1st - remote accounts
2414 * 2nd - local account
2418 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
2422 gchar *name1 = NULL;
2423 gchar *name2 = NULL;
2424 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2425 TnyFolderType type2 = TNY_FOLDER_TYPE_UNKNOWN;
2426 GObject *folder1 = NULL;
2427 GObject *folder2 = NULL;
2429 gtk_tree_model_get (tree_model, iter1,
2430 NAME_COLUMN, &name1,
2432 INSTANCE_COLUMN, &folder1,
2434 gtk_tree_model_get (tree_model, iter2,
2435 NAME_COLUMN, &name2,
2436 TYPE_COLUMN, &type2,
2437 INSTANCE_COLUMN, &folder2,
2440 /* Return if we get no folder. This could happen when folder
2441 operations are happening. The model is updated after the
2442 folder copy/move actually occurs, so there could be
2443 situations where the model to be drawn is not correct */
2444 if (!folder1 || !folder2)
2447 /* Sort by type. First the special folders, then the archives */
2448 cmp = get_cmp_pos (type, (TnyFolder *) folder1) - get_cmp_pos (type2, (TnyFolder *) folder2);
2452 /* Now we sort using the account of each folder */
2453 if (TNY_IS_FOLDER_STORE (folder1) &&
2454 TNY_IS_FOLDER_STORE (folder2)) {
2455 cmp = compare_accounts (TNY_FOLDER_STORE (folder1), TNY_FOLDER_STORE (folder2));
2459 /* Each group is preceeded by its account */
2460 cmp = compare_accounts_first (TNY_FOLDER_STORE (folder1), TNY_FOLDER_STORE (folder2));
2465 /* Pure sort by name */
2466 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
2469 g_object_unref(G_OBJECT(folder1));
2471 g_object_unref(G_OBJECT(folder2));
2479 /*****************************************************************************/
2480 /* DRAG and DROP stuff */
2481 /*****************************************************************************/
2483 * This function fills the #GtkSelectionData with the row and the
2484 * model that has been dragged. It's called when this widget is a
2485 * source for dnd after the event drop happened
2488 on_drag_data_get (GtkWidget *widget, GdkDragContext *context, GtkSelectionData *selection_data,
2489 guint info, guint time, gpointer data)
2491 GtkTreeSelection *selection;
2492 GtkTreeModel *model;
2494 GtkTreePath *source_row;
2496 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
2497 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
2499 source_row = gtk_tree_model_get_path (model, &iter);
2500 gtk_tree_set_row_drag_data (selection_data,
2504 gtk_tree_path_free (source_row);
2508 typedef struct _DndHelper {
2509 ModestFolderView *folder_view;
2510 gboolean delete_source;
2511 GtkTreePath *source_row;
2515 dnd_helper_destroyer (DndHelper *helper)
2517 /* Free the helper */
2518 gtk_tree_path_free (helper->source_row);
2519 g_slice_free (DndHelper, helper);
2523 xfer_folder_cb (ModestMailOperation *mail_op,
2524 TnyFolder *new_folder,
2528 /* Select the folder */
2529 modest_folder_view_select_folder (MODEST_FOLDER_VIEW (user_data),
2535 /* get the folder for the row the treepath refers to. */
2536 /* folder must be unref'd */
2537 static TnyFolderStore *
2538 tree_path_to_folder (GtkTreeModel *model, GtkTreePath *path)
2541 TnyFolderStore *folder = NULL;
2543 if (gtk_tree_model_get_iter (model,&iter, path))
2544 gtk_tree_model_get (model, &iter,
2545 INSTANCE_COLUMN, &folder,
2552 * This function is used by drag_data_received_cb to manage drag and
2553 * drop of a header, i.e, and drag from the header view to the folder
2557 drag_and_drop_from_header_view (GtkTreeModel *source_model,
2558 GtkTreeModel *dest_model,
2559 GtkTreePath *dest_row,
2560 GtkSelectionData *selection_data)
2562 TnyList *headers = NULL;
2563 TnyFolder *folder = NULL, *src_folder = NULL;
2564 TnyFolderType folder_type;
2565 GtkTreeIter source_iter, dest_iter;
2566 ModestWindowMgr *mgr = NULL;
2567 ModestWindow *main_win = NULL;
2568 gchar **uris, **tmp;
2570 /* Build the list of headers */
2571 mgr = modest_runtime_get_window_mgr ();
2572 headers = tny_simple_list_new ();
2573 uris = modest_dnd_selection_data_get_paths (selection_data);
2576 while (*tmp != NULL) {
2579 gboolean first = TRUE;
2582 path = gtk_tree_path_new_from_string (*tmp);
2583 gtk_tree_model_get_iter (source_model, &source_iter, path);
2584 gtk_tree_model_get (source_model, &source_iter,
2585 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
2588 /* Do not enable d&d of headers already opened */
2589 if (!modest_window_mgr_find_registered_header(mgr, header, NULL))
2590 tny_list_append (headers, G_OBJECT (header));
2592 if (G_UNLIKELY (first)) {
2593 src_folder = tny_header_get_folder (header);
2597 /* Free and go on */
2598 gtk_tree_path_free (path);
2599 g_object_unref (header);
2604 /* This could happen ig we perform a d&d very quickly over the
2605 same row that row could dissapear because message is
2607 if (!TNY_IS_FOLDER (src_folder))
2610 /* Get the target folder */
2611 gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
2612 gtk_tree_model_get (dest_model, &dest_iter,
2616 if (!folder || !TNY_IS_FOLDER(folder)) {
2617 /* g_warning ("%s: not a valid target folder (%p)", __FUNCTION__, folder); */
2621 folder_type = modest_tny_folder_guess_folder_type (folder);
2622 if (folder_type == TNY_FOLDER_TYPE_INVALID) {
2623 /* g_warning ("%s: invalid target folder", __FUNCTION__); */
2624 goto cleanup; /* cannot move messages there */
2627 if (modest_tny_folder_get_rules((TNY_FOLDER(folder))) & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE) {
2628 /* g_warning ("folder not writable"); */
2629 goto cleanup; /* verboten! */
2632 /* Ask for confirmation to move */
2633 main_win = modest_window_mgr_get_main_window (mgr, FALSE); /* don't create */
2635 g_warning ("%s: BUG: no main window found", __FUNCTION__);
2639 /* Transfer messages */
2640 modest_ui_actions_transfer_messages_helper (GTK_WINDOW (main_win), src_folder,
2645 if (G_IS_OBJECT (src_folder))
2646 g_object_unref (src_folder);
2647 if (G_IS_OBJECT(folder))
2648 g_object_unref (G_OBJECT (folder));
2649 if (G_IS_OBJECT(headers))
2650 g_object_unref (headers);
2654 TnyFolderStore *src_folder;
2655 TnyFolderStore *dst_folder;
2656 ModestFolderView *folder_view;
2661 dnd_folder_info_destroyer (DndFolderInfo *info)
2663 if (info->src_folder)
2664 g_object_unref (info->src_folder);
2665 if (info->dst_folder)
2666 g_object_unref (info->dst_folder);
2667 g_slice_free (DndFolderInfo, info);
2671 dnd_on_connection_failed_destroyer (DndFolderInfo *info,
2672 GtkWindow *parent_window,
2673 TnyAccount *account)
2676 modest_ui_actions_on_account_connection_error (parent_window, account);
2678 /* Free the helper & info */
2679 dnd_helper_destroyer (info->helper);
2680 dnd_folder_info_destroyer (info);
2684 drag_and_drop_from_folder_view_src_folder_performer (gboolean canceled,
2686 GtkWindow *parent_window,
2687 TnyAccount *account,
2690 DndFolderInfo *info = NULL;
2691 ModestMailOperation *mail_op;
2693 info = (DndFolderInfo *) user_data;
2695 if (err || canceled) {
2696 dnd_on_connection_failed_destroyer (info, parent_window, account);
2700 /* Do the mail operation */
2701 mail_op = modest_mail_operation_new_with_error_handling ((GObject *) parent_window,
2702 modest_ui_actions_move_folder_error_handler,
2703 info->src_folder, NULL);
2705 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
2708 /* Transfer the folder */
2709 modest_mail_operation_xfer_folder (mail_op,
2710 TNY_FOLDER (info->src_folder),
2712 info->helper->delete_source,
2714 info->helper->folder_view);
2717 g_object_unref (G_OBJECT (mail_op));
2718 dnd_helper_destroyer (info->helper);
2719 dnd_folder_info_destroyer (info);
2724 drag_and_drop_from_folder_view_dst_folder_performer (gboolean canceled,
2726 GtkWindow *parent_window,
2727 TnyAccount *account,
2730 DndFolderInfo *info = NULL;
2732 info = (DndFolderInfo *) user_data;
2734 if (err || canceled) {
2735 dnd_on_connection_failed_destroyer (info, parent_window, account);
2739 /* Connect to source folder and perform the copy/move */
2740 modest_platform_connect_if_remote_and_perform (NULL, TRUE,
2742 drag_and_drop_from_folder_view_src_folder_performer,
2747 * This function is used by drag_data_received_cb to manage drag and
2748 * drop of a folder, i.e, and drag from the folder view to the same
2752 drag_and_drop_from_folder_view (GtkTreeModel *source_model,
2753 GtkTreeModel *dest_model,
2754 GtkTreePath *dest_row,
2755 GtkSelectionData *selection_data,
2758 GtkTreeIter dest_iter, iter;
2759 TnyFolderStore *dest_folder = NULL;
2760 TnyFolderStore *folder = NULL;
2761 gboolean forbidden = FALSE;
2763 DndFolderInfo *info = NULL;
2765 win = modest_window_mgr_get_main_window (modest_runtime_get_window_mgr(), FALSE); /* don't create */
2767 g_warning ("%s: BUG: no main window", __FUNCTION__);
2768 dnd_helper_destroyer (helper);
2773 /* check the folder rules for the destination */
2774 folder = tree_path_to_folder (dest_model, dest_row);
2775 if (TNY_IS_FOLDER(folder)) {
2776 ModestTnyFolderRules rules =
2777 modest_tny_folder_get_rules (TNY_FOLDER (folder));
2778 forbidden = rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE;
2779 } else if (TNY_IS_FOLDER_STORE(folder)) {
2780 /* enable local root as destination for folders */
2781 if (!MODEST_IS_TNY_LOCAL_FOLDERS_ACCOUNT (folder) &&
2782 !modest_tny_account_is_memory_card_account (TNY_ACCOUNT (folder)))
2785 g_object_unref (folder);
2788 /* check the folder rules for the source */
2789 folder = tree_path_to_folder (source_model, helper->source_row);
2790 if (TNY_IS_FOLDER(folder)) {
2791 ModestTnyFolderRules rules =
2792 modest_tny_folder_get_rules (TNY_FOLDER (folder));
2793 forbidden = rules & MODEST_FOLDER_RULES_FOLDER_NON_MOVEABLE;
2796 g_object_unref (folder);
2800 /* Check if the drag is possible */
2801 if (forbidden || !gtk_tree_path_compare (helper->source_row, dest_row)) {
2803 modest_platform_run_information_dialog ((GtkWindow *) win,
2804 _("mail_in_ui_folder_move_target_error"),
2806 /* Restore the previous selection */
2807 folder = tree_path_to_folder (source_model, helper->source_row);
2809 if (TNY_IS_FOLDER (folder))
2810 modest_folder_view_select_folder (helper->folder_view,
2811 TNY_FOLDER (folder), FALSE);
2812 g_object_unref (folder);
2814 dnd_helper_destroyer (helper);
2819 gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
2820 gtk_tree_model_get (dest_model, &dest_iter,
2823 gtk_tree_model_get_iter (source_model, &iter, helper->source_row);
2824 gtk_tree_model_get (source_model, &iter,
2828 /* Create the info for the performer */
2829 info = g_slice_new0 (DndFolderInfo);
2830 info->src_folder = g_object_ref (folder);
2831 info->dst_folder = g_object_ref (dest_folder);
2832 info->helper = helper;
2834 /* Connect to the destination folder and perform the copy/move */
2835 modest_platform_connect_if_remote_and_perform (GTK_WINDOW (win), TRUE,
2837 drag_and_drop_from_folder_view_dst_folder_performer,
2841 g_object_unref (dest_folder);
2842 g_object_unref (folder);
2846 * This function receives the data set by the "drag-data-get" signal
2847 * handler. This information comes within the #GtkSelectionData. This
2848 * function will manage both the drags of folders of the treeview and
2849 * drags of headers of the header view widget.
2852 on_drag_data_received (GtkWidget *widget,
2853 GdkDragContext *context,
2856 GtkSelectionData *selection_data,
2861 GtkWidget *source_widget;
2862 GtkTreeModel *dest_model, *source_model;
2863 GtkTreePath *source_row, *dest_row;
2864 GtkTreeViewDropPosition pos;
2865 gboolean delete_source = FALSE;
2866 gboolean success = FALSE;
2868 /* Do not allow further process */
2869 g_signal_stop_emission_by_name (widget, "drag-data-received");
2870 source_widget = gtk_drag_get_source_widget (context);
2872 /* Get the action */
2873 if (context->action == GDK_ACTION_MOVE) {
2874 delete_source = TRUE;
2876 /* Notify that there is no folder selected. We need to
2877 do this in order to update the headers view (and
2878 its monitors, because when moving, the old folder
2879 won't longer exist. We can not wait for the end of
2880 the operation, because the operation won't start if
2881 the folder is in use */
2882 if (source_widget == widget) {
2883 GtkTreeSelection *sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
2884 gtk_tree_selection_unselect_all (sel);
2888 /* Check if the get_data failed */
2889 if (selection_data == NULL || selection_data->length < 0)
2892 /* Select the destination model */
2893 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
2895 /* Get the path to the destination row. Can not call
2896 gtk_tree_view_get_drag_dest_row() because the source row
2897 is not selected anymore */
2898 gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget), x, y,
2901 /* Only allow drops IN other rows */
2903 pos == GTK_TREE_VIEW_DROP_BEFORE ||
2904 pos == GTK_TREE_VIEW_DROP_AFTER)
2908 /* Drags from the header view */
2909 if (source_widget != widget) {
2910 source_model = gtk_tree_view_get_model (GTK_TREE_VIEW (source_widget));
2912 drag_and_drop_from_header_view (source_model,
2917 DndHelper *helper = NULL;
2919 /* Get the source model and row */
2920 gtk_tree_get_row_drag_data (selection_data,
2924 /* Create the helper */
2925 helper = g_slice_new0 (DndHelper);
2926 helper->delete_source = delete_source;
2927 helper->source_row = gtk_tree_path_copy (source_row);
2928 helper->folder_view = MODEST_FOLDER_VIEW (widget);
2930 drag_and_drop_from_folder_view (source_model,
2936 gtk_tree_path_free (source_row);
2940 gtk_tree_path_free (dest_row);
2943 /* Finish the drag and drop */
2944 gtk_drag_finish (context, success, FALSE, time);
2948 * We define a "drag-drop" signal handler because we do not want to
2949 * use the default one, because the default one always calls
2950 * gtk_drag_finish and we prefer to do it in the "drag-data-received"
2951 * signal handler, because there we have all the information available
2952 * to know if the dnd was a success or not.
2955 drag_drop_cb (GtkWidget *widget,
2956 GdkDragContext *context,
2964 if (!context->targets)
2967 /* Check if we're dragging a folder row */
2968 target = gtk_drag_dest_find_target (widget, context, NULL);
2970 /* Request the data from the source. */
2971 gtk_drag_get_data(widget, context, target, time);
2977 * This function expands a node of a tree view if it's not expanded
2978 * yet. Not sure why it needs the threads stuff, but gtk+`example code
2979 * does that, so that's why they're here.
2982 expand_row_timeout (gpointer data)
2984 GtkTreeView *tree_view = data;
2985 GtkTreePath *dest_path = NULL;
2986 GtkTreeViewDropPosition pos;
2987 gboolean result = FALSE;
2989 gdk_threads_enter ();
2991 gtk_tree_view_get_drag_dest_row (tree_view,
2996 (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER ||
2997 pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)) {
2998 gtk_tree_view_expand_row (tree_view, dest_path, FALSE);
2999 gtk_tree_path_free (dest_path);
3003 gtk_tree_path_free (dest_path);
3008 gdk_threads_leave ();
3014 * This function is called whenever the pointer is moved over a widget
3015 * while dragging some data. It installs a timeout that will expand a
3016 * node of the treeview if not expanded yet. This function also calls
3017 * gdk_drag_status in order to set the suggested action that will be
3018 * used by the "drag-data-received" signal handler to know if we
3019 * should do a move or just a copy of the data.
3022 on_drag_motion (GtkWidget *widget,
3023 GdkDragContext *context,
3029 GtkTreeViewDropPosition pos;
3030 GtkTreePath *dest_row;
3031 GtkTreeModel *dest_model;
3032 ModestFolderViewPrivate *priv;
3033 GdkDragAction suggested_action;
3034 gboolean valid_location = FALSE;
3035 TnyFolderStore *folder = NULL;
3037 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
3039 if (priv->timer_expander != 0) {
3040 g_source_remove (priv->timer_expander);
3041 priv->timer_expander = 0;
3044 gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
3049 /* Do not allow drops between folders */
3051 pos == GTK_TREE_VIEW_DROP_BEFORE ||
3052 pos == GTK_TREE_VIEW_DROP_AFTER) {
3053 gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW (widget), NULL, 0);
3054 gdk_drag_status(context, 0, time);
3055 valid_location = FALSE;
3058 valid_location = TRUE;
3061 /* Check that the destination folder is writable */
3062 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
3063 folder = tree_path_to_folder (dest_model, dest_row);
3064 if (folder && TNY_IS_FOLDER (folder)) {
3065 ModestTnyFolderRules rules = modest_tny_folder_get_rules(TNY_FOLDER (folder));
3067 if (rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE) {
3068 valid_location = FALSE;
3073 /* Expand the selected row after 1/2 second */
3074 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), dest_row)) {
3075 priv->timer_expander = g_timeout_add (500, expand_row_timeout, widget);
3077 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), dest_row, pos);
3079 /* Select the desired action. By default we pick MOVE */
3080 suggested_action = GDK_ACTION_MOVE;
3082 if (context->actions == GDK_ACTION_COPY)
3083 gdk_drag_status(context, GDK_ACTION_COPY, time);
3084 else if (context->actions == GDK_ACTION_MOVE)
3085 gdk_drag_status(context, GDK_ACTION_MOVE, time);
3086 else if (context->actions & suggested_action)
3087 gdk_drag_status(context, suggested_action, time);
3089 gdk_drag_status(context, GDK_ACTION_DEFAULT, time);
3093 g_object_unref (folder);
3095 gtk_tree_path_free (dest_row);
3097 g_signal_stop_emission_by_name (widget, "drag-motion");
3099 return valid_location;
3103 * This function sets the treeview as a source and a target for dnd
3104 * events. It also connects all the requirede signals.
3107 setup_drag_and_drop (GtkTreeView *self)
3109 /* Set up the folder view as a dnd destination. Set only the
3110 highlight flag, otherwise gtk will have a different
3112 #ifdef MODEST_TOOLKIT_HILDON2
3115 gtk_drag_dest_set (GTK_WIDGET (self),
3116 GTK_DEST_DEFAULT_HIGHLIGHT,
3117 folder_view_drag_types,
3118 G_N_ELEMENTS (folder_view_drag_types),
3119 GDK_ACTION_MOVE | GDK_ACTION_COPY);
3121 g_signal_connect (G_OBJECT (self),
3122 "drag_data_received",
3123 G_CALLBACK (on_drag_data_received),
3127 /* Set up the treeview as a dnd source */
3128 gtk_drag_source_set (GTK_WIDGET (self),
3130 folder_view_drag_types,
3131 G_N_ELEMENTS (folder_view_drag_types),
3132 GDK_ACTION_MOVE | GDK_ACTION_COPY);
3134 g_signal_connect (G_OBJECT (self),
3136 G_CALLBACK (on_drag_motion),
3139 g_signal_connect (G_OBJECT (self),
3141 G_CALLBACK (on_drag_data_get),
3144 g_signal_connect (G_OBJECT (self),
3146 G_CALLBACK (drag_drop_cb),
3151 * This function manages the navigation through the folders using the
3152 * keyboard or the hardware keys in the device
3155 on_key_pressed (GtkWidget *self,
3159 GtkTreeSelection *selection;
3161 GtkTreeModel *model;
3162 gboolean retval = FALSE;
3164 /* Up and Down are automatically managed by the treeview */
3165 if (event->keyval == GDK_Return) {
3166 /* Expand/Collapse the selected row */
3167 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
3168 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
3171 path = gtk_tree_model_get_path (model, &iter);
3173 if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (self), path))
3174 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
3176 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
3177 gtk_tree_path_free (path);
3179 /* No further processing */
3187 * We listen to the changes in the local folder account name key,
3188 * because we want to show the right name in the view. The local
3189 * folder account name corresponds to the device name in the Maemo
3190 * version. We do this because we do not want to query gconf on each
3191 * tree view refresh. It's better to cache it and change whenever
3195 on_configuration_key_changed (ModestConf* conf,
3197 ModestConfEvent event,
3198 ModestConfNotificationId id,
3199 ModestFolderView *self)
3201 ModestFolderViewPrivate *priv;
3204 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
3205 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
3207 if (!strcmp (key, MODEST_CONF_DEVICE_NAME)) {
3208 g_free (priv->local_account_name);
3210 if (event == MODEST_CONF_EVENT_KEY_UNSET)
3211 priv->local_account_name = g_strdup (MODEST_LOCAL_FOLDERS_DEFAULT_DISPLAY_NAME);
3213 priv->local_account_name = modest_conf_get_string (modest_runtime_get_conf(),
3214 MODEST_CONF_DEVICE_NAME, NULL);
3216 /* Force a redraw */
3217 #if GTK_CHECK_VERSION(2, 8, 0)
3218 GtkTreeViewColumn * tree_column;
3220 tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self),
3222 gtk_tree_view_column_queue_resize (tree_column);
3224 gtk_widget_queue_draw (GTK_WIDGET (self));
3230 modest_folder_view_set_style (ModestFolderView *self,
3231 ModestFolderViewStyle style)
3233 ModestFolderViewPrivate *priv;
3235 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
3236 g_return_if_fail (style == MODEST_FOLDER_VIEW_STYLE_SHOW_ALL ||
3237 style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE);
3239 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
3242 priv->style = style;
3246 modest_folder_view_set_account_id_of_visible_server_account (ModestFolderView *self,
3247 const gchar *account_id)
3249 ModestFolderViewPrivate *priv;
3250 GtkTreeModel *model;
3252 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
3254 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
3256 /* This will be used by the filter_row callback,
3257 * to decided which rows to show: */
3258 if (priv->visible_account_id) {
3259 g_free (priv->visible_account_id);
3260 priv->visible_account_id = NULL;
3263 priv->visible_account_id = g_strdup (account_id);
3266 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
3267 if (GTK_IS_TREE_MODEL_FILTER (model))
3268 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
3270 /* Save settings to gconf */
3271 modest_widget_memory_save (modest_runtime_get_conf (), G_OBJECT(self),
3272 MODEST_CONF_FOLDER_VIEW_KEY);
3276 modest_folder_view_get_account_id_of_visible_server_account (ModestFolderView *self)
3278 ModestFolderViewPrivate *priv;
3280 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW(self), NULL);
3282 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
3284 return (const gchar *) priv->visible_account_id;
3288 find_inbox_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *inbox_iter)
3292 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
3294 gtk_tree_model_get (model, iter,
3298 gboolean result = FALSE;
3299 if (type == TNY_FOLDER_TYPE_INBOX) {
3303 *inbox_iter = *iter;
3307 if (gtk_tree_model_iter_children (model, &child, iter)) {
3308 if (find_inbox_iter (model, &child, inbox_iter))
3312 } while (gtk_tree_model_iter_next (model, iter));
3321 modest_folder_view_select_first_inbox_or_local (ModestFolderView *self)
3323 GtkTreeModel *model;
3324 GtkTreeIter iter, inbox_iter;
3325 GtkTreeSelection *sel;
3326 GtkTreePath *path = NULL;
3328 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
3330 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
3334 expand_root_items (self);
3335 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
3337 if (!gtk_tree_model_get_iter_first (model, &iter)) {
3338 g_warning ("%s: model is empty", __FUNCTION__);
3342 if (find_inbox_iter (model, &iter, &inbox_iter))
3343 path = gtk_tree_model_get_path (model, &inbox_iter);
3345 path = gtk_tree_path_new_first ();
3347 /* Select the row and free */
3348 gtk_tree_view_set_cursor (GTK_TREE_VIEW (self), path, NULL, FALSE);
3349 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (self), path, NULL, FALSE, 0.0, 0.0);
3350 gtk_tree_path_free (path);
3353 gtk_widget_grab_focus (GTK_WIDGET(self));
3359 find_folder_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *folder_iter,
3364 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
3365 TnyFolder* a_folder;
3368 gtk_tree_model_get (model, iter,
3369 INSTANCE_COLUMN, &a_folder,
3375 if (folder == a_folder) {
3376 g_object_unref (a_folder);
3377 *folder_iter = *iter;
3380 g_object_unref (a_folder);
3382 if (gtk_tree_model_iter_children (model, &child, iter)) {
3383 if (find_folder_iter (model, &child, folder_iter, folder))
3387 } while (gtk_tree_model_iter_next (model, iter));
3392 #ifndef MODEST_TOOLKIT_HILDON2
3394 on_row_inserted_maybe_select_folder (GtkTreeModel *tree_model,
3397 ModestFolderView *self)
3399 ModestFolderViewPrivate *priv = NULL;
3400 GtkTreeSelection *sel;
3401 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
3402 GObject *instance = NULL;
3404 if (!MODEST_IS_FOLDER_VIEW(self))
3407 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
3409 priv->reexpand = TRUE;
3411 gtk_tree_model_get (tree_model, iter,
3413 INSTANCE_COLUMN, &instance,
3419 if (type == TNY_FOLDER_TYPE_INBOX && priv->folder_to_select == NULL) {
3420 priv->folder_to_select = g_object_ref (instance);
3422 g_object_unref (instance);
3424 if (priv->folder_to_select) {
3426 if (!modest_folder_view_select_folder (self, priv->folder_to_select,
3429 path = gtk_tree_model_get_path (tree_model, iter);
3430 gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
3432 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
3434 gtk_tree_selection_select_iter (sel, iter);
3435 gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
3437 gtk_tree_path_free (path);
3441 modest_folder_view_disable_next_folder_selection (self);
3443 /* Refilter the model */
3444 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (tree_model));
3450 modest_folder_view_disable_next_folder_selection (ModestFolderView *self)
3452 ModestFolderViewPrivate *priv;
3454 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
3456 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
3458 if (priv->folder_to_select)
3459 g_object_unref(priv->folder_to_select);
3461 priv->folder_to_select = NULL;
3465 modest_folder_view_select_folder (ModestFolderView *self, TnyFolder *folder,
3466 gboolean after_change)
3468 GtkTreeModel *model;
3469 GtkTreeIter iter, folder_iter;
3470 GtkTreeSelection *sel;
3471 ModestFolderViewPrivate *priv = NULL;
3473 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW (self), FALSE);
3474 g_return_val_if_fail (folder && TNY_IS_FOLDER (folder), FALSE);
3476 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
3479 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
3480 gtk_tree_selection_unselect_all (sel);
3482 if (priv->folder_to_select)
3483 g_object_unref(priv->folder_to_select);
3484 priv->folder_to_select = TNY_FOLDER(g_object_ref(folder));
3488 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
3493 /* Refilter the model, before selecting the folder */
3494 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
3496 if (!gtk_tree_model_get_iter_first (model, &iter)) {
3497 g_warning ("%s: model is empty", __FUNCTION__);
3501 if (find_folder_iter (model, &iter, &folder_iter, folder)) {
3504 path = gtk_tree_model_get_path (model, &folder_iter);
3505 gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
3507 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
3508 gtk_tree_selection_select_iter (sel, &folder_iter);
3509 gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
3511 gtk_tree_path_free (path);
3519 modest_folder_view_copy_selection (ModestFolderView *self)
3521 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
3523 /* Copy selection */
3524 _clipboard_set_selected_data (self, FALSE);
3528 modest_folder_view_cut_selection (ModestFolderView *folder_view)
3530 ModestFolderViewPrivate *priv = NULL;
3531 GtkTreeModel *model = NULL;
3532 const gchar **hidding = NULL;
3533 guint i, n_selected;
3535 g_return_if_fail (folder_view && MODEST_IS_FOLDER_VIEW (folder_view));
3536 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
3538 /* Copy selection */
3539 if (!_clipboard_set_selected_data (folder_view, TRUE))
3542 /* Get hidding ids */
3543 hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected);
3545 /* Clear hidding array created by previous cut operation */
3546 _clear_hidding_filter (MODEST_FOLDER_VIEW (folder_view));
3548 /* Copy hidding array */
3549 priv->n_selected = n_selected;
3550 priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
3551 for (i=0; i < n_selected; i++)
3552 priv->hidding_ids[i] = g_strdup(hidding[i]);
3554 /* Hide cut folders */
3555 model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
3556 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
3560 modest_folder_view_copy_model (ModestFolderView *folder_view_src,
3561 ModestFolderView *folder_view_dst)
3563 GtkTreeModel *filter_model = NULL;
3564 GtkTreeModel *model = NULL;
3565 GtkTreeModel *new_filter_model = NULL;
3567 g_return_if_fail (folder_view_src && MODEST_IS_FOLDER_VIEW (folder_view_src));
3568 g_return_if_fail (folder_view_dst && MODEST_IS_FOLDER_VIEW (folder_view_dst));
3571 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view_src));
3572 model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER(filter_model));
3574 /* Build new filter model */
3575 new_filter_model = gtk_tree_model_filter_new (model, NULL);
3576 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (new_filter_model),
3580 /* Set copied model */
3581 gtk_tree_view_set_model (GTK_TREE_VIEW (folder_view_dst), new_filter_model);
3582 #ifndef MODEST_TOOLKIT_HILDON2
3583 g_signal_connect (G_OBJECT(new_filter_model), "row-inserted",
3584 (GCallback) on_row_inserted_maybe_select_folder, folder_view_dst);
3588 g_object_unref (new_filter_model);
3592 modest_folder_view_show_non_move_folders (ModestFolderView *folder_view,
3595 GtkTreeModel *model = NULL;
3596 ModestFolderViewPrivate* priv;
3598 g_return_if_fail (folder_view && MODEST_IS_FOLDER_VIEW (folder_view));
3600 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
3601 priv->show_non_move = show;
3602 /* modest_folder_view_update_model(folder_view, */
3603 /* TNY_ACCOUNT_STORE(modest_runtime_get_account_store())); */
3605 /* Hide special folders */
3606 model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
3607 if (GTK_IS_TREE_MODEL_FILTER (model)) {
3608 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
3612 /* Returns FALSE if it did not selected anything */
3614 _clipboard_set_selected_data (ModestFolderView *folder_view,
3617 ModestFolderViewPrivate *priv = NULL;
3618 TnyFolderStore *folder = NULL;
3619 gboolean retval = FALSE;
3621 g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (folder_view), FALSE);
3622 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
3624 /* Set selected data on clipboard */
3625 g_return_val_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard), FALSE);
3626 folder = modest_folder_view_get_selected (folder_view);
3628 /* Do not allow to select an account */
3629 if (TNY_IS_FOLDER (folder)) {
3630 modest_email_clipboard_set_data (priv->clipboard, TNY_FOLDER(folder), NULL, delete);
3635 g_object_unref (folder);
3641 _clear_hidding_filter (ModestFolderView *folder_view)
3643 ModestFolderViewPrivate *priv;
3646 g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view));
3647 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
3649 if (priv->hidding_ids != NULL) {
3650 for (i=0; i < priv->n_selected; i++)
3651 g_free (priv->hidding_ids[i]);
3652 g_free(priv->hidding_ids);
3658 on_display_name_changed (ModestAccountMgr *mgr,
3659 const gchar *account,
3662 ModestFolderView *self;
3664 self = MODEST_FOLDER_VIEW (user_data);
3666 /* Force a redraw */
3667 #if GTK_CHECK_VERSION(2, 8, 0)
3668 GtkTreeViewColumn * tree_column;
3670 tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self),
3672 gtk_tree_view_column_queue_resize (tree_column);
3674 gtk_widget_queue_draw (GTK_WIDGET (self));
3679 modest_folder_view_set_cell_style (ModestFolderView *self,
3680 ModestFolderViewCellStyle cell_style)
3682 ModestFolderViewPrivate *priv = NULL;
3684 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
3685 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
3687 priv->cell_style = cell_style;
3689 g_object_set (G_OBJECT (priv->messages_renderer),
3690 "visible", (cell_style == MODEST_FOLDER_VIEW_CELL_STYLE_COMPACT),
3693 gtk_widget_queue_draw (GTK_WIDGET (self));
3697 update_style (ModestFolderView *self)
3699 ModestFolderViewPrivate *priv;
3700 GdkColor style_color;
3701 PangoAttrList *attr_list;
3703 PangoAttribute *attr;
3705 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
3706 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
3710 attr_list = pango_attr_list_new ();
3711 if (!gtk_style_lookup_color (GTK_WIDGET (self)->style, "SecondaryTextColor", &style_color)) {
3712 gdk_color_parse ("grey", &style_color);
3714 attr = pango_attr_foreground_new (style_color.red, style_color.green, style_color.blue);
3715 pango_attr_list_insert (attr_list, attr);
3718 style = gtk_rc_get_style_by_paths (gtk_widget_get_settings
3720 "SmallSystemFont", NULL,
3723 attr = pango_attr_font_desc_new (pango_font_description_copy
3724 (style->font_desc));
3725 pango_attr_list_insert (attr_list, attr);
3727 g_object_set (G_OBJECT (priv->messages_renderer),
3728 "foreground-gdk", &style_color,
3729 "foreground-set", TRUE,
3730 "attributes", attr_list,
3732 pango_attr_list_unref (attr_list);
3737 on_notify_style (GObject *obj, GParamSpec *spec, gpointer userdata)
3739 if (strcmp ("style", spec->name) == 0) {
3740 update_style (MODEST_FOLDER_VIEW (obj));
3741 gtk_widget_queue_draw (GTK_WIDGET (obj));
3746 modest_folder_view_set_filter (ModestFolderView *self,
3747 ModestFolderViewFilter filter)
3749 ModestFolderViewPrivate *priv;
3750 GtkTreeModel *filter_model;
3752 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
3753 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
3755 priv->filter |= filter;
3757 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
3758 if (GTK_IS_TREE_MODEL_FILTER(filter_model)) {
3759 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
3764 modest_folder_view_unset_filter (ModestFolderView *self,
3765 ModestFolderViewFilter filter)
3767 ModestFolderViewPrivate *priv;
3768 GtkTreeModel *filter_model;
3770 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
3771 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
3773 priv->filter &= ~filter;
3775 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
3776 if (GTK_IS_TREE_MODEL_FILTER(filter_model)) {
3777 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
3782 modest_folder_view_any_folder_fulfils_rules (ModestFolderView *self,
3783 ModestTnyFolderRules rules)
3785 GtkTreeModel *filter_model;
3787 gboolean fulfil = FALSE;
3789 if (!get_inner_models (self, &filter_model, NULL, NULL))
3792 if (!gtk_tree_model_get_iter_first (filter_model, &iter))
3796 TnyFolderStore *folder;
3798 gtk_tree_model_get (filter_model, &iter, INSTANCE_COLUMN, &folder, -1);
3800 if (TNY_IS_FOLDER (folder)) {
3801 ModestTnyFolderRules folder_rules = modest_tny_folder_get_rules (TNY_FOLDER (folder));
3802 /* Folder rules are negative: non_writable, non_deletable... */
3803 if (!(folder_rules & rules))
3806 g_object_unref (folder);
3809 } while (gtk_tree_model_iter_next (filter_model, &iter) && !fulfil);
3815 modest_folder_view_set_list_to_move (ModestFolderView *self,
3818 ModestFolderViewPrivate *priv;
3820 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
3821 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
3823 if (priv->list_to_move)
3824 g_object_unref (priv->list_to_move);
3827 g_object_ref (list);
3829 priv->list_to_move = list;