]> git.saurik.com Git - wxWidgets.git/blame - wxPython/wx/lib/mixins/rubberband.py
Version number
[wxWidgets.git] / wxPython / wx / lib / mixins / rubberband.py
CommitLineData
d14a1e28
RD
1#---------------------------------------------------------------------------
2# Name: wxPython.lib.mixins.rubberband
3# Purpose: A mixin class for doing "RubberBand"-ing on a window.
4#
5# Author: Robb Shecter and members of wxPython-users
6#
7# Created: 11-September-2002
8# RCS-ID: $Id$
9# Copyright: (c) 2002 by db-X Corporation
10# Licence: wxWindows license
11#---------------------------------------------------------------------------
1fded56b 12
d14a1e28
RD
13"""
14A mixin class for doing "RubberBand"-ing on a window.
15"""
1fded56b 16
d14a1e28
RD
17from wxPython.wx import *
18
19
20#
21# Some miscellaneous mathematical and geometrical functions
22#
23
24def isNegative(aNumber):
25 """
26 x < 0: 1
27 else: 0
28 """
29 return aNumber < 0
30
31
32def normalizeBox(box):
33 """
34 Convert any negative measurements in the current
35 box to positive, and adjust the origin.
36 """
37 x, y, w, h = box
38 if w < 0:
39 x += (w+1)
40 w *= -1
41 if h < 0:
42 y += (h+1)
43 h *= -1
44 return (x, y, w, h)
45
46
47def boxToExtent(box):
48 """
49 Convert a box specification to an extent specification.
50 I put this into a seperate function after I realized that
51 I had been implementing it wrong in several places.
52 """
53 b = normalizeBox(box)
54 return (b[0], b[1], b[0]+b[2]-1, b[1]+b[3]-1)
55
56
57def pointInBox(x, y, box):
58 """
59 Return True if the given point is contained in the box.
60 """
61 e = boxToExtent(box)
62 return x >= e[0] and x <= e[2] and y >= e[1] and y <= e[3]
63
64
65def pointOnBox(x, y, box, thickness=1):
66 """
67 Return True if the point is on the outside edge
68 of the box. The thickness defines how thick the
69 edge should be. This is necessary for HCI reasons:
70 For example, it's normally very difficult for a user
71 to manuever the mouse onto a one pixel border.
72 """
73 outerBox = box
74 innerBox = (box[0]+thickness, box[1]+thickness, box[2]-(thickness*2), box[3]-(thickness*2))
75 return pointInBox(x, y, outerBox) and not pointInBox(x, y, innerBox)
76
77
78def getCursorPosition(x, y, box, thickness=1):
79 """
80 Return a position number in the range 0 .. 7 to indicate
81 where on the box border the point is. The layout is:
82
83 0 1 2
84 7 3
85 6 5 4
86 """
87 x0, y0, x1, y1 = boxToExtent(box)
88 w, h = box[2], box[3]
89 delta = thickness - 1
90 p = None
91
92 if pointInBox(x, y, (x0, y0, thickness, thickness)):
93 p = 0
94 elif pointInBox(x, y, (x1-delta, y0, thickness, thickness)):
95 p = 2
96 elif pointInBox(x, y, (x1-delta, y1-delta, thickness, thickness)):
97 p = 4
98 elif pointInBox(x, y, (x0, y1-delta, thickness, thickness)):
99 p = 6
100 elif pointInBox(x, y, (x0+thickness, y0, w-(thickness*2), thickness)):
101 p = 1
102 elif pointInBox(x, y, (x1-delta, y0+thickness, thickness, h-(thickness*2))):
103 p = 3
104 elif pointInBox(x, y, (x0+thickness, y1-delta, w-(thickness*2), thickness)):
105 p = 5
106 elif pointInBox(x, y, (x0, y0+thickness, thickness, h-(thickness*2))):
107 p = 7
108
109 return p
110
111
112
113
114class RubberBand:
115 """
116 A stretchable border which is drawn on top of an
117 image to define an area.
118 """
119 def __init__(self, drawingSurface, aspectRatio=None):
120 self.__THICKNESS = 5
121 self.drawingSurface = drawingSurface
122 self.aspectRatio = aspectRatio
123 self.hasLetUp = 0
124 self.currentlyMoving = None
125 self.currentBox = None
126 self.__enabled = 1
127 self.__currentCursor = None
128 EVT_MOUSE_EVENTS(drawingSurface, self.__handleMouseEvents)
129 EVT_PAINT(drawingSurface, self.__handleOnPaint)
130
131 def __setEnabled(self, enabled):
132 self.__enabled = enabled
133
134 def __isEnabled(self):
135 return self.__enabled
136
137 def __handleOnPaint(self, event):
138 #print 'paint'
139 event.Skip()
140
141 def __isMovingCursor(self):
142 """
143 Return True if the current cursor is one used to
144 mean moving the rubberband.
145 """
146 return self.__currentCursor == wxCURSOR_HAND
147
148 def __isSizingCursor(self):
149 """
150 Return True if the current cursor is one of the ones
151 I may use to signify sizing.
152 """
153 sizingCursors = [wxCURSOR_SIZENESW,
154 wxCURSOR_SIZENS,
155 wxCURSOR_SIZENWSE,
156 wxCURSOR_SIZEWE,
157 wxCURSOR_SIZING,
158 wxCURSOR_CROSS]
159 try:
160 sizingCursors.index(self.__currentCursor)
161 return 1
162 except ValueError:
163 return 0
164
165
166 def __handleMouseEvents(self, event):
167 """
168 React according to the new event. This is the main
169 entry point into the class. This method contains the
170 logic for the class's behavior.
171 """
172 if not self.enabled:
173 return
174
175 x, y = event.GetPosition()
176
177 # First make sure we have started a box.
178 if self.currentBox == None and not event.LeftDown():
179 # No box started yet. Set cursor to the initial kind.
180 self.__setCursor(wxCURSOR_CROSS)
181 return
182
183 if event.LeftDown():
184 if self.currentBox == None:
185 # No RB Box, so start a new one.
186 self.currentBox = (x, y, 0, 0)
187 self.hasLetUp = 0
188 elif self.__isSizingCursor():
189 # Starting a sizing operation. Change the origin.
190 position = getCursorPosition(x, y, self.currentBox, thickness=self.__THICKNESS)
191 self.currentBox = self.__denormalizeBox(position, self.currentBox)
192
193 elif event.Dragging() and event.LeftIsDown():
194 # Use the cursor type to determine operation
195 if self.__isMovingCursor():
196 if self.currentlyMoving or pointInBox(x, y, self.currentBox):
197 if not self.currentlyMoving:
198 self.currentlyMoving = (x - self.currentBox[0], y - self.currentBox[1])
199 self.__moveTo(x - self.currentlyMoving[0], y - self.currentlyMoving[1])
200 elif self.__isSizingCursor():
201 self.__resizeBox(x, y)
202
203 elif event.LeftUp():
204 self.hasLetUp = 1
205 self.currentlyMoving = None
206 self.__normalizeBox()
207
208 elif event.Moving() and not event.Dragging():
209 # Simple mouse movement event
210 self.__mouseMoved(x,y)
211
212 def __denormalizeBox(self, position, box):
213 x, y, w, h = box
214 b = box
215 if position == 2 or position == 3:
216 b = (x, y + (h-1), w, h * -1)
217 elif position == 0 or position == 1 or position == 7:
218 b = (x + (w-1), y + (h-1), w * -1, h * -1)
219 elif position == 6:
220 b = (x + (w-1), y, w * -1, h)
221 return b
222
223 def __resizeBox(self, x, y):
224 """
225 Resize and repaint the box based on the given mouse
226 coordinates.
227 """
228 # Implement the correct behavior for dragging a side
229 # of the box: Only change one dimension.
230 if not self.aspectRatio:
231 if self.__currentCursor == wxCURSOR_SIZENS:
232 x = None
233 elif self.__currentCursor == wxCURSOR_SIZEWE:
234 y = None
235
236 x0,y0,w0,h0 = self.currentBox
237 currentExtent = boxToExtent(self.currentBox)
238 if x == None:
239 if w0 < 1:
240 w0 += 1
241 else:
242 w0 -= 1
243 x = x0 + w0
244 if y == None:
245 if h0 < 1:
246 h0 += 1
247 else:
248 h0 -= 1
249 y = y0 + h0
250 x1,y1 = x, y
251 w, h = abs(x1-x0)+1, abs(y1-y0)+1
252 if self.aspectRatio:
253 w = max(w, int(h * self.aspectRatio))
254 h = int(w / self.aspectRatio)
255 w *= [1,-1][isNegative(x1-x0)]
256 h *= [1,-1][isNegative(y1-y0)]
257 newbox = (x0, y0, w, h)
258 self.__drawAndErase(boxToDraw=normalizeBox(newbox), boxToErase=normalizeBox(self.currentBox))
259 self.currentBox = (x0, y0, w, h)
260
261 def __normalizeBox(self):
262 """
263 Convert any negative measurements in the current
264 box to positive, and adjust the origin.
265 """
266 self.currentBox = normalizeBox(self.currentBox)
267
268 def __mouseMoved(self, x, y):
269 """
270 Called when the mouse moved without any buttons pressed
271 or dragging being done.
272 """
273 # Are we on the bounding box?
274 if pointOnBox(x, y, self.currentBox, thickness=self.__THICKNESS):
275 position = getCursorPosition(x, y, self.currentBox, thickness=self.__THICKNESS)
276 cursor = [
277 wxCURSOR_SIZENWSE,
278 wxCURSOR_SIZENS,
279 wxCURSOR_SIZENESW,
280 wxCURSOR_SIZEWE,
281 wxCURSOR_SIZENWSE,
282 wxCURSOR_SIZENS,
283 wxCURSOR_SIZENESW,
284 wxCURSOR_SIZEWE
285 ] [position]
286 self.__setCursor(cursor)
287 elif pointInBox(x, y, self.currentBox):
288 self.__setCursor(wxCURSOR_HAND)
289 else:
290 self.__setCursor()
291
292 def __setCursor(self, id=None):
293 """
294 Set the mouse cursor to the given id.
295 """
296 if self.__currentCursor != id: # Avoid redundant calls
297 if id:
298 self.drawingSurface.SetCursor(wxStockCursor(id))
299 else:
300 self.drawingSurface.SetCursor(wxNullCursor)
301 self.__currentCursor = id
302
303 def __moveCenterTo(self, x, y):
304 """
305 Move the rubber band so that its center is at (x,y).
306 """
307 x0, y0, w, h = self.currentBox
308 x2, y2 = x - (w/2), y - (h/2)
309 self.__moveTo(x2, y2)
310
311 def __moveTo(self, x, y):
312 """
313 Move the rubber band so that its origin is at (x,y).
314 """
315 newbox = (x, y, self.currentBox[2], self.currentBox[3])
316 self.__drawAndErase(boxToDraw=newbox, boxToErase=self.currentBox)
317 self.currentBox = newbox
318
319 def __drawAndErase(self, boxToDraw, boxToErase=None):
320 """
321 Draw one box shape and possibly erase another.
322 """
323 dc = wxClientDC(self.drawingSurface)
324 dc.BeginDrawing()
325 dc.SetPen(wxPen(wxWHITE, 1, wxDOT))
326 dc.SetBrush(wxTRANSPARENT_BRUSH)
327 dc.SetLogicalFunction(wxXOR)
328 if boxToErase:
329 dc.DrawRectangle(*boxToErase)
330 dc.DrawRectangle(*boxToDraw)
331 dc.EndDrawing()
332
333 def __dumpMouseEvent(self, event):
334 print 'Moving: ',event.Moving()
335 print 'Dragging: ',event.Dragging()
336 print 'LeftDown: ',event.LeftDown()
337 print 'LeftisDown: ',event.LeftIsDown()
338 print 'LeftUp: ',event.LeftUp()
339 print 'Position: ',event.GetPosition()
340 print 'x,y: ',event.GetX(),event.GetY()
341 print
342
343
344 #
345 # The public API:
346 #
347
348 def reset(self, aspectRatio=None):
349 """
350 Clear the existing rubberband
351 """
352 self.currentBox = None
353 self.aspectRatio = aspectRatio
354 self.drawingSurface.Refresh()
355
356 def getCurrentExtent(self):
357 """
358 Return (x0, y0, x1, y1) or None if
359 no drawing has yet been done.
360 """
361 if not self.currentBox:
362 extent = None
363 else:
364 extent = boxToExtent(self.currentBox)
365 return extent
366
367 enabled = property(__isEnabled, __setEnabled, None, 'True if I am responding to mouse events')
368
369
370
371if __name__ == '__main__':
372 app = wxPySimpleApp()
373 frame = wxFrame(None, -1, title='RubberBand Test', size=(300,300))
374
375 # Add a panel that the rubberband will work on.
376 panel = wxPanel(frame, -1)
377 panel.SetBackgroundColour(wxBLUE)
378
379 # Create the rubberband
380 frame.rubberBand = RubberBand(drawingSurface=panel)
381 frame.rubberBand.reset(aspectRatio=0.5)
382
383 # Add a button that creates a new rubberband
384 def __newRubberBand(event):
385 frame.rubberBand.reset()
386 button = wxButton(frame, 100, 'Reset Rubberband')
387 EVT_BUTTON(frame, 100, __newRubberBand)
388
389 # Layout the frame
390 sizer = wxBoxSizer(wxVERTICAL)
391 sizer.Add(panel, 1, wxEXPAND | wxALL, 5)
392 sizer.Add(button, 0, wxALIGN_CENTER | wxALL, 5)
393 frame.SetAutoLayout(1)
394 frame.SetSizer(sizer)
395 frame.Show(1)
396 app.MainLoop()