Source code for exopy.utils.widgets.tree_nodes

# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# Copyright 2015-2018 by Exopy Authors, see AUTHORS for more details.
#
# Distributed under the terms of the BSD license.
#
# The full license is in the file LICENCE, distributed with this software.
# -----------------------------------------------------------------------------
"""Declarative node for tree node generation.

"""
from atom.api import (Str, Bool, List, Value, Property, Dict)
from enaml.core.declarative import Declarative, d_, d_func
from enaml.widgets.api import Menu


[docs]class TreeNode(Declarative): """Represents a tree node. This declaration is used to help the system determine how to extract informations from the underlying object to populate the node. Note that a Menu can be contributed as a child and will be used when right clicking a node. It will be passed a 'context' describing the node being right-clicked. The context will be a dictionary with the following keys : - 'copyable': bool, can the node be copied - 'cutable': bool, can the node be cut - 'pasteable': bool, can node be pasted here - 'renamable': bool, can the node be renamed - 'deletable': bool, can the node be deleted - 'not_root': bool, is the node the root node of the tree - 'data': tuple, (tree, TreeNode instance, object, id of the node) """ #: List of object classes and/or interfaces that the node applies to node_for = d_(List()) #: Either the name of a member containing a label, or a constant label, if #: the string starts with '='. label = d_(Str()) #: Either the name of a member containing a tooltip, or constant tooltip, #: if the string starts with '='. tooltip = d_(Str()) #: Name of the member containing children (if '', the node is a leaf). children_member = d_(Str()) #: Name of the signal use to notify changes to the children. The payload of #: the signal should be a ContainerChange instance. children_changed = d_(Str()) #: List of object classes than can be added or copied add = d_(List()) #: List of object classes that can be moved move = d_(List()) #: Name to use for a new instance name = d_(Str()) #: Can the object's children be renamed? rename = d_(Bool(True)) #: Can the object be renamed? rename_me = d_(Bool(True)) #: Can the object's children be copied? copy = d_(Bool(True)) #: Can the object's children be deleted? delete = d_(Bool(True)) #: Can the object be deleted (if its parent allows it)? delete_me = d_(Bool(True)) #: Can children be inserted (vs. appended)? insert = d_(Bool(True)) #: Should tree nodes be automatically opened (expanded)? auto_open = d_(Bool(False)) #: Automatically close sibling tree nodes? auto_close = d_(Bool(False)) #: Tuple of object classes that the node applies to node_for_class = Property() #: Name of leaf item icon icon_item = d_(Str('<item>')) #: Name of group item icon icon_group = d_(Str('<group>')) #: Name of opened group item icon icon_open = d_(Str('<open>')) #: Resource path used to locate the node icon icon_path = d_(Str('Icon')) #: Selector or name for background color background = Value('white') #: Selector or name for foreground color foreground = Value('black') _py_data = Value() _menu = Value() # --- Declarative functions -----------------------------------------------
[docs] @d_func def insert_child(self, obj, index, child): """Inserts a child into the object's children. """ getattr(obj, self.children_member)[index:index] = [child]
[docs] @d_func def confirm_delete(self, obj): """Checks whether a specified object can be deleted. Returns ------- - **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] @d_func def delete_child(self, obj, index): """Deletes a child at a specified index from the object's children. """ del getattr(obj, self.children_member)[index]
[docs] @d_func def move_child(self, obj, old, new): """Move a child of the object's children. """ child = getattr(obj, self.children_member)[old] del getattr(obj, self.children_member)[old] getattr(obj, self.children_member)[new:new] = [child]
[docs] @d_func def enter_rename(self, obj): """Start renaming an object. This method can be customized in case the renaming operation should not occur directly on the label. Parameters ---------- obj : Refrence to the object the tree node being renamed is representing. Returns ------- name : unicode String on which to perform the renaming. """ return self.get_label(obj)
[docs] @d_func def exit_rename(self, obj, label): """Sets the label for a specified object after a renaming operation. """ label_name = self.label if label_name[:1] != '=': setattr(obj, label_name, label)
[docs] @d_func def get_label(self, obj): """Gets the label to display for a specified object. """ label = self.label if label[:1] == '=': return label[1:] label = getattr(obj, label) return label
# ========================================================================= # --- Initializes the object ---------------------------------------------- # =========================================================================
[docs] def initialize(self): """Collect the Menu provided as a child. """ for ch in self.children: if isinstance(ch, Menu): self._menu = ch break
# ========================================================================= # --- Property Implementations -------------------------------------------- # ========================================================================= @node_for_class.getter def _get_node_for_class(self): return tuple([klass for klass in self.node_for]) # ========================================================================= # --- Overridable Methods: ------------------------------------------------ # =========================================================================
[docs] def allows_children(self, obj): """Returns whether this object can have children. """ return self.children_member != ''
[docs] def has_children(self, obj): """Returns whether the object has children. """ return len(self.get_children(obj)) > 0
[docs] def get_children(self, obj): """Gets the object's children. """ return getattr(obj, self.children_member)
[docs] def get_children_id(self, obj): """Gets the object's children identifier. """ return self.children_member
[docs] def append_child(self, obj, child): """Appends a child to the object's children. """ self.insert_child(obj, len(getattr(obj, self.children_member)), child)
[docs] def get_tooltip(self, obj): """Gets the tooltip to display for a specified object. """ tooltip = self.tooltip if tooltip == '': return tooltip if tooltip[:1] == '=': return tooltip[1:] tooltip = getattr(obj, tooltip) if not tooltip: tooltip = '' if self.tooltip_formatter is None: return tooltip return self.tooltip_formatter(obj, tooltip)
[docs] def get_icon(self, obj, is_expanded): """Returns the icon for a specified object. """ if not self.allows_children(obj): return self.icon_item if is_expanded: return self.icon_open return self.icon_group
[docs] def get_icon_path(self, obj): """Returns the path used to locate an object's icon. """ return self.icon_path
[docs] def get_name(self, obj): """Returns the name to use when adding a new object instance (displayed in the "New" submenu). """ return self.name
[docs] def get_menu(self, context): """Returns the right-click context menu for an object. """ if self._menu: self._menu.context = context return self._menu else: return None
[docs] def get_background(self, obj): """Returns the background color for the item. """ background = self.background if isinstance(background, str): background = getattr(obj, background, background) return background
[docs] def get_foreground(self, obj): """Returns the foreground color for the item. """ foreground = self.foreground if isinstance(foreground, str): foreground = getattr(obj, foreground, foreground) return foreground
[docs] def can_rename(self, obj): """Returns whether the object's children can be renamed. """ return self.rename
[docs] def can_rename_me(self, obj): """Returns whether the object can be renamed. """ return self.rename_me
[docs] def can_copy(self, obj): """Returns whether the object's children can be copied. """ return self.copy
[docs] def can_delete(self, obj): """Returns whether the object's children can be deleted. """ return self.delete
[docs] def can_delete_me(self, obj): """Returns whether the object can be deleted. """ return self.delete_me
[docs] def can_insert(self, obj): """Returns whether the object's children can be inserted (vs. appended). """ return self.insert
[docs] def can_auto_open(self, obj): """Returns whether the object's children should be automatically opened. """ return self.auto_open
[docs] def can_auto_close(self, obj): """Returns whether the object's children should be automatically closed. """ return self.auto_close
[docs] def is_node_for(self, obj): """Returns whether this is the node that handles a specified object. """ return isinstance(obj, self.node_for_class)
[docs] def can_add(self, obj, 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 (List, Dict): item = item[0] if issubclass(klass, item): return True return False
[docs] def get_add(self, obj): """Returns the list of classes that can be added to the object. """ return self.add
[docs] def get_drag_object(self, obj): """Returns a draggable version of a specified object. """ return obj
[docs] def drop_object(self, obj, 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 (List, Dict): if issubclass(klass, item[0]): return item[1](obj, dropped_object) elif issubclass(klass, item): return dropped_object return dropped_object
[docs] def select(self, obj): """Handles an object being selected. """ 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 (List, Dict): item = item[0] if issubclass(klass, item): return True return False
[docs] def when_label_changed(self, obj, listener, remove): """Sets up or removes a listener for the label being changed on a specified object. """ label = self.label if label[:1] != '=': if remove: obj.unobserve(label, listener) else: obj.observe(label, listener)
def _class_for(self, obj): """Returns the class of an object. """ if isinstance(obj, type): return obj return obj.__class__