]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/ogl/_composit.py
Use top-level parent for the dialog parent
[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 self._children.remove(child)
556 self._divisions.remove(child)
557 self.RemoveChildFromConstraints(child)
558 child.SetParent(None)
559
560 def DeleteConstraintsInvolvingChild(self, child):
561 """This function deletes constraints which mention the given child.
562
563 Used when deleting a child from the composite.
564 """
565 for constraint in self._constraints:
566 if constraint._constrainingObject == child or child in constraint._constrainedObjects:
567 self._constraints.remove(constraint)
568
569 def RemoveChildFromConstraints(self, child):
570 for constraint in self._constraints:
571 if child in constraint._constrainedObjects:
572 constraint._constrainedObjects.remove(child)
573 if constraint._constrainingObject == child:
574 constraint._constrainingObject = None
575
576 # Delete the constraint if no participants left
577 if not constraint._constrainingObject:
578 self._constraints.remove(constraint)
579
580 def AddConstraint(self, constraint):
581 """Adds a constraint to the composite."""
582 self._constraints.append(constraint)
583 if constraint._constraintId == 0:
584 constraint._constraintId = wx.NewId()
585 return constraint
586
587 def AddSimpleConstraint(self, type, constraining, constrained):
588 """Add a constraint of the given type to the composite.
589
590 constraining is the shape doing the constraining
591 constrained is a list of shapes being constrained
592 """
593 constraint = Constraint(type, constraining, constrained)
594 if constraint._constraintId == 0:
595 constraint._constraintId = wx.NewId()
596 self._constraints.append(constraint)
597 return constraint
598
599 def FindConstraint(self, cId):
600 """Finds the constraint with the given id.
601
602 Returns a tuple of the constraint and the actual composite the
603 constraint was in, in case that composite was a descendant of
604 this composit.
605
606 Returns None if not found.
607 """
608 for constraint in self._constraints:
609 if constraint._constraintId == cId:
610 return constraint, self
611
612 # If not found, try children
613 for child in self._children:
614 if isinstance(child, CompositeShape):
615 constraint = child.FindConstraint(cId)
616 if constraint:
617 return constraint[0], child
618
619 return None
620
621 def DeleteConstraint(self, constraint):
622 """Deletes constraint from composite."""
623 self._constraints.remove(constraint)
624
625 def CalculateSize(self):
626 """Calculates the size and position of the composite based on
627 child sizes and positions.
628 """
629 maxX = -999999.9
630 maxY = -999999.9
631 minX = 999999.9
632 minY = 999999.9
633
634 for child in self._children:
635 # Recalculate size of composite objects because may not conform
636 # to size it was set to - depends on the children.
637 if isinstance(child, CompositeShape):
638 child.CalculateSize()
639
640 w, h = child.GetBoundingBoxMax()
641 if child.GetX() + w / 2.0 > maxX:
642 maxX = child.GetX() + w / 2.0
643 if child.GetX() - w / 2.0 < minX:
644 minX = child.GetX() - w / 2.0
645 if child.GetY() + h / 2.0 > maxY:
646 maxY = child.GetY() + h / 2.0
647 if child.GetY() - h / 2.0 < minY:
648 minY = child.GetY() - h / 2.0
649
650 self._width = maxX - minX
651 self._height = maxY - minY
652 self._xpos = self._width / 2.0 + minX
653 self._ypos = self._height / 2.0 + minY
654
655 def Recompute(self):
656 """Recomputes any constraints associated with the object. If FALSE is
657 returned, the constraints could not be satisfied (there was an
658 inconsistency).
659 """
660 noIterations = 0
661 changed = True
662 while changed and noIterations < 500:
663 changed = self.Constrain()
664 noIterations += 1
665
666 return not changed
667
668 def Constrain(self):
669 self.CalculateSize()
670
671 changed = False
672 for child in self._children:
673 if isinstance(child, CompositeShape) and child.Constrain():
674 changed = True
675
676 for constraint in self._constraints:
677 if constraint.Evaluate():
678 changed = True
679
680 return changed
681
682 def MakeContainer(self):
683 """Makes this composite into a container by creating one child
684 DivisionShape.
685 """
686 division = self.OnCreateDivision()
687 self._divisions.append(division)
688 self.AddChild(division)
689
690 division.SetSize(self._width, self._height)
691
692 dc = wx.ClientDC(self.GetCanvas())
693 self.GetCanvas().PrepareDC(dc)
694
695 division.Move(dc, self.GetX(), self.GetY())
696 self.Recompute()
697 division.Show(True)
698
699 def OnCreateDivision(self):
700 return DivisionShape()
701
702 def FindContainerImage(self):
703 """Finds the image used to visualize a container. This is any child of
704 the composite that is not in the divisions list.
705 """
706 for child in self._children:
707 if child in self._divisions:
708 return child
709
710 return None
711
712 def ContainsDivision(self, division):
713 """Returns TRUE if division is a descendant of this container."""
714 if division in self._divisions:
715 return True
716
717 for child in self._children:
718 if isinstance(child, CompositeShape):
719 return child.ContainsDivision(division)
720
721 return False
722
723 def GetDivisions(self):
724 """Return the list of divisions."""
725 return self._divisions
726
727 def GetConstraints(self):
728 """Return the list of constraints."""
729 return self._constraints
730
731
732 # A division object is a composite with special properties,
733 # to be used for containment. It's a subdivision of a container.
734 # A containing node image consists of a composite with a main child shape
735 # such as rounded rectangle, plus a list of division objects.
736 # It needs to be a composite because a division contains pieces
737 # of diagram.
738 # NOTE a container has at least one wxDivisionShape for consistency.
739 # This can be subdivided, so it turns into two objects, then each of
740 # these can be subdivided, etc.
741
742 DIVISION_SIDE_NONE =0
743 DIVISION_SIDE_LEFT =1
744 DIVISION_SIDE_TOP =2
745 DIVISION_SIDE_RIGHT =3
746 DIVISION_SIDE_BOTTOM =4
747
748 originalX = 0.0
749 originalY = 0.0
750 originalW = 0.0
751 originalH = 0.0
752
753
754
755 class DivisionControlPoint(ControlPoint):
756 def __init__(self, the_canvas, object, size, the_xoffset, the_yoffset, the_type):
757 ControlPoint.__init__(self, the_canvas, object, size, the_xoffset, the_yoffset, the_type)
758 self.SetEraseObject(False)
759
760 # Implement resizing of canvas object
761 def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
762 ControlPoint.OnDragLeft(self, draw, x, y, keys, attachment)
763
764 def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
765 global originalX, originalY, originalW, originalH
766
767 originalX = self._shape.GetX()
768 originalY = self._shape.GetY()
769 originalW = self._shape.GetWidth()
770 originalH = self._shape.GetHeight()
771
772 ControlPoint.OnBeginDragLeft(self, x, y, keys, attachment)
773
774 def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
775 ControlPoint.OnEndDragLeft(self, x, y, keys, attachment)
776
777 dc = wx.ClientDC(self.GetCanvas())
778 self.GetCanvas().PrepareDC(dc)
779
780 division = self._shape
781 divisionParent = division.GetParent()
782
783 # Need to check it's within the bounds of the parent composite
784 x1 = divisionParent.GetX() - divisionParent.GetWidth() / 2.0
785 y1 = divisionParent.GetY() - divisionParent.GetHeight() / 2.0
786 x2 = divisionParent.GetX() + divisionParent.GetWidth() / 2.0
787 y2 = divisionParent.GetY() + divisionParent.GetHeight() / 2.0
788
789 # Need to check it has not made the division zero or negative
790 # width / height
791 dx1 = division.GetX() - division.GetWidth() / 2.0
792 dy1 = division.GetY() - division.GetHeight() / 2.0
793 dx2 = division.GetX() + division.GetWidth() / 2.0
794 dy2 = division.GetY() + division.GetHeight() / 2.0
795
796 success = True
797 if division.GetHandleSide() == DIVISION_SIDE_LEFT:
798 if x <= x1 or x >= x2 or x >= dx2:
799 success = False
800 # Try it out first...
801 elif not division.ResizeAdjoining(DIVISION_SIDE_LEFT, x, True):
802 success = False
803 else:
804 division.ResizeAdjoining(DIVISION_SIDE_LEFT, x, False)
805 elif division.GetHandleSide() == DIVISION_SIDE_TOP:
806 if y <= y1 or y >= y2 or y >= dy2:
807 success = False
808 elif not division.ResizeAdjoining(DIVISION_SIDE_TOP, y, True):
809 success = False
810 else:
811 division.ResizingAdjoining(DIVISION_SIDE_TOP, y, False)
812 elif division.GetHandleSide() == DIVISION_SIDE_RIGHT:
813 if x <= x1 or x >= x2 or x <= dx1:
814 success = False
815 elif not division.ResizeAdjoining(DIVISION_SIDE_RIGHT, x, True):
816 success = False
817 else:
818 division.ResizeAdjoining(DIVISION_SIDE_RIGHT, x, False)
819 elif division.GetHandleSide() == DIVISION_SIDE_BOTTOM:
820 if y <= y1 or y >= y2 or y <= dy1:
821 success = False
822 elif not division.ResizeAdjoining(DIVISION_SIDE_BOTTOM, y, True):
823 success = False
824 else:
825 division.ResizeAdjoining(DIVISION_SIDE_BOTTOM, y, False)
826
827 if not success:
828 division.SetSize(originalW, originalH)
829 division.Move(dc, originalX, originalY)
830
831 divisionParent.Draw(dc)
832 division.GetEventHandler().OnDrawControlPoints(dc)
833
834
835
836 DIVISION_MENU_SPLIT_HORIZONTALLY =1
837 DIVISION_MENU_SPLIT_VERTICALLY =2
838 DIVISION_MENU_EDIT_LEFT_EDGE =3
839 DIVISION_MENU_EDIT_TOP_EDGE =4
840 DIVISION_MENU_EDIT_RIGHT_EDGE =5
841 DIVISION_MENU_EDIT_BOTTOM_EDGE =6
842 DIVISION_MENU_DELETE_ALL =7
843
844
845
846 class PopupDivisionMenu(wx.Menu):
847 def __init__(self):
848 wx.Menu.__init__(self)
849 self.Append(DIVISION_MENU_SPLIT_HORIZONTALLY,"Split horizontally")
850 self.Append(DIVISION_MENU_SPLIT_VERTICALLY,"Split vertically")
851 self.AppendSeparator()
852 self.Append(DIVISION_MENU_EDIT_LEFT_EDGE,"Edit left edge")
853 self.Append(DIVISION_MENU_EDIT_TOP_EDGE,"Edit top edge")
854
855 wx.EVT_MENU_RANGE(self, DIVISION_MENU_SPLIT_HORIZONTALLY, DIVISION_MENU_EDIT_BOTTOM_EDGE, self.OnMenu)
856
857 def SetClientData(self, data):
858 self._clientData = data
859
860 def GetClientData(self):
861 return self._clientData
862
863 def OnMenu(self, event):
864 division = self.GetClientData()
865 if event.GetId() == DIVISION_MENU_SPLIT_HORIZONTALLY:
866 division.Divide(wx.HORIZONTAL)
867 elif event.GetId() == DIVISION_MENU_SPLIT_VERTICALLY:
868 division.Divide(wx.VERTICAL)
869 elif event.GetId() == DIVISION_MENU_EDIT_LEFT_EDGE:
870 division.EditEdge(DIVISION_SIDE_LEFT)
871 elif event.GetId() == DIVISION_MENU_EDIT_TOP_EDGE:
872 division.EditEdge(DIVISION_SIDE_TOP)
873
874
875
876 class DivisionShape(CompositeShape):
877 """A division shape is like a composite in that it can contain further
878 objects, but is used exclusively to divide another shape into regions,
879 or divisions. A wxDivisionShape is never free-standing.
880
881 Derived from:
882 wxCompositeShape
883 """
884 def __init__(self):
885 CompositeShape.__init__(self)
886 self.SetSensitivityFilter(OP_CLICK_LEFT | OP_CLICK_RIGHT | OP_DRAG_RIGHT)
887 self.SetCentreResize(False)
888 self.SetAttachmentMode(True)
889 self._leftSide = None
890 self._rightSide = None
891 self._topSide = None
892 self._bottomSide = None
893 self._handleSide = DIVISION_SIDE_NONE
894 self._leftSidePen = wx.BLACK_PEN
895 self._topSidePen = wx.BLACK_PEN
896 self._leftSideColour = "BLACK"
897 self._topSideColour = "BLACK"
898 self._leftSideStyle = "Solid"
899 self._topSideStyle = "Solid"
900 self.ClearRegions()
901
902 def SetLeftSide(self, shape):
903 """Set the the division on the left side of this division."""
904 self._leftSide = shape
905
906 def SetTopSide(self, shape):
907 """Set the the division on the top side of this division."""
908 self._topSide = shape
909
910 def SetRightSide(self, shape):
911 """Set the the division on the right side of this division."""
912 self._rightSide = shape
913
914 def SetBottomSide(self, shape):
915 """Set the the division on the bottom side of this division."""
916 self._bottomSide = shape
917
918 def GetLeftSide(self):
919 """Return the division on the left side of this division."""
920 return self._leftSide
921
922 def GetTopSide(self):
923 """Return the division on the top side of this division."""
924 return self._topSide
925
926 def GetRightSide(self):
927 """Return the division on the right side of this division."""
928 return self._rightSide
929
930 def GetBottomSide(self):
931 """Return the division on the bottom side of this division."""
932 return self._bottomSide
933
934 def SetHandleSide(self, side):
935 """Sets the side which the handle appears on.
936
937 Either DIVISION_SIDE_LEFT or DIVISION_SIDE_TOP.
938 """
939 self._handleSide = side
940
941 def GetHandleSide(self):
942 """Return the side which the handle appears on."""
943 return self._handleSide
944
945 def SetLeftSidePen(self, pen):
946 """Set the colour for drawing the left side of the division."""
947 self._leftSidePen = pen
948
949 def SetTopSidePen(self, pen):
950 """Set the colour for drawing the top side of the division."""
951 self._topSidePen = pen
952
953 def GetLeftSidePen(self):
954 """Return the pen used for drawing the left side of the division."""
955 return self._leftSidePen
956
957 def GetTopSidePen(self):
958 """Return the pen used for drawing the top side of the division."""
959 return self._topSidePen
960
961 def GetLeftSideColour(self):
962 """Return the colour used for drawing the left side of the division."""
963 return self._leftSideColour
964
965 def GetTopSideColour(self):
966 """Return the colour used for drawing the top side of the division."""
967 return self._topSideColour
968
969 def SetLeftSideColour(self, colour):
970 """Set the colour for drawing the left side of the division."""
971 self._leftSideColour = colour
972
973 def SetTopSideColour(self, colour):
974 """Set the colour for drawing the top side of the division."""
975 self._topSideColour = colour
976
977 def GetLeftSideStyle(self):
978 """Return the style used for the left side of the division."""
979 return self._leftSideStyle
980
981 def GetTopSideStyle(self):
982 """Return the style used for the top side of the division."""
983 return self._topSideStyle
984
985 def SetLeftSideStyle(self, style):
986 self._leftSideStyle = style
987
988 def SetTopSideStyle(self, style):
989 self._lefttopStyle = style
990
991 def OnDraw(self, dc):
992 dc.SetBrush(wx.TRANSPARENT_BRUSH)
993 dc.SetBackgroundMode(wx.TRANSPARENT)
994
995 x1 = self.GetX() - self.GetWidth() / 2.0
996 y1 = self.GetY() - self.GetHeight() / 2.0
997 x2 = self.GetX() + self.GetWidth() / 2.0
998 y2 = self.GetY() + self.GetHeight() / 2.0
999
1000 # Should subtract 1 pixel if drawing under Windows
1001 if sys.platform[:3] == "win":
1002 y2 -= 1
1003
1004 if self._leftSide:
1005 dc.SetPen(self._leftSidePen)
1006 dc.DrawLine(x1, y2, x1, y1)
1007
1008 if self._topSide:
1009 dc.SetPen(self._topSidePen)
1010 dc.DrawLine(x1, y1, x2, y1)
1011
1012 # For testing purposes, draw a rectangle so we know
1013 # how big the division is.
1014 #dc.SetBrush(wx.RED_BRUSH)
1015 #dc.DrawRectangle(x1, y1, self.GetWidth(), self.GetHeight())
1016
1017 def OnDrawContents(self, dc):
1018 CompositeShape.OnDrawContents(self, dc)
1019
1020 def OnMovePre(self, dc, x, y, oldx, oldy, display = True):
1021 diffX = x - oldx
1022 diffY = y - oldy
1023 for object in self._children:
1024 object.Erase(dc)
1025 object.Move(dc, object.GetX() + diffX, object.GetY() + diffY, display)
1026 return True
1027
1028 def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
1029 if self._sensitivity & OP_DRAG_LEFT != OP_DRAG_LEFT:
1030 if self._parent:
1031 hit = self._parent.HitTest(x, y)
1032 if hit:
1033 attachment, dist = hit
1034 self._parent.GetEventHandler().OnDragLeft(draw, x, y, keys, attachment)
1035 return
1036 Shape.OnDragLeft(self, draw, x, y, keys, attachment)
1037
1038 def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
1039 if self._sensitivity & OP_DRAG_LEFT != OP_DRAG_LEFT:
1040 if self._parent:
1041 hit = self._parent.HitTest(x, y)
1042 if hit:
1043 attachment, dist = hit
1044 self._parent.GetEventHandler().OnBeginDragLeft(x, y, keys, attachment)
1045 return
1046 Shape.OnBeginDragLeft(x, y, keys, attachment)
1047
1048 def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
1049 if self._canvas.HasCapture():
1050 self._canvas.ReleaseMouse()
1051 if self._sensitivity & OP_DRAG_LEFT != OP_DRAG_LEFT:
1052 if self._parent:
1053 hit = self._parent.HitTest(x, y)
1054 if hit:
1055 attachment, dist = hit
1056 self._parent.GetEventHandler().OnEndDragLeft(x, y, keys, attachment)
1057 return
1058
1059 dc = wx.ClientDC(self.GetCanvas())
1060 self.GetCanvas().PrepareDC(dc)
1061
1062 dc.SetLogicalFunction(wx.COPY)
1063
1064 self._xpos, self._ypos = self._canvas.Snap(self._xpos, self._ypos)
1065 self.GetEventHandler().OnMovePre(dc, x, y, self._oldX, self._oldY)
1066
1067 self.ResetControlPoints()
1068 self.Draw(dc)
1069 self.MoveLinks(dc)
1070 self.GetEventHandler().OnDrawControlPoints(dc)
1071
1072 if self._canvas and not self._canvas.GetQuickEditMode():
1073 self._canvas.Redraw(dc)
1074
1075 def SetSize(self, w, h, recursive = True):
1076 self._width = w
1077 self._height = h
1078 RectangleShape.SetSize(self, w, h, recursive)
1079
1080 def CalculateSize(self):
1081 pass
1082
1083 # Experimental
1084 def OnRightClick(self, x, y, keys = 0, attachment = 0):
1085 if keys & KEY_CTRL:
1086 self.PopupMenu(x, y)
1087 else:
1088 if self._parent:
1089 hit = self._parent.HitTest(x, y)
1090 if hit:
1091 attachment, dist = hit
1092 self._parent.GetEventHandler().OnRightClick(x, y, keys, attachment)
1093
1094 # Divide wx.HORIZONTALly or wx.VERTICALly
1095 def Divide(self, direction):
1096 """Divide this division into two further divisions,
1097 horizontally (direction is wxHORIZONTAL) or
1098 vertically (direction is wxVERTICAL).
1099 """
1100 # Calculate existing top-left, bottom-right
1101 x1 = self.GetX() - self.GetWidth() / 2.0
1102 y1 = self.GetY() - self.GetHeight() / 2.0
1103
1104 compositeParent = self.GetParent()
1105 oldWidth = self.GetWidth()
1106 oldHeight = self.GetHeight()
1107 if self.Selected():
1108 self.Select(False)
1109
1110 dc = wx.ClientDC(self.GetCanvas())
1111 self.GetCanvas().PrepareDC(dc)
1112
1113 if direction == wx.VERTICAL:
1114 # Dividing vertically means notionally putting a horizontal
1115 # line through it.
1116 # Break existing piece into two.
1117 newXPos1 = self.GetX()
1118 newYPos1 = y1 + self.GetHeight() / 4.0
1119 newXPos2 = self.GetX()
1120 newYPos2 = y1 + 3 * self.GetHeight() / 4.0
1121 newDivision = compositeParent.OnCreateDivision()
1122 newDivision.Show(True)
1123
1124 self.Erase(dc)
1125
1126 # Anything adjoining the bottom of this division now adjoins the
1127 # bottom of the new division.
1128 for obj in compositeParent.GetDivisions():
1129 if obj.GetTopSide() == self:
1130 obj.SetTopSide(newDivision)
1131
1132 newDivision.SetTopSide(self)
1133 newDivision.SetBottomSide(self._bottomSide)
1134 newDivision.SetLeftSide(self._leftSide)
1135 newDivision.SetRightSide(self._rightSide)
1136 self._bottomSide = newDivision
1137
1138 compositeParent.GetDivisions().append(newDivision)
1139
1140 # CHANGE: Need to insert this division at start of divisions in the
1141 # object list, because e.g.:
1142 # 1) Add division
1143 # 2) Add contained object
1144 # 3) Add division
1145 # Division is now receiving mouse events _before_ the contained
1146 # object, because it was added last (on top of all others)
1147
1148 # Add after the image that visualizes the container
1149 compositeParent.AddChild(newDivision, compositeParent.FindContainerImage())
1150
1151 self._handleSide = DIVISION_SIDE_BOTTOM
1152 newDivision.SetHandleSide(DIVISION_SIDE_TOP)
1153
1154 self.SetSize(oldWidth, oldHeight / 2.0)
1155 self.Move(dc, newXPos1, newYPos1)
1156
1157 newDivision.SetSize(oldWidth, oldHeight / 2.0)
1158 newDivision.Move(dc, newXPos2, newYPos2)
1159 else:
1160 # Dividing horizontally means notionally putting a vertical line
1161 # through it.
1162 # Break existing piece into two.
1163 newXPos1 = x1 + self.GetWidth() / 4.0
1164 newYPos1 = self.GetY()
1165 newXPos2 = x1 + 3 * self.GetWidth() / 4.0
1166 newYPos2 = self.GetY()
1167 newDivision = compositeParent.OnCreateDivision()
1168 newDivision.Show(True)
1169
1170 self.Erase(dc)
1171
1172 # Anything adjoining the left of this division now adjoins the
1173 # left of the new division.
1174 for obj in compositeParent.GetDivisions():
1175 if obj.GetLeftSide() == self:
1176 obj.SetLeftSide(newDivision)
1177
1178 newDivision.SetTopSide(self._topSide)
1179 newDivision.SetBottomSide(self._bottomSide)
1180 newDivision.SetLeftSide(self)
1181 newDivision.SetRightSide(self._rightSide)
1182 self._rightSide = newDivision
1183
1184 compositeParent.GetDivisions().append(newDivision)
1185 compositeParent.AddChild(newDivision, compositeParent.FindContainerImage())
1186
1187 self._handleSide = DIVISION_SIDE_RIGHT
1188 newDivision.SetHandleSide(DIVISION_SIDE_LEFT)
1189
1190 self.SetSize(oldWidth / 2.0, oldHeight)
1191 self.Move(dc, newXPos1, newYPos1)
1192
1193 newDivision.SetSize(oldWidth / 2.0, oldHeight)
1194 newDivision.Move(dc, newXPos2, newYPos2)
1195
1196 if compositeParent.Selected():
1197 compositeParent.DeleteControlPoints(dc)
1198 compositeParent.MakeControlPoints()
1199 compositeParent.MakeMandatoryControlPoints()
1200
1201 compositeParent.Draw(dc)
1202 return True
1203
1204 def MakeControlPoints(self):
1205 self.MakeMandatoryControlPoints()
1206
1207 def MakeMandatoryControlPoints(self):
1208 maxX, maxY = self.GetBoundingBoxMax()
1209 x = y = 0.0
1210 direction = 0
1211
1212 if self._handleSide == DIVISION_SIDE_LEFT:
1213 x = -maxX / 2.0
1214 direction = CONTROL_POINT_HORIZONTAL
1215 elif self._handleSide == DIVISION_SIDE_TOP:
1216 y = -maxY / 2.0
1217 direction = CONTROL_POINT_VERTICAL
1218 elif self._handleSide == DIVISION_SIDE_RIGHT:
1219 x = maxX / 2.0
1220 direction = CONTROL_POINT_HORIZONTAL
1221 elif self._handleSide == DIVISION_SIDE_BOTTOM:
1222 y = maxY / 2.0
1223 direction = CONTROL_POINT_VERTICAL
1224
1225 if self._handleSide != DIVISION_SIDE_NONE:
1226 control = DivisionControlPoint(self._canvas, self, CONTROL_POINT_SIZE, x, y, direction)
1227 self._canvas.AddShape(control)
1228 self._controlPoints.append(control)
1229
1230 def ResetControlPoints(self):
1231 self.ResetMandatoryControlPoints()
1232
1233 def ResetMandatoryControlPoints(self):
1234 if not self._controlPoints:
1235 return
1236
1237 maxX, maxY = self.GetBoundingBoxMax()
1238
1239 node = self._controlPoints[0]
1240
1241 if self._handleSide == DIVISION_SIDE_LEFT and node:
1242 node._xoffset = -maxX / 2.0
1243 node._yoffset = 0.0
1244
1245 if self._handleSide == DIVISION_SIDE_TOP and node:
1246 node._xoffset = 0.0
1247 node._yoffset = -maxY / 2.0
1248
1249 if self._handleSide == DIVISION_SIDE_RIGHT and node:
1250 node._xoffset = maxX / 2.0
1251 node._yoffset = 0.0
1252
1253 if self._handleSide == DIVISION_SIDE_BOTTOM and node:
1254 node._xoffset = 0.0
1255 node._yoffset = maxY / 2.0
1256
1257 def AdjustLeft(self, left, test):
1258 """Adjust a side.
1259
1260 Returns FALSE if it's not physically possible to adjust it to
1261 this point.
1262 """
1263 x2 = self.GetX() + self.GetWidth() / 2.0
1264
1265 if left >= x2:
1266 return False
1267
1268 if test:
1269 return True
1270
1271 newW = x2 - left
1272 newX = left + newW / 2.0
1273 self.SetSize(newW, self.GetHeight())
1274
1275 dc = wx.ClientDC(self.GetCanvas())
1276 self.GetCanvas().PrepareDC(dc)
1277
1278 self.Move(dc, newX, self.GetY())
1279 return True
1280
1281 def AdjustTop(self, top, test):
1282 """Adjust a side.
1283
1284 Returns FALSE if it's not physically possible to adjust it to
1285 this point.
1286 """
1287 y2 = self.GetY() + self.GetHeight() / 2.0
1288
1289 if top >= y2:
1290 return False
1291
1292 if test:
1293 return True
1294
1295 newH = y2 - top
1296 newY = top + newH / 2.0
1297 self.SetSize(self.GetWidth(), newH)
1298
1299 dc = wx.ClientDC(self.GetCanvas())
1300 self.GetCanvas().PrepareDC(dc)
1301
1302 self.Move(dc, self.GetX(), newY)
1303 return True
1304
1305 def AdjustRight(self, right, test):
1306 """Adjust a side.
1307
1308 Returns FALSE if it's not physically possible to adjust it to
1309 this point.
1310 """
1311 x1 = self.GetX() - self.GetWidth() / 2.0
1312
1313 if right <= x1:
1314 return False
1315
1316 if test:
1317 return True
1318
1319 newW = right - x1
1320 newX = x1 + newW / 2.0
1321 self.SetSize(newW, self.GetHeight())
1322
1323 dc = wx.ClientDC(self.GetCanvas())
1324 self.GetCanvas().PrepareDC(dc)
1325
1326 self.Move(dc, newX, self.GetY())
1327 return True
1328
1329 def AdjustTop(self, top, test):
1330 """Adjust a side.
1331
1332 Returns FALSE if it's not physically possible to adjust it to
1333 this point.
1334 """
1335 y1 = self.GetY() - self.GetHeight() / 2.0
1336
1337 if bottom <= y1:
1338 return False
1339
1340 if test:
1341 return True
1342
1343 newH = bottom - y1
1344 newY = y1 + newH / 2.0
1345 self.SetSize(self.GetWidth(), newH)
1346
1347 dc = wx.ClientDC(self.GetCanvas())
1348 self.GetCanvas().PrepareDC(dc)
1349
1350 self.Move(dc, self.GetX(), newY)
1351 return True
1352
1353 # Resize adjoining divisions.
1354
1355 # Behaviour should be as follows:
1356 # If right edge moves, find all objects whose left edge
1357 # adjoins this object, and move left edge accordingly.
1358 # If left..., move ... right.
1359 # If top..., move ... bottom.
1360 # If bottom..., move top.
1361 # If size goes to zero or end position is other side of start position,
1362 # resize to original size and return.
1363 #
1364 def ResizeAdjoining(self, side, newPos, test):
1365 """Resize adjoining divisions at the given side.
1366
1367 If test is TRUE, just see whether it's possible for each adjoining
1368 region, returning FALSE if it's not.
1369
1370 side can be one of:
1371
1372 * DIVISION_SIDE_NONE
1373 * DIVISION_SIDE_LEFT
1374 * DIVISION_SIDE_TOP
1375 * DIVISION_SIDE_RIGHT
1376 * DIVISION_SIDE_BOTTOM
1377 """
1378 divisionParent = self.GetParent()
1379 for division in divisionParent.GetDivisions():
1380 if side == DIVISION_SIDE_LEFT:
1381 if division._rightSide == self:
1382 success = division.AdjustRight(newPos, test)
1383 if not success and test:
1384 return false
1385 elif side == DIVISION_SIDE_TOP:
1386 if division._bottomSide == self:
1387 success = division.AdjustBottom(newPos, test)
1388 if not success and test:
1389 return False
1390 elif side == DIVISION_SIDE_RIGHT:
1391 if division._leftSide == self:
1392 success = division.AdjustLeft(newPos, test)
1393 if not success and test:
1394 return False
1395 elif side == DIVISION_SIDE_BOTTOM:
1396 if division._topSide == self:
1397 success = division.AdjustTop(newPos, test)
1398 if not success and test:
1399 return False
1400 return True
1401
1402 def EditEdge(self, side):
1403 print "EditEdge() not implemented."
1404
1405 def PopupMenu(self, x, y):
1406 menu = PopupDivisionMenu()
1407 menu.SetClientData(self)
1408 if self._leftSide:
1409 menu.Enable(DIVISION_MENU_EDIT_LEFT_EDGE, True)
1410 else:
1411 menu.Enable(DIVISION_MENU_EDIT_LEFT_EDGE, False)
1412 if self._topSide:
1413 menu.Enable(DIVISION_MENU_EDIT_TOP_EDGE, True)
1414 else:
1415 menu.Enable(DIVISION_MENU_EDIT_TOP_EDGE, False)
1416
1417 x1, y1 = self._canvas.GetViewStart()
1418 unit_x, unit_y = self._canvas.GetScrollPixelsPerUnit()
1419
1420 dc = wx.ClientDC(self.GetCanvas())
1421 self.GetCanvas().PrepareDC(dc)
1422
1423 mouse_x = dc.LogicalToDeviceX(x - x1 * unit_x)
1424 mouse_y = dc.LogicalToDeviceY(y - y1 * unit_y)
1425
1426 self._canvas.PopupMenu(menu, (mouse_x, mouse_y))
1427
1428