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-tny-account.h>
48 #include <modest-tny-folder.h>
49 #include <modest-tny-local-folders-account.h>
50 #include <modest-tny-outbox-account.h>
51 #include <modest-marshal.h>
52 #include <modest-icon-names.h>
53 #include <modest-tny-account-store.h>
54 #include <modest-tny-local-folders-account.h>
55 #include <modest-text-utils.h>
56 #include <modest-runtime.h>
57 #include "modest-folder-view.h"
58 #include <modest-platform.h>
59 #include <modest-widget-memory.h>
60 #include <modest-ui-actions.h>
61 #include "modest-dnd.h"
62 #include "modest-ui-constants.h"
63 #include "widgets/modest-window.h"
65 /* Folder view drag types */
66 const GtkTargetEntry folder_view_drag_types[] =
68 { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, MODEST_FOLDER_ROW },
69 { GTK_TREE_PATH_AS_STRING_LIST, GTK_TARGET_SAME_APP, MODEST_HEADER_ROW }
72 /* Default icon sizes for Fremantle style are different */
73 #ifdef MODEST_TOOLKIT_HILDON2
74 #define FOLDER_ICON_SIZE MODEST_ICON_SIZE_BIG
76 #define FOLDER_ICON_SIZE MODEST_ICON_SIZE_SMALL
79 /* Column names depending on we use list store or tree store */
80 #ifdef MODEST_TOOLKIT_HILDON2
81 #define NAME_COLUMN TNY_GTK_FOLDER_LIST_STORE_NAME_COLUMN
82 #define UNREAD_COLUMN TNY_GTK_FOLDER_LIST_STORE_UNREAD_COLUMN
83 #define ALL_COLUMN TNY_GTK_FOLDER_LIST_STORE_ALL_COLUMN
84 #define TYPE_COLUMN TNY_GTK_FOLDER_LIST_STORE_TYPE_COLUMN
85 #define INSTANCE_COLUMN TNY_GTK_FOLDER_LIST_STORE_INSTANCE_COLUMN
87 #define NAME_COLUMN TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN
88 #define UNREAD_COLUMN TNY_GTK_FOLDER_STORE_TREE_MODEL_UNREAD_COLUMN
89 #define ALL_COLUMN TNY_GTK_FOLDER_STORE_TREE_MODEL_ALL_COLUMN
90 #define TYPE_COLUMN TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN
91 #define INSTANCE_COLUMN TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN
94 /* 'private'/'protected' functions */
95 static void modest_folder_view_class_init (ModestFolderViewClass *klass);
96 static void modest_folder_view_init (ModestFolderView *obj);
97 static void modest_folder_view_finalize (GObject *obj);
99 static void tny_account_store_view_init (gpointer g,
100 gpointer iface_data);
102 static void modest_folder_view_set_account_store (TnyAccountStoreView *self,
103 TnyAccountStore *account_store);
105 static void on_selection_changed (GtkTreeSelection *sel,
108 static void on_row_activated (GtkTreeView *treeview,
110 GtkTreeViewColumn *column,
113 static void on_account_removed (TnyAccountStore *self,
117 static void on_account_inserted (TnyAccountStore *self,
121 static void on_account_changed (TnyAccountStore *self,
125 static gint cmp_rows (GtkTreeModel *tree_model,
130 static gboolean filter_row (GtkTreeModel *model,
134 static gboolean on_key_pressed (GtkWidget *self,
138 static void on_configuration_key_changed (ModestConf* conf,
140 ModestConfEvent event,
141 ModestConfNotificationId notification_id,
142 ModestFolderView *self);
145 static void on_drag_data_get (GtkWidget *widget,
146 GdkDragContext *context,
147 GtkSelectionData *selection_data,
152 static void on_drag_data_received (GtkWidget *widget,
153 GdkDragContext *context,
156 GtkSelectionData *selection_data,
161 static gboolean on_drag_motion (GtkWidget *widget,
162 GdkDragContext *context,
168 static void expand_root_items (ModestFolderView *self);
170 static gint expand_row_timeout (gpointer data);
172 static void setup_drag_and_drop (GtkTreeView *self);
174 static gboolean _clipboard_set_selected_data (ModestFolderView *folder_view,
177 static void _clear_hidding_filter (ModestFolderView *folder_view);
179 #ifndef MODEST_TOOLKIT_HILDON2
180 static void on_row_inserted_maybe_select_folder (GtkTreeModel *tree_model,
183 ModestFolderView *self);
186 static void on_display_name_changed (ModestAccountMgr *self,
187 const gchar *account,
189 static void update_style (ModestFolderView *self);
190 static void on_notify_style (GObject *obj, GParamSpec *spec, gpointer userdata);
191 static gint get_cmp_pos (TnyFolderType t, TnyFolder *folder_store);
192 static gboolean inbox_is_special (TnyFolderStore *folder_store);
194 static gboolean get_inner_models (ModestFolderView *self,
195 GtkTreeModel **filter_model,
196 GtkTreeModel **sort_model,
197 GtkTreeModel **tny_model);
200 FOLDER_SELECTION_CHANGED_SIGNAL,
201 FOLDER_DISPLAY_NAME_CHANGED_SIGNAL,
202 FOLDER_ACTIVATED_SIGNAL,
206 typedef struct _ModestFolderViewPrivate ModestFolderViewPrivate;
207 struct _ModestFolderViewPrivate {
208 TnyAccountStore *account_store;
209 TnyFolderStore *cur_folder_store;
211 TnyFolder *folder_to_select; /* folder to select after the next update */
213 gulong changed_signal;
214 gulong account_inserted_signal;
215 gulong account_removed_signal;
216 gulong account_changed_signal;
217 gulong conf_key_signal;
218 gulong display_name_changed_signal;
220 /* not unref this object, its a singlenton */
221 ModestEmailClipboard *clipboard;
223 /* Filter tree model */
226 ModestFolderViewFilter filter;
228 TnyFolderStoreQuery *query;
229 guint timer_expander;
231 gchar *local_account_name;
232 gchar *visible_account_id;
233 ModestFolderViewStyle style;
234 ModestFolderViewCellStyle cell_style;
236 gboolean reselect; /* we use this to force a reselection of the INBOX */
237 gboolean show_non_move;
238 TnyList *list_to_move;
239 gboolean reexpand; /* next time we expose, we'll expand all root folders */
241 GtkCellRenderer *messages_renderer;
243 gulong outbox_deleted_handler;
245 #define MODEST_FOLDER_VIEW_GET_PRIVATE(o) \
246 (G_TYPE_INSTANCE_GET_PRIVATE((o), \
247 MODEST_TYPE_FOLDER_VIEW, \
248 ModestFolderViewPrivate))
250 static GObjectClass *parent_class = NULL;
252 static guint signals[LAST_SIGNAL] = {0};
255 modest_folder_view_get_type (void)
257 static GType my_type = 0;
259 static const GTypeInfo my_info = {
260 sizeof(ModestFolderViewClass),
261 NULL, /* base init */
262 NULL, /* base finalize */
263 (GClassInitFunc) modest_folder_view_class_init,
264 NULL, /* class finalize */
265 NULL, /* class data */
266 sizeof(ModestFolderView),
268 (GInstanceInitFunc) modest_folder_view_init,
272 static const GInterfaceInfo tny_account_store_view_info = {
273 (GInterfaceInitFunc) tny_account_store_view_init, /* interface_init */
274 NULL, /* interface_finalize */
275 NULL /* interface_data */
279 my_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
283 g_type_add_interface_static (my_type,
284 TNY_TYPE_ACCOUNT_STORE_VIEW,
285 &tny_account_store_view_info);
291 modest_folder_view_class_init (ModestFolderViewClass *klass)
293 GObjectClass *gobject_class;
294 GtkTreeViewClass *treeview_class;
295 gobject_class = (GObjectClass*) klass;
296 treeview_class = (GtkTreeViewClass*) klass;
298 parent_class = g_type_class_peek_parent (klass);
299 gobject_class->finalize = modest_folder_view_finalize;
301 g_type_class_add_private (gobject_class,
302 sizeof(ModestFolderViewPrivate));
304 signals[FOLDER_SELECTION_CHANGED_SIGNAL] =
305 g_signal_new ("folder_selection_changed",
306 G_TYPE_FROM_CLASS (gobject_class),
308 G_STRUCT_OFFSET (ModestFolderViewClass,
309 folder_selection_changed),
311 modest_marshal_VOID__POINTER_BOOLEAN,
312 G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_BOOLEAN);
315 * This signal is emitted whenever the currently selected
316 * folder display name is computed. Note that the name could
317 * be different to the folder name, because we could append
318 * the unread messages count to the folder name to build the
319 * folder display name
321 signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL] =
322 g_signal_new ("folder-display-name-changed",
323 G_TYPE_FROM_CLASS (gobject_class),
325 G_STRUCT_OFFSET (ModestFolderViewClass,
326 folder_display_name_changed),
328 g_cclosure_marshal_VOID__STRING,
329 G_TYPE_NONE, 1, G_TYPE_STRING);
331 signals[FOLDER_ACTIVATED_SIGNAL] =
332 g_signal_new ("folder_activated",
333 G_TYPE_FROM_CLASS (gobject_class),
335 G_STRUCT_OFFSET (ModestFolderViewClass,
338 g_cclosure_marshal_VOID__POINTER,
339 G_TYPE_NONE, 1, G_TYPE_POINTER);
341 treeview_class->select_cursor_parent = NULL;
343 #ifdef MODEST_TOOLKIT_HILDON2
344 gtk_rc_parse_string ("class \"ModestFolderView\" style \"fremantle-touchlist\"");
350 /* Retrieves the filter, sort and tny models of the folder view. If
351 any of these does not exist then it returns FALSE */
353 get_inner_models (ModestFolderView *self,
354 GtkTreeModel **filter_model,
355 GtkTreeModel **sort_model,
356 GtkTreeModel **tny_model)
358 GtkTreeModel *s_model, *f_model, *t_model;
360 f_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
361 if (!GTK_IS_TREE_MODEL_FILTER(f_model)) {
362 g_warning ("BUG: %s: not a valid filter model", __FUNCTION__);
366 s_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (f_model));
367 if (!GTK_IS_TREE_MODEL_SORT(s_model)) {
368 g_warning ("BUG: %s: not a valid sort model", __FUNCTION__);
372 t_model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (s_model));
376 *filter_model = f_model;
378 *sort_model = s_model;
380 *tny_model = t_model;
385 /* Simplify checks for NULLs: */
387 strings_are_equal (const gchar *a, const gchar *b)
393 return (strcmp (a, b) == 0);
400 on_model_foreach_set_name(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
402 GObject *instance = NULL;
404 gtk_tree_model_get (model, iter,
405 INSTANCE_COLUMN, &instance,
409 return FALSE; /* keep walking */
411 if (!TNY_IS_ACCOUNT (instance)) {
412 g_object_unref (instance);
413 return FALSE; /* keep walking */
416 /* Check if this is the looked-for account: */
417 TnyAccount *this_account = TNY_ACCOUNT (instance);
418 TnyAccount *account = TNY_ACCOUNT (data);
420 const gchar *this_account_id = tny_account_get_id(this_account);
421 const gchar *account_id = tny_account_get_id(account);
422 g_object_unref (instance);
425 /* printf ("DEBUG: %s: this_account_id=%s, account_id=%s\n", __FUNCTION__, this_account_id, account_id); */
426 if (strings_are_equal(this_account_id, account_id)) {
427 /* Tell the model that the data has changed, so that
428 * it calls the cell_data_func callbacks again: */
429 /* TODO: This does not seem to actually cause the new string to be shown: */
430 gtk_tree_model_row_changed (model, path, iter);
432 return TRUE; /* stop walking */
435 return FALSE; /* keep walking */
440 ModestFolderView *self;
441 gchar *previous_name;
442 } GetMmcAccountNameData;
445 on_get_mmc_account_name (TnyStoreAccount* account, gpointer user_data)
447 /* printf ("DEBU1G: %s: account name=%s\n", __FUNCTION__, tny_account_get_name (TNY_ACCOUNT(account))); */
449 GetMmcAccountNameData *data = (GetMmcAccountNameData*)user_data;
451 if (!strings_are_equal (
452 tny_account_get_name(TNY_ACCOUNT(account)),
453 data->previous_name)) {
455 /* Tell the model that the data has changed, so that
456 * it calls the cell_data_func callbacks again: */
457 ModestFolderView *self = data->self;
458 GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
460 gtk_tree_model_foreach(model, on_model_foreach_set_name, account);
463 g_free (data->previous_name);
464 g_slice_free (GetMmcAccountNameData, data);
468 convert_parent_folders_to_dots (gchar **item_name)
472 gchar *last_separator;
474 if (item_name == NULL)
477 for (c = *item_name; *c != '\0'; c++) {
478 if (g_str_has_prefix (c, MODEST_FOLDER_PATH_SEPARATOR)) {
483 last_separator = g_strrstr (*item_name, MODEST_FOLDER_PATH_SEPARATOR);
484 if (last_separator != NULL) {
485 last_separator = last_separator + strlen (MODEST_FOLDER_PATH_SEPARATOR);
492 buffer = g_string_new ("");
493 for (i = 0; i < n_parents; i++) {
494 buffer = g_string_append (buffer, MODEST_FOLDER_DOT);
496 buffer = g_string_append (buffer, last_separator);
498 *item_name = g_string_free (buffer, FALSE);
504 format_compact_style (gchar **item_name,
507 gboolean multiaccount,
508 gboolean *use_markup)
512 TnyFolderType folder_type;
514 if (!TNY_IS_FOLDER (instance))
517 folder = (TnyFolder *) instance;
519 folder_type = tny_folder_get_folder_type (folder);
520 is_special = (get_cmp_pos (folder_type, folder)!= 4);
522 if (!is_special || multiaccount) {
523 TnyAccount *account = tny_folder_get_account (folder);
524 const gchar *folder_name;
525 gboolean concat_folder_name = FALSE;
528 /* Should not happen */
532 /* convert parent folders to dots */
533 convert_parent_folders_to_dots (item_name);
535 folder_name = tny_folder_get_name (folder);
536 if (g_str_has_suffix (*item_name, folder_name)) {
537 gchar *offset = g_strrstr (*item_name, folder_name);
539 concat_folder_name = TRUE;
542 buffer = g_string_new ("");
544 buffer = g_string_append (buffer, *item_name);
545 if (concat_folder_name) {
546 if (bold) buffer = g_string_append (buffer, "<span weight='bold'>");
547 buffer = g_string_append (buffer, folder_name);
548 if (bold) buffer = g_string_append (buffer, "</span>");
551 g_object_unref (account);
553 *item_name = g_string_free (buffer, FALSE);
561 text_cell_data (GtkTreeViewColumn *column,
562 GtkCellRenderer *renderer,
563 GtkTreeModel *tree_model,
567 ModestFolderViewPrivate *priv;
568 GObject *rendobj = (GObject *) renderer;
570 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
571 GObject *instance = NULL;
572 gboolean use_markup = FALSE;
574 gtk_tree_model_get (tree_model, iter,
577 INSTANCE_COLUMN, &instance,
579 if (!fname || !instance)
582 ModestFolderView *self = MODEST_FOLDER_VIEW (data);
583 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
585 gchar *item_name = NULL;
586 gint item_weight = 400;
588 if (type != TNY_FOLDER_TYPE_ROOT) {
592 if (modest_tny_folder_is_local_folder (TNY_FOLDER (instance)) ||
593 modest_tny_folder_is_memory_card_folder (TNY_FOLDER (instance))) {
594 type = modest_tny_folder_get_local_or_mmc_folder_type (TNY_FOLDER (instance));
595 if (type != TNY_FOLDER_TYPE_UNKNOWN) {
597 fname = g_strdup (modest_local_folder_info_get_type_display_name (type));
600 /* Sometimes an special folder is reported by the server as
601 NORMAL, like some versions of Dovecot */
602 if (type == TNY_FOLDER_TYPE_NORMAL ||
603 type == TNY_FOLDER_TYPE_UNKNOWN) {
604 type = modest_tny_folder_guess_folder_type (TNY_FOLDER (instance));
608 /* note: we cannot reliably get the counts from the
609 * tree model, we need to use explicit calls on
610 * tny_folder for some reason. Select the number to
611 * show: the unread or unsent messages. in case of
612 * outbox/drafts, show all */
613 if ((type == TNY_FOLDER_TYPE_DRAFTS) ||
614 (type == TNY_FOLDER_TYPE_OUTBOX) ||
615 (type == TNY_FOLDER_TYPE_MERGE)) { /* _OUTBOX actually returns _MERGE... */
616 number = tny_folder_get_all_count (TNY_FOLDER(instance));
619 number = tny_folder_get_unread_count (TNY_FOLDER(instance));
623 if (priv->cell_style == MODEST_FOLDER_VIEW_CELL_STYLE_COMPACT) {
624 item_name = g_strdup (fname);
631 /* Use bold font style if there are unread or unset messages */
633 item_name = g_strdup_printf ("%s (%d)", fname, number);
636 item_name = g_strdup (fname);
641 } else if (TNY_IS_ACCOUNT (instance)) {
642 /* If it's a server account */
643 if (modest_tny_account_is_virtual_local_folders (TNY_ACCOUNT (instance))) {
644 item_name = g_strdup (priv->local_account_name);
646 } else if (modest_tny_account_is_memory_card_account (TNY_ACCOUNT (instance))) {
647 /* fname is only correct when the items are first
648 * added to the model, not when the account is
649 * changed later, so get the name from the account
651 item_name = g_strdup (tny_account_get_name (TNY_ACCOUNT (instance)));
654 item_name = g_strdup (fname);
660 item_name = g_strdup ("unknown");
662 if (priv->cell_style == MODEST_FOLDER_VIEW_CELL_STYLE_COMPACT) {
663 gboolean multiaccount;
665 multiaccount = (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ALL);
666 /* Convert item_name to markup */
667 format_compact_style (&item_name, instance,
669 multiaccount, &use_markup);
672 if (item_name && item_weight) {
673 /* Set the name in the treeview cell: */
675 g_object_set (rendobj, "markup", item_name, NULL);
677 g_object_set (rendobj, "text", item_name, "weight", item_weight, NULL);
679 /* Notify display name observers */
680 /* TODO: What listens for this signal, and how can it use only the new name? */
681 if (((GObject *) priv->cur_folder_store) == instance) {
682 g_signal_emit (G_OBJECT(self),
683 signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
690 /* If it is a Memory card account, make sure that we have the correct name.
691 * This function will be trigerred again when the name has been retrieved: */
692 if (TNY_IS_STORE_ACCOUNT (instance) &&
693 modest_tny_account_is_memory_card_account (TNY_ACCOUNT (instance))) {
695 /* Get the account name asynchronously: */
696 GetMmcAccountNameData *callback_data =
697 g_slice_new0(GetMmcAccountNameData);
698 callback_data->self = self;
700 const gchar *name = tny_account_get_name (TNY_ACCOUNT(instance));
702 callback_data->previous_name = g_strdup (name);
704 modest_tny_account_get_mmc_account_name (TNY_STORE_ACCOUNT (instance),
705 on_get_mmc_account_name, callback_data);
709 g_object_unref (G_OBJECT (instance));
715 messages_cell_data (GtkTreeViewColumn *column,
716 GtkCellRenderer *renderer,
717 GtkTreeModel *tree_model,
721 ModestFolderView *self;
722 ModestFolderViewPrivate *priv;
723 GObject *rendobj = (GObject *) renderer;
724 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
725 GObject *instance = NULL;
726 gchar *item_name = NULL;
728 gtk_tree_model_get (tree_model, iter,
730 INSTANCE_COLUMN, &instance,
735 self = MODEST_FOLDER_VIEW (data);
736 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
739 if (type != TNY_FOLDER_TYPE_ROOT) {
743 if (modest_tny_folder_is_local_folder (TNY_FOLDER (instance)) ||
744 modest_tny_folder_is_memory_card_folder (TNY_FOLDER (instance))) {
745 type = modest_tny_folder_get_local_or_mmc_folder_type (TNY_FOLDER (instance));
747 /* Sometimes an special folder is reported by the server as
748 NORMAL, like some versions of Dovecot */
749 if (type == TNY_FOLDER_TYPE_NORMAL ||
750 type == TNY_FOLDER_TYPE_UNKNOWN) {
751 type = modest_tny_folder_guess_folder_type (TNY_FOLDER (instance));
755 /* note: we cannot reliably get the counts from the tree model, we need
756 * to use explicit calls on tny_folder for some reason.
758 /* Select the number to show: the unread or unsent messages. in case of outbox/drafts, show all */
759 if ((type == TNY_FOLDER_TYPE_DRAFTS) ||
760 (type == TNY_FOLDER_TYPE_OUTBOX) ||
761 (type == TNY_FOLDER_TYPE_MERGE)) { /* _OUTBOX actually returns _MERGE... */
762 number = tny_folder_get_all_count (TNY_FOLDER(instance));
765 number = tny_folder_get_unread_count (TNY_FOLDER(instance));
769 if (priv->cell_style == MODEST_FOLDER_VIEW_CELL_STYLE_COMPACT) {
771 item_name = g_strdup_printf (drafts?_("mcen_ti_messages"):_("mcen_ti_new_messages"),
779 item_name = g_strdup ("");
782 /* Set the name in the treeview cell: */
783 g_object_set (rendobj,"text", item_name, NULL);
791 g_object_unref (G_OBJECT (instance));
797 GdkPixbuf *pixbuf_open;
798 GdkPixbuf *pixbuf_close;
802 static inline GdkPixbuf *
803 get_composite_pixbuf (const gchar *icon_name,
805 GdkPixbuf *base_pixbuf)
807 GdkPixbuf *emblem, *retval = NULL;
809 emblem = modest_platform_get_icon (icon_name, size);
811 retval = gdk_pixbuf_copy (base_pixbuf);
812 gdk_pixbuf_composite (emblem, retval, 0, 0,
813 MIN (gdk_pixbuf_get_width (emblem),
814 gdk_pixbuf_get_width (retval)),
815 MIN (gdk_pixbuf_get_height (emblem),
816 gdk_pixbuf_get_height (retval)),
817 0, 0, 1, 1, GDK_INTERP_NEAREST, 255);
818 g_object_unref (emblem);
823 static inline ThreePixbufs *
824 get_composite_icons (const gchar *icon_code,
826 GdkPixbuf **pixbuf_open,
827 GdkPixbuf **pixbuf_close)
829 ThreePixbufs *retval;
832 *pixbuf = gdk_pixbuf_copy (modest_platform_get_icon (icon_code, FOLDER_ICON_SIZE));
835 *pixbuf_open = get_composite_pixbuf ("qgn_list_gene_fldr_exp",
840 *pixbuf_close = get_composite_pixbuf ("qgn_list_gene_fldr_clp",
844 retval = g_slice_new0 (ThreePixbufs);
846 retval->pixbuf = g_object_ref (*pixbuf);
848 retval->pixbuf_open = g_object_ref (*pixbuf_open);
850 retval->pixbuf_close = g_object_ref (*pixbuf_close);
855 static inline ThreePixbufs*
856 get_folder_icons (TnyFolderType type, GObject *instance)
858 static GdkPixbuf *inbox_pixbuf = NULL, *outbox_pixbuf = NULL,
859 *junk_pixbuf = NULL, *sent_pixbuf = NULL,
860 *trash_pixbuf = NULL, *draft_pixbuf = NULL,
861 *normal_pixbuf = NULL, *anorm_pixbuf = NULL, *mmc_pixbuf = NULL,
862 *ammc_pixbuf = NULL, *avirt_pixbuf = NULL;
864 static GdkPixbuf *inbox_pixbuf_open = NULL, *outbox_pixbuf_open = NULL,
865 *junk_pixbuf_open = NULL, *sent_pixbuf_open = NULL,
866 *trash_pixbuf_open = NULL, *draft_pixbuf_open = NULL,
867 *normal_pixbuf_open = NULL, *anorm_pixbuf_open = NULL, *mmc_pixbuf_open = NULL,
868 *ammc_pixbuf_open = NULL, *avirt_pixbuf_open = NULL;
870 static GdkPixbuf *inbox_pixbuf_close = NULL, *outbox_pixbuf_close = NULL,
871 *junk_pixbuf_close = NULL, *sent_pixbuf_close = NULL,
872 *trash_pixbuf_close = NULL, *draft_pixbuf_close = NULL,
873 *normal_pixbuf_close = NULL, *anorm_pixbuf_close = NULL, *mmc_pixbuf_close = NULL,
874 *ammc_pixbuf_close = NULL, *avirt_pixbuf_close = NULL;
876 ThreePixbufs *retval = NULL;
878 /* Sometimes an special folder is reported by the server as
879 NORMAL, like some versions of Dovecot */
880 if (type == TNY_FOLDER_TYPE_NORMAL ||
881 type == TNY_FOLDER_TYPE_UNKNOWN) {
882 type = modest_tny_folder_guess_folder_type (TNY_FOLDER (instance));
885 /* It's not enough with check the folder type. We need to
886 ensure that we're not giving a special folder icon to a
887 normal folder with the same name than a special folder */
888 if (TNY_IS_FOLDER (instance) &&
889 get_cmp_pos (type, TNY_FOLDER (instance)) == 4)
890 type = TNY_FOLDER_TYPE_NORMAL;
892 /* Remote folders should not be treated as special folders */
893 if (TNY_IS_FOLDER_STORE (instance) &&
894 !TNY_IS_ACCOUNT (instance) &&
895 type != TNY_FOLDER_TYPE_INBOX &&
896 modest_tny_folder_store_is_remote (TNY_FOLDER_STORE (instance))) {
897 #ifdef MODEST_TOOLKIT_HILDON2
898 return get_composite_icons (MODEST_FOLDER_ICON_ACCOUNT,
901 &anorm_pixbuf_close);
903 return get_composite_icons (MODEST_FOLDER_ICON_NORMAL,
906 &normal_pixbuf_close);
912 case TNY_FOLDER_TYPE_INVALID:
913 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
916 case TNY_FOLDER_TYPE_ROOT:
917 if (TNY_IS_ACCOUNT (instance)) {
919 if (modest_tny_account_is_virtual_local_folders (TNY_ACCOUNT (instance))) {
920 retval = get_composite_icons (MODEST_FOLDER_ICON_LOCAL_FOLDERS,
923 &avirt_pixbuf_close);
925 const gchar *account_id = tny_account_get_id (TNY_ACCOUNT (instance));
927 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
928 retval = get_composite_icons (MODEST_FOLDER_ICON_MMC,
933 retval = get_composite_icons (MODEST_FOLDER_ICON_ACCOUNT,
936 &anorm_pixbuf_close);
941 case TNY_FOLDER_TYPE_INBOX:
942 retval = get_composite_icons (MODEST_FOLDER_ICON_INBOX,
945 &inbox_pixbuf_close);
947 case TNY_FOLDER_TYPE_OUTBOX:
948 retval = get_composite_icons (MODEST_FOLDER_ICON_OUTBOX,
951 &outbox_pixbuf_close);
953 case TNY_FOLDER_TYPE_JUNK:
954 retval = get_composite_icons (MODEST_FOLDER_ICON_JUNK,
959 case TNY_FOLDER_TYPE_SENT:
960 retval = get_composite_icons (MODEST_FOLDER_ICON_SENT,
965 case TNY_FOLDER_TYPE_TRASH:
966 retval = get_composite_icons (MODEST_FOLDER_ICON_TRASH,
969 &trash_pixbuf_close);
971 case TNY_FOLDER_TYPE_DRAFTS:
972 retval = get_composite_icons (MODEST_FOLDER_ICON_DRAFTS,
975 &draft_pixbuf_close);
977 case TNY_FOLDER_TYPE_ARCHIVE:
978 retval = get_composite_icons (MODEST_FOLDER_ICON_MMC_FOLDER,
983 case TNY_FOLDER_TYPE_NORMAL:
985 /* Memory card folders could have an special icon */
986 if (modest_tny_folder_is_memory_card_folder (TNY_FOLDER (instance))) {
987 retval = get_composite_icons (MODEST_FOLDER_ICON_MMC_FOLDER,
992 retval = get_composite_icons (MODEST_FOLDER_ICON_NORMAL,
995 &normal_pixbuf_close);
1004 free_pixbufs (ThreePixbufs *pixbufs)
1006 if (pixbufs->pixbuf)
1007 g_object_unref (pixbufs->pixbuf);
1008 if (pixbufs->pixbuf_open)
1009 g_object_unref (pixbufs->pixbuf_open);
1010 if (pixbufs->pixbuf_close)
1011 g_object_unref (pixbufs->pixbuf_close);
1012 g_slice_free (ThreePixbufs, pixbufs);
1016 icon_cell_data (GtkTreeViewColumn *column,
1017 GtkCellRenderer *renderer,
1018 GtkTreeModel *tree_model,
1022 GObject *rendobj = NULL, *instance = NULL;
1023 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1024 gboolean has_children;
1025 ThreePixbufs *pixbufs;
1027 rendobj = (GObject *) renderer;
1029 gtk_tree_model_get (tree_model, iter,
1031 INSTANCE_COLUMN, &instance,
1037 has_children = gtk_tree_model_iter_has_child (tree_model, iter);
1038 pixbufs = get_folder_icons (type, instance);
1039 g_object_unref (instance);
1042 g_object_set (rendobj, "pixbuf", pixbufs->pixbuf, NULL);
1045 g_object_set (rendobj, "pixbuf-expander-open", pixbufs->pixbuf_open, NULL);
1046 g_object_set (rendobj, "pixbuf-expander-closed", pixbufs->pixbuf_close, NULL);
1049 free_pixbufs (pixbufs);
1053 add_columns (GtkWidget *treeview)
1055 GtkTreeViewColumn *column;
1056 GtkCellRenderer *renderer;
1057 GtkTreeSelection *sel;
1058 ModestFolderViewPrivate *priv;
1060 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(treeview);
1063 column = gtk_tree_view_column_new ();
1065 /* Set icon and text render function */
1066 renderer = gtk_cell_renderer_pixbuf_new();
1067 #ifdef MODEST_TOOLKIT_HILDON2
1068 g_object_set (renderer,
1069 "xpad", MODEST_MARGIN_DEFAULT,
1070 "ypad", MODEST_MARGIN_DEFAULT,
1073 gtk_tree_view_column_pack_start (column, renderer, FALSE);
1074 gtk_tree_view_column_set_cell_data_func(column, renderer,
1075 icon_cell_data, treeview, NULL);
1077 renderer = gtk_cell_renderer_text_new();
1078 g_object_set (renderer,
1079 #ifdef MODEST_TOOLKIT_HILDON2
1080 "ellipsize", PANGO_ELLIPSIZE_MIDDLE,
1081 "ypad", MODEST_MARGIN_DEFAULT,
1083 "ellipsize", PANGO_ELLIPSIZE_END,
1085 "ellipsize-set", TRUE, NULL);
1086 gtk_tree_view_column_pack_start (column, renderer, TRUE);
1087 gtk_tree_view_column_set_cell_data_func(column, renderer,
1088 text_cell_data, treeview, NULL);
1090 priv->messages_renderer = gtk_cell_renderer_text_new ();
1091 g_object_set (priv->messages_renderer,
1092 #ifdef MODEST_TOOLKIT_HILDON2
1094 "ypad", MODEST_MARGIN_DEFAULT,
1095 "xpad", MODEST_MARGIN_DOUBLE,
1097 "scale", PANGO_SCALE_X_SMALL,
1100 "alignment", PANGO_ALIGN_RIGHT,
1104 gtk_tree_view_column_pack_start (column, priv->messages_renderer, FALSE);
1105 gtk_tree_view_column_set_cell_data_func(column, priv->messages_renderer,
1106 messages_cell_data, treeview, NULL);
1108 /* Set selection mode */
1109 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(treeview));
1110 gtk_tree_selection_set_mode (sel, GTK_SELECTION_SINGLE);
1112 /* Set treeview appearance */
1113 gtk_tree_view_column_set_spacing (column, 2);
1114 gtk_tree_view_column_set_resizable (column, TRUE);
1115 gtk_tree_view_column_set_fixed_width (column, TRUE);
1116 gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(treeview), FALSE);
1117 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(treeview), FALSE);
1120 gtk_tree_view_append_column (GTK_TREE_VIEW(treeview),column);
1124 modest_folder_view_init (ModestFolderView *obj)
1126 ModestFolderViewPrivate *priv;
1129 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
1131 priv->timer_expander = 0;
1132 priv->account_store = NULL;
1134 priv->style = MODEST_FOLDER_VIEW_STYLE_SHOW_ALL;
1135 priv->cur_folder_store = NULL;
1136 priv->visible_account_id = NULL;
1137 priv->folder_to_select = NULL;
1138 priv->outbox_deleted_handler = 0;
1139 priv->reexpand = TRUE;
1141 /* Initialize the local account name */
1142 conf = modest_runtime_get_conf();
1143 priv->local_account_name = modest_conf_get_string (conf, MODEST_CONF_DEVICE_NAME, NULL);
1145 /* Init email clipboard */
1146 priv->clipboard = modest_runtime_get_email_clipboard ();
1147 priv->hidding_ids = NULL;
1148 priv->n_selected = 0;
1149 priv->filter = MODEST_FOLDER_VIEW_FILTER_NONE;
1150 priv->reselect = FALSE;
1151 priv->show_non_move = TRUE;
1152 priv->list_to_move = NULL;
1154 /* Build treeview */
1155 add_columns (GTK_WIDGET (obj));
1157 /* Setup drag and drop */
1158 setup_drag_and_drop (GTK_TREE_VIEW(obj));
1160 /* Connect signals */
1161 g_signal_connect (G_OBJECT (obj),
1163 G_CALLBACK (on_key_pressed), NULL);
1165 priv->display_name_changed_signal =
1166 g_signal_connect (modest_runtime_get_account_mgr (),
1167 "display_name_changed",
1168 G_CALLBACK (on_display_name_changed),
1172 * Track changes in the local account name (in the device it
1173 * will be the device name)
1175 priv->conf_key_signal = g_signal_connect (G_OBJECT(conf),
1177 G_CALLBACK(on_configuration_key_changed),
1181 g_signal_connect (G_OBJECT (obj), "notify::style", G_CALLBACK (on_notify_style), (gpointer) obj);
1187 tny_account_store_view_init (gpointer g, gpointer iface_data)
1189 TnyAccountStoreViewIface *klass = (TnyAccountStoreViewIface *)g;
1191 klass->set_account_store = modest_folder_view_set_account_store;
1195 modest_folder_view_finalize (GObject *obj)
1197 ModestFolderViewPrivate *priv;
1198 GtkTreeSelection *sel;
1199 TnyAccount *local_account;
1201 g_return_if_fail (obj);
1203 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
1205 if (priv->timer_expander != 0) {
1206 g_source_remove (priv->timer_expander);
1207 priv->timer_expander = 0;
1210 local_account = (TnyAccount *)
1211 modest_tny_account_store_get_local_folders_account (modest_runtime_get_account_store ());
1212 if (local_account) {
1213 if (g_signal_handler_is_connected (local_account,
1214 priv->outbox_deleted_handler))
1215 g_signal_handler_disconnect (local_account,
1216 priv->outbox_deleted_handler);
1217 g_object_unref (local_account);
1220 if (priv->account_store) {
1221 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
1222 priv->account_inserted_signal);
1223 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
1224 priv->account_removed_signal);
1225 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
1226 priv->account_changed_signal);
1227 g_object_unref (G_OBJECT(priv->account_store));
1228 priv->account_store = NULL;
1231 if (g_signal_handler_is_connected (modest_runtime_get_account_mgr (),
1232 priv->display_name_changed_signal)) {
1233 g_signal_handler_disconnect (modest_runtime_get_account_mgr (),
1234 priv->display_name_changed_signal);
1235 priv->display_name_changed_signal = 0;
1239 g_object_unref (G_OBJECT (priv->query));
1243 if (priv->folder_to_select) {
1244 g_object_unref (G_OBJECT(priv->folder_to_select));
1245 priv->folder_to_select = NULL;
1248 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(obj));
1250 g_signal_handler_disconnect (G_OBJECT(sel), priv->changed_signal);
1252 g_free (priv->local_account_name);
1253 g_free (priv->visible_account_id);
1255 if (priv->conf_key_signal) {
1256 g_signal_handler_disconnect (modest_runtime_get_conf (),
1257 priv->conf_key_signal);
1258 priv->conf_key_signal = 0;
1261 if (priv->cur_folder_store) {
1262 g_object_unref (priv->cur_folder_store);
1263 priv->cur_folder_store = NULL;
1266 if (priv->list_to_move) {
1267 g_object_unref (priv->list_to_move);
1268 priv->list_to_move = NULL;
1271 /* Clear hidding array created by cut operation */
1272 _clear_hidding_filter (MODEST_FOLDER_VIEW (obj));
1274 G_OBJECT_CLASS(parent_class)->finalize (obj);
1279 modest_folder_view_set_account_store (TnyAccountStoreView *self, TnyAccountStore *account_store)
1281 ModestFolderViewPrivate *priv;
1284 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
1285 g_return_if_fail (TNY_IS_ACCOUNT_STORE (account_store));
1287 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1288 device = tny_account_store_get_device (account_store);
1290 if (G_UNLIKELY (priv->account_store)) {
1292 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
1293 priv->account_inserted_signal))
1294 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
1295 priv->account_inserted_signal);
1296 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
1297 priv->account_removed_signal))
1298 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
1299 priv->account_removed_signal);
1300 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
1301 priv->account_changed_signal))
1302 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
1303 priv->account_changed_signal);
1304 g_object_unref (G_OBJECT (priv->account_store));
1307 priv->account_store = g_object_ref (G_OBJECT (account_store));
1309 priv->account_removed_signal =
1310 g_signal_connect (G_OBJECT(account_store), "account_removed",
1311 G_CALLBACK (on_account_removed), self);
1313 priv->account_inserted_signal =
1314 g_signal_connect (G_OBJECT(account_store), "account_inserted",
1315 G_CALLBACK (on_account_inserted), self);
1317 priv->account_changed_signal =
1318 g_signal_connect (G_OBJECT(account_store), "account_changed",
1319 G_CALLBACK (on_account_changed), self);
1321 modest_folder_view_update_model (MODEST_FOLDER_VIEW (self), account_store);
1322 priv->reselect = FALSE;
1323 modest_folder_view_select_first_inbox_or_local (MODEST_FOLDER_VIEW (self));
1325 g_object_unref (G_OBJECT (device));
1329 on_outbox_deleted_cb (ModestTnyLocalFoldersAccount *local_account,
1332 ModestFolderView *self;
1333 GtkTreeModel *model, *filter_model;
1336 self = MODEST_FOLDER_VIEW (user_data);
1338 if (!get_inner_models (self, &filter_model, NULL, &model))
1341 /* Remove outbox from model */
1342 outbox = modest_tny_local_folders_account_get_merged_outbox (local_account);
1343 tny_list_remove (TNY_LIST (model), G_OBJECT (outbox));
1344 g_object_unref (outbox);
1347 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1351 on_account_inserted (TnyAccountStore *account_store,
1352 TnyAccount *account,
1355 ModestFolderViewPrivate *priv;
1356 GtkTreeModel *model, *filter_model;
1358 /* Ignore transport account insertions, we're not showing them
1359 in the folder view */
1360 if (TNY_IS_TRANSPORT_ACCOUNT (account))
1363 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (user_data);
1366 /* If we're adding a new account, and there is no previous
1367 one, we need to select the visible server account */
1368 if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE &&
1369 !priv->visible_account_id)
1370 modest_widget_memory_restore (modest_runtime_get_conf(),
1371 G_OBJECT (user_data),
1372 MODEST_CONF_FOLDER_VIEW_KEY);
1376 if (!get_inner_models (MODEST_FOLDER_VIEW (user_data),
1377 &filter_model, NULL, &model))
1380 /* Insert the account in the model */
1381 tny_list_append (TNY_LIST (model), G_OBJECT (account));
1383 /* When the model is a list store (plain representation) the
1384 outbox is not a child of any account so we have to manually
1385 delete it because removing the local folders account won't
1386 delete it (because tny_folder_get_account() is not defined
1387 for a merge folder */
1388 if (TNY_IS_GTK_FOLDER_LIST_STORE (model) &&
1389 MODEST_IS_TNY_LOCAL_FOLDERS_ACCOUNT (account)) {
1391 priv->outbox_deleted_handler =
1392 g_signal_connect (account,
1394 G_CALLBACK (on_outbox_deleted_cb),
1398 /* Refilter the model */
1399 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1404 same_account_selected (ModestFolderView *self,
1405 TnyAccount *account)
1407 ModestFolderViewPrivate *priv;
1408 gboolean same_account = FALSE;
1410 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1412 if (priv->cur_folder_store) {
1413 TnyAccount *selected_folder_account = NULL;
1415 if (TNY_IS_FOLDER (priv->cur_folder_store)) {
1416 selected_folder_account =
1417 modest_tny_folder_get_account (TNY_FOLDER (priv->cur_folder_store));
1419 selected_folder_account =
1420 TNY_ACCOUNT (g_object_ref (priv->cur_folder_store));
1423 if (selected_folder_account == account)
1424 same_account = TRUE;
1426 g_object_unref (selected_folder_account);
1428 return same_account;
1433 * Selects the first inbox or the local account in an idle
1436 on_idle_select_first_inbox_or_local (gpointer user_data)
1438 ModestFolderView *self = MODEST_FOLDER_VIEW (user_data);
1440 gdk_threads_enter ();
1441 modest_folder_view_select_first_inbox_or_local (self);
1442 gdk_threads_leave ();
1448 on_account_changed (TnyAccountStore *account_store,
1449 TnyAccount *tny_account,
1452 ModestFolderView *self;
1453 ModestFolderViewPrivate *priv;
1454 GtkTreeModel *model, *filter_model;
1455 GtkTreeSelection *sel;
1456 gboolean same_account;
1458 /* Ignore transport account insertions, we're not showing them
1459 in the folder view */
1460 if (TNY_IS_TRANSPORT_ACCOUNT (tny_account))
1463 self = MODEST_FOLDER_VIEW (user_data);
1464 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (user_data);
1466 /* Get the inner model */
1467 if (!get_inner_models (MODEST_FOLDER_VIEW (user_data),
1468 &filter_model, NULL, &model))
1471 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (user_data));
1473 /* Invalidate the cur_folder_store only if the selected folder
1474 belongs to the account that is being removed */
1475 same_account = same_account_selected (self, tny_account);
1477 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1478 gtk_tree_selection_unselect_all (sel);
1481 /* Remove the account from the model */
1482 tny_list_remove (TNY_LIST (model), G_OBJECT (tny_account));
1484 /* Insert the account in the model */
1485 tny_list_append (TNY_LIST (model), G_OBJECT (tny_account));
1487 /* Refilter the model */
1488 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1490 /* Select the first INBOX if the currently selected folder
1491 belongs to the account that is being deleted */
1492 if (same_account && !MODEST_IS_TNY_LOCAL_FOLDERS_ACCOUNT (tny_account))
1493 g_idle_add (on_idle_select_first_inbox_or_local, self);
1497 on_account_removed (TnyAccountStore *account_store,
1498 TnyAccount *account,
1501 ModestFolderView *self = NULL;
1502 ModestFolderViewPrivate *priv;
1503 GtkTreeModel *model, *filter_model;
1504 GtkTreeSelection *sel = NULL;
1505 gboolean same_account = FALSE;
1507 /* Ignore transport account removals, we're not showing them
1508 in the folder view */
1509 if (TNY_IS_TRANSPORT_ACCOUNT (account))
1512 if (!MODEST_IS_FOLDER_VIEW(user_data)) {
1513 g_warning ("BUG: %s: not a valid folder view", __FUNCTION__);
1517 self = MODEST_FOLDER_VIEW (user_data);
1518 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1520 /* Invalidate the cur_folder_store only if the selected folder
1521 belongs to the account that is being removed */
1522 same_account = same_account_selected (self, account);
1524 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1525 gtk_tree_selection_unselect_all (sel);
1528 /* Invalidate row to select only if the folder to select
1529 belongs to the account that is being removed*/
1530 if (priv->folder_to_select) {
1531 TnyAccount *folder_to_select_account = NULL;
1533 folder_to_select_account = tny_folder_get_account (priv->folder_to_select);
1534 if (folder_to_select_account == account) {
1535 modest_folder_view_disable_next_folder_selection (self);
1536 g_object_unref (priv->folder_to_select);
1537 priv->folder_to_select = NULL;
1539 g_object_unref (folder_to_select_account);
1542 if (!get_inner_models (MODEST_FOLDER_VIEW (user_data),
1543 &filter_model, NULL, &model))
1546 /* Disconnect the signal handler */
1547 if (TNY_IS_GTK_FOLDER_LIST_STORE (model) &&
1548 MODEST_IS_TNY_LOCAL_FOLDERS_ACCOUNT (account)) {
1549 if (g_signal_handler_is_connected (account,
1550 priv->outbox_deleted_handler))
1551 g_signal_handler_disconnect (account,
1552 priv->outbox_deleted_handler);
1555 /* Remove the account from the model */
1556 tny_list_remove (TNY_LIST (model), G_OBJECT (account));
1558 /* If the removed account is the currently viewed one then
1559 clear the configuration value. The new visible account will be the default account */
1560 if (priv->visible_account_id &&
1561 !strcmp (priv->visible_account_id, tny_account_get_id (account))) {
1563 /* Clear the current visible account_id */
1564 modest_folder_view_set_account_id_of_visible_server_account (self, NULL);
1566 /* Call the restore method, this will set the new visible account */
1567 modest_widget_memory_restore (modest_runtime_get_conf(), G_OBJECT(self),
1568 MODEST_CONF_FOLDER_VIEW_KEY);
1571 /* Refilter the model */
1572 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1574 /* Select the first INBOX if the currently selected folder
1575 belongs to the account that is being deleted */
1577 g_idle_add (on_idle_select_first_inbox_or_local, self);
1581 modest_folder_view_set_title (ModestFolderView *self, const gchar *title)
1583 GtkTreeViewColumn *col;
1585 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
1587 col = gtk_tree_view_get_column (GTK_TREE_VIEW(self), 0);
1589 g_printerr ("modest: failed get column for title\n");
1593 gtk_tree_view_column_set_title (col, title);
1594 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self),
1599 modest_folder_view_on_map (ModestFolderView *self,
1600 GdkEventExpose *event,
1603 ModestFolderViewPrivate *priv;
1605 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1607 /* This won't happen often */
1608 if (G_UNLIKELY (priv->reselect)) {
1609 /* Select the first inbox or the local account if not found */
1611 /* TODO: this could cause a lock at startup, so we
1612 comment it for the moment. We know that this will
1613 be a bug, because the INBOX is not selected, but we
1614 need to rewrite some parts of Modest to avoid the
1615 deathlock situation */
1616 /* TODO: check if this is still the case */
1617 priv->reselect = FALSE;
1618 modest_folder_view_select_first_inbox_or_local (self);
1619 /* Notify the display name observers */
1620 g_signal_emit (G_OBJECT(self),
1621 signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
1625 if (priv->reexpand) {
1626 expand_root_items (self);
1627 priv->reexpand = FALSE;
1634 modest_folder_view_new (TnyFolderStoreQuery *query)
1637 ModestFolderViewPrivate *priv;
1638 GtkTreeSelection *sel;
1640 self = G_OBJECT (g_object_new (MODEST_TYPE_FOLDER_VIEW,
1641 #ifdef MODEST_TOOLKIT_HILDON2
1642 "hildon-ui-mode", HILDON_UI_MODE_NORMAL,
1645 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1648 priv->query = g_object_ref (query);
1650 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1651 priv->changed_signal = g_signal_connect (sel, "changed",
1652 G_CALLBACK (on_selection_changed), self);
1654 g_signal_connect (self, "row-activated", G_CALLBACK (on_row_activated), self);
1656 g_signal_connect (self, "expose-event", G_CALLBACK (modest_folder_view_on_map), NULL);
1658 return GTK_WIDGET(self);
1661 /* this feels dirty; any other way to expand all the root items? */
1663 expand_root_items (ModestFolderView *self)
1666 GtkTreeModel *model;
1669 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1670 path = gtk_tree_path_new_first ();
1672 /* all folders should have child items, so.. */
1674 gtk_tree_view_expand_row (GTK_TREE_VIEW(self), path, FALSE);
1675 gtk_tree_path_next (path);
1676 } while (gtk_tree_model_get_iter (model, &iter, path));
1678 gtk_tree_path_free (path);
1682 is_parent_of (TnyFolder *a, TnyFolder *b)
1685 gboolean retval = FALSE;
1687 a_id = tny_folder_get_id (a);
1689 gchar *string_to_match;
1692 string_to_match = g_strconcat (a_id, "/", NULL);
1693 b_id = tny_folder_get_id (b);
1694 retval = g_str_has_prefix (b_id, string_to_match);
1695 g_free (string_to_match);
1701 typedef struct _ForeachFolderInfo {
1704 } ForeachFolderInfo;
1707 foreach_folder_with_id (GtkTreeModel *model,
1712 ForeachFolderInfo *info;
1715 info = (ForeachFolderInfo *) data;
1716 gtk_tree_model_get (model, iter,
1717 INSTANCE_COLUMN, &instance,
1720 if (TNY_IS_FOLDER (instance)) {
1723 id = tny_folder_get_id (TNY_FOLDER (instance));
1725 collate = g_utf8_collate_key (id, -1);
1726 info->found = !strcmp (info->needle, collate);
1737 has_folder_with_id (ModestFolderView *self, const gchar *id)
1739 GtkTreeModel *model;
1740 ForeachFolderInfo info = {NULL, FALSE};
1742 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1743 info.needle = g_utf8_collate_key (id, -1);
1745 gtk_tree_model_foreach (model, foreach_folder_with_id, &info);
1746 g_free (info.needle);
1752 has_child_with_name_of (ModestFolderView *self, TnyFolder *a, TnyFolder *b)
1755 gboolean retval = FALSE;
1757 a_id = tny_folder_get_id (a);
1760 b_id = tny_folder_get_id (b);
1763 const gchar *last_bar;
1764 gchar *string_to_match;
1765 last_bar = g_strrstr (b_id, "/");
1770 string_to_match = g_strconcat (a_id, "/", last_bar, NULL);
1771 retval = has_folder_with_id (self, string_to_match);
1772 g_free (string_to_match);
1780 check_move_to_this_folder_valid (ModestFolderView *self, TnyFolder *folder)
1782 ModestFolderViewPrivate *priv;
1783 TnyIterator *iterator;
1784 gboolean retval = TRUE;
1786 g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (self), FALSE);
1787 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1789 for (iterator = tny_list_create_iterator (priv->list_to_move);
1790 retval && !tny_iterator_is_done (iterator);
1791 tny_iterator_next (iterator)) {
1793 instance = tny_iterator_get_current (iterator);
1794 if (instance == (GObject *) folder) {
1796 } else if (TNY_IS_FOLDER (instance)) {
1797 retval = !is_parent_of (TNY_FOLDER (instance), folder);
1799 retval = !has_child_with_name_of (self, folder, TNY_FOLDER (instance));
1802 g_object_unref (instance);
1804 g_object_unref (iterator);
1811 * We use this function to implement the
1812 * MODEST_FOLDER_VIEW_STYLE_SHOW_ONE style. We only show the default
1813 * account in this case, and the local folders.
1816 filter_row (GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
1818 ModestFolderViewPrivate *priv;
1819 gboolean retval = TRUE;
1820 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1821 GObject *instance = NULL;
1822 const gchar *id = NULL;
1824 gboolean found = FALSE;
1825 gboolean cleared = FALSE;
1826 ModestTnyFolderRules rules = 0;
1828 g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (data), FALSE);
1829 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (data);
1831 gtk_tree_model_get (model, iter,
1833 INSTANCE_COLUMN, &instance,
1836 /* Do not show if there is no instance, this could indeed
1837 happen when the model is being modified while it's being
1838 drawn. This could occur for example when moving folders
1843 if (TNY_IS_ACCOUNT (instance)) {
1844 TnyAccount *acc = TNY_ACCOUNT (instance);
1845 const gchar *account_id = tny_account_get_id (acc);
1847 /* If it isn't a special folder,
1848 * don't show it unless it is the visible account: */
1849 if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE &&
1850 !modest_tny_account_is_virtual_local_folders (acc) &&
1851 strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
1853 /* Show only the visible account id */
1854 if (priv->visible_account_id) {
1855 if (strcmp (account_id, priv->visible_account_id))
1862 /* Never show these to the user. They are merged into one folder
1863 * in the local-folders account instead: */
1864 if (retval && MODEST_IS_TNY_OUTBOX_ACCOUNT (acc))
1867 if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE) {
1868 /* Only show special folders for current account if needed */
1869 if (TNY_IS_FOLDER (instance) && !TNY_IS_MERGE_FOLDER (instance)) {
1870 TnyAccount *account;
1872 account = tny_folder_get_account (TNY_FOLDER (instance));
1874 if (TNY_IS_ACCOUNT (account)) {
1875 const gchar *account_id = tny_account_get_id (account);
1877 if (!modest_tny_account_is_virtual_local_folders (account) &&
1878 strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
1879 /* Show only the visible account id */
1880 if (priv->visible_account_id) {
1881 if (strcmp (account_id, priv->visible_account_id))
1885 g_object_unref (account);
1892 /* Check hiding (if necessary) */
1893 cleared = modest_email_clipboard_cleared (priv->clipboard);
1894 if ((retval) && (!cleared) && (TNY_IS_FOLDER (instance))) {
1895 id = tny_folder_get_id (TNY_FOLDER(instance));
1896 if (priv->hidding_ids != NULL)
1897 for (i=0; i < priv->n_selected && !found; i++)
1898 if (priv->hidding_ids[i] != NULL && id != NULL)
1899 found = (!strcmp (priv->hidding_ids[i], id));
1904 /* If this is a move to dialog, hide Sent, Outbox and Drafts
1905 folder as no message can be move there according to UI specs */
1906 if (!priv->show_non_move) {
1907 if (priv->list_to_move &&
1908 tny_list_get_length (priv->list_to_move) > 0 &&
1909 TNY_IS_FOLDER (instance)) {
1910 retval = check_move_to_this_folder_valid (MODEST_FOLDER_VIEW (data), TNY_FOLDER (instance));
1912 if (retval && TNY_IS_FOLDER (instance) &&
1913 modest_tny_folder_is_local_folder (TNY_FOLDER (instance))) {
1915 case TNY_FOLDER_TYPE_OUTBOX:
1916 case TNY_FOLDER_TYPE_SENT:
1917 case TNY_FOLDER_TYPE_DRAFTS:
1920 case TNY_FOLDER_TYPE_UNKNOWN:
1921 case TNY_FOLDER_TYPE_NORMAL:
1922 type = modest_tny_folder_guess_folder_type(TNY_FOLDER(instance));
1923 if (type == TNY_FOLDER_TYPE_INVALID)
1924 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1926 if (type == TNY_FOLDER_TYPE_OUTBOX ||
1927 type == TNY_FOLDER_TYPE_SENT
1928 || type == TNY_FOLDER_TYPE_DRAFTS)
1937 /* apply special filters */
1938 if (retval && (priv->filter & MODEST_FOLDER_VIEW_FILTER_HIDE_ACCOUNTS)) {
1939 if (TNY_IS_ACCOUNT (instance))
1943 if (retval && (priv->filter & MODEST_FOLDER_VIEW_FILTER_CAN_HAVE_FOLDERS)) {
1944 if (TNY_IS_FOLDER (instance)) {
1945 /* Check folder rules */
1946 ModestTnyFolderRules rules;
1948 rules = modest_tny_folder_get_rules (TNY_FOLDER (instance));
1949 retval = !(rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE);
1950 } else if (TNY_IS_ACCOUNT (instance)) {
1951 if (modest_tny_folder_store_is_remote (TNY_FOLDER_STORE (instance))) {
1959 if (retval && (priv->filter & MODEST_FOLDER_VIEW_FILTER_HIDE_MANDATORY_FOLDERS)) {
1960 if (TNY_IS_FOLDER (instance)) {
1961 TnyFolderType guess_type;
1963 if (TNY_FOLDER_TYPE_NORMAL) {
1964 guess_type = modest_tny_folder_guess_folder_type (TNY_FOLDER (instance));
1970 case TNY_FOLDER_TYPE_OUTBOX:
1971 case TNY_FOLDER_TYPE_SENT:
1972 case TNY_FOLDER_TYPE_DRAFTS:
1973 case TNY_FOLDER_TYPE_ARCHIVE:
1974 case TNY_FOLDER_TYPE_INBOX:
1977 case TNY_FOLDER_TYPE_UNKNOWN:
1978 case TNY_FOLDER_TYPE_NORMAL:
1984 } else if (TNY_IS_ACCOUNT (instance)) {
1989 if (retval && TNY_IS_FOLDER (instance)) {
1990 rules = modest_tny_folder_get_rules (TNY_FOLDER (instance));
1993 if (retval && (priv->filter & MODEST_FOLDER_VIEW_FILTER_DELETABLE)) {
1994 if (TNY_IS_FOLDER (instance)) {
1995 retval = !(rules & MODEST_FOLDER_RULES_FOLDER_NON_DELETABLE);
1996 } else if (TNY_IS_ACCOUNT (instance)) {
2001 if (retval && (priv->filter & MODEST_FOLDER_VIEW_FILTER_RENAMEABLE)) {
2002 if (TNY_IS_FOLDER (instance)) {
2003 retval = !(rules & MODEST_FOLDER_RULES_FOLDER_NON_RENAMEABLE);
2004 } else if (TNY_IS_ACCOUNT (instance)) {
2009 if (retval && (priv->filter & MODEST_FOLDER_VIEW_FILTER_MOVEABLE)) {
2010 if (TNY_IS_FOLDER (instance)) {
2011 retval = !(rules & MODEST_FOLDER_RULES_FOLDER_NON_MOVEABLE);
2012 } else if (TNY_IS_ACCOUNT (instance)) {
2018 g_object_unref (instance);
2025 modest_folder_view_update_model (ModestFolderView *self,
2026 TnyAccountStore *account_store)
2028 ModestFolderViewPrivate *priv;
2029 GtkTreeModel *model /* , *old_model */;
2030 GtkTreeModel *filter_model = NULL, *sortable = NULL;
2032 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW (self), FALSE);
2033 g_return_val_if_fail (account_store && MODEST_IS_TNY_ACCOUNT_STORE(account_store),
2036 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2038 /* Notify that there is no folder selected */
2039 g_signal_emit (G_OBJECT(self),
2040 signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
2042 if (priv->cur_folder_store) {
2043 g_object_unref (priv->cur_folder_store);
2044 priv->cur_folder_store = NULL;
2047 /* FIXME: the local accounts are not shown when the query
2048 selects only the subscribed folders */
2049 #ifdef MODEST_TOOLKIT_HILDON2
2050 model = tny_gtk_folder_list_store_new_with_flags (NULL,
2051 TNY_GTK_FOLDER_LIST_STORE_FLAG_SHOW_PATH);
2052 tny_gtk_folder_list_store_set_path_separator (TNY_GTK_FOLDER_LIST_STORE (model),
2053 MODEST_FOLDER_PATH_SEPARATOR);
2055 model = tny_gtk_folder_store_tree_model_new (NULL);
2058 /* When the model is a list store (plain representation) the
2059 outbox is not a child of any account so we have to manually
2060 delete it because removing the local folders account won't
2061 delete it (because tny_folder_get_account() is not defined
2062 for a merge folder */
2063 if (TNY_IS_GTK_FOLDER_LIST_STORE (model)) {
2064 TnyAccount *account;
2065 ModestTnyAccountStore *acc_store;
2067 acc_store = modest_runtime_get_account_store ();
2068 account = modest_tny_account_store_get_local_folders_account (acc_store);
2070 if (g_signal_handler_is_connected (account,
2071 priv->outbox_deleted_handler))
2072 g_signal_handler_disconnect (account,
2073 priv->outbox_deleted_handler);
2075 priv->outbox_deleted_handler =
2076 g_signal_connect (account,
2078 G_CALLBACK (on_outbox_deleted_cb),
2080 g_object_unref (account);
2083 /* Get the accounts: */
2084 tny_account_store_get_accounts (TNY_ACCOUNT_STORE(account_store),
2086 TNY_ACCOUNT_STORE_STORE_ACCOUNTS);
2088 sortable = gtk_tree_model_sort_new_with_model (model);
2089 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
2091 GTK_SORT_ASCENDING);
2092 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
2094 cmp_rows, NULL, NULL);
2096 /* Create filter model */
2097 filter_model = gtk_tree_model_filter_new (sortable, NULL);
2098 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
2104 gtk_tree_view_set_model (GTK_TREE_VIEW(self), filter_model);
2105 #ifndef MODEST_TOOLKIT_HILDON2
2106 g_signal_connect (G_OBJECT(filter_model), "row-inserted",
2107 (GCallback) on_row_inserted_maybe_select_folder, self);
2110 g_object_unref (model);
2111 g_object_unref (filter_model);
2112 g_object_unref (sortable);
2114 /* Force a reselection of the INBOX next time the widget is shown */
2115 priv->reselect = TRUE;
2122 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
2124 GtkTreeModel *model = NULL;
2125 TnyFolderStore *folder = NULL;
2127 ModestFolderView *tree_view = NULL;
2128 ModestFolderViewPrivate *priv = NULL;
2129 gboolean selected = FALSE;
2131 g_return_if_fail (sel);
2132 g_return_if_fail (user_data);
2134 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
2136 selected = gtk_tree_selection_get_selected (sel, &model, &iter);
2138 tree_view = MODEST_FOLDER_VIEW (user_data);
2141 gtk_tree_model_get (model, &iter,
2142 INSTANCE_COLUMN, &folder,
2145 /* If the folder is the same do not notify */
2146 if (folder && priv->cur_folder_store == folder) {
2147 g_object_unref (folder);
2152 /* Current folder was unselected */
2153 if (priv->cur_folder_store) {
2154 /* We must do this firstly because a libtinymail-camel
2155 implementation detail. If we issue the signal
2156 before doing the sync_async, then that signal could
2157 cause (and it actually does it) a free of the
2158 summary of the folder (because the main window will
2159 clear the headers view */
2160 if (TNY_IS_FOLDER(priv->cur_folder_store))
2161 tny_folder_sync_async (TNY_FOLDER(priv->cur_folder_store),
2162 FALSE, NULL, NULL, NULL);
2164 g_signal_emit (G_OBJECT(tree_view), signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
2165 priv->cur_folder_store, FALSE);
2167 g_object_unref (priv->cur_folder_store);
2168 priv->cur_folder_store = NULL;
2171 /* New current references */
2172 priv->cur_folder_store = folder;
2174 /* New folder has been selected. Do not notify if there is
2175 nothing new selected */
2177 g_signal_emit (G_OBJECT(tree_view),
2178 signals[FOLDER_SELECTION_CHANGED_SIGNAL],
2179 0, priv->cur_folder_store, TRUE);
2184 on_row_activated (GtkTreeView *treeview,
2185 GtkTreePath *treepath,
2186 GtkTreeViewColumn *column,
2189 GtkTreeModel *model = NULL;
2190 TnyFolderStore *folder = NULL;
2192 ModestFolderView *self = NULL;
2193 ModestFolderViewPrivate *priv = NULL;
2195 g_return_if_fail (treeview);
2196 g_return_if_fail (user_data);
2198 self = MODEST_FOLDER_VIEW (user_data);
2199 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
2201 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2203 if (!gtk_tree_model_get_iter (model, &iter, treepath))
2206 gtk_tree_model_get (model, &iter,
2207 INSTANCE_COLUMN, &folder,
2210 g_signal_emit (G_OBJECT(self),
2211 signals[FOLDER_ACTIVATED_SIGNAL],
2214 #ifdef MODEST_TOOLKIT_HILDON2
2215 HildonUIMode ui_mode;
2216 g_object_get (G_OBJECT (self), "hildon-ui-mode", &ui_mode, NULL);
2217 if (ui_mode == HILDON_UI_MODE_NORMAL) {
2218 if (priv->cur_folder_store)
2219 g_object_unref (priv->cur_folder_store);
2220 priv->cur_folder_store = g_object_ref (folder);
2224 g_object_unref (folder);
2228 modest_folder_view_get_selected (ModestFolderView *self)
2230 ModestFolderViewPrivate *priv;
2232 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW(self), NULL);
2234 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2235 if (priv->cur_folder_store)
2236 g_object_ref (priv->cur_folder_store);
2238 return priv->cur_folder_store;
2242 get_cmp_rows_type_pos (GObject *folder)
2244 /* Remote accounts -> Local account -> MMC account .*/
2247 if (TNY_IS_ACCOUNT (folder) &&
2248 modest_tny_account_is_virtual_local_folders (
2249 TNY_ACCOUNT (folder))) {
2251 } else if (TNY_IS_ACCOUNT (folder)) {
2252 TnyAccount *account = TNY_ACCOUNT (folder);
2253 const gchar *account_id = tny_account_get_id (account);
2254 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
2260 printf ("DEBUG: %s: unexpected type.\n", __FUNCTION__);
2261 return -1; /* Should never happen */
2266 inbox_is_special (TnyFolderStore *folder_store)
2268 gboolean is_special = TRUE;
2270 if (TNY_IS_FOLDER (folder_store)) {
2274 gchar *last_inbox_bar;
2276 id = tny_folder_get_id (TNY_FOLDER (folder_store));
2277 downcase = g_utf8_strdown (id, -1);
2278 last_bar = g_strrstr (downcase, "/");
2280 last_inbox_bar = g_strrstr (downcase, "inbox/");
2281 if ((last_inbox_bar == NULL) || (last_inbox_bar + 5 != last_bar))
2292 get_cmp_pos (TnyFolderType t, TnyFolder *folder_store)
2294 TnyAccount *account;
2295 gboolean is_special;
2296 /* Inbox, Outbox, Drafts, Sent, User */
2299 if (!TNY_IS_FOLDER (folder_store))
2302 case TNY_FOLDER_TYPE_INBOX:
2304 account = tny_folder_get_account (folder_store);
2305 is_special = (get_cmp_rows_type_pos (G_OBJECT (account)) == 0);
2307 /* In inbox case we need to know if the inbox is really the top
2308 * inbox of the account, or if it's a submailbox inbox. To do
2309 * this we'll apply an heuristic rule: Find last "/" and check
2310 * if it's preceeded by another Inbox */
2311 is_special = is_special && !inbox_is_special (TNY_FOLDER_STORE (folder_store));
2312 g_object_unref (account);
2313 return is_special?0:4;
2316 case TNY_FOLDER_TYPE_OUTBOX:
2317 return (TNY_IS_MERGE_FOLDER (folder_store))?2:4;
2319 case TNY_FOLDER_TYPE_DRAFTS:
2321 account = tny_folder_get_account (folder_store);
2322 is_special = (get_cmp_rows_type_pos (G_OBJECT (account)) == 1);
2323 g_object_unref (account);
2324 return is_special?1:4;
2327 case TNY_FOLDER_TYPE_SENT:
2329 account = tny_folder_get_account (folder_store);
2330 is_special = (get_cmp_rows_type_pos (G_OBJECT (account)) == 1);
2331 g_object_unref (account);
2332 return is_special?3:4;
2341 compare_account_names (TnyAccount *a1, TnyAccount *a2)
2343 const gchar *a1_name, *a2_name;
2345 a1_name = tny_account_get_name (a1);
2346 a2_name = tny_account_get_name (a2);
2348 return modest_text_utils_utf8_strcmp (a1_name, a2_name, TRUE);
2352 compare_accounts (TnyFolderStore *s1, TnyFolderStore *s2)
2354 TnyAccount *a1 = NULL, *a2 = NULL;
2357 if (TNY_IS_ACCOUNT (s1)) {
2358 a1 = TNY_ACCOUNT (g_object_ref (s1));
2359 } else if (!TNY_IS_MERGE_FOLDER (s1)) {
2360 a1 = tny_folder_get_account (TNY_FOLDER (s1));
2363 if (TNY_IS_ACCOUNT (s2)) {
2364 a2 = TNY_ACCOUNT (g_object_ref (s2));
2365 } else if (!TNY_IS_MERGE_FOLDER (s2)) {
2366 a2 = tny_folder_get_account (TNY_FOLDER (s2));
2383 /* First we sort with the type of account */
2384 cmp = get_cmp_rows_type_pos (G_OBJECT (a1)) - get_cmp_rows_type_pos (G_OBJECT (a2));
2388 cmp = compare_account_names (a1, a2);
2392 g_object_unref (a1);
2394 g_object_unref (a2);
2400 compare_accounts_first (TnyFolderStore *s1, TnyFolderStore *s2)
2402 gint is_account1, is_account2;
2404 is_account1 = TNY_IS_ACCOUNT (s1)?1:0;
2405 is_account2 = TNY_IS_ACCOUNT (s2)?1:0;
2407 return is_account2 - is_account1;
2411 * This function orders the mail accounts according to these rules:
2412 * 1st - remote accounts
2413 * 2nd - local account
2417 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
2421 gchar *name1 = NULL;
2422 gchar *name2 = NULL;
2423 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2424 TnyFolderType type2 = TNY_FOLDER_TYPE_UNKNOWN;
2425 GObject *folder1 = NULL;
2426 GObject *folder2 = NULL;
2428 gtk_tree_model_get (tree_model, iter1,
2429 NAME_COLUMN, &name1,
2431 INSTANCE_COLUMN, &folder1,
2433 gtk_tree_model_get (tree_model, iter2,
2434 NAME_COLUMN, &name2,
2435 TYPE_COLUMN, &type2,
2436 INSTANCE_COLUMN, &folder2,
2439 /* Return if we get no folder. This could happen when folder
2440 operations are happening. The model is updated after the
2441 folder copy/move actually occurs, so there could be
2442 situations where the model to be drawn is not correct */
2443 if (!folder1 || !folder2)
2446 /* Sort by type. First the special folders, then the archives */
2447 cmp = get_cmp_pos (type, (TnyFolder *) folder1) - get_cmp_pos (type2, (TnyFolder *) folder2);
2451 /* Now we sort using the account of each folder */
2452 if (TNY_IS_FOLDER_STORE (folder1) &&
2453 TNY_IS_FOLDER_STORE (folder2)) {
2454 cmp = compare_accounts (TNY_FOLDER_STORE (folder1), TNY_FOLDER_STORE (folder2));
2458 /* Each group is preceeded by its account */
2459 cmp = compare_accounts_first (TNY_FOLDER_STORE (folder1), TNY_FOLDER_STORE (folder2));
2464 /* Pure sort by name */
2465 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
2468 g_object_unref(G_OBJECT(folder1));
2470 g_object_unref(G_OBJECT(folder2));
2478 /*****************************************************************************/
2479 /* DRAG and DROP stuff */
2480 /*****************************************************************************/
2482 * This function fills the #GtkSelectionData with the row and the
2483 * model that has been dragged. It's called when this widget is a
2484 * source for dnd after the event drop happened
2487 on_drag_data_get (GtkWidget *widget, GdkDragContext *context, GtkSelectionData *selection_data,
2488 guint info, guint time, gpointer data)
2490 GtkTreeSelection *selection;
2491 GtkTreeModel *model;
2493 GtkTreePath *source_row;
2495 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
2496 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
2498 source_row = gtk_tree_model_get_path (model, &iter);
2499 gtk_tree_set_row_drag_data (selection_data,
2503 gtk_tree_path_free (source_row);
2507 typedef struct _DndHelper {
2508 ModestFolderView *folder_view;
2509 gboolean delete_source;
2510 GtkTreePath *source_row;
2514 dnd_helper_destroyer (DndHelper *helper)
2516 /* Free the helper */
2517 gtk_tree_path_free (helper->source_row);
2518 g_slice_free (DndHelper, helper);
2522 xfer_folder_cb (ModestMailOperation *mail_op,
2523 TnyFolder *new_folder,
2527 /* Select the folder */
2528 modest_folder_view_select_folder (MODEST_FOLDER_VIEW (user_data),
2534 /* get the folder for the row the treepath refers to. */
2535 /* folder must be unref'd */
2536 static TnyFolderStore *
2537 tree_path_to_folder (GtkTreeModel *model, GtkTreePath *path)
2540 TnyFolderStore *folder = NULL;
2542 if (gtk_tree_model_get_iter (model,&iter, path))
2543 gtk_tree_model_get (model, &iter,
2544 INSTANCE_COLUMN, &folder,
2551 * This function is used by drag_data_received_cb to manage drag and
2552 * drop of a header, i.e, and drag from the header view to the folder
2556 drag_and_drop_from_header_view (GtkTreeModel *source_model,
2557 GtkTreeModel *dest_model,
2558 GtkTreePath *dest_row,
2559 GtkSelectionData *selection_data)
2561 TnyList *headers = NULL;
2562 TnyFolder *folder = NULL, *src_folder = NULL;
2563 TnyFolderType folder_type;
2564 GtkTreeIter source_iter, dest_iter;
2565 ModestWindowMgr *mgr = NULL;
2566 ModestWindow *main_win = NULL;
2567 gchar **uris, **tmp;
2569 /* Build the list of headers */
2570 mgr = modest_runtime_get_window_mgr ();
2571 headers = tny_simple_list_new ();
2572 uris = modest_dnd_selection_data_get_paths (selection_data);
2575 while (*tmp != NULL) {
2578 gboolean first = TRUE;
2581 path = gtk_tree_path_new_from_string (*tmp);
2582 gtk_tree_model_get_iter (source_model, &source_iter, path);
2583 gtk_tree_model_get (source_model, &source_iter,
2584 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
2587 /* Do not enable d&d of headers already opened */
2588 if (!modest_window_mgr_find_registered_header(mgr, header, NULL))
2589 tny_list_append (headers, G_OBJECT (header));
2591 if (G_UNLIKELY (first)) {
2592 src_folder = tny_header_get_folder (header);
2596 /* Free and go on */
2597 gtk_tree_path_free (path);
2598 g_object_unref (header);
2603 /* This could happen ig we perform a d&d very quickly over the
2604 same row that row could dissapear because message is
2606 if (!TNY_IS_FOLDER (src_folder))
2609 /* Get the target folder */
2610 gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
2611 gtk_tree_model_get (dest_model, &dest_iter,
2615 if (!folder || !TNY_IS_FOLDER(folder)) {
2616 /* g_warning ("%s: not a valid target folder (%p)", __FUNCTION__, folder); */
2620 folder_type = modest_tny_folder_guess_folder_type (folder);
2621 if (folder_type == TNY_FOLDER_TYPE_INVALID) {
2622 /* g_warning ("%s: invalid target folder", __FUNCTION__); */
2623 goto cleanup; /* cannot move messages there */
2626 if (modest_tny_folder_get_rules((TNY_FOLDER(folder))) & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE) {
2627 /* g_warning ("folder not writable"); */
2628 goto cleanup; /* verboten! */
2631 /* Ask for confirmation to move */
2632 main_win = modest_window_mgr_get_main_window (mgr, FALSE); /* don't create */
2634 g_warning ("%s: BUG: no main window found", __FUNCTION__);
2638 /* Transfer messages */
2639 modest_ui_actions_transfer_messages_helper (GTK_WINDOW (main_win), src_folder,
2644 if (G_IS_OBJECT (src_folder))
2645 g_object_unref (src_folder);
2646 if (G_IS_OBJECT(folder))
2647 g_object_unref (G_OBJECT (folder));
2648 if (G_IS_OBJECT(headers))
2649 g_object_unref (headers);
2653 TnyFolderStore *src_folder;
2654 TnyFolderStore *dst_folder;
2655 ModestFolderView *folder_view;
2660 dnd_folder_info_destroyer (DndFolderInfo *info)
2662 if (info->src_folder)
2663 g_object_unref (info->src_folder);
2664 if (info->dst_folder)
2665 g_object_unref (info->dst_folder);
2666 g_slice_free (DndFolderInfo, info);
2670 dnd_on_connection_failed_destroyer (DndFolderInfo *info,
2671 GtkWindow *parent_window,
2672 TnyAccount *account)
2675 modest_ui_actions_on_account_connection_error (parent_window, account);
2677 /* Free the helper & info */
2678 dnd_helper_destroyer (info->helper);
2679 dnd_folder_info_destroyer (info);
2683 drag_and_drop_from_folder_view_src_folder_performer (gboolean canceled,
2685 GtkWindow *parent_window,
2686 TnyAccount *account,
2689 DndFolderInfo *info = NULL;
2690 ModestMailOperation *mail_op;
2692 info = (DndFolderInfo *) user_data;
2694 if (err || canceled) {
2695 dnd_on_connection_failed_destroyer (info, parent_window, account);
2699 /* Do the mail operation */
2700 mail_op = modest_mail_operation_new_with_error_handling ((GObject *) parent_window,
2701 modest_ui_actions_move_folder_error_handler,
2702 info->src_folder, NULL);
2704 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
2707 /* Transfer the folder */
2708 modest_mail_operation_xfer_folder (mail_op,
2709 TNY_FOLDER (info->src_folder),
2711 info->helper->delete_source,
2713 info->helper->folder_view);
2716 g_object_unref (G_OBJECT (mail_op));
2717 dnd_helper_destroyer (info->helper);
2718 dnd_folder_info_destroyer (info);
2723 drag_and_drop_from_folder_view_dst_folder_performer (gboolean canceled,
2725 GtkWindow *parent_window,
2726 TnyAccount *account,
2729 DndFolderInfo *info = NULL;
2731 info = (DndFolderInfo *) user_data;
2733 if (err || canceled) {
2734 dnd_on_connection_failed_destroyer (info, parent_window, account);
2738 /* Connect to source folder and perform the copy/move */
2739 modest_platform_connect_if_remote_and_perform (NULL, TRUE,
2741 drag_and_drop_from_folder_view_src_folder_performer,
2746 * This function is used by drag_data_received_cb to manage drag and
2747 * drop of a folder, i.e, and drag from the folder view to the same
2751 drag_and_drop_from_folder_view (GtkTreeModel *source_model,
2752 GtkTreeModel *dest_model,
2753 GtkTreePath *dest_row,
2754 GtkSelectionData *selection_data,
2757 GtkTreeIter dest_iter, iter;
2758 TnyFolderStore *dest_folder = NULL;
2759 TnyFolderStore *folder = NULL;
2760 gboolean forbidden = FALSE;
2762 DndFolderInfo *info = NULL;
2764 win = modest_window_mgr_get_main_window (modest_runtime_get_window_mgr(), FALSE); /* don't create */
2766 g_warning ("%s: BUG: no main window", __FUNCTION__);
2767 dnd_helper_destroyer (helper);
2772 /* check the folder rules for the destination */
2773 folder = tree_path_to_folder (dest_model, dest_row);
2774 if (TNY_IS_FOLDER(folder)) {
2775 ModestTnyFolderRules rules =
2776 modest_tny_folder_get_rules (TNY_FOLDER (folder));
2777 forbidden = rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE;
2778 } else if (TNY_IS_FOLDER_STORE(folder)) {
2779 /* enable local root as destination for folders */
2780 if (!MODEST_IS_TNY_LOCAL_FOLDERS_ACCOUNT (folder) &&
2781 !modest_tny_account_is_memory_card_account (TNY_ACCOUNT (folder)))
2784 g_object_unref (folder);
2787 /* check the folder rules for the source */
2788 folder = tree_path_to_folder (source_model, helper->source_row);
2789 if (TNY_IS_FOLDER(folder)) {
2790 ModestTnyFolderRules rules =
2791 modest_tny_folder_get_rules (TNY_FOLDER (folder));
2792 forbidden = rules & MODEST_FOLDER_RULES_FOLDER_NON_MOVEABLE;
2795 g_object_unref (folder);
2799 /* Check if the drag is possible */
2800 if (forbidden || !gtk_tree_path_compare (helper->source_row, dest_row)) {
2802 modest_platform_run_information_dialog ((GtkWindow *) win,
2803 _("mail_in_ui_folder_move_target_error"),
2805 /* Restore the previous selection */
2806 folder = tree_path_to_folder (source_model, helper->source_row);
2808 if (TNY_IS_FOLDER (folder))
2809 modest_folder_view_select_folder (helper->folder_view,
2810 TNY_FOLDER (folder), FALSE);
2811 g_object_unref (folder);
2813 dnd_helper_destroyer (helper);
2818 gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
2819 gtk_tree_model_get (dest_model, &dest_iter,
2822 gtk_tree_model_get_iter (source_model, &iter, helper->source_row);
2823 gtk_tree_model_get (source_model, &iter,
2827 /* Create the info for the performer */
2828 info = g_slice_new0 (DndFolderInfo);
2829 info->src_folder = g_object_ref (folder);
2830 info->dst_folder = g_object_ref (dest_folder);
2831 info->helper = helper;
2833 /* Connect to the destination folder and perform the copy/move */
2834 modest_platform_connect_if_remote_and_perform (GTK_WINDOW (win), TRUE,
2836 drag_and_drop_from_folder_view_dst_folder_performer,
2840 g_object_unref (dest_folder);
2841 g_object_unref (folder);
2845 * This function receives the data set by the "drag-data-get" signal
2846 * handler. This information comes within the #GtkSelectionData. This
2847 * function will manage both the drags of folders of the treeview and
2848 * drags of headers of the header view widget.
2851 on_drag_data_received (GtkWidget *widget,
2852 GdkDragContext *context,
2855 GtkSelectionData *selection_data,
2860 GtkWidget *source_widget;
2861 GtkTreeModel *dest_model, *source_model;
2862 GtkTreePath *source_row, *dest_row;
2863 GtkTreeViewDropPosition pos;
2864 gboolean delete_source = FALSE;
2865 gboolean success = FALSE;
2867 /* Do not allow further process */
2868 g_signal_stop_emission_by_name (widget, "drag-data-received");
2869 source_widget = gtk_drag_get_source_widget (context);
2871 /* Get the action */
2872 if (context->action == GDK_ACTION_MOVE) {
2873 delete_source = TRUE;
2875 /* Notify that there is no folder selected. We need to
2876 do this in order to update the headers view (and
2877 its monitors, because when moving, the old folder
2878 won't longer exist. We can not wait for the end of
2879 the operation, because the operation won't start if
2880 the folder is in use */
2881 if (source_widget == widget) {
2882 GtkTreeSelection *sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
2883 gtk_tree_selection_unselect_all (sel);
2887 /* Check if the get_data failed */
2888 if (selection_data == NULL || selection_data->length < 0)
2891 /* Select the destination model */
2892 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
2894 /* Get the path to the destination row. Can not call
2895 gtk_tree_view_get_drag_dest_row() because the source row
2896 is not selected anymore */
2897 gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget), x, y,
2900 /* Only allow drops IN other rows */
2902 pos == GTK_TREE_VIEW_DROP_BEFORE ||
2903 pos == GTK_TREE_VIEW_DROP_AFTER)
2907 /* Drags from the header view */
2908 if (source_widget != widget) {
2909 source_model = gtk_tree_view_get_model (GTK_TREE_VIEW (source_widget));
2911 drag_and_drop_from_header_view (source_model,
2916 DndHelper *helper = NULL;
2918 /* Get the source model and row */
2919 gtk_tree_get_row_drag_data (selection_data,
2923 /* Create the helper */
2924 helper = g_slice_new0 (DndHelper);
2925 helper->delete_source = delete_source;
2926 helper->source_row = gtk_tree_path_copy (source_row);
2927 helper->folder_view = MODEST_FOLDER_VIEW (widget);
2929 drag_and_drop_from_folder_view (source_model,
2935 gtk_tree_path_free (source_row);
2939 gtk_tree_path_free (dest_row);
2942 /* Finish the drag and drop */
2943 gtk_drag_finish (context, success, FALSE, time);
2947 * We define a "drag-drop" signal handler because we do not want to
2948 * use the default one, because the default one always calls
2949 * gtk_drag_finish and we prefer to do it in the "drag-data-received"
2950 * signal handler, because there we have all the information available
2951 * to know if the dnd was a success or not.
2954 drag_drop_cb (GtkWidget *widget,
2955 GdkDragContext *context,
2963 if (!context->targets)
2966 /* Check if we're dragging a folder row */
2967 target = gtk_drag_dest_find_target (widget, context, NULL);
2969 /* Request the data from the source. */
2970 gtk_drag_get_data(widget, context, target, time);
2976 * This function expands a node of a tree view if it's not expanded
2977 * yet. Not sure why it needs the threads stuff, but gtk+`example code
2978 * does that, so that's why they're here.
2981 expand_row_timeout (gpointer data)
2983 GtkTreeView *tree_view = data;
2984 GtkTreePath *dest_path = NULL;
2985 GtkTreeViewDropPosition pos;
2986 gboolean result = FALSE;
2988 gdk_threads_enter ();
2990 gtk_tree_view_get_drag_dest_row (tree_view,
2995 (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER ||
2996 pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)) {
2997 gtk_tree_view_expand_row (tree_view, dest_path, FALSE);
2998 gtk_tree_path_free (dest_path);
3002 gtk_tree_path_free (dest_path);
3007 gdk_threads_leave ();
3013 * This function is called whenever the pointer is moved over a widget
3014 * while dragging some data. It installs a timeout that will expand a
3015 * node of the treeview if not expanded yet. This function also calls
3016 * gdk_drag_status in order to set the suggested action that will be
3017 * used by the "drag-data-received" signal handler to know if we
3018 * should do a move or just a copy of the data.
3021 on_drag_motion (GtkWidget *widget,
3022 GdkDragContext *context,
3028 GtkTreeViewDropPosition pos;
3029 GtkTreePath *dest_row;
3030 GtkTreeModel *dest_model;
3031 ModestFolderViewPrivate *priv;
3032 GdkDragAction suggested_action;
3033 gboolean valid_location = FALSE;
3034 TnyFolderStore *folder = NULL;
3036 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
3038 if (priv->timer_expander != 0) {
3039 g_source_remove (priv->timer_expander);
3040 priv->timer_expander = 0;
3043 gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
3048 /* Do not allow drops between folders */
3050 pos == GTK_TREE_VIEW_DROP_BEFORE ||
3051 pos == GTK_TREE_VIEW_DROP_AFTER) {
3052 gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW (widget), NULL, 0);
3053 gdk_drag_status(context, 0, time);
3054 valid_location = FALSE;
3057 valid_location = TRUE;
3060 /* Check that the destination folder is writable */
3061 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
3062 folder = tree_path_to_folder (dest_model, dest_row);
3063 if (folder && TNY_IS_FOLDER (folder)) {
3064 ModestTnyFolderRules rules = modest_tny_folder_get_rules(TNY_FOLDER (folder));
3066 if (rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE) {
3067 valid_location = FALSE;
3072 /* Expand the selected row after 1/2 second */
3073 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), dest_row)) {
3074 priv->timer_expander = g_timeout_add (500, expand_row_timeout, widget);
3076 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), dest_row, pos);
3078 /* Select the desired action. By default we pick MOVE */
3079 suggested_action = GDK_ACTION_MOVE;
3081 if (context->actions == GDK_ACTION_COPY)
3082 gdk_drag_status(context, GDK_ACTION_COPY, time);
3083 else if (context->actions == GDK_ACTION_MOVE)
3084 gdk_drag_status(context, GDK_ACTION_MOVE, time);
3085 else if (context->actions & suggested_action)
3086 gdk_drag_status(context, suggested_action, time);
3088 gdk_drag_status(context, GDK_ACTION_DEFAULT, time);
3092 g_object_unref (folder);
3094 gtk_tree_path_free (dest_row);
3096 g_signal_stop_emission_by_name (widget, "drag-motion");
3098 return valid_location;
3102 * This function sets the treeview as a source and a target for dnd
3103 * events. It also connects all the requirede signals.
3106 setup_drag_and_drop (GtkTreeView *self)
3108 /* Set up the folder view as a dnd destination. Set only the
3109 highlight flag, otherwise gtk will have a different
3111 #ifdef MODEST_TOOLKIT_HILDON2
3114 gtk_drag_dest_set (GTK_WIDGET (self),
3115 GTK_DEST_DEFAULT_HIGHLIGHT,
3116 folder_view_drag_types,
3117 G_N_ELEMENTS (folder_view_drag_types),
3118 GDK_ACTION_MOVE | GDK_ACTION_COPY);
3120 g_signal_connect (G_OBJECT (self),
3121 "drag_data_received",
3122 G_CALLBACK (on_drag_data_received),
3126 /* Set up the treeview as a dnd source */
3127 gtk_drag_source_set (GTK_WIDGET (self),
3129 folder_view_drag_types,
3130 G_N_ELEMENTS (folder_view_drag_types),
3131 GDK_ACTION_MOVE | GDK_ACTION_COPY);
3133 g_signal_connect (G_OBJECT (self),
3135 G_CALLBACK (on_drag_motion),
3138 g_signal_connect (G_OBJECT (self),
3140 G_CALLBACK (on_drag_data_get),
3143 g_signal_connect (G_OBJECT (self),
3145 G_CALLBACK (drag_drop_cb),
3150 * This function manages the navigation through the folders using the
3151 * keyboard or the hardware keys in the device
3154 on_key_pressed (GtkWidget *self,
3158 GtkTreeSelection *selection;
3160 GtkTreeModel *model;
3161 gboolean retval = FALSE;
3163 /* Up and Down are automatically managed by the treeview */
3164 if (event->keyval == GDK_Return) {
3165 /* Expand/Collapse the selected row */
3166 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
3167 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
3170 path = gtk_tree_model_get_path (model, &iter);
3172 if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (self), path))
3173 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
3175 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
3176 gtk_tree_path_free (path);
3178 /* No further processing */
3186 * We listen to the changes in the local folder account name key,
3187 * because we want to show the right name in the view. The local
3188 * folder account name corresponds to the device name in the Maemo
3189 * version. We do this because we do not want to query gconf on each
3190 * tree view refresh. It's better to cache it and change whenever
3194 on_configuration_key_changed (ModestConf* conf,
3196 ModestConfEvent event,
3197 ModestConfNotificationId id,
3198 ModestFolderView *self)
3200 ModestFolderViewPrivate *priv;
3203 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
3204 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
3206 if (!strcmp (key, MODEST_CONF_DEVICE_NAME)) {
3207 g_free (priv->local_account_name);
3209 if (event == MODEST_CONF_EVENT_KEY_UNSET)
3210 priv->local_account_name = g_strdup (MODEST_LOCAL_FOLDERS_DEFAULT_DISPLAY_NAME);
3212 priv->local_account_name = modest_conf_get_string (modest_runtime_get_conf(),
3213 MODEST_CONF_DEVICE_NAME, NULL);
3215 /* Force a redraw */
3216 #if GTK_CHECK_VERSION(2, 8, 0)
3217 GtkTreeViewColumn * tree_column;
3219 tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self),
3221 gtk_tree_view_column_queue_resize (tree_column);
3223 gtk_widget_queue_draw (GTK_WIDGET (self));
3229 modest_folder_view_set_style (ModestFolderView *self,
3230 ModestFolderViewStyle style)
3232 ModestFolderViewPrivate *priv;
3234 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
3235 g_return_if_fail (style == MODEST_FOLDER_VIEW_STYLE_SHOW_ALL ||
3236 style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE);
3238 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
3241 priv->style = style;
3245 modest_folder_view_set_account_id_of_visible_server_account (ModestFolderView *self,
3246 const gchar *account_id)
3248 ModestFolderViewPrivate *priv;
3249 GtkTreeModel *model;
3251 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
3253 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
3255 /* This will be used by the filter_row callback,
3256 * to decided which rows to show: */
3257 if (priv->visible_account_id) {
3258 g_free (priv->visible_account_id);
3259 priv->visible_account_id = NULL;
3262 priv->visible_account_id = g_strdup (account_id);
3265 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
3266 if (GTK_IS_TREE_MODEL_FILTER (model))
3267 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
3269 /* Save settings to gconf */
3270 modest_widget_memory_save (modest_runtime_get_conf (), G_OBJECT(self),
3271 MODEST_CONF_FOLDER_VIEW_KEY);
3275 modest_folder_view_get_account_id_of_visible_server_account (ModestFolderView *self)
3277 ModestFolderViewPrivate *priv;
3279 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW(self), NULL);
3281 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
3283 return (const gchar *) priv->visible_account_id;
3287 find_inbox_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *inbox_iter)
3291 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
3293 gtk_tree_model_get (model, iter,
3297 gboolean result = FALSE;
3298 if (type == TNY_FOLDER_TYPE_INBOX) {
3302 *inbox_iter = *iter;
3306 if (gtk_tree_model_iter_children (model, &child, iter)) {
3307 if (find_inbox_iter (model, &child, inbox_iter))
3311 } while (gtk_tree_model_iter_next (model, iter));
3320 modest_folder_view_select_first_inbox_or_local (ModestFolderView *self)
3322 GtkTreeModel *model;
3323 GtkTreeIter iter, inbox_iter;
3324 GtkTreeSelection *sel;
3325 GtkTreePath *path = NULL;
3327 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
3329 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
3333 expand_root_items (self);
3334 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
3336 if (!gtk_tree_model_get_iter_first (model, &iter)) {
3337 g_warning ("%s: model is empty", __FUNCTION__);
3341 if (find_inbox_iter (model, &iter, &inbox_iter))
3342 path = gtk_tree_model_get_path (model, &inbox_iter);
3344 path = gtk_tree_path_new_first ();
3346 /* Select the row and free */
3347 gtk_tree_view_set_cursor (GTK_TREE_VIEW (self), path, NULL, FALSE);
3348 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (self), path, NULL, FALSE, 0.0, 0.0);
3349 gtk_tree_path_free (path);
3352 gtk_widget_grab_focus (GTK_WIDGET(self));
3358 find_folder_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *folder_iter,
3363 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
3364 TnyFolder* a_folder;
3367 gtk_tree_model_get (model, iter,
3368 INSTANCE_COLUMN, &a_folder,
3374 if (folder == a_folder) {
3375 g_object_unref (a_folder);
3376 *folder_iter = *iter;
3379 g_object_unref (a_folder);
3381 if (gtk_tree_model_iter_children (model, &child, iter)) {
3382 if (find_folder_iter (model, &child, folder_iter, folder))
3386 } while (gtk_tree_model_iter_next (model, iter));
3391 #ifndef MODEST_TOOLKIT_HILDON2
3393 on_row_inserted_maybe_select_folder (GtkTreeModel *tree_model,
3396 ModestFolderView *self)
3398 ModestFolderViewPrivate *priv = NULL;
3399 GtkTreeSelection *sel;
3400 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
3401 GObject *instance = NULL;
3403 if (!MODEST_IS_FOLDER_VIEW(self))
3406 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
3408 priv->reexpand = TRUE;
3410 gtk_tree_model_get (tree_model, iter,
3412 INSTANCE_COLUMN, &instance,
3418 if (type == TNY_FOLDER_TYPE_INBOX && priv->folder_to_select == NULL) {
3419 priv->folder_to_select = g_object_ref (instance);
3421 g_object_unref (instance);
3423 if (priv->folder_to_select) {
3425 if (!modest_folder_view_select_folder (self, priv->folder_to_select,
3428 path = gtk_tree_model_get_path (tree_model, iter);
3429 gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
3431 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
3433 gtk_tree_selection_select_iter (sel, iter);
3434 gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
3436 gtk_tree_path_free (path);
3440 modest_folder_view_disable_next_folder_selection (self);
3442 /* Refilter the model */
3443 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (tree_model));
3449 modest_folder_view_disable_next_folder_selection (ModestFolderView *self)
3451 ModestFolderViewPrivate *priv;
3453 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
3455 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
3457 if (priv->folder_to_select)
3458 g_object_unref(priv->folder_to_select);
3460 priv->folder_to_select = NULL;
3464 modest_folder_view_select_folder (ModestFolderView *self, TnyFolder *folder,
3465 gboolean after_change)
3467 GtkTreeModel *model;
3468 GtkTreeIter iter, folder_iter;
3469 GtkTreeSelection *sel;
3470 ModestFolderViewPrivate *priv = NULL;
3472 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW (self), FALSE);
3473 g_return_val_if_fail (folder && TNY_IS_FOLDER (folder), FALSE);
3475 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
3478 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
3479 gtk_tree_selection_unselect_all (sel);
3481 if (priv->folder_to_select)
3482 g_object_unref(priv->folder_to_select);
3483 priv->folder_to_select = TNY_FOLDER(g_object_ref(folder));
3487 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
3492 /* Refilter the model, before selecting the folder */
3493 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
3495 if (!gtk_tree_model_get_iter_first (model, &iter)) {
3496 g_warning ("%s: model is empty", __FUNCTION__);
3500 if (find_folder_iter (model, &iter, &folder_iter, folder)) {
3503 path = gtk_tree_model_get_path (model, &folder_iter);
3504 gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
3506 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
3507 gtk_tree_selection_select_iter (sel, &folder_iter);
3508 gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
3510 gtk_tree_path_free (path);
3518 modest_folder_view_copy_selection (ModestFolderView *self)
3520 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
3522 /* Copy selection */
3523 _clipboard_set_selected_data (self, FALSE);
3527 modest_folder_view_cut_selection (ModestFolderView *folder_view)
3529 ModestFolderViewPrivate *priv = NULL;
3530 GtkTreeModel *model = NULL;
3531 const gchar **hidding = NULL;
3532 guint i, n_selected;
3534 g_return_if_fail (folder_view && MODEST_IS_FOLDER_VIEW (folder_view));
3535 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
3537 /* Copy selection */
3538 if (!_clipboard_set_selected_data (folder_view, TRUE))
3541 /* Get hidding ids */
3542 hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected);
3544 /* Clear hidding array created by previous cut operation */
3545 _clear_hidding_filter (MODEST_FOLDER_VIEW (folder_view));
3547 /* Copy hidding array */
3548 priv->n_selected = n_selected;
3549 priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
3550 for (i=0; i < n_selected; i++)
3551 priv->hidding_ids[i] = g_strdup(hidding[i]);
3553 /* Hide cut folders */
3554 model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
3555 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
3559 modest_folder_view_copy_model (ModestFolderView *folder_view_src,
3560 ModestFolderView *folder_view_dst)
3562 GtkTreeModel *filter_model = NULL;
3563 GtkTreeModel *model = NULL;
3564 GtkTreeModel *new_filter_model = NULL;
3566 g_return_if_fail (folder_view_src && MODEST_IS_FOLDER_VIEW (folder_view_src));
3567 g_return_if_fail (folder_view_dst && MODEST_IS_FOLDER_VIEW (folder_view_dst));
3570 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view_src));
3571 model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER(filter_model));
3573 /* Build new filter model */
3574 new_filter_model = gtk_tree_model_filter_new (model, NULL);
3575 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (new_filter_model),
3579 /* Set copied model */
3580 gtk_tree_view_set_model (GTK_TREE_VIEW (folder_view_dst), new_filter_model);
3581 #ifndef MODEST_TOOLKIT_HILDON2
3582 g_signal_connect (G_OBJECT(new_filter_model), "row-inserted",
3583 (GCallback) on_row_inserted_maybe_select_folder, folder_view_dst);
3587 g_object_unref (new_filter_model);
3591 modest_folder_view_show_non_move_folders (ModestFolderView *folder_view,
3594 GtkTreeModel *model = NULL;
3595 ModestFolderViewPrivate* priv;
3597 g_return_if_fail (folder_view && MODEST_IS_FOLDER_VIEW (folder_view));
3599 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
3600 priv->show_non_move = show;
3601 /* modest_folder_view_update_model(folder_view, */
3602 /* TNY_ACCOUNT_STORE(modest_runtime_get_account_store())); */
3604 /* Hide special folders */
3605 model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
3606 if (GTK_IS_TREE_MODEL_FILTER (model)) {
3607 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
3611 /* Returns FALSE if it did not selected anything */
3613 _clipboard_set_selected_data (ModestFolderView *folder_view,
3616 ModestFolderViewPrivate *priv = NULL;
3617 TnyFolderStore *folder = NULL;
3618 gboolean retval = FALSE;
3620 g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (folder_view), FALSE);
3621 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
3623 /* Set selected data on clipboard */
3624 g_return_val_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard), FALSE);
3625 folder = modest_folder_view_get_selected (folder_view);
3627 /* Do not allow to select an account */
3628 if (TNY_IS_FOLDER (folder)) {
3629 modest_email_clipboard_set_data (priv->clipboard, TNY_FOLDER(folder), NULL, delete);
3634 g_object_unref (folder);
3640 _clear_hidding_filter (ModestFolderView *folder_view)
3642 ModestFolderViewPrivate *priv;
3645 g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view));
3646 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
3648 if (priv->hidding_ids != NULL) {
3649 for (i=0; i < priv->n_selected; i++)
3650 g_free (priv->hidding_ids[i]);
3651 g_free(priv->hidding_ids);
3657 on_display_name_changed (ModestAccountMgr *mgr,
3658 const gchar *account,
3661 ModestFolderView *self;
3663 self = MODEST_FOLDER_VIEW (user_data);
3665 /* Force a redraw */
3666 #if GTK_CHECK_VERSION(2, 8, 0)
3667 GtkTreeViewColumn * tree_column;
3669 tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self),
3671 gtk_tree_view_column_queue_resize (tree_column);
3673 gtk_widget_queue_draw (GTK_WIDGET (self));
3678 modest_folder_view_set_cell_style (ModestFolderView *self,
3679 ModestFolderViewCellStyle cell_style)
3681 ModestFolderViewPrivate *priv = NULL;
3683 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
3684 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
3686 priv->cell_style = cell_style;
3688 g_object_set (G_OBJECT (priv->messages_renderer),
3689 "visible", (cell_style == MODEST_FOLDER_VIEW_CELL_STYLE_COMPACT),
3692 gtk_widget_queue_draw (GTK_WIDGET (self));
3696 update_style (ModestFolderView *self)
3698 ModestFolderViewPrivate *priv;
3699 GdkColor style_color;
3700 PangoAttrList *attr_list;
3702 PangoAttribute *attr;
3704 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
3705 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
3709 attr_list = pango_attr_list_new ();
3710 if (!gtk_style_lookup_color (GTK_WIDGET (self)->style, "SecondaryTextColor", &style_color)) {
3711 gdk_color_parse ("grey", &style_color);
3713 attr = pango_attr_foreground_new (style_color.red, style_color.green, style_color.blue);
3714 pango_attr_list_insert (attr_list, attr);
3717 style = gtk_rc_get_style_by_paths (gtk_widget_get_settings
3719 "SmallSystemFont", NULL,
3722 attr = pango_attr_font_desc_new (pango_font_description_copy
3723 (style->font_desc));
3724 pango_attr_list_insert (attr_list, attr);
3726 g_object_set (G_OBJECT (priv->messages_renderer),
3727 "foreground-gdk", &style_color,
3728 "foreground-set", TRUE,
3729 "attributes", attr_list,
3731 pango_attr_list_unref (attr_list);
3736 on_notify_style (GObject *obj, GParamSpec *spec, gpointer userdata)
3738 if (strcmp ("style", spec->name) == 0) {
3739 update_style (MODEST_FOLDER_VIEW (obj));
3740 gtk_widget_queue_draw (GTK_WIDGET (obj));
3745 modest_folder_view_set_filter (ModestFolderView *self,
3746 ModestFolderViewFilter filter)
3748 ModestFolderViewPrivate *priv;
3749 GtkTreeModel *filter_model;
3751 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
3752 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
3754 priv->filter |= filter;
3756 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
3757 if (GTK_IS_TREE_MODEL_FILTER(filter_model)) {
3758 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
3763 modest_folder_view_unset_filter (ModestFolderView *self,
3764 ModestFolderViewFilter filter)
3766 ModestFolderViewPrivate *priv;
3767 GtkTreeModel *filter_model;
3769 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
3770 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
3772 priv->filter &= ~filter;
3774 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
3775 if (GTK_IS_TREE_MODEL_FILTER(filter_model)) {
3776 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
3781 modest_folder_view_any_folder_fulfils_rules (ModestFolderView *self,
3782 ModestTnyFolderRules rules)
3784 GtkTreeModel *filter_model;
3786 gboolean fulfil = FALSE;
3788 if (!get_inner_models (self, &filter_model, NULL, NULL))
3791 if (!gtk_tree_model_get_iter_first (filter_model, &iter))
3795 TnyFolderStore *folder;
3797 gtk_tree_model_get (filter_model, &iter, INSTANCE_COLUMN, &folder, -1);
3799 if (TNY_IS_FOLDER (folder)) {
3800 ModestTnyFolderRules folder_rules = modest_tny_folder_get_rules (TNY_FOLDER (folder));
3801 /* Folder rules are negative: non_writable, non_deletable... */
3802 if (!(folder_rules & rules))
3805 g_object_unref (folder);
3808 } while (gtk_tree_model_iter_next (filter_model, &iter) && !fulfil);
3814 modest_folder_view_set_list_to_move (ModestFolderView *self,
3817 ModestFolderViewPrivate *priv;
3819 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
3820 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
3822 if (priv->list_to_move)
3823 g_object_unref (priv->list_to_move);
3826 g_object_ref (list);
3828 priv->list_to_move = list;