Bug 789009 - Inbox is not expanded automatically on restart
[evolution.git] / src / mail / em-folder-tree.c
blob2314b5efce03abf753b1ae567e3b031a992aad81
1 /*
2 * This program is free software; you can redistribute it and/or modify it
3 * under the terms of the GNU Lesser General Public License as published by
4 * the Free Software Foundation.
6 * This program is distributed in the hope that it will be useful, but
7 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
8 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
9 * for more details.
11 * You should have received a copy of the GNU Lesser General Public License
12 * along with this program; if not, see <http://www.gnu.org/licenses/>.
15 * Authors:
16 * Jeffrey Stedfast <fejj@ximian.com>
18 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
22 #include "evolution-config.h"
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <unistd.h>
30 #include <fcntl.h>
31 #include <errno.h>
33 #include <libxml/tree.h>
35 #include <glib/gi18n.h>
36 #include <gdk/gdkkeysyms.h>
38 #include "em-vfolder-editor-rule.h"
40 #include "em-utils.h"
41 #include "em-folder-utils.h"
42 #include "em-folder-selector.h"
43 #include "em-folder-properties.h"
44 #include "em-event.h"
45 #include "mail-send-recv.h"
46 #include "mail-vfolder-ui.h"
48 #include "e-mail-ui-session.h"
50 #include "em-folder-tree.h"
52 #define d(x)
54 #define EM_FOLDER_TREE_GET_PRIVATE(obj) \
55 (G_TYPE_INSTANCE_GET_PRIVATE \
56 ((obj), EM_TYPE_FOLDER_TREE, EMFolderTreePrivate))
58 typedef struct _AsyncContext AsyncContext;
60 #define EM_FOLDER_TREE_GET_PRIVATE(obj) \
61 (G_TYPE_INSTANCE_GET_PRIVATE \
62 ((obj), EM_TYPE_FOLDER_TREE, EMFolderTreePrivate))
64 struct _selected_uri {
65 gchar *key; /* store:path or account/path */
66 gchar *uri;
67 CamelService *service;
68 gchar *path;
71 struct _EMFolderTreePrivate {
72 EMailSession *session;
73 EAlertSink *alert_sink;
75 /* selected_uri structures of each path pending selection. */
76 GSList *select_uris;
78 /* Removed as they're encountered, so use this
79 * to find URI's not presnet but selected. */
80 GHashTable *select_uris_table;
82 guint32 excluded;
83 gboolean (*excluded_func) (EMFolderTree *folder_tree,
84 GtkTreeModel *model,
85 GtkTreeIter *iter,
86 gpointer data);
87 gpointer excluded_data;
89 guint cursor_set:1; /* set to TRUE means we or something
90 * else has set the cursor, otherwise
91 * we need to set it when we set the
92 * selection */
94 guint autoscroll_id;
95 guint autoexpand_id;
96 GtkTreeRowReference *autoexpand_row;
98 guint loaded_row_id;
99 guint row_changed_id;
101 GtkTreeRowReference *drag_row;
102 gboolean skip_double_click;
104 GtkCellRenderer *text_renderer;
106 GtkWidget *selectable; /* an ESelectable, where to pass selectable calls */
108 gchar *select_store_uid_when_added;
110 /* Signal handler IDs */
111 gulong selection_changed_handler_id;
114 struct _AsyncContext {
115 EActivity *activity;
116 EMFolderTree *folder_tree;
117 GtkTreeRowReference *root;
118 gchar *full_name;
121 enum {
122 PROP_0,
123 PROP_ALERT_SINK,
124 PROP_COPY_TARGET_LIST,
125 PROP_MODEL,
126 PROP_PASTE_TARGET_LIST,
127 PROP_SESSION
130 enum {
131 FOLDER_ACTIVATED, /* aka double-clicked or user hit enter */
132 FOLDER_SELECTED,
133 POPUP_EVENT,
134 HIDDEN_KEY_EVENT,
135 LAST_SIGNAL
138 /* Drag & Drop types */
139 enum DndDragType {
140 DND_DRAG_TYPE_FOLDER, /* drag an evo folder */
141 DND_DRAG_TYPE_TEXT_URI_LIST, /* drag to an mbox file */
142 NUM_DRAG_TYPES
145 enum DndDropType {
146 DND_DROP_TYPE_UID_LIST, /* drop a list of message uids */
147 DND_DROP_TYPE_FOLDER, /* drop an evo folder */
148 DND_DROP_TYPE_MESSAGE_RFC822, /* drop a message/rfc822 stream */
149 DND_DROP_TYPE_TEXT_URI_LIST, /* drop an mbox file */
150 NUM_DROP_TYPES
153 static GtkTargetEntry drag_types[] = {
154 { (gchar *) "x-folder", 0, DND_DRAG_TYPE_FOLDER },
155 { (gchar *) "text/uri-list", 0, DND_DRAG_TYPE_TEXT_URI_LIST },
158 static GtkTargetEntry drop_types[] = {
159 { (gchar *) "x-uid-list" , 0, DND_DROP_TYPE_UID_LIST },
160 { (gchar *) "x-folder", 0, DND_DROP_TYPE_FOLDER },
161 { (gchar *) "message/rfc822", 0, DND_DROP_TYPE_MESSAGE_RFC822 },
162 { (gchar *) "text/uri-list", 0, DND_DROP_TYPE_TEXT_URI_LIST },
165 static GdkAtom drag_atoms[NUM_DRAG_TYPES];
166 static GdkAtom drop_atoms[NUM_DROP_TYPES];
168 static guint signals[LAST_SIGNAL] = { 0 };
170 struct _folder_tree_selection_data {
171 GtkTreeModel *model;
172 GtkTreeIter *iter;
173 gboolean set;
176 /* Forward Declarations */
177 static void em_folder_tree_selectable_init (ESelectableInterface *iface);
179 G_DEFINE_TYPE_WITH_CODE (
180 EMFolderTree,
181 em_folder_tree,
182 GTK_TYPE_TREE_VIEW,
183 G_IMPLEMENT_INTERFACE (
184 E_TYPE_SELECTABLE,
185 em_folder_tree_selectable_init))
187 static void
188 async_context_free (AsyncContext *context)
190 if (context->activity != NULL)
191 g_object_unref (context->activity);
193 if (context->folder_tree != NULL)
194 g_object_unref (context->folder_tree);
196 gtk_tree_row_reference_free (context->root);
198 g_free (context->full_name);
200 g_slice_free (AsyncContext, context);
203 static void
204 folder_tree_get_folder_info_cb (CamelStore *store,
205 GAsyncResult *result,
206 AsyncContext *context)
208 CamelFolderInfo *folder_info;
209 CamelFolderInfo *child_info;
210 EAlertSink *alert_sink;
211 GtkTreeView *tree_view;
212 GtkTreeModel *model;
213 GtkTreePath *path;
214 GtkTreeIter root;
215 GtkTreeIter iter;
216 GtkTreeIter titer;
217 gboolean is_store;
218 gboolean iter_is_placeholder;
219 gboolean valid;
220 GError *error = NULL;
222 alert_sink = e_activity_get_alert_sink (context->activity);
224 folder_info = camel_store_get_folder_info_finish (
225 store, result, &error);
227 tree_view = GTK_TREE_VIEW (context->folder_tree);
228 model = gtk_tree_view_get_model (tree_view);
230 /* Check if our parent folder has been deleted/unsubscribed. */
231 if (!gtk_tree_row_reference_valid (context->root)) {
232 g_clear_error (&error);
233 goto exit;
236 path = gtk_tree_row_reference_get_path (context->root);
237 valid = gtk_tree_model_get_iter (model, &root, path);
238 g_return_if_fail (valid);
240 gtk_tree_model_get (model, &root, COL_BOOL_IS_STORE, &is_store, -1);
242 /* If we had an error, then we need to re-set the
243 * load subdirs state and collapse the node. */
244 if (error != NULL) {
245 gtk_tree_store_set (
246 GTK_TREE_STORE (model), &root,
247 COL_BOOL_LOAD_SUBDIRS, TRUE, -1);
248 gtk_tree_view_collapse_row (tree_view, path);
251 gtk_tree_path_free (path);
253 if (e_activity_handle_cancellation (context->activity, error)) {
254 g_warn_if_fail (folder_info == NULL);
255 async_context_free (context);
256 g_error_free (error);
257 return;
259 /* XXX POP3 stores always return a "no folder" error because they
260 * have no folder hierarchy to scan. Just ignore the error. */
261 } else if (g_error_matches (
262 error, CAMEL_STORE_ERROR,
263 CAMEL_STORE_ERROR_NO_FOLDER)) {
264 g_warn_if_fail (folder_info == NULL);
265 async_context_free (context);
266 g_error_free (error);
267 return;
269 } else if (error != NULL) {
270 g_warn_if_fail (folder_info == NULL);
271 e_alert_submit (
272 alert_sink, "mail:folder-open",
273 error->message, NULL);
274 async_context_free (context);
275 g_error_free (error);
276 return;
279 /* If we've just set up an NNTP account, for example, and haven't
280 * subscribed to any folders yet, folder_info may legitimately be
281 * NULL at this point. We handle that case below. Proceed. */
283 /* Make sure we still need to load the tree subfolders. */
285 iter_is_placeholder = FALSE;
287 /* Get the first child (which will be a placeholder row). */
288 valid = gtk_tree_model_iter_children (model, &iter, &root);
290 /* Traverse to the last valid iter, or the placeholder row. */
291 while (valid) {
292 gboolean is_store_node = FALSE;
293 gboolean is_folder_node = FALSE;
295 titer = iter; /* Preserve the last valid iter */
297 gtk_tree_model_get (
298 model, &iter,
299 COL_BOOL_IS_STORE, &is_store_node,
300 COL_BOOL_IS_FOLDER, &is_folder_node, -1);
302 /* Stop on a "Loading..." placeholder row. */
303 if (!is_store_node && !is_folder_node) {
304 iter_is_placeholder = TRUE;
305 break;
308 valid = gtk_tree_model_iter_next (model, &iter);
311 iter = titer;
312 child_info = folder_info;
314 /* FIXME Camel's IMAP code is totally on crack here: the
315 * folder_info we got back should be for the folder
316 * we're expanding, and folder_info->child should be
317 * what we want to fill our tree with... *sigh* */
318 if (folder_info != NULL) {
319 gboolean names_match;
321 names_match = (g_strcmp0 (
322 folder_info->full_name,
323 context->full_name) == 0);
324 if (names_match) {
325 child_info = folder_info->child;
326 if (child_info == NULL)
327 child_info = folder_info->next;
331 /* The folder being expanded has no children after all. Remove
332 * the "Loading..." placeholder row and collapse the parent. */
333 if (child_info == NULL) {
334 if (iter_is_placeholder)
335 gtk_tree_store_remove (GTK_TREE_STORE (model), &iter);
337 if (is_store) {
338 path = gtk_tree_model_get_path (model, &root);
339 gtk_tree_view_collapse_row (tree_view, path);
340 gtk_tree_path_free (path);
341 goto exit;
344 } else {
345 while (child_info != NULL) {
346 GtkTreeRowReference *reference;
348 /* Check if we already have this row cached. */
349 reference = em_folder_tree_model_get_row_reference (
350 EM_FOLDER_TREE_MODEL (model),
351 store, child_info->full_name);
353 if (reference == NULL) {
354 /* If we're on a placeholder row, reuse
355 * the row for the first child folder. */
356 if (iter_is_placeholder)
357 iter_is_placeholder = FALSE;
358 else
359 gtk_tree_store_append (
360 GTK_TREE_STORE (model),
361 &iter, &root);
363 em_folder_tree_model_set_folder_info (
364 EM_FOLDER_TREE_MODEL (model),
365 &iter, store, child_info, TRUE);
368 child_info = child_info->next;
371 /* Remove the "Loading..." placeholder row. */
372 if (iter_is_placeholder)
373 gtk_tree_store_remove (GTK_TREE_STORE (model), &iter);
376 gtk_tree_store_set (
377 GTK_TREE_STORE (model), &root,
378 COL_BOOL_LOAD_SUBDIRS, FALSE, -1);
380 exit:
381 camel_folder_info_free (folder_info);
383 async_context_free (context);
386 static void
387 folder_tree_emit_popup_event (EMFolderTree *folder_tree,
388 GdkEvent *event)
390 g_signal_emit (folder_tree, signals[POPUP_EVENT], 0, event);
393 static void
394 folder_tree_free_select_uri (struct _selected_uri *u)
396 g_free (u->uri);
397 if (u->service)
398 g_object_unref (u->service);
399 g_free (u->key);
400 g_free (u->path);
401 g_free (u);
404 static gboolean
405 folder_tree_select_func (GtkTreeSelection *selection,
406 GtkTreeModel *model,
407 GtkTreePath *path,
408 gboolean selected)
410 EMFolderTreePrivate *priv;
411 GtkTreeView *tree_view;
412 gboolean is_store;
413 guint32 flags;
414 GtkTreeIter iter;
416 tree_view = gtk_tree_selection_get_tree_view (selection);
418 priv = EM_FOLDER_TREE_GET_PRIVATE (tree_view);
420 if (selected)
421 return TRUE;
423 if (priv->excluded == 0 && priv->excluded_func == NULL)
424 return TRUE;
426 if (!gtk_tree_model_get_iter (model, &iter, path))
427 return TRUE;
429 if (priv->excluded_func != NULL)
430 return priv->excluded_func (
431 EM_FOLDER_TREE (tree_view), model,
432 &iter, priv->excluded_data);
434 gtk_tree_model_get (
435 model, &iter, COL_UINT_FLAGS, &flags,
436 COL_BOOL_IS_STORE, &is_store, -1);
438 if (is_store)
439 flags |= CAMEL_FOLDER_NOSELECT;
441 return (flags & priv->excluded) == 0;
444 /* NOTE: Removes and frees the selected uri structure */
445 static void
446 folder_tree_select_uri (EMFolderTree *folder_tree,
447 GtkTreePath *path,
448 struct _selected_uri *u)
450 EMFolderTreePrivate *priv = folder_tree->priv;
451 GtkTreeView *tree_view;
452 GtkTreeSelection *selection;
454 tree_view = GTK_TREE_VIEW (folder_tree);
455 selection = gtk_tree_view_get_selection (tree_view);
456 gtk_tree_selection_select_path (selection, path);
457 if (!priv->cursor_set) {
458 gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE);
459 priv->cursor_set = TRUE;
461 gtk_tree_view_scroll_to_cell (tree_view, path, NULL, TRUE, 0.8f, 0.0f);
462 g_hash_table_remove (priv->select_uris_table, u->key);
463 priv->select_uris = g_slist_remove (priv->select_uris, u);
464 folder_tree_free_select_uri (u);
467 static void
468 folder_tree_expand_node (const gchar *key,
469 EMFolderTree *folder_tree)
471 GtkTreeRowReference *row = NULL;
472 GtkTreeView *tree_view;
473 GtkTreeModel *model;
474 GtkTreePath *path;
475 EMailSession *session;
476 CamelService *service;
477 const gchar *p;
478 gchar *uid;
479 gsize n;
480 struct _selected_uri *u;
482 if (!(p = strchr (key, '/')))
483 n = strlen (key);
484 else
485 n = (p - key);
487 uid = g_alloca (n + 1);
488 memcpy (uid, key, n);
489 uid[n] = '\0';
491 tree_view = GTK_TREE_VIEW (folder_tree);
492 model = gtk_tree_view_get_model (tree_view);
494 session = em_folder_tree_get_session (folder_tree);
496 service = camel_session_ref_service (CAMEL_SESSION (session), uid);
498 if (CAMEL_IS_STORE (service)) {
499 const gchar *folder_name;
501 if (p != NULL && p[1] != '\0')
502 folder_name = p + 1;
503 else
504 folder_name = NULL;
506 row = em_folder_tree_model_get_row_reference (
507 EM_FOLDER_TREE_MODEL (model),
508 CAMEL_STORE (service), folder_name);
511 g_clear_object (&service);
513 if (row == NULL)
514 return;
516 path = gtk_tree_row_reference_get_path (row);
517 gtk_tree_view_expand_to_path (tree_view, path);
519 u = g_hash_table_lookup (folder_tree->priv->select_uris_table, key);
520 if (u)
521 folder_tree_select_uri (folder_tree, path, u);
523 gtk_tree_path_free (path);
526 static void
527 folder_tree_maybe_expand_row (EMFolderTreeModel *model,
528 GtkTreePath *tree_path,
529 GtkTreeIter *iter,
530 EMFolderTree *folder_tree)
532 EMFolderTreePrivate *priv = folder_tree->priv;
533 CamelStore *store;
534 gchar *full_name;
535 gchar *key;
536 const gchar *uid;
537 struct _selected_uri *u;
539 gtk_tree_model_get (
540 GTK_TREE_MODEL (model), iter,
541 COL_STRING_FULL_NAME, &full_name,
542 COL_OBJECT_CAMEL_STORE, &store, -1);
544 uid = camel_service_get_uid (CAMEL_SERVICE (store));
545 key = g_strdup_printf ("%s/%s", uid, full_name ? full_name : "");
546 g_object_unref (store);
548 u = g_hash_table_lookup (priv->select_uris_table, key);
549 if (u) {
550 gchar *c = strrchr (key, '/');
552 /* 'c' cannot be NULL, because the above contructed 'key' has it there */
553 /* coverity[dereference] */
554 *c = '\0';
555 folder_tree_expand_node (key, folder_tree);
557 folder_tree_select_uri (folder_tree, tree_path, u);
560 g_free (full_name);
561 g_free (key);
564 static void
565 folder_tree_row_changed_cb (GtkTreeModel *model,
566 GtkTreePath *path,
567 GtkTreeIter *iter,
568 EMFolderTree *folder_tree)
570 CamelService *service = NULL;
571 gboolean is_store = FALSE;
572 gboolean select_store = FALSE;
574 g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree));
576 if (!folder_tree->priv->select_store_uid_when_added)
577 return;
579 if (gtk_tree_path_get_depth (path) != 1)
580 return;
582 gtk_tree_model_get (model, iter,
583 COL_OBJECT_CAMEL_STORE, &service,
584 COL_BOOL_IS_STORE, &is_store,
585 -1);
587 if (is_store && service != NULL) {
588 const gchar *uid1;
589 const gchar *uid2;
591 uid1 = camel_service_get_uid (service);
592 uid2 = folder_tree->priv->select_store_uid_when_added;
593 select_store = (g_strcmp0 (uid1, uid2) == 0);
596 if (select_store) {
597 GtkTreeView *tree_view;
598 GtkTreeSelection *selection;
600 g_free (folder_tree->priv->select_store_uid_when_added);
601 folder_tree->priv->select_store_uid_when_added = NULL;
603 tree_view = GTK_TREE_VIEW (folder_tree);
604 selection = gtk_tree_view_get_selection (tree_view);
606 gtk_tree_selection_select_iter (selection, iter);
607 gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE);
608 folder_tree->priv->cursor_set = TRUE;
609 gtk_tree_view_expand_row (tree_view, path, FALSE);
612 g_clear_object (&service);
615 static void
616 folder_tree_clear_selected_list (EMFolderTree *folder_tree)
618 EMFolderTreePrivate *priv = folder_tree->priv;
620 g_slist_free_full (
621 priv->select_uris,
622 (GDestroyNotify) folder_tree_free_select_uri);
623 g_hash_table_destroy (priv->select_uris_table);
624 priv->select_uris = NULL;
625 priv->select_uris_table = g_hash_table_new (g_str_hash, g_str_equal);
626 priv->cursor_set = FALSE;
629 static void
630 folder_tree_cell_edited_cb (EMFolderTree *folder_tree,
631 const gchar *path_string,
632 const gchar *new_name)
634 CamelFolderInfo *folder_info;
635 CamelStore *store;
636 GtkTreeView *tree_view;
637 GtkTreeModel *model;
638 GtkTreePath *path;
639 GtkTreeIter iter;
640 gchar *old_name = NULL;
641 gchar *old_full_name = NULL;
642 gchar *new_full_name = NULL;
643 gchar *folder_uri;
644 gchar **strv;
645 gpointer parent;
646 guint index;
647 GError *local_error = NULL;
649 /* XXX Consider splitting this into separate async functions:
650 * em_folder_tree_rename_folder_async()
651 * em_folder_tree_rename_folder_finish() */
653 parent = gtk_widget_get_toplevel (GTK_WIDGET (folder_tree));
654 parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
656 tree_view = GTK_TREE_VIEW (folder_tree);
657 model = gtk_tree_view_get_model (tree_view);
658 path = gtk_tree_path_new_from_string (path_string);
659 gtk_tree_model_get_iter (model, &iter, path);
660 gtk_tree_path_free (path);
662 gtk_tree_model_get (
663 model, &iter,
664 COL_OBJECT_CAMEL_STORE, &store,
665 COL_STRING_DISPLAY_NAME, &old_name,
666 COL_STRING_FULL_NAME, &old_full_name, -1);
668 if (old_name == NULL)
669 goto exit;
671 if (old_full_name == NULL)
672 goto exit;
674 if (g_strcmp0 (new_name, old_name) == 0)
675 goto exit;
677 /* Check for invalid characters. */
678 if (strchr (new_name, '/') != NULL) {
679 e_alert_run_dialog_for_args (
680 parent, "mail:no-rename-folder",
681 old_name, new_name,
682 _("Folder names cannot contain “/”"), NULL);
683 goto exit;
686 /* Build the new name from the old name. */
687 strv = g_strsplit_set (old_full_name, "/", 0);
688 index = g_strv_length (strv) - 1;
689 g_free (strv[index]);
690 strv[index] = g_strdup (new_name);
691 new_full_name = g_strjoinv ("/", strv);
692 g_strfreev (strv);
694 /* Check for duplicate folder name. */
695 /* FIXME camel_store_get_folder_info() may block. */
696 folder_info = camel_store_get_folder_info_sync (
697 store, new_full_name,
698 CAMEL_STORE_FOLDER_INFO_FAST, NULL, NULL);
699 if (folder_info != NULL) {
700 e_alert_run_dialog_for_args (
701 parent, "mail:no-rename-folder-exists",
702 old_name, new_name, NULL);
703 camel_folder_info_free (folder_info);
704 goto exit;
707 /* FIXME camel_store_rename_folder_sync() may block. */
708 camel_store_rename_folder_sync (
709 store, old_full_name, new_full_name, NULL, &local_error);
711 if (local_error != NULL) {
712 e_alert_run_dialog_for_args (
713 parent, "mail:no-rename-folder",
714 old_full_name, new_full_name,
715 local_error->message, NULL);
716 g_error_free (local_error);
717 goto exit;
720 folder_uri = e_mail_folder_uri_build (store, new_full_name);
721 em_folder_tree_set_selected (folder_tree, folder_uri, FALSE);
722 g_free (folder_uri);
724 exit:
725 g_free (old_name);
726 g_free (old_full_name);
727 g_free (new_full_name);
728 g_clear_object (&store);
731 static void
732 folder_tree_render_store_icon (GtkTreeViewColumn *column,
733 GtkCellRenderer *renderer,
734 GtkTreeModel *model,
735 GtkTreeIter *iter,
736 gpointer text_renderer)
738 GtkTreeIter parent;
739 gboolean expanded = TRUE, children_has_unread_mismatch = FALSE;
741 /* The first prerequisite: it's a root node and has children. */
742 if (gtk_tree_model_iter_parent (model, &parent, iter) ||
743 !gtk_tree_model_iter_has_child (model, iter)) {
744 g_object_set (renderer, "visible", FALSE, NULL);
745 return;
748 g_object_get (text_renderer, "is-expanded", &expanded, NULL);
750 /* The second prerequisite: it's not expanded and children has unread mismatch. */
751 if (!expanded) {
752 guint unread, unread_last_sel;
754 gtk_tree_model_get (model, iter,
755 COL_UINT_UNREAD, &unread,
756 COL_UINT_UNREAD_LAST_SEL, &unread_last_sel,
757 -1);
759 children_has_unread_mismatch = unread != unread_last_sel;
762 g_object_set (renderer, "visible", !expanded && children_has_unread_mismatch, NULL);
765 static void
766 folder_tree_reset_store_unread_value_cb (GtkTreeView *tree_view,
767 GtkTreeIter *iter,
768 GtkTreePath *path,
769 gpointer user_data)
771 GtkTreeIter parent;
772 GtkTreeModel *model;
774 g_return_if_fail (GTK_IS_TREE_VIEW (tree_view));
776 model = gtk_tree_view_get_model (tree_view);
777 if (!model)
778 return;
780 if (!gtk_tree_model_iter_parent (model, &parent, iter)) {
781 gtk_tree_store_set (GTK_TREE_STORE (model), iter,
782 COL_UINT_UNREAD_LAST_SEL, 0,
783 COL_UINT_UNREAD, 0,
784 -1);
788 static gboolean
789 subdirs_contain_unread (GtkTreeModel *model,
790 GtkTreeIter *root)
792 guint unread;
793 GtkTreeIter iter;
795 if (!gtk_tree_model_iter_children (model, &iter, root))
796 return FALSE;
798 do {
799 gtk_tree_model_get (model, &iter, COL_UINT_UNREAD, &unread, -1);
800 if (unread)
801 return TRUE;
803 if (gtk_tree_model_iter_has_child (model, &iter))
804 if (subdirs_contain_unread (model, &iter))
805 return TRUE;
806 } while (gtk_tree_model_iter_next (model, &iter));
808 return FALSE;
811 static void
812 folder_tree_render_display_name (GtkTreeViewColumn *column,
813 GtkCellRenderer *renderer,
814 GtkTreeModel *model,
815 GtkTreeIter *iter)
817 CamelService *service;
818 PangoWeight weight;
819 gboolean is_store, bold, subdirs_unread = FALSE;
820 gboolean editable;
821 guint unread;
822 gchar *name;
824 gtk_tree_model_get (
825 model, iter,
826 COL_STRING_DISPLAY_NAME, &name,
827 COL_OBJECT_CAMEL_STORE, &service,
828 COL_BOOL_IS_STORE, &is_store,
829 COL_UINT_UNREAD, &unread, -1);
831 g_object_get (renderer, "editable", &editable, NULL);
833 bold = is_store || unread;
835 if (gtk_tree_model_iter_has_child (model, iter)) {
836 gboolean expanded = TRUE;
838 g_object_get (renderer, "is-expanded", &expanded, NULL);
840 if (!bold || !expanded)
841 subdirs_unread = subdirs_contain_unread (model, iter);
844 bold = !editable && (bold || subdirs_unread);
845 weight = bold ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL;
846 g_object_set (renderer, "weight", weight, NULL);
848 if (is_store) {
849 const gchar *display_name;
851 display_name = camel_service_get_display_name (service);
852 g_object_set (renderer, "text", display_name, NULL);
854 } else if (!editable && unread > 0) {
855 gchar *name_and_unread;
857 name_and_unread = g_strdup_printf (
858 /* Translators: This is the string used for displaying the
859 * folder names in folder trees. The first "%s" will be
860 * replaced by the folder's name and "%u" will be replaced
861 * with the number of unread messages in the folder. The
862 * second %s will be replaced with a "+" letter for collapsed
863 * folders with unread messages in some subfolder too,
864 * or with an empty string for other cases.
866 * Most languages should translate this as "%s (%u%s)". The
867 * languages that use localized digits (like Persian) may
868 * need to replace "%u" with "%Iu". Right-to-left languages
869 * (like Arabic and Hebrew) may need to add bidirectional
870 * formatting codes to take care of the cases the folder
871 * name appears in either direction.
873 * Do not translate the "folder-display|" part. Remove it
874 * from your translation.
876 C_("folder-display", "%s (%u%s)"),
877 name, unread, subdirs_unread ? "+" : "");
878 g_object_set (renderer, "text", name_and_unread, NULL);
879 g_free (name_and_unread);
881 } else {
882 g_object_set (renderer, "text", name, NULL);
885 g_free (name);
886 g_clear_object (&service);
889 static void
890 folder_tree_render_icon (GtkTreeViewColumn *column,
891 GtkCellRenderer *renderer,
892 GtkTreeModel *model,
893 GtkTreeIter *iter)
895 GtkTreeSelection *selection;
896 GtkTreePath *drag_dest_row;
897 GtkWidget *tree_view;
898 GIcon *icon;
899 guint unread;
900 guint old_unread;
901 gchar *icon_name;
902 gboolean is_selected;
903 gboolean is_drafts = FALSE;
904 gboolean is_drag_dest = FALSE;
905 gboolean show_new_mail_emblem;
906 guint32 fi_flags = 0;
908 gtk_tree_model_get (
909 model, iter,
910 COL_STRING_ICON_NAME, &icon_name,
911 COL_UINT_UNREAD_LAST_SEL, &old_unread,
912 COL_UINT_UNREAD, &unread,
913 COL_BOOL_IS_DRAFT, &is_drafts,
914 COL_UINT_FLAGS, &fi_flags,
915 -1);
917 if (icon_name == NULL)
918 return;
920 tree_view = gtk_tree_view_column_get_tree_view (column);
921 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view));
922 is_selected = gtk_tree_selection_iter_is_selected (selection, iter);
924 gtk_tree_view_get_drag_dest_row (
925 GTK_TREE_VIEW (tree_view), &drag_dest_row, NULL);
926 if (drag_dest_row != NULL) {
927 GtkTreePath *path;
929 path = gtk_tree_model_get_path (model, iter);
930 if (gtk_tree_path_compare (path, drag_dest_row) == 0)
931 is_drag_dest = TRUE;
932 gtk_tree_path_free (path);
934 gtk_tree_path_free (drag_dest_row);
937 if (g_strcmp0 (icon_name, "folder") == 0) {
938 if (is_selected) {
939 g_free (icon_name);
940 icon_name = g_strdup ("folder-open");
941 } else if (is_drag_dest) {
942 g_free (icon_name);
943 icon_name = g_strdup ("folder-drag-accept");
947 icon = g_themed_icon_new (icon_name);
949 show_new_mail_emblem =
950 (unread > old_unread) &&
951 !is_selected && !is_drafts &&
952 ((fi_flags & CAMEL_FOLDER_VIRTUAL) == 0);
954 /* Show an emblem if there's new mail. */
955 if (show_new_mail_emblem) {
956 GIcon *temp_icon;
957 GEmblem *emblem;
959 temp_icon = g_themed_icon_new ("emblem-new");
960 emblem = g_emblem_new (temp_icon);
961 g_object_unref (temp_icon);
963 temp_icon = g_emblemed_icon_new (icon, emblem);
964 g_object_unref (emblem);
965 g_object_unref (icon);
967 icon = temp_icon;
970 g_object_set (renderer, "gicon", icon, NULL);
972 g_object_unref (icon);
973 g_free (icon_name);
976 static void
977 folder_tree_selection_changed_cb (EMFolderTree *folder_tree,
978 GtkTreeSelection *selection)
980 GtkTreeModel *model;
981 GtkTreeIter iter;
982 GList *list;
983 CamelStore *store = NULL;
984 CamelFolderInfoFlags flags = 0;
985 guint unread = 0;
986 guint old_unread = 0;
987 gchar *folder_name = NULL;
989 list = gtk_tree_selection_get_selected_rows (selection, &model);
991 if (list == NULL)
992 goto exit;
994 gtk_tree_model_get_iter (model, &iter, list->data);
996 gtk_tree_model_get (
997 model, &iter,
998 COL_OBJECT_CAMEL_STORE, &store,
999 COL_STRING_FULL_NAME, &folder_name,
1000 COL_UINT_FLAGS, &flags,
1001 COL_UINT_UNREAD, &unread,
1002 COL_UINT_UNREAD_LAST_SEL, &old_unread, -1);
1004 /* Sync unread counts to distinguish new incoming mail. */
1005 if (unread != old_unread) {
1006 gtk_tree_store_set (
1007 GTK_TREE_STORE (model), &iter,
1008 COL_UINT_UNREAD_LAST_SEL, unread, -1);
1011 exit:
1012 g_signal_emit (
1013 folder_tree, signals[FOLDER_SELECTED], 0,
1014 store, folder_name, flags);
1016 g_free (folder_name);
1017 g_clear_object (&store);
1019 g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
1020 g_list_free (list);
1023 static void
1024 folder_tree_copy_expanded_cb (GtkTreeView *unused,
1025 GtkTreePath *path,
1026 GtkTreeView *tree_view)
1028 gtk_tree_view_expand_row (tree_view, path, FALSE);
1031 static void
1032 folder_tree_copy_selection_cb (GtkTreeModel *model,
1033 GtkTreePath *path,
1034 GtkTreeIter *iter,
1035 GtkTreeView *tree_view)
1037 GtkTreeSelection *selection;
1039 selection = gtk_tree_view_get_selection (tree_view);
1040 gtk_tree_selection_select_path (selection, path);
1042 /* Center the tree view on the selected path. */
1043 gtk_tree_view_scroll_to_cell (tree_view, path, NULL, TRUE, 0.5, 0.0);
1046 static void
1047 folder_tree_copy_state (EMFolderTree *folder_tree)
1049 GtkTreeSelection *selection;
1050 GtkTreeView *tree_view;
1051 GtkTreeModel *model;
1053 tree_view = GTK_TREE_VIEW (folder_tree);
1054 model = gtk_tree_view_get_model (tree_view);
1056 selection = em_folder_tree_model_get_selection (
1057 EM_FOLDER_TREE_MODEL (model));
1058 if (selection == NULL)
1059 return;
1061 gtk_tree_view_map_expanded_rows (
1062 tree_view, (GtkTreeViewMappingFunc)
1063 folder_tree_copy_expanded_cb, folder_tree);
1065 gtk_tree_selection_selected_foreach (
1066 selection, (GtkTreeSelectionForeachFunc)
1067 folder_tree_copy_selection_cb, folder_tree);
1070 static void
1071 folder_tree_set_alert_sink (EMFolderTree *folder_tree,
1072 EAlertSink *alert_sink)
1074 g_return_if_fail (E_IS_ALERT_SINK (alert_sink));
1075 g_return_if_fail (folder_tree->priv->alert_sink == NULL);
1077 folder_tree->priv->alert_sink = g_object_ref (alert_sink);
1080 static void
1081 folder_tree_set_session (EMFolderTree *folder_tree,
1082 EMailSession *session)
1084 g_return_if_fail (E_IS_MAIL_SESSION (session));
1085 g_return_if_fail (folder_tree->priv->session == NULL);
1087 folder_tree->priv->session = g_object_ref (session);
1090 static GtkTargetList *
1091 folder_tree_get_copy_target_list (EMFolderTree *folder_tree)
1093 GtkTargetList *target_list = NULL;
1095 if (E_IS_SELECTABLE (folder_tree->priv->selectable)) {
1096 ESelectable *selectable;
1098 selectable = E_SELECTABLE (folder_tree->priv->selectable);
1099 target_list = e_selectable_get_copy_target_list (selectable);
1102 return target_list;
1105 static GtkTargetList *
1106 folder_tree_get_paste_target_list (EMFolderTree *folder_tree)
1108 GtkTargetList *target_list = NULL;
1110 if (E_IS_SELECTABLE (folder_tree->priv->selectable)) {
1111 ESelectable *selectable;
1113 selectable = E_SELECTABLE (folder_tree->priv->selectable);
1114 target_list = e_selectable_get_paste_target_list (selectable);
1117 return target_list;
1120 static void
1121 folder_tree_set_property (GObject *object,
1122 guint property_id,
1123 const GValue *value,
1124 GParamSpec *pspec)
1126 switch (property_id) {
1127 case PROP_ALERT_SINK:
1128 folder_tree_set_alert_sink (
1129 EM_FOLDER_TREE (object),
1130 g_value_get_object (value));
1131 return;
1133 case PROP_MODEL:
1134 gtk_tree_view_set_model (
1135 GTK_TREE_VIEW (object),
1136 g_value_get_object (value));
1137 return;
1139 case PROP_SESSION:
1140 folder_tree_set_session (
1141 EM_FOLDER_TREE (object),
1142 g_value_get_object (value));
1143 return;
1146 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1149 static void
1150 folder_tree_get_property (GObject *object,
1151 guint property_id,
1152 GValue *value,
1153 GParamSpec *pspec)
1155 switch (property_id) {
1156 case PROP_ALERT_SINK:
1157 g_value_set_object (
1158 value,
1159 em_folder_tree_get_alert_sink (
1160 EM_FOLDER_TREE (object)));
1161 return;
1163 case PROP_COPY_TARGET_LIST:
1164 g_value_set_boxed (
1165 value,
1166 folder_tree_get_copy_target_list (
1167 EM_FOLDER_TREE (object)));
1168 return;
1170 case PROP_MODEL:
1171 g_value_set_object (
1172 value,
1173 gtk_tree_view_get_model (
1174 GTK_TREE_VIEW (object)));
1175 return;
1177 case PROP_PASTE_TARGET_LIST:
1178 g_value_set_boxed (
1179 value,
1180 folder_tree_get_paste_target_list (
1181 EM_FOLDER_TREE (object)));
1182 return;
1184 case PROP_SESSION:
1185 g_value_set_object (
1186 value,
1187 em_folder_tree_get_session (
1188 EM_FOLDER_TREE (object)));
1189 return;
1192 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1195 static void
1196 folder_tree_dispose (GObject *object)
1198 EMFolderTreePrivate *priv;
1199 GtkTreeModel *model;
1200 GtkTreeSelection *selection;
1202 priv = EM_FOLDER_TREE_GET_PRIVATE (object);
1203 model = gtk_tree_view_get_model (GTK_TREE_VIEW (object));
1204 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (object));
1206 if (priv->loaded_row_id != 0) {
1207 g_signal_handler_disconnect (model, priv->loaded_row_id);
1208 priv->loaded_row_id = 0;
1211 if (priv->row_changed_id != 0) {
1212 g_signal_handler_disconnect (model, priv->row_changed_id);
1213 priv->row_changed_id = 0;
1216 if (priv->selection_changed_handler_id != 0) {
1217 g_signal_handler_disconnect (selection, priv->selection_changed_handler_id);
1218 priv->selection_changed_handler_id = 0;
1221 if (priv->autoscroll_id != 0) {
1222 g_source_remove (priv->autoscroll_id);
1223 priv->autoscroll_id = 0;
1226 if (priv->autoexpand_id != 0) {
1227 gtk_tree_row_reference_free (priv->autoexpand_row);
1228 priv->autoexpand_row = NULL;
1230 g_source_remove (priv->autoexpand_id);
1231 priv->autoexpand_id = 0;
1234 if (priv->alert_sink != NULL) {
1235 g_object_unref (priv->alert_sink);
1236 priv->alert_sink = NULL;
1239 if (priv->session != NULL) {
1240 g_object_unref (priv->session);
1241 priv->session = NULL;
1244 if (priv->text_renderer != NULL) {
1245 g_object_unref (priv->text_renderer);
1246 priv->text_renderer = NULL;
1249 /* Chain up to parent's dispose() method. */
1250 G_OBJECT_CLASS (em_folder_tree_parent_class)->dispose (object);
1253 static void
1254 folder_tree_finalize (GObject *object)
1256 EMFolderTreePrivate *priv;
1258 priv = EM_FOLDER_TREE_GET_PRIVATE (object);
1260 g_slist_free_full (
1261 priv->select_uris,
1262 (GDestroyNotify) folder_tree_free_select_uri);
1264 if (priv->select_uris_table != NULL)
1265 g_hash_table_destroy (priv->select_uris_table);
1267 g_free (priv->select_store_uid_when_added);
1269 /* Chain up to parent's finalize() method. */
1270 G_OBJECT_CLASS (em_folder_tree_parent_class)->finalize (object);
1273 static void
1274 folder_tree_constructed (GObject *object)
1276 EMFolderTreePrivate *priv;
1277 GtkTreeSelection *selection;
1278 GtkTreeViewColumn *column;
1279 GtkCellRenderer *renderer;
1280 GtkTreeView *tree_view;
1281 GtkTreeModel *model;
1282 gulong handler_id;
1284 priv = EM_FOLDER_TREE_GET_PRIVATE (object);
1286 /* Chain up to parent's constructed() method. */
1287 G_OBJECT_CLASS (em_folder_tree_parent_class)->constructed (object);
1289 tree_view = GTK_TREE_VIEW (object);
1290 model = gtk_tree_view_get_model (tree_view);
1291 selection = gtk_tree_view_get_selection (tree_view);
1293 handler_id = g_signal_connect (
1294 model, "loaded-row",
1295 G_CALLBACK (folder_tree_maybe_expand_row), object);
1296 priv->loaded_row_id = handler_id;
1298 /* Cannot attach to "row-inserted", because the row is inserted empty */
1299 handler_id = g_signal_connect (
1300 model, "row-changed",
1301 G_CALLBACK (folder_tree_row_changed_cb), object);
1302 priv->row_changed_id = handler_id;
1304 handler_id = g_signal_connect_swapped (
1305 selection, "changed",
1306 G_CALLBACK (folder_tree_selection_changed_cb), object);
1307 priv->selection_changed_handler_id = handler_id;
1309 column = gtk_tree_view_column_new ();
1310 gtk_tree_view_column_set_expand (column, TRUE);
1311 gtk_tree_view_column_set_sizing (
1312 column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
1313 gtk_tree_view_append_column (tree_view, column);
1315 renderer = gtk_cell_renderer_pixbuf_new ();
1316 gtk_tree_view_column_pack_start (column, renderer, FALSE);
1317 gtk_tree_view_column_add_attribute (
1318 column, renderer, "visible", COL_BOOL_IS_FOLDER);
1319 gtk_tree_view_column_set_cell_data_func (
1320 column, renderer, (GtkTreeCellDataFunc)
1321 folder_tree_render_icon, NULL, NULL);
1323 renderer = gtk_cell_renderer_pixbuf_new ();
1324 g_object_set (G_OBJECT (renderer), "icon-name", "mail-unread", NULL);
1325 gtk_tree_view_column_pack_start (column, renderer, FALSE);
1327 priv->text_renderer = g_object_ref (gtk_cell_renderer_text_new ());
1329 gtk_tree_view_column_set_cell_data_func (
1330 column, renderer, folder_tree_render_store_icon,
1331 g_object_ref (priv->text_renderer), g_object_unref);
1333 renderer = priv->text_renderer;
1334 g_object_set (renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1335 gtk_tree_view_column_pack_start (column, renderer, TRUE);
1336 gtk_tree_view_column_set_cell_data_func (
1337 column, renderer, (GtkTreeCellDataFunc)
1338 folder_tree_render_display_name, NULL, NULL);
1340 g_signal_connect_swapped (
1341 renderer, "edited",
1342 G_CALLBACK (folder_tree_cell_edited_cb), object);
1344 column = gtk_tree_view_column_new ();
1345 gtk_tree_view_append_column (tree_view, column);
1347 renderer = gtk_cell_renderer_pixbuf_new ();
1348 g_object_set (renderer, "xalign", 1.0, NULL);
1349 gtk_tree_view_column_pack_end (column, renderer, FALSE);
1350 gtk_tree_view_column_add_attribute (
1351 column, renderer, "gicon", COL_STATUS_ICON);
1352 gtk_tree_view_column_add_attribute (
1353 column, renderer, "visible", COL_STATUS_ICON_VISIBLE);
1355 renderer = gtk_cell_renderer_spinner_new ();
1356 g_object_set (renderer, "xalign", 1.0, NULL);
1357 gtk_tree_view_column_pack_end (column, renderer, FALSE);
1358 gtk_tree_view_column_add_attribute (
1359 column, renderer, "active", COL_BOOL_IS_STORE);
1360 gtk_tree_view_column_add_attribute (
1361 column, renderer, "pulse", COL_STATUS_SPINNER_PULSE);
1362 gtk_tree_view_column_add_attribute (
1363 column, renderer, "visible", COL_STATUS_SPINNER_VISIBLE);
1365 gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
1366 gtk_tree_selection_set_select_function (
1367 selection, (GtkTreeSelectionFunc)
1368 folder_tree_select_func, NULL, NULL);
1370 gtk_tree_view_set_headers_visible (tree_view, FALSE);
1371 gtk_tree_view_set_search_column (tree_view, COL_STRING_DISPLAY_NAME);
1373 folder_tree_copy_state (EM_FOLDER_TREE (object));
1374 gtk_widget_show (GTK_WIDGET (object));
1376 g_signal_connect (tree_view, "row-expanded",
1377 G_CALLBACK (folder_tree_reset_store_unread_value_cb), NULL);
1379 g_signal_connect (tree_view, "row-collapsed",
1380 G_CALLBACK (folder_tree_reset_store_unread_value_cb), NULL);
1383 static gboolean
1384 folder_tree_button_press_event (GtkWidget *widget,
1385 GdkEventButton *event)
1387 EMFolderTreePrivate *priv;
1388 GtkWidgetClass *widget_class;
1389 GtkTreeSelection *selection;
1390 GtkTreeView *tree_view;
1391 GtkTreePath *path;
1392 gulong handler_id;
1394 priv = EM_FOLDER_TREE_GET_PRIVATE (widget);
1396 tree_view = GTK_TREE_VIEW (widget);
1397 selection = gtk_tree_view_get_selection (tree_view);
1399 if (gtk_tree_selection_get_mode (selection) == GTK_SELECTION_SINGLE)
1400 folder_tree_clear_selected_list (EM_FOLDER_TREE (widget));
1402 priv->cursor_set = TRUE;
1404 if (event->button != 3)
1405 goto chainup;
1407 if (!gtk_tree_view_get_path_at_pos (
1408 tree_view, event->x, event->y,
1409 &path, NULL, NULL, NULL))
1410 goto chainup;
1412 /* Select and focus the row that was right-clicked, but prevent
1413 * a "folder-selected" signal emission since this does not count
1414 * as a folder selection in the sense we mean. */
1415 handler_id = priv->selection_changed_handler_id;
1416 g_signal_handler_block (selection, handler_id);
1417 gtk_tree_selection_select_path (selection, path);
1418 gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE);
1419 g_signal_handler_unblock (selection, handler_id);
1421 gtk_tree_path_free (path);
1423 folder_tree_emit_popup_event (
1424 EM_FOLDER_TREE (tree_view), (GdkEvent *) event);
1426 chainup:
1428 /* Chain up to parent's button_press_event() method. */
1429 widget_class = GTK_WIDGET_CLASS (em_folder_tree_parent_class);
1430 return widget_class->button_press_event (widget, event);
1433 static gboolean
1434 folder_tree_key_press_event (GtkWidget *widget,
1435 GdkEventKey *event)
1437 EMFolderTreePrivate *priv;
1438 GtkWidgetClass *widget_class;
1439 GtkTreeSelection *selection;
1440 GtkTreeView *tree_view;
1442 if (event && event->type == GDK_KEY_PRESS &&
1443 (event->keyval == GDK_KEY_space ||
1444 event->keyval == '.' ||
1445 event->keyval == ',' ||
1446 event->keyval == '[' ||
1447 event->keyval == ']')) {
1448 g_signal_emit (widget, signals[HIDDEN_KEY_EVENT], 0, event);
1450 return TRUE;
1453 priv = EM_FOLDER_TREE_GET_PRIVATE (widget);
1455 tree_view = GTK_TREE_VIEW (widget);
1456 selection = gtk_tree_view_get_selection (tree_view);
1458 if (gtk_tree_selection_get_mode (selection) == GTK_SELECTION_SINGLE)
1459 folder_tree_clear_selected_list (EM_FOLDER_TREE (widget));
1461 priv->cursor_set = TRUE;
1463 /* Chain up to parent's key_press_event() method. */
1464 widget_class = GTK_WIDGET_CLASS (em_folder_tree_parent_class);
1465 return widget_class->key_press_event (widget, event);
1468 static gboolean
1469 folder_tree_popup_menu (GtkWidget *widget)
1471 folder_tree_emit_popup_event (EM_FOLDER_TREE (widget), NULL);
1473 return TRUE;
1476 static void
1477 folder_tree_row_activated (GtkTreeView *tree_view,
1478 GtkTreePath *path,
1479 GtkTreeViewColumn *column)
1481 EMFolderTreePrivate *priv;
1482 GtkTreeModel *model;
1483 gchar *folder_name;
1484 GtkTreeIter iter;
1485 CamelStore *store;
1486 CamelFolderInfoFlags flags;
1488 priv = EM_FOLDER_TREE_GET_PRIVATE (tree_view);
1490 model = gtk_tree_view_get_model (tree_view);
1492 if (priv->skip_double_click)
1493 return;
1495 if (!gtk_tree_model_get_iter (model, &iter, path))
1496 return;
1498 gtk_tree_model_get (
1499 model, &iter,
1500 COL_OBJECT_CAMEL_STORE, &store,
1501 COL_STRING_FULL_NAME, &folder_name,
1502 COL_UINT_FLAGS, &flags, -1);
1504 folder_tree_clear_selected_list (EM_FOLDER_TREE (tree_view));
1506 g_signal_emit (
1507 tree_view, signals[FOLDER_SELECTED], 0,
1508 store, folder_name, flags);
1510 g_signal_emit (
1511 tree_view, signals[FOLDER_ACTIVATED], 0,
1512 store, folder_name);
1514 g_free (folder_name);
1515 g_clear_object (&store);
1518 static gboolean
1519 folder_tree_test_collapse_row (GtkTreeView *tree_view,
1520 GtkTreeIter *iter,
1521 GtkTreePath *path)
1523 GtkTreeSelection *selection;
1524 GtkTreeModel *model;
1525 GtkTreeIter cursor;
1527 selection = gtk_tree_view_get_selection (tree_view);
1529 if (!gtk_tree_selection_get_selected (selection, &model, &cursor))
1530 goto exit;
1532 /* Select the collapsed node IFF it is a
1533 * parent of the currently selected folder. */
1534 if (gtk_tree_store_is_ancestor (GTK_TREE_STORE (model), iter, &cursor))
1535 gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE);
1537 exit:
1538 return FALSE;
1541 static void
1542 folder_tree_row_expanded (GtkTreeView *tree_view,
1543 GtkTreeIter *iter,
1544 GtkTreePath *path)
1546 EActivity *activity;
1547 GCancellable *cancellable;
1548 EMFolderTree *folder_tree;
1549 AsyncContext *context;
1550 GtkTreeModel *model;
1551 CamelStore *store;
1552 gchar *full_name;
1553 gboolean load;
1555 folder_tree = EM_FOLDER_TREE (tree_view);
1556 model = gtk_tree_view_get_model (tree_view);
1558 gtk_tree_model_get (
1559 model, iter,
1560 COL_STRING_FULL_NAME, &full_name,
1561 COL_OBJECT_CAMEL_STORE, &store,
1562 COL_BOOL_LOAD_SUBDIRS, &load, -1);
1564 if (!load)
1565 goto exit;
1567 gtk_tree_store_set (
1568 GTK_TREE_STORE (model), iter,
1569 COL_BOOL_LOAD_SUBDIRS, FALSE, -1);
1571 /* Retrieve folder info asynchronously. */
1573 activity = em_folder_tree_new_activity (folder_tree);
1574 cancellable = e_activity_get_cancellable (activity);
1576 context = g_slice_new0 (AsyncContext);
1577 context->activity = activity;
1578 context->folder_tree = g_object_ref (folder_tree);
1579 context->root = gtk_tree_row_reference_new (model, path);
1580 context->full_name = g_strdup (full_name);
1582 camel_store_get_folder_info (
1583 store, full_name,
1584 CAMEL_STORE_FOLDER_INFO_FAST |
1585 CAMEL_STORE_FOLDER_INFO_RECURSIVE |
1586 CAMEL_STORE_FOLDER_INFO_SUBSCRIBED,
1587 G_PRIORITY_DEFAULT, cancellable,
1588 (GAsyncReadyCallback) folder_tree_get_folder_info_cb,
1589 context);
1591 exit:
1592 g_free (full_name);
1593 g_clear_object (&store);
1596 static void
1597 em_folder_tree_class_init (EMFolderTreeClass *class)
1599 GObjectClass *object_class;
1600 GtkWidgetClass *widget_class;
1601 GtkTreeViewClass *tree_view_class;
1603 g_type_class_add_private (class, sizeof (EMFolderTreePrivate));
1605 object_class = G_OBJECT_CLASS (class);
1606 object_class->set_property = folder_tree_set_property;
1607 object_class->get_property = folder_tree_get_property;
1608 object_class->dispose = folder_tree_dispose;
1609 object_class->finalize = folder_tree_finalize;
1610 object_class->constructed = folder_tree_constructed;
1612 widget_class = GTK_WIDGET_CLASS (class);
1613 widget_class->button_press_event = folder_tree_button_press_event;
1614 widget_class->key_press_event = folder_tree_key_press_event;
1615 widget_class->popup_menu = folder_tree_popup_menu;
1617 tree_view_class = GTK_TREE_VIEW_CLASS (class);
1618 tree_view_class->row_activated = folder_tree_row_activated;
1619 tree_view_class->test_collapse_row = folder_tree_test_collapse_row;
1620 tree_view_class->row_expanded = folder_tree_row_expanded;
1622 g_object_class_install_property (
1623 object_class,
1624 PROP_ALERT_SINK,
1625 g_param_spec_object (
1626 "alert-sink",
1627 NULL,
1628 NULL,
1629 E_TYPE_ALERT_SINK,
1630 G_PARAM_READWRITE |
1631 G_PARAM_CONSTRUCT_ONLY |
1632 G_PARAM_STATIC_STRINGS));
1634 /* Inherited from ESelectableInterface */
1635 g_object_class_override_property (
1636 object_class,
1637 PROP_COPY_TARGET_LIST,
1638 "copy-target-list");
1640 /* XXX We override the GtkTreeView:model property to add
1641 * G_PARAM_CONSTRUCT_ONLY so the model is set by the
1642 * time we get to folder_tree_constructed(). */
1643 g_object_class_install_property (
1644 object_class,
1645 PROP_MODEL,
1646 g_param_spec_object (
1647 "model",
1648 "TreeView Model",
1649 "The model for the tree view",
1650 GTK_TYPE_TREE_MODEL,
1651 G_PARAM_READWRITE |
1652 G_PARAM_CONSTRUCT_ONLY));
1654 /* Inherited from ESelectableInterface */
1655 g_object_class_override_property (
1656 object_class,
1657 PROP_PASTE_TARGET_LIST,
1658 "paste-target-list");
1660 g_object_class_install_property (
1661 object_class,
1662 PROP_SESSION,
1663 g_param_spec_object (
1664 "session",
1665 NULL,
1666 NULL,
1667 E_TYPE_MAIL_SESSION,
1668 G_PARAM_READWRITE |
1669 G_PARAM_CONSTRUCT_ONLY |
1670 G_PARAM_STATIC_STRINGS));
1672 signals[FOLDER_SELECTED] = g_signal_new (
1673 "folder-selected",
1674 G_OBJECT_CLASS_TYPE (object_class),
1675 G_SIGNAL_RUN_FIRST,
1676 G_STRUCT_OFFSET (EMFolderTreeClass, folder_selected),
1677 NULL, NULL,
1678 e_marshal_VOID__OBJECT_STRING_UINT,
1679 G_TYPE_NONE, 3,
1680 CAMEL_TYPE_STORE,
1681 G_TYPE_STRING,
1682 G_TYPE_UINT);
1684 signals[FOLDER_ACTIVATED] = g_signal_new (
1685 "folder-activated",
1686 G_OBJECT_CLASS_TYPE (object_class),
1687 G_SIGNAL_RUN_FIRST,
1688 G_STRUCT_OFFSET (EMFolderTreeClass, folder_activated),
1689 NULL, NULL,
1690 e_marshal_VOID__OBJECT_STRING,
1691 G_TYPE_NONE, 2,
1692 CAMEL_TYPE_STORE,
1693 G_TYPE_STRING);
1695 signals[POPUP_EVENT] = g_signal_new (
1696 "popup-event",
1697 G_OBJECT_CLASS_TYPE (object_class),
1698 G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
1699 G_STRUCT_OFFSET (EMFolderTreeClass, popup_event),
1700 NULL, NULL,
1701 g_cclosure_marshal_VOID__BOXED,
1702 G_TYPE_NONE, 1,
1703 GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
1705 signals[HIDDEN_KEY_EVENT] = g_signal_new (
1706 "hidden-key-event",
1707 G_OBJECT_CLASS_TYPE (object_class),
1708 G_SIGNAL_RUN_LAST,
1709 G_STRUCT_OFFSET (EMFolderTreeClass, hidden_key_event),
1710 NULL, NULL,
1711 g_cclosure_marshal_VOID__BOXED,
1712 G_TYPE_NONE, 1,
1713 GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
1716 static void
1717 em_folder_tree_init (EMFolderTree *folder_tree)
1719 GHashTable *select_uris_table;
1720 AtkObject *a11y;
1722 select_uris_table = g_hash_table_new (g_str_hash, g_str_equal);
1724 folder_tree->priv = EM_FOLDER_TREE_GET_PRIVATE (folder_tree);
1725 folder_tree->priv->select_uris_table = select_uris_table;
1727 /* FIXME Gross hack. */
1728 gtk_widget_set_can_focus (GTK_WIDGET (folder_tree), TRUE);
1730 a11y = gtk_widget_get_accessible (GTK_WIDGET (folder_tree));
1731 atk_object_set_name (a11y, _("Mail Folder Tree"));
1734 /* Sets a selectable widget, which will be used for update-actions and
1735 * select-all selectable interface functions. This can be NULL, then nothing
1736 * can be selected and calling selectable function does nothing. */
1737 void
1738 em_folder_tree_set_selectable_widget (EMFolderTree *folder_tree,
1739 GtkWidget *selectable)
1741 g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree));
1743 if (selectable != NULL)
1744 g_return_if_fail (E_IS_SELECTABLE (selectable));
1746 folder_tree->priv->selectable = selectable;
1749 static void
1750 folder_tree_selectable_update_actions (ESelectable *selectable,
1751 EFocusTracker *focus_tracker,
1752 GdkAtom *clipboard_targets,
1753 gint n_clipboard_targets)
1755 EMFolderTree *folder_tree;
1757 folder_tree = EM_FOLDER_TREE (selectable);
1758 g_return_if_fail (folder_tree != NULL);
1760 if (folder_tree->priv->selectable != NULL) {
1761 ESelectableInterface *iface;
1762 ESelectable *selectable;
1764 selectable = E_SELECTABLE (folder_tree->priv->selectable);
1765 iface = E_SELECTABLE_GET_INTERFACE (selectable);
1766 g_return_if_fail (iface->update_actions != NULL);
1768 iface->update_actions (
1769 selectable, focus_tracker,
1770 clipboard_targets, n_clipboard_targets);
1774 static void
1775 folder_tree_selectable_cut_clipboard (ESelectable *selectable)
1777 ESelectableInterface *iface;
1778 EMFolderTree *folder_tree;
1779 GtkWidget *proxy;
1781 folder_tree = EM_FOLDER_TREE (selectable);
1782 proxy = folder_tree->priv->selectable;
1784 if (!E_IS_SELECTABLE (proxy))
1785 return;
1787 iface = E_SELECTABLE_GET_INTERFACE (proxy);
1789 if (iface->cut_clipboard == NULL)
1790 return;
1792 if (gtk_widget_get_can_focus (proxy))
1793 gtk_widget_grab_focus (proxy);
1795 iface->cut_clipboard (E_SELECTABLE (proxy));
1798 static void
1799 folder_tree_selectable_copy_clipboard (ESelectable *selectable)
1801 ESelectableInterface *iface;
1802 EMFolderTree *folder_tree;
1803 GtkWidget *proxy;
1805 folder_tree = EM_FOLDER_TREE (selectable);
1806 proxy = folder_tree->priv->selectable;
1808 if (!E_IS_SELECTABLE (proxy))
1809 return;
1811 iface = E_SELECTABLE_GET_INTERFACE (proxy);
1813 if (iface->copy_clipboard == NULL)
1814 return;
1816 if (gtk_widget_get_can_focus (proxy))
1817 gtk_widget_grab_focus (proxy);
1819 iface->copy_clipboard (E_SELECTABLE (proxy));
1822 static void
1823 folder_tree_selectable_paste_clipboard (ESelectable *selectable)
1825 ESelectableInterface *iface;
1826 EMFolderTree *folder_tree;
1827 GtkWidget *proxy;
1829 folder_tree = EM_FOLDER_TREE (selectable);
1830 proxy = folder_tree->priv->selectable;
1832 if (!E_IS_SELECTABLE (proxy))
1833 return;
1835 iface = E_SELECTABLE_GET_INTERFACE (proxy);
1837 if (iface->paste_clipboard == NULL)
1838 return;
1840 if (gtk_widget_get_can_focus (proxy))
1841 gtk_widget_grab_focus (proxy);
1843 iface->paste_clipboard (E_SELECTABLE (proxy));
1846 static void
1847 folder_tree_selectable_delete_selection (ESelectable *selectable)
1849 ESelectableInterface *iface;
1850 EMFolderTree *folder_tree;
1851 GtkWidget *proxy;
1853 folder_tree = EM_FOLDER_TREE (selectable);
1854 proxy = folder_tree->priv->selectable;
1856 if (!E_IS_SELECTABLE (proxy))
1857 return;
1859 iface = E_SELECTABLE_GET_INTERFACE (proxy);
1861 if (iface->delete_selection == NULL)
1862 return;
1864 if (gtk_widget_get_can_focus (proxy))
1865 gtk_widget_grab_focus (proxy);
1867 iface->delete_selection (E_SELECTABLE (proxy));
1870 static void
1871 folder_tree_selectable_select_all (ESelectable *selectable)
1873 ESelectableInterface *iface;
1874 EMFolderTree *folder_tree;
1875 GtkWidget *proxy;
1877 folder_tree = EM_FOLDER_TREE (selectable);
1878 proxy = folder_tree->priv->selectable;
1880 if (!E_IS_SELECTABLE (proxy))
1881 return;
1883 iface = E_SELECTABLE_GET_INTERFACE (proxy);
1885 if (iface->select_all == NULL)
1886 return;
1888 if (gtk_widget_get_can_focus (proxy))
1889 gtk_widget_grab_focus (proxy);
1891 iface->select_all (E_SELECTABLE (proxy));
1894 static void
1895 em_folder_tree_selectable_init (ESelectableInterface *iface)
1897 iface->update_actions = folder_tree_selectable_update_actions;
1898 iface->cut_clipboard = folder_tree_selectable_cut_clipboard;
1899 iface->copy_clipboard = folder_tree_selectable_copy_clipboard;
1900 iface->paste_clipboard = folder_tree_selectable_paste_clipboard;
1901 iface->delete_selection = folder_tree_selectable_delete_selection;
1902 iface->select_all = folder_tree_selectable_select_all;
1905 GtkWidget *
1906 em_folder_tree_new (EMailSession *session,
1907 EAlertSink *alert_sink)
1909 EMFolderTreeModel *model;
1911 g_return_val_if_fail (E_IS_MAIL_SESSION (session), NULL);
1912 g_return_val_if_fail (E_IS_ALERT_SINK (alert_sink), NULL);
1914 model = em_folder_tree_model_get_default ();
1916 return em_folder_tree_new_with_model (session, alert_sink, model);
1919 GtkWidget *
1920 em_folder_tree_new_with_model (EMailSession *session,
1921 EAlertSink *alert_sink,
1922 EMFolderTreeModel *model)
1924 g_return_val_if_fail (E_IS_MAIL_SESSION (session), NULL);
1925 g_return_val_if_fail (E_IS_ALERT_SINK (alert_sink), NULL);
1926 g_return_val_if_fail (EM_IS_FOLDER_TREE_MODEL (model), NULL);
1928 return g_object_new (
1929 EM_TYPE_FOLDER_TREE,
1930 "alert-sink", alert_sink,
1931 "session", session,
1932 "model", model, NULL);
1935 EActivity *
1936 em_folder_tree_new_activity (EMFolderTree *folder_tree)
1938 EActivity *activity;
1939 EMailSession *session;
1940 EAlertSink *alert_sink;
1941 GCancellable *cancellable;
1943 g_return_val_if_fail (EM_IS_FOLDER_TREE (folder_tree), NULL);
1945 activity = e_activity_new ();
1947 alert_sink = em_folder_tree_get_alert_sink (folder_tree);
1948 e_activity_set_alert_sink (activity, alert_sink);
1950 cancellable = camel_operation_new ();
1951 e_activity_set_cancellable (activity, cancellable);
1952 g_object_unref (cancellable);
1954 session = em_folder_tree_get_session (folder_tree);
1955 e_mail_ui_session_add_activity (
1956 E_MAIL_UI_SESSION (session), activity);
1958 return activity;
1961 EAlertSink *
1962 em_folder_tree_get_alert_sink (EMFolderTree *folder_tree)
1964 g_return_val_if_fail (EM_IS_FOLDER_TREE (folder_tree), NULL);
1966 return folder_tree->priv->alert_sink;
1969 EMailSession *
1970 em_folder_tree_get_session (EMFolderTree *folder_tree)
1972 g_return_val_if_fail (EM_IS_FOLDER_TREE (folder_tree), NULL);
1974 return folder_tree->priv->session;
1977 static void
1978 tree_drag_begin (GtkWidget *widget,
1979 GdkDragContext *context,
1980 EMFolderTree *folder_tree)
1982 EMFolderTreePrivate *priv = folder_tree->priv;
1983 GtkTreeSelection *selection;
1984 GtkTreeView *tree_view;
1985 cairo_surface_t *s;
1986 GtkTreeModel *model;
1987 GtkTreePath *path;
1988 GtkTreeIter iter;
1990 tree_view = GTK_TREE_VIEW (widget);
1991 selection = gtk_tree_view_get_selection (tree_view);
1992 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1993 return;
1995 path = gtk_tree_model_get_path (model, &iter);
1996 priv->drag_row = gtk_tree_row_reference_new (model, path);
1998 s = gtk_tree_view_create_row_drag_icon (tree_view, path);
1999 gtk_drag_set_icon_surface (context, s);
2001 gtk_tree_path_free (path);
2004 static void
2005 tree_drag_data_get (GtkWidget *widget,
2006 GdkDragContext *context,
2007 GtkSelectionData *selection,
2008 guint info,
2009 guint time,
2010 EMFolderTree *folder_tree)
2012 EMFolderTreePrivate *priv = folder_tree->priv;
2013 GtkTreeModel *model;
2014 GtkTreePath *src_path;
2015 CamelFolder *folder;
2016 CamelStore *store = NULL;
2017 GtkTreeIter iter;
2018 gchar *folder_name = NULL;
2019 gchar *folder_uri;
2021 if (!priv->drag_row || !(src_path =
2022 gtk_tree_row_reference_get_path (priv->drag_row)))
2023 return;
2025 model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_tree));
2027 if (!gtk_tree_model_get_iter (model, &iter, src_path))
2028 goto fail;
2030 gtk_tree_model_get (
2031 model, &iter,
2032 COL_OBJECT_CAMEL_STORE, &store,
2033 COL_STRING_FULL_NAME, &folder_name, -1);
2035 /* make sure user isn't trying to drag on a placeholder row */
2036 if (folder_name == NULL)
2037 goto fail;
2039 folder_uri = e_mail_folder_uri_build (store, folder_name);
2041 switch (info) {
2042 case DND_DRAG_TYPE_FOLDER:
2043 /* dragging to a new location in the folder tree */
2044 gtk_selection_data_set (
2045 selection, drag_atoms[info], 8,
2046 (guchar *) folder_uri, strlen (folder_uri) + 1);
2047 break;
2048 case DND_DRAG_TYPE_TEXT_URI_LIST:
2049 /* dragging to nautilus or something, probably */
2050 /* FIXME camel_store_get_folder_sync() may block. */
2051 if ((folder = camel_store_get_folder_sync (
2052 store, folder_name, 0, NULL, NULL))) {
2054 GPtrArray *uids = camel_folder_get_uids (folder);
2056 em_utils_selection_set_urilist (selection, folder, uids);
2057 camel_folder_free_uids (folder, uids);
2058 g_object_unref (folder);
2060 break;
2061 default:
2062 abort ();
2065 g_free (folder_uri);
2067 fail:
2068 gtk_tree_path_free (src_path);
2069 g_clear_object (&store);
2070 g_free (folder_name);
2073 static gboolean
2074 ask_drop_folder (EMFolderTree *folder_tree,
2075 const gchar *src_folder_uri,
2076 const gchar *des_full_name,
2077 CamelStore *des_store,
2078 gboolean is_move)
2080 const gchar *key = is_move ? "prompt-on-folder-drop-move" : "prompt-on-folder-drop-copy";
2081 const gchar *src_display_name, *des_display_name;
2082 EMailSession *session;
2083 GSettings *settings;
2084 gchar *set_value, *src_folder_name = NULL;
2085 gchar *src_folder = NULL, *des_folder = NULL;
2086 CamelProvider *src_provider, *des_provider;
2087 CamelStore *src_store = NULL;
2088 GError *error = NULL;
2089 GtkWidget *widget;
2090 GtkWindow *parent;
2091 gint response;
2092 gboolean src_store_is_local, des_store_is_local, session_is_online;
2094 g_return_val_if_fail (folder_tree != NULL, FALSE);
2095 g_return_val_if_fail (src_folder_uri != NULL, FALSE);
2096 g_return_val_if_fail (des_full_name != NULL || des_store != NULL, FALSE);
2098 settings = e_util_ref_settings ("org.gnome.evolution.mail");
2099 set_value = g_settings_get_string (settings, key);
2101 if (g_strcmp0 (set_value, "never") == 0) {
2102 g_object_unref (settings);
2103 g_free (set_value);
2105 return FALSE;
2106 } else if (g_strcmp0 (set_value, "always") == 0) {
2107 g_object_unref (settings);
2108 g_free (set_value);
2110 return TRUE;
2113 g_free (set_value);
2115 session = em_folder_tree_get_session (folder_tree);
2117 e_mail_folder_uri_parse (
2118 CAMEL_SESSION (session),
2119 src_folder_uri, &src_store, &src_folder_name, &error);
2121 if (error != NULL) {
2122 g_warning (
2123 "%s: Failed to convert '%s' to folder name: %s",
2124 G_STRFUNC, src_folder_uri, error->message);
2125 g_object_unref (settings);
2126 g_error_free (error);
2128 return FALSE;
2131 session_is_online = camel_session_get_online (CAMEL_SESSION (session));
2133 src_provider = camel_service_get_provider (CAMEL_SERVICE (src_store));
2134 src_store_is_local = (src_provider->flags & CAMEL_PROVIDER_IS_LOCAL) != 0;
2135 src_display_name = camel_service_get_display_name (CAMEL_SERVICE (src_store));
2136 src_folder = g_strdup_printf ("%s: %s", src_display_name, src_folder_name);
2138 des_provider = camel_service_get_provider (CAMEL_SERVICE (des_store));
2139 des_store_is_local = (des_provider->flags & CAMEL_PROVIDER_IS_LOCAL) != 0;
2140 des_display_name = camel_service_get_display_name (CAMEL_SERVICE (des_store));
2141 des_folder = g_strdup_printf ("%s: %s", des_display_name, des_full_name);
2143 if (!session_is_online && (!src_store_is_local || !des_store_is_local)) {
2144 EAlertSink *alert_sink;
2146 alert_sink = em_folder_tree_get_alert_sink (folder_tree);
2147 e_alert_submit (
2148 alert_sink,
2149 "mail:online-operation",
2150 src_store_is_local ? des_folder : src_folder,
2151 NULL);
2152 g_free (src_folder_name);
2153 g_free (src_folder);
2154 g_free (des_folder);
2155 g_object_unref (src_store);
2156 g_object_unref (settings);
2158 return FALSE;
2161 parent = NULL;
2162 widget = gtk_widget_get_toplevel (GTK_WIDGET (folder_tree));
2163 if (widget && gtk_widget_is_toplevel (widget) && GTK_IS_WINDOW (widget))
2164 parent = GTK_WINDOW (widget);
2166 widget = e_alert_dialog_new_for_args (
2167 parent,
2168 is_move ? "mail:ask-folder-drop-move" : "mail:ask-folder-drop-copy",
2169 src_folder_name,
2170 des_full_name && *des_full_name ? des_folder :
2171 camel_service_get_display_name (CAMEL_SERVICE (des_store)),
2172 NULL);
2173 response = gtk_dialog_run (GTK_DIALOG (widget));
2174 gtk_widget_destroy (widget);
2176 if (response == GTK_RESPONSE_OK)
2177 g_settings_set_string (settings, key, "always");
2178 else if (response == GTK_RESPONSE_CANCEL)
2179 g_settings_set_string (settings, key, "never");
2181 g_free (src_folder_name);
2182 g_free (src_folder);
2183 g_free (des_folder);
2184 g_object_unref (src_store);
2185 g_object_unref (settings);
2187 return response == GTK_RESPONSE_YES || response == GTK_RESPONSE_OK;
2190 /* Drop handling */
2191 struct _DragDataReceivedAsync {
2192 MailMsg base;
2194 /* input data */
2195 GdkDragContext *context;
2197 /* Only selection->data and selection->length are valid */
2198 GtkSelectionData *selection;
2200 EMFolderTree *folder_tree;
2201 EMailSession *session;
2202 CamelStore *store;
2203 gchar *full_name;
2204 gchar *dest_folder_uri;
2205 guint32 action;
2206 guint info;
2208 guint move : 1;
2209 guint moved : 1;
2210 guint aborted : 1;
2213 static void
2214 folder_tree_drop_folder (struct _DragDataReceivedAsync *m)
2216 CamelFolder *folder;
2217 CamelStore *parent_store;
2218 GCancellable *cancellable;
2219 const gchar *folder_name;
2220 const gchar *full_name;
2221 const guchar *data;
2223 data = gtk_selection_data_get_data (m->selection);
2225 d (printf (" * Drop folder '%s' onto '%s'\n", data, m->full_name));
2227 cancellable = m->base.cancellable;
2229 folder = e_mail_session_uri_to_folder_sync (
2230 m->session, (gchar *) data, 0,
2231 cancellable, &m->base.error);
2232 if (folder == NULL)
2233 return;
2235 full_name = camel_folder_get_full_name (folder);
2236 parent_store = camel_folder_get_parent_store (folder);
2238 em_folder_utils_copy_folders (
2239 parent_store, full_name, m->store,
2240 m->full_name ? m->full_name : "", m->move);
2242 folder_name = strrchr (full_name, '/');
2243 if (folder_name)
2244 folder_name++;
2245 else
2246 folder_name = full_name;
2248 if (m->full_name) {
2249 gchar *dest_root_name;
2251 dest_root_name = g_strconcat (m->full_name, "/", folder_name, NULL);
2252 m->dest_folder_uri = e_mail_folder_uri_build (m->store, dest_root_name);
2253 g_free (dest_root_name);
2254 } else {
2255 m->dest_folder_uri = e_mail_folder_uri_build (m->store, folder_name);
2258 g_object_unref (folder);
2261 static gchar *
2262 folder_tree_drop_async__desc (struct _DragDataReceivedAsync *m)
2264 const guchar *data;
2266 data = gtk_selection_data_get_data (m->selection);
2268 if (m->info == DND_DROP_TYPE_FOLDER) {
2269 gchar *folder_name = NULL;
2270 gchar *res;
2272 e_mail_folder_uri_parse (
2273 CAMEL_SESSION (m->session),
2274 (gchar *) data, NULL, &folder_name, NULL);
2275 g_return_val_if_fail (folder_name != NULL, NULL);
2277 if (m->move)
2278 res = g_strdup_printf (
2279 _("Moving folder %s"), folder_name);
2280 else
2281 res = g_strdup_printf (
2282 _("Copying folder %s"), folder_name);
2283 g_free (folder_name);
2285 return res;
2286 } else {
2287 if (m->move)
2288 return g_strdup_printf (
2289 _("Moving messages into folder %s"),
2290 m->full_name);
2291 else
2292 return g_strdup_printf (
2293 _("Copying messages into folder %s"),
2294 m->full_name);
2298 static void
2299 folder_tree_drop_async__exec (struct _DragDataReceivedAsync *m,
2300 GCancellable *cancellable,
2301 GError **error)
2303 CamelFolder *folder;
2305 /* for types other than folder, we can't drop to the root path */
2306 if (m->info == DND_DROP_TYPE_FOLDER) {
2307 /* copy or move (aka rename) a folder */
2308 folder_tree_drop_folder (m);
2309 } else if (m->full_name == NULL) {
2310 g_set_error (
2311 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
2312 _("Cannot drop message(s) into toplevel store"));
2313 } else if ((folder = camel_store_get_folder_sync (
2314 m->store, m->full_name, 0, cancellable, error))) {
2316 switch (m->info) {
2317 case DND_DROP_TYPE_UID_LIST:
2318 /* import a list of uids from another evo folder */
2319 em_utils_selection_get_uidlist (
2320 m->selection, m->session, folder, m->move,
2321 cancellable, error);
2322 m->moved = m->move && (!error || !*error);
2323 break;
2324 case DND_DROP_TYPE_MESSAGE_RFC822:
2325 /* import a message/rfc822 stream */
2326 em_utils_selection_get_message (m->selection, folder);
2327 break;
2328 case DND_DROP_TYPE_TEXT_URI_LIST:
2329 /* import an mbox, maildir, or mh folder? */
2330 em_utils_selection_get_urilist (m->selection, folder);
2331 break;
2332 default:
2333 abort ();
2335 g_object_unref (folder);
2339 static void
2340 folder_tree_drop_async__free (struct _DragDataReceivedAsync *m)
2342 if (m->move && m->dest_folder_uri) {
2343 GList *selected_list;
2345 selected_list = g_list_append (NULL, m->dest_folder_uri);
2346 em_folder_tree_set_selected_list (m->folder_tree, selected_list, FALSE);
2347 g_list_free (selected_list);
2350 g_object_unref (m->folder_tree);
2351 g_object_unref (m->session);
2352 g_object_unref (m->context);
2353 g_object_unref (m->store);
2354 g_free (m->full_name);
2355 g_free (m->dest_folder_uri);
2356 gtk_selection_data_free (m->selection);
2359 static MailMsgInfo folder_tree_drop_async_info = {
2360 sizeof (struct _DragDataReceivedAsync),
2361 (MailMsgDescFunc) folder_tree_drop_async__desc,
2362 (MailMsgExecFunc) folder_tree_drop_async__exec,
2363 (MailMsgDoneFunc) NULL,
2364 (MailMsgFreeFunc) folder_tree_drop_async__free
2367 static void
2368 tree_drag_data_action (struct _DragDataReceivedAsync *m)
2370 m->move = m->action == GDK_ACTION_MOVE;
2371 mail_msg_unordered_push (m);
2374 static void
2375 tree_drag_data_received (GtkWidget *widget,
2376 GdkDragContext *context,
2377 gint x,
2378 gint y,
2379 GtkSelectionData *selection,
2380 guint info,
2381 guint time,
2382 EMFolderTree *folder_tree)
2384 GtkTreeViewDropPosition pos;
2385 GtkTreeModel *model;
2386 GtkTreeView *tree_view;
2387 GtkTreePath *dest_path = NULL;
2388 EMailSession *session;
2389 struct _DragDataReceivedAsync *m;
2390 gboolean is_store;
2391 CamelStore *store;
2392 GtkTreeIter iter;
2393 gchar *full_name;
2395 tree_view = GTK_TREE_VIEW (folder_tree);
2396 model = gtk_tree_view_get_model (tree_view);
2398 session = em_folder_tree_get_session (folder_tree);
2400 if (!gtk_tree_view_get_dest_row_at_pos (tree_view, x, y, &dest_path, &pos))
2401 return;
2403 /* this means we are receiving no data */
2404 if (gtk_selection_data_get_data (selection) == NULL) {
2405 gtk_drag_finish (context, FALSE, FALSE, GDK_CURRENT_TIME);
2406 gtk_tree_path_free (dest_path);
2407 return;
2410 if (gtk_selection_data_get_length (selection) == -1) {
2411 gtk_drag_finish (context, FALSE, FALSE, GDK_CURRENT_TIME);
2412 gtk_tree_path_free (dest_path);
2413 return;
2416 if (!gtk_tree_model_get_iter (model, &iter, dest_path)) {
2417 gtk_drag_finish (context, FALSE, FALSE, GDK_CURRENT_TIME);
2418 gtk_tree_path_free (dest_path);
2419 return;
2422 gtk_tree_model_get (
2423 model, &iter,
2424 COL_OBJECT_CAMEL_STORE, &store,
2425 COL_BOOL_IS_STORE, &is_store,
2426 COL_STRING_FULL_NAME, &full_name, -1);
2428 /* make sure user isn't try to drop on a placeholder row */
2429 if (full_name == NULL && !is_store) {
2430 gtk_drag_finish (context, FALSE, FALSE, GDK_CURRENT_TIME);
2431 gtk_tree_path_free (dest_path);
2432 g_clear_object (&store);
2433 return;
2436 if (info == DND_DROP_TYPE_FOLDER &&
2437 !ask_drop_folder (folder_tree,
2438 (const gchar *) gtk_selection_data_get_data (selection),
2439 full_name, store,
2440 gdk_drag_context_get_selected_action (context) == GDK_ACTION_MOVE)) {
2441 gtk_drag_finish (context, FALSE, FALSE, GDK_CURRENT_TIME);
2442 gtk_tree_path_free (dest_path);
2443 g_clear_object (&store);
2444 g_free (full_name);
2445 return;
2448 m = mail_msg_new (&folder_tree_drop_async_info);
2449 m->folder_tree = g_object_ref (folder_tree);
2450 m->session = g_object_ref (session);
2451 m->context = g_object_ref (context);
2452 m->store = g_object_ref (store);
2453 m->full_name = full_name;
2454 m->dest_folder_uri = NULL;
2455 m->action = gdk_drag_context_get_selected_action (context);
2456 m->info = info;
2458 /* need to copy, goes away once we exit */
2459 m->selection = gtk_selection_data_copy (selection);
2461 tree_drag_data_action (m);
2462 gtk_tree_path_free (dest_path);
2463 g_clear_object (&store);
2466 static gboolean
2467 is_special_local_folder (const gchar *name)
2469 return strcmp (name, "Drafts") == 0
2470 || strcmp (name, "Inbox") == 0
2471 || strcmp (name, "Outbox") == 0
2472 || strcmp (name, "Sent") == 0
2473 || strcmp (name, "Templates") == 0;
2476 static GdkAtom
2477 folder_tree_drop_target (EMFolderTree *folder_tree,
2478 GdkDragContext *context,
2479 GtkTreePath *path,
2480 GdkDragAction *actions,
2481 GdkDragAction *suggested_action)
2483 EMFolderTreePrivate *p = folder_tree->priv;
2484 gchar *dst_full_name = NULL;
2485 gchar *src_full_name = NULL;
2486 CamelStore *dst_store = NULL;
2487 CamelStore *src_store = NULL;
2488 GdkAtom atom = GDK_NONE;
2489 gboolean is_store;
2490 GtkTreeModel *model;
2491 GtkTreeIter iter;
2492 GList *targets;
2493 const gchar *uid;
2494 gboolean src_is_local;
2495 gboolean src_is_vfolder;
2496 gboolean dst_is_vfolder;
2497 guint32 flags = 0;
2499 /* This is a bit of a mess, but should handle all the cases properly */
2501 model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_tree));
2503 if (!gtk_tree_model_get_iter (model, &iter, path))
2504 return GDK_NONE;
2506 /* We may override these further down. */
2507 *actions = gdk_drag_context_get_actions (context);
2508 *suggested_action = gdk_drag_context_get_suggested_action (context);
2510 gtk_tree_model_get (
2511 model, &iter,
2512 COL_BOOL_IS_STORE, &is_store,
2513 COL_OBJECT_CAMEL_STORE, &dst_store,
2514 COL_STRING_FULL_NAME, &dst_full_name,
2515 COL_UINT_FLAGS, &flags, -1);
2517 uid = camel_service_get_uid (CAMEL_SERVICE (dst_store));
2518 dst_is_vfolder = (g_strcmp0 (uid, E_MAIL_SESSION_VFOLDER_UID) == 0);
2520 targets = gdk_drag_context_list_targets (context);
2522 /* Check for special destinations */
2524 /* Don't allow copying/moving into the UNMATCHED vfolder. */
2525 if (dst_is_vfolder)
2526 if (g_strcmp0 (dst_full_name, CAMEL_UNMATCHED_NAME) == 0)
2527 goto done;
2529 /* Don't allow copying/moving into a vTrash folder. */
2530 if (g_strcmp0 (dst_full_name, CAMEL_VTRASH_NAME) == 0)
2531 goto done;
2533 /* Don't allow copying/moving into a vJunk folder. */
2534 if (g_strcmp0 (dst_full_name, CAMEL_VJUNK_NAME) == 0)
2535 goto done;
2537 if (flags & CAMEL_FOLDER_NOSELECT)
2538 goto done;
2540 if (p->drag_row) {
2541 GtkTreePath *src_path = gtk_tree_row_reference_get_path (p->drag_row);
2543 if (src_path) {
2544 guint32 src_flags = 0;
2546 if (gtk_tree_model_get_iter (model, &iter, src_path))
2547 gtk_tree_model_get (
2548 model, &iter,
2549 COL_OBJECT_CAMEL_STORE, &src_store,
2550 COL_STRING_FULL_NAME, &src_full_name,
2551 COL_UINT_FLAGS, &src_flags, -1);
2553 /* can't dnd onto itself or below itself - bad things happen,
2554 * no point dragging to where we were either */
2555 if (gtk_tree_path_compare (path, src_path) == 0
2556 || gtk_tree_path_is_descendant (path, src_path)
2557 || (gtk_tree_path_is_ancestor (path, src_path)
2558 && gtk_tree_path_get_depth (path) ==
2559 gtk_tree_path_get_depth (src_path) - 1)) {
2560 gtk_tree_path_free (src_path);
2561 goto done;
2564 gtk_tree_path_free (src_path);
2566 if ((src_flags & CAMEL_FOLDER_TYPE_MASK) == CAMEL_FOLDER_TYPE_INBOX ||
2567 (src_flags & CAMEL_FOLDER_SYSTEM) != 0) {
2568 /* allow only copy of the Inbox and other system folders */
2569 GdkAtom xfolder;
2571 /* force copy for special local folders */
2572 *suggested_action = GDK_ACTION_COPY;
2573 *actions = GDK_ACTION_COPY;
2574 xfolder = drop_atoms[DND_DROP_TYPE_FOLDER];
2575 while (targets != NULL) {
2576 if (targets->data == (gpointer) xfolder) {
2577 atom = xfolder;
2578 goto done;
2581 targets = targets->next;
2584 goto done;
2589 /* Check for special sources, and vfolder stuff */
2590 if (src_store != NULL && src_full_name != NULL) {
2592 uid = camel_service_get_uid (CAMEL_SERVICE (src_store));
2594 src_is_local =
2595 (g_strcmp0 (uid, E_MAIL_SESSION_LOCAL_UID) == 0);
2596 src_is_vfolder =
2597 (g_strcmp0 (uid, E_MAIL_SESSION_VFOLDER_UID) == 0);
2599 /* FIXME: this is a total hack, but i think all we can do at present */
2600 /* Check for dragging from special folders which can't be moved/copied */
2602 /* Don't allow moving any of the the special local folders. */
2603 if (src_is_local && is_special_local_folder (src_full_name)) {
2604 GdkAtom xfolder;
2606 /* force copy for special local folders */
2607 *suggested_action = GDK_ACTION_COPY;
2608 *actions = GDK_ACTION_COPY;
2609 xfolder = drop_atoms[DND_DROP_TYPE_FOLDER];
2610 while (targets != NULL) {
2611 if (targets->data == (gpointer) xfolder) {
2612 atom = xfolder;
2613 goto done;
2616 targets = targets->next;
2619 goto done;
2622 /* Don't allow copying/moving the UNMATCHED vfolder. */
2623 if (src_is_vfolder)
2624 if (g_strcmp0 (src_full_name, CAMEL_UNMATCHED_NAME) == 0)
2625 goto done;
2627 /* Don't allow copying/moving any vTrash folder. */
2628 if (g_strcmp0 (src_full_name, CAMEL_VTRASH_NAME) == 0)
2629 goto done;
2631 /* Don't allow copying/moving any vJunk folder. */
2632 if (g_strcmp0 (src_full_name, CAMEL_VJUNK_NAME) == 0)
2633 goto done;
2635 /* Don't allow copying/moving any maildir 'inbox'. */
2636 if (g_strcmp0 (src_full_name, ".") == 0)
2637 goto done;
2639 /* Search Folders can only be dropped into other
2640 * Search Folders. */
2641 if (src_is_vfolder) {
2642 /* force move only for vfolders */
2643 *suggested_action = GDK_ACTION_MOVE;
2645 if (dst_is_vfolder) {
2646 GdkAtom xfolder;
2648 xfolder = drop_atoms[DND_DROP_TYPE_FOLDER];
2649 while (targets != NULL) {
2650 if (targets->data == (gpointer) xfolder) {
2651 atom = xfolder;
2652 goto done;
2655 targets = targets->next;
2659 goto done;
2663 /* Can't drag anything but a Search Folder into a Search Folder. */
2664 if (dst_is_vfolder)
2665 goto done;
2667 /* Now we either have a store or a normal folder. */
2669 if (is_store) {
2670 GdkAtom xfolder;
2672 xfolder = drop_atoms[DND_DROP_TYPE_FOLDER];
2673 while (targets != NULL) {
2674 if (targets->data == (gpointer) xfolder) {
2675 atom = xfolder;
2676 goto done;
2679 targets = targets->next;
2681 } else {
2682 GList *link;
2683 gint ii;
2685 /* The drop_atoms[] is sorted in the preference order. */
2686 for (ii = 0; ii < NUM_DROP_TYPES; ii++) {
2687 for (link = targets; link; link = g_list_next (link)) {
2688 if (link->data == (gpointer) drop_atoms[ii]) {
2689 atom = drop_atoms[ii];
2690 goto done;
2696 done:
2697 g_free (dst_full_name);
2698 g_free (src_full_name);
2699 g_clear_object (&dst_store);
2700 g_clear_object (&src_store);
2702 return atom;
2705 static gboolean
2706 tree_drag_drop (GtkWidget *widget,
2707 GdkDragContext *context,
2708 gint x,
2709 gint y,
2710 guint time,
2711 EMFolderTree *folder_tree)
2713 EMFolderTreePrivate *priv = folder_tree->priv;
2714 GtkTreeViewColumn *column;
2715 GtkTreeView *tree_view;
2716 gint cell_x, cell_y;
2717 GdkDragAction actions;
2718 GdkDragAction suggested_action;
2719 GtkTreePath *path;
2720 GdkAtom target;
2722 tree_view = GTK_TREE_VIEW (folder_tree);
2724 if (priv->autoscroll_id != 0) {
2725 g_source_remove (priv->autoscroll_id);
2726 priv->autoscroll_id = 0;
2729 if (priv->autoexpand_id != 0) {
2730 gtk_tree_row_reference_free (priv->autoexpand_row);
2731 priv->autoexpand_row = NULL;
2733 g_source_remove (priv->autoexpand_id);
2734 priv->autoexpand_id = 0;
2737 if (!gtk_tree_view_get_path_at_pos (
2738 tree_view, x, y, &path, &column, &cell_x, &cell_y))
2739 return FALSE;
2741 target = folder_tree_drop_target (
2742 folder_tree, context, path,
2743 &actions, &suggested_action);
2745 gtk_tree_path_free (path);
2747 return (target != GDK_NONE);
2750 static void
2751 tree_drag_end (GtkWidget *widget,
2752 GdkDragContext *context,
2753 EMFolderTree *folder_tree)
2755 EMFolderTreePrivate *priv = folder_tree->priv;
2757 if (priv->drag_row != NULL) {
2758 gtk_tree_row_reference_free (priv->drag_row);
2759 priv->drag_row = NULL;
2762 /* FIXME: undo anything done in drag-begin */
2765 static void
2766 tree_drag_leave (GtkWidget *widget,
2767 GdkDragContext *context,
2768 guint time,
2769 EMFolderTree *folder_tree)
2771 EMFolderTreePrivate *priv = folder_tree->priv;
2772 GtkTreeView *tree_view;
2774 tree_view = GTK_TREE_VIEW (folder_tree);
2776 if (priv->autoscroll_id != 0) {
2777 g_source_remove (priv->autoscroll_id);
2778 priv->autoscroll_id = 0;
2781 if (priv->autoexpand_id != 0) {
2782 gtk_tree_row_reference_free (priv->autoexpand_row);
2783 priv->autoexpand_row = NULL;
2785 g_source_remove (priv->autoexpand_id);
2786 priv->autoexpand_id = 0;
2789 gtk_tree_view_set_drag_dest_row (
2790 tree_view, NULL, GTK_TREE_VIEW_DROP_BEFORE);
2793 #define SCROLL_EDGE_SIZE 15
2795 static gboolean
2796 tree_autoscroll (gpointer user_data)
2798 EMFolderTree *folder_tree;
2799 GtkAdjustment *adjustment;
2800 GtkTreeView *tree_view;
2801 GtkScrollable *scrollable;
2802 GdkRectangle rect;
2803 GdkWindow *window;
2804 GdkDisplay *display;
2805 GdkDeviceManager *device_manager;
2806 GdkDevice *device;
2807 gdouble value;
2808 gint offset, y;
2810 folder_tree = EM_FOLDER_TREE (user_data);
2812 /* Get the y pointer position relative to the treeview. */
2813 tree_view = GTK_TREE_VIEW (folder_tree);
2814 window = gtk_tree_view_get_bin_window (tree_view);
2815 display = gdk_window_get_display (window);
2816 device_manager = gdk_display_get_device_manager (display);
2817 device = gdk_device_manager_get_client_pointer (device_manager);
2818 gdk_window_get_device_position (window, device, NULL, &y, NULL);
2820 /* Rect is in coorinates relative to the scrolled window,
2821 * relative to the treeview. */
2822 gtk_tree_view_get_visible_rect (tree_view, &rect);
2824 /* Move y into the same coordinate system as rect. */
2825 y += rect.y;
2827 /* See if we are near the top edge. */
2828 offset = y - (rect.y + 2 * SCROLL_EDGE_SIZE);
2829 if (offset > 0) {
2830 /* See if we are near the bottom edge. */
2831 offset = y - (rect.y + rect.height - 2 * SCROLL_EDGE_SIZE);
2832 if (offset < 0)
2833 return TRUE;
2836 scrollable = GTK_SCROLLABLE (folder_tree);
2837 adjustment = gtk_scrollable_get_vadjustment (scrollable);
2838 value = gtk_adjustment_get_value (adjustment);
2839 gtk_adjustment_set_value (adjustment, MAX (value + offset, 0.0));
2841 return TRUE;
2844 static gboolean
2845 tree_autoexpand (gpointer user_data)
2847 EMFolderTreePrivate *priv;
2848 GtkTreeView *tree_view;
2849 GtkTreePath *path;
2851 tree_view = GTK_TREE_VIEW (user_data);
2852 priv = EM_FOLDER_TREE_GET_PRIVATE (tree_view);
2854 path = gtk_tree_row_reference_get_path (priv->autoexpand_row);
2855 gtk_tree_view_expand_row (tree_view, path, FALSE);
2856 gtk_tree_path_free (path);
2858 return TRUE;
2861 static gboolean
2862 tree_drag_motion (GtkWidget *widget,
2863 GdkDragContext *context,
2864 gint x,
2865 gint y,
2866 guint time,
2867 EMFolderTree *folder_tree)
2869 EMFolderTreePrivate *priv = folder_tree->priv;
2870 GtkTreeViewDropPosition pos;
2871 GtkTreeView *tree_view;
2872 GtkTreeModel *model;
2873 GdkDragAction actions;
2874 GdkDragAction suggested_action;
2875 GdkDragAction chosen_action = 0;
2876 GtkTreePath *path = NULL;
2877 GtkTreeIter iter;
2878 GdkAtom target;
2879 gint i;
2881 tree_view = GTK_TREE_VIEW (folder_tree);
2882 model = gtk_tree_view_get_model (tree_view);
2884 if (!gtk_tree_view_get_dest_row_at_pos (tree_view, x, y, &path, &pos))
2885 return FALSE;
2887 if (priv->autoscroll_id == 0) {
2888 priv->autoscroll_id = e_named_timeout_add (
2889 150, tree_autoscroll, folder_tree);
2892 gtk_tree_model_get_iter (model, &iter, path);
2894 if (gtk_tree_model_iter_has_child (model, &iter) &&
2895 !gtk_tree_view_row_expanded (tree_view, path)) {
2897 if (priv->autoexpand_id != 0) {
2898 GtkTreePath *autoexpand_path;
2900 autoexpand_path = gtk_tree_row_reference_get_path (
2901 priv->autoexpand_row);
2902 if (gtk_tree_path_compare (autoexpand_path, path) != 0) {
2903 /* row changed, restart timer */
2904 gtk_tree_row_reference_free (priv->autoexpand_row);
2905 priv->autoexpand_row =
2906 gtk_tree_row_reference_new (model, path);
2907 g_source_remove (priv->autoexpand_id);
2908 priv->autoexpand_id = e_named_timeout_add (
2909 600, tree_autoexpand, folder_tree);
2912 gtk_tree_path_free (autoexpand_path);
2913 } else {
2914 priv->autoexpand_id = e_named_timeout_add (
2915 600, tree_autoexpand, folder_tree);
2916 priv->autoexpand_row =
2917 gtk_tree_row_reference_new (model, path);
2919 } else if (priv->autoexpand_id != 0) {
2920 gtk_tree_row_reference_free (priv->autoexpand_row);
2921 priv->autoexpand_row = NULL;
2923 g_source_remove (priv->autoexpand_id);
2924 priv->autoexpand_id = 0;
2927 target = folder_tree_drop_target (
2928 folder_tree, context, path,
2929 &actions, &suggested_action);
2930 for (i = 0; target != GDK_NONE && i < NUM_DROP_TYPES; i++) {
2931 if (drop_atoms[i] != target)
2932 continue;
2933 switch (i) {
2934 case DND_DROP_TYPE_UID_LIST:
2935 case DND_DROP_TYPE_FOLDER:
2936 chosen_action = suggested_action;
2937 if (chosen_action == GDK_ACTION_COPY &&
2938 (actions & GDK_ACTION_MOVE))
2939 chosen_action = GDK_ACTION_MOVE;
2940 gtk_tree_view_set_drag_dest_row (
2941 tree_view, path,
2942 GTK_TREE_VIEW_DROP_INTO_OR_AFTER);
2943 break;
2944 default:
2945 gtk_tree_view_set_drag_dest_row (
2946 tree_view, path,
2947 GTK_TREE_VIEW_DROP_INTO_OR_AFTER);
2948 chosen_action = suggested_action;
2949 break;
2952 break;
2955 gdk_drag_status (context, chosen_action, time);
2956 gtk_tree_path_free (path);
2958 return chosen_action != 0;
2961 void
2962 em_folder_tree_enable_drag_and_drop (EMFolderTree *folder_tree)
2964 GtkTreeView *tree_view;
2965 static gint setup = 0;
2966 gint i;
2968 g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree));
2970 tree_view = GTK_TREE_VIEW (folder_tree);
2972 if (!setup) {
2973 for (i = 0; i < NUM_DRAG_TYPES; i++)
2974 drag_atoms[i] = gdk_atom_intern (drag_types[i].target, FALSE);
2976 for (i = 0; i < NUM_DROP_TYPES; i++)
2977 drop_atoms[i] = gdk_atom_intern (drop_types[i].target, FALSE);
2979 setup = 1;
2982 gtk_drag_source_set (
2983 GTK_WIDGET (tree_view), GDK_BUTTON1_MASK,drag_types,
2984 NUM_DRAG_TYPES, GDK_ACTION_COPY | GDK_ACTION_MOVE);
2985 gtk_drag_dest_set (
2986 GTK_WIDGET (tree_view), GTK_DEST_DEFAULT_ALL, drop_types,
2987 NUM_DROP_TYPES, GDK_ACTION_COPY | GDK_ACTION_MOVE);
2989 g_signal_connect (
2990 tree_view, "drag-begin",
2991 G_CALLBACK (tree_drag_begin), folder_tree);
2992 g_signal_connect (
2993 tree_view, "drag-data-get",
2994 G_CALLBACK (tree_drag_data_get), folder_tree);
2995 g_signal_connect (
2996 tree_view, "drag-data-received",
2997 G_CALLBACK (tree_drag_data_received), folder_tree);
2998 g_signal_connect (
2999 tree_view, "drag-drop",
3000 G_CALLBACK (tree_drag_drop), folder_tree);
3001 g_signal_connect (
3002 tree_view, "drag-end",
3003 G_CALLBACK (tree_drag_end), folder_tree);
3004 g_signal_connect (
3005 tree_view, "drag-leave",
3006 G_CALLBACK (tree_drag_leave), folder_tree);
3007 g_signal_connect (
3008 tree_view, "drag-motion",
3009 G_CALLBACK (tree_drag_motion), folder_tree);
3012 void
3013 em_folder_tree_set_excluded (EMFolderTree *folder_tree,
3014 guint32 flags)
3016 g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree));
3018 folder_tree->priv->excluded = flags;
3021 void
3022 em_folder_tree_set_excluded_func (EMFolderTree *folder_tree,
3023 EMFTExcludeFunc exclude,
3024 gpointer data)
3026 g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree));
3027 g_return_if_fail (exclude != NULL);
3029 folder_tree->priv->excluded_func = exclude;
3030 folder_tree->priv->excluded_data = data;
3033 GList *
3034 em_folder_tree_get_selected_uris (EMFolderTree *folder_tree)
3036 GtkTreeSelection *selection;
3037 GtkTreeView *tree_view;
3038 GtkTreeModel *model;
3039 GList *list = NULL, *rows, *l;
3040 GSList *sl;
3042 tree_view = GTK_TREE_VIEW (folder_tree);
3043 selection = gtk_tree_view_get_selection (tree_view);
3045 /* at first, add lost uris */
3046 for (sl = folder_tree->priv->select_uris; sl; sl = g_slist_next (sl)) {
3047 const gchar *uri;
3048 uri = ((struct _selected_uri *) sl->data)->uri;
3049 list = g_list_append (list, g_strdup (uri));
3052 rows = gtk_tree_selection_get_selected_rows (selection, &model);
3053 for (l = rows; l; l = g_list_next (l)) {
3054 GtkTreeIter iter;
3055 GtkTreePath *path = l->data;
3057 if (gtk_tree_model_get_iter (model, &iter, path)) {
3058 CamelStore *store;
3059 gchar *folder_name;
3061 gtk_tree_model_get (
3062 model, &iter,
3063 COL_OBJECT_CAMEL_STORE, &store,
3064 COL_STRING_FULL_NAME, &folder_name, -1);
3066 if (CAMEL_IS_STORE (store) && folder_name != NULL) {
3067 gchar *folder_uri;
3069 folder_uri = e_mail_folder_uri_build (
3070 store, folder_name);
3071 list = g_list_prepend (list, folder_uri);
3074 g_free (folder_name);
3075 g_clear_object (&store);
3077 gtk_tree_path_free (path);
3079 g_list_free (rows);
3081 return g_list_reverse (list);
3084 static void
3085 get_selected_uris_path_iterate (GtkTreeModel *model,
3086 GtkTreePath *treepath,
3087 GtkTreeIter *iter,
3088 gpointer data)
3090 GList **list = (GList **) data;
3091 gchar *full_name;
3093 gtk_tree_model_get (model, iter, COL_STRING_FULL_NAME, &full_name, -1);
3094 *list = g_list_append (*list, full_name);
3097 GList *
3098 em_folder_tree_get_selected_paths (EMFolderTree *folder_tree)
3100 GtkTreeSelection *selection;
3101 GtkTreeView *tree_view;
3102 GList *list = NULL;
3104 tree_view = GTK_TREE_VIEW (folder_tree);
3105 selection = gtk_tree_view_get_selection (tree_view);
3107 gtk_tree_selection_selected_foreach (
3108 selection, get_selected_uris_path_iterate, &list);
3110 return list;
3113 void
3114 em_folder_tree_set_selected_list (EMFolderTree *folder_tree,
3115 GList *list,
3116 gboolean expand_only)
3118 EMFolderTreePrivate *priv = folder_tree->priv;
3119 EMailSession *session;
3121 session = em_folder_tree_get_session (folder_tree);
3123 /* FIXME: need to remove any currently selected stuff? */
3124 if (!expand_only)
3125 folder_tree_clear_selected_list (folder_tree);
3127 for (; list; list = list->next) {
3128 CamelStore *store;
3129 struct _selected_uri *u;
3130 const gchar *folder_uri;
3131 const gchar *uid;
3132 gchar *folder_name;
3133 gchar *expand_key;
3134 gchar *end;
3135 gboolean success;
3137 /* This makes sure all our parents up to the root are
3138 * expanded. */
3140 folder_uri = list->data;
3142 success = e_mail_folder_uri_parse (
3143 CAMEL_SESSION (session), folder_uri,
3144 &store, &folder_name, NULL);
3146 if (!success)
3147 continue;
3149 uid = camel_service_get_uid (CAMEL_SERVICE (store));
3150 expand_key = g_strdup_printf ("%s/%s", uid, folder_name);
3151 g_free (folder_name);
3153 u = g_malloc0 (sizeof (*u));
3154 u->uri = g_strdup (folder_uri);
3155 u->service = CAMEL_SERVICE (store); /* takes ownership */
3156 u->key = g_strdup (expand_key);
3158 if (!expand_only) {
3159 g_hash_table_insert (
3160 priv->select_uris_table, u->key, u);
3161 priv->select_uris =
3162 g_slist_append (priv->select_uris, u);
3165 while (end = strrchr (expand_key, '/'), end) {
3166 folder_tree_expand_node (expand_key, folder_tree);
3167 *end = 0;
3170 if (expand_only)
3171 folder_tree_free_select_uri (u);
3173 g_free (expand_key);
3177 #if 0
3178 static void
3179 dump_fi (CamelFolderInfo *fi,
3180 gint depth)
3182 gint i;
3184 while (fi != NULL) {
3185 for (i = 0; i < depth; i++)
3186 fputs (" ", stdout);
3188 printf ("path='%s'; full_name='%s'\n", fi->path, fi->full_name);
3190 if (fi->child)
3191 dump_fi (fi->child, depth + 1);
3193 fi = fi->sibling;
3196 #endif
3198 void
3199 em_folder_tree_set_selected (EMFolderTree *folder_tree,
3200 const gchar *uri,
3201 gboolean expand_only)
3203 GList *l = NULL;
3205 g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree));
3207 if (uri && uri[0])
3208 l = g_list_append (l, (gpointer) uri);
3210 em_folder_tree_set_selected_list (folder_tree, l, expand_only);
3211 g_list_free (l);
3214 void
3215 em_folder_tree_select_next_path (EMFolderTree *folder_tree,
3216 gboolean skip_read_folders)
3218 GtkTreeView *tree_view;
3219 GtkTreeSelection *selection;
3220 GtkTreeModel *model;
3221 GtkTreeIter iter, parent, child;
3222 GtkTreePath *current_path, *path = NULL;
3223 guint unread = 0;
3224 EMFolderTreePrivate *priv = folder_tree->priv;
3226 g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree));
3228 tree_view = GTK_TREE_VIEW (folder_tree);
3229 selection = gtk_tree_view_get_selection (tree_view);
3230 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
3232 current_path = gtk_tree_model_get_path (model, &iter);
3234 do {
3235 if (gtk_tree_model_iter_has_child (model, &iter)) {
3236 if (!gtk_tree_model_iter_children (model, &child, &iter))
3237 break;
3238 path = gtk_tree_model_get_path (model, &child);
3239 iter = child;
3240 } else {
3241 while (1) {
3242 gboolean has_parent;
3244 has_parent = gtk_tree_model_iter_parent (
3245 model, &parent, &iter);
3247 if (gtk_tree_model_iter_next (model, &iter)) {
3248 path = gtk_tree_model_get_path (model, &iter);
3249 break;
3250 } else {
3251 if (has_parent) {
3252 iter = parent;
3253 } else {
3254 /* Reached end. Wrapup*/
3255 if (gtk_tree_model_get_iter_first (model, &iter))
3256 path = gtk_tree_model_get_path (model, &iter);
3257 else
3258 path = NULL;
3259 break;
3264 if (!path)
3265 break;
3267 gtk_tree_model_get (model, &iter, COL_UINT_UNREAD, &unread, -1);
3269 /* TODO : Flags here for better options */
3270 } while (skip_read_folders && unread <=0 &&
3271 gtk_tree_path_compare (current_path, path));
3273 gtk_tree_path_free (current_path);
3276 if (path) {
3277 if (!gtk_tree_view_row_expanded (tree_view, path))
3278 gtk_tree_view_expand_to_path (tree_view, path);
3280 gtk_tree_selection_select_path (selection, path);
3282 if (!priv->cursor_set) {
3283 gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE);
3284 priv->cursor_set = TRUE;
3286 gtk_tree_view_scroll_to_cell (tree_view, path, NULL, TRUE, 0.5f, 0.0f);
3288 gtk_tree_path_free (path);
3291 return;
3294 static gboolean
3295 folder_tree_descend (GtkTreeModel *model,
3296 GtkTreeIter *iter,
3297 GtkTreeIter *root)
3299 GtkTreeIter parent;
3300 gint n_children;
3302 /* Finds the rightmost descendant of the given root. */
3304 if (root == NULL) {
3305 n_children = gtk_tree_model_iter_n_children (model, NULL);
3307 /* This will invalidate the iterator and return FALSE. */
3308 if (n_children == 0)
3309 return gtk_tree_model_get_iter_first (model, iter);
3311 gtk_tree_model_iter_nth_child (
3312 model, &parent, NULL, n_children - 1);
3313 } else
3314 parent = *root;
3316 n_children = gtk_tree_model_iter_n_children (model, &parent);
3318 while (n_children > 0) {
3319 GtkTreeIter child;
3321 gtk_tree_model_iter_nth_child (
3322 model, &child, &parent, n_children - 1);
3324 parent = child;
3326 n_children = gtk_tree_model_iter_n_children (model, &parent);
3329 *iter = parent;
3331 return TRUE;
3334 void
3335 em_folder_tree_select_prev_path (EMFolderTree *folder_tree,
3336 gboolean skip_read_folders)
3338 GtkTreeView *tree_view;
3339 GtkTreeSelection *selection;
3340 GtkTreeModel *model;
3341 GtkTreePath *path = NULL;
3342 GtkTreePath *sentinel;
3343 GtkTreeIter iter;
3344 guint unread = 0;
3345 EMFolderTreePrivate *priv = folder_tree->priv;
3347 g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree));
3349 tree_view = GTK_TREE_VIEW (folder_tree);
3350 selection = gtk_tree_view_get_selection (tree_view);
3352 /* Nothing selected means nothing to do. */
3353 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
3354 return;
3356 /* This prevents us from looping over the model indefinitely,
3357 * looking for unread messages when there are none. */
3358 sentinel = gtk_tree_model_get_path (model, &iter);
3360 do {
3361 GtkTreeIter descendant;
3363 if (path != NULL)
3364 gtk_tree_path_free (path);
3366 path = gtk_tree_model_get_path (model, &iter);
3368 if (gtk_tree_path_prev (path)) {
3369 gtk_tree_model_get_iter (model, &iter, path);
3370 folder_tree_descend (model, &descendant, &iter);
3372 gtk_tree_path_free (path);
3373 path = gtk_tree_model_get_path (model, &descendant);
3375 } else if (gtk_tree_path_get_depth (path) > 1) {
3376 gtk_tree_path_up (path);
3378 } else {
3379 folder_tree_descend (model, &descendant, NULL);
3381 gtk_tree_path_free (path);
3382 path = gtk_tree_model_get_path (model, &descendant);
3385 gtk_tree_model_get_iter (model, &iter, path);
3386 gtk_tree_model_get (model, &iter, COL_UINT_UNREAD, &unread, -1);
3388 } while (skip_read_folders && unread <= 0 &&
3389 gtk_tree_path_compare (path, sentinel) != 0);
3391 if (!gtk_tree_view_row_expanded (tree_view, path))
3392 gtk_tree_view_expand_to_path (tree_view, path);
3394 gtk_tree_selection_select_path (selection, path);
3396 if (!priv->cursor_set) {
3397 gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE);
3398 priv->cursor_set = TRUE;
3401 gtk_tree_view_scroll_to_cell (
3402 tree_view, path, NULL, TRUE, 0.5f, 0.0f);
3404 gtk_tree_path_free (sentinel);
3405 gtk_tree_path_free (path);
3408 void
3409 em_folder_tree_edit_selected (EMFolderTree *folder_tree)
3411 GtkTreeSelection *selection;
3412 GtkTreeViewColumn *column;
3413 GtkCellRenderer *renderer;
3414 GtkTreeView *tree_view;
3415 GtkTreeModel *model;
3416 GtkTreePath *path = NULL;
3417 GtkTreeIter iter;
3419 g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree));
3421 tree_view = GTK_TREE_VIEW (folder_tree);
3422 column = gtk_tree_view_get_column (tree_view, 0);
3423 selection = gtk_tree_view_get_selection (tree_view);
3424 renderer = folder_tree->priv->text_renderer;
3426 if (gtk_tree_selection_get_selected (selection, &model, &iter))
3427 path = gtk_tree_model_get_path (model, &iter);
3429 if (path == NULL)
3430 return;
3432 /* Make the text cell renderer editable, but only temporarily.
3433 * We don't want editing to be activated by simply clicking on
3434 * the folder name. Too easy for accidental edits to occur. */
3435 g_object_set (renderer, "editable", TRUE, NULL);
3436 gtk_tree_view_expand_to_path (tree_view, path);
3437 gtk_tree_view_set_cursor_on_cell (
3438 tree_view, path, column, renderer, TRUE);
3439 g_object_set (renderer, "editable", FALSE, NULL);
3441 gtk_tree_path_free (path);
3444 gboolean
3445 em_folder_tree_get_selected (EMFolderTree *folder_tree,
3446 CamelStore **out_store,
3447 gchar **out_folder_name)
3449 GtkTreeView *tree_view;
3450 GtkTreeSelection *selection;
3451 GtkTreeModel *model;
3452 GtkTreeIter iter;
3453 CamelStore *store = NULL;
3454 gchar *folder_name = NULL;
3456 g_return_val_if_fail (EM_IS_FOLDER_TREE (folder_tree), FALSE);
3458 tree_view = GTK_TREE_VIEW (folder_tree);
3459 selection = gtk_tree_view_get_selection (tree_view);
3461 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
3462 return FALSE;
3464 gtk_tree_model_get (
3465 model, &iter,
3466 COL_OBJECT_CAMEL_STORE, &store,
3467 COL_STRING_FULL_NAME, &folder_name, -1);
3469 /* We should always get a valid store. */
3470 g_return_val_if_fail (CAMEL_IS_STORE (store), FALSE);
3472 /* If a store is selected, the folder name will be NULL.
3473 * Treat this as though nothing is selected, so that callers
3474 * can assume a TRUE return value means a folder is selected. */
3475 if (folder_name == NULL) {
3476 g_clear_object (&store);
3477 return FALSE;
3480 if (out_store != NULL)
3481 *out_store = g_object_ref (store);
3483 if (out_folder_name != NULL)
3484 *out_folder_name = folder_name;
3485 else
3486 g_free (folder_name);
3488 g_clear_object (&store);
3490 return TRUE;
3493 gboolean
3494 em_folder_tree_store_root_selected (EMFolderTree *folder_tree,
3495 CamelStore **out_store)
3497 GtkTreeView *tree_view;
3498 GtkTreeSelection *selection;
3499 GtkTreeModel *model;
3500 GtkTreeIter iter;
3501 CamelStore *store = NULL;
3502 gboolean is_store = FALSE;
3504 g_return_val_if_fail (folder_tree != NULL, FALSE);
3505 g_return_val_if_fail (EM_IS_FOLDER_TREE (folder_tree), FALSE);
3507 tree_view = GTK_TREE_VIEW (folder_tree);
3508 selection = gtk_tree_view_get_selection (tree_view);
3510 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
3511 return FALSE;
3513 gtk_tree_model_get (
3514 model, &iter,
3515 COL_OBJECT_CAMEL_STORE, &store,
3516 COL_BOOL_IS_STORE, &is_store, -1);
3518 /* We should always get a valid store. */
3519 g_return_val_if_fail (CAMEL_IS_STORE (store), FALSE);
3521 if (!is_store) {
3522 g_clear_object (&store);
3523 return FALSE;
3526 if (out_store != NULL)
3527 *out_store = g_object_ref (store);
3529 g_clear_object (&store);
3531 return TRUE;
3534 gchar *
3535 em_folder_tree_get_selected_uri (EMFolderTree *folder_tree)
3537 GtkTreeView *tree_view;
3538 GtkTreeSelection *selection;
3539 GtkTreeModel *model;
3540 GtkTreeIter iter;
3541 CamelStore *store;
3542 gchar *folder_name;
3543 gchar *folder_uri = NULL;
3545 g_return_val_if_fail (EM_IS_FOLDER_TREE (folder_tree), NULL);
3547 tree_view = GTK_TREE_VIEW (folder_tree);
3548 selection = gtk_tree_view_get_selection (tree_view);
3550 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
3551 return NULL;
3553 gtk_tree_model_get (
3554 model, &iter,
3555 COL_OBJECT_CAMEL_STORE, &store,
3556 COL_STRING_FULL_NAME, &folder_name, -1);
3558 /* We should always get a valid store. */
3559 g_return_val_if_fail (CAMEL_IS_STORE (store), FALSE);
3561 if (folder_name != NULL)
3562 folder_uri = e_mail_folder_uri_build (store, folder_name);
3563 else
3564 folder_uri = e_mail_folder_uri_build (store, "");
3566 g_free (folder_name);
3567 g_clear_object (&store);
3569 return folder_uri;
3573 * em_folder_tree_ref_selected_store:
3574 * @folder_tree: an #EMFolderTree
3576 * Returns the #CamelStore for the selected row in @folder_tree, or %NULL
3577 * if no row is selected.
3579 * The returned #CamelStore is referenced for thread-safety and must be
3580 * unreferenced with g_object_unref() when finished with it.
3582 * Returns: a #CamelStore, or %NULL
3584 CamelStore *
3585 em_folder_tree_ref_selected_store (EMFolderTree *folder_tree)
3587 GtkTreeView *tree_view;
3588 GtkTreeSelection *selection;
3589 GtkTreeModel *model;
3590 GtkTreeIter iter;
3591 CamelStore *store = NULL;
3593 g_return_val_if_fail (EM_IS_FOLDER_TREE (folder_tree), NULL);
3595 /* Don't use em_folder_tree_get_selected() here because we
3596 * want this to work whether a folder or store is selected. */
3598 tree_view = GTK_TREE_VIEW (folder_tree);
3599 selection = gtk_tree_view_get_selection (tree_view);
3601 if (gtk_tree_selection_get_selected (selection, &model, &iter))
3602 gtk_tree_model_get (
3603 model, &iter,
3604 COL_OBJECT_CAMEL_STORE, &store, -1);
3606 return store;
3609 void
3610 em_folder_tree_set_skip_double_click (EMFolderTree *folder_tree,
3611 gboolean skip)
3613 folder_tree->priv->skip_double_click = skip;
3616 /* stores come first, then by uri */
3617 static gint
3618 sort_by_store_and_uri (gconstpointer name1,
3619 gconstpointer name2)
3621 const gchar *n1 = name1, *n2 = name2;
3622 gboolean is_store1, is_store2;
3624 if (n1 == NULL || n2 == NULL) {
3625 if (n1 == n2)
3626 return 0;
3627 else
3628 return n1 ? -1 : 1;
3631 is_store1 = g_str_has_prefix (n1, "Store ");
3632 is_store2 = g_str_has_prefix (n2, "Store ");
3634 if ((is_store1 || is_store2) && (!is_store1 || !is_store2)) {
3635 return is_store1 ? -1 : 1;
3638 return strcmp (n1, n2);
3641 /* restores state of a tree (collapsed/expanded) as stores in the given key_file */
3642 void
3643 em_folder_tree_restore_state (EMFolderTree *folder_tree,
3644 GKeyFile *key_file)
3646 EMFolderTreeModel *folder_tree_model;
3647 EMailSession *session;
3648 GtkTreeModel *tree_model;
3649 GtkTreeView *tree_view;
3650 GtkTreeIter iter;
3651 gboolean valid;
3652 gchar **groups_arr;
3653 GSList *groups, *group;
3654 gint ii;
3656 /* Make sure we have a key file to restore state from. */
3657 if (key_file == NULL)
3658 return;
3660 tree_view = GTK_TREE_VIEW (folder_tree);
3661 tree_model = gtk_tree_view_get_model (tree_view);
3663 folder_tree_model = EM_FOLDER_TREE_MODEL (tree_model);
3664 session = em_folder_tree_model_get_session (folder_tree_model);
3665 g_return_if_fail (E_IS_MAIL_SESSION (session));
3667 /* Set the initial folder tree expanded state in two stages:
3669 * 1) Iterate over the "Store" and "Folder" state file groups
3670 * and apply the "Expanded" keys where possible.
3672 * 2) Iterate over the top-level nodes in the folder tree
3673 * (these are all stores) and expand those that have no
3674 * corresponding "Expanded" key in the state file. This
3675 * ensures that new stores are expanded by default.
3678 /* Stage 1 */
3680 /* Collapse all so we have a clean slate. */
3681 gtk_tree_view_collapse_all (tree_view);
3683 groups_arr = g_key_file_get_groups (key_file, NULL);
3684 groups = NULL;
3686 for (ii = 0; groups_arr[ii] != NULL; ii++) {
3687 groups = g_slist_prepend (groups, groups_arr[ii]);
3690 groups = g_slist_sort (groups, sort_by_store_and_uri);
3692 for (group = groups; group != NULL; group = group->next) {
3693 GtkTreeRowReference *reference = NULL;
3694 CamelStore *store = NULL;
3695 const gchar *group_name = group->data;
3696 const gchar *key = STATE_KEY_EXPANDED;
3697 gchar *folder_name = NULL;
3698 gboolean expanded = FALSE;
3699 gboolean success = FALSE;
3701 if (g_str_has_prefix (group_name, "Store ")) {
3702 CamelService *service;
3703 const gchar *uid = group_name + 6;
3705 service = camel_session_ref_service (
3706 CAMEL_SESSION (session), uid);
3707 if (CAMEL_IS_STORE (service)) {
3708 store = g_object_ref (service);
3709 success = TRUE;
3711 if (service != NULL)
3712 g_object_unref (service);
3713 expanded = TRUE;
3715 } else if (g_str_has_prefix (group_name, "Folder ")) {
3716 const gchar *uri = group_name + 7;
3718 success = e_mail_folder_uri_parse (
3719 CAMEL_SESSION (session), uri,
3720 &store, &folder_name, NULL);
3721 expanded = FALSE;
3724 if (g_key_file_has_key (key_file, group_name, key, NULL))
3725 expanded = g_key_file_get_boolean (
3726 key_file, group_name, key, NULL);
3728 if (expanded && success) {
3729 reference = em_folder_tree_model_get_row_reference (
3730 folder_tree_model, store, folder_name);
3733 if (gtk_tree_row_reference_valid (reference)) {
3734 GtkTreePath *path;
3735 GtkTreeIter iter;
3737 path = gtk_tree_row_reference_get_path (reference);
3738 gtk_tree_model_get_iter (tree_model, &iter, path);
3739 gtk_tree_view_expand_row (tree_view, path, FALSE);
3740 gtk_tree_path_free (path);
3743 if (store != NULL)
3744 g_object_unref (store);
3745 g_free (folder_name);
3748 g_slist_free (groups);
3749 g_strfreev (groups_arr);
3751 /* Stage 2 */
3753 valid = gtk_tree_model_get_iter_first (tree_model, &iter);
3755 while (valid) {
3756 CamelStore *store;
3757 CamelService *service;
3758 const gchar *key = STATE_KEY_EXPANDED;
3759 const gchar *uid;
3760 gchar *group_name;
3762 gtk_tree_model_get (
3763 tree_model, &iter,
3764 COL_OBJECT_CAMEL_STORE, &store, -1);
3766 if (store == NULL)
3767 goto next;
3769 service = CAMEL_SERVICE (store);
3770 uid = camel_service_get_uid (service);
3771 group_name = g_strdup_printf ("Store %s", uid);
3773 /* Expand stores that have no "Expanded" key. */
3774 if (!g_key_file_has_key (key_file, group_name, key, NULL)) {
3775 GtkTreePath *path;
3777 path = gtk_tree_model_get_path (tree_model, &iter);
3778 gtk_tree_view_expand_row (tree_view, path, FALSE);
3779 gtk_tree_path_free (path);
3782 g_free (group_name);
3783 g_clear_object (&store);
3785 next:
3786 valid = gtk_tree_model_iter_next (tree_model, &iter);
3791 * em_folder_tree_select_store_when_added:
3792 * @folder_tree: an #EMFolderTree
3793 * @store_uid: UID of a CamelStore to remember to select
3795 * Instruct @folder_tree to select a CamelStore with UID @store_uid,
3796 * if/when it is added to the tree. This is necessary, because
3797 * the addition is done asynchronously.
3799 * Since: 3.12
3801 void
3802 em_folder_tree_select_store_when_added (EMFolderTree *folder_tree,
3803 const gchar *store_uid)
3805 g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree));
3807 if (g_strcmp0 (store_uid, folder_tree->priv->select_store_uid_when_added) == 0)
3808 return;
3810 g_free (folder_tree->priv->select_store_uid_when_added);
3811 folder_tree->priv->select_store_uid_when_added = g_strdup (store_uid);