mirror of
https://github.com/Sneed-Group/Poodletooth-iLand
synced 2024-12-30 07:02:48 -06:00
1821 lines
65 KiB
Python
1821 lines
65 KiB
Python
|
# -*- coding: utf-8 -*-
|
||
|
#----------------------------------------------------------------------------
|
||
|
# Name: lines.py
|
||
|
# Purpose: LineShape class
|
||
|
#
|
||
|
# 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 :class:`~lib.ogl.lines.LineShape` class may have arrowheads at the
|
||
|
beginning and end.
|
||
|
"""
|
||
|
import sys
|
||
|
import math
|
||
|
|
||
|
from .basic import Shape, ShapeRegion, ShapeTextLine, ControlPoint, RectangleShape
|
||
|
from .oglmisc import *
|
||
|
|
||
|
# Line alignment flags
|
||
|
# Vertical by default
|
||
|
LINE_ALIGNMENT_HORIZ = 1
|
||
|
LINE_ALIGNMENT_VERT = 0
|
||
|
LINE_ALIGNMENT_TO_NEXT_HANDLE = 2
|
||
|
LINE_ALIGNMENT_NONE = 0
|
||
|
|
||
|
|
||
|
|
||
|
class LineControlPoint(ControlPoint):
|
||
|
"""
|
||
|
The :class:`LineControlPoint` class.
|
||
|
"""
|
||
|
def __init__(self, theCanvas = None, object = None, size = 0.0, x = 0.0, y = 0.0, the_type = 0):
|
||
|
"""
|
||
|
Default class constructor.
|
||
|
|
||
|
:param `theCanvas`: a :class:`~lib.ogl.Canvas`
|
||
|
:param `object`: not used ???
|
||
|
:param float `size`: the size
|
||
|
:param float `x`: the x position
|
||
|
:param float `y`: 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, x, y, the_type)
|
||
|
self._xpos = x
|
||
|
self._ypos = y
|
||
|
self._type = the_type
|
||
|
self._point = None
|
||
|
self._originalPos = None
|
||
|
|
||
|
def OnDraw(self, dc):
|
||
|
"""The draw handler."""
|
||
|
RectangleShape.OnDraw(self, dc)
|
||
|
|
||
|
# Implement movement of Line point
|
||
|
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)
|
||
|
|
||
|
|
||
|
|
||
|
class ArrowHead(object):
|
||
|
"""
|
||
|
The arrow head class.
|
||
|
"""
|
||
|
def __init__(self, type = 0, end = 0, size = 0.0, dist = 0.0, name = "", mf = None, arrowId = -1):
|
||
|
"""
|
||
|
Default class constructor.
|
||
|
|
||
|
:param int `type`: the type
|
||
|
|
||
|
======================================== ==================================
|
||
|
Arrow head type Description
|
||
|
======================================== ==================================
|
||
|
`ARROW_HOLLOW_CIRCLE` a hollow circle
|
||
|
`ARROW_FILLED_CIRCLE` a filled circle
|
||
|
`ARROW_ARROW` an arrow
|
||
|
`ARROW_SINGLE_OBLIQUE` a single oblique
|
||
|
`ARROW_DOUBLE_OBLIQUE` a double oblique
|
||
|
`ARROW_METAFILE` custom, define in metafile
|
||
|
======================================== ==================================
|
||
|
|
||
|
:param int `end`: end of arrow head ???
|
||
|
:param float `size`: size of arrow head
|
||
|
:param float `dist`: dist ???
|
||
|
:param string `name`: name of arrow head
|
||
|
:param `mf`: mf ???
|
||
|
:param `arrowId`: id of arrow head
|
||
|
|
||
|
"""
|
||
|
if isinstance(type, ArrowHead):
|
||
|
pass
|
||
|
else:
|
||
|
self._arrowType = type
|
||
|
self._arrowEnd = end
|
||
|
self._arrowSize = size
|
||
|
self._xOffset = dist
|
||
|
self._yOffset = 0.0
|
||
|
self._spacing = 5.0
|
||
|
|
||
|
self._arrowName = name
|
||
|
self._metaFile = mf
|
||
|
self._id = arrowId
|
||
|
if self._id == -1:
|
||
|
self._id = wx.NewId()
|
||
|
|
||
|
def _GetType(self):
|
||
|
return self._arrowType
|
||
|
|
||
|
def GetPosition(self):
|
||
|
"""Get end position."""
|
||
|
return self._arrowEnd
|
||
|
|
||
|
def SetPosition(self, pos):
|
||
|
"""Set end position.
|
||
|
|
||
|
:param `pos`: position to set it to
|
||
|
"""
|
||
|
self._arrowEnd = pos
|
||
|
|
||
|
def GetXOffset(self):
|
||
|
"""Get the X offset."""
|
||
|
return self._xOffset
|
||
|
|
||
|
def GetYOffset(self):
|
||
|
"""Get the Y offset."""
|
||
|
return self._yOffset
|
||
|
|
||
|
def GetSpacing(self):
|
||
|
"""Get the spacing."""
|
||
|
return self._spacing
|
||
|
|
||
|
def GetSize(self):
|
||
|
"""Get the arrow size."""
|
||
|
return self._arrowSize
|
||
|
|
||
|
def SetSize(self, size):
|
||
|
"""Set the arrow size.
|
||
|
|
||
|
:param `size`: size in points???
|
||
|
|
||
|
:note: if a custom arrow is used size is used to scale the arrow???
|
||
|
"""
|
||
|
self._arrowSize = size
|
||
|
if self._arrowType == ARROW_METAFILE and self._metaFile:
|
||
|
oldWidth = self._metaFile._width
|
||
|
if oldWidth == 0:
|
||
|
return
|
||
|
|
||
|
scale = float(size) / oldWidth
|
||
|
if scale != 1:
|
||
|
self._metaFile.Scale(scale, scale)
|
||
|
|
||
|
def GetName(self):
|
||
|
"""Get the arrow name."""
|
||
|
return self._arrowName
|
||
|
|
||
|
def SetXOffset(self, x):
|
||
|
"""Set the X offset.
|
||
|
|
||
|
:param `x`: value to set the offset to???
|
||
|
"""
|
||
|
self._xOffset = x
|
||
|
|
||
|
def SetYOffset(self, y):
|
||
|
"""Set the Y offset.
|
||
|
|
||
|
:param `y`: value to set the offset to???
|
||
|
"""
|
||
|
self._yOffset = y
|
||
|
|
||
|
def GetMetaFile(self):
|
||
|
"""Get the metafile."""
|
||
|
return self._metaFile
|
||
|
|
||
|
def GetId(self):
|
||
|
"""Get the id."""
|
||
|
return self._id
|
||
|
|
||
|
def GetArrowEnd(self):
|
||
|
"""Get the arrow end position."""
|
||
|
return self._arrowEnd
|
||
|
|
||
|
def GetArrowSize(self):
|
||
|
"""Get the arrow size."""
|
||
|
return self._arrowSize
|
||
|
|
||
|
def SetSpacing(self, sp):
|
||
|
"""Set the spacing.
|
||
|
|
||
|
:param `sp`: the new spacing value
|
||
|
"""
|
||
|
self._spacing = sp
|
||
|
|
||
|
|
||
|
|
||
|
class LabelShape(RectangleShape):
|
||
|
"""
|
||
|
The label shape class.
|
||
|
"""
|
||
|
def __init__(self, parent, region, w, h):
|
||
|
"""
|
||
|
Default class constructor.
|
||
|
|
||
|
:param `parent`: the parent an instance of :class:`Line`
|
||
|
:param `region`: the shape region an instance of :class:`~lib.ogl.ShapeRegion`
|
||
|
:param `w`: width in points
|
||
|
:param `h`: heigth in points
|
||
|
|
||
|
"""
|
||
|
RectangleShape.__init__(self, w, h)
|
||
|
self._lineShape = parent
|
||
|
self._shapeRegion = region
|
||
|
self.SetPen(wx.Pen(wx.Colour(0, 0, 0), 1, wx.PENSTYLE_DOT))
|
||
|
|
||
|
def OnDraw(self, dc):
|
||
|
"""The draw handler."""
|
||
|
if self._lineShape and not self._lineShape.GetDrawHandles():
|
||
|
return
|
||
|
|
||
|
x1 = self._xpos - self._width / 2.0
|
||
|
y1 = self._ypos - self._height / 2.0
|
||
|
|
||
|
if self._pen:
|
||
|
if self._pen.GetWidth() == 0:
|
||
|
dc.SetPen(wx.Pen(wx.WHITE, 1, wx.TRANSPARENT))
|
||
|
else:
|
||
|
dc.SetPen(self._pen)
|
||
|
dc.SetBrush(wx.TRANSPARENT_BRUSH)
|
||
|
|
||
|
if self._cornerRadius > 0:
|
||
|
dc.DrawRoundedRectangle(x1, y1, self._width, self._height, self._cornerRadius)
|
||
|
else:
|
||
|
dc.DrawRectangle(x1, y1, self._width, self._height)
|
||
|
|
||
|
def OnDrawContents(self, dc):
|
||
|
"""not implemented???"""
|
||
|
pass
|
||
|
|
||
|
def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
|
||
|
"""The drag left handler."""
|
||
|
RectangleShape.OnDragLeft(self, draw, x, y, keys, attachment)
|
||
|
|
||
|
def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
|
||
|
"""The begin drag left handler."""
|
||
|
RectangleShape.OnBeginDragLeft(self, x, y, keys, attachment)
|
||
|
|
||
|
def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
|
||
|
"""The end drag left handler."""
|
||
|
RectangleShape.OnEndDragLeft(self, x, y, keys, attachment)
|
||
|
|
||
|
def OnMovePre(self, dc, x, y, old_x, old_y, display):
|
||
|
"""The move 'pre' handler.???"""
|
||
|
return self._lineShape.OnLabelMovePre(dc, self, x, y, old_x, old_y, display)
|
||
|
|
||
|
# Divert left and right clicks to line object
|
||
|
def OnLeftClick(self, x, y, keys = 0, attachment = 0):
|
||
|
"""The left click handler."""
|
||
|
self._lineShape.GetEventHandler().OnLeftClick(x, y, keys, attachment)
|
||
|
|
||
|
def OnRightClick(self, x, y, keys = 0, attachment = 0):
|
||
|
"""The right click handler."""
|
||
|
self._lineShape.GetEventHandler().OnRightClick(x, y, keys, attachment)
|
||
|
|
||
|
|
||
|
|
||
|
class LineShape(Shape):
|
||
|
"""
|
||
|
The LineShape class may be attached to two nodes, it may be segmented,
|
||
|
in which case a control point is drawn for each joint.
|
||
|
|
||
|
A :class:`LineShape` may have arrows at the beginning, end and centre.
|
||
|
"""
|
||
|
def __init__(self):
|
||
|
"""
|
||
|
Default class constructor.
|
||
|
|
||
|
"""
|
||
|
Shape.__init__(self)
|
||
|
|
||
|
self._sensitivity = OP_CLICK_LEFT | OP_CLICK_RIGHT
|
||
|
self._draggable = False
|
||
|
self._attachmentTo = 0
|
||
|
self._attachmentFrom = 0
|
||
|
self._from = None
|
||
|
self._to = None
|
||
|
self._erasing = False
|
||
|
self._arrowSpacing = 5.0
|
||
|
self._ignoreArrowOffsets = False
|
||
|
self._isSpline = False
|
||
|
self._maintainStraightLines = False
|
||
|
self._alignmentStart = 0
|
||
|
self._alignmentEnd = 0
|
||
|
|
||
|
self._lineControlPoints = None
|
||
|
|
||
|
# Clear any existing regions (created in an earlier constructor)
|
||
|
# and make the three line regions.
|
||
|
self.ClearRegions()
|
||
|
for name in ["Middle","Start","End"]:
|
||
|
newRegion = ShapeRegion()
|
||
|
newRegion.SetName(name)
|
||
|
newRegion.SetSize(150, 50)
|
||
|
self._regions.append(newRegion)
|
||
|
|
||
|
self._labelObjects = [None, None, None]
|
||
|
self._lineOrientations = []
|
||
|
self._lineControlPoints = []
|
||
|
self._arcArrows = []
|
||
|
|
||
|
def GetFrom(self):
|
||
|
"""Get the 'from' object."""
|
||
|
return self._from
|
||
|
|
||
|
def GetTo(self):
|
||
|
"""Get the 'to' object."""
|
||
|
return self._to
|
||
|
|
||
|
def GetAttachmentFrom(self):
|
||
|
"""Get the attachment point on the 'from' node."""
|
||
|
return self._attachmentFrom
|
||
|
|
||
|
def GetAttachmentTo(self):
|
||
|
"""Get the attachment point on the 'to' node."""
|
||
|
return self._attachmentTo
|
||
|
|
||
|
def GetLineControlPoints(self):
|
||
|
"""Get the line control points."""
|
||
|
return self._lineControlPoints
|
||
|
|
||
|
def SetSpline(self, spline):
|
||
|
"""Specifies whether a spline is to be drawn through the control points.
|
||
|
|
||
|
:param boolean `spline`: True draw a spline through control points.
|
||
|
|
||
|
"""
|
||
|
self._isSpline = spline
|
||
|
|
||
|
def IsSpline(self):
|
||
|
"""If `True` a spline is drawn through the control points."""
|
||
|
return self._isSpline
|
||
|
|
||
|
def SetAttachmentFrom(self, attach):
|
||
|
"""Set the 'from' shape attachment."""
|
||
|
self._attachmentFrom = attach
|
||
|
|
||
|
def SetAttachmentTo(self, attach):
|
||
|
"""Set the 'to' shape attachment."""
|
||
|
self._attachmentTo = attach
|
||
|
|
||
|
def Draggable(self):
|
||
|
"""
|
||
|
Line is not draggable.
|
||
|
|
||
|
:note: This is really to distinguish between lines and other images.
|
||
|
For lines we want to pass drag to canvas, since lines tend to prevent
|
||
|
dragging on a canvas (they get in the way.)
|
||
|
|
||
|
"""
|
||
|
return False
|
||
|
|
||
|
def SetIgnoreOffsets(self, ignore):
|
||
|
"""Set whether to ignore offsets from the end of the line when drawing."""
|
||
|
self._ignoreArrowOffsets = ignore
|
||
|
|
||
|
def GetArrows(self):
|
||
|
"""Get the defined arrows."""
|
||
|
return self._arcArrows
|
||
|
|
||
|
def GetAlignmentStart(self):
|
||
|
"""Get alignment start"""
|
||
|
return self._alignmentStart
|
||
|
|
||
|
def GetAlignmentEnd(self):
|
||
|
"""Get alignment end."""
|
||
|
return self._alignmentEnd
|
||
|
|
||
|
def IsEnd(self, nodeObject):
|
||
|
"""`True` if shape is at the end of the line."""
|
||
|
return self._to == nodeObject
|
||
|
|
||
|
def MakeLineControlPoints(self, n):
|
||
|
"""
|
||
|
Make a given number of control points.
|
||
|
|
||
|
:param int `n`: number of control points, minimum of two
|
||
|
|
||
|
"""
|
||
|
self._lineControlPoints = []
|
||
|
|
||
|
for _ in range(n):
|
||
|
point = wx.RealPoint(-999, -999)
|
||
|
self._lineControlPoints.append(point)
|
||
|
|
||
|
# pi: added _initialised to keep track of when we have set
|
||
|
# the middle points to something other than (-999, -999)
|
||
|
self._initialised = False
|
||
|
|
||
|
def InsertLineControlPoint(self, dc = None, point = None):
|
||
|
"""
|
||
|
Insert a control point at an optional given position.
|
||
|
|
||
|
:param `dc`: an instance of :class:`wx.MemoryDC`
|
||
|
:param `point`: an optional point, otherwise will use _lineControlPoints
|
||
|
|
||
|
"""
|
||
|
if dc:
|
||
|
self.Erase(dc)
|
||
|
|
||
|
if point:
|
||
|
line_x, line_y = point
|
||
|
else:
|
||
|
last_point = self._lineControlPoints[-1]
|
||
|
second_last_point = self._lineControlPoints[-2]
|
||
|
|
||
|
line_x = (last_point[0] + second_last_point[0]) / 2.0
|
||
|
line_y = (last_point[1] + second_last_point[1]) / 2.0
|
||
|
|
||
|
point = wx.RealPoint(line_x, line_y)
|
||
|
self._lineControlPoints.insert(len(self._lineControlPoints)-1, point)
|
||
|
|
||
|
def DeleteLineControlPoint(self):
|
||
|
"""Delete an arbitary point on the line."""
|
||
|
if len(self._lineControlPoints) < 3:
|
||
|
return False
|
||
|
|
||
|
del self._lineControlPoints[-2]
|
||
|
return True
|
||
|
|
||
|
def Initialise(self):
|
||
|
"""Initialise the line object."""
|
||
|
if self._lineControlPoints:
|
||
|
# Just move the first and last control points
|
||
|
first_point = self._lineControlPoints[0]
|
||
|
last_point = self._lineControlPoints[-1]
|
||
|
|
||
|
# If any of the line points are at -999, we must
|
||
|
# initialize them by placing them half way between the first
|
||
|
# and the last.
|
||
|
|
||
|
for i in range(1,len(self._lineControlPoints)):
|
||
|
point = self._lineControlPoints[i]
|
||
|
if point[0] == -999:
|
||
|
if first_point[0] < last_point[0]:
|
||
|
x1 = first_point[0]
|
||
|
x2 = last_point[0]
|
||
|
else:
|
||
|
x2 = first_point[0]
|
||
|
x1 = last_point[0]
|
||
|
if first_point[1] < last_point[1]:
|
||
|
y1 = first_point[1]
|
||
|
y2 = last_point[1]
|
||
|
else:
|
||
|
y2 = first_point[1]
|
||
|
y1 = last_point[1]
|
||
|
self._lineControlPoints[i] = wx.RealPoint((x2 - x1) / 2.0 + x1, (y2 - y1) / 2.0 + y1)
|
||
|
self._initialised = True
|
||
|
|
||
|
def FormatText(self, dc, s, i):
|
||
|
"""
|
||
|
Format a text string according to the region size, adding
|
||
|
strings with positions to region text list.
|
||
|
|
||
|
:param `dc`: an instance of :class:`wx.MemoryDC`
|
||
|
:param str `s`: the text string
|
||
|
:param int `i`: index to the region to be used???
|
||
|
|
||
|
"""
|
||
|
self.ClearText(i)
|
||
|
|
||
|
if len(self._regions) == 0 or i >= len(self._regions):
|
||
|
return
|
||
|
|
||
|
region = self._regions[i]
|
||
|
region.SetText(s)
|
||
|
dc.SetFont(region.GetFont())
|
||
|
|
||
|
w, h = region.GetSize()
|
||
|
# Initialize the size if zero
|
||
|
if (w == 0 or h == 0) and s:
|
||
|
w, h = 100, 50
|
||
|
region.SetSize(w, h)
|
||
|
|
||
|
string_list = FormatText(dc, s, w - 5, h - 5, region.GetFormatMode())
|
||
|
for s in string_list:
|
||
|
line = ShapeTextLine(0.0, 0.0, s)
|
||
|
region.GetFormattedText().append(line)
|
||
|
|
||
|
actualW = w
|
||
|
actualH = h
|
||
|
if region.GetFormatMode() & FORMAT_SIZE_TO_CONTENTS:
|
||
|
actualW, actualH = GetCentredTextExtent(dc, region.GetFormattedText(), self._xpos, self._ypos, w, h)
|
||
|
if actualW != w or actualH != h:
|
||
|
xx, yy = self.GetLabelPosition(i)
|
||
|
self.EraseRegion(dc, region, xx, yy)
|
||
|
if len(self._labelObjects) < i:
|
||
|
self._labelObjects[i].Select(False, dc)
|
||
|
self._labelObjects[i].Erase(dc)
|
||
|
self._labelObjects[i].SetSize(actualW, actualH)
|
||
|
|
||
|
region.SetSize(actualW, actualH)
|
||
|
|
||
|
if len(self._labelObjects) < i:
|
||
|
self._labelObjects[i].Select(True, dc)
|
||
|
self._labelObjects[i].Draw(dc)
|
||
|
|
||
|
CentreText(dc, region.GetFormattedText(), self._xpos, self._ypos, actualW, actualH, region.GetFormatMode())
|
||
|
self._formatted = True
|
||
|
|
||
|
def DrawRegion(self, dc, region, x, y):
|
||
|
"""
|
||
|
Format one region at this position.
|
||
|
|
||
|
:param `dc`: an instance of :class:`wx.MemoryDC`
|
||
|
:param `dc`: an instance of :class:`~lib.ogl.ShapeRegion`
|
||
|
:param `x`: the x position
|
||
|
:param `y`: the y position
|
||
|
|
||
|
"""
|
||
|
if self.GetDisableLabel():
|
||
|
return
|
||
|
|
||
|
w, h = region.GetSize()
|
||
|
|
||
|
# Get offset from x, y
|
||
|
xx, yy = region.GetPosition()
|
||
|
|
||
|
xp = xx + x
|
||
|
yp = yy + y
|
||
|
|
||
|
# First, clear a rectangle for the text IF there is any
|
||
|
if len(region.GetFormattedText()):
|
||
|
dc.SetPen(self.GetBackgroundPen())
|
||
|
dc.SetBrush(self.GetBackgroundBrush())
|
||
|
|
||
|
# Now draw the text
|
||
|
if region.GetFont():
|
||
|
dc.SetFont(region.GetFont())
|
||
|
dc.DrawRectangle(xp - w / 2.0, yp - h / 2.0, w, h)
|
||
|
|
||
|
if self._pen:
|
||
|
dc.SetPen(self._pen)
|
||
|
dc.SetTextForeground(region.GetActualColourObject())
|
||
|
|
||
|
DrawFormattedText(dc, region.GetFormattedText(), xp, yp, w, h, region.GetFormatMode())
|
||
|
|
||
|
def EraseRegion(self, dc, region, x, y):
|
||
|
"""
|
||
|
Erase one region at this position.
|
||
|
|
||
|
:param `dc`: an instance of :class:`wx.MemoryDC`
|
||
|
:param `dc`: an instance of :class:`~lib.ogl.ShapeRegion`
|
||
|
:param `x`: the x position
|
||
|
:param `y`: the y position
|
||
|
|
||
|
"""
|
||
|
if self.GetDisableLabel():
|
||
|
return
|
||
|
|
||
|
w, h = region.GetSize()
|
||
|
|
||
|
# Get offset from x, y
|
||
|
xx, yy = region.GetPosition()
|
||
|
|
||
|
xp = xx + x
|
||
|
yp = yy + y
|
||
|
|
||
|
if region.GetFormattedText():
|
||
|
dc.SetPen(self.GetBackgroundPen())
|
||
|
dc.SetBrush(self.GetBackgroundBrush())
|
||
|
|
||
|
dc.DrawRectangle(xp - w / 2.0, yp - h / 2.0, w, h)
|
||
|
|
||
|
def GetLabelPosition(self, position):
|
||
|
"""
|
||
|
Get the reference point for a label.
|
||
|
|
||
|
:param `position`: 0 = middle, 1 = start, 2 = end, Region x and y are offsets from this.
|
||
|
|
||
|
"""
|
||
|
if position == 0:
|
||
|
# Want to take the middle section for the label
|
||
|
half_way = int(len(self._lineControlPoints) / 2.0)
|
||
|
|
||
|
# Find middle of this line
|
||
|
point = self._lineControlPoints[half_way - 1]
|
||
|
next_point = self._lineControlPoints[half_way]
|
||
|
|
||
|
dx = next_point[0] - point[0]
|
||
|
dy = next_point[1] - point[1]
|
||
|
|
||
|
return point[0] + dx / 2.0, point[1] + dy / 2.0
|
||
|
elif position == 1:
|
||
|
return self._lineControlPoints[0][0], self._lineControlPoints[0][1]
|
||
|
elif position == 2:
|
||
|
return self._lineControlPoints[-1][0], self._lineControlPoints[-1][1]
|
||
|
|
||
|
def Straighten(self, dc = None):
|
||
|
"""
|
||
|
Straighten verticals and horizontals.
|
||
|
|
||
|
:param `dc`: an instance of :class:`wx.MemoryDC` or None
|
||
|
|
||
|
"""
|
||
|
if len(self._lineControlPoints) < 3:
|
||
|
return
|
||
|
|
||
|
if dc:
|
||
|
self.Erase(dc)
|
||
|
|
||
|
GraphicsStraightenLine(self._lineControlPoints[-1], self._lineControlPoints[-2])
|
||
|
|
||
|
for i in range(len(self._lineControlPoints) - 2):
|
||
|
GraphicsStraightenLine(self._lineControlPoints[i], self._lineControlPoints[i + 1])
|
||
|
|
||
|
if dc:
|
||
|
self.Draw(dc)
|
||
|
|
||
|
def Unlink(self):
|
||
|
"""Unlink the line from the nodes at either end."""
|
||
|
if self._to:
|
||
|
self._to.GetLines().remove(self)
|
||
|
if self._from:
|
||
|
self._from.GetLines().remove(self)
|
||
|
self._to = None
|
||
|
self._from = None
|
||
|
for i in range(3):
|
||
|
if self._labelObjects[i]:
|
||
|
self._labelObjects[i].Select(False)
|
||
|
self._labelObjects[i].RemoveFromCanvas(self._canvas)
|
||
|
self.ClearArrowsAtPosition(-1)
|
||
|
|
||
|
def Delete(self):
|
||
|
"""Delete the line, unlink it first."""
|
||
|
self.Unlink()
|
||
|
Shape.Delete(self)
|
||
|
|
||
|
def SetEnds(self, x1, y1, x2, y2):
|
||
|
"""
|
||
|
Set the end positions of the line.
|
||
|
|
||
|
:param: `x1`: x1 position
|
||
|
:param: `y1`: y1 position
|
||
|
:param: `x2`: x2 position
|
||
|
:param: `y2`: y2 position
|
||
|
|
||
|
|
||
|
"""
|
||
|
self._lineControlPoints[0] = wx.RealPoint(x1, y1)
|
||
|
self._lineControlPoints[-1] = wx.RealPoint(x2, y2)
|
||
|
|
||
|
# Find centre point
|
||
|
self._xpos = (x1 + x2) / 2.0
|
||
|
self._ypos = (y1 + y2) / 2.0
|
||
|
|
||
|
# Get absolute positions of ends
|
||
|
def GetEnds(self):
|
||
|
"""Get the visible endpoints of the lines for drawing between two objects."""
|
||
|
first_point = self._lineControlPoints[0]
|
||
|
last_point = self._lineControlPoints[-1]
|
||
|
|
||
|
return first_point[0], first_point[1], last_point[0], last_point[1]
|
||
|
|
||
|
def SetAttachments(self, from_attach, to_attach):
|
||
|
"""
|
||
|
Specify which object attachment points should be used at each end of the line.
|
||
|
|
||
|
:param `from_attach`: the from points ???
|
||
|
:param `to_attach`: the to points ???
|
||
|
|
||
|
"""
|
||
|
self._attachmentFrom = from_attach
|
||
|
self._attachmentTo = to_attach
|
||
|
|
||
|
def HitTest(self, x, y):
|
||
|
"""
|
||
|
Line hit test.
|
||
|
|
||
|
:param `x`: x position
|
||
|
:param `y`: y position
|
||
|
|
||
|
"""
|
||
|
if not self._lineControlPoints:
|
||
|
return False
|
||
|
|
||
|
# Look at label regions in case mouse is over a label
|
||
|
inLabelRegion = False
|
||
|
for i in range(3):
|
||
|
if self._regions[i]:
|
||
|
region = self._regions[i]
|
||
|
if len(region._formattedText):
|
||
|
xp, yp = self.GetLabelPosition(i)
|
||
|
# Offset region from default label position
|
||
|
cx, cy = region.GetPosition()
|
||
|
cw, ch = region.GetSize()
|
||
|
cx += xp
|
||
|
cy += yp
|
||
|
|
||
|
rLeft = cx - cw / 2.0
|
||
|
rTop = cy - ch / 2.0
|
||
|
rRight = cx + cw / 2.0
|
||
|
rBottom = cy + ch / 2.0
|
||
|
if x > rLeft and x < rRight and y > rTop and y < rBottom:
|
||
|
inLabelRegion = True
|
||
|
break
|
||
|
|
||
|
for i in range(len(self._lineControlPoints) - 1):
|
||
|
point1 = self._lineControlPoints[i]
|
||
|
point2 = self._lineControlPoints[i + 1]
|
||
|
|
||
|
# For inaccurate mousing allow 8 pixel corridor
|
||
|
extra = 4
|
||
|
|
||
|
dx = point2[0] - point1[0]
|
||
|
dy = point2[1] - point1[1]
|
||
|
|
||
|
seg_len = math.sqrt(dx * dx + dy * dy)
|
||
|
if dy == 0 and dx == 0:
|
||
|
continue
|
||
|
distance_from_seg = seg_len * float((x - point1[0]) * dy - (y - point1[1]) * dx) / (dy * dy + dx * dx)
|
||
|
distance_from_prev = seg_len * float((y - point1[1]) * dy + (x - point1[0]) * dx) / (dy * dy + dx * dx)
|
||
|
|
||
|
if abs(distance_from_seg) < extra and distance_from_prev >= 0 and distance_from_prev <= seg_len or inLabelRegion:
|
||
|
return 0, distance_from_seg
|
||
|
|
||
|
return False
|
||
|
|
||
|
def DrawArrows(self, dc):
|
||
|
"""Draw all arrows."""
|
||
|
# Distance along line of each arrow: space them out evenly
|
||
|
startArrowPos = 0.0
|
||
|
endArrowPos = 0.0
|
||
|
middleArrowPos = 0.0
|
||
|
|
||
|
for arrow in self._arcArrows:
|
||
|
ah = arrow.GetArrowEnd()
|
||
|
if ah == ARROW_POSITION_START:
|
||
|
if arrow.GetXOffset() and not self._ignoreArrowOffsets:
|
||
|
# If specified, x offset is proportional to line length
|
||
|
self.DrawArrow(dc, arrow, arrow.GetXOffset(), True)
|
||
|
else:
|
||
|
self.DrawArrow(dc, arrow, startArrowPos, False)
|
||
|
startArrowPos += arrow.GetSize() + arrow.GetSpacing()
|
||
|
elif ah == ARROW_POSITION_END:
|
||
|
if arrow.GetXOffset() and not self._ignoreArrowOffsets:
|
||
|
self.DrawArrow(dc, arrow, arrow.GetXOffset(), True)
|
||
|
else:
|
||
|
self.DrawArrow(dc, arrow, endArrowPos, False)
|
||
|
endArrowPos += arrow.GetSize() + arrow.GetSpacing()
|
||
|
elif ah == ARROW_POSITION_MIDDLE:
|
||
|
arrow.SetXOffset(middleArrowPos)
|
||
|
if arrow.GetXOffset() and not self._ignoreArrowOffsets:
|
||
|
self.DrawArrow(dc, arrow, arrow.GetXOffset(), True)
|
||
|
else:
|
||
|
self.DrawArrow(dc, arrow, middleArrowPos, False)
|
||
|
middleArrowPos += arrow.GetSize() + arrow.GetSpacing()
|
||
|
|
||
|
def DrawArrow(self, dc, arrow, XOffset, proportionalOffset):
|
||
|
"""
|
||
|
Draw the given arrowhead (or annotation).
|
||
|
|
||
|
:param `dc`: an instance of :class:`wx.MemoryDC`
|
||
|
:param `arrow`: an instannce of :class:`ArrowHead`
|
||
|
:param `XOffset`: the x offset ???
|
||
|
:param `proportionalOffset`: ???
|
||
|
|
||
|
"""
|
||
|
first_line_point = self._lineControlPoints[0]
|
||
|
second_line_point = self._lineControlPoints[1]
|
||
|
|
||
|
last_line_point = self._lineControlPoints[-1]
|
||
|
second_last_line_point = self._lineControlPoints[-2]
|
||
|
|
||
|
# Position of start point of line, at the end of which we draw the arrow
|
||
|
startPositionX, startPositionY = 0.0, 0.0
|
||
|
|
||
|
ap = arrow.GetPosition()
|
||
|
if ap == ARROW_POSITION_START:
|
||
|
# If we're using a proportional offset, calculate just where this
|
||
|
# will be on the line.
|
||
|
realOffset = XOffset
|
||
|
if proportionalOffset:
|
||
|
totalLength = math.sqrt((second_line_point[0] - first_line_point[0]) * (second_line_point[0] - first_line_point[0]) + (second_line_point[1] - first_line_point[1]) * (second_line_point[1] - first_line_point[1]))
|
||
|
realOffset = XOffset * totalLength
|
||
|
|
||
|
positionOnLineX, positionOnLineY = GetPointOnLine(second_line_point[0], second_line_point[1], first_line_point[0], first_line_point[1], realOffset)
|
||
|
|
||
|
startPositionX = second_line_point[0]
|
||
|
startPositionY = second_line_point[1]
|
||
|
elif ap == ARROW_POSITION_END:
|
||
|
# If we're using a proportional offset, calculate just where this
|
||
|
# will be on the line.
|
||
|
realOffset = XOffset
|
||
|
if proportionalOffset:
|
||
|
totalLength = math.sqrt((second_last_line_point[0] - last_line_point[0]) * (second_last_line_point[0] - last_line_point[0]) + (second_last_line_point[1] - last_line_point[1]) * (second_last_line_point[1] - last_line_point[1]));
|
||
|
realOffset = XOffset * totalLength
|
||
|
|
||
|
positionOnLineX, positionOnLineY = GetPointOnLine(second_last_line_point[0], second_last_line_point[1], last_line_point[0], last_line_point[1], realOffset)
|
||
|
|
||
|
startPositionX = second_last_line_point[0]
|
||
|
startPositionY = second_last_line_point[1]
|
||
|
elif ap == ARROW_POSITION_MIDDLE:
|
||
|
# Choose a point half way between the last and penultimate points
|
||
|
x = (last_line_point[0] + second_last_line_point[0]) / 2.0
|
||
|
y = (last_line_point[1] + second_last_line_point[1]) / 2.0
|
||
|
|
||
|
# If we're using a proportional offset, calculate just where this
|
||
|
# will be on the line.
|
||
|
realOffset = XOffset
|
||
|
if proportionalOffset:
|
||
|
totalLength = math.sqrt((second_last_line_point[0] - x) * (second_last_line_point[0] - x) + (second_last_line_point[1] - y) * (second_last_line_point[1] - y));
|
||
|
realOffset = XOffset * totalLength
|
||
|
|
||
|
positionOnLineX, positionOnLineY = GetPointOnLine(second_last_line_point[0], second_last_line_point[1], x, y, realOffset)
|
||
|
startPositionX = second_last_line_point[0]
|
||
|
startPositionY = second_last_line_point[1]
|
||
|
|
||
|
# Add yOffset to arrow, if any
|
||
|
|
||
|
# The translation that the y offset may give
|
||
|
deltaX = 0.0
|
||
|
deltaY = 0.0
|
||
|
if arrow.GetYOffset and not self._ignoreArrowOffsets:
|
||
|
# |(x4, y4)
|
||
|
# |d
|
||
|
# |
|
||
|
# (x1, y1)--------------(x3, y3)------------------(x2, y2)
|
||
|
# x4 = x3 - d * math.sin(theta)
|
||
|
# y4 = y3 + d * math.cos(theta)
|
||
|
#
|
||
|
# Where theta = math.tan(-1) of (y3-y1) / (x3-x1)
|
||
|
x1 = startPositionX
|
||
|
y1 = startPositionY
|
||
|
x3 = float(positionOnLineX)
|
||
|
y3 = float(positionOnLineY)
|
||
|
d = -arrow.GetYOffset() # Negate so +offset is above line
|
||
|
|
||
|
if x3 == x1:
|
||
|
theta = math.pi / 2.0
|
||
|
else:
|
||
|
theta = math.atan((y3 - y1) / (x3 - x1))
|
||
|
|
||
|
x4 = x3 - d * math.sin(theta)
|
||
|
y4 = y3 + d * math.cos(theta)
|
||
|
|
||
|
deltaX = x4 - positionOnLineX
|
||
|
deltaY = y4 - positionOnLineY
|
||
|
|
||
|
at = arrow._GetType()
|
||
|
if at == ARROW_ARROW:
|
||
|
arrowLength = arrow.GetSize()
|
||
|
arrowWidth = arrowLength / 3.0
|
||
|
|
||
|
tip_x, tip_y, side1_x, side1_y, side2_x, side2_y = GetArrowPoints(startPositionX + deltaX, startPositionY + deltaY, positionOnLineX + deltaX, positionOnLineY + deltaY, arrowLength, arrowWidth)
|
||
|
|
||
|
points = [[tip_x, tip_y],
|
||
|
[side1_x, side1_y],
|
||
|
[side2_x, side2_y],
|
||
|
[tip_x, tip_y]]
|
||
|
|
||
|
dc.SetPen(self._pen)
|
||
|
dc.SetBrush(self._brush)
|
||
|
dc.DrawPolygon(points)
|
||
|
elif at in [ARROW_HOLLOW_CIRCLE, ARROW_FILLED_CIRCLE]:
|
||
|
# Find point on line of centre of circle, which is a radius away
|
||
|
# from the end position
|
||
|
diameter = arrow.GetSize()
|
||
|
x, y = GetPointOnLine(startPositionX + deltaX, startPositionY + deltaY,
|
||
|
positionOnLineX + deltaX, positionOnLineY + deltaY,
|
||
|
diameter / 2.0)
|
||
|
x1 = x - diameter / 2.0
|
||
|
y1 = y - diameter / 2.0
|
||
|
dc.SetPen(self._pen)
|
||
|
if arrow._GetType() == ARROW_HOLLOW_CIRCLE:
|
||
|
dc.SetBrush(self.GetBackgroundBrush())
|
||
|
else:
|
||
|
dc.SetBrush(self._brush)
|
||
|
|
||
|
dc.DrawEllipse(x1, y1, diameter, diameter)
|
||
|
elif at == ARROW_SINGLE_OBLIQUE:
|
||
|
pass
|
||
|
elif at == ARROW_METAFILE:
|
||
|
if arrow.GetMetaFile():
|
||
|
# Find point on line of centre of object, which is a half-width away
|
||
|
# from the end position
|
||
|
#
|
||
|
# width
|
||
|
# <-- start pos <-----><-- positionOnLineX
|
||
|
# _____
|
||
|
# --------------| x | <-- e.g. rectangular arrowhead
|
||
|
# -----
|
||
|
#
|
||
|
x, y = GetPointOnLine(startPositionX, startPositionY,
|
||
|
positionOnLineX, positionOnLineY,
|
||
|
arrow.GetMetaFile()._width / 2.0)
|
||
|
# Calculate theta for rotating the metafile.
|
||
|
#
|
||
|
# |
|
||
|
# | o(x2, y2) 'o' represents the arrowhead.
|
||
|
# | /
|
||
|
# | /
|
||
|
# | /theta
|
||
|
# | /(x1, y1)
|
||
|
# |______________________
|
||
|
#
|
||
|
theta = 0.0
|
||
|
x1 = startPositionX
|
||
|
y1 = startPositionY
|
||
|
x2 = float(positionOnLineX)
|
||
|
y2 = float(positionOnLineY)
|
||
|
|
||
|
if x1 == x2 and y1 == y2:
|
||
|
theta = 0.0
|
||
|
elif x1 == x2 and y1 > y2:
|
||
|
theta = 3.0 * math.pi / 2.0
|
||
|
elif x1 == x2 and y2 > y1:
|
||
|
theta = math.pi / 2.0
|
||
|
elif x2 > x1 and y2 >= y1:
|
||
|
theta = math.atan((y2 - y1) / (x2 - x1))
|
||
|
elif x2 < x1:
|
||
|
theta = math.pi + math.atan((y2 - y1) / (x2 - x1))
|
||
|
elif x2 > x1 and y2 < y1:
|
||
|
theta = 2 * math.pi + math.atan((y2 - y1) / (x2 - x1))
|
||
|
else:
|
||
|
raise "Unknown arrowhead rotation case"
|
||
|
|
||
|
# Rotate about the centre of the object, then place
|
||
|
# the object on the line.
|
||
|
if arrow.GetMetaFile().GetRotateable():
|
||
|
arrow.GetMetaFile().Rotate(0.0, 0.0, theta)
|
||
|
|
||
|
if self._erasing:
|
||
|
# If erasing, just draw a rectangle
|
||
|
minX, minY, maxX, maxY = arrow.GetMetaFile().GetBounds()
|
||
|
# Make erasing rectangle slightly bigger or you get droppings
|
||
|
extraPixels = 4
|
||
|
dc.DrawRectangle(deltaX + x + minX - extraPixels / 2.0, deltaY + y + minY - extraPixels / 2.0, maxX - minX + extraPixels, maxY - minY + extraPixels)
|
||
|
else:
|
||
|
arrow.GetMetaFile().Draw(dc, x + deltaX, y + deltaY)
|
||
|
|
||
|
def OnErase(self, dc):
|
||
|
"""The erase handler."""
|
||
|
old_pen = self._pen
|
||
|
old_brush = self._brush
|
||
|
|
||
|
bg_pen = self.GetBackgroundPen()
|
||
|
bg_brush = self.GetBackgroundBrush()
|
||
|
self.SetPen(bg_pen)
|
||
|
self.SetBrush(bg_brush)
|
||
|
|
||
|
bound_x, bound_y = self.GetBoundingBoxMax()
|
||
|
if self._font:
|
||
|
dc.SetFont(self._font)
|
||
|
|
||
|
# Undraw text regions
|
||
|
for i in range(3):
|
||
|
if self._regions[i]:
|
||
|
x, y = self.GetLabelPosition(i)
|
||
|
self.EraseRegion(dc, self._regions[i], x, y)
|
||
|
|
||
|
# Undraw line
|
||
|
dc.SetPen(self.GetBackgroundPen())
|
||
|
dc.SetBrush(self.GetBackgroundBrush())
|
||
|
|
||
|
# Drawing over the line only seems to work if the line has a thickness
|
||
|
# of 1.
|
||
|
if old_pen and old_pen.GetWidth() > 1:
|
||
|
dc.DrawRectangle(self._xpos - bound_x / 2.0 - 2, self._ypos - bound_y / 2.0 - 2,
|
||
|
bound_x + 4, bound_y + 4)
|
||
|
else:
|
||
|
self._erasing = True
|
||
|
self.GetEventHandler().OnDraw(dc)
|
||
|
self.GetEventHandler().OnEraseControlPoints(dc)
|
||
|
self._erasing = False
|
||
|
|
||
|
if old_pen:
|
||
|
self.SetPen(old_pen)
|
||
|
if old_brush:
|
||
|
self.SetBrush(old_brush)
|
||
|
|
||
|
def GetBoundingBoxMin(self):
|
||
|
"""Get the minimum bounding box."""
|
||
|
x1, y1 = 10000, 10000
|
||
|
x2, y2 = -10000, -10000
|
||
|
|
||
|
for point in self._lineControlPoints:
|
||
|
if point[0] < x1:
|
||
|
x1 = point[0]
|
||
|
if point[1] < y1:
|
||
|
y1 = point[1]
|
||
|
if point[0] > x2:
|
||
|
x2 = point[0]
|
||
|
if point[1] > y2:
|
||
|
y2 = point[1]
|
||
|
|
||
|
return x2 - x1, y2 - y1
|
||
|
|
||
|
# For a node image of interest, finds the position of this arc
|
||
|
# amongst all the arcs which are attached to THIS SIDE of the node image,
|
||
|
# and the number of same.
|
||
|
def FindNth(self, image, incoming):
|
||
|
"""
|
||
|
Find the position of the line on the given object.
|
||
|
|
||
|
Specify whether incoming or outgoing lines are being considered
|
||
|
with incoming.
|
||
|
|
||
|
:param `image`: a node image
|
||
|
:param `incoming`: True to get incoming lines ???
|
||
|
|
||
|
:returns: nth line, number of lines ???
|
||
|
|
||
|
"""
|
||
|
n = -1
|
||
|
num = 0
|
||
|
|
||
|
if image == self._to:
|
||
|
this_attachment = self._attachmentTo
|
||
|
else:
|
||
|
this_attachment = self._attachmentFrom
|
||
|
|
||
|
# Find number of lines going into / out of this particular attachment point
|
||
|
for line in image.GetLines():
|
||
|
if line._from == image:
|
||
|
# This is the nth line attached to 'image'
|
||
|
if line == self and not incoming:
|
||
|
n = num
|
||
|
|
||
|
# Increment num count if this is the same side (attachment number)
|
||
|
if line._attachmentFrom == this_attachment:
|
||
|
num += 1
|
||
|
|
||
|
if line._to == image:
|
||
|
# This is the nth line attached to 'image'
|
||
|
if line == self and incoming:
|
||
|
n = num
|
||
|
|
||
|
# Increment num count if this is the same side (attachment number)
|
||
|
if line._attachmentTo == this_attachment:
|
||
|
num += 1
|
||
|
|
||
|
return n, num
|
||
|
|
||
|
def OnDrawOutline(self, dc, x, y, w, h):
|
||
|
"""The draw outline handler."""
|
||
|
old_pen = self._pen
|
||
|
old_brush = self._brush
|
||
|
|
||
|
dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.PENSTYLE_DOT)
|
||
|
self.SetPen(dottedPen)
|
||
|
self.SetBrush(wx.TRANSPARENT_BRUSH)
|
||
|
|
||
|
self.GetEventHandler().OnDraw(dc)
|
||
|
|
||
|
if old_pen:
|
||
|
self.SetPen(old_pen)
|
||
|
else:
|
||
|
self.SetPen(None)
|
||
|
if old_brush:
|
||
|
self.SetBrush(old_brush)
|
||
|
else:
|
||
|
self.SetBrush(None)
|
||
|
|
||
|
def OnMovePre(self, dc, x, y, old_x, old_y, display = True):
|
||
|
"""The move 'pre' handler. ???"""
|
||
|
x_offset = x - old_x
|
||
|
y_offset = y - old_y
|
||
|
|
||
|
if self._lineControlPoints and not (x_offset == 0 and y_offset == 0):
|
||
|
for point in self._lineControlPoints:
|
||
|
point[0] += x_offset
|
||
|
point[1] += y_offset
|
||
|
|
||
|
# Move temporary label rectangles if necessary
|
||
|
for i in range(3):
|
||
|
if self._labelObjects[i]:
|
||
|
self._labelObjects[i].Erase(dc)
|
||
|
xp, yp = self.GetLabelPosition(i)
|
||
|
if i < len(self._regions):
|
||
|
xr, yr = self._regions[i].GetPosition()
|
||
|
else:
|
||
|
xr, yr = 0, 0
|
||
|
self._labelObjects[i].Move(dc, xp + xr, yp + yr)
|
||
|
return True
|
||
|
|
||
|
def OnMoveLink(self, dc, moveControlPoints = True):
|
||
|
"""The move linke handler, called when a connected object has moved, to move the link to
|
||
|
correct position.
|
||
|
"""
|
||
|
if not self._from or not self._to:
|
||
|
return
|
||
|
|
||
|
# Do each end - nothing in the middle. User has to move other points
|
||
|
# manually if necessary
|
||
|
end_x, end_y, other_end_x, other_end_y = self.FindLineEndPoints()
|
||
|
|
||
|
oldX, oldY = self._xpos, self._ypos
|
||
|
|
||
|
# pi: The first time we go through FindLineEndPoints we can't
|
||
|
# use the middle points (since they don't have sane values),
|
||
|
# so we just do what we do for a normal line. Then we call
|
||
|
# Initialise to set the middle points, and then FindLineEndPoints
|
||
|
# again, but this time (and from now on) we use the middle
|
||
|
# points to calculate the end points.
|
||
|
# This was buggy in the C++ version too.
|
||
|
|
||
|
self.SetEnds(end_x, end_y, other_end_x, other_end_y)
|
||
|
|
||
|
if len(self._lineControlPoints) > 2:
|
||
|
self.Initialise()
|
||
|
|
||
|
# Do a second time, because one may depend on the other
|
||
|
end_x, end_y, other_end_x, other_end_y = self.FindLineEndPoints()
|
||
|
self.SetEnds(end_x, end_y, other_end_x, other_end_y)
|
||
|
|
||
|
# Try to move control points with the arc
|
||
|
x_offset = self._xpos - oldX
|
||
|
y_offset = self._ypos - oldY
|
||
|
|
||
|
# Only move control points if it's a self link. And only works
|
||
|
# if attachment mode is ON
|
||
|
if self._from == self._to and self._from.GetAttachmentMode() != ATTACHMENT_MODE_NONE and moveControlPoints and self._lineControlPoints and not (x_offset == 0 and y_offset == 0):
|
||
|
for point in self._lineControlPoints[1:-1]:
|
||
|
point[0] += x_offset
|
||
|
point[1] += y_offset
|
||
|
|
||
|
self.Move(dc, self._xpos, self._ypos)
|
||
|
|
||
|
def FindLineEndPoints(self):
|
||
|
"""
|
||
|
Finds the x, y points at the two ends of the line.
|
||
|
|
||
|
This function can be used by e.g. line-routing routines to
|
||
|
get the actual points on the two node images where the lines will be
|
||
|
drawn to / from.
|
||
|
"""
|
||
|
if not self._from or not self._to:
|
||
|
return
|
||
|
|
||
|
# Do each end - nothing in the middle. User has to move other points
|
||
|
# manually if necessary.
|
||
|
second_point = self._lineControlPoints[1]
|
||
|
second_last_point = self._lineControlPoints[-2]
|
||
|
|
||
|
# pi: If we have a segmented line and this is the first time,
|
||
|
# do this as a straight line.
|
||
|
if len(self._lineControlPoints) > 2 and self._initialised:
|
||
|
if self._from.GetAttachmentMode() != ATTACHMENT_MODE_NONE:
|
||
|
nth, no_arcs = self.FindNth(self._from, False) # Not incoming
|
||
|
end_x, end_y = self._from.GetAttachmentPosition(self._attachmentFrom, nth, no_arcs, self)
|
||
|
else:
|
||
|
end_x, end_y = self._from.GetPerimeterPoint(self._from.GetX(), self._from.GetY(), second_point[0], second_point[1])
|
||
|
|
||
|
if self._to.GetAttachmentMode() != ATTACHMENT_MODE_NONE:
|
||
|
nth, no_arch = self.FindNth(self._to, True) # Incoming
|
||
|
other_end_x, other_end_y = self._to.GetAttachmentPosition(self._attachmentTo, nth, no_arch, self)
|
||
|
else:
|
||
|
other_end_x, other_end_y = self._to.GetPerimeterPoint(self._to.GetX(), self._to.GetY(), second_last_point[0], second_last_point[1])
|
||
|
else:
|
||
|
fromX = self._from.GetX()
|
||
|
fromY = self._from.GetY()
|
||
|
toX = self._to.GetX()
|
||
|
toY = self._to.GetY()
|
||
|
|
||
|
if self._from.GetAttachmentMode() != ATTACHMENT_MODE_NONE:
|
||
|
nth, no_arcs = self.FindNth(self._from, False)
|
||
|
end_x, end_y = self._from.GetAttachmentPosition(self._attachmentFrom, nth, no_arcs, self)
|
||
|
fromX = end_x
|
||
|
fromY = end_y
|
||
|
|
||
|
if self._to.GetAttachmentMode() != ATTACHMENT_MODE_NONE:
|
||
|
nth, no_arcs = self.FindNth(self._to, True)
|
||
|
other_end_x, other_end_y = self._to.GetAttachmentPosition(self._attachmentTo, nth, no_arcs, self)
|
||
|
toX = other_end_x
|
||
|
toY = other_end_y
|
||
|
|
||
|
if self._from.GetAttachmentMode() == ATTACHMENT_MODE_NONE:
|
||
|
end_x, end_y = self._from.GetPerimeterPoint(self._from.GetX(), self._from.GetY(), toX, toY)
|
||
|
|
||
|
if self._to.GetAttachmentMode() == ATTACHMENT_MODE_NONE:
|
||
|
other_end_x, other_end_y = self._to.GetPerimeterPoint(self._to.GetX(), self._to.GetY(), fromX, fromY)
|
||
|
|
||
|
return end_x, end_y, other_end_x, other_end_y
|
||
|
|
||
|
def OnDraw(self, dc):
|
||
|
"""The draw handler."""
|
||
|
if not self._lineControlPoints:
|
||
|
return
|
||
|
|
||
|
if self._pen:
|
||
|
dc.SetPen(self._pen)
|
||
|
if self._brush:
|
||
|
dc.SetBrush(self._brush)
|
||
|
|
||
|
points = []
|
||
|
for point in self._lineControlPoints:
|
||
|
points.append(wx.Point(point[0], point[1]))
|
||
|
|
||
|
if self._isSpline:
|
||
|
dc.DrawSpline(points)
|
||
|
else:
|
||
|
dc.DrawLines(points)
|
||
|
|
||
|
if sys.platform[:3] == "win":
|
||
|
# For some reason, last point isn't drawn under Windows
|
||
|
pt = points[-1]
|
||
|
dc.DrawPoint(pt[0], pt[1])
|
||
|
|
||
|
# Problem with pen - if not a solid pen, does strange things
|
||
|
# to the arrowhead. So make (get) a new pen that's solid.
|
||
|
if self._pen and self._pen.GetStyle() != wx.PENSTYLE_SOLID:
|
||
|
solid_pen = wx.Pen(self._pen.GetColour(), 1, wx.PENSTYLE_SOLID)
|
||
|
if solid_pen:
|
||
|
dc.SetPen(solid_pen)
|
||
|
|
||
|
self.DrawArrows(dc)
|
||
|
|
||
|
def OnDrawControlPoints(self, dc):
|
||
|
"""The draw control points handler."""
|
||
|
if not self._drawHandles:
|
||
|
return
|
||
|
|
||
|
# Draw temporary label rectangles if necessary
|
||
|
for i in range(3):
|
||
|
if self._labelObjects[i]:
|
||
|
self._labelObjects[i].Draw(dc)
|
||
|
|
||
|
Shape.OnDrawControlPoints(self, dc)
|
||
|
|
||
|
def OnEraseControlPoints(self, dc):
|
||
|
"""The erase control points handler."""
|
||
|
# Erase temporary label rectangles if necessary
|
||
|
|
||
|
for i in range(3):
|
||
|
if self._labelObjects[i]:
|
||
|
self._labelObjects[i].Erase(dc)
|
||
|
|
||
|
Shape.OnEraseControlPoints(self, dc)
|
||
|
|
||
|
def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
|
||
|
"""not implemented???"""
|
||
|
pass
|
||
|
|
||
|
def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
|
||
|
"""not implemented???"""
|
||
|
pass
|
||
|
|
||
|
def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
|
||
|
"""not implemented???"""
|
||
|
pass
|
||
|
|
||
|
def OnDrawContents(self, dc):
|
||
|
"""The draw contents handler."""
|
||
|
if self.GetDisableLabel():
|
||
|
return
|
||
|
|
||
|
for i in range(3):
|
||
|
if self._regions[i]:
|
||
|
x, y = self.GetLabelPosition(i)
|
||
|
self.DrawRegion(dc, self._regions[i], x, y)
|
||
|
|
||
|
def SetTo(self, object):
|
||
|
"""Set the 'to' object for the line."""
|
||
|
self._to = object
|
||
|
|
||
|
def SetFrom(self, object):
|
||
|
"""Set the 'from' object for the line."""
|
||
|
self._from = object
|
||
|
|
||
|
def MakeControlPoints(self):
|
||
|
"""Make handle control points."""
|
||
|
if self._canvas and self._lineControlPoints:
|
||
|
first = self._lineControlPoints[0]
|
||
|
last = self._lineControlPoints[-1]
|
||
|
|
||
|
control = LineControlPoint(self._canvas, self, CONTROL_POINT_SIZE, first[0], first[1], CONTROL_POINT_ENDPOINT_FROM)
|
||
|
control._point = first
|
||
|
self._canvas.AddShape(control)
|
||
|
self._controlPoints.append(control)
|
||
|
|
||
|
for point in self._lineControlPoints[1:-1]:
|
||
|
control = LineControlPoint(self._canvas, self, CONTROL_POINT_SIZE, point[0], point[1], CONTROL_POINT_LINE)
|
||
|
control._point = point
|
||
|
self._canvas.AddShape(control)
|
||
|
self._controlPoints.append(control)
|
||
|
|
||
|
control = LineControlPoint(self._canvas, self, CONTROL_POINT_SIZE, last[0], last[1], CONTROL_POINT_ENDPOINT_TO)
|
||
|
control._point = last
|
||
|
self._canvas.AddShape(control)
|
||
|
self._controlPoints.append(control)
|
||
|
|
||
|
def ResetControlPoints(self):
|
||
|
"""Reset the control points."""
|
||
|
if self._canvas and self._lineControlPoints and self._controlPoints:
|
||
|
for i in range(min(len(self._controlPoints), len(self._lineControlPoints))):
|
||
|
point = self._lineControlPoints[i]
|
||
|
control = self._controlPoints[i]
|
||
|
control.SetX(point[0])
|
||
|
control.SetY(point[1])
|
||
|
|
||
|
def Select(self, select, dc = None):
|
||
|
"""Overridden select, to create / delete temporary label-moving objects."""
|
||
|
Shape.Select(self, select, dc)
|
||
|
if select:
|
||
|
for i in range(3):
|
||
|
if self._regions[i]:
|
||
|
region = self._regions[i]
|
||
|
if region._formattedText:
|
||
|
w, h = region.GetSize()
|
||
|
x, y = region.GetPosition()
|
||
|
xx, yy = self.GetLabelPosition(i)
|
||
|
|
||
|
if self._labelObjects[i]:
|
||
|
self._labelObjects[i].Select(False)
|
||
|
self._labelObjects[i].RemoveFromCanvas(self._canvas)
|
||
|
|
||
|
self._labelObjects[i] = self.OnCreateLabelShape(self, region, w, h)
|
||
|
self._labelObjects[i].AddToCanvas(self._canvas)
|
||
|
self._labelObjects[i].Show(True)
|
||
|
if dc:
|
||
|
self._labelObjects[i].Move(dc, x + xx, y + yy)
|
||
|
self._labelObjects[i].Select(True, dc)
|
||
|
else:
|
||
|
for i in range(3):
|
||
|
if self._labelObjects[i]:
|
||
|
self._labelObjects[i].Select(False, dc)
|
||
|
self._labelObjects[i].Erase(dc)
|
||
|
self._labelObjects[i].RemoveFromCanvas(self._canvas)
|
||
|
self._labelObjects[i] = None
|
||
|
|
||
|
# 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)
|
||
|
|
||
|
if pt._type == CONTROL_POINT_LINE:
|
||
|
x, y = self._canvas.Snap(x, y)
|
||
|
|
||
|
pt.SetX(x)
|
||
|
pt.SetY(y)
|
||
|
pt._point[0] = x
|
||
|
pt._point[1] = y
|
||
|
|
||
|
old_pen = self.GetPen()
|
||
|
old_brush = self.GetBrush()
|
||
|
|
||
|
self.SetPen(dottedPen)
|
||
|
self.SetBrush(wx.TRANSPARENT_BRUSH)
|
||
|
|
||
|
self.GetEventHandler().OnMoveLink(dc, False)
|
||
|
|
||
|
self.SetPen(old_pen)
|
||
|
self.SetBrush(old_brush)
|
||
|
|
||
|
def OnSizingBeginDragLeft(self, pt, x, y, keys = 0, attachment = 0):
|
||
|
"""The sizing begin drag left handler."""
|
||
|
dc = wx.MemoryDC()
|
||
|
dc.SelectObject(self.GetCanvas()._Buffer)
|
||
|
|
||
|
if pt._type == CONTROL_POINT_LINE:
|
||
|
pt._originalPos = pt._point
|
||
|
x, y = self._canvas.Snap(x, y)
|
||
|
|
||
|
self.Erase(dc)
|
||
|
|
||
|
# Redraw start and end objects because we've left holes
|
||
|
# when erasing the line
|
||
|
self.GetFrom().OnDraw(dc)
|
||
|
self.GetFrom().OnDrawContents(dc)
|
||
|
self.GetTo().OnDraw(dc)
|
||
|
self.GetTo().OnDrawContents(dc)
|
||
|
|
||
|
self.SetDisableLabel(True)
|
||
|
dc.SetLogicalFunction(OGLRBLF)
|
||
|
|
||
|
pt._xpos = x
|
||
|
pt._ypos = y
|
||
|
pt._point[0] = x
|
||
|
pt._point[1] = y
|
||
|
|
||
|
old_pen = self.GetPen()
|
||
|
old_brush = self.GetBrush()
|
||
|
|
||
|
dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.PENSTYLE_DOT)
|
||
|
self.SetPen(dottedPen)
|
||
|
self.SetBrush(wx.TRANSPARENT_BRUSH)
|
||
|
|
||
|
self.GetEventHandler().OnMoveLink(dc, False)
|
||
|
|
||
|
self.SetPen(old_pen)
|
||
|
self.SetBrush(old_brush)
|
||
|
|
||
|
if pt._type == CONTROL_POINT_ENDPOINT_FROM or pt._type == CONTROL_POINT_ENDPOINT_TO:
|
||
|
self._canvas.SetCursor(wx.Cursor(wx.CURSOR_BULLSEYE))
|
||
|
pt._oldCursor = wx.STANDARD_CURSOR
|
||
|
|
||
|
def OnSizingEndDragLeft(self, pt, x, y, keys = 0, attachment = 0):
|
||
|
"""The sizing end drag left handler."""
|
||
|
dc = wx.MemoryDC()
|
||
|
dc.SelectObject(self.GetCanvas()._Buffer)
|
||
|
|
||
|
self.SetDisableLabel(False)
|
||
|
|
||
|
if pt._type == CONTROL_POINT_LINE:
|
||
|
x, y = self._canvas.Snap(x, y)
|
||
|
|
||
|
rpt = wx.RealPoint(x, y)
|
||
|
|
||
|
# Move the control point back to where it was;
|
||
|
# MoveControlPoint will move it to the new position
|
||
|
# if it decides it wants. We only moved the position
|
||
|
# during user feedback so we could redraw the line
|
||
|
# as it changed shape.
|
||
|
pt._xpos = pt._originalPos[0]
|
||
|
pt._ypos = pt._originalPos[1]
|
||
|
pt._point[0] = pt._originalPos[0]
|
||
|
pt._point[1] = pt._originalPos[1]
|
||
|
|
||
|
self.OnMoveMiddleControlPoint(dc, pt, rpt)
|
||
|
|
||
|
if pt._type == CONTROL_POINT_ENDPOINT_FROM:
|
||
|
if pt._oldCursor:
|
||
|
self._canvas.SetCursor(pt._oldCursor)
|
||
|
|
||
|
if self.GetFrom():
|
||
|
self.GetFrom().MoveLineToNewAttachment(dc, self, x, y)
|
||
|
|
||
|
if pt._type == CONTROL_POINT_ENDPOINT_TO:
|
||
|
if pt._oldCursor:
|
||
|
self._canvas.SetCursor(pt._oldCursor)
|
||
|
|
||
|
if self.GetTo():
|
||
|
self.GetTo().MoveLineToNewAttachment(dc, self, x, y)
|
||
|
|
||
|
# This is called only when a non-end control point is moved
|
||
|
def OnMoveMiddleControlPoint(self, dc, lpt, pt):
|
||
|
"""The move middle control point handler."""
|
||
|
lpt._xpos = pt[0]
|
||
|
lpt._ypos = pt[1]
|
||
|
|
||
|
lpt._point[0] = pt[0]
|
||
|
lpt._point[1] = pt[1]
|
||
|
|
||
|
self.GetEventHandler().OnMoveLink(dc)
|
||
|
|
||
|
return True
|
||
|
|
||
|
def AddArrow(self, type, end = ARROW_POSITION_END, size = 10.0, xOffset = 0.0, name = "", mf = None, arrowId = -1):
|
||
|
"""
|
||
|
Add an arrow (or annotation) to the line.
|
||
|
|
||
|
:param `type`: an arrow head type, one of the following
|
||
|
|
||
|
======================================== ==================================
|
||
|
Arrow head type Description
|
||
|
======================================== ==================================
|
||
|
`ARROW_HOLLOW_CIRCLE` a hollow circle
|
||
|
`ARROW_FILLED_CIRCLE` a filled circle
|
||
|
`ARROW_ARROW` an arrow
|
||
|
`ARROW_SINGLE_OBLIQUE` a single oblique
|
||
|
`ARROW_DOUBLE_OBLIQUE` a double oblique
|
||
|
`ARROW_METAFILE` custom, define in metafile
|
||
|
======================================== ==================================
|
||
|
|
||
|
:param `end`: may be one of the following
|
||
|
|
||
|
======================================== ==================================
|
||
|
Arrow head type Description
|
||
|
======================================== ==================================
|
||
|
`ARROW_POSITION_END` arrow appears at the end
|
||
|
`ARROW_POSITION_START` arrow appears at the start
|
||
|
======================================== ==================================
|
||
|
|
||
|
:param `size`: specifies the lenght of the arrow
|
||
|
:param `xOffset`: specifies the offset from the end of the line
|
||
|
:param `name`: specifies a name
|
||
|
:param `mf`: mf can be a wxPseduoMetaFile, perhaps loaded from a simple Windows
|
||
|
metafile.
|
||
|
:param `arrowId`: the id for the arrow
|
||
|
|
||
|
"""
|
||
|
arrow = ArrowHead(type, end, size, xOffset, name, mf, arrowId)
|
||
|
self._arcArrows.append(arrow)
|
||
|
return arrow
|
||
|
|
||
|
# Add arrowhead at a particular position in the arrowhead list
|
||
|
def AddArrowOrdered(self, arrow, referenceList, end):
|
||
|
"""
|
||
|
Add an arrowhead in the position indicated by the reference list
|
||
|
of arrowheads, which contains all legal arrowheads for this line, in
|
||
|
the correct order. E.g.
|
||
|
|
||
|
Reference list: a b c d e
|
||
|
Current line list: a d
|
||
|
|
||
|
Add c, then line list is: a c d.
|
||
|
|
||
|
If no legal arrowhead position, return FALSE. Assume reference list
|
||
|
is for one end only, since it potentially defines the ordering for
|
||
|
any one of the 3 positions. So we don't check the reference list for
|
||
|
arrowhead position.
|
||
|
|
||
|
:param `arrow`: an instance of :class:`ArrowHead`
|
||
|
:param `referenceList`: ???
|
||
|
:param `end`: ???
|
||
|
|
||
|
"""
|
||
|
if not referenceList:
|
||
|
return False
|
||
|
|
||
|
targetName = arrow.GetName()
|
||
|
|
||
|
# First check whether we need to insert in front of list,
|
||
|
# because this arrowhead is the first in the reference
|
||
|
# list and should therefore be first in the current list.
|
||
|
refArrow = referenceList[0]
|
||
|
if refArrow.GetName() == targetName:
|
||
|
self._arcArrows.insert(0, arrow)
|
||
|
return True
|
||
|
|
||
|
i1 = i2 = 0
|
||
|
while i1 < len(referenceList) and i2 < len(self._arcArrows):
|
||
|
refArrow = referenceList[i1]
|
||
|
currArrow = self._arcArrows[i2]
|
||
|
|
||
|
# Matching: advance current arrow pointer
|
||
|
if currArrow.GetArrowEnd() == end and currArrow.GetName() == refArrow.GetName():
|
||
|
i2 += 1
|
||
|
|
||
|
# Check if we're at the correct position in the
|
||
|
# reference list
|
||
|
if targetName == refArrow.GetName():
|
||
|
if i2 < len(self._arcArrows):
|
||
|
self._arcArrows.insert(i2, arrow)
|
||
|
else:
|
||
|
self._arcArrows.append(arrow)
|
||
|
return True
|
||
|
i1 += 1
|
||
|
|
||
|
self._arcArrows.append(arrow)
|
||
|
return True
|
||
|
|
||
|
def ClearArrowsAtPosition(self, end):
|
||
|
"""
|
||
|
Delete the arrows at the specified position, or at any position if position is -1.
|
||
|
|
||
|
:param `end`: position to clear arrow from
|
||
|
|
||
|
"""
|
||
|
if end == -1:
|
||
|
self._arcArrows = []
|
||
|
return
|
||
|
|
||
|
for arrow in self._arcArrows:
|
||
|
if arrow.GetArrowEnd() == end:
|
||
|
self._arcArrows.remove(arrow)
|
||
|
|
||
|
def ClearArrow(self, name):
|
||
|
"""
|
||
|
Delete the arrow with the given name.
|
||
|
|
||
|
:param `name`: name of arrow to delete
|
||
|
|
||
|
"""
|
||
|
for arrow in self._arcArrows:
|
||
|
if arrow.GetName() == name:
|
||
|
self._arcArrows.remove(arrow)
|
||
|
return True
|
||
|
return False
|
||
|
|
||
|
def FindArrowHead(self, position, name):
|
||
|
"""
|
||
|
Find arrowhead by position and name.
|
||
|
|
||
|
if position is -1, matches any position.
|
||
|
|
||
|
:param `position`: position of arrow to find or -1
|
||
|
:param `name`: name of arrow to find
|
||
|
|
||
|
"""
|
||
|
for arrow in self._arcArrows:
|
||
|
if (position == -1 or position == arrow.GetArrowEnd()) and arrow.GetName() == name:
|
||
|
return arrow
|
||
|
|
||
|
return None
|
||
|
|
||
|
def FindArrowHeadId(self, arrowId):
|
||
|
"""
|
||
|
Find arrowhead by id.
|
||
|
|
||
|
:param `arrowId`: id of arrow to find
|
||
|
|
||
|
"""
|
||
|
for arrow in self._arcArrows:
|
||
|
if arrowId == arrow.GetId():
|
||
|
return arrow
|
||
|
|
||
|
return None
|
||
|
|
||
|
def DeleteArrowHead(self, position, name):
|
||
|
"""
|
||
|
Delete arrowhead by position and name.
|
||
|
|
||
|
if position is -1, matches any position.
|
||
|
|
||
|
:param `position`: position of arrow to delete or -1
|
||
|
:param `name`: name of arrow to delete
|
||
|
|
||
|
"""
|
||
|
for arrow in self._arcArrows:
|
||
|
if (position == -1 or position == arrow.GetArrowEnd()) and arrow.GetName() == name:
|
||
|
self._arcArrows.remove(arrow)
|
||
|
return True
|
||
|
return False
|
||
|
|
||
|
def DeleteArrowHeadId(self, arrowId):
|
||
|
"""
|
||
|
Delete arrowhead by id.
|
||
|
|
||
|
:param `arrowId`: id of arrow to delete
|
||
|
|
||
|
"""
|
||
|
for arrow in self._arcArrows:
|
||
|
if arrowId == arrow.GetId():
|
||
|
self._arcArrows.remove(arrow)
|
||
|
return True
|
||
|
return False
|
||
|
|
||
|
# Calculate the minimum width a line
|
||
|
# occupies, for the purposes of drawing lines in tools.
|
||
|
def FindMinimumWidth(self):
|
||
|
"""
|
||
|
Find the horizontal width for drawing a line with arrows in
|
||
|
minimum space. Assume arrows at end only.
|
||
|
"""
|
||
|
minWidth = 0.0
|
||
|
for arrowHead in self._arcArrows:
|
||
|
minWidth += arrowHead.GetSize()
|
||
|
if arrowHead != self._arcArrows[-1]:
|
||
|
minWidth += arrowHead + arrowHead.GetSpacing()
|
||
|
|
||
|
# We have ABSOLUTE minimum now. So
|
||
|
# scale it to give it reasonable aesthetics
|
||
|
# when drawing with line.
|
||
|
if minWidth > 0:
|
||
|
minWidth = minWidth * 1.4
|
||
|
else:
|
||
|
minWidth = 20.0
|
||
|
|
||
|
self.SetEnds(0.0, 0.0, minWidth, 0.0)
|
||
|
self.Initialise()
|
||
|
|
||
|
return minWidth
|
||
|
|
||
|
def FindLinePosition(self, x, y):
|
||
|
"""
|
||
|
Find which position we're talking about.
|
||
|
|
||
|
:param `x`: x position
|
||
|
:param `y`: y position
|
||
|
|
||
|
:returns: ARROW_POSITION_START or ARROW_POSITION_MIDDLE or ARROW_POSITION_END.
|
||
|
"""
|
||
|
startX, startY, endX, endY = self.GetEnds()
|
||
|
|
||
|
# Find distances from centre, start and end. The smallest wins
|
||
|
centreDistance = math.sqrt((x - self._xpos) * (x - self._xpos) + (y - self._ypos) * (y - self._ypos))
|
||
|
startDistance = math.sqrt((x - startX) * (x - startX) + (y - startY) * (y - startY))
|
||
|
endDistance = math.sqrt((x - endX) * (x - endX) + (y - endY) * (y - endY))
|
||
|
|
||
|
if centreDistance < startDistance and centreDistance < endDistance:
|
||
|
return ARROW_POSITION_MIDDLE
|
||
|
elif startDistance < endDistance:
|
||
|
return ARROW_POSITION_START
|
||
|
else:
|
||
|
return ARROW_POSITION_END
|
||
|
|
||
|
def SetAlignmentOrientation(self, isEnd, isHoriz):
|
||
|
"""
|
||
|
Set the alignment orientation.
|
||
|
|
||
|
:param `isEnd`: True or False ???
|
||
|
:param `isHoriz`: True of False ???
|
||
|
|
||
|
"""
|
||
|
if isEnd:
|
||
|
if isHoriz and self._alignmentEnd & LINE_ALIGNMENT_HORIZ != LINE_ALIGNMENT_HORIZ:
|
||
|
self._alignmentEnd != LINE_ALIGNMENT_HORIZ
|
||
|
elif not isHoriz and self._alignmentEnd & LINE_ALIGNMENT_HORIZ == LINE_ALIGNMENT_HORIZ:
|
||
|
self._alignmentEnd -= LINE_ALIGNMENT_HORIZ
|
||
|
else:
|
||
|
if isHoriz and self._alignmentStart & LINE_ALIGNMENT_HORIZ != LINE_ALIGNMENT_HORIZ:
|
||
|
self._alignmentStart != LINE_ALIGNMENT_HORIZ
|
||
|
elif not isHoriz and self._alignmentStart & LINE_ALIGNMENT_HORIZ == LINE_ALIGNMENT_HORIZ:
|
||
|
self._alignmentStart -= LINE_ALIGNMENT_HORIZ
|
||
|
|
||
|
def SetAlignmentType(self, isEnd, alignType):
|
||
|
"""
|
||
|
Set the alignment type.
|
||
|
|
||
|
:param `isEnd`: if True set the type for the begin, else for the end ???
|
||
|
:param `alignType`: one of the following
|
||
|
|
||
|
======================================== ==================================
|
||
|
Arrow head type Description
|
||
|
======================================== ==================================
|
||
|
`LINE_ALIGNMENT_HORIZ` Align horizontally
|
||
|
`LINE_ALIGNMENT_VERT` Align vertically
|
||
|
`LINE_ALIGNMENT_TO_NEXT_HANDLE` Align to next handle
|
||
|
`LINE_ALIGNMENT_NONE` vertical by default
|
||
|
======================================== ==================================
|
||
|
|
||
|
"""
|
||
|
if isEnd:
|
||
|
if alignType == LINE_ALIGNMENT_TO_NEXT_HANDLE:
|
||
|
if self._alignmentEnd & LINE_ALIGNMENT_TO_NEXT_HANDLE != LINE_ALIGNMENT_TO_NEXT_HANDLE:
|
||
|
self._alignmentEnd |= LINE_ALIGNMENT_TO_NEXT_HANDLE
|
||
|
elif self._alignmentEnd & LINE_ALIGNMENT_TO_NEXT_HANDLE == LINE_ALIGNMENT_TO_NEXT_HANDLE:
|
||
|
self._alignmentEnd -= LINE_ALIGNMENT_TO_NEXT_HANDLE
|
||
|
else:
|
||
|
if alignType == LINE_ALIGNMENT_TO_NEXT_HANDLE:
|
||
|
if self._alignmentStart & LINE_ALIGNMENT_TO_NEXT_HANDLE != LINE_ALIGNMENT_TO_NEXT_HANDLE:
|
||
|
self._alignmentStart |= LINE_ALIGNMENT_TO_NEXT_HANDLE
|
||
|
elif self._alignmentStart & LINE_ALIGNMENT_TO_NEXT_HANDLE == LINE_ALIGNMENT_TO_NEXT_HANDLE:
|
||
|
self._alignmentStart -= LINE_ALIGNMENT_TO_NEXT_HANDLE
|
||
|
|
||
|
def GetAlignmentOrientation(self, isEnd):
|
||
|
"""
|
||
|
Get the alignment orientation.
|
||
|
|
||
|
:param `isEnd`: if True get the type for the begin, else for the end ???
|
||
|
|
||
|
"""
|
||
|
if isEnd:
|
||
|
return self._alignmentEnd & LINE_ALIGNMENT_HORIZ == LINE_ALIGNMENT_HORIZ
|
||
|
else:
|
||
|
return self._alignmentStart & LINE_ALIGNMENT_HORIZ == LINE_ALIGNMENT_HORIZ
|
||
|
|
||
|
def GetAlignmentType(self, isEnd):
|
||
|
"""
|
||
|
Get the alignment type.
|
||
|
|
||
|
:param `isEnd`: if True get the type for the begin, else for the end ???
|
||
|
|
||
|
"""
|
||
|
if isEnd:
|
||
|
return self._alignmentEnd & LINE_ALIGNMENT_TO_NEXT_HANDLE
|
||
|
else:
|
||
|
return self._alignmentStart & LINE_ALIGNMENT_TO_NEXT_HANDLE
|
||
|
|
||
|
def GetNextControlPoint(self, shape):
|
||
|
"""Find the next control point in the line after the start / end point,
|
||
|
depending on whether the shape is at the start or end.
|
||
|
|
||
|
:param `shape`: an instance of :class:`~lib.ogl.Shape` ???
|
||
|
"""
|
||
|
n = len(self._lineControlPoints)
|
||
|
if self._to == shape:
|
||
|
# Must be END of line, so we want (n - 1)th control point.
|
||
|
# But indexing ends at n-1, so subtract 2.
|
||
|
nn = n - 2
|
||
|
else:
|
||
|
nn = 1
|
||
|
if nn < len(self._lineControlPoints):
|
||
|
return self._lineControlPoints[nn]
|
||
|
return None
|
||
|
|
||
|
def OnCreateLabelShape(self, parent, region, w, h):
|
||
|
"""Create label shape handler."""
|
||
|
return LabelShape(parent, region, w, h)
|
||
|
|
||
|
|
||
|
def OnLabelMovePre(self, dc, labelShape, x, y, old_x, old_y, display):
|
||
|
"""Label move 'pre' handler. ???"""
|
||
|
labelShape._shapeRegion.SetSize(labelShape.GetWidth(), labelShape.GetHeight())
|
||
|
|
||
|
# Find position in line's region list
|
||
|
i = self._regions.index(labelShape._shapeRegion)
|
||
|
|
||
|
xx, yy = self.GetLabelPosition(i)
|
||
|
# Set the region's offset, relative to the default position for
|
||
|
# each region.
|
||
|
labelShape._shapeRegion.SetPosition(x - xx, y - yy)
|
||
|
labelShape.SetX(x)
|
||
|
labelShape.SetY(y)
|
||
|
|
||
|
# Need to reformat to fit region
|
||
|
if labelShape._shapeRegion.GetText():
|
||
|
s = labelShape._shapeRegion.GetText()
|
||
|
labelShape.FormatText(dc, s, i)
|
||
|
self.DrawRegion(dc, labelShape._shapeRegion, xx, yy)
|
||
|
return True
|
||
|
|