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)
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
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
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
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)
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)
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)
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)
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)
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
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)
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()
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 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()