# -*- 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 #---------------------------------------------------------------------------- from __future__ import division 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 = 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 y1 = self._ypos-self._height / 2 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 line_y = (last_point[1] + second_last_point[1]) / 2 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]= 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)rLeft and xrTop and y= 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 y = (last_line_point[1] + second_last_line_point[1]) / 2 # 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 = positionOnLineX y3 = positionOnLineY d=-arrow.GetYOffset() # Negate so +offset is above line if x3 == x1: theta = pi / 2 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 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) x1 = x-diameter / 2 y1 = y-diameter / 2 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) # Calculate theta for rotating the metafile. # # | # | o(x2, y2) 'o' represents the arrowhead. # | / # | / # | /theta # | /(x1, y1) # |______________________ # theta = 0.0 x1 = startPositionX y1 = startPositionY x2 = positionOnLineX y2 = positionOnLineY if x1 == x2 and y1 == y2: theta = 0.0 elif x1 == x2 and y1>y2: theta = 3.0 * pi / 2 elif x1 == x2 and y2>y1: theta = pi / 2 elif x2>x1 and y2 >= y1: theta = math.atan((y2-y1) / (x2-x1)) elif x2x1 and y21: dc.DrawRectangle(self._xpos-bound_x / 2-2, self._ypos-bound_y / 2-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]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 i2: 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 i10: 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