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 /* note: we cannot reliably get the counts from the
608 * tree model, we need to use explicit calls on
609 * tny_folder for some reason. Select the number to
610 * show: the unread or unsent messages. in case of
611 * outbox/drafts, show all */
612 if ((type == TNY_FOLDER_TYPE_DRAFTS) ||
613 (type == TNY_FOLDER_TYPE_OUTBOX) ||
614 (type == TNY_FOLDER_TYPE_MERGE)) { /* _OUTBOX actually returns _MERGE... */
615 number = tny_folder_get_all_count (TNY_FOLDER(instance));
618 number = tny_folder_get_unread_count (TNY_FOLDER(instance));
622 if (priv->cell_style == MODEST_FOLDER_VIEW_CELL_STYLE_COMPACT) {
623 item_name = g_strdup (fname);
630 /* Use bold font style if there are unread or unset messages */
632 item_name = g_strdup_printf ("%s (%d)", fname, number);
635 item_name = g_strdup (fname);
640 } else if (TNY_IS_ACCOUNT (instance)) {
641 /* If it's a server account */
642 if (modest_tny_account_is_virtual_local_folders (TNY_ACCOUNT (instance))) {
643 item_name = g_strdup (priv->local_account_name);
645 } else if (modest_tny_account_is_memory_card_account (TNY_ACCOUNT (instance))) {
646 /* fname is only correct when the items are first
647 * added to the model, not when the account is
648 * changed later, so get the name from the account
650 item_name = g_strdup (tny_account_get_name (TNY_ACCOUNT (instance)));
653 item_name = g_strdup (fname);
659 item_name = g_strdup ("unknown");
661 if (priv->cell_style == MODEST_FOLDER_VIEW_CELL_STYLE_COMPACT) {
662 gboolean multiaccount;
664 multiaccount = (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ALL);
665 /* Convert item_name to markup */
666 format_compact_style (&item_name, instance,
668 multiaccount, &use_markup);
671 if (item_name && item_weight) {
672 /* Set the name in the treeview cell: */
674 g_object_set (rendobj, "markup", item_name, NULL);
676 g_object_set (rendobj, "text", item_name, "weight", item_weight, NULL);
678 /* Notify display name observers */
679 /* TODO: What listens for this signal, and how can it use only the new name? */
680 if (((GObject *) priv->cur_folder_store) == instance) {
681 g_signal_emit (G_OBJECT(self),
682 signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
689 /* If it is a Memory card account, make sure that we have the correct name.
690 * This function will be trigerred again when the name has been retrieved: */
691 if (TNY_IS_STORE_ACCOUNT (instance) &&
692 modest_tny_account_is_memory_card_account (TNY_ACCOUNT (instance))) {
694 /* Get the account name asynchronously: */
695 GetMmcAccountNameData *callback_data =
696 g_slice_new0(GetMmcAccountNameData);
697 callback_data->self = self;
699 const gchar *name = tny_account_get_name (TNY_ACCOUNT(instance));
701 callback_data->previous_name = g_strdup (name);
703 modest_tny_account_get_mmc_account_name (TNY_STORE_ACCOUNT (instance),
704 on_get_mmc_account_name, callback_data);
708 g_object_unref (G_OBJECT (instance));
714 messages_cell_data (GtkTreeViewColumn *column,
715 GtkCellRenderer *renderer,
716 GtkTreeModel *tree_model,
720 ModestFolderView *self;
721 ModestFolderViewPrivate *priv;
722 GObject *rendobj = (GObject *) renderer;
723 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
724 GObject *instance = NULL;
725 gchar *item_name = NULL;
727 gtk_tree_model_get (tree_model, iter,
729 INSTANCE_COLUMN, &instance,
734 self = MODEST_FOLDER_VIEW (data);
735 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
738 if (type != TNY_FOLDER_TYPE_ROOT) {
742 if (modest_tny_folder_is_local_folder (TNY_FOLDER (instance)) ||
743 modest_tny_folder_is_memory_card_folder (TNY_FOLDER (instance))) {
744 type = modest_tny_folder_get_local_or_mmc_folder_type (TNY_FOLDER (instance));
746 /* Sometimes an special folder is reported by the server as
747 NORMAL, like some versions of Dovecot */
748 if (type == TNY_FOLDER_TYPE_NORMAL ||
749 type == TNY_FOLDER_TYPE_UNKNOWN) {
750 type = modest_tny_folder_guess_folder_type (TNY_FOLDER (instance));
754 /* note: we cannot reliably get the counts from the tree model, we need
755 * to use explicit calls on tny_folder for some reason.
757 /* Select the number to show: the unread or unsent messages. in case of outbox/drafts, show all */
758 if ((type == TNY_FOLDER_TYPE_DRAFTS) ||
759 (type == TNY_FOLDER_TYPE_OUTBOX) ||
760 (type == TNY_FOLDER_TYPE_MERGE)) { /* _OUTBOX actually returns _MERGE... */
761 number = tny_folder_get_all_count (TNY_FOLDER(instance));
764 number = tny_folder_get_unread_count (TNY_FOLDER(instance));
768 if (priv->cell_style == MODEST_FOLDER_VIEW_CELL_STYLE_COMPACT) {
770 item_name = g_strdup_printf (drafts?_("mcen_ti_messages"):_("mcen_ti_new_messages"),
778 item_name = g_strdup ("");
781 /* Set the name in the treeview cell: */
782 g_object_set (rendobj,"text", item_name, NULL);
790 g_object_unref (G_OBJECT (instance));
796 GdkPixbuf *pixbuf_open;
797 GdkPixbuf *pixbuf_close;
801 static inline GdkPixbuf *
802 get_composite_pixbuf (const gchar *icon_name,
804 GdkPixbuf *base_pixbuf)
806 GdkPixbuf *emblem, *retval = NULL;
808 emblem = modest_platform_get_icon (icon_name, size);
810 retval = gdk_pixbuf_copy (base_pixbuf);
811 gdk_pixbuf_composite (emblem, retval, 0, 0,
812 MIN (gdk_pixbuf_get_width (emblem),
813 gdk_pixbuf_get_width (retval)),
814 MIN (gdk_pixbuf_get_height (emblem),
815 gdk_pixbuf_get_height (retval)),
816 0, 0, 1, 1, GDK_INTERP_NEAREST, 255);
817 g_object_unref (emblem);
822 static inline ThreePixbufs *
823 get_composite_icons (const gchar *icon_code,
825 GdkPixbuf **pixbuf_open,
826 GdkPixbuf **pixbuf_close)
828 ThreePixbufs *retval;
831 *pixbuf = gdk_pixbuf_copy (modest_platform_get_icon (icon_code, FOLDER_ICON_SIZE));
834 *pixbuf_open = get_composite_pixbuf ("qgn_list_gene_fldr_exp",
839 *pixbuf_close = get_composite_pixbuf ("qgn_list_gene_fldr_clp",
843 retval = g_slice_new0 (ThreePixbufs);
845 retval->pixbuf = g_object_ref (*pixbuf);
847 retval->pixbuf_open = g_object_ref (*pixbuf_open);
849 retval->pixbuf_close = g_object_ref (*pixbuf_close);
854 static inline ThreePixbufs*
855 get_folder_icons (TnyFolderType type, GObject *instance)
857 static GdkPixbuf *inbox_pixbuf = NULL, *outbox_pixbuf = NULL,
858 *junk_pixbuf = NULL, *sent_pixbuf = NULL,
859 *trash_pixbuf = NULL, *draft_pixbuf = NULL,
860 *normal_pixbuf = NULL, *anorm_pixbuf = NULL, *mmc_pixbuf = NULL,
861 *ammc_pixbuf = NULL, *avirt_pixbuf = NULL;
863 static GdkPixbuf *inbox_pixbuf_open = NULL, *outbox_pixbuf_open = NULL,
864 *junk_pixbuf_open = NULL, *sent_pixbuf_open = NULL,
865 *trash_pixbuf_open = NULL, *draft_pixbuf_open = NULL,
866 *normal_pixbuf_open = NULL, *anorm_pixbuf_open = NULL, *mmc_pixbuf_open = NULL,
867 *ammc_pixbuf_open = NULL, *avirt_pixbuf_open = NULL;
869 static GdkPixbuf *inbox_pixbuf_close = NULL, *outbox_pixbuf_close = NULL,
870 *junk_pixbuf_close = NULL, *sent_pixbuf_close = NULL,
871 *trash_pixbuf_close = NULL, *draft_pixbuf_close = NULL,
872 *normal_pixbuf_close = NULL, *anorm_pixbuf_close = NULL, *mmc_pixbuf_close = NULL,
873 *ammc_pixbuf_close = NULL, *avirt_pixbuf_close = NULL;
875 ThreePixbufs *retval = NULL;
877 /* Sometimes an special folder is reported by the server as
878 NORMAL, like some versions of Dovecot */
879 if (type == TNY_FOLDER_TYPE_NORMAL ||
880 type == TNY_FOLDER_TYPE_UNKNOWN) {
881 type = modest_tny_folder_guess_folder_type (TNY_FOLDER (instance));
884 /* It's not enough with check the folder type. We need to
885 ensure that we're not giving a special folder icon to a
886 normal folder with the same name than a special folder */
887 if (TNY_IS_FOLDER (instance) &&
888 get_cmp_pos (type, TNY_FOLDER (instance)) == 4)
889 type = TNY_FOLDER_TYPE_NORMAL;
891 /* Remote folders should not be treated as special folders */
892 if (TNY_IS_FOLDER_STORE (instance) &&
893 !TNY_IS_ACCOUNT (instance) &&
894 type != TNY_FOLDER_TYPE_INBOX &&
895 modest_tny_folder_store_is_remote (TNY_FOLDER_STORE (instance))) {
896 #ifdef MODEST_TOOLKIT_HILDON2
897 return get_composite_icons (MODEST_FOLDER_ICON_ACCOUNT,
900 &anorm_pixbuf_close);
902 return get_composite_icons (MODEST_FOLDER_ICON_NORMAL,
905 &normal_pixbuf_close);
911 case TNY_FOLDER_TYPE_INVALID:
912 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
915 case TNY_FOLDER_TYPE_ROOT:
916 if (TNY_IS_ACCOUNT (instance)) {
918 if (modest_tny_account_is_virtual_local_folders (TNY_ACCOUNT (instance))) {
919 retval = get_composite_icons (MODEST_FOLDER_ICON_LOCAL_FOLDERS,
922 &avirt_pixbuf_close);
924 const gchar *account_id = tny_account_get_id (TNY_ACCOUNT (instance));
926 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
927 retval = get_composite_icons (MODEST_FOLDER_ICON_MMC,
932 retval = get_composite_icons (MODEST_FOLDER_ICON_ACCOUNT,
935 &anorm_pixbuf_close);
940 case TNY_FOLDER_TYPE_INBOX:
941 retval = get_composite_icons (MODEST_FOLDER_ICON_INBOX,
944 &inbox_pixbuf_close);
946 case TNY_FOLDER_TYPE_OUTBOX:
947 retval = get_composite_icons (MODEST_FOLDER_ICON_OUTBOX,
950 &outbox_pixbuf_close);
952 case TNY_FOLDER_TYPE_JUNK:
953 retval = get_composite_icons (MODEST_FOLDER_ICON_JUNK,
958 case TNY_FOLDER_TYPE_SENT:
959 retval = get_composite_icons (MODEST_FOLDER_ICON_SENT,
964 case TNY_FOLDER_TYPE_TRASH:
965 retval = get_composite_icons (MODEST_FOLDER_ICON_TRASH,
968 &trash_pixbuf_close);
970 case TNY_FOLDER_TYPE_DRAFTS:
971 retval = get_composite_icons (MODEST_FOLDER_ICON_DRAFTS,
974 &draft_pixbuf_close);
976 case TNY_FOLDER_TYPE_ARCHIVE:
977 retval = get_composite_icons (MODEST_FOLDER_ICON_MMC_FOLDER,
982 case TNY_FOLDER_TYPE_NORMAL:
984 /* Memory card folders could have an special icon */
985 if (modest_tny_folder_is_memory_card_folder (TNY_FOLDER (instance))) {
986 retval = get_composite_icons (MODEST_FOLDER_ICON_MMC_FOLDER,
991 retval = get_composite_icons (MODEST_FOLDER_ICON_NORMAL,
994 &normal_pixbuf_close);
1003 free_pixbufs (ThreePixbufs *pixbufs)
1005 if (pixbufs->pixbuf)
1006 g_object_unref (pixbufs->pixbuf);
1007 if (pixbufs->pixbuf_open)
1008 g_object_unref (pixbufs->pixbuf_open);
1009 if (pixbufs->pixbuf_close)
1010 g_object_unref (pixbufs->pixbuf_close);
1011 g_slice_free (ThreePixbufs, pixbufs);
1015 icon_cell_data (GtkTreeViewColumn *column,
1016 GtkCellRenderer *renderer,
1017 GtkTreeModel *tree_model,
1021 GObject *rendobj = NULL, *instance = NULL;
1022 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1023 gboolean has_children;
1024 ThreePixbufs *pixbufs;
1026 rendobj = (GObject *) renderer;
1028 gtk_tree_model_get (tree_model, iter,
1030 INSTANCE_COLUMN, &instance,
1036 has_children = gtk_tree_model_iter_has_child (tree_model, iter);
1037 pixbufs = get_folder_icons (type, instance);
1038 g_object_unref (instance);
1041 g_object_set (rendobj, "pixbuf", pixbufs->pixbuf, NULL);
1044 g_object_set (rendobj, "pixbuf-expander-open", pixbufs->pixbuf_open, NULL);
1045 g_object_set (rendobj, "pixbuf-expander-closed", pixbufs->pixbuf_close, NULL);
1048 free_pixbufs (pixbufs);
1052 add_columns (GtkWidget *treeview)
1054 GtkTreeViewColumn *column;
1055 GtkCellRenderer *renderer;
1056 GtkTreeSelection *sel;
1057 ModestFolderViewPrivate *priv;
1059 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(treeview);
1062 column = gtk_tree_view_column_new ();
1064 /* Set icon and text render function */
1065 renderer = gtk_cell_renderer_pixbuf_new();
1066 #ifdef MODEST_TOOLKIT_HILDON2
1067 g_object_set (renderer,
1068 "xpad", MODEST_MARGIN_DEFAULT,
1069 "ypad", MODEST_MARGIN_DEFAULT,
1072 gtk_tree_view_column_pack_start (column, renderer, FALSE);
1073 gtk_tree_view_column_set_cell_data_func(column, renderer,
1074 icon_cell_data, treeview, NULL);
1076 renderer = gtk_cell_renderer_text_new();
1077 g_object_set (renderer,
1078 #ifdef MODEST_TOOLKIT_HILDON2
1079 "ellipsize", PANGO_ELLIPSIZE_MIDDLE,
1080 "ypad", MODEST_MARGIN_DEFAULT,
1082 "ellipsize", PANGO_ELLIPSIZE_END,
1084 "ellipsize-set", TRUE, NULL);
1085 gtk_tree_view_column_pack_start (column, renderer, TRUE);
1086 gtk_tree_view_column_set_cell_data_func(column, renderer,
1087 text_cell_data, treeview, NULL);
1089 priv->messages_renderer = gtk_cell_renderer_text_new ();
1090 g_object_set (priv->messages_renderer,
1091 #ifdef MODEST_TOOLKIT_HILDON2
1093 "ypad", MODEST_MARGIN_DEFAULT,
1094 "xpad", MODEST_MARGIN_DOUBLE,
1096 "scale", PANGO_SCALE_X_SMALL,
1099 "alignment", PANGO_ALIGN_RIGHT,
1103 gtk_tree_view_column_pack_start (column, priv->messages_renderer, FALSE);
1104 gtk_tree_view_column_set_cell_data_func(column, priv->messages_renderer,
1105 messages_cell_data, treeview, NULL);
1107 /* Set selection mode */
1108 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(treeview));
1109 gtk_tree_selection_set_mode (sel, GTK_SELECTION_SINGLE);
1111 /* Set treeview appearance */
1112 gtk_tree_view_column_set_spacing (column, 2);
1113 gtk_tree_view_column_set_resizable (column, TRUE);
1114 gtk_tree_view_column_set_fixed_width (column, TRUE);
1115 gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(treeview), FALSE);
1116 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(treeview), FALSE);
1119 gtk_tree_view_append_column (GTK_TREE_VIEW(treeview),column);
1123 modest_folder_view_init (ModestFolderView *obj)
1125 ModestFolderViewPrivate *priv;
1128 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
1130 priv->timer_expander = 0;
1131 priv->account_store = NULL;
1133 priv->style = MODEST_FOLDER_VIEW_STYLE_SHOW_ALL;
1134 priv->cur_folder_store = NULL;
1135 priv->visible_account_id = NULL;
1136 priv->folder_to_select = NULL;
1137 priv->outbox_deleted_handler = 0;
1138 priv->reexpand = TRUE;
1140 /* Initialize the local account name */
1141 conf = modest_runtime_get_conf();
1142 priv->local_account_name = modest_conf_get_string (conf, MODEST_CONF_DEVICE_NAME, NULL);
1144 /* Init email clipboard */
1145 priv->clipboard = modest_runtime_get_email_clipboard ();
1146 priv->hidding_ids = NULL;
1147 priv->n_selected = 0;
1148 priv->filter = MODEST_FOLDER_VIEW_FILTER_NONE;
1149 priv->reselect = FALSE;
1150 priv->show_non_move = TRUE;
1152 /* Build treeview */
1153 add_columns (GTK_WIDGET (obj));
1155 /* Setup drag and drop */
1156 setup_drag_and_drop (GTK_TREE_VIEW(obj));
1158 /* Connect signals */
1159 g_signal_connect (G_OBJECT (obj),
1161 G_CALLBACK (on_key_pressed), NULL);
1163 priv->display_name_changed_signal =
1164 g_signal_connect (modest_runtime_get_account_mgr (),
1165 "display_name_changed",
1166 G_CALLBACK (on_display_name_changed),
1170 * Track changes in the local account name (in the device it
1171 * will be the device name)
1173 priv->conf_key_signal = g_signal_connect (G_OBJECT(conf),
1175 G_CALLBACK(on_configuration_key_changed),
1179 g_signal_connect (G_OBJECT (obj), "notify::style", G_CALLBACK (on_notify_style), (gpointer) obj);
1185 tny_account_store_view_init (gpointer g, gpointer iface_data)
1187 TnyAccountStoreViewIface *klass = (TnyAccountStoreViewIface *)g;
1189 klass->set_account_store = modest_folder_view_set_account_store;
1193 modest_folder_view_finalize (GObject *obj)
1195 ModestFolderViewPrivate *priv;
1196 GtkTreeSelection *sel;
1197 TnyAccount *local_account;
1199 g_return_if_fail (obj);
1201 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
1203 if (priv->timer_expander != 0) {
1204 g_source_remove (priv->timer_expander);
1205 priv->timer_expander = 0;
1208 local_account = (TnyAccount *)
1209 modest_tny_account_store_get_local_folders_account (modest_runtime_get_account_store ());
1210 if (local_account) {
1211 if (g_signal_handler_is_connected (local_account,
1212 priv->outbox_deleted_handler))
1213 g_signal_handler_disconnect (local_account,
1214 priv->outbox_deleted_handler);
1215 g_object_unref (local_account);
1218 if (priv->account_store) {
1219 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
1220 priv->account_inserted_signal);
1221 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
1222 priv->account_removed_signal);
1223 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
1224 priv->account_changed_signal);
1225 g_object_unref (G_OBJECT(priv->account_store));
1226 priv->account_store = NULL;
1229 if (g_signal_handler_is_connected (modest_runtime_get_account_mgr (),
1230 priv->display_name_changed_signal)) {
1231 g_signal_handler_disconnect (modest_runtime_get_account_mgr (),
1232 priv->display_name_changed_signal);
1233 priv->display_name_changed_signal = 0;
1237 g_object_unref (G_OBJECT (priv->query));
1241 if (priv->folder_to_select) {
1242 g_object_unref (G_OBJECT(priv->folder_to_select));
1243 priv->folder_to_select = NULL;
1246 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(obj));
1248 g_signal_handler_disconnect (G_OBJECT(sel), priv->changed_signal);
1250 g_free (priv->local_account_name);
1251 g_free (priv->visible_account_id);
1253 if (priv->conf_key_signal) {
1254 g_signal_handler_disconnect (modest_runtime_get_conf (),
1255 priv->conf_key_signal);
1256 priv->conf_key_signal = 0;
1259 if (priv->cur_folder_store) {
1260 g_object_unref (priv->cur_folder_store);
1261 priv->cur_folder_store = NULL;
1264 /* Clear hidding array created by cut operation */
1265 _clear_hidding_filter (MODEST_FOLDER_VIEW (obj));
1267 G_OBJECT_CLASS(parent_class)->finalize (obj);
1272 modest_folder_view_set_account_store (TnyAccountStoreView *self, TnyAccountStore *account_store)
1274 ModestFolderViewPrivate *priv;
1277 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
1278 g_return_if_fail (TNY_IS_ACCOUNT_STORE (account_store));
1280 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1281 device = tny_account_store_get_device (account_store);
1283 if (G_UNLIKELY (priv->account_store)) {
1285 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
1286 priv->account_inserted_signal))
1287 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
1288 priv->account_inserted_signal);
1289 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
1290 priv->account_removed_signal))
1291 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
1292 priv->account_removed_signal);
1293 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
1294 priv->account_changed_signal))
1295 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
1296 priv->account_changed_signal);
1297 g_object_unref (G_OBJECT (priv->account_store));
1300 priv->account_store = g_object_ref (G_OBJECT (account_store));
1302 priv->account_removed_signal =
1303 g_signal_connect (G_OBJECT(account_store), "account_removed",
1304 G_CALLBACK (on_account_removed), self);
1306 priv->account_inserted_signal =
1307 g_signal_connect (G_OBJECT(account_store), "account_inserted",
1308 G_CALLBACK (on_account_inserted), self);
1310 priv->account_changed_signal =
1311 g_signal_connect (G_OBJECT(account_store), "account_changed",
1312 G_CALLBACK (on_account_changed), self);
1314 modest_folder_view_update_model (MODEST_FOLDER_VIEW (self), account_store);
1315 priv->reselect = FALSE;
1316 modest_folder_view_select_first_inbox_or_local (MODEST_FOLDER_VIEW (self));
1318 g_object_unref (G_OBJECT (device));
1322 on_outbox_deleted_cb (ModestTnyLocalFoldersAccount *local_account,
1325 ModestFolderView *self;
1326 GtkTreeModel *model, *filter_model;
1329 self = MODEST_FOLDER_VIEW (user_data);
1331 if (!get_inner_models (self, &filter_model, NULL, &model))
1334 /* Remove outbox from model */
1335 outbox = modest_tny_local_folders_account_get_merged_outbox (local_account);
1336 tny_list_remove (TNY_LIST (model), G_OBJECT (outbox));
1337 g_object_unref (outbox);
1340 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1344 on_account_inserted (TnyAccountStore *account_store,
1345 TnyAccount *account,
1348 ModestFolderViewPrivate *priv;
1349 GtkTreeModel *model, *filter_model;
1351 /* Ignore transport account insertions, we're not showing them
1352 in the folder view */
1353 if (TNY_IS_TRANSPORT_ACCOUNT (account))
1356 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (user_data);
1359 /* If we're adding a new account, and there is no previous
1360 one, we need to select the visible server account */
1361 if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE &&
1362 !priv->visible_account_id)
1363 modest_widget_memory_restore (modest_runtime_get_conf(),
1364 G_OBJECT (user_data),
1365 MODEST_CONF_FOLDER_VIEW_KEY);
1369 if (!get_inner_models (MODEST_FOLDER_VIEW (user_data),
1370 &filter_model, NULL, &model))
1373 /* Insert the account in the model */
1374 tny_list_append (TNY_LIST (model), G_OBJECT (account));
1376 /* When the model is a list store (plain representation) the
1377 outbox is not a child of any account so we have to manually
1378 delete it because removing the local folders account won't
1379 delete it (because tny_folder_get_account() is not defined
1380 for a merge folder */
1381 if (TNY_IS_GTK_FOLDER_LIST_STORE (model) &&
1382 MODEST_IS_TNY_LOCAL_FOLDERS_ACCOUNT (account)) {
1384 priv->outbox_deleted_handler =
1385 g_signal_connect (account,
1387 G_CALLBACK (on_outbox_deleted_cb),
1391 /* Refilter the model */
1392 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1397 same_account_selected (ModestFolderView *self,
1398 TnyAccount *account)
1400 ModestFolderViewPrivate *priv;
1401 gboolean same_account = FALSE;
1403 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1405 if (priv->cur_folder_store) {
1406 TnyAccount *selected_folder_account = NULL;
1408 if (TNY_IS_FOLDER (priv->cur_folder_store)) {
1409 selected_folder_account =
1410 modest_tny_folder_get_account (TNY_FOLDER (priv->cur_folder_store));
1412 selected_folder_account =
1413 TNY_ACCOUNT (g_object_ref (priv->cur_folder_store));
1416 if (selected_folder_account == account)
1417 same_account = TRUE;
1419 g_object_unref (selected_folder_account);
1421 return same_account;
1426 * Selects the first inbox or the local account in an idle
1429 on_idle_select_first_inbox_or_local (gpointer user_data)
1431 ModestFolderView *self = MODEST_FOLDER_VIEW (user_data);
1433 gdk_threads_enter ();
1434 modest_folder_view_select_first_inbox_or_local (self);
1435 gdk_threads_leave ();
1441 on_account_changed (TnyAccountStore *account_store,
1442 TnyAccount *tny_account,
1445 ModestFolderView *self;
1446 ModestFolderViewPrivate *priv;
1447 GtkTreeModel *model, *filter_model;
1448 GtkTreeSelection *sel;
1449 gboolean same_account;
1451 /* Ignore transport account insertions, we're not showing them
1452 in the folder view */
1453 if (TNY_IS_TRANSPORT_ACCOUNT (tny_account))
1456 self = MODEST_FOLDER_VIEW (user_data);
1457 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (user_data);
1459 /* Get the inner model */
1460 if (!get_inner_models (MODEST_FOLDER_VIEW (user_data),
1461 &filter_model, NULL, &model))
1464 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (user_data));
1466 /* Invalidate the cur_folder_store only if the selected folder
1467 belongs to the account that is being removed */
1468 same_account = same_account_selected (self, tny_account);
1470 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1471 gtk_tree_selection_unselect_all (sel);
1474 /* Remove the account from the model */
1475 tny_list_remove (TNY_LIST (model), G_OBJECT (tny_account));
1477 /* Insert the account in the model */
1478 tny_list_append (TNY_LIST (model), G_OBJECT (tny_account));
1480 /* Refilter the model */
1481 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1483 /* Select the first INBOX if the currently selected folder
1484 belongs to the account that is being deleted */
1485 if (same_account && !MODEST_IS_TNY_LOCAL_FOLDERS_ACCOUNT (tny_account))
1486 g_idle_add (on_idle_select_first_inbox_or_local, self);
1490 on_account_removed (TnyAccountStore *account_store,
1491 TnyAccount *account,
1494 ModestFolderView *self = NULL;
1495 ModestFolderViewPrivate *priv;
1496 GtkTreeModel *model, *filter_model;
1497 GtkTreeSelection *sel = NULL;
1498 gboolean same_account = FALSE;
1500 /* Ignore transport account removals, we're not showing them
1501 in the folder view */
1502 if (TNY_IS_TRANSPORT_ACCOUNT (account))
1505 if (!MODEST_IS_FOLDER_VIEW(user_data)) {
1506 g_warning ("BUG: %s: not a valid folder view", __FUNCTION__);
1510 self = MODEST_FOLDER_VIEW (user_data);
1511 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1513 /* Invalidate the cur_folder_store only if the selected folder
1514 belongs to the account that is being removed */
1515 same_account = same_account_selected (self, account);
1517 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1518 gtk_tree_selection_unselect_all (sel);
1521 /* Invalidate row to select only if the folder to select
1522 belongs to the account that is being removed*/
1523 if (priv->folder_to_select) {
1524 TnyAccount *folder_to_select_account = NULL;
1526 folder_to_select_account = tny_folder_get_account (priv->folder_to_select);
1527 if (folder_to_select_account == account) {
1528 modest_folder_view_disable_next_folder_selection (self);
1529 g_object_unref (priv->folder_to_select);
1530 priv->folder_to_select = NULL;
1532 g_object_unref (folder_to_select_account);
1535 if (!get_inner_models (MODEST_FOLDER_VIEW (user_data),
1536 &filter_model, NULL, &model))
1539 /* Disconnect the signal handler */
1540 if (TNY_IS_GTK_FOLDER_LIST_STORE (model) &&
1541 MODEST_IS_TNY_LOCAL_FOLDERS_ACCOUNT (account)) {
1542 if (g_signal_handler_is_connected (account,
1543 priv->outbox_deleted_handler))
1544 g_signal_handler_disconnect (account,
1545 priv->outbox_deleted_handler);
1548 /* Remove the account from the model */
1549 tny_list_remove (TNY_LIST (model), G_OBJECT (account));
1551 /* If the removed account is the currently viewed one then
1552 clear the configuration value. The new visible account will be the default account */
1553 if (priv->visible_account_id &&
1554 !strcmp (priv->visible_account_id, tny_account_get_id (account))) {
1556 /* Clear the current visible account_id */
1557 modest_folder_view_set_account_id_of_visible_server_account (self, NULL);
1559 /* Call the restore method, this will set the new visible account */
1560 modest_widget_memory_restore (modest_runtime_get_conf(), G_OBJECT(self),
1561 MODEST_CONF_FOLDER_VIEW_KEY);
1564 /* Refilter the model */
1565 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
1567 /* Select the first INBOX if the currently selected folder
1568 belongs to the account that is being deleted */
1570 g_idle_add (on_idle_select_first_inbox_or_local, self);
1574 modest_folder_view_set_title (ModestFolderView *self, const gchar *title)
1576 GtkTreeViewColumn *col;
1578 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
1580 col = gtk_tree_view_get_column (GTK_TREE_VIEW(self), 0);
1582 g_printerr ("modest: failed get column for title\n");
1586 gtk_tree_view_column_set_title (col, title);
1587 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self),
1592 modest_folder_view_on_map (ModestFolderView *self,
1593 GdkEventExpose *event,
1596 ModestFolderViewPrivate *priv;
1598 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1600 /* This won't happen often */
1601 if (G_UNLIKELY (priv->reselect)) {
1602 /* Select the first inbox or the local account if not found */
1604 /* TODO: this could cause a lock at startup, so we
1605 comment it for the moment. We know that this will
1606 be a bug, because the INBOX is not selected, but we
1607 need to rewrite some parts of Modest to avoid the
1608 deathlock situation */
1609 /* TODO: check if this is still the case */
1610 priv->reselect = FALSE;
1611 modest_folder_view_select_first_inbox_or_local (self);
1612 /* Notify the display name observers */
1613 g_signal_emit (G_OBJECT(self),
1614 signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
1618 if (priv->reexpand) {
1619 expand_root_items (self);
1620 priv->reexpand = FALSE;
1627 modest_folder_view_new (TnyFolderStoreQuery *query)
1630 ModestFolderViewPrivate *priv;
1631 GtkTreeSelection *sel;
1633 self = G_OBJECT (g_object_new (MODEST_TYPE_FOLDER_VIEW,
1634 #ifdef MODEST_TOOLKIT_HILDON2
1635 "hildon-ui-mode", HILDON_UI_MODE_NORMAL,
1638 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
1641 priv->query = g_object_ref (query);
1643 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1644 priv->changed_signal = g_signal_connect (sel, "changed",
1645 G_CALLBACK (on_selection_changed), self);
1647 g_signal_connect (self, "row-activated", G_CALLBACK (on_row_activated), self);
1649 g_signal_connect (self, "expose-event", G_CALLBACK (modest_folder_view_on_map), NULL);
1651 return GTK_WIDGET(self);
1654 /* this feels dirty; any other way to expand all the root items? */
1656 expand_root_items (ModestFolderView *self)
1659 GtkTreeModel *model;
1662 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1663 path = gtk_tree_path_new_first ();
1665 /* all folders should have child items, so.. */
1667 gtk_tree_view_expand_row (GTK_TREE_VIEW(self), path, FALSE);
1668 gtk_tree_path_next (path);
1669 } while (gtk_tree_model_get_iter (model, &iter, path));
1671 gtk_tree_path_free (path);
1675 * We use this function to implement the
1676 * MODEST_FOLDER_VIEW_STYLE_SHOW_ONE style. We only show the default
1677 * account in this case, and the local folders.
1680 filter_row (GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
1682 ModestFolderViewPrivate *priv;
1683 gboolean retval = TRUE;
1684 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1685 GObject *instance = NULL;
1686 const gchar *id = NULL;
1688 gboolean found = FALSE;
1689 gboolean cleared = FALSE;
1690 ModestTnyFolderRules rules = 0;
1692 g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (data), FALSE);
1693 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (data);
1695 gtk_tree_model_get (model, iter,
1697 INSTANCE_COLUMN, &instance,
1700 /* Do not show if there is no instance, this could indeed
1701 happen when the model is being modified while it's being
1702 drawn. This could occur for example when moving folders
1707 if (TNY_IS_ACCOUNT (instance)) {
1708 TnyAccount *acc = TNY_ACCOUNT (instance);
1709 const gchar *account_id = tny_account_get_id (acc);
1711 /* If it isn't a special folder,
1712 * don't show it unless it is the visible account: */
1713 if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE &&
1714 !modest_tny_account_is_virtual_local_folders (acc) &&
1715 strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
1717 /* Show only the visible account id */
1718 if (priv->visible_account_id) {
1719 if (strcmp (account_id, priv->visible_account_id))
1726 /* Never show these to the user. They are merged into one folder
1727 * in the local-folders account instead: */
1728 if (retval && MODEST_IS_TNY_OUTBOX_ACCOUNT (acc))
1731 if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE) {
1732 /* Only show special folders for current account if needed */
1733 if (TNY_IS_FOLDER (instance) && !TNY_IS_MERGE_FOLDER (instance)) {
1734 TnyAccount *account;
1736 account = tny_folder_get_account (TNY_FOLDER (instance));
1738 if (TNY_IS_ACCOUNT (account)) {
1739 const gchar *account_id = tny_account_get_id (account);
1741 if (!modest_tny_account_is_virtual_local_folders (account) &&
1742 strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
1743 /* Show only the visible account id */
1744 if (priv->visible_account_id) {
1745 if (strcmp (account_id, priv->visible_account_id))
1749 g_object_unref (account);
1756 /* Check hiding (if necessary) */
1757 cleared = modest_email_clipboard_cleared (priv->clipboard);
1758 if ((retval) && (!cleared) && (TNY_IS_FOLDER (instance))) {
1759 id = tny_folder_get_id (TNY_FOLDER(instance));
1760 if (priv->hidding_ids != NULL)
1761 for (i=0; i < priv->n_selected && !found; i++)
1762 if (priv->hidding_ids[i] != NULL && id != NULL)
1763 found = (!strcmp (priv->hidding_ids[i], id));
1768 /* If this is a move to dialog, hide Sent, Outbox and Drafts
1769 folder as no message can be move there according to UI specs */
1770 if (!priv->show_non_move) {
1771 if (TNY_IS_FOLDER (instance) &&
1772 modest_tny_folder_is_local_folder (TNY_FOLDER (instance))) {
1774 case TNY_FOLDER_TYPE_OUTBOX:
1775 case TNY_FOLDER_TYPE_SENT:
1776 case TNY_FOLDER_TYPE_DRAFTS:
1779 case TNY_FOLDER_TYPE_UNKNOWN:
1780 case TNY_FOLDER_TYPE_NORMAL:
1781 type = modest_tny_folder_guess_folder_type(TNY_FOLDER(instance));
1782 if (type == TNY_FOLDER_TYPE_INVALID)
1783 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1785 if (type == TNY_FOLDER_TYPE_OUTBOX ||
1786 type == TNY_FOLDER_TYPE_SENT
1787 || type == TNY_FOLDER_TYPE_DRAFTS)
1796 /* apply special filters */
1797 if (retval && (priv->filter & MODEST_FOLDER_VIEW_FILTER_HIDE_ACCOUNTS)) {
1798 if (TNY_IS_ACCOUNT (instance))
1802 if (retval && (priv->filter & MODEST_FOLDER_VIEW_FILTER_CAN_HAVE_FOLDERS)) {
1803 if (TNY_IS_FOLDER (instance)) {
1804 /* Check folder rules */
1805 ModestTnyFolderRules rules;
1807 rules = modest_tny_folder_get_rules (TNY_FOLDER (instance));
1808 retval = !(rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE);
1809 } else if (TNY_IS_ACCOUNT (instance)) {
1810 if (modest_tny_folder_store_is_remote (TNY_FOLDER_STORE (instance))) {
1818 if (retval && (priv->filter & MODEST_FOLDER_VIEW_FILTER_HIDE_MANDATORY_FOLDERS)) {
1819 if (TNY_IS_FOLDER (instance)) {
1820 TnyFolderType guess_type;
1822 if (TNY_FOLDER_TYPE_NORMAL) {
1823 guess_type = modest_tny_folder_guess_folder_type (TNY_FOLDER (instance));
1829 case TNY_FOLDER_TYPE_OUTBOX:
1830 case TNY_FOLDER_TYPE_SENT:
1831 case TNY_FOLDER_TYPE_DRAFTS:
1832 case TNY_FOLDER_TYPE_ARCHIVE:
1833 case TNY_FOLDER_TYPE_INBOX:
1836 case TNY_FOLDER_TYPE_UNKNOWN:
1837 case TNY_FOLDER_TYPE_NORMAL:
1843 } else if (TNY_IS_ACCOUNT (instance)) {
1848 if (retval && TNY_IS_FOLDER (instance)) {
1849 rules = modest_tny_folder_get_rules (TNY_FOLDER (instance));
1852 if (retval && (priv->filter & MODEST_FOLDER_VIEW_FILTER_DELETABLE)) {
1853 if (TNY_IS_FOLDER (instance)) {
1854 retval = !(rules & MODEST_FOLDER_RULES_FOLDER_NON_DELETABLE);
1855 } else if (TNY_IS_ACCOUNT (instance)) {
1860 if (retval && (priv->filter & MODEST_FOLDER_VIEW_FILTER_RENAMEABLE)) {
1861 if (TNY_IS_FOLDER (instance)) {
1862 retval = !(rules & MODEST_FOLDER_RULES_FOLDER_NON_RENAMEABLE);
1863 } else if (TNY_IS_ACCOUNT (instance)) {
1868 if (retval && (priv->filter & MODEST_FOLDER_VIEW_FILTER_MOVEABLE)) {
1869 if (TNY_IS_FOLDER (instance)) {
1870 retval = !(rules & MODEST_FOLDER_RULES_FOLDER_NON_MOVEABLE);
1871 } else if (TNY_IS_ACCOUNT (instance)) {
1877 g_object_unref (instance);
1884 modest_folder_view_update_model (ModestFolderView *self,
1885 TnyAccountStore *account_store)
1887 ModestFolderViewPrivate *priv;
1888 GtkTreeModel *model /* , *old_model */;
1889 GtkTreeModel *filter_model = NULL, *sortable = NULL;
1891 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW (self), FALSE);
1892 g_return_val_if_fail (account_store && MODEST_IS_TNY_ACCOUNT_STORE(account_store),
1895 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1897 /* Notify that there is no folder selected */
1898 g_signal_emit (G_OBJECT(self),
1899 signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
1901 if (priv->cur_folder_store) {
1902 g_object_unref (priv->cur_folder_store);
1903 priv->cur_folder_store = NULL;
1906 /* FIXME: the local accounts are not shown when the query
1907 selects only the subscribed folders */
1908 #ifdef MODEST_TOOLKIT_HILDON2
1909 model = tny_gtk_folder_list_store_new_with_flags (NULL,
1910 TNY_GTK_FOLDER_LIST_STORE_FLAG_SHOW_PATH);
1911 tny_gtk_folder_list_store_set_path_separator (TNY_GTK_FOLDER_LIST_STORE (model),
1912 MODEST_FOLDER_PATH_SEPARATOR);
1914 model = tny_gtk_folder_store_tree_model_new (NULL);
1917 /* When the model is a list store (plain representation) the
1918 outbox is not a child of any account so we have to manually
1919 delete it because removing the local folders account won't
1920 delete it (because tny_folder_get_account() is not defined
1921 for a merge folder */
1922 if (TNY_IS_GTK_FOLDER_LIST_STORE (model)) {
1923 TnyAccount *account;
1924 ModestTnyAccountStore *acc_store;
1926 acc_store = modest_runtime_get_account_store ();
1927 account = modest_tny_account_store_get_local_folders_account (acc_store);
1929 if (g_signal_handler_is_connected (account,
1930 priv->outbox_deleted_handler))
1931 g_signal_handler_disconnect (account,
1932 priv->outbox_deleted_handler);
1934 priv->outbox_deleted_handler =
1935 g_signal_connect (account,
1937 G_CALLBACK (on_outbox_deleted_cb),
1939 g_object_unref (account);
1942 /* Get the accounts: */
1943 tny_account_store_get_accounts (TNY_ACCOUNT_STORE(account_store),
1945 TNY_ACCOUNT_STORE_STORE_ACCOUNTS);
1947 sortable = gtk_tree_model_sort_new_with_model (model);
1948 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
1950 GTK_SORT_ASCENDING);
1951 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
1953 cmp_rows, NULL, NULL);
1955 /* Create filter model */
1956 filter_model = gtk_tree_model_filter_new (sortable, NULL);
1957 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
1963 gtk_tree_view_set_model (GTK_TREE_VIEW(self), filter_model);
1964 #ifndef MODEST_TOOLKIT_HILDON2
1965 g_signal_connect (G_OBJECT(filter_model), "row-inserted",
1966 (GCallback) on_row_inserted_maybe_select_folder, self);
1969 g_object_unref (model);
1970 g_object_unref (filter_model);
1971 g_object_unref (sortable);
1973 /* Force a reselection of the INBOX next time the widget is shown */
1974 priv->reselect = TRUE;
1981 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
1983 GtkTreeModel *model = NULL;
1984 TnyFolderStore *folder = NULL;
1986 ModestFolderView *tree_view = NULL;
1987 ModestFolderViewPrivate *priv = NULL;
1988 gboolean selected = FALSE;
1990 g_return_if_fail (sel);
1991 g_return_if_fail (user_data);
1993 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
1995 selected = gtk_tree_selection_get_selected (sel, &model, &iter);
1997 tree_view = MODEST_FOLDER_VIEW (user_data);
2000 gtk_tree_model_get (model, &iter,
2001 INSTANCE_COLUMN, &folder,
2004 /* If the folder is the same do not notify */
2005 if (folder && priv->cur_folder_store == folder) {
2006 g_object_unref (folder);
2011 /* Current folder was unselected */
2012 if (priv->cur_folder_store) {
2013 /* We must do this firstly because a libtinymail-camel
2014 implementation detail. If we issue the signal
2015 before doing the sync_async, then that signal could
2016 cause (and it actually does it) a free of the
2017 summary of the folder (because the main window will
2018 clear the headers view */
2019 if (TNY_IS_FOLDER(priv->cur_folder_store))
2020 tny_folder_sync_async (TNY_FOLDER(priv->cur_folder_store),
2021 FALSE, NULL, NULL, NULL);
2023 g_signal_emit (G_OBJECT(tree_view), signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
2024 priv->cur_folder_store, FALSE);
2026 g_object_unref (priv->cur_folder_store);
2027 priv->cur_folder_store = NULL;
2030 /* New current references */
2031 priv->cur_folder_store = folder;
2033 /* New folder has been selected. Do not notify if there is
2034 nothing new selected */
2036 g_signal_emit (G_OBJECT(tree_view),
2037 signals[FOLDER_SELECTION_CHANGED_SIGNAL],
2038 0, priv->cur_folder_store, TRUE);
2043 on_row_activated (GtkTreeView *treeview,
2044 GtkTreePath *treepath,
2045 GtkTreeViewColumn *column,
2048 GtkTreeModel *model = NULL;
2049 TnyFolderStore *folder = NULL;
2051 ModestFolderView *self = NULL;
2052 ModestFolderViewPrivate *priv = NULL;
2054 g_return_if_fail (treeview);
2055 g_return_if_fail (user_data);
2057 self = MODEST_FOLDER_VIEW (user_data);
2058 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
2060 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2062 if (!gtk_tree_model_get_iter (model, &iter, treepath))
2065 gtk_tree_model_get (model, &iter,
2066 INSTANCE_COLUMN, &folder,
2069 g_signal_emit (G_OBJECT(self),
2070 signals[FOLDER_ACTIVATED_SIGNAL],
2073 #ifdef MODEST_TOOLKIT_HILDON2
2074 HildonUIMode ui_mode;
2075 g_object_get (G_OBJECT (self), "hildon-ui-mode", &ui_mode, NULL);
2076 if (ui_mode == HILDON_UI_MODE_NORMAL) {
2077 if (priv->cur_folder_store)
2078 g_object_unref (priv->cur_folder_store);
2079 priv->cur_folder_store = g_object_ref (folder);
2083 g_object_unref (folder);
2087 modest_folder_view_get_selected (ModestFolderView *self)
2089 ModestFolderViewPrivate *priv;
2091 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW(self), NULL);
2093 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
2094 if (priv->cur_folder_store)
2095 g_object_ref (priv->cur_folder_store);
2097 return priv->cur_folder_store;
2101 get_cmp_rows_type_pos (GObject *folder)
2103 /* Remote accounts -> Local account -> MMC account .*/
2106 if (TNY_IS_ACCOUNT (folder) &&
2107 modest_tny_account_is_virtual_local_folders (
2108 TNY_ACCOUNT (folder))) {
2110 } else if (TNY_IS_ACCOUNT (folder)) {
2111 TnyAccount *account = TNY_ACCOUNT (folder);
2112 const gchar *account_id = tny_account_get_id (account);
2113 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
2119 printf ("DEBUG: %s: unexpected type.\n", __FUNCTION__);
2120 return -1; /* Should never happen */
2125 inbox_is_special (TnyFolderStore *folder_store)
2127 gboolean is_special = TRUE;
2129 if (TNY_IS_FOLDER (folder_store)) {
2133 gchar *last_inbox_bar;
2135 id = tny_folder_get_id (TNY_FOLDER (folder_store));
2136 downcase = g_utf8_strdown (id, -1);
2137 last_bar = g_strrstr (downcase, "/");
2139 last_inbox_bar = g_strrstr (downcase, "inbox/");
2140 if ((last_inbox_bar == NULL) || (last_inbox_bar + 5 != last_bar))
2151 get_cmp_pos (TnyFolderType t, TnyFolder *folder_store)
2153 TnyAccount *account;
2154 gboolean is_special;
2155 /* Inbox, Outbox, Drafts, Sent, User */
2158 if (!TNY_IS_FOLDER (folder_store))
2161 case TNY_FOLDER_TYPE_INBOX:
2163 account = tny_folder_get_account (folder_store);
2164 is_special = (get_cmp_rows_type_pos (G_OBJECT (account)) == 0);
2166 /* In inbox case we need to know if the inbox is really the top
2167 * inbox of the account, or if it's a submailbox inbox. To do
2168 * this we'll apply an heuristic rule: Find last "/" and check
2169 * if it's preceeded by another Inbox */
2170 is_special = is_special && !inbox_is_special (TNY_FOLDER_STORE (folder_store));
2171 g_object_unref (account);
2172 return is_special?0:4;
2175 case TNY_FOLDER_TYPE_OUTBOX:
2176 return (TNY_IS_MERGE_FOLDER (folder_store))?2:4;
2178 case TNY_FOLDER_TYPE_DRAFTS:
2180 account = tny_folder_get_account (folder_store);
2181 is_special = (get_cmp_rows_type_pos (G_OBJECT (account)) == 1);
2182 g_object_unref (account);
2183 return is_special?1:4;
2186 case TNY_FOLDER_TYPE_SENT:
2188 account = tny_folder_get_account (folder_store);
2189 is_special = (get_cmp_rows_type_pos (G_OBJECT (account)) == 1);
2190 g_object_unref (account);
2191 return is_special?3:4;
2200 compare_account_names (TnyAccount *a1, TnyAccount *a2)
2202 const gchar *a1_name, *a2_name;
2204 a1_name = tny_account_get_name (a1);
2205 a2_name = tny_account_get_name (a2);
2207 return modest_text_utils_utf8_strcmp (a1_name, a2_name, TRUE);
2211 compare_accounts (TnyFolderStore *s1, TnyFolderStore *s2)
2213 TnyAccount *a1 = NULL, *a2 = NULL;
2216 if (TNY_IS_ACCOUNT (s1)) {
2217 a1 = TNY_ACCOUNT (g_object_ref (s1));
2218 } else if (!TNY_IS_MERGE_FOLDER (s1)) {
2219 a1 = tny_folder_get_account (TNY_FOLDER (s1));
2222 if (TNY_IS_ACCOUNT (s2)) {
2223 a2 = TNY_ACCOUNT (g_object_ref (s2));
2224 } else if (!TNY_IS_MERGE_FOLDER (s2)) {
2225 a2 = tny_folder_get_account (TNY_FOLDER (s2));
2242 /* First we sort with the type of account */
2243 cmp = get_cmp_rows_type_pos (G_OBJECT (a1)) - get_cmp_rows_type_pos (G_OBJECT (a2));
2247 cmp = compare_account_names (a1, a2);
2251 g_object_unref (a1);
2253 g_object_unref (a2);
2259 compare_accounts_first (TnyFolderStore *s1, TnyFolderStore *s2)
2261 gint is_account1, is_account2;
2263 is_account1 = TNY_IS_ACCOUNT (s1)?1:0;
2264 is_account2 = TNY_IS_ACCOUNT (s2)?1:0;
2266 return is_account2 - is_account1;
2270 * This function orders the mail accounts according to these rules:
2271 * 1st - remote accounts
2272 * 2nd - local account
2276 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
2280 gchar *name1 = NULL;
2281 gchar *name2 = NULL;
2282 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
2283 TnyFolderType type2 = TNY_FOLDER_TYPE_UNKNOWN;
2284 GObject *folder1 = NULL;
2285 GObject *folder2 = NULL;
2287 gtk_tree_model_get (tree_model, iter1,
2288 NAME_COLUMN, &name1,
2290 INSTANCE_COLUMN, &folder1,
2292 gtk_tree_model_get (tree_model, iter2,
2293 NAME_COLUMN, &name2,
2294 TYPE_COLUMN, &type2,
2295 INSTANCE_COLUMN, &folder2,
2298 /* Return if we get no folder. This could happen when folder
2299 operations are happening. The model is updated after the
2300 folder copy/move actually occurs, so there could be
2301 situations where the model to be drawn is not correct */
2302 if (!folder1 || !folder2)
2305 /* Sort by type. First the special folders, then the archives */
2306 cmp = get_cmp_pos (type, (TnyFolder *) folder1) - get_cmp_pos (type2, (TnyFolder *) folder2);
2310 /* Now we sort using the account of each folder */
2311 if (TNY_IS_FOLDER_STORE (folder1) &&
2312 TNY_IS_FOLDER_STORE (folder2)) {
2313 cmp = compare_accounts (TNY_FOLDER_STORE (folder1), TNY_FOLDER_STORE (folder2));
2317 /* Each group is preceeded by its account */
2318 cmp = compare_accounts_first (TNY_FOLDER_STORE (folder1), TNY_FOLDER_STORE (folder2));
2323 /* Pure sort by name */
2324 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
2327 g_object_unref(G_OBJECT(folder1));
2329 g_object_unref(G_OBJECT(folder2));
2337 /*****************************************************************************/
2338 /* DRAG and DROP stuff */
2339 /*****************************************************************************/
2341 * This function fills the #GtkSelectionData with the row and the
2342 * model that has been dragged. It's called when this widget is a
2343 * source for dnd after the event drop happened
2346 on_drag_data_get (GtkWidget *widget, GdkDragContext *context, GtkSelectionData *selection_data,
2347 guint info, guint time, gpointer data)
2349 GtkTreeSelection *selection;
2350 GtkTreeModel *model;
2352 GtkTreePath *source_row;
2354 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
2355 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
2357 source_row = gtk_tree_model_get_path (model, &iter);
2358 gtk_tree_set_row_drag_data (selection_data,
2362 gtk_tree_path_free (source_row);
2366 typedef struct _DndHelper {
2367 ModestFolderView *folder_view;
2368 gboolean delete_source;
2369 GtkTreePath *source_row;
2373 dnd_helper_destroyer (DndHelper *helper)
2375 /* Free the helper */
2376 gtk_tree_path_free (helper->source_row);
2377 g_slice_free (DndHelper, helper);
2381 xfer_folder_cb (ModestMailOperation *mail_op,
2382 TnyFolder *new_folder,
2386 /* Select the folder */
2387 modest_folder_view_select_folder (MODEST_FOLDER_VIEW (user_data),
2393 /* get the folder for the row the treepath refers to. */
2394 /* folder must be unref'd */
2395 static TnyFolderStore *
2396 tree_path_to_folder (GtkTreeModel *model, GtkTreePath *path)
2399 TnyFolderStore *folder = NULL;
2401 if (gtk_tree_model_get_iter (model,&iter, path))
2402 gtk_tree_model_get (model, &iter,
2403 INSTANCE_COLUMN, &folder,
2410 * This function is used by drag_data_received_cb to manage drag and
2411 * drop of a header, i.e, and drag from the header view to the folder
2415 drag_and_drop_from_header_view (GtkTreeModel *source_model,
2416 GtkTreeModel *dest_model,
2417 GtkTreePath *dest_row,
2418 GtkSelectionData *selection_data)
2420 TnyList *headers = NULL;
2421 TnyFolder *folder = NULL, *src_folder = NULL;
2422 TnyFolderType folder_type;
2423 GtkTreeIter source_iter, dest_iter;
2424 ModestWindowMgr *mgr = NULL;
2425 ModestWindow *main_win = NULL;
2426 gchar **uris, **tmp;
2428 /* Build the list of headers */
2429 mgr = modest_runtime_get_window_mgr ();
2430 headers = tny_simple_list_new ();
2431 uris = modest_dnd_selection_data_get_paths (selection_data);
2434 while (*tmp != NULL) {
2437 gboolean first = TRUE;
2440 path = gtk_tree_path_new_from_string (*tmp);
2441 gtk_tree_model_get_iter (source_model, &source_iter, path);
2442 gtk_tree_model_get (source_model, &source_iter,
2443 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
2446 /* Do not enable d&d of headers already opened */
2447 if (!modest_window_mgr_find_registered_header(mgr, header, NULL))
2448 tny_list_append (headers, G_OBJECT (header));
2450 if (G_UNLIKELY (first)) {
2451 src_folder = tny_header_get_folder (header);
2455 /* Free and go on */
2456 gtk_tree_path_free (path);
2457 g_object_unref (header);
2462 /* This could happen ig we perform a d&d very quickly over the
2463 same row that row could dissapear because message is
2465 if (!TNY_IS_FOLDER (src_folder))
2468 /* Get the target folder */
2469 gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
2470 gtk_tree_model_get (dest_model, &dest_iter,
2474 if (!folder || !TNY_IS_FOLDER(folder)) {
2475 /* g_warning ("%s: not a valid target folder (%p)", __FUNCTION__, folder); */
2479 folder_type = modest_tny_folder_guess_folder_type (folder);
2480 if (folder_type == TNY_FOLDER_TYPE_INVALID) {
2481 /* g_warning ("%s: invalid target folder", __FUNCTION__); */
2482 goto cleanup; /* cannot move messages there */
2485 if (modest_tny_folder_get_rules((TNY_FOLDER(folder))) & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE) {
2486 /* g_warning ("folder not writable"); */
2487 goto cleanup; /* verboten! */
2490 /* Ask for confirmation to move */
2491 main_win = modest_window_mgr_get_main_window (mgr, FALSE); /* don't create */
2493 g_warning ("%s: BUG: no main window found", __FUNCTION__);
2497 /* Transfer messages */
2498 modest_ui_actions_transfer_messages_helper (GTK_WINDOW (main_win), src_folder,
2503 if (G_IS_OBJECT (src_folder))
2504 g_object_unref (src_folder);
2505 if (G_IS_OBJECT(folder))
2506 g_object_unref (G_OBJECT (folder));
2507 if (G_IS_OBJECT(headers))
2508 g_object_unref (headers);
2512 TnyFolderStore *src_folder;
2513 TnyFolderStore *dst_folder;
2514 ModestFolderView *folder_view;
2519 dnd_folder_info_destroyer (DndFolderInfo *info)
2521 if (info->src_folder)
2522 g_object_unref (info->src_folder);
2523 if (info->dst_folder)
2524 g_object_unref (info->dst_folder);
2525 g_slice_free (DndFolderInfo, info);
2529 dnd_on_connection_failed_destroyer (DndFolderInfo *info,
2530 GtkWindow *parent_window,
2531 TnyAccount *account)
2534 modest_ui_actions_on_account_connection_error (parent_window, account);
2536 /* Free the helper & info */
2537 dnd_helper_destroyer (info->helper);
2538 dnd_folder_info_destroyer (info);
2542 drag_and_drop_from_folder_view_src_folder_performer (gboolean canceled,
2544 GtkWindow *parent_window,
2545 TnyAccount *account,
2548 DndFolderInfo *info = NULL;
2549 ModestMailOperation *mail_op;
2551 info = (DndFolderInfo *) user_data;
2553 if (err || canceled) {
2554 dnd_on_connection_failed_destroyer (info, parent_window, account);
2558 /* Do the mail operation */
2559 mail_op = modest_mail_operation_new_with_error_handling ((GObject *) parent_window,
2560 modest_ui_actions_move_folder_error_handler,
2561 info->src_folder, NULL);
2563 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
2566 /* Transfer the folder */
2567 modest_mail_operation_xfer_folder (mail_op,
2568 TNY_FOLDER (info->src_folder),
2570 info->helper->delete_source,
2572 info->helper->folder_view);
2575 g_object_unref (G_OBJECT (mail_op));
2576 dnd_helper_destroyer (info->helper);
2577 dnd_folder_info_destroyer (info);
2582 drag_and_drop_from_folder_view_dst_folder_performer (gboolean canceled,
2584 GtkWindow *parent_window,
2585 TnyAccount *account,
2588 DndFolderInfo *info = NULL;
2590 info = (DndFolderInfo *) user_data;
2592 if (err || canceled) {
2593 dnd_on_connection_failed_destroyer (info, parent_window, account);
2597 /* Connect to source folder and perform the copy/move */
2598 modest_platform_connect_if_remote_and_perform (NULL, TRUE,
2600 drag_and_drop_from_folder_view_src_folder_performer,
2605 * This function is used by drag_data_received_cb to manage drag and
2606 * drop of a folder, i.e, and drag from the folder view to the same
2610 drag_and_drop_from_folder_view (GtkTreeModel *source_model,
2611 GtkTreeModel *dest_model,
2612 GtkTreePath *dest_row,
2613 GtkSelectionData *selection_data,
2616 GtkTreeIter dest_iter, iter;
2617 TnyFolderStore *dest_folder = NULL;
2618 TnyFolderStore *folder = NULL;
2619 gboolean forbidden = FALSE;
2621 DndFolderInfo *info = NULL;
2623 win = modest_window_mgr_get_main_window (modest_runtime_get_window_mgr(), FALSE); /* don't create */
2625 g_warning ("%s: BUG: no main window", __FUNCTION__);
2626 dnd_helper_destroyer (helper);
2631 /* check the folder rules for the destination */
2632 folder = tree_path_to_folder (dest_model, dest_row);
2633 if (TNY_IS_FOLDER(folder)) {
2634 ModestTnyFolderRules rules =
2635 modest_tny_folder_get_rules (TNY_FOLDER (folder));
2636 forbidden = rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE;
2637 } else if (TNY_IS_FOLDER_STORE(folder)) {
2638 /* enable local root as destination for folders */
2639 if (!MODEST_IS_TNY_LOCAL_FOLDERS_ACCOUNT (folder) &&
2640 !modest_tny_account_is_memory_card_account (TNY_ACCOUNT (folder)))
2643 g_object_unref (folder);
2646 /* check the folder rules for the source */
2647 folder = tree_path_to_folder (source_model, helper->source_row);
2648 if (TNY_IS_FOLDER(folder)) {
2649 ModestTnyFolderRules rules =
2650 modest_tny_folder_get_rules (TNY_FOLDER (folder));
2651 forbidden = rules & MODEST_FOLDER_RULES_FOLDER_NON_MOVEABLE;
2654 g_object_unref (folder);
2658 /* Check if the drag is possible */
2659 if (forbidden || !gtk_tree_path_compare (helper->source_row, dest_row)) {
2661 modest_platform_run_information_dialog ((GtkWindow *) win,
2662 _("mail_in_ui_folder_move_target_error"),
2664 /* Restore the previous selection */
2665 folder = tree_path_to_folder (source_model, helper->source_row);
2667 if (TNY_IS_FOLDER (folder))
2668 modest_folder_view_select_folder (helper->folder_view,
2669 TNY_FOLDER (folder), FALSE);
2670 g_object_unref (folder);
2672 dnd_helper_destroyer (helper);
2677 gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
2678 gtk_tree_model_get (dest_model, &dest_iter,
2681 gtk_tree_model_get_iter (source_model, &iter, helper->source_row);
2682 gtk_tree_model_get (source_model, &iter,
2686 /* Create the info for the performer */
2687 info = g_slice_new0 (DndFolderInfo);
2688 info->src_folder = g_object_ref (folder);
2689 info->dst_folder = g_object_ref (dest_folder);
2690 info->helper = helper;
2692 /* Connect to the destination folder and perform the copy/move */
2693 modest_platform_connect_if_remote_and_perform (GTK_WINDOW (win), TRUE,
2695 drag_and_drop_from_folder_view_dst_folder_performer,
2699 g_object_unref (dest_folder);
2700 g_object_unref (folder);
2704 * This function receives the data set by the "drag-data-get" signal
2705 * handler. This information comes within the #GtkSelectionData. This
2706 * function will manage both the drags of folders of the treeview and
2707 * drags of headers of the header view widget.
2710 on_drag_data_received (GtkWidget *widget,
2711 GdkDragContext *context,
2714 GtkSelectionData *selection_data,
2719 GtkWidget *source_widget;
2720 GtkTreeModel *dest_model, *source_model;
2721 GtkTreePath *source_row, *dest_row;
2722 GtkTreeViewDropPosition pos;
2723 gboolean delete_source = FALSE;
2724 gboolean success = FALSE;
2726 /* Do not allow further process */
2727 g_signal_stop_emission_by_name (widget, "drag-data-received");
2728 source_widget = gtk_drag_get_source_widget (context);
2730 /* Get the action */
2731 if (context->action == GDK_ACTION_MOVE) {
2732 delete_source = TRUE;
2734 /* Notify that there is no folder selected. We need to
2735 do this in order to update the headers view (and
2736 its monitors, because when moving, the old folder
2737 won't longer exist. We can not wait for the end of
2738 the operation, because the operation won't start if
2739 the folder is in use */
2740 if (source_widget == widget) {
2741 GtkTreeSelection *sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
2742 gtk_tree_selection_unselect_all (sel);
2746 /* Check if the get_data failed */
2747 if (selection_data == NULL || selection_data->length < 0)
2750 /* Select the destination model */
2751 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
2753 /* Get the path to the destination row. Can not call
2754 gtk_tree_view_get_drag_dest_row() because the source row
2755 is not selected anymore */
2756 gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget), x, y,
2759 /* Only allow drops IN other rows */
2761 pos == GTK_TREE_VIEW_DROP_BEFORE ||
2762 pos == GTK_TREE_VIEW_DROP_AFTER)
2766 /* Drags from the header view */
2767 if (source_widget != widget) {
2768 source_model = gtk_tree_view_get_model (GTK_TREE_VIEW (source_widget));
2770 drag_and_drop_from_header_view (source_model,
2775 DndHelper *helper = NULL;
2777 /* Get the source model and row */
2778 gtk_tree_get_row_drag_data (selection_data,
2782 /* Create the helper */
2783 helper = g_slice_new0 (DndHelper);
2784 helper->delete_source = delete_source;
2785 helper->source_row = gtk_tree_path_copy (source_row);
2786 helper->folder_view = MODEST_FOLDER_VIEW (widget);
2788 drag_and_drop_from_folder_view (source_model,
2794 gtk_tree_path_free (source_row);
2798 gtk_tree_path_free (dest_row);
2801 /* Finish the drag and drop */
2802 gtk_drag_finish (context, success, FALSE, time);
2806 * We define a "drag-drop" signal handler because we do not want to
2807 * use the default one, because the default one always calls
2808 * gtk_drag_finish and we prefer to do it in the "drag-data-received"
2809 * signal handler, because there we have all the information available
2810 * to know if the dnd was a success or not.
2813 drag_drop_cb (GtkWidget *widget,
2814 GdkDragContext *context,
2822 if (!context->targets)
2825 /* Check if we're dragging a folder row */
2826 target = gtk_drag_dest_find_target (widget, context, NULL);
2828 /* Request the data from the source. */
2829 gtk_drag_get_data(widget, context, target, time);
2835 * This function expands a node of a tree view if it's not expanded
2836 * yet. Not sure why it needs the threads stuff, but gtk+`example code
2837 * does that, so that's why they're here.
2840 expand_row_timeout (gpointer data)
2842 GtkTreeView *tree_view = data;
2843 GtkTreePath *dest_path = NULL;
2844 GtkTreeViewDropPosition pos;
2845 gboolean result = FALSE;
2847 gdk_threads_enter ();
2849 gtk_tree_view_get_drag_dest_row (tree_view,
2854 (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER ||
2855 pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)) {
2856 gtk_tree_view_expand_row (tree_view, dest_path, FALSE);
2857 gtk_tree_path_free (dest_path);
2861 gtk_tree_path_free (dest_path);
2866 gdk_threads_leave ();
2872 * This function is called whenever the pointer is moved over a widget
2873 * while dragging some data. It installs a timeout that will expand a
2874 * node of the treeview if not expanded yet. This function also calls
2875 * gdk_drag_status in order to set the suggested action that will be
2876 * used by the "drag-data-received" signal handler to know if we
2877 * should do a move or just a copy of the data.
2880 on_drag_motion (GtkWidget *widget,
2881 GdkDragContext *context,
2887 GtkTreeViewDropPosition pos;
2888 GtkTreePath *dest_row;
2889 GtkTreeModel *dest_model;
2890 ModestFolderViewPrivate *priv;
2891 GdkDragAction suggested_action;
2892 gboolean valid_location = FALSE;
2893 TnyFolderStore *folder = NULL;
2895 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
2897 if (priv->timer_expander != 0) {
2898 g_source_remove (priv->timer_expander);
2899 priv->timer_expander = 0;
2902 gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
2907 /* Do not allow drops between folders */
2909 pos == GTK_TREE_VIEW_DROP_BEFORE ||
2910 pos == GTK_TREE_VIEW_DROP_AFTER) {
2911 gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW (widget), NULL, 0);
2912 gdk_drag_status(context, 0, time);
2913 valid_location = FALSE;
2916 valid_location = TRUE;
2919 /* Check that the destination folder is writable */
2920 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
2921 folder = tree_path_to_folder (dest_model, dest_row);
2922 if (folder && TNY_IS_FOLDER (folder)) {
2923 ModestTnyFolderRules rules = modest_tny_folder_get_rules(TNY_FOLDER (folder));
2925 if (rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE) {
2926 valid_location = FALSE;
2931 /* Expand the selected row after 1/2 second */
2932 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), dest_row)) {
2933 priv->timer_expander = g_timeout_add (500, expand_row_timeout, widget);
2935 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), dest_row, pos);
2937 /* Select the desired action. By default we pick MOVE */
2938 suggested_action = GDK_ACTION_MOVE;
2940 if (context->actions == GDK_ACTION_COPY)
2941 gdk_drag_status(context, GDK_ACTION_COPY, time);
2942 else if (context->actions == GDK_ACTION_MOVE)
2943 gdk_drag_status(context, GDK_ACTION_MOVE, time);
2944 else if (context->actions & suggested_action)
2945 gdk_drag_status(context, suggested_action, time);
2947 gdk_drag_status(context, GDK_ACTION_DEFAULT, time);
2951 g_object_unref (folder);
2953 gtk_tree_path_free (dest_row);
2955 g_signal_stop_emission_by_name (widget, "drag-motion");
2957 return valid_location;
2961 * This function sets the treeview as a source and a target for dnd
2962 * events. It also connects all the requirede signals.
2965 setup_drag_and_drop (GtkTreeView *self)
2967 /* Set up the folder view as a dnd destination. Set only the
2968 highlight flag, otherwise gtk will have a different
2970 #ifdef MODEST_TOOLKIT_HILDON2
2973 gtk_drag_dest_set (GTK_WIDGET (self),
2974 GTK_DEST_DEFAULT_HIGHLIGHT,
2975 folder_view_drag_types,
2976 G_N_ELEMENTS (folder_view_drag_types),
2977 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2979 g_signal_connect (G_OBJECT (self),
2980 "drag_data_received",
2981 G_CALLBACK (on_drag_data_received),
2985 /* Set up the treeview as a dnd source */
2986 gtk_drag_source_set (GTK_WIDGET (self),
2988 folder_view_drag_types,
2989 G_N_ELEMENTS (folder_view_drag_types),
2990 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2992 g_signal_connect (G_OBJECT (self),
2994 G_CALLBACK (on_drag_motion),
2997 g_signal_connect (G_OBJECT (self),
2999 G_CALLBACK (on_drag_data_get),
3002 g_signal_connect (G_OBJECT (self),
3004 G_CALLBACK (drag_drop_cb),
3009 * This function manages the navigation through the folders using the
3010 * keyboard or the hardware keys in the device
3013 on_key_pressed (GtkWidget *self,
3017 GtkTreeSelection *selection;
3019 GtkTreeModel *model;
3020 gboolean retval = FALSE;
3022 /* Up and Down are automatically managed by the treeview */
3023 if (event->keyval == GDK_Return) {
3024 /* Expand/Collapse the selected row */
3025 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
3026 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
3029 path = gtk_tree_model_get_path (model, &iter);
3031 if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (self), path))
3032 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
3034 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
3035 gtk_tree_path_free (path);
3037 /* No further processing */
3045 * We listen to the changes in the local folder account name key,
3046 * because we want to show the right name in the view. The local
3047 * folder account name corresponds to the device name in the Maemo
3048 * version. We do this because we do not want to query gconf on each
3049 * tree view refresh. It's better to cache it and change whenever
3053 on_configuration_key_changed (ModestConf* conf,
3055 ModestConfEvent event,
3056 ModestConfNotificationId id,
3057 ModestFolderView *self)
3059 ModestFolderViewPrivate *priv;
3062 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
3063 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
3065 if (!strcmp (key, MODEST_CONF_DEVICE_NAME)) {
3066 g_free (priv->local_account_name);
3068 if (event == MODEST_CONF_EVENT_KEY_UNSET)
3069 priv->local_account_name = g_strdup (MODEST_LOCAL_FOLDERS_DEFAULT_DISPLAY_NAME);
3071 priv->local_account_name = modest_conf_get_string (modest_runtime_get_conf(),
3072 MODEST_CONF_DEVICE_NAME, NULL);
3074 /* Force a redraw */
3075 #if GTK_CHECK_VERSION(2, 8, 0)
3076 GtkTreeViewColumn * tree_column;
3078 tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self),
3080 gtk_tree_view_column_queue_resize (tree_column);
3082 gtk_widget_queue_draw (GTK_WIDGET (self));
3088 modest_folder_view_set_style (ModestFolderView *self,
3089 ModestFolderViewStyle style)
3091 ModestFolderViewPrivate *priv;
3093 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
3094 g_return_if_fail (style == MODEST_FOLDER_VIEW_STYLE_SHOW_ALL ||
3095 style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE);
3097 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
3100 priv->style = style;
3104 modest_folder_view_set_account_id_of_visible_server_account (ModestFolderView *self,
3105 const gchar *account_id)
3107 ModestFolderViewPrivate *priv;
3108 GtkTreeModel *model;
3110 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
3112 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
3114 /* This will be used by the filter_row callback,
3115 * to decided which rows to show: */
3116 if (priv->visible_account_id) {
3117 g_free (priv->visible_account_id);
3118 priv->visible_account_id = NULL;
3121 priv->visible_account_id = g_strdup (account_id);
3124 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
3125 if (GTK_IS_TREE_MODEL_FILTER (model))
3126 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
3128 /* Save settings to gconf */
3129 modest_widget_memory_save (modest_runtime_get_conf (), G_OBJECT(self),
3130 MODEST_CONF_FOLDER_VIEW_KEY);
3134 modest_folder_view_get_account_id_of_visible_server_account (ModestFolderView *self)
3136 ModestFolderViewPrivate *priv;
3138 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW(self), NULL);
3140 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
3142 return (const gchar *) priv->visible_account_id;
3146 find_inbox_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *inbox_iter)
3150 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
3152 gtk_tree_model_get (model, iter,
3156 gboolean result = FALSE;
3157 if (type == TNY_FOLDER_TYPE_INBOX) {
3161 *inbox_iter = *iter;
3165 if (gtk_tree_model_iter_children (model, &child, iter)) {
3166 if (find_inbox_iter (model, &child, inbox_iter))
3170 } while (gtk_tree_model_iter_next (model, iter));
3179 modest_folder_view_select_first_inbox_or_local (ModestFolderView *self)
3181 GtkTreeModel *model;
3182 GtkTreeIter iter, inbox_iter;
3183 GtkTreeSelection *sel;
3184 GtkTreePath *path = NULL;
3186 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
3188 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
3192 expand_root_items (self);
3193 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
3195 if (!gtk_tree_model_get_iter_first (model, &iter)) {
3196 g_warning ("%s: model is empty", __FUNCTION__);
3200 if (find_inbox_iter (model, &iter, &inbox_iter))
3201 path = gtk_tree_model_get_path (model, &inbox_iter);
3203 path = gtk_tree_path_new_first ();
3205 /* Select the row and free */
3206 gtk_tree_view_set_cursor (GTK_TREE_VIEW (self), path, NULL, FALSE);
3207 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (self), path, NULL, FALSE, 0.0, 0.0);
3208 gtk_tree_path_free (path);
3211 gtk_widget_grab_focus (GTK_WIDGET(self));
3217 find_folder_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *folder_iter,
3222 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
3223 TnyFolder* a_folder;
3226 gtk_tree_model_get (model, iter,
3227 INSTANCE_COLUMN, &a_folder,
3233 if (folder == a_folder) {
3234 g_object_unref (a_folder);
3235 *folder_iter = *iter;
3238 g_object_unref (a_folder);
3240 if (gtk_tree_model_iter_children (model, &child, iter)) {
3241 if (find_folder_iter (model, &child, folder_iter, folder))
3245 } while (gtk_tree_model_iter_next (model, iter));
3250 #ifndef MODEST_TOOLKIT_HILDON2
3252 on_row_inserted_maybe_select_folder (GtkTreeModel *tree_model,
3255 ModestFolderView *self)
3257 ModestFolderViewPrivate *priv = NULL;
3258 GtkTreeSelection *sel;
3259 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
3260 GObject *instance = NULL;
3262 if (!MODEST_IS_FOLDER_VIEW(self))
3265 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
3267 priv->reexpand = TRUE;
3269 gtk_tree_model_get (tree_model, iter,
3271 INSTANCE_COLUMN, &instance,
3277 if (type == TNY_FOLDER_TYPE_INBOX && priv->folder_to_select == NULL) {
3278 priv->folder_to_select = g_object_ref (instance);
3280 g_object_unref (instance);
3282 if (priv->folder_to_select) {
3284 if (!modest_folder_view_select_folder (self, priv->folder_to_select,
3287 path = gtk_tree_model_get_path (tree_model, iter);
3288 gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
3290 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
3292 gtk_tree_selection_select_iter (sel, iter);
3293 gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
3295 gtk_tree_path_free (path);
3299 modest_folder_view_disable_next_folder_selection (self);
3301 /* Refilter the model */
3302 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (tree_model));
3308 modest_folder_view_disable_next_folder_selection (ModestFolderView *self)
3310 ModestFolderViewPrivate *priv;
3312 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
3314 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
3316 if (priv->folder_to_select)
3317 g_object_unref(priv->folder_to_select);
3319 priv->folder_to_select = NULL;
3323 modest_folder_view_select_folder (ModestFolderView *self, TnyFolder *folder,
3324 gboolean after_change)
3326 GtkTreeModel *model;
3327 GtkTreeIter iter, folder_iter;
3328 GtkTreeSelection *sel;
3329 ModestFolderViewPrivate *priv = NULL;
3331 g_return_val_if_fail (self && MODEST_IS_FOLDER_VIEW (self), FALSE);
3332 g_return_val_if_fail (folder && TNY_IS_FOLDER (folder), FALSE);
3334 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
3337 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
3338 gtk_tree_selection_unselect_all (sel);
3340 if (priv->folder_to_select)
3341 g_object_unref(priv->folder_to_select);
3342 priv->folder_to_select = TNY_FOLDER(g_object_ref(folder));
3346 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
3351 /* Refilter the model, before selecting the folder */
3352 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
3354 if (!gtk_tree_model_get_iter_first (model, &iter)) {
3355 g_warning ("%s: model is empty", __FUNCTION__);
3359 if (find_folder_iter (model, &iter, &folder_iter, folder)) {
3362 path = gtk_tree_model_get_path (model, &folder_iter);
3363 gtk_tree_view_expand_to_path (GTK_TREE_VIEW(self), path);
3365 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
3366 gtk_tree_selection_select_iter (sel, &folder_iter);
3367 gtk_tree_view_set_cursor (GTK_TREE_VIEW(self), path, NULL, FALSE);
3369 gtk_tree_path_free (path);
3377 modest_folder_view_copy_selection (ModestFolderView *self)
3379 g_return_if_fail (self && MODEST_IS_FOLDER_VIEW(self));
3381 /* Copy selection */
3382 _clipboard_set_selected_data (self, FALSE);
3386 modest_folder_view_cut_selection (ModestFolderView *folder_view)
3388 ModestFolderViewPrivate *priv = NULL;
3389 GtkTreeModel *model = NULL;
3390 const gchar **hidding = NULL;
3391 guint i, n_selected;
3393 g_return_if_fail (folder_view && MODEST_IS_FOLDER_VIEW (folder_view));
3394 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
3396 /* Copy selection */
3397 if (!_clipboard_set_selected_data (folder_view, TRUE))
3400 /* Get hidding ids */
3401 hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected);
3403 /* Clear hidding array created by previous cut operation */
3404 _clear_hidding_filter (MODEST_FOLDER_VIEW (folder_view));
3406 /* Copy hidding array */
3407 priv->n_selected = n_selected;
3408 priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
3409 for (i=0; i < n_selected; i++)
3410 priv->hidding_ids[i] = g_strdup(hidding[i]);
3412 /* Hide cut folders */
3413 model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
3414 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
3418 modest_folder_view_copy_model (ModestFolderView *folder_view_src,
3419 ModestFolderView *folder_view_dst)
3421 GtkTreeModel *filter_model = NULL;
3422 GtkTreeModel *model = NULL;
3423 GtkTreeModel *new_filter_model = NULL;
3425 g_return_if_fail (folder_view_src && MODEST_IS_FOLDER_VIEW (folder_view_src));
3426 g_return_if_fail (folder_view_dst && MODEST_IS_FOLDER_VIEW (folder_view_dst));
3429 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view_src));
3430 model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER(filter_model));
3432 /* Build new filter model */
3433 new_filter_model = gtk_tree_model_filter_new (model, NULL);
3434 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (new_filter_model),
3438 /* Set copied model */
3439 gtk_tree_view_set_model (GTK_TREE_VIEW (folder_view_dst), new_filter_model);
3440 #ifndef MODEST_TOOLKIT_HILDON2
3441 g_signal_connect (G_OBJECT(new_filter_model), "row-inserted",
3442 (GCallback) on_row_inserted_maybe_select_folder, folder_view_dst);
3446 g_object_unref (new_filter_model);
3450 modest_folder_view_show_non_move_folders (ModestFolderView *folder_view,
3453 GtkTreeModel *model = NULL;
3454 ModestFolderViewPrivate* priv;
3456 g_return_if_fail (folder_view && MODEST_IS_FOLDER_VIEW (folder_view));
3458 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
3459 priv->show_non_move = show;
3460 /* modest_folder_view_update_model(folder_view, */
3461 /* TNY_ACCOUNT_STORE(modest_runtime_get_account_store())); */
3463 /* Hide special folders */
3464 model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
3465 if (GTK_IS_TREE_MODEL_FILTER (model)) {
3466 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
3470 /* Returns FALSE if it did not selected anything */
3472 _clipboard_set_selected_data (ModestFolderView *folder_view,
3475 ModestFolderViewPrivate *priv = NULL;
3476 TnyFolderStore *folder = NULL;
3477 gboolean retval = FALSE;
3479 g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (folder_view), FALSE);
3480 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
3482 /* Set selected data on clipboard */
3483 g_return_val_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard), FALSE);
3484 folder = modest_folder_view_get_selected (folder_view);
3486 /* Do not allow to select an account */
3487 if (TNY_IS_FOLDER (folder)) {
3488 modest_email_clipboard_set_data (priv->clipboard, TNY_FOLDER(folder), NULL, delete);
3493 g_object_unref (folder);
3499 _clear_hidding_filter (ModestFolderView *folder_view)
3501 ModestFolderViewPrivate *priv;
3504 g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view));
3505 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
3507 if (priv->hidding_ids != NULL) {
3508 for (i=0; i < priv->n_selected; i++)
3509 g_free (priv->hidding_ids[i]);
3510 g_free(priv->hidding_ids);
3516 on_display_name_changed (ModestAccountMgr *mgr,
3517 const gchar *account,
3520 ModestFolderView *self;
3522 self = MODEST_FOLDER_VIEW (user_data);
3524 /* Force a redraw */
3525 #if GTK_CHECK_VERSION(2, 8, 0)
3526 GtkTreeViewColumn * tree_column;
3528 tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self),
3530 gtk_tree_view_column_queue_resize (tree_column);
3532 gtk_widget_queue_draw (GTK_WIDGET (self));
3537 modest_folder_view_set_cell_style (ModestFolderView *self,
3538 ModestFolderViewCellStyle cell_style)
3540 ModestFolderViewPrivate *priv = NULL;
3542 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
3543 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
3545 priv->cell_style = cell_style;
3547 g_object_set (G_OBJECT (priv->messages_renderer),
3548 "visible", (cell_style == MODEST_FOLDER_VIEW_CELL_STYLE_COMPACT),
3551 gtk_widget_queue_draw (GTK_WIDGET (self));
3555 update_style (ModestFolderView *self)
3557 ModestFolderViewPrivate *priv;
3558 GdkColor style_color;
3559 PangoAttrList *attr_list;
3561 PangoAttribute *attr;
3563 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
3564 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
3568 attr_list = pango_attr_list_new ();
3569 if (!gtk_style_lookup_color (GTK_WIDGET (self)->style, "SecondaryTextColor", &style_color)) {
3570 gdk_color_parse ("grey", &style_color);
3572 attr = pango_attr_foreground_new (style_color.red, style_color.green, style_color.blue);
3573 pango_attr_list_insert (attr_list, attr);
3576 style = gtk_rc_get_style_by_paths (gtk_widget_get_settings
3578 "SmallSystemFont", NULL,
3581 attr = pango_attr_font_desc_new (pango_font_description_copy
3582 (style->font_desc));
3583 pango_attr_list_insert (attr_list, attr);
3585 g_object_set (G_OBJECT (priv->messages_renderer),
3586 "foreground-gdk", &style_color,
3587 "foreground-set", TRUE,
3588 "attributes", attr_list,
3590 pango_attr_list_unref (attr_list);
3595 on_notify_style (GObject *obj, GParamSpec *spec, gpointer userdata)
3597 if (strcmp ("style", spec->name) == 0) {
3598 update_style (MODEST_FOLDER_VIEW (obj));
3599 gtk_widget_queue_draw (GTK_WIDGET (obj));
3604 modest_folder_view_set_filter (ModestFolderView *self,
3605 ModestFolderViewFilter filter)
3607 ModestFolderViewPrivate *priv;
3608 GtkTreeModel *filter_model;
3610 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
3611 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
3613 priv->filter |= filter;
3615 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
3616 if (GTK_IS_TREE_MODEL_FILTER(filter_model)) {
3617 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
3622 modest_folder_view_unset_filter (ModestFolderView *self,
3623 ModestFolderViewFilter filter)
3625 ModestFolderViewPrivate *priv;
3626 GtkTreeModel *filter_model;
3628 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
3629 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
3631 priv->filter &= ~filter;
3633 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
3634 if (GTK_IS_TREE_MODEL_FILTER(filter_model)) {
3635 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));