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
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/>.
16 * Jeffrey Stedfast <fejj@ximian.com>
18 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
22 #include "evolution-config.h"
27 #include <sys/types.h>
33 #include <libxml/tree.h>
35 #include <glib/gi18n.h>
36 #include <gdk/gdkkeysyms.h>
38 #include "em-vfolder-editor-rule.h"
41 #include "em-folder-utils.h"
42 #include "em-folder-selector.h"
43 #include "em-folder-properties.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"
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 */
67 CamelService
*service
;
71 struct _EMFolderTreePrivate
{
72 EMailSession
*session
;
73 EAlertSink
*alert_sink
;
75 /* selected_uri structures of each path pending selection. */
78 /* Removed as they're encountered, so use this
79 * to find URI's not presnet but selected. */
80 GHashTable
*select_uris_table
;
83 gboolean (*excluded_func
) (EMFolderTree
*folder_tree
,
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
96 GtkTreeRowReference
*autoexpand_row
;
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
{
116 EMFolderTree
*folder_tree
;
117 GtkTreeRowReference
*root
;
124 PROP_COPY_TARGET_LIST
,
126 PROP_PASTE_TARGET_LIST
,
131 FOLDER_ACTIVATED
, /* aka double-clicked or user hit enter */
138 /* Drag & Drop types */
140 DND_DRAG_TYPE_FOLDER
, /* drag an evo folder */
141 DND_DRAG_TYPE_TEXT_URI_LIST
, /* drag to an mbox file */
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 */
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
{
176 /* Forward Declarations */
177 static void em_folder_tree_selectable_init (ESelectableInterface
*iface
);
179 G_DEFINE_TYPE_WITH_CODE (
183 G_IMPLEMENT_INTERFACE (
185 em_folder_tree_selectable_init
))
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
);
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
;
218 gboolean iter_is_placeholder
;
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
);
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. */
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
);
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
);
269 } else if (error
!= NULL
) {
270 g_warn_if_fail (folder_info
== NULL
);
272 alert_sink
, "mail:folder-open",
273 error
->message
, NULL
);
274 async_context_free (context
);
275 g_error_free (error
);
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. */
292 gboolean is_store_node
= FALSE
;
293 gboolean is_folder_node
= FALSE
;
295 titer
= iter
; /* Preserve the last valid 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
;
308 valid
= gtk_tree_model_iter_next (model
, &iter
);
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);
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
);
338 path
= gtk_tree_model_get_path (model
, &root
);
339 gtk_tree_view_collapse_row (tree_view
, path
);
340 gtk_tree_path_free (path
);
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
;
359 gtk_tree_store_append (
360 GTK_TREE_STORE (model
),
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
);
377 GTK_TREE_STORE (model
), &root
,
378 COL_BOOL_LOAD_SUBDIRS
, FALSE
, -1);
381 camel_folder_info_free (folder_info
);
383 async_context_free (context
);
387 folder_tree_emit_popup_event (EMFolderTree
*folder_tree
,
390 g_signal_emit (folder_tree
, signals
[POPUP_EVENT
], 0, event
);
394 folder_tree_free_select_uri (struct _selected_uri
*u
)
398 g_object_unref (u
->service
);
405 folder_tree_select_func (GtkTreeSelection
*selection
,
410 EMFolderTreePrivate
*priv
;
411 GtkTreeView
*tree_view
;
416 tree_view
= gtk_tree_selection_get_tree_view (selection
);
418 priv
= EM_FOLDER_TREE_GET_PRIVATE (tree_view
);
423 if (priv
->excluded
== 0 && priv
->excluded_func
== NULL
)
426 if (!gtk_tree_model_get_iter (model
, &iter
, path
))
429 if (priv
->excluded_func
!= NULL
)
430 return priv
->excluded_func (
431 EM_FOLDER_TREE (tree_view
), model
,
432 &iter
, priv
->excluded_data
);
435 model
, &iter
, COL_UINT_FLAGS
, &flags
,
436 COL_BOOL_IS_STORE
, &is_store
, -1);
439 flags
|= CAMEL_FOLDER_NOSELECT
;
441 return (flags
& priv
->excluded
) == 0;
444 /* NOTE: Removes and frees the selected uri structure */
446 folder_tree_select_uri (EMFolderTree
*folder_tree
,
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
);
468 folder_tree_expand_node (const gchar
*key
,
469 EMFolderTree
*folder_tree
)
471 GtkTreeRowReference
*row
= NULL
;
472 GtkTreeView
*tree_view
;
475 EMailSession
*session
;
476 CamelService
*service
;
480 struct _selected_uri
*u
;
482 if (!(p
= strchr (key
, '/')))
487 uid
= g_alloca (n
+ 1);
488 memcpy (uid
, key
, n
);
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')
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
);
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
);
521 folder_tree_select_uri (folder_tree
, path
, u
);
523 gtk_tree_path_free (path
);
527 folder_tree_maybe_expand_row (EMFolderTreeModel
*model
,
528 GtkTreePath
*tree_path
,
530 EMFolderTree
*folder_tree
)
532 EMFolderTreePrivate
*priv
= folder_tree
->priv
;
537 struct _selected_uri
*u
;
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
);
550 gchar
*c
= strrchr (key
, '/');
552 /* 'c' cannot be NULL, because the above contructed 'key' has it there */
553 /* coverity[dereference] */
555 folder_tree_expand_node (key
, folder_tree
);
557 folder_tree_select_uri (folder_tree
, tree_path
, u
);
565 folder_tree_row_changed_cb (GtkTreeModel
*model
,
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
)
579 if (gtk_tree_path_get_depth (path
) != 1)
582 gtk_tree_model_get (model
, iter
,
583 COL_OBJECT_CAMEL_STORE
, &service
,
584 COL_BOOL_IS_STORE
, &is_store
,
587 if (is_store
&& service
!= NULL
) {
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);
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
);
616 folder_tree_clear_selected_list (EMFolderTree
*folder_tree
)
618 EMFolderTreePrivate
*priv
= folder_tree
->priv
;
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
;
630 folder_tree_cell_edited_cb (EMFolderTree
*folder_tree
,
631 const gchar
*path_string
,
632 const gchar
*new_name
)
634 CamelFolderInfo
*folder_info
;
636 GtkTreeView
*tree_view
;
640 gchar
*old_name
= NULL
;
641 gchar
*old_full_name
= NULL
;
642 gchar
*new_full_name
= NULL
;
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
);
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
)
671 if (old_full_name
== NULL
)
674 if (g_strcmp0 (new_name
, old_name
) == 0)
677 /* Check for invalid characters. */
678 if (strchr (new_name
, '/') != NULL
) {
679 e_alert_run_dialog_for_args (
680 parent
, "mail:no-rename-folder",
682 _("Folder names cannot contain “/”"), NULL
);
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
);
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
);
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
);
720 folder_uri
= e_mail_folder_uri_build (store
, new_full_name
);
721 em_folder_tree_set_selected (folder_tree
, folder_uri
, FALSE
);
726 g_free (old_full_name
);
727 g_free (new_full_name
);
728 g_clear_object (&store
);
732 folder_tree_render_store_icon (GtkTreeViewColumn
*column
,
733 GtkCellRenderer
*renderer
,
736 gpointer text_renderer
)
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
);
748 g_object_get (text_renderer
, "is-expanded", &expanded
, NULL
);
750 /* The second prerequisite: it's not expanded and children has unread mismatch. */
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
,
759 children_has_unread_mismatch
= unread
!= unread_last_sel
;
762 g_object_set (renderer
, "visible", !expanded
&& children_has_unread_mismatch
, NULL
);
766 folder_tree_reset_store_unread_value_cb (GtkTreeView
*tree_view
,
774 g_return_if_fail (GTK_IS_TREE_VIEW (tree_view
));
776 model
= gtk_tree_view_get_model (tree_view
);
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,
789 subdirs_contain_unread (GtkTreeModel
*model
,
795 if (!gtk_tree_model_iter_children (model
, &iter
, root
))
799 gtk_tree_model_get (model
, &iter
, COL_UINT_UNREAD
, &unread
, -1);
803 if (gtk_tree_model_iter_has_child (model
, &iter
))
804 if (subdirs_contain_unread (model
, &iter
))
806 } while (gtk_tree_model_iter_next (model
, &iter
));
812 folder_tree_render_display_name (GtkTreeViewColumn
*column
,
813 GtkCellRenderer
*renderer
,
817 CamelService
*service
;
819 gboolean is_store
, bold
, subdirs_unread
= FALSE
;
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
);
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
);
882 g_object_set (renderer
, "text", name
, NULL
);
886 g_clear_object (&service
);
890 folder_tree_render_icon (GtkTreeViewColumn
*column
,
891 GtkCellRenderer
*renderer
,
895 GtkTreeSelection
*selection
;
896 GtkTreePath
*drag_dest_row
;
897 GtkWidget
*tree_view
;
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;
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
,
917 if (icon_name
== NULL
)
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
) {
929 path
= gtk_tree_model_get_path (model
, iter
);
930 if (gtk_tree_path_compare (path
, drag_dest_row
) == 0)
932 gtk_tree_path_free (path
);
934 gtk_tree_path_free (drag_dest_row
);
937 if (g_strcmp0 (icon_name
, "folder") == 0) {
940 icon_name
= g_strdup ("folder-open");
941 } else if (is_drag_dest
) {
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
) {
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
);
970 g_object_set (renderer
, "gicon", icon
, NULL
);
972 g_object_unref (icon
);
977 folder_tree_selection_changed_cb (EMFolderTree
*folder_tree
,
978 GtkTreeSelection
*selection
)
983 CamelStore
*store
= NULL
;
984 CamelFolderInfoFlags flags
= 0;
986 guint old_unread
= 0;
987 gchar
*folder_name
= NULL
;
989 list
= gtk_tree_selection_get_selected_rows (selection
, &model
);
994 gtk_tree_model_get_iter (model
, &iter
, list
->data
);
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);
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
);
1024 folder_tree_copy_expanded_cb (GtkTreeView
*unused
,
1026 GtkTreeView
*tree_view
)
1028 gtk_tree_view_expand_row (tree_view
, path
, FALSE
);
1032 folder_tree_copy_selection_cb (GtkTreeModel
*model
,
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);
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
)
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
);
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
);
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
);
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
);
1121 folder_tree_set_property (GObject
*object
,
1123 const GValue
*value
,
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
));
1134 gtk_tree_view_set_model (
1135 GTK_TREE_VIEW (object
),
1136 g_value_get_object (value
));
1140 folder_tree_set_session (
1141 EM_FOLDER_TREE (object
),
1142 g_value_get_object (value
));
1146 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, property_id
, pspec
);
1150 folder_tree_get_property (GObject
*object
,
1155 switch (property_id
) {
1156 case PROP_ALERT_SINK
:
1157 g_value_set_object (
1159 em_folder_tree_get_alert_sink (
1160 EM_FOLDER_TREE (object
)));
1163 case PROP_COPY_TARGET_LIST
:
1166 folder_tree_get_copy_target_list (
1167 EM_FOLDER_TREE (object
)));
1171 g_value_set_object (
1173 gtk_tree_view_get_model (
1174 GTK_TREE_VIEW (object
)));
1177 case PROP_PASTE_TARGET_LIST
:
1180 folder_tree_get_paste_target_list (
1181 EM_FOLDER_TREE (object
)));
1185 g_value_set_object (
1187 em_folder_tree_get_session (
1188 EM_FOLDER_TREE (object
)));
1192 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, property_id
, pspec
);
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
);
1254 folder_tree_finalize (GObject
*object
)
1256 EMFolderTreePrivate
*priv
;
1258 priv
= EM_FOLDER_TREE_GET_PRIVATE (object
);
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
);
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
;
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 (
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
);
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
;
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)
1407 if (!gtk_tree_view_get_path_at_pos (
1408 tree_view
, event
->x
, event
->y
,
1409 &path
, NULL
, NULL
, NULL
))
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
);
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
);
1434 folder_tree_key_press_event (GtkWidget
*widget
,
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
);
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
);
1469 folder_tree_popup_menu (GtkWidget
*widget
)
1471 folder_tree_emit_popup_event (EM_FOLDER_TREE (widget
), NULL
);
1477 folder_tree_row_activated (GtkTreeView
*tree_view
,
1479 GtkTreeViewColumn
*column
)
1481 EMFolderTreePrivate
*priv
;
1482 GtkTreeModel
*model
;
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
)
1495 if (!gtk_tree_model_get_iter (model
, &iter
, path
))
1498 gtk_tree_model_get (
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
));
1507 tree_view
, signals
[FOLDER_SELECTED
], 0,
1508 store
, folder_name
, flags
);
1511 tree_view
, signals
[FOLDER_ACTIVATED
], 0,
1512 store
, folder_name
);
1514 g_free (folder_name
);
1515 g_clear_object (&store
);
1519 folder_tree_test_collapse_row (GtkTreeView
*tree_view
,
1523 GtkTreeSelection
*selection
;
1524 GtkTreeModel
*model
;
1527 selection
= gtk_tree_view_get_selection (tree_view
);
1529 if (!gtk_tree_selection_get_selected (selection
, &model
, &cursor
))
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
);
1542 folder_tree_row_expanded (GtkTreeView
*tree_view
,
1546 EActivity
*activity
;
1547 GCancellable
*cancellable
;
1548 EMFolderTree
*folder_tree
;
1549 AsyncContext
*context
;
1550 GtkTreeModel
*model
;
1555 folder_tree
= EM_FOLDER_TREE (tree_view
);
1556 model
= gtk_tree_view_get_model (tree_view
);
1558 gtk_tree_model_get (
1560 COL_STRING_FULL_NAME
, &full_name
,
1561 COL_OBJECT_CAMEL_STORE
, &store
,
1562 COL_BOOL_LOAD_SUBDIRS
, &load
, -1);
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 (
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
,
1593 g_clear_object (&store
);
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 (
1625 g_param_spec_object (
1631 G_PARAM_CONSTRUCT_ONLY
|
1632 G_PARAM_STATIC_STRINGS
));
1634 /* Inherited from ESelectableInterface */
1635 g_object_class_override_property (
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 (
1646 g_param_spec_object (
1649 "The model for the tree view",
1650 GTK_TYPE_TREE_MODEL
,
1652 G_PARAM_CONSTRUCT_ONLY
));
1654 /* Inherited from ESelectableInterface */
1655 g_object_class_override_property (
1657 PROP_PASTE_TARGET_LIST
,
1658 "paste-target-list");
1660 g_object_class_install_property (
1663 g_param_spec_object (
1667 E_TYPE_MAIL_SESSION
,
1669 G_PARAM_CONSTRUCT_ONLY
|
1670 G_PARAM_STATIC_STRINGS
));
1672 signals
[FOLDER_SELECTED
] = g_signal_new (
1674 G_OBJECT_CLASS_TYPE (object_class
),
1676 G_STRUCT_OFFSET (EMFolderTreeClass
, folder_selected
),
1678 e_marshal_VOID__OBJECT_STRING_UINT
,
1684 signals
[FOLDER_ACTIVATED
] = g_signal_new (
1686 G_OBJECT_CLASS_TYPE (object_class
),
1688 G_STRUCT_OFFSET (EMFolderTreeClass
, folder_activated
),
1690 e_marshal_VOID__OBJECT_STRING
,
1695 signals
[POPUP_EVENT
] = g_signal_new (
1697 G_OBJECT_CLASS_TYPE (object_class
),
1698 G_SIGNAL_RUN_FIRST
| G_SIGNAL_ACTION
,
1699 G_STRUCT_OFFSET (EMFolderTreeClass
, popup_event
),
1701 g_cclosure_marshal_VOID__BOXED
,
1703 GDK_TYPE_EVENT
| G_SIGNAL_TYPE_STATIC_SCOPE
);
1705 signals
[HIDDEN_KEY_EVENT
] = g_signal_new (
1707 G_OBJECT_CLASS_TYPE (object_class
),
1709 G_STRUCT_OFFSET (EMFolderTreeClass
, hidden_key_event
),
1711 g_cclosure_marshal_VOID__BOXED
,
1713 GDK_TYPE_EVENT
| G_SIGNAL_TYPE_STATIC_SCOPE
);
1717 em_folder_tree_init (EMFolderTree
*folder_tree
)
1719 GHashTable
*select_uris_table
;
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. */
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
;
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
);
1775 folder_tree_selectable_cut_clipboard (ESelectable
*selectable
)
1777 ESelectableInterface
*iface
;
1778 EMFolderTree
*folder_tree
;
1781 folder_tree
= EM_FOLDER_TREE (selectable
);
1782 proxy
= folder_tree
->priv
->selectable
;
1784 if (!E_IS_SELECTABLE (proxy
))
1787 iface
= E_SELECTABLE_GET_INTERFACE (proxy
);
1789 if (iface
->cut_clipboard
== NULL
)
1792 if (gtk_widget_get_can_focus (proxy
))
1793 gtk_widget_grab_focus (proxy
);
1795 iface
->cut_clipboard (E_SELECTABLE (proxy
));
1799 folder_tree_selectable_copy_clipboard (ESelectable
*selectable
)
1801 ESelectableInterface
*iface
;
1802 EMFolderTree
*folder_tree
;
1805 folder_tree
= EM_FOLDER_TREE (selectable
);
1806 proxy
= folder_tree
->priv
->selectable
;
1808 if (!E_IS_SELECTABLE (proxy
))
1811 iface
= E_SELECTABLE_GET_INTERFACE (proxy
);
1813 if (iface
->copy_clipboard
== NULL
)
1816 if (gtk_widget_get_can_focus (proxy
))
1817 gtk_widget_grab_focus (proxy
);
1819 iface
->copy_clipboard (E_SELECTABLE (proxy
));
1823 folder_tree_selectable_paste_clipboard (ESelectable
*selectable
)
1825 ESelectableInterface
*iface
;
1826 EMFolderTree
*folder_tree
;
1829 folder_tree
= EM_FOLDER_TREE (selectable
);
1830 proxy
= folder_tree
->priv
->selectable
;
1832 if (!E_IS_SELECTABLE (proxy
))
1835 iface
= E_SELECTABLE_GET_INTERFACE (proxy
);
1837 if (iface
->paste_clipboard
== NULL
)
1840 if (gtk_widget_get_can_focus (proxy
))
1841 gtk_widget_grab_focus (proxy
);
1843 iface
->paste_clipboard (E_SELECTABLE (proxy
));
1847 folder_tree_selectable_delete_selection (ESelectable
*selectable
)
1849 ESelectableInterface
*iface
;
1850 EMFolderTree
*folder_tree
;
1853 folder_tree
= EM_FOLDER_TREE (selectable
);
1854 proxy
= folder_tree
->priv
->selectable
;
1856 if (!E_IS_SELECTABLE (proxy
))
1859 iface
= E_SELECTABLE_GET_INTERFACE (proxy
);
1861 if (iface
->delete_selection
== NULL
)
1864 if (gtk_widget_get_can_focus (proxy
))
1865 gtk_widget_grab_focus (proxy
);
1867 iface
->delete_selection (E_SELECTABLE (proxy
));
1871 folder_tree_selectable_select_all (ESelectable
*selectable
)
1873 ESelectableInterface
*iface
;
1874 EMFolderTree
*folder_tree
;
1877 folder_tree
= EM_FOLDER_TREE (selectable
);
1878 proxy
= folder_tree
->priv
->selectable
;
1880 if (!E_IS_SELECTABLE (proxy
))
1883 iface
= E_SELECTABLE_GET_INTERFACE (proxy
);
1885 if (iface
->select_all
== NULL
)
1888 if (gtk_widget_get_can_focus (proxy
))
1889 gtk_widget_grab_focus (proxy
);
1891 iface
->select_all (E_SELECTABLE (proxy
));
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
;
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
);
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
,
1932 "model", model
, NULL
);
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
);
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
;
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
;
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
;
1986 GtkTreeModel
*model
;
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
))
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
);
2005 tree_drag_data_get (GtkWidget
*widget
,
2006 GdkDragContext
*context
,
2007 GtkSelectionData
*selection
,
2010 EMFolderTree
*folder_tree
)
2012 EMFolderTreePrivate
*priv
= folder_tree
->priv
;
2013 GtkTreeModel
*model
;
2014 GtkTreePath
*src_path
;
2015 CamelFolder
*folder
;
2016 CamelStore
*store
= NULL
;
2018 gchar
*folder_name
= NULL
;
2021 if (!priv
->drag_row
|| !(src_path
=
2022 gtk_tree_row_reference_get_path (priv
->drag_row
)))
2025 model
= gtk_tree_view_get_model (GTK_TREE_VIEW (folder_tree
));
2027 if (!gtk_tree_model_get_iter (model
, &iter
, src_path
))
2030 gtk_tree_model_get (
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
)
2039 folder_uri
= e_mail_folder_uri_build (store
, folder_name
);
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);
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
);
2065 g_free (folder_uri
);
2068 gtk_tree_path_free (src_path
);
2069 g_clear_object (&store
);
2070 g_free (folder_name
);
2074 ask_drop_folder (EMFolderTree
*folder_tree
,
2075 const gchar
*src_folder_uri
,
2076 const gchar
*des_full_name
,
2077 CamelStore
*des_store
,
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
;
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
);
2106 } else if (g_strcmp0 (set_value
, "always") == 0) {
2107 g_object_unref (settings
);
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
) {
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
);
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
);
2149 "mail:online-operation",
2150 src_store_is_local
? des_folder
: src_folder
,
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
);
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 (
2168 is_move
? "mail:ask-folder-drop-move" : "mail:ask-folder-drop-copy",
2170 des_full_name
&& *des_full_name
? des_folder
:
2171 camel_service_get_display_name (CAMEL_SERVICE (des_store
)),
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
;
2191 struct _DragDataReceivedAsync
{
2195 GdkDragContext
*context
;
2197 /* Only selection->data and selection->length are valid */
2198 GtkSelectionData
*selection
;
2200 EMFolderTree
*folder_tree
;
2201 EMailSession
*session
;
2204 gchar
*dest_folder_uri
;
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
;
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
);
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
, '/');
2246 folder_name
= 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
);
2255 m
->dest_folder_uri
= e_mail_folder_uri_build (m
->store
, folder_name
);
2258 g_object_unref (folder
);
2262 folder_tree_drop_async__desc (struct _DragDataReceivedAsync
*m
)
2266 data
= gtk_selection_data_get_data (m
->selection
);
2268 if (m
->info
== DND_DROP_TYPE_FOLDER
) {
2269 gchar
*folder_name
= NULL
;
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
);
2278 res
= g_strdup_printf (
2279 _("Moving folder %s"), folder_name
);
2281 res
= g_strdup_printf (
2282 _("Copying folder %s"), folder_name
);
2283 g_free (folder_name
);
2288 return g_strdup_printf (
2289 _("Moving messages into folder %s"),
2292 return g_strdup_printf (
2293 _("Copying messages into folder %s"),
2299 folder_tree_drop_async__exec (struct _DragDataReceivedAsync
*m
,
2300 GCancellable
*cancellable
,
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
) {
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
))) {
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
);
2324 case DND_DROP_TYPE_MESSAGE_RFC822
:
2325 /* import a message/rfc822 stream */
2326 em_utils_selection_get_message (m
->selection
, folder
);
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
);
2335 g_object_unref (folder
);
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
2368 tree_drag_data_action (struct _DragDataReceivedAsync
*m
)
2370 m
->move
= m
->action
== GDK_ACTION_MOVE
;
2371 mail_msg_unordered_push (m
);
2375 tree_drag_data_received (GtkWidget
*widget
,
2376 GdkDragContext
*context
,
2379 GtkSelectionData
*selection
,
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
;
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
))
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
);
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
);
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
);
2422 gtk_tree_model_get (
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
);
2436 if (info
== DND_DROP_TYPE_FOLDER
&&
2437 !ask_drop_folder (folder_tree
,
2438 (const gchar
*) gtk_selection_data_get_data (selection
),
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
);
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
);
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
);
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;
2477 folder_tree_drop_target (EMFolderTree
*folder_tree
,
2478 GdkDragContext
*context
,
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
;
2490 GtkTreeModel
*model
;
2494 gboolean src_is_local
;
2495 gboolean src_is_vfolder
;
2496 gboolean dst_is_vfolder
;
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
))
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 (
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. */
2526 if (g_strcmp0 (dst_full_name
, CAMEL_UNMATCHED_NAME
) == 0)
2529 /* Don't allow copying/moving into a vTrash folder. */
2530 if (g_strcmp0 (dst_full_name
, CAMEL_VTRASH_NAME
) == 0)
2533 /* Don't allow copying/moving into a vJunk folder. */
2534 if (g_strcmp0 (dst_full_name
, CAMEL_VJUNK_NAME
) == 0)
2537 if (flags
& CAMEL_FOLDER_NOSELECT
)
2541 GtkTreePath
*src_path
= gtk_tree_row_reference_get_path (p
->drag_row
);
2544 guint32 src_flags
= 0;
2546 if (gtk_tree_model_get_iter (model
, &iter
, src_path
))
2547 gtk_tree_model_get (
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
);
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 */
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
) {
2581 targets
= targets
->next
;
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
));
2595 (g_strcmp0 (uid
, E_MAIL_SESSION_LOCAL_UID
) == 0);
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
)) {
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
) {
2616 targets
= targets
->next
;
2622 /* Don't allow copying/moving the UNMATCHED vfolder. */
2624 if (g_strcmp0 (src_full_name
, CAMEL_UNMATCHED_NAME
) == 0)
2627 /* Don't allow copying/moving any vTrash folder. */
2628 if (g_strcmp0 (src_full_name
, CAMEL_VTRASH_NAME
) == 0)
2631 /* Don't allow copying/moving any vJunk folder. */
2632 if (g_strcmp0 (src_full_name
, CAMEL_VJUNK_NAME
) == 0)
2635 /* Don't allow copying/moving any maildir 'inbox'. */
2636 if (g_strcmp0 (src_full_name
, ".") == 0)
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
) {
2648 xfolder
= drop_atoms
[DND_DROP_TYPE_FOLDER
];
2649 while (targets
!= NULL
) {
2650 if (targets
->data
== (gpointer
) xfolder
) {
2655 targets
= targets
->next
;
2663 /* Can't drag anything but a Search Folder into a Search Folder. */
2667 /* Now we either have a store or a normal folder. */
2672 xfolder
= drop_atoms
[DND_DROP_TYPE_FOLDER
];
2673 while (targets
!= NULL
) {
2674 if (targets
->data
== (gpointer
) xfolder
) {
2679 targets
= targets
->next
;
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
];
2697 g_free (dst_full_name
);
2698 g_free (src_full_name
);
2699 g_clear_object (&dst_store
);
2700 g_clear_object (&src_store
);
2706 tree_drag_drop (GtkWidget
*widget
,
2707 GdkDragContext
*context
,
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
;
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
))
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
);
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 */
2766 tree_drag_leave (GtkWidget
*widget
,
2767 GdkDragContext
*context
,
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
2796 tree_autoscroll (gpointer user_data
)
2798 EMFolderTree
*folder_tree
;
2799 GtkAdjustment
*adjustment
;
2800 GtkTreeView
*tree_view
;
2801 GtkScrollable
*scrollable
;
2804 GdkDisplay
*display
;
2805 GdkDeviceManager
*device_manager
;
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. */
2827 /* See if we are near the top edge. */
2828 offset
= y
- (rect
.y
+ 2 * SCROLL_EDGE_SIZE
);
2830 /* See if we are near the bottom edge. */
2831 offset
= y
- (rect
.y
+ rect
.height
- 2 * SCROLL_EDGE_SIZE
);
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));
2845 tree_autoexpand (gpointer user_data
)
2847 EMFolderTreePrivate
*priv
;
2848 GtkTreeView
*tree_view
;
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
);
2862 tree_drag_motion (GtkWidget
*widget
,
2863 GdkDragContext
*context
,
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
;
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
))
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
);
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
)
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 (
2942 GTK_TREE_VIEW_DROP_INTO_OR_AFTER
);
2945 gtk_tree_view_set_drag_dest_row (
2947 GTK_TREE_VIEW_DROP_INTO_OR_AFTER
);
2948 chosen_action
= suggested_action
;
2955 gdk_drag_status (context
, chosen_action
, time
);
2956 gtk_tree_path_free (path
);
2958 return chosen_action
!= 0;
2962 em_folder_tree_enable_drag_and_drop (EMFolderTree
*folder_tree
)
2964 GtkTreeView
*tree_view
;
2965 static gint setup
= 0;
2968 g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree
));
2970 tree_view
= GTK_TREE_VIEW (folder_tree
);
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
);
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
);
2986 GTK_WIDGET (tree_view
), GTK_DEST_DEFAULT_ALL
, drop_types
,
2987 NUM_DROP_TYPES
, GDK_ACTION_COPY
| GDK_ACTION_MOVE
);
2990 tree_view
, "drag-begin",
2991 G_CALLBACK (tree_drag_begin
), folder_tree
);
2993 tree_view
, "drag-data-get",
2994 G_CALLBACK (tree_drag_data_get
), folder_tree
);
2996 tree_view
, "drag-data-received",
2997 G_CALLBACK (tree_drag_data_received
), folder_tree
);
2999 tree_view
, "drag-drop",
3000 G_CALLBACK (tree_drag_drop
), folder_tree
);
3002 tree_view
, "drag-end",
3003 G_CALLBACK (tree_drag_end
), folder_tree
);
3005 tree_view
, "drag-leave",
3006 G_CALLBACK (tree_drag_leave
), folder_tree
);
3008 tree_view
, "drag-motion",
3009 G_CALLBACK (tree_drag_motion
), folder_tree
);
3013 em_folder_tree_set_excluded (EMFolderTree
*folder_tree
,
3016 g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree
));
3018 folder_tree
->priv
->excluded
= flags
;
3022 em_folder_tree_set_excluded_func (EMFolderTree
*folder_tree
,
3023 EMFTExcludeFunc exclude
,
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
;
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
;
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
)) {
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
)) {
3055 GtkTreePath
*path
= l
->data
;
3057 if (gtk_tree_model_get_iter (model
, &iter
, path
)) {
3061 gtk_tree_model_get (
3063 COL_OBJECT_CAMEL_STORE
, &store
,
3064 COL_STRING_FULL_NAME
, &folder_name
, -1);
3066 if (CAMEL_IS_STORE (store
) && folder_name
!= NULL
) {
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
);
3081 return g_list_reverse (list
);
3085 get_selected_uris_path_iterate (GtkTreeModel
*model
,
3086 GtkTreePath
*treepath
,
3090 GList
**list
= (GList
**) data
;
3093 gtk_tree_model_get (model
, iter
, COL_STRING_FULL_NAME
, &full_name
, -1);
3094 *list
= g_list_append (*list
, full_name
);
3098 em_folder_tree_get_selected_paths (EMFolderTree
*folder_tree
)
3100 GtkTreeSelection
*selection
;
3101 GtkTreeView
*tree_view
;
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
);
3114 em_folder_tree_set_selected_list (EMFolderTree
*folder_tree
,
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? */
3125 folder_tree_clear_selected_list (folder_tree
);
3127 for (; list
; list
= list
->next
) {
3129 struct _selected_uri
*u
;
3130 const gchar
*folder_uri
;
3137 /* This makes sure all our parents up to the root are
3140 folder_uri
= list
->data
;
3142 success
= e_mail_folder_uri_parse (
3143 CAMEL_SESSION (session
), folder_uri
,
3144 &store
, &folder_name
, NULL
);
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
);
3159 g_hash_table_insert (
3160 priv
->select_uris_table
, u
->key
, u
);
3162 g_slist_append (priv
->select_uris
, u
);
3165 while (end
= strrchr (expand_key
, '/'), end
) {
3166 folder_tree_expand_node (expand_key
, folder_tree
);
3171 folder_tree_free_select_uri (u
);
3173 g_free (expand_key
);
3179 dump_fi (CamelFolderInfo
*fi
,
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
);
3191 dump_fi (fi
->child
, depth
+ 1);
3199 em_folder_tree_set_selected (EMFolderTree
*folder_tree
,
3201 gboolean expand_only
)
3205 g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree
));
3208 l
= g_list_append (l
, (gpointer
) uri
);
3210 em_folder_tree_set_selected_list (folder_tree
, l
, expand_only
);
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
;
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
);
3235 if (gtk_tree_model_iter_has_child (model
, &iter
)) {
3236 if (!gtk_tree_model_iter_children (model
, &child
, &iter
))
3238 path
= gtk_tree_model_get_path (model
, &child
);
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
);
3254 /* Reached end. Wrapup*/
3255 if (gtk_tree_model_get_iter_first (model
, &iter
))
3256 path
= gtk_tree_model_get_path (model
, &iter
);
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
);
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
);
3295 folder_tree_descend (GtkTreeModel
*model
,
3302 /* Finds the rightmost descendant of the given root. */
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);
3316 n_children
= gtk_tree_model_iter_n_children (model
, &parent
);
3318 while (n_children
> 0) {
3321 gtk_tree_model_iter_nth_child (
3322 model
, &child
, &parent
, n_children
- 1);
3326 n_children
= gtk_tree_model_iter_n_children (model
, &parent
);
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
;
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
))
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
);
3361 GtkTreeIter descendant
;
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
);
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
);
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
;
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
);
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
);
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
;
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
))
3464 gtk_tree_model_get (
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
);
3480 if (out_store
!= NULL
)
3481 *out_store
= g_object_ref (store
);
3483 if (out_folder_name
!= NULL
)
3484 *out_folder_name
= folder_name
;
3486 g_free (folder_name
);
3488 g_clear_object (&store
);
3494 em_folder_tree_store_root_selected (EMFolderTree
*folder_tree
,
3495 CamelStore
**out_store
)
3497 GtkTreeView
*tree_view
;
3498 GtkTreeSelection
*selection
;
3499 GtkTreeModel
*model
;
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
))
3513 gtk_tree_model_get (
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
);
3522 g_clear_object (&store
);
3526 if (out_store
!= NULL
)
3527 *out_store
= g_object_ref (store
);
3529 g_clear_object (&store
);
3535 em_folder_tree_get_selected_uri (EMFolderTree
*folder_tree
)
3537 GtkTreeView
*tree_view
;
3538 GtkTreeSelection
*selection
;
3539 GtkTreeModel
*model
;
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
))
3553 gtk_tree_model_get (
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
);
3564 folder_uri
= e_mail_folder_uri_build (store
, "");
3566 g_free (folder_name
);
3567 g_clear_object (&store
);
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
3585 em_folder_tree_ref_selected_store (EMFolderTree
*folder_tree
)
3587 GtkTreeView
*tree_view
;
3588 GtkTreeSelection
*selection
;
3589 GtkTreeModel
*model
;
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 (
3604 COL_OBJECT_CAMEL_STORE
, &store
, -1);
3610 em_folder_tree_set_skip_double_click (EMFolderTree
*folder_tree
,
3613 folder_tree
->priv
->skip_double_click
= skip
;
3616 /* stores come first, then by uri */
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
) {
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 */
3643 em_folder_tree_restore_state (EMFolderTree
*folder_tree
,
3646 EMFolderTreeModel
*folder_tree_model
;
3647 EMailSession
*session
;
3648 GtkTreeModel
*tree_model
;
3649 GtkTreeView
*tree_view
;
3653 GSList
*groups
, *group
;
3656 /* Make sure we have a key file to restore state from. */
3657 if (key_file
== NULL
)
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.
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
);
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
);
3711 if (service
!= NULL
)
3712 g_object_unref (service
);
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
);
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
)) {
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
);
3744 g_object_unref (store
);
3745 g_free (folder_name
);
3748 g_slist_free (groups
);
3749 g_strfreev (groups_arr
);
3753 valid
= gtk_tree_model_get_iter_first (tree_model
, &iter
);
3757 CamelService
*service
;
3758 const gchar
*key
= STATE_KEY_EXPANDED
;
3762 gtk_tree_model_get (
3764 COL_OBJECT_CAMEL_STORE
, &store
, -1);
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
)) {
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
);
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.
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)
3810 g_free (folder_tree
->priv
->select_store_uid_when_added
);
3811 folder_tree
->priv
->select_store_uid_when_added
= g_strdup (store_uid
);