4
4
from numpy import inf
6
6
# Enthought library imports
7
from enable.api import BaseTool, Pointer
8
from traits.api import Bool, Enum, Float, Tuple
7
from enable.api import BaseTool, Pointer, KeySpec
8
from traits.api import Bool, Enum, Float, Tuple, Instance
11
11
class PanTool(BaseTool):
29
29
# constrain_direction to the desired direction.
30
30
constrain_key = Enum(None, "shift", "control", "alt")
32
# Keys to Pan via keyboard
33
pan_right_key = Instance(KeySpec, args=("Right",))
34
pan_left_key = Instance(KeySpec, args=("Left",))
35
pan_up_key = Instance(KeySpec, args=("Up",))
36
pan_down_key = Instance(KeySpec, args=("Down",))
38
# number of pixels the keys should pan
40
pan_keys_step = Float(0.0)
32
42
# Constrain the panning to one direction?
33
43
constrain = Bool(False)
67
77
# The possible event states of this tool (overrides enable.Interactor).
68
78
event_state = Enum("normal", "panning")
80
def normal_key_pressed(self, event):
81
""" Handles a key being pressed when the tool is in the 'normal'
84
if self.pan_keys_step == 0.0:
86
src = self.component.bounds[0]/2, self.component.bounds[1]/2
88
if self.pan_left_key.match(event):
89
dest = (src[0] - self.pan_keys_step,
91
elif self.pan_right_key.match(event):
92
dest = (src[0] + self.pan_keys_step,
94
elif self.pan_down_key.match(event):
96
src[1] - self.pan_keys_step)
97
elif self.pan_up_key.match(event):
99
src[1] + self.pan_keys_step)
101
self._original_xy = src
104
self.panning_mouse_move(event)
71
107
def normal_left_down(self, event):
72
108
""" Handles the left mouse button being pressed when the tool is in
137
173
if self._auto_constrain and self.constrain_direction is None:
138
174
# Determine the constraint direction
139
if abs(event.x - self._original_xy[0]) > abs(event.y - self._original_xy[1]):
175
x_orig, y_orig = self._original_xy
176
if abs(event.x - x_orig) > abs(event.y - y_orig):
140
177
self.constrain_direction = "x"
142
179
self.constrain_direction = "y"
144
for direction, bound_name, ndx in [("x","width",0), ("y","height",1)]:
181
direction_info = [("x", "width", 0), ("y", "height", 1)]
182
for direction, bound_name, index in direction_info:
145
183
if not self.constrain or self.constrain_direction == direction:
146
184
mapper = getattr(plot, direction + "_mapper")
148
185
domain_min, domain_max = mapper.domain_limits
149
186
eventpos = getattr(event, direction)
150
origpos = self._original_xy[ndx]
187
origpos = self._original_xy[index]
152
189
screenlow, screenhigh = mapper.screen_bounds
153
190
screendelta = self.speed * (eventpos - origpos)
154
#if getattr(plot, direction + "_direction", None) == "flipped":
155
# screendelta = -screendelta
157
192
newlow = mapper.map_data(screenlow - screendelta)
158
193
newhigh = mapper.map_data(screenhigh - screendelta)
166
201
# linear mappers (which is used 99% of the time).
167
202
if domain_min is None:
168
203
if self.restrict_to_data:
169
domain_min = min([source.get_data().min() for source in range.sources])
204
domain_min = min([source.get_data().min()
205
for source in mapper.range.sources])
171
207
domain_min = -inf
172
208
if domain_max is None:
173
209
if self.restrict_to_data:
174
domain_max = max([source.get_data().max() for source in range.sources])
210
domain_max = max([source.get_data().max()
211
for source in mapper.range.sources])
177
215
if (newlow <= domain_min) and (newhigh >= domain_max):
178
216
# Don't do anything; effectively, freeze the pan
180
219
if newlow <= domain_min:
181
delta = newhigh - newlow
182
220
newlow = domain_min
183
# Don't let the adjusted newhigh exceed domain_max; this
184
# can happen with a nonlinear mapper.
185
newhigh = min(domain_max, domain_min + delta)
221
# Calculate delta in screen space, which is always linear.
222
screen_delta = mapper.map_screen(domain_min) - screenlow
223
newhigh = mapper.map_data(screenhigh + screen_delta)
186
224
elif newhigh >= domain_max:
187
delta = newhigh - newlow
188
225
newhigh = domain_max
189
# Don't let the adjusted newlow go below domain_min; this
190
# can happen with a nonlinear mapper.
191
newlow = max(domain_min, domain_max - delta)
226
# Calculate delta in screen space, which is always linear.
227
screen_delta = mapper.map_screen(domain_max) - screenhigh
228
newlow = mapper.map_data(screenlow + screen_delta)
193
230
# Use .set_bounds() so that we don't generate two range_changed
194
231
# events on the DataRange
195
range.set_bounds(newlow, newhigh)
232
mapper.range.set_bounds(newlow, newhigh)
197
234
event.handled = True