1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
|
""" Defines the base DragTool class.
"""
# Enthought library imports
from traits.api import Bool, Enum, Tuple, Property, cached_property, List, Str
from enable.base_tool import BaseTool, KeySpec
class DragTool(BaseTool):
""" Base class for tools that are activated by a drag operation.
This tool insulates the drag operation from double clicks and the like, and
gracefully manages the transition into and out of drag mode.
"""
# The mouse button used for this drag operation.
drag_button = Enum("left", "right")
# End the drag operation if the mouse leaves the associated component?
end_drag_on_leave = Bool(True)
# These keys, if pressed during drag, cause the drag operation to reset.
cancel_keys = List(Str, ["Esc"])
# The position of the initial mouse click that started the drag.
# Typically, tools that move things around use this
# position to do hit-testing to determine what object to "pick up".
mouse_down_position = Tuple(0.0, 0.0)
# The modifier key that must be used to activate the tool.
modifier_key = Enum("none", "shift", "alt", "control")
# Whether or not to capture the mouse during the drag operation. In
# general this is a good idea.
capture_mouse = Bool(True)
#------------------------------------------------------------------------
# Private traits used by DragTool
#------------------------------------------------------------------------
# The possible states of this tool.
_drag_state = Enum("nondrag", "dragging")
# Records whether a mouse_down event has been received while in
# "nondrag" state. This is a safety check to prevent the tool from
# suddenly getting mouse focus while the mouse button is down (either from
# window_enter or programmatically) and erroneously
# initiating a drag.
_mouse_down_received = Bool(False)
# private property to hold the current list of KeySpec instances of the
# cancel keys
_cancel_keys = Property(List(KeySpec), depends_on='cancel_keys')
#------------------------------------------------------------------------
# Interface for subclasses
#------------------------------------------------------------------------
def is_draggable(self, x, y):
""" Returns whether the (x,y) position is in a region that is OK to
drag.
Used by the tool to determine when to start a drag.
"""
return True
def drag_start(self, event):
""" Called when the drag operation starts.
The *event* parameter is the mouse event that established the drag
operation; its **x** and **y** attributes correspond to the current
location of the mouse, and not to the position of the mouse when the
initial left_down or right_down event happened.
"""
pass
def dragging(self, event):
""" This method is called for every mouse_move event that the tool
receives while the user is dragging the mouse.
It is recommended that subclasses do most of their work in this method.
"""
pass
def drag_cancel(self, event):
""" Called when the drag is cancelled.
A drag is usually cancelled by receiving a mouse_leave event when
end_drag_on_leave is True, or by the user pressing any of the
**cancel_keys**.
"""
pass
def drag_end(self, event):
""" Called when a mouse event causes the drag operation to end.
"""
pass
#------------------------------------------------------------------------
# Private methods for handling drag
#------------------------------------------------------------------------
def _dispatch_stateful_event(self, event, suffix):
# We intercept a lot of the basic events and re-map them if
# necessary. "consume" indicates whether or not we should pass
# the event to the subclass's handlers.
consume = False
if suffix == self.drag_button + "_down":
consume = self._drag_button_down(event)
elif suffix == self.drag_button + "_up":
consume = self._drag_button_up(event)
elif suffix == "mouse_move":
consume = self._drag_mouse_move(event)
elif suffix == "mouse_leave":
consume = self._drag_mouse_leave(event)
elif suffix == "mouse_enter":
consume = self._drag_mouse_enter(event)
elif suffix == "key_pressed":
consume = self._drag_cancel_keypressed(event)
if not consume:
BaseTool._dispatch_stateful_event(self, event, suffix)
else:
event.handled = True
return
def _cancel_drag(self, event):
self._drag_state = "nondrag"
outcome = self.drag_cancel(event)
self._mouse_down_received = False
if event.window.mouse_owner == self:
event.window.set_mouse_owner(None)
return outcome
def _drag_cancel_keypressed(self, event):
if self._drag_state != "nondrag" and \
any(map(lambda x: x.match(event), self._cancel_keys)):
return self._cancel_drag(event)
else:
return False
def _drag_mouse_move(self, event):
state = self._drag_state
button_down = getattr(event, self.drag_button + "_down")
if state == "nondrag":
if button_down and self._mouse_down_received and \
self.is_draggable(*self.mouse_down_position):
self._drag_state = "dragging"
if self.capture_mouse:
event.window.set_mouse_owner(
self, transform=event.net_transform(),
history=event.dispatch_history)
self.drag_start(event)
return self._drag_mouse_move(event)
return False
elif state == "dragging":
if button_down:
return self.dragging(event)
else:
return self._drag_button_up(event)
# If we don't invoke the subclass drag handler, then don't consume the
# event.
return False
def _drag_button_down(self, event):
if self._drag_state == "nondrag":
self.mouse_down_position = (event.x, event.y)
self._mouse_down_received = True
return False
def _drag_button_up(self, event):
self._mouse_down_received = False
state = self._drag_state
if event.window.mouse_owner == self:
event.window.set_mouse_owner(None)
if state == "dragging":
self._drag_state = "nondrag"
return self.drag_end(event)
# If we don't invoke the subclass drag handler, then don't consume the
# event.
return False
def _drag_mouse_leave(self, event):
if self.end_drag_on_leave and self._drag_state == "dragging":
return self._cancel_drag(event)
return False
def _drag_mouse_enter(self, event):
state = self._drag_state
if state == "nondrag":
pass
elif state == "dragging":
pass
return False
#------------------------------------------------------------------------
# Private methods for trait getter/setters
#------------------------------------------------------------------------
@cached_property
def _get__cancel_keys(self):
return [KeySpec(key) for key in self.cancel_keys]
# EOF
|