mirror of
https://github.com/Sneed-Group/Poodletooth-iLand
synced 2024-12-27 21:52:25 -06:00
3982 lines
129 KiB
Python
3982 lines
129 KiB
Python
# -*- coding: utf-8 -*-
|
|
#----------------------------------------------------------------------------
|
|
# Name: basic.py
|
|
# Purpose: The basic OGL shapes
|
|
#
|
|
# Author: Pierre Hjälm (from C++ original by Julian Smart)
|
|
#
|
|
# Created: 2004-05-08
|
|
# Copyright: (c) 2004 Pierre Hjälm - 1998 Julian Smart
|
|
# Licence: wxWindows license
|
|
# Tags: phoenix-port, unittest, py3-port, documented
|
|
#----------------------------------------------------------------------------
|
|
"""
|
|
The basic shapes for OGL
|
|
"""
|
|
|
|
import wx
|
|
import math
|
|
|
|
from .oglmisc import *
|
|
|
|
DragOffsetX = 0.0
|
|
DragOffsetY = 0.0
|
|
|
|
|
|
def OGLInitialize():
|
|
"""Initialize OGL.
|
|
|
|
:note: This creates some pens and brushes that the OGL library uses.
|
|
It should be called after the app object has been created, but
|
|
before OGL is used.
|
|
"""
|
|
global WhiteBackgroundPen, WhiteBackgroundBrush, TransparentPen
|
|
global BlackForegroundPen, NormalFont
|
|
|
|
WhiteBackgroundPen = wx.Pen(wx.WHITE, 1, wx.PENSTYLE_SOLID)
|
|
WhiteBackgroundBrush = wx.Brush(wx.WHITE, wx.BRUSHSTYLE_SOLID)
|
|
|
|
TransparentPen = wx.Pen(wx.WHITE, 1, wx.PENSTYLE_TRANSPARENT)
|
|
BlackForegroundPen = wx.Pen(wx.BLACK, 1, wx.PENSTYLE_SOLID)
|
|
|
|
NormalFont = wx.Font(10, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)
|
|
|
|
|
|
def OGLCleanUp():
|
|
"""not implemented???"""
|
|
pass
|
|
|
|
|
|
class ShapeTextLine(object):
|
|
"""The :class:`ShapeTextLine` class."""
|
|
def __init__(self, the_x, the_y, the_line):
|
|
"""
|
|
Default class constructor.
|
|
|
|
:param int `the_x`: the x position
|
|
:param int `the_y`: the y position
|
|
:param str `the_line`: the text
|
|
|
|
"""
|
|
self._x = the_x
|
|
self._y = the_y
|
|
self._line = the_line
|
|
|
|
def GetX(self):
|
|
"""Get the x position."""
|
|
return self._x
|
|
|
|
def GetY(self):
|
|
"""Get the y position."""
|
|
return self._y
|
|
|
|
def SetX(self, x):
|
|
"""
|
|
Set the x position.
|
|
|
|
:param int `x`: the x position
|
|
|
|
"""
|
|
self._x = x
|
|
|
|
def SetY(self, y):
|
|
"""
|
|
Set the y position.
|
|
|
|
:param int `y`: the x position
|
|
|
|
"""
|
|
self._y = y
|
|
|
|
def SetText(self, text):
|
|
"""
|
|
Set the text.
|
|
|
|
:param str `text`: the text
|
|
|
|
"""
|
|
self._line = text
|
|
|
|
def GetText(self):
|
|
"""Get the text."""
|
|
return self._line
|
|
|
|
|
|
class ShapeEvtHandler(object):
|
|
"""The :class:`ShapeEvtHandler` class."""
|
|
def __init__(self, prev = None, shape = None):
|
|
"""
|
|
Default class constructor.
|
|
|
|
:param `pref`: the previous event handler, an instance of
|
|
:class:`ShapeEvtHandler` ???
|
|
:param `shape`: the shape, an instance of :class:`Shape`
|
|
|
|
"""
|
|
self._previousHandler = prev
|
|
self._handlerShape = shape
|
|
|
|
def SetShape(self, sh):
|
|
"""
|
|
Set associated shape
|
|
|
|
:param `sh`: the shape, an instance of :class:`Shape`
|
|
|
|
"""
|
|
self._handlerShape = sh
|
|
|
|
def GetShape(self):
|
|
"""Get associated shape."""
|
|
return self._handlerShape
|
|
|
|
def SetPreviousHandler(self, handler):
|
|
"""
|
|
Set previous event handler.
|
|
|
|
:param `handler`: the previous handler, an instance of
|
|
:class:`ShapeEvtHandler` ???
|
|
|
|
"""
|
|
self._previousHandler = handler
|
|
|
|
def GetPreviousHandler(self):
|
|
"""Get previous event handler."""
|
|
return self._previousHandler
|
|
|
|
def OnDelete(self):
|
|
"""The delete handler."""
|
|
if self!=self.GetShape():
|
|
del self
|
|
|
|
def OnDraw(self, dc):
|
|
"""The draw handler."""
|
|
if self._previousHandler:
|
|
self._previousHandler.OnDraw(dc)
|
|
|
|
def OnMoveLinks(self, dc):
|
|
"""The move links handler."""
|
|
if self._previousHandler:
|
|
self._previousHandler.OnMoveLinks(dc)
|
|
|
|
def OnMoveLink(self, dc, moveControlPoints = True):
|
|
"""The move link handler."""
|
|
if self._previousHandler:
|
|
self._previousHandler.OnMoveLink(dc, moveControlPoints)
|
|
|
|
def OnDrawContents(self, dc):
|
|
"""The draw contents handler."""
|
|
if self._previousHandler:
|
|
self._previousHandler.OnDrawContents(dc)
|
|
|
|
def OnDrawBranches(self, dc, erase = False):
|
|
"""The draw branches handler."""
|
|
if self._previousHandler:
|
|
self._previousHandler.OnDrawBranches(dc, erase = erase)
|
|
|
|
def OnSize(self, x, y):
|
|
"""The size handler."""
|
|
if self._previousHandler:
|
|
self._previousHandler.OnSize(x, y)
|
|
|
|
def OnMovePre(self, dc, x, y, old_x, old_y, display = True):
|
|
"""The pre move handler."""
|
|
if self._previousHandler:
|
|
return self._previousHandler.OnMovePre(dc, x, y, old_x, old_y, display)
|
|
else:
|
|
return True
|
|
|
|
def OnMovePost(self, dc, x, y, old_x, old_y, display = True):
|
|
"""The post move handler."""
|
|
if self._previousHandler:
|
|
return self._previousHandler.OnMovePost(dc, x, y, old_x, old_y, display)
|
|
else:
|
|
return True
|
|
|
|
def OnErase(self, dc):
|
|
"""The erase handler."""
|
|
if self._previousHandler:
|
|
self._previousHandler.OnErase(dc)
|
|
|
|
def OnEraseContents(self, dc):
|
|
"""The erase contents handler."""
|
|
if self._previousHandler:
|
|
self._previousHandler.OnEraseContents(dc)
|
|
|
|
def OnHighlight(self, dc):
|
|
"""The highlight handler."""
|
|
if self._previousHandler:
|
|
self._previousHandler.OnHighlight(dc)
|
|
|
|
def OnLeftClick(self, x, y, keys, attachment):
|
|
"""The left click handler."""
|
|
if self._previousHandler:
|
|
self._previousHandler.OnLeftClick(x, y, keys, attachment)
|
|
|
|
def OnLeftDoubleClick(self, x, y, keys = 0, attachment = 0):
|
|
"""The left double click handler."""
|
|
if self._previousHandler:
|
|
self._previousHandler.OnLeftDoubleClick(x, y, keys, attachment)
|
|
|
|
def OnRightClick(self, x, y, keys = 0, attachment = 0):
|
|
"""The right click handler."""
|
|
if self._previousHandler:
|
|
self._previousHandler.OnRightClick(x, y, keys, attachment)
|
|
|
|
def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
|
|
"""The drag left handler."""
|
|
if self._previousHandler:
|
|
self._previousHandler.OnDragLeft(draw, x, y, keys, attachment)
|
|
|
|
def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
|
|
"""The begin drag left handler."""
|
|
if self._previousHandler:
|
|
self._previousHandler.OnBeginDragLeft(x, y, keys, attachment)
|
|
|
|
def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
|
|
"""The end drag left handler."""
|
|
if self._previousHandler:
|
|
self._previousHandler.OnEndDragLeft(x, y, keys, attachment)
|
|
|
|
def OnDragRight(self, draw, x, y, keys = 0, attachment = 0):
|
|
"""The drag right handler."""
|
|
if self._previousHandler:
|
|
self._previousHandler.OnDragRight(draw, x, y, keys, attachment)
|
|
|
|
def OnBeginDragRight(self, x, y, keys = 0, attachment = 0):
|
|
"""The begin drag right handler."""
|
|
if self._previousHandler:
|
|
self._previousHandler.OnBeginDragRight(x, y, keys, attachment)
|
|
|
|
def OnEndDragRight(self, x, y, keys = 0, attachment = 0):
|
|
"""The end drag right handler."""
|
|
if self._previousHandler:
|
|
self._previousHandler.OnEndDragRight(x, y, keys, attachment)
|
|
|
|
# Control points ('handles') redirect control to the actual shape,
|
|
# to make it easier to override sizing behaviour.
|
|
def OnSizingDragLeft(self, pt, draw, x, y, keys = 0, attachment = 0):
|
|
"""The sizing drag left handler."""
|
|
if self._previousHandler:
|
|
self._previousHandler.OnSizingDragLeft(pt, draw, x, y, keys, attachment)
|
|
|
|
def OnSizingBeginDragLeft(self, pt, x, y, keys = 0, attachment = 0):
|
|
"""The sizing begin drag left handler."""
|
|
if self._previousHandler:
|
|
self._previousHandler.OnSizingBeginDragLeft(pt, x, y, keys, attachment)
|
|
|
|
def OnSizingEndDragLeft(self, pt, x, y, keys = 0, attachment = 0):
|
|
"""The sizing end drag left handler."""
|
|
if self._previousHandler:
|
|
self._previousHandler.OnSizingEndDragLeft(pt, x, y, keys, attachment)
|
|
|
|
def OnBeginSize(self, w, h):
|
|
"""not implemented???"""
|
|
pass
|
|
|
|
def OnEndSize(self, w, h):
|
|
"""not implemented???"""
|
|
pass
|
|
|
|
def OnDrawOutline(self, dc, x, y, w, h):
|
|
"""The drag outline handler."""
|
|
if self._previousHandler:
|
|
self._previousHandler.OnDrawOutline(dc, x, y, w, h)
|
|
|
|
def OnDrawControlPoints(self, dc):
|
|
"""The draw control points handler."""
|
|
if self._previousHandler:
|
|
self._previousHandler.OnDrawControlPoints(dc)
|
|
|
|
def OnEraseControlPoints(self, dc):
|
|
"""The erase control points handler."""
|
|
if self._previousHandler:
|
|
self._previousHandler.OnEraseControlPoints(dc)
|
|
|
|
# Can override this to prevent or intercept line reordering.
|
|
def OnChangeAttachment(self, attachment, line, ordering):
|
|
"""The change attachment handler."""
|
|
if self._previousHandler:
|
|
self._previousHandler.OnChangeAttachment(attachment, line, ordering)
|
|
|
|
|
|
class Shape(ShapeEvtHandler):
|
|
"""
|
|
The :class:`Shape` is the base class for OGL shapes.
|
|
|
|
The :class:`Shape` is the top-level, abstract object that all other objects
|
|
are derived from. All common functionality is represented by :class:`Shape`
|
|
members, and overriden members that appear in derived classes and have
|
|
behaviour as documented for :class:`Shape`, are not documented separately.
|
|
"""
|
|
GraphicsInSizeToContents = False
|
|
|
|
def __init__(self, canvas = None):
|
|
"""
|
|
Default class constructor.
|
|
|
|
:param `canvas`: an instance of :class:`~lib.ogl.Canvas`
|
|
|
|
"""
|
|
ShapeEvtHandler.__init__(self)
|
|
|
|
self._eventHandler = self
|
|
self.SetShape(self)
|
|
self._id = 0
|
|
self._formatted = False
|
|
self._canvas = canvas
|
|
self._xpos = 0.0
|
|
self._ypos = 0.0
|
|
self._pen = BlackForegroundPen
|
|
self._brush = wx.WHITE_BRUSH
|
|
self._font = NormalFont
|
|
self._textColour = wx.BLACK
|
|
self._textColourName = wx.BLACK
|
|
self._visible = False
|
|
self._selected = False
|
|
self._attachmentMode = ATTACHMENT_MODE_NONE
|
|
self._spaceAttachments = True
|
|
self._disableLabel = False
|
|
self._fixedWidth = False
|
|
self._fixedHeight = False
|
|
self._drawHandles = True
|
|
self._sensitivity = OP_ALL
|
|
self._draggable = True
|
|
self._parent = None
|
|
self._formatMode = FORMAT_CENTRE_HORIZ | FORMAT_CENTRE_VERT
|
|
self._shadowMode = SHADOW_NONE
|
|
self._shadowOffsetX = 6
|
|
self._shadowOffsetY = 6
|
|
self._shadowBrush = wx.BLACK_BRUSH
|
|
self._textMarginX = 5
|
|
self._textMarginY = 5
|
|
self._regionName = "0"
|
|
self._centreResize = True
|
|
self._maintainAspectRatio = False
|
|
self._highlighted = False
|
|
self._rotation = 0.0
|
|
self._branchNeckLength = 10
|
|
self._branchStemLength = 10
|
|
self._branchSpacing = 10
|
|
self._branchStyle = BRANCHING_ATTACHMENT_NORMAL
|
|
|
|
self._regions = []
|
|
self._lines = []
|
|
self._controlPoints = []
|
|
self._attachmentPoints = []
|
|
self._text = []
|
|
self._children = []
|
|
|
|
# Set up a default region. Much of the above will be put into
|
|
# the region eventually (the duplication is for compatibility)
|
|
region = ShapeRegion()
|
|
region.SetName("0")
|
|
region.SetFont(NormalFont)
|
|
region.SetFormatMode(FORMAT_CENTRE_HORIZ | FORMAT_CENTRE_VERT)
|
|
region.SetColour("BLACK")
|
|
self._regions.append(region)
|
|
|
|
def __str__(self):
|
|
return "<%s.%s>" % (self.__class__.__module__, self.__class__.__name__)
|
|
|
|
def GetClassName(self):
|
|
return str(self.__class__).split(".")[-1][:-2]
|
|
|
|
def Delete(self):
|
|
"""
|
|
Fully disconnect this shape from parents, children, the
|
|
canvas, etc.
|
|
"""
|
|
if self._parent:
|
|
self._parent.GetChildren().remove(self)
|
|
|
|
for child in self.GetChildren():
|
|
child.Delete()
|
|
|
|
self.ClearText()
|
|
self.ClearRegions()
|
|
self.ClearAttachments()
|
|
|
|
self._handlerShape = None
|
|
|
|
if self._canvas:
|
|
self.RemoveFromCanvas(self._canvas)
|
|
|
|
if self.GetEventHandler():
|
|
self.GetEventHandler().OnDelete()
|
|
self._eventHandler = None
|
|
|
|
def Draggable(self):
|
|
"""
|
|
Is shape draggable?
|
|
|
|
:returns: `True` if the shape may be dragged by the user.
|
|
"""
|
|
return True
|
|
|
|
def SetShape(self, sh):
|
|
"""Set shape ???
|
|
|
|
:param `sh`: an instance of :class:`Shape`
|
|
|
|
"""
|
|
self._handlerShape = sh
|
|
|
|
def GetCanvas(self):
|
|
"""Get the internal canvas."""
|
|
return self._canvas
|
|
|
|
def GetBranchStyle(self):
|
|
"""Get the branch style."""
|
|
return self._branchStyle
|
|
|
|
def GetRotation(self):
|
|
"""Return the angle of rotation in radians."""
|
|
return self._rotation
|
|
|
|
def SetRotation(self, rotation):
|
|
"""Set rotation
|
|
|
|
:param int `rotation`: rotation
|
|
|
|
"""
|
|
self._rotation = rotation
|
|
|
|
def SetHighlight(self, hi, recurse = False):
|
|
"""Set the highlight for a shape. Shape highlighting is unimplemented."""
|
|
self._highlighted = hi
|
|
if recurse:
|
|
for shape in self._children:
|
|
shape.SetHighlight(hi, recurse)
|
|
|
|
def SetSensitivityFilter(self, sens = OP_ALL, recursive = False):
|
|
"""
|
|
Set the shape to be sensitive or insensitive to specific mouse
|
|
operations.
|
|
|
|
:param `sens`: is a bitlist of the following:
|
|
|
|
========================== ===================
|
|
Mouse operation Description
|
|
========================== ===================
|
|
`OP_CLICK_LEFT` left clicked
|
|
`OP_CLICK_RIGHT` right clicked
|
|
`OP_DRAG_LEFT` left drag
|
|
`OP_DRAG_RIGHT` right drag
|
|
`OP_ALL` all of the above
|
|
========================== ===================
|
|
|
|
:param `recursive`: if `True` recurse through children
|
|
|
|
"""
|
|
self._draggable = sens & OP_DRAG_LEFT
|
|
|
|
self._sensitivity = sens
|
|
if recursive:
|
|
for shape in self._children:
|
|
shape.SetSensitivityFilter(sens, True)
|
|
|
|
def SetDraggable(self, drag, recursive = False):
|
|
"""Set the shape to be draggable or not draggable.
|
|
|
|
:param `drag`: if `True` make shape draggable
|
|
:param `recursive`: if `True` recurse through children
|
|
|
|
"""
|
|
self._draggable = drag
|
|
if drag:
|
|
self._sensitivity |= OP_DRAG_LEFT
|
|
elif self._sensitivity & OP_DRAG_LEFT:
|
|
self._sensitivity -= OP_DRAG_LEFT
|
|
|
|
if recursive:
|
|
for shape in self._children:
|
|
shape.SetDraggable(drag, True)
|
|
|
|
def SetDrawHandles(self, drawH):
|
|
"""
|
|
Set the drawHandles flag for this shape and all descendants.
|
|
|
|
:param `drawH`: if `True` (the default), any handles (control points)
|
|
will be drawn. Otherwise, the handles will not be drawn.
|
|
|
|
"""
|
|
self._drawHandles = drawH
|
|
for shape in self._children:
|
|
shape.SetDrawHandles(drawH)
|
|
|
|
def SetShadowMode(self, mode, redraw = False):
|
|
"""
|
|
Set the shadow mode (whether a shadow is drawn or not).
|
|
|
|
:param `mode`: can be one of the following:
|
|
|
|
=============================== ===========================
|
|
Shadow mode Description
|
|
=============================== ===========================
|
|
`SHADOW_NONE` No shadow (the default)
|
|
`SHADOW_LEFT` Shadow on the left side
|
|
`SHADOW_RIGHT` Shadow on the right side
|
|
=============================== ===========================
|
|
|
|
"""
|
|
if redraw and self.GetCanvas():
|
|
dc = wx.MemoryDC()
|
|
dc.SelectObject(self.GetCanvas()._Buffer)
|
|
self.Erase(dc)
|
|
self._shadowMode = mode
|
|
self.Draw(dc)
|
|
else:
|
|
self._shadowMode = mode
|
|
|
|
def GetShadowMode(self):
|
|
"""Get the current shadow mode setting."""
|
|
return self._shadowMode
|
|
|
|
def SetCanvas(self, theCanvas):
|
|
"""
|
|
Set the canvas, identical to Shape.Attach.
|
|
|
|
:param `theCanvas`: an instance of :class:`~lib.ogl.Canvas`
|
|
|
|
"""
|
|
self._canvas = theCanvas
|
|
for shape in self._children:
|
|
shape.SetCanvas(theCanvas)
|
|
|
|
def AddToCanvas(self, theCanvas, addAfter = None):
|
|
"""
|
|
Add the shape to the canvas's shape list.
|
|
|
|
:param `theCanvas`: an instance of :class:`~lib.ogl.Canvas`
|
|
:param `addAfter`: if non-NULL, will add the shape after this shape
|
|
|
|
"""
|
|
theCanvas.AddShape(self, addAfter)
|
|
|
|
lastImage = self
|
|
for object in self._children:
|
|
object.AddToCanvas(theCanvas, lastImage)
|
|
lastImage = object
|
|
|
|
def InsertInCanvas(self, theCanvas):
|
|
"""
|
|
Insert the shape at the front of the shape list of canvas.
|
|
|
|
:param `theCanvas`: an instance of :class:`~lib.ogl.Canvas`
|
|
|
|
"""
|
|
theCanvas.InsertShape(self)
|
|
|
|
lastImage = self
|
|
for object in self._children:
|
|
object.AddToCanvas(theCanvas, lastImage)
|
|
lastImage = object
|
|
|
|
def RemoveFromCanvas(self, theCanvas):
|
|
"""
|
|
Remove the shape from the canvas.
|
|
|
|
:param `theCanvas`: an instance of :class:`~lib.ogl.Canvas`
|
|
|
|
"""
|
|
if self.Selected():
|
|
self.Select(False)
|
|
|
|
self._canvas = None
|
|
theCanvas.RemoveShape(self)
|
|
for object in self._children:
|
|
object.RemoveFromCanvas(theCanvas)
|
|
|
|
def ClearAttachments(self):
|
|
"""Clear internal custom attachment point shapes (of class
|
|
:class:`~lib.ogl.AttachmentPoint`)
|
|
"""
|
|
self._attachmentPoints = []
|
|
|
|
def ClearText(self, regionId = 0):
|
|
"""
|
|
Clear the text from the specified text region.
|
|
|
|
:param `regionId`: the region identifier
|
|
|
|
"""
|
|
if regionId == 0:
|
|
self._text = ""
|
|
if regionId < len(self._regions):
|
|
self._regions[regionId].ClearText()
|
|
|
|
def ClearRegions(self):
|
|
"""Clear the ShapeRegions from the shape."""
|
|
self._regions = []
|
|
|
|
def AddRegion(self, region):
|
|
"""Add a region to the shape."""
|
|
self._regions.append(region)
|
|
|
|
def SetDefaultRegionSize(self):
|
|
"""Set the default region to be consistent with the shape size."""
|
|
if not self._regions:
|
|
return
|
|
w, h = self.GetBoundingBoxMax()
|
|
self._regions[0].SetSize(w, h)
|
|
|
|
def HitTest(self, x, y):
|
|
"""
|
|
Given a point on a canvas, returns `True` if the point was on the
|
|
shape, and returns the nearest attachment point and distance from
|
|
the given point and target.
|
|
|
|
:param `x`: the x position
|
|
:param `y`: the y position
|
|
|
|
"""
|
|
width, height = self.GetBoundingBoxMax()
|
|
if abs(width) < 4:
|
|
width = 4.0
|
|
if abs(height) < 4:
|
|
height = 4.0
|
|
|
|
width += 4 # Allowance for inaccurate mousing
|
|
height += 4
|
|
|
|
left = self._xpos - width / 2.0
|
|
top = self._ypos - height / 2.0
|
|
right = self._xpos + width / 2.0
|
|
bottom = self._ypos + height / 2.0
|
|
|
|
nearest_attachment = 0
|
|
|
|
# If within the bounding box, check the attachment points
|
|
# within the object.
|
|
if x >= left and x <= right and y >= top and y <= bottom:
|
|
n = self.GetNumberOfAttachments()
|
|
nearest = 999999
|
|
|
|
# GetAttachmentPosition[Edge] takes a logical attachment position,
|
|
# i.e. if it's rotated through 90%, position 0 is East-facing.
|
|
|
|
for i in range(n):
|
|
e = self.GetAttachmentPositionEdge(i)
|
|
if e:
|
|
xp, yp = e
|
|
l = math.sqrt(((xp - x) * (xp - x)) + (yp - y) * (yp - y))
|
|
if l < nearest:
|
|
nearest = l
|
|
nearest_attachment = i
|
|
|
|
return nearest_attachment, nearest
|
|
return False
|
|
|
|
# Format a text string according to the region size, adding
|
|
# strings with positions to region text list
|
|
|
|
def FormatText(self, dc, s, i = 0):
|
|
"""
|
|
Reformat the given text region; defaults to formatting the
|
|
default region.
|
|
|
|
:param `dc`: the device contexr
|
|
:param str `s`: the text string
|
|
:param int `i`: the region identifier
|
|
|
|
"""
|
|
self.ClearText(i)
|
|
|
|
if not self._regions:
|
|
return
|
|
|
|
if i >= len(self._regions):
|
|
return
|
|
|
|
region = self._regions[i]
|
|
region._regionText = s
|
|
dc.SetFont(region.GetFont())
|
|
|
|
w, h = region.GetSize()
|
|
|
|
stringList = FormatText(dc, s, (w - 2 * self._textMarginX), (h - 2 * self._textMarginY), region.GetFormatMode())
|
|
for s in stringList:
|
|
line = ShapeTextLine(0.0, 0.0, s)
|
|
region.GetFormattedText().append(line)
|
|
|
|
actualW = w
|
|
actualH = h
|
|
# Don't try to resize an object with more than one image (this
|
|
# case should be dealt with by overriden handlers)
|
|
if (region.GetFormatMode() & FORMAT_SIZE_TO_CONTENTS) and \
|
|
len(region.GetFormattedText()) and \
|
|
len(self._regions) == 1 and \
|
|
not Shape.GraphicsInSizeToContents:
|
|
|
|
actualW, actualH = GetCentredTextExtent(dc, region.GetFormattedText())
|
|
if actualW + 2 * self._textMarginX != w or actualH + 2 * self._textMarginY != h:
|
|
# If we are a descendant of a composite, must make sure
|
|
# the composite gets resized properly
|
|
|
|
topAncestor = self.GetTopAncestor()
|
|
if topAncestor != self:
|
|
Shape.GraphicsInSizeToContents = True
|
|
|
|
composite = topAncestor
|
|
composite.Erase(dc)
|
|
self.SetSize(actualW + 2 * self._textMarginX, actualH + 2 * self._textMarginY)
|
|
self.Move(dc, self._xpos, self._ypos)
|
|
composite.CalculateSize()
|
|
if composite.Selected():
|
|
composite.DeleteControlPoints(dc)
|
|
composite.MakeControlPoints()
|
|
composite.MakeMandatoryControlPoints()
|
|
# Where infinite recursion might happen if we didn't stop it
|
|
composite.Draw(dc)
|
|
Shape.GraphicsInSizeToContents = False
|
|
else:
|
|
self.Erase(dc)
|
|
|
|
self.SetSize(actualW + 2 * self._textMarginX, actualH + 2 * self._textMarginY)
|
|
self.Move(dc, self._xpos, self._ypos)
|
|
self.EraseContents(dc)
|
|
CentreText(dc, region.GetFormattedText(), self._xpos, self._ypos, actualW - 2 * self._textMarginX, actualH - 2 * self._textMarginY, region.GetFormatMode())
|
|
self._formatted = True
|
|
|
|
def Recentre(self, dc):
|
|
"""
|
|
Recentre (or other formatting) all the text regions for this shape.
|
|
"""
|
|
w, h = self.GetBoundingBoxMin()
|
|
for region in self._regions:
|
|
CentreText(dc, region.GetFormattedText(), self._xpos, self._ypos, w - 2 * self._textMarginX, h - 2 * self._textMarginY, region.GetFormatMode())
|
|
|
|
def GetPerimeterPoint(self, x1, y1, x2, y2):
|
|
"""
|
|
Get the point at which the line from (x1, y1) to (x2, y2) hits
|
|
the shape.
|
|
|
|
:param `x1`: the x1 position
|
|
:param `y1`: the y1 position
|
|
:param `x2`: the x2 position
|
|
:param `y2`: the y2 position
|
|
|
|
:returns: `False` if the line doesn't hit the perimeter.
|
|
|
|
"""
|
|
return False
|
|
|
|
def SetPen(self, the_pen):
|
|
"""Set the pen for drawing the shape's outline."""
|
|
self._pen = the_pen
|
|
|
|
def SetBrush(self, the_brush):
|
|
"""Set the brush for filling the shape's shape."""
|
|
self._brush = the_brush
|
|
|
|
# Get the top - most (non-division) ancestor, or self
|
|
def GetTopAncestor(self):
|
|
"""
|
|
Return the top-most ancestor of this shape (the root of
|
|
the composite).
|
|
"""
|
|
if not self.GetParent():
|
|
return self
|
|
|
|
if isinstance(self.GetParent(), DivisionShape):
|
|
return self
|
|
return self.GetParent().GetTopAncestor()
|
|
|
|
# Region functions
|
|
def SetFont(self, the_font, regionId = 0):
|
|
"""
|
|
Set the font for the specified text region.
|
|
|
|
:param `the_font`: an instance of :class:`Font` ???
|
|
:param `regionId`: the region identifier
|
|
|
|
"""
|
|
self._font = the_font
|
|
if regionId < len(self._regions):
|
|
self._regions[regionId].SetFont(the_font)
|
|
|
|
def GetFont(self, regionId = 0):
|
|
"""
|
|
Get the font for the specified text region.
|
|
|
|
:param `regionId`: the region identifier
|
|
|
|
"""
|
|
if regionId >= len(self._regions):
|
|
return None
|
|
return self._regions[regionId].GetFont()
|
|
|
|
def SetFormatMode(self, mode, regionId = 0):
|
|
"""
|
|
Set the format mode of the region.
|
|
|
|
:param `mode`: can be a bit list of the following
|
|
|
|
============================== ==============================
|
|
Format mode Description
|
|
============================== ==============================
|
|
`FORMAT_NONE` No formatting
|
|
`FORMAT_CENTRE_HORIZ` Horizontal centring
|
|
`FORMAT_CENTRE_VERT` Vertical centring
|
|
============================== ==============================
|
|
|
|
:param `regionId`: the region identifier, default=0
|
|
|
|
"""
|
|
if regionId < len(self._regions):
|
|
self._regions[regionId].SetFormatMode(mode)
|
|
|
|
def GetFormatMode(self, regionId = 0):
|
|
"""
|
|
Get the format mode.
|
|
|
|
:param `regionId`: the region identifier, default=0
|
|
|
|
"""
|
|
if regionId >= len(self._regions):
|
|
return 0
|
|
return self._regions[regionId].GetFormatMode()
|
|
|
|
def SetTextColour(self, the_colour, regionId = 0):
|
|
"""
|
|
Set the colour for the specified text region.
|
|
|
|
:param str `the_colour`: a valid colour name,
|
|
see :class:`ColourDatabase`
|
|
:param `regionId`: the region identifier
|
|
|
|
"""
|
|
self._textColour = wx.TheColourDatabase.Find(the_colour)
|
|
self._textColourName = the_colour
|
|
|
|
if regionId < len(self._regions):
|
|
self._regions[regionId].SetColour(the_colour)
|
|
|
|
def GetTextColour(self, regionId = 0):
|
|
"""
|
|
Get the colour for the specified text region.
|
|
|
|
:param `regionId`: the region identifier
|
|
|
|
"""
|
|
if regionId >= len(self._regions):
|
|
return ""
|
|
return self._regions[regionId].GetColour()
|
|
|
|
def SetRegionName(self, name, regionId = 0):
|
|
"""
|
|
Set the name for this region.
|
|
|
|
:param str `name`: the name to set
|
|
:param `regionId`: the region identifier
|
|
|
|
:note: The name for a region is unique within the scope of the whole
|
|
composite, whereas a region id is unique only for a single image.
|
|
|
|
"""
|
|
if regionId < len(self._regions):
|
|
self._regions[regionId].SetName(name)
|
|
|
|
def GetRegionName(self, regionId = 0):
|
|
"""
|
|
Get the region's name.
|
|
|
|
:param `regionId`: the region identifier
|
|
|
|
:note: A region's name can be used to uniquely determine a region within
|
|
an entire composite image hierarchy. See also
|
|
:meth:`~lib.ogl.Shape.SetRegionName`.
|
|
|
|
"""
|
|
if regionId >= len(self._regions):
|
|
return ""
|
|
return self._regions[regionId].GetName()
|
|
|
|
def GetRegionId(self, name):
|
|
"""
|
|
Get the region's identifier by name.
|
|
|
|
:param str `name`: the regions name
|
|
|
|
:note: This is not unique for within an entire composite, but is unique
|
|
for the image.
|
|
|
|
"""
|
|
for i, r in enumerate(self._regions):
|
|
if r.GetName() == name:
|
|
return i
|
|
return -1
|
|
|
|
# Name all _regions in all subimages recursively
|
|
def NameRegions(self, parentName=""):
|
|
"""
|
|
Make unique names for all the regions in a shape or composite shape.
|
|
|
|
:param str `parentName`: a prefix for the region names
|
|
|
|
"""
|
|
n = self.GetNumberOfTextRegions()
|
|
for i in range(n):
|
|
if parentName:
|
|
buff = parentName+"."+str(i)
|
|
else:
|
|
buff = str(i)
|
|
self.SetRegionName(buff, i)
|
|
|
|
for j, child in enumerate(self._children):
|
|
if parentName:
|
|
buff = parentName+"."+str(j)
|
|
else:
|
|
buff = str(j)
|
|
child.NameRegions(buff)
|
|
|
|
# Get a region by name, possibly looking recursively into composites
|
|
def FindRegion(self, name):
|
|
"""
|
|
Find the actual image ('this' if non-composite) and region id
|
|
for the given region name.
|
|
|
|
:param str `name`: the region name
|
|
|
|
"""
|
|
id = self.GetRegionId(name)
|
|
if id > -1:
|
|
return self, id
|
|
|
|
for child in self._children:
|
|
actualImage, regionId = child.FindRegion(name)
|
|
if actualImage:
|
|
return actualImage, regionId
|
|
|
|
return None, -1
|
|
|
|
# Finds all region names for this image (composite or simple).
|
|
def FindRegionNames(self):
|
|
"""Get a list of all region names for this image (composite or simple)."""
|
|
list = []
|
|
n = self.GetNumberOfTextRegions()
|
|
for i in range(n):
|
|
list.append(self.GetRegionName(i))
|
|
|
|
for child in self._children:
|
|
list += child.FindRegionNames()
|
|
|
|
return list
|
|
|
|
def AssignNewIds(self):
|
|
"""Assign new ids to this image and its children."""
|
|
self._id = wx.NewId()
|
|
for child in self._children:
|
|
child.AssignNewIds()
|
|
|
|
def OnDraw(self, dc):
|
|
"""not implemented???"""
|
|
pass
|
|
|
|
def OnMoveLinks(self, dc):
|
|
"""The move links handler."""
|
|
# Want to set the ends of all attached links
|
|
# to point to / from this object
|
|
|
|
for line in self._lines:
|
|
line.GetEventHandler().OnMoveLink(dc)
|
|
|
|
def OnDrawContents(self, dc):
|
|
"""The draw contents handler."""
|
|
if not self._regions:
|
|
return
|
|
|
|
bound_x, bound_y = self.GetBoundingBoxMin()
|
|
|
|
if self._pen:
|
|
dc.SetPen(self._pen)
|
|
|
|
for region in self._regions:
|
|
if region.GetFont():
|
|
dc.SetFont(region.GetFont())
|
|
|
|
dc.SetTextForeground(region.GetActualColourObject())
|
|
dc.SetBackgroundMode(wx.TRANSPARENT)
|
|
if not self._formatted:
|
|
CentreText(dc, region.GetFormattedText(), self._xpos, self._ypos, bound_x - 2 * self._textMarginX, bound_y - 2 * self._textMarginY, region.GetFormatMode())
|
|
self._formatted = True
|
|
|
|
if not self.GetDisableLabel():
|
|
DrawFormattedText(dc, region.GetFormattedText(), self._xpos, self._ypos, bound_x - 2 * self._textMarginX, bound_y - 2 * self._textMarginY, region.GetFormatMode())
|
|
|
|
def DrawContents(self, dc):
|
|
"""
|
|
Draw the internal graphic of the shape (such as text).
|
|
|
|
Do not override this function: override OnDrawContents, which
|
|
is called by this function.
|
|
"""
|
|
self.GetEventHandler().OnDrawContents(dc)
|
|
|
|
def OnSize(self, x, y):
|
|
"""not implemented???"""
|
|
pass
|
|
|
|
def OnMovePre(self, dc, x, y, old_x, old_y, display = True):
|
|
return True
|
|
|
|
def OnErase(self, dc):
|
|
"""The erase handler."""
|
|
if not self._visible:
|
|
return
|
|
|
|
# Erase links
|
|
for line in self._lines:
|
|
line.GetEventHandler().OnErase(dc)
|
|
|
|
self.GetEventHandler().OnEraseContents(dc)
|
|
|
|
def OnEraseContents(self, dc):
|
|
"""The erase contents handler."""
|
|
if not self._visible:
|
|
return
|
|
|
|
xp, yp = self.GetX(), self.GetY()
|
|
minX, minY = self.GetBoundingBoxMin()
|
|
maxX, maxY = self.GetBoundingBoxMax()
|
|
|
|
topLeftX = xp - maxX / 2.0 - 2
|
|
topLeftY = yp - maxY / 2.0 - 2
|
|
|
|
penWidth = 0
|
|
if self._pen:
|
|
penWidth = self._pen.GetWidth()
|
|
|
|
dc.SetPen(self.GetBackgroundPen())
|
|
dc.SetBrush(self.GetBackgroundBrush())
|
|
|
|
dc.DrawRectangle(topLeftX - penWidth, topLeftY - penWidth, maxX + penWidth * 2 + 4, maxY + penWidth * 2 + 4)
|
|
|
|
def EraseLinks(self, dc, attachment = -1, recurse = False):
|
|
"""
|
|
Erase links attached to this shape, but do not repair damage
|
|
caused to other shapes.
|
|
|
|
:param `dc`: the device context
|
|
:param `attachment`: ???
|
|
:param `recurse`: if `True` recurse through the children
|
|
|
|
"""
|
|
if not self._visible:
|
|
return
|
|
|
|
for line in self._lines:
|
|
if attachment == -1 or (line.GetTo() == self and line.GetAttachmentTo() == attachment or line.GetFrom() == self and line.GetAttachmentFrom() == attachment):
|
|
line.GetEventHandler().OnErase(dc)
|
|
|
|
if recurse:
|
|
for child in self._children:
|
|
child.EraseLinks(dc, attachment, recurse)
|
|
|
|
def DrawLinks(self, dc, attachment = -1, recurse = False):
|
|
"""
|
|
Draws any lines linked to this shape.
|
|
|
|
:param `dc`: the device context
|
|
:param `attachment`: ???
|
|
:param `recurse`: if `True` recurse through the children
|
|
|
|
"""
|
|
if not self._visible:
|
|
return
|
|
|
|
for line in self._lines:
|
|
if attachment == -1 or (line.GetTo() == self and line.GetAttachmentTo() == attachment or line.GetFrom() == self and line.GetAttachmentFrom() == attachment):
|
|
line.Draw(dc)
|
|
|
|
if recurse:
|
|
for child in self._children:
|
|
child.DrawLinks(dc, attachment, recurse)
|
|
|
|
# Returns TRUE if pt1 <= pt2 in the sense that one point comes before
|
|
# another on an edge of the shape.
|
|
# attachmentPoint is the attachment point (= side) in question.
|
|
|
|
# This is the default, rectangular implementation.
|
|
def AttachmentSortTest(self, attachmentPoint, pt1, pt2):
|
|
"""
|
|
Return TRUE if pt1 is less than or equal to pt2, in the sense
|
|
that one point comes before another on an edge of the shape.
|
|
|
|
attachment is the attachment point (side) in question.
|
|
|
|
This function is used in Shape.MoveLineToNewAttachment to determine
|
|
the new line ordering.
|
|
"""
|
|
physicalAttachment = self.LogicalToPhysicalAttachment(attachmentPoint)
|
|
if physicalAttachment in [0, 2]:
|
|
return pt1[0] <= pt2[0]
|
|
elif physicalAttachment in [1, 3]:
|
|
return pt1[1] <= pt2[1]
|
|
|
|
return False
|
|
|
|
def MoveLineToNewAttachment(self, dc, to_move, x, y):
|
|
"""
|
|
Move the given line (which must already be attached to the shape)
|
|
to a different attachment point on the shape, or a different order
|
|
on the same attachment.
|
|
|
|
Calls Shape.AttachmentSortTest and then
|
|
ShapeEvtHandler.OnChangeAttachment.
|
|
"""
|
|
if self.GetAttachmentMode() == ATTACHMENT_MODE_NONE:
|
|
return False
|
|
|
|
# Is (x, y) on this object? If so, find the new attachment point
|
|
# the user has moved the point to
|
|
hit = self.HitTest(x, y)
|
|
if not hit:
|
|
return False
|
|
|
|
newAttachment, distance = hit
|
|
|
|
self.EraseLinks(dc)
|
|
|
|
if to_move.GetTo() == self:
|
|
oldAttachment = to_move.GetAttachmentTo()
|
|
else:
|
|
oldAttachment = to_move.GetAttachmentFrom()
|
|
|
|
# The links in a new ordering
|
|
# First, add all links to the new list
|
|
newOrdering = self._lines[:]
|
|
|
|
# Delete the line object from the list of links; we're going to move
|
|
# it to another position in the list
|
|
del newOrdering[newOrdering.index(to_move)]
|
|
|
|
old_x = -99999.9
|
|
old_y = -99999.9
|
|
|
|
found = False
|
|
|
|
for line in newOrdering:
|
|
if line.GetTo() == self and oldAttachment == line.GetAttachmentTo() or \
|
|
line.GetFrom() == self and oldAttachment == line.GetAttachmentFrom():
|
|
startX, startY, endX, endY = line.GetEnds()
|
|
if line.GetTo() == self:
|
|
xp = endX
|
|
yp = endY
|
|
else:
|
|
xp = startX
|
|
yp = startY
|
|
|
|
thisPoint = wx.RealPoint(xp, yp)
|
|
lastPoint = wx.RealPoint(old_x, old_y)
|
|
newPoint = wx.RealPoint(x, y)
|
|
|
|
if self.AttachmentSortTest(newAttachment, newPoint, thisPoint) and self.AttachmentSortTest(newAttachment, lastPoint, newPoint):
|
|
found = True
|
|
newOrdering.insert(newOrdering.index(line), to_move)
|
|
|
|
old_x = xp
|
|
old_y = yp
|
|
if found:
|
|
break
|
|
|
|
if not found:
|
|
newOrdering.append(to_move)
|
|
|
|
self.GetEventHandler().OnChangeAttachment(newAttachment, to_move, newOrdering)
|
|
return True
|
|
|
|
def OnChangeAttachment(self, attachment, line, ordering):
|
|
"""Change attachment handler."""
|
|
if line.GetTo() == self:
|
|
line.SetAttachmentTo(attachment)
|
|
else:
|
|
line.SetAttachmentFrom(attachment)
|
|
|
|
self.ApplyAttachmentOrdering(ordering)
|
|
|
|
dc = wx.MemoryDC()
|
|
dc.SelectObject(self.GetCanvas()._Buffer)
|
|
self.MoveLinks(dc)
|
|
|
|
if not self.GetCanvas().GetQuickEditMode():
|
|
self.GetCanvas().Redraw(dc)
|
|
|
|
# Reorders the lines according to the given list
|
|
def ApplyAttachmentOrdering(self, linesToSort):
|
|
"""
|
|
Apply the line ordering in linesToSort to the shape, to reorder
|
|
the way lines are attached.
|
|
"""
|
|
linesStore = self._lines[:]
|
|
|
|
self._lines = []
|
|
|
|
for line in linesToSort:
|
|
if line in linesStore:
|
|
del linesStore[linesStore.index(line)]
|
|
self._lines.append(line)
|
|
|
|
# Now add any lines that haven't been listed in linesToSort
|
|
self._lines += linesStore
|
|
|
|
def SortLines(self, attachment, linesToSort):
|
|
"""
|
|
Reorder the lines coming into the node image at this attachment
|
|
position, in the order in which they appear in linesToSort.
|
|
|
|
Any remaining lines not in the list will be added to the end.
|
|
"""
|
|
# This is a temporary store of all the lines at this attachment
|
|
# point. We'll tick them off as we've processed them.
|
|
linesAtThisAttachment = []
|
|
|
|
for line in self._lines[:]:
|
|
if line.GetTo() == self and line.GetAttachmentTo() == attachment or \
|
|
line.GetFrom() == self and line.GetAttachmentFrom() == attachment:
|
|
linesAtThisAttachment.append(line)
|
|
del self._lines[self._lines.index(line)]
|
|
|
|
for line in linesToSort:
|
|
if line in linesAtThisAttachment:
|
|
# Done this one
|
|
del linesAtThisAttachment[linesAtThisAttachment.index(line)]
|
|
self._lines.append(line)
|
|
|
|
# Now add any lines that haven't been listed in linesToSort
|
|
self._lines += linesAtThisAttachment
|
|
|
|
def OnHighlight(self, dc):
|
|
"""not implemented???"""
|
|
pass
|
|
|
|
def OnLeftClick(self, x, y, keys = 0, attachment = 0):
|
|
"""The left click handler."""
|
|
if self._sensitivity & OP_CLICK_LEFT != OP_CLICK_LEFT:
|
|
if self._parent:
|
|
attachment, dist = self._parent.HitTest(x, y)
|
|
self._parent.GetEventHandler().OnLeftClick(x, y, keys, attachment)
|
|
|
|
def OnRightClick(self, x, y, keys = 0, attachment = 0):
|
|
"""The right click handler."""
|
|
if self._sensitivity & OP_CLICK_RIGHT != OP_CLICK_RIGHT:
|
|
attachment, dist = self._parent.HitTest(x, y)
|
|
self._parent.GetEventHandler().OnRightClick(x, y, keys, attachment)
|
|
|
|
def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
|
|
"""The drag left handler."""
|
|
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
|
|
|
|
# use the DCOverlay stuff, note that drawing is done to the ClientDC
|
|
dc = wx.ClientDC(self.GetCanvas())
|
|
odc = wx.DCOverlay(self.GetCanvas()._Overlay, dc)
|
|
dc.SetLogicalFunction(OGLRBLF)
|
|
|
|
dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.PENSTYLE_DOT)
|
|
|
|
dc.SetPen(dottedPen)
|
|
dc.SetBrush(wx.TRANSPARENT_BRUSH)
|
|
|
|
xx = x + DragOffsetX
|
|
yy = y + DragOffsetY
|
|
|
|
xx, yy = self._canvas.Snap(xx, yy)
|
|
w, h = self.GetBoundingBoxMax()
|
|
self.GetEventHandler().OnDrawOutline(dc, xx, yy, w, h)
|
|
|
|
def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
|
|
"""The begin drag left handler."""
|
|
global DragOffsetX, DragOffsetY
|
|
|
|
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
|
|
|
|
DragOffsetX = self._xpos - x
|
|
DragOffsetY = self._ypos - y
|
|
|
|
# use the DCOverlay stuff, note that drawing is done to the ClientDC
|
|
dc = wx.ClientDC(self.GetCanvas())
|
|
odc = wx.DCOverlay(self.GetCanvas()._Overlay, dc)
|
|
dc.SetLogicalFunction(OGLRBLF)
|
|
|
|
# New policy: don't erase shape until end of drag.
|
|
# self.Erase(dc)
|
|
xx = x + DragOffsetX
|
|
yy = y + DragOffsetY
|
|
xx, yy = self._canvas.Snap(xx, yy)
|
|
|
|
dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.PENSTYLE_DOT)
|
|
dc.SetPen(dottedPen)
|
|
dc.SetBrush(wx.TRANSPARENT_BRUSH)
|
|
|
|
w, h = self.GetBoundingBoxMax()
|
|
self.GetEventHandler().OnDrawOutline(dc, xx, yy, w, h)
|
|
self._canvas.CaptureMouse()
|
|
|
|
def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
|
|
"""The end drag left handler."""
|
|
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
|
|
|
|
# use the DCOverlay stuff, note that drawing is done to the ClientDC
|
|
dc = wx.ClientDC(self.GetCanvas())
|
|
odc = wx.DCOverlay(self.GetCanvas()._Overlay, dc)
|
|
dc.SetLogicalFunction(wx.COPY)
|
|
|
|
xx = x + DragOffsetX
|
|
yy = y + DragOffsetY
|
|
xx, yy = self._canvas.Snap(xx, yy)
|
|
|
|
# New policy: erase shape at end of drag.
|
|
self.Erase(dc)
|
|
|
|
self.Move(dc, xx, yy)
|
|
if self._canvas and not self._canvas.GetQuickEditMode():
|
|
self._canvas.Redraw(dc)
|
|
|
|
def OnDragRight(self, draw, x, y, keys = 0, attachment = 0):
|
|
"""The drag right handler."""
|
|
if self._sensitivity & OP_DRAG_RIGHT != OP_DRAG_RIGHT:
|
|
if self._parent:
|
|
attachment, dist = self._parent.HitTest(x, y)
|
|
self._parent.GetEventHandler().OnDragRight(draw, x, y, keys, attachment)
|
|
return
|
|
|
|
def OnBeginDragRight(self, x, y, keys = 0, attachment = 0):
|
|
"""The begin drag right handler."""
|
|
if self._sensitivity & OP_DRAG_RIGHT != OP_DRAG_RIGHT:
|
|
if self._parent:
|
|
attachment, dist = self._parent.HitTest(x, y)
|
|
self._parent.GetEventHandler().OnBeginDragRight(x, y, keys, attachment)
|
|
return
|
|
|
|
def OnEndDragRight(self, x, y, keys = 0, attachment = 0):
|
|
"""The end drag right handler."""
|
|
if self._sensitivity & OP_DRAG_RIGHT != OP_DRAG_RIGHT:
|
|
if self._parent:
|
|
attachment, dist = self._parent.HitTest(x, y)
|
|
self._parent.GetEventHandler().OnEndDragRight(x, y, keys, attachment)
|
|
return
|
|
|
|
def OnDrawOutline(self, dc, x, y, w, h):
|
|
"""The draw outline handler."""
|
|
points = [[x - w / 2.0, y - h / 2.0],
|
|
[x + w / 2.0, y - h / 2.0],
|
|
[x + w / 2.0, y + h / 2.0],
|
|
[x - w / 2.0, y + h / 2.0],
|
|
[x - w / 2.0, y - h / 2.0],
|
|
]
|
|
|
|
dc.DrawLines(points)
|
|
|
|
def Attach(self, can):
|
|
"""Set the shape's internal canvas pointer to point to the given canvas."""
|
|
self._canvas = can
|
|
|
|
def Detach(self):
|
|
"""Disassociates the shape from its canvas."""
|
|
self._canvas = None
|
|
|
|
def Move(self, dc, x, y, display = True):
|
|
"""
|
|
Move the shape to the given position.
|
|
|
|
:param `dc`: the device context
|
|
:param `x`: the x position
|
|
:param `y`: the y position
|
|
:param `display`: if `True` redraw
|
|
|
|
"""
|
|
old_x = self._xpos
|
|
old_y = self._ypos
|
|
|
|
if not self.GetEventHandler().OnMovePre(dc, x, y, old_x, old_y, display):
|
|
return
|
|
|
|
self._xpos, self._ypos = x, y
|
|
|
|
self.ResetControlPoints()
|
|
|
|
if display:
|
|
self.Draw(dc)
|
|
|
|
self.MoveLinks(dc)
|
|
|
|
self.GetEventHandler().OnMovePost(dc, x, y, old_x, old_y, display)
|
|
|
|
def MoveLinks(self, dc):
|
|
"""Redraw all the lines attached to the shape."""
|
|
self.GetEventHandler().OnMoveLinks(dc)
|
|
|
|
def Draw(self, dc):
|
|
"""
|
|
Draw the whole shape and any lines attached to it.
|
|
|
|
Do not override this function: override OnDraw, which is called
|
|
by this function.
|
|
"""
|
|
if self._visible:
|
|
self.GetEventHandler().OnDraw(dc)
|
|
self.GetEventHandler().OnDrawContents(dc)
|
|
self.GetEventHandler().OnDrawControlPoints(dc)
|
|
self.GetEventHandler().OnDrawBranches(dc)
|
|
|
|
def Flash(self):
|
|
"""Flash the shape."""
|
|
if self.GetCanvas():
|
|
dc = wx.MemoryDC()
|
|
dc.SelectObject(self.GetCanvas()._Buffer)
|
|
dc.SetLogicalFunction(OGLRBLF)
|
|
self.Draw(dc)
|
|
dc.SetLogicalFunction(wx.COPY)
|
|
self.Draw(dc)
|
|
|
|
def Show(self, show):
|
|
"""Set a flag indicating whether the shape should be drawn."""
|
|
self._visible = show
|
|
for child in self._children:
|
|
child.Show(show)
|
|
|
|
def Erase(self, dc):
|
|
"""
|
|
Erase the shape.
|
|
|
|
Does not repair damage caused to other shapes.
|
|
"""
|
|
self.GetEventHandler().OnErase(dc)
|
|
self.GetEventHandler().OnEraseControlPoints(dc)
|
|
self.GetEventHandler().OnDrawBranches(dc, erase = True)
|
|
|
|
def EraseContents(self, dc):
|
|
"""
|
|
Erase the shape contents, that is, the area within the shape's
|
|
minimum bounding box.
|
|
"""
|
|
self.GetEventHandler().OnEraseContents(dc)
|
|
|
|
def AddText(self, string):
|
|
"""Add a line of text to the shape's default text region."""
|
|
if not self._regions:
|
|
return
|
|
|
|
region = self._regions[0]
|
|
#region.ClearText()
|
|
new_line = ShapeTextLine(0, 0, string)
|
|
text = region.GetFormattedText()
|
|
text.append(new_line)
|
|
|
|
self._formatted = False
|
|
|
|
def SetSize(self, x, y, recursive = True):
|
|
"""Set the shape's size.
|
|
|
|
:param `x`: the x position
|
|
:param `y`: the y position
|
|
:param `recursive`: not used
|
|
|
|
"""
|
|
self.SetAttachmentSize(x, y)
|
|
self.SetDefaultRegionSize()
|
|
|
|
def SetAttachmentSize(self, w, h):
|
|
"""
|
|
Set the attachment size.
|
|
|
|
:param `w`: width
|
|
:param `h`: height
|
|
|
|
"""
|
|
width, height = self.GetBoundingBoxMin()
|
|
if width == 0:
|
|
scaleX = 1.0
|
|
else:
|
|
scaleX = float(w) / width
|
|
if height == 0:
|
|
scaleY = 1.0
|
|
else:
|
|
scaleY = float(h) / height
|
|
|
|
for point in self._attachmentPoints:
|
|
point._x = point._x * scaleX
|
|
point._y = point._y * scaleY
|
|
|
|
# Add line FROM this object
|
|
def AddLine(self, line, other, attachFrom = 0, attachTo = 0, positionFrom = -1, positionTo = -1):
|
|
"""
|
|
Add a line between this shape and the given other shape, at the
|
|
specified attachment points.
|
|
|
|
:param `line`: the line an instance of :class:`~lib.ogl.LineShape`
|
|
:param `other`: the other shape, an instance of :class:`Shape`
|
|
:param `attachFrom`: the attachment from point ???
|
|
:param `attachTo`: the attachment to point ???
|
|
:param `positionFrom`: the from position
|
|
:param `positionTo`: the to position
|
|
|
|
:note: The position in the list of lines at each end can also be
|
|
specified, so that the line will be drawn at a particular point on
|
|
its attachment point.
|
|
|
|
"""
|
|
if positionFrom == -1:
|
|
if not line in self._lines:
|
|
self._lines.append(line)
|
|
else:
|
|
# Don't preserve old ordering if we have new ordering instructions
|
|
try:
|
|
self._lines.remove(line)
|
|
except ValueError:
|
|
pass
|
|
if positionFrom < len(self._lines):
|
|
self._lines.insert(positionFrom, line)
|
|
else:
|
|
self._lines.append(line)
|
|
|
|
if positionTo == -1:
|
|
if not other in other._lines:
|
|
other._lines.append(line)
|
|
else:
|
|
# Don't preserve old ordering if we have new ordering instructions
|
|
try:
|
|
other._lines.remove(line)
|
|
except ValueError:
|
|
pass
|
|
if positionTo < len(other._lines):
|
|
other._lines.insert(positionTo, line)
|
|
else:
|
|
other._lines.append(line)
|
|
|
|
line.SetFrom(self)
|
|
line.SetTo(other)
|
|
line.SetAttachments(attachFrom, attachTo)
|
|
|
|
dc = wx.MemoryDC()
|
|
dc.SelectObject(self.GetCanvas()._Buffer)
|
|
self.MoveLinks(dc)
|
|
|
|
def RemoveLine(self, line):
|
|
"""
|
|
Remove the given line from the shape's list of attached lines.
|
|
|
|
:param `line`: an instance of :class:`~lib.ogl.LineShape`
|
|
|
|
"""
|
|
if line.GetFrom() == self:
|
|
line.GetTo()._lines.remove(line)
|
|
else:
|
|
line.GetFrom()._lines.remove(line)
|
|
|
|
self._lines.remove(line)
|
|
|
|
# Default - make 6 control points
|
|
def MakeControlPoints(self):
|
|
"""
|
|
Make a list of control points (draggable handles) appropriate to
|
|
the shape.
|
|
"""
|
|
maxX, maxY = self.GetBoundingBoxMax()
|
|
minX, minY = self.GetBoundingBoxMin()
|
|
|
|
widthMin = minX + CONTROL_POINT_SIZE + 2
|
|
heightMin = minY + CONTROL_POINT_SIZE + 2
|
|
|
|
# Offsets from main object
|
|
top = -heightMin / 2.0
|
|
bottom = heightMin / 2.0 + (maxY - minY)
|
|
left = -widthMin / 2.0
|
|
right = widthMin / 2.0 + (maxX - minX)
|
|
|
|
control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, left, top, CONTROL_POINT_DIAGONAL)
|
|
self._canvas.AddShape(control)
|
|
self._controlPoints.append(control)
|
|
|
|
control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, 0, top, CONTROL_POINT_VERTICAL)
|
|
self._canvas.AddShape(control)
|
|
self._controlPoints.append(control)
|
|
|
|
control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, right, top, CONTROL_POINT_DIAGONAL)
|
|
self._canvas.AddShape(control)
|
|
self._controlPoints.append(control)
|
|
|
|
control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, right, 0, CONTROL_POINT_HORIZONTAL)
|
|
self._canvas.AddShape(control)
|
|
self._controlPoints.append(control)
|
|
|
|
control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, right, bottom, CONTROL_POINT_DIAGONAL)
|
|
self._canvas.AddShape(control)
|
|
self._controlPoints.append(control)
|
|
|
|
control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, 0, bottom, CONTROL_POINT_VERTICAL)
|
|
self._canvas.AddShape(control)
|
|
self._controlPoints.append(control)
|
|
|
|
control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, left, bottom, CONTROL_POINT_DIAGONAL)
|
|
self._canvas.AddShape(control)
|
|
self._controlPoints.append(control)
|
|
|
|
control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, left, 0, CONTROL_POINT_HORIZONTAL)
|
|
self._canvas.AddShape(control)
|
|
self._controlPoints.append(control)
|
|
|
|
def MakeMandatoryControlPoints(self):
|
|
"""
|
|
Make the mandatory control points.
|
|
|
|
For example, the control point on a dividing line should appear even
|
|
if the divided rectangle shape's handles should not appear (because
|
|
it is the child of a composite, and children are not resizable).
|
|
"""
|
|
for child in self._children:
|
|
child.MakeMandatoryControlPoints()
|
|
|
|
def ResetMandatoryControlPoints(self):
|
|
"""Reset the mandatory control points."""
|
|
for child in self._children:
|
|
child.ResetMandatoryControlPoints()
|
|
|
|
def ResetControlPoints(self):
|
|
"""
|
|
Reset the positions of the control points (for instance when the
|
|
shape's shape has changed).
|
|
"""
|
|
self.ResetMandatoryControlPoints()
|
|
|
|
if len(self._controlPoints) == 0:
|
|
return
|
|
|
|
maxX, maxY = self.GetBoundingBoxMax()
|
|
minX, minY = self.GetBoundingBoxMin()
|
|
|
|
widthMin = minX + CONTROL_POINT_SIZE + 2
|
|
heightMin = minY + CONTROL_POINT_SIZE + 2
|
|
|
|
# Offsets from main object
|
|
top = -heightMin / 2.0
|
|
bottom = heightMin / 2.0 + (maxY - minY)
|
|
left = -widthMin / 2.0
|
|
right = widthMin / 2.0 + (maxX - minX)
|
|
|
|
self._controlPoints[0]._xoffset = left
|
|
self._controlPoints[0]._yoffset = top
|
|
|
|
self._controlPoints[1]._xoffset = 0
|
|
self._controlPoints[1]._yoffset = top
|
|
|
|
self._controlPoints[2]._xoffset = right
|
|
self._controlPoints[2]._yoffset = top
|
|
|
|
self._controlPoints[3]._xoffset = right
|
|
self._controlPoints[3]._yoffset = 0
|
|
|
|
self._controlPoints[4]._xoffset = right
|
|
self._controlPoints[4]._yoffset = bottom
|
|
|
|
self._controlPoints[5]._xoffset = 0
|
|
self._controlPoints[5]._yoffset = bottom
|
|
|
|
self._controlPoints[6]._xoffset = left
|
|
self._controlPoints[6]._yoffset = bottom
|
|
|
|
self._controlPoints[7]._xoffset = left
|
|
self._controlPoints[7]._yoffset = 0
|
|
|
|
def DeleteControlPoints(self, dc = None):
|
|
"""
|
|
Delete the control points (or handles) for the shape.
|
|
|
|
Does not redraw the shape.
|
|
"""
|
|
for control in self._controlPoints[:]:
|
|
if dc:
|
|
control.GetEventHandler().OnErase(dc)
|
|
control.Delete()
|
|
self._controlPoints.remove(control)
|
|
self._controlPoints = []
|
|
|
|
# Children of divisions are contained objects,
|
|
# so stop here
|
|
if not isinstance(self, DivisionShape):
|
|
for child in self._children:
|
|
child.DeleteControlPoints(dc)
|
|
|
|
def OnDrawControlPoints(self, dc):
|
|
"""The draw control points handler."""
|
|
if not self._drawHandles:
|
|
return
|
|
|
|
dc.SetBrush(wx.BLACK_BRUSH)
|
|
dc.SetPen(wx.BLACK_PEN)
|
|
|
|
for control in self._controlPoints:
|
|
control.Draw(dc)
|
|
|
|
# Children of divisions are contained objects,
|
|
# so stop here.
|
|
# This test bypasses the type facility for speed
|
|
# (critical when drawing)
|
|
|
|
if not isinstance(self, DivisionShape):
|
|
for child in self._children:
|
|
child.GetEventHandler().OnDrawControlPoints(dc)
|
|
|
|
def OnEraseControlPoints(self, dc):
|
|
"""The erase control points handler."""
|
|
for control in self._controlPoints:
|
|
control.Erase(dc)
|
|
|
|
if not isinstance(self, DivisionShape):
|
|
for child in self._children:
|
|
child.GetEventHandler().OnEraseControlPoints(dc)
|
|
|
|
def Select(self, select, dc = None):
|
|
"""
|
|
Select or deselect the given shape, drawing or erasing control points
|
|
(handles) as necessary.
|
|
|
|
:param `select`: `True` to select
|
|
:param `dc`: the device context
|
|
|
|
"""
|
|
self._selected = select
|
|
if select:
|
|
self.MakeControlPoints()
|
|
# Children of divisions are contained objects,
|
|
# so stop here
|
|
if not isinstance(self, DivisionShape):
|
|
for child in self._children:
|
|
child.MakeMandatoryControlPoints()
|
|
if dc:
|
|
self.GetEventHandler().OnDrawControlPoints(dc)
|
|
else:
|
|
self.DeleteControlPoints(dc)
|
|
if not isinstance(self, DivisionShape):
|
|
for child in self._children:
|
|
child.DeleteControlPoints(dc)
|
|
|
|
def Selected(self):
|
|
"""`True` if the shape is currently selected."""
|
|
return self._selected
|
|
|
|
def AncestorSelected(self):
|
|
"""`True` if the shape's ancestor is currently selected."""
|
|
if self._selected:
|
|
return True
|
|
if not self.GetParent():
|
|
return False
|
|
return self.GetParent().AncestorSelected()
|
|
|
|
def GetNumberOfAttachments(self):
|
|
"""Get the number of attachment points for this shape."""
|
|
# Should return the MAXIMUM attachment point id here,
|
|
# so higher-level functions can iterate through all attachments,
|
|
# even if they're not contiguous.
|
|
|
|
if len(self._attachmentPoints) == 0:
|
|
return 4
|
|
else:
|
|
maxN = 3
|
|
for point in self._attachmentPoints:
|
|
if point._id > maxN:
|
|
maxN = point._id
|
|
return maxN + 1
|
|
|
|
def AttachmentIsValid(self, attachment):
|
|
"""`True` if attachment is a valid attachment point."""
|
|
if len(self._attachmentPoints) == 0:
|
|
return attachment in range(4)
|
|
|
|
for point in self._attachmentPoints:
|
|
if point._id == attachment:
|
|
return True
|
|
return False
|
|
|
|
def GetAttachmentPosition(self, attachment, nth = 0, no_arcs = 1, line = None):
|
|
"""
|
|
Get the position at which the given attachment point should be drawn.
|
|
|
|
:param `attachment`: the attachment ???
|
|
:param `nth`: get nth attachment ???
|
|
:param `no_arcs`: ???
|
|
:param `line`: ???
|
|
|
|
If attachment isn't found among the attachment points of the shape,
|
|
returns None.
|
|
"""
|
|
if self._attachmentMode == ATTACHMENT_MODE_NONE:
|
|
return self._xpos, self._ypos
|
|
elif self._attachmentMode == ATTACHMENT_MODE_BRANCHING:
|
|
pt, stemPt = self.GetBranchingAttachmentPoint(attachment, nth)
|
|
return pt[0], pt[1]
|
|
elif self._attachmentMode == ATTACHMENT_MODE_EDGE:
|
|
if len(self._attachmentPoints):
|
|
for point in self._attachmentPoints:
|
|
if point._id == attachment:
|
|
return self._xpos + point._x, self._ypos + point._y
|
|
return None
|
|
else:
|
|
# Assume is rectangular
|
|
w, h = self.GetBoundingBoxMax()
|
|
top = self._ypos + h / 2.0
|
|
bottom = self._ypos - h / 2.0
|
|
left = self._xpos - w / 2.0
|
|
right = self._xpos + w / 2.0
|
|
|
|
# wtf?
|
|
line and line.IsEnd(self)
|
|
|
|
physicalAttachment = self.LogicalToPhysicalAttachment(attachment)
|
|
|
|
# Simplified code
|
|
if physicalAttachment == 0:
|
|
pt = self.CalcSimpleAttachment((left, bottom), (right, bottom), nth, no_arcs, line)
|
|
elif physicalAttachment == 1:
|
|
pt = self.CalcSimpleAttachment((right, bottom), (right, top), nth, no_arcs, line)
|
|
elif physicalAttachment == 2:
|
|
pt = self.CalcSimpleAttachment((left, top), (right, top), nth, no_arcs, line)
|
|
elif physicalAttachment == 3:
|
|
pt = self.CalcSimpleAttachment((left, bottom), (left, top), nth, no_arcs, line)
|
|
else:
|
|
return None
|
|
return pt[0], pt[1]
|
|
return None
|
|
|
|
def GetBoundingBoxMax(self):
|
|
"""
|
|
Get the maximum bounding box for the shape, taking into account
|
|
external features such as shadows.
|
|
"""
|
|
ww, hh = self.GetBoundingBoxMin()
|
|
if self._shadowMode != SHADOW_NONE:
|
|
ww += self._shadowOffsetX
|
|
hh += self._shadowOffsetY
|
|
return ww, hh
|
|
|
|
def GetBoundingBoxMin(self):
|
|
"""
|
|
Get the minimum bounding box for the shape, that defines the area
|
|
available for drawing the contents (such as text).
|
|
|
|
Must be overridden.
|
|
"""
|
|
return 0, 0
|
|
|
|
def HasDescendant(self, image):
|
|
"""
|
|
Is image a descendant of this composite.
|
|
|
|
:param `image`: the image, is this a shape???
|
|
:returns: `True` if it is a descendant
|
|
|
|
"""
|
|
if image == self:
|
|
return True
|
|
for child in self._children:
|
|
if child.HasDescendant(image):
|
|
return True
|
|
return False
|
|
|
|
# Assuming the attachment lies along a vertical or horizontal line,
|
|
# calculate the position on that point.
|
|
def CalcSimpleAttachment(self, pt1, pt2, nth, noArcs, line):
|
|
"""
|
|
Assuming the attachment lies along a vertical or horizontal line,
|
|
calculate the position on that point.
|
|
|
|
:param `pt1`: The first point of the line repesenting the edge of
|
|
the shape
|
|
:param `pt2`: The second point of the line representing the edge of
|
|
the shape
|
|
:param `nth`: The position on the edge (for example there may be 6
|
|
lines at this attachment point, and this may be the 2nd line.
|
|
:param `noArcs`: The number of lines at this edge.
|
|
:param `line`: The line shape.
|
|
|
|
:note: This function expects the line to be either vertical or horizontal,
|
|
and determines which.
|
|
|
|
"""
|
|
isEnd = line and line.IsEnd(self)
|
|
|
|
# Are we horizontal or vertical?
|
|
isHorizontal = RoughlyEqual(pt1[1], pt2[1])
|
|
|
|
if isHorizontal:
|
|
if pt1[0] > pt2[0]:
|
|
firstPoint = pt2
|
|
secondPoint = pt1
|
|
else:
|
|
firstPoint = pt1
|
|
secondPoint = pt2
|
|
|
|
if self._spaceAttachments:
|
|
if line and line.GetAlignmentType(isEnd) == LINE_ALIGNMENT_TO_NEXT_HANDLE:
|
|
# Align line according to the next handle along
|
|
point = line.GetNextControlPoint(self)
|
|
if point[0] < firstPoint[0]:
|
|
x = firstPoint[0]
|
|
elif point[0] > secondPoint[0]:
|
|
x = secondPoint[0]
|
|
else:
|
|
x = point[0]
|
|
else:
|
|
x = firstPoint[0] + (nth + 1) * (secondPoint[0] - firstPoint[0]) / (noArcs + 1.0)
|
|
else:
|
|
x = (secondPoint[0] - firstPoint[0]) / 2.0 # Midpoint
|
|
y = pt1[1]
|
|
else:
|
|
assert RoughlyEqual(pt1[0], pt2[0])
|
|
|
|
if pt1[1] > pt2[1]:
|
|
firstPoint = pt2
|
|
secondPoint = pt1
|
|
else:
|
|
firstPoint = pt1
|
|
secondPoint = pt2
|
|
|
|
if self._spaceAttachments:
|
|
if line and line.GetAlignmentType(isEnd) == LINE_ALIGNMENT_TO_NEXT_HANDLE:
|
|
# Align line according to the next handle along
|
|
point = line.GetNextControlPoint(self)
|
|
if point[1] < firstPoint[1]:
|
|
y = firstPoint[1]
|
|
elif point[1] > secondPoint[1]:
|
|
y = secondPoint[1]
|
|
else:
|
|
y = point[1]
|
|
else:
|
|
y = firstPoint[1] + (nth + 1) * (secondPoint[1] - firstPoint[1]) / (noArcs + 1.0)
|
|
else:
|
|
y = (secondPoint[1] - firstPoint[1]) / 2.0 # Midpoint
|
|
x = pt1[0]
|
|
|
|
return x, y
|
|
|
|
def GetLinePosition(self, line):
|
|
"""
|
|
Get the zero-based position of line in the list of lines
|
|
for this shape.
|
|
|
|
:param `line`: line to find position for
|
|
|
|
"""
|
|
try:
|
|
return self._lines.index(line)
|
|
except:
|
|
return 0
|
|
|
|
|
|
# |________|
|
|
# | <- root
|
|
# | <- neck
|
|
# shoulder1 ->---------<- shoulder2
|
|
# | | | | |
|
|
# <- branching attachment point N-1
|
|
|
|
def GetBranchingAttachmentInfo(self, attachment):
|
|
"""
|
|
Get information about where branching connections go.
|
|
|
|
:param `attachment`: ???
|
|
:returns: `False` if there are no lines at this attachment.
|
|
"""
|
|
physicalAttachment = self.LogicalToPhysicalAttachment(attachment)
|
|
|
|
# Number of lines at this attachment
|
|
lineCount = self.GetAttachmentLineCount(attachment)
|
|
|
|
if not lineCount:
|
|
return False
|
|
|
|
totalBranchLength = self._branchSpacing * (lineCount - 1)
|
|
root = self.GetBranchingAttachmentRoot(attachment)
|
|
|
|
neck = wx.RealPoint()
|
|
shoulder1 = wx.RealPoint()
|
|
shoulder2 = wx.RealPoint()
|
|
|
|
# Assume that we have attachment points 0 to 3: top, right, bottom, left
|
|
if physicalAttachment == 0:
|
|
neck[0] = self.GetX()
|
|
neck[1] = root[1] - self._branchNeckLength
|
|
|
|
shoulder1[0] = root[0] - totalBranchLength / 2.0
|
|
shoulder2[0] = root[0] + totalBranchLength / 2.0
|
|
|
|
shoulder1[1] = neck[1]
|
|
shoulder2[1] = neck[1]
|
|
elif physicalAttachment == 1:
|
|
neck[0] = root[0] + self._branchNeckLength
|
|
neck[1] = root[1]
|
|
|
|
shoulder1[0] = neck[0]
|
|
shoulder2[0] = neck[0]
|
|
|
|
shoulder1[1] = neck[1] - totalBranchLength / 2.0
|
|
shoulder1[1] = neck[1] + totalBranchLength / 2.0
|
|
elif physicalAttachment == 2:
|
|
neck[0] = self.GetX()
|
|
neck[1] = root[1] + self._branchNeckLength
|
|
|
|
shoulder1[0] = root[0] - totalBranchLength / 2.0
|
|
shoulder2[0] = root[0] + totalBranchLength / 2.0
|
|
|
|
shoulder1[1] = neck[1]
|
|
shoulder2[1] = neck[1]
|
|
elif physicalAttachment == 3:
|
|
neck[0] = root[0] - self._branchNeckLength
|
|
neck[1] = root[1]
|
|
|
|
shoulder1[0] = neck[0]
|
|
shoulder2[0] = neck[0]
|
|
|
|
shoulder1[1] = neck[1] - totalBranchLength / 2.0
|
|
shoulder2[1] = neck[1] + totalBranchLength / 2.0
|
|
else:
|
|
raise "Unrecognised attachment point in GetBranchingAttachmentInfo"
|
|
return root, neck, shoulder1, shoulder2
|
|
|
|
def GetBranchingAttachmentPoint(self, attachment, n):
|
|
"""
|
|
Get branching attachment point.
|
|
|
|
:param `attachment`: ???
|
|
:param `n`: ???
|
|
|
|
"""
|
|
physicalAttachment = self.LogicalToPhysicalAttachment(attachment)
|
|
|
|
root, neck, shoulder1, shoulder2 = self.GetBranchingAttachmentInfo(attachment)
|
|
pt = wx.RealPoint()
|
|
stemPt = wx.RealPoint()
|
|
|
|
if physicalAttachment == 0:
|
|
pt[1] = neck[1] - self._branchStemLength
|
|
pt[0] = shoulder1[0] + n * self._branchSpacing
|
|
|
|
stemPt[0] = pt[0]
|
|
stemPt[1] = neck[1]
|
|
elif physicalAttachment == 2:
|
|
pt[1] = neck[1] + self._branchStemLength
|
|
pt[0] = shoulder1[0] + n * self._branchStemLength
|
|
|
|
stemPt[0] = pt[0]
|
|
stemPt[1] = neck[1]
|
|
elif physicalAttachment == 1:
|
|
pt[0] = neck[0] + self._branchStemLength
|
|
pt[1] = shoulder1[1] + n * self._branchSpacing
|
|
|
|
stemPt[0] = neck[0]
|
|
stemPt[1] = pt[1]
|
|
elif physicalAttachment == 3:
|
|
pt[0] = neck[0] - self._branchStemLength
|
|
pt[1] = shoulder1[1] + n * self._branchSpacing
|
|
|
|
stemPt[0] = neck[0]
|
|
stemPt[1] = pt[1]
|
|
else:
|
|
raise "Unrecognised attachment point in GetBranchingAttachmentPoint"
|
|
|
|
return pt, stemPt
|
|
|
|
def GetAttachmentLineCount(self, attachment):
|
|
"""
|
|
Get the number of lines at this attachment position.
|
|
|
|
:param `attachment`: ???
|
|
:returns: the count of lines at this position
|
|
|
|
"""
|
|
count = 0
|
|
for lineShape in self._lines:
|
|
if lineShape.GetFrom() == self and lineShape.GetAttachmentFrom() == attachment:
|
|
count += 1
|
|
elif lineShape.GetTo() == self and lineShape.GetAttachmentTo() == attachment:
|
|
count += 1
|
|
return count
|
|
|
|
def GetBranchingAttachmentRoot(self, attachment):
|
|
"""
|
|
Get the root point at the given attachment.
|
|
|
|
:param `attachment`: ???
|
|
|
|
"""
|
|
physicalAttachment = self.LogicalToPhysicalAttachment(attachment)
|
|
|
|
root = wx.RealPoint()
|
|
|
|
width, height = self.GetBoundingBoxMax()
|
|
|
|
# Assume that we have attachment points 0 to 3: top, right, bottom, left
|
|
if physicalAttachment == 0:
|
|
root[0] = self.GetX()
|
|
root[1] = self.GetY() - height / 2.0
|
|
elif physicalAttachment == 1:
|
|
root[0] = self.GetX() + width / 2.0
|
|
root[1] = self.GetY()
|
|
elif physicalAttachment == 2:
|
|
root[0] = self.GetX()
|
|
root[1] = self.GetY() + height / 2.0
|
|
elif physicalAttachment == 3:
|
|
root[0] = self.GetX() - width / 2.0
|
|
root[1] = self.GetY()
|
|
else:
|
|
raise "Unrecognised attachment point in GetBranchingAttachmentRoot"
|
|
|
|
return root
|
|
|
|
# Draw or erase the branches (not the actual arcs though)
|
|
def OnDrawBranchesAttachment(self, dc, attachment, erase = False):
|
|
"""The draw branches attachment handler."""
|
|
count = self.GetAttachmentLineCount(attachment)
|
|
if count == 0:
|
|
return
|
|
|
|
root, neck, shoulder1, shoulder2 = self.GetBranchingAttachmentInfo(attachment)
|
|
|
|
if erase:
|
|
dc.SetPen(wx.WHITE_PEN)
|
|
dc.SetBrush(wx.WHITE_BRUSH)
|
|
else:
|
|
dc.SetPen(wx.BLACK_PEN)
|
|
dc.SetBrush(wx.BLACK_BRUSH)
|
|
|
|
# Draw neck
|
|
dc.DrawLine(root[0], root[1], neck[0], neck[1])
|
|
|
|
if count > 1:
|
|
# Draw shoulder-to-shoulder line
|
|
dc.DrawLine(shoulder1[0], shoulder1[1], shoulder2[0], shoulder2[1])
|
|
# Draw all the little branches
|
|
for i in range(count):
|
|
pt, stemPt = self.GetBranchingAttachmentPoint(attachment, i)
|
|
dc.DrawLine(stemPt[0], stemPt[1], pt[0], pt[1])
|
|
|
|
if self.GetBranchStyle() & BRANCHING_ATTACHMENT_BLOB and count > 1:
|
|
blobSize = 6.0
|
|
dc.DrawEllipse(stemPt[0] - blobSize / 2.0, stemPt[1] - blobSize / 2.0, blobSize, blobSize)
|
|
|
|
def OnDrawBranches(self, dc, erase = False):
|
|
"""The draw branches handler."""
|
|
if self._attachmentMode != ATTACHMENT_MODE_BRANCHING:
|
|
return
|
|
for i in range(self.GetNumberOfAttachments()):
|
|
self.OnDrawBranchesAttachment(dc, i, erase)
|
|
|
|
def GetAttachmentPositionEdge(self, attachment, nth = 0, no_arcs = 1, line = None):
|
|
"""
|
|
Only get the attachment position at the _edge_ of the shape,
|
|
ignoring branching mode. This is used e.g. to indicate the edge of
|
|
interest, not the point on the attachment branch.
|
|
|
|
:param `attachment`: the attachment ???
|
|
:param `nth`: get nth attachment ???
|
|
:param `no_arcs`: ???
|
|
:param `line`: ???
|
|
|
|
"""
|
|
oldMode = self._attachmentMode
|
|
|
|
# Calculate as if to edge, not branch
|
|
if self._attachmentMode == ATTACHMENT_MODE_BRANCHING:
|
|
self._attachmentMode = ATTACHMENT_MODE_EDGE
|
|
res = self.GetAttachmentPosition(attachment, nth, no_arcs, line)
|
|
self._attachmentMode = oldMode
|
|
|
|
return res
|
|
|
|
def PhysicalToLogicalAttachment(self, physicalAttachment):
|
|
"""
|
|
Rotate the standard attachment point from physical
|
|
(0 is always North) to logical (0 -> 1 if rotated by 90 degrees)
|
|
|
|
:param `physicalAttachment`: ???
|
|
|
|
"""
|
|
if RoughlyEqual(self.GetRotation(), 0):
|
|
i = physicalAttachment
|
|
elif RoughlyEqual(self.GetRotation(), math.pi / 2.0):
|
|
i = physicalAttachment - 1
|
|
elif RoughlyEqual(self.GetRotation(), math.pi):
|
|
i = physicalAttachment - 2
|
|
elif RoughlyEqual(self.GetRotation(), 3 * math.pi / 2.0):
|
|
i = physicalAttachment - 3
|
|
else:
|
|
# Can't handle -- assume the same
|
|
return physicalAttachment
|
|
|
|
if i < 0:
|
|
i += 4
|
|
|
|
return i
|
|
|
|
def LogicalToPhysicalAttachment(self, logicalAttachment):
|
|
"""
|
|
Rotate the standard attachment point from logical
|
|
to physical (0 is always North).
|
|
|
|
:param `logicalAttachment`: ???
|
|
|
|
"""
|
|
if RoughlyEqual(self.GetRotation(), 0):
|
|
i = logicalAttachment
|
|
elif RoughlyEqual(self.GetRotation(), math.pi / 2.0):
|
|
i = logicalAttachment + 1
|
|
elif RoughlyEqual(self.GetRotation(), math.pi):
|
|
i = logicalAttachment + 2
|
|
elif RoughlyEqual(self.GetRotation(), 3 * math.pi / 2.0):
|
|
i = logicalAttachment + 3
|
|
else:
|
|
return logicalAttachment
|
|
|
|
if i > 3:
|
|
i -= 4
|
|
|
|
return i
|
|
|
|
def Rotate(self, x, y, theta):
|
|
"""
|
|
Rotate about the given axis by the given amount in radians.
|
|
|
|
:param `x`: the x position
|
|
:param `y`: the y position
|
|
:param `theta`: the theta
|
|
|
|
"""
|
|
self._rotation = theta
|
|
if self._rotation < 0:
|
|
self._rotation += 2 * math.pi
|
|
elif self._rotation > 2 * math.pi:
|
|
self._rotation -= 2 * math.pi
|
|
|
|
def GetBackgroundPen(self):
|
|
"""Return pen of the right colour for the background."""
|
|
if self.GetCanvas():
|
|
return wx.Pen(self.GetCanvas().GetBackgroundColour(), 1, wx.PENSTYLE_SOLID)
|
|
return WhiteBackgroundPen
|
|
|
|
def GetBackgroundBrush(self):
|
|
"""Return brush of the right colour for the background."""
|
|
if self.GetCanvas():
|
|
return wx.Brush(self.GetCanvas().GetBackgroundColour(), wx.BRUSHSTYLE_SOLID)
|
|
return WhiteBackgroundBrush
|
|
|
|
def GetX(self):
|
|
"""Get the x position of the centre of the shape."""
|
|
return self._xpos
|
|
|
|
def GetY(self):
|
|
"""Get the y position of the centre of the shape."""
|
|
return self._ypos
|
|
|
|
def SetX(self, x):
|
|
"""
|
|
Set the x position of the shape.
|
|
|
|
:param `x`: the x position
|
|
"""
|
|
self._xpos = x
|
|
|
|
def SetY(self, y):
|
|
"""
|
|
Set the y position of the shape.
|
|
|
|
:param `y`: the y position
|
|
|
|
"""
|
|
self._ypos = y
|
|
|
|
def GetParent(self):
|
|
"""Get the parent of this shape, if it is part of a composite."""
|
|
return self._parent
|
|
|
|
def SetParent(self, p):
|
|
"""Set the parent
|
|
|
|
:param `p`: the parent
|
|
|
|
"""
|
|
self._parent = p
|
|
|
|
def GetChildren(self):
|
|
"""Get the list of children for this shape."""
|
|
return self._children
|
|
|
|
def GetDrawHandles(self):
|
|
"""Get the list of drawhandles."""
|
|
return self._drawHandles
|
|
|
|
def GetEventHandler(self):
|
|
"""Get the event handler for this shape."""
|
|
return self._eventHandler
|
|
|
|
def SetEventHandler(self, handler):
|
|
"""Set the event handler for this shape.
|
|
|
|
:param `handler`: an instance of :class:`ShapeEvtHandler`
|
|
|
|
"""
|
|
self._eventHandler = handler
|
|
|
|
def Recompute(self):
|
|
"""
|
|
Recomputes any constraints associated with the shape.
|
|
|
|
Normally applicable to CompositeShapes only, but harmless for
|
|
other classes of Shape.
|
|
"""
|
|
return True
|
|
|
|
def IsHighlighted(self):
|
|
"""
|
|
`True` if the shape is highlighted. Shape highlighting is unimplemented.
|
|
"""
|
|
return self._highlighted
|
|
|
|
def GetSensitivityFilter(self):
|
|
"""
|
|
Get the sensitivity filter, a bitlist of values.
|
|
|
|
See :meth:`Shape.SetSensitivityFilter`
|
|
|
|
"""
|
|
return self._sensitivity
|
|
|
|
def SetFixedSize(self, x, y):
|
|
"""
|
|
Set the shape to be fixed size.
|
|
|
|
:param `x`: the width
|
|
:param `y`: the height
|
|
|
|
"""
|
|
self._fixedWidth = x
|
|
self._fixedHeight = y
|
|
|
|
def GetFixedSize(self):
|
|
"""
|
|
Return flags indicating whether the shape is of fixed size in
|
|
either direction.
|
|
"""
|
|
return self._fixedWidth, self._fixedHeight
|
|
|
|
def GetFixedWidth(self):
|
|
"""`True` if the shape cannot be resized in the horizontal plane."""
|
|
return self._fixedWidth
|
|
|
|
def GetFixedHeight(self):
|
|
"""`True` if the shape cannot be resized in the vertical plane."""
|
|
return self._fixedHeight
|
|
|
|
def SetSpaceAttachments(self, sp):
|
|
"""
|
|
Indicate whether lines should be spaced out evenly at the point
|
|
they touch the node.
|
|
|
|
:param `sp`: if `True` space out evently, else they should join at a
|
|
single point.
|
|
|
|
"""
|
|
self._spaceAttachments = sp
|
|
|
|
def GetSpaceAttachments(self):
|
|
"""
|
|
Get whether lines should be spaced out evenly at the point they
|
|
touch the node (True), or whether they should join at a single point
|
|
(False).
|
|
"""
|
|
return self._spaceAttachments
|
|
|
|
def SetCentreResize(self, cr):
|
|
"""
|
|
Specify whether the shape is to be resized from the centre (the
|
|
centre stands still) or from the corner or side being dragged (the
|
|
other corner or side stands still).
|
|
"""
|
|
self._centreResize = cr
|
|
|
|
def GetCentreResize(self):
|
|
"""
|
|
`True` if the shape is to be resized from the centre (the centre stands
|
|
still), or `False` if from the corner or side being dragged (the other
|
|
corner or side stands still)
|
|
|
|
"""
|
|
return self._centreResize
|
|
|
|
def SetMaintainAspectRatio(self, ar):
|
|
"""
|
|
Set whether a shape that resizes should not change the aspect ratio
|
|
(width and height should be in the original proportion).
|
|
|
|
"""
|
|
self._maintainAspectRatio = ar
|
|
|
|
def GetMaintainAspectRatio(self):
|
|
"""`True` if shape keeps aspect ratio during resize."""
|
|
return self._maintainAspectRatio
|
|
|
|
def GetLines(self):
|
|
"""Return the list of lines connected to this shape."""
|
|
return self._lines
|
|
|
|
def SetDisableLabel(self, flag):
|
|
"""Set flag to `True` to stop the default region being shown."""
|
|
self._disableLabel = flag
|
|
|
|
def GetDisableLabel(self):
|
|
"""`True` if the default region will not be shown, `False` otherwise."""
|
|
return self._disableLabel
|
|
|
|
def SetAttachmentMode(self, mode):
|
|
"""
|
|
Set the attachment mode.
|
|
|
|
:param `mode`: if `True` attachment points will be significant when
|
|
drawing lines to and from this shape. If `False` lines will be drawn
|
|
as if to the centre of the shape.
|
|
|
|
"""
|
|
self._attachmentMode = mode
|
|
|
|
def GetAttachmentMode(self):
|
|
"""
|
|
Get the attachment mode.
|
|
|
|
See :meth:`Shape.SetAttachmentMode`
|
|
"""
|
|
return self._attachmentMode
|
|
|
|
def SetId(self, i):
|
|
"""Set the integer identifier for this shape."""
|
|
self._id = i
|
|
|
|
def GetId(self):
|
|
"""Get the integer identifier for this shape."""
|
|
return self._id
|
|
|
|
def IsShown(self):
|
|
"""
|
|
`True` if the shape is in a visible state, `False` otherwise.
|
|
|
|
:note: That this has nothing to do with whether the window is hidden
|
|
or the shape has scrolled off the canvas; it refers to the internal
|
|
visibility flag.
|
|
|
|
"""
|
|
return self._visible
|
|
|
|
def GetPen(self):
|
|
"""Get the pen used for drawing the shape's outline."""
|
|
return self._pen
|
|
|
|
def GetBrush(self):
|
|
"""Get the brush used for filling the shape."""
|
|
return self._brush
|
|
|
|
def GetNumberOfTextRegions(self):
|
|
"""Get the number of text regions for this shape."""
|
|
return len(self._regions)
|
|
|
|
def GetRegions(self):
|
|
"""Get the list of ShapeRegions."""
|
|
return self._regions
|
|
|
|
# Control points ('handles') redirect control to the actual shape, to
|
|
# make it easier to override sizing behaviour.
|
|
def OnSizingDragLeft(self, pt, draw, x, y, keys = 0, attachment = 0):
|
|
"""The sizing drag left handler."""
|
|
bound_x, bound_y = self.GetBoundingBoxMin()
|
|
|
|
dc = wx.MemoryDC()
|
|
dc.SelectObject(self.GetCanvas()._Buffer)
|
|
dc.SetLogicalFunction(OGLRBLF)
|
|
|
|
dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.PENSTYLE_DOT)
|
|
dc.SetPen(dottedPen)
|
|
dc.SetBrush(wx.TRANSPARENT_BRUSH)
|
|
|
|
if self.GetCentreResize():
|
|
# Maintain the same centre point
|
|
new_width = 2.0 * abs(x - self.GetX())
|
|
new_height = 2.0 * abs(y - self.GetY())
|
|
|
|
# Constrain sizing according to what control point you're dragging
|
|
if pt._type == CONTROL_POINT_HORIZONTAL:
|
|
if self.GetMaintainAspectRatio():
|
|
new_height = bound_y * (new_width / bound_x)
|
|
else:
|
|
new_height = bound_y
|
|
elif pt._type == CONTROL_POINT_VERTICAL:
|
|
if self.GetMaintainAspectRatio():
|
|
new_width = bound_x * (new_height / bound_y)
|
|
else:
|
|
new_width = bound_x
|
|
elif pt._type == CONTROL_POINT_DIAGONAL and (keys & KEY_SHIFT):
|
|
new_height = bound_y * (new_width / bound_x)
|
|
|
|
if self.GetFixedWidth():
|
|
new_width = bound_x
|
|
|
|
if self.GetFixedHeight():
|
|
new_height = bound_y
|
|
|
|
pt._controlPointDragEndWidth = new_width
|
|
pt._controlPointDragEndHeight = new_height
|
|
|
|
self.GetEventHandler().OnDrawOutline(dc, self.GetX(), self.GetY(), new_width, new_height)
|
|
else:
|
|
# Don't maintain the same centre point
|
|
newX1 = min(pt._controlPointDragStartX, x)
|
|
newY1 = min(pt._controlPointDragStartY, y)
|
|
newX2 = max(pt._controlPointDragStartX, x)
|
|
newY2 = max(pt._controlPointDragStartY, y)
|
|
if pt._type == CONTROL_POINT_HORIZONTAL:
|
|
newY1 = pt._controlPointDragStartY
|
|
newY2 = newY1 + pt._controlPointDragStartHeight
|
|
elif pt._type == CONTROL_POINT_VERTICAL:
|
|
newX1 = pt._controlPointDragStartX
|
|
newX2 = newX1 + pt._controlPointDragStartWidth
|
|
elif pt._type == CONTROL_POINT_DIAGONAL and (keys & KEY_SHIFT or self.GetMaintainAspectRatio()):
|
|
newH = (newX2 - newX1) * (float(pt._controlPointDragStartHeight) / pt._controlPointDragStartWidth)
|
|
if self.GetY() > pt._controlPointDragStartY:
|
|
newY2 = newY1 + newH
|
|
else:
|
|
newY1 = newY2 - newH
|
|
|
|
newWidth = float(newX2 - newX1)
|
|
newHeight = float(newY2 - newY1)
|
|
|
|
if pt._type == CONTROL_POINT_VERTICAL and self.GetMaintainAspectRatio():
|
|
newWidth = bound_x * (newHeight / bound_y)
|
|
|
|
if pt._type == CONTROL_POINT_HORIZONTAL and self.GetMaintainAspectRatio():
|
|
newHeight = bound_y * (newWidth / bound_x)
|
|
|
|
pt._controlPointDragPosX = newX1 + newWidth / 2.0
|
|
pt._controlPointDragPosY = newY1 + newHeight / 2.0
|
|
if self.GetFixedWidth():
|
|
newWidth = bound_x
|
|
|
|
if self.GetFixedHeight():
|
|
newHeight = bound_y
|
|
|
|
pt._controlPointDragEndWidth = newWidth
|
|
pt._controlPointDragEndHeight = newHeight
|
|
self.GetEventHandler().OnDrawOutline(dc, pt._controlPointDragPosX, pt._controlPointDragPosY, newWidth, newHeight)
|
|
|
|
def OnSizingBeginDragLeft(self, pt, x, y, keys = 0, attachment = 0):
|
|
"""The sizing begin drag left handler."""
|
|
self._canvas.CaptureMouse()
|
|
|
|
dc = wx.MemoryDC()
|
|
dc.SelectObject(self.GetCanvas()._Buffer)
|
|
dc.SetLogicalFunction(OGLRBLF)
|
|
|
|
bound_x, bound_y = self.GetBoundingBoxMin()
|
|
self.GetEventHandler().OnBeginSize(bound_x, bound_y)
|
|
|
|
# Choose the 'opposite corner' of the object as the stationary
|
|
# point in case this is non-centring resizing.
|
|
if pt.GetX() < self.GetX():
|
|
pt._controlPointDragStartX = self.GetX() + bound_x / 2.0
|
|
else:
|
|
pt._controlPointDragStartX = self.GetX() - bound_x / 2.0
|
|
|
|
if pt.GetY() < self.GetY():
|
|
pt._controlPointDragStartY = self.GetY() + bound_y / 2.0
|
|
else:
|
|
pt._controlPointDragStartY = self.GetY() - bound_y / 2.0
|
|
|
|
if pt._type == CONTROL_POINT_HORIZONTAL:
|
|
pt._controlPointDragStartY = self.GetY() - bound_y / 2.0
|
|
elif pt._type == CONTROL_POINT_VERTICAL:
|
|
pt._controlPointDragStartX = self.GetX() - bound_x / 2.0
|
|
|
|
# We may require the old width and height
|
|
pt._controlPointDragStartWidth = bound_x
|
|
pt._controlPointDragStartHeight = bound_y
|
|
|
|
dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.PENSTYLE_DOT)
|
|
dc.SetPen(dottedPen)
|
|
dc.SetBrush(wx.TRANSPARENT_BRUSH)
|
|
|
|
if self.GetCentreResize():
|
|
new_width = 2.0 * abs(x - self.GetX())
|
|
new_height = 2.0 * abs(y - self.GetY())
|
|
|
|
# Constrain sizing according to what control point you're dragging
|
|
if pt._type == CONTROL_POINT_HORIZONTAL:
|
|
if self.GetMaintainAspectRatio():
|
|
new_height = bound_y * (new_width / bound_x)
|
|
else:
|
|
new_height = bound_y
|
|
elif pt._type == CONTROL_POINT_VERTICAL:
|
|
if self.GetMaintainAspectRatio():
|
|
new_width = bound_x * (new_height / bound_y)
|
|
else:
|
|
new_width = bound_x
|
|
elif pt._type == CONTROL_POINT_DIAGONAL and (keys & KEY_SHIFT):
|
|
new_height = bound_y * (new_width / bound_x)
|
|
|
|
if self.GetFixedWidth():
|
|
new_width = bound_x
|
|
|
|
if self.GetFixedHeight():
|
|
new_height = bound_y
|
|
|
|
pt._controlPointDragEndWidth = new_width
|
|
pt._controlPointDragEndHeight = new_height
|
|
self.GetEventHandler().OnDrawOutline(dc, self.GetX(), self.GetY(), new_width, new_height)
|
|
else:
|
|
# Don't maintain the same centre point
|
|
newX1 = min(pt._controlPointDragStartX, x)
|
|
newY1 = min(pt._controlPointDragStartY, y)
|
|
newX2 = max(pt._controlPointDragStartX, x)
|
|
newY2 = max(pt._controlPointDragStartY, y)
|
|
if pt._type == CONTROL_POINT_HORIZONTAL:
|
|
newY1 = pt._controlPointDragStartY
|
|
newY2 = newY1 + pt._controlPointDragStartHeight
|
|
elif pt._type == CONTROL_POINT_VERTICAL:
|
|
newX1 = pt._controlPointDragStartX
|
|
newX2 = newX1 + pt._controlPointDragStartWidth
|
|
elif pt._type == CONTROL_POINT_DIAGONAL and (keys & KEY_SHIFT or self.GetMaintainAspectRatio()):
|
|
newH = (newX2 - newX1) * (float(pt._controlPointDragStartHeight) / pt._controlPointDragStartWidth)
|
|
if pt.GetY() > pt._controlPointDragStartY:
|
|
newY2 = newY1 + newH
|
|
else:
|
|
newY1 = newY2 - newH
|
|
|
|
newWidth = float(newX2 - newX1)
|
|
newHeight = float(newY2 - newY1)
|
|
|
|
if pt._type == CONTROL_POINT_VERTICAL and self.GetMaintainAspectRatio():
|
|
newWidth = bound_x * (newHeight / bound_y)
|
|
|
|
if pt._type == CONTROL_POINT_HORIZONTAL and self.GetMaintainAspectRatio():
|
|
newHeight = bound_y * (newWidth / bound_x)
|
|
|
|
pt._controlPointDragPosX = newX1 + newWidth / 2.0
|
|
pt._controlPointDragPosY = newY1 + newHeight / 2.0
|
|
if self.GetFixedWidth():
|
|
newWidth = bound_x
|
|
|
|
if self.GetFixedHeight():
|
|
newHeight = bound_y
|
|
|
|
pt._controlPointDragEndWidth = newWidth
|
|
pt._controlPointDragEndHeight = newHeight
|
|
self.GetEventHandler().OnDrawOutline(dc, pt._controlPointDragPosX, pt._controlPointDragPosY, newWidth, newHeight)
|
|
|
|
def OnSizingEndDragLeft(self, pt, x, y, keys = 0, attachment = 0):
|
|
"""The sizing end drag left handler."""
|
|
dc = wx.MemoryDC()
|
|
dc.SelectObject(self.GetCanvas()._Buffer)
|
|
|
|
if self._canvas.HasCapture():
|
|
self._canvas.ReleaseMouse()
|
|
dc.SetLogicalFunction(wx.COPY)
|
|
self.Recompute()
|
|
self.ResetControlPoints()
|
|
|
|
self.Erase(dc)
|
|
|
|
self.SetSize(pt._controlPointDragEndWidth, pt._controlPointDragEndHeight)
|
|
|
|
# The next operation could destroy this control point (it does for
|
|
# label objects, via formatting the text), so save all values we're
|
|
# going to use, or we'll be accessing garbage.
|
|
|
|
#return
|
|
|
|
if self.GetCentreResize():
|
|
self.Move(dc, self.GetX(), self.GetY())
|
|
else:
|
|
self.Move(dc, pt._controlPointDragPosX, pt._controlPointDragPosY)
|
|
|
|
# Recursively redraw links if we have a composite
|
|
if len(self.GetChildren()):
|
|
self.DrawLinks(dc, -1, True)
|
|
|
|
width, height = self.GetBoundingBoxMax()
|
|
self.GetEventHandler().OnEndSize(width, height)
|
|
|
|
if not self._canvas.GetQuickEditMode() and pt._eraseObject:
|
|
self._canvas.Redraw(dc)
|
|
|
|
|
|
class RectangleShape(Shape):
|
|
"""
|
|
The :class:`RectangleShape` class has rounded or square corners.
|
|
"""
|
|
def __init__(self, w = 0.0, h = 0.0):
|
|
"""
|
|
Default class constructor
|
|
|
|
:param float `w`: the width
|
|
:param float `h`: the height
|
|
|
|
"""
|
|
Shape.__init__(self)
|
|
self._width = w
|
|
self._height = h
|
|
self._cornerRadius = 0.0
|
|
self.SetDefaultRegionSize()
|
|
|
|
def OnDraw(self, dc):
|
|
"""The draw handler."""
|
|
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(TransparentPen)
|
|
|
|
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)
|
|
|
|
if self._pen:
|
|
if self._pen.GetWidth() == 0:
|
|
dc.SetPen(TransparentPen)
|
|
else:
|
|
dc.SetPen(self._pen)
|
|
if self._brush:
|
|
dc.SetBrush(self._brush)
|
|
|
|
if self._cornerRadius:
|
|
dc.DrawRoundedRectangle(x1, y1, self._width, self._height, self._cornerRadius)
|
|
else:
|
|
dc.DrawRectangle(x1, y1, self._width, self._height)
|
|
|
|
def GetBoundingBoxMin(self):
|
|
"""Get the bounding box minimum."""
|
|
return self._width, self._height
|
|
|
|
def SetSize(self, x, y, recursive = False):
|
|
"""
|
|
Set the size.
|
|
|
|
:param `x`: the width
|
|
:param `y`: the height
|
|
:param `recursive`: not used
|
|
|
|
"""
|
|
self.SetAttachmentSize(x, y)
|
|
self._width = max(x, 1)
|
|
self._height = max(y, 1)
|
|
self.SetDefaultRegionSize()
|
|
|
|
def GetCornerRadius(self):
|
|
"""Get the radius of the rectangle's rounded corners."""
|
|
return self._cornerRadius
|
|
|
|
def SetCornerRadius(self, rad):
|
|
"""
|
|
Set the radius of the rectangle's rounded corners.
|
|
|
|
:param `rad`: If the radius is zero, a non-rounded rectangle will be
|
|
drawn. If the radius is negative, the value is the proportion of the
|
|
smaller dimension of the rectangle.
|
|
|
|
"""
|
|
self._cornerRadius = rad
|
|
|
|
# Assume (x1, y1) is centre of box (most generally, line end at box)
|
|
def GetPerimeterPoint(self, x1, y1, x2, y2):
|
|
"""
|
|
Get the perimeter point.
|
|
|
|
:param `x1`: ???
|
|
:param `y1`: ???
|
|
:param `x2`: ???
|
|
:param `y2`: ???
|
|
|
|
"""
|
|
bound_x, bound_y = self.GetBoundingBoxMax()
|
|
return FindEndForBox(bound_x, bound_y, self._xpos, self._ypos, x2, y2)
|
|
|
|
def GetWidth(self):
|
|
"""Get the width."""
|
|
return self._width
|
|
|
|
def GetHeight(self):
|
|
"""Get the height."""
|
|
return self._height
|
|
|
|
def SetWidth(self, w):
|
|
"""
|
|
Set the width.
|
|
|
|
:param `w`: width to be set
|
|
|
|
"""
|
|
self._width = w
|
|
|
|
def SetHeight(self, h):
|
|
"""
|
|
Set the heigth.
|
|
|
|
:param `h`: heigth to be set
|
|
|
|
"""
|
|
self._height = h
|
|
|
|
|
|
class PolygonShape(Shape):
|
|
"""
|
|
The :class:`PolygonShape` class shape is defined by a number of points
|
|
passed to the object's constructor. It can be used to create new shapes
|
|
such as diamonds and triangles.
|
|
"""
|
|
def __init__(self):
|
|
"""
|
|
Default class constructor
|
|
|
|
Does not follow above statement, should it? or is Create called
|
|
automagically?
|
|
|
|
"""
|
|
Shape.__init__(self)
|
|
|
|
self._points = None
|
|
self._originalPoints = None
|
|
|
|
def Create(self, the_points = None):
|
|
"""
|
|
Takes a list of :class:`Points` or tuples; each point is an offset
|
|
from the centre.
|
|
"""
|
|
self.ClearPoints()
|
|
|
|
if not the_points:
|
|
self._originalPoints = []
|
|
self._points = []
|
|
else:
|
|
self._originalPoints = the_points
|
|
|
|
# Duplicate the list of points
|
|
self._points = []
|
|
for point in the_points:
|
|
new_point = wx.Point(point[0], point[1])
|
|
self._points.append(new_point)
|
|
self.CalculateBoundingBox()
|
|
self._originalWidth = self._boundWidth
|
|
self._originalHeight = self._boundHeight
|
|
self.SetDefaultRegionSize()
|
|
|
|
def ClearPoints(self):
|
|
"""Clear the points."""
|
|
self._points = []
|
|
self._originalPoints = []
|
|
|
|
# Width and height. Centre of object is centre of box
|
|
def GetBoundingBoxMin(self):
|
|
"""Get minimum bounding box."""
|
|
return self._boundWidth, self._boundHeight
|
|
|
|
def GetPoints(self):
|
|
"""Return the internal list of polygon vertices."""
|
|
return self._points
|
|
|
|
def GetOriginalPoints(self):
|
|
"""Get the original points."""
|
|
return self._originalPoints
|
|
|
|
def GetOriginalWidth(self):
|
|
"""Get the original width."""
|
|
return self._originalWidth
|
|
|
|
def GetOriginalHeight(self):
|
|
"""Get the original height."""
|
|
return self._originalHeight
|
|
|
|
def SetOriginalWidth(self, w):
|
|
"""
|
|
Set the original width.
|
|
|
|
:param `w`: the width
|
|
|
|
"""
|
|
self._originalWidth = w
|
|
|
|
def SetOriginalHeight(self, h):
|
|
"""
|
|
Set the original height.
|
|
|
|
:param `w`: the height
|
|
|
|
"""
|
|
self._originalHeight = h
|
|
|
|
def CalculateBoundingBox(self):
|
|
"""Calculate the bounding box."""
|
|
left = 10000
|
|
right = -10000
|
|
top = 10000
|
|
bottom = -10000
|
|
|
|
for point in self._points:
|
|
if point[0] < left:
|
|
left = point[0]
|
|
if point[0] > right:
|
|
right = point[0]
|
|
|
|
if point[1] < top:
|
|
top = point[1]
|
|
if point[1] > bottom:
|
|
bottom = point[1]
|
|
|
|
self._boundWidth = right - left
|
|
self._boundHeight = bottom - top
|
|
|
|
def CalculatePolygonCentre(self):
|
|
"""
|
|
Recalculates the centre of the polygon, and
|
|
readjusts the point offsets accordingly.
|
|
Necessary since the centre of the polygon
|
|
is expected to be the real centre of the bounding
|
|
box.
|
|
"""
|
|
left = 10000
|
|
right = -10000
|
|
top = 10000
|
|
bottom = -10000
|
|
|
|
for point in self._points:
|
|
if point[0] < left:
|
|
left = point[0]
|
|
if point[0] > right:
|
|
right = point[0]
|
|
|
|
if point[1] < top:
|
|
top = point[1]
|
|
if point[1] > bottom:
|
|
bottom = point[1]
|
|
|
|
bwidth = right - left
|
|
bheight = bottom - top
|
|
|
|
newCentreX = left + bwidth / 2.0
|
|
newCentreY = top + bheight / 2.0
|
|
|
|
for i in range(len(self._points)):
|
|
self._points[i] = self._points[i][0] - newCentreX, self._points[i][1] - newCentreY
|
|
self._xpos += newCentreX
|
|
self._ypos += newCentreY
|
|
|
|
def HitTest(self, x, y):
|
|
"""Hit text
|
|
|
|
:param `x`: the x position
|
|
:param `y`: the y position
|
|
|
|
"""
|
|
# Imagine four lines radiating from this point. If all of these lines
|
|
# hit the polygon, we're inside it, otherwise we're not. Obviously
|
|
# we'd need more radiating lines to be sure of correct results for
|
|
# very strange (concave) shapes.
|
|
endPointsX = [x, x + 1000, x, x - 1000]
|
|
endPointsY = [y - 1000, y, y + 1000, y]
|
|
|
|
xpoints = []
|
|
ypoints = []
|
|
|
|
for point in self._points:
|
|
xpoints.append(point[0] + self._xpos)
|
|
ypoints.append(point[1] + self._ypos)
|
|
|
|
# We assume it's inside the polygon UNLESS one or more
|
|
# lines don't hit the outline.
|
|
isContained = True
|
|
|
|
for i in range(4):
|
|
if not PolylineHitTest(xpoints, ypoints, x, y, endPointsX[i], endPointsY[i]):
|
|
isContained = False
|
|
|
|
if not isContained:
|
|
return False
|
|
|
|
nearest_attachment = 0
|
|
|
|
# If a hit, check the attachment points within the object
|
|
nearest = 999999
|
|
|
|
for i in range(self.GetNumberOfAttachments()):
|
|
e = self.GetAttachmentPositionEdge(i)
|
|
if e:
|
|
xp, yp = e
|
|
l = math.sqrt((xp - x) * (xp - x) + (yp - y) * (yp - y))
|
|
if l < nearest:
|
|
nearest = l
|
|
nearest_attachment = i
|
|
|
|
return nearest_attachment, nearest
|
|
|
|
# Really need to be able to reset the shape! Otherwise, if the
|
|
# points ever go to zero, we've lost it, and can't resize.
|
|
def SetSize(self, new_width, new_height, recursive = True):
|
|
"""
|
|
Set the size
|
|
|
|
:param `new_width`: the width
|
|
:param `new_height`: the height
|
|
:param `recursive`: not used
|
|
|
|
"""
|
|
self.SetAttachmentSize(new_width, new_height)
|
|
|
|
# Multiply all points by proportion of new size to old size
|
|
x_proportion = abs(float(new_width) / self._originalWidth)
|
|
y_proportion = abs(float(new_height) / self._originalHeight)
|
|
|
|
for i in range(max(len(self._points), len(self._originalPoints))):
|
|
self._points[i] = wx.Point(self._originalPoints[i][0] * x_proportion, self._originalPoints[i][1] * y_proportion)
|
|
|
|
self._boundWidth = abs(new_width)
|
|
self._boundHeight = abs(new_height)
|
|
self.SetDefaultRegionSize()
|
|
|
|
# Make the original points the same as the working points
|
|
def UpdateOriginalPoints(self):
|
|
"""
|
|
If we've changed the shape, must make the original points match the
|
|
working points with this function.
|
|
|
|
"""
|
|
self._originalPoints = []
|
|
|
|
for point in self._points:
|
|
original_point = wx.RealPoint(point[0], point[1])
|
|
self._originalPoints.append(original_point)
|
|
|
|
self.CalculateBoundingBox()
|
|
self._originalWidth = self._boundWidth
|
|
self._originalHeight = self._boundHeight
|
|
|
|
def AddPolygonPoint(self, pos):
|
|
"""
|
|
Add a control point after the given point.
|
|
|
|
:param `pos`: position of point
|
|
|
|
"""
|
|
try:
|
|
firstPoint = self._points[pos]
|
|
except ValueError:
|
|
firstPoint = self._points[0]
|
|
|
|
try:
|
|
secondPoint = self._points[pos + 1]
|
|
except ValueError:
|
|
secondPoint = self._points[0]
|
|
|
|
x = (secondPoint[0] - firstPoint[0]) / 2.0 + firstPoint[0]
|
|
y = (secondPoint[1] - firstPoint[1]) / 2.0 + firstPoint[1]
|
|
point = wx.RealPoint(x, y)
|
|
|
|
if pos >= len(self._points) - 1:
|
|
self._points.append(point)
|
|
else:
|
|
self._points.insert(pos + 1, point)
|
|
|
|
self.UpdateOriginalPoints()
|
|
|
|
if self._selected:
|
|
self.DeleteControlPoints()
|
|
self.MakeControlPoints()
|
|
|
|
def DeletePolygonPoint(self, pos):
|
|
"""
|
|
|
|
Delete the given control point.
|
|
|
|
:param `pos`: position of point
|
|
|
|
"""
|
|
if pos < len(self._points):
|
|
del self._points[pos]
|
|
self.UpdateOriginalPoints()
|
|
if self._selected:
|
|
self.DeleteControlPoints()
|
|
self.MakeControlPoints()
|
|
|
|
# Assume (x1, y1) is centre of box (most generally, line end at box)
|
|
def GetPerimeterPoint(self, x1, y1, x2, y2):
|
|
"""
|
|
Get the perimeter point.
|
|
|
|
:param `x1`: the x1 position
|
|
:param `y1`: the y1 position
|
|
:param `x2`: the x2 position
|
|
:param `y2`: the y2 position
|
|
|
|
"""
|
|
# First check for situation where the line is vertical,
|
|
# and we would want to connect to a point on that vertical --
|
|
# oglFindEndForPolyline can't cope with this (the arrow
|
|
# gets drawn to the wrong place).
|
|
if self._attachmentMode == ATTACHMENT_MODE_NONE and x1 == x2:
|
|
# Look for the point we'd be connecting to. This is
|
|
# a heuristic...
|
|
for point in self._points:
|
|
if point[0] == 0:
|
|
if y2 > y1 and point[1] > 0:
|
|
return point[0] + self._xpos, point[1] + self._ypos
|
|
elif y2 < y1 and point[1] < 0:
|
|
return point[0] + self._xpos, point[1] + self._ypos
|
|
|
|
xpoints = []
|
|
ypoints = []
|
|
for point in self._points:
|
|
xpoints.append(point[0] + self._xpos)
|
|
ypoints.append(point[1] + self._ypos)
|
|
|
|
return FindEndForPolyline(xpoints, ypoints, x1, y1, x2, y2)
|
|
|
|
def OnDraw(self, dc):
|
|
"""The draw handler."""
|
|
if self._shadowMode != SHADOW_NONE:
|
|
if self._shadowBrush:
|
|
dc.SetBrush(self._shadowBrush)
|
|
dc.SetPen(TransparentPen)
|
|
|
|
dc.DrawPolygon(self._points, self._xpos + self._shadowOffsetX, self._ypos, self._shadowOffsetY)
|
|
|
|
if self._pen:
|
|
if self._pen.GetWidth() == 0:
|
|
dc.SetPen(TransparentPen)
|
|
else:
|
|
dc.SetPen(self._pen)
|
|
if self._brush:
|
|
dc.SetBrush(self._brush)
|
|
dc.DrawPolygon(self._points, self._xpos, self._ypos)
|
|
|
|
def OnDrawOutline(self, dc, x, y, w, h):
|
|
"""The draw outline handler."""
|
|
dc.SetBrush(wx.TRANSPARENT_BRUSH)
|
|
# Multiply all points by proportion of new size to old size
|
|
x_proportion = abs(float(w) / self._originalWidth)
|
|
y_proportion = abs(float(h) / self._originalHeight)
|
|
|
|
intPoints = []
|
|
for point in self._originalPoints:
|
|
intPoints.append(wx.Point(x_proportion * point[0], y_proportion * point[1]))
|
|
dc.DrawPolygon(intPoints, x, y)
|
|
|
|
# Make as many control points as there are vertices
|
|
def MakeControlPoints(self):
|
|
"""Make control points."""
|
|
for point in self._points:
|
|
control = PolygonControlPoint(self._canvas, self, CONTROL_POINT_SIZE, point, point[0], point[1])
|
|
self._canvas.AddShape(control)
|
|
self._controlPoints.append(control)
|
|
|
|
def ResetControlPoints(self):
|
|
"""Reset control points."""
|
|
for i in range(min(len(self._points), len(self._controlPoints))):
|
|
point = self._points[i]
|
|
self._controlPoints[i]._xoffset = point[0]
|
|
self._controlPoints[i]._yoffset = point[1]
|
|
self._controlPoints[i].polygonVertex = point
|
|
|
|
def GetNumberOfAttachments(self):
|
|
"""Get number of attachments."""
|
|
maxN = max(len(self._points) - 1, 0)
|
|
for point in self._attachmentPoints:
|
|
if point._id > maxN:
|
|
maxN = point._id
|
|
return maxN + 1
|
|
|
|
def GetAttachmentPosition(self, attachment, nth = 0, no_arcs = 1, line = None):
|
|
"""
|
|
Get attachment position.
|
|
|
|
:param `attachment`: the attachment ???
|
|
:param `nth`: get nth attachment ???
|
|
:param `no_arcs`: ???
|
|
:param `line`: ???
|
|
|
|
"""
|
|
if self._attachmentMode == ATTACHMENT_MODE_EDGE and self._points and attachment < len(self._points):
|
|
point = self._points[0]
|
|
return point[0] + self._xpos, point[1] + self._ypos
|
|
return Shape.GetAttachmentPosition(self, attachment, nth, no_arcs, line)
|
|
|
|
def AttachmentIsValid(self, attachment):
|
|
"""
|
|
Is attachment valid?
|
|
|
|
:param `attachment`: ???
|
|
|
|
"""
|
|
if not self._points:
|
|
return False
|
|
|
|
if attachment >= 0 and attachment < len(self._points):
|
|
return True
|
|
|
|
for point in self._attachmentPoints:
|
|
if point._id == attachment:
|
|
return True
|
|
|
|
return False
|
|
|
|
def Rotate(self, x, y, theta):
|
|
"""
|
|
Rotate about the given axis by the given amount in radians.
|
|
|
|
:param `x`: the x position
|
|
:param `y`: the y position
|
|
:param `theta`: the theta
|
|
|
|
"""
|
|
|
|
actualTheta = theta - self._rotation
|
|
|
|
# Rotate attachment points
|
|
sinTheta = math.sin(actualTheta)
|
|
cosTheta = math.cos(actualTheta)
|
|
|
|
for point in self._attachmentPoints:
|
|
x1 = point._x
|
|
y1 = point._y
|
|
|
|
point._x = x1 * cosTheta - y1 * sinTheta + x * (1 - cosTheta) + y * sinTheta
|
|
point._y = x1 * sinTheta + y1 * cosTheta + y * (1 - cosTheta) + x * sinTheta
|
|
|
|
for i in range(len(self._points)):
|
|
x1, y1 = self._points[i]
|
|
|
|
self._points[i] = x1 * cosTheta - y1 * sinTheta + x * (1 - cosTheta) + y * sinTheta, x1 * sinTheta + y1 * cosTheta + y * (1 - cosTheta) + x * sinTheta
|
|
|
|
for i in range(len(self._originalPoints)):
|
|
x1, y1 = self._originalPoints[i]
|
|
|
|
self._originalPoints[i] = x1 * cosTheta - y1 * sinTheta + x * (1 - cosTheta) + y * sinTheta, x1 * sinTheta + y1 * cosTheta + y * (1 - cosTheta) + x * sinTheta
|
|
|
|
# Added by Pierre Hjälm. If we don't do this the outline will be
|
|
# the wrong size. Hopefully it won't have any ill effects.
|
|
self.UpdateOriginalPoints()
|
|
|
|
self._rotation = theta
|
|
|
|
self.CalculatePolygonCentre()
|
|
self.CalculateBoundingBox()
|
|
self.ResetControlPoints()
|
|
|
|
# Control points ('handles') redirect control to the actual shape, to
|
|
# make it easier to override sizing behaviour.
|
|
def OnSizingDragLeft(self, pt, draw, x, y, keys = 0, attachment = 0):
|
|
"""The sizing drag left handler."""
|
|
dc = wx.MemoryDC()
|
|
dc.SelectObject(self.GetCanvas()._Buffer)
|
|
dc.SetLogicalFunction(OGLRBLF)
|
|
|
|
dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.PENSTYLE_DOT)
|
|
dc.SetPen(dottedPen)
|
|
dc.SetBrush(wx.TRANSPARENT_BRUSH)
|
|
|
|
# Code for CTRL-drag in C++ version commented out
|
|
|
|
pt.CalculateNewSize(x, y)
|
|
|
|
self.GetEventHandler().OnDrawOutline(dc, self.GetX(), self.GetY(), pt.GetNewSize()[0], pt.GetNewSize()[1])
|
|
|
|
def OnSizingBeginDragLeft(self, pt, x, y, keys = 0, attachment = 0):
|
|
"""The sizing begin drag left handler."""
|
|
dc = wx.MemoryDC()
|
|
dc.SelectObject(self.GetCanvas()._Buffer)
|
|
dc.SetLogicalFunction(OGLRBLF)
|
|
|
|
self.Erase(dc)
|
|
|
|
bound_x, bound_y = self.GetBoundingBoxMin()
|
|
|
|
dist = math.sqrt((x - self.GetX()) * (x - self.GetX()) + (y - self.GetY()) * (y - self.GetY()))
|
|
|
|
pt._originalDistance = dist
|
|
pt._originalSize[0] = bound_x
|
|
pt._originalSize[1] = bound_y
|
|
|
|
if pt._originalDistance == 0:
|
|
pt._originalDistance = 0.0001
|
|
|
|
dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.PENSTYLE_DOT)
|
|
dc.SetPen(dottedPen)
|
|
dc.SetBrush(wx.TRANSPARENT_BRUSH)
|
|
|
|
# Code for CTRL-drag in C++ version commented out
|
|
|
|
pt.CalculateNewSize(x, y)
|
|
|
|
self.GetEventHandler().OnDrawOutline(dc, self.GetX(), self.GetY(), pt.GetNewSize()[0], pt.GetNewSize()[1])
|
|
|
|
self._canvas.CaptureMouse()
|
|
|
|
def OnSizingEndDragLeft(self, pt, x, y, keys = 0, attachment = 0):
|
|
"""The sizing end drag left handler."""
|
|
dc = wx.MemoryDC()
|
|
dc.SelectObject(self.GetCanvas()._Buffer)
|
|
|
|
if self._canvas.HasCapture():
|
|
self._canvas.ReleaseMouse()
|
|
dc.SetLogicalFunction(wx.COPY)
|
|
|
|
# If we're changing shape, must reset the original points
|
|
if keys & KEY_CTRL:
|
|
self.CalculateBoundingBox()
|
|
self.CalculatePolygonCentre()
|
|
else:
|
|
self.SetSize(pt.GetNewSize()[0], pt.GetNewSize()[1])
|
|
|
|
self.Recompute()
|
|
self.ResetControlPoints()
|
|
self.Move(dc, self.GetX(), self.GetY())
|
|
if not self._canvas.GetQuickEditMode():
|
|
self._canvas.Redraw(dc)
|
|
|
|
|
|
class EllipseShape(Shape):
|
|
"""
|
|
The :class:`EllipseShape` class behaves similarly to the
|
|
:class`RectangleShape` but is elliptical.
|
|
|
|
"""
|
|
def __init__(self, w, h):
|
|
"""
|
|
Default class constructor
|
|
|
|
:param `w`: the width
|
|
:param `h`: the height
|
|
|
|
"""
|
|
Shape.__init__(self)
|
|
self._width = w
|
|
self._height = h
|
|
self.SetDefaultRegionSize()
|
|
|
|
def GetBoundingBoxMin(self):
|
|
"""Get the minimum bounding box."""
|
|
return self._width, self._height
|
|
|
|
def GetPerimeterPoint(self, x1, y1, x2, y2):
|
|
"""
|
|
Get the perimeter point.
|
|
|
|
:param `x1`: the x1 position
|
|
:param `y1`: the y1 position
|
|
:param `x2`: the x2 position
|
|
:param `y2`: the y2 position
|
|
|
|
"""
|
|
bound_x, bound_y = self.GetBoundingBoxMax()
|
|
|
|
return DrawArcToEllipse(self._xpos, self._ypos, bound_x, bound_y, x2, y2, x1, y1)
|
|
|
|
def GetWidth(self):
|
|
"""Get the width."""
|
|
return self._width
|
|
|
|
def GetHeight(self):
|
|
"""Get the height."""
|
|
return self._height
|
|
|
|
def SetWidth(self, w):
|
|
"""
|
|
Set the width.
|
|
|
|
:param `w`: the width
|
|
|
|
"""
|
|
self._width = w
|
|
|
|
def SetHeight(self, h):
|
|
"""
|
|
Set the height.
|
|
|
|
:param `h`: the height
|
|
|
|
"""
|
|
self._height = h
|
|
|
|
def OnDraw(self, dc):
|
|
"""The draw handler."""
|
|
if self._shadowMode != SHADOW_NONE:
|
|
if self._shadowBrush:
|
|
dc.SetBrush(self._shadowBrush)
|
|
dc.SetPen(TransparentPen)
|
|
dc.DrawEllipse(self._xpos - self.GetWidth() / 2.0 + self._shadowOffsetX,
|
|
self._ypos - self.GetHeight() / 2.0 + self._shadowOffsetY,
|
|
self.GetWidth(), self.GetHeight())
|
|
|
|
if self._pen:
|
|
if self._pen.GetWidth() == 0:
|
|
dc.SetPen(TransparentPen)
|
|
else:
|
|
dc.SetPen(self._pen)
|
|
if self._brush:
|
|
dc.SetBrush(self._brush)
|
|
dc.DrawEllipse(self._xpos - self.GetWidth() / 2.0, self._ypos - self.GetHeight() / 2.0, self.GetWidth(), self.GetHeight())
|
|
|
|
def SetSize(self, x, y, recursive = True):
|
|
"""
|
|
Set the size.
|
|
|
|
:param `x`: the width
|
|
:param `y`: the height
|
|
:recursive: not used
|
|
|
|
"""
|
|
self.SetAttachmentSize(x, y)
|
|
self._width = x
|
|
self._height = y
|
|
self.SetDefaultRegionSize()
|
|
|
|
def GetNumberOfAttachments(self):
|
|
"""Get number of attachments."""
|
|
return Shape.GetNumberOfAttachments(self)
|
|
|
|
# There are 4 attachment points on an ellipse - 0 = top, 1 = right,
|
|
# 2 = bottom, 3 = left.
|
|
def GetAttachmentPosition(self, attachment, nth = 0, no_arcs = 1, line = None):
|
|
"""
|
|
Get attachment position.
|
|
|
|
:param `attachment`: the attachment ???
|
|
:param `nth`: get nth attachment ???
|
|
:param `no_arcs`: ???
|
|
:param `line`: ???
|
|
|
|
"""
|
|
if self._attachmentMode == ATTACHMENT_MODE_BRANCHING:
|
|
return Shape.GetAttachmentPosition(self, attachment, nth, no_arcs, line)
|
|
|
|
if self._attachmentMode != ATTACHMENT_MODE_NONE:
|
|
top = self._ypos + self._height / 2.0
|
|
bottom = self._ypos - self._height / 2.0
|
|
left = self._xpos - self._width / 2.0
|
|
right = self._xpos + self._width / 2.0
|
|
|
|
physicalAttachment = self.LogicalToPhysicalAttachment(attachment)
|
|
|
|
if physicalAttachment == 0:
|
|
if self._spaceAttachments:
|
|
x = left + (nth + 1) * self._width / (no_arcs + 1.0)
|
|
else:
|
|
x = self._xpos
|
|
y = top
|
|
# We now have the point on the bounding box: but get the point
|
|
# on the ellipse by imagining a vertical line from
|
|
# (x, self._ypos - self._height - 500) to (x, self._ypos) intersecting
|
|
# the ellipse.
|
|
|
|
return DrawArcToEllipse(self._xpos, self._ypos, self._width, self._height, x, self._ypos - self._height - 500, x, self._ypos)
|
|
elif physicalAttachment == 1:
|
|
x = right
|
|
if self._spaceAttachments:
|
|
y = bottom + (nth + 1) * self._height / (no_arcs + 1.0)
|
|
else:
|
|
y = self._ypos
|
|
return DrawArcToEllipse(self._xpos, self._ypos, self._width, self._height, self._xpos + self._width + 500, y, self._xpos, y)
|
|
elif physicalAttachment == 2:
|
|
if self._spaceAttachments:
|
|
x = left + (nth + 1) * self._width / (no_arcs + 1.0)
|
|
else:
|
|
x = self._xpos
|
|
y = bottom
|
|
return DrawArcToEllipse(self._xpos, self._ypos, self._width, self._height, x, self._ypos + self._height + 500, x, self._ypos)
|
|
elif physicalAttachment == 3:
|
|
x = left
|
|
if self._spaceAttachments:
|
|
y = bottom + (nth + 1) * self._height / (no_arcs + 1.0)
|
|
else:
|
|
y = self._ypos
|
|
return DrawArcToEllipse(self._xpos, self._ypos, self._width, self._height, self._xpos - self._width - 500, y, self._xpos, y)
|
|
else:
|
|
return Shape.GetAttachmentPosition(self, attachment, x, y, nth, no_arcs, line)
|
|
else:
|
|
return self._xpos, self._ypos
|
|
|
|
|
|
class CircleShape(EllipseShape):
|
|
"""
|
|
The :class:`CircleShape` class is an :class:`EllipseShape` whose width
|
|
and height are the same.
|
|
"""
|
|
def __init__(self, diameter):
|
|
"""
|
|
Default class constructor
|
|
|
|
:param `diameter`: the diameter
|
|
|
|
"""
|
|
EllipseShape.__init__(self, diameter, diameter)
|
|
self.SetMaintainAspectRatio(True)
|
|
|
|
def GetPerimeterPoint(self, x1, y1, x2, y2):
|
|
"""
|
|
Get the perimeter point.
|
|
|
|
:param `x1`: ???
|
|
:param `y1`: ???
|
|
:param `x2`: ???
|
|
:param `y2`: ???
|
|
:returns: ???
|
|
|
|
"""
|
|
return FindEndForCircle(self._width / 2.0, self._xpos, self._ypos, x2, y2)
|
|
|
|
|
|
class TextShape(RectangleShape):
|
|
"""
|
|
The :class:`TextShape` class like :class:`RectangleShape` but only the
|
|
text is displayed.
|
|
"""
|
|
def __init__(self, width, height):
|
|
"""
|
|
Default class constructor
|
|
|
|
:param `width`: the width
|
|
:param `height`: the height
|
|
|
|
"""
|
|
RectangleShape.__init__(self, width, height)
|
|
|
|
def OnDraw(self, dc):
|
|
"""not implemented???"""
|
|
pass
|
|
|
|
|
|
class ShapeRegion(object):
|
|
"""The :class:`ShapeRegion` class."""
|
|
def __init__(self, region = None):
|
|
"""
|
|
Default class constructor
|
|
|
|
:param `region`: a parent region or None???
|
|
|
|
"""
|
|
if region:
|
|
self._regionText = region._regionText
|
|
self._regionName = region._regionName
|
|
self._textColour = region._textColour
|
|
|
|
self._font = region._font
|
|
self._minHeight = region._minHeight
|
|
self._minWidth = region._minWidth
|
|
self._width = region._width
|
|
self._height = region._height
|
|
self._x = region._x
|
|
self._y = region._y
|
|
|
|
self._regionProportionX = region._regionProportionX
|
|
self._regionProportionY = region._regionProportionY
|
|
self._formatMode = region._formatMode
|
|
self._actualColourObject = region._actualColourObject
|
|
self._actualPenObject = None
|
|
self._penStyle = region._penStyle
|
|
self._penColour = region._penColour
|
|
|
|
self.ClearText()
|
|
for line in region._formattedText:
|
|
new_line = ShapeTextLine(line.GetX(), line.GetY(), line.GetText())
|
|
self._formattedText.append(new_line)
|
|
else:
|
|
self._regionText = ""
|
|
self._font = NormalFont
|
|
self._minHeight = 5.0
|
|
self._minWidth = 5.0
|
|
self._width = 0.0
|
|
self._height = 0.0
|
|
self._x = 0.0
|
|
self._y = 0.0
|
|
|
|
self._regionProportionX = -1.0
|
|
self._regionProportionY = -1.0
|
|
self._formatMode = FORMAT_CENTRE_HORIZ | FORMAT_CENTRE_VERT
|
|
self._regionName = ""
|
|
self._textColour = "BLACK"
|
|
self._penColour = "BLACK"
|
|
self._penStyle = wx.PENSTYLE_SOLID
|
|
self._actualColourObject = wx.TheColourDatabase.Find("BLACK")
|
|
self._actualPenObject = None
|
|
|
|
self._formattedText = []
|
|
|
|
def ClearText(self):
|
|
"""Clear the text."""
|
|
self._formattedText = []
|
|
|
|
def SetFont(self, f):
|
|
"""
|
|
Set the font.
|
|
|
|
:param `f`: an instance of :class:`Font`
|
|
"""
|
|
self._font = f
|
|
|
|
def SetMinSize(self, w, h):
|
|
"""
|
|
Set the minumum size.
|
|
|
|
:param `w`: the minimum width
|
|
:Param `h`: the minimum height
|
|
|
|
"""
|
|
self._minWidth = w
|
|
self._minHeight = h
|
|
|
|
def SetSize(self, w, h):
|
|
"""
|
|
Set the size.
|
|
|
|
:param `w`: the width
|
|
:Param `h`: the jeight
|
|
|
|
"""
|
|
self._width = w
|
|
self._height = h
|
|
|
|
def SetPosition(self, xp, yp):
|
|
"""
|
|
Set the position.
|
|
|
|
:param `xp`: the x position
|
|
:Param `yp`: the y position
|
|
|
|
"""
|
|
self._x = xp
|
|
self._y = yp
|
|
|
|
def SetProportions(self, xp, yp):
|
|
"""
|
|
Set the proportions.
|
|
|
|
:param `xp`: the x region proportion
|
|
:Param `yp`: the y region proportion
|
|
|
|
"""
|
|
self._regionProportionX = xp
|
|
self._regionProportionY = yp
|
|
|
|
def SetFormatMode(self, mode):
|
|
"""
|
|
Set the format mode of the region.
|
|
|
|
:param `mode`: can be a bit list of the following
|
|
|
|
============================== ==============================
|
|
Format mode Description
|
|
============================== ==============================
|
|
`FORMAT_NONE` No formatting
|
|
`FORMAT_CENTRE_HORIZ` Horizontal centring
|
|
`FORMAT_CENTRE_VERT` Vertical centring
|
|
============================== ==============================
|
|
|
|
"""
|
|
self._formatMode = mode
|
|
|
|
def SetColour(self, col):
|
|
"""
|
|
Set the colour.
|
|
|
|
:param str `col`: a valid colour name,
|
|
see :class:`ColourDatabase`
|
|
|
|
"""
|
|
self._textColour = col
|
|
self._actualColourObject = col
|
|
|
|
def GetActualColourObject(self):
|
|
"""Get the actual colour object from the :class:`ColourDatabase`."""
|
|
self._actualColourObject = wx.TheColourDatabase.Find(self.GetColour())
|
|
return self._actualColourObject
|
|
|
|
def SetPenColour(self, col):
|
|
"""
|
|
Set the pen colour.
|
|
|
|
:param str `col`: a valid colour name,
|
|
see :class:`ColourDatabase`
|
|
|
|
"""
|
|
self._penColour = col
|
|
self._actualPenObject = None
|
|
|
|
def GetActualPen(self):
|
|
"""
|
|
Get actual pen.
|
|
|
|
:note: Returns NULL if the pen is invisible
|
|
(different to pen being transparent; indicates that
|
|
region boundary should not be drawn.)
|
|
|
|
"""
|
|
if self._actualPenObject:
|
|
return self._actualPenObject
|
|
|
|
if not self._penColour:
|
|
return None
|
|
if self._penColour=="Invisible":
|
|
return None
|
|
self._actualPenObject = wx.Pen(self._penColour, 1, self._penStyle)
|
|
return self._actualPenObject
|
|
|
|
def SetText(self, s):
|
|
"""
|
|
Set the text.
|
|
|
|
:param str `s`: the text
|
|
|
|
"""
|
|
self._regionText = s
|
|
|
|
def SetName(self, s):
|
|
"""
|
|
Set the name.
|
|
|
|
:param str `s`: the name
|
|
|
|
"""
|
|
self._regionName = s
|
|
|
|
def GetText(self):
|
|
"""Get the text."""
|
|
return self._regionText
|
|
|
|
def GetFont(self):
|
|
"""Get the font."""
|
|
return self._font
|
|
|
|
def GetMinSize(self):
|
|
"""Get the minimum size."""
|
|
return self._minWidth, self._minHeight
|
|
|
|
def GetProportion(self):
|
|
"""Get the proportion."""
|
|
return self._regionProportionX, self._regionProportionY
|
|
|
|
def GetSize(self):
|
|
"""Get the size."""
|
|
return self._width, self._height
|
|
|
|
def GetPosition(self):
|
|
"""Get the position."""
|
|
return self._x, self._y
|
|
|
|
def GetFormatMode(self):
|
|
"""Get the format mode."""
|
|
return self._formatMode
|
|
|
|
def GetName(self):
|
|
"""Get the name."""
|
|
return self._regionName
|
|
|
|
def GetColour(self):
|
|
"""Get the colour."""
|
|
return self._textColour
|
|
|
|
def GetFormattedText(self):
|
|
"""Get the formatted text."""
|
|
return self._formattedText
|
|
|
|
def GetPenColour(self):
|
|
"""Get the pen colour"""
|
|
return self._penColour
|
|
|
|
def GetPenStyle(self):
|
|
"""Get the pen style."""
|
|
return self._penStyle
|
|
|
|
def SetPenStyle(self, style):
|
|
"""
|
|
Set the pen style.
|
|
|
|
:param `style`: the style, see :class:`Pen`
|
|
|
|
"""
|
|
self._penStyle = style
|
|
self._actualPenObject = None
|
|
|
|
def GetWidth(self):
|
|
"""Get the width."""
|
|
return self._width
|
|
|
|
def GetHeight(self):
|
|
"""Get the height."""
|
|
return self._height
|
|
|
|
|
|
class ControlPoint(RectangleShape):
|
|
"""The :class:`ControlPoint` class."""
|
|
def __init__(self, theCanvas, object, size, the_xoffset, the_yoffset, the_type):
|
|
"""
|
|
Default class constructor
|
|
|
|
:param `theCanvas`: a :class:`~lib.ogl.Canvas`
|
|
:param `object`: the shape, instance of :class:`~lib.ogl.Shape`
|
|
:param float `size`: the size
|
|
:param float `the_xoffset`: the x position
|
|
:param float `the_yoffset`: the y position
|
|
:param int `the_type`: one of the following types ???
|
|
|
|
======================================== ==================================
|
|
Control point type Description
|
|
======================================== ==================================
|
|
`CONTROL_POINT_VERTICAL` Vertical
|
|
`CONTROL_POINT_HORIZONTAL` Horizontal
|
|
`CONTROL_POINT_DIAGONAL` Diagonal
|
|
======================================== ==================================
|
|
|
|
"""
|
|
RectangleShape.__init__(self, size, size)
|
|
|
|
self._canvas = theCanvas
|
|
self._shape = object
|
|
self._xoffset = the_xoffset
|
|
self._yoffset = the_yoffset
|
|
self._type = the_type
|
|
self.SetPen(BlackForegroundPen)
|
|
self.SetBrush(wx.BLACK_BRUSH)
|
|
self._oldCursor = None
|
|
self._visible = True
|
|
self._eraseObject = True
|
|
|
|
# Don't even attempt to draw any text - waste of time
|
|
def OnDrawContents(self, dc):
|
|
"""not implemented???"""
|
|
pass
|
|
|
|
def OnDraw(self, dc):
|
|
"""The draw handler."""
|
|
self._xpos = self._shape.GetX() + self._xoffset
|
|
self._ypos = self._shape.GetY() + self._yoffset
|
|
RectangleShape.OnDraw(self, dc)
|
|
|
|
def OnErase(self, dc):
|
|
"""The erase handler."""
|
|
RectangleShape.OnErase(self, dc)
|
|
|
|
# Implement resizing of canvas object
|
|
def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
|
|
"""The drag left handler."""
|
|
self._shape.GetEventHandler().OnSizingDragLeft(self, draw, x, y, keys, attachment)
|
|
|
|
def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
|
|
"""The begin drag left handler."""
|
|
self._shape.GetEventHandler().OnSizingBeginDragLeft(self, x, y, keys, attachment)
|
|
|
|
def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
|
|
"""The end drag left handler."""
|
|
self._shape.GetEventHandler().OnSizingEndDragLeft(self, x, y, keys, attachment)
|
|
|
|
def GetNumberOfAttachments(self):
|
|
"""Get the number of attachments."""
|
|
return 1
|
|
|
|
def GetAttachmentPosition(self, attachment, nth = 0, no_arcs = 1, line = None):
|
|
"""
|
|
Get the attachment position.
|
|
|
|
:param `attachment`: the attachment ???
|
|
:param `nth`: get nth attachment ???
|
|
:param `no_arcs`: ???
|
|
:param `line`: ???
|
|
|
|
"""
|
|
return self._xpos, self._ypos
|
|
|
|
def SetEraseObject(self, er):
|
|
"""
|
|
Set the erase object ???
|
|
|
|
:param `er`: the object
|
|
|
|
"""
|
|
self._eraseObject = er
|
|
|
|
|
|
class PolygonControlPoint(ControlPoint):
|
|
"""The :class:`PolygonControlPoint` class."""
|
|
def __init__(self, theCanvas, object, size, vertex, the_xoffset, the_yoffset):
|
|
"""
|
|
Default class constructor
|
|
|
|
:param `theCanvas`: a :class:`~lib.ogl.Canvas`
|
|
:param `object`: the shape, instance of :class:`~lib.ogl.Shape`
|
|
:param float `size`: the size
|
|
:param `vertext`: the vertex
|
|
:param float `the_xoffset`: the x position
|
|
:param float `the_yoffset`: the y position
|
|
:param int `the_type`: one of the following types ???
|
|
|
|
======================================== ==================================
|
|
Control point type Description
|
|
======================================== ==================================
|
|
`CONTROL_POINT_VERTICAL` Vertical
|
|
`CONTROL_POINT_HORIZONTAL` Horizontal
|
|
`CONTROL_POINT_DIAGONAL` Diagonal
|
|
======================================== ==================================
|
|
|
|
"""
|
|
ControlPoint.__init__(self, theCanvas, object, size, the_xoffset, the_yoffset, 0)
|
|
self._polygonVertex = vertex
|
|
self._originalDistance = 0.0
|
|
self._newSize = wx.RealPoint()
|
|
self._originalSize = wx.RealPoint()
|
|
|
|
def GetNewSize(self):
|
|
"""Get the new size."""
|
|
return self._newSize
|
|
|
|
def CalculateNewSize(self, x, y):
|
|
"""
|
|
Calculate what new size would be, at end of resize.
|
|
|
|
:param `x`: x ???
|
|
:param `y`: y ???
|
|
|
|
"""
|
|
bound_x, bound_y = self.GetShape().GetBoundingBoxMax()
|
|
dist = math.sqrt((x - self._shape.GetX()) * (x - self._shape.GetX()) + (y - self._shape.GetY()) * (y - self._shape.GetY()))
|
|
|
|
self._newSize[0] = dist / self._originalDistance * self._originalSize[0]
|
|
self._newSize[1] = dist / self._originalDistance * self._originalSize[1]
|
|
|
|
# Implement resizing polygon or moving the vertex
|
|
def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
|
|
"""The drag left handler."""
|
|
self._shape.GetEventHandler().OnSizingDragLeft(self, draw, x, y, keys, attachment)
|
|
|
|
def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
|
|
"""The begin drag left handler."""
|
|
self._shape.GetEventHandler().OnSizingBeginDragLeft(self, x, y, keys, attachment)
|
|
|
|
def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
|
|
"""The end drag left handler."""
|
|
self._shape.GetEventHandler().OnSizingEndDragLeft(self, x, y, keys, attachment)
|
|
|
|
from .canvas import *
|
|
from .lines import *
|
|
from .composit import *
|