]>
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
)