X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/ec873c943d71f0d5f13e3398557071448cda6c23..a4027e74873007e3430af3bd77019bcab76f6c04:/wxPython/wx/lib/ogl/_composit.py diff --git a/wxPython/wx/lib/ogl/_composit.py b/wxPython/wx/lib/ogl/_composit.py deleted file mode 100644 index d07c3993a5..0000000000 --- a/wxPython/wx/lib/ogl/_composit.py +++ /dev/null @@ -1,1442 +0,0 @@ -# -*- coding: iso-8859-1 -*- -#---------------------------------------------------------------------------- -# Name: composit.py -# Purpose: Composite class -# -# Author: Pierre Hjälm (from C++ original by Julian Smart) -# -# Created: 2004-05-08 -# RCS-ID: $Id$ -# Copyright: (c) 2004 Pierre Hjälm - 1998 Julian Smart -# Licence: wxWindows license -#---------------------------------------------------------------------------- - -import sys -import wx - -from _basic import RectangleShape, Shape, ControlPoint -from _oglmisc import * - -KEY_SHIFT, KEY_CTRL = 1, 2 - -_objectStartX = 0.0 -_objectStartY = 0.0 - -CONSTRAINT_CENTRED_VERTICALLY = 1 -CONSTRAINT_CENTRED_HORIZONTALLY = 2 -CONSTRAINT_CENTRED_BOTH = 3 -CONSTRAINT_LEFT_OF = 4 -CONSTRAINT_RIGHT_OF = 5 -CONSTRAINT_ABOVE = 6 -CONSTRAINT_BELOW = 7 -CONSTRAINT_ALIGNED_TOP = 8 -CONSTRAINT_ALIGNED_BOTTOM = 9 -CONSTRAINT_ALIGNED_LEFT = 10 -CONSTRAINT_ALIGNED_RIGHT = 11 - -# Like aligned, but with the objects centred on the respective edge -# of the reference object. -CONSTRAINT_MIDALIGNED_TOP = 12 -CONSTRAINT_MIDALIGNED_BOTTOM = 13 -CONSTRAINT_MIDALIGNED_LEFT = 14 -CONSTRAINT_MIDALIGNED_RIGHT = 15 - - -# Backwards compatibility names. These should be removed eventually. -gyCONSTRAINT_CENTRED_VERTICALLY = CONSTRAINT_CENTRED_VERTICALLY -gyCONSTRAINT_CENTRED_HORIZONTALLY = CONSTRAINT_CENTRED_HORIZONTALLY -gyCONSTRAINT_CENTRED_BOTH = CONSTRAINT_CENTRED_BOTH -gyCONSTRAINT_LEFT_OF = CONSTRAINT_LEFT_OF -gyCONSTRAINT_RIGHT_OF = CONSTRAINT_RIGHT_OF -gyCONSTRAINT_ABOVE = CONSTRAINT_ABOVE -gyCONSTRAINT_BELOW = CONSTRAINT_BELOW -gyCONSTRAINT_ALIGNED_TOP = CONSTRAINT_ALIGNED_TOP -gyCONSTRAINT_ALIGNED_BOTTOM = CONSTRAINT_ALIGNED_BOTTOM -gyCONSTRAINT_ALIGNED_LEFT = CONSTRAINT_ALIGNED_LEFT -gyCONSTRAINT_ALIGNED_RIGHT = CONSTRAINT_ALIGNED_RIGHT -gyCONSTRAINT_MIDALIGNED_TOP = CONSTRAINT_MIDALIGNED_TOP -gyCONSTRAINT_MIDALIGNED_BOTTOM = CONSTRAINT_MIDALIGNED_BOTTOM -gyCONSTRAINT_MIDALIGNED_LEFT = CONSTRAINT_MIDALIGNED_LEFT -gyCONSTRAINT_MIDALIGNED_RIGHT = CONSTRAINT_MIDALIGNED_RIGHT - - - -class ConstraintType(object): - def __init__(self, theType, theName, thePhrase): - self._type = theType - self._name = theName - self._phrase = thePhrase - - - -ConstraintTypes = [ - [CONSTRAINT_CENTRED_VERTICALLY, - ConstraintType(CONSTRAINT_CENTRED_VERTICALLY, "Centre vertically", "centred vertically w.r.t.")], - - [CONSTRAINT_CENTRED_HORIZONTALLY, - ConstraintType(CONSTRAINT_CENTRED_HORIZONTALLY, "Centre horizontally", "centred horizontally w.r.t.")], - - [CONSTRAINT_CENTRED_BOTH, - ConstraintType(CONSTRAINT_CENTRED_BOTH, "Centre", "centred w.r.t.")], - - [CONSTRAINT_LEFT_OF, - ConstraintType(CONSTRAINT_LEFT_OF, "Left of", "left of")], - - [CONSTRAINT_RIGHT_OF, - ConstraintType(CONSTRAINT_RIGHT_OF, "Right of", "right of")], - - [CONSTRAINT_ABOVE, - ConstraintType(CONSTRAINT_ABOVE, "Above", "above")], - - [CONSTRAINT_BELOW, - ConstraintType(CONSTRAINT_BELOW, "Below", "below")], - - # Alignment - [CONSTRAINT_ALIGNED_TOP, - ConstraintType(CONSTRAINT_ALIGNED_TOP, "Top-aligned", "aligned to the top of")], - - [CONSTRAINT_ALIGNED_BOTTOM, - ConstraintType(CONSTRAINT_ALIGNED_BOTTOM, "Bottom-aligned", "aligned to the bottom of")], - - [CONSTRAINT_ALIGNED_LEFT, - ConstraintType(CONSTRAINT_ALIGNED_LEFT, "Left-aligned", "aligned to the left of")], - - [CONSTRAINT_ALIGNED_RIGHT, - ConstraintType(CONSTRAINT_ALIGNED_RIGHT, "Right-aligned", "aligned to the right of")], - - # Mid-alignment - [CONSTRAINT_MIDALIGNED_TOP, - ConstraintType(CONSTRAINT_MIDALIGNED_TOP, "Top-midaligned", "centred on the top of")], - - [CONSTRAINT_MIDALIGNED_BOTTOM, - ConstraintType(CONSTRAINT_MIDALIGNED_BOTTOM, "Bottom-midaligned", "centred on the bottom of")], - - [CONSTRAINT_MIDALIGNED_LEFT, - ConstraintType(CONSTRAINT_MIDALIGNED_LEFT, "Left-midaligned", "centred on the left of")], - - [CONSTRAINT_MIDALIGNED_RIGHT, - ConstraintType(CONSTRAINT_MIDALIGNED_RIGHT, "Right-midaligned", "centred on the right of")] - ] - - - - -class Constraint(object): - """A Constraint object helps specify how child shapes are laid out with - respect to siblings and parents. - - Derived from: - wxObject - """ - def __init__(self, type, constraining, constrained): - self._xSpacing = 0.0 - self._ySpacing = 0.0 - - self._constraintType = type - self._constrainingObject = constraining - - self._constraintId = 0 - self._constraintName = "noname" - - self._constrainedObjects = constrained[:] - - def __repr__(self): - return "<%s.%s>" % (self.__class__.__module__, self.__class__.__name__) - - def SetSpacing(self, x, y): - """Sets the horizontal and vertical spacing for the constraint.""" - self._xSpacing = x - self._ySpacing = y - - def Equals(self, a, b): - """Return TRUE if x and y are approximately equal (for the purposes - of evaluating the constraint). - """ - marg = 0.5 - - return b <= a + marg and b >= a - marg - - def Evaluate(self): - """Evaluate this constraint and return TRUE if anything changed.""" - maxWidth, maxHeight = self._constrainingObject.GetBoundingBoxMax() - minWidth, minHeight = self._constrainingObject.GetBoundingBoxMin() - x = self._constrainingObject.GetX() - y = self._constrainingObject.GetY() - - dc = wx.ClientDC(self._constrainingObject.GetCanvas()) - self._constrainingObject.GetCanvas().PrepareDC(dc) - - if self._constraintType == CONSTRAINT_CENTRED_VERTICALLY: - n = len(self._constrainedObjects) - totalObjectHeight = 0.0 - for constrainedObject in self._constrainedObjects: - width2, height2 = constrainedObject.GetBoundingBoxMax() - totalObjectHeight += height2 - - # Check if within the constraining object... - if totalObjectHeight + (n + 1) * self._ySpacing <= minHeight: - spacingY = (minHeight - totalObjectHeight) / (n + 1.0) - startY = y - minHeight / 2.0 - else: # Otherwise, use default spacing - spacingY = self._ySpacing - startY = y - (totalObjectHeight + (n + 1) * spacingY) / 2.0 - - # Now position the objects - changed = False - for constrainedObject in self._constrainedObjects: - width2, height2 = constrainedObject.GetBoundingBoxMax() - startY += spacingY + height2 / 2.0 - if not self.Equals(startY, constrainedObject.GetY()): - constrainedObject.Move(dc, constrainedObject.GetX(), startY, False) - changed = True - startY += height2 / 2.0 - return changed - elif self._constraintType == CONSTRAINT_CENTRED_HORIZONTALLY: - n = len(self._constrainedObjects) - totalObjectWidth = 0.0 - for constrainedObject in self._constrainedObjects: - width2, height2 = constrainedObject.GetBoundingBoxMax() - totalObjectWidth += width2 - - # Check if within the constraining object... - if totalObjectWidth + (n + 1) * self._xSpacing <= minWidth: - spacingX = (minWidth - totalObjectWidth) / (n + 1.0) - startX = x - minWidth / 2.0 - else: # Otherwise, use default spacing - spacingX = self._xSpacing - startX = x - (totalObjectWidth + (n + 1) * spacingX) / 2.0 - - # Now position the objects - changed = False - for constrainedObject in self._constrainedObjects: - width2, height2 = constrainedObject.GetBoundingBoxMax() - startX += spacingX + width2 / 2.0 - if not self.Equals(startX, constrainedObject.GetX()): - constrainedObject.Move(dc, startX, constrainedObject.GetY(), False) - changed = True - startX += width2 / 2.0 - return changed - elif self._constraintType == CONSTRAINT_CENTRED_BOTH: - n = len(self._constrainedObjects) - totalObjectWidth = 0.0 - totalObjectHeight = 0.0 - - for constrainedObject in self._constrainedObjects: - width2, height2 = constrainedObject.GetBoundingBoxMax() - totalObjectWidth += width2 - totalObjectHeight += height2 - - # Check if within the constraining object... - if totalObjectHeight + (n + 1) * self._xSpacing <= minWidth: - spacingX = (minWidth - totalObjectWidth) / (n + 1.0) - startX = x - minWidth / 2.0 - else: # Otherwise, use default spacing - spacingX = self._xSpacing - startX = x - (totalObjectWidth + (n + 1) * spacingX) / 2.0 - - # Check if within the constraining object... - if totalObjectHeight + (n + 1) * self._ySpacing <= minHeight: - spacingY = (minHeight - totalObjectHeight) / (n + 1.0) - startY = y - minHeight / 2.0 - else: # Otherwise, use default spacing - spacingY = self._ySpacing - startY = y - (totalObjectHeight + (n + 1) * spacingY) / 2.0 - - # Now position the objects - changed = False - for constrainedObject in self._constrainedObjects: - width2, height2 = constrainedObject.GetBoundingBoxMax() - startX += spacingX + width2 / 2.0 - startY += spacingY + height2 / 2.0 - - if not self.Equals(startX, constrainedObject.GetX()) or not self.Equals(startY, constrainedObject.GetY()): - constrainedObject.Move(dc, startX, startY, False) - changed = True - - startX += width2 / 2.0 - startY += height2 / 2.0 - return changed - elif self._constraintType == CONSTRAINT_LEFT_OF: - changed = False - for constrainedObject in self._constrainedObjects: - width2, height2 = constrainedObject.GetBoundingBoxMax() - - x3 = x - minWidth / 2.0 - width2 / 2.0 - self._xSpacing - if not self.Equals(x3, constrainedObject.GetX()): - changed = True - constrainedObject.Move(dc, x3, constrainedObject.GetY(), False) - return changed - elif self._constraintType == CONSTRAINT_RIGHT_OF: - changed = False - - for constrainedObject in self._constrainedObjects: - width2, height2 = constrainedObject.GetBoundingBoxMax() - x3 = x + minWidth / 2.0 + width2 / 2.0 + self._xSpacing - if not self.Equals(x3, constrainedObject.GetX()): - constrainedObject.Move(dc, x3, constrainedObject.GetY(), False) - changed = True - return changed - elif self._constraintType == CONSTRAINT_ABOVE: - changed = False - - for constrainedObject in self._constrainedObjects: - width2, height2 = constrainedObject.GetBoundingBoxMax() - - y3 = y - minHeight / 2.0 - height2 / 2.0 - self._ySpacing - if not self.Equals(y3, constrainedObject.GetY()): - changed = True - constrainedObject.Move(dc, constrainedObject.GetX(), y3, False) - return changed - elif self._constraintType == CONSTRAINT_BELOW: - changed = False - - for constrainedObject in self._constrainedObjects: - width2, height2 = constrainedObject.GetBoundingBoxMax() - - y3 = y + minHeight / 2.0 + height2 / 2.0 + self._ySpacing - if not self.Equals(y3, constrainedObject.GetY()): - changed = True - constrainedObject.Move(dc, constrainedObject.GetX(), y3, False) - return changed - elif self._constraintType == CONSTRAINT_ALIGNED_LEFT: - changed = False - for constrainedObject in self._constrainedObjects: - width2, height2 = constrainedObject.GetBoundingBoxMax() - x3 = x - minWidth / 2.0 + width2 / 2.0 + self._xSpacing - if not self.Equals(x3, constrainedObject.GetX()): - changed = True - constrainedObject.Move(dc, x3, constrainedObject.GetY(), False) - return changed - elif self._constraintType == CONSTRAINT_ALIGNED_RIGHT: - changed = False - for constrainedObject in self._constrainedObjects: - width2, height2 = constrainedObject.GetBoundingBoxMax() - x3 = x + minWidth / 2.0 - width2 / 2.0 - self._xSpacing - if not self.Equals(x3, constrainedObject.GetX()): - changed = True - constrainedObject.Move(dc, x3, constrainedObject.GetY(), False) - return changed - elif self._constraintType == CONSTRAINT_ALIGNED_TOP: - changed = False - for constrainedObject in self._constrainedObjects: - width2, height2 = constrainedObject.GetBoundingBoxMax() - y3 = y - minHeight / 2.0 + height2 / 2.0 + self._ySpacing - if not self.Equals(y3, constrainedObject.GetY()): - changed = True - constrainedObject.Move(dc, constrainedObject.GetX(), y3, False) - return changed - elif self._constraintType == CONSTRAINT_ALIGNED_BOTTOM: - changed = False - for constrainedObject in self._constrainedObjects: - width2, height2 = constrainedObject.GetBoundingBoxMax() - y3 = y + minHeight / 2.0 - height2 / 2.0 - self._ySpacing - if not self.Equals(y3, constrainedObject.GetY()): - changed = True - constrainedObject.Move(dc, constrainedObject.GetX(), y3, False) - return changed - elif self._constraintType == CONSTRAINT_MIDALIGNED_LEFT: - changed = False - for constrainedObject in self._constrainedObjects: - x3 = x - minWidth / 2.0 - if not self.Equals(x3, constrainedObject.GetX()): - changed = True - constrainedObject.Move(dc, x3, constrainedObject.GetY(), False) - return changed - elif self._constraintType == CONSTRAINT_MIDALIGNED_RIGHT: - changed = False - for constrainedObject in self._constrainedObjects: - x3 = x + minWidth / 2.0 - if not self.Equals(x3, constrainedObject.GetX()): - changed = True - constrainedObject.Move(dc, x3, constrainedObject.GetY(), False) - return changed - elif self._constraintType == CONSTRAINT_MIDALIGNED_TOP: - changed = False - for constrainedObject in self._constrainedObjects: - y3 = y - minHeight / 2.0 - if not self.Equals(y3, constrainedObject.GetY()): - changed = True - constrainedObject.Move(dc, constrainedObject.GetX(), y3, False) - return changed - elif self._constraintType == CONSTRAINT_MIDALIGNED_BOTTOM: - changed = False - for constrainedObject in self._constrainedObjects: - y3 = y + minHeight / 2.0 - if not self.Equals(y3, constrainedObject.GetY()): - changed = True - constrainedObject.Move(dc, constrainedObject.GetX(), y3, False) - return changed - - return False - -OGLConstraint = wx._core._deprecated(Constraint, - "The OGLConstraint name is deprecated, use `ogl.Constraint` instead.") - - -class CompositeShape(RectangleShape): - """This is an object with a list of child objects, and a list of size - and positioning constraints between the children. - - Derived from: - wxRectangleShape - """ - def __init__(self): - RectangleShape.__init__(self, 100.0, 100.0) - - self._oldX = self._xpos - self._oldY = self._ypos - - self._constraints = [] - self._divisions = [] # In case it's a container - - def OnDraw(self, dc): - x1 = self._xpos - self._width / 2.0 - y1 = self._ypos - self._height / 2.0 - - if self._shadowMode != SHADOW_NONE: - if self._shadowBrush: - dc.SetBrush(self._shadowBrush) - dc.SetPen(wx.Pen(wx.WHITE, 1, wx.TRANSPARENT)) - - if self._cornerRadius: - dc.DrawRoundedRectangle(x1 + self._shadowOffsetX, y1 + self._shadowOffsetY, self._width, self._height, self._cornerRadius) - else: - dc.DrawRectangle(x1 + self._shadowOffsetX, y1 + self._shadowOffsetY, self._width, self._height) - - # For debug purposes /pi - #dc.DrawRectangle(x1, y1, self._width, self._height) - - def OnDrawContents(self, dc): - for object in self._children: - object.Draw(dc) - object.DrawLinks(dc) - - Shape.OnDrawContents(self, dc) - - def OnMovePre(self, dc, x, y, old_x, old_y, display = True): - diffX = x - old_x - diffY = y - old_y - - for object in self._children: - object.Erase(dc) - object.Move(dc, object.GetX() + diffX, object.GetY() + diffY, display) - - return True - - def OnErase(self, dc): - RectangleShape.OnErase(self, dc) - for object in self._children: - object.Erase(dc) - - def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0): - xx, yy = self._canvas.Snap(x, y) - offsetX = xx - _objectStartX - offsetY = yy - _objectStartY - - dc = wx.ClientDC(self.GetCanvas()) - self.GetCanvas().PrepareDC(dc) - - dc.SetLogicalFunction(OGLRBLF) - dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.DOT) - dc.SetPen(dottedPen) - dc.SetBrush(wx.TRANSPARENT_BRUSH) - - self.GetEventHandler().OnDrawOutline(dc, self.GetX() + offsetX, self.GetY() + offsetY, self.GetWidth(), self.GetHeight()) - - def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0): - global _objectStartX, _objectStartY - - _objectStartX = x - _objectStartY = y - - dc = wx.ClientDC(self.GetCanvas()) - self.GetCanvas().PrepareDC(dc) - - #self.Erase(dc) - - dc.SetLogicalFunction(OGLRBLF) - dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.DOT) - dc.SetPen(dottedPen) - dc.SetBrush(wx.TRANSPARENT_BRUSH) - self._canvas.CaptureMouse() - - xx, yy = self._canvas.Snap(x, y) - offsetX = xx - _objectStartX - offsetY = yy - _objectStartY - - self.GetEventHandler().OnDrawOutline(dc, self.GetX() + offsetX, self.GetY() + offsetY, self.GetWidth(), self.GetHeight()) - - def OnEndDragLeft(self, x, y, keys = 0, attachment = 0): - dc = wx.ClientDC(self.GetCanvas()) - self.GetCanvas().PrepareDC(dc) - - if self._canvas.HasCapture(): - self._canvas.ReleaseMouse() - - if not self._draggable: - if self._parent: - self._parent.GetEventHandler().OnEndDragLeft(x, y, keys, 0) - return - - self.Erase(dc) - - dc.SetLogicalFunction(wx.COPY) - - xx, yy = self._canvas.Snap(x, y) - offsetX = xx - _objectStartX - offsetY = yy - _objectStartY - - self.Move(dc, self.GetX() + offsetX, self.GetY() + offsetY) - - if self._canvas and not self._canvas.GetQuickEditMode(): - self._canvas.Redraw(dc) - - def OnRightClick(self, x, y, keys = 0, attachment = 0): - # If we get a ctrl-right click, this means send the message to - # the division, so we can invoke a user interface for dealing - # with regions. - if keys & KEY_CTRL: - for division in self._divisions: - hit = division.HitTest(x, y) - if hit: - division.GetEventHandler().OnRightClick(x, y, keys, hit[0]) - break - - def SetSize(self, w, h, recursive = True): - self.SetAttachmentSize(w, h) - - xScale = float(w) / max(1, self.GetWidth()) - yScale = float(h) / max(1, self.GetHeight()) - - self._width = w - self._height = h - - if not recursive: - return - - dc = wx.ClientDC(self.GetCanvas()) - self.GetCanvas().PrepareDC(dc) - - for object in self._children: - # Scale the position first - newX = (object.GetX() - self.GetX()) * xScale + self.GetX() - newY = (object.GetY() - self.GetY()) * yScale + self.GetY() - object.Show(False) - object.Move(dc, newX, newY) - object.Show(True) - - # Now set the scaled size - xbound, ybound = object.GetBoundingBoxMax() - if not object.GetFixedWidth(): - xbound *= xScale - if not object.GetFixedHeight(): - ybound *= yScale - object.SetSize(xbound, ybound) - - self.SetDefaultRegionSize() - - def AddChild(self, child, addAfter = None): - """Adds a child shape to the composite. - - If addAfter is not None, the shape will be added after this shape. - """ - self._children.append(child) - child.SetParent(self) - if self._canvas: - # Ensure we add at the right position - if addAfter: - child.RemoveFromCanvas(self._canvas) - child.AddToCanvas(self._canvas, addAfter) - - def RemoveChild(self, child): - """Removes the child from the composite and any constraint - relationships, but does not delete the child. - """ - if child in self._children: - self._children.remove(child) - if child in self._divisions: - self._divisions.remove(child) - self.RemoveChildFromConstraints(child) - child.SetParent(None) - - def Delete(self): - """ - Fully disconnect this shape from parents, children, the - canvas, etc. - """ - for child in self.GetChildren(): - self.RemoveChild(child) - child.Delete() - RectangleShape.Delete(self) - self._constraints = [] - self._divisions = [] - - def DeleteConstraintsInvolvingChild(self, child): - """This function deletes constraints which mention the given child. - - Used when deleting a child from the composite. - """ - for constraint in self._constraints: - if constraint._constrainingObject == child or child in constraint._constrainedObjects: - self._constraints.remove(constraint) - - def RemoveChildFromConstraints(self, child): - for constraint in self._constraints: - if child in constraint._constrainedObjects: - constraint._constrainedObjects.remove(child) - if constraint._constrainingObject == child: - constraint._constrainingObject = None - - # Delete the constraint if no participants left - if not constraint._constrainingObject: - self._constraints.remove(constraint) - - def AddConstraint(self, constraint): - """Adds a constraint to the composite.""" - self._constraints.append(constraint) - if constraint._constraintId == 0: - constraint._constraintId = wx.NewId() - return constraint - - def AddSimpleConstraint(self, type, constraining, constrained): - """Add a constraint of the given type to the composite. - - constraining is the shape doing the constraining - constrained is a list of shapes being constrained - """ - constraint = Constraint(type, constraining, constrained) - if constraint._constraintId == 0: - constraint._constraintId = wx.NewId() - self._constraints.append(constraint) - return constraint - - def FindConstraint(self, cId): - """Finds the constraint with the given id. - - Returns a tuple of the constraint and the actual composite the - constraint was in, in case that composite was a descendant of - this composit. - - Returns None if not found. - """ - for constraint in self._constraints: - if constraint._constraintId == cId: - return constraint, self - - # If not found, try children - for child in self._children: - if isinstance(child, CompositeShape): - constraint = child.FindConstraint(cId) - if constraint: - return constraint[0], child - - return None - - def DeleteConstraint(self, constraint): - """Deletes constraint from composite.""" - self._constraints.remove(constraint) - - def CalculateSize(self): - """Calculates the size and position of the composite based on - child sizes and positions. - """ - maxX = -999999.9 - maxY = -999999.9 - minX = 999999.9 - minY = 999999.9 - - for child in self._children: - # Recalculate size of composite objects because may not conform - # to size it was set to - depends on the children. - if isinstance(child, CompositeShape): - child.CalculateSize() - - w, h = child.GetBoundingBoxMax() - if child.GetX() + w / 2.0 > maxX: - maxX = child.GetX() + w / 2.0 - if child.GetX() - w / 2.0 < minX: - minX = child.GetX() - w / 2.0 - if child.GetY() + h / 2.0 > maxY: - maxY = child.GetY() + h / 2.0 - if child.GetY() - h / 2.0 < minY: - minY = child.GetY() - h / 2.0 - - self._width = maxX - minX - self._height = maxY - minY - self._xpos = self._width / 2.0 + minX - self._ypos = self._height / 2.0 + minY - - def Recompute(self): - """Recomputes any constraints associated with the object. If FALSE is - returned, the constraints could not be satisfied (there was an - inconsistency). - """ - noIterations = 0 - changed = True - while changed and noIterations < 500: - changed = self.Constrain() - noIterations += 1 - - return not changed - - def Constrain(self): - self.CalculateSize() - - changed = False - for child in self._children: - if isinstance(child, CompositeShape) and child.Constrain(): - changed = True - - for constraint in self._constraints: - if constraint.Evaluate(): - changed = True - - return changed - - def MakeContainer(self): - """Makes this composite into a container by creating one child - DivisionShape. - """ - division = self.OnCreateDivision() - self._divisions.append(division) - self.AddChild(division) - - division.SetSize(self._width, self._height) - - dc = wx.ClientDC(self.GetCanvas()) - self.GetCanvas().PrepareDC(dc) - - division.Move(dc, self.GetX(), self.GetY()) - self.Recompute() - division.Show(True) - - def OnCreateDivision(self): - return DivisionShape() - - def FindContainerImage(self): - """Finds the image used to visualize a container. This is any child of - the composite that is not in the divisions list. - """ - for child in self._children: - if child in self._divisions: - return child - - return None - - def ContainsDivision(self, division): - """Returns TRUE if division is a descendant of this container.""" - if division in self._divisions: - return True - - for child in self._children: - if isinstance(child, CompositeShape): - return child.ContainsDivision(division) - - return False - - def GetDivisions(self): - """Return the list of divisions.""" - return self._divisions - - def GetConstraints(self): - """Return the list of constraints.""" - return self._constraints - - -# A division object is a composite with special properties, -# to be used for containment. It's a subdivision of a container. -# A containing node image consists of a composite with a main child shape -# such as rounded rectangle, plus a list of division objects. -# It needs to be a composite because a division contains pieces -# of diagram. -# NOTE a container has at least one wxDivisionShape for consistency. -# This can be subdivided, so it turns into two objects, then each of -# these can be subdivided, etc. - -DIVISION_SIDE_NONE =0 -DIVISION_SIDE_LEFT =1 -DIVISION_SIDE_TOP =2 -DIVISION_SIDE_RIGHT =3 -DIVISION_SIDE_BOTTOM =4 - -originalX = 0.0 -originalY = 0.0 -originalW = 0.0 -originalH = 0.0 - - - -class DivisionControlPoint(ControlPoint): - def __init__(self, the_canvas, object, size, the_xoffset, the_yoffset, the_type): - ControlPoint.__init__(self, the_canvas, object, size, the_xoffset, the_yoffset, the_type) - self.SetEraseObject(False) - - # Implement resizing of canvas object - def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0): - ControlPoint.OnDragLeft(self, draw, x, y, keys, attachment) - - def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0): - global originalX, originalY, originalW, originalH - - originalX = self._shape.GetX() - originalY = self._shape.GetY() - originalW = self._shape.GetWidth() - originalH = self._shape.GetHeight() - - ControlPoint.OnBeginDragLeft(self, x, y, keys, attachment) - - def OnEndDragLeft(self, x, y, keys = 0, attachment = 0): - ControlPoint.OnEndDragLeft(self, x, y, keys, attachment) - - dc = wx.ClientDC(self.GetCanvas()) - self.GetCanvas().PrepareDC(dc) - - division = self._shape - divisionParent = division.GetParent() - - # Need to check it's within the bounds of the parent composite - x1 = divisionParent.GetX() - divisionParent.GetWidth() / 2.0 - y1 = divisionParent.GetY() - divisionParent.GetHeight() / 2.0 - x2 = divisionParent.GetX() + divisionParent.GetWidth() / 2.0 - y2 = divisionParent.GetY() + divisionParent.GetHeight() / 2.0 - - # Need to check it has not made the division zero or negative - # width / height - dx1 = division.GetX() - division.GetWidth() / 2.0 - dy1 = division.GetY() - division.GetHeight() / 2.0 - dx2 = division.GetX() + division.GetWidth() / 2.0 - dy2 = division.GetY() + division.GetHeight() / 2.0 - - success = True - if division.GetHandleSide() == DIVISION_SIDE_LEFT: - if x <= x1 or x >= x2 or x >= dx2: - success = False - # Try it out first... - elif not division.ResizeAdjoining(DIVISION_SIDE_LEFT, x, True): - success = False - else: - division.ResizeAdjoining(DIVISION_SIDE_LEFT, x, False) - elif division.GetHandleSide() == DIVISION_SIDE_TOP: - if y <= y1 or y >= y2 or y >= dy2: - success = False - elif not division.ResizeAdjoining(DIVISION_SIDE_TOP, y, True): - success = False - else: - division.ResizingAdjoining(DIVISION_SIDE_TOP, y, False) - elif division.GetHandleSide() == DIVISION_SIDE_RIGHT: - if x <= x1 or x >= x2 or x <= dx1: - success = False - elif not division.ResizeAdjoining(DIVISION_SIDE_RIGHT, x, True): - success = False - else: - division.ResizeAdjoining(DIVISION_SIDE_RIGHT, x, False) - elif division.GetHandleSide() == DIVISION_SIDE_BOTTOM: - if y <= y1 or y >= y2 or y <= dy1: - success = False - elif not division.ResizeAdjoining(DIVISION_SIDE_BOTTOM, y, True): - success = False - else: - division.ResizeAdjoining(DIVISION_SIDE_BOTTOM, y, False) - - if not success: - division.SetSize(originalW, originalH) - division.Move(dc, originalX, originalY) - - divisionParent.Draw(dc) - division.GetEventHandler().OnDrawControlPoints(dc) - - - -DIVISION_MENU_SPLIT_HORIZONTALLY =1 -DIVISION_MENU_SPLIT_VERTICALLY =2 -DIVISION_MENU_EDIT_LEFT_EDGE =3 -DIVISION_MENU_EDIT_TOP_EDGE =4 -DIVISION_MENU_EDIT_RIGHT_EDGE =5 -DIVISION_MENU_EDIT_BOTTOM_EDGE =6 -DIVISION_MENU_DELETE_ALL =7 - - - -class PopupDivisionMenu(wx.Menu): - def __init__(self): - wx.Menu.__init__(self) - self.Append(DIVISION_MENU_SPLIT_HORIZONTALLY,"Split horizontally") - self.Append(DIVISION_MENU_SPLIT_VERTICALLY,"Split vertically") - self.AppendSeparator() - self.Append(DIVISION_MENU_EDIT_LEFT_EDGE,"Edit left edge") - self.Append(DIVISION_MENU_EDIT_TOP_EDGE,"Edit top edge") - - wx.EVT_MENU_RANGE(self, DIVISION_MENU_SPLIT_HORIZONTALLY, DIVISION_MENU_EDIT_BOTTOM_EDGE, self.OnMenu) - - def SetClientData(self, data): - self._clientData = data - - def GetClientData(self): - return self._clientData - - def OnMenu(self, event): - division = self.GetClientData() - if event.GetId() == DIVISION_MENU_SPLIT_HORIZONTALLY: - division.Divide(wx.HORIZONTAL) - elif event.GetId() == DIVISION_MENU_SPLIT_VERTICALLY: - division.Divide(wx.VERTICAL) - elif event.GetId() == DIVISION_MENU_EDIT_LEFT_EDGE: - division.EditEdge(DIVISION_SIDE_LEFT) - elif event.GetId() == DIVISION_MENU_EDIT_TOP_EDGE: - division.EditEdge(DIVISION_SIDE_TOP) - - - -class DivisionShape(CompositeShape): - """A division shape is like a composite in that it can contain further - objects, but is used exclusively to divide another shape into regions, - or divisions. A wxDivisionShape is never free-standing. - - Derived from: - wxCompositeShape - """ - def __init__(self): - CompositeShape.__init__(self) - self.SetSensitivityFilter(OP_CLICK_LEFT | OP_CLICK_RIGHT | OP_DRAG_RIGHT) - self.SetCentreResize(False) - self.SetAttachmentMode(True) - self._leftSide = None - self._rightSide = None - self._topSide = None - self._bottomSide = None - self._handleSide = DIVISION_SIDE_NONE - self._leftSidePen = wx.BLACK_PEN - self._topSidePen = wx.BLACK_PEN - self._leftSideColour = "BLACK" - self._topSideColour = "BLACK" - self._leftSideStyle = "Solid" - self._topSideStyle = "Solid" - self.ClearRegions() - - def SetLeftSide(self, shape): - """Set the the division on the left side of this division.""" - self._leftSide = shape - - def SetTopSide(self, shape): - """Set the the division on the top side of this division.""" - self._topSide = shape - - def SetRightSide(self, shape): - """Set the the division on the right side of this division.""" - self._rightSide = shape - - def SetBottomSide(self, shape): - """Set the the division on the bottom side of this division.""" - self._bottomSide = shape - - def GetLeftSide(self): - """Return the division on the left side of this division.""" - return self._leftSide - - def GetTopSide(self): - """Return the division on the top side of this division.""" - return self._topSide - - def GetRightSide(self): - """Return the division on the right side of this division.""" - return self._rightSide - - def GetBottomSide(self): - """Return the division on the bottom side of this division.""" - return self._bottomSide - - def SetHandleSide(self, side): - """Sets the side which the handle appears on. - - Either DIVISION_SIDE_LEFT or DIVISION_SIDE_TOP. - """ - self._handleSide = side - - def GetHandleSide(self): - """Return the side which the handle appears on.""" - return self._handleSide - - def SetLeftSidePen(self, pen): - """Set the colour for drawing the left side of the division.""" - self._leftSidePen = pen - - def SetTopSidePen(self, pen): - """Set the colour for drawing the top side of the division.""" - self._topSidePen = pen - - def GetLeftSidePen(self): - """Return the pen used for drawing the left side of the division.""" - return self._leftSidePen - - def GetTopSidePen(self): - """Return the pen used for drawing the top side of the division.""" - return self._topSidePen - - def GetLeftSideColour(self): - """Return the colour used for drawing the left side of the division.""" - return self._leftSideColour - - def GetTopSideColour(self): - """Return the colour used for drawing the top side of the division.""" - return self._topSideColour - - def SetLeftSideColour(self, colour): - """Set the colour for drawing the left side of the division.""" - self._leftSideColour = colour - - def SetTopSideColour(self, colour): - """Set the colour for drawing the top side of the division.""" - self._topSideColour = colour - - def GetLeftSideStyle(self): - """Return the style used for the left side of the division.""" - return self._leftSideStyle - - def GetTopSideStyle(self): - """Return the style used for the top side of the division.""" - return self._topSideStyle - - def SetLeftSideStyle(self, style): - self._leftSideStyle = style - - def SetTopSideStyle(self, style): - self._lefttopStyle = style - - def OnDraw(self, dc): - dc.SetBrush(wx.TRANSPARENT_BRUSH) - dc.SetBackgroundMode(wx.TRANSPARENT) - - x1 = self.GetX() - self.GetWidth() / 2.0 - y1 = self.GetY() - self.GetHeight() / 2.0 - x2 = self.GetX() + self.GetWidth() / 2.0 - y2 = self.GetY() + self.GetHeight() / 2.0 - - # Should subtract 1 pixel if drawing under Windows - if sys.platform[:3] == "win": - y2 -= 1 - - if self._leftSide: - dc.SetPen(self._leftSidePen) - dc.DrawLine(x1, y2, x1, y1) - - if self._topSide: - dc.SetPen(self._topSidePen) - dc.DrawLine(x1, y1, x2, y1) - - # For testing purposes, draw a rectangle so we know - # how big the division is. - #dc.SetBrush(wx.RED_BRUSH) - #dc.DrawRectangle(x1, y1, self.GetWidth(), self.GetHeight()) - - def OnDrawContents(self, dc): - CompositeShape.OnDrawContents(self, dc) - - def OnMovePre(self, dc, x, y, oldx, oldy, display = True): - diffX = x - oldx - diffY = y - oldy - for object in self._children: - object.Erase(dc) - object.Move(dc, object.GetX() + diffX, object.GetY() + diffY, display) - return True - - def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0): - if self._sensitivity & OP_DRAG_LEFT != OP_DRAG_LEFT: - if self._parent: - hit = self._parent.HitTest(x, y) - if hit: - attachment, dist = hit - self._parent.GetEventHandler().OnDragLeft(draw, x, y, keys, attachment) - return - Shape.OnDragLeft(self, draw, x, y, keys, attachment) - - def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0): - if self._sensitivity & OP_DRAG_LEFT != OP_DRAG_LEFT: - if self._parent: - hit = self._parent.HitTest(x, y) - if hit: - attachment, dist = hit - self._parent.GetEventHandler().OnBeginDragLeft(x, y, keys, attachment) - return - Shape.OnBeginDragLeft(x, y, keys, attachment) - - def OnEndDragLeft(self, x, y, keys = 0, attachment = 0): - if self._canvas.HasCapture(): - self._canvas.ReleaseMouse() - if self._sensitivity & OP_DRAG_LEFT != OP_DRAG_LEFT: - if self._parent: - hit = self._parent.HitTest(x, y) - if hit: - attachment, dist = hit - self._parent.GetEventHandler().OnEndDragLeft(x, y, keys, attachment) - return - - dc = wx.ClientDC(self.GetCanvas()) - self.GetCanvas().PrepareDC(dc) - - dc.SetLogicalFunction(wx.COPY) - - self._xpos, self._ypos = self._canvas.Snap(self._xpos, self._ypos) - self.GetEventHandler().OnMovePre(dc, x, y, self._oldX, self._oldY) - - self.ResetControlPoints() - self.Draw(dc) - self.MoveLinks(dc) - self.GetEventHandler().OnDrawControlPoints(dc) - - if self._canvas and not self._canvas.GetQuickEditMode(): - self._canvas.Redraw(dc) - - def SetSize(self, w, h, recursive = True): - self._width = w - self._height = h - RectangleShape.SetSize(self, w, h, recursive) - - def CalculateSize(self): - pass - - # Experimental - def OnRightClick(self, x, y, keys = 0, attachment = 0): - if keys & KEY_CTRL: - self.PopupMenu(x, y) - else: - if self._parent: - hit = self._parent.HitTest(x, y) - if hit: - attachment, dist = hit - self._parent.GetEventHandler().OnRightClick(x, y, keys, attachment) - - # Divide wx.HORIZONTALly or wx.VERTICALly - def Divide(self, direction): - """Divide this division into two further divisions, - horizontally (direction is wxHORIZONTAL) or - vertically (direction is wxVERTICAL). - """ - # Calculate existing top-left, bottom-right - x1 = self.GetX() - self.GetWidth() / 2.0 - y1 = self.GetY() - self.GetHeight() / 2.0 - - compositeParent = self.GetParent() - oldWidth = self.GetWidth() - oldHeight = self.GetHeight() - if self.Selected(): - self.Select(False) - - dc = wx.ClientDC(self.GetCanvas()) - self.GetCanvas().PrepareDC(dc) - - if direction == wx.VERTICAL: - # Dividing vertically means notionally putting a horizontal - # line through it. - # Break existing piece into two. - newXPos1 = self.GetX() - newYPos1 = y1 + self.GetHeight() / 4.0 - newXPos2 = self.GetX() - newYPos2 = y1 + 3 * self.GetHeight() / 4.0 - newDivision = compositeParent.OnCreateDivision() - newDivision.Show(True) - - self.Erase(dc) - - # Anything adjoining the bottom of this division now adjoins the - # bottom of the new division. - for obj in compositeParent.GetDivisions(): - if obj.GetTopSide() == self: - obj.SetTopSide(newDivision) - - newDivision.SetTopSide(self) - newDivision.SetBottomSide(self._bottomSide) - newDivision.SetLeftSide(self._leftSide) - newDivision.SetRightSide(self._rightSide) - self._bottomSide = newDivision - - compositeParent.GetDivisions().append(newDivision) - - # CHANGE: Need to insert this division at start of divisions in the - # object list, because e.g.: - # 1) Add division - # 2) Add contained object - # 3) Add division - # Division is now receiving mouse events _before_ the contained - # object, because it was added last (on top of all others) - - # Add after the image that visualizes the container - compositeParent.AddChild(newDivision, compositeParent.FindContainerImage()) - - self._handleSide = DIVISION_SIDE_BOTTOM - newDivision.SetHandleSide(DIVISION_SIDE_TOP) - - self.SetSize(oldWidth, oldHeight / 2.0) - self.Move(dc, newXPos1, newYPos1) - - newDivision.SetSize(oldWidth, oldHeight / 2.0) - newDivision.Move(dc, newXPos2, newYPos2) - else: - # Dividing horizontally means notionally putting a vertical line - # through it. - # Break existing piece into two. - newXPos1 = x1 + self.GetWidth() / 4.0 - newYPos1 = self.GetY() - newXPos2 = x1 + 3 * self.GetWidth() / 4.0 - newYPos2 = self.GetY() - newDivision = compositeParent.OnCreateDivision() - newDivision.Show(True) - - self.Erase(dc) - - # Anything adjoining the left of this division now adjoins the - # left of the new division. - for obj in compositeParent.GetDivisions(): - if obj.GetLeftSide() == self: - obj.SetLeftSide(newDivision) - - newDivision.SetTopSide(self._topSide) - newDivision.SetBottomSide(self._bottomSide) - newDivision.SetLeftSide(self) - newDivision.SetRightSide(self._rightSide) - self._rightSide = newDivision - - compositeParent.GetDivisions().append(newDivision) - compositeParent.AddChild(newDivision, compositeParent.FindContainerImage()) - - self._handleSide = DIVISION_SIDE_RIGHT - newDivision.SetHandleSide(DIVISION_SIDE_LEFT) - - self.SetSize(oldWidth / 2.0, oldHeight) - self.Move(dc, newXPos1, newYPos1) - - newDivision.SetSize(oldWidth / 2.0, oldHeight) - newDivision.Move(dc, newXPos2, newYPos2) - - if compositeParent.Selected(): - compositeParent.DeleteControlPoints(dc) - compositeParent.MakeControlPoints() - compositeParent.MakeMandatoryControlPoints() - - compositeParent.Draw(dc) - return True - - def MakeControlPoints(self): - self.MakeMandatoryControlPoints() - - def MakeMandatoryControlPoints(self): - maxX, maxY = self.GetBoundingBoxMax() - x = y = 0.0 - direction = 0 - - if self._handleSide == DIVISION_SIDE_LEFT: - x = -maxX / 2.0 - direction = CONTROL_POINT_HORIZONTAL - elif self._handleSide == DIVISION_SIDE_TOP: - y = -maxY / 2.0 - direction = CONTROL_POINT_VERTICAL - elif self._handleSide == DIVISION_SIDE_RIGHT: - x = maxX / 2.0 - direction = CONTROL_POINT_HORIZONTAL - elif self._handleSide == DIVISION_SIDE_BOTTOM: - y = maxY / 2.0 - direction = CONTROL_POINT_VERTICAL - - if self._handleSide != DIVISION_SIDE_NONE: - control = DivisionControlPoint(self._canvas, self, CONTROL_POINT_SIZE, x, y, direction) - self._canvas.AddShape(control) - self._controlPoints.append(control) - - def ResetControlPoints(self): - self.ResetMandatoryControlPoints() - - def ResetMandatoryControlPoints(self): - if not self._controlPoints: - return - - maxX, maxY = self.GetBoundingBoxMax() - - node = self._controlPoints[0] - - if self._handleSide == DIVISION_SIDE_LEFT and node: - node._xoffset = -maxX / 2.0 - node._yoffset = 0.0 - - if self._handleSide == DIVISION_SIDE_TOP and node: - node._xoffset = 0.0 - node._yoffset = -maxY / 2.0 - - if self._handleSide == DIVISION_SIDE_RIGHT and node: - node._xoffset = maxX / 2.0 - node._yoffset = 0.0 - - if self._handleSide == DIVISION_SIDE_BOTTOM and node: - node._xoffset = 0.0 - node._yoffset = maxY / 2.0 - - def AdjustLeft(self, left, test): - """Adjust a side. - - Returns FALSE if it's not physically possible to adjust it to - this point. - """ - x2 = self.GetX() + self.GetWidth() / 2.0 - - if left >= x2: - return False - - if test: - return True - - newW = x2 - left - newX = left + newW / 2.0 - self.SetSize(newW, self.GetHeight()) - - dc = wx.ClientDC(self.GetCanvas()) - self.GetCanvas().PrepareDC(dc) - - self.Move(dc, newX, self.GetY()) - return True - - def AdjustTop(self, top, test): - """Adjust a side. - - Returns FALSE if it's not physically possible to adjust it to - this point. - """ - y2 = self.GetY() + self.GetHeight() / 2.0 - - if top >= y2: - return False - - if test: - return True - - newH = y2 - top - newY = top + newH / 2.0 - self.SetSize(self.GetWidth(), newH) - - dc = wx.ClientDC(self.GetCanvas()) - self.GetCanvas().PrepareDC(dc) - - self.Move(dc, self.GetX(), newY) - return True - - def AdjustRight(self, right, test): - """Adjust a side. - - Returns FALSE if it's not physically possible to adjust it to - this point. - """ - x1 = self.GetX() - self.GetWidth() / 2.0 - - if right <= x1: - return False - - if test: - return True - - newW = right - x1 - newX = x1 + newW / 2.0 - self.SetSize(newW, self.GetHeight()) - - dc = wx.ClientDC(self.GetCanvas()) - self.GetCanvas().PrepareDC(dc) - - self.Move(dc, newX, self.GetY()) - return True - - def AdjustTop(self, top, test): - """Adjust a side. - - Returns FALSE if it's not physically possible to adjust it to - this point. - """ - y1 = self.GetY() - self.GetHeight() / 2.0 - - if bottom <= y1: - return False - - if test: - return True - - newH = bottom - y1 - newY = y1 + newH / 2.0 - self.SetSize(self.GetWidth(), newH) - - dc = wx.ClientDC(self.GetCanvas()) - self.GetCanvas().PrepareDC(dc) - - self.Move(dc, self.GetX(), newY) - return True - - # Resize adjoining divisions. - - # Behaviour should be as follows: - # If right edge moves, find all objects whose left edge - # adjoins this object, and move left edge accordingly. - # If left..., move ... right. - # If top..., move ... bottom. - # If bottom..., move top. - # If size goes to zero or end position is other side of start position, - # resize to original size and return. - # - def ResizeAdjoining(self, side, newPos, test): - """Resize adjoining divisions at the given side. - - If test is TRUE, just see whether it's possible for each adjoining - region, returning FALSE if it's not. - - side can be one of: - - * DIVISION_SIDE_NONE - * DIVISION_SIDE_LEFT - * DIVISION_SIDE_TOP - * DIVISION_SIDE_RIGHT - * DIVISION_SIDE_BOTTOM - """ - divisionParent = self.GetParent() - for division in divisionParent.GetDivisions(): - if side == DIVISION_SIDE_LEFT: - if division._rightSide == self: - success = division.AdjustRight(newPos, test) - if not success and test: - return false - elif side == DIVISION_SIDE_TOP: - if division._bottomSide == self: - success = division.AdjustBottom(newPos, test) - if not success and test: - return False - elif side == DIVISION_SIDE_RIGHT: - if division._leftSide == self: - success = division.AdjustLeft(newPos, test) - if not success and test: - return False - elif side == DIVISION_SIDE_BOTTOM: - if division._topSide == self: - success = division.AdjustTop(newPos, test) - if not success and test: - return False - return True - - def EditEdge(self, side): - print "EditEdge() not implemented." - - def PopupMenu(self, x, y): - menu = PopupDivisionMenu() - menu.SetClientData(self) - if self._leftSide: - menu.Enable(DIVISION_MENU_EDIT_LEFT_EDGE, True) - else: - menu.Enable(DIVISION_MENU_EDIT_LEFT_EDGE, False) - if self._topSide: - menu.Enable(DIVISION_MENU_EDIT_TOP_EDGE, True) - else: - menu.Enable(DIVISION_MENU_EDIT_TOP_EDGE, False) - - x1, y1 = self._canvas.GetViewStart() - unit_x, unit_y = self._canvas.GetScrollPixelsPerUnit() - - dc = wx.ClientDC(self.GetCanvas()) - self.GetCanvas().PrepareDC(dc) - - mouse_x = dc.LogicalToDeviceX(x - x1 * unit_x) - mouse_y = dc.LogicalToDeviceY(y - y1 * unit_y) - - self._canvas.PopupMenu(menu, (mouse_x, mouse_y)) - -