# -*- 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.NewIdRef()

    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`: height in points

        """
        RectangleShape.__init__(self, w, h)
        self._lineShape = parent
        self._shapeRegion = region
        self.SetPen(wx.Pen(wx.BLACK, 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 arbitrary 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)
                # |______________________
                #
                x1 = startPositionX
                y1 = startPositionY
                x2 = float(positionOnLineX)
                y2 = float(positionOnLineY)

                theta = math.atan2(y2 - y1, x2 - x1) % (2 * math.pi)

                # 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(int(self._xpos - bound_x / 2.0 - 2), int(self._ypos - bound_y / 2.0 - 2),
                             int(bound_x + 4), int(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.BLACK, 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 link 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(int(point[0]), int(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().GetBuffer())
        self.GetCanvas().PrepareDC(dc)
        dc.SetLogicalFunction(OGLRBLF)

        dottedPen = wx.Pen(wx.BLACK, 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().GetBuffer())
        self.GetCanvas().PrepareDC(dc)

        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.BLACK, 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().GetBuffer())
        self.GetCanvas().PrepareDC(dc)

        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 length 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

