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-marshal.h>
45 #include <modest-icon-names.h>
46 #include <modest-tny-account-store.h>
47 #include <modest-text-utils.h>
48 #include <modest-runtime.h>
49 #include "modest-folder-view.h"
50 #include <modest-dnd.h>
51 #include <modest-platform.h>
52 #include <modest-account-mgr-helpers.h>
53 #include <modest-widget-memory.h>
55 /* 'private'/'protected' functions */
56 static void modest_folder_view_class_init (ModestFolderViewClass *klass);
57 static void modest_folder_view_init (ModestFolderView *obj);
58 static void modest_folder_view_finalize (GObject *obj);
60 static void tny_account_store_view_init (gpointer g,
63 static void modest_folder_view_set_account_store (TnyAccountStoreView *self,
64 TnyAccountStore *account_store);
66 static gboolean update_model (ModestFolderView *self,
67 ModestTnyAccountStore *account_store);
69 static void on_selection_changed (GtkTreeSelection *sel, gpointer data);
71 static void on_account_update (TnyAccountStore *account_store,
75 static void on_accounts_reloaded (TnyAccountStore *store,
78 static gint cmp_rows (GtkTreeModel *tree_model,
83 static gboolean filter_row (GtkTreeModel *model,
87 static gboolean on_key_pressed (GtkWidget *self,
91 static void on_configuration_key_changed (ModestConf* conf,
93 ModestConfEvent event,
94 ModestFolderView *self);
97 static void on_drag_data_get (GtkWidget *widget,
98 GdkDragContext *context,
99 GtkSelectionData *selection_data,
104 static void on_drag_data_received (GtkWidget *widget,
105 GdkDragContext *context,
108 GtkSelectionData *selection_data,
113 static gboolean on_drag_motion (GtkWidget *widget,
114 GdkDragContext *context,
120 static gint expand_row_timeout (gpointer data);
122 static void setup_drag_and_drop (GtkTreeView *self);
125 FOLDER_SELECTION_CHANGED_SIGNAL,
126 FOLDER_DISPLAY_NAME_CHANGED_SIGNAL,
130 typedef struct _ModestFolderViewPrivate ModestFolderViewPrivate;
131 struct _ModestFolderViewPrivate {
132 TnyAccountStore *account_store;
133 TnyFolderStore *cur_folder_store;
135 gulong account_update_signal;
136 gulong changed_signal;
137 gulong accounts_reloaded_signal;
139 GtkTreeSelection *cur_selection;
140 TnyFolderStoreQuery *query;
141 guint timer_expander;
143 gchar *local_account_name;
144 gchar *visible_account_id;
145 ModestFolderViewStyle style;
147 #define MODEST_FOLDER_VIEW_GET_PRIVATE(o) \
148 (G_TYPE_INSTANCE_GET_PRIVATE((o), \
149 MODEST_TYPE_FOLDER_VIEW, \
150 ModestFolderViewPrivate))
152 static GObjectClass *parent_class = NULL;
154 static guint signals[LAST_SIGNAL] = {0};
157 modest_folder_view_get_type (void)
159 static GType my_type = 0;
161 static const GTypeInfo my_info = {
162 sizeof(ModestFolderViewClass),
163 NULL, /* base init */
164 NULL, /* base finalize */
165 (GClassInitFunc) modest_folder_view_class_init,
166 NULL, /* class finalize */
167 NULL, /* class data */
168 sizeof(ModestFolderView),
170 (GInstanceInitFunc) modest_folder_view_init,
174 static const GInterfaceInfo tny_account_store_view_info = {
175 (GInterfaceInitFunc) tny_account_store_view_init, /* interface_init */
176 NULL, /* interface_finalize */
177 NULL /* interface_data */
181 my_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
185 g_type_add_interface_static (my_type,
186 TNY_TYPE_ACCOUNT_STORE_VIEW,
187 &tny_account_store_view_info);
193 modest_folder_view_class_init (ModestFolderViewClass *klass)
195 GObjectClass *gobject_class;
196 gobject_class = (GObjectClass*) klass;
198 parent_class = g_type_class_peek_parent (klass);
199 gobject_class->finalize = modest_folder_view_finalize;
201 g_type_class_add_private (gobject_class,
202 sizeof(ModestFolderViewPrivate));
204 signals[FOLDER_SELECTION_CHANGED_SIGNAL] =
205 g_signal_new ("folder_selection_changed",
206 G_TYPE_FROM_CLASS (gobject_class),
208 G_STRUCT_OFFSET (ModestFolderViewClass,
209 folder_selection_changed),
211 modest_marshal_VOID__POINTER_BOOLEAN,
212 G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_BOOLEAN);
215 * This signal is emitted whenever the currently selected
216 * folder display name is computed. Note that the name could
217 * be different to the folder name, because we could append
218 * the unread messages count to the folder name to build the
219 * folder display name
221 signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL] =
222 g_signal_new ("folder-display-name-changed",
223 G_TYPE_FROM_CLASS (gobject_class),
225 G_STRUCT_OFFSET (ModestFolderViewClass,
226 folder_display_name_changed),
228 g_cclosure_marshal_VOID__STRING,
229 G_TYPE_NONE, 1, G_TYPE_STRING);
235 text_cell_data (GtkTreeViewColumn *column, GtkCellRenderer *renderer,
236 GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
238 ModestFolderViewPrivate *priv;
243 GObject *instance = NULL;
245 g_return_if_fail (column);
246 g_return_if_fail (tree_model);
248 gtk_tree_model_get (tree_model, iter,
249 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &fname,
250 TNY_GTK_FOLDER_STORE_TREE_MODEL_ALL_COLUMN, &all,
251 TNY_GTK_FOLDER_STORE_TREE_MODEL_UNREAD_COLUMN, &unread,
252 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
253 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
255 rendobj = G_OBJECT(renderer);
266 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (data);
268 if (type != TNY_FOLDER_TYPE_ROOT) {
271 if (modest_tny_folder_is_local_folder (TNY_FOLDER (instance))) {
273 type = modest_tny_folder_get_local_folder_type (TNY_FOLDER (instance));
274 if (type != TNY_FOLDER_TYPE_UNKNOWN) {
276 fname = g_strdup(modest_local_folder_info_get_type_display_name (type));
280 /* Select the number to show */
281 if ((type == TNY_FOLDER_TYPE_DRAFTS) || (type == TNY_FOLDER_TYPE_OUTBOX))
286 /* Use bold font style if there are unread messages */
288 gchar *folder_title = g_strdup_printf ("%s (%d)", fname, unread);
289 g_object_set (rendobj,"text", folder_title, "weight", 800, NULL);
290 if (G_OBJECT (priv->cur_folder_store) == instance)
291 g_signal_emit (G_OBJECT(data),
292 signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
294 g_free (folder_title);
296 g_object_set (rendobj,"text", fname, "weight", 400, NULL);
297 if (G_OBJECT (priv->cur_folder_store) == instance)
298 g_signal_emit (G_OBJECT(data),
299 signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
304 const gchar *account_name = NULL;
305 const gchar *account_id = NULL;
307 /* If it's a server account */
308 account_id = tny_account_get_id (TNY_ACCOUNT (instance));
309 if (!strcmp (account_id, MODEST_LOCAL_FOLDERS_ACCOUNT_ID)) {
310 account_name = priv->local_account_name;
312 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
313 /* TODO: get MMC card name */
315 account_name = fname;
319 /* Notify display name observers */
320 if (G_OBJECT (priv->cur_folder_store) == instance)
321 g_signal_emit (G_OBJECT(data),
322 signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
325 /* Use bold font style */
326 g_object_set (rendobj,"text", account_name, "weight", 800, NULL);
329 g_object_unref (G_OBJECT (instance));
336 icon_cell_data (GtkTreeViewColumn *column, GtkCellRenderer *renderer,
337 GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
339 GObject *rendobj = NULL, *instance = NULL;
340 GdkPixbuf *pixbuf = NULL;
343 const gchar *account_id = NULL;
346 rendobj = G_OBJECT(renderer);
347 gtk_tree_model_get (tree_model, iter,
348 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
349 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &fname,
350 TNY_GTK_FOLDER_STORE_TREE_MODEL_UNREAD_COLUMN, &unread,
351 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
362 if (type == TNY_FOLDER_TYPE_NORMAL || type == TNY_FOLDER_TYPE_UNKNOWN) {
363 type = modest_tny_folder_guess_folder_type_from_name (fname);
367 case TNY_FOLDER_TYPE_ROOT:
368 account_id = tny_account_get_id (TNY_ACCOUNT (instance));
369 if (!strcmp (account_id, MODEST_LOCAL_FOLDERS_ACCOUNT_ID)) {
370 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_LOCAL_FOLDERS);
372 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
373 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_MMC);
375 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_ACCOUNT);
378 case TNY_FOLDER_TYPE_INBOX:
379 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_INBOX);
381 case TNY_FOLDER_TYPE_OUTBOX:
382 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_OUTBOX);
384 case TNY_FOLDER_TYPE_JUNK:
385 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_JUNK);
387 case TNY_FOLDER_TYPE_SENT:
388 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_SENT);
390 case TNY_FOLDER_TYPE_TRASH:
391 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_TRASH);
393 case TNY_FOLDER_TYPE_DRAFTS:
394 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_DRAFTS);
396 case TNY_FOLDER_TYPE_NORMAL:
398 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_NORMAL);
401 g_object_unref (G_OBJECT (instance));
405 g_object_set (rendobj, "pixbuf", pixbuf, NULL);
408 g_object_unref (pixbuf);
412 add_columns (GtkWidget *treeview)
414 GtkTreeViewColumn *column;
415 GtkCellRenderer *renderer;
416 GtkTreeSelection *sel;
419 column = gtk_tree_view_column_new ();
421 /* Set icon and text render function */
422 renderer = gtk_cell_renderer_pixbuf_new();
423 gtk_tree_view_column_pack_start (column, renderer, FALSE);
424 gtk_tree_view_column_set_cell_data_func(column, renderer,
425 icon_cell_data, treeview, NULL);
427 renderer = gtk_cell_renderer_text_new();
428 gtk_tree_view_column_pack_start (column, renderer, FALSE);
429 gtk_tree_view_column_set_cell_data_func(column, renderer,
430 text_cell_data, treeview, NULL);
432 /* Set selection mode */
433 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(treeview));
434 gtk_tree_selection_set_mode (sel, GTK_SELECTION_SINGLE);
436 /* Set treeview appearance */
437 gtk_tree_view_column_set_spacing (column, 2);
438 gtk_tree_view_column_set_resizable (column, TRUE);
439 gtk_tree_view_column_set_fixed_width (column, TRUE);
440 gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(treeview), FALSE);
441 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(treeview), FALSE);
444 gtk_tree_view_append_column (GTK_TREE_VIEW(treeview),column);
448 modest_folder_view_init (ModestFolderView *obj)
450 ModestFolderViewPrivate *priv;
453 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
455 priv->timer_expander = 0;
456 priv->account_store = NULL;
458 priv->style = MODEST_FOLDER_VIEW_STYLE_SHOW_ALL;
459 priv->cur_folder_store = NULL;
460 priv->visible_account_id = NULL;
462 /* Initialize the local account name */
463 conf = modest_runtime_get_conf();
464 priv->local_account_name = modest_conf_get_string (conf, MODEST_CONF_DEVICE_NAME, NULL);
467 add_columns (GTK_WIDGET (obj));
469 /* Setup drag and drop */
470 setup_drag_and_drop (GTK_TREE_VIEW(obj));
473 modest_widget_memory_restore (conf, G_OBJECT (obj), MODEST_CONF_FOLDER_VIEW_KEY);
475 /* Connect signals */
476 g_signal_connect (G_OBJECT (obj),
478 G_CALLBACK (on_key_pressed), NULL);
481 * Track changes in the local account name (in the device it
482 * will be the device name)
484 g_signal_connect (G_OBJECT(conf),
486 G_CALLBACK(on_configuration_key_changed), obj);
491 tny_account_store_view_init (gpointer g, gpointer iface_data)
493 TnyAccountStoreViewIface *klass = (TnyAccountStoreViewIface *)g;
495 klass->set_account_store_func = modest_folder_view_set_account_store;
501 modest_folder_view_finalize (GObject *obj)
503 ModestFolderViewPrivate *priv;
504 GtkTreeSelection *sel;
506 g_return_if_fail (obj);
508 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
510 if (priv->timer_expander != 0) {
511 g_source_remove (priv->timer_expander);
512 priv->timer_expander = 0;
515 if (priv->account_store) {
516 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
517 priv->account_update_signal);
518 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
519 priv->accounts_reloaded_signal);
520 g_object_unref (G_OBJECT(priv->account_store));
521 priv->account_store = NULL;
525 g_object_unref (G_OBJECT (priv->query));
529 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(obj));
531 g_signal_handler_disconnect (G_OBJECT(sel), priv->changed_signal);
533 g_free (priv->local_account_name);
534 g_free (priv->visible_account_id);
536 G_OBJECT_CLASS(parent_class)->finalize (obj);
541 modest_folder_view_set_account_store (TnyAccountStoreView *self, TnyAccountStore *account_store)
543 ModestFolderViewPrivate *priv;
546 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
547 g_return_if_fail (TNY_IS_ACCOUNT_STORE (account_store));
549 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
550 device = tny_account_store_get_device (account_store);
552 if (G_UNLIKELY (priv->account_store)) {
554 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
555 priv->account_update_signal))
556 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
557 priv->account_update_signal);
558 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
559 priv->accounts_reloaded_signal))
560 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
561 priv->accounts_reloaded_signal);
563 g_object_unref (G_OBJECT (priv->account_store));
566 priv->account_store = g_object_ref (G_OBJECT (account_store));
568 priv->account_update_signal =
569 g_signal_connect (G_OBJECT(account_store), "account_update",
570 G_CALLBACK (on_account_update), self);
572 priv->accounts_reloaded_signal =
573 g_signal_connect (G_OBJECT(account_store), "accounts_reloaded",
574 G_CALLBACK (on_accounts_reloaded), self);
576 if (!update_model (MODEST_FOLDER_VIEW (self),
577 MODEST_TNY_ACCOUNT_STORE (priv->account_store)))
578 g_printerr ("modest: failed to update model\n");
580 g_object_unref (G_OBJECT (device));
584 on_account_update (TnyAccountStore *account_store, const gchar *account,
587 if (!update_model (MODEST_FOLDER_VIEW(user_data),
588 MODEST_TNY_ACCOUNT_STORE(account_store)))
589 g_printerr ("modest: failed to update model for changes in '%s'",
594 on_accounts_reloaded (TnyAccountStore *account_store,
597 update_model (MODEST_FOLDER_VIEW (user_data),
598 MODEST_TNY_ACCOUNT_STORE(account_store));
602 modest_folder_view_set_title (ModestFolderView *self, const gchar *title)
604 GtkTreeViewColumn *col;
606 g_return_if_fail (self);
608 col = gtk_tree_view_get_column (GTK_TREE_VIEW(self), 0);
610 g_printerr ("modest: failed get column for title\n");
614 gtk_tree_view_column_set_title (col, title);
615 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self),
620 modest_folder_view_new (TnyFolderStoreQuery *query)
623 ModestFolderViewPrivate *priv;
624 GtkTreeSelection *sel;
626 self = G_OBJECT (g_object_new (MODEST_TYPE_FOLDER_VIEW, NULL));
627 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
630 priv->query = g_object_ref (query);
632 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
633 priv->changed_signal = g_signal_connect (sel, "changed",
634 G_CALLBACK (on_selection_changed), self);
636 return GTK_WIDGET(self);
639 /* this feels dirty; any other way to expand all the root items? */
641 expand_root_items (ModestFolderView *self)
644 path = gtk_tree_path_new_first ();
646 /* all folders should have child items, so.. */
647 while (gtk_tree_view_expand_row (GTK_TREE_VIEW(self), path, FALSE))
648 gtk_tree_path_next (path);
650 gtk_tree_path_free (path);
654 * We use this function to implement the
655 * MODEST_FOLDER_VIEW_STYLE_SHOW_ONE style. We only show the default
656 * account in this case
659 filter_row (GtkTreeModel *model,
663 gboolean retval = TRUE;
665 GObject *instance = NULL;
667 gtk_tree_model_get (model, iter,
668 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
669 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
672 if (type == TNY_FOLDER_TYPE_ROOT) {
674 const gchar *account_id;
676 acc = TNY_ACCOUNT (instance);
677 account_id = tny_account_get_id (acc);
679 if (strcmp (account_id, MODEST_LOCAL_FOLDERS_ACCOUNT_ID) &&
680 strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
681 ModestFolderViewPrivate *priv;
683 /* Show only the visible account id */
684 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (data);
685 if (priv->visible_account_id && strcmp (account_id, priv->visible_account_id))
690 g_object_unref (instance);
696 update_model (ModestFolderView *self, ModestTnyAccountStore *account_store)
698 ModestFolderViewPrivate *priv;
699 TnyList *account_list;
702 g_return_val_if_fail (account_store, FALSE);
704 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
706 /* Notify that there is no folder selected */
707 g_signal_emit (G_OBJECT(self),
708 signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
711 /* FIXME: the local accounts are not shown when the query
712 selects only the subscribed folders. */
713 /* model = tny_gtk_folder_store_tree_model_new (TRUE, priv->query); */
714 model = tny_gtk_folder_store_tree_model_new (TRUE, NULL);
715 account_list = TNY_LIST(model);
717 tny_account_store_get_accounts (TNY_ACCOUNT_STORE(account_store),
719 TNY_ACCOUNT_STORE_STORE_ACCOUNTS);
721 GtkTreeModel *filter_model = NULL, *sortable = NULL;
723 sortable = gtk_tree_model_sort_new_with_model (model);
724 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
725 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
727 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
728 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
729 cmp_rows, NULL, NULL);
731 /* Create filter model */
732 if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE) {
733 filter_model = gtk_tree_model_filter_new (sortable, NULL);
734 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
741 gtk_tree_view_set_model (GTK_TREE_VIEW(self),
742 (filter_model) ? filter_model : sortable);
743 expand_root_items (self); /* expand all account folders */
744 g_object_unref (account_list);
752 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
755 TnyFolderStore *folder = NULL;
757 ModestFolderView *tree_view;
758 ModestFolderViewPrivate *priv;
761 g_return_if_fail (sel);
762 g_return_if_fail (user_data);
764 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
765 priv->cur_selection = sel;
767 /* folder was _un_selected if true */
768 if (!gtk_tree_selection_get_selected (sel, &model, &iter)) {
769 if (priv->cur_folder_store)
770 g_object_unref (priv->cur_folder_store);
771 priv->cur_folder_store = NULL;
773 /* Notify the display name observers */
774 g_signal_emit (G_OBJECT(user_data),
775 signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
780 tree_view = MODEST_FOLDER_VIEW (user_data);
782 gtk_tree_model_get (model, &iter,
783 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
784 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
787 /* If the folder is the same do not notify */
788 if (priv->cur_folder_store == folder) {
789 g_object_unref (folder);
793 /* Current folder was unselected */
794 if (priv->cur_folder_store) {
795 g_signal_emit (G_OBJECT(tree_view), signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
796 priv->cur_folder_store, FALSE);
797 g_object_unref (priv->cur_folder_store);
800 /* New current references */
801 priv->cur_folder_store = folder;
803 /* New folder has been selected */
804 g_signal_emit (G_OBJECT(tree_view),
805 signals[FOLDER_SELECTION_CHANGED_SIGNAL],
810 modest_folder_view_get_selected (ModestFolderView *self)
812 ModestFolderViewPrivate *priv;
814 g_return_val_if_fail (self, NULL);
816 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
817 if (priv->cur_folder_store)
818 g_object_ref (priv->cur_folder_store);
820 return priv->cur_folder_store;
824 * This function orders the mail accounts following the next rules
825 * 1st - remote accounts
826 * 2nd - local account
830 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
834 gchar *name1, *name2;
836 TnyFolder *folder1, *folder2;
838 gtk_tree_model_get (tree_model, iter1,
839 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name1,
840 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
841 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder1,
843 gtk_tree_model_get (tree_model, iter2,
844 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name2,
845 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder2,
848 /* Order must be: Remote accounts -> Local account -> MMC account */
849 if (type == TNY_FOLDER_TYPE_ROOT) {
850 const gchar *account_id = tny_account_get_id (TNY_ACCOUNT (folder1));
851 const gchar *account_id2 = tny_account_get_id (TNY_ACCOUNT (folder2));
853 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
856 if (!strcmp (account_id, MODEST_LOCAL_FOLDERS_ACCOUNT_ID)) {
857 if (!strcmp (account_id2, MODEST_MMC_ACCOUNT_ID))
862 if (!strcmp (account_id2, MODEST_LOCAL_FOLDERS_ACCOUNT_ID) ||
863 !strcmp (account_id2, MODEST_MMC_ACCOUNT_ID))
866 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
870 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
873 g_object_unref(G_OBJECT(folder1));
875 g_object_unref(G_OBJECT(folder2));
883 /*****************************************************************************/
884 /* DRAG and DROP stuff */
885 /*****************************************************************************/
888 * This function fills the #GtkSelectionData with the row and the
889 * model that has been dragged. It's called when this widget is a
890 * source for dnd after the event drop happened
893 on_drag_data_get (GtkWidget *widget,
894 GdkDragContext *context,
895 GtkSelectionData *selection_data,
900 GtkTreeSelection *selection;
903 GtkTreePath *source_row;
905 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
906 gtk_tree_selection_get_selected (selection, &model, &iter);
907 source_row = gtk_tree_model_get_path (model, &iter);
909 gtk_tree_set_row_drag_data (selection_data,
913 gtk_tree_path_free (source_row);
916 typedef struct _DndHelper {
917 gboolean delete_source;
918 GtkTreePath *source_row;
919 GdkDragContext *context;
925 * This function is the callback of the
926 * modest_mail_operation_xfer_msgs () and
927 * modest_mail_operation_xfer_folder() calls. We check here if the
928 * message/folder was correctly asynchronously transferred. The reason
929 * to use the same callback is that the code is the same, it only has
930 * to check that the operation went fine and then finalize the drag
934 on_progress_changed (ModestMailOperation *mail_op, gpointer user_data)
939 helper = (DndHelper *) user_data;
941 if (!modest_mail_operation_is_finished (mail_op))
944 if (modest_mail_operation_get_status (mail_op) ==
945 MODEST_MAIL_OPERATION_STATUS_SUCCESS) {
951 /* Notify the drag source. Never call delete, the monitor will
952 do the job if needed */
953 gtk_drag_finish (helper->context, success, FALSE, helper->time);
955 /* Free the helper */
956 gtk_tree_path_free (helper->source_row);
957 g_slice_free (DndHelper, helper);
961 * This function is used by drag_data_received_cb to manage drag and
962 * drop of a header, i.e, and drag from the header view to the folder
966 drag_and_drop_from_header_view (GtkTreeModel *source_model,
967 GtkTreeModel *dest_model,
968 GtkTreePath *dest_row,
974 ModestMailOperation *mail_op;
975 GtkTreeIter source_iter, dest_iter;
978 gtk_tree_model_get_iter (source_model, &source_iter, helper->source_row);
979 gtk_tree_model_get (source_model, &source_iter,
980 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
984 gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
985 gtk_tree_model_get (dest_model, &dest_iter,
986 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
989 /* Transfer message */
990 mail_op = modest_mail_operation_new (MODEST_MAIL_OPERATION_ID_RECEIVE, NULL);
992 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
994 g_signal_connect (G_OBJECT (mail_op), "progress-changed",
995 G_CALLBACK (on_progress_changed), helper);
997 /* FIXME: I replaced this because the API changed, but D&D
998 should be reviewed in order to allow multiple drags*/
999 headers = tny_simple_list_new ();
1000 tny_list_append (headers, G_OBJECT (header));
1001 modest_mail_operation_xfer_msgs (mail_op, headers, folder, helper->delete_source, NULL, NULL);
1004 g_object_unref (G_OBJECT (mail_op));
1005 g_object_unref (G_OBJECT (header));
1006 g_object_unref (G_OBJECT (folder));
1007 g_object_unref (headers);
1011 * This function is used by drag_data_received_cb to manage drag and
1012 * drop of a folder, i.e, and drag from the folder view to the same
1016 drag_and_drop_from_folder_view (GtkTreeModel *source_model,
1017 GtkTreeModel *dest_model,
1018 GtkTreePath *dest_row,
1019 GtkSelectionData *selection_data,
1022 ModestMailOperation *mail_op;
1023 GtkTreeIter parent_iter, iter;
1024 TnyFolderStore *parent_folder;
1027 /* Check if the drag is possible */
1028 /* if (!gtk_tree_path_compare (helper->source_row, dest_row) || */
1029 /* !gtk_tree_drag_dest_row_drop_possible (GTK_TREE_DRAG_DEST (dest_model), */
1031 /* selection_data)) { */
1032 if (!gtk_tree_path_compare (helper->source_row, dest_row)) {
1034 gtk_drag_finish (helper->context, FALSE, FALSE, helper->time);
1035 gtk_tree_path_free (helper->source_row);
1036 g_slice_free (DndHelper, helper);
1041 gtk_tree_model_get_iter (source_model, &parent_iter, dest_row);
1042 gtk_tree_model_get_iter (source_model, &iter, helper->source_row);
1043 gtk_tree_model_get (source_model, &parent_iter,
1044 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
1045 &parent_folder, -1);
1046 gtk_tree_model_get (source_model, &iter,
1047 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
1050 /* Do the mail operation */
1051 mail_op = modest_mail_operation_new (MODEST_MAIL_OPERATION_ID_RECEIVE, NULL);
1052 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1054 g_signal_connect (G_OBJECT (mail_op), "progress-changed",
1055 G_CALLBACK (on_progress_changed), helper);
1057 modest_mail_operation_xfer_folder_async (mail_op,
1060 helper->delete_source);
1063 g_object_unref (G_OBJECT (parent_folder));
1064 g_object_unref (G_OBJECT (folder));
1065 g_object_unref (G_OBJECT (mail_op));
1069 * This function receives the data set by the "drag-data-get" signal
1070 * handler. This information comes within the #GtkSelectionData. This
1071 * function will manage both the drags of folders of the treeview and
1072 * drags of headers of the header view widget.
1075 on_drag_data_received (GtkWidget *widget,
1076 GdkDragContext *context,
1079 GtkSelectionData *selection_data,
1084 GtkWidget *source_widget;
1085 GtkTreeModel *dest_model, *source_model;
1086 GtkTreePath *source_row, *dest_row;
1087 GtkTreeViewDropPosition pos;
1088 gboolean success = FALSE, delete_source = FALSE;
1089 DndHelper *helper = NULL;
1091 /* Do not allow further process */
1092 g_signal_stop_emission_by_name (widget, "drag-data-received");
1093 source_widget = gtk_drag_get_source_widget (context);
1095 /* Get the action */
1096 if (context->action == GDK_ACTION_MOVE) {
1097 delete_source = TRUE;
1099 /* Notify that there is no folder selected. We need to
1100 do this in order to update the headers view (and
1101 its monitors, because when moving, the old folder
1102 won't longer exist. We can not wait for the end of
1103 the operation, because the operation won't start if
1104 the folder is in use */
1105 /* TODO: helper was not instantiated before here: */
1106 if (helper && helper->delete_source && source_widget == widget)
1107 g_signal_emit (G_OBJECT (widget),
1108 signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0, NULL, TRUE);
1111 /* Check if the get_data failed */
1112 if (selection_data == NULL || selection_data->length < 0)
1113 gtk_drag_finish (context, success, FALSE, time);
1115 /* Get the models */
1116 gtk_tree_get_row_drag_data (selection_data,
1120 /* Select the destination model */
1121 if (source_widget == widget) {
1122 dest_model = source_model;
1124 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
1127 /* Get the path to the destination row. Can not call
1128 gtk_tree_view_get_drag_dest_row() because the source row
1129 is not selected anymore */
1130 gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget), x, y,
1133 /* Only allow drops IN other rows */
1134 if (!dest_row || pos == GTK_TREE_VIEW_DROP_BEFORE || pos == GTK_TREE_VIEW_DROP_AFTER)
1135 gtk_drag_finish (context, success, FALSE, time);
1137 /* Create the helper */
1138 helper = g_slice_new0 (DndHelper);
1139 helper->delete_source = delete_source;
1140 helper->source_row = gtk_tree_path_copy (source_row);
1141 helper->context = context;
1142 helper->time = time;
1144 /* Drags from the header view */
1145 if (source_widget != widget) {
1147 drag_and_drop_from_header_view (source_model,
1154 drag_and_drop_from_folder_view (source_model,
1162 gtk_tree_path_free (source_row);
1163 gtk_tree_path_free (dest_row);
1167 * We define a "drag-drop" signal handler because we do not want to
1168 * use the default one, because the default one always calls
1169 * gtk_drag_finish and we prefer to do it in the "drag-data-received"
1170 * signal handler, because there we have all the information available
1171 * to know if the dnd was a success or not.
1174 drag_drop_cb (GtkWidget *widget,
1175 GdkDragContext *context,
1183 if (!context->targets)
1186 /* Check if we're dragging a folder row */
1187 target = gtk_drag_dest_find_target (widget, context, NULL);
1189 /* Request the data from the source. */
1190 gtk_drag_get_data(widget, context, target, time);
1196 * This function expands a node of a tree view if it's not expanded
1197 * yet. Not sure why it needs the threads stuff, but gtk+`example code
1198 * does that, so that's why they're here.
1201 expand_row_timeout (gpointer data)
1203 GtkTreeView *tree_view = data;
1204 GtkTreePath *dest_path = NULL;
1205 GtkTreeViewDropPosition pos;
1206 gboolean result = FALSE;
1208 GDK_THREADS_ENTER ();
1210 gtk_tree_view_get_drag_dest_row (tree_view,
1215 (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER ||
1216 pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)) {
1217 gtk_tree_view_expand_row (tree_view, dest_path, FALSE);
1218 gtk_tree_path_free (dest_path);
1222 gtk_tree_path_free (dest_path);
1227 GDK_THREADS_LEAVE ();
1233 * This function is called whenever the pointer is moved over a widget
1234 * while dragging some data. It installs a timeout that will expand a
1235 * node of the treeview if not expanded yet. This function also calls
1236 * gdk_drag_status in order to set the suggested action that will be
1237 * used by the "drag-data-received" signal handler to know if we
1238 * should do a move or just a copy of the data.
1241 on_drag_motion (GtkWidget *widget,
1242 GdkDragContext *context,
1248 GtkTreeViewDropPosition pos;
1249 GtkTreePath *dest_row;
1250 ModestFolderViewPrivate *priv;
1251 GdkDragAction suggested_action;
1252 gboolean valid_location = FALSE;
1254 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
1256 if (priv->timer_expander != 0) {
1257 g_source_remove (priv->timer_expander);
1258 priv->timer_expander = 0;
1261 gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
1266 /* Do not allow drops between folders */
1268 pos == GTK_TREE_VIEW_DROP_BEFORE ||
1269 pos == GTK_TREE_VIEW_DROP_AFTER) {
1270 gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW (widget), NULL, 0);
1271 gdk_drag_status(context, 0, time);
1272 valid_location = FALSE;
1275 valid_location = TRUE;
1278 /* Expand the selected row after 1/2 second */
1279 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), dest_row)) {
1280 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), dest_row, pos);
1281 priv->timer_expander = g_timeout_add (500, expand_row_timeout, widget);
1284 /* Select the desired action. By default we pick MOVE */
1285 suggested_action = GDK_ACTION_MOVE;
1287 if (context->actions == GDK_ACTION_COPY)
1288 gdk_drag_status(context, GDK_ACTION_COPY, time);
1289 else if (context->actions == GDK_ACTION_MOVE)
1290 gdk_drag_status(context, GDK_ACTION_MOVE, time);
1291 else if (context->actions & suggested_action)
1292 gdk_drag_status(context, suggested_action, time);
1294 gdk_drag_status(context, GDK_ACTION_DEFAULT, time);
1298 gtk_tree_path_free (dest_row);
1299 g_signal_stop_emission_by_name (widget, "drag-motion");
1300 return valid_location;
1304 /* Folder view drag types */
1305 const GtkTargetEntry folder_view_drag_types[] =
1307 { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, MODEST_FOLDER_ROW },
1308 { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_APP, MODEST_HEADER_ROW }
1312 * This function sets the treeview as a source and a target for dnd
1313 * events. It also connects all the requirede signals.
1316 setup_drag_and_drop (GtkTreeView *self)
1318 /* Set up the folder view as a dnd destination. Set only the
1319 highlight flag, otherwise gtk will have a different
1321 gtk_drag_dest_set (GTK_WIDGET (self),
1322 GTK_DEST_DEFAULT_HIGHLIGHT,
1323 folder_view_drag_types,
1324 G_N_ELEMENTS (folder_view_drag_types),
1325 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1327 g_signal_connect (G_OBJECT (self),
1328 "drag_data_received",
1329 G_CALLBACK (on_drag_data_received),
1333 /* Set up the treeview as a dnd source */
1334 gtk_drag_source_set (GTK_WIDGET (self),
1336 folder_view_drag_types,
1337 G_N_ELEMENTS (folder_view_drag_types),
1338 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1340 g_signal_connect (G_OBJECT (self),
1342 G_CALLBACK (on_drag_motion),
1345 g_signal_connect (G_OBJECT (self),
1347 G_CALLBACK (on_drag_data_get),
1350 g_signal_connect (G_OBJECT (self),
1352 G_CALLBACK (drag_drop_cb),
1357 * This function manages the navigation through the folders using the
1358 * keyboard or the hardware keys in the device
1361 on_key_pressed (GtkWidget *self,
1365 GtkTreeSelection *selection;
1367 GtkTreeModel *model;
1368 gboolean retval = FALSE;
1370 /* Up and Down are automatically managed by the treeview */
1371 if (event->keyval == GDK_Return) {
1372 /* Expand/Collapse the selected row */
1373 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1374 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
1377 path = gtk_tree_model_get_path (model, &iter);
1379 if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (self), path))
1380 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1382 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1383 gtk_tree_path_free (path);
1385 /* No further processing */
1393 * We listen to the changes in the local folder account name key,
1394 * because we want to show the right name in the view. The local
1395 * folder account name corresponds to the device name in the Maemo
1396 * version. We do this because we do not want to query gconf on each
1397 * tree view refresh. It's better to cache it and change whenever
1401 on_configuration_key_changed (ModestConf* conf,
1403 ModestConfEvent event,
1404 ModestFolderView *self)
1406 ModestFolderViewPrivate *priv;
1411 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
1412 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1414 if (!strcmp (key, MODEST_CONF_DEVICE_NAME)) {
1415 g_free (priv->local_account_name);
1417 if (event == MODEST_CONF_EVENT_KEY_UNSET)
1418 priv->local_account_name = g_strdup (MODEST_LOCAL_FOLDERS_DEFAULT_DISPLAY_NAME);
1420 priv->local_account_name = modest_conf_get_string (modest_runtime_get_conf(),
1421 MODEST_CONF_DEVICE_NAME, NULL);
1423 /* Force a redraw */
1424 #if GTK_CHECK_VERSION(2, 8, 0) /* gtk_tree_view_column_queue_resize is only available in GTK+ 2.8 */
1425 GtkTreeViewColumn * tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self),
1426 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN);
1427 gtk_tree_view_column_queue_resize (tree_column);
1433 modest_folder_view_set_style (ModestFolderView *self,
1434 ModestFolderViewStyle style)
1436 ModestFolderViewPrivate *priv;
1438 g_return_if_fail (self);
1440 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1442 priv->style = style;
1446 modest_folder_view_set_account_id_of_visible_server_account (ModestFolderView *self,
1447 const gchar *account_id)
1449 ModestFolderViewPrivate *priv;
1451 GtkTreeModel *model;
1453 g_return_if_fail (self);
1455 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1457 if (priv->visible_account_id)
1458 g_free (priv->visible_account_id);
1459 priv->visible_account_id = g_strdup (account_id);
1461 /* Save preferences */
1462 conf = modest_runtime_get_conf ();
1463 modest_widget_memory_save (conf, G_OBJECT (self), MODEST_CONF_FOLDER_VIEW_KEY);
1466 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1467 if (GTK_IS_TREE_MODEL_FILTER (model))
1468 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
1472 modest_folder_view_get_account_id_of_visible_server_account (ModestFolderView *self)
1474 ModestFolderViewPrivate *priv;
1476 g_return_val_if_fail (self, NULL);
1478 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1480 return (const gchar *) priv->visible_account_id;