# (C) Copyright 2004-2023 Enthought, Inc., Austin, TX
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD
# license included in LICENSE.txt and may be redistributed only under
# the conditions described in the aforementioned license. The license
# is also available online at http://www.enthought.com/licenses/BSD.txt
#
# Thanks for using Enthought open source!
""" Defines the tree node descriptor used by the tree editor and tree editor
factory classes.
"""
from itertools import zip_longest
from traits.api import (
Adapter,
Any,
Bool,
Callable,
Dict,
HasPrivateTraits,
Instance,
Interface,
isinterface,
List,
Property,
Str,
Supports,
Union,
cached_property,
)
from traits.trait_base import (
SequenceTypes,
get_resource_path,
xgetattr,
xsetattr,
)
from .ui_traits import AView
# -------------------------------------------------------------------------
# 'TreeNode' class:
# -------------------------------------------------------------------------
[docs]class TreeNode(HasPrivateTraits):
"""Represents a tree node. Used by the tree editor and tree editor factory
classes.
"""
# -------------------------------------------------------------------------
# Trait definitions:
# -------------------------------------------------------------------------
#: Name of trait containing children (if '', the node is a leaf). Nested
#: attributes are allowed, e.g., 'library.books'
children = Str()
#: Either the name of a trait containing a label, or a constant label, if
#: the string starts with '='.
label = Str()
#: The name of a trait containing a list of labels for any columns.
column_labels = Str()
#: Either the name of a trait containing a tooltip, or constant tooltip, if
#: the string starts with '='.
tooltip = Str()
#: Name to use for a new instance
name = Str()
#: Can the object's children be renamed?
rename = Bool(True)
#: Can the object be renamed?
rename_me = Bool(True)
#: Can the object's children be copied?
copy = Bool(True)
#: Can the object's children be deleted?
delete = Bool(True)
#: Can the object be deleted (if its parent allows it)?
delete_me = Bool(True)
#: Can children be inserted (vs. appended)?
insert = Bool(True)
#: Should tree nodes be automatically opened (expanded)?
auto_open = Bool(False)
#: Automatically close sibling tree nodes?
auto_close = Bool(False)
#: List of object classes than can be added or copied. Elements of the list
#: can be either:
#:
#: - the klass itself that can be added or copied
#: - 2-tuples of the form (klass, prompt) in which case klass is as above
#: and prompt is a boolean indicating whether or not to prompt the user
#: to specify trait values after instantiation when adding the object.
#: - 3-tuples of the form (klass, prompt, factory) in which case klass and
#: prompt are as above. factory is a callable that is expected to return
#: an instance of klass. Useful if klass has required traits. Otherwise
#: added object is created by simply instantiating klass()
#: ref: enthought/traits#1502
add = List(Any)
#: List of object classes that can be moved
move = List(Any)
#: List of object classes and/or interfaces that the node applies to
node_for = List(Any)
#: Tuple of object classes that the node applies to
node_for_class = Property(observe="node_for")
#: List of object interfaces that the node applies to
node_for_interface = Property(observe="node_for")
#: Function for formatting the label
formatter = Callable()
#: Functions for formatting the other columns.
column_formatters = List(Union(None, Callable))
#: Function for formatting the tooltip
tooltip_formatter = Callable()
#: Function for handling selecting an object
on_select = Callable()
#: Function for handling clicking an object
on_click = Callable()
#: Function for handling double-clicking an object
on_dclick = Callable()
#: Function for handling activation of an object
#: (double-click or Enter key press when node is in focus)
on_activated = Callable()
#: View to use for editing the object
view = AView
#: Right-click context menu. The value can be one of:
#:
#: - Instance( Menu ): Use this menu as the context menu
#: - None: Use the default context menu
#: - False: Do not display a context menu
menu = Any()
#: Name of leaf item icon
icon_item = Str("<item>")
#: Name of group item icon
icon_group = Str("<group>")
#: Name of opened group item icon
icon_open = Str("<open>")
#: Resource path used to locate the node icon
icon_path = Str()
#: Selector or name for background color
background = Any()
#: Selector or name for foreground color
foreground = Any()
# fixme: The 'menu' trait should really be defined as:
# Instance( 'traitsui.menu.MenuBar' ), but it doesn't work
# right currently.
#: A toolkit-appropriate cell renderer (currently Qt only)
renderer = Any()
#: A cache for listeners that need to keep state.
_listener_cache = Dict()
def __init__(self, **traits):
super().__init__(**traits)
if self.icon_path == "":
self.icon_path = get_resource_path()
# -- Property Implementations ---------------------------------------------
@cached_property
def _get_node_for_class(self):
return tuple(
[klass for klass in self.node_for if not isinterface(klass)]
)
@cached_property
def _get_node_for_interface(self):
return [klass for klass in self.node_for if isinterface(klass)]
# -- Overridable Methods: -------------------------------------------------
[docs] def allows_children(self, object):
"""Returns whether this object can have children."""
return self.children != ""
[docs] def has_children(self, object):
"""Returns whether the object has children."""
return len(self.get_children(object)) > 0
[docs] def get_children(self, object):
"""Gets the object's children."""
return getattr(object, self.children, None)
[docs] def get_children_id(self, object):
"""Gets the object's children identifier."""
return self.children
[docs] def append_child(self, object, child):
"""Appends a child to the object's children."""
children = self.get_children(object)
children.append(child)
[docs] def insert_child(self, object, index, child):
"""Inserts a child into the object's children."""
children = self.get_children(object)
children[index:index] = [child]
[docs] def confirm_delete(self, object):
"""Checks whether a specified object can be deleted.
The following return values are possible:
- **True** if the object should be deleted with no further prompting.
- **False** if the object should not be deleted.
- Anything else: Caller should take its default action (which might
include prompting the user to confirm deletion).
"""
return None
[docs] def delete_child(self, object, index):
"""Deletes a child at a specified index from the object's children."""
del self.get_children(object)[index]
[docs] def when_children_replaced(self, object, listener, remove):
"""Sets up or removes a listener for children being replaced on a
specified object.
"""
object.on_trait_change(
listener, self.children, remove=remove, dispatch="ui"
)
[docs] def when_children_changed(self, object, listener, remove):
"""Sets up or removes a listener for children being changed on a
specified object.
"""
object.on_trait_change(
listener,
self.children + "_items",
remove=remove,
dispatch="ui",
)
[docs] def get_label(self, object):
"""Gets the label to display for a specified object."""
label = self.label
if label[:1] == "=":
return label[1:]
label = xgetattr(object, label, "")
if self.formatter is None:
return label
return self.formatter(object, label)
[docs] def set_label(self, object, label):
"""Sets the label for a specified object."""
label_name = self.label
if label_name[:1] != "=":
xsetattr(object, label_name, label)
[docs] def when_label_changed(self, object, listener, remove):
"""Sets up or removes a listener for the label being changed on a
specified object.
"""
label = self.label
if label[:1] != "=":
memo = ("label", label, object, listener)
if not remove:
def wrapped_listener(target, name, new):
"""Ensure listener gets called with correct object."""
return listener(object, name, new)
self._listener_cache[memo] = wrapped_listener
else:
wrapped_listener = self._listener_cache.pop(memo, None)
if wrapped_listener is None:
return
object.on_trait_change(
wrapped_listener, label, remove=remove, dispatch="ui"
)
[docs] def get_column_labels(self, object):
"""Get the labels for any columns that have been defined."""
trait = self.column_labels
labels = xgetattr(object, trait, [])
formatted = []
for formatter, label in zip_longest(self.column_formatters, labels):
# If the list of column formatters is shorter than the list of
# labels, then zip_longest() will extend it with Nones. Just pass
# the label as preformatted. Similarly, explicitly using None in
# the list will pass through the item.
if formatter is None:
formatted.append(label)
else:
formatted.append(formatter(label))
return formatted
[docs] def when_column_labels_change(self, object, listener, remove):
"""Sets up or removes a listener for the column labels being changed on
a specified object.
This will fire when either the list is reassigned or when it is
modified. I.e., it listens both to the trait change event and the
trait_items change event. Implement the listener appropriately to
handle either case.
"""
trait = self.column_labels
if trait != "":
memo = ("column_label", trait, object, listener)
if not remove:
def wrapped_listener(target, name, new):
"""Ensure listener gets called with correct object."""
return listener(object, name, new)
self._listener_cache[memo] = wrapped_listener
else:
wrapped_listener = self._listener_cache.pop(memo, None)
if wrapped_listener is None:
return
object.on_trait_change(
wrapped_listener, trait, remove=remove, dispatch="ui"
)
object.on_trait_change(
wrapped_listener,
trait + "_items",
remove=remove,
dispatch="ui",
)
[docs] def get_icon(self, object, is_expanded):
"""Returns the icon for a specified object."""
if not self.allows_children(object):
return self.icon_item
if is_expanded:
return self.icon_open
return self.icon_group
[docs] def get_icon_path(self, object):
"""Returns the path used to locate an object's icon."""
return self.icon_path
[docs] def get_name(self, object):
"""Returns the name to use when adding a new object instance
(displayed in the "New" submenu).
"""
return self.name
[docs] def get_view(self, object):
"""Gets the view to use when editing an object."""
return self.view
[docs] def get_background(self, object):
background = self.background
if isinstance(background, str):
background = xgetattr(object, background, default=background)
return background
[docs] def get_foreground(self, object):
foreground = self.foreground
if isinstance(foreground, str):
foreground = xgetattr(object, foreground, default=foreground)
return foreground
[docs] def get_renderer(self, object, column=0):
"""Return the renderer for the object and column."""
return self.renderer
[docs] def can_rename(self, object):
"""Returns whether the object's children can be renamed."""
return self.rename
[docs] def can_rename_me(self, object):
"""Returns whether the object can be renamed."""
return self.rename_me
[docs] def can_copy(self, object):
"""Returns whether the object's children can be copied."""
return self.copy
[docs] def can_delete(self, object):
"""Returns whether the object's children can be deleted."""
return self.delete
[docs] def can_delete_me(self, object):
"""Returns whether the object can be deleted."""
return self.delete_me
[docs] def can_insert(self, object):
"""Returns whether the object's children can be inserted (vs.
appended).
"""
return self.insert
[docs] def can_auto_open(self, object):
"""Returns whether the object's children should be automatically
opened.
"""
return self.auto_open
[docs] def can_auto_close(self, object):
"""Returns whether the object's children should be automatically
closed.
"""
return self.auto_close
[docs] def is_node_for(self, object):
"""Returns whether this is the node that handles a specified object."""
return isinstance(
object, self.node_for_class
) or object.has_traits_interface(*self.node_for_interface)
[docs] def can_add(self, object, add_object):
"""Returns whether a given object is droppable on the node."""
klass = self._class_for(add_object)
if self.is_addable(klass):
return True
for item in self.move:
if type(item) in SequenceTypes:
item = item[0]
if issubclass(klass, item):
return True
return False
[docs] def get_add(self, object):
"""Returns the list of classes that can be added to the object."""
return self.add
[docs] def get_drag_object(self, object):
"""Returns a draggable version of a specified object."""
return object
[docs] def drop_object(self, object, dropped_object):
"""Returns a droppable version of a specified object."""
klass = self._class_for(dropped_object)
if self.is_addable(klass):
return dropped_object
for item in self.move:
if type(item) in SequenceTypes:
if issubclass(klass, item[0]):
return item[1](object, dropped_object)
elif issubclass(klass, item):
return dropped_object
return dropped_object
[docs] def select(self, object):
"""Handles an object being selected."""
if self.on_select is not None:
self.on_select(object)
return None
return True
[docs] def click(self, object):
"""Handles an object being clicked."""
if self.on_click is not None:
self.on_click(object)
return None
return True
[docs] def dclick(self, object):
"""Handles an object being double-clicked."""
if self.on_dclick is not None:
self.on_dclick(object)
return None
return True
[docs] def activated(self, object):
"""Handles an object being activated."""
if self.on_activated is not None:
self.on_activated(object)
return None
return True
[docs] def is_addable(self, klass):
"""Returns whether a specified object class can be added to the node."""
for item in self.add:
if type(item) in SequenceTypes:
item = item[0]
if issubclass(klass, item):
return True
return False
def _class_for(self, object):
"""Returns the class of an object."""
if isinstance(object, type):
return object
return object.__class__
# -------------------------------------------------------------------------
# 'ITreeNode' class
# -------------------------------------------------------------------------
[docs]class ITreeNode(Interface):
[docs] def allows_children(self):
"""Returns whether this object can have children."""
[docs] def has_children(self):
"""Returns whether the object has children."""
[docs] def get_children(self):
"""Gets the object's children."""
[docs] def get_children_id(self):
"""Gets the object's children identifier."""
[docs] def append_child(self, child):
"""Appends a child to the object's children."""
[docs] def insert_child(self, index, child):
"""Inserts a child into the object's children."""
[docs] def confirm_delete(self):
"""Checks whether a specified object can be deleted.
The following return values are possible:
- **True** if the object should be deleted with no further prompting.
- **False** if the object should not be deleted.
- Anything else: Caller should take its default action (which might
include prompting the user to confirm deletion).
"""
[docs] def delete_child(self, index):
"""Deletes a child at a specified index from the object's children."""
[docs] def when_children_replaced(self, listener, remove):
"""Sets up or removes a listener for children being replaced on a
specified object.
"""
[docs] def when_children_changed(self, listener, remove):
"""Sets up or removes a listener for children being changed on a
specified object.
"""
[docs] def get_label(self):
"""Gets the label to display for a specified object."""
[docs] def set_label(self, label):
"""Sets the label for a specified object."""
[docs] def when_label_changed(self, listener, remove):
"""Sets up or removes a listener for the label being changed on a
specified object.
"""
[docs] def get_column_labels(self, object):
"""Get the labels for any columns that have been defined."""
[docs] def when_column_labels_change(self, object, listener, remove):
"""Sets up or removes a listener for the column labels being changed
on a specified object.
This will fire when either the list is reassigned or when it is
modified. I.e., it listens both to the trait change event and the
trait_items change event. Implement the listener appropriately to
handle either case.
"""
[docs] def get_icon(self, is_expanded):
"""Returns the icon for a specified object."""
[docs] def get_icon_path(self):
"""Returns the path used to locate an object's icon."""
[docs] def get_name(self):
"""Returns the name to use when adding a new object instance
(displayed in the "New" submenu).
"""
[docs] def get_view(self):
"""Gets the view to use when editing an object."""
[docs] def can_rename(self):
"""Returns whether the object's children can be renamed."""
[docs] def can_rename_me(self):
"""Returns whether the object can be renamed."""
[docs] def can_copy(self):
"""Returns whether the object's children can be copied."""
[docs] def can_delete(self):
"""Returns whether the object's children can be deleted."""
[docs] def can_delete_me(self):
"""Returns whether the object can be deleted."""
[docs] def can_insert(self):
"""Returns whether the object's children can be inserted (vs.
appended).
"""
[docs] def can_auto_open(self):
"""Returns whether the object's children should be automatically
opened.
"""
[docs] def can_auto_close(self):
"""Returns whether the object's children should be automatically
closed.
"""
[docs] def can_add(self, add_object):
"""Returns whether a given object is droppable on the node."""
[docs] def get_add(self):
"""Returns the list of classes that can be added to the object."""
[docs] def get_drag_object(self):
"""Returns a draggable version of a specified object."""
[docs] def drop_object(self, dropped_object):
"""Returns a droppable version of a specified object."""
[docs] def select(self):
"""Handles an object being selected."""
[docs] def click(self):
"""Handles an object being clicked."""
[docs] def dclick(self):
"""Handles an object being double-clicked."""
[docs] def activated(self):
"""Handles an object being activated."""
# -------------------------------------------------------------------------
# 'ITreeNodeAdapter' class
# -------------------------------------------------------------------------
[docs]class ITreeNodeAdapter(Adapter):
"""Abstract base class for an adapter that implements the ITreeNode
interface.
Usage:
1. Create a subclass of ITreeNodeAdapter.
2. Register the adapter to define what class (or classes) this is an
ITreeNode adapter for (ie.
``register_factory(<from class>, ITreeNode, ITreeNodeAdapter)``).
3. Override any of the following methods as necessary, using the
``self.adaptee`` trait to access the adapted object if needed.
Note
----
This base class implements all of the ITreeNode interface methods,
but does not necessarily provide useful implementations for all of the
methods. It allows you to get a new adapter class up and running quickly,
but you should carefully review your final adapter implementation class
to make sure it behaves correctly in your application.
"""
[docs] def allows_children(self):
"""Returns whether this object can have children."""
return False
[docs] def has_children(self):
"""Returns whether the object has children."""
return False
[docs] def get_children(self):
"""Gets the object's children."""
return []
[docs] def get_children_id(self):
"""Gets the object's children identifier."""
return ""
[docs] def append_child(self, child):
"""Appends a child to the object's children."""
pass
[docs] def insert_child(self, index, child):
"""Inserts a child into the object's children."""
pass
[docs] def confirm_delete(self):
"""Checks whether a specified object can be deleted.
The following return values are possible:
- **True** if the object should be deleted with no further prompting.
- **False** if the object should not be deleted.
- Anything else: Caller should take its default action (which might
include prompting the user to confirm deletion).
"""
return False
[docs] def delete_child(self, index):
"""Deletes a child at a specified index from the object's children."""
pass
[docs] def when_children_replaced(self, listener, remove):
"""Sets up or removes a listener for children being replaced on a
specified object.
"""
pass
[docs] def when_children_changed(self, listener, remove):
"""Sets up or removes a listener for children being changed on a
specified object.
"""
pass
[docs] def get_label(self):
"""Gets the label to display for a specified object."""
return "No label specified"
[docs] def set_label(self, label):
"""Sets the label for a specified object."""
pass
[docs] def when_label_changed(self, listener, remove):
"""Sets up or removes a listener for the label being changed on a
specified object.
"""
pass
[docs] def get_column_labels(self):
"""Get the labels for any columns that have been defined."""
return []
[docs] def when_column_labels_change(self, listener, remove):
"""Sets up or removes a listener for the column labels being changed
on a specified object.
This will fire when either the list is reassigned or when it is
modified. I.e., it listens both to the trait change event and the
trait_items change event. Implement the listener appropriately to
handle either case.
"""
pass
[docs] def get_icon(self, is_expanded):
"""Returns the icon for a specified object.
Valid values are '<item>' (file looking icon), '<group>' (closed folder
looking icon) and '<open>' (open folder looking icon).
"""
return "<item>"
[docs] def get_icon_path(self):
"""Returns the path used to locate an object's icon."""
return ""
[docs] def get_name(self):
"""Returns the name to use when adding a new object instance
(displayed in the "New" submenu).
"""
return ""
[docs] def get_view(self):
"""Gets the view to use when editing an object."""
return None
[docs] def get_background(self):
"""Returns the background for object"""
return None
[docs] def get_foreground(self):
"""Returns the foreground for object"""
return None
[docs] def get_renderer(self, column=0):
"""Returns the renderer for object"""
return None
[docs] def can_rename(self):
"""Returns whether the object's children can be renamed."""
return False
[docs] def can_rename_me(self):
"""Returns whether the object can be renamed."""
return False
[docs] def can_copy(self):
"""Returns whether the object's children can be copied."""
return False
[docs] def can_delete(self):
"""Returns whether the object's children can be deleted."""
return False
[docs] def can_delete_me(self):
"""Returns whether the object can be deleted."""
return False
[docs] def can_insert(self):
"""Returns whether the object's children can be inserted (vs.
appended).
"""
return False
[docs] def can_auto_open(self):
"""Returns whether the object's children should be automatically
opened.
"""
return False
[docs] def can_auto_close(self):
"""Returns whether the object's children should be automatically
closed.
"""
return False
[docs] def can_add(self, add_object):
"""Returns whether a given object is droppable on the node."""
return False
[docs] def get_add(self):
"""Returns the list of classes that can be added to the object."""
return []
[docs] def get_drag_object(self):
"""Returns a draggable version of a specified object."""
return self.adaptee
[docs] def drop_object(self, dropped_object):
"""Returns a droppable version of a specified object."""
return dropped_object
[docs] def select(self):
"""Handles an object being selected."""
pass
[docs] def click(self):
"""Handles an object being clicked."""
pass
[docs] def dclick(self):
"""Handles an object being double-clicked."""
pass
[docs] def activated(self):
"""Handles an object being activated."""
pass
# -------------------------------------------------------------------------
# 'ITreeNodeAdapterBridge' class
# -------------------------------------------------------------------------
[docs]class ITreeNodeAdapterBridge(HasPrivateTraits):
"""Private class for use by a toolkit-specific implementation of the
TreeEditor to allow bridging the TreeNode interface used by the editor
to the ITreeNode interface used by object adapters.
"""
#: The ITreeNode adapter being bridged:
adapter = Supports(ITreeNode)
# -- TreeNode implementation ----------------------------------------------
[docs] def allows_children(self, object):
"""Returns whether this object can have children."""
return self.adapter.allows_children()
[docs] def has_children(self, object):
"""Returns whether the object has children."""
return self.adapter.has_children()
[docs] def get_children(self, object):
"""Gets the object's children."""
return self.adapter.get_children()
[docs] def get_children_id(self, object):
"""Gets the object's children identifier."""
return self.adapter.get_children_id()
[docs] def append_child(self, object, child):
"""Appends a child to the object's children."""
return self.adapter.append_child(child)
[docs] def insert_child(self, object, index, child):
"""Inserts a child into the object's children."""
return self.adapter.insert_child(index, child)
[docs] def confirm_delete(self, object):
"""Checks whether a specified object can be deleted.
The following return values are possible:
- **True** if the object should be deleted with no further prompting.
- **False** if the object should not be deleted.
- Anything else: Caller should take its default action (which might
include prompting the user to confirm deletion).
"""
return self.adapter.confirm_delete()
[docs] def delete_child(self, object, index):
"""Deletes a child at a specified index from the object's children."""
return self.adapter.delete_child(index)
[docs] def when_children_replaced(self, object, listener, remove):
"""Sets up or removes a listener for children being replaced on a
specified object.
"""
return self.adapter.when_children_replaced(listener, remove)
[docs] def when_children_changed(self, object, listener, remove):
"""Sets up or removes a listener for children being changed on a
specified object.
"""
return self.adapter.when_children_changed(listener, remove)
[docs] def get_label(self, object):
"""Gets the label to display for a specified object."""
return self.adapter.get_label()
[docs] def set_label(self, object, label):
"""Sets the label for a specified object."""
return self.adapter.set_label(label)
[docs] def when_label_changed(self, object, listener, remove):
"""Sets up or removes a listener for the label being changed on a
specified object.
"""
return self.adapter.when_label_changed(listener, remove)
[docs] def get_column_labels(self, object):
"""Get the labels for any columns that have been defined."""
return self.adapter.get_column_labels()
[docs] def when_column_labels_change(self, object, listener, remove):
"""Sets up or removes a listener for the column labels being changed
on a specified object.
This will fire when either the list is reassigned or when it is
modified. I.e., it listens both to the trait change event and the
trait_items change event. Implement the listener appropriately to
handle either case.
"""
return self.adapter.when_column_labels_change(listener, remove)
[docs] def get_icon(self, object, is_expanded):
"""Returns the icon for a specified object."""
return self.adapter.get_icon(is_expanded)
[docs] def get_icon_path(self, object):
"""Returns the path used to locate an object's icon."""
return self.adapter.get_icon_path()
[docs] def get_name(self, object):
"""Returns the name to use when adding a new object instance
(displayed in the "New" submenu).
"""
return self.adapter.get_name()
[docs] def get_view(self, object):
"""Gets the view to use when editing an object."""
return self.adapter.get_view()
[docs] def get_background(self, object):
"""Returns the background for object"""
return self.adapter.get_background()
[docs] def get_foreground(self, object):
"""Returns the foreground for object"""
return self.adapter.get_foreground()
[docs] def get_renderer(self, object, column=0):
"""Returns the renderer for object"""
return self.adapter.get_renderer(column)
[docs] def can_rename(self, object):
"""Returns whether the object's children can be renamed."""
return self.adapter.can_rename()
[docs] def can_rename_me(self, object):
"""Returns whether the object can be renamed."""
return self.adapter.can_rename_me()
[docs] def can_copy(self, object):
"""Returns whether the object's children can be copied."""
return self.adapter.can_copy()
[docs] def can_delete(self, object):
"""Returns whether the object's children can be deleted."""
return self.adapter.can_delete()
[docs] def can_delete_me(self, object):
"""Returns whether the object can be deleted."""
return self.adapter.can_delete_me()
[docs] def can_insert(self, object):
"""Returns whether the object's children can be inserted (vs.
appended).
"""
return self.adapter.can_insert()
[docs] def can_auto_open(self, object):
"""Returns whether the object's children should be automatically
opened.
"""
return self.adapter.can_auto_open()
[docs] def can_auto_close(self, object):
"""Returns whether the object's children should be automatically
closed.
"""
return self.adapter.can_auto_close()
[docs] def can_add(self, object, add_object):
"""Returns whether a given object is droppable on the node."""
return self.adapter.can_add(add_object)
[docs] def get_add(self, object):
"""Returns the list of classes that can be added to the object."""
return self.adapter.get_add()
[docs] def get_drag_object(self, object):
"""Returns a draggable version of a specified object."""
return self.adapter.get_drag_object()
[docs] def drop_object(self, object, dropped_object):
"""Returns a droppable version of a specified object."""
return self.adapter.drop_object(dropped_object)
[docs] def select(self, object):
"""Handles an object being selected."""
return self.adapter.select()
[docs] def click(self, object):
"""Handles an object being clicked."""
return self.adapter.click()
[docs] def dclick(self, object):
"""Handles an object being double-clicked."""
return self.adapter.dclick()
[docs] def activated(self, object):
"""Handles an object being activated."""
return self.adapter.activated()
# FIXME RTK: add the column_labels API to the following TreeNodes, too.
# -------------------------------------------------------------------------
# 'ObjectTreeNode' class
# -------------------------------------------------------------------------
[docs]class ObjectTreeNode(TreeNode):
[docs] def allows_children(self, object):
"""Returns whether this object can have children."""
return object.tno_allows_children(self)
[docs] def has_children(self, object):
"""Returns whether the object has children."""
return object.tno_has_children(self)
[docs] def get_children(self, object):
"""Gets the object's children."""
return object.tno_get_children(self)
[docs] def get_children_id(self, object):
"""Gets the object's children identifier."""
return object.tno_get_children_id(self)
[docs] def append_child(self, object, child):
"""Appends a child to the object's children."""
return object.tno_append_child(self, child)
[docs] def insert_child(self, object, index, child):
"""Inserts a child into the object's children."""
return object.tno_insert_child(self, index, child)
[docs] def confirm_delete(self, object):
"""Checks whether a specified object can be deleted.
The following return values are possible:
- **True** if the object should be deleted with no further prompting.
- **False** if the object should not be deleted.
- Anything else: Caller should take its default action (which might
include prompting the user to confirm deletion).
"""
return object.tno_confirm_delete(self)
[docs] def delete_child(self, object, index):
"""Deletes a child at a specified index from the object's children."""
return object.tno_delete_child(self, index)
[docs] def when_children_replaced(self, object, listener, remove):
"""Sets up or removes a listener for children being replaced on a
specified object.
"""
return object.tno_when_children_replaced(self, listener, remove)
[docs] def when_children_changed(self, object, listener, remove):
"""Sets up or removes a listener for children being changed on a
specified object.
"""
return object.tno_when_children_changed(self, listener, remove)
[docs] def get_label(self, object):
"""Gets the label to display for a specified object."""
return object.tno_get_label(self)
[docs] def set_label(self, object, label):
"""Sets the label for a specified object."""
return object.tno_set_label(self, label)
[docs] def when_label_changed(self, object, listener, remove):
"""Sets up or removes a listener for the label being changed on a
specified object.
"""
return object.tno_when_label_changed(self, listener, remove)
[docs] def get_icon(self, object, is_expanded):
"""Returns the icon for a specified object."""
return object.tno_get_icon(self, is_expanded)
[docs] def get_icon_path(self, object):
"""Returns the path used to locate an object's icon."""
return object.tno_get_icon_path(self)
[docs] def get_name(self, object):
"""Returns the name to use when adding a new object instance
(displayed in the "New" submenu).
"""
return object.tno_get_name(self)
[docs] def get_view(self, object):
"""Gets the view to use when editing an object."""
return object.tno_get_view(self)
[docs] def can_rename(self, object):
"""Returns whether the object's children can be renamed."""
return object.tno_can_rename(self)
[docs] def can_rename_me(self, object):
"""Returns whether the object can be renamed."""
return object.tno_can_rename_me(self)
[docs] def can_copy(self, object):
"""Returns whether the object's children can be copied."""
return object.tno_can_copy(self)
[docs] def can_delete(self, object):
"""Returns whether the object's children can be deleted."""
return object.tno_can_delete(self)
[docs] def can_delete_me(self, object):
"""Returns whether the object can be deleted."""
return object.tno_can_delete_me(self)
[docs] def can_insert(self, object):
"""Returns whether the object's children can be inserted (vs.
appended).
"""
return object.tno_can_insert(self)
[docs] def can_auto_open(self, object):
"""Returns whether the object's children should be automatically
opened.
"""
return object.tno_can_auto_open(self)
[docs] def can_auto_close(self, object):
"""Returns whether the object's children should be automatically
closed.
"""
return object.tno_can_auto_close(self)
[docs] def is_node_for(self, object):
"""Returns whether this is the node that should handle a
specified object.
"""
if isinstance(object, TreeNodeObject):
return object.tno_is_node_for(self)
return False
[docs] def can_add(self, object, add_object):
"""Returns whether a given object is droppable on the node."""
return object.tno_can_add(self, add_object)
[docs] def get_add(self, object):
"""Returns the list of classes that can be added to the object."""
return object.tno_get_add(self)
[docs] def get_drag_object(self, object):
"""Returns a draggable version of a specified object."""
return object.tno_get_drag_object(self)
[docs] def drop_object(self, object, dropped_object):
"""Returns a droppable version of a specified object."""
return object.tno_drop_object(self, dropped_object)
[docs] def select(self, object):
"""Handles an object being selected."""
return object.tno_select(self)
[docs] def click(self, object):
"""Handles an object being clicked."""
return object.tno_click(self)
[docs] def dclick(self, object):
"""Handles an object being double-clicked."""
return object.tno_dclick(self)
[docs] def activated(self, object):
"""Handles an object being activated."""
return object.tno_activated(self)
[docs]class TreeNodeObject(HasPrivateTraits):
"""Represents the object that corresponds to a tree node."""
#: A cache for listeners that need to keep state.
_listener_cache = Dict()
[docs] def tno_allows_children(self, node):
"""Returns whether this object allows children."""
return node.children != ""
[docs] def tno_has_children(self, node):
"""Returns whether this object has children."""
return len(self.tno_get_children(node)) > 0
[docs] def tno_get_children(self, node):
"""Gets the object's children."""
return getattr(self, node.children, None)
[docs] def tno_get_children_id(self, node):
"""Gets the object's children identifier."""
return node.children
[docs] def tno_append_child(self, node, child):
"""Appends a child to the object's children."""
self.tno_get_children(node).append(child)
[docs] def tno_insert_child(self, node, index, child):
"""Inserts a child into the object's children."""
children = self.tno_get_children(node)
children[index:index] = [child]
[docs] def tno_confirm_delete(self, node):
"""Checks whether a specified object can be deleted.
The following return values are possible:
- **True** if the object should be deleted with no further prompting.
- **False** if the object should not be deleted.
- Anything else: Caller should take its default action (which might
include prompting the user to confirm deletion).
"""
return None
[docs] def tno_delete_child(self, node, index):
"""Deletes a child at a specified index from the object's children."""
del self.tno_get_children(node)[index]
[docs] def tno_when_children_replaced(self, node, listener, remove):
"""Sets up or removes a listener for children being replaced on a
specified object.
"""
self.on_trait_change(
listener, node.children, remove=remove, dispatch="ui"
)
[docs] def tno_when_children_changed(self, node, listener, remove):
"""Sets up or removes a listener for children being changed on a
specified object.
"""
self.on_trait_change(
listener,
node.children + "_items",
remove=remove,
dispatch="ui",
)
[docs] def tno_get_label(self, node):
"""Gets the label to display for a specified object."""
label = node.label
if label[:1] == "=":
return label[1:]
label = xgetattr(self, label)
if node.formatter is None:
return label
return node.formatter(self, label)
[docs] def tno_set_label(self, node, label):
"""Sets the label for a specified object."""
label_name = node.label
if label_name[:1] != "=":
xsetattr(self, label_name, label)
[docs] def tno_when_label_changed(self, node, listener, remove):
"""Sets up or removes a listener for the label being changed on a
specified object.
"""
label = node.label
if label[:1] != "=":
memo = ("label", label, node, listener)
if not remove:
def wrapped_listener(target, name, new):
"""Ensure listener gets called with correct object."""
return listener(self, name, new)
self._listener_cache[memo] = wrapped_listener
else:
wrapped_listener = self._listener_cache.pop(memo, None)
if wrapped_listener is None:
return
self.on_trait_change(
wrapped_listener, label, remove=remove, dispatch="ui"
)
[docs] def tno_get_icon(self, node, is_expanded):
"""Returns the icon for a specified object."""
if not self.tno_allows_children(node):
return node.icon_item
if is_expanded:
return node.icon_open
return node.icon_group
[docs] def tno_get_icon_path(self, node):
"""Returns the path used to locate an object's icon."""
return node.icon_path
[docs] def tno_get_name(self, node):
"""Returns the name to use when adding a new object instance
(displayed in the "New" submenu).
"""
return node.name
[docs] def tno_get_view(self, node):
"""Gets the view to use when editing an object."""
return node.view
[docs] def tno_can_rename(self, node):
"""Returns whether the object's children can be renamed."""
return node.rename
[docs] def tno_can_rename_me(self, node):
"""Returns whether the object can be renamed."""
return node.rename_me
[docs] def tno_can_copy(self, node):
"""Returns whether the object's children can be copied."""
return node.copy
[docs] def tno_can_delete(self, node):
"""Returns whether the object's children can be deleted."""
return node.delete
[docs] def tno_can_delete_me(self, node):
"""Returns whether the object can be deleted."""
return node.delete_me
[docs] def tno_can_insert(self, node):
"""Returns whether the object's children can be inserted (vs.
appended).
"""
return node.insert
[docs] def tno_can_auto_open(self, node):
"""Returns whether the object's children should be automatically
opened.
"""
return node.auto_open
[docs] def tno_can_auto_close(self, node):
"""Returns whether the object's children should be automatically
closed.
"""
return node.auto_close
[docs] def tno_is_node_for(self, node):
"""Returns whether this is the node that should handle a
specified object.
"""
return isinstance(
self, node.node_for_class
) or self.has_traits_interface(*node.node_for_interface)
[docs] def tno_can_add(self, node, add_object):
"""Returns whether a given object is droppable on the node."""
klass = node._class_for(add_object)
if node.is_addable(klass):
return True
for item in node.move:
if type(item) in SequenceTypes:
item = item[0]
if issubclass(klass, item):
return True
return False
[docs] def tno_get_add(self, node):
"""Returns the list of classes that can be added to the object."""
return node.add
[docs] def tno_get_drag_object(self, node):
"""Returns a draggable version of a specified object."""
return self
[docs] def tno_drop_object(self, node, dropped_object):
"""Returns a droppable version of a specified object."""
if node.is_addable(dropped_object):
return dropped_object
for item in node.move:
if type(item) in SequenceTypes:
if isinstance(dropped_object, item[0]):
return item[1](self, dropped_object)
else:
if isinstance(dropped_object, item):
return dropped_object
[docs] def tno_select(self, node):
"""Handles an object being selected."""
if node.on_select is not None:
node.on_select(self)
return None
return True
[docs] def tno_click(self, node):
"""Handles an object being clicked."""
if node.on_click is not None:
node.on_click(self)
return None
return True
[docs] def tno_dclick(self, node):
"""Handles an object being double-clicked."""
if node.on_dclick is not None:
node.on_dclick(self)
return None
return True
[docs] def tno_activated(self, node):
"""Handles an object being activated."""
if node.on_activated is not None:
node.on_activated(self)
return None
return True
# -------------------------------------------------------------------------
# 'MultiTreeNode' object:
# -------------------------------------------------------------------------
[docs]class MultiTreeNode(TreeNode):
# -------------------------------------------------------------------------
# Trait definitions:
# -------------------------------------------------------------------------
#: TreeNode that applies to the base object itself
root_node = Instance(TreeNode)
#: List of TreeNodes (one for each sub-item list)
nodes = List(TreeNode)
[docs] def allows_children(self, object):
"""Returns whether this object can have children (True for this
class).
"""
return True
[docs] def has_children(self, object):
"""Returns whether this object has children (True for this class)."""
return True
[docs] def get_children(self, object):
"""Gets the object's children."""
return [(object, node) for node in self.nodes]
[docs] def get_children_id(self, object):
"""Gets the object's children identifier."""
return ""
[docs] def when_children_replaced(self, object, listener, remove):
"""Sets up or removes a listener for children being replaced on a
specified object.
"""
pass
[docs] def when_children_changed(self, object, listener, remove):
"""Sets up or removes a listener for children being changed on a
specified object.
"""
pass
[docs] def get_label(self, object):
"""Gets the label to display for a specified object."""
return self.root_node.get_label(object)
[docs] def set_label(self, object, label):
"""Sets the label for a specified object."""
return self.root_node.set_label(object, label)
[docs] def when_label_changed(self, object, listener, remove):
"""Sets up or removes a listener for the label being changed on a
specified object.
"""
return self.root_node.when_label_changed(object, listener, remove)
[docs] def get_icon(self, object, is_expanded):
"""Returns the icon for a specified object."""
return self.root_node.get_icon(object, is_expanded)
[docs] def get_icon_path(self, object):
"""Returns the path used to locate an object's icon."""
return self.root_node.get_icon_path(object)
[docs] def get_name(self, object):
"""Returns the name to use when adding a new object instance
(displayed in the "New" submenu).
"""
return self.root_node.get_name(object)
[docs] def get_view(self, object):
"""Gets the view to use when editing an object."""
return self.root_node.get_view(object)
[docs] def can_rename(self, object):
"""Returns whether the object's children can be renamed (False for
this class).
"""
return False
[docs] def can_rename_me(self, object):
"""Returns whether the object can be renamed (False for this class)."""
return False
[docs] def can_copy(self, object):
"""Returns whether the object's children can be copied."""
return self.root_node.can_copy(object)
[docs] def can_delete(self, object):
"""Returns whether the object's children can be deleted (False for
this class).
"""
return False
[docs] def can_delete_me(self, object):
"""Returns whether the object can be deleted (True for this class)."""
return True
[docs] def can_insert(self, object):
"""Returns whether the object's children can be inserted (False,
meaning that children are appended, for this class).
"""
return False
[docs] def can_auto_open(self, object):
"""Returns whether the object's children should be automatically
opened.
"""
return self.root_node.can_auto_open(object)
[docs] def can_auto_close(self, object):
"""Returns whether the object's children should be automatically
closed.
"""
return self.root_node.can_auto_close(object)
[docs] def can_add(self, object, add_object):
"""Returns whether a given object is droppable on the node (False for
this class).
"""
return False
[docs] def get_add(self, object):
"""Returns the list of classes that can be added to the object."""
return []
[docs] def get_drag_object(self, object):
"""Returns a draggable version of a specified object."""
return self.root_node.get_drag_object(object)
[docs] def drop_object(self, object, dropped_object):
"""Returns a droppable version of a specified object."""
return self.root_node.drop_object(object, dropped_object)
[docs] def select(self, object):
"""Handles an object being selected."""
return self.root_node.select(object)
[docs] def click(self, object):
"""Handles an object being clicked."""
return self.root_node.click(object)
[docs] def dclick(self, object):
"""Handles an object being double-clicked."""
return self.root_node.dclick(object)
[docs] def activated(self, object):
"""Handles an object being activated."""
return self.root_node.activated(object)