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