Example #1
0
    def _updateTextAnchors(self):
        n = len(self._items)

        items = self._items
        dist = 15

        shape = reduce(QPainterPath.united, [item.path() for item in items])
        brect = shape.boundingRect()
        bradius = max(brect.width() / 2, brect.height() / 2)

        center = self.boundingRect().center()

        anchors = _category_anchors(items)
        self._textanchors = []
        for angle, anchor_h, anchor_v in anchors:
            line = QLineF.fromPolar(bradius, angle)
            ext = QLineF.fromPolar(dist, angle)
            line = QLineF(line.p1(), line.p2() + ext.p2())
            line = line.translated(center)

            anchor_pos = line.p2()
            self._textanchors.append((anchor_pos, anchor_h, anchor_v))

        for i in range(n):
            self._updateTextItemPos(i)
Example #2
0
    def _updateTextAnchors(self):
        n = len(self._items)

        items = self._items
        dist = 15

        shape = reduce(QPainterPath.united, [item.path() for item in items])
        brect = shape.boundingRect()
        bradius = max(brect.width() / 2, brect.height() / 2)

        center = self.boundingRect().center()

        anchors = _category_anchors(items)
        self._textanchors = []
        for angle, anchor_h, anchor_v in anchors:
            line = QLineF.fromPolar(bradius, angle)
            ext = QLineF.fromPolar(dist, angle)
            line = QLineF(line.p1(), line.p2() + ext.p2())
            line = line.translated(center)

            anchor_pos = line.p2()
            self._textanchors.append((anchor_pos, anchor_h, anchor_v))

        for i in range(n):
            self._updateTextItemPos(i)
Example #3
0
def qpainterpath_simple_split(path, t):
    # type: (QPainterPath, float) -> Tuple[QPainterPath, QPainterPath]
    """
    Split a QPainterPath defined simple curve.

    The path must be either empty or composed of a single LineToElement or
    CurveToElement.

    Parameters
    ----------
    path : QPainterPath

    t : float
        Point where to split specified as a percentage along the path

    Returns
    -------
    splitpath: Tuple[QPainterPath, QPainterPath]
        A pair of QPainterPaths
    """
    assert path.elementCount() > 0
    el0 = path.elementAt(0)
    assert el0.type == QPainterPath.MoveToElement
    if path.elementCount() == 1:
        p1 = QPainterPath()
        p1.moveTo(el0.x, el0.y)
        return p1, QPainterPath(p1)

    el1 = path.elementAt(1)
    if el1.type == QPainterPath.LineToElement:
        pointat = path.pointAtPercent(t)
        l1 = QLineF(el0.x, el0.y, pointat.x(), pointat.y())
        l2 = QLineF(pointat.x(), pointat.y(), el1.x, el1.y)
        p1 = QPainterPath()
        p2 = QPainterPath()
        p1.moveTo(l1.p1())
        p1.lineTo(l1.p2())
        p2.moveTo(l2.p1())
        p2.lineTo(l2.p2())
        return p1, p2
    elif el1.type == QPainterPath.CurveToElement:
        c0, c1, c2, c3 = el0, el1, path.elementAt(2), path.elementAt(3)
        assert all(el.type == QPainterPath.CurveToDataElement
                   for el in [c2, c3])
        cp = [QPointF(el.x, el.y) for el in [c0, c1, c2, c3]]
        first, second = bezier_subdivide(cp, t)
        p1, p2 = QPainterPath(), QPainterPath()
        p1.moveTo(first[0])
        p1.cubicTo(*first[1:])
        p2.moveTo(second[0])
        p2.cubicTo(*second[1:])
        return p1, p2
    else:
        assert False
Example #4
0
def qpainterpath_simple_split(path, t):
    """
    Split a QPainterPath defined simple curve.

    The path must be either empty or composed of a single LineToElement or
    CurveToElement.

    Parameters
    ----------
    path : QPainterPath

    t : float
        Point where to split specified as a percentage along the path

    Returns
    -------
    splitpath: Tuple[QPainterPath, QPainterPath]
        A pair of QPainterPaths
    """
    assert path.elementCount() > 0
    el0 = path.elementAt(0)
    assert el0.type == QPainterPath.MoveToElement
    if path.elementCount() == 1:
        p1 = QPainterPath()
        p1.moveTo(el0.x, el0.y)
        return p1, QPainterPath(p1)

    el1 = path.elementAt(1)
    if el1.type == QPainterPath.LineToElement:
        pointat = path.pointAtPercent(t)
        l1 = QLineF(el0.x, el0.y, pointat.x(), pointat.y())
        l2 = QLineF(pointat.x(), pointat.y(), el1.x, el1.y)
        p1 = QPainterPath()
        p2 = QPainterPath()
        p1.moveTo(l1.p1())
        p1.lineTo(l1.p2())
        p2.moveTo(l2.p1())
        p2.lineTo(l2.p2())
        return p1, p2
    elif el1.type == QPainterPath.CurveToElement:
        c0, c1, c2, c3 = el0, el1, path.elementAt(2), path.elementAt(3)
        assert all(el.type == QPainterPath.CurveToDataElement
                   for el in [c2, c3])
        cp = [QPointF(el.x, el.y) for el in [c0, c1, c2, c3]]
        first, second = bezier_subdivide(cp, t)
        p1, p2 = QPainterPath(), QPainterPath()
        p1.moveTo(first[0])
        p1.cubicTo(*first[1:])
        p2.moveTo(second[0])
        p2.cubicTo(*second[1:])
        return p1, p2
    else:
        assert False
Example #5
0
def arrow_path_concave(line, width):
    # type: (QLineF, float) -> QPainterPath
    """
    Return a :class:`QPainterPath` of a pretty 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))

    start, end = baseline.p1(), baseline.p2()
    mid = (start + end) / 2.0
    normal = QLineF.fromPolar(1.0, baseline.angle() + 90).p2()

    path.moveTo(start)
    path.lineTo(start + (normal * width / 4.0))

    path.quadTo(mid + (normal * width / 4.0),
                end + (normal * width / 1.5))

    path.lineTo(end - (normal * width / 1.5))
    path.quadTo(mid - (normal * width / 4.0),
                start - (normal * width / 4.0))
    path.closeSubpath()

    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(),
              baseline.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
Example #6
0
    def setLine(self, line):
        """
        Set the arrow base line (a `QLineF` in object coordinates).
        """
        if self.__line != line:
            self.__line = QLineF(line)

            # local item coordinate system
            geom = self.geometry().translated(-self.pos())

            if geom.isNull() and not line.isNull():
                geom = QRectF(0, 0, 1, 1)

            arrow_shape = arrow_path_concave(line, self.lineWidth())
            arrow_rect = arrow_shape.boundingRect()

            if not (geom.contains(arrow_rect)):
                geom = geom.united(arrow_rect)

            if self.__autoAdjustGeometry:
                # Shrink the geometry if required.
                geom = geom.intersected(arrow_rect)

            # topLeft can move changing the local coordinates.
            diff = geom.topLeft()
            line = QLineF(line.p1() - diff, line.p2() - diff)
            self.__arrowItem.setLine(line)
            self.__arrowShadowBase.setLine(line)
            self.__line = line

            # parent item coordinate system
            geom.translate(self.pos())
            self.setGeometry(geom)
Example #7
0
    def setLine(self, line):
        # type: (QLineF) -> None
        """
        Set the arrow base line (a `QLineF` in object coordinates).
        """
        if self.__line != line:
            self.__line = QLineF(line)

            # local item coordinate system
            geom = self.geometry().translated(-self.pos())

            if geom.isNull() and not line.isNull():
                geom = QRectF(0, 0, 1, 1)

            arrow_shape = arrow_path_concave(line, self.lineWidth())
            arrow_rect = arrow_shape.boundingRect()

            if not (geom.contains(arrow_rect)):
                geom = geom.united(arrow_rect)

            if self.__autoAdjustGeometry:
                # Shrink the geometry if required.
                geom = geom.intersected(arrow_rect)

            # topLeft can move changing the local coordinates.
            diff = geom.topLeft()
            line = QLineF(line.p1() - diff, line.p2() - diff)
            self.__arrowItem.setLine(line)
            self.__arrowShadowBase.setLine(line)
            self.__line = line

            # parent item coordinate system
            geom.translate(self.pos())
            self.setGeometry(geom)
Example #8
0
    def adjustGeometry(self):
        # type: () -> None
        """
        Adjust the widget geometry to exactly fit the arrow inside
        while preserving the arrow path scene geometry.

        """
        # local system coordinate
        geom = self.geometry().translated(-self.pos())
        line = self.__line

        arrow_rect = self.__arrowItem.shape().boundingRect()

        if geom.isNull() and not line.isNull():
            geom = QRectF(0, 0, 1, 1)

        if not (geom.contains(arrow_rect)):
            geom = geom.united(arrow_rect)

        geom = geom.intersected(arrow_rect)
        diff = geom.topLeft()
        line = QLineF(line.p1() - diff, line.p2() - diff)
        geom.translate(self.pos())
        self.setGeometry(geom)
        self.setLine(line)
Example #9
0
def arrow_path_concave(line, width):
    """
    Return a :class:`QPainterPath` of a pretty 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))

    start, end = baseline.p1(), baseline.p2()
    mid = (start + end) / 2.0
    normal = QLineF.fromPolar(1.0, baseline.angle() + 90).p2()

    path.moveTo(start)
    path.lineTo(start + (normal * width / 4.0))

    path.quadTo(mid + (normal * width / 4.0),
                end + (normal * width / 1.5))

    path.lineTo(end - (normal * width / 1.5))
    path.quadTo(mid - (normal * width / 4.0),
                start - (normal * width / 4.0))
    path.closeSubpath()

    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(),
              baseline.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
    def paintArc(self, painter, option, widget):
        assert self.source is self.dest
        node = self.source

        def best_angle():
            """...is the one furthest away from all other angles"""
            angles = [
                QLineF(node.pos(), other.pos()).angle() for other in chain((
                    edge.source for edge in node.edges
                    if edge.dest == node and edge.source != node), (
                        edge.dest for edge in node.edges
                        if edge.dest != node and edge.source == node))
            ]
            angles.sort()
            if not angles:  # If this self-constraint is the only edge
                return 225
            deltas = np.array(angles[1:] + [360 + angles[0]]) - angles
            return (angles[deltas.argmax()] + deltas.max() / 2) % 360

        angle = best_angle()
        inf = QPointF(-1e20, -1e20)  # Doesn't work with real -np.inf!
        line0 = QLineF(node.pos(), inf)
        line1 = QLineF(node.pos(), inf)
        line2 = QLineF(node.pos(), inf)
        line0.setAngle(angle)
        line1.setAngle(angle - 13)
        line2.setAngle(angle + 13)

        p0 = shape_line_intersection(node.shape(), node.pos(), line0)
        p1 = shape_line_intersection(node.shape(), node.pos(), line1)
        p2 = shape_line_intersection(node.shape(), node.pos(), line2)
        path = QPainterPath()
        path.moveTo(p1)
        line = QLineF(node.pos(), p0)
        line.setLength(3 * line.length())
        pt = line.p2()
        path.quadTo(pt, p2)

        line = QLineF(node.pos(), pt)
        self.setLine(line)  # This invalidates DeviceCoordinateCache
        painter.drawPath(path)

        # Draw arrow head
        line = QLineF(pt, p2)
        self.arrowHead.clear()
        for point in self._arrowhead_points(line):
            self.arrowHead.append(point)
        painter.setBrush(self.pen().color())
        painter.drawPolygon(self.arrowHead)

        # Update label position
        self.label.setPos(path.pointAtPercent(.5))
        if 90 < angle < 270:  # Right-align the label
            pos = self.label.pos()
            x, y = pos.x(), pos.y()
            self.label.setPos(x - self.label.boundingRect().width(), y)
        self.squares.placeBelow(self.label)
Example #11
0
    def paintArc(self, painter, option, widget):
        assert self.source is self.dest
        node = self.source
        def best_angle():
            """...is the one furthest away from all other angles"""
            angles = [QLineF(node.pos(), other.pos()).angle()
                      for other in chain((edge.source for edge in node.edges
                                          if edge.dest == node and edge.source != node),
                                         (edge.dest for edge in node.edges
                                          if edge.dest != node and edge.source == node))]
            angles.sort()
            if not angles:  # If this self-constraint is the only edge
                return 225
            deltas = np.array(angles[1:] + [360 + angles[0]]) - angles
            return (angles[deltas.argmax()] + deltas.max()/2) % 360

        angle = best_angle()
        inf = QPointF(-1e20, -1e20)  # Doesn't work with real -np.inf!
        line0 = QLineF(node.pos(), inf)
        line1 = QLineF(node.pos(), inf)
        line2 = QLineF(node.pos(), inf)
        line0.setAngle(angle)
        line1.setAngle(angle - 13)
        line2.setAngle(angle + 13)

        p0 = shape_line_intersection(node.shape(), node.pos(), line0)
        p1 = shape_line_intersection(node.shape(), node.pos(), line1)
        p2 = shape_line_intersection(node.shape(), node.pos(), line2)
        path = QPainterPath()
        path.moveTo(p1)
        line = QLineF(node.pos(), p0)
        line.setLength(3*line.length())
        pt = line.p2()
        path.quadTo(pt, p2)

        line = QLineF(node.pos(), pt)
        self.setLine(line)  # This invalidates DeviceCoordinateCache
        painter.drawPath(path)

        # Draw arrow head
        line = QLineF(pt, p2)
        self.arrowHead.clear()
        for point in self._arrowhead_points(line):
            self.arrowHead.append(point)
        painter.setBrush(self.pen().color())
        painter.drawPolygon(self.arrowHead)

        # Update label position
        self.label.setPos(path.pointAtPercent(.5))
        if 90 < angle < 270:  # Right-align the label
            pos = self.label.pos()
            x, y = pos.x(), pos.y()
            self.label.setPos(x - self.label.boundingRect().width(), y)
        self.squares.placeBelow(self.label)
Example #12
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
Example #13
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
Example #14
0
    def adjustGeometry(self):
        """
        Adjust the widget geometry to exactly fit the arrow inside
        while preserving the arrow path scene geometry.

        """
        # local system coordinate
        geom = self.geometry().translated(-self.pos())
        line = self.__line

        arrow_rect = self.__arrowItem.shape().boundingRect()

        if geom.isNull() and not line.isNull():
            geom = QRectF(0, 0, 1, 1)

        if not (geom.contains(arrow_rect)):
            geom = geom.united(arrow_rect)

        geom = geom.intersected(arrow_rect)
        diff = geom.topLeft()
        line = QLineF(line.p1() - diff, line.p2() - diff)
        geom.translate(self.pos())
        self.setGeometry(geom)
        self.setLine(line)
Example #15
0
class ControlPointLine(QGraphicsObject):

    lineChanged = Signal(QLineF)
    lineEdited = Signal(QLineF)

    def __init__(self, parent=None, **kwargs):
        # type: (Optional[QGraphicsItem], Any) -> None
        super().__init__(parent, **kwargs)
        self.setFlag(QGraphicsItem.ItemHasNoContents)
        self.setFlag(QGraphicsItem.ItemIsFocusable)

        self.__line = QLineF()
        self.__points = [
            ControlPoint(self, ControlPoint.TopLeft,
                         cursor=Qt.DragMoveCursor),  # TopLeft is line start
            ControlPoint(self,
                         ControlPoint.BottomRight,
                         cursor=Qt.DragMoveCursor)  # line end
        ]
        self.__activeControl = None  # type: Optional[ControlPoint]

        if self.scene():
            self.__installFilter()

        for p in self.__points:
            p.setFlag(QGraphicsItem.ItemIsFocusable)
            p.setFocusProxy(self)

    def setLine(self, line):
        # type: (QLineF) -> None
        if not isinstance(line, QLineF):
            raise TypeError()

        if line != self.__line:
            self.__line = QLineF(line)
            self.__pointsLayout()
            self.lineChanged.emit(line)

    def line(self):
        # type: () -> QLineF
        return QLineF(self.__line)

    def isControlActive(self):
        # type: () -> bool
        """Return the state of the control. True if the control is
        active (user is dragging one of the points) False otherwise.
        """
        return self.__activeControl is not None

    def __installFilter(self):
        # type: () -> None
        for p in self.__points:
            p.installSceneEventFilter(self)

    def itemChange(self, change, value):
        # type: (QGraphicsItem.GraphicsItemChange, Any) -> Any
        if change == QGraphicsItem.ItemSceneHasChanged:
            if self.scene():
                self.__installFilter()
        return super().itemChange(change, value)

    def sceneEventFilter(self, obj, event):
        # type: (QGraphicsItem, QEvent) -> bool
        obj = toGraphicsObjectIfPossible(obj)
        if isinstance(obj, ControlPoint):
            etype = event.type()
            if etype in (QEvent.GraphicsSceneMousePress,
                         QEvent.GraphicsSceneMouseDoubleClick):
                self.__setActiveControl(obj)
            elif etype == QEvent.GraphicsSceneMouseRelease:
                self.__setActiveControl(None)
        return super().sceneEventFilter(obj, event)

    def __pointsLayout(self):
        # type: () -> None
        self.__points[0].setPos(self.__line.p1())
        self.__points[1].setPos(self.__line.p2())

    def __setActiveControl(self, control):
        # type: (Optional[ControlPoint]) -> None
        if self.__activeControl != control:
            if self.__activeControl is not None:
                self.__activeControl.positionChanged[QPointF].disconnect(
                    self.__activeControlMoved)

            self.__activeControl = control

            if control is not None:
                control.positionChanged[QPointF].connect(
                    self.__activeControlMoved)

    def __activeControlMoved(self, pos):
        # type: (QPointF) -> None
        line = QLineF(self.__line)
        control = self.__activeControl
        assert control is not None
        if control.anchor() == ControlPoint.TopLeft:
            line.setP1(pos)
        elif control.anchor() == ControlPoint.BottomRight:
            line.setP2(pos)

        if self.__line != line:
            self.blockSignals(True)
            self.setLine(line)
            self.blockSignals(False)
            self.lineEdited.emit(line)

    def boundingRect(self):
        # type: () -> QRectF
        return QRectF()
Example #16
0
class ControlPointLine(QGraphicsObject):

    lineChanged = Signal(QLineF)
    lineEdited = Signal(QLineF)

    def __init__(self, parent=None, **kwargs):
        QGraphicsObject.__init__(self, parent, **kwargs)
        self.setFlag(QGraphicsItem.ItemHasNoContents)
        self.setFlag(QGraphicsItem.ItemIsFocusable)

        self.__line = QLineF()
        self.__points = \
            [ControlPoint(self, ControlPoint.TopLeft),  # TopLeft is line start
             ControlPoint(self, ControlPoint.BottomRight)  # line end
             ]

        self.__activeControl = None

        if self.scene():
            self.__installFilter()

        for p in self.__points:
            p.setFlag(QGraphicsItem.ItemIsFocusable)
            p.setFocusProxy(self)

    def setLine(self, line):
        if not isinstance(line, QLineF):
            raise TypeError()

        if line != self.__line:
            self.__line = line
            self.__pointsLayout()
            self.lineChanged.emit(line)

    def line(self):
        return self.__line

    def isControlActive(self):
        """Return the state of the control. True if the control is
        active (user is dragging one of the points) False otherwise.

        """
        return self.__activeControl is not None

    def __installFilter(self):
        for p in self.__points:
            p.installSceneEventFilter(self)

    def itemChange(self, change, value):
        if change == QGraphicsItem.ItemSceneHasChanged:
            if self.scene():
                self.__installFilter()
        return QGraphicsObject.itemChange(self, change, value)

    def sceneEventFilter(self, obj, event):
        try:
            obj = toGraphicsObjectIfPossible(obj)
            if isinstance(obj, ControlPoint):
                etype = event.type()
                if etype == QEvent.GraphicsSceneMousePress:
                    self.__setActiveControl(obj)
                elif etype == QEvent.GraphicsSceneMouseRelease:
                    self.__setActiveControl(None)

            return QGraphicsObject.sceneEventFilter(self, obj, event)
        except Exception:
            log.error("", exc_info=True)

    def __pointsLayout(self):
        self.__points[0].setPos(self.__line.p1())
        self.__points[1].setPos(self.__line.p2())

    def __setActiveControl(self, control):
        if self.__activeControl != control:
            if self.__activeControl is not None:
                self.__activeControl.positionChanged[QPointF].disconnect(
                    self.__activeControlMoved
                )

            self.__activeControl = control

            if control is not None:
                control.positionChanged[QPointF].connect(
                    self.__activeControlMoved
                )

    def __activeControlMoved(self, pos):
        line = QLineF(self.__line)
        control = self.__activeControl
        if control.anchor() == ControlPoint.TopLeft:
            line.setP1(pos)
        elif control.anchor() == ControlPoint.BottomRight:
            line.setP2(pos)

        if self.__line != line:
            self.blockSignals(True)
            self.setLine(line)
            self.blockSignals(False)
            self.lineEdited.emit(line)

    def boundingRect(self):
        return QRectF()
Example #17
0
class ControlPointLine(QGraphicsObject):

    lineChanged = Signal(QLineF)
    lineEdited = Signal(QLineF)

    def __init__(self, parent=None, **kwargs):
        QGraphicsObject.__init__(self, parent, **kwargs)
        self.setFlag(QGraphicsItem.ItemHasNoContents)
        self.setFlag(QGraphicsItem.ItemIsFocusable)

        self.__line = QLineF()
        self.__points = [
            ControlPoint(self, ControlPoint.TopLeft),  # TopLeft is line start
            ControlPoint(self, ControlPoint.BottomRight),  # line end
        ]

        self.__activeControl = None

        if self.scene():
            self.__installFilter()

        for p in self.__points:
            p.setFlag(QGraphicsItem.ItemIsFocusable)
            p.setFocusProxy(self)

    def setLine(self, line):
        if not isinstance(line, QLineF):
            raise TypeError()

        if line != self.__line:
            self.__line = line
            self.__pointsLayout()
            self.lineChanged.emit(line)

    def line(self):
        return self.__line

    def isControlActive(self):
        """Return the state of the control. True if the control is
        active (user is dragging one of the points) False otherwise.

        """
        return self.__activeControl is not None

    def __installFilter(self):
        for p in self.__points:
            p.installSceneEventFilter(self)

    def itemChange(self, change, value):
        if change == QGraphicsItem.ItemSceneHasChanged:
            if self.scene():
                self.__installFilter()
        return QGraphicsObject.itemChange(self, change, value)

    def sceneEventFilter(self, obj, event):
        try:
            obj = toGraphicsObjectIfPossible(obj)
            if isinstance(obj, ControlPoint):
                etype = event.type()
                if etype == QEvent.GraphicsSceneMousePress:
                    self.__setActiveControl(obj)
                elif etype == QEvent.GraphicsSceneMouseRelease:
                    self.__setActiveControl(None)

            return QGraphicsObject.sceneEventFilter(self, obj, event)
        except Exception:
            log.error("", exc_info=True)

    def __pointsLayout(self):
        self.__points[0].setPos(self.__line.p1())
        self.__points[1].setPos(self.__line.p2())

    def __setActiveControl(self, control):
        if self.__activeControl != control:
            if self.__activeControl is not None:
                self.__activeControl.positionChanged[QPointF].disconnect(
                    self.__activeControlMoved)

            self.__activeControl = control

            if control is not None:
                control.positionChanged[QPointF].connect(
                    self.__activeControlMoved)

    def __activeControlMoved(self, pos):
        line = QLineF(self.__line)
        control = self.__activeControl
        if control.anchor() == ControlPoint.TopLeft:
            line.setP1(pos)
        elif control.anchor() == ControlPoint.BottomRight:
            line.setP2(pos)

        if self.__line != line:
            self.blockSignals(True)
            self.setLine(line)
            self.blockSignals(False)
            self.lineEdited.emit(line)

    def boundingRect(self):
        return QRectF()
class SliderLine(QGraphicsObject):
    """A movable slider line."""
    valueChanged = Signal(float)

    linePressed = Signal()
    lineMoved = Signal()
    lineReleased = Signal()
    rangeChanged = Signal(float, float)

    def __init__(self,
                 parent=None,
                 orientation=Qt.Vertical,
                 value=0.0,
                 length=10.0,
                 **kwargs):
        self._orientation = orientation
        self._value = value
        self._length = length
        self._min = 0.0
        self._max = 1.0
        self._line = QLineF()  # type: Optional[QLineF]
        self._pen = QPen()
        super().__init__(parent, **kwargs)

        self.setAcceptedMouseButtons(Qt.LeftButton)
        self.setPen(
            make_pen(brush=QColor(50, 50, 50),
                     width=1,
                     cosmetic=False,
                     style=Qt.DashLine))

        if self._orientation == Qt.Vertical:
            self.setCursor(Qt.SizeVerCursor)
        else:
            self.setCursor(Qt.SizeHorCursor)

    def setPen(self, pen: Union[QPen, Qt.GlobalColor, Qt.PenStyle]) -> None:
        pen = QPen(pen)
        if self._pen != pen:
            self.prepareGeometryChange()
            self._pen = pen
            self._line = None
            self.update()

    def pen(self) -> QPen:
        return QPen(self._pen)

    def setValue(self, value: float):
        value = min(max(value, self._min), self._max)

        if self._value != value:
            self.prepareGeometryChange()
            self._value = value
            self._line = None
            self.valueChanged.emit(value)

    def value(self) -> float:
        return self._value

    def setRange(self, minval: float, maxval: float) -> None:
        maxval = max(minval, maxval)
        if minval != self._min or maxval != self._max:
            self._min = minval
            self._max = maxval
            self.rangeChanged.emit(minval, maxval)
            self.setValue(self._value)

    def setLength(self, length: float):
        if self._length != length:
            self.prepareGeometryChange()
            self._length = length
            self._line = None

    def length(self) -> float:
        return self._length

    def setOrientation(self, orientation: Qt.Orientation):
        if self._orientation != orientation:
            self.prepareGeometryChange()
            self._orientation = orientation
            self._line = None
            if self._orientation == Qt.Vertical:
                self.setCursor(Qt.SizeVerCursor)
            else:
                self.setCursor(Qt.SizeHorCursor)

    def mousePressEvent(self, event: QGraphicsSceneMouseEvent) -> None:
        event.accept()
        self.linePressed.emit()

    def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent) -> None:
        pos = event.pos()
        if self._orientation == Qt.Vertical:
            self.setValue(pos.y())
        else:
            self.setValue(pos.x())
        self.lineMoved.emit()
        event.accept()

    def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent) -> None:
        if self._orientation == Qt.Vertical:
            self.setValue(event.pos().y())
        else:
            self.setValue(event.pos().x())
        self.lineReleased.emit()
        event.accept()

    def boundingRect(self) -> QRectF:
        if self._line is None:
            if self._orientation == Qt.Vertical:
                self._line = QLineF(0, self._value, self._length, self._value)
            else:
                self._line = QLineF(self._value, 0, self._value, self._length)
        r = QRectF(self._line.p1(), self._line.p2())
        penw = self.pen().width()
        return r.adjusted(-penw, -penw, penw, penw)

    def paint(self, painter, *args):
        if self._line is None:
            self.boundingRect()

        painter.save()
        painter.setPen(self.pen())
        painter.drawLine(self._line)
        painter.restore()