~ubuntu-branches/ubuntu/utopic/python-chaco/utopic

« back to all changes in this revision

Viewing changes to examples/demo/canvas/mptools.py

  • Committer: Package Import Robot
  • Author(s): Andrew Starr-Bochicchio
  • Date: 2014-06-01 17:04:08 UTC
  • mfrom: (7.2.5 sid)
  • Revision ID: package-import@ubuntu.com-20140601170408-m86xvdjd83a4qon0
Tags: 4.4.1-1ubuntu1
* Merge from Debian unstable. Remaining Ubuntu changes:
 - Let the binary-predeb target work on the usr/lib/python* directory
   as we don't have usr/share/pyshared anymore.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
""" A collection of Chaco tools that respond to a multi-pointer interface
2
 
"""
3
 
from numpy import asarray, dot, sqrt
4
 
 
5
 
# Enthought library imports
6
 
from traits.api import Delegate, Dict, Enum, Instance, Int, Property, Trait, Tuple, CArray
7
 
 
8
 
# Chaco imports
9
 
from chaco.api import BaseTool
10
 
from chaco.tools.api import PanTool, DragZoom, LegendTool, RangeSelection
11
 
 
12
 
 
13
 
BOGUS_BLOB_ID = -1
14
 
 
15
 
def l2norm(v):
16
 
    return sqrt(dot(v,v))
17
 
 
18
 
class MPPanTool(PanTool):
19
 
    cur_bid = Int(BOGUS_BLOB_ID)
20
 
 
21
 
    def normal_blob_down(self, event):
22
 
        if self.cur_bid == BOGUS_BLOB_ID:
23
 
            self.cur_bid = event.bid
24
 
            self._start_pan(event, capture_mouse=False)
25
 
            event.window.capture_blob(self, event.bid, event.net_transform())
26
 
 
27
 
    def panning_blob_up(self, event):
28
 
        if event.bid == self.cur_bid:
29
 
            self.cur_bid = BOGUS_BLOB_ID
30
 
            self._end_pan(event)
31
 
 
32
 
    def panning_blob_move(self, event):
33
 
        if event.bid == self.cur_bid:
34
 
            self._dispatch_stateful_event(event, "mouse_move")
35
 
 
36
 
    def panning_mouse_leave(self, event):
37
 
        """ Handles the mouse leaving the plot when the tool is in the 'panning'
38
 
        state.
39
 
 
40
 
        Don't end panning.
41
 
        """
42
 
        return
43
 
 
44
 
    def _end_pan(self, event):
45
 
        if hasattr(event, "bid"):
46
 
            event.window.release_blob(event.bid)
47
 
        PanTool._end_pan(self, event)
48
 
 
49
 
 
50
 
class MPDragZoom(DragZoom):
51
 
 
52
 
    speed = 1.0
53
 
 
54
 
    # The original dataspace points where blobs 1 and 2 went down
55
 
    _orig_low = CArray #Trait(None, None, Tuple)
56
 
    _orig_high = CArray #Trait(None, None, Tuple)
57
 
 
58
 
    # Dataspace center of the zoom action
59
 
    _center_pt = Trait(None, None, Tuple)
60
 
 
61
 
    # Maps blob ID numbers to the (x,y) coordinates that came in.
62
 
    _blobs = Dict()
63
 
 
64
 
    # Maps blob ID numbers to the (x0,y0) coordinates from blob_move events.
65
 
    _moves = Dict()
66
 
 
67
 
    # Properties to convert the dictionaries to map from blob ID numbers to
68
 
    # a single coordinate appropriate for the axis the range selects on.
69
 
    _axis_blobs = Property(Dict)
70
 
    _axis_moves = Property(Dict)
71
 
 
72
 
    def _convert_to_axis(self, d):
73
 
        """ Convert a mapping of ID to (x,y) tuple to a mapping of ID to just
74
 
        the coordinate appropriate for the selected axis.
75
 
        """
76
 
        if self.axis == 'index':
77
 
            idx = self.axis_index
78
 
        else:
79
 
            idx = 1-self.axis_index
80
 
        d2 = {}
81
 
        for id, coords in d.items():
82
 
            d2[id] = coords[idx]
83
 
        return d2
84
 
 
85
 
    def _get__axis_blobs(self):
86
 
        return self._convert_to_axis(self._blobs)
87
 
 
88
 
    def _get__axis_moves(self):
89
 
        return self._convert_to_axis(self._moves)
90
 
 
91
 
    def drag_start(self, event, capture_mouse=False):
92
 
        bid1, bid2 = sorted(self._moves)
93
 
        xy01, xy02 = self._moves[bid1], self._moves[bid2]
94
 
        self._orig_low, self._orig_high = map(asarray,
95
 
            self._map_coordinate_box(xy01, xy02))
96
 
        self.orig_center = (self._orig_high + self._orig_low) / 2.0
97
 
        self.orig_diag = l2norm(self._orig_high - self._orig_low)
98
 
 
99
 
        #DragZoom.drag_start(self, event, capture_mouse)
100
 
        self._original_xy = xy02
101
 
        c = self.component
102
 
        self._orig_screen_bounds = ((c.x,c.y), (c.x2,c.y2))
103
 
        self._original_data = (c.x_mapper.map_data(xy02[0]), c.y_mapper.map_data(xy02[1]))
104
 
        self._prev_y = xy02[1]
105
 
        if capture_mouse:
106
 
            event.window.set_pointer(self.drag_pointer)
107
 
 
108
 
    def normal_blob_down(self, event):
109
 
        if len(self._blobs) < 2:
110
 
            self._blobs[event.bid] = (event.x, event.y)
111
 
            event.window.capture_blob(self, event.bid,
112
 
                transform=event.net_transform())
113
 
            event.handled = True
114
 
 
115
 
    def normal_blob_up(self, event):
116
 
        self._handle_blob_leave(event)
117
 
 
118
 
    def normal_blob_move(self, event):
119
 
        self._handle_blob_move(event)
120
 
 
121
 
    def normal_blob_frame_end(self, event):
122
 
        if len(self._moves) == 2:
123
 
            self.event_state = "dragging"
124
 
            self.drag_start(event, capture_mouse=False)
125
 
 
126
 
    def dragging_blob_move(self, event):
127
 
        self._handle_blob_move(event)
128
 
 
129
 
    def dragging_blob_frame_end(self, event):
130
 
        # Get dataspace coordinates of the previous and new coordinates
131
 
        bid1, bid2 = sorted(self._moves)
132
 
        p1, p2 = self._blobs[bid1], self._blobs[bid2]
133
 
        low, high = map(asarray, self._map_coordinate_box(p1, p2))
134
 
 
135
 
        # Compute the amount of translation
136
 
        center = (high + low) / 2.0
137
 
        translation = center - self.orig_center
138
 
 
139
 
        # Computing the zoom factor.  We have the coordinates of the original
140
 
        # blob_down events, and we have a new box as well.  For now, just use
141
 
        # the relative sizes of the diagonals.
142
 
        diag = l2norm(high - low)
143
 
        zoom = self.speed * self.orig_diag / diag
144
 
 
145
 
        # The original screen bounds are used to test if we've reached max_zoom
146
 
        orig_screen_low, orig_screen_high = \
147
 
                map(asarray, self._map_coordinate_box(*self._orig_screen_bounds))
148
 
        new_low = center - zoom * (center - orig_screen_low) - translation
149
 
        new_high = center + zoom * (orig_screen_high - center) - translation
150
 
 
151
 
        for ndx in (0,1):
152
 
            if self._zoom_limit_reached(orig_screen_low[ndx],
153
 
                    orig_screen_high[ndx], new_low[ndx], new_high[ndx]):
154
 
                return
155
 
 
156
 
        c = self.component
157
 
        c.x_mapper.range.set_bounds(new_low[0], new_high[0])
158
 
        c.y_mapper.range.set_bounds(new_low[1], new_high[1])
159
 
 
160
 
        self.component.request_redraw()
161
 
 
162
 
    def dragging_blob_up(self, event):
163
 
        self._handle_blob_leave(event)
164
 
 
165
 
    def _handle_blob_move(self, event):
166
 
        if event.bid not in self._blobs:
167
 
            return
168
 
        self._blobs[event.bid] = event.x, event.y
169
 
        self._moves[event.bid] = event.x0, event.y0
170
 
        event.handled = True
171
 
 
172
 
    def _handle_blob_leave(self, event):
173
 
        if event.bid in self._blobs:
174
 
            del self._blobs[event.bid]
175
 
            self._moves.pop(event.bid, None)
176
 
            event.window.release_blob(event.bid)
177
 
        if len(self._blobs) < 2:
178
 
            self.event_state = "normal"
179
 
 
180
 
 
181
 
class MPPanZoom(BaseTool):
182
 
    """ This tool wraps a pan and a zoom tool, and automatically switches
183
 
    behavior back and forth depending on how many blobs are tracked on
184
 
    screen.
185
 
    """
186
 
 
187
 
    pan = Instance(MPPanTool)
188
 
 
189
 
    zoom = Instance(MPDragZoom)
190
 
 
191
 
    event_state = Enum("normal", "pan", "zoom")
192
 
 
193
 
    _blobs = Delegate('zoom')
194
 
    _moves = Delegate('zoom')
195
 
 
196
 
    def _dispatch_stateful_event(self, event, suffix):
197
 
        self.zoom.dispatch(event, suffix)
198
 
        event.handled = False
199
 
        self.pan.dispatch(event, suffix)
200
 
        if len(self._blobs) == 2:
201
 
            self.event_state = 'zoom'
202
 
        elif len(self._blobs) == 1:
203
 
            self.event_state = 'pan'
204
 
        elif len(self._blobs) == 0:
205
 
            self.event_state = 'normal'
206
 
        else:
207
 
            assert len(self._blobs) <= 2
208
 
        if suffix == 'blob_up':
209
 
            event.window.release_blob(event.bid)
210
 
        elif suffix == 'blob_down':
211
 
            event.window.release_blob(event.bid)
212
 
            event.window.capture_blob(self, event.bid, event.net_transform())
213
 
            event.handled = True
214
 
 
215
 
    def _component_changed(self, old, new):
216
 
        self.pan.component = new
217
 
        self.zoom.component = new
218
 
 
219
 
    def _pan_default(self):
220
 
        return MPPanTool(self.component)
221
 
 
222
 
    def _zoom_default(self):
223
 
        return MPDragZoom(self.component)
224
 
 
225
 
 
226
 
class MPLegendTool(LegendTool):
227
 
 
228
 
    event_state = Enum("normal", "dragging")
229
 
 
230
 
    cur_bid = Int(-1)
231
 
 
232
 
    def normal_blob_down(self, event):
233
 
        if self.cur_bid == -1 and self.is_draggable(event.x, event.y):
234
 
            self.cur_bid = event.bid
235
 
            self.drag_start(event)
236
 
 
237
 
    def dragging_blob_up(self, event):
238
 
        if event.bid == self.cur_bid:
239
 
            self.cur_bid = -1
240
 
            self.drag_end(event)
241
 
 
242
 
    def dragging_blob_move(self, event):
243
 
        if event.bid == self.cur_bid:
244
 
            self.dragging(event)
245
 
 
246
 
    def drag_start(self, event):
247
 
        if self.component:
248
 
            self.original_padding = self.component.padding
249
 
            if hasattr(event, "bid"):
250
 
                event.window.capture_blob(self, event.bid,
251
 
                                          event.net_transform())
252
 
            else:
253
 
                event.window.set_mouse_owner(self, event.net_transform())
254
 
            self.mouse_down_position = (event.x,event.y)
255
 
            self.event_state = "dragging"
256
 
            event.handled = True
257
 
        return
258
 
 
259
 
    def drag_end(self, event):
260
 
        if hasattr(event, "bid"):
261
 
            event.window.release_blob(event.bid)
262
 
        self.event_state = "normal"
263
 
        LegendTool.drag_end(self, event)
264
 
 
265
 
 
266
 
 
267
 
class MPRangeSelection(RangeSelection):
268
 
 
269
 
    # Maps blob ID numbers to the (x,y) coordinates that came in.
270
 
    _blobs = Dict()
271
 
 
272
 
    # Maps blob ID numbers to the (x0,y0) coordinates from blob_move events.
273
 
    _moves = Dict()
274
 
 
275
 
    # Properties to convert the dictionaries to map from blob ID numbers to
276
 
    # a single coordinate appropriate for the axis the range selects on.
277
 
    _axis_blobs = Property(Dict)
278
 
    _axis_moves = Property(Dict)
279
 
 
280
 
    def _convert_to_axis(self, d):
281
 
        """ Convert a mapping of ID to (x,y) tuple to a mapping of ID to just
282
 
        the coordinate appropriate for the selected axis.
283
 
        """
284
 
        if self.axis == 'index':
285
 
            idx = self.axis_index
286
 
        else:
287
 
            idx = 1-self.axis_index
288
 
        d2 = {}
289
 
        for id, coords in d.items():
290
 
            d2[id] = coords[idx]
291
 
        return d2
292
 
 
293
 
    def _get__axis_blobs(self):
294
 
        return self._convert_to_axis(self._blobs)
295
 
 
296
 
    def _get__axis_moves(self):
297
 
        return self._convert_to_axis(self._moves)
298
 
 
299
 
    def normal_blob_down(self, event):
300
 
        if len(self._blobs) < 2:
301
 
            self._blobs[event.bid] = (event.x, event.y)
302
 
            event.window.capture_blob(self, event.bid,
303
 
                transform=event.net_transform())
304
 
            event.handled = True
305
 
 
306
 
    def normal_blob_up(self, event):
307
 
        self._handle_blob_leave(event)
308
 
 
309
 
    def normal_blob_frame_end(self, event):
310
 
        if len(self._blobs) == 2:
311
 
            self.event_state = "selecting"
312
 
            #self.drag_start(event, capture_mouse=False)
313
 
            #self.selecting_mouse_move(event)
314
 
            self._set_sizing_cursor(event)
315
 
            self.selection = sorted(self._axis_blobs.values())
316
 
 
317
 
    def selecting_blob_move(self, event):
318
 
        if event.bid in self._blobs:
319
 
            self._blobs[event.bid] = event.x, event.y
320
 
            self._moves[event.bid] = event.x0, event.y0
321
 
 
322
 
    def selecting_blob_up(self, event):
323
 
        self._handle_blob_leave(event)
324
 
 
325
 
    def selecting_blob_frame_end(self, event):
326
 
        if self.selection is None:
327
 
            return
328
 
        elif len(self._blobs) == 2:
329
 
            axis_index = self.axis_index
330
 
            low = self.plot.position[axis_index]
331
 
            high = low + self.plot.bounds[axis_index] - 1
332
 
            p1, p2 = self._axis_blobs.values()
333
 
            # XXX: what if p1 or p2 is out of bounds?
334
 
            m1 = self.mapper.map_data(p1)
335
 
            m2 = self.mapper.map_data(p2)
336
 
            low_val = min(m1, m2)
337
 
            high_val = max(m1, m2)
338
 
            self.selection = (low_val, high_val)
339
 
            self.component.request_redraw()
340
 
        elif len(self._moves) == 1:
341
 
            id, p0 = self._axis_moves.items()[0]
342
 
            m0 = self.mapper.map_data(p0)
343
 
            low, high = self.selection
344
 
            if low <= m0 <= high:
345
 
                m1 = self.mapper.map_data(self._axis_blobs[id])
346
 
                dm = m1 - m0
347
 
                self.selection = (low+dm, high+dm)
348
 
 
349
 
    def selected_blob_down(self, event):
350
 
        if len(self._blobs) < 2:
351
 
            self._blobs[event.bid] = (event.x, event.y)
352
 
            event.window.capture_blob(self, event.bid,
353
 
                transform=event.net_transform())
354
 
            event.handled = True
355
 
 
356
 
    def selected_blob_move(self, event):
357
 
        if event.bid in self._blobs:
358
 
            self._blobs[event.bid] = event.x, event.y
359
 
            self._moves[event.bid] = event.x0, event.y0
360
 
 
361
 
    def selected_blob_frame_end(self, event):
362
 
        self.selecting_blob_frame_end(event)
363
 
 
364
 
    def selected_blob_up(self, event):
365
 
        self._handle_blob_leave(event)
366
 
 
367
 
    def _handle_blob_leave(self, event):
368
 
        self._moves.pop(event.bid, None)
369
 
        if event.bid in self._blobs:
370
 
            del self._blobs[event.bid]
371
 
            event.window.release_blob(event.bid)
372
 
 
373
 
        # Treat the blob leave as a selecting_mouse_up event
374
 
        self.selecting_right_up(event)
375
 
 
376
 
        if len(self._blobs) < 2:
377
 
            self.event_state = "selected"
378