Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
traitsui / value_tree.py
Size: Mime:
#------------------------------------------------------------------------------
#
#  Copyright (c) 2006, Enthought, Inc.
#  All rights reserved.
#
#  This software is provided without warranty under the terms of the BSD
#  license included in enthought/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!
#
#  Author: David C. Morrill
#  Date:   01/05/2006
#
#------------------------------------------------------------------------------

""" Defines tree node classes and editors for various types of values.
"""

#-------------------------------------------------------------------------
#  Imports:
#-------------------------------------------------------------------------

from __future__ import absolute_import

import inspect
from operator import itemgetter

from types import FunctionType, MethodType

from traits.api import Any, Bool, HasPrivateTraits, HasTraits, Instance, List, Str

from .tree_node import ObjectTreeNode, TreeNode, TreeNodeObject

from .editors.tree_editor import TreeEditor
import six

#-------------------------------------------------------------------------
#  'SingleValueTreeNodeObject' class:
#-------------------------------------------------------------------------


class SingleValueTreeNodeObject(TreeNodeObject):
    """ A tree node for objects of types that have a single value.
    """

    #-------------------------------------------------------------------------
    #  Trait definitions:
    #-------------------------------------------------------------------------

    # The parent of this node
    parent = Instance(TreeNodeObject)

    # Name of the value
    name = Str

    # User-specified override of the default label
    label = Str

    # The value itself
    value = Any

    # Is the value readonly?
    readonly = Bool(False)

    #-------------------------------------------------------------------------
    #  Returns whether chidren of this object are allowed or not:
    #-------------------------------------------------------------------------

    def tno_allows_children(self, node):
        """ Returns whether this object can have children (False for this
        class).
        """
        return False

    #-------------------------------------------------------------------------
    #  Returns whether or not the object has children:
    #-------------------------------------------------------------------------

    def tno_has_children(self, node):
        """ Returns whether the object has children (False for this class).
        """
        return False

    #-------------------------------------------------------------------------
    #  Returns whether or not the object's children can be renamed:
    #-------------------------------------------------------------------------

    def tno_can_rename(self, node):
        """ Returns whether the object's children can be renamed (False for
        this class).
        """
        return False

    #-------------------------------------------------------------------------
    #  Returns whether or not the object's children can be copied:
    #-------------------------------------------------------------------------

    def tno_can_copy(self, node):
        """ Returns whether the object's children can be copied (True for this
        class).
        """
        return True

    #-------------------------------------------------------------------------
    #  Returns whether or not the object's children can be deleted:
    #-------------------------------------------------------------------------

    def tno_can_delete(self, node):
        """ Returns whether the object's children can be deleted (False for
        this class).
        """
        return False

    #-------------------------------------------------------------------------
    #  Returns whether or not the object's children can be inserted (or just
    #  appended):
    #-------------------------------------------------------------------------

    def tno_can_insert(self, node):
        """ Returns whether the object's children can be inserted (False,
        meaning children are appended, for this class).
        """
        return False

    #-------------------------------------------------------------------------
    #  Returns the icon for a specified object:
    #-------------------------------------------------------------------------

    def tno_get_icon(self, node, is_expanded):
        """ Returns the icon for a specified object.
        """
        return ('@icons:%s_node' % self.__class__.__name__[: -4].lower())

    #-------------------------------------------------------------------------
    #  Sets the label for a specified node:
    #-------------------------------------------------------------------------

    def tno_set_label(self, node, label):
        """ Sets the label for a specified object.
        """
        if label == '?':
            label = ''
        self.label = label

    #-------------------------------------------------------------------------
    #  Gets the label to display for a specified object:
    #-------------------------------------------------------------------------

    def tno_get_label(self, node):
        """ Gets the label to display for a specified object.
        """
        if self.label != '':
            return self.label

        if self.name == '':
            return self.format_value(self.value)

        return '%s: %s' % (self.name, self.format_value(self.value))

    #-------------------------------------------------------------------------
    #  Returns the formatted version of the value:
    #-------------------------------------------------------------------------

    def format_value(self, value):
        """ Returns the formatted version of the value.
        """
        return repr(value)

    #-------------------------------------------------------------------------
    #  Returns the correct node type for a specified value:
    #-------------------------------------------------------------------------

    def node_for(self, name, value):
        """ Returns the correct node type for a specified value.
        """
        for type, node in basic_types():
            if isinstance(value, type):
                break
        else:
            node = OtherNode
            if inspect.isclass(value):
                node = ClassNode

            elif hasattr(value, '__class__'):
                node = ObjectNode

        return node(parent=self,
                    name=name,
                    value=value,
                    readonly=self.readonly)

#-------------------------------------------------------------------------
#  'MultiValueTreeNodeObject' class:
#-------------------------------------------------------------------------


class MultiValueTreeNodeObject(SingleValueTreeNodeObject):
    """ A tree node for objects of types that have multiple values.
    """

    #-------------------------------------------------------------------------
    #  Returns whether chidren of this object are allowed or not:
    #-------------------------------------------------------------------------

    def tno_allows_children(self, node):
        """ Returns whether this object can have children (True for this class).
        """
        return True

    #-------------------------------------------------------------------------
    #  Returns whether or not the object has children:
    #-------------------------------------------------------------------------

    def tno_has_children(self, node):
        """ Returns whether the object has children (True for this class).
        """
        return True

#-------------------------------------------------------------------------
#  'StringNode' class:
#-------------------------------------------------------------------------


class StringNode(SingleValueTreeNodeObject):
    """ A tree node for strings.
    """

    #-------------------------------------------------------------------------
    #  Returns the formatted version of the value:
    #-------------------------------------------------------------------------

    def format_value(self, value):
        """ Returns the formatted version of the value.
        """
        n = len(value)
        if len(value) > 80:
            value = '%s...%s' % (value[:42], value[-35:])

        return '%s [%d]' % (repr(value), n)

#-------------------------------------------------------------------------
#  'NoneNode' class:
#-------------------------------------------------------------------------


class NoneNode(SingleValueTreeNodeObject):
    """ A tree node for None values.
    """
    pass

#-------------------------------------------------------------------------
#  'BoolNode' class:
#-------------------------------------------------------------------------


class BoolNode(SingleValueTreeNodeObject):
    """ A tree node for Boolean values.
    """
    pass

#-------------------------------------------------------------------------
#  'IntNode' class:
#-------------------------------------------------------------------------


class IntNode(SingleValueTreeNodeObject):
    """ A tree node for integer values.
    """
    pass

#-------------------------------------------------------------------------
#  'FloatNode' class:
#-------------------------------------------------------------------------


class FloatNode(SingleValueTreeNodeObject):
    """ A tree node for floating point values.
    """
    pass

#-------------------------------------------------------------------------
#  'ComplexNode' class:
#-------------------------------------------------------------------------


class ComplexNode(SingleValueTreeNodeObject):
    """ A tree node for complex number values.
    """
    pass

#-------------------------------------------------------------------------
#  'OtherNode' class:
#-------------------------------------------------------------------------


class OtherNode(SingleValueTreeNodeObject):
    """ A tree node for single-value types for which there is not another
    node type.
    """
    pass

#-------------------------------------------------------------------------
#  'TupleNode' class:
#-------------------------------------------------------------------------


class TupleNode(MultiValueTreeNodeObject):
    """ A tree node for tuples.
    """
    #-------------------------------------------------------------------------
    #  Returns the formatted version of the value:
    #-------------------------------------------------------------------------

    def format_value(self, value):
        """ Returns the formatted version of the value.
        """
        return 'Tuple(%d)' % len(value)

    #-------------------------------------------------------------------------
    #  Returns whether or not the object has children:
    #-------------------------------------------------------------------------

    def tno_has_children(self, node):
        """ Returns whether the object has children, based on the length of
            the tuple.
        """
        return (len(self.value) > 0)

    #-------------------------------------------------------------------------
    #  Gets the object's children:
    #-------------------------------------------------------------------------

    def tno_get_children(self, node):
        """ Gets the object's children.
        """
        node_for = self.node_for
        value = self.value
        if len(value) > 500:
            return ([node_for('[%d]' % i, x)
                     for i, x in enumerate(value[: 250])] +
                    [StringNode(value='...', readonly=True)] +
                    [node_for('[%d]' % i, x)
                     for i, x in enumerate(value[-250:])])

        return [node_for('[%d]' % i, x) for i, x in enumerate(value)]

#-------------------------------------------------------------------------
#  'ListNode' class:
#-------------------------------------------------------------------------


class ListNode(TupleNode):
    """ A tree node for lists.
    """

    #-------------------------------------------------------------------------
    #  Returns the formatted version of the value:
    #-------------------------------------------------------------------------

    def format_value(self, value):
        """ Returns the formatted version of the value.
        """
        return 'List(%d)' % len(value)

    #-------------------------------------------------------------------------
    #  Returns whether or not the object's children can be deleted:
    #-------------------------------------------------------------------------

    def tno_can_delete(self, node):
        """ Returns whether the object's children can be deleted.
        """
        return (not self.readonly)

    #-------------------------------------------------------------------------
    #  Returns whether or not the object's children can be inserted (or just
    #  appended):
    #-------------------------------------------------------------------------

    def tno_can_insert(self, node):
        """ Returns whether the object's children can be inserted (vs.
        appended).
        """
        return (not self.readonly)

#-------------------------------------------------------------------------
#  'SetNode' class:
#-------------------------------------------------------------------------


class SetNode(ListNode):
    """ A tree node for sets.
    """

    #-------------------------------------------------------------------------
    #  Returns the formatted version of the value:
    #-------------------------------------------------------------------------

    def format_value(self, value):
        """ Returns the formatted version of the value.
        """
        return 'Set(%d)' % len(value)

#-------------------------------------------------------------------------
#  'ArrayNode' class:
#-------------------------------------------------------------------------


class ArrayNode(TupleNode):
    """ A tree node for arrays.
    """

    #-------------------------------------------------------------------------
    #  Returns the formatted version of the value:
    #-------------------------------------------------------------------------

    def format_value(self, value):
        """ Returns the formatted version of the value.
        """
        return 'Array(%s)' % ','.join([str(n) for n in value.shape])

#-------------------------------------------------------------------------
#  'DictNode' class:
#-------------------------------------------------------------------------


class DictNode(TupleNode):
    """ A tree node for dictionaries.
    """

    #-------------------------------------------------------------------------
    #  Returns the formatted version of the value:
    #-------------------------------------------------------------------------

    def format_value(self, value):
        """ Returns the formatted version of the value.
        """
        return 'Dict(%d)' % len(value)

    #-------------------------------------------------------------------------
    #  Gets the object's children:
    #-------------------------------------------------------------------------

    def tno_get_children(self, node):
        """ Gets the object's children.
        """
        node_for = self.node_for
        items = [(repr(k), v) for k, v in self.value.items()]
        items.sort(key=itemgetter(0))
        if len(items) > 500:
            return ([node_for('[%s]' % k, v) for k, v in items[: 250]] +
                    [StringNode(value='...', readonly=True)] +
                    [node_for('[%s]' % k, v) for k, v in items[-250:]])

        return [node_for('[%s]' % k, v) for k, v in items]

    #-------------------------------------------------------------------------
    #  Returns whether or not the object's children can be deleted:
    #-------------------------------------------------------------------------

    def tno_can_delete(self, node):
        """ Returns whether the object's children can be deleted.
        """
        return (not self.readonly)

#-------------------------------------------------------------------------
#  'FunctionNode' class:
#-------------------------------------------------------------------------


class FunctionNode(SingleValueTreeNodeObject):
    """ A tree node for functions
    """

    #-------------------------------------------------------------------------
    #  Returns the formatted version of the value:
    #-------------------------------------------------------------------------

    def format_value(self, value):
        """ Returns the formatted version of the value.
        """
        return 'Function %s()' % (value.__name__)

#---------------------------------------------------------------------------
#  'MethodNode' class:
#---------------------------------------------------------------------------


class MethodNode(MultiValueTreeNodeObject):

    #-------------------------------------------------------------------------
    #  Returns the formatted version of the value:
    #-------------------------------------------------------------------------

    def format_value(self, value):
        """ Returns the formatted version of the value.
        """
        type = 'B'
        if value.__self__ is None:
            type = 'Unb'

        return '%sound method %s.%s()' % (
            type,
            value.__self__.__class__.__name__,
            value.__func__.__name__)

    #-------------------------------------------------------------------------
    #  Returns whether or not the object has children:
    #-------------------------------------------------------------------------

    def tno_has_children(self, node):
        """ Returns whether the object has children.
        """
        return (self.value.__func__ is not None)

    #-------------------------------------------------------------------------
    #  Gets the object's children:
    #-------------------------------------------------------------------------

    def tno_get_children(self, node):
        """ Gets the object's children.
        """
        return [self.node_for('Object', self.value.__self__)]

#-------------------------------------------------------------------------
#  'ObjectNode' class:
#-------------------------------------------------------------------------


class ObjectNode(MultiValueTreeNodeObject):
    """ A tree node for objects.
    """

    #-------------------------------------------------------------------------
    #  Returns the formatted version of the value:
    #-------------------------------------------------------------------------

    def format_value(self, value):
        """ Returns the formatted version of the value.
        """
        try:
            klass = value.__class__.__name__
        except:
            klass = '???'
        return '%s(0x%08X)' % (klass, id(value))

    #-------------------------------------------------------------------------
    #  Returns whether or not the object has children:
    #-------------------------------------------------------------------------

    def tno_has_children(self, node):
        """ Returns whether the object has children.
        """
        try:
            return (len(self.value.__dict__) > 0)
        except:
            return False

    #-------------------------------------------------------------------------
    #  Gets the object's children:
    #-------------------------------------------------------------------------

    def tno_get_children(self, node):
        """ Gets the object's children.
        """
        items = [(k, v) for k, v in self.value.__dict__.items()]
        items.sort(key=itemgetter(0))
        return [self.node_for('.' + k, v) for k, v in items]

#-------------------------------------------------------------------------
#  'ClassNode' class:
#-------------------------------------------------------------------------


class ClassNode(ObjectNode):
    """ A tree node for classes.
    """

    #-------------------------------------------------------------------------
    #  Returns the formatted version of the value:
    #-------------------------------------------------------------------------

    def format_value(self, value):
        """ Returns the formatted version of the value.
        """
        return value.__name__

#-------------------------------------------------------------------------
#  'TraitsNode' class:
#-------------------------------------------------------------------------


class TraitsNode(ObjectNode):
    """ A tree node for traits.
    """

    #-------------------------------------------------------------------------
    #  Returns whether or not the object has children:
    #-------------------------------------------------------------------------

    def tno_has_children(self, node):
        """ Returns whether the object has children.
        """
        return (len(self._get_names()) > 0)

    #-------------------------------------------------------------------------
    #  Gets the object's children:
    #-------------------------------------------------------------------------

    def tno_get_children(self, node):
        """ Gets the object's children.
        """
        names = sorted(self._get_names())
        value = self.value
        node_for = self.node_for
        nodes = []
        for name in names:
            try:
                item_value = getattr(value, name, '<unknown>')
            except Exception as excp:
                item_value = '<%s>' % excp
            nodes.append(node_for('.' + name, item_value))

        return nodes

    #-------------------------------------------------------------------------
    #  Gets the names of all defined traits/attributes:
    #-------------------------------------------------------------------------

    def _get_names(self):
        """ Gets the names of all defined traits or attributes.
        """
        value = self.value
        names = {}
        for name in value.trait_names(type=lambda x: x != 'event'):
            names[name] = None
        for name in value.__dict__.keys():
            names[name] = None
        return list(names.keys())

    #-------------------------------------------------------------------------
    #  Sets up/Tears down a listener for 'children replaced' on a specified
    #  object:
    #-------------------------------------------------------------------------

    def tno_when_children_replaced(self, node, listener, remove):
        """ Sets up or removes a listener for children being replaced on a
        specified object.
        """
        self._listener = listener
        self.value.on_trait_change(self._children_replaced, remove=remove,
                                   dispatch='ui')

    def _children_replaced(self):
        self._listener(self)

    #-------------------------------------------------------------------------
    #  Sets up/Tears down a listener for 'children changed' on a specified
    #  object:
    #-------------------------------------------------------------------------

    def tno_when_children_changed(self, node, listener, remove):
        """ Sets up or removes a listener for children being changed on a
        specified object.
        """
        pass

#-------------------------------------------------------------------------
#  'RootNode' class:
#-------------------------------------------------------------------------


class RootNode(MultiValueTreeNodeObject):
    """ A root node.
    """

    #-------------------------------------------------------------------------
    #  Returns the formatted version of the value:
    #-------------------------------------------------------------------------

    def format_value(self, value):
        """ Returns the formatted version of the value.
        """
        return ''

    #-------------------------------------------------------------------------
    #  Gets the object's children:
    #-------------------------------------------------------------------------

    def tno_get_children(self, node):
        """ Gets the object's children.
        """
        return [self.node_for('', self.value)]

#-------------------------------------------------------------------------
#  Define the mapping of object types to nodes:
#-------------------------------------------------------------------------

_basic_types = None


def basic_types():
    global _basic_types

    if _basic_types is None:
        # Create the mapping of object types to nodes:
        _basic_types = [
            (type(None), NoneNode),
            (str, StringNode),
            (six.text_type, StringNode),
            (bool, BoolNode),
            (int, IntNode),
            (float, FloatNode),
            (complex, ComplexNode),
            (tuple, TupleNode),
            (list, ListNode),
            (set, SetNode),
            (dict, DictNode),
            (FunctionType, FunctionNode),
            (MethodType, MethodNode),
            (HasTraits, TraitsNode)
        ]

        try:
            from numpy import array

            _basic_types.append((type(array([1])), ArrayNode))
        except ImportError:
            pass

    return _basic_types

#-------------------------------------------------------------------------
#  '_ValueTree' class:
#-------------------------------------------------------------------------


class _ValueTree(HasPrivateTraits):

    #-------------------------------------------------------------------------
    #  Trait definitions:
    #-------------------------------------------------------------------------

    # List of arbitrary Python values contained in the tree:
    values = List(SingleValueTreeNodeObject)

#-------------------------------------------------------------------------
#  Defines the value tree editor(s):
#-------------------------------------------------------------------------

# Nodes in a value tree:
value_tree_nodes = [
    ObjectTreeNode(
        node_for=[NoneNode, StringNode, BoolNode, IntNode, FloatNode,
                  ComplexNode, OtherNode, TupleNode, ListNode, ArrayNode,
                  DictNode, SetNode, FunctionNode, MethodNode, ObjectNode,
                  TraitsNode, RootNode, ClassNode])
]

# Editor for a value tree:
value_tree_editor = TreeEditor(
    auto_open=3,
    hide_root=True,
    editable=False,
    nodes=value_tree_nodes
)

# Editor for a value tree with a root:
value_tree_editor_with_root = TreeEditor(
    auto_open=3,
    editable=False,
    nodes=[
        ObjectTreeNode(
            node_for=[NoneNode, StringNode, BoolNode, IntNode, FloatNode,
                      ComplexNode, OtherNode, TupleNode, ListNode, ArrayNode,
                      DictNode, SetNode, FunctionNode, MethodNode,
                      ObjectNode, TraitsNode, RootNode, ClassNode]
        ),
        TreeNode(node_for=[_ValueTree],
                 auto_open=True,
                 children='values',
                 move=[SingleValueTreeNodeObject],
                 copy=False,
                 label='=Values',
                 icon_group='traits_node',
                 icon_open='traits_node')
    ]
)

#-------------------------------------------------------------------------
#  Defines a 'ValueTree' trait:
#-------------------------------------------------------------------------

# Trait for a value tree:
ValueTree = Instance(_ValueTree, (), editor=value_tree_editor_with_root)