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 #ifndef MODEST_TOOLKIT_HILDON2
1084 "scale", PANGO_SCALE_X_SMALL,
1087 "alignment", PANGO_ALIGN_RIGHT,
1091 gtk_tree_view_column_pack_start (column, priv->messages_renderer, FALSE);
1092 gtk_tree_view_column_set_cell_data_func(column, priv->messages_renderer,
1093 messages_cell_data, treeview, NULL);
1095 /* Set selection mode */
1096 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(treeview));
1097 gtk_tree_selection_set_mode (sel, GTK_SELECTION_SINGLE);
1099 /* Set treeview appearance */
1100 gtk_tree_view_column_set_spacing (column, 2);
1101 gtk_tree_view_column_set_resizable (column, TRUE);
1102 gtk_tree_view_column_set_fixed_width (column, TRUE);
1103 gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(treeview), FALSE);
1104 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(treeview), FALSE);
1107 gtk_tree_view_append_column (GTK_TREE_VIEW(treeview),column);
1111 modest_folder_view_init (ModestFolderView *obj)
1113 ModestFolderViewPrivate *priv;
1116 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
1118 priv->timer_expander = 0;
1119 priv->account_store = NULL;
1121 priv->style = MODEST_FOLDER_VIEW_STYLE_SHOW_ALL;
1122 priv->cur_folder_store = NULL;
1123 priv->visible_account_id = NULL;
1124 priv->folder_to_select = NULL;
1125 priv->outbox_deleted_handler = 0;
1126 priv->reexpand = TRUE;
1128 /* Initialize the local account name */
1129 conf = modest_runtime_get_conf();
1130 priv->local_account_name = modest_conf_get_string (conf, MODEST_CONF_DEVICE_NAME, NULL);
1132 /* Init email clipboard */
1133 priv->clipboard = modest_runtime_get_email_clipboard ();
1134 priv->hidding_ids = NULL;
1135 priv->n_selected = 0;
1136 priv->filter = MODEST_FOLDER_VIEW_FILTER_NONE;
1137 priv->reselect = FALSE;
1138 priv->show_non_move = TRUE;
1140 /* Build treeview */
1141 add_columns (GTK_WIDGET (obj));
1143 /* Setup drag and drop */
1144 setup_drag_and_drop (GTK_TREE_VIEW(obj));
1146 /* Connect signals */
1147 g_signal_connect (G_OBJECT (obj),
1149 G_CALLBACK (on_key_pressed), NULL);
1151 priv->display_name_changed_signal =
1152 g_signal_connect (modest_runtime_get_account_mgr (),
1153 "display_name_changed",
1154 G_CALLBACK (on_display_name_changed),
1158 * Track changes in the local account name (in the device it
1159 * will be the device name)
1161 priv->conf_key_signal = g_signal_connect (G_OBJECT(conf),
1163 G_CALLBACK(on_configuration_key_changed),
1167 g_signal_connect (G_OBJECT (obj), "notify::style", G_CALLBACK (on_notify_style), (gpointer) obj);
1173 tny_account_store_view_init (gpointer g, gpointer iface_data)
1175 TnyAccountStoreViewIface *klass = (TnyAccountStoreViewIface *)g;
1177 klass->set_account_store = modest_folder_view_set_account_store;
1181 modest_folder_view_finalize (GObject *obj)
1183 ModestFolderViewPrivate *priv;
1184 GtkTreeSelection *sel;
1185 TnyAccount *local_account;
1187 g_return_if_fail (obj);
1189 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
1191 if (priv->timer_expander != 0) {
1192 g_source_remove (priv->timer_expander);
1193 priv->timer_expander = 0;
1196 local_account = (TnyAccount *)
1197 modest_tny_account_store_get_local_folders_account (modest_runtime_get_account_store ());
1198 if (local_account) {
1199 if (g_signal_handler_is_connected (local_account,
1200 priv->outbox_deleted_handler))
1201 g_signal_handler_disconnect (local_account,
1202 priv->outbox_deleted_handler);
1203 g_object_unref (local_account);
1206 if (priv->account_store) {
1207 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
1208 priv->account_inserted_signal);
1209 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
1210 priv->account_removed_signal);
1211 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
1212 priv->account_changed_signal);
1213 g_object_unref (G_OBJECT(priv->account_store));
1214 priv->account_store = NULL;
1217 if (g_signal_handler_is_connected (modest_runtime_get_account_mgr (),
1218 priv->display_name_changed_signal)) {
1219 g_signal_handler_disconnect (modest_runtime_get_account_mgr (),
1220 priv->display_name_changed_signal);
1221 priv->display_name_changed_signal = 0;
1225 g_object_unref (G_OBJECT (priv->query));
1229 if (priv->folder_to_select) {
1230 g_object_unref (G_OBJECT(priv->folder_to_select));
1231 priv->folder_to_select = NULL;
1234 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(obj));
1236 g_signal_handler_disconnect (G_OBJECT(sel), priv->changed_signal);
1238 g_free (priv->local_account_name);
1239 g_free (priv->visible_account_id);
1241 if (priv->conf_key_signal) {
1242 g_signal_handler_disconnect (modest_runtime_get_conf (),
1243 priv->conf_key_signal);
1244 priv->conf_key_signal = 0;
1247 if (priv->cur_folder_store) {
1248 g_object_unref (priv->cur_folder_store);
1249 priv->cur_folder_store = NULL;
1252 /* Clear hidding array created by cut operation */
1253 _clear_hidding_filter (MODEST_FOLDER_VIEW (obj));
1255 G_OBJECT_CLASS(parent_class)->finalize (obj);
1260 modest_folder_view_set_account_store (TnyAccountStoreView *self, TnyAccountStore *account_store)
1262 ModestFolderViewPrivate *priv;
1265 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
1266 g_return_if_fail (TNY_IS_ACCOUNT_STORE (account_store));
1268 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1269 device = tny_account_store_get_device (account_store);
1271 if (G_UNLIKELY (priv->account_store)) {
1273 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
1274 priv->account_inserted_signal))
1275 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
1276 priv->account_inserted_signal);
1277 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
1278 priv->account_removed_signal))
1279 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
1280 priv->account_removed_signal);
1281 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
1282 priv->account_changed_signal))
1283 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
1284 priv->account_changed_signal);
1285 g_object_unref (G_OBJECT (priv->account_store));
1288 priv->account_store = g_object_ref (G_OBJECT (account_store));
1290 priv->account_removed_signal =
1291 g_signal_connect (G_OBJECT(account_store), "account_removed",
1292 G_CALLBACK (on_account_removed), self);
1294 priv->account_inserted_signal =
1295 g_signal_connect (G_OBJECT(account_store), "account_inserted",
1296 G_CALLBACK (on_account_inserted), self);
1298 priv->account_changed_signal =
1299 g_signal_connect (G_OBJECT(account_store), "account_changed",
1300 G_CALLBACK (on_account_changed), self);
1302 modest_folder_view_update_model (MODEST_FOLDER_VIEW (self), account_store);
1303 priv->reselect = FALSE;
1304 modest_folder_view_select_first_inbox_or_local (MODEST_FOLDER_VIEW (self));
1306 g_object_unref (G_OBJECT (device));
1310 on_outbox_deleted_cb (ModestTnyLocalFoldersAccount *local_account,
1313 ModestFolderView *self;
1314 GtkTreeModel *model, *filter_model;
1317 self = MODEST_FOLDER_VIEW (user_data);
1319 if (!get_inner_models (self, &filter_model, NULL, &model))
1322 /* Remove outbox from model */
1323 outbox = modest_tny_local_folders_account_get_merged_outbox (local_account);
1324 tny_list_remove (TNY_LIST (model), G_OBJECT (outbox));
1325 g_object_unref (outbox);
1328 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1332 on_account_inserted (TnyAccountStore *account_store,
1333 TnyAccount *account,
1336 ModestFolderViewPrivate *priv;
1337 GtkTreeModel *model, *filter_model;
1339 /* Ignore transport account insertions, we're not showing them
1340 in the folder view */
1341 if (TNY_IS_TRANSPORT_ACCOUNT (account))
1344 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (user_data);
1347 /* If we're adding a new account, and there is no previous
1348 one, we need to select the visible server account */
1349 if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE &&
1350 !priv->visible_account_id)
1351 modest_widget_memory_restore (modest_runtime_get_conf(),
1352 G_OBJECT (user_data),
1353 MODEST_CONF_FOLDER_VIEW_KEY);
1357 if (!get_inner_models (MODEST_FOLDER_VIEW (user_data),
1358 &filter_model, NULL, &model))
1361 /* Insert the account in the model */
1362 tny_list_append (TNY_LIST (model), G_OBJECT (account));
1364 /* When the model is a list store (plain representation) the
1365 outbox is not a child of any account so we have to manually
1366 delete it because removing the local folders account won't
1367 delete it (because tny_folder_get_account() is not defined
1368 for a merge folder */
1369 if (TNY_IS_GTK_FOLDER_LIST_STORE (model) &&
1370 MODEST_IS_TNY_LOCAL_FOLDERS_ACCOUNT (account)) {
1372 priv->outbox_deleted_handler =
1373 g_signal_connect (account,
1375 G_CALLBACK (on_outbox_deleted_cb),
1379 /* Refilter the model */
1380 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1385 same_account_selected (ModestFolderView *self,
1386 TnyAccount *account)
1388 ModestFolderViewPrivate *priv;
1389 gboolean same_account = FALSE;
1391 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1393 if (priv->cur_folder_store) {
1394 TnyAccount *selected_folder_account = NULL;
1396 if (TNY_IS_FOLDER (priv->cur_folder_store)) {
1397 selected_folder_account =
1398 modest_tny_folder_get_account (TNY_FOLDER (priv->cur_folder_store));
1400 selected_folder_account =
1401 TNY_ACCOUNT (g_object_ref (priv->cur_folder_store));
1404 if (selected_folder_account == account)
1405 same_account = TRUE;
1407 g_object_unref (selected_folder_account);
1409 return same_account;
1414 * Selects the first inbox or the local account in an idle
1417 on_idle_select_first_inbox_or_local (gpointer user_data)
1419 ModestFolderView *self = MODEST_FOLDER_VIEW (user_data);
1421 gdk_threads_enter ();
1422 modest_folder_view_select_first_inbox_or_local (self);
1423 gdk_threads_leave ();
1429 on_account_changed (TnyAccountStore *account_store,
1430 TnyAccount *tny_account,
1433 ModestFolderView *self;
1434 ModestFolderViewPrivate *priv;
1435 GtkTreeModel *model, *filter_model;
1436 GtkTreeSelection *sel;
1437 gboolean same_account;
1439 /* Ignore transport account insertions, we're not showing them
1440 in the folder view */
1441 if (TNY_IS_TRANSPORT_ACCOUNT (tny_account))
1444 self = MODEST_FOLDER_VIEW (user_data);
1445 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (user_data);
1447 /* Get the inner model */
1448 if (!get_inner_models (MODEST_FOLDER_VIEW (user_data),
1449 &filter_model, NULL, &model))
1452 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (user_data));
1454 /* Invalidate the cur_folder_store only if the selected folder
1455 belongs to the account that is being removed */
1456 same_account = same_account_selected (self, tny_account);
1458 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1459 gtk_tree_selection_unselect_all (sel);
1462 /* Remove the account from the model */
1463 tny_list_remove (TNY_LIST (model), G_OBJECT (tny_account));
1465 /* Insert the account in the model */
1466 tny_list_append (TNY_LIST (model), G_OBJECT (tny_account));
1468 /* Refilter the model */
1469 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1471 /* Select the first INBOX if the currently selected folder
1472 belongs to the account that is being deleted */
1473 if (same_account && !MODEST_IS_TNY_LOCAL_FOLDERS_ACCOUNT (tny_account))
1474 g_idle_add (on_idle_select_first_inbox_or_local, self);
1478 on_account_removed (TnyAccountStore *account_store,
1479 TnyAccount *account,
1482 ModestFolderView *self = NULL;
1483 ModestFolderViewPrivate *priv;
1484 GtkTreeModel *model, *filter_model;
1485 GtkTreeSelection *sel = NULL;
1486 gboolean same_account = FALSE;
1488 /* Ignore transport account removals, we're not showing them
1489 in the folder view */
1490 if (TNY_IS_TRANSPORT_ACCOUNT (account))
1493 if (!MODEST_IS_FOLDER_VIEW(user_data)) {
1494 g_warning ("BUG: %s: not a valid folder view", __FUNCTION__);
1498 self = MODEST_FOLDER_VIEW (user_data);
1499 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1501 /* Invalidate the cur_folder_store only if the selected folder
1502 belongs to the account that is being removed */
1503 same_account = same_account_selected (self, account);
1505 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1506 gtk_tree_selection_unselect_all (sel);
1509 /* Invalidate row to select only if the folder to select
1510 belongs to the account that is being removed*/
1511 if (priv->folder_to_select) {
1512 TnyAccount *folder_to_select_account = NULL;
1514 folder_to_select_account = tny_folder_get_account (priv->folder_to_select);
1515 if (folder_to_select_account == account) {
1516 modest_folder_view_disable_next_folder_selection (self);
1517 g_object_unref (priv->folder_to_select);
1518 priv->folder_to_select = NULL;
1520 g_object_unref (folder_to_select_account);
1523 if (!get_inner_models (MODEST_FOLDER_VIEW (user_data),
1524 &filter_model, NULL, &model))
1527 /* Disconnect the signal handler */
1528 if (TNY_IS_GTK_FOLDER_LIST_STORE (model) &&
1529 MODEST_IS_TNY_LOCAL_FOLDERS_ACCOUNT (account)) {
1530 if (g_signal_handler_is_connected (account,
1531 priv->outbox_deleted_handler))
1532 g_signal_handler_disconnect (account,
1533 priv->outbox_deleted_handler);
1536 /* Remove the account from the model */
1537 tny_list_remove (TNY_LIST (model), G_OBJECT (account));
1539 /* If the removed account is the currently viewed one then
1540 clear the configuration value. The new visible account will be the default account */
1541 if (priv->visible_account_id &&
1542 !strcmp (priv->visible_account_id, tny_account_get_id (account))) {
1544 /* Clear the current visible account_id */
1545 modest_folder_view_set_account_id_of_visible_server_account (self, NULL);
1547 /* Call the restore method, this will set the new visible account */
1548 modest_widget_memory_restore (modest_runtime_get_conf(), G_OBJECT(self),
1549 MODEST_CONF_FOLDER_VIEW_KEY);
1552 /* Refilter the model */
1553 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1555 /* Select the first INBOX if the currently selected folder
1556 belongs to the account that is being deleted */
1558 g_idle_add (on_idle_select_first_inbox_or_local, self);
1562 modest_folder_view_set_title (ModestFolderView *self, const gchar *title)
1564 GtkTreeViewColumn *col;
1566 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
1568 col = gtk_tree_view_get_column (GTK_TREE_VIEW(self), 0);
1570 g_printerr ("modest: failed get column for title\n");
1574 gtk_tree_view_column_set_title (col, title);
1575 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self),
1580 modest_folder_view_on_map (ModestFolderView *self,
1581 GdkEventExpose *event,
1584 ModestFolderViewPrivate *priv;
1586 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1588 /* This won't happen often */
1589 if (G_UNLIKELY (priv->reselect)) {
1590 /* Select the first inbox or the local account if not found */
1592 /* TODO: this could cause a lock at startup, so we
1593 comment it for the moment. We know that this will
1594 be a bug, because the INBOX is not selected, but we
1595 need to rewrite some parts of Modest to avoid the
1596 deathlock situation */
1597 /* TODO: check if this is still the case */
1598 priv->reselect = FALSE;
1599 modest_folder_view_select_first_inbox_or_local (self);
1600 /* Notify the display name observers */
1601 g_signal_emit (G_OBJECT(self),
1602 signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
1606 if (priv->reexpand) {
1607 expand_root_items (self);
1608 priv->reexpand = FALSE;
1615 modest_folder_view_new (TnyFolderStoreQuery *query)
1618 ModestFolderViewPrivate *priv;
1619 GtkTreeSelection *sel;
1621 self = G_OBJECT (g_object_new (MODEST_TYPE_FOLDER_VIEW,
1622 #ifdef MODEST_TOOLKIT_HILDON2
1623 "hildon-ui-mode", HILDON_UI_MODE_NORMAL,
1626 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1629 priv->query = g_object_ref (query);
1631 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1632 priv->changed_signal = g_signal_connect (sel, "changed",
1633 G_CALLBACK (on_selection_changed), self);
1635 g_signal_connect (self, "row-activated", G_CALLBACK (on_row_activated), self);
1637 g_signal_connect (self, "expose-event", G_CALLBACK (modest_folder_view_on_map), NULL);
1639 return GTK_WIDGET(self);
1642 /* this feels dirty; any other way to expand all the root items? */
1644 expand_root_items (ModestFolderView *self)
1647 GtkTreeModel *model;
1650 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1651 path = gtk_tree_path_new_first ();
1653 /* all folders should have child items, so.. */
1655 gtk_tree_view_expand_row (GTK_TREE_VIEW(self), path, FALSE);
1656 gtk_tree_path_next (path);
1657 } while (gtk_tree_model_get_iter (model, &iter, path));
1659 gtk_tree_path_free (path);
1663 * We use this function to implement the
1664 * MODEST_FOLDER_VIEW_STYLE_SHOW_ONE style. We only show the default
1665 * account in this case, and the local folders.
1668 filter_row (GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
1670 ModestFolderViewPrivate *priv;
1671 gboolean retval = TRUE;
1672 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1673 GObject *instance = NULL;
1674 const gchar *id = NULL;
1676 gboolean found = FALSE;
1677 gboolean cleared = FALSE;
1678 ModestTnyFolderRules rules = 0;
1680 g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (data), FALSE);
1681 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (data);
1683 gtk_tree_model_get (model, iter,
1685 INSTANCE_COLUMN, &instance,
1688 /* Do not show if there is no instance, this could indeed
1689 happen when the model is being modified while it's being
1690 drawn. This could occur for example when moving folders
1695 if (TNY_IS_ACCOUNT (instance)) {
1696 TnyAccount *acc = TNY_ACCOUNT (instance);
1697 const gchar *account_id = tny_account_get_id (acc);
1699 /* If it isn't a special folder,
1700 * don't show it unless it is the visible account: */
1701 if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE &&
1702 !modest_tny_account_is_virtual_local_folders (acc) &&
1703 strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
1705 /* Show only the visible account id */
1706 if (priv->visible_account_id) {
1707 if (strcmp (account_id, priv->visible_account_id))
1714 /* Never show these to the user. They are merged into one folder
1715 * in the local-folders account instead: */
1716 if (retval && MODEST_IS_TNY_OUTBOX_ACCOUNT (acc))
1719 if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE) {
1720 /* Only show special folders for current account if needed */
1721 if (TNY_IS_FOLDER (instance) && !TNY_IS_MERGE_FOLDER (instance)) {
1722 TnyAccount *account;
1724 account = tny_folder_get_account (TNY_FOLDER (instance));
1726 if (TNY_IS_ACCOUNT (account)) {
1727 const gchar *account_id = tny_account_get_id (account);
1729 if (!modest_tny_account_is_virtual_local_folders (account) &&
1730 strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
1731 /* Show only the visible account id */
1732 if (priv->visible_account_id) {
1733 if (strcmp (account_id, priv->visible_account_id))
1737 g_object_unref (account);
1744 /* Check hiding (if necessary) */
1745 cleared = modest_email_clipboard_cleared (priv->clipboard);
1746 if ((retval) && (!cleared) && (TNY_IS_FOLDER (instance))) {
1747 id = tny_folder_get_id (TNY_FOLDER(instance));
1748 if (priv->hidding_ids != NULL)
1749 for (i=0; i < priv->n_selected && !found; i++)
1750 if (priv->hidding_ids[i] != NULL && id != NULL)
1751 found = (!strcmp (priv->hidding_ids[i], id));
1756 /* If this is a move to dialog, hide Sent, Outbox and Drafts
1757 folder as no message can be move there according to UI specs */
1758 if (!priv->show_non_move) {
1759 if (TNY_IS_FOLDER (instance) &&
1760 modest_tny_folder_is_local_folder (TNY_FOLDER (instance))) {
1762 case TNY_FOLDER_TYPE_OUTBOX:
1763 case TNY_FOLDER_TYPE_SENT:
1764 case TNY_FOLDER_TYPE_DRAFTS:
1767 case TNY_FOLDER_TYPE_UNKNOWN:
1768 case TNY_FOLDER_TYPE_NORMAL:
1769 type = modest_tny_folder_guess_folder_type(TNY_FOLDER(instance));
1770 if (type == TNY_FOLDER_TYPE_INVALID)
1771 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1773 if (type == TNY_FOLDER_TYPE_OUTBOX ||
1774 type == TNY_FOLDER_TYPE_SENT
1775 || type == TNY_FOLDER_TYPE_DRAFTS)
1784 /* apply special filters */
1785 if (retval && (priv->filter & MODEST_FOLDER_VIEW_FILTER_HIDE_ACCOUNTS)) {
1786 if (TNY_IS_ACCOUNT (instance))
1790 if (retval && (priv->filter & MODEST_FOLDER_VIEW_FILTER_CAN_HAVE_FOLDERS)) {
1791 if (TNY_IS_FOLDER (instance)) {
1792 /* Check folder rules */
1793 ModestTnyFolderRules rules;
1795 rules = modest_tny_folder_get_rules (TNY_FOLDER (instance));
1796 retval = !(rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE);
1797 } else if (TNY_IS_ACCOUNT (instance)) {
1798 if (modest_tny_folder_store_is_remote (TNY_FOLDER_STORE (instance))) {
1806 if (retval && (priv->filter & MODEST_FOLDER_VIEW_FILTER_HIDE_MANDATORY_FOLDERS)) {
1807 if (TNY_IS_FOLDER (instance)) {
1808 TnyFolderType guess_type;
1810 if (TNY_FOLDER_TYPE_NORMAL) {
1811 guess_type = modest_tny_folder_guess_folder_type (TNY_FOLDER (instance));
1817 case TNY_FOLDER_TYPE_OUTBOX:
1818 case TNY_FOLDER_TYPE_SENT:
1819 case TNY_FOLDER_TYPE_DRAFTS:
1820 case TNY_FOLDER_TYPE_ARCHIVE:
1821 case TNY_FOLDER_TYPE_INBOX:
1824 case TNY_FOLDER_TYPE_UNKNOWN:
1825 case TNY_FOLDER_TYPE_NORMAL:
1831 } else if (TNY_IS_ACCOUNT (instance)) {
1836 if (retval && TNY_IS_FOLDER (instance)) {
1837 rules = modest_tny_folder_get_rules (TNY_FOLDER (instance));
1840 if (retval && (priv->filter & MODEST_FOLDER_VIEW_FILTER_DELETABLE)) {
1841 if (TNY_IS_FOLDER (instance)) {
1842 retval = !(rules & MODEST_FOLDER_RULES_FOLDER_NON_DELETABLE);
1843 } else if (TNY_IS_ACCOUNT (instance)) {
1848 if (retval && (priv->filter & MODEST_FOLDER_VIEW_FILTER_RENAMEABLE)) {
1849 if (TNY_IS_FOLDER (instance)) {
1850 retval = !(rules & MODEST_FOLDER_RULES_FOLDER_NON_RENAMEABLE);
1851 } else if (TNY_IS_ACCOUNT (instance)) {
1856 if (retval && (priv->filter & MODEST_FOLDER_VIEW_FILTER_MOVEABLE)) {
1857 if (TNY_IS_FOLDER (instance)) {
1858 retval = !(rules & MODEST_FOLDER_RULES_FOLDER_NON_MOVEABLE);
1859 } else if (TNY_IS_ACCOUNT (instance)) {
1865 g_object_unref (instance);
1872 modest_folder_view_update_model (ModestFolderView *self,
1873 TnyAccountStore *account_store)
1875 ModestFolderViewPrivate *priv;
1876 GtkTreeModel *model /* , *old_model */;
1877 GtkTreeModel *filter_model = NULL, *sortable = NULL;
1879 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW (self), FALSE);
1880 g_return_val_if_fail (account_store && MODEST_IS_TNY_ACCOUNT_STORE(account_store),
1883 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1885 /* Notify that there is no folder selected */
1886 g_signal_emit (G_OBJECT(self),
1887 signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
1889 if (priv->cur_folder_store) {
1890 g_object_unref (priv->cur_folder_store);
1891 priv->cur_folder_store = NULL;
1894 /* FIXME: the local accounts are not shown when the query
1895 selects only the subscribed folders */
1896 #ifdef MODEST_TOOLKIT_HILDON2
1897 model = tny_gtk_folder_list_store_new_with_flags (NULL,
1898 TNY_GTK_FOLDER_LIST_STORE_FLAG_SHOW_PATH);
1899 tny_gtk_folder_list_store_set_path_separator (TNY_GTK_FOLDER_LIST_STORE (model),
1900 MODEST_FOLDER_PATH_SEPARATOR);
1902 model = tny_gtk_folder_store_tree_model_new (NULL);
1905 /* When the model is a list store (plain representation) the
1906 outbox is not a child of any account so we have to manually
1907 delete it because removing the local folders account won't
1908 delete it (because tny_folder_get_account() is not defined
1909 for a merge folder */
1910 if (TNY_IS_GTK_FOLDER_LIST_STORE (model)) {
1911 TnyAccount *account;
1912 ModestTnyAccountStore *acc_store;
1914 acc_store = modest_runtime_get_account_store ();
1915 account = modest_tny_account_store_get_local_folders_account (acc_store);
1917 if (g_signal_handler_is_connected (account,
1918 priv->outbox_deleted_handler))
1919 g_signal_handler_disconnect (account,
1920 priv->outbox_deleted_handler);
1922 priv->outbox_deleted_handler =
1923 g_signal_connect (account,
1925 G_CALLBACK (on_outbox_deleted_cb),
1927 g_object_unref (account);
1930 /* Get the accounts: */
1931 tny_account_store_get_accounts (TNY_ACCOUNT_STORE(account_store),
1933 TNY_ACCOUNT_STORE_STORE_ACCOUNTS);
1935 sortable = gtk_tree_model_sort_new_with_model (model);
1936 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
1938 GTK_SORT_ASCENDING);
1939 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
1941 cmp_rows, NULL, NULL);
1943 /* Create filter model */
1944 filter_model = gtk_tree_model_filter_new (sortable, NULL);
1945 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
1951 gtk_tree_view_set_model (GTK_TREE_VIEW(self), filter_model);
1952 #ifndef MODEST_TOOLKIT_HILDON2
1953 g_signal_connect (G_OBJECT(filter_model), "row-inserted",
1954 (GCallback) on_row_inserted_maybe_select_folder, self);
1957 g_object_unref (model);
1958 g_object_unref (filter_model);
1959 g_object_unref (sortable);
1961 /* Force a reselection of the INBOX next time the widget is shown */
1962 priv->reselect = TRUE;
1969 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
1971 GtkTreeModel *model = NULL;
1972 TnyFolderStore *folder = NULL;
1974 ModestFolderView *tree_view = NULL;
1975 ModestFolderViewPrivate *priv = NULL;
1976 gboolean selected = FALSE;
1978 g_return_if_fail (sel);
1979 g_return_if_fail (user_data);
1981 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
1983 selected = gtk_tree_selection_get_selected (sel, &model, &iter);
1985 tree_view = MODEST_FOLDER_VIEW (user_data);
1988 gtk_tree_model_get (model, &iter,
1989 INSTANCE_COLUMN, &folder,
1992 /* If the folder is the same do not notify */
1993 if (folder && priv->cur_folder_store == folder) {
1994 g_object_unref (folder);
1999 /* Current folder was unselected */
2000 if (priv->cur_folder_store) {
2001 /* We must do this firstly because a libtinymail-camel
2002 implementation detail. If we issue the signal
2003 before doing the sync_async, then that signal could
2004 cause (and it actually does it) a free of the
2005 summary of the folder (because the main window will
2006 clear the headers view */
2007 if (TNY_IS_FOLDER(priv->cur_folder_store))
2008 tny_folder_sync_async (TNY_FOLDER(priv->cur_folder_store),
2009 FALSE, NULL, NULL, NULL);
2011 g_signal_emit (G_OBJECT(tree_view), signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
2012 priv->cur_folder_store, FALSE);
2014 g_object_unref (priv->cur_folder_store);
2015 priv->cur_folder_store = NULL;
2018 /* New current references */
2019 priv->cur_folder_store = folder;
2021 /* New folder has been selected. Do not notify if there is
2022 nothing new selected */
2024 g_signal_emit (G_OBJECT(tree_view),
2025 signals[FOLDER_SELECTION_CHANGED_SIGNAL],
2026 0, priv->cur_folder_store, TRUE);
2031 on_row_activated (GtkTreeView *treeview,
2032 GtkTreePath *treepath,
2033 GtkTreeViewColumn *column,
2036 GtkTreeModel *model = NULL;
2037 TnyFolderStore *folder = NULL;
2039 ModestFolderView *self = NULL;
2040 ModestFolderViewPrivate *priv = NULL;
2042 g_return_if_fail (treeview);
2043 g_return_if_fail (user_data);
2045 self = MODEST_FOLDER_VIEW (user_data);
2046 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
2048 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2050 if (!gtk_tree_model_get_iter (model, &iter, treepath))
2053 gtk_tree_model_get (model, &iter,
2054 INSTANCE_COLUMN, &folder,
2057 g_signal_emit (G_OBJECT(self),
2058 signals[FOLDER_ACTIVATED_SIGNAL],
2061 #ifdef MODEST_TOOLKIT_HILDON2
2062 HildonUIMode ui_mode;
2063 g_object_get (G_OBJECT (self), "hildon-ui-mode", &ui_mode, NULL);
2064 if (ui_mode == HILDON_UI_MODE_NORMAL) {
2065 if (priv->cur_folder_store)
2066 g_object_unref (priv->cur_folder_store);
2067 priv->cur_folder_store = g_object_ref (folder);
2071 g_object_unref (folder);
2075 modest_folder_view_get_selected (ModestFolderView *self)
2077 ModestFolderViewPrivate *priv;
2079 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW(self), NULL);
2081 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2082 if (priv->cur_folder_store)
2083 g_object_ref (priv->cur_folder_store);
2085 return priv->cur_folder_store;
2089 get_cmp_rows_type_pos (GObject *folder)
2091 /* Remote accounts -> Local account -> MMC account .*/
2094 if (TNY_IS_ACCOUNT (folder) &&
2095 modest_tny_account_is_virtual_local_folders (
2096 TNY_ACCOUNT (folder))) {
2098 } else if (TNY_IS_ACCOUNT (folder)) {
2099 TnyAccount *account = TNY_ACCOUNT (folder);
2100 const gchar *account_id = tny_account_get_id (account);
2101 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
2107 printf ("DEBUG: %s: unexpected type.\n", __FUNCTION__);
2108 return -1; /* Should never happen */
2113 inbox_is_special (TnyFolderStore *folder_store)
2115 gboolean is_special = TRUE;
2117 if (TNY_IS_FOLDER (folder_store)) {
2121 gchar *last_inbox_bar;
2123 id = tny_folder_get_id (TNY_FOLDER (folder_store));
2124 downcase = g_utf8_strdown (id, -1);
2125 last_bar = g_strrstr (downcase, "/");
2127 last_inbox_bar = g_strrstr (downcase, "inbox/");
2128 if ((last_inbox_bar == NULL) || (last_inbox_bar + 5 != last_bar))
2137 get_cmp_pos (TnyFolderType t, TnyFolder *folder_store)
2139 TnyAccount *account;
2140 gboolean is_special;
2141 /* Inbox, Outbox, Drafts, Sent, User */
2144 if (!TNY_IS_FOLDER (folder_store))
2147 case TNY_FOLDER_TYPE_INBOX:
2149 account = tny_folder_get_account (folder_store);
2150 is_special = (get_cmp_rows_type_pos (G_OBJECT (account)) == 0);
2152 /* In inbox case we need to know if the inbox is really the top
2153 * inbox of the account, or if it's a submailbox inbox. To do
2154 * this we'll apply an heuristic rule: Find last "/" and check
2155 * if it's preceeded by another Inbox */
2156 is_special = is_special && inbox_is_special (TNY_FOLDER_STORE (folder_store));
2157 g_object_unref (account);
2158 return is_special?0:4;
2161 case TNY_FOLDER_TYPE_OUTBOX:
2162 return (TNY_IS_MERGE_FOLDER (folder_store))?2:4;
2164 case TNY_FOLDER_TYPE_DRAFTS:
2166 account = tny_folder_get_account (folder_store);
2167 is_special = (get_cmp_rows_type_pos (G_OBJECT (account)) == 1);
2168 g_object_unref (account);
2169 return is_special?1:4;
2172 case TNY_FOLDER_TYPE_SENT:
2174 account = tny_folder_get_account (folder_store);
2175 is_special = (get_cmp_rows_type_pos (G_OBJECT (account)) == 1);
2176 g_object_unref (account);
2177 return is_special?3:4;
2186 compare_account_names (TnyAccount *a1, TnyAccount *a2)
2188 const gchar *a1_name, *a2_name;
2190 a1_name = tny_account_get_name (a1);
2191 a2_name = tny_account_get_name (a2);
2193 return modest_text_utils_utf8_strcmp (a1_name, a2_name, TRUE);
2197 compare_accounts (TnyFolderStore *s1, TnyFolderStore *s2)
2199 TnyAccount *a1 = NULL, *a2 = NULL;
2202 if (TNY_IS_ACCOUNT (s1)) {
2203 a1 = TNY_ACCOUNT (g_object_ref (s1));
2204 } else if (!TNY_IS_MERGE_FOLDER (s1)) {
2205 a1 = tny_folder_get_account (TNY_FOLDER (s1));
2208 if (TNY_IS_ACCOUNT (s2)) {
2209 a2 = TNY_ACCOUNT (g_object_ref (s2));
2210 } else if (!TNY_IS_MERGE_FOLDER (s2)) {
2211 a2 = tny_folder_get_account (TNY_FOLDER (s2));
2228 /* First we sort with the type of account */
2229 cmp = get_cmp_rows_type_pos (G_OBJECT (a1)) - get_cmp_rows_type_pos (G_OBJECT (a2));
2233 cmp = compare_account_names (a1, a2);
2237 g_object_unref (a1);
2239 g_object_unref (a2);
2245 compare_accounts_first (TnyFolderStore *s1, TnyFolderStore *s2)
2247 gint is_account1, is_account2;
2249 is_account1 = TNY_IS_ACCOUNT (s1)?1:0;
2250 is_account2 = TNY_IS_ACCOUNT (s2)?1:0;
2252 return is_account2 - is_account1;
2256 * This function orders the mail accounts according to these rules:
2257 * 1st - remote accounts
2258 * 2nd - local account
2262 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
2266 gchar *name1 = NULL;
2267 gchar *name2 = NULL;
2268 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2269 TnyFolderType type2 = TNY_FOLDER_TYPE_UNKNOWN;
2270 GObject *folder1 = NULL;
2271 GObject *folder2 = NULL;
2273 gtk_tree_model_get (tree_model, iter1,
2274 NAME_COLUMN, &name1,
2276 INSTANCE_COLUMN, &folder1,
2278 gtk_tree_model_get (tree_model, iter2,
2279 NAME_COLUMN, &name2,
2280 TYPE_COLUMN, &type2,
2281 INSTANCE_COLUMN, &folder2,
2284 /* Return if we get no folder. This could happen when folder
2285 operations are happening. The model is updated after the
2286 folder copy/move actually occurs, so there could be
2287 situations where the model to be drawn is not correct */
2288 if (!folder1 || !folder2)
2291 /* Sort by type. First the special folders, then the archives */
2292 cmp = get_cmp_pos (type, (TnyFolder *) folder1) - get_cmp_pos (type2, (TnyFolder *) folder2);
2296 /* Now we sort using the account of each folder */
2297 if (TNY_IS_FOLDER_STORE (folder1) &&
2298 TNY_IS_FOLDER_STORE (folder2)) {
2299 cmp = compare_accounts (TNY_FOLDER_STORE (folder1), TNY_FOLDER_STORE (folder2));
2303 /* Each group is preceeded by its account */
2304 cmp = compare_accounts_first (TNY_FOLDER_STORE (folder1), TNY_FOLDER_STORE (folder2));
2309 /* Pure sort by name */
2310 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
2313 g_object_unref(G_OBJECT(folder1));
2315 g_object_unref(G_OBJECT(folder2));
2323 /*****************************************************************************/
2324 /* DRAG and DROP stuff */
2325 /*****************************************************************************/
2327 * This function fills the #GtkSelectionData with the row and the
2328 * model that has been dragged. It's called when this widget is a
2329 * source for dnd after the event drop happened
2332 on_drag_data_get (GtkWidget *widget, GdkDragContext *context, GtkSelectionData *selection_data,
2333 guint info, guint time, gpointer data)
2335 GtkTreeSelection *selection;
2336 GtkTreeModel *model;
2338 GtkTreePath *source_row;
2340 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
2341 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
2343 source_row = gtk_tree_model_get_path (model, &iter);
2344 gtk_tree_set_row_drag_data (selection_data,
2348 gtk_tree_path_free (source_row);
2352 typedef struct _DndHelper {
2353 ModestFolderView *folder_view;
2354 gboolean delete_source;
2355 GtkTreePath *source_row;
2359 dnd_helper_destroyer (DndHelper *helper)
2361 /* Free the helper */
2362 gtk_tree_path_free (helper->source_row);
2363 g_slice_free (DndHelper, helper);
2367 xfer_folder_cb (ModestMailOperation *mail_op,
2368 TnyFolder *new_folder,
2372 /* Select the folder */
2373 modest_folder_view_select_folder (MODEST_FOLDER_VIEW (user_data),
2379 /* get the folder for the row the treepath refers to. */
2380 /* folder must be unref'd */
2381 static TnyFolderStore *
2382 tree_path_to_folder (GtkTreeModel *model, GtkTreePath *path)
2385 TnyFolderStore *folder = NULL;
2387 if (gtk_tree_model_get_iter (model,&iter, path))
2388 gtk_tree_model_get (model, &iter,
2389 INSTANCE_COLUMN, &folder,
2396 * This function is used by drag_data_received_cb to manage drag and
2397 * drop of a header, i.e, and drag from the header view to the folder
2401 drag_and_drop_from_header_view (GtkTreeModel *source_model,
2402 GtkTreeModel *dest_model,
2403 GtkTreePath *dest_row,
2404 GtkSelectionData *selection_data)
2406 TnyList *headers = NULL;
2407 TnyFolder *folder = NULL, *src_folder = NULL;
2408 TnyFolderType folder_type;
2409 GtkTreeIter source_iter, dest_iter;
2410 ModestWindowMgr *mgr = NULL;
2411 ModestWindow *main_win = NULL;
2412 gchar **uris, **tmp;
2414 /* Build the list of headers */
2415 mgr = modest_runtime_get_window_mgr ();
2416 headers = tny_simple_list_new ();
2417 uris = modest_dnd_selection_data_get_paths (selection_data);
2420 while (*tmp != NULL) {
2423 gboolean first = TRUE;
2426 path = gtk_tree_path_new_from_string (*tmp);
2427 gtk_tree_model_get_iter (source_model, &source_iter, path);
2428 gtk_tree_model_get (source_model, &source_iter,
2429 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
2432 /* Do not enable d&d of headers already opened */
2433 if (!modest_window_mgr_find_registered_header(mgr, header, NULL))
2434 tny_list_append (headers, G_OBJECT (header));
2436 if (G_UNLIKELY (first)) {
2437 src_folder = tny_header_get_folder (header);
2441 /* Free and go on */
2442 gtk_tree_path_free (path);
2443 g_object_unref (header);
2448 /* This could happen ig we perform a d&d very quickly over the
2449 same row that row could dissapear because message is
2451 if (!TNY_IS_FOLDER (src_folder))
2454 /* Get the target folder */
2455 gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
2456 gtk_tree_model_get (dest_model, &dest_iter,
2460 if (!folder || !TNY_IS_FOLDER(folder)) {
2461 /* g_warning ("%s: not a valid target folder (%p)", __FUNCTION__, folder); */
2465 folder_type = modest_tny_folder_guess_folder_type (folder);
2466 if (folder_type == TNY_FOLDER_TYPE_INVALID) {
2467 /* g_warning ("%s: invalid target folder", __FUNCTION__); */
2468 goto cleanup; /* cannot move messages there */
2471 if (modest_tny_folder_get_rules((TNY_FOLDER(folder))) & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE) {
2472 /* g_warning ("folder not writable"); */
2473 goto cleanup; /* verboten! */
2476 /* Ask for confirmation to move */
2477 main_win = modest_window_mgr_get_main_window (mgr, FALSE); /* don't create */
2479 g_warning ("%s: BUG: no main window found", __FUNCTION__);
2483 /* Transfer messages */
2484 modest_ui_actions_transfer_messages_helper (GTK_WINDOW (main_win), src_folder,
2489 if (G_IS_OBJECT (src_folder))
2490 g_object_unref (src_folder);
2491 if (G_IS_OBJECT(folder))
2492 g_object_unref (G_OBJECT (folder));
2493 if (G_IS_OBJECT(headers))
2494 g_object_unref (headers);
2498 TnyFolderStore *src_folder;
2499 TnyFolderStore *dst_folder;
2500 ModestFolderView *folder_view;
2505 dnd_folder_info_destroyer (DndFolderInfo *info)
2507 if (info->src_folder)
2508 g_object_unref (info->src_folder);
2509 if (info->dst_folder)
2510 g_object_unref (info->dst_folder);
2511 g_slice_free (DndFolderInfo, info);
2515 dnd_on_connection_failed_destroyer (DndFolderInfo *info,
2516 GtkWindow *parent_window,
2517 TnyAccount *account)
2520 modest_ui_actions_on_account_connection_error (parent_window, account);
2522 /* Free the helper & info */
2523 dnd_helper_destroyer (info->helper);
2524 dnd_folder_info_destroyer (info);
2528 drag_and_drop_from_folder_view_src_folder_performer (gboolean canceled,
2530 GtkWindow *parent_window,
2531 TnyAccount *account,
2534 DndFolderInfo *info = NULL;
2535 ModestMailOperation *mail_op;
2537 info = (DndFolderInfo *) user_data;
2539 if (err || canceled) {
2540 dnd_on_connection_failed_destroyer (info, parent_window, account);
2544 /* Do the mail operation */
2545 mail_op = modest_mail_operation_new_with_error_handling ((GObject *) parent_window,
2546 modest_ui_actions_move_folder_error_handler,
2547 info->src_folder, NULL);
2549 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
2552 /* Transfer the folder */
2553 modest_mail_operation_xfer_folder (mail_op,
2554 TNY_FOLDER (info->src_folder),
2556 info->helper->delete_source,
2558 info->helper->folder_view);
2561 g_object_unref (G_OBJECT (mail_op));
2562 dnd_helper_destroyer (info->helper);
2563 dnd_folder_info_destroyer (info);
2568 drag_and_drop_from_folder_view_dst_folder_performer (gboolean canceled,
2570 GtkWindow *parent_window,
2571 TnyAccount *account,
2574 DndFolderInfo *info = NULL;
2576 info = (DndFolderInfo *) user_data;
2578 if (err || canceled) {
2579 dnd_on_connection_failed_destroyer (info, parent_window, account);
2583 /* Connect to source folder and perform the copy/move */
2584 modest_platform_connect_if_remote_and_perform (NULL, TRUE,
2586 drag_and_drop_from_folder_view_src_folder_performer,
2591 * This function is used by drag_data_received_cb to manage drag and
2592 * drop of a folder, i.e, and drag from the folder view to the same
2596 drag_and_drop_from_folder_view (GtkTreeModel *source_model,
2597 GtkTreeModel *dest_model,
2598 GtkTreePath *dest_row,
2599 GtkSelectionData *selection_data,
2602 GtkTreeIter dest_iter, iter;
2603 TnyFolderStore *dest_folder = NULL;
2604 TnyFolderStore *folder = NULL;
2605 gboolean forbidden = FALSE;
2607 DndFolderInfo *info = NULL;
2609 win = modest_window_mgr_get_main_window (modest_runtime_get_window_mgr(), FALSE); /* don't create */
2611 g_warning ("%s: BUG: no main window", __FUNCTION__);
2612 dnd_helper_destroyer (helper);
2617 /* check the folder rules for the destination */
2618 folder = tree_path_to_folder (dest_model, dest_row);
2619 if (TNY_IS_FOLDER(folder)) {
2620 ModestTnyFolderRules rules =
2621 modest_tny_folder_get_rules (TNY_FOLDER (folder));
2622 forbidden = rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE;
2623 } else if (TNY_IS_FOLDER_STORE(folder)) {
2624 /* enable local root as destination for folders */
2625 if (!MODEST_IS_TNY_LOCAL_FOLDERS_ACCOUNT (folder) &&
2626 !modest_tny_account_is_memory_card_account (TNY_ACCOUNT (folder)))
2629 g_object_unref (folder);
2632 /* check the folder rules for the source */
2633 folder = tree_path_to_folder (source_model, helper->source_row);
2634 if (TNY_IS_FOLDER(folder)) {
2635 ModestTnyFolderRules rules =
2636 modest_tny_folder_get_rules (TNY_FOLDER (folder));
2637 forbidden = rules & MODEST_FOLDER_RULES_FOLDER_NON_MOVEABLE;
2640 g_object_unref (folder);
2644 /* Check if the drag is possible */
2645 if (forbidden || !gtk_tree_path_compare (helper->source_row, dest_row)) {
2647 modest_platform_run_information_dialog ((GtkWindow *) win,
2648 _("mail_in_ui_folder_move_target_error"),
2650 /* Restore the previous selection */
2651 folder = tree_path_to_folder (source_model, helper->source_row);
2653 if (TNY_IS_FOLDER (folder))
2654 modest_folder_view_select_folder (helper->folder_view,
2655 TNY_FOLDER (folder), FALSE);
2656 g_object_unref (folder);
2658 dnd_helper_destroyer (helper);
2663 gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
2664 gtk_tree_model_get (dest_model, &dest_iter,
2667 gtk_tree_model_get_iter (source_model, &iter, helper->source_row);
2668 gtk_tree_model_get (source_model, &iter,
2672 /* Create the info for the performer */
2673 info = g_slice_new0 (DndFolderInfo);
2674 info->src_folder = g_object_ref (folder);
2675 info->dst_folder = g_object_ref (dest_folder);
2676 info->helper = helper;
2678 /* Connect to the destination folder and perform the copy/move */
2679 modest_platform_connect_if_remote_and_perform (GTK_WINDOW (win), TRUE,
2681 drag_and_drop_from_folder_view_dst_folder_performer,
2685 g_object_unref (dest_folder);
2686 g_object_unref (folder);
2690 * This function receives the data set by the "drag-data-get" signal
2691 * handler. This information comes within the #GtkSelectionData. This
2692 * function will manage both the drags of folders of the treeview and
2693 * drags of headers of the header view widget.
2696 on_drag_data_received (GtkWidget *widget,
2697 GdkDragContext *context,
2700 GtkSelectionData *selection_data,
2705 GtkWidget *source_widget;
2706 GtkTreeModel *dest_model, *source_model;
2707 GtkTreePath *source_row, *dest_row;
2708 GtkTreeViewDropPosition pos;
2709 gboolean delete_source = FALSE;
2710 gboolean success = FALSE;
2712 /* Do not allow further process */
2713 g_signal_stop_emission_by_name (widget, "drag-data-received");
2714 source_widget = gtk_drag_get_source_widget (context);
2716 /* Get the action */
2717 if (context->action == GDK_ACTION_MOVE) {
2718 delete_source = TRUE;
2720 /* Notify that there is no folder selected. We need to
2721 do this in order to update the headers view (and
2722 its monitors, because when moving, the old folder
2723 won't longer exist. We can not wait for the end of
2724 the operation, because the operation won't start if
2725 the folder is in use */
2726 if (source_widget == widget) {
2727 GtkTreeSelection *sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
2728 gtk_tree_selection_unselect_all (sel);
2732 /* Check if the get_data failed */
2733 if (selection_data == NULL || selection_data->length < 0)
2736 /* Select the destination model */
2737 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
2739 /* Get the path to the destination row. Can not call
2740 gtk_tree_view_get_drag_dest_row() because the source row
2741 is not selected anymore */
2742 gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget), x, y,
2745 /* Only allow drops IN other rows */
2747 pos == GTK_TREE_VIEW_DROP_BEFORE ||
2748 pos == GTK_TREE_VIEW_DROP_AFTER)
2752 /* Drags from the header view */
2753 if (source_widget != widget) {
2754 source_model = gtk_tree_view_get_model (GTK_TREE_VIEW (source_widget));
2756 drag_and_drop_from_header_view (source_model,
2761 DndHelper *helper = NULL;
2763 /* Get the source model and row */
2764 gtk_tree_get_row_drag_data (selection_data,
2768 /* Create the helper */
2769 helper = g_slice_new0 (DndHelper);
2770 helper->delete_source = delete_source;
2771 helper->source_row = gtk_tree_path_copy (source_row);
2772 helper->folder_view = MODEST_FOLDER_VIEW (widget);
2774 drag_and_drop_from_folder_view (source_model,
2780 gtk_tree_path_free (source_row);
2784 gtk_tree_path_free (dest_row);
2787 /* Finish the drag and drop */
2788 gtk_drag_finish (context, success, FALSE, time);
2792 * We define a "drag-drop" signal handler because we do not want to
2793 * use the default one, because the default one always calls
2794 * gtk_drag_finish and we prefer to do it in the "drag-data-received"
2795 * signal handler, because there we have all the information available
2796 * to know if the dnd was a success or not.
2799 drag_drop_cb (GtkWidget *widget,
2800 GdkDragContext *context,
2808 if (!context->targets)
2811 /* Check if we're dragging a folder row */
2812 target = gtk_drag_dest_find_target (widget, context, NULL);
2814 /* Request the data from the source. */
2815 gtk_drag_get_data(widget, context, target, time);
2821 * This function expands a node of a tree view if it's not expanded
2822 * yet. Not sure why it needs the threads stuff, but gtk+`example code
2823 * does that, so that's why they're here.
2826 expand_row_timeout (gpointer data)
2828 GtkTreeView *tree_view = data;
2829 GtkTreePath *dest_path = NULL;
2830 GtkTreeViewDropPosition pos;
2831 gboolean result = FALSE;
2833 gdk_threads_enter ();
2835 gtk_tree_view_get_drag_dest_row (tree_view,
2840 (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER ||
2841 pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)) {
2842 gtk_tree_view_expand_row (tree_view, dest_path, FALSE);
2843 gtk_tree_path_free (dest_path);
2847 gtk_tree_path_free (dest_path);
2852 gdk_threads_leave ();
2858 * This function is called whenever the pointer is moved over a widget
2859 * while dragging some data. It installs a timeout that will expand a
2860 * node of the treeview if not expanded yet. This function also calls
2861 * gdk_drag_status in order to set the suggested action that will be
2862 * used by the "drag-data-received" signal handler to know if we
2863 * should do a move or just a copy of the data.
2866 on_drag_motion (GtkWidget *widget,
2867 GdkDragContext *context,
2873 GtkTreeViewDropPosition pos;
2874 GtkTreePath *dest_row;
2875 GtkTreeModel *dest_model;
2876 ModestFolderViewPrivate *priv;
2877 GdkDragAction suggested_action;
2878 gboolean valid_location = FALSE;
2879 TnyFolderStore *folder = NULL;
2881 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
2883 if (priv->timer_expander != 0) {
2884 g_source_remove (priv->timer_expander);
2885 priv->timer_expander = 0;
2888 gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
2893 /* Do not allow drops between folders */
2895 pos == GTK_TREE_VIEW_DROP_BEFORE ||
2896 pos == GTK_TREE_VIEW_DROP_AFTER) {
2897 gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW (widget), NULL, 0);
2898 gdk_drag_status(context, 0, time);
2899 valid_location = FALSE;
2902 valid_location = TRUE;
2905 /* Check that the destination folder is writable */
2906 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
2907 folder = tree_path_to_folder (dest_model, dest_row);
2908 if (folder && TNY_IS_FOLDER (folder)) {
2909 ModestTnyFolderRules rules = modest_tny_folder_get_rules(TNY_FOLDER (folder));
2911 if (rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE) {
2912 valid_location = FALSE;
2917 /* Expand the selected row after 1/2 second */
2918 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), dest_row)) {
2919 priv->timer_expander = g_timeout_add (500, expand_row_timeout, widget);
2921 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), dest_row, pos);
2923 /* Select the desired action. By default we pick MOVE */
2924 suggested_action = GDK_ACTION_MOVE;
2926 if (context->actions == GDK_ACTION_COPY)
2927 gdk_drag_status(context, GDK_ACTION_COPY, time);
2928 else if (context->actions == GDK_ACTION_MOVE)
2929 gdk_drag_status(context, GDK_ACTION_MOVE, time);
2930 else if (context->actions & suggested_action)
2931 gdk_drag_status(context, suggested_action, time);
2933 gdk_drag_status(context, GDK_ACTION_DEFAULT, time);
2937 g_object_unref (folder);
2939 gtk_tree_path_free (dest_row);
2941 g_signal_stop_emission_by_name (widget, "drag-motion");
2943 return valid_location;
2947 * This function sets the treeview as a source and a target for dnd
2948 * events. It also connects all the requirede signals.
2951 setup_drag_and_drop (GtkTreeView *self)
2953 /* Set up the folder view as a dnd destination. Set only the
2954 highlight flag, otherwise gtk will have a different
2956 #ifdef MODEST_TOOLKIT_HILDON2
2959 gtk_drag_dest_set (GTK_WIDGET (self),
2960 GTK_DEST_DEFAULT_HIGHLIGHT,
2961 folder_view_drag_types,
2962 G_N_ELEMENTS (folder_view_drag_types),
2963 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2965 g_signal_connect (G_OBJECT (self),
2966 "drag_data_received",
2967 G_CALLBACK (on_drag_data_received),
2971 /* Set up the treeview as a dnd source */
2972 gtk_drag_source_set (GTK_WIDGET (self),
2974 folder_view_drag_types,
2975 G_N_ELEMENTS (folder_view_drag_types),
2976 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2978 g_signal_connect (G_OBJECT (self),
2980 G_CALLBACK (on_drag_motion),
2983 g_signal_connect (G_OBJECT (self),
2985 G_CALLBACK (on_drag_data_get),
2988 g_signal_connect (G_OBJECT (self),
2990 G_CALLBACK (drag_drop_cb),
2995 * This function manages the navigation through the folders using the
2996 * keyboard or the hardware keys in the device
2999 on_key_pressed (GtkWidget *self,
3003 GtkTreeSelection *selection;
3005 GtkTreeModel *model;
3006 gboolean retval = FALSE;
3008 /* Up and Down are automatically managed by the treeview */
3009 if (event->keyval == GDK_Return) {
3010 /* Expand/Collapse the selected row */
3011 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
3012 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
3015 path = gtk_tree_model_get_path (model, &iter);
3017 if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (self), path))
3018 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
3020 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
3021 gtk_tree_path_free (path);
3023 /* No further processing */
3031 * We listen to the changes in the local folder account name key,
3032 * because we want to show the right name in the view. The local
3033 * folder account name corresponds to the device name in the Maemo
3034 * version. We do this because we do not want to query gconf on each
3035 * tree view refresh. It's better to cache it and change whenever
3039 on_configuration_key_changed (ModestConf* conf,
3041 ModestConfEvent event,
3042 ModestConfNotificationId id,
3043 ModestFolderView *self)
3045 ModestFolderViewPrivate *priv;
3048 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
3049 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
3051 if (!strcmp (key, MODEST_CONF_DEVICE_NAME)) {
3052 g_free (priv->local_account_name);
3054 if (event == MODEST_CONF_EVENT_KEY_UNSET)
3055 priv->local_account_name = g_strdup (MODEST_LOCAL_FOLDERS_DEFAULT_DISPLAY_NAME);
3057 priv->local_account_name = modest_conf_get_string (modest_runtime_get_conf(),
3058 MODEST_CONF_DEVICE_NAME, NULL);
3060 /* Force a redraw */
3061 #if GTK_CHECK_VERSION(2, 8, 0)
3062 GtkTreeViewColumn * tree_column;
3064 tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self),
3066 gtk_tree_view_column_queue_resize (tree_column);
3068 gtk_widget_queue_draw (GTK_WIDGET (self));
3074 modest_folder_view_set_style (ModestFolderView *self,
3075 ModestFolderViewStyle style)
3077 ModestFolderViewPrivate *priv;
3079 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
3080 g_return_if_fail (style == MODEST_FOLDER_VIEW_STYLE_SHOW_ALL ||
3081 style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE);
3083 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
3086 priv->style = style;
3090 modest_folder_view_set_account_id_of_visible_server_account (ModestFolderView *self,
3091 const gchar *account_id)
3093 ModestFolderViewPrivate *priv;
3094 GtkTreeModel *model;
3096 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
3098 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
3100 /* This will be used by the filter_row callback,
3101 * to decided which rows to show: */
3102 if (priv->visible_account_id) {
3103 g_free (priv->visible_account_id);
3104 priv->visible_account_id = NULL;
3107 priv->visible_account_id = g_strdup (account_id);
3110 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
3111 if (GTK_IS_TREE_MODEL_FILTER (model))
3112 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
3114 /* Save settings to gconf */
3115 modest_widget_memory_save (modest_runtime_get_conf (), G_OBJECT(self),
3116 MODEST_CONF_FOLDER_VIEW_KEY);
3120 modest_folder_view_get_account_id_of_visible_server_account (ModestFolderView *self)
3122 ModestFolderViewPrivate *priv;
3124 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW(self), NULL);
3126 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
3128 return (const gchar *) priv->visible_account_id;
3132 find_inbox_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *inbox_iter)
3136 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
3138 gtk_tree_model_get (model, iter,
3142 gboolean result = FALSE;
3143 if (type == TNY_FOLDER_TYPE_INBOX) {
3147 *inbox_iter = *iter;
3151 if (gtk_tree_model_iter_children (model, &child, iter)) {
3152 if (find_inbox_iter (model, &child, inbox_iter))
3156 } while (gtk_tree_model_iter_next (model, iter));
3165 modest_folder_view_select_first_inbox_or_local (ModestFolderView *self)
3167 GtkTreeModel *model;
3168 GtkTreeIter iter, inbox_iter;
3169 GtkTreeSelection *sel;
3170 GtkTreePath *path = NULL;
3172 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
3174 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
3178 expand_root_items (self);
3179 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
3181 if (!gtk_tree_model_get_iter_first (model, &iter)) {
3182 g_warning ("%s: model is empty", __FUNCTION__);
3186 if (find_inbox_iter (model, &iter, &inbox_iter))
3187 path = gtk_tree_model_get_path (model, &inbox_iter);
3189 path = gtk_tree_path_new_first ();
3191 /* Select the row and free */
3192 gtk_tree_view_set_cursor (GTK_TREE_VIEW (self), path, NULL, FALSE);
3193 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (self), path, NULL, FALSE, 0.0, 0.0);
3194 gtk_tree_path_free (path);
3197 gtk_widget_grab_focus (GTK_WIDGET(self));
3203 find_folder_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *folder_iter,
3208 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
3209 TnyFolder* a_folder;
3212 gtk_tree_model_get (model, iter,
3213 INSTANCE_COLUMN, &a_folder,
3219 if (folder == a_folder) {
3220 g_object_unref (a_folder);
3221 *folder_iter = *iter;
3224 g_object_unref (a_folder);
3226 if (gtk_tree_model_iter_children (model, &child, iter)) {
3227 if (find_folder_iter (model, &child, folder_iter, folder))
3231 } while (gtk_tree_model_iter_next (model, iter));
3236 #ifndef MODEST_TOOLKIT_HILDON2
3238 on_row_inserted_maybe_select_folder (GtkTreeModel *tree_model,
3241 ModestFolderView *self)
3243 ModestFolderViewPrivate *priv = NULL;
3244 GtkTreeSelection *sel;
3245 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
3246 GObject *instance = NULL;
3248 if (!MODEST_IS_FOLDER_VIEW(self))
3251 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
3253 priv->reexpand = TRUE;
3255 gtk_tree_model_get (tree_model, iter,
3257 INSTANCE_COLUMN, &instance,
3263 if (type == TNY_FOLDER_TYPE_INBOX && priv->folder_to_select == NULL) {
3264 priv->folder_to_select = g_object_ref (instance);
3266 g_object_unref (instance);
3268 if (priv->folder_to_select) {
3270 if (!modest_folder_view_select_folder (self, priv->folder_to_select,
3273 path = gtk_tree_model_get_path (tree_model, iter);
3274 gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
3276 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
3278 gtk_tree_selection_select_iter (sel, iter);
3279 gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
3281 gtk_tree_path_free (path);
3285 modest_folder_view_disable_next_folder_selection (self);
3287 /* Refilter the model */
3288 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (tree_model));
3294 modest_folder_view_disable_next_folder_selection (ModestFolderView *self)
3296 ModestFolderViewPrivate *priv;
3298 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
3300 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
3302 if (priv->folder_to_select)
3303 g_object_unref(priv->folder_to_select);
3305 priv->folder_to_select = NULL;
3309 modest_folder_view_select_folder (ModestFolderView *self, TnyFolder *folder,
3310 gboolean after_change)
3312 GtkTreeModel *model;
3313 GtkTreeIter iter, folder_iter;
3314 GtkTreeSelection *sel;
3315 ModestFolderViewPrivate *priv = NULL;
3317 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW (self), FALSE);
3318 g_return_val_if_fail (folder && TNY_IS_FOLDER (folder), FALSE);
3320 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
3323 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
3324 gtk_tree_selection_unselect_all (sel);
3326 if (priv->folder_to_select)
3327 g_object_unref(priv->folder_to_select);
3328 priv->folder_to_select = TNY_FOLDER(g_object_ref(folder));
3332 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
3337 /* Refilter the model, before selecting the folder */
3338 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
3340 if (!gtk_tree_model_get_iter_first (model, &iter)) {
3341 g_warning ("%s: model is empty", __FUNCTION__);
3345 if (find_folder_iter (model, &iter, &folder_iter, folder)) {
3348 path = gtk_tree_model_get_path (model, &folder_iter);
3349 gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
3351 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
3352 gtk_tree_selection_select_iter (sel, &folder_iter);
3353 gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
3355 gtk_tree_path_free (path);
3363 modest_folder_view_copy_selection (ModestFolderView *self)
3365 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
3367 /* Copy selection */
3368 _clipboard_set_selected_data (self, FALSE);
3372 modest_folder_view_cut_selection (ModestFolderView *folder_view)
3374 ModestFolderViewPrivate *priv = NULL;
3375 GtkTreeModel *model = NULL;
3376 const gchar **hidding = NULL;
3377 guint i, n_selected;
3379 g_return_if_fail (folder_view && MODEST_IS_FOLDER_VIEW (folder_view));
3380 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
3382 /* Copy selection */
3383 if (!_clipboard_set_selected_data (folder_view, TRUE))
3386 /* Get hidding ids */
3387 hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected);
3389 /* Clear hidding array created by previous cut operation */
3390 _clear_hidding_filter (MODEST_FOLDER_VIEW (folder_view));
3392 /* Copy hidding array */
3393 priv->n_selected = n_selected;
3394 priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
3395 for (i=0; i < n_selected; i++)
3396 priv->hidding_ids[i] = g_strdup(hidding[i]);
3398 /* Hide cut folders */
3399 model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
3400 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
3404 modest_folder_view_copy_model (ModestFolderView *folder_view_src,
3405 ModestFolderView *folder_view_dst)
3407 GtkTreeModel *filter_model = NULL;
3408 GtkTreeModel *model = NULL;
3409 GtkTreeModel *new_filter_model = NULL;
3411 g_return_if_fail (folder_view_src && MODEST_IS_FOLDER_VIEW (folder_view_src));
3412 g_return_if_fail (folder_view_dst && MODEST_IS_FOLDER_VIEW (folder_view_dst));
3415 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view_src));
3416 model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER(filter_model));
3418 /* Build new filter model */
3419 new_filter_model = gtk_tree_model_filter_new (model, NULL);
3420 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (new_filter_model),
3424 /* Set copied model */
3425 gtk_tree_view_set_model (GTK_TREE_VIEW (folder_view_dst), new_filter_model);
3426 #ifndef MODEST_TOOLKIT_HILDON2
3427 g_signal_connect (G_OBJECT(new_filter_model), "row-inserted",
3428 (GCallback) on_row_inserted_maybe_select_folder, folder_view_dst);
3432 g_object_unref (new_filter_model);
3436 modest_folder_view_show_non_move_folders (ModestFolderView *folder_view,
3439 GtkTreeModel *model = NULL;
3440 ModestFolderViewPrivate* priv;
3442 g_return_if_fail (folder_view && MODEST_IS_FOLDER_VIEW (folder_view));
3444 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
3445 priv->show_non_move = show;
3446 /* modest_folder_view_update_model(folder_view, */
3447 /* TNY_ACCOUNT_STORE(modest_runtime_get_account_store())); */
3449 /* Hide special folders */
3450 model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
3451 if (GTK_IS_TREE_MODEL_FILTER (model)) {
3452 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
3456 /* Returns FALSE if it did not selected anything */
3458 _clipboard_set_selected_data (ModestFolderView *folder_view,
3461 ModestFolderViewPrivate *priv = NULL;
3462 TnyFolderStore *folder = NULL;
3463 gboolean retval = FALSE;
3465 g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (folder_view), FALSE);
3466 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
3468 /* Set selected data on clipboard */
3469 g_return_val_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard), FALSE);
3470 folder = modest_folder_view_get_selected (folder_view);
3472 /* Do not allow to select an account */
3473 if (TNY_IS_FOLDER (folder)) {
3474 modest_email_clipboard_set_data (priv->clipboard, TNY_FOLDER(folder), NULL, delete);
3479 g_object_unref (folder);
3485 _clear_hidding_filter (ModestFolderView *folder_view)
3487 ModestFolderViewPrivate *priv;
3490 g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view));
3491 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
3493 if (priv->hidding_ids != NULL) {
3494 for (i=0; i < priv->n_selected; i++)
3495 g_free (priv->hidding_ids[i]);
3496 g_free(priv->hidding_ids);
3502 on_display_name_changed (ModestAccountMgr *mgr,
3503 const gchar *account,
3506 ModestFolderView *self;
3508 self = MODEST_FOLDER_VIEW (user_data);
3510 /* Force a redraw */
3511 #if GTK_CHECK_VERSION(2, 8, 0)
3512 GtkTreeViewColumn * tree_column;
3514 tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self),
3516 gtk_tree_view_column_queue_resize (tree_column);
3518 gtk_widget_queue_draw (GTK_WIDGET (self));
3523 modest_folder_view_set_cell_style (ModestFolderView *self,
3524 ModestFolderViewCellStyle cell_style)
3526 ModestFolderViewPrivate *priv = NULL;
3528 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
3529 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
3531 priv->cell_style = cell_style;
3533 g_object_set (G_OBJECT (priv->messages_renderer),
3534 "visible", (cell_style == MODEST_FOLDER_VIEW_CELL_STYLE_COMPACT),
3537 gtk_widget_queue_draw (GTK_WIDGET (self));
3541 update_style (ModestFolderView *self)
3543 ModestFolderViewPrivate *priv;
3544 GdkColor style_color;
3545 PangoAttrList *attr_list;
3547 PangoAttribute *attr;
3549 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
3550 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
3554 attr_list = pango_attr_list_new ();
3555 if (!gtk_style_lookup_color (GTK_WIDGET (self)->style, "SecondaryTextColor", &style_color)) {
3556 gdk_color_parse ("grey", &style_color);
3558 attr = pango_attr_foreground_new (style_color.red, style_color.green, style_color.blue);
3559 pango_attr_list_insert (attr_list, attr);
3562 style = gtk_rc_get_style_by_paths (gtk_widget_get_settings
3564 "SmallSystemFont", NULL,
3566 attr = pango_attr_font_desc_new (pango_font_description_copy
3567 (style->font_desc));
3568 pango_attr_list_insert (attr_list, attr);
3570 g_object_set (G_OBJECT (priv->messages_renderer),
3571 "foreground-gdk", &style_color,
3572 "foreground-set", TRUE,
3573 "attributes", attr_list,
3575 pango_attr_list_unref (attr_list);
3579 on_notify_style (GObject *obj, GParamSpec *spec, gpointer userdata)
3581 if (strcmp ("style", spec->name) == 0) {
3582 update_style (MODEST_FOLDER_VIEW (obj));
3583 gtk_widget_queue_draw (GTK_WIDGET (obj));
3588 modest_folder_view_set_filter (ModestFolderView *self,
3589 ModestFolderViewFilter filter)
3591 ModestFolderViewPrivate *priv;
3592 GtkTreeModel *filter_model;
3594 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
3595 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
3597 priv->filter |= filter;
3599 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
3600 if (GTK_IS_TREE_MODEL_FILTER(filter_model)) {
3601 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
3606 modest_folder_view_unset_filter (ModestFolderView *self,
3607 ModestFolderViewFilter filter)
3609 ModestFolderViewPrivate *priv;
3610 GtkTreeModel *filter_model;
3612 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
3613 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
3615 priv->filter &= ~filter;
3617 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
3618 if (GTK_IS_TREE_MODEL_FILTER(filter_model)) {
3619 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));