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
8
# RCS-ID: $Id: rubberband.py 24889 2003-12-17 00:34:40Z RD $
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)