]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/ogl/_composit.py
wxCheckListBox doesn't require wxUSE_OWNER_DRAWN when using WXUNIVERSAL
[wxWidgets.git] / wxPython / wx / lib / ogl / _composit.py
1 # -*- coding: iso-8859-1 -*-
2 #----------------------------------------------------------------------------
3 # Name: composit.py
4 # Purpose: Composite class
5 #
6 # Author: Pierre Hjälm (from C++ original by Julian Smart)
7 #
8 # Created: 2004-05-08
9 # RCS-ID: $Id$
10 # Copyright: (c) 2004 Pierre Hjälm - 1998 Julian Smart
11 # Licence: wxWindows license
12 #----------------------------------------------------------------------------
13
14 import sys
15 import wx
16
17 from _basic import RectangleShape, Shape, ControlPoint
18 from _oglmisc import *
19
20 KEY_SHIFT, KEY_CTRL = 1, 2
21
22 _objectStartX = 0.0
23 _objectStartY = 0.0
24
25 CONSTRAINT_CENTRED_VERTICALLY = 1
26 CONSTRAINT_CENTRED_HORIZONTALLY = 2
27 CONSTRAINT_CENTRED_BOTH = 3
28 CONSTRAINT_LEFT_OF = 4
29 CONSTRAINT_RIGHT_OF = 5
30 CONSTRAINT_ABOVE = 6
31 CONSTRAINT_BELOW = 7
32 CONSTRAINT_ALIGNED_TOP = 8
33 CONSTRAINT_ALIGNED_BOTTOM = 9
34 CONSTRAINT_ALIGNED_LEFT = 10
35 CONSTRAINT_ALIGNED_RIGHT = 11
36
37 # Like aligned, but with the objects centred on the respective edge
38 # of the reference object.
39 CONSTRAINT_MIDALIGNED_TOP = 12
40 CONSTRAINT_MIDALIGNED_BOTTOM = 13
41 CONSTRAINT_MIDALIGNED_LEFT = 14
42 CONSTRAINT_MIDALIGNED_RIGHT = 15
43
44
45 # Backwards compatibility names. These should be removed eventually.
46 gyCONSTRAINT_CENTRED_VERTICALLY = CONSTRAINT_CENTRED_VERTICALLY
47 gyCONSTRAINT_CENTRED_HORIZONTALLY = CONSTRAINT_CENTRED_HORIZONTALLY
48 gyCONSTRAINT_CENTRED_BOTH = CONSTRAINT_CENTRED_BOTH
49 gyCONSTRAINT_LEFT_OF = CONSTRAINT_LEFT_OF
50 gyCONSTRAINT_RIGHT_OF = CONSTRAINT_RIGHT_OF
51 gyCONSTRAINT_ABOVE = CONSTRAINT_ABOVE
52 gyCONSTRAINT_BELOW = CONSTRAINT_BELOW
53 gyCONSTRAINT_ALIGNED_TOP = CONSTRAINT_ALIGNED_TOP
54 gyCONSTRAINT_ALIGNED_BOTTOM = CONSTRAINT_ALIGNED_BOTTOM
55 gyCONSTRAINT_ALIGNED_LEFT = CONSTRAINT_ALIGNED_LEFT
56 gyCONSTRAINT_ALIGNED_RIGHT = CONSTRAINT_ALIGNED_RIGHT
57 gyCONSTRAINT_MIDALIGNED_TOP = CONSTRAINT_MIDALIGNED_TOP
58 gyCONSTRAINT_MIDALIGNED_BOTTOM = CONSTRAINT_MIDALIGNED_BOTTOM
59 gyCONSTRAINT_MIDALIGNED_LEFT = CONSTRAINT_MIDALIGNED_LEFT
60 gyCONSTRAINT_MIDALIGNED_RIGHT = CONSTRAINT_MIDALIGNED_RIGHT
61
62
63
64 class ConstraintType(object):
65 def __init__(self, theType, theName, thePhrase):
66 self._type = theType
67 self._name = theName
68 self._phrase = thePhrase
69
70
71
72 ConstraintTypes = [
73 [CONSTRAINT_CENTRED_VERTICALLY,
74 ConstraintType(CONSTRAINT_CENTRED_VERTICALLY, "Centre vertically", "centred vertically w.r.t.")],
75
76 [CONSTRAINT_CENTRED_HORIZONTALLY,
77 ConstraintType(CONSTRAINT_CENTRED_HORIZONTALLY, "Centre horizontally", "centred horizontally w.r.t.")],
78
79 [CONSTRAINT_CENTRED_BOTH,
80 ConstraintType(CONSTRAINT_CENTRED_BOTH, "Centre", "centred w.r.t.")],
81
82 [CONSTRAINT_LEFT_OF,
83 ConstraintType(CONSTRAINT_LEFT_OF, "Left of", "left of")],
84
85 [CONSTRAINT_RIGHT_OF,
86 ConstraintType(CONSTRAINT_RIGHT_OF, "Right of", "right of")],
87
88 [CONSTRAINT_ABOVE,
89 ConstraintType(CONSTRAINT_ABOVE, "Above", "above")],
90
91 [CONSTRAINT_BELOW,
92 ConstraintType(CONSTRAINT_BELOW, "Below", "below")],
93
94 # Alignment
95 [CONSTRAINT_ALIGNED_TOP,
96 ConstraintType(CONSTRAINT_ALIGNED_TOP, "Top-aligned", "aligned to the top of")],
97
98 [CONSTRAINT_ALIGNED_BOTTOM,
99 ConstraintType(CONSTRAINT_ALIGNED_BOTTOM, "Bottom-aligned", "aligned to the bottom of")],
100
101 [CONSTRAINT_ALIGNED_LEFT,
102 ConstraintType(CONSTRAINT_ALIGNED_LEFT, "Left-aligned", "aligned to the left of")],
103
104 [CONSTRAINT_ALIGNED_RIGHT,
105 ConstraintType(CONSTRAINT_ALIGNED_RIGHT, "Right-aligned", "aligned to the right of")],
106
107 # Mid-alignment
108 [CONSTRAINT_MIDALIGNED_TOP,
109 ConstraintType(CONSTRAINT_MIDALIGNED_TOP, "Top-midaligned", "centred on the top of")],
110
111 [CONSTRAINT_MIDALIGNED_BOTTOM,
112 ConstraintType(CONSTRAINT_MIDALIGNED_BOTTOM, "Bottom-midaligned", "centred on the bottom of")],
113
114 [CONSTRAINT_MIDALIGNED_LEFT,
115 ConstraintType(CONSTRAINT_MIDALIGNED_LEFT, "Left-midaligned", "centred on the left of")],
116
117 [CONSTRAINT_MIDALIGNED_RIGHT,
118 ConstraintType(CONSTRAINT_MIDALIGNED_RIGHT, "Right-midaligned", "centred on the right of")]
119 ]
120
121
122
123
124 class Constraint(object):
125 """A Constraint object helps specify how child shapes are laid out with
126 respect to siblings and parents.
127
128 Derived from:
129 wxObject
130 """
131 def __init__(self, type, constraining, constrained):
132 self._xSpacing = 0.0
133 self._ySpacing = 0.0
134
135 self._constraintType = type
136 self._constraintingObject = constraining
137
138 self._constraintId = 0
139 self._constraintName = "noname"
140
141 self._constrainedObjects = constrained[:]
142
143 def __repr__(self):
144 return "<%s.%s>" % (self.__class__.__module__, self.__class__.__name__)
145
146 def SetSpacing(self, x, y):
147 """Sets the horizontal and vertical spacing for the constraint."""
148 self._xSpacing = x
149 self._ySpacing = y
150
151 def Equals(self, a, b):
152 """Return TRUE if x and y are approximately equal (for the purposes
153 of evaluating the constraint).
154 """
155 marg = 0.5
156
157 return b <= a + marg and b >= a - marg
158
159 def Evaluate(self):
160 """Evaluate this constraint and return TRUE if anything changed."""
161 maxWidth, maxHeight = self._constraintingObject.GetBoundingBoxMax()
162 minWidth, minHeight = self._constraintingObject.GetBoundingBoxMin()
163 x = self._constraintingObject.GetX()
164 y = self._constraintingObject.GetY()
165
166 dc = wx.ClientDC(self._constraintingObject.GetCanvas())
167 self._constraintingObject.GetCanvas().PrepareDC(dc)
168
169 if self._constraintType == CONSTRAINT_CENTRED_VERTICALLY:
170 n = len(self._constrainedObjects)
171 totalObjectHeight = 0.0
172 for constrainedObject in self._constrainedObjects:
173 width2, height2 = constrainedObject.GetBoundingBoxMax()
174 totalObjectHeight += height2
175
176 # Check if within the constraining object...
177 if totalObjectHeight + (n + 1) * self._ySpacing <= minHeight:
178 spacingY = (minHeight - totalObjectHeight) / (n + 1.0)
179 startY = y - minHeight / 2.0
180 else: # Otherwise, use default spacing
181 spacingY = self._ySpacing
182 startY = y - (totalObjectHeight + (n + 1) * spacingY) / 2.0
183
184 # Now position the objects
185 changed = False
186 for constrainedObject in self._constrainedObjects:
187 width2, height2 = constrainedObject.GetBoundingBoxMax()
188 startY += spacingY + height2 / 2.0
189 if not self.Equals(startY, constrainedObject.GetY()):
190 constrainedObject.Move(dc, constrainedObject.GetX(), startY, False)
191 changed = True
192 startY += height2 / 2.0
193 return changed
194 elif self._constraintType == CONSTRAINT_CENTRED_HORIZONTALLY:
195 n = len(self._constrainedObjects)
196 totalObjectWidth = 0.0
197 for constrainedObject in self._constrainedObjects:
198 width2, height2 = constrainedObject.GetBoundingBoxMax()
199 totalObjectWidth += width2
200
201 # Check if within the constraining object...
202 if totalObjectWidth + (n + 1) * self._xSpacing <= minWidth:
203 spacingX = (minWidth - totalObjectWidth) / (n + 1.0)
204 startX = x - minWidth / 2.0
205 else: # Otherwise, use default spacing
206 spacingX = self._xSpacing
207 startX = x - (totalObjectWidth + (n + 1) * spacingX) / 2.0
208
209 # Now position the objects
210 changed = False
211 for constrainedObject in self._constrainedObjects:
212 width2, height2 = constrainedObject.GetBoundingBoxMax()
213 startX += spacingX + width2 / 2.0
214 if not self.Equals(startX, constrainedObject.GetX()):
215 constrainedObject.Move(dc, startX, constrainedObject.GetY(), False)
216 changed = True
217 startX += width2 / 2.0
218 return changed
219 elif self._constraintType == CONSTRAINT_CENTRED_BOTH:
220 n = len(self._constrainedObjects)
221 totalObjectWidth = 0.0
222 totalObjectHeight = 0.0
223
224 for constrainedObject in self._constrainedObjects:
225 width2, height2 = constrainedObject.GetBoundingBoxMax()
226 totalObjectWidth += width2
227 totalObjectHeight += height2
228
229 # Check if within the constraining object...
230 if totalObjectHeight + (n + 1) * self._xSpacing <= minWidth:
231 spacingX = (minWidth - totalObjectWidth) / (n + 1.0)
232 startX = x - minWidth / 2.0
233 else: # Otherwise, use default spacing
234 spacingX = self._xSpacing
235 startX = x - (totalObjectWidth + (n + 1) * spacingX) / 2.0
236
237 # Check if within the constraining object...
238 if totalObjectHeight + (n + 1) * self._ySpacing <= minHeight:
239 spacingY = (minHeight - totalObjectHeight) / (n + 1.0)
240 startY = y - minHeight / 2.0
241 else: # Otherwise, use default spacing
242 spacingY = self._ySpacing
243 startY = y - (totalObjectHeight + (n + 1) * spacingY) / 2.0
244
245 # Now position the objects
246 changed = False
247 for constrainedObject in self._constrainedObjects:
248 width2, height2 = constrainedObject.GetBoundingBoxMax()
249 startX += spacingX + width2 / 2.0
250 startY += spacingY + height2 / 2.0
251
252 if not self.Equals(startX, constrainedObject.GetX()) or not self.Equals(startY, constrainedObject.GetY()):
253 constrainedObject.Move(dc, startX, startY, False)
254 changed = True
255
256 startX += width2 / 2.0
257 startY += height2 / 2.0
258 return changed
259 elif self._constraintType == CONSTRAINT_LEFT_OF:
260 changed = False
261 for constrainedObject in self._constrainedObjects:
262 width2, height2 = constrainedObject.GetBoundingBoxMax()
263
264 x3 = x - minWidth / 2.0 - width2 / 2.0 - self._xSpacing
265 if not self.Equals(x3, constrainedObject.GetX()):
266 changed = True
267 constrainedObject.Move(dc, x3, constrainedObject.GetY(), False)
268 return changed
269 elif self._constraintType == CONSTRAINT_RIGHT_OF:
270 changed = False
271
272 for constrainedObject in self._constrainedObjects:
273 width2, height2 = constrainedObject.GetBoundingBoxMax()
274 x3 = x + minWidth / 2.0 + width2 / 2.0 + self._xSpacing
275 if not self.Equals(x3, constrainedObject.GetX()):
276 constrainedObject.Move(dc, x3, constrainedObject.GetY(), False)
277 changed = True
278 return changed
279 elif self._constraintType == CONSTRAINT_ABOVE:
280 changed = False
281
282 for constrainedObject in self._constrainedObjects:
283 width2, height2 = constrainedObject.GetBoundingBoxMax()
284
285 y3 = y - minHeight / 2.0 - height2 / 2.0 - self._ySpacing
286 if not self.Equals(y3, constrainedObject.GetY()):
287 changed = True
288 constrainedObject.Move(dc, constrainedObject.GetX(), y3, False)
289 return changed
290 elif self._constraintType == CONSTRAINT_BELOW:
291 changed = False
292
293 for constrainedObject in self._constrainedObjects:
294 width2, height2 = constrainedObject.GetBoundingBoxMax()
295
296 y3 = y + minHeight / 2.0 + height2 / 2.0 + self._ySpacing
297 if not self.Equals(y3, constrainedObject.GetY()):
298 changed = True
299 constrainedObject.Move(dc, constrainedObject.GetX(), y3, False)
300 return changed
301 elif self._constraintType == CONSTRAINT_ALIGNED_LEFT:
302 changed = False
303 for constrainedObject in self._constrainedObjects:
304 width2, height2 = constrainedObject.GetBoundingBoxMax()
305 x3 = x - minWidth / 2.0 + width2 / 2.0 + self._xSpacing
306 if not self.Equals(x3, constrainedObject.GetX()):
307 changed = True
308 constrainedObject.Move(dc, x3, constrainedObject.GetY(), False)
309 return changed
310 elif self._constraintType == CONSTRAINT_ALIGNED_RIGHT:
311 changed = False
312 for constrainedObject in self._constrainedObjects:
313 width2, height2 = constrainedObject.GetBoundingBoxMax()
314 x3 = x + minWidth / 2.0 - width2 / 2.0 - self._xSpacing
315 if not self.Equals(x3, constrainedObject.GetX()):
316 changed = True
317 constrainedObject.Move(dc, x3, constrainedObject.GetY(), False)
318 return changed
319 elif self._constraintType == CONSTRAINT_ALIGNED_TOP:
320 changed = False
321 for constrainedObject in self._constrainedObjects:
322 width2, height2 = constrainedObject.GetBoundingBoxMax()
323 y3 = y - minHeight / 2.0 + height2 / 2.0 + self._ySpacing
324 if not self.Equals(y3, constrainedObject.GetY()):
325 changed = True
326 constrainedObject.Move(dc, constrainedObject.GetX(), y3, False)
327 return changed
328 elif self._constraintType == CONSTRAINT_ALIGNED_BOTTOM:
329 changed = False
330 for constrainedObject in self._constrainedObjects:
331 width2, height2 = constrainedObject.GetBoundingBoxMax()
332 y3 = y + minHeight / 2.0 - height2 / 2.0 - self._ySpacing
333 if not self.Equals(y3, constrainedObject.GetY()):
334 changed = True
335 constrainedObject.Move(dc, constrainedObject.GetX(), y3, False)
336 return changed
337 elif self._constraintType == CONSTRAINT_MIDALIGNED_LEFT:
338 changed = False
339 for constrainedObject in self._constrainedObjects:
340 x3 = x - minWidth / 2.0
341 if not self.Equals(x3, constrainedObject.GetX()):
342 changed = True
343 constrainedObject.Move(dc, x3, constrainedObject.GetY(), False)
344 return changed
345 elif self._constraintType == CONSTRAINT_MIDALIGNED_RIGHT:
346 changed = False
347 for constrainedObject in self._constrainedObjects:
348 x3 = x + minWidth / 2.0
349 if not self.Equals(x3, constrainedObject.GetX()):
350 changed = True
351 constrainedObject.Move(dc, x3, constrainedObject.GetY(), False)
352 return changed
353 elif self._constraintType == CONSTRAINT_MIDALIGNED_TOP:
354 changed = False
355 for constrainedObject in self._constrainedObjects:
356 y3 = y - minHeight / 2.0
357 if not self.Equals(y3, constrainedObject.GetY()):
358 changed = True
359 constrainedObject.Move(dc, constrainedObject.GetX(), y3, False)
360 return changed
361 elif self._constraintType == CONSTRAINT_MIDALIGNED_BOTTOM:
362 changed = False
363 for constrainedObject in self._constrainedObjects:
364 y3 = y + minHeight / 2.0
365 if not self.Equals(y3, constrainedObject.GetY()):
366 changed = True
367 constrainedObject.Move(dc, constrainedObject.GetX(), y3, False)
368 return changed
369
370 return False
371
372 OGLConstraint = wx._core._deprecated(Constraint,
373 "The OGLConstraint name is deprecated, use `ogl.Constraint` instead.")
374
375
376 class CompositeShape(RectangleShape):
377 """This is an object with a list of child objects, and a list of size
378 and positioning constraints between the children.
379
380 Derived from:
381 wxRectangleShape
382 """
383 def __init__(self):
384 RectangleShape.__init__(self, 100.0, 100.0)
385
386 self._oldX = self._xpos
387 self._oldY = self._ypos
388
389 self._constraints = []
390 self._divisions = [] # In case it's a container
391
392 def OnDraw(self, dc):
393 x1 = self._xpos - self._width / 2.0
394 y1 = self._ypos - self._height / 2.0
395
396 if self._shadowMode != SHADOW_NONE:
397 if self._shadowBrush:
398 dc.SetBrush(self._shadowBrush)
399 dc.SetPen(wx.Pen(wx.WHITE, 1, wx.TRANSPARENT))
400
401 if self._cornerRadius:
402 dc.DrawRoundedRectangle(x1 + self._shadowOffsetX, y1 + self._shadowOffsetY, self._width, self._height, self._cornerRadius)
403 else:
404 dc.DrawRectangle(x1 + self._shadowOffsetX, y1 + self._shadowOffsetY, self._width, self._height)
405
406 # For debug purposes /pi
407 #dc.DrawRectangle(x1, y1, self._width, self._height)
408
409 def OnDrawContents(self, dc):
410 for object in self._children:
411 object.Draw(dc)
412 object.DrawLinks(dc)
413
414 Shape.OnDrawContents(self, dc)
415
416 def OnMovePre(self, dc, x, y, old_x, old_y, display = True):
417 diffX = x - old_x
418 diffY = y - old_y
419
420 for object in self._children:
421 object.Erase(dc)
422 object.Move(dc, object.GetX() + diffX, object.GetY() + diffY, display)
423
424 return True
425
426 def OnErase(self, dc):
427 RectangleShape.OnErase(self, dc)
428 for object in self._children:
429 object.Erase(dc)
430
431 def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
432 xx, yy = self._canvas.Snap(x, y)
433 offsetX = xx - _objectStartX
434 offsetY = yy - _objectStartY
435
436 dc = wx.ClientDC(self.GetCanvas())
437 self.GetCanvas().PrepareDC(dc)
438
439 dc.SetLogicalFunction(OGLRBLF)
440 dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.DOT)
441 dc.SetPen(dottedPen)
442 dc.SetBrush(wx.TRANSPARENT_BRUSH)
443
444 self.GetEventHandler().OnDrawOutline(dc, self.GetX() + offsetX, self.GetY() + offsetY, self.GetWidth(), self.GetHeight())
445
446 def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
447 global _objectStartX, _objectStartY
448
449 _objectStartX = x
450 _objectStartY = y
451
452 dc = wx.ClientDC(self.GetCanvas())
453 self.GetCanvas().PrepareDC(dc)
454
455 #self.Erase(dc)
456
457 dc.SetLogicalFunction(OGLRBLF)
458 dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.DOT)
459 dc.SetPen(dottedPen)
460 dc.SetBrush(wx.TRANSPARENT_BRUSH)
461 self._canvas.CaptureMouse()
462
463 xx, yy = self._canvas.Snap(x, y)
464 offsetX = xx - _objectStartX
465 offsetY = yy - _objectStartY
466
467 self.GetEventHandler().OnDrawOutline(dc, self.GetX() + offsetX, self.GetY() + offsetY, self.GetWidth(), self.GetHeight())
468
469 def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
470 dc = wx.ClientDC(self.GetCanvas())
471 self.GetCanvas().PrepareDC(dc)
472
473 if self._canvas.HasCapture():
474 self._canvas.ReleaseMouse()
475
476 if not self._draggable:
477 if self._parent:
478 self._parent.GetEventHandler().OnEndDragLeft(x, y, keys, 0)
479 return
480
481 self.Erase(dc)
482
483 dc.SetLogicalFunction(wx.COPY)
484
485 xx, yy = self._canvas.Snap(x, y)
486 offsetX = xx - _objectStartX
487 offsetY = yy - _objectStartY
488
489 self.Move(dc, self.GetX() + offsetX, self.GetY() + offsetY)
490
491 if self._canvas and not self._canvas.GetQuickEditMode():
492 self._canvas.Redraw(dc)
493
494 def OnRightClick(self, x, y, keys = 0, attachment = 0):
495 # If we get a ctrl-right click, this means send the message to
496 # the division, so we can invoke a user interface for dealing
497 # with regions.
498 if keys & KEY_CTRL:
499 for division in self._divisions:
500 hit = division.HitTest(x, y)
501 if hit:
502 division.GetEventHandler().OnRightClick(x, y, keys, hit[0])
503 break
504
505 def SetSize(self, w, h, recursive = True):
506 self.SetAttachmentSize(w, h)
507
508 xScale = float(w) / max(1, self.GetWidth())
509 yScale = float(h) / max(1, self.GetHeight())
510
511 self._width = w
512 self._height = h
513
514 if not recursive:
515 return
516
517 dc = wx.ClientDC(self.GetCanvas())
518 self.GetCanvas().PrepareDC(dc)
519
520 for object in self._children:
521 # Scale the position first
522 newX = (object.GetX() - self.GetX()) * xScale + self.GetX()
523 newY = (object.GetY() - self.GetY()) * yScale + self.GetY()
524 object.Show(False)
525 object.Move(dc, newX, newY)
526 object.Show(True)
527
528 # Now set the scaled size
529 xbound, ybound = object.GetBoundingBoxMax()
530 if not object.GetFixedWidth():
531 xbound *= xScale
532 if not object.GetFixedHeight():
533 ybound *= yScale
534 object.SetSize(xbound, ybound)
535
536 self.SetDefaultRegionSize()
537
538 def AddChild(self, child, addAfter = None):
539 """Adds a child shape to the composite.
540
541 If addAfter is not None, the shape will be added after this shape.
542 """
543 self._children.append(child)
544 child.SetParent(self)
545 if self._canvas:
546 # Ensure we add at the right position
547 if addAfter:
548 child.RemoveFromCanvas(self._canvas)
549 child.AddToCanvas(self._canvas, addAfter)
550
551 def RemoveChild(self, child):
552 """Removes the child from the composite and any constraint
553 relationships, but does not delete the child.
554 """
555 if child in self._children:
556 self._children.remove(child)
557 if child in self._divisions:
558 self._divisions.remove(child)
559 self.RemoveChildFromConstraints(child)
560 child.SetParent(None)
561
562 def DeleteConstraintsInvolvingChild(self, child):
563 """This function deletes constraints which mention the given child.
564
565 Used when deleting a child from the composite.
566 """
567 for constraint in self._constraints:
568 if constraint._constrainingObject == child or child in constraint._constrainedObjects:
569 self._constraints.remove(constraint)
570
571 def RemoveChildFromConstraints(self, child):
572 for constraint in self._constraints:
573 if child in constraint._constrainedObjects:
574 constraint._constrainedObjects.remove(child)
575 if constraint._constrainingObject == child:
576 constraint._constrainingObject = None
577
578 # Delete the constraint if no participants left
579 if not constraint._constrainingObject:
580 self._constraints.remove(constraint)
581
582 def AddConstraint(self, constraint):
583 """Adds a constraint to the composite."""
584 self._constraints.append(constraint)
585 if constraint._constraintId == 0:
586 constraint._constraintId = wx.NewId()
587 return constraint
588
589 def AddSimpleConstraint(self, type, constraining, constrained):
590 """Add a constraint of the given type to the composite.
591
592 constraining is the shape doing the constraining
593 constrained is a list of shapes being constrained
594 """
595 constraint = Constraint(type, constraining, constrained)
596 if constraint._constraintId == 0:
597 constraint._constraintId = wx.NewId()
598 self._constraints.append(constraint)
599 return constraint
600
601 def FindConstraint(self, cId):
602 """Finds the constraint with the given id.
603
604 Returns a tuple of the constraint and the actual composite the
605 constraint was in, in case that composite was a descendant of
606 this composit.
607
608 Returns None if not found.
609 """
610 for constraint in self._constraints:
611 if constraint._constraintId == cId:
612 return constraint, self
613
614 # If not found, try children
615 for child in self._children:
616 if isinstance(child, CompositeShape):
617 constraint = child.FindConstraint(cId)
618 if constraint:
619 return constraint[0], child
620
621 return None
622
623 def DeleteConstraint(self, constraint):
624 """Deletes constraint from composite."""
625 self._constraints.remove(constraint)
626
627 def CalculateSize(self):
628 """Calculates the size and position of the composite based on
629 child sizes and positions.
630 """
631 maxX = -999999.9
632 maxY = -999999.9
633 minX = 999999.9
634 minY = 999999.9
635
636 for child in self._children:
637 # Recalculate size of composite objects because may not conform
638 # to size it was set to - depends on the children.
639 if isinstance(child, CompositeShape):
640 child.CalculateSize()
641
642 w, h = child.GetBoundingBoxMax()
643 if child.GetX() + w / 2.0 > maxX:
644 maxX = child.GetX() + w / 2.0
645 if child.GetX() - w / 2.0 < minX:
646 minX = child.GetX() - w / 2.0
647 if child.GetY() + h / 2.0 > maxY:
648 maxY = child.GetY() + h / 2.0
649 if child.GetY() - h / 2.0 < minY:
650 minY = child.GetY() - h / 2.0
651
652 self._width = maxX - minX
653 self._height = maxY - minY
654 self._xpos = self._width / 2.0 + minX
655 self._ypos = self._height / 2.0 + minY
656
657 def Recompute(self):
658 """Recomputes any constraints associated with the object. If FALSE is
659 returned, the constraints could not be satisfied (there was an
660 inconsistency).
661 """
662 noIterations = 0
663 changed = True
664 while changed and noIterations < 500:
665 changed = self.Constrain()
666 noIterations += 1
667
668 return not changed
669
670 def Constrain(self):
671 self.CalculateSize()
672
673 changed = False
674 for child in self._children:
675 if isinstance(child, CompositeShape) and child.Constrain():
676 changed = True
677
678 for constraint in self._constraints:
679 if constraint.Evaluate():
680 changed = True
681
682 return changed
683
684 def MakeContainer(self):
685 """Makes this composite into a container by creating one child
686 DivisionShape.
687 """
688 division = self.OnCreateDivision()
689 self._divisions.append(division)
690 self.AddChild(division)
691
692 division.SetSize(self._width, self._height)
693
694 dc = wx.ClientDC(self.GetCanvas())
695 self.GetCanvas().PrepareDC(dc)
696
697 division.Move(dc, self.GetX(), self.GetY())
698 self.Recompute()
699 division.Show(True)
700
701 def OnCreateDivision(self):
702 return DivisionShape()
703
704 def FindContainerImage(self):
705 """Finds the image used to visualize a container. This is any child of
706 the composite that is not in the divisions list.
707 """
708 for child in self._children:
709 if child in self._divisions:
710 return child
711
712 return None
713
714 def ContainsDivision(self, division):
715 """Returns TRUE if division is a descendant of this container."""
716 if division in self._divisions:
717 return True
718
719 for child in self._children:
720 if isinstance(child, CompositeShape):
721 return child.ContainsDivision(division)
722
723 return False
724
725 def GetDivisions(self):
726 """Return the list of divisions."""
727 return self._divisions
728
729 def GetConstraints(self):
730 """Return the list of constraints."""
731 return self._constraints
732
733
734 # A division object is a composite with special properties,
735 # to be used for containment. It's a subdivision of a container.
736 # A containing node image consists of a composite with a main child shape
737 # such as rounded rectangle, plus a list of division objects.
738 # It needs to be a composite because a division contains pieces
739 # of diagram.
740 # NOTE a container has at least one wxDivisionShape for consistency.
741 # This can be subdivided, so it turns into two objects, then each of
742 # these can be subdivided, etc.
743
744 DIVISION_SIDE_NONE =0
745 DIVISION_SIDE_LEFT =1
746 DIVISION_SIDE_TOP =2
747 DIVISION_SIDE_RIGHT =3
748 DIVISION_SIDE_BOTTOM =4
749
750 originalX = 0.0
751 originalY = 0.0
752 originalW = 0.0
753 originalH = 0.0
754
755
756
757 class DivisionControlPoint(ControlPoint):
758 def __init__(self, the_canvas, object, size, the_xoffset, the_yoffset, the_type):
759 ControlPoint.__init__(self, the_canvas, object, size, the_xoffset, the_yoffset, the_type)
760 self.SetEraseObject(False)
761
762 # Implement resizing of canvas object
763 def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
764 ControlPoint.OnDragLeft(self, draw, x, y, keys, attachment)
765
766 def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
767 global originalX, originalY, originalW, originalH
768
769 originalX = self._shape.GetX()
770 originalY = self._shape.GetY()
771 originalW = self._shape.GetWidth()
772 originalH = self._shape.GetHeight()
773
774 ControlPoint.OnBeginDragLeft(self, x, y, keys, attachment)
775
776 def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
777 ControlPoint.OnEndDragLeft(self, x, y, keys, attachment)
778
779 dc = wx.ClientDC(self.GetCanvas())
780 self.GetCanvas().PrepareDC(dc)
781
782 division = self._shape
783 divisionParent = division.GetParent()
784
785 # Need to check it's within the bounds of the parent composite
786 x1 = divisionParent.GetX() - divisionParent.GetWidth() / 2.0
787 y1 = divisionParent.GetY() - divisionParent.GetHeight() / 2.0
788 x2 = divisionParent.GetX() + divisionParent.GetWidth() / 2.0
789 y2 = divisionParent.GetY() + divisionParent.GetHeight() / 2.0
790
791 # Need to check it has not made the division zero or negative
792 # width / height
793 dx1 = division.GetX() - division.GetWidth() / 2.0
794 dy1 = division.GetY() - division.GetHeight() / 2.0
795 dx2 = division.GetX() + division.GetWidth() / 2.0
796 dy2 = division.GetY() + division.GetHeight() / 2.0
797
798 success = True
799 if division.GetHandleSide() == DIVISION_SIDE_LEFT:
800 if x <= x1 or x >= x2 or x >= dx2:
801 success = False
802 # Try it out first...
803 elif not division.ResizeAdjoining(DIVISION_SIDE_LEFT, x, True):
804 success = False
805 else:
806 division.ResizeAdjoining(DIVISION_SIDE_LEFT, x, False)
807 elif division.GetHandleSide() == DIVISION_SIDE_TOP:
808 if y <= y1 or y >= y2 or y >= dy2:
809 success = False
810 elif not division.ResizeAdjoining(DIVISION_SIDE_TOP, y, True):
811 success = False
812 else:
813 division.ResizingAdjoining(DIVISION_SIDE_TOP, y, False)
814 elif division.GetHandleSide() == DIVISION_SIDE_RIGHT:
815 if x <= x1 or x >= x2 or x <= dx1:
816 success = False
817 elif not division.ResizeAdjoining(DIVISION_SIDE_RIGHT, x, True):
818 success = False
819 else:
820 division.ResizeAdjoining(DIVISION_SIDE_RIGHT, x, False)
821 elif division.GetHandleSide() == DIVISION_SIDE_BOTTOM:
822 if y <= y1 or y >= y2 or y <= dy1:
823 success = False
824 elif not division.ResizeAdjoining(DIVISION_SIDE_BOTTOM, y, True):
825 success = False
826 else:
827 division.ResizeAdjoining(DIVISION_SIDE_BOTTOM, y, False)
828
829 if not success:
830 division.SetSize(originalW, originalH)
831 division.Move(dc, originalX, originalY)
832
833 divisionParent.Draw(dc)
834 division.GetEventHandler().OnDrawControlPoints(dc)
835
836
837
838 DIVISION_MENU_SPLIT_HORIZONTALLY =1
839 DIVISION_MENU_SPLIT_VERTICALLY =2
840 DIVISION_MENU_EDIT_LEFT_EDGE =3
841 DIVISION_MENU_EDIT_TOP_EDGE =4
842 DIVISION_MENU_EDIT_RIGHT_EDGE =5
843 DIVISION_MENU_EDIT_BOTTOM_EDGE =6
844 DIVISION_MENU_DELETE_ALL =7
845
846
847
848 class PopupDivisionMenu(wx.Menu):
849 def __init__(self):
850 wx.Menu.__init__(self)
851 self.Append(DIVISION_MENU_SPLIT_HORIZONTALLY,"Split horizontally")
852 self.Append(DIVISION_MENU_SPLIT_VERTICALLY,"Split vertically")
853 self.AppendSeparator()
854 self.Append(DIVISION_MENU_EDIT_LEFT_EDGE,"Edit left edge")
855 self.Append(DIVISION_MENU_EDIT_TOP_EDGE,"Edit top edge")
856
857 wx.EVT_MENU_RANGE(self, DIVISION_MENU_SPLIT_HORIZONTALLY, DIVISION_MENU_EDIT_BOTTOM_EDGE, self.OnMenu)
858
859 def SetClientData(self, data):
860 self._clientData = data
861
862 def GetClientData(self):
863 return self._clientData
864
865 def OnMenu(self, event):
866 division = self.GetClientData()
867 if event.GetId() == DIVISION_MENU_SPLIT_HORIZONTALLY:
868 division.Divide(wx.HORIZONTAL)
869 elif event.GetId() == DIVISION_MENU_SPLIT_VERTICALLY:
870 division.Divide(wx.VERTICAL)
871 elif event.GetId() == DIVISION_MENU_EDIT_LEFT_EDGE:
872 division.EditEdge(DIVISION_SIDE_LEFT)
873 elif event.GetId() == DIVISION_MENU_EDIT_TOP_EDGE:
874 division.EditEdge(DIVISION_SIDE_TOP)
875
876
877
878 class DivisionShape(CompositeShape):
879 """A division shape is like a composite in that it can contain further
880 objects, but is used exclusively to divide another shape into regions,
881 or divisions. A wxDivisionShape is never free-standing.
882
883 Derived from:
884 wxCompositeShape
885 """
886 def __init__(self):
887 CompositeShape.__init__(self)
888 self.SetSensitivityFilter(OP_CLICK_LEFT | OP_CLICK_RIGHT | OP_DRAG_RIGHT)
889 self.SetCentreResize(False)
890 self.SetAttachmentMode(True)
891 self._leftSide = None
892 self._rightSide = None
893 self._topSide = None
894 self._bottomSide = None
895 self._handleSide = DIVISION_SIDE_NONE
896 self._leftSidePen = wx.BLACK_PEN
897 self._topSidePen = wx.BLACK_PEN
898 self._leftSideColour = "BLACK"
899 self._topSideColour = "BLACK"
900 self._leftSideStyle = "Solid"
901 self._topSideStyle = "Solid"
902 self.ClearRegions()
903
904 def SetLeftSide(self, shape):
905 """Set the the division on the left side of this division."""
906 self._leftSide = shape
907
908 def SetTopSide(self, shape):
909 """Set the the division on the top side of this division."""
910 self._topSide = shape
911
912 def SetRightSide(self, shape):
913 """Set the the division on the right side of this division."""
914 self._rightSide = shape
915
916 def SetBottomSide(self, shape):
917 """Set the the division on the bottom side of this division."""
918 self._bottomSide = shape
919
920 def GetLeftSide(self):
921 """Return the division on the left side of this division."""
922 return self._leftSide
923
924 def GetTopSide(self):
925 """Return the division on the top side of this division."""
926 return self._topSide
927
928 def GetRightSide(self):
929 """Return the division on the right side of this division."""
930 return self._rightSide
931
932 def GetBottomSide(self):
933 """Return the division on the bottom side of this division."""
934 return self._bottomSide
935
936 def SetHandleSide(self, side):
937 """Sets the side which the handle appears on.
938
939 Either DIVISION_SIDE_LEFT or DIVISION_SIDE_TOP.
940 """
941 self._handleSide = side
942
943 def GetHandleSide(self):
944 """Return the side which the handle appears on."""
945 return self._handleSide
946
947 def SetLeftSidePen(self, pen):
948 """Set the colour for drawing the left side of the division."""
949 self._leftSidePen = pen
950
951 def SetTopSidePen(self, pen):
952 """Set the colour for drawing the top side of the division."""
953 self._topSidePen = pen
954
955 def GetLeftSidePen(self):
956 """Return the pen used for drawing the left side of the division."""
957 return self._leftSidePen
958
959 def GetTopSidePen(self):
960 """Return the pen used for drawing the top side of the division."""
961 return self._topSidePen
962
963 def GetLeftSideColour(self):
964 """Return the colour used for drawing the left side of the division."""
965 return self._leftSideColour
966
967 def GetTopSideColour(self):
968 """Return the colour used for drawing the top side of the division."""
969 return self._topSideColour
970
971 def SetLeftSideColour(self, colour):
972 """Set the colour for drawing the left side of the division."""
973 self._leftSideColour = colour
974
975 def SetTopSideColour(self, colour):
976 """Set the colour for drawing the top side of the division."""
977 self._topSideColour = colour
978
979 def GetLeftSideStyle(self):
980 """Return the style used for the left side of the division."""
981 return self._leftSideStyle
982
983 def GetTopSideStyle(self):
984 """Return the style used for the top side of the division."""
985 return self._topSideStyle
986
987 def SetLeftSideStyle(self, style):
988 self._leftSideStyle = style
989
990 def SetTopSideStyle(self, style):
991 self._lefttopStyle = style
992
993 def OnDraw(self, dc):
994 dc.SetBrush(wx.TRANSPARENT_BRUSH)
995 dc.SetBackgroundMode(wx.TRANSPARENT)
996
997 x1 = self.GetX() - self.GetWidth() / 2.0
998 y1 = self.GetY() - self.GetHeight() / 2.0
999 x2 = self.GetX() + self.GetWidth() / 2.0
1000 y2 = self.GetY() + self.GetHeight() / 2.0
1001
1002 # Should subtract 1 pixel if drawing under Windows
1003 if sys.platform[:3] == "win":
1004 y2 -= 1
1005
1006 if self._leftSide:
1007 dc.SetPen(self._leftSidePen)
1008 dc.DrawLine(x1, y2, x1, y1)
1009
1010 if self._topSide:
1011 dc.SetPen(self._topSidePen)
1012 dc.DrawLine(x1, y1, x2, y1)
1013
1014 # For testing purposes, draw a rectangle so we know
1015 # how big the division is.
1016 #dc.SetBrush(wx.RED_BRUSH)
1017 #dc.DrawRectangle(x1, y1, self.GetWidth(), self.GetHeight())
1018
1019 def OnDrawContents(self, dc):
1020 CompositeShape.OnDrawContents(self, dc)
1021
1022 def OnMovePre(self, dc, x, y, oldx, oldy, display = True):
1023 diffX = x - oldx
1024 diffY = y - oldy
1025 for object in self._children:
1026 object.Erase(dc)
1027 object.Move(dc, object.GetX() + diffX, object.GetY() + diffY, display)
1028 return True
1029
1030 def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
1031 if self._sensitivity & OP_DRAG_LEFT != OP_DRAG_LEFT:
1032 if self._parent:
1033 hit = self._parent.HitTest(x, y)
1034 if hit:
1035 attachment, dist = hit
1036 self._parent.GetEventHandler().OnDragLeft(draw, x, y, keys, attachment)
1037 return
1038 Shape.OnDragLeft(self, draw, x, y, keys, attachment)
1039
1040 def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
1041 if self._sensitivity & OP_DRAG_LEFT != OP_DRAG_LEFT:
1042 if self._parent:
1043 hit = self._parent.HitTest(x, y)
1044 if hit:
1045 attachment, dist = hit
1046 self._parent.GetEventHandler().OnBeginDragLeft(x, y, keys, attachment)
1047 return
1048 Shape.OnBeginDragLeft(x, y, keys, attachment)
1049
1050 def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
1051 if self._canvas.HasCapture():
1052 self._canvas.ReleaseMouse()
1053 if self._sensitivity & OP_DRAG_LEFT != OP_DRAG_LEFT:
1054 if self._parent:
1055 hit = self._parent.HitTest(x, y)
1056 if hit:
1057 attachment, dist = hit
1058 self._parent.GetEventHandler().OnEndDragLeft(x, y, keys, attachment)
1059 return
1060
1061 dc = wx.ClientDC(self.GetCanvas())
1062 self.GetCanvas().PrepareDC(dc)
1063
1064 dc.SetLogicalFunction(wx.COPY)
1065
1066 self._xpos, self._ypos = self._canvas.Snap(self._xpos, self._ypos)
1067 self.GetEventHandler().OnMovePre(dc, x, y, self._oldX, self._oldY)
1068
1069 self.ResetControlPoints()
1070 self.Draw(dc)
1071 self.MoveLinks(dc)
1072 self.GetEventHandler().OnDrawControlPoints(dc)
1073
1074 if self._canvas and not self._canvas.GetQuickEditMode():
1075 self._canvas.Redraw(dc)
1076
1077 def SetSize(self, w, h, recursive = True):
1078 self._width = w
1079 self._height = h
1080 RectangleShape.SetSize(self, w, h, recursive)
1081
1082 def CalculateSize(self):
1083 pass
1084
1085 # Experimental
1086 def OnRightClick(self, x, y, keys = 0, attachment = 0):
1087 if keys & KEY_CTRL:
1088 self.PopupMenu(x, y)
1089 else:
1090 if self._parent:
1091 hit = self._parent.HitTest(x, y)
1092 if hit:
1093 attachment, dist = hit
1094 self._parent.GetEventHandler().OnRightClick(x, y, keys, attachment)
1095
1096 # Divide wx.HORIZONTALly or wx.VERTICALly
1097 def Divide(self, direction):
1098 """Divide this division into two further divisions,
1099 horizontally (direction is wxHORIZONTAL) or
1100 vertically (direction is wxVERTICAL).
1101 """
1102 # Calculate existing top-left, bottom-right
1103 x1 = self.GetX() - self.GetWidth() / 2.0
1104 y1 = self.GetY() - self.GetHeight() / 2.0
1105
1106 compositeParent = self.GetParent()
1107 oldWidth = self.GetWidth()
1108 oldHeight = self.GetHeight()
1109 if self.Selected():
1110 self.Select(False)
1111
1112 dc = wx.ClientDC(self.GetCanvas())
1113 self.GetCanvas().PrepareDC(dc)
1114
1115 if direction == wx.VERTICAL:
1116 # Dividing vertically means notionally putting a horizontal
1117 # line through it.
1118 # Break existing piece into two.
1119 newXPos1 = self.GetX()
1120 newYPos1 = y1 + self.GetHeight() / 4.0
1121 newXPos2 = self.GetX()
1122 newYPos2 = y1 + 3 * self.GetHeight() / 4.0
1123 newDivision = compositeParent.OnCreateDivision()
1124 newDivision.Show(True)
1125
1126 self.Erase(dc)
1127
1128 # Anything adjoining the bottom of this division now adjoins the
1129 # bottom of the new division.
1130 for obj in compositeParent.GetDivisions():
1131 if obj.GetTopSide() == self:
1132 obj.SetTopSide(newDivision)
1133
1134 newDivision.SetTopSide(self)
1135 newDivision.SetBottomSide(self._bottomSide)
1136 newDivision.SetLeftSide(self._leftSide)
1137 newDivision.SetRightSide(self._rightSide)
1138 self._bottomSide = newDivision
1139
1140 compositeParent.GetDivisions().append(newDivision)
1141
1142 # CHANGE: Need to insert this division at start of divisions in the
1143 # object list, because e.g.:
1144 # 1) Add division
1145 # 2) Add contained object
1146 # 3) Add division
1147 # Division is now receiving mouse events _before_ the contained
1148 # object, because it was added last (on top of all others)
1149
1150 # Add after the image that visualizes the container
1151 compositeParent.AddChild(newDivision, compositeParent.FindContainerImage())
1152
1153 self._handleSide = DIVISION_SIDE_BOTTOM
1154 newDivision.SetHandleSide(DIVISION_SIDE_TOP)
1155
1156 self.SetSize(oldWidth, oldHeight / 2.0)
1157 self.Move(dc, newXPos1, newYPos1)
1158
1159 newDivision.SetSize(oldWidth, oldHeight / 2.0)
1160 newDivision.Move(dc, newXPos2, newYPos2)
1161 else:
1162 # Dividing horizontally means notionally putting a vertical line
1163 # through it.
1164 # Break existing piece into two.
1165 newXPos1 = x1 + self.GetWidth() / 4.0
1166 newYPos1 = self.GetY()
1167 newXPos2 = x1 + 3 * self.GetWidth() / 4.0
1168 newYPos2 = self.GetY()
1169 newDivision = compositeParent.OnCreateDivision()
1170 newDivision.Show(True)
1171
1172 self.Erase(dc)
1173
1174 # Anything adjoining the left of this division now adjoins the
1175 # left of the new division.
1176 for obj in compositeParent.GetDivisions():
1177 if obj.GetLeftSide() == self:
1178 obj.SetLeftSide(newDivision)
1179
1180 newDivision.SetTopSide(self._topSide)
1181 newDivision.SetBottomSide(self._bottomSide)
1182 newDivision.SetLeftSide(self)
1183 newDivision.SetRightSide(self._rightSide)
1184 self._rightSide = newDivision
1185
1186 compositeParent.GetDivisions().append(newDivision)
1187 compositeParent.AddChild(newDivision, compositeParent.FindContainerImage())
1188
1189 self._handleSide = DIVISION_SIDE_RIGHT
1190 newDivision.SetHandleSide(DIVISION_SIDE_LEFT)
1191
1192 self.SetSize(oldWidth / 2.0, oldHeight)
1193 self.Move(dc, newXPos1, newYPos1)
1194
1195 newDivision.SetSize(oldWidth / 2.0, oldHeight)
1196 newDivision.Move(dc, newXPos2, newYPos2)
1197
1198 if compositeParent.Selected():
1199 compositeParent.DeleteControlPoints(dc)
1200 compositeParent.MakeControlPoints()
1201 compositeParent.MakeMandatoryControlPoints()
1202
1203 compositeParent.Draw(dc)
1204 return True
1205
1206 def MakeControlPoints(self):
1207 self.MakeMandatoryControlPoints()
1208
1209 def MakeMandatoryControlPoints(self):
1210 maxX, maxY = self.GetBoundingBoxMax()
1211 x = y = 0.0
1212 direction = 0
1213
1214 if self._handleSide == DIVISION_SIDE_LEFT:
1215 x = -maxX / 2.0
1216 direction = CONTROL_POINT_HORIZONTAL
1217 elif self._handleSide == DIVISION_SIDE_TOP:
1218 y = -maxY / 2.0
1219 direction = CONTROL_POINT_VERTICAL
1220 elif self._handleSide == DIVISION_SIDE_RIGHT:
1221 x = maxX / 2.0
1222 direction = CONTROL_POINT_HORIZONTAL
1223 elif self._handleSide == DIVISION_SIDE_BOTTOM:
1224 y = maxY / 2.0
1225 direction = CONTROL_POINT_VERTICAL
1226
1227 if self._handleSide != DIVISION_SIDE_NONE:
1228 control = DivisionControlPoint(self._canvas, self, CONTROL_POINT_SIZE, x, y, direction)
1229 self._canvas.AddShape(control)
1230 self._controlPoints.append(control)
1231
1232 def ResetControlPoints(self):
1233 self.ResetMandatoryControlPoints()
1234
1235 def ResetMandatoryControlPoints(self):
1236 if not self._controlPoints:
1237 return
1238
1239 maxX, maxY = self.GetBoundingBoxMax()
1240
1241 node = self._controlPoints[0]
1242
1243 if self._handleSide == DIVISION_SIDE_LEFT and node:
1244 node._xoffset = -maxX / 2.0
1245 node._yoffset = 0.0
1246
1247 if self._handleSide == DIVISION_SIDE_TOP and node:
1248 node._xoffset = 0.0
1249 node._yoffset = -maxY / 2.0
1250
1251 if self._handleSide == DIVISION_SIDE_RIGHT and node:
1252 node._xoffset = maxX / 2.0
1253 node._yoffset = 0.0
1254
1255 if self._handleSide == DIVISION_SIDE_BOTTOM and node:
1256 node._xoffset = 0.0
1257 node._yoffset = maxY / 2.0
1258
1259 def AdjustLeft(self, left, test):
1260 """Adjust a side.
1261
1262 Returns FALSE if it's not physically possible to adjust it to
1263 this point.
1264 """
1265 x2 = self.GetX() + self.GetWidth() / 2.0
1266
1267 if left >= x2:
1268 return False
1269
1270 if test:
1271 return True
1272
1273 newW = x2 - left
1274 newX = left + newW / 2.0
1275 self.SetSize(newW, self.GetHeight())
1276
1277 dc = wx.ClientDC(self.GetCanvas())
1278 self.GetCanvas().PrepareDC(dc)
1279
1280 self.Move(dc, newX, self.GetY())
1281 return True
1282
1283 def AdjustTop(self, top, test):
1284 """Adjust a side.
1285
1286 Returns FALSE if it's not physically possible to adjust it to
1287 this point.
1288 """
1289 y2 = self.GetY() + self.GetHeight() / 2.0
1290
1291 if top >= y2:
1292 return False
1293
1294 if test:
1295 return True
1296
1297 newH = y2 - top
1298 newY = top + newH / 2.0
1299 self.SetSize(self.GetWidth(), newH)
1300
1301 dc = wx.ClientDC(self.GetCanvas())
1302 self.GetCanvas().PrepareDC(dc)
1303
1304 self.Move(dc, self.GetX(), newY)
1305 return True
1306
1307 def AdjustRight(self, right, test):
1308 """Adjust a side.
1309
1310 Returns FALSE if it's not physically possible to adjust it to
1311 this point.
1312 """
1313 x1 = self.GetX() - self.GetWidth() / 2.0
1314
1315 if right <= x1:
1316 return False
1317
1318 if test:
1319 return True
1320
1321 newW = right - x1
1322 newX = x1 + newW / 2.0
1323 self.SetSize(newW, self.GetHeight())
1324
1325 dc = wx.ClientDC(self.GetCanvas())
1326 self.GetCanvas().PrepareDC(dc)
1327
1328 self.Move(dc, newX, self.GetY())
1329 return True
1330
1331 def AdjustTop(self, top, test):
1332 """Adjust a side.
1333
1334 Returns FALSE if it's not physically possible to adjust it to
1335 this point.
1336 """
1337 y1 = self.GetY() - self.GetHeight() / 2.0
1338
1339 if bottom <= y1:
1340 return False
1341
1342 if test:
1343 return True
1344
1345 newH = bottom - y1
1346 newY = y1 + newH / 2.0
1347 self.SetSize(self.GetWidth(), newH)
1348
1349 dc = wx.ClientDC(self.GetCanvas())
1350 self.GetCanvas().PrepareDC(dc)
1351
1352 self.Move(dc, self.GetX(), newY)
1353 return True
1354
1355 # Resize adjoining divisions.
1356
1357 # Behaviour should be as follows:
1358 # If right edge moves, find all objects whose left edge
1359 # adjoins this object, and move left edge accordingly.
1360 # If left..., move ... right.
1361 # If top..., move ... bottom.
1362 # If bottom..., move top.
1363 # If size goes to zero or end position is other side of start position,
1364 # resize to original size and return.
1365 #
1366 def ResizeAdjoining(self, side, newPos, test):
1367 """Resize adjoining divisions at the given side.
1368
1369 If test is TRUE, just see whether it's possible for each adjoining
1370 region, returning FALSE if it's not.
1371
1372 side can be one of:
1373
1374 * DIVISION_SIDE_NONE
1375 * DIVISION_SIDE_LEFT
1376 * DIVISION_SIDE_TOP
1377 * DIVISION_SIDE_RIGHT
1378 * DIVISION_SIDE_BOTTOM
1379 """
1380 divisionParent = self.GetParent()
1381 for division in divisionParent.GetDivisions():
1382 if side == DIVISION_SIDE_LEFT:
1383 if division._rightSide == self:
1384 success = division.AdjustRight(newPos, test)
1385 if not success and test:
1386 return false
1387 elif side == DIVISION_SIDE_TOP:
1388 if division._bottomSide == self:
1389 success = division.AdjustBottom(newPos, test)
1390 if not success and test:
1391 return False
1392 elif side == DIVISION_SIDE_RIGHT:
1393 if division._leftSide == self:
1394 success = division.AdjustLeft(newPos, test)
1395 if not success and test:
1396 return False
1397 elif side == DIVISION_SIDE_BOTTOM:
1398 if division._topSide == self:
1399 success = division.AdjustTop(newPos, test)
1400 if not success and test:
1401 return False
1402 return True
1403
1404 def EditEdge(self, side):
1405 print "EditEdge() not implemented."
1406
1407 def PopupMenu(self, x, y):
1408 menu = PopupDivisionMenu()
1409 menu.SetClientData(self)
1410 if self._leftSide:
1411 menu.Enable(DIVISION_MENU_EDIT_LEFT_EDGE, True)
1412 else:
1413 menu.Enable(DIVISION_MENU_EDIT_LEFT_EDGE, False)
1414 if self._topSide:
1415 menu.Enable(DIVISION_MENU_EDIT_TOP_EDGE, True)
1416 else:
1417 menu.Enable(DIVISION_MENU_EDIT_TOP_EDGE, False)
1418
1419 x1, y1 = self._canvas.GetViewStart()
1420 unit_x, unit_y = self._canvas.GetScrollPixelsPerUnit()
1421
1422 dc = wx.ClientDC(self.GetCanvas())
1423 self.GetCanvas().PrepareDC(dc)
1424
1425 mouse_x = dc.LogicalToDeviceX(x - x1 * unit_x)
1426 mouse_y = dc.LogicalToDeviceY(y - y1 * unit_y)
1427
1428 self._canvas.PopupMenu(menu, (mouse_x, mouse_y))
1429
1430