c9c4689547
git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@28825 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
1532 lines
55 KiB
Python
1532 lines
55 KiB
Python
# -*- coding: iso-8859-1 -*-
|
|
#----------------------------------------------------------------------------
|
|
# Name: lines.py
|
|
# Purpose: LineShape class
|
|
#
|
|
# Author: Pierre Hjälm (from C++ original by Julian Smart)
|
|
#
|
|
# Created: 2004-05-08
|
|
# RCS-ID: $Id$
|
|
# Copyright: (c) 2004 Pierre Hjälm - 1998 Julian Smart
|
|
# Licence: wxWindows license
|
|
#----------------------------------------------------------------------------
|
|
|
|
import sys
|
|
import math
|
|
|
|
from _basic import Shape, ShapeRegion, 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):
|
|
def __init__(self, theCanvas = None, object = None, size = 0.0, x = 0.0, y = 0.0, the_type = 0):
|
|
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):
|
|
RectangleShape.OnDraw(self, dc)
|
|
|
|
# Implement movement of Line point
|
|
def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
|
|
self._shape.GetEventHandler().OnSizingDragLeft(self, draw, x, y, keys, attachment)
|
|
|
|
def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
|
|
self._shape.GetEventHandler().OnSizingBeginDragLeft(self, x, y, keys, attachment)
|
|
|
|
def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
|
|
self._shape.GetEventHandler().OnSizingEndDragLeft(self, x, y, keys, attachment)
|
|
|
|
|
|
|
|
class ArrowHead(object):
|
|
def __init__(self, type = 0, end = 0, size = 0.0, dist = 0.0, name = "", mf = None, arrowId = -1):
|
|
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):
|
|
return self._arrowEnd
|
|
|
|
def SetPosition(self, pos):
|
|
self._arrowEnd = pos
|
|
|
|
def GetXOffset(self):
|
|
return self._xOffset
|
|
|
|
def GetYOffset(self):
|
|
return self._yOffset
|
|
|
|
def GetSpacing(self):
|
|
return self._spacing
|
|
|
|
def GetSize(self):
|
|
return self._arrowSize
|
|
|
|
def SetSize(self, size):
|
|
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):
|
|
return self._arrowName
|
|
|
|
def SetXOffset(self, x):
|
|
self._xOffset = x
|
|
|
|
def SetYOffset(self, y):
|
|
self._yOffset = y
|
|
|
|
def GetMetaFile(self):
|
|
return self._metaFile
|
|
|
|
def GetId(self):
|
|
return self._id
|
|
|
|
def GetArrowEnd(self):
|
|
return self._arrowEnd
|
|
|
|
def GetArrowSize(self):
|
|
return self._arrowSize
|
|
|
|
def SetSpacing(self, sp):
|
|
self._spacing = sp
|
|
|
|
|
|
|
|
class LabelShape(RectangleShape):
|
|
def __init__(self, parent, region, w, h):
|
|
RectangleShape.__init__(self, w, h)
|
|
self._lineShape = parent
|
|
self._shapeRegion = region
|
|
self.SetPen(wx.ThePenList.FindOrCreatePen(wx.Colour(0, 0, 0), 1, wx.DOT))
|
|
|
|
def OnDraw(self, dc):
|
|
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):
|
|
pass
|
|
|
|
def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
|
|
RectangleShape.OnDragLeft(self, draw, x, y, keys, attachment)
|
|
|
|
def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
|
|
RectangleShape.OnBeginDragLeft(self, x, y, keys, attachment)
|
|
|
|
def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
|
|
RectangleShape.OnEndDragLeft(self, x, y, keys, attachment)
|
|
|
|
def OnMovePre(self, dc, x, y, old_x, old_y, display):
|
|
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):
|
|
self._lineShape.GetEventHandler().OnLeftClick(x, y, keys, attachment)
|
|
|
|
def OnRightClick(self, x, y, keys = 0, attachment = 0):
|
|
self._lineShape.GetEventHandler().OnRightClick(x, y, keys, attachment)
|
|
|
|
|
|
|
|
class LineShape(Shape):
|
|
"""LineShape may be attached to two nodes;
|
|
it may be segmented, in which case a control point is drawn for each joint.
|
|
|
|
A wxLineShape may have arrows at the beginning, end and centre.
|
|
|
|
Derived from:
|
|
Shape
|
|
"""
|
|
def __init__(self):
|
|
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 __del__(self):
|
|
if self._lineControlPoints:
|
|
self.ClearPointList(self._lineControlPoints)
|
|
self._lineControlPoints = []
|
|
for i in range(3):
|
|
if self._labelObjects[i]:
|
|
self._labelObjects[i].Select(False)
|
|
self._labelObjects[i].RemoveFromCanvas(self._canvas)
|
|
self._labelObjects = []
|
|
self.ClearArrowsAtPosition(-1)
|
|
|
|
def GetFrom(self):
|
|
"""Return the 'from' object."""
|
|
return self._from
|
|
|
|
def GetTo(self):
|
|
"""Return the 'to' object."""
|
|
return self._to
|
|
|
|
def GetAttachmentFrom(self):
|
|
"""Return the attachment point on the 'from' node."""
|
|
return self._attachmentFrom
|
|
|
|
def GetAttachmentTo(self):
|
|
"""Return the attachment point on the 'to' node."""
|
|
return self._attachmentTo
|
|
|
|
def GetLineControlPoints(self):
|
|
return self._lineControlPoints
|
|
|
|
def SetSpline(self, spline):
|
|
"""Specifies whether a spline is to be drawn through the control points."""
|
|
self._isSpline = spline
|
|
|
|
def IsSpline(self):
|
|
"""TRUE if 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
|
|
|
|
# This is really to distinguish between lines and other images.
|
|
# For lines, want to pass drag to canvas, since lines tend to prevent
|
|
# dragging on a canvas (they get in the way.)
|
|
def Draggable(self):
|
|
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):
|
|
return self._arcArrows
|
|
|
|
def GetAlignmentStart(self):
|
|
return self._alignmentStart
|
|
|
|
def GetAlignmentEnd(self):
|
|
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 (minimum of two)."""
|
|
if self._lineControlPoints:
|
|
self.ClearPointList(self._lineControlPoints)
|
|
self._lineControlPoints = []
|
|
|
|
for _ in range(n):
|
|
point = wx.RealPoint(-999, -999)
|
|
self._lineControlPoints.append(point)
|
|
|
|
def InsertLineControlPoint(self, dc = None):
|
|
"""Insert a control point at an arbitrary position."""
|
|
if dc:
|
|
self.Erase(dc)
|
|
|
|
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), 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 point in self._lineControlPoints[1:]:
|
|
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]
|
|
point[0] = (x2 - x1) / 2.0 + x1
|
|
point[1] = (y2 - y1) / 2.0 + y1
|
|
|
|
def FormatText(self, dc, s, i):
|
|
"""Format a text string according to the region size, adding
|
|
strings with positions to region text list.
|
|
"""
|
|
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."""
|
|
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."""
|
|
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.
|
|
|
|
Region x and y are offsets from this.
|
|
position is 0 (middle), 1 (start), 2 (end).
|
|
"""
|
|
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."""
|
|
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
|
|
|
|
def SetEnds(self, x1, y1, x2, y2):
|
|
"""Set the end positions of the line."""
|
|
# Find centre point
|
|
first_point = self._lineControlPoints[0]
|
|
last_point = self._lineControlPoints[-1]
|
|
|
|
first_point[0] = x1
|
|
first_point[1] = y1
|
|
last_point[0] = x2
|
|
last_point[1] = y2
|
|
|
|
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.
|
|
"""
|
|
self._attachmentFrom = from_attach
|
|
self._attachmentTo = to_attach
|
|
|
|
def HitTest(self, x, y):
|
|
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 or dx == 0:
|
|
return False
|
|
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)."""
|
|
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):
|
|
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):
|
|
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.
|
|
"""
|
|
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):
|
|
old_pen = self._pen
|
|
old_brush = self._brush
|
|
|
|
dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.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):
|
|
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):
|
|
"""Called when a connected object has moved, to move the link to
|
|
correct position
|
|
"""
|
|
if not self._from or not self._to:
|
|
return
|
|
|
|
if len(self._lineControlPoints) > 2:
|
|
self.Initialise()
|
|
|
|
# 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()
|
|
|
|
first = self._lineControlPoints[0]
|
|
last = self._lineControlPoints[-1]
|
|
|
|
oldX, oldY = self._xpos, self._ypos
|
|
|
|
self.SetEnds(end_x, end_y, other_end_x, other_end_y)
|
|
|
|
# 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.x += x_offset
|
|
point.y += 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]
|
|
|
|
if len(self._lineControlPoints) > 2:
|
|
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)
|
|
|
|
#print type(self._from), type(self._to), end_x, end_y, other_end_x, other_end_y
|
|
return end_x, end_y, other_end_x, other_end_y
|
|
|
|
def OnDraw(self, dc):
|
|
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.x, point.y))
|
|
|
|
#print points
|
|
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.x, pt.y)
|
|
|
|
# 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.SOLID:
|
|
solid_pen = wx.ThePenList.FindOrCreatePen(self._pen.GetColour(), 1, wx.SOLID)
|
|
if solid_pen:
|
|
dc.SetPen(solid_pen)
|
|
|
|
self.DrawArrows(dc)
|
|
|
|
def OnDrawControlPoints(self, dc):
|
|
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):
|
|
# 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):
|
|
pass
|
|
|
|
def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
|
|
pass
|
|
|
|
def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
|
|
pass
|
|
|
|
def OnDrawContents(self, dc):
|
|
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):
|
|
if self._canvas and self._lineControlPoints:
|
|
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])
|
|
|
|
# Override select, to create / delete temporary label-moving objects
|
|
def Select(self, select, dc = None):
|
|
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):
|
|
dc = wx.ClientDC(self.GetCanvas())
|
|
self.GetCanvas().PrepareDC(dc)
|
|
|
|
dc.SetLogicalFunction(OGLRBLF)
|
|
|
|
dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.DOT)
|
|
dc.SetPen(dottedPen)
|
|
dc.SetBrush(wx.TRANSPARENT_BRUSH)
|
|
|
|
if pt._type == CONTROL_POINT_LINE:
|
|
x, y = self._canvas.Snap()
|
|
|
|
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):
|
|
dc = wx.ClientDC(self.GetCanvas())
|
|
self.GetCanvas().PrepareDC(dc)
|
|
|
|
if pt._type == CONTROL_POINT_LINE:
|
|
pt._originalPos = pt._point
|
|
x, y = self._canvas.Snap()
|
|
|
|
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.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):
|
|
dc = wx.ClientDC(self.GetCanvas())
|
|
self.GetCanvas().PrepareDC(dc)
|
|
|
|
self.SetDisableLabel(False)
|
|
|
|
if pt._type == CONTROL_POINT_LINE:
|
|
x, y = self._canvas.Snap()
|
|
|
|
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):
|
|
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.
|
|
|
|
type may currently be one of:
|
|
|
|
ARROW_HOLLOW_CIRCLE
|
|
Hollow circle.
|
|
ARROW_FILLED_CIRCLE
|
|
Filled circle.
|
|
ARROW_ARROW
|
|
Conventional arrowhead.
|
|
ARROW_SINGLE_OBLIQUE
|
|
Single oblique stroke.
|
|
ARROW_DOUBLE_OBLIQUE
|
|
Double oblique stroke.
|
|
ARROW_DOUBLE_METAFILE
|
|
Custom arrowhead.
|
|
|
|
end may currently be one of:
|
|
|
|
ARROW_POSITION_END
|
|
Arrow appears at the end.
|
|
ARROW_POSITION_START
|
|
Arrow appears at the start.
|
|
|
|
arrowSize specifies the length of the arrow.
|
|
|
|
xOffset specifies the offset from the end of the line.
|
|
|
|
name specifies a name for the arrow.
|
|
|
|
mf can be a wxPseduoMetaFile, perhaps loaded from a simple Windows
|
|
metafile.
|
|
|
|
arrowId is 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.
|
|
"""
|
|
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.
|
|
"""
|
|
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."""
|
|
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.
|
|
"""
|
|
for arrow in self._arcArrows:
|
|
if (position == -1 or position == arrow.GetArrowEnd()) and arrow.GetName() == name:
|
|
return arow
|
|
|
|
return None
|
|
|
|
def FindArrowHeadId(self, arrowId):
|
|
"""Find arrowhead by id."""
|
|
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.
|
|
"""
|
|
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, id):
|
|
"""Delete arrowhead by id."""
|
|
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 + 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 at this x, y.
|
|
|
|
Returns ARROW_POSITION_START, ARROW_POSITION_MIDDLE, 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):
|
|
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):
|
|
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):
|
|
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):
|
|
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.
|
|
"""
|
|
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):
|
|
return LabelShape(parent, region, w, h)
|
|
|
|
|
|
def OnLabelMovePre(self, dc, labelShape, x, y, old_x, old_y, display):
|
|
labelShape._shapeRegion.SetSize(labelShape.GetWidth(), labelShape.GetHeight())
|
|
|
|
# Find position in line's region list
|
|
i = 0
|
|
for region in self.GetRegions():
|
|
if labelShape._shapeRegion == region:
|
|
self.GetRegions().remove(region)
|
|
else:
|
|
i += 1
|
|
|
|
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
|
|
|