1 # -*- coding: iso-8859-1 -*-
2 #----------------------------------------------------------------------------
4 # Purpose: Composite class
6 # Author: Pierre Hjälm (from C++ original by Julian Smart)
10 # Copyright: (c) 2004 Pierre Hjälm - 1998 Julian Smart
11 # Licence: wxWindows license
12 #----------------------------------------------------------------------------
14 from __future__
import division
19 from basic
import RectangleShape
, Shape
, ControlPoint
22 KEY_SHIFT
, KEY_CTRL
= 1, 2
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
34 CONSTRAINT_ALIGNED_TOP
= 8
35 CONSTRAINT_ALIGNED_BOTTOM
= 9
36 CONSTRAINT_ALIGNED_LEFT
= 10
37 CONSTRAINT_ALIGNED_RIGHT
= 11
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
48 class ConstraintType(object):
49 def __init__(self
, theType
, theName
, thePhrase
):
52 self
._phrase
= thePhrase
57 [CONSTRAINT_CENTRED_VERTICALLY
,
58 ConstraintType(CONSTRAINT_CENTRED_VERTICALLY
, "Centre vertically", "centred vertically w.r.t.")],
60 [CONSTRAINT_CENTRED_HORIZONTALLY
,
61 ConstraintType(CONSTRAINT_CENTRED_HORIZONTALLY
, "Centre horizontally", "centred horizontally w.r.t.")],
63 [CONSTRAINT_CENTRED_BOTH
,
64 ConstraintType(CONSTRAINT_CENTRED_BOTH
, "Centre", "centred w.r.t.")],
67 ConstraintType(CONSTRAINT_LEFT_OF
, "Left of", "left of")],
70 ConstraintType(CONSTRAINT_RIGHT_OF
, "Right of", "right of")],
73 ConstraintType(CONSTRAINT_ABOVE
, "Above", "above")],
76 ConstraintType(CONSTRAINT_BELOW
, "Below", "below")],
79 [CONSTRAINT_ALIGNED_TOP
,
80 ConstraintType(CONSTRAINT_ALIGNED_TOP
, "Top-aligned", "aligned to the top of")],
82 [CONSTRAINT_ALIGNED_BOTTOM
,
83 ConstraintType(CONSTRAINT_ALIGNED_BOTTOM
, "Bottom-aligned", "aligned to the bottom of")],
85 [CONSTRAINT_ALIGNED_LEFT
,
86 ConstraintType(CONSTRAINT_ALIGNED_LEFT
, "Left-aligned", "aligned to the left of")],
88 [CONSTRAINT_ALIGNED_RIGHT
,
89 ConstraintType(CONSTRAINT_ALIGNED_RIGHT
, "Right-aligned", "aligned to the right of")],
92 [CONSTRAINT_MIDALIGNED_TOP
,
93 ConstraintType(CONSTRAINT_MIDALIGNED_TOP
, "Top-midaligned", "centred on the top of")],
95 [CONSTRAINT_MIDALIGNED_BOTTOM
,
96 ConstraintType(CONSTRAINT_MIDALIGNED_BOTTOM
, "Bottom-midaligned", "centred on the bottom of")],
98 [CONSTRAINT_MIDALIGNED_LEFT
,
99 ConstraintType(CONSTRAINT_MIDALIGNED_LEFT
, "Left-midaligned", "centred on the left of")],
101 [CONSTRAINT_MIDALIGNED_RIGHT
,
102 ConstraintType(CONSTRAINT_MIDALIGNED_RIGHT
, "Right-midaligned", "centred on the right of")]
108 class Constraint(object):
109 """A Constraint object helps specify how child shapes are laid out with
110 respect to siblings and parents.
115 def __init__(self
, type, constraining
, constrained
):
119 self
._constraintType
= type
120 self
._constraintingObject
= constraining
122 self
._constraintId
= 0
123 self
._constraintName
="noname"
125 self
._constrainedObjects
= constrained
[:]
128 return "<%s.%s>" % (self
.__class
__.__module
__, self
.__class
__.__name
__)
130 def SetSpacing(self
, x
, y
):
131 """Sets the horizontal and vertical spacing for the constraint."""
135 def Equals(self
, a
, b
):
136 """Return TRUE if x and y are approximately equal (for the purposes
137 of evaluating the constraint).
141 return b
<= a
+ marg
and b
>= a
-marg
144 """Evaluate this constraint and return TRUE if anything changed."""
145 maxWidth
, maxHeight
= self
._constraintingObject
.GetBoundingBoxMax()
146 minWidth
, minHeight
= self
._constraintingObject
.GetBoundingBoxMin()
147 x
= self
._constraintingObject
.GetX()
148 y
= self
._constraintingObject
.GetY()
150 dc
= wx
.ClientDC(self
._constraintingObject
.GetCanvas())
151 self
._constraintingObject
.GetCanvas().PrepareDC(dc
)
153 if self
._constraintType
== CONSTRAINT_CENTRED_VERTICALLY
:
154 n
= len(self
._constrainedObjects
)
155 totalObjectHeight
= 0.0
156 for constrainedObject
in self
._constrainedObjects
:
157 width2
, height2
= constrainedObject
.GetBoundingBoxMax()
158 totalObjectHeight
+= height2
160 # Check if within the constraining object...
161 if totalObjectHeight
+ (n
+ 1) * self
._ySpacing
<= minHeight
:
162 spacingY
= (minHeight
-totalObjectHeight
) / (n
+ 1)
163 startY
= y
-minHeight
/ 2
164 else: # Otherwise, use default spacing
165 spacingY
= self
._ySpacing
166 startY
= y
-(totalObjectHeight
+ (n
+ 1) * spacingY
) / 2
168 # Now position the objects
170 for constrainedObject
in self
._constrainedObjects
:
171 width2
, height2
= constrainedObject
.GetBoundingBoxMax()
172 startY
+= spacingY
+ height2
/ 2
173 if not self
.Equals(startY
, constrainedObject
.GetY()):
174 constrainedObject
.Move(dc
, constrainedObject
.GetX(), startY
, False)
176 startY
+= height2
/ 2
178 elif self
._constraintType
== CONSTRAINT_CENTRED_HORIZONTALLY
:
179 n
= len(self
._constrainedObjects
)
180 totalObjectWidth
= 0.0
181 for constrainedObject
in self
._constrainedObjects
:
182 width2
, height2
= constrainedObject
.GetBoundingBoxMax()
183 totalObjectWidth
+= width2
185 # Check if within the constraining object...
186 if totalObjectWidth
+ (n
+ 1) * self
._xSpacing
<minWidth
:
187 spacingX
= (minWidth
-totalObjectWidth
) / (n
+ 1)
188 startX
= x
-minWidth
/ 2
189 else: # Otherwise, use default spacing
190 spacingX
= self
._xSpacing
191 startX
= x
-(totalObjectWidth
+ (n
+ 1) * spacingX
) / 2
193 # Now position the objects
195 for constrainedObject
in self
._constrainedObjects
:
196 width2
, height2
= constrainedObject
.GetBoundingBoxMax()
197 startX
+= spacingX
+ width2
/ 2
198 if not self
.Equals(startX
, constrainedObject
.GetX()):
199 constrainedObject
.Move(dc
, startX
, constrainedObject
.GetY(), False)
203 elif self
._constraintType
== CONSTRAINT_CENTRED_BOTH
:
204 n
= len(self
._constrainedObjects
)
205 totalObjectWidth
= 0.0
206 totalObjectHeight
= 0.0
208 for constrainedObject
in self
._constrainedObjects
:
209 width2
, height2
= constrainedObject
.GetBoundingBoxMax()
210 totalObjectWidth
+= width2
211 totalObjectHeight
+= height2
213 # Check if within the constraining object...
214 if totalObjectHeight
+ (n
+ 1) * self
._xSpacing
<= minWidth
:
215 spacingX
= (minWidth
-totalObjectWidth
) / (n
+ 1)
216 startX
= x
-minWidth
/ 2
217 else: # Otherwise, use default spacing
218 spacingX
= self
._xSpacing
219 startX
= x
-(totalObjectWidth
+ (n
+ 1) * spacingX
) / 2
221 # Check if within the constraining object...
222 if totalObjectHeight
+ (n
+ 1) * self
._ySpacing
<= minHeight
:
223 spacingY
= (minHeight
-totalObjectHeight
) / (n
+ 1)
224 startY
= y
-minHeight
/ 2
225 else: # Otherwise, use default spacing
226 spacingY
= self
._ySpacing
227 startY
= y
-(totalObjectHeight
+ (n
+ 1) * spacingY
) / 2
229 # Now position the objects
231 for constrainedObject
in self
._constrainedObjects
:
232 width2
, height2
= constrainedObject
.GetBoundingBoxMax()
233 startX
+= spacingX
+ width2
/ 2
234 startY
+= spacingY
+ height2
/ 2
236 if not self
.Equals(startX
, constrainedObject
.GetX()) or not self
.Equals(startY
, constrainedObject
.GetY()):
237 constrainedObject
.Move(dc
, startX
, startY
, False)
241 startY
+= height2
/ 2
243 elif self
._constraintType
== CONSTRAINT_LEFT_OF
:
245 for constrainedObject
in self
._constrainedObjects
:
246 width2
, height2
= constrainedObject
.GetBoundingBoxMax()
248 x3
= x
-minWidth
/ 2-width2
/ 2-self
._xSpacing
249 if not self
.Equals(x3
, constrainedObject
.GetX()):
251 constrainedObject
.Move(dc
, x3
, constrainedObject
.GetY(), False)
253 elif self
._constraintType
== CONSTRAINT_RIGHT_OF
:
256 for constrainedObject
in self
._constrainedObjects
:
257 width2
, height2
= constrainedObject
.GetBoundingBoxMax()
258 x3
= x
+ minWidth
/ 2 + width2
/ 2 + self
._xSpacing
259 if not self
.Equals(x3
, constrainedObject
.GetX()):
260 constrainedObject
.Move(dc
, x3
, constrainedObject
.GetY(), False)
263 elif self
._constraintType
== CONSTRAINT_ABOVE
:
266 for constrainedObject
in self
._constrainedObjects
:
267 width2
, height2
= constrainedObject
.GetBoundingBoxMax()
269 y3
= y
-minHeight
/ 2-height2
/ 2-self
._ySpacing
270 if not self
.Equals(y3
, constrainedObject
.GetY()):
272 constrainedObject
.Move(dc
, constrainedObject
.GetX(), y3
, False)
274 elif self
._constraintType
== CONSTRAINT_BELOW
:
277 for constrainedObject
in self
._constrainedObjects
:
278 width2
, height2
= constrainedObject
.GetBoundingBoxMax()
280 y3
= y
+ minHeight
/ 2 + height2
/ 2 + self
._ySpacing
281 if not self
.Equals(y3
, constrainedObject
.GetY()):
283 constrainedObject
.Move(dc
, constrainedObject
.GetX(), y3
, False)
285 elif self
._constraintType
== CONSTRAINT_ALIGNED_LEFT
:
287 for constrainedObject
in self
._constrainedObjects
:
288 width2
, height2
= constrainedObject
.GetBoundingBoxMax()
289 x3
= x
-minWidth
/ 2 + width2
/ 2 + self
._xSpacing
290 if not self
.Equals(x3
, constrainedObject
.GetX()):
292 constrainedObject
.Move(dc
, x3
, constrainedObject
.GetY(), False)
294 elif self
._constraintType
== CONSTRAINT_ALIGNED_RIGHT
:
296 for constrainedObject
in self
._constrainedObjects
:
297 width2
, height2
= constrainedObject
.GetBoundingBoxMax()
298 x3
= x
+ minWidth
/ 2-width2
/ 2-self
._xSpacing
299 if not self
.Equals(x3
, constrainedObject
.GetX()):
301 constrainedObject
.Move(dc
, x3
, constrainedObject
.GetY(), False)
303 elif self
._constraintType
== CONSTRAINT_ALIGNED_TOP
:
305 for constrainedObject
in self
._constrainedObjects
:
306 width2
, height2
= constrainedObject
.GetBoundingBoxMax()
307 y3
= y
-minHeight
/ 2 + height2
/ 2 + self
._ySpacing
308 if not self
.Equals(y3
, constrainedObject
.GetY()):
310 constrainedObject
.Move(dc
, constrainedObject
.GetX(), y3
, False)
312 elif self
._constraintType
== CONSTRAINT_ALIGNED_BOTTOM
:
314 for constrainedObject
in self
._constrainedObjects
:
315 width2
, height2
= constrainedObject
.GetBoundingBoxMax()
316 y3
= y
+ minHeight
/ 2-height2
/ 2-self
._ySpacing
317 if not self
.Equals(y3
, constrainedObject
.GetY()):
319 constrainedObject
.Move(dc
, constrainedObject
.GetX(), y3
, False)
321 elif self
._constraintType
== CONSTRAINT_MIDALIGNED_LEFT
:
323 for constrainedObject
in self
._constrainedObjects
:
325 if not self
.Equals(x3
, constrainedObject
.GetX()):
327 constrainedObject
.Move(dc
, x3
, constrainedObject
.GetY(), False)
329 elif self
._constraintType
== CONSTRAINT_MIDALIGNED_RIGHT
:
331 for constrainedObject
in self
._constrainedObjects
:
332 x3
= x
+ minWidth
/ 2
333 if not self
.Equals(x3
, constrainedObject
.GetX()):
335 constrainedObject
.Move(dc
, x3
, constrainedObject
.GetY(), False)
337 elif self
._constraintType
== CONSTRAINT_MIDALIGNED_TOP
:
339 for constrainedObject
in self
._constrainedObjects
:
341 if not self
.Equals(y3
, constrainedObject
.GetY()):
343 constrainedObject
.Move(dc
, constrainedObject
.GetX(), y3
, False)
345 elif self
._constraintType
== CONSTRAINT_MIDALIGNED_BOTTOM
:
347 for constrainedObject
in self
._constrainedObjects
:
348 y3
= y
+ minHeight
/ 2
349 if not self
.Equals(y3
, constrainedObject
.GetY()):
351 constrainedObject
.Move(dc
, constrainedObject
.GetX(), y3
, False)
358 class CompositeShape(RectangleShape
):
359 """This is an object with a list of child objects, and a list of size
360 and positioning constraints between the children.
366 RectangleShape
.__init
__(self
, 100.0, 100.0)
368 self
._oldX
= self
._xpos
369 self
._oldY
= self
._ypos
371 self
._constraints
= []
372 self
._divisions
= [] # In case it's a container
374 def OnDraw(self
, dc
):
375 x1
= self
._xpos
-self
._width
/ 2
376 y1
= self
._ypos
-self
._height
/ 2
378 if self
._shadowMode
!= SHADOW_NONE
:
379 if self
._shadowBrush
:
380 dc
.SetBrush(self
._shadowBrush
)
381 dc
.SetPen(wx
.Pen(wx
.WHITE
, 1, wx
.TRANSPARENT
))
383 if self
._cornerRadius
:
384 dc
.DrawRoundedRectangle(x1
+ self
._shadowOffsetX
, y1
+ self
._shadowOffsetY
, self
._width
, self
._height
, self
._cornerRadius
)
386 dc
.DrawRectangle(x1
+ self
._shadowOffsetX
, y1
+ self
._shadowOffsetY
, self
._width
, self
._height
)
388 # For debug purposes /pi
389 #dc.DrawRectangle(x1, y1, self._width, self._height)
391 def OnDrawContents(self
, dc
):
392 for object in self
._children
:
396 Shape
.OnDrawContents(self
, dc
)
398 def OnMovePre(self
, dc
, x
, y
, old_x
, old_y
, display
= True):
402 for object in self
._children
:
404 object.Move(dc
, object.GetX() + diffX
, object.GetY() + diffY
, display
)
408 def OnErase(self
, dc
):
409 RectangleShape
.OnErase(self
, dc
)
410 for object in self
._children
:
413 def OnDragLeft(self
, draw
, x
, y
, keys
= 0, attachment
= 0):
414 xx
, yy
= self
._canvas
.Snap(x
, y
)
415 offsetX
= xx
-objectStartX
416 offsetY
= yy
-objectStartY
418 dc
= wx
.ClientDC(self
.GetCanvas())
419 self
.GetCanvas().PrepareDC(dc
)
421 dc
.SetLogicalFunction(OGLRBLF
)
422 dottedPen
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
)
424 dc
.SetBrush(wx
.TRANSPARENT_BRUSH
)
426 self
.GetEventHandler().OnDrawOutline(dc
, self
.GetX() + offsetX
, self
.GetY() + offsetY
, self
.GetWidth(), self
.GetHeight())
428 def OnBeginDragLeft(self
, x
, y
, keys
= 0, attachment
= 0):
429 global objectStartX
, objectStartY
434 dc
= wx
.ClientDC(self
.GetCanvas())
435 self
.GetCanvas().PrepareDC(dc
)
439 dc
.SetLogicalFunction(OGLRBLF
)
440 dottedPen
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
)
442 dc
.SetBrush(wx
.TRANSPARENT_BRUSH
)
443 self
._canvas
.CaptureMouse()
445 xx
, yy
= self
._canvas
.Snap(x
, y
)
446 offsetX
= xx
-objectStartX
447 offsetY
= yy
-objectStartY
449 self
.GetEventHandler().OnDrawOutline(dc
, self
.GetX() + offsetX
, self
.GetY() + offsetY
, self
.GetWidth(), self
.GetHeight())
451 def OnEndDragLeft(self
, x
, y
, keys
= 0, attachment
= 0):
452 dc
= wx
.ClientDC(self
.GetCanvas())
453 self
.GetCanvas().PrepareDC(dc
)
455 if self
._canvas
.HasCapture():
456 self
._canvas
.ReleaseMouse()
458 if not self
._draggable
:
460 self
._parent
.GetEventHandler().OnEndDragLeft(x
, y
, keys
, 0)
465 dc
.SetLogicalFunction(wx
.COPY
)
467 xx
, yy
= self
._canvas
.Snap(x
, y
)
468 offsetX
= xx
-objectStartX
469 offsetY
= yy
-objectStartY
471 self
.Move(dc
, self
.GetX() + offsetX
, self
.GetY() + offsetY
)
473 if self
._canvas
and not self
._canvas
.GetQuickEditMode():
474 self
._canvas
.Redraw(dc
)
476 def OnRightClick(self
, x
, y
, keys
= 0, attachment
= 0):
477 # If we get a ctrl-right click, this means send the message to
478 # the division, so we can invoke a user interface for dealing
481 for division
in self
._divisions
:
482 hit
= division
.HitTest(x
, y
)
484 division
.GetEventHandler().OnRightClick(x
, y
, keys
, hit
[0])
487 def SetSize(self
, w
, h
, recursive
= True):
488 self
.SetAttachmentSize(w
, h
)
490 xScale
= w
/ max(1, self
.GetWidth())
491 yScale
= h
/ max(1, self
.GetHeight())
499 dc
= wx
.ClientDC(self
.GetCanvas())
500 self
.GetCanvas().PrepareDC(dc
)
502 for object in self
._children
:
503 # Scale the position first
504 newX
= (object.GetX()-self
.GetX()) * xScale
+ self
.GetX()
505 newY
= (object.GetY()-self
.GetY()) * yScale
+ self
.GetY()
507 object.Move(dc
, newX
, newY
)
510 # Now set the scaled size
511 xbound
, ybound
= object.GetBoundingBoxMax()
512 if not object.GetFixedWidth():
514 if not object.GetFixedHeight():
516 object.SetSize(xbound
, ybound
)
518 self
.SetDefaultRegionSize()
520 def AddChild(self
, child
, addAfter
= None):
521 """Adds a child shape to the composite.
523 If addAfter is not None, the shape will be added after this shape.
525 self
._children
.append(child
)
526 child
.SetParent(self
)
528 # Ensure we add at the right position
530 child
.RemoveFromCanvas(self
._canvas
)
531 child
.AddToCanvas(self
._canvas
, addAfter
)
533 def RemoveChild(self
, child
):
534 """Removes the child from the composite and any constraint
535 relationships, but does not delete the child.
537 self
._children
.remove(child
)
538 self
._divisions
.remove(child
)
539 self
.RemoveChildFromConstraints(child
)
540 child
.SetParent(None)
542 def DeleteConstraintsInvolvingChild(self
, child
):
543 """This function deletes constraints which mention the given child.
545 Used when deleting a child from the composite.
547 for constraint
in self
._constraints
:
548 if constraint
._constrainingObject
== child
or child
in constraint
._constrainedObjects
:
549 self
._constraints
.remove(constraint
)
551 def RemoveChildFromConstraints(self
, child
):
552 for constraint
in self
._constraints
:
553 if child
in constraint
._constrainedObjects
:
554 constraint
._constrainedObjects
.remove(child
)
555 if constraint
._constrainingObject
== child
:
556 constraint
._constrainingObject
= None
558 # Delete the constraint if no participants left
559 if not constraint
._constrainingObject
:
560 self
._constraints
.remove(constraint
)
562 def AddConstraint(self
, constraint
):
563 """Adds a constraint to the composite."""
564 self
._constraints
.append(constraint
)
565 if constraint
._constraintId
== 0:
566 constraint
._constraintId
= wx
.NewId()
569 def AddSimpleConstraint(self
, type, constraining
, constrained
):
570 """Add a constraint of the given type to the composite.
572 constraining is the shape doing the constraining
573 constrained is a list of shapes being constrained
575 constraint
= Constraint(type, constraining
, constrained
)
576 if constraint
._constraintId
== 0:
577 constraint
._constraintId
= wx
.NewId()
578 self
._constraints
.append(constraint
)
581 def FindConstraint(self
, cId
):
582 """Finds the constraint with the given id.
584 Returns a tuple of the constraint and the actual composite the
585 constraint was in, in case that composite was a descendant of
588 Returns None if not found.
590 for constraint
in self
._constraints
:
591 if constraint
._constraintId
== cId
:
592 return constraint
, self
594 # If not found, try children
595 for child
in self
._children
:
596 if isinstance(child
, CompositeShape
):
597 constraint
= child
.FindConstraint(cId
)
599 return constraint
[0], child
603 def DeleteConstraint(self
, constraint
):
604 """Deletes constraint from composite."""
605 self
._constraints
.remove(constraint
)
607 def CalculateSize(self
):
608 """Calculates the size and position of the composite based on
609 child sizes and positions.
616 for child
in self
._children
:
617 # Recalculate size of composite objects because may not conform
618 # to size it was set to - depends on the children.
619 if isinstance(child
, CompositeShape
):
620 child
.CalculateSize()
622 w
, h
= child
.GetBoundingBoxMax()
623 if child
.GetX() + w
/ 2>maxX
:
624 maxX
= child
.GetX() + w
/ 2
625 if child
.GetX()-w
/ 2<minX
:
626 minX
= child
.GetX()-w
/ 2
627 if child
.GetY() + h
/ 2>maxY
:
628 maxY
= child
.GetY() + h
/ 2
629 if child
.GetY()-h
/ 2<minY
:
630 minY
= child
.GetY()-h
/ 2
632 self
._width
= maxX
-minX
633 self
._height
= maxY
-minY
634 self
._xpos
= self
._width
/ 2 + minX
635 self
._ypos
= self
._height
/ 2 + minY
638 """Recomputes any constraints associated with the object. If FALSE is
639 returned, the constraints could not be satisfied (there was an
644 while changed
and noIterations
<500:
645 changed
= self
.Constrain()
654 for child
in self
._children
:
655 if isinstance(child
, CompositeShape
) and child
.Constrain():
658 for constraint
in self
._constraints
:
659 if constraint
.Evaluate():
664 def MakeContainer(self
):
665 """Makes this composite into a container by creating one child
668 division
= self
.OnCreateDivision()
669 self
._divisions
.append(division
)
670 self
.AddChild(division
)
672 division
.SetSize(self
._width
, self
._height
)
674 dc
= wx
.ClientDC(self
.GetCanvas())
675 self
.GetCanvas().PrepareDC(dc
)
677 division
.Move(dc
, self
.GetX(), self
.GetY())
681 def OnCreateDivision(self
):
682 return DivisionShape()
684 def FindContainerImage(self
):
685 """Finds the image used to visualize a container. This is any child of
686 the composite that is not in the divisions list.
688 for child
in self
._children
:
689 if child
in self
._divisions
:
694 def ContainsDivision(self
, division
):
695 """Returns TRUE if division is a descendant of this container."""
696 if division
in self
._divisions
:
699 for child
in self
._children
:
700 if isinstance(child
, CompositeShape
):
701 return child
.ContainsDivision(division
)
705 def GetDivisions(self
):
706 """Return the list of divisions."""
707 return self
._divisions
709 def GetConstraints(self
):
710 """Return the list of constraints."""
711 return self
._constraints
714 # A division object is a composite with special properties,
715 # to be used for containment. It's a subdivision of a container.
716 # A containing node image consists of a composite with a main child shape
717 # such as rounded rectangle, plus a list of division objects.
718 # It needs to be a composite because a division contains pieces
720 # NOTE a container has at least one wxDivisionShape for consistency.
721 # This can be subdivided, so it turns into two objects, then each of
722 # these can be subdivided, etc.
724 DIVISION_SIDE_NONE
=0
725 DIVISION_SIDE_LEFT
=1
727 DIVISION_SIDE_RIGHT
=3
728 DIVISION_SIDE_BOTTOM
=4
737 class DivisionControlPoint(ControlPoint
):
738 def __init__(self
, the_canvas
, object, size
, the_xoffset
, the_yoffset
, the_type
):
739 ControlPoint
.__init
__(self
, the_canvas
, object, size
, the_xoffset
, the_yoffset
, the_type
)
740 self
.SetEraseObject(False)
742 # Implement resizing of canvas object
743 def OnDragLeft(self
, draw
, x
, y
, keys
= 0, attachment
= 0):
744 ControlPoint
.OnDragLeft(self
, draw
, x
, y
, keys
, attachment
)
746 def OnBeginDragLeft(self
, x
, y
, keys
= 0, attachment
= 0):
747 global originalX
, originalY
, originalW
, originalH
749 originalX
= self
._shape
.GetX()
750 originalY
= self
._shape
.GetY()
751 originalW
= self
._shape
.GetWidth()
752 originalH
= self
._shape
.GetHeight()
754 ControlPoint
.OnBeginDragLeft(self
, x
, y
, keys
, attachment
)
756 def OnEndDragLeft(self
, x
, y
, keys
= 0, attachment
= 0):
757 ControlPoint
.OnEndDragLeft(self
, x
, y
, keys
, attachment
)
759 dc
= wx
.ClientDC(self
.GetCanvas())
760 self
.GetCanvas().PrepareDC(dc
)
762 division
= self
._shape
763 divisionParent
= division
.GetParent()
765 # Need to check it's within the bounds of the parent composite
766 x1
= divisionParent
.GetX()-divisionParent
.GetWidth() / 2
767 y1
= divisionParent
.GetY()-divisionParent
.GetHeight() / 2
768 x2
= divisionParent
.GetX() + divisionParent
.GetWidth() / 2
769 y2
= divisionParent
.GetY() + divisionParent
.GetHeight() / 2
771 # Need to check it has not made the division zero or negative
773 dx1
= division
.GetX()-division
.GetWidth() / 2
774 dy1
= division
.GetY()-division
.GetHeight() / 2
775 dx2
= division
.GetX() + division
.GetWidth() / 2
776 dy2
= division
.GetY() + division
.GetHeight() / 2
779 if division
.GetHandleSide() == DIVISION_SIDE_LEFT
:
780 if x
<= x1
or x
>= x2
or x
>= dx2
:
782 # Try it out first...
783 elif not division
.ResizeAdjoining(DIVISION_SIDE_LEFT
, x
, True):
786 division
.ResizeAdjoining(DIVISION_SIDE_LEFT
, x
, False)
787 elif division
.GetHandleSide() == DIVISION_SIDE_TOP
:
788 if y
<= y1
or y
>= y2
or y
>= dy2
:
790 elif not division
.ResizeAdjoining(DIVISION_SIDE_TOP
, y
, True):
793 division
.ResizingAdjoining(DIVISION_SIDE_TOP
, y
, False)
794 elif division
.GetHandleSide() == DIVISION_SIDE_RIGHT
:
795 if x
<= x1
or x
>= x2
or x
<= dx1
:
797 elif not division
.ResizeAdjoining(DIVISION_SIDE_RIGHT
, x
, True):
800 division
.ResizeAdjoining(DIVISION_SIDE_RIGHT
, x
, False)
801 elif division
.GetHandleSide() == DIVISION_SIDE_BOTTOM
:
802 if y
<= y1
or y
>= y2
or y
<= dy1
:
804 elif not division
.ResizeAdjoining(DIVISION_SIDE_BOTTOM
, y
, True):
807 division
.ResizeAdjoining(DIVISION_SIDE_BOTTOM
, y
, False)
810 division
.SetSize(originalW
, originalH
)
811 division
.Move(dc
, originalX
, originalY
)
813 divisionParent
.Draw(dc
)
814 division
.GetEventHandler().OnDrawControlPoints(dc
)
818 DIVISION_MENU_SPLIT_HORIZONTALLY
=1
819 DIVISION_MENU_SPLIT_VERTICALLY
=2
820 DIVISION_MENU_EDIT_LEFT_EDGE
=3
821 DIVISION_MENU_EDIT_TOP_EDGE
=4
822 DIVISION_MENU_EDIT_RIGHT_EDGE
=5
823 DIVISION_MENU_EDIT_BOTTOM_EDGE
=6
824 DIVISION_MENU_DELETE_ALL
=7
828 class PopupDivisionMenu(wx
.Menu
):
830 wx
.Menu
.__init
__(self
)
831 self
.Append(DIVISION_MENU_SPLIT_HORIZONTALLY
,"Split horizontally")
832 self
.Append(DIVISION_MENU_SPLIT_VERTICALLY
,"Split vertically")
833 self
.AppendSeparator()
834 self
.Append(DIVISION_MENU_EDIT_LEFT_EDGE
,"Edit left edge")
835 self
.Append(DIVISION_MENU_EDIT_TOP_EDGE
,"Edit top edge")
837 wx
.EVT_MENU_RANGE(self
, DIVISION_MENU_SPLIT_HORIZONTALLY
, DIVISION_MENU_EDIT_BOTTOM_EDGE
, self
.OnMenu
)
839 def SetClientData(self
, data
):
840 self
._clientData
= data
842 def GetClientData(self
):
843 return self
._clientData
845 def OnMenu(self
, event
):
846 division
= self
.GetClientData()
847 if event
.GetId() == DIVISION_MENU_SPLIT_HORIZONTALLY
:
848 division
.Divide(wx
.HORIZONTAL
)
849 elif event
.GetId() == DIVISION_MENU_SPLIT_VERTICALLY
:
850 division
.Divide(wx
.VERTICAL
)
851 elif event
.GetId() == DIVISION_MENU_EDIT_LEFT_EDGE
:
852 division
.EditEdge(DIVISION_SIDE_LEFT
)
853 elif event
.GetId() == DIVISION_MENU_EDIT_TOP_EDGE
:
854 division
.EditEdge(DIVISION_SIDE_TOP
)
858 class DivisionShape(CompositeShape
):
859 """A division shape is like a composite in that it can contain further
860 objects, but is used exclusively to divide another shape into regions,
861 or divisions. A wxDivisionShape is never free-standing.
867 CompositeShape
.__init
__(self
)
868 self
.SetSensitivityFilter(OP_CLICK_LEFT | OP_CLICK_RIGHT | OP_DRAG_RIGHT
)
869 self
.SetCentreResize(False)
870 self
.SetAttachmentMode(True)
871 self
._leftSide
= None
872 self
._rightSide
= None
874 self
._bottomSide
= None
875 self
._handleSide
= DIVISION_SIDE_NONE
876 self
._leftSidePen
= wx
.BLACK_PEN
877 self
._topSidePen
= wx
.BLACK_PEN
878 self
._leftSideColour
="BLACK"
879 self
._topSideColour
="BLACK"
880 self
._leftSideStyle
="Solid"
881 self
._topSideStyle
="Solid"
884 def SetLeftSide(self
, shape
):
885 """Set the the division on the left side of this division."""
886 self
._leftSide
= shape
888 def SetTopSide(self
, shape
):
889 """Set the the division on the top side of this division."""
890 self
._topSide
= shape
892 def SetRightSide(self
, shape
):
893 """Set the the division on the right side of this division."""
894 self
._rightSide
= shape
896 def SetBottomSide(self
, shape
):
897 """Set the the division on the bottom side of this division."""
898 self
._bottomSide
= shape
900 def GetLeftSide(self
):
901 """Return the division on the left side of this division."""
902 return self
._leftSide
904 def GetTopSide(self
):
905 """Return the division on the top side of this division."""
908 def GetRightSide(self
):
909 """Return the division on the right side of this division."""
910 return self
._rightSide
912 def GetBottomSide(self
):
913 """Return the division on the bottom side of this division."""
914 return self
._bottomSide
916 def SetHandleSide(self
, side
):
917 """Sets the side which the handle appears on.
919 Either DIVISION_SIDE_LEFT or DIVISION_SIDE_TOP.
921 self
._handleSide
= side
923 def GetHandleSide(self
):
924 """Return the side which the handle appears on."""
925 return self
._handleSide
927 def SetLeftSidePen(self
, pen
):
928 """Set the colour for drawing the left side of the division."""
929 self
._leftSidePen
= pen
931 def SetTopSidePen(self
, pen
):
932 """Set the colour for drawing the top side of the division."""
933 self
._topSidePen
= pen
935 def GetLeftSidePen(self
):
936 """Return the pen used for drawing the left side of the division."""
937 return self
._leftSidePen
939 def GetTopSidePen(self
):
940 """Return the pen used for drawing the top side of the division."""
941 return self
._topSidePen
943 def GetLeftSideColour(self
):
944 """Return the colour used for drawing the left side of the division."""
945 return self
._leftSideColour
947 def GetTopSideColour(self
):
948 """Return the colour used for drawing the top side of the division."""
949 return self
._topSideColour
951 def SetLeftSideColour(self
, colour
):
952 """Set the colour for drawing the left side of the division."""
953 self
._leftSideColour
= colour
955 def SetTopSideColour(self
, colour
):
956 """Set the colour for drawing the top side of the division."""
957 self
._topSideColour
= colour
959 def GetLeftSideStyle(self
):
960 """Return the style used for the left side of the division."""
961 return self
._leftSideStyle
963 def GetTopSideStyle(self
):
964 """Return the style used for the top side of the division."""
965 return self
._topSideStyle
967 def SetLeftSideStyle(self
, style
):
968 self
._leftSideStyle
= style
970 def SetTopSideStyle(self
, style
):
971 self
._lefttopStyle
= style
973 def OnDraw(self
, dc
):
974 dc
.SetBrush(wx
.TRANSPARENT_BRUSH
)
975 dc
.SetBackgroundMode(wx
.TRANSPARENT
)
977 x1
= self
.GetX()-self
.GetWidth() / 2
978 y1
= self
.GetY()-self
.GetHeight() / 2
979 x2
= self
.GetX() + self
.GetWidth() / 2
980 y2
= self
.GetY() + self
.GetHeight() / 2
982 # Should subtract 1 pixel if drawing under Windows
983 if sys
.platform
[:3]=="win":
987 dc
.SetPen(self
._leftSidePen
)
988 dc
.DrawLine(x1
, y2
, x1
, y1
)
991 dc
.SetPen(self
._topSidePen
)
992 dc
.DrawLine(x1
, y1
, x2
, y1
)
994 # For testing purposes, draw a rectangle so we know
995 # how big the division is.
996 #dc.SetBrush(wx.RED_BRUSH)
997 #dc.DrawRectangle(x1, y1, self.GetWidth(), self.GetHeight())
999 def OnDrawContents(self
, dc
):
1000 CompositeShape
.OnDrawContents(self
, dc
)
1002 def OnMovePre(self
, dc
, x
, y
, oldx
, oldy
, display
= True):
1005 for object in self
._children
:
1007 object.Move(dc
, object.GetX() + diffX
, object.GetY() + diffY
, display
)
1010 def OnDragLeft(self
, draw
, x
, y
, keys
= 0, attachment
= 0):
1011 if self
._sensitivity
& OP_DRAG_LEFT
!= OP_DRAG_LEFT
:
1013 hit
= self
._parent
.HitTest(x
, y
)
1015 attachment
, dist
= hit
1016 self
._parent
.GetEventHandler().OnDragLeft(draw
, x
, y
, keys
, attachment
)
1018 Shape
.OnDragLeft(self
, draw
, x
, y
, keys
, attachment
)
1020 def OnBeginDragLeft(self
, x
, y
, keys
= 0, attachment
= 0):
1021 if self
._sensitivity
& OP_DRAG_LEFT
!= OP_DRAG_LEFT
:
1023 hit
= self
._parent
.HitTest(x
, y
)
1025 attachment
, dist
= hit
1026 self
._parent
.GetEventHandler().OnBeginDragLeft(x
, y
, keys
, attachment
)
1028 Shape
.OnBeginDragLeft(x
, y
, keys
, attachment
)
1030 def OnEndDragLeft(self
, x
, y
, keys
= 0, attachment
= 0):
1031 if self
._canvas
.HasCapture():
1032 self
._canvas
.ReleaseMouse()
1033 if self
._sensitivity
& OP_DRAG_LEFT
!= OP_DRAG_LEFT
:
1035 hit
= self
._parent
.HitTest(x
, y
)
1037 attachment
, dist
= hit
1038 self
._parent
.GetEventHandler().OnEndDragLeft(x
, y
, keys
, attachment
)
1041 dc
= wx
.ClientDC(self
.GetCanvas())
1042 self
.GetCanvas().PrepareDC(dc
)
1044 dc
.SetLogicalFunction(wx
.COPY
)
1046 self
._xpos
, self
._ypos
= self
._canvas
.Snap(self
._xpos
, self
._ypos
)
1047 self
.GetEventHandler().OnMovePre(dc
, x
, y
, self
._oldX
, self
._oldY
)
1049 self
.ResetControlPoints()
1052 self
.GetEventHandler().OnDrawControlPoints(dc
)
1054 if self
._canvas
and not self
._canvas
.GetQuickEditMode():
1055 self
._canvas
.Redraw(dc
)
1057 def SetSize(self
, w
, h
, recursive
= True):
1060 RectangleShape
.SetSize(self
, w
, h
, recursive
)
1062 def CalculateSize(self
):
1066 def OnRightClick(self
, x
, y
, keys
= 0, attachment
= 0):
1068 self
.PopupMenu(x
, y
)
1071 hit
= self
._parent
.HitTest(x
, y
)
1073 attachment
, dist
= hit
1074 self
._parent
.GetEventHandler().OnRightClick(x
, y
, keys
, attachment
)
1076 # Divide wx.HORIZONTALly or wx.VERTICALly
1077 def Divide(self
, direction
):
1078 """Divide this division into two further divisions,
1079 horizontally (direction is wxHORIZONTAL) or
1080 vertically (direction is wxVERTICAL).
1082 # Calculate existing top-left, bottom-right
1083 x1
= self
.GetX()-self
.GetWidth() / 2
1084 y1
= self
.GetY()-self
.GetHeight() / 2
1086 compositeParent
= self
.GetParent()
1087 oldWidth
= self
.GetWidth()
1088 oldHeight
= self
.GetHeight()
1092 dc
= wx
.ClientDC(self
.GetCanvas())
1093 self
.GetCanvas().PrepareDC(dc
)
1095 if direction
== wx
.VERTICAL
:
1096 # Dividing vertically means notionally putting a horizontal
1098 # Break existing piece into two.
1099 newXPos1
= self
.GetX()
1100 newYPos1
= y1
+ self
.GetHeight() / 4
1101 newXPos2
= self
.GetX()
1102 newYPos2
= y1
+ 3 * self
.GetHeight() / 4
1103 newDivision
= compositeParent
.OnCreateDivision()
1104 newDivision
.Show(True)
1108 # Anything adjoining the bottom of this division now adjoins the
1109 # bottom of the new division.
1110 for obj
in compositeParent
.GetDivisions():
1111 if obj
.GetTopSide() == self
:
1112 obj
.SetTopSide(newDivision
)
1114 newDivision
.SetTopSide(self
)
1115 newDivision
.SetBottomSide(self
._bottomSide
)
1116 newDivision
.SetLeftSide(self
._leftSide
)
1117 newDivision
.SetRightSide(self
._rightSide
)
1118 self
._bottomSide
= newDivision
1120 compositeParent
.GetDivisions().append(newDivision
)
1122 # CHANGE: Need to insert this division at start of divisions in the
1123 # object list, because e.g.:
1125 # 2) Add contained object
1127 # Division is now receiving mouse events _before_ the contained
1128 # object, because it was added last (on top of all others)
1130 # Add after the image that visualizes the container
1131 compositeParent
.AddChild(newDivision
, compositeParent
.FindContainerImage())
1133 self
._handleSide
= DIVISION_SIDE_BOTTOM
1134 newDivision
.SetHandleSide(DIVISION_SIDE_TOP
)
1136 self
.SetSize(oldWidth
, oldHeight
/ 2)
1137 self
.Move(dc
, newXPos1
, newYPos1
)
1139 newDivision
.SetSize(oldWidth
, oldHeight
/ 2)
1140 newDivision
.Move(dc
, newXPos2
, newYPos2
)
1142 # Dividing horizontally means notionally putting a vertical line
1144 # Break existing piece into two.
1145 newXPos1
= x1
+ self
.GetWidth() / 4
1146 newYPos1
= self
.GetY()
1147 newXPos2
= x1
+ 3 * self
.GetWidth() / 4
1148 newYPos2
= self
.GetY()
1149 newDivision
= compositeParent
.OnCreateDivision()
1150 newDivision
.Show(True)
1154 # Anything adjoining the left of this division now adjoins the
1155 # left of the new division.
1156 for obj
in compositeParent
.GetDivisions():
1157 if obj
.GetLeftSide() == self
:
1158 obj
.SetLeftSide(newDivision
)
1160 newDivision
.SetTopSide(self
._topSide
)
1161 newDivision
.SetBottomSide(self
._bottomSide
)
1162 newDivision
.SetLeftSide(self
)
1163 newDivision
.SetRightSide(self
._rightSide
)
1164 self
._rightSide
= newDivision
1166 compositeParent
.GetDivisions().append(newDivision
)
1167 compositeParent
.AddChild(newDivision
, compositeParent
.FindContainerImage())
1169 self
._handleSide
= DIVISION_SIDE_RIGHT
1170 newDivision
.SetHandleSide(DIVISION_SIDE_LEFT
)
1172 self
.SetSize(oldWidth
/ 2, oldHeight
)
1173 self
.Move(dc
, newXPos1
, newYPos1
)
1175 newDivision
.SetSize(oldWidth
/ 2, oldHeight
)
1176 newDivision
.Move(dc
, newXPos2
, newYPos2
)
1178 if compositeParent
.Selected():
1179 compositeParent
.DeleteControlPoints(dc
)
1180 compositeParent
.MakeControlPoints()
1181 compositeParent
.MakeMandatoryControlPoints()
1183 compositeParent
.Draw(dc
)
1186 def MakeControlPoints(self
):
1187 self
.MakeMandatoryControlPoints()
1189 def MakeMandatoryControlPoints(self
):
1190 maxX
, maxY
= self
.GetBoundingBoxMax()
1194 if self
._handleSide
== DIVISION_SIDE_LEFT
:
1196 direction
= CONTROL_POINT_HORIZONTAL
1197 elif self
._handleSide
== DIVISION_SIDE_TOP
:
1199 direction
= CONTROL_POINT_VERTICAL
1200 elif self
._handleSide
== DIVISION_SIDE_RIGHT
:
1202 direction
= CONTROL_POINT_HORIZONTAL
1203 elif self
._handleSide
== DIVISION_SIDE_BOTTOM
:
1205 direction
= CONTROL_POINT_VERTICAL
1207 if self
._handleSide
!= DIVISION_SIDE_NONE
:
1208 control
= DivisionControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, x
, y
, direction
)
1209 self
._canvas
.AddShape(control
)
1210 self
._controlPoints
.append(control
)
1212 def ResetControlPoints(self
):
1213 self
.ResetMandatoryControlPoints()
1215 def ResetMandatoryControlPoints(self
):
1216 if not self
._controlPoints
:
1219 maxX
, maxY
= self
.GetBoundingBoxMax()
1221 node
= self
._controlPoints
[0]
1223 if self
._handleSide
== DIVISION_SIDE_LEFT
and node
:
1224 node
._xoffset
=-maxX
/ 2
1227 if self
._handleSide
== DIVISION_SIDE_TOP
and node
:
1229 node
._yoffset
=-maxY
/ 2
1231 if self
._handleSide
== DIVISION_SIDE_RIGHT
and node
:
1232 node
._xoffset
= maxX
/ 2
1235 if self
._handleSide
== DIVISION_SIDE_BOTTOM
and node
:
1237 node
._yoffset
= maxY
/ 2
1239 def AdjustLeft(self
, left
, test
):
1242 Returns FALSE if it's not physically possible to adjust it to
1245 x2
= self
.GetX() + self
.GetWidth() / 2
1254 newX
= left
+ newW
/ 2
1255 self
.SetSize(newW
, self
.GetHeight())
1257 dc
= wx
.ClientDC(self
.GetCanvas())
1258 self
.GetCanvas().PrepareDC(dc
)
1260 self
.Move(dc
, newX
, self
.GetY())
1263 def AdjustTop(self
, top
, test
):
1266 Returns FALSE if it's not physically possible to adjust it to
1269 y2
= self
.GetY() + self
.GetHeight() / 2
1278 newY
= top
+ newH
/ 2
1279 self
.SetSize(self
.GetWidth(), newH
)
1281 dc
= wx
.ClientDC(self
.GetCanvas())
1282 self
.GetCanvas().PrepareDC(dc
)
1284 self
.Move(dc
, self
.GetX(), newY
)
1287 def AdjustRight(self
, right
, test
):
1290 Returns FALSE if it's not physically possible to adjust it to
1293 x1
= self
.GetX()-self
.GetWidth() / 2
1302 newX
= x1
+ newW
/ 2
1303 self
.SetSize(newW
, self
.GetHeight())
1305 dc
= wx
.ClientDC(self
.GetCanvas())
1306 self
.GetCanvas().PrepareDC(dc
)
1308 self
.Move(dc
, newX
, self
.GetY())
1311 def AdjustTop(self
, top
, test
):
1314 Returns FALSE if it's not physically possible to adjust it to
1317 y1
= self
.GetY()-self
.GetHeight() / 2
1326 newY
= y1
+ newH
/ 2
1327 self
.SetSize(self
.GetWidth(), newH
)
1329 dc
= wx
.ClientDC(self
.GetCanvas())
1330 self
.GetCanvas().PrepareDC(dc
)
1332 self
.Move(dc
, self
.GetX(), newY
)
1335 # Resize adjoining divisions.
1337 # Behaviour should be as follows:
1338 # If right edge moves, find all objects whose left edge
1339 # adjoins this object, and move left edge accordingly.
1340 # If left..., move ... right.
1341 # If top..., move ... bottom.
1342 # If bottom..., move top.
1343 # If size goes to zero or end position is other side of start position,
1344 # resize to original size and return.
1346 def ResizeAdjoining(self
, side
, newPos
, test
):
1347 """Resize adjoining divisions at the given side.
1349 If test is TRUE, just see whether it's possible for each adjoining
1350 region, returning FALSE if it's not.
1354 * DIVISION_SIDE_NONE
1355 * DIVISION_SIDE_LEFT
1357 * DIVISION_SIDE_RIGHT
1358 * DIVISION_SIDE_BOTTOM
1360 divisionParent
= self
.GetParent()
1361 for division
in divisionParent
.GetDivisions():
1362 if side
== DIVISION_SIDE_LEFT
:
1363 if division
._rightSide
== self
:
1364 success
= division
.AdjustRight(newPos
, test
)
1365 if not success
and test
:
1367 elif side
== DIVISION_SIDE_TOP
:
1368 if division
._bottomSide
== self
:
1369 success
= division
.AdjustBottom(newPos
, test
)
1370 if not success
and test
:
1372 elif side
== DIVISION_SIDE_RIGHT
:
1373 if division
._leftSide
== self
:
1374 success
= division
.AdjustLeft(newPos
, test
)
1375 if not success
and test
:
1377 elif side
== DIVISION_SIDE_BOTTOM
:
1378 if division
._topSide
== self
:
1379 success
= division
.AdjustTop(newPos
, test
)
1380 if not success
and test
:
1384 def EditEdge(self
, side
):
1385 print "EditEdge() not implemented."
1387 def PopupMenu(self
, x
, y
):
1388 menu
= PopupDivisionMenu()
1389 menu
.SetClientData(self
)
1391 menu
.Enable(DIVISION_MENU_EDIT_LEFT_EDGE
, True)
1393 menu
.Enable(DIVISION_MENU_EDIT_LEFT_EDGE
, False)
1395 menu
.Enable(DIVISION_MENU_EDIT_TOP_EDGE
, True)
1397 menu
.Enable(DIVISION_MENU_EDIT_TOP_EDGE
, False)
1399 x1
, y1
= self
._canvas
.GetViewStart()
1400 unit_x
, unit_y
= self
._canvas
.GetScrollPixelsPerUnit()
1402 dc
= wx
.ClientDC(self
.GetCanvas())
1403 self
.GetCanvas().PrepareDC(dc
)
1405 mouse_x
= dc
.LogicalToDeviceX(x
-x1
* unit_x
)
1406 mouse_y
= dc
.LogicalToDeviceY(y
-y1
* unit_y
)
1408 self
._canvas
.PopupMenu(menu
, (mouse_x
, mouse_y
))