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 gboolean reexpand; /* next time we expose, we'll expand all root folders */
240 GtkCellRenderer *messages_renderer;
242 gulong outbox_deleted_handler;
244 #define MODEST_FOLDER_VIEW_GET_PRIVATE(o) \
245 (G_TYPE_INSTANCE_GET_PRIVATE((o), \
246 MODEST_TYPE_FOLDER_VIEW, \
247 ModestFolderViewPrivate))
249 static GObjectClass *parent_class = NULL;
251 static guint signals[LAST_SIGNAL] = {0};
254 modest_folder_view_get_type (void)
256 static GType my_type = 0;
258 static const GTypeInfo my_info = {
259 sizeof(ModestFolderViewClass),
260 NULL, /* base init */
261 NULL, /* base finalize */
262 (GClassInitFunc) modest_folder_view_class_init,
263 NULL, /* class finalize */
264 NULL, /* class data */
265 sizeof(ModestFolderView),
267 (GInstanceInitFunc) modest_folder_view_init,
271 static const GInterfaceInfo tny_account_store_view_info = {
272 (GInterfaceInitFunc) tny_account_store_view_init, /* interface_init */
273 NULL, /* interface_finalize */
274 NULL /* interface_data */
278 my_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
282 g_type_add_interface_static (my_type,
283 TNY_TYPE_ACCOUNT_STORE_VIEW,
284 &tny_account_store_view_info);
290 modest_folder_view_class_init (ModestFolderViewClass *klass)
292 GObjectClass *gobject_class;
293 GtkTreeViewClass *treeview_class;
294 gobject_class = (GObjectClass*) klass;
295 treeview_class = (GtkTreeViewClass*) klass;
297 parent_class = g_type_class_peek_parent (klass);
298 gobject_class->finalize = modest_folder_view_finalize;
300 g_type_class_add_private (gobject_class,
301 sizeof(ModestFolderViewPrivate));
303 signals[FOLDER_SELECTION_CHANGED_SIGNAL] =
304 g_signal_new ("folder_selection_changed",
305 G_TYPE_FROM_CLASS (gobject_class),
307 G_STRUCT_OFFSET (ModestFolderViewClass,
308 folder_selection_changed),
310 modest_marshal_VOID__POINTER_BOOLEAN,
311 G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_BOOLEAN);
314 * This signal is emitted whenever the currently selected
315 * folder display name is computed. Note that the name could
316 * be different to the folder name, because we could append
317 * the unread messages count to the folder name to build the
318 * folder display name
320 signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL] =
321 g_signal_new ("folder-display-name-changed",
322 G_TYPE_FROM_CLASS (gobject_class),
324 G_STRUCT_OFFSET (ModestFolderViewClass,
325 folder_display_name_changed),
327 g_cclosure_marshal_VOID__STRING,
328 G_TYPE_NONE, 1, G_TYPE_STRING);
330 signals[FOLDER_ACTIVATED_SIGNAL] =
331 g_signal_new ("folder_activated",
332 G_TYPE_FROM_CLASS (gobject_class),
334 G_STRUCT_OFFSET (ModestFolderViewClass,
337 g_cclosure_marshal_VOID__POINTER,
338 G_TYPE_NONE, 1, G_TYPE_POINTER);
340 treeview_class->select_cursor_parent = NULL;
342 #ifdef MODEST_TOOLKIT_HILDON2
343 gtk_rc_parse_string ("class \"ModestFolderView\" style \"fremantle-touchlist\"");
349 /* Retrieves the filter, sort and tny models of the folder view. If
350 any of these does not exist then it returns FALSE */
352 get_inner_models (ModestFolderView *self,
353 GtkTreeModel **filter_model,
354 GtkTreeModel **sort_model,
355 GtkTreeModel **tny_model)
357 GtkTreeModel *s_model, *f_model, *t_model;
359 f_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
360 if (!GTK_IS_TREE_MODEL_FILTER(f_model)) {
361 g_warning ("BUG: %s: not a valid filter model", __FUNCTION__);
365 s_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (f_model));
366 if (!GTK_IS_TREE_MODEL_SORT(s_model)) {
367 g_warning ("BUG: %s: not a valid sort model", __FUNCTION__);
371 t_model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (s_model));
375 *filter_model = f_model;
377 *sort_model = s_model;
379 *tny_model = t_model;
384 /* Simplify checks for NULLs: */
386 strings_are_equal (const gchar *a, const gchar *b)
392 return (strcmp (a, b) == 0);
399 on_model_foreach_set_name(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
401 GObject *instance = NULL;
403 gtk_tree_model_get (model, iter,
404 INSTANCE_COLUMN, &instance,
408 return FALSE; /* keep walking */
410 if (!TNY_IS_ACCOUNT (instance)) {
411 g_object_unref (instance);
412 return FALSE; /* keep walking */
415 /* Check if this is the looked-for account: */
416 TnyAccount *this_account = TNY_ACCOUNT (instance);
417 TnyAccount *account = TNY_ACCOUNT (data);
419 const gchar *this_account_id = tny_account_get_id(this_account);
420 const gchar *account_id = tny_account_get_id(account);
421 g_object_unref (instance);
424 /* printf ("DEBUG: %s: this_account_id=%s, account_id=%s\n", __FUNCTION__, this_account_id, account_id); */
425 if (strings_are_equal(this_account_id, account_id)) {
426 /* Tell the model that the data has changed, so that
427 * it calls the cell_data_func callbacks again: */
428 /* TODO: This does not seem to actually cause the new string to be shown: */
429 gtk_tree_model_row_changed (model, path, iter);
431 return TRUE; /* stop walking */
434 return FALSE; /* keep walking */
439 ModestFolderView *self;
440 gchar *previous_name;
441 } GetMmcAccountNameData;
444 on_get_mmc_account_name (TnyStoreAccount* account, gpointer user_data)
446 /* printf ("DEBU1G: %s: account name=%s\n", __FUNCTION__, tny_account_get_name (TNY_ACCOUNT(account))); */
448 GetMmcAccountNameData *data = (GetMmcAccountNameData*)user_data;
450 if (!strings_are_equal (
451 tny_account_get_name(TNY_ACCOUNT(account)),
452 data->previous_name)) {
454 /* Tell the model that the data has changed, so that
455 * it calls the cell_data_func callbacks again: */
456 ModestFolderView *self = data->self;
457 GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
459 gtk_tree_model_foreach(model, on_model_foreach_set_name, account);
462 g_free (data->previous_name);
463 g_slice_free (GetMmcAccountNameData, data);
467 convert_parent_folders_to_dots (gchar **item_name)
471 gchar *last_separator;
473 if (item_name == NULL)
476 for (c = *item_name; *c != '\0'; c++) {
477 if (g_str_has_prefix (c, MODEST_FOLDER_PATH_SEPARATOR)) {
482 last_separator = g_strrstr (*item_name, MODEST_FOLDER_PATH_SEPARATOR);
483 if (last_separator != NULL) {
484 last_separator = last_separator + strlen (MODEST_FOLDER_PATH_SEPARATOR);
491 buffer = g_string_new ("");
492 for (i = 0; i < n_parents; i++) {
493 buffer = g_string_append (buffer, MODEST_FOLDER_DOT);
495 buffer = g_string_append (buffer, last_separator);
497 *item_name = g_string_free (buffer, FALSE);
503 format_compact_style (gchar **item_name,
506 gboolean multiaccount,
507 gboolean *use_markup)
511 TnyFolderType folder_type;
513 if (!TNY_IS_FOLDER (instance))
516 folder = (TnyFolder *) instance;
518 folder_type = tny_folder_get_folder_type (folder);
519 is_special = (get_cmp_pos (folder_type, folder)!= 4);
521 if (!is_special || multiaccount) {
522 TnyAccount *account = tny_folder_get_account (folder);
523 const gchar *folder_name;
524 gboolean concat_folder_name = FALSE;
527 /* Should not happen */
531 /* convert parent folders to dots */
532 convert_parent_folders_to_dots (item_name);
534 folder_name = tny_folder_get_name (folder);
535 if (g_str_has_suffix (*item_name, folder_name)) {
536 gchar *offset = g_strrstr (*item_name, folder_name);
538 concat_folder_name = TRUE;
541 buffer = g_string_new ("");
543 buffer = g_string_append (buffer, *item_name);
544 if (concat_folder_name) {
545 if (bold) buffer = g_string_append (buffer, "<span weight='bold'>");
546 buffer = g_string_append (buffer, folder_name);
547 if (bold) buffer = g_string_append (buffer, "</span>");
550 g_object_unref (account);
552 *item_name = g_string_free (buffer, FALSE);
560 text_cell_data (GtkTreeViewColumn *column,
561 GtkCellRenderer *renderer,
562 GtkTreeModel *tree_model,
566 ModestFolderViewPrivate *priv;
567 GObject *rendobj = (GObject *) renderer;
569 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
570 GObject *instance = NULL;
571 gboolean use_markup = FALSE;
573 gtk_tree_model_get (tree_model, iter,
576 INSTANCE_COLUMN, &instance,
578 if (!fname || !instance)
581 ModestFolderView *self = MODEST_FOLDER_VIEW (data);
582 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
584 gchar *item_name = NULL;
585 gint item_weight = 400;
587 if (type != TNY_FOLDER_TYPE_ROOT) {
591 if (modest_tny_folder_is_local_folder (TNY_FOLDER (instance)) ||
592 modest_tny_folder_is_memory_card_folder (TNY_FOLDER (instance))) {
593 type = modest_tny_folder_get_local_or_mmc_folder_type (TNY_FOLDER (instance));
594 if (type != TNY_FOLDER_TYPE_UNKNOWN) {
596 fname = g_strdup (modest_local_folder_info_get_type_display_name (type));
599 /* Sometimes an special folder is reported by the server as
600 NORMAL, like some versions of Dovecot */
601 if (type == TNY_FOLDER_TYPE_NORMAL ||
602 type == TNY_FOLDER_TYPE_UNKNOWN) {
603 type = modest_tny_folder_guess_folder_type (TNY_FOLDER (instance));
607 if (type == TNY_FOLDER_TYPE_INBOX) {
608 if (inbox_is_special (TNY_FOLDER_STORE (instance))) {
610 fname = g_strdup (_("mcen_me_folder_inbox"));
614 /* note: we cannot reliably get the counts from the tree model, we need
615 * to use explicit calls on tny_folder for some reason.
617 /* Select the number to show: the unread or unsent messages. in case of outbox/drafts, show all */
618 if ((type == TNY_FOLDER_TYPE_DRAFTS) ||
619 (type == TNY_FOLDER_TYPE_OUTBOX) ||
620 (type == TNY_FOLDER_TYPE_MERGE)) { /* _OUTBOX actually returns _MERGE... */
621 number = tny_folder_get_all_count (TNY_FOLDER(instance));
624 number = tny_folder_get_unread_count (TNY_FOLDER(instance));
628 if (priv->cell_style == MODEST_FOLDER_VIEW_CELL_STYLE_COMPACT) {
629 item_name = g_strdup (fname);
636 /* Use bold font style if there are unread or unset messages */
638 item_name = g_strdup_printf ("%s (%d)", fname, number);
641 item_name = g_strdup (fname);
646 } else if (TNY_IS_ACCOUNT (instance)) {
647 /* If it's a server account */
648 if (modest_tny_account_is_virtual_local_folders (TNY_ACCOUNT (instance))) {
649 item_name = g_strdup (priv->local_account_name);
651 } else if (modest_tny_account_is_memory_card_account (TNY_ACCOUNT (instance))) {
652 /* fname is only correct when the items are first
653 * added to the model, not when the account is
654 * changed later, so get the name from the account
656 item_name = g_strdup (tny_account_get_name (TNY_ACCOUNT (instance)));
659 item_name = g_strdup (fname);
665 item_name = g_strdup ("unknown");
667 if (priv->cell_style == MODEST_FOLDER_VIEW_CELL_STYLE_COMPACT) {
668 gboolean multiaccount;
670 multiaccount = (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ALL);
671 /* Convert item_name to markup */
672 format_compact_style (&item_name, instance,
674 multiaccount, &use_markup);
677 if (item_name && item_weight) {
678 /* Set the name in the treeview cell: */
680 g_object_set (rendobj, "markup", item_name, NULL);
682 g_object_set (rendobj, "text", item_name, "weight", item_weight, NULL);
684 /* Notify display name observers */
685 /* TODO: What listens for this signal, and how can it use only the new name? */
686 if (((GObject *) priv->cur_folder_store) == instance) {
687 g_signal_emit (G_OBJECT(self),
688 signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
695 /* If it is a Memory card account, make sure that we have the correct name.
696 * This function will be trigerred again when the name has been retrieved: */
697 if (TNY_IS_STORE_ACCOUNT (instance) &&
698 modest_tny_account_is_memory_card_account (TNY_ACCOUNT (instance))) {
700 /* Get the account name asynchronously: */
701 GetMmcAccountNameData *callback_data =
702 g_slice_new0(GetMmcAccountNameData);
703 callback_data->self = self;
705 const gchar *name = tny_account_get_name (TNY_ACCOUNT(instance));
707 callback_data->previous_name = g_strdup (name);
709 modest_tny_account_get_mmc_account_name (TNY_STORE_ACCOUNT (instance),
710 on_get_mmc_account_name, callback_data);
714 g_object_unref (G_OBJECT (instance));
720 messages_cell_data (GtkTreeViewColumn *column,
721 GtkCellRenderer *renderer,
722 GtkTreeModel *tree_model,
726 ModestFolderView *self;
727 ModestFolderViewPrivate *priv;
728 GObject *rendobj = (GObject *) renderer;
729 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
730 GObject *instance = NULL;
731 gchar *item_name = NULL;
733 gtk_tree_model_get (tree_model, iter,
735 INSTANCE_COLUMN, &instance,
740 self = MODEST_FOLDER_VIEW (data);
741 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
744 if (type != TNY_FOLDER_TYPE_ROOT) {
748 if (modest_tny_folder_is_local_folder (TNY_FOLDER (instance)) ||
749 modest_tny_folder_is_memory_card_folder (TNY_FOLDER (instance))) {
750 type = modest_tny_folder_get_local_or_mmc_folder_type (TNY_FOLDER (instance));
752 /* Sometimes an special folder is reported by the server as
753 NORMAL, like some versions of Dovecot */
754 if (type == TNY_FOLDER_TYPE_NORMAL ||
755 type == TNY_FOLDER_TYPE_UNKNOWN) {
756 type = modest_tny_folder_guess_folder_type (TNY_FOLDER (instance));
760 /* note: we cannot reliably get the counts from the tree model, we need
761 * to use explicit calls on tny_folder for some reason.
763 /* Select the number to show: the unread or unsent messages. in case of outbox/drafts, show all */
764 if ((type == TNY_FOLDER_TYPE_DRAFTS) ||
765 (type == TNY_FOLDER_TYPE_OUTBOX) ||
766 (type == TNY_FOLDER_TYPE_MERGE)) { /* _OUTBOX actually returns _MERGE... */
767 number = tny_folder_get_all_count (TNY_FOLDER(instance));
770 number = tny_folder_get_unread_count (TNY_FOLDER(instance));
774 if (priv->cell_style == MODEST_FOLDER_VIEW_CELL_STYLE_COMPACT) {
776 item_name = g_strdup_printf (drafts?_("mcen_ti_messages"):_("mcen_ti_new_messages"),
784 item_name = g_strdup ("");
787 /* Set the name in the treeview cell: */
788 g_object_set (rendobj,"text", item_name, NULL);
796 g_object_unref (G_OBJECT (instance));
802 GdkPixbuf *pixbuf_open;
803 GdkPixbuf *pixbuf_close;
807 static inline GdkPixbuf *
808 get_composite_pixbuf (const gchar *icon_name,
810 GdkPixbuf *base_pixbuf)
812 GdkPixbuf *emblem, *retval = NULL;
814 emblem = modest_platform_get_icon (icon_name, size);
816 retval = gdk_pixbuf_copy (base_pixbuf);
817 gdk_pixbuf_composite (emblem, retval, 0, 0,
818 MIN (gdk_pixbuf_get_width (emblem),
819 gdk_pixbuf_get_width (retval)),
820 MIN (gdk_pixbuf_get_height (emblem),
821 gdk_pixbuf_get_height (retval)),
822 0, 0, 1, 1, GDK_INTERP_NEAREST, 255);
823 g_object_unref (emblem);
828 static inline ThreePixbufs *
829 get_composite_icons (const gchar *icon_code,
831 GdkPixbuf **pixbuf_open,
832 GdkPixbuf **pixbuf_close)
834 ThreePixbufs *retval;
837 *pixbuf = gdk_pixbuf_copy (modest_platform_get_icon (icon_code, FOLDER_ICON_SIZE));
840 *pixbuf_open = get_composite_pixbuf ("qgn_list_gene_fldr_exp",
845 *pixbuf_close = get_composite_pixbuf ("qgn_list_gene_fldr_clp",
849 retval = g_slice_new0 (ThreePixbufs);
851 retval->pixbuf = g_object_ref (*pixbuf);
853 retval->pixbuf_open = g_object_ref (*pixbuf_open);
855 retval->pixbuf_close = g_object_ref (*pixbuf_close);
860 static inline ThreePixbufs*
861 get_folder_icons (TnyFolderType type, GObject *instance)
863 static GdkPixbuf *inbox_pixbuf = NULL, *outbox_pixbuf = NULL,
864 *junk_pixbuf = NULL, *sent_pixbuf = NULL,
865 *trash_pixbuf = NULL, *draft_pixbuf = NULL,
866 *normal_pixbuf = NULL, *anorm_pixbuf = NULL, *mmc_pixbuf = NULL,
867 *ammc_pixbuf = NULL, *avirt_pixbuf = NULL;
869 static GdkPixbuf *inbox_pixbuf_open = NULL, *outbox_pixbuf_open = NULL,
870 *junk_pixbuf_open = NULL, *sent_pixbuf_open = NULL,
871 *trash_pixbuf_open = NULL, *draft_pixbuf_open = NULL,
872 *normal_pixbuf_open = NULL, *anorm_pixbuf_open = NULL, *mmc_pixbuf_open = NULL,
873 *ammc_pixbuf_open = NULL, *avirt_pixbuf_open = NULL;
875 static GdkPixbuf *inbox_pixbuf_close = NULL, *outbox_pixbuf_close = NULL,
876 *junk_pixbuf_close = NULL, *sent_pixbuf_close = NULL,
877 *trash_pixbuf_close = NULL, *draft_pixbuf_close = NULL,
878 *normal_pixbuf_close = NULL, *anorm_pixbuf_close = NULL, *mmc_pixbuf_close = NULL,
879 *ammc_pixbuf_close = NULL, *avirt_pixbuf_close = NULL;
881 ThreePixbufs *retval = NULL;
883 /* Sometimes an special folder is reported by the server as
884 NORMAL, like some versions of Dovecot */
885 if (type == TNY_FOLDER_TYPE_NORMAL ||
886 type == TNY_FOLDER_TYPE_UNKNOWN) {
887 type = modest_tny_folder_guess_folder_type (TNY_FOLDER (instance));
890 /* Remote folders should not be treated as special folders */
891 if (TNY_IS_FOLDER_STORE (instance) &&
892 !TNY_IS_ACCOUNT (instance) &&
893 type != TNY_FOLDER_TYPE_INBOX &&
894 modest_tny_folder_store_is_remote (TNY_FOLDER_STORE (instance))) {
895 #ifdef MODEST_TOOLKIT_HILDON2
896 return get_composite_icons (MODEST_FOLDER_ICON_ACCOUNT,
899 &anorm_pixbuf_close);
901 return get_composite_icons (MODEST_FOLDER_ICON_NORMAL,
904 &normal_pixbuf_close);
910 case TNY_FOLDER_TYPE_INVALID:
911 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
914 case TNY_FOLDER_TYPE_ROOT:
915 if (TNY_IS_ACCOUNT (instance)) {
917 if (modest_tny_account_is_virtual_local_folders (TNY_ACCOUNT (instance))) {
918 retval = get_composite_icons (MODEST_FOLDER_ICON_LOCAL_FOLDERS,
921 &avirt_pixbuf_close);
923 const gchar *account_id = tny_account_get_id (TNY_ACCOUNT (instance));
925 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
926 retval = get_composite_icons (MODEST_FOLDER_ICON_MMC,
931 retval = get_composite_icons (MODEST_FOLDER_ICON_ACCOUNT,
934 &anorm_pixbuf_close);
939 case TNY_FOLDER_TYPE_INBOX:
940 retval = get_composite_icons (MODEST_FOLDER_ICON_INBOX,
943 &inbox_pixbuf_close);
945 case TNY_FOLDER_TYPE_OUTBOX:
946 retval = get_composite_icons (MODEST_FOLDER_ICON_OUTBOX,
949 &outbox_pixbuf_close);
951 case TNY_FOLDER_TYPE_JUNK:
952 retval = get_composite_icons (MODEST_FOLDER_ICON_JUNK,
957 case TNY_FOLDER_TYPE_SENT:
958 retval = get_composite_icons (MODEST_FOLDER_ICON_SENT,
963 case TNY_FOLDER_TYPE_TRASH:
964 retval = get_composite_icons (MODEST_FOLDER_ICON_TRASH,
967 &trash_pixbuf_close);
969 case TNY_FOLDER_TYPE_DRAFTS:
970 retval = get_composite_icons (MODEST_FOLDER_ICON_DRAFTS,
973 &draft_pixbuf_close);
975 case TNY_FOLDER_TYPE_ARCHIVE:
976 retval = get_composite_icons (MODEST_FOLDER_ICON_MMC_FOLDER,
981 case TNY_FOLDER_TYPE_NORMAL:
983 /* Memory card folders could have an special icon */
984 if (modest_tny_folder_is_memory_card_folder (TNY_FOLDER (instance))) {
985 retval = get_composite_icons (MODEST_FOLDER_ICON_MMC_FOLDER,
990 retval = get_composite_icons (MODEST_FOLDER_ICON_NORMAL,
993 &normal_pixbuf_close);
1002 free_pixbufs (ThreePixbufs *pixbufs)
1004 if (pixbufs->pixbuf)
1005 g_object_unref (pixbufs->pixbuf);
1006 if (pixbufs->pixbuf_open)
1007 g_object_unref (pixbufs->pixbuf_open);
1008 if (pixbufs->pixbuf_close)
1009 g_object_unref (pixbufs->pixbuf_close);
1010 g_slice_free (ThreePixbufs, pixbufs);
1014 icon_cell_data (GtkTreeViewColumn *column,
1015 GtkCellRenderer *renderer,
1016 GtkTreeModel *tree_model,
1020 GObject *rendobj = NULL, *instance = NULL;
1021 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1022 gboolean has_children;
1023 ThreePixbufs *pixbufs;
1025 rendobj = (GObject *) renderer;
1027 gtk_tree_model_get (tree_model, iter,
1029 INSTANCE_COLUMN, &instance,
1035 has_children = gtk_tree_model_iter_has_child (tree_model, iter);
1036 pixbufs = get_folder_icons (type, instance);
1037 g_object_unref (instance);
1040 g_object_set (rendobj, "pixbuf", pixbufs->pixbuf, NULL);
1043 g_object_set (rendobj, "pixbuf-expander-open", pixbufs->pixbuf_open, NULL);
1044 g_object_set (rendobj, "pixbuf-expander-closed", pixbufs->pixbuf_close, NULL);
1047 free_pixbufs (pixbufs);
1051 add_columns (GtkWidget *treeview)
1053 GtkTreeViewColumn *column;
1054 GtkCellRenderer *renderer;
1055 GtkTreeSelection *sel;
1056 ModestFolderViewPrivate *priv;
1058 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(treeview);
1061 column = gtk_tree_view_column_new ();
1063 /* Set icon and text render function */
1064 renderer = gtk_cell_renderer_pixbuf_new();
1065 gtk_tree_view_column_pack_start (column, renderer, FALSE);
1066 gtk_tree_view_column_set_cell_data_func(column, renderer,
1067 icon_cell_data, treeview, NULL);
1069 renderer = gtk_cell_renderer_text_new();
1070 g_object_set (renderer,
1071 #ifdef MODEST_TOOLKIT_HILDON2
1072 "ellipsize", PANGO_ELLIPSIZE_MIDDLE,
1074 "ellipsize", PANGO_ELLIPSIZE_END,
1076 "ellipsize-set", TRUE, NULL);
1077 gtk_tree_view_column_pack_start (column, renderer, TRUE);
1078 gtk_tree_view_column_set_cell_data_func(column, renderer,
1079 text_cell_data, treeview, NULL);
1081 priv->messages_renderer = gtk_cell_renderer_text_new ();
1082 g_object_set (priv->messages_renderer,
1083 "scale", PANGO_SCALE_X_SMALL,
1085 "alignment", PANGO_ALIGN_RIGHT,
1089 gtk_tree_view_column_pack_start (column, priv->messages_renderer, FALSE);
1090 gtk_tree_view_column_set_cell_data_func(column, priv->messages_renderer,
1091 messages_cell_data, treeview, NULL);
1093 /* Set selection mode */
1094 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(treeview));
1095 gtk_tree_selection_set_mode (sel, GTK_SELECTION_SINGLE);
1097 /* Set treeview appearance */
1098 gtk_tree_view_column_set_spacing (column, 2);
1099 gtk_tree_view_column_set_resizable (column, TRUE);
1100 gtk_tree_view_column_set_fixed_width (column, TRUE);
1101 gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(treeview), FALSE);
1102 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(treeview), FALSE);
1105 gtk_tree_view_append_column (GTK_TREE_VIEW(treeview),column);
1109 modest_folder_view_init (ModestFolderView *obj)
1111 ModestFolderViewPrivate *priv;
1114 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
1116 priv->timer_expander = 0;
1117 priv->account_store = NULL;
1119 priv->style = MODEST_FOLDER_VIEW_STYLE_SHOW_ALL;
1120 priv->cur_folder_store = NULL;
1121 priv->visible_account_id = NULL;
1122 priv->folder_to_select = NULL;
1123 priv->outbox_deleted_handler = 0;
1124 priv->reexpand = TRUE;
1126 /* Initialize the local account name */
1127 conf = modest_runtime_get_conf();
1128 priv->local_account_name = modest_conf_get_string (conf, MODEST_CONF_DEVICE_NAME, NULL);
1130 /* Init email clipboard */
1131 priv->clipboard = modest_runtime_get_email_clipboard ();
1132 priv->hidding_ids = NULL;
1133 priv->n_selected = 0;
1134 priv->filter = MODEST_FOLDER_VIEW_FILTER_NONE;
1135 priv->reselect = FALSE;
1136 priv->show_non_move = TRUE;
1138 /* Build treeview */
1139 add_columns (GTK_WIDGET (obj));
1141 /* Setup drag and drop */
1142 setup_drag_and_drop (GTK_TREE_VIEW(obj));
1144 /* Connect signals */
1145 g_signal_connect (G_OBJECT (obj),
1147 G_CALLBACK (on_key_pressed), NULL);
1149 priv->display_name_changed_signal =
1150 g_signal_connect (modest_runtime_get_account_mgr (),
1151 "display_name_changed",
1152 G_CALLBACK (on_display_name_changed),
1156 * Track changes in the local account name (in the device it
1157 * will be the device name)
1159 priv->conf_key_signal = g_signal_connect (G_OBJECT(conf),
1161 G_CALLBACK(on_configuration_key_changed),
1165 g_signal_connect (G_OBJECT (obj), "notify::style", G_CALLBACK (on_notify_style), (gpointer) obj);
1171 tny_account_store_view_init (gpointer g, gpointer iface_data)
1173 TnyAccountStoreViewIface *klass = (TnyAccountStoreViewIface *)g;
1175 klass->set_account_store = modest_folder_view_set_account_store;
1179 modest_folder_view_finalize (GObject *obj)
1181 ModestFolderViewPrivate *priv;
1182 GtkTreeSelection *sel;
1183 TnyAccount *local_account;
1185 g_return_if_fail (obj);
1187 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
1189 if (priv->timer_expander != 0) {
1190 g_source_remove (priv->timer_expander);
1191 priv->timer_expander = 0;
1194 local_account = (TnyAccount *)
1195 modest_tny_account_store_get_local_folders_account (modest_runtime_get_account_store ());
1196 if (local_account) {
1197 if (g_signal_handler_is_connected (local_account,
1198 priv->outbox_deleted_handler))
1199 g_signal_handler_disconnect (local_account,
1200 priv->outbox_deleted_handler);
1201 g_object_unref (local_account);
1204 if (priv->account_store) {
1205 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
1206 priv->account_inserted_signal);
1207 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
1208 priv->account_removed_signal);
1209 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
1210 priv->account_changed_signal);
1211 g_object_unref (G_OBJECT(priv->account_store));
1212 priv->account_store = NULL;
1215 if (g_signal_handler_is_connected (modest_runtime_get_account_mgr (),
1216 priv->display_name_changed_signal)) {
1217 g_signal_handler_disconnect (modest_runtime_get_account_mgr (),
1218 priv->display_name_changed_signal);
1219 priv->display_name_changed_signal = 0;
1223 g_object_unref (G_OBJECT (priv->query));
1227 if (priv->folder_to_select) {
1228 g_object_unref (G_OBJECT(priv->folder_to_select));
1229 priv->folder_to_select = NULL;
1232 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(obj));
1234 g_signal_handler_disconnect (G_OBJECT(sel), priv->changed_signal);
1236 g_free (priv->local_account_name);
1237 g_free (priv->visible_account_id);
1239 if (priv->conf_key_signal) {
1240 g_signal_handler_disconnect (modest_runtime_get_conf (),
1241 priv->conf_key_signal);
1242 priv->conf_key_signal = 0;
1245 if (priv->cur_folder_store) {
1246 g_object_unref (priv->cur_folder_store);
1247 priv->cur_folder_store = NULL;
1250 /* Clear hidding array created by cut operation */
1251 _clear_hidding_filter (MODEST_FOLDER_VIEW (obj));
1253 G_OBJECT_CLASS(parent_class)->finalize (obj);
1258 modest_folder_view_set_account_store (TnyAccountStoreView *self, TnyAccountStore *account_store)
1260 ModestFolderViewPrivate *priv;
1263 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
1264 g_return_if_fail (TNY_IS_ACCOUNT_STORE (account_store));
1266 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1267 device = tny_account_store_get_device (account_store);
1269 if (G_UNLIKELY (priv->account_store)) {
1271 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
1272 priv->account_inserted_signal))
1273 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
1274 priv->account_inserted_signal);
1275 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
1276 priv->account_removed_signal))
1277 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
1278 priv->account_removed_signal);
1279 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
1280 priv->account_changed_signal))
1281 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
1282 priv->account_changed_signal);
1283 g_object_unref (G_OBJECT (priv->account_store));
1286 priv->account_store = g_object_ref (G_OBJECT (account_store));
1288 priv->account_removed_signal =
1289 g_signal_connect (G_OBJECT(account_store), "account_removed",
1290 G_CALLBACK (on_account_removed), self);
1292 priv->account_inserted_signal =
1293 g_signal_connect (G_OBJECT(account_store), "account_inserted",
1294 G_CALLBACK (on_account_inserted), self);
1296 priv->account_changed_signal =
1297 g_signal_connect (G_OBJECT(account_store), "account_changed",
1298 G_CALLBACK (on_account_changed), self);
1300 modest_folder_view_update_model (MODEST_FOLDER_VIEW (self), account_store);
1301 priv->reselect = FALSE;
1302 modest_folder_view_select_first_inbox_or_local (MODEST_FOLDER_VIEW (self));
1304 g_object_unref (G_OBJECT (device));
1308 on_outbox_deleted_cb (ModestTnyLocalFoldersAccount *local_account,
1311 ModestFolderView *self;
1312 GtkTreeModel *model, *filter_model;
1315 self = MODEST_FOLDER_VIEW (user_data);
1317 if (!get_inner_models (self, &filter_model, NULL, &model))
1320 /* Remove outbox from model */
1321 outbox = modest_tny_local_folders_account_get_merged_outbox (local_account);
1322 tny_list_remove (TNY_LIST (model), G_OBJECT (outbox));
1323 g_object_unref (outbox);
1326 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1330 on_account_inserted (TnyAccountStore *account_store,
1331 TnyAccount *account,
1334 ModestFolderViewPrivate *priv;
1335 GtkTreeModel *model, *filter_model;
1337 /* Ignore transport account insertions, we're not showing them
1338 in the folder view */
1339 if (TNY_IS_TRANSPORT_ACCOUNT (account))
1342 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (user_data);
1345 /* If we're adding a new account, and there is no previous
1346 one, we need to select the visible server account */
1347 if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE &&
1348 !priv->visible_account_id)
1349 modest_widget_memory_restore (modest_runtime_get_conf(),
1350 G_OBJECT (user_data),
1351 MODEST_CONF_FOLDER_VIEW_KEY);
1355 if (!get_inner_models (MODEST_FOLDER_VIEW (user_data),
1356 &filter_model, NULL, &model))
1359 /* Insert the account in the model */
1360 tny_list_append (TNY_LIST (model), G_OBJECT (account));
1362 /* When the model is a list store (plain representation) the
1363 outbox is not a child of any account so we have to manually
1364 delete it because removing the local folders account won't
1365 delete it (because tny_folder_get_account() is not defined
1366 for a merge folder */
1367 if (TNY_IS_GTK_FOLDER_LIST_STORE (model) &&
1368 MODEST_IS_TNY_LOCAL_FOLDERS_ACCOUNT (account)) {
1370 priv->outbox_deleted_handler =
1371 g_signal_connect (account,
1373 G_CALLBACK (on_outbox_deleted_cb),
1377 /* Refilter the model */
1378 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1383 same_account_selected (ModestFolderView *self,
1384 TnyAccount *account)
1386 ModestFolderViewPrivate *priv;
1387 gboolean same_account = FALSE;
1389 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1391 if (priv->cur_folder_store) {
1392 TnyAccount *selected_folder_account = NULL;
1394 if (TNY_IS_FOLDER (priv->cur_folder_store)) {
1395 selected_folder_account =
1396 modest_tny_folder_get_account (TNY_FOLDER (priv->cur_folder_store));
1398 selected_folder_account =
1399 TNY_ACCOUNT (g_object_ref (priv->cur_folder_store));
1402 if (selected_folder_account == account)
1403 same_account = TRUE;
1405 g_object_unref (selected_folder_account);
1407 return same_account;
1412 * Selects the first inbox or the local account in an idle
1415 on_idle_select_first_inbox_or_local (gpointer user_data)
1417 ModestFolderView *self = MODEST_FOLDER_VIEW (user_data);
1419 gdk_threads_enter ();
1420 modest_folder_view_select_first_inbox_or_local (self);
1421 gdk_threads_leave ();
1427 on_account_changed (TnyAccountStore *account_store,
1428 TnyAccount *tny_account,
1431 ModestFolderView *self;
1432 ModestFolderViewPrivate *priv;
1433 GtkTreeModel *model, *filter_model;
1434 GtkTreeSelection *sel;
1435 gboolean same_account;
1437 /* Ignore transport account insertions, we're not showing them
1438 in the folder view */
1439 if (TNY_IS_TRANSPORT_ACCOUNT (tny_account))
1442 self = MODEST_FOLDER_VIEW (user_data);
1443 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (user_data);
1445 /* Get the inner model */
1446 if (!get_inner_models (MODEST_FOLDER_VIEW (user_data),
1447 &filter_model, NULL, &model))
1450 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (user_data));
1452 /* Invalidate the cur_folder_store only if the selected folder
1453 belongs to the account that is being removed */
1454 same_account = same_account_selected (self, tny_account);
1456 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1457 gtk_tree_selection_unselect_all (sel);
1460 /* Remove the account from the model */
1461 tny_list_remove (TNY_LIST (model), G_OBJECT (tny_account));
1463 /* Insert the account in the model */
1464 tny_list_append (TNY_LIST (model), G_OBJECT (tny_account));
1466 /* Refilter the model */
1467 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1469 /* Select the first INBOX if the currently selected folder
1470 belongs to the account that is being deleted */
1471 if (same_account && !MODEST_IS_TNY_LOCAL_FOLDERS_ACCOUNT (tny_account))
1472 g_idle_add (on_idle_select_first_inbox_or_local, self);
1476 on_account_removed (TnyAccountStore *account_store,
1477 TnyAccount *account,
1480 ModestFolderView *self = NULL;
1481 ModestFolderViewPrivate *priv;
1482 GtkTreeModel *model, *filter_model;
1483 GtkTreeSelection *sel = NULL;
1484 gboolean same_account = FALSE;
1486 /* Ignore transport account removals, we're not showing them
1487 in the folder view */
1488 if (TNY_IS_TRANSPORT_ACCOUNT (account))
1491 if (!MODEST_IS_FOLDER_VIEW(user_data)) {
1492 g_warning ("BUG: %s: not a valid folder view", __FUNCTION__);
1496 self = MODEST_FOLDER_VIEW (user_data);
1497 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1499 /* Invalidate the cur_folder_store only if the selected folder
1500 belongs to the account that is being removed */
1501 same_account = same_account_selected (self, account);
1503 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1504 gtk_tree_selection_unselect_all (sel);
1507 /* Invalidate row to select only if the folder to select
1508 belongs to the account that is being removed*/
1509 if (priv->folder_to_select) {
1510 TnyAccount *folder_to_select_account = NULL;
1512 folder_to_select_account = tny_folder_get_account (priv->folder_to_select);
1513 if (folder_to_select_account == account) {
1514 modest_folder_view_disable_next_folder_selection (self);
1515 g_object_unref (priv->folder_to_select);
1516 priv->folder_to_select = NULL;
1518 g_object_unref (folder_to_select_account);
1521 if (!get_inner_models (MODEST_FOLDER_VIEW (user_data),
1522 &filter_model, NULL, &model))
1525 /* Disconnect the signal handler */
1526 if (TNY_IS_GTK_FOLDER_LIST_STORE (model) &&
1527 MODEST_IS_TNY_LOCAL_FOLDERS_ACCOUNT (account)) {
1528 if (g_signal_handler_is_connected (account,
1529 priv->outbox_deleted_handler))
1530 g_signal_handler_disconnect (account,
1531 priv->outbox_deleted_handler);
1534 /* Remove the account from the model */
1535 tny_list_remove (TNY_LIST (model), G_OBJECT (account));
1537 /* If the removed account is the currently viewed one then
1538 clear the configuration value. The new visible account will be the default account */
1539 if (priv->visible_account_id &&
1540 !strcmp (priv->visible_account_id, tny_account_get_id (account))) {
1542 /* Clear the current visible account_id */
1543 modest_folder_view_set_account_id_of_visible_server_account (self, NULL);
1545 /* Call the restore method, this will set the new visible account */
1546 modest_widget_memory_restore (modest_runtime_get_conf(), G_OBJECT(self),
1547 MODEST_CONF_FOLDER_VIEW_KEY);
1550 /* Refilter the model */
1551 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1553 /* Select the first INBOX if the currently selected folder
1554 belongs to the account that is being deleted */
1556 g_idle_add (on_idle_select_first_inbox_or_local, self);
1560 modest_folder_view_set_title (ModestFolderView *self, const gchar *title)
1562 GtkTreeViewColumn *col;
1564 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
1566 col = gtk_tree_view_get_column (GTK_TREE_VIEW(self), 0);
1568 g_printerr ("modest: failed get column for title\n");
1572 gtk_tree_view_column_set_title (col, title);
1573 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self),
1578 modest_folder_view_on_map (ModestFolderView *self,
1579 GdkEventExpose *event,
1582 ModestFolderViewPrivate *priv;
1584 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1586 /* This won't happen often */
1587 if (G_UNLIKELY (priv->reselect)) {
1588 /* Select the first inbox or the local account if not found */
1590 /* TODO: this could cause a lock at startup, so we
1591 comment it for the moment. We know that this will
1592 be a bug, because the INBOX is not selected, but we
1593 need to rewrite some parts of Modest to avoid the
1594 deathlock situation */
1595 /* TODO: check if this is still the case */
1596 priv->reselect = FALSE;
1597 modest_folder_view_select_first_inbox_or_local (self);
1598 /* Notify the display name observers */
1599 g_signal_emit (G_OBJECT(self),
1600 signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
1604 if (priv->reexpand) {
1605 expand_root_items (self);
1606 priv->reexpand = FALSE;
1613 modest_folder_view_new (TnyFolderStoreQuery *query)
1616 ModestFolderViewPrivate *priv;
1617 GtkTreeSelection *sel;
1619 self = G_OBJECT (g_object_new (MODEST_TYPE_FOLDER_VIEW,
1620 #ifdef MODEST_TOOLKIT_HILDON2
1621 "hildon-ui-mode", HILDON_UI_MODE_NORMAL,
1624 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1627 priv->query = g_object_ref (query);
1629 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1630 priv->changed_signal = g_signal_connect (sel, "changed",
1631 G_CALLBACK (on_selection_changed), self);
1633 g_signal_connect (self, "row-activated", G_CALLBACK (on_row_activated), self);
1635 g_signal_connect (self, "expose-event", G_CALLBACK (modest_folder_view_on_map), NULL);
1637 return GTK_WIDGET(self);
1640 /* this feels dirty; any other way to expand all the root items? */
1642 expand_root_items (ModestFolderView *self)
1645 GtkTreeModel *model;
1648 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1649 path = gtk_tree_path_new_first ();
1651 /* all folders should have child items, so.. */
1653 gtk_tree_view_expand_row (GTK_TREE_VIEW(self), path, FALSE);
1654 gtk_tree_path_next (path);
1655 } while (gtk_tree_model_get_iter (model, &iter, path));
1657 gtk_tree_path_free (path);
1661 * We use this function to implement the
1662 * MODEST_FOLDER_VIEW_STYLE_SHOW_ONE style. We only show the default
1663 * account in this case, and the local folders.
1666 filter_row (GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
1668 ModestFolderViewPrivate *priv;
1669 gboolean retval = TRUE;
1670 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1671 GObject *instance = NULL;
1672 const gchar *id = NULL;
1674 gboolean found = FALSE;
1675 gboolean cleared = FALSE;
1676 ModestTnyFolderRules rules = 0;
1678 g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (data), FALSE);
1679 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (data);
1681 gtk_tree_model_get (model, iter,
1683 INSTANCE_COLUMN, &instance,
1686 /* Do not show if there is no instance, this could indeed
1687 happen when the model is being modified while it's being
1688 drawn. This could occur for example when moving folders
1693 if (TNY_IS_ACCOUNT (instance)) {
1694 TnyAccount *acc = TNY_ACCOUNT (instance);
1695 const gchar *account_id = tny_account_get_id (acc);
1697 /* If it isn't a special folder,
1698 * don't show it unless it is the visible account: */
1699 if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE &&
1700 !modest_tny_account_is_virtual_local_folders (acc) &&
1701 strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
1703 /* Show only the visible account id */
1704 if (priv->visible_account_id) {
1705 if (strcmp (account_id, priv->visible_account_id))
1712 /* Never show these to the user. They are merged into one folder
1713 * in the local-folders account instead: */
1714 if (retval && MODEST_IS_TNY_OUTBOX_ACCOUNT (acc))
1717 if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE) {
1718 /* Only show special folders for current account if needed */
1719 if (TNY_IS_FOLDER (instance) && !TNY_IS_MERGE_FOLDER (instance)) {
1720 TnyAccount *account;
1722 account = tny_folder_get_account (TNY_FOLDER (instance));
1724 if (TNY_IS_ACCOUNT (account)) {
1725 const gchar *account_id = tny_account_get_id (account);
1727 if (!modest_tny_account_is_virtual_local_folders (account) &&
1728 strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
1729 /* Show only the visible account id */
1730 if (priv->visible_account_id) {
1731 if (strcmp (account_id, priv->visible_account_id))
1735 g_object_unref (account);
1742 /* Check hiding (if necessary) */
1743 cleared = modest_email_clipboard_cleared (priv->clipboard);
1744 if ((retval) && (!cleared) && (TNY_IS_FOLDER (instance))) {
1745 id = tny_folder_get_id (TNY_FOLDER(instance));
1746 if (priv->hidding_ids != NULL)
1747 for (i=0; i < priv->n_selected && !found; i++)
1748 if (priv->hidding_ids[i] != NULL && id != NULL)
1749 found = (!strcmp (priv->hidding_ids[i], id));
1754 /* If this is a move to dialog, hide Sent, Outbox and Drafts
1755 folder as no message can be move there according to UI specs */
1756 if (!priv->show_non_move) {
1757 if (TNY_IS_FOLDER (instance) &&
1758 modest_tny_folder_is_local_folder (TNY_FOLDER (instance))) {
1760 case TNY_FOLDER_TYPE_OUTBOX:
1761 case TNY_FOLDER_TYPE_SENT:
1762 case TNY_FOLDER_TYPE_DRAFTS:
1765 case TNY_FOLDER_TYPE_UNKNOWN:
1766 case TNY_FOLDER_TYPE_NORMAL:
1767 type = modest_tny_folder_guess_folder_type(TNY_FOLDER(instance));
1768 if (type == TNY_FOLDER_TYPE_INVALID)
1769 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1771 if (type == TNY_FOLDER_TYPE_OUTBOX ||
1772 type == TNY_FOLDER_TYPE_SENT
1773 || type == TNY_FOLDER_TYPE_DRAFTS)
1782 /* apply special filters */
1783 if (retval && (priv->filter & MODEST_FOLDER_VIEW_FILTER_HIDE_ACCOUNTS)) {
1784 if (TNY_IS_ACCOUNT (instance))
1788 if (retval && (priv->filter & MODEST_FOLDER_VIEW_FILTER_CAN_HAVE_FOLDERS)) {
1789 if (TNY_IS_FOLDER (instance)) {
1790 /* Check folder rules */
1791 ModestTnyFolderRules rules;
1793 rules = modest_tny_folder_get_rules (TNY_FOLDER (instance));
1794 retval = !(rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE);
1795 } else if (TNY_IS_ACCOUNT (instance)) {
1796 if (modest_tny_folder_store_is_remote (TNY_FOLDER_STORE (instance))) {
1804 if (retval && (priv->filter & MODEST_FOLDER_VIEW_FILTER_HIDE_MANDATORY_FOLDERS)) {
1805 if (TNY_IS_FOLDER (instance)) {
1806 TnyFolderType guess_type;
1808 if (TNY_FOLDER_TYPE_NORMAL) {
1809 guess_type = modest_tny_folder_guess_folder_type (TNY_FOLDER (instance));
1815 case TNY_FOLDER_TYPE_OUTBOX:
1816 case TNY_FOLDER_TYPE_SENT:
1817 case TNY_FOLDER_TYPE_DRAFTS:
1818 case TNY_FOLDER_TYPE_ARCHIVE:
1819 case TNY_FOLDER_TYPE_INBOX:
1822 case TNY_FOLDER_TYPE_UNKNOWN:
1823 case TNY_FOLDER_TYPE_NORMAL:
1829 } else if (TNY_IS_ACCOUNT (instance)) {
1834 if (retval && TNY_IS_FOLDER (instance)) {
1835 rules = modest_tny_folder_get_rules (TNY_FOLDER (instance));
1838 if (retval && (priv->filter & MODEST_FOLDER_VIEW_FILTER_DELETABLE)) {
1839 if (TNY_IS_FOLDER (instance)) {
1840 retval = !(rules & MODEST_FOLDER_RULES_FOLDER_NON_DELETABLE);
1841 } else if (TNY_IS_ACCOUNT (instance)) {
1846 if (retval && (priv->filter & MODEST_FOLDER_VIEW_FILTER_RENAMEABLE)) {
1847 if (TNY_IS_FOLDER (instance)) {
1848 retval = !(rules & MODEST_FOLDER_RULES_FOLDER_NON_RENAMEABLE);
1849 } else if (TNY_IS_ACCOUNT (instance)) {
1854 if (retval && (priv->filter & MODEST_FOLDER_VIEW_FILTER_MOVEABLE)) {
1855 if (TNY_IS_FOLDER (instance)) {
1856 retval = !(rules & MODEST_FOLDER_RULES_FOLDER_NON_MOVEABLE);
1857 } else if (TNY_IS_ACCOUNT (instance)) {
1863 g_object_unref (instance);
1870 modest_folder_view_update_model (ModestFolderView *self,
1871 TnyAccountStore *account_store)
1873 ModestFolderViewPrivate *priv;
1874 GtkTreeModel *model /* , *old_model */;
1875 GtkTreeModel *filter_model = NULL, *sortable = NULL;
1877 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW (self), FALSE);
1878 g_return_val_if_fail (account_store && MODEST_IS_TNY_ACCOUNT_STORE(account_store),
1881 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1883 /* Notify that there is no folder selected */
1884 g_signal_emit (G_OBJECT(self),
1885 signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
1887 if (priv->cur_folder_store) {
1888 g_object_unref (priv->cur_folder_store);
1889 priv->cur_folder_store = NULL;
1892 /* FIXME: the local accounts are not shown when the query
1893 selects only the subscribed folders */
1894 #ifdef MODEST_TOOLKIT_HILDON2
1895 model = tny_gtk_folder_list_store_new_with_flags (NULL,
1896 TNY_GTK_FOLDER_LIST_STORE_FLAG_SHOW_PATH);
1897 tny_gtk_folder_list_store_set_path_separator (TNY_GTK_FOLDER_LIST_STORE (model),
1898 MODEST_FOLDER_PATH_SEPARATOR);
1900 model = tny_gtk_folder_store_tree_model_new (NULL);
1903 /* When the model is a list store (plain representation) the
1904 outbox is not a child of any account so we have to manually
1905 delete it because removing the local folders account won't
1906 delete it (because tny_folder_get_account() is not defined
1907 for a merge folder */
1908 if (TNY_IS_GTK_FOLDER_LIST_STORE (model)) {
1909 TnyAccount *account;
1910 ModestTnyAccountStore *acc_store;
1912 acc_store = modest_runtime_get_account_store ();
1913 account = modest_tny_account_store_get_local_folders_account (acc_store);
1915 if (g_signal_handler_is_connected (account,
1916 priv->outbox_deleted_handler))
1917 g_signal_handler_disconnect (account,
1918 priv->outbox_deleted_handler);
1920 priv->outbox_deleted_handler =
1921 g_signal_connect (account,
1923 G_CALLBACK (on_outbox_deleted_cb),
1925 g_object_unref (account);
1928 /* Get the accounts: */
1929 tny_account_store_get_accounts (TNY_ACCOUNT_STORE(account_store),
1931 TNY_ACCOUNT_STORE_STORE_ACCOUNTS);
1933 sortable = gtk_tree_model_sort_new_with_model (model);
1934 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
1936 GTK_SORT_ASCENDING);
1937 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
1939 cmp_rows, NULL, NULL);
1941 /* Create filter model */
1942 filter_model = gtk_tree_model_filter_new (sortable, NULL);
1943 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
1949 gtk_tree_view_set_model (GTK_TREE_VIEW(self), filter_model);
1950 #ifndef MODEST_TOOLKIT_HILDON2
1951 g_signal_connect (G_OBJECT(filter_model), "row-inserted",
1952 (GCallback) on_row_inserted_maybe_select_folder, self);
1955 g_object_unref (model);
1956 g_object_unref (filter_model);
1957 g_object_unref (sortable);
1959 /* Force a reselection of the INBOX next time the widget is shown */
1960 priv->reselect = TRUE;
1967 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
1969 GtkTreeModel *model = NULL;
1970 TnyFolderStore *folder = NULL;
1972 ModestFolderView *tree_view = NULL;
1973 ModestFolderViewPrivate *priv = NULL;
1974 gboolean selected = FALSE;
1976 g_return_if_fail (sel);
1977 g_return_if_fail (user_data);
1979 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
1981 selected = gtk_tree_selection_get_selected (sel, &model, &iter);
1983 tree_view = MODEST_FOLDER_VIEW (user_data);
1986 gtk_tree_model_get (model, &iter,
1987 INSTANCE_COLUMN, &folder,
1990 /* If the folder is the same do not notify */
1991 if (folder && priv->cur_folder_store == folder) {
1992 g_object_unref (folder);
1997 /* Current folder was unselected */
1998 if (priv->cur_folder_store) {
1999 /* We must do this firstly because a libtinymail-camel
2000 implementation detail. If we issue the signal
2001 before doing the sync_async, then that signal could
2002 cause (and it actually does it) a free of the
2003 summary of the folder (because the main window will
2004 clear the headers view */
2005 if (TNY_IS_FOLDER(priv->cur_folder_store))
2006 tny_folder_sync_async (TNY_FOLDER(priv->cur_folder_store),
2007 FALSE, NULL, NULL, NULL);
2009 g_signal_emit (G_OBJECT(tree_view), signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
2010 priv->cur_folder_store, FALSE);
2012 g_object_unref (priv->cur_folder_store);
2013 priv->cur_folder_store = NULL;
2016 /* New current references */
2017 priv->cur_folder_store = folder;
2019 /* New folder has been selected. Do not notify if there is
2020 nothing new selected */
2022 g_signal_emit (G_OBJECT(tree_view),
2023 signals[FOLDER_SELECTION_CHANGED_SIGNAL],
2024 0, priv->cur_folder_store, TRUE);
2029 on_row_activated (GtkTreeView *treeview,
2030 GtkTreePath *treepath,
2031 GtkTreeViewColumn *column,
2034 GtkTreeModel *model = NULL;
2035 TnyFolderStore *folder = NULL;
2037 ModestFolderView *self = NULL;
2038 ModestFolderViewPrivate *priv = NULL;
2040 g_return_if_fail (treeview);
2041 g_return_if_fail (user_data);
2043 self = MODEST_FOLDER_VIEW (user_data);
2044 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
2046 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2048 if (!gtk_tree_model_get_iter (model, &iter, treepath))
2051 gtk_tree_model_get (model, &iter,
2052 INSTANCE_COLUMN, &folder,
2055 g_signal_emit (G_OBJECT(self),
2056 signals[FOLDER_ACTIVATED_SIGNAL],
2059 #ifdef MODEST_TOOLKIT_HILDON2
2060 HildonUIMode ui_mode;
2061 g_object_get (G_OBJECT (self), "hildon-ui-mode", &ui_mode, NULL);
2062 if (ui_mode == HILDON_UI_MODE_NORMAL) {
2063 if (priv->cur_folder_store)
2064 g_object_unref (priv->cur_folder_store);
2065 priv->cur_folder_store = g_object_ref (folder);
2069 g_object_unref (folder);
2073 modest_folder_view_get_selected (ModestFolderView *self)
2075 ModestFolderViewPrivate *priv;
2077 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW(self), NULL);
2079 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2080 if (priv->cur_folder_store)
2081 g_object_ref (priv->cur_folder_store);
2083 return priv->cur_folder_store;
2087 get_cmp_rows_type_pos (GObject *folder)
2089 /* Remote accounts -> Local account -> MMC account .*/
2092 if (TNY_IS_ACCOUNT (folder) &&
2093 modest_tny_account_is_virtual_local_folders (
2094 TNY_ACCOUNT (folder))) {
2096 } else if (TNY_IS_ACCOUNT (folder)) {
2097 TnyAccount *account = TNY_ACCOUNT (folder);
2098 const gchar *account_id = tny_account_get_id (account);
2099 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
2105 printf ("DEBUG: %s: unexpected type.\n", __FUNCTION__);
2106 return -1; /* Should never happen */
2111 inbox_is_special (TnyFolderStore *folder_store)
2113 gboolean is_special = TRUE;
2115 if (TNY_IS_FOLDER (folder_store)) {
2119 gchar *last_inbox_bar;
2121 id = tny_folder_get_id (TNY_FOLDER (folder_store));
2122 downcase = g_utf8_strdown (id, -1);
2123 last_bar = g_strrstr (downcase, "/");
2125 last_inbox_bar = g_strrstr (downcase, "inbox/");
2126 if ((last_inbox_bar == NULL) || (last_inbox_bar + 5 != last_bar))
2135 get_cmp_pos (TnyFolderType t, TnyFolder *folder_store)
2137 TnyAccount *account;
2138 gboolean is_special;
2139 /* Inbox, Outbox, Drafts, Sent, User */
2142 if (!TNY_IS_FOLDER (folder_store))
2145 case TNY_FOLDER_TYPE_INBOX:
2147 account = tny_folder_get_account (folder_store);
2148 is_special = (get_cmp_rows_type_pos (G_OBJECT (account)) == 0);
2150 /* In inbox case we need to know if the inbox is really the top
2151 * inbox of the account, or if it's a submailbox inbox. To do
2152 * this we'll apply an heuristic rule: Find last "/" and check
2153 * if it's preceeded by another Inbox */
2154 is_special = is_special && inbox_is_special (TNY_FOLDER_STORE (folder_store));
2155 g_object_unref (account);
2156 return is_special?0:4;
2159 case TNY_FOLDER_TYPE_OUTBOX:
2160 return (TNY_IS_MERGE_FOLDER (folder_store))?2:4;
2162 case TNY_FOLDER_TYPE_DRAFTS:
2164 account = tny_folder_get_account (folder_store);
2165 is_special = (get_cmp_rows_type_pos (G_OBJECT (account)) == 1);
2166 g_object_unref (account);
2167 return is_special?1:4;
2170 case TNY_FOLDER_TYPE_SENT:
2172 account = tny_folder_get_account (folder_store);
2173 is_special = (get_cmp_rows_type_pos (G_OBJECT (account)) == 1);
2174 g_object_unref (account);
2175 return is_special?3:4;
2184 compare_account_names (TnyAccount *a1, TnyAccount *a2)
2186 const gchar *a1_name, *a2_name;
2188 a1_name = tny_account_get_name (a1);
2189 a2_name = tny_account_get_name (a2);
2191 return modest_text_utils_utf8_strcmp (a1_name, a2_name, TRUE);
2195 compare_accounts (TnyFolderStore *s1, TnyFolderStore *s2)
2197 TnyAccount *a1 = NULL, *a2 = NULL;
2200 if (TNY_IS_ACCOUNT (s1)) {
2201 a1 = TNY_ACCOUNT (g_object_ref (s1));
2202 } else if (!TNY_IS_MERGE_FOLDER (s1)) {
2203 a1 = tny_folder_get_account (TNY_FOLDER (s1));
2206 if (TNY_IS_ACCOUNT (s2)) {
2207 a2 = TNY_ACCOUNT (g_object_ref (s2));
2208 } else if (!TNY_IS_MERGE_FOLDER (s2)) {
2209 a2 = tny_folder_get_account (TNY_FOLDER (s2));
2226 /* First we sort with the type of account */
2227 cmp = get_cmp_rows_type_pos (G_OBJECT (a1)) - get_cmp_rows_type_pos (G_OBJECT (a2));
2231 cmp = compare_account_names (a1, a2);
2235 g_object_unref (a1);
2237 g_object_unref (a2);
2243 compare_accounts_first (TnyFolderStore *s1, TnyFolderStore *s2)
2245 gint is_account1, is_account2;
2247 is_account1 = TNY_IS_ACCOUNT (s1)?1:0;
2248 is_account2 = TNY_IS_ACCOUNT (s2)?1:0;
2250 return is_account2 - is_account1;
2254 * This function orders the mail accounts according to these rules:
2255 * 1st - remote accounts
2256 * 2nd - local account
2260 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
2264 gchar *name1 = NULL;
2265 gchar *name2 = NULL;
2266 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2267 TnyFolderType type2 = TNY_FOLDER_TYPE_UNKNOWN;
2268 GObject *folder1 = NULL;
2269 GObject *folder2 = NULL;
2271 gtk_tree_model_get (tree_model, iter1,
2272 NAME_COLUMN, &name1,
2274 INSTANCE_COLUMN, &folder1,
2276 gtk_tree_model_get (tree_model, iter2,
2277 NAME_COLUMN, &name2,
2278 TYPE_COLUMN, &type2,
2279 INSTANCE_COLUMN, &folder2,
2282 /* Return if we get no folder. This could happen when folder
2283 operations are happening. The model is updated after the
2284 folder copy/move actually occurs, so there could be
2285 situations where the model to be drawn is not correct */
2286 if (!folder1 || !folder2)
2289 /* Sort by type. First the special folders, then the archives */
2290 cmp = get_cmp_pos (type, (TnyFolder *) folder1) - get_cmp_pos (type2, (TnyFolder *) folder2);
2294 /* Now we sort using the account of each folder */
2295 if (TNY_IS_FOLDER_STORE (folder1) &&
2296 TNY_IS_FOLDER_STORE (folder2)) {
2297 cmp = compare_accounts (TNY_FOLDER_STORE (folder1), TNY_FOLDER_STORE (folder2));
2301 /* Each group is preceeded by its account */
2302 cmp = compare_accounts_first (TNY_FOLDER_STORE (folder1), TNY_FOLDER_STORE (folder2));
2307 /* Pure sort by name */
2308 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
2311 g_object_unref(G_OBJECT(folder1));
2313 g_object_unref(G_OBJECT(folder2));
2321 /*****************************************************************************/
2322 /* DRAG and DROP stuff */
2323 /*****************************************************************************/
2325 * This function fills the #GtkSelectionData with the row and the
2326 * model that has been dragged. It's called when this widget is a
2327 * source for dnd after the event drop happened
2330 on_drag_data_get (GtkWidget *widget, GdkDragContext *context, GtkSelectionData *selection_data,
2331 guint info, guint time, gpointer data)
2333 GtkTreeSelection *selection;
2334 GtkTreeModel *model;
2336 GtkTreePath *source_row;
2338 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
2339 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
2341 source_row = gtk_tree_model_get_path (model, &iter);
2342 gtk_tree_set_row_drag_data (selection_data,
2346 gtk_tree_path_free (source_row);
2350 typedef struct _DndHelper {
2351 ModestFolderView *folder_view;
2352 gboolean delete_source;
2353 GtkTreePath *source_row;
2357 dnd_helper_destroyer (DndHelper *helper)
2359 /* Free the helper */
2360 gtk_tree_path_free (helper->source_row);
2361 g_slice_free (DndHelper, helper);
2365 xfer_folder_cb (ModestMailOperation *mail_op,
2366 TnyFolder *new_folder,
2370 /* Select the folder */
2371 modest_folder_view_select_folder (MODEST_FOLDER_VIEW (user_data),
2377 /* get the folder for the row the treepath refers to. */
2378 /* folder must be unref'd */
2379 static TnyFolderStore *
2380 tree_path_to_folder (GtkTreeModel *model, GtkTreePath *path)
2383 TnyFolderStore *folder = NULL;
2385 if (gtk_tree_model_get_iter (model,&iter, path))
2386 gtk_tree_model_get (model, &iter,
2387 INSTANCE_COLUMN, &folder,
2394 * This function is used by drag_data_received_cb to manage drag and
2395 * drop of a header, i.e, and drag from the header view to the folder
2399 drag_and_drop_from_header_view (GtkTreeModel *source_model,
2400 GtkTreeModel *dest_model,
2401 GtkTreePath *dest_row,
2402 GtkSelectionData *selection_data)
2404 TnyList *headers = NULL;
2405 TnyFolder *folder = NULL, *src_folder = NULL;
2406 TnyFolderType folder_type;
2407 GtkTreeIter source_iter, dest_iter;
2408 ModestWindowMgr *mgr = NULL;
2409 ModestWindow *main_win = NULL;
2410 gchar **uris, **tmp;
2412 /* Build the list of headers */
2413 mgr = modest_runtime_get_window_mgr ();
2414 headers = tny_simple_list_new ();
2415 uris = modest_dnd_selection_data_get_paths (selection_data);
2418 while (*tmp != NULL) {
2421 gboolean first = TRUE;
2424 path = gtk_tree_path_new_from_string (*tmp);
2425 gtk_tree_model_get_iter (source_model, &source_iter, path);
2426 gtk_tree_model_get (source_model, &source_iter,
2427 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
2430 /* Do not enable d&d of headers already opened */
2431 if (!modest_window_mgr_find_registered_header(mgr, header, NULL))
2432 tny_list_append (headers, G_OBJECT (header));
2434 if (G_UNLIKELY (first)) {
2435 src_folder = tny_header_get_folder (header);
2439 /* Free and go on */
2440 gtk_tree_path_free (path);
2441 g_object_unref (header);
2446 /* This could happen ig we perform a d&d very quickly over the
2447 same row that row could dissapear because message is
2449 if (!TNY_IS_FOLDER (src_folder))
2452 /* Get the target folder */
2453 gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
2454 gtk_tree_model_get (dest_model, &dest_iter,
2458 if (!folder || !TNY_IS_FOLDER(folder)) {
2459 /* g_warning ("%s: not a valid target folder (%p)", __FUNCTION__, folder); */
2463 folder_type = modest_tny_folder_guess_folder_type (folder);
2464 if (folder_type == TNY_FOLDER_TYPE_INVALID) {
2465 /* g_warning ("%s: invalid target folder", __FUNCTION__); */
2466 goto cleanup; /* cannot move messages there */
2469 if (modest_tny_folder_get_rules((TNY_FOLDER(folder))) & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE) {
2470 /* g_warning ("folder not writable"); */
2471 goto cleanup; /* verboten! */
2474 /* Ask for confirmation to move */
2475 main_win = modest_window_mgr_get_main_window (mgr, FALSE); /* don't create */
2477 g_warning ("%s: BUG: no main window found", __FUNCTION__);
2481 /* Transfer messages */
2482 modest_ui_actions_transfer_messages_helper (GTK_WINDOW (main_win), src_folder,
2487 if (G_IS_OBJECT (src_folder))
2488 g_object_unref (src_folder);
2489 if (G_IS_OBJECT(folder))
2490 g_object_unref (G_OBJECT (folder));
2491 if (G_IS_OBJECT(headers))
2492 g_object_unref (headers);
2496 TnyFolderStore *src_folder;
2497 TnyFolderStore *dst_folder;
2498 ModestFolderView *folder_view;
2503 dnd_folder_info_destroyer (DndFolderInfo *info)
2505 if (info->src_folder)
2506 g_object_unref (info->src_folder);
2507 if (info->dst_folder)
2508 g_object_unref (info->dst_folder);
2509 g_slice_free (DndFolderInfo, info);
2513 dnd_on_connection_failed_destroyer (DndFolderInfo *info,
2514 GtkWindow *parent_window,
2515 TnyAccount *account)
2518 modest_ui_actions_on_account_connection_error (parent_window, account);
2520 /* Free the helper & info */
2521 dnd_helper_destroyer (info->helper);
2522 dnd_folder_info_destroyer (info);
2526 drag_and_drop_from_folder_view_src_folder_performer (gboolean canceled,
2528 GtkWindow *parent_window,
2529 TnyAccount *account,
2532 DndFolderInfo *info = NULL;
2533 ModestMailOperation *mail_op;
2535 info = (DndFolderInfo *) user_data;
2537 if (err || canceled) {
2538 dnd_on_connection_failed_destroyer (info, parent_window, account);
2542 /* Do the mail operation */
2543 mail_op = modest_mail_operation_new_with_error_handling ((GObject *) parent_window,
2544 modest_ui_actions_move_folder_error_handler,
2545 info->src_folder, NULL);
2547 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
2550 /* Transfer the folder */
2551 modest_mail_operation_xfer_folder (mail_op,
2552 TNY_FOLDER (info->src_folder),
2554 info->helper->delete_source,
2556 info->helper->folder_view);
2559 g_object_unref (G_OBJECT (mail_op));
2560 dnd_helper_destroyer (info->helper);
2561 dnd_folder_info_destroyer (info);
2566 drag_and_drop_from_folder_view_dst_folder_performer (gboolean canceled,
2568 GtkWindow *parent_window,
2569 TnyAccount *account,
2572 DndFolderInfo *info = NULL;
2574 info = (DndFolderInfo *) user_data;
2576 if (err || canceled) {
2577 dnd_on_connection_failed_destroyer (info, parent_window, account);
2581 /* Connect to source folder and perform the copy/move */
2582 modest_platform_connect_if_remote_and_perform (NULL, TRUE,
2584 drag_and_drop_from_folder_view_src_folder_performer,
2589 * This function is used by drag_data_received_cb to manage drag and
2590 * drop of a folder, i.e, and drag from the folder view to the same
2594 drag_and_drop_from_folder_view (GtkTreeModel *source_model,
2595 GtkTreeModel *dest_model,
2596 GtkTreePath *dest_row,
2597 GtkSelectionData *selection_data,
2600 GtkTreeIter dest_iter, iter;
2601 TnyFolderStore *dest_folder = NULL;
2602 TnyFolderStore *folder = NULL;
2603 gboolean forbidden = FALSE;
2605 DndFolderInfo *info = NULL;
2607 win = modest_window_mgr_get_main_window (modest_runtime_get_window_mgr(), FALSE); /* don't create */
2609 g_warning ("%s: BUG: no main window", __FUNCTION__);
2610 dnd_helper_destroyer (helper);
2615 /* check the folder rules for the destination */
2616 folder = tree_path_to_folder (dest_model, dest_row);
2617 if (TNY_IS_FOLDER(folder)) {
2618 ModestTnyFolderRules rules =
2619 modest_tny_folder_get_rules (TNY_FOLDER (folder));
2620 forbidden = rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE;
2621 } else if (TNY_IS_FOLDER_STORE(folder)) {
2622 /* enable local root as destination for folders */
2623 if (!MODEST_IS_TNY_LOCAL_FOLDERS_ACCOUNT (folder) &&
2624 !modest_tny_account_is_memory_card_account (TNY_ACCOUNT (folder)))
2627 g_object_unref (folder);
2630 /* check the folder rules for the source */
2631 folder = tree_path_to_folder (source_model, helper->source_row);
2632 if (TNY_IS_FOLDER(folder)) {
2633 ModestTnyFolderRules rules =
2634 modest_tny_folder_get_rules (TNY_FOLDER (folder));
2635 forbidden = rules & MODEST_FOLDER_RULES_FOLDER_NON_MOVEABLE;
2638 g_object_unref (folder);
2642 /* Check if the drag is possible */
2643 if (forbidden || !gtk_tree_path_compare (helper->source_row, dest_row)) {
2645 modest_platform_run_information_dialog ((GtkWindow *) win,
2646 _("mail_in_ui_folder_move_target_error"),
2648 /* Restore the previous selection */
2649 folder = tree_path_to_folder (source_model, helper->source_row);
2651 if (TNY_IS_FOLDER (folder))
2652 modest_folder_view_select_folder (helper->folder_view,
2653 TNY_FOLDER (folder), FALSE);
2654 g_object_unref (folder);
2656 dnd_helper_destroyer (helper);
2661 gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
2662 gtk_tree_model_get (dest_model, &dest_iter,
2665 gtk_tree_model_get_iter (source_model, &iter, helper->source_row);
2666 gtk_tree_model_get (source_model, &iter,
2670 /* Create the info for the performer */
2671 info = g_slice_new0 (DndFolderInfo);
2672 info->src_folder = g_object_ref (folder);
2673 info->dst_folder = g_object_ref (dest_folder);
2674 info->helper = helper;
2676 /* Connect to the destination folder and perform the copy/move */
2677 modest_platform_connect_if_remote_and_perform (GTK_WINDOW (win), TRUE,
2679 drag_and_drop_from_folder_view_dst_folder_performer,
2683 g_object_unref (dest_folder);
2684 g_object_unref (folder);
2688 * This function receives the data set by the "drag-data-get" signal
2689 * handler. This information comes within the #GtkSelectionData. This
2690 * function will manage both the drags of folders of the treeview and
2691 * drags of headers of the header view widget.
2694 on_drag_data_received (GtkWidget *widget,
2695 GdkDragContext *context,
2698 GtkSelectionData *selection_data,
2703 GtkWidget *source_widget;
2704 GtkTreeModel *dest_model, *source_model;
2705 GtkTreePath *source_row, *dest_row;
2706 GtkTreeViewDropPosition pos;
2707 gboolean delete_source = FALSE;
2708 gboolean success = FALSE;
2710 /* Do not allow further process */
2711 g_signal_stop_emission_by_name (widget, "drag-data-received");
2712 source_widget = gtk_drag_get_source_widget (context);
2714 /* Get the action */
2715 if (context->action == GDK_ACTION_MOVE) {
2716 delete_source = TRUE;
2718 /* Notify that there is no folder selected. We need to
2719 do this in order to update the headers view (and
2720 its monitors, because when moving, the old folder
2721 won't longer exist. We can not wait for the end of
2722 the operation, because the operation won't start if
2723 the folder is in use */
2724 if (source_widget == widget) {
2725 GtkTreeSelection *sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
2726 gtk_tree_selection_unselect_all (sel);
2730 /* Check if the get_data failed */
2731 if (selection_data == NULL || selection_data->length < 0)
2734 /* Select the destination model */
2735 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
2737 /* Get the path to the destination row. Can not call
2738 gtk_tree_view_get_drag_dest_row() because the source row
2739 is not selected anymore */
2740 gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget), x, y,
2743 /* Only allow drops IN other rows */
2745 pos == GTK_TREE_VIEW_DROP_BEFORE ||
2746 pos == GTK_TREE_VIEW_DROP_AFTER)
2750 /* Drags from the header view */
2751 if (source_widget != widget) {
2752 source_model = gtk_tree_view_get_model (GTK_TREE_VIEW (source_widget));
2754 drag_and_drop_from_header_view (source_model,
2759 DndHelper *helper = NULL;
2761 /* Get the source model and row */
2762 gtk_tree_get_row_drag_data (selection_data,
2766 /* Create the helper */
2767 helper = g_slice_new0 (DndHelper);
2768 helper->delete_source = delete_source;
2769 helper->source_row = gtk_tree_path_copy (source_row);
2770 helper->folder_view = MODEST_FOLDER_VIEW (widget);
2772 drag_and_drop_from_folder_view (source_model,
2778 gtk_tree_path_free (source_row);
2782 gtk_tree_path_free (dest_row);
2785 /* Finish the drag and drop */
2786 gtk_drag_finish (context, success, FALSE, time);
2790 * We define a "drag-drop" signal handler because we do not want to
2791 * use the default one, because the default one always calls
2792 * gtk_drag_finish and we prefer to do it in the "drag-data-received"
2793 * signal handler, because there we have all the information available
2794 * to know if the dnd was a success or not.
2797 drag_drop_cb (GtkWidget *widget,
2798 GdkDragContext *context,
2806 if (!context->targets)
2809 /* Check if we're dragging a folder row */
2810 target = gtk_drag_dest_find_target (widget, context, NULL);
2812 /* Request the data from the source. */
2813 gtk_drag_get_data(widget, context, target, time);
2819 * This function expands a node of a tree view if it's not expanded
2820 * yet. Not sure why it needs the threads stuff, but gtk+`example code
2821 * does that, so that's why they're here.
2824 expand_row_timeout (gpointer data)
2826 GtkTreeView *tree_view = data;
2827 GtkTreePath *dest_path = NULL;
2828 GtkTreeViewDropPosition pos;
2829 gboolean result = FALSE;
2831 gdk_threads_enter ();
2833 gtk_tree_view_get_drag_dest_row (tree_view,
2838 (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER ||
2839 pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)) {
2840 gtk_tree_view_expand_row (tree_view, dest_path, FALSE);
2841 gtk_tree_path_free (dest_path);
2845 gtk_tree_path_free (dest_path);
2850 gdk_threads_leave ();
2856 * This function is called whenever the pointer is moved over a widget
2857 * while dragging some data. It installs a timeout that will expand a
2858 * node of the treeview if not expanded yet. This function also calls
2859 * gdk_drag_status in order to set the suggested action that will be
2860 * used by the "drag-data-received" signal handler to know if we
2861 * should do a move or just a copy of the data.
2864 on_drag_motion (GtkWidget *widget,
2865 GdkDragContext *context,
2871 GtkTreeViewDropPosition pos;
2872 GtkTreePath *dest_row;
2873 GtkTreeModel *dest_model;
2874 ModestFolderViewPrivate *priv;
2875 GdkDragAction suggested_action;
2876 gboolean valid_location = FALSE;
2877 TnyFolderStore *folder = NULL;
2879 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
2881 if (priv->timer_expander != 0) {
2882 g_source_remove (priv->timer_expander);
2883 priv->timer_expander = 0;
2886 gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
2891 /* Do not allow drops between folders */
2893 pos == GTK_TREE_VIEW_DROP_BEFORE ||
2894 pos == GTK_TREE_VIEW_DROP_AFTER) {
2895 gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW (widget), NULL, 0);
2896 gdk_drag_status(context, 0, time);
2897 valid_location = FALSE;
2900 valid_location = TRUE;
2903 /* Check that the destination folder is writable */
2904 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
2905 folder = tree_path_to_folder (dest_model, dest_row);
2906 if (folder && TNY_IS_FOLDER (folder)) {
2907 ModestTnyFolderRules rules = modest_tny_folder_get_rules(TNY_FOLDER (folder));
2909 if (rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE) {
2910 valid_location = FALSE;
2915 /* Expand the selected row after 1/2 second */
2916 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), dest_row)) {
2917 priv->timer_expander = g_timeout_add (500, expand_row_timeout, widget);
2919 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), dest_row, pos);
2921 /* Select the desired action. By default we pick MOVE */
2922 suggested_action = GDK_ACTION_MOVE;
2924 if (context->actions == GDK_ACTION_COPY)
2925 gdk_drag_status(context, GDK_ACTION_COPY, time);
2926 else if (context->actions == GDK_ACTION_MOVE)
2927 gdk_drag_status(context, GDK_ACTION_MOVE, time);
2928 else if (context->actions & suggested_action)
2929 gdk_drag_status(context, suggested_action, time);
2931 gdk_drag_status(context, GDK_ACTION_DEFAULT, time);
2935 g_object_unref (folder);
2937 gtk_tree_path_free (dest_row);
2939 g_signal_stop_emission_by_name (widget, "drag-motion");
2941 return valid_location;
2945 * This function sets the treeview as a source and a target for dnd
2946 * events. It also connects all the requirede signals.
2949 setup_drag_and_drop (GtkTreeView *self)
2951 /* Set up the folder view as a dnd destination. Set only the
2952 highlight flag, otherwise gtk will have a different
2954 #ifdef MODEST_TOOLKIT_HILDON2
2957 gtk_drag_dest_set (GTK_WIDGET (self),
2958 GTK_DEST_DEFAULT_HIGHLIGHT,
2959 folder_view_drag_types,
2960 G_N_ELEMENTS (folder_view_drag_types),
2961 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2963 g_signal_connect (G_OBJECT (self),
2964 "drag_data_received",
2965 G_CALLBACK (on_drag_data_received),
2969 /* Set up the treeview as a dnd source */
2970 gtk_drag_source_set (GTK_WIDGET (self),
2972 folder_view_drag_types,
2973 G_N_ELEMENTS (folder_view_drag_types),
2974 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2976 g_signal_connect (G_OBJECT (self),
2978 G_CALLBACK (on_drag_motion),
2981 g_signal_connect (G_OBJECT (self),
2983 G_CALLBACK (on_drag_data_get),
2986 g_signal_connect (G_OBJECT (self),
2988 G_CALLBACK (drag_drop_cb),
2993 * This function manages the navigation through the folders using the
2994 * keyboard or the hardware keys in the device
2997 on_key_pressed (GtkWidget *self,
3001 GtkTreeSelection *selection;
3003 GtkTreeModel *model;
3004 gboolean retval = FALSE;
3006 /* Up and Down are automatically managed by the treeview */
3007 if (event->keyval == GDK_Return) {
3008 /* Expand/Collapse the selected row */
3009 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
3010 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
3013 path = gtk_tree_model_get_path (model, &iter);
3015 if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (self), path))
3016 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
3018 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
3019 gtk_tree_path_free (path);
3021 /* No further processing */
3029 * We listen to the changes in the local folder account name key,
3030 * because we want to show the right name in the view. The local
3031 * folder account name corresponds to the device name in the Maemo
3032 * version. We do this because we do not want to query gconf on each
3033 * tree view refresh. It's better to cache it and change whenever
3037 on_configuration_key_changed (ModestConf* conf,
3039 ModestConfEvent event,
3040 ModestConfNotificationId id,
3041 ModestFolderView *self)
3043 ModestFolderViewPrivate *priv;
3046 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
3047 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
3049 if (!strcmp (key, MODEST_CONF_DEVICE_NAME)) {
3050 g_free (priv->local_account_name);
3052 if (event == MODEST_CONF_EVENT_KEY_UNSET)
3053 priv->local_account_name = g_strdup (MODEST_LOCAL_FOLDERS_DEFAULT_DISPLAY_NAME);
3055 priv->local_account_name = modest_conf_get_string (modest_runtime_get_conf(),
3056 MODEST_CONF_DEVICE_NAME, NULL);
3058 /* Force a redraw */
3059 #if GTK_CHECK_VERSION(2, 8, 0)
3060 GtkTreeViewColumn * tree_column;
3062 tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self),
3064 gtk_tree_view_column_queue_resize (tree_column);
3066 gtk_widget_queue_draw (GTK_WIDGET (self));
3072 modest_folder_view_set_style (ModestFolderView *self,
3073 ModestFolderViewStyle style)
3075 ModestFolderViewPrivate *priv;
3077 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
3078 g_return_if_fail (style == MODEST_FOLDER_VIEW_STYLE_SHOW_ALL ||
3079 style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE);
3081 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
3084 priv->style = style;
3088 modest_folder_view_set_account_id_of_visible_server_account (ModestFolderView *self,
3089 const gchar *account_id)
3091 ModestFolderViewPrivate *priv;
3092 GtkTreeModel *model;
3094 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
3096 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
3098 /* This will be used by the filter_row callback,
3099 * to decided which rows to show: */
3100 if (priv->visible_account_id) {
3101 g_free (priv->visible_account_id);
3102 priv->visible_account_id = NULL;
3105 priv->visible_account_id = g_strdup (account_id);
3108 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
3109 if (GTK_IS_TREE_MODEL_FILTER (model))
3110 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
3112 /* Save settings to gconf */
3113 modest_widget_memory_save (modest_runtime_get_conf (), G_OBJECT(self),
3114 MODEST_CONF_FOLDER_VIEW_KEY);
3118 modest_folder_view_get_account_id_of_visible_server_account (ModestFolderView *self)
3120 ModestFolderViewPrivate *priv;
3122 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW(self), NULL);
3124 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
3126 return (const gchar *) priv->visible_account_id;
3130 find_inbox_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *inbox_iter)
3134 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
3136 gtk_tree_model_get (model, iter,
3140 gboolean result = FALSE;
3141 if (type == TNY_FOLDER_TYPE_INBOX) {
3145 *inbox_iter = *iter;
3149 if (gtk_tree_model_iter_children (model, &child, iter)) {
3150 if (find_inbox_iter (model, &child, inbox_iter))
3154 } while (gtk_tree_model_iter_next (model, iter));
3163 modest_folder_view_select_first_inbox_or_local (ModestFolderView *self)
3165 GtkTreeModel *model;
3166 GtkTreeIter iter, inbox_iter;
3167 GtkTreeSelection *sel;
3168 GtkTreePath *path = NULL;
3170 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
3172 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
3176 expand_root_items (self);
3177 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
3179 if (!gtk_tree_model_get_iter_first (model, &iter)) {
3180 g_warning ("%s: model is empty", __FUNCTION__);
3184 if (find_inbox_iter (model, &iter, &inbox_iter))
3185 path = gtk_tree_model_get_path (model, &inbox_iter);
3187 path = gtk_tree_path_new_first ();
3189 /* Select the row and free */
3190 gtk_tree_view_set_cursor (GTK_TREE_VIEW (self), path, NULL, FALSE);
3191 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (self), path, NULL, FALSE, 0.0, 0.0);
3192 gtk_tree_path_free (path);
3195 gtk_widget_grab_focus (GTK_WIDGET(self));
3201 find_folder_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *folder_iter,
3206 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
3207 TnyFolder* a_folder;
3210 gtk_tree_model_get (model, iter,
3211 INSTANCE_COLUMN, &a_folder,
3217 if (folder == a_folder) {
3218 g_object_unref (a_folder);
3219 *folder_iter = *iter;
3222 g_object_unref (a_folder);
3224 if (gtk_tree_model_iter_children (model, &child, iter)) {
3225 if (find_folder_iter (model, &child, folder_iter, folder))
3229 } while (gtk_tree_model_iter_next (model, iter));
3234 #ifndef MODEST_TOOLKIT_HILDON2
3236 on_row_inserted_maybe_select_folder (GtkTreeModel *tree_model,
3239 ModestFolderView *self)
3241 ModestFolderViewPrivate *priv = NULL;
3242 GtkTreeSelection *sel;
3243 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
3244 GObject *instance = NULL;
3246 if (!MODEST_IS_FOLDER_VIEW(self))
3249 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
3251 priv->reexpand = TRUE;
3253 gtk_tree_model_get (tree_model, iter,
3255 INSTANCE_COLUMN, &instance,
3261 if (type == TNY_FOLDER_TYPE_INBOX && priv->folder_to_select == NULL) {
3262 priv->folder_to_select = g_object_ref (instance);
3264 g_object_unref (instance);
3266 if (priv->folder_to_select) {
3268 if (!modest_folder_view_select_folder (self, priv->folder_to_select,
3271 path = gtk_tree_model_get_path (tree_model, iter);
3272 gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
3274 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
3276 gtk_tree_selection_select_iter (sel, iter);
3277 gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
3279 gtk_tree_path_free (path);
3283 modest_folder_view_disable_next_folder_selection (self);
3285 /* Refilter the model */
3286 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (tree_model));
3292 modest_folder_view_disable_next_folder_selection (ModestFolderView *self)
3294 ModestFolderViewPrivate *priv;
3296 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
3298 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
3300 if (priv->folder_to_select)
3301 g_object_unref(priv->folder_to_select);
3303 priv->folder_to_select = NULL;
3307 modest_folder_view_select_folder (ModestFolderView *self, TnyFolder *folder,
3308 gboolean after_change)
3310 GtkTreeModel *model;
3311 GtkTreeIter iter, folder_iter;
3312 GtkTreeSelection *sel;
3313 ModestFolderViewPrivate *priv = NULL;
3315 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW (self), FALSE);
3316 g_return_val_if_fail (folder && TNY_IS_FOLDER (folder), FALSE);
3318 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
3321 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
3322 gtk_tree_selection_unselect_all (sel);
3324 if (priv->folder_to_select)
3325 g_object_unref(priv->folder_to_select);
3326 priv->folder_to_select = TNY_FOLDER(g_object_ref(folder));
3330 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
3335 /* Refilter the model, before selecting the folder */
3336 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
3338 if (!gtk_tree_model_get_iter_first (model, &iter)) {
3339 g_warning ("%s: model is empty", __FUNCTION__);
3343 if (find_folder_iter (model, &iter, &folder_iter, folder)) {
3346 path = gtk_tree_model_get_path (model, &folder_iter);
3347 gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
3349 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
3350 gtk_tree_selection_select_iter (sel, &folder_iter);
3351 gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
3353 gtk_tree_path_free (path);
3361 modest_folder_view_copy_selection (ModestFolderView *self)
3363 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
3365 /* Copy selection */
3366 _clipboard_set_selected_data (self, FALSE);
3370 modest_folder_view_cut_selection (ModestFolderView *folder_view)
3372 ModestFolderViewPrivate *priv = NULL;
3373 GtkTreeModel *model = NULL;
3374 const gchar **hidding = NULL;
3375 guint i, n_selected;
3377 g_return_if_fail (folder_view && MODEST_IS_FOLDER_VIEW (folder_view));
3378 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
3380 /* Copy selection */
3381 if (!_clipboard_set_selected_data (folder_view, TRUE))
3384 /* Get hidding ids */
3385 hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected);
3387 /* Clear hidding array created by previous cut operation */
3388 _clear_hidding_filter (MODEST_FOLDER_VIEW (folder_view));
3390 /* Copy hidding array */
3391 priv->n_selected = n_selected;
3392 priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
3393 for (i=0; i < n_selected; i++)
3394 priv->hidding_ids[i] = g_strdup(hidding[i]);
3396 /* Hide cut folders */
3397 model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
3398 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
3402 modest_folder_view_copy_model (ModestFolderView *folder_view_src,
3403 ModestFolderView *folder_view_dst)
3405 GtkTreeModel *filter_model = NULL;
3406 GtkTreeModel *model = NULL;
3407 GtkTreeModel *new_filter_model = NULL;
3409 g_return_if_fail (folder_view_src && MODEST_IS_FOLDER_VIEW (folder_view_src));
3410 g_return_if_fail (folder_view_dst && MODEST_IS_FOLDER_VIEW (folder_view_dst));
3413 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view_src));
3414 model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER(filter_model));
3416 /* Build new filter model */
3417 new_filter_model = gtk_tree_model_filter_new (model, NULL);
3418 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (new_filter_model),
3422 /* Set copied model */
3423 gtk_tree_view_set_model (GTK_TREE_VIEW (folder_view_dst), new_filter_model);
3424 #ifndef MODEST_TOOLKIT_HILDON2
3425 g_signal_connect (G_OBJECT(new_filter_model), "row-inserted",
3426 (GCallback) on_row_inserted_maybe_select_folder, folder_view_dst);
3430 g_object_unref (new_filter_model);
3434 modest_folder_view_show_non_move_folders (ModestFolderView *folder_view,
3437 GtkTreeModel *model = NULL;
3438 ModestFolderViewPrivate* priv;
3440 g_return_if_fail (folder_view && MODEST_IS_FOLDER_VIEW (folder_view));
3442 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
3443 priv->show_non_move = show;
3444 /* modest_folder_view_update_model(folder_view, */
3445 /* TNY_ACCOUNT_STORE(modest_runtime_get_account_store())); */
3447 /* Hide special folders */
3448 model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
3449 if (GTK_IS_TREE_MODEL_FILTER (model)) {
3450 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
3454 /* Returns FALSE if it did not selected anything */
3456 _clipboard_set_selected_data (ModestFolderView *folder_view,
3459 ModestFolderViewPrivate *priv = NULL;
3460 TnyFolderStore *folder = NULL;
3461 gboolean retval = FALSE;
3463 g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (folder_view), FALSE);
3464 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
3466 /* Set selected data on clipboard */
3467 g_return_val_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard), FALSE);
3468 folder = modest_folder_view_get_selected (folder_view);
3470 /* Do not allow to select an account */
3471 if (TNY_IS_FOLDER (folder)) {
3472 modest_email_clipboard_set_data (priv->clipboard, TNY_FOLDER(folder), NULL, delete);
3477 g_object_unref (folder);
3483 _clear_hidding_filter (ModestFolderView *folder_view)
3485 ModestFolderViewPrivate *priv;
3488 g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view));
3489 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
3491 if (priv->hidding_ids != NULL) {
3492 for (i=0; i < priv->n_selected; i++)
3493 g_free (priv->hidding_ids[i]);
3494 g_free(priv->hidding_ids);
3500 on_display_name_changed (ModestAccountMgr *mgr,
3501 const gchar *account,
3504 ModestFolderView *self;
3506 self = MODEST_FOLDER_VIEW (user_data);
3508 /* Force a redraw */
3509 #if GTK_CHECK_VERSION(2, 8, 0)
3510 GtkTreeViewColumn * tree_column;
3512 tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self),
3514 gtk_tree_view_column_queue_resize (tree_column);
3516 gtk_widget_queue_draw (GTK_WIDGET (self));
3521 modest_folder_view_set_cell_style (ModestFolderView *self,
3522 ModestFolderViewCellStyle cell_style)
3524 ModestFolderViewPrivate *priv = NULL;
3526 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
3527 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
3529 priv->cell_style = cell_style;
3531 g_object_set (G_OBJECT (priv->messages_renderer),
3532 "visible", (cell_style == MODEST_FOLDER_VIEW_CELL_STYLE_COMPACT),
3535 gtk_widget_queue_draw (GTK_WIDGET (self));
3539 update_style (ModestFolderView *self)
3541 ModestFolderViewPrivate *priv;
3542 GdkColor style_color;
3544 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
3545 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
3547 if (!gtk_style_lookup_color (GTK_WIDGET (self)->style, "SecondaryTextColor", &style_color)) {
3548 gdk_color_parse ("grey", &style_color);
3551 g_object_set (G_OBJECT (priv->messages_renderer),
3552 "foreground-gdk", &style_color,
3553 "foreground-set", TRUE,
3558 on_notify_style (GObject *obj, GParamSpec *spec, gpointer userdata)
3560 if (strcmp ("style", spec->name) == 0) {
3561 update_style (MODEST_FOLDER_VIEW (obj));
3562 gtk_widget_queue_draw (GTK_WIDGET (obj));
3567 modest_folder_view_set_filter (ModestFolderView *self,
3568 ModestFolderViewFilter filter)
3570 ModestFolderViewPrivate *priv;
3571 GtkTreeModel *filter_model;
3573 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
3574 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
3576 priv->filter |= filter;
3578 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
3579 if (GTK_IS_TREE_MODEL_FILTER(filter_model)) {
3580 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
3585 modest_folder_view_unset_filter (ModestFolderView *self,
3586 ModestFolderViewFilter filter)
3588 ModestFolderViewPrivate *priv;
3589 GtkTreeModel *filter_model;
3591 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
3592 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
3594 priv->filter &= ~filter;
3596 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
3597 if (GTK_IS_TREE_MODEL_FILTER(filter_model)) {
3598 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));