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);
1702 has_folder_with_id (ModestFolderView *self, const gchar *id)
1708 has_child_with_name_of (ModestFolderView *self, TnyFolder *a, TnyFolder *b)
1711 gboolean retval = FALSE;
1713 a_id = tny_folder_get_id (a);
1716 b_id = tny_folder_get_id (b);
1719 const gchar *last_bar;
1720 gchar *string_to_match;
1721 last_bar = g_strrstr (b_id, "/");
1724 string_to_match = g_strconcat (a_id, "/", last_bar, NULL);
1725 retval = has_folder_with_id (self, string_to_match);
1726 g_free (string_to_match);
1734 check_move_to_this_folder_valid (ModestFolderView *self, TnyFolder *folder)
1736 ModestFolderViewPrivate *priv;
1737 TnyIterator *iterator;
1738 gboolean retval = TRUE;
1740 g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (self), FALSE);
1741 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1743 for (iterator = tny_list_create_iterator (priv->list_to_move);
1744 retval && !tny_iterator_is_done (iterator);
1745 tny_iterator_next (iterator)) {
1747 instance = tny_iterator_get_current (iterator);
1748 if (instance == (GObject *) folder) {
1750 } else if (TNY_IS_FOLDER (instance)) {
1751 retval = !is_parent_of (TNY_FOLDER (instance), folder);
1753 retval = !has_child_with_name_of (self, folder, TNY_FOLDER (instance));
1756 g_object_unref (instance);
1764 * We use this function to implement the
1765 * MODEST_FOLDER_VIEW_STYLE_SHOW_ONE style. We only show the default
1766 * account in this case, and the local folders.
1769 filter_row (GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
1771 ModestFolderViewPrivate *priv;
1772 gboolean retval = TRUE;
1773 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1774 GObject *instance = NULL;
1775 const gchar *id = NULL;
1777 gboolean found = FALSE;
1778 gboolean cleared = FALSE;
1779 ModestTnyFolderRules rules = 0;
1781 g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (data), FALSE);
1782 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (data);
1784 gtk_tree_model_get (model, iter,
1786 INSTANCE_COLUMN, &instance,
1789 /* Do not show if there is no instance, this could indeed
1790 happen when the model is being modified while it's being
1791 drawn. This could occur for example when moving folders
1796 if (TNY_IS_ACCOUNT (instance)) {
1797 TnyAccount *acc = TNY_ACCOUNT (instance);
1798 const gchar *account_id = tny_account_get_id (acc);
1800 /* If it isn't a special folder,
1801 * don't show it unless it is the visible account: */
1802 if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE &&
1803 !modest_tny_account_is_virtual_local_folders (acc) &&
1804 strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
1806 /* Show only the visible account id */
1807 if (priv->visible_account_id) {
1808 if (strcmp (account_id, priv->visible_account_id))
1815 /* Never show these to the user. They are merged into one folder
1816 * in the local-folders account instead: */
1817 if (retval && MODEST_IS_TNY_OUTBOX_ACCOUNT (acc))
1820 if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE) {
1821 /* Only show special folders for current account if needed */
1822 if (TNY_IS_FOLDER (instance) && !TNY_IS_MERGE_FOLDER (instance)) {
1823 TnyAccount *account;
1825 account = tny_folder_get_account (TNY_FOLDER (instance));
1827 if (TNY_IS_ACCOUNT (account)) {
1828 const gchar *account_id = tny_account_get_id (account);
1830 if (!modest_tny_account_is_virtual_local_folders (account) &&
1831 strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
1832 /* Show only the visible account id */
1833 if (priv->visible_account_id) {
1834 if (strcmp (account_id, priv->visible_account_id))
1838 g_object_unref (account);
1845 /* Check hiding (if necessary) */
1846 cleared = modest_email_clipboard_cleared (priv->clipboard);
1847 if ((retval) && (!cleared) && (TNY_IS_FOLDER (instance))) {
1848 id = tny_folder_get_id (TNY_FOLDER(instance));
1849 if (priv->hidding_ids != NULL)
1850 for (i=0; i < priv->n_selected && !found; i++)
1851 if (priv->hidding_ids[i] != NULL && id != NULL)
1852 found = (!strcmp (priv->hidding_ids[i], id));
1857 /* If this is a move to dialog, hide Sent, Outbox and Drafts
1858 folder as no message can be move there according to UI specs */
1859 if (!priv->show_non_move) {
1860 if (priv->list_to_move &&
1861 tny_list_get_length (priv->list_to_move) > 0 &&
1862 TNY_IS_FOLDER (instance)) {
1863 retval = check_move_to_this_folder_valid (MODEST_FOLDER_VIEW (data), TNY_FOLDER (instance));
1865 if (retval && TNY_IS_FOLDER (instance) &&
1866 modest_tny_folder_is_local_folder (TNY_FOLDER (instance))) {
1868 case TNY_FOLDER_TYPE_OUTBOX:
1869 case TNY_FOLDER_TYPE_SENT:
1870 case TNY_FOLDER_TYPE_DRAFTS:
1873 case TNY_FOLDER_TYPE_UNKNOWN:
1874 case TNY_FOLDER_TYPE_NORMAL:
1875 type = modest_tny_folder_guess_folder_type(TNY_FOLDER(instance));
1876 if (type == TNY_FOLDER_TYPE_INVALID)
1877 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1879 if (type == TNY_FOLDER_TYPE_OUTBOX ||
1880 type == TNY_FOLDER_TYPE_SENT
1881 || type == TNY_FOLDER_TYPE_DRAFTS)
1890 /* apply special filters */
1891 if (retval && (priv->filter & MODEST_FOLDER_VIEW_FILTER_HIDE_ACCOUNTS)) {
1892 if (TNY_IS_ACCOUNT (instance))
1896 if (retval && (priv->filter & MODEST_FOLDER_VIEW_FILTER_CAN_HAVE_FOLDERS)) {
1897 if (TNY_IS_FOLDER (instance)) {
1898 /* Check folder rules */
1899 ModestTnyFolderRules rules;
1901 rules = modest_tny_folder_get_rules (TNY_FOLDER (instance));
1902 retval = !(rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE);
1903 } else if (TNY_IS_ACCOUNT (instance)) {
1904 if (modest_tny_folder_store_is_remote (TNY_FOLDER_STORE (instance))) {
1912 if (retval && (priv->filter & MODEST_FOLDER_VIEW_FILTER_HIDE_MANDATORY_FOLDERS)) {
1913 if (TNY_IS_FOLDER (instance)) {
1914 TnyFolderType guess_type;
1916 if (TNY_FOLDER_TYPE_NORMAL) {
1917 guess_type = modest_tny_folder_guess_folder_type (TNY_FOLDER (instance));
1923 case TNY_FOLDER_TYPE_OUTBOX:
1924 case TNY_FOLDER_TYPE_SENT:
1925 case TNY_FOLDER_TYPE_DRAFTS:
1926 case TNY_FOLDER_TYPE_ARCHIVE:
1927 case TNY_FOLDER_TYPE_INBOX:
1930 case TNY_FOLDER_TYPE_UNKNOWN:
1931 case TNY_FOLDER_TYPE_NORMAL:
1937 } else if (TNY_IS_ACCOUNT (instance)) {
1942 if (retval && TNY_IS_FOLDER (instance)) {
1943 rules = modest_tny_folder_get_rules (TNY_FOLDER (instance));
1946 if (retval && (priv->filter & MODEST_FOLDER_VIEW_FILTER_DELETABLE)) {
1947 if (TNY_IS_FOLDER (instance)) {
1948 retval = !(rules & MODEST_FOLDER_RULES_FOLDER_NON_DELETABLE);
1949 } else if (TNY_IS_ACCOUNT (instance)) {
1954 if (retval && (priv->filter & MODEST_FOLDER_VIEW_FILTER_RENAMEABLE)) {
1955 if (TNY_IS_FOLDER (instance)) {
1956 retval = !(rules & MODEST_FOLDER_RULES_FOLDER_NON_RENAMEABLE);
1957 } else if (TNY_IS_ACCOUNT (instance)) {
1962 if (retval && (priv->filter & MODEST_FOLDER_VIEW_FILTER_MOVEABLE)) {
1963 if (TNY_IS_FOLDER (instance)) {
1964 retval = !(rules & MODEST_FOLDER_RULES_FOLDER_NON_MOVEABLE);
1965 } else if (TNY_IS_ACCOUNT (instance)) {
1971 g_object_unref (instance);
1978 modest_folder_view_update_model (ModestFolderView *self,
1979 TnyAccountStore *account_store)
1981 ModestFolderViewPrivate *priv;
1982 GtkTreeModel *model /* , *old_model */;
1983 GtkTreeModel *filter_model = NULL, *sortable = NULL;
1985 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW (self), FALSE);
1986 g_return_val_if_fail (account_store && MODEST_IS_TNY_ACCOUNT_STORE(account_store),
1989 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1991 /* Notify that there is no folder selected */
1992 g_signal_emit (G_OBJECT(self),
1993 signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
1995 if (priv->cur_folder_store) {
1996 g_object_unref (priv->cur_folder_store);
1997 priv->cur_folder_store = NULL;
2000 /* FIXME: the local accounts are not shown when the query
2001 selects only the subscribed folders */
2002 #ifdef MODEST_TOOLKIT_HILDON2
2003 model = tny_gtk_folder_list_store_new_with_flags (NULL,
2004 TNY_GTK_FOLDER_LIST_STORE_FLAG_SHOW_PATH);
2005 tny_gtk_folder_list_store_set_path_separator (TNY_GTK_FOLDER_LIST_STORE (model),
2006 MODEST_FOLDER_PATH_SEPARATOR);
2008 model = tny_gtk_folder_store_tree_model_new (NULL);
2011 /* When the model is a list store (plain representation) the
2012 outbox is not a child of any account so we have to manually
2013 delete it because removing the local folders account won't
2014 delete it (because tny_folder_get_account() is not defined
2015 for a merge folder */
2016 if (TNY_IS_GTK_FOLDER_LIST_STORE (model)) {
2017 TnyAccount *account;
2018 ModestTnyAccountStore *acc_store;
2020 acc_store = modest_runtime_get_account_store ();
2021 account = modest_tny_account_store_get_local_folders_account (acc_store);
2023 if (g_signal_handler_is_connected (account,
2024 priv->outbox_deleted_handler))
2025 g_signal_handler_disconnect (account,
2026 priv->outbox_deleted_handler);
2028 priv->outbox_deleted_handler =
2029 g_signal_connect (account,
2031 G_CALLBACK (on_outbox_deleted_cb),
2033 g_object_unref (account);
2036 /* Get the accounts: */
2037 tny_account_store_get_accounts (TNY_ACCOUNT_STORE(account_store),
2039 TNY_ACCOUNT_STORE_STORE_ACCOUNTS);
2041 sortable = gtk_tree_model_sort_new_with_model (model);
2042 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
2044 GTK_SORT_ASCENDING);
2045 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
2047 cmp_rows, NULL, NULL);
2049 /* Create filter model */
2050 filter_model = gtk_tree_model_filter_new (sortable, NULL);
2051 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
2057 gtk_tree_view_set_model (GTK_TREE_VIEW(self), filter_model);
2058 #ifndef MODEST_TOOLKIT_HILDON2
2059 g_signal_connect (G_OBJECT(filter_model), "row-inserted",
2060 (GCallback) on_row_inserted_maybe_select_folder, self);
2063 g_object_unref (model);
2064 g_object_unref (filter_model);
2065 g_object_unref (sortable);
2067 /* Force a reselection of the INBOX next time the widget is shown */
2068 priv->reselect = TRUE;
2075 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
2077 GtkTreeModel *model = NULL;
2078 TnyFolderStore *folder = NULL;
2080 ModestFolderView *tree_view = NULL;
2081 ModestFolderViewPrivate *priv = NULL;
2082 gboolean selected = FALSE;
2084 g_return_if_fail (sel);
2085 g_return_if_fail (user_data);
2087 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
2089 selected = gtk_tree_selection_get_selected (sel, &model, &iter);
2091 tree_view = MODEST_FOLDER_VIEW (user_data);
2094 gtk_tree_model_get (model, &iter,
2095 INSTANCE_COLUMN, &folder,
2098 /* If the folder is the same do not notify */
2099 if (folder && priv->cur_folder_store == folder) {
2100 g_object_unref (folder);
2105 /* Current folder was unselected */
2106 if (priv->cur_folder_store) {
2107 /* We must do this firstly because a libtinymail-camel
2108 implementation detail. If we issue the signal
2109 before doing the sync_async, then that signal could
2110 cause (and it actually does it) a free of the
2111 summary of the folder (because the main window will
2112 clear the headers view */
2113 if (TNY_IS_FOLDER(priv->cur_folder_store))
2114 tny_folder_sync_async (TNY_FOLDER(priv->cur_folder_store),
2115 FALSE, NULL, NULL, NULL);
2117 g_signal_emit (G_OBJECT(tree_view), signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
2118 priv->cur_folder_store, FALSE);
2120 g_object_unref (priv->cur_folder_store);
2121 priv->cur_folder_store = NULL;
2124 /* New current references */
2125 priv->cur_folder_store = folder;
2127 /* New folder has been selected. Do not notify if there is
2128 nothing new selected */
2130 g_signal_emit (G_OBJECT(tree_view),
2131 signals[FOLDER_SELECTION_CHANGED_SIGNAL],
2132 0, priv->cur_folder_store, TRUE);
2137 on_row_activated (GtkTreeView *treeview,
2138 GtkTreePath *treepath,
2139 GtkTreeViewColumn *column,
2142 GtkTreeModel *model = NULL;
2143 TnyFolderStore *folder = NULL;
2145 ModestFolderView *self = NULL;
2146 ModestFolderViewPrivate *priv = NULL;
2148 g_return_if_fail (treeview);
2149 g_return_if_fail (user_data);
2151 self = MODEST_FOLDER_VIEW (user_data);
2152 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
2154 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2156 if (!gtk_tree_model_get_iter (model, &iter, treepath))
2159 gtk_tree_model_get (model, &iter,
2160 INSTANCE_COLUMN, &folder,
2163 g_signal_emit (G_OBJECT(self),
2164 signals[FOLDER_ACTIVATED_SIGNAL],
2167 #ifdef MODEST_TOOLKIT_HILDON2
2168 HildonUIMode ui_mode;
2169 g_object_get (G_OBJECT (self), "hildon-ui-mode", &ui_mode, NULL);
2170 if (ui_mode == HILDON_UI_MODE_NORMAL) {
2171 if (priv->cur_folder_store)
2172 g_object_unref (priv->cur_folder_store);
2173 priv->cur_folder_store = g_object_ref (folder);
2177 g_object_unref (folder);
2181 modest_folder_view_get_selected (ModestFolderView *self)
2183 ModestFolderViewPrivate *priv;
2185 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW(self), NULL);
2187 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2188 if (priv->cur_folder_store)
2189 g_object_ref (priv->cur_folder_store);
2191 return priv->cur_folder_store;
2195 get_cmp_rows_type_pos (GObject *folder)
2197 /* Remote accounts -> Local account -> MMC account .*/
2200 if (TNY_IS_ACCOUNT (folder) &&
2201 modest_tny_account_is_virtual_local_folders (
2202 TNY_ACCOUNT (folder))) {
2204 } else if (TNY_IS_ACCOUNT (folder)) {
2205 TnyAccount *account = TNY_ACCOUNT (folder);
2206 const gchar *account_id = tny_account_get_id (account);
2207 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
2213 printf ("DEBUG: %s: unexpected type.\n", __FUNCTION__);
2214 return -1; /* Should never happen */
2219 inbox_is_special (TnyFolderStore *folder_store)
2221 gboolean is_special = TRUE;
2223 if (TNY_IS_FOLDER (folder_store)) {
2227 gchar *last_inbox_bar;
2229 id = tny_folder_get_id (TNY_FOLDER (folder_store));
2230 downcase = g_utf8_strdown (id, -1);
2231 last_bar = g_strrstr (downcase, "/");
2233 last_inbox_bar = g_strrstr (downcase, "inbox/");
2234 if ((last_inbox_bar == NULL) || (last_inbox_bar + 5 != last_bar))
2245 get_cmp_pos (TnyFolderType t, TnyFolder *folder_store)
2247 TnyAccount *account;
2248 gboolean is_special;
2249 /* Inbox, Outbox, Drafts, Sent, User */
2252 if (!TNY_IS_FOLDER (folder_store))
2255 case TNY_FOLDER_TYPE_INBOX:
2257 account = tny_folder_get_account (folder_store);
2258 is_special = (get_cmp_rows_type_pos (G_OBJECT (account)) == 0);
2260 /* In inbox case we need to know if the inbox is really the top
2261 * inbox of the account, or if it's a submailbox inbox. To do
2262 * this we'll apply an heuristic rule: Find last "/" and check
2263 * if it's preceeded by another Inbox */
2264 is_special = is_special && !inbox_is_special (TNY_FOLDER_STORE (folder_store));
2265 g_object_unref (account);
2266 return is_special?0:4;
2269 case TNY_FOLDER_TYPE_OUTBOX:
2270 return (TNY_IS_MERGE_FOLDER (folder_store))?2:4;
2272 case TNY_FOLDER_TYPE_DRAFTS:
2274 account = tny_folder_get_account (folder_store);
2275 is_special = (get_cmp_rows_type_pos (G_OBJECT (account)) == 1);
2276 g_object_unref (account);
2277 return is_special?1:4;
2280 case TNY_FOLDER_TYPE_SENT:
2282 account = tny_folder_get_account (folder_store);
2283 is_special = (get_cmp_rows_type_pos (G_OBJECT (account)) == 1);
2284 g_object_unref (account);
2285 return is_special?3:4;
2294 compare_account_names (TnyAccount *a1, TnyAccount *a2)
2296 const gchar *a1_name, *a2_name;
2298 a1_name = tny_account_get_name (a1);
2299 a2_name = tny_account_get_name (a2);
2301 return modest_text_utils_utf8_strcmp (a1_name, a2_name, TRUE);
2305 compare_accounts (TnyFolderStore *s1, TnyFolderStore *s2)
2307 TnyAccount *a1 = NULL, *a2 = NULL;
2310 if (TNY_IS_ACCOUNT (s1)) {
2311 a1 = TNY_ACCOUNT (g_object_ref (s1));
2312 } else if (!TNY_IS_MERGE_FOLDER (s1)) {
2313 a1 = tny_folder_get_account (TNY_FOLDER (s1));
2316 if (TNY_IS_ACCOUNT (s2)) {
2317 a2 = TNY_ACCOUNT (g_object_ref (s2));
2318 } else if (!TNY_IS_MERGE_FOLDER (s2)) {
2319 a2 = tny_folder_get_account (TNY_FOLDER (s2));
2336 /* First we sort with the type of account */
2337 cmp = get_cmp_rows_type_pos (G_OBJECT (a1)) - get_cmp_rows_type_pos (G_OBJECT (a2));
2341 cmp = compare_account_names (a1, a2);
2345 g_object_unref (a1);
2347 g_object_unref (a2);
2353 compare_accounts_first (TnyFolderStore *s1, TnyFolderStore *s2)
2355 gint is_account1, is_account2;
2357 is_account1 = TNY_IS_ACCOUNT (s1)?1:0;
2358 is_account2 = TNY_IS_ACCOUNT (s2)?1:0;
2360 return is_account2 - is_account1;
2364 * This function orders the mail accounts according to these rules:
2365 * 1st - remote accounts
2366 * 2nd - local account
2370 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
2374 gchar *name1 = NULL;
2375 gchar *name2 = NULL;
2376 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2377 TnyFolderType type2 = TNY_FOLDER_TYPE_UNKNOWN;
2378 GObject *folder1 = NULL;
2379 GObject *folder2 = NULL;
2381 gtk_tree_model_get (tree_model, iter1,
2382 NAME_COLUMN, &name1,
2384 INSTANCE_COLUMN, &folder1,
2386 gtk_tree_model_get (tree_model, iter2,
2387 NAME_COLUMN, &name2,
2388 TYPE_COLUMN, &type2,
2389 INSTANCE_COLUMN, &folder2,
2392 /* Return if we get no folder. This could happen when folder
2393 operations are happening. The model is updated after the
2394 folder copy/move actually occurs, so there could be
2395 situations where the model to be drawn is not correct */
2396 if (!folder1 || !folder2)
2399 /* Sort by type. First the special folders, then the archives */
2400 cmp = get_cmp_pos (type, (TnyFolder *) folder1) - get_cmp_pos (type2, (TnyFolder *) folder2);
2404 /* Now we sort using the account of each folder */
2405 if (TNY_IS_FOLDER_STORE (folder1) &&
2406 TNY_IS_FOLDER_STORE (folder2)) {
2407 cmp = compare_accounts (TNY_FOLDER_STORE (folder1), TNY_FOLDER_STORE (folder2));
2411 /* Each group is preceeded by its account */
2412 cmp = compare_accounts_first (TNY_FOLDER_STORE (folder1), TNY_FOLDER_STORE (folder2));
2417 /* Pure sort by name */
2418 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
2421 g_object_unref(G_OBJECT(folder1));
2423 g_object_unref(G_OBJECT(folder2));
2431 /*****************************************************************************/
2432 /* DRAG and DROP stuff */
2433 /*****************************************************************************/
2435 * This function fills the #GtkSelectionData with the row and the
2436 * model that has been dragged. It's called when this widget is a
2437 * source for dnd after the event drop happened
2440 on_drag_data_get (GtkWidget *widget, GdkDragContext *context, GtkSelectionData *selection_data,
2441 guint info, guint time, gpointer data)
2443 GtkTreeSelection *selection;
2444 GtkTreeModel *model;
2446 GtkTreePath *source_row;
2448 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
2449 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
2451 source_row = gtk_tree_model_get_path (model, &iter);
2452 gtk_tree_set_row_drag_data (selection_data,
2456 gtk_tree_path_free (source_row);
2460 typedef struct _DndHelper {
2461 ModestFolderView *folder_view;
2462 gboolean delete_source;
2463 GtkTreePath *source_row;
2467 dnd_helper_destroyer (DndHelper *helper)
2469 /* Free the helper */
2470 gtk_tree_path_free (helper->source_row);
2471 g_slice_free (DndHelper, helper);
2475 xfer_folder_cb (ModestMailOperation *mail_op,
2476 TnyFolder *new_folder,
2480 /* Select the folder */
2481 modest_folder_view_select_folder (MODEST_FOLDER_VIEW (user_data),
2487 /* get the folder for the row the treepath refers to. */
2488 /* folder must be unref'd */
2489 static TnyFolderStore *
2490 tree_path_to_folder (GtkTreeModel *model, GtkTreePath *path)
2493 TnyFolderStore *folder = NULL;
2495 if (gtk_tree_model_get_iter (model,&iter, path))
2496 gtk_tree_model_get (model, &iter,
2497 INSTANCE_COLUMN, &folder,
2504 * This function is used by drag_data_received_cb to manage drag and
2505 * drop of a header, i.e, and drag from the header view to the folder
2509 drag_and_drop_from_header_view (GtkTreeModel *source_model,
2510 GtkTreeModel *dest_model,
2511 GtkTreePath *dest_row,
2512 GtkSelectionData *selection_data)
2514 TnyList *headers = NULL;
2515 TnyFolder *folder = NULL, *src_folder = NULL;
2516 TnyFolderType folder_type;
2517 GtkTreeIter source_iter, dest_iter;
2518 ModestWindowMgr *mgr = NULL;
2519 ModestWindow *main_win = NULL;
2520 gchar **uris, **tmp;
2522 /* Build the list of headers */
2523 mgr = modest_runtime_get_window_mgr ();
2524 headers = tny_simple_list_new ();
2525 uris = modest_dnd_selection_data_get_paths (selection_data);
2528 while (*tmp != NULL) {
2531 gboolean first = TRUE;
2534 path = gtk_tree_path_new_from_string (*tmp);
2535 gtk_tree_model_get_iter (source_model, &source_iter, path);
2536 gtk_tree_model_get (source_model, &source_iter,
2537 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
2540 /* Do not enable d&d of headers already opened */
2541 if (!modest_window_mgr_find_registered_header(mgr, header, NULL))
2542 tny_list_append (headers, G_OBJECT (header));
2544 if (G_UNLIKELY (first)) {
2545 src_folder = tny_header_get_folder (header);
2549 /* Free and go on */
2550 gtk_tree_path_free (path);
2551 g_object_unref (header);
2556 /* This could happen ig we perform a d&d very quickly over the
2557 same row that row could dissapear because message is
2559 if (!TNY_IS_FOLDER (src_folder))
2562 /* Get the target folder */
2563 gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
2564 gtk_tree_model_get (dest_model, &dest_iter,
2568 if (!folder || !TNY_IS_FOLDER(folder)) {
2569 /* g_warning ("%s: not a valid target folder (%p)", __FUNCTION__, folder); */
2573 folder_type = modest_tny_folder_guess_folder_type (folder);
2574 if (folder_type == TNY_FOLDER_TYPE_INVALID) {
2575 /* g_warning ("%s: invalid target folder", __FUNCTION__); */
2576 goto cleanup; /* cannot move messages there */
2579 if (modest_tny_folder_get_rules((TNY_FOLDER(folder))) & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE) {
2580 /* g_warning ("folder not writable"); */
2581 goto cleanup; /* verboten! */
2584 /* Ask for confirmation to move */
2585 main_win = modest_window_mgr_get_main_window (mgr, FALSE); /* don't create */
2587 g_warning ("%s: BUG: no main window found", __FUNCTION__);
2591 /* Transfer messages */
2592 modest_ui_actions_transfer_messages_helper (GTK_WINDOW (main_win), src_folder,
2597 if (G_IS_OBJECT (src_folder))
2598 g_object_unref (src_folder);
2599 if (G_IS_OBJECT(folder))
2600 g_object_unref (G_OBJECT (folder));
2601 if (G_IS_OBJECT(headers))
2602 g_object_unref (headers);
2606 TnyFolderStore *src_folder;
2607 TnyFolderStore *dst_folder;
2608 ModestFolderView *folder_view;
2613 dnd_folder_info_destroyer (DndFolderInfo *info)
2615 if (info->src_folder)
2616 g_object_unref (info->src_folder);
2617 if (info->dst_folder)
2618 g_object_unref (info->dst_folder);
2619 g_slice_free (DndFolderInfo, info);
2623 dnd_on_connection_failed_destroyer (DndFolderInfo *info,
2624 GtkWindow *parent_window,
2625 TnyAccount *account)
2628 modest_ui_actions_on_account_connection_error (parent_window, account);
2630 /* Free the helper & info */
2631 dnd_helper_destroyer (info->helper);
2632 dnd_folder_info_destroyer (info);
2636 drag_and_drop_from_folder_view_src_folder_performer (gboolean canceled,
2638 GtkWindow *parent_window,
2639 TnyAccount *account,
2642 DndFolderInfo *info = NULL;
2643 ModestMailOperation *mail_op;
2645 info = (DndFolderInfo *) user_data;
2647 if (err || canceled) {
2648 dnd_on_connection_failed_destroyer (info, parent_window, account);
2652 /* Do the mail operation */
2653 mail_op = modest_mail_operation_new_with_error_handling ((GObject *) parent_window,
2654 modest_ui_actions_move_folder_error_handler,
2655 info->src_folder, NULL);
2657 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
2660 /* Transfer the folder */
2661 modest_mail_operation_xfer_folder (mail_op,
2662 TNY_FOLDER (info->src_folder),
2664 info->helper->delete_source,
2666 info->helper->folder_view);
2669 g_object_unref (G_OBJECT (mail_op));
2670 dnd_helper_destroyer (info->helper);
2671 dnd_folder_info_destroyer (info);
2676 drag_and_drop_from_folder_view_dst_folder_performer (gboolean canceled,
2678 GtkWindow *parent_window,
2679 TnyAccount *account,
2682 DndFolderInfo *info = NULL;
2684 info = (DndFolderInfo *) user_data;
2686 if (err || canceled) {
2687 dnd_on_connection_failed_destroyer (info, parent_window, account);
2691 /* Connect to source folder and perform the copy/move */
2692 modest_platform_connect_if_remote_and_perform (NULL, TRUE,
2694 drag_and_drop_from_folder_view_src_folder_performer,
2699 * This function is used by drag_data_received_cb to manage drag and
2700 * drop of a folder, i.e, and drag from the folder view to the same
2704 drag_and_drop_from_folder_view (GtkTreeModel *source_model,
2705 GtkTreeModel *dest_model,
2706 GtkTreePath *dest_row,
2707 GtkSelectionData *selection_data,
2710 GtkTreeIter dest_iter, iter;
2711 TnyFolderStore *dest_folder = NULL;
2712 TnyFolderStore *folder = NULL;
2713 gboolean forbidden = FALSE;
2715 DndFolderInfo *info = NULL;
2717 win = modest_window_mgr_get_main_window (modest_runtime_get_window_mgr(), FALSE); /* don't create */
2719 g_warning ("%s: BUG: no main window", __FUNCTION__);
2720 dnd_helper_destroyer (helper);
2725 /* check the folder rules for the destination */
2726 folder = tree_path_to_folder (dest_model, dest_row);
2727 if (TNY_IS_FOLDER(folder)) {
2728 ModestTnyFolderRules rules =
2729 modest_tny_folder_get_rules (TNY_FOLDER (folder));
2730 forbidden = rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE;
2731 } else if (TNY_IS_FOLDER_STORE(folder)) {
2732 /* enable local root as destination for folders */
2733 if (!MODEST_IS_TNY_LOCAL_FOLDERS_ACCOUNT (folder) &&
2734 !modest_tny_account_is_memory_card_account (TNY_ACCOUNT (folder)))
2737 g_object_unref (folder);
2740 /* check the folder rules for the source */
2741 folder = tree_path_to_folder (source_model, helper->source_row);
2742 if (TNY_IS_FOLDER(folder)) {
2743 ModestTnyFolderRules rules =
2744 modest_tny_folder_get_rules (TNY_FOLDER (folder));
2745 forbidden = rules & MODEST_FOLDER_RULES_FOLDER_NON_MOVEABLE;
2748 g_object_unref (folder);
2752 /* Check if the drag is possible */
2753 if (forbidden || !gtk_tree_path_compare (helper->source_row, dest_row)) {
2755 modest_platform_run_information_dialog ((GtkWindow *) win,
2756 _("mail_in_ui_folder_move_target_error"),
2758 /* Restore the previous selection */
2759 folder = tree_path_to_folder (source_model, helper->source_row);
2761 if (TNY_IS_FOLDER (folder))
2762 modest_folder_view_select_folder (helper->folder_view,
2763 TNY_FOLDER (folder), FALSE);
2764 g_object_unref (folder);
2766 dnd_helper_destroyer (helper);
2771 gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
2772 gtk_tree_model_get (dest_model, &dest_iter,
2775 gtk_tree_model_get_iter (source_model, &iter, helper->source_row);
2776 gtk_tree_model_get (source_model, &iter,
2780 /* Create the info for the performer */
2781 info = g_slice_new0 (DndFolderInfo);
2782 info->src_folder = g_object_ref (folder);
2783 info->dst_folder = g_object_ref (dest_folder);
2784 info->helper = helper;
2786 /* Connect to the destination folder and perform the copy/move */
2787 modest_platform_connect_if_remote_and_perform (GTK_WINDOW (win), TRUE,
2789 drag_and_drop_from_folder_view_dst_folder_performer,
2793 g_object_unref (dest_folder);
2794 g_object_unref (folder);
2798 * This function receives the data set by the "drag-data-get" signal
2799 * handler. This information comes within the #GtkSelectionData. This
2800 * function will manage both the drags of folders of the treeview and
2801 * drags of headers of the header view widget.
2804 on_drag_data_received (GtkWidget *widget,
2805 GdkDragContext *context,
2808 GtkSelectionData *selection_data,
2813 GtkWidget *source_widget;
2814 GtkTreeModel *dest_model, *source_model;
2815 GtkTreePath *source_row, *dest_row;
2816 GtkTreeViewDropPosition pos;
2817 gboolean delete_source = FALSE;
2818 gboolean success = FALSE;
2820 /* Do not allow further process */
2821 g_signal_stop_emission_by_name (widget, "drag-data-received");
2822 source_widget = gtk_drag_get_source_widget (context);
2824 /* Get the action */
2825 if (context->action == GDK_ACTION_MOVE) {
2826 delete_source = TRUE;
2828 /* Notify that there is no folder selected. We need to
2829 do this in order to update the headers view (and
2830 its monitors, because when moving, the old folder
2831 won't longer exist. We can not wait for the end of
2832 the operation, because the operation won't start if
2833 the folder is in use */
2834 if (source_widget == widget) {
2835 GtkTreeSelection *sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
2836 gtk_tree_selection_unselect_all (sel);
2840 /* Check if the get_data failed */
2841 if (selection_data == NULL || selection_data->length < 0)
2844 /* Select the destination model */
2845 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
2847 /* Get the path to the destination row. Can not call
2848 gtk_tree_view_get_drag_dest_row() because the source row
2849 is not selected anymore */
2850 gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget), x, y,
2853 /* Only allow drops IN other rows */
2855 pos == GTK_TREE_VIEW_DROP_BEFORE ||
2856 pos == GTK_TREE_VIEW_DROP_AFTER)
2860 /* Drags from the header view */
2861 if (source_widget != widget) {
2862 source_model = gtk_tree_view_get_model (GTK_TREE_VIEW (source_widget));
2864 drag_and_drop_from_header_view (source_model,
2869 DndHelper *helper = NULL;
2871 /* Get the source model and row */
2872 gtk_tree_get_row_drag_data (selection_data,
2876 /* Create the helper */
2877 helper = g_slice_new0 (DndHelper);
2878 helper->delete_source = delete_source;
2879 helper->source_row = gtk_tree_path_copy (source_row);
2880 helper->folder_view = MODEST_FOLDER_VIEW (widget);
2882 drag_and_drop_from_folder_view (source_model,
2888 gtk_tree_path_free (source_row);
2892 gtk_tree_path_free (dest_row);
2895 /* Finish the drag and drop */
2896 gtk_drag_finish (context, success, FALSE, time);
2900 * We define a "drag-drop" signal handler because we do not want to
2901 * use the default one, because the default one always calls
2902 * gtk_drag_finish and we prefer to do it in the "drag-data-received"
2903 * signal handler, because there we have all the information available
2904 * to know if the dnd was a success or not.
2907 drag_drop_cb (GtkWidget *widget,
2908 GdkDragContext *context,
2916 if (!context->targets)
2919 /* Check if we're dragging a folder row */
2920 target = gtk_drag_dest_find_target (widget, context, NULL);
2922 /* Request the data from the source. */
2923 gtk_drag_get_data(widget, context, target, time);
2929 * This function expands a node of a tree view if it's not expanded
2930 * yet. Not sure why it needs the threads stuff, but gtk+`example code
2931 * does that, so that's why they're here.
2934 expand_row_timeout (gpointer data)
2936 GtkTreeView *tree_view = data;
2937 GtkTreePath *dest_path = NULL;
2938 GtkTreeViewDropPosition pos;
2939 gboolean result = FALSE;
2941 gdk_threads_enter ();
2943 gtk_tree_view_get_drag_dest_row (tree_view,
2948 (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER ||
2949 pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)) {
2950 gtk_tree_view_expand_row (tree_view, dest_path, FALSE);
2951 gtk_tree_path_free (dest_path);
2955 gtk_tree_path_free (dest_path);
2960 gdk_threads_leave ();
2966 * This function is called whenever the pointer is moved over a widget
2967 * while dragging some data. It installs a timeout that will expand a
2968 * node of the treeview if not expanded yet. This function also calls
2969 * gdk_drag_status in order to set the suggested action that will be
2970 * used by the "drag-data-received" signal handler to know if we
2971 * should do a move or just a copy of the data.
2974 on_drag_motion (GtkWidget *widget,
2975 GdkDragContext *context,
2981 GtkTreeViewDropPosition pos;
2982 GtkTreePath *dest_row;
2983 GtkTreeModel *dest_model;
2984 ModestFolderViewPrivate *priv;
2985 GdkDragAction suggested_action;
2986 gboolean valid_location = FALSE;
2987 TnyFolderStore *folder = NULL;
2989 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
2991 if (priv->timer_expander != 0) {
2992 g_source_remove (priv->timer_expander);
2993 priv->timer_expander = 0;
2996 gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
3001 /* Do not allow drops between folders */
3003 pos == GTK_TREE_VIEW_DROP_BEFORE ||
3004 pos == GTK_TREE_VIEW_DROP_AFTER) {
3005 gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW (widget), NULL, 0);
3006 gdk_drag_status(context, 0, time);
3007 valid_location = FALSE;
3010 valid_location = TRUE;
3013 /* Check that the destination folder is writable */
3014 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
3015 folder = tree_path_to_folder (dest_model, dest_row);
3016 if (folder && TNY_IS_FOLDER (folder)) {
3017 ModestTnyFolderRules rules = modest_tny_folder_get_rules(TNY_FOLDER (folder));
3019 if (rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE) {
3020 valid_location = FALSE;
3025 /* Expand the selected row after 1/2 second */
3026 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), dest_row)) {
3027 priv->timer_expander = g_timeout_add (500, expand_row_timeout, widget);
3029 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), dest_row, pos);
3031 /* Select the desired action. By default we pick MOVE */
3032 suggested_action = GDK_ACTION_MOVE;
3034 if (context->actions == GDK_ACTION_COPY)
3035 gdk_drag_status(context, GDK_ACTION_COPY, time);
3036 else if (context->actions == GDK_ACTION_MOVE)
3037 gdk_drag_status(context, GDK_ACTION_MOVE, time);
3038 else if (context->actions & suggested_action)
3039 gdk_drag_status(context, suggested_action, time);
3041 gdk_drag_status(context, GDK_ACTION_DEFAULT, time);
3045 g_object_unref (folder);
3047 gtk_tree_path_free (dest_row);
3049 g_signal_stop_emission_by_name (widget, "drag-motion");
3051 return valid_location;
3055 * This function sets the treeview as a source and a target for dnd
3056 * events. It also connects all the requirede signals.
3059 setup_drag_and_drop (GtkTreeView *self)
3061 /* Set up the folder view as a dnd destination. Set only the
3062 highlight flag, otherwise gtk will have a different
3064 #ifdef MODEST_TOOLKIT_HILDON2
3067 gtk_drag_dest_set (GTK_WIDGET (self),
3068 GTK_DEST_DEFAULT_HIGHLIGHT,
3069 folder_view_drag_types,
3070 G_N_ELEMENTS (folder_view_drag_types),
3071 GDK_ACTION_MOVE | GDK_ACTION_COPY);
3073 g_signal_connect (G_OBJECT (self),
3074 "drag_data_received",
3075 G_CALLBACK (on_drag_data_received),
3079 /* Set up the treeview as a dnd source */
3080 gtk_drag_source_set (GTK_WIDGET (self),
3082 folder_view_drag_types,
3083 G_N_ELEMENTS (folder_view_drag_types),
3084 GDK_ACTION_MOVE | GDK_ACTION_COPY);
3086 g_signal_connect (G_OBJECT (self),
3088 G_CALLBACK (on_drag_motion),
3091 g_signal_connect (G_OBJECT (self),
3093 G_CALLBACK (on_drag_data_get),
3096 g_signal_connect (G_OBJECT (self),
3098 G_CALLBACK (drag_drop_cb),
3103 * This function manages the navigation through the folders using the
3104 * keyboard or the hardware keys in the device
3107 on_key_pressed (GtkWidget *self,
3111 GtkTreeSelection *selection;
3113 GtkTreeModel *model;
3114 gboolean retval = FALSE;
3116 /* Up and Down are automatically managed by the treeview */
3117 if (event->keyval == GDK_Return) {
3118 /* Expand/Collapse the selected row */
3119 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
3120 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
3123 path = gtk_tree_model_get_path (model, &iter);
3125 if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (self), path))
3126 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
3128 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
3129 gtk_tree_path_free (path);
3131 /* No further processing */
3139 * We listen to the changes in the local folder account name key,
3140 * because we want to show the right name in the view. The local
3141 * folder account name corresponds to the device name in the Maemo
3142 * version. We do this because we do not want to query gconf on each
3143 * tree view refresh. It's better to cache it and change whenever
3147 on_configuration_key_changed (ModestConf* conf,
3149 ModestConfEvent event,
3150 ModestConfNotificationId id,
3151 ModestFolderView *self)
3153 ModestFolderViewPrivate *priv;
3156 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
3157 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
3159 if (!strcmp (key, MODEST_CONF_DEVICE_NAME)) {
3160 g_free (priv->local_account_name);
3162 if (event == MODEST_CONF_EVENT_KEY_UNSET)
3163 priv->local_account_name = g_strdup (MODEST_LOCAL_FOLDERS_DEFAULT_DISPLAY_NAME);
3165 priv->local_account_name = modest_conf_get_string (modest_runtime_get_conf(),
3166 MODEST_CONF_DEVICE_NAME, NULL);
3168 /* Force a redraw */
3169 #if GTK_CHECK_VERSION(2, 8, 0)
3170 GtkTreeViewColumn * tree_column;
3172 tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self),
3174 gtk_tree_view_column_queue_resize (tree_column);
3176 gtk_widget_queue_draw (GTK_WIDGET (self));
3182 modest_folder_view_set_style (ModestFolderView *self,
3183 ModestFolderViewStyle style)
3185 ModestFolderViewPrivate *priv;
3187 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
3188 g_return_if_fail (style == MODEST_FOLDER_VIEW_STYLE_SHOW_ALL ||
3189 style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE);
3191 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
3194 priv->style = style;
3198 modest_folder_view_set_account_id_of_visible_server_account (ModestFolderView *self,
3199 const gchar *account_id)
3201 ModestFolderViewPrivate *priv;
3202 GtkTreeModel *model;
3204 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
3206 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
3208 /* This will be used by the filter_row callback,
3209 * to decided which rows to show: */
3210 if (priv->visible_account_id) {
3211 g_free (priv->visible_account_id);
3212 priv->visible_account_id = NULL;
3215 priv->visible_account_id = g_strdup (account_id);
3218 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
3219 if (GTK_IS_TREE_MODEL_FILTER (model))
3220 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
3222 /* Save settings to gconf */
3223 modest_widget_memory_save (modest_runtime_get_conf (), G_OBJECT(self),
3224 MODEST_CONF_FOLDER_VIEW_KEY);
3228 modest_folder_view_get_account_id_of_visible_server_account (ModestFolderView *self)
3230 ModestFolderViewPrivate *priv;
3232 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW(self), NULL);
3234 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
3236 return (const gchar *) priv->visible_account_id;
3240 find_inbox_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *inbox_iter)
3244 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
3246 gtk_tree_model_get (model, iter,
3250 gboolean result = FALSE;
3251 if (type == TNY_FOLDER_TYPE_INBOX) {
3255 *inbox_iter = *iter;
3259 if (gtk_tree_model_iter_children (model, &child, iter)) {
3260 if (find_inbox_iter (model, &child, inbox_iter))
3264 } while (gtk_tree_model_iter_next (model, iter));
3273 modest_folder_view_select_first_inbox_or_local (ModestFolderView *self)
3275 GtkTreeModel *model;
3276 GtkTreeIter iter, inbox_iter;
3277 GtkTreeSelection *sel;
3278 GtkTreePath *path = NULL;
3280 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
3282 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
3286 expand_root_items (self);
3287 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
3289 if (!gtk_tree_model_get_iter_first (model, &iter)) {
3290 g_warning ("%s: model is empty", __FUNCTION__);
3294 if (find_inbox_iter (model, &iter, &inbox_iter))
3295 path = gtk_tree_model_get_path (model, &inbox_iter);
3297 path = gtk_tree_path_new_first ();
3299 /* Select the row and free */
3300 gtk_tree_view_set_cursor (GTK_TREE_VIEW (self), path, NULL, FALSE);
3301 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (self), path, NULL, FALSE, 0.0, 0.0);
3302 gtk_tree_path_free (path);
3305 gtk_widget_grab_focus (GTK_WIDGET(self));
3311 find_folder_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *folder_iter,
3316 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
3317 TnyFolder* a_folder;
3320 gtk_tree_model_get (model, iter,
3321 INSTANCE_COLUMN, &a_folder,
3327 if (folder == a_folder) {
3328 g_object_unref (a_folder);
3329 *folder_iter = *iter;
3332 g_object_unref (a_folder);
3334 if (gtk_tree_model_iter_children (model, &child, iter)) {
3335 if (find_folder_iter (model, &child, folder_iter, folder))
3339 } while (gtk_tree_model_iter_next (model, iter));
3344 #ifndef MODEST_TOOLKIT_HILDON2
3346 on_row_inserted_maybe_select_folder (GtkTreeModel *tree_model,
3349 ModestFolderView *self)
3351 ModestFolderViewPrivate *priv = NULL;
3352 GtkTreeSelection *sel;
3353 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
3354 GObject *instance = NULL;
3356 if (!MODEST_IS_FOLDER_VIEW(self))
3359 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
3361 priv->reexpand = TRUE;
3363 gtk_tree_model_get (tree_model, iter,
3365 INSTANCE_COLUMN, &instance,
3371 if (type == TNY_FOLDER_TYPE_INBOX && priv->folder_to_select == NULL) {
3372 priv->folder_to_select = g_object_ref (instance);
3374 g_object_unref (instance);
3376 if (priv->folder_to_select) {
3378 if (!modest_folder_view_select_folder (self, priv->folder_to_select,
3381 path = gtk_tree_model_get_path (tree_model, iter);
3382 gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
3384 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
3386 gtk_tree_selection_select_iter (sel, iter);
3387 gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
3389 gtk_tree_path_free (path);
3393 modest_folder_view_disable_next_folder_selection (self);
3395 /* Refilter the model */
3396 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (tree_model));
3402 modest_folder_view_disable_next_folder_selection (ModestFolderView *self)
3404 ModestFolderViewPrivate *priv;
3406 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
3408 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
3410 if (priv->folder_to_select)
3411 g_object_unref(priv->folder_to_select);
3413 priv->folder_to_select = NULL;
3417 modest_folder_view_select_folder (ModestFolderView *self, TnyFolder *folder,
3418 gboolean after_change)
3420 GtkTreeModel *model;
3421 GtkTreeIter iter, folder_iter;
3422 GtkTreeSelection *sel;
3423 ModestFolderViewPrivate *priv = NULL;
3425 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW (self), FALSE);
3426 g_return_val_if_fail (folder && TNY_IS_FOLDER (folder), FALSE);
3428 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
3431 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
3432 gtk_tree_selection_unselect_all (sel);
3434 if (priv->folder_to_select)
3435 g_object_unref(priv->folder_to_select);
3436 priv->folder_to_select = TNY_FOLDER(g_object_ref(folder));
3440 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
3445 /* Refilter the model, before selecting the folder */
3446 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
3448 if (!gtk_tree_model_get_iter_first (model, &iter)) {
3449 g_warning ("%s: model is empty", __FUNCTION__);
3453 if (find_folder_iter (model, &iter, &folder_iter, folder)) {
3456 path = gtk_tree_model_get_path (model, &folder_iter);
3457 gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
3459 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
3460 gtk_tree_selection_select_iter (sel, &folder_iter);
3461 gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
3463 gtk_tree_path_free (path);
3471 modest_folder_view_copy_selection (ModestFolderView *self)
3473 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
3475 /* Copy selection */
3476 _clipboard_set_selected_data (self, FALSE);
3480 modest_folder_view_cut_selection (ModestFolderView *folder_view)
3482 ModestFolderViewPrivate *priv = NULL;
3483 GtkTreeModel *model = NULL;
3484 const gchar **hidding = NULL;
3485 guint i, n_selected;
3487 g_return_if_fail (folder_view && MODEST_IS_FOLDER_VIEW (folder_view));
3488 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
3490 /* Copy selection */
3491 if (!_clipboard_set_selected_data (folder_view, TRUE))
3494 /* Get hidding ids */
3495 hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected);
3497 /* Clear hidding array created by previous cut operation */
3498 _clear_hidding_filter (MODEST_FOLDER_VIEW (folder_view));
3500 /* Copy hidding array */
3501 priv->n_selected = n_selected;
3502 priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
3503 for (i=0; i < n_selected; i++)
3504 priv->hidding_ids[i] = g_strdup(hidding[i]);
3506 /* Hide cut folders */
3507 model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
3508 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
3512 modest_folder_view_copy_model (ModestFolderView *folder_view_src,
3513 ModestFolderView *folder_view_dst)
3515 GtkTreeModel *filter_model = NULL;
3516 GtkTreeModel *model = NULL;
3517 GtkTreeModel *new_filter_model = NULL;
3519 g_return_if_fail (folder_view_src && MODEST_IS_FOLDER_VIEW (folder_view_src));
3520 g_return_if_fail (folder_view_dst && MODEST_IS_FOLDER_VIEW (folder_view_dst));
3523 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view_src));
3524 model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER(filter_model));
3526 /* Build new filter model */
3527 new_filter_model = gtk_tree_model_filter_new (model, NULL);
3528 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (new_filter_model),
3532 /* Set copied model */
3533 gtk_tree_view_set_model (GTK_TREE_VIEW (folder_view_dst), new_filter_model);
3534 #ifndef MODEST_TOOLKIT_HILDON2
3535 g_signal_connect (G_OBJECT(new_filter_model), "row-inserted",
3536 (GCallback) on_row_inserted_maybe_select_folder, folder_view_dst);
3540 g_object_unref (new_filter_model);
3544 modest_folder_view_show_non_move_folders (ModestFolderView *folder_view,
3547 GtkTreeModel *model = NULL;
3548 ModestFolderViewPrivate* priv;
3550 g_return_if_fail (folder_view && MODEST_IS_FOLDER_VIEW (folder_view));
3552 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
3553 priv->show_non_move = show;
3554 /* modest_folder_view_update_model(folder_view, */
3555 /* TNY_ACCOUNT_STORE(modest_runtime_get_account_store())); */
3557 /* Hide special folders */
3558 model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
3559 if (GTK_IS_TREE_MODEL_FILTER (model)) {
3560 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
3564 /* Returns FALSE if it did not selected anything */
3566 _clipboard_set_selected_data (ModestFolderView *folder_view,
3569 ModestFolderViewPrivate *priv = NULL;
3570 TnyFolderStore *folder = NULL;
3571 gboolean retval = FALSE;
3573 g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (folder_view), FALSE);
3574 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
3576 /* Set selected data on clipboard */
3577 g_return_val_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard), FALSE);
3578 folder = modest_folder_view_get_selected (folder_view);
3580 /* Do not allow to select an account */
3581 if (TNY_IS_FOLDER (folder)) {
3582 modest_email_clipboard_set_data (priv->clipboard, TNY_FOLDER(folder), NULL, delete);
3587 g_object_unref (folder);
3593 _clear_hidding_filter (ModestFolderView *folder_view)
3595 ModestFolderViewPrivate *priv;
3598 g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view));
3599 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
3601 if (priv->hidding_ids != NULL) {
3602 for (i=0; i < priv->n_selected; i++)
3603 g_free (priv->hidding_ids[i]);
3604 g_free(priv->hidding_ids);
3610 on_display_name_changed (ModestAccountMgr *mgr,
3611 const gchar *account,
3614 ModestFolderView *self;
3616 self = MODEST_FOLDER_VIEW (user_data);
3618 /* Force a redraw */
3619 #if GTK_CHECK_VERSION(2, 8, 0)
3620 GtkTreeViewColumn * tree_column;
3622 tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self),
3624 gtk_tree_view_column_queue_resize (tree_column);
3626 gtk_widget_queue_draw (GTK_WIDGET (self));
3631 modest_folder_view_set_cell_style (ModestFolderView *self,
3632 ModestFolderViewCellStyle cell_style)
3634 ModestFolderViewPrivate *priv = NULL;
3636 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
3637 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
3639 priv->cell_style = cell_style;
3641 g_object_set (G_OBJECT (priv->messages_renderer),
3642 "visible", (cell_style == MODEST_FOLDER_VIEW_CELL_STYLE_COMPACT),
3645 gtk_widget_queue_draw (GTK_WIDGET (self));
3649 update_style (ModestFolderView *self)
3651 ModestFolderViewPrivate *priv;
3652 GdkColor style_color;
3653 PangoAttrList *attr_list;
3655 PangoAttribute *attr;
3657 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
3658 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
3662 attr_list = pango_attr_list_new ();
3663 if (!gtk_style_lookup_color (GTK_WIDGET (self)->style, "SecondaryTextColor", &style_color)) {
3664 gdk_color_parse ("grey", &style_color);
3666 attr = pango_attr_foreground_new (style_color.red, style_color.green, style_color.blue);
3667 pango_attr_list_insert (attr_list, attr);
3670 style = gtk_rc_get_style_by_paths (gtk_widget_get_settings
3672 "SmallSystemFont", NULL,
3675 attr = pango_attr_font_desc_new (pango_font_description_copy
3676 (style->font_desc));
3677 pango_attr_list_insert (attr_list, attr);
3679 g_object_set (G_OBJECT (priv->messages_renderer),
3680 "foreground-gdk", &style_color,
3681 "foreground-set", TRUE,
3682 "attributes", attr_list,
3684 pango_attr_list_unref (attr_list);
3689 on_notify_style (GObject *obj, GParamSpec *spec, gpointer userdata)
3691 if (strcmp ("style", spec->name) == 0) {
3692 update_style (MODEST_FOLDER_VIEW (obj));
3693 gtk_widget_queue_draw (GTK_WIDGET (obj));
3698 modest_folder_view_set_filter (ModestFolderView *self,
3699 ModestFolderViewFilter filter)
3701 ModestFolderViewPrivate *priv;
3702 GtkTreeModel *filter_model;
3704 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
3705 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
3707 priv->filter |= filter;
3709 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
3710 if (GTK_IS_TREE_MODEL_FILTER(filter_model)) {
3711 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
3716 modest_folder_view_unset_filter (ModestFolderView *self,
3717 ModestFolderViewFilter filter)
3719 ModestFolderViewPrivate *priv;
3720 GtkTreeModel *filter_model;
3722 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
3723 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
3725 priv->filter &= ~filter;
3727 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
3728 if (GTK_IS_TREE_MODEL_FILTER(filter_model)) {
3729 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
3734 modest_folder_view_any_folder_fulfils_rules (ModestFolderView *self,
3735 ModestTnyFolderRules rules)
3737 GtkTreeModel *filter_model;
3739 gboolean fulfil = FALSE;
3741 if (!get_inner_models (self, &filter_model, NULL, NULL))
3744 if (!gtk_tree_model_get_iter_first (filter_model, &iter))
3748 TnyFolderStore *folder;
3750 gtk_tree_model_get (filter_model, &iter, INSTANCE_COLUMN, &folder, -1);
3752 if (TNY_IS_FOLDER (folder)) {
3753 ModestTnyFolderRules folder_rules = modest_tny_folder_get_rules (TNY_FOLDER (folder));
3754 /* Folder rules are negative: non_writable, non_deletable... */
3755 if (!(folder_rules & rules))
3758 g_object_unref (folder);
3761 } while (gtk_tree_model_iter_next (filter_model, &iter) && !fulfil);
3767 modest_folder_view_set_list_to_move (ModestFolderView *self,
3770 ModestFolderViewPrivate *priv;
3772 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
3773 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
3775 if (priv->list_to_move)
3776 g_object_unref (priv->list_to_move);
3779 g_object_ref (list);
3781 priv->list_to_move = list;