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 */
776 /* TODO: this could cause a lock at startup, so we
777 comment it for the moment. We know that this will
778 be a bug, because the INBOX is not selected, but we
779 need to rewrite some parts of Modest to avoid the
780 deathlock situation */
781 /* modest_folder_view_select_first_inbox_or_local (self); */
782 priv->reselect = FALSE;
788 modest_folder_view_new (TnyFolderStoreQuery *query)
791 ModestFolderViewPrivate *priv;
792 GtkTreeSelection *sel;
794 self = G_OBJECT (g_object_new (MODEST_TYPE_FOLDER_VIEW, NULL));
795 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (self);
798 priv->query = g_object_ref (query);
800 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
801 priv->changed_signal = g_signal_connect (sel, "changed",
802 G_CALLBACK (on_selection_changed), self);
804 g_signal_connect (self, "expose-event", G_CALLBACK (modest_folder_view_on_map), NULL);
806 return GTK_WIDGET(self);
809 /* this feels dirty; any other way to expand all the root items? */
811 expand_root_items (ModestFolderView *self)
814 path = gtk_tree_path_new_first ();
816 /* all folders should have child items, so.. */
817 while (gtk_tree_view_expand_row (GTK_TREE_VIEW(self), path, FALSE))
818 gtk_tree_path_next (path);
820 gtk_tree_path_free (path);
824 * We use this function to implement the
825 * MODEST_FOLDER_VIEW_STYLE_SHOW_ONE style. We only show the default
826 * account in this case, and the local folders.
829 filter_row (GtkTreeModel *model,
833 ModestFolderViewPrivate *priv;
834 gboolean retval = TRUE;
835 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
836 GObject *instance = NULL;
837 const gchar *id = NULL;
839 gboolean found = FALSE;
840 gboolean cleared = FALSE;
842 g_return_val_if_fail (MODEST_IS_FOLDER_VIEW (data), FALSE);
843 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (data);
845 gtk_tree_model_get (model, iter,
846 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
847 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &instance,
850 /* Do not show if there is no instance, this could indeed
851 happen when the model is being modified while it's being
852 drawn. This could occur for example when moving folders
857 if (type == TNY_FOLDER_TYPE_ROOT) {
858 /* TNY_FOLDER_TYPE_ROOT means that the instance is an
859 account instead of a folder. */
860 if (TNY_IS_ACCOUNT (instance)) {
861 TnyAccount *acc = TNY_ACCOUNT (instance);
862 const gchar *account_id = tny_account_get_id (acc);
864 /* If it isn't a special folder,
865 * don't show it unless it is the visible account: */
866 if (!modest_tny_account_is_virtual_local_folders (acc) &&
867 strcmp (account_id, MODEST_MMC_ACCOUNT_ID)) {
868 /* Show only the visible account id */
869 if (priv->visible_account_id && strcmp (account_id, priv->visible_account_id))
873 /* Never show these to the user. They are merged into one folder
874 * in the local-folders account instead: */
875 if (retval && MODEST_IS_TNY_OUTBOX_ACCOUNT (acc))
880 /* The virtual local-folders folder store is also shown by default. */
882 /* Check hiding (if necessary) */
883 cleared = modest_email_clipboard_cleared (priv->clipboard);
884 if ((retval) && (!cleared) && (TNY_IS_FOLDER (instance))) {
885 id = tny_folder_get_id (TNY_FOLDER(instance));
886 if (priv->hidding_ids != NULL)
887 for (i=0; i < priv->n_selected && !found; i++)
888 if (priv->hidding_ids[i] != NULL && id != NULL)
889 found = (!strcmp (priv->hidding_ids[i], id));
895 g_object_unref (instance);
901 modest_folder_view_update_model (ModestFolderView *self,
902 TnyAccountStore *account_store)
904 ModestFolderViewPrivate *priv;
905 GtkTreeModel *model /* , *old_model */;
906 /* TnyAccount *local_account; */
907 TnyList *model_as_list;
909 g_return_val_if_fail (account_store, FALSE);
911 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
913 /* Notify that there is no folder selected */
914 g_signal_emit (G_OBJECT(self),
915 signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
917 if (priv->cur_folder_store) {
918 g_object_unref (priv->cur_folder_store);
919 priv->cur_folder_store = NULL;
922 /* FIXME: the local accounts are not shown when the query
923 selects only the subscribed folders. */
924 /* model = tny_gtk_folder_store_tree_model_new (TRUE, priv->query); */
925 model = tny_gtk_folder_store_tree_model_new (TRUE, NULL);
927 /* Deal with the model via its TnyList Interface,
928 * filling the TnyList via a get_accounts() call: */
929 model_as_list = TNY_LIST(model);
931 /* Get the accounts: */
932 tny_account_store_get_accounts (TNY_ACCOUNT_STORE(account_store),
934 TNY_ACCOUNT_STORE_STORE_ACCOUNTS);
935 g_object_unref (model_as_list);
936 model_as_list = NULL;
938 GtkTreeModel *filter_model = NULL, *sortable = NULL;
940 sortable = gtk_tree_model_sort_new_with_model (model);
941 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
942 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
944 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
945 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN,
946 cmp_rows, NULL, NULL);
948 /* Create filter model */
949 filter_model = gtk_tree_model_filter_new (sortable, NULL);
950 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
954 /* if (priv->style == MODEST_FOLDER_VIEW_STYLE_SHOW_ONE) { */
955 /* filter_model = gtk_tree_model_filter_new (sortable, NULL); */
956 /* gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model), */
963 gtk_tree_view_set_model (GTK_TREE_VIEW(self), filter_model);
964 /* gtk_tree_view_set_model (GTK_TREE_VIEW(self), */
965 /* (filter_model) ? filter_model : sortable); */
967 g_object_unref (model);
968 g_object_unref (filter_model);
969 /* if (filter_model) */
970 /* g_object_unref (filter_model); */
972 g_object_unref (sortable);
974 /* Force a reselection of the INBOX next time the widget is shown */
975 priv->reselect = TRUE;
982 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
985 TnyFolderStore *folder = NULL;
987 ModestFolderView *tree_view;
988 ModestFolderViewPrivate *priv;
990 g_return_if_fail (sel);
991 g_return_if_fail (user_data);
993 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(user_data);
995 if(!gtk_tree_selection_get_selected (sel, &model, &iter))
998 /* Notify the display name observers */
999 g_signal_emit (G_OBJECT(user_data),
1000 signals[FOLDER_DISPLAY_NAME_CHANGED_SIGNAL], 0,
1003 tree_view = MODEST_FOLDER_VIEW (user_data);
1004 gtk_tree_model_get (model, &iter,
1005 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder,
1008 /* If the folder is the same do not notify */
1009 if (priv->cur_folder_store == folder && folder) {
1010 g_object_unref (folder);
1014 /* Current folder was unselected */
1015 if (priv->cur_folder_store) {
1016 if (TNY_IS_FOLDER(priv->cur_folder_store))
1017 tny_folder_sync (TNY_FOLDER(priv->cur_folder_store), TRUE, NULL);
1018 /* expunge the message */
1020 g_signal_emit (G_OBJECT(tree_view), signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0,
1021 priv->cur_folder_store, FALSE);
1022 g_object_unref (priv->cur_folder_store);
1023 priv->cur_folder_store = NULL;
1026 /* New current references */
1027 priv->cur_folder_store = folder;
1029 /* New folder has been selected */
1030 g_signal_emit (G_OBJECT(tree_view),
1031 signals[FOLDER_SELECTION_CHANGED_SIGNAL],
1032 0, priv->cur_folder_store, TRUE);
1036 modest_folder_view_get_selected (ModestFolderView *self)
1038 ModestFolderViewPrivate *priv;
1040 g_return_val_if_fail (self, NULL);
1042 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1043 if (priv->cur_folder_store)
1044 g_object_ref (priv->cur_folder_store);
1046 return priv->cur_folder_store;
1050 get_cmp_rows_type_pos (GObject *folder)
1052 /* Remote accounts -> Local account -> MMC account .*/
1055 if (TNY_IS_ACCOUNT (folder) &&
1056 modest_tny_account_is_virtual_local_folders (
1057 TNY_ACCOUNT (folder))) {
1059 } else if (TNY_IS_ACCOUNT (folder)) {
1060 TnyAccount *account = TNY_ACCOUNT (folder);
1061 const gchar *account_id = tny_account_get_id (account);
1062 if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
1068 printf ("DEBUG: %s: unexpected type.\n", __FUNCTION__);
1069 return -1; /* Should never happen */
1074 get_cmp_subfolder_type_pos (TnyFolderType t)
1076 /* Outbox, Drafts, Sent, User */
1080 case TNY_FOLDER_TYPE_OUTBOX:
1083 case TNY_FOLDER_TYPE_DRAFTS:
1086 case TNY_FOLDER_TYPE_SENT:
1095 * This function orders the mail accounts according to these rules:
1096 * 1st - remote accounts
1097 * 2nd - local account
1101 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1105 gchar *name1 = NULL;
1106 gchar *name2 = NULL;
1107 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1108 TnyFolderType type2 = TNY_FOLDER_TYPE_UNKNOWN;
1109 GObject *folder1 = NULL;
1110 GObject *folder2 = NULL;
1112 gtk_tree_model_get (tree_model, iter1,
1113 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name1,
1114 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type,
1115 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder1,
1117 gtk_tree_model_get (tree_model, iter2,
1118 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name2,
1119 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &type2,
1120 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &folder2,
1123 if (type == TNY_FOLDER_TYPE_ROOT) {
1124 /* Compare the types, so that
1125 * Remote accounts -> Local account -> MMC account .*/
1126 const gint pos1 = get_cmp_rows_type_pos (folder1);
1127 const gint pos2 = get_cmp_rows_type_pos (folder2);
1128 /* printf ("DEBUG: %s:\n type1=%s, pos1=%d\n type2=%s, pos2=%d\n",
1129 __FUNCTION__, G_OBJECT_TYPE_NAME(folder1), pos1, G_OBJECT_TYPE_NAME(folder2), pos2); */
1132 else if (pos1 > pos2)
1135 /* Compare items of the same type: */
1137 TnyAccount *account1 = NULL;
1138 if (TNY_IS_ACCOUNT (folder1))
1139 account1 = TNY_ACCOUNT (folder1);
1141 TnyAccount *account2 = NULL;
1142 if (TNY_IS_ACCOUNT (folder2))
1143 account2 = TNY_ACCOUNT (folder2);
1145 const gchar *account_id = account1 ? tny_account_get_id (account1) : NULL;
1146 const gchar *account_id2 = account2 ? tny_account_get_id (account2) : NULL;
1148 if (!account_id && !account_id2)
1150 else if (!account_id)
1152 else if (!account_id2)
1154 else if (!strcmp (account_id, MODEST_MMC_ACCOUNT_ID))
1157 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
1161 gboolean has_parent;
1162 gint cmp1 = 0, cmp2 = 0;
1163 /* get the parent to know if it's a local folder */
1164 has_parent = gtk_tree_model_iter_parent (tree_model, &parent, iter1);
1166 GObject *parent_folder;
1167 TnyFolderType parent_type = TNY_FOLDER_TYPE_UNKNOWN;
1168 gtk_tree_model_get (tree_model, &parent,
1169 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN, &parent_type,
1170 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN, &parent_folder,
1172 if ((parent_type == TNY_FOLDER_TYPE_ROOT) &&
1173 TNY_IS_ACCOUNT (parent_folder) &&
1174 modest_tny_account_is_virtual_local_folders (TNY_ACCOUNT (parent_folder))) {
1175 cmp1 = get_cmp_subfolder_type_pos (modest_tny_folder_get_local_folder_type (TNY_FOLDER (folder1)));
1176 cmp2 = get_cmp_subfolder_type_pos (modest_tny_folder_get_local_folder_type (TNY_FOLDER (folder2)));
1178 g_object_unref (parent_folder);
1181 cmp = modest_text_utils_utf8_strcmp (name1, name2, TRUE);
1183 cmp = (cmp1 - cmp2);
1187 g_object_unref(G_OBJECT(folder1));
1189 g_object_unref(G_OBJECT(folder2));
1197 /*****************************************************************************/
1198 /* DRAG and DROP stuff */
1199 /*****************************************************************************/
1202 * This function fills the #GtkSelectionData with the row and the
1203 * model that has been dragged. It's called when this widget is a
1204 * source for dnd after the event drop happened
1207 on_drag_data_get (GtkWidget *widget,
1208 GdkDragContext *context,
1209 GtkSelectionData *selection_data,
1214 GtkTreeSelection *selection;
1215 GtkTreeModel *model;
1217 GtkTreePath *source_row;
1219 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1220 gtk_tree_selection_get_selected (selection, &model, &iter);
1221 source_row = gtk_tree_model_get_path (model, &iter);
1223 gtk_tree_set_row_drag_data (selection_data,
1227 gtk_tree_path_free (source_row);
1230 typedef struct _DndHelper {
1231 gboolean delete_source;
1232 GtkTreePath *source_row;
1233 GdkDragContext *context;
1239 * This function is the callback of the
1240 * modest_mail_operation_xfer_msgs () and
1241 * modest_mail_operation_xfer_folder() calls. We check here if the
1242 * message/folder was correctly asynchronously transferred. The reason
1243 * to use the same callback is that the code is the same, it only has
1244 * to check that the operation went fine and then finalize the drag
1248 on_progress_changed (ModestMailOperation *mail_op,
1249 ModestMailOperationState *state,
1255 helper = (DndHelper *) user_data;
1257 if (!state->finished)
1260 if (state->status == MODEST_MAIL_OPERATION_STATUS_SUCCESS) {
1266 /* Notify the drag source. Never call delete, the monitor will
1267 do the job if needed */
1268 gtk_drag_finish (helper->context, success, FALSE, helper->time);
1270 /* Free the helper */
1271 gtk_tree_path_free (helper->source_row);
1272 g_slice_free (DndHelper, helper);
1276 * This function is used by drag_data_received_cb to manage drag and
1277 * drop of a header, i.e, and drag from the header view to the folder
1281 drag_and_drop_from_header_view (GtkTreeModel *source_model,
1282 GtkTreeModel *dest_model,
1283 GtkTreePath *dest_row,
1286 TnyList *headers = NULL;
1287 TnyHeader *header = NULL;
1288 TnyFolder *folder = NULL;
1289 ModestMailOperation *mail_op = NULL;
1290 GtkTreeIter source_iter, dest_iter;
1292 g_return_if_fail (GTK_IS_TREE_MODEL(source_model));
1293 g_return_if_fail (GTK_IS_TREE_MODEL(dest_model));
1294 g_return_if_fail (dest_row);
1295 g_return_if_fail (helper);
1298 gtk_tree_model_get_iter (source_model, &source_iter, helper->source_row);
1299 gtk_tree_model_get (source_model, &source_iter,
1300 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1302 if (!TNY_IS_HEADER(header)) {
1303 g_warning ("BUG: %s could not get a valid header", __FUNCTION__);
1308 gtk_tree_model_get_iter (dest_model, &dest_iter, dest_row);
1309 gtk_tree_model_get (dest_model, &dest_iter,
1310 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
1313 if (!TNY_IS_FOLDER(folder)) {
1314 g_warning ("BUG: %s could not get a valid folder", __FUNCTION__);
1318 /* Transfer message */
1319 mail_op = modest_mail_operation_new (MODEST_MAIL_OPERATION_TYPE_RECEIVE, NULL);
1320 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1322 g_signal_connect (G_OBJECT (mail_op), "progress-changed",
1323 G_CALLBACK (on_progress_changed), helper);
1325 headers = tny_simple_list_new ();
1326 tny_list_append (headers, G_OBJECT (header));
1327 modest_mail_operation_xfer_msgs (mail_op,
1330 helper->delete_source,
1335 if (G_IS_OBJECT(mail_op))
1336 g_object_unref (G_OBJECT (mail_op));
1337 if (G_IS_OBJECT(header))
1338 g_object_unref (G_OBJECT (header));
1339 if (G_IS_OBJECT(folder))
1340 g_object_unref (G_OBJECT (folder));
1341 if (G_IS_OBJECT(headers))
1342 g_object_unref (headers);
1346 * This function is used by drag_data_received_cb to manage drag and
1347 * drop of a folder, i.e, and drag from the folder view to the same
1351 drag_and_drop_from_folder_view (GtkTreeModel *source_model,
1352 GtkTreeModel *dest_model,
1353 GtkTreePath *dest_row,
1354 GtkSelectionData *selection_data,
1357 ModestMailOperation *mail_op;
1358 GtkTreeIter parent_iter, iter;
1359 TnyFolderStore *parent_folder;
1362 /* Check if the drag is possible */
1363 /* if (!gtk_tree_path_compare (helper->source_row, dest_row) || */
1364 /* !gtk_tree_drag_dest_row_drop_possible (GTK_TREE_DRAG_DEST (dest_model), */
1366 /* selection_data)) { */
1367 if (!gtk_tree_path_compare (helper->source_row, dest_row)) {
1369 gtk_drag_finish (helper->context, FALSE, FALSE, helper->time);
1370 gtk_tree_path_free (helper->source_row);
1371 g_slice_free (DndHelper, helper);
1376 gtk_tree_model_get_iter (source_model, &parent_iter, dest_row);
1377 gtk_tree_model_get_iter (source_model, &iter, helper->source_row);
1378 gtk_tree_model_get (source_model, &parent_iter,
1379 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
1380 &parent_folder, -1);
1381 gtk_tree_model_get (source_model, &iter,
1382 TNY_GTK_FOLDER_STORE_TREE_MODEL_INSTANCE_COLUMN,
1385 /* Do the mail operation */
1386 mail_op = modest_mail_operation_new_with_error_handling (MODEST_MAIL_OPERATION_TYPE_RECEIVE,
1388 modest_ui_actions_move_folder_error_handler,
1390 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1392 g_signal_connect (G_OBJECT (mail_op), "progress-changed",
1393 G_CALLBACK (on_progress_changed), helper);
1395 modest_mail_operation_xfer_folder (mail_op,
1398 helper->delete_source);
1401 g_object_unref (G_OBJECT (parent_folder));
1402 g_object_unref (G_OBJECT (folder));
1403 g_object_unref (G_OBJECT (mail_op));
1407 * This function receives the data set by the "drag-data-get" signal
1408 * handler. This information comes within the #GtkSelectionData. This
1409 * function will manage both the drags of folders of the treeview and
1410 * drags of headers of the header view widget.
1413 on_drag_data_received (GtkWidget *widget,
1414 GdkDragContext *context,
1417 GtkSelectionData *selection_data,
1422 GtkWidget *source_widget;
1423 GtkTreeModel *dest_model, *source_model;
1424 GtkTreePath *source_row, *dest_row;
1425 GtkTreeViewDropPosition pos;
1426 gboolean success = FALSE, delete_source = FALSE;
1427 DndHelper *helper = NULL;
1429 /* Do not allow further process */
1430 g_signal_stop_emission_by_name (widget, "drag-data-received");
1431 source_widget = gtk_drag_get_source_widget (context);
1433 /* Get the action */
1434 if (context->action == GDK_ACTION_MOVE) {
1435 delete_source = TRUE;
1437 /* Notify that there is no folder selected. We need to
1438 do this in order to update the headers view (and
1439 its monitors, because when moving, the old folder
1440 won't longer exist. We can not wait for the end of
1441 the operation, because the operation won't start if
1442 the folder is in use */
1443 if (source_widget == widget) {
1444 ModestFolderViewPrivate *priv;
1446 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
1447 if (priv->cur_folder_store) {
1448 g_object_unref (priv->cur_folder_store);
1449 priv->cur_folder_store = NULL;
1452 g_signal_emit (G_OBJECT (widget),
1453 signals[FOLDER_SELECTION_CHANGED_SIGNAL], 0, NULL, FALSE);
1457 /* Check if the get_data failed */
1458 if (selection_data == NULL || selection_data->length < 0)
1459 gtk_drag_finish (context, success, FALSE, time);
1461 /* Get the models */
1462 gtk_tree_get_row_drag_data (selection_data,
1466 /* Select the destination model */
1467 if (source_widget == widget) {
1468 dest_model = source_model;
1470 dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
1473 /* Get the path to the destination row. Can not call
1474 gtk_tree_view_get_drag_dest_row() because the source row
1475 is not selected anymore */
1476 gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget), x, y,
1479 /* Only allow drops IN other rows */
1480 if (!dest_row || pos == GTK_TREE_VIEW_DROP_BEFORE || pos == GTK_TREE_VIEW_DROP_AFTER)
1481 gtk_drag_finish (context, success, FALSE, time);
1483 /* Create the helper */
1484 helper = g_slice_new0 (DndHelper);
1485 helper->delete_source = delete_source;
1486 helper->source_row = gtk_tree_path_copy (source_row);
1487 helper->context = context;
1488 helper->time = time;
1490 /* Drags from the header view */
1491 if (source_widget != widget) {
1493 drag_and_drop_from_header_view (source_model,
1500 drag_and_drop_from_folder_view (source_model,
1508 gtk_tree_path_free (source_row);
1509 gtk_tree_path_free (dest_row);
1513 * We define a "drag-drop" signal handler because we do not want to
1514 * use the default one, because the default one always calls
1515 * gtk_drag_finish and we prefer to do it in the "drag-data-received"
1516 * signal handler, because there we have all the information available
1517 * to know if the dnd was a success or not.
1520 drag_drop_cb (GtkWidget *widget,
1521 GdkDragContext *context,
1529 if (!context->targets)
1532 /* Check if we're dragging a folder row */
1533 target = gtk_drag_dest_find_target (widget, context, NULL);
1535 /* Request the data from the source. */
1536 gtk_drag_get_data(widget, context, target, time);
1542 * This function expands a node of a tree view if it's not expanded
1543 * yet. Not sure why it needs the threads stuff, but gtk+`example code
1544 * does that, so that's why they're here.
1547 expand_row_timeout (gpointer data)
1549 GtkTreeView *tree_view = data;
1550 GtkTreePath *dest_path = NULL;
1551 GtkTreeViewDropPosition pos;
1552 gboolean result = FALSE;
1554 GDK_THREADS_ENTER ();
1556 gtk_tree_view_get_drag_dest_row (tree_view,
1561 (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER ||
1562 pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)) {
1563 gtk_tree_view_expand_row (tree_view, dest_path, FALSE);
1564 gtk_tree_path_free (dest_path);
1568 gtk_tree_path_free (dest_path);
1573 GDK_THREADS_LEAVE ();
1579 * This function is called whenever the pointer is moved over a widget
1580 * while dragging some data. It installs a timeout that will expand a
1581 * node of the treeview if not expanded yet. This function also calls
1582 * gdk_drag_status in order to set the suggested action that will be
1583 * used by the "drag-data-received" signal handler to know if we
1584 * should do a move or just a copy of the data.
1587 on_drag_motion (GtkWidget *widget,
1588 GdkDragContext *context,
1594 GtkTreeViewDropPosition pos;
1595 GtkTreePath *dest_row;
1596 ModestFolderViewPrivate *priv;
1597 GdkDragAction suggested_action;
1598 gboolean valid_location = FALSE;
1600 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (widget);
1602 if (priv->timer_expander != 0) {
1603 g_source_remove (priv->timer_expander);
1604 priv->timer_expander = 0;
1607 gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
1612 /* Do not allow drops between folders */
1614 pos == GTK_TREE_VIEW_DROP_BEFORE ||
1615 pos == GTK_TREE_VIEW_DROP_AFTER) {
1616 gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW (widget), NULL, 0);
1617 gdk_drag_status(context, 0, time);
1618 valid_location = FALSE;
1621 valid_location = TRUE;
1624 /* Expand the selected row after 1/2 second */
1625 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), dest_row)) {
1626 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), dest_row, pos);
1627 priv->timer_expander = g_timeout_add (500, expand_row_timeout, widget);
1630 /* Select the desired action. By default we pick MOVE */
1631 suggested_action = GDK_ACTION_MOVE;
1633 if (context->actions == GDK_ACTION_COPY)
1634 gdk_drag_status(context, GDK_ACTION_COPY, time);
1635 else if (context->actions == GDK_ACTION_MOVE)
1636 gdk_drag_status(context, GDK_ACTION_MOVE, time);
1637 else if (context->actions & suggested_action)
1638 gdk_drag_status(context, suggested_action, time);
1640 gdk_drag_status(context, GDK_ACTION_DEFAULT, time);
1644 gtk_tree_path_free (dest_row);
1645 g_signal_stop_emission_by_name (widget, "drag-motion");
1646 return valid_location;
1650 /* Folder view drag types */
1651 const GtkTargetEntry folder_view_drag_types[] =
1653 { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, MODEST_FOLDER_ROW },
1654 { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_APP, MODEST_HEADER_ROW }
1658 * This function sets the treeview as a source and a target for dnd
1659 * events. It also connects all the requirede signals.
1662 setup_drag_and_drop (GtkTreeView *self)
1664 /* Set up the folder view as a dnd destination. Set only the
1665 highlight flag, otherwise gtk will have a different
1667 gtk_drag_dest_set (GTK_WIDGET (self),
1668 GTK_DEST_DEFAULT_HIGHLIGHT,
1669 folder_view_drag_types,
1670 G_N_ELEMENTS (folder_view_drag_types),
1671 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1673 g_signal_connect (G_OBJECT (self),
1674 "drag_data_received",
1675 G_CALLBACK (on_drag_data_received),
1679 /* Set up the treeview as a dnd source */
1680 gtk_drag_source_set (GTK_WIDGET (self),
1682 folder_view_drag_types,
1683 G_N_ELEMENTS (folder_view_drag_types),
1684 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1686 g_signal_connect (G_OBJECT (self),
1688 G_CALLBACK (on_drag_motion),
1691 g_signal_connect (G_OBJECT (self),
1693 G_CALLBACK (on_drag_data_get),
1696 g_signal_connect (G_OBJECT (self),
1698 G_CALLBACK (drag_drop_cb),
1703 * This function manages the navigation through the folders using the
1704 * keyboard or the hardware keys in the device
1707 on_key_pressed (GtkWidget *self,
1711 GtkTreeSelection *selection;
1713 GtkTreeModel *model;
1714 gboolean retval = FALSE;
1716 /* Up and Down are automatically managed by the treeview */
1717 if (event->keyval == GDK_Return) {
1718 /* Expand/Collapse the selected row */
1719 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1720 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
1723 path = gtk_tree_model_get_path (model, &iter);
1725 if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (self), path))
1726 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1728 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1729 gtk_tree_path_free (path);
1731 /* No further processing */
1739 * We listen to the changes in the local folder account name key,
1740 * because we want to show the right name in the view. The local
1741 * folder account name corresponds to the device name in the Maemo
1742 * version. We do this because we do not want to query gconf on each
1743 * tree view refresh. It's better to cache it and change whenever
1747 on_configuration_key_changed (ModestConf* conf,
1749 ModestConfEvent event,
1750 ModestFolderView *self)
1752 ModestFolderViewPrivate *priv;
1757 g_return_if_fail (MODEST_IS_FOLDER_VIEW (self));
1758 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1760 if (!strcmp (key, MODEST_CONF_DEVICE_NAME)) {
1761 g_free (priv->local_account_name);
1763 if (event == MODEST_CONF_EVENT_KEY_UNSET)
1764 priv->local_account_name = g_strdup (MODEST_LOCAL_FOLDERS_DEFAULT_DISPLAY_NAME);
1766 priv->local_account_name = modest_conf_get_string (modest_runtime_get_conf(),
1767 MODEST_CONF_DEVICE_NAME, NULL);
1769 /* Force a redraw */
1770 #if GTK_CHECK_VERSION(2, 8, 0) /* gtk_tree_view_column_queue_resize is only available in GTK+ 2.8 */
1771 GtkTreeViewColumn * tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (self),
1772 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN);
1773 gtk_tree_view_column_queue_resize (tree_column);
1779 modest_folder_view_set_style (ModestFolderView *self,
1780 ModestFolderViewStyle style)
1782 ModestFolderViewPrivate *priv;
1784 g_return_if_fail (self);
1786 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1788 priv->style = style;
1792 modest_folder_view_set_account_id_of_visible_server_account (ModestFolderView *self,
1793 const gchar *account_id)
1795 ModestFolderViewPrivate *priv;
1796 GtkTreeModel *model;
1798 g_return_if_fail (self);
1800 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1802 /* This will be used by the filter_row callback,
1803 * to decided which rows to show: */
1804 if (priv->visible_account_id) {
1805 g_free (priv->visible_account_id);
1806 priv->visible_account_id = NULL;
1809 priv->visible_account_id = g_strdup (account_id);
1812 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1813 if (GTK_IS_TREE_MODEL_FILTER (model))
1814 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
1816 /* Save settings to gconf */
1817 modest_widget_memory_save (modest_runtime_get_conf (), G_OBJECT(self),
1818 MODEST_CONF_FOLDER_VIEW_KEY);
1822 modest_folder_view_get_account_id_of_visible_server_account (ModestFolderView *self)
1824 ModestFolderViewPrivate *priv;
1826 g_return_val_if_fail (self, NULL);
1828 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(self);
1830 return (const gchar *) priv->visible_account_id;
1834 find_inbox_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *inbox_iter)
1838 TnyFolderType type = TNY_FOLDER_TYPE_UNKNOWN;
1841 gtk_tree_model_get (model, iter,
1842 TNY_GTK_FOLDER_STORE_TREE_MODEL_NAME_COLUMN, &name,
1843 TNY_GTK_FOLDER_STORE_TREE_MODEL_TYPE_COLUMN,
1847 printf ("DEBUG: %s: name=%s, type=%d, TNY_FOLDER_TYPE_INBOX=%d\n",
1848 __FUNCTION__, name, type, TNY_FOLDER_TYPE_INBOX);
1851 gboolean result = FALSE;
1852 if (type == TNY_FOLDER_TYPE_INBOX) {
1854 } else if (type == TNY_FOLDER_TYPE_NORMAL) {
1855 /* tinymail's camel implementation only provides TNY_FOLDER_TYPE_NORMAL
1856 * when getting folders from the cache, before connectin, so we do
1857 * an extra check. We could fix this in tinymail, but it's easier
1860 if (strcmp (name, "Inbox") == 0)
1867 *inbox_iter = *iter;
1871 if (gtk_tree_model_iter_children (model, &child, iter)) {
1872 if (find_inbox_iter (model, &child, inbox_iter))
1876 } while (gtk_tree_model_iter_next (model, iter));
1882 modest_folder_view_select_first_inbox_or_local (ModestFolderView *self)
1884 GtkTreeModel *model;
1885 GtkTreeIter iter, inbox_iter;
1886 GtkTreeSelection *sel;
1888 /* /\* Do not set it if the folder view was not painted *\/ */
1889 /* if (!GTK_WIDGET_MAPPED (self)) */
1892 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1896 expand_root_items (self);
1897 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1899 gtk_tree_model_get_iter_first (model, &iter);
1900 if (find_inbox_iter (model, &iter, &inbox_iter)) {
1901 gtk_tree_selection_select_iter (sel, &inbox_iter);
1904 gtk_tree_model_get_iter_first (model, &iter);
1905 gtk_tree_selection_select_iter (sel, &iter);
1910 modest_folder_view_copy_selection (ModestFolderView *folder_view)
1912 /* Copy selection */
1913 _clipboard_set_selected_data (folder_view, FALSE);
1917 modest_folder_view_cut_selection (ModestFolderView *folder_view)
1919 ModestFolderViewPrivate *priv = NULL;
1920 GtkTreeModel *model = NULL;
1921 const gchar **hidding = NULL;
1922 guint i, n_selected;
1924 g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view));
1925 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
1927 /* Copy selection */
1928 _clipboard_set_selected_data (folder_view, TRUE);
1930 /* Get hidding ids */
1931 hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected);
1933 /* Clear hidding array created by previous cut operation */
1934 _clear_hidding_filter (MODEST_FOLDER_VIEW (folder_view));
1936 /* Copy hidding array */
1937 priv->n_selected = n_selected;
1938 priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
1939 for (i=0; i < n_selected; i++)
1940 priv->hidding_ids[i] = g_strdup(hidding[i]);
1942 /* Hide cut folders */
1943 model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_view));
1944 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
1948 _clipboard_set_selected_data (ModestFolderView *folder_view,
1951 ModestFolderViewPrivate *priv = NULL;
1952 TnyFolderStore *folder = NULL;
1954 g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view));
1955 priv = MODEST_FOLDER_VIEW_GET_PRIVATE (folder_view);
1957 /* Set selected data on clipboard */
1958 g_return_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard));
1959 folder = modest_folder_view_get_selected (folder_view);
1960 modest_email_clipboard_set_data (priv->clipboard, TNY_FOLDER(folder), NULL, delete);
1963 g_object_unref (folder);
1967 _clear_hidding_filter (ModestFolderView *folder_view)
1969 ModestFolderViewPrivate *priv;
1972 g_return_if_fail (MODEST_IS_FOLDER_VIEW (folder_view));
1973 priv = MODEST_FOLDER_VIEW_GET_PRIVATE(folder_view);
1975 if (priv->hidding_ids != NULL) {
1976 for (i=0; i < priv->n_selected; i++)
1977 g_free (priv->hidding_ids[i]);
1978 g_free(priv->hidding_ids);