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