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-folder-store-observer.h>
39 #include <tny-account-store.h>
40 #include <tny-account.h>
41 #include <tny-folder.h>
42 #include <tny-camel-folder.h>
43 #include <tny-simple-list.h>
44 #include <modest-tny-account.h>
45 #include <modest-tny-folder.h>
46 #include <modest-tny-local-folders-account.h>
47 #include <modest-tny-outbox-account.h>
48 #include <modest-marshal.h>
49 #include <modest-icon-names.h>
50 #include <modest-tny-account-store.h>
51 #include <modest-text-utils.h>
52 #include <modest-runtime.h>
53 #include "modest-folder-view.h"
54 #include <modest-dnd.h>
55 #include <modest-platform.h>
56 #include <modest-widget-memory.h>
57 #include <modest-ui-actions.h>
59 /* 'private'/'protected' functions */
60 static void modest_folder_view_class_init (ModestFolderViewClass *klass);
61 static void modest_folder_view_init (ModestFolderView *obj);
62 static void modest_folder_view_finalize (GObject *obj);
64 static void tny_account_store_view_init (gpointer g,
67 static void modest_folder_view_set_account_store (TnyAccountStoreView *self,
68 TnyAccountStore *account_store);
70 static void on_selection_changed (GtkTreeSelection *sel, gpointer data);
72 static void on_account_update (TnyAccountStore *account_store,
76 static void on_account_removed (TnyAccountStore *self,
80 static void on_accounts_reloaded (TnyAccountStore *store,
83 static gint cmp_rows (GtkTreeModel *tree_model,
88 static gboolean filter_row (GtkTreeModel *model,
92 static gboolean on_key_pressed (GtkWidget *self,
96 static void on_configuration_key_changed (ModestConf* conf,
98 ModestConfEvent event,
99 ModestFolderView *self);
102 static void on_drag_data_get (GtkWidget *widget,
103 GdkDragContext *context,
104 GtkSelectionData *selection_data,
109 static void on_drag_data_received (GtkWidget *widget,
110 GdkDragContext *context,
113 GtkSelectionData *selection_data,
118 static gboolean on_drag_motion (GtkWidget *widget,
119 GdkDragContext *context,
125 static gint expand_row_timeout (gpointer data);
127 static void setup_drag_and_drop (GtkTreeView *self);
129 static void _clipboard_set_selected_data (ModestFolderView *folder_view, gboolean delete);
131 static void _clear_hidding_filter (ModestFolderView *folder_view);
136 FOLDER_SELECTION_CHANGED_SIGNAL,
137 FOLDER_DISPLAY_NAME_CHANGED_SIGNAL,
141 typedef struct _ModestFolderViewPrivate ModestFolderViewPrivate;
142 struct _ModestFolderViewPrivate {
143 TnyAccountStore *account_store;
144 TnyFolderStore *cur_folder_store;
146 gulong account_update_signal;
147 gulong changed_signal;
148 gulong accounts_reloaded_signal;
149 gulong account_removed_signal;
150 gulong conf_key_signal;
152 /* not unref this object, its a singlenton */
153 ModestEmailClipboard *clipboard;
155 /* Filter tree model */
159 TnyFolderStoreQuery *query;
160 guint timer_expander;
162 gchar *local_account_name;
163 gchar *visible_account_id;
164 ModestFolderViewStyle style;
166 gboolean reselect; /* we use this to force a reselection of the INBOX */
168 #define MODEST_FOLDER_VIEW_GET_PRIVATE(o) \
169 (G_TYPE_INSTANCE_GET_PRIVATE((o), \
170 MODEST_TYPE_FOLDER_VIEW, \
171 ModestFolderViewPrivate))
173 static GObjectClass *parent_class = NULL;
175 static guint signals[LAST_SIGNAL] = {0};
178 modest_folder_view_get_type (void)
180 static GType my_type = 0;
182 static const GTypeInfo my_info = {
183 sizeof(ModestFolderViewClass),
184 NULL, /* base init */
185 NULL, /* base finalize */
186 (GClassInitFunc) modest_folder_view_class_init,
187 NULL, /* class finalize */
188 NULL, /* class data */
189 sizeof(ModestFolderView),
191 (GInstanceInitFunc) modest_folder_view_init,
195 static const GInterfaceInfo tny_account_store_view_info = {
196 (GInterfaceInitFunc) tny_account_store_view_init, /* interface_init */
197 NULL, /* interface_finalize */
198 NULL /* interface_data */
202 my_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
206 g_type_add_interface_static (my_type,
207 TNY_TYPE_ACCOUNT_STORE_VIEW,
208 &tny_account_store_view_info);
214 modest_folder_view_class_init (ModestFolderViewClass *klass)
216 GObjectClass *gobject_class;
217 gobject_class = (GObjectClass*) klass;
219 parent_class = g_type_class_peek_parent (klass);
220 gobject_class->finalize = modest_folder_view_finalize;
222 g_type_class_add_private (gobject_class,
223 sizeof(ModestFolderViewPrivate));
225 signals[FOLDER_SELECTION_CHANGED_SIGNAL] =
226 g_signal_new ("folder_selection_changed",
227 G_TYPE_FROM_CLASS (gobject_class),
229 G_STRUCT_OFFSET (ModestFolderViewClass,
230 folder_selection_changed),
232 modest_marshal_VOID__POINTER_BOOLEAN,
233 G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_BOOLEAN);
236 * This signal is emitted whenever the currently selected
237 * folder display name is computed. Note that the name could
238 * be different to the folder name, because we could append
239 * the unread messages count to the folder name to build the
240 * folder display name
242 signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL] =
243 g_signal_new ("folder-display-name-changed",
244 G_TYPE_FROM_CLASS (gobject_class),
246 G_STRUCT_OFFSET (ModestFolderViewClass,
247 folder_display_name_changed),
249 g_cclosure_marshal_VOID__STRING,
250 G_TYPE_NONE, 1, G_TYPE_STRING);
253 static gboolean on_model_foreach_set_name(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
255 GObject *instance = NULL;
257 gtk_tree_model_get (model, iter,
258 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
262 return FALSE; /* keep walking */
264 if (!TNY_IS_ACCOUNT (instance)) {
265 g_object_unref (instance);
266 return FALSE; /* keep walking */
269 /* Check if this is the looked-for account: */
270 TnyAccount *this_account = TNY_ACCOUNT (instance);
271 TnyAccount *account = TNY_ACCOUNT (data);
273 const gchar *this_account_id = tny_account_get_id(this_account);
274 const gchar *account_id = tny_account_get_id(account);
275 if (this_account_id && account_id &&
276 (strcmp (this_account_id, account_id) == 0)) {
278 /* Tell the model that the data has changed, so that
279 * it calls the cell_data_func callbacks again: */
280 gtk_tree_model_row_changed (model, path, iter);
282 g_object_unref (instance);
283 return TRUE; /* stop walking */
286 g_object_unref (instance);
287 return FALSE; /* keep walking */
290 void on_get_mmc_account_name (TnyStoreAccount* account, gpointer user_data)
292 ModestFolderView *self = MODEST_FOLDER_VIEW (user_data);
294 /* If this has been called then it means that the account name has
295 * changed, so we tell the model that the data has changed, so that
296 * it calls the cell_data_func callbacks again: */
297 GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
301 gtk_tree_model_foreach(model, on_model_foreach_set_name, self);
305 text_cell_data (GtkTreeViewColumn *column, GtkCellRenderer *renderer,
306 GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
308 ModestFolderViewPrivate *priv;
313 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
314 GObject *instance = NULL;
316 g_return_if_fail (column);
317 g_return_if_fail (tree_model);
319 gtk_tree_model_get (tree_model, iter,
320 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &fname,
321 TNY_GTK_FOLDER_STORE_TREE_MODEL_ALL_COLUMN, &all,
322 TNY_GTK_FOLDER_STORE_TREE_MODEL_UNREAD_COLUMN, &unread,
323 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
324 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
326 rendobj = G_OBJECT(renderer);
336 ModestFolderView *self = MODEST_FOLDER_VIEW (data);
337 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
339 gchar *item_name = NULL;
340 gint item_weight = 400;
342 if (type != TNY_FOLDER_TYPE_ROOT) {
345 if (modest_tny_folder_is_local_folder (TNY_FOLDER (instance))) {
346 type = modest_tny_folder_get_local_folder_type (TNY_FOLDER (instance));
347 if (type != TNY_FOLDER_TYPE_UNKNOWN) {
349 fname = g_strdup(modest_local_folder_info_get_type_display_name (type));
353 /* Select the number to show: the unread or unsent messages */
354 if ((type == TNY_FOLDER_TYPE_DRAFTS) || (type == TNY_FOLDER_TYPE_OUTBOX))
359 /* Use bold font style if there are unread or unset messages */
361 item_name = g_strdup_printf ("%s (%d)", fname, number);
364 item_name = g_strdup (fname);
368 } else if (TNY_IS_ACCOUNT (instance)) {
369 /* If it's a server account */
370 if (modest_tny_account_is_virtual_local_folders (
371 TNY_ACCOUNT (instance))) {
372 item_name = g_strdup (priv->local_account_name);
375 item_name = g_strdup (fname);
381 item_name = g_strdup ("unknown");
383 if (item_name && item_weight) {
384 /* Set the name in the treeview cell: */
385 g_object_set (rendobj,"text", item_name, "weight", item_weight, NULL);
387 /* Notify display name observers */
388 if (G_OBJECT (priv->cur_folder_store) == instance) {
389 g_signal_emit (G_OBJECT(data),
390 signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
398 /* If it is a Memory card account, make sure that we have the correct name: */
399 if (TNY_IS_STORE_ACCOUNT (instance) &&
400 modest_tny_account_is_memory_card_account (TNY_ACCOUNT (instance))) {
401 /* Get the account name asynchronously: */
402 modest_tny_account_get_mmc_account_name (TNY_STORE_ACCOUNT (instance),
403 on_get_mmc_account_name, self);
406 g_object_unref (G_OBJECT (instance));
413 icon_cell_data (GtkTreeViewColumn *column, GtkCellRenderer *renderer,
414 GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
416 GObject *rendobj = NULL, *instance = NULL;
417 GdkPixbuf *pixbuf = NULL;
418 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
420 const gchar *account_id = NULL;
423 rendobj = G_OBJECT(renderer);
424 gtk_tree_model_get (tree_model, iter,
425 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
426 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &fname,
427 TNY_GTK_FOLDER_STORE_TREE_MODEL_UNREAD_COLUMN, &unread,
428 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
439 /* We include the MERGE type here because it's used to create
440 the local OUTBOX folder */
441 if (type == TNY_FOLDER_TYPE_NORMAL ||
442 type == TNY_FOLDER_TYPE_UNKNOWN ||
443 type == TNY_FOLDER_TYPE_MERGE) {
444 type = modest_tny_folder_guess_folder_type (TNY_FOLDER (instance));
448 case TNY_FOLDER_TYPE_ROOT:
449 if (TNY_IS_ACCOUNT (instance)) {
451 if (modest_tny_account_is_virtual_local_folders (
452 TNY_ACCOUNT (instance))) {
453 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_LOCAL_FOLDERS);
456 account_id = tny_account_get_id (TNY_ACCOUNT (instance));
458 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
459 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_MMC);
461 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_ACCOUNT);
465 case TNY_FOLDER_TYPE_INBOX:
466 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_INBOX);
468 case TNY_FOLDER_TYPE_OUTBOX:
469 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_OUTBOX);
471 case TNY_FOLDER_TYPE_JUNK:
472 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_JUNK);
474 case TNY_FOLDER_TYPE_SENT:
475 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_SENT);
477 case TNY_FOLDER_TYPE_TRASH:
478 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_TRASH);
480 case TNY_FOLDER_TYPE_DRAFTS:
481 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_DRAFTS);
483 case TNY_FOLDER_TYPE_NORMAL:
485 pixbuf = modest_platform_get_icon (MODEST_FOLDER_ICON_NORMAL);
489 g_object_unref (G_OBJECT (instance));
493 g_object_set (rendobj, "pixbuf", pixbuf, NULL);
496 g_object_unref (pixbuf);
500 add_columns (GtkWidget *treeview)
502 GtkTreeViewColumn *column;
503 GtkCellRenderer *renderer;
504 GtkTreeSelection *sel;
507 column = gtk_tree_view_column_new ();
509 /* Set icon and text render function */
510 renderer = gtk_cell_renderer_pixbuf_new();
511 gtk_tree_view_column_pack_start (column, renderer, FALSE);
512 gtk_tree_view_column_set_cell_data_func(column, renderer,
513 icon_cell_data, treeview, NULL);
515 renderer = gtk_cell_renderer_text_new();
516 gtk_tree_view_column_pack_start (column, renderer, FALSE);
517 gtk_tree_view_column_set_cell_data_func(column, renderer,
518 text_cell_data, treeview, NULL);
520 /* Set selection mode */
521 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(treeview));
522 gtk_tree_selection_set_mode (sel, GTK_SELECTION_SINGLE);
524 /* Set treeview appearance */
525 gtk_tree_view_column_set_spacing (column, 2);
526 gtk_tree_view_column_set_resizable (column, TRUE);
527 gtk_tree_view_column_set_fixed_width (column, TRUE);
528 gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(treeview), FALSE);
529 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(treeview), FALSE);
532 gtk_tree_view_append_column (GTK_TREE_VIEW(treeview),column);
536 modest_folder_view_init (ModestFolderView *obj)
538 ModestFolderViewPrivate *priv;
541 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
543 priv->timer_expander = 0;
544 priv->account_store = NULL;
546 priv->style = MODEST_FOLDER_VIEW_STYLE_SHOW_ALL;
547 priv->cur_folder_store = NULL;
548 priv->visible_account_id = NULL;
550 /* Initialize the local account name */
551 conf = modest_runtime_get_conf();
552 priv->local_account_name = modest_conf_get_string (conf, MODEST_CONF_DEVICE_NAME, NULL);
554 /* Init email clipboard */
555 priv->clipboard = modest_runtime_get_email_clipboard ();
556 priv->hidding_ids = NULL;
557 priv->n_selected = 0;
558 priv->reselect = FALSE;
561 add_columns (GTK_WIDGET (obj));
563 /* Setup drag and drop */
564 setup_drag_and_drop (GTK_TREE_VIEW(obj));
566 /* Connect signals */
567 g_signal_connect (G_OBJECT (obj),
569 G_CALLBACK (on_key_pressed), NULL);
572 * Track changes in the local account name (in the device it
573 * will be the device name)
575 priv->conf_key_signal =
576 g_signal_connect (G_OBJECT(conf),
578 G_CALLBACK(on_configuration_key_changed), obj);
582 tny_account_store_view_init (gpointer g, gpointer iface_data)
584 TnyAccountStoreViewIface *klass = (TnyAccountStoreViewIface *)g;
586 klass->set_account_store_func = modest_folder_view_set_account_store;
592 modest_folder_view_finalize (GObject *obj)
594 ModestFolderViewPrivate *priv;
595 GtkTreeSelection *sel;
597 g_return_if_fail (obj);
599 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(obj);
601 if (priv->timer_expander != 0) {
602 g_source_remove (priv->timer_expander);
603 priv->timer_expander = 0;
606 if (priv->account_store) {
607 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
608 priv->account_update_signal);
609 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
610 priv->accounts_reloaded_signal);
611 g_signal_handler_disconnect (G_OBJECT(priv->account_store),
612 priv->account_removed_signal);
613 g_object_unref (G_OBJECT(priv->account_store));
614 priv->account_store = NULL;
618 g_object_unref (G_OBJECT (priv->query));
622 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(obj));
624 g_signal_handler_disconnect (G_OBJECT(sel), priv->changed_signal);
626 g_free (priv->local_account_name);
627 g_free (priv->visible_account_id);
629 if (priv->conf_key_signal) {
630 g_signal_handler_disconnect (modest_runtime_get_conf (),
631 priv->conf_key_signal);
632 priv->conf_key_signal = 0;
635 if (priv->cur_folder_store) {
636 if (TNY_IS_FOLDER(priv->cur_folder_store))
637 tny_folder_sync (TNY_FOLDER(priv->cur_folder_store), TRUE, NULL);
638 /* expunge the message */
640 g_object_unref (priv->cur_folder_store);
641 priv->cur_folder_store = NULL;
644 /* Clear hidding array created by cut operation */
645 _clear_hidding_filter (MODEST_FOLDER_VIEW (obj));
647 G_OBJECT_CLASS(parent_class)->finalize (obj);
652 modest_folder_view_set_account_store (TnyAccountStoreView *self, TnyAccountStore *account_store)
654 ModestFolderViewPrivate *priv;
657 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
658 g_return_if_fail (TNY_IS_ACCOUNT_STORE (account_store));
660 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
661 device = tny_account_store_get_device (account_store);
663 if (G_UNLIKELY (priv->account_store)) {
665 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
666 priv->account_update_signal))
667 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
668 priv->account_update_signal);
669 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
670 priv->accounts_reloaded_signal))
671 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
672 priv->accounts_reloaded_signal);
673 if (g_signal_handler_is_connected (G_OBJECT (priv->account_store),
674 priv->account_removed_signal))
675 g_signal_handler_disconnect (G_OBJECT (priv->account_store),
676 priv->account_removed_signal);
678 g_object_unref (G_OBJECT (priv->account_store));
681 priv->account_store = g_object_ref (G_OBJECT (account_store));
683 priv->account_update_signal =
684 g_signal_connect (G_OBJECT(account_store), "account_update",
685 G_CALLBACK (on_account_update), self);
687 priv->account_removed_signal =
688 g_signal_connect (G_OBJECT(account_store), "account_removed",
689 G_CALLBACK (on_account_removed), self);
691 priv->accounts_reloaded_signal =
692 g_signal_connect (G_OBJECT(account_store), "accounts_reloaded",
693 G_CALLBACK (on_accounts_reloaded), self);
695 g_signal_connect (G_OBJECT(account_store), "connecting_finished",
696 G_CALLBACK (on_accounts_reloaded), self);
698 on_accounts_reloaded (account_store, (gpointer ) self);
700 g_object_unref (G_OBJECT (device));
704 on_account_removed (TnyAccountStore *account_store,
708 ModestFolderView *self = MODEST_FOLDER_VIEW (user_data);
709 ModestFolderViewPrivate *priv;
711 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
713 /* If the removed account is the currently viewed one then
714 clear the configuration value. The new visible account will be the default account */
715 if (!strcmp (priv->visible_account_id, tny_account_get_id (account))) {
716 modest_folder_view_set_account_id_of_visible_server_account (self, NULL);
721 on_account_update (TnyAccountStore *account_store,
722 const gchar *account,
725 ModestFolderView *self = MODEST_FOLDER_VIEW (user_data);
726 ModestFolderViewPrivate *priv;
728 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
729 if (!priv->visible_account_id)
730 modest_widget_memory_restore (modest_runtime_get_conf(), G_OBJECT(self),
731 MODEST_CONF_FOLDER_VIEW_KEY);
733 if (!modest_folder_view_update_model (self, account_store))
734 g_printerr ("modest: failed to update model for changes in '%s'",
739 on_accounts_reloaded (TnyAccountStore *account_store,
742 modest_folder_view_update_model (MODEST_FOLDER_VIEW (user_data), account_store);
746 modest_folder_view_set_title (ModestFolderView *self, const gchar *title)
748 GtkTreeViewColumn *col;
750 g_return_if_fail (self);
752 col = gtk_tree_view_get_column (GTK_TREE_VIEW(self), 0);
754 g_printerr ("modest: failed get column for title\n");
758 gtk_tree_view_column_set_title (col, title);
759 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self),
764 modest_folder_view_on_map (ModestFolderView *self,
765 GdkEventExpose *event,
768 ModestFolderViewPrivate *priv;
770 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
772 /* This won't happen often */
773 if (G_UNLIKELY (priv->reselect)) {
774 /* Select the first inbox or the local account if not found */
775 modest_folder_view_select_first_inbox_or_local (self);
776 priv->reselect = FALSE;
782 modest_folder_view_new (TnyFolderStoreQuery *query)
785 ModestFolderViewPrivate *priv;
786 GtkTreeSelection *sel;
788 self = G_OBJECT (g_object_new (MODEST_TYPE_FOLDER_VIEW, NULL));
789 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
792 priv->query = g_object_ref (query);
794 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
795 priv->changed_signal = g_signal_connect (sel, "changed",
796 G_CALLBACK (on_selection_changed), self);
798 g_signal_connect (self, "expose-event", G_CALLBACK (modest_folder_view_on_map), NULL);
800 return GTK_WIDGET(self);
803 /* this feels dirty; any other way to expand all the root items? */
805 expand_root_items (ModestFolderView *self)
808 path = gtk_tree_path_new_first ();
810 /* all folders should have child items, so.. */
811 while (gtk_tree_view_expand_row (GTK_TREE_VIEW(self), path, FALSE))
812 gtk_tree_path_next (path);
814 gtk_tree_path_free (path);
818 * We use this function to implement the
819 * MODEST_FOLDER_VIEW_STYLE_SHOW_ONE style. We only show the default
820 * account in this case, and the local folders.
823 filter_row (GtkTreeModel *model,
827 ModestFolderViewPrivate *priv;
828 gboolean retval = TRUE;
829 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
830 GObject *instance = NULL;
831 const gchar *id = NULL;
833 gboolean found = FALSE;
834 gboolean cleared = FALSE;
836 g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (data), FALSE);
837 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (data);
839 gtk_tree_model_get (model, iter,
840 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
841 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
844 /* Do not show if there is no instance, this could indeed
845 happen when the model is being modified while it's being
846 drawn. This could occur for example when moving folders
851 if (type == TNY_FOLDER_TYPE_ROOT) {
852 /* TNY_FOLDER_TYPE_ROOT means that the instance is an
853 account instead of a folder. */
854 if (TNY_IS_ACCOUNT (instance)) {
855 TnyAccount *acc = TNY_ACCOUNT (instance);
856 const gchar *account_id = tny_account_get_id (acc);
858 /* If it isn't a special folder,
859 * don't show it unless it is the visible account: */
860 if (!modest_tny_account_is_virtual_local_folders (acc) &&
861 strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
862 /* Show only the visible account id */
863 if (priv->visible_account_id && strcmp (account_id, priv->visible_account_id))
867 /* Never show these to the user. They are merged into one folder
868 * in the local-folders account instead: */
869 if (retval && MODEST_IS_TNY_OUTBOX_ACCOUNT (acc))
874 /* The virtual local-folders folder store is also shown by default. */
876 /* Check hiding (if necessary) */
877 cleared = modest_email_clipboard_cleared (priv->clipboard);
878 if ((retval) && (!cleared) && (TNY_IS_FOLDER (instance))) {
879 id = tny_folder_get_id (TNY_FOLDER(instance));
880 if (priv->hidding_ids != NULL)
881 for (i=0; i < priv->n_selected && !found; i++)
882 if (priv->hidding_ids[i] != NULL && id != NULL)
883 found = (!strcmp (priv->hidding_ids[i], id));
889 g_object_unref (instance);
895 modest_folder_view_update_model (ModestFolderView *self,
896 TnyAccountStore *account_store)
898 ModestFolderViewPrivate *priv;
899 GtkTreeModel *model /* , *old_model */;
900 /* TnyAccount *local_account; */
901 TnyList *model_as_list;
903 g_return_val_if_fail (account_store, FALSE);
905 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
907 /* Notify that there is no folder selected */
908 g_signal_emit (G_OBJECT(self),
909 signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
911 if (priv->cur_folder_store) {
912 g_object_unref (priv->cur_folder_store);
913 priv->cur_folder_store = NULL;
916 /* FIXME: the local accounts are not shown when the query
917 selects only the subscribed folders. */
918 /* model = tny_gtk_folder_store_tree_model_new (TRUE, priv->query); */
919 model = tny_gtk_folder_store_tree_model_new (TRUE, NULL);
921 /* Deal with the model via its TnyList Interface,
922 * filling the TnyList via a get_accounts() call: */
923 model_as_list = TNY_LIST(model);
925 /* Get the accounts: */
926 tny_account_store_get_accounts (TNY_ACCOUNT_STORE(account_store),
928 TNY_ACCOUNT_STORE_STORE_ACCOUNTS);
929 g_object_unref (model_as_list);
930 model_as_list = NULL;
932 GtkTreeModel *filter_model = NULL, *sortable = NULL;
934 sortable = gtk_tree_model_sort_new_with_model (model);
935 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
936 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
938 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
939 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
940 cmp_rows, NULL, NULL);
942 /* Create filter model */
943 filter_model = gtk_tree_model_filter_new (sortable, NULL);
944 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
948 /* if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE) { */
949 /* filter_model = gtk_tree_model_filter_new (sortable, NULL); */
950 /* gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model), */
957 gtk_tree_view_set_model (GTK_TREE_VIEW(self), filter_model);
958 /* gtk_tree_view_set_model (GTK_TREE_VIEW(self), */
959 /* (filter_model) ? filter_model : sortable); */
961 g_object_unref (model);
962 g_object_unref (filter_model);
963 /* if (filter_model) */
964 /* g_object_unref (filter_model); */
966 g_object_unref (sortable);
968 /* Force a reselection of the INBOX next time the widget is shown */
969 priv->reselect = TRUE;
976 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
979 TnyFolderStore *folder = NULL;
981 ModestFolderView *tree_view;
982 ModestFolderViewPrivate *priv;
984 g_return_if_fail (sel);
985 g_return_if_fail (user_data);
987 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
989 if(!gtk_tree_selection_get_selected (sel, &model, &iter))
992 /* Notify the display name observers */
993 g_signal_emit (G_OBJECT(user_data),
994 signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
997 tree_view = MODEST_FOLDER_VIEW (user_data);
998 gtk_tree_model_get (model, &iter,
999 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
1002 /* If the folder is the same do not notify */
1003 if (priv->cur_folder_store == folder && folder) {
1004 g_object_unref (folder);
1008 /* Current folder was unselected */
1009 if (priv->cur_folder_store) {
1010 if (TNY_IS_FOLDER(priv->cur_folder_store))
1011 tny_folder_sync (TNY_FOLDER(priv->cur_folder_store), TRUE, NULL);
1012 /* expunge the message */
1014 g_signal_emit (G_OBJECT(tree_view), signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
1015 priv->cur_folder_store, FALSE);
1016 g_object_unref (priv->cur_folder_store);
1017 priv->cur_folder_store = NULL;
1020 /* New current references */
1021 priv->cur_folder_store = folder;
1023 /* New folder has been selected */
1024 g_signal_emit (G_OBJECT(tree_view),
1025 signals[FOLDER_SELECTION_CHANGED_SIGNAL],
1026 0, priv->cur_folder_store, TRUE);
1030 modest_folder_view_get_selected (ModestFolderView *self)
1032 ModestFolderViewPrivate *priv;
1034 g_return_val_if_fail (self, NULL);
1036 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1037 if (priv->cur_folder_store)
1038 g_object_ref (priv->cur_folder_store);
1040 return priv->cur_folder_store;
1044 get_cmp_rows_type_pos (GObject *folder)
1046 /* Remote accounts -> Local account -> MMC account .*/
1049 if (TNY_IS_ACCOUNT (folder) &&
1050 modest_tny_account_is_virtual_local_folders (
1051 TNY_ACCOUNT (folder))) {
1053 } else if (TNY_IS_ACCOUNT (folder)) {
1054 TnyAccount *account = TNY_ACCOUNT (folder);
1055 const gchar *account_id = tny_account_get_id (account);
1056 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
1062 printf ("DEBUG: %s: unexpected type.\n", __FUNCTION__);
1063 return -1; /* Should never happen */
1068 get_cmp_subfolder_type_pos (TnyFolderType t)
1070 /* Outbox, Drafts, Sent, User */
1074 case TNY_FOLDER_TYPE_OUTBOX:
1077 case TNY_FOLDER_TYPE_DRAFTS:
1080 case TNY_FOLDER_TYPE_SENT:
1089 * This function orders the mail accounts according to these rules:
1090 * 1st - remote accounts
1091 * 2nd - local account
1095 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1099 gchar *name1 = NULL;
1100 gchar *name2 = NULL;
1101 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1102 TnyFolderType type2 = TNY_FOLDER_TYPE_UNKNOWN;
1103 GObject *folder1 = NULL;
1104 GObject *folder2 = NULL;
1106 gtk_tree_model_get (tree_model, iter1,
1107 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name1,
1108 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
1109 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder1,
1111 gtk_tree_model_get (tree_model, iter2,
1112 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name2,
1113 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type2,
1114 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder2,
1117 if (type == TNY_FOLDER_TYPE_ROOT) {
1118 /* Compare the types, so that
1119 * Remote accounts -> Local account -> MMC account .*/
1120 const gint pos1 = get_cmp_rows_type_pos (folder1);
1121 const gint pos2 = get_cmp_rows_type_pos (folder2);
1122 /* printf ("DEBUG: %s:\n type1=%s, pos1=%d\n type2=%s, pos2=%d\n",
1123 __FUNCTION__, G_OBJECT_TYPE_NAME(folder1), pos1, G_OBJECT_TYPE_NAME(folder2), pos2); */
1126 else if (pos1 > pos2)
1129 /* Compare items of the same type: */
1131 TnyAccount *account1 = NULL;
1132 if (TNY_IS_ACCOUNT (folder1))
1133 account1 = TNY_ACCOUNT (folder1);
1135 TnyAccount *account2 = NULL;
1136 if (TNY_IS_ACCOUNT (folder2))
1137 account2 = TNY_ACCOUNT (folder2);
1139 const gchar *account_id = account1 ? tny_account_get_id (account1) : NULL;
1140 const gchar *account_id2 = account2 ? tny_account_get_id (account2) : NULL;
1142 if (!account_id && !account_id2)
1144 else if (!account_id)
1146 else if (!account_id2)
1148 else if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
1151 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
1155 gboolean has_parent;
1156 gint cmp1 = 0, cmp2 = 0;
1157 /* get the parent to know if it's a local folder */
1158 has_parent = gtk_tree_model_iter_parent (tree_model, &parent, iter1);
1160 GObject *parent_folder;
1161 TnyFolderType parent_type = TNY_FOLDER_TYPE_UNKNOWN;
1162 gtk_tree_model_get (tree_model, &parent,
1163 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &parent_type,
1164 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &parent_folder,
1166 if ((parent_type == TNY_FOLDER_TYPE_ROOT) &&
1167 TNY_IS_ACCOUNT (parent_folder) &&
1168 modest_tny_account_is_virtual_local_folders (TNY_ACCOUNT (parent_folder))) {
1169 cmp1 = get_cmp_subfolder_type_pos (modest_tny_folder_get_local_folder_type (TNY_FOLDER (folder1)));
1170 cmp2 = get_cmp_subfolder_type_pos (modest_tny_folder_get_local_folder_type (TNY_FOLDER (folder2)));
1172 g_object_unref (parent_folder);
1175 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
1177 cmp = (cmp1 - cmp2);
1181 g_object_unref(G_OBJECT(folder1));
1183 g_object_unref(G_OBJECT(folder2));
1191 /*****************************************************************************/
1192 /* DRAG and DROP stuff */
1193 /*****************************************************************************/
1196 * This function fills the #GtkSelectionData with the row and the
1197 * model that has been dragged. It's called when this widget is a
1198 * source for dnd after the event drop happened
1201 on_drag_data_get (GtkWidget *widget,
1202 GdkDragContext *context,
1203 GtkSelectionData *selection_data,
1208 GtkTreeSelection *selection;
1209 GtkTreeModel *model;
1211 GtkTreePath *source_row;
1213 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1214 gtk_tree_selection_get_selected (selection, &model, &iter);
1215 source_row = gtk_tree_model_get_path (model, &iter);
1217 gtk_tree_set_row_drag_data (selection_data,
1221 gtk_tree_path_free (source_row);
1224 typedef struct _DndHelper {
1225 gboolean delete_source;
1226 GtkTreePath *source_row;
1227 GdkDragContext *context;
1233 * This function is the callback of the
1234 * modest_mail_operation_xfer_msgs () and
1235 * modest_mail_operation_xfer_folder() calls. We check here if the
1236 * message/folder was correctly asynchronously transferred. The reason
1237 * to use the same callback is that the code is the same, it only has
1238 * to check that the operation went fine and then finalize the drag
1242 on_progress_changed (ModestMailOperation *mail_op,
1243 ModestMailOperationState *state,
1249 helper = (DndHelper *) user_data;
1251 if (!state->finished)
1254 if (state->status == MODEST_MAIL_OPERATION_STATUS_SUCCESS) {
1260 /* Notify the drag source. Never call delete, the monitor will
1261 do the job if needed */
1262 gtk_drag_finish (helper->context, success, FALSE, helper->time);
1264 /* Free the helper */
1265 gtk_tree_path_free (helper->source_row);
1266 g_slice_free (DndHelper, helper);
1270 * This function is used by drag_data_received_cb to manage drag and
1271 * drop of a header, i.e, and drag from the header view to the folder
1275 drag_and_drop_from_header_view (GtkTreeModel *source_model,
1276 GtkTreeModel *dest_model,
1277 GtkTreePath *dest_row,
1280 TnyList *headers = NULL;
1281 TnyHeader *header = NULL;
1282 TnyFolder *folder = NULL;
1283 ModestMailOperation *mail_op = NULL;
1284 GtkTreeIter source_iter, dest_iter;
1286 g_return_if_fail (GTK_IS_TREE_MODEL(source_model));
1287 g_return_if_fail (GTK_IS_TREE_MODEL(dest_model));
1288 g_return_if_fail (dest_row);
1289 g_return_if_fail (helper);
1292 gtk_tree_model_get_iter (source_model, &source_iter, helper->source_row);
1293 gtk_tree_model_get (source_model, &source_iter,
1294 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1296 if (!TNY_IS_HEADER(header)) {
1297 g_warning ("BUG: %s could not get a valid header", __FUNCTION__);
1302 gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
1303 gtk_tree_model_get (dest_model, &dest_iter,
1304 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
1307 if (!TNY_IS_FOLDER(folder)) {
1308 g_warning ("BUG: %s could not get a valid folder", __FUNCTION__);
1312 /* Transfer message */
1313 mail_op = modest_mail_operation_new (MODEST_MAIL_OPERATION_TYPE_RECEIVE, NULL);
1314 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1316 g_signal_connect (G_OBJECT (mail_op), "progress-changed",
1317 G_CALLBACK (on_progress_changed), helper);
1319 headers = tny_simple_list_new ();
1320 tny_list_append (headers, G_OBJECT (header));
1321 modest_mail_operation_xfer_msgs (mail_op,
1324 helper->delete_source,
1329 if (G_IS_OBJECT(mail_op))
1330 g_object_unref (G_OBJECT (mail_op));
1331 if (G_IS_OBJECT(header))
1332 g_object_unref (G_OBJECT (header));
1333 if (G_IS_OBJECT(folder))
1334 g_object_unref (G_OBJECT (folder));
1335 if (G_IS_OBJECT(headers))
1336 g_object_unref (headers);
1340 * This function is used by drag_data_received_cb to manage drag and
1341 * drop of a folder, i.e, and drag from the folder view to the same
1345 drag_and_drop_from_folder_view (GtkTreeModel *source_model,
1346 GtkTreeModel *dest_model,
1347 GtkTreePath *dest_row,
1348 GtkSelectionData *selection_data,
1351 ModestMailOperation *mail_op;
1352 GtkTreeIter parent_iter, iter;
1353 TnyFolderStore *parent_folder;
1356 /* Check if the drag is possible */
1357 /* if (!gtk_tree_path_compare (helper->source_row, dest_row) || */
1358 /* !gtk_tree_drag_dest_row_drop_possible (GTK_TREE_DRAG_DEST (dest_model), */
1360 /* selection_data)) { */
1361 if (!gtk_tree_path_compare (helper->source_row, dest_row)) {
1363 gtk_drag_finish (helper->context, FALSE, FALSE, helper->time);
1364 gtk_tree_path_free (helper->source_row);
1365 g_slice_free (DndHelper, helper);
1370 gtk_tree_model_get_iter (source_model, &parent_iter, dest_row);
1371 gtk_tree_model_get_iter (source_model, &iter, helper->source_row);
1372 gtk_tree_model_get (source_model, &parent_iter,
1373 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
1374 &parent_folder, -1);
1375 gtk_tree_model_get (source_model, &iter,
1376 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
1379 /* Do the mail operation */
1380 mail_op = modest_mail_operation_new_with_error_handling (MODEST_MAIL_OPERATION_TYPE_RECEIVE,
1382 modest_ui_actions_move_folder_error_handler,
1384 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1386 g_signal_connect (G_OBJECT (mail_op), "progress-changed",
1387 G_CALLBACK (on_progress_changed), helper);
1389 modest_mail_operation_xfer_folder (mail_op,
1392 helper->delete_source);
1395 g_object_unref (G_OBJECT (parent_folder));
1396 g_object_unref (G_OBJECT (folder));
1397 g_object_unref (G_OBJECT (mail_op));
1401 * This function receives the data set by the "drag-data-get" signal
1402 * handler. This information comes within the #GtkSelectionData. This
1403 * function will manage both the drags of folders of the treeview and
1404 * drags of headers of the header view widget.
1407 on_drag_data_received (GtkWidget *widget,
1408 GdkDragContext *context,
1411 GtkSelectionData *selection_data,
1416 GtkWidget *source_widget;
1417 GtkTreeModel *dest_model, *source_model;
1418 GtkTreePath *source_row, *dest_row;
1419 GtkTreeViewDropPosition pos;
1420 gboolean success = FALSE, delete_source = FALSE;
1421 DndHelper *helper = NULL;
1423 /* Do not allow further process */
1424 g_signal_stop_emission_by_name (widget, "drag-data-received");
1425 source_widget = gtk_drag_get_source_widget (context);
1427 /* Get the action */
1428 if (context->action == GDK_ACTION_MOVE) {
1429 delete_source = TRUE;
1431 /* Notify that there is no folder selected. We need to
1432 do this in order to update the headers view (and
1433 its monitors, because when moving, the old folder
1434 won't longer exist. We can not wait for the end of
1435 the operation, because the operation won't start if
1436 the folder is in use */
1437 if (source_widget == widget) {
1438 ModestFolderViewPrivate *priv;
1440 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
1441 if (priv->cur_folder_store) {
1442 g_object_unref (priv->cur_folder_store);
1443 priv->cur_folder_store = NULL;
1446 g_signal_emit (G_OBJECT (widget),
1447 signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0, NULL, FALSE);
1451 /* Check if the get_data failed */
1452 if (selection_data == NULL || selection_data->length < 0)
1453 gtk_drag_finish (context, success, FALSE, time);
1455 /* Get the models */
1456 gtk_tree_get_row_drag_data (selection_data,
1460 /* Select the destination model */
1461 if (source_widget == widget) {
1462 dest_model = source_model;
1464 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
1467 /* Get the path to the destination row. Can not call
1468 gtk_tree_view_get_drag_dest_row() because the source row
1469 is not selected anymore */
1470 gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget), x, y,
1473 /* Only allow drops IN other rows */
1474 if (!dest_row || pos == GTK_TREE_VIEW_DROP_BEFORE || pos == GTK_TREE_VIEW_DROP_AFTER)
1475 gtk_drag_finish (context, success, FALSE, time);
1477 /* Create the helper */
1478 helper = g_slice_new0 (DndHelper);
1479 helper->delete_source = delete_source;
1480 helper->source_row = gtk_tree_path_copy (source_row);
1481 helper->context = context;
1482 helper->time = time;
1484 /* Drags from the header view */
1485 if (source_widget != widget) {
1487 drag_and_drop_from_header_view (source_model,
1494 drag_and_drop_from_folder_view (source_model,
1502 gtk_tree_path_free (source_row);
1503 gtk_tree_path_free (dest_row);
1507 * We define a "drag-drop" signal handler because we do not want to
1508 * use the default one, because the default one always calls
1509 * gtk_drag_finish and we prefer to do it in the "drag-data-received"
1510 * signal handler, because there we have all the information available
1511 * to know if the dnd was a success or not.
1514 drag_drop_cb (GtkWidget *widget,
1515 GdkDragContext *context,
1523 if (!context->targets)
1526 /* Check if we're dragging a folder row */
1527 target = gtk_drag_dest_find_target (widget, context, NULL);
1529 /* Request the data from the source. */
1530 gtk_drag_get_data(widget, context, target, time);
1536 * This function expands a node of a tree view if it's not expanded
1537 * yet. Not sure why it needs the threads stuff, but gtk+`example code
1538 * does that, so that's why they're here.
1541 expand_row_timeout (gpointer data)
1543 GtkTreeView *tree_view = data;
1544 GtkTreePath *dest_path = NULL;
1545 GtkTreeViewDropPosition pos;
1546 gboolean result = FALSE;
1548 GDK_THREADS_ENTER ();
1550 gtk_tree_view_get_drag_dest_row (tree_view,
1555 (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER ||
1556 pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)) {
1557 gtk_tree_view_expand_row (tree_view, dest_path, FALSE);
1558 gtk_tree_path_free (dest_path);
1562 gtk_tree_path_free (dest_path);
1567 GDK_THREADS_LEAVE ();
1573 * This function is called whenever the pointer is moved over a widget
1574 * while dragging some data. It installs a timeout that will expand a
1575 * node of the treeview if not expanded yet. This function also calls
1576 * gdk_drag_status in order to set the suggested action that will be
1577 * used by the "drag-data-received" signal handler to know if we
1578 * should do a move or just a copy of the data.
1581 on_drag_motion (GtkWidget *widget,
1582 GdkDragContext *context,
1588 GtkTreeViewDropPosition pos;
1589 GtkTreePath *dest_row;
1590 ModestFolderViewPrivate *priv;
1591 GdkDragAction suggested_action;
1592 gboolean valid_location = FALSE;
1594 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
1596 if (priv->timer_expander != 0) {
1597 g_source_remove (priv->timer_expander);
1598 priv->timer_expander = 0;
1601 gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
1606 /* Do not allow drops between folders */
1608 pos == GTK_TREE_VIEW_DROP_BEFORE ||
1609 pos == GTK_TREE_VIEW_DROP_AFTER) {
1610 gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW (widget), NULL, 0);
1611 gdk_drag_status(context, 0, time);
1612 valid_location = FALSE;
1615 valid_location = TRUE;
1618 /* Expand the selected row after 1/2 second */
1619 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), dest_row)) {
1620 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), dest_row, pos);
1621 priv->timer_expander = g_timeout_add (500, expand_row_timeout, widget);
1624 /* Select the desired action. By default we pick MOVE */
1625 suggested_action = GDK_ACTION_MOVE;
1627 if (context->actions == GDK_ACTION_COPY)
1628 gdk_drag_status(context, GDK_ACTION_COPY, time);
1629 else if (context->actions == GDK_ACTION_MOVE)
1630 gdk_drag_status(context, GDK_ACTION_MOVE, time);
1631 else if (context->actions & suggested_action)
1632 gdk_drag_status(context, suggested_action, time);
1634 gdk_drag_status(context, GDK_ACTION_DEFAULT, time);
1638 gtk_tree_path_free (dest_row);
1639 g_signal_stop_emission_by_name (widget, "drag-motion");
1640 return valid_location;
1644 /* Folder view drag types */
1645 const GtkTargetEntry folder_view_drag_types[] =
1647 { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, MODEST_FOLDER_ROW },
1648 { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_APP, MODEST_HEADER_ROW }
1652 * This function sets the treeview as a source and a target for dnd
1653 * events. It also connects all the requirede signals.
1656 setup_drag_and_drop (GtkTreeView *self)
1658 /* Set up the folder view as a dnd destination. Set only the
1659 highlight flag, otherwise gtk will have a different
1661 gtk_drag_dest_set (GTK_WIDGET (self),
1662 GTK_DEST_DEFAULT_HIGHLIGHT,
1663 folder_view_drag_types,
1664 G_N_ELEMENTS (folder_view_drag_types),
1665 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1667 g_signal_connect (G_OBJECT (self),
1668 "drag_data_received",
1669 G_CALLBACK (on_drag_data_received),
1673 /* Set up the treeview as a dnd source */
1674 gtk_drag_source_set (GTK_WIDGET (self),
1676 folder_view_drag_types,
1677 G_N_ELEMENTS (folder_view_drag_types),
1678 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1680 g_signal_connect (G_OBJECT (self),
1682 G_CALLBACK (on_drag_motion),
1685 g_signal_connect (G_OBJECT (self),
1687 G_CALLBACK (on_drag_data_get),
1690 g_signal_connect (G_OBJECT (self),
1692 G_CALLBACK (drag_drop_cb),
1697 * This function manages the navigation through the folders using the
1698 * keyboard or the hardware keys in the device
1701 on_key_pressed (GtkWidget *self,
1705 GtkTreeSelection *selection;
1707 GtkTreeModel *model;
1708 gboolean retval = FALSE;
1710 /* Up and Down are automatically managed by the treeview */
1711 if (event->keyval == GDK_Return) {
1712 /* Expand/Collapse the selected row */
1713 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1714 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
1717 path = gtk_tree_model_get_path (model, &iter);
1719 if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (self), path))
1720 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1722 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1723 gtk_tree_path_free (path);
1725 /* No further processing */
1733 * We listen to the changes in the local folder account name key,
1734 * because we want to show the right name in the view. The local
1735 * folder account name corresponds to the device name in the Maemo
1736 * version. We do this because we do not want to query gconf on each
1737 * tree view refresh. It's better to cache it and change whenever
1741 on_configuration_key_changed (ModestConf* conf,
1743 ModestConfEvent event,
1744 ModestFolderView *self)
1746 ModestFolderViewPrivate *priv;
1751 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
1752 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1754 if (!strcmp (key, MODEST_CONF_DEVICE_NAME)) {
1755 g_free (priv->local_account_name);
1757 if (event == MODEST_CONF_EVENT_KEY_UNSET)
1758 priv->local_account_name = g_strdup (MODEST_LOCAL_FOLDERS_DEFAULT_DISPLAY_NAME);
1760 priv->local_account_name = modest_conf_get_string (modest_runtime_get_conf(),
1761 MODEST_CONF_DEVICE_NAME, NULL);
1763 /* Force a redraw */
1764 #if GTK_CHECK_VERSION(2, 8, 0) /* gtk_tree_view_column_queue_resize is only available in GTK+ 2.8 */
1765 GtkTreeViewColumn * tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self),
1766 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN);
1767 gtk_tree_view_column_queue_resize (tree_column);
1773 modest_folder_view_set_style (ModestFolderView *self,
1774 ModestFolderViewStyle style)
1776 ModestFolderViewPrivate *priv;
1778 g_return_if_fail (self);
1780 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1782 priv->style = style;
1786 modest_folder_view_set_account_id_of_visible_server_account (ModestFolderView *self,
1787 const gchar *account_id)
1789 ModestFolderViewPrivate *priv;
1790 GtkTreeModel *model;
1792 g_return_if_fail (self);
1794 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1796 /* This will be used by the filter_row callback,
1797 * to decided which rows to show: */
1798 if (priv->visible_account_id) {
1799 g_free (priv->visible_account_id);
1800 priv->visible_account_id = NULL;
1803 priv->visible_account_id = g_strdup (account_id);
1806 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1807 if (GTK_IS_TREE_MODEL_FILTER (model))
1808 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
1810 /* Save settings to gconf */
1811 modest_widget_memory_save (modest_runtime_get_conf (), G_OBJECT(self),
1812 MODEST_CONF_FOLDER_VIEW_KEY);
1816 modest_folder_view_get_account_id_of_visible_server_account (ModestFolderView *self)
1818 ModestFolderViewPrivate *priv;
1820 g_return_val_if_fail (self, NULL);
1822 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1824 return (const gchar *) priv->visible_account_id;
1828 find_inbox_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *inbox_iter)
1832 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1835 gtk_tree_model_get (model, iter,
1836 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name,
1837 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN,
1841 printf ("DEBUG: %s: name=%s, type=%d, TNY_FOLDER_TYPE_INBOX=%d\n",
1842 __FUNCTION__, name, type, TNY_FOLDER_TYPE_INBOX);
1845 gboolean result = FALSE;
1846 if (type == TNY_FOLDER_TYPE_INBOX) {
1848 } else if (type == TNY_FOLDER_TYPE_NORMAL) {
1849 /* tinymail's camel implementation only provides TNY_FOLDER_TYPE_NORMAL
1850 * when getting folders from the cache, before connectin, so we do
1851 * an extra check. We could fix this in tinymail, but it's easier
1854 if (strcmp (name, "Inbox") == 0)
1861 *inbox_iter = *iter;
1865 if (gtk_tree_model_iter_children (model, &child, iter)) {
1866 if (find_inbox_iter (model, &child, inbox_iter))
1870 } while (gtk_tree_model_iter_next (model, iter));
1876 modest_folder_view_select_first_inbox_or_local (ModestFolderView *self)
1878 GtkTreeModel *model;
1879 GtkTreeIter iter, inbox_iter;
1880 GtkTreeSelection *sel;
1882 /* /\* Do not set it if the folder view was not painted *\/ */
1883 /* if (!GTK_WIDGET_MAPPED (self)) */
1886 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1890 expand_root_items (self);
1891 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1893 gtk_tree_model_get_iter_first (model, &iter);
1894 if (find_inbox_iter (model, &iter, &inbox_iter)) {
1895 gtk_tree_selection_select_iter (sel, &inbox_iter);
1898 gtk_tree_model_get_iter_first (model, &iter);
1899 gtk_tree_selection_select_iter (sel, &iter);
1904 modest_folder_view_copy_selection (ModestFolderView *folder_view)
1906 /* Copy selection */
1907 _clipboard_set_selected_data (folder_view, FALSE);
1911 modest_folder_view_cut_selection (ModestFolderView *folder_view)
1913 ModestFolderViewPrivate *priv = NULL;
1914 GtkTreeModel *model = NULL;
1915 const gchar **hidding = NULL;
1916 guint i, n_selected;
1918 g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view));
1919 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
1921 /* Copy selection */
1922 _clipboard_set_selected_data (folder_view, TRUE);
1924 /* Get hidding ids */
1925 hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected);
1927 /* Clear hidding array created by previous cut operation */
1928 _clear_hidding_filter (MODEST_FOLDER_VIEW (folder_view));
1930 /* Copy hidding array */
1931 priv->n_selected = n_selected;
1932 priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
1933 for (i=0; i < n_selected; i++)
1934 priv->hidding_ids[i] = g_strdup(hidding[i]);
1936 /* Hide cut folders */
1937 model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
1938 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
1942 _clipboard_set_selected_data (ModestFolderView *folder_view,
1945 ModestFolderViewPrivate *priv = NULL;
1946 TnyFolderStore *folder = NULL;
1948 g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view));
1949 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
1951 /* Set selected data on clipboard */
1952 g_return_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard));
1953 folder = modest_folder_view_get_selected (folder_view);
1954 modest_email_clipboard_set_data (priv->clipboard, TNY_FOLDER(folder), NULL, delete);
1957 g_object_unref (folder);
1961 _clear_hidding_filter (ModestFolderView *folder_view)
1963 ModestFolderViewPrivate *priv;
1966 g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view));
1967 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
1969 if (priv->hidding_ids != NULL) {
1970 for (i=0; i < priv->n_selected; i++)
1971 g_free (priv->hidding_ids[i]);
1972 g_free(priv->hidding_ids);