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-store-tree-model.h>
36 #include <tny-gtk-header-list-model.h>
37 #include <tny-folder.h>
38 #include <tny-account-store.h>
39 #include <tny-account.h>
40 #include <tny-folder.h>
41 #include <tny-camel-folder.h>
42 #include <tny-simple-list.h>
43 #include <modest-tny-folder.h>
44 #include <modest-tny-local-folders-account.h>
45 #include <modest-marshal.h>
46 #include <modest-icon-names.h>
47 #include <modest-tny-account-store.h>
48 #include <modest-text-utils.h>
49 #include <modest-runtime.h>
50 #include "modest-folder-view.h"
51 #include <modest-dnd.h>
52 #include <modest-platform.h>
53 #include <modest-account-mgr-helpers.h>
54 #include <modest-widget-memory.h>
55 #include <modest-ui-actions.h>
57 /* 'private'/'protected' functions */
58 static void modest_folder_view_class_init (ModestFolderViewClass *klass);
59 static void modest_folder_view_init (ModestFolderView *obj);
60 static void modest_folder_view_finalize (GObject *obj);
62 static void tny_account_store_view_init (gpointer g,
65 static void modest_folder_view_set_account_store (TnyAccountStoreView *self,
66 TnyAccountStore *account_store);
68 static gboolean update_model (ModestFolderView *self,
69 ModestTnyAccountStore *account_store);
71 static void on_selection_changed (GtkTreeSelection *sel, gpointer data);
73 static void on_account_update (TnyAccountStore *account_store,
77 static void on_accounts_reloaded (TnyAccountStore *store,
80 static gint cmp_rows (GtkTreeModel *tree_model,
85 static gboolean filter_row (GtkTreeModel *model,
89 static gboolean on_key_pressed (GtkWidget *self,
93 static void on_configuration_key_changed (ModestConf* conf,
95 ModestConfEvent event,
96 ModestFolderView *self);
99 static void on_drag_data_get (GtkWidget *widget,
100 GdkDragContext *context,
101 GtkSelectionData *selection_data,
106 static void on_drag_data_received (GtkWidget *widget,
107 GdkDragContext *context,
110 GtkSelectionData *selection_data,
115 static gboolean on_drag_motion (GtkWidget *widget,
116 GdkDragContext *context,
122 static gint expand_row_timeout (gpointer data);
124 static void setup_drag_and_drop (GtkTreeView *self);
127 FOLDER_SELECTION_CHANGED_SIGNAL,
128 FOLDER_DISPLAY_NAME_CHANGED_SIGNAL,
132 typedef struct _ModestFolderViewPrivate ModestFolderViewPrivate;
133 struct _ModestFolderViewPrivate {
134 TnyAccountStore *account_store;
135 TnyFolderStore *cur_folder_store;
137 gulong account_update_signal;
138 gulong changed_signal;
139 gulong accounts_reloaded_signal;
141 TnyFolderStoreQuery *query;
142 guint timer_expander;
144 gchar *local_account_name;
145 gchar *visible_account_id;
146 ModestFolderViewStyle style;
148 #define MODEST_FOLDER_VIEW_GET_PRIVATE(o) \
149 (G_TYPE_INSTANCE_GET_PRIVATE((o), \
150 MODEST_TYPE_FOLDER_VIEW, \
151 ModestFolderViewPrivate))
153 static GObjectClass *parent_class = NULL;
155 static guint signals[LAST_SIGNAL] = {0};
158 modest_folder_view_get_type (void)
160 static GType my_type = 0;
162 static const GTypeInfo my_info = {
163 sizeof(ModestFolderViewClass),
164 NULL, /* base init */
165 NULL, /* base finalize */
166 (GClassInitFunc) modest_folder_view_class_init,
167 NULL, /* class finalize */
168 NULL, /* class data */
169 sizeof(ModestFolderView),
171 (GInstanceInitFunc) modest_folder_view_init,
175 static const GInterfaceInfo tny_account_store_view_info = {
176 (GInterfaceInitFunc) tny_account_store_view_init, /* interface_init */
177 NULL, /* interface_finalize */
178 NULL /* interface_data */
182 my_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
186 g_type_add_interface_static (my_type,
187 TNY_TYPE_ACCOUNT_STORE_VIEW,
188 &tny_account_store_view_info);
194 modest_folder_view_class_init (ModestFolderViewClass *klass)
196 GObjectClass *gobject_class;
197 gobject_class = (GObjectClass*) klass;
199 parent_class = g_type_class_peek_parent (klass);
200 gobject_class->finalize = modest_folder_view_finalize;
202 g_type_class_add_private (gobject_class,
203 sizeof(ModestFolderViewPrivate));
205 signals[FOLDER_SELECTION_CHANGED_SIGNAL] =
206 g_signal_new ("folder_selection_changed",
207 G_TYPE_FROM_CLASS (gobject_class),
209 G_STRUCT_OFFSET (ModestFolderViewClass,
210 folder_selection_changed),
212 modest_marshal_VOID__POINTER_BOOLEAN,
213 G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_BOOLEAN);
216 * This signal is emitted whenever the currently selected
217 * folder display name is computed. Note that the name could
218 * be different to the folder name, because we could append
219 * the unread messages count to the folder name to build the
220 * folder display name
222 signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL] =
223 g_signal_new ("folder-display-name-changed",
224 G_TYPE_FROM_CLASS (gobject_class),
226 G_STRUCT_OFFSET (ModestFolderViewClass,
227 folder_display_name_changed),
229 g_cclosure_marshal_VOID__STRING,
230 G_TYPE_NONE, 1, G_TYPE_STRING);
234 text_cell_data (GtkTreeViewColumn *column, GtkCellRenderer *renderer,
235 GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
237 ModestFolderViewPrivate *priv;
242 GObject *instance = NULL;
244 g_return_if_fail (column);
245 g_return_if_fail (tree_model);
247 gtk_tree_model_get (tree_model, iter,
248 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &fname,
249 TNY_GTK_FOLDER_STORE_TREE_MODEL_ALL_COLUMN, &all,
250 TNY_GTK_FOLDER_STORE_TREE_MODEL_UNREAD_COLUMN, &unread,
251 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
252 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
254 rendobj = G_OBJECT(renderer);
265 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (data);
267 gchar *item_name = NULL;
268 gint item_weight = 400;
270 if (type != TNY_FOLDER_TYPE_ROOT) {
273 if (modest_tny_folder_is_local_folder (TNY_FOLDER (instance))) {
275 type = modest_tny_folder_get_local_folder_type (TNY_FOLDER (instance));
276 if (type != TNY_FOLDER_TYPE_UNKNOWN) {
278 fname = g_strdup(modest_local_folder_info_get_type_display_name (type));
282 /* Select the number to show */
283 if ((type == TNY_FOLDER_TYPE_DRAFTS) || (type == TNY_FOLDER_TYPE_OUTBOX))
288 /* Use bold font style if there are unread messages */
290 item_name = g_strdup_printf ("%s (%d)", fname, unread);
293 item_name = g_strdup (fname);
297 } else if (TNY_IS_ACCOUNT (instance)) {
298 /* If it's a server account */
299 if (modest_tny_account_is_virtual_local_folders (
300 TNY_ACCOUNT (instance))) {
301 item_name = g_strdup (priv->local_account_name);
304 item_name = g_strdup (fname);
310 item_name = g_strdup ("unknown");
312 if (item_name && item_weight) {
313 /* Set the name in the treeview cell: */
314 g_object_set (rendobj,"text", item_name, "weight", item_weight, NULL);
316 /* Notify display name observers */
317 if (G_OBJECT (priv->cur_folder_store) == instance) {
318 g_signal_emit (G_OBJECT(data),
319 signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
327 g_object_unref (G_OBJECT (instance));
334 icon_cell_data (GtkTreeViewColumn *column, GtkCellRenderer *renderer,
335 GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
337 GObject *rendobj = NULL, *instance = NULL;
338 GdkPixbuf *pixbuf = NULL;
341 const gchar *account_id = NULL;
344 rendobj = G_OBJECT(renderer);
345 gtk_tree_model_get (tree_model, iter,
346 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
347 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &fname,
348 TNY_GTK_FOLDER_STORE_TREE_MODEL_UNREAD_COLUMN, &unread,
349 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
360 if (type == TNY_FOLDER_TYPE_NORMAL || type == TNY_FOLDER_TYPE_UNKNOWN) {
361 type = modest_tny_folder_guess_folder_type_from_name (fname);
365 case TNY_FOLDER_TYPE_ROOT:
366 if (TNY_IS_ACCOUNT (instance)) {
368 if (modest_tny_account_is_virtual_local_folders (
369 TNY_ACCOUNT (instance))) {
370 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_LOCAL_FOLDERS);
373 account_id = tny_account_get_id (TNY_ACCOUNT (instance));
375 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
376 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_MMC);
378 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_ACCOUNT);
382 case TNY_FOLDER_TYPE_INBOX:
383 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_INBOX);
385 case TNY_FOLDER_TYPE_OUTBOX:
386 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_OUTBOX);
388 case TNY_FOLDER_TYPE_JUNK:
389 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_JUNK);
391 case TNY_FOLDER_TYPE_SENT:
392 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_SENT);
394 case TNY_FOLDER_TYPE_TRASH:
395 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_TRASH);
397 case TNY_FOLDER_TYPE_DRAFTS:
398 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_DRAFTS);
400 case TNY_FOLDER_TYPE_NORMAL:
402 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_NORMAL);
406 g_object_unref (G_OBJECT (instance));
410 g_object_set (rendobj, "pixbuf", pixbuf, NULL);
413 g_object_unref (pixbuf);
417 add_columns (GtkWidget *treeview)
419 GtkTreeViewColumn *column;
420 GtkCellRenderer *renderer;
421 GtkTreeSelection *sel;
424 column = gtk_tree_view_column_new ();
426 /* Set icon and text render function */
427 renderer = gtk_cell_renderer_pixbuf_new();
428 gtk_tree_view_column_pack_start (column, renderer, FALSE);
429 gtk_tree_view_column_set_cell_data_func(column, renderer,
430 icon_cell_data, treeview, NULL);
432 renderer = gtk_cell_renderer_text_new();
433 gtk_tree_view_column_pack_start (column, renderer, FALSE);
434 gtk_tree_view_column_set_cell_data_func(column, renderer,
435 text_cell_data, treeview, NULL);
437 /* Set selection mode */
438 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(treeview));
439 gtk_tree_selection_set_mode (sel, GTK_SELECTION_SINGLE);
441 /* Set treeview appearance */
442 gtk_tree_view_column_set_spacing (column, 2);
443 gtk_tree_view_column_set_resizable (column, TRUE);
444 gtk_tree_view_column_set_fixed_width (column, TRUE);
445 gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(treeview), FALSE);
446 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(treeview), FALSE);
449 gtk_tree_view_append_column (GTK_TREE_VIEW(treeview),column);
453 modest_folder_view_init (ModestFolderView *obj)
455 ModestFolderViewPrivate *priv;
458 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
460 priv->timer_expander = 0;
461 priv->account_store = NULL;
463 priv->style = MODEST_FOLDER_VIEW_STYLE_SHOW_ALL;
464 priv->cur_folder_store = NULL;
465 priv->visible_account_id = NULL;
467 /* Initialize the local account name */
468 conf = modest_runtime_get_conf();
469 priv->local_account_name = modest_conf_get_string (conf, MODEST_CONF_DEVICE_NAME, NULL);
472 add_columns (GTK_WIDGET (obj));
474 /* Setup drag and drop */
475 setup_drag_and_drop (GTK_TREE_VIEW(obj));
477 /* Connect signals */
478 g_signal_connect (G_OBJECT (obj),
480 G_CALLBACK (on_key_pressed), NULL);
483 * Track changes in the local account name (in the device it
484 * will be the device name)
486 g_signal_connect (G_OBJECT(conf),
488 G_CALLBACK(on_configuration_key_changed), obj);
493 tny_account_store_view_init (gpointer g, gpointer iface_data)
495 TnyAccountStoreViewIface *klass = (TnyAccountStoreViewIface *)g;
497 klass->set_account_store_func = modest_folder_view_set_account_store;
503 modest_folder_view_finalize (GObject *obj)
505 ModestFolderViewPrivate *priv;
506 GtkTreeSelection *sel;
508 g_return_if_fail (obj);
510 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
512 if (priv->timer_expander != 0) {
513 g_source_remove (priv->timer_expander);
514 priv->timer_expander = 0;
517 if (priv->account_store) {
518 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
519 priv->account_update_signal);
520 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
521 priv->accounts_reloaded_signal);
522 g_object_unref (G_OBJECT(priv->account_store));
523 priv->account_store = NULL;
527 g_object_unref (G_OBJECT (priv->query));
531 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(obj));
533 g_signal_handler_disconnect (G_OBJECT(sel), priv->changed_signal);
535 g_free (priv->local_account_name);
536 g_free (priv->visible_account_id);
538 G_OBJECT_CLASS(parent_class)->finalize (obj);
543 modest_folder_view_set_account_store (TnyAccountStoreView *self, TnyAccountStore *account_store)
545 ModestFolderViewPrivate *priv;
548 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
549 g_return_if_fail (TNY_IS_ACCOUNT_STORE (account_store));
551 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
552 device = tny_account_store_get_device (account_store);
554 if (G_UNLIKELY (priv->account_store)) {
556 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
557 priv->account_update_signal))
558 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
559 priv->account_update_signal);
560 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
561 priv->accounts_reloaded_signal))
562 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
563 priv->accounts_reloaded_signal);
565 g_object_unref (G_OBJECT (priv->account_store));
568 priv->account_store = g_object_ref (G_OBJECT (account_store));
570 priv->account_update_signal =
571 g_signal_connect (G_OBJECT(account_store), "account_update",
572 G_CALLBACK (on_account_update), self);
574 priv->accounts_reloaded_signal =
575 g_signal_connect (G_OBJECT(account_store), "accounts_reloaded",
576 G_CALLBACK (on_accounts_reloaded), self);
578 g_object_unref (G_OBJECT (device));
582 on_account_update (TnyAccountStore *account_store, const gchar *account,
585 if (!update_model (MODEST_FOLDER_VIEW(user_data),
586 MODEST_TNY_ACCOUNT_STORE(account_store)))
587 g_printerr ("modest: failed to update model for changes in '%s'",
592 on_accounts_reloaded (TnyAccountStore *account_store,
595 ModestConf *conf = modest_runtime_get_conf ();
597 modest_widget_memory_save (conf, G_OBJECT (user_data), MODEST_CONF_FOLDER_VIEW_KEY);
598 update_model (MODEST_FOLDER_VIEW (user_data),
599 MODEST_TNY_ACCOUNT_STORE(account_store));
600 modest_widget_memory_restore (conf, G_OBJECT (user_data), MODEST_CONF_FOLDER_VIEW_KEY);
604 modest_folder_view_set_title (ModestFolderView *self, const gchar *title)
606 GtkTreeViewColumn *col;
608 g_return_if_fail (self);
610 col = gtk_tree_view_get_column (GTK_TREE_VIEW(self), 0);
612 g_printerr ("modest: failed get column for title\n");
616 gtk_tree_view_column_set_title (col, title);
617 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self),
622 modest_folder_view_new (TnyFolderStoreQuery *query)
625 ModestFolderViewPrivate *priv;
626 GtkTreeSelection *sel;
628 self = G_OBJECT (g_object_new (MODEST_TYPE_FOLDER_VIEW, NULL));
629 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
632 priv->query = g_object_ref (query);
634 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
635 priv->changed_signal = g_signal_connect (sel, "changed",
636 G_CALLBACK (on_selection_changed), self);
638 return GTK_WIDGET(self);
641 /* this feels dirty; any other way to expand all the root items? */
643 expand_root_items (ModestFolderView *self)
646 path = gtk_tree_path_new_first ();
648 /* all folders should have child items, so.. */
649 while (gtk_tree_view_expand_row (GTK_TREE_VIEW(self), path, FALSE))
650 gtk_tree_path_next (path);
652 gtk_tree_path_free (path);
656 * We use this function to implement the
657 * MODEST_FOLDER_VIEW_STYLE_SHOW_ONE style. We only show the default
658 * account in this case, and the local folders.
661 filter_row (GtkTreeModel *model,
665 gboolean retval = TRUE;
667 GObject *instance = NULL;
669 gtk_tree_model_get (model, iter,
670 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
671 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
674 /* Do not show if there is no instance, this could indeed
675 happen when the model is being modified while it's being
676 drawn. This could occur for example when moving folders
681 if (type == TNY_FOLDER_TYPE_ROOT) {
682 /* TNY_FOLDER_TYPE_ROOT means that the instance is an account instead of a folder. */
683 if (TNY_IS_ACCOUNT (instance)) {
684 TnyAccount *acc = TNY_ACCOUNT (instance);
685 const gchar *account_id = tny_account_get_id (acc);
687 /* If it isn't a special folder,
688 * don't show it unless it is the visible account: */
689 if (!modest_tny_account_is_virtual_local_folders (acc) &&
690 strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
691 /* Show only the visible account id */
692 ModestFolderViewPrivate *priv =
693 MODEST_FOLDER_VIEW_GET_PRIVATE (data);
694 if (priv->visible_account_id && strcmp (account_id, priv->visible_account_id))
700 /* The virtual local-folders folder store is also shown by default. */
702 g_object_unref (instance);
708 static void on_tnylist_accounts_debug_print(gpointer data, gpointer user_data)
710 TnyAccount* account = TNY_ACCOUNT(data);
711 const gchar *prefix = (const gchar*)(user_data);
713 printf("%s account id=%s\n", prefix, tny_account_get_id (account));
718 update_model (ModestFolderView *self, ModestTnyAccountStore *account_store)
720 ModestFolderViewPrivate *priv;
723 g_return_val_if_fail (account_store, FALSE);
725 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
727 /* Notify that there is no folder selected */
728 g_signal_emit (G_OBJECT(self),
729 signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
732 /* FIXME: the local accounts are not shown when the query
733 selects only the subscribed folders. */
734 /* model = tny_gtk_folder_store_tree_model_new (TRUE, priv->query); */
735 model = tny_gtk_folder_store_tree_model_new (TRUE, NULL);
737 /* Deal with the model via its TnyList Interface,
738 * filling the TnyList via a get_accounts() call: */
739 TnyList *model_as_list = TNY_LIST(model);
741 /* Get the accounts: */
742 tny_account_store_get_accounts (TNY_ACCOUNT_STORE(account_store),
744 TNY_ACCOUNT_STORE_STORE_ACCOUNTS);
747 g_object_unref (model_as_list);
748 model_as_list = NULL;
752 tny_list_foreach (account_list, on_tnylist_accounts_debug_print, "update_model: ");
755 GtkTreeModel *filter_model = NULL, *sortable = NULL;
757 sortable = gtk_tree_model_sort_new_with_model (model);
758 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
759 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
761 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
762 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
763 cmp_rows, NULL, NULL);
765 /* Create filter model */
766 if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE) {
767 filter_model = gtk_tree_model_filter_new (sortable, NULL);
768 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
775 gtk_tree_view_set_model (GTK_TREE_VIEW(self),
776 (filter_model) ? filter_model : sortable);
777 expand_root_items (self); /* expand all account folders */
784 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
787 TnyFolderStore *folder = NULL;
789 ModestFolderView *tree_view;
790 ModestFolderViewPrivate *priv;
793 g_return_if_fail (sel);
794 g_return_if_fail (user_data);
796 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
798 /* folder was _un_selected if true */
799 if (!gtk_tree_selection_get_selected (sel, &model, &iter)) {
800 if (priv->cur_folder_store)
801 g_object_unref (priv->cur_folder_store);
802 priv->cur_folder_store = NULL;
804 /* Notify the display name observers */
805 g_signal_emit (G_OBJECT(user_data),
806 signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
811 tree_view = MODEST_FOLDER_VIEW (user_data);
813 gtk_tree_model_get (model, &iter,
814 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
815 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
818 /* If the folder is the same do not notify */
819 if (priv->cur_folder_store == folder) {
820 g_object_unref (folder);
824 /* Current folder was unselected */
825 if (priv->cur_folder_store) {
826 g_signal_emit (G_OBJECT(tree_view), signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
827 priv->cur_folder_store, FALSE);
828 g_object_unref (priv->cur_folder_store);
831 /* New current references */
832 priv->cur_folder_store = folder;
834 /* New folder has been selected */
835 g_signal_emit (G_OBJECT(tree_view),
836 signals[FOLDER_SELECTION_CHANGED_SIGNAL],
841 modest_folder_view_get_selected (ModestFolderView *self)
843 ModestFolderViewPrivate *priv;
845 g_return_val_if_fail (self, NULL);
847 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
848 if (priv->cur_folder_store)
849 g_object_ref (priv->cur_folder_store);
851 return priv->cur_folder_store;
855 get_cmp_rows_type_pos (GObject *folder)
857 /* Remote accounts -> Local account -> MMC account .*/
860 if (TNY_IS_ACCOUNT (folder) &&
861 modest_tny_account_is_virtual_local_folders (
862 TNY_ACCOUNT (folder))) {
864 } else if (TNY_IS_ACCOUNT (folder)) {
865 TnyAccount *account = TNY_ACCOUNT (folder);
866 const gchar *account_id = tny_account_get_id (account);
867 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
873 printf ("DEBUG: %s: unexpected type.\n", __FUNCTION__);
874 return -1; /* Should never happen */
879 * This function orders the mail accounts according to these rules:
880 * 1st - remote accounts
881 * 2nd - local account
885 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
889 gchar *name1, *name2;
891 GObject *folder1 = NULL;
892 GObject *folder2 = NULL;
894 gtk_tree_model_get (tree_model, iter1,
895 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name1,
896 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
897 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder1,
899 gtk_tree_model_get (tree_model, iter2,
900 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name2,
901 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder2,
904 if (type == TNY_FOLDER_TYPE_ROOT) {
905 /* Compare the types, so that
906 * Remote accounts -> Local account -> MMC account .*/
907 const gint pos1 = get_cmp_rows_type_pos (folder1);
908 const gint pos2 = get_cmp_rows_type_pos (folder2);
909 /* printf ("DEBUG: %s:\n type1=%s, pos1=%d\n type2=%s, pos2=%d\n",
910 __FUNCTION__, G_OBJECT_TYPE_NAME(folder1), pos1, G_OBJECT_TYPE_NAME(folder2), pos2); */
913 else if (pos1 > pos2)
916 /* Compare items of the same type: */
918 TnyAccount *account1 = NULL;
919 if (TNY_IS_ACCOUNT (folder1))
920 account1 = TNY_ACCOUNT (folder1);
922 TnyAccount *account2 = NULL;
923 if (TNY_IS_ACCOUNT (folder2))
924 account2 = TNY_ACCOUNT (folder2);
926 const gchar *account_id = account1 ? tny_account_get_id (account1) : NULL;
927 const gchar *account_id2 = account2 ? tny_account_get_id (account2) : NULL;
929 if (!account_id && !account_id2)
931 else if (!account_id)
933 else if (!account_id2)
935 else if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
938 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
941 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
945 g_object_unref(G_OBJECT(folder1));
947 g_object_unref(G_OBJECT(folder2));
955 /*****************************************************************************/
956 /* DRAG and DROP stuff */
957 /*****************************************************************************/
960 * This function fills the #GtkSelectionData with the row and the
961 * model that has been dragged. It's called when this widget is a
962 * source for dnd after the event drop happened
965 on_drag_data_get (GtkWidget *widget,
966 GdkDragContext *context,
967 GtkSelectionData *selection_data,
972 GtkTreeSelection *selection;
975 GtkTreePath *source_row;
977 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
978 gtk_tree_selection_get_selected (selection, &model, &iter);
979 source_row = gtk_tree_model_get_path (model, &iter);
981 gtk_tree_set_row_drag_data (selection_data,
985 gtk_tree_path_free (source_row);
988 typedef struct _DndHelper {
989 gboolean delete_source;
990 GtkTreePath *source_row;
991 GdkDragContext *context;
997 * This function is the callback of the
998 * modest_mail_operation_xfer_msgs () and
999 * modest_mail_operation_xfer_folder() calls. We check here if the
1000 * message/folder was correctly asynchronously transferred. The reason
1001 * to use the same callback is that the code is the same, it only has
1002 * to check that the operation went fine and then finalize the drag
1006 on_progress_changed (ModestMailOperation *mail_op,
1007 ModestMailOperationState *state,
1013 helper = (DndHelper *) user_data;
1015 if (!state->finished)
1018 if (state->status == MODEST_MAIL_OPERATION_STATUS_SUCCESS) {
1024 /* Notify the drag source. Never call delete, the monitor will
1025 do the job if needed */
1026 gtk_drag_finish (helper->context, success, FALSE, helper->time);
1028 /* Free the helper */
1029 gtk_tree_path_free (helper->source_row);
1030 g_slice_free (DndHelper, helper);
1034 * This function is used by drag_data_received_cb to manage drag and
1035 * drop of a header, i.e, and drag from the header view to the folder
1039 drag_and_drop_from_header_view (GtkTreeModel *source_model,
1040 GtkTreeModel *dest_model,
1041 GtkTreePath *dest_row,
1047 ModestMailOperation *mail_op;
1048 GtkTreeIter source_iter, dest_iter;
1051 gtk_tree_model_get_iter (source_model, &source_iter, helper->source_row);
1052 gtk_tree_model_get (source_model, &source_iter,
1053 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1057 gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
1058 gtk_tree_model_get (dest_model, &dest_iter,
1059 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
1062 /* Transfer message */
1063 mail_op = modest_mail_operation_new (MODEST_MAIL_OPERATION_TYPE_RECEIVE, NULL);
1065 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1067 g_signal_connect (G_OBJECT (mail_op), "progress-changed",
1068 G_CALLBACK (on_progress_changed), helper);
1070 /* FIXME: I replaced this because the API changed, but D&D
1071 should be reviewed in order to allow multiple drags*/
1072 headers = tny_simple_list_new ();
1073 tny_list_append (headers, G_OBJECT (header));
1074 modest_mail_operation_xfer_msgs (mail_op, headers, folder, helper->delete_source, NULL, NULL);
1077 g_object_unref (G_OBJECT (mail_op));
1078 g_object_unref (G_OBJECT (header));
1079 g_object_unref (G_OBJECT (folder));
1080 g_object_unref (headers);
1084 * This function is used by drag_data_received_cb to manage drag and
1085 * drop of a folder, i.e, and drag from the folder view to the same
1089 drag_and_drop_from_folder_view (GtkTreeModel *source_model,
1090 GtkTreeModel *dest_model,
1091 GtkTreePath *dest_row,
1092 GtkSelectionData *selection_data,
1095 ModestMailOperation *mail_op;
1096 GtkTreeIter parent_iter, iter;
1097 TnyFolderStore *parent_folder;
1100 /* Check if the drag is possible */
1101 /* if (!gtk_tree_path_compare (helper->source_row, dest_row) || */
1102 /* !gtk_tree_drag_dest_row_drop_possible (GTK_TREE_DRAG_DEST (dest_model), */
1104 /* selection_data)) { */
1105 if (!gtk_tree_path_compare (helper->source_row, dest_row)) {
1107 gtk_drag_finish (helper->context, FALSE, FALSE, helper->time);
1108 gtk_tree_path_free (helper->source_row);
1109 g_slice_free (DndHelper, helper);
1114 gtk_tree_model_get_iter (source_model, &parent_iter, dest_row);
1115 gtk_tree_model_get_iter (source_model, &iter, helper->source_row);
1116 gtk_tree_model_get (source_model, &parent_iter,
1117 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
1118 &parent_folder, -1);
1119 gtk_tree_model_get (source_model, &iter,
1120 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
1123 /* Do the mail operation */
1124 mail_op = modest_mail_operation_new_with_error_handling (MODEST_MAIL_OPERATION_TYPE_RECEIVE,
1126 modest_ui_actions_move_folder_error_handler,
1128 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1130 g_signal_connect (G_OBJECT (mail_op), "progress-changed",
1131 G_CALLBACK (on_progress_changed), helper);
1133 modest_mail_operation_xfer_folder (mail_op,
1136 helper->delete_source);
1139 g_object_unref (G_OBJECT (parent_folder));
1140 g_object_unref (G_OBJECT (folder));
1141 g_object_unref (G_OBJECT (mail_op));
1145 * This function receives the data set by the "drag-data-get" signal
1146 * handler. This information comes within the #GtkSelectionData. This
1147 * function will manage both the drags of folders of the treeview and
1148 * drags of headers of the header view widget.
1151 on_drag_data_received (GtkWidget *widget,
1152 GdkDragContext *context,
1155 GtkSelectionData *selection_data,
1160 GtkWidget *source_widget;
1161 GtkTreeModel *dest_model, *source_model;
1162 GtkTreePath *source_row, *dest_row;
1163 GtkTreeViewDropPosition pos;
1164 gboolean success = FALSE, delete_source = FALSE;
1165 DndHelper *helper = NULL;
1167 /* Do not allow further process */
1168 g_signal_stop_emission_by_name (widget, "drag-data-received");
1169 source_widget = gtk_drag_get_source_widget (context);
1171 /* Get the action */
1172 if (context->action == GDK_ACTION_MOVE) {
1173 delete_source = TRUE;
1175 /* Notify that there is no folder selected. We need to
1176 do this in order to update the headers view (and
1177 its monitors, because when moving, the old folder
1178 won't longer exist. We can not wait for the end of
1179 the operation, because the operation won't start if
1180 the folder is in use */
1181 if (source_widget == widget)
1182 g_signal_emit (G_OBJECT (widget),
1183 signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0, NULL, TRUE);
1186 /* Check if the get_data failed */
1187 if (selection_data == NULL || selection_data->length < 0)
1188 gtk_drag_finish (context, success, FALSE, time);
1190 /* Get the models */
1191 gtk_tree_get_row_drag_data (selection_data,
1195 /* Select the destination model */
1196 if (source_widget == widget) {
1197 dest_model = source_model;
1199 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
1202 /* Get the path to the destination row. Can not call
1203 gtk_tree_view_get_drag_dest_row() because the source row
1204 is not selected anymore */
1205 gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget), x, y,
1208 /* Only allow drops IN other rows */
1209 if (!dest_row || pos == GTK_TREE_VIEW_DROP_BEFORE || pos == GTK_TREE_VIEW_DROP_AFTER)
1210 gtk_drag_finish (context, success, FALSE, time);
1212 /* Create the helper */
1213 helper = g_slice_new0 (DndHelper);
1214 helper->delete_source = delete_source;
1215 helper->source_row = gtk_tree_path_copy (source_row);
1216 helper->context = context;
1217 helper->time = time;
1219 /* Drags from the header view */
1220 if (source_widget != widget) {
1222 drag_and_drop_from_header_view (source_model,
1229 drag_and_drop_from_folder_view (source_model,
1237 gtk_tree_path_free (source_row);
1238 gtk_tree_path_free (dest_row);
1242 * We define a "drag-drop" signal handler because we do not want to
1243 * use the default one, because the default one always calls
1244 * gtk_drag_finish and we prefer to do it in the "drag-data-received"
1245 * signal handler, because there we have all the information available
1246 * to know if the dnd was a success or not.
1249 drag_drop_cb (GtkWidget *widget,
1250 GdkDragContext *context,
1258 if (!context->targets)
1261 /* Check if we're dragging a folder row */
1262 target = gtk_drag_dest_find_target (widget, context, NULL);
1264 /* Request the data from the source. */
1265 gtk_drag_get_data(widget, context, target, time);
1271 * This function expands a node of a tree view if it's not expanded
1272 * yet. Not sure why it needs the threads stuff, but gtk+`example code
1273 * does that, so that's why they're here.
1276 expand_row_timeout (gpointer data)
1278 GtkTreeView *tree_view = data;
1279 GtkTreePath *dest_path = NULL;
1280 GtkTreeViewDropPosition pos;
1281 gboolean result = FALSE;
1283 GDK_THREADS_ENTER ();
1285 gtk_tree_view_get_drag_dest_row (tree_view,
1290 (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER ||
1291 pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)) {
1292 gtk_tree_view_expand_row (tree_view, dest_path, FALSE);
1293 gtk_tree_path_free (dest_path);
1297 gtk_tree_path_free (dest_path);
1302 GDK_THREADS_LEAVE ();
1308 * This function is called whenever the pointer is moved over a widget
1309 * while dragging some data. It installs a timeout that will expand a
1310 * node of the treeview if not expanded yet. This function also calls
1311 * gdk_drag_status in order to set the suggested action that will be
1312 * used by the "drag-data-received" signal handler to know if we
1313 * should do a move or just a copy of the data.
1316 on_drag_motion (GtkWidget *widget,
1317 GdkDragContext *context,
1323 GtkTreeViewDropPosition pos;
1324 GtkTreePath *dest_row;
1325 ModestFolderViewPrivate *priv;
1326 GdkDragAction suggested_action;
1327 gboolean valid_location = FALSE;
1329 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
1331 if (priv->timer_expander != 0) {
1332 g_source_remove (priv->timer_expander);
1333 priv->timer_expander = 0;
1336 gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
1341 /* Do not allow drops between folders */
1343 pos == GTK_TREE_VIEW_DROP_BEFORE ||
1344 pos == GTK_TREE_VIEW_DROP_AFTER) {
1345 gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW (widget), NULL, 0);
1346 gdk_drag_status(context, 0, time);
1347 valid_location = FALSE;
1350 valid_location = TRUE;
1353 /* Expand the selected row after 1/2 second */
1354 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), dest_row)) {
1355 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), dest_row, pos);
1356 priv->timer_expander = g_timeout_add (500, expand_row_timeout, widget);
1359 /* Select the desired action. By default we pick MOVE */
1360 suggested_action = GDK_ACTION_MOVE;
1362 if (context->actions == GDK_ACTION_COPY)
1363 gdk_drag_status(context, GDK_ACTION_COPY, time);
1364 else if (context->actions == GDK_ACTION_MOVE)
1365 gdk_drag_status(context, GDK_ACTION_MOVE, time);
1366 else if (context->actions & suggested_action)
1367 gdk_drag_status(context, suggested_action, time);
1369 gdk_drag_status(context, GDK_ACTION_DEFAULT, time);
1373 gtk_tree_path_free (dest_row);
1374 g_signal_stop_emission_by_name (widget, "drag-motion");
1375 return valid_location;
1379 /* Folder view drag types */
1380 const GtkTargetEntry folder_view_drag_types[] =
1382 { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, MODEST_FOLDER_ROW },
1383 { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_APP, MODEST_HEADER_ROW }
1387 * This function sets the treeview as a source and a target for dnd
1388 * events. It also connects all the requirede signals.
1391 setup_drag_and_drop (GtkTreeView *self)
1393 /* Set up the folder view as a dnd destination. Set only the
1394 highlight flag, otherwise gtk will have a different
1396 gtk_drag_dest_set (GTK_WIDGET (self),
1397 GTK_DEST_DEFAULT_HIGHLIGHT,
1398 folder_view_drag_types,
1399 G_N_ELEMENTS (folder_view_drag_types),
1400 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1402 g_signal_connect (G_OBJECT (self),
1403 "drag_data_received",
1404 G_CALLBACK (on_drag_data_received),
1408 /* Set up the treeview as a dnd source */
1409 gtk_drag_source_set (GTK_WIDGET (self),
1411 folder_view_drag_types,
1412 G_N_ELEMENTS (folder_view_drag_types),
1413 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1415 g_signal_connect (G_OBJECT (self),
1417 G_CALLBACK (on_drag_motion),
1420 g_signal_connect (G_OBJECT (self),
1422 G_CALLBACK (on_drag_data_get),
1425 g_signal_connect (G_OBJECT (self),
1427 G_CALLBACK (drag_drop_cb),
1432 * This function manages the navigation through the folders using the
1433 * keyboard or the hardware keys in the device
1436 on_key_pressed (GtkWidget *self,
1440 GtkTreeSelection *selection;
1442 GtkTreeModel *model;
1443 gboolean retval = FALSE;
1445 /* Up and Down are automatically managed by the treeview */
1446 if (event->keyval == GDK_Return) {
1447 /* Expand/Collapse the selected row */
1448 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1449 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
1452 path = gtk_tree_model_get_path (model, &iter);
1454 if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (self), path))
1455 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1457 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1458 gtk_tree_path_free (path);
1460 /* No further processing */
1468 * We listen to the changes in the local folder account name key,
1469 * because we want to show the right name in the view. The local
1470 * folder account name corresponds to the device name in the Maemo
1471 * version. We do this because we do not want to query gconf on each
1472 * tree view refresh. It's better to cache it and change whenever
1476 on_configuration_key_changed (ModestConf* conf,
1478 ModestConfEvent event,
1479 ModestFolderView *self)
1481 ModestFolderViewPrivate *priv;
1486 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
1487 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1489 if (!strcmp (key, MODEST_CONF_DEVICE_NAME)) {
1490 g_free (priv->local_account_name);
1492 if (event == MODEST_CONF_EVENT_KEY_UNSET)
1493 priv->local_account_name = g_strdup (MODEST_LOCAL_FOLDERS_DEFAULT_DISPLAY_NAME);
1495 priv->local_account_name = modest_conf_get_string (modest_runtime_get_conf(),
1496 MODEST_CONF_DEVICE_NAME, NULL);
1498 /* Force a redraw */
1499 #if GTK_CHECK_VERSION(2, 8, 0) /* gtk_tree_view_column_queue_resize is only available in GTK+ 2.8 */
1500 GtkTreeViewColumn * tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self),
1501 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN);
1502 gtk_tree_view_column_queue_resize (tree_column);
1508 modest_folder_view_set_style (ModestFolderView *self,
1509 ModestFolderViewStyle style)
1511 ModestFolderViewPrivate *priv;
1513 g_return_if_fail (self);
1515 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1517 priv->style = style;
1521 modest_folder_view_set_account_id_of_visible_server_account (ModestFolderView *self,
1522 const gchar *account_id)
1524 ModestFolderViewPrivate *priv;
1525 GtkTreeModel *model;
1527 g_return_if_fail (self);
1529 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1531 /* This will be used by the filter_row callback,
1532 * to decided which rows to show: */
1533 if (priv->visible_account_id)
1534 g_free (priv->visible_account_id);
1535 priv->visible_account_id = g_strdup (account_id);
1538 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1539 if (GTK_IS_TREE_MODEL_FILTER (model))
1540 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
1544 modest_folder_view_get_account_id_of_visible_server_account (ModestFolderView *self)
1546 ModestFolderViewPrivate *priv;
1548 g_return_val_if_fail (self, NULL);
1550 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1552 return (const gchar *) priv->visible_account_id;