]>
git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/mixins/rubberband.py
1 #---------------------------------------------------------------------------
2 # Name: wxPython.lib.mixins.rubberband
3 # Purpose: A mixin class for doing "RubberBand"-ing on a window.
5 # Author: Robb Shecter and members of wxPython-users
7 # Created: 11-September-2002
9 # Copyright: (c) 2002 by db-X Corporation
10 # Licence: wxWindows license
11 #---------------------------------------------------------------------------
12 # 12/14/2003 - Jeff Grimmett (grimmtooth@softhome.net)
14 # o 2.5 compatability update.
15 # o Tested, but there is an anomaly between first use and subsequent uses.
16 # First use is odd, subsequent uses seem to be OK. Init error?
17 # -- No, the first time it uses an aspect ratio, but after the reset it doesn't.
21 A mixin class for doing "RubberBand"-ing on a window.
27 # Some miscellaneous mathematical and geometrical functions
30 def isNegative(aNumber
):
38 def normalizeBox(box
):
40 Convert any negative measurements in the current
41 box to positive, and adjust the origin.
55 Convert a box specification to an extent specification.
56 I put this into a seperate function after I realized that
57 I had been implementing it wrong in several places.
60 return (b
[0], b
[1], b
[0]+b
[2]-1, b
[1]+b
[3]-1)
63 def pointInBox(x
, y
, box
):
65 Return True if the given point is contained in the box.
68 return x
>= e
[0] and x
<= e
[2] and y
>= e
[1] and y
<= e
[3]
71 def pointOnBox(x
, y
, box
, thickness
=1):
73 Return True if the point is on the outside edge
74 of the box. The thickness defines how thick the
75 edge should be. This is necessary for HCI reasons:
76 For example, it's normally very difficult for a user
77 to manuever the mouse onto a one pixel border.
80 innerBox
= (box
[0]+thickness
, box
[1]+thickness
, box
[2]-(thickness
*2), box
[3]-(thickness
*2))
81 return pointInBox(x
, y
, outerBox
) and not pointInBox(x
, y
, innerBox
)
84 def getCursorPosition(x
, y
, box
, thickness
=1):
86 Return a position number in the range 0 .. 7 to indicate
87 where on the box border the point is. The layout is:
93 x0
, y0
, x1
, y1
= boxToExtent(box
)
98 if pointInBox(x
, y
, (x0
, y0
, thickness
, thickness
)):
100 elif pointInBox(x
, y
, (x1
-delta
, y0
, thickness
, thickness
)):
102 elif pointInBox(x
, y
, (x1
-delta
, y1
-delta
, thickness
, thickness
)):
104 elif pointInBox(x
, y
, (x0
, y1
-delta
, thickness
, thickness
)):
106 elif pointInBox(x
, y
, (x0
+thickness
, y0
, w
-(thickness
*2), thickness
)):
108 elif pointInBox(x
, y
, (x1
-delta
, y0
+thickness
, thickness
, h
-(thickness
*2))):
110 elif pointInBox(x
, y
, (x0
+thickness
, y1
-delta
, w
-(thickness
*2), thickness
)):
112 elif pointInBox(x
, y
, (x0
, y0
+thickness
, thickness
, h
-(thickness
*2))):
122 A stretchable border which is drawn on top of an
123 image to define an area.
125 def __init__(self
, drawingSurface
, aspectRatio
=None):
127 self
.drawingSurface
= drawingSurface
128 self
.aspectRatio
= aspectRatio
130 self
.currentlyMoving
= None
131 self
.currentBox
= None
133 self
.__currentCursor
= None
135 drawingSurface
.Bind(wx
.EVT_MOUSE_EVENTS
, self
.__handleMouseEvents
)
136 drawingSurface
.Bind(wx
.EVT_PAINT
, self
.__handleOnPaint
)
138 def __setEnabled(self
, enabled
):
139 self
.__enabled
= enabled
141 def __isEnabled(self
):
142 return self
.__enabled
144 def __handleOnPaint(self
, event
):
148 def __isMovingCursor(self
):
150 Return True if the current cursor is one used to
151 mean moving the rubberband.
153 return self
.__currentCursor
== wx
.CURSOR_HAND
155 def __isSizingCursor(self
):
157 Return True if the current cursor is one of the ones
158 I may use to signify sizing.
160 sizingCursors
= [wx
.CURSOR_SIZENESW
,
167 sizingCursors
.index(self
.__currentCursor
)
173 def __handleMouseEvents(self
, event
):
175 React according to the new event. This is the main
176 entry point into the class. This method contains the
177 logic for the class's behavior.
182 x
, y
= event
.GetPosition()
184 # First make sure we have started a box.
185 if self
.currentBox
== None and not event
.LeftDown():
186 # No box started yet. Set cursor to the initial kind.
187 self
.__setCursor
(wx
.CURSOR_CROSS
)
191 if self
.currentBox
== None:
192 # No RB Box, so start a new one.
193 self
.currentBox
= (x
, y
, 0, 0)
195 elif self
.__isSizingCursor
():
196 # Starting a sizing operation. Change the origin.
197 position
= getCursorPosition(x
, y
, self
.currentBox
, thickness
=self
.__THICKNESS
)
198 self
.currentBox
= self
.__denormalizeBox
(position
, self
.currentBox
)
200 elif event
.Dragging() and event
.LeftIsDown():
201 # Use the cursor type to determine operation
202 if self
.__isMovingCursor
():
203 if self
.currentlyMoving
or pointInBox(x
, y
, self
.currentBox
):
204 if not self
.currentlyMoving
:
205 self
.currentlyMoving
= (x
- self
.currentBox
[0], y
- self
.currentBox
[1])
206 self
.__moveTo
(x
- self
.currentlyMoving
[0], y
- self
.currentlyMoving
[1])
207 elif self
.__isSizingCursor
():
208 self
.__resizeBox
(x
, y
)
212 self
.currentlyMoving
= None
213 self
.__normalizeBox
()
215 elif event
.Moving() and not event
.Dragging():
216 # Simple mouse movement event
217 self
.__mouseMoved
(x
,y
)
219 def __denormalizeBox(self
, position
, box
):
222 if position
== 2 or position
== 3:
223 b
= (x
, y
+ (h
-1), w
, h
* -1)
224 elif position
== 0 or position
== 1 or position
== 7:
225 b
= (x
+ (w
-1), y
+ (h
-1), w
* -1, h
* -1)
227 b
= (x
+ (w
-1), y
, w
* -1, h
)
230 def __resizeBox(self
, x
, y
):
232 Resize and repaint the box based on the given mouse
235 # Implement the correct behavior for dragging a side
236 # of the box: Only change one dimension.
237 if not self
.aspectRatio
:
238 if self
.__currentCursor
== wx
.CURSOR_SIZENS
:
240 elif self
.__currentCursor
== wx
.CURSOR_SIZEWE
:
243 x0
,y0
,w0
,h0
= self
.currentBox
244 currentExtent
= boxToExtent(self
.currentBox
)
258 w
, h
= abs(x1
-x0
)+1, abs(y1
-y0
)+1
260 w
= max(w
, int(h
* self
.aspectRatio
))
261 h
= int(w
/ self
.aspectRatio
)
262 w
*= [1,-1][isNegative(x1
-x0
)]
263 h
*= [1,-1][isNegative(y1
-y0
)]
264 newbox
= (x0
, y0
, w
, h
)
265 self
.__drawAndErase
(boxToDraw
=normalizeBox(newbox
), boxToErase
=normalizeBox(self
.currentBox
))
266 self
.currentBox
= (x0
, y0
, w
, h
)
268 def __normalizeBox(self
):
270 Convert any negative measurements in the current
271 box to positive, and adjust the origin.
273 self
.currentBox
= normalizeBox(self
.currentBox
)
275 def __mouseMoved(self
, x
, y
):
277 Called when the mouse moved without any buttons pressed
278 or dragging being done.
280 # Are we on the bounding box?
281 if pointOnBox(x
, y
, self
.currentBox
, thickness
=self
.__THICKNESS
):
282 position
= getCursorPosition(x
, y
, self
.currentBox
, thickness
=self
.__THICKNESS
)
293 self
.__setCursor
(cursor
)
294 elif pointInBox(x
, y
, self
.currentBox
):
295 self
.__setCursor
(wx
.CURSOR_HAND
)
299 def __setCursor(self
, id=None):
301 Set the mouse cursor to the given id.
303 if self
.__currentCursor
!= id: # Avoid redundant calls
305 self
.drawingSurface
.SetCursor(wx
.StockCursor(id))
307 self
.drawingSurface
.SetCursor(wx
.NullCursor
)
308 self
.__currentCursor
= id
310 def __moveCenterTo(self
, x
, y
):
312 Move the rubber band so that its center is at (x,y).
314 x0
, y0
, w
, h
= self
.currentBox
315 x2
, y2
= x
- (w
/2), y
- (h
/2)
316 self
.__moveTo
(x2
, y2
)
318 def __moveTo(self
, x
, y
):
320 Move the rubber band so that its origin is at (x,y).
322 newbox
= (x
, y
, self
.currentBox
[2], self
.currentBox
[3])
323 self
.__drawAndErase
(boxToDraw
=newbox
, boxToErase
=self
.currentBox
)
324 self
.currentBox
= newbox
326 def __drawAndErase(self
, boxToDraw
, boxToErase
=None):
328 Draw one box shape and possibly erase another.
330 dc
= wx
.ClientDC(self
.drawingSurface
)
332 dc
.SetPen(wx
.Pen(wx
.WHITE
, 1, wx
.DOT
))
333 dc
.SetBrush(wx
.TRANSPARENT_BRUSH
)
334 dc
.SetLogicalFunction(wx
.XOR
)
336 r
= wx
.Rect(*boxToErase
)
337 dc
.DrawRectangleRect(r
)
339 r
= wx
.Rect(*boxToDraw
)
340 dc
.DrawRectangleRect(r
)
343 def __dumpMouseEvent(self
, event
):
344 print 'Moving: ',event
.Moving()
345 print 'Dragging: ',event
.Dragging()
346 print 'LeftDown: ',event
.LeftDown()
347 print 'LeftisDown: ',event
.LeftIsDown()
348 print 'LeftUp: ',event
.LeftUp()
349 print 'Position: ',event
.GetPosition()
350 print 'x,y: ',event
.GetX(),event
.GetY()
358 def reset(self
, aspectRatio
=None):
360 Clear the existing rubberband
362 self
.currentBox
= None
363 self
.aspectRatio
= aspectRatio
364 self
.drawingSurface
.Refresh()
366 def getCurrentExtent(self
):
368 Return (x0, y0, x1, y1) or None if
369 no drawing has yet been done.
371 if not self
.currentBox
:
374 extent
= boxToExtent(self
.currentBox
)
377 enabled
= property(__isEnabled
, __setEnabled
, None, 'True if I am responding to mouse events')
381 if __name__
== '__main__':
382 app
= wx
.PySimpleApp()
383 frame
= wx
.Frame(None, -1, title
='RubberBand Test', size
=(300,300))
385 # Add a panel that the rubberband will work on.
386 panel
= wx
.Panel(frame
, -1)
387 panel
.SetBackgroundColour(wx
.BLUE
)
389 # Create the rubberband
390 frame
.rubberBand
= RubberBand(drawingSurface
=panel
)
391 frame
.rubberBand
.reset(aspectRatio
=0.5)
393 # Add a button that creates a new rubberband
394 def __newRubberBand(event
):
395 frame
.rubberBand
.reset()
396 button
= wx
.Button(frame
, 100, 'Reset Rubberband')
397 frame
.Bind(wx
.EVT_BUTTON
, __newRubberBand
, button
)
400 sizer
= wx
.BoxSizer(wx
.VERTICAL
)
401 sizer
.Add(panel
, 1, wx
.EXPAND | wx
.ALL
, 5)
402 sizer
.Add(button
, 0, wx
.ALIGN_CENTER | wx
.ALL
, 5)
403 frame
.SetAutoLayout(1)
404 frame
.SetSizer(sizer
)