1
by Brian Sidebotham
Initial import into Bazaar |
1 |
#!/usr/bin/env python
|
2 |
"""
|
|
3 |
||
4 |
Module that holds the GUI modes used by FloatCanvas
|
|
5 |
||
6 |
Note that this can only be imported after a wx.App() has been created.
|
|
7 |
||
8 |
This approach was inpired by Christian Blouin, who also wrote the initial
|
|
9 |
version of the code.
|
|
10 |
||
11 |
"""
|
|
12 |
||
13 |
import wx |
|
14 |
## fixme: events should live in their own module, so all of FloatCanvas
|
|
15 |
## wouldn't have to be imported here.
|
|
16 |
import FloatCanvas, Resources |
|
17 |
from Utilities import BBox |
|
18 |
import numpy as N |
|
19 |
||
20 |
class Cursors(object): |
|
21 |
"""
|
|
22 |
Class to hold the standard Cursors
|
|
23 |
|
|
24 |
"""
|
|
25 |
def __init__(self): |
|
26 |
if "wxMac" in wx.PlatformInfo: # use 16X16 cursors for wxMac |
|
27 |
self.HandCursor = wx.CursorFromImage(Resources.getHand16Image()) |
|
28 |
self.GrabHandCursor = wx.CursorFromImage(Resources.getGrabHand16Image()) |
|
29 |
||
30 |
img = Resources.getMagPlus16Image() |
|
31 |
img.SetOptionInt(wx.IMAGE_OPTION_CUR_HOTSPOT_X, 6) |
|
32 |
img.SetOptionInt(wx.IMAGE_OPTION_CUR_HOTSPOT_Y, 6) |
|
33 |
self.MagPlusCursor = wx.CursorFromImage(img) |
|
34 |
||
35 |
img = Resources.getMagMinus16Image() |
|
36 |
img.SetOptionInt(wx.IMAGE_OPTION_CUR_HOTSPOT_X, 6) |
|
37 |
img.SetOptionInt(wx.IMAGE_OPTION_CUR_HOTSPOT_Y, 6) |
|
38 |
self.MagMinusCursor = wx.CursorFromImage(img) |
|
39 |
else: # use 24X24 cursors for GTK and Windows |
|
40 |
self.HandCursor = wx.CursorFromImage(Resources.getHandImage()) |
|
41 |
self.GrabHandCursor = wx.CursorFromImage(Resources.getGrabHandImage()) |
|
42 |
||
43 |
img = Resources.getMagPlusImage() |
|
44 |
img.SetOptionInt(wx.IMAGE_OPTION_CUR_HOTSPOT_X, 9) |
|
45 |
img.SetOptionInt(wx.IMAGE_OPTION_CUR_HOTSPOT_Y, 9) |
|
46 |
self.MagPlusCursor = wx.CursorFromImage(img) |
|
47 |
||
48 |
img = Resources.getMagMinusImage() |
|
49 |
img.SetOptionInt(wx.IMAGE_OPTION_CUR_HOTSPOT_X, 9) |
|
50 |
img.SetOptionInt(wx.IMAGE_OPTION_CUR_HOTSPOT_Y, 9) |
|
51 |
self.MagMinusCursor = wx.CursorFromImage(img) |
|
52 |
||
53 |
||
54 |
class GUIBase(object): |
|
55 |
"""
|
|
56 |
Basic Mouse mode and baseclass for other GUImode.
|
|
57 |
||
58 |
This one does nothing with any event
|
|
59 |
||
60 |
"""
|
|
61 |
def __init__(self, Canvas=None): |
|
62 |
self.Canvas = Canvas # set the FloatCanvas for the mode |
|
63 |
# it gets set when the Mode is set on the Canvas.
|
|
64 |
self.Cursors = Cursors() |
|
65 |
||
66 |
Cursor = wx.NullCursor |
|
67 |
def UnSet(self): |
|
68 |
"""
|
|
69 |
this method gets called by FloatCanvas when a new mode is being set
|
|
70 |
on the Canvas
|
|
71 |
"""
|
|
72 |
pass
|
|
73 |
# Handlers
|
|
74 |
def OnLeftDown(self, event): |
|
75 |
pass
|
|
76 |
def OnLeftUp(self, event): |
|
77 |
pass
|
|
78 |
def OnLeftDouble(self, event): |
|
79 |
pass
|
|
80 |
def OnRightDown(self, event): |
|
81 |
pass
|
|
82 |
def OnRightUp(self, event): |
|
83 |
pass
|
|
84 |
def OnRightDouble(self, event): |
|
85 |
pass
|
|
86 |
def OnMiddleDown(self, event): |
|
87 |
pass
|
|
88 |
def OnMiddleUp(self, event): |
|
89 |
pass
|
|
90 |
def OnMiddleDouble(self, event): |
|
91 |
pass
|
|
92 |
def OnWheel(self, event): |
|
93 |
pass
|
|
94 |
def OnMove(self, event): |
|
95 |
pass
|
|
96 |
def OnKeyDown(self, event): |
|
97 |
pass
|
|
98 |
def OnKeyUp(self, event): |
|
99 |
pass
|
|
100 |
def UpdateScreen(self): |
|
101 |
"""
|
|
102 |
Update gets called if the screen has been repainted in the middle of a zoom in
|
|
103 |
so the Rubber Band Box can get updated. Other GUIModes may require something similar
|
|
104 |
"""
|
|
105 |
pass
|
|
106 |
||
107 |
class GUIMouse(GUIBase): |
|
108 |
"""
|
|
109 |
||
110 |
Mouse mode checks for a hit test, and if nothing is hit,
|
|
111 |
raises a FloatCanvas mouse event for each event.
|
|
112 |
||
113 |
"""
|
|
114 |
||
115 |
Cursor = wx.NullCursor |
|
116 |
||
117 |
# Handlers
|
|
118 |
def OnLeftDown(self, event): |
|
119 |
EventType = FloatCanvas.EVT_FC_LEFT_DOWN |
|
120 |
if not self.Canvas.HitTest(event, EventType): |
|
121 |
self.Canvas._RaiseMouseEvent(event, EventType) |
|
122 |
||
123 |
def OnLeftUp(self, event): |
|
124 |
EventType = FloatCanvas.EVT_FC_LEFT_UP |
|
125 |
if not self.Canvas.HitTest(event, EventType): |
|
126 |
self.Canvas._RaiseMouseEvent(event, EventType) |
|
127 |
||
128 |
def OnLeftDouble(self, event): |
|
129 |
EventType = FloatCanvas.EVT_FC_LEFT_DCLICK |
|
130 |
if not self.Canvas.HitTest(event, EventType): |
|
131 |
self.Canvas._RaiseMouseEvent(event, EventType) |
|
132 |
||
133 |
def OnMiddleDown(self, event): |
|
134 |
EventType = FloatCanvas.EVT_FC_MIDDLE_DOWN |
|
135 |
if not self.Canvas.HitTest(event, EventType): |
|
136 |
self.Canvas._RaiseMouseEvent(event, EventType) |
|
137 |
||
138 |
def OnMiddleUp(self, event): |
|
139 |
EventType = FloatCanvas.EVT_FC_MIDDLE_UP |
|
140 |
if not self.Canvas.HitTest(event, EventType): |
|
141 |
self.Canvas._RaiseMouseEvent(event, EventType) |
|
142 |
||
143 |
def OnMiddleDouble(self, event): |
|
144 |
EventType = FloatCanvas.EVT_FC_MIDDLE_DCLICK |
|
145 |
if not self.Canvas.HitTest(event, EventType): |
|
146 |
self.Canvas._RaiseMouseEvent(event, EventType) |
|
147 |
||
148 |
def OnRightDown(self, event): |
|
149 |
EventType = FloatCanvas.EVT_FC_RIGHT_DOWN |
|
150 |
if not self.Canvas.HitTest(event, EventType): |
|
151 |
self.Canvas._RaiseMouseEvent(event, EventType) |
|
152 |
||
153 |
def OnRightUp(self, event): |
|
154 |
EventType = FloatCanvas.EVT_FC_RIGHT_UP |
|
155 |
if not self.Canvas.HitTest(event, EventType): |
|
156 |
self.Canvas._RaiseMouseEvent(event, EventType) |
|
157 |
||
158 |
def OnRightDouble(self, event): |
|
159 |
EventType = FloatCanvas.EVT_FC_RIGHT_DCLICK |
|
160 |
if not self.Canvas.HitTest(event, EventType): |
|
161 |
self.Canvas._RaiseMouseEvent(event, EventType) |
|
162 |
||
163 |
def OnWheel(self, event): |
|
164 |
EventType = FloatCanvas.EVT_FC_MOUSEWHEEL |
|
165 |
self.Canvas._RaiseMouseEvent(event, EventType) |
|
166 |
||
167 |
def OnMove(self, event): |
|
168 |
## The Move event always gets raised, even if there is a hit-test
|
|
169 |
self.Canvas.MouseOverTest(event) |
|
170 |
self.Canvas._RaiseMouseEvent(event,FloatCanvas.EVT_FC_MOTION) |
|
171 |
||
172 |
||
173 |
class GUIMove(GUIBase): |
|
174 |
"""
|
|
175 |
Mode that moves the image (pans).
|
|
176 |
It doesn't change any coordinates, it only changes what the viewport is
|
|
177 |
"""
|
|
178 |
def __init__(self, canvas=None): |
|
179 |
GUIBase.__init__(self, canvas) |
|
180 |
self.Cursor = self.Cursors.HandCursor |
|
181 |
self.GrabCursor = self.Cursors.GrabHandCursor |
|
182 |
self.StartMove = None |
|
183 |
self.MidMove = None |
|
184 |
self.PrevMoveXY = None |
|
185 |
||
186 |
## timer to give a delay when moving so that buffers aren't re-built too many times.
|
|
187 |
self.MoveTimer = wx.PyTimer(self.OnMoveTimer) |
|
188 |
||
189 |
def OnLeftDown(self, event): |
|
190 |
self.Canvas.SetCursor(self.GrabCursor) |
|
191 |
self.Canvas.CaptureMouse() |
|
192 |
self.StartMove = N.array( event.GetPosition() ) |
|
193 |
self.MidMove = self.StartMove |
|
194 |
self.PrevMoveXY = (0,0) |
|
195 |
||
196 |
def OnLeftUp(self, event): |
|
197 |
self.Canvas.SetCursor(self.Cursor) |
|
198 |
if self.StartMove is not None: |
|
199 |
self.EndMove = N.array(event.GetPosition()) |
|
200 |
DiffMove = self.MidMove-self.EndMove |
|
201 |
self.Canvas.MoveImage(DiffMove, 'Pixel', ReDraw=True) |
|
202 |
||
203 |
def OnMove(self, event): |
|
204 |
# Always raise the Move event.
|
|
205 |
self.Canvas._RaiseMouseEvent(event, FloatCanvas.EVT_FC_MOTION) |
|
206 |
if event.Dragging() and event.LeftIsDown() and not self.StartMove is None: |
|
207 |
self.EndMove = N.array(event.GetPosition()) |
|
208 |
self.MoveImage(event) |
|
209 |
DiffMove = self.MidMove-self.EndMove |
|
210 |
self.Canvas.MoveImage(DiffMove, 'Pixel', ReDraw=False)# reset the canvas without re-drawing |
|
211 |
self.MidMove = self.EndMove |
|
212 |
self.MoveTimer.Start(30, oneShot=True) |
|
213 |
||
214 |
def OnMoveTimer(self, event=None): |
|
215 |
self.Canvas.Draw() |
|
216 |
||
217 |
def UpdateScreen(self): |
|
218 |
## The screen has been re-drawn, so StartMove needs to be reset.
|
|
219 |
self.StartMove = self.MidMove |
|
220 |
||
221 |
def MoveImage(self, event ): |
|
222 |
#xy1 = N.array( event.GetPosition() )
|
|
223 |
xy1 = self.EndMove |
|
224 |
wh = self.Canvas.PanelSize |
|
225 |
xy_tl = xy1 - self.StartMove |
|
226 |
dc = wx.ClientDC(self.Canvas) |
|
227 |
dc.BeginDrawing() |
|
228 |
x1,y1 = self.PrevMoveXY |
|
229 |
x2,y2 = xy_tl |
|
230 |
w,h = self.Canvas.PanelSize |
|
231 |
##fixme: This sure could be cleaner!
|
|
232 |
## This is all to fill in the background with the background color
|
|
233 |
## without flashing as the image moves.
|
|
234 |
if x2 > x1 and y2 > y1: |
|
235 |
xa = xb = x1 |
|
236 |
ya = yb = y1 |
|
237 |
wa = w |
|
238 |
ha = y2 - y1 |
|
239 |
wb = x2- x1 |
|
240 |
hb = h |
|
241 |
elif x2 > x1 and y2 <= y1: |
|
242 |
xa = x1 |
|
243 |
ya = y1 |
|
244 |
wa = x2 - x1 |
|
245 |
ha = h |
|
246 |
xb = x1 |
|
247 |
yb = y2 + h |
|
248 |
wb = w |
|
249 |
hb = y1 - y2 |
|
250 |
elif x2 <= x1 and y2 > y1: |
|
251 |
xa = x1 |
|
252 |
ya = y1 |
|
253 |
wa = w |
|
254 |
ha = y2 - y1 |
|
255 |
xb = x2 + w |
|
256 |
yb = y1 |
|
257 |
wb = x1 - x2 |
|
258 |
hb = h - y2 + y1 |
|
259 |
elif x2 <= x1 and y2 <= y1: |
|
260 |
xa = x2 + w |
|
261 |
ya = y1 |
|
262 |
wa = x1 - x2 |
|
263 |
ha = h |
|
264 |
xb = x1 |
|
265 |
yb = y2 + h |
|
266 |
wb = w |
|
267 |
hb = y1 - y2 |
|
268 |
||
269 |
dc.SetPen(wx.TRANSPARENT_PEN) |
|
270 |
dc.SetBrush(self.Canvas.BackgroundBrush) |
|
271 |
dc.DrawRectangle(xa, ya, wa, ha) |
|
272 |
dc.DrawRectangle(xb, yb, wb, hb) |
|
273 |
self.PrevMoveXY = xy_tl |
|
274 |
if self.Canvas._ForeDrawList: |
|
275 |
dc.DrawBitmapPoint(self.Canvas._ForegroundBuffer,xy_tl) |
|
276 |
else: |
|
277 |
dc.DrawBitmapPoint(self.Canvas._Buffer,xy_tl) |
|
278 |
dc.EndDrawing() |
|
279 |
#self.Canvas.Update()
|
|
280 |
||
281 |
def OnWheel(self, event): |
|
282 |
"""
|
|
283 |
By default, zoom in/out by a 0.1 factor per Wheel event.
|
|
284 |
"""
|
|
285 |
if event.GetWheelRotation() < 0: |
|
286 |
self.Canvas.Zoom(0.9) |
|
287 |
else: |
|
288 |
self.Canvas.Zoom(1.1) |
|
289 |
||
290 |
class GUIZoomIn(GUIBase): |
|
291 |
||
292 |
def __init__(self, canvas=None): |
|
293 |
GUIBase.__init__(self, canvas) |
|
294 |
self.StartRBBox = None |
|
295 |
self.PrevRBBox = None |
|
296 |
self.Cursor = self.Cursors.MagPlusCursor |
|
297 |
||
298 |
def OnLeftDown(self, event): |
|
299 |
self.StartRBBox = N.array( event.GetPosition() ) |
|
300 |
self.PrevRBBox = None |
|
301 |
self.Canvas.CaptureMouse() |
|
302 |
||
303 |
def OnLeftUp(self, event): |
|
304 |
if event.LeftUp() and not self.StartRBBox is None: |
|
305 |
self.PrevRBBox = None |
|
306 |
EndRBBox = event.GetPosition() |
|
307 |
StartRBBox = self.StartRBBox |
|
308 |
# if mouse has moved less that ten pixels, don't use the box.
|
|
309 |
if ( abs(StartRBBox[0] - EndRBBox[0]) > 10 |
|
310 |
and abs(StartRBBox[1] - EndRBBox[1]) > 10 ): |
|
311 |
EndRBBox = self.Canvas.PixelToWorld(EndRBBox) |
|
312 |
StartRBBox = self.Canvas.PixelToWorld(StartRBBox) |
|
313 |
self.Canvas.ZoomToBB( BBox.fromPoints(N.r_[EndRBBox,StartRBBox]) ) |
|
314 |
else: |
|
315 |
Center = self.Canvas.PixelToWorld(StartRBBox) |
|
316 |
self.Canvas.Zoom(1.5,Center) |
|
317 |
self.StartRBBox = None |
|
318 |
||
319 |
def OnMove(self, event): |
|
320 |
# Always raise the Move event.
|
|
321 |
self.Canvas._RaiseMouseEvent(event,FloatCanvas.EVT_FC_MOTION) |
|
322 |
if event.Dragging() and event.LeftIsDown() and not (self.StartRBBox is None): |
|
323 |
xy0 = self.StartRBBox |
|
324 |
xy1 = N.array( event.GetPosition() ) |
|
325 |
wh = abs(xy1 - xy0) |
|
326 |
wh[0] = max(wh[0], int(wh[1]*self.Canvas.AspectRatio)) |
|
327 |
wh[1] = int(wh[0] / self.Canvas.AspectRatio) |
|
328 |
xy_c = (xy0 + xy1) / 2 |
|
329 |
dc = wx.ClientDC(self.Canvas) |
|
330 |
dc.BeginDrawing() |
|
331 |
dc.SetPen(wx.Pen('WHITE', 2, wx.SHORT_DASH)) |
|
332 |
dc.SetBrush(wx.TRANSPARENT_BRUSH) |
|
333 |
dc.SetLogicalFunction(wx.XOR) |
|
334 |
if self.PrevRBBox: |
|
335 |
dc.DrawRectanglePointSize(*self.PrevRBBox) |
|
336 |
self.PrevRBBox = ( xy_c - wh/2, wh ) |
|
337 |
dc.DrawRectanglePointSize( *self.PrevRBBox ) |
|
338 |
dc.EndDrawing() |
|
339 |
||
340 |
def UpdateScreen(self): |
|
341 |
"""
|
|
342 |
Update gets called if the screen has been repainted in the middle of a zoom in
|
|
343 |
so the Rubber Band Box can get updated
|
|
344 |
"""
|
|
345 |
#if False:
|
|
346 |
if self.PrevRBBox is not None: |
|
347 |
dc = wx.ClientDC(self.Canvas) |
|
348 |
dc.SetPen(wx.Pen('WHITE', 2, wx.SHORT_DASH)) |
|
349 |
dc.SetBrush(wx.TRANSPARENT_BRUSH) |
|
350 |
dc.SetLogicalFunction(wx.XOR) |
|
351 |
dc.DrawRectanglePointSize(*self.PrevRBBox) |
|
352 |
||
353 |
def OnRightDown(self, event): |
|
354 |
self.Canvas.Zoom(1/1.5, event.GetPosition(), centerCoords="pixel") |
|
355 |
||
356 |
def OnWheel(self, event): |
|
357 |
if event.GetWheelRotation() < 0: |
|
358 |
self.Canvas.Zoom(0.9) |
|
359 |
else: |
|
360 |
self.Canvas.Zoom(1.1) |
|
361 |
||
362 |
class GUIZoomOut(GUIBase): |
|
363 |
||
364 |
def __init__(self, Canvas=None): |
|
365 |
GUIBase.__init__(self, Canvas) |
|
366 |
self.Cursor = self.Cursors.MagMinusCursor |
|
367 |
||
368 |
def OnLeftDown(self, event): |
|
369 |
self.Canvas.Zoom(1/1.5, event.GetPosition(), centerCoords="pixel") |
|
370 |
||
371 |
def OnRightDown(self, event): |
|
372 |
self.Canvas.Zoom(1.5, event.GetPosition(), centerCoords="pixel") |
|
373 |
||
374 |
def OnWheel(self, event): |
|
375 |
if event.GetWheelRotation() < 0: |
|
376 |
self.Canvas.Zoom(0.9) |
|
377 |
else: |
|
378 |
self.Canvas.Zoom(1.1) |
|
379 |
||
380 |
def OnMove(self, event): |
|
381 |
# Always raise the Move event.
|
|
382 |
self.Canvas._RaiseMouseEvent(event,FloatCanvas.EVT_FC_MOTION) |
|
383 |