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