Exemple #1
0
    def setAnchorPath(self, path):
        """
        Set the anchor's curve path as a :class:`QPainterPath`.
        """
        self.prepareGeometryChange()
        self.__boundingRect = None

        self.__anchorPath = path
        # Create a stroke of the path.
        stroke_path = QPainterPathStroker()
        stroke_path.setCapStyle(Qt.RoundCap)

        # Shape is wider (bigger mouse hit area - should be settable)
        stroke_path.setWidth(12)
        self.__shape = stroke_path.createStroke(path)

        # The full stroke
        stroke_path.setWidth(3)
        self.__fullStroke = stroke_path.createStroke(path)

        # The dotted stroke (when not connected to anything)
        stroke_path.setDashPattern(Qt.DotLine)
        self.__dottedStroke = stroke_path.createStroke(path)

        if self.anchored:
            self.setPath(self.__fullStroke)
            self.setBrush(self.connectedBrush)
        else:
            self.setPath(self.__dottedStroke)
            self.setBrush(self.normalBrush)
Exemple #2
0
    def _draw_hint_move_path(self, move):
        path = QPainterPath()
        start = QPointF(
            *self.get_field_center(self.index_by_label[move.from_field]))
        path.moveTo(start)
        prev = start
        for step in move.steps:
            pos = QPointF(
                *self.get_field_center(self.index_by_label[step.field]))
            path.lineTo(pos)
            last_line = pos - prev
            prev = pos
        sz = math.sqrt(last_line.x()**2 + last_line.y()**2)
        direction = last_line / sz
        head_size = self._get_hint_arrow_head_size()
        w = 0.2 * head_size * QPointF(-direction.y(), direction.x())
        p1 = pos + w
        p2 = pos + 0.3 * head_size * direction
        p3 = pos - w
        path.moveTo(p1)
        path.lineTo(p2)
        path.lineTo(p3)

        pen = QPen(self.theme.hint_color,
                   self._get_hint_arrow_width(),
                   cap=Qt.RoundCap,
                   join=Qt.MiterJoin)
        stroker = QPainterPathStroker(pen)
        path = stroker.createStroke(path).simplified()
        return path
Exemple #3
0
 def shape(self):
     """generates outline for path
     """
     qp = QPainterPathStroker()
     qp.setWidth(8)
     path = qp.createStroke(self.path())
     return path
 def shape(self):
     # return interactive path
     qp = QPainterPathStroker()
     qp.setWidth(8)
     # create outline of path
     path = qp.createStroke(self.path())
     return path
def _shapeFromPath(path, width):
    if path.isEmpty():
        return path
    ps = QPainterPathStroker()
    ps.setWidth(width)
    p = ps.createStroke(path)
    p.addPath(path)
    return p
Exemple #6
0
 def shape(self) -> QPainterPath:
     rect = self.rect()
     path = QPainterPath()
     stroker = QPainterPathStroker()
     path.addRect(rect)
     stroker.setWidth(self.pen().width())
     shape = stroker.createStroke(path)
     return shape
Exemple #7
0
def _shapeFromPath(path, width):
    if path.isEmpty():
        return path
    ps = QPainterPathStroker()
    ps.setWidth(width)
    p = ps.createStroke(path)
    p.addPath(path)
    return p
Exemple #8
0
 def shape(self):
     path = QPainterPath()
     stroker = QPainterPathStroker()
     path.moveTo(self.line().p1())
     path.lineTo(self.line().p2())
     path.addPolygon(self._arrow_head)
     path.addPolygon(self._arrow_tail)
     stroker.setWidth(self.pen().width())
     return stroker.createStroke(path)
Exemple #9
0
def stroke_path(path, pen):
    """Create a QPainterPath stroke from the `path` drawn with `pen`.
    """
    stroker = QPainterPathStroker()
    stroker.setCapStyle(pen.capStyle())
    stroker.setJoinStyle(pen.joinStyle())
    stroker.setMiterLimit(pen.miterLimit())
    stroker.setWidth(max(pen.widthF(), 1e-9))

    return stroker.createStroke(path)
Exemple #10
0
    def shape(self):
        """
        Reimplemented function to select outline only.
        @return: Returns the Outline only
        """
        painterStrock = QPainterPathStroker()
        painterStrock.setCurveThreshold(0.01)
        painterStrock.setWidth(0)

        stroke = painterStrock.createStroke(self.path)
        return stroke
Exemple #11
0
    def shape(self):
        """
        Reimplemented function to select outline only.
        @return: Returns the Outline only
        """
        painterStrock = QPainterPathStroker()
        painterStrock.setCurveThreshold(0.01)
        painterStrock.setWidth(0)

        stroke = painterStrock.createStroke(self.path)
        return stroke
Exemple #12
0
 def shape(self) -> QPainterPath:
     rect = self.rect()
     path = QPainterPath()
     stroker = QPainterPathStroker()
     path.addEllipse(rect)
     stroker.setWidth(self.pen().width())
     if self.isSelected():
         path.addRect(QRectF(rect.topLeft(), QSizeF(5., 5.)))
         path.addRect(QRectF(rect.topRight() - QPointF(5., 0.), QSizeF(5., 5.)))
         path.addRect(QRectF(rect.bottomLeft() - QPointF(0., 5.), QSizeF(5., 5.)))
         path.addRect(QRectF(rect.bottomRight() - QPointF(5., 5.), QSizeF(5., 5.)))
     return stroker.createStroke(path)
Exemple #13
0
    def shape_from_path(self, path, pen):
        if path == QPainterPath() or pen == Qt.NoPen:
            return path

        ps = QPainterPathStroker()
        ps.setCapStyle(pen.capStyle())
        ps.setWidth(pen.widthF())
        ps.setJoinStyle(pen.joinStyle())
        ps.setMiterLimit(pen.miterLimit())

        p = ps.createStroke(path)
        p.addPath(path)
        return p
Exemple #14
0
 def shape(self):
     """Overrides shape method and set shape to segment on which grabber is located"""
     index = self.m_index
     # take start and end point of segment
     startPoint = QPointF(self.parentItem().points[index])
     endPoint = QPointF(self.parentItem().points[index + 1])
     # map in grabber's co-ordinate
     startPoint = self.mapFromParent(startPoint)
     endPoint = self.mapFromParent(endPoint)
     # create path as line
     path = QPainterPath(startPoint)
     path.lineTo(endPoint)
     # generate outlines for path
     stroke = QPainterPathStroker()
     stroke.setWidth(8)
     return stroke.createStroke(path)
	def buildPath(self):
		srcPos, tarPos = self.getNodePos()
		if self.pathPnt and (self.pathPnt[0]-srcPos).manhattanLength() < 0.05 and (self.pathPnt[1]-tarPos).manhattanLength() < 0.05:
			return self.path
		self.pathPnt = (srcPos, tarPos)
		path = QtGui.QPainterPath()
		path.moveTo(srcPos)
		dx = tarPos.x() - srcPos.x()
		p1 = srcPos + QtCore.QPointF(dx*0.3, 0)
		p2 = tarPos + QtCore.QPointF(-dx*0.7, 0)
		path.cubicTo(p1,p2,tarPos)
		self.curve = QtGui.QPainterPath(path)
		self.path = path

		from PyQt5.QtGui import QPainterPathStroker
		stroker = QPainterPathStroker()
		stroker.setWidth(10.0)
		self.pathShape = stroker.createStroke(self.path)
		return path
    def buildPath(self):
        srcPos, tarPos = self.getNodePos()
        if self.pathPnt and (self.pathPnt[0] - srcPos).manhattanLength(
        ) < 0.05 and (self.pathPnt[1] - tarPos).manhattanLength() < 0.05:
            return self.path
        self.pathPnt = (srcPos, tarPos)
        path = QtGui.QPainterPath()
        path.moveTo(srcPos)
        dx = tarPos.x() - srcPos.x()
        p1 = srcPos + QtCore.QPointF(dx * 0.3, 0)
        p2 = tarPos + QtCore.QPointF(-dx * 0.7, 0)
        path.cubicTo(p1, p2, tarPos)
        self.curve = QtGui.QPainterPath(path)
        self.path = path

        from PyQt5.QtGui import QPainterPathStroker
        stroker = QPainterPathStroker()
        stroker.setWidth(10.0)
        self.pathShape = stroker.createStroke(self.path)
        return path
 def shape(self):
     if len(self._points) == 2:
         path = QPainterPath()
         u = self.line().p2() - self.line().p1()
         u /= mag2D(u)
         v = QPointF(-u.y(), u.x())  # perp vector
         v *= self.arrowHeadSize()
         p1 = self.line().p1()
         p2 = self.line().p2()
         size = self.arrowHeadSize()
         poly = QPolygonF([p1 + v, p1 - v, p2 - v, p2 + v, p1 + v])
         path.addPolygon(poly)
         return path
     else:
         path = QPainterPath()
         path.moveTo(self._points[0].pos())
         path.cubicTo(self._points[1].pos(), self._points[2].pos(),
                      self._points[3].pos())
         stroker = QPainterPathStroker(
             QPen(Qt.black,
                  self.arrowHeadSize() * 2))
         return stroker.createStroke(path)
Exemple #18
0
def arrow_path_plain(line, width):
    """
    Return an :class:`QPainterPath` of a plain looking arrow.
    """
    path = QPainterPath()
    p1, p2 = line.p1(), line.p2()

    if p1 == p2:
        return path

    baseline = QLineF(line)
    # Require some minimum length.
    baseline.setLength(max(line.length() - width * 3, width * 3))
    path.moveTo(baseline.p1())
    path.lineTo(baseline.p2())

    stroker = QPainterPathStroker()
    stroker.setWidth(width)
    path = stroker.createStroke(path)

    arrow_head_len = width * 4
    arrow_head_angle = 50
    line_angle = line.angle() - 180

    angle_1 = line_angle - arrow_head_angle / 2.0
    angle_2 = line_angle + arrow_head_angle / 2.0

    points = [
        p2, p2 + QLineF.fromPolar(arrow_head_len, angle_1).p2(),
        p2 + QLineF.fromPolar(arrow_head_len, angle_2).p2(), p2
    ]

    poly = QPolygonF(points)
    path_head = QPainterPath()
    path_head.addPolygon(poly)
    path = path.united(path_head)
    return path
Exemple #19
0
 def shape(self):
     qp = QPainterPathStroker()
     qp.setWidth(10)
     qp.setCapStyle(Qt.SquareCap)
     return qp.createStroke(self.path())
Exemple #20
0
 def shape(self):
     # 设置宽度 这里用了100 很大
     qps = QPainterPathStroker()
     qps.setWidth(100)
     return qps.createStroke(self.calc_path())
 def shape(self):
     stroker = QPainterPathStroker()
     stroker.setWidth(Constants.connectionItemSize * 3)
     return stroker.createStroke(self.path())
Exemple #22
0
class _GQtRayOPL(QGraphicsPathItem):
    """The graphical class corresponding to :class:`.Ray`."""

    # note: @g_counterpart will add a keyword argument, "element"
    def __init__(self, **kwargs):
        QGraphicsPathItem.__init__(self, **kwargs)

        self.setAcceptHoverEvents(True)
        self.setFlag(QGraphicsItem.ItemIsSelectable, True)
        # Rays stay just below the source,
        # without any need to set Z-value explicitly
        # This avoid selection problems (ray is always behind handles)
        self.setFlag(QGraphicsItem.ItemStacksBehindParent, True)

        # pen for normal state
        self.pen_normal = QPen(Qt.blue, 1.5, Qt.SolidLine)
        self.pen_normal.setCosmetic(True)  # thickness does not scale
        self.setPen(self.pen_normal)
        # pen for hover state
        self.pen_hover = QPen(Qt.gray, 1.5, Qt.SolidLine)
        self.pen_hover.setCosmetic(True)  # thickness does not scale

        # will be used in shape()
        self.stroker = QPainterPathStroker()

        self._selected = False

    def g_draw(self):
        self.prepareGeometryChange()
        # prevent BSPtree corruption (Qt crash)
        self.e.source.g.prepareGeometryChange()
        path = QPainterPath()
        # begin at the beginning
        p0 = self.e.parts[0].line.p
        path.moveTo(p0.x, p0.y)
        # add lines
        for i, part in enumerate(self.e.parts):
            if math.isinf(part.s):
                # something large, but not inf, for Qt
                # make sure the ray extends more than the whole scene
                scene_rect = self.scene().sceneRect()
                s = 2 * max(scene_rect.width(), scene_rect.height())

                # Drawn length is dependent on defined OPL
                s = self.e.L - sum([p.s * p.n for p in self.e.parts[0:i]])

            else:
                s = part.s
            path.lineTo(part.line.p.x + part.line.u.x * s,
                        part.line.p.y + part.line.u.y * s)

        # update to the new path
        self.setPath(path)

    def g_add_part(self, u, s, n=None):
        RayOPL.add_part(self.e, u, s, n)
        self.g_draw()

    def g_change_s(self, part_number, new_s):
        RayOPL.change_s(self.e, part_number, new_s)
        self.g_draw()

    def hoverEnterEvent(self, event):
        """Overload QGraphicsPathItem method."""

        self.setPen(self.pen_hover)
        QGraphicsPathItem.hoverEnterEvent(self, event)

    def hoverLeaveEvent(self, event):
        """Overload QGraphicsPathItem method."""

        self.setPen(self.pen_normal)
        QGraphicsPathItem.hoverEnterEvent(self, event)

    def itemChange(self, change, value):
        """Overload QGraphicsPathItem method."""

        if (change == QGraphicsItem.ItemSelectedChange):
            # usually setSelected should not be called here, but
            # setSelected has been overriden and does not call the base method
            self.setSelected(value)
            # return False to avoid the deselection of the pointhandle
            # (multiple selection seems impossible without holding ctrl)
            return False
        # forward event
        return QGraphicsPathItem.itemChange(self, change, value)

    def paint(self, painter, option, widget=None):
        """Overload QGraphicsPathItem method."""

        new_option = QStyleOptionGraphicsItem(option)
        # suppress the "selected" state
        # this avoids the dashed rectangle surrounding the ray when selected
        new_option.state = QStyle.State_None
        QGraphicsPathItem.paint(self, painter, new_option, widget)

    def shape(self):
        """Overload QGraphicsPathItem method."""

        # by default, the shape is the path,
        # but closed, even if the path is a line
        # then sometimes the ray seems hovered, even when mouse is not on it
        # to avoid that, we need to reimplement shape,
        # with a QPainterPathStroker which
        # creates a shape that closely fits the line
        return self.stroker.createStroke(self.path())

    def setSelected(self, selected):
        """Overload QGraphicsPathItem method."""

        # override base method, without calling it
        # otherwise the selection of pointHandle
        # deselected the ray and vice-versa
        # (multiple selection seems impossible without holding ctrl)
        # FIXME: should not use the element(.g) here. Find another way.
        self.e.source.g.setSelected(selected)
        self._selected = selected

    def isSelected(self):
        """Overload QGraphicsPathItem method."""

        return self._selected
 def shape(self):
     stroker = QPainterPathStroker(Pen(Qt.black, self.arrowHeadSize() * 2))
     return stroker.createStroke(self.tailPath())
Exemple #24
0
 def shape(self):
     pen = self.pen()
     pen.setWidth(self.path.width)
     st = QPainterPathStroker(pen)
     return st.createStroke(self.path)
Exemple #25
0
 def drawEdge(self, x1, y1, x2, y2, index, curve, tear=False):
     """
     Draw an edge from x1, y1 to x2, y2.  Edges connect two nodes
     --Args--
     index: the edge index
     curve: distance from center of straight edge to a point on
            curved edge (can be positive or negitive.  Used to
            keep edges from overlapping.
     tear: if true draw in tear edge style
     """
     # determine if edge conntects a node to itself.
     if abs(x1 - x2) < 0.01 and abs(y1 - y2) < 0.01:
         path = QPainterPath()
         curve = curve * 2
         path.addEllipse(x1, y1 - curve / 2.0, curve, curve)
         if tear:
             gi = self.addPath(path, self.edgePen)
         else:
             gi = self.addPath(path, self.tearEdgePen)
     else:
         # mid point of the edge if it is a straight line
         xmid = (x1 + x2) / 2.0
         ymid = (y1 + y2) / 2.0
         # get the angle of the edge and the angle perpendicular
         ang = math.atan2((y2 - y1), (x2 - x1))
         ang_perp = math.atan2((x1 - x2), (y2 - y1))
         # calculate the mid point of the curved edge
         xcurve = xmid + curve * math.cos(ang_perp)
         ycurve = ymid + curve * math.sin(ang_perp)
         # calculate control point for drawing quaratic curve
         xcontrol = 2 * xcurve - xmid
         ycontrol = 2 * ycurve - ymid
         # draw Edge
         path = QPainterPath()
         path.moveTo(x1, y1)
         path.quadTo(xcontrol, ycontrol, x2, y2)
         p2 = QPainterPathStroker()
         path = p2.createStroke(path)
         # if edge is selected draw it highlighted
         if index in self.selectedEdges:
             self.addPath(path, self.eSelectionPen)
         # if edge is a tear draw it tear style else draw it normal
         if tear:
             gi = self.addPath(path, self.tearEdgePen)
         else:
             gi = self.addPath(path, self.edgePen)
         # Add data to edge so if seleted we can determine that it
         # is an edge and which edge it is.
         gi.setData(1, index)
         gi.setData(2, "edge")
         # Draw the arrow
         path = QPainterPath()
         xs = xcurve + self.edgeArrowSize * math.cos(ang)
         ys = ycurve + self.edgeArrowSize * math.sin(ang)
         path.moveTo(xs, ys)
         path.lineTo(
             xs
             - self.edgeArrowSize * math.cos(ang)
             + self.edgeArrowSize / 2.0 * math.cos(ang_perp),
             ys
             - self.edgeArrowSize * math.sin(ang)
             + self.edgeArrowSize / 2.0 * math.sin(ang_perp),
         )
         path.lineTo(
             xs
             - self.edgeArrowSize * math.cos(ang)
             - self.edgeArrowSize / 2.0 * math.cos(ang_perp),
             ys
             - self.edgeArrowSize * math.sin(ang)
             - self.edgeArrowSize / 2.0 * math.sin(ang_perp),
         )
         path.lineTo(xs, ys)
         gi = self.addPath(path, self.edgePen, self.edgeArrowBrush)
         # Add data so selecting the arrow in like selecting the edge
         gi.setData(1, index)
         gi.setData(2, "edge")
Exemple #26
0
class PFSRelation(PFSElement):
    def __init__(self, id: str, source: PFSNode, target: PFSNode):
        PFSElement.__init__(self, id)
        self._source = source
        self._sourceNum = 0
        self._target = target
        self._targetNum = 0
        self._midPoints = []
        self._firstPoint = None
        self._lastPoint = None
        self._pen = QPen(Qt.black)
        self._penSelected = QPen(PFSElement.SELECTED_PEN)
        self._penSelectedAlt = QPen(PFSElement.SELECTED_PEN_ALT)
        self._obj = PFSGraphItems()
        self.penEdited = self._obj.penEdited
        self._stroke = QPainterPathStroker()
        self._stroke.setWidth(20)

    def sceneEventFilter(self, item, ev: QEvent):
        print(ev.type())
        return QGraphicsItem.sceneEventFilter(self, item, ev)

    def closestMiddlePoint(self, pos: QPointF):
        d = -1
        p1 = None
        for p in self._midPoints:
            aux = QLineF(pos, p).length()
            if p1 is None or aux < d:
                p1 = p
                d = aux
        return p1

    def closeMiddlePoint(self, pos: QPointF):
        for p in self._midPoints:
            if QLineF(pos, p).length() < 3:
                return p
        return None

    def closestPoint(self, pos: QPointF):
        d = -1
        p = None
        p1 = self._firstPoint
        prev = -1
        for i in range(len(self._midPoints)):
            p2 = self._midPoints[i]
            l = QLineF(p1, p2)
            x = QPointF.dotProduct(pos - p1, p2 - p1) / l.length()
            if x < 0:
                paux = p1
            elif x > 1:
                paux = p2
            else:
                paux = l.pointAt(x)
            aux = QLineF(paux, pos).length()
            if p is None or aux < d:
                p = paux
                d = aux
                prev = i
            p1 = p2
        p2 = self._lastPoint
        l = QLineF(p1, p2)
        x = QPointF.dotProduct(pos - p1, p2 - p1) / (l.length() * l.length())
        if x < 0:
            paux = p1
        elif x > 1:
            paux = p2
        else:
            paux = l.pointAt(x)
        aux = QLineF(paux, pos).length()
        if p is None or aux < d:
            p = paux
            d = aux
            prev = -1
        return p, prev

    def addMiddlePoint(self, point: QPointF, i=-1):
        if i < 0:
            self._midPoints.append(point)
        else:
            self._midPoints.insert(i, point)

    def createMiddlePoint(self, pos: QPointF):
        p, i = self.closestPoint(pos)
        self.addMiddlePoint(p, i)
        self.scene().update()

    def moveX(self, txt, update=True):
        value = float(txt)
        for point in self._midPoints:
            point.setX(point.x() + value)
        if update:
            self.scene().update()

    def moveY(self, txt, update=True):
        value = float(txt)
        for point in self._midPoints:
            point.setY(point.y() + value)
        if update:
            self.scene().update()

    def simpleTree(self, parent):
        tree = PFSTreeItem(parent, [self._id], 0, QIcon(PFSRelationIcon()))
        tree.clicked.connect(self.selectSingle)
        return tree

    def tree(self, parent):
        tree = PFSTreeItem(parent, [self._id], 0, QIcon(PFSRelationIcon()))
        tree.clicked.connect(self.selectSingle)
        child = self._source.simpleTree(tree)
        child = self._target.simpleTree(tree)
        return tree

    def hasSubPage(self):
        return False

    def copy(self, x, y):
        ans = PFSRelationContent()
        ans._id = self._id
        ans._midPoints = []
        for point in ans._midPoints:
            ans._midPoints.append(QPointF(point.x() - x, point.y() - y))
        ans._pen = self._pen
        ans._tags = self._tags
        ans._source = self._source._id
        ans._target = self._target._id
        ans._sourceNum = self._sourceNum
        ans._targetNum = self._targetNum
        return ans

    def paste(content, id, dx, dy, itemList):
        ans = PFSRelation.createRelation(id, itemList[content._source],
                                         itemList[content._target])
        ans._pen = content._pen
        ans._sourceNum = content._sourceNum
        ans._targetNum = content._targetNum
        for tag in content._tags:
            ans.addTag(tag._name, tag._use, False)
        for point in content._midPoints:
            ans._midPoints.append(QPointF(point.x() + dx, point.y() + dy))
        ans.updatePoints()
        return ans

    def createRelation(id: str, source: PFSNode, target: PFSNode):
        if isinstance(source, PFSActive):
            if isinstance(target, PFSPassive):
                r = PFSRelation(id, source, target)
                source.changed.connect(r.updatePoints)
                target.changed.connect(r.updatePoints)
                source.deleted.connect(r.putInDelete)
                target.deleted.connect(r.putInDelete)
                return r
        elif isinstance(source, PFSPassive):
            if isinstance(target, PFSActive):
                r = PFSRelation(id, source, target)
                source.changed.connect(r.updatePoints)
                target.changed.connect(r.updatePoints)
                source.deleted.connect(r.putInDelete)
                target.deleted.connect(r.putInDelete)
                return r
        return None

    def updatePoints(self):
        if len(self._midPoints) == 0:
            if isinstance(self._source, PFSActive):
                self._firstPoint = self._source.getBestRelationPointOutput(
                    QRect(self._target.x(), self._target.y(),
                          self._target._width, self._target._height).center(),
                    self._sourceNum)
                self._lastPoint = self._target.getBestRelationPointInput(
                    self._firstPoint, self._targetNum)
            else:
                self._lastPoint = self._target.getBestRelationPointInput(
                    QRect(self._source.x(), self._source.y(),
                          self._source._width, self._source._height).center(),
                    self._targetNum)
                self._firstPoint = self._source.getBestRelationPointOutput(
                    self._lastPoint, self._sourceNum)
        else:
            self._firstPoint = self._source.getBestRelationPointOutput(
                self._midPoints[0], self._sourceNum)
            self._lastPoint = self._target.getBestRelationPointInput(
                self._midPoints[-1], self._targetNum)

    def getPath(self) -> QPainterPath:
        pol = QPolygonF()
        pol.append(self._firstPoint)
        for p in self._midPoints:
            pol.append(p)
        pol.append(self._lastPoint)
        ans = QPainterPath()
        ans.addPolygon(pol)
        return ans

    def shape(self) -> QPainterPath:
        return self._stroke.createStroke(self.getPath())

    def paint(self, p: QPainter, o: QStyleOptionGraphicsItem, w: QWidget):
        p.setPen(self._pen)
        if self.isSelected():
            if self._pen.color() == PFSElement.SELECTED_PEN:
                p.setPen(self._penSelectedAlt)
            else:
                p.setPen(self._penSelected)
        path = self.getPath()
        p.drawPath(path)
        if len(self._midPoints) == 0:
            ang = math.atan2(self._lastPoint.y() - self._firstPoint.y(),
                             self._lastPoint.x() - self._firstPoint.x())
        else:
            ang = math.atan2(self._lastPoint.y() - self._midPoints[-1].y(),
                             self._lastPoint.x() - self._midPoints[-1].x())
        p.save()
        p.translate(self._lastPoint)
        p.rotate(ang * 180 / math.pi)
        self.drawArrow(p)
        p.restore()

    def drawArrow(self, p: QPainter):
        p.drawLine(-10, -6, 0, 0)
        p.drawLine(-10, 6, 0, 0)

    def boundingRect(self):
        if self._firstPoint is None or self._lastPoint is None:
            self.updatePoints()
        t = min(self._firstPoint.y(), self._lastPoint.y())
        b = max(self._firstPoint.y(), self._lastPoint.y())
        l = min(self._firstPoint.x(), self._lastPoint.x())
        r = max(self._firstPoint.x(), self._lastPoint.x())
        for p in self._midPoints:
            if p.x() < l:
                l = p.x()
            if p.x() > r:
                r = p.x()
            if p.y() < t:
                t = p.y()
            if p.y() > b:
                b = p.y()
        w = r - l
        if w < 5:
            w = 5
        h = b - t
        if h < 5:
            h = 5
        return QRectF(l, t, w, h)

    def generateXml(self, xml: QXmlStreamWriter):
        PFSXmlBase.open(xml)
        xml.writeStartElement("relation")
        xml.writeAttribute("id", self._id)
        xml.writeAttribute("source", self._source._id)
        xml.writeAttribute("sourceport", str(self._sourceNum))
        xml.writeAttribute("target", self._target._id)
        xml.writeAttribute("targetport", str(self._targetNum))
        PFSXmlBase.graphicsArc(xml, self._midPoints, self._pen)
        PFSBasicElement.generateXml(self, xml)
        xml.writeEndElement()  #fecha distributor
        PFSXmlBase.close(xml)

    def move(self, x, y):
        delta = QPointF(x, y)
        for p in self._midPoints:
            p += delta

    def createFromXml(node: QDomNode):
        if node.nodeName() != "relation":
            return None
        attr = node.attributes()
        if not (node.hasAttributes() and attr.contains("id")):
            return None
        id = attr.namedItem("id").nodeValue()
        if not (attr.contains("source") and attr.contains("target")):
            return None
        source = attr.namedItem("source").nodeValue()
        target = attr.namedItem("target").nodeValue()
        sourceNum = 0
        targetNum = 0
        if attr.contains("sourceport"):
            sourceNum = int(attr.namedItem("sourceport").nodeValue())
        if attr.contains("targetport"):
            targetNum = int(attr.namedItem("targetport").nodeValue())
        graphics = None
        tags = []
        childs = node.childNodes()
        for i in range(childs.count()):
            child = childs.at(i)
            if child.nodeName() == "graphics":
                graphics = PFSXmlBase.getArc(child)
            if child.nodeName() == "tags":
                tags = PFSBasicElement.createFromXml(child)
        re = PFSRelationContent()
        re._id = id
        re._source = source
        re._sourceNum = sourceNum
        re._target = target
        re._targetNum = targetNum
        if graphics is not None and graphics.line is not None:
            re._pen = graphics.line
        re._midPoints = []
        if graphics is not None and graphics.pos is not None:
            for pos in graphics.pos:
                re._midPoints.append(QPointF(pos.x, pos.y))
        re._tags = tags
        return re

    def putInDelete(self):
        if self.scene() is not None:
            lst = self.scene()._itemsDeleted
            if self not in lst:
                lst.append(self)

    def setPenColor(self, color: QColor):
        self._pen.setColor(color)
        self.scene().update()

    def setPenStyle(self, style: Qt):
        self._pen.setStyle(style)
        self.scene().update()
        self.penEdited.emit(style)

    def setPenWidth(self, width: str):
        self._pen.setWidth(float(width))
        self.scene().update()

    def setSourceNum(self, text: str):
        self._sourceNum = int(text)
        if self.scene() is not None:
            self.updatePoints()
            self.scene().update()

    def setTargetNum(self, text: str):
        self._targetNum = int(text)
        if self.scene() is not None:
            self.updatePoints()
            self.scene().update()

    def propertiesTable(self):
        ans = []
        lblType = PFSTableLabel("Elemento")
        lblValue = PFSTableNormal("Arco")
        lblValue.setFlags(Qt.NoItemFlags)
        ans.append([lblType, lblValue])
        lblType = PFSTableLabel("ID")
        lblValue = PFSTableNormal(self._id)
        lblValue.setFlags(Qt.NoItemFlags)
        ans.append([lblType, lblValue])
        lblType = PFSTableLabel("Cor do contorno")
        lblValue = PFSTableValueButton(self._pen.color().name())
        lblValue.clicked.connect(self.changeLineColor)
        ans.append([lblType, lblValue])
        lblType = PFSTableLabel("Linha do contorno")
        lblValue = PFSTableValueCombo(self.PEN_LIST, self._pen.style())
        self.penEdited.connect(lblValue.updateText)
        lblValue.currentTextChanged.connect(self.changeLineStyle)
        ans.append([lblType, lblValue])
        lblType = PFSTableLabel("Espessura do contorno")
        lblValue = PFSTableValueText(str(self._pen.width()))
        lblValue.edited.connect(self.changeLineWidth)
        ans.append([lblType, lblValue])
        lblType = PFSTableLabel("Porta origem")
        lblValue = PFSTableValueCombo(
            {str(x + 1): str(x)
             for x in range(self._source.outputNum())}, str(self._sourceNum))
        lblValue.currentTextChanged.connect(self.changeSourceNum)
        ans.append([lblType, lblValue])
        lblType = PFSTableLabel("Porta destino")
        lblValue = PFSTableValueCombo(
            {str(x + 1): str(x)
             for x in range(self._target.inputNum())}, str(self._targetNum))
        lblValue.currentTextChanged.connect(self.changeTargetNum)
        ans.append([lblType, lblValue])
        lblType = PFSTableLabelTags("Tags")
        lblValue = PFSTableValueBox(self._tags, self.createTag)
        ans.append([lblType, lblValue])
        return ans

    def changeLineColor(self):
        color = QColorDialog.getColor(self._pen.color(),
                                      self.scene()._page._net,
                                      "Escolha a cor do contorno")
        if color.isValid() and color != self._pen.color():
            x = PFSUndoPropertyButton(color, self._pen.color(),
                                      self.setPenColor)
            x.setText("Alterar cor")
            self.scene()._page._net.undoStack.push(x)

    def changeLineStyle(self, text):
        if text in self.PEN_LIST:
            x = PFSUndoPropertyCombo(self.PEN_LIST[text], self._pen.style(),
                                     self.setPenStyle)
            x.setText("Alterar linha")
            self.scene()._page._net.undoStack.push(x)

    def changeLineWidth(self, prop):
        x = PFSUndoPropertyText(prop, self.setPenWidth)
        x.setText("Alterar espessura")
        self.scene()._page._net.undoStack.push(x)

    def changeSourceNum(self, text):
        try:
            x = PFSUndoPropertyCombo(str(int(text) - 1), str(self._sourceNum),
                                     self.setSourceNum)
            x.setText("Alterar porta entrada")
            self.scene()._page._net.undoStack.push(x)
        except:
            pass

    def changeTargetNum(self, text):
        try:
            x = PFSUndoPropertyCombo(str(int(text) - 1), str(self._targetNum),
                                     self.setTargetNum)
            x.setText("Alterar porta saída")
            self.scene()._page._net.undoStack.push(x)
        except:
            pass