Source code for enable.base_tool
# (C) Copyright 2005-2022 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 base class for all Chaco tools. See docs/event_handling.txt for an
overview of how event handling works in Chaco.
"""
# Enthought library imports
from traits.api import Bool, Enum, Instance
# Local relative imports
from .component import Component
from .interactor import Interactor
[docs]class KeySpec(object):
"""
Creates a key specification to facilitate tools interacting with the
keyboard. A tool can declare either a class attribute::
magic_key = KeySpec("Right", "control", ignore=['shift'])
or a trait::
magic_key = Instance(KeySpec, args=("Right", "control"),
kw={'ignore': ['shift']})
and then check to see if the key was pressed by calling::
if self.magic_key.match(event):
# do stuff...
The names of the keys come from Enable, so both examples above
are specifying the user pressing Ctrl + Right_arrow with Alt not pressed
and Shift either pressed or not.
"""
def __init__(self, key, *modifiers, **kwmods):
""" Creates this key spec with the given modifiers. """
self.key = key
mods = set(m.lower() for m in modifiers)
self.alt = "alt" in mods
self.shift = "shift" in mods
self.control = "control" in mods
ignore = kwmods.get("ignore", [])
self.ignore = set(m.lower() for m in ignore)
[docs] def match(self, event):
"""
Returns True if the given Enable key_pressed event matches this key
specification.
"""
return (
(self.key == getattr(event, "character", None))
and ("alt" in self.ignore or self.alt == event.alt_down)
and (
"control" in self.ignore or self.control == event.control_down
)
and ("shift" in self.ignore or self.shift == event.shift_down)
)
[docs] @classmethod
def from_string(cls, s):
""" Create a KeySpec from a string joined by '+' characters. """
codes = s.split("+")
key = codes[-1]
modifiers = set(code.lower() for code in codes[:-1])
ignore = {"alt", "shift", "control"} - modifiers
return cls(key, *modifiers, ignore=ignore)
[docs]class BaseTool(Interactor):
""" The base class for Chaco tools.
Tools are not Enable components, but they can draw. They do not
participate in layout, but are instead attached to a Component, which
dispatches methods to the tool and calls the tools' draw() method.
See docs/event_handling.txt for more information on how tools are
structured.
"""
# The component that this tool is attached to.
component = Instance(Component)
# Is this tool's visual representation visible? For passive inspector-type
# tools, this is a constant value set in the class definition;
# for stateful or modal tools, the tool's listener sets this attribute.
visible = Bool(False)
# How the tool draws on top of its component. This, in conjuction with a
# a tool's status on the component, is used by the component to determine
# how to render itself. In general, the meanings of the draw modes are:
#
# normal:
# The appearance of part of the component is modified such that
# the component is redrawn even if it has not otherwise
# received any indication that its previous rendering is invalid.
# The tool controls its own drawing loop, and calls out to this
# tool after it is done drawing itself.
# overlay:
# The component needs to be drawn, but can be drawn after all
# of the background and foreground elements in the component.
# Furthermore, the tool renders correctly regardless
# of how the component renders itself (e.g., via a cached image).
# The overlay gets full control of the rendering loop, and must
# explicitly call the component's _draw() method; otherwise the
# component does not render.
# none:
# The tool does not have a visual representation that the component
# needs to render.
draw_mode = Enum("none", "overlay", "normal")
# ------------------------------------------------------------------------
# Concrete methods
# ------------------------------------------------------------------------
# Implement __init__ so `component` trait can be passed as a positional arg
def __init__(self, component=None, **traits):
if component is not None:
traits["component"] = component
super().__init__(**traits)
[docs] def dispatch(self, event, suffix):
""" Dispatches a mouse event based on the current event state.
Overrides enable.Interactor.
"""
self._dispatch_stateful_event(event, suffix)
def _dispatch_stateful_event(self, event, suffix):
# Override the default enable.Interactor behavior of automatically
# setting the event.handled if a handler is found. (Without this
# level of manual control, we could never support multiple listeners.)
handler = getattr(self, self.event_state + "_" + suffix, None)
if handler is not None:
handler(event)
# ------------------------------------------------------------------------
# Abstract methods that subclasses should implement
# ------------------------------------------------------------------------
[docs] def draw(self, gc, view_bounds=None):
""" Draws this tool on a graphics context.
It is assumed that the graphics context has a coordinate transform that
matches the origin of its component. (For containers, this is just the
origin; for components, it is the origin of their containers.)
"""
pass
def _activate(self):
""" Called by a Component when this becomes the active tool.
"""
pass
def _deactivate(self):
""" Called by a Component when this is no longer the active tool.
"""
pass
[docs] def deactivate(self, component=None):
""" Handles this component no longer being the active tool.
"""
# Compatibility with [Chaco's] AbstractController interface
self._deactivate()